{
    "file_diffs": [
        {
            "old_file_content": "from itertools import chain\nfrom contextlib import contextmanager\n\nimport typing\nfrom typing import Any, List, Tuple, Dict, Optional, Set, Union\n\nimport numpy as np\n\nfrom AnyQt.QtWidgets import (\n    QGraphicsWidget, QGraphicsObject, QGraphicsScene, QGridLayout, QSizePolicy,\n    QAction, QComboBox, QGraphicsGridLayout, QGraphicsSceneMouseEvent\n)\nfrom AnyQt.QtGui import QColor, QPen, QFont, QKeySequence\nfrom AnyQt.QtCore import Qt, QSize, QSizeF, QPointF, QRectF, QLineF, QEvent\nfrom AnyQt.QtCore import pyqtSignal as Signal, pyqtSlot as Slot\n\nimport pyqtgraph as pg\n\nimport Orange.data\nfrom Orange.data.domain import filter_visible\nfrom Orange.data import Domain\nimport Orange.misc\nfrom Orange.clustering.hierarchical import \\\n    postorder, preorder, Tree, tree_from_linkage, dist_matrix_linkage, \\\n    leaves, prune, top_clusters\nfrom Orange.data.util import get_unique_names\n\nfrom Orange.widgets import widget, gui, settings\nfrom Orange.widgets.utils import itemmodels, combobox\nfrom Orange.widgets.utils.annotated_data import (create_annotated_table,\n                                                 ANNOTATED_DATA_SIGNAL_NAME)\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.widget import Input, Output, Msg\n\nfrom Orange.widgets.utils.stickygraphicsview import StickyGraphicsView\nfrom Orange.widgets.utils.graphicstextlist import TextListWidget\nfrom Orange.widgets.utils.dendrogram import DendrogramWidget\n\n__all__ = [\"OWHierarchicalClustering\"]\n\n\nLINKAGE = [\"Single\", \"Average\", \"Weighted\", \"Complete\", \"Ward\"]\n\n\ndef make_pen(brush=Qt.black, width=1, style=Qt.SolidLine,\n             cap_style=Qt.SquareCap, join_style=Qt.BevelJoin,\n             cosmetic=False):\n    pen = QPen(brush)\n    pen.setWidth(width)\n    pen.setStyle(style)\n    pen.setCapStyle(cap_style)\n    pen.setJoinStyle(join_style)\n    pen.setCosmetic(cosmetic)\n    return pen\n\n\n@contextmanager\ndef blocked(obj):\n    old = obj.signalsBlocked()\n    obj.blockSignals(True)\n    try:\n        yield obj\n    finally:\n        obj.blockSignals(old)\n\n\nclass SaveStateSettingsHandler(settings.SettingsHandler):\n    \"\"\"\n    A settings handler that delegates session data store/restore to the\n    OWWidget instance.\n\n    The OWWidget subclass must implement `save_state() -> Dict[str, Any]` and\n    `set_restore_state(state: Dict[str, Any])` methods.\n    \"\"\"\n    def initialize(self, instance, data=None):\n        super().initialize(instance, data)\n        if data is not None and \"__session_state_data\" in data:\n            session_data = data[\"__session_state_data\"]\n            instance.set_restore_state(session_data)\n\n    def pack_data(self, widget):\n        # type: (widget.OWWidget) -> dict\n        res = super().pack_data(widget)\n        state = widget.save_state()\n        if state:\n            assert \"__session_state_data\" not in res\n            res[\"__session_state_data\"] = state\n        return res\n\n\nclass _DomainContextHandler(settings.DomainContextHandler,\n                            SaveStateSettingsHandler):\n    pass\n\n\nif typing.TYPE_CHECKING:\n    #: Encoded selection state for persistent storage.\n    #: This is a list of tuples of leaf indices in the selection and\n    #: a (N, 3) linkage matrix for validation (the 4-th column from scipy\n    #: is omitted).\n    SelectionState = Tuple[List[Tuple[int]], List[Tuple[int, int, float]]]\n\n\nclass OWHierarchicalClustering(widget.OWWidget):\n    name = \"Hierarchical Clustering\"\n    description = \"Display a dendrogram of a hierarchical clustering \" \\\n                  \"constructed from the input distance matrix.\"\n    icon = \"icons/HierarchicalClustering.svg\"\n    priority = 2100\n    keywords = []\n\n    class Inputs:\n        distances = Input(\"Distances\", Orange.misc.DistMatrix)\n\n    class Outputs:\n        selected_data = Output(\"Selected Data\", Orange.data.Table, default=True)\n        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table)\n\n    settingsHandler = _DomainContextHandler()\n\n    #: Selected linkage\n    linkage = settings.Setting(1)\n    #: Index of the selected annotation item (variable, ...)\n    annotation = settings.ContextSetting(\"Enumeration\")\n    #: Out-of-context setting for the case when the \"Name\" option is available\n    annotation_if_names = settings.Setting(\"Name\")\n    #: Out-of-context setting for the case with just \"Enumerate\" and \"None\"\n    annotation_if_enumerate = settings.Setting(\"Enumerate\")\n    #: Selected tree pruning (none/max depth)\n    pruning = settings.Setting(0)\n    #: Maximum depth when max depth pruning is selected\n    max_depth = settings.Setting(10)\n\n    #: Selected cluster selection method (none, cut distance, top n)\n    selection_method = settings.Setting(0)\n    #: Cut height ratio wrt root height\n    cut_ratio = settings.Setting(75.0)\n    #: Number of top clusters to select\n    top_n = settings.Setting(3)\n    #: Dendrogram zoom factor\n    zoom_factor = settings.Setting(0)\n\n    autocommit = settings.Setting(True)\n\n    graph_name = \"scene\"\n\n    basic_annotations = [\"None\", \"Enumeration\"]\n\n    class Error(widget.OWWidget.Error):\n        not_finite_distances = Msg(\"Some distances are infinite\")\n\n    #: Stored (manual) selection state (from a saved workflow) to restore.\n    __pending_selection_restore = None  # type: Optional[SelectionState]\n\n    def __init__(self):\n        super().__init__()\n\n        self.matrix = None\n        self.items = None\n        self.linkmatrix = None\n        self.root = None\n        self._displayed_root = None\n        self.cutoff_height = 0.0\n\n        gui.comboBox(\n            self.controlArea, self, \"linkage\", items=LINKAGE, box=\"Linkage\",\n            callback=self._invalidate_clustering)\n\n        model = itemmodels.VariableListModel()\n        model[:] = self.basic_annotations\n\n        box = gui.widgetBox(self.controlArea, \"Annotations\")\n        self.label_cb = cb = combobox.ComboBoxSearch(\n            minimumContentsLength=14,\n            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon\n        )\n        cb.setModel(model)\n        cb.setCurrentIndex(cb.findData(self.annotation, Qt.EditRole))\n\n        def on_annotation_activated():\n            self.annotation = cb.currentData(Qt.EditRole)\n            self._update_labels()\n        cb.activated.connect(on_annotation_activated)\n\n        def on_annotation_changed(value):\n            cb.setCurrentIndex(cb.findData(value, Qt.EditRole))\n        self.connect_control(\"annotation\", on_annotation_changed)\n\n        box.layout().addWidget(self.label_cb)\n\n        box = gui.radioButtons(\n            self.controlArea, self, \"pruning\", box=\"Pruning\",\n            callback=self._invalidate_pruning)\n        grid = QGridLayout()\n        box.layout().addLayout(grid)\n        grid.addWidget(\n            gui.appendRadioButton(box, \"None\", addToLayout=False),\n            0, 0\n        )\n        self.max_depth_spin = gui.spin(\n            box, self, \"max_depth\", minv=1, maxv=100,\n            callback=self._invalidate_pruning,\n            keyboardTracking=False, addToLayout=False\n        )\n\n        grid.addWidget(\n            gui.appendRadioButton(box, \"Max depth:\", addToLayout=False),\n            1, 0)\n        grid.addWidget(self.max_depth_spin, 1, 1)\n\n        self.selection_box = gui.radioButtons(\n            self.controlArea, self, \"selection_method\",\n            box=\"Selection\",\n            callback=self._selection_method_changed)\n\n        grid = QGridLayout()\n        self.selection_box.layout().addLayout(grid)\n        grid.addWidget(\n            gui.appendRadioButton(\n                self.selection_box, \"Manual\", addToLayout=False),\n            0, 0\n        )\n        grid.addWidget(\n            gui.appendRadioButton(\n                self.selection_box, \"Height ratio:\", addToLayout=False),\n            1, 0\n        )\n        self.cut_ratio_spin = gui.spin(\n            self.selection_box, self, \"cut_ratio\", 0, 100, step=1e-1,\n            spinType=float, callback=self._selection_method_changed,\n            addToLayout=False\n        )\n        self.cut_ratio_spin.setSuffix(\"%\")\n\n        grid.addWidget(self.cut_ratio_spin, 1, 1)\n\n        grid.addWidget(\n            gui.appendRadioButton(\n                self.selection_box, \"Top N:\", addToLayout=False),\n            2, 0\n        )\n        self.top_n_spin = gui.spin(self.selection_box, self, \"top_n\", 1, 20,\n                                   callback=self._selection_method_changed,\n                                   addToLayout=False)\n        grid.addWidget(self.top_n_spin, 2, 1)\n\n        self.zoom_slider = gui.hSlider(\n            self.controlArea, self, \"zoom_factor\", box=\"Zoom\",\n            minValue=-6, maxValue=3, step=1, ticks=True, createLabel=False,\n            callback=self.__update_font_scale)\n\n        zoom_in = QAction(\n            \"Zoom in\", self, shortcut=QKeySequence.ZoomIn,\n            triggered=self.__zoom_in\n        )\n        zoom_out = QAction(\n            \"Zoom out\", self, shortcut=QKeySequence.ZoomOut,\n            triggered=self.__zoom_out\n        )\n        zoom_reset = QAction(\n            \"Reset zoom\", self,\n            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0),\n            triggered=self.__zoom_reset\n        )\n        self.addActions([zoom_in, zoom_out, zoom_reset])\n\n        self.controlArea.layout().addStretch()\n\n        gui.auto_send(self.buttonsArea, self, \"autocommit\")\n\n        self.scene = QGraphicsScene(self)\n        self.view = StickyGraphicsView(\n            self.scene,\n            horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff,\n            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn,\n            alignment=Qt.AlignLeft | Qt.AlignVCenter\n        )\n        self.mainArea.layout().setSpacing(1)\n        self.mainArea.layout().addWidget(self.view)\n\n        def axis_view(orientation):\n            ax = AxisItem(orientation=orientation, maxTickLength=7)\n            ax.mousePressed.connect(self._activate_cut_line)\n            ax.mouseMoved.connect(self._activate_cut_line)\n            ax.mouseReleased.connect(self._activate_cut_line)\n            ax.setRange(1.0, 0.0)\n            return ax\n\n        self.top_axis = axis_view(\"top\")\n        self.bottom_axis = axis_view(\"bottom\")\n\n        self._main_graphics = QGraphicsWidget()\n        scenelayout = QGraphicsGridLayout()\n        scenelayout.setHorizontalSpacing(10)\n        scenelayout.setVerticalSpacing(10)\n\n        self._main_graphics.setLayout(scenelayout)\n        self.scene.addItem(self._main_graphics)\n\n        self.dendrogram = DendrogramWidget()\n        self.dendrogram.setSizePolicy(QSizePolicy.MinimumExpanding,\n                                      QSizePolicy.MinimumExpanding)\n        self.dendrogram.selectionChanged.connect(self._invalidate_output)\n        self.dendrogram.selectionEdited.connect(self._selection_edited)\n\n        self.labels = TextListWidget()\n        self.labels.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)\n        self.labels.setAlignment(Qt.AlignLeft)\n        self.labels.setMaximumWidth(200)\n\n        scenelayout.addItem(self.top_axis, 0, 0,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        scenelayout.addItem(self.dendrogram, 1, 0,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        scenelayout.addItem(self.labels, 1, 1,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        scenelayout.addItem(self.bottom_axis, 2, 0,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        self.view.viewport().installEventFilter(self)\n        self._main_graphics.installEventFilter(self)\n\n        self.top_axis.setZValue(self.dendrogram.zValue() + 10)\n        self.bottom_axis.setZValue(self.dendrogram.zValue() + 10)\n        self.cut_line = SliderLine(self.top_axis,\n                                   orientation=Qt.Horizontal)\n        self.cut_line.valueChanged.connect(self._dendrogram_slider_changed)\n        self.dendrogram.geometryChanged.connect(self._dendrogram_geom_changed)\n        self._set_cut_line_visible(self.selection_method == 1)\n        self.__update_font_scale()\n\n    @Inputs.distances\n    def set_distances(self, matrix):\n        if self.__pending_selection_restore is not None:\n            selection_state = self.__pending_selection_restore\n        else:\n            # save the current selection to (possibly) restore later\n            selection_state = self._save_selection()\n\n        self.error()\n        self.Error.clear()\n        if matrix is not None:\n            N, _ = matrix.shape\n            if N < 2:\n                self.error(\"Empty distance matrix\")\n                matrix = None\n        if matrix is not None:\n            if not np.all(np.isfinite(matrix)):\n                self.Error.not_finite_distances()\n                matrix = None\n\n        self.matrix = matrix\n        if matrix is not None:\n            self._set_items(matrix.row_items, matrix.axis)\n        else:\n            self._set_items(None)\n        self._invalidate_clustering()\n\n        # Can now attempt to restore session state from a saved workflow.\n        if self.root and selection_state is not None:\n            self._restore_selection(selection_state)\n            self.__pending_selection_restore = None\n\n        self.unconditional_commit()\n\n    def _set_items(self, items, axis=1):\n        self.closeContext()\n        self.items = items\n        model = self.label_cb.model()\n        if len(model) == 3:\n            self.annotation_if_names = self.annotation\n        elif len(model) == 2:\n            self.annotation_if_enumerate = self.annotation\n        if isinstance(items, Orange.data.Table) and axis:\n            model[:] = chain(\n                self.basic_annotations,\n                [model.Separator],\n                items.domain.class_vars,\n                items.domain.metas,\n                [model.Separator] if (items.domain.class_vars or items.domain.metas) and\n                next(filter_visible(items.domain.attributes), False) else [],\n                filter_visible(items.domain.attributes)\n            )\n            if items.domain.class_vars:\n                self.annotation = items.domain.class_vars[0]\n            else:\n                self.annotation = \"Enumeration\"\n            self.openContext(items.domain)\n        else:\n            name_option = bool(\n                items is not None and (\n                    not axis or\n                    isinstance(items, list) and\n                    all(isinstance(var, Orange.data.Variable) for var in items)))\n            model[:] = self.basic_annotations + [\"Name\"] * name_option\n            self.annotation = self.annotation_if_names if name_option \\\n                else self.annotation_if_enumerate\n\n    def _clear_plot(self):\n        self.dendrogram.set_root(None)\n        self.labels.setItems([])\n\n    def _set_displayed_root(self, root):\n        self._clear_plot()\n        self._displayed_root = root\n        self.dendrogram.set_root(root)\n\n        self._update_labels()\n\n        self._main_graphics.resize(\n            self._main_graphics.size().width(),\n            self._main_graphics.sizeHint(Qt.PreferredSize).height()\n        )\n        self._main_graphics.layout().activate()\n\n    def _update(self):\n        self._clear_plot()\n\n        distances = self.matrix\n\n        if distances is not None:\n            method = LINKAGE[self.linkage].lower()\n            Z = dist_matrix_linkage(distances, linkage=method)\n\n            tree = tree_from_linkage(Z)\n            self.linkmatrix = Z\n            self.root = tree\n\n            self.top_axis.setRange(tree.value.height, 0.0)\n            self.bottom_axis.setRange(tree.value.height, 0.0)\n\n            if self.pruning:\n                self._set_displayed_root(prune(tree, level=self.max_depth))\n            else:\n                self._set_displayed_root(tree)\n        else:\n            self.linkmatrix = None\n            self.root = None\n            self._set_displayed_root(None)\n\n        self._apply_selection()\n\n    def _update_labels(self):\n        labels = []\n        if self.root and self._displayed_root:\n            indices = [leaf.value.index for leaf in leaves(self.root)]\n\n            if self.annotation == \"None\":\n                labels = []\n            elif self.annotation == \"Enumeration\":\n                labels = [str(i+1) for i in indices]\n            elif self.annotation == \"Name\":\n                attr = self.matrix.row_items.domain.attributes\n                labels = [str(attr[i]) for i in indices]\n            elif isinstance(self.annotation, Orange.data.Variable):\n                col_data, _ = self.items.get_column_view(self.annotation)\n                labels = [self.annotation.str_val(val) for val in col_data]\n                labels = [labels[idx] for idx in indices]\n            else:\n                labels = []\n\n            if labels and self._displayed_root is not self.root:\n                joined = leaves(self._displayed_root)\n                labels = [\", \".join(labels[leaf.value.first: leaf.value.last])\n                          for leaf in joined]\n\n        self.labels.setItems(labels)\n        self.labels.setMinimumWidth(1 if labels else -1)\n\n    def _restore_selection(self, state):\n        # type: (SelectionState) -> bool\n        \"\"\"\n        Restore the (manual) node selection state.\n\n        Return True if successful; False otherwise.\n        \"\"\"\n        linkmatrix = self.linkmatrix\n        if self.selection_method == 0 and self.root:\n            selected, linksaved = state\n            linkstruct = np.array(linksaved, dtype=float)\n            selected = set(selected)  # type: Set[Tuple[int]]\n            if not selected:\n                return False\n            if linkmatrix.shape[0] != linkstruct.shape[0]:\n                return False\n            # check that the linkage matrix structure matches. Use isclose for\n            # the height column to account for inexact floating point math\n            # (e.g. summation order in different ?gemm implementations for\n            # euclidean distances, ...)\n            if np.any(linkstruct[:, :2] != linkmatrix[:, :2]) or \\\n                    not np.all(np.isclose(linkstruct[:, 2], linkstruct[:, 2])):\n                return False\n            selection = []\n            indices = np.array([n.value.index for n in leaves(self.root)],\n                               dtype=int)\n            # mapping from ranges to display (pruned) nodes\n            mapping = {node.value.range: node\n                       for node in postorder(self._displayed_root)}\n            for node in postorder(self.root):  # type: Tree\n                r = tuple(indices[node.value.first: node.value.last])\n                if r in selected:\n                    if node.value.range not in mapping:\n                        # the node was pruned from display and cannot be\n                        # selected\n                        break\n                    selection.append(mapping[node.value.range])\n                    selected.remove(r)\n                if not selected:\n                    break  # found all, nothing more to do\n            if selection and selected:\n                # Could not restore all selected nodes (only partial match)\n                return False\n\n            self._set_selected_nodes(selection)\n            return True\n        return False\n\n    def _set_selected_nodes(self, selection):\n        # type: (List[Tree]) -> None\n        \"\"\"\n        Set the nodes in `selection` to be the current selected nodes.\n\n        The selection nodes must be subtrees of the current `_displayed_root`.\n        \"\"\"\n        self.dendrogram.selectionChanged.disconnect(self._invalidate_output)\n        try:\n            self.dendrogram.set_selected_clusters(selection)\n        finally:\n            self.dendrogram.selectionChanged.connect(self._invalidate_output)\n\n    def _invalidate_clustering(self):\n        self._update()\n        self._update_labels()\n        self._invalidate_output()\n\n    def _invalidate_output(self):\n        self.commit()\n\n    def _invalidate_pruning(self):\n        if self.root:\n            selection = self.dendrogram.selected_nodes()\n            ranges = [node.value.range for node in selection]\n            if self.pruning:\n                self._set_displayed_root(\n                    prune(self.root, level=self.max_depth))\n            else:\n                self._set_displayed_root(self.root)\n            selected = [node for node in preorder(self._displayed_root)\n                        if node.value.range in ranges]\n\n            self.dendrogram.set_selected_clusters(selected)\n\n        self._apply_selection()\n\n    def commit(self):\n        items = getattr(self.matrix, \"items\", self.items)\n        if not items:\n            self.Outputs.selected_data.send(None)\n            self.Outputs.annotated_data.send(None)\n            return\n\n        selection = self.dendrogram.selected_nodes()\n        selection = sorted(selection, key=lambda c: c.value.first)\n\n        indices = [leaf.value.index for leaf in leaves(self.root)]\n\n        maps = [indices[node.value.first:node.value.last]\n                for node in selection]\n\n        selected_indices = list(chain(*maps))\n        unselected_indices = sorted(set(range(self.root.value.last)) -\n                                    set(selected_indices))\n\n        if not selected_indices:\n            self.Outputs.selected_data.send(None)\n            annotated_data = create_annotated_table(items, []) \\\n                if self.selection_method == 0 and self.matrix.axis else None\n            self.Outputs.annotated_data.send(annotated_data)\n            return\n\n        selected_data = None\n\n        if isinstance(items, Orange.data.Table) and self.matrix.axis == 1:\n            # Select rows\n            c = np.zeros(self.matrix.shape[0])\n\n            for i, indices in enumerate(maps):\n                c[indices] = i\n            c[unselected_indices] = len(maps)\n\n            mask = c != len(maps)\n\n            data, domain = items, items.domain\n            attrs = domain.attributes\n            classes = domain.class_vars\n            metas = domain.metas\n\n            var_name = get_unique_names(domain, \"Cluster\")\n            values = [f\"C{i + 1}\" for i in range(len(maps))]\n\n            clust_var = Orange.data.DiscreteVariable(\n                var_name, values=values + [\"Other\"])\n            domain = Orange.data.Domain(attrs, classes, metas + (clust_var,))\n            data = items.transform(domain)\n            data.get_column_view(clust_var)[0][:] = c\n\n            if selected_indices:\n                selected_data = data[mask]\n                clust_var = Orange.data.DiscreteVariable(\n                    var_name, values=values)\n                selected_data.domain = Domain(\n                    attrs, classes, metas + (clust_var, ))\n\n        elif isinstance(items, Orange.data.Table) and self.matrix.axis == 0:\n            # Select columns\n            domain = Orange.data.Domain(\n                [items.domain[i] for i in selected_indices],\n                items.domain.class_vars, items.domain.metas)\n            selected_data = items.from_table(domain, items)\n            data = None\n\n        self.Outputs.selected_data.send(selected_data)\n        annotated_data = create_annotated_table(data, selected_indices)\n        self.Outputs.annotated_data.send(annotated_data)\n\n    def eventFilter(self, obj, event):\n        if obj is self.view.viewport() and event.type() == QEvent.Resize:\n            # NOTE: not using viewport.width(), due to 'transient' scroll bars\n            # (macOS). Viewport covers the whole view, but QGraphicsView still\n            # scrolls left, right with scroll bar extent (other\n            # QAbstractScrollArea widgets behave as expected).\n            w_frame = self.view.frameWidth()\n            margin = self.view.viewportMargins()\n            w_scroll = self.view.verticalScrollBar().width()\n            width = (self.view.width() - w_frame * 2 -\n                     margin.left() - margin.right() - w_scroll)\n            # layout with new width constraint\n            self.__layout_main_graphics(width=width)\n        elif obj is self._main_graphics and \\\n                event.type() == QEvent.LayoutRequest:\n            # layout preserving the width (vertical re layout)\n            self.__layout_main_graphics()\n        return super().eventFilter(obj, event)\n\n    @Slot(QPointF)\n    def _activate_cut_line(self, pos: QPointF):\n        \"\"\"Activate cut line selection an set cut value to `pos.x()`.\"\"\"\n        self.selection_method = 1\n        self.cut_line.setValue(pos.x())\n        self._selection_method_changed()\n\n    def onDeleteWidget(self):\n        super().onDeleteWidget()\n        self._clear_plot()\n        self.dendrogram.clear()\n        self.dendrogram.deleteLater()\n\n    def _dendrogram_geom_changed(self):\n        pos = self.dendrogram.pos_at_height(self.cutoff_height)\n        geom = self.dendrogram.geometry()\n        self._set_slider_value(pos.x(), geom.width())\n\n        self.cut_line.setLength(\n            self.bottom_axis.geometry().bottom()\n            - self.top_axis.geometry().top()\n        )\n\n        geom = self._main_graphics.geometry()\n        assert geom.topLeft() == QPointF(0, 0)\n\n        def adjustLeft(rect):\n            rect = QRectF(rect)\n            rect.setLeft(geom.left())\n            return rect\n        margin = 3\n        self.scene.setSceneRect(geom)\n        self.view.setSceneRect(geom)\n        self.view.setHeaderSceneRect(\n            adjustLeft(self.top_axis.geometry()).adjusted(0, 0, 0, margin)\n        )\n        self.view.setFooterSceneRect(\n            adjustLeft(self.bottom_axis.geometry()).adjusted(0, -margin, 0, 0)\n        )\n\n    def _dendrogram_slider_changed(self, value):\n        p = QPointF(value, 0)\n        cl_height = self.dendrogram.height_at(p)\n\n        self.set_cutoff_height(cl_height)\n\n    def _set_slider_value(self, value, span):\n        with blocked(self.cut_line):\n            self.cut_line.setRange(0, span)\n            self.cut_line.setValue(value)\n\n    def set_cutoff_height(self, height):\n        self.cutoff_height = height\n        if self.root:\n            self.cut_ratio = 100 * height / self.root.value.height\n        self.select_max_height(height)\n\n    def _set_cut_line_visible(self, visible):\n        self.cut_line.setVisible(visible)\n\n    def select_top_n(self, n):\n        root = self._displayed_root\n        if root:\n            clusters = top_clusters(root, n)\n            self.dendrogram.set_selected_clusters(clusters)\n\n    def select_max_height(self, height):\n        root = self._displayed_root\n        if root:\n            clusters = clusters_at_height(root, height)\n            self.dendrogram.set_selected_clusters(clusters)\n\n    def _selection_method_changed(self):\n        self._set_cut_line_visible(self.selection_method == 1)\n        if self.root:\n            self._apply_selection()\n\n    def _apply_selection(self):\n        if not self.root:\n            return\n\n        if self.selection_method == 0:\n            pass\n        elif self.selection_method == 1:\n            height = self.cut_ratio * self.root.value.height / 100\n            self.set_cutoff_height(height)\n            pos = self.dendrogram.pos_at_height(height)\n            self._set_slider_value(pos.x(), self.dendrogram.size().width())\n        elif self.selection_method == 2:\n            self.select_top_n(self.top_n)\n\n    def _selection_edited(self):\n        # Selection was edited by clicking on a cluster in the\n        # dendrogram view.\n        self.selection_method = 0\n        self._selection_method_changed()\n        self._invalidate_output()\n\n    def _save_selection(self):\n        # Save the current manual node selection state\n        selection_state = None\n        if self.selection_method == 0 and self.root:\n            assert self.linkmatrix is not None\n            linkmat = [(int(_0), int(_1), _2)\n                       for _0, _1, _2 in self.linkmatrix[:, :3].tolist()]\n            nodes_ = self.dendrogram.selected_nodes()\n            # match the display (pruned) nodes back (by ranges)\n            mapping = {node.value.range: node for node in postorder(self.root)}\n            nodes = [mapping[node.value.range] for node in nodes_]\n            indices = [tuple(node.value.index for node in leaves(node))\n                       for node in nodes]\n            if nodes:\n                selection_state = (indices, linkmat)\n        return selection_state\n\n    def save_state(self):\n        # type: () -> Dict[str, Any]\n        \"\"\"\n        Save state for `set_restore_state`\n        \"\"\"\n        selection = self._save_selection()\n        res = {\"version\": (0, 0, 0)}\n        if selection is not None:\n            res[\"selection_state\"] = selection\n        return res\n\n    def set_restore_state(self, state):\n        # type: (Dict[str, Any]) -> bool\n        \"\"\"\n        Restore session data from a saved state.\n\n        Parameters\n        ----------\n        state : Dict[str, Any]\n\n        NOTE\n        ----\n        This is method called while the instance (self) is being constructed,\n        even before its `__init__` is called. Consider `self` to be only a\n        `QObject` at this stage.\n        \"\"\"\n        if \"selection_state\" in state:\n            selection = state[\"selection_state\"]\n            self.__pending_selection_restore = selection\n        return True\n\n    def __zoom_in(self):\n        def clip(minval, maxval, val):\n            return min(max(val, minval), maxval)\n        self.zoom_factor = clip(self.zoom_slider.minimum(),\n                                self.zoom_slider.maximum(),\n                                self.zoom_factor + 1)\n        self.__update_font_scale()\n\n    def __zoom_out(self):\n        def clip(minval, maxval, val):\n            return min(max(val, minval), maxval)\n        self.zoom_factor = clip(self.zoom_slider.minimum(),\n                                self.zoom_slider.maximum(),\n                                self.zoom_factor - 1)\n        self.__update_font_scale()\n\n    def __zoom_reset(self):\n        self.zoom_factor = 0\n        self.__update_font_scale()\n\n    def __layout_main_graphics(self, width=-1):\n        if width < 0:\n            # Preserve current width.\n            width = self._main_graphics.size().width()\n        preferred = self._main_graphics.effectiveSizeHint(\n            Qt.PreferredSize, constraint=QSizeF(width, -1))\n        self._main_graphics.resize(QSizeF(width, preferred.height()))\n        mw = self._main_graphics.minimumWidth() + 4\n        self.view.setMinimumWidth(mw + self.view.verticalScrollBar().width())\n\n    def __update_font_scale(self):\n        font = self.scene.font()\n        factor = (1.25 ** self.zoom_factor)\n        font = qfont_scaled(font, factor)\n        self._main_graphics.setFont(font)\n\n    def send_report(self):\n        annot = self.label_cb.currentText()\n        if isinstance(self.annotation, str):\n            annot = annot.lower()\n        if self.selection_method == 0:\n            sel = \"manual\"\n        elif self.selection_method == 1:\n            sel = \"at {:.1f} of height\".format(self.cut_ratio)\n        else:\n            sel = \"top {} clusters\".format(self.top_n)\n        self.report_items((\n            (\"Linkage\", LINKAGE[self.linkage].lower()),\n            (\"Annotation\", annot),\n            (\"Prunning\",\n             self.pruning != 0 and \"{} levels\".format(self.max_depth)),\n            (\"Selection\", sel),\n        ))\n        self.report_plot()\n\n\ndef qfont_scaled(font, factor):\n    scaled = QFont(font)\n    if font.pointSizeF() != -1:\n        scaled.setPointSizeF(font.pointSizeF() * factor)\n    elif font.pixelSize() != -1:\n        scaled.setPixelSize(int(font.pixelSize() * factor))\n    return scaled\n\n\nclass AxisItem(pg.AxisItem):\n    mousePressed = Signal(QPointF, Qt.MouseButton)\n    mouseMoved = Signal(QPointF, Qt.MouseButtons)\n    mouseReleased = Signal(QPointF, Qt.MouseButton)\n\n    #: \\reimp\n    def wheelEvent(self, event):\n        event.ignore()  # ignore event to propagate to the view -> scroll\n\n    def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:\n        self.mousePressed.emit(event.pos(), event.button())\n        super().mousePressEvent(event)\n        event.accept()\n\n    def mouseMoveEvent(self, event):\n        self.mouseMoved.emit(event.pos(), event.buttons())\n        super().mouseMoveEvent(event)\n        event.accept()\n\n    def mouseReleaseEvent(self, event):\n        self.mouseReleased.emit(event.pos(), event.button())\n        super().mouseReleaseEvent(event)\n        event.accept()\n\n\nclass SliderLine(QGraphicsObject):\n    \"\"\"A movable slider line.\"\"\"\n    valueChanged = Signal(float)\n\n    linePressed = Signal()\n    lineMoved = Signal()\n    lineReleased = Signal()\n    rangeChanged = Signal(float, float)\n\n    def __init__(self, parent=None, orientation=Qt.Vertical, value=0.0,\n                 length=10.0, **kwargs):\n        self._orientation = orientation\n        self._value = value\n        self._length = length\n        self._min = 0.0\n        self._max = 1.0\n        self._line = QLineF()  # type: Optional[QLineF]\n        self._pen = QPen()\n        super().__init__(parent, **kwargs)\n\n        self.setAcceptedMouseButtons(Qt.LeftButton)\n        self.setPen(make_pen(brush=QColor(50, 50, 50), width=1, cosmetic=False,\n                             style=Qt.DashLine))\n\n        if self._orientation == Qt.Vertical:\n            self.setCursor(Qt.SizeVerCursor)\n        else:\n            self.setCursor(Qt.SizeHorCursor)\n\n    def setPen(self, pen: Union[QPen, Qt.GlobalColor, Qt.PenStyle]) -> None:\n        pen = QPen(pen)\n        if self._pen != pen:\n            self.prepareGeometryChange()\n            self._pen = pen\n            self._line = None\n            self.update()\n\n    def pen(self) -> QPen:\n        return QPen(self._pen)\n\n    def setValue(self, value: float):\n        value = min(max(value, self._min), self._max)\n\n        if self._value != value:\n            self.prepareGeometryChange()\n            self._value = value\n            self._line = None\n            self.valueChanged.emit(value)\n\n    def value(self) -> float:\n        return self._value\n\n    def setRange(self, minval: float, maxval: float) -> None:\n        maxval = max(minval, maxval)\n        if minval != self._min or maxval != self._max:\n            self._min = minval\n            self._max = maxval\n            self.rangeChanged.emit(minval, maxval)\n            self.setValue(self._value)\n\n    def setLength(self, length: float):\n        if self._length != length:\n            self.prepareGeometryChange()\n            self._length = length\n            self._line = None\n\n    def length(self) -> float:\n        return self._length\n\n    def setOrientation(self, orientation: Qt.Orientation):\n        if self._orientation != orientation:\n            self.prepareGeometryChange()\n            self._orientation = orientation\n            self._line = None\n            if self._orientation == Qt.Vertical:\n                self.setCursor(Qt.SizeVerCursor)\n            else:\n                self.setCursor(Qt.SizeHorCursor)\n\n    def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:\n        event.accept()\n        self.linePressed.emit()\n\n    def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None:\n        pos = event.pos()\n        if self._orientation == Qt.Vertical:\n            self.setValue(pos.y())\n        else:\n            self.setValue(pos.x())\n        self.lineMoved.emit()\n        event.accept()\n\n    def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None:\n        if self._orientation == Qt.Vertical:\n            self.setValue(event.pos().y())\n        else:\n            self.setValue(event.pos().x())\n        self.lineReleased.emit()\n        event.accept()\n\n    def boundingRect(self) -> QRectF:\n        if self._line is None:\n            if self._orientation == Qt.Vertical:\n                self._line = QLineF(0, self._value, self._length, self._value)\n            else:\n                self._line = QLineF(self._value, 0, self._value, self._length)\n        r = QRectF(self._line.p1(), self._line.p2())\n        penw = self.pen().width()\n        return r.adjusted(-penw, -penw, penw, penw)\n\n    def paint(self, painter, *args):\n        if self._line is None:\n            self.boundingRect()\n\n        painter.save()\n        painter.setPen(self.pen())\n        painter.drawLine(self._line)\n        painter.restore()\n\n\ndef clusters_at_height(root, height):\n    \"\"\"Return a list of clusters by cutting the clustering at `height`.\n    \"\"\"\n    lower = set()\n    cluster_list = []\n    for cl in preorder(root):\n        if cl in lower:\n            continue\n        if cl.value.height < height:\n            cluster_list.append(cl)\n            lower.update(preorder(cl))\n    return cluster_list\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    from Orange import distance\n    data = Orange.data.Table(\"iris\")\n    matrix = distance.Euclidean(distance._preprocess(data))\n    WidgetPreview(OWHierarchicalClustering).run(matrix)\n",
            "new_file_content": "from itertools import chain\nfrom contextlib import contextmanager\n\nimport typing\nfrom typing import Any, List, Tuple, Dict, Optional, Set, Union\n\nimport numpy as np\n\nfrom AnyQt.QtWidgets import (\n    QGraphicsWidget, QGraphicsObject, QGraphicsScene, QGridLayout, QSizePolicy,\n    QAction, QComboBox, QGraphicsGridLayout, QGraphicsSceneMouseEvent\n)\nfrom AnyQt.QtGui import QColor, QPen, QFont, QKeySequence\nfrom AnyQt.QtCore import Qt, QSize, QSizeF, QPointF, QRectF, QLineF, QEvent\nfrom AnyQt.QtCore import pyqtSignal as Signal, pyqtSlot as Slot\n\nimport pyqtgraph as pg\n\nimport Orange.data\nfrom Orange.data.domain import filter_visible\nfrom Orange.data import Domain\nimport Orange.misc\nfrom Orange.clustering.hierarchical import \\\n    postorder, preorder, Tree, tree_from_linkage, dist_matrix_linkage, \\\n    leaves, prune, top_clusters\nfrom Orange.data.util import get_unique_names\n\nfrom Orange.widgets import widget, gui, settings\nfrom Orange.widgets.utils import itemmodels, combobox\nfrom Orange.widgets.utils.annotated_data import (create_annotated_table,\n                                                 ANNOTATED_DATA_SIGNAL_NAME)\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.widget import Input, Output, Msg\n\nfrom Orange.widgets.utils.stickygraphicsview import StickyGraphicsView\nfrom Orange.widgets.utils.graphicstextlist import TextListWidget\nfrom Orange.widgets.utils.dendrogram import DendrogramWidget\n\n__all__ = [\"OWHierarchicalClustering\"]\n\n\nLINKAGE = [\"Single\", \"Average\", \"Weighted\", \"Complete\", \"Ward\"]\n\n\ndef make_pen(brush=Qt.black, width=1, style=Qt.SolidLine,\n             cap_style=Qt.SquareCap, join_style=Qt.BevelJoin,\n             cosmetic=False):\n    pen = QPen(brush)\n    pen.setWidth(width)\n    pen.setStyle(style)\n    pen.setCapStyle(cap_style)\n    pen.setJoinStyle(join_style)\n    pen.setCosmetic(cosmetic)\n    return pen\n\n\n@contextmanager\ndef blocked(obj):\n    old = obj.signalsBlocked()\n    obj.blockSignals(True)\n    try:\n        yield obj\n    finally:\n        obj.blockSignals(old)\n\n\nclass SaveStateSettingsHandler(settings.SettingsHandler):\n    \"\"\"\n    A settings handler that delegates session data store/restore to the\n    OWWidget instance.\n\n    The OWWidget subclass must implement `save_state() -> Dict[str, Any]` and\n    `set_restore_state(state: Dict[str, Any])` methods.\n    \"\"\"\n    def initialize(self, instance, data=None):\n        super().initialize(instance, data)\n        if data is not None and \"__session_state_data\" in data:\n            session_data = data[\"__session_state_data\"]\n            instance.set_restore_state(session_data)\n\n    def pack_data(self, widget):\n        # type: (widget.OWWidget) -> dict\n        res = super().pack_data(widget)\n        state = widget.save_state()\n        if state:\n            assert \"__session_state_data\" not in res\n            res[\"__session_state_data\"] = state\n        return res\n\n\nclass _DomainContextHandler(settings.DomainContextHandler,\n                            SaveStateSettingsHandler):\n    pass\n\n\nif typing.TYPE_CHECKING:\n    #: Encoded selection state for persistent storage.\n    #: This is a list of tuples of leaf indices in the selection and\n    #: a (N, 3) linkage matrix for validation (the 4-th column from scipy\n    #: is omitted).\n    SelectionState = Tuple[List[Tuple[int]], List[Tuple[int, int, float]]]\n\n\nclass OWHierarchicalClustering(widget.OWWidget):\n    name = \"Hierarchical Clustering\"\n    description = \"Display a dendrogram of a hierarchical clustering \" \\\n                  \"constructed from the input distance matrix.\"\n    icon = \"icons/HierarchicalClustering.svg\"\n    priority = 2100\n    keywords = []\n\n    class Inputs:\n        distances = Input(\"Distances\", Orange.misc.DistMatrix)\n\n    class Outputs:\n        selected_data = Output(\"Selected Data\", Orange.data.Table, default=True)\n        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table)\n\n    settingsHandler = _DomainContextHandler()\n\n    #: Selected linkage\n    linkage = settings.Setting(1)\n    #: Index of the selected annotation item (variable, ...)\n    annotation = settings.ContextSetting(\"Enumeration\")\n    #: Out-of-context setting for the case when the \"Name\" option is available\n    annotation_if_names = settings.Setting(\"Name\")\n    #: Out-of-context setting for the case with just \"Enumerate\" and \"None\"\n    annotation_if_enumerate = settings.Setting(\"Enumerate\")\n    #: Selected tree pruning (none/max depth)\n    pruning = settings.Setting(0)\n    #: Maximum depth when max depth pruning is selected\n    max_depth = settings.Setting(10)\n\n    #: Selected cluster selection method (none, cut distance, top n)\n    selection_method = settings.Setting(0)\n    #: Cut height ratio wrt root height\n    cut_ratio = settings.Setting(75.0)\n    #: Number of top clusters to select\n    top_n = settings.Setting(3)\n    #: Dendrogram zoom factor\n    zoom_factor = settings.Setting(0)\n\n    autocommit = settings.Setting(True)\n\n    graph_name = \"scene\"\n\n    basic_annotations = [\"None\", \"Enumeration\"]\n\n    class Error(widget.OWWidget.Error):\n        not_finite_distances = Msg(\"Some distances are infinite\")\n\n    #: Stored (manual) selection state (from a saved workflow) to restore.\n    __pending_selection_restore = None  # type: Optional[SelectionState]\n\n    def __init__(self):\n        super().__init__()\n\n        self.matrix = None\n        self.items = None\n        self.linkmatrix = None\n        self.root = None\n        self._displayed_root = None\n        self.cutoff_height = 0.0\n\n        gui.comboBox(\n            self.controlArea, self, \"linkage\", items=LINKAGE, box=\"Linkage\",\n            callback=self._invalidate_clustering)\n\n        model = itemmodels.VariableListModel()\n        model[:] = self.basic_annotations\n\n        box = gui.widgetBox(self.controlArea, \"Annotations\")\n        self.label_cb = cb = combobox.ComboBoxSearch(\n            minimumContentsLength=14,\n            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon\n        )\n        cb.setModel(model)\n        cb.setCurrentIndex(cb.findData(self.annotation, Qt.EditRole))\n\n        def on_annotation_activated():\n            self.annotation = cb.currentData(Qt.EditRole)\n            self._update_labels()\n        cb.activated.connect(on_annotation_activated)\n\n        def on_annotation_changed(value):\n            cb.setCurrentIndex(cb.findData(value, Qt.EditRole))\n        self.connect_control(\"annotation\", on_annotation_changed)\n\n        box.layout().addWidget(self.label_cb)\n\n        box = gui.radioButtons(\n            self.controlArea, self, \"pruning\", box=\"Pruning\",\n            callback=self._invalidate_pruning)\n        grid = QGridLayout()\n        box.layout().addLayout(grid)\n        grid.addWidget(\n            gui.appendRadioButton(box, \"None\", addToLayout=False),\n            0, 0\n        )\n        self.max_depth_spin = gui.spin(\n            box, self, \"max_depth\", minv=1, maxv=100,\n            callback=self._invalidate_pruning,\n            keyboardTracking=False, addToLayout=False\n        )\n\n        grid.addWidget(\n            gui.appendRadioButton(box, \"Max depth:\", addToLayout=False),\n            1, 0)\n        grid.addWidget(self.max_depth_spin, 1, 1)\n\n        self.selection_box = gui.radioButtons(\n            self.controlArea, self, \"selection_method\",\n            box=\"Selection\",\n            callback=self._selection_method_changed)\n\n        grid = QGridLayout()\n        self.selection_box.layout().addLayout(grid)\n        grid.addWidget(\n            gui.appendRadioButton(\n                self.selection_box, \"Manual\", addToLayout=False),\n            0, 0\n        )\n        grid.addWidget(\n            gui.appendRadioButton(\n                self.selection_box, \"Height ratio:\", addToLayout=False),\n            1, 0\n        )\n        self.cut_ratio_spin = gui.spin(\n            self.selection_box, self, \"cut_ratio\", 0, 100, step=1e-1,\n            spinType=float, callback=self._selection_method_changed,\n            addToLayout=False\n        )\n        self.cut_ratio_spin.setSuffix(\"%\")\n\n        grid.addWidget(self.cut_ratio_spin, 1, 1)\n\n        grid.addWidget(\n            gui.appendRadioButton(\n                self.selection_box, \"Top N:\", addToLayout=False),\n            2, 0\n        )\n        self.top_n_spin = gui.spin(self.selection_box, self, \"top_n\", 1, 20,\n                                   callback=self._selection_method_changed,\n                                   addToLayout=False)\n        grid.addWidget(self.top_n_spin, 2, 1)\n\n        self.zoom_slider = gui.hSlider(\n            self.controlArea, self, \"zoom_factor\", box=\"Zoom\",\n            minValue=-6, maxValue=3, step=1, ticks=True, createLabel=False,\n            callback=self.__update_font_scale)\n\n        zoom_in = QAction(\n            \"Zoom in\", self, shortcut=QKeySequence.ZoomIn,\n            triggered=self.__zoom_in\n        )\n        zoom_out = QAction(\n            \"Zoom out\", self, shortcut=QKeySequence.ZoomOut,\n            triggered=self.__zoom_out\n        )\n        zoom_reset = QAction(\n            \"Reset zoom\", self,\n            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0),\n            triggered=self.__zoom_reset\n        )\n        self.addActions([zoom_in, zoom_out, zoom_reset])\n\n        self.controlArea.layout().addStretch()\n\n        gui.auto_send(self.buttonsArea, self, \"autocommit\")\n\n        self.scene = QGraphicsScene(self)\n        self.view = StickyGraphicsView(\n            self.scene,\n            horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff,\n            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn,\n            alignment=Qt.AlignLeft | Qt.AlignVCenter\n        )\n        self.mainArea.layout().setSpacing(1)\n        self.mainArea.layout().addWidget(self.view)\n\n        def axis_view(orientation):\n            ax = AxisItem(orientation=orientation, maxTickLength=7)\n            ax.mousePressed.connect(self._activate_cut_line)\n            ax.mouseMoved.connect(self._activate_cut_line)\n            ax.mouseReleased.connect(self._activate_cut_line)\n            ax.setRange(1.0, 0.0)\n            return ax\n\n        self.top_axis = axis_view(\"top\")\n        self.bottom_axis = axis_view(\"bottom\")\n\n        self._main_graphics = QGraphicsWidget()\n        scenelayout = QGraphicsGridLayout()\n        scenelayout.setHorizontalSpacing(10)\n        scenelayout.setVerticalSpacing(10)\n\n        self._main_graphics.setLayout(scenelayout)\n        self.scene.addItem(self._main_graphics)\n\n        self.dendrogram = DendrogramWidget()\n        self.dendrogram.setSizePolicy(QSizePolicy.MinimumExpanding,\n                                      QSizePolicy.MinimumExpanding)\n        self.dendrogram.selectionChanged.connect(self._invalidate_output)\n        self.dendrogram.selectionEdited.connect(self._selection_edited)\n\n        self.labels = TextListWidget()\n        self.labels.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)\n        self.labels.setAlignment(Qt.AlignLeft)\n        self.labels.setMaximumWidth(200)\n\n        scenelayout.addItem(self.top_axis, 0, 0,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        scenelayout.addItem(self.dendrogram, 1, 0,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        scenelayout.addItem(self.labels, 1, 1,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        scenelayout.addItem(self.bottom_axis, 2, 0,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        self.view.viewport().installEventFilter(self)\n        self._main_graphics.installEventFilter(self)\n\n        self.top_axis.setZValue(self.dendrogram.zValue() + 10)\n        self.bottom_axis.setZValue(self.dendrogram.zValue() + 10)\n        self.cut_line = SliderLine(self.top_axis,\n                                   orientation=Qt.Horizontal)\n        self.cut_line.valueChanged.connect(self._dendrogram_slider_changed)\n        self.dendrogram.geometryChanged.connect(self._dendrogram_geom_changed)\n        self._set_cut_line_visible(self.selection_method == 1)\n        self.__update_font_scale()\n\n    @Inputs.distances\n    def set_distances(self, matrix):\n        if self.__pending_selection_restore is not None:\n            selection_state = self.__pending_selection_restore\n        else:\n            # save the current selection to (possibly) restore later\n            selection_state = self._save_selection()\n\n        self.error()\n        self.Error.clear()\n        if matrix is not None:\n            N, _ = matrix.shape\n            if N < 2:\n                self.error(\"Empty distance matrix\")\n                matrix = None\n        if matrix is not None:\n            if not np.all(np.isfinite(matrix)):\n                self.Error.not_finite_distances()\n                matrix = None\n\n        self.matrix = matrix\n        if matrix is not None:\n            self._set_items(matrix.row_items, matrix.axis)\n        else:\n            self._set_items(None)\n        self._invalidate_clustering()\n\n        # Can now attempt to restore session state from a saved workflow.\n        if self.root and selection_state is not None:\n            self._restore_selection(selection_state)\n            self.__pending_selection_restore = None\n\n        self.unconditional_commit()\n\n    def _set_items(self, items, axis=1):\n        self.closeContext()\n        self.items = items\n        model = self.label_cb.model()\n        if len(model) == 3:\n            self.annotation_if_names = self.annotation\n        elif len(model) == 2:\n            self.annotation_if_enumerate = self.annotation\n        if isinstance(items, Orange.data.Table) and axis:\n            model[:] = chain(\n                self.basic_annotations,\n                [model.Separator],\n                items.domain.class_vars,\n                items.domain.metas,\n                [model.Separator] if (items.domain.class_vars or items.domain.metas) and\n                next(filter_visible(items.domain.attributes), False) else [],\n                filter_visible(items.domain.attributes)\n            )\n            if items.domain.class_vars:\n                self.annotation = items.domain.class_vars[0]\n            else:\n                self.annotation = \"Enumeration\"\n            self.openContext(items.domain)\n        else:\n            name_option = bool(\n                items is not None and (\n                    not axis or\n                    isinstance(items, list) and\n                    all(isinstance(var, Orange.data.Variable) for var in items)))\n            model[:] = self.basic_annotations + [\"Name\"] * name_option\n            self.annotation = self.annotation_if_names if name_option \\\n                else self.annotation_if_enumerate\n\n    def _clear_plot(self):\n        self.dendrogram.set_root(None)\n        self.labels.setItems([])\n\n    def _set_displayed_root(self, root):\n        self._clear_plot()\n        self._displayed_root = root\n        self.dendrogram.set_root(root)\n\n        self._update_labels()\n\n        self._main_graphics.resize(\n            self._main_graphics.size().width(),\n            self._main_graphics.sizeHint(Qt.PreferredSize).height()\n        )\n        self._main_graphics.layout().activate()\n\n    def _update(self):\n        self._clear_plot()\n\n        distances = self.matrix\n\n        if distances is not None:\n            method = LINKAGE[self.linkage].lower()\n            Z = dist_matrix_linkage(distances, linkage=method)\n\n            tree = tree_from_linkage(Z)\n            self.linkmatrix = Z\n            self.root = tree\n\n            self.top_axis.setRange(tree.value.height, 0.0)\n            self.bottom_axis.setRange(tree.value.height, 0.0)\n\n            if self.pruning:\n                self._set_displayed_root(prune(tree, level=self.max_depth))\n            else:\n                self._set_displayed_root(tree)\n        else:\n            self.linkmatrix = None\n            self.root = None\n            self._set_displayed_root(None)\n\n        self._apply_selection()\n\n    def _update_labels(self):\n        labels = []\n        if self.root and self._displayed_root:\n            indices = [leaf.value.index for leaf in leaves(self.root)]\n\n            if self.annotation == \"None\":\n                labels = []\n            elif self.annotation == \"Enumeration\":\n                labels = [str(i+1) for i in indices]\n            elif self.annotation == \"Name\":\n                attr = self.matrix.row_items.domain.attributes\n                labels = [str(attr[i]) for i in indices]\n            elif isinstance(self.annotation, Orange.data.Variable):\n                col_data, _ = self.items.get_column_view(self.annotation)\n                labels = [self.annotation.str_val(val) for val in col_data]\n                labels = [labels[idx] for idx in indices]\n            else:\n                labels = []\n\n            if labels and self._displayed_root is not self.root:\n                joined = leaves(self._displayed_root)\n                labels = [\", \".join(labels[leaf.value.first: leaf.value.last])\n                          for leaf in joined]\n\n        self.labels.setItems(labels)\n        self.labels.setMinimumWidth(1 if labels else -1)\n\n    def _restore_selection(self, state):\n        # type: (SelectionState) -> bool\n        \"\"\"\n        Restore the (manual) node selection state.\n\n        Return True if successful; False otherwise.\n        \"\"\"\n        linkmatrix = self.linkmatrix\n        if self.selection_method == 0 and self.root:\n            selected, linksaved = state\n            linkstruct = np.array(linksaved, dtype=float)\n            selected = set(selected)  # type: Set[Tuple[int]]\n            if not selected:\n                return False\n            if linkmatrix.shape[0] != linkstruct.shape[0]:\n                return False\n            # check that the linkage matrix structure matches. Use isclose for\n            # the height column to account for inexact floating point math\n            # (e.g. summation order in different ?gemm implementations for\n            # euclidean distances, ...)\n            if np.any(linkstruct[:, :2] != linkmatrix[:, :2]) or \\\n                    not np.all(np.isclose(linkstruct[:, 2], linkstruct[:, 2])):\n                return False\n            selection = []\n            indices = np.array([n.value.index for n in leaves(self.root)],\n                               dtype=int)\n            # mapping from ranges to display (pruned) nodes\n            mapping = {node.value.range: node\n                       for node in postorder(self._displayed_root)}\n            for node in postorder(self.root):  # type: Tree\n                r = tuple(indices[node.value.first: node.value.last])\n                if r in selected:\n                    if node.value.range not in mapping:\n                        # the node was pruned from display and cannot be\n                        # selected\n                        break\n                    selection.append(mapping[node.value.range])\n                    selected.remove(r)\n                if not selected:\n                    break  # found all, nothing more to do\n            if selection and selected:\n                # Could not restore all selected nodes (only partial match)\n                return False\n\n            self._set_selected_nodes(selection)\n            return True\n        return False\n\n    def _set_selected_nodes(self, selection):\n        # type: (List[Tree]) -> None\n        \"\"\"\n        Set the nodes in `selection` to be the current selected nodes.\n\n        The selection nodes must be subtrees of the current `_displayed_root`.\n        \"\"\"\n        self.dendrogram.selectionChanged.disconnect(self._invalidate_output)\n        try:\n            self.dendrogram.set_selected_clusters(selection)\n        finally:\n            self.dendrogram.selectionChanged.connect(self._invalidate_output)\n\n    def _invalidate_clustering(self):\n        self._update()\n        self._update_labels()\n        self._invalidate_output()\n\n    def _invalidate_output(self):\n        self.commit()\n\n    def _invalidate_pruning(self):\n        if self.root:\n            selection = self.dendrogram.selected_nodes()\n            ranges = [node.value.range for node in selection]\n            if self.pruning:\n                self._set_displayed_root(\n                    prune(self.root, level=self.max_depth))\n            else:\n                self._set_displayed_root(self.root)\n            selected = [node for node in preorder(self._displayed_root)\n                        if node.value.range in ranges]\n\n            self.dendrogram.set_selected_clusters(selected)\n\n        self._apply_selection()\n\n    def commit(self):\n        items = getattr(self.matrix, \"items\", self.items)\n        if not items:\n            self.Outputs.selected_data.send(None)\n            self.Outputs.annotated_data.send(None)\n            return\n\n        selection = self.dendrogram.selected_nodes()\n        selection = sorted(selection, key=lambda c: c.value.first)\n\n        indices = [leaf.value.index for leaf in leaves(self.root)]\n\n        maps = [indices[node.value.first:node.value.last]\n                for node in selection]\n\n        selected_indices = list(chain(*maps))\n        unselected_indices = sorted(set(range(self.root.value.last)) -\n                                    set(selected_indices))\n\n        if not selected_indices:\n            self.Outputs.selected_data.send(None)\n            annotated_data = create_annotated_table(items, []) \\\n                if self.selection_method == 0 and self.matrix.axis else None\n            self.Outputs.annotated_data.send(annotated_data)\n            return\n\n        selected_data = None\n\n        if isinstance(items, Orange.data.Table) and self.matrix.axis == 1:\n            # Select rows\n            c = np.zeros(self.matrix.shape[0])\n\n            for i, indices in enumerate(maps):\n                c[indices] = i\n            c[unselected_indices] = len(maps)\n\n            mask = c != len(maps)\n\n            data, domain = items, items.domain\n            attrs = domain.attributes\n            classes = domain.class_vars\n            metas = domain.metas\n\n            var_name = get_unique_names(domain, \"Cluster\")\n            values = [f\"C{i + 1}\" for i in range(len(maps))]\n\n            clust_var = Orange.data.DiscreteVariable(\n                var_name, values=values + [\"Other\"])\n            domain = Orange.data.Domain(attrs, classes, metas + (clust_var,))\n            data = items.transform(domain)\n            data.get_column_view(clust_var)[0][:] = c\n\n            if selected_indices:\n                selected_data = data[mask]\n                clust_var = Orange.data.DiscreteVariable(\n                    var_name, values=values)\n                selected_data.domain = Domain(\n                    attrs, classes, metas + (clust_var, ))\n\n            annotated_data = create_annotated_table(data, selected_indices)\n\n        elif isinstance(items, Orange.data.Table) and self.matrix.axis == 0:\n            # Select columns\n            attrs = []\n            for clust, indices in chain(enumerate(maps, start=1),\n                                        [(0, unselected_indices)]):\n                for i in indices:\n                    attr = items.domain[i].copy()\n                    attr.attributes[\"cluster\"] = clust\n                    attrs.append(attr)\n            domain = Orange.data.Domain(\n                # len(unselected_indices) can be 0\n                attrs[:len(attrs) - len(unselected_indices)],\n                items.domain.class_vars, items.domain.metas)\n            selected_data = items.from_table(domain, items)\n\n            domain = Orange.data.Domain(\n                attrs,\n                items.domain.class_vars, items.domain.metas)\n            annotated_data = items.from_table(domain, items)\n\n        self.Outputs.selected_data.send(selected_data)\n        self.Outputs.annotated_data.send(annotated_data)\n\n    def eventFilter(self, obj, event):\n        if obj is self.view.viewport() and event.type() == QEvent.Resize:\n            # NOTE: not using viewport.width(), due to 'transient' scroll bars\n            # (macOS). Viewport covers the whole view, but QGraphicsView still\n            # scrolls left, right with scroll bar extent (other\n            # QAbstractScrollArea widgets behave as expected).\n            w_frame = self.view.frameWidth()\n            margin = self.view.viewportMargins()\n            w_scroll = self.view.verticalScrollBar().width()\n            width = (self.view.width() - w_frame * 2 -\n                     margin.left() - margin.right() - w_scroll)\n            # layout with new width constraint\n            self.__layout_main_graphics(width=width)\n        elif obj is self._main_graphics and \\\n                event.type() == QEvent.LayoutRequest:\n            # layout preserving the width (vertical re layout)\n            self.__layout_main_graphics()\n        return super().eventFilter(obj, event)\n\n    @Slot(QPointF)\n    def _activate_cut_line(self, pos: QPointF):\n        \"\"\"Activate cut line selection an set cut value to `pos.x()`.\"\"\"\n        self.selection_method = 1\n        self.cut_line.setValue(pos.x())\n        self._selection_method_changed()\n\n    def onDeleteWidget(self):\n        super().onDeleteWidget()\n        self._clear_plot()\n        self.dendrogram.clear()\n        self.dendrogram.deleteLater()\n\n    def _dendrogram_geom_changed(self):\n        pos = self.dendrogram.pos_at_height(self.cutoff_height)\n        geom = self.dendrogram.geometry()\n        self._set_slider_value(pos.x(), geom.width())\n\n        self.cut_line.setLength(\n            self.bottom_axis.geometry().bottom()\n            - self.top_axis.geometry().top()\n        )\n\n        geom = self._main_graphics.geometry()\n        assert geom.topLeft() == QPointF(0, 0)\n\n        def adjustLeft(rect):\n            rect = QRectF(rect)\n            rect.setLeft(geom.left())\n            return rect\n        margin = 3\n        self.scene.setSceneRect(geom)\n        self.view.setSceneRect(geom)\n        self.view.setHeaderSceneRect(\n            adjustLeft(self.top_axis.geometry()).adjusted(0, 0, 0, margin)\n        )\n        self.view.setFooterSceneRect(\n            adjustLeft(self.bottom_axis.geometry()).adjusted(0, -margin, 0, 0)\n        )\n\n    def _dendrogram_slider_changed(self, value):\n        p = QPointF(value, 0)\n        cl_height = self.dendrogram.height_at(p)\n\n        self.set_cutoff_height(cl_height)\n\n    def _set_slider_value(self, value, span):\n        with blocked(self.cut_line):\n            self.cut_line.setRange(0, span)\n            self.cut_line.setValue(value)\n\n    def set_cutoff_height(self, height):\n        self.cutoff_height = height\n        if self.root:\n            self.cut_ratio = 100 * height / self.root.value.height\n        self.select_max_height(height)\n\n    def _set_cut_line_visible(self, visible):\n        self.cut_line.setVisible(visible)\n\n    def select_top_n(self, n):\n        root = self._displayed_root\n        if root:\n            clusters = top_clusters(root, n)\n            self.dendrogram.set_selected_clusters(clusters)\n\n    def select_max_height(self, height):\n        root = self._displayed_root\n        if root:\n            clusters = clusters_at_height(root, height)\n            self.dendrogram.set_selected_clusters(clusters)\n\n    def _selection_method_changed(self):\n        self._set_cut_line_visible(self.selection_method == 1)\n        if self.root:\n            self._apply_selection()\n\n    def _apply_selection(self):\n        if not self.root:\n            return\n\n        if self.selection_method == 0:\n            pass\n        elif self.selection_method == 1:\n            height = self.cut_ratio * self.root.value.height / 100\n            self.set_cutoff_height(height)\n            pos = self.dendrogram.pos_at_height(height)\n            self._set_slider_value(pos.x(), self.dendrogram.size().width())\n        elif self.selection_method == 2:\n            self.select_top_n(self.top_n)\n\n    def _selection_edited(self):\n        # Selection was edited by clicking on a cluster in the\n        # dendrogram view.\n        self.selection_method = 0\n        self._selection_method_changed()\n        self._invalidate_output()\n\n    def _save_selection(self):\n        # Save the current manual node selection state\n        selection_state = None\n        if self.selection_method == 0 and self.root:\n            assert self.linkmatrix is not None\n            linkmat = [(int(_0), int(_1), _2)\n                       for _0, _1, _2 in self.linkmatrix[:, :3].tolist()]\n            nodes_ = self.dendrogram.selected_nodes()\n            # match the display (pruned) nodes back (by ranges)\n            mapping = {node.value.range: node for node in postorder(self.root)}\n            nodes = [mapping[node.value.range] for node in nodes_]\n            indices = [tuple(node.value.index for node in leaves(node))\n                       for node in nodes]\n            if nodes:\n                selection_state = (indices, linkmat)\n        return selection_state\n\n    def save_state(self):\n        # type: () -> Dict[str, Any]\n        \"\"\"\n        Save state for `set_restore_state`\n        \"\"\"\n        selection = self._save_selection()\n        res = {\"version\": (0, 0, 0)}\n        if selection is not None:\n            res[\"selection_state\"] = selection\n        return res\n\n    def set_restore_state(self, state):\n        # type: (Dict[str, Any]) -> bool\n        \"\"\"\n        Restore session data from a saved state.\n\n        Parameters\n        ----------\n        state : Dict[str, Any]\n\n        NOTE\n        ----\n        This is method called while the instance (self) is being constructed,\n        even before its `__init__` is called. Consider `self` to be only a\n        `QObject` at this stage.\n        \"\"\"\n        if \"selection_state\" in state:\n            selection = state[\"selection_state\"]\n            self.__pending_selection_restore = selection\n        return True\n\n    def __zoom_in(self):\n        def clip(minval, maxval, val):\n            return min(max(val, minval), maxval)\n        self.zoom_factor = clip(self.zoom_slider.minimum(),\n                                self.zoom_slider.maximum(),\n                                self.zoom_factor + 1)\n        self.__update_font_scale()\n\n    def __zoom_out(self):\n        def clip(minval, maxval, val):\n            return min(max(val, minval), maxval)\n        self.zoom_factor = clip(self.zoom_slider.minimum(),\n                                self.zoom_slider.maximum(),\n                                self.zoom_factor - 1)\n        self.__update_font_scale()\n\n    def __zoom_reset(self):\n        self.zoom_factor = 0\n        self.__update_font_scale()\n\n    def __layout_main_graphics(self, width=-1):\n        if width < 0:\n            # Preserve current width.\n            width = self._main_graphics.size().width()\n        preferred = self._main_graphics.effectiveSizeHint(\n            Qt.PreferredSize, constraint=QSizeF(width, -1))\n        self._main_graphics.resize(QSizeF(width, preferred.height()))\n        mw = self._main_graphics.minimumWidth() + 4\n        self.view.setMinimumWidth(mw + self.view.verticalScrollBar().width())\n\n    def __update_font_scale(self):\n        font = self.scene.font()\n        factor = (1.25 ** self.zoom_factor)\n        font = qfont_scaled(font, factor)\n        self._main_graphics.setFont(font)\n\n    def send_report(self):\n        annot = self.label_cb.currentText()\n        if isinstance(self.annotation, str):\n            annot = annot.lower()\n        if self.selection_method == 0:\n            sel = \"manual\"\n        elif self.selection_method == 1:\n            sel = \"at {:.1f} of height\".format(self.cut_ratio)\n        else:\n            sel = \"top {} clusters\".format(self.top_n)\n        self.report_items((\n            (\"Linkage\", LINKAGE[self.linkage].lower()),\n            (\"Annotation\", annot),\n            (\"Prunning\",\n             self.pruning != 0 and \"{} levels\".format(self.max_depth)),\n            (\"Selection\", sel),\n        ))\n        self.report_plot()\n\n\ndef qfont_scaled(font, factor):\n    scaled = QFont(font)\n    if font.pointSizeF() != -1:\n        scaled.setPointSizeF(font.pointSizeF() * factor)\n    elif font.pixelSize() != -1:\n        scaled.setPixelSize(int(font.pixelSize() * factor))\n    return scaled\n\n\nclass AxisItem(pg.AxisItem):\n    mousePressed = Signal(QPointF, Qt.MouseButton)\n    mouseMoved = Signal(QPointF, Qt.MouseButtons)\n    mouseReleased = Signal(QPointF, Qt.MouseButton)\n\n    #: \\reimp\n    def wheelEvent(self, event):\n        event.ignore()  # ignore event to propagate to the view -> scroll\n\n    def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:\n        self.mousePressed.emit(event.pos(), event.button())\n        super().mousePressEvent(event)\n        event.accept()\n\n    def mouseMoveEvent(self, event):\n        self.mouseMoved.emit(event.pos(), event.buttons())\n        super().mouseMoveEvent(event)\n        event.accept()\n\n    def mouseReleaseEvent(self, event):\n        self.mouseReleased.emit(event.pos(), event.button())\n        super().mouseReleaseEvent(event)\n        event.accept()\n\n\nclass SliderLine(QGraphicsObject):\n    \"\"\"A movable slider line.\"\"\"\n    valueChanged = Signal(float)\n\n    linePressed = Signal()\n    lineMoved = Signal()\n    lineReleased = Signal()\n    rangeChanged = Signal(float, float)\n\n    def __init__(self, parent=None, orientation=Qt.Vertical, value=0.0,\n                 length=10.0, **kwargs):\n        self._orientation = orientation\n        self._value = value\n        self._length = length\n        self._min = 0.0\n        self._max = 1.0\n        self._line = QLineF()  # type: Optional[QLineF]\n        self._pen = QPen()\n        super().__init__(parent, **kwargs)\n\n        self.setAcceptedMouseButtons(Qt.LeftButton)\n        self.setPen(make_pen(brush=QColor(50, 50, 50), width=1, cosmetic=False,\n                             style=Qt.DashLine))\n\n        if self._orientation == Qt.Vertical:\n            self.setCursor(Qt.SizeVerCursor)\n        else:\n            self.setCursor(Qt.SizeHorCursor)\n\n    def setPen(self, pen: Union[QPen, Qt.GlobalColor, Qt.PenStyle]) -> None:\n        pen = QPen(pen)\n        if self._pen != pen:\n            self.prepareGeometryChange()\n            self._pen = pen\n            self._line = None\n            self.update()\n\n    def pen(self) -> QPen:\n        return QPen(self._pen)\n\n    def setValue(self, value: float):\n        value = min(max(value, self._min), self._max)\n\n        if self._value != value:\n            self.prepareGeometryChange()\n            self._value = value\n            self._line = None\n            self.valueChanged.emit(value)\n\n    def value(self) -> float:\n        return self._value\n\n    def setRange(self, minval: float, maxval: float) -> None:\n        maxval = max(minval, maxval)\n        if minval != self._min or maxval != self._max:\n            self._min = minval\n            self._max = maxval\n            self.rangeChanged.emit(minval, maxval)\n            self.setValue(self._value)\n\n    def setLength(self, length: float):\n        if self._length != length:\n            self.prepareGeometryChange()\n            self._length = length\n            self._line = None\n\n    def length(self) -> float:\n        return self._length\n\n    def setOrientation(self, orientation: Qt.Orientation):\n        if self._orientation != orientation:\n            self.prepareGeometryChange()\n            self._orientation = orientation\n            self._line = None\n            if self._orientation == Qt.Vertical:\n                self.setCursor(Qt.SizeVerCursor)\n            else:\n                self.setCursor(Qt.SizeHorCursor)\n\n    def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:\n        event.accept()\n        self.linePressed.emit()\n\n    def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None:\n        pos = event.pos()\n        if self._orientation == Qt.Vertical:\n            self.setValue(pos.y())\n        else:\n            self.setValue(pos.x())\n        self.lineMoved.emit()\n        event.accept()\n\n    def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None:\n        if self._orientation == Qt.Vertical:\n            self.setValue(event.pos().y())\n        else:\n            self.setValue(event.pos().x())\n        self.lineReleased.emit()\n        event.accept()\n\n    def boundingRect(self) -> QRectF:\n        if self._line is None:\n            if self._orientation == Qt.Vertical:\n                self._line = QLineF(0, self._value, self._length, self._value)\n            else:\n                self._line = QLineF(self._value, 0, self._value, self._length)\n        r = QRectF(self._line.p1(), self._line.p2())\n        penw = self.pen().width()\n        return r.adjusted(-penw, -penw, penw, penw)\n\n    def paint(self, painter, *args):\n        if self._line is None:\n            self.boundingRect()\n\n        painter.save()\n        painter.setPen(self.pen())\n        painter.drawLine(self._line)\n        painter.restore()\n\n\ndef clusters_at_height(root, height):\n    \"\"\"Return a list of clusters by cutting the clustering at `height`.\n    \"\"\"\n    lower = set()\n    cluster_list = []\n    for cl in preorder(root):\n        if cl in lower:\n            continue\n        if cl.value.height < height:\n            cluster_list.append(cl)\n            lower.update(preorder(cl))\n    return cluster_list\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    from Orange import distance\n    data = Orange.data.Table(\"iris\")\n    matrix = distance.Euclidean(distance._preprocess(data))\n    WidgetPreview(OWHierarchicalClustering).run(matrix)\n",
            "header": {
                "file": {
                    "path": "Orange/widgets/unsupervised/owhierarchicalclustering.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "6d3f87120",
                "new_commit_hash": "b55c7af91",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/widgets/unsupervised/owhierarchicalclustering.py"
            },
            "plus_file": {
                "path": "b/Orange/widgets/unsupervised/owhierarchicalclustering.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 610,
                            "length": 16
                        },
                        "new_range": {
                            "start": 610,
                            "length": 29
                        },
                        "section": "class OWHierarchicalClustering(widget.OWWidget):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "                selected_data.domain = Domain(",
                                "type": "context"
                            },
                            {
                                "content": "                    attrs, classes, metas + (clust_var, ))",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "            annotated_data = create_annotated_table(data, selected_indices)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        elif isinstance(items, Orange.data.Table) and self.matrix.axis == 0:",
                                "type": "context"
                            },
                            {
                                "content": "            # Select columns",
                                "type": "context"
                            },
                            {
                                "content": "            attrs = []",
                                "type": "added"
                            },
                            {
                                "content": "            for clust, indices in chain(enumerate(maps, start=1),",
                                "type": "added"
                            },
                            {
                                "content": "                                        [(0, unselected_indices)]):",
                                "type": "added"
                            },
                            {
                                "content": "                for i in indices:",
                                "type": "added"
                            },
                            {
                                "content": "                    attr = items.domain[i].copy()",
                                "type": "added"
                            },
                            {
                                "content": "                    attr.attributes[\"cluster\"] = clust",
                                "type": "added"
                            },
                            {
                                "content": "                    attrs.append(attr)",
                                "type": "added"
                            },
                            {
                                "content": "            domain = Orange.data.Domain(",
                                "type": "context"
                            },
                            {
                                "content": "                [items.domain[i] for i in selected_indices],",
                                "type": "deleted"
                            },
                            {
                                "content": "                # len(unselected_indices) can be 0",
                                "type": "added"
                            },
                            {
                                "content": "                attrs[:len(attrs) - len(unselected_indices)],",
                                "type": "added"
                            },
                            {
                                "content": "                items.domain.class_vars, items.domain.metas)",
                                "type": "context"
                            },
                            {
                                "content": "            selected_data = items.from_table(domain, items)",
                                "type": "context"
                            },
                            {
                                "content": "            data = None",
                                "type": "deleted"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "            domain = Orange.data.Domain(",
                                "type": "added"
                            },
                            {
                                "content": "                attrs,",
                                "type": "added"
                            },
                            {
                                "content": "                items.domain.class_vars, items.domain.metas)",
                                "type": "added"
                            },
                            {
                                "content": "            annotated_data = items.from_table(domain, items)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        self.Outputs.selected_data.send(selected_data)",
                                "type": "context"
                            },
                            {
                                "content": "        annotated_data = create_annotated_table(data, selected_indices)",
                                "type": "deleted"
                            },
                            {
                                "content": "        self.Outputs.annotated_data.send(annotated_data)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def eventFilter(self, obj, event):",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/unsupervised/owhierarchicalclustering.py",
                            "type": "class",
                            "name": "OWHierarchicalClustering",
                            "content": "class OWHierarchicalClustering(widget.OWWidget):\n    name = \"Hierarchical Clustering\"\n    description = \"Display a dendrogram of a hierarchical clustering \" \\\n                  \"constructed from the input distance matrix.\"\n    icon = \"icons/HierarchicalClustering.svg\"\n    priority = 2100\n    keywords = []\n\n    class Inputs:\n        distances = Input(\"Distances\", Orange.misc.DistMatrix)\n\n    class Outputs:\n        selected_data = Output(\"Selected Data\", Orange.data.Table, default=True)\n        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table)\n\n    settingsHandler = _DomainContextHandler()\n\n    #: Selected linkage\n    linkage = settings.Setting(1)\n    #: Index of the selected annotation item (variable, ...)\n    annotation = settings.ContextSetting(\"Enumeration\")\n    #: Out-of-context setting for the case when the \"Name\" option is available\n    annotation_if_names = settings.Setting(\"Name\")\n    #: Out-of-context setting for the case with just \"Enumerate\" and \"None\"\n    annotation_if_enumerate = settings.Setting(\"Enumerate\")\n    #: Selected tree pruning (none/max depth)\n    pruning = settings.Setting(0)\n    #: Maximum depth when max depth pruning is selected\n    max_depth = settings.Setting(10)\n\n    #: Selected cluster selection method (none, cut distance, top n)\n    selection_method = settings.Setting(0)\n    #: Cut height ratio wrt root height\n    cut_ratio = settings.Setting(75.0)\n    #: Number of top clusters to select\n    top_n = settings.Setting(3)\n    #: Dendrogram zoom factor\n    zoom_factor = settings.Setting(0)\n\n    autocommit = settings.Setting(True)\n\n    graph_name = \"scene\"\n\n    basic_annotations = [\"None\", \"Enumeration\"]\n\n    class Error(widget.OWWidget.Error):\n        not_finite_distances = Msg(\"Some distances are infinite\")\n\n    #: Stored (manual) selection state (from a saved workflow) to restore.\n    __pending_selection_restore = None  # type: Optional[SelectionState]\n\n    def __init__(self):\n        super().__init__()\n\n        self.matrix = None\n        self.items = None\n        self.linkmatrix = None\n        self.root = None\n        self._displayed_root = None\n        self.cutoff_height = 0.0\n\n        gui.comboBox(\n            self.controlArea, self, \"linkage\", items=LINKAGE, box=\"Linkage\",\n            callback=self._invalidate_clustering)\n\n        model = itemmodels.VariableListModel()\n        model[:] = self.basic_annotations\n\n        box = gui.widgetBox(self.controlArea, \"Annotations\")\n        self.label_cb = cb = combobox.ComboBoxSearch(\n            minimumContentsLength=14,\n            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon\n        )\n        cb.setModel(model)\n        cb.setCurrentIndex(cb.findData(self.annotation, Qt.EditRole))\n\n        def on_annotation_activated():\n            self.annotation = cb.currentData(Qt.EditRole)\n            self._update_labels()\n        cb.activated.connect(on_annotation_activated)\n\n        def on_annotation_changed(value):\n            cb.setCurrentIndex(cb.findData(value, Qt.EditRole))\n        self.connect_control(\"annotation\", on_annotation_changed)\n\n        box.layout().addWidget(self.label_cb)\n\n        box = gui.radioButtons(\n            self.controlArea, self, \"pruning\", box=\"Pruning\",\n            callback=self._invalidate_pruning)\n        grid = QGridLayout()\n        box.layout().addLayout(grid)\n        grid.addWidget(\n            gui.appendRadioButton(box, \"None\", addToLayout=False),\n            0, 0\n        )\n        self.max_depth_spin = gui.spin(\n            box, self, \"max_depth\", minv=1, maxv=100,\n            callback=self._invalidate_pruning,\n            keyboardTracking=False, addToLayout=False\n        )\n\n        grid.addWidget(\n            gui.appendRadioButton(box, \"Max depth:\", addToLayout=False),\n            1, 0)\n        grid.addWidget(self.max_depth_spin, 1, 1)\n\n        self.selection_box = gui.radioButtons(\n            self.controlArea, self, \"selection_method\",\n            box=\"Selection\",\n            callback=self._selection_method_changed)\n\n        grid = QGridLayout()\n        self.selection_box.layout().addLayout(grid)\n        grid.addWidget(\n            gui.appendRadioButton(\n                self.selection_box, \"Manual\", addToLayout=False),\n            0, 0\n        )\n        grid.addWidget(\n            gui.appendRadioButton(\n                self.selection_box, \"Height ratio:\", addToLayout=False),\n            1, 0\n        )\n        self.cut_ratio_spin = gui.spin(\n            self.selection_box, self, \"cut_ratio\", 0, 100, step=1e-1,\n            spinType=float, callback=self._selection_method_changed,\n            addToLayout=False\n        )\n        self.cut_ratio_spin.setSuffix(\"%\")\n\n        grid.addWidget(self.cut_ratio_spin, 1, 1)\n\n        grid.addWidget(\n            gui.appendRadioButton(\n                self.selection_box, \"Top N:\", addToLayout=False),\n            2, 0\n        )\n        self.top_n_spin = gui.spin(self.selection_box, self, \"top_n\", 1, 20,\n                                   callback=self._selection_method_changed,\n                                   addToLayout=False)\n        grid.addWidget(self.top_n_spin, 2, 1)\n\n        self.zoom_slider = gui.hSlider(\n            self.controlArea, self, \"zoom_factor\", box=\"Zoom\",\n            minValue=-6, maxValue=3, step=1, ticks=True, createLabel=False,\n            callback=self.__update_font_scale)\n\n        zoom_in = QAction(\n            \"Zoom in\", self, shortcut=QKeySequence.ZoomIn,\n            triggered=self.__zoom_in\n        )\n        zoom_out = QAction(\n            \"Zoom out\", self, shortcut=QKeySequence.ZoomOut,\n            triggered=self.__zoom_out\n        )\n        zoom_reset = QAction(\n            \"Reset zoom\", self,\n            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0),\n            triggered=self.__zoom_reset\n        )\n        self.addActions([zoom_in, zoom_out, zoom_reset])\n\n        self.controlArea.layout().addStretch()\n\n        gui.auto_send(self.buttonsArea, self, \"autocommit\")\n\n        self.scene = QGraphicsScene(self)\n        self.view = StickyGraphicsView(\n            self.scene,\n            horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff,\n            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn,\n            alignment=Qt.AlignLeft | Qt.AlignVCenter\n        )\n        self.mainArea.layout().setSpacing(1)\n        self.mainArea.layout().addWidget(self.view)\n\n        def axis_view(orientation):\n            ax = AxisItem(orientation=orientation, maxTickLength=7)\n            ax.mousePressed.connect(self._activate_cut_line)\n            ax.mouseMoved.connect(self._activate_cut_line)\n            ax.mouseReleased.connect(self._activate_cut_line)\n            ax.setRange(1.0, 0.0)\n            return ax\n\n        self.top_axis = axis_view(\"top\")\n        self.bottom_axis = axis_view(\"bottom\")\n\n        self._main_graphics = QGraphicsWidget()\n        scenelayout = QGraphicsGridLayout()\n        scenelayout.setHorizontalSpacing(10)\n        scenelayout.setVerticalSpacing(10)\n\n        self._main_graphics.setLayout(scenelayout)\n        self.scene.addItem(self._main_graphics)\n\n        self.dendrogram = DendrogramWidget()\n        self.dendrogram.setSizePolicy(QSizePolicy.MinimumExpanding,\n                                      QSizePolicy.MinimumExpanding)\n        self.dendrogram.selectionChanged.connect(self._invalidate_output)\n        self.dendrogram.selectionEdited.connect(self._selection_edited)\n\n        self.labels = TextListWidget()\n        self.labels.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)\n        self.labels.setAlignment(Qt.AlignLeft)\n        self.labels.setMaximumWidth(200)\n\n        scenelayout.addItem(self.top_axis, 0, 0,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        scenelayout.addItem(self.dendrogram, 1, 0,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        scenelayout.addItem(self.labels, 1, 1,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        scenelayout.addItem(self.bottom_axis, 2, 0,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        self.view.viewport().installEventFilter(self)\n        self._main_graphics.installEventFilter(self)\n\n        self.top_axis.setZValue(self.dendrogram.zValue() + 10)\n        self.bottom_axis.setZValue(self.dendrogram.zValue() + 10)\n        self.cut_line = SliderLine(self.top_axis,\n                                   orientation=Qt.Horizontal)\n        self.cut_line.valueChanged.connect(self._dendrogram_slider_changed)\n        self.dendrogram.geometryChanged.connect(self._dendrogram_geom_changed)\n        self._set_cut_line_visible(self.selection_method == 1)\n        self.__update_font_scale()\n\n    @Inputs.distances\n    def set_distances(self, matrix):\n        if self.__pending_selection_restore is not None:\n            selection_state = self.__pending_selection_restore\n        else:\n            # save the current selection to (possibly) restore later\n            selection_state = self._save_selection()\n\n        self.error()\n        self.Error.clear()\n        if matrix is not None:\n            N, _ = matrix.shape\n            if N < 2:\n                self.error(\"Empty distance matrix\")\n                matrix = None\n        if matrix is not None:\n            if not np.all(np.isfinite(matrix)):\n                self.Error.not_finite_distances()\n                matrix = None\n\n        self.matrix = matrix\n        if matrix is not None:\n            self._set_items(matrix.row_items, matrix.axis)\n        else:\n            self._set_items(None)\n        self._invalidate_clustering()\n\n        # Can now attempt to restore session state from a saved workflow.\n        if self.root and selection_state is not None:\n            self._restore_selection(selection_state)\n            self.__pending_selection_restore = None\n\n        self.unconditional_commit()\n\n    def _set_items(self, items, axis=1):\n        self.closeContext()\n        self.items = items\n        model = self.label_cb.model()\n        if len(model) == 3:\n            self.annotation_if_names = self.annotation\n        elif len(model) == 2:\n            self.annotation_if_enumerate = self.annotation\n        if isinstance(items, Orange.data.Table) and axis:\n            model[:] = chain(\n                self.basic_annotations,\n                [model.Separator],\n                items.domain.class_vars,\n                items.domain.metas,\n                [model.Separator] if (items.domain.class_vars or items.domain.metas) and\n                next(filter_visible(items.domain.attributes), False) else [],\n                filter_visible(items.domain.attributes)\n            )\n            if items.domain.class_vars:\n                self.annotation = items.domain.class_vars[0]\n            else:\n                self.annotation = \"Enumeration\"\n            self.openContext(items.domain)\n        else:\n            name_option = bool(\n                items is not None and (\n                    not axis or\n                    isinstance(items, list) and\n                    all(isinstance(var, Orange.data.Variable) for var in items)))\n            model[:] = self.basic_annotations + [\"Name\"] * name_option\n            self.annotation = self.annotation_if_names if name_option \\\n                else self.annotation_if_enumerate\n\n    def _clear_plot(self):\n        self.dendrogram.set_root(None)\n        self.labels.setItems([])\n\n    def _set_displayed_root(self, root):\n        self._clear_plot()\n        self._displayed_root = root\n        self.dendrogram.set_root(root)\n\n        self._update_labels()\n\n        self._main_graphics.resize(\n            self._main_graphics.size().width(),\n            self._main_graphics.sizeHint(Qt.PreferredSize).height()\n        )\n        self._main_graphics.layout().activate()\n\n    def _update(self):\n        self._clear_plot()\n\n        distances = self.matrix\n\n        if distances is not None:\n            method = LINKAGE[self.linkage].lower()\n            Z = dist_matrix_linkage(distances, linkage=method)\n\n            tree = tree_from_linkage(Z)\n            self.linkmatrix = Z\n            self.root = tree\n\n            self.top_axis.setRange(tree.value.height, 0.0)\n            self.bottom_axis.setRange(tree.value.height, 0.0)\n\n            if self.pruning:\n                self._set_displayed_root(prune(tree, level=self.max_depth))\n            else:\n                self._set_displayed_root(tree)\n        else:\n            self.linkmatrix = None\n            self.root = None\n            self._set_displayed_root(None)\n\n        self._apply_selection()\n\n    def _update_labels(self):\n        labels = []\n        if self.root and self._displayed_root:\n            indices = [leaf.value.index for leaf in leaves(self.root)]\n\n            if self.annotation == \"None\":\n                labels = []\n            elif self.annotation == \"Enumeration\":\n                labels = [str(i+1) for i in indices]\n            elif self.annotation == \"Name\":\n                attr = self.matrix.row_items.domain.attributes\n                labels = [str(attr[i]) for i in indices]\n            elif isinstance(self.annotation, Orange.data.Variable):\n                col_data, _ = self.items.get_column_view(self.annotation)\n                labels = [self.annotation.str_val(val) for val in col_data]\n                labels = [labels[idx] for idx in indices]\n            else:\n                labels = []\n\n            if labels and self._displayed_root is not self.root:\n                joined = leaves(self._displayed_root)\n                labels = [\", \".join(labels[leaf.value.first: leaf.value.last])\n                          for leaf in joined]\n\n        self.labels.setItems(labels)\n        self.labels.setMinimumWidth(1 if labels else -1)\n\n    def _restore_selection(self, state):\n        # type: (SelectionState) -> bool\n        \"\"\"\n        Restore the (manual) node selection state.\n\n        Return True if successful; False otherwise.\n        \"\"\"\n        linkmatrix = self.linkmatrix\n        if self.selection_method == 0 and self.root:\n            selected, linksaved = state\n            linkstruct = np.array(linksaved, dtype=float)\n            selected = set(selected)  # type: Set[Tuple[int]]\n            if not selected:\n                return False\n            if linkmatrix.shape[0] != linkstruct.shape[0]:\n                return False\n            # check that the linkage matrix structure matches. Use isclose for\n            # the height column to account for inexact floating point math\n            # (e.g. summation order in different ?gemm implementations for\n            # euclidean distances, ...)\n            if np.any(linkstruct[:, :2] != linkmatrix[:, :2]) or \\\n                    not np.all(np.isclose(linkstruct[:, 2], linkstruct[:, 2])):\n                return False\n            selection = []\n            indices = np.array([n.value.index for n in leaves(self.root)],\n                               dtype=int)\n            # mapping from ranges to display (pruned) nodes\n            mapping = {node.value.range: node\n                       for node in postorder(self._displayed_root)}\n            for node in postorder(self.root):  # type: Tree\n                r = tuple(indices[node.value.first: node.value.last])\n                if r in selected:\n                    if node.value.range not in mapping:\n                        # the node was pruned from display and cannot be\n                        # selected\n                        break\n                    selection.append(mapping[node.value.range])\n                    selected.remove(r)\n                if not selected:\n                    break  # found all, nothing more to do\n            if selection and selected:\n                # Could not restore all selected nodes (only partial match)\n                return False\n\n            self._set_selected_nodes(selection)\n            return True\n        return False\n\n    def _set_selected_nodes(self, selection):\n        # type: (List[Tree]) -> None\n        \"\"\"\n        Set the nodes in `selection` to be the current selected nodes.\n\n        The selection nodes must be subtrees of the current `_displayed_root`.\n        \"\"\"\n        self.dendrogram.selectionChanged.disconnect(self._invalidate_output)\n        try:\n            self.dendrogram.set_selected_clusters(selection)\n        finally:\n            self.dendrogram.selectionChanged.connect(self._invalidate_output)\n\n    def _invalidate_clustering(self):\n        self._update()\n        self._update_labels()\n        self._invalidate_output()\n\n    def _invalidate_output(self):\n        self.commit()\n\n    def _invalidate_pruning(self):\n        if self.root:\n            selection = self.dendrogram.selected_nodes()\n            ranges = [node.value.range for node in selection]\n            if self.pruning:\n                self._set_displayed_root(\n                    prune(self.root, level=self.max_depth))\n            else:\n                self._set_displayed_root(self.root)\n            selected = [node for node in preorder(self._displayed_root)\n                        if node.value.range in ranges]\n\n            self.dendrogram.set_selected_clusters(selected)\n\n        self._apply_selection()\n\n    def commit(self):\n        items = getattr(self.matrix, \"items\", self.items)\n        if not items:\n            self.Outputs.selected_data.send(None)\n            self.Outputs.annotated_data.send(None)\n            return\n\n        selection = self.dendrogram.selected_nodes()\n        selection = sorted(selection, key=lambda c: c.value.first)\n\n        indices = [leaf.value.index for leaf in leaves(self.root)]\n\n        maps = [indices[node.value.first:node.value.last]\n                for node in selection]\n\n        selected_indices = list(chain(*maps))\n        unselected_indices = sorted(set(range(self.root.value.last)) -\n                                    set(selected_indices))\n\n        if not selected_indices:\n            self.Outputs.selected_data.send(None)\n            annotated_data = create_annotated_table(items, []) \\\n                if self.selection_method == 0 and self.matrix.axis else None\n            self.Outputs.annotated_data.send(annotated_data)\n            return\n\n        selected_data = None\n\n        if isinstance(items, Orange.data.Table) and self.matrix.axis == 1:\n            # Select rows\n            c = np.zeros(self.matrix.shape[0])\n\n            for i, indices in enumerate(maps):\n                c[indices] = i\n            c[unselected_indices] = len(maps)\n\n            mask = c != len(maps)\n\n            data, domain = items, items.domain\n            attrs = domain.attributes\n            classes = domain.class_vars\n            metas = domain.metas\n\n            var_name = get_unique_names(domain, \"Cluster\")\n            values = [f\"C{i + 1}\" for i in range(len(maps))]\n\n            clust_var = Orange.data.DiscreteVariable(\n                var_name, values=values + [\"Other\"])\n            domain = Orange.data.Domain(attrs, classes, metas + (clust_var,))\n            data = items.transform(domain)\n            data.get_column_view(clust_var)[0][:] = c\n\n            if selected_indices:\n                selected_data = data[mask]\n                clust_var = Orange.data.DiscreteVariable(\n                    var_name, values=values)\n                selected_data.domain = Domain(\n                    attrs, classes, metas + (clust_var, ))\n\n        elif isinstance(items, Orange.data.Table) and self.matrix.axis == 0:\n            # Select columns\n            domain = Orange.data.Domain(\n                [items.domain[i] for i in selected_indices],\n                items.domain.class_vars, items.domain.metas)\n            selected_data = items.from_table(domain, items)\n            data = None\n\n        self.Outputs.selected_data.send(selected_data)\n        annotated_data = create_annotated_table(data, selected_indices)\n        self.Outputs.annotated_data.send(annotated_data)\n\n    def eventFilter(self, obj, event):\n        if obj is self.view.viewport() and event.type() == QEvent.Resize:\n            # NOTE: not using viewport.width(), due to 'transient' scroll bars\n            # (macOS). Viewport covers the whole view, but QGraphicsView still\n            # scrolls left, right with scroll bar extent (other\n            # QAbstractScrollArea widgets behave as expected).\n            w_frame = self.view.frameWidth()\n            margin = self.view.viewportMargins()\n            w_scroll = self.view.verticalScrollBar().width()\n            width = (self.view.width() - w_frame * 2 -\n                     margin.left() - margin.right() - w_scroll)\n            # layout with new width constraint\n            self.__layout_main_graphics(width=width)\n        elif obj is self._main_graphics and \\\n                event.type() == QEvent.LayoutRequest:\n            # layout preserving the width (vertical re layout)\n            self.__layout_main_graphics()\n        return super().eventFilter(obj, event)\n\n    @Slot(QPointF)\n    def _activate_cut_line(self, pos: QPointF):\n        \"\"\"Activate cut line selection an set cut value to `pos.x()`.\"\"\"\n        self.selection_method = 1\n        self.cut_line.setValue(pos.x())\n        self._selection_method_changed()\n\n    def onDeleteWidget(self):\n        super().onDeleteWidget()\n        self._clear_plot()\n        self.dendrogram.clear()\n        self.dendrogram.deleteLater()\n\n    def _dendrogram_geom_changed(self):\n        pos = self.dendrogram.pos_at_height(self.cutoff_height)\n        geom = self.dendrogram.geometry()\n        self._set_slider_value(pos.x(), geom.width())\n\n        self.cut_line.setLength(\n            self.bottom_axis.geometry().bottom()\n            - self.top_axis.geometry().top()\n        )\n\n        geom = self._main_graphics.geometry()\n        assert geom.topLeft() == QPointF(0, 0)\n\n        def adjustLeft(rect):\n            rect = QRectF(rect)\n            rect.setLeft(geom.left())\n            return rect\n        margin = 3\n        self.scene.setSceneRect(geom)\n        self.view.setSceneRect(geom)\n        self.view.setHeaderSceneRect(\n            adjustLeft(self.top_axis.geometry()).adjusted(0, 0, 0, margin)\n        )\n        self.view.setFooterSceneRect(\n            adjustLeft(self.bottom_axis.geometry()).adjusted(0, -margin, 0, 0)\n        )\n\n    def _dendrogram_slider_changed(self, value):\n        p = QPointF(value, 0)\n        cl_height = self.dendrogram.height_at(p)\n\n        self.set_cutoff_height(cl_height)\n\n    def _set_slider_value(self, value, span):\n        with blocked(self.cut_line):\n            self.cut_line.setRange(0, span)\n            self.cut_line.setValue(value)\n\n    def set_cutoff_height(self, height):\n        self.cutoff_height = height\n        if self.root:\n            self.cut_ratio = 100 * height / self.root.value.height\n        self.select_max_height(height)\n\n    def _set_cut_line_visible(self, visible):\n        self.cut_line.setVisible(visible)\n\n    def select_top_n(self, n):\n        root = self._displayed_root\n        if root:\n            clusters = top_clusters(root, n)\n            self.dendrogram.set_selected_clusters(clusters)\n\n    def select_max_height(self, height):\n        root = self._displayed_root\n        if root:\n            clusters = clusters_at_height(root, height)\n            self.dendrogram.set_selected_clusters(clusters)\n\n    def _selection_method_changed(self):\n        self._set_cut_line_visible(self.selection_method == 1)\n        if self.root:\n            self._apply_selection()\n\n    def _apply_selection(self):\n        if not self.root:\n            return\n\n        if self.selection_method == 0:\n            pass\n        elif self.selection_method == 1:\n            height = self.cut_ratio * self.root.value.height / 100\n            self.set_cutoff_height(height)\n            pos = self.dendrogram.pos_at_height(height)\n            self._set_slider_value(pos.x(), self.dendrogram.size().width())\n        elif self.selection_method == 2:\n            self.select_top_n(self.top_n)\n\n    def _selection_edited(self):\n        # Selection was edited by clicking on a cluster in the\n        # dendrogram view.\n        self.selection_method = 0\n        self._selection_method_changed()\n        self._invalidate_output()\n\n    def _save_selection(self):\n        # Save the current manual node selection state\n        selection_state = None\n        if self.selection_method == 0 and self.root:\n            assert self.linkmatrix is not None\n            linkmat = [(int(_0), int(_1), _2)\n                       for _0, _1, _2 in self.linkmatrix[:, :3].tolist()]\n            nodes_ = self.dendrogram.selected_nodes()\n            # match the display (pruned) nodes back (by ranges)\n            mapping = {node.value.range: node for node in postorder(self.root)}\n            nodes = [mapping[node.value.range] for node in nodes_]\n            indices = [tuple(node.value.index for node in leaves(node))\n                       for node in nodes]\n            if nodes:\n                selection_state = (indices, linkmat)\n        return selection_state\n\n    def save_state(self):\n        # type: () -> Dict[str, Any]\n        \"\"\"\n        Save state for `set_restore_state`\n        \"\"\"\n        selection = self._save_selection()\n        res = {\"version\": (0, 0, 0)}\n        if selection is not None:\n            res[\"selection_state\"] = selection\n        return res\n\n    def set_restore_state(self, state):\n        # type: (Dict[str, Any]) -> bool\n        \"\"\"\n        Restore session data from a saved state.\n\n        Parameters\n        ----------\n        state : Dict[str, Any]\n\n        NOTE\n        ----\n        This is method called while the instance (self) is being constructed,\n        even before its `__init__` is called. Consider `self` to be only a\n        `QObject` at this stage.\n        \"\"\"\n        if \"selection_state\" in state:\n            selection = state[\"selection_state\"]\n            self.__pending_selection_restore = selection\n        return True\n\n    def __zoom_in(self):\n        def clip(minval, maxval, val):\n            return min(max(val, minval), maxval)\n        self.zoom_factor = clip(self.zoom_slider.minimum(),\n                                self.zoom_slider.maximum(),\n                                self.zoom_factor + 1)\n        self.__update_font_scale()\n\n    def __zoom_out(self):\n        def clip(minval, maxval, val):\n            return min(max(val, minval), maxval)\n        self.zoom_factor = clip(self.zoom_slider.minimum(),\n                                self.zoom_slider.maximum(),\n                                self.zoom_factor - 1)\n        self.__update_font_scale()\n\n    def __zoom_reset(self):\n        self.zoom_factor = 0\n        self.__update_font_scale()\n\n    def __layout_main_graphics(self, width=-1):\n        if width < 0:\n            # Preserve current width.\n            width = self._main_graphics.size().width()\n        preferred = self._main_graphics.effectiveSizeHint(\n            Qt.PreferredSize, constraint=QSizeF(width, -1))\n        self._main_graphics.resize(QSizeF(width, preferred.height()))\n        mw = self._main_graphics.minimumWidth() + 4\n        self.view.setMinimumWidth(mw + self.view.verticalScrollBar().width())\n\n    def __update_font_scale(self):\n        font = self.scene.font()\n        factor = (1.25 ** self.zoom_factor)\n        font = qfont_scaled(font, factor)\n        self._main_graphics.setFont(font)\n\n    def send_report(self):\n        annot = self.label_cb.currentText()\n        if isinstance(self.annotation, str):\n            annot = annot.lower()\n        if self.selection_method == 0:\n            sel = \"manual\"\n        elif self.selection_method == 1:\n            sel = \"at {:.1f} of height\".format(self.cut_ratio)\n        else:\n            sel = \"top {} clusters\".format(self.top_n)\n        self.report_items((\n            (\"Linkage\", LINKAGE[self.linkage].lower()),\n            (\"Annotation\", annot),\n            (\"Prunning\",\n             self.pruning != 0 and \"{} levels\".format(self.max_depth)),\n            (\"Selection\", sel),\n        ))\n        self.report_plot()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 104,
                            "end_lineno": 843,
                            "parent": null
                        },
                        {
                            "file_name": "Orange/widgets/unsupervised/owhierarchicalclustering.py",
                            "type": "method",
                            "name": "OWHierarchicalClustering.commit",
                            "content": "class OWHierarchicalClustering(widget.OWWidget):\n    name = \"Hierarchical Clustering\"\n    description = \"Display a dendrogram of a hierarchical clustering \" \\\n                  \"constructed from the input distance matrix.\"\n    icon = \"icons/HierarchicalClustering.svg\"\n    priority = 2100\n    keywords = []\n\n    class Inputs:\n        distances = Input(\"Distances\", Orange.misc.DistMatrix)\n\n    class Outputs:\n        selected_data = Output(\"Selected Data\", Orange.data.Table, default=True)\n        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table)\n\n    settingsHandler = _DomainContextHandler()\n\n    #: Selected linkage\n    linkage = settings.Setting(1)\n    #: Index of the selected annotation item (variable, ...)\n    annotation = settings.ContextSetting(\"Enumeration\")\n    #: Out-of-context setting for the case when the \"Name\" option is available\n    annotation_if_names = settings.Setting(\"Name\")\n    #: Out-of-context setting for the case with just \"Enumerate\" and \"None\"\n    annotation_if_enumerate = settings.Setting(\"Enumerate\")\n    #: Selected tree pruning (none/max depth)\n    pruning = settings.Setting(0)\n    #: Maximum depth when max depth pruning is selected\n    max_depth = settings.Setting(10)\n\n    #: Selected cluster selection method (none, cut distance, top n)\n    selection_method = settings.Setting(0)\n    #: Cut height ratio wrt root height\n    cut_ratio = settings.Setting(75.0)\n    #: Number of top clusters to select\n    top_n = settings.Setting(3)\n    #: Dendrogram zoom factor\n    zoom_factor = settings.Setting(0)\n\n    autocommit = settings.Setting(True)\n\n    graph_name = \"scene\"\n\n    basic_annotations = [\"None\", \"Enumeration\"]\n\n    class Error(widget.OWWidget.Error):\n        not_finite_distances = Msg(\"Some distances are infinite\")\n\n    #: Stored (manual) selection state (from a saved workflow) to restore.\n    __pending_selection_restore = None  # type: Optional[SelectionState]\n\n    def __init__(self):\n        super().__init__()\n\n        self.matrix = None\n        self.items = None\n        self.linkmatrix = None\n        self.root = None\n        self._displayed_root = None\n        self.cutoff_height = 0.0\n\n        gui.comboBox(\n            self.controlArea, self, \"linkage\", items=LINKAGE, box=\"Linkage\",\n            callback=self._invalidate_clustering)\n\n        model = itemmodels.VariableListModel()\n        model[:] = self.basic_annotations\n\n        box = gui.widgetBox(self.controlArea, \"Annotations\")\n        self.label_cb = cb = combobox.ComboBoxSearch(\n            minimumContentsLength=14,\n            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon\n        )\n        cb.setModel(model)\n        cb.setCurrentIndex(cb.findData(self.annotation, Qt.EditRole))\n\n        def on_annotation_activated():\n            self.annotation = cb.currentData(Qt.EditRole)\n            self._update_labels()\n        cb.activated.connect(on_annotation_activated)\n\n        def on_annotation_changed(value):\n            cb.setCurrentIndex(cb.findData(value, Qt.EditRole))\n        self.connect_control(\"annotation\", on_annotation_changed)\n\n        box.layout().addWidget(self.label_cb)\n\n        box = gui.radioButtons(\n            self.controlArea, self, \"pruning\", box=\"Pruning\",\n            callback=self._invalidate_pruning)\n        grid = QGridLayout()\n        box.layout().addLayout(grid)\n        grid.addWidget(\n            gui.appendRadioButton(box, \"None\", addToLayout=False),\n            0, 0\n        )\n        self.max_depth_spin = gui.spin(\n            box, self, \"max_depth\", minv=1, maxv=100,\n            callback=self._invalidate_pruning,\n            keyboardTracking=False, addToLayout=False\n        )\n\n        grid.addWidget(\n            gui.appendRadioButton(box, \"Max depth:\", addToLayout=False),\n            1, 0)\n        grid.addWidget(self.max_depth_spin, 1, 1)\n\n        self.selection_box = gui.radioButtons(\n            self.controlArea, self, \"selection_method\",\n            box=\"Selection\",\n            callback=self._selection_method_changed)\n\n        grid = QGridLayout()\n        self.selection_box.layout().addLayout(grid)\n        grid.addWidget(\n            gui.appendRadioButton(\n                self.selection_box, \"Manual\", addToLayout=False),\n            0, 0\n        )\n        grid.addWidget(\n            gui.appendRadioButton(\n                self.selection_box, \"Height ratio:\", addToLayout=False),\n            1, 0\n        )\n        self.cut_ratio_spin = gui.spin(\n            self.selection_box, self, \"cut_ratio\", 0, 100, step=1e-1,\n            spinType=float, callback=self._selection_method_changed,\n            addToLayout=False\n        )\n        self.cut_ratio_spin.setSuffix(\"%\")\n\n        grid.addWidget(self.cut_ratio_spin, 1, 1)\n\n        grid.addWidget(\n            gui.appendRadioButton(\n                self.selection_box, \"Top N:\", addToLayout=False),\n            2, 0\n        )\n        self.top_n_spin = gui.spin(self.selection_box, self, \"top_n\", 1, 20,\n                                   callback=self._selection_method_changed,\n                                   addToLayout=False)\n        grid.addWidget(self.top_n_spin, 2, 1)\n\n        self.zoom_slider = gui.hSlider(\n            self.controlArea, self, \"zoom_factor\", box=\"Zoom\",\n            minValue=-6, maxValue=3, step=1, ticks=True, createLabel=False,\n            callback=self.__update_font_scale)\n\n        zoom_in = QAction(\n            \"Zoom in\", self, shortcut=QKeySequence.ZoomIn,\n            triggered=self.__zoom_in\n        )\n        zoom_out = QAction(\n            \"Zoom out\", self, shortcut=QKeySequence.ZoomOut,\n            triggered=self.__zoom_out\n        )\n        zoom_reset = QAction(\n            \"Reset zoom\", self,\n            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0),\n            triggered=self.__zoom_reset\n        )\n        self.addActions([zoom_in, zoom_out, zoom_reset])\n\n        self.controlArea.layout().addStretch()\n\n        gui.auto_send(self.buttonsArea, self, \"autocommit\")\n\n        self.scene = QGraphicsScene(self)\n        self.view = StickyGraphicsView(\n            self.scene,\n            horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff,\n            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn,\n            alignment=Qt.AlignLeft | Qt.AlignVCenter\n        )\n        self.mainArea.layout().setSpacing(1)\n        self.mainArea.layout().addWidget(self.view)\n\n        def axis_view(orientation):\n            ax = AxisItem(orientation=orientation, maxTickLength=7)\n            ax.mousePressed.connect(self._activate_cut_line)\n            ax.mouseMoved.connect(self._activate_cut_line)\n            ax.mouseReleased.connect(self._activate_cut_line)\n            ax.setRange(1.0, 0.0)\n            return ax\n\n        self.top_axis = axis_view(\"top\")\n        self.bottom_axis = axis_view(\"bottom\")\n\n        self._main_graphics = QGraphicsWidget()\n        scenelayout = QGraphicsGridLayout()\n        scenelayout.setHorizontalSpacing(10)\n        scenelayout.setVerticalSpacing(10)\n\n        self._main_graphics.setLayout(scenelayout)\n        self.scene.addItem(self._main_graphics)\n\n        self.dendrogram = DendrogramWidget()\n        self.dendrogram.setSizePolicy(QSizePolicy.MinimumExpanding,\n                                      QSizePolicy.MinimumExpanding)\n        self.dendrogram.selectionChanged.connect(self._invalidate_output)\n        self.dendrogram.selectionEdited.connect(self._selection_edited)\n\n        self.labels = TextListWidget()\n        self.labels.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)\n        self.labels.setAlignment(Qt.AlignLeft)\n        self.labels.setMaximumWidth(200)\n\n        scenelayout.addItem(self.top_axis, 0, 0,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        scenelayout.addItem(self.dendrogram, 1, 0,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        scenelayout.addItem(self.labels, 1, 1,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        scenelayout.addItem(self.bottom_axis, 2, 0,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        self.view.viewport().installEventFilter(self)\n        self._main_graphics.installEventFilter(self)\n\n        self.top_axis.setZValue(self.dendrogram.zValue() + 10)\n        self.bottom_axis.setZValue(self.dendrogram.zValue() + 10)\n        self.cut_line = SliderLine(self.top_axis,\n                                   orientation=Qt.Horizontal)\n        self.cut_line.valueChanged.connect(self._dendrogram_slider_changed)\n        self.dendrogram.geometryChanged.connect(self._dendrogram_geom_changed)\n        self._set_cut_line_visible(self.selection_method == 1)\n        self.__update_font_scale()\n\n    @Inputs.distances\n    def set_distances(self, matrix):\n        if self.__pending_selection_restore is not None:\n            selection_state = self.__pending_selection_restore\n        else:\n            # save the current selection to (possibly) restore later\n            selection_state = self._save_selection()\n\n        self.error()\n        self.Error.clear()\n        if matrix is not None:\n            N, _ = matrix.shape\n            if N < 2:\n                self.error(\"Empty distance matrix\")\n                matrix = None\n        if matrix is not None:\n            if not np.all(np.isfinite(matrix)):\n                self.Error.not_finite_distances()\n                matrix = None\n\n        self.matrix = matrix\n        if matrix is not None:\n            self._set_items(matrix.row_items, matrix.axis)\n        else:\n            self._set_items(None)\n        self._invalidate_clustering()\n\n        # Can now attempt to restore session state from a saved workflow.\n        if self.root and selection_state is not None:\n            self._restore_selection(selection_state)\n            self.__pending_selection_restore = None\n\n        self.unconditional_commit()\n\n    def _set_items(self, items, axis=1):\n        self.closeContext()\n        self.items = items\n        model = self.label_cb.model()\n        if len(model) == 3:\n            self.annotation_if_names = self.annotation\n        elif len(model) == 2:\n            self.annotation_if_enumerate = self.annotation\n        if isinstance(items, Orange.data.Table) and axis:\n            model[:] = chain(\n                self.basic_annotations,\n                [model.Separator],\n                items.domain.class_vars,\n                items.domain.metas,\n                [model.Separator] if (items.domain.class_vars or items.domain.metas) and\n                next(filter_visible(items.domain.attributes), False) else [],\n                filter_visible(items.domain.attributes)\n            )\n            if items.domain.class_vars:\n                self.annotation = items.domain.class_vars[0]\n            else:\n                self.annotation = \"Enumeration\"\n            self.openContext(items.domain)\n        else:\n            name_option = bool(\n                items is not None and (\n                    not axis or\n                    isinstance(items, list) and\n                    all(isinstance(var, Orange.data.Variable) for var in items)))\n            model[:] = self.basic_annotations + [\"Name\"] * name_option\n            self.annotation = self.annotation_if_names if name_option \\\n                else self.annotation_if_enumerate\n\n    def _clear_plot(self):\n        self.dendrogram.set_root(None)\n        self.labels.setItems([])\n\n    def _set_displayed_root(self, root):\n        self._clear_plot()\n        self._displayed_root = root\n        self.dendrogram.set_root(root)\n\n        self._update_labels()\n\n        self._main_graphics.resize(\n            self._main_graphics.size().width(),\n            self._main_graphics.sizeHint(Qt.PreferredSize).height()\n        )\n        self._main_graphics.layout().activate()\n\n    def _update(self):\n        self._clear_plot()\n\n        distances = self.matrix\n\n        if distances is not None:\n            method = LINKAGE[self.linkage].lower()\n            Z = dist_matrix_linkage(distances, linkage=method)\n\n            tree = tree_from_linkage(Z)\n            self.linkmatrix = Z\n            self.root = tree\n\n            self.top_axis.setRange(tree.value.height, 0.0)\n            self.bottom_axis.setRange(tree.value.height, 0.0)\n\n            if self.pruning:\n                self._set_displayed_root(prune(tree, level=self.max_depth))\n            else:\n                self._set_displayed_root(tree)\n        else:\n            self.linkmatrix = None\n            self.root = None\n            self._set_displayed_root(None)\n\n        self._apply_selection()\n\n    def _update_labels(self):\n        labels = []\n        if self.root and self._displayed_root:\n            indices = [leaf.value.index for leaf in leaves(self.root)]\n\n            if self.annotation == \"None\":\n                labels = []\n            elif self.annotation == \"Enumeration\":\n                labels = [str(i+1) for i in indices]\n            elif self.annotation == \"Name\":\n                attr = self.matrix.row_items.domain.attributes\n                labels = [str(attr[i]) for i in indices]\n            elif isinstance(self.annotation, Orange.data.Variable):\n                col_data, _ = self.items.get_column_view(self.annotation)\n                labels = [self.annotation.str_val(val) for val in col_data]\n                labels = [labels[idx] for idx in indices]\n            else:\n                labels = []\n\n            if labels and self._displayed_root is not self.root:\n                joined = leaves(self._displayed_root)\n                labels = [\", \".join(labels[leaf.value.first: leaf.value.last])\n                          for leaf in joined]\n\n        self.labels.setItems(labels)\n        self.labels.setMinimumWidth(1 if labels else -1)\n\n    def _restore_selection(self, state):\n        # type: (SelectionState) -> bool\n        \"\"\"\n        Restore the (manual) node selection state.\n\n        Return True if successful; False otherwise.\n        \"\"\"\n        linkmatrix = self.linkmatrix\n        if self.selection_method == 0 and self.root:\n            selected, linksaved = state\n            linkstruct = np.array(linksaved, dtype=float)\n            selected = set(selected)  # type: Set[Tuple[int]]\n            if not selected:\n                return False\n            if linkmatrix.shape[0] != linkstruct.shape[0]:\n                return False\n            # check that the linkage matrix structure matches. Use isclose for\n            # the height column to account for inexact floating point math\n            # (e.g. summation order in different ?gemm implementations for\n            # euclidean distances, ...)\n            if np.any(linkstruct[:, :2] != linkmatrix[:, :2]) or \\\n                    not np.all(np.isclose(linkstruct[:, 2], linkstruct[:, 2])):\n                return False\n            selection = []\n            indices = np.array([n.value.index for n in leaves(self.root)],\n                               dtype=int)\n            # mapping from ranges to display (pruned) nodes\n            mapping = {node.value.range: node\n                       for node in postorder(self._displayed_root)}\n            for node in postorder(self.root):  # type: Tree\n                r = tuple(indices[node.value.first: node.value.last])\n                if r in selected:\n                    if node.value.range not in mapping:\n                        # the node was pruned from display and cannot be\n                        # selected\n                        break\n                    selection.append(mapping[node.value.range])\n                    selected.remove(r)\n                if not selected:\n                    break  # found all, nothing more to do\n            if selection and selected:\n                # Could not restore all selected nodes (only partial match)\n                return False\n\n            self._set_selected_nodes(selection)\n            return True\n        return False\n\n    def _set_selected_nodes(self, selection):\n        # type: (List[Tree]) -> None\n        \"\"\"\n        Set the nodes in `selection` to be the current selected nodes.\n\n        The selection nodes must be subtrees of the current `_displayed_root`.\n        \"\"\"\n        self.dendrogram.selectionChanged.disconnect(self._invalidate_output)\n        try:\n            self.dendrogram.set_selected_clusters(selection)\n        finally:\n            self.dendrogram.selectionChanged.connect(self._invalidate_output)\n\n    def _invalidate_clustering(self):\n        self._update()\n        self._update_labels()\n        self._invalidate_output()\n\n    def _invalidate_output(self):\n        self.commit()\n\n    def _invalidate_pruning(self):\n        if self.root:\n            selection = self.dendrogram.selected_nodes()\n            ranges = [node.value.range for node in selection]\n            if self.pruning:\n                self._set_displayed_root(\n                    prune(self.root, level=self.max_depth))\n            else:\n                self._set_displayed_root(self.root)\n            selected = [node for node in preorder(self._displayed_root)\n                        if node.value.range in ranges]\n\n            self.dendrogram.set_selected_clusters(selected)\n\n        self._apply_selection()\n\n    def commit(self):\n        items = getattr(self.matrix, \"items\", self.items)\n        if not items:\n            self.Outputs.selected_data.send(None)\n            self.Outputs.annotated_data.send(None)\n            return\n\n        selection = self.dendrogram.selected_nodes()\n        selection = sorted(selection, key=lambda c: c.value.first)\n\n        indices = [leaf.value.index for leaf in leaves(self.root)]\n\n        maps = [indices[node.value.first:node.value.last]\n                for node in selection]\n\n        selected_indices = list(chain(*maps))\n        unselected_indices = sorted(set(range(self.root.value.last)) -\n                                    set(selected_indices))\n\n        if not selected_indices:\n            self.Outputs.selected_data.send(None)\n            annotated_data = create_annotated_table(items, []) \\\n                if self.selection_method == 0 and self.matrix.axis else None\n            self.Outputs.annotated_data.send(annotated_data)\n            return\n\n        selected_data = None\n\n        if isinstance(items, Orange.data.Table) and self.matrix.axis == 1:\n            # Select rows\n            c = np.zeros(self.matrix.shape[0])\n\n            for i, indices in enumerate(maps):\n                c[indices] = i\n            c[unselected_indices] = len(maps)\n\n            mask = c != len(maps)\n\n            data, domain = items, items.domain\n            attrs = domain.attributes\n            classes = domain.class_vars\n            metas = domain.metas\n\n            var_name = get_unique_names(domain, \"Cluster\")\n            values = [f\"C{i + 1}\" for i in range(len(maps))]\n\n            clust_var = Orange.data.DiscreteVariable(\n                var_name, values=values + [\"Other\"])\n            domain = Orange.data.Domain(attrs, classes, metas + (clust_var,))\n            data = items.transform(domain)\n            data.get_column_view(clust_var)[0][:] = c\n\n            if selected_indices:\n                selected_data = data[mask]\n                clust_var = Orange.data.DiscreteVariable(\n                    var_name, values=values)\n                selected_data.domain = Domain(\n                    attrs, classes, metas + (clust_var, ))\n\n        elif isinstance(items, Orange.data.Table) and self.matrix.axis == 0:\n            # Select columns\n            domain = Orange.data.Domain(\n                [items.domain[i] for i in selected_indices],\n                items.domain.class_vars, items.domain.metas)\n            selected_data = items.from_table(domain, items)\n            data = None\n\n        self.Outputs.selected_data.send(selected_data)\n        annotated_data = create_annotated_table(data, selected_indices)\n        self.Outputs.annotated_data.send(annotated_data)\n\n    def eventFilter(self, obj, event):\n        if obj is self.view.viewport() and event.type() == QEvent.Resize:\n            # NOTE: not using viewport.width(), due to 'transient' scroll bars\n            # (macOS). Viewport covers the whole view, but QGraphicsView still\n            # scrolls left, right with scroll bar extent (other\n            # QAbstractScrollArea widgets behave as expected).\n            w_frame = self.view.frameWidth()\n            margin = self.view.viewportMargins()\n            w_scroll = self.view.verticalScrollBar().width()\n            width = (self.view.width() - w_frame * 2 -\n                     margin.left() - margin.right() - w_scroll)\n            # layout with new width constraint\n            self.__layout_main_graphics(width=width)\n        elif obj is self._main_graphics and \\\n                event.type() == QEvent.LayoutRequest:\n            # layout preserving the width (vertical re layout)\n            self.__layout_main_graphics()\n        return super().eventFilter(obj, event)\n\n    @Slot(QPointF)\n    def _activate_cut_line(self, pos: QPointF):\n        \"\"\"Activate cut line selection an set cut value to `pos.x()`.\"\"\"\n        self.selection_method = 1\n        self.cut_line.setValue(pos.x())\n        self._selection_method_changed()\n\n    def onDeleteWidget(self):\n        super().onDeleteWidget()\n        self._clear_plot()\n        self.dendrogram.clear()\n        self.dendrogram.deleteLater()\n\n    def _dendrogram_geom_changed(self):\n        pos = self.dendrogram.pos_at_height(self.cutoff_height)\n        geom = self.dendrogram.geometry()\n        self._set_slider_value(pos.x(), geom.width())\n\n        self.cut_line.setLength(\n            self.bottom_axis.geometry().bottom()\n            - self.top_axis.geometry().top()\n        )\n\n        geom = self._main_graphics.geometry()\n        assert geom.topLeft() == QPointF(0, 0)\n\n        def adjustLeft(rect):\n            rect = QRectF(rect)\n            rect.setLeft(geom.left())\n            return rect\n        margin = 3\n        self.scene.setSceneRect(geom)\n        self.view.setSceneRect(geom)\n        self.view.setHeaderSceneRect(\n            adjustLeft(self.top_axis.geometry()).adjusted(0, 0, 0, margin)\n        )\n        self.view.setFooterSceneRect(\n            adjustLeft(self.bottom_axis.geometry()).adjusted(0, -margin, 0, 0)\n        )\n\n    def _dendrogram_slider_changed(self, value):\n        p = QPointF(value, 0)\n        cl_height = self.dendrogram.height_at(p)\n\n        self.set_cutoff_height(cl_height)\n\n    def _set_slider_value(self, value, span):\n        with blocked(self.cut_line):\n            self.cut_line.setRange(0, span)\n            self.cut_line.setValue(value)\n\n    def set_cutoff_height(self, height):\n        self.cutoff_height = height\n        if self.root:\n            self.cut_ratio = 100 * height / self.root.value.height\n        self.select_max_height(height)\n\n    def _set_cut_line_visible(self, visible):\n        self.cut_line.setVisible(visible)\n\n    def select_top_n(self, n):\n        root = self._displayed_root\n        if root:\n            clusters = top_clusters(root, n)\n            self.dendrogram.set_selected_clusters(clusters)\n\n    def select_max_height(self, height):\n        root = self._displayed_root\n        if root:\n            clusters = clusters_at_height(root, height)\n            self.dendrogram.set_selected_clusters(clusters)\n\n    def _selection_method_changed(self):\n        self._set_cut_line_visible(self.selection_method == 1)\n        if self.root:\n            self._apply_selection()\n\n    def _apply_selection(self):\n        if not self.root:\n            return\n\n        if self.selection_method == 0:\n            pass\n        elif self.selection_method == 1:\n            height = self.cut_ratio * self.root.value.height / 100\n            self.set_cutoff_height(height)\n            pos = self.dendrogram.pos_at_height(height)\n            self._set_slider_value(pos.x(), self.dendrogram.size().width())\n        elif self.selection_method == 2:\n            self.select_top_n(self.top_n)\n\n    def _selection_edited(self):\n        # Selection was edited by clicking on a cluster in the\n        # dendrogram view.\n        self.selection_method = 0\n        self._selection_method_changed()\n        self._invalidate_output()\n\n    def _save_selection(self):\n        # Save the current manual node selection state\n        selection_state = None\n        if self.selection_method == 0 and self.root:\n            assert self.linkmatrix is not None\n            linkmat = [(int(_0), int(_1), _2)\n                       for _0, _1, _2 in self.linkmatrix[:, :3].tolist()]\n            nodes_ = self.dendrogram.selected_nodes()\n            # match the display (pruned) nodes back (by ranges)\n            mapping = {node.value.range: node for node in postorder(self.root)}\n            nodes = [mapping[node.value.range] for node in nodes_]\n            indices = [tuple(node.value.index for node in leaves(node))\n                       for node in nodes]\n            if nodes:\n                selection_state = (indices, linkmat)\n        return selection_state\n\n    def save_state(self):\n        # type: () -> Dict[str, Any]\n        \"\"\"\n        Save state for `set_restore_state`\n        \"\"\"\n        selection = self._save_selection()\n        res = {\"version\": (0, 0, 0)}\n        if selection is not None:\n            res[\"selection_state\"] = selection\n        return res\n\n    def set_restore_state(self, state):\n        # type: (Dict[str, Any]) -> bool\n        \"\"\"\n        Restore session data from a saved state.\n\n        Parameters\n        ----------\n        state : Dict[str, Any]\n\n        NOTE\n        ----\n        This is method called while the instance (self) is being constructed,\n        even before its `__init__` is called. Consider `self` to be only a\n        `QObject` at this stage.\n        \"\"\"\n        if \"selection_state\" in state:\n            selection = state[\"selection_state\"]\n            self.__pending_selection_restore = selection\n        return True\n\n    def __zoom_in(self):\n        def clip(minval, maxval, val):\n            return min(max(val, minval), maxval)\n        self.zoom_factor = clip(self.zoom_slider.minimum(),\n                                self.zoom_slider.maximum(),\n                                self.zoom_factor + 1)\n        self.__update_font_scale()\n\n    def __zoom_out(self):\n        def clip(minval, maxval, val):\n            return min(max(val, minval), maxval)\n        self.zoom_factor = clip(self.zoom_slider.minimum(),\n                                self.zoom_slider.maximum(),\n                                self.zoom_factor - 1)\n        self.__update_font_scale()\n\n    def __zoom_reset(self):\n        self.zoom_factor = 0\n        self.__update_font_scale()\n\n    def __layout_main_graphics(self, width=-1):\n        if width < 0:\n            # Preserve current width.\n            width = self._main_graphics.size().width()\n        preferred = self._main_graphics.effectiveSizeHint(\n            Qt.PreferredSize, constraint=QSizeF(width, -1))\n        self._main_graphics.resize(QSizeF(width, preferred.height()))\n        mw = self._main_graphics.minimumWidth() + 4\n        self.view.setMinimumWidth(mw + self.view.verticalScrollBar().width())\n\n    def __update_font_scale(self):\n        font = self.scene.font()\n        factor = (1.25 ** self.zoom_factor)\n        font = qfont_scaled(font, factor)\n        self._main_graphics.setFont(font)\n\n    def send_report(self):\n        annot = self.label_cb.currentText()\n        if isinstance(self.annotation, str):\n            annot = annot.lower()\n        if self.selection_method == 0:\n            sel = \"manual\"\n        elif self.selection_method == 1:\n            sel = \"at {:.1f} of height\".format(self.cut_ratio)\n        else:\n            sel = \"top {} clusters\".format(self.top_n)\n        self.report_items((\n            (\"Linkage\", LINKAGE[self.linkage].lower()),\n            (\"Annotation\", annot),\n            (\"Prunning\",\n             self.pruning != 0 and \"{} levels\".format(self.max_depth)),\n            (\"Selection\", sel),\n        ))\n        self.report_plot()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 554,
                            "end_lineno": 623,
                            "parent": {
                                "file_name": "Orange/widgets/unsupervised/owhierarchicalclustering.py",
                                "type": "class",
                                "name": "OWHierarchicalClustering",
                                "content": "class OWHierarchicalClustering(widget.OWWidget):\n    name = \"Hierarchical Clustering\"\n    description = \"Display a dendrogram of a hierarchical clustering \" \\\n                  \"constructed from the input distance matrix.\"\n    icon = \"icons/HierarchicalClustering.svg\"\n    priority = 2100\n    keywords = []\n\n    class Inputs:\n        distances = Input(\"Distances\", Orange.misc.DistMatrix)\n\n    class Outputs:\n        selected_data = Output(\"Selected Data\", Orange.data.Table, default=True)\n        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table)\n\n    settingsHandler = _DomainContextHandler()\n\n    #: Selected linkage\n    linkage = settings.Setting(1)\n    #: Index of the selected annotation item (variable, ...)\n    annotation = settings.ContextSetting(\"Enumeration\")\n    #: Out-of-context setting for the case when the \"Name\" option is available\n    annotation_if_names = settings.Setting(\"Name\")\n    #: Out-of-context setting for the case with just \"Enumerate\" and \"None\"\n    annotation_if_enumerate = settings.Setting(\"Enumerate\")\n    #: Selected tree pruning (none/max depth)\n    pruning = settings.Setting(0)\n    #: Maximum depth when max depth pruning is selected\n    max_depth = settings.Setting(10)\n\n    #: Selected cluster selection method (none, cut distance, top n)\n    selection_method = settings.Setting(0)\n    #: Cut height ratio wrt root height\n    cut_ratio = settings.Setting(75.0)\n    #: Number of top clusters to select\n    top_n = settings.Setting(3)\n    #: Dendrogram zoom factor\n    zoom_factor = settings.Setting(0)\n\n    autocommit = settings.Setting(True)\n\n    graph_name = \"scene\"\n\n    basic_annotations = [\"None\", \"Enumeration\"]\n\n    class Error(widget.OWWidget.Error):\n        not_finite_distances = Msg(\"Some distances are infinite\")\n\n    #: Stored (manual) selection state (from a saved workflow) to restore.\n    __pending_selection_restore = None  # type: Optional[SelectionState]\n\n    def __init__(self):\n        super().__init__()\n\n        self.matrix = None\n        self.items = None\n        self.linkmatrix = None\n        self.root = None\n        self._displayed_root = None\n        self.cutoff_height = 0.0\n\n        gui.comboBox(\n            self.controlArea, self, \"linkage\", items=LINKAGE, box=\"Linkage\",\n            callback=self._invalidate_clustering)\n\n        model = itemmodels.VariableListModel()\n        model[:] = self.basic_annotations\n\n        box = gui.widgetBox(self.controlArea, \"Annotations\")\n        self.label_cb = cb = combobox.ComboBoxSearch(\n            minimumContentsLength=14,\n            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon\n        )\n        cb.setModel(model)\n        cb.setCurrentIndex(cb.findData(self.annotation, Qt.EditRole))\n\n        def on_annotation_activated():\n            self.annotation = cb.currentData(Qt.EditRole)\n            self._update_labels()\n        cb.activated.connect(on_annotation_activated)\n\n        def on_annotation_changed(value):\n            cb.setCurrentIndex(cb.findData(value, Qt.EditRole))\n        self.connect_control(\"annotation\", on_annotation_changed)\n\n        box.layout().addWidget(self.label_cb)\n\n        box = gui.radioButtons(\n            self.controlArea, self, \"pruning\", box=\"Pruning\",\n            callback=self._invalidate_pruning)\n        grid = QGridLayout()\n        box.layout().addLayout(grid)\n        grid.addWidget(\n            gui.appendRadioButton(box, \"None\", addToLayout=False),\n            0, 0\n        )\n        self.max_depth_spin = gui.spin(\n            box, self, \"max_depth\", minv=1, maxv=100,\n            callback=self._invalidate_pruning,\n            keyboardTracking=False, addToLayout=False\n        )\n\n        grid.addWidget(\n            gui.appendRadioButton(box, \"Max depth:\", addToLayout=False),\n            1, 0)\n        grid.addWidget(self.max_depth_spin, 1, 1)\n\n        self.selection_box = gui.radioButtons(\n            self.controlArea, self, \"selection_method\",\n            box=\"Selection\",\n            callback=self._selection_method_changed)\n\n        grid = QGridLayout()\n        self.selection_box.layout().addLayout(grid)\n        grid.addWidget(\n            gui.appendRadioButton(\n                self.selection_box, \"Manual\", addToLayout=False),\n            0, 0\n        )\n        grid.addWidget(\n            gui.appendRadioButton(\n                self.selection_box, \"Height ratio:\", addToLayout=False),\n            1, 0\n        )\n        self.cut_ratio_spin = gui.spin(\n            self.selection_box, self, \"cut_ratio\", 0, 100, step=1e-1,\n            spinType=float, callback=self._selection_method_changed,\n            addToLayout=False\n        )\n        self.cut_ratio_spin.setSuffix(\"%\")\n\n        grid.addWidget(self.cut_ratio_spin, 1, 1)\n\n        grid.addWidget(\n            gui.appendRadioButton(\n                self.selection_box, \"Top N:\", addToLayout=False),\n            2, 0\n        )\n        self.top_n_spin = gui.spin(self.selection_box, self, \"top_n\", 1, 20,\n                                   callback=self._selection_method_changed,\n                                   addToLayout=False)\n        grid.addWidget(self.top_n_spin, 2, 1)\n\n        self.zoom_slider = gui.hSlider(\n            self.controlArea, self, \"zoom_factor\", box=\"Zoom\",\n            minValue=-6, maxValue=3, step=1, ticks=True, createLabel=False,\n            callback=self.__update_font_scale)\n\n        zoom_in = QAction(\n            \"Zoom in\", self, shortcut=QKeySequence.ZoomIn,\n            triggered=self.__zoom_in\n        )\n        zoom_out = QAction(\n            \"Zoom out\", self, shortcut=QKeySequence.ZoomOut,\n            triggered=self.__zoom_out\n        )\n        zoom_reset = QAction(\n            \"Reset zoom\", self,\n            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0),\n            triggered=self.__zoom_reset\n        )\n        self.addActions([zoom_in, zoom_out, zoom_reset])\n\n        self.controlArea.layout().addStretch()\n\n        gui.auto_send(self.buttonsArea, self, \"autocommit\")\n\n        self.scene = QGraphicsScene(self)\n        self.view = StickyGraphicsView(\n            self.scene,\n            horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff,\n            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn,\n            alignment=Qt.AlignLeft | Qt.AlignVCenter\n        )\n        self.mainArea.layout().setSpacing(1)\n        self.mainArea.layout().addWidget(self.view)\n\n        def axis_view(orientation):\n            ax = AxisItem(orientation=orientation, maxTickLength=7)\n            ax.mousePressed.connect(self._activate_cut_line)\n            ax.mouseMoved.connect(self._activate_cut_line)\n            ax.mouseReleased.connect(self._activate_cut_line)\n            ax.setRange(1.0, 0.0)\n            return ax\n\n        self.top_axis = axis_view(\"top\")\n        self.bottom_axis = axis_view(\"bottom\")\n\n        self._main_graphics = QGraphicsWidget()\n        scenelayout = QGraphicsGridLayout()\n        scenelayout.setHorizontalSpacing(10)\n        scenelayout.setVerticalSpacing(10)\n\n        self._main_graphics.setLayout(scenelayout)\n        self.scene.addItem(self._main_graphics)\n\n        self.dendrogram = DendrogramWidget()\n        self.dendrogram.setSizePolicy(QSizePolicy.MinimumExpanding,\n                                      QSizePolicy.MinimumExpanding)\n        self.dendrogram.selectionChanged.connect(self._invalidate_output)\n        self.dendrogram.selectionEdited.connect(self._selection_edited)\n\n        self.labels = TextListWidget()\n        self.labels.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)\n        self.labels.setAlignment(Qt.AlignLeft)\n        self.labels.setMaximumWidth(200)\n\n        scenelayout.addItem(self.top_axis, 0, 0,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        scenelayout.addItem(self.dendrogram, 1, 0,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        scenelayout.addItem(self.labels, 1, 1,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        scenelayout.addItem(self.bottom_axis, 2, 0,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        self.view.viewport().installEventFilter(self)\n        self._main_graphics.installEventFilter(self)\n\n        self.top_axis.setZValue(self.dendrogram.zValue() + 10)\n        self.bottom_axis.setZValue(self.dendrogram.zValue() + 10)\n        self.cut_line = SliderLine(self.top_axis,\n                                   orientation=Qt.Horizontal)\n        self.cut_line.valueChanged.connect(self._dendrogram_slider_changed)\n        self.dendrogram.geometryChanged.connect(self._dendrogram_geom_changed)\n        self._set_cut_line_visible(self.selection_method == 1)\n        self.__update_font_scale()\n\n    @Inputs.distances\n    def set_distances(self, matrix):\n        if self.__pending_selection_restore is not None:\n            selection_state = self.__pending_selection_restore\n        else:\n            # save the current selection to (possibly) restore later\n            selection_state = self._save_selection()\n\n        self.error()\n        self.Error.clear()\n        if matrix is not None:\n            N, _ = matrix.shape\n            if N < 2:\n                self.error(\"Empty distance matrix\")\n                matrix = None\n        if matrix is not None:\n            if not np.all(np.isfinite(matrix)):\n                self.Error.not_finite_distances()\n                matrix = None\n\n        self.matrix = matrix\n        if matrix is not None:\n            self._set_items(matrix.row_items, matrix.axis)\n        else:\n            self._set_items(None)\n        self._invalidate_clustering()\n\n        # Can now attempt to restore session state from a saved workflow.\n        if self.root and selection_state is not None:\n            self._restore_selection(selection_state)\n            self.__pending_selection_restore = None\n\n        self.unconditional_commit()\n\n    def _set_items(self, items, axis=1):\n        self.closeContext()\n        self.items = items\n        model = self.label_cb.model()\n        if len(model) == 3:\n            self.annotation_if_names = self.annotation\n        elif len(model) == 2:\n            self.annotation_if_enumerate = self.annotation\n        if isinstance(items, Orange.data.Table) and axis:\n            model[:] = chain(\n                self.basic_annotations,\n                [model.Separator],\n                items.domain.class_vars,\n                items.domain.metas,\n                [model.Separator] if (items.domain.class_vars or items.domain.metas) and\n                next(filter_visible(items.domain.attributes), False) else [],\n                filter_visible(items.domain.attributes)\n            )\n            if items.domain.class_vars:\n                self.annotation = items.domain.class_vars[0]\n            else:\n                self.annotation = \"Enumeration\"\n            self.openContext(items.domain)\n        else:\n            name_option = bool(\n                items is not None and (\n                    not axis or\n                    isinstance(items, list) and\n                    all(isinstance(var, Orange.data.Variable) for var in items)))\n            model[:] = self.basic_annotations + [\"Name\"] * name_option\n            self.annotation = self.annotation_if_names if name_option \\\n                else self.annotation_if_enumerate\n\n    def _clear_plot(self):\n        self.dendrogram.set_root(None)\n        self.labels.setItems([])\n\n    def _set_displayed_root(self, root):\n        self._clear_plot()\n        self._displayed_root = root\n        self.dendrogram.set_root(root)\n\n        self._update_labels()\n\n        self._main_graphics.resize(\n            self._main_graphics.size().width(),\n            self._main_graphics.sizeHint(Qt.PreferredSize).height()\n        )\n        self._main_graphics.layout().activate()\n\n    def _update(self):\n        self._clear_plot()\n\n        distances = self.matrix\n\n        if distances is not None:\n            method = LINKAGE[self.linkage].lower()\n            Z = dist_matrix_linkage(distances, linkage=method)\n\n            tree = tree_from_linkage(Z)\n            self.linkmatrix = Z\n            self.root = tree\n\n            self.top_axis.setRange(tree.value.height, 0.0)\n            self.bottom_axis.setRange(tree.value.height, 0.0)\n\n            if self.pruning:\n                self._set_displayed_root(prune(tree, level=self.max_depth))\n            else:\n                self._set_displayed_root(tree)\n        else:\n            self.linkmatrix = None\n            self.root = None\n            self._set_displayed_root(None)\n\n        self._apply_selection()\n\n    def _update_labels(self):\n        labels = []\n        if self.root and self._displayed_root:\n            indices = [leaf.value.index for leaf in leaves(self.root)]\n\n            if self.annotation == \"None\":\n                labels = []\n            elif self.annotation == \"Enumeration\":\n                labels = [str(i+1) for i in indices]\n            elif self.annotation == \"Name\":\n                attr = self.matrix.row_items.domain.attributes\n                labels = [str(attr[i]) for i in indices]\n            elif isinstance(self.annotation, Orange.data.Variable):\n                col_data, _ = self.items.get_column_view(self.annotation)\n                labels = [self.annotation.str_val(val) for val in col_data]\n                labels = [labels[idx] for idx in indices]\n            else:\n                labels = []\n\n            if labels and self._displayed_root is not self.root:\n                joined = leaves(self._displayed_root)\n                labels = [\", \".join(labels[leaf.value.first: leaf.value.last])\n                          for leaf in joined]\n\n        self.labels.setItems(labels)\n        self.labels.setMinimumWidth(1 if labels else -1)\n\n    def _restore_selection(self, state):\n        # type: (SelectionState) -> bool\n        \"\"\"\n        Restore the (manual) node selection state.\n\n        Return True if successful; False otherwise.\n        \"\"\"\n        linkmatrix = self.linkmatrix\n        if self.selection_method == 0 and self.root:\n            selected, linksaved = state\n            linkstruct = np.array(linksaved, dtype=float)\n            selected = set(selected)  # type: Set[Tuple[int]]\n            if not selected:\n                return False\n            if linkmatrix.shape[0] != linkstruct.shape[0]:\n                return False\n            # check that the linkage matrix structure matches. Use isclose for\n            # the height column to account for inexact floating point math\n            # (e.g. summation order in different ?gemm implementations for\n            # euclidean distances, ...)\n            if np.any(linkstruct[:, :2] != linkmatrix[:, :2]) or \\\n                    not np.all(np.isclose(linkstruct[:, 2], linkstruct[:, 2])):\n                return False\n            selection = []\n            indices = np.array([n.value.index for n in leaves(self.root)],\n                               dtype=int)\n            # mapping from ranges to display (pruned) nodes\n            mapping = {node.value.range: node\n                       for node in postorder(self._displayed_root)}\n            for node in postorder(self.root):  # type: Tree\n                r = tuple(indices[node.value.first: node.value.last])\n                if r in selected:\n                    if node.value.range not in mapping:\n                        # the node was pruned from display and cannot be\n                        # selected\n                        break\n                    selection.append(mapping[node.value.range])\n                    selected.remove(r)\n                if not selected:\n                    break  # found all, nothing more to do\n            if selection and selected:\n                # Could not restore all selected nodes (only partial match)\n                return False\n\n            self._set_selected_nodes(selection)\n            return True\n        return False\n\n    def _set_selected_nodes(self, selection):\n        # type: (List[Tree]) -> None\n        \"\"\"\n        Set the nodes in `selection` to be the current selected nodes.\n\n        The selection nodes must be subtrees of the current `_displayed_root`.\n        \"\"\"\n        self.dendrogram.selectionChanged.disconnect(self._invalidate_output)\n        try:\n            self.dendrogram.set_selected_clusters(selection)\n        finally:\n            self.dendrogram.selectionChanged.connect(self._invalidate_output)\n\n    def _invalidate_clustering(self):\n        self._update()\n        self._update_labels()\n        self._invalidate_output()\n\n    def _invalidate_output(self):\n        self.commit()\n\n    def _invalidate_pruning(self):\n        if self.root:\n            selection = self.dendrogram.selected_nodes()\n            ranges = [node.value.range for node in selection]\n            if self.pruning:\n                self._set_displayed_root(\n                    prune(self.root, level=self.max_depth))\n            else:\n                self._set_displayed_root(self.root)\n            selected = [node for node in preorder(self._displayed_root)\n                        if node.value.range in ranges]\n\n            self.dendrogram.set_selected_clusters(selected)\n\n        self._apply_selection()\n\n    def commit(self):\n        items = getattr(self.matrix, \"items\", self.items)\n        if not items:\n            self.Outputs.selected_data.send(None)\n            self.Outputs.annotated_data.send(None)\n            return\n\n        selection = self.dendrogram.selected_nodes()\n        selection = sorted(selection, key=lambda c: c.value.first)\n\n        indices = [leaf.value.index for leaf in leaves(self.root)]\n\n        maps = [indices[node.value.first:node.value.last]\n                for node in selection]\n\n        selected_indices = list(chain(*maps))\n        unselected_indices = sorted(set(range(self.root.value.last)) -\n                                    set(selected_indices))\n\n        if not selected_indices:\n            self.Outputs.selected_data.send(None)\n            annotated_data = create_annotated_table(items, []) \\\n                if self.selection_method == 0 and self.matrix.axis else None\n            self.Outputs.annotated_data.send(annotated_data)\n            return\n\n        selected_data = None\n\n        if isinstance(items, Orange.data.Table) and self.matrix.axis == 1:\n            # Select rows\n            c = np.zeros(self.matrix.shape[0])\n\n            for i, indices in enumerate(maps):\n                c[indices] = i\n            c[unselected_indices] = len(maps)\n\n            mask = c != len(maps)\n\n            data, domain = items, items.domain\n            attrs = domain.attributes\n            classes = domain.class_vars\n            metas = domain.metas\n\n            var_name = get_unique_names(domain, \"Cluster\")\n            values = [f\"C{i + 1}\" for i in range(len(maps))]\n\n            clust_var = Orange.data.DiscreteVariable(\n                var_name, values=values + [\"Other\"])\n            domain = Orange.data.Domain(attrs, classes, metas + (clust_var,))\n            data = items.transform(domain)\n            data.get_column_view(clust_var)[0][:] = c\n\n            if selected_indices:\n                selected_data = data[mask]\n                clust_var = Orange.data.DiscreteVariable(\n                    var_name, values=values)\n                selected_data.domain = Domain(\n                    attrs, classes, metas + (clust_var, ))\n\n        elif isinstance(items, Orange.data.Table) and self.matrix.axis == 0:\n            # Select columns\n            domain = Orange.data.Domain(\n                [items.domain[i] for i in selected_indices],\n                items.domain.class_vars, items.domain.metas)\n            selected_data = items.from_table(domain, items)\n            data = None\n\n        self.Outputs.selected_data.send(selected_data)\n        annotated_data = create_annotated_table(data, selected_indices)\n        self.Outputs.annotated_data.send(annotated_data)\n\n    def eventFilter(self, obj, event):\n        if obj is self.view.viewport() and event.type() == QEvent.Resize:\n            # NOTE: not using viewport.width(), due to 'transient' scroll bars\n            # (macOS). Viewport covers the whole view, but QGraphicsView still\n            # scrolls left, right with scroll bar extent (other\n            # QAbstractScrollArea widgets behave as expected).\n            w_frame = self.view.frameWidth()\n            margin = self.view.viewportMargins()\n            w_scroll = self.view.verticalScrollBar().width()\n            width = (self.view.width() - w_frame * 2 -\n                     margin.left() - margin.right() - w_scroll)\n            # layout with new width constraint\n            self.__layout_main_graphics(width=width)\n        elif obj is self._main_graphics and \\\n                event.type() == QEvent.LayoutRequest:\n            # layout preserving the width (vertical re layout)\n            self.__layout_main_graphics()\n        return super().eventFilter(obj, event)\n\n    @Slot(QPointF)\n    def _activate_cut_line(self, pos: QPointF):\n        \"\"\"Activate cut line selection an set cut value to `pos.x()`.\"\"\"\n        self.selection_method = 1\n        self.cut_line.setValue(pos.x())\n        self._selection_method_changed()\n\n    def onDeleteWidget(self):\n        super().onDeleteWidget()\n        self._clear_plot()\n        self.dendrogram.clear()\n        self.dendrogram.deleteLater()\n\n    def _dendrogram_geom_changed(self):\n        pos = self.dendrogram.pos_at_height(self.cutoff_height)\n        geom = self.dendrogram.geometry()\n        self._set_slider_value(pos.x(), geom.width())\n\n        self.cut_line.setLength(\n            self.bottom_axis.geometry().bottom()\n            - self.top_axis.geometry().top()\n        )\n\n        geom = self._main_graphics.geometry()\n        assert geom.topLeft() == QPointF(0, 0)\n\n        def adjustLeft(rect):\n            rect = QRectF(rect)\n            rect.setLeft(geom.left())\n            return rect\n        margin = 3\n        self.scene.setSceneRect(geom)\n        self.view.setSceneRect(geom)\n        self.view.setHeaderSceneRect(\n            adjustLeft(self.top_axis.geometry()).adjusted(0, 0, 0, margin)\n        )\n        self.view.setFooterSceneRect(\n            adjustLeft(self.bottom_axis.geometry()).adjusted(0, -margin, 0, 0)\n        )\n\n    def _dendrogram_slider_changed(self, value):\n        p = QPointF(value, 0)\n        cl_height = self.dendrogram.height_at(p)\n\n        self.set_cutoff_height(cl_height)\n\n    def _set_slider_value(self, value, span):\n        with blocked(self.cut_line):\n            self.cut_line.setRange(0, span)\n            self.cut_line.setValue(value)\n\n    def set_cutoff_height(self, height):\n        self.cutoff_height = height\n        if self.root:\n            self.cut_ratio = 100 * height / self.root.value.height\n        self.select_max_height(height)\n\n    def _set_cut_line_visible(self, visible):\n        self.cut_line.setVisible(visible)\n\n    def select_top_n(self, n):\n        root = self._displayed_root\n        if root:\n            clusters = top_clusters(root, n)\n            self.dendrogram.set_selected_clusters(clusters)\n\n    def select_max_height(self, height):\n        root = self._displayed_root\n        if root:\n            clusters = clusters_at_height(root, height)\n            self.dendrogram.set_selected_clusters(clusters)\n\n    def _selection_method_changed(self):\n        self._set_cut_line_visible(self.selection_method == 1)\n        if self.root:\n            self._apply_selection()\n\n    def _apply_selection(self):\n        if not self.root:\n            return\n\n        if self.selection_method == 0:\n            pass\n        elif self.selection_method == 1:\n            height = self.cut_ratio * self.root.value.height / 100\n            self.set_cutoff_height(height)\n            pos = self.dendrogram.pos_at_height(height)\n            self._set_slider_value(pos.x(), self.dendrogram.size().width())\n        elif self.selection_method == 2:\n            self.select_top_n(self.top_n)\n\n    def _selection_edited(self):\n        # Selection was edited by clicking on a cluster in the\n        # dendrogram view.\n        self.selection_method = 0\n        self._selection_method_changed()\n        self._invalidate_output()\n\n    def _save_selection(self):\n        # Save the current manual node selection state\n        selection_state = None\n        if self.selection_method == 0 and self.root:\n            assert self.linkmatrix is not None\n            linkmat = [(int(_0), int(_1), _2)\n                       for _0, _1, _2 in self.linkmatrix[:, :3].tolist()]\n            nodes_ = self.dendrogram.selected_nodes()\n            # match the display (pruned) nodes back (by ranges)\n            mapping = {node.value.range: node for node in postorder(self.root)}\n            nodes = [mapping[node.value.range] for node in nodes_]\n            indices = [tuple(node.value.index for node in leaves(node))\n                       for node in nodes]\n            if nodes:\n                selection_state = (indices, linkmat)\n        return selection_state\n\n    def save_state(self):\n        # type: () -> Dict[str, Any]\n        \"\"\"\n        Save state for `set_restore_state`\n        \"\"\"\n        selection = self._save_selection()\n        res = {\"version\": (0, 0, 0)}\n        if selection is not None:\n            res[\"selection_state\"] = selection\n        return res\n\n    def set_restore_state(self, state):\n        # type: (Dict[str, Any]) -> bool\n        \"\"\"\n        Restore session data from a saved state.\n\n        Parameters\n        ----------\n        state : Dict[str, Any]\n\n        NOTE\n        ----\n        This is method called while the instance (self) is being constructed,\n        even before its `__init__` is called. Consider `self` to be only a\n        `QObject` at this stage.\n        \"\"\"\n        if \"selection_state\" in state:\n            selection = state[\"selection_state\"]\n            self.__pending_selection_restore = selection\n        return True\n\n    def __zoom_in(self):\n        def clip(minval, maxval, val):\n            return min(max(val, minval), maxval)\n        self.zoom_factor = clip(self.zoom_slider.minimum(),\n                                self.zoom_slider.maximum(),\n                                self.zoom_factor + 1)\n        self.__update_font_scale()\n\n    def __zoom_out(self):\n        def clip(minval, maxval, val):\n            return min(max(val, minval), maxval)\n        self.zoom_factor = clip(self.zoom_slider.minimum(),\n                                self.zoom_slider.maximum(),\n                                self.zoom_factor - 1)\n        self.__update_font_scale()\n\n    def __zoom_reset(self):\n        self.zoom_factor = 0\n        self.__update_font_scale()\n\n    def __layout_main_graphics(self, width=-1):\n        if width < 0:\n            # Preserve current width.\n            width = self._main_graphics.size().width()\n        preferred = self._main_graphics.effectiveSizeHint(\n            Qt.PreferredSize, constraint=QSizeF(width, -1))\n        self._main_graphics.resize(QSizeF(width, preferred.height()))\n        mw = self._main_graphics.minimumWidth() + 4\n        self.view.setMinimumWidth(mw + self.view.verticalScrollBar().width())\n\n    def __update_font_scale(self):\n        font = self.scene.font()\n        factor = (1.25 ** self.zoom_factor)\n        font = qfont_scaled(font, factor)\n        self._main_graphics.setFont(font)\n\n    def send_report(self):\n        annot = self.label_cb.currentText()\n        if isinstance(self.annotation, str):\n            annot = annot.lower()\n        if self.selection_method == 0:\n            sel = \"manual\"\n        elif self.selection_method == 1:\n            sel = \"at {:.1f} of height\".format(self.cut_ratio)\n        else:\n            sel = \"top {} clusters\".format(self.top_n)\n        self.report_items((\n            (\"Linkage\", LINKAGE[self.linkage].lower()),\n            (\"Annotation\", annot),\n            (\"Prunning\",\n             self.pruning != 0 and \"{} levels\".format(self.max_depth)),\n            (\"Selection\", sel),\n        ))\n        self.report_plot()",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 104,
                                "end_lineno": 843,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "# Test methods with long descriptive names can omit docstrings\n# pylint: disable=missing-docstring, protected-access\nimport warnings\n\nimport numpy as np\n\nfrom AnyQt.QtCore import QPoint, Qt\nfrom AnyQt.QtTest import QTest\n\nimport Orange.misc\nfrom Orange.data import Table, Domain, ContinuousVariable, DiscreteVariable\nfrom Orange.distance import Euclidean\nfrom Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin\nfrom Orange.widgets.unsupervised.owhierarchicalclustering import \\\n    OWHierarchicalClustering\n\n\nclass TestOWHierarchicalClustering(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.distances = Euclidean(cls.data)\n        cls.signal_name = \"Distances\"\n        cls.signal_data = cls.distances\n        cls.same_input_output_domain = False\n\n    def setUp(self):\n        self.widget = self.create_widget(OWHierarchicalClustering)\n\n    def _select_data(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[4]]\n        self.widget.dendrogram.set_selected_items([cluster])\n        return [14, 15, 32, 33]\n\n    def _compare_selected_annotated_domains(self, selected, annotated):\n        self.assertEqual(annotated.domain.variables,\n                         selected.domain.variables)\n        self.assertNotIn(\"Other\", selected.domain.metas[0].values)\n        self.assertIn(\"Other\", annotated.domain.metas[0].values)\n        self.assertLess(set(var.name for var in selected.domain.metas),\n                        set(var.name for var in annotated.domain.metas))\n\n    def test_selection_box_output(self):\n        \"\"\"Check output if Selection method changes\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Height ratio'\n        self.widget.selection_box.buttons[1].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Top N'\n        self.widget.selection_box.buttons[2].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_all_zero_inputs(self):\n        d = Orange.misc.DistMatrix(np.zeros((10, 10)))\n        self.widget.set_distances(d)\n\n    def test_annotation_settings_retrieval(self):\n        \"\"\"Check whether widget retrieves correct settings for annotation\"\"\"\n        widget = self.widget\n\n        dist_names = Orange.misc.DistMatrix(\n            np.zeros((4, 4)), self.data, axis=0)\n        dist_no_names = Orange.misc.DistMatrix(np.zeros((10, 10)), axis=1)\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        # Check that default is set (class variable)\n        self.assertEqual(widget.annotation, self.data.domain.class_var)\n\n        var2 = self.data.domain[2]\n        widget.annotation = var2\n        # Iris now has var2 as annotation\n\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")  # Check default\n        widget.annotation = \"None\"\n        # Pure matrix with axis=1 now has None as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Name\")  # Check default\n        widget.annotation = \"Enumeration\"\n        # Pure matrix with axis=1 has Enumerate as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n    def test_domain_loses_class(self):\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        data = self.data[:, :4]\n        distances = Euclidean(data)\n        self.send_signal(self.widget.Inputs.distances, distances)\n\n    def test_infinite_distances(self):\n        \"\"\"\n        Scipy does not accept infinite distances and neither does this widget.\n        Error is shown.\n        GH-2380\n        \"\"\"\n        table = Table.from_list(\n            Domain(\n                [ContinuousVariable(\"a\")],\n                [DiscreteVariable(\"b\", values=(\"y\", ))]),\n            list(zip([1.79e308, -1e120],\n                     \"yy\"))\n        )\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", \".*\", RuntimeWarning)\n            distances = Euclidean(table)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, distances)\n        self.assertTrue(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n\n    def test_output_cut_ratio(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n\n        # no data is selected\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertIsNotNone(annotated)\n\n        # selecting clusters with cutoff should select all data\n        QTest.mousePress(\n            self.widget.view.headerView().viewport(),\n            Qt.LeftButton, Qt.NoModifier,\n            QPoint(100, 10)\n        )\n        selected = self.get_output(self.widget.Outputs.selected_data)\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(len(selected), len(self.data))\n        self.assertIsNotNone(annotated)\n\n    def test_retain_selection(self):\n        \"\"\"Hierarchical Clustering didn't retain selection. GH-1563\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n\n    def test_restore_state(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        ids_1 = self.get_output(self.widget.Outputs.selected_data).ids\n        state = self.widget.settingsHandler.pack_data(self.widget)\n        w = self.create_widget(\n            OWHierarchicalClustering, stored_settings=state\n        )\n        self.send_signal(w.Inputs.distances, self.distances, widget=w)\n        ids_2 = self.get_output(w.Outputs.selected_data, widget=w).ids\n        self.assertSequenceEqual(list(ids_1), list(ids_2))\n",
            "new_file_content": "# Test methods with long descriptive names can omit docstrings\n# pylint: disable=missing-docstring, protected-access\nimport warnings\n\nimport numpy as np\n\nfrom AnyQt.QtCore import QPoint, Qt\nfrom AnyQt.QtTest import QTest\n\nimport Orange.misc\nfrom Orange.data import Table, Domain, ContinuousVariable, DiscreteVariable\nfrom Orange.distance import Euclidean\nfrom Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin\nfrom Orange.widgets.unsupervised.owhierarchicalclustering import \\\n    OWHierarchicalClustering\n\n\nclass TestOWHierarchicalClustering(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.distances = Euclidean(cls.data)\n        cls.signal_name = \"Distances\"\n        cls.signal_data = cls.distances\n        cls.same_input_output_domain = False\n\n        cls.distances_cols = Euclidean(cls.data, axis=0)\n\n    def setUp(self):\n        self.widget = self.create_widget(OWHierarchicalClustering)\n\n    def _select_data(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[4]]\n        self.widget.dendrogram.set_selected_items([cluster])\n        return [14, 15, 32, 33]\n\n    def _select_data_columns(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[5]]\n        self.widget.dendrogram.set_selected_items([cluster])\n\n    def _compare_selected_annotated_domains(self, selected, annotated):\n        self.assertEqual(annotated.domain.variables,\n                         selected.domain.variables)\n        self.assertNotIn(\"Other\", selected.domain.metas[0].values)\n        self.assertIn(\"Other\", annotated.domain.metas[0].values)\n        self.assertLess(set(var.name for var in selected.domain.metas),\n                        set(var.name for var in annotated.domain.metas))\n\n    def test_selection_box_output(self):\n        \"\"\"Check output if Selection method changes\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Height ratio'\n        self.widget.selection_box.buttons[1].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Top N'\n        self.widget.selection_box.buttons[2].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_all_zero_inputs(self):\n        d = Orange.misc.DistMatrix(np.zeros((10, 10)))\n        self.widget.set_distances(d)\n\n    def test_annotation_settings_retrieval(self):\n        \"\"\"Check whether widget retrieves correct settings for annotation\"\"\"\n        widget = self.widget\n\n        dist_names = Orange.misc.DistMatrix(\n            np.zeros((4, 4)), self.data, axis=0)\n        dist_no_names = Orange.misc.DistMatrix(np.zeros((10, 10)), axis=1)\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        # Check that default is set (class variable)\n        self.assertEqual(widget.annotation, self.data.domain.class_var)\n\n        var2 = self.data.domain[2]\n        widget.annotation = var2\n        # Iris now has var2 as annotation\n\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")  # Check default\n        widget.annotation = \"None\"\n        # Pure matrix with axis=1 now has None as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Name\")  # Check default\n        widget.annotation = \"Enumeration\"\n        # Pure matrix with axis=1 has Enumerate as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n    def test_domain_loses_class(self):\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        data = self.data[:, :4]\n        distances = Euclidean(data)\n        self.send_signal(self.widget.Inputs.distances, distances)\n\n    def test_infinite_distances(self):\n        \"\"\"\n        Scipy does not accept infinite distances and neither does this widget.\n        Error is shown.\n        GH-2380\n        \"\"\"\n        table = Table.from_list(\n            Domain(\n                [ContinuousVariable(\"a\")],\n                [DiscreteVariable(\"b\", values=(\"y\", ))]),\n            list(zip([1.79e308, -1e120],\n                     \"yy\"))\n        )\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", \".*\", RuntimeWarning)\n            distances = Euclidean(table)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, distances)\n        self.assertTrue(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n\n    def test_output_cut_ratio(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n\n        # no data is selected\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertIsNotNone(annotated)\n\n        # selecting clusters with cutoff should select all data\n        QTest.mousePress(\n            self.widget.view.headerView().viewport(),\n            Qt.LeftButton, Qt.NoModifier,\n            QPoint(100, 10)\n        )\n        selected = self.get_output(self.widget.Outputs.selected_data)\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(len(selected), len(self.data))\n        self.assertIsNotNone(annotated)\n\n    def test_retain_selection(self):\n        \"\"\"Hierarchical Clustering didn't retain selection. GH-1563\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n\n    def test_restore_state(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        ids_1 = self.get_output(self.widget.Outputs.selected_data).ids\n        state = self.widget.settingsHandler.pack_data(self.widget)\n        w = self.create_widget(\n            OWHierarchicalClustering, stored_settings=state\n        )\n        self.send_signal(w.Inputs.distances, self.distances, widget=w)\n        ids_2 = self.get_output(w.Outputs.selected_data, widget=w).ids\n        self.assertSequenceEqual(list(ids_1), list(ids_2))\n\n    def test_column_distances(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances_cols)\n        self._select_data_columns()\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal width', 1), ('petal length', 1),\n                                     ('sepal length', 0), ('petal width', 0)])\n\n        self.widget.selection_box.buttons[2].click()  # top N\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal length', 1), ('petal width', 2),\n                                     ('sepal width', 3), ('petal length', 3)])\n",
            "header": {
                "file": {
                    "path": "Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "bfd75ed87",
                "new_commit_hash": "c3e48e4e5",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py"
            },
            "plus_file": {
                "path": "b/Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 26,
                            "length": 6
                        },
                        "new_range": {
                            "start": 26,
                            "length": 8
                        },
                        "section": "class TestOWHierarchicalClustering(WidgetTest, WidgetOutputsTestMixin):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        cls.signal_data = cls.distances",
                                "type": "context"
                            },
                            {
                                "content": "        cls.same_input_output_domain = False",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        cls.distances_cols = Euclidean(cls.data, axis=0)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    def setUp(self):",
                                "type": "context"
                            },
                            {
                                "content": "        self.widget = self.create_widget(OWHierarchicalClustering)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py",
                            "type": "class",
                            "name": "TestOWHierarchicalClustering",
                            "content": "class TestOWHierarchicalClustering(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.distances = Euclidean(cls.data)\n        cls.signal_name = \"Distances\"\n        cls.signal_data = cls.distances\n        cls.same_input_output_domain = False\n\n        cls.distances_cols = Euclidean(cls.data, axis=0)\n\n    def setUp(self):\n        self.widget = self.create_widget(OWHierarchicalClustering)\n\n    def _select_data(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[4]]\n        self.widget.dendrogram.set_selected_items([cluster])\n        return [14, 15, 32, 33]\n\n    def _select_data_columns(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[5]]\n        self.widget.dendrogram.set_selected_items([cluster])\n\n    def _compare_selected_annotated_domains(self, selected, annotated):\n        self.assertEqual(annotated.domain.variables,\n                         selected.domain.variables)\n        self.assertNotIn(\"Other\", selected.domain.metas[0].values)\n        self.assertIn(\"Other\", annotated.domain.metas[0].values)\n        self.assertLess(set(var.name for var in selected.domain.metas),\n                        set(var.name for var in annotated.domain.metas))\n\n    def test_selection_box_output(self):\n        \"\"\"Check output if Selection method changes\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Height ratio'\n        self.widget.selection_box.buttons[1].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Top N'\n        self.widget.selection_box.buttons[2].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_all_zero_inputs(self):\n        d = Orange.misc.DistMatrix(np.zeros((10, 10)))\n        self.widget.set_distances(d)\n\n    def test_annotation_settings_retrieval(self):\n        \"\"\"Check whether widget retrieves correct settings for annotation\"\"\"\n        widget = self.widget\n\n        dist_names = Orange.misc.DistMatrix(\n            np.zeros((4, 4)), self.data, axis=0)\n        dist_no_names = Orange.misc.DistMatrix(np.zeros((10, 10)), axis=1)\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        # Check that default is set (class variable)\n        self.assertEqual(widget.annotation, self.data.domain.class_var)\n\n        var2 = self.data.domain[2]\n        widget.annotation = var2\n        # Iris now has var2 as annotation\n\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")  # Check default\n        widget.annotation = \"None\"\n        # Pure matrix with axis=1 now has None as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Name\")  # Check default\n        widget.annotation = \"Enumeration\"\n        # Pure matrix with axis=1 has Enumerate as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n    def test_domain_loses_class(self):\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        data = self.data[:, :4]\n        distances = Euclidean(data)\n        self.send_signal(self.widget.Inputs.distances, distances)\n\n    def test_infinite_distances(self):\n        \"\"\"\n        Scipy does not accept infinite distances and neither does this widget.\n        Error is shown.\n        GH-2380\n        \"\"\"\n        table = Table.from_list(\n            Domain(\n                [ContinuousVariable(\"a\")],\n                [DiscreteVariable(\"b\", values=(\"y\", ))]),\n            list(zip([1.79e308, -1e120],\n                     \"yy\"))\n        )\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", \".*\", RuntimeWarning)\n            distances = Euclidean(table)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, distances)\n        self.assertTrue(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n\n    def test_output_cut_ratio(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n\n        # no data is selected\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertIsNotNone(annotated)\n\n        # selecting clusters with cutoff should select all data\n        QTest.mousePress(\n            self.widget.view.headerView().viewport(),\n            Qt.LeftButton, Qt.NoModifier,\n            QPoint(100, 10)\n        )\n        selected = self.get_output(self.widget.Outputs.selected_data)\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(len(selected), len(self.data))\n        self.assertIsNotNone(annotated)\n\n    def test_retain_selection(self):\n        \"\"\"Hierarchical Clustering didn't retain selection. GH-1563\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n\n    def test_restore_state(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        ids_1 = self.get_output(self.widget.Outputs.selected_data).ids\n        state = self.widget.settingsHandler.pack_data(self.widget)\n        w = self.create_widget(\n            OWHierarchicalClustering, stored_settings=state\n        )\n        self.send_signal(w.Inputs.distances, self.distances, widget=w)\n        ids_2 = self.get_output(w.Outputs.selected_data, widget=w).ids\n        self.assertSequenceEqual(list(ids_1), list(ids_2))\n\n    def test_column_distances(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances_cols)\n        self._select_data_columns()\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal width', 1), ('petal length', 1),\n                                     ('sepal length', 0), ('petal width', 0)])\n\n        self.widget.selection_box.buttons[2].click()  # top N\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal length', 1), ('petal width', 2),\n                                     ('sepal width', 3), ('petal length', 3)])",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 18,
                            "end_lineno": 193,
                            "parent": null
                        },
                        {
                            "file_name": "Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py",
                            "type": "method",
                            "name": "TestOWHierarchicalClustering.setUpClass",
                            "content": "class TestOWHierarchicalClustering(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.distances = Euclidean(cls.data)\n        cls.signal_name = \"Distances\"\n        cls.signal_data = cls.distances\n        cls.same_input_output_domain = False\n\n        cls.distances_cols = Euclidean(cls.data, axis=0)\n\n    def setUp(self):\n        self.widget = self.create_widget(OWHierarchicalClustering)\n\n    def _select_data(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[4]]\n        self.widget.dendrogram.set_selected_items([cluster])\n        return [14, 15, 32, 33]\n\n    def _select_data_columns(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[5]]\n        self.widget.dendrogram.set_selected_items([cluster])\n\n    def _compare_selected_annotated_domains(self, selected, annotated):\n        self.assertEqual(annotated.domain.variables,\n                         selected.domain.variables)\n        self.assertNotIn(\"Other\", selected.domain.metas[0].values)\n        self.assertIn(\"Other\", annotated.domain.metas[0].values)\n        self.assertLess(set(var.name for var in selected.domain.metas),\n                        set(var.name for var in annotated.domain.metas))\n\n    def test_selection_box_output(self):\n        \"\"\"Check output if Selection method changes\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Height ratio'\n        self.widget.selection_box.buttons[1].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Top N'\n        self.widget.selection_box.buttons[2].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_all_zero_inputs(self):\n        d = Orange.misc.DistMatrix(np.zeros((10, 10)))\n        self.widget.set_distances(d)\n\n    def test_annotation_settings_retrieval(self):\n        \"\"\"Check whether widget retrieves correct settings for annotation\"\"\"\n        widget = self.widget\n\n        dist_names = Orange.misc.DistMatrix(\n            np.zeros((4, 4)), self.data, axis=0)\n        dist_no_names = Orange.misc.DistMatrix(np.zeros((10, 10)), axis=1)\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        # Check that default is set (class variable)\n        self.assertEqual(widget.annotation, self.data.domain.class_var)\n\n        var2 = self.data.domain[2]\n        widget.annotation = var2\n        # Iris now has var2 as annotation\n\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")  # Check default\n        widget.annotation = \"None\"\n        # Pure matrix with axis=1 now has None as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Name\")  # Check default\n        widget.annotation = \"Enumeration\"\n        # Pure matrix with axis=1 has Enumerate as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n    def test_domain_loses_class(self):\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        data = self.data[:, :4]\n        distances = Euclidean(data)\n        self.send_signal(self.widget.Inputs.distances, distances)\n\n    def test_infinite_distances(self):\n        \"\"\"\n        Scipy does not accept infinite distances and neither does this widget.\n        Error is shown.\n        GH-2380\n        \"\"\"\n        table = Table.from_list(\n            Domain(\n                [ContinuousVariable(\"a\")],\n                [DiscreteVariable(\"b\", values=(\"y\", ))]),\n            list(zip([1.79e308, -1e120],\n                     \"yy\"))\n        )\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", \".*\", RuntimeWarning)\n            distances = Euclidean(table)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, distances)\n        self.assertTrue(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n\n    def test_output_cut_ratio(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n\n        # no data is selected\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertIsNotNone(annotated)\n\n        # selecting clusters with cutoff should select all data\n        QTest.mousePress(\n            self.widget.view.headerView().viewport(),\n            Qt.LeftButton, Qt.NoModifier,\n            QPoint(100, 10)\n        )\n        selected = self.get_output(self.widget.Outputs.selected_data)\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(len(selected), len(self.data))\n        self.assertIsNotNone(annotated)\n\n    def test_retain_selection(self):\n        \"\"\"Hierarchical Clustering didn't retain selection. GH-1563\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n\n    def test_restore_state(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        ids_1 = self.get_output(self.widget.Outputs.selected_data).ids\n        state = self.widget.settingsHandler.pack_data(self.widget)\n        w = self.create_widget(\n            OWHierarchicalClustering, stored_settings=state\n        )\n        self.send_signal(w.Inputs.distances, self.distances, widget=w)\n        ids_2 = self.get_output(w.Outputs.selected_data, widget=w).ids\n        self.assertSequenceEqual(list(ids_1), list(ids_2))\n\n    def test_column_distances(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances_cols)\n        self._select_data_columns()\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal width', 1), ('petal length', 1),\n                                     ('sepal length', 0), ('petal width', 0)])\n\n        self.widget.selection_box.buttons[2].click()  # top N\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal length', 1), ('petal width', 2),\n                                     ('sepal width', 3), ('petal length', 3)])",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 20,
                            "end_lineno": 29,
                            "parent": {
                                "file_name": "Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py",
                                "type": "class",
                                "name": "TestOWHierarchicalClustering",
                                "content": "class TestOWHierarchicalClustering(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.distances = Euclidean(cls.data)\n        cls.signal_name = \"Distances\"\n        cls.signal_data = cls.distances\n        cls.same_input_output_domain = False\n\n        cls.distances_cols = Euclidean(cls.data, axis=0)\n\n    def setUp(self):\n        self.widget = self.create_widget(OWHierarchicalClustering)\n\n    def _select_data(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[4]]\n        self.widget.dendrogram.set_selected_items([cluster])\n        return [14, 15, 32, 33]\n\n    def _select_data_columns(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[5]]\n        self.widget.dendrogram.set_selected_items([cluster])\n\n    def _compare_selected_annotated_domains(self, selected, annotated):\n        self.assertEqual(annotated.domain.variables,\n                         selected.domain.variables)\n        self.assertNotIn(\"Other\", selected.domain.metas[0].values)\n        self.assertIn(\"Other\", annotated.domain.metas[0].values)\n        self.assertLess(set(var.name for var in selected.domain.metas),\n                        set(var.name for var in annotated.domain.metas))\n\n    def test_selection_box_output(self):\n        \"\"\"Check output if Selection method changes\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Height ratio'\n        self.widget.selection_box.buttons[1].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Top N'\n        self.widget.selection_box.buttons[2].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_all_zero_inputs(self):\n        d = Orange.misc.DistMatrix(np.zeros((10, 10)))\n        self.widget.set_distances(d)\n\n    def test_annotation_settings_retrieval(self):\n        \"\"\"Check whether widget retrieves correct settings for annotation\"\"\"\n        widget = self.widget\n\n        dist_names = Orange.misc.DistMatrix(\n            np.zeros((4, 4)), self.data, axis=0)\n        dist_no_names = Orange.misc.DistMatrix(np.zeros((10, 10)), axis=1)\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        # Check that default is set (class variable)\n        self.assertEqual(widget.annotation, self.data.domain.class_var)\n\n        var2 = self.data.domain[2]\n        widget.annotation = var2\n        # Iris now has var2 as annotation\n\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")  # Check default\n        widget.annotation = \"None\"\n        # Pure matrix with axis=1 now has None as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Name\")  # Check default\n        widget.annotation = \"Enumeration\"\n        # Pure matrix with axis=1 has Enumerate as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n    def test_domain_loses_class(self):\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        data = self.data[:, :4]\n        distances = Euclidean(data)\n        self.send_signal(self.widget.Inputs.distances, distances)\n\n    def test_infinite_distances(self):\n        \"\"\"\n        Scipy does not accept infinite distances and neither does this widget.\n        Error is shown.\n        GH-2380\n        \"\"\"\n        table = Table.from_list(\n            Domain(\n                [ContinuousVariable(\"a\")],\n                [DiscreteVariable(\"b\", values=(\"y\", ))]),\n            list(zip([1.79e308, -1e120],\n                     \"yy\"))\n        )\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", \".*\", RuntimeWarning)\n            distances = Euclidean(table)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, distances)\n        self.assertTrue(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n\n    def test_output_cut_ratio(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n\n        # no data is selected\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertIsNotNone(annotated)\n\n        # selecting clusters with cutoff should select all data\n        QTest.mousePress(\n            self.widget.view.headerView().viewport(),\n            Qt.LeftButton, Qt.NoModifier,\n            QPoint(100, 10)\n        )\n        selected = self.get_output(self.widget.Outputs.selected_data)\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(len(selected), len(self.data))\n        self.assertIsNotNone(annotated)\n\n    def test_retain_selection(self):\n        \"\"\"Hierarchical Clustering didn't retain selection. GH-1563\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n\n    def test_restore_state(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        ids_1 = self.get_output(self.widget.Outputs.selected_data).ids\n        state = self.widget.settingsHandler.pack_data(self.widget)\n        w = self.create_widget(\n            OWHierarchicalClustering, stored_settings=state\n        )\n        self.send_signal(w.Inputs.distances, self.distances, widget=w)\n        ids_2 = self.get_output(w.Outputs.selected_data, widget=w).ids\n        self.assertSequenceEqual(list(ids_1), list(ids_2))\n\n    def test_column_distances(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances_cols)\n        self._select_data_columns()\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal width', 1), ('petal length', 1),\n                                     ('sepal length', 0), ('petal width', 0)])\n\n        self.widget.selection_box.buttons[2].click()  # top N\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal length', 1), ('petal width', 2),\n                                     ('sepal width', 3), ('petal length', 3)])",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 18,
                                "end_lineno": 193,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 35,
                            "length": 6
                        },
                        "new_range": {
                            "start": 37,
                            "length": 11
                        },
                        "section": "class TestOWHierarchicalClustering(WidgetTest, WidgetOutputsTestMixin):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.widget.dendrogram.set_selected_items([cluster])",
                                "type": "context"
                            },
                            {
                                "content": "        return [14, 15, 32, 33]",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def _select_data_columns(self):",
                                "type": "added"
                            },
                            {
                                "content": "        items = self.widget.dendrogram._items",
                                "type": "added"
                            },
                            {
                                "content": "        cluster = items[sorted(list(items.keys()))[5]]",
                                "type": "added"
                            },
                            {
                                "content": "        self.widget.dendrogram.set_selected_items([cluster])",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    def _compare_selected_annotated_domains(self, selected, annotated):",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertEqual(annotated.domain.variables,",
                                "type": "context"
                            },
                            {
                                "content": "                         selected.domain.variables)",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py",
                            "type": "class",
                            "name": "TestOWHierarchicalClustering",
                            "content": "class TestOWHierarchicalClustering(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.distances = Euclidean(cls.data)\n        cls.signal_name = \"Distances\"\n        cls.signal_data = cls.distances\n        cls.same_input_output_domain = False\n\n        cls.distances_cols = Euclidean(cls.data, axis=0)\n\n    def setUp(self):\n        self.widget = self.create_widget(OWHierarchicalClustering)\n\n    def _select_data(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[4]]\n        self.widget.dendrogram.set_selected_items([cluster])\n        return [14, 15, 32, 33]\n\n    def _select_data_columns(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[5]]\n        self.widget.dendrogram.set_selected_items([cluster])\n\n    def _compare_selected_annotated_domains(self, selected, annotated):\n        self.assertEqual(annotated.domain.variables,\n                         selected.domain.variables)\n        self.assertNotIn(\"Other\", selected.domain.metas[0].values)\n        self.assertIn(\"Other\", annotated.domain.metas[0].values)\n        self.assertLess(set(var.name for var in selected.domain.metas),\n                        set(var.name for var in annotated.domain.metas))\n\n    def test_selection_box_output(self):\n        \"\"\"Check output if Selection method changes\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Height ratio'\n        self.widget.selection_box.buttons[1].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Top N'\n        self.widget.selection_box.buttons[2].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_all_zero_inputs(self):\n        d = Orange.misc.DistMatrix(np.zeros((10, 10)))\n        self.widget.set_distances(d)\n\n    def test_annotation_settings_retrieval(self):\n        \"\"\"Check whether widget retrieves correct settings for annotation\"\"\"\n        widget = self.widget\n\n        dist_names = Orange.misc.DistMatrix(\n            np.zeros((4, 4)), self.data, axis=0)\n        dist_no_names = Orange.misc.DistMatrix(np.zeros((10, 10)), axis=1)\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        # Check that default is set (class variable)\n        self.assertEqual(widget.annotation, self.data.domain.class_var)\n\n        var2 = self.data.domain[2]\n        widget.annotation = var2\n        # Iris now has var2 as annotation\n\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")  # Check default\n        widget.annotation = \"None\"\n        # Pure matrix with axis=1 now has None as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Name\")  # Check default\n        widget.annotation = \"Enumeration\"\n        # Pure matrix with axis=1 has Enumerate as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n    def test_domain_loses_class(self):\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        data = self.data[:, :4]\n        distances = Euclidean(data)\n        self.send_signal(self.widget.Inputs.distances, distances)\n\n    def test_infinite_distances(self):\n        \"\"\"\n        Scipy does not accept infinite distances and neither does this widget.\n        Error is shown.\n        GH-2380\n        \"\"\"\n        table = Table.from_list(\n            Domain(\n                [ContinuousVariable(\"a\")],\n                [DiscreteVariable(\"b\", values=(\"y\", ))]),\n            list(zip([1.79e308, -1e120],\n                     \"yy\"))\n        )\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", \".*\", RuntimeWarning)\n            distances = Euclidean(table)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, distances)\n        self.assertTrue(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n\n    def test_output_cut_ratio(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n\n        # no data is selected\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertIsNotNone(annotated)\n\n        # selecting clusters with cutoff should select all data\n        QTest.mousePress(\n            self.widget.view.headerView().viewport(),\n            Qt.LeftButton, Qt.NoModifier,\n            QPoint(100, 10)\n        )\n        selected = self.get_output(self.widget.Outputs.selected_data)\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(len(selected), len(self.data))\n        self.assertIsNotNone(annotated)\n\n    def test_retain_selection(self):\n        \"\"\"Hierarchical Clustering didn't retain selection. GH-1563\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n\n    def test_restore_state(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        ids_1 = self.get_output(self.widget.Outputs.selected_data).ids\n        state = self.widget.settingsHandler.pack_data(self.widget)\n        w = self.create_widget(\n            OWHierarchicalClustering, stored_settings=state\n        )\n        self.send_signal(w.Inputs.distances, self.distances, widget=w)\n        ids_2 = self.get_output(w.Outputs.selected_data, widget=w).ids\n        self.assertSequenceEqual(list(ids_1), list(ids_2))\n\n    def test_column_distances(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances_cols)\n        self._select_data_columns()\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal width', 1), ('petal length', 1),\n                                     ('sepal length', 0), ('petal width', 0)])\n\n        self.widget.selection_box.buttons[2].click()  # top N\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal length', 1), ('petal width', 2),\n                                     ('sepal width', 3), ('petal length', 3)])",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 18,
                            "end_lineno": 193,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py",
                            "type": "method",
                            "name": "TestOWHierarchicalClustering._select_data_columns",
                            "content": "class TestOWHierarchicalClustering(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.distances = Euclidean(cls.data)\n        cls.signal_name = \"Distances\"\n        cls.signal_data = cls.distances\n        cls.same_input_output_domain = False\n\n        cls.distances_cols = Euclidean(cls.data, axis=0)\n\n    def setUp(self):\n        self.widget = self.create_widget(OWHierarchicalClustering)\n\n    def _select_data(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[4]]\n        self.widget.dendrogram.set_selected_items([cluster])\n        return [14, 15, 32, 33]\n\n    def _select_data_columns(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[5]]\n        self.widget.dendrogram.set_selected_items([cluster])\n\n    def _compare_selected_annotated_domains(self, selected, annotated):\n        self.assertEqual(annotated.domain.variables,\n                         selected.domain.variables)\n        self.assertNotIn(\"Other\", selected.domain.metas[0].values)\n        self.assertIn(\"Other\", annotated.domain.metas[0].values)\n        self.assertLess(set(var.name for var in selected.domain.metas),\n                        set(var.name for var in annotated.domain.metas))\n\n    def test_selection_box_output(self):\n        \"\"\"Check output if Selection method changes\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Height ratio'\n        self.widget.selection_box.buttons[1].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Top N'\n        self.widget.selection_box.buttons[2].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_all_zero_inputs(self):\n        d = Orange.misc.DistMatrix(np.zeros((10, 10)))\n        self.widget.set_distances(d)\n\n    def test_annotation_settings_retrieval(self):\n        \"\"\"Check whether widget retrieves correct settings for annotation\"\"\"\n        widget = self.widget\n\n        dist_names = Orange.misc.DistMatrix(\n            np.zeros((4, 4)), self.data, axis=0)\n        dist_no_names = Orange.misc.DistMatrix(np.zeros((10, 10)), axis=1)\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        # Check that default is set (class variable)\n        self.assertEqual(widget.annotation, self.data.domain.class_var)\n\n        var2 = self.data.domain[2]\n        widget.annotation = var2\n        # Iris now has var2 as annotation\n\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")  # Check default\n        widget.annotation = \"None\"\n        # Pure matrix with axis=1 now has None as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Name\")  # Check default\n        widget.annotation = \"Enumeration\"\n        # Pure matrix with axis=1 has Enumerate as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n    def test_domain_loses_class(self):\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        data = self.data[:, :4]\n        distances = Euclidean(data)\n        self.send_signal(self.widget.Inputs.distances, distances)\n\n    def test_infinite_distances(self):\n        \"\"\"\n        Scipy does not accept infinite distances and neither does this widget.\n        Error is shown.\n        GH-2380\n        \"\"\"\n        table = Table.from_list(\n            Domain(\n                [ContinuousVariable(\"a\")],\n                [DiscreteVariable(\"b\", values=(\"y\", ))]),\n            list(zip([1.79e308, -1e120],\n                     \"yy\"))\n        )\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", \".*\", RuntimeWarning)\n            distances = Euclidean(table)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, distances)\n        self.assertTrue(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n\n    def test_output_cut_ratio(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n\n        # no data is selected\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertIsNotNone(annotated)\n\n        # selecting clusters with cutoff should select all data\n        QTest.mousePress(\n            self.widget.view.headerView().viewport(),\n            Qt.LeftButton, Qt.NoModifier,\n            QPoint(100, 10)\n        )\n        selected = self.get_output(self.widget.Outputs.selected_data)\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(len(selected), len(self.data))\n        self.assertIsNotNone(annotated)\n\n    def test_retain_selection(self):\n        \"\"\"Hierarchical Clustering didn't retain selection. GH-1563\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n\n    def test_restore_state(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        ids_1 = self.get_output(self.widget.Outputs.selected_data).ids\n        state = self.widget.settingsHandler.pack_data(self.widget)\n        w = self.create_widget(\n            OWHierarchicalClustering, stored_settings=state\n        )\n        self.send_signal(w.Inputs.distances, self.distances, widget=w)\n        ids_2 = self.get_output(w.Outputs.selected_data, widget=w).ids\n        self.assertSequenceEqual(list(ids_1), list(ids_2))\n\n    def test_column_distances(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances_cols)\n        self._select_data_columns()\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal width', 1), ('petal length', 1),\n                                     ('sepal length', 0), ('petal width', 0)])\n\n        self.widget.selection_box.buttons[2].click()  # top N\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal length', 1), ('petal width', 2),\n                                     ('sepal width', 3), ('petal length', 3)])",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 40,
                            "end_lineno": 43,
                            "parent": {
                                "file_name": "Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py",
                                "type": "class",
                                "name": "TestOWHierarchicalClustering",
                                "content": "class TestOWHierarchicalClustering(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.distances = Euclidean(cls.data)\n        cls.signal_name = \"Distances\"\n        cls.signal_data = cls.distances\n        cls.same_input_output_domain = False\n\n        cls.distances_cols = Euclidean(cls.data, axis=0)\n\n    def setUp(self):\n        self.widget = self.create_widget(OWHierarchicalClustering)\n\n    def _select_data(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[4]]\n        self.widget.dendrogram.set_selected_items([cluster])\n        return [14, 15, 32, 33]\n\n    def _select_data_columns(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[5]]\n        self.widget.dendrogram.set_selected_items([cluster])\n\n    def _compare_selected_annotated_domains(self, selected, annotated):\n        self.assertEqual(annotated.domain.variables,\n                         selected.domain.variables)\n        self.assertNotIn(\"Other\", selected.domain.metas[0].values)\n        self.assertIn(\"Other\", annotated.domain.metas[0].values)\n        self.assertLess(set(var.name for var in selected.domain.metas),\n                        set(var.name for var in annotated.domain.metas))\n\n    def test_selection_box_output(self):\n        \"\"\"Check output if Selection method changes\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Height ratio'\n        self.widget.selection_box.buttons[1].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Top N'\n        self.widget.selection_box.buttons[2].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_all_zero_inputs(self):\n        d = Orange.misc.DistMatrix(np.zeros((10, 10)))\n        self.widget.set_distances(d)\n\n    def test_annotation_settings_retrieval(self):\n        \"\"\"Check whether widget retrieves correct settings for annotation\"\"\"\n        widget = self.widget\n\n        dist_names = Orange.misc.DistMatrix(\n            np.zeros((4, 4)), self.data, axis=0)\n        dist_no_names = Orange.misc.DistMatrix(np.zeros((10, 10)), axis=1)\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        # Check that default is set (class variable)\n        self.assertEqual(widget.annotation, self.data.domain.class_var)\n\n        var2 = self.data.domain[2]\n        widget.annotation = var2\n        # Iris now has var2 as annotation\n\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")  # Check default\n        widget.annotation = \"None\"\n        # Pure matrix with axis=1 now has None as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Name\")  # Check default\n        widget.annotation = \"Enumeration\"\n        # Pure matrix with axis=1 has Enumerate as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n    def test_domain_loses_class(self):\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        data = self.data[:, :4]\n        distances = Euclidean(data)\n        self.send_signal(self.widget.Inputs.distances, distances)\n\n    def test_infinite_distances(self):\n        \"\"\"\n        Scipy does not accept infinite distances and neither does this widget.\n        Error is shown.\n        GH-2380\n        \"\"\"\n        table = Table.from_list(\n            Domain(\n                [ContinuousVariable(\"a\")],\n                [DiscreteVariable(\"b\", values=(\"y\", ))]),\n            list(zip([1.79e308, -1e120],\n                     \"yy\"))\n        )\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", \".*\", RuntimeWarning)\n            distances = Euclidean(table)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, distances)\n        self.assertTrue(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n\n    def test_output_cut_ratio(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n\n        # no data is selected\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertIsNotNone(annotated)\n\n        # selecting clusters with cutoff should select all data\n        QTest.mousePress(\n            self.widget.view.headerView().viewport(),\n            Qt.LeftButton, Qt.NoModifier,\n            QPoint(100, 10)\n        )\n        selected = self.get_output(self.widget.Outputs.selected_data)\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(len(selected), len(self.data))\n        self.assertIsNotNone(annotated)\n\n    def test_retain_selection(self):\n        \"\"\"Hierarchical Clustering didn't retain selection. GH-1563\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n\n    def test_restore_state(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        ids_1 = self.get_output(self.widget.Outputs.selected_data).ids\n        state = self.widget.settingsHandler.pack_data(self.widget)\n        w = self.create_widget(\n            OWHierarchicalClustering, stored_settings=state\n        )\n        self.send_signal(w.Inputs.distances, self.distances, widget=w)\n        ids_2 = self.get_output(w.Outputs.selected_data, widget=w).ids\n        self.assertSequenceEqual(list(ids_1), list(ids_2))\n\n    def test_column_distances(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances_cols)\n        self._select_data_columns()\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal width', 1), ('petal length', 1),\n                                     ('sepal length', 0), ('petal width', 0)])\n\n        self.widget.selection_box.buttons[2].click()  # top N\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal length', 1), ('petal width', 2),\n                                     ('sepal width', 3), ('petal length', 3)])",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 18,
                                "end_lineno": 193,
                                "parent": null
                            }
                        }
                    ],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 170,
                            "length": 3
                        },
                        "new_range": {
                            "start": 177,
                            "length": 17
                        },
                        "section": "class TestOWHierarchicalClustering(WidgetTest, WidgetOutputsTestMixin):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.send_signal(w.Inputs.distances, self.distances, widget=w)",
                                "type": "context"
                            },
                            {
                                "content": "        ids_2 = self.get_output(w.Outputs.selected_data, widget=w).ids",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertSequenceEqual(list(ids_1), list(ids_2))",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    def test_column_distances(self):",
                                "type": "added"
                            },
                            {
                                "content": "        self.send_signal(self.widget.Inputs.distances, self.distances_cols)",
                                "type": "added"
                            },
                            {
                                "content": "        self._select_data_columns()",
                                "type": "added"
                            },
                            {
                                "content": "        o = self.get_output(self.widget.Outputs.annotated_data)",
                                "type": "added"
                            },
                            {
                                "content": "        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(annotated, [('sepal width', 1), ('petal length', 1),",
                                "type": "added"
                            },
                            {
                                "content": "                                     ('sepal length', 0), ('petal width', 0)])",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        self.widget.selection_box.buttons[2].click()  # top N",
                                "type": "added"
                            },
                            {
                                "content": "        o = self.get_output(self.widget.Outputs.annotated_data)",
                                "type": "added"
                            },
                            {
                                "content": "        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(annotated, [('sepal length', 1), ('petal width', 2),",
                                "type": "added"
                            },
                            {
                                "content": "                                     ('sepal width', 3), ('petal length', 3)])",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py",
                            "type": "class",
                            "name": "TestOWHierarchicalClustering",
                            "content": "class TestOWHierarchicalClustering(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.distances = Euclidean(cls.data)\n        cls.signal_name = \"Distances\"\n        cls.signal_data = cls.distances\n        cls.same_input_output_domain = False\n\n        cls.distances_cols = Euclidean(cls.data, axis=0)\n\n    def setUp(self):\n        self.widget = self.create_widget(OWHierarchicalClustering)\n\n    def _select_data(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[4]]\n        self.widget.dendrogram.set_selected_items([cluster])\n        return [14, 15, 32, 33]\n\n    def _select_data_columns(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[5]]\n        self.widget.dendrogram.set_selected_items([cluster])\n\n    def _compare_selected_annotated_domains(self, selected, annotated):\n        self.assertEqual(annotated.domain.variables,\n                         selected.domain.variables)\n        self.assertNotIn(\"Other\", selected.domain.metas[0].values)\n        self.assertIn(\"Other\", annotated.domain.metas[0].values)\n        self.assertLess(set(var.name for var in selected.domain.metas),\n                        set(var.name for var in annotated.domain.metas))\n\n    def test_selection_box_output(self):\n        \"\"\"Check output if Selection method changes\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Height ratio'\n        self.widget.selection_box.buttons[1].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Top N'\n        self.widget.selection_box.buttons[2].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_all_zero_inputs(self):\n        d = Orange.misc.DistMatrix(np.zeros((10, 10)))\n        self.widget.set_distances(d)\n\n    def test_annotation_settings_retrieval(self):\n        \"\"\"Check whether widget retrieves correct settings for annotation\"\"\"\n        widget = self.widget\n\n        dist_names = Orange.misc.DistMatrix(\n            np.zeros((4, 4)), self.data, axis=0)\n        dist_no_names = Orange.misc.DistMatrix(np.zeros((10, 10)), axis=1)\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        # Check that default is set (class variable)\n        self.assertEqual(widget.annotation, self.data.domain.class_var)\n\n        var2 = self.data.domain[2]\n        widget.annotation = var2\n        # Iris now has var2 as annotation\n\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")  # Check default\n        widget.annotation = \"None\"\n        # Pure matrix with axis=1 now has None as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Name\")  # Check default\n        widget.annotation = \"Enumeration\"\n        # Pure matrix with axis=1 has Enumerate as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n    def test_domain_loses_class(self):\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        data = self.data[:, :4]\n        distances = Euclidean(data)\n        self.send_signal(self.widget.Inputs.distances, distances)\n\n    def test_infinite_distances(self):\n        \"\"\"\n        Scipy does not accept infinite distances and neither does this widget.\n        Error is shown.\n        GH-2380\n        \"\"\"\n        table = Table.from_list(\n            Domain(\n                [ContinuousVariable(\"a\")],\n                [DiscreteVariable(\"b\", values=(\"y\", ))]),\n            list(zip([1.79e308, -1e120],\n                     \"yy\"))\n        )\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", \".*\", RuntimeWarning)\n            distances = Euclidean(table)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, distances)\n        self.assertTrue(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n\n    def test_output_cut_ratio(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n\n        # no data is selected\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertIsNotNone(annotated)\n\n        # selecting clusters with cutoff should select all data\n        QTest.mousePress(\n            self.widget.view.headerView().viewport(),\n            Qt.LeftButton, Qt.NoModifier,\n            QPoint(100, 10)\n        )\n        selected = self.get_output(self.widget.Outputs.selected_data)\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(len(selected), len(self.data))\n        self.assertIsNotNone(annotated)\n\n    def test_retain_selection(self):\n        \"\"\"Hierarchical Clustering didn't retain selection. GH-1563\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n\n    def test_restore_state(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        ids_1 = self.get_output(self.widget.Outputs.selected_data).ids\n        state = self.widget.settingsHandler.pack_data(self.widget)\n        w = self.create_widget(\n            OWHierarchicalClustering, stored_settings=state\n        )\n        self.send_signal(w.Inputs.distances, self.distances, widget=w)\n        ids_2 = self.get_output(w.Outputs.selected_data, widget=w).ids\n        self.assertSequenceEqual(list(ids_1), list(ids_2))\n\n    def test_column_distances(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances_cols)\n        self._select_data_columns()\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal width', 1), ('petal length', 1),\n                                     ('sepal length', 0), ('petal width', 0)])\n\n        self.widget.selection_box.buttons[2].click()  # top N\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal length', 1), ('petal width', 2),\n                                     ('sepal width', 3), ('petal length', 3)])",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 18,
                            "end_lineno": 193,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py",
                            "type": "method",
                            "name": "TestOWHierarchicalClustering.test_column_distances",
                            "content": "class TestOWHierarchicalClustering(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.distances = Euclidean(cls.data)\n        cls.signal_name = \"Distances\"\n        cls.signal_data = cls.distances\n        cls.same_input_output_domain = False\n\n        cls.distances_cols = Euclidean(cls.data, axis=0)\n\n    def setUp(self):\n        self.widget = self.create_widget(OWHierarchicalClustering)\n\n    def _select_data(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[4]]\n        self.widget.dendrogram.set_selected_items([cluster])\n        return [14, 15, 32, 33]\n\n    def _select_data_columns(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[5]]\n        self.widget.dendrogram.set_selected_items([cluster])\n\n    def _compare_selected_annotated_domains(self, selected, annotated):\n        self.assertEqual(annotated.domain.variables,\n                         selected.domain.variables)\n        self.assertNotIn(\"Other\", selected.domain.metas[0].values)\n        self.assertIn(\"Other\", annotated.domain.metas[0].values)\n        self.assertLess(set(var.name for var in selected.domain.metas),\n                        set(var.name for var in annotated.domain.metas))\n\n    def test_selection_box_output(self):\n        \"\"\"Check output if Selection method changes\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Height ratio'\n        self.widget.selection_box.buttons[1].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Top N'\n        self.widget.selection_box.buttons[2].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_all_zero_inputs(self):\n        d = Orange.misc.DistMatrix(np.zeros((10, 10)))\n        self.widget.set_distances(d)\n\n    def test_annotation_settings_retrieval(self):\n        \"\"\"Check whether widget retrieves correct settings for annotation\"\"\"\n        widget = self.widget\n\n        dist_names = Orange.misc.DistMatrix(\n            np.zeros((4, 4)), self.data, axis=0)\n        dist_no_names = Orange.misc.DistMatrix(np.zeros((10, 10)), axis=1)\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        # Check that default is set (class variable)\n        self.assertEqual(widget.annotation, self.data.domain.class_var)\n\n        var2 = self.data.domain[2]\n        widget.annotation = var2\n        # Iris now has var2 as annotation\n\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")  # Check default\n        widget.annotation = \"None\"\n        # Pure matrix with axis=1 now has None as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Name\")  # Check default\n        widget.annotation = \"Enumeration\"\n        # Pure matrix with axis=1 has Enumerate as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n    def test_domain_loses_class(self):\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        data = self.data[:, :4]\n        distances = Euclidean(data)\n        self.send_signal(self.widget.Inputs.distances, distances)\n\n    def test_infinite_distances(self):\n        \"\"\"\n        Scipy does not accept infinite distances and neither does this widget.\n        Error is shown.\n        GH-2380\n        \"\"\"\n        table = Table.from_list(\n            Domain(\n                [ContinuousVariable(\"a\")],\n                [DiscreteVariable(\"b\", values=(\"y\", ))]),\n            list(zip([1.79e308, -1e120],\n                     \"yy\"))\n        )\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", \".*\", RuntimeWarning)\n            distances = Euclidean(table)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, distances)\n        self.assertTrue(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n\n    def test_output_cut_ratio(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n\n        # no data is selected\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertIsNotNone(annotated)\n\n        # selecting clusters with cutoff should select all data\n        QTest.mousePress(\n            self.widget.view.headerView().viewport(),\n            Qt.LeftButton, Qt.NoModifier,\n            QPoint(100, 10)\n        )\n        selected = self.get_output(self.widget.Outputs.selected_data)\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(len(selected), len(self.data))\n        self.assertIsNotNone(annotated)\n\n    def test_retain_selection(self):\n        \"\"\"Hierarchical Clustering didn't retain selection. GH-1563\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n\n    def test_restore_state(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        ids_1 = self.get_output(self.widget.Outputs.selected_data).ids\n        state = self.widget.settingsHandler.pack_data(self.widget)\n        w = self.create_widget(\n            OWHierarchicalClustering, stored_settings=state\n        )\n        self.send_signal(w.Inputs.distances, self.distances, widget=w)\n        ids_2 = self.get_output(w.Outputs.selected_data, widget=w).ids\n        self.assertSequenceEqual(list(ids_1), list(ids_2))\n\n    def test_column_distances(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances_cols)\n        self._select_data_columns()\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal width', 1), ('petal length', 1),\n                                     ('sepal length', 0), ('petal width', 0)])\n\n        self.widget.selection_box.buttons[2].click()  # top N\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal length', 1), ('petal width', 2),\n                                     ('sepal width', 3), ('petal length', 3)])",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 181,
                            "end_lineno": 193,
                            "parent": {
                                "file_name": "Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py",
                                "type": "class",
                                "name": "TestOWHierarchicalClustering",
                                "content": "class TestOWHierarchicalClustering(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.distances = Euclidean(cls.data)\n        cls.signal_name = \"Distances\"\n        cls.signal_data = cls.distances\n        cls.same_input_output_domain = False\n\n        cls.distances_cols = Euclidean(cls.data, axis=0)\n\n    def setUp(self):\n        self.widget = self.create_widget(OWHierarchicalClustering)\n\n    def _select_data(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[4]]\n        self.widget.dendrogram.set_selected_items([cluster])\n        return [14, 15, 32, 33]\n\n    def _select_data_columns(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[5]]\n        self.widget.dendrogram.set_selected_items([cluster])\n\n    def _compare_selected_annotated_domains(self, selected, annotated):\n        self.assertEqual(annotated.domain.variables,\n                         selected.domain.variables)\n        self.assertNotIn(\"Other\", selected.domain.metas[0].values)\n        self.assertIn(\"Other\", annotated.domain.metas[0].values)\n        self.assertLess(set(var.name for var in selected.domain.metas),\n                        set(var.name for var in annotated.domain.metas))\n\n    def test_selection_box_output(self):\n        \"\"\"Check output if Selection method changes\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Height ratio'\n        self.widget.selection_box.buttons[1].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Top N'\n        self.widget.selection_box.buttons[2].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_all_zero_inputs(self):\n        d = Orange.misc.DistMatrix(np.zeros((10, 10)))\n        self.widget.set_distances(d)\n\n    def test_annotation_settings_retrieval(self):\n        \"\"\"Check whether widget retrieves correct settings for annotation\"\"\"\n        widget = self.widget\n\n        dist_names = Orange.misc.DistMatrix(\n            np.zeros((4, 4)), self.data, axis=0)\n        dist_no_names = Orange.misc.DistMatrix(np.zeros((10, 10)), axis=1)\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        # Check that default is set (class variable)\n        self.assertEqual(widget.annotation, self.data.domain.class_var)\n\n        var2 = self.data.domain[2]\n        widget.annotation = var2\n        # Iris now has var2 as annotation\n\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")  # Check default\n        widget.annotation = \"None\"\n        # Pure matrix with axis=1 now has None as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Name\")  # Check default\n        widget.annotation = \"Enumeration\"\n        # Pure matrix with axis=1 has Enumerate as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n    def test_domain_loses_class(self):\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        data = self.data[:, :4]\n        distances = Euclidean(data)\n        self.send_signal(self.widget.Inputs.distances, distances)\n\n    def test_infinite_distances(self):\n        \"\"\"\n        Scipy does not accept infinite distances and neither does this widget.\n        Error is shown.\n        GH-2380\n        \"\"\"\n        table = Table.from_list(\n            Domain(\n                [ContinuousVariable(\"a\")],\n                [DiscreteVariable(\"b\", values=(\"y\", ))]),\n            list(zip([1.79e308, -1e120],\n                     \"yy\"))\n        )\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", \".*\", RuntimeWarning)\n            distances = Euclidean(table)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, distances)\n        self.assertTrue(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n\n    def test_output_cut_ratio(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n\n        # no data is selected\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertIsNotNone(annotated)\n\n        # selecting clusters with cutoff should select all data\n        QTest.mousePress(\n            self.widget.view.headerView().viewport(),\n            Qt.LeftButton, Qt.NoModifier,\n            QPoint(100, 10)\n        )\n        selected = self.get_output(self.widget.Outputs.selected_data)\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(len(selected), len(self.data))\n        self.assertIsNotNone(annotated)\n\n    def test_retain_selection(self):\n        \"\"\"Hierarchical Clustering didn't retain selection. GH-1563\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n\n    def test_restore_state(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        ids_1 = self.get_output(self.widget.Outputs.selected_data).ids\n        state = self.widget.settingsHandler.pack_data(self.widget)\n        w = self.create_widget(\n            OWHierarchicalClustering, stored_settings=state\n        )\n        self.send_signal(w.Inputs.distances, self.distances, widget=w)\n        ids_2 = self.get_output(w.Outputs.selected_data, widget=w).ids\n        self.assertSequenceEqual(list(ids_1), list(ids_2))\n\n    def test_column_distances(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances_cols)\n        self._select_data_columns()\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal width', 1), ('petal length', 1),\n                                     ('sepal length', 0), ('petal width', 0)])\n\n        self.widget.selection_box.buttons[2].click()  # top N\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal length', 1), ('petal width', 2),\n                                     ('sepal width', 3), ('petal length', 3)])",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 18,
                                "end_lineno": 193,
                                "parent": null
                            }
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "1d3c7286c7fd343156e73d84609d72b95bc304f6^",
    "new_commit_hash": "1d3c7286c7fd343156e73d84609d72b95bc304f6",
    "commit_message": "Merge pull request #5514 from janezd/hierarchical-annotate-vars\n\n[ENH] Hierarchical Clustering: Annotate variables with clusters",
    "commit_date": "2021-08-09T12:19:02+02:00",
    "metadata": {}
}
{
    "repo_name": "orange3",
    "new_commit_hash": "1d3c7286c7fd343156e73d84609d72b95bc304f6",
    "test_file_codes": [
        "# Test methods with long descriptive names can omit docstrings\n# pylint: disable=missing-docstring, protected-access\nimport warnings\n\nimport numpy as np\n\nfrom AnyQt.QtCore import QPoint, Qt\nfrom AnyQt.QtTest import QTest\n\nimport Orange.misc\nfrom Orange.data import Table, Domain, ContinuousVariable, DiscreteVariable\nfrom Orange.distance import Euclidean\nfrom Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin\nfrom Orange.widgets.unsupervised.owhierarchicalclustering import \\\n    OWHierarchicalClustering\n\n\nclass TestOWHierarchicalClustering(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.distances = Euclidean(cls.data)\n        cls.signal_name = \"Distances\"\n        cls.signal_data = cls.distances\n        cls.same_input_output_domain = False\n\n        cls.distances_cols = Euclidean(cls.data, axis=0)\n\n    def setUp(self):\n        self.widget = self.create_widget(OWHierarchicalClustering)\n\n    def _select_data(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[4]]\n        self.widget.dendrogram.set_selected_items([cluster])\n        return [14, 15, 32, 33]\n\n    def _select_data_columns(self):\n        items = self.widget.dendrogram._items\n        cluster = items[sorted(list(items.keys()))[5]]\n        self.widget.dendrogram.set_selected_items([cluster])\n\n    def _compare_selected_annotated_domains(self, selected, annotated):\n        self.assertEqual(annotated.domain.variables,\n                         selected.domain.variables)\n        self.assertNotIn(\"Other\", selected.domain.metas[0].values)\n        self.assertIn(\"Other\", annotated.domain.metas[0].values)\n        self.assertLess(set(var.name for var in selected.domain.metas),\n                        set(var.name for var in annotated.domain.metas))\n\n    def test_selection_box_output(self):\n        \"\"\"Check output if Selection method changes\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Height ratio'\n        self.widget.selection_box.buttons[1].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        # change selection to 'Top N'\n        self.widget.selection_box.buttons[2].click()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_all_zero_inputs(self):\n        d = Orange.misc.DistMatrix(np.zeros((10, 10)))\n        self.widget.set_distances(d)\n\n    def test_annotation_settings_retrieval(self):\n        \"\"\"Check whether widget retrieves correct settings for annotation\"\"\"\n        widget = self.widget\n\n        dist_names = Orange.misc.DistMatrix(\n            np.zeros((4, 4)), self.data, axis=0)\n        dist_no_names = Orange.misc.DistMatrix(np.zeros((10, 10)), axis=1)\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        # Check that default is set (class variable)\n        self.assertEqual(widget.annotation, self.data.domain.class_var)\n\n        var2 = self.data.domain[2]\n        widget.annotation = var2\n        # Iris now has var2 as annotation\n\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")  # Check default\n        widget.annotation = \"None\"\n        # Pure matrix with axis=1 now has None as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Name\")  # Check default\n        widget.annotation = \"Enumeration\"\n        # Pure matrix with axis=1 has Enumerate as annotation\n\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIs(widget.annotation, var2)\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n        self.send_signal(self.widget.Inputs.distances, dist_names)\n        self.assertEqual(widget.annotation, \"Enumeration\")\n        self.send_signal(self.widget.Inputs.distances, dist_no_names)\n        self.assertEqual(widget.annotation, \"None\")\n\n    def test_domain_loses_class(self):\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        data = self.data[:, :4]\n        distances = Euclidean(data)\n        self.send_signal(self.widget.Inputs.distances, distances)\n\n    def test_infinite_distances(self):\n        \"\"\"\n        Scipy does not accept infinite distances and neither does this widget.\n        Error is shown.\n        GH-2380\n        \"\"\"\n        table = Table.from_list(\n            Domain(\n                [ContinuousVariable(\"a\")],\n                [DiscreteVariable(\"b\", values=(\"y\", ))]),\n            list(zip([1.79e308, -1e120],\n                     \"yy\"))\n        )\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", \".*\", RuntimeWarning)\n            distances = Euclidean(table)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, distances)\n        self.assertTrue(self.widget.Error.not_finite_distances.is_shown())\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertFalse(self.widget.Error.not_finite_distances.is_shown())\n\n    def test_output_cut_ratio(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n\n        # no data is selected\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertIsNotNone(annotated)\n\n        # selecting clusters with cutoff should select all data\n        QTest.mousePress(\n            self.widget.view.headerView().viewport(),\n            Qt.LeftButton, Qt.NoModifier,\n            QPoint(100, 10)\n        )\n        selected = self.get_output(self.widget.Outputs.selected_data)\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(len(selected), len(self.data))\n        self.assertIsNotNone(annotated)\n\n    def test_retain_selection(self):\n        \"\"\"Hierarchical Clustering didn't retain selection. GH-1563\"\"\"\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.selected_data))\n\n    def test_restore_state(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances)\n        self._select_data()\n        ids_1 = self.get_output(self.widget.Outputs.selected_data).ids\n        state = self.widget.settingsHandler.pack_data(self.widget)\n        w = self.create_widget(\n            OWHierarchicalClustering, stored_settings=state\n        )\n        self.send_signal(w.Inputs.distances, self.distances, widget=w)\n        ids_2 = self.get_output(w.Outputs.selected_data, widget=w).ids\n        self.assertSequenceEqual(list(ids_1), list(ids_2))\n\n    def test_column_distances(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances_cols)\n        self._select_data_columns()\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal width', 1), ('petal length', 1),\n                                     ('sepal length', 0), ('petal width', 0)])\n\n        self.widget.selection_box.buttons[2].click()  # top N\n        o = self.get_output(self.widget.Outputs.annotated_data)\n        annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\n        self.assertEqual(annotated, [('sepal length', 1), ('petal width', 2),\n                                     ('sepal width', 3), ('petal length', 3)])\n"
    ],
    "test_file_names": [
        "test_1.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "Starting Orange installation attempts...\nAttempting installation with Python 3.7...\nrunning build_ext\nCompiling Orange/classification/_tree_scorers.pyx because it changed.\nCompiling Orange/data/_contingency.pyx because it changed.\nCompiling Orange/data/_io.pyx because it changed.\nCompiling Orange/data/_valuecount.pyx because it changed.\nCompiling Orange/data/_variable.pyx because it changed.\nCompiling Orange/distance/_distance.pyx because it changed.\nCompiling Orange/preprocess/_discretize.pyx because it changed.\nCompiling Orange/preprocess/_relieff.pyx because it changed.\nCompiling Orange/projection/_som.pyx because it changed.\n[1/9] Cythonizing Orange/classification/_tree_scorers.pyx\n[2/9] Cythonizing Orange/data/_contingency.pyx\n[3/9] Cythonizing Orange/data/_io.pyx\n[4/9] Cythonizing Orange/data/_valuecount.pyx\n[5/9] Cythonizing Orange/data/_variable.pyx\n[6/9] Cythonizing Orange/distance/_distance.pyx\n[7/9] Cythonizing Orange/preprocess/_discretize.pyx\n[8/9] Cythonizing Orange/preprocess/_relieff.pyx\n[9/9] Cythonizing Orange/projection/_som.pyx\nbuilding 'Orange.classification._tree_scorers' extension\ncreating build\ncreating build/temp.linux-x86_64-3.7\ncreating build/temp.linux-x86_64-3.7/Orange\ncreating build/temp.linux-x86_64-3.7/Orange/classification\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/classification/_tree_scorers.c -o build/temp.linux-x86_64-3.7/Orange/classification/_tree_scorers.o\ncreating build/lib.linux-x86_64-3.7\ncreating build/lib.linux-x86_64-3.7/Orange\ncreating build/lib.linux-x86_64-3.7/Orange/classification\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/classification/_tree_scorers.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/classification/_tree_scorers.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._contingency' extension\ncreating build/temp.linux-x86_64-3.7/Orange/data\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/data/_contingency.c -o build/temp.linux-x86_64-3.7/Orange/data/_contingency.o\ncreating build/lib.linux-x86_64-3.7/Orange/data\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_contingency.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/data/_contingency.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._io' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/data/_io.c -o build/temp.linux-x86_64-3.7/Orange/data/_io.o\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_io.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/data/_io.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._valuecount' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/data/_valuecount.c -o build/temp.linux-x86_64-3.7/Orange/data/_valuecount.o\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_valuecount.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/data/_valuecount.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._variable' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/data/_variable.c -o build/temp.linux-x86_64-3.7/Orange/data/_variable.o\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_variable.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/data/_variable.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.distance._distance' extension\ncreating build/temp.linux-x86_64-3.7/Orange/distance\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/distance/_distance.c -o build/temp.linux-x86_64-3.7/Orange/distance/_distance.o\ncreating build/lib.linux-x86_64-3.7/Orange/distance\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/distance/_distance.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/distance/_distance.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._discretize' extension\ncreating build/temp.linux-x86_64-3.7/Orange/preprocess\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/preprocess/_discretize.c -o build/temp.linux-x86_64-3.7/Orange/preprocess/_discretize.o\ncreating build/lib.linux-x86_64-3.7/Orange/preprocess\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/preprocess/_discretize.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/preprocess/_discretize.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._relieff' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/preprocess/_relieff.cpp -o build/temp.linux-x86_64-3.7/Orange/preprocess/_relieff.o\nc++ -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/preprocess/_relieff.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/preprocess/_relieff.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.projection._som' extension\ncreating build/temp.linux-x86_64-3.7/Orange/projection\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/projection/_som.c -o build/temp.linux-x86_64-3.7/Orange/projection/_som.o\ncreating build/lib.linux-x86_64-3.7/Orange/projection\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/projection/_som.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/projection/_som.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.classification._simple_tree' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/classification/_simple_tree.c -o build/temp.linux-x86_64-3.7/Orange/classification/_simple_tree.o\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/classification/_simple_tree.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/classification/_simple_tree.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.widgets.utils._grid_density' extension\ncreating build/temp.linux-x86_64-3.7/Orange/widgets\ncreating build/temp.linux-x86_64-3.7/Orange/widgets/utils\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/widgets/utils/_grid_density.cpp -o build/temp.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.o\ncreating build/lib.linux-x86_64-3.7/Orange/widgets\ncreating build/lib.linux-x86_64-3.7/Orange/widgets/utils\nc++ -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.cpython-37m-x86_64-linux-gnu.so\ncopying build/lib.linux-x86_64-3.7/Orange/classification/_tree_scorers.cpython-37m-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-3.7/Orange/data/_contingency.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_io.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_valuecount.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_variable.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/distance/_distance.cpython-37m-x86_64-linux-gnu.so -> Orange/distance\ncopying build/lib.linux-x86_64-3.7/Orange/preprocess/_discretize.cpython-37m-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-3.7/Orange/preprocess/_relieff.cpython-37m-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-3.7/Orange/projection/_som.cpython-37m-x86_64-linux-gnu.so -> Orange/projection\ncopying build/lib.linux-x86_64-3.7/Orange/classification/_simple_tree.cpython-37m-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.cpython-37m-x86_64-linux-gnu.so -> Orange/widgets/utils\nrunning develop\nrunning egg_info\ncreating Orange3.egg-info\nwriting Orange3.egg-info/PKG-INFO\nwriting dependency_links to Orange3.egg-info/dependency_links.txt\nwriting entry points to Orange3.egg-info/entry_points.txt\nwriting requirements to Orange3.egg-info/requires.txt\nwriting top-level names to Orange3.egg-info/top_level.txt\nwriting manifest file 'Orange3.egg-info/SOURCES.txt'\nreading manifest file 'Orange3.egg-info/SOURCES.txt'\nreading manifest template 'MANIFEST.in'\nadding license file 'LICENSE'\nwriting manifest file 'Orange3.egg-info/SOURCES.txt'\nrunning build_ext\ncopying build/lib.linux-x86_64-3.7/Orange/classification/_tree_scorers.cpython-37m-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-3.7/Orange/data/_contingency.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_io.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_valuecount.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_variable.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/distance/_distance.cpython-37m-x86_64-linux-gnu.so -> Orange/distance\ncopying build/lib.linux-x86_64-3.7/Orange/preprocess/_discretize.cpython-37m-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-3.7/Orange/preprocess/_relieff.cpython-37m-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-3.7/Orange/projection/_som.cpython-37m-x86_64-linux-gnu.so -> Orange/projection\ncopying build/lib.linux-x86_64-3.7/Orange/classification/_simple_tree.cpython-37m-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.cpython-37m-x86_64-linux-gnu.so -> Orange/widgets/utils\nCreating /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/Orange3.egg-link (link to .)\nAdding Orange3 3.30.0.dev0+1d3c728 to easy-install.pth file\nInstalling orange-canvas script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\n\nInstalled /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6\nProcessing dependencies for Orange3==3.30.0.dev0+1d3c728\nSearching for XlsxWriter==3.2.0\nBest match: XlsxWriter 3.2.0\nAdding XlsxWriter 3.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for xlrd==2.0.1\nBest match: xlrd 2.0.1\nAdding xlrd 2.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for setuptools==59.8.0\nBest match: setuptools 59.8.0\nAdding setuptools 59.8.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for serverfiles==0.3.1\nBest match: serverfiles 0.3.1\nAdding serverfiles 0.3.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for scipy==1.7.3\nBest match: scipy 1.7.3\nAdding scipy 1.7.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for scikit-learn==1.0.2\nBest match: scikit-learn 1.0.2\nAdding scikit-learn 1.0.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for requests==2.31.0\nBest match: requests 2.31.0\nAdding requests 2.31.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for PyYAML==6.0.1\nBest match: PyYAML 6.0.1\nAdding PyYAML 6.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for python-louvain==0.16\nBest match: python-louvain 0.16\nAdding python-louvain 0.16 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for pyqtgraph==0.12.4\nBest match: pyqtgraph 0.12.4\nAdding pyqtgraph 0.12.4 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for pip==24.0\nBest match: pip 24.0\nAdding pip 24.0 to easy-install.pth file\nInstalling pip script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\nInstalling pip3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\nInstalling pip3.10 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for pandas==1.3.5\nBest match: pandas 1.3.5\nAdding pandas 1.3.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for orange-widget-base==4.22.0\nBest match: orange-widget-base 4.22.0\nAdding orange-widget-base 4.22.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for orange-canvas-core==0.1.35\nBest match: orange-canvas-core 0.1.35\nAdding orange-canvas-core 0.1.35 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for openpyxl==3.1.3\nBest match: openpyxl 3.1.3\nAdding openpyxl 3.1.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for openTSNE==1.0.1\nBest match: openTSNE 1.0.1\nAdding openTSNE 1.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for numpy==1.17.5\nBest match: numpy 1.17.5\nAdding numpy 1.17.5 to easy-install.pth file\nInstalling f2py script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\nInstalling f2py3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\nInstalling f2py3.7 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for networkx==2.6.3\nBest match: networkx 2.6.3\nAdding networkx 2.6.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for matplotlib==3.5.3\nBest match: matplotlib 3.5.3\nAdding matplotlib 3.5.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for keyrings.alt==4.2.0\nBest match: keyrings.alt 4.2.0\nAdding keyrings.alt 4.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for keyring==24.1.1\nBest match: keyring 24.1.1\nAdding keyring 24.1.1 to easy-install.pth file\nInstalling keyring script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for joblib==1.3.2\nBest match: joblib 1.3.2\nAdding joblib 1.3.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for httpx==0.16.1\nBest match: httpx 0.16.1\nAdding httpx 0.16.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for chardet==5.2.0\nBest match: chardet 5.2.0\nAdding chardet 5.2.0 to easy-install.pth file\nInstalling chardetect script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for Bottleneck==1.4.0\nBest match: Bottleneck 1.4.0\nAdding Bottleneck 1.4.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for baycomp==1.0.3\nBest match: baycomp 1.0.3\nAdding baycomp 1.0.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for PyQtWebEngine==5.15.6\nBest match: PyQtWebEngine 5.15.6\nAdding PyQtWebEngine 5.15.6 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for PyQt5==5.15.10\nBest match: PyQt5 5.15.10\nAdding PyQt5 5.15.10 to easy-install.pth file\nInstalling pylupdate5 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\nInstalling pyrcc5 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\nInstalling pyuic5 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for AnyQt==0.2.0\nBest match: AnyQt 0.2.0\nAdding AnyQt 0.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for threadpoolctl==3.1.0\nBest match: threadpoolctl 3.1.0\nAdding threadpoolctl 3.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for charset-normalizer==3.4.1\nBest match: charset-normalizer 3.4.1\nAdding charset-normalizer 3.4.1 to easy-install.pth file\nInstalling normalizer script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for urllib3==2.0.7\nBest match: urllib3 2.0.7\nAdding urllib3 2.0.7 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for certifi==2024.12.14\nBest match: certifi 2024.12.14\nAdding certifi 2024.12.14 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for idna==3.10\nBest match: idna 3.10\nAdding idna 3.10 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for pytz==2024.2\nBest match: pytz 2024.2\nAdding pytz 2024.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for python-dateutil==2.9.0.post0\nBest match: python-dateutil 2.9.0.post0\nAdding python-dateutil 2.9.0.post0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for typing-extensions==4.7.1\nBest match: typing-extensions 4.7.1\nAdding typing-extensions 4.7.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for docutils==0.20.1\nBest match: docutils 0.20.1\nAdding docutils 0.20.1 to easy-install.pth file\nInstalling docutils script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for packaging==24.0\nBest match: packaging 24.0\nAdding packaging 24.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for importlib-metadata==6.7.0\nBest match: importlib-metadata 6.7.0\nAdding importlib-metadata 6.7.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for commonmark==0.9.1\nBest match: commonmark 0.9.1\nAdding commonmark 0.9.1 to easy-install.pth file\nInstalling cmark script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for qasync==0.25.0\nBest match: qasync 0.25.0\nAdding qasync 0.25.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for requests-cache==1.1.1\nBest match: requests-cache 1.1.1\nAdding requests-cache 1.1.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for dictdiffer==0.9.0\nBest match: dictdiffer 0.9.0\nAdding dictdiffer 0.9.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for et-xmlfile==1.1.0\nBest match: et-xmlfile 1.1.0\nAdding et-xmlfile 1.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for kiwisolver==1.4.5\nBest match: kiwisolver 1.4.5\nAdding kiwisolver 1.4.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for cycler==0.11.0\nBest match: cycler 0.11.0\nAdding cycler 0.11.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for pyparsing==3.1.4\nBest match: pyparsing 3.1.4\nAdding pyparsing 3.1.4 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for fonttools==4.38.0\nBest match: fonttools 4.38.0\nAdding fonttools 4.38.0 to easy-install.pth file\nInstalling fonttools script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\nInstalling pyftmerge script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\nInstalling pyftsubset script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\nInstalling ttx script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for Pillow==9.5.0\nBest match: Pillow 9.5.0\nAdding Pillow 9.5.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for jaraco.classes==3.2.3\nBest match: jaraco.classes 3.2.3\nAdding jaraco.classes 3.2.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for SecretStorage==3.3.3\nBest match: SecretStorage 3.3.3\nAdding SecretStorage 3.3.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for importlib-resources==5.12.0\nBest match: importlib-resources 5.12.0\nAdding importlib-resources 5.12.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for jeepney==0.8.0\nBest match: jeepney 0.8.0\nAdding jeepney 0.8.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for sniffio==1.3.1\nBest match: sniffio 1.3.1\nAdding sniffio 1.3.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for httpcore==0.12.3\nBest match: httpcore 0.12.3\nAdding httpcore 0.12.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for rfc3986==1.5.0\nBest match: rfc3986 1.5.0\nAdding rfc3986 1.5.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for PyQt5-sip==12.13.0\nBest match: PyQt5-sip 12.13.0\nAdding PyQt5-sip 12.13.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for PyQtWebEngine-Qt5==5.15.16\nBest match: PyQtWebEngine-Qt5 5.15.16\nAdding PyQtWebEngine-Qt5 5.15.16 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for PyQt5-Qt5==5.15.16\nBest match: PyQt5-Qt5 5.15.16\nAdding PyQt5-Qt5 5.15.16 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for six==1.17.0\nBest match: six 1.17.0\nAdding six 1.17.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for zipp==3.15.0\nBest match: zipp 3.15.0\nAdding zipp 3.15.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for attrs==24.2.0\nBest match: attrs 24.2.0\nAdding attrs 24.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for cattrs==23.1.2\nBest match: cattrs 23.1.2\nAdding cattrs 23.1.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for url-normalize==1.4.3\nBest match: url-normalize 1.4.3\nAdding url-normalize 1.4.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for platformdirs==4.0.0\nBest match: platformdirs 4.0.0\nAdding platformdirs 4.0.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for more-itertools==9.1.0\nBest match: more-itertools 9.1.0\nAdding more-itertools 9.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for cryptography==44.0.0\nBest match: cryptography 44.0.0\nAdding cryptography 44.0.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for h11==0.14.0\nBest match: h11 0.14.0\nAdding h11 0.14.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for exceptiongroup==1.2.2\nBest match: exceptiongroup 1.2.2\nAdding exceptiongroup 1.2.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for cffi==1.15.1\nBest match: cffi 1.15.1\nAdding cffi 1.15.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nSearching for pycparser==2.21\nBest match: pycparser 2.21\nAdding pycparser 2.21 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages\nFinished processing dependencies for Orange3==3.30.0.dev0+1d3c728\nVerifying Orange installation...\n✅ Orange installation successful!\nSuccessfully installed orange using Python 3.7\n",
    "setup_res_stderr": "Using CPython 3.7.9\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 18 packages in 76ms\nPrepared 18 packages in 27ms\nInstalled 18 packages in 127ms\n + cython==0.29.37\n + exceptiongroup==1.2.2\n + importlib-metadata==6.7.0\n + iniconfig==2.0.0\n + numpy==1.17.5\n + packaging==24.0\n + pluggy==1.2.0\n + pyqt5==5.15.10\n + pyqt5-qt5==5.15.16\n + pyqt5-sip==12.13.0\n + pyqtwebengine==5.15.6\n + pyqtwebengine-qt5==5.15.16\n + pytest==7.4.4\n + setuptools==59.8.0\n + tomli==2.0.1\n + typing-extensions==4.7.1\n + wheel==0.42.0\n + zipp==3.15.0\nResolved 53 packages in 281ms\nInstalled 47 packages in 801ms\n + baycomp==1.0.3\n + bottleneck==1.4.0\n + certifi==2024.12.14\n + cffi==1.15.1\n + chardet==5.2.0\n + charset-normalizer==3.4.1\n + cryptography==44.0.0\n + cycler==0.11.0\n + et-xmlfile==1.1.0\n + fonttools==4.38.0\n + h11==0.14.0\n + httpcore==0.12.3\n + httpx==0.16.1\n + idna==3.10\n + importlib-resources==5.12.0\n + jaraco-classes==3.2.3\n + jeepney==0.8.0\n + joblib==1.3.2\n + keyring==24.1.1\n + keyrings-alt==4.2.0\n + kiwisolver==1.4.5\n + matplotlib==3.5.3\n + more-itertools==9.1.0\n + networkx==2.6.3\n + openpyxl==3.1.3\n + opentsne==1.0.1\n + pandas==1.3.5\n + pillow==9.5.0\n + pip==24.0\n + pycparser==2.21\n + pyparsing==3.1.4\n + python-dateutil==2.9.0.post0\n + python-louvain==0.16\n + pytz==2024.2\n + pyyaml==6.0.1\n + requests==2.31.0\n + rfc3986==1.5.0\n + scikit-learn==1.0.2\n + scipy==1.7.3\n + secretstorage==3.3.3\n + serverfiles==0.3.1\n + six==1.17.0\n + sniffio==1.3.1\n + threadpoolctl==3.1.0\n + urllib3==2.0.7\n + xlrd==2.0.1\n + xlsxwriter==3.2.0\nResolved 39 packages in 122ms\nInstalled 13 packages in 122ms\n + anyqt==0.2.0\n + attrs==24.2.0\n + cattrs==23.1.2\n + commonmark==0.9.1\n + dictdiffer==0.9.0\n + docutils==0.20.1\n + orange-canvas-core==0.1.35\n + orange-widget-base==4.22.0\n + platformdirs==4.0.0\n + pyqtgraph==0.12.4\n + qasync==0.25.0\n + requests-cache==1.1.1\n + url-normalize==1.4.3\nResolved 2 packages in 3ms\nInstalled 2 packages in 123ms\n + psycopg2==2.9.9\n + pymssql==2.3.2\nResolved 19 packages in 57ms\nInstalled 5 packages in 1.56s\n + catboost==1.2.7\n + graphviz==0.20.1\n + plotly==5.18.0\n + tenacity==8.2.3\n + xgboost==1.6.2\nAudited 2 packages in 25ms\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/classification/_tree_scorers.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_2find_threshold_entropy’:\nOrange/classification/_tree_scorers.c:3413:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 3413 |   __pyx_t_7 = ((__pyx_v_N <= __pyx_v_min_leaf) != 0);\n      |                           ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_6find_threshold_MSE’:\nOrange/classification/_tree_scorers.c:4869:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 4869 |   __pyx_t_1 = ((__pyx_v_N <= __pyx_v_min_leaf) != 0);\n      |                           ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_8find_binarization_MSE’:\nOrange/classification/_tree_scorers.c:5615:43: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5615 |           for (__pyx_t_19 = 0; __pyx_t_19 < __pyx_t_18; __pyx_t_19+=1) {\n      |                                           ^\nOrange/classification/_tree_scorers.c:5736:39: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5736 |           __pyx_t_20 = ((__pyx_v_left >= __pyx_v_min_leaf) != 0);\n      |                                       ^~\nOrange/classification/_tree_scorers.c:5742:53: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5742 |           __pyx_t_20 = (((__pyx_v_N - __pyx_v_left) >= __pyx_v_min_leaf) != 0);\n      |                                                     ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_14compute_predictions_csr’:\nOrange/classification/_tree_scorers.c:7202:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7202 |         for (__pyx_t_11 = 0; __pyx_t_11 < __pyx_t_10; __pyx_t_11+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_16compute_predictions_csc’:\nOrange/classification/_tree_scorers.c:7857:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7857 |         for (__pyx_t_11 = 0; __pyx_t_11 < __pyx_t_10; __pyx_t_11+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c:7917:143: warning: comparison of integer expressions of different signedness: ‘__pyx_t_5numpy_int32_t’ {aka ‘int’} and ‘unsigned int’ [-Wsign-compare]\n 7917 |               __pyx_t_15 = (((*((__pyx_t_5numpy_int32_t *) ( /* dim=0 */ (__pyx_v_indices.data + __pyx_t_14 * __pyx_v_indices.strides[0]) ))) != __pyx_v_i) != 0);\n      |                                                                                                                                               ^~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/data/_contingency.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/data/_io.c: In function ‘__pyx_pf_6Orange_4data_3_io_sparse_read_float.constprop’:\nOrange/data/_io.c:4120:43: warning: ‘__pyx_v_decs’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4120 |           __pyx_v_value = ((__pyx_v_value * __pyx_v_decs) + (__pyx_v_c & 0xf));\n      |                            ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/data/_io.c:4318:24: warning: ‘__pyx_v_value’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4318 |           __pyx_t_10 = PyFloat_FromDouble(__pyx_v_value); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 223, __pyx_L5_error)\n      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:683:40: warning: ‘__pyx_v_attr_index’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n  683 |   #define PyInt_FromLong               PyLong_FromLong\n      |                                        ^~~~~~~~~~~~~~~\nOrange/data/_io.c:1590:7: note: ‘__pyx_v_attr_index’ was declared here\n 1590 |   int __pyx_v_attr_index;\n      |       ^~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:3183:25: warning: ‘__pyx_v_atomp’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3183 |           __pyx_v_atomp = (__pyx_v_atomp + 1);\n      |           ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/data/_valuecount.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/distance/_distance.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/preprocess/_discretize.c:774:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/preprocess/_relieff.cpp:790:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/projection/_som.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_5update_hex’:\nOrange/projection/_som.c:3863:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3863 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:3862:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3862 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pf_6Orange_10projection_4_som_8update_sparse.constprop’:\nOrange/projection/_som.c:4934:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4934 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4933:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4933 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_7get_winners_sparse’:\nOrange/projection/_som.c:4732:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4732 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_26, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_27, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4307:7: note: ‘__pyx_v_win_y’ was declared here\n 4307 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4721:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4721 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_27, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_26, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4306:7: note: ‘__pyx_v_win_x’ was declared here\n 4306 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pf_6Orange_10projection_4_som_10update_sparse_hex.constprop’:\nOrange/projection/_som.c:5682:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5682 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:5681:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5681 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_1get_winners’:\nOrange/projection/_som.c:3184:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3184 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_22, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_23, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2887:7: note: ‘__pyx_v_win_y’ was declared here\n 2887 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:3173:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3173 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_23, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_22, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2886:7: note: ‘__pyx_v_win_x’ was declared here\n 2886 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_3update’:\nOrange/projection/_som.c:3357:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3357 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/classification/_simple_tree.c: In function ‘gain_ratio_c’:\nOrange/classification/_simple_tree.c:63:9: warning: implicit declaration of function ‘qsort_r’; did you mean ‘qsort’? [-Wimplicit-function-declaration]\n   63 |         qsort_r(base, nel, size, compar, thunk)\n      |         ^~~~~~~\nOrange/classification/_simple_tree.c:147:9: note: in expansion of macro ‘QSORT_R_FUNC’\n  147 |         QSORT_R_FUNC(examples, size, sizeof(struct Example), (void*) &compar_attr, compar_examples);\n      |         ^~~~~~~~~~~~\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/setuptools/command/easy_install.py:159: EasyInstallDeprecationWarning: easy_install command is deprecated. Use build and pip and other standards-based tools.\n  EasyInstallDeprecationWarning,\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6/.venv/lib/python3.7/site-packages/setuptools/command/install.py:37: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.\n  setuptools.SetuptoolsDeprecationWarning,\nwarning: no files found matching 'README*' under directory 'Orange'\nwarning: no files found matching 'LICENSE' under directory 'Orange'\nwarning: no files found matching '*.basket' under directory 'Orange/datasets'\nwarning: no files found matching '*.js' under directory 'Orange/widgets'\nwarning: no files found matching '*.css' under directory 'Orange/widgets'\nwarning: no files found matching '*.html' under directory 'Orange/widgets'\nwarning: no files found matching 'COPYING'\n",
    "new_commit_res_code": 0,
    "new_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6\ncollected 16 items\n\nr2e_tests/test_1.py sss.............                                     [100%]\n\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_all_zero_inputs\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_annotation_settings_retrieval\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_column_distances\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_domain_loses_class\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_image_export\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_infinite_distances\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_minimum_size\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_msg_base_class\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_output_cut_ratio\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_outputs\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_restore_state\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_retain_selection\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_selection_box_output\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:244: image exporting not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:238: minimum size not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:250: msg base class not tested as .widget was not set\n======================== 13 passed, 3 skipped in 5.37s =========================\nWidgets left: 0    Max widgets: 1498 \n\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_1d3c7286c7fd343156e73d84609d72b95bc304f6\ncollected 16 items\n\nr2e_tests/test_1.py sss..F..........                                     [100%]\n\n=================================== FAILURES ===================================\n______________ TestOWHierarchicalClustering.test_column_distances ______________\n\nself = <r2e_tests.test_1.TestOWHierarchicalClustering testMethod=test_column_distances>\n\n    def test_column_distances(self):\n        self.send_signal(self.widget.Inputs.distances, self.distances_cols)\n        self._select_data_columns()\n        o = self.get_output(self.widget.Outputs.annotated_data)\n>       annotated = [(a.name, a.attributes['cluster']) for a in o.domain.attributes]\nE       AttributeError: 'NoneType' object has no attribute 'domain'\n\nr2e_tests/test_1.py:185: AttributeError\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_all_zero_inputs\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_annotation_settings_retrieval\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_domain_loses_class\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_image_export\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_infinite_distances\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_minimum_size\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_msg_base_class\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_output_cut_ratio\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_outputs\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_restore_state\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_retain_selection\nPASSED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_selection_box_output\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:244: image exporting not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:238: minimum size not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:250: msg base class not tested as .widget was not set\nFAILED r2e_tests/test_1.py::TestOWHierarchicalClustering::test_column_distances\n=================== 1 failed, 12 passed, 3 skipped in 3.39s ====================\nWidgets left: 0    Max widgets: 1498 \n\n",
    "old_commit_res_stderr": ""
}
Orange/widgets/unsupervised/owhierarchicalclustering.py Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Annotated Data Missing When Using Column Distances in Hierarchical Clustering

**Description:**
When performing hierarchical clustering using column distances, the widget fails to produce the annotated data. This results in an `AttributeError` when attempting to access the domain attributes of the annotated data.

**Example:**
```python
def test_column_distances():
    widget.send_signal(Input.DISTANCES, distances_cols)
    widget.select_data_columns()
    output = widget.get_output(Output.ANNOTATED_DATA)
    annotated = [(attr.name, attr.attributes['cluster']) for attr in output.domain.attributes]
```
In this test scenario, the `output` is `None`, leading to the following error:
```
AttributeError: 'NoneType' object has no attribute 'domain'
```

**Expected Behavior:**
When using column distances for hierarchical clustering, the widget should successfully annotate the variables with their respective clusters. Accessing the `domain` attributes of the annotated data should work without errors, allowing further analysis and processing.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### Orange/widgets/unsupervised/owhierarchicalclustering.py
from itertools import chain
from contextlib import contextmanager

import typing
from typing import Any, List, Tuple, Dict, Optional, Set, Union

import numpy as np

from AnyQt.QtWidgets import (
    QGraphicsWidget, QGraphicsObject, QGraphicsScene, QGridLayout, QSizePolicy,
    QAction, QComboBox, QGraphicsGridLayout, QGraphicsSceneMouseEvent
)
from AnyQt.QtGui import QColor, QPen, QFont, QKeySequence
from AnyQt.QtCore import Qt, QSize, QSizeF, QPointF, QRectF, QLineF, QEvent
from AnyQt.QtCore import pyqtSignal as Signal, pyqtSlot as Slot

import pyqtgraph as pg

import Orange.data
from Orange.data.domain import filter_visible
from Orange.data import Domain
import Orange.misc
from Orange.clustering.hierarchical import \
    postorder, preorder, Tree, tree_from_linkage, dist_matrix_linkage, \
    leaves, prune, top_clusters
from Orange.data.util import get_unique_names

from Orange.widgets import widget, gui, settings
from Orange.widgets.utils import itemmodels, combobox
from Orange.widgets.utils.annotated_data import (create_annotated_table,
                                                 ANNOTATED_DATA_SIGNAL_NAME)
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.widget import Input, Output, Msg

from Orange.widgets.utils.stickygraphicsview import StickyGraphicsView
from Orange.widgets.utils.graphicstextlist import TextListWidget
from Orange.widgets.utils.dendrogram import DendrogramWidget

__all__ = ["OWHierarchicalClustering"]


LINKAGE = ["Single", "Average", "Weighted", "Complete", "Ward"]


def make_pen(brush=Qt.black, width=1, style=Qt.SolidLine,
             cap_style=Qt.SquareCap, join_style=Qt.BevelJoin,
             cosmetic=False):
    pen = QPen(brush)
    pen.setWidth(width)
    pen.setStyle(style)
    pen.setCapStyle(cap_style)
    pen.setJoinStyle(join_style)
    pen.setCosmetic(cosmetic)
    return pen


@contextmanager
def blocked(obj):
    old = obj.signalsBlocked()
    obj.blockSignals(True)
    try:
        yield obj
    finally:
        obj.blockSignals(old)


class SaveStateSettingsHandler(settings.SettingsHandler):
    """
    A settings handler that delegates session data store/restore to the
    OWWidget instance.

    The OWWidget subclass must implement `save_state() -> Dict[str, Any]` and
    `set_restore_state(state: Dict[str, Any])` methods.
    """
    def initialize(self, instance, data=None):
        super().initialize(instance, data)
        if data is not None and "__session_state_data" in data:
            session_data = data["__session_state_data"]
            instance.set_restore_state(session_data)

    def pack_data(self, widget):
        # type: (widget.OWWidget) -> dict
        res = super().pack_data(widget)
        state = widget.save_state()
        if state:
            assert "__session_state_data" not in res
            res["__session_state_data"] = state
        return res


class _DomainContextHandler(settings.DomainContextHandler,
                            SaveStateSettingsHandler):
    pass


if typing.TYPE_CHECKING:
    #: Encoded selection state for persistent storage.
    #: This is a list of tuples of leaf indices in the selection and
    #: a (N, 3) linkage matrix for validation (the 4-th column from scipy
    #: is omitted).
    SelectionState = Tuple[List[Tuple[int]], List[Tuple[int, int, float]]]


class OWHierarchicalClustering(widget.OWWidget):
    name = "Hierarchical Clustering"
    description = "Display a dendrogram of a hierarchical clustering " \
                  "constructed from the input distance matrix."
    icon = "icons/HierarchicalClustering.svg"
    priority = 2100
    keywords = []

    class Inputs:
        distances = Input("Distances", Orange.misc.DistMatrix)

    class Outputs:
        selected_data = Output("Selected Data", Orange.data.Table, default=True)
        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table)

    settingsHandler = _DomainContextHandler()

    #: Selected linkage
    linkage = settings.Setting(1)
    #: Index of the selected annotation item (variable, ...)
    annotation = settings.ContextSetting("Enumeration")
    #: Out-of-context setting for the case when the "Name" option is available
    annotation_if_names = settings.Setting("Name")
    #: Out-of-context setting for the case with just "Enumerate" and "None"
    annotation_if_enumerate = settings.Setting("Enumerate")
    #: Selected tree pruning (none/max depth)
    pruning = settings.Setting(0)
    #: Maximum depth when max depth pruning is selected
    max_depth = settings.Setting(10)

    #: Selected cluster selection method (none, cut distance, top n)
    selection_method = settings.Setting(0)
    #: Cut height ratio wrt root height
    cut_ratio = settings.Setting(75.0)
    #: Number of top clusters to select
    top_n = settings.Setting(3)
    #: Dendrogram zoom factor
    zoom_factor = settings.Setting(0)

    autocommit = settings.Setting(True)

    graph_name = "scene"

    basic_annotations = ["None", "Enumeration"]

    class Error(widget.OWWidget.Error):
        not_finite_distances = Msg("Some distances are infinite")

    #: Stored (manual) selection state (from a saved workflow) to restore.
    __pending_selection_restore = None  # type: Optional[SelectionState]

    def __init__(self):
        super().__init__()

        self.matrix = None
        self.items = None
        self.linkmatrix = None
        self.root = None
        self._displayed_root = None
        self.cutoff_height = 0.0

        gui.comboBox(
            self.controlArea, self, "linkage", items=LINKAGE, box="Linkage",
            callback=self._invalidate_clustering)

        model = itemmodels.VariableListModel()
        model[:] = self.basic_annotations

        box = gui.widgetBox(self.controlArea, "Annotations")
        self.label_cb = cb = combobox.ComboBoxSearch(
            minimumContentsLength=14,
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon
        )
        cb.setModel(model)
        cb.setCurrentIndex(cb.findData(self.annotation, Qt.EditRole))

        def on_annotation_activated():
            self.annotation = cb.currentData(Qt.EditRole)
            self._update_labels()
        cb.activated.connect(on_annotation_activated)

        def on_annotation_changed(value):
            cb.setCurrentIndex(cb.findData(value, Qt.EditRole))
        self.connect_control("annotation", on_annotation_changed)

        box.layout().addWidget(self.label_cb)

        box = gui.radioButtons(
            self.controlArea, self, "pruning", box="Pruning",
            callback=self._invalidate_pruning)
        grid = QGridLayout()
        box.layout().addLayout(grid)
        grid.addWidget(
            gui.appendRadioButton(box, "None", addToLayout=False),
            0, 0
        )
        self.max_depth_spin = gui.spin(
            box, self, "max_depth", minv=1, maxv=100,
            callback=self._invalidate_pruning,
            keyboardTracking=False, addToLayout=False
        )

        grid.addWidget(
            gui.appendRadioButton(box, "Max depth:", addToLayout=False),
            1, 0)
        grid.addWidget(self.max_depth_spin, 1, 1)

        self.selection_box = gui.radioButtons(
            self.controlArea, self, "selection_method",
            box="Selection",
            callback=self._selection_method_changed)

        grid = QGridLayout()
        self.selection_box.layout().addLayout(grid)
        grid.addWidget(
            gui.appendRadioButton(
                self.selection_box, "Manual", addToLayout=False),
            0, 0
        )
        grid.addWidget(
            gui.appendRadioButton(
                self.selection_box, "Height ratio:", addToLayout=False),
            1, 0
        )
        self.cut_ratio_spin = gui.spin(
            self.selection_box, self, "cut_ratio", 0, 100, step=1e-1,
            spinType=float, callback=self._selection_method_changed,
            addToLayout=False
        )
        self.cut_ratio_spin.setSuffix("%")

        grid.addWidget(self.cut_ratio_spin, 1, 1)

        grid.addWidget(
            gui.appendRadioButton(
                self.selection_box, "Top N:", addToLayout=False),
            2, 0
        )
        self.top_n_spin = gui.spin(self.selection_box, self, "top_n", 1, 20,
                                   callback=self._selection_method_changed,
                                   addToLayout=False)
        grid.addWidget(self.top_n_spin, 2, 1)

        self.zoom_slider = gui.hSlider(
            self.controlArea, self, "zoom_factor", box="Zoom",
            minValue=-6, maxValue=3, step=1, ticks=True, createLabel=False,
            callback=self.__update_font_scale)

        zoom_in = QAction(
            "Zoom in", self, shortcut=QKeySequence.ZoomIn,
            triggered=self.__zoom_in
        )
        zoom_out = QAction(
            "Zoom out", self, shortcut=QKeySequence.ZoomOut,
            triggered=self.__zoom_out
        )
        zoom_reset = QAction(
            "Reset zoom", self,
            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0),
            triggered=self.__zoom_reset
        )
        self.addActions([zoom_in, zoom_out, zoom_reset])

        self.controlArea.layout().addStretch()

        gui.auto_send(self.buttonsArea, self, "autocommit")

        self.scene = QGraphicsScene(self)
        self.view = StickyGraphicsView(
            self.scene,
            horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff,
            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn,
            alignment=Qt.AlignLeft | Qt.AlignVCenter
        )
        self.mainArea.layout().setSpacing(1)
        self.mainArea.layout().addWidget(self.view)

        def axis_view(orientation):
            ax = AxisItem(orientation=orientation, maxTickLength=7)
            ax.mousePressed.connect(self._activate_cut_line)
            ax.mouseMoved.connect(self._activate_cut_line)
            ax.mouseReleased.connect(self._activate_cut_line)
            ax.setRange(1.0, 0.0)
            return ax

        self.top_axis = axis_view("top")
        self.bottom_axis = axis_view("bottom")

        self._main_graphics = QGraphicsWidget()
        scenelayout = QGraphicsGridLayout()
        scenelayout.setHorizontalSpacing(10)
        scenelayout.setVerticalSpacing(10)

        self._main_graphics.setLayout(scenelayout)
        self.scene.addItem(self._main_graphics)

        self.dendrogram = DendrogramWidget()
        self.dendrogram.setSizePolicy(QSizePolicy.MinimumExpanding,
                                      QSizePolicy.MinimumExpanding)
        self.dendrogram.selectionChanged.connect(self._invalidate_output)
        self.dendrogram.selectionEdited.connect(self._selection_edited)

        self.labels = TextListWidget()
        self.labels.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
        self.labels.setAlignment(Qt.AlignLeft)
        self.labels.setMaximumWidth(200)

        scenelayout.addItem(self.top_axis, 0, 0,
                            alignment=Qt.AlignLeft | Qt.AlignVCenter)
        scenelayout.addItem(self.dendrogram, 1, 0,
                            alignment=Qt.AlignLeft | Qt.AlignVCenter)
        scenelayout.addItem(self.labels, 1, 1,
                            alignment=Qt.AlignLeft | Qt.AlignVCenter)
        scenelayout.addItem(self.bottom_axis, 2, 0,
                            alignment=Qt.AlignLeft | Qt.AlignVCenter)
        self.view.viewport().installEventFilter(self)
        self._main_graphics.installEventFilter(self)

        self.top_axis.setZValue(self.dendrogram.zValue() + 10)
        self.bottom_axis.setZValue(self.dendrogram.zValue() + 10)
        self.cut_line = SliderLine(self.top_axis,
                                   orientation=Qt.Horizontal)
        self.cut_line.valueChanged.connect(self._dendrogram_slider_changed)
        self.dendrogram.geometryChanged.connect(self._dendrogram_geom_changed)
        self._set_cut_line_visible(self.selection_method == 1)
        self.__update_font_scale()

    @Inputs.distances
    def set_distances(self, matrix):
        if self.__pending_selection_restore is not None:
            selection_state = self.__pending_selection_restore
        else:
            # save the current selection to (possibly) restore later
            selection_state = self._save_selection()

        self.error()
        self.Error.clear()
        if matrix is not None:
            N, _ = matrix.shape
            if N < 2:
                self.error("Empty distance matrix")
                matrix = None
        if matrix is not None:
            if not np.all(np.isfinite(matrix)):
                self.Error.not_finite_distances()
                matrix = None

        self.matrix = matrix
        if matrix is not None:
            self._set_items(matrix.row_items, matrix.axis)
        else:
            self._set_items(None)
        self._invalidate_clustering()

        # Can now attempt to restore session state from a saved workflow.
        if self.root and selection_state is not None:
            self._restore_selection(selection_state)
            self.__pending_selection_restore = None

        self.unconditional_commit()

    def _set_items(self, items, axis=1):
        self.closeContext()
        self.items = items
        model = self.label_cb.model()
        if len(model) == 3:
            self.annotation_if_names = self.annotation
        elif len(model) == 2:
            self.annotation_if_enumerate = self.annotation
        if isinstance(items, Orange.data.Table) and axis:
            model[:] = chain(
                self.basic_annotations,
                [model.Separator],
                items.domain.class_vars,
                items.domain.metas,
                [model.Separator] if (items.domain.class_vars or items.domain.metas) and
                next(filter_visible(items.domain.attributes), False) else [],
                filter_visible(items.domain.attributes)
            )
            if items.domain.class_vars:
                self.annotation = items.domain.class_vars[0]
            else:
                self.annotation = "Enumeration"
            self.openContext(items.domain)
        else:
            name_option = bool(
                items is not None and (
                    not axis or
                    isinstance(items, list) and
                    all(isinstance(var, Orange.data.Variable) for var in items)))
            model[:] = self.basic_annotations + ["Name"] * name_option
            self.annotation = self.annotation_if_names if name_option \
                else self.annotation_if_enumerate

    def _clear_plot(self):
        self.dendrogram.set_root(None)
        self.labels.setItems([])

    def _set_displayed_root(self, root):
        self._clear_plot()
        self._displayed_root = root
        self.dendrogram.set_root(root)

        self._update_labels()

        self._main_graphics.resize(
            self._main_graphics.size().width(),
            self._main_graphics.sizeHint(Qt.PreferredSize).height()
        )
        self._main_graphics.layout().activate()

    def _update(self):
        self._clear_plot()

        distances = self.matrix

        if distances is not None:
            method = LINKAGE[self.linkage].lower()
            Z = dist_matrix_linkage(distances, linkage=method)

            tree = tree_from_linkage(Z)
            self.linkmatrix = Z
            self.root = tree

            self.top_axis.setRange(tree.value.height, 0.0)
            self.bottom_axis.setRange(tree.value.height, 0.0)

            if self.pruning:
                self._set_displayed_root(prune(tree, level=self.max_depth))
            else:
                self._set_displayed_root(tree)
        else:
            self.linkmatrix = None
            self.root = None
            self._set_displayed_root(None)

        self._apply_selection()

    def _update_labels(self):
        labels = []
        if self.root and self._displayed_root:
            indices = [leaf.value.index for leaf in leaves(self.root)]

            if self.annotation == "None":
                labels = []
            elif self.annotation == "Enumeration":
                labels = [str(i+1) for i in indices]
            elif self.annotation == "Name":
                attr = self.matrix.row_items.domain.attributes
                labels = [str(attr[i]) for i in indices]
            elif isinstance(self.annotation, Orange.data.Variable):
                col_data, _ = self.items.get_column_view(self.annotation)
                labels = [self.annotation.str_val(val) for val in col_data]
                labels = [labels[idx] for idx in indices]
            else:
                labels = []

            if labels and self._displayed_root is not self.root:
                joined = leaves(self._displayed_root)
                labels = [", ".join(labels[leaf.value.first: leaf.value.last])
                          for leaf in joined]

        self.labels.setItems(labels)
        self.labels.setMinimumWidth(1 if labels else -1)

    def _restore_selection(self, state):
        # type: (SelectionState) -> bool
        """
        Restore the (manual) node selection state.

        Return True if successful; False otherwise.
        """
        linkmatrix = self.linkmatrix
        if self.selection_method == 0 and self.root:
            selected, linksaved = state
            linkstruct = np.array(linksaved, dtype=float)
            selected = set(selected)  # type: Set[Tuple[int]]
            if not selected:
                return False
            if linkmatrix.shape[0] != linkstruct.shape[0]:
                return False
            # check that the linkage matrix structure matches. Use isclose for
            # the height column to account for inexact floating point math
            # (e.g. summation order in different ?gemm implementations for
            # euclidean distances, ...)
            if np.any(linkstruct[:, :2] != linkmatrix[:, :2]) or \
                    not np.all(np.isclose(linkstruct[:, 2], linkstruct[:, 2])):
                return False
            selection = []
            indices = np.array([n.value.index for n in leaves(self.root)],
                               dtype=int)
            # mapping from ranges to display (pruned) nodes
            mapping = {node.value.range: node
                       for node in postorder(self._displayed_root)}
            for node in postorder(self.root):  # type: Tree
                r = tuple(indices[node.value.first: node.value.last])
                if r in selected:
                    if node.value.range not in mapping:
                        # the node was pruned from display and cannot be
                        # selected
                        break
                    selection.append(mapping[node.value.range])
                    selected.remove(r)
                if not selected:
                    break  # found all, nothing more to do
            if selection and selected:
                # Could not restore all selected nodes (only partial match)
                return False

            self._set_selected_nodes(selection)
            return True
        return False

    def _set_selected_nodes(self, selection):
        # type: (List[Tree]) -> None
        """
        Set the nodes in `selection` to be the current selected nodes.

        The selection nodes must be subtrees of the current `_displayed_root`.
        """
        self.dendrogram.selectionChanged.disconnect(self._invalidate_output)
        try:
            self.dendrogram.set_selected_clusters(selection)
        finally:
            self.dendrogram.selectionChanged.connect(self._invalidate_output)

    def _invalidate_clustering(self):
        self._update()
        self._update_labels()
        self._invalidate_output()

    def _invalidate_output(self):
        self.commit()

    def _invalidate_pruning(self):
        if self.root:
            selection = self.dendrogram.selected_nodes()
            ranges = [node.value.range for node in selection]
            if self.pruning:
                self._set_displayed_root(
                    prune(self.root, level=self.max_depth))
            else:
                self._set_displayed_root(self.root)
            selected = [node for node in preorder(self._displayed_root)
                        if node.value.range in ranges]

            self.dendrogram.set_selected_clusters(selected)

        self._apply_selection()

    def commit(self):
        items = getattr(self.matrix, "items", self.items)
        if not items:
            self.Outputs.selected_data.send(None)
            self.Outputs.annotated_data.send(None)
            return

        selection = self.dendrogram.selected_nodes()
        selection = sorted(selection, key=lambda c: c.value.first)

        indices = [leaf.value.index for leaf in leaves(self.root)]

        maps = [indices[node.value.first:node.value.last]
                for node in selection]

        selected_indices = list(chain(*maps))
        unselected_indices = sorted(set(range(self.root.value.last)) -
                                    set(selected_indices))

        if not selected_indices:
            self.Outputs.selected_data.send(None)
            annotated_data = create_annotated_table(items, []) \
                if self.selection_method == 0 and self.matrix.axis else None
            self.Outputs.annotated_data.send(annotated_data)
            return

        selected_data = None

        if isinstance(items, Orange.data.Table) and self.matrix.axis == 1:
            # Select rows
            c = np.zeros(self.matrix.shape[0])

            for i, indices in enumerate(maps):
                c[indices] = i
            c[unselected_indices] = len(maps)

            mask = c != len(maps)

            data, domain = items, items.domain
            attrs = domain.attributes
            classes = domain.class_vars
            metas = domain.metas

            var_name = get_unique_names(domain, "Cluster")
            values = [f"C{i + 1}" for i in range(len(maps))]

            clust_var = Orange.data.DiscreteVariable(
                var_name, values=values + ["Other"])
            domain = Orange.data.Domain(attrs, classes, metas + (clust_var,))
            data = items.transform(domain)
            data.get_column_view(clust_var)[0][:] = c

            if selected_indices:
                selected_data = data[mask]
                clust_var = Orange.data.DiscreteVariable(
                    var_name, values=values)
                selected_data.domain = Domain(
                    attrs, classes, metas + (clust_var, ))

        elif isinstance(items, Orange.data.Table) and self.matrix.axis == 0:
            # Select columns
            domain = Orange.data.Domain(
                [items.domain[i] for i in selected_indices],
                items.domain.class_vars, items.domain.metas)
            selected_data = items.from_table(domain, items)
            data = None

        self.Outputs.selected_data.send(selected_data)
        annotated_data = create_annotated_table(data, selected_indices)
        self.Outputs.annotated_data.send(annotated_data)

    def eventFilter(self, obj, event):
        if obj is self.view.viewport() and event.type() == QEvent.Resize:
            # NOTE: not using viewport.width(), due to 'transient' scroll bars
            # (macOS). Viewport covers the whole view, but QGraphicsView still
            # scrolls left, right with scroll bar extent (other
            # QAbstractScrollArea widgets behave as expected).
            w_frame = self.view.frameWidth()
            margin = self.view.viewportMargins()
            w_scroll = self.view.verticalScrollBar().width()
            width = (self.view.width() - w_frame * 2 -
                     margin.left() - margin.right() - w_scroll)
            # layout with new width constraint
            self.__layout_main_graphics(width=width)
        elif obj is self._main_graphics and \
                event.type() == QEvent.LayoutRequest:
            # layout preserving the width (vertical re layout)
            self.__layout_main_graphics()
        return super().eventFilter(obj, event)

    @Slot(QPointF)
    def _activate_cut_line(self, pos: QPointF):
        """Activate cut line selection an set cut value to `pos.x()`."""
        self.selection_method = 1
        self.cut_line.setValue(pos.x())
        self._selection_method_changed()

    def onDeleteWidget(self):
        super().onDeleteWidget()
        self._clear_plot()
        self.dendrogram.clear()
        self.dendrogram.deleteLater()

    def _dendrogram_geom_changed(self):
        pos = self.dendrogram.pos_at_height(self.cutoff_height)
        geom = self.dendrogram.geometry()
        self._set_slider_value(pos.x(), geom.width())

        self.cut_line.setLength(
            self.bottom_axis.geometry().bottom()
            - self.top_axis.geometry().top()
        )

        geom = self._main_graphics.geometry()
        assert geom.topLeft() == QPointF(0, 0)

        def adjustLeft(rect):
            rect = QRectF(rect)
            rect.setLeft(geom.left())
            return rect
        margin = 3
        self.scene.setSceneRect(geom)
        self.view.setSceneRect(geom)
        self.view.setHeaderSceneRect(
            adjustLeft(self.top_axis.geometry()).adjusted(0, 0, 0, margin)
        )
        self.view.setFooterSceneRect(
            adjustLeft(self.bottom_axis.geometry()).adjusted(0, -margin, 0, 0)
        )

    def _dendrogram_slider_changed(self, value):
        p = QPointF(value, 0)
        cl_height = self.dendrogram.height_at(p)

        self.set_cutoff_height(cl_height)

    def _set_slider_value(self, value, span):
        with blocked(self.cut_line):
            self.cut_line.setRange(0, span)
            self.cut_line.setValue(value)

    def set_cutoff_height(self, height):
        self.cutoff_height = height
        if self.root:
            self.cut_ratio = 100 * height / self.root.value.height
        self.select_max_height(height)

    def _set_cut_line_visible(self, visible):
        self.cut_line.setVisible(visible)

    def select_top_n(self, n):
        root = self._displayed_root
        if root:
            clusters = top_clusters(root, n)
            self.dendrogram.set_selected_clusters(clusters)

    def select_max_height(self, height):
        root = self._displayed_root
        if root:
            clusters = clusters_at_height(root, height)
            self.dendrogram.set_selected_clusters(clusters)

    def _selection_method_changed(self):
        self._set_cut_line_visible(self.selection_method == 1)
        if self.root:
            self._apply_selection()

    def _apply_selection(self):
        if not self.root:
            return

        if self.selection_method == 0:
            pass
        elif self.selection_method == 1:
            height = self.cut_ratio * self.root.value.height / 100
            self.set_cutoff_height(height)
            pos = self.dendrogram.pos_at_height(height)
            self._set_slider_value(pos.x(), self.dendrogram.size().width())
        elif self.selection_method == 2:
            self.select_top_n(self.top_n)

    def _selection_edited(self):
        # Selection was edited by clicking on a cluster in the
        # dendrogram view.
        self.selection_method = 0
        self._selection_method_changed()
        self._invalidate_output()

    def _save_selection(self):
        # Save the current manual node selection state
        selection_state = None
        if self.selection_method == 0 and self.root:
            assert self.linkmatrix is not None
            linkmat = [(int(_0), int(_1), _2)
                       for _0, _1, _2 in self.linkmatrix[:, :3].tolist()]
            nodes_ = self.dendrogram.selected_nodes()
            # match the display (pruned) nodes back (by ranges)
            mapping = {node.value.range: node for node in postorder(self.root)}
            nodes = [mapping[node.value.range] for node in nodes_]
            indices = [tuple(node.value.index for node in leaves(node))
                       for node in nodes]
            if nodes:
                selection_state = (indices, linkmat)
        return selection_state

    def save_state(self):
        # type: () -> Dict[str, Any]
        """
        Save state for `set_restore_state`
        """
        selection = self._save_selection()
        res = {"version": (0, 0, 0)}
        if selection is not None:
            res["selection_state"] = selection
        return res

    def set_restore_state(self, state):
        # type: (Dict[str, Any]) -> bool
        """
        Restore session data from a saved state.

        Parameters
        ----------
        state : Dict[str, Any]

        NOTE
        ----
        This is method called while the instance (self) is being constructed,
        even before its `__init__` is called. Consider `self` to be only a
        `QObject` at this stage.
        """
        if "selection_state" in state:
            selection = state["selection_state"]
            self.__pending_selection_restore = selection
        return True

    def __zoom_in(self):
        def clip(minval, maxval, val):
            return min(max(val, minval), maxval)
        self.zoom_factor = clip(self.zoom_slider.minimum(),
                                self.zoom_slider.maximum(),
                                self.zoom_factor + 1)
        self.__update_font_scale()

    def __zoom_out(self):
        def clip(minval, maxval, val):
            return min(max(val, minval), maxval)
        self.zoom_factor = clip(self.zoom_slider.minimum(),
                                self.zoom_slider.maximum(),
                                self.zoom_factor - 1)
        self.__update_font_scale()

    def __zoom_reset(self):
        self.zoom_factor = 0
        self.__update_font_scale()

    def __layout_main_graphics(self, width=-1):
        if width < 0:
            # Preserve current width.
            width = self._main_graphics.size().width()
        preferred = self._main_graphics.effectiveSizeHint(
            Qt.PreferredSize, constraint=QSizeF(width, -1))
        self._main_graphics.resize(QSizeF(width, preferred.height()))
        mw = self._main_graphics.minimumWidth() + 4
        self.view.setMinimumWidth(mw + self.view.verticalScrollBar().width())

    def __update_font_scale(self):
        font = self.scene.font()
        factor = (1.25 ** self.zoom_factor)
        font = qfont_scaled(font, factor)
        self._main_graphics.setFont(font)

    def send_report(self):
        annot = self.label_cb.currentText()
        if isinstance(self.annotation, str):
            annot = annot.lower()
        if self.selection_method == 0:
            sel = "manual"
        elif self.selection_method == 1:
            sel = "at {:.1f} of height".format(self.cut_ratio)
        else:
            sel = "top {} clusters".format(self.top_n)
        self.report_items((
            ("Linkage", LINKAGE[self.linkage].lower()),
            ("Annotation", annot),
            ("Prunning",
             self.pruning != 0 and "{} levels".format(self.max_depth)),
            ("Selection", sel),
        ))
        self.report_plot()


def qfont_scaled(font, factor):
    scaled = QFont(font)
    if font.pointSizeF() != -1:
        scaled.setPointSizeF(font.pointSizeF() * factor)
    elif font.pixelSize() != -1:
        scaled.setPixelSize(int(font.pixelSize() * factor))
    return scaled


class AxisItem(pg.AxisItem):
    mousePressed = Signal(QPointF, Qt.MouseButton)
    mouseMoved = Signal(QPointF, Qt.MouseButtons)
    mouseReleased = Signal(QPointF, Qt.MouseButton)

    #: \reimp
    def wheelEvent(self, event):
        event.ignore()  # ignore event to propagate to the view -> scroll

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        self.mousePressed.emit(event.pos(), event.button())
        super().mousePressEvent(event)
        event.accept()

    def mouseMoveEvent(self, event):
        self.mouseMoved.emit(event.pos(), event.buttons())
        super().mouseMoveEvent(event)
        event.accept()

    def mouseReleaseEvent(self, event):
        self.mouseReleased.emit(event.pos(), event.button())
        super().mouseReleaseEvent(event)
        event.accept()


class SliderLine(QGraphicsObject):
    """A movable slider line."""
    valueChanged = Signal(float)

    linePressed = Signal()
    lineMoved = Signal()
    lineReleased = Signal()
    rangeChanged = Signal(float, float)

    def __init__(self, parent=None, orientation=Qt.Vertical, value=0.0,
                 length=10.0, **kwargs):
        self._orientation = orientation
        self._value = value
        self._length = length
        self._min = 0.0
        self._max = 1.0
        self._line = QLineF()  # type: Optional[QLineF]
        self._pen = QPen()
        super().__init__(parent, **kwargs)

        self.setAcceptedMouseButtons(Qt.LeftButton)
        self.setPen(make_pen(brush=QColor(50, 50, 50), width=1, cosmetic=False,
                             style=Qt.DashLine))

        if self._orientation == Qt.Vertical:
            self.setCursor(Qt.SizeVerCursor)
        else:
            self.setCursor(Qt.SizeHorCursor)

    def setPen(self, pen: Union[QPen, Qt.GlobalColor, Qt.PenStyle]) -> None:
        pen = QPen(pen)
        if self._pen != pen:
            self.prepareGeometryChange()
            self._pen = pen
            self._line = None
            self.update()

    def pen(self) -> QPen:
        return QPen(self._pen)

    def setValue(self, value: float):
        value = min(max(value, self._min), self._max)

        if self._value != value:
            self.prepareGeometryChange()
            self._value = value
            self._line = None
            self.valueChanged.emit(value)

    def value(self) -> float:
        return self._value

    def setRange(self, minval: float, maxval: float) -> None:
        maxval = max(minval, maxval)
        if minval != self._min or maxval != self._max:
            self._min = minval
            self._max = maxval
            self.rangeChanged.emit(minval, maxval)
            self.setValue(self._value)

    def setLength(self, length: float):
        if self._length != length:
            self.prepareGeometryChange()
            self._length = length
            self._line = None

    def length(self) -> float:
        return self._length

    def setOrientation(self, orientation: Qt.Orientation):
        if self._orientation != orientation:
            self.prepareGeometryChange()
            self._orientation = orientation
            self._line = None
            if self._orientation == Qt.Vertical:
                self.setCursor(Qt.SizeVerCursor)
            else:
                self.setCursor(Qt.SizeHorCursor)

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        event.accept()
        self.linePressed.emit()

    def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        pos = event.pos()
        if self._orientation == Qt.Vertical:
            self.setValue(pos.y())
        else:
            self.setValue(pos.x())
        self.lineMoved.emit()
        event.accept()

    def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        if self._orientation == Qt.Vertical:
            self.setValue(event.pos().y())
        else:
            self.setValue(event.pos().x())
        self.lineReleased.emit()
        event.accept()

    def boundingRect(self) -> QRectF:
        if self._line is None:
            if self._orientation == Qt.Vertical:
                self._line = QLineF(0, self._value, self._length, self._value)
            else:
                self._line = QLineF(self._value, 0, self._value, self._length)
        r = QRectF(self._line.p1(), self._line.p2())
        penw = self.pen().width()
        return r.adjusted(-penw, -penw, penw, penw)

    def paint(self, painter, *args):
        if self._line is None:
            self.boundingRect()

        painter.save()
        painter.setPen(self.pen())
        painter.drawLine(self._line)
        painter.restore()


def clusters_at_height(root, height):
    """Return a list of clusters by cutting the clustering at `height`.
    """
    lower = set()
    cluster_list = []
    for cl in preorder(root):
        if cl in lower:
            continue
        if cl.value.height < height:
            cluster_list.append(cl)
            lower.update(preorder(cl))
    return cluster_list


if __name__ == "__main__":  # pragma: no cover
    from Orange import distance
    data = Orange.data.Table("iris")
    matrix = distance.Euclidean(distance._preprocess(data))
    WidgetPreview(OWHierarchicalClustering).run(matrix)

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
[ISSUE]
**Title:** Annotated Data Missing When Using Column Distances in Hierarchical Clustering

**Description:**
When performing hierarchical clustering using column distances, the widget fails to produce the annotated data. This results in an `AttributeError` when attempting to access the domain attributes of the annotated data.

**Example:**
```python
def test_column_distances():
    widget.send_signal(Input.DISTANCES, distances_cols)
    widget.select_data_columns()
    output = widget.get_output(Output.ANNOTATED_DATA)
    annotated = [(attr.name, attr.attributes['cluster']) for attr in output.domain.attributes]
```
In this test scenario, the `output` is `None`, leading to the following error:
```
AttributeError: 'NoneType' object has no attribute 'domain'
```

**Expected Behavior:**
When using column distances for hierarchical clustering, the widget should successfully annotate the variables with their respective clusters. Accessing the `domain` attributes of the annotated data should work without errors, allowing further analysis and processing.

[/ISSUE]
{
    "TestOWHierarchicalClustering.test_all_zero_inputs": "PASSED",
    "TestOWHierarchicalClustering.test_annotation_settings_retrieval": "PASSED",
    "TestOWHierarchicalClustering.test_column_distances": "PASSED",
    "TestOWHierarchicalClustering.test_domain_loses_class": "PASSED",
    "TestOWHierarchicalClustering.test_image_export": "PASSED",
    "TestOWHierarchicalClustering.test_infinite_distances": "PASSED",
    "TestOWHierarchicalClustering.test_minimum_size": "PASSED",
    "TestOWHierarchicalClustering.test_msg_base_class": "PASSED",
    "TestOWHierarchicalClustering.test_output_cut_ratio": "PASSED",
    "TestOWHierarchicalClustering.test_outputs": "PASSED",
    "TestOWHierarchicalClustering.test_restore_state": "PASSED",
    "TestOWHierarchicalClustering.test_retain_selection": "PASSED",
    "TestOWHierarchicalClustering.test_selection_box_output": "PASSED"
}
diff --git a/Orange/widgets/unsupervised/owhierarchicalclustering.py b/Orange/widgets/unsupervised/owhierarchicalclustering.py
index 6d3f87120..b55c7af91 100644
--- a/Orange/widgets/unsupervised/owhierarchicalclustering.py
+++ b/Orange/widgets/unsupervised/owhierarchicalclustering.py
@@ -610,16 +610,29 @@ class OWHierarchicalClustering(widget.OWWidget):
                 selected_data.domain = Domain(
                     attrs, classes, metas + (clust_var, ))
 
+            annotated_data = create_annotated_table(data, selected_indices)
+
         elif isinstance(items, Orange.data.Table) and self.matrix.axis == 0:
             # Select columns
+            attrs = []
+            for clust, indices in chain(enumerate(maps, start=1),
+                                        [(0, unselected_indices)]):
+                for i in indices:
+                    attr = items.domain[i].copy()
+                    attr.attributes["cluster"] = clust
+                    attrs.append(attr)
             domain = Orange.data.Domain(
-                [items.domain[i] for i in selected_indices],
+                # len(unselected_indices) can be 0
+                attrs[:len(attrs) - len(unselected_indices)],
                 items.domain.class_vars, items.domain.metas)
             selected_data = items.from_table(domain, items)
-            data = None
+
+            domain = Orange.data.Domain(
+                attrs,
+                items.domain.class_vars, items.domain.metas)
+            annotated_data = items.from_table(domain, items)
 
         self.Outputs.selected_data.send(selected_data)
-        annotated_data = create_annotated_table(data, selected_indices)
         self.Outputs.annotated_data.send(annotated_data)
 
     def eventFilter(self, obj, event):

{"Orange/widgets/unsupervised/owhierarchicalclustering.py": "from itertools import chain\nfrom contextlib import contextmanager\n\nimport typing\nfrom typing import Any, List, Tuple, Dict, Optional, Set, Union\n\nimport numpy as np\n\nfrom AnyQt.QtWidgets import (\n    QGraphicsWidget, QGraphicsObject, QGraphicsScene, QGridLayout, QSizePolicy,\n    QAction, QComboBox, QGraphicsGridLayout, QGraphicsSceneMouseEvent\n)\nfrom AnyQt.QtGui import QColor, QPen, QFont, QKeySequence\nfrom AnyQt.QtCore import Qt, QSize, QSizeF, QPointF, QRectF, QLineF, QEvent\nfrom AnyQt.QtCore import pyqtSignal as Signal, pyqtSlot as Slot\n\nimport pyqtgraph as pg\n\nimport Orange.data\nfrom Orange.data.domain import filter_visible\nfrom Orange.data import Domain\nimport Orange.misc\nfrom Orange.clustering.hierarchical import \\\n    postorder, preorder, Tree, tree_from_linkage, dist_matrix_linkage, \\\n    leaves, prune, top_clusters\nfrom Orange.data.util import get_unique_names\n\nfrom Orange.widgets import widget, gui, settings\nfrom Orange.widgets.utils import itemmodels, combobox\nfrom Orange.widgets.utils.annotated_data import (create_annotated_table,\n                                                 ANNOTATED_DATA_SIGNAL_NAME)\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.widget import Input, Output, Msg\n\nfrom Orange.widgets.utils.stickygraphicsview import StickyGraphicsView\nfrom Orange.widgets.utils.graphicstextlist import TextListWidget\nfrom Orange.widgets.utils.dendrogram import DendrogramWidget\n\n__all__ = [\"OWHierarchicalClustering\"]\n\n\nLINKAGE = [\"Single\", \"Average\", \"Weighted\", \"Complete\", \"Ward\"]\n\n\ndef make_pen(brush=Qt.black, width=1, style=Qt.SolidLine,\n             cap_style=Qt.SquareCap, join_style=Qt.BevelJoin,\n             cosmetic=False):\n    pen = QPen(brush)\n    pen.setWidth(width)\n    pen.setStyle(style)\n    pen.setCapStyle(cap_style)\n    pen.setJoinStyle(join_style)\n    pen.setCosmetic(cosmetic)\n    return pen\n\n\n@contextmanager\ndef blocked(obj):\n    old = obj.signalsBlocked()\n    obj.blockSignals(True)\n    try:\n        yield obj\n    finally:\n        obj.blockSignals(old)\n\n\nclass SaveStateSettingsHandler(settings.SettingsHandler):\n    \"\"\"\n    A settings handler that delegates session data store/restore to the\n    OWWidget instance.\n\n    The OWWidget subclass must implement `save_state() -> Dict[str, Any]` and\n    `set_restore_state(state: Dict[str, Any])` methods.\n    \"\"\"\n    def initialize(self, instance, data=None):\n        super().initialize(instance, data)\n        if data is not None and \"__session_state_data\" in data:\n            session_data = data[\"__session_state_data\"]\n            instance.set_restore_state(session_data)\n\n    def pack_data(self, widget):\n        # type: (widget.OWWidget) -> dict\n        res = super().pack_data(widget)\n        state = widget.save_state()\n        if state:\n            assert \"__session_state_data\" not in res\n            res[\"__session_state_data\"] = state\n        return res\n\n\nclass _DomainContextHandler(settings.DomainContextHandler,\n                            SaveStateSettingsHandler):\n    pass\n\n\nif typing.TYPE_CHECKING:\n    #: Encoded selection state for persistent storage.\n    #: This is a list of tuples of leaf indices in the selection and\n    #: a (N, 3) linkage matrix for validation (the 4-th column from scipy\n    #: is omitted).\n    SelectionState = Tuple[List[Tuple[int]], List[Tuple[int, int, float]]]\n\n\nclass OWHierarchicalClustering(widget.OWWidget):\n    name = \"Hierarchical Clustering\"\n    description = \"Display a dendrogram of a hierarchical clustering \" \\\n                  \"constructed from the input distance matrix.\"\n    icon = \"icons/HierarchicalClustering.svg\"\n    priority = 2100\n    keywords = []\n\n    class Inputs:\n        distances = Input(\"Distances\", Orange.misc.DistMatrix)\n\n    class Outputs:\n        selected_data = Output(\"Selected Data\", Orange.data.Table, default=True)\n        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table)\n\n    settingsHandler = _DomainContextHandler()\n\n    #: Selected linkage\n    linkage = settings.Setting(1)\n    #: Index of the selected annotation item (variable, ...)\n    annotation = settings.ContextSetting(\"Enumeration\")\n    #: Out-of-context setting for the case when the \"Name\" option is available\n    annotation_if_names = settings.Setting(\"Name\")\n    #: Out-of-context setting for the case with just \"Enumerate\" and \"None\"\n    annotation_if_enumerate = settings.Setting(\"Enumerate\")\n    #: Selected tree pruning (none/max depth)\n    pruning = settings.Setting(0)\n    #: Maximum depth when max depth pruning is selected\n    max_depth = settings.Setting(10)\n\n    #: Selected cluster selection method (none, cut distance, top n)\n    selection_method = settings.Setting(0)\n    #: Cut height ratio wrt root height\n    cut_ratio = settings.Setting(75.0)\n    #: Number of top clusters to select\n    top_n = settings.Setting(3)\n    #: Dendrogram zoom factor\n    zoom_factor = settings.Setting(0)\n\n    autocommit = settings.Setting(True)\n\n    graph_name = \"scene\"\n\n    basic_annotations = [\"None\", \"Enumeration\"]\n\n    class Error(widget.OWWidget.Error):\n        not_finite_distances = Msg(\"Some distances are infinite\")\n\n    #: Stored (manual) selection state (from a saved workflow) to restore.\n    __pending_selection_restore = None  # type: Optional[SelectionState]\n\n    def __init__(self):\n        super().__init__()\n\n        self.matrix = None\n        self.items = None\n        self.linkmatrix = None\n        self.root = None\n        self._displayed_root = None\n        self.cutoff_height = 0.0\n\n        gui.comboBox(\n            self.controlArea, self, \"linkage\", items=LINKAGE, box=\"Linkage\",\n            callback=self._invalidate_clustering)\n\n        model = itemmodels.VariableListModel()\n        model[:] = self.basic_annotations\n\n        box = gui.widgetBox(self.controlArea, \"Annotations\")\n        self.label_cb = cb = combobox.ComboBoxSearch(\n            minimumContentsLength=14,\n            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon\n        )\n        cb.setModel(model)\n        cb.setCurrentIndex(cb.findData(self.annotation, Qt.EditRole))\n\n        def on_annotation_activated():\n            self.annotation = cb.currentData(Qt.EditRole)\n            self._update_labels()\n        cb.activated.connect(on_annotation_activated)\n\n        def on_annotation_changed(value):\n            cb.setCurrentIndex(cb.findData(value, Qt.EditRole))\n        self.connect_control(\"annotation\", on_annotation_changed)\n\n        box.layout().addWidget(self.label_cb)\n\n        box = gui.radioButtons(\n            self.controlArea, self, \"pruning\", box=\"Pruning\",\n            callback=self._invalidate_pruning)\n        grid = QGridLayout()\n        box.layout().addLayout(grid)\n        grid.addWidget(\n            gui.appendRadioButton(box, \"None\", addToLayout=False),\n            0, 0\n        )\n        self.max_depth_spin = gui.spin(\n            box, self, \"max_depth\", minv=1, maxv=100,\n            callback=self._invalidate_pruning,\n            keyboardTracking=False, addToLayout=False\n        )\n\n        grid.addWidget(\n            gui.appendRadioButton(box, \"Max depth:\", addToLayout=False),\n            1, 0)\n        grid.addWidget(self.max_depth_spin, 1, 1)\n\n        self.selection_box = gui.radioButtons(\n            self.controlArea, self, \"selection_method\",\n            box=\"Selection\",\n            callback=self._selection_method_changed)\n\n        grid = QGridLayout()\n        self.selection_box.layout().addLayout(grid)\n        grid.addWidget(\n            gui.appendRadioButton(\n                self.selection_box, \"Manual\", addToLayout=False),\n            0, 0\n        )\n        grid.addWidget(\n            gui.appendRadioButton(\n                self.selection_box, \"Height ratio:\", addToLayout=False),\n            1, 0\n        )\n        self.cut_ratio_spin = gui.spin(\n            self.selection_box, self, \"cut_ratio\", 0, 100, step=1e-1,\n            spinType=float, callback=self._selection_method_changed,\n            addToLayout=False\n        )\n        self.cut_ratio_spin.setSuffix(\"%\")\n\n        grid.addWidget(self.cut_ratio_spin, 1, 1)\n\n        grid.addWidget(\n            gui.appendRadioButton(\n                self.selection_box, \"Top N:\", addToLayout=False),\n            2, 0\n        )\n        self.top_n_spin = gui.spin(self.selection_box, self, \"top_n\", 1, 20,\n                                   callback=self._selection_method_changed,\n                                   addToLayout=False)\n        grid.addWidget(self.top_n_spin, 2, 1)\n\n        self.zoom_slider = gui.hSlider(\n            self.controlArea, self, \"zoom_factor\", box=\"Zoom\",\n            minValue=-6, maxValue=3, step=1, ticks=True, createLabel=False,\n            callback=self.__update_font_scale)\n\n        zoom_in = QAction(\n            \"Zoom in\", self, shortcut=QKeySequence.ZoomIn,\n            triggered=self.__zoom_in\n        )\n        zoom_out = QAction(\n            \"Zoom out\", self, shortcut=QKeySequence.ZoomOut,\n            triggered=self.__zoom_out\n        )\n        zoom_reset = QAction(\n            \"Reset zoom\", self,\n            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0),\n            triggered=self.__zoom_reset\n        )\n        self.addActions([zoom_in, zoom_out, zoom_reset])\n\n        self.controlArea.layout().addStretch()\n\n        gui.auto_send(self.buttonsArea, self, \"autocommit\")\n\n        self.scene = QGraphicsScene(self)\n        self.view = StickyGraphicsView(\n            self.scene,\n            horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff,\n            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn,\n            alignment=Qt.AlignLeft | Qt.AlignVCenter\n        )\n        self.mainArea.layout().setSpacing(1)\n        self.mainArea.layout().addWidget(self.view)\n\n        def axis_view(orientation):\n            ax = AxisItem(orientation=orientation, maxTickLength=7)\n            ax.mousePressed.connect(self._activate_cut_line)\n            ax.mouseMoved.connect(self._activate_cut_line)\n            ax.mouseReleased.connect(self._activate_cut_line)\n            ax.setRange(1.0, 0.0)\n            return ax\n\n        self.top_axis = axis_view(\"top\")\n        self.bottom_axis = axis_view(\"bottom\")\n\n        self._main_graphics = QGraphicsWidget()\n        scenelayout = QGraphicsGridLayout()\n        scenelayout.setHorizontalSpacing(10)\n        scenelayout.setVerticalSpacing(10)\n\n        self._main_graphics.setLayout(scenelayout)\n        self.scene.addItem(self._main_graphics)\n\n        self.dendrogram = DendrogramWidget()\n        self.dendrogram.setSizePolicy(QSizePolicy.MinimumExpanding,\n                                      QSizePolicy.MinimumExpanding)\n        self.dendrogram.selectionChanged.connect(self._invalidate_output)\n        self.dendrogram.selectionEdited.connect(self._selection_edited)\n\n        self.labels = TextListWidget()\n        self.labels.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)\n        self.labels.setAlignment(Qt.AlignLeft)\n        self.labels.setMaximumWidth(200)\n\n        scenelayout.addItem(self.top_axis, 0, 0,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        scenelayout.addItem(self.dendrogram, 1, 0,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        scenelayout.addItem(self.labels, 1, 1,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        scenelayout.addItem(self.bottom_axis, 2, 0,\n                            alignment=Qt.AlignLeft | Qt.AlignVCenter)\n        self.view.viewport().installEventFilter(self)\n        self._main_graphics.installEventFilter(self)\n\n        self.top_axis.setZValue(self.dendrogram.zValue() + 10)\n        self.bottom_axis.setZValue(self.dendrogram.zValue() + 10)\n        self.cut_line = SliderLine(self.top_axis,\n                                   orientation=Qt.Horizontal)\n        self.cut_line.valueChanged.connect(self._dendrogram_slider_changed)\n        self.dendrogram.geometryChanged.connect(self._dendrogram_geom_changed)\n        self._set_cut_line_visible(self.selection_method == 1)\n        self.__update_font_scale()\n\n    @Inputs.distances\n    def set_distances(self, matrix):\n        if self.__pending_selection_restore is not None:\n            selection_state = self.__pending_selection_restore\n        else:\n            # save the current selection to (possibly) restore later\n            selection_state = self._save_selection()\n\n        self.error()\n        self.Error.clear()\n        if matrix is not None:\n            N, _ = matrix.shape\n            if N < 2:\n                self.error(\"Empty distance matrix\")\n                matrix = None\n        if matrix is not None:\n            if not np.all(np.isfinite(matrix)):\n                self.Error.not_finite_distances()\n                matrix = None\n\n        self.matrix = matrix\n        if matrix is not None:\n            self._set_items(matrix.row_items, matrix.axis)\n        else:\n            self._set_items(None)\n        self._invalidate_clustering()\n\n        # Can now attempt to restore session state from a saved workflow.\n        if self.root and selection_state is not None:\n            self._restore_selection(selection_state)\n            self.__pending_selection_restore = None\n\n        self.unconditional_commit()\n\n    def _set_items(self, items, axis=1):\n        self.closeContext()\n        self.items = items\n        model = self.label_cb.model()\n        if len(model) == 3:\n            self.annotation_if_names = self.annotation\n        elif len(model) == 2:\n            self.annotation_if_enumerate = self.annotation\n        if isinstance(items, Orange.data.Table) and axis:\n            model[:] = chain(\n                self.basic_annotations,\n                [model.Separator],\n                items.domain.class_vars,\n                items.domain.metas,\n                [model.Separator] if (items.domain.class_vars or items.domain.metas) and\n                next(filter_visible(items.domain.attributes), False) else [],\n                filter_visible(items.domain.attributes)\n            )\n            if items.domain.class_vars:\n                self.annotation = items.domain.class_vars[0]\n            else:\n                self.annotation = \"Enumeration\"\n            self.openContext(items.domain)\n        else:\n            name_option = bool(\n                items is not None and (\n                    not axis or\n                    isinstance(items, list) and\n                    all(isinstance(var, Orange.data.Variable) for var in items)))\n            model[:] = self.basic_annotations + [\"Name\"] * name_option\n            self.annotation = self.annotation_if_names if name_option \\\n                else self.annotation_if_enumerate\n\n    def _clear_plot(self):\n        self.dendrogram.set_root(None)\n        self.labels.setItems([])\n\n    def _set_displayed_root(self, root):\n        self._clear_plot()\n        self._displayed_root = root\n        self.dendrogram.set_root(root)\n\n        self._update_labels()\n\n        self._main_graphics.resize(\n            self._main_graphics.size().width(),\n            self._main_graphics.sizeHint(Qt.PreferredSize).height()\n        )\n        self._main_graphics.layout().activate()\n\n    def _update(self):\n        self._clear_plot()\n\n        distances = self.matrix\n\n        if distances is not None:\n            method = LINKAGE[self.linkage].lower()\n            Z = dist_matrix_linkage(distances, linkage=method)\n\n            tree = tree_from_linkage(Z)\n            self.linkmatrix = Z\n            self.root = tree\n\n            self.top_axis.setRange(tree.value.height, 0.0)\n            self.bottom_axis.setRange(tree.value.height, 0.0)\n\n            if self.pruning:\n                self._set_displayed_root(prune(tree, level=self.max_depth))\n            else:\n                self._set_displayed_root(tree)\n        else:\n            self.linkmatrix = None\n            self.root = None\n            self._set_displayed_root(None)\n\n        self._apply_selection()\n\n    def _update_labels(self):\n        labels = []\n        if self.root and self._displayed_root:\n            indices = [leaf.value.index for leaf in leaves(self.root)]\n\n            if self.annotation == \"None\":\n                labels = []\n            elif self.annotation == \"Enumeration\":\n                labels = [str(i+1) for i in indices]\n            elif self.annotation == \"Name\":\n                attr = self.matrix.row_items.domain.attributes\n                labels = [str(attr[i]) for i in indices]\n            elif isinstance(self.annotation, Orange.data.Variable):\n                col_data, _ = self.items.get_column_view(self.annotation)\n                labels = [self.annotation.str_val(val) for val in col_data]\n                labels = [labels[idx] for idx in indices]\n            else:\n                labels = []\n\n            if labels and self._displayed_root is not self.root:\n                joined = leaves(self._displayed_root)\n                labels = [\", \".join(labels[leaf.value.first: leaf.value.last])\n                          for leaf in joined]\n\n        self.labels.setItems(labels)\n        self.labels.setMinimumWidth(1 if labels else -1)\n\n    def _restore_selection(self, state):\n        # type: (SelectionState) -> bool\n        \"\"\"\n        Restore the (manual) node selection state.\n\n        Return True if successful; False otherwise.\n        \"\"\"\n        linkmatrix = self.linkmatrix\n        if self.selection_method == 0 and self.root:\n            selected, linksaved = state\n            linkstruct = np.array(linksaved, dtype=float)\n            selected = set(selected)  # type: Set[Tuple[int]]\n            if not selected:\n                return False\n            if linkmatrix.shape[0] != linkstruct.shape[0]:\n                return False\n            # check that the linkage matrix structure matches. Use isclose for\n            # the height column to account for inexact floating point math\n            # (e.g. summation order in different ?gemm implementations for\n            # euclidean distances, ...)\n            if np.any(linkstruct[:, :2] != linkmatrix[:, :2]) or \\\n                    not np.all(np.isclose(linkstruct[:, 2], linkstruct[:, 2])):\n                return False\n            selection = []\n            indices = np.array([n.value.index for n in leaves(self.root)],\n                               dtype=int)\n            # mapping from ranges to display (pruned) nodes\n            mapping = {node.value.range: node\n                       for node in postorder(self._displayed_root)}\n            for node in postorder(self.root):  # type: Tree\n                r = tuple(indices[node.value.first: node.value.last])\n                if r in selected:\n                    if node.value.range not in mapping:\n                        # the node was pruned from display and cannot be\n                        # selected\n                        break\n                    selection.append(mapping[node.value.range])\n                    selected.remove(r)\n                if not selected:\n                    break  # found all, nothing more to do\n            if selection and selected:\n                # Could not restore all selected nodes (only partial match)\n                return False\n\n            self._set_selected_nodes(selection)\n            return True\n        return False\n\n    def _set_selected_nodes(self, selection):\n        # type: (List[Tree]) -> None\n        \"\"\"\n        Set the nodes in `selection` to be the current selected nodes.\n\n        The selection nodes must be subtrees of the current `_displayed_root`.\n        \"\"\"\n        self.dendrogram.selectionChanged.disconnect(self._invalidate_output)\n        try:\n            self.dendrogram.set_selected_clusters(selection)\n        finally:\n            self.dendrogram.selectionChanged.connect(self._invalidate_output)\n\n    def _invalidate_clustering(self):\n        self._update()\n        self._update_labels()\n        self._invalidate_output()\n\n    def _invalidate_output(self):\n        self.commit()\n\n    def _invalidate_pruning(self):\n        if self.root:\n            selection = self.dendrogram.selected_nodes()\n            ranges = [node.value.range for node in selection]\n            if self.pruning:\n                self._set_displayed_root(\n                    prune(self.root, level=self.max_depth))\n            else:\n                self._set_displayed_root(self.root)\n            selected = [node for node in preorder(self._displayed_root)\n                        if node.value.range in ranges]\n\n            self.dendrogram.set_selected_clusters(selected)\n\n        self._apply_selection()\n\n    def commit(self):\n        items = getattr(self.matrix, \"items\", self.items)\n        if not items:\n            self.Outputs.selected_data.send(None)\n            self.Outputs.annotated_data.send(None)\n            return\n\n        selection = self.dendrogram.selected_nodes()\n        selection = sorted(selection, key=lambda c: c.value.first)\n\n        indices = [leaf.value.index for leaf in leaves(self.root)]\n\n        maps = [indices[node.value.first:node.value.last]\n                for node in selection]\n\n        selected_indices = list(chain(*maps))\n        unselected_indices = sorted(set(range(self.root.value.last)) -\n                                    set(selected_indices))\n\n        if not selected_indices:\n            self.Outputs.selected_data.send(None)\n            annotated_data = create_annotated_table(items, []) \\\n                if self.selection_method == 0 and self.matrix.axis else None\n            self.Outputs.annotated_data.send(annotated_data)\n            return\n\n        selected_data = None\n\n        if isinstance(items, Orange.data.Table) and self.matrix.axis == 1:\n            # Select rows\n            c = np.zeros(self.matrix.shape[0])\n\n            for i, indices in enumerate(maps):\n                c[indices] = i\n            c[unselected_indices] = len(maps)\n\n            mask = c != len(maps)\n\n            data, domain = items, items.domain\n            attrs = domain.attributes\n            classes = domain.class_vars\n            metas = domain.metas\n\n            var_name = get_unique_names(domain, \"Cluster\")\n            values = [f\"C{i + 1}\" for i in range(len(maps))]\n\n            clust_var = Orange.data.DiscreteVariable(\n                var_name, values=values + [\"Other\"])\n            domain = Orange.data.Domain(attrs, classes, metas + (clust_var,))\n            data = items.transform(domain)\n            data.get_column_view(clust_var)[0][:] = c\n\n            if selected_indices:\n                selected_data = data[mask]\n                clust_var = Orange.data.DiscreteVariable(\n                    var_name, values=values)\n                selected_data.domain = Domain(\n                    attrs, classes, metas + (clust_var, ))\n\n        elif isinstance(items, Orange.data.Table) and self.matrix.axis == 0:\n            # Select columns\n            domain = Orange.data.Domain(\n                [items.domain[i] for i in selected_indices],\n                items.domain.class_vars, items.domain.metas)\n            selected_data = items.from_table(domain, items)\n            data = None\n\n        self.Outputs.selected_data.send(selected_data)\n        annotated_data = create_annotated_table(data, selected_indices)\n        self.Outputs.annotated_data.send(annotated_data)\n\n    def eventFilter(self, obj, event):\n        if obj is self.view.viewport() and event.type() == QEvent.Resize:\n            # NOTE: not using viewport.width(), due to 'transient' scroll bars\n            # (macOS). Viewport covers the whole view, but QGraphicsView still\n            # scrolls left, right with scroll bar extent (other\n            # QAbstractScrollArea widgets behave as expected).\n            w_frame = self.view.frameWidth()\n            margin = self.view.viewportMargins()\n            w_scroll = self.view.verticalScrollBar().width()\n            width = (self.view.width() - w_frame * 2 -\n                     margin.left() - margin.right() - w_scroll)\n            # layout with new width constraint\n            self.__layout_main_graphics(width=width)\n        elif obj is self._main_graphics and \\\n                event.type() == QEvent.LayoutRequest:\n            # layout preserving the width (vertical re layout)\n            self.__layout_main_graphics()\n        return super().eventFilter(obj, event)\n\n    @Slot(QPointF)\n    def _activate_cut_line(self, pos: QPointF):\n        \"\"\"Activate cut line selection an set cut value to `pos.x()`.\"\"\"\n        self.selection_method = 1\n        self.cut_line.setValue(pos.x())\n        self._selection_method_changed()\n\n    def onDeleteWidget(self):\n        super().onDeleteWidget()\n        self._clear_plot()\n        self.dendrogram.clear()\n        self.dendrogram.deleteLater()\n\n    def _dendrogram_geom_changed(self):\n        pos = self.dendrogram.pos_at_height(self.cutoff_height)\n        geom = self.dendrogram.geometry()\n        self._set_slider_value(pos.x(), geom.width())\n\n        self.cut_line.setLength(\n            self.bottom_axis.geometry().bottom()\n            - self.top_axis.geometry().top()\n        )\n\n        geom = self._main_graphics.geometry()\n        assert geom.topLeft() == QPointF(0, 0)\n\n        def adjustLeft(rect):\n            rect = QRectF(rect)\n            rect.setLeft(geom.left())\n            return rect\n        margin = 3\n        self.scene.setSceneRect(geom)\n        self.view.setSceneRect(geom)\n        self.view.setHeaderSceneRect(\n            adjustLeft(self.top_axis.geometry()).adjusted(0, 0, 0, margin)\n        )\n        self.view.setFooterSceneRect(\n            adjustLeft(self.bottom_axis.geometry()).adjusted(0, -margin, 0, 0)\n        )\n\n    def _dendrogram_slider_changed(self, value):\n        p = QPointF(value, 0)\n        cl_height = self.dendrogram.height_at(p)\n\n        self.set_cutoff_height(cl_height)\n\n    def _set_slider_value(self, value, span):\n        with blocked(self.cut_line):\n            self.cut_line.setRange(0, span)\n            self.cut_line.setValue(value)\n\n    def set_cutoff_height(self, height):\n        self.cutoff_height = height\n        if self.root:\n            self.cut_ratio = 100 * height / self.root.value.height\n        self.select_max_height(height)\n\n    def _set_cut_line_visible(self, visible):\n        self.cut_line.setVisible(visible)\n\n    def select_top_n(self, n):\n        root = self._displayed_root\n        if root:\n            clusters = top_clusters(root, n)\n            self.dendrogram.set_selected_clusters(clusters)\n\n    def select_max_height(self, height):\n        root = self._displayed_root\n        if root:\n            clusters = clusters_at_height(root, height)\n            self.dendrogram.set_selected_clusters(clusters)\n\n    def _selection_method_changed(self):\n        self._set_cut_line_visible(self.selection_method == 1)\n        if self.root:\n            self._apply_selection()\n\n    def _apply_selection(self):\n        if not self.root:\n            return\n\n        if self.selection_method == 0:\n            pass\n        elif self.selection_method == 1:\n            height = self.cut_ratio * self.root.value.height / 100\n            self.set_cutoff_height(height)\n            pos = self.dendrogram.pos_at_height(height)\n            self._set_slider_value(pos.x(), self.dendrogram.size().width())\n        elif self.selection_method == 2:\n            self.select_top_n(self.top_n)\n\n    def _selection_edited(self):\n        # Selection was edited by clicking on a cluster in the\n        # dendrogram view.\n        self.selection_method = 0\n        self._selection_method_changed()\n        self._invalidate_output()\n\n    def _save_selection(self):\n        # Save the current manual node selection state\n        selection_state = None\n        if self.selection_method == 0 and self.root:\n            assert self.linkmatrix is not None\n            linkmat = [(int(_0), int(_1), _2)\n                       for _0, _1, _2 in self.linkmatrix[:, :3].tolist()]\n            nodes_ = self.dendrogram.selected_nodes()\n            # match the display (pruned) nodes back (by ranges)\n            mapping = {node.value.range: node for node in postorder(self.root)}\n            nodes = [mapping[node.value.range] for node in nodes_]\n            indices = [tuple(node.value.index for node in leaves(node))\n                       for node in nodes]\n            if nodes:\n                selection_state = (indices, linkmat)\n        return selection_state\n\n    def save_state(self):\n        # type: () -> Dict[str, Any]\n        \"\"\"\n        Save state for `set_restore_state`\n        \"\"\"\n        selection = self._save_selection()\n        res = {\"version\": (0, 0, 0)}\n        if selection is not None:\n            res[\"selection_state\"] = selection\n        return res\n\n    def set_restore_state(self, state):\n        # type: (Dict[str, Any]) -> bool\n        \"\"\"\n        Restore session data from a saved state.\n\n        Parameters\n        ----------\n        state : Dict[str, Any]\n\n        NOTE\n        ----\n        This is method called while the instance (self) is being constructed,\n        even before its `__init__` is called. Consider `self` to be only a\n        `QObject` at this stage.\n        \"\"\"\n        if \"selection_state\" in state:\n            selection = state[\"selection_state\"]\n            self.__pending_selection_restore = selection\n        return True\n\n    def __zoom_in(self):\n        def clip(minval, maxval, val):\n            return min(max(val, minval), maxval)\n        self.zoom_factor = clip(self.zoom_slider.minimum(),\n                                self.zoom_slider.maximum(),\n                                self.zoom_factor + 1)\n        self.__update_font_scale()\n\n    def __zoom_out(self):\n        def clip(minval, maxval, val):\n            return min(max(val, minval), maxval)\n        self.zoom_factor = clip(self.zoom_slider.minimum(),\n                                self.zoom_slider.maximum(),\n                                self.zoom_factor - 1)\n        self.__update_font_scale()\n\n    def __zoom_reset(self):\n        self.zoom_factor = 0\n        self.__update_font_scale()\n\n    def __layout_main_graphics(self, width=-1):\n        if width < 0:\n            # Preserve current width.\n            width = self._main_graphics.size().width()\n        preferred = self._main_graphics.effectiveSizeHint(\n            Qt.PreferredSize, constraint=QSizeF(width, -1))\n        self._main_graphics.resize(QSizeF(width, preferred.height()))\n        mw = self._main_graphics.minimumWidth() + 4\n        self.view.setMinimumWidth(mw + self.view.verticalScrollBar().width())\n\n    def __update_font_scale(self):\n        font = self.scene.font()\n        factor = (1.25 ** self.zoom_factor)\n        font = qfont_scaled(font, factor)\n        self._main_graphics.setFont(font)\n\n    def send_report(self):\n        annot = self.label_cb.currentText()\n        if isinstance(self.annotation, str):\n            annot = annot.lower()\n        if self.selection_method == 0:\n            sel = \"manual\"\n        elif self.selection_method == 1:\n            sel = \"at {:.1f} of height\".format(self.cut_ratio)\n        else:\n            sel = \"top {} clusters\".format(self.top_n)\n        self.report_items((\n            (\"Linkage\", LINKAGE[self.linkage].lower()),\n            (\"Annotation\", annot),\n            (\"Prunning\",\n             self.pruning != 0 and \"{} levels\".format(self.max_depth)),\n            (\"Selection\", sel),\n        ))\n        self.report_plot()\n\n\ndef qfont_scaled(font, factor):\n    scaled = QFont(font)\n    if font.pointSizeF() != -1:\n        scaled.setPointSizeF(font.pointSizeF() * factor)\n    elif font.pixelSize() != -1:\n        scaled.setPixelSize(int(font.pixelSize() * factor))\n    return scaled\n\n\nclass AxisItem(pg.AxisItem):\n    mousePressed = Signal(QPointF, Qt.MouseButton)\n    mouseMoved = Signal(QPointF, Qt.MouseButtons)\n    mouseReleased = Signal(QPointF, Qt.MouseButton)\n\n    #: \\reimp\n    def wheelEvent(self, event):\n        event.ignore()  # ignore event to propagate to the view -> scroll\n\n    def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:\n        self.mousePressed.emit(event.pos(), event.button())\n        super().mousePressEvent(event)\n        event.accept()\n\n    def mouseMoveEvent(self, event):\n        self.mouseMoved.emit(event.pos(), event.buttons())\n        super().mouseMoveEvent(event)\n        event.accept()\n\n    def mouseReleaseEvent(self, event):\n        self.mouseReleased.emit(event.pos(), event.button())\n        super().mouseReleaseEvent(event)\n        event.accept()\n\n\nclass SliderLine(QGraphicsObject):\n    \"\"\"A movable slider line.\"\"\"\n    valueChanged = Signal(float)\n\n    linePressed = Signal()\n    lineMoved = Signal()\n    lineReleased = Signal()\n    rangeChanged = Signal(float, float)\n\n    def __init__(self, parent=None, orientation=Qt.Vertical, value=0.0,\n                 length=10.0, **kwargs):\n        self._orientation = orientation\n        self._value = value\n        self._length = length\n        self._min = 0.0\n        self._max = 1.0\n        self._line = QLineF()  # type: Optional[QLineF]\n        self._pen = QPen()\n        super().__init__(parent, **kwargs)\n\n        self.setAcceptedMouseButtons(Qt.LeftButton)\n        self.setPen(make_pen(brush=QColor(50, 50, 50), width=1, cosmetic=False,\n                             style=Qt.DashLine))\n\n        if self._orientation == Qt.Vertical:\n            self.setCursor(Qt.SizeVerCursor)\n        else:\n            self.setCursor(Qt.SizeHorCursor)\n\n    def setPen(self, pen: Union[QPen, Qt.GlobalColor, Qt.PenStyle]) -> None:\n        pen = QPen(pen)\n        if self._pen != pen:\n            self.prepareGeometryChange()\n            self._pen = pen\n            self._line = None\n            self.update()\n\n    def pen(self) -> QPen:\n        return QPen(self._pen)\n\n    def setValue(self, value: float):\n        value = min(max(value, self._min), self._max)\n\n        if self._value != value:\n            self.prepareGeometryChange()\n            self._value = value\n            self._line = None\n            self.valueChanged.emit(value)\n\n    def value(self) -> float:\n        return self._value\n\n    def setRange(self, minval: float, maxval: float) -> None:\n        maxval = max(minval, maxval)\n        if minval != self._min or maxval != self._max:\n            self._min = minval\n            self._max = maxval\n            self.rangeChanged.emit(minval, maxval)\n            self.setValue(self._value)\n\n    def setLength(self, length: float):\n        if self._length != length:\n            self.prepareGeometryChange()\n            self._length = length\n            self._line = None\n\n    def length(self) -> float:\n        return self._length\n\n    def setOrientation(self, orientation: Qt.Orientation):\n        if self._orientation != orientation:\n            self.prepareGeometryChange()\n            self._orientation = orientation\n            self._line = None\n            if self._orientation == Qt.Vertical:\n                self.setCursor(Qt.SizeVerCursor)\n            else:\n                self.setCursor(Qt.SizeHorCursor)\n\n    def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:\n        event.accept()\n        self.linePressed.emit()\n\n    def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None:\n        pos = event.pos()\n        if self._orientation == Qt.Vertical:\n            self.setValue(pos.y())\n        else:\n            self.setValue(pos.x())\n        self.lineMoved.emit()\n        event.accept()\n\n    def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None:\n        if self._orientation == Qt.Vertical:\n            self.setValue(event.pos().y())\n        else:\n            self.setValue(event.pos().x())\n        self.lineReleased.emit()\n        event.accept()\n\n    def boundingRect(self) -> QRectF:\n        if self._line is None:\n            if self._orientation == Qt.Vertical:\n                self._line = QLineF(0, self._value, self._length, self._value)\n            else:\n                self._line = QLineF(self._value, 0, self._value, self._length)\n        r = QRectF(self._line.p1(), self._line.p2())\n        penw = self.pen().width()\n        return r.adjusted(-penw, -penw, penw, penw)\n\n    def paint(self, painter, *args):\n        if self._line is None:\n            self.boundingRect()\n\n        painter.save()\n        painter.setPen(self.pen())\n        painter.drawLine(self._line)\n        painter.restore()\n\n\ndef clusters_at_height(root, height):\n    \"\"\"Return a list of clusters by cutting the clustering at `height`.\n    \"\"\"\n    lower = set()\n    cluster_list = []\n    for cl in preorder(root):\n        if cl in lower:\n            continue\n        if cl.value.height < height:\n            cluster_list.append(cl)\n            lower.update(preorder(cl))\n    return cluster_list\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    from Orange import distance\n    data = Orange.data.Table(\"iris\")\n    matrix = distance.Euclidean(distance._preprocess(data))\n    WidgetPreview(OWHierarchicalClustering).run(matrix)\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Annotated Data Missing When Using Column Distances in Hierarchical Clustering

**Description:**
When performing hierarchical clustering using column distances, the widget fails to produce the annotated data. This results in an `AttributeError` when attempting to access the domain attributes of the annotated data.

**Example:**
```python
def test_column_distances():
    widget.send_signal(Input.DISTANCES, distances_cols)
    widget.select_data_columns()
    output = widget.get_output(Output.ANNOTATED_DATA)
    annotated = [(attr.name, attr.attributes['cluster']) for attr in output.domain.attributes]
```
In this test scenario, the `output` is `None`, leading to the following error:
```
AttributeError: 'NoneType' object has no attribute 'domain'
```

**Expected Behavior:**
When using column distances for hierarchical clustering, the widget should successfully annotate the variables with their respective clusters. Accessing the `domain` attributes of the annotated data should work without errors, allowing further analysis and processing.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### Orange/widgets/unsupervised/owhierarchicalclustering.py
from itertools import chain
from contextlib import contextmanager

import typing
from typing import Any, List, Tuple, Dict, Optional, Set, Union

import numpy as np

from AnyQt.QtWidgets import (
    QGraphicsWidget, QGraphicsObject, QGraphicsScene, QGridLayout, QSizePolicy,
    QAction, QComboBox, QGraphicsGridLayout, QGraphicsSceneMouseEvent
)
from AnyQt.QtGui import QColor, QPen, QFont, QKeySequence
from AnyQt.QtCore import Qt, QSize, QSizeF, QPointF, QRectF, QLineF, QEvent
from AnyQt.QtCore import pyqtSignal as Signal, pyqtSlot as Slot

import pyqtgraph as pg

import Orange.data
from Orange.data.domain import filter_visible
from Orange.data import Domain
import Orange.misc
from Orange.clustering.hierarchical import \
    postorder, preorder, Tree, tree_from_linkage, dist_matrix_linkage, \
    leaves, prune, top_clusters
from Orange.data.util import get_unique_names

from Orange.widgets import widget, gui, settings
from Orange.widgets.utils import itemmodels, combobox
from Orange.widgets.utils.annotated_data import (create_annotated_table,
                                                 ANNOTATED_DATA_SIGNAL_NAME)
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.widget import Input, Output, Msg

from Orange.widgets.utils.stickygraphicsview import StickyGraphicsView
from Orange.widgets.utils.graphicstextlist import TextListWidget
from Orange.widgets.utils.dendrogram import DendrogramWidget

__all__ = ["OWHierarchicalClustering"]


LINKAGE = ["Single", "Average", "Weighted", "Complete", "Ward"]


def make_pen(brush=Qt.black, width=1, style=Qt.SolidLine,
             cap_style=Qt.SquareCap, join_style=Qt.BevelJoin,
             cosmetic=False):
    pen = QPen(brush)
    pen.setWidth(width)
    pen.setStyle(style)
    pen.setCapStyle(cap_style)
    pen.setJoinStyle(join_style)
    pen.setCosmetic(cosmetic)
    return pen


@contextmanager
def blocked(obj):
    old = obj.signalsBlocked()
    obj.blockSignals(True)
    try:
        yield obj
    finally:
        obj.blockSignals(old)


class SaveStateSettingsHandler(settings.SettingsHandler):
    """
    A settings handler that delegates session data store/restore to the
    OWWidget instance.

    The OWWidget subclass must implement `save_state() -> Dict[str, Any]` and
    `set_restore_state(state: Dict[str, Any])` methods.
    """
    def initialize(self, instance, data=None):
        super().initialize(instance, data)
        if data is not None and "__session_state_data" in data:
            session_data = data["__session_state_data"]
            instance.set_restore_state(session_data)

    def pack_data(self, widget):
        # type: (widget.OWWidget) -> dict
        res = super().pack_data(widget)
        state = widget.save_state()
        if state:
            assert "__session_state_data" not in res
            res["__session_state_data"] = state
        return res


class _DomainContextHandler(settings.DomainContextHandler,
                            SaveStateSettingsHandler):
    pass


if typing.TYPE_CHECKING:
    #: Encoded selection state for persistent storage.
    #: This is a list of tuples of leaf indices in the selection and
    #: a (N, 3) linkage matrix for validation (the 4-th column from scipy
    #: is omitted).
    SelectionState = Tuple[List[Tuple[int]], List[Tuple[int, int, float]]]


class OWHierarchicalClustering(widget.OWWidget):
    name = "Hierarchical Clustering"
    description = "Display a dendrogram of a hierarchical clustering " \
                  "constructed from the input distance matrix."
    icon = "icons/HierarchicalClustering.svg"
    priority = 2100
    keywords = []

    class Inputs:
        distances = Input("Distances", Orange.misc.DistMatrix)

    class Outputs:
        selected_data = Output("Selected Data", Orange.data.Table, default=True)
        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table)

    settingsHandler = _DomainContextHandler()

    #: Selected linkage
    linkage = settings.Setting(1)
    #: Index of the selected annotation item (variable, ...)
    annotation = settings.ContextSetting("Enumeration")
    #: Out-of-context setting for the case when the "Name" option is available
    annotation_if_names = settings.Setting("Name")
    #: Out-of-context setting for the case with just "Enumerate" and "None"
    annotation_if_enumerate = settings.Setting("Enumerate")
    #: Selected tree pruning (none/max depth)
    pruning = settings.Setting(0)
    #: Maximum depth when max depth pruning is selected
    max_depth = settings.Setting(10)

    #: Selected cluster selection method (none, cut distance, top n)
    selection_method = settings.Setting(0)
    #: Cut height ratio wrt root height
    cut_ratio = settings.Setting(75.0)
    #: Number of top clusters to select
    top_n = settings.Setting(3)
    #: Dendrogram zoom factor
    zoom_factor = settings.Setting(0)

    autocommit = settings.Setting(True)

    graph_name = "scene"

    basic_annotations = ["None", "Enumeration"]

    class Error(widget.OWWidget.Error):
        not_finite_distances = Msg("Some distances are infinite")

    #: Stored (manual) selection state (from a saved workflow) to restore.
    __pending_selection_restore = None  # type: Optional[SelectionState]

    def __init__(self):
        super().__init__()

        self.matrix = None
        self.items = None
        self.linkmatrix = None
        self.root = None
        self._displayed_root = None
        self.cutoff_height = 0.0

        gui.comboBox(
            self.controlArea, self, "linkage", items=LINKAGE, box="Linkage",
            callback=self._invalidate_clustering)

        model = itemmodels.VariableListModel()
        model[:] = self.basic_annotations

        box = gui.widgetBox(self.controlArea, "Annotations")
        self.label_cb = cb = combobox.ComboBoxSearch(
            minimumContentsLength=14,
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon
        )
        cb.setModel(model)
        cb.setCurrentIndex(cb.findData(self.annotation, Qt.EditRole))

        def on_annotation_activated():
            self.annotation = cb.currentData(Qt.EditRole)
            self._update_labels()
        cb.activated.connect(on_annotation_activated)

        def on_annotation_changed(value):
            cb.setCurrentIndex(cb.findData(value, Qt.EditRole))
        self.connect_control("annotation", on_annotation_changed)

        box.layout().addWidget(self.label_cb)

        box = gui.radioButtons(
            self.controlArea, self, "pruning", box="Pruning",
            callback=self._invalidate_pruning)
        grid = QGridLayout()
        box.layout().addLayout(grid)
        grid.addWidget(
            gui.appendRadioButton(box, "None", addToLayout=False),
            0, 0
        )
        self.max_depth_spin = gui.spin(
            box, self, "max_depth", minv=1, maxv=100,
            callback=self._invalidate_pruning,
            keyboardTracking=False, addToLayout=False
        )

        grid.addWidget(
            gui.appendRadioButton(box, "Max depth:", addToLayout=False),
            1, 0)
        grid.addWidget(self.max_depth_spin, 1, 1)

        self.selection_box = gui.radioButtons(
            self.controlArea, self, "selection_method",
            box="Selection",
            callback=self._selection_method_changed)

        grid = QGridLayout()
        self.selection_box.layout().addLayout(grid)
        grid.addWidget(
            gui.appendRadioButton(
                self.selection_box, "Manual", addToLayout=False),
            0, 0
        )
        grid.addWidget(
            gui.appendRadioButton(
                self.selection_box, "Height ratio:", addToLayout=False),
            1, 0
        )
        self.cut_ratio_spin = gui.spin(
            self.selection_box, self, "cut_ratio", 0, 100, step=1e-1,
            spinType=float, callback=self._selection_method_changed,
            addToLayout=False
        )
        self.cut_ratio_spin.setSuffix("%")

        grid.addWidget(self.cut_ratio_spin, 1, 1)

        grid.addWidget(
            gui.appendRadioButton(
                self.selection_box, "Top N:", addToLayout=False),
            2, 0
        )
        self.top_n_spin = gui.spin(self.selection_box, self, "top_n", 1, 20,
                                   callback=self._selection_method_changed,
                                   addToLayout=False)
        grid.addWidget(self.top_n_spin, 2, 1)

        self.zoom_slider = gui.hSlider(
            self.controlArea, self, "zoom_factor", box="Zoom",
            minValue=-6, maxValue=3, step=1, ticks=True, createLabel=False,
            callback=self.__update_font_scale)

        zoom_in = QAction(
            "Zoom in", self, shortcut=QKeySequence.ZoomIn,
            triggered=self.__zoom_in
        )
        zoom_out = QAction(
            "Zoom out", self, shortcut=QKeySequence.ZoomOut,
            triggered=self.__zoom_out
        )
        zoom_reset = QAction(
            "Reset zoom", self,
            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0),
            triggered=self.__zoom_reset
        )
        self.addActions([zoom_in, zoom_out, zoom_reset])

        self.controlArea.layout().addStretch()

        gui.auto_send(self.buttonsArea, self, "autocommit")

        self.scene = QGraphicsScene(self)
        self.view = StickyGraphicsView(
            self.scene,
            horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff,
            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn,
            alignment=Qt.AlignLeft | Qt.AlignVCenter
        )
        self.mainArea.layout().setSpacing(1)
        self.mainArea.layout().addWidget(self.view)

        def axis_view(orientation):
            ax = AxisItem(orientation=orientation, maxTickLength=7)
            ax.mousePressed.connect(self._activate_cut_line)
            ax.mouseMoved.connect(self._activate_cut_line)
            ax.mouseReleased.connect(self._activate_cut_line)
            ax.setRange(1.0, 0.0)
            return ax

        self.top_axis = axis_view("top")
        self.bottom_axis = axis_view("bottom")

        self._main_graphics = QGraphicsWidget()
        scenelayout = QGraphicsGridLayout()
        scenelayout.setHorizontalSpacing(10)
        scenelayout.setVerticalSpacing(10)

        self._main_graphics.setLayout(scenelayout)
        self.scene.addItem(self._main_graphics)

        self.dendrogram = DendrogramWidget()
        self.dendrogram.setSizePolicy(QSizePolicy.MinimumExpanding,
                                      QSizePolicy.MinimumExpanding)
        self.dendrogram.selectionChanged.connect(self._invalidate_output)
        self.dendrogram.selectionEdited.connect(self._selection_edited)

        self.labels = TextListWidget()
        self.labels.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
        self.labels.setAlignment(Qt.AlignLeft)
        self.labels.setMaximumWidth(200)

        scenelayout.addItem(self.top_axis, 0, 0,
                            alignment=Qt.AlignLeft | Qt.AlignVCenter)
        scenelayout.addItem(self.dendrogram, 1, 0,
                            alignment=Qt.AlignLeft | Qt.AlignVCenter)
        scenelayout.addItem(self.labels, 1, 1,
                            alignment=Qt.AlignLeft | Qt.AlignVCenter)
        scenelayout.addItem(self.bottom_axis, 2, 0,
                            alignment=Qt.AlignLeft | Qt.AlignVCenter)
        self.view.viewport().installEventFilter(self)
        self._main_graphics.installEventFilter(self)

        self.top_axis.setZValue(self.dendrogram.zValue() + 10)
        self.bottom_axis.setZValue(self.dendrogram.zValue() + 10)
        self.cut_line = SliderLine(self.top_axis,
                                   orientation=Qt.Horizontal)
        self.cut_line.valueChanged.connect(self._dendrogram_slider_changed)
        self.dendrogram.geometryChanged.connect(self._dendrogram_geom_changed)
        self._set_cut_line_visible(self.selection_method == 1)
        self.__update_font_scale()

    @Inputs.distances
    def set_distances(self, matrix):
        if self.__pending_selection_restore is not None:
            selection_state = self.__pending_selection_restore
        else:
            # save the current selection to (possibly) restore later
            selection_state = self._save_selection()

        self.error()
        self.Error.clear()
        if matrix is not None:
            N, _ = matrix.shape
            if N < 2:
                self.error("Empty distance matrix")
                matrix = None
        if matrix is not None:
            if not np.all(np.isfinite(matrix)):
                self.Error.not_finite_distances()
                matrix = None

        self.matrix = matrix
        if matrix is not None:
            self._set_items(matrix.row_items, matrix.axis)
        else:
            self._set_items(None)
        self._invalidate_clustering()

        # Can now attempt to restore session state from a saved workflow.
        if self.root and selection_state is not None:
            self._restore_selection(selection_state)
            self.__pending_selection_restore = None

        self.unconditional_commit()

    def _set_items(self, items, axis=1):
        self.closeContext()
        self.items = items
        model = self.label_cb.model()
        if len(model) == 3:
            self.annotation_if_names = self.annotation
        elif len(model) == 2:
            self.annotation_if_enumerate = self.annotation
        if isinstance(items, Orange.data.Table) and axis:
            model[:] = chain(
                self.basic_annotations,
                [model.Separator],
                items.domain.class_vars,
                items.domain.metas,
                [model.Separator] if (items.domain.class_vars or items.domain.metas) and
                next(filter_visible(items.domain.attributes), False) else [],
                filter_visible(items.domain.attributes)
            )
            if items.domain.class_vars:
                self.annotation = items.domain.class_vars[0]
            else:
                self.annotation = "Enumeration"
            self.openContext(items.domain)
        else:
            name_option = bool(
                items is not None and (
                    not axis or
                    isinstance(items, list) and
                    all(isinstance(var, Orange.data.Variable) for var in items)))
            model[:] = self.basic_annotations + ["Name"] * name_option
            self.annotation = self.annotation_if_names if name_option \
                else self.annotation_if_enumerate

    def _clear_plot(self):
        self.dendrogram.set_root(None)
        self.labels.setItems([])

    def _set_displayed_root(self, root):
        self._clear_plot()
        self._displayed_root = root
        self.dendrogram.set_root(root)

        self._update_labels()

        self._main_graphics.resize(
            self._main_graphics.size().width(),
            self._main_graphics.sizeHint(Qt.PreferredSize).height()
        )
        self._main_graphics.layout().activate()

    def _update(self):
        self._clear_plot()

        distances = self.matrix

        if distances is not None:
            method = LINKAGE[self.linkage].lower()
            Z = dist_matrix_linkage(distances, linkage=method)

            tree = tree_from_linkage(Z)
            self.linkmatrix = Z
            self.root = tree

            self.top_axis.setRange(tree.value.height, 0.0)
            self.bottom_axis.setRange(tree.value.height, 0.0)

            if self.pruning:
                self._set_displayed_root(prune(tree, level=self.max_depth))
            else:
                self._set_displayed_root(tree)
        else:
            self.linkmatrix = None
            self.root = None
            self._set_displayed_root(None)

        self._apply_selection()

    def _update_labels(self):
        labels = []
        if self.root and self._displayed_root:
            indices = [leaf.value.index for leaf in leaves(self.root)]

            if self.annotation == "None":
                labels = []
            elif self.annotation == "Enumeration":
                labels = [str(i+1) for i in indices]
            elif self.annotation == "Name":
                attr = self.matrix.row_items.domain.attributes
                labels = [str(attr[i]) for i in indices]
            elif isinstance(self.annotation, Orange.data.Variable):
                col_data, _ = self.items.get_column_view(self.annotation)
                labels = [self.annotation.str_val(val) for val in col_data]
                labels = [labels[idx] for idx in indices]
            else:
                labels = []

            if labels and self._displayed_root is not self.root:
                joined = leaves(self._displayed_root)
                labels = [", ".join(labels[leaf.value.first: leaf.value.last])
                          for leaf in joined]

        self.labels.setItems(labels)
        self.labels.setMinimumWidth(1 if labels else -1)

    def _restore_selection(self, state):
        # type: (SelectionState) -> bool
        """
        Restore the (manual) node selection state.

        Return True if successful; False otherwise.
        """
        linkmatrix = self.linkmatrix
        if self.selection_method == 0 and self.root:
            selected, linksaved = state
            linkstruct = np.array(linksaved, dtype=float)
            selected = set(selected)  # type: Set[Tuple[int]]
            if not selected:
                return False
            if linkmatrix.shape[0] != linkstruct.shape[0]:
                return False
            # check that the linkage matrix structure matches. Use isclose for
            # the height column to account for inexact floating point math
            # (e.g. summation order in different ?gemm implementations for
            # euclidean distances, ...)
            if np.any(linkstruct[:, :2] != linkmatrix[:, :2]) or \
                    not np.all(np.isclose(linkstruct[:, 2], linkstruct[:, 2])):
                return False
            selection = []
            indices = np.array([n.value.index for n in leaves(self.root)],
                               dtype=int)
            # mapping from ranges to display (pruned) nodes
            mapping = {node.value.range: node
                       for node in postorder(self._displayed_root)}
            for node in postorder(self.root):  # type: Tree
                r = tuple(indices[node.value.first: node.value.last])
                if r in selected:
                    if node.value.range not in mapping:
                        # the node was pruned from display and cannot be
                        # selected
                        break
                    selection.append(mapping[node.value.range])
                    selected.remove(r)
                if not selected:
                    break  # found all, nothing more to do
            if selection and selected:
                # Could not restore all selected nodes (only partial match)
                return False

            self._set_selected_nodes(selection)
            return True
        return False

    def _set_selected_nodes(self, selection):
        # type: (List[Tree]) -> None
        """
        Set the nodes in `selection` to be the current selected nodes.

        The selection nodes must be subtrees of the current `_displayed_root`.
        """
        self.dendrogram.selectionChanged.disconnect(self._invalidate_output)
        try:
            self.dendrogram.set_selected_clusters(selection)
        finally:
            self.dendrogram.selectionChanged.connect(self._invalidate_output)

    def _invalidate_clustering(self):
        self._update()
        self._update_labels()
        self._invalidate_output()

    def _invalidate_output(self):
        self.commit()

    def _invalidate_pruning(self):
        if self.root:
            selection = self.dendrogram.selected_nodes()
            ranges = [node.value.range for node in selection]
            if self.pruning:
                self._set_displayed_root(
                    prune(self.root, level=self.max_depth))
            else:
                self._set_displayed_root(self.root)
            selected = [node for node in preorder(self._displayed_root)
                        if node.value.range in ranges]

            self.dendrogram.set_selected_clusters(selected)

        self._apply_selection()

    def commit(self):
        items = getattr(self.matrix, "items", self.items)
        if not items:
            self.Outputs.selected_data.send(None)
            self.Outputs.annotated_data.send(None)
            return

        selection = self.dendrogram.selected_nodes()
        selection = sorted(selection, key=lambda c: c.value.first)

        indices = [leaf.value.index for leaf in leaves(self.root)]

        maps = [indices[node.value.first:node.value.last]
                for node in selection]

        selected_indices = list(chain(*maps))
        unselected_indices = sorted(set(range(self.root.value.last)) -
                                    set(selected_indices))

        if not selected_indices:
            self.Outputs.selected_data.send(None)
            annotated_data = create_annotated_table(items, []) \
                if self.selection_method == 0 and self.matrix.axis else None
            self.Outputs.annotated_data.send(annotated_data)
            return

        selected_data = None

        if isinstance(items, Orange.data.Table) and self.matrix.axis == 1:
            # Select rows
            c = np.zeros(self.matrix.shape[0])

            for i, indices in enumerate(maps):
                c[indices] = i
            c[unselected_indices] = len(maps)

            mask = c != len(maps)

            data, domain = items, items.domain
            attrs = domain.attributes
            classes = domain.class_vars
            metas = domain.metas

            var_name = get_unique_names(domain, "Cluster")
            values = [f"C{i + 1}" for i in range(len(maps))]

            clust_var = Orange.data.DiscreteVariable(
                var_name, values=values + ["Other"])
            domain = Orange.data.Domain(attrs, classes, metas + (clust_var,))
            data = items.transform(domain)
            data.get_column_view(clust_var)[0][:] = c

            if selected_indices:
                selected_data = data[mask]
                clust_var = Orange.data.DiscreteVariable(
                    var_name, values=values)
                selected_data.domain = Domain(
                    attrs, classes, metas + (clust_var, ))

        elif isinstance(items, Orange.data.Table) and self.matrix.axis == 0:
            # Select columns
            domain = Orange.data.Domain(
                [items.domain[i] for i in selected_indices],
                items.domain.class_vars, items.domain.metas)
            selected_data = items.from_table(domain, items)
            data = None

        self.Outputs.selected_data.send(selected_data)
        annotated_data = create_annotated_table(data, selected_indices)
        self.Outputs.annotated_data.send(annotated_data)

    def eventFilter(self, obj, event):
        if obj is self.view.viewport() and event.type() == QEvent.Resize:
            # NOTE: not using viewport.width(), due to 'transient' scroll bars
            # (macOS). Viewport covers the whole view, but QGraphicsView still
            # scrolls left, right with scroll bar extent (other
            # QAbstractScrollArea widgets behave as expected).
            w_frame = self.view.frameWidth()
            margin = self.view.viewportMargins()
            w_scroll = self.view.verticalScrollBar().width()
            width = (self.view.width() - w_frame * 2 -
                     margin.left() - margin.right() - w_scroll)
            # layout with new width constraint
            self.__layout_main_graphics(width=width)
        elif obj is self._main_graphics and \
                event.type() == QEvent.LayoutRequest:
            # layout preserving the width (vertical re layout)
            self.__layout_main_graphics()
        return super().eventFilter(obj, event)

    @Slot(QPointF)
    def _activate_cut_line(self, pos: QPointF):
        """Activate cut line selection an set cut value to `pos.x()`."""
        self.selection_method = 1
        self.cut_line.setValue(pos.x())
        self._selection_method_changed()

    def onDeleteWidget(self):
        super().onDeleteWidget()
        self._clear_plot()
        self.dendrogram.clear()
        self.dendrogram.deleteLater()

    def _dendrogram_geom_changed(self):
        pos = self.dendrogram.pos_at_height(self.cutoff_height)
        geom = self.dendrogram.geometry()
        self._set_slider_value(pos.x(), geom.width())

        self.cut_line.setLength(
            self.bottom_axis.geometry().bottom()
            - self.top_axis.geometry().top()
        )

        geom = self._main_graphics.geometry()
        assert geom.topLeft() == QPointF(0, 0)

        def adjustLeft(rect):
            rect = QRectF(rect)
            rect.setLeft(geom.left())
            return rect
        margin = 3
        self.scene.setSceneRect(geom)
        self.view.setSceneRect(geom)
        self.view.setHeaderSceneRect(
            adjustLeft(self.top_axis.geometry()).adjusted(0, 0, 0, margin)
        )
        self.view.setFooterSceneRect(
            adjustLeft(self.bottom_axis.geometry()).adjusted(0, -margin, 0, 0)
        )

    def _dendrogram_slider_changed(self, value):
        p = QPointF(value, 0)
        cl_height = self.dendrogram.height_at(p)

        self.set_cutoff_height(cl_height)

    def _set_slider_value(self, value, span):
        with blocked(self.cut_line):
            self.cut_line.setRange(0, span)
            self.cut_line.setValue(value)

    def set_cutoff_height(self, height):
        self.cutoff_height = height
        if self.root:
            self.cut_ratio = 100 * height / self.root.value.height
        self.select_max_height(height)

    def _set_cut_line_visible(self, visible):
        self.cut_line.setVisible(visible)

    def select_top_n(self, n):
        root = self._displayed_root
        if root:
            clusters = top_clusters(root, n)
            self.dendrogram.set_selected_clusters(clusters)

    def select_max_height(self, height):
        root = self._displayed_root
        if root:
            clusters = clusters_at_height(root, height)
            self.dendrogram.set_selected_clusters(clusters)

    def _selection_method_changed(self):
        self._set_cut_line_visible(self.selection_method == 1)
        if self.root:
            self._apply_selection()

    def _apply_selection(self):
        if not self.root:
            return

        if self.selection_method == 0:
            pass
        elif self.selection_method == 1:
            height = self.cut_ratio * self.root.value.height / 100
            self.set_cutoff_height(height)
            pos = self.dendrogram.pos_at_height(height)
            self._set_slider_value(pos.x(), self.dendrogram.size().width())
        elif self.selection_method == 2:
            self.select_top_n(self.top_n)

    def _selection_edited(self):
        # Selection was edited by clicking on a cluster in the
        # dendrogram view.
        self.selection_method = 0
        self._selection_method_changed()
        self._invalidate_output()

    def _save_selection(self):
        # Save the current manual node selection state
        selection_state = None
        if self.selection_method == 0 and self.root:
            assert self.linkmatrix is not None
            linkmat = [(int(_0), int(_1), _2)
                       for _0, _1, _2 in self.linkmatrix[:, :3].tolist()]
            nodes_ = self.dendrogram.selected_nodes()
            # match the display (pruned) nodes back (by ranges)
            mapping = {node.value.range: node for node in postorder(self.root)}
            nodes = [mapping[node.value.range] for node in nodes_]
            indices = [tuple(node.value.index for node in leaves(node))
                       for node in nodes]
            if nodes:
                selection_state = (indices, linkmat)
        return selection_state

    def save_state(self):
        # type: () -> Dict[str, Any]
        """
        Save state for `set_restore_state`
        """
        selection = self._save_selection()
        res = {"version": (0, 0, 0)}
        if selection is not None:
            res["selection_state"] = selection
        return res

    def set_restore_state(self, state):
        # type: (Dict[str, Any]) -> bool
        """
        Restore session data from a saved state.

        Parameters
        ----------
        state : Dict[str, Any]

        NOTE
        ----
        This is method called while the instance (self) is being constructed,
        even before its `__init__` is called. Consider `self` to be only a
        `QObject` at this stage.
        """
        if "selection_state" in state:
            selection = state["selection_state"]
            self.__pending_selection_restore = selection
        return True

    def __zoom_in(self):
        def clip(minval, maxval, val):
            return min(max(val, minval), maxval)
        self.zoom_factor = clip(self.zoom_slider.minimum(),
                                self.zoom_slider.maximum(),
                                self.zoom_factor + 1)
        self.__update_font_scale()

    def __zoom_out(self):
        def clip(minval, maxval, val):
            return min(max(val, minval), maxval)
        self.zoom_factor = clip(self.zoom_slider.minimum(),
                                self.zoom_slider.maximum(),
                                self.zoom_factor - 1)
        self.__update_font_scale()

    def __zoom_reset(self):
        self.zoom_factor = 0
        self.__update_font_scale()

    def __layout_main_graphics(self, width=-1):
        if width < 0:
            # Preserve current width.
            width = self._main_graphics.size().width()
        preferred = self._main_graphics.effectiveSizeHint(
            Qt.PreferredSize, constraint=QSizeF(width, -1))
        self._main_graphics.resize(QSizeF(width, preferred.height()))
        mw = self._main_graphics.minimumWidth() + 4
        self.view.setMinimumWidth(mw + self.view.verticalScrollBar().width())

    def __update_font_scale(self):
        font = self.scene.font()
        factor = (1.25 ** self.zoom_factor)
        font = qfont_scaled(font, factor)
        self._main_graphics.setFont(font)

    def send_report(self):
        annot = self.label_cb.currentText()
        if isinstance(self.annotation, str):
            annot = annot.lower()
        if self.selection_method == 0:
            sel = "manual"
        elif self.selection_method == 1:
            sel = "at {:.1f} of height".format(self.cut_ratio)
        else:
            sel = "top {} clusters".format(self.top_n)
        self.report_items((
            ("Linkage", LINKAGE[self.linkage].lower()),
            ("Annotation", annot),
            ("Prunning",
             self.pruning != 0 and "{} levels".format(self.max_depth)),
            ("Selection", sel),
        ))
        self.report_plot()


def qfont_scaled(font, factor):
    scaled = QFont(font)
    if font.pointSizeF() != -1:
        scaled.setPointSizeF(font.pointSizeF() * factor)
    elif font.pixelSize() != -1:
        scaled.setPixelSize(int(font.pixelSize() * factor))
    return scaled


class AxisItem(pg.AxisItem):
    mousePressed = Signal(QPointF, Qt.MouseButton)
    mouseMoved = Signal(QPointF, Qt.MouseButtons)
    mouseReleased = Signal(QPointF, Qt.MouseButton)

    #: \reimp
    def wheelEvent(self, event):
        event.ignore()  # ignore event to propagate to the view -> scroll

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        self.mousePressed.emit(event.pos(), event.button())
        super().mousePressEvent(event)
        event.accept()

    def mouseMoveEvent(self, event):
        self.mouseMoved.emit(event.pos(), event.buttons())
        super().mouseMoveEvent(event)
        event.accept()

    def mouseReleaseEvent(self, event):
        self.mouseReleased.emit(event.pos(), event.button())
        super().mouseReleaseEvent(event)
        event.accept()


class SliderLine(QGraphicsObject):
    """A movable slider line."""
    valueChanged = Signal(float)

    linePressed = Signal()
    lineMoved = Signal()
    lineReleased = Signal()
    rangeChanged = Signal(float, float)

    def __init__(self, parent=None, orientation=Qt.Vertical, value=0.0,
                 length=10.0, **kwargs):
        self._orientation = orientation
        self._value = value
        self._length = length
        self._min = 0.0
        self._max = 1.0
        self._line = QLineF()  # type: Optional[QLineF]
        self._pen = QPen()
        super().__init__(parent, **kwargs)

        self.setAcceptedMouseButtons(Qt.LeftButton)
        self.setPen(make_pen(brush=QColor(50, 50, 50), width=1, cosmetic=False,
                             style=Qt.DashLine))

        if self._orientation == Qt.Vertical:
            self.setCursor(Qt.SizeVerCursor)
        else:
            self.setCursor(Qt.SizeHorCursor)

    def setPen(self, pen: Union[QPen, Qt.GlobalColor, Qt.PenStyle]) -> None:
        pen = QPen(pen)
        if self._pen != pen:
            self.prepareGeometryChange()
            self._pen = pen
            self._line = None
            self.update()

    def pen(self) -> QPen:
        return QPen(self._pen)

    def setValue(self, value: float):
        value = min(max(value, self._min), self._max)

        if self._value != value:
            self.prepareGeometryChange()
            self._value = value
            self._line = None
            self.valueChanged.emit(value)

    def value(self) -> float:
        return self._value

    def setRange(self, minval: float, maxval: float) -> None:
        maxval = max(minval, maxval)
        if minval != self._min or maxval != self._max:
            self._min = minval
            self._max = maxval
            self.rangeChanged.emit(minval, maxval)
            self.setValue(self._value)

    def setLength(self, length: float):
        if self._length != length:
            self.prepareGeometryChange()
            self._length = length
            self._line = None

    def length(self) -> float:
        return self._length

    def setOrientation(self, orientation: Qt.Orientation):
        if self._orientation != orientation:
            self.prepareGeometryChange()
            self._orientation = orientation
            self._line = None
            if self._orientation == Qt.Vertical:
                self.setCursor(Qt.SizeVerCursor)
            else:
                self.setCursor(Qt.SizeHorCursor)

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        event.accept()
        self.linePressed.emit()

    def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        pos = event.pos()
        if self._orientation == Qt.Vertical:
            self.setValue(pos.y())
        else:
            self.setValue(pos.x())
        self.lineMoved.emit()
        event.accept()

    def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        if self._orientation == Qt.Vertical:
            self.setValue(event.pos().y())
        else:
            self.setValue(event.pos().x())
        self.lineReleased.emit()
        event.accept()

    def boundingRect(self) -> QRectF:
        if self._line is None:
            if self._orientation == Qt.Vertical:
                self._line = QLineF(0, self._value, self._length, self._value)
            else:
                self._line = QLineF(self._value, 0, self._value, self._length)
        r = QRectF(self._line.p1(), self._line.p2())
        penw = self.pen().width()
        return r.adjusted(-penw, -penw, penw, penw)

    def paint(self, painter, *args):
        if self._line is None:
            self.boundingRect()

        painter.save()
        painter.setPen(self.pen())
        painter.drawLine(self._line)
        painter.restore()


def clusters_at_height(root, height):
    """Return a list of clusters by cutting the clustering at `height`.
    """
    lower = set()
    cluster_list = []
    for cl in preorder(root):
        if cl in lower:
            continue
        if cl.value.height < height:
            cluster_list.append(cl)
            lower.update(preorder(cl))
    return cluster_list


if __name__ == "__main__":  # pragma: no cover
    from Orange import distance
    data = Orange.data.Table("iris")
    matrix = distance.Euclidean(distance._preprocess(data))
    WidgetPreview(OWHierarchicalClustering).run(matrix)

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user