{
    "file_diffs": [
        {
            "old_file_content": "from collections import defaultdict\nfrom functools import reduce\nfrom itertools import product, chain, repeat\nfrom math import sqrt, log\nfrom operator import mul, attrgetter\n\nimport numpy as np\nfrom scipy.stats import distributions\nfrom scipy.special import comb\nfrom AnyQt.QtCore import Qt, QSize, pyqtSignal as Signal\nfrom AnyQt.QtGui import QColor, QPainter, QPen, QStandardItem\nfrom AnyQt.QtWidgets import (\n    QGraphicsScene, QGraphicsLineItem, QGraphicsItemGroup)\n\nfrom Orange.data import Table, filter, Variable, Domain\nfrom Orange.data.sql.table import SqlTable, LARGE_TABLE, DEFAULT_SAMPLE_TIME\nfrom Orange.preprocess import Discretize\nfrom Orange.preprocess.discretize import EqualFreq\nfrom Orange.preprocess.score import ReliefF\nfrom Orange.statistics.distribution import get_distribution, get_distributions\nfrom Orange.widgets import gui, settings\nfrom Orange.widgets.gui import OWComponent\nfrom Orange.widgets.settings import (\n    Setting, DomainContextHandler, ContextSetting, SettingProvider)\nfrom Orange.widgets.utils import to_html, get_variable_values_sorted\nfrom Orange.widgets.utils.annotated_data import (create_annotated_table,\n                                                 ANNOTATED_DATA_SIGNAL_NAME)\nfrom Orange.widgets.utils.itemmodels import DomainModel\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.visualize.utils import (\n    CanvasText, CanvasRectangle, ViewWithPress, VizRankDialog)\nfrom Orange.widgets.visualize.utils.plotutils import wrap_legend_items\nfrom Orange.widgets.widget import OWWidget, Msg, Input, Output\n\n\nclass MosaicVizRank(VizRankDialog, OWComponent):\n    \"\"\"VizRank dialog for Mosaic\"\"\"\n    captionTitle = \"Mosaic Ranking\"\n    max_attrs = Setting(3)\n\n    pairSelected = Signal(Variable, Variable, Variable, Variable)\n    _AttrRole = next(gui.OrangeUserRole)\n\n    def __init__(self, master):\n        \"\"\"Add the spin box for maximal number of attributes\"\"\"\n        VizRankDialog.__init__(self, master)\n        OWComponent.__init__(self, master)\n\n        box = gui.hBox(self)\n        self.max_attr_spin = gui.spin(\n            box, self, \"max_attrs\", 2, 4,\n            label=\"Limit the number of attributes to: \",\n            controlWidth=50, alignment=Qt.AlignRight,\n            callback=self.max_attr_changed)\n        gui.rubber(box)\n        self.layout().addWidget(self.button)\n        self.attr_ordering = None\n        self.marginal = {}\n        self.last_run_max_attr = None\n\n        self.master.attrs_changed_manually.connect(self.on_manual_change)\n\n    def sizeHint(self):\n        return QSize(400, 512)\n\n    def initialize(self):\n        \"\"\"Clear the ordering to trigger recomputation when needed\"\"\"\n        super().initialize()\n        self.attr_ordering = None\n\n    def initialize_keep_ordering(self):\n        \"\"\"Initialize triggered by change of coloring\"\"\"\n        super().initialize()\n\n    def before_running(self):\n        \"\"\"\n        Disable the spin for maximal number of attributes before running and\n        enable afterwards. Also, if the number of attributes is different than\n        in the last run, reset the saved state (if it was paused).\n        \"\"\"\n        if self.max_attrs != self.last_run_max_attr:\n            self.saved_state = None\n            self.saved_progress = 0\n        if self.saved_state is None:\n            self.scores = []\n            self.rank_model.clear()\n        self.compute_attr_order()\n        self.last_run_max_attr = self.max_attrs\n        self.max_attr_spin.setDisabled(True)\n\n    def stopped(self):\n        self.max_attr_spin.setDisabled(False)\n\n    def max_attr_changed(self):\n        \"\"\"\n        Change the button label when the maximal number of attributes changes.\n\n        The method does not reset anything so the user can still see the\n        results until actually restarting the search.\n        \"\"\"\n        if self.max_attrs != self.last_run_max_attr or self.saved_state is None:\n            self.button.setText(\"Start\")\n        else:\n            self.button.setText(\"Continue\")\n        self.button.setEnabled(self.check_preconditions())\n\n    def coloring_changed(self):\n        self.stop_and_reset(self.initialize_keep_ordering)\n\n    def check_preconditions(self):\n        \"\"\"Require at least one variable to allow ranking.\"\"\"\n        self.Information.add_message(\"no_attributes\", \"No variables to rank.\")\n        self.Information.no_attributes.clear()\n        data = self.master.discrete_data\n        if not super().check_preconditions() or data is None:\n            return False\n        if not data.domain.attributes:\n            self.Information.no_attributes()\n            return False\n        return True\n\n    def compute_attr_order(self):\n        \"\"\"\n        Order attributes by Relief if there is a target variable. In case of\n        ties or without target, order by name.\n\n        Add the class variable at the beginning when not coloring by class\n        distribution.\n\n        If `self.attrs` is not `None`, keep the ordering and just add or remove\n        the class as needed.\n        \"\"\"\n        data = self.master.discrete_data\n        class_var = data.domain.class_var\n        if not self.attr_ordering:\n            if class_var is None:\n                self.attr_ordering = sorted(data.domain, key=attrgetter(\"name\"))\n            else:\n                weights = ReliefF(n_iterations=100, k_nearest=10)(data)\n                attrs = sorted(zip(weights, data.domain.attributes),\n                               key=lambda x: (-x[0], x[1].name))\n                self.attr_ordering = [a for _, a in attrs]\n\n    def _compute_class_dists(self):\n        return self.master.variable_color is not None\n\n    def state_count(self):\n        \"\"\"\n        Return the number of combinations, starting with a single attribute\n        if Mosaic is colored by class distributions, and two if by Pearson\n        \"\"\"\n        n_attrs = len(self.master.discrete_data.domain.attributes)\n        min_attrs = 1 if self._compute_class_dists() else 2\n        max_attrs = min(n_attrs, self.max_attrs)\n        return sum(comb(n_attrs, k, exact=True)\n                   for k in range(min_attrs, max_attrs + 1))\n\n    def iterate_states(self, state):\n        \"\"\"\n        Iterate through all combinations of attributes as ordered by Relief,\n        starting with a single attribute if Mosaic is colored by class\n        distributions, and two if by Pearson.\n        \"\"\"\n        # If we put initialization of `self.attrs` to `initialize`,\n        # `score_heuristic` would be run on every call to master's `set_data`.\n        master = self.master\n        data = master.discrete_data\n        if state is None:  # on the first call, compute order\n            if self._compute_class_dists():\n                self.marginal = get_distribution(data, data.domain.class_var)\n                self.marginal.normalize()\n                state = [0]\n            else:\n                self.marginal = get_distributions(data)\n                for dist in self.marginal:\n                    dist.normalize()\n                state = [0, 1]\n        n_attrs = len(data.domain.attributes)\n        while True:\n            yield state\n            # Reset while running; just abort\n            if self.attr_ordering is None:\n                break\n            for up, _ in enumerate(state):\n                state[up] += 1\n                if up + 1 == len(state) or state[up] < state[up + 1]:\n                    break\n                state[up] = up\n            if state[-1] == len(self.attr_ordering):\n                if len(state) < min(self.max_attrs, n_attrs):\n                    state = list(range(len(state) + 1))\n                else:\n                    break\n\n    def compute_score(self, state):\n        \"\"\"\n        Compute score using chi-square test of independence.\n\n        If mosaic colors by class distribution, chi-square is computed by\n        comparing the expected (prior) and observed class distribution in\n        each cell. Otherwise, compute the independence of the shown attributes.\n        \"\"\"\n        master = self.master\n        data = master.discrete_data\n        domain = data.domain\n        attrlist = [self.attr_ordering[i] for i in state]\n        cond_dist = get_conditional_distribution(data, attrlist)[0]\n        n = cond_dist[\"\"]\n        ss = 0\n        if self._compute_class_dists():\n            class_values = domain.class_var.values\n        else:\n            class_values = None\n            attr_indices = [domain.index(attr) for attr in attrlist]\n        for indices in product(*(range(len(a.values)) for a in attrlist)):\n            attr_vals = \"-\".join(attr.values[ind]\n                                 for attr, ind in zip(attrlist, indices))\n            total = cond_dist[attr_vals]\n            if class_values:  # showing class distributions\n                for i, class_val in enumerate(class_values):\n                    expected = total * self.marginal[i]\n                    if expected > 1e-6:\n                        observed = cond_dist[attr_vals + '-' + class_val]\n                        ss += (expected - observed) ** 2 / expected\n            else:\n                observed = cond_dist[attr_vals]\n                expected = n * reduce(\n                    mul,\n                    (self.marginal[attr_idx][ind]\n                     for attr_idx, ind in zip(attr_indices, indices)))\n                if expected > 1e-6:\n                    ss += (expected - observed) ** 2 / expected\n        if class_values:\n            dof = (len(class_values) - 1) * \\\n                  reduce(mul, (len(attr.values) for attr in attrlist))\n        else:\n            dof = reduce(mul, (len(attr.values) - 1 for attr in attrlist))\n        return distributions.chi2.sf(ss, dof)\n\n    def bar_length(self, score):\n        return 1 if score == 0 else -log(score, 10) / 50\n\n    def on_selection_changed(self, selected, deselected):\n        if not selected.isEmpty():\n            attrs = selected.indexes()[0].data(self._AttrRole)\n            self.selectionChanged.emit(attrs + (None, ) * (4 - len(attrs)))\n\n    def on_manual_change(self, attrs):\n        model = self.rank_model\n        self.rank_table.selectionModel().clear()\n        for row in range(model.rowCount()):\n            row_attrs = model.data(model.index(row, 0), self._AttrRole)\n            if row_attrs == tuple(attrs):\n                self.rank_table.selectRow(row)\n                return\n\n    def row_for_state(self, score, state):\n        \"\"\"The row consists of attributes sorted by name; class is at the\n        beginning, if present, so it's on the x-axis and not lost somewhere.\"\"\"\n        class_var = self.master.color_data.domain.class_var\n        attrs = tuple(\n            sorted((self.attr_ordering[x] for x in state),\n                   key=lambda attr: (1 - (attr is class_var), attr.name)))\n        item = QStandardItem(\", \".join(a.name for a in attrs))\n        item.setData(attrs, self._AttrRole)\n        return [item]\n\n\nclass OWMosaicDisplay(OWWidget):\n    name = \"Mosaic Display\"\n    description = \"Display data in a mosaic plot.\"\n    icon = \"icons/MosaicDisplay.svg\"\n    priority = 220\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Table, default=True)\n        data_subset = Input(\"Data Subset\", Table)\n\n    class Outputs:\n        selected_data = Output(\"Selected Data\", Table, default=True)\n        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)\n\n    settingsHandler = DomainContextHandler()\n    vizrank = SettingProvider(MosaicVizRank)\n    settings_version = 2\n    use_boxes = Setting(True)\n    variable1 = ContextSetting(None)\n    variable2 = ContextSetting(None)\n    variable3 = ContextSetting(None)\n    variable4 = ContextSetting(None)\n    variable_color = ContextSetting(None)\n    selection = ContextSetting(set())\n\n    BAR_WIDTH = 5\n    SPACING = 4\n    ATTR_NAME_OFFSET = 20\n    ATTR_VAL_OFFSET = 3\n    BLUE_COLORS = [QColor(255, 255, 255), QColor(210, 210, 255),\n                   QColor(110, 110, 255), QColor(0, 0, 255)]\n    RED_COLORS = [QColor(255, 255, 255), QColor(255, 200, 200),\n                  QColor(255, 100, 100), QColor(255, 0, 0)]\n    graph_name = \"canvas\"\n\n    attrs_changed_manually = Signal(list)\n\n    class Warning(OWWidget.Warning):\n        incompatible_subset = Msg(\"Data subset is incompatible with Data\")\n        no_valid_data = Msg(\"No valid data\")\n        no_cont_selection_sql = \\\n            Msg(\"Selection of numeric features on SQL is not supported\")\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self.discrete_data = None\n        self.subset_data = None\n        self.subset_indices = None\n\n        self.color_data = None\n\n        self.areas = []\n\n        self.canvas = QGraphicsScene()\n        self.canvas_view = ViewWithPress(\n            self.canvas, handler=self.clear_selection)\n        self.mainArea.layout().addWidget(self.canvas_view)\n        self.canvas_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setRenderHint(QPainter.Antialiasing)\n\n        box = gui.vBox(self.controlArea, box=True)\n        self.model_1 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE)\n        self.model_234 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(None)\")\n        self.attr_combos = [\n            gui.comboBox(\n                box, self, value=\"variable{}\".format(i),\n                orientation=Qt.Horizontal, contentsLength=12,\n                callback=self.attr_changed,\n                model=self.model_1 if i == 1 else self.model_234)\n            for i in range(1, 5)]\n        self.vizrank, self.vizrank_button = MosaicVizRank.add_vizrank(\n            box, self, \"Find Informative Mosaics\", self.set_attr)\n\n        box2 = gui.vBox(self.controlArea, box=\"Interior Coloring\")\n        self.color_model = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(Pearson residuals)\")\n        self.cb_attr_color = gui.comboBox(\n            box2, self, value=\"variable_color\",\n            orientation=Qt.Horizontal, contentsLength=12, labelWidth=50,\n            callback=self.set_color_data, model=self.color_model)\n        self.bar_button = gui.checkBox(\n            box2, self, 'use_boxes', label='Compare with total',\n            callback=self.update_graph)\n        gui.rubber(self.controlArea)\n\n    def sizeHint(self):\n        return QSize(720, 530)\n\n    def _get_discrete_data(self, data):\n        \"\"\"\n        Discretize continuous attributes.\n        Return None when there is no data, no rows, or no primitive attributes.\n        \"\"\"\n        if (data is None or\n                not len(data) or\n                not any(attr.is_discrete or attr.is_continuous\n                        for attr in chain(data.domain.variables,\n                                          data.domain.metas))):\n            return None\n        elif any(attr.is_continuous for attr in data.domain.variables):\n            return Discretize(\n                method=EqualFreq(n=4), remove_const=False, discretize_classes=True,\n                discretize_metas=True)(data)\n        else:\n            return data\n\n    def init_combos(self, data):\n        def set_combos(value):\n            self.model_1.set_domain(value)\n            self.model_234.set_domain(value)\n            self.color_model.set_domain(value)\n\n        if data is None:\n            set_combos(None)\n            self.variable1 = self.variable2 = self.variable3 \\\n                = self.variable4 = self.variable_color = None\n            return\n        set_combos(self.data.domain)\n\n        if len(self.model_1) > 0:\n            self.variable1 = self.model_1[0]\n            self.variable2 = self.model_1[min(1, len(self.model_1) - 1)]\n        self.variable3 = self.variable4 = None\n        self.variable_color = self.data.domain.class_var  # None is OK, too\n\n    def get_disc_attr_list(self):\n        return [self.discrete_data.domain[var.name]\n                for var in (self.variable1, self.variable2,\n                            self.variable3, self.variable4)\n                if var]\n\n    def set_attr(self, *attrs):\n        self.variable1, self.variable2, self.variable3, self.variable4 = [\n            attr and self.data.domain[attr.name] for attr in attrs]\n        self.reset_graph()\n\n    def attr_changed(self):\n        self.attrs_changed_manually.emit(self.get_disc_attr_list())\n        self.reset_graph()\n\n    def resizeEvent(self, e):\n        OWWidget.resizeEvent(self, e)\n        self.update_graph()\n\n    def showEvent(self, ev):\n        OWWidget.showEvent(self, ev)\n        self.update_graph()\n\n    @Inputs.data\n    def set_data(self, data):\n        if isinstance(data, SqlTable) and data.approx_len() > LARGE_TABLE:\n            data = data.sample_time(DEFAULT_SAMPLE_TIME)\n\n        self.closeContext()\n        self.data = data\n\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(\n            self.data is not None and len(self.data) > 1\n            and len(self.data.domain.attributes) >= 1)\n\n        if self.data is None:\n            self.discrete_data = None\n            self.init_combos(None)\n            return\n\n        self.init_combos(self.data)\n        self.openContext(self.data)\n\n    @Inputs.data_subset\n    def set_subset_data(self, data):\n        self.subset_data = data\n\n    # this is called by widget after setData and setSubsetData are called.\n    # this way the graph is updated only once\n    def handleNewSignals(self):\n        self.Warning.incompatible_subset.clear()\n        self.subset_indices = None\n        if self.data is not None and self.subset_data:\n            transformed = self.subset_data.transform(self.data.domain)\n            if np.all(np.isnan(transformed.X)) \\\n                    and np.all(np.isnan(transformed.Y)):\n                self.Warning.incompatible_subset()\n            else:\n                indices = {e.id for e in transformed}\n                self.subset_indices = [ex.id in indices for ex in self.data]\n        self.set_color_data()\n        self.reset_graph()\n\n    def clear_selection(self):\n        self.selection = set()\n        self.update_selection_rects()\n        self.send_selection()\n\n    def coloring_changed(self):\n        self.vizrank.coloring_changed()\n        self.update_graph()\n\n    def reset_graph(self):\n        self.clear_selection()\n        self.update_graph()\n\n    def set_color_data(self):\n        if self.data is None:\n            return\n        self.bar_button.setEnabled(self.variable_color is not None)\n        attrs = [v for v in self.model_1 if v and v is not self.variable_color]\n        domain = Domain(attrs, self.variable_color, None)\n        self.color_data = self.data.from_table(domain, self.data)\n        self.discrete_data = self._get_discrete_data(self.color_data)\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(True)\n        self.coloring_changed()\n\n    def update_selection_rects(self):\n        pens = (QPen(), QPen(Qt.black, 3, Qt.DotLine))\n        for i, (_, _, area) in enumerate(self.areas):\n            area.setPen(pens[i in self.selection])\n\n    def select_area(self, index, ev):\n        if ev.button() != Qt.LeftButton:\n            return\n        if ev.modifiers() & Qt.ControlModifier:\n            self.selection ^= {index}\n        else:\n            self.selection = {index}\n        self.update_selection_rects()\n        self.send_selection()\n\n    def send_selection(self):\n        if not self.selection or self.data is None:\n            self.Outputs.selected_data.send(None)\n            self.Outputs.annotated_data.send(\n                create_annotated_table(self.data, []))\n            return\n        filters = []\n        self.Warning.no_cont_selection_sql.clear()\n        if self.discrete_data is not self.data:\n            if isinstance(self.data, SqlTable):\n                self.Warning.no_cont_selection_sql()\n        for i in self.selection:\n            cols, vals, _ = self.areas[i]\n            filters.append(\n                filter.Values(\n                    filter.FilterDiscrete(col, [val])\n                    for col, val in zip(cols, vals)))\n        if len(filters) > 1:\n            filters = filter.Values(filters, conjunction=False)\n        else:\n            filters = filters[0]\n        selection = filters(self.discrete_data)\n        idset = set(selection.ids)\n        sel_idx = [i for i, id in enumerate(self.data.ids) if id in idset]\n        if self.discrete_data is not self.data:\n            selection = self.data[sel_idx]\n        self.Outputs.selected_data.send(selection)\n        self.Outputs.annotated_data.send(\n            create_annotated_table(self.data, sel_idx))\n\n    def send_report(self):\n        self.report_plot(self.canvas)\n\n    def update_graph(self):\n        spacing = self.SPACING\n        bar_width = self.BAR_WIDTH\n\n        def get_counts(attr_vals, values):\n            \"\"\"Calculate rectangles' widths; if all are 0, they are set to 1.\"\"\"\n            if not attr_vals:\n                counts = [conditionaldict[val] for val in values]\n            else:\n                counts = [conditionaldict[attr_vals + \"-\" + val]\n                          for val in values]\n            total = sum(counts)\n            if total == 0:\n                counts = [1] * len(values)\n                total = sum(counts)\n            return total, counts\n\n        def draw_data(attr_list, x0_x1, y0_y1, side, condition,\n                      total_attrs, used_attrs, used_vals, attr_vals=\"\"):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if conditionaldict[attr_vals] == 0:\n                add_rect(x0, x1, y0, y1, \"\",\n                         used_attrs, used_vals, attr_vals=attr_vals)\n                # store coordinates for later drawing of labels\n                draw_text(side, attr_list[0], (x0, x1), (y0, y1), total_attrs,\n                          used_attrs, used_vals, attr_vals)\n                return\n\n            attr = attr_list[0]\n            # how much smaller rectangles do we draw\n            edge = len(attr_list) * spacing\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]  # reverse names if necessary\n\n            if side % 2 == 0:  # we are drawing on the x axis\n                # remove the space needed for separating different attr. values\n                whole = max(0, (x1 - x0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (x1 - x0) / float(len(values) - 1)\n            else:  # we are drawing on the y axis\n                whole = max(0, (y1 - y0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (y1 - y0) / float(len(values) - 1)\n\n            total, counts = get_counts(attr_vals, values)\n\n            # when visualizing the third attribute and the first attribute has\n            # the last value, reverse the order in which the boxes are drawn;\n            # otherwise, if the last cell, nearest to the labels of the fourth\n            # attribute, is empty, we wouldn't be able to position the labels\n            valrange = list(range(len(values)))\n            if len(attr_list + used_attrs) == 4 and len(used_attrs) == 2:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] == attr1values[-1]:\n                    valrange = valrange[::-1]\n\n            for i in valrange:\n                start = i * edge + whole * float(sum(counts[:i]) / total)\n                end = i * edge + whole * float(sum(counts[:i + 1]) / total)\n                val = values[i]\n                htmlval = to_html(val)\n                newattrvals = attr_vals + \"-\" + val if attr_vals else val\n\n                tooltip = \"{}&nbsp;&nbsp;&nbsp;&nbsp;{}: <b>{}</b><br/>\".format(\n                    condition, attr.name, htmlval)\n                attrs = used_attrs + [attr]\n                vals = used_vals + [val]\n                args = attrs, vals, newattrvals\n                if side % 2 == 0:  # if we are moving horizontally\n                    if len(attr_list) == 1:\n                        add_rect(x0 + start, x0 + end, y0, y1, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0 + start, x0 + end), (y0, y1),\n                            side + 1, tooltip, total_attrs, *args)\n                else:\n                    if len(attr_list) == 1:\n                        add_rect(x0, x1, y0 + start, y0 + end, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0, x1), (y0 + start, y0 + end),\n                            side + 1, tooltip, total_attrs, *args)\n            draw_text(side, attr_list[0], (x0, x1), (y0, y1),\n                      total_attrs, used_attrs, used_vals, attr_vals)\n\n        def draw_text(side, attr, x0_x1, y0_y1,\n                      total_attrs, used_attrs, used_vals, attr_vals):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if side in drawn_sides:\n                return\n\n            # the text on the right will be drawn when we are processing\n            # visualization of the last value of the first attribute\n            if side == 3:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] != attr1values[-1]:\n                    return\n\n            if not conditionaldict[attr_vals]:\n                if side not in draw_positions:\n                    draw_positions[side] = (x0, x1, y0, y1)\n                return\n            else:\n                if side in draw_positions:\n                    # restore the positions of attribute values and name\n                    (x0, x1, y0, y1) = draw_positions[side]\n\n            drawn_sides.add(side)\n\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]\n\n            spaces = spacing * (total_attrs - side) * (len(values) - 1)\n            width = x1 - x0 - spaces * (side % 2 == 0)\n            height = y1 - y0 - spaces * (side % 2 == 1)\n\n            # calculate position of first attribute\n            currpos = 0\n            total, counts = get_counts(attr_vals, values)\n            aligns = [Qt.AlignTop | Qt.AlignHCenter,\n                      Qt.AlignRight | Qt.AlignVCenter,\n                      Qt.AlignBottom | Qt.AlignHCenter,\n                      Qt.AlignLeft | Qt.AlignVCenter]\n            align = aligns[side]\n            for i, val in enumerate(values):\n                if distributiondict[val] != 0:\n                    perc = counts[i] / float(total)\n                    rwidth = width * perc\n                    xs = [x0 + currpos + rwidth / 2,\n                          x0 - self.ATTR_VAL_OFFSET,\n                          x0 + currpos + rwidth / 2,\n                          x1 + self.ATTR_VAL_OFFSET]\n                    ys = [y1 + self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc,\n                          y0 - self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc]\n\n                    CanvasText(\n                        self.canvas, val, xs[side], ys[side], align,\n                        max_width=rwidth if side == 0 else None)\n                    space = height if side % 2 else width\n                    currpos += perc * space + spacing * (total_attrs - side)\n\n            xs = [x0 + (x1 - x0) / 2,\n                  x0 - max_ylabel_w1 - self.ATTR_VAL_OFFSET,\n                  x0 + (x1 - x0) / 2,\n                  x1 + max_ylabel_w2 + self.ATTR_VAL_OFFSET]\n            ys = [y1 + self.ATTR_VAL_OFFSET + self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2,\n                  y0 - self.ATTR_VAL_OFFSET - self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2]\n            CanvasText(\n                self.canvas, attr.name, xs[side], ys[side], align, bold=True,\n                vertical=side % 2)\n\n        def add_rect(x0, x1, y0, y1, condition,\n                     used_attrs, used_vals, attr_vals=\"\"):\n            area_index = len(self.areas)\n            x1 += (x0 == x1)\n            y1 += (y0 == y1)\n            # rectangles of width and height 1 are not shown - increase\n            y1 += (x1 - x0 + y1 - y0 == 2)\n            colors = class_var and [QColor(*col) for col in class_var.colors]\n\n            def select_area(_, ev):\n                self.select_area(area_index, ev)\n\n            def rect(x, y, w, h, z, pen_color=None, brush_color=None, **args):\n                if pen_color is None:\n                    return CanvasRectangle(\n                        self.canvas, x, y, w, h, z=z, onclick=select_area,\n                        **args)\n                if brush_color is None:\n                    brush_color = pen_color\n                return CanvasRectangle(\n                    self.canvas, x, y, w, h, pen_color, brush_color, z=z,\n                    onclick=select_area, **args)\n\n            def line(x1, y1, x2, y2):\n                r = QGraphicsLineItem(x1, y1, x2, y2, None)\n                self.canvas.addItem(r)\n                r.setPen(QPen(Qt.white, 2))\n                r.setZValue(30)\n\n            outer_rect = rect(x0, y0, x1 - x0, y1 - y0, 30)\n            self.areas.append((used_attrs, used_vals, outer_rect))\n            if not conditionaldict[attr_vals]:\n                return\n\n            if self.variable_color is None:\n                s = sum(apriori_dists[0])\n                expected = s * reduce(\n                    mul,\n                    (apriori_dists[i][used_vals[i]] / float(s)\n                     for i in range(len(used_vals))))\n                actual = conditionaldict[attr_vals]\n                pearson = float((actual - expected) / sqrt(expected))\n                if pearson == 0:\n                    ind = 0\n                else:\n                    ind = max(0, min(int(log(abs(pearson), 2)), 3))\n                color = [self.RED_COLORS, self.BLUE_COLORS][pearson > 0][ind]\n                rect(x0, y0, x1 - x0, y1 - y0, -20, color)\n                outer_rect.setToolTip(\n                    condition + \"<hr/>\" +\n                    \"Expected instances: %.1f<br>\"\n                    \"Actual instances: %d<br>\"\n                    \"Standardized (Pearson) residual: %.1f\" %\n                    (expected, conditionaldict[attr_vals], pearson))\n            else:\n                cls_values = get_variable_values_sorted(class_var)\n                prior = get_distribution(data, class_var.name)\n                total = 0\n                for i, value in enumerate(cls_values):\n                    val = conditionaldict[attr_vals + \"-\" + value]\n                    if val == 0:\n                        continue\n                    if i == len(cls_values) - 1:\n                        v = y1 - y0 - total\n                    else:\n                        v = ((y1 - y0) * val) / conditionaldict[attr_vals]\n                    rect(x0, y0 + total, x1 - x0, v, -20, colors[i])\n                    total += v\n\n                if self.use_boxes and \\\n                        abs(x1 - x0) > bar_width and abs(y1 - y0) > bar_width:\n                    total = 0\n                    line(x0 + bar_width, y0, x0 + bar_width, y1)\n                    n = sum(prior)\n                    for i, (val, color) in enumerate(zip(prior, colors)):\n                        if i == len(prior) - 1:\n                            h = y1 - y0 - total\n                        else:\n                            h = (y1 - y0) * val / n\n                        rect(x0, y0 + total, bar_width, h, 20, color)\n                        total += h\n\n                if conditionalsubsetdict:\n                    if conditionalsubsetdict[attr_vals]:\n                        if self.subset_indices is not None:\n                            line(x1 - bar_width, y0, x1 - bar_width, y1)\n                            total = 0\n                            n = conditionalsubsetdict[attr_vals]\n                            if n:\n                                for i, (cls, color) in \\\n                                        enumerate(zip(cls_values, colors)):\n                                    val = conditionalsubsetdict[\n                                        attr_vals + \"-\" + cls]\n                                    if val == 0:\n                                        continue\n                                    if i == len(prior) - 1:\n                                        v = y1 - y0 - total\n                                    else:\n                                        v = ((y1 - y0) * val) / n\n                                    rect(x1 - bar_width, y0 + total,\n                                         bar_width, v, 15, color)\n                                    total += v\n\n                actual = [conditionaldict[attr_vals + \"-\" + cls_values[i]]\n                          for i in range(len(prior))]\n                n_actual = sum(actual)\n                if n_actual > 0:\n                    apriori = [prior[key] for key in cls_values]\n                    n_apriori = sum(apriori)\n                    text = \"<br/>\".join(\n                        \"<b>%s</b>: %d / %.1f%% (Expected %.1f / %.1f%%)\" %\n                        (cls, act, 100.0 * act / n_actual,\n                         apr / n_apriori * n_actual, 100.0 * apr / n_apriori)\n                        for cls, act, apr in zip(cls_values, actual, apriori))\n                else:\n                    text = \"\"\n                outer_rect.setToolTip(\n                    \"{}<hr>Instances: {}<br><br>{}\".format(\n                        condition, n_actual, text[:-4]))\n\n        def create_legend():\n            if self.variable_color is None:\n                names = [\"<-8\", \"-8:-4\", \"-4:-2\", \"-2:2\", \"2:4\", \"4:8\", \">8\",\n                         \"Residuals:\"]\n                colors = self.RED_COLORS[::-1] + self.BLUE_COLORS[1:]\n                edges = repeat(Qt.black)\n            else:\n                names = get_variable_values_sorted(class_var)\n                edges = colors = [QColor(*col) for col in class_var.colors]\n\n            items = []\n            size = 8\n            for name, color, edgecolor in zip(names, colors, edges):\n                item = QGraphicsItemGroup()\n                item.addToGroup(\n                    CanvasRectangle(None, -size / 2, -size / 2, size, size,\n                                    edgecolor, color))\n                item.addToGroup(\n                    CanvasText(None, name, size, 0, Qt.AlignVCenter))\n                items.append(item)\n            return wrap_legend_items(\n                items, hspacing=20, vspacing=16 + size,\n                max_width=self.canvas_view.width() - xoff)\n\n        self.canvas.clear()\n        self.areas = []\n\n        data = self.discrete_data\n        if data is None:\n            return\n        attr_list = self.get_disc_attr_list()\n        class_var = data.domain.class_var\n        if class_var:\n            sql = isinstance(data, SqlTable)\n            name = not sql and data.name\n            # save class_var because it is removed in the next line\n            data = data[:, attr_list + [class_var]]\n            data.domain.class_var = class_var\n            if not sql:\n                data.name = name\n        else:\n            data = data[:, attr_list]\n        # TODO: check this\n        # data = Preprocessor_dropMissing(data)\n        if len(data) == 0:\n            self.Warning.no_valid_data()\n            return\n        else:\n            self.Warning.no_valid_data.clear()\n\n        attrs = [attr for attr in attr_list if not attr.values]\n        if attrs:\n            CanvasText(self.canvas,\n                       \"Feature {} has no values\".format(attrs[0]),\n                       (self.canvas_view.width() - 120) / 2,\n                       self.canvas_view.height() / 2)\n            return\n        if self.variable_color is None:\n            apriori_dists = [get_distribution(data, attr) for attr in attr_list]\n        else:\n            apriori_dists = []\n\n        def get_max_label_width(attr):\n            values = get_variable_values_sorted(attr)\n            maxw = 0\n            for val in values:\n                t = CanvasText(self.canvas, val, 0, 0, bold=0, show=False)\n                maxw = max(int(t.boundingRect().width()), maxw)\n            return maxw\n\n        xoff = 20\n\n        # get the maximum width of rectangle\n        width = 20\n        max_ylabel_w1 = max_ylabel_w2 = 0\n        if len(attr_list) > 1:\n            text = CanvasText(self.canvas, attr_list[1].name, bold=1, show=0)\n            max_ylabel_w1 = min(get_max_label_width(attr_list[1]), 150)\n            width = 5 + text.boundingRect().height() + \\\n                self.ATTR_VAL_OFFSET + max_ylabel_w1\n            xoff = width\n            if len(attr_list) == 4:\n                text = CanvasText(self.canvas, attr_list[3].name, bold=1, show=0)\n                max_ylabel_w2 = min(get_max_label_width(attr_list[3]), 150)\n                width += text.boundingRect().height() + \\\n                    self.ATTR_VAL_OFFSET + max_ylabel_w2 - 10\n\n        legend = create_legend()\n\n        # get the maximum height of rectangle\n        yoff = 45\n        legendoff = yoff + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35\n        square_size = min(self.canvas_view.width() - width - 20,\n                          self.canvas_view.height() - legendoff\n                          - legend.boundingRect().height())\n\n        if square_size < 0:\n            return  # canvas is too small to draw rectangles\n        self.canvas_view.setSceneRect(\n            0, 0, self.canvas_view.width(), self.canvas_view.height())\n\n        drawn_sides = set()\n        draw_positions = {}\n\n        conditionaldict, distributiondict = \\\n            get_conditional_distribution(data, attr_list)\n        conditionalsubsetdict = None\n        if self.subset_indices:\n            conditionalsubsetdict, _ = get_conditional_distribution(\n                self.discrete_data[self.subset_indices], attr_list)\n\n        # draw rectangles\n        draw_data(\n            attr_list, (xoff, xoff + square_size), (yoff, yoff + square_size),\n            0, \"\", len(attr_list), [], [])\n\n        self.canvas.addItem(legend)\n        legend.setPos(\n            xoff - legend.boundingRect().x()\n            + max(0, (square_size - legend.boundingRect().width()) / 2),\n            legendoff + square_size)\n        self.update_selection_rects()\n\n    @classmethod\n    def migrate_context(cls, context, version):\n        if version < 2:\n            settings.migrate_str_to_variable(context, none_placeholder=\"(None)\")\n\n\ndef get_conditional_distribution(data, attrs):\n    cond_dist = defaultdict(int)\n    dist = defaultdict(int)\n    cond_dist[\"\"] = dist[\"\"] = len(data)\n    all_attrs = attrs[:]\n    if data.domain.class_var is not None:\n        all_attrs.append(data.domain.class_var)\n\n    for i in range(1, len(all_attrs) + 1):\n        attr = all_attrs[:i]\n        if isinstance(data, SqlTable):\n            # make all possible pairs of attributes + class_var\n            attr = [a.to_sql() for a in attr]\n            fields = attr + [\"COUNT(*)\"]\n            query = data._sql_query(fields, group_by=attr)\n            with data._execute_sql_query(query) as cur:\n                res = cur.fetchall()\n            for r in res:\n                str_values = [a.repr_val(a.to_val(x))\n                              for a, x in zip(all_attrs, r[:-1])]\n                str_values = [x if x != '?' else 'None' for x in str_values]\n                cond_dist['-'.join(str_values)] = r[-1]\n                dist[str_values[-1]] += r[-1]\n        else:\n            for indices in product(*(range(len(a.values)) for a in attr)):\n                vals = []\n                conditions = []\n                for k, ind in enumerate(indices):\n                    vals.append(attr[k].values[ind])\n                    fd = filter.FilterDiscrete(\n                        column=attr[k], values=[attr[k].values[ind]])\n                    conditions.append(fd)\n                filt = filter.Values(conditions)\n                filtdata = filt(data)\n                cond_dist['-'.join(vals)] = len(filtdata)\n                dist[vals[-1]] += len(filtdata)\n    return cond_dist, dist\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    data = Table(\"zoo\")\n    WidgetPreview(OWMosaicDisplay).run(data, set_subset_data=data[::10])\n",
            "new_file_content": "from collections import defaultdict\nfrom functools import reduce\nfrom itertools import product, chain, repeat\nfrom math import sqrt, log\nfrom operator import mul, attrgetter\n\nimport numpy as np\nfrom scipy.stats import distributions\nfrom scipy.special import comb\nfrom AnyQt.QtCore import Qt, QSize, pyqtSignal as Signal\nfrom AnyQt.QtGui import QColor, QPainter, QPen, QStandardItem\nfrom AnyQt.QtWidgets import (\n    QGraphicsScene, QGraphicsLineItem, QGraphicsItemGroup)\n\nfrom Orange.data import Table, filter, Variable, Domain\nfrom Orange.data.sql.table import SqlTable, LARGE_TABLE, DEFAULT_SAMPLE_TIME\nfrom Orange.preprocess import Discretize\nfrom Orange.preprocess.discretize import EqualFreq\nfrom Orange.preprocess.score import ReliefF\nfrom Orange.statistics.distribution import get_distribution, get_distributions\nfrom Orange.widgets import gui, settings\nfrom Orange.widgets.gui import OWComponent\nfrom Orange.widgets.settings import (\n    Setting, DomainContextHandler, ContextSetting, SettingProvider)\nfrom Orange.widgets.utils import to_html, get_variable_values_sorted\nfrom Orange.widgets.utils.annotated_data import (create_annotated_table,\n                                                 ANNOTATED_DATA_SIGNAL_NAME)\nfrom Orange.widgets.utils.itemmodels import DomainModel\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.visualize.utils import (\n    CanvasText, CanvasRectangle, ViewWithPress, VizRankDialog)\nfrom Orange.widgets.visualize.utils.plotutils import wrap_legend_items\nfrom Orange.widgets.widget import OWWidget, Msg, Input, Output\n\n\nclass MosaicVizRank(VizRankDialog, OWComponent):\n    \"\"\"VizRank dialog for Mosaic\"\"\"\n    captionTitle = \"Mosaic Ranking\"\n    max_attrs = Setting(3)\n\n    pairSelected = Signal(Variable, Variable, Variable, Variable)\n    _AttrRole = next(gui.OrangeUserRole)\n\n    def __init__(self, master):\n        \"\"\"Add the spin box for maximal number of attributes\"\"\"\n        VizRankDialog.__init__(self, master)\n        OWComponent.__init__(self, master)\n\n        box = gui.hBox(self)\n        self.max_attr_spin = gui.spin(\n            box, self, \"max_attrs\", 2, 4,\n            label=\"Limit the number of attributes to: \",\n            controlWidth=50, alignment=Qt.AlignRight,\n            callback=self.max_attr_changed)\n        gui.rubber(box)\n        self.layout().addWidget(self.button)\n        self.attr_ordering = None\n        self.marginal = {}\n        self.last_run_max_attr = None\n\n        self.master.attrs_changed_manually.connect(self.on_manual_change)\n\n    def sizeHint(self):\n        return QSize(400, 512)\n\n    def initialize(self):\n        \"\"\"Clear the ordering to trigger recomputation when needed\"\"\"\n        super().initialize()\n        self.attr_ordering = None\n\n    def initialize_keep_ordering(self):\n        \"\"\"Initialize triggered by change of coloring\"\"\"\n        super().initialize()\n\n    def before_running(self):\n        \"\"\"\n        Disable the spin for maximal number of attributes before running and\n        enable afterwards. Also, if the number of attributes is different than\n        in the last run, reset the saved state (if it was paused).\n        \"\"\"\n        if self.max_attrs != self.last_run_max_attr:\n            self.saved_state = None\n            self.saved_progress = 0\n        if self.saved_state is None:\n            self.scores = []\n            self.rank_model.clear()\n        self.compute_attr_order()\n        self.last_run_max_attr = self.max_attrs\n        self.max_attr_spin.setDisabled(True)\n\n    def stopped(self):\n        self.max_attr_spin.setDisabled(False)\n\n    def max_attr_changed(self):\n        \"\"\"\n        Change the button label when the maximal number of attributes changes.\n\n        The method does not reset anything so the user can still see the\n        results until actually restarting the search.\n        \"\"\"\n        if self.max_attrs != self.last_run_max_attr or self.saved_state is None:\n            self.button.setText(\"Start\")\n        else:\n            self.button.setText(\"Continue\")\n        self.button.setEnabled(self.check_preconditions())\n\n    def coloring_changed(self):\n        self.stop_and_reset(self.initialize_keep_ordering)\n\n    def check_preconditions(self):\n        \"\"\"Require at least one variable to allow ranking.\"\"\"\n        self.Information.add_message(\"no_attributes\", \"No variables to rank.\")\n        self.Information.no_attributes.clear()\n        data = self.master.discrete_data\n        if not super().check_preconditions() or data is None:\n            return False\n        if not data.domain.attributes:\n            self.Information.no_attributes()\n            return False\n        return True\n\n    def compute_attr_order(self):\n        \"\"\"\n        Order attributes by Relief if there is a target variable. In case of\n        ties or without target, order by name.\n\n        Add the class variable at the beginning when not coloring by class\n        distribution.\n\n        If `self.attrs` is not `None`, keep the ordering and just add or remove\n        the class as needed.\n        \"\"\"\n        data = self.master.discrete_data\n        class_var = data.domain.class_var\n        if not self.attr_ordering:\n            if class_var is None:\n                self.attr_ordering = sorted(data.domain, key=attrgetter(\"name\"))\n            else:\n                weights = ReliefF(n_iterations=100, k_nearest=10)(data)\n                attrs = sorted(zip(weights, data.domain.attributes),\n                               key=lambda x: (-x[0], x[1].name))\n                self.attr_ordering = [a for _, a in attrs]\n\n    def _compute_class_dists(self):\n        return self.master.variable_color is not None\n\n    def state_count(self):\n        \"\"\"\n        Return the number of combinations, starting with a single attribute\n        if Mosaic is colored by class distributions, and two if by Pearson\n        \"\"\"\n        n_attrs = len(self.master.discrete_data.domain.attributes)\n        min_attrs = 1 if self._compute_class_dists() else 2\n        max_attrs = min(n_attrs, self.max_attrs)\n        return sum(comb(n_attrs, k, exact=True)\n                   for k in range(min_attrs, max_attrs + 1))\n\n    def iterate_states(self, state):\n        \"\"\"\n        Iterate through all combinations of attributes as ordered by Relief,\n        starting with a single attribute if Mosaic is colored by class\n        distributions, and two if by Pearson.\n        \"\"\"\n        # If we put initialization of `self.attrs` to `initialize`,\n        # `score_heuristic` would be run on every call to master's `set_data`.\n        master = self.master\n        data = master.discrete_data\n        if state is None:  # on the first call, compute order\n            if self._compute_class_dists():\n                self.marginal = get_distribution(data, data.domain.class_var)\n                self.marginal.normalize()\n                state = [0]\n            else:\n                self.marginal = get_distributions(data)\n                for dist in self.marginal:\n                    dist.normalize()\n                state = [0, 1]\n        n_attrs = len(data.domain.attributes)\n        while True:\n            yield state\n            # Reset while running; just abort\n            if self.attr_ordering is None:\n                break\n            for up, _ in enumerate(state):\n                state[up] += 1\n                if up + 1 == len(state) or state[up] < state[up + 1]:\n                    break\n                state[up] = up\n            if state[-1] == len(self.attr_ordering):\n                if len(state) < min(self.max_attrs, n_attrs):\n                    state = list(range(len(state) + 1))\n                else:\n                    break\n\n    def compute_score(self, state):\n        \"\"\"\n        Compute score using chi-square test of independence.\n\n        If mosaic colors by class distribution, chi-square is computed by\n        comparing the expected (prior) and observed class distribution in\n        each cell. Otherwise, compute the independence of the shown attributes.\n        \"\"\"\n        master = self.master\n        data = master.discrete_data\n        domain = data.domain\n        attrlist = [self.attr_ordering[i] for i in state]\n        cond_dist = get_conditional_distribution(data, attrlist)[0]\n        n = cond_dist[\"\"]\n        ss = 0\n        if self._compute_class_dists():\n            class_values = domain.class_var.values\n        else:\n            class_values = None\n            attr_indices = [domain.index(attr) for attr in attrlist]\n        for indices in product(*(range(len(a.values)) for a in attrlist)):\n            attr_vals = \"-\".join(attr.values[ind]\n                                 for attr, ind in zip(attrlist, indices))\n            total = cond_dist[attr_vals]\n            if class_values:  # showing class distributions\n                for i, class_val in enumerate(class_values):\n                    expected = total * self.marginal[i]\n                    if expected > 1e-6:\n                        observed = cond_dist[attr_vals + '-' + class_val]\n                        ss += (expected - observed) ** 2 / expected\n            else:\n                observed = cond_dist[attr_vals]\n                expected = n * reduce(\n                    mul,\n                    (self.marginal[attr_idx][ind]\n                     for attr_idx, ind in zip(attr_indices, indices)))\n                if expected > 1e-6:\n                    ss += (expected - observed) ** 2 / expected\n        if class_values:\n            dof = (len(class_values) - 1) * \\\n                  reduce(mul, (len(attr.values) for attr in attrlist))\n        else:\n            dof = reduce(mul, (len(attr.values) - 1 for attr in attrlist))\n        return distributions.chi2.sf(ss, dof)\n\n    def bar_length(self, score):\n        return 1 if score == 0 else -log(score, 10) / 50\n\n    def on_selection_changed(self, selected, deselected):\n        if not selected.isEmpty():\n            attrs = selected.indexes()[0].data(self._AttrRole)\n            self.selectionChanged.emit(attrs + (None, ) * (4 - len(attrs)))\n\n    def on_manual_change(self, attrs):\n        model = self.rank_model\n        self.rank_table.selectionModel().clear()\n        for row in range(model.rowCount()):\n            row_attrs = model.data(model.index(row, 0), self._AttrRole)\n            if row_attrs == tuple(attrs):\n                self.rank_table.selectRow(row)\n                return\n\n    def row_for_state(self, score, state):\n        \"\"\"The row consists of attributes sorted by name; class is at the\n        beginning, if present, so it's on the x-axis and not lost somewhere.\"\"\"\n        class_var = self.master.color_data.domain.class_var\n        attrs = tuple(\n            sorted((self.attr_ordering[x] for x in state),\n                   key=lambda attr: (1 - (attr is class_var), attr.name)))\n        item = QStandardItem(\", \".join(a.name for a in attrs))\n        item.setData(attrs, self._AttrRole)\n        return [item]\n\n\nclass OWMosaicDisplay(OWWidget):\n    name = \"Mosaic Display\"\n    description = \"Display data in a mosaic plot.\"\n    icon = \"icons/MosaicDisplay.svg\"\n    priority = 220\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Table, default=True)\n        data_subset = Input(\"Data Subset\", Table)\n\n    class Outputs:\n        selected_data = Output(\"Selected Data\", Table, default=True)\n        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)\n\n    settingsHandler = DomainContextHandler()\n    vizrank = SettingProvider(MosaicVizRank)\n    settings_version = 2\n    use_boxes = Setting(True)\n    variable1 = ContextSetting(None)\n    variable2 = ContextSetting(None)\n    variable3 = ContextSetting(None)\n    variable4 = ContextSetting(None)\n    variable_color = ContextSetting(None)\n    selection = ContextSetting(set())\n\n    BAR_WIDTH = 5\n    SPACING = 4\n    ATTR_NAME_OFFSET = 20\n    ATTR_VAL_OFFSET = 3\n    BLUE_COLORS = [QColor(255, 255, 255), QColor(210, 210, 255),\n                   QColor(110, 110, 255), QColor(0, 0, 255)]\n    RED_COLORS = [QColor(255, 255, 255), QColor(255, 200, 200),\n                  QColor(255, 100, 100), QColor(255, 0, 0)]\n    graph_name = \"canvas\"\n\n    attrs_changed_manually = Signal(list)\n\n    class Warning(OWWidget.Warning):\n        incompatible_subset = Msg(\"Data subset is incompatible with Data\")\n        no_valid_data = Msg(\"No valid data\")\n        no_cont_selection_sql = \\\n            Msg(\"Selection of numeric features on SQL is not supported\")\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self.discrete_data = None\n        self.subset_data = None\n        self.subset_indices = None\n        self.__pending_selection = None\n\n        self.color_data = None\n\n        self.areas = []\n\n        self.canvas = QGraphicsScene()\n        self.canvas_view = ViewWithPress(\n            self.canvas, handler=self.clear_selection)\n        self.mainArea.layout().addWidget(self.canvas_view)\n        self.canvas_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setRenderHint(QPainter.Antialiasing)\n\n        box = gui.vBox(self.controlArea, box=True)\n        self.model_1 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE)\n        self.model_234 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(None)\")\n        self.attr_combos = [\n            gui.comboBox(\n                box, self, value=\"variable{}\".format(i),\n                orientation=Qt.Horizontal, contentsLength=12,\n                callback=self.attr_changed,\n                model=self.model_1 if i == 1 else self.model_234)\n            for i in range(1, 5)]\n        self.vizrank, self.vizrank_button = MosaicVizRank.add_vizrank(\n            box, self, \"Find Informative Mosaics\", self.set_attr)\n\n        box2 = gui.vBox(self.controlArea, box=\"Interior Coloring\")\n        self.color_model = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(Pearson residuals)\")\n        self.cb_attr_color = gui.comboBox(\n            box2, self, value=\"variable_color\",\n            orientation=Qt.Horizontal, contentsLength=12, labelWidth=50,\n            callback=self.set_color_data, model=self.color_model)\n        self.bar_button = gui.checkBox(\n            box2, self, 'use_boxes', label='Compare with total',\n            callback=self.update_graph)\n        gui.rubber(self.controlArea)\n\n    def sizeHint(self):\n        return QSize(720, 530)\n\n    def _get_discrete_data(self, data):\n        \"\"\"\n        Discretize continuous attributes.\n        Return None when there is no data, no rows, or no primitive attributes.\n        \"\"\"\n        if (data is None or\n                not len(data) or\n                not any(attr.is_discrete or attr.is_continuous\n                        for attr in chain(data.domain.variables,\n                                          data.domain.metas))):\n            return None\n        elif any(attr.is_continuous for attr in data.domain.variables):\n            return Discretize(\n                method=EqualFreq(n=4), remove_const=False, discretize_classes=True,\n                discretize_metas=True)(data)\n        else:\n            return data\n\n    def init_combos(self, data):\n        def set_combos(value):\n            self.model_1.set_domain(value)\n            self.model_234.set_domain(value)\n            self.color_model.set_domain(value)\n\n        if data is None:\n            set_combos(None)\n            self.variable1 = self.variable2 = self.variable3 \\\n                = self.variable4 = self.variable_color = None\n            return\n        set_combos(self.data.domain)\n\n        if len(self.model_1) > 0:\n            self.variable1 = self.model_1[0]\n            self.variable2 = self.model_1[min(1, len(self.model_1) - 1)]\n        self.variable3 = self.variable4 = None\n        self.variable_color = self.data.domain.class_var  # None is OK, too\n\n    def get_disc_attr_list(self):\n        return [self.discrete_data.domain[var.name]\n                for var in (self.variable1, self.variable2,\n                            self.variable3, self.variable4)\n                if var]\n\n    def set_attr(self, *attrs):\n        self.variable1, self.variable2, self.variable3, self.variable4 = [\n            attr and self.data.domain[attr.name] for attr in attrs]\n        self.reset_graph()\n\n    def attr_changed(self):\n        self.attrs_changed_manually.emit(self.get_disc_attr_list())\n        self.reset_graph()\n\n    def resizeEvent(self, e):\n        OWWidget.resizeEvent(self, e)\n        self.update_graph()\n\n    def showEvent(self, ev):\n        OWWidget.showEvent(self, ev)\n        self.update_graph()\n\n    @Inputs.data\n    def set_data(self, data):\n        if isinstance(data, SqlTable) and data.approx_len() > LARGE_TABLE:\n            data = data.sample_time(DEFAULT_SAMPLE_TIME)\n\n        self.closeContext()\n        self.data = data\n\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(\n            self.data is not None and len(self.data) > 1\n            and len(self.data.domain.attributes) >= 1)\n\n        if self.data is None:\n            self.discrete_data = None\n            self.init_combos(None)\n            return\n\n        self.init_combos(self.data)\n        self.openContext(self.data)\n        self.__pending_selection = self.selection\n\n    @Inputs.data_subset\n    def set_subset_data(self, data):\n        self.subset_data = data\n\n    # this is called by widget after setData and setSubsetData are called.\n    # this way the graph is updated only once\n    def handleNewSignals(self):\n        self.Warning.incompatible_subset.clear()\n        self.subset_indices = None\n        if self.data is not None and self.subset_data:\n            transformed = self.subset_data.transform(self.data.domain)\n            if np.all(np.isnan(transformed.X)) \\\n                    and np.all(np.isnan(transformed.Y)):\n                self.Warning.incompatible_subset()\n            else:\n                indices = {e.id for e in transformed}\n                self.subset_indices = [ex.id in indices for ex in self.data]\n        self.set_color_data()\n        self.reset_graph()\n\n    def clear_selection(self):\n        self.selection = set()\n        self.update_selection_rects()\n        self.send_selection()\n\n    def coloring_changed(self):\n        self.vizrank.coloring_changed()\n        self.update_graph()\n\n    def reset_graph(self):\n        self.clear_selection()\n        self.update_graph()\n        if self.__pending_selection is not None:\n            self.selection = self.__pending_selection\n            self.__pending_selection = None\n            self.update_selection_rects()\n            self.send_selection()\n\n    def set_color_data(self):\n        if self.data is None:\n            return\n        self.bar_button.setEnabled(self.variable_color is not None)\n        attrs = [v for v in self.model_1 if v and v is not self.variable_color]\n        domain = Domain(attrs, self.variable_color, None)\n        self.color_data = self.data.from_table(domain, self.data)\n        self.discrete_data = self._get_discrete_data(self.color_data)\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(True)\n        self.coloring_changed()\n\n    def update_selection_rects(self):\n        pens = (QPen(), QPen(Qt.black, 3, Qt.DotLine))\n        for i, (_, _, area) in enumerate(self.areas):\n            area.setPen(pens[i in self.selection])\n\n    def select_area(self, index, ev):\n        if ev.button() != Qt.LeftButton:\n            return\n        if ev.modifiers() & Qt.ControlModifier:\n            self.selection ^= {index}\n        else:\n            self.selection = {index}\n        self.update_selection_rects()\n        self.send_selection()\n\n    def send_selection(self):\n        if not self.selection or self.data is None:\n            self.Outputs.selected_data.send(None)\n            self.Outputs.annotated_data.send(\n                create_annotated_table(self.data, []))\n            return\n        filters = []\n        self.Warning.no_cont_selection_sql.clear()\n        if self.discrete_data is not self.data:\n            if isinstance(self.data, SqlTable):\n                self.Warning.no_cont_selection_sql()\n        for i in self.selection:\n            cols, vals, _ = self.areas[i]\n            filters.append(\n                filter.Values(\n                    filter.FilterDiscrete(col, [val])\n                    for col, val in zip(cols, vals)))\n        if len(filters) > 1:\n            filters = filter.Values(filters, conjunction=False)\n        else:\n            filters = filters[0]\n        selection = filters(self.discrete_data)\n        idset = set(selection.ids)\n        sel_idx = [i for i, id in enumerate(self.data.ids) if id in idset]\n        if self.discrete_data is not self.data:\n            selection = self.data[sel_idx]\n        self.Outputs.selected_data.send(selection)\n        self.Outputs.annotated_data.send(\n            create_annotated_table(self.data, sel_idx))\n\n    def send_report(self):\n        self.report_plot(self.canvas)\n\n    def update_graph(self):\n        spacing = self.SPACING\n        bar_width = self.BAR_WIDTH\n\n        def get_counts(attr_vals, values):\n            \"\"\"Calculate rectangles' widths; if all are 0, they are set to 1.\"\"\"\n            if not attr_vals:\n                counts = [conditionaldict[val] for val in values]\n            else:\n                counts = [conditionaldict[attr_vals + \"-\" + val]\n                          for val in values]\n            total = sum(counts)\n            if total == 0:\n                counts = [1] * len(values)\n                total = sum(counts)\n            return total, counts\n\n        def draw_data(attr_list, x0_x1, y0_y1, side, condition,\n                      total_attrs, used_attrs, used_vals, attr_vals=\"\"):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if conditionaldict[attr_vals] == 0:\n                add_rect(x0, x1, y0, y1, \"\",\n                         used_attrs, used_vals, attr_vals=attr_vals)\n                # store coordinates for later drawing of labels\n                draw_text(side, attr_list[0], (x0, x1), (y0, y1), total_attrs,\n                          used_attrs, used_vals, attr_vals)\n                return\n\n            attr = attr_list[0]\n            # how much smaller rectangles do we draw\n            edge = len(attr_list) * spacing\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]  # reverse names if necessary\n\n            if side % 2 == 0:  # we are drawing on the x axis\n                # remove the space needed for separating different attr. values\n                whole = max(0, (x1 - x0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (x1 - x0) / float(len(values) - 1)\n            else:  # we are drawing on the y axis\n                whole = max(0, (y1 - y0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (y1 - y0) / float(len(values) - 1)\n\n            total, counts = get_counts(attr_vals, values)\n\n            # when visualizing the third attribute and the first attribute has\n            # the last value, reverse the order in which the boxes are drawn;\n            # otherwise, if the last cell, nearest to the labels of the fourth\n            # attribute, is empty, we wouldn't be able to position the labels\n            valrange = list(range(len(values)))\n            if len(attr_list + used_attrs) == 4 and len(used_attrs) == 2:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] == attr1values[-1]:\n                    valrange = valrange[::-1]\n\n            for i in valrange:\n                start = i * edge + whole * float(sum(counts[:i]) / total)\n                end = i * edge + whole * float(sum(counts[:i + 1]) / total)\n                val = values[i]\n                htmlval = to_html(val)\n                newattrvals = attr_vals + \"-\" + val if attr_vals else val\n\n                tooltip = \"{}&nbsp;&nbsp;&nbsp;&nbsp;{}: <b>{}</b><br/>\".format(\n                    condition, attr.name, htmlval)\n                attrs = used_attrs + [attr]\n                vals = used_vals + [val]\n                args = attrs, vals, newattrvals\n                if side % 2 == 0:  # if we are moving horizontally\n                    if len(attr_list) == 1:\n                        add_rect(x0 + start, x0 + end, y0, y1, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0 + start, x0 + end), (y0, y1),\n                            side + 1, tooltip, total_attrs, *args)\n                else:\n                    if len(attr_list) == 1:\n                        add_rect(x0, x1, y0 + start, y0 + end, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0, x1), (y0 + start, y0 + end),\n                            side + 1, tooltip, total_attrs, *args)\n            draw_text(side, attr_list[0], (x0, x1), (y0, y1),\n                      total_attrs, used_attrs, used_vals, attr_vals)\n\n        def draw_text(side, attr, x0_x1, y0_y1,\n                      total_attrs, used_attrs, used_vals, attr_vals):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if side in drawn_sides:\n                return\n\n            # the text on the right will be drawn when we are processing\n            # visualization of the last value of the first attribute\n            if side == 3:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] != attr1values[-1]:\n                    return\n\n            if not conditionaldict[attr_vals]:\n                if side not in draw_positions:\n                    draw_positions[side] = (x0, x1, y0, y1)\n                return\n            else:\n                if side in draw_positions:\n                    # restore the positions of attribute values and name\n                    (x0, x1, y0, y1) = draw_positions[side]\n\n            drawn_sides.add(side)\n\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]\n\n            spaces = spacing * (total_attrs - side) * (len(values) - 1)\n            width = x1 - x0 - spaces * (side % 2 == 0)\n            height = y1 - y0 - spaces * (side % 2 == 1)\n\n            # calculate position of first attribute\n            currpos = 0\n            total, counts = get_counts(attr_vals, values)\n            aligns = [Qt.AlignTop | Qt.AlignHCenter,\n                      Qt.AlignRight | Qt.AlignVCenter,\n                      Qt.AlignBottom | Qt.AlignHCenter,\n                      Qt.AlignLeft | Qt.AlignVCenter]\n            align = aligns[side]\n            for i, val in enumerate(values):\n                if distributiondict[val] != 0:\n                    perc = counts[i] / float(total)\n                    rwidth = width * perc\n                    xs = [x0 + currpos + rwidth / 2,\n                          x0 - self.ATTR_VAL_OFFSET,\n                          x0 + currpos + rwidth / 2,\n                          x1 + self.ATTR_VAL_OFFSET]\n                    ys = [y1 + self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc,\n                          y0 - self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc]\n\n                    CanvasText(\n                        self.canvas, val, xs[side], ys[side], align,\n                        max_width=rwidth if side == 0 else None)\n                    space = height if side % 2 else width\n                    currpos += perc * space + spacing * (total_attrs - side)\n\n            xs = [x0 + (x1 - x0) / 2,\n                  x0 - max_ylabel_w1 - self.ATTR_VAL_OFFSET,\n                  x0 + (x1 - x0) / 2,\n                  x1 + max_ylabel_w2 + self.ATTR_VAL_OFFSET]\n            ys = [y1 + self.ATTR_VAL_OFFSET + self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2,\n                  y0 - self.ATTR_VAL_OFFSET - self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2]\n            CanvasText(\n                self.canvas, attr.name, xs[side], ys[side], align, bold=True,\n                vertical=side % 2)\n\n        def add_rect(x0, x1, y0, y1, condition,\n                     used_attrs, used_vals, attr_vals=\"\"):\n            area_index = len(self.areas)\n            x1 += (x0 == x1)\n            y1 += (y0 == y1)\n            # rectangles of width and height 1 are not shown - increase\n            y1 += (x1 - x0 + y1 - y0 == 2)\n            colors = class_var and [QColor(*col) for col in class_var.colors]\n\n            def select_area(_, ev):\n                self.select_area(area_index, ev)\n\n            def rect(x, y, w, h, z, pen_color=None, brush_color=None, **args):\n                if pen_color is None:\n                    return CanvasRectangle(\n                        self.canvas, x, y, w, h, z=z, onclick=select_area,\n                        **args)\n                if brush_color is None:\n                    brush_color = pen_color\n                return CanvasRectangle(\n                    self.canvas, x, y, w, h, pen_color, brush_color, z=z,\n                    onclick=select_area, **args)\n\n            def line(x1, y1, x2, y2):\n                r = QGraphicsLineItem(x1, y1, x2, y2, None)\n                self.canvas.addItem(r)\n                r.setPen(QPen(Qt.white, 2))\n                r.setZValue(30)\n\n            outer_rect = rect(x0, y0, x1 - x0, y1 - y0, 30)\n            self.areas.append((used_attrs, used_vals, outer_rect))\n            if not conditionaldict[attr_vals]:\n                return\n\n            if self.variable_color is None:\n                s = sum(apriori_dists[0])\n                expected = s * reduce(\n                    mul,\n                    (apriori_dists[i][used_vals[i]] / float(s)\n                     for i in range(len(used_vals))))\n                actual = conditionaldict[attr_vals]\n                pearson = float((actual - expected) / sqrt(expected))\n                if pearson == 0:\n                    ind = 0\n                else:\n                    ind = max(0, min(int(log(abs(pearson), 2)), 3))\n                color = [self.RED_COLORS, self.BLUE_COLORS][pearson > 0][ind]\n                rect(x0, y0, x1 - x0, y1 - y0, -20, color)\n                outer_rect.setToolTip(\n                    condition + \"<hr/>\" +\n                    \"Expected instances: %.1f<br>\"\n                    \"Actual instances: %d<br>\"\n                    \"Standardized (Pearson) residual: %.1f\" %\n                    (expected, conditionaldict[attr_vals], pearson))\n            else:\n                cls_values = get_variable_values_sorted(class_var)\n                prior = get_distribution(data, class_var.name)\n                total = 0\n                for i, value in enumerate(cls_values):\n                    val = conditionaldict[attr_vals + \"-\" + value]\n                    if val == 0:\n                        continue\n                    if i == len(cls_values) - 1:\n                        v = y1 - y0 - total\n                    else:\n                        v = ((y1 - y0) * val) / conditionaldict[attr_vals]\n                    rect(x0, y0 + total, x1 - x0, v, -20, colors[i])\n                    total += v\n\n                if self.use_boxes and \\\n                        abs(x1 - x0) > bar_width and abs(y1 - y0) > bar_width:\n                    total = 0\n                    line(x0 + bar_width, y0, x0 + bar_width, y1)\n                    n = sum(prior)\n                    for i, (val, color) in enumerate(zip(prior, colors)):\n                        if i == len(prior) - 1:\n                            h = y1 - y0 - total\n                        else:\n                            h = (y1 - y0) * val / n\n                        rect(x0, y0 + total, bar_width, h, 20, color)\n                        total += h\n\n                if conditionalsubsetdict:\n                    if conditionalsubsetdict[attr_vals]:\n                        if self.subset_indices is not None:\n                            line(x1 - bar_width, y0, x1 - bar_width, y1)\n                            total = 0\n                            n = conditionalsubsetdict[attr_vals]\n                            if n:\n                                for i, (cls, color) in \\\n                                        enumerate(zip(cls_values, colors)):\n                                    val = conditionalsubsetdict[\n                                        attr_vals + \"-\" + cls]\n                                    if val == 0:\n                                        continue\n                                    if i == len(prior) - 1:\n                                        v = y1 - y0 - total\n                                    else:\n                                        v = ((y1 - y0) * val) / n\n                                    rect(x1 - bar_width, y0 + total,\n                                         bar_width, v, 15, color)\n                                    total += v\n\n                actual = [conditionaldict[attr_vals + \"-\" + cls_values[i]]\n                          for i in range(len(prior))]\n                n_actual = sum(actual)\n                if n_actual > 0:\n                    apriori = [prior[key] for key in cls_values]\n                    n_apriori = sum(apriori)\n                    text = \"<br/>\".join(\n                        \"<b>%s</b>: %d / %.1f%% (Expected %.1f / %.1f%%)\" %\n                        (cls, act, 100.0 * act / n_actual,\n                         apr / n_apriori * n_actual, 100.0 * apr / n_apriori)\n                        for cls, act, apr in zip(cls_values, actual, apriori))\n                else:\n                    text = \"\"\n                outer_rect.setToolTip(\n                    \"{}<hr>Instances: {}<br><br>{}\".format(\n                        condition, n_actual, text[:-4]))\n\n        def create_legend():\n            if self.variable_color is None:\n                names = [\"<-8\", \"-8:-4\", \"-4:-2\", \"-2:2\", \"2:4\", \"4:8\", \">8\",\n                         \"Residuals:\"]\n                colors = self.RED_COLORS[::-1] + self.BLUE_COLORS[1:]\n                edges = repeat(Qt.black)\n            else:\n                names = get_variable_values_sorted(class_var)\n                edges = colors = [QColor(*col) for col in class_var.colors]\n\n            items = []\n            size = 8\n            for name, color, edgecolor in zip(names, colors, edges):\n                item = QGraphicsItemGroup()\n                item.addToGroup(\n                    CanvasRectangle(None, -size / 2, -size / 2, size, size,\n                                    edgecolor, color))\n                item.addToGroup(\n                    CanvasText(None, name, size, 0, Qt.AlignVCenter))\n                items.append(item)\n            return wrap_legend_items(\n                items, hspacing=20, vspacing=16 + size,\n                max_width=self.canvas_view.width() - xoff)\n\n        self.canvas.clear()\n        self.areas = []\n\n        data = self.discrete_data\n        if data is None:\n            return\n        attr_list = self.get_disc_attr_list()\n        class_var = data.domain.class_var\n        if class_var:\n            sql = isinstance(data, SqlTable)\n            name = not sql and data.name\n            # save class_var because it is removed in the next line\n            data = data[:, attr_list + [class_var]]\n            data.domain.class_var = class_var\n            if not sql:\n                data.name = name\n        else:\n            data = data[:, attr_list]\n        # TODO: check this\n        # data = Preprocessor_dropMissing(data)\n        if len(data) == 0:\n            self.Warning.no_valid_data()\n            return\n        else:\n            self.Warning.no_valid_data.clear()\n\n        attrs = [attr for attr in attr_list if not attr.values]\n        if attrs:\n            CanvasText(self.canvas,\n                       \"Feature {} has no values\".format(attrs[0]),\n                       (self.canvas_view.width() - 120) / 2,\n                       self.canvas_view.height() / 2)\n            return\n        if self.variable_color is None:\n            apriori_dists = [get_distribution(data, attr) for attr in attr_list]\n        else:\n            apriori_dists = []\n\n        def get_max_label_width(attr):\n            values = get_variable_values_sorted(attr)\n            maxw = 0\n            for val in values:\n                t = CanvasText(self.canvas, val, 0, 0, bold=0, show=False)\n                maxw = max(int(t.boundingRect().width()), maxw)\n            return maxw\n\n        xoff = 20\n\n        # get the maximum width of rectangle\n        width = 20\n        max_ylabel_w1 = max_ylabel_w2 = 0\n        if len(attr_list) > 1:\n            text = CanvasText(self.canvas, attr_list[1].name, bold=1, show=0)\n            max_ylabel_w1 = min(get_max_label_width(attr_list[1]), 150)\n            width = 5 + text.boundingRect().height() + \\\n                self.ATTR_VAL_OFFSET + max_ylabel_w1\n            xoff = width\n            if len(attr_list) == 4:\n                text = CanvasText(self.canvas, attr_list[3].name, bold=1, show=0)\n                max_ylabel_w2 = min(get_max_label_width(attr_list[3]), 150)\n                width += text.boundingRect().height() + \\\n                    self.ATTR_VAL_OFFSET + max_ylabel_w2 - 10\n\n        legend = create_legend()\n\n        # get the maximum height of rectangle\n        yoff = 45\n        legendoff = yoff + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35\n        square_size = min(self.canvas_view.width() - width - 20,\n                          self.canvas_view.height() - legendoff\n                          - legend.boundingRect().height())\n\n        if square_size < 0:\n            return  # canvas is too small to draw rectangles\n        self.canvas_view.setSceneRect(\n            0, 0, self.canvas_view.width(), self.canvas_view.height())\n\n        drawn_sides = set()\n        draw_positions = {}\n\n        conditionaldict, distributiondict = \\\n            get_conditional_distribution(data, attr_list)\n        conditionalsubsetdict = None\n        if self.subset_indices:\n            conditionalsubsetdict, _ = get_conditional_distribution(\n                self.discrete_data[self.subset_indices], attr_list)\n\n        # draw rectangles\n        draw_data(\n            attr_list, (xoff, xoff + square_size), (yoff, yoff + square_size),\n            0, \"\", len(attr_list), [], [])\n\n        self.canvas.addItem(legend)\n        legend.setPos(\n            xoff - legend.boundingRect().x()\n            + max(0, (square_size - legend.boundingRect().width()) / 2),\n            legendoff + square_size)\n        self.update_selection_rects()\n\n    @classmethod\n    def migrate_context(cls, context, version):\n        if version < 2:\n            settings.migrate_str_to_variable(context, none_placeholder=\"(None)\")\n\n\ndef get_conditional_distribution(data, attrs):\n    cond_dist = defaultdict(int)\n    dist = defaultdict(int)\n    cond_dist[\"\"] = dist[\"\"] = len(data)\n    all_attrs = attrs[:]\n    if data.domain.class_var is not None:\n        all_attrs.append(data.domain.class_var)\n\n    for i in range(1, len(all_attrs) + 1):\n        attr = all_attrs[:i]\n        if isinstance(data, SqlTable):\n            # make all possible pairs of attributes + class_var\n            attr = [a.to_sql() for a in attr]\n            fields = attr + [\"COUNT(*)\"]\n            query = data._sql_query(fields, group_by=attr)\n            with data._execute_sql_query(query) as cur:\n                res = cur.fetchall()\n            for r in res:\n                str_values = [a.repr_val(a.to_val(x))\n                              for a, x in zip(all_attrs, r[:-1])]\n                str_values = [x if x != '?' else 'None' for x in str_values]\n                cond_dist['-'.join(str_values)] = r[-1]\n                dist[str_values[-1]] += r[-1]\n        else:\n            for indices in product(*(range(len(a.values)) for a in attr)):\n                vals = []\n                conditions = []\n                for k, ind in enumerate(indices):\n                    vals.append(attr[k].values[ind])\n                    fd = filter.FilterDiscrete(\n                        column=attr[k], values=[attr[k].values[ind]])\n                    conditions.append(fd)\n                filt = filter.Values(conditions)\n                filtdata = filt(data)\n                cond_dist['-'.join(vals)] = len(filtdata)\n                dist[vals[-1]] += len(filtdata)\n    return cond_dist, dist\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    data = Table(\"zoo\")\n    WidgetPreview(OWMosaicDisplay).run(data, set_subset_data=data[::10])\n",
            "header": {
                "file": {
                    "path": "Orange/widgets/visualize/owmosaic.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "a6c07800b",
                "new_commit_hash": "ec16bc9e0",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/widgets/visualize/owmosaic.py"
            },
            "plus_file": {
                "path": "b/Orange/widgets/visualize/owmosaic.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 317,
                            "length": 6
                        },
                        "new_range": {
                            "start": 317,
                            "length": 7
                        },
                        "section": "class OWMosaicDisplay(OWWidget):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.discrete_data = None",
                                "type": "context"
                            },
                            {
                                "content": "        self.subset_data = None",
                                "type": "context"
                            },
                            {
                                "content": "        self.subset_indices = None",
                                "type": "context"
                            },
                            {
                                "content": "        self.__pending_selection = None",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        self.color_data = None",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/visualize/owmosaic.py",
                            "type": "method",
                            "name": "OWMosaicDisplay.__init__",
                            "content": "class OWMosaicDisplay(OWWidget):\n    name = \"Mosaic Display\"\n    description = \"Display data in a mosaic plot.\"\n    icon = \"icons/MosaicDisplay.svg\"\n    priority = 220\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Table, default=True)\n        data_subset = Input(\"Data Subset\", Table)\n\n    class Outputs:\n        selected_data = Output(\"Selected Data\", Table, default=True)\n        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)\n\n    settingsHandler = DomainContextHandler()\n    vizrank = SettingProvider(MosaicVizRank)\n    settings_version = 2\n    use_boxes = Setting(True)\n    variable1 = ContextSetting(None)\n    variable2 = ContextSetting(None)\n    variable3 = ContextSetting(None)\n    variable4 = ContextSetting(None)\n    variable_color = ContextSetting(None)\n    selection = ContextSetting(set())\n\n    BAR_WIDTH = 5\n    SPACING = 4\n    ATTR_NAME_OFFSET = 20\n    ATTR_VAL_OFFSET = 3\n    BLUE_COLORS = [QColor(255, 255, 255), QColor(210, 210, 255),\n                   QColor(110, 110, 255), QColor(0, 0, 255)]\n    RED_COLORS = [QColor(255, 255, 255), QColor(255, 200, 200),\n                  QColor(255, 100, 100), QColor(255, 0, 0)]\n    graph_name = \"canvas\"\n\n    attrs_changed_manually = Signal(list)\n\n    class Warning(OWWidget.Warning):\n        incompatible_subset = Msg(\"Data subset is incompatible with Data\")\n        no_valid_data = Msg(\"No valid data\")\n        no_cont_selection_sql = \\\n            Msg(\"Selection of numeric features on SQL is not supported\")\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self.discrete_data = None\n        self.subset_data = None\n        self.subset_indices = None\n        self.__pending_selection = None\n\n        self.color_data = None\n\n        self.areas = []\n\n        self.canvas = QGraphicsScene()\n        self.canvas_view = ViewWithPress(\n            self.canvas, handler=self.clear_selection)\n        self.mainArea.layout().addWidget(self.canvas_view)\n        self.canvas_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setRenderHint(QPainter.Antialiasing)\n\n        box = gui.vBox(self.controlArea, box=True)\n        self.model_1 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE)\n        self.model_234 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(None)\")\n        self.attr_combos = [\n            gui.comboBox(\n                box, self, value=\"variable{}\".format(i),\n                orientation=Qt.Horizontal, contentsLength=12,\n                callback=self.attr_changed,\n                model=self.model_1 if i == 1 else self.model_234)\n            for i in range(1, 5)]\n        self.vizrank, self.vizrank_button = MosaicVizRank.add_vizrank(\n            box, self, \"Find Informative Mosaics\", self.set_attr)\n\n        box2 = gui.vBox(self.controlArea, box=\"Interior Coloring\")\n        self.color_model = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(Pearson residuals)\")\n        self.cb_attr_color = gui.comboBox(\n            box2, self, value=\"variable_color\",\n            orientation=Qt.Horizontal, contentsLength=12, labelWidth=50,\n            callback=self.set_color_data, model=self.color_model)\n        self.bar_button = gui.checkBox(\n            box2, self, 'use_boxes', label='Compare with total',\n            callback=self.update_graph)\n        gui.rubber(self.controlArea)\n\n    def sizeHint(self):\n        return QSize(720, 530)\n\n    def _get_discrete_data(self, data):\n        \"\"\"\n        Discretize continuous attributes.\n        Return None when there is no data, no rows, or no primitive attributes.\n        \"\"\"\n        if (data is None or\n                not len(data) or\n                not any(attr.is_discrete or attr.is_continuous\n                        for attr in chain(data.domain.variables,\n                                          data.domain.metas))):\n            return None\n        elif any(attr.is_continuous for attr in data.domain.variables):\n            return Discretize(\n                method=EqualFreq(n=4), remove_const=False, discretize_classes=True,\n                discretize_metas=True)(data)\n        else:\n            return data\n\n    def init_combos(self, data):\n        def set_combos(value):\n            self.model_1.set_domain(value)\n            self.model_234.set_domain(value)\n            self.color_model.set_domain(value)\n\n        if data is None:\n            set_combos(None)\n            self.variable1 = self.variable2 = self.variable3 \\\n                = self.variable4 = self.variable_color = None\n            return\n        set_combos(self.data.domain)\n\n        if len(self.model_1) > 0:\n            self.variable1 = self.model_1[0]\n            self.variable2 = self.model_1[min(1, len(self.model_1) - 1)]\n        self.variable3 = self.variable4 = None\n        self.variable_color = self.data.domain.class_var  # None is OK, too\n\n    def get_disc_attr_list(self):\n        return [self.discrete_data.domain[var.name]\n                for var in (self.variable1, self.variable2,\n                            self.variable3, self.variable4)\n                if var]\n\n    def set_attr(self, *attrs):\n        self.variable1, self.variable2, self.variable3, self.variable4 = [\n            attr and self.data.domain[attr.name] for attr in attrs]\n        self.reset_graph()\n\n    def attr_changed(self):\n        self.attrs_changed_manually.emit(self.get_disc_attr_list())\n        self.reset_graph()\n\n    def resizeEvent(self, e):\n        OWWidget.resizeEvent(self, e)\n        self.update_graph()\n\n    def showEvent(self, ev):\n        OWWidget.showEvent(self, ev)\n        self.update_graph()\n\n    @Inputs.data\n    def set_data(self, data):\n        if isinstance(data, SqlTable) and data.approx_len() > LARGE_TABLE:\n            data = data.sample_time(DEFAULT_SAMPLE_TIME)\n\n        self.closeContext()\n        self.data = data\n\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(\n            self.data is not None and len(self.data) > 1\n            and len(self.data.domain.attributes) >= 1)\n\n        if self.data is None:\n            self.discrete_data = None\n            self.init_combos(None)\n            return\n\n        self.init_combos(self.data)\n        self.openContext(self.data)\n        self.__pending_selection = self.selection\n\n    @Inputs.data_subset\n    def set_subset_data(self, data):\n        self.subset_data = data\n\n    # this is called by widget after setData and setSubsetData are called.\n    # this way the graph is updated only once\n    def handleNewSignals(self):\n        self.Warning.incompatible_subset.clear()\n        self.subset_indices = None\n        if self.data is not None and self.subset_data:\n            transformed = self.subset_data.transform(self.data.domain)\n            if np.all(np.isnan(transformed.X)) \\\n                    and np.all(np.isnan(transformed.Y)):\n                self.Warning.incompatible_subset()\n            else:\n                indices = {e.id for e in transformed}\n                self.subset_indices = [ex.id in indices for ex in self.data]\n        self.set_color_data()\n        self.reset_graph()\n\n    def clear_selection(self):\n        self.selection = set()\n        self.update_selection_rects()\n        self.send_selection()\n\n    def coloring_changed(self):\n        self.vizrank.coloring_changed()\n        self.update_graph()\n\n    def reset_graph(self):\n        self.clear_selection()\n        self.update_graph()\n        if self.__pending_selection is not None:\n            self.selection = self.__pending_selection\n            self.__pending_selection = None\n            self.update_selection_rects()\n            self.send_selection()\n\n    def set_color_data(self):\n        if self.data is None:\n            return\n        self.bar_button.setEnabled(self.variable_color is not None)\n        attrs = [v for v in self.model_1 if v and v is not self.variable_color]\n        domain = Domain(attrs, self.variable_color, None)\n        self.color_data = self.data.from_table(domain, self.data)\n        self.discrete_data = self._get_discrete_data(self.color_data)\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(True)\n        self.coloring_changed()\n\n    def update_selection_rects(self):\n        pens = (QPen(), QPen(Qt.black, 3, Qt.DotLine))\n        for i, (_, _, area) in enumerate(self.areas):\n            area.setPen(pens[i in self.selection])\n\n    def select_area(self, index, ev):\n        if ev.button() != Qt.LeftButton:\n            return\n        if ev.modifiers() & Qt.ControlModifier:\n            self.selection ^= {index}\n        else:\n            self.selection = {index}\n        self.update_selection_rects()\n        self.send_selection()\n\n    def send_selection(self):\n        if not self.selection or self.data is None:\n            self.Outputs.selected_data.send(None)\n            self.Outputs.annotated_data.send(\n                create_annotated_table(self.data, []))\n            return\n        filters = []\n        self.Warning.no_cont_selection_sql.clear()\n        if self.discrete_data is not self.data:\n            if isinstance(self.data, SqlTable):\n                self.Warning.no_cont_selection_sql()\n        for i in self.selection:\n            cols, vals, _ = self.areas[i]\n            filters.append(\n                filter.Values(\n                    filter.FilterDiscrete(col, [val])\n                    for col, val in zip(cols, vals)))\n        if len(filters) > 1:\n            filters = filter.Values(filters, conjunction=False)\n        else:\n            filters = filters[0]\n        selection = filters(self.discrete_data)\n        idset = set(selection.ids)\n        sel_idx = [i for i, id in enumerate(self.data.ids) if id in idset]\n        if self.discrete_data is not self.data:\n            selection = self.data[sel_idx]\n        self.Outputs.selected_data.send(selection)\n        self.Outputs.annotated_data.send(\n            create_annotated_table(self.data, sel_idx))\n\n    def send_report(self):\n        self.report_plot(self.canvas)\n\n    def update_graph(self):\n        spacing = self.SPACING\n        bar_width = self.BAR_WIDTH\n\n        def get_counts(attr_vals, values):\n            \"\"\"Calculate rectangles' widths; if all are 0, they are set to 1.\"\"\"\n            if not attr_vals:\n                counts = [conditionaldict[val] for val in values]\n            else:\n                counts = [conditionaldict[attr_vals + \"-\" + val]\n                          for val in values]\n            total = sum(counts)\n            if total == 0:\n                counts = [1] * len(values)\n                total = sum(counts)\n            return total, counts\n\n        def draw_data(attr_list, x0_x1, y0_y1, side, condition,\n                      total_attrs, used_attrs, used_vals, attr_vals=\"\"):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if conditionaldict[attr_vals] == 0:\n                add_rect(x0, x1, y0, y1, \"\",\n                         used_attrs, used_vals, attr_vals=attr_vals)\n                # store coordinates for later drawing of labels\n                draw_text(side, attr_list[0], (x0, x1), (y0, y1), total_attrs,\n                          used_attrs, used_vals, attr_vals)\n                return\n\n            attr = attr_list[0]\n            # how much smaller rectangles do we draw\n            edge = len(attr_list) * spacing\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]  # reverse names if necessary\n\n            if side % 2 == 0:  # we are drawing on the x axis\n                # remove the space needed for separating different attr. values\n                whole = max(0, (x1 - x0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (x1 - x0) / float(len(values) - 1)\n            else:  # we are drawing on the y axis\n                whole = max(0, (y1 - y0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (y1 - y0) / float(len(values) - 1)\n\n            total, counts = get_counts(attr_vals, values)\n\n            # when visualizing the third attribute and the first attribute has\n            # the last value, reverse the order in which the boxes are drawn;\n            # otherwise, if the last cell, nearest to the labels of the fourth\n            # attribute, is empty, we wouldn't be able to position the labels\n            valrange = list(range(len(values)))\n            if len(attr_list + used_attrs) == 4 and len(used_attrs) == 2:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] == attr1values[-1]:\n                    valrange = valrange[::-1]\n\n            for i in valrange:\n                start = i * edge + whole * float(sum(counts[:i]) / total)\n                end = i * edge + whole * float(sum(counts[:i + 1]) / total)\n                val = values[i]\n                htmlval = to_html(val)\n                newattrvals = attr_vals + \"-\" + val if attr_vals else val\n\n                tooltip = \"{}&nbsp;&nbsp;&nbsp;&nbsp;{}: <b>{}</b><br/>\".format(\n                    condition, attr.name, htmlval)\n                attrs = used_attrs + [attr]\n                vals = used_vals + [val]\n                args = attrs, vals, newattrvals\n                if side % 2 == 0:  # if we are moving horizontally\n                    if len(attr_list) == 1:\n                        add_rect(x0 + start, x0 + end, y0, y1, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0 + start, x0 + end), (y0, y1),\n                            side + 1, tooltip, total_attrs, *args)\n                else:\n                    if len(attr_list) == 1:\n                        add_rect(x0, x1, y0 + start, y0 + end, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0, x1), (y0 + start, y0 + end),\n                            side + 1, tooltip, total_attrs, *args)\n            draw_text(side, attr_list[0], (x0, x1), (y0, y1),\n                      total_attrs, used_attrs, used_vals, attr_vals)\n\n        def draw_text(side, attr, x0_x1, y0_y1,\n                      total_attrs, used_attrs, used_vals, attr_vals):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if side in drawn_sides:\n                return\n\n            # the text on the right will be drawn when we are processing\n            # visualization of the last value of the first attribute\n            if side == 3:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] != attr1values[-1]:\n                    return\n\n            if not conditionaldict[attr_vals]:\n                if side not in draw_positions:\n                    draw_positions[side] = (x0, x1, y0, y1)\n                return\n            else:\n                if side in draw_positions:\n                    # restore the positions of attribute values and name\n                    (x0, x1, y0, y1) = draw_positions[side]\n\n            drawn_sides.add(side)\n\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]\n\n            spaces = spacing * (total_attrs - side) * (len(values) - 1)\n            width = x1 - x0 - spaces * (side % 2 == 0)\n            height = y1 - y0 - spaces * (side % 2 == 1)\n\n            # calculate position of first attribute\n            currpos = 0\n            total, counts = get_counts(attr_vals, values)\n            aligns = [Qt.AlignTop | Qt.AlignHCenter,\n                      Qt.AlignRight | Qt.AlignVCenter,\n                      Qt.AlignBottom | Qt.AlignHCenter,\n                      Qt.AlignLeft | Qt.AlignVCenter]\n            align = aligns[side]\n            for i, val in enumerate(values):\n                if distributiondict[val] != 0:\n                    perc = counts[i] / float(total)\n                    rwidth = width * perc\n                    xs = [x0 + currpos + rwidth / 2,\n                          x0 - self.ATTR_VAL_OFFSET,\n                          x0 + currpos + rwidth / 2,\n                          x1 + self.ATTR_VAL_OFFSET]\n                    ys = [y1 + self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc,\n                          y0 - self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc]\n\n                    CanvasText(\n                        self.canvas, val, xs[side], ys[side], align,\n                        max_width=rwidth if side == 0 else None)\n                    space = height if side % 2 else width\n                    currpos += perc * space + spacing * (total_attrs - side)\n\n            xs = [x0 + (x1 - x0) / 2,\n                  x0 - max_ylabel_w1 - self.ATTR_VAL_OFFSET,\n                  x0 + (x1 - x0) / 2,\n                  x1 + max_ylabel_w2 + self.ATTR_VAL_OFFSET]\n            ys = [y1 + self.ATTR_VAL_OFFSET + self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2,\n                  y0 - self.ATTR_VAL_OFFSET - self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2]\n            CanvasText(\n                self.canvas, attr.name, xs[side], ys[side], align, bold=True,\n                vertical=side % 2)\n\n        def add_rect(x0, x1, y0, y1, condition,\n                     used_attrs, used_vals, attr_vals=\"\"):\n            area_index = len(self.areas)\n            x1 += (x0 == x1)\n            y1 += (y0 == y1)\n            # rectangles of width and height 1 are not shown - increase\n            y1 += (x1 - x0 + y1 - y0 == 2)\n            colors = class_var and [QColor(*col) for col in class_var.colors]\n\n            def select_area(_, ev):\n                self.select_area(area_index, ev)\n\n            def rect(x, y, w, h, z, pen_color=None, brush_color=None, **args):\n                if pen_color is None:\n                    return CanvasRectangle(\n                        self.canvas, x, y, w, h, z=z, onclick=select_area,\n                        **args)\n                if brush_color is None:\n                    brush_color = pen_color\n                return CanvasRectangle(\n                    self.canvas, x, y, w, h, pen_color, brush_color, z=z,\n                    onclick=select_area, **args)\n\n            def line(x1, y1, x2, y2):\n                r = QGraphicsLineItem(x1, y1, x2, y2, None)\n                self.canvas.addItem(r)\n                r.setPen(QPen(Qt.white, 2))\n                r.setZValue(30)\n\n            outer_rect = rect(x0, y0, x1 - x0, y1 - y0, 30)\n            self.areas.append((used_attrs, used_vals, outer_rect))\n            if not conditionaldict[attr_vals]:\n                return\n\n            if self.variable_color is None:\n                s = sum(apriori_dists[0])\n                expected = s * reduce(\n                    mul,\n                    (apriori_dists[i][used_vals[i]] / float(s)\n                     for i in range(len(used_vals))))\n                actual = conditionaldict[attr_vals]\n                pearson = float((actual - expected) / sqrt(expected))\n                if pearson == 0:\n                    ind = 0\n                else:\n                    ind = max(0, min(int(log(abs(pearson), 2)), 3))\n                color = [self.RED_COLORS, self.BLUE_COLORS][pearson > 0][ind]\n                rect(x0, y0, x1 - x0, y1 - y0, -20, color)\n                outer_rect.setToolTip(\n                    condition + \"<hr/>\" +\n                    \"Expected instances: %.1f<br>\"\n                    \"Actual instances: %d<br>\"\n                    \"Standardized (Pearson) residual: %.1f\" %\n                    (expected, conditionaldict[attr_vals], pearson))\n            else:\n                cls_values = get_variable_values_sorted(class_var)\n                prior = get_distribution(data, class_var.name)\n                total = 0\n                for i, value in enumerate(cls_values):\n                    val = conditionaldict[attr_vals + \"-\" + value]\n                    if val == 0:\n                        continue\n                    if i == len(cls_values) - 1:\n                        v = y1 - y0 - total\n                    else:\n                        v = ((y1 - y0) * val) / conditionaldict[attr_vals]\n                    rect(x0, y0 + total, x1 - x0, v, -20, colors[i])\n                    total += v\n\n                if self.use_boxes and \\\n                        abs(x1 - x0) > bar_width and abs(y1 - y0) > bar_width:\n                    total = 0\n                    line(x0 + bar_width, y0, x0 + bar_width, y1)\n                    n = sum(prior)\n                    for i, (val, color) in enumerate(zip(prior, colors)):\n                        if i == len(prior) - 1:\n                            h = y1 - y0 - total\n                        else:\n                            h = (y1 - y0) * val / n\n                        rect(x0, y0 + total, bar_width, h, 20, color)\n                        total += h\n\n                if conditionalsubsetdict:\n                    if conditionalsubsetdict[attr_vals]:\n                        if self.subset_indices is not None:\n                            line(x1 - bar_width, y0, x1 - bar_width, y1)\n                            total = 0\n                            n = conditionalsubsetdict[attr_vals]\n                            if n:\n                                for i, (cls, color) in \\\n                                        enumerate(zip(cls_values, colors)):\n                                    val = conditionalsubsetdict[\n                                        attr_vals + \"-\" + cls]\n                                    if val == 0:\n                                        continue\n                                    if i == len(prior) - 1:\n                                        v = y1 - y0 - total\n                                    else:\n                                        v = ((y1 - y0) * val) / n\n                                    rect(x1 - bar_width, y0 + total,\n                                         bar_width, v, 15, color)\n                                    total += v\n\n                actual = [conditionaldict[attr_vals + \"-\" + cls_values[i]]\n                          for i in range(len(prior))]\n                n_actual = sum(actual)\n                if n_actual > 0:\n                    apriori = [prior[key] for key in cls_values]\n                    n_apriori = sum(apriori)\n                    text = \"<br/>\".join(\n                        \"<b>%s</b>: %d / %.1f%% (Expected %.1f / %.1f%%)\" %\n                        (cls, act, 100.0 * act / n_actual,\n                         apr / n_apriori * n_actual, 100.0 * apr / n_apriori)\n                        for cls, act, apr in zip(cls_values, actual, apriori))\n                else:\n                    text = \"\"\n                outer_rect.setToolTip(\n                    \"{}<hr>Instances: {}<br><br>{}\".format(\n                        condition, n_actual, text[:-4]))\n\n        def create_legend():\n            if self.variable_color is None:\n                names = [\"<-8\", \"-8:-4\", \"-4:-2\", \"-2:2\", \"2:4\", \"4:8\", \">8\",\n                         \"Residuals:\"]\n                colors = self.RED_COLORS[::-1] + self.BLUE_COLORS[1:]\n                edges = repeat(Qt.black)\n            else:\n                names = get_variable_values_sorted(class_var)\n                edges = colors = [QColor(*col) for col in class_var.colors]\n\n            items = []\n            size = 8\n            for name, color, edgecolor in zip(names, colors, edges):\n                item = QGraphicsItemGroup()\n                item.addToGroup(\n                    CanvasRectangle(None, -size / 2, -size / 2, size, size,\n                                    edgecolor, color))\n                item.addToGroup(\n                    CanvasText(None, name, size, 0, Qt.AlignVCenter))\n                items.append(item)\n            return wrap_legend_items(\n                items, hspacing=20, vspacing=16 + size,\n                max_width=self.canvas_view.width() - xoff)\n\n        self.canvas.clear()\n        self.areas = []\n\n        data = self.discrete_data\n        if data is None:\n            return\n        attr_list = self.get_disc_attr_list()\n        class_var = data.domain.class_var\n        if class_var:\n            sql = isinstance(data, SqlTable)\n            name = not sql and data.name\n            # save class_var because it is removed in the next line\n            data = data[:, attr_list + [class_var]]\n            data.domain.class_var = class_var\n            if not sql:\n                data.name = name\n        else:\n            data = data[:, attr_list]\n        # TODO: check this\n        # data = Preprocessor_dropMissing(data)\n        if len(data) == 0:\n            self.Warning.no_valid_data()\n            return\n        else:\n            self.Warning.no_valid_data.clear()\n\n        attrs = [attr for attr in attr_list if not attr.values]\n        if attrs:\n            CanvasText(self.canvas,\n                       \"Feature {} has no values\".format(attrs[0]),\n                       (self.canvas_view.width() - 120) / 2,\n                       self.canvas_view.height() / 2)\n            return\n        if self.variable_color is None:\n            apriori_dists = [get_distribution(data, attr) for attr in attr_list]\n        else:\n            apriori_dists = []\n\n        def get_max_label_width(attr):\n            values = get_variable_values_sorted(attr)\n            maxw = 0\n            for val in values:\n                t = CanvasText(self.canvas, val, 0, 0, bold=0, show=False)\n                maxw = max(int(t.boundingRect().width()), maxw)\n            return maxw\n\n        xoff = 20\n\n        # get the maximum width of rectangle\n        width = 20\n        max_ylabel_w1 = max_ylabel_w2 = 0\n        if len(attr_list) > 1:\n            text = CanvasText(self.canvas, attr_list[1].name, bold=1, show=0)\n            max_ylabel_w1 = min(get_max_label_width(attr_list[1]), 150)\n            width = 5 + text.boundingRect().height() + \\\n                self.ATTR_VAL_OFFSET + max_ylabel_w1\n            xoff = width\n            if len(attr_list) == 4:\n                text = CanvasText(self.canvas, attr_list[3].name, bold=1, show=0)\n                max_ylabel_w2 = min(get_max_label_width(attr_list[3]), 150)\n                width += text.boundingRect().height() + \\\n                    self.ATTR_VAL_OFFSET + max_ylabel_w2 - 10\n\n        legend = create_legend()\n\n        # get the maximum height of rectangle\n        yoff = 45\n        legendoff = yoff + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35\n        square_size = min(self.canvas_view.width() - width - 20,\n                          self.canvas_view.height() - legendoff\n                          - legend.boundingRect().height())\n\n        if square_size < 0:\n            return  # canvas is too small to draw rectangles\n        self.canvas_view.setSceneRect(\n            0, 0, self.canvas_view.width(), self.canvas_view.height())\n\n        drawn_sides = set()\n        draw_positions = {}\n\n        conditionaldict, distributiondict = \\\n            get_conditional_distribution(data, attr_list)\n        conditionalsubsetdict = None\n        if self.subset_indices:\n            conditionalsubsetdict, _ = get_conditional_distribution(\n                self.discrete_data[self.subset_indices], attr_list)\n\n        # draw rectangles\n        draw_data(\n            attr_list, (xoff, xoff + square_size), (yoff, yoff + square_size),\n            0, \"\", len(attr_list), [], [])\n\n        self.canvas.addItem(legend)\n        legend.setPos(\n            xoff - legend.boundingRect().x()\n            + max(0, (square_size - legend.boundingRect().width()) / 2),\n            legendoff + square_size)\n        self.update_selection_rects()\n\n    @classmethod\n    def migrate_context(cls, context, version):\n        if version < 2:\n            settings.migrate_str_to_variable(context, none_placeholder=\"(None)\")",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 313,
                            "end_lineno": 361,
                            "parent": {
                                "file_name": "Orange/widgets/visualize/owmosaic.py",
                                "type": "class",
                                "name": "OWMosaicDisplay",
                                "content": "class OWMosaicDisplay(OWWidget):\n    name = \"Mosaic Display\"\n    description = \"Display data in a mosaic plot.\"\n    icon = \"icons/MosaicDisplay.svg\"\n    priority = 220\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Table, default=True)\n        data_subset = Input(\"Data Subset\", Table)\n\n    class Outputs:\n        selected_data = Output(\"Selected Data\", Table, default=True)\n        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)\n\n    settingsHandler = DomainContextHandler()\n    vizrank = SettingProvider(MosaicVizRank)\n    settings_version = 2\n    use_boxes = Setting(True)\n    variable1 = ContextSetting(None)\n    variable2 = ContextSetting(None)\n    variable3 = ContextSetting(None)\n    variable4 = ContextSetting(None)\n    variable_color = ContextSetting(None)\n    selection = ContextSetting(set())\n\n    BAR_WIDTH = 5\n    SPACING = 4\n    ATTR_NAME_OFFSET = 20\n    ATTR_VAL_OFFSET = 3\n    BLUE_COLORS = [QColor(255, 255, 255), QColor(210, 210, 255),\n                   QColor(110, 110, 255), QColor(0, 0, 255)]\n    RED_COLORS = [QColor(255, 255, 255), QColor(255, 200, 200),\n                  QColor(255, 100, 100), QColor(255, 0, 0)]\n    graph_name = \"canvas\"\n\n    attrs_changed_manually = Signal(list)\n\n    class Warning(OWWidget.Warning):\n        incompatible_subset = Msg(\"Data subset is incompatible with Data\")\n        no_valid_data = Msg(\"No valid data\")\n        no_cont_selection_sql = \\\n            Msg(\"Selection of numeric features on SQL is not supported\")\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self.discrete_data = None\n        self.subset_data = None\n        self.subset_indices = None\n        self.__pending_selection = None\n\n        self.color_data = None\n\n        self.areas = []\n\n        self.canvas = QGraphicsScene()\n        self.canvas_view = ViewWithPress(\n            self.canvas, handler=self.clear_selection)\n        self.mainArea.layout().addWidget(self.canvas_view)\n        self.canvas_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setRenderHint(QPainter.Antialiasing)\n\n        box = gui.vBox(self.controlArea, box=True)\n        self.model_1 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE)\n        self.model_234 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(None)\")\n        self.attr_combos = [\n            gui.comboBox(\n                box, self, value=\"variable{}\".format(i),\n                orientation=Qt.Horizontal, contentsLength=12,\n                callback=self.attr_changed,\n                model=self.model_1 if i == 1 else self.model_234)\n            for i in range(1, 5)]\n        self.vizrank, self.vizrank_button = MosaicVizRank.add_vizrank(\n            box, self, \"Find Informative Mosaics\", self.set_attr)\n\n        box2 = gui.vBox(self.controlArea, box=\"Interior Coloring\")\n        self.color_model = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(Pearson residuals)\")\n        self.cb_attr_color = gui.comboBox(\n            box2, self, value=\"variable_color\",\n            orientation=Qt.Horizontal, contentsLength=12, labelWidth=50,\n            callback=self.set_color_data, model=self.color_model)\n        self.bar_button = gui.checkBox(\n            box2, self, 'use_boxes', label='Compare with total',\n            callback=self.update_graph)\n        gui.rubber(self.controlArea)\n\n    def sizeHint(self):\n        return QSize(720, 530)\n\n    def _get_discrete_data(self, data):\n        \"\"\"\n        Discretize continuous attributes.\n        Return None when there is no data, no rows, or no primitive attributes.\n        \"\"\"\n        if (data is None or\n                not len(data) or\n                not any(attr.is_discrete or attr.is_continuous\n                        for attr in chain(data.domain.variables,\n                                          data.domain.metas))):\n            return None\n        elif any(attr.is_continuous for attr in data.domain.variables):\n            return Discretize(\n                method=EqualFreq(n=4), remove_const=False, discretize_classes=True,\n                discretize_metas=True)(data)\n        else:\n            return data\n\n    def init_combos(self, data):\n        def set_combos(value):\n            self.model_1.set_domain(value)\n            self.model_234.set_domain(value)\n            self.color_model.set_domain(value)\n\n        if data is None:\n            set_combos(None)\n            self.variable1 = self.variable2 = self.variable3 \\\n                = self.variable4 = self.variable_color = None\n            return\n        set_combos(self.data.domain)\n\n        if len(self.model_1) > 0:\n            self.variable1 = self.model_1[0]\n            self.variable2 = self.model_1[min(1, len(self.model_1) - 1)]\n        self.variable3 = self.variable4 = None\n        self.variable_color = self.data.domain.class_var  # None is OK, too\n\n    def get_disc_attr_list(self):\n        return [self.discrete_data.domain[var.name]\n                for var in (self.variable1, self.variable2,\n                            self.variable3, self.variable4)\n                if var]\n\n    def set_attr(self, *attrs):\n        self.variable1, self.variable2, self.variable3, self.variable4 = [\n            attr and self.data.domain[attr.name] for attr in attrs]\n        self.reset_graph()\n\n    def attr_changed(self):\n        self.attrs_changed_manually.emit(self.get_disc_attr_list())\n        self.reset_graph()\n\n    def resizeEvent(self, e):\n        OWWidget.resizeEvent(self, e)\n        self.update_graph()\n\n    def showEvent(self, ev):\n        OWWidget.showEvent(self, ev)\n        self.update_graph()\n\n    @Inputs.data\n    def set_data(self, data):\n        if isinstance(data, SqlTable) and data.approx_len() > LARGE_TABLE:\n            data = data.sample_time(DEFAULT_SAMPLE_TIME)\n\n        self.closeContext()\n        self.data = data\n\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(\n            self.data is not None and len(self.data) > 1\n            and len(self.data.domain.attributes) >= 1)\n\n        if self.data is None:\n            self.discrete_data = None\n            self.init_combos(None)\n            return\n\n        self.init_combos(self.data)\n        self.openContext(self.data)\n        self.__pending_selection = self.selection\n\n    @Inputs.data_subset\n    def set_subset_data(self, data):\n        self.subset_data = data\n\n    # this is called by widget after setData and setSubsetData are called.\n    # this way the graph is updated only once\n    def handleNewSignals(self):\n        self.Warning.incompatible_subset.clear()\n        self.subset_indices = None\n        if self.data is not None and self.subset_data:\n            transformed = self.subset_data.transform(self.data.domain)\n            if np.all(np.isnan(transformed.X)) \\\n                    and np.all(np.isnan(transformed.Y)):\n                self.Warning.incompatible_subset()\n            else:\n                indices = {e.id for e in transformed}\n                self.subset_indices = [ex.id in indices for ex in self.data]\n        self.set_color_data()\n        self.reset_graph()\n\n    def clear_selection(self):\n        self.selection = set()\n        self.update_selection_rects()\n        self.send_selection()\n\n    def coloring_changed(self):\n        self.vizrank.coloring_changed()\n        self.update_graph()\n\n    def reset_graph(self):\n        self.clear_selection()\n        self.update_graph()\n        if self.__pending_selection is not None:\n            self.selection = self.__pending_selection\n            self.__pending_selection = None\n            self.update_selection_rects()\n            self.send_selection()\n\n    def set_color_data(self):\n        if self.data is None:\n            return\n        self.bar_button.setEnabled(self.variable_color is not None)\n        attrs = [v for v in self.model_1 if v and v is not self.variable_color]\n        domain = Domain(attrs, self.variable_color, None)\n        self.color_data = self.data.from_table(domain, self.data)\n        self.discrete_data = self._get_discrete_data(self.color_data)\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(True)\n        self.coloring_changed()\n\n    def update_selection_rects(self):\n        pens = (QPen(), QPen(Qt.black, 3, Qt.DotLine))\n        for i, (_, _, area) in enumerate(self.areas):\n            area.setPen(pens[i in self.selection])\n\n    def select_area(self, index, ev):\n        if ev.button() != Qt.LeftButton:\n            return\n        if ev.modifiers() & Qt.ControlModifier:\n            self.selection ^= {index}\n        else:\n            self.selection = {index}\n        self.update_selection_rects()\n        self.send_selection()\n\n    def send_selection(self):\n        if not self.selection or self.data is None:\n            self.Outputs.selected_data.send(None)\n            self.Outputs.annotated_data.send(\n                create_annotated_table(self.data, []))\n            return\n        filters = []\n        self.Warning.no_cont_selection_sql.clear()\n        if self.discrete_data is not self.data:\n            if isinstance(self.data, SqlTable):\n                self.Warning.no_cont_selection_sql()\n        for i in self.selection:\n            cols, vals, _ = self.areas[i]\n            filters.append(\n                filter.Values(\n                    filter.FilterDiscrete(col, [val])\n                    for col, val in zip(cols, vals)))\n        if len(filters) > 1:\n            filters = filter.Values(filters, conjunction=False)\n        else:\n            filters = filters[0]\n        selection = filters(self.discrete_data)\n        idset = set(selection.ids)\n        sel_idx = [i for i, id in enumerate(self.data.ids) if id in idset]\n        if self.discrete_data is not self.data:\n            selection = self.data[sel_idx]\n        self.Outputs.selected_data.send(selection)\n        self.Outputs.annotated_data.send(\n            create_annotated_table(self.data, sel_idx))\n\n    def send_report(self):\n        self.report_plot(self.canvas)\n\n    def update_graph(self):\n        spacing = self.SPACING\n        bar_width = self.BAR_WIDTH\n\n        def get_counts(attr_vals, values):\n            \"\"\"Calculate rectangles' widths; if all are 0, they are set to 1.\"\"\"\n            if not attr_vals:\n                counts = [conditionaldict[val] for val in values]\n            else:\n                counts = [conditionaldict[attr_vals + \"-\" + val]\n                          for val in values]\n            total = sum(counts)\n            if total == 0:\n                counts = [1] * len(values)\n                total = sum(counts)\n            return total, counts\n\n        def draw_data(attr_list, x0_x1, y0_y1, side, condition,\n                      total_attrs, used_attrs, used_vals, attr_vals=\"\"):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if conditionaldict[attr_vals] == 0:\n                add_rect(x0, x1, y0, y1, \"\",\n                         used_attrs, used_vals, attr_vals=attr_vals)\n                # store coordinates for later drawing of labels\n                draw_text(side, attr_list[0], (x0, x1), (y0, y1), total_attrs,\n                          used_attrs, used_vals, attr_vals)\n                return\n\n            attr = attr_list[0]\n            # how much smaller rectangles do we draw\n            edge = len(attr_list) * spacing\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]  # reverse names if necessary\n\n            if side % 2 == 0:  # we are drawing on the x axis\n                # remove the space needed for separating different attr. values\n                whole = max(0, (x1 - x0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (x1 - x0) / float(len(values) - 1)\n            else:  # we are drawing on the y axis\n                whole = max(0, (y1 - y0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (y1 - y0) / float(len(values) - 1)\n\n            total, counts = get_counts(attr_vals, values)\n\n            # when visualizing the third attribute and the first attribute has\n            # the last value, reverse the order in which the boxes are drawn;\n            # otherwise, if the last cell, nearest to the labels of the fourth\n            # attribute, is empty, we wouldn't be able to position the labels\n            valrange = list(range(len(values)))\n            if len(attr_list + used_attrs) == 4 and len(used_attrs) == 2:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] == attr1values[-1]:\n                    valrange = valrange[::-1]\n\n            for i in valrange:\n                start = i * edge + whole * float(sum(counts[:i]) / total)\n                end = i * edge + whole * float(sum(counts[:i + 1]) / total)\n                val = values[i]\n                htmlval = to_html(val)\n                newattrvals = attr_vals + \"-\" + val if attr_vals else val\n\n                tooltip = \"{}&nbsp;&nbsp;&nbsp;&nbsp;{}: <b>{}</b><br/>\".format(\n                    condition, attr.name, htmlval)\n                attrs = used_attrs + [attr]\n                vals = used_vals + [val]\n                args = attrs, vals, newattrvals\n                if side % 2 == 0:  # if we are moving horizontally\n                    if len(attr_list) == 1:\n                        add_rect(x0 + start, x0 + end, y0, y1, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0 + start, x0 + end), (y0, y1),\n                            side + 1, tooltip, total_attrs, *args)\n                else:\n                    if len(attr_list) == 1:\n                        add_rect(x0, x1, y0 + start, y0 + end, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0, x1), (y0 + start, y0 + end),\n                            side + 1, tooltip, total_attrs, *args)\n            draw_text(side, attr_list[0], (x0, x1), (y0, y1),\n                      total_attrs, used_attrs, used_vals, attr_vals)\n\n        def draw_text(side, attr, x0_x1, y0_y1,\n                      total_attrs, used_attrs, used_vals, attr_vals):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if side in drawn_sides:\n                return\n\n            # the text on the right will be drawn when we are processing\n            # visualization of the last value of the first attribute\n            if side == 3:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] != attr1values[-1]:\n                    return\n\n            if not conditionaldict[attr_vals]:\n                if side not in draw_positions:\n                    draw_positions[side] = (x0, x1, y0, y1)\n                return\n            else:\n                if side in draw_positions:\n                    # restore the positions of attribute values and name\n                    (x0, x1, y0, y1) = draw_positions[side]\n\n            drawn_sides.add(side)\n\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]\n\n            spaces = spacing * (total_attrs - side) * (len(values) - 1)\n            width = x1 - x0 - spaces * (side % 2 == 0)\n            height = y1 - y0 - spaces * (side % 2 == 1)\n\n            # calculate position of first attribute\n            currpos = 0\n            total, counts = get_counts(attr_vals, values)\n            aligns = [Qt.AlignTop | Qt.AlignHCenter,\n                      Qt.AlignRight | Qt.AlignVCenter,\n                      Qt.AlignBottom | Qt.AlignHCenter,\n                      Qt.AlignLeft | Qt.AlignVCenter]\n            align = aligns[side]\n            for i, val in enumerate(values):\n                if distributiondict[val] != 0:\n                    perc = counts[i] / float(total)\n                    rwidth = width * perc\n                    xs = [x0 + currpos + rwidth / 2,\n                          x0 - self.ATTR_VAL_OFFSET,\n                          x0 + currpos + rwidth / 2,\n                          x1 + self.ATTR_VAL_OFFSET]\n                    ys = [y1 + self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc,\n                          y0 - self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc]\n\n                    CanvasText(\n                        self.canvas, val, xs[side], ys[side], align,\n                        max_width=rwidth if side == 0 else None)\n                    space = height if side % 2 else width\n                    currpos += perc * space + spacing * (total_attrs - side)\n\n            xs = [x0 + (x1 - x0) / 2,\n                  x0 - max_ylabel_w1 - self.ATTR_VAL_OFFSET,\n                  x0 + (x1 - x0) / 2,\n                  x1 + max_ylabel_w2 + self.ATTR_VAL_OFFSET]\n            ys = [y1 + self.ATTR_VAL_OFFSET + self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2,\n                  y0 - self.ATTR_VAL_OFFSET - self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2]\n            CanvasText(\n                self.canvas, attr.name, xs[side], ys[side], align, bold=True,\n                vertical=side % 2)\n\n        def add_rect(x0, x1, y0, y1, condition,\n                     used_attrs, used_vals, attr_vals=\"\"):\n            area_index = len(self.areas)\n            x1 += (x0 == x1)\n            y1 += (y0 == y1)\n            # rectangles of width and height 1 are not shown - increase\n            y1 += (x1 - x0 + y1 - y0 == 2)\n            colors = class_var and [QColor(*col) for col in class_var.colors]\n\n            def select_area(_, ev):\n                self.select_area(area_index, ev)\n\n            def rect(x, y, w, h, z, pen_color=None, brush_color=None, **args):\n                if pen_color is None:\n                    return CanvasRectangle(\n                        self.canvas, x, y, w, h, z=z, onclick=select_area,\n                        **args)\n                if brush_color is None:\n                    brush_color = pen_color\n                return CanvasRectangle(\n                    self.canvas, x, y, w, h, pen_color, brush_color, z=z,\n                    onclick=select_area, **args)\n\n            def line(x1, y1, x2, y2):\n                r = QGraphicsLineItem(x1, y1, x2, y2, None)\n                self.canvas.addItem(r)\n                r.setPen(QPen(Qt.white, 2))\n                r.setZValue(30)\n\n            outer_rect = rect(x0, y0, x1 - x0, y1 - y0, 30)\n            self.areas.append((used_attrs, used_vals, outer_rect))\n            if not conditionaldict[attr_vals]:\n                return\n\n            if self.variable_color is None:\n                s = sum(apriori_dists[0])\n                expected = s * reduce(\n                    mul,\n                    (apriori_dists[i][used_vals[i]] / float(s)\n                     for i in range(len(used_vals))))\n                actual = conditionaldict[attr_vals]\n                pearson = float((actual - expected) / sqrt(expected))\n                if pearson == 0:\n                    ind = 0\n                else:\n                    ind = max(0, min(int(log(abs(pearson), 2)), 3))\n                color = [self.RED_COLORS, self.BLUE_COLORS][pearson > 0][ind]\n                rect(x0, y0, x1 - x0, y1 - y0, -20, color)\n                outer_rect.setToolTip(\n                    condition + \"<hr/>\" +\n                    \"Expected instances: %.1f<br>\"\n                    \"Actual instances: %d<br>\"\n                    \"Standardized (Pearson) residual: %.1f\" %\n                    (expected, conditionaldict[attr_vals], pearson))\n            else:\n                cls_values = get_variable_values_sorted(class_var)\n                prior = get_distribution(data, class_var.name)\n                total = 0\n                for i, value in enumerate(cls_values):\n                    val = conditionaldict[attr_vals + \"-\" + value]\n                    if val == 0:\n                        continue\n                    if i == len(cls_values) - 1:\n                        v = y1 - y0 - total\n                    else:\n                        v = ((y1 - y0) * val) / conditionaldict[attr_vals]\n                    rect(x0, y0 + total, x1 - x0, v, -20, colors[i])\n                    total += v\n\n                if self.use_boxes and \\\n                        abs(x1 - x0) > bar_width and abs(y1 - y0) > bar_width:\n                    total = 0\n                    line(x0 + bar_width, y0, x0 + bar_width, y1)\n                    n = sum(prior)\n                    for i, (val, color) in enumerate(zip(prior, colors)):\n                        if i == len(prior) - 1:\n                            h = y1 - y0 - total\n                        else:\n                            h = (y1 - y0) * val / n\n                        rect(x0, y0 + total, bar_width, h, 20, color)\n                        total += h\n\n                if conditionalsubsetdict:\n                    if conditionalsubsetdict[attr_vals]:\n                        if self.subset_indices is not None:\n                            line(x1 - bar_width, y0, x1 - bar_width, y1)\n                            total = 0\n                            n = conditionalsubsetdict[attr_vals]\n                            if n:\n                                for i, (cls, color) in \\\n                                        enumerate(zip(cls_values, colors)):\n                                    val = conditionalsubsetdict[\n                                        attr_vals + \"-\" + cls]\n                                    if val == 0:\n                                        continue\n                                    if i == len(prior) - 1:\n                                        v = y1 - y0 - total\n                                    else:\n                                        v = ((y1 - y0) * val) / n\n                                    rect(x1 - bar_width, y0 + total,\n                                         bar_width, v, 15, color)\n                                    total += v\n\n                actual = [conditionaldict[attr_vals + \"-\" + cls_values[i]]\n                          for i in range(len(prior))]\n                n_actual = sum(actual)\n                if n_actual > 0:\n                    apriori = [prior[key] for key in cls_values]\n                    n_apriori = sum(apriori)\n                    text = \"<br/>\".join(\n                        \"<b>%s</b>: %d / %.1f%% (Expected %.1f / %.1f%%)\" %\n                        (cls, act, 100.0 * act / n_actual,\n                         apr / n_apriori * n_actual, 100.0 * apr / n_apriori)\n                        for cls, act, apr in zip(cls_values, actual, apriori))\n                else:\n                    text = \"\"\n                outer_rect.setToolTip(\n                    \"{}<hr>Instances: {}<br><br>{}\".format(\n                        condition, n_actual, text[:-4]))\n\n        def create_legend():\n            if self.variable_color is None:\n                names = [\"<-8\", \"-8:-4\", \"-4:-2\", \"-2:2\", \"2:4\", \"4:8\", \">8\",\n                         \"Residuals:\"]\n                colors = self.RED_COLORS[::-1] + self.BLUE_COLORS[1:]\n                edges = repeat(Qt.black)\n            else:\n                names = get_variable_values_sorted(class_var)\n                edges = colors = [QColor(*col) for col in class_var.colors]\n\n            items = []\n            size = 8\n            for name, color, edgecolor in zip(names, colors, edges):\n                item = QGraphicsItemGroup()\n                item.addToGroup(\n                    CanvasRectangle(None, -size / 2, -size / 2, size, size,\n                                    edgecolor, color))\n                item.addToGroup(\n                    CanvasText(None, name, size, 0, Qt.AlignVCenter))\n                items.append(item)\n            return wrap_legend_items(\n                items, hspacing=20, vspacing=16 + size,\n                max_width=self.canvas_view.width() - xoff)\n\n        self.canvas.clear()\n        self.areas = []\n\n        data = self.discrete_data\n        if data is None:\n            return\n        attr_list = self.get_disc_attr_list()\n        class_var = data.domain.class_var\n        if class_var:\n            sql = isinstance(data, SqlTable)\n            name = not sql and data.name\n            # save class_var because it is removed in the next line\n            data = data[:, attr_list + [class_var]]\n            data.domain.class_var = class_var\n            if not sql:\n                data.name = name\n        else:\n            data = data[:, attr_list]\n        # TODO: check this\n        # data = Preprocessor_dropMissing(data)\n        if len(data) == 0:\n            self.Warning.no_valid_data()\n            return\n        else:\n            self.Warning.no_valid_data.clear()\n\n        attrs = [attr for attr in attr_list if not attr.values]\n        if attrs:\n            CanvasText(self.canvas,\n                       \"Feature {} has no values\".format(attrs[0]),\n                       (self.canvas_view.width() - 120) / 2,\n                       self.canvas_view.height() / 2)\n            return\n        if self.variable_color is None:\n            apriori_dists = [get_distribution(data, attr) for attr in attr_list]\n        else:\n            apriori_dists = []\n\n        def get_max_label_width(attr):\n            values = get_variable_values_sorted(attr)\n            maxw = 0\n            for val in values:\n                t = CanvasText(self.canvas, val, 0, 0, bold=0, show=False)\n                maxw = max(int(t.boundingRect().width()), maxw)\n            return maxw\n\n        xoff = 20\n\n        # get the maximum width of rectangle\n        width = 20\n        max_ylabel_w1 = max_ylabel_w2 = 0\n        if len(attr_list) > 1:\n            text = CanvasText(self.canvas, attr_list[1].name, bold=1, show=0)\n            max_ylabel_w1 = min(get_max_label_width(attr_list[1]), 150)\n            width = 5 + text.boundingRect().height() + \\\n                self.ATTR_VAL_OFFSET + max_ylabel_w1\n            xoff = width\n            if len(attr_list) == 4:\n                text = CanvasText(self.canvas, attr_list[3].name, bold=1, show=0)\n                max_ylabel_w2 = min(get_max_label_width(attr_list[3]), 150)\n                width += text.boundingRect().height() + \\\n                    self.ATTR_VAL_OFFSET + max_ylabel_w2 - 10\n\n        legend = create_legend()\n\n        # get the maximum height of rectangle\n        yoff = 45\n        legendoff = yoff + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35\n        square_size = min(self.canvas_view.width() - width - 20,\n                          self.canvas_view.height() - legendoff\n                          - legend.boundingRect().height())\n\n        if square_size < 0:\n            return  # canvas is too small to draw rectangles\n        self.canvas_view.setSceneRect(\n            0, 0, self.canvas_view.width(), self.canvas_view.height())\n\n        drawn_sides = set()\n        draw_positions = {}\n\n        conditionaldict, distributiondict = \\\n            get_conditional_distribution(data, attr_list)\n        conditionalsubsetdict = None\n        if self.subset_indices:\n            conditionalsubsetdict, _ = get_conditional_distribution(\n                self.discrete_data[self.subset_indices], attr_list)\n\n        # draw rectangles\n        draw_data(\n            attr_list, (xoff, xoff + square_size), (yoff, yoff + square_size),\n            0, \"\", len(attr_list), [], [])\n\n        self.canvas.addItem(legend)\n        legend.setPos(\n            xoff - legend.boundingRect().x()\n            + max(0, (square_size - legend.boundingRect().width()) / 2),\n            legendoff + square_size)\n        self.update_selection_rects()\n\n    @classmethod\n    def migrate_context(cls, context, version):\n        if version < 2:\n            settings.migrate_str_to_variable(context, none_placeholder=\"(None)\")",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 269,
                                "end_lineno": 951,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "Orange/widgets/visualize/owmosaic.py",
                            "type": "class",
                            "name": "OWMosaicDisplay",
                            "content": "class OWMosaicDisplay(OWWidget):\n    name = \"Mosaic Display\"\n    description = \"Display data in a mosaic plot.\"\n    icon = \"icons/MosaicDisplay.svg\"\n    priority = 220\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Table, default=True)\n        data_subset = Input(\"Data Subset\", Table)\n\n    class Outputs:\n        selected_data = Output(\"Selected Data\", Table, default=True)\n        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)\n\n    settingsHandler = DomainContextHandler()\n    vizrank = SettingProvider(MosaicVizRank)\n    settings_version = 2\n    use_boxes = Setting(True)\n    variable1 = ContextSetting(None)\n    variable2 = ContextSetting(None)\n    variable3 = ContextSetting(None)\n    variable4 = ContextSetting(None)\n    variable_color = ContextSetting(None)\n    selection = ContextSetting(set())\n\n    BAR_WIDTH = 5\n    SPACING = 4\n    ATTR_NAME_OFFSET = 20\n    ATTR_VAL_OFFSET = 3\n    BLUE_COLORS = [QColor(255, 255, 255), QColor(210, 210, 255),\n                   QColor(110, 110, 255), QColor(0, 0, 255)]\n    RED_COLORS = [QColor(255, 255, 255), QColor(255, 200, 200),\n                  QColor(255, 100, 100), QColor(255, 0, 0)]\n    graph_name = \"canvas\"\n\n    attrs_changed_manually = Signal(list)\n\n    class Warning(OWWidget.Warning):\n        incompatible_subset = Msg(\"Data subset is incompatible with Data\")\n        no_valid_data = Msg(\"No valid data\")\n        no_cont_selection_sql = \\\n            Msg(\"Selection of numeric features on SQL is not supported\")\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self.discrete_data = None\n        self.subset_data = None\n        self.subset_indices = None\n        self.__pending_selection = None\n\n        self.color_data = None\n\n        self.areas = []\n\n        self.canvas = QGraphicsScene()\n        self.canvas_view = ViewWithPress(\n            self.canvas, handler=self.clear_selection)\n        self.mainArea.layout().addWidget(self.canvas_view)\n        self.canvas_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setRenderHint(QPainter.Antialiasing)\n\n        box = gui.vBox(self.controlArea, box=True)\n        self.model_1 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE)\n        self.model_234 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(None)\")\n        self.attr_combos = [\n            gui.comboBox(\n                box, self, value=\"variable{}\".format(i),\n                orientation=Qt.Horizontal, contentsLength=12,\n                callback=self.attr_changed,\n                model=self.model_1 if i == 1 else self.model_234)\n            for i in range(1, 5)]\n        self.vizrank, self.vizrank_button = MosaicVizRank.add_vizrank(\n            box, self, \"Find Informative Mosaics\", self.set_attr)\n\n        box2 = gui.vBox(self.controlArea, box=\"Interior Coloring\")\n        self.color_model = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(Pearson residuals)\")\n        self.cb_attr_color = gui.comboBox(\n            box2, self, value=\"variable_color\",\n            orientation=Qt.Horizontal, contentsLength=12, labelWidth=50,\n            callback=self.set_color_data, model=self.color_model)\n        self.bar_button = gui.checkBox(\n            box2, self, 'use_boxes', label='Compare with total',\n            callback=self.update_graph)\n        gui.rubber(self.controlArea)\n\n    def sizeHint(self):\n        return QSize(720, 530)\n\n    def _get_discrete_data(self, data):\n        \"\"\"\n        Discretize continuous attributes.\n        Return None when there is no data, no rows, or no primitive attributes.\n        \"\"\"\n        if (data is None or\n                not len(data) or\n                not any(attr.is_discrete or attr.is_continuous\n                        for attr in chain(data.domain.variables,\n                                          data.domain.metas))):\n            return None\n        elif any(attr.is_continuous for attr in data.domain.variables):\n            return Discretize(\n                method=EqualFreq(n=4), remove_const=False, discretize_classes=True,\n                discretize_metas=True)(data)\n        else:\n            return data\n\n    def init_combos(self, data):\n        def set_combos(value):\n            self.model_1.set_domain(value)\n            self.model_234.set_domain(value)\n            self.color_model.set_domain(value)\n\n        if data is None:\n            set_combos(None)\n            self.variable1 = self.variable2 = self.variable3 \\\n                = self.variable4 = self.variable_color = None\n            return\n        set_combos(self.data.domain)\n\n        if len(self.model_1) > 0:\n            self.variable1 = self.model_1[0]\n            self.variable2 = self.model_1[min(1, len(self.model_1) - 1)]\n        self.variable3 = self.variable4 = None\n        self.variable_color = self.data.domain.class_var  # None is OK, too\n\n    def get_disc_attr_list(self):\n        return [self.discrete_data.domain[var.name]\n                for var in (self.variable1, self.variable2,\n                            self.variable3, self.variable4)\n                if var]\n\n    def set_attr(self, *attrs):\n        self.variable1, self.variable2, self.variable3, self.variable4 = [\n            attr and self.data.domain[attr.name] for attr in attrs]\n        self.reset_graph()\n\n    def attr_changed(self):\n        self.attrs_changed_manually.emit(self.get_disc_attr_list())\n        self.reset_graph()\n\n    def resizeEvent(self, e):\n        OWWidget.resizeEvent(self, e)\n        self.update_graph()\n\n    def showEvent(self, ev):\n        OWWidget.showEvent(self, ev)\n        self.update_graph()\n\n    @Inputs.data\n    def set_data(self, data):\n        if isinstance(data, SqlTable) and data.approx_len() > LARGE_TABLE:\n            data = data.sample_time(DEFAULT_SAMPLE_TIME)\n\n        self.closeContext()\n        self.data = data\n\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(\n            self.data is not None and len(self.data) > 1\n            and len(self.data.domain.attributes) >= 1)\n\n        if self.data is None:\n            self.discrete_data = None\n            self.init_combos(None)\n            return\n\n        self.init_combos(self.data)\n        self.openContext(self.data)\n        self.__pending_selection = self.selection\n\n    @Inputs.data_subset\n    def set_subset_data(self, data):\n        self.subset_data = data\n\n    # this is called by widget after setData and setSubsetData are called.\n    # this way the graph is updated only once\n    def handleNewSignals(self):\n        self.Warning.incompatible_subset.clear()\n        self.subset_indices = None\n        if self.data is not None and self.subset_data:\n            transformed = self.subset_data.transform(self.data.domain)\n            if np.all(np.isnan(transformed.X)) \\\n                    and np.all(np.isnan(transformed.Y)):\n                self.Warning.incompatible_subset()\n            else:\n                indices = {e.id for e in transformed}\n                self.subset_indices = [ex.id in indices for ex in self.data]\n        self.set_color_data()\n        self.reset_graph()\n\n    def clear_selection(self):\n        self.selection = set()\n        self.update_selection_rects()\n        self.send_selection()\n\n    def coloring_changed(self):\n        self.vizrank.coloring_changed()\n        self.update_graph()\n\n    def reset_graph(self):\n        self.clear_selection()\n        self.update_graph()\n        if self.__pending_selection is not None:\n            self.selection = self.__pending_selection\n            self.__pending_selection = None\n            self.update_selection_rects()\n            self.send_selection()\n\n    def set_color_data(self):\n        if self.data is None:\n            return\n        self.bar_button.setEnabled(self.variable_color is not None)\n        attrs = [v for v in self.model_1 if v and v is not self.variable_color]\n        domain = Domain(attrs, self.variable_color, None)\n        self.color_data = self.data.from_table(domain, self.data)\n        self.discrete_data = self._get_discrete_data(self.color_data)\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(True)\n        self.coloring_changed()\n\n    def update_selection_rects(self):\n        pens = (QPen(), QPen(Qt.black, 3, Qt.DotLine))\n        for i, (_, _, area) in enumerate(self.areas):\n            area.setPen(pens[i in self.selection])\n\n    def select_area(self, index, ev):\n        if ev.button() != Qt.LeftButton:\n            return\n        if ev.modifiers() & Qt.ControlModifier:\n            self.selection ^= {index}\n        else:\n            self.selection = {index}\n        self.update_selection_rects()\n        self.send_selection()\n\n    def send_selection(self):\n        if not self.selection or self.data is None:\n            self.Outputs.selected_data.send(None)\n            self.Outputs.annotated_data.send(\n                create_annotated_table(self.data, []))\n            return\n        filters = []\n        self.Warning.no_cont_selection_sql.clear()\n        if self.discrete_data is not self.data:\n            if isinstance(self.data, SqlTable):\n                self.Warning.no_cont_selection_sql()\n        for i in self.selection:\n            cols, vals, _ = self.areas[i]\n            filters.append(\n                filter.Values(\n                    filter.FilterDiscrete(col, [val])\n                    for col, val in zip(cols, vals)))\n        if len(filters) > 1:\n            filters = filter.Values(filters, conjunction=False)\n        else:\n            filters = filters[0]\n        selection = filters(self.discrete_data)\n        idset = set(selection.ids)\n        sel_idx = [i for i, id in enumerate(self.data.ids) if id in idset]\n        if self.discrete_data is not self.data:\n            selection = self.data[sel_idx]\n        self.Outputs.selected_data.send(selection)\n        self.Outputs.annotated_data.send(\n            create_annotated_table(self.data, sel_idx))\n\n    def send_report(self):\n        self.report_plot(self.canvas)\n\n    def update_graph(self):\n        spacing = self.SPACING\n        bar_width = self.BAR_WIDTH\n\n        def get_counts(attr_vals, values):\n            \"\"\"Calculate rectangles' widths; if all are 0, they are set to 1.\"\"\"\n            if not attr_vals:\n                counts = [conditionaldict[val] for val in values]\n            else:\n                counts = [conditionaldict[attr_vals + \"-\" + val]\n                          for val in values]\n            total = sum(counts)\n            if total == 0:\n                counts = [1] * len(values)\n                total = sum(counts)\n            return total, counts\n\n        def draw_data(attr_list, x0_x1, y0_y1, side, condition,\n                      total_attrs, used_attrs, used_vals, attr_vals=\"\"):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if conditionaldict[attr_vals] == 0:\n                add_rect(x0, x1, y0, y1, \"\",\n                         used_attrs, used_vals, attr_vals=attr_vals)\n                # store coordinates for later drawing of labels\n                draw_text(side, attr_list[0], (x0, x1), (y0, y1), total_attrs,\n                          used_attrs, used_vals, attr_vals)\n                return\n\n            attr = attr_list[0]\n            # how much smaller rectangles do we draw\n            edge = len(attr_list) * spacing\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]  # reverse names if necessary\n\n            if side % 2 == 0:  # we are drawing on the x axis\n                # remove the space needed for separating different attr. values\n                whole = max(0, (x1 - x0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (x1 - x0) / float(len(values) - 1)\n            else:  # we are drawing on the y axis\n                whole = max(0, (y1 - y0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (y1 - y0) / float(len(values) - 1)\n\n            total, counts = get_counts(attr_vals, values)\n\n            # when visualizing the third attribute and the first attribute has\n            # the last value, reverse the order in which the boxes are drawn;\n            # otherwise, if the last cell, nearest to the labels of the fourth\n            # attribute, is empty, we wouldn't be able to position the labels\n            valrange = list(range(len(values)))\n            if len(attr_list + used_attrs) == 4 and len(used_attrs) == 2:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] == attr1values[-1]:\n                    valrange = valrange[::-1]\n\n            for i in valrange:\n                start = i * edge + whole * float(sum(counts[:i]) / total)\n                end = i * edge + whole * float(sum(counts[:i + 1]) / total)\n                val = values[i]\n                htmlval = to_html(val)\n                newattrvals = attr_vals + \"-\" + val if attr_vals else val\n\n                tooltip = \"{}&nbsp;&nbsp;&nbsp;&nbsp;{}: <b>{}</b><br/>\".format(\n                    condition, attr.name, htmlval)\n                attrs = used_attrs + [attr]\n                vals = used_vals + [val]\n                args = attrs, vals, newattrvals\n                if side % 2 == 0:  # if we are moving horizontally\n                    if len(attr_list) == 1:\n                        add_rect(x0 + start, x0 + end, y0, y1, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0 + start, x0 + end), (y0, y1),\n                            side + 1, tooltip, total_attrs, *args)\n                else:\n                    if len(attr_list) == 1:\n                        add_rect(x0, x1, y0 + start, y0 + end, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0, x1), (y0 + start, y0 + end),\n                            side + 1, tooltip, total_attrs, *args)\n            draw_text(side, attr_list[0], (x0, x1), (y0, y1),\n                      total_attrs, used_attrs, used_vals, attr_vals)\n\n        def draw_text(side, attr, x0_x1, y0_y1,\n                      total_attrs, used_attrs, used_vals, attr_vals):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if side in drawn_sides:\n                return\n\n            # the text on the right will be drawn when we are processing\n            # visualization of the last value of the first attribute\n            if side == 3:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] != attr1values[-1]:\n                    return\n\n            if not conditionaldict[attr_vals]:\n                if side not in draw_positions:\n                    draw_positions[side] = (x0, x1, y0, y1)\n                return\n            else:\n                if side in draw_positions:\n                    # restore the positions of attribute values and name\n                    (x0, x1, y0, y1) = draw_positions[side]\n\n            drawn_sides.add(side)\n\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]\n\n            spaces = spacing * (total_attrs - side) * (len(values) - 1)\n            width = x1 - x0 - spaces * (side % 2 == 0)\n            height = y1 - y0 - spaces * (side % 2 == 1)\n\n            # calculate position of first attribute\n            currpos = 0\n            total, counts = get_counts(attr_vals, values)\n            aligns = [Qt.AlignTop | Qt.AlignHCenter,\n                      Qt.AlignRight | Qt.AlignVCenter,\n                      Qt.AlignBottom | Qt.AlignHCenter,\n                      Qt.AlignLeft | Qt.AlignVCenter]\n            align = aligns[side]\n            for i, val in enumerate(values):\n                if distributiondict[val] != 0:\n                    perc = counts[i] / float(total)\n                    rwidth = width * perc\n                    xs = [x0 + currpos + rwidth / 2,\n                          x0 - self.ATTR_VAL_OFFSET,\n                          x0 + currpos + rwidth / 2,\n                          x1 + self.ATTR_VAL_OFFSET]\n                    ys = [y1 + self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc,\n                          y0 - self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc]\n\n                    CanvasText(\n                        self.canvas, val, xs[side], ys[side], align,\n                        max_width=rwidth if side == 0 else None)\n                    space = height if side % 2 else width\n                    currpos += perc * space + spacing * (total_attrs - side)\n\n            xs = [x0 + (x1 - x0) / 2,\n                  x0 - max_ylabel_w1 - self.ATTR_VAL_OFFSET,\n                  x0 + (x1 - x0) / 2,\n                  x1 + max_ylabel_w2 + self.ATTR_VAL_OFFSET]\n            ys = [y1 + self.ATTR_VAL_OFFSET + self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2,\n                  y0 - self.ATTR_VAL_OFFSET - self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2]\n            CanvasText(\n                self.canvas, attr.name, xs[side], ys[side], align, bold=True,\n                vertical=side % 2)\n\n        def add_rect(x0, x1, y0, y1, condition,\n                     used_attrs, used_vals, attr_vals=\"\"):\n            area_index = len(self.areas)\n            x1 += (x0 == x1)\n            y1 += (y0 == y1)\n            # rectangles of width and height 1 are not shown - increase\n            y1 += (x1 - x0 + y1 - y0 == 2)\n            colors = class_var and [QColor(*col) for col in class_var.colors]\n\n            def select_area(_, ev):\n                self.select_area(area_index, ev)\n\n            def rect(x, y, w, h, z, pen_color=None, brush_color=None, **args):\n                if pen_color is None:\n                    return CanvasRectangle(\n                        self.canvas, x, y, w, h, z=z, onclick=select_area,\n                        **args)\n                if brush_color is None:\n                    brush_color = pen_color\n                return CanvasRectangle(\n                    self.canvas, x, y, w, h, pen_color, brush_color, z=z,\n                    onclick=select_area, **args)\n\n            def line(x1, y1, x2, y2):\n                r = QGraphicsLineItem(x1, y1, x2, y2, None)\n                self.canvas.addItem(r)\n                r.setPen(QPen(Qt.white, 2))\n                r.setZValue(30)\n\n            outer_rect = rect(x0, y0, x1 - x0, y1 - y0, 30)\n            self.areas.append((used_attrs, used_vals, outer_rect))\n            if not conditionaldict[attr_vals]:\n                return\n\n            if self.variable_color is None:\n                s = sum(apriori_dists[0])\n                expected = s * reduce(\n                    mul,\n                    (apriori_dists[i][used_vals[i]] / float(s)\n                     for i in range(len(used_vals))))\n                actual = conditionaldict[attr_vals]\n                pearson = float((actual - expected) / sqrt(expected))\n                if pearson == 0:\n                    ind = 0\n                else:\n                    ind = max(0, min(int(log(abs(pearson), 2)), 3))\n                color = [self.RED_COLORS, self.BLUE_COLORS][pearson > 0][ind]\n                rect(x0, y0, x1 - x0, y1 - y0, -20, color)\n                outer_rect.setToolTip(\n                    condition + \"<hr/>\" +\n                    \"Expected instances: %.1f<br>\"\n                    \"Actual instances: %d<br>\"\n                    \"Standardized (Pearson) residual: %.1f\" %\n                    (expected, conditionaldict[attr_vals], pearson))\n            else:\n                cls_values = get_variable_values_sorted(class_var)\n                prior = get_distribution(data, class_var.name)\n                total = 0\n                for i, value in enumerate(cls_values):\n                    val = conditionaldict[attr_vals + \"-\" + value]\n                    if val == 0:\n                        continue\n                    if i == len(cls_values) - 1:\n                        v = y1 - y0 - total\n                    else:\n                        v = ((y1 - y0) * val) / conditionaldict[attr_vals]\n                    rect(x0, y0 + total, x1 - x0, v, -20, colors[i])\n                    total += v\n\n                if self.use_boxes and \\\n                        abs(x1 - x0) > bar_width and abs(y1 - y0) > bar_width:\n                    total = 0\n                    line(x0 + bar_width, y0, x0 + bar_width, y1)\n                    n = sum(prior)\n                    for i, (val, color) in enumerate(zip(prior, colors)):\n                        if i == len(prior) - 1:\n                            h = y1 - y0 - total\n                        else:\n                            h = (y1 - y0) * val / n\n                        rect(x0, y0 + total, bar_width, h, 20, color)\n                        total += h\n\n                if conditionalsubsetdict:\n                    if conditionalsubsetdict[attr_vals]:\n                        if self.subset_indices is not None:\n                            line(x1 - bar_width, y0, x1 - bar_width, y1)\n                            total = 0\n                            n = conditionalsubsetdict[attr_vals]\n                            if n:\n                                for i, (cls, color) in \\\n                                        enumerate(zip(cls_values, colors)):\n                                    val = conditionalsubsetdict[\n                                        attr_vals + \"-\" + cls]\n                                    if val == 0:\n                                        continue\n                                    if i == len(prior) - 1:\n                                        v = y1 - y0 - total\n                                    else:\n                                        v = ((y1 - y0) * val) / n\n                                    rect(x1 - bar_width, y0 + total,\n                                         bar_width, v, 15, color)\n                                    total += v\n\n                actual = [conditionaldict[attr_vals + \"-\" + cls_values[i]]\n                          for i in range(len(prior))]\n                n_actual = sum(actual)\n                if n_actual > 0:\n                    apriori = [prior[key] for key in cls_values]\n                    n_apriori = sum(apriori)\n                    text = \"<br/>\".join(\n                        \"<b>%s</b>: %d / %.1f%% (Expected %.1f / %.1f%%)\" %\n                        (cls, act, 100.0 * act / n_actual,\n                         apr / n_apriori * n_actual, 100.0 * apr / n_apriori)\n                        for cls, act, apr in zip(cls_values, actual, apriori))\n                else:\n                    text = \"\"\n                outer_rect.setToolTip(\n                    \"{}<hr>Instances: {}<br><br>{}\".format(\n                        condition, n_actual, text[:-4]))\n\n        def create_legend():\n            if self.variable_color is None:\n                names = [\"<-8\", \"-8:-4\", \"-4:-2\", \"-2:2\", \"2:4\", \"4:8\", \">8\",\n                         \"Residuals:\"]\n                colors = self.RED_COLORS[::-1] + self.BLUE_COLORS[1:]\n                edges = repeat(Qt.black)\n            else:\n                names = get_variable_values_sorted(class_var)\n                edges = colors = [QColor(*col) for col in class_var.colors]\n\n            items = []\n            size = 8\n            for name, color, edgecolor in zip(names, colors, edges):\n                item = QGraphicsItemGroup()\n                item.addToGroup(\n                    CanvasRectangle(None, -size / 2, -size / 2, size, size,\n                                    edgecolor, color))\n                item.addToGroup(\n                    CanvasText(None, name, size, 0, Qt.AlignVCenter))\n                items.append(item)\n            return wrap_legend_items(\n                items, hspacing=20, vspacing=16 + size,\n                max_width=self.canvas_view.width() - xoff)\n\n        self.canvas.clear()\n        self.areas = []\n\n        data = self.discrete_data\n        if data is None:\n            return\n        attr_list = self.get_disc_attr_list()\n        class_var = data.domain.class_var\n        if class_var:\n            sql = isinstance(data, SqlTable)\n            name = not sql and data.name\n            # save class_var because it is removed in the next line\n            data = data[:, attr_list + [class_var]]\n            data.domain.class_var = class_var\n            if not sql:\n                data.name = name\n        else:\n            data = data[:, attr_list]\n        # TODO: check this\n        # data = Preprocessor_dropMissing(data)\n        if len(data) == 0:\n            self.Warning.no_valid_data()\n            return\n        else:\n            self.Warning.no_valid_data.clear()\n\n        attrs = [attr for attr in attr_list if not attr.values]\n        if attrs:\n            CanvasText(self.canvas,\n                       \"Feature {} has no values\".format(attrs[0]),\n                       (self.canvas_view.width() - 120) / 2,\n                       self.canvas_view.height() / 2)\n            return\n        if self.variable_color is None:\n            apriori_dists = [get_distribution(data, attr) for attr in attr_list]\n        else:\n            apriori_dists = []\n\n        def get_max_label_width(attr):\n            values = get_variable_values_sorted(attr)\n            maxw = 0\n            for val in values:\n                t = CanvasText(self.canvas, val, 0, 0, bold=0, show=False)\n                maxw = max(int(t.boundingRect().width()), maxw)\n            return maxw\n\n        xoff = 20\n\n        # get the maximum width of rectangle\n        width = 20\n        max_ylabel_w1 = max_ylabel_w2 = 0\n        if len(attr_list) > 1:\n            text = CanvasText(self.canvas, attr_list[1].name, bold=1, show=0)\n            max_ylabel_w1 = min(get_max_label_width(attr_list[1]), 150)\n            width = 5 + text.boundingRect().height() + \\\n                self.ATTR_VAL_OFFSET + max_ylabel_w1\n            xoff = width\n            if len(attr_list) == 4:\n                text = CanvasText(self.canvas, attr_list[3].name, bold=1, show=0)\n                max_ylabel_w2 = min(get_max_label_width(attr_list[3]), 150)\n                width += text.boundingRect().height() + \\\n                    self.ATTR_VAL_OFFSET + max_ylabel_w2 - 10\n\n        legend = create_legend()\n\n        # get the maximum height of rectangle\n        yoff = 45\n        legendoff = yoff + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35\n        square_size = min(self.canvas_view.width() - width - 20,\n                          self.canvas_view.height() - legendoff\n                          - legend.boundingRect().height())\n\n        if square_size < 0:\n            return  # canvas is too small to draw rectangles\n        self.canvas_view.setSceneRect(\n            0, 0, self.canvas_view.width(), self.canvas_view.height())\n\n        drawn_sides = set()\n        draw_positions = {}\n\n        conditionaldict, distributiondict = \\\n            get_conditional_distribution(data, attr_list)\n        conditionalsubsetdict = None\n        if self.subset_indices:\n            conditionalsubsetdict, _ = get_conditional_distribution(\n                self.discrete_data[self.subset_indices], attr_list)\n\n        # draw rectangles\n        draw_data(\n            attr_list, (xoff, xoff + square_size), (yoff, yoff + square_size),\n            0, \"\", len(attr_list), [], [])\n\n        self.canvas.addItem(legend)\n        legend.setPos(\n            xoff - legend.boundingRect().x()\n            + max(0, (square_size - legend.boundingRect().width()) / 2),\n            legendoff + square_size)\n        self.update_selection_rects()\n\n    @classmethod\n    def migrate_context(cls, context, version):\n        if version < 2:\n            settings.migrate_str_to_variable(context, none_placeholder=\"(None)\")",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 269,
                            "end_lineno": 951,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 442,
                            "length": 6
                        },
                        "new_range": {
                            "start": 443,
                            "length": 7
                        },
                        "section": "class OWMosaicDisplay(OWWidget):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        self.init_combos(self.data)",
                                "type": "context"
                            },
                            {
                                "content": "        self.openContext(self.data)",
                                "type": "context"
                            },
                            {
                                "content": "        self.__pending_selection = self.selection",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    @Inputs.data_subset",
                                "type": "context"
                            },
                            {
                                "content": "    def set_subset_data(self, data):",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/visualize/owmosaic.py",
                            "type": "method",
                            "name": "OWMosaicDisplay.set_data",
                            "content": "class OWMosaicDisplay(OWWidget):\n    name = \"Mosaic Display\"\n    description = \"Display data in a mosaic plot.\"\n    icon = \"icons/MosaicDisplay.svg\"\n    priority = 220\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Table, default=True)\n        data_subset = Input(\"Data Subset\", Table)\n\n    class Outputs:\n        selected_data = Output(\"Selected Data\", Table, default=True)\n        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)\n\n    settingsHandler = DomainContextHandler()\n    vizrank = SettingProvider(MosaicVizRank)\n    settings_version = 2\n    use_boxes = Setting(True)\n    variable1 = ContextSetting(None)\n    variable2 = ContextSetting(None)\n    variable3 = ContextSetting(None)\n    variable4 = ContextSetting(None)\n    variable_color = ContextSetting(None)\n    selection = ContextSetting(set())\n\n    BAR_WIDTH = 5\n    SPACING = 4\n    ATTR_NAME_OFFSET = 20\n    ATTR_VAL_OFFSET = 3\n    BLUE_COLORS = [QColor(255, 255, 255), QColor(210, 210, 255),\n                   QColor(110, 110, 255), QColor(0, 0, 255)]\n    RED_COLORS = [QColor(255, 255, 255), QColor(255, 200, 200),\n                  QColor(255, 100, 100), QColor(255, 0, 0)]\n    graph_name = \"canvas\"\n\n    attrs_changed_manually = Signal(list)\n\n    class Warning(OWWidget.Warning):\n        incompatible_subset = Msg(\"Data subset is incompatible with Data\")\n        no_valid_data = Msg(\"No valid data\")\n        no_cont_selection_sql = \\\n            Msg(\"Selection of numeric features on SQL is not supported\")\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self.discrete_data = None\n        self.subset_data = None\n        self.subset_indices = None\n        self.__pending_selection = None\n\n        self.color_data = None\n\n        self.areas = []\n\n        self.canvas = QGraphicsScene()\n        self.canvas_view = ViewWithPress(\n            self.canvas, handler=self.clear_selection)\n        self.mainArea.layout().addWidget(self.canvas_view)\n        self.canvas_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setRenderHint(QPainter.Antialiasing)\n\n        box = gui.vBox(self.controlArea, box=True)\n        self.model_1 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE)\n        self.model_234 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(None)\")\n        self.attr_combos = [\n            gui.comboBox(\n                box, self, value=\"variable{}\".format(i),\n                orientation=Qt.Horizontal, contentsLength=12,\n                callback=self.attr_changed,\n                model=self.model_1 if i == 1 else self.model_234)\n            for i in range(1, 5)]\n        self.vizrank, self.vizrank_button = MosaicVizRank.add_vizrank(\n            box, self, \"Find Informative Mosaics\", self.set_attr)\n\n        box2 = gui.vBox(self.controlArea, box=\"Interior Coloring\")\n        self.color_model = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(Pearson residuals)\")\n        self.cb_attr_color = gui.comboBox(\n            box2, self, value=\"variable_color\",\n            orientation=Qt.Horizontal, contentsLength=12, labelWidth=50,\n            callback=self.set_color_data, model=self.color_model)\n        self.bar_button = gui.checkBox(\n            box2, self, 'use_boxes', label='Compare with total',\n            callback=self.update_graph)\n        gui.rubber(self.controlArea)\n\n    def sizeHint(self):\n        return QSize(720, 530)\n\n    def _get_discrete_data(self, data):\n        \"\"\"\n        Discretize continuous attributes.\n        Return None when there is no data, no rows, or no primitive attributes.\n        \"\"\"\n        if (data is None or\n                not len(data) or\n                not any(attr.is_discrete or attr.is_continuous\n                        for attr in chain(data.domain.variables,\n                                          data.domain.metas))):\n            return None\n        elif any(attr.is_continuous for attr in data.domain.variables):\n            return Discretize(\n                method=EqualFreq(n=4), remove_const=False, discretize_classes=True,\n                discretize_metas=True)(data)\n        else:\n            return data\n\n    def init_combos(self, data):\n        def set_combos(value):\n            self.model_1.set_domain(value)\n            self.model_234.set_domain(value)\n            self.color_model.set_domain(value)\n\n        if data is None:\n            set_combos(None)\n            self.variable1 = self.variable2 = self.variable3 \\\n                = self.variable4 = self.variable_color = None\n            return\n        set_combos(self.data.domain)\n\n        if len(self.model_1) > 0:\n            self.variable1 = self.model_1[0]\n            self.variable2 = self.model_1[min(1, len(self.model_1) - 1)]\n        self.variable3 = self.variable4 = None\n        self.variable_color = self.data.domain.class_var  # None is OK, too\n\n    def get_disc_attr_list(self):\n        return [self.discrete_data.domain[var.name]\n                for var in (self.variable1, self.variable2,\n                            self.variable3, self.variable4)\n                if var]\n\n    def set_attr(self, *attrs):\n        self.variable1, self.variable2, self.variable3, self.variable4 = [\n            attr and self.data.domain[attr.name] for attr in attrs]\n        self.reset_graph()\n\n    def attr_changed(self):\n        self.attrs_changed_manually.emit(self.get_disc_attr_list())\n        self.reset_graph()\n\n    def resizeEvent(self, e):\n        OWWidget.resizeEvent(self, e)\n        self.update_graph()\n\n    def showEvent(self, ev):\n        OWWidget.showEvent(self, ev)\n        self.update_graph()\n\n    @Inputs.data\n    def set_data(self, data):\n        if isinstance(data, SqlTable) and data.approx_len() > LARGE_TABLE:\n            data = data.sample_time(DEFAULT_SAMPLE_TIME)\n\n        self.closeContext()\n        self.data = data\n\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(\n            self.data is not None and len(self.data) > 1\n            and len(self.data.domain.attributes) >= 1)\n\n        if self.data is None:\n            self.discrete_data = None\n            self.init_combos(None)\n            return\n\n        self.init_combos(self.data)\n        self.openContext(self.data)\n        self.__pending_selection = self.selection\n\n    @Inputs.data_subset\n    def set_subset_data(self, data):\n        self.subset_data = data\n\n    # this is called by widget after setData and setSubsetData are called.\n    # this way the graph is updated only once\n    def handleNewSignals(self):\n        self.Warning.incompatible_subset.clear()\n        self.subset_indices = None\n        if self.data is not None and self.subset_data:\n            transformed = self.subset_data.transform(self.data.domain)\n            if np.all(np.isnan(transformed.X)) \\\n                    and np.all(np.isnan(transformed.Y)):\n                self.Warning.incompatible_subset()\n            else:\n                indices = {e.id for e in transformed}\n                self.subset_indices = [ex.id in indices for ex in self.data]\n        self.set_color_data()\n        self.reset_graph()\n\n    def clear_selection(self):\n        self.selection = set()\n        self.update_selection_rects()\n        self.send_selection()\n\n    def coloring_changed(self):\n        self.vizrank.coloring_changed()\n        self.update_graph()\n\n    def reset_graph(self):\n        self.clear_selection()\n        self.update_graph()\n        if self.__pending_selection is not None:\n            self.selection = self.__pending_selection\n            self.__pending_selection = None\n            self.update_selection_rects()\n            self.send_selection()\n\n    def set_color_data(self):\n        if self.data is None:\n            return\n        self.bar_button.setEnabled(self.variable_color is not None)\n        attrs = [v for v in self.model_1 if v and v is not self.variable_color]\n        domain = Domain(attrs, self.variable_color, None)\n        self.color_data = self.data.from_table(domain, self.data)\n        self.discrete_data = self._get_discrete_data(self.color_data)\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(True)\n        self.coloring_changed()\n\n    def update_selection_rects(self):\n        pens = (QPen(), QPen(Qt.black, 3, Qt.DotLine))\n        for i, (_, _, area) in enumerate(self.areas):\n            area.setPen(pens[i in self.selection])\n\n    def select_area(self, index, ev):\n        if ev.button() != Qt.LeftButton:\n            return\n        if ev.modifiers() & Qt.ControlModifier:\n            self.selection ^= {index}\n        else:\n            self.selection = {index}\n        self.update_selection_rects()\n        self.send_selection()\n\n    def send_selection(self):\n        if not self.selection or self.data is None:\n            self.Outputs.selected_data.send(None)\n            self.Outputs.annotated_data.send(\n                create_annotated_table(self.data, []))\n            return\n        filters = []\n        self.Warning.no_cont_selection_sql.clear()\n        if self.discrete_data is not self.data:\n            if isinstance(self.data, SqlTable):\n                self.Warning.no_cont_selection_sql()\n        for i in self.selection:\n            cols, vals, _ = self.areas[i]\n            filters.append(\n                filter.Values(\n                    filter.FilterDiscrete(col, [val])\n                    for col, val in zip(cols, vals)))\n        if len(filters) > 1:\n            filters = filter.Values(filters, conjunction=False)\n        else:\n            filters = filters[0]\n        selection = filters(self.discrete_data)\n        idset = set(selection.ids)\n        sel_idx = [i for i, id in enumerate(self.data.ids) if id in idset]\n        if self.discrete_data is not self.data:\n            selection = self.data[sel_idx]\n        self.Outputs.selected_data.send(selection)\n        self.Outputs.annotated_data.send(\n            create_annotated_table(self.data, sel_idx))\n\n    def send_report(self):\n        self.report_plot(self.canvas)\n\n    def update_graph(self):\n        spacing = self.SPACING\n        bar_width = self.BAR_WIDTH\n\n        def get_counts(attr_vals, values):\n            \"\"\"Calculate rectangles' widths; if all are 0, they are set to 1.\"\"\"\n            if not attr_vals:\n                counts = [conditionaldict[val] for val in values]\n            else:\n                counts = [conditionaldict[attr_vals + \"-\" + val]\n                          for val in values]\n            total = sum(counts)\n            if total == 0:\n                counts = [1] * len(values)\n                total = sum(counts)\n            return total, counts\n\n        def draw_data(attr_list, x0_x1, y0_y1, side, condition,\n                      total_attrs, used_attrs, used_vals, attr_vals=\"\"):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if conditionaldict[attr_vals] == 0:\n                add_rect(x0, x1, y0, y1, \"\",\n                         used_attrs, used_vals, attr_vals=attr_vals)\n                # store coordinates for later drawing of labels\n                draw_text(side, attr_list[0], (x0, x1), (y0, y1), total_attrs,\n                          used_attrs, used_vals, attr_vals)\n                return\n\n            attr = attr_list[0]\n            # how much smaller rectangles do we draw\n            edge = len(attr_list) * spacing\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]  # reverse names if necessary\n\n            if side % 2 == 0:  # we are drawing on the x axis\n                # remove the space needed for separating different attr. values\n                whole = max(0, (x1 - x0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (x1 - x0) / float(len(values) - 1)\n            else:  # we are drawing on the y axis\n                whole = max(0, (y1 - y0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (y1 - y0) / float(len(values) - 1)\n\n            total, counts = get_counts(attr_vals, values)\n\n            # when visualizing the third attribute and the first attribute has\n            # the last value, reverse the order in which the boxes are drawn;\n            # otherwise, if the last cell, nearest to the labels of the fourth\n            # attribute, is empty, we wouldn't be able to position the labels\n            valrange = list(range(len(values)))\n            if len(attr_list + used_attrs) == 4 and len(used_attrs) == 2:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] == attr1values[-1]:\n                    valrange = valrange[::-1]\n\n            for i in valrange:\n                start = i * edge + whole * float(sum(counts[:i]) / total)\n                end = i * edge + whole * float(sum(counts[:i + 1]) / total)\n                val = values[i]\n                htmlval = to_html(val)\n                newattrvals = attr_vals + \"-\" + val if attr_vals else val\n\n                tooltip = \"{}&nbsp;&nbsp;&nbsp;&nbsp;{}: <b>{}</b><br/>\".format(\n                    condition, attr.name, htmlval)\n                attrs = used_attrs + [attr]\n                vals = used_vals + [val]\n                args = attrs, vals, newattrvals\n                if side % 2 == 0:  # if we are moving horizontally\n                    if len(attr_list) == 1:\n                        add_rect(x0 + start, x0 + end, y0, y1, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0 + start, x0 + end), (y0, y1),\n                            side + 1, tooltip, total_attrs, *args)\n                else:\n                    if len(attr_list) == 1:\n                        add_rect(x0, x1, y0 + start, y0 + end, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0, x1), (y0 + start, y0 + end),\n                            side + 1, tooltip, total_attrs, *args)\n            draw_text(side, attr_list[0], (x0, x1), (y0, y1),\n                      total_attrs, used_attrs, used_vals, attr_vals)\n\n        def draw_text(side, attr, x0_x1, y0_y1,\n                      total_attrs, used_attrs, used_vals, attr_vals):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if side in drawn_sides:\n                return\n\n            # the text on the right will be drawn when we are processing\n            # visualization of the last value of the first attribute\n            if side == 3:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] != attr1values[-1]:\n                    return\n\n            if not conditionaldict[attr_vals]:\n                if side not in draw_positions:\n                    draw_positions[side] = (x0, x1, y0, y1)\n                return\n            else:\n                if side in draw_positions:\n                    # restore the positions of attribute values and name\n                    (x0, x1, y0, y1) = draw_positions[side]\n\n            drawn_sides.add(side)\n\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]\n\n            spaces = spacing * (total_attrs - side) * (len(values) - 1)\n            width = x1 - x0 - spaces * (side % 2 == 0)\n            height = y1 - y0 - spaces * (side % 2 == 1)\n\n            # calculate position of first attribute\n            currpos = 0\n            total, counts = get_counts(attr_vals, values)\n            aligns = [Qt.AlignTop | Qt.AlignHCenter,\n                      Qt.AlignRight | Qt.AlignVCenter,\n                      Qt.AlignBottom | Qt.AlignHCenter,\n                      Qt.AlignLeft | Qt.AlignVCenter]\n            align = aligns[side]\n            for i, val in enumerate(values):\n                if distributiondict[val] != 0:\n                    perc = counts[i] / float(total)\n                    rwidth = width * perc\n                    xs = [x0 + currpos + rwidth / 2,\n                          x0 - self.ATTR_VAL_OFFSET,\n                          x0 + currpos + rwidth / 2,\n                          x1 + self.ATTR_VAL_OFFSET]\n                    ys = [y1 + self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc,\n                          y0 - self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc]\n\n                    CanvasText(\n                        self.canvas, val, xs[side], ys[side], align,\n                        max_width=rwidth if side == 0 else None)\n                    space = height if side % 2 else width\n                    currpos += perc * space + spacing * (total_attrs - side)\n\n            xs = [x0 + (x1 - x0) / 2,\n                  x0 - max_ylabel_w1 - self.ATTR_VAL_OFFSET,\n                  x0 + (x1 - x0) / 2,\n                  x1 + max_ylabel_w2 + self.ATTR_VAL_OFFSET]\n            ys = [y1 + self.ATTR_VAL_OFFSET + self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2,\n                  y0 - self.ATTR_VAL_OFFSET - self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2]\n            CanvasText(\n                self.canvas, attr.name, xs[side], ys[side], align, bold=True,\n                vertical=side % 2)\n\n        def add_rect(x0, x1, y0, y1, condition,\n                     used_attrs, used_vals, attr_vals=\"\"):\n            area_index = len(self.areas)\n            x1 += (x0 == x1)\n            y1 += (y0 == y1)\n            # rectangles of width and height 1 are not shown - increase\n            y1 += (x1 - x0 + y1 - y0 == 2)\n            colors = class_var and [QColor(*col) for col in class_var.colors]\n\n            def select_area(_, ev):\n                self.select_area(area_index, ev)\n\n            def rect(x, y, w, h, z, pen_color=None, brush_color=None, **args):\n                if pen_color is None:\n                    return CanvasRectangle(\n                        self.canvas, x, y, w, h, z=z, onclick=select_area,\n                        **args)\n                if brush_color is None:\n                    brush_color = pen_color\n                return CanvasRectangle(\n                    self.canvas, x, y, w, h, pen_color, brush_color, z=z,\n                    onclick=select_area, **args)\n\n            def line(x1, y1, x2, y2):\n                r = QGraphicsLineItem(x1, y1, x2, y2, None)\n                self.canvas.addItem(r)\n                r.setPen(QPen(Qt.white, 2))\n                r.setZValue(30)\n\n            outer_rect = rect(x0, y0, x1 - x0, y1 - y0, 30)\n            self.areas.append((used_attrs, used_vals, outer_rect))\n            if not conditionaldict[attr_vals]:\n                return\n\n            if self.variable_color is None:\n                s = sum(apriori_dists[0])\n                expected = s * reduce(\n                    mul,\n                    (apriori_dists[i][used_vals[i]] / float(s)\n                     for i in range(len(used_vals))))\n                actual = conditionaldict[attr_vals]\n                pearson = float((actual - expected) / sqrt(expected))\n                if pearson == 0:\n                    ind = 0\n                else:\n                    ind = max(0, min(int(log(abs(pearson), 2)), 3))\n                color = [self.RED_COLORS, self.BLUE_COLORS][pearson > 0][ind]\n                rect(x0, y0, x1 - x0, y1 - y0, -20, color)\n                outer_rect.setToolTip(\n                    condition + \"<hr/>\" +\n                    \"Expected instances: %.1f<br>\"\n                    \"Actual instances: %d<br>\"\n                    \"Standardized (Pearson) residual: %.1f\" %\n                    (expected, conditionaldict[attr_vals], pearson))\n            else:\n                cls_values = get_variable_values_sorted(class_var)\n                prior = get_distribution(data, class_var.name)\n                total = 0\n                for i, value in enumerate(cls_values):\n                    val = conditionaldict[attr_vals + \"-\" + value]\n                    if val == 0:\n                        continue\n                    if i == len(cls_values) - 1:\n                        v = y1 - y0 - total\n                    else:\n                        v = ((y1 - y0) * val) / conditionaldict[attr_vals]\n                    rect(x0, y0 + total, x1 - x0, v, -20, colors[i])\n                    total += v\n\n                if self.use_boxes and \\\n                        abs(x1 - x0) > bar_width and abs(y1 - y0) > bar_width:\n                    total = 0\n                    line(x0 + bar_width, y0, x0 + bar_width, y1)\n                    n = sum(prior)\n                    for i, (val, color) in enumerate(zip(prior, colors)):\n                        if i == len(prior) - 1:\n                            h = y1 - y0 - total\n                        else:\n                            h = (y1 - y0) * val / n\n                        rect(x0, y0 + total, bar_width, h, 20, color)\n                        total += h\n\n                if conditionalsubsetdict:\n                    if conditionalsubsetdict[attr_vals]:\n                        if self.subset_indices is not None:\n                            line(x1 - bar_width, y0, x1 - bar_width, y1)\n                            total = 0\n                            n = conditionalsubsetdict[attr_vals]\n                            if n:\n                                for i, (cls, color) in \\\n                                        enumerate(zip(cls_values, colors)):\n                                    val = conditionalsubsetdict[\n                                        attr_vals + \"-\" + cls]\n                                    if val == 0:\n                                        continue\n                                    if i == len(prior) - 1:\n                                        v = y1 - y0 - total\n                                    else:\n                                        v = ((y1 - y0) * val) / n\n                                    rect(x1 - bar_width, y0 + total,\n                                         bar_width, v, 15, color)\n                                    total += v\n\n                actual = [conditionaldict[attr_vals + \"-\" + cls_values[i]]\n                          for i in range(len(prior))]\n                n_actual = sum(actual)\n                if n_actual > 0:\n                    apriori = [prior[key] for key in cls_values]\n                    n_apriori = sum(apriori)\n                    text = \"<br/>\".join(\n                        \"<b>%s</b>: %d / %.1f%% (Expected %.1f / %.1f%%)\" %\n                        (cls, act, 100.0 * act / n_actual,\n                         apr / n_apriori * n_actual, 100.0 * apr / n_apriori)\n                        for cls, act, apr in zip(cls_values, actual, apriori))\n                else:\n                    text = \"\"\n                outer_rect.setToolTip(\n                    \"{}<hr>Instances: {}<br><br>{}\".format(\n                        condition, n_actual, text[:-4]))\n\n        def create_legend():\n            if self.variable_color is None:\n                names = [\"<-8\", \"-8:-4\", \"-4:-2\", \"-2:2\", \"2:4\", \"4:8\", \">8\",\n                         \"Residuals:\"]\n                colors = self.RED_COLORS[::-1] + self.BLUE_COLORS[1:]\n                edges = repeat(Qt.black)\n            else:\n                names = get_variable_values_sorted(class_var)\n                edges = colors = [QColor(*col) for col in class_var.colors]\n\n            items = []\n            size = 8\n            for name, color, edgecolor in zip(names, colors, edges):\n                item = QGraphicsItemGroup()\n                item.addToGroup(\n                    CanvasRectangle(None, -size / 2, -size / 2, size, size,\n                                    edgecolor, color))\n                item.addToGroup(\n                    CanvasText(None, name, size, 0, Qt.AlignVCenter))\n                items.append(item)\n            return wrap_legend_items(\n                items, hspacing=20, vspacing=16 + size,\n                max_width=self.canvas_view.width() - xoff)\n\n        self.canvas.clear()\n        self.areas = []\n\n        data = self.discrete_data\n        if data is None:\n            return\n        attr_list = self.get_disc_attr_list()\n        class_var = data.domain.class_var\n        if class_var:\n            sql = isinstance(data, SqlTable)\n            name = not sql and data.name\n            # save class_var because it is removed in the next line\n            data = data[:, attr_list + [class_var]]\n            data.domain.class_var = class_var\n            if not sql:\n                data.name = name\n        else:\n            data = data[:, attr_list]\n        # TODO: check this\n        # data = Preprocessor_dropMissing(data)\n        if len(data) == 0:\n            self.Warning.no_valid_data()\n            return\n        else:\n            self.Warning.no_valid_data.clear()\n\n        attrs = [attr for attr in attr_list if not attr.values]\n        if attrs:\n            CanvasText(self.canvas,\n                       \"Feature {} has no values\".format(attrs[0]),\n                       (self.canvas_view.width() - 120) / 2,\n                       self.canvas_view.height() / 2)\n            return\n        if self.variable_color is None:\n            apriori_dists = [get_distribution(data, attr) for attr in attr_list]\n        else:\n            apriori_dists = []\n\n        def get_max_label_width(attr):\n            values = get_variable_values_sorted(attr)\n            maxw = 0\n            for val in values:\n                t = CanvasText(self.canvas, val, 0, 0, bold=0, show=False)\n                maxw = max(int(t.boundingRect().width()), maxw)\n            return maxw\n\n        xoff = 20\n\n        # get the maximum width of rectangle\n        width = 20\n        max_ylabel_w1 = max_ylabel_w2 = 0\n        if len(attr_list) > 1:\n            text = CanvasText(self.canvas, attr_list[1].name, bold=1, show=0)\n            max_ylabel_w1 = min(get_max_label_width(attr_list[1]), 150)\n            width = 5 + text.boundingRect().height() + \\\n                self.ATTR_VAL_OFFSET + max_ylabel_w1\n            xoff = width\n            if len(attr_list) == 4:\n                text = CanvasText(self.canvas, attr_list[3].name, bold=1, show=0)\n                max_ylabel_w2 = min(get_max_label_width(attr_list[3]), 150)\n                width += text.boundingRect().height() + \\\n                    self.ATTR_VAL_OFFSET + max_ylabel_w2 - 10\n\n        legend = create_legend()\n\n        # get the maximum height of rectangle\n        yoff = 45\n        legendoff = yoff + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35\n        square_size = min(self.canvas_view.width() - width - 20,\n                          self.canvas_view.height() - legendoff\n                          - legend.boundingRect().height())\n\n        if square_size < 0:\n            return  # canvas is too small to draw rectangles\n        self.canvas_view.setSceneRect(\n            0, 0, self.canvas_view.width(), self.canvas_view.height())\n\n        drawn_sides = set()\n        draw_positions = {}\n\n        conditionaldict, distributiondict = \\\n            get_conditional_distribution(data, attr_list)\n        conditionalsubsetdict = None\n        if self.subset_indices:\n            conditionalsubsetdict, _ = get_conditional_distribution(\n                self.discrete_data[self.subset_indices], attr_list)\n\n        # draw rectangles\n        draw_data(\n            attr_list, (xoff, xoff + square_size), (yoff, yoff + square_size),\n            0, \"\", len(attr_list), [], [])\n\n        self.canvas.addItem(legend)\n        legend.setPos(\n            xoff - legend.boundingRect().x()\n            + max(0, (square_size - legend.boundingRect().width()) / 2),\n            legendoff + square_size)\n        self.update_selection_rects()\n\n    @classmethod\n    def migrate_context(cls, context, version):\n        if version < 2:\n            settings.migrate_str_to_variable(context, none_placeholder=\"(None)\")",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 427,
                            "end_lineno": 446,
                            "parent": {
                                "file_name": "Orange/widgets/visualize/owmosaic.py",
                                "type": "class",
                                "name": "OWMosaicDisplay",
                                "content": "class OWMosaicDisplay(OWWidget):\n    name = \"Mosaic Display\"\n    description = \"Display data in a mosaic plot.\"\n    icon = \"icons/MosaicDisplay.svg\"\n    priority = 220\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Table, default=True)\n        data_subset = Input(\"Data Subset\", Table)\n\n    class Outputs:\n        selected_data = Output(\"Selected Data\", Table, default=True)\n        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)\n\n    settingsHandler = DomainContextHandler()\n    vizrank = SettingProvider(MosaicVizRank)\n    settings_version = 2\n    use_boxes = Setting(True)\n    variable1 = ContextSetting(None)\n    variable2 = ContextSetting(None)\n    variable3 = ContextSetting(None)\n    variable4 = ContextSetting(None)\n    variable_color = ContextSetting(None)\n    selection = ContextSetting(set())\n\n    BAR_WIDTH = 5\n    SPACING = 4\n    ATTR_NAME_OFFSET = 20\n    ATTR_VAL_OFFSET = 3\n    BLUE_COLORS = [QColor(255, 255, 255), QColor(210, 210, 255),\n                   QColor(110, 110, 255), QColor(0, 0, 255)]\n    RED_COLORS = [QColor(255, 255, 255), QColor(255, 200, 200),\n                  QColor(255, 100, 100), QColor(255, 0, 0)]\n    graph_name = \"canvas\"\n\n    attrs_changed_manually = Signal(list)\n\n    class Warning(OWWidget.Warning):\n        incompatible_subset = Msg(\"Data subset is incompatible with Data\")\n        no_valid_data = Msg(\"No valid data\")\n        no_cont_selection_sql = \\\n            Msg(\"Selection of numeric features on SQL is not supported\")\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self.discrete_data = None\n        self.subset_data = None\n        self.subset_indices = None\n        self.__pending_selection = None\n\n        self.color_data = None\n\n        self.areas = []\n\n        self.canvas = QGraphicsScene()\n        self.canvas_view = ViewWithPress(\n            self.canvas, handler=self.clear_selection)\n        self.mainArea.layout().addWidget(self.canvas_view)\n        self.canvas_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setRenderHint(QPainter.Antialiasing)\n\n        box = gui.vBox(self.controlArea, box=True)\n        self.model_1 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE)\n        self.model_234 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(None)\")\n        self.attr_combos = [\n            gui.comboBox(\n                box, self, value=\"variable{}\".format(i),\n                orientation=Qt.Horizontal, contentsLength=12,\n                callback=self.attr_changed,\n                model=self.model_1 if i == 1 else self.model_234)\n            for i in range(1, 5)]\n        self.vizrank, self.vizrank_button = MosaicVizRank.add_vizrank(\n            box, self, \"Find Informative Mosaics\", self.set_attr)\n\n        box2 = gui.vBox(self.controlArea, box=\"Interior Coloring\")\n        self.color_model = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(Pearson residuals)\")\n        self.cb_attr_color = gui.comboBox(\n            box2, self, value=\"variable_color\",\n            orientation=Qt.Horizontal, contentsLength=12, labelWidth=50,\n            callback=self.set_color_data, model=self.color_model)\n        self.bar_button = gui.checkBox(\n            box2, self, 'use_boxes', label='Compare with total',\n            callback=self.update_graph)\n        gui.rubber(self.controlArea)\n\n    def sizeHint(self):\n        return QSize(720, 530)\n\n    def _get_discrete_data(self, data):\n        \"\"\"\n        Discretize continuous attributes.\n        Return None when there is no data, no rows, or no primitive attributes.\n        \"\"\"\n        if (data is None or\n                not len(data) or\n                not any(attr.is_discrete or attr.is_continuous\n                        for attr in chain(data.domain.variables,\n                                          data.domain.metas))):\n            return None\n        elif any(attr.is_continuous for attr in data.domain.variables):\n            return Discretize(\n                method=EqualFreq(n=4), remove_const=False, discretize_classes=True,\n                discretize_metas=True)(data)\n        else:\n            return data\n\n    def init_combos(self, data):\n        def set_combos(value):\n            self.model_1.set_domain(value)\n            self.model_234.set_domain(value)\n            self.color_model.set_domain(value)\n\n        if data is None:\n            set_combos(None)\n            self.variable1 = self.variable2 = self.variable3 \\\n                = self.variable4 = self.variable_color = None\n            return\n        set_combos(self.data.domain)\n\n        if len(self.model_1) > 0:\n            self.variable1 = self.model_1[0]\n            self.variable2 = self.model_1[min(1, len(self.model_1) - 1)]\n        self.variable3 = self.variable4 = None\n        self.variable_color = self.data.domain.class_var  # None is OK, too\n\n    def get_disc_attr_list(self):\n        return [self.discrete_data.domain[var.name]\n                for var in (self.variable1, self.variable2,\n                            self.variable3, self.variable4)\n                if var]\n\n    def set_attr(self, *attrs):\n        self.variable1, self.variable2, self.variable3, self.variable4 = [\n            attr and self.data.domain[attr.name] for attr in attrs]\n        self.reset_graph()\n\n    def attr_changed(self):\n        self.attrs_changed_manually.emit(self.get_disc_attr_list())\n        self.reset_graph()\n\n    def resizeEvent(self, e):\n        OWWidget.resizeEvent(self, e)\n        self.update_graph()\n\n    def showEvent(self, ev):\n        OWWidget.showEvent(self, ev)\n        self.update_graph()\n\n    @Inputs.data\n    def set_data(self, data):\n        if isinstance(data, SqlTable) and data.approx_len() > LARGE_TABLE:\n            data = data.sample_time(DEFAULT_SAMPLE_TIME)\n\n        self.closeContext()\n        self.data = data\n\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(\n            self.data is not None and len(self.data) > 1\n            and len(self.data.domain.attributes) >= 1)\n\n        if self.data is None:\n            self.discrete_data = None\n            self.init_combos(None)\n            return\n\n        self.init_combos(self.data)\n        self.openContext(self.data)\n        self.__pending_selection = self.selection\n\n    @Inputs.data_subset\n    def set_subset_data(self, data):\n        self.subset_data = data\n\n    # this is called by widget after setData and setSubsetData are called.\n    # this way the graph is updated only once\n    def handleNewSignals(self):\n        self.Warning.incompatible_subset.clear()\n        self.subset_indices = None\n        if self.data is not None and self.subset_data:\n            transformed = self.subset_data.transform(self.data.domain)\n            if np.all(np.isnan(transformed.X)) \\\n                    and np.all(np.isnan(transformed.Y)):\n                self.Warning.incompatible_subset()\n            else:\n                indices = {e.id for e in transformed}\n                self.subset_indices = [ex.id in indices for ex in self.data]\n        self.set_color_data()\n        self.reset_graph()\n\n    def clear_selection(self):\n        self.selection = set()\n        self.update_selection_rects()\n        self.send_selection()\n\n    def coloring_changed(self):\n        self.vizrank.coloring_changed()\n        self.update_graph()\n\n    def reset_graph(self):\n        self.clear_selection()\n        self.update_graph()\n        if self.__pending_selection is not None:\n            self.selection = self.__pending_selection\n            self.__pending_selection = None\n            self.update_selection_rects()\n            self.send_selection()\n\n    def set_color_data(self):\n        if self.data is None:\n            return\n        self.bar_button.setEnabled(self.variable_color is not None)\n        attrs = [v for v in self.model_1 if v and v is not self.variable_color]\n        domain = Domain(attrs, self.variable_color, None)\n        self.color_data = self.data.from_table(domain, self.data)\n        self.discrete_data = self._get_discrete_data(self.color_data)\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(True)\n        self.coloring_changed()\n\n    def update_selection_rects(self):\n        pens = (QPen(), QPen(Qt.black, 3, Qt.DotLine))\n        for i, (_, _, area) in enumerate(self.areas):\n            area.setPen(pens[i in self.selection])\n\n    def select_area(self, index, ev):\n        if ev.button() != Qt.LeftButton:\n            return\n        if ev.modifiers() & Qt.ControlModifier:\n            self.selection ^= {index}\n        else:\n            self.selection = {index}\n        self.update_selection_rects()\n        self.send_selection()\n\n    def send_selection(self):\n        if not self.selection or self.data is None:\n            self.Outputs.selected_data.send(None)\n            self.Outputs.annotated_data.send(\n                create_annotated_table(self.data, []))\n            return\n        filters = []\n        self.Warning.no_cont_selection_sql.clear()\n        if self.discrete_data is not self.data:\n            if isinstance(self.data, SqlTable):\n                self.Warning.no_cont_selection_sql()\n        for i in self.selection:\n            cols, vals, _ = self.areas[i]\n            filters.append(\n                filter.Values(\n                    filter.FilterDiscrete(col, [val])\n                    for col, val in zip(cols, vals)))\n        if len(filters) > 1:\n            filters = filter.Values(filters, conjunction=False)\n        else:\n            filters = filters[0]\n        selection = filters(self.discrete_data)\n        idset = set(selection.ids)\n        sel_idx = [i for i, id in enumerate(self.data.ids) if id in idset]\n        if self.discrete_data is not self.data:\n            selection = self.data[sel_idx]\n        self.Outputs.selected_data.send(selection)\n        self.Outputs.annotated_data.send(\n            create_annotated_table(self.data, sel_idx))\n\n    def send_report(self):\n        self.report_plot(self.canvas)\n\n    def update_graph(self):\n        spacing = self.SPACING\n        bar_width = self.BAR_WIDTH\n\n        def get_counts(attr_vals, values):\n            \"\"\"Calculate rectangles' widths; if all are 0, they are set to 1.\"\"\"\n            if not attr_vals:\n                counts = [conditionaldict[val] for val in values]\n            else:\n                counts = [conditionaldict[attr_vals + \"-\" + val]\n                          for val in values]\n            total = sum(counts)\n            if total == 0:\n                counts = [1] * len(values)\n                total = sum(counts)\n            return total, counts\n\n        def draw_data(attr_list, x0_x1, y0_y1, side, condition,\n                      total_attrs, used_attrs, used_vals, attr_vals=\"\"):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if conditionaldict[attr_vals] == 0:\n                add_rect(x0, x1, y0, y1, \"\",\n                         used_attrs, used_vals, attr_vals=attr_vals)\n                # store coordinates for later drawing of labels\n                draw_text(side, attr_list[0], (x0, x1), (y0, y1), total_attrs,\n                          used_attrs, used_vals, attr_vals)\n                return\n\n            attr = attr_list[0]\n            # how much smaller rectangles do we draw\n            edge = len(attr_list) * spacing\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]  # reverse names if necessary\n\n            if side % 2 == 0:  # we are drawing on the x axis\n                # remove the space needed for separating different attr. values\n                whole = max(0, (x1 - x0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (x1 - x0) / float(len(values) - 1)\n            else:  # we are drawing on the y axis\n                whole = max(0, (y1 - y0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (y1 - y0) / float(len(values) - 1)\n\n            total, counts = get_counts(attr_vals, values)\n\n            # when visualizing the third attribute and the first attribute has\n            # the last value, reverse the order in which the boxes are drawn;\n            # otherwise, if the last cell, nearest to the labels of the fourth\n            # attribute, is empty, we wouldn't be able to position the labels\n            valrange = list(range(len(values)))\n            if len(attr_list + used_attrs) == 4 and len(used_attrs) == 2:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] == attr1values[-1]:\n                    valrange = valrange[::-1]\n\n            for i in valrange:\n                start = i * edge + whole * float(sum(counts[:i]) / total)\n                end = i * edge + whole * float(sum(counts[:i + 1]) / total)\n                val = values[i]\n                htmlval = to_html(val)\n                newattrvals = attr_vals + \"-\" + val if attr_vals else val\n\n                tooltip = \"{}&nbsp;&nbsp;&nbsp;&nbsp;{}: <b>{}</b><br/>\".format(\n                    condition, attr.name, htmlval)\n                attrs = used_attrs + [attr]\n                vals = used_vals + [val]\n                args = attrs, vals, newattrvals\n                if side % 2 == 0:  # if we are moving horizontally\n                    if len(attr_list) == 1:\n                        add_rect(x0 + start, x0 + end, y0, y1, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0 + start, x0 + end), (y0, y1),\n                            side + 1, tooltip, total_attrs, *args)\n                else:\n                    if len(attr_list) == 1:\n                        add_rect(x0, x1, y0 + start, y0 + end, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0, x1), (y0 + start, y0 + end),\n                            side + 1, tooltip, total_attrs, *args)\n            draw_text(side, attr_list[0], (x0, x1), (y0, y1),\n                      total_attrs, used_attrs, used_vals, attr_vals)\n\n        def draw_text(side, attr, x0_x1, y0_y1,\n                      total_attrs, used_attrs, used_vals, attr_vals):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if side in drawn_sides:\n                return\n\n            # the text on the right will be drawn when we are processing\n            # visualization of the last value of the first attribute\n            if side == 3:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] != attr1values[-1]:\n                    return\n\n            if not conditionaldict[attr_vals]:\n                if side not in draw_positions:\n                    draw_positions[side] = (x0, x1, y0, y1)\n                return\n            else:\n                if side in draw_positions:\n                    # restore the positions of attribute values and name\n                    (x0, x1, y0, y1) = draw_positions[side]\n\n            drawn_sides.add(side)\n\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]\n\n            spaces = spacing * (total_attrs - side) * (len(values) - 1)\n            width = x1 - x0 - spaces * (side % 2 == 0)\n            height = y1 - y0 - spaces * (side % 2 == 1)\n\n            # calculate position of first attribute\n            currpos = 0\n            total, counts = get_counts(attr_vals, values)\n            aligns = [Qt.AlignTop | Qt.AlignHCenter,\n                      Qt.AlignRight | Qt.AlignVCenter,\n                      Qt.AlignBottom | Qt.AlignHCenter,\n                      Qt.AlignLeft | Qt.AlignVCenter]\n            align = aligns[side]\n            for i, val in enumerate(values):\n                if distributiondict[val] != 0:\n                    perc = counts[i] / float(total)\n                    rwidth = width * perc\n                    xs = [x0 + currpos + rwidth / 2,\n                          x0 - self.ATTR_VAL_OFFSET,\n                          x0 + currpos + rwidth / 2,\n                          x1 + self.ATTR_VAL_OFFSET]\n                    ys = [y1 + self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc,\n                          y0 - self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc]\n\n                    CanvasText(\n                        self.canvas, val, xs[side], ys[side], align,\n                        max_width=rwidth if side == 0 else None)\n                    space = height if side % 2 else width\n                    currpos += perc * space + spacing * (total_attrs - side)\n\n            xs = [x0 + (x1 - x0) / 2,\n                  x0 - max_ylabel_w1 - self.ATTR_VAL_OFFSET,\n                  x0 + (x1 - x0) / 2,\n                  x1 + max_ylabel_w2 + self.ATTR_VAL_OFFSET]\n            ys = [y1 + self.ATTR_VAL_OFFSET + self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2,\n                  y0 - self.ATTR_VAL_OFFSET - self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2]\n            CanvasText(\n                self.canvas, attr.name, xs[side], ys[side], align, bold=True,\n                vertical=side % 2)\n\n        def add_rect(x0, x1, y0, y1, condition,\n                     used_attrs, used_vals, attr_vals=\"\"):\n            area_index = len(self.areas)\n            x1 += (x0 == x1)\n            y1 += (y0 == y1)\n            # rectangles of width and height 1 are not shown - increase\n            y1 += (x1 - x0 + y1 - y0 == 2)\n            colors = class_var and [QColor(*col) for col in class_var.colors]\n\n            def select_area(_, ev):\n                self.select_area(area_index, ev)\n\n            def rect(x, y, w, h, z, pen_color=None, brush_color=None, **args):\n                if pen_color is None:\n                    return CanvasRectangle(\n                        self.canvas, x, y, w, h, z=z, onclick=select_area,\n                        **args)\n                if brush_color is None:\n                    brush_color = pen_color\n                return CanvasRectangle(\n                    self.canvas, x, y, w, h, pen_color, brush_color, z=z,\n                    onclick=select_area, **args)\n\n            def line(x1, y1, x2, y2):\n                r = QGraphicsLineItem(x1, y1, x2, y2, None)\n                self.canvas.addItem(r)\n                r.setPen(QPen(Qt.white, 2))\n                r.setZValue(30)\n\n            outer_rect = rect(x0, y0, x1 - x0, y1 - y0, 30)\n            self.areas.append((used_attrs, used_vals, outer_rect))\n            if not conditionaldict[attr_vals]:\n                return\n\n            if self.variable_color is None:\n                s = sum(apriori_dists[0])\n                expected = s * reduce(\n                    mul,\n                    (apriori_dists[i][used_vals[i]] / float(s)\n                     for i in range(len(used_vals))))\n                actual = conditionaldict[attr_vals]\n                pearson = float((actual - expected) / sqrt(expected))\n                if pearson == 0:\n                    ind = 0\n                else:\n                    ind = max(0, min(int(log(abs(pearson), 2)), 3))\n                color = [self.RED_COLORS, self.BLUE_COLORS][pearson > 0][ind]\n                rect(x0, y0, x1 - x0, y1 - y0, -20, color)\n                outer_rect.setToolTip(\n                    condition + \"<hr/>\" +\n                    \"Expected instances: %.1f<br>\"\n                    \"Actual instances: %d<br>\"\n                    \"Standardized (Pearson) residual: %.1f\" %\n                    (expected, conditionaldict[attr_vals], pearson))\n            else:\n                cls_values = get_variable_values_sorted(class_var)\n                prior = get_distribution(data, class_var.name)\n                total = 0\n                for i, value in enumerate(cls_values):\n                    val = conditionaldict[attr_vals + \"-\" + value]\n                    if val == 0:\n                        continue\n                    if i == len(cls_values) - 1:\n                        v = y1 - y0 - total\n                    else:\n                        v = ((y1 - y0) * val) / conditionaldict[attr_vals]\n                    rect(x0, y0 + total, x1 - x0, v, -20, colors[i])\n                    total += v\n\n                if self.use_boxes and \\\n                        abs(x1 - x0) > bar_width and abs(y1 - y0) > bar_width:\n                    total = 0\n                    line(x0 + bar_width, y0, x0 + bar_width, y1)\n                    n = sum(prior)\n                    for i, (val, color) in enumerate(zip(prior, colors)):\n                        if i == len(prior) - 1:\n                            h = y1 - y0 - total\n                        else:\n                            h = (y1 - y0) * val / n\n                        rect(x0, y0 + total, bar_width, h, 20, color)\n                        total += h\n\n                if conditionalsubsetdict:\n                    if conditionalsubsetdict[attr_vals]:\n                        if self.subset_indices is not None:\n                            line(x1 - bar_width, y0, x1 - bar_width, y1)\n                            total = 0\n                            n = conditionalsubsetdict[attr_vals]\n                            if n:\n                                for i, (cls, color) in \\\n                                        enumerate(zip(cls_values, colors)):\n                                    val = conditionalsubsetdict[\n                                        attr_vals + \"-\" + cls]\n                                    if val == 0:\n                                        continue\n                                    if i == len(prior) - 1:\n                                        v = y1 - y0 - total\n                                    else:\n                                        v = ((y1 - y0) * val) / n\n                                    rect(x1 - bar_width, y0 + total,\n                                         bar_width, v, 15, color)\n                                    total += v\n\n                actual = [conditionaldict[attr_vals + \"-\" + cls_values[i]]\n                          for i in range(len(prior))]\n                n_actual = sum(actual)\n                if n_actual > 0:\n                    apriori = [prior[key] for key in cls_values]\n                    n_apriori = sum(apriori)\n                    text = \"<br/>\".join(\n                        \"<b>%s</b>: %d / %.1f%% (Expected %.1f / %.1f%%)\" %\n                        (cls, act, 100.0 * act / n_actual,\n                         apr / n_apriori * n_actual, 100.0 * apr / n_apriori)\n                        for cls, act, apr in zip(cls_values, actual, apriori))\n                else:\n                    text = \"\"\n                outer_rect.setToolTip(\n                    \"{}<hr>Instances: {}<br><br>{}\".format(\n                        condition, n_actual, text[:-4]))\n\n        def create_legend():\n            if self.variable_color is None:\n                names = [\"<-8\", \"-8:-4\", \"-4:-2\", \"-2:2\", \"2:4\", \"4:8\", \">8\",\n                         \"Residuals:\"]\n                colors = self.RED_COLORS[::-1] + self.BLUE_COLORS[1:]\n                edges = repeat(Qt.black)\n            else:\n                names = get_variable_values_sorted(class_var)\n                edges = colors = [QColor(*col) for col in class_var.colors]\n\n            items = []\n            size = 8\n            for name, color, edgecolor in zip(names, colors, edges):\n                item = QGraphicsItemGroup()\n                item.addToGroup(\n                    CanvasRectangle(None, -size / 2, -size / 2, size, size,\n                                    edgecolor, color))\n                item.addToGroup(\n                    CanvasText(None, name, size, 0, Qt.AlignVCenter))\n                items.append(item)\n            return wrap_legend_items(\n                items, hspacing=20, vspacing=16 + size,\n                max_width=self.canvas_view.width() - xoff)\n\n        self.canvas.clear()\n        self.areas = []\n\n        data = self.discrete_data\n        if data is None:\n            return\n        attr_list = self.get_disc_attr_list()\n        class_var = data.domain.class_var\n        if class_var:\n            sql = isinstance(data, SqlTable)\n            name = not sql and data.name\n            # save class_var because it is removed in the next line\n            data = data[:, attr_list + [class_var]]\n            data.domain.class_var = class_var\n            if not sql:\n                data.name = name\n        else:\n            data = data[:, attr_list]\n        # TODO: check this\n        # data = Preprocessor_dropMissing(data)\n        if len(data) == 0:\n            self.Warning.no_valid_data()\n            return\n        else:\n            self.Warning.no_valid_data.clear()\n\n        attrs = [attr for attr in attr_list if not attr.values]\n        if attrs:\n            CanvasText(self.canvas,\n                       \"Feature {} has no values\".format(attrs[0]),\n                       (self.canvas_view.width() - 120) / 2,\n                       self.canvas_view.height() / 2)\n            return\n        if self.variable_color is None:\n            apriori_dists = [get_distribution(data, attr) for attr in attr_list]\n        else:\n            apriori_dists = []\n\n        def get_max_label_width(attr):\n            values = get_variable_values_sorted(attr)\n            maxw = 0\n            for val in values:\n                t = CanvasText(self.canvas, val, 0, 0, bold=0, show=False)\n                maxw = max(int(t.boundingRect().width()), maxw)\n            return maxw\n\n        xoff = 20\n\n        # get the maximum width of rectangle\n        width = 20\n        max_ylabel_w1 = max_ylabel_w2 = 0\n        if len(attr_list) > 1:\n            text = CanvasText(self.canvas, attr_list[1].name, bold=1, show=0)\n            max_ylabel_w1 = min(get_max_label_width(attr_list[1]), 150)\n            width = 5 + text.boundingRect().height() + \\\n                self.ATTR_VAL_OFFSET + max_ylabel_w1\n            xoff = width\n            if len(attr_list) == 4:\n                text = CanvasText(self.canvas, attr_list[3].name, bold=1, show=0)\n                max_ylabel_w2 = min(get_max_label_width(attr_list[3]), 150)\n                width += text.boundingRect().height() + \\\n                    self.ATTR_VAL_OFFSET + max_ylabel_w2 - 10\n\n        legend = create_legend()\n\n        # get the maximum height of rectangle\n        yoff = 45\n        legendoff = yoff + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35\n        square_size = min(self.canvas_view.width() - width - 20,\n                          self.canvas_view.height() - legendoff\n                          - legend.boundingRect().height())\n\n        if square_size < 0:\n            return  # canvas is too small to draw rectangles\n        self.canvas_view.setSceneRect(\n            0, 0, self.canvas_view.width(), self.canvas_view.height())\n\n        drawn_sides = set()\n        draw_positions = {}\n\n        conditionaldict, distributiondict = \\\n            get_conditional_distribution(data, attr_list)\n        conditionalsubsetdict = None\n        if self.subset_indices:\n            conditionalsubsetdict, _ = get_conditional_distribution(\n                self.discrete_data[self.subset_indices], attr_list)\n\n        # draw rectangles\n        draw_data(\n            attr_list, (xoff, xoff + square_size), (yoff, yoff + square_size),\n            0, \"\", len(attr_list), [], [])\n\n        self.canvas.addItem(legend)\n        legend.setPos(\n            xoff - legend.boundingRect().x()\n            + max(0, (square_size - legend.boundingRect().width()) / 2),\n            legendoff + square_size)\n        self.update_selection_rects()\n\n    @classmethod\n    def migrate_context(cls, context, version):\n        if version < 2:\n            settings.migrate_str_to_variable(context, none_placeholder=\"(None)\")",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 269,
                                "end_lineno": 951,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "Orange/widgets/visualize/owmosaic.py",
                            "type": "class",
                            "name": "OWMosaicDisplay",
                            "content": "class OWMosaicDisplay(OWWidget):\n    name = \"Mosaic Display\"\n    description = \"Display data in a mosaic plot.\"\n    icon = \"icons/MosaicDisplay.svg\"\n    priority = 220\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Table, default=True)\n        data_subset = Input(\"Data Subset\", Table)\n\n    class Outputs:\n        selected_data = Output(\"Selected Data\", Table, default=True)\n        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)\n\n    settingsHandler = DomainContextHandler()\n    vizrank = SettingProvider(MosaicVizRank)\n    settings_version = 2\n    use_boxes = Setting(True)\n    variable1 = ContextSetting(None)\n    variable2 = ContextSetting(None)\n    variable3 = ContextSetting(None)\n    variable4 = ContextSetting(None)\n    variable_color = ContextSetting(None)\n    selection = ContextSetting(set())\n\n    BAR_WIDTH = 5\n    SPACING = 4\n    ATTR_NAME_OFFSET = 20\n    ATTR_VAL_OFFSET = 3\n    BLUE_COLORS = [QColor(255, 255, 255), QColor(210, 210, 255),\n                   QColor(110, 110, 255), QColor(0, 0, 255)]\n    RED_COLORS = [QColor(255, 255, 255), QColor(255, 200, 200),\n                  QColor(255, 100, 100), QColor(255, 0, 0)]\n    graph_name = \"canvas\"\n\n    attrs_changed_manually = Signal(list)\n\n    class Warning(OWWidget.Warning):\n        incompatible_subset = Msg(\"Data subset is incompatible with Data\")\n        no_valid_data = Msg(\"No valid data\")\n        no_cont_selection_sql = \\\n            Msg(\"Selection of numeric features on SQL is not supported\")\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self.discrete_data = None\n        self.subset_data = None\n        self.subset_indices = None\n        self.__pending_selection = None\n\n        self.color_data = None\n\n        self.areas = []\n\n        self.canvas = QGraphicsScene()\n        self.canvas_view = ViewWithPress(\n            self.canvas, handler=self.clear_selection)\n        self.mainArea.layout().addWidget(self.canvas_view)\n        self.canvas_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setRenderHint(QPainter.Antialiasing)\n\n        box = gui.vBox(self.controlArea, box=True)\n        self.model_1 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE)\n        self.model_234 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(None)\")\n        self.attr_combos = [\n            gui.comboBox(\n                box, self, value=\"variable{}\".format(i),\n                orientation=Qt.Horizontal, contentsLength=12,\n                callback=self.attr_changed,\n                model=self.model_1 if i == 1 else self.model_234)\n            for i in range(1, 5)]\n        self.vizrank, self.vizrank_button = MosaicVizRank.add_vizrank(\n            box, self, \"Find Informative Mosaics\", self.set_attr)\n\n        box2 = gui.vBox(self.controlArea, box=\"Interior Coloring\")\n        self.color_model = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(Pearson residuals)\")\n        self.cb_attr_color = gui.comboBox(\n            box2, self, value=\"variable_color\",\n            orientation=Qt.Horizontal, contentsLength=12, labelWidth=50,\n            callback=self.set_color_data, model=self.color_model)\n        self.bar_button = gui.checkBox(\n            box2, self, 'use_boxes', label='Compare with total',\n            callback=self.update_graph)\n        gui.rubber(self.controlArea)\n\n    def sizeHint(self):\n        return QSize(720, 530)\n\n    def _get_discrete_data(self, data):\n        \"\"\"\n        Discretize continuous attributes.\n        Return None when there is no data, no rows, or no primitive attributes.\n        \"\"\"\n        if (data is None or\n                not len(data) or\n                not any(attr.is_discrete or attr.is_continuous\n                        for attr in chain(data.domain.variables,\n                                          data.domain.metas))):\n            return None\n        elif any(attr.is_continuous for attr in data.domain.variables):\n            return Discretize(\n                method=EqualFreq(n=4), remove_const=False, discretize_classes=True,\n                discretize_metas=True)(data)\n        else:\n            return data\n\n    def init_combos(self, data):\n        def set_combos(value):\n            self.model_1.set_domain(value)\n            self.model_234.set_domain(value)\n            self.color_model.set_domain(value)\n\n        if data is None:\n            set_combos(None)\n            self.variable1 = self.variable2 = self.variable3 \\\n                = self.variable4 = self.variable_color = None\n            return\n        set_combos(self.data.domain)\n\n        if len(self.model_1) > 0:\n            self.variable1 = self.model_1[0]\n            self.variable2 = self.model_1[min(1, len(self.model_1) - 1)]\n        self.variable3 = self.variable4 = None\n        self.variable_color = self.data.domain.class_var  # None is OK, too\n\n    def get_disc_attr_list(self):\n        return [self.discrete_data.domain[var.name]\n                for var in (self.variable1, self.variable2,\n                            self.variable3, self.variable4)\n                if var]\n\n    def set_attr(self, *attrs):\n        self.variable1, self.variable2, self.variable3, self.variable4 = [\n            attr and self.data.domain[attr.name] for attr in attrs]\n        self.reset_graph()\n\n    def attr_changed(self):\n        self.attrs_changed_manually.emit(self.get_disc_attr_list())\n        self.reset_graph()\n\n    def resizeEvent(self, e):\n        OWWidget.resizeEvent(self, e)\n        self.update_graph()\n\n    def showEvent(self, ev):\n        OWWidget.showEvent(self, ev)\n        self.update_graph()\n\n    @Inputs.data\n    def set_data(self, data):\n        if isinstance(data, SqlTable) and data.approx_len() > LARGE_TABLE:\n            data = data.sample_time(DEFAULT_SAMPLE_TIME)\n\n        self.closeContext()\n        self.data = data\n\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(\n            self.data is not None and len(self.data) > 1\n            and len(self.data.domain.attributes) >= 1)\n\n        if self.data is None:\n            self.discrete_data = None\n            self.init_combos(None)\n            return\n\n        self.init_combos(self.data)\n        self.openContext(self.data)\n        self.__pending_selection = self.selection\n\n    @Inputs.data_subset\n    def set_subset_data(self, data):\n        self.subset_data = data\n\n    # this is called by widget after setData and setSubsetData are called.\n    # this way the graph is updated only once\n    def handleNewSignals(self):\n        self.Warning.incompatible_subset.clear()\n        self.subset_indices = None\n        if self.data is not None and self.subset_data:\n            transformed = self.subset_data.transform(self.data.domain)\n            if np.all(np.isnan(transformed.X)) \\\n                    and np.all(np.isnan(transformed.Y)):\n                self.Warning.incompatible_subset()\n            else:\n                indices = {e.id for e in transformed}\n                self.subset_indices = [ex.id in indices for ex in self.data]\n        self.set_color_data()\n        self.reset_graph()\n\n    def clear_selection(self):\n        self.selection = set()\n        self.update_selection_rects()\n        self.send_selection()\n\n    def coloring_changed(self):\n        self.vizrank.coloring_changed()\n        self.update_graph()\n\n    def reset_graph(self):\n        self.clear_selection()\n        self.update_graph()\n        if self.__pending_selection is not None:\n            self.selection = self.__pending_selection\n            self.__pending_selection = None\n            self.update_selection_rects()\n            self.send_selection()\n\n    def set_color_data(self):\n        if self.data is None:\n            return\n        self.bar_button.setEnabled(self.variable_color is not None)\n        attrs = [v for v in self.model_1 if v and v is not self.variable_color]\n        domain = Domain(attrs, self.variable_color, None)\n        self.color_data = self.data.from_table(domain, self.data)\n        self.discrete_data = self._get_discrete_data(self.color_data)\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(True)\n        self.coloring_changed()\n\n    def update_selection_rects(self):\n        pens = (QPen(), QPen(Qt.black, 3, Qt.DotLine))\n        for i, (_, _, area) in enumerate(self.areas):\n            area.setPen(pens[i in self.selection])\n\n    def select_area(self, index, ev):\n        if ev.button() != Qt.LeftButton:\n            return\n        if ev.modifiers() & Qt.ControlModifier:\n            self.selection ^= {index}\n        else:\n            self.selection = {index}\n        self.update_selection_rects()\n        self.send_selection()\n\n    def send_selection(self):\n        if not self.selection or self.data is None:\n            self.Outputs.selected_data.send(None)\n            self.Outputs.annotated_data.send(\n                create_annotated_table(self.data, []))\n            return\n        filters = []\n        self.Warning.no_cont_selection_sql.clear()\n        if self.discrete_data is not self.data:\n            if isinstance(self.data, SqlTable):\n                self.Warning.no_cont_selection_sql()\n        for i in self.selection:\n            cols, vals, _ = self.areas[i]\n            filters.append(\n                filter.Values(\n                    filter.FilterDiscrete(col, [val])\n                    for col, val in zip(cols, vals)))\n        if len(filters) > 1:\n            filters = filter.Values(filters, conjunction=False)\n        else:\n            filters = filters[0]\n        selection = filters(self.discrete_data)\n        idset = set(selection.ids)\n        sel_idx = [i for i, id in enumerate(self.data.ids) if id in idset]\n        if self.discrete_data is not self.data:\n            selection = self.data[sel_idx]\n        self.Outputs.selected_data.send(selection)\n        self.Outputs.annotated_data.send(\n            create_annotated_table(self.data, sel_idx))\n\n    def send_report(self):\n        self.report_plot(self.canvas)\n\n    def update_graph(self):\n        spacing = self.SPACING\n        bar_width = self.BAR_WIDTH\n\n        def get_counts(attr_vals, values):\n            \"\"\"Calculate rectangles' widths; if all are 0, they are set to 1.\"\"\"\n            if not attr_vals:\n                counts = [conditionaldict[val] for val in values]\n            else:\n                counts = [conditionaldict[attr_vals + \"-\" + val]\n                          for val in values]\n            total = sum(counts)\n            if total == 0:\n                counts = [1] * len(values)\n                total = sum(counts)\n            return total, counts\n\n        def draw_data(attr_list, x0_x1, y0_y1, side, condition,\n                      total_attrs, used_attrs, used_vals, attr_vals=\"\"):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if conditionaldict[attr_vals] == 0:\n                add_rect(x0, x1, y0, y1, \"\",\n                         used_attrs, used_vals, attr_vals=attr_vals)\n                # store coordinates for later drawing of labels\n                draw_text(side, attr_list[0], (x0, x1), (y0, y1), total_attrs,\n                          used_attrs, used_vals, attr_vals)\n                return\n\n            attr = attr_list[0]\n            # how much smaller rectangles do we draw\n            edge = len(attr_list) * spacing\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]  # reverse names if necessary\n\n            if side % 2 == 0:  # we are drawing on the x axis\n                # remove the space needed for separating different attr. values\n                whole = max(0, (x1 - x0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (x1 - x0) / float(len(values) - 1)\n            else:  # we are drawing on the y axis\n                whole = max(0, (y1 - y0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (y1 - y0) / float(len(values) - 1)\n\n            total, counts = get_counts(attr_vals, values)\n\n            # when visualizing the third attribute and the first attribute has\n            # the last value, reverse the order in which the boxes are drawn;\n            # otherwise, if the last cell, nearest to the labels of the fourth\n            # attribute, is empty, we wouldn't be able to position the labels\n            valrange = list(range(len(values)))\n            if len(attr_list + used_attrs) == 4 and len(used_attrs) == 2:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] == attr1values[-1]:\n                    valrange = valrange[::-1]\n\n            for i in valrange:\n                start = i * edge + whole * float(sum(counts[:i]) / total)\n                end = i * edge + whole * float(sum(counts[:i + 1]) / total)\n                val = values[i]\n                htmlval = to_html(val)\n                newattrvals = attr_vals + \"-\" + val if attr_vals else val\n\n                tooltip = \"{}&nbsp;&nbsp;&nbsp;&nbsp;{}: <b>{}</b><br/>\".format(\n                    condition, attr.name, htmlval)\n                attrs = used_attrs + [attr]\n                vals = used_vals + [val]\n                args = attrs, vals, newattrvals\n                if side % 2 == 0:  # if we are moving horizontally\n                    if len(attr_list) == 1:\n                        add_rect(x0 + start, x0 + end, y0, y1, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0 + start, x0 + end), (y0, y1),\n                            side + 1, tooltip, total_attrs, *args)\n                else:\n                    if len(attr_list) == 1:\n                        add_rect(x0, x1, y0 + start, y0 + end, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0, x1), (y0 + start, y0 + end),\n                            side + 1, tooltip, total_attrs, *args)\n            draw_text(side, attr_list[0], (x0, x1), (y0, y1),\n                      total_attrs, used_attrs, used_vals, attr_vals)\n\n        def draw_text(side, attr, x0_x1, y0_y1,\n                      total_attrs, used_attrs, used_vals, attr_vals):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if side in drawn_sides:\n                return\n\n            # the text on the right will be drawn when we are processing\n            # visualization of the last value of the first attribute\n            if side == 3:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] != attr1values[-1]:\n                    return\n\n            if not conditionaldict[attr_vals]:\n                if side not in draw_positions:\n                    draw_positions[side] = (x0, x1, y0, y1)\n                return\n            else:\n                if side in draw_positions:\n                    # restore the positions of attribute values and name\n                    (x0, x1, y0, y1) = draw_positions[side]\n\n            drawn_sides.add(side)\n\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]\n\n            spaces = spacing * (total_attrs - side) * (len(values) - 1)\n            width = x1 - x0 - spaces * (side % 2 == 0)\n            height = y1 - y0 - spaces * (side % 2 == 1)\n\n            # calculate position of first attribute\n            currpos = 0\n            total, counts = get_counts(attr_vals, values)\n            aligns = [Qt.AlignTop | Qt.AlignHCenter,\n                      Qt.AlignRight | Qt.AlignVCenter,\n                      Qt.AlignBottom | Qt.AlignHCenter,\n                      Qt.AlignLeft | Qt.AlignVCenter]\n            align = aligns[side]\n            for i, val in enumerate(values):\n                if distributiondict[val] != 0:\n                    perc = counts[i] / float(total)\n                    rwidth = width * perc\n                    xs = [x0 + currpos + rwidth / 2,\n                          x0 - self.ATTR_VAL_OFFSET,\n                          x0 + currpos + rwidth / 2,\n                          x1 + self.ATTR_VAL_OFFSET]\n                    ys = [y1 + self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc,\n                          y0 - self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc]\n\n                    CanvasText(\n                        self.canvas, val, xs[side], ys[side], align,\n                        max_width=rwidth if side == 0 else None)\n                    space = height if side % 2 else width\n                    currpos += perc * space + spacing * (total_attrs - side)\n\n            xs = [x0 + (x1 - x0) / 2,\n                  x0 - max_ylabel_w1 - self.ATTR_VAL_OFFSET,\n                  x0 + (x1 - x0) / 2,\n                  x1 + max_ylabel_w2 + self.ATTR_VAL_OFFSET]\n            ys = [y1 + self.ATTR_VAL_OFFSET + self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2,\n                  y0 - self.ATTR_VAL_OFFSET - self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2]\n            CanvasText(\n                self.canvas, attr.name, xs[side], ys[side], align, bold=True,\n                vertical=side % 2)\n\n        def add_rect(x0, x1, y0, y1, condition,\n                     used_attrs, used_vals, attr_vals=\"\"):\n            area_index = len(self.areas)\n            x1 += (x0 == x1)\n            y1 += (y0 == y1)\n            # rectangles of width and height 1 are not shown - increase\n            y1 += (x1 - x0 + y1 - y0 == 2)\n            colors = class_var and [QColor(*col) for col in class_var.colors]\n\n            def select_area(_, ev):\n                self.select_area(area_index, ev)\n\n            def rect(x, y, w, h, z, pen_color=None, brush_color=None, **args):\n                if pen_color is None:\n                    return CanvasRectangle(\n                        self.canvas, x, y, w, h, z=z, onclick=select_area,\n                        **args)\n                if brush_color is None:\n                    brush_color = pen_color\n                return CanvasRectangle(\n                    self.canvas, x, y, w, h, pen_color, brush_color, z=z,\n                    onclick=select_area, **args)\n\n            def line(x1, y1, x2, y2):\n                r = QGraphicsLineItem(x1, y1, x2, y2, None)\n                self.canvas.addItem(r)\n                r.setPen(QPen(Qt.white, 2))\n                r.setZValue(30)\n\n            outer_rect = rect(x0, y0, x1 - x0, y1 - y0, 30)\n            self.areas.append((used_attrs, used_vals, outer_rect))\n            if not conditionaldict[attr_vals]:\n                return\n\n            if self.variable_color is None:\n                s = sum(apriori_dists[0])\n                expected = s * reduce(\n                    mul,\n                    (apriori_dists[i][used_vals[i]] / float(s)\n                     for i in range(len(used_vals))))\n                actual = conditionaldict[attr_vals]\n                pearson = float((actual - expected) / sqrt(expected))\n                if pearson == 0:\n                    ind = 0\n                else:\n                    ind = max(0, min(int(log(abs(pearson), 2)), 3))\n                color = [self.RED_COLORS, self.BLUE_COLORS][pearson > 0][ind]\n                rect(x0, y0, x1 - x0, y1 - y0, -20, color)\n                outer_rect.setToolTip(\n                    condition + \"<hr/>\" +\n                    \"Expected instances: %.1f<br>\"\n                    \"Actual instances: %d<br>\"\n                    \"Standardized (Pearson) residual: %.1f\" %\n                    (expected, conditionaldict[attr_vals], pearson))\n            else:\n                cls_values = get_variable_values_sorted(class_var)\n                prior = get_distribution(data, class_var.name)\n                total = 0\n                for i, value in enumerate(cls_values):\n                    val = conditionaldict[attr_vals + \"-\" + value]\n                    if val == 0:\n                        continue\n                    if i == len(cls_values) - 1:\n                        v = y1 - y0 - total\n                    else:\n                        v = ((y1 - y0) * val) / conditionaldict[attr_vals]\n                    rect(x0, y0 + total, x1 - x0, v, -20, colors[i])\n                    total += v\n\n                if self.use_boxes and \\\n                        abs(x1 - x0) > bar_width and abs(y1 - y0) > bar_width:\n                    total = 0\n                    line(x0 + bar_width, y0, x0 + bar_width, y1)\n                    n = sum(prior)\n                    for i, (val, color) in enumerate(zip(prior, colors)):\n                        if i == len(prior) - 1:\n                            h = y1 - y0 - total\n                        else:\n                            h = (y1 - y0) * val / n\n                        rect(x0, y0 + total, bar_width, h, 20, color)\n                        total += h\n\n                if conditionalsubsetdict:\n                    if conditionalsubsetdict[attr_vals]:\n                        if self.subset_indices is not None:\n                            line(x1 - bar_width, y0, x1 - bar_width, y1)\n                            total = 0\n                            n = conditionalsubsetdict[attr_vals]\n                            if n:\n                                for i, (cls, color) in \\\n                                        enumerate(zip(cls_values, colors)):\n                                    val = conditionalsubsetdict[\n                                        attr_vals + \"-\" + cls]\n                                    if val == 0:\n                                        continue\n                                    if i == len(prior) - 1:\n                                        v = y1 - y0 - total\n                                    else:\n                                        v = ((y1 - y0) * val) / n\n                                    rect(x1 - bar_width, y0 + total,\n                                         bar_width, v, 15, color)\n                                    total += v\n\n                actual = [conditionaldict[attr_vals + \"-\" + cls_values[i]]\n                          for i in range(len(prior))]\n                n_actual = sum(actual)\n                if n_actual > 0:\n                    apriori = [prior[key] for key in cls_values]\n                    n_apriori = sum(apriori)\n                    text = \"<br/>\".join(\n                        \"<b>%s</b>: %d / %.1f%% (Expected %.1f / %.1f%%)\" %\n                        (cls, act, 100.0 * act / n_actual,\n                         apr / n_apriori * n_actual, 100.0 * apr / n_apriori)\n                        for cls, act, apr in zip(cls_values, actual, apriori))\n                else:\n                    text = \"\"\n                outer_rect.setToolTip(\n                    \"{}<hr>Instances: {}<br><br>{}\".format(\n                        condition, n_actual, text[:-4]))\n\n        def create_legend():\n            if self.variable_color is None:\n                names = [\"<-8\", \"-8:-4\", \"-4:-2\", \"-2:2\", \"2:4\", \"4:8\", \">8\",\n                         \"Residuals:\"]\n                colors = self.RED_COLORS[::-1] + self.BLUE_COLORS[1:]\n                edges = repeat(Qt.black)\n            else:\n                names = get_variable_values_sorted(class_var)\n                edges = colors = [QColor(*col) for col in class_var.colors]\n\n            items = []\n            size = 8\n            for name, color, edgecolor in zip(names, colors, edges):\n                item = QGraphicsItemGroup()\n                item.addToGroup(\n                    CanvasRectangle(None, -size / 2, -size / 2, size, size,\n                                    edgecolor, color))\n                item.addToGroup(\n                    CanvasText(None, name, size, 0, Qt.AlignVCenter))\n                items.append(item)\n            return wrap_legend_items(\n                items, hspacing=20, vspacing=16 + size,\n                max_width=self.canvas_view.width() - xoff)\n\n        self.canvas.clear()\n        self.areas = []\n\n        data = self.discrete_data\n        if data is None:\n            return\n        attr_list = self.get_disc_attr_list()\n        class_var = data.domain.class_var\n        if class_var:\n            sql = isinstance(data, SqlTable)\n            name = not sql and data.name\n            # save class_var because it is removed in the next line\n            data = data[:, attr_list + [class_var]]\n            data.domain.class_var = class_var\n            if not sql:\n                data.name = name\n        else:\n            data = data[:, attr_list]\n        # TODO: check this\n        # data = Preprocessor_dropMissing(data)\n        if len(data) == 0:\n            self.Warning.no_valid_data()\n            return\n        else:\n            self.Warning.no_valid_data.clear()\n\n        attrs = [attr for attr in attr_list if not attr.values]\n        if attrs:\n            CanvasText(self.canvas,\n                       \"Feature {} has no values\".format(attrs[0]),\n                       (self.canvas_view.width() - 120) / 2,\n                       self.canvas_view.height() / 2)\n            return\n        if self.variable_color is None:\n            apriori_dists = [get_distribution(data, attr) for attr in attr_list]\n        else:\n            apriori_dists = []\n\n        def get_max_label_width(attr):\n            values = get_variable_values_sorted(attr)\n            maxw = 0\n            for val in values:\n                t = CanvasText(self.canvas, val, 0, 0, bold=0, show=False)\n                maxw = max(int(t.boundingRect().width()), maxw)\n            return maxw\n\n        xoff = 20\n\n        # get the maximum width of rectangle\n        width = 20\n        max_ylabel_w1 = max_ylabel_w2 = 0\n        if len(attr_list) > 1:\n            text = CanvasText(self.canvas, attr_list[1].name, bold=1, show=0)\n            max_ylabel_w1 = min(get_max_label_width(attr_list[1]), 150)\n            width = 5 + text.boundingRect().height() + \\\n                self.ATTR_VAL_OFFSET + max_ylabel_w1\n            xoff = width\n            if len(attr_list) == 4:\n                text = CanvasText(self.canvas, attr_list[3].name, bold=1, show=0)\n                max_ylabel_w2 = min(get_max_label_width(attr_list[3]), 150)\n                width += text.boundingRect().height() + \\\n                    self.ATTR_VAL_OFFSET + max_ylabel_w2 - 10\n\n        legend = create_legend()\n\n        # get the maximum height of rectangle\n        yoff = 45\n        legendoff = yoff + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35\n        square_size = min(self.canvas_view.width() - width - 20,\n                          self.canvas_view.height() - legendoff\n                          - legend.boundingRect().height())\n\n        if square_size < 0:\n            return  # canvas is too small to draw rectangles\n        self.canvas_view.setSceneRect(\n            0, 0, self.canvas_view.width(), self.canvas_view.height())\n\n        drawn_sides = set()\n        draw_positions = {}\n\n        conditionaldict, distributiondict = \\\n            get_conditional_distribution(data, attr_list)\n        conditionalsubsetdict = None\n        if self.subset_indices:\n            conditionalsubsetdict, _ = get_conditional_distribution(\n                self.discrete_data[self.subset_indices], attr_list)\n\n        # draw rectangles\n        draw_data(\n            attr_list, (xoff, xoff + square_size), (yoff, yoff + square_size),\n            0, \"\", len(attr_list), [], [])\n\n        self.canvas.addItem(legend)\n        legend.setPos(\n            xoff - legend.boundingRect().x()\n            + max(0, (square_size - legend.boundingRect().width()) / 2),\n            legendoff + square_size)\n        self.update_selection_rects()\n\n    @classmethod\n    def migrate_context(cls, context, version):\n        if version < 2:\n            settings.migrate_str_to_variable(context, none_placeholder=\"(None)\")",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 269,
                            "end_lineno": 951,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 475,
                            "length": 6
                        },
                        "new_range": {
                            "start": 477,
                            "length": 11
                        },
                        "section": "class OWMosaicDisplay(OWWidget):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    def reset_graph(self):",
                                "type": "context"
                            },
                            {
                                "content": "        self.clear_selection()",
                                "type": "context"
                            },
                            {
                                "content": "        self.update_graph()",
                                "type": "context"
                            },
                            {
                                "content": "        if self.__pending_selection is not None:",
                                "type": "added"
                            },
                            {
                                "content": "            self.selection = self.__pending_selection",
                                "type": "added"
                            },
                            {
                                "content": "            self.__pending_selection = None",
                                "type": "added"
                            },
                            {
                                "content": "            self.update_selection_rects()",
                                "type": "added"
                            },
                            {
                                "content": "            self.send_selection()",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def set_color_data(self):",
                                "type": "context"
                            },
                            {
                                "content": "        if self.data is None:",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/visualize/owmosaic.py",
                            "type": "class",
                            "name": "OWMosaicDisplay",
                            "content": "class OWMosaicDisplay(OWWidget):\n    name = \"Mosaic Display\"\n    description = \"Display data in a mosaic plot.\"\n    icon = \"icons/MosaicDisplay.svg\"\n    priority = 220\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Table, default=True)\n        data_subset = Input(\"Data Subset\", Table)\n\n    class Outputs:\n        selected_data = Output(\"Selected Data\", Table, default=True)\n        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)\n\n    settingsHandler = DomainContextHandler()\n    vizrank = SettingProvider(MosaicVizRank)\n    settings_version = 2\n    use_boxes = Setting(True)\n    variable1 = ContextSetting(None)\n    variable2 = ContextSetting(None)\n    variable3 = ContextSetting(None)\n    variable4 = ContextSetting(None)\n    variable_color = ContextSetting(None)\n    selection = ContextSetting(set())\n\n    BAR_WIDTH = 5\n    SPACING = 4\n    ATTR_NAME_OFFSET = 20\n    ATTR_VAL_OFFSET = 3\n    BLUE_COLORS = [QColor(255, 255, 255), QColor(210, 210, 255),\n                   QColor(110, 110, 255), QColor(0, 0, 255)]\n    RED_COLORS = [QColor(255, 255, 255), QColor(255, 200, 200),\n                  QColor(255, 100, 100), QColor(255, 0, 0)]\n    graph_name = \"canvas\"\n\n    attrs_changed_manually = Signal(list)\n\n    class Warning(OWWidget.Warning):\n        incompatible_subset = Msg(\"Data subset is incompatible with Data\")\n        no_valid_data = Msg(\"No valid data\")\n        no_cont_selection_sql = \\\n            Msg(\"Selection of numeric features on SQL is not supported\")\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self.discrete_data = None\n        self.subset_data = None\n        self.subset_indices = None\n        self.__pending_selection = None\n\n        self.color_data = None\n\n        self.areas = []\n\n        self.canvas = QGraphicsScene()\n        self.canvas_view = ViewWithPress(\n            self.canvas, handler=self.clear_selection)\n        self.mainArea.layout().addWidget(self.canvas_view)\n        self.canvas_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setRenderHint(QPainter.Antialiasing)\n\n        box = gui.vBox(self.controlArea, box=True)\n        self.model_1 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE)\n        self.model_234 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(None)\")\n        self.attr_combos = [\n            gui.comboBox(\n                box, self, value=\"variable{}\".format(i),\n                orientation=Qt.Horizontal, contentsLength=12,\n                callback=self.attr_changed,\n                model=self.model_1 if i == 1 else self.model_234)\n            for i in range(1, 5)]\n        self.vizrank, self.vizrank_button = MosaicVizRank.add_vizrank(\n            box, self, \"Find Informative Mosaics\", self.set_attr)\n\n        box2 = gui.vBox(self.controlArea, box=\"Interior Coloring\")\n        self.color_model = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(Pearson residuals)\")\n        self.cb_attr_color = gui.comboBox(\n            box2, self, value=\"variable_color\",\n            orientation=Qt.Horizontal, contentsLength=12, labelWidth=50,\n            callback=self.set_color_data, model=self.color_model)\n        self.bar_button = gui.checkBox(\n            box2, self, 'use_boxes', label='Compare with total',\n            callback=self.update_graph)\n        gui.rubber(self.controlArea)\n\n    def sizeHint(self):\n        return QSize(720, 530)\n\n    def _get_discrete_data(self, data):\n        \"\"\"\n        Discretize continuous attributes.\n        Return None when there is no data, no rows, or no primitive attributes.\n        \"\"\"\n        if (data is None or\n                not len(data) or\n                not any(attr.is_discrete or attr.is_continuous\n                        for attr in chain(data.domain.variables,\n                                          data.domain.metas))):\n            return None\n        elif any(attr.is_continuous for attr in data.domain.variables):\n            return Discretize(\n                method=EqualFreq(n=4), remove_const=False, discretize_classes=True,\n                discretize_metas=True)(data)\n        else:\n            return data\n\n    def init_combos(self, data):\n        def set_combos(value):\n            self.model_1.set_domain(value)\n            self.model_234.set_domain(value)\n            self.color_model.set_domain(value)\n\n        if data is None:\n            set_combos(None)\n            self.variable1 = self.variable2 = self.variable3 \\\n                = self.variable4 = self.variable_color = None\n            return\n        set_combos(self.data.domain)\n\n        if len(self.model_1) > 0:\n            self.variable1 = self.model_1[0]\n            self.variable2 = self.model_1[min(1, len(self.model_1) - 1)]\n        self.variable3 = self.variable4 = None\n        self.variable_color = self.data.domain.class_var  # None is OK, too\n\n    def get_disc_attr_list(self):\n        return [self.discrete_data.domain[var.name]\n                for var in (self.variable1, self.variable2,\n                            self.variable3, self.variable4)\n                if var]\n\n    def set_attr(self, *attrs):\n        self.variable1, self.variable2, self.variable3, self.variable4 = [\n            attr and self.data.domain[attr.name] for attr in attrs]\n        self.reset_graph()\n\n    def attr_changed(self):\n        self.attrs_changed_manually.emit(self.get_disc_attr_list())\n        self.reset_graph()\n\n    def resizeEvent(self, e):\n        OWWidget.resizeEvent(self, e)\n        self.update_graph()\n\n    def showEvent(self, ev):\n        OWWidget.showEvent(self, ev)\n        self.update_graph()\n\n    @Inputs.data\n    def set_data(self, data):\n        if isinstance(data, SqlTable) and data.approx_len() > LARGE_TABLE:\n            data = data.sample_time(DEFAULT_SAMPLE_TIME)\n\n        self.closeContext()\n        self.data = data\n\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(\n            self.data is not None and len(self.data) > 1\n            and len(self.data.domain.attributes) >= 1)\n\n        if self.data is None:\n            self.discrete_data = None\n            self.init_combos(None)\n            return\n\n        self.init_combos(self.data)\n        self.openContext(self.data)\n        self.__pending_selection = self.selection\n\n    @Inputs.data_subset\n    def set_subset_data(self, data):\n        self.subset_data = data\n\n    # this is called by widget after setData and setSubsetData are called.\n    # this way the graph is updated only once\n    def handleNewSignals(self):\n        self.Warning.incompatible_subset.clear()\n        self.subset_indices = None\n        if self.data is not None and self.subset_data:\n            transformed = self.subset_data.transform(self.data.domain)\n            if np.all(np.isnan(transformed.X)) \\\n                    and np.all(np.isnan(transformed.Y)):\n                self.Warning.incompatible_subset()\n            else:\n                indices = {e.id for e in transformed}\n                self.subset_indices = [ex.id in indices for ex in self.data]\n        self.set_color_data()\n        self.reset_graph()\n\n    def clear_selection(self):\n        self.selection = set()\n        self.update_selection_rects()\n        self.send_selection()\n\n    def coloring_changed(self):\n        self.vizrank.coloring_changed()\n        self.update_graph()\n\n    def reset_graph(self):\n        self.clear_selection()\n        self.update_graph()\n        if self.__pending_selection is not None:\n            self.selection = self.__pending_selection\n            self.__pending_selection = None\n            self.update_selection_rects()\n            self.send_selection()\n\n    def set_color_data(self):\n        if self.data is None:\n            return\n        self.bar_button.setEnabled(self.variable_color is not None)\n        attrs = [v for v in self.model_1 if v and v is not self.variable_color]\n        domain = Domain(attrs, self.variable_color, None)\n        self.color_data = self.data.from_table(domain, self.data)\n        self.discrete_data = self._get_discrete_data(self.color_data)\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(True)\n        self.coloring_changed()\n\n    def update_selection_rects(self):\n        pens = (QPen(), QPen(Qt.black, 3, Qt.DotLine))\n        for i, (_, _, area) in enumerate(self.areas):\n            area.setPen(pens[i in self.selection])\n\n    def select_area(self, index, ev):\n        if ev.button() != Qt.LeftButton:\n            return\n        if ev.modifiers() & Qt.ControlModifier:\n            self.selection ^= {index}\n        else:\n            self.selection = {index}\n        self.update_selection_rects()\n        self.send_selection()\n\n    def send_selection(self):\n        if not self.selection or self.data is None:\n            self.Outputs.selected_data.send(None)\n            self.Outputs.annotated_data.send(\n                create_annotated_table(self.data, []))\n            return\n        filters = []\n        self.Warning.no_cont_selection_sql.clear()\n        if self.discrete_data is not self.data:\n            if isinstance(self.data, SqlTable):\n                self.Warning.no_cont_selection_sql()\n        for i in self.selection:\n            cols, vals, _ = self.areas[i]\n            filters.append(\n                filter.Values(\n                    filter.FilterDiscrete(col, [val])\n                    for col, val in zip(cols, vals)))\n        if len(filters) > 1:\n            filters = filter.Values(filters, conjunction=False)\n        else:\n            filters = filters[0]\n        selection = filters(self.discrete_data)\n        idset = set(selection.ids)\n        sel_idx = [i for i, id in enumerate(self.data.ids) if id in idset]\n        if self.discrete_data is not self.data:\n            selection = self.data[sel_idx]\n        self.Outputs.selected_data.send(selection)\n        self.Outputs.annotated_data.send(\n            create_annotated_table(self.data, sel_idx))\n\n    def send_report(self):\n        self.report_plot(self.canvas)\n\n    def update_graph(self):\n        spacing = self.SPACING\n        bar_width = self.BAR_WIDTH\n\n        def get_counts(attr_vals, values):\n            \"\"\"Calculate rectangles' widths; if all are 0, they are set to 1.\"\"\"\n            if not attr_vals:\n                counts = [conditionaldict[val] for val in values]\n            else:\n                counts = [conditionaldict[attr_vals + \"-\" + val]\n                          for val in values]\n            total = sum(counts)\n            if total == 0:\n                counts = [1] * len(values)\n                total = sum(counts)\n            return total, counts\n\n        def draw_data(attr_list, x0_x1, y0_y1, side, condition,\n                      total_attrs, used_attrs, used_vals, attr_vals=\"\"):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if conditionaldict[attr_vals] == 0:\n                add_rect(x0, x1, y0, y1, \"\",\n                         used_attrs, used_vals, attr_vals=attr_vals)\n                # store coordinates for later drawing of labels\n                draw_text(side, attr_list[0], (x0, x1), (y0, y1), total_attrs,\n                          used_attrs, used_vals, attr_vals)\n                return\n\n            attr = attr_list[0]\n            # how much smaller rectangles do we draw\n            edge = len(attr_list) * spacing\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]  # reverse names if necessary\n\n            if side % 2 == 0:  # we are drawing on the x axis\n                # remove the space needed for separating different attr. values\n                whole = max(0, (x1 - x0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (x1 - x0) / float(len(values) - 1)\n            else:  # we are drawing on the y axis\n                whole = max(0, (y1 - y0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (y1 - y0) / float(len(values) - 1)\n\n            total, counts = get_counts(attr_vals, values)\n\n            # when visualizing the third attribute and the first attribute has\n            # the last value, reverse the order in which the boxes are drawn;\n            # otherwise, if the last cell, nearest to the labels of the fourth\n            # attribute, is empty, we wouldn't be able to position the labels\n            valrange = list(range(len(values)))\n            if len(attr_list + used_attrs) == 4 and len(used_attrs) == 2:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] == attr1values[-1]:\n                    valrange = valrange[::-1]\n\n            for i in valrange:\n                start = i * edge + whole * float(sum(counts[:i]) / total)\n                end = i * edge + whole * float(sum(counts[:i + 1]) / total)\n                val = values[i]\n                htmlval = to_html(val)\n                newattrvals = attr_vals + \"-\" + val if attr_vals else val\n\n                tooltip = \"{}&nbsp;&nbsp;&nbsp;&nbsp;{}: <b>{}</b><br/>\".format(\n                    condition, attr.name, htmlval)\n                attrs = used_attrs + [attr]\n                vals = used_vals + [val]\n                args = attrs, vals, newattrvals\n                if side % 2 == 0:  # if we are moving horizontally\n                    if len(attr_list) == 1:\n                        add_rect(x0 + start, x0 + end, y0, y1, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0 + start, x0 + end), (y0, y1),\n                            side + 1, tooltip, total_attrs, *args)\n                else:\n                    if len(attr_list) == 1:\n                        add_rect(x0, x1, y0 + start, y0 + end, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0, x1), (y0 + start, y0 + end),\n                            side + 1, tooltip, total_attrs, *args)\n            draw_text(side, attr_list[0], (x0, x1), (y0, y1),\n                      total_attrs, used_attrs, used_vals, attr_vals)\n\n        def draw_text(side, attr, x0_x1, y0_y1,\n                      total_attrs, used_attrs, used_vals, attr_vals):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if side in drawn_sides:\n                return\n\n            # the text on the right will be drawn when we are processing\n            # visualization of the last value of the first attribute\n            if side == 3:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] != attr1values[-1]:\n                    return\n\n            if not conditionaldict[attr_vals]:\n                if side not in draw_positions:\n                    draw_positions[side] = (x0, x1, y0, y1)\n                return\n            else:\n                if side in draw_positions:\n                    # restore the positions of attribute values and name\n                    (x0, x1, y0, y1) = draw_positions[side]\n\n            drawn_sides.add(side)\n\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]\n\n            spaces = spacing * (total_attrs - side) * (len(values) - 1)\n            width = x1 - x0 - spaces * (side % 2 == 0)\n            height = y1 - y0 - spaces * (side % 2 == 1)\n\n            # calculate position of first attribute\n            currpos = 0\n            total, counts = get_counts(attr_vals, values)\n            aligns = [Qt.AlignTop | Qt.AlignHCenter,\n                      Qt.AlignRight | Qt.AlignVCenter,\n                      Qt.AlignBottom | Qt.AlignHCenter,\n                      Qt.AlignLeft | Qt.AlignVCenter]\n            align = aligns[side]\n            for i, val in enumerate(values):\n                if distributiondict[val] != 0:\n                    perc = counts[i] / float(total)\n                    rwidth = width * perc\n                    xs = [x0 + currpos + rwidth / 2,\n                          x0 - self.ATTR_VAL_OFFSET,\n                          x0 + currpos + rwidth / 2,\n                          x1 + self.ATTR_VAL_OFFSET]\n                    ys = [y1 + self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc,\n                          y0 - self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc]\n\n                    CanvasText(\n                        self.canvas, val, xs[side], ys[side], align,\n                        max_width=rwidth if side == 0 else None)\n                    space = height if side % 2 else width\n                    currpos += perc * space + spacing * (total_attrs - side)\n\n            xs = [x0 + (x1 - x0) / 2,\n                  x0 - max_ylabel_w1 - self.ATTR_VAL_OFFSET,\n                  x0 + (x1 - x0) / 2,\n                  x1 + max_ylabel_w2 + self.ATTR_VAL_OFFSET]\n            ys = [y1 + self.ATTR_VAL_OFFSET + self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2,\n                  y0 - self.ATTR_VAL_OFFSET - self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2]\n            CanvasText(\n                self.canvas, attr.name, xs[side], ys[side], align, bold=True,\n                vertical=side % 2)\n\n        def add_rect(x0, x1, y0, y1, condition,\n                     used_attrs, used_vals, attr_vals=\"\"):\n            area_index = len(self.areas)\n            x1 += (x0 == x1)\n            y1 += (y0 == y1)\n            # rectangles of width and height 1 are not shown - increase\n            y1 += (x1 - x0 + y1 - y0 == 2)\n            colors = class_var and [QColor(*col) for col in class_var.colors]\n\n            def select_area(_, ev):\n                self.select_area(area_index, ev)\n\n            def rect(x, y, w, h, z, pen_color=None, brush_color=None, **args):\n                if pen_color is None:\n                    return CanvasRectangle(\n                        self.canvas, x, y, w, h, z=z, onclick=select_area,\n                        **args)\n                if brush_color is None:\n                    brush_color = pen_color\n                return CanvasRectangle(\n                    self.canvas, x, y, w, h, pen_color, brush_color, z=z,\n                    onclick=select_area, **args)\n\n            def line(x1, y1, x2, y2):\n                r = QGraphicsLineItem(x1, y1, x2, y2, None)\n                self.canvas.addItem(r)\n                r.setPen(QPen(Qt.white, 2))\n                r.setZValue(30)\n\n            outer_rect = rect(x0, y0, x1 - x0, y1 - y0, 30)\n            self.areas.append((used_attrs, used_vals, outer_rect))\n            if not conditionaldict[attr_vals]:\n                return\n\n            if self.variable_color is None:\n                s = sum(apriori_dists[0])\n                expected = s * reduce(\n                    mul,\n                    (apriori_dists[i][used_vals[i]] / float(s)\n                     for i in range(len(used_vals))))\n                actual = conditionaldict[attr_vals]\n                pearson = float((actual - expected) / sqrt(expected))\n                if pearson == 0:\n                    ind = 0\n                else:\n                    ind = max(0, min(int(log(abs(pearson), 2)), 3))\n                color = [self.RED_COLORS, self.BLUE_COLORS][pearson > 0][ind]\n                rect(x0, y0, x1 - x0, y1 - y0, -20, color)\n                outer_rect.setToolTip(\n                    condition + \"<hr/>\" +\n                    \"Expected instances: %.1f<br>\"\n                    \"Actual instances: %d<br>\"\n                    \"Standardized (Pearson) residual: %.1f\" %\n                    (expected, conditionaldict[attr_vals], pearson))\n            else:\n                cls_values = get_variable_values_sorted(class_var)\n                prior = get_distribution(data, class_var.name)\n                total = 0\n                for i, value in enumerate(cls_values):\n                    val = conditionaldict[attr_vals + \"-\" + value]\n                    if val == 0:\n                        continue\n                    if i == len(cls_values) - 1:\n                        v = y1 - y0 - total\n                    else:\n                        v = ((y1 - y0) * val) / conditionaldict[attr_vals]\n                    rect(x0, y0 + total, x1 - x0, v, -20, colors[i])\n                    total += v\n\n                if self.use_boxes and \\\n                        abs(x1 - x0) > bar_width and abs(y1 - y0) > bar_width:\n                    total = 0\n                    line(x0 + bar_width, y0, x0 + bar_width, y1)\n                    n = sum(prior)\n                    for i, (val, color) in enumerate(zip(prior, colors)):\n                        if i == len(prior) - 1:\n                            h = y1 - y0 - total\n                        else:\n                            h = (y1 - y0) * val / n\n                        rect(x0, y0 + total, bar_width, h, 20, color)\n                        total += h\n\n                if conditionalsubsetdict:\n                    if conditionalsubsetdict[attr_vals]:\n                        if self.subset_indices is not None:\n                            line(x1 - bar_width, y0, x1 - bar_width, y1)\n                            total = 0\n                            n = conditionalsubsetdict[attr_vals]\n                            if n:\n                                for i, (cls, color) in \\\n                                        enumerate(zip(cls_values, colors)):\n                                    val = conditionalsubsetdict[\n                                        attr_vals + \"-\" + cls]\n                                    if val == 0:\n                                        continue\n                                    if i == len(prior) - 1:\n                                        v = y1 - y0 - total\n                                    else:\n                                        v = ((y1 - y0) * val) / n\n                                    rect(x1 - bar_width, y0 + total,\n                                         bar_width, v, 15, color)\n                                    total += v\n\n                actual = [conditionaldict[attr_vals + \"-\" + cls_values[i]]\n                          for i in range(len(prior))]\n                n_actual = sum(actual)\n                if n_actual > 0:\n                    apriori = [prior[key] for key in cls_values]\n                    n_apriori = sum(apriori)\n                    text = \"<br/>\".join(\n                        \"<b>%s</b>: %d / %.1f%% (Expected %.1f / %.1f%%)\" %\n                        (cls, act, 100.0 * act / n_actual,\n                         apr / n_apriori * n_actual, 100.0 * apr / n_apriori)\n                        for cls, act, apr in zip(cls_values, actual, apriori))\n                else:\n                    text = \"\"\n                outer_rect.setToolTip(\n                    \"{}<hr>Instances: {}<br><br>{}\".format(\n                        condition, n_actual, text[:-4]))\n\n        def create_legend():\n            if self.variable_color is None:\n                names = [\"<-8\", \"-8:-4\", \"-4:-2\", \"-2:2\", \"2:4\", \"4:8\", \">8\",\n                         \"Residuals:\"]\n                colors = self.RED_COLORS[::-1] + self.BLUE_COLORS[1:]\n                edges = repeat(Qt.black)\n            else:\n                names = get_variable_values_sorted(class_var)\n                edges = colors = [QColor(*col) for col in class_var.colors]\n\n            items = []\n            size = 8\n            for name, color, edgecolor in zip(names, colors, edges):\n                item = QGraphicsItemGroup()\n                item.addToGroup(\n                    CanvasRectangle(None, -size / 2, -size / 2, size, size,\n                                    edgecolor, color))\n                item.addToGroup(\n                    CanvasText(None, name, size, 0, Qt.AlignVCenter))\n                items.append(item)\n            return wrap_legend_items(\n                items, hspacing=20, vspacing=16 + size,\n                max_width=self.canvas_view.width() - xoff)\n\n        self.canvas.clear()\n        self.areas = []\n\n        data = self.discrete_data\n        if data is None:\n            return\n        attr_list = self.get_disc_attr_list()\n        class_var = data.domain.class_var\n        if class_var:\n            sql = isinstance(data, SqlTable)\n            name = not sql and data.name\n            # save class_var because it is removed in the next line\n            data = data[:, attr_list + [class_var]]\n            data.domain.class_var = class_var\n            if not sql:\n                data.name = name\n        else:\n            data = data[:, attr_list]\n        # TODO: check this\n        # data = Preprocessor_dropMissing(data)\n        if len(data) == 0:\n            self.Warning.no_valid_data()\n            return\n        else:\n            self.Warning.no_valid_data.clear()\n\n        attrs = [attr for attr in attr_list if not attr.values]\n        if attrs:\n            CanvasText(self.canvas,\n                       \"Feature {} has no values\".format(attrs[0]),\n                       (self.canvas_view.width() - 120) / 2,\n                       self.canvas_view.height() / 2)\n            return\n        if self.variable_color is None:\n            apriori_dists = [get_distribution(data, attr) for attr in attr_list]\n        else:\n            apriori_dists = []\n\n        def get_max_label_width(attr):\n            values = get_variable_values_sorted(attr)\n            maxw = 0\n            for val in values:\n                t = CanvasText(self.canvas, val, 0, 0, bold=0, show=False)\n                maxw = max(int(t.boundingRect().width()), maxw)\n            return maxw\n\n        xoff = 20\n\n        # get the maximum width of rectangle\n        width = 20\n        max_ylabel_w1 = max_ylabel_w2 = 0\n        if len(attr_list) > 1:\n            text = CanvasText(self.canvas, attr_list[1].name, bold=1, show=0)\n            max_ylabel_w1 = min(get_max_label_width(attr_list[1]), 150)\n            width = 5 + text.boundingRect().height() + \\\n                self.ATTR_VAL_OFFSET + max_ylabel_w1\n            xoff = width\n            if len(attr_list) == 4:\n                text = CanvasText(self.canvas, attr_list[3].name, bold=1, show=0)\n                max_ylabel_w2 = min(get_max_label_width(attr_list[3]), 150)\n                width += text.boundingRect().height() + \\\n                    self.ATTR_VAL_OFFSET + max_ylabel_w2 - 10\n\n        legend = create_legend()\n\n        # get the maximum height of rectangle\n        yoff = 45\n        legendoff = yoff + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35\n        square_size = min(self.canvas_view.width() - width - 20,\n                          self.canvas_view.height() - legendoff\n                          - legend.boundingRect().height())\n\n        if square_size < 0:\n            return  # canvas is too small to draw rectangles\n        self.canvas_view.setSceneRect(\n            0, 0, self.canvas_view.width(), self.canvas_view.height())\n\n        drawn_sides = set()\n        draw_positions = {}\n\n        conditionaldict, distributiondict = \\\n            get_conditional_distribution(data, attr_list)\n        conditionalsubsetdict = None\n        if self.subset_indices:\n            conditionalsubsetdict, _ = get_conditional_distribution(\n                self.discrete_data[self.subset_indices], attr_list)\n\n        # draw rectangles\n        draw_data(\n            attr_list, (xoff, xoff + square_size), (yoff, yoff + square_size),\n            0, \"\", len(attr_list), [], [])\n\n        self.canvas.addItem(legend)\n        legend.setPos(\n            xoff - legend.boundingRect().x()\n            + max(0, (square_size - legend.boundingRect().width()) / 2),\n            legendoff + square_size)\n        self.update_selection_rects()\n\n    @classmethod\n    def migrate_context(cls, context, version):\n        if version < 2:\n            settings.migrate_str_to_variable(context, none_placeholder=\"(None)\")",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 269,
                            "end_lineno": 951,
                            "parent": null
                        },
                        {
                            "file_name": "Orange/widgets/visualize/owmosaic.py",
                            "type": "method",
                            "name": "OWMosaicDisplay.reset_graph",
                            "content": "class OWMosaicDisplay(OWWidget):\n    name = \"Mosaic Display\"\n    description = \"Display data in a mosaic plot.\"\n    icon = \"icons/MosaicDisplay.svg\"\n    priority = 220\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Table, default=True)\n        data_subset = Input(\"Data Subset\", Table)\n\n    class Outputs:\n        selected_data = Output(\"Selected Data\", Table, default=True)\n        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)\n\n    settingsHandler = DomainContextHandler()\n    vizrank = SettingProvider(MosaicVizRank)\n    settings_version = 2\n    use_boxes = Setting(True)\n    variable1 = ContextSetting(None)\n    variable2 = ContextSetting(None)\n    variable3 = ContextSetting(None)\n    variable4 = ContextSetting(None)\n    variable_color = ContextSetting(None)\n    selection = ContextSetting(set())\n\n    BAR_WIDTH = 5\n    SPACING = 4\n    ATTR_NAME_OFFSET = 20\n    ATTR_VAL_OFFSET = 3\n    BLUE_COLORS = [QColor(255, 255, 255), QColor(210, 210, 255),\n                   QColor(110, 110, 255), QColor(0, 0, 255)]\n    RED_COLORS = [QColor(255, 255, 255), QColor(255, 200, 200),\n                  QColor(255, 100, 100), QColor(255, 0, 0)]\n    graph_name = \"canvas\"\n\n    attrs_changed_manually = Signal(list)\n\n    class Warning(OWWidget.Warning):\n        incompatible_subset = Msg(\"Data subset is incompatible with Data\")\n        no_valid_data = Msg(\"No valid data\")\n        no_cont_selection_sql = \\\n            Msg(\"Selection of numeric features on SQL is not supported\")\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self.discrete_data = None\n        self.subset_data = None\n        self.subset_indices = None\n        self.__pending_selection = None\n\n        self.color_data = None\n\n        self.areas = []\n\n        self.canvas = QGraphicsScene()\n        self.canvas_view = ViewWithPress(\n            self.canvas, handler=self.clear_selection)\n        self.mainArea.layout().addWidget(self.canvas_view)\n        self.canvas_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setRenderHint(QPainter.Antialiasing)\n\n        box = gui.vBox(self.controlArea, box=True)\n        self.model_1 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE)\n        self.model_234 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(None)\")\n        self.attr_combos = [\n            gui.comboBox(\n                box, self, value=\"variable{}\".format(i),\n                orientation=Qt.Horizontal, contentsLength=12,\n                callback=self.attr_changed,\n                model=self.model_1 if i == 1 else self.model_234)\n            for i in range(1, 5)]\n        self.vizrank, self.vizrank_button = MosaicVizRank.add_vizrank(\n            box, self, \"Find Informative Mosaics\", self.set_attr)\n\n        box2 = gui.vBox(self.controlArea, box=\"Interior Coloring\")\n        self.color_model = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(Pearson residuals)\")\n        self.cb_attr_color = gui.comboBox(\n            box2, self, value=\"variable_color\",\n            orientation=Qt.Horizontal, contentsLength=12, labelWidth=50,\n            callback=self.set_color_data, model=self.color_model)\n        self.bar_button = gui.checkBox(\n            box2, self, 'use_boxes', label='Compare with total',\n            callback=self.update_graph)\n        gui.rubber(self.controlArea)\n\n    def sizeHint(self):\n        return QSize(720, 530)\n\n    def _get_discrete_data(self, data):\n        \"\"\"\n        Discretize continuous attributes.\n        Return None when there is no data, no rows, or no primitive attributes.\n        \"\"\"\n        if (data is None or\n                not len(data) or\n                not any(attr.is_discrete or attr.is_continuous\n                        for attr in chain(data.domain.variables,\n                                          data.domain.metas))):\n            return None\n        elif any(attr.is_continuous for attr in data.domain.variables):\n            return Discretize(\n                method=EqualFreq(n=4), remove_const=False, discretize_classes=True,\n                discretize_metas=True)(data)\n        else:\n            return data\n\n    def init_combos(self, data):\n        def set_combos(value):\n            self.model_1.set_domain(value)\n            self.model_234.set_domain(value)\n            self.color_model.set_domain(value)\n\n        if data is None:\n            set_combos(None)\n            self.variable1 = self.variable2 = self.variable3 \\\n                = self.variable4 = self.variable_color = None\n            return\n        set_combos(self.data.domain)\n\n        if len(self.model_1) > 0:\n            self.variable1 = self.model_1[0]\n            self.variable2 = self.model_1[min(1, len(self.model_1) - 1)]\n        self.variable3 = self.variable4 = None\n        self.variable_color = self.data.domain.class_var  # None is OK, too\n\n    def get_disc_attr_list(self):\n        return [self.discrete_data.domain[var.name]\n                for var in (self.variable1, self.variable2,\n                            self.variable3, self.variable4)\n                if var]\n\n    def set_attr(self, *attrs):\n        self.variable1, self.variable2, self.variable3, self.variable4 = [\n            attr and self.data.domain[attr.name] for attr in attrs]\n        self.reset_graph()\n\n    def attr_changed(self):\n        self.attrs_changed_manually.emit(self.get_disc_attr_list())\n        self.reset_graph()\n\n    def resizeEvent(self, e):\n        OWWidget.resizeEvent(self, e)\n        self.update_graph()\n\n    def showEvent(self, ev):\n        OWWidget.showEvent(self, ev)\n        self.update_graph()\n\n    @Inputs.data\n    def set_data(self, data):\n        if isinstance(data, SqlTable) and data.approx_len() > LARGE_TABLE:\n            data = data.sample_time(DEFAULT_SAMPLE_TIME)\n\n        self.closeContext()\n        self.data = data\n\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(\n            self.data is not None and len(self.data) > 1\n            and len(self.data.domain.attributes) >= 1)\n\n        if self.data is None:\n            self.discrete_data = None\n            self.init_combos(None)\n            return\n\n        self.init_combos(self.data)\n        self.openContext(self.data)\n        self.__pending_selection = self.selection\n\n    @Inputs.data_subset\n    def set_subset_data(self, data):\n        self.subset_data = data\n\n    # this is called by widget after setData and setSubsetData are called.\n    # this way the graph is updated only once\n    def handleNewSignals(self):\n        self.Warning.incompatible_subset.clear()\n        self.subset_indices = None\n        if self.data is not None and self.subset_data:\n            transformed = self.subset_data.transform(self.data.domain)\n            if np.all(np.isnan(transformed.X)) \\\n                    and np.all(np.isnan(transformed.Y)):\n                self.Warning.incompatible_subset()\n            else:\n                indices = {e.id for e in transformed}\n                self.subset_indices = [ex.id in indices for ex in self.data]\n        self.set_color_data()\n        self.reset_graph()\n\n    def clear_selection(self):\n        self.selection = set()\n        self.update_selection_rects()\n        self.send_selection()\n\n    def coloring_changed(self):\n        self.vizrank.coloring_changed()\n        self.update_graph()\n\n    def reset_graph(self):\n        self.clear_selection()\n        self.update_graph()\n        if self.__pending_selection is not None:\n            self.selection = self.__pending_selection\n            self.__pending_selection = None\n            self.update_selection_rects()\n            self.send_selection()\n\n    def set_color_data(self):\n        if self.data is None:\n            return\n        self.bar_button.setEnabled(self.variable_color is not None)\n        attrs = [v for v in self.model_1 if v and v is not self.variable_color]\n        domain = Domain(attrs, self.variable_color, None)\n        self.color_data = self.data.from_table(domain, self.data)\n        self.discrete_data = self._get_discrete_data(self.color_data)\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(True)\n        self.coloring_changed()\n\n    def update_selection_rects(self):\n        pens = (QPen(), QPen(Qt.black, 3, Qt.DotLine))\n        for i, (_, _, area) in enumerate(self.areas):\n            area.setPen(pens[i in self.selection])\n\n    def select_area(self, index, ev):\n        if ev.button() != Qt.LeftButton:\n            return\n        if ev.modifiers() & Qt.ControlModifier:\n            self.selection ^= {index}\n        else:\n            self.selection = {index}\n        self.update_selection_rects()\n        self.send_selection()\n\n    def send_selection(self):\n        if not self.selection or self.data is None:\n            self.Outputs.selected_data.send(None)\n            self.Outputs.annotated_data.send(\n                create_annotated_table(self.data, []))\n            return\n        filters = []\n        self.Warning.no_cont_selection_sql.clear()\n        if self.discrete_data is not self.data:\n            if isinstance(self.data, SqlTable):\n                self.Warning.no_cont_selection_sql()\n        for i in self.selection:\n            cols, vals, _ = self.areas[i]\n            filters.append(\n                filter.Values(\n                    filter.FilterDiscrete(col, [val])\n                    for col, val in zip(cols, vals)))\n        if len(filters) > 1:\n            filters = filter.Values(filters, conjunction=False)\n        else:\n            filters = filters[0]\n        selection = filters(self.discrete_data)\n        idset = set(selection.ids)\n        sel_idx = [i for i, id in enumerate(self.data.ids) if id in idset]\n        if self.discrete_data is not self.data:\n            selection = self.data[sel_idx]\n        self.Outputs.selected_data.send(selection)\n        self.Outputs.annotated_data.send(\n            create_annotated_table(self.data, sel_idx))\n\n    def send_report(self):\n        self.report_plot(self.canvas)\n\n    def update_graph(self):\n        spacing = self.SPACING\n        bar_width = self.BAR_WIDTH\n\n        def get_counts(attr_vals, values):\n            \"\"\"Calculate rectangles' widths; if all are 0, they are set to 1.\"\"\"\n            if not attr_vals:\n                counts = [conditionaldict[val] for val in values]\n            else:\n                counts = [conditionaldict[attr_vals + \"-\" + val]\n                          for val in values]\n            total = sum(counts)\n            if total == 0:\n                counts = [1] * len(values)\n                total = sum(counts)\n            return total, counts\n\n        def draw_data(attr_list, x0_x1, y0_y1, side, condition,\n                      total_attrs, used_attrs, used_vals, attr_vals=\"\"):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if conditionaldict[attr_vals] == 0:\n                add_rect(x0, x1, y0, y1, \"\",\n                         used_attrs, used_vals, attr_vals=attr_vals)\n                # store coordinates for later drawing of labels\n                draw_text(side, attr_list[0], (x0, x1), (y0, y1), total_attrs,\n                          used_attrs, used_vals, attr_vals)\n                return\n\n            attr = attr_list[0]\n            # how much smaller rectangles do we draw\n            edge = len(attr_list) * spacing\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]  # reverse names if necessary\n\n            if side % 2 == 0:  # we are drawing on the x axis\n                # remove the space needed for separating different attr. values\n                whole = max(0, (x1 - x0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (x1 - x0) / float(len(values) - 1)\n            else:  # we are drawing on the y axis\n                whole = max(0, (y1 - y0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (y1 - y0) / float(len(values) - 1)\n\n            total, counts = get_counts(attr_vals, values)\n\n            # when visualizing the third attribute and the first attribute has\n            # the last value, reverse the order in which the boxes are drawn;\n            # otherwise, if the last cell, nearest to the labels of the fourth\n            # attribute, is empty, we wouldn't be able to position the labels\n            valrange = list(range(len(values)))\n            if len(attr_list + used_attrs) == 4 and len(used_attrs) == 2:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] == attr1values[-1]:\n                    valrange = valrange[::-1]\n\n            for i in valrange:\n                start = i * edge + whole * float(sum(counts[:i]) / total)\n                end = i * edge + whole * float(sum(counts[:i + 1]) / total)\n                val = values[i]\n                htmlval = to_html(val)\n                newattrvals = attr_vals + \"-\" + val if attr_vals else val\n\n                tooltip = \"{}&nbsp;&nbsp;&nbsp;&nbsp;{}: <b>{}</b><br/>\".format(\n                    condition, attr.name, htmlval)\n                attrs = used_attrs + [attr]\n                vals = used_vals + [val]\n                args = attrs, vals, newattrvals\n                if side % 2 == 0:  # if we are moving horizontally\n                    if len(attr_list) == 1:\n                        add_rect(x0 + start, x0 + end, y0, y1, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0 + start, x0 + end), (y0, y1),\n                            side + 1, tooltip, total_attrs, *args)\n                else:\n                    if len(attr_list) == 1:\n                        add_rect(x0, x1, y0 + start, y0 + end, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0, x1), (y0 + start, y0 + end),\n                            side + 1, tooltip, total_attrs, *args)\n            draw_text(side, attr_list[0], (x0, x1), (y0, y1),\n                      total_attrs, used_attrs, used_vals, attr_vals)\n\n        def draw_text(side, attr, x0_x1, y0_y1,\n                      total_attrs, used_attrs, used_vals, attr_vals):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if side in drawn_sides:\n                return\n\n            # the text on the right will be drawn when we are processing\n            # visualization of the last value of the first attribute\n            if side == 3:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] != attr1values[-1]:\n                    return\n\n            if not conditionaldict[attr_vals]:\n                if side not in draw_positions:\n                    draw_positions[side] = (x0, x1, y0, y1)\n                return\n            else:\n                if side in draw_positions:\n                    # restore the positions of attribute values and name\n                    (x0, x1, y0, y1) = draw_positions[side]\n\n            drawn_sides.add(side)\n\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]\n\n            spaces = spacing * (total_attrs - side) * (len(values) - 1)\n            width = x1 - x0 - spaces * (side % 2 == 0)\n            height = y1 - y0 - spaces * (side % 2 == 1)\n\n            # calculate position of first attribute\n            currpos = 0\n            total, counts = get_counts(attr_vals, values)\n            aligns = [Qt.AlignTop | Qt.AlignHCenter,\n                      Qt.AlignRight | Qt.AlignVCenter,\n                      Qt.AlignBottom | Qt.AlignHCenter,\n                      Qt.AlignLeft | Qt.AlignVCenter]\n            align = aligns[side]\n            for i, val in enumerate(values):\n                if distributiondict[val] != 0:\n                    perc = counts[i] / float(total)\n                    rwidth = width * perc\n                    xs = [x0 + currpos + rwidth / 2,\n                          x0 - self.ATTR_VAL_OFFSET,\n                          x0 + currpos + rwidth / 2,\n                          x1 + self.ATTR_VAL_OFFSET]\n                    ys = [y1 + self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc,\n                          y0 - self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc]\n\n                    CanvasText(\n                        self.canvas, val, xs[side], ys[side], align,\n                        max_width=rwidth if side == 0 else None)\n                    space = height if side % 2 else width\n                    currpos += perc * space + spacing * (total_attrs - side)\n\n            xs = [x0 + (x1 - x0) / 2,\n                  x0 - max_ylabel_w1 - self.ATTR_VAL_OFFSET,\n                  x0 + (x1 - x0) / 2,\n                  x1 + max_ylabel_w2 + self.ATTR_VAL_OFFSET]\n            ys = [y1 + self.ATTR_VAL_OFFSET + self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2,\n                  y0 - self.ATTR_VAL_OFFSET - self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2]\n            CanvasText(\n                self.canvas, attr.name, xs[side], ys[side], align, bold=True,\n                vertical=side % 2)\n\n        def add_rect(x0, x1, y0, y1, condition,\n                     used_attrs, used_vals, attr_vals=\"\"):\n            area_index = len(self.areas)\n            x1 += (x0 == x1)\n            y1 += (y0 == y1)\n            # rectangles of width and height 1 are not shown - increase\n            y1 += (x1 - x0 + y1 - y0 == 2)\n            colors = class_var and [QColor(*col) for col in class_var.colors]\n\n            def select_area(_, ev):\n                self.select_area(area_index, ev)\n\n            def rect(x, y, w, h, z, pen_color=None, brush_color=None, **args):\n                if pen_color is None:\n                    return CanvasRectangle(\n                        self.canvas, x, y, w, h, z=z, onclick=select_area,\n                        **args)\n                if brush_color is None:\n                    brush_color = pen_color\n                return CanvasRectangle(\n                    self.canvas, x, y, w, h, pen_color, brush_color, z=z,\n                    onclick=select_area, **args)\n\n            def line(x1, y1, x2, y2):\n                r = QGraphicsLineItem(x1, y1, x2, y2, None)\n                self.canvas.addItem(r)\n                r.setPen(QPen(Qt.white, 2))\n                r.setZValue(30)\n\n            outer_rect = rect(x0, y0, x1 - x0, y1 - y0, 30)\n            self.areas.append((used_attrs, used_vals, outer_rect))\n            if not conditionaldict[attr_vals]:\n                return\n\n            if self.variable_color is None:\n                s = sum(apriori_dists[0])\n                expected = s * reduce(\n                    mul,\n                    (apriori_dists[i][used_vals[i]] / float(s)\n                     for i in range(len(used_vals))))\n                actual = conditionaldict[attr_vals]\n                pearson = float((actual - expected) / sqrt(expected))\n                if pearson == 0:\n                    ind = 0\n                else:\n                    ind = max(0, min(int(log(abs(pearson), 2)), 3))\n                color = [self.RED_COLORS, self.BLUE_COLORS][pearson > 0][ind]\n                rect(x0, y0, x1 - x0, y1 - y0, -20, color)\n                outer_rect.setToolTip(\n                    condition + \"<hr/>\" +\n                    \"Expected instances: %.1f<br>\"\n                    \"Actual instances: %d<br>\"\n                    \"Standardized (Pearson) residual: %.1f\" %\n                    (expected, conditionaldict[attr_vals], pearson))\n            else:\n                cls_values = get_variable_values_sorted(class_var)\n                prior = get_distribution(data, class_var.name)\n                total = 0\n                for i, value in enumerate(cls_values):\n                    val = conditionaldict[attr_vals + \"-\" + value]\n                    if val == 0:\n                        continue\n                    if i == len(cls_values) - 1:\n                        v = y1 - y0 - total\n                    else:\n                        v = ((y1 - y0) * val) / conditionaldict[attr_vals]\n                    rect(x0, y0 + total, x1 - x0, v, -20, colors[i])\n                    total += v\n\n                if self.use_boxes and \\\n                        abs(x1 - x0) > bar_width and abs(y1 - y0) > bar_width:\n                    total = 0\n                    line(x0 + bar_width, y0, x0 + bar_width, y1)\n                    n = sum(prior)\n                    for i, (val, color) in enumerate(zip(prior, colors)):\n                        if i == len(prior) - 1:\n                            h = y1 - y0 - total\n                        else:\n                            h = (y1 - y0) * val / n\n                        rect(x0, y0 + total, bar_width, h, 20, color)\n                        total += h\n\n                if conditionalsubsetdict:\n                    if conditionalsubsetdict[attr_vals]:\n                        if self.subset_indices is not None:\n                            line(x1 - bar_width, y0, x1 - bar_width, y1)\n                            total = 0\n                            n = conditionalsubsetdict[attr_vals]\n                            if n:\n                                for i, (cls, color) in \\\n                                        enumerate(zip(cls_values, colors)):\n                                    val = conditionalsubsetdict[\n                                        attr_vals + \"-\" + cls]\n                                    if val == 0:\n                                        continue\n                                    if i == len(prior) - 1:\n                                        v = y1 - y0 - total\n                                    else:\n                                        v = ((y1 - y0) * val) / n\n                                    rect(x1 - bar_width, y0 + total,\n                                         bar_width, v, 15, color)\n                                    total += v\n\n                actual = [conditionaldict[attr_vals + \"-\" + cls_values[i]]\n                          for i in range(len(prior))]\n                n_actual = sum(actual)\n                if n_actual > 0:\n                    apriori = [prior[key] for key in cls_values]\n                    n_apriori = sum(apriori)\n                    text = \"<br/>\".join(\n                        \"<b>%s</b>: %d / %.1f%% (Expected %.1f / %.1f%%)\" %\n                        (cls, act, 100.0 * act / n_actual,\n                         apr / n_apriori * n_actual, 100.0 * apr / n_apriori)\n                        for cls, act, apr in zip(cls_values, actual, apriori))\n                else:\n                    text = \"\"\n                outer_rect.setToolTip(\n                    \"{}<hr>Instances: {}<br><br>{}\".format(\n                        condition, n_actual, text[:-4]))\n\n        def create_legend():\n            if self.variable_color is None:\n                names = [\"<-8\", \"-8:-4\", \"-4:-2\", \"-2:2\", \"2:4\", \"4:8\", \">8\",\n                         \"Residuals:\"]\n                colors = self.RED_COLORS[::-1] + self.BLUE_COLORS[1:]\n                edges = repeat(Qt.black)\n            else:\n                names = get_variable_values_sorted(class_var)\n                edges = colors = [QColor(*col) for col in class_var.colors]\n\n            items = []\n            size = 8\n            for name, color, edgecolor in zip(names, colors, edges):\n                item = QGraphicsItemGroup()\n                item.addToGroup(\n                    CanvasRectangle(None, -size / 2, -size / 2, size, size,\n                                    edgecolor, color))\n                item.addToGroup(\n                    CanvasText(None, name, size, 0, Qt.AlignVCenter))\n                items.append(item)\n            return wrap_legend_items(\n                items, hspacing=20, vspacing=16 + size,\n                max_width=self.canvas_view.width() - xoff)\n\n        self.canvas.clear()\n        self.areas = []\n\n        data = self.discrete_data\n        if data is None:\n            return\n        attr_list = self.get_disc_attr_list()\n        class_var = data.domain.class_var\n        if class_var:\n            sql = isinstance(data, SqlTable)\n            name = not sql and data.name\n            # save class_var because it is removed in the next line\n            data = data[:, attr_list + [class_var]]\n            data.domain.class_var = class_var\n            if not sql:\n                data.name = name\n        else:\n            data = data[:, attr_list]\n        # TODO: check this\n        # data = Preprocessor_dropMissing(data)\n        if len(data) == 0:\n            self.Warning.no_valid_data()\n            return\n        else:\n            self.Warning.no_valid_data.clear()\n\n        attrs = [attr for attr in attr_list if not attr.values]\n        if attrs:\n            CanvasText(self.canvas,\n                       \"Feature {} has no values\".format(attrs[0]),\n                       (self.canvas_view.width() - 120) / 2,\n                       self.canvas_view.height() / 2)\n            return\n        if self.variable_color is None:\n            apriori_dists = [get_distribution(data, attr) for attr in attr_list]\n        else:\n            apriori_dists = []\n\n        def get_max_label_width(attr):\n            values = get_variable_values_sorted(attr)\n            maxw = 0\n            for val in values:\n                t = CanvasText(self.canvas, val, 0, 0, bold=0, show=False)\n                maxw = max(int(t.boundingRect().width()), maxw)\n            return maxw\n\n        xoff = 20\n\n        # get the maximum width of rectangle\n        width = 20\n        max_ylabel_w1 = max_ylabel_w2 = 0\n        if len(attr_list) > 1:\n            text = CanvasText(self.canvas, attr_list[1].name, bold=1, show=0)\n            max_ylabel_w1 = min(get_max_label_width(attr_list[1]), 150)\n            width = 5 + text.boundingRect().height() + \\\n                self.ATTR_VAL_OFFSET + max_ylabel_w1\n            xoff = width\n            if len(attr_list) == 4:\n                text = CanvasText(self.canvas, attr_list[3].name, bold=1, show=0)\n                max_ylabel_w2 = min(get_max_label_width(attr_list[3]), 150)\n                width += text.boundingRect().height() + \\\n                    self.ATTR_VAL_OFFSET + max_ylabel_w2 - 10\n\n        legend = create_legend()\n\n        # get the maximum height of rectangle\n        yoff = 45\n        legendoff = yoff + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35\n        square_size = min(self.canvas_view.width() - width - 20,\n                          self.canvas_view.height() - legendoff\n                          - legend.boundingRect().height())\n\n        if square_size < 0:\n            return  # canvas is too small to draw rectangles\n        self.canvas_view.setSceneRect(\n            0, 0, self.canvas_view.width(), self.canvas_view.height())\n\n        drawn_sides = set()\n        draw_positions = {}\n\n        conditionaldict, distributiondict = \\\n            get_conditional_distribution(data, attr_list)\n        conditionalsubsetdict = None\n        if self.subset_indices:\n            conditionalsubsetdict, _ = get_conditional_distribution(\n                self.discrete_data[self.subset_indices], attr_list)\n\n        # draw rectangles\n        draw_data(\n            attr_list, (xoff, xoff + square_size), (yoff, yoff + square_size),\n            0, \"\", len(attr_list), [], [])\n\n        self.canvas.addItem(legend)\n        legend.setPos(\n            xoff - legend.boundingRect().x()\n            + max(0, (square_size - legend.boundingRect().width()) / 2),\n            legendoff + square_size)\n        self.update_selection_rects()\n\n    @classmethod\n    def migrate_context(cls, context, version):\n        if version < 2:\n            settings.migrate_str_to_variable(context, none_placeholder=\"(None)\")",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 477,
                            "end_lineno": 484,
                            "parent": {
                                "file_name": "Orange/widgets/visualize/owmosaic.py",
                                "type": "class",
                                "name": "OWMosaicDisplay",
                                "content": "class OWMosaicDisplay(OWWidget):\n    name = \"Mosaic Display\"\n    description = \"Display data in a mosaic plot.\"\n    icon = \"icons/MosaicDisplay.svg\"\n    priority = 220\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Table, default=True)\n        data_subset = Input(\"Data Subset\", Table)\n\n    class Outputs:\n        selected_data = Output(\"Selected Data\", Table, default=True)\n        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)\n\n    settingsHandler = DomainContextHandler()\n    vizrank = SettingProvider(MosaicVizRank)\n    settings_version = 2\n    use_boxes = Setting(True)\n    variable1 = ContextSetting(None)\n    variable2 = ContextSetting(None)\n    variable3 = ContextSetting(None)\n    variable4 = ContextSetting(None)\n    variable_color = ContextSetting(None)\n    selection = ContextSetting(set())\n\n    BAR_WIDTH = 5\n    SPACING = 4\n    ATTR_NAME_OFFSET = 20\n    ATTR_VAL_OFFSET = 3\n    BLUE_COLORS = [QColor(255, 255, 255), QColor(210, 210, 255),\n                   QColor(110, 110, 255), QColor(0, 0, 255)]\n    RED_COLORS = [QColor(255, 255, 255), QColor(255, 200, 200),\n                  QColor(255, 100, 100), QColor(255, 0, 0)]\n    graph_name = \"canvas\"\n\n    attrs_changed_manually = Signal(list)\n\n    class Warning(OWWidget.Warning):\n        incompatible_subset = Msg(\"Data subset is incompatible with Data\")\n        no_valid_data = Msg(\"No valid data\")\n        no_cont_selection_sql = \\\n            Msg(\"Selection of numeric features on SQL is not supported\")\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self.discrete_data = None\n        self.subset_data = None\n        self.subset_indices = None\n        self.__pending_selection = None\n\n        self.color_data = None\n\n        self.areas = []\n\n        self.canvas = QGraphicsScene()\n        self.canvas_view = ViewWithPress(\n            self.canvas, handler=self.clear_selection)\n        self.mainArea.layout().addWidget(self.canvas_view)\n        self.canvas_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setRenderHint(QPainter.Antialiasing)\n\n        box = gui.vBox(self.controlArea, box=True)\n        self.model_1 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE)\n        self.model_234 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(None)\")\n        self.attr_combos = [\n            gui.comboBox(\n                box, self, value=\"variable{}\".format(i),\n                orientation=Qt.Horizontal, contentsLength=12,\n                callback=self.attr_changed,\n                model=self.model_1 if i == 1 else self.model_234)\n            for i in range(1, 5)]\n        self.vizrank, self.vizrank_button = MosaicVizRank.add_vizrank(\n            box, self, \"Find Informative Mosaics\", self.set_attr)\n\n        box2 = gui.vBox(self.controlArea, box=\"Interior Coloring\")\n        self.color_model = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(Pearson residuals)\")\n        self.cb_attr_color = gui.comboBox(\n            box2, self, value=\"variable_color\",\n            orientation=Qt.Horizontal, contentsLength=12, labelWidth=50,\n            callback=self.set_color_data, model=self.color_model)\n        self.bar_button = gui.checkBox(\n            box2, self, 'use_boxes', label='Compare with total',\n            callback=self.update_graph)\n        gui.rubber(self.controlArea)\n\n    def sizeHint(self):\n        return QSize(720, 530)\n\n    def _get_discrete_data(self, data):\n        \"\"\"\n        Discretize continuous attributes.\n        Return None when there is no data, no rows, or no primitive attributes.\n        \"\"\"\n        if (data is None or\n                not len(data) or\n                not any(attr.is_discrete or attr.is_continuous\n                        for attr in chain(data.domain.variables,\n                                          data.domain.metas))):\n            return None\n        elif any(attr.is_continuous for attr in data.domain.variables):\n            return Discretize(\n                method=EqualFreq(n=4), remove_const=False, discretize_classes=True,\n                discretize_metas=True)(data)\n        else:\n            return data\n\n    def init_combos(self, data):\n        def set_combos(value):\n            self.model_1.set_domain(value)\n            self.model_234.set_domain(value)\n            self.color_model.set_domain(value)\n\n        if data is None:\n            set_combos(None)\n            self.variable1 = self.variable2 = self.variable3 \\\n                = self.variable4 = self.variable_color = None\n            return\n        set_combos(self.data.domain)\n\n        if len(self.model_1) > 0:\n            self.variable1 = self.model_1[0]\n            self.variable2 = self.model_1[min(1, len(self.model_1) - 1)]\n        self.variable3 = self.variable4 = None\n        self.variable_color = self.data.domain.class_var  # None is OK, too\n\n    def get_disc_attr_list(self):\n        return [self.discrete_data.domain[var.name]\n                for var in (self.variable1, self.variable2,\n                            self.variable3, self.variable4)\n                if var]\n\n    def set_attr(self, *attrs):\n        self.variable1, self.variable2, self.variable3, self.variable4 = [\n            attr and self.data.domain[attr.name] for attr in attrs]\n        self.reset_graph()\n\n    def attr_changed(self):\n        self.attrs_changed_manually.emit(self.get_disc_attr_list())\n        self.reset_graph()\n\n    def resizeEvent(self, e):\n        OWWidget.resizeEvent(self, e)\n        self.update_graph()\n\n    def showEvent(self, ev):\n        OWWidget.showEvent(self, ev)\n        self.update_graph()\n\n    @Inputs.data\n    def set_data(self, data):\n        if isinstance(data, SqlTable) and data.approx_len() > LARGE_TABLE:\n            data = data.sample_time(DEFAULT_SAMPLE_TIME)\n\n        self.closeContext()\n        self.data = data\n\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(\n            self.data is not None and len(self.data) > 1\n            and len(self.data.domain.attributes) >= 1)\n\n        if self.data is None:\n            self.discrete_data = None\n            self.init_combos(None)\n            return\n\n        self.init_combos(self.data)\n        self.openContext(self.data)\n        self.__pending_selection = self.selection\n\n    @Inputs.data_subset\n    def set_subset_data(self, data):\n        self.subset_data = data\n\n    # this is called by widget after setData and setSubsetData are called.\n    # this way the graph is updated only once\n    def handleNewSignals(self):\n        self.Warning.incompatible_subset.clear()\n        self.subset_indices = None\n        if self.data is not None and self.subset_data:\n            transformed = self.subset_data.transform(self.data.domain)\n            if np.all(np.isnan(transformed.X)) \\\n                    and np.all(np.isnan(transformed.Y)):\n                self.Warning.incompatible_subset()\n            else:\n                indices = {e.id for e in transformed}\n                self.subset_indices = [ex.id in indices for ex in self.data]\n        self.set_color_data()\n        self.reset_graph()\n\n    def clear_selection(self):\n        self.selection = set()\n        self.update_selection_rects()\n        self.send_selection()\n\n    def coloring_changed(self):\n        self.vizrank.coloring_changed()\n        self.update_graph()\n\n    def reset_graph(self):\n        self.clear_selection()\n        self.update_graph()\n        if self.__pending_selection is not None:\n            self.selection = self.__pending_selection\n            self.__pending_selection = None\n            self.update_selection_rects()\n            self.send_selection()\n\n    def set_color_data(self):\n        if self.data is None:\n            return\n        self.bar_button.setEnabled(self.variable_color is not None)\n        attrs = [v for v in self.model_1 if v and v is not self.variable_color]\n        domain = Domain(attrs, self.variable_color, None)\n        self.color_data = self.data.from_table(domain, self.data)\n        self.discrete_data = self._get_discrete_data(self.color_data)\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(True)\n        self.coloring_changed()\n\n    def update_selection_rects(self):\n        pens = (QPen(), QPen(Qt.black, 3, Qt.DotLine))\n        for i, (_, _, area) in enumerate(self.areas):\n            area.setPen(pens[i in self.selection])\n\n    def select_area(self, index, ev):\n        if ev.button() != Qt.LeftButton:\n            return\n        if ev.modifiers() & Qt.ControlModifier:\n            self.selection ^= {index}\n        else:\n            self.selection = {index}\n        self.update_selection_rects()\n        self.send_selection()\n\n    def send_selection(self):\n        if not self.selection or self.data is None:\n            self.Outputs.selected_data.send(None)\n            self.Outputs.annotated_data.send(\n                create_annotated_table(self.data, []))\n            return\n        filters = []\n        self.Warning.no_cont_selection_sql.clear()\n        if self.discrete_data is not self.data:\n            if isinstance(self.data, SqlTable):\n                self.Warning.no_cont_selection_sql()\n        for i in self.selection:\n            cols, vals, _ = self.areas[i]\n            filters.append(\n                filter.Values(\n                    filter.FilterDiscrete(col, [val])\n                    for col, val in zip(cols, vals)))\n        if len(filters) > 1:\n            filters = filter.Values(filters, conjunction=False)\n        else:\n            filters = filters[0]\n        selection = filters(self.discrete_data)\n        idset = set(selection.ids)\n        sel_idx = [i for i, id in enumerate(self.data.ids) if id in idset]\n        if self.discrete_data is not self.data:\n            selection = self.data[sel_idx]\n        self.Outputs.selected_data.send(selection)\n        self.Outputs.annotated_data.send(\n            create_annotated_table(self.data, sel_idx))\n\n    def send_report(self):\n        self.report_plot(self.canvas)\n\n    def update_graph(self):\n        spacing = self.SPACING\n        bar_width = self.BAR_WIDTH\n\n        def get_counts(attr_vals, values):\n            \"\"\"Calculate rectangles' widths; if all are 0, they are set to 1.\"\"\"\n            if not attr_vals:\n                counts = [conditionaldict[val] for val in values]\n            else:\n                counts = [conditionaldict[attr_vals + \"-\" + val]\n                          for val in values]\n            total = sum(counts)\n            if total == 0:\n                counts = [1] * len(values)\n                total = sum(counts)\n            return total, counts\n\n        def draw_data(attr_list, x0_x1, y0_y1, side, condition,\n                      total_attrs, used_attrs, used_vals, attr_vals=\"\"):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if conditionaldict[attr_vals] == 0:\n                add_rect(x0, x1, y0, y1, \"\",\n                         used_attrs, used_vals, attr_vals=attr_vals)\n                # store coordinates for later drawing of labels\n                draw_text(side, attr_list[0], (x0, x1), (y0, y1), total_attrs,\n                          used_attrs, used_vals, attr_vals)\n                return\n\n            attr = attr_list[0]\n            # how much smaller rectangles do we draw\n            edge = len(attr_list) * spacing\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]  # reverse names if necessary\n\n            if side % 2 == 0:  # we are drawing on the x axis\n                # remove the space needed for separating different attr. values\n                whole = max(0, (x1 - x0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (x1 - x0) / float(len(values) - 1)\n            else:  # we are drawing on the y axis\n                whole = max(0, (y1 - y0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (y1 - y0) / float(len(values) - 1)\n\n            total, counts = get_counts(attr_vals, values)\n\n            # when visualizing the third attribute and the first attribute has\n            # the last value, reverse the order in which the boxes are drawn;\n            # otherwise, if the last cell, nearest to the labels of the fourth\n            # attribute, is empty, we wouldn't be able to position the labels\n            valrange = list(range(len(values)))\n            if len(attr_list + used_attrs) == 4 and len(used_attrs) == 2:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] == attr1values[-1]:\n                    valrange = valrange[::-1]\n\n            for i in valrange:\n                start = i * edge + whole * float(sum(counts[:i]) / total)\n                end = i * edge + whole * float(sum(counts[:i + 1]) / total)\n                val = values[i]\n                htmlval = to_html(val)\n                newattrvals = attr_vals + \"-\" + val if attr_vals else val\n\n                tooltip = \"{}&nbsp;&nbsp;&nbsp;&nbsp;{}: <b>{}</b><br/>\".format(\n                    condition, attr.name, htmlval)\n                attrs = used_attrs + [attr]\n                vals = used_vals + [val]\n                args = attrs, vals, newattrvals\n                if side % 2 == 0:  # if we are moving horizontally\n                    if len(attr_list) == 1:\n                        add_rect(x0 + start, x0 + end, y0, y1, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0 + start, x0 + end), (y0, y1),\n                            side + 1, tooltip, total_attrs, *args)\n                else:\n                    if len(attr_list) == 1:\n                        add_rect(x0, x1, y0 + start, y0 + end, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0, x1), (y0 + start, y0 + end),\n                            side + 1, tooltip, total_attrs, *args)\n            draw_text(side, attr_list[0], (x0, x1), (y0, y1),\n                      total_attrs, used_attrs, used_vals, attr_vals)\n\n        def draw_text(side, attr, x0_x1, y0_y1,\n                      total_attrs, used_attrs, used_vals, attr_vals):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if side in drawn_sides:\n                return\n\n            # the text on the right will be drawn when we are processing\n            # visualization of the last value of the first attribute\n            if side == 3:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] != attr1values[-1]:\n                    return\n\n            if not conditionaldict[attr_vals]:\n                if side not in draw_positions:\n                    draw_positions[side] = (x0, x1, y0, y1)\n                return\n            else:\n                if side in draw_positions:\n                    # restore the positions of attribute values and name\n                    (x0, x1, y0, y1) = draw_positions[side]\n\n            drawn_sides.add(side)\n\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]\n\n            spaces = spacing * (total_attrs - side) * (len(values) - 1)\n            width = x1 - x0 - spaces * (side % 2 == 0)\n            height = y1 - y0 - spaces * (side % 2 == 1)\n\n            # calculate position of first attribute\n            currpos = 0\n            total, counts = get_counts(attr_vals, values)\n            aligns = [Qt.AlignTop | Qt.AlignHCenter,\n                      Qt.AlignRight | Qt.AlignVCenter,\n                      Qt.AlignBottom | Qt.AlignHCenter,\n                      Qt.AlignLeft | Qt.AlignVCenter]\n            align = aligns[side]\n            for i, val in enumerate(values):\n                if distributiondict[val] != 0:\n                    perc = counts[i] / float(total)\n                    rwidth = width * perc\n                    xs = [x0 + currpos + rwidth / 2,\n                          x0 - self.ATTR_VAL_OFFSET,\n                          x0 + currpos + rwidth / 2,\n                          x1 + self.ATTR_VAL_OFFSET]\n                    ys = [y1 + self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc,\n                          y0 - self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc]\n\n                    CanvasText(\n                        self.canvas, val, xs[side], ys[side], align,\n                        max_width=rwidth if side == 0 else None)\n                    space = height if side % 2 else width\n                    currpos += perc * space + spacing * (total_attrs - side)\n\n            xs = [x0 + (x1 - x0) / 2,\n                  x0 - max_ylabel_w1 - self.ATTR_VAL_OFFSET,\n                  x0 + (x1 - x0) / 2,\n                  x1 + max_ylabel_w2 + self.ATTR_VAL_OFFSET]\n            ys = [y1 + self.ATTR_VAL_OFFSET + self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2,\n                  y0 - self.ATTR_VAL_OFFSET - self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2]\n            CanvasText(\n                self.canvas, attr.name, xs[side], ys[side], align, bold=True,\n                vertical=side % 2)\n\n        def add_rect(x0, x1, y0, y1, condition,\n                     used_attrs, used_vals, attr_vals=\"\"):\n            area_index = len(self.areas)\n            x1 += (x0 == x1)\n            y1 += (y0 == y1)\n            # rectangles of width and height 1 are not shown - increase\n            y1 += (x1 - x0 + y1 - y0 == 2)\n            colors = class_var and [QColor(*col) for col in class_var.colors]\n\n            def select_area(_, ev):\n                self.select_area(area_index, ev)\n\n            def rect(x, y, w, h, z, pen_color=None, brush_color=None, **args):\n                if pen_color is None:\n                    return CanvasRectangle(\n                        self.canvas, x, y, w, h, z=z, onclick=select_area,\n                        **args)\n                if brush_color is None:\n                    brush_color = pen_color\n                return CanvasRectangle(\n                    self.canvas, x, y, w, h, pen_color, brush_color, z=z,\n                    onclick=select_area, **args)\n\n            def line(x1, y1, x2, y2):\n                r = QGraphicsLineItem(x1, y1, x2, y2, None)\n                self.canvas.addItem(r)\n                r.setPen(QPen(Qt.white, 2))\n                r.setZValue(30)\n\n            outer_rect = rect(x0, y0, x1 - x0, y1 - y0, 30)\n            self.areas.append((used_attrs, used_vals, outer_rect))\n            if not conditionaldict[attr_vals]:\n                return\n\n            if self.variable_color is None:\n                s = sum(apriori_dists[0])\n                expected = s * reduce(\n                    mul,\n                    (apriori_dists[i][used_vals[i]] / float(s)\n                     for i in range(len(used_vals))))\n                actual = conditionaldict[attr_vals]\n                pearson = float((actual - expected) / sqrt(expected))\n                if pearson == 0:\n                    ind = 0\n                else:\n                    ind = max(0, min(int(log(abs(pearson), 2)), 3))\n                color = [self.RED_COLORS, self.BLUE_COLORS][pearson > 0][ind]\n                rect(x0, y0, x1 - x0, y1 - y0, -20, color)\n                outer_rect.setToolTip(\n                    condition + \"<hr/>\" +\n                    \"Expected instances: %.1f<br>\"\n                    \"Actual instances: %d<br>\"\n                    \"Standardized (Pearson) residual: %.1f\" %\n                    (expected, conditionaldict[attr_vals], pearson))\n            else:\n                cls_values = get_variable_values_sorted(class_var)\n                prior = get_distribution(data, class_var.name)\n                total = 0\n                for i, value in enumerate(cls_values):\n                    val = conditionaldict[attr_vals + \"-\" + value]\n                    if val == 0:\n                        continue\n                    if i == len(cls_values) - 1:\n                        v = y1 - y0 - total\n                    else:\n                        v = ((y1 - y0) * val) / conditionaldict[attr_vals]\n                    rect(x0, y0 + total, x1 - x0, v, -20, colors[i])\n                    total += v\n\n                if self.use_boxes and \\\n                        abs(x1 - x0) > bar_width and abs(y1 - y0) > bar_width:\n                    total = 0\n                    line(x0 + bar_width, y0, x0 + bar_width, y1)\n                    n = sum(prior)\n                    for i, (val, color) in enumerate(zip(prior, colors)):\n                        if i == len(prior) - 1:\n                            h = y1 - y0 - total\n                        else:\n                            h = (y1 - y0) * val / n\n                        rect(x0, y0 + total, bar_width, h, 20, color)\n                        total += h\n\n                if conditionalsubsetdict:\n                    if conditionalsubsetdict[attr_vals]:\n                        if self.subset_indices is not None:\n                            line(x1 - bar_width, y0, x1 - bar_width, y1)\n                            total = 0\n                            n = conditionalsubsetdict[attr_vals]\n                            if n:\n                                for i, (cls, color) in \\\n                                        enumerate(zip(cls_values, colors)):\n                                    val = conditionalsubsetdict[\n                                        attr_vals + \"-\" + cls]\n                                    if val == 0:\n                                        continue\n                                    if i == len(prior) - 1:\n                                        v = y1 - y0 - total\n                                    else:\n                                        v = ((y1 - y0) * val) / n\n                                    rect(x1 - bar_width, y0 + total,\n                                         bar_width, v, 15, color)\n                                    total += v\n\n                actual = [conditionaldict[attr_vals + \"-\" + cls_values[i]]\n                          for i in range(len(prior))]\n                n_actual = sum(actual)\n                if n_actual > 0:\n                    apriori = [prior[key] for key in cls_values]\n                    n_apriori = sum(apriori)\n                    text = \"<br/>\".join(\n                        \"<b>%s</b>: %d / %.1f%% (Expected %.1f / %.1f%%)\" %\n                        (cls, act, 100.0 * act / n_actual,\n                         apr / n_apriori * n_actual, 100.0 * apr / n_apriori)\n                        for cls, act, apr in zip(cls_values, actual, apriori))\n                else:\n                    text = \"\"\n                outer_rect.setToolTip(\n                    \"{}<hr>Instances: {}<br><br>{}\".format(\n                        condition, n_actual, text[:-4]))\n\n        def create_legend():\n            if self.variable_color is None:\n                names = [\"<-8\", \"-8:-4\", \"-4:-2\", \"-2:2\", \"2:4\", \"4:8\", \">8\",\n                         \"Residuals:\"]\n                colors = self.RED_COLORS[::-1] + self.BLUE_COLORS[1:]\n                edges = repeat(Qt.black)\n            else:\n                names = get_variable_values_sorted(class_var)\n                edges = colors = [QColor(*col) for col in class_var.colors]\n\n            items = []\n            size = 8\n            for name, color, edgecolor in zip(names, colors, edges):\n                item = QGraphicsItemGroup()\n                item.addToGroup(\n                    CanvasRectangle(None, -size / 2, -size / 2, size, size,\n                                    edgecolor, color))\n                item.addToGroup(\n                    CanvasText(None, name, size, 0, Qt.AlignVCenter))\n                items.append(item)\n            return wrap_legend_items(\n                items, hspacing=20, vspacing=16 + size,\n                max_width=self.canvas_view.width() - xoff)\n\n        self.canvas.clear()\n        self.areas = []\n\n        data = self.discrete_data\n        if data is None:\n            return\n        attr_list = self.get_disc_attr_list()\n        class_var = data.domain.class_var\n        if class_var:\n            sql = isinstance(data, SqlTable)\n            name = not sql and data.name\n            # save class_var because it is removed in the next line\n            data = data[:, attr_list + [class_var]]\n            data.domain.class_var = class_var\n            if not sql:\n                data.name = name\n        else:\n            data = data[:, attr_list]\n        # TODO: check this\n        # data = Preprocessor_dropMissing(data)\n        if len(data) == 0:\n            self.Warning.no_valid_data()\n            return\n        else:\n            self.Warning.no_valid_data.clear()\n\n        attrs = [attr for attr in attr_list if not attr.values]\n        if attrs:\n            CanvasText(self.canvas,\n                       \"Feature {} has no values\".format(attrs[0]),\n                       (self.canvas_view.width() - 120) / 2,\n                       self.canvas_view.height() / 2)\n            return\n        if self.variable_color is None:\n            apriori_dists = [get_distribution(data, attr) for attr in attr_list]\n        else:\n            apriori_dists = []\n\n        def get_max_label_width(attr):\n            values = get_variable_values_sorted(attr)\n            maxw = 0\n            for val in values:\n                t = CanvasText(self.canvas, val, 0, 0, bold=0, show=False)\n                maxw = max(int(t.boundingRect().width()), maxw)\n            return maxw\n\n        xoff = 20\n\n        # get the maximum width of rectangle\n        width = 20\n        max_ylabel_w1 = max_ylabel_w2 = 0\n        if len(attr_list) > 1:\n            text = CanvasText(self.canvas, attr_list[1].name, bold=1, show=0)\n            max_ylabel_w1 = min(get_max_label_width(attr_list[1]), 150)\n            width = 5 + text.boundingRect().height() + \\\n                self.ATTR_VAL_OFFSET + max_ylabel_w1\n            xoff = width\n            if len(attr_list) == 4:\n                text = CanvasText(self.canvas, attr_list[3].name, bold=1, show=0)\n                max_ylabel_w2 = min(get_max_label_width(attr_list[3]), 150)\n                width += text.boundingRect().height() + \\\n                    self.ATTR_VAL_OFFSET + max_ylabel_w2 - 10\n\n        legend = create_legend()\n\n        # get the maximum height of rectangle\n        yoff = 45\n        legendoff = yoff + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35\n        square_size = min(self.canvas_view.width() - width - 20,\n                          self.canvas_view.height() - legendoff\n                          - legend.boundingRect().height())\n\n        if square_size < 0:\n            return  # canvas is too small to draw rectangles\n        self.canvas_view.setSceneRect(\n            0, 0, self.canvas_view.width(), self.canvas_view.height())\n\n        drawn_sides = set()\n        draw_positions = {}\n\n        conditionaldict, distributiondict = \\\n            get_conditional_distribution(data, attr_list)\n        conditionalsubsetdict = None\n        if self.subset_indices:\n            conditionalsubsetdict, _ = get_conditional_distribution(\n                self.discrete_data[self.subset_indices], attr_list)\n\n        # draw rectangles\n        draw_data(\n            attr_list, (xoff, xoff + square_size), (yoff, yoff + square_size),\n            0, \"\", len(attr_list), [], [])\n\n        self.canvas.addItem(legend)\n        legend.setPos(\n            xoff - legend.boundingRect().x()\n            + max(0, (square_size - legend.boundingRect().width()) / 2),\n            legendoff + square_size)\n        self.update_selection_rects()\n\n    @classmethod\n    def migrate_context(cls, context, version):\n        if version < 2:\n            settings.migrate_str_to_variable(context, none_placeholder=\"(None)\")",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 269,
                                "end_lineno": 951,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "# pylint: disable=missing-docstring,protected-access\nimport time\nimport unittest\nfrom unittest.mock import patch\n\nimport numpy as np\n\nfrom AnyQt.QtCore import QEvent, QPoint, Qt\nfrom AnyQt.QtGui import QMouseEvent\n\nfrom Orange.data import Table, DiscreteVariable, Domain, ContinuousVariable, \\\n    StringVariable\nfrom Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin\nfrom Orange.widgets.visualize.owmosaic import OWMosaicDisplay\nfrom Orange.widgets.tests.utils import simulate\n\n\nclass TestOWMosaicDisplay(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.signal_name = \"Data\"\n        cls.signal_data = cls.data\n\n    def setUp(self):\n        self.widget = self.create_widget(OWMosaicDisplay)\n\n    def test_no_data(self):\n        \"\"\"Check that the widget doesn't crash on empty data\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[:0])\n\n    def test_empty_column(self):\n        \"\"\"Check that the widget doesn't crash if the columns are empty\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[:, :0])\n\n    def _select_data(self):\n        self.widget.select_area(1, QMouseEvent(\n            QEvent.MouseButtonPress, QPoint(), Qt.LeftButton,\n            Qt.LeftButton, Qt.KeyboardModifiers()))\n        return [2, 3, 9, 23, 29, 30, 34, 35, 37, 42, 47, 49]\n\n    def test_continuous_metas(self):\n        \"\"\"Check widget for dataset with continuous metas\"\"\"\n        domain = Domain([ContinuousVariable(\"c1\")],\n                        metas=[ContinuousVariable(\"m\")])\n        data = Table(domain, np.arange(6).reshape(6, 1),\n                     metas=np.arange(6).reshape(6, 1))\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_string_meta(self):\n        \"\"\"Check widget for dataset with only one string meta\"\"\"\n        domain = Domain([], metas=[StringVariable(\"m\")])\n        data = Table(domain, np.empty((6, 0)),\n                     metas=np.array([\"meta\"] * 6).reshape(6, 1))\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_missing_values(self):\n        \"\"\"Check widget for dataset with missing values\"\"\"\n        data = Table(Domain([DiscreteVariable(\"c1\", [])]),\n                     np.array([np.nan] * 6)[:, None])\n        self.send_signal(self.widget.Inputs.data, data)\n\n        # missings in class variable\n        attrs = [DiscreteVariable(\"c1\", [\"a\", \"b\", \"c\"])]\n        class_var = DiscreteVariable(\"cls\", [])\n        X = np.array([1, 2, 0, 1, 0, 2])[:, None]\n        data = Table(Domain(attrs, class_var), X, np.array([np.nan] * 6))\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_keyerror(self):\n        \"\"\"gh-2014\n        Check if it works when a table has only one row or duplicates.\n        Discretizer must have remove_const set to False.\n        \"\"\"\n        data = Table(\"iris\")\n        data = data[0:1]\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_combos_and_mosaic(self):\n        \"\"\"\n        Text in combos is wiped away when there is no data and mosaic disappears as well.\n        GH-2459\n        GH-2462\n        \"\"\"\n        def assertCount(cb_color, cb_attr, areas):\n            self.assertEqual(len(self.widget.areas), areas)\n            self.assertEqual(self.widget.cb_attr_color.count(), cb_color)\n            for combo, cb_count in zip(self.widget.attr_combos, cb_attr):\n                self.assertEqual(combo.count(), cb_count)\n\n        data = Table(\"iris\")\n        assertCount(1, [0, 1, 1, 1], 0)\n        self.send_signal(self.widget.Inputs.data, data)\n        assertCount(6, [5, 6, 6, 6], 16)\n        self.send_signal(self.widget.Inputs.data, None)\n        assertCount(1, [0, 1, 1, 1], 0)\n\n    @patch('Orange.widgets.visualize.owmosaic.CanvasRectangle')\n    @patch('Orange.widgets.visualize.owmosaic.QGraphicsItemGroup.addToGroup')\n    def test_different_number_of_attributes(self, _, canvas_rectangle):\n        domain = Domain([DiscreteVariable(c, values=\"01\") for c in \"abcd\"])\n        data = Table.from_list(\n            domain,\n            [list(\"{:04b}\".format(i)[-4:]) for i in range(16)])\n        self.send_signal(self.widget.Inputs.data, data)\n        widget = self.widget\n        widget.variable2 = widget.variable3 = widget.variable4 = None\n        for i, attr in enumerate(data.domain[:4], start=1):\n            canvas_rectangle.reset_mock()\n            setattr(self.widget, \"variable\" + str(i), attr)\n            self.widget.update_graph()\n            self.assertEqual(canvas_rectangle.call_count, 7 + 2 ** (i + 1))\n\n    def test_change_domain(self):\n        \"\"\"Test for GH-3419 fix\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[:, :2])\n        subset = self.data[:1, 2:3]\n        self.send_signal(self.widget.Inputs.data, subset)\n        output = self.get_output(self.widget.Outputs.annotated_data)\n        np.testing.assert_array_equal(output.X, subset.X)\n\n    def test_subset(self):\n        \"\"\"Test for GH-3416 fix\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data)\n        self.send_signal(self.widget.Inputs.data_subset, self.data[10:])\n        self.send_signal(self.widget.Inputs.data, self.data[:1])\n        output = self.get_output(self.widget.Outputs.annotated_data)\n        np.testing.assert_array_equal(output.X, self.data[:1].X)\n\n    @patch('Orange.widgets.visualize.owmosaic.MosaicVizRank.on_manual_change')\n    def test_vizrank_receives_manual_change(self, on_manual_change):\n        # Recreate the widget so the patch kicks in\n        self.widget = self.create_widget(OWMosaicDisplay)\n        data = Table(\"iris.tab\")\n        self.send_signal(self.widget.Inputs.data, data)\n        self.widget.variable1 = data.domain[0]\n        self.widget.variable2 = data.domain[1]\n        simulate.combobox_activate_index(self.widget.controls.variable2, 3)\n        self.assertEqual(self.widget.variable2, data.domain[2])\n        call_args = on_manual_change.call_args[0][0]\n        self.assertEqual(len(call_args), 2)\n        self.assertEqual(call_args[0].name, data.domain[0].name)\n        self.assertEqual(call_args[1].name, data.domain[2].name)\n\n\n# Derive from WidgetTest to simplify creation of the Mosaic widget, although\n# we are actually testing the MosaicVizRank dialog and not the widget\n\n# These tests are rather crude: the main challenge of this widget is to handle\n# user interactions and interrupts, e.g. changing the widget settings or\n# getting new data while the computation is running.\nclass MosaicVizRankTests(WidgetTest):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        cls.iris = Table(\"iris.tab\")\n        dom = Domain(cls.iris.domain.attributes, [])\n        cls.iris_no_class = Table(dom, cls.iris)\n\n    def setUp(self):\n        self.widget = self.create_widget(OWMosaicDisplay)\n        self.vizrank = self.widget.vizrank\n\n    def tearDown(self):\n        self.widget.onDeleteWidget()\n        super().tearDown()\n\n    def test_count(self):\n        \"\"\"MosaicVizrank correctly computes the number of combinations\"\"\"\n        vizrank = self.vizrank\n\n        data = self.iris\n        attributes = [v for v in data.domain.attributes[1:]]\n        metas = [data.domain.attributes[0]]\n        domain = Domain(attributes, data.domain.class_var, metas)\n        new_data = data.from_table(domain, data)\n        self.send_signal(self.widget.Inputs.data, new_data)\n\n        for data in [self.iris, new_data]:\n            self.send_signal(self.widget.Inputs.data, data)\n\n            simulate.combobox_activate_index(self.widget.controls.variable_color, 0, 0)\n            vizrank.max_attrs = 2\n            self.assertEqual(vizrank.state_count(), 10)  # 5x4 / 2\n            vizrank.max_attrs = 3\n            self.assertEqual(vizrank.state_count(), 20)  # above + 5x4x3 / 2x3\n            vizrank.max_attrs = 4\n            self.assertEqual(vizrank.state_count(), 25)  # above + 5x4x3x2 / 2x3x4\n\n            simulate.combobox_activate_index(self.widget.controls.variable_color, 2, 0)\n            vizrank.max_attrs = 2\n            self.assertEqual(vizrank.state_count(), 10)  # 4 + 4x3 / 2\n            vizrank.max_attrs = 3\n            self.assertEqual(vizrank.state_count(), 14)  # above + 4x3x2 / 3x2\n            vizrank.max_attrs = 4\n            self.assertEqual(vizrank.state_count(), 15)  # above + 4x3x2x1 / 2x3x4\n\n            self.send_signal(self.widget.Inputs.data, self.iris_no_class)\n            simulate.combobox_activate_index(self.widget.controls.variable_color, 0, 0)\n            vizrank.max_attrs = 2\n            self.assertEqual(vizrank.state_count(), 6)  # 4x3 / 2\n            vizrank.max_attrs = 3\n            self.assertEqual(vizrank.state_count(), 10)  # above + 4x3x2 / 3x2\n            vizrank.max_attrs = 4\n            self.assertEqual(vizrank.state_count(), 11)  # above + 4x3x2x1 / 2x3x4\n\n    def test_iteration(self):\n        \"\"\"MosaicVizrank correctly iterates through states\"\"\"\n        widget = self.widget\n        vizrank = self.vizrank\n        self.send_signal(self.widget.Inputs.data, self.iris)\n        vizrank.compute_attr_order()\n\n        vizrank.max_attrs = 4\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states(None)],\n                         [[0], [1], [2], [3],\n                          [0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3],\n                          [0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3],\n                          [0, 1, 2, 3]])\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states([0, 3])],\n                         [[0, 3], [1, 3], [2, 3],\n                          [0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3],\n                          [0, 1, 2, 3]])\n\n        vizrank.max_attrs = 2\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states(None)],\n                         [[0], [1], [2], [3],\n                          [0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]])\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states([0, 3])],\n                         [[0, 3], [1, 3], [2, 3]])\n\n        widget.variable_color = None\n        vizrank.max_attrs = 4\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states(None)],\n                         [[0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3],\n                          [0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3],\n                          [0, 1, 2, 3]])\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states([0, 3])],\n                         [[0, 3], [1, 3], [2, 3],\n                          [0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3],\n                          [0, 1, 2, 3]])\n\n        vizrank.max_attrs = 2\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states(None)],\n                         [[0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]])\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states([0, 3])],\n                         [[0, 3], [1, 3], [2, 3]])\n\n    def test_row_for_state(self):\n        \"\"\"MosaicVizrank returns table row corresponding to the state\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.iris)\n        self.vizrank.attr_ordering = [DiscreteVariable(n) for n in \"abcd\"]\n        items = self.vizrank.row_for_state(0, [1, 3, 0])\n        self.assertEqual(len(items), 1)\n        item = items[0]\n        self.assertEqual(item.text(), \"a, b, d\")\n        self.assertEqual(\n            item.data(self.vizrank._AttrRole),\n            tuple(self.vizrank.attr_ordering[i] for i in [0, 1, 3]))\n\n    @unittest.skip(\"Appveyor sometimes fails.\")\n    def test_does_not_crash(self):\n        \"\"\"MosaicVizrank computes rankings without crashing\"\"\"\n        widget = self.widget\n        vizrank = self.vizrank\n        self.send_signal(self.widget.Inputs.data, self.iris)\n        vizrank.max_attrs = 2\n\n        widget.interior_coloring = widget.PEARSON\n        vizrank.toggle()\n        time.sleep(0.5)\n        self.assertEqual(vizrank.rank_model.rowCount(), 10)  # 4x5 / 2\n        widget.interior_coloring = widget.CLASS_DISTRIBUTION\n        vizrank.toggle()\n        time.sleep(0.5)\n        self.assertEqual(vizrank.rank_model.rowCount(), 10)  # 4 + 4x5 / 2\n\n        self.send_signal(self.widget.Inputs.data, self.iris_no_class)\n        vizrank.toggle()\n\n    def test_does_not_crash_cont_class(self):\n        \"\"\"MosaicVizrank computes rankings without crashing\"\"\"\n        data = Table(\"housing.tab\")\n        self.send_signal(self.widget.Inputs.data, data)\n        self.vizrank.toggle()\n\n    def test_pause_continue(self):\n        data = Table(\"housing.tab\")\n        self.send_signal(self.widget.Inputs.data, data)\n        self.vizrank.toggle()  # start\n        self.process_events(until=lambda: self.vizrank.saved_progress > 5)\n        self.vizrank.toggle()  # stop\n        self.process_events(until=lambda: not self.vizrank.keep_running)\n        self.vizrank.toggle()  # continue\n        self.process_events(until=lambda: self.vizrank.saved_progress > 20)\n\n    def test_finished(self):\n        data = Table(\"iris.tab\")\n        self.send_signal(self.widget.Inputs.data, data)\n        self.vizrank.toggle()\n        self.process_events(until=lambda: not self.vizrank.keep_running)\n        self.assertEqual(len(self.vizrank.scores), self.vizrank.state_count())\n\n    def test_nan_column(self):\n        \"\"\"\n        A column with only NaN-s used to throw an error\n        (ZeroDivisionError) when loaded into widget.\n        GH-2046\n        \"\"\"\n        table = Table(\n            Domain(\n                [ContinuousVariable(\"a\"), ContinuousVariable(\"b\"), ContinuousVariable(\"c\")]),\n            np.array([\n                [0, np.NaN, 0],\n                [0, np.NaN, 0],\n                [0, np.NaN, 0]\n            ])\n        )\n        self.send_signal(self.widget.Inputs.data, table)\n\n    def test_color_combo(self):\n        \"\"\"\n        Color combo enables to select class values. Checks if class values\n        are selected correctly.\n        GH-2133\n        GH-2036\n        \"\"\"\n        RESULTS = [[0, 2, 6], [0, 3, 10], [0, 4, 11],\n                   [1, 2, 6], [1, 3, 7], [1, 4, 7]]\n        table = Table(\"titanic\")\n        self.send_signal(self.widget.Inputs.data, table)\n        color_vars = [\"(Pearson residuals)\"] + [str(x) for x in table.domain.variables]\n        for i, cv in enumerate(color_vars):\n            idx = self.widget.cb_attr_color.findText(cv)\n            self.widget.cb_attr_color.setCurrentIndex(idx)\n            color = self.widget.cb_attr_color.currentText()\n            simulate.combobox_activate_index(self.widget.controls.variable_color, idx, 0)\n            discrete_data = self.widget.discrete_data\n\n            if color == \"(Pearson residuals)\":\n                self.assertIsNone(discrete_data.domain.class_var)\n            else:\n                self.assertEqual(color, str(discrete_data.domain.class_var))\n\n            output = self.get_output(\"Data\")\n            self.assertEqual(output.domain.class_var, table.domain.class_var)\n\n            for ma in range(2, 5):\n                self.vizrank.max_attrs = ma\n                sc = self.vizrank.state_count()\n                self.assertTrue([i > 0, ma, sc] in RESULTS)\n\n    def test_scores(self):\n        \"\"\"\n        Test scores without running vizrank.\n        GH-2299\n        GH-2036\n        \"\"\"\n        SCORES = {('status', ): 4.35e-40,\n                  ('sex', ): 6.18e-100,\n                  ('age', ): 2.82e-05,\n                  ('sex', 'status'): 1.06e-123,\n                  ('age', 'status'): 4.15e-47,\n                  ('age', 'sex'): 5.49e-102,\n                  ('age', 'sex', 'status'): 5.3e-128}\n        table = Table(\"titanic\")\n        self.send_signal(self.widget.Inputs.data, table)\n        self.vizrank.compute_attr_order()\n        self.widget.vizrank.max_attrs = 3\n        state = None\n        for state in self.vizrank.iterate_states(state):\n            self.vizrank.iterate_states(state)\n            attrlist = tuple(sorted(self.vizrank.attr_ordering[i].name for i in state))\n            sc = self.vizrank.compute_score(state)\n            self.assertTrue(np.allclose(sc, SCORES[attrlist], rtol=0.003, atol=0))\n\n    def test_subset_data(self):\n        \"\"\"\n        Mosaic should not crash on subset data and should properly interpret it.\n        GH-2515\n        GH-2528\n        \"\"\"\n        table_titanic = Table(\"titanic\")\n        self.send_signal(self.widget.Inputs.data, table_titanic)\n        self.send_signal(self.widget.Inputs.data_subset, table_titanic[::20])\n        table_housing = Table(\"housing\")\n        self.send_signal(self.widget.Inputs.data, table_housing)\n        self.send_signal(self.widget.Inputs.data_subset, table_housing[::20])\n\n    def test_incompatible_subset(self):\n        \"\"\"\n        Show warning when subset data is not compatible with data.\n        GH-2528\n        \"\"\"\n        table_titanic = Table(\"titanic\")\n        self.assertFalse(self.widget.Warning.incompatible_subset.is_shown())\n        self.send_signal(self.widget.Inputs.data, self.iris)\n        self.send_signal(self.widget.Inputs.data_subset, table_titanic)\n        self.assertTrue(self.widget.Warning.incompatible_subset.is_shown())\n        self.send_signal(self.widget.Inputs.data_subset, self.iris)\n        self.assertFalse(self.widget.Warning.incompatible_subset.is_shown())\n\n    def test_on_manual_change(self):\n        data = Table(\"iris.tab\")\n        self.send_signal(self.widget.Inputs.data, data)\n        self.vizrank.toggle()\n        self.process_events(until=lambda: not self.vizrank.keep_running)\n\n        model = self.vizrank.rank_model\n        attrs = model.data(model.index(3, 0), self.vizrank._AttrRole)\n        self.vizrank.on_manual_change(attrs)\n        selection = self.vizrank.rank_table.selectedIndexes()\n        self.assertEqual(len(selection), 1)\n        self.assertEqual(selection[0].row(), 3)\n\n        self.vizrank.on_manual_change(attrs[::-1])\n        selection = self.vizrank.rank_table.selectedIndexes()\n        self.assertEqual(len(selection), 0)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n",
            "new_file_content": "# pylint: disable=missing-docstring,protected-access\nimport time\nimport unittest\nfrom unittest.mock import patch\n\nimport numpy as np\n\nfrom AnyQt.QtCore import QEvent, QPoint, Qt\nfrom AnyQt.QtGui import QMouseEvent\n\nfrom Orange.data import Table, DiscreteVariable, Domain, ContinuousVariable, \\\n    StringVariable\nfrom Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin\nfrom Orange.widgets.visualize.owmosaic import OWMosaicDisplay\nfrom Orange.widgets.tests.utils import simulate\n\n\nclass TestOWMosaicDisplay(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.signal_name = \"Data\"\n        cls.signal_data = cls.data\n\n    def setUp(self):\n        self.widget = self.create_widget(OWMosaicDisplay)\n\n    def test_no_data(self):\n        \"\"\"Check that the widget doesn't crash on empty data\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[:0])\n\n    def test_empty_column(self):\n        \"\"\"Check that the widget doesn't crash if the columns are empty\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[:, :0])\n\n    def _select_data(self):\n        self.widget.select_area(1, QMouseEvent(\n            QEvent.MouseButtonPress, QPoint(), Qt.LeftButton,\n            Qt.LeftButton, Qt.KeyboardModifiers()))\n        return [2, 3, 9, 23, 29, 30, 34, 35, 37, 42, 47, 49]\n\n    def test_continuous_metas(self):\n        \"\"\"Check widget for dataset with continuous metas\"\"\"\n        domain = Domain([ContinuousVariable(\"c1\")],\n                        metas=[ContinuousVariable(\"m\")])\n        data = Table(domain, np.arange(6).reshape(6, 1),\n                     metas=np.arange(6).reshape(6, 1))\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_string_meta(self):\n        \"\"\"Check widget for dataset with only one string meta\"\"\"\n        domain = Domain([], metas=[StringVariable(\"m\")])\n        data = Table(domain, np.empty((6, 0)),\n                     metas=np.array([\"meta\"] * 6).reshape(6, 1))\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_missing_values(self):\n        \"\"\"Check widget for dataset with missing values\"\"\"\n        data = Table(Domain([DiscreteVariable(\"c1\", [])]),\n                     np.array([np.nan] * 6)[:, None])\n        self.send_signal(self.widget.Inputs.data, data)\n\n        # missings in class variable\n        attrs = [DiscreteVariable(\"c1\", [\"a\", \"b\", \"c\"])]\n        class_var = DiscreteVariable(\"cls\", [])\n        X = np.array([1, 2, 0, 1, 0, 2])[:, None]\n        data = Table(Domain(attrs, class_var), X, np.array([np.nan] * 6))\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_keyerror(self):\n        \"\"\"gh-2014\n        Check if it works when a table has only one row or duplicates.\n        Discretizer must have remove_const set to False.\n        \"\"\"\n        data = Table(\"iris\")\n        data = data[0:1]\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_combos_and_mosaic(self):\n        \"\"\"\n        Text in combos is wiped away when there is no data and mosaic disappears as well.\n        GH-2459\n        GH-2462\n        \"\"\"\n        def assertCount(cb_color, cb_attr, areas):\n            self.assertEqual(len(self.widget.areas), areas)\n            self.assertEqual(self.widget.cb_attr_color.count(), cb_color)\n            for combo, cb_count in zip(self.widget.attr_combos, cb_attr):\n                self.assertEqual(combo.count(), cb_count)\n\n        data = Table(\"iris\")\n        assertCount(1, [0, 1, 1, 1], 0)\n        self.send_signal(self.widget.Inputs.data, data)\n        assertCount(6, [5, 6, 6, 6], 16)\n        self.send_signal(self.widget.Inputs.data, None)\n        assertCount(1, [0, 1, 1, 1], 0)\n\n    @patch('Orange.widgets.visualize.owmosaic.CanvasRectangle')\n    @patch('Orange.widgets.visualize.owmosaic.QGraphicsItemGroup.addToGroup')\n    def test_different_number_of_attributes(self, _, canvas_rectangle):\n        domain = Domain([DiscreteVariable(c, values=\"01\") for c in \"abcd\"])\n        data = Table.from_list(\n            domain,\n            [list(\"{:04b}\".format(i)[-4:]) for i in range(16)])\n        self.send_signal(self.widget.Inputs.data, data)\n        widget = self.widget\n        widget.variable2 = widget.variable3 = widget.variable4 = None\n        for i, attr in enumerate(data.domain[:4], start=1):\n            canvas_rectangle.reset_mock()\n            setattr(self.widget, \"variable\" + str(i), attr)\n            self.widget.update_graph()\n            self.assertEqual(canvas_rectangle.call_count, 7 + 2 ** (i + 1))\n\n    def test_change_domain(self):\n        \"\"\"Test for GH-3419 fix\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[:, :2])\n        subset = self.data[:1, 2:3]\n        self.send_signal(self.widget.Inputs.data, subset)\n        output = self.get_output(self.widget.Outputs.annotated_data)\n        np.testing.assert_array_equal(output.X, subset.X)\n\n    def test_subset(self):\n        \"\"\"Test for GH-3416 fix\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data)\n        self.send_signal(self.widget.Inputs.data_subset, self.data[10:])\n        self.send_signal(self.widget.Inputs.data, self.data[:1])\n        output = self.get_output(self.widget.Outputs.annotated_data)\n        np.testing.assert_array_equal(output.X, self.data[:1].X)\n\n    @patch('Orange.widgets.visualize.owmosaic.MosaicVizRank.on_manual_change')\n    def test_vizrank_receives_manual_change(self, on_manual_change):\n        # Recreate the widget so the patch kicks in\n        self.widget = self.create_widget(OWMosaicDisplay)\n        data = Table(\"iris.tab\")\n        self.send_signal(self.widget.Inputs.data, data)\n        self.widget.variable1 = data.domain[0]\n        self.widget.variable2 = data.domain[1]\n        simulate.combobox_activate_index(self.widget.controls.variable2, 3)\n        self.assertEqual(self.widget.variable2, data.domain[2])\n        call_args = on_manual_change.call_args[0][0]\n        self.assertEqual(len(call_args), 2)\n        self.assertEqual(call_args[0].name, data.domain[0].name)\n        self.assertEqual(call_args[1].name, data.domain[2].name)\n\n    def test_selection_setting(self):\n        widget = self.widget\n        data = Table(\"iris.tab\")\n        self.send_signal(widget.Inputs.data, data)\n\n        widget.select_area(\n            1,\n            QMouseEvent(QEvent.MouseButtonPress, QPoint(), Qt.LeftButton,\n                        Qt.LeftButton, Qt.KeyboardModifiers()))\n\n        self.send_signal(widget.Inputs.data, None)\n        self.assertFalse(bool(widget.selection))\n        self.assertIsNone(self.get_output(widget.Outputs.selected_data))\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertEqual(widget.selection, {1})\n        self.assertIsNotNone(self.get_output(widget.Outputs.selected_data))\n\n\n# Derive from WidgetTest to simplify creation of the Mosaic widget, although\n# we are actually testing the MosaicVizRank dialog and not the widget\n\n# These tests are rather crude: the main challenge of this widget is to handle\n# user interactions and interrupts, e.g. changing the widget settings or\n# getting new data while the computation is running.\nclass MosaicVizRankTests(WidgetTest):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        cls.iris = Table(\"iris.tab\")\n        dom = Domain(cls.iris.domain.attributes, [])\n        cls.iris_no_class = Table(dom, cls.iris)\n\n    def setUp(self):\n        self.widget = self.create_widget(OWMosaicDisplay)\n        self.vizrank = self.widget.vizrank\n\n    def tearDown(self):\n        self.widget.onDeleteWidget()\n        super().tearDown()\n\n    def test_count(self):\n        \"\"\"MosaicVizrank correctly computes the number of combinations\"\"\"\n        vizrank = self.vizrank\n\n        data = self.iris\n        attributes = [v for v in data.domain.attributes[1:]]\n        metas = [data.domain.attributes[0]]\n        domain = Domain(attributes, data.domain.class_var, metas)\n        new_data = data.from_table(domain, data)\n        self.send_signal(self.widget.Inputs.data, new_data)\n\n        for data in [self.iris, new_data]:\n            self.send_signal(self.widget.Inputs.data, data)\n\n            simulate.combobox_activate_index(self.widget.controls.variable_color, 0, 0)\n            vizrank.max_attrs = 2\n            self.assertEqual(vizrank.state_count(), 10)  # 5x4 / 2\n            vizrank.max_attrs = 3\n            self.assertEqual(vizrank.state_count(), 20)  # above + 5x4x3 / 2x3\n            vizrank.max_attrs = 4\n            self.assertEqual(vizrank.state_count(), 25)  # above + 5x4x3x2 / 2x3x4\n\n            simulate.combobox_activate_index(self.widget.controls.variable_color, 2, 0)\n            vizrank.max_attrs = 2\n            self.assertEqual(vizrank.state_count(), 10)  # 4 + 4x3 / 2\n            vizrank.max_attrs = 3\n            self.assertEqual(vizrank.state_count(), 14)  # above + 4x3x2 / 3x2\n            vizrank.max_attrs = 4\n            self.assertEqual(vizrank.state_count(), 15)  # above + 4x3x2x1 / 2x3x4\n\n            self.send_signal(self.widget.Inputs.data, self.iris_no_class)\n            simulate.combobox_activate_index(self.widget.controls.variable_color, 0, 0)\n            vizrank.max_attrs = 2\n            self.assertEqual(vizrank.state_count(), 6)  # 4x3 / 2\n            vizrank.max_attrs = 3\n            self.assertEqual(vizrank.state_count(), 10)  # above + 4x3x2 / 3x2\n            vizrank.max_attrs = 4\n            self.assertEqual(vizrank.state_count(), 11)  # above + 4x3x2x1 / 2x3x4\n\n    def test_iteration(self):\n        \"\"\"MosaicVizrank correctly iterates through states\"\"\"\n        widget = self.widget\n        vizrank = self.vizrank\n        self.send_signal(self.widget.Inputs.data, self.iris)\n        vizrank.compute_attr_order()\n\n        vizrank.max_attrs = 4\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states(None)],\n                         [[0], [1], [2], [3],\n                          [0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3],\n                          [0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3],\n                          [0, 1, 2, 3]])\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states([0, 3])],\n                         [[0, 3], [1, 3], [2, 3],\n                          [0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3],\n                          [0, 1, 2, 3]])\n\n        vizrank.max_attrs = 2\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states(None)],\n                         [[0], [1], [2], [3],\n                          [0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]])\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states([0, 3])],\n                         [[0, 3], [1, 3], [2, 3]])\n\n        widget.variable_color = None\n        vizrank.max_attrs = 4\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states(None)],\n                         [[0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3],\n                          [0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3],\n                          [0, 1, 2, 3]])\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states([0, 3])],\n                         [[0, 3], [1, 3], [2, 3],\n                          [0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3],\n                          [0, 1, 2, 3]])\n\n        vizrank.max_attrs = 2\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states(None)],\n                         [[0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]])\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states([0, 3])],\n                         [[0, 3], [1, 3], [2, 3]])\n\n    def test_row_for_state(self):\n        \"\"\"MosaicVizrank returns table row corresponding to the state\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.iris)\n        self.vizrank.attr_ordering = [DiscreteVariable(n) for n in \"abcd\"]\n        items = self.vizrank.row_for_state(0, [1, 3, 0])\n        self.assertEqual(len(items), 1)\n        item = items[0]\n        self.assertEqual(item.text(), \"a, b, d\")\n        self.assertEqual(\n            item.data(self.vizrank._AttrRole),\n            tuple(self.vizrank.attr_ordering[i] for i in [0, 1, 3]))\n\n    @unittest.skip(\"Appveyor sometimes fails.\")\n    def test_does_not_crash(self):\n        \"\"\"MosaicVizrank computes rankings without crashing\"\"\"\n        widget = self.widget\n        vizrank = self.vizrank\n        self.send_signal(self.widget.Inputs.data, self.iris)\n        vizrank.max_attrs = 2\n\n        widget.interior_coloring = widget.PEARSON\n        vizrank.toggle()\n        time.sleep(0.5)\n        self.assertEqual(vizrank.rank_model.rowCount(), 10)  # 4x5 / 2\n        widget.interior_coloring = widget.CLASS_DISTRIBUTION\n        vizrank.toggle()\n        time.sleep(0.5)\n        self.assertEqual(vizrank.rank_model.rowCount(), 10)  # 4 + 4x5 / 2\n\n        self.send_signal(self.widget.Inputs.data, self.iris_no_class)\n        vizrank.toggle()\n\n    def test_does_not_crash_cont_class(self):\n        \"\"\"MosaicVizrank computes rankings without crashing\"\"\"\n        data = Table(\"housing.tab\")\n        self.send_signal(self.widget.Inputs.data, data)\n        self.vizrank.toggle()\n\n    def test_pause_continue(self):\n        data = Table(\"housing.tab\")\n        self.send_signal(self.widget.Inputs.data, data)\n        self.vizrank.toggle()  # start\n        self.process_events(until=lambda: self.vizrank.saved_progress > 5)\n        self.vizrank.toggle()  # stop\n        self.process_events(until=lambda: not self.vizrank.keep_running)\n        self.vizrank.toggle()  # continue\n        self.process_events(until=lambda: self.vizrank.saved_progress > 20)\n\n    def test_finished(self):\n        data = Table(\"iris.tab\")\n        self.send_signal(self.widget.Inputs.data, data)\n        self.vizrank.toggle()\n        self.process_events(until=lambda: not self.vizrank.keep_running)\n        self.assertEqual(len(self.vizrank.scores), self.vizrank.state_count())\n\n    def test_nan_column(self):\n        \"\"\"\n        A column with only NaN-s used to throw an error\n        (ZeroDivisionError) when loaded into widget.\n        GH-2046\n        \"\"\"\n        table = Table(\n            Domain(\n                [ContinuousVariable(\"a\"), ContinuousVariable(\"b\"), ContinuousVariable(\"c\")]),\n            np.array([\n                [0, np.NaN, 0],\n                [0, np.NaN, 0],\n                [0, np.NaN, 0]\n            ])\n        )\n        self.send_signal(self.widget.Inputs.data, table)\n\n    def test_color_combo(self):\n        \"\"\"\n        Color combo enables to select class values. Checks if class values\n        are selected correctly.\n        GH-2133\n        GH-2036\n        \"\"\"\n        RESULTS = [[0, 2, 6], [0, 3, 10], [0, 4, 11],\n                   [1, 2, 6], [1, 3, 7], [1, 4, 7]]\n        table = Table(\"titanic\")\n        self.send_signal(self.widget.Inputs.data, table)\n        color_vars = [\"(Pearson residuals)\"] + [str(x) for x in table.domain.variables]\n        for i, cv in enumerate(color_vars):\n            idx = self.widget.cb_attr_color.findText(cv)\n            self.widget.cb_attr_color.setCurrentIndex(idx)\n            color = self.widget.cb_attr_color.currentText()\n            simulate.combobox_activate_index(self.widget.controls.variable_color, idx, 0)\n            discrete_data = self.widget.discrete_data\n\n            if color == \"(Pearson residuals)\":\n                self.assertIsNone(discrete_data.domain.class_var)\n            else:\n                self.assertEqual(color, str(discrete_data.domain.class_var))\n\n            output = self.get_output(\"Data\")\n            self.assertEqual(output.domain.class_var, table.domain.class_var)\n\n            for ma in range(2, 5):\n                self.vizrank.max_attrs = ma\n                sc = self.vizrank.state_count()\n                self.assertTrue([i > 0, ma, sc] in RESULTS)\n\n    def test_scores(self):\n        \"\"\"\n        Test scores without running vizrank.\n        GH-2299\n        GH-2036\n        \"\"\"\n        SCORES = {('status', ): 4.35e-40,\n                  ('sex', ): 6.18e-100,\n                  ('age', ): 2.82e-05,\n                  ('sex', 'status'): 1.06e-123,\n                  ('age', 'status'): 4.15e-47,\n                  ('age', 'sex'): 5.49e-102,\n                  ('age', 'sex', 'status'): 5.3e-128}\n        table = Table(\"titanic\")\n        self.send_signal(self.widget.Inputs.data, table)\n        self.vizrank.compute_attr_order()\n        self.widget.vizrank.max_attrs = 3\n        state = None\n        for state in self.vizrank.iterate_states(state):\n            self.vizrank.iterate_states(state)\n            attrlist = tuple(sorted(self.vizrank.attr_ordering[i].name for i in state))\n            sc = self.vizrank.compute_score(state)\n            self.assertTrue(np.allclose(sc, SCORES[attrlist], rtol=0.003, atol=0))\n\n    def test_subset_data(self):\n        \"\"\"\n        Mosaic should not crash on subset data and should properly interpret it.\n        GH-2515\n        GH-2528\n        \"\"\"\n        table_titanic = Table(\"titanic\")\n        self.send_signal(self.widget.Inputs.data, table_titanic)\n        self.send_signal(self.widget.Inputs.data_subset, table_titanic[::20])\n        table_housing = Table(\"housing\")\n        self.send_signal(self.widget.Inputs.data, table_housing)\n        self.send_signal(self.widget.Inputs.data_subset, table_housing[::20])\n\n    def test_incompatible_subset(self):\n        \"\"\"\n        Show warning when subset data is not compatible with data.\n        GH-2528\n        \"\"\"\n        table_titanic = Table(\"titanic\")\n        self.assertFalse(self.widget.Warning.incompatible_subset.is_shown())\n        self.send_signal(self.widget.Inputs.data, self.iris)\n        self.send_signal(self.widget.Inputs.data_subset, table_titanic)\n        self.assertTrue(self.widget.Warning.incompatible_subset.is_shown())\n        self.send_signal(self.widget.Inputs.data_subset, self.iris)\n        self.assertFalse(self.widget.Warning.incompatible_subset.is_shown())\n\n    def test_on_manual_change(self):\n        data = Table(\"iris.tab\")\n        self.send_signal(self.widget.Inputs.data, data)\n        self.vizrank.toggle()\n        self.process_events(until=lambda: not self.vizrank.keep_running)\n\n        model = self.vizrank.rank_model\n        attrs = model.data(model.index(3, 0), self.vizrank._AttrRole)\n        self.vizrank.on_manual_change(attrs)\n        selection = self.vizrank.rank_table.selectedIndexes()\n        self.assertEqual(len(selection), 1)\n        self.assertEqual(selection[0].row(), 3)\n\n        self.vizrank.on_manual_change(attrs[::-1])\n        selection = self.vizrank.rank_table.selectedIndexes()\n        self.assertEqual(len(selection), 0)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n",
            "header": {
                "file": {
                    "path": "Orange/widgets/visualize/tests/test_owmosaic.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "b9aaa08c5",
                "new_commit_hash": "345c54578",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/widgets/visualize/tests/test_owmosaic.py"
            },
            "plus_file": {
                "path": "b/Orange/widgets/visualize/tests/test_owmosaic.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 144,
                            "length": 6
                        },
                        "new_range": {
                            "start": 144,
                            "length": 24
                        },
                        "section": "class TestOWMosaicDisplay(WidgetTest, WidgetOutputsTestMixin):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.assertEqual(call_args[0].name, data.domain[0].name)",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertEqual(call_args[1].name, data.domain[2].name)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def test_selection_setting(self):",
                                "type": "added"
                            },
                            {
                                "content": "        widget = self.widget",
                                "type": "added"
                            },
                            {
                                "content": "        data = Table(\"iris.tab\")",
                                "type": "added"
                            },
                            {
                                "content": "        self.send_signal(widget.Inputs.data, data)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        widget.select_area(",
                                "type": "added"
                            },
                            {
                                "content": "            1,",
                                "type": "added"
                            },
                            {
                                "content": "            QMouseEvent(QEvent.MouseButtonPress, QPoint(), Qt.LeftButton,",
                                "type": "added"
                            },
                            {
                                "content": "                        Qt.LeftButton, Qt.KeyboardModifiers()))",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        self.send_signal(widget.Inputs.data, None)",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertFalse(bool(widget.selection))",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertIsNone(self.get_output(widget.Outputs.selected_data))",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        self.send_signal(widget.Inputs.data, data)",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(widget.selection, {1})",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertIsNotNone(self.get_output(widget.Outputs.selected_data))",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "# Derive from WidgetTest to simplify creation of the Mosaic widget, although",
                                "type": "context"
                            },
                            {
                                "content": "# we are actually testing the MosaicVizRank dialog and not the widget",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/visualize/tests/test_owmosaic.py",
                            "type": "class",
                            "name": "TestOWMosaicDisplay",
                            "content": "class TestOWMosaicDisplay(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.signal_name = \"Data\"\n        cls.signal_data = cls.data\n\n    def setUp(self):\n        self.widget = self.create_widget(OWMosaicDisplay)\n\n    def test_no_data(self):\n        \"\"\"Check that the widget doesn't crash on empty data\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[:0])\n\n    def test_empty_column(self):\n        \"\"\"Check that the widget doesn't crash if the columns are empty\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[:, :0])\n\n    def _select_data(self):\n        self.widget.select_area(1, QMouseEvent(\n            QEvent.MouseButtonPress, QPoint(), Qt.LeftButton,\n            Qt.LeftButton, Qt.KeyboardModifiers()))\n        return [2, 3, 9, 23, 29, 30, 34, 35, 37, 42, 47, 49]\n\n    def test_continuous_metas(self):\n        \"\"\"Check widget for dataset with continuous metas\"\"\"\n        domain = Domain([ContinuousVariable(\"c1\")],\n                        metas=[ContinuousVariable(\"m\")])\n        data = Table(domain, np.arange(6).reshape(6, 1),\n                     metas=np.arange(6).reshape(6, 1))\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_string_meta(self):\n        \"\"\"Check widget for dataset with only one string meta\"\"\"\n        domain = Domain([], metas=[StringVariable(\"m\")])\n        data = Table(domain, np.empty((6, 0)),\n                     metas=np.array([\"meta\"] * 6).reshape(6, 1))\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_missing_values(self):\n        \"\"\"Check widget for dataset with missing values\"\"\"\n        data = Table(Domain([DiscreteVariable(\"c1\", [])]),\n                     np.array([np.nan] * 6)[:, None])\n        self.send_signal(self.widget.Inputs.data, data)\n\n        # missings in class variable\n        attrs = [DiscreteVariable(\"c1\", [\"a\", \"b\", \"c\"])]\n        class_var = DiscreteVariable(\"cls\", [])\n        X = np.array([1, 2, 0, 1, 0, 2])[:, None]\n        data = Table(Domain(attrs, class_var), X, np.array([np.nan] * 6))\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_keyerror(self):\n        \"\"\"gh-2014\n        Check if it works when a table has only one row or duplicates.\n        Discretizer must have remove_const set to False.\n        \"\"\"\n        data = Table(\"iris\")\n        data = data[0:1]\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_combos_and_mosaic(self):\n        \"\"\"\n        Text in combos is wiped away when there is no data and mosaic disappears as well.\n        GH-2459\n        GH-2462\n        \"\"\"\n        def assertCount(cb_color, cb_attr, areas):\n            self.assertEqual(len(self.widget.areas), areas)\n            self.assertEqual(self.widget.cb_attr_color.count(), cb_color)\n            for combo, cb_count in zip(self.widget.attr_combos, cb_attr):\n                self.assertEqual(combo.count(), cb_count)\n\n        data = Table(\"iris\")\n        assertCount(1, [0, 1, 1, 1], 0)\n        self.send_signal(self.widget.Inputs.data, data)\n        assertCount(6, [5, 6, 6, 6], 16)\n        self.send_signal(self.widget.Inputs.data, None)\n        assertCount(1, [0, 1, 1, 1], 0)\n\n    @patch('Orange.widgets.visualize.owmosaic.CanvasRectangle')\n    @patch('Orange.widgets.visualize.owmosaic.QGraphicsItemGroup.addToGroup')\n    def test_different_number_of_attributes(self, _, canvas_rectangle):\n        domain = Domain([DiscreteVariable(c, values=\"01\") for c in \"abcd\"])\n        data = Table.from_list(\n            domain,\n            [list(\"{:04b}\".format(i)[-4:]) for i in range(16)])\n        self.send_signal(self.widget.Inputs.data, data)\n        widget = self.widget\n        widget.variable2 = widget.variable3 = widget.variable4 = None\n        for i, attr in enumerate(data.domain[:4], start=1):\n            canvas_rectangle.reset_mock()\n            setattr(self.widget, \"variable\" + str(i), attr)\n            self.widget.update_graph()\n            self.assertEqual(canvas_rectangle.call_count, 7 + 2 ** (i + 1))\n\n    def test_change_domain(self):\n        \"\"\"Test for GH-3419 fix\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[:, :2])\n        subset = self.data[:1, 2:3]\n        self.send_signal(self.widget.Inputs.data, subset)\n        output = self.get_output(self.widget.Outputs.annotated_data)\n        np.testing.assert_array_equal(output.X, subset.X)\n\n    def test_subset(self):\n        \"\"\"Test for GH-3416 fix\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data)\n        self.send_signal(self.widget.Inputs.data_subset, self.data[10:])\n        self.send_signal(self.widget.Inputs.data, self.data[:1])\n        output = self.get_output(self.widget.Outputs.annotated_data)\n        np.testing.assert_array_equal(output.X, self.data[:1].X)\n\n    @patch('Orange.widgets.visualize.owmosaic.MosaicVizRank.on_manual_change')\n    def test_vizrank_receives_manual_change(self, on_manual_change):\n        # Recreate the widget so the patch kicks in\n        self.widget = self.create_widget(OWMosaicDisplay)\n        data = Table(\"iris.tab\")\n        self.send_signal(self.widget.Inputs.data, data)\n        self.widget.variable1 = data.domain[0]\n        self.widget.variable2 = data.domain[1]\n        simulate.combobox_activate_index(self.widget.controls.variable2, 3)\n        self.assertEqual(self.widget.variable2, data.domain[2])\n        call_args = on_manual_change.call_args[0][0]\n        self.assertEqual(len(call_args), 2)\n        self.assertEqual(call_args[0].name, data.domain[0].name)\n        self.assertEqual(call_args[1].name, data.domain[2].name)\n\n    def test_selection_setting(self):\n        widget = self.widget\n        data = Table(\"iris.tab\")\n        self.send_signal(widget.Inputs.data, data)\n\n        widget.select_area(\n            1,\n            QMouseEvent(QEvent.MouseButtonPress, QPoint(), Qt.LeftButton,\n                        Qt.LeftButton, Qt.KeyboardModifiers()))\n\n        self.send_signal(widget.Inputs.data, None)\n        self.assertFalse(bool(widget.selection))\n        self.assertIsNone(self.get_output(widget.Outputs.selected_data))\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertEqual(widget.selection, {1})\n        self.assertIsNotNone(self.get_output(widget.Outputs.selected_data))",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 18,
                            "end_lineno": 163,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "Orange/widgets/visualize/tests/test_owmosaic.py",
                            "type": "method",
                            "name": "TestOWMosaicDisplay.test_selection_setting",
                            "content": "class TestOWMosaicDisplay(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.signal_name = \"Data\"\n        cls.signal_data = cls.data\n\n    def setUp(self):\n        self.widget = self.create_widget(OWMosaicDisplay)\n\n    def test_no_data(self):\n        \"\"\"Check that the widget doesn't crash on empty data\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[:0])\n\n    def test_empty_column(self):\n        \"\"\"Check that the widget doesn't crash if the columns are empty\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[:, :0])\n\n    def _select_data(self):\n        self.widget.select_area(1, QMouseEvent(\n            QEvent.MouseButtonPress, QPoint(), Qt.LeftButton,\n            Qt.LeftButton, Qt.KeyboardModifiers()))\n        return [2, 3, 9, 23, 29, 30, 34, 35, 37, 42, 47, 49]\n\n    def test_continuous_metas(self):\n        \"\"\"Check widget for dataset with continuous metas\"\"\"\n        domain = Domain([ContinuousVariable(\"c1\")],\n                        metas=[ContinuousVariable(\"m\")])\n        data = Table(domain, np.arange(6).reshape(6, 1),\n                     metas=np.arange(6).reshape(6, 1))\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_string_meta(self):\n        \"\"\"Check widget for dataset with only one string meta\"\"\"\n        domain = Domain([], metas=[StringVariable(\"m\")])\n        data = Table(domain, np.empty((6, 0)),\n                     metas=np.array([\"meta\"] * 6).reshape(6, 1))\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_missing_values(self):\n        \"\"\"Check widget for dataset with missing values\"\"\"\n        data = Table(Domain([DiscreteVariable(\"c1\", [])]),\n                     np.array([np.nan] * 6)[:, None])\n        self.send_signal(self.widget.Inputs.data, data)\n\n        # missings in class variable\n        attrs = [DiscreteVariable(\"c1\", [\"a\", \"b\", \"c\"])]\n        class_var = DiscreteVariable(\"cls\", [])\n        X = np.array([1, 2, 0, 1, 0, 2])[:, None]\n        data = Table(Domain(attrs, class_var), X, np.array([np.nan] * 6))\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_keyerror(self):\n        \"\"\"gh-2014\n        Check if it works when a table has only one row or duplicates.\n        Discretizer must have remove_const set to False.\n        \"\"\"\n        data = Table(\"iris\")\n        data = data[0:1]\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_combos_and_mosaic(self):\n        \"\"\"\n        Text in combos is wiped away when there is no data and mosaic disappears as well.\n        GH-2459\n        GH-2462\n        \"\"\"\n        def assertCount(cb_color, cb_attr, areas):\n            self.assertEqual(len(self.widget.areas), areas)\n            self.assertEqual(self.widget.cb_attr_color.count(), cb_color)\n            for combo, cb_count in zip(self.widget.attr_combos, cb_attr):\n                self.assertEqual(combo.count(), cb_count)\n\n        data = Table(\"iris\")\n        assertCount(1, [0, 1, 1, 1], 0)\n        self.send_signal(self.widget.Inputs.data, data)\n        assertCount(6, [5, 6, 6, 6], 16)\n        self.send_signal(self.widget.Inputs.data, None)\n        assertCount(1, [0, 1, 1, 1], 0)\n\n    @patch('Orange.widgets.visualize.owmosaic.CanvasRectangle')\n    @patch('Orange.widgets.visualize.owmosaic.QGraphicsItemGroup.addToGroup')\n    def test_different_number_of_attributes(self, _, canvas_rectangle):\n        domain = Domain([DiscreteVariable(c, values=\"01\") for c in \"abcd\"])\n        data = Table.from_list(\n            domain,\n            [list(\"{:04b}\".format(i)[-4:]) for i in range(16)])\n        self.send_signal(self.widget.Inputs.data, data)\n        widget = self.widget\n        widget.variable2 = widget.variable3 = widget.variable4 = None\n        for i, attr in enumerate(data.domain[:4], start=1):\n            canvas_rectangle.reset_mock()\n            setattr(self.widget, \"variable\" + str(i), attr)\n            self.widget.update_graph()\n            self.assertEqual(canvas_rectangle.call_count, 7 + 2 ** (i + 1))\n\n    def test_change_domain(self):\n        \"\"\"Test for GH-3419 fix\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[:, :2])\n        subset = self.data[:1, 2:3]\n        self.send_signal(self.widget.Inputs.data, subset)\n        output = self.get_output(self.widget.Outputs.annotated_data)\n        np.testing.assert_array_equal(output.X, subset.X)\n\n    def test_subset(self):\n        \"\"\"Test for GH-3416 fix\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data)\n        self.send_signal(self.widget.Inputs.data_subset, self.data[10:])\n        self.send_signal(self.widget.Inputs.data, self.data[:1])\n        output = self.get_output(self.widget.Outputs.annotated_data)\n        np.testing.assert_array_equal(output.X, self.data[:1].X)\n\n    @patch('Orange.widgets.visualize.owmosaic.MosaicVizRank.on_manual_change')\n    def test_vizrank_receives_manual_change(self, on_manual_change):\n        # Recreate the widget so the patch kicks in\n        self.widget = self.create_widget(OWMosaicDisplay)\n        data = Table(\"iris.tab\")\n        self.send_signal(self.widget.Inputs.data, data)\n        self.widget.variable1 = data.domain[0]\n        self.widget.variable2 = data.domain[1]\n        simulate.combobox_activate_index(self.widget.controls.variable2, 3)\n        self.assertEqual(self.widget.variable2, data.domain[2])\n        call_args = on_manual_change.call_args[0][0]\n        self.assertEqual(len(call_args), 2)\n        self.assertEqual(call_args[0].name, data.domain[0].name)\n        self.assertEqual(call_args[1].name, data.domain[2].name)\n\n    def test_selection_setting(self):\n        widget = self.widget\n        data = Table(\"iris.tab\")\n        self.send_signal(widget.Inputs.data, data)\n\n        widget.select_area(\n            1,\n            QMouseEvent(QEvent.MouseButtonPress, QPoint(), Qt.LeftButton,\n                        Qt.LeftButton, Qt.KeyboardModifiers()))\n\n        self.send_signal(widget.Inputs.data, None)\n        self.assertFalse(bool(widget.selection))\n        self.assertIsNone(self.get_output(widget.Outputs.selected_data))\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertEqual(widget.selection, {1})\n        self.assertIsNotNone(self.get_output(widget.Outputs.selected_data))",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 147,
                            "end_lineno": 163,
                            "parent": {
                                "file_name": "Orange/widgets/visualize/tests/test_owmosaic.py",
                                "type": "class",
                                "name": "TestOWMosaicDisplay",
                                "content": "class TestOWMosaicDisplay(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.signal_name = \"Data\"\n        cls.signal_data = cls.data\n\n    def setUp(self):\n        self.widget = self.create_widget(OWMosaicDisplay)\n\n    def test_no_data(self):\n        \"\"\"Check that the widget doesn't crash on empty data\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[:0])\n\n    def test_empty_column(self):\n        \"\"\"Check that the widget doesn't crash if the columns are empty\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[:, :0])\n\n    def _select_data(self):\n        self.widget.select_area(1, QMouseEvent(\n            QEvent.MouseButtonPress, QPoint(), Qt.LeftButton,\n            Qt.LeftButton, Qt.KeyboardModifiers()))\n        return [2, 3, 9, 23, 29, 30, 34, 35, 37, 42, 47, 49]\n\n    def test_continuous_metas(self):\n        \"\"\"Check widget for dataset with continuous metas\"\"\"\n        domain = Domain([ContinuousVariable(\"c1\")],\n                        metas=[ContinuousVariable(\"m\")])\n        data = Table(domain, np.arange(6).reshape(6, 1),\n                     metas=np.arange(6).reshape(6, 1))\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_string_meta(self):\n        \"\"\"Check widget for dataset with only one string meta\"\"\"\n        domain = Domain([], metas=[StringVariable(\"m\")])\n        data = Table(domain, np.empty((6, 0)),\n                     metas=np.array([\"meta\"] * 6).reshape(6, 1))\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_missing_values(self):\n        \"\"\"Check widget for dataset with missing values\"\"\"\n        data = Table(Domain([DiscreteVariable(\"c1\", [])]),\n                     np.array([np.nan] * 6)[:, None])\n        self.send_signal(self.widget.Inputs.data, data)\n\n        # missings in class variable\n        attrs = [DiscreteVariable(\"c1\", [\"a\", \"b\", \"c\"])]\n        class_var = DiscreteVariable(\"cls\", [])\n        X = np.array([1, 2, 0, 1, 0, 2])[:, None]\n        data = Table(Domain(attrs, class_var), X, np.array([np.nan] * 6))\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_keyerror(self):\n        \"\"\"gh-2014\n        Check if it works when a table has only one row or duplicates.\n        Discretizer must have remove_const set to False.\n        \"\"\"\n        data = Table(\"iris\")\n        data = data[0:1]\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_combos_and_mosaic(self):\n        \"\"\"\n        Text in combos is wiped away when there is no data and mosaic disappears as well.\n        GH-2459\n        GH-2462\n        \"\"\"\n        def assertCount(cb_color, cb_attr, areas):\n            self.assertEqual(len(self.widget.areas), areas)\n            self.assertEqual(self.widget.cb_attr_color.count(), cb_color)\n            for combo, cb_count in zip(self.widget.attr_combos, cb_attr):\n                self.assertEqual(combo.count(), cb_count)\n\n        data = Table(\"iris\")\n        assertCount(1, [0, 1, 1, 1], 0)\n        self.send_signal(self.widget.Inputs.data, data)\n        assertCount(6, [5, 6, 6, 6], 16)\n        self.send_signal(self.widget.Inputs.data, None)\n        assertCount(1, [0, 1, 1, 1], 0)\n\n    @patch('Orange.widgets.visualize.owmosaic.CanvasRectangle')\n    @patch('Orange.widgets.visualize.owmosaic.QGraphicsItemGroup.addToGroup')\n    def test_different_number_of_attributes(self, _, canvas_rectangle):\n        domain = Domain([DiscreteVariable(c, values=\"01\") for c in \"abcd\"])\n        data = Table.from_list(\n            domain,\n            [list(\"{:04b}\".format(i)[-4:]) for i in range(16)])\n        self.send_signal(self.widget.Inputs.data, data)\n        widget = self.widget\n        widget.variable2 = widget.variable3 = widget.variable4 = None\n        for i, attr in enumerate(data.domain[:4], start=1):\n            canvas_rectangle.reset_mock()\n            setattr(self.widget, \"variable\" + str(i), attr)\n            self.widget.update_graph()\n            self.assertEqual(canvas_rectangle.call_count, 7 + 2 ** (i + 1))\n\n    def test_change_domain(self):\n        \"\"\"Test for GH-3419 fix\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[:, :2])\n        subset = self.data[:1, 2:3]\n        self.send_signal(self.widget.Inputs.data, subset)\n        output = self.get_output(self.widget.Outputs.annotated_data)\n        np.testing.assert_array_equal(output.X, subset.X)\n\n    def test_subset(self):\n        \"\"\"Test for GH-3416 fix\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data)\n        self.send_signal(self.widget.Inputs.data_subset, self.data[10:])\n        self.send_signal(self.widget.Inputs.data, self.data[:1])\n        output = self.get_output(self.widget.Outputs.annotated_data)\n        np.testing.assert_array_equal(output.X, self.data[:1].X)\n\n    @patch('Orange.widgets.visualize.owmosaic.MosaicVizRank.on_manual_change')\n    def test_vizrank_receives_manual_change(self, on_manual_change):\n        # Recreate the widget so the patch kicks in\n        self.widget = self.create_widget(OWMosaicDisplay)\n        data = Table(\"iris.tab\")\n        self.send_signal(self.widget.Inputs.data, data)\n        self.widget.variable1 = data.domain[0]\n        self.widget.variable2 = data.domain[1]\n        simulate.combobox_activate_index(self.widget.controls.variable2, 3)\n        self.assertEqual(self.widget.variable2, data.domain[2])\n        call_args = on_manual_change.call_args[0][0]\n        self.assertEqual(len(call_args), 2)\n        self.assertEqual(call_args[0].name, data.domain[0].name)\n        self.assertEqual(call_args[1].name, data.domain[2].name)\n\n    def test_selection_setting(self):\n        widget = self.widget\n        data = Table(\"iris.tab\")\n        self.send_signal(widget.Inputs.data, data)\n\n        widget.select_area(\n            1,\n            QMouseEvent(QEvent.MouseButtonPress, QPoint(), Qt.LeftButton,\n                        Qt.LeftButton, Qt.KeyboardModifiers()))\n\n        self.send_signal(widget.Inputs.data, None)\n        self.assertFalse(bool(widget.selection))\n        self.assertIsNone(self.get_output(widget.Outputs.selected_data))\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertEqual(widget.selection, {1})\n        self.assertIsNotNone(self.get_output(widget.Outputs.selected_data))",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 18,
                                "end_lineno": 163,
                                "parent": null
                            }
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "68aa64457e0c4b85d80ce1d948acdc424475fa07^",
    "new_commit_hash": "68aa64457e0c4b85d80ce1d948acdc424475fa07",
    "commit_message": "OWMosaic: Retrieve selection from context setting",
    "commit_date": "2019-09-20T10:50:34+02:00",
    "metadata": {}
}
{
    "repo_name": "orange3",
    "new_commit_hash": "68aa64457e0c4b85d80ce1d948acdc424475fa07",
    "test_file_codes": [
        "# pylint: disable=missing-docstring,protected-access\nimport time\nimport unittest\nfrom unittest.mock import patch\n\nimport numpy as np\n\nfrom AnyQt.QtCore import QEvent, QPoint, Qt\nfrom AnyQt.QtGui import QMouseEvent\n\nfrom Orange.data import Table, DiscreteVariable, Domain, ContinuousVariable, \\\n    StringVariable\nfrom Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin\nfrom Orange.widgets.visualize.owmosaic import OWMosaicDisplay\nfrom Orange.widgets.tests.utils import simulate\n\n\nclass TestOWMosaicDisplay(WidgetTest, WidgetOutputsTestMixin):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        WidgetOutputsTestMixin.init(cls)\n\n        cls.signal_name = \"Data\"\n        cls.signal_data = cls.data\n\n    def setUp(self):\n        self.widget = self.create_widget(OWMosaicDisplay)\n\n    def test_no_data(self):\n        \"\"\"Check that the widget doesn't crash on empty data\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[:0])\n\n    def test_empty_column(self):\n        \"\"\"Check that the widget doesn't crash if the columns are empty\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[:, :0])\n\n    def _select_data(self):\n        self.widget.select_area(1, QMouseEvent(\n            QEvent.MouseButtonPress, QPoint(), Qt.LeftButton,\n            Qt.LeftButton, Qt.KeyboardModifiers()))\n        return [2, 3, 9, 23, 29, 30, 34, 35, 37, 42, 47, 49]\n\n    def test_continuous_metas(self):\n        \"\"\"Check widget for dataset with continuous metas\"\"\"\n        domain = Domain([ContinuousVariable(\"c1\")],\n                        metas=[ContinuousVariable(\"m\")])\n        data = Table(domain, np.arange(6).reshape(6, 1),\n                     metas=np.arange(6).reshape(6, 1))\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_string_meta(self):\n        \"\"\"Check widget for dataset with only one string meta\"\"\"\n        domain = Domain([], metas=[StringVariable(\"m\")])\n        data = Table(domain, np.empty((6, 0)),\n                     metas=np.array([\"meta\"] * 6).reshape(6, 1))\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_missing_values(self):\n        \"\"\"Check widget for dataset with missing values\"\"\"\n        data = Table(Domain([DiscreteVariable(\"c1\", [])]),\n                     np.array([np.nan] * 6)[:, None])\n        self.send_signal(self.widget.Inputs.data, data)\n\n        # missings in class variable\n        attrs = [DiscreteVariable(\"c1\", [\"a\", \"b\", \"c\"])]\n        class_var = DiscreteVariable(\"cls\", [])\n        X = np.array([1, 2, 0, 1, 0, 2])[:, None]\n        data = Table(Domain(attrs, class_var), X, np.array([np.nan] * 6))\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_keyerror(self):\n        \"\"\"gh-2014\n        Check if it works when a table has only one row or duplicates.\n        Discretizer must have remove_const set to False.\n        \"\"\"\n        data = Table(\"iris\")\n        data = data[0:1]\n        self.send_signal(self.widget.Inputs.data, data)\n\n    def test_combos_and_mosaic(self):\n        \"\"\"\n        Text in combos is wiped away when there is no data and mosaic disappears as well.\n        GH-2459\n        GH-2462\n        \"\"\"\n        def assertCount(cb_color, cb_attr, areas):\n            self.assertEqual(len(self.widget.areas), areas)\n            self.assertEqual(self.widget.cb_attr_color.count(), cb_color)\n            for combo, cb_count in zip(self.widget.attr_combos, cb_attr):\n                self.assertEqual(combo.count(), cb_count)\n\n        data = Table(\"iris\")\n        assertCount(1, [0, 1, 1, 1], 0)\n        self.send_signal(self.widget.Inputs.data, data)\n        assertCount(6, [5, 6, 6, 6], 16)\n        self.send_signal(self.widget.Inputs.data, None)\n        assertCount(1, [0, 1, 1, 1], 0)\n\n    @patch('Orange.widgets.visualize.owmosaic.CanvasRectangle')\n    @patch('Orange.widgets.visualize.owmosaic.QGraphicsItemGroup.addToGroup')\n    def test_different_number_of_attributes(self, _, canvas_rectangle):\n        domain = Domain([DiscreteVariable(c, values=\"01\") for c in \"abcd\"])\n        data = Table.from_list(\n            domain,\n            [list(\"{:04b}\".format(i)[-4:]) for i in range(16)])\n        self.send_signal(self.widget.Inputs.data, data)\n        widget = self.widget\n        widget.variable2 = widget.variable3 = widget.variable4 = None\n        for i, attr in enumerate(data.domain[:4], start=1):\n            canvas_rectangle.reset_mock()\n            setattr(self.widget, \"variable\" + str(i), attr)\n            self.widget.update_graph()\n            self.assertEqual(canvas_rectangle.call_count, 7 + 2 ** (i + 1))\n\n    def test_change_domain(self):\n        \"\"\"Test for GH-3419 fix\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[:, :2])\n        subset = self.data[:1, 2:3]\n        self.send_signal(self.widget.Inputs.data, subset)\n        output = self.get_output(self.widget.Outputs.annotated_data)\n        np.testing.assert_array_equal(output.X, subset.X)\n\n    def test_subset(self):\n        \"\"\"Test for GH-3416 fix\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data)\n        self.send_signal(self.widget.Inputs.data_subset, self.data[10:])\n        self.send_signal(self.widget.Inputs.data, self.data[:1])\n        output = self.get_output(self.widget.Outputs.annotated_data)\n        np.testing.assert_array_equal(output.X, self.data[:1].X)\n\n    @patch('Orange.widgets.visualize.owmosaic.MosaicVizRank.on_manual_change')\n    def test_vizrank_receives_manual_change(self, on_manual_change):\n        # Recreate the widget so the patch kicks in\n        self.widget = self.create_widget(OWMosaicDisplay)\n        data = Table(\"iris.tab\")\n        self.send_signal(self.widget.Inputs.data, data)\n        self.widget.variable1 = data.domain[0]\n        self.widget.variable2 = data.domain[1]\n        simulate.combobox_activate_index(self.widget.controls.variable2, 3)\n        self.assertEqual(self.widget.variable2, data.domain[2])\n        call_args = on_manual_change.call_args[0][0]\n        self.assertEqual(len(call_args), 2)\n        self.assertEqual(call_args[0].name, data.domain[0].name)\n        self.assertEqual(call_args[1].name, data.domain[2].name)\n\n    def test_selection_setting(self):\n        widget = self.widget\n        data = Table(\"iris.tab\")\n        self.send_signal(widget.Inputs.data, data)\n\n        widget.select_area(\n            1,\n            QMouseEvent(QEvent.MouseButtonPress, QPoint(), Qt.LeftButton,\n                        Qt.LeftButton, Qt.KeyboardModifiers()))\n\n        self.send_signal(widget.Inputs.data, None)\n        self.assertFalse(bool(widget.selection))\n        self.assertIsNone(self.get_output(widget.Outputs.selected_data))\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertEqual(widget.selection, {1})\n        self.assertIsNotNone(self.get_output(widget.Outputs.selected_data))\n\n\n# Derive from WidgetTest to simplify creation of the Mosaic widget, although\n# we are actually testing the MosaicVizRank dialog and not the widget\n\n# These tests are rather crude: the main challenge of this widget is to handle\n# user interactions and interrupts, e.g. changing the widget settings or\n# getting new data while the computation is running.\nclass MosaicVizRankTests(WidgetTest):\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        cls.iris = Table(\"iris.tab\")\n        dom = Domain(cls.iris.domain.attributes, [])\n        cls.iris_no_class = Table(dom, cls.iris)\n\n    def setUp(self):\n        self.widget = self.create_widget(OWMosaicDisplay)\n        self.vizrank = self.widget.vizrank\n\n    def tearDown(self):\n        self.widget.onDeleteWidget()\n        super().tearDown()\n\n    def test_count(self):\n        \"\"\"MosaicVizrank correctly computes the number of combinations\"\"\"\n        vizrank = self.vizrank\n\n        data = self.iris\n        attributes = [v for v in data.domain.attributes[1:]]\n        metas = [data.domain.attributes[0]]\n        domain = Domain(attributes, data.domain.class_var, metas)\n        new_data = data.from_table(domain, data)\n        self.send_signal(self.widget.Inputs.data, new_data)\n\n        for data in [self.iris, new_data]:\n            self.send_signal(self.widget.Inputs.data, data)\n\n            simulate.combobox_activate_index(self.widget.controls.variable_color, 0, 0)\n            vizrank.max_attrs = 2\n            self.assertEqual(vizrank.state_count(), 10)  # 5x4 / 2\n            vizrank.max_attrs = 3\n            self.assertEqual(vizrank.state_count(), 20)  # above + 5x4x3 / 2x3\n            vizrank.max_attrs = 4\n            self.assertEqual(vizrank.state_count(), 25)  # above + 5x4x3x2 / 2x3x4\n\n            simulate.combobox_activate_index(self.widget.controls.variable_color, 2, 0)\n            vizrank.max_attrs = 2\n            self.assertEqual(vizrank.state_count(), 10)  # 4 + 4x3 / 2\n            vizrank.max_attrs = 3\n            self.assertEqual(vizrank.state_count(), 14)  # above + 4x3x2 / 3x2\n            vizrank.max_attrs = 4\n            self.assertEqual(vizrank.state_count(), 15)  # above + 4x3x2x1 / 2x3x4\n\n            self.send_signal(self.widget.Inputs.data, self.iris_no_class)\n            simulate.combobox_activate_index(self.widget.controls.variable_color, 0, 0)\n            vizrank.max_attrs = 2\n            self.assertEqual(vizrank.state_count(), 6)  # 4x3 / 2\n            vizrank.max_attrs = 3\n            self.assertEqual(vizrank.state_count(), 10)  # above + 4x3x2 / 3x2\n            vizrank.max_attrs = 4\n            self.assertEqual(vizrank.state_count(), 11)  # above + 4x3x2x1 / 2x3x4\n\n    def test_iteration(self):\n        \"\"\"MosaicVizrank correctly iterates through states\"\"\"\n        widget = self.widget\n        vizrank = self.vizrank\n        self.send_signal(self.widget.Inputs.data, self.iris)\n        vizrank.compute_attr_order()\n\n        vizrank.max_attrs = 4\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states(None)],\n                         [[0], [1], [2], [3],\n                          [0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3],\n                          [0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3],\n                          [0, 1, 2, 3]])\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states([0, 3])],\n                         [[0, 3], [1, 3], [2, 3],\n                          [0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3],\n                          [0, 1, 2, 3]])\n\n        vizrank.max_attrs = 2\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states(None)],\n                         [[0], [1], [2], [3],\n                          [0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]])\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states([0, 3])],\n                         [[0, 3], [1, 3], [2, 3]])\n\n        widget.variable_color = None\n        vizrank.max_attrs = 4\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states(None)],\n                         [[0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3],\n                          [0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3],\n                          [0, 1, 2, 3]])\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states([0, 3])],\n                         [[0, 3], [1, 3], [2, 3],\n                          [0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3],\n                          [0, 1, 2, 3]])\n\n        vizrank.max_attrs = 2\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states(None)],\n                         [[0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]])\n        self.assertEqual([state.copy()\n                          for state in vizrank.iterate_states([0, 3])],\n                         [[0, 3], [1, 3], [2, 3]])\n\n    def test_row_for_state(self):\n        \"\"\"MosaicVizrank returns table row corresponding to the state\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.iris)\n        self.vizrank.attr_ordering = [DiscreteVariable(n) for n in \"abcd\"]\n        items = self.vizrank.row_for_state(0, [1, 3, 0])\n        self.assertEqual(len(items), 1)\n        item = items[0]\n        self.assertEqual(item.text(), \"a, b, d\")\n        self.assertEqual(\n            item.data(self.vizrank._AttrRole),\n            tuple(self.vizrank.attr_ordering[i] for i in [0, 1, 3]))\n\n    @unittest.skip(\"Appveyor sometimes fails.\")\n    def test_does_not_crash(self):\n        \"\"\"MosaicVizrank computes rankings without crashing\"\"\"\n        widget = self.widget\n        vizrank = self.vizrank\n        self.send_signal(self.widget.Inputs.data, self.iris)\n        vizrank.max_attrs = 2\n\n        widget.interior_coloring = widget.PEARSON\n        vizrank.toggle()\n        time.sleep(0.5)\n        self.assertEqual(vizrank.rank_model.rowCount(), 10)  # 4x5 / 2\n        widget.interior_coloring = widget.CLASS_DISTRIBUTION\n        vizrank.toggle()\n        time.sleep(0.5)\n        self.assertEqual(vizrank.rank_model.rowCount(), 10)  # 4 + 4x5 / 2\n\n        self.send_signal(self.widget.Inputs.data, self.iris_no_class)\n        vizrank.toggle()\n\n    def test_does_not_crash_cont_class(self):\n        \"\"\"MosaicVizrank computes rankings without crashing\"\"\"\n        data = Table(\"housing.tab\")\n        self.send_signal(self.widget.Inputs.data, data)\n        self.vizrank.toggle()\n\n    def test_pause_continue(self):\n        data = Table(\"housing.tab\")\n        self.send_signal(self.widget.Inputs.data, data)\n        self.vizrank.toggle()  # start\n        self.process_events(until=lambda: self.vizrank.saved_progress > 5)\n        self.vizrank.toggle()  # stop\n        self.process_events(until=lambda: not self.vizrank.keep_running)\n        self.vizrank.toggle()  # continue\n        self.process_events(until=lambda: self.vizrank.saved_progress > 20)\n\n    def test_finished(self):\n        data = Table(\"iris.tab\")\n        self.send_signal(self.widget.Inputs.data, data)\n        self.vizrank.toggle()\n        self.process_events(until=lambda: not self.vizrank.keep_running)\n        self.assertEqual(len(self.vizrank.scores), self.vizrank.state_count())\n\n    def test_nan_column(self):\n        \"\"\"\n        A column with only NaN-s used to throw an error\n        (ZeroDivisionError) when loaded into widget.\n        GH-2046\n        \"\"\"\n        table = Table(\n            Domain(\n                [ContinuousVariable(\"a\"), ContinuousVariable(\"b\"), ContinuousVariable(\"c\")]),\n            np.array([\n                [0, np.NaN, 0],\n                [0, np.NaN, 0],\n                [0, np.NaN, 0]\n            ])\n        )\n        self.send_signal(self.widget.Inputs.data, table)\n\n    def test_color_combo(self):\n        \"\"\"\n        Color combo enables to select class values. Checks if class values\n        are selected correctly.\n        GH-2133\n        GH-2036\n        \"\"\"\n        RESULTS = [[0, 2, 6], [0, 3, 10], [0, 4, 11],\n                   [1, 2, 6], [1, 3, 7], [1, 4, 7]]\n        table = Table(\"titanic\")\n        self.send_signal(self.widget.Inputs.data, table)\n        color_vars = [\"(Pearson residuals)\"] + [str(x) for x in table.domain.variables]\n        for i, cv in enumerate(color_vars):\n            idx = self.widget.cb_attr_color.findText(cv)\n            self.widget.cb_attr_color.setCurrentIndex(idx)\n            color = self.widget.cb_attr_color.currentText()\n            simulate.combobox_activate_index(self.widget.controls.variable_color, idx, 0)\n            discrete_data = self.widget.discrete_data\n\n            if color == \"(Pearson residuals)\":\n                self.assertIsNone(discrete_data.domain.class_var)\n            else:\n                self.assertEqual(color, str(discrete_data.domain.class_var))\n\n            output = self.get_output(\"Data\")\n            self.assertEqual(output.domain.class_var, table.domain.class_var)\n\n            for ma in range(2, 5):\n                self.vizrank.max_attrs = ma\n                sc = self.vizrank.state_count()\n                self.assertTrue([i > 0, ma, sc] in RESULTS)\n\n    def test_scores(self):\n        \"\"\"\n        Test scores without running vizrank.\n        GH-2299\n        GH-2036\n        \"\"\"\n        SCORES = {('status', ): 4.35e-40,\n                  ('sex', ): 6.18e-100,\n                  ('age', ): 2.82e-05,\n                  ('sex', 'status'): 1.06e-123,\n                  ('age', 'status'): 4.15e-47,\n                  ('age', 'sex'): 5.49e-102,\n                  ('age', 'sex', 'status'): 5.3e-128}\n        table = Table(\"titanic\")\n        self.send_signal(self.widget.Inputs.data, table)\n        self.vizrank.compute_attr_order()\n        self.widget.vizrank.max_attrs = 3\n        state = None\n        for state in self.vizrank.iterate_states(state):\n            self.vizrank.iterate_states(state)\n            attrlist = tuple(sorted(self.vizrank.attr_ordering[i].name for i in state))\n            sc = self.vizrank.compute_score(state)\n            self.assertTrue(np.allclose(sc, SCORES[attrlist], rtol=0.003, atol=0))\n\n    def test_subset_data(self):\n        \"\"\"\n        Mosaic should not crash on subset data and should properly interpret it.\n        GH-2515\n        GH-2528\n        \"\"\"\n        table_titanic = Table(\"titanic\")\n        self.send_signal(self.widget.Inputs.data, table_titanic)\n        self.send_signal(self.widget.Inputs.data_subset, table_titanic[::20])\n        table_housing = Table(\"housing\")\n        self.send_signal(self.widget.Inputs.data, table_housing)\n        self.send_signal(self.widget.Inputs.data_subset, table_housing[::20])\n\n    def test_incompatible_subset(self):\n        \"\"\"\n        Show warning when subset data is not compatible with data.\n        GH-2528\n        \"\"\"\n        table_titanic = Table(\"titanic\")\n        self.assertFalse(self.widget.Warning.incompatible_subset.is_shown())\n        self.send_signal(self.widget.Inputs.data, self.iris)\n        self.send_signal(self.widget.Inputs.data_subset, table_titanic)\n        self.assertTrue(self.widget.Warning.incompatible_subset.is_shown())\n        self.send_signal(self.widget.Inputs.data_subset, self.iris)\n        self.assertFalse(self.widget.Warning.incompatible_subset.is_shown())\n\n    def test_on_manual_change(self):\n        data = Table(\"iris.tab\")\n        self.send_signal(self.widget.Inputs.data, data)\n        self.vizrank.toggle()\n        self.process_events(until=lambda: not self.vizrank.keep_running)\n\n        model = self.vizrank.rank_model\n        attrs = model.data(model.index(3, 0), self.vizrank._AttrRole)\n        self.vizrank.on_manual_change(attrs)\n        selection = self.vizrank.rank_table.selectedIndexes()\n        self.assertEqual(len(selection), 1)\n        self.assertEqual(selection[0].row(), 3)\n\n        self.vizrank.on_manual_change(attrs[::-1])\n        selection = self.vizrank.rank_table.selectedIndexes()\n        self.assertEqual(len(selection), 0)\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/tmpahs223u8/tmp\ncreating /tmp/tmpahs223u8/tmp/tmpahs223u8\ncompile options: '-MMD -MF /tmp/tmpahs223u8/file.c.d -c'\ncc: /tmp/tmpahs223u8/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/tmp5mpvq4m9/tmp\ncreating /tmp/tmp5mpvq4m9/tmp/tmp5mpvq4m9\ncompile options: '-MMD -MF /tmp/tmp5mpvq4m9/file.c.d -c'\ncc: /tmp/tmp5mpvq4m9/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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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/tmp9_fqmhdz/tmp\ncreating /tmp/tmp9_fqmhdz/tmp/tmp9_fqmhdz\ncompile options: '-MMD -MF /tmp/tmp9_fqmhdz/file.c.d -c'\ncc: /tmp/tmp9_fqmhdz/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/tmp4rnhiian/tmp\ncreating /tmp/tmp4rnhiian/tmp/tmp4rnhiian\ncompile options: '-MMD -MF /tmp/tmp4rnhiian/file.c.d -c'\ncc: /tmp/tmp4rnhiian/file.c\ncustomize UnixCCompiler using build_ext\nCreating /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/Orange3.egg-link (link to .)\nAdding Orange3 3.24.0.dev0+68aa644 to easy-install.pth file\nInstalling orange-canvas script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/bin\n\nInstalled /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07\nProcessing dependencies for Orange3==3.24.0.dev0+68aa644\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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/bin\nInstalling pip3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/bin\nInstalling pip3.10 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/bin\nInstalling f2py3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/bin\nInstalling f2py3.7 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/bin\nInstalling pyftmerge script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/bin\nInstalling pyftsubset script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/bin\nInstalling ttx script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages\nFinished processing dependencies for Orange3==3.24.0.dev0+68aa644\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 38ms\nPrepared 18 packages in 7ms\nInstalled 18 packages in 423ms\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 259ms\nInstalled 33 packages in 932ms\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 117ms\nInstalled 19 packages in 53ms\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 3ms\nInstalled 2 packages in 14ms\n + psycopg2==2.9.9\n + pymssql==2.3.2\nAudited 1 package in 17ms\nAudited 2 packages in 2ms\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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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_68aa64457e0c4b85d80ce1d948acdc424475fa07/.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": 0,
    "new_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_68aa64457e0c4b85d80ce1d948acdc424475fa07\ncollected 32 items\n\nr2e_tests/test_1.py ss.................s............                     [100%]\n\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_change_domain\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_combos_and_mosaic\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_continuous_metas\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_different_number_of_attributes\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_empty_column\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_keyerror\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_minimum_size\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_missing_values\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_msg_base_class\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_no_data\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_outputs\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_selection_setting\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_string_meta\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_subset\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_vizrank_receives_manual_change\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_color_combo\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_count\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_does_not_crash_cont_class\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_finished\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_incompatible_subset\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_iteration\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_minimum_size\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_msg_base_class\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_nan_column\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_on_manual_change\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_pause_continue\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_row_for_state\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_scores\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_subset_data\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\nSKIPPED [1] r2e_tests/test_1.py:289: Appveyor sometimes fails.\n======================== 29 passed, 3 skipped in 7.85s =========================\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_68aa64457e0c4b85d80ce1d948acdc424475fa07\ncollected 32 items\n\nr2e_tests/test_1.py ss...........F.....s............                     [100%]\n\n=================================== FAILURES ===================================\n__________________ TestOWMosaicDisplay.test_selection_setting __________________\n\nself = <r2e_tests.test_1.TestOWMosaicDisplay testMethod=test_selection_setting>\n\n    def test_selection_setting(self):\n        widget = self.widget\n        data = Table(\"iris.tab\")\n        self.send_signal(widget.Inputs.data, data)\n    \n        widget.select_area(\n            1,\n            QMouseEvent(QEvent.MouseButtonPress, QPoint(), Qt.LeftButton,\n                        Qt.LeftButton, Qt.KeyboardModifiers()))\n    \n        self.send_signal(widget.Inputs.data, None)\n        self.assertFalse(bool(widget.selection))\n        self.assertIsNone(self.get_output(widget.Outputs.selected_data))\n    \n        self.send_signal(widget.Inputs.data, data)\n>       self.assertEqual(widget.selection, {1})\nE       AssertionError: Items in the second set but not the first:\nE       1\n\nr2e_tests/test_1.py:162: AssertionError\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_change_domain\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_combos_and_mosaic\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_continuous_metas\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_different_number_of_attributes\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_empty_column\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_keyerror\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_minimum_size\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_missing_values\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_msg_base_class\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_no_data\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_outputs\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_string_meta\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_subset\nPASSED r2e_tests/test_1.py::TestOWMosaicDisplay::test_vizrank_receives_manual_change\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_color_combo\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_count\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_does_not_crash_cont_class\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_finished\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_incompatible_subset\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_iteration\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_minimum_size\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_msg_base_class\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_nan_column\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_on_manual_change\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_pause_continue\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_row_for_state\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_scores\nPASSED r2e_tests/test_1.py::MosaicVizRankTests::test_subset_data\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\nSKIPPED [1] r2e_tests/test_1.py:289: Appveyor sometimes fails.\nFAILED r2e_tests/test_1.py::TestOWMosaicDisplay::test_selection_setting - Ass...\n=================== 1 failed, 28 passed, 3 skipped in 6.73s ====================\n",
    "old_commit_res_stderr": ""
}
Orange/widgets/visualize/owmosaic.py Orange/widgets/visualize/tests/test_owmosaic.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Selection Not Preserved After Resetting Graph in OWMosaicDisplay

**Description**
When a selection is made in the Mosaic widget and the data is subsequently cleared and reloaded, the selection does not persist as expected. This results in the selected area being lost, which can disrupt the user's workflow and data analysis.

**Steps to Reproduce**
```python
widget = OWMosaicDisplay()
data = Table("iris.tab")
widget.set_data(data)
widget.select_area(1, QMouseEvent(QEvent.MouseButtonPress, QPoint(), Qt.LeftButton, Qt.LeftButton, Qt.KeyboardModifiers()))
widget.clear_data()
widget.set_data(data)
```

**Expected Behavior**
After reloading the data, the previously selected area should remain selected, and the corresponding selection data should be available for further operations.

**Actual Behavior**
The selection is lost upon reloading the data, resulting in no area being selected despite prior user interaction.
[/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/owmosaic.py
from collections import defaultdict
from functools import reduce
from itertools import product, chain, repeat
from math import sqrt, log
from operator import mul, attrgetter

import numpy as np
from scipy.stats import distributions
from scipy.special import comb
from AnyQt.QtCore import Qt, QSize, pyqtSignal as Signal
from AnyQt.QtGui import QColor, QPainter, QPen, QStandardItem
from AnyQt.QtWidgets import (
    QGraphicsScene, QGraphicsLineItem, QGraphicsItemGroup)

from Orange.data import Table, filter, Variable, Domain
from Orange.data.sql.table import SqlTable, LARGE_TABLE, DEFAULT_SAMPLE_TIME
from Orange.preprocess import Discretize
from Orange.preprocess.discretize import EqualFreq
from Orange.preprocess.score import ReliefF
from Orange.statistics.distribution import get_distribution, get_distributions
from Orange.widgets import gui, settings
from Orange.widgets.gui import OWComponent
from Orange.widgets.settings import (
    Setting, DomainContextHandler, ContextSetting, SettingProvider)
from Orange.widgets.utils import to_html, get_variable_values_sorted
from Orange.widgets.utils.annotated_data import (create_annotated_table,
                                                 ANNOTATED_DATA_SIGNAL_NAME)
from Orange.widgets.utils.itemmodels import DomainModel
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.visualize.utils import (
    CanvasText, CanvasRectangle, ViewWithPress, VizRankDialog)
from Orange.widgets.visualize.utils.plotutils import wrap_legend_items
from Orange.widgets.widget import OWWidget, Msg, Input, Output


class MosaicVizRank(VizRankDialog, OWComponent):
    """VizRank dialog for Mosaic"""
    captionTitle = "Mosaic Ranking"
    max_attrs = Setting(3)

    pairSelected = Signal(Variable, Variable, Variable, Variable)
    _AttrRole = next(gui.OrangeUserRole)

    def __init__(self, master):
        """Add the spin box for maximal number of attributes"""
        VizRankDialog.__init__(self, master)
        OWComponent.__init__(self, master)

        box = gui.hBox(self)
        self.max_attr_spin = gui.spin(
            box, self, "max_attrs", 2, 4,
            label="Limit the number of attributes to: ",
            controlWidth=50, alignment=Qt.AlignRight,
            callback=self.max_attr_changed)
        gui.rubber(box)
        self.layout().addWidget(self.button)
        self.attr_ordering = None
        self.marginal = {}
        self.last_run_max_attr = None

        self.master.attrs_changed_manually.connect(self.on_manual_change)

    def sizeHint(self):
        return QSize(400, 512)

    def initialize(self):
        """Clear the ordering to trigger recomputation when needed"""
        super().initialize()
        self.attr_ordering = None

    def initialize_keep_ordering(self):
        """Initialize triggered by change of coloring"""
        super().initialize()

    def before_running(self):
        """
        Disable the spin for maximal number of attributes before running and
        enable afterwards. Also, if the number of attributes is different than
        in the last run, reset the saved state (if it was paused).
        """
        if self.max_attrs != self.last_run_max_attr:
            self.saved_state = None
            self.saved_progress = 0
        if self.saved_state is None:
            self.scores = []
            self.rank_model.clear()
        self.compute_attr_order()
        self.last_run_max_attr = self.max_attrs
        self.max_attr_spin.setDisabled(True)

    def stopped(self):
        self.max_attr_spin.setDisabled(False)

    def max_attr_changed(self):
        """
        Change the button label when the maximal number of attributes changes.

        The method does not reset anything so the user can still see the
        results until actually restarting the search.
        """
        if self.max_attrs != self.last_run_max_attr or self.saved_state is None:
            self.button.setText("Start")
        else:
            self.button.setText("Continue")
        self.button.setEnabled(self.check_preconditions())

    def coloring_changed(self):
        self.stop_and_reset(self.initialize_keep_ordering)

    def check_preconditions(self):
        """Require at least one variable to allow ranking."""
        self.Information.add_message("no_attributes", "No variables to rank.")
        self.Information.no_attributes.clear()
        data = self.master.discrete_data
        if not super().check_preconditions() or data is None:
            return False
        if not data.domain.attributes:
            self.Information.no_attributes()
            return False
        return True

    def compute_attr_order(self):
        """
        Order attributes by Relief if there is a target variable. In case of
        ties or without target, order by name.

        Add the class variable at the beginning when not coloring by class
        distribution.

        If `self.attrs` is not `None`, keep the ordering and just add or remove
        the class as needed.
        """
        data = self.master.discrete_data
        class_var = data.domain.class_var
        if not self.attr_ordering:
            if class_var is None:
                self.attr_ordering = sorted(data.domain, key=attrgetter("name"))
            else:
                weights = ReliefF(n_iterations=100, k_nearest=10)(data)
                attrs = sorted(zip(weights, data.domain.attributes),
                               key=lambda x: (-x[0], x[1].name))
                self.attr_ordering = [a for _, a in attrs]

    def _compute_class_dists(self):
        return self.master.variable_color is not None

    def state_count(self):
        """
        Return the number of combinations, starting with a single attribute
        if Mosaic is colored by class distributions, and two if by Pearson
        """
        n_attrs = len(self.master.discrete_data.domain.attributes)
        min_attrs = 1 if self._compute_class_dists() else 2
        max_attrs = min(n_attrs, self.max_attrs)
        return sum(comb(n_attrs, k, exact=True)
                   for k in range(min_attrs, max_attrs + 1))

    def iterate_states(self, state):
        """
        Iterate through all combinations of attributes as ordered by Relief,
        starting with a single attribute if Mosaic is colored by class
        distributions, and two if by Pearson.
        """
        # If we put initialization of `self.attrs` to `initialize`,
        # `score_heuristic` would be run on every call to master's `set_data`.
        master = self.master
        data = master.discrete_data
        if state is None:  # on the first call, compute order
            if self._compute_class_dists():
                self.marginal = get_distribution(data, data.domain.class_var)
                self.marginal.normalize()
                state = [0]
            else:
                self.marginal = get_distributions(data)
                for dist in self.marginal:
                    dist.normalize()
                state = [0, 1]
        n_attrs = len(data.domain.attributes)
        while True:
            yield state
            # Reset while running; just abort
            if self.attr_ordering is None:
                break
            for up, _ in enumerate(state):
                state[up] += 1
                if up + 1 == len(state) or state[up] < state[up + 1]:
                    break
                state[up] = up
            if state[-1] == len(self.attr_ordering):
                if len(state) < min(self.max_attrs, n_attrs):
                    state = list(range(len(state) + 1))
                else:
                    break

    def compute_score(self, state):
        """
        Compute score using chi-square test of independence.

        If mosaic colors by class distribution, chi-square is computed by
        comparing the expected (prior) and observed class distribution in
        each cell. Otherwise, compute the independence of the shown attributes.
        """
        master = self.master
        data = master.discrete_data
        domain = data.domain
        attrlist = [self.attr_ordering[i] for i in state]
        cond_dist = get_conditional_distribution(data, attrlist)[0]
        n = cond_dist[""]
        ss = 0
        if self._compute_class_dists():
            class_values = domain.class_var.values
        else:
            class_values = None
            attr_indices = [domain.index(attr) for attr in attrlist]
        for indices in product(*(range(len(a.values)) for a in attrlist)):
            attr_vals = "-".join(attr.values[ind]
                                 for attr, ind in zip(attrlist, indices))
            total = cond_dist[attr_vals]
            if class_values:  # showing class distributions
                for i, class_val in enumerate(class_values):
                    expected = total * self.marginal[i]
                    if expected > 1e-6:
                        observed = cond_dist[attr_vals + '-' + class_val]
                        ss += (expected - observed) ** 2 / expected
            else:
                observed = cond_dist[attr_vals]
                expected = n * reduce(
                    mul,
                    (self.marginal[attr_idx][ind]
                     for attr_idx, ind in zip(attr_indices, indices)))
                if expected > 1e-6:
                    ss += (expected - observed) ** 2 / expected
        if class_values:
            dof = (len(class_values) - 1) * \
                  reduce(mul, (len(attr.values) for attr in attrlist))
        else:
            dof = reduce(mul, (len(attr.values) - 1 for attr in attrlist))
        return distributions.chi2.sf(ss, dof)

    def bar_length(self, score):
        return 1 if score == 0 else -log(score, 10) / 50

    def on_selection_changed(self, selected, deselected):
        if not selected.isEmpty():
            attrs = selected.indexes()[0].data(self._AttrRole)
            self.selectionChanged.emit(attrs + (None, ) * (4 - len(attrs)))

    def on_manual_change(self, attrs):
        model = self.rank_model
        self.rank_table.selectionModel().clear()
        for row in range(model.rowCount()):
            row_attrs = model.data(model.index(row, 0), self._AttrRole)
            if row_attrs == tuple(attrs):
                self.rank_table.selectRow(row)
                return

    def row_for_state(self, score, state):
        """The row consists of attributes sorted by name; class is at the
        beginning, if present, so it's on the x-axis and not lost somewhere."""
        class_var = self.master.color_data.domain.class_var
        attrs = tuple(
            sorted((self.attr_ordering[x] for x in state),
                   key=lambda attr: (1 - (attr is class_var), attr.name)))
        item = QStandardItem(", ".join(a.name for a in attrs))
        item.setData(attrs, self._AttrRole)
        return [item]


class OWMosaicDisplay(OWWidget):
    name = "Mosaic Display"
    description = "Display data in a mosaic plot."
    icon = "icons/MosaicDisplay.svg"
    priority = 220
    keywords = []

    class Inputs:
        data = Input("Data", Table, default=True)
        data_subset = Input("Data Subset", Table)

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

    settingsHandler = DomainContextHandler()
    vizrank = SettingProvider(MosaicVizRank)
    settings_version = 2
    use_boxes = Setting(True)
    variable1 = ContextSetting(None)
    variable2 = ContextSetting(None)
    variable3 = ContextSetting(None)
    variable4 = ContextSetting(None)
    variable_color = ContextSetting(None)
    selection = ContextSetting(set())

    BAR_WIDTH = 5
    SPACING = 4
    ATTR_NAME_OFFSET = 20
    ATTR_VAL_OFFSET = 3
    BLUE_COLORS = [QColor(255, 255, 255), QColor(210, 210, 255),
                   QColor(110, 110, 255), QColor(0, 0, 255)]
    RED_COLORS = [QColor(255, 255, 255), QColor(255, 200, 200),
                  QColor(255, 100, 100), QColor(255, 0, 0)]
    graph_name = "canvas"

    attrs_changed_manually = Signal(list)

    class Warning(OWWidget.Warning):
        incompatible_subset = Msg("Data subset is incompatible with Data")
        no_valid_data = Msg("No valid data")
        no_cont_selection_sql = \
            Msg("Selection of numeric features on SQL is not supported")

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

        self.data = None
        self.discrete_data = None
        self.subset_data = None
        self.subset_indices = None

        self.color_data = None

        self.areas = []

        self.canvas = QGraphicsScene()
        self.canvas_view = ViewWithPress(
            self.canvas, handler=self.clear_selection)
        self.mainArea.layout().addWidget(self.canvas_view)
        self.canvas_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.canvas_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.canvas_view.setRenderHint(QPainter.Antialiasing)

        box = gui.vBox(self.controlArea, box=True)
        self.model_1 = DomainModel(
            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE)
        self.model_234 = DomainModel(
            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,
            placeholder="(None)")
        self.attr_combos = [
            gui.comboBox(
                box, self, value="variable{}".format(i),
                orientation=Qt.Horizontal, contentsLength=12,
                callback=self.attr_changed,
                model=self.model_1 if i == 1 else self.model_234)
            for i in range(1, 5)]
        self.vizrank, self.vizrank_button = MosaicVizRank.add_vizrank(
            box, self, "Find Informative Mosaics", self.set_attr)

        box2 = gui.vBox(self.controlArea, box="Interior Coloring")
        self.color_model = DomainModel(
            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,
            placeholder="(Pearson residuals)")
        self.cb_attr_color = gui.comboBox(
            box2, self, value="variable_color",
            orientation=Qt.Horizontal, contentsLength=12, labelWidth=50,
            callback=self.set_color_data, model=self.color_model)
        self.bar_button = gui.checkBox(
            box2, self, 'use_boxes', label='Compare with total',
            callback=self.update_graph)
        gui.rubber(self.controlArea)

    def sizeHint(self):
        return QSize(720, 530)

    def _get_discrete_data(self, data):
        """
        Discretize continuous attributes.
        Return None when there is no data, no rows, or no primitive attributes.
        """
        if (data is None or
                not len(data) or
                not any(attr.is_discrete or attr.is_continuous
                        for attr in chain(data.domain.variables,
                                          data.domain.metas))):
            return None
        elif any(attr.is_continuous for attr in data.domain.variables):
            return Discretize(
                method=EqualFreq(n=4), remove_const=False, discretize_classes=True,
                discretize_metas=True)(data)
        else:
            return data

    def init_combos(self, data):
        def set_combos(value):
            self.model_1.set_domain(value)
            self.model_234.set_domain(value)
            self.color_model.set_domain(value)

        if data is None:
            set_combos(None)
            self.variable1 = self.variable2 = self.variable3 \
                = self.variable4 = self.variable_color = None
            return
        set_combos(self.data.domain)

        if len(self.model_1) > 0:
            self.variable1 = self.model_1[0]
            self.variable2 = self.model_1[min(1, len(self.model_1) - 1)]
        self.variable3 = self.variable4 = None
        self.variable_color = self.data.domain.class_var  # None is OK, too

    def get_disc_attr_list(self):
        return [self.discrete_data.domain[var.name]
                for var in (self.variable1, self.variable2,
                            self.variable3, self.variable4)
                if var]

    def set_attr(self, *attrs):
        self.variable1, self.variable2, self.variable3, self.variable4 = [
            attr and self.data.domain[attr.name] for attr in attrs]
        self.reset_graph()

    def attr_changed(self):
        self.attrs_changed_manually.emit(self.get_disc_attr_list())
        self.reset_graph()

    def resizeEvent(self, e):
        OWWidget.resizeEvent(self, e)
        self.update_graph()

    def showEvent(self, ev):
        OWWidget.showEvent(self, ev)
        self.update_graph()

    @Inputs.data
    def set_data(self, data):
        if isinstance(data, SqlTable) and data.approx_len() > LARGE_TABLE:
            data = data.sample_time(DEFAULT_SAMPLE_TIME)

        self.closeContext()
        self.data = data

        self.vizrank.stop_and_reset()
        self.vizrank_button.setEnabled(
            self.data is not None and len(self.data) > 1
            and len(self.data.domain.attributes) >= 1)

        if self.data is None:
            self.discrete_data = None
            self.init_combos(None)
            return

        self.init_combos(self.data)
        self.openContext(self.data)

    @Inputs.data_subset
    def set_subset_data(self, data):
        self.subset_data = data

    # this is called by widget after setData and setSubsetData are called.
    # this way the graph is updated only once
    def handleNewSignals(self):
        self.Warning.incompatible_subset.clear()
        self.subset_indices = None
        if self.data is not None and self.subset_data:
            transformed = self.subset_data.transform(self.data.domain)
            if np.all(np.isnan(transformed.X)) \
                    and np.all(np.isnan(transformed.Y)):
                self.Warning.incompatible_subset()
            else:
                indices = {e.id for e in transformed}
                self.subset_indices = [ex.id in indices for ex in self.data]
        self.set_color_data()
        self.reset_graph()

    def clear_selection(self):
        self.selection = set()
        self.update_selection_rects()
        self.send_selection()

    def coloring_changed(self):
        self.vizrank.coloring_changed()
        self.update_graph()

    def reset_graph(self):
        self.clear_selection()
        self.update_graph()

    def set_color_data(self):
        if self.data is None:
            return
        self.bar_button.setEnabled(self.variable_color is not None)
        attrs = [v for v in self.model_1 if v and v is not self.variable_color]
        domain = Domain(attrs, self.variable_color, None)
        self.color_data = self.data.from_table(domain, self.data)
        self.discrete_data = self._get_discrete_data(self.color_data)
        self.vizrank.stop_and_reset()
        self.vizrank_button.setEnabled(True)
        self.coloring_changed()

    def update_selection_rects(self):
        pens = (QPen(), QPen(Qt.black, 3, Qt.DotLine))
        for i, (_, _, area) in enumerate(self.areas):
            area.setPen(pens[i in self.selection])

    def select_area(self, index, ev):
        if ev.button() != Qt.LeftButton:
            return
        if ev.modifiers() & Qt.ControlModifier:
            self.selection ^= {index}
        else:
            self.selection = {index}
        self.update_selection_rects()
        self.send_selection()

    def send_selection(self):
        if not self.selection or self.data is None:
            self.Outputs.selected_data.send(None)
            self.Outputs.annotated_data.send(
                create_annotated_table(self.data, []))
            return
        filters = []
        self.Warning.no_cont_selection_sql.clear()
        if self.discrete_data is not self.data:
            if isinstance(self.data, SqlTable):
                self.Warning.no_cont_selection_sql()
        for i in self.selection:
            cols, vals, _ = self.areas[i]
            filters.append(
                filter.Values(
                    filter.FilterDiscrete(col, [val])
                    for col, val in zip(cols, vals)))
        if len(filters) > 1:
            filters = filter.Values(filters, conjunction=False)
        else:
            filters = filters[0]
        selection = filters(self.discrete_data)
        idset = set(selection.ids)
        sel_idx = [i for i, id in enumerate(self.data.ids) if id in idset]
        if self.discrete_data is not self.data:
            selection = self.data[sel_idx]
        self.Outputs.selected_data.send(selection)
        self.Outputs.annotated_data.send(
            create_annotated_table(self.data, sel_idx))

    def send_report(self):
        self.report_plot(self.canvas)

    def update_graph(self):
        spacing = self.SPACING
        bar_width = self.BAR_WIDTH

        def get_counts(attr_vals, values):
            """Calculate rectangles' widths; if all are 0, they are set to 1."""
            if not attr_vals:
                counts = [conditionaldict[val] for val in values]
            else:
                counts = [conditionaldict[attr_vals + "-" + val]
                          for val in values]
            total = sum(counts)
            if total == 0:
                counts = [1] * len(values)
                total = sum(counts)
            return total, counts

        def draw_data(attr_list, x0_x1, y0_y1, side, condition,
                      total_attrs, used_attrs, used_vals, attr_vals=""):
            x0, x1 = x0_x1
            y0, y1 = y0_y1
            if conditionaldict[attr_vals] == 0:
                add_rect(x0, x1, y0, y1, "",
                         used_attrs, used_vals, attr_vals=attr_vals)
                # store coordinates for later drawing of labels
                draw_text(side, attr_list[0], (x0, x1), (y0, y1), total_attrs,
                          used_attrs, used_vals, attr_vals)
                return

            attr = attr_list[0]
            # how much smaller rectangles do we draw
            edge = len(attr_list) * spacing
            values = get_variable_values_sorted(attr)
            if side % 2:
                values = values[::-1]  # reverse names if necessary

            if side % 2 == 0:  # we are drawing on the x axis
                # remove the space needed for separating different attr. values
                whole = max(0, (x1 - x0) - edge * (len(values) - 1))
                if whole == 0:
                    edge = (x1 - x0) / float(len(values) - 1)
            else:  # we are drawing on the y axis
                whole = max(0, (y1 - y0) - edge * (len(values) - 1))
                if whole == 0:
                    edge = (y1 - y0) / float(len(values) - 1)

            total, counts = get_counts(attr_vals, values)

            # when visualizing the third attribute and the first attribute has
            # the last value, reverse the order in which the boxes are drawn;
            # otherwise, if the last cell, nearest to the labels of the fourth
            # attribute, is empty, we wouldn't be able to position the labels
            valrange = list(range(len(values)))
            if len(attr_list + used_attrs) == 4 and len(used_attrs) == 2:
                attr1values = get_variable_values_sorted(used_attrs[0])
                if used_vals[0] == attr1values[-1]:
                    valrange = valrange[::-1]

            for i in valrange:
                start = i * edge + whole * float(sum(counts[:i]) / total)
                end = i * edge + whole * float(sum(counts[:i + 1]) / total)
                val = values[i]
                htmlval = to_html(val)
                newattrvals = attr_vals + "-" + val if attr_vals else val

                tooltip = "{}&nbsp;&nbsp;&nbsp;&nbsp;{}: <b>{}</b><br/>".format(
                    condition, attr.name, htmlval)
                attrs = used_attrs + [attr]
                vals = used_vals + [val]
                args = attrs, vals, newattrvals
                if side % 2 == 0:  # if we are moving horizontally
                    if len(attr_list) == 1:
                        add_rect(x0 + start, x0 + end, y0, y1, tooltip, *args)
                    else:
                        draw_data(
                            attr_list[1:], (x0 + start, x0 + end), (y0, y1),
                            side + 1, tooltip, total_attrs, *args)
                else:
                    if len(attr_list) == 1:
                        add_rect(x0, x1, y0 + start, y0 + end, tooltip, *args)
                    else:
                        draw_data(
                            attr_list[1:], (x0, x1), (y0 + start, y0 + end),
                            side + 1, tooltip, total_attrs, *args)
            draw_text(side, attr_list[0], (x0, x1), (y0, y1),
                      total_attrs, used_attrs, used_vals, attr_vals)

        def draw_text(side, attr, x0_x1, y0_y1,
                      total_attrs, used_attrs, used_vals, attr_vals):
            x0, x1 = x0_x1
            y0, y1 = y0_y1
            if side in drawn_sides:
                return

            # the text on the right will be drawn when we are processing
            # visualization of the last value of the first attribute
            if side == 3:
                attr1values = get_variable_values_sorted(used_attrs[0])
                if used_vals[0] != attr1values[-1]:
                    return

            if not conditionaldict[attr_vals]:
                if side not in draw_positions:
                    draw_positions[side] = (x0, x1, y0, y1)
                return
            else:
                if side in draw_positions:
                    # restore the positions of attribute values and name
                    (x0, x1, y0, y1) = draw_positions[side]

            drawn_sides.add(side)

            values = get_variable_values_sorted(attr)
            if side % 2:
                values = values[::-1]

            spaces = spacing * (total_attrs - side) * (len(values) - 1)
            width = x1 - x0 - spaces * (side % 2 == 0)
            height = y1 - y0 - spaces * (side % 2 == 1)

            # calculate position of first attribute
            currpos = 0
            total, counts = get_counts(attr_vals, values)
            aligns = [Qt.AlignTop | Qt.AlignHCenter,
                      Qt.AlignRight | Qt.AlignVCenter,
                      Qt.AlignBottom | Qt.AlignHCenter,
                      Qt.AlignLeft | Qt.AlignVCenter]
            align = aligns[side]
            for i, val in enumerate(values):
                if distributiondict[val] != 0:
                    perc = counts[i] / float(total)
                    rwidth = width * perc
                    xs = [x0 + currpos + rwidth / 2,
                          x0 - self.ATTR_VAL_OFFSET,
                          x0 + currpos + rwidth / 2,
                          x1 + self.ATTR_VAL_OFFSET]
                    ys = [y1 + self.ATTR_VAL_OFFSET,
                          y0 + currpos + height * 0.5 * perc,
                          y0 - self.ATTR_VAL_OFFSET,
                          y0 + currpos + height * 0.5 * perc]

                    CanvasText(
                        self.canvas, val, xs[side], ys[side], align,
                        max_width=rwidth if side == 0 else None)
                    space = height if side % 2 else width
                    currpos += perc * space + spacing * (total_attrs - side)

            xs = [x0 + (x1 - x0) / 2,
                  x0 - max_ylabel_w1 - self.ATTR_VAL_OFFSET,
                  x0 + (x1 - x0) / 2,
                  x1 + max_ylabel_w2 + self.ATTR_VAL_OFFSET]
            ys = [y1 + self.ATTR_VAL_OFFSET + self.ATTR_NAME_OFFSET,
                  y0 + (y1 - y0) / 2,
                  y0 - self.ATTR_VAL_OFFSET - self.ATTR_NAME_OFFSET,
                  y0 + (y1 - y0) / 2]
            CanvasText(
                self.canvas, attr.name, xs[side], ys[side], align, bold=True,
                vertical=side % 2)

        def add_rect(x0, x1, y0, y1, condition,
                     used_attrs, used_vals, attr_vals=""):
            area_index = len(self.areas)
            x1 += (x0 == x1)
            y1 += (y0 == y1)
            # rectangles of width and height 1 are not shown - increase
            y1 += (x1 - x0 + y1 - y0 == 2)
            colors = class_var and [QColor(*col) for col in class_var.colors]

            def select_area(_, ev):
                self.select_area(area_index, ev)

            def rect(x, y, w, h, z, pen_color=None, brush_color=None, **args):
                if pen_color is None:
                    return CanvasRectangle(
                        self.canvas, x, y, w, h, z=z, onclick=select_area,
                        **args)
                if brush_color is None:
                    brush_color = pen_color
                return CanvasRectangle(
                    self.canvas, x, y, w, h, pen_color, brush_color, z=z,
                    onclick=select_area, **args)

            def line(x1, y1, x2, y2):
                r = QGraphicsLineItem(x1, y1, x2, y2, None)
                self.canvas.addItem(r)
                r.setPen(QPen(Qt.white, 2))
                r.setZValue(30)

            outer_rect = rect(x0, y0, x1 - x0, y1 - y0, 30)
            self.areas.append((used_attrs, used_vals, outer_rect))
            if not conditionaldict[attr_vals]:
                return

            if self.variable_color is None:
                s = sum(apriori_dists[0])
                expected = s * reduce(
                    mul,
                    (apriori_dists[i][used_vals[i]] / float(s)
                     for i in range(len(used_vals))))
                actual = conditionaldict[attr_vals]
                pearson = float((actual - expected) / sqrt(expected))
                if pearson == 0:
                    ind = 0
                else:
                    ind = max(0, min(int(log(abs(pearson), 2)), 3))
                color = [self.RED_COLORS, self.BLUE_COLORS][pearson > 0][ind]
                rect(x0, y0, x1 - x0, y1 - y0, -20, color)
                outer_rect.setToolTip(
                    condition + "<hr/>" +
                    "Expected instances: %.1f<br>"
                    "Actual instances: %d<br>"
                    "Standardized (Pearson) residual: %.1f" %
                    (expected, conditionaldict[attr_vals], pearson))
            else:
                cls_values = get_variable_values_sorted(class_var)
                prior = get_distribution(data, class_var.name)
                total = 0
                for i, value in enumerate(cls_values):
                    val = conditionaldict[attr_vals + "-" + value]
                    if val == 0:
                        continue
                    if i == len(cls_values) - 1:
                        v = y1 - y0 - total
                    else:
                        v = ((y1 - y0) * val) / conditionaldict[attr_vals]
                    rect(x0, y0 + total, x1 - x0, v, -20, colors[i])
                    total += v

                if self.use_boxes and \
                        abs(x1 - x0) > bar_width and abs(y1 - y0) > bar_width:
                    total = 0
                    line(x0 + bar_width, y0, x0 + bar_width, y1)
                    n = sum(prior)
                    for i, (val, color) in enumerate(zip(prior, colors)):
                        if i == len(prior) - 1:
                            h = y1 - y0 - total
                        else:
                            h = (y1 - y0) * val / n
                        rect(x0, y0 + total, bar_width, h, 20, color)
                        total += h

                if conditionalsubsetdict:
                    if conditionalsubsetdict[attr_vals]:
                        if self.subset_indices is not None:
                            line(x1 - bar_width, y0, x1 - bar_width, y1)
                            total = 0
                            n = conditionalsubsetdict[attr_vals]
                            if n:
                                for i, (cls, color) in \
                                        enumerate(zip(cls_values, colors)):
                                    val = conditionalsubsetdict[
                                        attr_vals + "-" + cls]
                                    if val == 0:
                                        continue
                                    if i == len(prior) - 1:
                                        v = y1 - y0 - total
                                    else:
                                        v = ((y1 - y0) * val) / n
                                    rect(x1 - bar_width, y0 + total,
                                         bar_width, v, 15, color)
                                    total += v

                actual = [conditionaldict[attr_vals + "-" + cls_values[i]]
                          for i in range(len(prior))]
                n_actual = sum(actual)
                if n_actual > 0:
                    apriori = [prior[key] for key in cls_values]
                    n_apriori = sum(apriori)
                    text = "<br/>".join(
                        "<b>%s</b>: %d / %.1f%% (Expected %.1f / %.1f%%)" %
                        (cls, act, 100.0 * act / n_actual,
                         apr / n_apriori * n_actual, 100.0 * apr / n_apriori)
                        for cls, act, apr in zip(cls_values, actual, apriori))
                else:
                    text = ""
                outer_rect.setToolTip(
                    "{}<hr>Instances: {}<br><br>{}".format(
                        condition, n_actual, text[:-4]))

        def create_legend():
            if self.variable_color is None:
                names = ["<-8", "-8:-4", "-4:-2", "-2:2", "2:4", "4:8", ">8",
                         "Residuals:"]
                colors = self.RED_COLORS[::-1] + self.BLUE_COLORS[1:]
                edges = repeat(Qt.black)
            else:
                names = get_variable_values_sorted(class_var)
                edges = colors = [QColor(*col) for col in class_var.colors]

            items = []
            size = 8
            for name, color, edgecolor in zip(names, colors, edges):
                item = QGraphicsItemGroup()
                item.addToGroup(
                    CanvasRectangle(None, -size / 2, -size / 2, size, size,
                                    edgecolor, color))
                item.addToGroup(
                    CanvasText(None, name, size, 0, Qt.AlignVCenter))
                items.append(item)
            return wrap_legend_items(
                items, hspacing=20, vspacing=16 + size,
                max_width=self.canvas_view.width() - xoff)

        self.canvas.clear()
        self.areas = []

        data = self.discrete_data
        if data is None:
            return
        attr_list = self.get_disc_attr_list()
        class_var = data.domain.class_var
        if class_var:
            sql = isinstance(data, SqlTable)
            name = not sql and data.name
            # save class_var because it is removed in the next line
            data = data[:, attr_list + [class_var]]
            data.domain.class_var = class_var
            if not sql:
                data.name = name
        else:
            data = data[:, attr_list]
        # TODO: check this
        # data = Preprocessor_dropMissing(data)
        if len(data) == 0:
            self.Warning.no_valid_data()
            return
        else:
            self.Warning.no_valid_data.clear()

        attrs = [attr for attr in attr_list if not attr.values]
        if attrs:
            CanvasText(self.canvas,
                       "Feature {} has no values".format(attrs[0]),
                       (self.canvas_view.width() - 120) / 2,
                       self.canvas_view.height() / 2)
            return
        if self.variable_color is None:
            apriori_dists = [get_distribution(data, attr) for attr in attr_list]
        else:
            apriori_dists = []

        def get_max_label_width(attr):
            values = get_variable_values_sorted(attr)
            maxw = 0
            for val in values:
                t = CanvasText(self.canvas, val, 0, 0, bold=0, show=False)
                maxw = max(int(t.boundingRect().width()), maxw)
            return maxw

        xoff = 20

        # get the maximum width of rectangle
        width = 20
        max_ylabel_w1 = max_ylabel_w2 = 0
        if len(attr_list) > 1:
            text = CanvasText(self.canvas, attr_list[1].name, bold=1, show=0)
            max_ylabel_w1 = min(get_max_label_width(attr_list[1]), 150)
            width = 5 + text.boundingRect().height() + \
                self.ATTR_VAL_OFFSET + max_ylabel_w1
            xoff = width
            if len(attr_list) == 4:
                text = CanvasText(self.canvas, attr_list[3].name, bold=1, show=0)
                max_ylabel_w2 = min(get_max_label_width(attr_list[3]), 150)
                width += text.boundingRect().height() + \
                    self.ATTR_VAL_OFFSET + max_ylabel_w2 - 10

        legend = create_legend()

        # get the maximum height of rectangle
        yoff = 45
        legendoff = yoff + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35
        square_size = min(self.canvas_view.width() - width - 20,
                          self.canvas_view.height() - legendoff
                          - legend.boundingRect().height())

        if square_size < 0:
            return  # canvas is too small to draw rectangles
        self.canvas_view.setSceneRect(
            0, 0, self.canvas_view.width(), self.canvas_view.height())

        drawn_sides = set()
        draw_positions = {}

        conditionaldict, distributiondict = \
            get_conditional_distribution(data, attr_list)
        conditionalsubsetdict = None
        if self.subset_indices:
            conditionalsubsetdict, _ = get_conditional_distribution(
                self.discrete_data[self.subset_indices], attr_list)

        # draw rectangles
        draw_data(
            attr_list, (xoff, xoff + square_size), (yoff, yoff + square_size),
            0, "", len(attr_list), [], [])

        self.canvas.addItem(legend)
        legend.setPos(
            xoff - legend.boundingRect().x()
            + max(0, (square_size - legend.boundingRect().width()) / 2),
            legendoff + square_size)
        self.update_selection_rects()

    @classmethod
    def migrate_context(cls, context, version):
        if version < 2:
            settings.migrate_str_to_variable(context, none_placeholder="(None)")


def get_conditional_distribution(data, attrs):
    cond_dist = defaultdict(int)
    dist = defaultdict(int)
    cond_dist[""] = dist[""] = len(data)
    all_attrs = attrs[:]
    if data.domain.class_var is not None:
        all_attrs.append(data.domain.class_var)

    for i in range(1, len(all_attrs) + 1):
        attr = all_attrs[:i]
        if isinstance(data, SqlTable):
            # make all possible pairs of attributes + class_var
            attr = [a.to_sql() for a in attr]
            fields = attr + ["COUNT(*)"]
            query = data._sql_query(fields, group_by=attr)
            with data._execute_sql_query(query) as cur:
                res = cur.fetchall()
            for r in res:
                str_values = [a.repr_val(a.to_val(x))
                              for a, x in zip(all_attrs, r[:-1])]
                str_values = [x if x != '?' else 'None' for x in str_values]
                cond_dist['-'.join(str_values)] = r[-1]
                dist[str_values[-1]] += r[-1]
        else:
            for indices in product(*(range(len(a.values)) for a in attr)):
                vals = []
                conditions = []
                for k, ind in enumerate(indices):
                    vals.append(attr[k].values[ind])
                    fd = filter.FilterDiscrete(
                        column=attr[k], values=[attr[k].values[ind]])
                    conditions.append(fd)
                filt = filter.Values(conditions)
                filtdata = filt(data)
                cond_dist['-'.join(vals)] = len(filtdata)
                dist[vals[-1]] += len(filtdata)
    return cond_dist, dist


if __name__ == "__main__":  # pragma: no cover
    data = Table("zoo")
    WidgetPreview(OWMosaicDisplay).run(data, set_subset_data=data[::10])

```
--- 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:** Selection Not Preserved After Resetting Graph in OWMosaicDisplay

**Description**
When a selection is made in the Mosaic widget and the data is subsequently cleared and reloaded, the selection does not persist as expected. This results in the selected area being lost, which can disrupt the user's workflow and data analysis.

**Steps to Reproduce**
```python
widget = OWMosaicDisplay()
data = Table("iris.tab")
widget.set_data(data)
widget.select_area(1, QMouseEvent(QEvent.MouseButtonPress, QPoint(), Qt.LeftButton, Qt.LeftButton, Qt.KeyboardModifiers()))
widget.clear_data()
widget.set_data(data)
```

**Expected Behavior**
After reloading the data, the previously selected area should remain selected, and the corresponding selection data should be available for further operations.

**Actual Behavior**
The selection is lost upon reloading the data, resulting in no area being selected despite prior user interaction.
[/ISSUE]
{
    "TestOWMosaicDisplay.test_change_domain": "PASSED",
    "TestOWMosaicDisplay.test_combos_and_mosaic": "PASSED",
    "TestOWMosaicDisplay.test_continuous_metas": "PASSED",
    "TestOWMosaicDisplay.test_different_number_of_attributes": "PASSED",
    "TestOWMosaicDisplay.test_empty_column": "PASSED",
    "TestOWMosaicDisplay.test_keyerror": "PASSED",
    "TestOWMosaicDisplay.test_minimum_size": "PASSED",
    "TestOWMosaicDisplay.test_missing_values": "PASSED",
    "TestOWMosaicDisplay.test_msg_base_class": "PASSED",
    "TestOWMosaicDisplay.test_no_data": "PASSED",
    "TestOWMosaicDisplay.test_outputs": "PASSED",
    "TestOWMosaicDisplay.test_selection_setting": "PASSED",
    "TestOWMosaicDisplay.test_string_meta": "PASSED",
    "TestOWMosaicDisplay.test_subset": "PASSED",
    "TestOWMosaicDisplay.test_vizrank_receives_manual_change": "PASSED",
    "MosaicVizRankTests.test_color_combo": "PASSED",
    "MosaicVizRankTests.test_count": "PASSED",
    "MosaicVizRankTests.test_does_not_crash_cont_class": "PASSED",
    "MosaicVizRankTests.test_finished": "PASSED",
    "MosaicVizRankTests.test_incompatible_subset": "PASSED",
    "MosaicVizRankTests.test_iteration": "PASSED",
    "MosaicVizRankTests.test_minimum_size": "PASSED",
    "MosaicVizRankTests.test_msg_base_class": "PASSED",
    "MosaicVizRankTests.test_nan_column": "PASSED",
    "MosaicVizRankTests.test_on_manual_change": "PASSED",
    "MosaicVizRankTests.test_pause_continue": "PASSED",
    "MosaicVizRankTests.test_row_for_state": "PASSED",
    "MosaicVizRankTests.test_scores": "PASSED",
    "MosaicVizRankTests.test_subset_data": "PASSED"
}
diff --git a/Orange/widgets/visualize/owmosaic.py b/Orange/widgets/visualize/owmosaic.py
index a6c07800b..ec16bc9e0 100644
--- a/Orange/widgets/visualize/owmosaic.py
+++ b/Orange/widgets/visualize/owmosaic.py
@@ -317,6 +317,7 @@ class OWMosaicDisplay(OWWidget):
         self.discrete_data = None
         self.subset_data = None
         self.subset_indices = None
+        self.__pending_selection = None
 
         self.color_data = None
 
@@ -442,6 +443,7 @@ class OWMosaicDisplay(OWWidget):
 
         self.init_combos(self.data)
         self.openContext(self.data)
+        self.__pending_selection = self.selection
 
     @Inputs.data_subset
     def set_subset_data(self, data):
@@ -475,6 +477,11 @@ class OWMosaicDisplay(OWWidget):
     def reset_graph(self):
         self.clear_selection()
         self.update_graph()
+        if self.__pending_selection is not None:
+            self.selection = self.__pending_selection
+            self.__pending_selection = None
+            self.update_selection_rects()
+            self.send_selection()
 
     def set_color_data(self):
         if self.data is None:

{"Orange/widgets/visualize/owmosaic.py": "from collections import defaultdict\nfrom functools import reduce\nfrom itertools import product, chain, repeat\nfrom math import sqrt, log\nfrom operator import mul, attrgetter\n\nimport numpy as np\nfrom scipy.stats import distributions\nfrom scipy.special import comb\nfrom AnyQt.QtCore import Qt, QSize, pyqtSignal as Signal\nfrom AnyQt.QtGui import QColor, QPainter, QPen, QStandardItem\nfrom AnyQt.QtWidgets import (\n    QGraphicsScene, QGraphicsLineItem, QGraphicsItemGroup)\n\nfrom Orange.data import Table, filter, Variable, Domain\nfrom Orange.data.sql.table import SqlTable, LARGE_TABLE, DEFAULT_SAMPLE_TIME\nfrom Orange.preprocess import Discretize\nfrom Orange.preprocess.discretize import EqualFreq\nfrom Orange.preprocess.score import ReliefF\nfrom Orange.statistics.distribution import get_distribution, get_distributions\nfrom Orange.widgets import gui, settings\nfrom Orange.widgets.gui import OWComponent\nfrom Orange.widgets.settings import (\n    Setting, DomainContextHandler, ContextSetting, SettingProvider)\nfrom Orange.widgets.utils import to_html, get_variable_values_sorted\nfrom Orange.widgets.utils.annotated_data import (create_annotated_table,\n                                                 ANNOTATED_DATA_SIGNAL_NAME)\nfrom Orange.widgets.utils.itemmodels import DomainModel\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.visualize.utils import (\n    CanvasText, CanvasRectangle, ViewWithPress, VizRankDialog)\nfrom Orange.widgets.visualize.utils.plotutils import wrap_legend_items\nfrom Orange.widgets.widget import OWWidget, Msg, Input, Output\n\n\nclass MosaicVizRank(VizRankDialog, OWComponent):\n    \"\"\"VizRank dialog for Mosaic\"\"\"\n    captionTitle = \"Mosaic Ranking\"\n    max_attrs = Setting(3)\n\n    pairSelected = Signal(Variable, Variable, Variable, Variable)\n    _AttrRole = next(gui.OrangeUserRole)\n\n    def __init__(self, master):\n        \"\"\"Add the spin box for maximal number of attributes\"\"\"\n        VizRankDialog.__init__(self, master)\n        OWComponent.__init__(self, master)\n\n        box = gui.hBox(self)\n        self.max_attr_spin = gui.spin(\n            box, self, \"max_attrs\", 2, 4,\n            label=\"Limit the number of attributes to: \",\n            controlWidth=50, alignment=Qt.AlignRight,\n            callback=self.max_attr_changed)\n        gui.rubber(box)\n        self.layout().addWidget(self.button)\n        self.attr_ordering = None\n        self.marginal = {}\n        self.last_run_max_attr = None\n\n        self.master.attrs_changed_manually.connect(self.on_manual_change)\n\n    def sizeHint(self):\n        return QSize(400, 512)\n\n    def initialize(self):\n        \"\"\"Clear the ordering to trigger recomputation when needed\"\"\"\n        super().initialize()\n        self.attr_ordering = None\n\n    def initialize_keep_ordering(self):\n        \"\"\"Initialize triggered by change of coloring\"\"\"\n        super().initialize()\n\n    def before_running(self):\n        \"\"\"\n        Disable the spin for maximal number of attributes before running and\n        enable afterwards. Also, if the number of attributes is different than\n        in the last run, reset the saved state (if it was paused).\n        \"\"\"\n        if self.max_attrs != self.last_run_max_attr:\n            self.saved_state = None\n            self.saved_progress = 0\n        if self.saved_state is None:\n            self.scores = []\n            self.rank_model.clear()\n        self.compute_attr_order()\n        self.last_run_max_attr = self.max_attrs\n        self.max_attr_spin.setDisabled(True)\n\n    def stopped(self):\n        self.max_attr_spin.setDisabled(False)\n\n    def max_attr_changed(self):\n        \"\"\"\n        Change the button label when the maximal number of attributes changes.\n\n        The method does not reset anything so the user can still see the\n        results until actually restarting the search.\n        \"\"\"\n        if self.max_attrs != self.last_run_max_attr or self.saved_state is None:\n            self.button.setText(\"Start\")\n        else:\n            self.button.setText(\"Continue\")\n        self.button.setEnabled(self.check_preconditions())\n\n    def coloring_changed(self):\n        self.stop_and_reset(self.initialize_keep_ordering)\n\n    def check_preconditions(self):\n        \"\"\"Require at least one variable to allow ranking.\"\"\"\n        self.Information.add_message(\"no_attributes\", \"No variables to rank.\")\n        self.Information.no_attributes.clear()\n        data = self.master.discrete_data\n        if not super().check_preconditions() or data is None:\n            return False\n        if not data.domain.attributes:\n            self.Information.no_attributes()\n            return False\n        return True\n\n    def compute_attr_order(self):\n        \"\"\"\n        Order attributes by Relief if there is a target variable. In case of\n        ties or without target, order by name.\n\n        Add the class variable at the beginning when not coloring by class\n        distribution.\n\n        If `self.attrs` is not `None`, keep the ordering and just add or remove\n        the class as needed.\n        \"\"\"\n        data = self.master.discrete_data\n        class_var = data.domain.class_var\n        if not self.attr_ordering:\n            if class_var is None:\n                self.attr_ordering = sorted(data.domain, key=attrgetter(\"name\"))\n            else:\n                weights = ReliefF(n_iterations=100, k_nearest=10)(data)\n                attrs = sorted(zip(weights, data.domain.attributes),\n                               key=lambda x: (-x[0], x[1].name))\n                self.attr_ordering = [a for _, a in attrs]\n\n    def _compute_class_dists(self):\n        return self.master.variable_color is not None\n\n    def state_count(self):\n        \"\"\"\n        Return the number of combinations, starting with a single attribute\n        if Mosaic is colored by class distributions, and two if by Pearson\n        \"\"\"\n        n_attrs = len(self.master.discrete_data.domain.attributes)\n        min_attrs = 1 if self._compute_class_dists() else 2\n        max_attrs = min(n_attrs, self.max_attrs)\n        return sum(comb(n_attrs, k, exact=True)\n                   for k in range(min_attrs, max_attrs + 1))\n\n    def iterate_states(self, state):\n        \"\"\"\n        Iterate through all combinations of attributes as ordered by Relief,\n        starting with a single attribute if Mosaic is colored by class\n        distributions, and two if by Pearson.\n        \"\"\"\n        # If we put initialization of `self.attrs` to `initialize`,\n        # `score_heuristic` would be run on every call to master's `set_data`.\n        master = self.master\n        data = master.discrete_data\n        if state is None:  # on the first call, compute order\n            if self._compute_class_dists():\n                self.marginal = get_distribution(data, data.domain.class_var)\n                self.marginal.normalize()\n                state = [0]\n            else:\n                self.marginal = get_distributions(data)\n                for dist in self.marginal:\n                    dist.normalize()\n                state = [0, 1]\n        n_attrs = len(data.domain.attributes)\n        while True:\n            yield state\n            # Reset while running; just abort\n            if self.attr_ordering is None:\n                break\n            for up, _ in enumerate(state):\n                state[up] += 1\n                if up + 1 == len(state) or state[up] < state[up + 1]:\n                    break\n                state[up] = up\n            if state[-1] == len(self.attr_ordering):\n                if len(state) < min(self.max_attrs, n_attrs):\n                    state = list(range(len(state) + 1))\n                else:\n                    break\n\n    def compute_score(self, state):\n        \"\"\"\n        Compute score using chi-square test of independence.\n\n        If mosaic colors by class distribution, chi-square is computed by\n        comparing the expected (prior) and observed class distribution in\n        each cell. Otherwise, compute the independence of the shown attributes.\n        \"\"\"\n        master = self.master\n        data = master.discrete_data\n        domain = data.domain\n        attrlist = [self.attr_ordering[i] for i in state]\n        cond_dist = get_conditional_distribution(data, attrlist)[0]\n        n = cond_dist[\"\"]\n        ss = 0\n        if self._compute_class_dists():\n            class_values = domain.class_var.values\n        else:\n            class_values = None\n            attr_indices = [domain.index(attr) for attr in attrlist]\n        for indices in product(*(range(len(a.values)) for a in attrlist)):\n            attr_vals = \"-\".join(attr.values[ind]\n                                 for attr, ind in zip(attrlist, indices))\n            total = cond_dist[attr_vals]\n            if class_values:  # showing class distributions\n                for i, class_val in enumerate(class_values):\n                    expected = total * self.marginal[i]\n                    if expected > 1e-6:\n                        observed = cond_dist[attr_vals + '-' + class_val]\n                        ss += (expected - observed) ** 2 / expected\n            else:\n                observed = cond_dist[attr_vals]\n                expected = n * reduce(\n                    mul,\n                    (self.marginal[attr_idx][ind]\n                     for attr_idx, ind in zip(attr_indices, indices)))\n                if expected > 1e-6:\n                    ss += (expected - observed) ** 2 / expected\n        if class_values:\n            dof = (len(class_values) - 1) * \\\n                  reduce(mul, (len(attr.values) for attr in attrlist))\n        else:\n            dof = reduce(mul, (len(attr.values) - 1 for attr in attrlist))\n        return distributions.chi2.sf(ss, dof)\n\n    def bar_length(self, score):\n        return 1 if score == 0 else -log(score, 10) / 50\n\n    def on_selection_changed(self, selected, deselected):\n        if not selected.isEmpty():\n            attrs = selected.indexes()[0].data(self._AttrRole)\n            self.selectionChanged.emit(attrs + (None, ) * (4 - len(attrs)))\n\n    def on_manual_change(self, attrs):\n        model = self.rank_model\n        self.rank_table.selectionModel().clear()\n        for row in range(model.rowCount()):\n            row_attrs = model.data(model.index(row, 0), self._AttrRole)\n            if row_attrs == tuple(attrs):\n                self.rank_table.selectRow(row)\n                return\n\n    def row_for_state(self, score, state):\n        \"\"\"The row consists of attributes sorted by name; class is at the\n        beginning, if present, so it's on the x-axis and not lost somewhere.\"\"\"\n        class_var = self.master.color_data.domain.class_var\n        attrs = tuple(\n            sorted((self.attr_ordering[x] for x in state),\n                   key=lambda attr: (1 - (attr is class_var), attr.name)))\n        item = QStandardItem(\", \".join(a.name for a in attrs))\n        item.setData(attrs, self._AttrRole)\n        return [item]\n\n\nclass OWMosaicDisplay(OWWidget):\n    name = \"Mosaic Display\"\n    description = \"Display data in a mosaic plot.\"\n    icon = \"icons/MosaicDisplay.svg\"\n    priority = 220\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Table, default=True)\n        data_subset = Input(\"Data Subset\", Table)\n\n    class Outputs:\n        selected_data = Output(\"Selected Data\", Table, default=True)\n        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)\n\n    settingsHandler = DomainContextHandler()\n    vizrank = SettingProvider(MosaicVizRank)\n    settings_version = 2\n    use_boxes = Setting(True)\n    variable1 = ContextSetting(None)\n    variable2 = ContextSetting(None)\n    variable3 = ContextSetting(None)\n    variable4 = ContextSetting(None)\n    variable_color = ContextSetting(None)\n    selection = ContextSetting(set())\n\n    BAR_WIDTH = 5\n    SPACING = 4\n    ATTR_NAME_OFFSET = 20\n    ATTR_VAL_OFFSET = 3\n    BLUE_COLORS = [QColor(255, 255, 255), QColor(210, 210, 255),\n                   QColor(110, 110, 255), QColor(0, 0, 255)]\n    RED_COLORS = [QColor(255, 255, 255), QColor(255, 200, 200),\n                  QColor(255, 100, 100), QColor(255, 0, 0)]\n    graph_name = \"canvas\"\n\n    attrs_changed_manually = Signal(list)\n\n    class Warning(OWWidget.Warning):\n        incompatible_subset = Msg(\"Data subset is incompatible with Data\")\n        no_valid_data = Msg(\"No valid data\")\n        no_cont_selection_sql = \\\n            Msg(\"Selection of numeric features on SQL is not supported\")\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self.discrete_data = None\n        self.subset_data = None\n        self.subset_indices = None\n\n        self.color_data = None\n\n        self.areas = []\n\n        self.canvas = QGraphicsScene()\n        self.canvas_view = ViewWithPress(\n            self.canvas, handler=self.clear_selection)\n        self.mainArea.layout().addWidget(self.canvas_view)\n        self.canvas_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        self.canvas_view.setRenderHint(QPainter.Antialiasing)\n\n        box = gui.vBox(self.controlArea, box=True)\n        self.model_1 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE)\n        self.model_234 = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(None)\")\n        self.attr_combos = [\n            gui.comboBox(\n                box, self, value=\"variable{}\".format(i),\n                orientation=Qt.Horizontal, contentsLength=12,\n                callback=self.attr_changed,\n                model=self.model_1 if i == 1 else self.model_234)\n            for i in range(1, 5)]\n        self.vizrank, self.vizrank_button = MosaicVizRank.add_vizrank(\n            box, self, \"Find Informative Mosaics\", self.set_attr)\n\n        box2 = gui.vBox(self.controlArea, box=\"Interior Coloring\")\n        self.color_model = DomainModel(\n            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,\n            placeholder=\"(Pearson residuals)\")\n        self.cb_attr_color = gui.comboBox(\n            box2, self, value=\"variable_color\",\n            orientation=Qt.Horizontal, contentsLength=12, labelWidth=50,\n            callback=self.set_color_data, model=self.color_model)\n        self.bar_button = gui.checkBox(\n            box2, self, 'use_boxes', label='Compare with total',\n            callback=self.update_graph)\n        gui.rubber(self.controlArea)\n\n    def sizeHint(self):\n        return QSize(720, 530)\n\n    def _get_discrete_data(self, data):\n        \"\"\"\n        Discretize continuous attributes.\n        Return None when there is no data, no rows, or no primitive attributes.\n        \"\"\"\n        if (data is None or\n                not len(data) or\n                not any(attr.is_discrete or attr.is_continuous\n                        for attr in chain(data.domain.variables,\n                                          data.domain.metas))):\n            return None\n        elif any(attr.is_continuous for attr in data.domain.variables):\n            return Discretize(\n                method=EqualFreq(n=4), remove_const=False, discretize_classes=True,\n                discretize_metas=True)(data)\n        else:\n            return data\n\n    def init_combos(self, data):\n        def set_combos(value):\n            self.model_1.set_domain(value)\n            self.model_234.set_domain(value)\n            self.color_model.set_domain(value)\n\n        if data is None:\n            set_combos(None)\n            self.variable1 = self.variable2 = self.variable3 \\\n                = self.variable4 = self.variable_color = None\n            return\n        set_combos(self.data.domain)\n\n        if len(self.model_1) > 0:\n            self.variable1 = self.model_1[0]\n            self.variable2 = self.model_1[min(1, len(self.model_1) - 1)]\n        self.variable3 = self.variable4 = None\n        self.variable_color = self.data.domain.class_var  # None is OK, too\n\n    def get_disc_attr_list(self):\n        return [self.discrete_data.domain[var.name]\n                for var in (self.variable1, self.variable2,\n                            self.variable3, self.variable4)\n                if var]\n\n    def set_attr(self, *attrs):\n        self.variable1, self.variable2, self.variable3, self.variable4 = [\n            attr and self.data.domain[attr.name] for attr in attrs]\n        self.reset_graph()\n\n    def attr_changed(self):\n        self.attrs_changed_manually.emit(self.get_disc_attr_list())\n        self.reset_graph()\n\n    def resizeEvent(self, e):\n        OWWidget.resizeEvent(self, e)\n        self.update_graph()\n\n    def showEvent(self, ev):\n        OWWidget.showEvent(self, ev)\n        self.update_graph()\n\n    @Inputs.data\n    def set_data(self, data):\n        if isinstance(data, SqlTable) and data.approx_len() > LARGE_TABLE:\n            data = data.sample_time(DEFAULT_SAMPLE_TIME)\n\n        self.closeContext()\n        self.data = data\n\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(\n            self.data is not None and len(self.data) > 1\n            and len(self.data.domain.attributes) >= 1)\n\n        if self.data is None:\n            self.discrete_data = None\n            self.init_combos(None)\n            return\n\n        self.init_combos(self.data)\n        self.openContext(self.data)\n\n    @Inputs.data_subset\n    def set_subset_data(self, data):\n        self.subset_data = data\n\n    # this is called by widget after setData and setSubsetData are called.\n    # this way the graph is updated only once\n    def handleNewSignals(self):\n        self.Warning.incompatible_subset.clear()\n        self.subset_indices = None\n        if self.data is not None and self.subset_data:\n            transformed = self.subset_data.transform(self.data.domain)\n            if np.all(np.isnan(transformed.X)) \\\n                    and np.all(np.isnan(transformed.Y)):\n                self.Warning.incompatible_subset()\n            else:\n                indices = {e.id for e in transformed}\n                self.subset_indices = [ex.id in indices for ex in self.data]\n        self.set_color_data()\n        self.reset_graph()\n\n    def clear_selection(self):\n        self.selection = set()\n        self.update_selection_rects()\n        self.send_selection()\n\n    def coloring_changed(self):\n        self.vizrank.coloring_changed()\n        self.update_graph()\n\n    def reset_graph(self):\n        self.clear_selection()\n        self.update_graph()\n\n    def set_color_data(self):\n        if self.data is None:\n            return\n        self.bar_button.setEnabled(self.variable_color is not None)\n        attrs = [v for v in self.model_1 if v and v is not self.variable_color]\n        domain = Domain(attrs, self.variable_color, None)\n        self.color_data = self.data.from_table(domain, self.data)\n        self.discrete_data = self._get_discrete_data(self.color_data)\n        self.vizrank.stop_and_reset()\n        self.vizrank_button.setEnabled(True)\n        self.coloring_changed()\n\n    def update_selection_rects(self):\n        pens = (QPen(), QPen(Qt.black, 3, Qt.DotLine))\n        for i, (_, _, area) in enumerate(self.areas):\n            area.setPen(pens[i in self.selection])\n\n    def select_area(self, index, ev):\n        if ev.button() != Qt.LeftButton:\n            return\n        if ev.modifiers() & Qt.ControlModifier:\n            self.selection ^= {index}\n        else:\n            self.selection = {index}\n        self.update_selection_rects()\n        self.send_selection()\n\n    def send_selection(self):\n        if not self.selection or self.data is None:\n            self.Outputs.selected_data.send(None)\n            self.Outputs.annotated_data.send(\n                create_annotated_table(self.data, []))\n            return\n        filters = []\n        self.Warning.no_cont_selection_sql.clear()\n        if self.discrete_data is not self.data:\n            if isinstance(self.data, SqlTable):\n                self.Warning.no_cont_selection_sql()\n        for i in self.selection:\n            cols, vals, _ = self.areas[i]\n            filters.append(\n                filter.Values(\n                    filter.FilterDiscrete(col, [val])\n                    for col, val in zip(cols, vals)))\n        if len(filters) > 1:\n            filters = filter.Values(filters, conjunction=False)\n        else:\n            filters = filters[0]\n        selection = filters(self.discrete_data)\n        idset = set(selection.ids)\n        sel_idx = [i for i, id in enumerate(self.data.ids) if id in idset]\n        if self.discrete_data is not self.data:\n            selection = self.data[sel_idx]\n        self.Outputs.selected_data.send(selection)\n        self.Outputs.annotated_data.send(\n            create_annotated_table(self.data, sel_idx))\n\n    def send_report(self):\n        self.report_plot(self.canvas)\n\n    def update_graph(self):\n        spacing = self.SPACING\n        bar_width = self.BAR_WIDTH\n\n        def get_counts(attr_vals, values):\n            \"\"\"Calculate rectangles' widths; if all are 0, they are set to 1.\"\"\"\n            if not attr_vals:\n                counts = [conditionaldict[val] for val in values]\n            else:\n                counts = [conditionaldict[attr_vals + \"-\" + val]\n                          for val in values]\n            total = sum(counts)\n            if total == 0:\n                counts = [1] * len(values)\n                total = sum(counts)\n            return total, counts\n\n        def draw_data(attr_list, x0_x1, y0_y1, side, condition,\n                      total_attrs, used_attrs, used_vals, attr_vals=\"\"):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if conditionaldict[attr_vals] == 0:\n                add_rect(x0, x1, y0, y1, \"\",\n                         used_attrs, used_vals, attr_vals=attr_vals)\n                # store coordinates for later drawing of labels\n                draw_text(side, attr_list[0], (x0, x1), (y0, y1), total_attrs,\n                          used_attrs, used_vals, attr_vals)\n                return\n\n            attr = attr_list[0]\n            # how much smaller rectangles do we draw\n            edge = len(attr_list) * spacing\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]  # reverse names if necessary\n\n            if side % 2 == 0:  # we are drawing on the x axis\n                # remove the space needed for separating different attr. values\n                whole = max(0, (x1 - x0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (x1 - x0) / float(len(values) - 1)\n            else:  # we are drawing on the y axis\n                whole = max(0, (y1 - y0) - edge * (len(values) - 1))\n                if whole == 0:\n                    edge = (y1 - y0) / float(len(values) - 1)\n\n            total, counts = get_counts(attr_vals, values)\n\n            # when visualizing the third attribute and the first attribute has\n            # the last value, reverse the order in which the boxes are drawn;\n            # otherwise, if the last cell, nearest to the labels of the fourth\n            # attribute, is empty, we wouldn't be able to position the labels\n            valrange = list(range(len(values)))\n            if len(attr_list + used_attrs) == 4 and len(used_attrs) == 2:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] == attr1values[-1]:\n                    valrange = valrange[::-1]\n\n            for i in valrange:\n                start = i * edge + whole * float(sum(counts[:i]) / total)\n                end = i * edge + whole * float(sum(counts[:i + 1]) / total)\n                val = values[i]\n                htmlval = to_html(val)\n                newattrvals = attr_vals + \"-\" + val if attr_vals else val\n\n                tooltip = \"{}&nbsp;&nbsp;&nbsp;&nbsp;{}: <b>{}</b><br/>\".format(\n                    condition, attr.name, htmlval)\n                attrs = used_attrs + [attr]\n                vals = used_vals + [val]\n                args = attrs, vals, newattrvals\n                if side % 2 == 0:  # if we are moving horizontally\n                    if len(attr_list) == 1:\n                        add_rect(x0 + start, x0 + end, y0, y1, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0 + start, x0 + end), (y0, y1),\n                            side + 1, tooltip, total_attrs, *args)\n                else:\n                    if len(attr_list) == 1:\n                        add_rect(x0, x1, y0 + start, y0 + end, tooltip, *args)\n                    else:\n                        draw_data(\n                            attr_list[1:], (x0, x1), (y0 + start, y0 + end),\n                            side + 1, tooltip, total_attrs, *args)\n            draw_text(side, attr_list[0], (x0, x1), (y0, y1),\n                      total_attrs, used_attrs, used_vals, attr_vals)\n\n        def draw_text(side, attr, x0_x1, y0_y1,\n                      total_attrs, used_attrs, used_vals, attr_vals):\n            x0, x1 = x0_x1\n            y0, y1 = y0_y1\n            if side in drawn_sides:\n                return\n\n            # the text on the right will be drawn when we are processing\n            # visualization of the last value of the first attribute\n            if side == 3:\n                attr1values = get_variable_values_sorted(used_attrs[0])\n                if used_vals[0] != attr1values[-1]:\n                    return\n\n            if not conditionaldict[attr_vals]:\n                if side not in draw_positions:\n                    draw_positions[side] = (x0, x1, y0, y1)\n                return\n            else:\n                if side in draw_positions:\n                    # restore the positions of attribute values and name\n                    (x0, x1, y0, y1) = draw_positions[side]\n\n            drawn_sides.add(side)\n\n            values = get_variable_values_sorted(attr)\n            if side % 2:\n                values = values[::-1]\n\n            spaces = spacing * (total_attrs - side) * (len(values) - 1)\n            width = x1 - x0 - spaces * (side % 2 == 0)\n            height = y1 - y0 - spaces * (side % 2 == 1)\n\n            # calculate position of first attribute\n            currpos = 0\n            total, counts = get_counts(attr_vals, values)\n            aligns = [Qt.AlignTop | Qt.AlignHCenter,\n                      Qt.AlignRight | Qt.AlignVCenter,\n                      Qt.AlignBottom | Qt.AlignHCenter,\n                      Qt.AlignLeft | Qt.AlignVCenter]\n            align = aligns[side]\n            for i, val in enumerate(values):\n                if distributiondict[val] != 0:\n                    perc = counts[i] / float(total)\n                    rwidth = width * perc\n                    xs = [x0 + currpos + rwidth / 2,\n                          x0 - self.ATTR_VAL_OFFSET,\n                          x0 + currpos + rwidth / 2,\n                          x1 + self.ATTR_VAL_OFFSET]\n                    ys = [y1 + self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc,\n                          y0 - self.ATTR_VAL_OFFSET,\n                          y0 + currpos + height * 0.5 * perc]\n\n                    CanvasText(\n                        self.canvas, val, xs[side], ys[side], align,\n                        max_width=rwidth if side == 0 else None)\n                    space = height if side % 2 else width\n                    currpos += perc * space + spacing * (total_attrs - side)\n\n            xs = [x0 + (x1 - x0) / 2,\n                  x0 - max_ylabel_w1 - self.ATTR_VAL_OFFSET,\n                  x0 + (x1 - x0) / 2,\n                  x1 + max_ylabel_w2 + self.ATTR_VAL_OFFSET]\n            ys = [y1 + self.ATTR_VAL_OFFSET + self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2,\n                  y0 - self.ATTR_VAL_OFFSET - self.ATTR_NAME_OFFSET,\n                  y0 + (y1 - y0) / 2]\n            CanvasText(\n                self.canvas, attr.name, xs[side], ys[side], align, bold=True,\n                vertical=side % 2)\n\n        def add_rect(x0, x1, y0, y1, condition,\n                     used_attrs, used_vals, attr_vals=\"\"):\n            area_index = len(self.areas)\n            x1 += (x0 == x1)\n            y1 += (y0 == y1)\n            # rectangles of width and height 1 are not shown - increase\n            y1 += (x1 - x0 + y1 - y0 == 2)\n            colors = class_var and [QColor(*col) for col in class_var.colors]\n\n            def select_area(_, ev):\n                self.select_area(area_index, ev)\n\n            def rect(x, y, w, h, z, pen_color=None, brush_color=None, **args):\n                if pen_color is None:\n                    return CanvasRectangle(\n                        self.canvas, x, y, w, h, z=z, onclick=select_area,\n                        **args)\n                if brush_color is None:\n                    brush_color = pen_color\n                return CanvasRectangle(\n                    self.canvas, x, y, w, h, pen_color, brush_color, z=z,\n                    onclick=select_area, **args)\n\n            def line(x1, y1, x2, y2):\n                r = QGraphicsLineItem(x1, y1, x2, y2, None)\n                self.canvas.addItem(r)\n                r.setPen(QPen(Qt.white, 2))\n                r.setZValue(30)\n\n            outer_rect = rect(x0, y0, x1 - x0, y1 - y0, 30)\n            self.areas.append((used_attrs, used_vals, outer_rect))\n            if not conditionaldict[attr_vals]:\n                return\n\n            if self.variable_color is None:\n                s = sum(apriori_dists[0])\n                expected = s * reduce(\n                    mul,\n                    (apriori_dists[i][used_vals[i]] / float(s)\n                     for i in range(len(used_vals))))\n                actual = conditionaldict[attr_vals]\n                pearson = float((actual - expected) / sqrt(expected))\n                if pearson == 0:\n                    ind = 0\n                else:\n                    ind = max(0, min(int(log(abs(pearson), 2)), 3))\n                color = [self.RED_COLORS, self.BLUE_COLORS][pearson > 0][ind]\n                rect(x0, y0, x1 - x0, y1 - y0, -20, color)\n                outer_rect.setToolTip(\n                    condition + \"<hr/>\" +\n                    \"Expected instances: %.1f<br>\"\n                    \"Actual instances: %d<br>\"\n                    \"Standardized (Pearson) residual: %.1f\" %\n                    (expected, conditionaldict[attr_vals], pearson))\n            else:\n                cls_values = get_variable_values_sorted(class_var)\n                prior = get_distribution(data, class_var.name)\n                total = 0\n                for i, value in enumerate(cls_values):\n                    val = conditionaldict[attr_vals + \"-\" + value]\n                    if val == 0:\n                        continue\n                    if i == len(cls_values) - 1:\n                        v = y1 - y0 - total\n                    else:\n                        v = ((y1 - y0) * val) / conditionaldict[attr_vals]\n                    rect(x0, y0 + total, x1 - x0, v, -20, colors[i])\n                    total += v\n\n                if self.use_boxes and \\\n                        abs(x1 - x0) > bar_width and abs(y1 - y0) > bar_width:\n                    total = 0\n                    line(x0 + bar_width, y0, x0 + bar_width, y1)\n                    n = sum(prior)\n                    for i, (val, color) in enumerate(zip(prior, colors)):\n                        if i == len(prior) - 1:\n                            h = y1 - y0 - total\n                        else:\n                            h = (y1 - y0) * val / n\n                        rect(x0, y0 + total, bar_width, h, 20, color)\n                        total += h\n\n                if conditionalsubsetdict:\n                    if conditionalsubsetdict[attr_vals]:\n                        if self.subset_indices is not None:\n                            line(x1 - bar_width, y0, x1 - bar_width, y1)\n                            total = 0\n                            n = conditionalsubsetdict[attr_vals]\n                            if n:\n                                for i, (cls, color) in \\\n                                        enumerate(zip(cls_values, colors)):\n                                    val = conditionalsubsetdict[\n                                        attr_vals + \"-\" + cls]\n                                    if val == 0:\n                                        continue\n                                    if i == len(prior) - 1:\n                                        v = y1 - y0 - total\n                                    else:\n                                        v = ((y1 - y0) * val) / n\n                                    rect(x1 - bar_width, y0 + total,\n                                         bar_width, v, 15, color)\n                                    total += v\n\n                actual = [conditionaldict[attr_vals + \"-\" + cls_values[i]]\n                          for i in range(len(prior))]\n                n_actual = sum(actual)\n                if n_actual > 0:\n                    apriori = [prior[key] for key in cls_values]\n                    n_apriori = sum(apriori)\n                    text = \"<br/>\".join(\n                        \"<b>%s</b>: %d / %.1f%% (Expected %.1f / %.1f%%)\" %\n                        (cls, act, 100.0 * act / n_actual,\n                         apr / n_apriori * n_actual, 100.0 * apr / n_apriori)\n                        for cls, act, apr in zip(cls_values, actual, apriori))\n                else:\n                    text = \"\"\n                outer_rect.setToolTip(\n                    \"{}<hr>Instances: {}<br><br>{}\".format(\n                        condition, n_actual, text[:-4]))\n\n        def create_legend():\n            if self.variable_color is None:\n                names = [\"<-8\", \"-8:-4\", \"-4:-2\", \"-2:2\", \"2:4\", \"4:8\", \">8\",\n                         \"Residuals:\"]\n                colors = self.RED_COLORS[::-1] + self.BLUE_COLORS[1:]\n                edges = repeat(Qt.black)\n            else:\n                names = get_variable_values_sorted(class_var)\n                edges = colors = [QColor(*col) for col in class_var.colors]\n\n            items = []\n            size = 8\n            for name, color, edgecolor in zip(names, colors, edges):\n                item = QGraphicsItemGroup()\n                item.addToGroup(\n                    CanvasRectangle(None, -size / 2, -size / 2, size, size,\n                                    edgecolor, color))\n                item.addToGroup(\n                    CanvasText(None, name, size, 0, Qt.AlignVCenter))\n                items.append(item)\n            return wrap_legend_items(\n                items, hspacing=20, vspacing=16 + size,\n                max_width=self.canvas_view.width() - xoff)\n\n        self.canvas.clear()\n        self.areas = []\n\n        data = self.discrete_data\n        if data is None:\n            return\n        attr_list = self.get_disc_attr_list()\n        class_var = data.domain.class_var\n        if class_var:\n            sql = isinstance(data, SqlTable)\n            name = not sql and data.name\n            # save class_var because it is removed in the next line\n            data = data[:, attr_list + [class_var]]\n            data.domain.class_var = class_var\n            if not sql:\n                data.name = name\n        else:\n            data = data[:, attr_list]\n        # TODO: check this\n        # data = Preprocessor_dropMissing(data)\n        if len(data) == 0:\n            self.Warning.no_valid_data()\n            return\n        else:\n            self.Warning.no_valid_data.clear()\n\n        attrs = [attr for attr in attr_list if not attr.values]\n        if attrs:\n            CanvasText(self.canvas,\n                       \"Feature {} has no values\".format(attrs[0]),\n                       (self.canvas_view.width() - 120) / 2,\n                       self.canvas_view.height() / 2)\n            return\n        if self.variable_color is None:\n            apriori_dists = [get_distribution(data, attr) for attr in attr_list]\n        else:\n            apriori_dists = []\n\n        def get_max_label_width(attr):\n            values = get_variable_values_sorted(attr)\n            maxw = 0\n            for val in values:\n                t = CanvasText(self.canvas, val, 0, 0, bold=0, show=False)\n                maxw = max(int(t.boundingRect().width()), maxw)\n            return maxw\n\n        xoff = 20\n\n        # get the maximum width of rectangle\n        width = 20\n        max_ylabel_w1 = max_ylabel_w2 = 0\n        if len(attr_list) > 1:\n            text = CanvasText(self.canvas, attr_list[1].name, bold=1, show=0)\n            max_ylabel_w1 = min(get_max_label_width(attr_list[1]), 150)\n            width = 5 + text.boundingRect().height() + \\\n                self.ATTR_VAL_OFFSET + max_ylabel_w1\n            xoff = width\n            if len(attr_list) == 4:\n                text = CanvasText(self.canvas, attr_list[3].name, bold=1, show=0)\n                max_ylabel_w2 = min(get_max_label_width(attr_list[3]), 150)\n                width += text.boundingRect().height() + \\\n                    self.ATTR_VAL_OFFSET + max_ylabel_w2 - 10\n\n        legend = create_legend()\n\n        # get the maximum height of rectangle\n        yoff = 45\n        legendoff = yoff + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35\n        square_size = min(self.canvas_view.width() - width - 20,\n                          self.canvas_view.height() - legendoff\n                          - legend.boundingRect().height())\n\n        if square_size < 0:\n            return  # canvas is too small to draw rectangles\n        self.canvas_view.setSceneRect(\n            0, 0, self.canvas_view.width(), self.canvas_view.height())\n\n        drawn_sides = set()\n        draw_positions = {}\n\n        conditionaldict, distributiondict = \\\n            get_conditional_distribution(data, attr_list)\n        conditionalsubsetdict = None\n        if self.subset_indices:\n            conditionalsubsetdict, _ = get_conditional_distribution(\n                self.discrete_data[self.subset_indices], attr_list)\n\n        # draw rectangles\n        draw_data(\n            attr_list, (xoff, xoff + square_size), (yoff, yoff + square_size),\n            0, \"\", len(attr_list), [], [])\n\n        self.canvas.addItem(legend)\n        legend.setPos(\n            xoff - legend.boundingRect().x()\n            + max(0, (square_size - legend.boundingRect().width()) / 2),\n            legendoff + square_size)\n        self.update_selection_rects()\n\n    @classmethod\n    def migrate_context(cls, context, version):\n        if version < 2:\n            settings.migrate_str_to_variable(context, none_placeholder=\"(None)\")\n\n\ndef get_conditional_distribution(data, attrs):\n    cond_dist = defaultdict(int)\n    dist = defaultdict(int)\n    cond_dist[\"\"] = dist[\"\"] = len(data)\n    all_attrs = attrs[:]\n    if data.domain.class_var is not None:\n        all_attrs.append(data.domain.class_var)\n\n    for i in range(1, len(all_attrs) + 1):\n        attr = all_attrs[:i]\n        if isinstance(data, SqlTable):\n            # make all possible pairs of attributes + class_var\n            attr = [a.to_sql() for a in attr]\n            fields = attr + [\"COUNT(*)\"]\n            query = data._sql_query(fields, group_by=attr)\n            with data._execute_sql_query(query) as cur:\n                res = cur.fetchall()\n            for r in res:\n                str_values = [a.repr_val(a.to_val(x))\n                              for a, x in zip(all_attrs, r[:-1])]\n                str_values = [x if x != '?' else 'None' for x in str_values]\n                cond_dist['-'.join(str_values)] = r[-1]\n                dist[str_values[-1]] += r[-1]\n        else:\n            for indices in product(*(range(len(a.values)) for a in attr)):\n                vals = []\n                conditions = []\n                for k, ind in enumerate(indices):\n                    vals.append(attr[k].values[ind])\n                    fd = filter.FilterDiscrete(\n                        column=attr[k], values=[attr[k].values[ind]])\n                    conditions.append(fd)\n                filt = filter.Values(conditions)\n                filtdata = filt(data)\n                cond_dist['-'.join(vals)] = len(filtdata)\n                dist[vals[-1]] += len(filtdata)\n    return cond_dist, dist\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    data = Table(\"zoo\")\n    WidgetPreview(OWMosaicDisplay).run(data, set_subset_data=data[::10])\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:** Selection Not Preserved After Resetting Graph in OWMosaicDisplay

**Description**
When a selection is made in the Mosaic widget and the data is subsequently cleared and reloaded, the selection does not persist as expected. This results in the selected area being lost, which can disrupt the user's workflow and data analysis.

**Steps to Reproduce**
```python
widget = OWMosaicDisplay()
data = Table("iris.tab")
widget.set_data(data)
widget.select_area(1, QMouseEvent(QEvent.MouseButtonPress, QPoint(), Qt.LeftButton, Qt.LeftButton, Qt.KeyboardModifiers()))
widget.clear_data()
widget.set_data(data)
```

**Expected Behavior**
After reloading the data, the previously selected area should remain selected, and the corresponding selection data should be available for further operations.

**Actual Behavior**
The selection is lost upon reloading the data, resulting in no area being selected despite prior user interaction.
[/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/owmosaic.py
from collections import defaultdict
from functools import reduce
from itertools import product, chain, repeat
from math import sqrt, log
from operator import mul, attrgetter

import numpy as np
from scipy.stats import distributions
from scipy.special import comb
from AnyQt.QtCore import Qt, QSize, pyqtSignal as Signal
from AnyQt.QtGui import QColor, QPainter, QPen, QStandardItem
from AnyQt.QtWidgets import (
    QGraphicsScene, QGraphicsLineItem, QGraphicsItemGroup)

from Orange.data import Table, filter, Variable, Domain
from Orange.data.sql.table import SqlTable, LARGE_TABLE, DEFAULT_SAMPLE_TIME
from Orange.preprocess import Discretize
from Orange.preprocess.discretize import EqualFreq
from Orange.preprocess.score import ReliefF
from Orange.statistics.distribution import get_distribution, get_distributions
from Orange.widgets import gui, settings
from Orange.widgets.gui import OWComponent
from Orange.widgets.settings import (
    Setting, DomainContextHandler, ContextSetting, SettingProvider)
from Orange.widgets.utils import to_html, get_variable_values_sorted
from Orange.widgets.utils.annotated_data import (create_annotated_table,
                                                 ANNOTATED_DATA_SIGNAL_NAME)
from Orange.widgets.utils.itemmodels import DomainModel
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.visualize.utils import (
    CanvasText, CanvasRectangle, ViewWithPress, VizRankDialog)
from Orange.widgets.visualize.utils.plotutils import wrap_legend_items
from Orange.widgets.widget import OWWidget, Msg, Input, Output


class MosaicVizRank(VizRankDialog, OWComponent):
    """VizRank dialog for Mosaic"""
    captionTitle = "Mosaic Ranking"
    max_attrs = Setting(3)

    pairSelected = Signal(Variable, Variable, Variable, Variable)
    _AttrRole = next(gui.OrangeUserRole)

    def __init__(self, master):
        """Add the spin box for maximal number of attributes"""
        VizRankDialog.__init__(self, master)
        OWComponent.__init__(self, master)

        box = gui.hBox(self)
        self.max_attr_spin = gui.spin(
            box, self, "max_attrs", 2, 4,
            label="Limit the number of attributes to: ",
            controlWidth=50, alignment=Qt.AlignRight,
            callback=self.max_attr_changed)
        gui.rubber(box)
        self.layout().addWidget(self.button)
        self.attr_ordering = None
        self.marginal = {}
        self.last_run_max_attr = None

        self.master.attrs_changed_manually.connect(self.on_manual_change)

    def sizeHint(self):
        return QSize(400, 512)

    def initialize(self):
        """Clear the ordering to trigger recomputation when needed"""
        super().initialize()
        self.attr_ordering = None

    def initialize_keep_ordering(self):
        """Initialize triggered by change of coloring"""
        super().initialize()

    def before_running(self):
        """
        Disable the spin for maximal number of attributes before running and
        enable afterwards. Also, if the number of attributes is different than
        in the last run, reset the saved state (if it was paused).
        """
        if self.max_attrs != self.last_run_max_attr:
            self.saved_state = None
            self.saved_progress = 0
        if self.saved_state is None:
            self.scores = []
            self.rank_model.clear()
        self.compute_attr_order()
        self.last_run_max_attr = self.max_attrs
        self.max_attr_spin.setDisabled(True)

    def stopped(self):
        self.max_attr_spin.setDisabled(False)

    def max_attr_changed(self):
        """
        Change the button label when the maximal number of attributes changes.

        The method does not reset anything so the user can still see the
        results until actually restarting the search.
        """
        if self.max_attrs != self.last_run_max_attr or self.saved_state is None:
            self.button.setText("Start")
        else:
            self.button.setText("Continue")
        self.button.setEnabled(self.check_preconditions())

    def coloring_changed(self):
        self.stop_and_reset(self.initialize_keep_ordering)

    def check_preconditions(self):
        """Require at least one variable to allow ranking."""
        self.Information.add_message("no_attributes", "No variables to rank.")
        self.Information.no_attributes.clear()
        data = self.master.discrete_data
        if not super().check_preconditions() or data is None:
            return False
        if not data.domain.attributes:
            self.Information.no_attributes()
            return False
        return True

    def compute_attr_order(self):
        """
        Order attributes by Relief if there is a target variable. In case of
        ties or without target, order by name.

        Add the class variable at the beginning when not coloring by class
        distribution.

        If `self.attrs` is not `None`, keep the ordering and just add or remove
        the class as needed.
        """
        data = self.master.discrete_data
        class_var = data.domain.class_var
        if not self.attr_ordering:
            if class_var is None:
                self.attr_ordering = sorted(data.domain, key=attrgetter("name"))
            else:
                weights = ReliefF(n_iterations=100, k_nearest=10)(data)
                attrs = sorted(zip(weights, data.domain.attributes),
                               key=lambda x: (-x[0], x[1].name))
                self.attr_ordering = [a for _, a in attrs]

    def _compute_class_dists(self):
        return self.master.variable_color is not None

    def state_count(self):
        """
        Return the number of combinations, starting with a single attribute
        if Mosaic is colored by class distributions, and two if by Pearson
        """
        n_attrs = len(self.master.discrete_data.domain.attributes)
        min_attrs = 1 if self._compute_class_dists() else 2
        max_attrs = min(n_attrs, self.max_attrs)
        return sum(comb(n_attrs, k, exact=True)
                   for k in range(min_attrs, max_attrs + 1))

    def iterate_states(self, state):
        """
        Iterate through all combinations of attributes as ordered by Relief,
        starting with a single attribute if Mosaic is colored by class
        distributions, and two if by Pearson.
        """
        # If we put initialization of `self.attrs` to `initialize`,
        # `score_heuristic` would be run on every call to master's `set_data`.
        master = self.master
        data = master.discrete_data
        if state is None:  # on the first call, compute order
            if self._compute_class_dists():
                self.marginal = get_distribution(data, data.domain.class_var)
                self.marginal.normalize()
                state = [0]
            else:
                self.marginal = get_distributions(data)
                for dist in self.marginal:
                    dist.normalize()
                state = [0, 1]
        n_attrs = len(data.domain.attributes)
        while True:
            yield state
            # Reset while running; just abort
            if self.attr_ordering is None:
                break
            for up, _ in enumerate(state):
                state[up] += 1
                if up + 1 == len(state) or state[up] < state[up + 1]:
                    break
                state[up] = up
            if state[-1] == len(self.attr_ordering):
                if len(state) < min(self.max_attrs, n_attrs):
                    state = list(range(len(state) + 1))
                else:
                    break

    def compute_score(self, state):
        """
        Compute score using chi-square test of independence.

        If mosaic colors by class distribution, chi-square is computed by
        comparing the expected (prior) and observed class distribution in
        each cell. Otherwise, compute the independence of the shown attributes.
        """
        master = self.master
        data = master.discrete_data
        domain = data.domain
        attrlist = [self.attr_ordering[i] for i in state]
        cond_dist = get_conditional_distribution(data, attrlist)[0]
        n = cond_dist[""]
        ss = 0
        if self._compute_class_dists():
            class_values = domain.class_var.values
        else:
            class_values = None
            attr_indices = [domain.index(attr) for attr in attrlist]
        for indices in product(*(range(len(a.values)) for a in attrlist)):
            attr_vals = "-".join(attr.values[ind]
                                 for attr, ind in zip(attrlist, indices))
            total = cond_dist[attr_vals]
            if class_values:  # showing class distributions
                for i, class_val in enumerate(class_values):
                    expected = total * self.marginal[i]
                    if expected > 1e-6:
                        observed = cond_dist[attr_vals + '-' + class_val]
                        ss += (expected - observed) ** 2 / expected
            else:
                observed = cond_dist[attr_vals]
                expected = n * reduce(
                    mul,
                    (self.marginal[attr_idx][ind]
                     for attr_idx, ind in zip(attr_indices, indices)))
                if expected > 1e-6:
                    ss += (expected - observed) ** 2 / expected
        if class_values:
            dof = (len(class_values) - 1) * \
                  reduce(mul, (len(attr.values) for attr in attrlist))
        else:
            dof = reduce(mul, (len(attr.values) - 1 for attr in attrlist))
        return distributions.chi2.sf(ss, dof)

    def bar_length(self, score):
        return 1 if score == 0 else -log(score, 10) / 50

    def on_selection_changed(self, selected, deselected):
        if not selected.isEmpty():
            attrs = selected.indexes()[0].data(self._AttrRole)
            self.selectionChanged.emit(attrs + (None, ) * (4 - len(attrs)))

    def on_manual_change(self, attrs):
        model = self.rank_model
        self.rank_table.selectionModel().clear()
        for row in range(model.rowCount()):
            row_attrs = model.data(model.index(row, 0), self._AttrRole)
            if row_attrs == tuple(attrs):
                self.rank_table.selectRow(row)
                return

    def row_for_state(self, score, state):
        """The row consists of attributes sorted by name; class is at the
        beginning, if present, so it's on the x-axis and not lost somewhere."""
        class_var = self.master.color_data.domain.class_var
        attrs = tuple(
            sorted((self.attr_ordering[x] for x in state),
                   key=lambda attr: (1 - (attr is class_var), attr.name)))
        item = QStandardItem(", ".join(a.name for a in attrs))
        item.setData(attrs, self._AttrRole)
        return [item]


class OWMosaicDisplay(OWWidget):
    name = "Mosaic Display"
    description = "Display data in a mosaic plot."
    icon = "icons/MosaicDisplay.svg"
    priority = 220
    keywords = []

    class Inputs:
        data = Input("Data", Table, default=True)
        data_subset = Input("Data Subset", Table)

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

    settingsHandler = DomainContextHandler()
    vizrank = SettingProvider(MosaicVizRank)
    settings_version = 2
    use_boxes = Setting(True)
    variable1 = ContextSetting(None)
    variable2 = ContextSetting(None)
    variable3 = ContextSetting(None)
    variable4 = ContextSetting(None)
    variable_color = ContextSetting(None)
    selection = ContextSetting(set())

    BAR_WIDTH = 5
    SPACING = 4
    ATTR_NAME_OFFSET = 20
    ATTR_VAL_OFFSET = 3
    BLUE_COLORS = [QColor(255, 255, 255), QColor(210, 210, 255),
                   QColor(110, 110, 255), QColor(0, 0, 255)]
    RED_COLORS = [QColor(255, 255, 255), QColor(255, 200, 200),
                  QColor(255, 100, 100), QColor(255, 0, 0)]
    graph_name = "canvas"

    attrs_changed_manually = Signal(list)

    class Warning(OWWidget.Warning):
        incompatible_subset = Msg("Data subset is incompatible with Data")
        no_valid_data = Msg("No valid data")
        no_cont_selection_sql = \
            Msg("Selection of numeric features on SQL is not supported")

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

        self.data = None
        self.discrete_data = None
        self.subset_data = None
        self.subset_indices = None

        self.color_data = None

        self.areas = []

        self.canvas = QGraphicsScene()
        self.canvas_view = ViewWithPress(
            self.canvas, handler=self.clear_selection)
        self.mainArea.layout().addWidget(self.canvas_view)
        self.canvas_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.canvas_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.canvas_view.setRenderHint(QPainter.Antialiasing)

        box = gui.vBox(self.controlArea, box=True)
        self.model_1 = DomainModel(
            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE)
        self.model_234 = DomainModel(
            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,
            placeholder="(None)")
        self.attr_combos = [
            gui.comboBox(
                box, self, value="variable{}".format(i),
                orientation=Qt.Horizontal, contentsLength=12,
                callback=self.attr_changed,
                model=self.model_1 if i == 1 else self.model_234)
            for i in range(1, 5)]
        self.vizrank, self.vizrank_button = MosaicVizRank.add_vizrank(
            box, self, "Find Informative Mosaics", self.set_attr)

        box2 = gui.vBox(self.controlArea, box="Interior Coloring")
        self.color_model = DomainModel(
            order=DomainModel.MIXED, valid_types=DomainModel.PRIMITIVE,
            placeholder="(Pearson residuals)")
        self.cb_attr_color = gui.comboBox(
            box2, self, value="variable_color",
            orientation=Qt.Horizontal, contentsLength=12, labelWidth=50,
            callback=self.set_color_data, model=self.color_model)
        self.bar_button = gui.checkBox(
            box2, self, 'use_boxes', label='Compare with total',
            callback=self.update_graph)
        gui.rubber(self.controlArea)

    def sizeHint(self):
        return QSize(720, 530)

    def _get_discrete_data(self, data):
        """
        Discretize continuous attributes.
        Return None when there is no data, no rows, or no primitive attributes.
        """
        if (data is None or
                not len(data) or
                not any(attr.is_discrete or attr.is_continuous
                        for attr in chain(data.domain.variables,
                                          data.domain.metas))):
            return None
        elif any(attr.is_continuous for attr in data.domain.variables):
            return Discretize(
                method=EqualFreq(n=4), remove_const=False, discretize_classes=True,
                discretize_metas=True)(data)
        else:
            return data

    def init_combos(self, data):
        def set_combos(value):
            self.model_1.set_domain(value)
            self.model_234.set_domain(value)
            self.color_model.set_domain(value)

        if data is None:
            set_combos(None)
            self.variable1 = self.variable2 = self.variable3 \
                = self.variable4 = self.variable_color = None
            return
        set_combos(self.data.domain)

        if len(self.model_1) > 0:
            self.variable1 = self.model_1[0]
            self.variable2 = self.model_1[min(1, len(self.model_1) - 1)]
        self.variable3 = self.variable4 = None
        self.variable_color = self.data.domain.class_var  # None is OK, too

    def get_disc_attr_list(self):
        return [self.discrete_data.domain[var.name]
                for var in (self.variable1, self.variable2,
                            self.variable3, self.variable4)
                if var]

    def set_attr(self, *attrs):
        self.variable1, self.variable2, self.variable3, self.variable4 = [
            attr and self.data.domain[attr.name] for attr in attrs]
        self.reset_graph()

    def attr_changed(self):
        self.attrs_changed_manually.emit(self.get_disc_attr_list())
        self.reset_graph()

    def resizeEvent(self, e):
        OWWidget.resizeEvent(self, e)
        self.update_graph()

    def showEvent(self, ev):
        OWWidget.showEvent(self, ev)
        self.update_graph()

    @Inputs.data
    def set_data(self, data):
        if isinstance(data, SqlTable) and data.approx_len() > LARGE_TABLE:
            data = data.sample_time(DEFAULT_SAMPLE_TIME)

        self.closeContext()
        self.data = data

        self.vizrank.stop_and_reset()
        self.vizrank_button.setEnabled(
            self.data is not None and len(self.data) > 1
            and len(self.data.domain.attributes) >= 1)

        if self.data is None:
            self.discrete_data = None
            self.init_combos(None)
            return

        self.init_combos(self.data)
        self.openContext(self.data)

    @Inputs.data_subset
    def set_subset_data(self, data):
        self.subset_data = data

    # this is called by widget after setData and setSubsetData are called.
    # this way the graph is updated only once
    def handleNewSignals(self):
        self.Warning.incompatible_subset.clear()
        self.subset_indices = None
        if self.data is not None and self.subset_data:
            transformed = self.subset_data.transform(self.data.domain)
            if np.all(np.isnan(transformed.X)) \
                    and np.all(np.isnan(transformed.Y)):
                self.Warning.incompatible_subset()
            else:
                indices = {e.id for e in transformed}
                self.subset_indices = [ex.id in indices for ex in self.data]
        self.set_color_data()
        self.reset_graph()

    def clear_selection(self):
        self.selection = set()
        self.update_selection_rects()
        self.send_selection()

    def coloring_changed(self):
        self.vizrank.coloring_changed()
        self.update_graph()

    def reset_graph(self):
        self.clear_selection()
        self.update_graph()

    def set_color_data(self):
        if self.data is None:
            return
        self.bar_button.setEnabled(self.variable_color is not None)
        attrs = [v for v in self.model_1 if v and v is not self.variable_color]
        domain = Domain(attrs, self.variable_color, None)
        self.color_data = self.data.from_table(domain, self.data)
        self.discrete_data = self._get_discrete_data(self.color_data)
        self.vizrank.stop_and_reset()
        self.vizrank_button.setEnabled(True)
        self.coloring_changed()

    def update_selection_rects(self):
        pens = (QPen(), QPen(Qt.black, 3, Qt.DotLine))
        for i, (_, _, area) in enumerate(self.areas):
            area.setPen(pens[i in self.selection])

    def select_area(self, index, ev):
        if ev.button() != Qt.LeftButton:
            return
        if ev.modifiers() & Qt.ControlModifier:
            self.selection ^= {index}
        else:
            self.selection = {index}
        self.update_selection_rects()
        self.send_selection()

    def send_selection(self):
        if not self.selection or self.data is None:
            self.Outputs.selected_data.send(None)
            self.Outputs.annotated_data.send(
                create_annotated_table(self.data, []))
            return
        filters = []
        self.Warning.no_cont_selection_sql.clear()
        if self.discrete_data is not self.data:
            if isinstance(self.data, SqlTable):
                self.Warning.no_cont_selection_sql()
        for i in self.selection:
            cols, vals, _ = self.areas[i]
            filters.append(
                filter.Values(
                    filter.FilterDiscrete(col, [val])
                    for col, val in zip(cols, vals)))
        if len(filters) > 1:
            filters = filter.Values(filters, conjunction=False)
        else:
            filters = filters[0]
        selection = filters(self.discrete_data)
        idset = set(selection.ids)
        sel_idx = [i for i, id in enumerate(self.data.ids) if id in idset]
        if self.discrete_data is not self.data:
            selection = self.data[sel_idx]
        self.Outputs.selected_data.send(selection)
        self.Outputs.annotated_data.send(
            create_annotated_table(self.data, sel_idx))

    def send_report(self):
        self.report_plot(self.canvas)

    def update_graph(self):
        spacing = self.SPACING
        bar_width = self.BAR_WIDTH

        def get_counts(attr_vals, values):
            """Calculate rectangles' widths; if all are 0, they are set to 1."""
            if not attr_vals:
                counts = [conditionaldict[val] for val in values]
            else:
                counts = [conditionaldict[attr_vals + "-" + val]
                          for val in values]
            total = sum(counts)
            if total == 0:
                counts = [1] * len(values)
                total = sum(counts)
            return total, counts

        def draw_data(attr_list, x0_x1, y0_y1, side, condition,
                      total_attrs, used_attrs, used_vals, attr_vals=""):
            x0, x1 = x0_x1
            y0, y1 = y0_y1
            if conditionaldict[attr_vals] == 0:
                add_rect(x0, x1, y0, y1, "",
                         used_attrs, used_vals, attr_vals=attr_vals)
                # store coordinates for later drawing of labels
                draw_text(side, attr_list[0], (x0, x1), (y0, y1), total_attrs,
                          used_attrs, used_vals, attr_vals)
                return

            attr = attr_list[0]
            # how much smaller rectangles do we draw
            edge = len(attr_list) * spacing
            values = get_variable_values_sorted(attr)
            if side % 2:
                values = values[::-1]  # reverse names if necessary

            if side % 2 == 0:  # we are drawing on the x axis
                # remove the space needed for separating different attr. values
                whole = max(0, (x1 - x0) - edge * (len(values) - 1))
                if whole == 0:
                    edge = (x1 - x0) / float(len(values) - 1)
            else:  # we are drawing on the y axis
                whole = max(0, (y1 - y0) - edge * (len(values) - 1))
                if whole == 0:
                    edge = (y1 - y0) / float(len(values) - 1)

            total, counts = get_counts(attr_vals, values)

            # when visualizing the third attribute and the first attribute has
            # the last value, reverse the order in which the boxes are drawn;
            # otherwise, if the last cell, nearest to the labels of the fourth
            # attribute, is empty, we wouldn't be able to position the labels
            valrange = list(range(len(values)))
            if len(attr_list + used_attrs) == 4 and len(used_attrs) == 2:
                attr1values = get_variable_values_sorted(used_attrs[0])
                if used_vals[0] == attr1values[-1]:
                    valrange = valrange[::-1]

            for i in valrange:
                start = i * edge + whole * float(sum(counts[:i]) / total)
                end = i * edge + whole * float(sum(counts[:i + 1]) / total)
                val = values[i]
                htmlval = to_html(val)
                newattrvals = attr_vals + "-" + val if attr_vals else val

                tooltip = "{}&nbsp;&nbsp;&nbsp;&nbsp;{}: <b>{}</b><br/>".format(
                    condition, attr.name, htmlval)
                attrs = used_attrs + [attr]
                vals = used_vals + [val]
                args = attrs, vals, newattrvals
                if side % 2 == 0:  # if we are moving horizontally
                    if len(attr_list) == 1:
                        add_rect(x0 + start, x0 + end, y0, y1, tooltip, *args)
                    else:
                        draw_data(
                            attr_list[1:], (x0 + start, x0 + end), (y0, y1),
                            side + 1, tooltip, total_attrs, *args)
                else:
                    if len(attr_list) == 1:
                        add_rect(x0, x1, y0 + start, y0 + end, tooltip, *args)
                    else:
                        draw_data(
                            attr_list[1:], (x0, x1), (y0 + start, y0 + end),
                            side + 1, tooltip, total_attrs, *args)
            draw_text(side, attr_list[0], (x0, x1), (y0, y1),
                      total_attrs, used_attrs, used_vals, attr_vals)

        def draw_text(side, attr, x0_x1, y0_y1,
                      total_attrs, used_attrs, used_vals, attr_vals):
            x0, x1 = x0_x1
            y0, y1 = y0_y1
            if side in drawn_sides:
                return

            # the text on the right will be drawn when we are processing
            # visualization of the last value of the first attribute
            if side == 3:
                attr1values = get_variable_values_sorted(used_attrs[0])
                if used_vals[0] != attr1values[-1]:
                    return

            if not conditionaldict[attr_vals]:
                if side not in draw_positions:
                    draw_positions[side] = (x0, x1, y0, y1)
                return
            else:
                if side in draw_positions:
                    # restore the positions of attribute values and name
                    (x0, x1, y0, y1) = draw_positions[side]

            drawn_sides.add(side)

            values = get_variable_values_sorted(attr)
            if side % 2:
                values = values[::-1]

            spaces = spacing * (total_attrs - side) * (len(values) - 1)
            width = x1 - x0 - spaces * (side % 2 == 0)
            height = y1 - y0 - spaces * (side % 2 == 1)

            # calculate position of first attribute
            currpos = 0
            total, counts = get_counts(attr_vals, values)
            aligns = [Qt.AlignTop | Qt.AlignHCenter,
                      Qt.AlignRight | Qt.AlignVCenter,
                      Qt.AlignBottom | Qt.AlignHCenter,
                      Qt.AlignLeft | Qt.AlignVCenter]
            align = aligns[side]
            for i, val in enumerate(values):
                if distributiondict[val] != 0:
                    perc = counts[i] / float(total)
                    rwidth = width * perc
                    xs = [x0 + currpos + rwidth / 2,
                          x0 - self.ATTR_VAL_OFFSET,
                          x0 + currpos + rwidth / 2,
                          x1 + self.ATTR_VAL_OFFSET]
                    ys = [y1 + self.ATTR_VAL_OFFSET,
                          y0 + currpos + height * 0.5 * perc,
                          y0 - self.ATTR_VAL_OFFSET,
                          y0 + currpos + height * 0.5 * perc]

                    CanvasText(
                        self.canvas, val, xs[side], ys[side], align,
                        max_width=rwidth if side == 0 else None)
                    space = height if side % 2 else width
                    currpos += perc * space + spacing * (total_attrs - side)

            xs = [x0 + (x1 - x0) / 2,
                  x0 - max_ylabel_w1 - self.ATTR_VAL_OFFSET,
                  x0 + (x1 - x0) / 2,
                  x1 + max_ylabel_w2 + self.ATTR_VAL_OFFSET]
            ys = [y1 + self.ATTR_VAL_OFFSET + self.ATTR_NAME_OFFSET,
                  y0 + (y1 - y0) / 2,
                  y0 - self.ATTR_VAL_OFFSET - self.ATTR_NAME_OFFSET,
                  y0 + (y1 - y0) / 2]
            CanvasText(
                self.canvas, attr.name, xs[side], ys[side], align, bold=True,
                vertical=side % 2)

        def add_rect(x0, x1, y0, y1, condition,
                     used_attrs, used_vals, attr_vals=""):
            area_index = len(self.areas)
            x1 += (x0 == x1)
            y1 += (y0 == y1)
            # rectangles of width and height 1 are not shown - increase
            y1 += (x1 - x0 + y1 - y0 == 2)
            colors = class_var and [QColor(*col) for col in class_var.colors]

            def select_area(_, ev):
                self.select_area(area_index, ev)

            def rect(x, y, w, h, z, pen_color=None, brush_color=None, **args):
                if pen_color is None:
                    return CanvasRectangle(
                        self.canvas, x, y, w, h, z=z, onclick=select_area,
                        **args)
                if brush_color is None:
                    brush_color = pen_color
                return CanvasRectangle(
                    self.canvas, x, y, w, h, pen_color, brush_color, z=z,
                    onclick=select_area, **args)

            def line(x1, y1, x2, y2):
                r = QGraphicsLineItem(x1, y1, x2, y2, None)
                self.canvas.addItem(r)
                r.setPen(QPen(Qt.white, 2))
                r.setZValue(30)

            outer_rect = rect(x0, y0, x1 - x0, y1 - y0, 30)
            self.areas.append((used_attrs, used_vals, outer_rect))
            if not conditionaldict[attr_vals]:
                return

            if self.variable_color is None:
                s = sum(apriori_dists[0])
                expected = s * reduce(
                    mul,
                    (apriori_dists[i][used_vals[i]] / float(s)
                     for i in range(len(used_vals))))
                actual = conditionaldict[attr_vals]
                pearson = float((actual - expected) / sqrt(expected))
                if pearson == 0:
                    ind = 0
                else:
                    ind = max(0, min(int(log(abs(pearson), 2)), 3))
                color = [self.RED_COLORS, self.BLUE_COLORS][pearson > 0][ind]
                rect(x0, y0, x1 - x0, y1 - y0, -20, color)
                outer_rect.setToolTip(
                    condition + "<hr/>" +
                    "Expected instances: %.1f<br>"
                    "Actual instances: %d<br>"
                    "Standardized (Pearson) residual: %.1f" %
                    (expected, conditionaldict[attr_vals], pearson))
            else:
                cls_values = get_variable_values_sorted(class_var)
                prior = get_distribution(data, class_var.name)
                total = 0
                for i, value in enumerate(cls_values):
                    val = conditionaldict[attr_vals + "-" + value]
                    if val == 0:
                        continue
                    if i == len(cls_values) - 1:
                        v = y1 - y0 - total
                    else:
                        v = ((y1 - y0) * val) / conditionaldict[attr_vals]
                    rect(x0, y0 + total, x1 - x0, v, -20, colors[i])
                    total += v

                if self.use_boxes and \
                        abs(x1 - x0) > bar_width and abs(y1 - y0) > bar_width:
                    total = 0
                    line(x0 + bar_width, y0, x0 + bar_width, y1)
                    n = sum(prior)
                    for i, (val, color) in enumerate(zip(prior, colors)):
                        if i == len(prior) - 1:
                            h = y1 - y0 - total
                        else:
                            h = (y1 - y0) * val / n
                        rect(x0, y0 + total, bar_width, h, 20, color)
                        total += h

                if conditionalsubsetdict:
                    if conditionalsubsetdict[attr_vals]:
                        if self.subset_indices is not None:
                            line(x1 - bar_width, y0, x1 - bar_width, y1)
                            total = 0
                            n = conditionalsubsetdict[attr_vals]
                            if n:
                                for i, (cls, color) in \
                                        enumerate(zip(cls_values, colors)):
                                    val = conditionalsubsetdict[
                                        attr_vals + "-" + cls]
                                    if val == 0:
                                        continue
                                    if i == len(prior) - 1:
                                        v = y1 - y0 - total
                                    else:
                                        v = ((y1 - y0) * val) / n
                                    rect(x1 - bar_width, y0 + total,
                                         bar_width, v, 15, color)
                                    total += v

                actual = [conditionaldict[attr_vals + "-" + cls_values[i]]
                          for i in range(len(prior))]
                n_actual = sum(actual)
                if n_actual > 0:
                    apriori = [prior[key] for key in cls_values]
                    n_apriori = sum(apriori)
                    text = "<br/>".join(
                        "<b>%s</b>: %d / %.1f%% (Expected %.1f / %.1f%%)" %
                        (cls, act, 100.0 * act / n_actual,
                         apr / n_apriori * n_actual, 100.0 * apr / n_apriori)
                        for cls, act, apr in zip(cls_values, actual, apriori))
                else:
                    text = ""
                outer_rect.setToolTip(
                    "{}<hr>Instances: {}<br><br>{}".format(
                        condition, n_actual, text[:-4]))

        def create_legend():
            if self.variable_color is None:
                names = ["<-8", "-8:-4", "-4:-2", "-2:2", "2:4", "4:8", ">8",
                         "Residuals:"]
                colors = self.RED_COLORS[::-1] + self.BLUE_COLORS[1:]
                edges = repeat(Qt.black)
            else:
                names = get_variable_values_sorted(class_var)
                edges = colors = [QColor(*col) for col in class_var.colors]

            items = []
            size = 8
            for name, color, edgecolor in zip(names, colors, edges):
                item = QGraphicsItemGroup()
                item.addToGroup(
                    CanvasRectangle(None, -size / 2, -size / 2, size, size,
                                    edgecolor, color))
                item.addToGroup(
                    CanvasText(None, name, size, 0, Qt.AlignVCenter))
                items.append(item)
            return wrap_legend_items(
                items, hspacing=20, vspacing=16 + size,
                max_width=self.canvas_view.width() - xoff)

        self.canvas.clear()
        self.areas = []

        data = self.discrete_data
        if data is None:
            return
        attr_list = self.get_disc_attr_list()
        class_var = data.domain.class_var
        if class_var:
            sql = isinstance(data, SqlTable)
            name = not sql and data.name
            # save class_var because it is removed in the next line
            data = data[:, attr_list + [class_var]]
            data.domain.class_var = class_var
            if not sql:
                data.name = name
        else:
            data = data[:, attr_list]
        # TODO: check this
        # data = Preprocessor_dropMissing(data)
        if len(data) == 0:
            self.Warning.no_valid_data()
            return
        else:
            self.Warning.no_valid_data.clear()

        attrs = [attr for attr in attr_list if not attr.values]
        if attrs:
            CanvasText(self.canvas,
                       "Feature {} has no values".format(attrs[0]),
                       (self.canvas_view.width() - 120) / 2,
                       self.canvas_view.height() / 2)
            return
        if self.variable_color is None:
            apriori_dists = [get_distribution(data, attr) for attr in attr_list]
        else:
            apriori_dists = []

        def get_max_label_width(attr):
            values = get_variable_values_sorted(attr)
            maxw = 0
            for val in values:
                t = CanvasText(self.canvas, val, 0, 0, bold=0, show=False)
                maxw = max(int(t.boundingRect().width()), maxw)
            return maxw

        xoff = 20

        # get the maximum width of rectangle
        width = 20
        max_ylabel_w1 = max_ylabel_w2 = 0
        if len(attr_list) > 1:
            text = CanvasText(self.canvas, attr_list[1].name, bold=1, show=0)
            max_ylabel_w1 = min(get_max_label_width(attr_list[1]), 150)
            width = 5 + text.boundingRect().height() + \
                self.ATTR_VAL_OFFSET + max_ylabel_w1
            xoff = width
            if len(attr_list) == 4:
                text = CanvasText(self.canvas, attr_list[3].name, bold=1, show=0)
                max_ylabel_w2 = min(get_max_label_width(attr_list[3]), 150)
                width += text.boundingRect().height() + \
                    self.ATTR_VAL_OFFSET + max_ylabel_w2 - 10

        legend = create_legend()

        # get the maximum height of rectangle
        yoff = 45
        legendoff = yoff + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35
        square_size = min(self.canvas_view.width() - width - 20,
                          self.canvas_view.height() - legendoff
                          - legend.boundingRect().height())

        if square_size < 0:
            return  # canvas is too small to draw rectangles
        self.canvas_view.setSceneRect(
            0, 0, self.canvas_view.width(), self.canvas_view.height())

        drawn_sides = set()
        draw_positions = {}

        conditionaldict, distributiondict = \
            get_conditional_distribution(data, attr_list)
        conditionalsubsetdict = None
        if self.subset_indices:
            conditionalsubsetdict, _ = get_conditional_distribution(
                self.discrete_data[self.subset_indices], attr_list)

        # draw rectangles
        draw_data(
            attr_list, (xoff, xoff + square_size), (yoff, yoff + square_size),
            0, "", len(attr_list), [], [])

        self.canvas.addItem(legend)
        legend.setPos(
            xoff - legend.boundingRect().x()
            + max(0, (square_size - legend.boundingRect().width()) / 2),
            legendoff + square_size)
        self.update_selection_rects()

    @classmethod
    def migrate_context(cls, context, version):
        if version < 2:
            settings.migrate_str_to_variable(context, none_placeholder="(None)")


def get_conditional_distribution(data, attrs):
    cond_dist = defaultdict(int)
    dist = defaultdict(int)
    cond_dist[""] = dist[""] = len(data)
    all_attrs = attrs[:]
    if data.domain.class_var is not None:
        all_attrs.append(data.domain.class_var)

    for i in range(1, len(all_attrs) + 1):
        attr = all_attrs[:i]
        if isinstance(data, SqlTable):
            # make all possible pairs of attributes + class_var
            attr = [a.to_sql() for a in attr]
            fields = attr + ["COUNT(*)"]
            query = data._sql_query(fields, group_by=attr)
            with data._execute_sql_query(query) as cur:
                res = cur.fetchall()
            for r in res:
                str_values = [a.repr_val(a.to_val(x))
                              for a, x in zip(all_attrs, r[:-1])]
                str_values = [x if x != '?' else 'None' for x in str_values]
                cond_dist['-'.join(str_values)] = r[-1]
                dist[str_values[-1]] += r[-1]
        else:
            for indices in product(*(range(len(a.values)) for a in attr)):
                vals = []
                conditions = []
                for k, ind in enumerate(indices):
                    vals.append(attr[k].values[ind])
                    fd = filter.FilterDiscrete(
                        column=attr[k], values=[attr[k].values[ind]])
                    conditions.append(fd)
                filt = filter.Values(conditions)
                filtdata = filt(data)
                cond_dist['-'.join(vals)] = len(filtdata)
                dist[vals[-1]] += len(filtdata)
    return cond_dist, dist


if __name__ == "__main__":  # pragma: no cover
    data = Table("zoo")
    WidgetPreview(OWMosaicDisplay).run(data, set_subset_data=data[::10])

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