{
    "file_diffs": [
        {
            "old_file_content": "\"\"\"\nVenn Diagram Widget\n-------------------\n\n\"\"\"\n\nimport math\nimport unicodedata\nfrom collections import namedtuple, defaultdict, OrderedDict, Counter\nfrom itertools import count\nfrom functools import reduce\nfrom xml.sax.saxutils import escape\n\nimport numpy\n\nfrom AnyQt.QtWidgets import (\n    QComboBox, QGraphicsScene, QGraphicsView, QGraphicsWidget,\n    QGraphicsPathItem, QGraphicsTextItem, QStyle\n)\nfrom AnyQt.QtGui import (\n    QPainterPath, QPainter, QTransform, QColor, QBrush, QPen, QPalette\n)\nfrom AnyQt.QtCore import Qt, QPointF, QRectF, QLineF\nfrom AnyQt.QtCore import pyqtSignal as Signal\n\nimport Orange.data\nfrom Orange.statistics import util\nfrom Orange.widgets import widget, gui, settings\nfrom Orange.widgets.utils import itemmodels, colorpalette\nfrom Orange.widgets.utils.annotated_data import (create_annotated_table,\n                                                 ANNOTATED_DATA_SIGNAL_NAME)\nfrom Orange.widgets.utils.sql import check_sql_input\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.widget import Input, Output\n\n\n_InputData = namedtuple(\"_InputData\", [\"key\", \"name\", \"table\"])\n_ItemSet = namedtuple(\"_ItemSet\", [\"key\", \"name\", \"title\", \"items\"])\n\n\nclass OWVennDiagram(widget.OWWidget):\n    name = \"Venn Diagram\"\n    description = \"A graphical visualization of the overlap of data instances \" \\\n                  \"from a collection of input datasets.\"\n    icon = \"icons/VennDiagram.svg\"\n    priority = 280\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table, multiple=True)\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    inputhints: dict\n    selection: list\n\n    # Selected disjoint subset indices\n    selection = settings.Setting([])\n    #: Stored input set hints\n    #: {(index, inputname, attributes): (selectedattrname, itemsettitle)}\n    #: The 'selectedattrname' can be None\n    inputhints = settings.Setting({})\n    #: Use identifier columns for instance matching\n    useidentifiers = settings.Setting(True)\n    #: Output unique items (one output row for every unique instance `key`)\n    #: or preserve all duplicates in the output.\n    output_duplicates = settings.Setting(False)\n    autocommit = settings.Setting(True)\n\n    graph_name = \"scene\"\n\n    def __init__(self):\n        super().__init__()\n\n        # Diagram update is in progress\n        self._updating = False\n        # Input update is in progress\n        self._inputUpdate = False\n        # All input tables have the same domain.\n        self.samedomain = True\n        # Input datasets in the order they were 'connected'.\n        self.data = OrderedDict()\n        # Extracted input item sets in the order they were 'connected'\n        self.itemsets = OrderedDict()\n\n        # GUI\n        box = gui.vBox(self.controlArea, \"Info\")\n        self.infolabel = gui.widgetLabel(box, \"No data on input.\\n\")\n\n        self.identifiersBox = gui.radioButtonsInBox(\n            self.controlArea, self, \"useidentifiers\", [],\n            box=\"Data Instance Identifiers\",\n            callback=self._on_useidentifiersChanged\n        )\n        self.useequalityButton = gui.appendRadioButton(\n            self.identifiersBox, \"Use instance equality\"\n        )\n        self.useidentifiersButton = rb = gui.appendRadioButton(\n            self.identifiersBox, \"Use identifiers\"\n        )\n        self.inputsBox = gui.indentedBox(\n            self.identifiersBox, sep=gui.checkButtonOffsetHint(rb)\n        )\n        self.inputsBox.setEnabled(bool(self.useidentifiers))\n\n        for i in range(5):\n            box = gui.vBox(self.inputsBox, \"Dataset #%i\" % (i + 1),\n                           addSpace=False)\n            box.setFlat(True)\n            model = itemmodels.VariableListModel(parent=self)\n            cb = gui.OrangeComboBox(\n                minimumContentsLength=12,\n                sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon\n            )\n            cb.setModel(model)\n            cb.activated[int].connect(self._on_inputAttrActivated)\n            box.setEnabled(False)\n            # Store the combo in the box for later use.\n            box.combo_box = cb\n            box.layout().addWidget(cb)\n\n        gui.rubber(self.controlArea)\n\n        box = gui.vBox(self.controlArea, \"Output\")\n        gui.checkBox(box, self, \"output_duplicates\", \"Output duplicates\",\n                     callback=lambda: self.commit())\n        gui.auto_commit(box, self, \"autocommit\", \"Send Selection\", \"Send Automatically\", box=False)\n\n        # Main area view\n        self.scene = QGraphicsScene()\n        self.view = QGraphicsView(self.scene)\n        self.view.setRenderHint(QPainter.Antialiasing)\n        self.view.setBackgroundRole(QPalette.Window)\n        self.view.setFrameStyle(QGraphicsView.StyledPanel)\n\n        self.mainArea.layout().addWidget(self.view)\n        self.vennwidget = VennDiagram()\n        self.vennwidget.resize(400, 400)\n        self.vennwidget.itemTextEdited.connect(self._on_itemTextEdited)\n        self.scene.selectionChanged.connect(self._on_selectionChanged)\n\n        self.scene.addItem(self.vennwidget)\n\n        self.resize(self.controlArea.sizeHint().width() + 550,\n                    max(self.controlArea.sizeHint().height(), 550))\n\n        self._queue = []\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data, key=None):\n        self.error()\n        if not self._inputUpdate:\n            # Store hints only on the first setData call.\n            self._storeHints()\n            self._inputUpdate = True\n\n        if key in self.data:\n            if data is None:\n                # Remove the input\n                self._remove(key)\n            else:\n                # Update existing item\n                self._update(key, data)\n        elif data is not None:\n            # TODO: Allow setting more them 5 inputs and let the user\n            # select the 5 to display.\n            if len(self.data) == 5:\n                self.error(\"Venn diagram accepts at most five datasets.\")\n                return\n            # Add a new input\n            self._add(key, data)\n\n    def handleNewSignals(self):\n        self._inputUpdate = False\n\n        # Check if all inputs are from the same domain.\n        domains = [input.table.domain for input in self.data.values()]\n        samedomain = all(domain_eq(d1, d2) for d1, d2 in pairwise(domains))\n\n        self.samedomain = samedomain\n\n        has_identifiers = all(source_attributes(input.table.domain)\n                              for input in self.data.values())\n        has_any_identifiers = any(source_attributes(input.table.domain)\n                                  for input in self.data.values())\n        self.useequalityButton.setEnabled(samedomain)\n        self.useidentifiersButton.setEnabled(\n            has_any_identifiers or len(self.data) == 0)\n        self.inputsBox.setEnabled(has_any_identifiers)\n\n        if not samedomain and has_any_identifiers and not self.useidentifiers:\n            self.useidentifiers = 1\n        elif samedomain and not has_identifiers:\n            self.useidentifiers = 0\n\n        incremental = all(inc for _, inc in self._queue)\n\n        if incremental:\n            # Only received updated data on existing link.\n            self._updateItemsets()\n        else:\n            # Links were removed and/or added.\n            self._createItemsets()\n            self._restoreHints()\n            self._updateItemsets()\n\n        del self._queue[:]\n\n        self._createDiagram()\n        if self.data:\n            self.infolabel.setText(f\"{len(self.data)} datasets on input.\\n\")\n        else:\n            self.infolabel.setText(\"No data on input\\n\")\n\n        self._updateInfo()\n        super().handleNewSignals()\n\n    def _invalidate(self, keys=None, incremental=True):\n        \"\"\"\n        Invalidate input for a list of input keys.\n        \"\"\"\n        if keys is None:\n            keys = list(self.data.keys())\n\n        self._queue.extend((key, incremental) for key in keys)\n\n    def itemsetAttr(self, key):\n        index = list(self.data.keys()).index(key)\n        _, combo = self._controlAtIndex(index)\n        model = combo.model()\n        attr_index = combo.currentIndex()\n        if attr_index >= 0:\n            return model[attr_index]\n        else:\n            return None\n\n    def _controlAtIndex(self, index):\n        group_box = self.inputsBox.layout().itemAt(index).widget()\n        combo = group_box.combo_box\n        return group_box, combo\n\n    def _setAttributes(self, index, attrs):\n        box, combo = self._controlAtIndex(index)\n        model = combo.model()\n\n        if attrs is None:\n            model[:] = []\n            box.setEnabled(False)\n        else:\n            if model[:] != attrs:\n                model[:] = attrs\n\n            box.setEnabled(True)\n\n    def _add(self, key, table):\n        name = table.name\n        index = len(self.data)\n        attrs = source_attributes(table.domain)\n\n        self.data[key] = _InputData(key, name, table)\n\n        self._setAttributes(index, attrs)\n\n        self._invalidate([key], incremental=False)\n\n        item = self.inputsBox.layout().itemAt(index)\n        box = item.widget()\n        box.setTitle(\"Dataset: {}\".format(name))\n\n    def _remove(self, key):\n        index = list(self.data.keys()).index(key)\n\n        # Clear possible warnings.\n        self.warning()\n\n        self._setAttributes(index, None)\n\n        del self.data[key]\n\n        layout = self.inputsBox.layout()\n        item = layout.takeAt(index)\n        layout.addItem(item)\n        inputs = list(self.data.values())\n\n        for i in range(5):\n            box, _ = self._controlAtIndex(i)\n            if i < len(inputs):\n                title = \"Dataset: {}\".format(inputs[i].name)\n            else:\n                title = \"Dataset #{}\".format(i + 1)\n            box.setTitle(title)\n\n        self._invalidate([key], incremental=False)\n\n    def _update(self, key, table):\n        name = table.name\n        index = list(self.data.keys()).index(key)\n        attrs = source_attributes(table.domain)\n\n        self.data[key] = self.data[key]._replace(name=name, table=table)\n\n        self._setAttributes(index, attrs)\n        self._invalidate([key])\n\n        item = self.inputsBox.layout().itemAt(index)\n        box = item.widget()\n        box.setTitle(\"Dataset: {}\".format(name))\n\n    def _itemsForInput(self, key):\n        useidentifiers = self.useidentifiers or not self.samedomain\n\n        def items_by_key(key, input):\n            attr = self.itemsetAttr(key)\n            if attr is not None:\n                return [str(inst[attr]) for inst in input.table\n                        if not numpy.isnan(inst[attr])]\n            else:\n                return []\n\n        def items_by_eq(key, input):\n            return list(map(ComparableInstance, input.table))\n\n        input = self.data[key]\n        if useidentifiers:\n            items = items_by_key(key, input)\n        else:\n            items = items_by_eq(key, input)\n        return items\n\n    def _updateItemsets(self):\n        assert list(self.data.keys()) == list(self.itemsets.keys())\n        for key, input in list(self.data.items()):\n            items = self._itemsForInput(key)\n            item = self.itemsets[key]\n            item = item._replace(items=items)\n            name = input.name\n            if item.name != name:\n                item = item._replace(name=name, title=name)\n            self.itemsets[key] = item\n\n    def _createItemsets(self):\n        olditemsets = dict(self.itemsets)\n        self.itemsets.clear()\n\n        for key, input in self.data.items():\n            items = self._itemsForInput(key)\n            name = input.name\n            if key in olditemsets and olditemsets[key].name == name:\n                # Reuse the title (which might have been changed by the user)\n                title = olditemsets[key].title\n            else:\n                title = name\n\n            itemset = _ItemSet(key=key, name=name, title=title, items=items)\n            self.itemsets[key] = itemset\n\n    def _storeHints(self):\n        if self.data:\n            self.inputhints.clear()\n            for i, (key, input) in enumerate(self.data.items()):\n                attrs = source_attributes(input.table.domain)\n                attrs = tuple(attr.name for attr in attrs)\n                selected = self.itemsetAttr(key)\n                if selected is not None:\n                    attr_name = selected.name\n                else:\n                    attr_name = None\n                itemset = self.itemsets[key]\n                self.inputhints[(i, input.name, attrs)] = \\\n                    (attr_name, itemset.title)\n\n    def _restoreHints(self):\n        settings = []\n        for i, (key, input) in enumerate(self.data.items()):\n            attrs = source_attributes(input.table.domain)\n            attrs = tuple(attr.name for attr in attrs)\n            hint = self.inputhints.get((i, input.name, attrs), None)\n            if hint is not None:\n                attr, name = hint\n                attr_ind = attrs.index(attr) if attr is not None else -1\n                settings.append((attr_ind, name))\n            else:\n                return\n\n        # all inputs match the stored hints\n        for i, key in enumerate(self.itemsets):\n            attr, itemtitle = settings[i]\n            self.itemsets[key] = self.itemsets[key]._replace(title=itemtitle)\n            _, cb = self._controlAtIndex(i)\n            cb.setCurrentIndex(attr)\n\n    def _createDiagram(self):\n        self._updating = True\n\n        oldselection = list(self.selection)\n\n        self.vennwidget.clear()\n        n = len(self.itemsets)\n        self.disjoint = disjoint(set(s.items) for s in self.itemsets.values())\n\n        vennitems = []\n        colors = colorpalette.ColorPaletteHSV(n)\n\n        for i, (_, item) in enumerate(self.itemsets.items()):\n            count = len(set(item.items))\n            count_all = len(item.items)\n            if count != count_all:\n                fmt = '{} <i>(all: {})</i>'\n            else:\n                fmt = '{}'\n            counts = fmt.format(count, count_all)\n            gr = VennSetItem(text=item.title, informativeText=counts)\n            color = colors[i]\n            color.setAlpha(100)\n            gr.setBrush(QBrush(color))\n            gr.setPen(QPen(Qt.NoPen))\n            vennitems.append(gr)\n\n        self.vennwidget.setItems(vennitems)\n\n        for i, area in enumerate(self.vennwidget.vennareas()):\n            area_items = list(map(str, list(self.disjoint[i])))\n            if i:\n                area.setText(\"{0}\".format(len(area_items)))\n\n            label = disjoint_set_label(i, n, simplify=False)\n            head = \"<h4>|{}| = {}</h4>\".format(label, len(area_items))\n            if len(area_items) > 32:\n                items_str = \", \".join(map(escape, area_items[:32]))\n                hidden = len(area_items) - 32\n                tooltip = (\"{}<span>{}, ...</br>({} items not shown)<span>\"\n                           .format(head, items_str, hidden))\n            elif area_items:\n                tooltip = \"{}<span>{}</span>\".format(\n                    head,\n                    \", \".join(map(escape, area_items))\n                )\n            else:\n                tooltip = head\n\n            area.setToolTip(tooltip)\n\n            area.setPen(QPen(QColor(10, 10, 10, 200), 1.5))\n            area.setFlag(QGraphicsPathItem.ItemIsSelectable, True)\n            area.setSelected(i in oldselection)\n\n        self._updating = False\n        self._on_selectionChanged()\n\n    def _updateInfo(self):\n        # Clear all warnings\n        self.warning()\n\n        if not len(self.data):\n            self.infolabel.setText(\"No data on input\\n\")\n        else:\n            self.infolabel.setText(f\"{len(self.data)} datasets on input\\n\")\n\n        if self.useidentifiers:\n            no_idx = [\"#{}\".format(i + 1)\n                      for i, key in enumerate(self.data)\n                      if not source_attributes(self.data[key].table.domain)]\n            if len(no_idx) == 1:\n                self.warning(\"Dataset {} has no suitable identifiers.\"\n                             .format(no_idx[0]))\n            elif len(no_idx) > 1:\n                self.warning(\"Datasets {} and {} have no suitable identifiers.\"\n                             .format(\", \".join(no_idx[:-1]), no_idx[-1]))\n\n    def _on_selectionChanged(self):\n        if self._updating:\n            return\n\n        areas = self.vennwidget.vennareas()\n        indices = [i for i, area in enumerate(areas)\n                   if area.isSelected()]\n\n        self.selection = indices\n\n        self.invalidateOutput()\n\n    def _on_useidentifiersChanged(self):\n        self.inputsBox.setEnabled(self.useidentifiers == 1)\n        # Invalidate all itemsets\n        self._invalidate()\n        self._updateItemsets()\n        self._createDiagram()\n\n        self._updateInfo()\n\n    def _on_inputAttrActivated(self, attr_index):\n        combo = self.sender()\n        # Find the input index to which the combo box belongs\n        # (they are reordered when removing inputs).\n        index = None\n        inputs = list(self.data.items())\n        for i in range(len(inputs)):\n            _, c = self._controlAtIndex(i)\n            if c is combo:\n                index = i\n                break\n\n        assert index is not None\n\n        key, _ = inputs[index]\n\n        self._invalidate([key])\n        self._updateItemsets()\n        self._createDiagram()\n\n    def _on_itemTextEdited(self, index, text):\n        text = str(text)\n        key = list(self.itemsets.keys())[index]\n        self.itemsets[key] = self.itemsets[key]._replace(title=text)\n\n    def invalidateOutput(self):\n        self.commit()\n\n    def commit(self):\n        selected_subsets = []\n        annotated_data = None\n        annotated_data_subsets = []\n        annotated_data_masks = []\n        selected_items = reduce(\n            set.union, [self.disjoint[index] for index in self.selection],\n            set()\n        )\n\n        def match(val):\n            if numpy.isnan(val):\n                return False\n            else:\n                return str(val) in selected_items\n\n        source_var = Orange.data.StringVariable(\"source\")\n        item_id_var = Orange.data.StringVariable(\"item_id\")\n\n        names = [itemset.title.strip() for itemset in self.itemsets.values()]\n        names = uniquify(names)\n\n        for i, (key, input) in enumerate(self.data.items()):\n            # cell vars are in functions that are only used in the loop\n            # pylint: disable=cell-var-from-loop\n            if not len(input.table):\n                continue\n            if self.useidentifiers:\n                attr = self.itemsetAttr(key)\n                if attr is not None:\n                    mask = list(map(match, (inst[attr] for inst in input.table)))\n                else:\n                    mask = [False] * len(input.table)\n\n                def instance_key(inst):\n                    return str(inst[attr])\n\n                def instance_key_all(inst):\n                    return str(inst[attr])\n            else:\n                mask = [ComparableInstance(inst) in selected_items\n                        for inst in input.table]\n                _map = {item: str(i) for i, item in enumerate(selected_items)}\n                _map_all = {\n                    ComparableInstance(i):  hash(ComparableInstance(i))\n                    for i in input.table\n                }\n\n                def instance_key(inst):\n                    return _map[ComparableInstance(inst)]\n\n                def instance_key_all(inst):\n                    return _map_all[ComparableInstance(inst)]\n\n            mask = numpy.array(mask, dtype=bool)\n            subset = input.table[mask]\n\n            annotated_subset = input.table\n            id_column = numpy.array(\n                [[instance_key_all(inst)] for inst in annotated_subset])\n            source_names = numpy.array([[names[i]]] * len(annotated_subset))\n            annotated_subset = append_column(\n                annotated_subset, \"M\", source_var, source_names)\n            annotated_subset = append_column(\n                annotated_subset, \"M\", item_id_var, id_column)\n            annotated_data_subsets.append(annotated_subset)\n            annotated_data_masks.append(mask)\n\n            if len(subset) == 0:\n                continue\n\n            # add columns with source table id and set id\n\n            if not self.output_duplicates:\n                id_column = numpy.array([[instance_key(inst)] for inst in subset],\n                                        dtype=object)\n                source_names = numpy.array([[names[i]]] * len(subset),\n                                           dtype=object)\n\n                subset = append_column(subset, \"M\", source_var, source_names)\n                subset = append_column(subset, \"M\", item_id_var, id_column)\n\n            selected_subsets.append(subset)\n\n        if selected_subsets and not self.output_duplicates:\n            data = table_concat(selected_subsets)\n            # Get all variables which are not constant between the same\n            # item set\n            varying = varying_between(data, item_id_var)\n\n            if source_var in varying:\n                varying.remove(source_var)\n\n            data = reshape_wide(data, varying, [item_id_var], [source_var])\n            # remove the temporary item set id column\n            data = drop_columns(data, [item_id_var])\n        elif selected_subsets:\n            data = table_concat(selected_subsets)\n        else:\n            data = None\n\n        if annotated_data_subsets:\n            annotated_data = table_concat(annotated_data_subsets)\n            indices = numpy.hstack(annotated_data_masks)\n            annotated_data = create_annotated_table(annotated_data, indices)\n            varying = varying_between(annotated_data, item_id_var)\n            if source_var in varying:\n                varying.remove(source_var)\n            annotated_data = reshape_wide(annotated_data, varying,\n                                          [item_id_var], [source_var])\n            annotated_data = drop_columns(annotated_data, [item_id_var])\n\n        self.Outputs.selected_data.send(data)\n        self.Outputs.annotated_data.send(annotated_data)\n\n    def getSettings(self, *args, **kwargs):\n        self._storeHints()\n        return super().getSettings(self, *args, **kwargs)\n\n    def send_report(self):\n        self.report_plot()\n\n\ndef pairwise(iterable):\n    \"\"\"\n    Return an iterator over consecutive pairs in `iterable`.\n\n    >>> list(pairwise([1, 2, 3, 4])\n    [(1, 2), (2, 3), (3, 4)]\n\n    \"\"\"\n    it = iter(iterable)\n    try:\n        first = next(it)\n    except StopIteration:\n        return\n    for second in it:\n        yield first, second\n        first = second\n\n\n# Custom domain comparison (domains do not seem to compare equal\n# even if they have exactly the same variables).\n# TODO: What about metas.\ndef domain_eq(d1, d2):\n    return d1.variables == d2.variables\n\n\n# Comparing/hashing Orange.data.Instance across domains ignoring metas.\nclass ComparableInstance:\n    __slots__ = [\"inst\", \"domain\", \"__hash\"]\n\n    def __init__(self, inst):\n        self.inst = inst\n        self.domain = inst.domain\n        self.__hash = hash((self.inst.x.data.tobytes(),\n                            self.inst.y.data.tobytes()))\n\n    def __hash__(self):\n        return self.__hash\n\n    def __eq__(self, other):\n        # XXX: comparing NaN with different payload\n        return (isinstance(other, ComparableInstance)\n                and domain_eq(self.domain, other.domain)\n                and self.inst.x.data.tobytes() == other.inst.x.data.tobytes()\n                and self.inst.y.data.tobytes() == other.inst.y.data.tobytes())\n\n    def __iter__(self):\n        return iter(self.inst)\n\n    def __repr__(self):\n        return repr(self.inst)\n\n    def __str__(self):\n        return str(self.inst)\n\n\ndef table_concat(tables):\n    \"\"\"\n    Concatenate a list of tables.\n\n    The resulting table will have a union of all attributes of `tables`.\n\n    \"\"\"\n    attributes = []\n    class_vars = []\n    metas = []\n    variables_seen = set()\n\n    for table in tables:\n        attributes.extend(v for v in table.domain.attributes\n                          if v not in variables_seen)\n        variables_seen.update(table.domain.attributes)\n\n        class_vars.extend(v for v in table.domain.class_vars\n                          if v not in variables_seen)\n        variables_seen.update(table.domain.class_vars)\n\n        metas.extend(v for v in table.domain.metas\n                     if v not in variables_seen)\n\n        variables_seen.update(table.domain.metas)\n\n    domain = Orange.data.Domain(attributes, class_vars, metas)\n\n    tables = [tab.transform(domain) for tab in tables]\n    return tables[0].concatenate(tables, axis=0)\n\n\ndef copy_descriptor(descriptor, newname=None):\n    \"\"\"\n    Create a copy of the descriptor.\n\n    If newname is not None the new descriptor will have the same\n    name as the input.\n\n    \"\"\"\n    if newname is None:\n        newname = descriptor.name\n\n    if descriptor.is_discrete:\n        newf = Orange.data.DiscreteVariable(\n            newname,\n            values=descriptor.values,\n            ordered=descriptor.ordered,\n        )\n        newf.attributes = dict(descriptor.attributes)\n\n    elif descriptor.is_continuous:\n        newf = Orange.data.ContinuousVariable(newname)\n        newf.number_of_decimals = descriptor.number_of_decimals\n        newf.attributes = dict(descriptor.attributes)\n\n    else:\n        newf = type(descriptor)(newname)\n        newf.attributes = dict(descriptor.attributes)\n\n    return newf\n\n\ndef reshape_wide(table, varlist, idvarlist, groupvarlist):\n    \"\"\"\n    Reshape a data table into a wide format.\n\n    :param Orange.data.Table table:\n        Source data table in long format.\n    :param varlist:\n        A list of variables to reshape.\n    :param list idvarlist:\n        A list of variables in `table` uniquely identifying multiple\n        records in `table` (i.e. subject id).\n    :param groupvarlist:\n        A list of variables differentiating multiple records\n        (i.e. conditions).\n\n    \"\"\"\n    def inst_key(inst, vars):\n        return tuple(str(inst[var]) for var in vars)\n\n    instance_groups = [inst_key(inst, groupvarlist) for inst in table]\n    # A list of groups (for each element in a group the varying variable\n    # will be duplicated)\n    groups = list(unique(instance_groups))\n    group_names = [\", \".join(group) for group in groups]\n\n    # A list of instance ids (subject ids)\n    # Each instance in the output will correspond to one of these ids)\n    instance_ids = [inst_key(inst, idvarlist) for inst in table]\n    ids = list(unique(instance_ids))\n\n    # an mapping from ids to an list of input instance indices\n    # each instance in this list belongs to one group (but not all\n    # groups need to be present).\n    inst_by_id = defaultdict(list)\n    id_by_inst = defaultdict(list)  # inverse mapping\n\n    for i in range(len(table)):\n        inst_id = instance_ids[i]\n        inst_by_id[inst_id].append(i)\n        id_by_inst[i] = inst_id\n\n    newfeatures = []\n    newclass_vars = []\n    newmetas = []\n    expanded_features = {}\n\n    def expanded(feat):\n        return [copy_descriptor(feat, newname=\"%s (%s)\" %\n                                (feat.name, group_name))\n                for group_name in group_names]\n\n    for feat in table.domain.attributes:\n        if feat in varlist:\n            features = expanded(feat)\n            newfeatures.extend(features)\n            expanded_features[feat] = dict(zip(groups, features))\n        elif feat not in groupvarlist:\n            newfeatures.append(feat)\n\n    for feat in table.domain.class_vars:\n        if feat in varlist:\n            features = expanded(feat)\n            newclass_vars.extend(features)\n            expanded_features[feat] = dict(zip(groups, features))\n        elif feat not in groupvarlist:\n            newclass_vars.append(feat)\n\n    for meta in table.domain.metas:\n        if meta in varlist:\n            metas = expanded(meta)\n            newmetas.extend(metas)\n            expanded_features[meta] = dict(zip(groups, metas))\n        elif meta not in groupvarlist:\n            newmetas.append(meta)\n\n    domain = Orange.data.Domain(newfeatures, newclass_vars, newmetas)\n    prototype_indices = [inst_by_id[inst_id][0] for inst_id in ids]\n    newtable = table[prototype_indices].transform(domain)\n    in_expanded = set(f for efd in expanded_features.values() for f in efd.values())\n\n    # Fill-in nan values\n    for var in domain.variables + domain.metas:\n        if var in idvarlist or var in in_expanded:\n            continue\n        col, _ = newtable.get_column_view(var)\n        nan_indices = (i for i in col.nonzero()[0]\n                       if isinstance(col[i], str) or numpy.isnan(col[i]))\n        for i in nan_indices:\n            for ind in inst_by_id[ids[i]]:\n                if not numpy.isnan(table[ind, var]):\n                    newtable[i, var] = table[ind, var]\n                    break\n\n    # Fill-in expanded features if any\n    for i, inst_id in enumerate(ids):\n        indices = inst_by_id[inst_id]\n        instance = newtable[i]\n\n        for index in indices:\n            source_inst = table[index]\n            group = instance_groups[index]\n            for source_var in varlist:\n                newf = expanded_features[source_var][group]\n                instance[newf] = source_inst[source_var]\n\n    return newtable\n\n\ndef unique(seq):\n    \"\"\"\n    Return an iterator over unique items of `seq`.\n\n    .. note:: Items must be hashable.\n\n    \"\"\"\n    seen = set()\n    for item in seq:\n        if item not in seen:\n            yield item\n            seen.add(item)\n\n\ndef unique_non_nan(ar):\n    # metas have sometimes object dtype, but values are numpy floats\n    ar = ar.astype('float64')\n    uniq = numpy.unique(ar)\n    return uniq[~numpy.isnan(uniq)]\n\n\ndef varying_between(table, idvar):\n    \"\"\"\n    Return a list of all variables with non constant values between\n    groups defined by `idvar`.\n\n    \"\"\"\n    all_possible = [var for var in table.domain.variables + table.domain.metas\n                    if var != idvar]\n    candidate_set = set(all_possible)\n\n    idmap = group_table_indices(table, idvar)\n\n    varying = set()\n    for indices in idmap.values():\n        subset = table[indices]\n        for var in list(candidate_set):\n            column, _ = subset.get_column_view(var)\n            with numpy.errstate(invalid=\"ignore\"):  # nans are removed below\n                values = util.unique(column)\n\n            if not var.is_string:\n                values = unique_non_nan(values)\n\n            if len(values) > 1:\n                varying.add(var)\n                candidate_set.remove(var)\n\n    return sorted(varying, key=all_possible.index)\n\n\ndef uniquify(strings):\n    \"\"\"\n    Return a list of unique strings.\n\n    The string at i'th position will have the same prefix as strings[i]\n    with an appended suffix to make the item unique (if necessary).\n\n    >>> uniquify([\"cat\", \"dog\", \"cat\"])\n    [\"cat 1\", \"dog\", \"cat 2\"]\n\n    \"\"\"\n    counter = Counter(strings)\n    counts = defaultdict(count)\n    newstrings = []\n    for string in strings:\n        if counter[string] > 1:\n            newstrings.append(string + (\" %i\" % (next(counts[string]) + 1)))\n        else:\n            newstrings.append(string)\n\n    return newstrings\n\n\ndef string_attributes(domain):\n    \"\"\"\n    Return all string attributes from the domain.\n    \"\"\"\n    return [attr for attr in domain.variables + domain.metas if attr.is_string]\n\n\ndef discrete_attributes(domain):\n    \"\"\"\n    Return all discrete attributes from the domain.\n    \"\"\"\n    return [attr for attr in domain.variables + domain.metas if attr.is_discrete]\n\n\ndef source_attributes(domain):\n    \"\"\"\n    Return all suitable attributes for the venn diagram.\n    \"\"\"\n    return string_attributes(domain)  # + discrete_attributes(domain)\n\n\ndef disjoint(sets):\n    \"\"\"\n    Return all disjoint subsets.\n    \"\"\"\n    sets = list(sets)\n    n = len(sets)\n    disjoint_sets = [None] * (2 ** n)\n    for i in range(2 ** n):\n        key = setkey(i, n)\n        included = [s for s, inc in zip(sets, key) if inc]\n        excluded = [s for s, inc in zip(sets, key) if not inc]\n        if any(included):\n            s = reduce(set.intersection, included)\n        else:\n            s = set()\n\n        s = reduce(set.difference, excluded, s)\n\n        disjoint_sets[i] = s\n\n    return disjoint_sets\n\n\ndef disjoint_set_label(i, n, simplify=False):\n    \"\"\"\n    Return a html formated label for a disjoint set indexed by `i`.\n    \"\"\"\n    intersection = unicodedata.lookup(\"INTERSECTION\")\n    # comp = unicodedata.lookup(\"COMPLEMENT\")  #\n    # This depends on the font but the unicode complement in\n    # general does not look nice in a super script so we use\n    # plain c instead.\n    comp = \"c\"\n\n    def label_for_index(i):\n        return chr(ord(\"A\") + i)\n\n    if simplify:\n        return \"\".join(label_for_index(i) for i, b in enumerate(setkey(i, n))\n                       if b)\n    else:\n        return intersection.join(label_for_index(i) +\n                                 (\"\" if b else \"<sup>\" + comp + \"</sup>\")\n                                 for i, b in enumerate(setkey(i, n)))\n\n\nclass VennSetItem(QGraphicsPathItem):\n    def __init__(self, parent=None, text=\"\", informativeText=\"\"):\n        super(VennSetItem, self).__init__(parent)\n        # Plain text title (editable by the VennDiagram)\n        self.text = text\n        # Extra informative text (possibly rich text)\n        self.informativeText = informativeText\n\n# TODO: Use palette's selected/highligted text / background colors to\n# indicate selection\n\nclass VennIntersectionArea(QGraphicsPathItem):\n    def __init__(self, parent=None, text=\"\"):\n        super().__init__(parent)\n        self.setAcceptHoverEvents(True)\n        self.setPen(QPen(Qt.NoPen))\n\n        self.text = QGraphicsTextItem(self)\n        layout = self.text.document().documentLayout()\n        layout.documentSizeChanged.connect(self._onLayoutChanged)\n\n        self._text = \"\"\n        self._anchor = QPointF()\n\n    def setText(self, text):\n        if self._text != text:\n            self._text = text\n            self.text.setPlainText(text)\n\n    def setTextAnchor(self, pos):\n        if self._anchor != pos:\n            self._anchor = pos\n            self._updateTextAnchor()\n\n    def hoverEnterEvent(self, event):\n        self.setZValue(self.zValue() + 1)\n        return QGraphicsPathItem.hoverEnterEvent(self, event)\n\n    def hoverLeaveEvent(self, event):\n        self.setZValue(self.zValue() - 1)\n        return QGraphicsPathItem.hoverLeaveEvent(self, event)\n\n    def mousePressEvent(self, event):\n        if event.button() == Qt.LeftButton:\n            if event.modifiers() & Qt.AltModifier:\n                self.setSelected(False)\n            elif event.modifiers() & Qt.ControlModifier:\n                self.setSelected(not self.isSelected())\n            elif event.modifiers() & Qt.ShiftModifier:\n                self.setSelected(True)\n            else:\n                for area in self.parentWidget().vennareas():\n                    area.setSelected(False)\n                self.setSelected(True)\n\n    def mouseReleaseEvent(self, event):\n        pass\n\n    def paint(self, painter, option, widget=None):\n        painter.save()\n        path = self.path()\n        brush = QBrush(self.brush())\n        pen = QPen(self.pen())\n\n        if option.state & QStyle.State_Selected:\n            pen.setColor(Qt.red)\n            brush.setStyle(Qt.DiagCrossPattern)\n            brush.setColor(QColor(40, 40, 40, 100))\n\n        elif option.state & QStyle.State_MouseOver:\n            pen.setColor(Qt.blue)\n\n        if option.state & QStyle.State_MouseOver:\n            brush.setColor(QColor(100, 100, 100, 100))\n            if brush.style() == Qt.NoBrush:\n                # Make sure the highlight is actually visible.\n                brush.setStyle(Qt.SolidPattern)\n\n        painter.setPen(pen)\n        painter.setBrush(brush)\n        painter.drawPath(path)\n        painter.restore()\n\n    def itemChange(self, change, value):\n        if change == QGraphicsPathItem.ItemSelectedHasChanged:\n            self.setZValue(self.zValue() + (1 if value else -1))\n        return QGraphicsPathItem.itemChange(self, change, value)\n\n    def _updateTextAnchor(self):\n        rect = self.text.boundingRect()\n        pos = anchor_rect(rect, self._anchor)\n        self.text.setPos(pos)\n\n    def _onLayoutChanged(self):\n        self._updateTextAnchor()\n\n\nclass GraphicsTextEdit(QGraphicsTextItem):\n    #: Edit triggers\n    NoEditTriggers, DoubleClicked = 0, 1\n\n    editingFinished = Signal()\n    editingStarted = Signal()\n\n    documentSizeChanged = Signal()\n\n    def __init__(self, *args, **kwargs):\n        super(GraphicsTextEdit, self).__init__(*args, **kwargs)\n        self.setCursor(Qt.IBeamCursor)\n        self.setTabChangesFocus(True)\n        self._edittrigger = GraphicsTextEdit.DoubleClicked\n        self._editing = False\n        self.document().documentLayout().documentSizeChanged.connect(\n            self.documentSizeChanged\n        )\n\n    def mouseDoubleClickEvent(self, event):\n        super(GraphicsTextEdit, self).mouseDoubleClickEvent(event)\n        if self._edittrigger == GraphicsTextEdit.DoubleClicked:\n            self._start()\n\n    def focusOutEvent(self, event):\n        super(GraphicsTextEdit, self).focusOutEvent(event)\n\n        if self._editing:\n            self._end()\n\n    def _start(self):\n        self._editing = True\n        self.setTextInteractionFlags(Qt.TextEditorInteraction)\n        self.setFocus(Qt.MouseFocusReason)\n        self.editingStarted.emit()\n\n    def _end(self):\n        self._editing = False\n        self.setTextInteractionFlags(Qt.NoTextInteraction)\n        self.editingFinished.emit()\n\n\nclass VennDiagram(QGraphicsWidget):\n    # rect and petal are for future work\n    Circle, Ellipse, Rect, Petal = 1, 2, 3, 4\n\n    TitleFormat = \"<center><h4>{0}</h4>{1}</center>\"\n\n    selectionChanged = Signal()\n    itemTextEdited = Signal(int, str)\n\n    def __init__(self, parent=None):\n        super(VennDiagram, self).__init__(parent)\n        self.shapeType = VennDiagram.Circle\n\n        self._setup()\n\n    def _setup(self):\n        self._items = []\n        self._vennareas = []\n        self._textitems = []\n\n    def item(self, index):\n        return self._items[index]\n\n    def items(self):\n        return list(self._items)\n\n    def count(self):\n        return len(self._items)\n\n    def setItems(self, items):\n        if self._items:\n            self.clear()\n\n        self._items = list(items)\n\n        for item in self._items:\n            item.setParentItem(self)\n            item.setVisible(True)\n\n        fmt = self.TitleFormat.format\n\n        font = self.font()\n        font.setPixelSize(14)\n\n        for item in items:\n            text = GraphicsTextEdit(self)\n            text.setFont(font)\n            text.setDefaultTextColor(QColor(\"#333\"))\n            text.setHtml(fmt(escape(item.text), item.informativeText))\n            text.adjustSize()\n            text.editingStarted.connect(self._on_editingStarted)\n            text.editingFinished.connect(self._on_editingFinished)\n            text.documentSizeChanged.connect(\n                self._on_itemTextSizeChanged\n            )\n\n            self._textitems.append(text)\n\n        self._vennareas = [\n            VennIntersectionArea(parent=self)\n            for i in range(2 ** len(items))\n        ]\n        self._subsettextitems = [\n            QGraphicsTextItem(parent=self)\n            for i in range(2 ** len(items))\n        ]\n\n        self._updateLayout()\n\n    def clear(self):\n        scene = self.scene()\n        items = self.vennareas() + list(self.items()) + self._textitems\n\n        for item in self._textitems:\n            item.editingStarted.disconnect(self._on_editingStarted)\n            item.editingFinished.disconnect(self._on_editingFinished)\n            item.documentSizeChanged.disconnect(\n                self._on_itemTextSizeChanged\n            )\n\n        self._items = []\n        self._vennareas = []\n        self._textitems = []\n\n        for item in items:\n            item.setVisible(False)\n            item.setParentItem(None)\n            if scene is not None:\n                scene.removeItem(item)\n\n    def vennareas(self):\n        return list(self._vennareas)\n\n    def setFont(self, font):\n        if font != self.font():\n            self.prepareGeometryChange()\n            super().setFont(font)\n\n            for item in self.items():\n                item.setFont(font)\n\n    def _updateLayout(self):\n        rect = self.geometry()\n        n = len(self._items)\n        if not n:\n            return\n\n        regions = venn_diagram(n, shape=self.shapeType)\n\n        # The y axis in Qt points downward\n        transform = QTransform().scale(1, -1)\n        regions = list(map(transform.map, regions))\n\n        union_brect = reduce(QRectF.united,\n                             (path.boundingRect() for path in regions))\n\n        scalex = rect.width() / union_brect.width()\n        scaley = rect.height() / union_brect.height()\n        scale = min(scalex, scaley)\n\n        transform = QTransform().scale(scale, scale)\n\n        regions = [transform.map(path) for path in regions]\n\n        center = rect.width() / 2, rect.height() / 2\n        for item, path in zip(self.items(), regions):\n            item.setPath(path)\n            item.setPos(*center)\n\n        intersections = venn_intersections(regions)\n        assert len(intersections) == 2 ** n\n        assert len(self.vennareas()) == 2 ** n\n\n        anchors = [(0, 0)] + subset_anchors(self._items)\n\n        anchor_transform = QTransform().scale(rect.width(), -rect.height())\n        for i, area in enumerate(self.vennareas()):\n            area.setPath(intersections[setkey(i, n)])\n            area.setPos(*center)\n            x, y = anchors[i]\n            anchor = anchor_transform.map(QPointF(x, y))\n            area.setTextAnchor(anchor)\n            area.setZValue(30)\n\n        self._updateTextAnchors()\n\n    def _updateTextAnchors(self):\n        n = len(self._items)\n\n        items = self._items\n        dist = 15\n\n        shape = reduce(QPainterPath.united, [item.path() for item in items])\n        brect = shape.boundingRect()\n        bradius = max(brect.width() / 2, brect.height() / 2)\n\n        center = self.boundingRect().center()\n\n        anchors = _category_anchors(items)\n        self._textanchors = []\n        for angle, anchor_h, anchor_v in anchors:\n            line = QLineF.fromPolar(bradius, angle)\n            ext = QLineF.fromPolar(dist, angle)\n            line = QLineF(line.p1(), line.p2() + ext.p2())\n            line = line.translated(center)\n\n            anchor_pos = line.p2()\n            self._textanchors.append((anchor_pos, anchor_h, anchor_v))\n\n        for i in range(n):\n            self._updateTextItemPos(i)\n\n    def _updateTextItemPos(self, i):\n        item = self._textitems[i]\n        anchor_pos, anchor_h, anchor_v = self._textanchors[i]\n        rect = item.boundingRect()\n        pos = anchor_rect(rect, anchor_pos, anchor_h, anchor_v)\n        item.setPos(pos)\n\n    def setGeometry(self, geometry):\n        super(VennDiagram, self).setGeometry(geometry)\n        self._updateLayout()\n\n    def _on_editingStarted(self):\n        item = self.sender()\n        index = self._textitems.index(item)\n        text = self._items[index].text\n        item.setTextWidth(-1)\n        item.setHtml(self.TitleFormat.format(escape(text), \"<br/>\"))\n\n    def _on_editingFinished(self):\n        item = self.sender()\n        index = self._textitems.index(item)\n        text = item.toPlainText()\n        if text != self._items[index].text:\n            self._items[index].text = text\n\n            self.itemTextEdited.emit(index, text)\n\n        item.setHtml(\n            self.TitleFormat.format(\n                escape(text), self._items[index].informativeText))\n        item.adjustSize()\n\n    def _on_itemTextSizeChanged(self):\n        item = self.sender()\n        index = self._textitems.index(item)\n        self._updateTextItemPos(index)\n\n\ndef anchor_rect(rect, anchor_pos,\n                anchor_h=Qt.AnchorHorizontalCenter,\n                anchor_v=Qt.AnchorVerticalCenter):\n\n    if anchor_h == Qt.AnchorLeft:\n        x = anchor_pos.x()\n    elif anchor_h == Qt.AnchorHorizontalCenter:\n        x = anchor_pos.x() - rect.width() / 2\n    elif anchor_h == Qt.AnchorRight:\n        x = anchor_pos.x() - rect.width()\n    else:\n        raise ValueError(anchor_h)\n\n    if anchor_v == Qt.AnchorTop:\n        y = anchor_pos.y()\n    elif anchor_v == Qt.AnchorVerticalCenter:\n        y = anchor_pos.y() - rect.height() / 2\n    elif anchor_v == Qt.AnchorBottom:\n        y = anchor_pos.y() - rect.height()\n    else:\n        raise ValueError(anchor_v)\n\n    return QPointF(x, y)\n\n\ndef radians(angle):\n    return 2 * math.pi * angle / 360\n\n\ndef unit_point(x, r=1.0):\n    x = radians(x)\n    return (r * math.cos(x), r * math.sin(x))\n\n\ndef _category_anchors(shapes):\n    n = len(shapes)\n    return _CATEGORY_ANCHORS[n - 1]\n\n\n# (angle, horizontal anchor, vertical anchor)\n_CATEGORY_ANCHORS = (\n    # n == 1\n    ((90, Qt.AnchorHorizontalCenter, Qt.AnchorBottom),),\n    # n == 2\n    ((180, Qt.AnchorRight, Qt.AnchorVerticalCenter),\n     (0, Qt.AnchorLeft, Qt.AnchorVerticalCenter)),\n    # n == 3\n    ((150, Qt.AnchorRight, Qt.AnchorBottom),\n     (30, Qt.AnchorLeft, Qt.AnchorBottom),\n     (270, Qt.AnchorHorizontalCenter, Qt.AnchorTop)),\n    # n == 4\n    ((270 + 45, Qt.AnchorLeft, Qt.AnchorTop),\n     (270 - 45, Qt.AnchorRight, Qt.AnchorTop),\n     (90 - 15, Qt.AnchorLeft, Qt.AnchorBottom),\n     (90 + 15, Qt.AnchorRight, Qt.AnchorBottom)),\n    # n == 5\n    ((90 - 5, Qt.AnchorHorizontalCenter, Qt.AnchorBottom),\n     (18 - 5, Qt.AnchorLeft, Qt.AnchorVerticalCenter),\n     (306 - 5, Qt.AnchorLeft, Qt.AnchorTop),\n     (234 - 5, Qt.AnchorRight, Qt.AnchorTop),\n     (162 - 5, Qt.AnchorRight, Qt.AnchorVerticalCenter),)\n)\n\n\ndef subset_anchors(shapes):\n    n = len(shapes)\n    if n == 1:\n        return [(0, 0)]\n    elif n == 2:\n        return [unit_point(180, r=1/3),\n                unit_point(0, r=1/3),\n                (0, 0)]\n    elif n == 3:\n        return [unit_point(150, r=0.35),  # A\n                unit_point(30, r=0.35),   # B\n                unit_point(90, r=0.27),   # AB\n                unit_point(270, r=0.35),  # C\n                unit_point(210, r=0.27),  # AC\n                unit_point(330, r=0.27),  # BC\n                unit_point(0, r=0),]      # ABC\n    elif n == 4:\n        anchors = [\n            (0.400, 0.110),    # A\n            (-0.400, 0.110),   # B\n            (0.000, -0.285),   # AB\n            (0.180, 0.330),    # C\n            (0.265, 0.205),    # AC\n            (-0.240, -0.110),  # BC\n            (-0.100, -0.190),  # ABC\n            (-0.180, 0.330),   # D\n            (0.240, -0.110),   # AD\n            (-0.265, 0.205),   # BD\n            (0.100, -0.190),   # ABD\n            (0.000, 0.250),    # CD\n            (0.153, 0.090),    # ACD\n            (-0.153, 0.090),   # BCD\n            (0.000, -0.060),   # ABCD\n        ]\n        return anchors\n\n    elif n == 5:\n        anchors = [None] * 32\n        # Base anchors\n        A = (0.033, 0.385)\n        AD = (0.095, 0.250)\n        AE = (-0.100, 0.265)\n        ACE = (-0.130, 0.220)\n        ADE = (0.010, 0.225)\n        ACDE = (-0.095, 0.175)\n        ABCDE = (0.0, 0.0)\n\n        anchors[-1] = ABCDE\n\n        bases = [(0b00001, A),\n                 (0b01001, AD),\n                 (0b10001, AE),\n                 (0b10101, ACE),\n                 (0b11001, ADE),\n                 (0b11101, ACDE)]\n\n        for i in range(5):\n            for index, anchor in bases:\n                index = bit_rot_left(index, i, bits=5)\n                assert anchors[index] is None\n                anchors[index] = rotate_point(anchor, - 72 * i)\n\n        assert all(anchors[1:])\n        return anchors[1:]\n    return None\n\n\ndef bit_rot_left(x, y, bits=32):\n    mask = 2 ** bits - 1\n    x_masked = x & mask\n    return (x << y) & mask | (x_masked >> bits - y)\n\n\ndef rotate_point(p, angle):\n    r = radians(angle)\n    R = numpy.array([[math.cos(r), -math.sin(r)],\n                     [math.sin(r), math.cos(r)]])\n    x, y = numpy.dot(R, p)\n    return (float(x), float(y))\n\n\ndef line_extended(line, distance):\n    \"\"\"\n    Return an QLineF extended by `distance` units in the positive direction.\n    \"\"\"\n    angle = line.angle() / 360 * 2 * math.pi\n    dx, dy = unit_point(angle, r=distance)\n    return QLineF(line.p1(), line.p2() + QPointF(dx, dy))\n\n\ndef circle_path(center, r=1.0):\n    return ellipse_path(center, r, r, rotation=0)\n\n\ndef ellipse_path(center, a, b, rotation=0):\n    if not isinstance(center, QPointF):\n        center = QPointF(*center)\n\n    brect = QRectF(-a, -b, 2 * a, 2 * b)\n\n    path = QPainterPath()\n    path.addEllipse(brect)\n\n    if rotation != 0:\n        transform = QTransform().rotate(rotation)\n        path = transform.map(path)\n\n    path.translate(center)\n    return path\n\n\n# TODO: Should include anchors for text layout (both inside and outside).\n# for each item {path: QPainterPath,\n#                text_anchors: [{center}] * (2 ** n)\n#                mayor_axis: QLineF,\n#                boundingRect QPolygonF (with 4 vertices)}\n#\n# Should be a new class with overloads for ellipse/circle, rect, and petal\n# shapes, should store all constructor parameters, rotation, center,\n# mayor/minor axis.\n\n\ndef venn_diagram(n, shape=VennDiagram.Circle):\n    if n < 1 or n > 5:\n        raise ValueError()\n\n    paths = []\n\n    if n == 1:\n        paths = [circle_path(center=(0, 0), r=0.5)]\n    elif n == 2:\n        angles = [180, 0]\n        paths = [circle_path(center=unit_point(x, r=1/6), r=1/3)\n                 for x in angles]\n    elif n == 3:\n        angles = [150 - 120 * i for i in range(3)]\n        paths = [circle_path(center=unit_point(x, r=1/6), r=1/3)\n                 for x in angles]\n    elif n == 4:\n        # Constants shamelessly stolen from VennDiagram R package\n        paths = [\n            ellipse_path((0.65 - 0.5, 0.47 - 0.5), 0.35, 0.20, 45),\n            ellipse_path((0.35 - 0.5, 0.47 - 0.5), 0.35, 0.20, 135),\n            ellipse_path((0.5 - 0.5, 0.57 - 0.5), 0.35, 0.20, 45),\n            ellipse_path((0.5 - 0.5, 0.57 - 0.5), 0.35, 0.20, 134),\n        ]\n    elif n == 5:\n        # Constants shamelessly stolen from VennDiagram R package\n        d = 0.13\n        a, b = 0.24, 0.48\n        a, b = b, a\n        a, b = 0.48, 0.24\n        paths = [ellipse_path(unit_point((1 - i) * 72, r=d),\n                              a, b, rotation=90 - (i * 72))\n                 for i in range(5)]\n\n    return paths\n\n\ndef setkey(intval, n):\n    return tuple(bool(intval & (2 ** i)) for i in range(n))\n\n\ndef keyrange(n):\n    if n < 0:\n        raise ValueError()\n\n    for i in range(2 ** n):\n        yield setkey(i, n)\n\n\ndef venn_intersections(paths):\n    n = len(paths)\n    return {key: venn_intersection(paths, key) for key in keyrange(n)}\n\n\ndef venn_intersection(paths, key):\n    if not any(key):\n        return QPainterPath()\n\n    # first take the intersection of all included paths\n    path = reduce(QPainterPath.intersected,\n                  (path for path, included in zip(paths, key) if included))\n\n    # subtract all the excluded sets (i.e. take the intersection\n    # with the excluded set complements)\n    path = reduce(QPainterPath.subtracted,\n                  (path for path, included in zip(paths, key) if not included),\n                  path)\n\n    return path\n\n\ndef append_column(data, where, variable, column):\n    X, Y, M = data.X, data.Y, data.metas\n    domain = data.domain\n    attr = domain.attributes\n    class_vars = domain.class_vars\n    metas = domain.metas\n    if where == \"X\":\n        attr = attr + (variable,)\n        X = numpy.hstack((X, column))\n    elif where == \"Y\":\n        class_vars = class_vars + (variable,)\n        Y = numpy.hstack((Y, column))\n    elif where == \"M\":\n        metas = metas + (variable,)\n        M = numpy.hstack((M, column))\n    else:\n        raise ValueError\n    domain = Orange.data.Domain(attr, class_vars, metas)\n    new_data = data.transform(domain)\n    new_data[:, variable] = column\n    return new_data\n\n\ndef drop_columns(data, columns):\n    columns = set(data.domain[col] for col in columns)\n\n    def filter_vars(vars):\n        return tuple(var for var in vars if var not in columns)\n\n    domain = Orange.data.Domain(\n        filter_vars(data.domain.attributes),\n        filter_vars(data.domain.class_vars),\n        filter_vars(data.domain.metas)\n    )\n    return Orange.data.Table.from_table(domain, data)\n\n\ndef group_table_indices(table, key_var):\n    \"\"\"\n    Group table indices based on values of selected columns (`key_vars`).\n\n    Return a dictionary mapping all unique value combinations (keys)\n    into a list of indices in the table where they are present.\n    \"\"\"\n    groups = defaultdict(list)\n    for i, inst in enumerate(table):\n        groups[str(inst[key_var])].append(i)\n    return groups\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    from Orange.evaluation import ShuffleSplit\n\n    data = Orange.data.Table(\"brown-selected\")\n    # data1 = Orange.data.Table(\"brown-selected\")\n    # datasets = [(data, 1), (data, 2)]\n\n    data = append_column(data, \"M\", Orange.data.StringVariable(\"Test\"),\n                         numpy.arange(len(data)).reshape(-1, 1) % 30)\n    res = ShuffleSplit(data, [None], n_resamples=5,\n                       test_size=0.7, stratified=False)\n    indices = iter(res.indices)\n    datasets = []\n    for i in range(1, 6):\n        sample, _ = next(indices)\n        data1 = data[sample]\n        data1.name = chr(ord(\"A\") + i)\n        datasets.append((data1, i))\n\n    WidgetPreview(OWVennDiagram).run(setData=datasets)\n",
            "new_file_content": "\"\"\"\nVenn Diagram Widget\n-------------------\n\n\"\"\"\n\nimport math\nimport unicodedata\nfrom collections import namedtuple, defaultdict, OrderedDict, Counter\nfrom itertools import count\nfrom functools import reduce\nfrom xml.sax.saxutils import escape\n\nimport numpy\n\nfrom AnyQt.QtWidgets import (\n    QComboBox, QGraphicsScene, QGraphicsView, QGraphicsWidget,\n    QGraphicsPathItem, QGraphicsTextItem, QStyle\n)\nfrom AnyQt.QtGui import (\n    QPainterPath, QPainter, QTransform, QColor, QBrush, QPen, QPalette\n)\nfrom AnyQt.QtCore import Qt, QPointF, QRectF, QLineF\nfrom AnyQt.QtCore import pyqtSignal as Signal\n\nimport Orange.data\nfrom Orange.statistics import util\nfrom Orange.widgets import widget, gui, settings\nfrom Orange.widgets.utils import itemmodels, colorpalette\nfrom Orange.widgets.utils.annotated_data import (create_annotated_table,\n                                                 ANNOTATED_DATA_SIGNAL_NAME)\nfrom Orange.widgets.utils.sql import check_sql_input\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.widget import Input, Output\n\n\n_InputData = namedtuple(\"_InputData\", [\"key\", \"name\", \"table\"])\n_ItemSet = namedtuple(\"_ItemSet\", [\"key\", \"name\", \"title\", \"items\"])\n\n\nclass OWVennDiagram(widget.OWWidget):\n    name = \"Venn Diagram\"\n    description = \"A graphical visualization of the overlap of data instances \" \\\n                  \"from a collection of input datasets.\"\n    icon = \"icons/VennDiagram.svg\"\n    priority = 280\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table, multiple=True)\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    inputhints: dict\n    selection: list\n\n    # Selected disjoint subset indices\n    selection = settings.Setting([])\n    #: Stored input set hints\n    #: {(index, inputname, attributes): (selectedattrname, itemsettitle)}\n    #: The 'selectedattrname' can be None\n    inputhints = settings.Setting({})\n    #: Use identifier columns for instance matching\n    useidentifiers = settings.Setting(True)\n    #: Output unique items (one output row for every unique instance `key`)\n    #: or preserve all duplicates in the output.\n    output_duplicates = settings.Setting(False)\n    autocommit = settings.Setting(True)\n\n    graph_name = \"scene\"\n\n    def __init__(self):\n        super().__init__()\n\n        # Diagram update is in progress\n        self._updating = False\n        # Input update is in progress\n        self._inputUpdate = False\n        # All input tables have the same domain.\n        self.samedomain = True\n        # Input datasets in the order they were 'connected'.\n        self.data = OrderedDict()\n        # Extracted input item sets in the order they were 'connected'\n        self.itemsets = OrderedDict()\n\n        # GUI\n        box = gui.vBox(self.controlArea, \"Info\")\n        self.infolabel = gui.widgetLabel(box, \"No data on input.\\n\")\n\n        self.identifiersBox = gui.radioButtonsInBox(\n            self.controlArea, self, \"useidentifiers\", [],\n            box=\"Data Instance Identifiers\",\n            callback=self._on_useidentifiersChanged\n        )\n        self.useequalityButton = gui.appendRadioButton(\n            self.identifiersBox, \"Use instance equality\"\n        )\n        self.useidentifiersButton = rb = gui.appendRadioButton(\n            self.identifiersBox, \"Use identifiers\"\n        )\n        self.inputsBox = gui.indentedBox(\n            self.identifiersBox, sep=gui.checkButtonOffsetHint(rb)\n        )\n        self.inputsBox.setEnabled(bool(self.useidentifiers))\n\n        for i in range(5):\n            box = gui.vBox(self.inputsBox, \"Dataset #%i\" % (i + 1),\n                           addSpace=False)\n            box.setFlat(True)\n            model = itemmodels.VariableListModel(parent=self)\n            cb = gui.OrangeComboBox(\n                minimumContentsLength=12,\n                sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon\n            )\n            cb.setModel(model)\n            cb.activated[int].connect(self._on_inputAttrActivated)\n            box.setEnabled(False)\n            # Store the combo in the box for later use.\n            box.combo_box = cb\n            box.layout().addWidget(cb)\n\n        gui.rubber(self.controlArea)\n\n        box = gui.vBox(self.controlArea, \"Output\")\n        gui.checkBox(box, self, \"output_duplicates\", \"Output duplicates\",\n                     callback=lambda: self.commit())\n        gui.auto_commit(box, self, \"autocommit\", \"Send Selection\", \"Send Automatically\", box=False)\n\n        # Main area view\n        self.scene = QGraphicsScene()\n        self.view = QGraphicsView(self.scene)\n        self.view.setRenderHint(QPainter.Antialiasing)\n        self.view.setBackgroundRole(QPalette.Window)\n        self.view.setFrameStyle(QGraphicsView.StyledPanel)\n\n        self.mainArea.layout().addWidget(self.view)\n        self.vennwidget = VennDiagram()\n        self.vennwidget.resize(400, 400)\n        self.vennwidget.itemTextEdited.connect(self._on_itemTextEdited)\n        self.scene.selectionChanged.connect(self._on_selectionChanged)\n\n        self.scene.addItem(self.vennwidget)\n\n        self.resize(self.controlArea.sizeHint().width() + 550,\n                    max(self.controlArea.sizeHint().height(), 550))\n\n        self._queue = []\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data, key=None):\n        self.error()\n        if not self._inputUpdate:\n            # Store hints only on the first setData call.\n            self._storeHints()\n            self._inputUpdate = True\n\n        if key in self.data:\n            if data is None:\n                # Remove the input\n                self._remove(key)\n            else:\n                # Update existing item\n                self._update(key, data)\n        elif data is not None:\n            # TODO: Allow setting more them 5 inputs and let the user\n            # select the 5 to display.\n            if len(self.data) == 5:\n                self.error(\"Venn diagram accepts at most five datasets.\")\n                return\n            # Add a new input\n            self._add(key, data)\n\n    def handleNewSignals(self):\n        self._inputUpdate = False\n\n        # Check if all inputs are from the same domain.\n        domains = [input.table.domain for input in self.data.values()]\n        samedomain = all(domain_eq(d1, d2) for d1, d2 in pairwise(domains))\n\n        self.samedomain = samedomain\n\n        has_identifiers = all(source_attributes(input.table.domain)\n                              for input in self.data.values())\n        has_any_identifiers = any(source_attributes(input.table.domain)\n                                  for input in self.data.values())\n        self.useequalityButton.setEnabled(samedomain)\n        self.useidentifiersButton.setEnabled(\n            has_any_identifiers or len(self.data) == 0)\n        self.inputsBox.setEnabled(has_any_identifiers)\n\n        if not samedomain and has_any_identifiers and not self.useidentifiers:\n            self.useidentifiers = 1\n        elif samedomain and not has_identifiers:\n            self.useidentifiers = 0\n\n        incremental = all(inc for _, inc in self._queue)\n\n        if incremental:\n            # Only received updated data on existing link.\n            self._updateItemsets()\n        else:\n            # Links were removed and/or added.\n            self._createItemsets()\n            self._restoreHints()\n            self._updateItemsets()\n\n        del self._queue[:]\n\n        self._createDiagram()\n        # If autocommit is enabled, _createDiagram already outputs data\n        # If not, call unconditional_commit from here\n        if not self.autocommit:\n            self.unconditional_commit()\n        if self.data:\n            self.infolabel.setText(f\"{len(self.data)} datasets on input.\\n\")\n        else:\n            self.infolabel.setText(\"No data on input\\n\")\n\n        self._updateInfo()\n        super().handleNewSignals()\n\n    def _invalidate(self, keys=None, incremental=True):\n        \"\"\"\n        Invalidate input for a list of input keys.\n        \"\"\"\n        if keys is None:\n            keys = list(self.data.keys())\n\n        self._queue.extend((key, incremental) for key in keys)\n\n    def itemsetAttr(self, key):\n        index = list(self.data.keys()).index(key)\n        _, combo = self._controlAtIndex(index)\n        model = combo.model()\n        attr_index = combo.currentIndex()\n        if attr_index >= 0:\n            return model[attr_index]\n        else:\n            return None\n\n    def _controlAtIndex(self, index):\n        group_box = self.inputsBox.layout().itemAt(index).widget()\n        combo = group_box.combo_box\n        return group_box, combo\n\n    def _setAttributes(self, index, attrs):\n        box, combo = self._controlAtIndex(index)\n        model = combo.model()\n\n        if attrs is None:\n            model[:] = []\n            box.setEnabled(False)\n        else:\n            if model[:] != attrs:\n                model[:] = attrs\n\n            box.setEnabled(True)\n\n    def _add(self, key, table):\n        name = table.name\n        index = len(self.data)\n        attrs = source_attributes(table.domain)\n\n        self.data[key] = _InputData(key, name, table)\n\n        self._setAttributes(index, attrs)\n\n        self._invalidate([key], incremental=False)\n\n        item = self.inputsBox.layout().itemAt(index)\n        box = item.widget()\n        box.setTitle(\"Dataset: {}\".format(name))\n\n    def _remove(self, key):\n        index = list(self.data.keys()).index(key)\n\n        # Clear possible warnings.\n        self.warning()\n\n        self._setAttributes(index, None)\n\n        del self.data[key]\n\n        layout = self.inputsBox.layout()\n        item = layout.takeAt(index)\n        layout.addItem(item)\n        inputs = list(self.data.values())\n\n        for i in range(5):\n            box, _ = self._controlAtIndex(i)\n            if i < len(inputs):\n                title = \"Dataset: {}\".format(inputs[i].name)\n            else:\n                title = \"Dataset #{}\".format(i + 1)\n            box.setTitle(title)\n\n        self._invalidate([key], incremental=False)\n\n    def _update(self, key, table):\n        name = table.name\n        index = list(self.data.keys()).index(key)\n        attrs = source_attributes(table.domain)\n\n        self.data[key] = self.data[key]._replace(name=name, table=table)\n\n        self._setAttributes(index, attrs)\n        self._invalidate([key])\n\n        item = self.inputsBox.layout().itemAt(index)\n        box = item.widget()\n        box.setTitle(\"Dataset: {}\".format(name))\n\n    def _itemsForInput(self, key):\n        useidentifiers = self.useidentifiers or not self.samedomain\n\n        def items_by_key(key, input):\n            attr = self.itemsetAttr(key)\n            if attr is not None:\n                return [str(inst[attr]) for inst in input.table\n                        if not numpy.isnan(inst[attr])]\n            else:\n                return []\n\n        def items_by_eq(key, input):\n            return list(map(ComparableInstance, input.table))\n\n        input = self.data[key]\n        if useidentifiers:\n            items = items_by_key(key, input)\n        else:\n            items = items_by_eq(key, input)\n        return items\n\n    def _updateItemsets(self):\n        assert list(self.data.keys()) == list(self.itemsets.keys())\n        for key, input in list(self.data.items()):\n            items = self._itemsForInput(key)\n            item = self.itemsets[key]\n            item = item._replace(items=items)\n            name = input.name\n            if item.name != name:\n                item = item._replace(name=name, title=name)\n            self.itemsets[key] = item\n\n    def _createItemsets(self):\n        olditemsets = dict(self.itemsets)\n        self.itemsets.clear()\n\n        for key, input in self.data.items():\n            items = self._itemsForInput(key)\n            name = input.name\n            if key in olditemsets and olditemsets[key].name == name:\n                # Reuse the title (which might have been changed by the user)\n                title = olditemsets[key].title\n            else:\n                title = name\n\n            itemset = _ItemSet(key=key, name=name, title=title, items=items)\n            self.itemsets[key] = itemset\n\n    def _storeHints(self):\n        if self.data:\n            self.inputhints.clear()\n            for i, (key, input) in enumerate(self.data.items()):\n                attrs = source_attributes(input.table.domain)\n                attrs = tuple(attr.name for attr in attrs)\n                selected = self.itemsetAttr(key)\n                if selected is not None:\n                    attr_name = selected.name\n                else:\n                    attr_name = None\n                itemset = self.itemsets[key]\n                self.inputhints[(i, input.name, attrs)] = \\\n                    (attr_name, itemset.title)\n\n    def _restoreHints(self):\n        settings = []\n        for i, (key, input) in enumerate(self.data.items()):\n            attrs = source_attributes(input.table.domain)\n            attrs = tuple(attr.name for attr in attrs)\n            hint = self.inputhints.get((i, input.name, attrs), None)\n            if hint is not None:\n                attr, name = hint\n                attr_ind = attrs.index(attr) if attr is not None else -1\n                settings.append((attr_ind, name))\n            else:\n                return\n\n        # all inputs match the stored hints\n        for i, key in enumerate(self.itemsets):\n            attr, itemtitle = settings[i]\n            self.itemsets[key] = self.itemsets[key]._replace(title=itemtitle)\n            _, cb = self._controlAtIndex(i)\n            cb.setCurrentIndex(attr)\n\n    def _createDiagram(self):\n        self._updating = True\n\n        oldselection = list(self.selection)\n\n        self.vennwidget.clear()\n        n = len(self.itemsets)\n        self.disjoint = disjoint(set(s.items) for s in self.itemsets.values())\n\n        vennitems = []\n        colors = colorpalette.ColorPaletteHSV(n)\n\n        for i, (_, item) in enumerate(self.itemsets.items()):\n            count = len(set(item.items))\n            count_all = len(item.items)\n            if count != count_all:\n                fmt = '{} <i>(all: {})</i>'\n            else:\n                fmt = '{}'\n            counts = fmt.format(count, count_all)\n            gr = VennSetItem(text=item.title, informativeText=counts)\n            color = colors[i]\n            color.setAlpha(100)\n            gr.setBrush(QBrush(color))\n            gr.setPen(QPen(Qt.NoPen))\n            vennitems.append(gr)\n\n        self.vennwidget.setItems(vennitems)\n\n        for i, area in enumerate(self.vennwidget.vennareas()):\n            area_items = list(map(str, list(self.disjoint[i])))\n            if i:\n                area.setText(\"{0}\".format(len(area_items)))\n\n            label = disjoint_set_label(i, n, simplify=False)\n            head = \"<h4>|{}| = {}</h4>\".format(label, len(area_items))\n            if len(area_items) > 32:\n                items_str = \", \".join(map(escape, area_items[:32]))\n                hidden = len(area_items) - 32\n                tooltip = (\"{}<span>{}, ...</br>({} items not shown)<span>\"\n                           .format(head, items_str, hidden))\n            elif area_items:\n                tooltip = \"{}<span>{}</span>\".format(\n                    head,\n                    \", \".join(map(escape, area_items))\n                )\n            else:\n                tooltip = head\n\n            area.setToolTip(tooltip)\n\n            area.setPen(QPen(QColor(10, 10, 10, 200), 1.5))\n            area.setFlag(QGraphicsPathItem.ItemIsSelectable, True)\n            area.setSelected(i in oldselection)\n\n        self._updating = False\n        self._on_selectionChanged()\n\n    def _updateInfo(self):\n        # Clear all warnings\n        self.warning()\n\n        if not len(self.data):\n            self.infolabel.setText(\"No data on input\\n\")\n        else:\n            self.infolabel.setText(f\"{len(self.data)} datasets on input\\n\")\n\n        if self.useidentifiers:\n            no_idx = [\"#{}\".format(i + 1)\n                      for i, key in enumerate(self.data)\n                      if not source_attributes(self.data[key].table.domain)]\n            if len(no_idx) == 1:\n                self.warning(\"Dataset {} has no suitable identifiers.\"\n                             .format(no_idx[0]))\n            elif len(no_idx) > 1:\n                self.warning(\"Datasets {} and {} have no suitable identifiers.\"\n                             .format(\", \".join(no_idx[:-1]), no_idx[-1]))\n\n    def _on_selectionChanged(self):\n        if self._updating:\n            return\n\n        areas = self.vennwidget.vennareas()\n        indices = [i for i, area in enumerate(areas)\n                   if area.isSelected()]\n\n        self.selection = indices\n\n        self.invalidateOutput()\n\n    def _on_useidentifiersChanged(self):\n        self.inputsBox.setEnabled(self.useidentifiers == 1)\n        # Invalidate all itemsets\n        self._invalidate()\n        self._updateItemsets()\n        self._createDiagram()\n\n        self._updateInfo()\n\n    def _on_inputAttrActivated(self, attr_index):\n        combo = self.sender()\n        # Find the input index to which the combo box belongs\n        # (they are reordered when removing inputs).\n        index = None\n        inputs = list(self.data.items())\n        for i in range(len(inputs)):\n            _, c = self._controlAtIndex(i)\n            if c is combo:\n                index = i\n                break\n\n        assert index is not None\n\n        key, _ = inputs[index]\n\n        self._invalidate([key])\n        self._updateItemsets()\n        self._createDiagram()\n\n    def _on_itemTextEdited(self, index, text):\n        text = str(text)\n        key = list(self.itemsets.keys())[index]\n        self.itemsets[key] = self.itemsets[key]._replace(title=text)\n\n    def invalidateOutput(self):\n        self.commit()\n\n    def commit(self):\n        selected_subsets = []\n        annotated_data = None\n        annotated_data_subsets = []\n        annotated_data_masks = []\n        selected_items = reduce(\n            set.union, [self.disjoint[index] for index in self.selection],\n            set()\n        )\n\n        def match(val):\n            if numpy.isnan(val):\n                return False\n            else:\n                return str(val) in selected_items\n\n        source_var = Orange.data.StringVariable(\"source\")\n        item_id_var = Orange.data.StringVariable(\"item_id\")\n\n        names = [itemset.title.strip() for itemset in self.itemsets.values()]\n        names = uniquify(names)\n\n        for i, (key, input) in enumerate(self.data.items()):\n            # cell vars are in functions that are only used in the loop\n            # pylint: disable=cell-var-from-loop\n            if not len(input.table):\n                continue\n            if self.useidentifiers:\n                attr = self.itemsetAttr(key)\n                if attr is not None:\n                    mask = list(map(match, (inst[attr] for inst in input.table)))\n                else:\n                    mask = [False] * len(input.table)\n\n                def instance_key(inst):\n                    return str(inst[attr])\n\n                def instance_key_all(inst):\n                    return str(inst[attr])\n            else:\n                mask = [ComparableInstance(inst) in selected_items\n                        for inst in input.table]\n                _map = {item: str(i) for i, item in enumerate(selected_items)}\n                _map_all = {\n                    ComparableInstance(i):  hash(ComparableInstance(i))\n                    for i in input.table\n                }\n\n                def instance_key(inst):\n                    return _map[ComparableInstance(inst)]\n\n                def instance_key_all(inst):\n                    return _map_all[ComparableInstance(inst)]\n\n            mask = numpy.array(mask, dtype=bool)\n            subset = input.table[mask]\n\n            annotated_subset = input.table\n            id_column = numpy.array(\n                [[instance_key_all(inst)] for inst in annotated_subset])\n            source_names = numpy.array([[names[i]]] * len(annotated_subset))\n            annotated_subset = append_column(\n                annotated_subset, \"M\", source_var, source_names)\n            annotated_subset = append_column(\n                annotated_subset, \"M\", item_id_var, id_column)\n            annotated_data_subsets.append(annotated_subset)\n            annotated_data_masks.append(mask)\n\n            if len(subset) == 0:\n                continue\n\n            # add columns with source table id and set id\n\n            if not self.output_duplicates:\n                id_column = numpy.array([[instance_key(inst)] for inst in subset],\n                                        dtype=object)\n                source_names = numpy.array([[names[i]]] * len(subset),\n                                           dtype=object)\n\n                subset = append_column(subset, \"M\", source_var, source_names)\n                subset = append_column(subset, \"M\", item_id_var, id_column)\n\n            selected_subsets.append(subset)\n\n        if selected_subsets and not self.output_duplicates:\n            data = table_concat(selected_subsets)\n            # Get all variables which are not constant between the same\n            # item set\n            varying = varying_between(data, item_id_var)\n\n            if source_var in varying:\n                varying.remove(source_var)\n\n            data = reshape_wide(data, varying, [item_id_var], [source_var])\n            # remove the temporary item set id column\n            data = drop_columns(data, [item_id_var])\n        elif selected_subsets:\n            data = table_concat(selected_subsets)\n        else:\n            data = None\n\n        if annotated_data_subsets:\n            annotated_data = table_concat(annotated_data_subsets)\n            indices = numpy.hstack(annotated_data_masks)\n            annotated_data = create_annotated_table(annotated_data, indices)\n            varying = varying_between(annotated_data, item_id_var)\n            if source_var in varying:\n                varying.remove(source_var)\n            annotated_data = reshape_wide(annotated_data, varying,\n                                          [item_id_var], [source_var])\n            annotated_data = drop_columns(annotated_data, [item_id_var])\n\n        self.Outputs.selected_data.send(data)\n        self.Outputs.annotated_data.send(annotated_data)\n\n    def getSettings(self, *args, **kwargs):\n        self._storeHints()\n        return super().getSettings(self, *args, **kwargs)\n\n    def send_report(self):\n        self.report_plot()\n\n\ndef pairwise(iterable):\n    \"\"\"\n    Return an iterator over consecutive pairs in `iterable`.\n\n    >>> list(pairwise([1, 2, 3, 4])\n    [(1, 2), (2, 3), (3, 4)]\n\n    \"\"\"\n    it = iter(iterable)\n    try:\n        first = next(it)\n    except StopIteration:\n        return\n    for second in it:\n        yield first, second\n        first = second\n\n\n# Custom domain comparison (domains do not seem to compare equal\n# even if they have exactly the same variables).\n# TODO: What about metas.\ndef domain_eq(d1, d2):\n    return d1.variables == d2.variables\n\n\n# Comparing/hashing Orange.data.Instance across domains ignoring metas.\nclass ComparableInstance:\n    __slots__ = [\"inst\", \"domain\", \"__hash\"]\n\n    def __init__(self, inst):\n        self.inst = inst\n        self.domain = inst.domain\n        self.__hash = hash((self.inst.x.data.tobytes(),\n                            self.inst.y.data.tobytes()))\n\n    def __hash__(self):\n        return self.__hash\n\n    def __eq__(self, other):\n        # XXX: comparing NaN with different payload\n        return (isinstance(other, ComparableInstance)\n                and domain_eq(self.domain, other.domain)\n                and self.inst.x.data.tobytes() == other.inst.x.data.tobytes()\n                and self.inst.y.data.tobytes() == other.inst.y.data.tobytes())\n\n    def __iter__(self):\n        return iter(self.inst)\n\n    def __repr__(self):\n        return repr(self.inst)\n\n    def __str__(self):\n        return str(self.inst)\n\n\ndef table_concat(tables):\n    \"\"\"\n    Concatenate a list of tables.\n\n    The resulting table will have a union of all attributes of `tables`.\n\n    \"\"\"\n    attributes = []\n    class_vars = []\n    metas = []\n    variables_seen = set()\n\n    for table in tables:\n        attributes.extend(v for v in table.domain.attributes\n                          if v not in variables_seen)\n        variables_seen.update(table.domain.attributes)\n\n        class_vars.extend(v for v in table.domain.class_vars\n                          if v not in variables_seen)\n        variables_seen.update(table.domain.class_vars)\n\n        metas.extend(v for v in table.domain.metas\n                     if v not in variables_seen)\n\n        variables_seen.update(table.domain.metas)\n\n    domain = Orange.data.Domain(attributes, class_vars, metas)\n\n    tables = [tab.transform(domain) for tab in tables]\n    return tables[0].concatenate(tables, axis=0)\n\n\ndef copy_descriptor(descriptor, newname=None):\n    \"\"\"\n    Create a copy of the descriptor.\n\n    If newname is not None the new descriptor will have the same\n    name as the input.\n\n    \"\"\"\n    if newname is None:\n        newname = descriptor.name\n\n    if descriptor.is_discrete:\n        newf = Orange.data.DiscreteVariable(\n            newname,\n            values=descriptor.values,\n            ordered=descriptor.ordered,\n        )\n        newf.attributes = dict(descriptor.attributes)\n\n    elif descriptor.is_continuous:\n        newf = Orange.data.ContinuousVariable(newname)\n        newf.number_of_decimals = descriptor.number_of_decimals\n        newf.attributes = dict(descriptor.attributes)\n\n    else:\n        newf = type(descriptor)(newname)\n        newf.attributes = dict(descriptor.attributes)\n\n    return newf\n\n\ndef reshape_wide(table, varlist, idvarlist, groupvarlist):\n    \"\"\"\n    Reshape a data table into a wide format.\n\n    :param Orange.data.Table table:\n        Source data table in long format.\n    :param varlist:\n        A list of variables to reshape.\n    :param list idvarlist:\n        A list of variables in `table` uniquely identifying multiple\n        records in `table` (i.e. subject id).\n    :param groupvarlist:\n        A list of variables differentiating multiple records\n        (i.e. conditions).\n\n    \"\"\"\n    def inst_key(inst, vars):\n        return tuple(str(inst[var]) for var in vars)\n\n    instance_groups = [inst_key(inst, groupvarlist) for inst in table]\n    # A list of groups (for each element in a group the varying variable\n    # will be duplicated)\n    groups = list(unique(instance_groups))\n    group_names = [\", \".join(group) for group in groups]\n\n    # A list of instance ids (subject ids)\n    # Each instance in the output will correspond to one of these ids)\n    instance_ids = [inst_key(inst, idvarlist) for inst in table]\n    ids = list(unique(instance_ids))\n\n    # an mapping from ids to an list of input instance indices\n    # each instance in this list belongs to one group (but not all\n    # groups need to be present).\n    inst_by_id = defaultdict(list)\n    id_by_inst = defaultdict(list)  # inverse mapping\n\n    for i in range(len(table)):\n        inst_id = instance_ids[i]\n        inst_by_id[inst_id].append(i)\n        id_by_inst[i] = inst_id\n\n    newfeatures = []\n    newclass_vars = []\n    newmetas = []\n    expanded_features = {}\n\n    def expanded(feat):\n        return [copy_descriptor(feat, newname=\"%s (%s)\" %\n                                (feat.name, group_name))\n                for group_name in group_names]\n\n    for feat in table.domain.attributes:\n        if feat in varlist:\n            features = expanded(feat)\n            newfeatures.extend(features)\n            expanded_features[feat] = dict(zip(groups, features))\n        elif feat not in groupvarlist:\n            newfeatures.append(feat)\n\n    for feat in table.domain.class_vars:\n        if feat in varlist:\n            features = expanded(feat)\n            newclass_vars.extend(features)\n            expanded_features[feat] = dict(zip(groups, features))\n        elif feat not in groupvarlist:\n            newclass_vars.append(feat)\n\n    for meta in table.domain.metas:\n        if meta in varlist:\n            metas = expanded(meta)\n            newmetas.extend(metas)\n            expanded_features[meta] = dict(zip(groups, metas))\n        elif meta not in groupvarlist:\n            newmetas.append(meta)\n\n    domain = Orange.data.Domain(newfeatures, newclass_vars, newmetas)\n    prototype_indices = [inst_by_id[inst_id][0] for inst_id in ids]\n    newtable = table[prototype_indices].transform(domain)\n    in_expanded = set(f for efd in expanded_features.values() for f in efd.values())\n\n    # Fill-in nan values\n    for var in domain.variables + domain.metas:\n        if var in idvarlist or var in in_expanded:\n            continue\n        col, _ = newtable.get_column_view(var)\n        nan_indices = (i for i in col.nonzero()[0]\n                       if isinstance(col[i], str) or numpy.isnan(col[i]))\n        for i in nan_indices:\n            for ind in inst_by_id[ids[i]]:\n                if not numpy.isnan(table[ind, var]):\n                    newtable[i, var] = table[ind, var]\n                    break\n\n    # Fill-in expanded features if any\n    for i, inst_id in enumerate(ids):\n        indices = inst_by_id[inst_id]\n        instance = newtable[i]\n\n        for index in indices:\n            source_inst = table[index]\n            group = instance_groups[index]\n            for source_var in varlist:\n                newf = expanded_features[source_var][group]\n                instance[newf] = source_inst[source_var]\n\n    return newtable\n\n\ndef unique(seq):\n    \"\"\"\n    Return an iterator over unique items of `seq`.\n\n    .. note:: Items must be hashable.\n\n    \"\"\"\n    seen = set()\n    for item in seq:\n        if item not in seen:\n            yield item\n            seen.add(item)\n\n\ndef unique_non_nan(ar):\n    # metas have sometimes object dtype, but values are numpy floats\n    ar = ar.astype('float64')\n    uniq = numpy.unique(ar)\n    return uniq[~numpy.isnan(uniq)]\n\n\ndef varying_between(table, idvar):\n    \"\"\"\n    Return a list of all variables with non constant values between\n    groups defined by `idvar`.\n\n    \"\"\"\n    all_possible = [var for var in table.domain.variables + table.domain.metas\n                    if var != idvar]\n    candidate_set = set(all_possible)\n\n    idmap = group_table_indices(table, idvar)\n\n    varying = set()\n    for indices in idmap.values():\n        subset = table[indices]\n        for var in list(candidate_set):\n            column, _ = subset.get_column_view(var)\n            with numpy.errstate(invalid=\"ignore\"):  # nans are removed below\n                values = util.unique(column)\n\n            if not var.is_string:\n                values = unique_non_nan(values)\n\n            if len(values) > 1:\n                varying.add(var)\n                candidate_set.remove(var)\n\n    return sorted(varying, key=all_possible.index)\n\n\ndef uniquify(strings):\n    \"\"\"\n    Return a list of unique strings.\n\n    The string at i'th position will have the same prefix as strings[i]\n    with an appended suffix to make the item unique (if necessary).\n\n    >>> uniquify([\"cat\", \"dog\", \"cat\"])\n    [\"cat 1\", \"dog\", \"cat 2\"]\n\n    \"\"\"\n    counter = Counter(strings)\n    counts = defaultdict(count)\n    newstrings = []\n    for string in strings:\n        if counter[string] > 1:\n            newstrings.append(string + (\" %i\" % (next(counts[string]) + 1)))\n        else:\n            newstrings.append(string)\n\n    return newstrings\n\n\ndef string_attributes(domain):\n    \"\"\"\n    Return all string attributes from the domain.\n    \"\"\"\n    return [attr for attr in domain.variables + domain.metas if attr.is_string]\n\n\ndef discrete_attributes(domain):\n    \"\"\"\n    Return all discrete attributes from the domain.\n    \"\"\"\n    return [attr for attr in domain.variables + domain.metas if attr.is_discrete]\n\n\ndef source_attributes(domain):\n    \"\"\"\n    Return all suitable attributes for the venn diagram.\n    \"\"\"\n    return string_attributes(domain)  # + discrete_attributes(domain)\n\n\ndef disjoint(sets):\n    \"\"\"\n    Return all disjoint subsets.\n    \"\"\"\n    sets = list(sets)\n    n = len(sets)\n    disjoint_sets = [None] * (2 ** n)\n    for i in range(2 ** n):\n        key = setkey(i, n)\n        included = [s for s, inc in zip(sets, key) if inc]\n        excluded = [s for s, inc in zip(sets, key) if not inc]\n        if any(included):\n            s = reduce(set.intersection, included)\n        else:\n            s = set()\n\n        s = reduce(set.difference, excluded, s)\n\n        disjoint_sets[i] = s\n\n    return disjoint_sets\n\n\ndef disjoint_set_label(i, n, simplify=False):\n    \"\"\"\n    Return a html formated label for a disjoint set indexed by `i`.\n    \"\"\"\n    intersection = unicodedata.lookup(\"INTERSECTION\")\n    # comp = unicodedata.lookup(\"COMPLEMENT\")  #\n    # This depends on the font but the unicode complement in\n    # general does not look nice in a super script so we use\n    # plain c instead.\n    comp = \"c\"\n\n    def label_for_index(i):\n        return chr(ord(\"A\") + i)\n\n    if simplify:\n        return \"\".join(label_for_index(i) for i, b in enumerate(setkey(i, n))\n                       if b)\n    else:\n        return intersection.join(label_for_index(i) +\n                                 (\"\" if b else \"<sup>\" + comp + \"</sup>\")\n                                 for i, b in enumerate(setkey(i, n)))\n\n\nclass VennSetItem(QGraphicsPathItem):\n    def __init__(self, parent=None, text=\"\", informativeText=\"\"):\n        super(VennSetItem, self).__init__(parent)\n        # Plain text title (editable by the VennDiagram)\n        self.text = text\n        # Extra informative text (possibly rich text)\n        self.informativeText = informativeText\n\n# TODO: Use palette's selected/highligted text / background colors to\n# indicate selection\n\nclass VennIntersectionArea(QGraphicsPathItem):\n    def __init__(self, parent=None, text=\"\"):\n        super().__init__(parent)\n        self.setAcceptHoverEvents(True)\n        self.setPen(QPen(Qt.NoPen))\n\n        self.text = QGraphicsTextItem(self)\n        layout = self.text.document().documentLayout()\n        layout.documentSizeChanged.connect(self._onLayoutChanged)\n\n        self._text = \"\"\n        self._anchor = QPointF()\n\n    def setText(self, text):\n        if self._text != text:\n            self._text = text\n            self.text.setPlainText(text)\n\n    def setTextAnchor(self, pos):\n        if self._anchor != pos:\n            self._anchor = pos\n            self._updateTextAnchor()\n\n    def hoverEnterEvent(self, event):\n        self.setZValue(self.zValue() + 1)\n        return QGraphicsPathItem.hoverEnterEvent(self, event)\n\n    def hoverLeaveEvent(self, event):\n        self.setZValue(self.zValue() - 1)\n        return QGraphicsPathItem.hoverLeaveEvent(self, event)\n\n    def mousePressEvent(self, event):\n        if event.button() == Qt.LeftButton:\n            if event.modifiers() & Qt.AltModifier:\n                self.setSelected(False)\n            elif event.modifiers() & Qt.ControlModifier:\n                self.setSelected(not self.isSelected())\n            elif event.modifiers() & Qt.ShiftModifier:\n                self.setSelected(True)\n            else:\n                for area in self.parentWidget().vennareas():\n                    area.setSelected(False)\n                self.setSelected(True)\n\n    def mouseReleaseEvent(self, event):\n        pass\n\n    def paint(self, painter, option, widget=None):\n        painter.save()\n        path = self.path()\n        brush = QBrush(self.brush())\n        pen = QPen(self.pen())\n\n        if option.state & QStyle.State_Selected:\n            pen.setColor(Qt.red)\n            brush.setStyle(Qt.DiagCrossPattern)\n            brush.setColor(QColor(40, 40, 40, 100))\n\n        elif option.state & QStyle.State_MouseOver:\n            pen.setColor(Qt.blue)\n\n        if option.state & QStyle.State_MouseOver:\n            brush.setColor(QColor(100, 100, 100, 100))\n            if brush.style() == Qt.NoBrush:\n                # Make sure the highlight is actually visible.\n                brush.setStyle(Qt.SolidPattern)\n\n        painter.setPen(pen)\n        painter.setBrush(brush)\n        painter.drawPath(path)\n        painter.restore()\n\n    def itemChange(self, change, value):\n        if change == QGraphicsPathItem.ItemSelectedHasChanged:\n            self.setZValue(self.zValue() + (1 if value else -1))\n        return QGraphicsPathItem.itemChange(self, change, value)\n\n    def _updateTextAnchor(self):\n        rect = self.text.boundingRect()\n        pos = anchor_rect(rect, self._anchor)\n        self.text.setPos(pos)\n\n    def _onLayoutChanged(self):\n        self._updateTextAnchor()\n\n\nclass GraphicsTextEdit(QGraphicsTextItem):\n    #: Edit triggers\n    NoEditTriggers, DoubleClicked = 0, 1\n\n    editingFinished = Signal()\n    editingStarted = Signal()\n\n    documentSizeChanged = Signal()\n\n    def __init__(self, *args, **kwargs):\n        super(GraphicsTextEdit, self).__init__(*args, **kwargs)\n        self.setCursor(Qt.IBeamCursor)\n        self.setTabChangesFocus(True)\n        self._edittrigger = GraphicsTextEdit.DoubleClicked\n        self._editing = False\n        self.document().documentLayout().documentSizeChanged.connect(\n            self.documentSizeChanged\n        )\n\n    def mouseDoubleClickEvent(self, event):\n        super(GraphicsTextEdit, self).mouseDoubleClickEvent(event)\n        if self._edittrigger == GraphicsTextEdit.DoubleClicked:\n            self._start()\n\n    def focusOutEvent(self, event):\n        super(GraphicsTextEdit, self).focusOutEvent(event)\n\n        if self._editing:\n            self._end()\n\n    def _start(self):\n        self._editing = True\n        self.setTextInteractionFlags(Qt.TextEditorInteraction)\n        self.setFocus(Qt.MouseFocusReason)\n        self.editingStarted.emit()\n\n    def _end(self):\n        self._editing = False\n        self.setTextInteractionFlags(Qt.NoTextInteraction)\n        self.editingFinished.emit()\n\n\nclass VennDiagram(QGraphicsWidget):\n    # rect and petal are for future work\n    Circle, Ellipse, Rect, Petal = 1, 2, 3, 4\n\n    TitleFormat = \"<center><h4>{0}</h4>{1}</center>\"\n\n    selectionChanged = Signal()\n    itemTextEdited = Signal(int, str)\n\n    def __init__(self, parent=None):\n        super(VennDiagram, self).__init__(parent)\n        self.shapeType = VennDiagram.Circle\n\n        self._setup()\n\n    def _setup(self):\n        self._items = []\n        self._vennareas = []\n        self._textitems = []\n\n    def item(self, index):\n        return self._items[index]\n\n    def items(self):\n        return list(self._items)\n\n    def count(self):\n        return len(self._items)\n\n    def setItems(self, items):\n        if self._items:\n            self.clear()\n\n        self._items = list(items)\n\n        for item in self._items:\n            item.setParentItem(self)\n            item.setVisible(True)\n\n        fmt = self.TitleFormat.format\n\n        font = self.font()\n        font.setPixelSize(14)\n\n        for item in items:\n            text = GraphicsTextEdit(self)\n            text.setFont(font)\n            text.setDefaultTextColor(QColor(\"#333\"))\n            text.setHtml(fmt(escape(item.text), item.informativeText))\n            text.adjustSize()\n            text.editingStarted.connect(self._on_editingStarted)\n            text.editingFinished.connect(self._on_editingFinished)\n            text.documentSizeChanged.connect(\n                self._on_itemTextSizeChanged\n            )\n\n            self._textitems.append(text)\n\n        self._vennareas = [\n            VennIntersectionArea(parent=self)\n            for i in range(2 ** len(items))\n        ]\n        self._subsettextitems = [\n            QGraphicsTextItem(parent=self)\n            for i in range(2 ** len(items))\n        ]\n\n        self._updateLayout()\n\n    def clear(self):\n        scene = self.scene()\n        items = self.vennareas() + list(self.items()) + self._textitems\n\n        for item in self._textitems:\n            item.editingStarted.disconnect(self._on_editingStarted)\n            item.editingFinished.disconnect(self._on_editingFinished)\n            item.documentSizeChanged.disconnect(\n                self._on_itemTextSizeChanged\n            )\n\n        self._items = []\n        self._vennareas = []\n        self._textitems = []\n\n        for item in items:\n            item.setVisible(False)\n            item.setParentItem(None)\n            if scene is not None:\n                scene.removeItem(item)\n\n    def vennareas(self):\n        return list(self._vennareas)\n\n    def setFont(self, font):\n        if font != self.font():\n            self.prepareGeometryChange()\n            super().setFont(font)\n\n            for item in self.items():\n                item.setFont(font)\n\n    def _updateLayout(self):\n        rect = self.geometry()\n        n = len(self._items)\n        if not n:\n            return\n\n        regions = venn_diagram(n, shape=self.shapeType)\n\n        # The y axis in Qt points downward\n        transform = QTransform().scale(1, -1)\n        regions = list(map(transform.map, regions))\n\n        union_brect = reduce(QRectF.united,\n                             (path.boundingRect() for path in regions))\n\n        scalex = rect.width() / union_brect.width()\n        scaley = rect.height() / union_brect.height()\n        scale = min(scalex, scaley)\n\n        transform = QTransform().scale(scale, scale)\n\n        regions = [transform.map(path) for path in regions]\n\n        center = rect.width() / 2, rect.height() / 2\n        for item, path in zip(self.items(), regions):\n            item.setPath(path)\n            item.setPos(*center)\n\n        intersections = venn_intersections(regions)\n        assert len(intersections) == 2 ** n\n        assert len(self.vennareas()) == 2 ** n\n\n        anchors = [(0, 0)] + subset_anchors(self._items)\n\n        anchor_transform = QTransform().scale(rect.width(), -rect.height())\n        for i, area in enumerate(self.vennareas()):\n            area.setPath(intersections[setkey(i, n)])\n            area.setPos(*center)\n            x, y = anchors[i]\n            anchor = anchor_transform.map(QPointF(x, y))\n            area.setTextAnchor(anchor)\n            area.setZValue(30)\n\n        self._updateTextAnchors()\n\n    def _updateTextAnchors(self):\n        n = len(self._items)\n\n        items = self._items\n        dist = 15\n\n        shape = reduce(QPainterPath.united, [item.path() for item in items])\n        brect = shape.boundingRect()\n        bradius = max(brect.width() / 2, brect.height() / 2)\n\n        center = self.boundingRect().center()\n\n        anchors = _category_anchors(items)\n        self._textanchors = []\n        for angle, anchor_h, anchor_v in anchors:\n            line = QLineF.fromPolar(bradius, angle)\n            ext = QLineF.fromPolar(dist, angle)\n            line = QLineF(line.p1(), line.p2() + ext.p2())\n            line = line.translated(center)\n\n            anchor_pos = line.p2()\n            self._textanchors.append((anchor_pos, anchor_h, anchor_v))\n\n        for i in range(n):\n            self._updateTextItemPos(i)\n\n    def _updateTextItemPos(self, i):\n        item = self._textitems[i]\n        anchor_pos, anchor_h, anchor_v = self._textanchors[i]\n        rect = item.boundingRect()\n        pos = anchor_rect(rect, anchor_pos, anchor_h, anchor_v)\n        item.setPos(pos)\n\n    def setGeometry(self, geometry):\n        super(VennDiagram, self).setGeometry(geometry)\n        self._updateLayout()\n\n    def _on_editingStarted(self):\n        item = self.sender()\n        index = self._textitems.index(item)\n        text = self._items[index].text\n        item.setTextWidth(-1)\n        item.setHtml(self.TitleFormat.format(escape(text), \"<br/>\"))\n\n    def _on_editingFinished(self):\n        item = self.sender()\n        index = self._textitems.index(item)\n        text = item.toPlainText()\n        if text != self._items[index].text:\n            self._items[index].text = text\n\n            self.itemTextEdited.emit(index, text)\n\n        item.setHtml(\n            self.TitleFormat.format(\n                escape(text), self._items[index].informativeText))\n        item.adjustSize()\n\n    def _on_itemTextSizeChanged(self):\n        item = self.sender()\n        index = self._textitems.index(item)\n        self._updateTextItemPos(index)\n\n\ndef anchor_rect(rect, anchor_pos,\n                anchor_h=Qt.AnchorHorizontalCenter,\n                anchor_v=Qt.AnchorVerticalCenter):\n\n    if anchor_h == Qt.AnchorLeft:\n        x = anchor_pos.x()\n    elif anchor_h == Qt.AnchorHorizontalCenter:\n        x = anchor_pos.x() - rect.width() / 2\n    elif anchor_h == Qt.AnchorRight:\n        x = anchor_pos.x() - rect.width()\n    else:\n        raise ValueError(anchor_h)\n\n    if anchor_v == Qt.AnchorTop:\n        y = anchor_pos.y()\n    elif anchor_v == Qt.AnchorVerticalCenter:\n        y = anchor_pos.y() - rect.height() / 2\n    elif anchor_v == Qt.AnchorBottom:\n        y = anchor_pos.y() - rect.height()\n    else:\n        raise ValueError(anchor_v)\n\n    return QPointF(x, y)\n\n\ndef radians(angle):\n    return 2 * math.pi * angle / 360\n\n\ndef unit_point(x, r=1.0):\n    x = radians(x)\n    return (r * math.cos(x), r * math.sin(x))\n\n\ndef _category_anchors(shapes):\n    n = len(shapes)\n    return _CATEGORY_ANCHORS[n - 1]\n\n\n# (angle, horizontal anchor, vertical anchor)\n_CATEGORY_ANCHORS = (\n    # n == 1\n    ((90, Qt.AnchorHorizontalCenter, Qt.AnchorBottom),),\n    # n == 2\n    ((180, Qt.AnchorRight, Qt.AnchorVerticalCenter),\n     (0, Qt.AnchorLeft, Qt.AnchorVerticalCenter)),\n    # n == 3\n    ((150, Qt.AnchorRight, Qt.AnchorBottom),\n     (30, Qt.AnchorLeft, Qt.AnchorBottom),\n     (270, Qt.AnchorHorizontalCenter, Qt.AnchorTop)),\n    # n == 4\n    ((270 + 45, Qt.AnchorLeft, Qt.AnchorTop),\n     (270 - 45, Qt.AnchorRight, Qt.AnchorTop),\n     (90 - 15, Qt.AnchorLeft, Qt.AnchorBottom),\n     (90 + 15, Qt.AnchorRight, Qt.AnchorBottom)),\n    # n == 5\n    ((90 - 5, Qt.AnchorHorizontalCenter, Qt.AnchorBottom),\n     (18 - 5, Qt.AnchorLeft, Qt.AnchorVerticalCenter),\n     (306 - 5, Qt.AnchorLeft, Qt.AnchorTop),\n     (234 - 5, Qt.AnchorRight, Qt.AnchorTop),\n     (162 - 5, Qt.AnchorRight, Qt.AnchorVerticalCenter),)\n)\n\n\ndef subset_anchors(shapes):\n    n = len(shapes)\n    if n == 1:\n        return [(0, 0)]\n    elif n == 2:\n        return [unit_point(180, r=1/3),\n                unit_point(0, r=1/3),\n                (0, 0)]\n    elif n == 3:\n        return [unit_point(150, r=0.35),  # A\n                unit_point(30, r=0.35),   # B\n                unit_point(90, r=0.27),   # AB\n                unit_point(270, r=0.35),  # C\n                unit_point(210, r=0.27),  # AC\n                unit_point(330, r=0.27),  # BC\n                unit_point(0, r=0),]      # ABC\n    elif n == 4:\n        anchors = [\n            (0.400, 0.110),    # A\n            (-0.400, 0.110),   # B\n            (0.000, -0.285),   # AB\n            (0.180, 0.330),    # C\n            (0.265, 0.205),    # AC\n            (-0.240, -0.110),  # BC\n            (-0.100, -0.190),  # ABC\n            (-0.180, 0.330),   # D\n            (0.240, -0.110),   # AD\n            (-0.265, 0.205),   # BD\n            (0.100, -0.190),   # ABD\n            (0.000, 0.250),    # CD\n            (0.153, 0.090),    # ACD\n            (-0.153, 0.090),   # BCD\n            (0.000, -0.060),   # ABCD\n        ]\n        return anchors\n\n    elif n == 5:\n        anchors = [None] * 32\n        # Base anchors\n        A = (0.033, 0.385)\n        AD = (0.095, 0.250)\n        AE = (-0.100, 0.265)\n        ACE = (-0.130, 0.220)\n        ADE = (0.010, 0.225)\n        ACDE = (-0.095, 0.175)\n        ABCDE = (0.0, 0.0)\n\n        anchors[-1] = ABCDE\n\n        bases = [(0b00001, A),\n                 (0b01001, AD),\n                 (0b10001, AE),\n                 (0b10101, ACE),\n                 (0b11001, ADE),\n                 (0b11101, ACDE)]\n\n        for i in range(5):\n            for index, anchor in bases:\n                index = bit_rot_left(index, i, bits=5)\n                assert anchors[index] is None\n                anchors[index] = rotate_point(anchor, - 72 * i)\n\n        assert all(anchors[1:])\n        return anchors[1:]\n    return None\n\n\ndef bit_rot_left(x, y, bits=32):\n    mask = 2 ** bits - 1\n    x_masked = x & mask\n    return (x << y) & mask | (x_masked >> bits - y)\n\n\ndef rotate_point(p, angle):\n    r = radians(angle)\n    R = numpy.array([[math.cos(r), -math.sin(r)],\n                     [math.sin(r), math.cos(r)]])\n    x, y = numpy.dot(R, p)\n    return (float(x), float(y))\n\n\ndef line_extended(line, distance):\n    \"\"\"\n    Return an QLineF extended by `distance` units in the positive direction.\n    \"\"\"\n    angle = line.angle() / 360 * 2 * math.pi\n    dx, dy = unit_point(angle, r=distance)\n    return QLineF(line.p1(), line.p2() + QPointF(dx, dy))\n\n\ndef circle_path(center, r=1.0):\n    return ellipse_path(center, r, r, rotation=0)\n\n\ndef ellipse_path(center, a, b, rotation=0):\n    if not isinstance(center, QPointF):\n        center = QPointF(*center)\n\n    brect = QRectF(-a, -b, 2 * a, 2 * b)\n\n    path = QPainterPath()\n    path.addEllipse(brect)\n\n    if rotation != 0:\n        transform = QTransform().rotate(rotation)\n        path = transform.map(path)\n\n    path.translate(center)\n    return path\n\n\n# TODO: Should include anchors for text layout (both inside and outside).\n# for each item {path: QPainterPath,\n#                text_anchors: [{center}] * (2 ** n)\n#                mayor_axis: QLineF,\n#                boundingRect QPolygonF (with 4 vertices)}\n#\n# Should be a new class with overloads for ellipse/circle, rect, and petal\n# shapes, should store all constructor parameters, rotation, center,\n# mayor/minor axis.\n\n\ndef venn_diagram(n, shape=VennDiagram.Circle):\n    if n < 1 or n > 5:\n        raise ValueError()\n\n    paths = []\n\n    if n == 1:\n        paths = [circle_path(center=(0, 0), r=0.5)]\n    elif n == 2:\n        angles = [180, 0]\n        paths = [circle_path(center=unit_point(x, r=1/6), r=1/3)\n                 for x in angles]\n    elif n == 3:\n        angles = [150 - 120 * i for i in range(3)]\n        paths = [circle_path(center=unit_point(x, r=1/6), r=1/3)\n                 for x in angles]\n    elif n == 4:\n        # Constants shamelessly stolen from VennDiagram R package\n        paths = [\n            ellipse_path((0.65 - 0.5, 0.47 - 0.5), 0.35, 0.20, 45),\n            ellipse_path((0.35 - 0.5, 0.47 - 0.5), 0.35, 0.20, 135),\n            ellipse_path((0.5 - 0.5, 0.57 - 0.5), 0.35, 0.20, 45),\n            ellipse_path((0.5 - 0.5, 0.57 - 0.5), 0.35, 0.20, 134),\n        ]\n    elif n == 5:\n        # Constants shamelessly stolen from VennDiagram R package\n        d = 0.13\n        a, b = 0.24, 0.48\n        a, b = b, a\n        a, b = 0.48, 0.24\n        paths = [ellipse_path(unit_point((1 - i) * 72, r=d),\n                              a, b, rotation=90 - (i * 72))\n                 for i in range(5)]\n\n    return paths\n\n\ndef setkey(intval, n):\n    return tuple(bool(intval & (2 ** i)) for i in range(n))\n\n\ndef keyrange(n):\n    if n < 0:\n        raise ValueError()\n\n    for i in range(2 ** n):\n        yield setkey(i, n)\n\n\ndef venn_intersections(paths):\n    n = len(paths)\n    return {key: venn_intersection(paths, key) for key in keyrange(n)}\n\n\ndef venn_intersection(paths, key):\n    if not any(key):\n        return QPainterPath()\n\n    # first take the intersection of all included paths\n    path = reduce(QPainterPath.intersected,\n                  (path for path, included in zip(paths, key) if included))\n\n    # subtract all the excluded sets (i.e. take the intersection\n    # with the excluded set complements)\n    path = reduce(QPainterPath.subtracted,\n                  (path for path, included in zip(paths, key) if not included),\n                  path)\n\n    return path\n\n\ndef append_column(data, where, variable, column):\n    X, Y, M = data.X, data.Y, data.metas\n    domain = data.domain\n    attr = domain.attributes\n    class_vars = domain.class_vars\n    metas = domain.metas\n    if where == \"X\":\n        attr = attr + (variable,)\n        X = numpy.hstack((X, column))\n    elif where == \"Y\":\n        class_vars = class_vars + (variable,)\n        Y = numpy.hstack((Y, column))\n    elif where == \"M\":\n        metas = metas + (variable,)\n        M = numpy.hstack((M, column))\n    else:\n        raise ValueError\n    domain = Orange.data.Domain(attr, class_vars, metas)\n    new_data = data.transform(domain)\n    new_data[:, variable] = column\n    return new_data\n\n\ndef drop_columns(data, columns):\n    columns = set(data.domain[col] for col in columns)\n\n    def filter_vars(vars):\n        return tuple(var for var in vars if var not in columns)\n\n    domain = Orange.data.Domain(\n        filter_vars(data.domain.attributes),\n        filter_vars(data.domain.class_vars),\n        filter_vars(data.domain.metas)\n    )\n    return Orange.data.Table.from_table(domain, data)\n\n\ndef group_table_indices(table, key_var):\n    \"\"\"\n    Group table indices based on values of selected columns (`key_vars`).\n\n    Return a dictionary mapping all unique value combinations (keys)\n    into a list of indices in the table where they are present.\n    \"\"\"\n    groups = defaultdict(list)\n    for i, inst in enumerate(table):\n        groups[str(inst[key_var])].append(i)\n    return groups\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    from Orange.evaluation import ShuffleSplit\n\n    data = Orange.data.Table(\"brown-selected\")\n    # data1 = Orange.data.Table(\"brown-selected\")\n    # datasets = [(data, 1), (data, 2)]\n\n    data = append_column(data, \"M\", Orange.data.StringVariable(\"Test\"),\n                         numpy.arange(len(data)).reshape(-1, 1) % 30)\n    res = ShuffleSplit(data, [None], n_resamples=5,\n                       test_size=0.7, stratified=False)\n    indices = iter(res.indices)\n    datasets = []\n    for i in range(1, 6):\n        sample, _ = next(indices)\n        data1 = data[sample]\n        data1.name = chr(ord(\"A\") + i)\n        datasets.append((data1, i))\n\n    WidgetPreview(OWVennDiagram).run(setData=datasets)\n",
            "header": {
                "file": {
                    "path": "Orange/widgets/visualize/owvenndiagram.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "528bbf5ac",
                "new_commit_hash": "58b1d5077",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/widgets/visualize/owvenndiagram.py"
            },
            "plus_file": {
                "path": "b/Orange/widgets/visualize/owvenndiagram.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 210,
                            "length": 6
                        },
                        "new_range": {
                            "start": 210,
                            "length": 10
                        },
                        "section": "class OWVennDiagram(widget.OWWidget):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        del self._queue[:]",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        self._createDiagram()",
                                "type": "context"
                            },
                            {
                                "content": "        # If autocommit is enabled, _createDiagram already outputs data",
                                "type": "added"
                            },
                            {
                                "content": "        # If not, call unconditional_commit from here",
                                "type": "added"
                            },
                            {
                                "content": "        if not self.autocommit:",
                                "type": "added"
                            },
                            {
                                "content": "            self.unconditional_commit()",
                                "type": "added"
                            },
                            {
                                "content": "        if self.data:",
                                "type": "context"
                            },
                            {
                                "content": "            self.infolabel.setText(f\"{len(self.data)} datasets on input.\\n\")",
                                "type": "context"
                            },
                            {
                                "content": "        else:",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/visualize/owvenndiagram.py",
                            "type": "method",
                            "name": "OWVennDiagram.handleNewSignals",
                            "content": "class OWVennDiagram(widget.OWWidget):\n    name = \"Venn Diagram\"\n    description = \"A graphical visualization of the overlap of data instances \" \\\n                  \"from a collection of input datasets.\"\n    icon = \"icons/VennDiagram.svg\"\n    priority = 280\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table, multiple=True)\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    inputhints: dict\n    selection: list\n\n    # Selected disjoint subset indices\n    selection = settings.Setting([])\n    #: Stored input set hints\n    #: {(index, inputname, attributes): (selectedattrname, itemsettitle)}\n    #: The 'selectedattrname' can be None\n    inputhints = settings.Setting({})\n    #: Use identifier columns for instance matching\n    useidentifiers = settings.Setting(True)\n    #: Output unique items (one output row for every unique instance `key`)\n    #: or preserve all duplicates in the output.\n    output_duplicates = settings.Setting(False)\n    autocommit = settings.Setting(True)\n\n    graph_name = \"scene\"\n\n    def __init__(self):\n        super().__init__()\n\n        # Diagram update is in progress\n        self._updating = False\n        # Input update is in progress\n        self._inputUpdate = False\n        # All input tables have the same domain.\n        self.samedomain = True\n        # Input datasets in the order they were 'connected'.\n        self.data = OrderedDict()\n        # Extracted input item sets in the order they were 'connected'\n        self.itemsets = OrderedDict()\n\n        # GUI\n        box = gui.vBox(self.controlArea, \"Info\")\n        self.infolabel = gui.widgetLabel(box, \"No data on input.\\n\")\n\n        self.identifiersBox = gui.radioButtonsInBox(\n            self.controlArea, self, \"useidentifiers\", [],\n            box=\"Data Instance Identifiers\",\n            callback=self._on_useidentifiersChanged\n        )\n        self.useequalityButton = gui.appendRadioButton(\n            self.identifiersBox, \"Use instance equality\"\n        )\n        self.useidentifiersButton = rb = gui.appendRadioButton(\n            self.identifiersBox, \"Use identifiers\"\n        )\n        self.inputsBox = gui.indentedBox(\n            self.identifiersBox, sep=gui.checkButtonOffsetHint(rb)\n        )\n        self.inputsBox.setEnabled(bool(self.useidentifiers))\n\n        for i in range(5):\n            box = gui.vBox(self.inputsBox, \"Dataset #%i\" % (i + 1),\n                           addSpace=False)\n            box.setFlat(True)\n            model = itemmodels.VariableListModel(parent=self)\n            cb = gui.OrangeComboBox(\n                minimumContentsLength=12,\n                sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon\n            )\n            cb.setModel(model)\n            cb.activated[int].connect(self._on_inputAttrActivated)\n            box.setEnabled(False)\n            # Store the combo in the box for later use.\n            box.combo_box = cb\n            box.layout().addWidget(cb)\n\n        gui.rubber(self.controlArea)\n\n        box = gui.vBox(self.controlArea, \"Output\")\n        gui.checkBox(box, self, \"output_duplicates\", \"Output duplicates\",\n                     callback=lambda: self.commit())\n        gui.auto_commit(box, self, \"autocommit\", \"Send Selection\", \"Send Automatically\", box=False)\n\n        # Main area view\n        self.scene = QGraphicsScene()\n        self.view = QGraphicsView(self.scene)\n        self.view.setRenderHint(QPainter.Antialiasing)\n        self.view.setBackgroundRole(QPalette.Window)\n        self.view.setFrameStyle(QGraphicsView.StyledPanel)\n\n        self.mainArea.layout().addWidget(self.view)\n        self.vennwidget = VennDiagram()\n        self.vennwidget.resize(400, 400)\n        self.vennwidget.itemTextEdited.connect(self._on_itemTextEdited)\n        self.scene.selectionChanged.connect(self._on_selectionChanged)\n\n        self.scene.addItem(self.vennwidget)\n\n        self.resize(self.controlArea.sizeHint().width() + 550,\n                    max(self.controlArea.sizeHint().height(), 550))\n\n        self._queue = []\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data, key=None):\n        self.error()\n        if not self._inputUpdate:\n            # Store hints only on the first setData call.\n            self._storeHints()\n            self._inputUpdate = True\n\n        if key in self.data:\n            if data is None:\n                # Remove the input\n                self._remove(key)\n            else:\n                # Update existing item\n                self._update(key, data)\n        elif data is not None:\n            # TODO: Allow setting more them 5 inputs and let the user\n            # select the 5 to display.\n            if len(self.data) == 5:\n                self.error(\"Venn diagram accepts at most five datasets.\")\n                return\n            # Add a new input\n            self._add(key, data)\n\n    def handleNewSignals(self):\n        self._inputUpdate = False\n\n        # Check if all inputs are from the same domain.\n        domains = [input.table.domain for input in self.data.values()]\n        samedomain = all(domain_eq(d1, d2) for d1, d2 in pairwise(domains))\n\n        self.samedomain = samedomain\n\n        has_identifiers = all(source_attributes(input.table.domain)\n                              for input in self.data.values())\n        has_any_identifiers = any(source_attributes(input.table.domain)\n                                  for input in self.data.values())\n        self.useequalityButton.setEnabled(samedomain)\n        self.useidentifiersButton.setEnabled(\n            has_any_identifiers or len(self.data) == 0)\n        self.inputsBox.setEnabled(has_any_identifiers)\n\n        if not samedomain and has_any_identifiers and not self.useidentifiers:\n            self.useidentifiers = 1\n        elif samedomain and not has_identifiers:\n            self.useidentifiers = 0\n\n        incremental = all(inc for _, inc in self._queue)\n\n        if incremental:\n            # Only received updated data on existing link.\n            self._updateItemsets()\n        else:\n            # Links were removed and/or added.\n            self._createItemsets()\n            self._restoreHints()\n            self._updateItemsets()\n\n        del self._queue[:]\n\n        self._createDiagram()\n        # If autocommit is enabled, _createDiagram already outputs data\n        # If not, call unconditional_commit from here\n        if not self.autocommit:\n            self.unconditional_commit()\n        if self.data:\n            self.infolabel.setText(f\"{len(self.data)} datasets on input.\\n\")\n        else:\n            self.infolabel.setText(\"No data on input\\n\")\n\n        self._updateInfo()\n        super().handleNewSignals()\n\n    def _invalidate(self, keys=None, incremental=True):\n        \"\"\"\n        Invalidate input for a list of input keys.\n        \"\"\"\n        if keys is None:\n            keys = list(self.data.keys())\n\n        self._queue.extend((key, incremental) for key in keys)\n\n    def itemsetAttr(self, key):\n        index = list(self.data.keys()).index(key)\n        _, combo = self._controlAtIndex(index)\n        model = combo.model()\n        attr_index = combo.currentIndex()\n        if attr_index >= 0:\n            return model[attr_index]\n        else:\n            return None\n\n    def _controlAtIndex(self, index):\n        group_box = self.inputsBox.layout().itemAt(index).widget()\n        combo = group_box.combo_box\n        return group_box, combo\n\n    def _setAttributes(self, index, attrs):\n        box, combo = self._controlAtIndex(index)\n        model = combo.model()\n\n        if attrs is None:\n            model[:] = []\n            box.setEnabled(False)\n        else:\n            if model[:] != attrs:\n                model[:] = attrs\n\n            box.setEnabled(True)\n\n    def _add(self, key, table):\n        name = table.name\n        index = len(self.data)\n        attrs = source_attributes(table.domain)\n\n        self.data[key] = _InputData(key, name, table)\n\n        self._setAttributes(index, attrs)\n\n        self._invalidate([key], incremental=False)\n\n        item = self.inputsBox.layout().itemAt(index)\n        box = item.widget()\n        box.setTitle(\"Dataset: {}\".format(name))\n\n    def _remove(self, key):\n        index = list(self.data.keys()).index(key)\n\n        # Clear possible warnings.\n        self.warning()\n\n        self._setAttributes(index, None)\n\n        del self.data[key]\n\n        layout = self.inputsBox.layout()\n        item = layout.takeAt(index)\n        layout.addItem(item)\n        inputs = list(self.data.values())\n\n        for i in range(5):\n            box, _ = self._controlAtIndex(i)\n            if i < len(inputs):\n                title = \"Dataset: {}\".format(inputs[i].name)\n            else:\n                title = \"Dataset #{}\".format(i + 1)\n            box.setTitle(title)\n\n        self._invalidate([key], incremental=False)\n\n    def _update(self, key, table):\n        name = table.name\n        index = list(self.data.keys()).index(key)\n        attrs = source_attributes(table.domain)\n\n        self.data[key] = self.data[key]._replace(name=name, table=table)\n\n        self._setAttributes(index, attrs)\n        self._invalidate([key])\n\n        item = self.inputsBox.layout().itemAt(index)\n        box = item.widget()\n        box.setTitle(\"Dataset: {}\".format(name))\n\n    def _itemsForInput(self, key):\n        useidentifiers = self.useidentifiers or not self.samedomain\n\n        def items_by_key(key, input):\n            attr = self.itemsetAttr(key)\n            if attr is not None:\n                return [str(inst[attr]) for inst in input.table\n                        if not numpy.isnan(inst[attr])]\n            else:\n                return []\n\n        def items_by_eq(key, input):\n            return list(map(ComparableInstance, input.table))\n\n        input = self.data[key]\n        if useidentifiers:\n            items = items_by_key(key, input)\n        else:\n            items = items_by_eq(key, input)\n        return items\n\n    def _updateItemsets(self):\n        assert list(self.data.keys()) == list(self.itemsets.keys())\n        for key, input in list(self.data.items()):\n            items = self._itemsForInput(key)\n            item = self.itemsets[key]\n            item = item._replace(items=items)\n            name = input.name\n            if item.name != name:\n                item = item._replace(name=name, title=name)\n            self.itemsets[key] = item\n\n    def _createItemsets(self):\n        olditemsets = dict(self.itemsets)\n        self.itemsets.clear()\n\n        for key, input in self.data.items():\n            items = self._itemsForInput(key)\n            name = input.name\n            if key in olditemsets and olditemsets[key].name == name:\n                # Reuse the title (which might have been changed by the user)\n                title = olditemsets[key].title\n            else:\n                title = name\n\n            itemset = _ItemSet(key=key, name=name, title=title, items=items)\n            self.itemsets[key] = itemset\n\n    def _storeHints(self):\n        if self.data:\n            self.inputhints.clear()\n            for i, (key, input) in enumerate(self.data.items()):\n                attrs = source_attributes(input.table.domain)\n                attrs = tuple(attr.name for attr in attrs)\n                selected = self.itemsetAttr(key)\n                if selected is not None:\n                    attr_name = selected.name\n                else:\n                    attr_name = None\n                itemset = self.itemsets[key]\n                self.inputhints[(i, input.name, attrs)] = \\\n                    (attr_name, itemset.title)\n\n    def _restoreHints(self):\n        settings = []\n        for i, (key, input) in enumerate(self.data.items()):\n            attrs = source_attributes(input.table.domain)\n            attrs = tuple(attr.name for attr in attrs)\n            hint = self.inputhints.get((i, input.name, attrs), None)\n            if hint is not None:\n                attr, name = hint\n                attr_ind = attrs.index(attr) if attr is not None else -1\n                settings.append((attr_ind, name))\n            else:\n                return\n\n        # all inputs match the stored hints\n        for i, key in enumerate(self.itemsets):\n            attr, itemtitle = settings[i]\n            self.itemsets[key] = self.itemsets[key]._replace(title=itemtitle)\n            _, cb = self._controlAtIndex(i)\n            cb.setCurrentIndex(attr)\n\n    def _createDiagram(self):\n        self._updating = True\n\n        oldselection = list(self.selection)\n\n        self.vennwidget.clear()\n        n = len(self.itemsets)\n        self.disjoint = disjoint(set(s.items) for s in self.itemsets.values())\n\n        vennitems = []\n        colors = colorpalette.ColorPaletteHSV(n)\n\n        for i, (_, item) in enumerate(self.itemsets.items()):\n            count = len(set(item.items))\n            count_all = len(item.items)\n            if count != count_all:\n                fmt = '{} <i>(all: {})</i>'\n            else:\n                fmt = '{}'\n            counts = fmt.format(count, count_all)\n            gr = VennSetItem(text=item.title, informativeText=counts)\n            color = colors[i]\n            color.setAlpha(100)\n            gr.setBrush(QBrush(color))\n            gr.setPen(QPen(Qt.NoPen))\n            vennitems.append(gr)\n\n        self.vennwidget.setItems(vennitems)\n\n        for i, area in enumerate(self.vennwidget.vennareas()):\n            area_items = list(map(str, list(self.disjoint[i])))\n            if i:\n                area.setText(\"{0}\".format(len(area_items)))\n\n            label = disjoint_set_label(i, n, simplify=False)\n            head = \"<h4>|{}| = {}</h4>\".format(label, len(area_items))\n            if len(area_items) > 32:\n                items_str = \", \".join(map(escape, area_items[:32]))\n                hidden = len(area_items) - 32\n                tooltip = (\"{}<span>{}, ...</br>({} items not shown)<span>\"\n                           .format(head, items_str, hidden))\n            elif area_items:\n                tooltip = \"{}<span>{}</span>\".format(\n                    head,\n                    \", \".join(map(escape, area_items))\n                )\n            else:\n                tooltip = head\n\n            area.setToolTip(tooltip)\n\n            area.setPen(QPen(QColor(10, 10, 10, 200), 1.5))\n            area.setFlag(QGraphicsPathItem.ItemIsSelectable, True)\n            area.setSelected(i in oldselection)\n\n        self._updating = False\n        self._on_selectionChanged()\n\n    def _updateInfo(self):\n        # Clear all warnings\n        self.warning()\n\n        if not len(self.data):\n            self.infolabel.setText(\"No data on input\\n\")\n        else:\n            self.infolabel.setText(f\"{len(self.data)} datasets on input\\n\")\n\n        if self.useidentifiers:\n            no_idx = [\"#{}\".format(i + 1)\n                      for i, key in enumerate(self.data)\n                      if not source_attributes(self.data[key].table.domain)]\n            if len(no_idx) == 1:\n                self.warning(\"Dataset {} has no suitable identifiers.\"\n                             .format(no_idx[0]))\n            elif len(no_idx) > 1:\n                self.warning(\"Datasets {} and {} have no suitable identifiers.\"\n                             .format(\", \".join(no_idx[:-1]), no_idx[-1]))\n\n    def _on_selectionChanged(self):\n        if self._updating:\n            return\n\n        areas = self.vennwidget.vennareas()\n        indices = [i for i, area in enumerate(areas)\n                   if area.isSelected()]\n\n        self.selection = indices\n\n        self.invalidateOutput()\n\n    def _on_useidentifiersChanged(self):\n        self.inputsBox.setEnabled(self.useidentifiers == 1)\n        # Invalidate all itemsets\n        self._invalidate()\n        self._updateItemsets()\n        self._createDiagram()\n\n        self._updateInfo()\n\n    def _on_inputAttrActivated(self, attr_index):\n        combo = self.sender()\n        # Find the input index to which the combo box belongs\n        # (they are reordered when removing inputs).\n        index = None\n        inputs = list(self.data.items())\n        for i in range(len(inputs)):\n            _, c = self._controlAtIndex(i)\n            if c is combo:\n                index = i\n                break\n\n        assert index is not None\n\n        key, _ = inputs[index]\n\n        self._invalidate([key])\n        self._updateItemsets()\n        self._createDiagram()\n\n    def _on_itemTextEdited(self, index, text):\n        text = str(text)\n        key = list(self.itemsets.keys())[index]\n        self.itemsets[key] = self.itemsets[key]._replace(title=text)\n\n    def invalidateOutput(self):\n        self.commit()\n\n    def commit(self):\n        selected_subsets = []\n        annotated_data = None\n        annotated_data_subsets = []\n        annotated_data_masks = []\n        selected_items = reduce(\n            set.union, [self.disjoint[index] for index in self.selection],\n            set()\n        )\n\n        def match(val):\n            if numpy.isnan(val):\n                return False\n            else:\n                return str(val) in selected_items\n\n        source_var = Orange.data.StringVariable(\"source\")\n        item_id_var = Orange.data.StringVariable(\"item_id\")\n\n        names = [itemset.title.strip() for itemset in self.itemsets.values()]\n        names = uniquify(names)\n\n        for i, (key, input) in enumerate(self.data.items()):\n            # cell vars are in functions that are only used in the loop\n            # pylint: disable=cell-var-from-loop\n            if not len(input.table):\n                continue\n            if self.useidentifiers:\n                attr = self.itemsetAttr(key)\n                if attr is not None:\n                    mask = list(map(match, (inst[attr] for inst in input.table)))\n                else:\n                    mask = [False] * len(input.table)\n\n                def instance_key(inst):\n                    return str(inst[attr])\n\n                def instance_key_all(inst):\n                    return str(inst[attr])\n            else:\n                mask = [ComparableInstance(inst) in selected_items\n                        for inst in input.table]\n                _map = {item: str(i) for i, item in enumerate(selected_items)}\n                _map_all = {\n                    ComparableInstance(i):  hash(ComparableInstance(i))\n                    for i in input.table\n                }\n\n                def instance_key(inst):\n                    return _map[ComparableInstance(inst)]\n\n                def instance_key_all(inst):\n                    return _map_all[ComparableInstance(inst)]\n\n            mask = numpy.array(mask, dtype=bool)\n            subset = input.table[mask]\n\n            annotated_subset = input.table\n            id_column = numpy.array(\n                [[instance_key_all(inst)] for inst in annotated_subset])\n            source_names = numpy.array([[names[i]]] * len(annotated_subset))\n            annotated_subset = append_column(\n                annotated_subset, \"M\", source_var, source_names)\n            annotated_subset = append_column(\n                annotated_subset, \"M\", item_id_var, id_column)\n            annotated_data_subsets.append(annotated_subset)\n            annotated_data_masks.append(mask)\n\n            if len(subset) == 0:\n                continue\n\n            # add columns with source table id and set id\n\n            if not self.output_duplicates:\n                id_column = numpy.array([[instance_key(inst)] for inst in subset],\n                                        dtype=object)\n                source_names = numpy.array([[names[i]]] * len(subset),\n                                           dtype=object)\n\n                subset = append_column(subset, \"M\", source_var, source_names)\n                subset = append_column(subset, \"M\", item_id_var, id_column)\n\n            selected_subsets.append(subset)\n\n        if selected_subsets and not self.output_duplicates:\n            data = table_concat(selected_subsets)\n            # Get all variables which are not constant between the same\n            # item set\n            varying = varying_between(data, item_id_var)\n\n            if source_var in varying:\n                varying.remove(source_var)\n\n            data = reshape_wide(data, varying, [item_id_var], [source_var])\n            # remove the temporary item set id column\n            data = drop_columns(data, [item_id_var])\n        elif selected_subsets:\n            data = table_concat(selected_subsets)\n        else:\n            data = None\n\n        if annotated_data_subsets:\n            annotated_data = table_concat(annotated_data_subsets)\n            indices = numpy.hstack(annotated_data_masks)\n            annotated_data = create_annotated_table(annotated_data, indices)\n            varying = varying_between(annotated_data, item_id_var)\n            if source_var in varying:\n                varying.remove(source_var)\n            annotated_data = reshape_wide(annotated_data, varying,\n                                          [item_id_var], [source_var])\n            annotated_data = drop_columns(annotated_data, [item_id_var])\n\n        self.Outputs.selected_data.send(data)\n        self.Outputs.annotated_data.send(annotated_data)\n\n    def getSettings(self, *args, **kwargs):\n        self._storeHints()\n        return super().getSettings(self, *args, **kwargs)\n\n    def send_report(self):\n        self.report_plot()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 176,
                            "end_lineno": 223,
                            "parent": {
                                "file_name": "Orange/widgets/visualize/owvenndiagram.py",
                                "type": "class",
                                "name": "OWVennDiagram",
                                "content": "class OWVennDiagram(widget.OWWidget):\n    name = \"Venn Diagram\"\n    description = \"A graphical visualization of the overlap of data instances \" \\\n                  \"from a collection of input datasets.\"\n    icon = \"icons/VennDiagram.svg\"\n    priority = 280\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table, multiple=True)\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    inputhints: dict\n    selection: list\n\n    # Selected disjoint subset indices\n    selection = settings.Setting([])\n    #: Stored input set hints\n    #: {(index, inputname, attributes): (selectedattrname, itemsettitle)}\n    #: The 'selectedattrname' can be None\n    inputhints = settings.Setting({})\n    #: Use identifier columns for instance matching\n    useidentifiers = settings.Setting(True)\n    #: Output unique items (one output row for every unique instance `key`)\n    #: or preserve all duplicates in the output.\n    output_duplicates = settings.Setting(False)\n    autocommit = settings.Setting(True)\n\n    graph_name = \"scene\"\n\n    def __init__(self):\n        super().__init__()\n\n        # Diagram update is in progress\n        self._updating = False\n        # Input update is in progress\n        self._inputUpdate = False\n        # All input tables have the same domain.\n        self.samedomain = True\n        # Input datasets in the order they were 'connected'.\n        self.data = OrderedDict()\n        # Extracted input item sets in the order they were 'connected'\n        self.itemsets = OrderedDict()\n\n        # GUI\n        box = gui.vBox(self.controlArea, \"Info\")\n        self.infolabel = gui.widgetLabel(box, \"No data on input.\\n\")\n\n        self.identifiersBox = gui.radioButtonsInBox(\n            self.controlArea, self, \"useidentifiers\", [],\n            box=\"Data Instance Identifiers\",\n            callback=self._on_useidentifiersChanged\n        )\n        self.useequalityButton = gui.appendRadioButton(\n            self.identifiersBox, \"Use instance equality\"\n        )\n        self.useidentifiersButton = rb = gui.appendRadioButton(\n            self.identifiersBox, \"Use identifiers\"\n        )\n        self.inputsBox = gui.indentedBox(\n            self.identifiersBox, sep=gui.checkButtonOffsetHint(rb)\n        )\n        self.inputsBox.setEnabled(bool(self.useidentifiers))\n\n        for i in range(5):\n            box = gui.vBox(self.inputsBox, \"Dataset #%i\" % (i + 1),\n                           addSpace=False)\n            box.setFlat(True)\n            model = itemmodels.VariableListModel(parent=self)\n            cb = gui.OrangeComboBox(\n                minimumContentsLength=12,\n                sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon\n            )\n            cb.setModel(model)\n            cb.activated[int].connect(self._on_inputAttrActivated)\n            box.setEnabled(False)\n            # Store the combo in the box for later use.\n            box.combo_box = cb\n            box.layout().addWidget(cb)\n\n        gui.rubber(self.controlArea)\n\n        box = gui.vBox(self.controlArea, \"Output\")\n        gui.checkBox(box, self, \"output_duplicates\", \"Output duplicates\",\n                     callback=lambda: self.commit())\n        gui.auto_commit(box, self, \"autocommit\", \"Send Selection\", \"Send Automatically\", box=False)\n\n        # Main area view\n        self.scene = QGraphicsScene()\n        self.view = QGraphicsView(self.scene)\n        self.view.setRenderHint(QPainter.Antialiasing)\n        self.view.setBackgroundRole(QPalette.Window)\n        self.view.setFrameStyle(QGraphicsView.StyledPanel)\n\n        self.mainArea.layout().addWidget(self.view)\n        self.vennwidget = VennDiagram()\n        self.vennwidget.resize(400, 400)\n        self.vennwidget.itemTextEdited.connect(self._on_itemTextEdited)\n        self.scene.selectionChanged.connect(self._on_selectionChanged)\n\n        self.scene.addItem(self.vennwidget)\n\n        self.resize(self.controlArea.sizeHint().width() + 550,\n                    max(self.controlArea.sizeHint().height(), 550))\n\n        self._queue = []\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data, key=None):\n        self.error()\n        if not self._inputUpdate:\n            # Store hints only on the first setData call.\n            self._storeHints()\n            self._inputUpdate = True\n\n        if key in self.data:\n            if data is None:\n                # Remove the input\n                self._remove(key)\n            else:\n                # Update existing item\n                self._update(key, data)\n        elif data is not None:\n            # TODO: Allow setting more them 5 inputs and let the user\n            # select the 5 to display.\n            if len(self.data) == 5:\n                self.error(\"Venn diagram accepts at most five datasets.\")\n                return\n            # Add a new input\n            self._add(key, data)\n\n    def handleNewSignals(self):\n        self._inputUpdate = False\n\n        # Check if all inputs are from the same domain.\n        domains = [input.table.domain for input in self.data.values()]\n        samedomain = all(domain_eq(d1, d2) for d1, d2 in pairwise(domains))\n\n        self.samedomain = samedomain\n\n        has_identifiers = all(source_attributes(input.table.domain)\n                              for input in self.data.values())\n        has_any_identifiers = any(source_attributes(input.table.domain)\n                                  for input in self.data.values())\n        self.useequalityButton.setEnabled(samedomain)\n        self.useidentifiersButton.setEnabled(\n            has_any_identifiers or len(self.data) == 0)\n        self.inputsBox.setEnabled(has_any_identifiers)\n\n        if not samedomain and has_any_identifiers and not self.useidentifiers:\n            self.useidentifiers = 1\n        elif samedomain and not has_identifiers:\n            self.useidentifiers = 0\n\n        incremental = all(inc for _, inc in self._queue)\n\n        if incremental:\n            # Only received updated data on existing link.\n            self._updateItemsets()\n        else:\n            # Links were removed and/or added.\n            self._createItemsets()\n            self._restoreHints()\n            self._updateItemsets()\n\n        del self._queue[:]\n\n        self._createDiagram()\n        # If autocommit is enabled, _createDiagram already outputs data\n        # If not, call unconditional_commit from here\n        if not self.autocommit:\n            self.unconditional_commit()\n        if self.data:\n            self.infolabel.setText(f\"{len(self.data)} datasets on input.\\n\")\n        else:\n            self.infolabel.setText(\"No data on input\\n\")\n\n        self._updateInfo()\n        super().handleNewSignals()\n\n    def _invalidate(self, keys=None, incremental=True):\n        \"\"\"\n        Invalidate input for a list of input keys.\n        \"\"\"\n        if keys is None:\n            keys = list(self.data.keys())\n\n        self._queue.extend((key, incremental) for key in keys)\n\n    def itemsetAttr(self, key):\n        index = list(self.data.keys()).index(key)\n        _, combo = self._controlAtIndex(index)\n        model = combo.model()\n        attr_index = combo.currentIndex()\n        if attr_index >= 0:\n            return model[attr_index]\n        else:\n            return None\n\n    def _controlAtIndex(self, index):\n        group_box = self.inputsBox.layout().itemAt(index).widget()\n        combo = group_box.combo_box\n        return group_box, combo\n\n    def _setAttributes(self, index, attrs):\n        box, combo = self._controlAtIndex(index)\n        model = combo.model()\n\n        if attrs is None:\n            model[:] = []\n            box.setEnabled(False)\n        else:\n            if model[:] != attrs:\n                model[:] = attrs\n\n            box.setEnabled(True)\n\n    def _add(self, key, table):\n        name = table.name\n        index = len(self.data)\n        attrs = source_attributes(table.domain)\n\n        self.data[key] = _InputData(key, name, table)\n\n        self._setAttributes(index, attrs)\n\n        self._invalidate([key], incremental=False)\n\n        item = self.inputsBox.layout().itemAt(index)\n        box = item.widget()\n        box.setTitle(\"Dataset: {}\".format(name))\n\n    def _remove(self, key):\n        index = list(self.data.keys()).index(key)\n\n        # Clear possible warnings.\n        self.warning()\n\n        self._setAttributes(index, None)\n\n        del self.data[key]\n\n        layout = self.inputsBox.layout()\n        item = layout.takeAt(index)\n        layout.addItem(item)\n        inputs = list(self.data.values())\n\n        for i in range(5):\n            box, _ = self._controlAtIndex(i)\n            if i < len(inputs):\n                title = \"Dataset: {}\".format(inputs[i].name)\n            else:\n                title = \"Dataset #{}\".format(i + 1)\n            box.setTitle(title)\n\n        self._invalidate([key], incremental=False)\n\n    def _update(self, key, table):\n        name = table.name\n        index = list(self.data.keys()).index(key)\n        attrs = source_attributes(table.domain)\n\n        self.data[key] = self.data[key]._replace(name=name, table=table)\n\n        self._setAttributes(index, attrs)\n        self._invalidate([key])\n\n        item = self.inputsBox.layout().itemAt(index)\n        box = item.widget()\n        box.setTitle(\"Dataset: {}\".format(name))\n\n    def _itemsForInput(self, key):\n        useidentifiers = self.useidentifiers or not self.samedomain\n\n        def items_by_key(key, input):\n            attr = self.itemsetAttr(key)\n            if attr is not None:\n                return [str(inst[attr]) for inst in input.table\n                        if not numpy.isnan(inst[attr])]\n            else:\n                return []\n\n        def items_by_eq(key, input):\n            return list(map(ComparableInstance, input.table))\n\n        input = self.data[key]\n        if useidentifiers:\n            items = items_by_key(key, input)\n        else:\n            items = items_by_eq(key, input)\n        return items\n\n    def _updateItemsets(self):\n        assert list(self.data.keys()) == list(self.itemsets.keys())\n        for key, input in list(self.data.items()):\n            items = self._itemsForInput(key)\n            item = self.itemsets[key]\n            item = item._replace(items=items)\n            name = input.name\n            if item.name != name:\n                item = item._replace(name=name, title=name)\n            self.itemsets[key] = item\n\n    def _createItemsets(self):\n        olditemsets = dict(self.itemsets)\n        self.itemsets.clear()\n\n        for key, input in self.data.items():\n            items = self._itemsForInput(key)\n            name = input.name\n            if key in olditemsets and olditemsets[key].name == name:\n                # Reuse the title (which might have been changed by the user)\n                title = olditemsets[key].title\n            else:\n                title = name\n\n            itemset = _ItemSet(key=key, name=name, title=title, items=items)\n            self.itemsets[key] = itemset\n\n    def _storeHints(self):\n        if self.data:\n            self.inputhints.clear()\n            for i, (key, input) in enumerate(self.data.items()):\n                attrs = source_attributes(input.table.domain)\n                attrs = tuple(attr.name for attr in attrs)\n                selected = self.itemsetAttr(key)\n                if selected is not None:\n                    attr_name = selected.name\n                else:\n                    attr_name = None\n                itemset = self.itemsets[key]\n                self.inputhints[(i, input.name, attrs)] = \\\n                    (attr_name, itemset.title)\n\n    def _restoreHints(self):\n        settings = []\n        for i, (key, input) in enumerate(self.data.items()):\n            attrs = source_attributes(input.table.domain)\n            attrs = tuple(attr.name for attr in attrs)\n            hint = self.inputhints.get((i, input.name, attrs), None)\n            if hint is not None:\n                attr, name = hint\n                attr_ind = attrs.index(attr) if attr is not None else -1\n                settings.append((attr_ind, name))\n            else:\n                return\n\n        # all inputs match the stored hints\n        for i, key in enumerate(self.itemsets):\n            attr, itemtitle = settings[i]\n            self.itemsets[key] = self.itemsets[key]._replace(title=itemtitle)\n            _, cb = self._controlAtIndex(i)\n            cb.setCurrentIndex(attr)\n\n    def _createDiagram(self):\n        self._updating = True\n\n        oldselection = list(self.selection)\n\n        self.vennwidget.clear()\n        n = len(self.itemsets)\n        self.disjoint = disjoint(set(s.items) for s in self.itemsets.values())\n\n        vennitems = []\n        colors = colorpalette.ColorPaletteHSV(n)\n\n        for i, (_, item) in enumerate(self.itemsets.items()):\n            count = len(set(item.items))\n            count_all = len(item.items)\n            if count != count_all:\n                fmt = '{} <i>(all: {})</i>'\n            else:\n                fmt = '{}'\n            counts = fmt.format(count, count_all)\n            gr = VennSetItem(text=item.title, informativeText=counts)\n            color = colors[i]\n            color.setAlpha(100)\n            gr.setBrush(QBrush(color))\n            gr.setPen(QPen(Qt.NoPen))\n            vennitems.append(gr)\n\n        self.vennwidget.setItems(vennitems)\n\n        for i, area in enumerate(self.vennwidget.vennareas()):\n            area_items = list(map(str, list(self.disjoint[i])))\n            if i:\n                area.setText(\"{0}\".format(len(area_items)))\n\n            label = disjoint_set_label(i, n, simplify=False)\n            head = \"<h4>|{}| = {}</h4>\".format(label, len(area_items))\n            if len(area_items) > 32:\n                items_str = \", \".join(map(escape, area_items[:32]))\n                hidden = len(area_items) - 32\n                tooltip = (\"{}<span>{}, ...</br>({} items not shown)<span>\"\n                           .format(head, items_str, hidden))\n            elif area_items:\n                tooltip = \"{}<span>{}</span>\".format(\n                    head,\n                    \", \".join(map(escape, area_items))\n                )\n            else:\n                tooltip = head\n\n            area.setToolTip(tooltip)\n\n            area.setPen(QPen(QColor(10, 10, 10, 200), 1.5))\n            area.setFlag(QGraphicsPathItem.ItemIsSelectable, True)\n            area.setSelected(i in oldselection)\n\n        self._updating = False\n        self._on_selectionChanged()\n\n    def _updateInfo(self):\n        # Clear all warnings\n        self.warning()\n\n        if not len(self.data):\n            self.infolabel.setText(\"No data on input\\n\")\n        else:\n            self.infolabel.setText(f\"{len(self.data)} datasets on input\\n\")\n\n        if self.useidentifiers:\n            no_idx = [\"#{}\".format(i + 1)\n                      for i, key in enumerate(self.data)\n                      if not source_attributes(self.data[key].table.domain)]\n            if len(no_idx) == 1:\n                self.warning(\"Dataset {} has no suitable identifiers.\"\n                             .format(no_idx[0]))\n            elif len(no_idx) > 1:\n                self.warning(\"Datasets {} and {} have no suitable identifiers.\"\n                             .format(\", \".join(no_idx[:-1]), no_idx[-1]))\n\n    def _on_selectionChanged(self):\n        if self._updating:\n            return\n\n        areas = self.vennwidget.vennareas()\n        indices = [i for i, area in enumerate(areas)\n                   if area.isSelected()]\n\n        self.selection = indices\n\n        self.invalidateOutput()\n\n    def _on_useidentifiersChanged(self):\n        self.inputsBox.setEnabled(self.useidentifiers == 1)\n        # Invalidate all itemsets\n        self._invalidate()\n        self._updateItemsets()\n        self._createDiagram()\n\n        self._updateInfo()\n\n    def _on_inputAttrActivated(self, attr_index):\n        combo = self.sender()\n        # Find the input index to which the combo box belongs\n        # (they are reordered when removing inputs).\n        index = None\n        inputs = list(self.data.items())\n        for i in range(len(inputs)):\n            _, c = self._controlAtIndex(i)\n            if c is combo:\n                index = i\n                break\n\n        assert index is not None\n\n        key, _ = inputs[index]\n\n        self._invalidate([key])\n        self._updateItemsets()\n        self._createDiagram()\n\n    def _on_itemTextEdited(self, index, text):\n        text = str(text)\n        key = list(self.itemsets.keys())[index]\n        self.itemsets[key] = self.itemsets[key]._replace(title=text)\n\n    def invalidateOutput(self):\n        self.commit()\n\n    def commit(self):\n        selected_subsets = []\n        annotated_data = None\n        annotated_data_subsets = []\n        annotated_data_masks = []\n        selected_items = reduce(\n            set.union, [self.disjoint[index] for index in self.selection],\n            set()\n        )\n\n        def match(val):\n            if numpy.isnan(val):\n                return False\n            else:\n                return str(val) in selected_items\n\n        source_var = Orange.data.StringVariable(\"source\")\n        item_id_var = Orange.data.StringVariable(\"item_id\")\n\n        names = [itemset.title.strip() for itemset in self.itemsets.values()]\n        names = uniquify(names)\n\n        for i, (key, input) in enumerate(self.data.items()):\n            # cell vars are in functions that are only used in the loop\n            # pylint: disable=cell-var-from-loop\n            if not len(input.table):\n                continue\n            if self.useidentifiers:\n                attr = self.itemsetAttr(key)\n                if attr is not None:\n                    mask = list(map(match, (inst[attr] for inst in input.table)))\n                else:\n                    mask = [False] * len(input.table)\n\n                def instance_key(inst):\n                    return str(inst[attr])\n\n                def instance_key_all(inst):\n                    return str(inst[attr])\n            else:\n                mask = [ComparableInstance(inst) in selected_items\n                        for inst in input.table]\n                _map = {item: str(i) for i, item in enumerate(selected_items)}\n                _map_all = {\n                    ComparableInstance(i):  hash(ComparableInstance(i))\n                    for i in input.table\n                }\n\n                def instance_key(inst):\n                    return _map[ComparableInstance(inst)]\n\n                def instance_key_all(inst):\n                    return _map_all[ComparableInstance(inst)]\n\n            mask = numpy.array(mask, dtype=bool)\n            subset = input.table[mask]\n\n            annotated_subset = input.table\n            id_column = numpy.array(\n                [[instance_key_all(inst)] for inst in annotated_subset])\n            source_names = numpy.array([[names[i]]] * len(annotated_subset))\n            annotated_subset = append_column(\n                annotated_subset, \"M\", source_var, source_names)\n            annotated_subset = append_column(\n                annotated_subset, \"M\", item_id_var, id_column)\n            annotated_data_subsets.append(annotated_subset)\n            annotated_data_masks.append(mask)\n\n            if len(subset) == 0:\n                continue\n\n            # add columns with source table id and set id\n\n            if not self.output_duplicates:\n                id_column = numpy.array([[instance_key(inst)] for inst in subset],\n                                        dtype=object)\n                source_names = numpy.array([[names[i]]] * len(subset),\n                                           dtype=object)\n\n                subset = append_column(subset, \"M\", source_var, source_names)\n                subset = append_column(subset, \"M\", item_id_var, id_column)\n\n            selected_subsets.append(subset)\n\n        if selected_subsets and not self.output_duplicates:\n            data = table_concat(selected_subsets)\n            # Get all variables which are not constant between the same\n            # item set\n            varying = varying_between(data, item_id_var)\n\n            if source_var in varying:\n                varying.remove(source_var)\n\n            data = reshape_wide(data, varying, [item_id_var], [source_var])\n            # remove the temporary item set id column\n            data = drop_columns(data, [item_id_var])\n        elif selected_subsets:\n            data = table_concat(selected_subsets)\n        else:\n            data = None\n\n        if annotated_data_subsets:\n            annotated_data = table_concat(annotated_data_subsets)\n            indices = numpy.hstack(annotated_data_masks)\n            annotated_data = create_annotated_table(annotated_data, indices)\n            varying = varying_between(annotated_data, item_id_var)\n            if source_var in varying:\n                varying.remove(source_var)\n            annotated_data = reshape_wide(annotated_data, varying,\n                                          [item_id_var], [source_var])\n            annotated_data = drop_columns(annotated_data, [item_id_var])\n\n        self.Outputs.selected_data.send(data)\n        self.Outputs.annotated_data.send(annotated_data)\n\n    def getSettings(self, *args, **kwargs):\n        self._storeHints()\n        return super().getSettings(self, *args, **kwargs)\n\n    def send_report(self):\n        self.report_plot()",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 41,
                                "end_lineno": 646,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "Orange/widgets/visualize/owvenndiagram.py",
                            "type": "class",
                            "name": "OWVennDiagram",
                            "content": "class OWVennDiagram(widget.OWWidget):\n    name = \"Venn Diagram\"\n    description = \"A graphical visualization of the overlap of data instances \" \\\n                  \"from a collection of input datasets.\"\n    icon = \"icons/VennDiagram.svg\"\n    priority = 280\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table, multiple=True)\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    inputhints: dict\n    selection: list\n\n    # Selected disjoint subset indices\n    selection = settings.Setting([])\n    #: Stored input set hints\n    #: {(index, inputname, attributes): (selectedattrname, itemsettitle)}\n    #: The 'selectedattrname' can be None\n    inputhints = settings.Setting({})\n    #: Use identifier columns for instance matching\n    useidentifiers = settings.Setting(True)\n    #: Output unique items (one output row for every unique instance `key`)\n    #: or preserve all duplicates in the output.\n    output_duplicates = settings.Setting(False)\n    autocommit = settings.Setting(True)\n\n    graph_name = \"scene\"\n\n    def __init__(self):\n        super().__init__()\n\n        # Diagram update is in progress\n        self._updating = False\n        # Input update is in progress\n        self._inputUpdate = False\n        # All input tables have the same domain.\n        self.samedomain = True\n        # Input datasets in the order they were 'connected'.\n        self.data = OrderedDict()\n        # Extracted input item sets in the order they were 'connected'\n        self.itemsets = OrderedDict()\n\n        # GUI\n        box = gui.vBox(self.controlArea, \"Info\")\n        self.infolabel = gui.widgetLabel(box, \"No data on input.\\n\")\n\n        self.identifiersBox = gui.radioButtonsInBox(\n            self.controlArea, self, \"useidentifiers\", [],\n            box=\"Data Instance Identifiers\",\n            callback=self._on_useidentifiersChanged\n        )\n        self.useequalityButton = gui.appendRadioButton(\n            self.identifiersBox, \"Use instance equality\"\n        )\n        self.useidentifiersButton = rb = gui.appendRadioButton(\n            self.identifiersBox, \"Use identifiers\"\n        )\n        self.inputsBox = gui.indentedBox(\n            self.identifiersBox, sep=gui.checkButtonOffsetHint(rb)\n        )\n        self.inputsBox.setEnabled(bool(self.useidentifiers))\n\n        for i in range(5):\n            box = gui.vBox(self.inputsBox, \"Dataset #%i\" % (i + 1),\n                           addSpace=False)\n            box.setFlat(True)\n            model = itemmodels.VariableListModel(parent=self)\n            cb = gui.OrangeComboBox(\n                minimumContentsLength=12,\n                sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon\n            )\n            cb.setModel(model)\n            cb.activated[int].connect(self._on_inputAttrActivated)\n            box.setEnabled(False)\n            # Store the combo in the box for later use.\n            box.combo_box = cb\n            box.layout().addWidget(cb)\n\n        gui.rubber(self.controlArea)\n\n        box = gui.vBox(self.controlArea, \"Output\")\n        gui.checkBox(box, self, \"output_duplicates\", \"Output duplicates\",\n                     callback=lambda: self.commit())\n        gui.auto_commit(box, self, \"autocommit\", \"Send Selection\", \"Send Automatically\", box=False)\n\n        # Main area view\n        self.scene = QGraphicsScene()\n        self.view = QGraphicsView(self.scene)\n        self.view.setRenderHint(QPainter.Antialiasing)\n        self.view.setBackgroundRole(QPalette.Window)\n        self.view.setFrameStyle(QGraphicsView.StyledPanel)\n\n        self.mainArea.layout().addWidget(self.view)\n        self.vennwidget = VennDiagram()\n        self.vennwidget.resize(400, 400)\n        self.vennwidget.itemTextEdited.connect(self._on_itemTextEdited)\n        self.scene.selectionChanged.connect(self._on_selectionChanged)\n\n        self.scene.addItem(self.vennwidget)\n\n        self.resize(self.controlArea.sizeHint().width() + 550,\n                    max(self.controlArea.sizeHint().height(), 550))\n\n        self._queue = []\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data, key=None):\n        self.error()\n        if not self._inputUpdate:\n            # Store hints only on the first setData call.\n            self._storeHints()\n            self._inputUpdate = True\n\n        if key in self.data:\n            if data is None:\n                # Remove the input\n                self._remove(key)\n            else:\n                # Update existing item\n                self._update(key, data)\n        elif data is not None:\n            # TODO: Allow setting more them 5 inputs and let the user\n            # select the 5 to display.\n            if len(self.data) == 5:\n                self.error(\"Venn diagram accepts at most five datasets.\")\n                return\n            # Add a new input\n            self._add(key, data)\n\n    def handleNewSignals(self):\n        self._inputUpdate = False\n\n        # Check if all inputs are from the same domain.\n        domains = [input.table.domain for input in self.data.values()]\n        samedomain = all(domain_eq(d1, d2) for d1, d2 in pairwise(domains))\n\n        self.samedomain = samedomain\n\n        has_identifiers = all(source_attributes(input.table.domain)\n                              for input in self.data.values())\n        has_any_identifiers = any(source_attributes(input.table.domain)\n                                  for input in self.data.values())\n        self.useequalityButton.setEnabled(samedomain)\n        self.useidentifiersButton.setEnabled(\n            has_any_identifiers or len(self.data) == 0)\n        self.inputsBox.setEnabled(has_any_identifiers)\n\n        if not samedomain and has_any_identifiers and not self.useidentifiers:\n            self.useidentifiers = 1\n        elif samedomain and not has_identifiers:\n            self.useidentifiers = 0\n\n        incremental = all(inc for _, inc in self._queue)\n\n        if incremental:\n            # Only received updated data on existing link.\n            self._updateItemsets()\n        else:\n            # Links were removed and/or added.\n            self._createItemsets()\n            self._restoreHints()\n            self._updateItemsets()\n\n        del self._queue[:]\n\n        self._createDiagram()\n        # If autocommit is enabled, _createDiagram already outputs data\n        # If not, call unconditional_commit from here\n        if not self.autocommit:\n            self.unconditional_commit()\n        if self.data:\n            self.infolabel.setText(f\"{len(self.data)} datasets on input.\\n\")\n        else:\n            self.infolabel.setText(\"No data on input\\n\")\n\n        self._updateInfo()\n        super().handleNewSignals()\n\n    def _invalidate(self, keys=None, incremental=True):\n        \"\"\"\n        Invalidate input for a list of input keys.\n        \"\"\"\n        if keys is None:\n            keys = list(self.data.keys())\n\n        self._queue.extend((key, incremental) for key in keys)\n\n    def itemsetAttr(self, key):\n        index = list(self.data.keys()).index(key)\n        _, combo = self._controlAtIndex(index)\n        model = combo.model()\n        attr_index = combo.currentIndex()\n        if attr_index >= 0:\n            return model[attr_index]\n        else:\n            return None\n\n    def _controlAtIndex(self, index):\n        group_box = self.inputsBox.layout().itemAt(index).widget()\n        combo = group_box.combo_box\n        return group_box, combo\n\n    def _setAttributes(self, index, attrs):\n        box, combo = self._controlAtIndex(index)\n        model = combo.model()\n\n        if attrs is None:\n            model[:] = []\n            box.setEnabled(False)\n        else:\n            if model[:] != attrs:\n                model[:] = attrs\n\n            box.setEnabled(True)\n\n    def _add(self, key, table):\n        name = table.name\n        index = len(self.data)\n        attrs = source_attributes(table.domain)\n\n        self.data[key] = _InputData(key, name, table)\n\n        self._setAttributes(index, attrs)\n\n        self._invalidate([key], incremental=False)\n\n        item = self.inputsBox.layout().itemAt(index)\n        box = item.widget()\n        box.setTitle(\"Dataset: {}\".format(name))\n\n    def _remove(self, key):\n        index = list(self.data.keys()).index(key)\n\n        # Clear possible warnings.\n        self.warning()\n\n        self._setAttributes(index, None)\n\n        del self.data[key]\n\n        layout = self.inputsBox.layout()\n        item = layout.takeAt(index)\n        layout.addItem(item)\n        inputs = list(self.data.values())\n\n        for i in range(5):\n            box, _ = self._controlAtIndex(i)\n            if i < len(inputs):\n                title = \"Dataset: {}\".format(inputs[i].name)\n            else:\n                title = \"Dataset #{}\".format(i + 1)\n            box.setTitle(title)\n\n        self._invalidate([key], incremental=False)\n\n    def _update(self, key, table):\n        name = table.name\n        index = list(self.data.keys()).index(key)\n        attrs = source_attributes(table.domain)\n\n        self.data[key] = self.data[key]._replace(name=name, table=table)\n\n        self._setAttributes(index, attrs)\n        self._invalidate([key])\n\n        item = self.inputsBox.layout().itemAt(index)\n        box = item.widget()\n        box.setTitle(\"Dataset: {}\".format(name))\n\n    def _itemsForInput(self, key):\n        useidentifiers = self.useidentifiers or not self.samedomain\n\n        def items_by_key(key, input):\n            attr = self.itemsetAttr(key)\n            if attr is not None:\n                return [str(inst[attr]) for inst in input.table\n                        if not numpy.isnan(inst[attr])]\n            else:\n                return []\n\n        def items_by_eq(key, input):\n            return list(map(ComparableInstance, input.table))\n\n        input = self.data[key]\n        if useidentifiers:\n            items = items_by_key(key, input)\n        else:\n            items = items_by_eq(key, input)\n        return items\n\n    def _updateItemsets(self):\n        assert list(self.data.keys()) == list(self.itemsets.keys())\n        for key, input in list(self.data.items()):\n            items = self._itemsForInput(key)\n            item = self.itemsets[key]\n            item = item._replace(items=items)\n            name = input.name\n            if item.name != name:\n                item = item._replace(name=name, title=name)\n            self.itemsets[key] = item\n\n    def _createItemsets(self):\n        olditemsets = dict(self.itemsets)\n        self.itemsets.clear()\n\n        for key, input in self.data.items():\n            items = self._itemsForInput(key)\n            name = input.name\n            if key in olditemsets and olditemsets[key].name == name:\n                # Reuse the title (which might have been changed by the user)\n                title = olditemsets[key].title\n            else:\n                title = name\n\n            itemset = _ItemSet(key=key, name=name, title=title, items=items)\n            self.itemsets[key] = itemset\n\n    def _storeHints(self):\n        if self.data:\n            self.inputhints.clear()\n            for i, (key, input) in enumerate(self.data.items()):\n                attrs = source_attributes(input.table.domain)\n                attrs = tuple(attr.name for attr in attrs)\n                selected = self.itemsetAttr(key)\n                if selected is not None:\n                    attr_name = selected.name\n                else:\n                    attr_name = None\n                itemset = self.itemsets[key]\n                self.inputhints[(i, input.name, attrs)] = \\\n                    (attr_name, itemset.title)\n\n    def _restoreHints(self):\n        settings = []\n        for i, (key, input) in enumerate(self.data.items()):\n            attrs = source_attributes(input.table.domain)\n            attrs = tuple(attr.name for attr in attrs)\n            hint = self.inputhints.get((i, input.name, attrs), None)\n            if hint is not None:\n                attr, name = hint\n                attr_ind = attrs.index(attr) if attr is not None else -1\n                settings.append((attr_ind, name))\n            else:\n                return\n\n        # all inputs match the stored hints\n        for i, key in enumerate(self.itemsets):\n            attr, itemtitle = settings[i]\n            self.itemsets[key] = self.itemsets[key]._replace(title=itemtitle)\n            _, cb = self._controlAtIndex(i)\n            cb.setCurrentIndex(attr)\n\n    def _createDiagram(self):\n        self._updating = True\n\n        oldselection = list(self.selection)\n\n        self.vennwidget.clear()\n        n = len(self.itemsets)\n        self.disjoint = disjoint(set(s.items) for s in self.itemsets.values())\n\n        vennitems = []\n        colors = colorpalette.ColorPaletteHSV(n)\n\n        for i, (_, item) in enumerate(self.itemsets.items()):\n            count = len(set(item.items))\n            count_all = len(item.items)\n            if count != count_all:\n                fmt = '{} <i>(all: {})</i>'\n            else:\n                fmt = '{}'\n            counts = fmt.format(count, count_all)\n            gr = VennSetItem(text=item.title, informativeText=counts)\n            color = colors[i]\n            color.setAlpha(100)\n            gr.setBrush(QBrush(color))\n            gr.setPen(QPen(Qt.NoPen))\n            vennitems.append(gr)\n\n        self.vennwidget.setItems(vennitems)\n\n        for i, area in enumerate(self.vennwidget.vennareas()):\n            area_items = list(map(str, list(self.disjoint[i])))\n            if i:\n                area.setText(\"{0}\".format(len(area_items)))\n\n            label = disjoint_set_label(i, n, simplify=False)\n            head = \"<h4>|{}| = {}</h4>\".format(label, len(area_items))\n            if len(area_items) > 32:\n                items_str = \", \".join(map(escape, area_items[:32]))\n                hidden = len(area_items) - 32\n                tooltip = (\"{}<span>{}, ...</br>({} items not shown)<span>\"\n                           .format(head, items_str, hidden))\n            elif area_items:\n                tooltip = \"{}<span>{}</span>\".format(\n                    head,\n                    \", \".join(map(escape, area_items))\n                )\n            else:\n                tooltip = head\n\n            area.setToolTip(tooltip)\n\n            area.setPen(QPen(QColor(10, 10, 10, 200), 1.5))\n            area.setFlag(QGraphicsPathItem.ItemIsSelectable, True)\n            area.setSelected(i in oldselection)\n\n        self._updating = False\n        self._on_selectionChanged()\n\n    def _updateInfo(self):\n        # Clear all warnings\n        self.warning()\n\n        if not len(self.data):\n            self.infolabel.setText(\"No data on input\\n\")\n        else:\n            self.infolabel.setText(f\"{len(self.data)} datasets on input\\n\")\n\n        if self.useidentifiers:\n            no_idx = [\"#{}\".format(i + 1)\n                      for i, key in enumerate(self.data)\n                      if not source_attributes(self.data[key].table.domain)]\n            if len(no_idx) == 1:\n                self.warning(\"Dataset {} has no suitable identifiers.\"\n                             .format(no_idx[0]))\n            elif len(no_idx) > 1:\n                self.warning(\"Datasets {} and {} have no suitable identifiers.\"\n                             .format(\", \".join(no_idx[:-1]), no_idx[-1]))\n\n    def _on_selectionChanged(self):\n        if self._updating:\n            return\n\n        areas = self.vennwidget.vennareas()\n        indices = [i for i, area in enumerate(areas)\n                   if area.isSelected()]\n\n        self.selection = indices\n\n        self.invalidateOutput()\n\n    def _on_useidentifiersChanged(self):\n        self.inputsBox.setEnabled(self.useidentifiers == 1)\n        # Invalidate all itemsets\n        self._invalidate()\n        self._updateItemsets()\n        self._createDiagram()\n\n        self._updateInfo()\n\n    def _on_inputAttrActivated(self, attr_index):\n        combo = self.sender()\n        # Find the input index to which the combo box belongs\n        # (they are reordered when removing inputs).\n        index = None\n        inputs = list(self.data.items())\n        for i in range(len(inputs)):\n            _, c = self._controlAtIndex(i)\n            if c is combo:\n                index = i\n                break\n\n        assert index is not None\n\n        key, _ = inputs[index]\n\n        self._invalidate([key])\n        self._updateItemsets()\n        self._createDiagram()\n\n    def _on_itemTextEdited(self, index, text):\n        text = str(text)\n        key = list(self.itemsets.keys())[index]\n        self.itemsets[key] = self.itemsets[key]._replace(title=text)\n\n    def invalidateOutput(self):\n        self.commit()\n\n    def commit(self):\n        selected_subsets = []\n        annotated_data = None\n        annotated_data_subsets = []\n        annotated_data_masks = []\n        selected_items = reduce(\n            set.union, [self.disjoint[index] for index in self.selection],\n            set()\n        )\n\n        def match(val):\n            if numpy.isnan(val):\n                return False\n            else:\n                return str(val) in selected_items\n\n        source_var = Orange.data.StringVariable(\"source\")\n        item_id_var = Orange.data.StringVariable(\"item_id\")\n\n        names = [itemset.title.strip() for itemset in self.itemsets.values()]\n        names = uniquify(names)\n\n        for i, (key, input) in enumerate(self.data.items()):\n            # cell vars are in functions that are only used in the loop\n            # pylint: disable=cell-var-from-loop\n            if not len(input.table):\n                continue\n            if self.useidentifiers:\n                attr = self.itemsetAttr(key)\n                if attr is not None:\n                    mask = list(map(match, (inst[attr] for inst in input.table)))\n                else:\n                    mask = [False] * len(input.table)\n\n                def instance_key(inst):\n                    return str(inst[attr])\n\n                def instance_key_all(inst):\n                    return str(inst[attr])\n            else:\n                mask = [ComparableInstance(inst) in selected_items\n                        for inst in input.table]\n                _map = {item: str(i) for i, item in enumerate(selected_items)}\n                _map_all = {\n                    ComparableInstance(i):  hash(ComparableInstance(i))\n                    for i in input.table\n                }\n\n                def instance_key(inst):\n                    return _map[ComparableInstance(inst)]\n\n                def instance_key_all(inst):\n                    return _map_all[ComparableInstance(inst)]\n\n            mask = numpy.array(mask, dtype=bool)\n            subset = input.table[mask]\n\n            annotated_subset = input.table\n            id_column = numpy.array(\n                [[instance_key_all(inst)] for inst in annotated_subset])\n            source_names = numpy.array([[names[i]]] * len(annotated_subset))\n            annotated_subset = append_column(\n                annotated_subset, \"M\", source_var, source_names)\n            annotated_subset = append_column(\n                annotated_subset, \"M\", item_id_var, id_column)\n            annotated_data_subsets.append(annotated_subset)\n            annotated_data_masks.append(mask)\n\n            if len(subset) == 0:\n                continue\n\n            # add columns with source table id and set id\n\n            if not self.output_duplicates:\n                id_column = numpy.array([[instance_key(inst)] for inst in subset],\n                                        dtype=object)\n                source_names = numpy.array([[names[i]]] * len(subset),\n                                           dtype=object)\n\n                subset = append_column(subset, \"M\", source_var, source_names)\n                subset = append_column(subset, \"M\", item_id_var, id_column)\n\n            selected_subsets.append(subset)\n\n        if selected_subsets and not self.output_duplicates:\n            data = table_concat(selected_subsets)\n            # Get all variables which are not constant between the same\n            # item set\n            varying = varying_between(data, item_id_var)\n\n            if source_var in varying:\n                varying.remove(source_var)\n\n            data = reshape_wide(data, varying, [item_id_var], [source_var])\n            # remove the temporary item set id column\n            data = drop_columns(data, [item_id_var])\n        elif selected_subsets:\n            data = table_concat(selected_subsets)\n        else:\n            data = None\n\n        if annotated_data_subsets:\n            annotated_data = table_concat(annotated_data_subsets)\n            indices = numpy.hstack(annotated_data_masks)\n            annotated_data = create_annotated_table(annotated_data, indices)\n            varying = varying_between(annotated_data, item_id_var)\n            if source_var in varying:\n                varying.remove(source_var)\n            annotated_data = reshape_wide(annotated_data, varying,\n                                          [item_id_var], [source_var])\n            annotated_data = drop_columns(annotated_data, [item_id_var])\n\n        self.Outputs.selected_data.send(data)\n        self.Outputs.annotated_data.send(annotated_data)\n\n    def getSettings(self, *args, **kwargs):\n        self._storeHints()\n        return super().getSettings(self, *args, **kwargs)\n\n    def send_report(self):\n        self.report_plot()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 41,
                            "end_lineno": 646,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "# Test methods with long descriptive names can omit docstrings\n# pylint: disable=missing-docstring\n\nimport unittest\nfrom collections import defaultdict\n\nimport numpy as np\nimport scipy.sparse as sp\n\nfrom Orange.data import (Table, Domain, StringVariable,\n                         DiscreteVariable, ContinuousVariable, Variable)\nfrom Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin\nfrom Orange.widgets.utils.annotated_data import (ANNOTATED_DATA_FEATURE_NAME)\nfrom Orange.widgets.visualize.owvenndiagram import (reshape_wide,\n                                                    table_concat,\n                                                    varying_between,\n                                                    drop_columns,\n                                                    OWVennDiagram,\n                                                    group_table_indices,\n                                                    copy_descriptor)\nfrom Orange.tests import test_filename\n\n\nclass TestVennDiagram(unittest.TestCase):\n    def add_metas(self, table, meta_attrs, meta_data):\n        domain = Domain(table.domain.attributes,\n                        table.domain.class_vars,\n                        table.domain.metas + meta_attrs)\n        metas = np.hstack((table.metas, meta_data))\n        return Table(domain, table.X, table.Y, metas)\n\n    def test_reshape_wide(self):\n        class_var = DiscreteVariable(\"c\", values=[\"x\"])\n        item_id_var = StringVariable(\"item_id\")\n        source_var = StringVariable(\"source\")\n        c1, c, item_id, ca, cb = np.random.randint(10, size=5)\n        data = Table(Domain([ContinuousVariable(\"c1\")], [class_var],\n                            [DiscreteVariable(\"c(a)\", class_var.values),\n                             DiscreteVariable(\"c(b)\", class_var.values),\n                             source_var, item_id_var]),\n                     np.array([[c1], [c1]], dtype=object),\n                     np.array([[c], [c]], dtype=object),\n                     np.array([[ca, np.nan, \"a\", item_id],\n                               [np.nan, cb, \"b\", item_id]], dtype=object))\n\n        data = reshape_wide(data, [], [item_id_var], [source_var])\n        self.assertFalse(any(np.isnan(data.metas.astype(np.float32)[0])))\n        self.assertEqual(len(data), 1)\n        np.testing.assert_equal(data.metas, np.array([[ca, cb, item_id]],\n                                                     dtype=object))\n\n    def test_reshape_wide_missing_vals(self):\n        data = Table(test_filename(\"datasets/test9.tab\"))\n        reshaped_data = reshape_wide(data, [], [data.domain[0]],\n                                     [data.domain[0]])\n        self.assertEqual(2, len(reshaped_data))\n\n    def test_varying_between_missing_vals(self):\n        data = Table(test_filename(\"datasets/test9.tab\"))\n        self.assertEqual(6, len(varying_between(data, data.domain[0])))\n\n    def test_venn_diagram(self):\n        sources = [\"SVM Learner\", \"Naive Bayes\", \"Random Forest\"]\n        item_id_var = StringVariable(\"item_id\")\n        source_var = StringVariable(\"source\")\n        table = Table(\"zoo\")\n        class_var = table.domain.class_var\n        cv = np.random.randint(len(class_var.values), size=(3, len(sources)))\n\n        tables = []\n        # pylint: disable=consider-using-enumerate\n        for i in range(len(sources)):\n            temp_table = Table.from_table(table.domain, table,\n                                          [0 + i, 1 + i, 2 + i])\n            temp_d = (DiscreteVariable(\"%s(%s)\" % (class_var.name,\n                                                   sources[0 + i]),\n                                       class_var.values),\n                      source_var, item_id_var)\n            temp_m = np.array([[cv[0, i], sources[i], table.metas[0 + i, 0]],\n                               [cv[1, i], sources[i], table.metas[1 + i, 0]],\n                               [cv[2, i], sources[i], table.metas[2 + i, 0]]],\n                              dtype=object)\n            temp_table = self.add_metas(temp_table, temp_d, temp_m)\n            tables.append(temp_table)\n\n        data = table_concat(tables)\n        varying = varying_between(data, item_id_var)\n        if source_var in varying:\n            varying.remove(source_var)\n        data = reshape_wide(data, varying, [item_id_var], [source_var])\n        data = drop_columns(data, [item_id_var])\n\n        result = np.array([[table.metas[0, 0], cv[0, 0], np.nan, np.nan],\n                           [table.metas[1, 0], cv[1, 0], cv[0, 1], np.nan],\n                           [table.metas[2, 0], cv[2, 0], cv[1, 1], cv[0, 2]],\n                           [table.metas[3, 0], np.nan, cv[2, 1], cv[1, 2]],\n                           [table.metas[4, 0], np.nan, np.nan, cv[2, 2]]],\n                          dtype=object)\n\n        for i in range(len(result)):\n            for j in range(len(result[0])):\n                val = result[i][j]\n                if isinstance(val, float) and np.isnan(val):\n                    self.assertTrue(np.isnan(data.metas[i][j]))\n                else:\n                    np.testing.assert_equal(data.metas[i][j], result[i][j])\n\n\nclass TestOWVennDiagram(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.signal_data = cls.data[:25]\n\n    def setUp(self):\n        self.widget = self.create_widget(OWVennDiagram)\n        self.signal_name = self.widget.Inputs.data\n\n    def _select_data(self):\n        self.widget.vennwidget.vennareas()[1].setSelected(True)\n        return list(range(len(self.signal_data)))\n\n    def test_multiple_input(self):\n        self.send_signal(self.signal_name, self.data[:100], 1)\n        self.send_signal(self.signal_name, self.data[50:], 2)\n\n        # check selected data output\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n\n        # check annotated data output\n        feature_name = ANNOTATED_DATA_FEATURE_NAME\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(0, np.sum([i[feature_name] for i in annotated]))\n\n        # select data instances\n        self.widget.vennwidget.vennareas()[3].setSelected(True)\n        selected_indices = list(range(50, 100))\n\n        # check selected data output\n        selected = self.get_output(self.widget.Outputs.selected_data)\n        n_sel, n_attr = len(selected), len(self.data.domain.attributes)\n        self.assertGreater(n_sel, 0)\n        self.assertEqual(selected.domain == self.data.domain,\n                         self.same_input_output_domain)\n        np.testing.assert_array_equal(selected.X[:, :n_attr],\n                                      self.data.X[selected_indices])\n\n        # check annotated data output\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(n_sel, np.sum([i[feature_name] for i in annotated]))\n\n        # compare selected and annotated data domains\n        self._compare_selected_annotated_domains(selected, annotated)\n\n        # check output when data is removed\n        self.send_signal(self.signal_name, None, 1)\n        self.send_signal(self.signal_name, None, 2)\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_no_data(self):\n        \"\"\"Check that the widget doesn't crash on empty data\"\"\"\n        self.send_signal(self.signal_name, self.data[:0], 1)\n        self.send_signal(self.signal_name, self.data[:100], 2)\n        self.send_signal(self.signal_name, self.data[50:], 3)\n\n        for i in range(1, 4):\n            self.send_signal(self.signal_name, None, i)\n\n        self.send_signal(self.signal_name, self.data[:100], 1)\n        self.send_signal(self.signal_name, self.data[:0], 1)\n        self.send_signal(self.signal_name, self.data[50:], 3)\n\n        for i in range(1, 4):\n            self.send_signal(self.signal_name, None, i)\n\n        self.send_signal(self.signal_name, self.data[:100], 1)\n        self.send_signal(self.signal_name, self.data[50:], 2)\n        self.send_signal(self.signal_name, self.data[:0], 3)\n\n\nclass GroupTableIndicesTest(unittest.TestCase):\n\n    def test_varying_between_combined(self):\n        X = np.array([[0, 0, 0, 0, 0, 1,],\n                      [0, 0, 1, 1, 0, 1,],\n                      [0, 0, 0, 2, np.nan, np.nan,],\n                      [0, 1, 0, 0, 0, 0,],\n                      [0, 1, 0, 2, 0, 0,],\n                      [0, 1, 0, 0, np.nan, 0,]])\n\n        M = np.array([[\"A\", 0, 0, 0, 0, 0, 1,],\n                      [\"A\", 0, 0, 1, 1, 0, 1,],\n                      [\"A\", 0, 0, 0, 2, np.nan, np.nan,],\n                      [\"B\", 0, 1, 0, 0, 0, 0,],\n                      [\"B\", 0, 1, 0, 2, 0, 0,],\n                      [\"B\", 0, 1, 0, 0, np.nan, 0,]], dtype=str)\n\n        variables = [ContinuousVariable(name=\"F%d\" % j) for j in range(X.shape[1])]\n        metas = [StringVariable(name=\"M%d\" % j) for j in range(M.shape[1])]\n        domain = Domain(attributes=variables, metas=metas)\n\n        data = Table.from_numpy(X=X, domain=domain, metas=M)\n\n        self.assertEqual(varying_between(data, idvar=data.domain.metas[0]),\n                         [variables[2], variables[3], metas[3], metas[4], metas[5], metas[6]])\n\n        data = Table.from_numpy(X=sp.csr_matrix(X), domain=domain, metas=M)\n        self.assertEqual(varying_between(data, idvar=data.domain.metas[0]),\n                         [variables[2], variables[3], metas[3], metas[4], metas[5], metas[6]])\n\n\n    def test_group_table_indices(self):\n        table = Table(test_filename(\"datasets/test9.tab\"))\n        dd = defaultdict(list)\n        dd[\"1\"] = [0, 1]\n        dd[\"huh\"] = [2]\n        dd[\"hoy\"] = [3]\n        dd[\"?\"] = [4]\n        dd[\"2\"] = [5]\n        dd[\"oh yeah\"] = [6]\n        dd[\"3\"] = [7]\n        self.assertEqual(dd, group_table_indices(table, \"g\"))\n\n\nclass TestVennUtilities(unittest.TestCase):\n    def test_copy_descriptor_discrete(self):\n        var = DiscreteVariable(\"foo\", values=list(\"abc\"), ordered=True)\n        var.attributes = {\"bar\": 42, \"baz\": 13}\n        copied = copy_descriptor(var)\n        self.assertIsInstance(copied, DiscreteVariable)\n        self.assertEqual(copied.name, \"foo\")\n        self.assertEqual(list(copied.values), list(\"abc\"))\n        self.assertTrue(copied.ordered)\n        self.assertEqual(copied.attributes, var.attributes)\n        self.assertIsNot(copied.attributes, var.attributes)\n\n        var = DiscreteVariable(\"foo\", values=list(\"abc\"), ordered=False)\n        copied = copy_descriptor(var, \"cux\")\n        self.assertEqual(copied.name, \"cux\")\n        self.assertFalse(copied.ordered)\n\n    def test_copy_descriptor_continuous(self):\n        var = ContinuousVariable(\"foo\", number_of_decimals=42)\n        var.attributes = {\"bar\": 42, \"baz\": 13}\n        copied = copy_descriptor(var)\n        self.assertIsInstance(copied, ContinuousVariable)\n        self.assertEqual(copied.name, \"foo\")\n        self.assertEqual(copied.number_of_decimals, 42)\n        self.assertEqual(copied.attributes, var.attributes)\n        self.assertIsNot(copied.attributes, var.attributes)\n\n        var = ContinuousVariable(\"foo\", number_of_decimals=42)\n        copied = copy_descriptor(var, \"cux\")\n        self.assertEqual(copied.name, \"cux\")\n\n    def test_copy_descriptor_other_types(self):\n        class SomeVariable(Variable):\n            pass\n        var = SomeVariable(\"foo\")\n        var.attributes = {\"bar\": 42, \"baz\": 13}\n        copied = copy_descriptor(var)\n        self.assertIsInstance(copied, SomeVariable)\n        self.assertEqual(copied.name, \"foo\")\n        self.assertEqual(copied.attributes, var.attributes)\n        self.assertIsNot(copied.attributes, var.attributes)\n\n        copied = copy_descriptor(var, \"cux\")\n        self.assertEqual(copied.name, \"cux\")\n",
            "new_file_content": "# Test methods with long descriptive names can omit docstrings\n# pylint: disable=missing-docstring\n\nimport unittest\nfrom unittest.mock import patch\nfrom collections import defaultdict\n\nimport numpy as np\nimport scipy.sparse as sp\n\nfrom Orange.data import (Table, Domain, StringVariable,\n                         DiscreteVariable, ContinuousVariable, Variable)\nfrom Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin\nfrom Orange.widgets.utils.annotated_data import (ANNOTATED_DATA_FEATURE_NAME)\nfrom Orange.widgets.visualize.owvenndiagram import (reshape_wide,\n                                                    table_concat,\n                                                    varying_between,\n                                                    drop_columns,\n                                                    OWVennDiagram,\n                                                    group_table_indices,\n                                                    copy_descriptor)\nfrom Orange.tests import test_filename\n\n\nclass TestVennDiagram(unittest.TestCase):\n    def add_metas(self, table, meta_attrs, meta_data):\n        domain = Domain(table.domain.attributes,\n                        table.domain.class_vars,\n                        table.domain.metas + meta_attrs)\n        metas = np.hstack((table.metas, meta_data))\n        return Table(domain, table.X, table.Y, metas)\n\n    def test_reshape_wide(self):\n        class_var = DiscreteVariable(\"c\", values=[\"x\"])\n        item_id_var = StringVariable(\"item_id\")\n        source_var = StringVariable(\"source\")\n        c1, c, item_id, ca, cb = np.random.randint(10, size=5)\n        data = Table(Domain([ContinuousVariable(\"c1\")], [class_var],\n                            [DiscreteVariable(\"c(a)\", class_var.values),\n                             DiscreteVariable(\"c(b)\", class_var.values),\n                             source_var, item_id_var]),\n                     np.array([[c1], [c1]], dtype=object),\n                     np.array([[c], [c]], dtype=object),\n                     np.array([[ca, np.nan, \"a\", item_id],\n                               [np.nan, cb, \"b\", item_id]], dtype=object))\n\n        data = reshape_wide(data, [], [item_id_var], [source_var])\n        self.assertFalse(any(np.isnan(data.metas.astype(np.float32)[0])))\n        self.assertEqual(len(data), 1)\n        np.testing.assert_equal(data.metas, np.array([[ca, cb, item_id]],\n                                                     dtype=object))\n\n    def test_reshape_wide_missing_vals(self):\n        data = Table(test_filename(\"datasets/test9.tab\"))\n        reshaped_data = reshape_wide(data, [], [data.domain[0]],\n                                     [data.domain[0]])\n        self.assertEqual(2, len(reshaped_data))\n\n    def test_varying_between_missing_vals(self):\n        data = Table(test_filename(\"datasets/test9.tab\"))\n        self.assertEqual(6, len(varying_between(data, data.domain[0])))\n\n    def test_venn_diagram(self):\n        sources = [\"SVM Learner\", \"Naive Bayes\", \"Random Forest\"]\n        item_id_var = StringVariable(\"item_id\")\n        source_var = StringVariable(\"source\")\n        table = Table(\"zoo\")\n        class_var = table.domain.class_var\n        cv = np.random.randint(len(class_var.values), size=(3, len(sources)))\n\n        tables = []\n        # pylint: disable=consider-using-enumerate\n        for i in range(len(sources)):\n            temp_table = Table.from_table(table.domain, table,\n                                          [0 + i, 1 + i, 2 + i])\n            temp_d = (DiscreteVariable(\"%s(%s)\" % (class_var.name,\n                                                   sources[0 + i]),\n                                       class_var.values),\n                      source_var, item_id_var)\n            temp_m = np.array([[cv[0, i], sources[i], table.metas[0 + i, 0]],\n                               [cv[1, i], sources[i], table.metas[1 + i, 0]],\n                               [cv[2, i], sources[i], table.metas[2 + i, 0]]],\n                              dtype=object)\n            temp_table = self.add_metas(temp_table, temp_d, temp_m)\n            tables.append(temp_table)\n\n        data = table_concat(tables)\n        varying = varying_between(data, item_id_var)\n        if source_var in varying:\n            varying.remove(source_var)\n        data = reshape_wide(data, varying, [item_id_var], [source_var])\n        data = drop_columns(data, [item_id_var])\n\n        result = np.array([[table.metas[0, 0], cv[0, 0], np.nan, np.nan],\n                           [table.metas[1, 0], cv[1, 0], cv[0, 1], np.nan],\n                           [table.metas[2, 0], cv[2, 0], cv[1, 1], cv[0, 2]],\n                           [table.metas[3, 0], np.nan, cv[2, 1], cv[1, 2]],\n                           [table.metas[4, 0], np.nan, np.nan, cv[2, 2]]],\n                          dtype=object)\n\n        for i in range(len(result)):\n            for j in range(len(result[0])):\n                val = result[i][j]\n                if isinstance(val, float) and np.isnan(val):\n                    self.assertTrue(np.isnan(data.metas[i][j]))\n                else:\n                    np.testing.assert_equal(data.metas[i][j], result[i][j])\n\n\nclass TestOWVennDiagram(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.signal_data = cls.data[:25]\n\n    def setUp(self):\n        self.widget = self.create_widget(OWVennDiagram)\n        self.signal_name = self.widget.Inputs.data\n\n    def _select_data(self):\n        self.widget.vennwidget.vennareas()[1].setSelected(True)\n        return list(range(len(self.signal_data)))\n\n    def test_multiple_input(self):\n        self.send_signal(self.signal_name, self.data[:100], 1)\n        self.send_signal(self.signal_name, self.data[50:], 2)\n\n        # check selected data output\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n\n        # check annotated data output\n        feature_name = ANNOTATED_DATA_FEATURE_NAME\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(0, np.sum([i[feature_name] for i in annotated]))\n\n        # select data instances\n        self.widget.vennwidget.vennareas()[3].setSelected(True)\n        selected_indices = list(range(50, 100))\n\n        # check selected data output\n        selected = self.get_output(self.widget.Outputs.selected_data)\n        n_sel, n_attr = len(selected), len(self.data.domain.attributes)\n        self.assertGreater(n_sel, 0)\n        self.assertEqual(selected.domain == self.data.domain,\n                         self.same_input_output_domain)\n        np.testing.assert_array_equal(selected.X[:, :n_attr],\n                                      self.data.X[selected_indices])\n\n        # check annotated data output\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(n_sel, np.sum([i[feature_name] for i in annotated]))\n\n        # compare selected and annotated data domains\n        self._compare_selected_annotated_domains(selected, annotated)\n\n        # check output when data is removed\n        self.send_signal(self.signal_name, None, 1)\n        self.send_signal(self.signal_name, None, 2)\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_no_data(self):\n        \"\"\"Check that the widget doesn't crash on empty data\"\"\"\n        self.send_signal(self.signal_name, self.data[:0], 1)\n        self.send_signal(self.signal_name, self.data[:100], 2)\n        self.send_signal(self.signal_name, self.data[50:], 3)\n\n        for i in range(1, 4):\n            self.send_signal(self.signal_name, None, i)\n\n        self.send_signal(self.signal_name, self.data[:100], 1)\n        self.send_signal(self.signal_name, self.data[:0], 1)\n        self.send_signal(self.signal_name, self.data[50:], 3)\n\n        for i in range(1, 4):\n            self.send_signal(self.signal_name, None, i)\n\n        self.send_signal(self.signal_name, self.data[:100], 1)\n        self.send_signal(self.signal_name, self.data[50:], 2)\n        self.send_signal(self.signal_name, self.data[:0], 3)\n\n    def test_unconditional_commit_on_new_signal(self):\n        with patch.object(self.widget, 'unconditional_commit') as commit:\n            self.widget.autocommit = False\n            commit.reset_mock()\n            self.send_signal(self.signal_name, self.data[:100], 1)\n            commit.assert_called()\n\n\nclass GroupTableIndicesTest(unittest.TestCase):\n\n    def test_varying_between_combined(self):\n        X = np.array([[0, 0, 0, 0, 0, 1,],\n                      [0, 0, 1, 1, 0, 1,],\n                      [0, 0, 0, 2, np.nan, np.nan,],\n                      [0, 1, 0, 0, 0, 0,],\n                      [0, 1, 0, 2, 0, 0,],\n                      [0, 1, 0, 0, np.nan, 0,]])\n\n        M = np.array([[\"A\", 0, 0, 0, 0, 0, 1,],\n                      [\"A\", 0, 0, 1, 1, 0, 1,],\n                      [\"A\", 0, 0, 0, 2, np.nan, np.nan,],\n                      [\"B\", 0, 1, 0, 0, 0, 0,],\n                      [\"B\", 0, 1, 0, 2, 0, 0,],\n                      [\"B\", 0, 1, 0, 0, np.nan, 0,]], dtype=str)\n\n        variables = [ContinuousVariable(name=\"F%d\" % j) for j in range(X.shape[1])]\n        metas = [StringVariable(name=\"M%d\" % j) for j in range(M.shape[1])]\n        domain = Domain(attributes=variables, metas=metas)\n\n        data = Table.from_numpy(X=X, domain=domain, metas=M)\n\n        self.assertEqual(varying_between(data, idvar=data.domain.metas[0]),\n                         [variables[2], variables[3], metas[3], metas[4], metas[5], metas[6]])\n\n        data = Table.from_numpy(X=sp.csr_matrix(X), domain=domain, metas=M)\n        self.assertEqual(varying_between(data, idvar=data.domain.metas[0]),\n                         [variables[2], variables[3], metas[3], metas[4], metas[5], metas[6]])\n\n\n    def test_group_table_indices(self):\n        table = Table(test_filename(\"datasets/test9.tab\"))\n        dd = defaultdict(list)\n        dd[\"1\"] = [0, 1]\n        dd[\"huh\"] = [2]\n        dd[\"hoy\"] = [3]\n        dd[\"?\"] = [4]\n        dd[\"2\"] = [5]\n        dd[\"oh yeah\"] = [6]\n        dd[\"3\"] = [7]\n        self.assertEqual(dd, group_table_indices(table, \"g\"))\n\n\nclass TestVennUtilities(unittest.TestCase):\n    def test_copy_descriptor_discrete(self):\n        var = DiscreteVariable(\"foo\", values=list(\"abc\"), ordered=True)\n        var.attributes = {\"bar\": 42, \"baz\": 13}\n        copied = copy_descriptor(var)\n        self.assertIsInstance(copied, DiscreteVariable)\n        self.assertEqual(copied.name, \"foo\")\n        self.assertEqual(list(copied.values), list(\"abc\"))\n        self.assertTrue(copied.ordered)\n        self.assertEqual(copied.attributes, var.attributes)\n        self.assertIsNot(copied.attributes, var.attributes)\n\n        var = DiscreteVariable(\"foo\", values=list(\"abc\"), ordered=False)\n        copied = copy_descriptor(var, \"cux\")\n        self.assertEqual(copied.name, \"cux\")\n        self.assertFalse(copied.ordered)\n\n    def test_copy_descriptor_continuous(self):\n        var = ContinuousVariable(\"foo\", number_of_decimals=42)\n        var.attributes = {\"bar\": 42, \"baz\": 13}\n        copied = copy_descriptor(var)\n        self.assertIsInstance(copied, ContinuousVariable)\n        self.assertEqual(copied.name, \"foo\")\n        self.assertEqual(copied.number_of_decimals, 42)\n        self.assertEqual(copied.attributes, var.attributes)\n        self.assertIsNot(copied.attributes, var.attributes)\n\n        var = ContinuousVariable(\"foo\", number_of_decimals=42)\n        copied = copy_descriptor(var, \"cux\")\n        self.assertEqual(copied.name, \"cux\")\n\n    def test_copy_descriptor_other_types(self):\n        class SomeVariable(Variable):\n            pass\n        var = SomeVariable(\"foo\")\n        var.attributes = {\"bar\": 42, \"baz\": 13}\n        copied = copy_descriptor(var)\n        self.assertIsInstance(copied, SomeVariable)\n        self.assertEqual(copied.name, \"foo\")\n        self.assertEqual(copied.attributes, var.attributes)\n        self.assertIsNot(copied.attributes, var.attributes)\n\n        copied = copy_descriptor(var, \"cux\")\n        self.assertEqual(copied.name, \"cux\")\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n",
            "header": {
                "file": {
                    "path": "Orange/widgets/visualize/tests/test_owvenndiagram.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "9d0dfcad8",
                "new_commit_hash": "76ddb4c62",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/widgets/visualize/tests/test_owvenndiagram.py"
            },
            "plus_file": {
                "path": "b/Orange/widgets/visualize/tests/test_owvenndiagram.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 2,
                            "length": 6
                        },
                        "new_range": {
                            "start": 2,
                            "length": 7
                        },
                        "section": ""
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "# pylint: disable=missing-docstring",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "import unittest",
                                "type": "context"
                            },
                            {
                                "content": "from unittest.mock import patch",
                                "type": "added"
                            },
                            {
                                "content": "from collections import defaultdict",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "import numpy as np",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "Orange/widgets/visualize/tests/test_owvenndiagram.py",
                            "type": "statement",
                            "name": "",
                            "content": "from unittest.mock import patch",
                            "ast_type_str": "ImportFrom",
                            "start_lineno": 5,
                            "end_lineno": 5,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 180,
                            "length": 6
                        },
                        "new_range": {
                            "start": 181,
                            "length": 13
                        },
                        "section": "class TestOWVennDiagram(WidgetTest, WidgetOutputsTestMixin):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.send_signal(self.signal_name, self.data[50:], 2)",
                                "type": "context"
                            },
                            {
                                "content": "        self.send_signal(self.signal_name, self.data[:0], 3)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def test_unconditional_commit_on_new_signal(self):",
                                "type": "added"
                            },
                            {
                                "content": "        with patch.object(self.widget, 'unconditional_commit') as commit:",
                                "type": "added"
                            },
                            {
                                "content": "            self.widget.autocommit = False",
                                "type": "added"
                            },
                            {
                                "content": "            commit.reset_mock()",
                                "type": "added"
                            },
                            {
                                "content": "            self.send_signal(self.signal_name, self.data[:100], 1)",
                                "type": "added"
                            },
                            {
                                "content": "            commit.assert_called()",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "class GroupTableIndicesTest(unittest.TestCase):",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/visualize/tests/test_owvenndiagram.py",
                            "type": "class",
                            "name": "TestOWVennDiagram",
                            "content": "class TestOWVennDiagram(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.signal_data = cls.data[:25]\n\n    def setUp(self):\n        self.widget = self.create_widget(OWVennDiagram)\n        self.signal_name = self.widget.Inputs.data\n\n    def _select_data(self):\n        self.widget.vennwidget.vennareas()[1].setSelected(True)\n        return list(range(len(self.signal_data)))\n\n    def test_multiple_input(self):\n        self.send_signal(self.signal_name, self.data[:100], 1)\n        self.send_signal(self.signal_name, self.data[50:], 2)\n\n        # check selected data output\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n\n        # check annotated data output\n        feature_name = ANNOTATED_DATA_FEATURE_NAME\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(0, np.sum([i[feature_name] for i in annotated]))\n\n        # select data instances\n        self.widget.vennwidget.vennareas()[3].setSelected(True)\n        selected_indices = list(range(50, 100))\n\n        # check selected data output\n        selected = self.get_output(self.widget.Outputs.selected_data)\n        n_sel, n_attr = len(selected), len(self.data.domain.attributes)\n        self.assertGreater(n_sel, 0)\n        self.assertEqual(selected.domain == self.data.domain,\n                         self.same_input_output_domain)\n        np.testing.assert_array_equal(selected.X[:, :n_attr],\n                                      self.data.X[selected_indices])\n\n        # check annotated data output\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(n_sel, np.sum([i[feature_name] for i in annotated]))\n\n        # compare selected and annotated data domains\n        self._compare_selected_annotated_domains(selected, annotated)\n\n        # check output when data is removed\n        self.send_signal(self.signal_name, None, 1)\n        self.send_signal(self.signal_name, None, 2)\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_no_data(self):\n        \"\"\"Check that the widget doesn't crash on empty data\"\"\"\n        self.send_signal(self.signal_name, self.data[:0], 1)\n        self.send_signal(self.signal_name, self.data[:100], 2)\n        self.send_signal(self.signal_name, self.data[50:], 3)\n\n        for i in range(1, 4):\n            self.send_signal(self.signal_name, None, i)\n\n        self.send_signal(self.signal_name, self.data[:100], 1)\n        self.send_signal(self.signal_name, self.data[:0], 1)\n        self.send_signal(self.signal_name, self.data[50:], 3)\n\n        for i in range(1, 4):\n            self.send_signal(self.signal_name, None, i)\n\n        self.send_signal(self.signal_name, self.data[:100], 1)\n        self.send_signal(self.signal_name, self.data[50:], 2)\n        self.send_signal(self.signal_name, self.data[:0], 3)\n\n    def test_unconditional_commit_on_new_signal(self):\n        with patch.object(self.widget, 'unconditional_commit') as commit:\n            self.widget.autocommit = False\n            commit.reset_mock()\n            self.send_signal(self.signal_name, self.data[:100], 1)\n            commit.assert_called()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 110,
                            "end_lineno": 189,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "Orange/widgets/visualize/tests/test_owvenndiagram.py",
                            "type": "method",
                            "name": "TestOWVennDiagram.test_unconditional_commit_on_new_signal",
                            "content": "class TestOWVennDiagram(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.signal_data = cls.data[:25]\n\n    def setUp(self):\n        self.widget = self.create_widget(OWVennDiagram)\n        self.signal_name = self.widget.Inputs.data\n\n    def _select_data(self):\n        self.widget.vennwidget.vennareas()[1].setSelected(True)\n        return list(range(len(self.signal_data)))\n\n    def test_multiple_input(self):\n        self.send_signal(self.signal_name, self.data[:100], 1)\n        self.send_signal(self.signal_name, self.data[50:], 2)\n\n        # check selected data output\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n\n        # check annotated data output\n        feature_name = ANNOTATED_DATA_FEATURE_NAME\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(0, np.sum([i[feature_name] for i in annotated]))\n\n        # select data instances\n        self.widget.vennwidget.vennareas()[3].setSelected(True)\n        selected_indices = list(range(50, 100))\n\n        # check selected data output\n        selected = self.get_output(self.widget.Outputs.selected_data)\n        n_sel, n_attr = len(selected), len(self.data.domain.attributes)\n        self.assertGreater(n_sel, 0)\n        self.assertEqual(selected.domain == self.data.domain,\n                         self.same_input_output_domain)\n        np.testing.assert_array_equal(selected.X[:, :n_attr],\n                                      self.data.X[selected_indices])\n\n        # check annotated data output\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(n_sel, np.sum([i[feature_name] for i in annotated]))\n\n        # compare selected and annotated data domains\n        self._compare_selected_annotated_domains(selected, annotated)\n\n        # check output when data is removed\n        self.send_signal(self.signal_name, None, 1)\n        self.send_signal(self.signal_name, None, 2)\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_no_data(self):\n        \"\"\"Check that the widget doesn't crash on empty data\"\"\"\n        self.send_signal(self.signal_name, self.data[:0], 1)\n        self.send_signal(self.signal_name, self.data[:100], 2)\n        self.send_signal(self.signal_name, self.data[50:], 3)\n\n        for i in range(1, 4):\n            self.send_signal(self.signal_name, None, i)\n\n        self.send_signal(self.signal_name, self.data[:100], 1)\n        self.send_signal(self.signal_name, self.data[:0], 1)\n        self.send_signal(self.signal_name, self.data[50:], 3)\n\n        for i in range(1, 4):\n            self.send_signal(self.signal_name, None, i)\n\n        self.send_signal(self.signal_name, self.data[:100], 1)\n        self.send_signal(self.signal_name, self.data[50:], 2)\n        self.send_signal(self.signal_name, self.data[:0], 3)\n\n    def test_unconditional_commit_on_new_signal(self):\n        with patch.object(self.widget, 'unconditional_commit') as commit:\n            self.widget.autocommit = False\n            commit.reset_mock()\n            self.send_signal(self.signal_name, self.data[:100], 1)\n            commit.assert_called()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 184,
                            "end_lineno": 189,
                            "parent": {
                                "file_name": "Orange/widgets/visualize/tests/test_owvenndiagram.py",
                                "type": "class",
                                "name": "TestOWVennDiagram",
                                "content": "class TestOWVennDiagram(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.signal_data = cls.data[:25]\n\n    def setUp(self):\n        self.widget = self.create_widget(OWVennDiagram)\n        self.signal_name = self.widget.Inputs.data\n\n    def _select_data(self):\n        self.widget.vennwidget.vennareas()[1].setSelected(True)\n        return list(range(len(self.signal_data)))\n\n    def test_multiple_input(self):\n        self.send_signal(self.signal_name, self.data[:100], 1)\n        self.send_signal(self.signal_name, self.data[50:], 2)\n\n        # check selected data output\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n\n        # check annotated data output\n        feature_name = ANNOTATED_DATA_FEATURE_NAME\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(0, np.sum([i[feature_name] for i in annotated]))\n\n        # select data instances\n        self.widget.vennwidget.vennareas()[3].setSelected(True)\n        selected_indices = list(range(50, 100))\n\n        # check selected data output\n        selected = self.get_output(self.widget.Outputs.selected_data)\n        n_sel, n_attr = len(selected), len(self.data.domain.attributes)\n        self.assertGreater(n_sel, 0)\n        self.assertEqual(selected.domain == self.data.domain,\n                         self.same_input_output_domain)\n        np.testing.assert_array_equal(selected.X[:, :n_attr],\n                                      self.data.X[selected_indices])\n\n        # check annotated data output\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(n_sel, np.sum([i[feature_name] for i in annotated]))\n\n        # compare selected and annotated data domains\n        self._compare_selected_annotated_domains(selected, annotated)\n\n        # check output when data is removed\n        self.send_signal(self.signal_name, None, 1)\n        self.send_signal(self.signal_name, None, 2)\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_no_data(self):\n        \"\"\"Check that the widget doesn't crash on empty data\"\"\"\n        self.send_signal(self.signal_name, self.data[:0], 1)\n        self.send_signal(self.signal_name, self.data[:100], 2)\n        self.send_signal(self.signal_name, self.data[50:], 3)\n\n        for i in range(1, 4):\n            self.send_signal(self.signal_name, None, i)\n\n        self.send_signal(self.signal_name, self.data[:100], 1)\n        self.send_signal(self.signal_name, self.data[:0], 1)\n        self.send_signal(self.signal_name, self.data[50:], 3)\n\n        for i in range(1, 4):\n            self.send_signal(self.signal_name, None, i)\n\n        self.send_signal(self.signal_name, self.data[:100], 1)\n        self.send_signal(self.signal_name, self.data[50:], 2)\n        self.send_signal(self.signal_name, self.data[:0], 3)\n\n    def test_unconditional_commit_on_new_signal(self):\n        with patch.object(self.widget, 'unconditional_commit') as commit:\n            self.widget.autocommit = False\n            commit.reset_mock()\n            self.send_signal(self.signal_name, self.data[:100], 1)\n            commit.assert_called()",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 110,
                                "end_lineno": 189,
                                "parent": null
                            }
                        }
                    ],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 269,
                            "length": 3
                        },
                        "new_range": {
                            "start": 277,
                            "length": 7
                        },
                        "section": "class TestVennUtilities(unittest.TestCase):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        copied = copy_descriptor(var, \"cux\")",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertEqual(copied.name, \"cux\")",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "if __name__ == \"__main__\":",
                                "type": "added"
                            },
                            {
                                "content": "    unittest.main()",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "Orange/widgets/visualize/tests/test_owvenndiagram.py",
                            "type": "statement",
                            "name": "",
                            "content": "if __name__ == \"__main__\":\n    unittest.main()",
                            "ast_type_str": "If",
                            "start_lineno": 282,
                            "end_lineno": 283,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "a5566a65725e55528ed27b2ddfcd93c5573d6998^",
    "new_commit_hash": "a5566a65725e55528ed27b2ddfcd93c5573d6998",
    "commit_message": "OWVennDiagram: Unconditional commit on new data",
    "commit_date": "2019-09-20T10:50:34+02:00",
    "metadata": {}
}
{
    "repo_name": "orange3",
    "new_commit_hash": "a5566a65725e55528ed27b2ddfcd93c5573d6998",
    "test_file_codes": [
        "# Test methods with long descriptive names can omit docstrings\n# pylint: disable=missing-docstring\n\nimport unittest\nfrom unittest.mock import patch\nfrom collections import defaultdict\n\nimport numpy as np\nimport scipy.sparse as sp\n\nfrom Orange.data import (Table, Domain, StringVariable,\n                         DiscreteVariable, ContinuousVariable, Variable)\nfrom Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin\nfrom Orange.widgets.utils.annotated_data import (ANNOTATED_DATA_FEATURE_NAME)\nfrom Orange.widgets.visualize.owvenndiagram import (reshape_wide,\n                                                    table_concat,\n                                                    varying_between,\n                                                    drop_columns,\n                                                    OWVennDiagram,\n                                                    group_table_indices,\n                                                    copy_descriptor)\nfrom Orange.tests import test_filename\n\n\nclass TestVennDiagram(unittest.TestCase):\n    def add_metas(self, table, meta_attrs, meta_data):\n        domain = Domain(table.domain.attributes,\n                        table.domain.class_vars,\n                        table.domain.metas + meta_attrs)\n        metas = np.hstack((table.metas, meta_data))\n        return Table(domain, table.X, table.Y, metas)\n\n    def test_reshape_wide(self):\n        class_var = DiscreteVariable(\"c\", values=[\"x\"])\n        item_id_var = StringVariable(\"item_id\")\n        source_var = StringVariable(\"source\")\n        c1, c, item_id, ca, cb = np.random.randint(10, size=5)\n        data = Table(Domain([ContinuousVariable(\"c1\")], [class_var],\n                            [DiscreteVariable(\"c(a)\", class_var.values),\n                             DiscreteVariable(\"c(b)\", class_var.values),\n                             source_var, item_id_var]),\n                     np.array([[c1], [c1]], dtype=object),\n                     np.array([[c], [c]], dtype=object),\n                     np.array([[ca, np.nan, \"a\", item_id],\n                               [np.nan, cb, \"b\", item_id]], dtype=object))\n\n        data = reshape_wide(data, [], [item_id_var], [source_var])\n        self.assertFalse(any(np.isnan(data.metas.astype(np.float32)[0])))\n        self.assertEqual(len(data), 1)\n        np.testing.assert_equal(data.metas, np.array([[ca, cb, item_id]],\n                                                     dtype=object))\n\n    def test_reshape_wide_missing_vals(self):\n        data = Table(test_filename(\"datasets/test9.tab\"))\n        reshaped_data = reshape_wide(data, [], [data.domain[0]],\n                                     [data.domain[0]])\n        self.assertEqual(2, len(reshaped_data))\n\n    def test_varying_between_missing_vals(self):\n        data = Table(test_filename(\"datasets/test9.tab\"))\n        self.assertEqual(6, len(varying_between(data, data.domain[0])))\n\n    def test_venn_diagram(self):\n        sources = [\"SVM Learner\", \"Naive Bayes\", \"Random Forest\"]\n        item_id_var = StringVariable(\"item_id\")\n        source_var = StringVariable(\"source\")\n        table = Table(\"zoo\")\n        class_var = table.domain.class_var\n        cv = np.random.randint(len(class_var.values), size=(3, len(sources)))\n\n        tables = []\n        # pylint: disable=consider-using-enumerate\n        for i in range(len(sources)):\n            temp_table = Table.from_table(table.domain, table,\n                                          [0 + i, 1 + i, 2 + i])\n            temp_d = (DiscreteVariable(\"%s(%s)\" % (class_var.name,\n                                                   sources[0 + i]),\n                                       class_var.values),\n                      source_var, item_id_var)\n            temp_m = np.array([[cv[0, i], sources[i], table.metas[0 + i, 0]],\n                               [cv[1, i], sources[i], table.metas[1 + i, 0]],\n                               [cv[2, i], sources[i], table.metas[2 + i, 0]]],\n                              dtype=object)\n            temp_table = self.add_metas(temp_table, temp_d, temp_m)\n            tables.append(temp_table)\n\n        data = table_concat(tables)\n        varying = varying_between(data, item_id_var)\n        if source_var in varying:\n            varying.remove(source_var)\n        data = reshape_wide(data, varying, [item_id_var], [source_var])\n        data = drop_columns(data, [item_id_var])\n\n        result = np.array([[table.metas[0, 0], cv[0, 0], np.nan, np.nan],\n                           [table.metas[1, 0], cv[1, 0], cv[0, 1], np.nan],\n                           [table.metas[2, 0], cv[2, 0], cv[1, 1], cv[0, 2]],\n                           [table.metas[3, 0], np.nan, cv[2, 1], cv[1, 2]],\n                           [table.metas[4, 0], np.nan, np.nan, cv[2, 2]]],\n                          dtype=object)\n\n        for i in range(len(result)):\n            for j in range(len(result[0])):\n                val = result[i][j]\n                if isinstance(val, float) and np.isnan(val):\n                    self.assertTrue(np.isnan(data.metas[i][j]))\n                else:\n                    np.testing.assert_equal(data.metas[i][j], result[i][j])\n\n\nclass TestOWVennDiagram(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.signal_data = cls.data[:25]\n\n    def setUp(self):\n        self.widget = self.create_widget(OWVennDiagram)\n        self.signal_name = self.widget.Inputs.data\n\n    def _select_data(self):\n        self.widget.vennwidget.vennareas()[1].setSelected(True)\n        return list(range(len(self.signal_data)))\n\n    def test_multiple_input(self):\n        self.send_signal(self.signal_name, self.data[:100], 1)\n        self.send_signal(self.signal_name, self.data[50:], 2)\n\n        # check selected data output\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n\n        # check annotated data output\n        feature_name = ANNOTATED_DATA_FEATURE_NAME\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(0, np.sum([i[feature_name] for i in annotated]))\n\n        # select data instances\n        self.widget.vennwidget.vennareas()[3].setSelected(True)\n        selected_indices = list(range(50, 100))\n\n        # check selected data output\n        selected = self.get_output(self.widget.Outputs.selected_data)\n        n_sel, n_attr = len(selected), len(self.data.domain.attributes)\n        self.assertGreater(n_sel, 0)\n        self.assertEqual(selected.domain == self.data.domain,\n                         self.same_input_output_domain)\n        np.testing.assert_array_equal(selected.X[:, :n_attr],\n                                      self.data.X[selected_indices])\n\n        # check annotated data output\n        annotated = self.get_output(self.widget.Outputs.annotated_data)\n        self.assertEqual(n_sel, np.sum([i[feature_name] for i in annotated]))\n\n        # compare selected and annotated data domains\n        self._compare_selected_annotated_domains(selected, annotated)\n\n        # check output when data is removed\n        self.send_signal(self.signal_name, None, 1)\n        self.send_signal(self.signal_name, None, 2)\n        self.assertIsNone(self.get_output(self.widget.Outputs.selected_data))\n        self.assertIsNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_no_data(self):\n        \"\"\"Check that the widget doesn't crash on empty data\"\"\"\n        self.send_signal(self.signal_name, self.data[:0], 1)\n        self.send_signal(self.signal_name, self.data[:100], 2)\n        self.send_signal(self.signal_name, self.data[50:], 3)\n\n        for i in range(1, 4):\n            self.send_signal(self.signal_name, None, i)\n\n        self.send_signal(self.signal_name, self.data[:100], 1)\n        self.send_signal(self.signal_name, self.data[:0], 1)\n        self.send_signal(self.signal_name, self.data[50:], 3)\n\n        for i in range(1, 4):\n            self.send_signal(self.signal_name, None, i)\n\n        self.send_signal(self.signal_name, self.data[:100], 1)\n        self.send_signal(self.signal_name, self.data[50:], 2)\n        self.send_signal(self.signal_name, self.data[:0], 3)\n\n    def test_unconditional_commit_on_new_signal(self):\n        with patch.object(self.widget, 'unconditional_commit') as commit:\n            self.widget.autocommit = False\n            commit.reset_mock()\n            self.send_signal(self.signal_name, self.data[:100], 1)\n            commit.assert_called()\n\n\nclass GroupTableIndicesTest(unittest.TestCase):\n\n    def test_varying_between_combined(self):\n        X = np.array([[0, 0, 0, 0, 0, 1,],\n                      [0, 0, 1, 1, 0, 1,],\n                      [0, 0, 0, 2, np.nan, np.nan,],\n                      [0, 1, 0, 0, 0, 0,],\n                      [0, 1, 0, 2, 0, 0,],\n                      [0, 1, 0, 0, np.nan, 0,]])\n\n        M = np.array([[\"A\", 0, 0, 0, 0, 0, 1,],\n                      [\"A\", 0, 0, 1, 1, 0, 1,],\n                      [\"A\", 0, 0, 0, 2, np.nan, np.nan,],\n                      [\"B\", 0, 1, 0, 0, 0, 0,],\n                      [\"B\", 0, 1, 0, 2, 0, 0,],\n                      [\"B\", 0, 1, 0, 0, np.nan, 0,]], dtype=str)\n\n        variables = [ContinuousVariable(name=\"F%d\" % j) for j in range(X.shape[1])]\n        metas = [StringVariable(name=\"M%d\" % j) for j in range(M.shape[1])]\n        domain = Domain(attributes=variables, metas=metas)\n\n        data = Table.from_numpy(X=X, domain=domain, metas=M)\n\n        self.assertEqual(varying_between(data, idvar=data.domain.metas[0]),\n                         [variables[2], variables[3], metas[3], metas[4], metas[5], metas[6]])\n\n        data = Table.from_numpy(X=sp.csr_matrix(X), domain=domain, metas=M)\n        self.assertEqual(varying_between(data, idvar=data.domain.metas[0]),\n                         [variables[2], variables[3], metas[3], metas[4], metas[5], metas[6]])\n\n\n    def test_group_table_indices(self):\n        table = Table(test_filename(\"datasets/test9.tab\"))\n        dd = defaultdict(list)\n        dd[\"1\"] = [0, 1]\n        dd[\"huh\"] = [2]\n        dd[\"hoy\"] = [3]\n        dd[\"?\"] = [4]\n        dd[\"2\"] = [5]\n        dd[\"oh yeah\"] = [6]\n        dd[\"3\"] = [7]\n        self.assertEqual(dd, group_table_indices(table, \"g\"))\n\n\nclass TestVennUtilities(unittest.TestCase):\n    def test_copy_descriptor_discrete(self):\n        var = DiscreteVariable(\"foo\", values=list(\"abc\"), ordered=True)\n        var.attributes = {\"bar\": 42, \"baz\": 13}\n        copied = copy_descriptor(var)\n        self.assertIsInstance(copied, DiscreteVariable)\n        self.assertEqual(copied.name, \"foo\")\n        self.assertEqual(list(copied.values), list(\"abc\"))\n        self.assertTrue(copied.ordered)\n        self.assertEqual(copied.attributes, var.attributes)\n        self.assertIsNot(copied.attributes, var.attributes)\n\n        var = DiscreteVariable(\"foo\", values=list(\"abc\"), ordered=False)\n        copied = copy_descriptor(var, \"cux\")\n        self.assertEqual(copied.name, \"cux\")\n        self.assertFalse(copied.ordered)\n\n    def test_copy_descriptor_continuous(self):\n        var = ContinuousVariable(\"foo\", number_of_decimals=42)\n        var.attributes = {\"bar\": 42, \"baz\": 13}\n        copied = copy_descriptor(var)\n        self.assertIsInstance(copied, ContinuousVariable)\n        self.assertEqual(copied.name, \"foo\")\n        self.assertEqual(copied.number_of_decimals, 42)\n        self.assertEqual(copied.attributes, var.attributes)\n        self.assertIsNot(copied.attributes, var.attributes)\n\n        var = ContinuousVariable(\"foo\", number_of_decimals=42)\n        copied = copy_descriptor(var, \"cux\")\n        self.assertEqual(copied.name, \"cux\")\n\n    def test_copy_descriptor_other_types(self):\n        class SomeVariable(Variable):\n            pass\n        var = SomeVariable(\"foo\")\n        var.attributes = {\"bar\": 42, \"baz\": 13}\n        copied = copy_descriptor(var)\n        self.assertIsInstance(copied, SomeVariable)\n        self.assertEqual(copied.name, \"foo\")\n        self.assertEqual(copied.attributes, var.attributes)\n        self.assertIsNot(copied.attributes, var.attributes)\n\n        copied = copy_descriptor(var, \"cux\")\n        self.assertEqual(copied.name, \"cux\")\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
    ],
    "test_file_names": [
        "test_1.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "Starting Orange installation attempts...\nAttempting installation with Python 3.7...\nWarning: 'keywords' should be a list, got type 'tuple'\nWarning: 'classifiers' should be a list, got type 'tuple'\nWarning: 'keywords' should be a list, got type 'tuple'\nWarning: 'classifiers' should be a list, got type 'tuple'\nrunning build_ext\nrunning build_src\nbuild_src\nbuilding extension \"Orange.classification._simple_tree\" sources\nbuilding extension \"Orange.classification._tree_scorers\" sources\nbuilding extension \"Orange.data._valuecount\" sources\nbuilding extension \"Orange.data._contingency\" sources\nbuilding extension \"Orange.data._io\" sources\nbuilding extension \"Orange.data._variable\" sources\nbuilding extension \"Orange.distance._distance\" sources\nbuilding extension \"Orange.preprocess._discretize\" sources\nbuilding extension \"Orange.preprocess._relieff\" sources\nbuilding extension \"Orange.projection._som\" sources\nbuilding extension \"Orange.widgets.utils._grid_density\" sources\nbuild_src: building npy-pkg config files\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmpwlqj6i0s/tmp\ncreating /tmp/tmpwlqj6i0s/tmp/tmpwlqj6i0s\ncompile options: '-MMD -MF /tmp/tmpwlqj6i0s/file.c.d -c'\ncc: /tmp/tmpwlqj6i0s/file.c\ncustomize UnixCCompiler using build_ext\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmp66olb138/tmp\ncreating /tmp/tmp66olb138/tmp/tmp66olb138\ncompile options: '-MMD -MF /tmp/tmp66olb138/file.c.d -c'\ncc: /tmp/tmp66olb138/file.c\ncustomize UnixCCompiler using build_ext\nbuilding 'Orange.classification._simple_tree' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange\ncreating build/temp.linux-x86_64-3.7/Orange/classification\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/classification/_simple_tree.c\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 Orange/classification/_simple_tree.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.classification._tree_scorers' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/classification/_tree_scorers.c\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 Orange/classification/_tree_scorers.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._valuecount' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/data\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_valuecount.c\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 Orange/data/_valuecount.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._contingency' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_contingency.c\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 Orange/data/_contingency.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._io' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_io.c\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 Orange/data/_io.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._variable' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_variable.c\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 Orange/data/_variable.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.distance._distance' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/distance\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/distance/_distance.c\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 Orange/distance/_distance.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._discretize' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/preprocess\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/preprocess/_discretize.c\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 Orange/preprocess/_discretize.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._relieff' extension\ncompiling C++ sources\nC compiler: c++ -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\nc++: Orange/preprocess/_relieff.cpp\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 Orange/preprocess/_relieff.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.projection._som' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/projection\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/projection/_som.c\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 Orange/projection/_som.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.widgets.utils._grid_density' extension\ncompiling C++ sources\nC compiler: c++ -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/widgets\ncreating build/temp.linux-x86_64-3.7/Orange/widgets/utils\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\nc++: Orange/widgets/utils/_grid_density.cpp\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 Orange/widgets/utils/_grid_density.cpython-37m-x86_64-linux-gnu.so\nWarning: 'keywords' should be a list, got type 'tuple'\nWarning: 'classifiers' should be a list, got type 'tuple'\nWarning: 'keywords' should be a list, got type 'tuple'\nWarning: 'classifiers' should be a list, got type 'tuple'\nrunning develop\nrunning build_scripts\nrunning egg_info\nrunning build_src\nbuild_src\nbuilding extension \"Orange.classification._simple_tree\" sources\nbuilding extension \"Orange.classification._tree_scorers\" sources\nbuilding extension \"Orange.data._valuecount\" sources\nbuilding extension \"Orange.data._contingency\" sources\nbuilding extension \"Orange.data._io\" sources\nbuilding extension \"Orange.data._variable\" sources\nbuilding extension \"Orange.distance._distance\" sources\nbuilding extension \"Orange.preprocess._discretize\" sources\nbuilding extension \"Orange.preprocess._relieff\" sources\nbuilding extension \"Orange.projection._som\" sources\nbuilding extension \"Orange.widgets.utils._grid_density\" sources\nbuild_src: building npy-pkg config files\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'\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'\nadding license file 'LICENSE'\nwriting manifest file 'Orange3.egg-info/SOURCES.txt'\nrunning build_ext\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmpcxme5srr/tmp\ncreating /tmp/tmpcxme5srr/tmp/tmpcxme5srr\ncompile options: '-MMD -MF /tmp/tmpcxme5srr/file.c.d -c'\ncc: /tmp/tmpcxme5srr/file.c\ncustomize UnixCCompiler using build_ext\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmp22od5sxg/tmp\ncreating /tmp/tmp22od5sxg/tmp/tmp22od5sxg\ncompile options: '-MMD -MF /tmp/tmp22od5sxg/file.c.d -c'\ncc: /tmp/tmp22od5sxg/file.c\ncustomize UnixCCompiler using build_ext\nCreating /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/Orange3.egg-link (link to .)\nAdding Orange3 3.24.0.dev0+a5566a6 to easy-install.pth file\nInstalling orange-canvas script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/bin\n\nInstalled /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998\nProcessing dependencies for Orange3==3.24.0.dev0+a5566a6\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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/bin\nInstalling pip3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/bin\nInstalling pip3.10 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages\nSearching for orange-widget-base==4.0.1\nBest match: orange-widget-base 4.0.1\nAdding orange-widget-base 4.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/bin\nInstalling f2py3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/bin\nInstalling f2py3.7 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/bin\nInstalling pyftmerge script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/bin\nInstalling pyftsubset script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/bin\nInstalling ttx script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages\nFinished processing dependencies for Orange3==3.24.0.dev0+a5566a6\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 83ms\nPrepared 18 packages in 4ms\nInstalled 18 packages in 145ms\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 38 packages in 206ms\nInstalled 33 packages in 727ms\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 + 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 + more-itertools==9.1.0\n + networkx==2.6.3\n + opentsne==1.0.1\n + pandas==1.3.5\n + pip==24.0\n + pycparser==2.21\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 + 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 + threadpoolctl==3.1.0\n + urllib3==2.0.7\n + xlrd==2.0.1\n + xlsxwriter==3.2.0\nResolved 34 packages in 229ms\nInstalled 19 packages in 351ms\n + anyqt==0.2.0\n + attrs==24.2.0\n + cattrs==23.1.2\n + commonmark==0.9.1\n + cycler==0.11.0\n + dictdiffer==0.9.0\n + docutils==0.20.1\n + fonttools==4.38.0\n + kiwisolver==1.4.5\n + matplotlib==3.5.3\n + orange-canvas-core==0.1.35\n + orange-widget-base==4.0.1\n + pillow==9.5.0\n + platformdirs==4.0.0\n + pyparsing==3.1.4\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 4ms\nInstalled 2 packages in 29ms\n + psycopg2==2.9.9\n + pymssql==2.3.2\nAudited 1 package in 31ms\nAudited 2 packages in 15ms\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      |         ^~~~~~~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/classification/_tree_scorers.c:567:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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:3115:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 3115 |   __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:4619:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 4619 |   __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:5396:43: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5396 |           for (__pyx_t_22 = 0; __pyx_t_22 < __pyx_t_21; __pyx_t_22+=1) {\n      |                                           ^\nOrange/classification/_tree_scorers.c:5517:39: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5517 |           __pyx_t_27 = ((__pyx_v_left >= __pyx_v_min_leaf) != 0);\n      |                                       ^~\nOrange/classification/_tree_scorers.c:5523:53: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5523 |           __pyx_t_27 = (((__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:7051:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7051 |         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:7741:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7741 |         for (__pyx_t_11 = 0; __pyx_t_11 < __pyx_t_10; __pyx_t_11+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c:7801:143: warning: comparison of integer expressions of different signedness: ‘__pyx_t_5numpy_int32_t’ {aka ‘int’} and ‘unsigned int’ [-Wsign-compare]\n 7801 |               __pyx_t_17 = (((*((__pyx_t_5numpy_int32_t *) ( /* dim=0 */ (__pyx_v_indices.data + __pyx_t_18 * __pyx_v_indices.strides[0]) ))) != __pyx_v_i) != 0);\n      |                                                                                                                                               ^~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/data/_valuecount.c:567:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/data/_contingency.c:567:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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:3881:24: warning: ‘__pyx_v_decs’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3881 |           __pyx_v_decs = (__pyx_v_decs / 10.0);\n      |           ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:4070:24: warning: ‘__pyx_v_value’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4070 |           __pyx_t_10 = PyFloat_FromDouble(__pyx_v_value); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 221, __pyx_L5_error)\n      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:480:40: warning: ‘__pyx_v_attr_index’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n  480 |   #define PyInt_FromLong               PyLong_FromLong\n      |                                        ^~~~~~~~~~~~~~~\nOrange/data/_io.c:1301:7: note: ‘__pyx_v_attr_index’ was declared here\n 1301 |   int __pyx_v_attr_index;\n      |       ^~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:2935:25: warning: ‘__pyx_v_atomp’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 2935 |           __pyx_v_atomp = (__pyx_v_atomp + 1);\n      |           ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/distance/_distance.c:567:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/preprocess/_discretize.c:567:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/preprocess/_relieff.cpp:581:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/projection/_som.c:598:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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:3655:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3655 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:3654:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3654 |   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:4547:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4547 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_38, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_39, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4114:7: note: ‘__pyx_v_win_y’ was declared here\n 4114 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4536:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4536 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_36, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_37, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4113:7: note: ‘__pyx_v_win_x’ was declared here\n 4113 |   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:4746:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4746 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4745:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4745 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_3update’:\nOrange/projection/_som.c:3141:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3141 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_1get_winners’:\nOrange/projection/_som.c:2964:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 2964 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_27, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_28, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2665:7: note: ‘__pyx_v_win_y’ was declared here\n 2665 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:2953:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 2953 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_25, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_26, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2664:7: note: ‘__pyx_v_win_x’ was declared here\n 2664 |   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:5515:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5515 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:5514:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5514 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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,\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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_a5566a65725e55528ed27b2ddfcd93c5573d6998/.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,\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/.venv/lib/python3.7/site-packages/setuptools/command/egg_info.py:628: SetuptoolsDeprecationWarning: Custom 'build_py' does not implement 'get_data_files_without_manifest'.\nPlease extend command classes from setuptools instead of distutils.\n  SetuptoolsDeprecationWarning\n",
    "new_commit_res_code": 1,
    "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_a5566a65725e55528ed27b2ddfcd93c5573d6998\ncollected 18 items\n\nr2e_tests/test_1.py ssE...............                                   [100%]\n\n==================================== ERRORS ====================================\n_______________________ ERROR at setup of test_filename ________________________\nfile /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/Orange/tests/__init__.py, line 65\n  def test_filename(path):\nE       fixture 'path' not found\n>       available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory\n>       use 'pytest --fixtures [testpath]' for help on them.\n\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/Orange/tests/__init__.py:65\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::TestVennDiagram::test_reshape_wide\nPASSED r2e_tests/test_1.py::TestVennDiagram::test_reshape_wide_missing_vals\nPASSED r2e_tests/test_1.py::TestVennDiagram::test_varying_between_missing_vals\nPASSED r2e_tests/test_1.py::TestVennDiagram::test_venn_diagram\nPASSED r2e_tests/test_1.py::TestOWVennDiagram::test_minimum_size\nPASSED r2e_tests/test_1.py::TestOWVennDiagram::test_msg_base_class\nPASSED r2e_tests/test_1.py::TestOWVennDiagram::test_multiple_input\nPASSED r2e_tests/test_1.py::TestOWVennDiagram::test_no_data\nPASSED r2e_tests/test_1.py::TestOWVennDiagram::test_outputs\nPASSED r2e_tests/test_1.py::TestOWVennDiagram::test_unconditional_commit_on_new_signal\nPASSED r2e_tests/test_1.py::GroupTableIndicesTest::test_group_table_indices\nPASSED r2e_tests/test_1.py::GroupTableIndicesTest::test_varying_between_combined\nPASSED r2e_tests/test_1.py::TestVennUtilities::test_copy_descriptor_continuous\nPASSED r2e_tests/test_1.py::TestVennUtilities::test_copy_descriptor_discrete\nPASSED r2e_tests/test_1.py::TestVennUtilities::test_copy_descriptor_other_types\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:86: minimum size not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:92: msg base class not tested as .widget was not set\nERROR r2e_tests/test_1.py::test_filename\n==================== 15 passed, 2 skipped, 1 error in 4.51s ====================\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_a5566a65725e55528ed27b2ddfcd93c5573d6998\ncollected 18 items\n\nr2e_tests/test_1.py ssE.........F.....                                   [100%]\n\n==================================== ERRORS ====================================\n_______________________ ERROR at setup of test_filename ________________________\nfile /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/Orange/tests/__init__.py, line 65\n  def test_filename(path):\nE       fixture 'path' not found\n>       available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory\n>       use 'pytest --fixtures [testpath]' for help on them.\n\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_a5566a65725e55528ed27b2ddfcd93c5573d6998/Orange/tests/__init__.py:65\n=================================== FAILURES ===================================\n__________ TestOWVennDiagram.test_unconditional_commit_on_new_signal ___________\n\nself = <r2e_tests.test_1.TestOWVennDiagram testMethod=test_unconditional_commit_on_new_signal>\n\n    def test_unconditional_commit_on_new_signal(self):\n        with patch.object(self.widget, 'unconditional_commit') as commit:\n            self.widget.autocommit = False\n            commit.reset_mock()\n            self.send_signal(self.signal_name, self.data[:100], 1)\n>           commit.assert_called()\n\nr2e_tests/test_1.py:189: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\n_mock_self = <MagicMock name='unconditional_commit' id='138574700435088'>\n\n    def assert_called(_mock_self):\n        \"\"\"assert that the mock was called at least once\n        \"\"\"\n        self = _mock_self\n        if self.call_count == 0:\n            msg = (\"Expected '%s' to have been called.\" %\n                   self._mock_name or 'mock')\n>           raise AssertionError(msg)\nE           AssertionError: Expected 'unconditional_commit' to have been called.\n\n../../../../.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib/python3.7/unittest/mock.py:850: AssertionError\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::TestVennDiagram::test_reshape_wide\nPASSED r2e_tests/test_1.py::TestVennDiagram::test_reshape_wide_missing_vals\nPASSED r2e_tests/test_1.py::TestVennDiagram::test_varying_between_missing_vals\nPASSED r2e_tests/test_1.py::TestVennDiagram::test_venn_diagram\nPASSED r2e_tests/test_1.py::TestOWVennDiagram::test_minimum_size\nPASSED r2e_tests/test_1.py::TestOWVennDiagram::test_msg_base_class\nPASSED r2e_tests/test_1.py::TestOWVennDiagram::test_multiple_input\nPASSED r2e_tests/test_1.py::TestOWVennDiagram::test_no_data\nPASSED r2e_tests/test_1.py::TestOWVennDiagram::test_outputs\nPASSED r2e_tests/test_1.py::GroupTableIndicesTest::test_group_table_indices\nPASSED r2e_tests/test_1.py::GroupTableIndicesTest::test_varying_between_combined\nPASSED r2e_tests/test_1.py::TestVennUtilities::test_copy_descriptor_continuous\nPASSED r2e_tests/test_1.py::TestVennUtilities::test_copy_descriptor_discrete\nPASSED r2e_tests/test_1.py::TestVennUtilities::test_copy_descriptor_other_types\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:86: minimum size not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:92: msg base class not tested as .widget was not set\nERROR r2e_tests/test_1.py::test_filename\nFAILED r2e_tests/test_1.py::TestOWVennDiagram::test_unconditional_commit_on_new_signal\n=============== 1 failed, 14 passed, 2 skipped, 1 error in 4.19s ===============\n",
    "old_commit_res_stderr": ""
}
Orange/widgets/visualize/owvenndiagram.py Orange/widgets/visualize/tests/test_owvenndiagram.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** OWVennDiagram Does Not Commit Data When Autocommit Is Disabled

**Description:**
When the `autocommit` feature is disabled in the `OWVennDiagram` widget, sending new data does not trigger a commit. As a result, the widget fails to output the expected data, leaving the output empty despite receiving valid input.

**Example Code:**
```python
def test_disable_autocommit():
    widget = OWVennDiagram()
    widget.autocommit = False
    widget.send_signal("data", new_data)
    # Expected: Data is committed and output is available
    # Actual: No data is committed, and output remains empty
```

**Expected Behavior:**
With `autocommit` turned off, the widget should still commit incoming data upon receiving new signals to ensure that the data is processed and output correctly.

**Actual Behavior:**
When `autocommit` is disabled, sending new data does not result in a commit, and the widget does not produce any output data.

[/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/visualize/owvenndiagram.py
"""
Venn Diagram Widget
-------------------

"""

import math
import unicodedata
from collections import namedtuple, defaultdict, OrderedDict, Counter
from itertools import count
from functools import reduce
from xml.sax.saxutils import escape

import numpy

from AnyQt.QtWidgets import (
    QComboBox, QGraphicsScene, QGraphicsView, QGraphicsWidget,
    QGraphicsPathItem, QGraphicsTextItem, QStyle
)
from AnyQt.QtGui import (
    QPainterPath, QPainter, QTransform, QColor, QBrush, QPen, QPalette
)
from AnyQt.QtCore import Qt, QPointF, QRectF, QLineF
from AnyQt.QtCore import pyqtSignal as Signal

import Orange.data
from Orange.statistics import util
from Orange.widgets import widget, gui, settings
from Orange.widgets.utils import itemmodels, colorpalette
from Orange.widgets.utils.annotated_data import (create_annotated_table,
                                                 ANNOTATED_DATA_SIGNAL_NAME)
from Orange.widgets.utils.sql import check_sql_input
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.widget import Input, Output


_InputData = namedtuple("_InputData", ["key", "name", "table"])
_ItemSet = namedtuple("_ItemSet", ["key", "name", "title", "items"])


class OWVennDiagram(widget.OWWidget):
    name = "Venn Diagram"
    description = "A graphical visualization of the overlap of data instances " \
                  "from a collection of input datasets."
    icon = "icons/VennDiagram.svg"
    priority = 280
    keywords = []

    class Inputs:
        data = Input("Data", Orange.data.Table, multiple=True)

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

    inputhints: dict
    selection: list

    # Selected disjoint subset indices
    selection = settings.Setting([])
    #: Stored input set hints
    #: {(index, inputname, attributes): (selectedattrname, itemsettitle)}
    #: The 'selectedattrname' can be None
    inputhints = settings.Setting({})
    #: Use identifier columns for instance matching
    useidentifiers = settings.Setting(True)
    #: Output unique items (one output row for every unique instance `key`)
    #: or preserve all duplicates in the output.
    output_duplicates = settings.Setting(False)
    autocommit = settings.Setting(True)

    graph_name = "scene"

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

        # Diagram update is in progress
        self._updating = False
        # Input update is in progress
        self._inputUpdate = False
        # All input tables have the same domain.
        self.samedomain = True
        # Input datasets in the order they were 'connected'.
        self.data = OrderedDict()
        # Extracted input item sets in the order they were 'connected'
        self.itemsets = OrderedDict()

        # GUI
        box = gui.vBox(self.controlArea, "Info")
        self.infolabel = gui.widgetLabel(box, "No data on input.\n")

        self.identifiersBox = gui.radioButtonsInBox(
            self.controlArea, self, "useidentifiers", [],
            box="Data Instance Identifiers",
            callback=self._on_useidentifiersChanged
        )
        self.useequalityButton = gui.appendRadioButton(
            self.identifiersBox, "Use instance equality"
        )
        self.useidentifiersButton = rb = gui.appendRadioButton(
            self.identifiersBox, "Use identifiers"
        )
        self.inputsBox = gui.indentedBox(
            self.identifiersBox, sep=gui.checkButtonOffsetHint(rb)
        )
        self.inputsBox.setEnabled(bool(self.useidentifiers))

        for i in range(5):
            box = gui.vBox(self.inputsBox, "Dataset #%i" % (i + 1),
                           addSpace=False)
            box.setFlat(True)
            model = itemmodels.VariableListModel(parent=self)
            cb = gui.OrangeComboBox(
                minimumContentsLength=12,
                sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon
            )
            cb.setModel(model)
            cb.activated[int].connect(self._on_inputAttrActivated)
            box.setEnabled(False)
            # Store the combo in the box for later use.
            box.combo_box = cb
            box.layout().addWidget(cb)

        gui.rubber(self.controlArea)

        box = gui.vBox(self.controlArea, "Output")
        gui.checkBox(box, self, "output_duplicates", "Output duplicates",
                     callback=lambda: self.commit())
        gui.auto_commit(box, self, "autocommit", "Send Selection", "Send Automatically", box=False)

        # Main area view
        self.scene = QGraphicsScene()
        self.view = QGraphicsView(self.scene)
        self.view.setRenderHint(QPainter.Antialiasing)
        self.view.setBackgroundRole(QPalette.Window)
        self.view.setFrameStyle(QGraphicsView.StyledPanel)

        self.mainArea.layout().addWidget(self.view)
        self.vennwidget = VennDiagram()
        self.vennwidget.resize(400, 400)
        self.vennwidget.itemTextEdited.connect(self._on_itemTextEdited)
        self.scene.selectionChanged.connect(self._on_selectionChanged)

        self.scene.addItem(self.vennwidget)

        self.resize(self.controlArea.sizeHint().width() + 550,
                    max(self.controlArea.sizeHint().height(), 550))

        self._queue = []

    @Inputs.data
    @check_sql_input
    def setData(self, data, key=None):
        self.error()
        if not self._inputUpdate:
            # Store hints only on the first setData call.
            self._storeHints()
            self._inputUpdate = True

        if key in self.data:
            if data is None:
                # Remove the input
                self._remove(key)
            else:
                # Update existing item
                self._update(key, data)
        elif data is not None:
            # TODO: Allow setting more them 5 inputs and let the user
            # select the 5 to display.
            if len(self.data) == 5:
                self.error("Venn diagram accepts at most five datasets.")
                return
            # Add a new input
            self._add(key, data)

    def handleNewSignals(self):
        self._inputUpdate = False

        # Check if all inputs are from the same domain.
        domains = [input.table.domain for input in self.data.values()]
        samedomain = all(domain_eq(d1, d2) for d1, d2 in pairwise(domains))

        self.samedomain = samedomain

        has_identifiers = all(source_attributes(input.table.domain)
                              for input in self.data.values())
        has_any_identifiers = any(source_attributes(input.table.domain)
                                  for input in self.data.values())
        self.useequalityButton.setEnabled(samedomain)
        self.useidentifiersButton.setEnabled(
            has_any_identifiers or len(self.data) == 0)
        self.inputsBox.setEnabled(has_any_identifiers)

        if not samedomain and has_any_identifiers and not self.useidentifiers:
            self.useidentifiers = 1
        elif samedomain and not has_identifiers:
            self.useidentifiers = 0

        incremental = all(inc for _, inc in self._queue)

        if incremental:
            # Only received updated data on existing link.
            self._updateItemsets()
        else:
            # Links were removed and/or added.
            self._createItemsets()
            self._restoreHints()
            self._updateItemsets()

        del self._queue[:]

        self._createDiagram()
        if self.data:
            self.infolabel.setText(f"{len(self.data)} datasets on input.\n")
        else:
            self.infolabel.setText("No data on input\n")

        self._updateInfo()
        super().handleNewSignals()

    def _invalidate(self, keys=None, incremental=True):
        """
        Invalidate input for a list of input keys.
        """
        if keys is None:
            keys = list(self.data.keys())

        self._queue.extend((key, incremental) for key in keys)

    def itemsetAttr(self, key):
        index = list(self.data.keys()).index(key)
        _, combo = self._controlAtIndex(index)
        model = combo.model()
        attr_index = combo.currentIndex()
        if attr_index >= 0:
            return model[attr_index]
        else:
            return None

    def _controlAtIndex(self, index):
        group_box = self.inputsBox.layout().itemAt(index).widget()
        combo = group_box.combo_box
        return group_box, combo

    def _setAttributes(self, index, attrs):
        box, combo = self._controlAtIndex(index)
        model = combo.model()

        if attrs is None:
            model[:] = []
            box.setEnabled(False)
        else:
            if model[:] != attrs:
                model[:] = attrs

            box.setEnabled(True)

    def _add(self, key, table):
        name = table.name
        index = len(self.data)
        attrs = source_attributes(table.domain)

        self.data[key] = _InputData(key, name, table)

        self._setAttributes(index, attrs)

        self._invalidate([key], incremental=False)

        item = self.inputsBox.layout().itemAt(index)
        box = item.widget()
        box.setTitle("Dataset: {}".format(name))

    def _remove(self, key):
        index = list(self.data.keys()).index(key)

        # Clear possible warnings.
        self.warning()

        self._setAttributes(index, None)

        del self.data[key]

        layout = self.inputsBox.layout()
        item = layout.takeAt(index)
        layout.addItem(item)
        inputs = list(self.data.values())

        for i in range(5):
            box, _ = self._controlAtIndex(i)
            if i < len(inputs):
                title = "Dataset: {}".format(inputs[i].name)
            else:
                title = "Dataset #{}".format(i + 1)
            box.setTitle(title)

        self._invalidate([key], incremental=False)

    def _update(self, key, table):
        name = table.name
        index = list(self.data.keys()).index(key)
        attrs = source_attributes(table.domain)

        self.data[key] = self.data[key]._replace(name=name, table=table)

        self._setAttributes(index, attrs)
        self._invalidate([key])

        item = self.inputsBox.layout().itemAt(index)
        box = item.widget()
        box.setTitle("Dataset: {}".format(name))

    def _itemsForInput(self, key):
        useidentifiers = self.useidentifiers or not self.samedomain

        def items_by_key(key, input):
            attr = self.itemsetAttr(key)
            if attr is not None:
                return [str(inst[attr]) for inst in input.table
                        if not numpy.isnan(inst[attr])]
            else:
                return []

        def items_by_eq(key, input):
            return list(map(ComparableInstance, input.table))

        input = self.data[key]
        if useidentifiers:
            items = items_by_key(key, input)
        else:
            items = items_by_eq(key, input)
        return items

    def _updateItemsets(self):
        assert list(self.data.keys()) == list(self.itemsets.keys())
        for key, input in list(self.data.items()):
            items = self._itemsForInput(key)
            item = self.itemsets[key]
            item = item._replace(items=items)
            name = input.name
            if item.name != name:
                item = item._replace(name=name, title=name)
            self.itemsets[key] = item

    def _createItemsets(self):
        olditemsets = dict(self.itemsets)
        self.itemsets.clear()

        for key, input in self.data.items():
            items = self._itemsForInput(key)
            name = input.name
            if key in olditemsets and olditemsets[key].name == name:
                # Reuse the title (which might have been changed by the user)
                title = olditemsets[key].title
            else:
                title = name

            itemset = _ItemSet(key=key, name=name, title=title, items=items)
            self.itemsets[key] = itemset

    def _storeHints(self):
        if self.data:
            self.inputhints.clear()
            for i, (key, input) in enumerate(self.data.items()):
                attrs = source_attributes(input.table.domain)
                attrs = tuple(attr.name for attr in attrs)
                selected = self.itemsetAttr(key)
                if selected is not None:
                    attr_name = selected.name
                else:
                    attr_name = None
                itemset = self.itemsets[key]
                self.inputhints[(i, input.name, attrs)] = \
                    (attr_name, itemset.title)

    def _restoreHints(self):
        settings = []
        for i, (key, input) in enumerate(self.data.items()):
            attrs = source_attributes(input.table.domain)
            attrs = tuple(attr.name for attr in attrs)
            hint = self.inputhints.get((i, input.name, attrs), None)
            if hint is not None:
                attr, name = hint
                attr_ind = attrs.index(attr) if attr is not None else -1
                settings.append((attr_ind, name))
            else:
                return

        # all inputs match the stored hints
        for i, key in enumerate(self.itemsets):
            attr, itemtitle = settings[i]
            self.itemsets[key] = self.itemsets[key]._replace(title=itemtitle)
            _, cb = self._controlAtIndex(i)
            cb.setCurrentIndex(attr)

    def _createDiagram(self):
        self._updating = True

        oldselection = list(self.selection)

        self.vennwidget.clear()
        n = len(self.itemsets)
        self.disjoint = disjoint(set(s.items) for s in self.itemsets.values())

        vennitems = []
        colors = colorpalette.ColorPaletteHSV(n)

        for i, (_, item) in enumerate(self.itemsets.items()):
            count = len(set(item.items))
            count_all = len(item.items)
            if count != count_all:
                fmt = '{} <i>(all: {})</i>'
            else:
                fmt = '{}'
            counts = fmt.format(count, count_all)
            gr = VennSetItem(text=item.title, informativeText=counts)
            color = colors[i]
            color.setAlpha(100)
            gr.setBrush(QBrush(color))
            gr.setPen(QPen(Qt.NoPen))
            vennitems.append(gr)

        self.vennwidget.setItems(vennitems)

        for i, area in enumerate(self.vennwidget.vennareas()):
            area_items = list(map(str, list(self.disjoint[i])))
            if i:
                area.setText("{0}".format(len(area_items)))

            label = disjoint_set_label(i, n, simplify=False)
            head = "<h4>|{}| = {}</h4>".format(label, len(area_items))
            if len(area_items) > 32:
                items_str = ", ".join(map(escape, area_items[:32]))
                hidden = len(area_items) - 32
                tooltip = ("{}<span>{}, ...</br>({} items not shown)<span>"
                           .format(head, items_str, hidden))
            elif area_items:
                tooltip = "{}<span>{}</span>".format(
                    head,
                    ", ".join(map(escape, area_items))
                )
            else:
                tooltip = head

            area.setToolTip(tooltip)

            area.setPen(QPen(QColor(10, 10, 10, 200), 1.5))
            area.setFlag(QGraphicsPathItem.ItemIsSelectable, True)
            area.setSelected(i in oldselection)

        self._updating = False
        self._on_selectionChanged()

    def _updateInfo(self):
        # Clear all warnings
        self.warning()

        if not len(self.data):
            self.infolabel.setText("No data on input\n")
        else:
            self.infolabel.setText(f"{len(self.data)} datasets on input\n")

        if self.useidentifiers:
            no_idx = ["#{}".format(i + 1)
                      for i, key in enumerate(self.data)
                      if not source_attributes(self.data[key].table.domain)]
            if len(no_idx) == 1:
                self.warning("Dataset {} has no suitable identifiers."
                             .format(no_idx[0]))
            elif len(no_idx) > 1:
                self.warning("Datasets {} and {} have no suitable identifiers."
                             .format(", ".join(no_idx[:-1]), no_idx[-1]))

    def _on_selectionChanged(self):
        if self._updating:
            return

        areas = self.vennwidget.vennareas()
        indices = [i for i, area in enumerate(areas)
                   if area.isSelected()]

        self.selection = indices

        self.invalidateOutput()

    def _on_useidentifiersChanged(self):
        self.inputsBox.setEnabled(self.useidentifiers == 1)
        # Invalidate all itemsets
        self._invalidate()
        self._updateItemsets()
        self._createDiagram()

        self._updateInfo()

    def _on_inputAttrActivated(self, attr_index):
        combo = self.sender()
        # Find the input index to which the combo box belongs
        # (they are reordered when removing inputs).
        index = None
        inputs = list(self.data.items())
        for i in range(len(inputs)):
            _, c = self._controlAtIndex(i)
            if c is combo:
                index = i
                break

        assert index is not None

        key, _ = inputs[index]

        self._invalidate([key])
        self._updateItemsets()
        self._createDiagram()

    def _on_itemTextEdited(self, index, text):
        text = str(text)
        key = list(self.itemsets.keys())[index]
        self.itemsets[key] = self.itemsets[key]._replace(title=text)

    def invalidateOutput(self):
        self.commit()

    def commit(self):
        selected_subsets = []
        annotated_data = None
        annotated_data_subsets = []
        annotated_data_masks = []
        selected_items = reduce(
            set.union, [self.disjoint[index] for index in self.selection],
            set()
        )

        def match(val):
            if numpy.isnan(val):
                return False
            else:
                return str(val) in selected_items

        source_var = Orange.data.StringVariable("source")
        item_id_var = Orange.data.StringVariable("item_id")

        names = [itemset.title.strip() for itemset in self.itemsets.values()]
        names = uniquify(names)

        for i, (key, input) in enumerate(self.data.items()):
            # cell vars are in functions that are only used in the loop
            # pylint: disable=cell-var-from-loop
            if not len(input.table):
                continue
            if self.useidentifiers:
                attr = self.itemsetAttr(key)
                if attr is not None:
                    mask = list(map(match, (inst[attr] for inst in input.table)))
                else:
                    mask = [False] * len(input.table)

                def instance_key(inst):
                    return str(inst[attr])

                def instance_key_all(inst):
                    return str(inst[attr])
            else:
                mask = [ComparableInstance(inst) in selected_items
                        for inst in input.table]
                _map = {item: str(i) for i, item in enumerate(selected_items)}
                _map_all = {
                    ComparableInstance(i):  hash(ComparableInstance(i))
                    for i in input.table
                }

                def instance_key(inst):
                    return _map[ComparableInstance(inst)]

                def instance_key_all(inst):
                    return _map_all[ComparableInstance(inst)]

            mask = numpy.array(mask, dtype=bool)
            subset = input.table[mask]

            annotated_subset = input.table
            id_column = numpy.array(
                [[instance_key_all(inst)] for inst in annotated_subset])
            source_names = numpy.array([[names[i]]] * len(annotated_subset))
            annotated_subset = append_column(
                annotated_subset, "M", source_var, source_names)
            annotated_subset = append_column(
                annotated_subset, "M", item_id_var, id_column)
            annotated_data_subsets.append(annotated_subset)
            annotated_data_masks.append(mask)

            if len(subset) == 0:
                continue

            # add columns with source table id and set id

            if not self.output_duplicates:
                id_column = numpy.array([[instance_key(inst)] for inst in subset],
                                        dtype=object)
                source_names = numpy.array([[names[i]]] * len(subset),
                                           dtype=object)

                subset = append_column(subset, "M", source_var, source_names)
                subset = append_column(subset, "M", item_id_var, id_column)

            selected_subsets.append(subset)

        if selected_subsets and not self.output_duplicates:
            data = table_concat(selected_subsets)
            # Get all variables which are not constant between the same
            # item set
            varying = varying_between(data, item_id_var)

            if source_var in varying:
                varying.remove(source_var)

            data = reshape_wide(data, varying, [item_id_var], [source_var])
            # remove the temporary item set id column
            data = drop_columns(data, [item_id_var])
        elif selected_subsets:
            data = table_concat(selected_subsets)
        else:
            data = None

        if annotated_data_subsets:
            annotated_data = table_concat(annotated_data_subsets)
            indices = numpy.hstack(annotated_data_masks)
            annotated_data = create_annotated_table(annotated_data, indices)
            varying = varying_between(annotated_data, item_id_var)
            if source_var in varying:
                varying.remove(source_var)
            annotated_data = reshape_wide(annotated_data, varying,
                                          [item_id_var], [source_var])
            annotated_data = drop_columns(annotated_data, [item_id_var])

        self.Outputs.selected_data.send(data)
        self.Outputs.annotated_data.send(annotated_data)

    def getSettings(self, *args, **kwargs):
        self._storeHints()
        return super().getSettings(self, *args, **kwargs)

    def send_report(self):
        self.report_plot()


def pairwise(iterable):
    """
    Return an iterator over consecutive pairs in `iterable`.

    >>> list(pairwise([1, 2, 3, 4])
    [(1, 2), (2, 3), (3, 4)]

    """
    it = iter(iterable)
    try:
        first = next(it)
    except StopIteration:
        return
    for second in it:
        yield first, second
        first = second


# Custom domain comparison (domains do not seem to compare equal
# even if they have exactly the same variables).
# TODO: What about metas.
def domain_eq(d1, d2):
    return d1.variables == d2.variables


# Comparing/hashing Orange.data.Instance across domains ignoring metas.
class ComparableInstance:
    __slots__ = ["inst", "domain", "__hash"]

    def __init__(self, inst):
        self.inst = inst
        self.domain = inst.domain
        self.__hash = hash((self.inst.x.data.tobytes(),
                            self.inst.y.data.tobytes()))

    def __hash__(self):
        return self.__hash

    def __eq__(self, other):
        # XXX: comparing NaN with different payload
        return (isinstance(other, ComparableInstance)
                and domain_eq(self.domain, other.domain)
                and self.inst.x.data.tobytes() == other.inst.x.data.tobytes()
                and self.inst.y.data.tobytes() == other.inst.y.data.tobytes())

    def __iter__(self):
        return iter(self.inst)

    def __repr__(self):
        return repr(self.inst)

    def __str__(self):
        return str(self.inst)


def table_concat(tables):
    """
    Concatenate a list of tables.

    The resulting table will have a union of all attributes of `tables`.

    """
    attributes = []
    class_vars = []
    metas = []
    variables_seen = set()

    for table in tables:
        attributes.extend(v for v in table.domain.attributes
                          if v not in variables_seen)
        variables_seen.update(table.domain.attributes)

        class_vars.extend(v for v in table.domain.class_vars
                          if v not in variables_seen)
        variables_seen.update(table.domain.class_vars)

        metas.extend(v for v in table.domain.metas
                     if v not in variables_seen)

        variables_seen.update(table.domain.metas)

    domain = Orange.data.Domain(attributes, class_vars, metas)

    tables = [tab.transform(domain) for tab in tables]
    return tables[0].concatenate(tables, axis=0)


def copy_descriptor(descriptor, newname=None):
    """
    Create a copy of the descriptor.

    If newname is not None the new descriptor will have the same
    name as the input.

    """
    if newname is None:
        newname = descriptor.name

    if descriptor.is_discrete:
        newf = Orange.data.DiscreteVariable(
            newname,
            values=descriptor.values,
            ordered=descriptor.ordered,
        )
        newf.attributes = dict(descriptor.attributes)

    elif descriptor.is_continuous:
        newf = Orange.data.ContinuousVariable(newname)
        newf.number_of_decimals = descriptor.number_of_decimals
        newf.attributes = dict(descriptor.attributes)

    else:
        newf = type(descriptor)(newname)
        newf.attributes = dict(descriptor.attributes)

    return newf


def reshape_wide(table, varlist, idvarlist, groupvarlist):
    """
    Reshape a data table into a wide format.

    :param Orange.data.Table table:
        Source data table in long format.
    :param varlist:
        A list of variables to reshape.
    :param list idvarlist:
        A list of variables in `table` uniquely identifying multiple
        records in `table` (i.e. subject id).
    :param groupvarlist:
        A list of variables differentiating multiple records
        (i.e. conditions).

    """
    def inst_key(inst, vars):
        return tuple(str(inst[var]) for var in vars)

    instance_groups = [inst_key(inst, groupvarlist) for inst in table]
    # A list of groups (for each element in a group the varying variable
    # will be duplicated)
    groups = list(unique(instance_groups))
    group_names = [", ".join(group) for group in groups]

    # A list of instance ids (subject ids)
    # Each instance in the output will correspond to one of these ids)
    instance_ids = [inst_key(inst, idvarlist) for inst in table]
    ids = list(unique(instance_ids))

    # an mapping from ids to an list of input instance indices
    # each instance in this list belongs to one group (but not all
    # groups need to be present).
    inst_by_id = defaultdict(list)
    id_by_inst = defaultdict(list)  # inverse mapping

    for i in range(len(table)):
        inst_id = instance_ids[i]
        inst_by_id[inst_id].append(i)
        id_by_inst[i] = inst_id

    newfeatures = []
    newclass_vars = []
    newmetas = []
    expanded_features = {}

    def expanded(feat):
        return [copy_descriptor(feat, newname="%s (%s)" %
                                (feat.name, group_name))
                for group_name in group_names]

    for feat in table.domain.attributes:
        if feat in varlist:
            features = expanded(feat)
            newfeatures.extend(features)
            expanded_features[feat] = dict(zip(groups, features))
        elif feat not in groupvarlist:
            newfeatures.append(feat)

    for feat in table.domain.class_vars:
        if feat in varlist:
            features = expanded(feat)
            newclass_vars.extend(features)
            expanded_features[feat] = dict(zip(groups, features))
        elif feat not in groupvarlist:
            newclass_vars.append(feat)

    for meta in table.domain.metas:
        if meta in varlist:
            metas = expanded(meta)
            newmetas.extend(metas)
            expanded_features[meta] = dict(zip(groups, metas))
        elif meta not in groupvarlist:
            newmetas.append(meta)

    domain = Orange.data.Domain(newfeatures, newclass_vars, newmetas)
    prototype_indices = [inst_by_id[inst_id][0] for inst_id in ids]
    newtable = table[prototype_indices].transform(domain)
    in_expanded = set(f for efd in expanded_features.values() for f in efd.values())

    # Fill-in nan values
    for var in domain.variables + domain.metas:
        if var in idvarlist or var in in_expanded:
            continue
        col, _ = newtable.get_column_view(var)
        nan_indices = (i for i in col.nonzero()[0]
                       if isinstance(col[i], str) or numpy.isnan(col[i]))
        for i in nan_indices:
            for ind in inst_by_id[ids[i]]:
                if not numpy.isnan(table[ind, var]):
                    newtable[i, var] = table[ind, var]
                    break

    # Fill-in expanded features if any
    for i, inst_id in enumerate(ids):
        indices = inst_by_id[inst_id]
        instance = newtable[i]

        for index in indices:
            source_inst = table[index]
            group = instance_groups[index]
            for source_var in varlist:
                newf = expanded_features[source_var][group]
                instance[newf] = source_inst[source_var]

    return newtable


def unique(seq):
    """
    Return an iterator over unique items of `seq`.

    .. note:: Items must be hashable.

    """
    seen = set()
    for item in seq:
        if item not in seen:
            yield item
            seen.add(item)


def unique_non_nan(ar):
    # metas have sometimes object dtype, but values are numpy floats
    ar = ar.astype('float64')
    uniq = numpy.unique(ar)
    return uniq[~numpy.isnan(uniq)]


def varying_between(table, idvar):
    """
    Return a list of all variables with non constant values between
    groups defined by `idvar`.

    """
    all_possible = [var for var in table.domain.variables + table.domain.metas
                    if var != idvar]
    candidate_set = set(all_possible)

    idmap = group_table_indices(table, idvar)

    varying = set()
    for indices in idmap.values():
        subset = table[indices]
        for var in list(candidate_set):
            column, _ = subset.get_column_view(var)
            with numpy.errstate(invalid="ignore"):  # nans are removed below
                values = util.unique(column)

            if not var.is_string:
                values = unique_non_nan(values)

            if len(values) > 1:
                varying.add(var)
                candidate_set.remove(var)

    return sorted(varying, key=all_possible.index)


def uniquify(strings):
    """
    Return a list of unique strings.

    The string at i'th position will have the same prefix as strings[i]
    with an appended suffix to make the item unique (if necessary).

    >>> uniquify(["cat", "dog", "cat"])
    ["cat 1", "dog", "cat 2"]

    """
    counter = Counter(strings)
    counts = defaultdict(count)
    newstrings = []
    for string in strings:
        if counter[string] > 1:
            newstrings.append(string + (" %i" % (next(counts[string]) + 1)))
        else:
            newstrings.append(string)

    return newstrings


def string_attributes(domain):
    """
    Return all string attributes from the domain.
    """
    return [attr for attr in domain.variables + domain.metas if attr.is_string]


def discrete_attributes(domain):
    """
    Return all discrete attributes from the domain.
    """
    return [attr for attr in domain.variables + domain.metas if attr.is_discrete]


def source_attributes(domain):
    """
    Return all suitable attributes for the venn diagram.
    """
    return string_attributes(domain)  # + discrete_attributes(domain)


def disjoint(sets):
    """
    Return all disjoint subsets.
    """
    sets = list(sets)
    n = len(sets)
    disjoint_sets = [None] * (2 ** n)
    for i in range(2 ** n):
        key = setkey(i, n)
        included = [s for s, inc in zip(sets, key) if inc]
        excluded = [s for s, inc in zip(sets, key) if not inc]
        if any(included):
            s = reduce(set.intersection, included)
        else:
            s = set()

        s = reduce(set.difference, excluded, s)

        disjoint_sets[i] = s

    return disjoint_sets


def disjoint_set_label(i, n, simplify=False):
    """
    Return a html formated label for a disjoint set indexed by `i`.
    """
    intersection = unicodedata.lookup("INTERSECTION")
    # comp = unicodedata.lookup("COMPLEMENT")  #
    # This depends on the font but the unicode complement in
    # general does not look nice in a super script so we use
    # plain c instead.
    comp = "c"

    def label_for_index(i):
        return chr(ord("A") + i)

    if simplify:
        return "".join(label_for_index(i) for i, b in enumerate(setkey(i, n))
                       if b)
    else:
        return intersection.join(label_for_index(i) +
                                 ("" if b else "<sup>" + comp + "</sup>")
                                 for i, b in enumerate(setkey(i, n)))


class VennSetItem(QGraphicsPathItem):
    def __init__(self, parent=None, text="", informativeText=""):
        super(VennSetItem, self).__init__(parent)
        # Plain text title (editable by the VennDiagram)
        self.text = text
        # Extra informative text (possibly rich text)
        self.informativeText = informativeText

# TODO: Use palette's selected/highligted text / background colors to
# indicate selection

class VennIntersectionArea(QGraphicsPathItem):
    def __init__(self, parent=None, text=""):
        super().__init__(parent)
        self.setAcceptHoverEvents(True)
        self.setPen(QPen(Qt.NoPen))

        self.text = QGraphicsTextItem(self)
        layout = self.text.document().documentLayout()
        layout.documentSizeChanged.connect(self._onLayoutChanged)

        self._text = ""
        self._anchor = QPointF()

    def setText(self, text):
        if self._text != text:
            self._text = text
            self.text.setPlainText(text)

    def setTextAnchor(self, pos):
        if self._anchor != pos:
            self._anchor = pos
            self._updateTextAnchor()

    def hoverEnterEvent(self, event):
        self.setZValue(self.zValue() + 1)
        return QGraphicsPathItem.hoverEnterEvent(self, event)

    def hoverLeaveEvent(self, event):
        self.setZValue(self.zValue() - 1)
        return QGraphicsPathItem.hoverLeaveEvent(self, event)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            if event.modifiers() & Qt.AltModifier:
                self.setSelected(False)
            elif event.modifiers() & Qt.ControlModifier:
                self.setSelected(not self.isSelected())
            elif event.modifiers() & Qt.ShiftModifier:
                self.setSelected(True)
            else:
                for area in self.parentWidget().vennareas():
                    area.setSelected(False)
                self.setSelected(True)

    def mouseReleaseEvent(self, event):
        pass

    def paint(self, painter, option, widget=None):
        painter.save()
        path = self.path()
        brush = QBrush(self.brush())
        pen = QPen(self.pen())

        if option.state & QStyle.State_Selected:
            pen.setColor(Qt.red)
            brush.setStyle(Qt.DiagCrossPattern)
            brush.setColor(QColor(40, 40, 40, 100))

        elif option.state & QStyle.State_MouseOver:
            pen.setColor(Qt.blue)

        if option.state & QStyle.State_MouseOver:
            brush.setColor(QColor(100, 100, 100, 100))
            if brush.style() == Qt.NoBrush:
                # Make sure the highlight is actually visible.
                brush.setStyle(Qt.SolidPattern)

        painter.setPen(pen)
        painter.setBrush(brush)
        painter.drawPath(path)
        painter.restore()

    def itemChange(self, change, value):
        if change == QGraphicsPathItem.ItemSelectedHasChanged:
            self.setZValue(self.zValue() + (1 if value else -1))
        return QGraphicsPathItem.itemChange(self, change, value)

    def _updateTextAnchor(self):
        rect = self.text.boundingRect()
        pos = anchor_rect(rect, self._anchor)
        self.text.setPos(pos)

    def _onLayoutChanged(self):
        self._updateTextAnchor()


class GraphicsTextEdit(QGraphicsTextItem):
    #: Edit triggers
    NoEditTriggers, DoubleClicked = 0, 1

    editingFinished = Signal()
    editingStarted = Signal()

    documentSizeChanged = Signal()

    def __init__(self, *args, **kwargs):
        super(GraphicsTextEdit, self).__init__(*args, **kwargs)
        self.setCursor(Qt.IBeamCursor)
        self.setTabChangesFocus(True)
        self._edittrigger = GraphicsTextEdit.DoubleClicked
        self._editing = False
        self.document().documentLayout().documentSizeChanged.connect(
            self.documentSizeChanged
        )

    def mouseDoubleClickEvent(self, event):
        super(GraphicsTextEdit, self).mouseDoubleClickEvent(event)
        if self._edittrigger == GraphicsTextEdit.DoubleClicked:
            self._start()

    def focusOutEvent(self, event):
        super(GraphicsTextEdit, self).focusOutEvent(event)

        if self._editing:
            self._end()

    def _start(self):
        self._editing = True
        self.setTextInteractionFlags(Qt.TextEditorInteraction)
        self.setFocus(Qt.MouseFocusReason)
        self.editingStarted.emit()

    def _end(self):
        self._editing = False
        self.setTextInteractionFlags(Qt.NoTextInteraction)
        self.editingFinished.emit()


class VennDiagram(QGraphicsWidget):
    # rect and petal are for future work
    Circle, Ellipse, Rect, Petal = 1, 2, 3, 4

    TitleFormat = "<center><h4>{0}</h4>{1}</center>"

    selectionChanged = Signal()
    itemTextEdited = Signal(int, str)

    def __init__(self, parent=None):
        super(VennDiagram, self).__init__(parent)
        self.shapeType = VennDiagram.Circle

        self._setup()

    def _setup(self):
        self._items = []
        self._vennareas = []
        self._textitems = []

    def item(self, index):
        return self._items[index]

    def items(self):
        return list(self._items)

    def count(self):
        return len(self._items)

    def setItems(self, items):
        if self._items:
            self.clear()

        self._items = list(items)

        for item in self._items:
            item.setParentItem(self)
            item.setVisible(True)

        fmt = self.TitleFormat.format

        font = self.font()
        font.setPixelSize(14)

        for item in items:
            text = GraphicsTextEdit(self)
            text.setFont(font)
            text.setDefaultTextColor(QColor("#333"))
            text.setHtml(fmt(escape(item.text), item.informativeText))
            text.adjustSize()
            text.editingStarted.connect(self._on_editingStarted)
            text.editingFinished.connect(self._on_editingFinished)
            text.documentSizeChanged.connect(
                self._on_itemTextSizeChanged
            )

            self._textitems.append(text)

        self._vennareas = [
            VennIntersectionArea(parent=self)
            for i in range(2 ** len(items))
        ]
        self._subsettextitems = [
            QGraphicsTextItem(parent=self)
            for i in range(2 ** len(items))
        ]

        self._updateLayout()

    def clear(self):
        scene = self.scene()
        items = self.vennareas() + list(self.items()) + self._textitems

        for item in self._textitems:
            item.editingStarted.disconnect(self._on_editingStarted)
            item.editingFinished.disconnect(self._on_editingFinished)
            item.documentSizeChanged.disconnect(
                self._on_itemTextSizeChanged
            )

        self._items = []
        self._vennareas = []
        self._textitems = []

        for item in items:
            item.setVisible(False)
            item.setParentItem(None)
            if scene is not None:
                scene.removeItem(item)

    def vennareas(self):
        return list(self._vennareas)

    def setFont(self, font):
        if font != self.font():
            self.prepareGeometryChange()
            super().setFont(font)

            for item in self.items():
                item.setFont(font)

    def _updateLayout(self):
        rect = self.geometry()
        n = len(self._items)
        if not n:
            return

        regions = venn_diagram(n, shape=self.shapeType)

        # The y axis in Qt points downward
        transform = QTransform().scale(1, -1)
        regions = list(map(transform.map, regions))

        union_brect = reduce(QRectF.united,
                             (path.boundingRect() for path in regions))

        scalex = rect.width() / union_brect.width()
        scaley = rect.height() / union_brect.height()
        scale = min(scalex, scaley)

        transform = QTransform().scale(scale, scale)

        regions = [transform.map(path) for path in regions]

        center = rect.width() / 2, rect.height() / 2
        for item, path in zip(self.items(), regions):
            item.setPath(path)
            item.setPos(*center)

        intersections = venn_intersections(regions)
        assert len(intersections) == 2 ** n
        assert len(self.vennareas()) == 2 ** n

        anchors = [(0, 0)] + subset_anchors(self._items)

        anchor_transform = QTransform().scale(rect.width(), -rect.height())
        for i, area in enumerate(self.vennareas()):
            area.setPath(intersections[setkey(i, n)])
            area.setPos(*center)
            x, y = anchors[i]
            anchor = anchor_transform.map(QPointF(x, y))
            area.setTextAnchor(anchor)
            area.setZValue(30)

        self._updateTextAnchors()

    def _updateTextAnchors(self):
        n = len(self._items)

        items = self._items
        dist = 15

        shape = reduce(QPainterPath.united, [item.path() for item in items])
        brect = shape.boundingRect()
        bradius = max(brect.width() / 2, brect.height() / 2)

        center = self.boundingRect().center()

        anchors = _category_anchors(items)
        self._textanchors = []
        for angle, anchor_h, anchor_v in anchors:
            line = QLineF.fromPolar(bradius, angle)
            ext = QLineF.fromPolar(dist, angle)
            line = QLineF(line.p1(), line.p2() + ext.p2())
            line = line.translated(center)

            anchor_pos = line.p2()
            self._textanchors.append((anchor_pos, anchor_h, anchor_v))

        for i in range(n):
            self._updateTextItemPos(i)

    def _updateTextItemPos(self, i):
        item = self._textitems[i]
        anchor_pos, anchor_h, anchor_v = self._textanchors[i]
        rect = item.boundingRect()
        pos = anchor_rect(rect, anchor_pos, anchor_h, anchor_v)
        item.setPos(pos)

    def setGeometry(self, geometry):
        super(VennDiagram, self).setGeometry(geometry)
        self._updateLayout()

    def _on_editingStarted(self):
        item = self.sender()
        index = self._textitems.index(item)
        text = self._items[index].text
        item.setTextWidth(-1)
        item.setHtml(self.TitleFormat.format(escape(text), "<br/>"))

    def _on_editingFinished(self):
        item = self.sender()
        index = self._textitems.index(item)
        text = item.toPlainText()
        if text != self._items[index].text:
            self._items[index].text = text

            self.itemTextEdited.emit(index, text)

        item.setHtml(
            self.TitleFormat.format(
                escape(text), self._items[index].informativeText))
        item.adjustSize()

    def _on_itemTextSizeChanged(self):
        item = self.sender()
        index = self._textitems.index(item)
        self._updateTextItemPos(index)


def anchor_rect(rect, anchor_pos,
                anchor_h=Qt.AnchorHorizontalCenter,
                anchor_v=Qt.AnchorVerticalCenter):

    if anchor_h == Qt.AnchorLeft:
        x = anchor_pos.x()
    elif anchor_h == Qt.AnchorHorizontalCenter:
        x = anchor_pos.x() - rect.width() / 2
    elif anchor_h == Qt.AnchorRight:
        x = anchor_pos.x() - rect.width()
    else:
        raise ValueError(anchor_h)

    if anchor_v == Qt.AnchorTop:
        y = anchor_pos.y()
    elif anchor_v == Qt.AnchorVerticalCenter:
        y = anchor_pos.y() - rect.height() / 2
    elif anchor_v == Qt.AnchorBottom:
        y = anchor_pos.y() - rect.height()
    else:
        raise ValueError(anchor_v)

    return QPointF(x, y)


def radians(angle):
    return 2 * math.pi * angle / 360


def unit_point(x, r=1.0):
    x = radians(x)
    return (r * math.cos(x), r * math.sin(x))


def _category_anchors(shapes):
    n = len(shapes)
    return _CATEGORY_ANCHORS[n - 1]


# (angle, horizontal anchor, vertical anchor)
_CATEGORY_ANCHORS = (
    # n == 1
    ((90, Qt.AnchorHorizontalCenter, Qt.AnchorBottom),),
    # n == 2
    ((180, Qt.AnchorRight, Qt.AnchorVerticalCenter),
     (0, Qt.AnchorLeft, Qt.AnchorVerticalCenter)),
    # n == 3
    ((150, Qt.AnchorRight, Qt.AnchorBottom),
     (30, Qt.AnchorLeft, Qt.AnchorBottom),
     (270, Qt.AnchorHorizontalCenter, Qt.AnchorTop)),
    # n == 4
    ((270 + 45, Qt.AnchorLeft, Qt.AnchorTop),
     (270 - 45, Qt.AnchorRight, Qt.AnchorTop),
     (90 - 15, Qt.AnchorLeft, Qt.AnchorBottom),
     (90 + 15, Qt.AnchorRight, Qt.AnchorBottom)),
    # n == 5
    ((90 - 5, Qt.AnchorHorizontalCenter, Qt.AnchorBottom),
     (18 - 5, Qt.AnchorLeft, Qt.AnchorVerticalCenter),
     (306 - 5, Qt.AnchorLeft, Qt.AnchorTop),
     (234 - 5, Qt.AnchorRight, Qt.AnchorTop),
     (162 - 5, Qt.AnchorRight, Qt.AnchorVerticalCenter),)
)


def subset_anchors(shapes):
    n = len(shapes)
    if n == 1:
        return [(0, 0)]
    elif n == 2:
        return [unit_point(180, r=1/3),
                unit_point(0, r=1/3),
                (0, 0)]
    elif n == 3:
        return [unit_point(150, r=0.35),  # A
                unit_point(30, r=0.35),   # B
                unit_point(90, r=0.27),   # AB
                unit_point(270, r=0.35),  # C
                unit_point(210, r=0.27),  # AC
                unit_point(330, r=0.27),  # BC
                unit_point(0, r=0),]      # ABC
    elif n == 4:
        anchors = [
            (0.400, 0.110),    # A
            (-0.400, 0.110),   # B
            (0.000, -0.285),   # AB
            (0.180, 0.330),    # C
            (0.265, 0.205),    # AC
            (-0.240, -0.110),  # BC
            (-0.100, -0.190),  # ABC
            (-0.180, 0.330),   # D
            (0.240, -0.110),   # AD
            (-0.265, 0.205),   # BD
            (0.100, -0.190),   # ABD
            (0.000, 0.250),    # CD
            (0.153, 0.090),    # ACD
            (-0.153, 0.090),   # BCD
            (0.000, -0.060),   # ABCD
        ]
        return anchors

    elif n == 5:
        anchors = [None] * 32
        # Base anchors
        A = (0.033, 0.385)
        AD = (0.095, 0.250)
        AE = (-0.100, 0.265)
        ACE = (-0.130, 0.220)
        ADE = (0.010, 0.225)
        ACDE = (-0.095, 0.175)
        ABCDE = (0.0, 0.0)

        anchors[-1] = ABCDE

        bases = [(0b00001, A),
                 (0b01001, AD),
                 (0b10001, AE),
                 (0b10101, ACE),
                 (0b11001, ADE),
                 (0b11101, ACDE)]

        for i in range(5):
            for index, anchor in bases:
                index = bit_rot_left(index, i, bits=5)
                assert anchors[index] is None
                anchors[index] = rotate_point(anchor, - 72 * i)

        assert all(anchors[1:])
        return anchors[1:]
    return None


def bit_rot_left(x, y, bits=32):
    mask = 2 ** bits - 1
    x_masked = x & mask
    return (x << y) & mask | (x_masked >> bits - y)


def rotate_point(p, angle):
    r = radians(angle)
    R = numpy.array([[math.cos(r), -math.sin(r)],
                     [math.sin(r), math.cos(r)]])
    x, y = numpy.dot(R, p)
    return (float(x), float(y))


def line_extended(line, distance):
    """
    Return an QLineF extended by `distance` units in the positive direction.
    """
    angle = line.angle() / 360 * 2 * math.pi
    dx, dy = unit_point(angle, r=distance)
    return QLineF(line.p1(), line.p2() + QPointF(dx, dy))


def circle_path(center, r=1.0):
    return ellipse_path(center, r, r, rotation=0)


def ellipse_path(center, a, b, rotation=0):
    if not isinstance(center, QPointF):
        center = QPointF(*center)

    brect = QRectF(-a, -b, 2 * a, 2 * b)

    path = QPainterPath()
    path.addEllipse(brect)

    if rotation != 0:
        transform = QTransform().rotate(rotation)
        path = transform.map(path)

    path.translate(center)
    return path


# TODO: Should include anchors for text layout (both inside and outside).
# for each item {path: QPainterPath,
#                text_anchors: [{center}] * (2 ** n)
#                mayor_axis: QLineF,
#                boundingRect QPolygonF (with 4 vertices)}
#
# Should be a new class with overloads for ellipse/circle, rect, and petal
# shapes, should store all constructor parameters, rotation, center,
# mayor/minor axis.


def venn_diagram(n, shape=VennDiagram.Circle):
    if n < 1 or n > 5:
        raise ValueError()

    paths = []

    if n == 1:
        paths = [circle_path(center=(0, 0), r=0.5)]
    elif n == 2:
        angles = [180, 0]
        paths = [circle_path(center=unit_point(x, r=1/6), r=1/3)
                 for x in angles]
    elif n == 3:
        angles = [150 - 120 * i for i in range(3)]
        paths = [circle_path(center=unit_point(x, r=1/6), r=1/3)
                 for x in angles]
    elif n == 4:
        # Constants shamelessly stolen from VennDiagram R package
        paths = [
            ellipse_path((0.65 - 0.5, 0.47 - 0.5), 0.35, 0.20, 45),
            ellipse_path((0.35 - 0.5, 0.47 - 0.5), 0.35, 0.20, 135),
            ellipse_path((0.5 - 0.5, 0.57 - 0.5), 0.35, 0.20, 45),
            ellipse_path((0.5 - 0.5, 0.57 - 0.5), 0.35, 0.20, 134),
        ]
    elif n == 5:
        # Constants shamelessly stolen from VennDiagram R package
        d = 0.13
        a, b = 0.24, 0.48
        a, b = b, a
        a, b = 0.48, 0.24
        paths = [ellipse_path(unit_point((1 - i) * 72, r=d),
                              a, b, rotation=90 - (i * 72))
                 for i in range(5)]

    return paths


def setkey(intval, n):
    return tuple(bool(intval & (2 ** i)) for i in range(n))


def keyrange(n):
    if n < 0:
        raise ValueError()

    for i in range(2 ** n):
        yield setkey(i, n)


def venn_intersections(paths):
    n = len(paths)
    return {key: venn_intersection(paths, key) for key in keyrange(n)}


def venn_intersection(paths, key):
    if not any(key):
        return QPainterPath()

    # first take the intersection of all included paths
    path = reduce(QPainterPath.intersected,
                  (path for path, included in zip(paths, key) if included))

    # subtract all the excluded sets (i.e. take the intersection
    # with the excluded set complements)
    path = reduce(QPainterPath.subtracted,
                  (path for path, included in zip(paths, key) if not included),
                  path)

    return path


def append_column(data, where, variable, column):
    X, Y, M = data.X, data.Y, data.metas
    domain = data.domain
    attr = domain.attributes
    class_vars = domain.class_vars
    metas = domain.metas
    if where == "X":
        attr = attr + (variable,)
        X = numpy.hstack((X, column))
    elif where == "Y":
        class_vars = class_vars + (variable,)
        Y = numpy.hstack((Y, column))
    elif where == "M":
        metas = metas + (variable,)
        M = numpy.hstack((M, column))
    else:
        raise ValueError
    domain = Orange.data.Domain(attr, class_vars, metas)
    new_data = data.transform(domain)
    new_data[:, variable] = column
    return new_data


def drop_columns(data, columns):
    columns = set(data.domain[col] for col in columns)

    def filter_vars(vars):
        return tuple(var for var in vars if var not in columns)

    domain = Orange.data.Domain(
        filter_vars(data.domain.attributes),
        filter_vars(data.domain.class_vars),
        filter_vars(data.domain.metas)
    )
    return Orange.data.Table.from_table(domain, data)


def group_table_indices(table, key_var):
    """
    Group table indices based on values of selected columns (`key_vars`).

    Return a dictionary mapping all unique value combinations (keys)
    into a list of indices in the table where they are present.
    """
    groups = defaultdict(list)
    for i, inst in enumerate(table):
        groups[str(inst[key_var])].append(i)
    return groups


if __name__ == "__main__":  # pragma: no cover
    from Orange.evaluation import ShuffleSplit

    data = Orange.data.Table("brown-selected")
    # data1 = Orange.data.Table("brown-selected")
    # datasets = [(data, 1), (data, 2)]

    data = append_column(data, "M", Orange.data.StringVariable("Test"),
                         numpy.arange(len(data)).reshape(-1, 1) % 30)
    res = ShuffleSplit(data, [None], n_resamples=5,
                       test_size=0.7, stratified=False)
    indices = iter(res.indices)
    datasets = []
    for i in range(1, 6):
        sample, _ = next(indices)
        data1 = data[sample]
        data1.name = chr(ord("A") + i)
        datasets.append((data1, i))

    WidgetPreview(OWVennDiagram).run(setData=datasets)

```
--- 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:** OWVennDiagram Does Not Commit Data When Autocommit Is Disabled

**Description:**
When the `autocommit` feature is disabled in the `OWVennDiagram` widget, sending new data does not trigger a commit. As a result, the widget fails to output the expected data, leaving the output empty despite receiving valid input.

**Example Code:**
```python
def test_disable_autocommit():
    widget = OWVennDiagram()
    widget.autocommit = False
    widget.send_signal("data", new_data)
    # Expected: Data is committed and output is available
    # Actual: No data is committed, and output remains empty
```

**Expected Behavior:**
With `autocommit` turned off, the widget should still commit incoming data upon receiving new signals to ensure that the data is processed and output correctly.

**Actual Behavior:**
When `autocommit` is disabled, sending new data does not result in a commit, and the widget does not produce any output data.

[/ISSUE]
{
    "TestVennDiagram.test_reshape_wide": "PASSED",
    "TestVennDiagram.test_reshape_wide_missing_vals": "PASSED",
    "TestVennDiagram.test_varying_between_missing_vals": "PASSED",
    "TestVennDiagram.test_venn_diagram": "PASSED",
    "TestOWVennDiagram.test_minimum_size": "PASSED",
    "TestOWVennDiagram.test_msg_base_class": "PASSED",
    "TestOWVennDiagram.test_multiple_input": "PASSED",
    "TestOWVennDiagram.test_no_data": "PASSED",
    "TestOWVennDiagram.test_outputs": "PASSED",
    "TestOWVennDiagram.test_unconditional_commit_on_new_signal": "PASSED",
    "GroupTableIndicesTest.test_group_table_indices": "PASSED",
    "GroupTableIndicesTest.test_varying_between_combined": "PASSED",
    "TestVennUtilities.test_copy_descriptor_continuous": "PASSED",
    "TestVennUtilities.test_copy_descriptor_discrete": "PASSED",
    "TestVennUtilities.test_copy_descriptor_other_types": "PASSED",
    "test_filename": "ERROR"
}
diff --git a/Orange/widgets/visualize/owvenndiagram.py b/Orange/widgets/visualize/owvenndiagram.py
index 528bbf5ac..58b1d5077 100644
--- a/Orange/widgets/visualize/owvenndiagram.py
+++ b/Orange/widgets/visualize/owvenndiagram.py
@@ -210,6 +210,10 @@ class OWVennDiagram(widget.OWWidget):
         del self._queue[:]
 
         self._createDiagram()
+        # If autocommit is enabled, _createDiagram already outputs data
+        # If not, call unconditional_commit from here
+        if not self.autocommit:
+            self.unconditional_commit()
         if self.data:
             self.infolabel.setText(f"{len(self.data)} datasets on input.\n")
         else:

{"Orange/widgets/visualize/owvenndiagram.py": "\"\"\"\nVenn Diagram Widget\n-------------------\n\n\"\"\"\n\nimport math\nimport unicodedata\nfrom collections import namedtuple, defaultdict, OrderedDict, Counter\nfrom itertools import count\nfrom functools import reduce\nfrom xml.sax.saxutils import escape\n\nimport numpy\n\nfrom AnyQt.QtWidgets import (\n    QComboBox, QGraphicsScene, QGraphicsView, QGraphicsWidget,\n    QGraphicsPathItem, QGraphicsTextItem, QStyle\n)\nfrom AnyQt.QtGui import (\n    QPainterPath, QPainter, QTransform, QColor, QBrush, QPen, QPalette\n)\nfrom AnyQt.QtCore import Qt, QPointF, QRectF, QLineF\nfrom AnyQt.QtCore import pyqtSignal as Signal\n\nimport Orange.data\nfrom Orange.statistics import util\nfrom Orange.widgets import widget, gui, settings\nfrom Orange.widgets.utils import itemmodels, colorpalette\nfrom Orange.widgets.utils.annotated_data import (create_annotated_table,\n                                                 ANNOTATED_DATA_SIGNAL_NAME)\nfrom Orange.widgets.utils.sql import check_sql_input\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.widget import Input, Output\n\n\n_InputData = namedtuple(\"_InputData\", [\"key\", \"name\", \"table\"])\n_ItemSet = namedtuple(\"_ItemSet\", [\"key\", \"name\", \"title\", \"items\"])\n\n\nclass OWVennDiagram(widget.OWWidget):\n    name = \"Venn Diagram\"\n    description = \"A graphical visualization of the overlap of data instances \" \\\n                  \"from a collection of input datasets.\"\n    icon = \"icons/VennDiagram.svg\"\n    priority = 280\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table, multiple=True)\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    inputhints: dict\n    selection: list\n\n    # Selected disjoint subset indices\n    selection = settings.Setting([])\n    #: Stored input set hints\n    #: {(index, inputname, attributes): (selectedattrname, itemsettitle)}\n    #: The 'selectedattrname' can be None\n    inputhints = settings.Setting({})\n    #: Use identifier columns for instance matching\n    useidentifiers = settings.Setting(True)\n    #: Output unique items (one output row for every unique instance `key`)\n    #: or preserve all duplicates in the output.\n    output_duplicates = settings.Setting(False)\n    autocommit = settings.Setting(True)\n\n    graph_name = \"scene\"\n\n    def __init__(self):\n        super().__init__()\n\n        # Diagram update is in progress\n        self._updating = False\n        # Input update is in progress\n        self._inputUpdate = False\n        # All input tables have the same domain.\n        self.samedomain = True\n        # Input datasets in the order they were 'connected'.\n        self.data = OrderedDict()\n        # Extracted input item sets in the order they were 'connected'\n        self.itemsets = OrderedDict()\n\n        # GUI\n        box = gui.vBox(self.controlArea, \"Info\")\n        self.infolabel = gui.widgetLabel(box, \"No data on input.\\n\")\n\n        self.identifiersBox = gui.radioButtonsInBox(\n            self.controlArea, self, \"useidentifiers\", [],\n            box=\"Data Instance Identifiers\",\n            callback=self._on_useidentifiersChanged\n        )\n        self.useequalityButton = gui.appendRadioButton(\n            self.identifiersBox, \"Use instance equality\"\n        )\n        self.useidentifiersButton = rb = gui.appendRadioButton(\n            self.identifiersBox, \"Use identifiers\"\n        )\n        self.inputsBox = gui.indentedBox(\n            self.identifiersBox, sep=gui.checkButtonOffsetHint(rb)\n        )\n        self.inputsBox.setEnabled(bool(self.useidentifiers))\n\n        for i in range(5):\n            box = gui.vBox(self.inputsBox, \"Dataset #%i\" % (i + 1),\n                           addSpace=False)\n            box.setFlat(True)\n            model = itemmodels.VariableListModel(parent=self)\n            cb = gui.OrangeComboBox(\n                minimumContentsLength=12,\n                sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon\n            )\n            cb.setModel(model)\n            cb.activated[int].connect(self._on_inputAttrActivated)\n            box.setEnabled(False)\n            # Store the combo in the box for later use.\n            box.combo_box = cb\n            box.layout().addWidget(cb)\n\n        gui.rubber(self.controlArea)\n\n        box = gui.vBox(self.controlArea, \"Output\")\n        gui.checkBox(box, self, \"output_duplicates\", \"Output duplicates\",\n                     callback=lambda: self.commit())\n        gui.auto_commit(box, self, \"autocommit\", \"Send Selection\", \"Send Automatically\", box=False)\n\n        # Main area view\n        self.scene = QGraphicsScene()\n        self.view = QGraphicsView(self.scene)\n        self.view.setRenderHint(QPainter.Antialiasing)\n        self.view.setBackgroundRole(QPalette.Window)\n        self.view.setFrameStyle(QGraphicsView.StyledPanel)\n\n        self.mainArea.layout().addWidget(self.view)\n        self.vennwidget = VennDiagram()\n        self.vennwidget.resize(400, 400)\n        self.vennwidget.itemTextEdited.connect(self._on_itemTextEdited)\n        self.scene.selectionChanged.connect(self._on_selectionChanged)\n\n        self.scene.addItem(self.vennwidget)\n\n        self.resize(self.controlArea.sizeHint().width() + 550,\n                    max(self.controlArea.sizeHint().height(), 550))\n\n        self._queue = []\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data, key=None):\n        self.error()\n        if not self._inputUpdate:\n            # Store hints only on the first setData call.\n            self._storeHints()\n            self._inputUpdate = True\n\n        if key in self.data:\n            if data is None:\n                # Remove the input\n                self._remove(key)\n            else:\n                # Update existing item\n                self._update(key, data)\n        elif data is not None:\n            # TODO: Allow setting more them 5 inputs and let the user\n            # select the 5 to display.\n            if len(self.data) == 5:\n                self.error(\"Venn diagram accepts at most five datasets.\")\n                return\n            # Add a new input\n            self._add(key, data)\n\n    def handleNewSignals(self):\n        self._inputUpdate = False\n\n        # Check if all inputs are from the same domain.\n        domains = [input.table.domain for input in self.data.values()]\n        samedomain = all(domain_eq(d1, d2) for d1, d2 in pairwise(domains))\n\n        self.samedomain = samedomain\n\n        has_identifiers = all(source_attributes(input.table.domain)\n                              for input in self.data.values())\n        has_any_identifiers = any(source_attributes(input.table.domain)\n                                  for input in self.data.values())\n        self.useequalityButton.setEnabled(samedomain)\n        self.useidentifiersButton.setEnabled(\n            has_any_identifiers or len(self.data) == 0)\n        self.inputsBox.setEnabled(has_any_identifiers)\n\n        if not samedomain and has_any_identifiers and not self.useidentifiers:\n            self.useidentifiers = 1\n        elif samedomain and not has_identifiers:\n            self.useidentifiers = 0\n\n        incremental = all(inc for _, inc in self._queue)\n\n        if incremental:\n            # Only received updated data on existing link.\n            self._updateItemsets()\n        else:\n            # Links were removed and/or added.\n            self._createItemsets()\n            self._restoreHints()\n            self._updateItemsets()\n\n        del self._queue[:]\n\n        self._createDiagram()\n        if self.data:\n            self.infolabel.setText(f\"{len(self.data)} datasets on input.\\n\")\n        else:\n            self.infolabel.setText(\"No data on input\\n\")\n\n        self._updateInfo()\n        super().handleNewSignals()\n\n    def _invalidate(self, keys=None, incremental=True):\n        \"\"\"\n        Invalidate input for a list of input keys.\n        \"\"\"\n        if keys is None:\n            keys = list(self.data.keys())\n\n        self._queue.extend((key, incremental) for key in keys)\n\n    def itemsetAttr(self, key):\n        index = list(self.data.keys()).index(key)\n        _, combo = self._controlAtIndex(index)\n        model = combo.model()\n        attr_index = combo.currentIndex()\n        if attr_index >= 0:\n            return model[attr_index]\n        else:\n            return None\n\n    def _controlAtIndex(self, index):\n        group_box = self.inputsBox.layout().itemAt(index).widget()\n        combo = group_box.combo_box\n        return group_box, combo\n\n    def _setAttributes(self, index, attrs):\n        box, combo = self._controlAtIndex(index)\n        model = combo.model()\n\n        if attrs is None:\n            model[:] = []\n            box.setEnabled(False)\n        else:\n            if model[:] != attrs:\n                model[:] = attrs\n\n            box.setEnabled(True)\n\n    def _add(self, key, table):\n        name = table.name\n        index = len(self.data)\n        attrs = source_attributes(table.domain)\n\n        self.data[key] = _InputData(key, name, table)\n\n        self._setAttributes(index, attrs)\n\n        self._invalidate([key], incremental=False)\n\n        item = self.inputsBox.layout().itemAt(index)\n        box = item.widget()\n        box.setTitle(\"Dataset: {}\".format(name))\n\n    def _remove(self, key):\n        index = list(self.data.keys()).index(key)\n\n        # Clear possible warnings.\n        self.warning()\n\n        self._setAttributes(index, None)\n\n        del self.data[key]\n\n        layout = self.inputsBox.layout()\n        item = layout.takeAt(index)\n        layout.addItem(item)\n        inputs = list(self.data.values())\n\n        for i in range(5):\n            box, _ = self._controlAtIndex(i)\n            if i < len(inputs):\n                title = \"Dataset: {}\".format(inputs[i].name)\n            else:\n                title = \"Dataset #{}\".format(i + 1)\n            box.setTitle(title)\n\n        self._invalidate([key], incremental=False)\n\n    def _update(self, key, table):\n        name = table.name\n        index = list(self.data.keys()).index(key)\n        attrs = source_attributes(table.domain)\n\n        self.data[key] = self.data[key]._replace(name=name, table=table)\n\n        self._setAttributes(index, attrs)\n        self._invalidate([key])\n\n        item = self.inputsBox.layout().itemAt(index)\n        box = item.widget()\n        box.setTitle(\"Dataset: {}\".format(name))\n\n    def _itemsForInput(self, key):\n        useidentifiers = self.useidentifiers or not self.samedomain\n\n        def items_by_key(key, input):\n            attr = self.itemsetAttr(key)\n            if attr is not None:\n                return [str(inst[attr]) for inst in input.table\n                        if not numpy.isnan(inst[attr])]\n            else:\n                return []\n\n        def items_by_eq(key, input):\n            return list(map(ComparableInstance, input.table))\n\n        input = self.data[key]\n        if useidentifiers:\n            items = items_by_key(key, input)\n        else:\n            items = items_by_eq(key, input)\n        return items\n\n    def _updateItemsets(self):\n        assert list(self.data.keys()) == list(self.itemsets.keys())\n        for key, input in list(self.data.items()):\n            items = self._itemsForInput(key)\n            item = self.itemsets[key]\n            item = item._replace(items=items)\n            name = input.name\n            if item.name != name:\n                item = item._replace(name=name, title=name)\n            self.itemsets[key] = item\n\n    def _createItemsets(self):\n        olditemsets = dict(self.itemsets)\n        self.itemsets.clear()\n\n        for key, input in self.data.items():\n            items = self._itemsForInput(key)\n            name = input.name\n            if key in olditemsets and olditemsets[key].name == name:\n                # Reuse the title (which might have been changed by the user)\n                title = olditemsets[key].title\n            else:\n                title = name\n\n            itemset = _ItemSet(key=key, name=name, title=title, items=items)\n            self.itemsets[key] = itemset\n\n    def _storeHints(self):\n        if self.data:\n            self.inputhints.clear()\n            for i, (key, input) in enumerate(self.data.items()):\n                attrs = source_attributes(input.table.domain)\n                attrs = tuple(attr.name for attr in attrs)\n                selected = self.itemsetAttr(key)\n                if selected is not None:\n                    attr_name = selected.name\n                else:\n                    attr_name = None\n                itemset = self.itemsets[key]\n                self.inputhints[(i, input.name, attrs)] = \\\n                    (attr_name, itemset.title)\n\n    def _restoreHints(self):\n        settings = []\n        for i, (key, input) in enumerate(self.data.items()):\n            attrs = source_attributes(input.table.domain)\n            attrs = tuple(attr.name for attr in attrs)\n            hint = self.inputhints.get((i, input.name, attrs), None)\n            if hint is not None:\n                attr, name = hint\n                attr_ind = attrs.index(attr) if attr is not None else -1\n                settings.append((attr_ind, name))\n            else:\n                return\n\n        # all inputs match the stored hints\n        for i, key in enumerate(self.itemsets):\n            attr, itemtitle = settings[i]\n            self.itemsets[key] = self.itemsets[key]._replace(title=itemtitle)\n            _, cb = self._controlAtIndex(i)\n            cb.setCurrentIndex(attr)\n\n    def _createDiagram(self):\n        self._updating = True\n\n        oldselection = list(self.selection)\n\n        self.vennwidget.clear()\n        n = len(self.itemsets)\n        self.disjoint = disjoint(set(s.items) for s in self.itemsets.values())\n\n        vennitems = []\n        colors = colorpalette.ColorPaletteHSV(n)\n\n        for i, (_, item) in enumerate(self.itemsets.items()):\n            count = len(set(item.items))\n            count_all = len(item.items)\n            if count != count_all:\n                fmt = '{} <i>(all: {})</i>'\n            else:\n                fmt = '{}'\n            counts = fmt.format(count, count_all)\n            gr = VennSetItem(text=item.title, informativeText=counts)\n            color = colors[i]\n            color.setAlpha(100)\n            gr.setBrush(QBrush(color))\n            gr.setPen(QPen(Qt.NoPen))\n            vennitems.append(gr)\n\n        self.vennwidget.setItems(vennitems)\n\n        for i, area in enumerate(self.vennwidget.vennareas()):\n            area_items = list(map(str, list(self.disjoint[i])))\n            if i:\n                area.setText(\"{0}\".format(len(area_items)))\n\n            label = disjoint_set_label(i, n, simplify=False)\n            head = \"<h4>|{}| = {}</h4>\".format(label, len(area_items))\n            if len(area_items) > 32:\n                items_str = \", \".join(map(escape, area_items[:32]))\n                hidden = len(area_items) - 32\n                tooltip = (\"{}<span>{}, ...</br>({} items not shown)<span>\"\n                           .format(head, items_str, hidden))\n            elif area_items:\n                tooltip = \"{}<span>{}</span>\".format(\n                    head,\n                    \", \".join(map(escape, area_items))\n                )\n            else:\n                tooltip = head\n\n            area.setToolTip(tooltip)\n\n            area.setPen(QPen(QColor(10, 10, 10, 200), 1.5))\n            area.setFlag(QGraphicsPathItem.ItemIsSelectable, True)\n            area.setSelected(i in oldselection)\n\n        self._updating = False\n        self._on_selectionChanged()\n\n    def _updateInfo(self):\n        # Clear all warnings\n        self.warning()\n\n        if not len(self.data):\n            self.infolabel.setText(\"No data on input\\n\")\n        else:\n            self.infolabel.setText(f\"{len(self.data)} datasets on input\\n\")\n\n        if self.useidentifiers:\n            no_idx = [\"#{}\".format(i + 1)\n                      for i, key in enumerate(self.data)\n                      if not source_attributes(self.data[key].table.domain)]\n            if len(no_idx) == 1:\n                self.warning(\"Dataset {} has no suitable identifiers.\"\n                             .format(no_idx[0]))\n            elif len(no_idx) > 1:\n                self.warning(\"Datasets {} and {} have no suitable identifiers.\"\n                             .format(\", \".join(no_idx[:-1]), no_idx[-1]))\n\n    def _on_selectionChanged(self):\n        if self._updating:\n            return\n\n        areas = self.vennwidget.vennareas()\n        indices = [i for i, area in enumerate(areas)\n                   if area.isSelected()]\n\n        self.selection = indices\n\n        self.invalidateOutput()\n\n    def _on_useidentifiersChanged(self):\n        self.inputsBox.setEnabled(self.useidentifiers == 1)\n        # Invalidate all itemsets\n        self._invalidate()\n        self._updateItemsets()\n        self._createDiagram()\n\n        self._updateInfo()\n\n    def _on_inputAttrActivated(self, attr_index):\n        combo = self.sender()\n        # Find the input index to which the combo box belongs\n        # (they are reordered when removing inputs).\n        index = None\n        inputs = list(self.data.items())\n        for i in range(len(inputs)):\n            _, c = self._controlAtIndex(i)\n            if c is combo:\n                index = i\n                break\n\n        assert index is not None\n\n        key, _ = inputs[index]\n\n        self._invalidate([key])\n        self._updateItemsets()\n        self._createDiagram()\n\n    def _on_itemTextEdited(self, index, text):\n        text = str(text)\n        key = list(self.itemsets.keys())[index]\n        self.itemsets[key] = self.itemsets[key]._replace(title=text)\n\n    def invalidateOutput(self):\n        self.commit()\n\n    def commit(self):\n        selected_subsets = []\n        annotated_data = None\n        annotated_data_subsets = []\n        annotated_data_masks = []\n        selected_items = reduce(\n            set.union, [self.disjoint[index] for index in self.selection],\n            set()\n        )\n\n        def match(val):\n            if numpy.isnan(val):\n                return False\n            else:\n                return str(val) in selected_items\n\n        source_var = Orange.data.StringVariable(\"source\")\n        item_id_var = Orange.data.StringVariable(\"item_id\")\n\n        names = [itemset.title.strip() for itemset in self.itemsets.values()]\n        names = uniquify(names)\n\n        for i, (key, input) in enumerate(self.data.items()):\n            # cell vars are in functions that are only used in the loop\n            # pylint: disable=cell-var-from-loop\n            if not len(input.table):\n                continue\n            if self.useidentifiers:\n                attr = self.itemsetAttr(key)\n                if attr is not None:\n                    mask = list(map(match, (inst[attr] for inst in input.table)))\n                else:\n                    mask = [False] * len(input.table)\n\n                def instance_key(inst):\n                    return str(inst[attr])\n\n                def instance_key_all(inst):\n                    return str(inst[attr])\n            else:\n                mask = [ComparableInstance(inst) in selected_items\n                        for inst in input.table]\n                _map = {item: str(i) for i, item in enumerate(selected_items)}\n                _map_all = {\n                    ComparableInstance(i):  hash(ComparableInstance(i))\n                    for i in input.table\n                }\n\n                def instance_key(inst):\n                    return _map[ComparableInstance(inst)]\n\n                def instance_key_all(inst):\n                    return _map_all[ComparableInstance(inst)]\n\n            mask = numpy.array(mask, dtype=bool)\n            subset = input.table[mask]\n\n            annotated_subset = input.table\n            id_column = numpy.array(\n                [[instance_key_all(inst)] for inst in annotated_subset])\n            source_names = numpy.array([[names[i]]] * len(annotated_subset))\n            annotated_subset = append_column(\n                annotated_subset, \"M\", source_var, source_names)\n            annotated_subset = append_column(\n                annotated_subset, \"M\", item_id_var, id_column)\n            annotated_data_subsets.append(annotated_subset)\n            annotated_data_masks.append(mask)\n\n            if len(subset) == 0:\n                continue\n\n            # add columns with source table id and set id\n\n            if not self.output_duplicates:\n                id_column = numpy.array([[instance_key(inst)] for inst in subset],\n                                        dtype=object)\n                source_names = numpy.array([[names[i]]] * len(subset),\n                                           dtype=object)\n\n                subset = append_column(subset, \"M\", source_var, source_names)\n                subset = append_column(subset, \"M\", item_id_var, id_column)\n\n            selected_subsets.append(subset)\n\n        if selected_subsets and not self.output_duplicates:\n            data = table_concat(selected_subsets)\n            # Get all variables which are not constant between the same\n            # item set\n            varying = varying_between(data, item_id_var)\n\n            if source_var in varying:\n                varying.remove(source_var)\n\n            data = reshape_wide(data, varying, [item_id_var], [source_var])\n            # remove the temporary item set id column\n            data = drop_columns(data, [item_id_var])\n        elif selected_subsets:\n            data = table_concat(selected_subsets)\n        else:\n            data = None\n\n        if annotated_data_subsets:\n            annotated_data = table_concat(annotated_data_subsets)\n            indices = numpy.hstack(annotated_data_masks)\n            annotated_data = create_annotated_table(annotated_data, indices)\n            varying = varying_between(annotated_data, item_id_var)\n            if source_var in varying:\n                varying.remove(source_var)\n            annotated_data = reshape_wide(annotated_data, varying,\n                                          [item_id_var], [source_var])\n            annotated_data = drop_columns(annotated_data, [item_id_var])\n\n        self.Outputs.selected_data.send(data)\n        self.Outputs.annotated_data.send(annotated_data)\n\n    def getSettings(self, *args, **kwargs):\n        self._storeHints()\n        return super().getSettings(self, *args, **kwargs)\n\n    def send_report(self):\n        self.report_plot()\n\n\ndef pairwise(iterable):\n    \"\"\"\n    Return an iterator over consecutive pairs in `iterable`.\n\n    >>> list(pairwise([1, 2, 3, 4])\n    [(1, 2), (2, 3), (3, 4)]\n\n    \"\"\"\n    it = iter(iterable)\n    try:\n        first = next(it)\n    except StopIteration:\n        return\n    for second in it:\n        yield first, second\n        first = second\n\n\n# Custom domain comparison (domains do not seem to compare equal\n# even if they have exactly the same variables).\n# TODO: What about metas.\ndef domain_eq(d1, d2):\n    return d1.variables == d2.variables\n\n\n# Comparing/hashing Orange.data.Instance across domains ignoring metas.\nclass ComparableInstance:\n    __slots__ = [\"inst\", \"domain\", \"__hash\"]\n\n    def __init__(self, inst):\n        self.inst = inst\n        self.domain = inst.domain\n        self.__hash = hash((self.inst.x.data.tobytes(),\n                            self.inst.y.data.tobytes()))\n\n    def __hash__(self):\n        return self.__hash\n\n    def __eq__(self, other):\n        # XXX: comparing NaN with different payload\n        return (isinstance(other, ComparableInstance)\n                and domain_eq(self.domain, other.domain)\n                and self.inst.x.data.tobytes() == other.inst.x.data.tobytes()\n                and self.inst.y.data.tobytes() == other.inst.y.data.tobytes())\n\n    def __iter__(self):\n        return iter(self.inst)\n\n    def __repr__(self):\n        return repr(self.inst)\n\n    def __str__(self):\n        return str(self.inst)\n\n\ndef table_concat(tables):\n    \"\"\"\n    Concatenate a list of tables.\n\n    The resulting table will have a union of all attributes of `tables`.\n\n    \"\"\"\n    attributes = []\n    class_vars = []\n    metas = []\n    variables_seen = set()\n\n    for table in tables:\n        attributes.extend(v for v in table.domain.attributes\n                          if v not in variables_seen)\n        variables_seen.update(table.domain.attributes)\n\n        class_vars.extend(v for v in table.domain.class_vars\n                          if v not in variables_seen)\n        variables_seen.update(table.domain.class_vars)\n\n        metas.extend(v for v in table.domain.metas\n                     if v not in variables_seen)\n\n        variables_seen.update(table.domain.metas)\n\n    domain = Orange.data.Domain(attributes, class_vars, metas)\n\n    tables = [tab.transform(domain) for tab in tables]\n    return tables[0].concatenate(tables, axis=0)\n\n\ndef copy_descriptor(descriptor, newname=None):\n    \"\"\"\n    Create a copy of the descriptor.\n\n    If newname is not None the new descriptor will have the same\n    name as the input.\n\n    \"\"\"\n    if newname is None:\n        newname = descriptor.name\n\n    if descriptor.is_discrete:\n        newf = Orange.data.DiscreteVariable(\n            newname,\n            values=descriptor.values,\n            ordered=descriptor.ordered,\n        )\n        newf.attributes = dict(descriptor.attributes)\n\n    elif descriptor.is_continuous:\n        newf = Orange.data.ContinuousVariable(newname)\n        newf.number_of_decimals = descriptor.number_of_decimals\n        newf.attributes = dict(descriptor.attributes)\n\n    else:\n        newf = type(descriptor)(newname)\n        newf.attributes = dict(descriptor.attributes)\n\n    return newf\n\n\ndef reshape_wide(table, varlist, idvarlist, groupvarlist):\n    \"\"\"\n    Reshape a data table into a wide format.\n\n    :param Orange.data.Table table:\n        Source data table in long format.\n    :param varlist:\n        A list of variables to reshape.\n    :param list idvarlist:\n        A list of variables in `table` uniquely identifying multiple\n        records in `table` (i.e. subject id).\n    :param groupvarlist:\n        A list of variables differentiating multiple records\n        (i.e. conditions).\n\n    \"\"\"\n    def inst_key(inst, vars):\n        return tuple(str(inst[var]) for var in vars)\n\n    instance_groups = [inst_key(inst, groupvarlist) for inst in table]\n    # A list of groups (for each element in a group the varying variable\n    # will be duplicated)\n    groups = list(unique(instance_groups))\n    group_names = [\", \".join(group) for group in groups]\n\n    # A list of instance ids (subject ids)\n    # Each instance in the output will correspond to one of these ids)\n    instance_ids = [inst_key(inst, idvarlist) for inst in table]\n    ids = list(unique(instance_ids))\n\n    # an mapping from ids to an list of input instance indices\n    # each instance in this list belongs to one group (but not all\n    # groups need to be present).\n    inst_by_id = defaultdict(list)\n    id_by_inst = defaultdict(list)  # inverse mapping\n\n    for i in range(len(table)):\n        inst_id = instance_ids[i]\n        inst_by_id[inst_id].append(i)\n        id_by_inst[i] = inst_id\n\n    newfeatures = []\n    newclass_vars = []\n    newmetas = []\n    expanded_features = {}\n\n    def expanded(feat):\n        return [copy_descriptor(feat, newname=\"%s (%s)\" %\n                                (feat.name, group_name))\n                for group_name in group_names]\n\n    for feat in table.domain.attributes:\n        if feat in varlist:\n            features = expanded(feat)\n            newfeatures.extend(features)\n            expanded_features[feat] = dict(zip(groups, features))\n        elif feat not in groupvarlist:\n            newfeatures.append(feat)\n\n    for feat in table.domain.class_vars:\n        if feat in varlist:\n            features = expanded(feat)\n            newclass_vars.extend(features)\n            expanded_features[feat] = dict(zip(groups, features))\n        elif feat not in groupvarlist:\n            newclass_vars.append(feat)\n\n    for meta in table.domain.metas:\n        if meta in varlist:\n            metas = expanded(meta)\n            newmetas.extend(metas)\n            expanded_features[meta] = dict(zip(groups, metas))\n        elif meta not in groupvarlist:\n            newmetas.append(meta)\n\n    domain = Orange.data.Domain(newfeatures, newclass_vars, newmetas)\n    prototype_indices = [inst_by_id[inst_id][0] for inst_id in ids]\n    newtable = table[prototype_indices].transform(domain)\n    in_expanded = set(f for efd in expanded_features.values() for f in efd.values())\n\n    # Fill-in nan values\n    for var in domain.variables + domain.metas:\n        if var in idvarlist or var in in_expanded:\n            continue\n        col, _ = newtable.get_column_view(var)\n        nan_indices = (i for i in col.nonzero()[0]\n                       if isinstance(col[i], str) or numpy.isnan(col[i]))\n        for i in nan_indices:\n            for ind in inst_by_id[ids[i]]:\n                if not numpy.isnan(table[ind, var]):\n                    newtable[i, var] = table[ind, var]\n                    break\n\n    # Fill-in expanded features if any\n    for i, inst_id in enumerate(ids):\n        indices = inst_by_id[inst_id]\n        instance = newtable[i]\n\n        for index in indices:\n            source_inst = table[index]\n            group = instance_groups[index]\n            for source_var in varlist:\n                newf = expanded_features[source_var][group]\n                instance[newf] = source_inst[source_var]\n\n    return newtable\n\n\ndef unique(seq):\n    \"\"\"\n    Return an iterator over unique items of `seq`.\n\n    .. note:: Items must be hashable.\n\n    \"\"\"\n    seen = set()\n    for item in seq:\n        if item not in seen:\n            yield item\n            seen.add(item)\n\n\ndef unique_non_nan(ar):\n    # metas have sometimes object dtype, but values are numpy floats\n    ar = ar.astype('float64')\n    uniq = numpy.unique(ar)\n    return uniq[~numpy.isnan(uniq)]\n\n\ndef varying_between(table, idvar):\n    \"\"\"\n    Return a list of all variables with non constant values between\n    groups defined by `idvar`.\n\n    \"\"\"\n    all_possible = [var for var in table.domain.variables + table.domain.metas\n                    if var != idvar]\n    candidate_set = set(all_possible)\n\n    idmap = group_table_indices(table, idvar)\n\n    varying = set()\n    for indices in idmap.values():\n        subset = table[indices]\n        for var in list(candidate_set):\n            column, _ = subset.get_column_view(var)\n            with numpy.errstate(invalid=\"ignore\"):  # nans are removed below\n                values = util.unique(column)\n\n            if not var.is_string:\n                values = unique_non_nan(values)\n\n            if len(values) > 1:\n                varying.add(var)\n                candidate_set.remove(var)\n\n    return sorted(varying, key=all_possible.index)\n\n\ndef uniquify(strings):\n    \"\"\"\n    Return a list of unique strings.\n\n    The string at i'th position will have the same prefix as strings[i]\n    with an appended suffix to make the item unique (if necessary).\n\n    >>> uniquify([\"cat\", \"dog\", \"cat\"])\n    [\"cat 1\", \"dog\", \"cat 2\"]\n\n    \"\"\"\n    counter = Counter(strings)\n    counts = defaultdict(count)\n    newstrings = []\n    for string in strings:\n        if counter[string] > 1:\n            newstrings.append(string + (\" %i\" % (next(counts[string]) + 1)))\n        else:\n            newstrings.append(string)\n\n    return newstrings\n\n\ndef string_attributes(domain):\n    \"\"\"\n    Return all string attributes from the domain.\n    \"\"\"\n    return [attr for attr in domain.variables + domain.metas if attr.is_string]\n\n\ndef discrete_attributes(domain):\n    \"\"\"\n    Return all discrete attributes from the domain.\n    \"\"\"\n    return [attr for attr in domain.variables + domain.metas if attr.is_discrete]\n\n\ndef source_attributes(domain):\n    \"\"\"\n    Return all suitable attributes for the venn diagram.\n    \"\"\"\n    return string_attributes(domain)  # + discrete_attributes(domain)\n\n\ndef disjoint(sets):\n    \"\"\"\n    Return all disjoint subsets.\n    \"\"\"\n    sets = list(sets)\n    n = len(sets)\n    disjoint_sets = [None] * (2 ** n)\n    for i in range(2 ** n):\n        key = setkey(i, n)\n        included = [s for s, inc in zip(sets, key) if inc]\n        excluded = [s for s, inc in zip(sets, key) if not inc]\n        if any(included):\n            s = reduce(set.intersection, included)\n        else:\n            s = set()\n\n        s = reduce(set.difference, excluded, s)\n\n        disjoint_sets[i] = s\n\n    return disjoint_sets\n\n\ndef disjoint_set_label(i, n, simplify=False):\n    \"\"\"\n    Return a html formated label for a disjoint set indexed by `i`.\n    \"\"\"\n    intersection = unicodedata.lookup(\"INTERSECTION\")\n    # comp = unicodedata.lookup(\"COMPLEMENT\")  #\n    # This depends on the font but the unicode complement in\n    # general does not look nice in a super script so we use\n    # plain c instead.\n    comp = \"c\"\n\n    def label_for_index(i):\n        return chr(ord(\"A\") + i)\n\n    if simplify:\n        return \"\".join(label_for_index(i) for i, b in enumerate(setkey(i, n))\n                       if b)\n    else:\n        return intersection.join(label_for_index(i) +\n                                 (\"\" if b else \"<sup>\" + comp + \"</sup>\")\n                                 for i, b in enumerate(setkey(i, n)))\n\n\nclass VennSetItem(QGraphicsPathItem):\n    def __init__(self, parent=None, text=\"\", informativeText=\"\"):\n        super(VennSetItem, self).__init__(parent)\n        # Plain text title (editable by the VennDiagram)\n        self.text = text\n        # Extra informative text (possibly rich text)\n        self.informativeText = informativeText\n\n# TODO: Use palette's selected/highligted text / background colors to\n# indicate selection\n\nclass VennIntersectionArea(QGraphicsPathItem):\n    def __init__(self, parent=None, text=\"\"):\n        super().__init__(parent)\n        self.setAcceptHoverEvents(True)\n        self.setPen(QPen(Qt.NoPen))\n\n        self.text = QGraphicsTextItem(self)\n        layout = self.text.document().documentLayout()\n        layout.documentSizeChanged.connect(self._onLayoutChanged)\n\n        self._text = \"\"\n        self._anchor = QPointF()\n\n    def setText(self, text):\n        if self._text != text:\n            self._text = text\n            self.text.setPlainText(text)\n\n    def setTextAnchor(self, pos):\n        if self._anchor != pos:\n            self._anchor = pos\n            self._updateTextAnchor()\n\n    def hoverEnterEvent(self, event):\n        self.setZValue(self.zValue() + 1)\n        return QGraphicsPathItem.hoverEnterEvent(self, event)\n\n    def hoverLeaveEvent(self, event):\n        self.setZValue(self.zValue() - 1)\n        return QGraphicsPathItem.hoverLeaveEvent(self, event)\n\n    def mousePressEvent(self, event):\n        if event.button() == Qt.LeftButton:\n            if event.modifiers() & Qt.AltModifier:\n                self.setSelected(False)\n            elif event.modifiers() & Qt.ControlModifier:\n                self.setSelected(not self.isSelected())\n            elif event.modifiers() & Qt.ShiftModifier:\n                self.setSelected(True)\n            else:\n                for area in self.parentWidget().vennareas():\n                    area.setSelected(False)\n                self.setSelected(True)\n\n    def mouseReleaseEvent(self, event):\n        pass\n\n    def paint(self, painter, option, widget=None):\n        painter.save()\n        path = self.path()\n        brush = QBrush(self.brush())\n        pen = QPen(self.pen())\n\n        if option.state & QStyle.State_Selected:\n            pen.setColor(Qt.red)\n            brush.setStyle(Qt.DiagCrossPattern)\n            brush.setColor(QColor(40, 40, 40, 100))\n\n        elif option.state & QStyle.State_MouseOver:\n            pen.setColor(Qt.blue)\n\n        if option.state & QStyle.State_MouseOver:\n            brush.setColor(QColor(100, 100, 100, 100))\n            if brush.style() == Qt.NoBrush:\n                # Make sure the highlight is actually visible.\n                brush.setStyle(Qt.SolidPattern)\n\n        painter.setPen(pen)\n        painter.setBrush(brush)\n        painter.drawPath(path)\n        painter.restore()\n\n    def itemChange(self, change, value):\n        if change == QGraphicsPathItem.ItemSelectedHasChanged:\n            self.setZValue(self.zValue() + (1 if value else -1))\n        return QGraphicsPathItem.itemChange(self, change, value)\n\n    def _updateTextAnchor(self):\n        rect = self.text.boundingRect()\n        pos = anchor_rect(rect, self._anchor)\n        self.text.setPos(pos)\n\n    def _onLayoutChanged(self):\n        self._updateTextAnchor()\n\n\nclass GraphicsTextEdit(QGraphicsTextItem):\n    #: Edit triggers\n    NoEditTriggers, DoubleClicked = 0, 1\n\n    editingFinished = Signal()\n    editingStarted = Signal()\n\n    documentSizeChanged = Signal()\n\n    def __init__(self, *args, **kwargs):\n        super(GraphicsTextEdit, self).__init__(*args, **kwargs)\n        self.setCursor(Qt.IBeamCursor)\n        self.setTabChangesFocus(True)\n        self._edittrigger = GraphicsTextEdit.DoubleClicked\n        self._editing = False\n        self.document().documentLayout().documentSizeChanged.connect(\n            self.documentSizeChanged\n        )\n\n    def mouseDoubleClickEvent(self, event):\n        super(GraphicsTextEdit, self).mouseDoubleClickEvent(event)\n        if self._edittrigger == GraphicsTextEdit.DoubleClicked:\n            self._start()\n\n    def focusOutEvent(self, event):\n        super(GraphicsTextEdit, self).focusOutEvent(event)\n\n        if self._editing:\n            self._end()\n\n    def _start(self):\n        self._editing = True\n        self.setTextInteractionFlags(Qt.TextEditorInteraction)\n        self.setFocus(Qt.MouseFocusReason)\n        self.editingStarted.emit()\n\n    def _end(self):\n        self._editing = False\n        self.setTextInteractionFlags(Qt.NoTextInteraction)\n        self.editingFinished.emit()\n\n\nclass VennDiagram(QGraphicsWidget):\n    # rect and petal are for future work\n    Circle, Ellipse, Rect, Petal = 1, 2, 3, 4\n\n    TitleFormat = \"<center><h4>{0}</h4>{1}</center>\"\n\n    selectionChanged = Signal()\n    itemTextEdited = Signal(int, str)\n\n    def __init__(self, parent=None):\n        super(VennDiagram, self).__init__(parent)\n        self.shapeType = VennDiagram.Circle\n\n        self._setup()\n\n    def _setup(self):\n        self._items = []\n        self._vennareas = []\n        self._textitems = []\n\n    def item(self, index):\n        return self._items[index]\n\n    def items(self):\n        return list(self._items)\n\n    def count(self):\n        return len(self._items)\n\n    def setItems(self, items):\n        if self._items:\n            self.clear()\n\n        self._items = list(items)\n\n        for item in self._items:\n            item.setParentItem(self)\n            item.setVisible(True)\n\n        fmt = self.TitleFormat.format\n\n        font = self.font()\n        font.setPixelSize(14)\n\n        for item in items:\n            text = GraphicsTextEdit(self)\n            text.setFont(font)\n            text.setDefaultTextColor(QColor(\"#333\"))\n            text.setHtml(fmt(escape(item.text), item.informativeText))\n            text.adjustSize()\n            text.editingStarted.connect(self._on_editingStarted)\n            text.editingFinished.connect(self._on_editingFinished)\n            text.documentSizeChanged.connect(\n                self._on_itemTextSizeChanged\n            )\n\n            self._textitems.append(text)\n\n        self._vennareas = [\n            VennIntersectionArea(parent=self)\n            for i in range(2 ** len(items))\n        ]\n        self._subsettextitems = [\n            QGraphicsTextItem(parent=self)\n            for i in range(2 ** len(items))\n        ]\n\n        self._updateLayout()\n\n    def clear(self):\n        scene = self.scene()\n        items = self.vennareas() + list(self.items()) + self._textitems\n\n        for item in self._textitems:\n            item.editingStarted.disconnect(self._on_editingStarted)\n            item.editingFinished.disconnect(self._on_editingFinished)\n            item.documentSizeChanged.disconnect(\n                self._on_itemTextSizeChanged\n            )\n\n        self._items = []\n        self._vennareas = []\n        self._textitems = []\n\n        for item in items:\n            item.setVisible(False)\n            item.setParentItem(None)\n            if scene is not None:\n                scene.removeItem(item)\n\n    def vennareas(self):\n        return list(self._vennareas)\n\n    def setFont(self, font):\n        if font != self.font():\n            self.prepareGeometryChange()\n            super().setFont(font)\n\n            for item in self.items():\n                item.setFont(font)\n\n    def _updateLayout(self):\n        rect = self.geometry()\n        n = len(self._items)\n        if not n:\n            return\n\n        regions = venn_diagram(n, shape=self.shapeType)\n\n        # The y axis in Qt points downward\n        transform = QTransform().scale(1, -1)\n        regions = list(map(transform.map, regions))\n\n        union_brect = reduce(QRectF.united,\n                             (path.boundingRect() for path in regions))\n\n        scalex = rect.width() / union_brect.width()\n        scaley = rect.height() / union_brect.height()\n        scale = min(scalex, scaley)\n\n        transform = QTransform().scale(scale, scale)\n\n        regions = [transform.map(path) for path in regions]\n\n        center = rect.width() / 2, rect.height() / 2\n        for item, path in zip(self.items(), regions):\n            item.setPath(path)\n            item.setPos(*center)\n\n        intersections = venn_intersections(regions)\n        assert len(intersections) == 2 ** n\n        assert len(self.vennareas()) == 2 ** n\n\n        anchors = [(0, 0)] + subset_anchors(self._items)\n\n        anchor_transform = QTransform().scale(rect.width(), -rect.height())\n        for i, area in enumerate(self.vennareas()):\n            area.setPath(intersections[setkey(i, n)])\n            area.setPos(*center)\n            x, y = anchors[i]\n            anchor = anchor_transform.map(QPointF(x, y))\n            area.setTextAnchor(anchor)\n            area.setZValue(30)\n\n        self._updateTextAnchors()\n\n    def _updateTextAnchors(self):\n        n = len(self._items)\n\n        items = self._items\n        dist = 15\n\n        shape = reduce(QPainterPath.united, [item.path() for item in items])\n        brect = shape.boundingRect()\n        bradius = max(brect.width() / 2, brect.height() / 2)\n\n        center = self.boundingRect().center()\n\n        anchors = _category_anchors(items)\n        self._textanchors = []\n        for angle, anchor_h, anchor_v in anchors:\n            line = QLineF.fromPolar(bradius, angle)\n            ext = QLineF.fromPolar(dist, angle)\n            line = QLineF(line.p1(), line.p2() + ext.p2())\n            line = line.translated(center)\n\n            anchor_pos = line.p2()\n            self._textanchors.append((anchor_pos, anchor_h, anchor_v))\n\n        for i in range(n):\n            self._updateTextItemPos(i)\n\n    def _updateTextItemPos(self, i):\n        item = self._textitems[i]\n        anchor_pos, anchor_h, anchor_v = self._textanchors[i]\n        rect = item.boundingRect()\n        pos = anchor_rect(rect, anchor_pos, anchor_h, anchor_v)\n        item.setPos(pos)\n\n    def setGeometry(self, geometry):\n        super(VennDiagram, self).setGeometry(geometry)\n        self._updateLayout()\n\n    def _on_editingStarted(self):\n        item = self.sender()\n        index = self._textitems.index(item)\n        text = self._items[index].text\n        item.setTextWidth(-1)\n        item.setHtml(self.TitleFormat.format(escape(text), \"<br/>\"))\n\n    def _on_editingFinished(self):\n        item = self.sender()\n        index = self._textitems.index(item)\n        text = item.toPlainText()\n        if text != self._items[index].text:\n            self._items[index].text = text\n\n            self.itemTextEdited.emit(index, text)\n\n        item.setHtml(\n            self.TitleFormat.format(\n                escape(text), self._items[index].informativeText))\n        item.adjustSize()\n\n    def _on_itemTextSizeChanged(self):\n        item = self.sender()\n        index = self._textitems.index(item)\n        self._updateTextItemPos(index)\n\n\ndef anchor_rect(rect, anchor_pos,\n                anchor_h=Qt.AnchorHorizontalCenter,\n                anchor_v=Qt.AnchorVerticalCenter):\n\n    if anchor_h == Qt.AnchorLeft:\n        x = anchor_pos.x()\n    elif anchor_h == Qt.AnchorHorizontalCenter:\n        x = anchor_pos.x() - rect.width() / 2\n    elif anchor_h == Qt.AnchorRight:\n        x = anchor_pos.x() - rect.width()\n    else:\n        raise ValueError(anchor_h)\n\n    if anchor_v == Qt.AnchorTop:\n        y = anchor_pos.y()\n    elif anchor_v == Qt.AnchorVerticalCenter:\n        y = anchor_pos.y() - rect.height() / 2\n    elif anchor_v == Qt.AnchorBottom:\n        y = anchor_pos.y() - rect.height()\n    else:\n        raise ValueError(anchor_v)\n\n    return QPointF(x, y)\n\n\ndef radians(angle):\n    return 2 * math.pi * angle / 360\n\n\ndef unit_point(x, r=1.0):\n    x = radians(x)\n    return (r * math.cos(x), r * math.sin(x))\n\n\ndef _category_anchors(shapes):\n    n = len(shapes)\n    return _CATEGORY_ANCHORS[n - 1]\n\n\n# (angle, horizontal anchor, vertical anchor)\n_CATEGORY_ANCHORS = (\n    # n == 1\n    ((90, Qt.AnchorHorizontalCenter, Qt.AnchorBottom),),\n    # n == 2\n    ((180, Qt.AnchorRight, Qt.AnchorVerticalCenter),\n     (0, Qt.AnchorLeft, Qt.AnchorVerticalCenter)),\n    # n == 3\n    ((150, Qt.AnchorRight, Qt.AnchorBottom),\n     (30, Qt.AnchorLeft, Qt.AnchorBottom),\n     (270, Qt.AnchorHorizontalCenter, Qt.AnchorTop)),\n    # n == 4\n    ((270 + 45, Qt.AnchorLeft, Qt.AnchorTop),\n     (270 - 45, Qt.AnchorRight, Qt.AnchorTop),\n     (90 - 15, Qt.AnchorLeft, Qt.AnchorBottom),\n     (90 + 15, Qt.AnchorRight, Qt.AnchorBottom)),\n    # n == 5\n    ((90 - 5, Qt.AnchorHorizontalCenter, Qt.AnchorBottom),\n     (18 - 5, Qt.AnchorLeft, Qt.AnchorVerticalCenter),\n     (306 - 5, Qt.AnchorLeft, Qt.AnchorTop),\n     (234 - 5, Qt.AnchorRight, Qt.AnchorTop),\n     (162 - 5, Qt.AnchorRight, Qt.AnchorVerticalCenter),)\n)\n\n\ndef subset_anchors(shapes):\n    n = len(shapes)\n    if n == 1:\n        return [(0, 0)]\n    elif n == 2:\n        return [unit_point(180, r=1/3),\n                unit_point(0, r=1/3),\n                (0, 0)]\n    elif n == 3:\n        return [unit_point(150, r=0.35),  # A\n                unit_point(30, r=0.35),   # B\n                unit_point(90, r=0.27),   # AB\n                unit_point(270, r=0.35),  # C\n                unit_point(210, r=0.27),  # AC\n                unit_point(330, r=0.27),  # BC\n                unit_point(0, r=0),]      # ABC\n    elif n == 4:\n        anchors = [\n            (0.400, 0.110),    # A\n            (-0.400, 0.110),   # B\n            (0.000, -0.285),   # AB\n            (0.180, 0.330),    # C\n            (0.265, 0.205),    # AC\n            (-0.240, -0.110),  # BC\n            (-0.100, -0.190),  # ABC\n            (-0.180, 0.330),   # D\n            (0.240, -0.110),   # AD\n            (-0.265, 0.205),   # BD\n            (0.100, -0.190),   # ABD\n            (0.000, 0.250),    # CD\n            (0.153, 0.090),    # ACD\n            (-0.153, 0.090),   # BCD\n            (0.000, -0.060),   # ABCD\n        ]\n        return anchors\n\n    elif n == 5:\n        anchors = [None] * 32\n        # Base anchors\n        A = (0.033, 0.385)\n        AD = (0.095, 0.250)\n        AE = (-0.100, 0.265)\n        ACE = (-0.130, 0.220)\n        ADE = (0.010, 0.225)\n        ACDE = (-0.095, 0.175)\n        ABCDE = (0.0, 0.0)\n\n        anchors[-1] = ABCDE\n\n        bases = [(0b00001, A),\n                 (0b01001, AD),\n                 (0b10001, AE),\n                 (0b10101, ACE),\n                 (0b11001, ADE),\n                 (0b11101, ACDE)]\n\n        for i in range(5):\n            for index, anchor in bases:\n                index = bit_rot_left(index, i, bits=5)\n                assert anchors[index] is None\n                anchors[index] = rotate_point(anchor, - 72 * i)\n\n        assert all(anchors[1:])\n        return anchors[1:]\n    return None\n\n\ndef bit_rot_left(x, y, bits=32):\n    mask = 2 ** bits - 1\n    x_masked = x & mask\n    return (x << y) & mask | (x_masked >> bits - y)\n\n\ndef rotate_point(p, angle):\n    r = radians(angle)\n    R = numpy.array([[math.cos(r), -math.sin(r)],\n                     [math.sin(r), math.cos(r)]])\n    x, y = numpy.dot(R, p)\n    return (float(x), float(y))\n\n\ndef line_extended(line, distance):\n    \"\"\"\n    Return an QLineF extended by `distance` units in the positive direction.\n    \"\"\"\n    angle = line.angle() / 360 * 2 * math.pi\n    dx, dy = unit_point(angle, r=distance)\n    return QLineF(line.p1(), line.p2() + QPointF(dx, dy))\n\n\ndef circle_path(center, r=1.0):\n    return ellipse_path(center, r, r, rotation=0)\n\n\ndef ellipse_path(center, a, b, rotation=0):\n    if not isinstance(center, QPointF):\n        center = QPointF(*center)\n\n    brect = QRectF(-a, -b, 2 * a, 2 * b)\n\n    path = QPainterPath()\n    path.addEllipse(brect)\n\n    if rotation != 0:\n        transform = QTransform().rotate(rotation)\n        path = transform.map(path)\n\n    path.translate(center)\n    return path\n\n\n# TODO: Should include anchors for text layout (both inside and outside).\n# for each item {path: QPainterPath,\n#                text_anchors: [{center}] * (2 ** n)\n#                mayor_axis: QLineF,\n#                boundingRect QPolygonF (with 4 vertices)}\n#\n# Should be a new class with overloads for ellipse/circle, rect, and petal\n# shapes, should store all constructor parameters, rotation, center,\n# mayor/minor axis.\n\n\ndef venn_diagram(n, shape=VennDiagram.Circle):\n    if n < 1 or n > 5:\n        raise ValueError()\n\n    paths = []\n\n    if n == 1:\n        paths = [circle_path(center=(0, 0), r=0.5)]\n    elif n == 2:\n        angles = [180, 0]\n        paths = [circle_path(center=unit_point(x, r=1/6), r=1/3)\n                 for x in angles]\n    elif n == 3:\n        angles = [150 - 120 * i for i in range(3)]\n        paths = [circle_path(center=unit_point(x, r=1/6), r=1/3)\n                 for x in angles]\n    elif n == 4:\n        # Constants shamelessly stolen from VennDiagram R package\n        paths = [\n            ellipse_path((0.65 - 0.5, 0.47 - 0.5), 0.35, 0.20, 45),\n            ellipse_path((0.35 - 0.5, 0.47 - 0.5), 0.35, 0.20, 135),\n            ellipse_path((0.5 - 0.5, 0.57 - 0.5), 0.35, 0.20, 45),\n            ellipse_path((0.5 - 0.5, 0.57 - 0.5), 0.35, 0.20, 134),\n        ]\n    elif n == 5:\n        # Constants shamelessly stolen from VennDiagram R package\n        d = 0.13\n        a, b = 0.24, 0.48\n        a, b = b, a\n        a, b = 0.48, 0.24\n        paths = [ellipse_path(unit_point((1 - i) * 72, r=d),\n                              a, b, rotation=90 - (i * 72))\n                 for i in range(5)]\n\n    return paths\n\n\ndef setkey(intval, n):\n    return tuple(bool(intval & (2 ** i)) for i in range(n))\n\n\ndef keyrange(n):\n    if n < 0:\n        raise ValueError()\n\n    for i in range(2 ** n):\n        yield setkey(i, n)\n\n\ndef venn_intersections(paths):\n    n = len(paths)\n    return {key: venn_intersection(paths, key) for key in keyrange(n)}\n\n\ndef venn_intersection(paths, key):\n    if not any(key):\n        return QPainterPath()\n\n    # first take the intersection of all included paths\n    path = reduce(QPainterPath.intersected,\n                  (path for path, included in zip(paths, key) if included))\n\n    # subtract all the excluded sets (i.e. take the intersection\n    # with the excluded set complements)\n    path = reduce(QPainterPath.subtracted,\n                  (path for path, included in zip(paths, key) if not included),\n                  path)\n\n    return path\n\n\ndef append_column(data, where, variable, column):\n    X, Y, M = data.X, data.Y, data.metas\n    domain = data.domain\n    attr = domain.attributes\n    class_vars = domain.class_vars\n    metas = domain.metas\n    if where == \"X\":\n        attr = attr + (variable,)\n        X = numpy.hstack((X, column))\n    elif where == \"Y\":\n        class_vars = class_vars + (variable,)\n        Y = numpy.hstack((Y, column))\n    elif where == \"M\":\n        metas = metas + (variable,)\n        M = numpy.hstack((M, column))\n    else:\n        raise ValueError\n    domain = Orange.data.Domain(attr, class_vars, metas)\n    new_data = data.transform(domain)\n    new_data[:, variable] = column\n    return new_data\n\n\ndef drop_columns(data, columns):\n    columns = set(data.domain[col] for col in columns)\n\n    def filter_vars(vars):\n        return tuple(var for var in vars if var not in columns)\n\n    domain = Orange.data.Domain(\n        filter_vars(data.domain.attributes),\n        filter_vars(data.domain.class_vars),\n        filter_vars(data.domain.metas)\n    )\n    return Orange.data.Table.from_table(domain, data)\n\n\ndef group_table_indices(table, key_var):\n    \"\"\"\n    Group table indices based on values of selected columns (`key_vars`).\n\n    Return a dictionary mapping all unique value combinations (keys)\n    into a list of indices in the table where they are present.\n    \"\"\"\n    groups = defaultdict(list)\n    for i, inst in enumerate(table):\n        groups[str(inst[key_var])].append(i)\n    return groups\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    from Orange.evaluation import ShuffleSplit\n\n    data = Orange.data.Table(\"brown-selected\")\n    # data1 = Orange.data.Table(\"brown-selected\")\n    # datasets = [(data, 1), (data, 2)]\n\n    data = append_column(data, \"M\", Orange.data.StringVariable(\"Test\"),\n                         numpy.arange(len(data)).reshape(-1, 1) % 30)\n    res = ShuffleSplit(data, [None], n_resamples=5,\n                       test_size=0.7, stratified=False)\n    indices = iter(res.indices)\n    datasets = []\n    for i in range(1, 6):\n        sample, _ = next(indices)\n        data1 = data[sample]\n        data1.name = chr(ord(\"A\") + i)\n        datasets.append((data1, i))\n\n    WidgetPreview(OWVennDiagram).run(setData=datasets)\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:** OWVennDiagram Does Not Commit Data When Autocommit Is Disabled

**Description:**
When the `autocommit` feature is disabled in the `OWVennDiagram` widget, sending new data does not trigger a commit. As a result, the widget fails to output the expected data, leaving the output empty despite receiving valid input.

**Example Code:**
```python
def test_disable_autocommit():
    widget = OWVennDiagram()
    widget.autocommit = False
    widget.send_signal("data", new_data)
    # Expected: Data is committed and output is available
    # Actual: No data is committed, and output remains empty
```

**Expected Behavior:**
With `autocommit` turned off, the widget should still commit incoming data upon receiving new signals to ensure that the data is processed and output correctly.

**Actual Behavior:**
When `autocommit` is disabled, sending new data does not result in a commit, and the widget does not produce any output data.

[/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/visualize/owvenndiagram.py
"""
Venn Diagram Widget
-------------------

"""

import math
import unicodedata
from collections import namedtuple, defaultdict, OrderedDict, Counter
from itertools import count
from functools import reduce
from xml.sax.saxutils import escape

import numpy

from AnyQt.QtWidgets import (
    QComboBox, QGraphicsScene, QGraphicsView, QGraphicsWidget,
    QGraphicsPathItem, QGraphicsTextItem, QStyle
)
from AnyQt.QtGui import (
    QPainterPath, QPainter, QTransform, QColor, QBrush, QPen, QPalette
)
from AnyQt.QtCore import Qt, QPointF, QRectF, QLineF
from AnyQt.QtCore import pyqtSignal as Signal

import Orange.data
from Orange.statistics import util
from Orange.widgets import widget, gui, settings
from Orange.widgets.utils import itemmodels, colorpalette
from Orange.widgets.utils.annotated_data import (create_annotated_table,
                                                 ANNOTATED_DATA_SIGNAL_NAME)
from Orange.widgets.utils.sql import check_sql_input
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.widget import Input, Output


_InputData = namedtuple("_InputData", ["key", "name", "table"])
_ItemSet = namedtuple("_ItemSet", ["key", "name", "title", "items"])


class OWVennDiagram(widget.OWWidget):
    name = "Venn Diagram"
    description = "A graphical visualization of the overlap of data instances " \
                  "from a collection of input datasets."
    icon = "icons/VennDiagram.svg"
    priority = 280
    keywords = []

    class Inputs:
        data = Input("Data", Orange.data.Table, multiple=True)

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

    inputhints: dict
    selection: list

    # Selected disjoint subset indices
    selection = settings.Setting([])
    #: Stored input set hints
    #: {(index, inputname, attributes): (selectedattrname, itemsettitle)}
    #: The 'selectedattrname' can be None
    inputhints = settings.Setting({})
    #: Use identifier columns for instance matching
    useidentifiers = settings.Setting(True)
    #: Output unique items (one output row for every unique instance `key`)
    #: or preserve all duplicates in the output.
    output_duplicates = settings.Setting(False)
    autocommit = settings.Setting(True)

    graph_name = "scene"

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

        # Diagram update is in progress
        self._updating = False
        # Input update is in progress
        self._inputUpdate = False
        # All input tables have the same domain.
        self.samedomain = True
        # Input datasets in the order they were 'connected'.
        self.data = OrderedDict()
        # Extracted input item sets in the order they were 'connected'
        self.itemsets = OrderedDict()

        # GUI
        box = gui.vBox(self.controlArea, "Info")
        self.infolabel = gui.widgetLabel(box, "No data on input.\n")

        self.identifiersBox = gui.radioButtonsInBox(
            self.controlArea, self, "useidentifiers", [],
            box="Data Instance Identifiers",
            callback=self._on_useidentifiersChanged
        )
        self.useequalityButton = gui.appendRadioButton(
            self.identifiersBox, "Use instance equality"
        )
        self.useidentifiersButton = rb = gui.appendRadioButton(
            self.identifiersBox, "Use identifiers"
        )
        self.inputsBox = gui.indentedBox(
            self.identifiersBox, sep=gui.checkButtonOffsetHint(rb)
        )
        self.inputsBox.setEnabled(bool(self.useidentifiers))

        for i in range(5):
            box = gui.vBox(self.inputsBox, "Dataset #%i" % (i + 1),
                           addSpace=False)
            box.setFlat(True)
            model = itemmodels.VariableListModel(parent=self)
            cb = gui.OrangeComboBox(
                minimumContentsLength=12,
                sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon
            )
            cb.setModel(model)
            cb.activated[int].connect(self._on_inputAttrActivated)
            box.setEnabled(False)
            # Store the combo in the box for later use.
            box.combo_box = cb
            box.layout().addWidget(cb)

        gui.rubber(self.controlArea)

        box = gui.vBox(self.controlArea, "Output")
        gui.checkBox(box, self, "output_duplicates", "Output duplicates",
                     callback=lambda: self.commit())
        gui.auto_commit(box, self, "autocommit", "Send Selection", "Send Automatically", box=False)

        # Main area view
        self.scene = QGraphicsScene()
        self.view = QGraphicsView(self.scene)
        self.view.setRenderHint(QPainter.Antialiasing)
        self.view.setBackgroundRole(QPalette.Window)
        self.view.setFrameStyle(QGraphicsView.StyledPanel)

        self.mainArea.layout().addWidget(self.view)
        self.vennwidget = VennDiagram()
        self.vennwidget.resize(400, 400)
        self.vennwidget.itemTextEdited.connect(self._on_itemTextEdited)
        self.scene.selectionChanged.connect(self._on_selectionChanged)

        self.scene.addItem(self.vennwidget)

        self.resize(self.controlArea.sizeHint().width() + 550,
                    max(self.controlArea.sizeHint().height(), 550))

        self._queue = []

    @Inputs.data
    @check_sql_input
    def setData(self, data, key=None):
        self.error()
        if not self._inputUpdate:
            # Store hints only on the first setData call.
            self._storeHints()
            self._inputUpdate = True

        if key in self.data:
            if data is None:
                # Remove the input
                self._remove(key)
            else:
                # Update existing item
                self._update(key, data)
        elif data is not None:
            # TODO: Allow setting more them 5 inputs and let the user
            # select the 5 to display.
            if len(self.data) == 5:
                self.error("Venn diagram accepts at most five datasets.")
                return
            # Add a new input
            self._add(key, data)

    def handleNewSignals(self):
        self._inputUpdate = False

        # Check if all inputs are from the same domain.
        domains = [input.table.domain for input in self.data.values()]
        samedomain = all(domain_eq(d1, d2) for d1, d2 in pairwise(domains))

        self.samedomain = samedomain

        has_identifiers = all(source_attributes(input.table.domain)
                              for input in self.data.values())
        has_any_identifiers = any(source_attributes(input.table.domain)
                                  for input in self.data.values())
        self.useequalityButton.setEnabled(samedomain)
        self.useidentifiersButton.setEnabled(
            has_any_identifiers or len(self.data) == 0)
        self.inputsBox.setEnabled(has_any_identifiers)

        if not samedomain and has_any_identifiers and not self.useidentifiers:
            self.useidentifiers = 1
        elif samedomain and not has_identifiers:
            self.useidentifiers = 0

        incremental = all(inc for _, inc in self._queue)

        if incremental:
            # Only received updated data on existing link.
            self._updateItemsets()
        else:
            # Links were removed and/or added.
            self._createItemsets()
            self._restoreHints()
            self._updateItemsets()

        del self._queue[:]

        self._createDiagram()
        if self.data:
            self.infolabel.setText(f"{len(self.data)} datasets on input.\n")
        else:
            self.infolabel.setText("No data on input\n")

        self._updateInfo()
        super().handleNewSignals()

    def _invalidate(self, keys=None, incremental=True):
        """
        Invalidate input for a list of input keys.
        """
        if keys is None:
            keys = list(self.data.keys())

        self._queue.extend((key, incremental) for key in keys)

    def itemsetAttr(self, key):
        index = list(self.data.keys()).index(key)
        _, combo = self._controlAtIndex(index)
        model = combo.model()
        attr_index = combo.currentIndex()
        if attr_index >= 0:
            return model[attr_index]
        else:
            return None

    def _controlAtIndex(self, index):
        group_box = self.inputsBox.layout().itemAt(index).widget()
        combo = group_box.combo_box
        return group_box, combo

    def _setAttributes(self, index, attrs):
        box, combo = self._controlAtIndex(index)
        model = combo.model()

        if attrs is None:
            model[:] = []
            box.setEnabled(False)
        else:
            if model[:] != attrs:
                model[:] = attrs

            box.setEnabled(True)

    def _add(self, key, table):
        name = table.name
        index = len(self.data)
        attrs = source_attributes(table.domain)

        self.data[key] = _InputData(key, name, table)

        self._setAttributes(index, attrs)

        self._invalidate([key], incremental=False)

        item = self.inputsBox.layout().itemAt(index)
        box = item.widget()
        box.setTitle("Dataset: {}".format(name))

    def _remove(self, key):
        index = list(self.data.keys()).index(key)

        # Clear possible warnings.
        self.warning()

        self._setAttributes(index, None)

        del self.data[key]

        layout = self.inputsBox.layout()
        item = layout.takeAt(index)
        layout.addItem(item)
        inputs = list(self.data.values())

        for i in range(5):
            box, _ = self._controlAtIndex(i)
            if i < len(inputs):
                title = "Dataset: {}".format(inputs[i].name)
            else:
                title = "Dataset #{}".format(i + 1)
            box.setTitle(title)

        self._invalidate([key], incremental=False)

    def _update(self, key, table):
        name = table.name
        index = list(self.data.keys()).index(key)
        attrs = source_attributes(table.domain)

        self.data[key] = self.data[key]._replace(name=name, table=table)

        self._setAttributes(index, attrs)
        self._invalidate([key])

        item = self.inputsBox.layout().itemAt(index)
        box = item.widget()
        box.setTitle("Dataset: {}".format(name))

    def _itemsForInput(self, key):
        useidentifiers = self.useidentifiers or not self.samedomain

        def items_by_key(key, input):
            attr = self.itemsetAttr(key)
            if attr is not None:
                return [str(inst[attr]) for inst in input.table
                        if not numpy.isnan(inst[attr])]
            else:
                return []

        def items_by_eq(key, input):
            return list(map(ComparableInstance, input.table))

        input = self.data[key]
        if useidentifiers:
            items = items_by_key(key, input)
        else:
            items = items_by_eq(key, input)
        return items

    def _updateItemsets(self):
        assert list(self.data.keys()) == list(self.itemsets.keys())
        for key, input in list(self.data.items()):
            items = self._itemsForInput(key)
            item = self.itemsets[key]
            item = item._replace(items=items)
            name = input.name
            if item.name != name:
                item = item._replace(name=name, title=name)
            self.itemsets[key] = item

    def _createItemsets(self):
        olditemsets = dict(self.itemsets)
        self.itemsets.clear()

        for key, input in self.data.items():
            items = self._itemsForInput(key)
            name = input.name
            if key in olditemsets and olditemsets[key].name == name:
                # Reuse the title (which might have been changed by the user)
                title = olditemsets[key].title
            else:
                title = name

            itemset = _ItemSet(key=key, name=name, title=title, items=items)
            self.itemsets[key] = itemset

    def _storeHints(self):
        if self.data:
            self.inputhints.clear()
            for i, (key, input) in enumerate(self.data.items()):
                attrs = source_attributes(input.table.domain)
                attrs = tuple(attr.name for attr in attrs)
                selected = self.itemsetAttr(key)
                if selected is not None:
                    attr_name = selected.name
                else:
                    attr_name = None
                itemset = self.itemsets[key]
                self.inputhints[(i, input.name, attrs)] = \
                    (attr_name, itemset.title)

    def _restoreHints(self):
        settings = []
        for i, (key, input) in enumerate(self.data.items()):
            attrs = source_attributes(input.table.domain)
            attrs = tuple(attr.name for attr in attrs)
            hint = self.inputhints.get((i, input.name, attrs), None)
            if hint is not None:
                attr, name = hint
                attr_ind = attrs.index(attr) if attr is not None else -1
                settings.append((attr_ind, name))
            else:
                return

        # all inputs match the stored hints
        for i, key in enumerate(self.itemsets):
            attr, itemtitle = settings[i]
            self.itemsets[key] = self.itemsets[key]._replace(title=itemtitle)
            _, cb = self._controlAtIndex(i)
            cb.setCurrentIndex(attr)

    def _createDiagram(self):
        self._updating = True

        oldselection = list(self.selection)

        self.vennwidget.clear()
        n = len(self.itemsets)
        self.disjoint = disjoint(set(s.items) for s in self.itemsets.values())

        vennitems = []
        colors = colorpalette.ColorPaletteHSV(n)

        for i, (_, item) in enumerate(self.itemsets.items()):
            count = len(set(item.items))
            count_all = len(item.items)
            if count != count_all:
                fmt = '{} <i>(all: {})</i>'
            else:
                fmt = '{}'
            counts = fmt.format(count, count_all)
            gr = VennSetItem(text=item.title, informativeText=counts)
            color = colors[i]
            color.setAlpha(100)
            gr.setBrush(QBrush(color))
            gr.setPen(QPen(Qt.NoPen))
            vennitems.append(gr)

        self.vennwidget.setItems(vennitems)

        for i, area in enumerate(self.vennwidget.vennareas()):
            area_items = list(map(str, list(self.disjoint[i])))
            if i:
                area.setText("{0}".format(len(area_items)))

            label = disjoint_set_label(i, n, simplify=False)
            head = "<h4>|{}| = {}</h4>".format(label, len(area_items))
            if len(area_items) > 32:
                items_str = ", ".join(map(escape, area_items[:32]))
                hidden = len(area_items) - 32
                tooltip = ("{}<span>{}, ...</br>({} items not shown)<span>"
                           .format(head, items_str, hidden))
            elif area_items:
                tooltip = "{}<span>{}</span>".format(
                    head,
                    ", ".join(map(escape, area_items))
                )
            else:
                tooltip = head

            area.setToolTip(tooltip)

            area.setPen(QPen(QColor(10, 10, 10, 200), 1.5))
            area.setFlag(QGraphicsPathItem.ItemIsSelectable, True)
            area.setSelected(i in oldselection)

        self._updating = False
        self._on_selectionChanged()

    def _updateInfo(self):
        # Clear all warnings
        self.warning()

        if not len(self.data):
            self.infolabel.setText("No data on input\n")
        else:
            self.infolabel.setText(f"{len(self.data)} datasets on input\n")

        if self.useidentifiers:
            no_idx = ["#{}".format(i + 1)
                      for i, key in enumerate(self.data)
                      if not source_attributes(self.data[key].table.domain)]
            if len(no_idx) == 1:
                self.warning("Dataset {} has no suitable identifiers."
                             .format(no_idx[0]))
            elif len(no_idx) > 1:
                self.warning("Datasets {} and {} have no suitable identifiers."
                             .format(", ".join(no_idx[:-1]), no_idx[-1]))

    def _on_selectionChanged(self):
        if self._updating:
            return

        areas = self.vennwidget.vennareas()
        indices = [i for i, area in enumerate(areas)
                   if area.isSelected()]

        self.selection = indices

        self.invalidateOutput()

    def _on_useidentifiersChanged(self):
        self.inputsBox.setEnabled(self.useidentifiers == 1)
        # Invalidate all itemsets
        self._invalidate()
        self._updateItemsets()
        self._createDiagram()

        self._updateInfo()

    def _on_inputAttrActivated(self, attr_index):
        combo = self.sender()
        # Find the input index to which the combo box belongs
        # (they are reordered when removing inputs).
        index = None
        inputs = list(self.data.items())
        for i in range(len(inputs)):
            _, c = self._controlAtIndex(i)
            if c is combo:
                index = i
                break

        assert index is not None

        key, _ = inputs[index]

        self._invalidate([key])
        self._updateItemsets()
        self._createDiagram()

    def _on_itemTextEdited(self, index, text):
        text = str(text)
        key = list(self.itemsets.keys())[index]
        self.itemsets[key] = self.itemsets[key]._replace(title=text)

    def invalidateOutput(self):
        self.commit()

    def commit(self):
        selected_subsets = []
        annotated_data = None
        annotated_data_subsets = []
        annotated_data_masks = []
        selected_items = reduce(
            set.union, [self.disjoint[index] for index in self.selection],
            set()
        )

        def match(val):
            if numpy.isnan(val):
                return False
            else:
                return str(val) in selected_items

        source_var = Orange.data.StringVariable("source")
        item_id_var = Orange.data.StringVariable("item_id")

        names = [itemset.title.strip() for itemset in self.itemsets.values()]
        names = uniquify(names)

        for i, (key, input) in enumerate(self.data.items()):
            # cell vars are in functions that are only used in the loop
            # pylint: disable=cell-var-from-loop
            if not len(input.table):
                continue
            if self.useidentifiers:
                attr = self.itemsetAttr(key)
                if attr is not None:
                    mask = list(map(match, (inst[attr] for inst in input.table)))
                else:
                    mask = [False] * len(input.table)

                def instance_key(inst):
                    return str(inst[attr])

                def instance_key_all(inst):
                    return str(inst[attr])
            else:
                mask = [ComparableInstance(inst) in selected_items
                        for inst in input.table]
                _map = {item: str(i) for i, item in enumerate(selected_items)}
                _map_all = {
                    ComparableInstance(i):  hash(ComparableInstance(i))
                    for i in input.table
                }

                def instance_key(inst):
                    return _map[ComparableInstance(inst)]

                def instance_key_all(inst):
                    return _map_all[ComparableInstance(inst)]

            mask = numpy.array(mask, dtype=bool)
            subset = input.table[mask]

            annotated_subset = input.table
            id_column = numpy.array(
                [[instance_key_all(inst)] for inst in annotated_subset])
            source_names = numpy.array([[names[i]]] * len(annotated_subset))
            annotated_subset = append_column(
                annotated_subset, "M", source_var, source_names)
            annotated_subset = append_column(
                annotated_subset, "M", item_id_var, id_column)
            annotated_data_subsets.append(annotated_subset)
            annotated_data_masks.append(mask)

            if len(subset) == 0:
                continue

            # add columns with source table id and set id

            if not self.output_duplicates:
                id_column = numpy.array([[instance_key(inst)] for inst in subset],
                                        dtype=object)
                source_names = numpy.array([[names[i]]] * len(subset),
                                           dtype=object)

                subset = append_column(subset, "M", source_var, source_names)
                subset = append_column(subset, "M", item_id_var, id_column)

            selected_subsets.append(subset)

        if selected_subsets and not self.output_duplicates:
            data = table_concat(selected_subsets)
            # Get all variables which are not constant between the same
            # item set
            varying = varying_between(data, item_id_var)

            if source_var in varying:
                varying.remove(source_var)

            data = reshape_wide(data, varying, [item_id_var], [source_var])
            # remove the temporary item set id column
            data = drop_columns(data, [item_id_var])
        elif selected_subsets:
            data = table_concat(selected_subsets)
        else:
            data = None

        if annotated_data_subsets:
            annotated_data = table_concat(annotated_data_subsets)
            indices = numpy.hstack(annotated_data_masks)
            annotated_data = create_annotated_table(annotated_data, indices)
            varying = varying_between(annotated_data, item_id_var)
            if source_var in varying:
                varying.remove(source_var)
            annotated_data = reshape_wide(annotated_data, varying,
                                          [item_id_var], [source_var])
            annotated_data = drop_columns(annotated_data, [item_id_var])

        self.Outputs.selected_data.send(data)
        self.Outputs.annotated_data.send(annotated_data)

    def getSettings(self, *args, **kwargs):
        self._storeHints()
        return super().getSettings(self, *args, **kwargs)

    def send_report(self):
        self.report_plot()


def pairwise(iterable):
    """
    Return an iterator over consecutive pairs in `iterable`.

    >>> list(pairwise([1, 2, 3, 4])
    [(1, 2), (2, 3), (3, 4)]

    """
    it = iter(iterable)
    try:
        first = next(it)
    except StopIteration:
        return
    for second in it:
        yield first, second
        first = second


# Custom domain comparison (domains do not seem to compare equal
# even if they have exactly the same variables).
# TODO: What about metas.
def domain_eq(d1, d2):
    return d1.variables == d2.variables


# Comparing/hashing Orange.data.Instance across domains ignoring metas.
class ComparableInstance:
    __slots__ = ["inst", "domain", "__hash"]

    def __init__(self, inst):
        self.inst = inst
        self.domain = inst.domain
        self.__hash = hash((self.inst.x.data.tobytes(),
                            self.inst.y.data.tobytes()))

    def __hash__(self):
        return self.__hash

    def __eq__(self, other):
        # XXX: comparing NaN with different payload
        return (isinstance(other, ComparableInstance)
                and domain_eq(self.domain, other.domain)
                and self.inst.x.data.tobytes() == other.inst.x.data.tobytes()
                and self.inst.y.data.tobytes() == other.inst.y.data.tobytes())

    def __iter__(self):
        return iter(self.inst)

    def __repr__(self):
        return repr(self.inst)

    def __str__(self):
        return str(self.inst)


def table_concat(tables):
    """
    Concatenate a list of tables.

    The resulting table will have a union of all attributes of `tables`.

    """
    attributes = []
    class_vars = []
    metas = []
    variables_seen = set()

    for table in tables:
        attributes.extend(v for v in table.domain.attributes
                          if v not in variables_seen)
        variables_seen.update(table.domain.attributes)

        class_vars.extend(v for v in table.domain.class_vars
                          if v not in variables_seen)
        variables_seen.update(table.domain.class_vars)

        metas.extend(v for v in table.domain.metas
                     if v not in variables_seen)

        variables_seen.update(table.domain.metas)

    domain = Orange.data.Domain(attributes, class_vars, metas)

    tables = [tab.transform(domain) for tab in tables]
    return tables[0].concatenate(tables, axis=0)


def copy_descriptor(descriptor, newname=None):
    """
    Create a copy of the descriptor.

    If newname is not None the new descriptor will have the same
    name as the input.

    """
    if newname is None:
        newname = descriptor.name

    if descriptor.is_discrete:
        newf = Orange.data.DiscreteVariable(
            newname,
            values=descriptor.values,
            ordered=descriptor.ordered,
        )
        newf.attributes = dict(descriptor.attributes)

    elif descriptor.is_continuous:
        newf = Orange.data.ContinuousVariable(newname)
        newf.number_of_decimals = descriptor.number_of_decimals
        newf.attributes = dict(descriptor.attributes)

    else:
        newf = type(descriptor)(newname)
        newf.attributes = dict(descriptor.attributes)

    return newf


def reshape_wide(table, varlist, idvarlist, groupvarlist):
    """
    Reshape a data table into a wide format.

    :param Orange.data.Table table:
        Source data table in long format.
    :param varlist:
        A list of variables to reshape.
    :param list idvarlist:
        A list of variables in `table` uniquely identifying multiple
        records in `table` (i.e. subject id).
    :param groupvarlist:
        A list of variables differentiating multiple records
        (i.e. conditions).

    """
    def inst_key(inst, vars):
        return tuple(str(inst[var]) for var in vars)

    instance_groups = [inst_key(inst, groupvarlist) for inst in table]
    # A list of groups (for each element in a group the varying variable
    # will be duplicated)
    groups = list(unique(instance_groups))
    group_names = [", ".join(group) for group in groups]

    # A list of instance ids (subject ids)
    # Each instance in the output will correspond to one of these ids)
    instance_ids = [inst_key(inst, idvarlist) for inst in table]
    ids = list(unique(instance_ids))

    # an mapping from ids to an list of input instance indices
    # each instance in this list belongs to one group (but not all
    # groups need to be present).
    inst_by_id = defaultdict(list)
    id_by_inst = defaultdict(list)  # inverse mapping

    for i in range(len(table)):
        inst_id = instance_ids[i]
        inst_by_id[inst_id].append(i)
        id_by_inst[i] = inst_id

    newfeatures = []
    newclass_vars = []
    newmetas = []
    expanded_features = {}

    def expanded(feat):
        return [copy_descriptor(feat, newname="%s (%s)" %
                                (feat.name, group_name))
                for group_name in group_names]

    for feat in table.domain.attributes:
        if feat in varlist:
            features = expanded(feat)
            newfeatures.extend(features)
            expanded_features[feat] = dict(zip(groups, features))
        elif feat not in groupvarlist:
            newfeatures.append(feat)

    for feat in table.domain.class_vars:
        if feat in varlist:
            features = expanded(feat)
            newclass_vars.extend(features)
            expanded_features[feat] = dict(zip(groups, features))
        elif feat not in groupvarlist:
            newclass_vars.append(feat)

    for meta in table.domain.metas:
        if meta in varlist:
            metas = expanded(meta)
            newmetas.extend(metas)
            expanded_features[meta] = dict(zip(groups, metas))
        elif meta not in groupvarlist:
            newmetas.append(meta)

    domain = Orange.data.Domain(newfeatures, newclass_vars, newmetas)
    prototype_indices = [inst_by_id[inst_id][0] for inst_id in ids]
    newtable = table[prototype_indices].transform(domain)
    in_expanded = set(f for efd in expanded_features.values() for f in efd.values())

    # Fill-in nan values
    for var in domain.variables + domain.metas:
        if var in idvarlist or var in in_expanded:
            continue
        col, _ = newtable.get_column_view(var)
        nan_indices = (i for i in col.nonzero()[0]
                       if isinstance(col[i], str) or numpy.isnan(col[i]))
        for i in nan_indices:
            for ind in inst_by_id[ids[i]]:
                if not numpy.isnan(table[ind, var]):
                    newtable[i, var] = table[ind, var]
                    break

    # Fill-in expanded features if any
    for i, inst_id in enumerate(ids):
        indices = inst_by_id[inst_id]
        instance = newtable[i]

        for index in indices:
            source_inst = table[index]
            group = instance_groups[index]
            for source_var in varlist:
                newf = expanded_features[source_var][group]
                instance[newf] = source_inst[source_var]

    return newtable


def unique(seq):
    """
    Return an iterator over unique items of `seq`.

    .. note:: Items must be hashable.

    """
    seen = set()
    for item in seq:
        if item not in seen:
            yield item
            seen.add(item)


def unique_non_nan(ar):
    # metas have sometimes object dtype, but values are numpy floats
    ar = ar.astype('float64')
    uniq = numpy.unique(ar)
    return uniq[~numpy.isnan(uniq)]


def varying_between(table, idvar):
    """
    Return a list of all variables with non constant values between
    groups defined by `idvar`.

    """
    all_possible = [var for var in table.domain.variables + table.domain.metas
                    if var != idvar]
    candidate_set = set(all_possible)

    idmap = group_table_indices(table, idvar)

    varying = set()
    for indices in idmap.values():
        subset = table[indices]
        for var in list(candidate_set):
            column, _ = subset.get_column_view(var)
            with numpy.errstate(invalid="ignore"):  # nans are removed below
                values = util.unique(column)

            if not var.is_string:
                values = unique_non_nan(values)

            if len(values) > 1:
                varying.add(var)
                candidate_set.remove(var)

    return sorted(varying, key=all_possible.index)


def uniquify(strings):
    """
    Return a list of unique strings.

    The string at i'th position will have the same prefix as strings[i]
    with an appended suffix to make the item unique (if necessary).

    >>> uniquify(["cat", "dog", "cat"])
    ["cat 1", "dog", "cat 2"]

    """
    counter = Counter(strings)
    counts = defaultdict(count)
    newstrings = []
    for string in strings:
        if counter[string] > 1:
            newstrings.append(string + (" %i" % (next(counts[string]) + 1)))
        else:
            newstrings.append(string)

    return newstrings


def string_attributes(domain):
    """
    Return all string attributes from the domain.
    """
    return [attr for attr in domain.variables + domain.metas if attr.is_string]


def discrete_attributes(domain):
    """
    Return all discrete attributes from the domain.
    """
    return [attr for attr in domain.variables + domain.metas if attr.is_discrete]


def source_attributes(domain):
    """
    Return all suitable attributes for the venn diagram.
    """
    return string_attributes(domain)  # + discrete_attributes(domain)


def disjoint(sets):
    """
    Return all disjoint subsets.
    """
    sets = list(sets)
    n = len(sets)
    disjoint_sets = [None] * (2 ** n)
    for i in range(2 ** n):
        key = setkey(i, n)
        included = [s for s, inc in zip(sets, key) if inc]
        excluded = [s for s, inc in zip(sets, key) if not inc]
        if any(included):
            s = reduce(set.intersection, included)
        else:
            s = set()

        s = reduce(set.difference, excluded, s)

        disjoint_sets[i] = s

    return disjoint_sets


def disjoint_set_label(i, n, simplify=False):
    """
    Return a html formated label for a disjoint set indexed by `i`.
    """
    intersection = unicodedata.lookup("INTERSECTION")
    # comp = unicodedata.lookup("COMPLEMENT")  #
    # This depends on the font but the unicode complement in
    # general does not look nice in a super script so we use
    # plain c instead.
    comp = "c"

    def label_for_index(i):
        return chr(ord("A") + i)

    if simplify:
        return "".join(label_for_index(i) for i, b in enumerate(setkey(i, n))
                       if b)
    else:
        return intersection.join(label_for_index(i) +
                                 ("" if b else "<sup>" + comp + "</sup>")
                                 for i, b in enumerate(setkey(i, n)))


class VennSetItem(QGraphicsPathItem):
    def __init__(self, parent=None, text="", informativeText=""):
        super(VennSetItem, self).__init__(parent)
        # Plain text title (editable by the VennDiagram)
        self.text = text
        # Extra informative text (possibly rich text)
        self.informativeText = informativeText

# TODO: Use palette's selected/highligted text / background colors to
# indicate selection

class VennIntersectionArea(QGraphicsPathItem):
    def __init__(self, parent=None, text=""):
        super().__init__(parent)
        self.setAcceptHoverEvents(True)
        self.setPen(QPen(Qt.NoPen))

        self.text = QGraphicsTextItem(self)
        layout = self.text.document().documentLayout()
        layout.documentSizeChanged.connect(self._onLayoutChanged)

        self._text = ""
        self._anchor = QPointF()

    def setText(self, text):
        if self._text != text:
            self._text = text
            self.text.setPlainText(text)

    def setTextAnchor(self, pos):
        if self._anchor != pos:
            self._anchor = pos
            self._updateTextAnchor()

    def hoverEnterEvent(self, event):
        self.setZValue(self.zValue() + 1)
        return QGraphicsPathItem.hoverEnterEvent(self, event)

    def hoverLeaveEvent(self, event):
        self.setZValue(self.zValue() - 1)
        return QGraphicsPathItem.hoverLeaveEvent(self, event)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            if event.modifiers() & Qt.AltModifier:
                self.setSelected(False)
            elif event.modifiers() & Qt.ControlModifier:
                self.setSelected(not self.isSelected())
            elif event.modifiers() & Qt.ShiftModifier:
                self.setSelected(True)
            else:
                for area in self.parentWidget().vennareas():
                    area.setSelected(False)
                self.setSelected(True)

    def mouseReleaseEvent(self, event):
        pass

    def paint(self, painter, option, widget=None):
        painter.save()
        path = self.path()
        brush = QBrush(self.brush())
        pen = QPen(self.pen())

        if option.state & QStyle.State_Selected:
            pen.setColor(Qt.red)
            brush.setStyle(Qt.DiagCrossPattern)
            brush.setColor(QColor(40, 40, 40, 100))

        elif option.state & QStyle.State_MouseOver:
            pen.setColor(Qt.blue)

        if option.state & QStyle.State_MouseOver:
            brush.setColor(QColor(100, 100, 100, 100))
            if brush.style() == Qt.NoBrush:
                # Make sure the highlight is actually visible.
                brush.setStyle(Qt.SolidPattern)

        painter.setPen(pen)
        painter.setBrush(brush)
        painter.drawPath(path)
        painter.restore()

    def itemChange(self, change, value):
        if change == QGraphicsPathItem.ItemSelectedHasChanged:
            self.setZValue(self.zValue() + (1 if value else -1))
        return QGraphicsPathItem.itemChange(self, change, value)

    def _updateTextAnchor(self):
        rect = self.text.boundingRect()
        pos = anchor_rect(rect, self._anchor)
        self.text.setPos(pos)

    def _onLayoutChanged(self):
        self._updateTextAnchor()


class GraphicsTextEdit(QGraphicsTextItem):
    #: Edit triggers
    NoEditTriggers, DoubleClicked = 0, 1

    editingFinished = Signal()
    editingStarted = Signal()

    documentSizeChanged = Signal()

    def __init__(self, *args, **kwargs):
        super(GraphicsTextEdit, self).__init__(*args, **kwargs)
        self.setCursor(Qt.IBeamCursor)
        self.setTabChangesFocus(True)
        self._edittrigger = GraphicsTextEdit.DoubleClicked
        self._editing = False
        self.document().documentLayout().documentSizeChanged.connect(
            self.documentSizeChanged
        )

    def mouseDoubleClickEvent(self, event):
        super(GraphicsTextEdit, self).mouseDoubleClickEvent(event)
        if self._edittrigger == GraphicsTextEdit.DoubleClicked:
            self._start()

    def focusOutEvent(self, event):
        super(GraphicsTextEdit, self).focusOutEvent(event)

        if self._editing:
            self._end()

    def _start(self):
        self._editing = True
        self.setTextInteractionFlags(Qt.TextEditorInteraction)
        self.setFocus(Qt.MouseFocusReason)
        self.editingStarted.emit()

    def _end(self):
        self._editing = False
        self.setTextInteractionFlags(Qt.NoTextInteraction)
        self.editingFinished.emit()


class VennDiagram(QGraphicsWidget):
    # rect and petal are for future work
    Circle, Ellipse, Rect, Petal = 1, 2, 3, 4

    TitleFormat = "<center><h4>{0}</h4>{1}</center>"

    selectionChanged = Signal()
    itemTextEdited = Signal(int, str)

    def __init__(self, parent=None):
        super(VennDiagram, self).__init__(parent)
        self.shapeType = VennDiagram.Circle

        self._setup()

    def _setup(self):
        self._items = []
        self._vennareas = []
        self._textitems = []

    def item(self, index):
        return self._items[index]

    def items(self):
        return list(self._items)

    def count(self):
        return len(self._items)

    def setItems(self, items):
        if self._items:
            self.clear()

        self._items = list(items)

        for item in self._items:
            item.setParentItem(self)
            item.setVisible(True)

        fmt = self.TitleFormat.format

        font = self.font()
        font.setPixelSize(14)

        for item in items:
            text = GraphicsTextEdit(self)
            text.setFont(font)
            text.setDefaultTextColor(QColor("#333"))
            text.setHtml(fmt(escape(item.text), item.informativeText))
            text.adjustSize()
            text.editingStarted.connect(self._on_editingStarted)
            text.editingFinished.connect(self._on_editingFinished)
            text.documentSizeChanged.connect(
                self._on_itemTextSizeChanged
            )

            self._textitems.append(text)

        self._vennareas = [
            VennIntersectionArea(parent=self)
            for i in range(2 ** len(items))
        ]
        self._subsettextitems = [
            QGraphicsTextItem(parent=self)
            for i in range(2 ** len(items))
        ]

        self._updateLayout()

    def clear(self):
        scene = self.scene()
        items = self.vennareas() + list(self.items()) + self._textitems

        for item in self._textitems:
            item.editingStarted.disconnect(self._on_editingStarted)
            item.editingFinished.disconnect(self._on_editingFinished)
            item.documentSizeChanged.disconnect(
                self._on_itemTextSizeChanged
            )

        self._items = []
        self._vennareas = []
        self._textitems = []

        for item in items:
            item.setVisible(False)
            item.setParentItem(None)
            if scene is not None:
                scene.removeItem(item)

    def vennareas(self):
        return list(self._vennareas)

    def setFont(self, font):
        if font != self.font():
            self.prepareGeometryChange()
            super().setFont(font)

            for item in self.items():
                item.setFont(font)

    def _updateLayout(self):
        rect = self.geometry()
        n = len(self._items)
        if not n:
            return

        regions = venn_diagram(n, shape=self.shapeType)

        # The y axis in Qt points downward
        transform = QTransform().scale(1, -1)
        regions = list(map(transform.map, regions))

        union_brect = reduce(QRectF.united,
                             (path.boundingRect() for path in regions))

        scalex = rect.width() / union_brect.width()
        scaley = rect.height() / union_brect.height()
        scale = min(scalex, scaley)

        transform = QTransform().scale(scale, scale)

        regions = [transform.map(path) for path in regions]

        center = rect.width() / 2, rect.height() / 2
        for item, path in zip(self.items(), regions):
            item.setPath(path)
            item.setPos(*center)

        intersections = venn_intersections(regions)
        assert len(intersections) == 2 ** n
        assert len(self.vennareas()) == 2 ** n

        anchors = [(0, 0)] + subset_anchors(self._items)

        anchor_transform = QTransform().scale(rect.width(), -rect.height())
        for i, area in enumerate(self.vennareas()):
            area.setPath(intersections[setkey(i, n)])
            area.setPos(*center)
            x, y = anchors[i]
            anchor = anchor_transform.map(QPointF(x, y))
            area.setTextAnchor(anchor)
            area.setZValue(30)

        self._updateTextAnchors()

    def _updateTextAnchors(self):
        n = len(self._items)

        items = self._items
        dist = 15

        shape = reduce(QPainterPath.united, [item.path() for item in items])
        brect = shape.boundingRect()
        bradius = max(brect.width() / 2, brect.height() / 2)

        center = self.boundingRect().center()

        anchors = _category_anchors(items)
        self._textanchors = []
        for angle, anchor_h, anchor_v in anchors:
            line = QLineF.fromPolar(bradius, angle)
            ext = QLineF.fromPolar(dist, angle)
            line = QLineF(line.p1(), line.p2() + ext.p2())
            line = line.translated(center)

            anchor_pos = line.p2()
            self._textanchors.append((anchor_pos, anchor_h, anchor_v))

        for i in range(n):
            self._updateTextItemPos(i)

    def _updateTextItemPos(self, i):
        item = self._textitems[i]
        anchor_pos, anchor_h, anchor_v = self._textanchors[i]
        rect = item.boundingRect()
        pos = anchor_rect(rect, anchor_pos, anchor_h, anchor_v)
        item.setPos(pos)

    def setGeometry(self, geometry):
        super(VennDiagram, self).setGeometry(geometry)
        self._updateLayout()

    def _on_editingStarted(self):
        item = self.sender()
        index = self._textitems.index(item)
        text = self._items[index].text
        item.setTextWidth(-1)
        item.setHtml(self.TitleFormat.format(escape(text), "<br/>"))

    def _on_editingFinished(self):
        item = self.sender()
        index = self._textitems.index(item)
        text = item.toPlainText()
        if text != self._items[index].text:
            self._items[index].text = text

            self.itemTextEdited.emit(index, text)

        item.setHtml(
            self.TitleFormat.format(
                escape(text), self._items[index].informativeText))
        item.adjustSize()

    def _on_itemTextSizeChanged(self):
        item = self.sender()
        index = self._textitems.index(item)
        self._updateTextItemPos(index)


def anchor_rect(rect, anchor_pos,
                anchor_h=Qt.AnchorHorizontalCenter,
                anchor_v=Qt.AnchorVerticalCenter):

    if anchor_h == Qt.AnchorLeft:
        x = anchor_pos.x()
    elif anchor_h == Qt.AnchorHorizontalCenter:
        x = anchor_pos.x() - rect.width() / 2
    elif anchor_h == Qt.AnchorRight:
        x = anchor_pos.x() - rect.width()
    else:
        raise ValueError(anchor_h)

    if anchor_v == Qt.AnchorTop:
        y = anchor_pos.y()
    elif anchor_v == Qt.AnchorVerticalCenter:
        y = anchor_pos.y() - rect.height() / 2
    elif anchor_v == Qt.AnchorBottom:
        y = anchor_pos.y() - rect.height()
    else:
        raise ValueError(anchor_v)

    return QPointF(x, y)


def radians(angle):
    return 2 * math.pi * angle / 360


def unit_point(x, r=1.0):
    x = radians(x)
    return (r * math.cos(x), r * math.sin(x))


def _category_anchors(shapes):
    n = len(shapes)
    return _CATEGORY_ANCHORS[n - 1]


# (angle, horizontal anchor, vertical anchor)
_CATEGORY_ANCHORS = (
    # n == 1
    ((90, Qt.AnchorHorizontalCenter, Qt.AnchorBottom),),
    # n == 2
    ((180, Qt.AnchorRight, Qt.AnchorVerticalCenter),
     (0, Qt.AnchorLeft, Qt.AnchorVerticalCenter)),
    # n == 3
    ((150, Qt.AnchorRight, Qt.AnchorBottom),
     (30, Qt.AnchorLeft, Qt.AnchorBottom),
     (270, Qt.AnchorHorizontalCenter, Qt.AnchorTop)),
    # n == 4
    ((270 + 45, Qt.AnchorLeft, Qt.AnchorTop),
     (270 - 45, Qt.AnchorRight, Qt.AnchorTop),
     (90 - 15, Qt.AnchorLeft, Qt.AnchorBottom),
     (90 + 15, Qt.AnchorRight, Qt.AnchorBottom)),
    # n == 5
    ((90 - 5, Qt.AnchorHorizontalCenter, Qt.AnchorBottom),
     (18 - 5, Qt.AnchorLeft, Qt.AnchorVerticalCenter),
     (306 - 5, Qt.AnchorLeft, Qt.AnchorTop),
     (234 - 5, Qt.AnchorRight, Qt.AnchorTop),
     (162 - 5, Qt.AnchorRight, Qt.AnchorVerticalCenter),)
)


def subset_anchors(shapes):
    n = len(shapes)
    if n == 1:
        return [(0, 0)]
    elif n == 2:
        return [unit_point(180, r=1/3),
                unit_point(0, r=1/3),
                (0, 0)]
    elif n == 3:
        return [unit_point(150, r=0.35),  # A
                unit_point(30, r=0.35),   # B
                unit_point(90, r=0.27),   # AB
                unit_point(270, r=0.35),  # C
                unit_point(210, r=0.27),  # AC
                unit_point(330, r=0.27),  # BC
                unit_point(0, r=0),]      # ABC
    elif n == 4:
        anchors = [
            (0.400, 0.110),    # A
            (-0.400, 0.110),   # B
            (0.000, -0.285),   # AB
            (0.180, 0.330),    # C
            (0.265, 0.205),    # AC
            (-0.240, -0.110),  # BC
            (-0.100, -0.190),  # ABC
            (-0.180, 0.330),   # D
            (0.240, -0.110),   # AD
            (-0.265, 0.205),   # BD
            (0.100, -0.190),   # ABD
            (0.000, 0.250),    # CD
            (0.153, 0.090),    # ACD
            (-0.153, 0.090),   # BCD
            (0.000, -0.060),   # ABCD
        ]
        return anchors

    elif n == 5:
        anchors = [None] * 32
        # Base anchors
        A = (0.033, 0.385)
        AD = (0.095, 0.250)
        AE = (-0.100, 0.265)
        ACE = (-0.130, 0.220)
        ADE = (0.010, 0.225)
        ACDE = (-0.095, 0.175)
        ABCDE = (0.0, 0.0)

        anchors[-1] = ABCDE

        bases = [(0b00001, A),
                 (0b01001, AD),
                 (0b10001, AE),
                 (0b10101, ACE),
                 (0b11001, ADE),
                 (0b11101, ACDE)]

        for i in range(5):
            for index, anchor in bases:
                index = bit_rot_left(index, i, bits=5)
                assert anchors[index] is None
                anchors[index] = rotate_point(anchor, - 72 * i)

        assert all(anchors[1:])
        return anchors[1:]
    return None


def bit_rot_left(x, y, bits=32):
    mask = 2 ** bits - 1
    x_masked = x & mask
    return (x << y) & mask | (x_masked >> bits - y)


def rotate_point(p, angle):
    r = radians(angle)
    R = numpy.array([[math.cos(r), -math.sin(r)],
                     [math.sin(r), math.cos(r)]])
    x, y = numpy.dot(R, p)
    return (float(x), float(y))


def line_extended(line, distance):
    """
    Return an QLineF extended by `distance` units in the positive direction.
    """
    angle = line.angle() / 360 * 2 * math.pi
    dx, dy = unit_point(angle, r=distance)
    return QLineF(line.p1(), line.p2() + QPointF(dx, dy))


def circle_path(center, r=1.0):
    return ellipse_path(center, r, r, rotation=0)


def ellipse_path(center, a, b, rotation=0):
    if not isinstance(center, QPointF):
        center = QPointF(*center)

    brect = QRectF(-a, -b, 2 * a, 2 * b)

    path = QPainterPath()
    path.addEllipse(brect)

    if rotation != 0:
        transform = QTransform().rotate(rotation)
        path = transform.map(path)

    path.translate(center)
    return path


# TODO: Should include anchors for text layout (both inside and outside).
# for each item {path: QPainterPath,
#                text_anchors: [{center}] * (2 ** n)
#                mayor_axis: QLineF,
#                boundingRect QPolygonF (with 4 vertices)}
#
# Should be a new class with overloads for ellipse/circle, rect, and petal
# shapes, should store all constructor parameters, rotation, center,
# mayor/minor axis.


def venn_diagram(n, shape=VennDiagram.Circle):
    if n < 1 or n > 5:
        raise ValueError()

    paths = []

    if n == 1:
        paths = [circle_path(center=(0, 0), r=0.5)]
    elif n == 2:
        angles = [180, 0]
        paths = [circle_path(center=unit_point(x, r=1/6), r=1/3)
                 for x in angles]
    elif n == 3:
        angles = [150 - 120 * i for i in range(3)]
        paths = [circle_path(center=unit_point(x, r=1/6), r=1/3)
                 for x in angles]
    elif n == 4:
        # Constants shamelessly stolen from VennDiagram R package
        paths = [
            ellipse_path((0.65 - 0.5, 0.47 - 0.5), 0.35, 0.20, 45),
            ellipse_path((0.35 - 0.5, 0.47 - 0.5), 0.35, 0.20, 135),
            ellipse_path((0.5 - 0.5, 0.57 - 0.5), 0.35, 0.20, 45),
            ellipse_path((0.5 - 0.5, 0.57 - 0.5), 0.35, 0.20, 134),
        ]
    elif n == 5:
        # Constants shamelessly stolen from VennDiagram R package
        d = 0.13
        a, b = 0.24, 0.48
        a, b = b, a
        a, b = 0.48, 0.24
        paths = [ellipse_path(unit_point((1 - i) * 72, r=d),
                              a, b, rotation=90 - (i * 72))
                 for i in range(5)]

    return paths


def setkey(intval, n):
    return tuple(bool(intval & (2 ** i)) for i in range(n))


def keyrange(n):
    if n < 0:
        raise ValueError()

    for i in range(2 ** n):
        yield setkey(i, n)


def venn_intersections(paths):
    n = len(paths)
    return {key: venn_intersection(paths, key) for key in keyrange(n)}


def venn_intersection(paths, key):
    if not any(key):
        return QPainterPath()

    # first take the intersection of all included paths
    path = reduce(QPainterPath.intersected,
                  (path for path, included in zip(paths, key) if included))

    # subtract all the excluded sets (i.e. take the intersection
    # with the excluded set complements)
    path = reduce(QPainterPath.subtracted,
                  (path for path, included in zip(paths, key) if not included),
                  path)

    return path


def append_column(data, where, variable, column):
    X, Y, M = data.X, data.Y, data.metas
    domain = data.domain
    attr = domain.attributes
    class_vars = domain.class_vars
    metas = domain.metas
    if where == "X":
        attr = attr + (variable,)
        X = numpy.hstack((X, column))
    elif where == "Y":
        class_vars = class_vars + (variable,)
        Y = numpy.hstack((Y, column))
    elif where == "M":
        metas = metas + (variable,)
        M = numpy.hstack((M, column))
    else:
        raise ValueError
    domain = Orange.data.Domain(attr, class_vars, metas)
    new_data = data.transform(domain)
    new_data[:, variable] = column
    return new_data


def drop_columns(data, columns):
    columns = set(data.domain[col] for col in columns)

    def filter_vars(vars):
        return tuple(var for var in vars if var not in columns)

    domain = Orange.data.Domain(
        filter_vars(data.domain.attributes),
        filter_vars(data.domain.class_vars),
        filter_vars(data.domain.metas)
    )
    return Orange.data.Table.from_table(domain, data)


def group_table_indices(table, key_var):
    """
    Group table indices based on values of selected columns (`key_vars`).

    Return a dictionary mapping all unique value combinations (keys)
    into a list of indices in the table where they are present.
    """
    groups = defaultdict(list)
    for i, inst in enumerate(table):
        groups[str(inst[key_var])].append(i)
    return groups


if __name__ == "__main__":  # pragma: no cover
    from Orange.evaluation import ShuffleSplit

    data = Orange.data.Table("brown-selected")
    # data1 = Orange.data.Table("brown-selected")
    # datasets = [(data, 1), (data, 2)]

    data = append_column(data, "M", Orange.data.StringVariable("Test"),
                         numpy.arange(len(data)).reshape(-1, 1) % 30)
    res = ShuffleSplit(data, [None], n_resamples=5,
                       test_size=0.7, stratified=False)
    indices = iter(res.indices)
    datasets = []
    for i in range(1, 6):
        sample, _ = next(indices)
        data1 = data[sample]
        data1.name = chr(ord("A") + i)
        datasets.append((data1, i))

    WidgetPreview(OWVennDiagram).run(setData=datasets)

```
--- 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