"""GPR heatmap and object-only rendering helpers."""

from __future__ import annotations

from PyQt6.QtCore import QRectF
import numpy as np
import pyqtgraph as pg

from python_app.models.dataset_model import ResultCollection
from python_app.orchestration.gpr_locator import (
    collection_payload_by_name as gpr_collection_payload_by_name,
    collection_payloads_by_prefix as gpr_collection_payloads_by_prefix,
    gpr_object_rows as extract_gpr_object_rows,
)


class AppWindowGprPlotMixin:
    """Renders GPR accumulator heatmaps and detected object overlays."""

    def _gpr_plot_title(self, *, objects_only: bool = False) -> str:
        """Return title for the active GPR-like processing mode."""
        if self._processing_mode.currentText() == "legacy_gpr":
            title = f"Legacy GPR {self._legacy_gpr_config_mode.currentText()}"
        else:
            title = "GPR coherent BP"
        return f"{title} Objects Only" if objects_only else title

    def _clear_gpr_plot(self) -> None:
        """Clear latest GPR plot surface."""
        if not hasattr(self, "_gpr_plot"):
            return
        self._configure_gpr_plot_axes()
        self._clear_gpr_point_labels()
        self._clear_gpr_region_labels()
        self._clear_gpr_region_masks()
        if self._gpr_image_item is not None:
            self._gpr_image_item.hide()
        if self._gpr_tx_item is not None:
            self._gpr_tx_item.setData(x=[], y=[])
            self._gpr_tx_item.hide()
        if self._gpr_rx_item is not None:
            self._gpr_rx_item.setData(x=[], y=[])
            self._gpr_rx_item.hide()
        if self._gpr_points_item is not None:
            self._gpr_points_item.setData(x=[], y=[])
            self._gpr_points_item.hide()
        if self._gpr_region_centers_item is not None:
            self._gpr_region_centers_item.setData(x=[], y=[])
            self._gpr_region_centers_item.hide()
        self._gpr_plot.setTitle(self._gpr_plot_title())

    def _configure_gpr_plot_axes(self) -> None:
        """Apply persistent GPR plot axis labels and base view settings."""
        plot = self._gpr_plot
        plot_item = plot.getPlotItem()
        plot_item.showAxis("left", show=True)
        plot_item.showAxis("bottom", show=True)
        plot_item.setClipToView(True)
        plot.setLabel("bottom", "X", units="m")
        plot.setLabel("left", "Range", units="m")
        view_box = plot.getViewBox()
        view_box.invertY(False)
        view_box.enableAutoRange(x=False, y=False)
        self._disable_gpr_plot_interaction(plot, view_box)

    @staticmethod
    def _disable_gpr_plot_interaction(plot: pg.PlotWidget, view_box: pg.ViewBox) -> None:
        """Disable mouse-driven GPR pan/zoom; ranges are controlled by widgets."""
        AppWindowGprPlotMixin._call_if_present(plot, "setMouseEnabled", x=False, y=False)
        AppWindowGprPlotMixin._call_if_present(plot, "setMenuEnabled", False)
        AppWindowGprPlotMixin._call_if_present(plot.getPlotItem(), "hideButtons")
        AppWindowGprPlotMixin._call_if_present(view_box, "setMouseEnabled", x=False, y=False)
        AppWindowGprPlotMixin._call_if_present(view_box, "setMenuEnabled", False)
        for axis_name in ("bottom", "left"):
            AppWindowGprPlotMixin._call_if_present(
                plot.getPlotItem().getAxis(axis_name),
                "setMouseEnabled",
                False,
            )

    @staticmethod
    def _call_if_present(obj: object, method_name: str, *args, **kwargs) -> None:
        """Call optional pyqtgraph API when available in the installed version."""
        method = getattr(obj, method_name, None)
        if method is not None:
            method(*args, **kwargs)

    def _ensure_gpr_plot_items(self) -> None:
        """Create persistent GPR plot items once and reuse them on redraw."""
        if self._gpr_image_item is not None:
            return

        plot = self._gpr_plot
        self._configure_gpr_plot_axes()

        if self._gpr_lookup_table is None:
            self._gpr_lookup_table = self._build_lut(["#081c15", "#1b4332", "#ffd166", "#f94144"])

        self._gpr_image_item = pg.ImageItem(axisOrder="row-major")
        self._gpr_image_item.setZValue(0)
        self._gpr_image_item.hide()
        plot.addItem(self._gpr_image_item)

        self._gpr_tx_item = pg.ScatterPlotItem()
        self._gpr_tx_item.setZValue(20)
        self._gpr_tx_item.hide()
        plot.addItem(self._gpr_tx_item)

        self._gpr_rx_item = pg.ScatterPlotItem()
        self._gpr_rx_item.setZValue(20)
        self._gpr_rx_item.hide()
        plot.addItem(self._gpr_rx_item)

        self._gpr_points_item = pg.ScatterPlotItem()
        self._gpr_points_item.setZValue(30)
        self._gpr_points_item.hide()
        plot.addItem(self._gpr_points_item)

        self._gpr_region_centers_item = pg.ScatterPlotItem()
        self._gpr_region_centers_item.setZValue(30)
        self._gpr_region_centers_item.hide()
        plot.addItem(self._gpr_region_centers_item)

    def _clear_gpr_point_labels(self) -> None:
        """Remove dynamic point-score labels from GPR plot."""
        for item in self._gpr_point_labels:
            try:
                self._gpr_plot.removeItem(item)
            except Exception as exc:  # noqa: BLE001
                self._log_warning(
                    f"Failed to remove GPR point label: {type(exc).__name__}: {exc}",
                    once_key="gpr_remove_point_label_failed",
                )
        self._gpr_point_labels.clear()

    def _clear_gpr_region_labels(self) -> None:
        """Remove dynamic region labels from GPR plot."""
        for item in self._gpr_region_center_labels:
            try:
                self._gpr_plot.removeItem(item)
            except Exception as exc:  # noqa: BLE001
                self._log_warning(
                    f"Failed to remove GPR region label: {type(exc).__name__}: {exc}",
                    once_key="gpr_remove_region_label_failed",
                )
        self._gpr_region_center_labels.clear()

    def _clear_gpr_region_masks(self) -> None:
        """Remove dynamic region contour carriers from GPR plot."""
        for item in self._gpr_region_mask_items:
            try:
                self._gpr_plot.removeItem(item)
            except Exception as exc:  # noqa: BLE001
                self._log_warning(
                    f"Failed to remove GPR region mask: {type(exc).__name__}: {exc}",
                    once_key="gpr_remove_region_mask_failed",
                )
        self._gpr_region_mask_items.clear()
        self._gpr_region_contours.clear()

    @staticmethod
    def _collection_payload_by_name(collection: ResultCollection, name: str, kind: int | None = None):
        """Return first collection payload matching name and optional kind."""
        return gpr_collection_payload_by_name(collection, name, kind)

    @staticmethod
    def _collection_payloads_by_prefix(collection: ResultCollection, prefix: str, kind: int | None = None):
        """Return collection payloads matching processing-name prefix."""
        return gpr_collection_payloads_by_prefix(collection, prefix, kind)

    def _selected_gpr_geometry(self) -> tuple[np.ndarray, np.ndarray]:
        """Resolve selected Tx/Rx geometry arrays for current GPR selection."""
        if self._processing_mode.currentText() == "legacy_gpr":
            input_positions_text = self._legacy_gpr_input_positions_input.text()
            output_positions_text = self._legacy_gpr_output_positions_input.text()
        else:
            input_positions_text = self._gpr_input_positions_input.text()
            output_positions_text = self._gpr_output_positions_input.text()
        requested_inputs = tuple(self._parse_csv_int_list(input_positions_text))
        requested_outputs = tuple(self._parse_csv_int_list(output_positions_text))
        signature = (
            self._gpr_tx_geometry_input.toPlainText(),
            self._gpr_rx_geometry_input.toPlainText(),
            requested_inputs,
            requested_outputs,
        )
        if signature == self._gpr_geometry_signature and self._gpr_selected_geometry is not None:
            return self._gpr_selected_geometry

        tx_entries = self._parse_gpr_tx_geometry_text(signature[0])
        rx_entries = self._parse_gpr_rx_geometry_text(signature[1])
        requested_input_set = set(requested_inputs)
        requested_output_set = set(requested_outputs)

        rx_entries = sorted(rx_entries, key=lambda entry: int(entry.input_pos))
        tx_entries = sorted(tx_entries, key=lambda entry: int(entry.output_pos))
        if requested_input_set:
            rx_entries = [entry for entry in rx_entries if int(entry.input_pos) in requested_input_set]
        if requested_output_set:
            tx_entries = [entry for entry in tx_entries if int(entry.output_pos) in requested_output_set]

        x_tx = np.asarray([float(entry.x_m) for entry in tx_entries], dtype=np.float32)
        x_rx = np.asarray([float(entry.x_m) for entry in rx_entries], dtype=np.float32)
        self._gpr_geometry_signature = signature
        self._gpr_selected_geometry = (x_tx, x_rx)
        return self._gpr_selected_geometry

    def _draw_gpr_map(self, collection: ResultCollection) -> bool:
        """Draw latest collection-level GPR plot according to current render mode."""
        if self._gpr_render_mode_text() == "objects_only":
            return self._draw_gpr_objects_only(collection)
        return self._draw_gpr_heatmap(collection)

    def _draw_gpr_heatmap(self, collection: ResultCollection) -> bool:
        """Draw latest collection-level GPR accumulator and annotations."""
        accumulator_payload = self._collection_payload_by_name(collection, "gpr_accumulator", kind=3)
        if accumulator_payload is None:
            self._clear_gpr_plot()
            return False

        image = np.asarray(accumulator_payload.image, dtype=np.float32)
        x_axis = np.asarray(accumulator_payload.image_x_axis, dtype=np.float32)
        y_axis = np.asarray(accumulator_payload.image_y_axis, dtype=np.float32)
        if image.ndim != 2 or image.size == 0 or x_axis.size == 0 or y_axis.size == 0:
            self._clear_gpr_plot()
            return False

        x_min = float(x_axis[0])
        x_max = float(x_axis[-1])
        y_min = float(y_axis[0])
        y_max = float(y_axis[-1])
        rect = QRectF(x_min, y_min, max(x_max - x_min, 1e-6), max(y_max - y_min, 1e-6))

        plot = self._gpr_plot
        plot.setUpdatesEnabled(False)
        try:
            self._ensure_gpr_plot_items()
            plot.getViewBox().invertY(False)
            self._clear_gpr_point_labels()
            self._clear_gpr_region_labels()
            self._clear_gpr_region_masks()

            self._gpr_image_item.setImage(image, autoLevels=False)
            self._gpr_image_item.setRect(rect)
            self._gpr_image_item.setLookupTable(self._gpr_lookup_table)
            self._gpr_image_item.setLevels((float(np.min(image)), float(np.max(image) + 1e-6)))
            self._gpr_image_item.show()

            visible_x_min, visible_x_max, visible_z_min, visible_z_max = self._gpr_visible_bounds()
            plot.setXRange(visible_x_min, visible_x_max, padding=0.0)
            plot.setYRange(self._gpr_display_y_min(visible_z_min, visible_z_max), visible_z_max, padding=0.0)

            self._draw_gpr_geometry_markers()

            points_payload = self._collection_payload_by_name(collection, "gpr_points", kind=4)
            if points_payload is not None and np.asarray(points_payload.table).size > 0:
                points = (
                    self._filtered_gpr_object_rows(collection)
                    if self._processing_mode.currentText() == "gpr"
                    else np.asarray(points_payload.table, dtype=np.float32)
                )
            else:
                points = np.zeros((0, 3), dtype=np.float32)

            if points.size > 0:
                self._gpr_points_item.setData(
                    x=points[:, 0],
                    y=points[:, 1],
                    symbol="d",
                    size=11,
                    brush=pg.mkBrush("#ffffff"),
                    pen=pg.mkPen("#111111", width=1.1),
                )
                self._gpr_points_item.show()
                for x_value, y_value, score in points:
                    label_text = f"{float(score):.0f}" if self._processing_mode.currentText() == "legacy_gpr" else f"{float(score):.2f}"
                    label = pg.TextItem(text=label_text, color="#ffffff", anchor=(0.0, 1.0))
                    label.setZValue(40)
                    label.setPos(float(x_value), float(y_value))
                    plot.addItem(label)
                    self._gpr_point_labels.append(label)
            else:
                self._gpr_points_item.setData(x=[], y=[])
                self._gpr_points_item.hide()

            region_centers_payload = self._collection_payload_by_name(collection, "gpr_region_centers", kind=4)
            if region_centers_payload is not None and np.asarray(region_centers_payload.table).size > 0:
                centers = np.asarray(region_centers_payload.table, dtype=np.float32)
                self._gpr_region_centers_item.setData(
                    x=centers[:, 0],
                    y=centers[:, 1],
                    symbol="o",
                    size=10,
                    brush=pg.mkBrush("#80ed99"),
                    pen=pg.mkPen("#081c15", width=1.1),
                )
                self._gpr_region_centers_item.show()
                for row in centers:
                    label = pg.TextItem(text=f"{float(row[2]):.0f}", color="#d8f3dc", anchor=(0.0, 1.0))
                    label.setZValue(40)
                    label.setPos(float(row[0]), float(row[1]))
                    plot.addItem(label)
                    self._gpr_region_center_labels.append(label)
            else:
                self._gpr_region_centers_item.setData(x=[], y=[])
                self._gpr_region_centers_item.hide()

            for payload in self._collection_payloads_by_prefix(collection, "gpr_region_mask_", kind=3):
                mask = np.asarray(payload.image, dtype=np.float32)
                if mask.ndim != 2 or mask.size == 0:
                    continue
                mask_image = pg.ImageItem(axisOrder="row-major")
                mask_image.setZValue(5)
                mask_image.setImage(mask, autoLevels=False)
                mask_image.setRect(rect)
                mask_image.setOpacity(0.0)
                plot.addItem(mask_image)
                contour = pg.IsocurveItem(data=mask, level=0.5, pen=pg.mkPen("#4cc9f0", width=1.3))
                contour.setParentItem(mask_image)
                self._gpr_region_mask_items.append(mask_image)
                self._gpr_region_contours.append(contour)

            plot.setTitle(self._gpr_plot_title())
        finally:
            plot.setUpdatesEnabled(True)
        return True

    @staticmethod
    def _normalized_display_range(start: float, stop: float, *, minimum_span: float = 0.1) -> tuple[float, float]:
        """Return ordered display bounds with a non-zero span."""
        lower = min(float(start), float(stop))
        upper = max(float(start), float(stop))
        if upper - lower >= minimum_span:
            return lower, upper
        center = 0.5 * (lower + upper)
        half_span = 0.5 * minimum_span
        return center - half_span, center + half_span

    def _gpr_visible_bounds(self) -> tuple[float, float, float, float]:
        """Return normalized GPR visible X/Z bounds from GUI controls."""
        if self._processing_mode.currentText() == "legacy_gpr":
            x_min_widget = self._legacy_gpr_visible_x_min_m
            x_max_widget = self._legacy_gpr_visible_x_max_m
            z_min_widget = self._legacy_gpr_visible_z_min_m
            z_max_widget = self._legacy_gpr_visible_z_max_m
        else:
            x_min_widget = self._gpr_visible_x_min_m
            x_max_widget = self._gpr_visible_x_max_m
            z_min_widget = self._gpr_visible_z_min_m
            z_max_widget = self._gpr_visible_z_max_m
        x_min, x_max = self._normalized_display_range(
            float(x_min_widget.value()),
            float(x_max_widget.value()),
            minimum_span=0.1,
        )
        z_min, z_max = self._normalized_display_range(
            float(z_min_widget.value()),
            float(z_max_widget.value()),
            minimum_span=0.1,
        )
        return x_min, x_max, z_min, z_max

    def _gpr_visible_object_bounds(self) -> tuple[float, float, float, float]:
        """Return normalized object/locator visible X/Z bounds from GUI controls."""
        return self._gpr_visible_bounds()

    def _gpr_render_mode_text(self) -> str:
        """Return render mode for the active GPR-like processing mode."""
        if self._processing_mode.currentText() == "legacy_gpr":
            return self._legacy_gpr_render_mode.currentText()
        return self._gpr_render_mode.currentText()

    def _gpr_locator_threshold(self) -> float:
        """Return object threshold using the active GPR mode's score semantics."""
        if self._processing_mode.currentText() == "legacy_gpr":
            return float(self._legacy_gpr_min_visible_pair_count.value())
        return float(self._gpr_min_visible_score.value())

    def _gpr_draw_limits(self) -> tuple[int, int] | None:
        """Return GPR object draw limits, or None for legacy GPR."""
        if self._processing_mode.currentText() == "legacy_gpr":
            return None
        return (
            int(self._gpr_max_detected_objects_to_draw.value()),
            int(self._gpr_draw_top_m_objects.value()),
        )

    @staticmethod
    def _apply_object_draw_limits(rows: np.ndarray, limits: tuple[int, int] | None) -> np.ndarray:
        """Apply object count/top-M drawing rules to already-filtered rows."""
        if limits is None or rows.size == 0:
            return rows

        max_detected_objects, draw_top_objects = limits
        if rows.shape[0] > int(max_detected_objects):
            return np.zeros((0, rows.shape[1]), dtype=rows.dtype)
        return rows[: max(0, int(draw_top_objects))]

    @staticmethod
    def _gpr_display_y_min(z_min: float, z_max: float) -> float:
        """Return lower display bound, preserving surface markers only when surface is visible."""
        lower = float(z_min)
        if lower > 0.0:
            return lower
        span = max(float(z_max) - lower, 1e-6)
        marker_margin = max(span * 0.03, 0.06)
        return lower - marker_margin

    def _draw_gpr_geometry_markers(self) -> None:
        """Render selected Tx/Rx geometry markers on current GPR plot."""
        x_tx, x_rx = self._selected_gpr_geometry()
        if x_tx.size > 0:
            self._gpr_tx_item.setData(
                x=x_tx,
                y=np.zeros_like(x_tx),
                symbol="t",
                size=13,
                brush=pg.mkBrush("#ff595e"),
                pen=pg.mkPen("#ffca3a", width=1.0),
            )
            self._gpr_tx_item.show()
        else:
            self._gpr_tx_item.setData(x=[], y=[])
            self._gpr_tx_item.hide()

        if x_rx.size > 0:
            self._gpr_rx_item.setData(
                x=x_rx,
                y=np.zeros_like(x_rx),
                symbol="t1",
                size=13,
                brush=pg.mkBrush("#4cc9f0"),
                pen=pg.mkPen("#e0fbfc", width=1.0),
            )
            self._gpr_rx_item.show()
        else:
            self._gpr_rx_item.setData(x=[], y=[])
            self._gpr_rx_item.hide()

    def _format_gpr_object_label(self, x_m: float, z_m: float, score: float) -> str:
        """Format object-only annotation text with normalized BP score and coordinates."""
        if self._processing_mode.currentText() == "legacy_gpr":
            return f"{int(round(score))} | x={x_m:.1f} | z={z_m:.1f}"
        return f"{score:.2f} | x={x_m:.1f} | z={z_m:.1f}"

    @staticmethod
    def _expanded_scene_rect(rect: QRectF, *, padding_px: float = 4.0) -> QRectF:
        """Return scene rect padded to keep labels visually separated."""
        return rect.adjusted(-padding_px, -padding_px, padding_px, padding_px)

    @staticmethod
    def _scene_rect_intersects_any(rect: QRectF, occupied_rects: list[QRectF]) -> bool:
        """Return whether candidate label rect intersects any already placed label."""
        return any(rect.intersects(occupied_rect) for occupied_rect in occupied_rects)

    @staticmethod
    def _gpr_object_label_candidates(
        x_m: float,
        z_m: float,
        *,
        x_span: float,
        z_span: float,
    ) -> list[tuple[float, float, tuple[float, float]]]:
        """Return candidate label placements around one object."""
        x_offset = max(x_span * 0.015, 0.02)
        z_offset = max(z_span * 0.02, 0.02)
        return [
            (x_m + x_offset, z_m - z_offset, (0.0, 1.0)),
            (x_m + x_offset, z_m + z_offset, (0.0, 0.0)),
            (x_m - x_offset, z_m - z_offset, (1.0, 1.0)),
            (x_m - x_offset, z_m + z_offset, (1.0, 0.0)),
            (x_m, z_m - (z_offset * 1.35), (0.5, 1.0)),
            (x_m, z_m + (z_offset * 1.35), (0.5, 0.0)),
            (x_m + (x_offset * 2.2), z_m - (z_offset * 1.5), (0.0, 1.0)),
            (x_m + (x_offset * 2.2), z_m + (z_offset * 1.5), (0.0, 0.0)),
            (x_m - (x_offset * 2.2), z_m - (z_offset * 1.5), (1.0, 1.0)),
            (x_m - (x_offset * 2.2), z_m + (z_offset * 1.5), (1.0, 0.0)),
        ]

    def _place_gpr_object_label(
        self,
        *,
        label: pg.TextItem,
        x_m: float,
        z_m: float,
        x_span: float,
        z_span: float,
        occupied_scene_rects: list[QRectF],
    ) -> None:
        """Place one object label using the first non-overlapping candidate position."""
        last_rect: QRectF | None = None
        for label_x, label_z, anchor in self._gpr_object_label_candidates(
            x_m,
            z_m,
            x_span=x_span,
            z_span=z_span,
        ):
            label.setAnchor(anchor)
            label.setPos(label_x, label_z)
            candidate_rect = self._expanded_scene_rect(label.sceneBoundingRect())
            last_rect = candidate_rect
            if not self._scene_rect_intersects_any(candidate_rect, occupied_scene_rects):
                occupied_scene_rects.append(candidate_rect)
                return

        if last_rect is not None:
            occupied_scene_rects.append(last_rect)

    def _gpr_object_rows(self, collection: ResultCollection) -> np.ndarray:
        """Return object rows as `[x_m, z_m, score]` from current GPR result payload."""
        return extract_gpr_object_rows(collection)

    def _filtered_gpr_object_rows(self, collection: ResultCollection) -> np.ndarray:
        """Return object rows filtered by threshold, visible X/Z bounds, and active GPR draw limits."""
        rows = self._gpr_object_rows(collection)
        if rows.size == 0:
            return rows

        x_min, x_max, z_min, z_max = self._gpr_visible_object_bounds()
        min_score = self._gpr_locator_threshold()
        finite_mask = np.all(np.isfinite(rows[:, :3]), axis=1)
        visible_mask = (
            finite_mask
            & (rows[:, 2] >= min_score)
            & (rows[:, 0] >= x_min)
            & (rows[:, 0] <= x_max)
            & (rows[:, 1] >= z_min)
            & (rows[:, 1] <= z_max)
        )
        return self._apply_object_draw_limits(rows[visible_mask], self._gpr_draw_limits())

    def _draw_gpr_objects_only(self, collection: ResultCollection) -> bool:
        """Draw only detected GPR objects inside configured X/Z bounds."""
        accumulator_payload = self._collection_payload_by_name(collection, "gpr_accumulator", kind=3)
        object_rows = self._filtered_gpr_object_rows(collection)
        if accumulator_payload is None and object_rows.size == 0:
            self._clear_gpr_plot()
            return False

        x_min, x_max, z_min, z_max = self._gpr_visible_object_bounds()
        plot = self._gpr_plot
        plot.setUpdatesEnabled(False)
        try:
            self._ensure_gpr_plot_items()
            plot.getViewBox().invertY(False)
            self._clear_gpr_point_labels()
            self._clear_gpr_region_labels()
            self._clear_gpr_region_masks()

            self._gpr_image_item.hide()
            self._draw_gpr_geometry_markers()
            self._gpr_region_centers_item.setData(x=[], y=[])
            self._gpr_region_centers_item.hide()

            if object_rows.size > 0:
                self._gpr_points_item.setData(
                    x=object_rows[:, 0],
                    y=object_rows[:, 1],
                    symbol="o",
                    size=18,
                    brush=pg.mkBrush("#ff4d4f"),
                    pen=pg.mkPen("#ff4d4f", width=1.6),
                )
                self._gpr_points_item.show()

                occupied_scene_rects: list[QRectF] = []
                x_span = x_max - x_min
                z_span = z_max - z_min
                for x_value, z_value, score in object_rows:
                    label = pg.TextItem(
                        text=self._format_gpr_object_label(
                            float(x_value),
                            float(z_value),
                            float(score),
                        ),
                        color="#ffd6d9",
                        anchor=(0.0, 1.0),
                    )
                    label.setZValue(40)
                    plot.addItem(label)
                    self._place_gpr_object_label(
                        label=label,
                        x_m=float(x_value),
                        z_m=float(z_value),
                        x_span=x_span,
                        z_span=z_span,
                        occupied_scene_rects=occupied_scene_rects,
                    )
                    self._gpr_point_labels.append(label)
            else:
                self._gpr_points_item.setData(x=[], y=[])
                self._gpr_points_item.hide()

            plot.setXRange(x_min, x_max, padding=0.0)
            plot.setYRange(self._gpr_display_y_min(z_min, z_max), z_max, padding=0.0)
            plot.setTitle(self._gpr_plot_title(objects_only=True))
        finally:
            plot.setUpdatesEnabled(True)
        return True
