def test_read_only(self): model = DomainModel() domain = Domain([ContinuousVariable(x) for x in "abc"]) model.set_domain(domain) index = model.index(0, 0) self.assertRaises(TypeError, model.append, 42) self.assertRaises(TypeError, model.extend, [42]) self.assertRaises(TypeError, model.insert, 0, 42) self.assertRaises(TypeError, model.remove, 0) self.assertRaises(TypeError, model.pop) self.assertRaises(TypeError, model.clear) self.assertRaises(TypeError, model.reverse) self.assertRaises(TypeError, model.sort) with self.assertRaises(TypeError): model[0] = 1 with self.assertRaises(TypeError): del model[0] self.assertFalse(model.setData(index, domain[0], Qt.EditRole)) self.assertTrue(model.setData(index, "foo", Qt.ToolTipRole)) self.assertFalse(model.setItemData(index, {Qt.EditRole: domain[0], Qt.ToolTipRole: "foo"})) self.assertTrue(model.setItemData(index, {Qt.ToolTipRole: "foo"})) self.assertFalse(model.insertRows(0, 1)) self.assertSequenceEqual(model, domain) self.assertFalse(model.removeRows(0, 1)) self.assertSequenceEqual(model, domain)
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 __init__(self): super().__init__() self.data = None box = gui.radioButtons( self.controlArea, self, "feature_type", box="Feature names", callback=lambda: self.apply()) button = gui.appendRadioButton(box, "Generic") edit = gui.lineEdit( gui.indentedBox(box, gui.checkButtonOffsetHint(button)), self, "feature_name", placeholderText="Type a prefix ...", toolTip="Custom feature name") edit.editingFinished.connect(self._apply_editing) self.meta_button = gui.appendRadioButton(box, "From meta attribute:") self.feature_model = DomainModel( order=DomainModel.METAS, valid_types=StringVariable, alphabetical=True) self.feature_combo = gui.comboBox( gui.indentedBox(box, gui.checkButtonOffsetHint(button)), self, "feature_names_column", callback=self._feature_combo_changed, model=self.feature_model) self.apply_button = gui.auto_commit( self.controlArea, self, "auto_apply", "&Apply", box=False, commit=self.apply) self.apply_button.button.setAutoDefault(False) self.set_controls()
def __init__(self): super().__init__() dbox = gui.widgetBox(self.controlArea, "Image values") rbox = gui.radioButtons( dbox, self, "value_type", callback=self._change_integration) gui.appendRadioButton(rbox, "From spectra") self.box_values_spectra = gui.indentedBox(rbox) gui.comboBox( self.box_values_spectra, self, "integration_method", valueType=int, items=(a.name for a in self.integration_methods), callback=self._change_integral_type) gui.rubber(self.controlArea) gui.appendRadioButton(rbox, "Use feature") self.box_values_feature = gui.indentedBox(rbox) self.feature_value_model = DomainModel(DomainModel.METAS | DomainModel.CLASSES, valid_types=DomainModel.PRIMITIVE) self.feature_value = gui.comboBox( self.box_values_feature, self, "attr_value", callback=self.update_feature_value, model=self.feature_value_model, sendSelectedValue=True, valueType=str) splitter = QSplitter(self) splitter.setOrientation(Qt.Vertical) self.imageplot = ImagePlot(self) self.imageplot.selection_changed.connect(self.output_image_selection) self.curveplot = CurvePlotHyper(self, select=SELECTONE) self.curveplot.selection_changed.connect(self.redraw_data) self.curveplot.plot.vb.x_padding = 0.005 # pad view so that lines are not hidden splitter.addWidget(self.imageplot) splitter.addWidget(self.curveplot) self.mainArea.layout().addWidget(splitter) self.line1 = MovableVline(position=self.lowlim, label="", report=self.curveplot) self.line1.sigMoved.connect(lambda v: setattr(self, "lowlim", v)) self.line2 = MovableVline(position=self.highlim, label="", report=self.curveplot) self.line2.sigMoved.connect(lambda v: setattr(self, "highlim", v)) self.line3 = MovableVline(position=self.choose, label="", report=self.curveplot) self.line3.sigMoved.connect(lambda v: setattr(self, "choose", v)) for line in [self.line1, self.line2, self.line3]: line.sigMoveFinished.connect(self.changed_integral_range) self.curveplot.add_marking(line) line.hide() self.data = None self.disable_integral_range = False self.resize(900, 700) self._update_integration_type() # prepare interface according to the new context self.contextAboutToBeOpened.connect(lambda x: self.init_interface_data(x[0]))
def __init__(self): super().__init__() self.data = None self.cont_data = None # GUI box = gui.vBox(self.mainArea) self.correlation_combo = gui.comboBox( box, self, "correlation_type", items=CorrelationType.items(), orientation=Qt.Horizontal, callback=self._correlation_combo_changed ) self.feature_model = DomainModel( order=DomainModel.ATTRIBUTES, separators=False, placeholder="(All combinations)", valid_types=ContinuousVariable) gui.comboBox( box, self, "feature", callback=self._feature_combo_changed, model=self.feature_model ) self.vizrank, _ = CorrelationRank.add_vizrank( None, self, None, self._vizrank_selection_changed) self.vizrank.progressBar = self.progressBar self.vizrank.button.setEnabled(False) self.vizrank.threadStopped.connect(self._vizrank_stopped) gui.separator(box) box.layout().addWidget(self.vizrank.filter) box.layout().addWidget(self.vizrank.rank_table) button_box = gui.hBox(self.mainArea) button_box.layout().addWidget(self.vizrank.button)
def __init__(self): # pylint: disable=missing-docstring super().__init__() self.data = self.discrete_data = None self.attrs = [] self.input_features = None self.areas = [] self.selection = set() self.attr_box = gui.hBox(self.mainArea) self.domain_model = DomainModel(valid_types=DomainModel.PRIMITIVE) combo_args = dict( widget=self.attr_box, master=self, contentsLength=12, callback=self.update_attr, sendSelectedValue=True, valueType=str, model=self.domain_model) fixed_size = (QSizePolicy.Fixed, QSizePolicy.Fixed) gui.comboBox(value="attr_x", **combo_args) gui.widgetLabel(self.attr_box, "\u2715", sizePolicy=fixed_size) gui.comboBox(value="attr_y", **combo_args) self.vizrank, self.vizrank_button = SieveRank.add_vizrank( self.attr_box, self, "Score Combinations", self.set_attr) self.vizrank_button.setSizePolicy(*fixed_size) self.canvas = QGraphicsScene() self.canvasView = ViewWithPress( self.canvas, self.mainArea, handler=self.reset_selection) self.mainArea.layout().addWidget(self.canvasView) self.canvasView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.canvasView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
def _add_controls(self): infobox = gui.widgetBox(self.controlArea, "Info") self.infoLabel = gui.widgetLabel(infobox, "No data on input.") displaybox = gui.widgetBox(self.controlArea, "Display") gui.checkBox(displaybox, self, "show_profiles", "Lines", callback=self.__show_profiles_changed, tooltip="Plot lines") gui.checkBox(displaybox, self, "show_range", "Range", callback=self.__show_range_changed, tooltip="Plot range between 10th and 90th percentile") gui.checkBox(displaybox, self, "show_mean", "Mean", callback=self.__show_mean_changed, tooltip="Plot mean curve") gui.checkBox(displaybox, self, "show_error", "Error bars", callback=self.__show_error_changed, tooltip="Show standard deviation") self.group_vars = DomainModel( placeholder="None", separators=False, valid_types=DiscreteVariable) self.group_view = gui.listView( self.controlArea, self, "group_var", box="Group by", model=self.group_vars, callback=self.__group_var_changed) self.group_view.setEnabled(False) self.group_view.setMinimumSize(QSize(30, 100)) self.group_view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored) plot_gui = OWPlotGUI(self) plot_gui.box_zoom_select(self.controlArea) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, "auto_commit", "Send Selection", "Send Automatically")
def __init__(self): super().__init__() self.data = None self.feature_model = DomainModel(valid_types=DiscreteVariable) self.table = None box = gui.vBox(self.controlArea, "Rows") gui.comboBox(box, self, 'rows', sendSelectedValue=True, model=self.feature_model, callback=self._attribute_changed) box = gui.vBox(self.controlArea, "Columns") gui.comboBox(box, self, 'columns', sendSelectedValue=True, model=self.feature_model, callback=self._attribute_changed) gui.rubber(self.controlArea) box = gui.vBox(self.controlArea, "Scores") self.scores = gui.widgetLabel(box, "\n\n") self.apply_button = gui.auto_commit( self.controlArea, self, "auto_apply", "&Apply", box=False) self.tableview = ContingencyTable(self) self.mainArea.layout().addWidget(self.tableview)
def __init__(self): super().__init__() self.dataset = None self.attrs = DomainModel( valid_types=Orange.data.DiscreteVariable, separators=False) cb = gui.comboBox( self.controlArea, self, "attribute", box=True, model=self.attrs, callback=self.update_scene, contentsLength=12) grid = QGridLayout() self.legend = gui.widgetBox(gui.indentedBox(cb.box), orientation=grid) grid.setColumnStretch(1, 1) grid.setHorizontalSpacing(6) self.legend_items = [] self.split_vars = DomainModel( valid_types=Orange.data.DiscreteVariable, separators=False, placeholder="None", ) gui.comboBox( self.controlArea, self, "split_var", box="Split by", model=self.split_vars, callback=self.update_scene) gui.checkBox( self.controlArea, self, "explode", "Explode pies", box=True, callback=self.update_scene) gui.rubber(self.controlArea) gui.widgetLabel( gui.hBox(self.controlArea, box=True), "The aim of this widget is to\n" "demonstrate that pie charts are\n" "a terrible visualization. Please\n" "don't use it for any other purpose.") self.scene = QGraphicsScene() self.view = QGraphicsView(self.scene) self.view.setRenderHints( QPainter.Antialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform) self.mainArea.layout().addWidget(self.view) self.mainArea.setMinimumWidth(600)
def __init__(self): super().__init__() self.data = None self.set_data(self.data) # show warning self.group_vars = DomainModel( placeholder="None", separators=False, valid_types=Orange.data.DiscreteVariable) self.group_view = gui.listView( self.controlArea, self, "group_var", box="Group by", model=self.group_vars, callback=self.grouping_changed) gui.auto_commit(self.controlArea, self, "autocommit", "Apply")
def __init__(self): super().__init__() self.data = None # type: Optional[Table] # Information panel info_box = gui.vBox(self.controlArea, 'Info') info_box.setMinimumWidth(200) self.info_summary = gui.widgetLabel(info_box, wordWrap=True) self.info_attr = gui.widgetLabel(info_box, wordWrap=True) self.info_class = gui.widgetLabel(info_box, wordWrap=True) self.info_meta = gui.widgetLabel(info_box, wordWrap=True) self.set_info() # TODO: Implement filtering on the model # filter_box = gui.vBox(self.controlArea, 'Filter') # self.filter_text = gui.lineEdit( # filter_box, self, value='filter_string', # placeholderText='Filter variables by name', # callback=self._filter_table_variables, callbackOnType=True, # ) # shortcut = QShortcut(QKeySequence('Ctrl+f'), self, self.filter_text.setFocus) # shortcut.setWhatsThis('Filter variables by name') self.color_var_model = DomainModel( valid_types=(ContinuousVariable, DiscreteVariable), placeholder='None', ) box = gui.vBox(self.controlArea, 'Histogram') self.cb_color_var = gui.comboBox( box, master=self, value='color_var', model=self.color_var_model, label='Color:', orientation=Qt.Horizontal, ) self.cb_color_var.activated.connect(self.__color_var_changed) gui.rubber(self.controlArea) gui.auto_commit( self.buttonsArea, self, 'auto_commit', 'Send Selected Rows', 'Send Automatically', ) # Main area self.model = FeatureStatisticsTableModel(parent=self) self.table_view = FeatureStatisticsTableView(self.model, parent=self) self.table_view.selectionModel().selectionChanged.connect(self.on_select) self.table_view.horizontalHeader().sectionClicked.connect(self.on_header_click) self.mainArea.layout().addWidget(self.table_view)
def _add_controls_axis(self): common_options = dict( labelWidth=50, orientation=Qt.Horizontal, sendSelectedValue=True, valueType=str, contentsLength=14 ) box = gui.vBox(self.controlArea, True) dmod = DomainModel self.xy_model = DomainModel(dmod.MIXED, valid_types=ContinuousVariable) self.cb_attr_x = gui.comboBox( box, self, "attr_x", label="Axis x:", callback=self.set_attr_from_combo, model=self.xy_model, **common_options) self.cb_attr_y = gui.comboBox( box, self, "attr_y", label="Axis y:", callback=self.set_attr_from_combo, model=self.xy_model, **common_options) vizrank_box = gui.hBox(box) self.vizrank, self.vizrank_button = ScatterPlotVizRank.add_vizrank( vizrank_box, self, "Find Informative Projections", self.set_attr)
def __init__(self): super().__init__() # TODO: add input box for selecting which should be the reference frame box = gui.widgetBox(self.controlArea, "Axes") common_options = dict( labelWidth=50, orientation=Qt.Horizontal, sendSelectedValue=True, valueType=str) self.xy_model = DomainModel(DomainModel.METAS | DomainModel.CLASSES, valid_types=ContinuousVariable) self.cb_attr_x = gui.comboBox( box, self, "attr_x", label="Axis x:", callback=self._update_attr, model=self.xy_model, **common_options) self.cb_attr_y = gui.comboBox( box, self, "attr_y", label="Axis y:", callback=self._update_attr, model=self.xy_model, **common_options) self.contextAboutToBeOpened.connect(self._init_interface_data) box = gui.widgetBox(self.controlArea, "Parameters") gui.checkBox(box, self, "sobel_filter", label="Use sobel filter", callback=self._sobel_changed) gui.separator(box) hbox = gui.hBox(box) self.le1 = lineEditIntRange(box, self, "ref_frame_num", bottom=1, default=1, callback=self._ref_frame_changed) hbox.layout().addWidget(QLabel("Reference frame:", self)) hbox.layout().addWidget(self.le1) gui.rubber(self.controlArea) plot_box = gui.widgetBox(self.mainArea, "Shift curves") self.plotview = pg.PlotWidget(background="w") plot_box.layout().addWidget(self.plotview) # TODO: resize widget to make it a bit smaller self.data = None gui.auto_commit(self.controlArea, self, "autocommit", "Send Data")
def __init__(self): super().__init__() self.data = None # GUI box = gui.vBox(self.controlArea, "Feature names") self.feature_radio = gui.radioButtonsInBox( box, self, "feature_type", callback=lambda: self.apply(), btnLabels=["Generic", "From meta attribute:"] ) self.feature_model = DomainModel(order=DomainModel.METAS, valid_types=StringVariable, alphabetical=True) self.feature_combo = gui.comboBox( gui.indentedBox(box, gui.checkButtonOffsetHint(self.feature_radio.buttons[0])), self, "feature_names_column", callback=self._feature_combo_changed, model=self.feature_model, ) self.apply_button = gui.auto_commit( self.controlArea, self, "auto_apply", "&Apply", box=False, commit=self.apply )
def test_no_separators(self): """ GH-2697 """ attrs = [ContinuousVariable(n) for n in "abg"] classes = [ContinuousVariable(n) for n in "deh"] metas = [ContinuousVariable(n) for n in "ijf"] model = DomainModel(order=DomainModel.SEPARATED, separators=False) model.set_domain(Domain(attrs, classes, metas)) self.assertEqual(list(model), classes + metas + attrs) model = DomainModel(order=DomainModel.SEPARATED, separators=True) model.set_domain(Domain(attrs, classes, metas)) self.assertEqual( list(model), classes + [PyListModel.Separator] + metas + [PyListModel.Separator] + attrs)
def test_subparts(self): attrs = [ContinuousVariable(n) for n in "abg"] classes = [ContinuousVariable(n) for n in "deh"] metas = [ContinuousVariable(n) for n in "ijf"] m = DomainModel sep = m.Separator model = DomainModel( order=(m.ATTRIBUTES | m.METAS, sep, m.CLASSES)) model.set_domain(Domain(attrs, classes, metas)) self.assertEqual(list(model), attrs + metas + [sep] + classes) m = DomainModel sep = m.Separator model = DomainModel( order=(m.ATTRIBUTES | m.METAS, sep, m.CLASSES), alphabetical=True) model.set_domain(Domain(attrs, classes, metas)) self.assertEqual(list(model), sorted(attrs + metas, key=lambda x: x.name) + [sep] + sorted(classes, key=lambda x: x.name))
class OWScatterPlot(OWDataProjectionWidget): """Scatterplot visualization with explorative analysis and intelligent data visualization enhancements.""" name = 'Scatter Plot' description = "Interactive scatter plot visualization with " \ "intelligent data visualization enhancements." icon = "icons/ScatterPlot.svg" priority = 140 keywords = [] class Inputs(OWDataProjectionWidget.Inputs): features = Input("Features", AttributeList) class Outputs(OWDataProjectionWidget.Outputs): features = Output("Features", AttributeList, dynamic=False) settings_version = 4 auto_sample = Setting(True) attr_x = ContextSetting(None) attr_y = ContextSetting(None) tooltip_shows_all = Setting(True) GRAPH_CLASS = OWScatterPlotGraph graph = SettingProvider(OWScatterPlotGraph) embedding_variables_names = None xy_changed_manually = Signal(Variable, Variable) class Warning(OWDataProjectionWidget.Warning): missing_coords = Msg("Plot cannot be displayed because '{}' or '{}' " "is missing for all data points.") no_continuous_vars = Msg("Data has no numeric variables.") class Information(OWDataProjectionWidget.Information): sampled_sql = Msg("Large SQL table; showing a sample.") missing_coords = Msg( "Points with missing '{}' or '{}' are not displayed") def __init__(self): self.sql_data = None # Orange.data.sql.table.SqlTable self.attribute_selection_list = None # list of Orange.data.Variable self.__timer = QTimer(self, interval=1200) self.__timer.timeout.connect(self.add_data) super().__init__() # manually register Matplotlib file writers self.graph_writers = self.graph_writers.copy() for w in [MatplotlibFormat, MatplotlibPDFFormat]: self.graph_writers.append(w) def _add_controls(self): self._add_controls_axis() self._add_controls_sampling() super()._add_controls() self.gui.add_widgets([ self.gui.ShowGridLines, self.gui.ToolTipShowsAll, self.gui.RegressionLine ], self._plot_box) gui.checkBox( gui.indentedBox(self._plot_box), self, value="graph.orthonormal_regression", label="Treat variables as independent", callback=self.graph.update_regression_line, tooltip= "If checked, fit line to group (minimize distance from points);\n" "otherwise fit y as a function of x (minimize vertical distances)", disabledBy=self.cb_reg_line) def _add_controls_axis(self): common_options = dict(labelWidth=50, orientation=Qt.Horizontal, sendSelectedValue=True, contentsLength=14) self.attr_box = gui.vBox(self.controlArea, True) dmod = DomainModel self.xy_model = DomainModel(dmod.MIXED, valid_types=ContinuousVariable) self.cb_attr_x = gui.comboBox(self.attr_box, self, "attr_x", label="Axis x:", callback=self.set_attr_from_combo, model=self.xy_model, **common_options, searchable=True) self.cb_attr_y = gui.comboBox(self.attr_box, self, "attr_y", label="Axis y:", callback=self.set_attr_from_combo, model=self.xy_model, **common_options, searchable=True) vizrank_box = gui.hBox(self.attr_box) self.vizrank, self.vizrank_button = ScatterPlotVizRank.add_vizrank( vizrank_box, self, "Find Informative Projections", self.set_attr) def _add_controls_sampling(self): self.sampling = gui.auto_commit(self.controlArea, self, "auto_sample", "Sample", box="Sampling", callback=self.switch_sampling, commit=lambda: self.add_data(1)) self.sampling.setVisible(False) @property def effective_variables(self): return [self.attr_x, self.attr_y ] if self.attr_x and self.attr_y else [] @property def effective_data(self): eff_var = self.effective_variables if eff_var and self.attr_x.name == self.attr_y.name: eff_var = [self.attr_x] return self.data.transform(Domain(eff_var)) def _vizrank_color_change(self): self.vizrank.initialize() err_msg = "" if self.data is None: err_msg = "No data on input" elif self.data.is_sparse(): err_msg = "Data is sparse" elif len(self.xy_model) < 3: err_msg = "Not enough features for ranking" elif self.attr_color is None: err_msg = "Color variable is not selected" elif np.isnan( self.data.get_column_view( self.attr_color)[0].astype(float)).all(): err_msg = "Color variable has no values" self.vizrank_button.setEnabled(not err_msg) self.vizrank_button.setToolTip(err_msg) def set_data(self, data): super().set_data(data) self._vizrank_color_change() def findvar(name, iterable): """Find a Orange.data.Variable in `iterable` by name""" for el in iterable: if isinstance(el, Variable) and el.name == name: return el return None # handle restored settings from < 3.3.9 when attr_* were stored # by name if isinstance(self.attr_x, str): self.attr_x = findvar(self.attr_x, self.xy_model) if isinstance(self.attr_y, str): self.attr_y = findvar(self.attr_y, self.xy_model) if isinstance(self.attr_label, str): self.attr_label = findvar(self.attr_label, self.gui.label_model) if isinstance(self.attr_color, str): self.attr_color = findvar(self.attr_color, self.gui.color_model) if isinstance(self.attr_shape, str): self.attr_shape = findvar(self.attr_shape, self.gui.shape_model) if isinstance(self.attr_size, str): self.attr_size = findvar(self.attr_size, self.gui.size_model) def check_data(self): super().check_data() self.__timer.stop() self.sampling.setVisible(False) self.sql_data = None if isinstance(self.data, SqlTable): if self.data.approx_len() < 4000: self.data = Table(self.data) else: self.Information.sampled_sql() self.sql_data = self.data data_sample = self.data.sample_time(0.8, no_cache=True) data_sample.download_data(2000, partial=True) self.data = Table(data_sample) self.sampling.setVisible(True) if self.auto_sample: self.__timer.start() if self.data is not None: if not self.data.domain.has_continuous_attributes(True, True): self.Warning.no_continuous_vars() self.data = None if self.data is not None and (len(self.data) == 0 or len(self.data.domain) == 0): self.data = None def get_embedding(self): self.valid_data = None if self.data is None: return None x_data = self.get_column(self.attr_x, filter_valid=False) y_data = self.get_column(self.attr_y, filter_valid=False) if x_data is None or y_data is None: return None self.Warning.missing_coords.clear() self.Information.missing_coords.clear() self.valid_data = np.isfinite(x_data) & np.isfinite(y_data) if self.valid_data is not None and not np.all(self.valid_data): msg = self.Information if np.any(self.valid_data) else self.Warning msg.missing_coords(self.attr_x.name, self.attr_y.name) return np.vstack((x_data, y_data)).T # Tooltip def _point_tooltip(self, point_id, skip_attrs=()): point_data = self.data[point_id] xy_attrs = (self.attr_x, self.attr_y) text = "<br/>".join( escape('{} = {}'.format(var.name, point_data[var])) for var in xy_attrs) if self.tooltip_shows_all: others = super()._point_tooltip(point_id, skip_attrs=xy_attrs) if others: text = "<b>{}</b><br/><br/>{}".format(text, others) return text def add_data(self, time=0.4): if self.data and len(self.data) > 2000: self.__timer.stop() return data_sample = self.sql_data.sample_time(time, no_cache=True) if data_sample: data_sample.download_data(2000, partial=True) data = Table(data_sample) self.data = Table.concatenate((self.data, data), axis=0) self.handleNewSignals() def init_attr_values(self): super().init_attr_values() data = self.data domain = data.domain if data and len(data) else None self.xy_model.set_domain(domain) self.attr_x = self.xy_model[0] if self.xy_model else None self.attr_y = self.xy_model[1] if len(self.xy_model) >= 2 \ else self.attr_x def switch_sampling(self): self.__timer.stop() if self.auto_sample and self.sql_data: self.add_data() self.__timer.start() def set_subset_data(self, subset_data): self.warning() if isinstance(subset_data, SqlTable): if subset_data.approx_len() < AUTO_DL_LIMIT: subset_data = Table(subset_data) else: self.warning("Data subset does not support large Sql tables") subset_data = None super().set_subset_data(subset_data) # called when all signals are received, so the graph is updated only once def handleNewSignals(self): self.attr_box.setEnabled(True) self.vizrank.setEnabled(True) if self.attribute_selection_list and self.data is not None and \ self.data.domain is not None and \ all(attr in self.data.domain for attr in self.attribute_selection_list): self.attr_x, self.attr_y = self.attribute_selection_list[:2] self.attr_box.setEnabled(False) self.vizrank.setEnabled(False) super().handleNewSignals() if self._domain_invalidated: self.graph.update_axes() self._domain_invalidated = False @Inputs.features def set_shown_attributes(self, attributes): if attributes and len(attributes) >= 2: self.attribute_selection_list = attributes[:2] self._invalidated = self._invalidated \ or self.attr_x != attributes[0] \ or self.attr_y != attributes[1] else: self.attribute_selection_list = None def set_attr(self, attr_x, attr_y): if attr_x != self.attr_x or attr_y != self.attr_y: self.attr_x, self.attr_y = attr_x, attr_y self.attr_changed() def set_attr_from_combo(self): self.attr_changed() self.xy_changed_manually.emit(self.attr_x, self.attr_y) def attr_changed(self): self.setup_plot() self.commit() def get_axes(self): return {"bottom": self.attr_x, "left": self.attr_y} def colors_changed(self): super().colors_changed() self._vizrank_color_change() def commit(self): super().commit() self.send_features() def send_features(self): features = [attr for attr in [self.attr_x, self.attr_y] if attr] self.Outputs.features.send(features or None) def get_widget_name_extension(self): if self.data is not None: return "{} vs {}".format(self.attr_x.name, self.attr_y.name) return None @classmethod def migrate_settings(cls, settings, version): if version < 2 and "selection" in settings and settings["selection"]: settings["selection_group"] = [(a, 1) for a in settings["selection"]] if version < 3: if "auto_send_selection" in settings: settings["auto_commit"] = settings["auto_send_selection"] if "selection_group" in settings: settings["selection"] = settings["selection_group"] @classmethod def migrate_context(cls, context, version): values = context.values if version < 3: values["attr_color"] = values["graph"]["attr_color"] values["attr_size"] = values["graph"]["attr_size"] values["attr_shape"] = values["graph"]["attr_shape"] values["attr_label"] = values["graph"]["attr_label"] if version < 4: if values["attr_x"][1] % 100 == 1 or values["attr_y"][1] % 100 == 1: raise IncompatibleContext()
def __init__(self, parent): QWidget.__init__(self) OWComponent.__init__(self, parent) SelectionGroupMixin.__init__(self) self.parent = parent self.selection_type = SELECTMANY self.saving_enabled = True self.selection_enabled = True self.viewtype = INDIVIDUAL # required bt InteractiveViewBox self.highlighted = None self.data_points = None self.data_values = None self.data_imagepixels = None self.plotview = pg.PlotWidget(background="w", viewBox=InteractiveViewBox(self)) self.plot = self.plotview.getPlotItem() self.plot.scene().installEventFilter( HelpEventDelegate(self.help_event, self)) layout = QVBoxLayout() self.setLayout(layout) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().addWidget(self.plotview) self.img = ImageItemNan() self.img.setOpts(axisOrder='row-major') self.plot.addItem(self.img) self.plot.vb.setAspectLocked() self.plot.scene().sigMouseMoved.connect(self.plot.vb.mouseMovedEvent) layout = QGridLayout() self.plotview.setLayout(layout) self.button = QPushButton("View", self.plotview) self.button.setAutoDefault(False) layout.setRowStretch(1, 1) layout.setColumnStretch(1, 1) layout.addWidget(self.button, 0, 0) view_menu = MenuFocus(self) self.button.setMenu(view_menu) # prepare interface according to the new context self.parent.contextAboutToBeOpened.connect(lambda x: self.init_interface_data(x[0])) actions = [] zoom_in = QAction( "Zoom in", self, triggered=self.plot.vb.set_mode_zooming ) zoom_in.setShortcuts([Qt.Key_Z, QKeySequence(QKeySequence.ZoomIn)]) zoom_in.setShortcutContext(Qt.WidgetWithChildrenShortcut) actions.append(zoom_in) zoom_fit = QAction( "Zoom to fit", self, triggered=lambda x: (self.plot.vb.autoRange(), self.plot.vb.set_mode_panning()) ) zoom_fit.setShortcuts([Qt.Key_Backspace, QKeySequence(Qt.ControlModifier | Qt.Key_0)]) zoom_fit.setShortcutContext(Qt.WidgetWithChildrenShortcut) actions.append(zoom_fit) select_square = QAction( "Select (square)", self, triggered=self.plot.vb.set_mode_select_square, ) select_square.setShortcuts([Qt.Key_S]) select_square.setShortcutContext(Qt.WidgetWithChildrenShortcut) actions.append(select_square) select_polygon = QAction( "Select (polygon)", self, triggered=self.plot.vb.set_mode_select_polygon, ) select_polygon.setShortcuts([Qt.Key_P]) select_polygon.setShortcutContext(Qt.WidgetWithChildrenShortcut) actions.append(select_polygon) if self.saving_enabled: save_graph = QAction( "Save graph", self, triggered=self.save_graph, ) save_graph.setShortcuts([QKeySequence(Qt.ControlModifier | Qt.Key_I)]) actions.append(save_graph) view_menu.addActions(actions) self.addActions(actions) common_options = dict( labelWidth=50, orientation=Qt.Horizontal, sendSelectedValue=True, valueType=str) choose_xy = QWidgetAction(self) box = gui.vBox(self) box.setFocusPolicy(Qt.TabFocus) self.xy_model = DomainModel(DomainModel.METAS | DomainModel.CLASSES, valid_types=DomainModel.PRIMITIVE) self.cb_attr_x = gui.comboBox( box, self, "attr_x", label="Axis x:", callback=self.update_attr, model=self.xy_model, **common_options) self.cb_attr_y = gui.comboBox( box, self, "attr_y", label="Axis y:", callback=self.update_attr, model=self.xy_model, **common_options) box.setFocusProxy(self.cb_attr_x) self.color_cb = gui.comboBox(box, self, "palette_index", label="Color:", labelWidth=50, orientation=Qt.Horizontal) self.color_cb.setIconSize(QSize(64, 16)) palettes = _color_palettes self.palette_index = min(self.palette_index, len(palettes) - 1) model = color_palette_model(palettes, self.color_cb.iconSize()) model.setParent(self) self.color_cb.setModel(model) self.color_cb.activated.connect(self.update_color_schema) self.color_cb.setCurrentIndex(self.palette_index) form = QFormLayout( formAlignment=Qt.AlignLeft, labelAlignment=Qt.AlignLeft, fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow ) self._level_low_le = lineEditDecimalOrNone(self, self, "level_low", callback=lambda: self.update_levels() or self.reset_thresholds()) self._level_low_le.validator().setDefault(0) form.addRow("Low limit:", self._level_low_le) self._level_high_le = lineEditDecimalOrNone(self, self, "level_high", callback=lambda: self.update_levels() or self.reset_thresholds()) self._level_high_le.validator().setDefault(1) form.addRow("High limit:", self._level_high_le) lowslider = gui.hSlider( box, self, "threshold_low", minValue=0.0, maxValue=1.0, step=0.05, ticks=True, intOnly=False, createLabel=False, callback=self.update_levels) highslider = gui.hSlider( box, self, "threshold_high", minValue=0.0, maxValue=1.0, step=0.05, ticks=True, intOnly=False, createLabel=False, callback=self.update_levels) form.addRow("Low:", lowslider) form.addRow("High:", highslider) box.layout().addLayout(form) choose_xy.setDefaultWidget(box) view_menu.addAction(choose_xy) self.markings_integral = [] self.lsx = None # info about the X axis self.lsy = None # info about the Y axis self.data = None self.data_ids = {}
def __add_annotation_controls(self): combo_options = { "labelWidth": 50, "orientation": Qt.Horizontal, "searchable": True, "sendSelectedValue": True, "contentsLength": 14 } box = gui.vBox(self.controlArea, box="Axes") order = DomainModel.METAS, DomainModel.ATTRIBUTES, DomainModel.CLASSES mod = DomainModel(order, valid_types=ContinuousVariable) gui.comboBox(box, self, "attr_x", label="Axis x:", model=mod, callback=self.__on_axis_attr_changed, **combo_options) gui.comboBox(box, self, "attr_y", label="Axis y:", model=mod, callback=self.__on_axis_attr_changed, **combo_options) box = gui.vBox(self.controlArea, box="Annotation") rbuttons = gui.radioButtons(box, self, "clustering_type", callback=self.__on_clustering_type_changed) gui.appendRadioButton(rbuttons, "DBSCAN") self.dbscan_box = ibox = gui.indentedBox(rbuttons, 20, orientation=Qt.Horizontal) gui.checkBox(ibox, self, "use_epsilon", "epsilon:", labelWidth=80, callback=self.__on_epsilon_check_changed) gui.doubleSpin(ibox, self, "epsilon", 0.1, 10, 0.1, callback=self.__on_epsilon_changed) gui.appendRadioButton(rbuttons, "Gaussian mixture models") self.gmm_box = ibox = gui.indentedBox(rbuttons, 20, orientation=Qt.Horizontal) gui.checkBox(ibox, self, "use_n_components", "clusters:", labelWidth=80, callback=self.__on_n_components_check_changed) gui.spin(ibox, self, "n_components", 1, 100, callback=self.__on_n_components_changed) gui.appendRadioButton(rbuttons, "From variable") self.var_box = ibox = gui.indentedBox(rbuttons, 20, orientation=Qt.Horizontal) gui.comboBox(ibox, self, "cluster_var", model=DomainModel(order, valid_types=DiscreteVariable), callback=self.__on_cluster_var_changed, **combo_options) gui.doubleSpin(box, self, "p_threshold", 0, 1, 0.01, label="FDR threshold:", labelWidth=100, callback=self.__on_fdr_threshold_changed)
class OWTranspose(OWWidget): name = "Transpose" description = "Transpose data table." icon = "icons/Transpose.svg" priority = 2000 class Inputs: data = Input("Data", Table) class Outputs: data = Output("Data", Table, dynamic=False) resizing_enabled = False want_main_area = False settingsHandler = DomainContextHandler() feature_type = ContextSetting(0) feature_names_column = ContextSetting(None) auto_apply = Setting(True) class Error(OWWidget.Error): value_error = Msg("{}") def __init__(self): super().__init__() self.data = None # GUI box = gui.vBox(self.controlArea, "Feature names") self.feature_radio = gui.radioButtonsInBox( box, self, "feature_type", callback=lambda: self.apply(), btnLabels=["Generic", "From meta attribute:"]) self.feature_model = DomainModel(order=DomainModel.METAS, valid_types=StringVariable, alphabetical=True) self.feature_combo = gui.comboBox(gui.indentedBox( box, gui.checkButtonOffsetHint(self.feature_radio.buttons[0])), self, "feature_names_column", callback=self._feature_combo_changed, model=self.feature_model) self.apply_button = gui.auto_commit(self.controlArea, self, "auto_apply", "&Apply", box=False, commit=self.apply) def _feature_combo_changed(self): self.feature_type = 1 self.apply() @Inputs.data def set_data(self, data): # Skip the context if the combo is empty: a context with # feature_model == None would then match all domains if self.feature_model: self.closeContext() self.data = data self.update_controls() if self.data is not None and self.feature_model: self.openContext(data) self.apply() def update_controls(self): self.feature_model.set_domain(None) if self.data: self.feature_model.set_domain(self.data.domain) if self.feature_model: self.feature_names_column = self.feature_model[0] enabled = bool(self.feature_model) self.feature_radio.buttons[1].setEnabled(enabled) self.feature_combo.setEnabled(enabled) self.feature_type = int(enabled) def apply(self): self.clear_messages() transposed = None if self.data: try: transposed = Table.transpose( self.data, self.feature_type and self.feature_names_column) except ValueError as e: self.Error.value_error(e) self.Outputs.data.send(transposed) def send_report(self): text = "from meta attribute: {}".format(self.feature_names_column) \ if self.feature_type else "generic" self.report_items("", [("Feature names", text)]) if self.data: self.report_data("Data", self.data)
def __init__(self): super().__init__() box = gui.vBox(self.mainArea, True, margin=0) self.graph = OWScatterPlotGraph(self, box, "ScatterPlot") box.layout().addWidget(self.graph.plot_widget) plot = self.graph.plot_widget axispen = QPen(self.palette().color(QPalette.Text)) axis = plot.getAxis("bottom") axis.setPen(axispen) axis = plot.getAxis("left") axis.setPen(axispen) self.data = None # Orange.data.Table self.subset_data = None # Orange.data.Table self.sql_data = None # Orange.data.sql.table.SqlTable self.attribute_selection_list = None # list of Orange.data.Variable self.__timer = QTimer(self, interval=1200) self.__timer.timeout.connect(self.add_data) #: Remember the saved state to restore self.__pending_selection_restore = self.selection_group self.selection_group = None common_options = dict( labelWidth=50, orientation=Qt.Horizontal, sendSelectedValue=True, valueType=str) box = gui.vBox(self.controlArea, "Axis Data") dmod = DomainModel self.xy_model = DomainModel(dmod.MIXED, valid_types=dmod.PRIMITIVE) self.cb_attr_x = gui.comboBox( box, self, "attr_x", label="Axis x:", callback=self.update_attr, model=self.xy_model, **common_options) self.cb_attr_y = gui.comboBox( box, self, "attr_y", label="Axis y:", callback=self.update_attr, model=self.xy_model, **common_options) vizrank_box = gui.hBox(box) gui.separator(vizrank_box, width=common_options["labelWidth"]) self.vizrank, self.vizrank_button = ScatterPlotVizRank.add_vizrank( vizrank_box, self, "Find Informative Projections", self.set_attr) gui.separator(box) g = self.graph.gui g.add_widgets([g.JitterSizeSlider, g.JitterNumericValues], box) self.sampling = gui.auto_commit( self.controlArea, self, "auto_sample", "Sample", box="Sampling", callback=self.switch_sampling, commit=lambda: self.add_data(1)) self.sampling.setVisible(False) g.point_properties_box(self.controlArea) self.models = [self.xy_model] + g.points_models box_plot_prop = gui.vBox(self.controlArea, "Plot Properties") g.add_widgets([g.ShowLegend, g.ShowGridLines, g.ToolTipShowsAll, g.ClassDensity, g.RegressionLine, g.LabelOnlySelected], box_plot_prop) self.graph.box_zoom_select(self.controlArea) self.controlArea.layout().addStretch(100) self.icons = gui.attributeIconDict p = self.graph.plot_widget.palette() self.graph.set_palette(p) gui.auto_commit(self.controlArea, self, "auto_send_selection", "Send Selection", "Send Automatically") self.graph.zoom_actions(self)
class LineScanPlot(QWidget, OWComponent, SelectionGroupMixin, ImageColorSettingMixin, ImageZoomMixin): attr_x = ContextSetting(None) gamma = Setting(0) selection_changed = Signal() def __init__(self, parent): QWidget.__init__(self) OWComponent.__init__(self, parent) SelectionGroupMixin.__init__(self) ImageColorSettingMixin.__init__(self) self.parent = parent self.selection_type = SELECTMANY self.saving_enabled = True self.selection_enabled = True self.viewtype = INDIVIDUAL # required bt InteractiveViewBox self.highlighted = None self.data_points = None self.data_imagepixels = None self.plotview = pg.PlotWidget(background="w", viewBox=InteractiveViewBox(self)) self.plot = self.plotview.getPlotItem() self.plot.scene().installEventFilter( HelpEventDelegate(self.help_event, self)) layout = QVBoxLayout() self.setLayout(layout) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().addWidget(self.plotview) self.img = ImageItemNan() self.img.setOpts(axisOrder='row-major') self.plot.addItem(self.img) self.plot.scene().sigMouseMoved.connect(self.plot.vb.mouseMovedEvent) layout = QGridLayout() self.plotview.setLayout(layout) self.button = QPushButton("Menu", self.plotview) self.button.setAutoDefault(False) layout.setRowStretch(1, 1) layout.setColumnStretch(1, 1) layout.addWidget(self.button, 0, 0) view_menu = MenuFocus(self) self.button.setMenu(view_menu) # prepare interface according to the new context self.parent.contextAboutToBeOpened.connect(lambda x: self.init_interface_data(x[0])) self.add_zoom_actions(view_menu) common_options = dict( labelWidth=50, orientation=Qt.Horizontal, sendSelectedValue=True, valueType=str) choose_xy = QWidgetAction(self) box = gui.vBox(self) box.setFocusPolicy(Qt.TabFocus) self.xy_model = DomainModel(DomainModel.METAS | DomainModel.CLASSES, valid_types=DomainModel.PRIMITIVE, placeholder="Position (index)") self.cb_attr_x = gui.comboBox( box, self, "attr_x", label="Axis x:", callback=self.update_attr, model=self.xy_model, **common_options) box.setFocusProxy(self.cb_attr_x) box.layout().addWidget(self.color_settings_box()) choose_xy.setDefaultWidget(box) view_menu.addAction(choose_xy) self.lsx = None # info about the X axis self.lsy = None # info about the Y axis self.data = None self.data_ids = {} def init_interface_data(self, data): same_domain = (self.data and data and data.domain == self.data.domain) if not same_domain: self.init_attr_values(data) def help_event(self, ev): pos = self.plot.vb.mapSceneToView(ev.scenePos()) sel, wavenumber_ind = self._points_at_pos(pos) prepared = [] if sel is not None: prepared.append(str(self.wavenumbers[wavenumber_ind])) for d in self.data[sel]: variables = [v for v in self.data.domain.metas + self.data.domain.class_vars if v not in [self.attr_x]] features = ['{} = {}'.format(attr.name, d[attr]) for attr in variables] features.append('value = {}'.format(d[wavenumber_ind])) prepared.append("\n".join(features)) text = "\n\n".join(prepared) if text: text = ('<span style="white-space:pre">{}</span>' .format(escape(text))) QToolTip.showText(ev.screenPos(), text, widget=self.plotview) return True else: return False def update_attr(self): self.update_view() def init_attr_values(self, data): domain = data.domain if data is not None else None self.xy_model.set_domain(domain) self.attr_x = self.xy_model[0] if self.xy_model else None def set_data(self, data): if data: self.data = data self.data_ids = {e: i for i, e in enumerate(data.ids)} self.restore_selection_settings() else: self.data = None self.data_ids = {} def update_view(self): self.img.clear() self.img.setSelection(None) self.lsx = None self.lsy = None self.wavenumbers = None self.data_xs = None self.data_imagepixels = None if self.data and len(self.data.domain.attributes): if self.attr_x is not None: xat = self.data.domain[self.attr_x] ndom = Domain([xat]) datam = Table(ndom, self.data) coorx = datam.X[:, 0] else: coorx = np.arange(len(self.data)) self.lsx = lsx = values_to_linspace(coorx) self.data_xs = coorx self.wavenumbers = wavenumbers = getx(self.data) self.lsy = lsy = values_to_linspace(wavenumbers) # set data imdata = np.ones((lsy[2], lsx[2])) * float("nan") xindex = index_values(coorx, lsx) yindex = index_values(wavenumbers, lsy) for xind, d in zip(xindex, self.data.X): imdata[yindex, xind] = d self.data_imagepixels = xindex self.img.setImage(imdata, autoLevels=False) self.img.setLevels([0, 1]) self.update_levels() self.update_color_schema() # shift centres of the pixels so that the axes are useful shiftx = _shift(lsx) shifty = _shift(lsy) left = lsx[0] - shiftx bottom = lsy[0] - shifty width = (lsx[1]-lsx[0]) + 2*shiftx height = (lsy[1]-lsy[0]) + 2*shifty self.img.setRect(QRectF(left, bottom, width, height)) self.refresh_img_selection() def refresh_img_selection(self): selected_px = np.zeros((self.lsy[2], self.lsx[2]), dtype=np.uint8) selected_px[:, self.data_imagepixels] = self.selection_group self.img.setSelection(selected_px) def make_selection(self, selected, add): """Add selected indices to the selection.""" add_to_group, add_group, remove = selection_modifiers() if self.data and self.lsx and self.lsy: if add_to_group: # both keys - need to test it before add_group selnum = np.max(self.selection_group) elif add_group: selnum = np.max(self.selection_group) + 1 elif remove: selnum = 0 else: self.selection_group *= 0 selnum = 1 if selected is not None: self.selection_group[selected] = selnum self.refresh_img_selection() self.prepare_settings_for_saving() self.selection_changed.emit() def _points_at_pos(self, pos): if self.data and self.lsx and self.lsy: x, y = pos.x(), pos.y() x_distance = np.abs(self.data_xs - x) sel = (x_distance < _shift(self.lsx)) wavenumber_distance = np.abs(self.wavenumbers - y) wavenumber_ind = np.argmin(wavenumber_distance) return sel, wavenumber_ind return None, None def select_by_click(self, pos, add): sel, _ = self._points_at_pos(pos) self.make_selection(sel, add)
class OWQuickSelect(widget.OWWidget): name = 'Quick Select' icon = 'icons/QuickSelect.svg' description = 'Select instances with specific feature value.' class Inputs: data = widget.Input("Data", Table) class Outputs: matching = widget.Output("Matching Data", Table, default=True) unmatched = widget.Output("Unmatched Data", Table) annotated = widget.Output(ANNOTATED_DATA_SIGNAL_NAME, Table) class Error(widget.OWWidget.Error): no_categorical = Msg("No categorical variables") want_main_area = False resizing_enabled = False settingsHandler = settings.DomainContextHandler() variable = settings.ContextSetting(None) pending_value = settings.ContextSetting("") def __init__(self): super().__init__() # This is not a setting because openContext cannot retrieve it before # filling the combo. Settings store this as pending_value self.value = "" self.data = None self.n_matched = None form = QFormLayout() gui.widgetBox(self.controlArea, orientation=form) self.var_model = DomainModel(order=DomainModel.MIXED, valid_types=DiscreteVariable) var_combo = gui.comboBox(None, self, "variable", contentsLength=50, model=self.var_model, callback=self._on_variable_changed) self.value_model = PyListModel() value_combo = gui.comboBox(None, self, "value", label="Value: ", model=self.value_model, callback=self._on_value_changed, contentsLength=50, sizePolicy=(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)) form.addRow("Variable:", var_combo) form.addRow("Value:", value_combo) @Inputs.data def set_data(self, data): self.closeContext() self.data = data self.Error.no_categorical.clear() # TODO: Check that contexts are retrieved properly, also when removing # and re-adding a connection if data: self.var_model.set_domain(data.domain) self.info.set_input_summary(len(data), format_summary_details(data)) if not self.var_model.rowCount(): self.data = None self.Error.no_categorical() else: self.var_model.set_domain(None) self.info.set_input_summary(self.info.NoInput) self.variable = self.var_model[0] if self.data else None self.openContext(self.data) self.set_value_list() if self.variable and self.pending_value in self.variable.values: self.value = self.pending_value self.commit() def set_value_list(self): if self.variable is None: self.value_model.clear() self.value = "" else: self.value_model[:] = self.variable.values self.value = self.value_model[0] if self.variable.values else "" def _on_variable_changed(self): self.set_value_list() self.commit() def _on_value_changed(self): self.pending_value = self.value self.commit() def commit(self): if not (self.data and self.variable and self.value): annotated = matching = unmatched = None self.n_matched = None else: column = self.data.get_column_view(self.variable)[0] valind = self.variable.values.index(self.value) mask = column == valind annotated = create_annotated_table(self.data, np.flatnonzero(mask)) matching = self.data[mask] unmatched = self.data[~mask] self.n_matched = len(matching) self.Outputs.matching.send(matching) self.Outputs.unmatched.send(unmatched) self.Outputs.annotated.send(annotated) def send_report(self): if not self.data: return self.report_items( "", [("Filter", f"{self.variable.name} = '{self.value}'"), ("Matching instances", f"{self.n_matched} (out of {len(self.data)})")])
def __init__(self): super().__init__() self.map = map = LeafletMap(self) # type: LeafletMap self.mainArea.layout().addWidget(map) self.selection = None self.data = None self.learner = None def selectionChanged(indices): self.selection = self.data[indices] if self.data is not None and indices else None self._indices = indices self.commit() map.selectionChanged.connect(selectionChanged) def _set_map_provider(): map.set_map_provider(self.TILE_PROVIDERS[self.tile_provider]) box = gui.vBox(self.controlArea, 'Map') gui.comboBox(box, self, 'tile_provider', orientation=Qt.Horizontal, label='Map:', items=tuple(self.TILE_PROVIDERS.keys()), sendSelectedValue=True, callback=_set_map_provider) self._latlon_model = DomainModel( parent=self, valid_types=ContinuousVariable) self._class_model = DomainModel( parent=self, placeholder='(None)', valid_types=DomainModel.PRIMITIVE) self._color_model = DomainModel( parent=self, placeholder='(Same color)', valid_types=DomainModel.PRIMITIVE) self._shape_model = DomainModel( parent=self, placeholder='(Same shape)', valid_types=DiscreteVariable) self._size_model = DomainModel( parent=self, placeholder='(Same size)', valid_types=ContinuousVariable) self._label_model = DomainModel( parent=self, placeholder='(No labels)') def _set_lat_long(): self.map.set_data(self.data, self.lat_attr, self.lon_attr) self.train_model() self._combo_lat = combo = gui.comboBox( box, self, 'lat_attr', orientation=Qt.Horizontal, label='Latitude:', sendSelectedValue=True, callback=_set_lat_long) combo.setModel(self._latlon_model) self._combo_lon = combo = gui.comboBox( box, self, 'lon_attr', orientation=Qt.Horizontal, label='Longitude:', sendSelectedValue=True, callback=_set_lat_long) combo.setModel(self._latlon_model) def _toggle_legend(): self.map.toggle_legend(self.show_legend) gui.checkBox(box, self, 'show_legend', label='Show legend', callback=_toggle_legend) box = gui.vBox(self.controlArea, 'Overlay') self._combo_class = combo = gui.comboBox( box, self, 'class_attr', orientation=Qt.Horizontal, label='Target:', sendSelectedValue=True, callback=self.train_model ) self.controls.class_attr.setModel(self._class_model) self.set_learner(self.learner) box = gui.vBox(self.controlArea, 'Points') self._combo_color = combo = gui.comboBox( box, self, 'color_attr', orientation=Qt.Horizontal, label='Color:', sendSelectedValue=True, callback=lambda: self.map.set_marker_color(self.color_attr)) combo.setModel(self._color_model) self._combo_label = combo = gui.comboBox( box, self, 'label_attr', orientation=Qt.Horizontal, label='Label:', sendSelectedValue=True, callback=lambda: self.map.set_marker_label(self.label_attr)) combo.setModel(self._label_model) self._combo_shape = combo = gui.comboBox( box, self, 'shape_attr', orientation=Qt.Horizontal, label='Shape:', sendSelectedValue=True, callback=lambda: self.map.set_marker_shape(self.shape_attr)) combo.setModel(self._shape_model) self._combo_size = combo = gui.comboBox( box, self, 'size_attr', orientation=Qt.Horizontal, label='Size:', sendSelectedValue=True, callback=lambda: self.map.set_marker_size(self.size_attr)) combo.setModel(self._size_model) def _set_opacity(): map.set_marker_opacity(self.opacity) def _set_zoom(): map.set_marker_size_coefficient(self.zoom) def _set_jittering(): map.set_jittering(self.jittering) def _set_clustering(): map.set_clustering(self.cluster_points) self._opacity_slider = gui.hSlider( box, self, 'opacity', None, 1, 100, 5, label='Opacity:', labelFormat=' %d%%', callback=_set_opacity) self._zoom_slider = gui.valueSlider( box, self, 'zoom', None, values=(20, 50, 100, 200, 300, 400, 500, 700, 1000), label='Symbol size:', labelFormat=' %d%%', callback=_set_zoom) self._jittering = gui.valueSlider( box, self, 'jittering', label='Jittering:', values=(0, .5, 1, 2, 5), labelFormat=' %.1f%%', ticks=True, callback=_set_jittering) self._clustering_check = gui.checkBox( box, self, 'cluster_points', label='Cluster points', callback=_set_clustering) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, 'autocommit', 'Send Selection') QTimer.singleShot(0, _set_map_provider) QTimer.singleShot(0, _toggle_legend) QTimer.singleShot(0, _set_opacity) QTimer.singleShot(0, _set_zoom) QTimer.singleShot(0, _set_jittering) QTimer.singleShot(0, _set_clustering)
class OWMap(widget.OWWidget): name = 'Geo Map' description = 'Show data points on a world map.' icon = "icons/Map.svg" inputs = [("Data", Table, "set_data", widget.Default), ("Data Subset", Table, "set_subset"), ("Learner", Learner, "set_learner")] outputs = [("Selected Data", Table, widget.Default), (ANNOTATED_DATA_SIGNAL_NAME, Table)] settingsHandler = settings.DomainContextHandler() want_main_area = True autocommit = settings.Setting(True) tile_provider = settings.Setting('Black and white') lat_attr = settings.ContextSetting('') lon_attr = settings.ContextSetting('') class_attr = settings.ContextSetting('(None)') color_attr = settings.ContextSetting('') label_attr = settings.ContextSetting('') shape_attr = settings.ContextSetting('') size_attr = settings.ContextSetting('') opacity = settings.Setting(100) zoom = settings.Setting(100) jittering = settings.Setting(0) cluster_points = settings.Setting(False) show_legend = settings.Setting(True) TILE_PROVIDERS = OrderedDict(( ('Black and white', 'OpenStreetMap.BlackAndWhite'), ('OpenStreetMap', 'OpenStreetMap.Mapnik'), ('Topographic', 'Thunderforest.OpenCycleMap'), ('Topographic 2', 'Thunderforest.Outdoors'), ('Satellite', 'Esri.WorldImagery'), ('Print', 'Stamen.TonerLite'), ('Dark', 'CartoDB.DarkMatter'), ('Watercolor', 'Stamen.Watercolor'), )) class Error(widget.OWWidget.Error): model_error = widget.Msg("Error predicting: {}") learner_error = widget.Msg("Error modelling: {}") UserAdviceMessages = [ widget.Message( 'Select markers by holding <b><kbd>Shift</kbd></b> key and dragging ' 'a rectangle around them. Clear the selection by clicking anywhere.', 'shift-selection') ] graph_name = "map" def __init__(self): super().__init__() self.map = map = LeafletMap(self) # type: LeafletMap self.mainArea.layout().addWidget(map) self.selection = None self.data = None self.learner = None def selectionChanged(indices): self.selection = self.data[indices] if self.data is not None and indices else None self._indices = indices self.commit() map.selectionChanged.connect(selectionChanged) def _set_map_provider(): map.set_map_provider(self.TILE_PROVIDERS[self.tile_provider]) box = gui.vBox(self.controlArea, 'Map') gui.comboBox(box, self, 'tile_provider', orientation=Qt.Horizontal, label='Map:', items=tuple(self.TILE_PROVIDERS.keys()), sendSelectedValue=True, callback=_set_map_provider) self._latlon_model = DomainModel( parent=self, valid_types=ContinuousVariable) self._class_model = DomainModel( parent=self, placeholder='(None)', valid_types=DomainModel.PRIMITIVE) self._color_model = DomainModel( parent=self, placeholder='(Same color)', valid_types=DomainModel.PRIMITIVE) self._shape_model = DomainModel( parent=self, placeholder='(Same shape)', valid_types=DiscreteVariable) self._size_model = DomainModel( parent=self, placeholder='(Same size)', valid_types=ContinuousVariable) self._label_model = DomainModel( parent=self, placeholder='(No labels)') def _set_lat_long(): self.map.set_data(self.data, self.lat_attr, self.lon_attr) self.train_model() self._combo_lat = combo = gui.comboBox( box, self, 'lat_attr', orientation=Qt.Horizontal, label='Latitude:', sendSelectedValue=True, callback=_set_lat_long) combo.setModel(self._latlon_model) self._combo_lon = combo = gui.comboBox( box, self, 'lon_attr', orientation=Qt.Horizontal, label='Longitude:', sendSelectedValue=True, callback=_set_lat_long) combo.setModel(self._latlon_model) def _toggle_legend(): self.map.toggle_legend(self.show_legend) gui.checkBox(box, self, 'show_legend', label='Show legend', callback=_toggle_legend) box = gui.vBox(self.controlArea, 'Overlay') self._combo_class = combo = gui.comboBox( box, self, 'class_attr', orientation=Qt.Horizontal, label='Target:', sendSelectedValue=True, callback=self.train_model ) self.controls.class_attr.setModel(self._class_model) self.set_learner(self.learner) box = gui.vBox(self.controlArea, 'Points') self._combo_color = combo = gui.comboBox( box, self, 'color_attr', orientation=Qt.Horizontal, label='Color:', sendSelectedValue=True, callback=lambda: self.map.set_marker_color(self.color_attr)) combo.setModel(self._color_model) self._combo_label = combo = gui.comboBox( box, self, 'label_attr', orientation=Qt.Horizontal, label='Label:', sendSelectedValue=True, callback=lambda: self.map.set_marker_label(self.label_attr)) combo.setModel(self._label_model) self._combo_shape = combo = gui.comboBox( box, self, 'shape_attr', orientation=Qt.Horizontal, label='Shape:', sendSelectedValue=True, callback=lambda: self.map.set_marker_shape(self.shape_attr)) combo.setModel(self._shape_model) self._combo_size = combo = gui.comboBox( box, self, 'size_attr', orientation=Qt.Horizontal, label='Size:', sendSelectedValue=True, callback=lambda: self.map.set_marker_size(self.size_attr)) combo.setModel(self._size_model) def _set_opacity(): map.set_marker_opacity(self.opacity) def _set_zoom(): map.set_marker_size_coefficient(self.zoom) def _set_jittering(): map.set_jittering(self.jittering) def _set_clustering(): map.set_clustering(self.cluster_points) self._opacity_slider = gui.hSlider( box, self, 'opacity', None, 1, 100, 5, label='Opacity:', labelFormat=' %d%%', callback=_set_opacity) self._zoom_slider = gui.valueSlider( box, self, 'zoom', None, values=(20, 50, 100, 200, 300, 400, 500, 700, 1000), label='Symbol size:', labelFormat=' %d%%', callback=_set_zoom) self._jittering = gui.valueSlider( box, self, 'jittering', label='Jittering:', values=(0, .5, 1, 2, 5), labelFormat=' %.1f%%', ticks=True, callback=_set_jittering) self._clustering_check = gui.checkBox( box, self, 'cluster_points', label='Cluster points', callback=_set_clustering) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, 'autocommit', 'Send Selection') QTimer.singleShot(0, _set_map_provider) QTimer.singleShot(0, _toggle_legend) QTimer.singleShot(0, _set_opacity) QTimer.singleShot(0, _set_zoom) QTimer.singleShot(0, _set_jittering) QTimer.singleShot(0, _set_clustering) autocommit = settings.Setting(True) def __del__(self): self.progressBarFinished(None) self.map = None def commit(self): self.send('Selected Data', self.selection) self.send(ANNOTATED_DATA_SIGNAL_NAME, create_annotated_table(self.data, self._indices)) def set_data(self, data): self.data = data self.closeContext() if data is None or not len(data): return self.clear() all_vars = list(chain(self.data.domain.variables, self.data.domain.metas)) domain = data is not None and data.domain for model in (self._latlon_model, self._class_model, self._color_model, self._shape_model, self._size_model, self._label_model): model.set_domain(domain) def _find_lat_lon(): lat_attr = next( (attr for attr in all_vars if attr.is_continuous and attr.name.lower().startswith(('latitude', 'lat'))), None) lon_attr = next( (attr for attr in all_vars if attr.is_continuous and attr.name.lower().startswith(('longitude', 'lng', 'long', 'lon'))), None) def _all_between(vals, min, max): return np.all((min <= vals) & (vals <= max)) if not lat_attr: for attr in all_vars: if attr.is_continuous: values = np.nan_to_num(data.get_column_view(attr)[0].astype(float)) if _all_between(values, -90, 90): lat_attr = attr break if not lon_attr: for attr in all_vars: if attr.is_continuous: values = np.nan_to_num(data.get_column_view(attr)[0].astype(float)) if _all_between(values, -180, 180): lon_attr = attr break return lat_attr, lon_attr lat, lon = _find_lat_lon() if lat or lon: self._combo_lat.setCurrentIndex(-1 if lat is None else self._latlon_model.indexOf(lat)) self._combo_lon.setCurrentIndex(-1 if lat is None else self._latlon_model.indexOf(lon)) self.lat_attr = lat.name self.lon_attr = lon.name if data.domain.class_var: self.color_attr = data.domain.class_var.name elif len(self._color_model): self._combo_color.setCurrentIndex(0) if len(self._shape_model): self._combo_shape.setCurrentIndex(0) if len(self._size_model): self._combo_size.setCurrentIndex(0) if len(self._label_model): self._combo_label.setCurrentIndex(0) if len(self._class_model): self._combo_class.setCurrentIndex(0) self.openContext(data) self.map.set_data(self.data, self.lat_attr, self.lon_attr) self.map.set_marker_color(self.color_attr, update=False) self.map.set_marker_label(self.label_attr, update=False) self.map.set_marker_shape(self.shape_attr, update=False) self.map.set_marker_size(self.size_attr, update=True) def set_subset(self, subset): self.map.set_subset_ids(subset.ids if subset is not None else np.array([])) def handleNewSignals(self): super().handleNewSignals() self.train_model() def set_learner(self, learner): self.learner = learner self.controls.class_attr.setEnabled(learner is not None) self.controls.class_attr.setToolTip( 'Needs a Learner input for modelling.' if learner is None else '') def train_model(self): model = None self.Error.clear() if self.data and self.learner and self.class_attr != '(None)': domain = self.data.domain if self.lat_attr and self.lon_attr and self.class_attr in domain: domain = Domain([domain[self.lat_attr], domain[self.lon_attr]], [domain[self.class_attr]]) # I am retarded train = self.data.transform(domain) try: model = self.learner(train) except Exception as e: self.Error.learner_error(e) self.map.set_model(model) def disable_some_controls(self, disabled): tooltip = ( "Available when the zoom is close enough to have " "<{} points in the viewport.".format(self.map.N_POINTS_PER_ITER) if disabled else '') for widget in (self._combo_label, self._combo_shape, self._clustering_check): widget.setDisabled(disabled) widget.setToolTip(tooltip) def clear(self): self.map.set_data(None, '', '') for model in (self._latlon_model, self._class_model, self._color_model, self._shape_model, self._size_model, self._label_model): model.set_domain(None) self.lat_attr = self.lon_attr = self.class_attr = self.color_attr = \ self.label_attr = self.shape_attr = self.size_attr = ''
def test_filtering(self): cont = [ContinuousVariable(n) for n in "abc"] disc = [DiscreteVariable(n) for n in "def"] attrs = cont + disc model = DomainModel(valid_types=(ContinuousVariable, )) model.set_domain(Domain(attrs)) self.assertEqual(list(model), cont) model = DomainModel(valid_types=(DiscreteVariable, )) model.set_domain(Domain(attrs)) self.assertEqual(list(model), disc) disc[0].attributes["hidden"] = True model.set_domain(Domain(attrs)) self.assertEqual(list(model), disc[1:]) model = DomainModel(valid_types=(DiscreteVariable, ), skip_hidden_vars=False) model.set_domain(Domain(attrs)) self.assertEqual(list(model), disc)
def test_separators(self): attrs = [ContinuousVariable(n) for n in "abg"] classes = [ContinuousVariable(n) for n in "deh"] metas = [ContinuousVariable(n) for n in "ijf"] model = DomainModel() sep = [model.Separator] model.set_domain(Domain(attrs, classes, metas)) self.assertEqual(list(model), classes + sep + metas + sep + attrs) model = DomainModel() model.set_domain(Domain(attrs, [], metas)) self.assertEqual(list(model), metas + sep + attrs) model = DomainModel() model.set_domain(Domain([], [], metas)) self.assertEqual(list(model), metas) model = DomainModel(placeholder="foo") model.set_domain(Domain([], [], metas)) self.assertEqual(list(model), [None] + sep + metas) model = DomainModel(placeholder="foo") model.set_domain(Domain(attrs, [], metas)) self.assertEqual(list(model), [None] + sep + metas + sep + attrs)
def __init__(self): super().__init__() self.data = None # The following lists are of the same length as self.active_rules #: list of pairs with counts of matches for each patter when the # patterns are applied in order and when applied on the entire set, # disregarding the preceding patterns self.match_counts = [] #: list of list of QLineEdit: line edit pairs for each pattern self.line_edits = [] #: list of QPushButton: list of remove buttons self.remove_buttons = [] #: list of list of QLabel: pairs of labels with counts self.counts = [] gui.lineEdit(self.controlArea, self, "class_name", orientation=Qt.Horizontal, box="New Class Name") variable_select_box = gui.vBox(self.controlArea, "Match by Substring") combo = gui.comboBox(variable_select_box, self, "attribute", label="From column:", orientation=Qt.Horizontal, searchable=True, callback=self.update_rules, model=DomainModel(valid_types=(StringVariable, DiscreteVariable))) # Don't use setSizePolicy keyword argument here: it applies to box, # not the combo combo.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred) patternbox = gui.vBox(variable_select_box) #: QWidget: the box that contains the remove buttons, line edits and # count labels. The lines are added and removed dynamically. self.rules_box = rules_box = QGridLayout() rules_box.setSpacing(4) rules_box.setContentsMargins(4, 4, 4, 4) self.rules_box.setColumnMinimumWidth(1, 70) self.rules_box.setColumnMinimumWidth(0, 10) self.rules_box.setColumnStretch(0, 1) self.rules_box.setColumnStretch(1, 1) self.rules_box.setColumnStretch(2, 100) rules_box.addWidget(QLabel("Name"), 0, 1) rules_box.addWidget(QLabel("Substring"), 0, 2) rules_box.addWidget(QLabel("Count"), 0, 3, 1, 2) self.update_rules() widget = QWidget(patternbox) widget.setLayout(rules_box) patternbox.layout().addWidget(widget) box = gui.hBox(patternbox) gui.rubber(box) gui.button(box, self, "+", callback=self.add_row, autoDefault=False, width=34, sizePolicy=(QSizePolicy.Maximum, QSizePolicy.Maximum)) optionsbox = gui.vBox(self.controlArea, "Options") gui.checkBox(optionsbox, self, "match_beginning", "Match only at the beginning", callback=self.options_changed) gui.checkBox(optionsbox, self, "case_sensitive", "Case sensitive", callback=self.options_changed) gui.rubber(self.controlArea) gui.button(self.buttonsArea, self, "Apply", callback=self.apply) # TODO: Resizing upon changing the number of rules does not work self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum)
def __init__(self): super().__init__() box = gui.vBox(self.mainArea, True, margin=0) self.graph = OWScatterPlotGraph(self, box, "ScatterPlot") box.layout().addWidget(self.graph.plot_widget) plot = self.graph.plot_widget axispen = QPen(self.palette().color(QPalette.Text)) axis = plot.getAxis("bottom") axis.setPen(axispen) axis = plot.getAxis("left") axis.setPen(axispen) self.data = None # Orange.data.Table self.subset_data = None # Orange.data.Table self.data_metas_X = None # self.data, where primitive metas are moved to X self.sql_data = None # Orange.data.sql.table.SqlTable self.attribute_selection_list = None # list of Orange.data.Variable self.__timer = QTimer(self, interval=1200) self.__timer.timeout.connect(self.add_data) common_options = dict(labelWidth=50, orientation=Qt.Horizontal, sendSelectedValue=True, valueType=str) box = gui.vBox(self.controlArea, "Axis Data") dmod = DomainModel self.xy_model = DomainModel(dmod.MIXED, valid_types=dmod.PRIMITIVE) self.cb_attr_x = gui.comboBox(box, self, "attr_x", label="Axis x:", callback=self.update_attr, model=self.xy_model, **common_options) self.cb_attr_y = gui.comboBox(box, self, "attr_y", label="Axis y:", callback=self.update_attr, model=self.xy_model, **common_options) vizrank_box = gui.hBox(box) gui.separator(vizrank_box, width=common_options["labelWidth"]) self.vizrank, self.vizrank_button = ScatterPlotVizRank.add_vizrank( vizrank_box, self, "Find Informative Projections", self.set_attr) gui.separator(box) g = self.graph.gui g.add_widgets([g.JitterSizeSlider, g.JitterNumericValues], box) self.sampling = gui.auto_commit(self.controlArea, self, "auto_sample", "Sample", box="Sampling", callback=self.switch_sampling, commit=lambda: self.add_data(1)) self.sampling.setVisible(False) g.point_properties_box(self.controlArea) self.models = [self.xy_model] + g.points_models box_plot_prop = gui.vBox(self.controlArea, "Plot Properties") g.add_widgets([ g.ShowLegend, g.ShowGridLines, g.ToolTipShowsAll, g.ClassDensity, g.RegressionLine, g.LabelOnlySelected ], box_plot_prop) self.graph.box_zoom_select(self.controlArea) self.controlArea.layout().addStretch(100) self.icons = gui.attributeIconDict p = self.graph.plot_widget.palette() self.graph.set_palette(p) gui.auto_commit(self.controlArea, self, "auto_send_selection", "Send Selection", "Send Automatically") self.graph.zoom_actions(self)
class OWChoropleth(widget.OWWidget): name = 'Choropleth Map' description = 'A thematic map in which areas are shaded in proportion ' \ 'to the measurement of the statistical variable being displayed.' icon = "icons/Choropleth.svg" priority = 120 class Inputs: data = Input("Data", Table, default=True) class Outputs: selected_data = Output("Selected Data", Table, default=True) annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table) settingsHandler = settings.DomainContextHandler() want_main_area = True AGG_FUNCS = ( 'Count', 'Count defined', 'Sum', 'Mean', 'Median', 'Mode', 'Max', 'Min', 'Std', ) AGG_FUNCS_TRANSFORM = { 'Count': 'size', 'Count defined': 'count', 'Mode': lambda x: stats.mode(x, nan_policy='omit').mode[0], } AGG_FUNCS_DISCRETE = ('Count', 'Count defined', 'Mode') AGG_FUNCS_CANT_TIME = ('Count', 'Count defined', 'Sum', 'Std') autocommit = settings.Setting(True) lat_attr = settings.ContextSetting('') lon_attr = settings.ContextSetting('') attr = settings.ContextSetting('') agg_func = settings.ContextSetting(AGG_FUNCS[0]) admin = settings.Setting(0) opacity = settings.Setting(70) color_steps = settings.Setting(5) color_quantization = settings.Setting('equidistant') show_labels = settings.Setting(True) show_legend = settings.Setting(True) show_details = settings.Setting(True) selection = settings.ContextSetting([]) class Error(widget.OWWidget.Error): aggregation_discrete = widget.Msg("Only certain types of aggregation defined on categorical attributes: {}") class Warning(widget.OWWidget.Warning): logarithmic_nonpositive = widget.Msg("Logarithmic quantization requires all values > 0. Using 'equidistant' quantization instead.") graph_name = "map" def __init__(self): super().__init__() self.map = map = LeafletChoropleth(self) self.mainArea.layout().addWidget(map) self.selection = [] self.data = None self.latlon = None self.result_min_nonpositive = False self._should_fit_bounds = False def selectionChanged(selection): self._indices = self.ids.isin(selection).nonzero()[0] self.selection = selection self.commit() map.selectionChanged.connect(selectionChanged) box = gui.vBox(self.controlArea, 'Aggregation') self._latlon_model = DomainModel(parent=self, valid_types=ContinuousVariable) self._combo_lat = combo = gui.comboBox( box, self, 'lat_attr', orientation=Qt.Horizontal, label='Latitude:', sendSelectedValue=True, callback=self.aggregate) combo.setModel(self._latlon_model) self._combo_lon = combo = gui.comboBox( box, self, 'lon_attr', orientation=Qt.Horizontal, label='Longitude:', sendSelectedValue=True, callback=self.aggregate) combo.setModel(self._latlon_model) self._combo_attr = combo = gui.comboBox( box, self, 'attr', orientation=Qt.Horizontal, label='Attribute:', sendSelectedValue=True, callback=self.aggregate) combo.setModel(DomainModel(parent=self, valid_types=(ContinuousVariable, DiscreteVariable))) gui.comboBox( box, self, 'agg_func', orientation=Qt.Horizontal, items=self.AGG_FUNCS, label='Aggregation:', sendSelectedValue=True, callback=self.aggregate) self._detail_slider = gui.hSlider( box, self, 'admin', None, 0, 2, 1, label='Administrative level:', labelFormat=' %d', callback=self.aggregate) box = gui.vBox(self.controlArea, 'Visualization') gui.spin(box, self, 'color_steps', 3, 15, 1, label='Color steps:', callback=lambda: self.map.set_color_steps(self.color_steps)) def _set_quantization(): self.Warning.logarithmic_nonpositive( shown=(self.color_quantization.startswith('log') and self.result_min_nonpositive)) self.map.set_quantization(self.color_quantization) gui.comboBox(box, self, 'color_quantization', label='Color quantization:', orientation=Qt.Horizontal, sendSelectedValue=True, items=('equidistant', 'logarithmic', 'quantile', 'k-means'), callback=_set_quantization) self._opacity_slider = gui.hSlider( box, self, 'opacity', None, 20, 100, 5, label='Opacity:', labelFormat=' %d%%', callback=lambda: self.map.set_opacity(self.opacity)) gui.checkBox(box, self, 'show_legend', label='Show legend', callback=lambda: self.map.toggle_legend(self.show_legend)) gui.checkBox(box, self, 'show_labels', label='Show map labels', callback=lambda: self.map.toggle_map_labels(self.show_labels)) gui.checkBox(box, self, 'show_details', label='Show region details in tooltip', callback=lambda: self.map.toggle_tooltip_details(self.show_details)) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, 'autocommit', 'Send Selection') self.map.toggle_legend(self.show_legend) self.map.toggle_map_labels(self.show_labels) self.map.toggle_tooltip_details(self.show_details) self.map.set_quantization(self.color_quantization) self.map.set_color_steps(self.color_steps) self.map.set_opacity(self.opacity) def __del__(self): self.map = None def commit(self): if self.data is not None and self.selection: selected_data = self.data[self._indices] annotated_data = create_annotated_table(self.data, self._indices) else: selected_data = annotated_data = None self.Outputs.selected_data.send(selected_data) self.Outputs.annotated_data.send(annotated_data) @Inputs.data def set_data(self, data): self.data = data self.closeContext() self.clear() if data is None: return self._combo_attr.model().set_domain(data.domain) self._latlon_model.set_domain(data.domain) lat, lon = find_lat_lon(data) if lat or lon: self._combo_lat.setCurrentIndex(-1 if lat is None else self._latlon_model.indexOf(lat)) self._combo_lon.setCurrentIndex(-1 if lat is None else self._latlon_model.indexOf(lon)) self.lat_attr = lat.name if lat else None self.lon_attr = lon.name if lon else None if lat and lon: self.latlon = np.c_[self.data.get_column_view(self.lat_attr)[0], self.data.get_column_view(self.lon_attr)[0]] if data.domain.class_var: self.attr = data.domain.class_var.name else: self.attr = self._combo_attr.itemText(0) self.openContext(data) if self.selection: self.map.preset_region_selection(self.selection) self.aggregate() self.map.set_opacity(self.opacity) if self.isVisible(): self.map.fit_to_bounds() else: self._should_fit_bounds = True def showEvent(self, event): super().showEvent(event) if self._should_fit_bounds: QTimer.singleShot(500, self.map.fit_to_bounds) self._should_fit_bounds = False def aggregate(self): if self.latlon is None or self.attr not in self.data.domain: self.clear(caches=False) return attr = self.data.domain[self.attr] if attr.is_discrete and self.agg_func not in self.AGG_FUNCS_DISCRETE: self.Error.aggregation_discrete(', '.join(map(str.lower, self.AGG_FUNCS_DISCRETE))) self.Warning.logarithmic_nonpositive.clear() self.clear(caches=False) return else: self.Error.aggregation_discrete.clear() try: regions, adm0, result, self.map.bounds = \ self.get_grouped(self.lat_attr, self.lon_attr, self.admin, self.attr, self.agg_func) except ValueError: # This might happen if widget scheme File→Choropleth, and # some attr is selected in choropleth, and then the same attr # is set to string attr in File and dataset reloaded. # Our "dataflow" arch can suck my balls return # Only show discrete values that are contained in aggregated results discrete_values = [] if attr.is_discrete and not self.agg_func.startswith('Count'): subset = sorted(result.drop_duplicates().dropna().astype(int)) discrete_values = np.array(attr.values)[subset].tolist() discrete_colors = np.array(attr.colors)[subset].tolist() result.replace(subset, list(range(len(subset))), inplace=True) self.result_min_nonpositive = attr.is_continuous and result.min() <= 0 force_quantization = self.color_quantization.startswith('log') and self.result_min_nonpositive self.Warning.logarithmic_nonpositive(shown=force_quantization) repr_time = isinstance(attr, TimeVariable) and self.agg_func not in self.AGG_FUNCS_CANT_TIME self.map.exposeObject( 'results', dict(discrete=discrete_values, colors=[color_to_hex(i) for i in (discrete_colors if discrete_values else ((0, 0, 255), (255, 255, 0)) if attr.is_discrete else attr.colors[:-1])], # ??? regions=list(adm0), attr=attr.name, have_nonpositive=self.result_min_nonpositive or bool(discrete_values), values=result.to_dict(), repr_vals=result.map(attr.repr_val).to_dict() if repr_time else {}, minmax=([result.min(), result.max()] if attr.is_discrete and not discrete_values else [attr.repr_val(result.min()), attr.repr_val(result.max())] if repr_time or not discrete_values else []))) self.map.evalJS('replot();') @memoize_method(3) def get_regions(self, lat_attr, lon_attr, admin): latlon = np.c_[self.data.get_column_view(lat_attr)[0], self.data.get_column_view(lon_attr)[0]] regions = latlon2region(latlon, admin) adm0 = ({'0'} if admin == 0 else {'1-' + a3 for a3 in (i.get('adm0_a3') for i in regions) if a3} if admin == 1 else {('2-' if a3 in ADMIN2_COUNTRIES else '1-') + a3 for a3 in (i.get('adm0_a3') for i in regions) if a3}) ids = [i.get('_id') for i in regions] self.ids = pd.Series(ids) regions = set(ids) - {None} bounds = get_bounding_rect(regions) if regions else None return regions, ids, adm0, bounds @memoize_method(6) def get_grouped(self, lat_attr, lon_attr, admin, attr, agg_func): log.debug('Grouping %s(%s) by (%s, %s; admin%d)', agg_func, attr, lat_attr, lon_attr, admin) regions, ids, adm0, bounds = self.get_regions(lat_attr, lon_attr, admin) attr = self.data.domain[attr] result = pd.Series(self.data.get_column_view(attr)[0], dtype=float)\ .groupby(ids)\ .agg(self.AGG_FUNCS_TRANSFORM.get(agg_func, agg_func.lower())) return regions, adm0, result, bounds def clear(self, caches=True): if caches: try: self.get_regions.cache_clear() self.get_grouped.cache_clear() except AttributeError: pass # back-compat https://github.com/biolab/orange3/pull/2229 self.selection = [] self.map.exposeObject('results', {}) self.map.evalJS('replot();')
def __init__(self): super().__init__() self.stats = [] self.dataset = None self.posthoc_lines = [] self.label_txts = self.mean_labels = self.boxes = self.labels = \ self.label_txts_all = self.attr_labels = self.order = [] self.scale_x = self.scene_min_x = self.scene_width = 0 self.label_width = 0 self.attrs = VariableListModel() view = gui.listView( self.controlArea, self, "attribute", box="Variable", model=self.attrs, callback=self.attr_changed) view.setMinimumSize(QSize(30, 30)) # Any other policy than Ignored will let the QListBox's scrollbar # set the minimal height (see the penultimate paragraph of # http://doc.qt.io/qt-4.8/qabstractscrollarea.html#addScrollBarWidget) view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored) gui.separator(view.box, 6, 6) self.cb_order = gui.checkBox( view.box, self, "order_by_importance", "Order by relevance", tooltip="Order by 𝜒² or ANOVA over the subgroups", callback=self.apply_sorting) self.group_vars = DomainModel( placeholder="None", separators=False, valid_types=Orange.data.DiscreteVariable) self.group_view = view = gui.listView( self.controlArea, self, "group_var", box="Subgroups", model=self.group_vars, callback=self.grouping_changed) view.setEnabled(False) view.setMinimumSize(QSize(30, 30)) # See the comment above view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored) # TODO: move Compare median/mean to grouping box # The vertical size policy is needed to let only the list views expand self.display_box = gui.vBox( self.controlArea, "Display", sizePolicy=(QSizePolicy.Minimum, QSizePolicy.Maximum), addSpace=False) gui.checkBox(self.display_box, self, "show_annotations", "Annotate", callback=self.display_changed) self.compare_rb = gui.radioButtonsInBox( self.display_box, self, 'compare', btnLabels=["No comparison", "Compare medians", "Compare means"], callback=self.layout_changed) # The vertical size policy is needed to let only the list views expand self.stretching_box = box = gui.vBox( self.controlArea, box="Display", sizePolicy=(QSizePolicy.Minimum, QSizePolicy.Fixed)) self.stretching_box.sizeHint = self.display_box.sizeHint gui.checkBox( box, self, 'stretched', "Stretch bars", callback=self.display_changed) gui.checkBox( box, self, 'show_labels', "Show box labels", callback=self.display_changed) self.sort_cb = gui.checkBox( box, self, 'sort_freqs', "Sort by subgroup frequencies", callback=self.display_changed) gui.rubber(box) gui.auto_commit(self.controlArea, self, "auto_commit", "Send Selection", "Send Automatically") gui.vBox(self.mainArea, addSpace=True) self.box_scene = QGraphicsScene() self.box_scene.selectionChanged.connect(self.commit) self.box_view = QGraphicsView(self.box_scene) self.box_view.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform) self.box_view.viewport().installEventFilter(self) self.mainArea.layout().addWidget(self.box_view) e = gui.hBox(self.mainArea, addSpace=False) self.infot1 = gui.widgetLabel(e, "<center>No test results.</center>") self.mainArea.setMinimumWidth(300) self.stats = self.dist = self.conts = [] self.is_continuous = False self.update_display_box()
class OWNormalization(widget.OWWidget): name = 'Normalize' description = 'Normalization of single cell count data' icon = 'icons/Normalization.svg' priority = 160 DEFAULT_CELL_NORM = "(One group per cell)" SCORERS = (ScBatchScorer, ) LINK_FUNCTIONS = sorted(LINKS.keys()) class Inputs: data = Input("Data", Table) class Outputs: data = Output("Data", Table) preprocessor = Output("Preprocessor", Preprocess) # Widget settings want_main_area = False resizing_enabled = False settingsHandler = settings.DomainContextHandler() autocommit = settings.Setting(True, schema_only=True) # Settings (basic preprocessor) normalize_cells = settings.Setting(True, schema_only=True) selected_attr_index = settings.Setting(0, schema_only=True) log_check = settings.Setting(False, schema_only=True) log_base = settings.Setting(2, schema_only=True) bin_check = settings.Setting(False, schema_only=True) bin_thresh = settings.Setting(0, schema_only=True) # Settings (batch preprocessor) batch_link_index = settings.Setting(0, schema_only=True) batch_vars_selected = settings.Setting([], schema_only=True) batch_vars_all = [] batch_vars_names = [] # Preprocessors pp = None pp_batch = None # ranksView global settings sorting = settings.Setting((0, Qt.DescendingOrder)) def __init__(self): self.data = None self.info = gui.label(self.controlArea, self, "No data on input", box="Info") # Library / group variable box0 = gui.vBox( self.controlArea, "Data from multiple libraries") self.normalize_check = gui.checkBox(box0, self, "normalize_cells", "Normalize median expressions on cell groups:", callback=self.on_changed, addSpace=True) self.attrs_model = DomainModel( placeholder=self.DEFAULT_CELL_NORM, order=(DomainModel.CLASSES, DomainModel.METAS), valid_types=DiscreteVariable) self.combo_attrs = gui.comboBox( box0, self, 'selected_attr_index', callback=self.on_changed) # Steps and parameters box1 = gui.widgetBox(self.controlArea, 'Further steps and parameters') gui.spin(box1, self, "log_base", 2.0, 10.0, label="Log(1 + x) transform, base: ", checked="log_check", alignment=Qt.AlignRight, callback=self.on_changed, checkCallback=self.on_changed, controlWidth=60) gui.spin(box1, self, "bin_thresh", 0, 1000.0, label="Binarization threshold (>): ", checked="bin_check", alignment=Qt.AlignRight, callback=self.on_changed, checkCallback=self.on_changed, controlWidth=60) # Batch effects - link function box2 = gui.vBox(self.controlArea, "Variables to regress out (batch effects)") self.batch_link_combo = gui.comboBox( box2, self, 'batch_link_index', callback=self.on_changed, items=self.LINK_FUNCTIONS) # Batch effects - variables self.ranksModel = model = TableModel(parent=self) # type: TableModel self.ranksView = view = TableView(self) # type: TableView box2.layout().addWidget(view) view.setModel(model) view.setColumnWidth(0, 30) view.setSelectionMode(TableView.MultiSelection) view.selectionModel().selectionChanged.connect(self.on_select) view.horizontalHeader().sectionClicked.connect(self.on_header_click) # Autocommit gui.auto_commit(self.controlArea, self, 'autocommit', '&Apply') ### Called once at the arrival of new data ### @Inputs.data def set_data(self, data): self.data = data if self.data is None: self.selected_attr_index = 0 self.attrs_model.set_domain(None) self.batch_vars_selected.clear() self.ranksModel.clear() self.ranksModel.resetSorting(True) self.info.setText("No data on input") self.commit() return self.info.setText("%d cells, %d features." % (len(data), len(data.domain.attributes))) self.attrs_model.set_domain(data.domain) self.normalize_check.setEnabled(len(self.attrs_model) > 0) self.combo_attrs.setEnabled(self.normalize_cells) self.combo_attrs.setModel(self.attrs_model) self.set_batch_variables() # Implicit commit self.update_state() def set_batch_variables(self): """ Search for meta variables and classes in new data. """ self.batch_vars_all = [var for var in self.data.domain.metas + self.data.domain.class_vars if isinstance(var, ContinuousVariable) or isinstance(var, DiscreteVariable)] self.batch_vars_names = tuple(a.name for a in self.batch_vars_all) if self.data is not None and len(self.batch_vars_all) == 0: return self.ranksModel.setVerticalHeaderLabels(self.batch_vars_all) self.ranksView.setVHeaderFixedWidthFromLabel(max(self.batch_vars_names, key=len)) ### Event handlers ### def on_changed(self): """ Update graphics, model parameters and commit. """ self.combo_attrs.setEnabled(self.normalize_cells) self.update_state() self.commit() def on_select(self): """ Save indices of attributes in the original, unsorted domain. Warning: this method must not call update_scores; """ selected_rows = self.ranksModel.mapToSourceRows([ i.row() for i in self.ranksView.selectionModel().selectedRows(0)]) self.batch_vars_selected.clear() self.batch_vars_selected.extend([self.batch_vars_all[i].name for i in selected_rows]) self.update_preprocessors() self.commit() def on_header_click(self): """ Store the header states. """ sort_order = self.ranksModel.sortOrder() sort_column = self.ranksModel.sortColumn() - 1 # -1 for '#' (discrete count) column self.sorting = (sort_column, sort_order) ### Updates to model parameters / scores ### def update_state(self): """ Updates preprocessors and scores in the correct order. """ self.update_preprocessors() self.update_scores() def update_selection(self): """ Update selected rows (potentially from loaded scheme) at once.""" sel_model = self.ranksView.selectionModel() ncol = self.ranksModel.columnCount() model = self.ranksModel selection = QItemSelection() selected_rows = [self.batch_vars_names.index(b) for b in self.batch_vars_selected.copy() if b in self.batch_vars_names] if len(selected_rows): for row in model.mapFromSourceRows(selected_rows): selection.append(QItemSelectionRange( model.index(row, 0), model.index(row, ncol - 1))) sel_model.select(selection, QItemSelectionModel.ClearAndSelect) else: self.commit() def update_preprocessors(self): """ Update parameters of processors. """ log_base = self.log_base if self.log_check else None bin_thresh = self.bin_thresh if self.bin_check else None library_var = None selected_attr = self.attrs_model[self.selected_attr_index] batch_link = self.LINK_FUNCTIONS[self.batch_link_index] if self.data is not None and \ self.normalize_cells and \ selected_attr in self.data.domain: library_var = self.data.domain[selected_attr] self.pp = SCNormalizer(equalize_var=library_var, normalize_cells=self.normalize_cells, log_base=log_base, bin_thresh=bin_thresh) self.pp_batch = SCBatchNormalizer(link=batch_link, nonzero_only=batch_link == LINK_LOG, batch_vars=self.batch_vars_selected) def update_scores(self): """ Update scores for current data and preprocessors. """ if self.data is None: self.ranksModel.clear() return method_scores = tuple(self.get_method_scores(method) for method in self.SCORERS) labels = tuple(method.friendly_name for method in self.SCORERS) model_array = np.column_stack( ([len(a.values) if a.is_discrete else np.nan for a in self.batch_vars_all],) + (method_scores if method_scores else ()) ) # Set fixed extreme values self.ranksModel.setExtremesFrom(column=1, values=[0, 1]) # Update, but retain previous selection self.ranksModel.wrap(model_array.tolist()) self.ranksModel.setHorizontalHeaderLabels(('#',) + labels) self.ranksView.setColumnWidth(0, 40) # Rows must be reselected again as ranksModel.wrap resets the selection self.update_selection() # Re-apply sort try: sort_column, sort_order = self.sorting if sort_column < len(labels): self.ranksModel.sort(sort_column + 1, sort_order) # +1 for '#' (discrete count) column self.ranksView.horizontalHeader().setSortIndicator(sort_column + 1, sort_order) except ValueError: pass def get_method_scores(self, method): """ Compute scores for all batch variables. Scores must be computed after applying the first pre-processor. """ assert self.pp is not None estimator = method() data = self.pp(self.data) try: scores = np.array([estimator.score_data(data=data, feature=attr) for attr in self.batch_vars_all]) except ValueError: log.error( "Scorer %s wasn't able to compute scores at all", estimator.name) scores = np.full(len(self.batch_vars_all), np.nan) return scores ### Set output ### def commit(self): """ Update parameters to preprocessors and set output signals. """ data = None if self.data is not None: data = self.pp_batch(self.pp(self.data)) self.Outputs.data.send(data) self.Outputs.preprocessor.send(PreprocessorList([self.pp, self.pp_batch]))
class OWFeatureStatistics(widget.OWWidget): name = 'Feature Statistics' description = 'Show basic statistics for data features.' icon = 'icons/FeatureStatistics.svg' class Inputs: data = Input('Data', Table, default=True) class Outputs: reduced_data = Output('Reduced Data', Table, default=True) statistics = Output('Statistics', Table) want_main_area = True buttons_area_orientation = Qt.Vertical settingsHandler = DomainContextHandler() auto_commit = ContextSetting(True) color_var = ContextSetting(None) # type: Optional[Variable] # filter_string = ContextSetting('') sorting = ContextSetting((0, Qt.DescendingOrder)) selected_rows = ContextSetting([]) def __init__(self): super().__init__() self.data = None # type: Optional[Table] # Information panel info_box = gui.vBox(self.controlArea, 'Info') info_box.setMinimumWidth(200) self.info_summary = gui.widgetLabel(info_box, wordWrap=True) self.info_attr = gui.widgetLabel(info_box, wordWrap=True) self.info_class = gui.widgetLabel(info_box, wordWrap=True) self.info_meta = gui.widgetLabel(info_box, wordWrap=True) self.set_info() # TODO: Implement filtering on the model # filter_box = gui.vBox(self.controlArea, 'Filter') # self.filter_text = gui.lineEdit( # filter_box, self, value='filter_string', # placeholderText='Filter variables by name', # callback=self._filter_table_variables, callbackOnType=True, # ) # shortcut = QShortcut(QKeySequence('Ctrl+f'), self, self.filter_text.setFocus) # shortcut.setWhatsThis('Filter variables by name') self.color_var_model = DomainModel( valid_types=(ContinuousVariable, DiscreteVariable), placeholder='None', ) box = gui.vBox(self.controlArea, 'Histogram') self.cb_color_var = gui.comboBox( box, master=self, value='color_var', model=self.color_var_model, label='Color:', orientation=Qt.Horizontal, ) self.cb_color_var.activated.connect(self.__color_var_changed) gui.rubber(self.controlArea) gui.auto_commit( self.buttonsArea, self, 'auto_commit', 'Send Selected Rows', 'Send Automatically', ) # Main area self.model = FeatureStatisticsTableModel(parent=self) self.table_view = FeatureStatisticsTableView(self.model, parent=self) self.table_view.selectionModel().selectionChanged.connect(self.on_select) self.table_view.horizontalHeader().sectionClicked.connect(self.on_header_click) self.mainArea.layout().addWidget(self.table_view) def sizeHint(self): return QSize(1050, 500) def _filter_table_variables(self): regex = QRegExp(self.filter_string) # If the user explicitly types different cases, we assume they know # what they are searching for and account for letter case in filter different_case = ( any(c.islower() for c in self.filter_string) and any(c.isupper() for c in self.filter_string) ) if not different_case: regex.setCaseSensitivity(Qt.CaseInsensitive) @Inputs.data def set_data(self, data): # Clear outputs and reset widget state self.closeContext() self.selected_rows = [] self.model.resetSorting() self.Outputs.reduced_data.send(None) self.Outputs.statistics.send(None) # Setup widget state for new data and restore settings self.data = data if data is not None: self.color_var_model.set_domain(data.domain) if self.data.domain.class_vars: self.color_var = self.data.domain.class_vars[0] else: self.color_var_model.set_domain(None) self.color_var = None self.model.set_data(data) self.openContext(self.data) self.__restore_selection() self.__restore_sorting() # self._filter_table_variables() self.__color_var_changed() self.set_info() self.commit() def __restore_selection(self): """Restore the selection on the table view from saved settings.""" selection_model = self.table_view.selectionModel() selection = QItemSelection() if len(self.selected_rows): for row in self.model.mapFromSourceRows(self.selected_rows): selection.append(QItemSelectionRange( self.model.index(row, 0), self.model.index(row, self.model.columnCount() - 1) )) selection_model.select(selection, QItemSelectionModel.ClearAndSelect) def __restore_sorting(self): """Restore the sort column and order from saved settings.""" sort_column, sort_order = self.sorting if sort_column < self.model.columnCount(): self.model.sort(sort_column, sort_order) self.table_view.horizontalHeader().setSortIndicator(sort_column, sort_order) @pyqtSlot(int) def on_header_click(self, *_): # Store the header states sort_order = self.model.sortOrder() sort_column = self.model.sortColumn() self.sorting = sort_column, sort_order @pyqtSlot(int) def __color_var_changed(self, *_): if self.model is not None: self.model.set_target_var(self.color_var) def _format_variables_string(self, variables): agg = [] for var_type_name, var_type in [ ('categorical', DiscreteVariable), ('numeric', ContinuousVariable), ('time', TimeVariable), ('string', StringVariable) ]: # Disable pylint here because a `TimeVariable` is also a # `ContinuousVariable`, and should be labelled as such. That is why # it is necessary to check the type this way instead of using # `isinstance`, which would fail in the above case var_type_list = [v for v in variables if type(v) is var_type] # pylint: disable=unidiomatic-typecheck if var_type_list: shown = var_type in self.model.HIDDEN_VAR_TYPES agg.append(( '%d %s%s' % (len(var_type_list), var_type_name, ['', ' (not shown)'][shown]), len(var_type_list) )) if not agg: return 'No variables' attrs, counts = list(zip(*agg)) if len(attrs) > 1: var_string = ', '.join(attrs[:-1]) + ' and ' + attrs[-1] else: var_string = attrs[0] return plural('%s variable{s}' % var_string, sum(counts)) def set_info(self): if self.data is not None: self.info_summary.setText('<b>%s</b> contains %s with %s' % ( self.data.name, plural('{number} instance{s}', self.model.n_instances), plural('{number} feature{s}', self.model.n_attributes) )) self.info_attr.setText( '<b>Attributes:</b><br>%s' % self._format_variables_string(self.data.domain.attributes) ) self.info_class.setText( '<b>Class variables:</b><br>%s' % self._format_variables_string(self.data.domain.class_vars) ) self.info_meta.setText( '<b>Metas:</b><br>%s' % self._format_variables_string(self.data.domain.metas) ) else: self.info_summary.setText('No data on input.') self.info_attr.setText('') self.info_class.setText('') self.info_meta.setText('') def on_select(self): self.selected_rows = self.model.mapToSourceRows([ i.row() for i in self.table_view.selectionModel().selectedRows() ]) self.commit() def commit(self): if not len(self.selected_rows): self.Outputs.reduced_data.send(None) self.Outputs.statistics.send(None) return # Send a table with only selected columns to output variables = self.model.variables[self.selected_rows] self.Outputs.reduced_data.send(self.data[:, variables]) # Send the statistics of the selected variables to ouput labels, data = self.model.get_statistics_matrix(variables, return_labels=True) var_names = np.atleast_2d([var.name for var in variables]).T domain = Domain( attributes=[ContinuousVariable(name) for name in labels], metas=[StringVariable('Feature')] ) statistics = Table(domain, data, metas=var_names) statistics.name = '%s (Feature Statistics)' % self.data.name self.Outputs.statistics.send(statistics) def send_report(self): pass
class OWLinePlot(OWWidget): name = "Line Plot" description = "Visualization of data profiles (e.g., time series)." icon = "icons/LinePlot.svg" priority = 180 enable_selection = Signal(bool) 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() group_var = ContextSetting(None) show_profiles = Setting(False) show_range = Setting(True) show_mean = Setting(True) show_error = Setting(False) auto_commit = Setting(True) selection = Setting(None, schema_only=True) visual_settings = Setting({}, schema_only=True) graph_name = "graph.plotItem" class Error(OWWidget.Error): not_enough_attrs = Msg("Need at least one continuous feature.") no_valid_data = Msg("No plot due to no valid data.") class Warning(OWWidget.Warning): no_display_option = Msg("No display option is selected.") class Information(OWWidget.Information): hidden_instances = Msg("Instances with unknown values are not shown.") too_many_features = Msg("Data has too many features. Only first {}" " are shown.".format(MAX_FEATURES)) def __init__(self, parent=None): super().__init__(parent) self.__groups = [] self.data = None self.valid_data = None self.subset_data = None self.subset_indices = None self.__pending_selection = self.selection self.graph_variables = [] self.graph = None self.group_vars = None self.group_view = None self.setup_gui() VisualSettingsDialog(self, self.graph.parameter_setter.initial_settings) self.graph.view_box.selection_changed.connect(self.selection_changed) self.enable_selection.connect(self.graph.view_box.enable_selection) def setup_gui(self): self._add_graph() self._add_controls() def _add_graph(self): box = gui.vBox(self.mainArea, True, margin=0) self.graph = LinePlotGraph(self) box.layout().addWidget(self.graph) def _add_controls(self): displaybox = gui.widgetBox(self.controlArea, "Display") gui.checkBox(displaybox, self, "show_profiles", "Lines", callback=self.__show_profiles_changed, tooltip="Plot lines") gui.checkBox(displaybox, self, "show_range", "Range", callback=self.__show_range_changed, tooltip="Plot range between 10th and 90th percentile") gui.checkBox(displaybox, self, "show_mean", "Mean", callback=self.__show_mean_changed, tooltip="Plot mean curve") gui.checkBox(displaybox, self, "show_error", "Error bars", callback=self.__show_error_changed, tooltip="Show standard deviation") self.group_vars = DomainModel(placeholder="None", separators=False, valid_types=DiscreteVariable) self.group_view = gui.listView(self.controlArea, self, "group_var", box="Group by", model=self.group_vars, callback=self.__group_var_changed, sizeHint=QSize(30, 100)) self.group_view.setEnabled(False) plot_gui = OWPlotGUI(self) plot_gui.box_zoom_select(self.controlArea) gui.rubber(self.controlArea) gui.auto_send(self.controlArea, self, "auto_commit") self.info.set_input_summary(self.info.NoInput) self.info.set_output_summary(self.info.NoOutput) def __show_profiles_changed(self): self.check_display_options() self._update_visibility("profiles") def __show_range_changed(self): self.check_display_options() self._update_visibility("range") def __show_mean_changed(self): self.check_display_options() self._update_visibility("mean") def __show_error_changed(self): self._update_visibility("error") def __group_var_changed(self): if self.data is None or not self.graph_variables: return self.plot_groups() self._update_profiles_color() self._update_sel_profiles_and_range() self._update_sel_profiles_color() self._update_sub_profiles() @Inputs.data @check_sql_input def set_data(self, data): self.closeContext() self.data = data self._set_input_summary() self.clear() self.check_data() self.check_display_options() if self.data is not None: self.group_vars.set_domain(self.data.domain) self.group_view.setEnabled(len(self.group_vars) > 1) self.group_var = self.data.domain.class_var \ if self.data.domain.has_discrete_class else None self.openContext(data) self.setup_plot() self.unconditional_commit() def check_data(self): def error(err): err() self.data = None self.clear_messages() if self.data is not None: self.graph_variables = [ var for var in self.data.domain.attributes if var.is_continuous ] self.valid_data = ~countnans(self.data.X, axis=1).astype(bool) if len(self.graph_variables) < 1: error(self.Error.not_enough_attrs) elif not np.sum(self.valid_data): error(self.Error.no_valid_data) else: if not np.all(self.valid_data): self.Information.hidden_instances() if len(self.graph_variables) > MAX_FEATURES: self.Information.too_many_features() self.graph_variables = self.graph_variables[:MAX_FEATURES] def check_display_options(self): self.Warning.no_display_option.clear() if self.data is not None: if not (self.show_profiles or self.show_range or self.show_mean): self.Warning.no_display_option() enable = (self.show_profiles or self.show_range) and \ len(self.data[self.valid_data]) < SEL_MAX_INSTANCES self.enable_selection.emit(enable) def _set_input_summary(self): summary = len(self.data) if self.data else self.info.NoInput details = format_summary_details(self.data) if self.data else "" self.info.set_input_summary(summary, details) @Inputs.data_subset @check_sql_input def set_subset_data(self, subset): self.subset_data = subset def handleNewSignals(self): self.set_subset_ids() if self.data is not None: self._update_profiles_color() self._update_sel_profiles_color() self._update_sub_profiles() def set_subset_ids(self): sub_ids = {e.id for e in self.subset_data} \ if self.subset_data is not None else {} self.subset_indices = None if self.data is not None and sub_ids: self.subset_indices = [ x.id for x in self.data[self.valid_data] if x.id in sub_ids ] def setup_plot(self): if self.data is None: return ticks = [a.name for a in self.graph_variables] self.graph.getAxis("bottom").set_ticks(ticks) self.plot_groups() self.apply_selection() self.graph.view_box.enableAutoRange() self.graph.view_box.updateAutoRange() def plot_groups(self): self._remove_groups() data = self.data[self.valid_data, self.graph_variables] if self.group_var is None: self._plot_group(data, np.where(self.valid_data)[0]) else: class_col_data, _ = self.data.get_column_view(self.group_var) for index in range(len(self.group_var.values)): mask = np.logical_and(class_col_data == index, self.valid_data) indices = np.flatnonzero(mask) if not len(indices): continue group_data = self.data[indices, self.graph_variables] self._plot_group(group_data, indices, index) self.graph.update_legend(self.group_var) self.graph.groups = self.__groups self.graph.view_box.add_profiles(data.X) def _remove_groups(self): for group in self.__groups: group.remove_items() self.graph.view_box.remove_profiles() self.graph.groups = [] self.__groups = [] def _plot_group(self, data, indices, index=None): color = self.__get_group_color(index) group = ProfileGroup(data, indices, color, self.graph) kwargs = self.__get_visibility_flags() group.set_visible_error(**kwargs) group.set_visible_mean(**kwargs) group.set_visible_range(**kwargs) group.set_visible_profiles(**kwargs) self.__groups.append(group) def __get_group_color(self, index): if self.group_var is not None: return QColor(*self.group_var.colors[index]) return QColor(LinePlotStyle.DEFAULT_COLOR) def __get_visibility_flags(self): return { "show_profiles": self.show_profiles, "show_range": self.show_range, "show_mean": self.show_mean, "show_error": self.show_error } def _update_profiles_color(self): # color alpha depends on subset and selection; with selection or # subset profiles color has more opacity if not self.show_profiles: return for group in self.__groups: has_sel = bool(self.subset_indices) or bool(self.selection) group.update_profiles_color(has_sel) def _update_sel_profiles_and_range(self): # mark selected instances and selected range if not (self.show_profiles or self.show_range): return for group in self.__groups: inds = [i for i in group.indices if self.__in(i, self.selection)] table = self.data[inds, self.graph_variables].X if inds else None if self.show_profiles: group.update_sel_profiles(table) if self.show_range: group.update_sel_range(table) def _update_sel_profiles_color(self): # color depends on subset; when subset is present, # selected profiles are black if not self.selection or not self.show_profiles: return for group in self.__groups: group.update_sel_profiles_color(bool(self.subset_indices)) def _update_sub_profiles(self): # mark subset instances if not (self.show_profiles or self.show_range): return for group in self.__groups: inds = [ i for i, _id in zip(group.indices, group.ids) if self.__in(_id, self.subset_indices) ] table = self.data[inds, self.graph_variables].X if inds else None group.update_sub_profiles(table) def _update_visibility(self, obj_name): if not len(self.__groups): return self._update_profiles_color() self._update_sel_profiles_and_range() self._update_sel_profiles_color() kwargs = self.__get_visibility_flags() for group in self.__groups: getattr(group, "set_visible_{}".format(obj_name))(**kwargs) self.graph.view_box.updateAutoRange() def apply_selection(self): if self.data is not None and self.__pending_selection is not None: sel = [i for i in self.__pending_selection if i < len(self.data)] mask = np.zeros(len(self.data), dtype=bool) mask[sel] = True mask = mask[self.valid_data] self.selection_changed(mask) self.__pending_selection = None def selection_changed(self, mask): if self.data is None: return # need indices for self.data: mask refers to self.data[self.valid_data] indices = np.arange(len(self.data))[self.valid_data][mask] self.graph.select(indices) old = self.selection self.selection = None if self.data and isinstance(self.data, SqlTable)\ else list(self.graph.selection) if not old and self.selection or old and not self.selection: self._update_profiles_color() self._update_sel_profiles_and_range() self._update_sel_profiles_color() self.commit() def commit(self): selected = self.data[self.selection] \ if self.data is not None and bool(self.selection) else None annotated = create_annotated_table(self.data, self.selection) summary = len(selected) if selected else self.info.NoOutput details = format_summary_details(selected) if selected else "" self.info.set_output_summary(summary, details) self.Outputs.selected_data.send(selected) self.Outputs.annotated_data.send(annotated) def send_report(self): if self.data is None: return caption = report.render_items_vert((("Group by", self.group_var), )) self.report_plot() if caption: self.report_caption(caption) def sizeHint(self): return QSize(1132, 708) def clear(self): self.valid_data = None self.selection = None self.__groups = [] self.graph_variables = [] self.graph.reset() self.group_vars.set_domain(None) self.group_view.setEnabled(False) @staticmethod def __in(obj, collection): return collection is not None and obj in collection def set_visual_settings(self, key, value): self.graph.parameter_setter.set_parameter(key, value) self.visual_settings[key] = value
def __init__(self): super().__init__() self.__pending_selection = self.selection self._optimizer = None self._optimizer_thread = None self.stop_optimization = False self.data = self.cont_x = None self.cells = self.member_data = None self.selection = None self.colors = self.thresholds = self.bin_labels = None box = gui.vBox(self.controlArea, box="SOM") shape = gui.comboBox(box, self, "", items=("Hexagonal grid", "Square grid")) shape.setCurrentIndex(1 - self.hexagonal) box2 = gui.indentedBox(box, 10) auto_dim = gui.checkBox(box2, self, "auto_dimension", "Set dimensions automatically", callback=self.on_auto_dimension_changed) self.manual_box = box3 = gui.hBox(box2) spinargs = dict(value="", widget=box3, master=self, minv=5, maxv=100, step=5, alignment=Qt.AlignRight) spin_x = gui.spin(**spinargs) spin_x.setValue(self.size_x) gui.widgetLabel(box3, "×") spin_y = gui.spin(**spinargs) spin_y.setValue(self.size_y) gui.rubber(box3) self.manual_box.setEnabled(not self.auto_dimension) initialization = gui.comboBox(box, self, "initialization", items=("Initialize with PCA", "Random initialization", "Replicable random")) start = gui.button(box, self, "Restart", callback=self.restart_som_pressed, sizePolicy=(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)) self.opt_controls = self.OptControls(shape, auto_dim, spin_x, spin_y, initialization, start) box = gui.vBox(self.controlArea, "Color") gui.comboBox(box, self, "attr_color", searchable=True, callback=self.on_attr_color_change, model=DomainModel(placeholder="(Same color)", valid_types=DomainModel.PRIMITIVE)) gui.checkBox(box, self, "pie_charts", label="Show pie charts", callback=self.on_pie_chart_change) gui.checkBox(box, self, "size_by_instances", label="Size by number of instances", callback=self.on_attr_size_change) gui.rubber(self.controlArea) self.scene = QGraphicsScene(self) self.view = SomView(self.scene) self.view.setMinimumWidth(400) self.view.setMinimumHeight(400) self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.view.setRenderHint(QPainter.Antialiasing) self.view.selection_changed.connect(self.on_selection_change) self.view.selection_moved.connect(self.on_selection_move) self.view.selection_mark_changed.connect(self.on_selection_mark_change) self.mainArea.layout().addWidget(self.view) self.elements = None self.grid = None self.grid_cells = None self.legend = None
class OWCurveFit(OWBaseLearner): name = "Curve Fit" description = "Fit a function to data." icon = "icons/CurveFit.svg" priority = 90 keywords = ["function"] class Outputs(OWBaseLearner.Outputs): coefficients = Output("Coefficients", Table, explicit=True) class Warning(OWBaseLearner.Warning): duplicate_parameter = Msg("Duplicated parameter name.") unused_parameter = Msg("Unused parameter '{}' in " "'Parameters' declaration.") data_missing = Msg("Provide data on the input.") class Error(OWBaseLearner.Error): invalid_exp = Msg("Invalid expression.") no_parameter = Msg("Missing a fitting parameter.\n" "Use 'Feature Constructor' widget instead.") unknown_parameter = Msg("Unknown parameter '{}'.\n" "Declare the parameter in 'Parameters' box") parameter_in_attrs = Msg("Some parameters and features have the same " "name '{}'.") LEARNER = CurveFitLearner supports_sparse = False parameters: Mapping[str, Tuple[Any, ...]] = Setting({}, schema_only=True) expression: str = Setting("", schema_only=True) FEATURE_PLACEHOLDER = "Select Feature" PARAM_PLACEHOLDER = "Select Parameter" FUNCTION_PLACEHOLDER = "Select Function" _feature: Optional[ContinuousVariable] = None _parameter: str = PARAM_PLACEHOLDER _function: str = FUNCTION_PLACEHOLDER def __init__(self, *args, **kwargs): self.__pp_data: Optional[Table] = None self.__param_widget: ParametersWidget = None self.__expression_edit: QLineEdit = None self.__feature_combo: ComboBoxSearch = None self.__parameter_combo: ComboBoxSearch = None self.__function_combo: ComboBoxSearch = None self.__feature_model = DomainModel( order=DomainModel.ATTRIBUTES, placeholder=self.FEATURE_PLACEHOLDER, separators=False, valid_types=ContinuousVariable) self.__param_model = PyListModel([self.PARAM_PLACEHOLDER]) self.__pending_parameters = self.parameters self.__pending_expression = self.expression super().__init__(*args, **kwargs) self.Warning.data_missing() def add_main_layout(self): box = gui.vBox(self.controlArea, "Parameters") self.__param_widget = ParametersWidget(self) self.__param_widget.sigDataChanged.connect( self.__on_parameters_changed) box.layout().addWidget(self.__param_widget) function_box = gui.vBox(self.controlArea, box="Expression") self.__expression_edit = gui.lineEdit(function_box, self, "expression", placeholderText="Expression...", callback=self.settings_changed) hbox = gui.hBox(function_box) combo_options = dict(sendSelectedValue=True, searchable=True, contentsLength=13) self.__feature_combo = gui.comboBox(hbox, self, "_feature", model=self.__feature_model, callback=self.__on_feature_added, **combo_options) self.__parameter_combo = gui.comboBox( hbox, self, "_parameter", model=self.__param_model, callback=self.__on_parameter_added, **combo_options) sorted_funcs = sorted(FUNCTIONS) function_model = PyListModelTooltip( chain([self.FUNCTION_PLACEHOLDER], sorted_funcs), [""] + [FUNCTIONS[f].__doc__ for f in sorted_funcs], parent=self) self.__function_combo = gui.comboBox(hbox, self, "_function", model=function_model, callback=self.__on_function_added, **combo_options) def __on_parameters_changed(self, parameters: List[Parameter]): self.parameters = params = {p.name: p.to_tuple() for p in parameters} self.__param_model[:] = chain([self.PARAM_PLACEHOLDER], params) self.settings_changed() self.Error.parameter_in_attrs.clear() self.Warning.duplicate_parameter.clear() if len(self.parameters) != len(parameters): self.Warning.duplicate_parameter() names = [f.name for f in self.__feature_model[1:]] forbidden = [p.name for p in parameters if p.name in names] if forbidden: self.Error.parameter_in_attrs(forbidden[0]) def __on_feature_added(self): index = self.__feature_combo.currentIndex() if index > 0: self.__insert_into_expression(sanitized_name(self._feature.name)) self.__feature_combo.setCurrentIndex(0) self.settings_changed() def __on_parameter_added(self): index = self.__parameter_combo.currentIndex() if index > 0: self.__insert_into_expression(sanitized_name(self._parameter)) self.__parameter_combo.setCurrentIndex(0) self.settings_changed() def __on_function_added(self): index = self.__function_combo.currentIndex() if index > 0: if not callable(FUNCTIONS[self._function]): # e, pi, inf, nan self.__insert_into_expression(self._function) elif self._function in [ "arctan2", "copysign", "fmod", "gcd", "hypot", "isclose", "ldexp", "power", "remainder" ]: self.__insert_into_expression(self._function + "(,)", 2) else: self.__insert_into_expression(self._function + "()", 1) self.__function_combo.setCurrentIndex(0) self.settings_changed() def __insert_into_expression(self, what: str, offset=0): pos = self.__expression_edit.cursorPosition() text = self.__expression_edit.text() self.__expression_edit.setText(text[:pos] + what + text[pos:]) self.__expression_edit.setCursorPosition(pos + len(what) - offset) self.__expression_edit.setFocus() @OWBaseLearner.Inputs.data def set_data(self, data: Optional[Table]): self.Warning.data_missing(shown=not bool(data)) self.learner = None super().set_data(data) def set_preprocessor(self, preprocessor: Preprocess): self.preprocessors = preprocessor feature_names_changed = False if self.data and self.__pp_data: pp_data = preprocess(self.data, preprocessor) feature_names_changed = \ set(a.name for a in pp_data.domain.attributes) != \ set(a.name for a in self.__pp_data.domain.attributes) if feature_names_changed: self.expression = "" def handleNewSignals(self): self.__preprocess_data() self.__init_models() self.__set_pending() super().handleNewSignals() def __preprocess_data(self): self.__pp_data = preprocess(self.data, self.preprocessors) def __init_models(self): domain = self.__pp_data.domain if self.__pp_data else None self.__feature_model.set_domain(domain) self._feature = self.__feature_model[0] def __set_pending(self): if self.__pending_parameters: parameters = [ Parameter(*p) for p in self.__pending_parameters.values() ] self.__param_widget.set_data(parameters) self.__on_parameters_changed(parameters) self.__pending_parameters = [] if self.__pending_expression: self.expression = self.__pending_expression self.__pending_expression = "" def create_learner(self) -> Optional[CurveFitLearner]: self.Error.invalid_exp.clear() self.Error.no_parameter.clear() self.Error.unknown_parameter.clear() self.Warning.unused_parameter.clear() expression = self.expression.strip() if not self.__pp_data or not expression: return None if not self.__validate_expression(expression): self.Error.invalid_exp() return None p0, bounds = {}, {} for name in self.parameters: param = Parameter(*self.parameters[name]) p0[name] = param.initial bounds[name] = (param.lower if param.use_lower else -np.inf, param.upper if param.use_upper else np.inf) learner = self.LEARNER( expression, available_feature_names=[a.name for a in self.__feature_model[1:]], functions=FUNCTIONS, sanitizer=sanitized_name, p0=p0, bounds=bounds, preprocessors=self.preprocessors) params_names = learner.parameters_names if not params_names: self.Error.no_parameter() return None unknown = [p for p in params_names if p not in self.parameters] if unknown: self.Error.unknown_parameter(unknown[0]) return None unused = [p for p in self.parameters if p not in params_names] if unused: self.Warning.unused_parameter(unused[0]) return learner def get_learner_parameters(self) -> Tuple[Tuple[str, Any]]: return (("Expression", self.expression), ) def update_model(self): super().update_model() coefficients = None if self.model is not None: coefficients = self.model.coefficients self.Outputs.coefficients.send(coefficients) def check_data(self): learner_existed = self.learner is not None if self.data: data = preprocess(self.data, self.preprocessors) dom = data.domain cont_attrs = [a for a in dom.attributes if a.is_continuous] if len(cont_attrs) == 0: self.Error.data_error("Data has no continuous features.") elif not self.learner: # create dummy learner in order to check data self.learner = self.LEARNER(lambda: 1, [], []) # parent's check_data() needs learner instantiated self.valid_data = super().check_data() if not learner_existed: self.valid_data = False self.learner = None return self.valid_data @staticmethod def __validate_expression(expression: str): try: tree = ast.parse(expression, mode="eval") valid = validate_exp(tree) # pylint: disable=broad-except except Exception: return False return valid
def __init__(self): super().__init__() self.data = None self.domainmodels = [] self.unmatched = [] top = self.controlArea def _radioChanged(): self.mainArea.setVisible(self.is_decoding == 0 and len(self.unmatched)) self.commit() modes = gui.radioButtons(top, self, 'is_decoding', callback=_radioChanged) gui.appendRadioButton( modes, '&Encode region names into geographical coordinates:', insertInto=top) box = gui.indentedBox(top) model = DomainModel(parent=self, valid_types=(StringVariable, DiscreteVariable)) self.domainmodels.append(model) combo = gui.comboBox(box, self, 'str_attr', label='Region identifier:', orientation=Qt.Horizontal, callback=self.region_attr_changed, sendSelectedValue=True, model=model) gui.comboBox(box, self, 'str_type', label='Identifier type:', orientation=Qt.Horizontal, items=tuple(self.ID_TYPE.keys()), callback=lambda: self.commit(), sendSelectedValue=True) # Select first mode if any of its combos are changed for combo in box.findChildren(QComboBox): combo.activated.connect(lambda: setattr(self, 'is_decoding', 0)) gui.appendRadioButton(modes, '&Decode latitude and longitude into regions:', insertInto=top) box = gui.indentedBox(top) model = DomainModel(parent=self, valid_types=ContinuousVariable) self.domainmodels.append(model) combo = gui.comboBox(box, self, 'lat_attr', label='Latitude:', orientation=Qt.Horizontal, callback=lambda: self.commit(), sendSelectedValue=True, model=model) combo = gui.comboBox(box, self, 'lon_attr', label='Longitude:', orientation=Qt.Horizontal, callback=lambda: self.commit(), sendSelectedValue=True, model=model) gui.comboBox( box, self, 'admin', label='Administrative level:', orientation=Qt.Horizontal, callback=lambda: self.commit(), items= ('Country', '1st-level subdivision (state, region, province, municipality, ...)', '2nd-level subdivisions (1st-level & US counties)'), ) # Select second mode if any of its combos are changed for combo in box.findChildren(QComboBox): combo.activated.connect(lambda: setattr(self, 'is_decoding', 1)) gui.checkBox( top, self, 'append_features', label='E&xtend coded data with additional region properties', callback=lambda: self.commit(), toolTip='Extend coded data with region properties, such as' 'ISO codes, continent, subregion, region type, ' 'economy type, FIPS/HASC codes, region capital etc. as available.') gui.auto_commit(self.controlArea, self, 'autocommit', '&Apply') gui.rubber(self.controlArea) model = self.replacementsModel = PyTableModel(self.replacements, parent=self, editable=[False, True]) view = gui.TableView(self, sortingEnabled=False, selectionMode=gui.TableView.NoSelection, editTriggers=gui.TableView.AllEditTriggers) view.horizontalHeader().setResizeMode(QHeaderView.Stretch) view.verticalHeader().setSectionResizeMode(0) view.setModel(model) owwidget = self class TableItemDelegate(QItemDelegate): def createEditor(self, parent, options, index): nonlocal owwidget edit = QLineEdit(parent) wordlist = [''] + ToLatLon.valid_values( owwidget.ID_TYPE[owwidget.str_type]) edit.setCompleter( QCompleter(wordlist, edit, caseSensitivity=Qt.CaseInsensitive, filterMode=Qt.MatchContains)) def save_and_commit(): if edit.text() and edit.text() in wordlist: model = index.model() pindex = QPersistentModelIndex(index) if pindex.isValid(): new_index = pindex.sibling(pindex.row(), pindex.column()) save = model.setData(new_index, edit.text(), Qt.EditRole) if save: owwidget.commit() return edit.clear() edit.editingFinished.connect(save_and_commit) return edit view.setItemDelegate(TableItemDelegate()) model.setHorizontalHeaderLabels( ['Unmatched Identifier', 'Custom Replacement']) box = gui.vBox(self.mainArea) self.info_str = ' /' gui.label(box, self, 'Unmatched identifiers: %(info_str)s') box.layout().addWidget(view) self.mainArea.setVisible(self.is_decoding == 0)
class OWScatterPlot(OWDataProjectionWidget): """Scatterplot visualization with explorative analysis and intelligent data visualization enhancements.""" name = 'Scatter Plot' description = "Interactive scatter plot visualization with " \ "intelligent data visualization enhancements." icon = "icons/ScatterPlot.svg" priority = 140 keywords = [] class Inputs(OWDataProjectionWidget.Inputs): features = Input("Features", AttributeList) class Outputs(OWDataProjectionWidget.Outputs): features = Output("Features", AttributeList, dynamic=False) settings_version = 3 auto_sample = Setting(True) attr_x = ContextSetting(None) attr_y = ContextSetting(None) tooltip_shows_all = Setting(True) GRAPH_CLASS = OWScatterPlotGraph graph = SettingProvider(OWScatterPlotGraph) embedding_variables_names = None class Warning(OWDataProjectionWidget.Warning): missing_coords = Msg( "Plot cannot be displayed because '{}' or '{}' " "is missing for all data points") class Information(OWDataProjectionWidget.Information): sampled_sql = Msg("Large SQL table; showing a sample.") missing_coords = Msg( "Points with missing '{}' or '{}' are not displayed") def __init__(self): self.sql_data = None # Orange.data.sql.table.SqlTable self.attribute_selection_list = None # list of Orange.data.Variable self.__timer = QTimer(self, interval=1200) self.__timer.timeout.connect(self.add_data) super().__init__() # manually register Matplotlib file writers self.graph_writers = self.graph_writers.copy() for w in [MatplotlibFormat, MatplotlibPDFFormat]: for ext in w.EXTENSIONS: self.graph_writers[ext] = w def _add_controls(self): self._add_controls_axis() self._add_controls_sampling() super()._add_controls() self.graph.gui.add_widget(self.graph.gui.JitterNumericValues, self._effects_box) self.graph.gui.add_widgets([self.graph.gui.ShowGridLines, self.graph.gui.ToolTipShowsAll, self.graph.gui.RegressionLine], self._plot_box) def _add_controls_axis(self): common_options = dict( labelWidth=50, orientation=Qt.Horizontal, sendSelectedValue=True, valueType=str, contentsLength=14 ) box = gui.vBox(self.controlArea, True) dmod = DomainModel self.xy_model = DomainModel(dmod.MIXED, valid_types=dmod.PRIMITIVE) self.cb_attr_x = gui.comboBox( box, self, "attr_x", label="Axis x:", callback=self.attr_changed, model=self.xy_model, **common_options) self.cb_attr_y = gui.comboBox( box, self, "attr_y", label="Axis y:", callback=self.attr_changed, model=self.xy_model, **common_options) vizrank_box = gui.hBox(box) self.vizrank, self.vizrank_button = ScatterPlotVizRank.add_vizrank( vizrank_box, self, "Find Informative Projections", self.set_attr) def _add_controls_sampling(self): self.sampling = gui.auto_commit( self.controlArea, self, "auto_sample", "Sample", box="Sampling", callback=self.switch_sampling, commit=lambda: self.add_data(1)) self.sampling.setVisible(False) def _vizrank_color_change(self): self.vizrank.initialize() is_enabled = self.data is not None and not self.data.is_sparse() and \ len(self.xy_model) > 2 and len(self.data[self.valid_data]) > 1 \ and np.all(np.nan_to_num(np.nanstd(self.data.X, 0)) != 0) self.vizrank_button.setEnabled( is_enabled and self.attr_color is not None and not np.isnan(self.data.get_column_view( self.attr_color)[0].astype(float)).all()) text = "Color variable has to be selected." \ if is_enabled and self.attr_color is None else "" self.vizrank_button.setToolTip(text) def set_data(self, data): if self.data and data and self.data.checksum() == data.checksum(): return super().set_data(data) def findvar(name, iterable): """Find a Orange.data.Variable in `iterable` by name""" for el in iterable: if isinstance(el, Variable) and el.name == name: return el return None # handle restored settings from < 3.3.9 when attr_* were stored # by name if isinstance(self.attr_x, str): self.attr_x = findvar(self.attr_x, self.xy_model) if isinstance(self.attr_y, str): self.attr_y = findvar(self.attr_y, self.xy_model) if isinstance(self.attr_label, str): self.attr_label = findvar( self.attr_label, self.graph.gui.label_model) if isinstance(self.attr_color, str): self.attr_color = findvar( self.attr_color, self.graph.gui.color_model) if isinstance(self.attr_shape, str): self.attr_shape = findvar( self.attr_shape, self.graph.gui.shape_model) if isinstance(self.attr_size, str): self.attr_size = findvar( self.attr_size, self.graph.gui.size_model) def check_data(self): self.clear_messages() self.__timer.stop() self.sampling.setVisible(False) self.sql_data = None if isinstance(self.data, SqlTable): if self.data.approx_len() < 4000: self.data = Table(self.data) else: self.Information.sampled_sql() self.sql_data = self.data data_sample = self.data.sample_time(0.8, no_cache=True) data_sample.download_data(2000, partial=True) self.data = Table(data_sample) self.sampling.setVisible(True) if self.auto_sample: self.__timer.start() if self.data is not None and (len(self.data) == 0 or len(self.data.domain) == 0): self.data = None def get_embedding(self): self.valid_data = None if self.data is None: return None x_data = self.get_column(self.attr_x, filter_valid=False) y_data = self.get_column(self.attr_y, filter_valid=False) if x_data is None or y_data is None: return None self.Warning.missing_coords.clear() self.Information.missing_coords.clear() self.valid_data = np.isfinite(x_data) & np.isfinite(y_data) if self.valid_data is not None and not np.all(self.valid_data): msg = self.Information if np.any(self.valid_data) else self.Warning msg.missing_coords(self.attr_x.name, self.attr_y.name) return np.vstack((x_data, y_data)).T # Tooltip def _point_tooltip(self, point_id, skip_attrs=()): point_data = self.data[point_id] xy_attrs = (self.attr_x, self.attr_y) text = "<br/>".join( escape('{} = {}'.format(var.name, point_data[var])) for var in xy_attrs) if self.tooltip_shows_all: others = super()._point_tooltip(point_id, skip_attrs=xy_attrs) if others: text = "<b>{}</b><br/><br/>{}".format(text, others) return text def can_draw_regresssion_line(self): return self.data is not None and\ self.data.domain is not None and \ self.attr_x.is_continuous and \ self.attr_y.is_continuous def add_data(self, time=0.4): if self.data and len(self.data) > 2000: self.__timer.stop() return data_sample = self.sql_data.sample_time(time, no_cache=True) if data_sample: data_sample.download_data(2000, partial=True) data = Table(data_sample) self.data = Table.concatenate((self.data, data), axis=0) self.handleNewSignals() def init_attr_values(self): super().init_attr_values() data = self.data domain = data.domain if data and len(data) else None self.xy_model.set_domain(domain) self.attr_x = self.xy_model[0] if self.xy_model else None self.attr_y = self.xy_model[1] if len(self.xy_model) >= 2 \ else self.attr_x def switch_sampling(self): self.__timer.stop() if self.auto_sample and self.sql_data: self.add_data() self.__timer.start() def set_subset_data(self, subset_data): self.warning() if isinstance(subset_data, SqlTable): if subset_data.approx_len() < AUTO_DL_LIMIT: subset_data = Table(subset_data) else: self.warning("Data subset does not support large Sql tables") subset_data = None super().set_subset_data(subset_data) # called when all signals are received, so the graph is updated only once def handleNewSignals(self): if self.attribute_selection_list and self.data is not None and \ self.data.domain is not None and \ all(attr in self.data.domain for attr in self.attribute_selection_list): self.attr_x = self.attribute_selection_list[0] self.attr_y = self.attribute_selection_list[1] self.attribute_selection_list = None super().handleNewSignals() self._vizrank_color_change() self.cb_reg_line.setEnabled(self.can_draw_regresssion_line()) @Inputs.features def set_shown_attributes(self, attributes): if attributes and len(attributes) >= 2: self.attribute_selection_list = attributes[:2] else: self.attribute_selection_list = None def set_attr(self, attr_x, attr_y): self.attr_x, self.attr_y = attr_x, attr_y self.attr_changed() def attr_changed(self): self.cb_reg_line.setEnabled(self.can_draw_regresssion_line()) self.setup_plot() self.commit() def setup_plot(self): super().setup_plot() for axis, var in (("bottom", self.attr_x), ("left", self.attr_y)): self.graph.set_axis_title(axis, var) if var and var.is_discrete: self.graph.set_axis_labels(axis, get_variable_values_sorted(var)) else: self.graph.set_axis_labels(axis, None) def colors_changed(self): super().colors_changed() self._vizrank_color_change() def commit(self): super().commit() self.send_features() def send_features(self): features = [attr for attr in [self.attr_x, self.attr_y] if attr] self.Outputs.features.send(features or None) def get_widget_name_extension(self): if self.data is not None: return "{} vs {}".format(self.attr_x.name, self.attr_y.name) return None def _get_send_report_caption(self): return report.render_items_vert(( ("Color", self._get_caption_var_name(self.attr_color)), ("Label", self._get_caption_var_name(self.attr_label)), ("Shape", self._get_caption_var_name(self.attr_shape)), ("Size", self._get_caption_var_name(self.attr_size)), ("Jittering", (self.attr_x.is_discrete or self.attr_y.is_discrete or self.graph.jitter_continuous) and self.graph.jitter_size))) @classmethod def migrate_settings(cls, settings, version): if version < 2 and "selection" in settings and settings["selection"]: settings["selection_group"] = [(a, 1) for a in settings["selection"]] if version < 3: if "auto_send_selection" in settings: settings["auto_commit"] = settings["auto_send_selection"] if "selection_group" in settings: settings["selection"] = settings["selection_group"] @classmethod def migrate_context(cls, context, version): if version < 3: values = context.values values["attr_color"] = values["graph"]["attr_color"] values["attr_size"] = values["graph"]["attr_size"] values["attr_shape"] = values["graph"]["attr_shape"] values["attr_label"] = values["graph"]["attr_label"]
class ImagePlot(QWidget, OWComponent, SelectionGroupMixin): attr_x = ContextSetting(None) attr_y = ContextSetting(None) gamma = Setting(0) threshold_low = Setting(0.0, schema_only=True) threshold_high = Setting(1.0, schema_only=True) level_low = Setting(None, schema_only=True) level_high = Setting(None, schema_only=True) palette_index = Setting(0) selection_changed = Signal() def __init__(self, parent): QWidget.__init__(self) OWComponent.__init__(self, parent) SelectionGroupMixin.__init__(self) self.parent = parent self.selection_type = SELECTMANY self.saving_enabled = True self.selection_enabled = True self.viewtype = INDIVIDUAL # required bt InteractiveViewBox self.highlighted = None self.data_points = None self.data_values = None self.data_imagepixels = None self.plotview = pg.PlotWidget(background="w", viewBox=InteractiveViewBox(self)) self.plot = self.plotview.getPlotItem() self.plot.scene().installEventFilter( HelpEventDelegate(self.help_event, self)) layout = QVBoxLayout() self.setLayout(layout) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().addWidget(self.plotview) self.img = ImageItemNan() self.img.setOpts(axisOrder='row-major') self.plot.addItem(self.img) self.plot.vb.setAspectLocked() self.plot.scene().sigMouseMoved.connect(self.plot.vb.mouseMovedEvent) layout = QGridLayout() self.plotview.setLayout(layout) self.button = QPushButton("View", self.plotview) self.button.setAutoDefault(False) layout.setRowStretch(1, 1) layout.setColumnStretch(1, 1) layout.addWidget(self.button, 0, 0) view_menu = MenuFocus(self) self.button.setMenu(view_menu) # prepare interface according to the new context self.parent.contextAboutToBeOpened.connect(lambda x: self.init_interface_data(x[0])) actions = [] zoom_in = QAction( "Zoom in", self, triggered=self.plot.vb.set_mode_zooming ) zoom_in.setShortcuts([Qt.Key_Z, QKeySequence(QKeySequence.ZoomIn)]) zoom_in.setShortcutContext(Qt.WidgetWithChildrenShortcut) actions.append(zoom_in) zoom_fit = QAction( "Zoom to fit", self, triggered=lambda x: (self.plot.vb.autoRange(), self.plot.vb.set_mode_panning()) ) zoom_fit.setShortcuts([Qt.Key_Backspace, QKeySequence(Qt.ControlModifier | Qt.Key_0)]) zoom_fit.setShortcutContext(Qt.WidgetWithChildrenShortcut) actions.append(zoom_fit) select_square = QAction( "Select (square)", self, triggered=self.plot.vb.set_mode_select_square, ) select_square.setShortcuts([Qt.Key_S]) select_square.setShortcutContext(Qt.WidgetWithChildrenShortcut) actions.append(select_square) select_polygon = QAction( "Select (polygon)", self, triggered=self.plot.vb.set_mode_select_polygon, ) select_polygon.setShortcuts([Qt.Key_P]) select_polygon.setShortcutContext(Qt.WidgetWithChildrenShortcut) actions.append(select_polygon) if self.saving_enabled: save_graph = QAction( "Save graph", self, triggered=self.save_graph, ) save_graph.setShortcuts([QKeySequence(Qt.ControlModifier | Qt.Key_I)]) actions.append(save_graph) view_menu.addActions(actions) self.addActions(actions) common_options = dict( labelWidth=50, orientation=Qt.Horizontal, sendSelectedValue=True, valueType=str) choose_xy = QWidgetAction(self) box = gui.vBox(self) box.setFocusPolicy(Qt.TabFocus) self.xy_model = DomainModel(DomainModel.METAS | DomainModel.CLASSES, valid_types=DomainModel.PRIMITIVE) self.cb_attr_x = gui.comboBox( box, self, "attr_x", label="Axis x:", callback=self.update_attr, model=self.xy_model, **common_options) self.cb_attr_y = gui.comboBox( box, self, "attr_y", label="Axis y:", callback=self.update_attr, model=self.xy_model, **common_options) box.setFocusProxy(self.cb_attr_x) self.color_cb = gui.comboBox(box, self, "palette_index", label="Color:", labelWidth=50, orientation=Qt.Horizontal) self.color_cb.setIconSize(QSize(64, 16)) palettes = _color_palettes self.palette_index = min(self.palette_index, len(palettes) - 1) model = color_palette_model(palettes, self.color_cb.iconSize()) model.setParent(self) self.color_cb.setModel(model) self.color_cb.activated.connect(self.update_color_schema) self.color_cb.setCurrentIndex(self.palette_index) form = QFormLayout( formAlignment=Qt.AlignLeft, labelAlignment=Qt.AlignLeft, fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow ) self._level_low_le = lineEditDecimalOrNone(self, self, "level_low", callback=lambda: self.update_levels() or self.reset_thresholds()) self._level_low_le.validator().setDefault(0) form.addRow("Low limit:", self._level_low_le) self._level_high_le = lineEditDecimalOrNone(self, self, "level_high", callback=lambda: self.update_levels() or self.reset_thresholds()) self._level_high_le.validator().setDefault(1) form.addRow("High limit:", self._level_high_le) lowslider = gui.hSlider( box, self, "threshold_low", minValue=0.0, maxValue=1.0, step=0.05, ticks=True, intOnly=False, createLabel=False, callback=self.update_levels) highslider = gui.hSlider( box, self, "threshold_high", minValue=0.0, maxValue=1.0, step=0.05, ticks=True, intOnly=False, createLabel=False, callback=self.update_levels) form.addRow("Low:", lowslider) form.addRow("High:", highslider) box.layout().addLayout(form) choose_xy.setDefaultWidget(box) view_menu.addAction(choose_xy) self.markings_integral = [] self.lsx = None # info about the X axis self.lsy = None # info about the Y axis self.data = None self.data_ids = {} def init_interface_data(self, data): same_domain = (self.data and data and data.domain == self.data.domain) if not same_domain: self.init_attr_values(data) def help_event(self, ev): pos = self.plot.vb.mapSceneToView(ev.scenePos()) sel = self._points_at_pos(pos) prepared = [] if sel is not None: data, vals, points = self.data[sel], self.data_values[sel], self.data_points[sel] for d, v, p in zip(data, vals, points): basic = "({}, {}): {}".format(p[0], p[1], v) variables = [ v for v in self.data.domain.metas + self.data.domain.class_vars if v not in [self.attr_x, self.attr_y]] features = ['{} = {}'.format(attr.name, d[attr]) for attr in variables] prepared.append("\n".join([basic] + features)) text = "\n\n".join(prepared) if text: text = ('<span style="white-space:pre">{}</span>' .format(escape(text))) QToolTip.showText(ev.screenPos(), text, widget=self.plotview) return True else: return False def reset_thresholds(self): self.threshold_low = 0. self.threshold_high = 1. def update_levels(self): if not self.data: return if not self.threshold_low < self.threshold_high: # TODO this belongs here, not in the parent self.parent.Warning.threshold_error() return else: self.parent.Warning.threshold_error.clear() if self.img.image is not None: levels = get_levels(self.img.image) else: levels = [0, 255] prec = pixels_to_decimals((levels[1] - levels[0])/1000) rounded_levels = [float_to_str_decimals(levels[0], prec), float_to_str_decimals(levels[1], prec)] self._level_low_le.validator().setDefault(rounded_levels[0]) self._level_high_le.validator().setDefault(rounded_levels[1]) self._level_low_le.setPlaceholderText(rounded_levels[0]) self._level_high_le.setPlaceholderText(rounded_levels[1]) ll = float(self.level_low) if self.level_low is not None else levels[0] lh = float(self.level_high) if self.level_high is not None else levels[1] ll_threshold = ll + (lh - ll) * self.threshold_low lh_threshold = ll + (lh - ll) * self.threshold_high self.img.setLevels([ll_threshold, lh_threshold]) def update_color_schema(self): if not self.data: return if self.parent.value_type == 1: dat = self.data.domain[self.parent.attr_value] if isinstance(dat, DiscreteVariable): # use a defined discrete palette self.img.setLookupTable(dat.colors) return # use a continuous palette data = self.color_cb.itemData(self.palette_index, role=Qt.UserRole) _, colors = max(data.items()) cols = color_palette_table(colors) self.img.setLookupTable(cols) def update_attr(self): self.update_view() def init_attr_values(self, data): domain = data.domain if data is not None else None self.xy_model.set_domain(domain) self.attr_x = self.xy_model[0] if self.xy_model else None self.attr_y = self.xy_model[1] if len(self.xy_model) >= 2 \ else self.attr_x def save_graph(self): saveplot.save_plot(self.plotview, FileFormat.img_writers) def set_data(self, data): if data: self.data = data self.data_ids = {e: i for i, e in enumerate(data.ids)} self.restore_selection_settings() else: self.data = None self.data_ids = {} def refresh_markings(self, di): refresh_integral_markings([{"draw": di}], self.markings_integral, self.parent.curveplot) def update_view(self): self.img.clear() self.img.setSelection(None) self.lsx = None self.lsy = None self.data_points = None self.data_values = None self.data_imagepixels = None if self.data and self.attr_x and self.attr_y: xat = self.data.domain[self.attr_x] yat = self.data.domain[self.attr_y] ndom = Orange.data.Domain([xat, yat]) datam = Orange.data.Table(ndom, self.data) coorx = datam.X[:, 0] coory = datam.X[:, 1] self.data_points = datam.X self.lsx = lsx = values_to_linspace(coorx) self.lsy = lsy = values_to_linspace(coory) if lsx[-1] * lsy[-1] > IMAGE_TOO_BIG: self.parent.Error.image_too_big(lsx[-1], lsy[-1]) return else: self.parent.Error.image_too_big.clear() di = {} if self.parent.value_type == 0: # integrals imethod = self.parent.integration_methods[self.parent.integration_method] if imethod != Integrate.PeakAt: datai = Integrate(methods=imethod, limits=[[self.parent.lowlim, self.parent.highlim]])(self.data) else: datai = Integrate(methods=imethod, limits=[[self.parent.choose, self.parent.choose]])(self.data) if np.any(self.parent.curveplot.selection_group): # curveplot can have a subset of curves on the input> match IDs ind = np.flatnonzero(self.parent.curveplot.selection_group)[0] dind = self.data_ids[self.parent.curveplot.data[ind].id] di = datai.domain.attributes[0].compute_value.draw_info(self.data[dind:dind+1]) d = datai.X[:, 0] else: dat = self.data.domain[self.parent.attr_value] ndom = Orange.data.Domain([dat]) d = Orange.data.Table(ndom, self.data).X[:, 0] self.refresh_markings(di) # set data imdata = np.ones((lsy[2], lsx[2])) * float("nan") xindex = index_values(coorx, lsx) yindex = index_values(coory, lsy) imdata[yindex, xindex] = d self.data_values = d self.data_imagepixels = np.vstack((yindex, xindex)).T self.img.setImage(imdata, autoLevels=False) self.img.setLevels([0, 1]) self.update_levels() self.update_color_schema() # shift centres of the pixels so that the axes are useful shiftx = _shift(lsx) shifty = _shift(lsy) left = lsx[0] - shiftx bottom = lsy[0] - shifty width = (lsx[1]-lsx[0]) + 2*shiftx height = (lsy[1]-lsy[0]) + 2*shifty self.img.setRect(QRectF(left, bottom, width, height)) self.refresh_img_selection() def refresh_img_selection(self): selected_px = np.zeros((self.lsy[2], self.lsx[2]), dtype=np.uint8) selected_px[self.data_imagepixels[:, 0], self.data_imagepixels[:, 1]] = self.selection_group self.img.setSelection(selected_px) def make_selection(self, selected, add): """Add selected indices to the selection.""" add_to_group, add_group, remove = selection_modifiers() if self.data and self.lsx and self.lsy: if add_to_group: # both keys - need to test it before add_group selnum = np.max(self.selection_group) elif add_group: selnum = np.max(self.selection_group) + 1 elif remove: selnum = 0 else: self.selection_group *= 0 selnum = 1 if selected is not None: self.selection_group[selected] = selnum self.refresh_img_selection() self.prepare_settings_for_saving() self.selection_changed.emit() def select_square(self, p1, p2, add): """ Select elements within a square drawn by the user. A selection needs to contain whole pixels """ x1, y1 = p1.x(), p1.y() x2, y2 = p2.x(), p2.y() polygon = [QPointF(x1, y1), QPointF(x2, y1), QPointF(x2, y2), QPointF(x1, y2), QPointF(x1, y1)] self.select_polygon(polygon, add) def select_polygon(self, polygon, add): """ Select by a polygon which has to contain whole pixels. """ if self.data and self.lsx and self.lsy: polygon = [(p.x(), p.y()) for p in polygon] # a polygon should contain all pixel shiftx = _shift(self.lsx) shifty = _shift(self.lsy) points_edges = [self.data_points + [[shiftx, shifty]], self.data_points + [[-shiftx, shifty]], self.data_points + [[shiftx, -shifty]], self.data_points + [[-shiftx, -shifty]]] inp = in_polygon(points_edges[0], polygon) for p in points_edges[1:]: inp *= in_polygon(p, polygon) self.make_selection(inp, add) def _points_at_pos(self, pos): if self.data and self.lsx and self.lsy: x, y = pos.x(), pos.y() distance = np.abs(self.data_points - [[x, y]]) sel = (distance[:, 0] < _shift(self.lsx)) * (distance[:, 1] < _shift(self.lsy)) return sel def select_by_click(self, pos, add): sel = self._points_at_pos(pos) self.make_selection(sel, add)
class OWPieChart(widget.OWWidget): name = "Pie Chart" description = "Make fun of Pie Charts." keywords = ["pie chart", "chart", "visualisation"] icon = "icons/PieChart.svg" priority = 700 class Inputs: data = Input("Data", Orange.data.Table) settingsHandler = DomainContextHandler() attribute = ContextSetting(None) split_var = ContextSetting(None) explode = Setting(False) graph_name = "scene" def __init__(self): super().__init__() self.dataset = None self.attrs = DomainModel(valid_types=Orange.data.DiscreteVariable, separators=False) cb = gui.comboBox(self.controlArea, self, "attribute", box=True, model=self.attrs, callback=self.update_scene, contentsLength=12) grid = QGridLayout() self.legend = gui.widgetBox(gui.indentedBox(cb.box), orientation=grid) grid.setColumnStretch(1, 1) grid.setHorizontalSpacing(6) self.legend_items = [] self.split_vars = DomainModel( valid_types=Orange.data.DiscreteVariable, separators=False, placeholder="None", ) self.split_combobox = gui.comboBox(self.controlArea, self, "split_var", box="Split by", model=self.split_vars, callback=self.update_scene) self.explode_checkbox = gui.checkBox(self.controlArea, self, "explode", "Explode pies", box=True, callback=self.update_scene) gui.rubber(self.controlArea) gui.widgetLabel( gui.hBox(self.controlArea, box=True), "The aim of this widget is to\n" "demonstrate that pie charts are\n" "a terrible visualization. Please\n" "don't use it for any other purpose.") self.scene = QGraphicsScene() self.view = QGraphicsView(self.scene) self.view.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform) self.mainArea.layout().addWidget(self.view) self.mainArea.setMinimumWidth(500) def sizeHint(self): return QSize(200, 150) # Horizontal size is regulated by mainArea @Inputs.data def set_data(self, dataset): if dataset is not None and (not bool(dataset) or not len(dataset.domain)): dataset = None self.closeContext() self.dataset = dataset self.attribute = None self.split_var = None domain = dataset.domain if dataset is not None else None self.attrs.set_domain(domain) self.split_vars.set_domain(domain) if dataset is not None: self.select_default_variables(domain) self.openContext(self.dataset) self.update_scene() def select_default_variables(self, domain): if len(self.attrs) > len(domain.class_vars): first_attr = self.split_vars[len(domain.class_vars)] else: first_attr = None if len(self.attrs): self.attribute, self.split_var = self.attrs[0], first_attr else: self.attribute, self.split_var = self.split_var, None def update_scene(self): self.scene.clear() if self.dataset is None or self.attribute is None: return dists, labels = self.compute_box_data() colors = self.attribute.colors for x, (dist, label) in enumerate(zip(dists, labels)): self.pie_chart(SCALE * x, 0, 0.8 * SCALE, dist, colors) self.pie_label(SCALE * x, 0, label) self.update_legend([QColor(*col) for col in colors], self.attribute.values) self.view.centerOn(SCALE * len(dists) / 2, 0) def update_legend(self, colors, labels): layout = self.legend.layout() while self.legend_items: w = self.legend_items.pop() layout.removeWidget(w) w.deleteLater() for row, (color, label) in enumerate(zip(colors, labels)): icon = QLabel() p = QPixmap(12, 12) p.fill(color) icon.setPixmap(p) label = QLabel(label) layout.addWidget(icon, row, 0) layout.addWidget(label, row, 1, alignment=Qt.AlignLeft) self.legend_items += (icon, label) def pie_chart(self, x, y, r, dist, colors): start_angle = 0 dist = np.asarray(dist) spans = dist / (float(np.sum(dist)) or 1) * 360 * 16 for span, color in zip(spans, colors): if not span: continue if self.explode: mid_ang = (start_angle + span / 2) / 360 / 16 * 2 * pi dx = r / 30 * cos(mid_ang) dy = r / 30 * sin(mid_ang) else: dx = dy = 0 ellipse = QGraphicsEllipseItem(x - r / 2 + dx, y - r / 2 - dy, r, r) if len(spans) > 1: ellipse.setStartAngle(start_angle) ellipse.setSpanAngle(span) ellipse.setBrush(QColor(*color)) self.scene.addItem(ellipse) start_angle += span def pie_label(self, x, y, label): if not label: return text = QGraphicsSimpleTextItem(label) for cut in range(1, len(label)): if text.boundingRect().width() < 0.95 * SCALE: break text = QGraphicsSimpleTextItem(label[:-cut] + "...") text.setPos(x - text.boundingRect().width() / 2, y + 0.5 * SCALE) self.scene.addItem(text) def compute_box_data(self): if self.split_var: return (contingency.get_contingency(self.dataset, self.attribute, self.split_var), self.split_var.values) else: return [ distribution.get_distribution(self.dataset, self.attribute) ], [""] def send_report(self): self.report_plot() text = "" if self.attribute is not None: text += "Box plot for '{}' ".format(self.attribute.name) if self.split_var is not None: text += "split by '{}'".format(self.split_var.name) if text: self.report_caption(text)
class OWHyper(OWWidget): name = "HyperSpectra" class Inputs: data = Input("Data", Orange.data.Table, default=True) class Outputs: selected_data = Output("Selection", Orange.data.Table, default=True) annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table) icon = "icons/hyper.svg" priority = 20 replaces = ["orangecontrib.infrared.widgets.owhyper.OWHyper"] settings_version = 3 settingsHandler = DomainContextHandler() imageplot = SettingProvider(ImagePlot) curveplot = SettingProvider(CurvePlotHyper) integration_method = Setting(0) integration_methods = Integrate.INTEGRALS value_type = Setting(0) attr_value = ContextSetting(None) lowlim = Setting(None) highlim = Setting(None) choose = Setting(None) graph_name = "imageplot.plotview" # defined so that the save button is shown class Warning(OWWidget.Warning): threshold_error = Msg("Low slider should be less than High") class Error(OWWidget.Warning): image_too_big = Msg("Image for chosen features is too big ({} x {}).") @classmethod def migrate_settings(cls, settings_, version): if version < 2: # delete the saved attr_value to prevent crashes try: del settings_["context_settings"][0].values["attr_value"] except: pass # migrate selection if version <= 2: try: current_context = settings_["context_settings"][0] selection = getattr(current_context, "selection", None) if selection is not None: selection = [(i, 1) for i in np.flatnonzero(np.array(selection))] settings_.setdefault("imageplot", {})["selection_group_saved"] = selection except: pass def __init__(self): super().__init__() dbox = gui.widgetBox(self.controlArea, "Image values") rbox = gui.radioButtons( dbox, self, "value_type", callback=self._change_integration) gui.appendRadioButton(rbox, "From spectra") self.box_values_spectra = gui.indentedBox(rbox) gui.comboBox( self.box_values_spectra, self, "integration_method", valueType=int, items=(a.name for a in self.integration_methods), callback=self._change_integral_type) gui.rubber(self.controlArea) gui.appendRadioButton(rbox, "Use feature") self.box_values_feature = gui.indentedBox(rbox) self.feature_value_model = DomainModel(DomainModel.METAS | DomainModel.CLASSES, valid_types=DomainModel.PRIMITIVE) self.feature_value = gui.comboBox( self.box_values_feature, self, "attr_value", callback=self.update_feature_value, model=self.feature_value_model, sendSelectedValue=True, valueType=str) splitter = QSplitter(self) splitter.setOrientation(Qt.Vertical) self.imageplot = ImagePlot(self) self.imageplot.selection_changed.connect(self.output_image_selection) self.curveplot = CurvePlotHyper(self, select=SELECTONE) self.curveplot.selection_changed.connect(self.redraw_data) self.curveplot.plot.vb.x_padding = 0.005 # pad view so that lines are not hidden splitter.addWidget(self.imageplot) splitter.addWidget(self.curveplot) self.mainArea.layout().addWidget(splitter) self.line1 = MovableVline(position=self.lowlim, label="", report=self.curveplot) self.line1.sigMoved.connect(lambda v: setattr(self, "lowlim", v)) self.line2 = MovableVline(position=self.highlim, label="", report=self.curveplot) self.line2.sigMoved.connect(lambda v: setattr(self, "highlim", v)) self.line3 = MovableVline(position=self.choose, label="", report=self.curveplot) self.line3.sigMoved.connect(lambda v: setattr(self, "choose", v)) for line in [self.line1, self.line2, self.line3]: line.sigMoveFinished.connect(self.changed_integral_range) self.curveplot.add_marking(line) line.hide() self.data = None self.disable_integral_range = False self.resize(900, 700) self._update_integration_type() # prepare interface according to the new context self.contextAboutToBeOpened.connect(lambda x: self.init_interface_data(x[0])) def init_interface_data(self, data): same_domain = (self.data and data and data.domain == self.data.domain) if not same_domain: self.init_attr_values(data) def output_image_selection(self): if not self.data: self.Outputs.selected_data.send(None) self.Outputs.annotated_data.send(None) self.curveplot.set_data(None) return indices = np.flatnonzero(self.imageplot.selection_group) annotated_data = create_groups_table(self.data, self.imageplot.selection_group) if annotated_data is not None: annotated_data.X = self.data.X # workaround for Orange's copying on domain conversio self.Outputs.annotated_data.send(annotated_data) selected = self.data[indices] self.Outputs.selected_data.send(selected if selected else None) if selected: self.curveplot.set_data(selected) else: self.curveplot.set_data(self.data) def init_attr_values(self, data): domain = data.domain if data is not None else None self.feature_value_model.set_domain(domain) self.attr_value = self.feature_value_model[0] if self.feature_value_model else None def redraw_data(self): self.imageplot.update_view() def update_feature_value(self): self.redraw_data() def _update_integration_type(self): self.line1.hide() self.line2.hide() self.line3.hide() if self.value_type == 0: self.box_values_spectra.setDisabled(False) self.box_values_feature.setDisabled(True) if self.integration_methods[self.integration_method] != Integrate.PeakAt: self.line1.show() self.line2.show() else: self.line3.show() elif self.value_type == 1: self.box_values_spectra.setDisabled(True) self.box_values_feature.setDisabled(False) QTest.qWait(1) # first update the interface def _change_integration(self): # change what to show on the image self._update_integration_type() self.redraw_data() def changed_integral_range(self): if self.disable_integral_range: return self.redraw_data() def _change_integral_type(self): self._change_integration() @Inputs.data def set_data(self, data): self.closeContext() def valid_context(data): if data is None: return False annotation_features = [v for v in data.domain.metas + data.domain.class_vars if isinstance(v, (DiscreteVariable, ContinuousVariable))] return len(annotation_features) >= 1 if valid_context(data): self.openContext(data) else: # to generate valid interface even if context was not loaded self.contextAboutToBeOpened.emit([data]) self.data = data self.imageplot.set_data(data) self.curveplot.set_data(data) self._init_integral_boundaries() self.imageplot.update_view() self.output_image_selection() def _init_integral_boundaries(self): # requires data in curveplot self.disable_integral_range = True if self.curveplot.data_x is not None and len(self.curveplot.data_x): minx = self.curveplot.data_x[0] maxx = self.curveplot.data_x[-1] else: minx = 0. maxx = 1. if self.lowlim is None or not minx <= self.lowlim <= maxx: self.lowlim = minx self.line1.setValue(self.lowlim) if self.highlim is None or not minx <= self.highlim <= maxx: self.highlim = maxx self.line2.setValue(self.highlim) if self.choose is None: self.choose = (minx + maxx)/2 elif self.choose < minx: self.choose = minx elif self.choose > maxx: self.choose = maxx self.line3.setValue(self.choose) self.disable_integral_range = False def save_graph(self): # directly call save_graph so it hides axes self.imageplot.save_graph()
class OWUnique(widget.OWWidget): name = 'Unique' icon = 'icons/Unique.svg' description = 'Filter instances unique by specified key attribute(s).' class Inputs: data = widget.Input("Data", Table) class Outputs: data = widget.Output("Data", Table) want_main_area = False TIEBREAKERS = { 'Last instance': itemgetter(-1), 'First instance': itemgetter(0), 'Middle instance': lambda seq: seq[len(seq) // 2], 'Random instance': np.random.choice, 'Discard non-unique instances': lambda seq: seq[0] if len(seq) == 1 else None } settingsHandler = settings.DomainContextHandler() selected_vars = settings.ContextSetting([]) tiebreaker = settings.Setting(next(iter(TIEBREAKERS))) autocommit = settings.Setting(True) def __init__(self): # Commit is thunked because autocommit redefines it # pylint: disable=unnecessary-lambda super().__init__() self.data = None self.var_model = DomainModel(parent=self, order=DomainModel.MIXED) var_list = gui.listView(self.controlArea, self, "selected_vars", box="Group by", model=self.var_model, callback=self.commit.deferred, viewType=ListViewSearch) var_list.setSelectionMode(var_list.ExtendedSelection) gui.comboBox(self.controlArea, self, 'tiebreaker', box=True, label='Instance to select in each group:', items=tuple(self.TIEBREAKERS), callback=self.commit.deferred, sendSelectedValue=True) gui.auto_commit(self.controlArea, self, 'autocommit', 'Commit', orientation=Qt.Horizontal) @Inputs.data def set_data(self, data): self.closeContext() self.data = data self.selected_vars = [] if data: self.var_model.set_domain(data.domain) self.selected_vars = self.var_model[:] self.openContext(data.domain) else: self.var_model.set_domain(None) self.commit.now() @gui.deferred def commit(self): if self.data is None: self.Outputs.data.send(None) else: self.Outputs.data.send(self._compute_unique_data()) def _compute_unique_data(self): uniques = {} keys = zip(*[ self.data.get_column_view(attr)[0] for attr in self.selected_vars or self.var_model ]) for i, key in enumerate(keys): uniques.setdefault(key, []).append(i) choose = self.TIEBREAKERS[self.tiebreaker] selection = sorted(x for x in (choose(inds) for inds in uniques.values()) if x is not None) if selection: return self.data[selection] else: return None
class OWTranspose(OWWidget): name = "矩阵转置" description = "转置数据表。" icon = "icons/Transpose.svg" priority = 2000 keywords = [] class Inputs: data = Input("Data", Table) class Outputs: data = Output("Data", Table, dynamic=False) GENERIC, FROM_META_ATTR = range(2) resizing_enabled = False want_main_area = False DEFAULT_PREFIX = "Feature" settingsHandler = DomainContextHandler() feature_type = ContextSetting(GENERIC) feature_name = ContextSetting("") feature_names_column = ContextSetting(None) auto_apply = Setting(True) class Error(OWWidget.Error): value_error = Msg("{}") def __init__(self): super().__init__() self.data = None box = gui.radioButtons(self.controlArea, self, "feature_type", box="特征名称", callback=lambda: self.apply()) button = gui.appendRadioButton(box, "通用") edit = gui.lineEdit(gui.indentedBox(box, gui.checkButtonOffsetHint(button)), self, "feature_name", placeholderText="Type a prefix ...", toolTip="自定义特征名称") edit.editingFinished.connect(self._apply_editing) self.meta_button = gui.appendRadioButton(box, "来自元属性:") self.feature_model = DomainModel(order=DomainModel.METAS, valid_types=StringVariable, alphabetical=True) self.feature_combo = gui.comboBox(gui.indentedBox( box, gui.checkButtonOffsetHint(button)), self, "feature_names_column", contentsLength=12, callback=self._feature_combo_changed, model=self.feature_model) self.apply_button = gui.auto_commit(self.controlArea, self, "auto_apply", "应用", box=False, commit=self.apply) self.apply_button.button.setAutoDefault(False) self.set_controls() def _apply_editing(self): self.feature_type = self.GENERIC self.feature_name = self.feature_name.strip() self.apply() def _feature_combo_changed(self): self.feature_type = self.FROM_META_ATTR self.apply() @Inputs.data def set_data(self, data): # Skip the context if the combo is empty: a context with # feature_model == None would then match all domains if self.feature_model: self.closeContext() self.data = data self.set_controls() if self.feature_model: self.openContext(data) self.apply() def set_controls(self): self.feature_model.set_domain(self.data and self.data.domain) self.meta_button.setEnabled(bool(self.feature_model)) if self.feature_model: self.feature_names_column = self.feature_model[0] self.feature_type = self.FROM_META_ATTR else: self.feature_names_column = None def apply(self): self.clear_messages() transposed = None if self.data: try: transposed = Table.transpose( self.data, self.feature_type == self.FROM_META_ATTR and self.feature_names_column, feature_name=self.feature_name or self.DEFAULT_PREFIX) except ValueError as e: self.Error.value_error(e) self.Outputs.data.send(transposed) def send_report(self): if self.feature_type == self.GENERIC: names = self.feature_name or self.DEFAULT_PREFIX else: names = "from meta attribute" if self.feature_names_column: names += " '{}'".format(self.feature_names_column.name) self.report_items("", [("Feature names", names)]) if self.data: self.report_data("Data", self.data)
class OWClusterAnalysis(widget.OWWidget): name = "Cluster Analysis" description = "Perform cluster analysis." icon = "icons/ClusterAnalysis.svg" priority = 2010 class Inputs: data = Input("Data", Table, default=True) genes = Input("Genes", Table) class Outputs: selected_data = Output("Selected Data", Table, default=True) annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table) contingency = Output("Contingency Table", Table) N_GENES_PER_CLUSTER_MAX = 10 N_MOST_ENRICHED_MAX = 50 CELL_SIZES = (14, 22, 30) settingsHandler = DomainContextHandler(metas_in_res=True) cluster_var = ContextSetting(None) selection = ContextSetting(set()) gene_selection = ContextSetting(0) differential_expression = ContextSetting(0) cell_size_ix = ContextSetting(2) _diff_exprs = ("high", "low", "either") n_genes_per_cluster = ContextSetting(3) n_most_enriched = ContextSetting(20) biclustering = ContextSetting(True) auto_apply = Setting(True) want_main_area = True def __init__(self): super().__init__() self.ca = None self.clusters = None self.data = None self.feature_model = DomainModel(valid_types=DiscreteVariable) self.gene_list = None self.model = None self.pvalues = None self._executor = ThreadExecutor() self._gene_selection_history = (self.gene_selection, self.gene_selection) self._task = None box = gui.vBox(self.controlArea, "Info") self.infobox = gui.widgetLabel(box, self._get_info_string()) box = gui.vBox(self.controlArea, "Cluster Variable") gui.comboBox(box, self, "cluster_var", sendSelectedValue=True, model=self.feature_model, callback=self._run_cluster_analysis) layout = QGridLayout() self.gene_selection_radio_group = gui.radioButtonsInBox( self.controlArea, self, "gene_selection", orientation=layout, box="Gene Selection", callback=self._gene_selection_changed) def conditional_set_gene_selection(id): def f(): if self.gene_selection == id: return self._set_gene_selection() return f layout.addWidget( gui.appendRadioButton(self.gene_selection_radio_group, "", addToLayout=False), 1, 1) cb = gui.hBox(None, margin=0) gui.widgetLabel(cb, "Top") self.n_genes_per_cluster_spin = gui.spin( cb, self, "n_genes_per_cluster", minv=1, maxv=self.N_GENES_PER_CLUSTER_MAX, controlWidth=60, alignment=Qt.AlignRight, callback=conditional_set_gene_selection(0)) gui.widgetLabel(cb, "genes per cluster") gui.rubber(cb) layout.addWidget(cb, 1, 2, Qt.AlignLeft) layout.addWidget( gui.appendRadioButton(self.gene_selection_radio_group, "", addToLayout=False), 2, 1) mb = gui.hBox(None, margin=0) gui.widgetLabel(mb, "Top") self.n_most_enriched_spin = gui.spin( mb, self, "n_most_enriched", minv=1, maxv=self.N_MOST_ENRICHED_MAX, controlWidth=60, alignment=Qt.AlignRight, callback=conditional_set_gene_selection(1)) gui.widgetLabel(mb, "highest enrichments") gui.rubber(mb) layout.addWidget(mb, 2, 2, Qt.AlignLeft) layout.addWidget( gui.appendRadioButton(self.gene_selection_radio_group, "", addToLayout=False, disabled=True), 3, 1) sb = gui.hBox(None, margin=0) gui.widgetLabel(sb, "User-provided list of genes") gui.rubber(sb) layout.addWidget(sb, 3, 2) layout = QGridLayout() self.differential_expression_radio_group = gui.radioButtonsInBox( self.controlArea, self, "differential_expression", orientation=layout, box="Differential Expression", callback=self._set_gene_selection) layout.addWidget( gui.appendRadioButton(self.differential_expression_radio_group, "Overexpressed in cluster", addToLayout=False), 1, 1) layout.addWidget( gui.appendRadioButton(self.differential_expression_radio_group, "Underexpressed in cluster", addToLayout=False), 2, 1) layout.addWidget( gui.appendRadioButton(self.differential_expression_radio_group, "Either", addToLayout=False), 3, 1) box = gui.vBox(self.controlArea, "Sorting and Zoom") gui.checkBox(box, self, "biclustering", "Biclustering of analysis results", callback=self._set_gene_selection) gui.radioButtons(box, self, "cell_size_ix", btnLabels=("S", "M", "L"), callback=lambda: self.tableview.set_cell_size( self.CELL_SIZES[self.cell_size_ix]), orientation=Qt.Horizontal) gui.rubber(self.controlArea) self.apply_button = gui.auto_commit(self.controlArea, self, "auto_apply", "&Apply", box=False) self.tableview = ContingencyTable(self) self.mainArea.layout().addWidget(self.tableview) def _get_current_gene_selection(self): return self._gene_selection_history[0] def _get_previous_gene_selection(self): return self._gene_selection_history[1] def _progress_gene_selection_history(self, new_gene_selection): self._gene_selection_history = (new_gene_selection, self._gene_selection_history[0]) def _get_info_string(self): formatstr = "Cells: {0}\nGenes: {1}\nClusters: {2}" if self.data: return formatstr.format(len(self.data), len(self.data.domain.attributes), len(self.cluster_var.values)) else: return formatstr.format(*["No input data"] * 3) @Inputs.data @check_sql_input def set_data(self, data): if self.feature_model: self.closeContext() self.data = data self.feature_model.set_domain(None) self.ca = None self.cluster_var = None self.columns = None self.clusters = None self.gene_list = None self.model = None self.pvalues = None self.n_genes_per_cluster_spin.setMaximum(self.N_GENES_PER_CLUSTER_MAX) self.n_most_enriched_spin.setMaximum(self.N_MOST_ENRICHED_MAX) if self.data: self.feature_model.set_domain(self.data.domain) if self.feature_model: self.openContext(self.data) if self.cluster_var is None: self.cluster_var = self.feature_model[0] self._run_cluster_analysis() else: self.tableview.clear() else: self.tableview.clear() @Inputs.genes def set_genes(self, data): self.Error.clear() gene_list_radio = self.gene_selection_radio_group.group.buttons()[2] if (data is None or GENE_AS_ATTRIBUTE_NAME not in data.attributes or not data.attributes[GENE_AS_ATTRIBUTE_NAME] and GENE_ID_COLUMN not in data.attributes or data.attributes[GENE_AS_ATTRIBUTE_NAME] and GENE_ID_ATTRIBUTE not in data.attributes): if data is not None: self.error( "Gene annotations missing in the input data. Use Gene Name Matching widget." ) self.gene_list = None gene_list_radio.setDisabled(True) if self.gene_selection == 2: self.gene_selection_radio_group.group.buttons()[ self._get_previous_gene_selection()].click() else: if data.attributes[GENE_AS_ATTRIBUTE_NAME]: gene_id_attribute = data.attributes.get( GENE_ID_ATTRIBUTE, None) self.gene_list = tuple( str(var.attributes[gene_id_attribute]) for var in data.domain.attributes if gene_id_attribute in var.attributes and var.attributes[gene_id_attribute] != "?") else: gene_id_column = data.attributes.get(GENE_ID_COLUMN, None) self.gene_list = tuple( str(v) for v in data.get_column_view(gene_id_column)[0] if v not in ("", "?")) gene_list_radio.setDisabled(False) if self.gene_selection == 2: self._set_gene_selection() else: gene_list_radio.click() def _run_cluster_analysis(self): self.infobox.setText(self._get_info_string()) gene_count = len(self.data.domain.attributes) cluster_count = len(self.cluster_var.values) self.n_genes_per_cluster_spin.setMaximum( min(self.N_GENES_PER_CLUSTER_MAX, gene_count // cluster_count)) self.n_most_enriched_spin.setMaximum( min(self.N_MOST_ENRICHED_MAX, gene_count)) # TODO: what happens if error occurs? If CA fails, widget should properly handle it. self._start_task_init( partial(ClusterAnalysis, self.data, self.cluster_var.name)) def _start_task_init(self, f): if self._task is not None: self.cancel() assert self._task is None self._task = Task("init") def callback(finished): if self._task.cancelled: raise KeyboardInterrupt() self.progressBarSet(finished * 50) f = partial(f, callback=callback) self.progressBarInit() self._task.future = self._executor.submit(f) self._task.watcher = FutureWatcher(self._task.future) self._task.watcher.done.connect(self._init_task_finished) def _start_task_gene_selection(self, f): if self._task is not None: self.cancel() assert self._task is None self._task = Task("gene_selection") def callback(finished): if self._task.cancelled: raise KeyboardInterrupt() self.progressBarSet(50 + finished * 50) f = partial(f, callback=callback) self.progressBarInit() self.progressBarSet(50) self._task.future = self._executor.submit(f) self._task.watcher = FutureWatcher(self._task.future) self._task.watcher.done.connect(self._gene_selection_task_finished) @Slot(concurrent.futures.Future) def _init_task_finished(self, f): assert self.thread() is QThread.currentThread() assert self._task is not None assert self._task.future is f assert f.done() self._task = None self.progressBarFinished() self.ca = f.result() self._set_gene_selection() @Slot(concurrent.futures.Future) def _gene_selection_task_finished(self, f): assert self.thread() is QThread.currentThread() assert self._task is not None assert self._task.future is f assert f.done() self._task = None self.progressBarFinished() self.clusters, genes, self.model, self.pvalues = f.result() genes = [str(gene) for gene in genes] self.columns = DiscreteVariable("Gene", genes, ordered=True) self.tableview.set_headers( self.clusters, self.columns.values, circles=True, cell_size=self.CELL_SIZES[self.cell_size_ix], bold_headers=False) def tooltip(i, j): return ( "<b>cluster</b>: {}<br /><b>gene</b>: {}<br /><b>fraction expressing</b>: {:.2f}<br />\ <b>p-value</b>: {:.2e}".format( self.clusters[i], self.columns.values[j], self.model[i, j], self.pvalues[i, j])) self.tableview.update_table(self.model, tooltip=tooltip) self._invalidate() def cancel(self): """ Cancel the current task (if any). """ if self._task is not None: self._task.cancel() assert self._task.future.done() # disconnect the `_task_finished` slot if self._task.type == "init": self._task.watcher.done.disconnect(self._init_task_finished) else: self._task.watcher.done.disconnect( self._gene_selection_task_finished) self._task = None def onDeleteWidget(self): self.cancel() super().onDeleteWidget() def _gene_selection_changed(self): if self.gene_selection != self._get_current_gene_selection(): self._progress_gene_selection_history(self.gene_selection) self.differential_expression_radio_group.setDisabled( self.gene_selection == 2) self._set_gene_selection() def _set_gene_selection(self): self.Warning.clear() if self.ca is not None and (self._task is None or self._task.type != "init"): if self.gene_selection == 0: f = partial(self.ca.enriched_genes_per_cluster, self.n_genes_per_cluster) elif self.gene_selection == 1: f = partial(self.ca.enriched_genes_data, self.n_most_enriched) else: if self.data is not None and GENE_ID_ATTRIBUTE not in self.data.attributes: self.error( "Gene annotations missing in the input data. Use Gene Name Matching widget." ) if self.gene_selection == 2: self.gene_selection_radio_group.group.buttons()[ self._get_previous_gene_selection()].click() return relevant_genes = tuple(self.ca.intersection(self.gene_list)) if len(relevant_genes) > self.N_MOST_ENRICHED_MAX: self.warning("Only first {} reference genes shown.".format( self.N_MOST_ENRICHED_MAX)) f = partial(self.ca.enriched_genes, relevant_genes[:self.N_MOST_ENRICHED_MAX]) f = partial( f, enrichment=self._diff_exprs[self.differential_expression], biclustering=self.biclustering) self._start_task_gene_selection(f) else: self._invalidate() def handleNewSignals(self): self._invalidate() def commit(self): if len(self.selection): cluster_ids = set() column_ids = set() for (ir, ic) in self.selection: cluster_ids.add(ir) column_ids.add(ic) new_domain = Domain([ self.data.domain[self.columns.values[col]] for col in column_ids ], self.data.domain.class_vars, self.data.domain.metas) selected_data = Values([ FilterDiscrete(self.cluster_var, [self.clusters[ir]]) for ir in cluster_ids ], conjunction=False)(self.data) selected_data = selected_data.transform(new_domain) annotated_data = create_annotated_table( self.data.transform(new_domain), np.where(np.in1d(self.data.ids, selected_data.ids, True))) else: selected_data = None annotated_data = create_annotated_table(self.data, []) if self.ca is not None and self._task is None: table = self.ca.create_contingency_table() else: table = None self.Outputs.selected_data.send(selected_data) self.Outputs.annotated_data.send(annotated_data) self.Outputs.contingency.send(table) def _invalidate(self): self.selection = self.tableview.get_selection() self.commit() def send_report(self): rows = None columns = None if self.data is not None: rows = self.cluster_var if rows in self.data.domain: rows = self.data.domain[rows] columns = self.columns if columns in self.data.domain: columns = self.data.domain[columns] self.report_items(( ("Rows", rows), ("Columns", columns), ))
def __init__(self): super().__init__() self.data = None self.valid_data = self.valid_group_data = None self.bar_items = [] self.curve_items = [] self.curve_descriptions = None self.binnings = [] self.last_click_idx = None self.drag_operation = self.DragNone self.key_operation = None self._user_var_bins = {} gui.listView(self.controlArea, self, "var", box="变量", model=DomainModel(valid_types=DomainModel.PRIMITIVE, separators=False), callback=self._on_var_changed) box = self.continuous_box = gui.vBox(self.controlArea, "分布") slider = gui.hSlider(box, self, "number_of_bins", label="Bin 宽度", orientation=Qt.Horizontal, minValue=0, maxValue=max(1, len(self.binnings) - 1), createLabel=False, callback=self._on_bins_changed) self.bin_width_label = gui.widgetLabel(slider.box) self.bin_width_label.setFixedWidth(35) self.bin_width_label.setAlignment(Qt.AlignRight) slider.sliderReleased.connect(self._on_bin_slider_released) gui.comboBox(box, self, "fitted_distribution", label="拟合分布", orientation=Qt.Horizontal, items=(name[0] for name in self.Fitters), callback=self._on_fitted_dist_changed) self.smoothing_box = gui.indentedBox(box, 40) gui.hSlider(self.smoothing_box, self, "kde_smoothing", label="平滑化", orientation=Qt.Horizontal, minValue=2, maxValue=20, callback=self.replot) gui.checkBox(box, self, "hide_bars", "隐藏柱子", stateWhenDisabled=False, callback=self._on_hide_bars_changed, disabled=not self.fitted_distribution) box = gui.vBox(self.controlArea, "列") gui.comboBox(box, self, "cvar", label="由...分割", orientation=Qt.Horizontal, model=DomainModel( placeholder="(None)", valid_types=(DiscreteVariable), ), callback=self._on_cvar_changed, contentsLength=18) gui.checkBox(box, self, "stacked_columns", "堆叠列", callback=self.replot) gui.checkBox(box, self, "show_probs", "显示概率", callback=self._on_show_probabilities_changed) gui.checkBox(box, self, "cumulative_distr", "Show cumulative distribution", callback=self.replot) gui.auto_apply(self.controlArea, self, commit=self.apply) self._set_smoothing_visibility() self._setup_plots() self._setup_legend()
def _add_controls(self): options = dict(labelWidth=75, orientation=Qt.Horizontal, sendSelectedValue=True, contentsLength=14) lat_lon_box = gui.vBox(self.controlArea, True) self.lat_lon_model = DomainModel(DomainModel.MIXED, valid_types=(ContinuousVariable, )) # Added by Jean 2020/04/25 for support of selecting Tile provider gui.comboBox(lat_lon_box, self, 'graph.tile_provider_key', label='Map:', items=list(TILE_PROVIDERS.keys()), callback=self.graph.update_tile_provider, **options) gui.comboBox(lat_lon_box, self, 'attr_lat', label='Latitude:', callback=self.setup_plot, model=self.lat_lon_model, **options) gui.comboBox(lat_lon_box, self, 'attr_lon', label='Longitude:', callback=self.setup_plot, model=self.lat_lon_model, **options) agg_box = gui.vBox(self.controlArea, True) self.agg_attr_model = DomainModel(valid_types=(ContinuousVariable, DiscreteVariable)) gui.comboBox(agg_box, self, 'agg_attr', label='Attribute:', callback=self.update_agg, model=self.agg_attr_model, **options) self.agg_func_combo = gui.comboBox(agg_box, self, 'agg_func', label='Agg.:', items=[DEFAULT_AGG_FUNC], callback=self.graph.update_colors, **options) # Modified by Jean 2020/05/13, set max to 3 a_slider = gui.hSlider(agg_box, self, 'admin_level', minValue=0, maxValue=4, step=1, label='Detail:', createLabel=False, callback=self.setup_plot) a_slider.setFixedWidth(176) visualization_box = gui.vBox(self.controlArea, True) b_slider = gui.hSlider(visualization_box, self, "binning_index", label="Bin width:", minValue=0, maxValue=max(1, len(self.binnings) - 1), createLabel=False, callback=self.graph.update_colors) b_slider.setFixedWidth(176) av_slider = gui.hSlider(visualization_box, self, "graph.alpha_value", minValue=0, maxValue=255, step=10, label="Opacity:", createLabel=False, callback=self.graph.update_colors) av_slider.setFixedWidth(176) gui.checkBox(visualization_box, self, "graph.show_legend", "Show legend", callback=self.graph.update_legend_visibility) # Added by Jean 2020/06/16 for support of selecting color palette av_slider.setFixedWidth(176) gui.comboBox( visualization_box, self, 'palette_key', label='Palette:', items=list(ContinuousPalettes.keys()), # items = [palette.friendly_name for palette in ContinuousPalettes.values()], callback=self.update_palette, **options) self.controlArea.layout().addStretch(100) plot_gui = OWPlotGUI(self) plot_gui.box_zoom_select(self.controlArea) gui.auto_send(self.controlArea, self, "auto_commit")
def __init__(self): super().__init__() self.map = map = LeafletMap(self) self.mainArea.layout().addWidget(map) self.selection = None self.data = None self.learner = None def selectionChanged(indices): self.selection = self.data[indices] if self.data is not None and indices else None self._indices = indices self.commit() map.selectionChanged.connect(selectionChanged) def _set_map_provider(): map.set_map_provider(self.TILE_PROVIDERS[self.tile_provider]) box = gui.vBox(self.controlArea, 'Map') gui.comboBox(box, self, 'tile_provider', orientation=Qt.Horizontal, label='Map:', items=tuple(self.TILE_PROVIDERS.keys()), sendSelectedValue=True, callback=_set_map_provider) self._latlon_model = DomainModel( parent=self, valid_types=ContinuousVariable) self._class_model = DomainModel( parent=self, placeholder='(None)', valid_types=DomainModel.PRIMITIVE) self._color_model = DomainModel( parent=self, placeholder='(Same color)', valid_types=DomainModel.PRIMITIVE) self._shape_model = DomainModel( parent=self, placeholder='(Same shape)', valid_types=DiscreteVariable) self._size_model = DomainModel( parent=self, placeholder='(Same size)', valid_types=ContinuousVariable) self._label_model = DomainModel( parent=self, placeholder='(No labels)') def _set_lat_long(): self.map.set_data(self.data, self.lat_attr, self.lon_attr) self.train_model() self._combo_lat = combo = gui.comboBox( box, self, 'lat_attr', orientation=Qt.Horizontal, label='Latitude:', sendSelectedValue=True, callback=_set_lat_long) combo.setModel(self._latlon_model) self._combo_lon = combo = gui.comboBox( box, self, 'lon_attr', orientation=Qt.Horizontal, label='Longitude:', sendSelectedValue=True, callback=_set_lat_long) combo.setModel(self._latlon_model) def _toggle_legend(): self.map.toggle_legend(self.show_legend) gui.checkBox(box, self, 'show_legend', label='Show legend', callback=_toggle_legend) box = gui.vBox(self.controlArea, 'Overlay') self._combo_class = combo = gui.comboBox( box, self, 'class_attr', orientation=Qt.Horizontal, label='Target:', sendSelectedValue=True, callback=self.train_model ) self.controls.class_attr.setModel(self._class_model) self.set_learner(self.learner) box = gui.vBox(self.controlArea, 'Points') self._combo_color = combo = gui.comboBox( box, self, 'color_attr', orientation=Qt.Horizontal, label='Color:', sendSelectedValue=True, callback=lambda: self.map.set_marker_color(self.color_attr)) combo.setModel(self._color_model) self._combo_label = combo = gui.comboBox( box, self, 'label_attr', orientation=Qt.Horizontal, label='Label:', sendSelectedValue=True, callback=lambda: self.map.set_marker_label(self.label_attr)) combo.setModel(self._label_model) self._combo_shape = combo = gui.comboBox( box, self, 'shape_attr', orientation=Qt.Horizontal, label='Shape:', sendSelectedValue=True, callback=lambda: self.map.set_marker_shape(self.shape_attr)) combo.setModel(self._shape_model) self._combo_size = combo = gui.comboBox( box, self, 'size_attr', orientation=Qt.Horizontal, label='Size:', sendSelectedValue=True, callback=lambda: self.map.set_marker_size(self.size_attr)) combo.setModel(self._size_model) def _set_opacity(): map.set_marker_opacity(self.opacity) def _set_zoom(): map.set_marker_size_coefficient(self.zoom) def _set_jittering(): map.set_jittering(self.jittering) def _set_clustering(): map.set_clustering(self.cluster_points) self._opacity_slider = gui.hSlider( box, self, 'opacity', None, 1, 100, 5, label='Opacity:', labelFormat=' %d%%', callback=_set_opacity) self._zoom_slider = gui.valueSlider( box, self, 'zoom', None, values=(20, 50, 100, 200, 300, 400, 500, 700, 1000), label='Symbol size:', labelFormat=' %d%%', callback=_set_zoom) self._jittering = gui.valueSlider( box, self, 'jittering', label='Jittering:', values=(0, .5, 1, 2, 5), labelFormat=' %.1f%%', ticks=True, callback=_set_jittering) self._clustering_check = gui.checkBox( box, self, 'cluster_points', label='Cluster points', callback=_set_clustering) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, 'autocommit', 'Send Selection') QTimer.singleShot(0, _set_map_provider) QTimer.singleShot(0, _toggle_legend) QTimer.singleShot(0, _set_opacity) QTimer.singleShot(0, _set_zoom) QTimer.singleShot(0, _set_jittering) QTimer.singleShot(0, _set_clustering)
class OWWordList(OWWidget): name = "Word List" description = "Create a list of words." icon = "icons/WordList.svg" priority = 1000 class Inputs: words = Input("Words", Table) class Outputs: selected_words = Output("Selected Words", Table) words = Output("Words", Table) class Warning(OWWidget.Warning): no_string_vars = Msg("Input needs at least one Text variable.") NONE, CACHED, LIBRARY = range(3) # library list modification types want_main_area = False resizing_enabled = True settingsHandler = DomainContextHandler() word_list_library: List[Dict] = Setting([ { "name": WordList.generate_word_list_name([]), "words": [] }, ]) word_list_index: int = Setting(0) words_var: Optional[StringVariable] = ContextSetting(None) update_rule_index: int = Setting(UpdateRules.INTERSECT) words: List[str] = Setting(None, schema_only=True) selected_words: Set[str] = Setting(set(), schema_only=True) def __init__(self): super().__init__(self) flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable self.library_model = PyListModel([], self, flags=flags) self.words_model = PyListModel([], self, flags=flags, enable_dnd=True) self.library_view: QListView = None self.words_view: ListView = None self.__input_words_model = DomainModel(valid_types=(StringVariable, )) self.__input_words: Optional[Table] = None self.__library_box: QGroupBox = gui.vBox(None, "Library") self.__input_box: QGroupBox = gui.vBox(None, "Input") self.__words_box: QGroupBox = gui.vBox(None, box=True) self.__update_rule_rb: QRadioButton = None self.__add_word_action: QAction = None self.__remove_word_action: QAction = None self._setup_gui() self._restore_state() self.settingsAboutToBePacked.connect(self._save_state) def _setup_gui(self): layout = QGridLayout() gui.widgetBox(self.controlArea, orientation=layout) self._setup_library_box() self._setup_input_box() self._setup_words_box() layout.addWidget(self.__library_box, 0, 0) layout.addWidget(self.__input_box, 1, 0) layout.addWidget(self.__words_box, 0, 1, 0, 1) def _setup_library_box(self): self.library_view = QListView( editTriggers=QListView.DoubleClicked | QListView.EditKeyPressed, minimumWidth=200, sizePolicy=QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Expanding), ) self.library_view.setItemDelegate(WordListItemDelegate(self)) self.library_view.setModel(self.library_model) self.library_view.selectionModel().selectionChanged.connect( self.__on_library_selection_changed) self.__library_box.layout().setSpacing(1) self.__library_box.layout().addWidget(self.library_view) actions_widget = ModelActionsWidget() actions_widget.layout().setSpacing(1) action = QAction("+", self) action.setToolTip("Add a new word list to the library") action.triggered.connect(self.__on_add_word_list) actions_widget.addAction(action) action = QAction("\N{MINUS SIGN}", self) action.setToolTip("Remove word list from library") action.triggered.connect(self.__on_remove_word_list) actions_widget.addAction(action) action = QAction("Update", self) action.setToolTip("Save changes in the editor to library") action.setShortcut(QKeySequence(QKeySequence.Save)) action.triggered.connect(self.__on_update_word_list) actions_widget.addAction(action) gui.rubber(actions_widget.layout()) action = QAction("More", self, toolTip="More actions") new_from_file = QAction("Import Words from File", self) new_from_file.triggered.connect(self.__on_import_word_list) save_to_file = QAction("Save Words to File", self) save_to_file.setShortcut(QKeySequence(QKeySequence.SaveAs)) save_to_file.triggered.connect(self.__on_save_word_list) menu = QMenu(actions_widget) menu.addAction(new_from_file) menu.addAction(save_to_file) action.setMenu(menu) button = actions_widget.addAction(action) button.setPopupMode(QToolButton.InstantPopup) self.__library_box.layout().addWidget(actions_widget) def __on_library_selection_changed(self, selected: QItemSelection, *_): index = [i.row() for i in selected.indexes()] if index: current = index[0] word_list: WordList = self.library_model[current] self.word_list_index = current self.selected_words = set() self.words_model.wrap(list(word_list.cached_words)) self._apply_update_rule() def __on_add_word_list(self): taken = [l.name for l in self.library_model] name = WordList.generate_word_list_name(taken) word_list = WordList(name, self.words_model[:]) self.library_model.append(word_list) self._set_selected_word_list(len(self.library_model) - 1) def __on_remove_word_list(self): index = self._get_selected_word_list_index() if index is not None: del self.library_model[index] self._set_selected_word_list(max(index - 1, 0)) self._apply_update_rule() def __on_update_word_list(self): self._set_word_list_modified(mod_type=self.LIBRARY) def __on_import_word_list(self): filename, _ = QFileDialog.getOpenFileName( self, "Open Word List", os.path.expanduser("~/"), "Text files (*.txt)\nAll files(*.*)") if filename: name = os.path.basename(filename) with open(filename, encoding="utf-8") as f: words = [line.strip() for line in f.readlines()] self.library_model.append(WordList(name, words, filename=filename)) self._set_selected_word_list(len(self.library_model) - 1) self._apply_update_rule() def __on_save_word_list(self): index = self._get_selected_word_list_index() if index is not None: word_list = self.library_model[index] filename = word_list.filename else: filename = os.path.expanduser("~/") filename, _ = QFileDialog.getSaveFileName( self, "Save Word List", filename, "Text files (*.txt)\nAll files(*.*)") if filename: head, tail = os.path.splitext(filename) if not tail: filename = head + ".txt" with open(filename, "w", encoding="utf-8") as f: for word in self.words_model: f.write(f"{word}\n") def _setup_input_box(self): gui.comboBox(self.__input_box, self, "words_var", label="Word variable:", orientation=Qt.Vertical, model=self.__input_words_model, callback=self._apply_update_rule) gui.radioButtons(self.__input_box, self, "update_rule_index", UpdateRules.ITEMS, label="Update: ", orientation=Qt.Vertical, callback=self.__on_update_rule_changed) self.__input_box.setEnabled(False) def __on_update_rule_changed(self): self._enable_words_actions() self._apply_update_rule() def _setup_words_box(self): self.words_view = ListView() self.words_view.drop_finished.connect(self.__on_words_data_changed) self.words_view.setModel(self.words_model) self.words_view.selectionModel().selectionChanged.connect( self.__on_words_selection_changed) self.words_model.dataChanged.connect(self.__on_words_data_changed) self.__words_box.layout().setSpacing(1) self.__words_box.layout().addWidget(self.words_view) actions_widget = ModelActionsWidget() actions_widget.layout().setSpacing(1) action = QAction("+", self.words_view, toolTip="Add a new word") action.triggered.connect(self.__on_add_word) actions_widget.addAction(action) self.__add_word_action = action action = QAction("\N{MINUS SIGN}", self, toolTip="Remove word") action.triggered.connect(self.__on_remove_word) actions_widget.addAction(action) self.__remove_word_action = action gui.rubber(actions_widget) action = QAction("Sort", self) action.setToolTip("Sort words alphabetically") action.triggered.connect(self.__on_apply_sorting) actions_widget.addAction(action) self.__words_box.layout().addWidget(actions_widget) def __on_words_data_changed(self): self._set_word_list_modified(mod_type=self.CACHED) self.commit() def __on_words_selection_changed(self): self.commit() def __on_add_word(self): row = self.words_model.rowCount() if not self.words_model.insertRow(self.words_model.rowCount()): return with disconnected(self.words_view.selectionModel().selectionChanged, self.__on_words_selection_changed): self._set_selected_words([0]) index = self.words_model.index(row, 0) self.words_view.setCurrentIndex(index) self.words_model.setItemData(index, {Qt.EditRole: ""}) self.words_view.edit(index) def __on_remove_word(self): rows = self.words_view.selectionModel().selectedRows(0) if not rows: return indices = sorted([row.row() for row in rows], reverse=True) with disconnected(self.words_view.selectionModel().selectionChanged, self.__on_words_selection_changed): for index in indices: self.words_model.removeRow(index) if self.words_model: self._set_selected_words([max(0, indices[-1] - 1)]) self.__on_words_data_changed() def __on_apply_sorting(self): if not self.words_model: return words = self.words_model[:] mask = np.zeros(len(words), dtype=bool) selection = self._get_selected_words_indices() if selection: mask[selection] = True indices = np.argsort(words) self.words_model.wrap([words[i] for i in indices]) self._set_word_list_modified(mod_type=self.CACHED) if selection: self._set_selected_words(list(np.flatnonzero(mask[indices]))) else: self.commit() @Inputs.words def set_words(self, words: Optional[Table]): self.closeContext() self.__input_words = words self._check_input_words() self._init_controls() self.openContext(self.__input_words) self._apply_update_rule() def _check_input_words(self): self.Warning.no_string_vars.clear() if self.__input_words: metas = self.__input_words.domain.metas if not any(isinstance(m, StringVariable) for m in metas): self.Warning.no_string_vars() self.__input_words = None def _init_controls(self): words = self.__input_words domain = words.domain if words is not None else None self.__input_words_model.set_domain(domain) if len(self.__input_words_model) > 0: self.words_var = self.__input_words_model[0] self.__input_box.setEnabled(bool(self.__input_words_model)) self._enable_words_actions() def _enable_words_actions(self): if bool(self.__input_words_model) \ and self.update_rule_index != UpdateRules.LIBRARY: self.words_view.setEditTriggers(QListView.NoEditTriggers) self.__add_word_action.setEnabled(False) self.__remove_word_action.setEnabled(False) else: self.words_view.setEditTriggers(QListView.DoubleClicked | QListView.EditKeyPressed) self.__add_word_action.setEnabled(True) self.__remove_word_action.setEnabled(True) def _apply_update_rule(self): lib_index = self._get_selected_word_list_index() lib_words, in_words, update_rule = [], [], UpdateRules.LIBRARY if lib_index is not None: lib_words = self.library_model[lib_index].cached_words else: lib_words = self.words_model[:] if self.__input_words is not None: in_words = self.__input_words.get_column_view(self.words_var)[0] in_words = list(in_words) update_rule = self.update_rule_index UpdateRules.update(self.words_model, lib_words, in_words, update_rule) if lib_index is not None: cached = self.library_model[lib_index].cached_words modified = WordList.NotModified if cached == self.words_model[:] \ else WordList.Modified self.library_model[lib_index].update_rule_flag = modified self._set_word_list_modified(mod_type=self.NONE) self.library_view.repaint() # Apply selection. selection_changed invokes commit(). # If there is no selection, call commit explicitly. if any(w in self.selected_words for w in self.words_model): self.set_selected_words() self.words_view.repaint() else: self.commit() def commit(self): selection = self._get_selected_words_indices() self.selected_words = set(np.array(self.words_model)[selection]) words, selected_words = None, None if self.words_model: words_var = StringVariable("Words") words_var.attributes = {"type": "words"} domain = Domain([], metas=[words_var]) _words = Table.from_list(domain, [[w] for w in self.words_model]) _words.name = "Words" if selection: selected_words = _words[selection] words = create_annotated_table(_words, selection) self.Outputs.words.send(words) self.Outputs.selected_words.send(selected_words) def _set_word_list_modified(self, mod_type): index = self._get_selected_word_list_index() if index is not None: if mod_type == self.LIBRARY: self.library_model[index].words = self.words_model[:] self.library_model[index].cached_words = self.words_model[:] self.library_model[index].update_rule_flag \ = WordList.NotModified elif mod_type == self.CACHED: self.library_model[index].cached_words = self.words_model[:] elif mod_type == self.NONE: pass else: raise NotImplementedError self.library_model.emitDataChanged(index) self.library_view.repaint() def _set_selected_word_list(self, index: int): sel_model: QItemSelectionModel = self.library_view.selectionModel() sel_model.select(self.library_model.index(index, 0), QItemSelectionModel.ClearAndSelect) def _get_selected_word_list_index(self) -> Optional[int]: rows = self.library_view.selectionModel().selectedRows() return rows[0].row() if rows else None def _set_selected_words(self, indices: List[int]): selection = QItemSelection() sel_model: QItemSelectionModel = self.words_view.selectionModel() for i in indices: selection.append(QItemSelectionRange(self.words_model.index(i, 0))) sel_model.select(selection, QItemSelectionModel.ClearAndSelect) def _get_selected_words_indices(self) -> List[int]: rows = self.words_view.selectionModel().selectedRows() return [row.row() for row in rows] def set_selected_words(self): if self.selected_words: indices = [ i for i, w in enumerate(self.words_model) if w in self.selected_words ] self._set_selected_words(indices) def _restore_state(self): source = [WordList.from_dict(s) for s in self.word_list_library] self.library_model.wrap(source) # __on_library_selection_changed() (invoked by _set_selected_word_list) # clears self.selected_words selected_words = self.selected_words self._set_selected_word_list(self.word_list_index) if self.words is not None: self.words_model.wrap(list(self.words)) self._set_word_list_modified(mod_type=self.CACHED) if selected_words: self.selected_words = selected_words self.set_selected_words() elif len(self.word_list_library) > self.word_list_index and \ self.word_list_library[self.word_list_index] != self.words: self.commit() def _save_state(self): self.word_list_library = [s.as_dict() for s in self.library_model] self.words = self.words_model[:] def send_report(self): library = self.library_model[self.word_list_index].name \ if self.library_model else "/" settings = [("Library", library)] if self.__input_words: self.report_data("Input Words", self.__input_words) settings.append(("Word variable", self.words_var)) rule = UpdateRules.ITEMS[self.update_rule_index] settings.append(("Update", rule)) self.report_items("Settings", settings) self.report_paragraph("Words", ", ".join(self.words_model[:]))
class OWChoropleth(OWWidget): """ This is to `OWDataProjectionWidget` what `OWChoroplethPlotGraph` is to `OWScatterPlotBase`. """ name = 'Choropleth Map' description = 'A thematic map in which areas are shaded in proportion ' \ 'to the measurement of the statistical variable being displayed.' icon = "icons/Choropleth.svg" priority = 120 class Inputs: data = Input("Data", Table, default=True) class Outputs: selected_data = Output("Selected Data", Table, default=True) annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table) # Added by Jean 2020/06/20, output agg data for future useage agg_data = Output("Aggregated data", Table) settings_version = 2 settingsHandler = DomainContextHandler() selection = Setting(None, schema_only=True) auto_commit = Setting(True) attr_lat = ContextSetting(None) attr_lon = ContextSetting(None) agg_attr = ContextSetting(None) # Added by Jean 2020/06/16 palette_key = Setting(next(iter(ContinuousPalettes))) agg_func = ContextSetting(DEFAULT_AGG_FUNC) admin_level = Setting(0) binning_index = Setting(0) GRAPH_CLASS = OWChoroplethPlotMapGraph graph = SettingProvider(OWChoroplethPlotMapGraph) graph_name = "graph.plot_widget.plotItem" input_changed = Signal(object) output_changed = Signal(object) class Error(OWWidget.Error): no_lat_lon_vars = Msg("Data has no latitude and longitude variables.") class Warning(OWWidget.Warning): no_region = Msg("{} points are not in any region.") def __init__(self): super().__init__() self.data = None self.data_ids = None # type: Optional[np.ndarray] self.agg_data = None # type: Optional[np.ndarray] self.region_ids = None # type: Optional[np.ndarray] self.choropleth_regions = [] self.binnings = [] self.input_changed.connect(self.set_input_summary) self.output_changed.connect(self.set_output_summary) self.setup_gui() def setup_gui(self): self._add_graph() self._add_controls() self.input_changed.emit(None) self.output_changed.emit(None) def _add_graph(self): box = gui.vBox(self.mainArea, True, margin=0) self.graph = self.GRAPH_CLASS(self, box) box.layout().addWidget(self.graph.plot_widget) def _add_controls(self): options = dict(labelWidth=75, orientation=Qt.Horizontal, sendSelectedValue=True, contentsLength=14) lat_lon_box = gui.vBox(self.controlArea, True) self.lat_lon_model = DomainModel(DomainModel.MIXED, valid_types=(ContinuousVariable, )) # Added by Jean 2020/04/25 for support of selecting Tile provider gui.comboBox(lat_lon_box, self, 'graph.tile_provider_key', label='Map:', items=list(TILE_PROVIDERS.keys()), callback=self.graph.update_tile_provider, **options) gui.comboBox(lat_lon_box, self, 'attr_lat', label='Latitude:', callback=self.setup_plot, model=self.lat_lon_model, **options) gui.comboBox(lat_lon_box, self, 'attr_lon', label='Longitude:', callback=self.setup_plot, model=self.lat_lon_model, **options) agg_box = gui.vBox(self.controlArea, True) self.agg_attr_model = DomainModel(valid_types=(ContinuousVariable, DiscreteVariable)) gui.comboBox(agg_box, self, 'agg_attr', label='Attribute:', callback=self.update_agg, model=self.agg_attr_model, **options) self.agg_func_combo = gui.comboBox(agg_box, self, 'agg_func', label='Agg.:', items=[DEFAULT_AGG_FUNC], callback=self.graph.update_colors, **options) # Modified by Jean 2020/05/13, set max to 3 a_slider = gui.hSlider(agg_box, self, 'admin_level', minValue=0, maxValue=4, step=1, label='Detail:', createLabel=False, callback=self.setup_plot) a_slider.setFixedWidth(176) visualization_box = gui.vBox(self.controlArea, True) b_slider = gui.hSlider(visualization_box, self, "binning_index", label="Bin width:", minValue=0, maxValue=max(1, len(self.binnings) - 1), createLabel=False, callback=self.graph.update_colors) b_slider.setFixedWidth(176) av_slider = gui.hSlider(visualization_box, self, "graph.alpha_value", minValue=0, maxValue=255, step=10, label="Opacity:", createLabel=False, callback=self.graph.update_colors) av_slider.setFixedWidth(176) gui.checkBox(visualization_box, self, "graph.show_legend", "Show legend", callback=self.graph.update_legend_visibility) # Added by Jean 2020/06/16 for support of selecting color palette av_slider.setFixedWidth(176) gui.comboBox( visualization_box, self, 'palette_key', label='Palette:', items=list(ContinuousPalettes.keys()), # items = [palette.friendly_name for palette in ContinuousPalettes.values()], callback=self.update_palette, **options) self.controlArea.layout().addStretch(100) plot_gui = OWPlotGUI(self) plot_gui.box_zoom_select(self.controlArea) gui.auto_send(self.controlArea, self, "auto_commit") @property def effective_variables(self): return [self.attr_lat, self.attr_lon] \ if self.attr_lat and self.attr_lon else [] @property def effective_data(self): return self.data.transform(Domain(self.effective_variables)) # Input @Inputs.data @check_sql_input def set_data(self, data): data_existed = self.data is not None effective_data = self.effective_data if data_existed else None self.closeContext() self.data = data self.Warning.no_region.clear() self.Error.no_lat_lon_vars.clear() self.agg_func = DEFAULT_AGG_FUNC self.check_data() self.init_attr_values() self.openContext(self.data) if not (data_existed and self.data is not None and array_equal(effective_data.X, self.effective_data.X)): self.clear(cache=True) self.input_changed.emit(data) self.setup_plot() self.update_agg() self.apply_selection() self.unconditional_commit() def check_data(self): if self.data is not None and (len(self.data) == 0 or len(self.data.domain) == 0): self.data = None def init_attr_values(self): lat, lon = None, None if self.data is not None: lat, lon = find_lat_lon(self.data, filter_hidden=True) if lat is None or lon is None: # we either find both or we don't have valid data self.Error.no_lat_lon_vars() self.data = None lat, lon = None, None domain = self.data.domain if self.data is not None else None self.lat_lon_model.set_domain(domain) self.agg_attr_model.set_domain(domain) self.agg_attr = domain.class_var if domain is not None else None self.attr_lat, self.attr_lon = lat, lon def set_input_summary(self, data): summary = str(len(data)) if data else self.info.NoInput self.info.set_input_summary(summary) def set_output_summary(self, data): summary = str(len(data)) if data else self.info.NoOutput self.info.set_output_summary(summary) def update_agg(self): current_agg = self.agg_func self.agg_func_combo.clear() if self.agg_attr is not None: new_aggs = list(AGG_FUNCS) if self.agg_attr.is_discrete: new_aggs = [agg for agg in AGG_FUNCS if AGG_FUNCS[agg].disc] elif self.agg_attr.is_time: new_aggs = [agg for agg in AGG_FUNCS if AGG_FUNCS[agg].time] else: new_aggs = [DEFAULT_AGG_FUNC] self.agg_func_combo.addItems(new_aggs) if current_agg in new_aggs: self.agg_func = current_agg else: self.agg_func = DEFAULT_AGG_FUNC self.graph.update_colors() # Added by Jean 2020/06/16 for support of selecting color palette def update_palette(self): # print(self.palette_key) # print(ContinuousPalettes[self.palette_key]) self.agg_attr.palette = ContinuousPalettes[self.palette_key] self.graph.update_colors() def setup_plot(self): self.controls.binning_index.setEnabled(not self.is_mode()) self.clear() self.graph.reset_graph() def apply_selection(self): if self.data is not None and self.selection is not None: index_group = np.array(self.selection).T selection = np.zeros(self.graph.n_ids, dtype=np.uint8) selection[index_group[0]] = index_group[1] self.graph.selection = selection self.graph.update_selection_colors() def selection_changed(self): sel = None if self.data and isinstance(self.data, SqlTable) \ else self.graph.selection self.selection = [(i, x) for i, x in enumerate(sel) if x] \ if sel is not None else None self.commit() def commit(self): self.send_data() def send_data(self): data, graph_sel = self.data, self.graph.get_selection() selected_data, ann_data = None, None if data: group_sel = np.zeros(len(data), dtype=int) if len(graph_sel): # we get selection by region ids so we have to map it to points for id, s in zip(self.region_ids, graph_sel): if s == 0: continue id_indices = np.where(self.data_ids == id)[0] group_sel[id_indices] = s else: graph_sel = [0] if np.sum(graph_sel) > 0: selected_data = create_groups_table(data, group_sel, False, "Group") if data is not None: if np.max(graph_sel) > 1: ann_data = create_groups_table(data, group_sel) else: ann_data = create_annotated_table(data, group_sel.astype(bool)) self.output_changed.emit(selected_data) self.Outputs.selected_data.send(selected_data) self.Outputs.annotated_data.send(ann_data) # Added by Jean 2020/06/20, output aggdata for future usage agg_data = self.agg_data # type: Optional[np.ndarray] region_ids = self.region_ids # type: Optional[np.ndarray] if agg_data is not None: agg_data = agg_data.reshape(agg_data.shape[0], 1) region_ids = region_ids.reshape(region_ids.shape[0], 1) agg_data = Table.from_numpy(None, agg_data, None, region_ids) self.Outputs.agg_data.send(agg_data) def recompute_binnings(self): if self.is_mode(): return if self.is_time(): self.binnings = time_binnings(self.agg_data, min_bins=3, max_bins=15) else: self.binnings = decimal_binnings(self.agg_data, min_bins=3, max_bins=15) max_bins = len(self.binnings) - 1 self.controls.binning_index.setMaximum(max_bins) self.binning_index = min(max_bins, self.binning_index) def get_binning(self) -> BinDefinition: return self.binnings[self.binning_index] def get_palette(self): if self.agg_func in ('Count', 'Count defined'): return DefaultContinuousPalette elif self.is_mode(): return LimitedDiscretePalette(MAX_COLORS) else: return self.agg_attr.palette def get_color_data(self): return self.get_reduced_agg_data() def get_color_labels(self): if self.is_mode(): return self.get_reduced_agg_data(return_labels=True) elif self.is_time(): return self.agg_attr.str_val def get_reduced_agg_data(self, return_labels=False): """ This returns agg data or its labels. It also merges infrequent data. """ needs_merging = self.is_mode() \ and len(self.agg_attr.values) >= MAX_COLORS if return_labels and not needs_merging: return self.agg_attr.values if not needs_merging: return self.agg_data dist = bincount(self.agg_data, max_val=len(self.agg_attr.values) - 1)[0] infrequent = np.zeros(len(self.agg_attr.values), dtype=bool) infrequent[np.argsort(dist)[:-(MAX_COLORS - 1)]] = True if return_labels: return [ value for value, infreq in zip(self.agg_attr.values, infrequent) if not infreq ] + ["Other"] else: result = self.agg_data.copy() freq_vals = [i for i, f in enumerate(infrequent) if not f] for i, infreq in enumerate(infrequent): if infreq: result[self.agg_data == i] = MAX_COLORS - 1 else: result[self.agg_data == i] = freq_vals.index(i) return result def is_mode(self): return self.agg_attr is not None and \ self.agg_attr.is_discrete and \ self.agg_func == 'Mode' def is_time(self): return self.agg_attr is not None and \ self.agg_attr.is_time and \ self.agg_func not in ('Count', 'Count defined') @memoize_method(3) def get_regions(self, lat_attr, lon_attr, admin): """ Map points to regions and get regions information. Returns: ndarray of ids corresponding to points, dict of region ids matched to their additional info, dict of region ids matched to their polygon """ latlon = np.c_[self.data.get_column_view(lat_attr)[0], self.data.get_column_view(lon_attr)[0]] region_info = latlon2region(latlon, admin) ids = np.array([region.get('_id') for region in region_info]) region_info = {info.get('_id'): info for info in region_info} self.data_ids = np.array(ids) no_region = np.sum(self.data_ids == None) if no_region: self.Warning.no_region(no_region) unique_ids = list(set(ids) - {None}) polygons = { _id: poly for _id, poly in zip(unique_ids, get_shape(unique_ids)) } return ids, region_info, polygons def get_grouped(self, lat_attr, lon_attr, admin, attr, agg_func): """ Get aggregation value for points grouped by regions. Returns: Series of aggregated values """ if attr is not None: data = self.data.get_column_view(attr)[0] else: data = np.ones(len(self.data)) ids, _, _ = self.get_regions(lat_attr, lon_attr, admin) result = pd.Series(data, dtype=float)\ .groupby(ids)\ .agg(AGG_FUNCS[agg_func].transform) return result def get_agg_data(self) -> np.ndarray: result = self.get_grouped(self.attr_lat, self.attr_lon, self.admin_level, self.agg_attr, self.agg_func) self.agg_data = np.array(result.values) self.region_ids = np.array(result.index) arg_region_sort = np.argsort(self.region_ids) self.region_ids = self.region_ids[arg_region_sort] self.agg_data = self.agg_data[arg_region_sort] self.recompute_binnings() # Added by Jean 2020/06/20, output aggregated data self.send_data() return self.agg_data def format_agg_val(self, value): if self.agg_func in ('Count', 'Count defined'): return f"{value:d}" else: return self.agg_attr.repr_val(value) def get_choropleth_regions(self) -> List[_ChoroplethRegion]: """Recalculate regions""" if self.attr_lat is None: # if we don't have locations we can't compute regions return [] _, region_info, polygons = self.get_regions(self.attr_lat, self.attr_lon, self.admin_level) regions = [] for _id in polygons: if isinstance(polygons[_id], MultiPolygon): # some regions consist of multiple polygons polys = list(polygons[_id].geoms) else: polys = [polygons[_id]] qpolys = [ self.poly2qpoly(transform(self.deg2canvas, poly)) for poly in polys ] regions.append( _ChoroplethRegion(id=_id, info=region_info[_id], qpolys=qpolys)) self.choropleth_regions = sorted(regions, key=lambda cr: cr.id) self.get_agg_data() return self.choropleth_regions @staticmethod def poly2qpoly(poly: Polygon) -> QPolygonF: return QPolygonF([QPointF(x, y) for x, y in poly.exterior.coords]) @staticmethod def deg2canvas(x, y): x, y = deg2norm(x, y) y = 1 - y return x, y def clear(self, cache=False): self.choropleth_regions = [] if cache: self.get_regions.cache_clear() def send_report(self): if self.data is None: return self.report_plot() def sizeHint(self): return QSize(1132, 708) def onDeleteWidget(self): super().onDeleteWidget() self.graph.plot_widget.getViewBox().deleteLater() self.graph.plot_widget.clear() self.graph.clear() def keyPressEvent(self, event): """Update the tip about using the modifier keys when selecting""" super().keyPressEvent(event) self.graph.update_tooltip(event.modifiers()) def keyReleaseEvent(self, event): """Update the tip about using the modifier keys when selecting""" super().keyReleaseEvent(event) self.graph.update_tooltip(event.modifiers()) def showEvent(self, ev): super().showEvent(ev) # reset the map on show event since before that we didn't know the # right resolution self.graph.update_view_range() def resizeEvent(self, ev): super().resizeEvent(ev) # when resizing we need to constantly reset the map so that new # portions are drawn self.graph.update_view_range(match_data=False) @classmethod def migrate_settings(cls, settings, version): if version < 2: settings["graph"] = {} rename_setting(settings, "admin", "admin_level") rename_setting(settings, "autocommit", "auto_commit") settings["graph"]["alpha_value"] = \ round(settings["opacity"] * 2.55) settings["graph"]["show_legend"] = settings["show_legend"] @classmethod def migrate_context(cls, context, version): if version < 2: migrate_str_to_variable(context, names="lat_attr", none_placeholder="") migrate_str_to_variable(context, names="lon_attr", none_placeholder="") migrate_str_to_variable(context, names="attr", none_placeholder="") rename_setting(context, "lat_attr", "attr_lat") rename_setting(context, "lon_attr", "attr_lon") rename_setting(context, "attr", "agg_attr") # old selection will not be ported rename_setting(context, "selection", "old_selection") if context.values["agg_func"][0] == "Max": context.values["agg_func"] = ("Maximal", context.values["agg_func"][1]) elif context.values["agg_func"][0] == "Min": context.values["agg_func"] = ("Minimal", context.values["agg_func"][1]) elif context.values["agg_func"][0] == "Std": context.values["agg_func"] = ("Std.", context.values["agg_func"][1])
class OWScatterPlot(OWWidget): """Scatterplot visualization with explorative analysis and intelligent data visualization enhancements.""" name = 'Scatter Plot' description = "Interactive scatter plot visualization with " \ "intelligent data visualization enhancements." icon = "icons/ScatterPlot.svg" priority = 140 class Inputs: data = Input("Data", Table, default=True) data_subset = Input("Data Subset", Table) features = Input("Features", AttributeList) class Outputs: selected_data = Output("Selected Data", Table, default=True) annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table) features = Output("Features", AttributeList, dynamic=False) settings_version = 2 settingsHandler = DomainContextHandler() auto_send_selection = Setting(True) auto_sample = Setting(True) toolbar_selection = Setting(0) attr_x = ContextSetting(None) attr_y = ContextSetting(None) #: Serialized selection state to be restored selection_group = Setting(None, schema_only=True) graph = SettingProvider(OWScatterPlotGraph) jitter_sizes = [0, 0.1, 0.5, 1, 2, 3, 4, 5, 7, 10] graph_name = "graph.plot_widget.plotItem" class Information(OWWidget.Information): sampled_sql = Msg("Large SQL table; showing a sample.") def __init__(self): super().__init__() box = gui.vBox(self.mainArea, True, margin=0) self.graph = OWScatterPlotGraph(self, box, "ScatterPlot") box.layout().addWidget(self.graph.plot_widget) plot = self.graph.plot_widget axispen = QPen(self.palette().color(QPalette.Text)) axis = plot.getAxis("bottom") axis.setPen(axispen) axis = plot.getAxis("left") axis.setPen(axispen) self.data = None # Orange.data.Table self.subset_data = None # Orange.data.Table self.sql_data = None # Orange.data.sql.table.SqlTable self.attribute_selection_list = None # list of Orange.data.Variable self.__timer = QTimer(self, interval=1200) self.__timer.timeout.connect(self.add_data) #: Remember the saved state to restore self.__pending_selection_restore = self.selection_group self.selection_group = None common_options = dict( labelWidth=50, orientation=Qt.Horizontal, sendSelectedValue=True, valueType=str) box = gui.vBox(self.controlArea, "Axis Data") dmod = DomainModel self.xy_model = DomainModel(dmod.MIXED, valid_types=dmod.PRIMITIVE) self.cb_attr_x = gui.comboBox( box, self, "attr_x", label="Axis x:", callback=self.update_attr, model=self.xy_model, **common_options) self.cb_attr_y = gui.comboBox( box, self, "attr_y", label="Axis y:", callback=self.update_attr, model=self.xy_model, **common_options) vizrank_box = gui.hBox(box) gui.separator(vizrank_box, width=common_options["labelWidth"]) self.vizrank, self.vizrank_button = ScatterPlotVizRank.add_vizrank( vizrank_box, self, "Find Informative Projections", self.set_attr) gui.separator(box) g = self.graph.gui g.add_widgets([g.JitterSizeSlider, g.JitterNumericValues], box) self.sampling = gui.auto_commit( self.controlArea, self, "auto_sample", "Sample", box="Sampling", callback=self.switch_sampling, commit=lambda: self.add_data(1)) self.sampling.setVisible(False) g.point_properties_box(self.controlArea) self.models = [self.xy_model] + g.points_models box_plot_prop = gui.vBox(self.controlArea, "Plot Properties") g.add_widgets([g.ShowLegend, g.ShowGridLines, g.ToolTipShowsAll, g.ClassDensity, g.RegressionLine, g.LabelOnlySelected], box_plot_prop) self.graph.box_zoom_select(self.controlArea) self.controlArea.layout().addStretch(100) self.icons = gui.attributeIconDict p = self.graph.plot_widget.palette() self.graph.set_palette(p) gui.auto_commit(self.controlArea, self, "auto_send_selection", "Send Selection", "Send Automatically") self.graph.zoom_actions(self) def keyPressEvent(self, event): super().keyPressEvent(event) self.graph.update_tooltip(event.modifiers()) def keyReleaseEvent(self, event): super().keyReleaseEvent(event) self.graph.update_tooltip(event.modifiers()) def reset_graph_data(self, *_): if self.data is not None: self.graph.rescale_data() self.update_graph() def _vizrank_color_change(self): self.vizrank.initialize() is_enabled = self.data is not None and not self.data.is_sparse() and \ len([v for v in chain(self.data.domain.variables, self.data.domain.metas) if v.is_primitive]) > 2\ and len(self.data) > 1 self.vizrank_button.setEnabled( is_enabled and self.graph.attr_color is not None and not np.isnan(self.data.get_column_view(self.graph.attr_color)[0].astype(float)).all()) if is_enabled and self.graph.attr_color is None: self.vizrank_button.setToolTip("Color variable has to be selected.") else: self.vizrank_button.setToolTip("") @Inputs.data def set_data(self, data): self.clear_messages() self.Information.sampled_sql.clear() self.__timer.stop() self.sampling.setVisible(False) self.sql_data = None if isinstance(data, SqlTable): if data.approx_len() < 4000: data = Table(data) else: self.Information.sampled_sql() self.sql_data = data data_sample = data.sample_time(0.8, no_cache=True) data_sample.download_data(2000, partial=True) data = Table(data_sample) self.sampling.setVisible(True) if self.auto_sample: self.__timer.start() if data is not None and (len(data) == 0 or len(data.domain) == 0): data = None if self.data and data and self.data.checksum() == data.checksum(): return self.closeContext() same_domain = (self.data and data and data.domain.checksum() == self.data.domain.checksum()) self.data = data if not same_domain: self.init_attr_values() self.openContext(self.data) self._vizrank_color_change() def findvar(name, iterable): """Find a Orange.data.Variable in `iterable` by name""" for el in iterable: if isinstance(el, Orange.data.Variable) and el.name == name: return el return None # handle restored settings from < 3.3.9 when attr_* were stored # by name if isinstance(self.attr_x, str): self.attr_x = findvar(self.attr_x, self.xy_model) if isinstance(self.attr_y, str): self.attr_y = findvar(self.attr_y, self.xy_model) if isinstance(self.graph.attr_label, str): self.graph.attr_label = findvar( self.graph.attr_label, self.graph.gui.label_model) if isinstance(self.graph.attr_color, str): self.graph.attr_color = findvar( self.graph.attr_color, self.graph.gui.color_model) if isinstance(self.graph.attr_shape, str): self.graph.attr_shape = findvar( self.graph.attr_shape, self.graph.gui.shape_model) if isinstance(self.graph.attr_size, str): self.graph.attr_size = findvar( self.graph.attr_size, self.graph.gui.size_model) def add_data(self, time=0.4): if self.data and len(self.data) > 2000: return self.__timer.stop() data_sample = self.sql_data.sample_time(time, no_cache=True) if data_sample: data_sample.download_data(2000, partial=True) data = Table(data_sample) self.data = Table.concatenate((self.data, data), axis=0) self.handleNewSignals() def switch_sampling(self): self.__timer.stop() if self.auto_sample and self.sql_data: self.add_data() self.__timer.start() @Inputs.data_subset def set_subset_data(self, subset_data): self.warning() if isinstance(subset_data, SqlTable): if subset_data.approx_len() < AUTO_DL_LIMIT: subset_data = Table(subset_data) else: self.warning("Data subset does not support large Sql tables") subset_data = None self.subset_data = subset_data self.controls.graph.alpha_value.setEnabled(subset_data is None) # called when all signals are received, so the graph is updated only once def handleNewSignals(self): self.graph.new_data(self.data, self.subset_data) if self.attribute_selection_list and self.graph.domain is not None and \ all(attr in self.graph.domain for attr in self.attribute_selection_list): self.attr_x = self.attribute_selection_list[0] self.attr_y = self.attribute_selection_list[1] self.attribute_selection_list = None self.update_graph() self.cb_class_density.setEnabled(self.graph.can_draw_density()) self.cb_reg_line.setEnabled(self.graph.can_draw_regresssion_line()) if self.data is not None and self.__pending_selection_restore is not None: self.apply_selection(self.__pending_selection_restore) self.__pending_selection_restore = None self.unconditional_commit() def apply_selection(self, selection): """Apply `selection` to the current plot.""" if self.data is not None: self.graph.selection = np.zeros(len(self.data), dtype=np.uint8) self.selection_group = [x for x in selection if x[0] < len(self.data)] selection_array = np.array(self.selection_group).T self.graph.selection[selection_array[0]] = selection_array[1] self.graph.update_colors(keep_colors=True) @Inputs.features def set_shown_attributes(self, attributes): if attributes and len(attributes) >= 2: self.attribute_selection_list = attributes[:2] else: self.attribute_selection_list = None def init_attr_values(self): data = self.data domain = data.domain if data and len(data) else None self.xy_model.set_domain(domain) self.attr_x = self.xy_model[0] if self.xy_model else None self.attr_y = self.xy_model[1] if len(self.xy_model) >= 2 \ else self.attr_x self.graph.set_domain(data) def set_attr(self, attr_x, attr_y): self.attr_x, self.attr_y = attr_x, attr_y self.update_attr() def update_attr(self): self.update_graph() self.cb_class_density.setEnabled(self.graph.can_draw_density()) self.cb_reg_line.setEnabled(self.graph.can_draw_regresssion_line()) self.send_features() def update_colors(self): self._vizrank_color_change() self.cb_class_density.setEnabled(self.graph.can_draw_density()) def update_density(self): self.update_graph(reset_view=False) def update_regression_line(self): self.update_graph(reset_view=False) def update_graph(self, reset_view=True, **_): self.graph.zoomStack = [] if self.graph.data is None: return self.graph.update_data(self.attr_x, self.attr_y, reset_view) def selection_changed(self): # Store current selection in a setting that is stored in workflow if isinstance(self.data, SqlTable): selection = None elif self.data is not None: selection = self.graph.get_selection() else: selection = None if selection is not None and len(selection): self.selection_group = list(zip(selection, self.graph.selection[selection])) else: self.selection_group = None self.commit() def send_data(self): # TODO: Implement selection for sql data def _get_selected(): if not len(selection): return None return create_groups_table(data, graph.selection, False, "Group") def _get_annotated(): if graph.selection is not None and np.max(graph.selection) > 1: return create_groups_table(data, graph.selection) else: return create_annotated_table(data, selection) graph = self.graph data = self.data selection = graph.get_selection() self.Outputs.annotated_data.send(_get_annotated()) self.Outputs.selected_data.send(_get_selected()) def send_features(self): features = [attr for attr in [self.attr_x, self.attr_y] if attr] self.Outputs.features.send(features or None) def commit(self): self.send_data() self.send_features() def get_widget_name_extension(self): if self.data is not None: return "{} vs {}".format(self.attr_x.name, self.attr_y.name) def send_report(self): if self.data is None: return def name(var): return var and var.name caption = report.render_items_vert(( ("Color", name(self.graph.attr_color)), ("Label", name(self.graph.attr_label)), ("Shape", name(self.graph.attr_shape)), ("Size", name(self.graph.attr_size)), ("Jittering", (self.attr_x.is_discrete or self.attr_y.is_discrete or self.graph.jitter_continuous) and self.graph.jitter_size))) self.report_plot() if caption: self.report_caption(caption) def onDeleteWidget(self): super().onDeleteWidget() self.graph.plot_widget.getViewBox().deleteLater() self.graph.plot_widget.clear() @classmethod def migrate_settings(cls, settings, version): if version < 2 and "selection" in settings and settings["selection"]: settings["selection_group"] = [(a, 1) for a in settings["selection"]]
def __init__(self): super().__init__() self.data = None self.valid_data = self.valid_group_data = None self.bar_items = [] self.curve_items = [] self.curve_descriptions = None self.binnings = [] self.last_click_idx = None self.drag_operation = self.DragNone self.key_operation = None self._user_var_bins = {} varview = gui.listView(self.controlArea, self, "var", box="Variable", model=DomainModel( valid_types=DomainModel.PRIMITIVE, separators=False), callback=self._on_var_changed, viewType=ListViewSearch) gui.checkBox(varview.box, self, "sort_by_freq", "Sort categories by frequency", callback=self._on_sort_by_freq, stateWhenDisabled=False) box = self.continuous_box = gui.vBox(self.controlArea, "Distribution") slider = gui.hSlider(box, self, "number_of_bins", label="Bin width", orientation=Qt.Horizontal, minValue=0, maxValue=max(1, len(self.binnings) - 1), createLabel=False, callback=self._on_bins_changed) self.bin_width_label = gui.widgetLabel(slider.box) self.bin_width_label.setFixedWidth(35) self.bin_width_label.setAlignment(Qt.AlignRight) slider.sliderReleased.connect(self._on_bin_slider_released) gui.comboBox(box, self, "fitted_distribution", label="Fitted distribution", orientation=Qt.Horizontal, items=(name[0] for name in self.Fitters), callback=self._on_fitted_dist_changed) self.smoothing_box = gui.indentedBox(box, 40) gui.hSlider(self.smoothing_box, self, "kde_smoothing", label="Smoothing", orientation=Qt.Horizontal, minValue=2, maxValue=20, callback=self.replot) gui.checkBox(box, self, "hide_bars", "Hide bars", stateWhenDisabled=False, callback=self._on_hide_bars_changed, disabled=not self.fitted_distribution) box = gui.vBox(self.controlArea, "Columns") gui.comboBox(box, self, "cvar", label="Split by", orientation=Qt.Horizontal, searchable=True, model=DomainModel( placeholder="(None)", valid_types=(DiscreteVariable), ), callback=self._on_cvar_changed, contentsLength=18) gui.checkBox(box, self, "stacked_columns", "Stack columns", callback=self.replot) gui.checkBox(box, self, "show_probs", "Show probabilities", callback=self._on_show_probabilities_changed) gui.checkBox(box, self, "cumulative_distr", "Show cumulative distribution", callback=self._on_show_cumulative) gui.auto_apply(self.controlArea, self, commit=self.apply) self.info.set_input_summary(self.info.NoInput) self.info.set_output_summary(self.info.NoOutput) self._set_smoothing_visibility() self._setup_plots() self._setup_legend()
class OWSieveDiagram(OWWidget): name = "Sieve Diagram" description = "Visualize the observed and expected frequencies " \ "for a combination of values." icon = "icons/SieveDiagram.svg" priority = 200 class Inputs: data = Input("Data", Table, default=True) features = Input("Features", AttributeList) class Outputs: selected_data = Output("Selected Data", Table, default=True) annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table) graph_name = "canvas" want_control_area = False settings_version = 1 settingsHandler = DomainContextHandler() attr_x = ContextSetting(None) attr_y = ContextSetting(None) selection = ContextSetting(set()) def __init__(self): # pylint: disable=missing-docstring super().__init__() self.data = self.discrete_data = None self.attrs = [] self.input_features = None self.areas = [] self.selection = set() self.attr_box = gui.hBox(self.mainArea) self.domain_model = DomainModel(valid_types=DomainModel.PRIMITIVE) combo_args = dict( widget=self.attr_box, master=self, contentsLength=12, callback=self.update_attr, sendSelectedValue=True, valueType=str, model=self.domain_model) fixed_size = (QSizePolicy.Fixed, QSizePolicy.Fixed) gui.comboBox(value="attr_x", **combo_args) gui.widgetLabel(self.attr_box, "\u2715", sizePolicy=fixed_size) gui.comboBox(value="attr_y", **combo_args) self.vizrank, self.vizrank_button = SieveRank.add_vizrank( self.attr_box, self, "Score Combinations", self.set_attr) self.vizrank_button.setSizePolicy(*fixed_size) self.canvas = QGraphicsScene() self.canvasView = ViewWithPress( self.canvas, self.mainArea, handler=self.reset_selection) self.mainArea.layout().addWidget(self.canvasView) self.canvasView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.canvasView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) def sizeHint(self): return QSize(450, 550) def resizeEvent(self, event): super().resizeEvent(event) self.update_graph() def showEvent(self, event): super().showEvent(event) self.update_graph() @classmethod def migrate_context(cls, context, version): if not version: settings.rename_setting(context, "attrX", "attr_x") settings.rename_setting(context, "attrY", "attr_y") settings.migrate_str_to_variable(context) @Inputs.data def set_data(self, data): """ Discretize continuous attributes, and put all attributes and discrete metas into self.attrs. Select the first two attributes unless context overrides this. Method `resolve_shown_attributes` is called to use the attributes from the input, if it exists and matches the attributes in the data. Remove selection; again let the context override this. Initialize the vizrank dialog, but don't show it. Args: data (Table): input data """ if isinstance(data, SqlTable) and data.approx_len() > LARGE_TABLE: data = data.sample_time(DEFAULT_SAMPLE_TIME) self.closeContext() self.data = data self.areas = [] self.selection = set() if self.data is None: self.attrs[:] = [] self.domain_model.set_domain(None) self.discrete_data = None else: self.domain_model.set_domain(data.domain) self.attrs = [x for x in self.domain_model if isinstance(x, Variable)] if self.attrs: self.attr_x = self.attrs[0] self.attr_y = self.attrs[len(self.attrs) > 1] else: self.attr_x = self.attr_y = None self.areas = [] self.selection = set() self.openContext(self.data) if self.data: self.discrete_data = self.sparse_to_dense(data, True) self.resolve_shown_attributes() self.update_graph() self.update_selection() self.vizrank.initialize() self.vizrank_button.setEnabled( self.data is not None and len(self.data) > 1 and len(self.data.domain.attributes) > 1 and not self.data.is_sparse()) def set_attr(self, attr_x, attr_y): self.attr_x, self.attr_y = attr_x, attr_y self.update_attr() def update_attr(self): """Update the graph and selection.""" self.selection = set() self.discrete_data = self.sparse_to_dense(self.data) self.update_graph() self.update_selection() def sparse_to_dense(self, data, init=False): """ Extracts two selected columns from sparse matrix. GH-2260 """ def discretizer(data): if any(attr.is_continuous for attr in chain(data.domain.variables, data.domain.metas)): discretize = Discretize( method=EqualFreq(n=4), remove_const=False, discretize_classes=True, discretize_metas=True) return discretize(data).to_dense() return data if not data.is_sparse() and not init: return self.discrete_data if data.is_sparse(): attrs = {self.attr_x, self.attr_y} new_domain = data.domain.select_columns(attrs) data = Table.from_table(new_domain, data) return discretizer(data) @Inputs.features def set_input_features(self, attr_list): """ Handler for the Features signal. The method stores the attributes and calls `resolve_shown_attributes` Args: attr_list (AttributeList): data from the signal """ self.input_features = attr_list self.resolve_shown_attributes() self.update_selection() def resolve_shown_attributes(self): """ Use the attributes from the input signal if the signal is present and at least two attributes appear in the domain. If there are multiple, use the first two. Combos are disabled if inputs are used. """ self.warning() self.attr_box.setEnabled(True) if not self.input_features: # None or empty return features = [f for f in self.input_features if f in self.domain_model] if not features: self.warning( "Features from the input signal are not present in the data") return old_attrs = self.attr_x, self.attr_y self.attr_x, self.attr_y = [f for f in (features * 2)[:2]] self.attr_box.setEnabled(False) if (self.attr_x, self.attr_y) != old_attrs: self.selection = set() self.update_graph() def reset_selection(self): self.selection = set() self.update_selection() def select_area(self, area, event): """ Add or remove the clicked area from the selection Args: area (QRect): the area that is clicked event (QEvent): event description """ if event.button() != Qt.LeftButton: return index = self.areas.index(area) if event.modifiers() & Qt.ControlModifier: self.selection ^= {index} else: self.selection = {index} self.update_selection() def update_selection(self): """ Update the graph (pen width) to show the current selection. Filter and output the data. """ if self.areas is None or not self.selection: self.Outputs.selected_data.send(None) self.Outputs.annotated_data.send(create_annotated_table(self.data, [])) return filts = [] for i, area in enumerate(self.areas): if i in self.selection: width = 4 val_x, val_y = area.value_pair filts.append( filter.Values([ filter.FilterDiscrete(self.attr_x.name, [val_x]), filter.FilterDiscrete(self.attr_y.name, [val_y]) ])) else: width = 1 pen = area.pen() pen.setWidth(width) area.setPen(pen) if len(filts) == 1: filts = filts[0] else: filts = filter.Values(filts, conjunction=False) selection = filts(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 update_graph(self): # Function uses weird names like r, g, b, but it does it with utmost # caution, hence # pylint: disable=invalid-name """Update the graph.""" def text(txt, *args, **kwargs): return CanvasText(self.canvas, "", html_text=to_html(txt), *args, **kwargs) def width(txt): return text(txt, 0, 0, show=False).boundingRect().width() def fmt(val): return str(int(val)) if val % 1 == 0 else "{:.2f}".format(val) def show_pearson(rect, pearson, pen_width): """ Color the given rectangle according to its corresponding standardized Pearson residual. Args: rect (QRect): the rectangle being drawn pearson (float): signed standardized pearson residual pen_width (int): pen width (bolder pen is used for selection) """ r = rect.rect() x, y, w, h = r.x(), r.y(), r.width(), r.height() if w == 0 or h == 0: return r = b = 255 if pearson > 0: r = g = max(255 - 20 * pearson, 55) elif pearson < 0: b = g = max(255 + 20 * pearson, 55) else: r = g = b = 224 rect.setBrush(QBrush(QColor(r, g, b))) pen_color = QColor(255 * (r == 255), 255 * (g == 255), 255 * (b == 255)) pen = QPen(pen_color, pen_width) rect.setPen(pen) if pearson > 0: pearson = min(pearson, 10) dist = 20 - 1.6 * pearson else: pearson = max(pearson, -10) dist = 20 - 8 * pearson pen.setWidth(1) def _offseted_line(ax, ay): r = QGraphicsLineItem(x + ax, y + ay, x + (ax or w), y + (ay or h)) self.canvas.addItem(r) r.setPen(pen) ax = dist while ax < w: _offseted_line(ax, 0) ax += dist ay = dist while ay < h: _offseted_line(0, ay) ay += dist def make_tooltip(): """Create the tooltip. The function uses local variables from the enclosing scope.""" # pylint: disable=undefined-loop-variable def _oper(attr, txt): if self.data.domain[attr.name] is ddomain[attr.name]: return "=" return " " if txt[0] in "<≥" else " in " return ( "<b>{attr_x}{xeq}{xval_name}</b>: {obs_x}/{n} ({p_x:.0f} %)". format(attr_x=to_html(attr_x.name), xeq=_oper(attr_x, xval_name), xval_name=to_html(xval_name), obs_x=fmt(chi.probs_x[x] * n), n=int(n), p_x=100 * chi.probs_x[x]) + "<br/>" + "<b>{attr_y}{yeq}{yval_name}</b>: {obs_y}/{n} ({p_y:.0f} %)". format(attr_y=to_html(attr_y.name), yeq=_oper(attr_y, yval_name), yval_name=to_html(yval_name), obs_y=fmt(chi.probs_y[y] * n), n=int(n), p_y=100 * chi.probs_y[y]) + "<hr/>" + """<b>combination of values: </b><br/> expected {exp} ({p_exp:.0f} %)<br/> observed {obs} ({p_obs:.0f} %)""". format(exp=fmt(chi.expected[y, x]), p_exp=100 * chi.expected[y, x] / n, obs=fmt(chi.observed[y, x]), p_obs=100 * chi.observed[y, x] / n)) for item in self.canvas.items(): self.canvas.removeItem(item) if self.data is None or len(self.data) == 0 or \ self.attr_x is None or self.attr_y is None: return ddomain = self.discrete_data.domain attr_x, attr_y = self.attr_x, self.attr_y disc_x, disc_y = ddomain[attr_x.name], ddomain[attr_y.name] view = self.canvasView chi = ChiSqStats(self.discrete_data, disc_x, disc_y) max_ylabel_w = max((width(val) for val in disc_y.values), default=0) max_ylabel_w = min(max_ylabel_w, 200) x_off = width(attr_x.name) + max_ylabel_w y_off = 15 square_size = min(view.width() - x_off - 35, view.height() - y_off - 80) square_size = max(square_size, 10) self.canvasView.setSceneRect(0, 0, view.width(), view.height()) if not disc_x.values or not disc_y.values: text_ = "Features {} and {} have no values".format(disc_x, disc_y) \ if not disc_x.values and \ not disc_y.values and \ disc_x != disc_y \ else \ "Feature {} has no values".format( disc_x if not disc_x.values else disc_y) text(text_, view.width() / 2 + 70, view.height() / 2, Qt.AlignRight | Qt.AlignVCenter) return n = chi.n curr_x = x_off max_xlabel_h = 0 self.areas = [] for x, (px, xval_name) in enumerate(zip(chi.probs_x, disc_x.values)): if px == 0: continue width = square_size * px curr_y = y_off for y in range(len(chi.probs_y) - 1, -1, -1): # bottom-up order py = chi.probs_y[y] yval_name = disc_y.values[y] if py == 0: continue height = square_size * py selected = len(self.areas) in self.selection rect = CanvasRectangle( self.canvas, curr_x + 2, curr_y + 2, width - 4, height - 4, z=-10, onclick=self.select_area) rect.value_pair = x, y self.areas.append(rect) show_pearson(rect, chi.residuals[y, x], 3 * selected) rect.setToolTip(make_tooltip()) if x == 0: text(yval_name, x_off, curr_y + height / 2, Qt.AlignRight | Qt.AlignVCenter) curr_y += height xl = text(xval_name, curr_x + width / 2, y_off + square_size, Qt.AlignHCenter | Qt.AlignTop) max_xlabel_h = max(int(xl.boundingRect().height()), max_xlabel_h) curr_x += width bottom = y_off + square_size + max_xlabel_h text(attr_y.name, 0, y_off + square_size / 2, Qt.AlignLeft | Qt.AlignVCenter, bold=True, vertical=True) text(attr_x.name, x_off + square_size / 2, bottom, Qt.AlignHCenter | Qt.AlignTop, bold=True) bottom += 30 xl = text("χ²={:.2f}, p={:.3f}".format(chi.chisq, chi.p), 0, bottom) # Assume similar height for both lines text("N = " + fmt(chi.n), 0, bottom - xl.boundingRect().height()) def get_widget_name_extension(self): if self.data is not None: return "{} vs {}".format(self.attr_x.name, self.attr_y.name) def send_report(self): self.report_plot()
class OWMap(widget.OWWidget): name = 'Geo Map' description = 'Show data points on a world map.' icon = "icons/GeoMap.svg" priority = 100 class Inputs: data = Input("Data", Table, default=True) data_subset = Input("Data Subset", Table) learner = Input("Learner", Learner) class Outputs: selected_data = Output("Selected Data", Table, default=True) annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table) replaces = [ "Orange.widgets.visualize.owmap.OWMap", ] settingsHandler = settings.DomainContextHandler() want_main_area = True autocommit = settings.Setting(True) tile_provider = settings.Setting('Black and white') lat_attr = settings.ContextSetting('') lon_attr = settings.ContextSetting('') class_attr = settings.ContextSetting('(None)') color_attr = settings.ContextSetting('') label_attr = settings.ContextSetting('') shape_attr = settings.ContextSetting('') size_attr = settings.ContextSetting('') opacity = settings.Setting(100) zoom = settings.Setting(100) jittering = settings.Setting(0) cluster_points = settings.Setting(False) show_legend = settings.Setting(True) TILE_PROVIDERS = OrderedDict(( ('Black and white', 'OpenStreetMap.BlackAndWhite'), ('OpenStreetMap', 'OpenStreetMap.Mapnik'), ('Topographic', 'OpenTopoMap'), ('Satellite', 'Esri.WorldImagery'), ('Print', 'Stamen.TonerLite'), ('Dark', 'CartoDB.DarkMatter'), ('Watercolor', 'Stamen.Watercolor'), )) class Error(widget.OWWidget.Error): model_error = widget.Msg("Error predicting: {}") learner_error = widget.Msg("Error modelling: {}") class Warning(widget.OWWidget.Warning): all_nan_slice = widget.Msg('Latitude and/or longitude has no defined values (is all-NaN)') UserAdviceMessages = [ widget.Message( 'Select markers by holding <b><kbd>Shift</kbd></b> key and dragging ' 'a rectangle around them. Clear the selection by clicking anywhere.', 'shift-selection') ] graph_name = "map" def __init__(self): super().__init__() self.map = map = LeafletMap(self) # type: LeafletMap self.mainArea.layout().addWidget(map) self.selection = None self.data = None self.learner = None def selectionChanged(indices): self.selection = self.data[indices] if self.data is not None and indices else None self._indices = indices self.commit() map.selectionChanged.connect(selectionChanged) def _set_map_provider(): map.set_map_provider(self.TILE_PROVIDERS[self.tile_provider]) box = gui.vBox(self.controlArea, 'Map') gui.comboBox(box, self, 'tile_provider', orientation=Qt.Horizontal, label='Map:', items=tuple(self.TILE_PROVIDERS.keys()), sendSelectedValue=True, callback=_set_map_provider) self._latlon_model = DomainModel( parent=self, valid_types=ContinuousVariable) self._class_model = DomainModel( parent=self, placeholder='(None)', valid_types=DomainModel.PRIMITIVE) self._color_model = DomainModel( parent=self, placeholder='(Same color)', valid_types=DomainModel.PRIMITIVE) self._shape_model = DomainModel( parent=self, placeholder='(Same shape)', valid_types=DiscreteVariable) self._size_model = DomainModel( parent=self, placeholder='(Same size)', valid_types=ContinuousVariable) self._label_model = DomainModel( parent=self, placeholder='(No labels)') def _set_lat_long(): self.map.set_data(self.data, self.lat_attr, self.lon_attr) self.train_model() self._combo_lat = combo = gui.comboBox( box, self, 'lat_attr', orientation=Qt.Horizontal, label='Latitude:', sendSelectedValue=True, callback=_set_lat_long) combo.setModel(self._latlon_model) self._combo_lon = combo = gui.comboBox( box, self, 'lon_attr', orientation=Qt.Horizontal, label='Longitude:', sendSelectedValue=True, callback=_set_lat_long) combo.setModel(self._latlon_model) def _toggle_legend(): self.map.toggle_legend(self.show_legend) gui.checkBox(box, self, 'show_legend', label='Show legend', callback=_toggle_legend) box = gui.vBox(self.controlArea, 'Overlay') self._combo_class = combo = gui.comboBox( box, self, 'class_attr', orientation=Qt.Horizontal, label='Target:', sendSelectedValue=True, callback=self.train_model ) self.controls.class_attr.setModel(self._class_model) self.set_learner(self.learner) box = gui.vBox(self.controlArea, 'Points') self._combo_color = combo = gui.comboBox( box, self, 'color_attr', orientation=Qt.Horizontal, label='Color:', sendSelectedValue=True, callback=lambda: self.map.set_marker_color(self.color_attr)) combo.setModel(self._color_model) self._combo_label = combo = gui.comboBox( box, self, 'label_attr', orientation=Qt.Horizontal, label='Label:', sendSelectedValue=True, callback=lambda: self.map.set_marker_label(self.label_attr)) combo.setModel(self._label_model) self._combo_shape = combo = gui.comboBox( box, self, 'shape_attr', orientation=Qt.Horizontal, label='Shape:', sendSelectedValue=True, callback=lambda: self.map.set_marker_shape(self.shape_attr)) combo.setModel(self._shape_model) self._combo_size = combo = gui.comboBox( box, self, 'size_attr', orientation=Qt.Horizontal, label='Size:', sendSelectedValue=True, callback=lambda: self.map.set_marker_size(self.size_attr)) combo.setModel(self._size_model) def _set_opacity(): map.set_marker_opacity(self.opacity) def _set_zoom(): map.set_marker_size_coefficient(self.zoom) def _set_jittering(): map.set_jittering(self.jittering) def _set_clustering(): map.set_clustering(self.cluster_points) self._opacity_slider = gui.hSlider( box, self, 'opacity', None, 1, 100, 5, label='Opacity:', labelFormat=' %d%%', callback=_set_opacity) self._zoom_slider = gui.valueSlider( box, self, 'zoom', None, values=(20, 50, 100, 200, 300, 400, 500, 700, 1000), label='Symbol size:', labelFormat=' %d%%', callback=_set_zoom) self._jittering = gui.valueSlider( box, self, 'jittering', label='Jittering:', values=(0, .5, 1, 2, 5), labelFormat=' %.1f%%', ticks=True, callback=_set_jittering) self._clustering_check = gui.checkBox( box, self, 'cluster_points', label='Cluster points', callback=_set_clustering) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, 'autocommit', 'Send Selection') QTimer.singleShot(0, _set_map_provider) QTimer.singleShot(0, _toggle_legend) QTimer.singleShot(0, _set_opacity) QTimer.singleShot(0, _set_zoom) QTimer.singleShot(0, _set_jittering) QTimer.singleShot(0, _set_clustering) autocommit = settings.Setting(True) def onDeleteWidget(self): self.map.stop = True super().onDeleteWidget() def commit(self): self.Outputs.selected_data.send(self.selection) self.Outputs.annotated_data.send(create_annotated_table(self.data, self._indices)) @Inputs.data def set_data(self, data): self.data = data self.closeContext() if data is None or not len(data): return self.clear() domain = data is not None and data.domain for model in (self._latlon_model, self._class_model, self._color_model, self._shape_model, self._size_model, self._label_model): model.set_domain(domain) lat, lon = find_lat_lon(data) if lat is not None and lon is not None: self._combo_lat.setCurrentIndex(-1 if lat is None else self._latlon_model.indexOf(lat)) self._combo_lon.setCurrentIndex(-1 if lat is None else self._latlon_model.indexOf(lon)) self.lat_attr = lat.name self.lon_attr = lon.name if data.domain.class_var: self.color_attr = data.domain.class_var.name elif len(self._color_model): self._combo_color.setCurrentIndex(0) if len(self._shape_model): self._combo_shape.setCurrentIndex(0) if len(self._size_model): self._combo_size.setCurrentIndex(0) if len(self._label_model): self._combo_label.setCurrentIndex(0) if len(self._class_model): self._combo_class.setCurrentIndex(0) self.openContext(data) self.map.set_data(self.data, self.lat_attr, self.lon_attr, redraw=False) self.map.set_marker_color(self.color_attr, update=False) self.map.set_marker_label(self.label_attr, update=False) self.map.set_marker_shape(self.shape_attr, update=False) self.map.set_marker_size(self.size_attr, update=True) @Inputs.data_subset def set_subset(self, subset): self.map.set_subset_ids(subset.ids if subset is not None else np.array([])) def handleNewSignals(self): super().handleNewSignals() self.train_model() @Inputs.learner def set_learner(self, learner): self.learner = learner self.controls.class_attr.setEnabled(learner is not None) self.controls.class_attr.setToolTip( 'Needs a Learner input for modelling.' if learner is None else '') def train_model(self): model = None self.Error.clear() if self.data and self.learner and self.class_attr != '(None)': domain = self.data.domain if self.lat_attr and self.lon_attr and self.class_attr in domain: domain = Domain([domain[self.lat_attr], domain[self.lon_attr]], [domain[self.class_attr]]) # I am retarded train = Table.from_table(domain, self.data) try: model = self.learner(train) except Exception as e: self.Error.learner_error(e) self.map.set_model(model) def disable_some_controls(self, disabled): tooltip = ( "Available when the zoom is close enough to have " "<{} points in the viewport.".format(self.map.N_POINTS_PER_ITER) if disabled else '') for widget in (self._combo_label, self._combo_shape, self._clustering_check): widget.setDisabled(disabled) widget.setToolTip(tooltip) def clear(self): self.map.set_data(None, '', '') for model in (self._latlon_model, self._class_model, self._color_model, self._shape_model, self._size_model, self._label_model): model.set_domain(None) self.lat_attr = self.lon_attr = self.class_attr = self.color_attr = \ self.label_attr = self.shape_attr = self.size_attr = None
class OWSelectRows(widget.OWWidget): name = "Select Rows" id = "Orange.widgets.data.file" description = "Select rows from the data based on values of variables." icon = "icons/SelectRows.svg" priority = 100 category = "Data" keywords = ["filter"] class Inputs: data = Input("Data", Table) class Outputs: matching_data = Output("Matching Data", Table, default=True) unmatched_data = Output("Unmatched Data", Table) annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table) want_main_area = False settingsHandler = SelectRowsContextHandler() conditions = ContextSetting([]) update_on_change = Setting(True) purge_attributes = Setting(False, schema_only=True) purge_classes = Setting(False, schema_only=True) auto_commit = Setting(True) settings_version = 2 Operators = { ContinuousVariable: [ (FilterContinuous.Equal, "equals"), (FilterContinuous.NotEqual, "is not"), (FilterContinuous.Less, "is below"), (FilterContinuous.LessEqual, "is at most"), (FilterContinuous.Greater, "is greater than"), (FilterContinuous.GreaterEqual, "is at least"), (FilterContinuous.Between, "is between"), (FilterContinuous.Outside, "is outside"), (FilterContinuous.IsDefined, "is defined"), ], DiscreteVariable: [(FilterDiscreteType.Equal, "is"), (FilterDiscreteType.NotEqual, "is not"), (FilterDiscreteType.In, "is one of"), (FilterDiscreteType.IsDefined, "is defined")], StringVariable: [ (FilterString.Equal, "equals"), (FilterString.NotEqual, "is not"), (FilterString.Less, "is before"), (FilterString.LessEqual, "is equal or before"), (FilterString.Greater, "is after"), (FilterString.GreaterEqual, "is equal or after"), (FilterString.Between, "is between"), (FilterString.Outside, "is outside"), (FilterString.Contains, "contains"), (FilterString.StartsWith, "begins with"), (FilterString.EndsWith, "ends with"), (FilterString.IsDefined, "is defined"), ] } Operators[TimeVariable] = Operators[ContinuousVariable] AllTypes = {} for _all_name, _all_type, _all_ops in (("All variables", 0, [ (None, "are defined") ]), ("All numeric variables", 2, [ (v, _plural(t)) for v, t in Operators[ContinuousVariable] ]), ("All string variables", 3, [(v, _plural(t)) for v, t in Operators[StringVariable]])): Operators[_all_name] = _all_ops AllTypes[_all_name] = _all_type operator_names = { vtype: [name for _, name in filters] for vtype, filters in Operators.items() } class Error(widget.OWWidget.Error): parsing_error = Msg("{}") def __init__(self): super().__init__() self.old_purge_classes = True self.conditions = [] self.last_output_conditions = None self.data = None self.data_desc = self.match_desc = self.nonmatch_desc = None self.variable_model = DomainModel([ list(self.AllTypes), DomainModel.Separator, DomainModel.CLASSES, DomainModel.ATTRIBUTES, DomainModel.METAS ]) box = gui.vBox(self.controlArea, 'Conditions', stretch=100) self.cond_list = QTableWidget(box, showGrid=False, selectionMode=QTableWidget.NoSelection) box.layout().addWidget(self.cond_list) self.cond_list.setColumnCount(4) self.cond_list.setRowCount(0) self.cond_list.verticalHeader().hide() self.cond_list.horizontalHeader().hide() for i in range(3): self.cond_list.horizontalHeader().setSectionResizeMode( i, QHeaderView.Stretch) self.cond_list.horizontalHeader().resizeSection(3, 30) self.cond_list.viewport().setBackgroundRole(QPalette.Window) box2 = gui.hBox(box) gui.rubber(box2) self.add_button = gui.button(box2, self, "Add Condition", callback=self.add_row) self.add_all_button = gui.button(box2, self, "Add All Variables", callback=self.add_all) self.remove_all_button = gui.button(box2, self, "Remove All", callback=self.remove_all) gui.rubber(box2) boxes = gui.widgetBox(self.controlArea, orientation=QHBoxLayout()) layout = boxes.layout() box_setting = gui.vBox(boxes, addToLayout=False, box=True) self.cb_pa = gui.checkBox(box_setting, self, "purge_attributes", "Remove unused features", callback=self.conditions_changed) gui.separator(box_setting, height=1) self.cb_pc = gui.checkBox(box_setting, self, "purge_classes", "Remove unused classes", callback=self.conditions_changed) layout.addWidget(box_setting, 1) self.report_button.setFixedWidth(120) gui.rubber(self.buttonsArea.layout()) layout.addWidget(self.buttonsArea) acbox = gui.auto_send(None, self, "auto_commit") layout.addWidget(acbox, 1) layout.setAlignment(acbox, Qt.AlignBottom) self.info.set_input_summary(self.info.NoInput) self.info.set_output_summary(self.info.NoOutput) self.set_data(None) self.resize(600, 400) def add_row(self, attr=None, condition_type=None, condition_value=None): model = self.cond_list.model() row = model.rowCount() model.insertRow(row) attr_combo = ComboBoxSearch( minimumContentsLength=12, sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon) attr_combo.setModel(self.variable_model) attr_combo.row = row attr_combo.setCurrentIndex( self.variable_model.indexOf(attr) if attr else len(self.AllTypes) + 1) self.cond_list.setCellWidget(row, 0, attr_combo) index = QPersistentModelIndex(model.index(row, 3)) temp_button = QPushButton( '×', self, flat=True, styleSheet='* {font-size: 16pt; color: silver}' '*:hover {color: black}') temp_button.clicked.connect(lambda: self.remove_one(index.row())) self.cond_list.setCellWidget(row, 3, temp_button) self.remove_all_button.setDisabled(False) self.set_new_operators(attr_combo, attr is not None, condition_type, condition_value) attr_combo.currentIndexChanged.connect( lambda _: self.set_new_operators(attr_combo, False)) self.cond_list.resizeRowToContents(row) def add_all(self): if self.cond_list.rowCount(): Mb = QMessageBox if Mb.question( self, "Remove existing filters", "This will replace the existing filters with " "filters for all variables.", Mb.Ok | Mb.Cancel) != Mb.Ok: return self.remove_all() for attr in self.variable_model[len(self.AllTypes) + 1:]: self.add_row(attr) self.conditions_changed() def remove_one(self, rownum): self.remove_one_row(rownum) self.conditions_changed() def remove_all(self): self.remove_all_rows() self.conditions_changed() def remove_one_row(self, rownum): self.cond_list.removeRow(rownum) if self.cond_list.model().rowCount() == 0: self.remove_all_button.setDisabled(True) def remove_all_rows(self): # Disconnect signals to avoid stray emits when changing variable_model for row in range(self.cond_list.rowCount()): for col in (0, 1): widget = self.cond_list.cellWidget(row, col) if widget: widget.currentIndexChanged.disconnect() self.cond_list.clear() self.cond_list.setRowCount(0) self.remove_all_button.setDisabled(True) def set_new_operators(self, attr_combo, adding_all, selected_index=None, selected_values=None): old_combo = self.cond_list.cellWidget(attr_combo.row, 1) prev_text = old_combo.currentText() if old_combo else "" oper_combo = QComboBox() oper_combo.row = attr_combo.row oper_combo.attr_combo = attr_combo attr_name = attr_combo.currentText() if attr_name in self.AllTypes: oper_combo.addItems(self.operator_names[attr_name]) else: var = self.data.domain[attr_name] oper_combo.addItems(self.operator_names[type(var)]) if selected_index is None: selected_index = oper_combo.findText(prev_text) if selected_index == -1: selected_index = 0 oper_combo.setCurrentIndex(selected_index) self.cond_list.setCellWidget(oper_combo.row, 1, oper_combo) self.set_new_values(oper_combo, adding_all, selected_values) oper_combo.currentIndexChanged.connect( lambda _: self.set_new_values(oper_combo, False)) @staticmethod def _get_lineedit_contents(box): return [ child.text() for child in getattr(box, "controls", [box]) if isinstance(child, QLineEdit) ] @staticmethod def _get_value_contents(box): cont = [] names = [] for child in getattr(box, "controls", [box]): if isinstance(child, QLineEdit): cont.append(child.text()) elif isinstance(child, QComboBox): cont.append(child.currentIndex()) elif isinstance(child, QToolButton): if child.popup is not None: model = child.popup.list_view.model() for row in range(model.rowCount()): item = model.item(row) if item.checkState(): cont.append(row + 1) names.append(item.text()) child.desc_text = ', '.join(names) child.set_text() elif isinstance(child, QLabel) or child is None: pass else: raise TypeError('Type %s not supported.' % type(child)) return tuple(cont) class QDoubleValidatorEmpty(QDoubleValidator): def validate(self, input_, pos): if not input_: return QDoubleValidator.Acceptable, input_, pos if self.locale().groupSeparator() in input_: return QDoubleValidator.Invalid, input_, pos return super().validate(input_, pos) def set_new_values(self, oper_combo, adding_all, selected_values=None): # def remove_children(): # for child in box.children()[1:]: # box.layout().removeWidget(child) # child.setParent(None) def add_textual(contents): le = gui.lineEdit(box, self, None, sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) if contents: le.setText(contents) le.setAlignment(Qt.AlignRight) le.editingFinished.connect(self.conditions_changed) return le def add_numeric(contents): le = add_textual(contents) le.setValidator(OWSelectRows.QDoubleValidatorEmpty()) return le def add_datetime(contents): le = add_textual(contents) le.setValidator(QRegExpValidator(QRegExp(TimeVariable.REGEX))) return le box = self.cond_list.cellWidget(oper_combo.row, 2) lc = ["", ""] oper = oper_combo.currentIndex() attr_name = oper_combo.attr_combo.currentText() if attr_name in self.AllTypes: vtype = self.AllTypes[attr_name] var = None else: var = self.data.domain[attr_name] vtype = vartype(var) if selected_values is not None: lc = list(selected_values) + ["", ""] lc = [str(x) for x in lc[:2]] if box and vtype == box.var_type: lc = self._get_lineedit_contents(box) + lc if oper_combo.currentText().endswith(" defined"): label = QLabel() label.var_type = vtype self.cond_list.setCellWidget(oper_combo.row, 2, label) elif var is not None and var.is_discrete: if oper_combo.currentText().endswith(" one of"): if selected_values: lc = [x for x in list(selected_values)] button = DropDownToolButton(self, var, lc) button.var_type = vtype self.cond_list.setCellWidget(oper_combo.row, 2, button) else: combo = ComboBoxSearch() combo.addItems(("", ) + var.values) if lc[0]: combo.setCurrentIndex(int(lc[0])) else: combo.setCurrentIndex(0) combo.var_type = vartype(var) self.cond_list.setCellWidget(oper_combo.row, 2, combo) combo.currentIndexChanged.connect(self.conditions_changed) else: box = gui.hBox(self, addToLayout=False) box.var_type = vtype self.cond_list.setCellWidget(oper_combo.row, 2, box) if vtype in (2, 4): # continuous, time: validator = add_datetime if isinstance( var, TimeVariable) else add_numeric box.controls = [validator(lc[0])] if oper > 5: gui.widgetLabel(box, " and ") box.controls.append(validator(lc[1])) elif vtype == 3: # string: box.controls = [add_textual(lc[0])] if oper in [6, 7]: gui.widgetLabel(box, " and ") box.controls.append(add_textual(lc[1])) else: box.controls = [] if not adding_all: self.conditions_changed() @Inputs.data def set_data(self, data): self.closeContext() self.data = data self.cb_pa.setEnabled(not isinstance(data, SqlTable)) self.cb_pc.setEnabled(not isinstance(data, SqlTable)) self.remove_all_rows() self.add_button.setDisabled(data is None) self.add_all_button.setDisabled( data is None or len(data.domain.variables) + len(data.domain.metas) > 100) if not data: self.info.set_input_summary(self.info.NoInput) self.data_desc = None self.variable_model.set_domain(None) self.commit() return self.data_desc = report.describe_data_brief(data) self.variable_model.set_domain(data.domain) self.conditions = [] self.openContext(data) for attr, cond_type, cond_value in self.conditions: if attr in self.variable_model: self.add_row(attr, cond_type, cond_value) if not self.cond_list.model().rowCount(): self.add_row() self.info.set_input_summary(data.approx_len(), format_summary_details(data)) self.unconditional_commit() def conditions_changed(self): try: cells_by_rows = ([ self.cond_list.cellWidget(row, col) for col in range(3) ] for row in range(self.cond_list.rowCount())) self.conditions = [ (var_cell.currentData(gui.TableVariable) or var_cell.currentText(), oper_cell.currentIndex(), self._get_value_contents(val_cell)) for var_cell, oper_cell, val_cell in cells_by_rows ] if self.update_on_change and ( self.last_output_conditions is None or self.last_output_conditions != self.conditions): self.commit() except AttributeError: # Attribute error appears if the signal is triggered when the # controls are being constructed pass def _values_to_floats(self, attr, values): if not len(values): return values if not all(values): return None if isinstance(attr, TimeVariable): parse = lambda x: (attr.parse(x), True) else: parse = QLocale().toDouble try: floats, ok = zip(*[parse(v) for v in values]) if not all(ok): raise ValueError('Some values could not be parsed as floats' 'in the current locale: {}'.format(values)) except TypeError: floats = values # values already floats assert all(isinstance(v, float) for v in floats) return floats def commit(self): matching_output = self.data non_matching_output = None annotated_output = None self.Error.clear() if self.data: domain = self.data.domain conditions = [] for attr_name, oper_idx, values in self.conditions: if attr_name in self.AllTypes: attr_index = attr = None attr_type = self.AllTypes[attr_name] operators = self.Operators[attr_name] else: attr_index = domain.index(attr_name) attr = domain[attr_index] attr_type = vartype(attr) operators = self.Operators[type(attr)] opertype, _ = operators[oper_idx] if attr_type == 0: filter = data_filter.IsDefined() elif attr_type in (2, 4): # continuous, time try: floats = self._values_to_floats(attr, values) except ValueError as e: self.Error.parsing_error(e.args[0]) return if floats is None: continue filter = data_filter.FilterContinuous( attr_index, opertype, *floats) elif attr_type == 3: # string filter = data_filter.FilterString( attr_index, opertype, *[str(v) for v in values]) else: if opertype == FilterDiscreteType.IsDefined: f_values = None else: if not values or not values[0]: continue values = [attr.values[i - 1] for i in values] if opertype == FilterDiscreteType.Equal: f_values = {values[0]} elif opertype == FilterDiscreteType.NotEqual: f_values = set(attr.values) f_values.remove(values[0]) elif opertype == FilterDiscreteType.In: f_values = set(values) else: raise ValueError("invalid operand") filter = data_filter.FilterDiscrete(attr_index, f_values) conditions.append(filter) if conditions: self.filters = data_filter.Values(conditions) matching_output = self.filters(self.data) self.filters.negate = True non_matching_output = self.filters(self.data) row_sel = np.in1d(self.data.ids, matching_output.ids) annotated_output = create_annotated_table(self.data, row_sel) # if hasattr(self.data, "name"): # matching_output.name = self.data.name # non_matching_output.name = self.data.name purge_attrs = self.purge_attributes purge_classes = self.purge_classes if (purge_attrs or purge_classes) and \ not isinstance(self.data, SqlTable): attr_flags = sum([ Remove.RemoveConstant * purge_attrs, Remove.RemoveUnusedValues * purge_attrs ]) class_flags = sum([ Remove.RemoveConstant * purge_classes, Remove.RemoveUnusedValues * purge_classes ]) # same settings used for attributes and meta features remover = Remove(attr_flags, class_flags, attr_flags) matching_output = remover(matching_output) non_matching_output = remover(non_matching_output) annotated_output = remover(annotated_output) if matching_output is not None and not len(matching_output): matching_output = None if non_matching_output is not None and not len(non_matching_output): non_matching_output = None if annotated_output is not None and not len(annotated_output): annotated_output = None self.Outputs.matching_data.send(matching_output) self.Outputs.unmatched_data.send(non_matching_output) self.Outputs.annotated_data.send(annotated_output) self.match_desc = report.describe_data_brief(matching_output) self.nonmatch_desc = report.describe_data_brief(non_matching_output) summary = matching_output.approx_len() if matching_output else \ self.info.NoOutput details = format_summary_details( matching_output) if matching_output else "" self.info.set_output_summary(summary, details) def send_report(self): if not self.data: self.report_paragraph("No data.") return pdesc = None describe_domain = False for d in (self.data_desc, self.match_desc, self.nonmatch_desc): if not d or not d["Data instances"]: continue ndesc = d.copy() del ndesc["Data instances"] if pdesc is not None and pdesc != ndesc: describe_domain = True pdesc = ndesc conditions = [] for attr, oper, values in self.conditions: if isinstance(attr, str): attr_name = attr var_type = self.AllTypes[attr] names = self.operator_names[attr_name] else: attr_name = attr.name var_type = vartype(attr) names = self.operator_names[type(attr)] name = names[oper] if oper == len(names) - 1: conditions.append("{} {}".format(attr_name, name)) elif var_type == 1: # discrete if name == "is one of": valnames = [attr.values[v - 1] for v in values] if not valnames: continue if len(valnames) == 1: valstr = valnames[0] else: valstr = f"{', '.join(valnames[:-1])} or {valnames[-1]}" conditions.append(f"{attr} is {valstr}") elif values and values[0]: value = values[0] - 1 conditions.append(f"{attr} {name} {attr.values[value]}") elif var_type == 3: # string variable conditions.append( f"{attr} {name} {' and '.join(map(repr, values))}") elif all(x for x in values): # numeric variable conditions.append(f"{attr} {name} {' and '.join(values)}") items = OrderedDict() if describe_domain: items.update(self.data_desc) else: items["Instances"] = self.data_desc["Data instances"] items["Condition"] = " AND ".join(conditions) or "no conditions" self.report_items("Data", items) if describe_domain: self.report_items("Matching data", self.match_desc) self.report_items("Non-matching data", self.nonmatch_desc) else: match_inst = \ bool(self.match_desc) and \ self.match_desc["Data instances"] nonmatch_inst = \ bool(self.nonmatch_desc) and \ self.nonmatch_desc["Data instances"] self.report_items( "Output", (("Matching data", "{} instances".format(match_inst) if match_inst else "None"), ("Non-matching data", nonmatch_inst > 0 and "{} instances".format(nonmatch_inst))))
def __init__(self, parent): QWidget.__init__(self) OWComponent.__init__(self, parent) SelectionGroupMixin.__init__(self) ImageColorSettingMixin.__init__(self) self.parent = parent self.selection_type = SELECTMANY self.saving_enabled = True self.selection_enabled = True self.viewtype = INDIVIDUAL # required bt InteractiveViewBox self.highlighted = None self.data_points = None self.data_imagepixels = None self.plotview = pg.PlotWidget(background="w", viewBox=InteractiveViewBox(self)) self.plot = self.plotview.getPlotItem() self.plot.scene().installEventFilter( HelpEventDelegate(self.help_event, self)) layout = QVBoxLayout() self.setLayout(layout) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().addWidget(self.plotview) self.img = ImageItemNan() self.img.setOpts(axisOrder='row-major') self.plot.addItem(self.img) self.plot.scene().sigMouseMoved.connect(self.plot.vb.mouseMovedEvent) layout = QGridLayout() self.plotview.setLayout(layout) self.button = QPushButton("Menu", self.plotview) self.button.setAutoDefault(False) layout.setRowStretch(1, 1) layout.setColumnStretch(1, 1) layout.addWidget(self.button, 0, 0) view_menu = MenuFocus(self) self.button.setMenu(view_menu) # prepare interface according to the new context self.parent.contextAboutToBeOpened.connect(lambda x: self.init_interface_data(x[0])) self.add_zoom_actions(view_menu) common_options = dict( labelWidth=50, orientation=Qt.Horizontal, sendSelectedValue=True, valueType=str) choose_xy = QWidgetAction(self) box = gui.vBox(self) box.setFocusPolicy(Qt.TabFocus) self.xy_model = DomainModel(DomainModel.METAS | DomainModel.CLASSES, valid_types=DomainModel.PRIMITIVE, placeholder="Position (index)") self.cb_attr_x = gui.comboBox( box, self, "attr_x", label="Axis x:", callback=self.update_attr, model=self.xy_model, **common_options) box.setFocusProxy(self.cb_attr_x) box.layout().addWidget(self.color_settings_box()) choose_xy.setDefaultWidget(box) view_menu.addAction(choose_xy) self.lsx = None # info about the X axis self.lsy = None # info about the Y axis self.data = None self.data_ids = {}
def __init__(self): super().__init__() self.old_purge_classes = True self.conditions = [] self.last_output_conditions = None self.data = None self.data_desc = self.match_desc = self.nonmatch_desc = None self.variable_model = DomainModel([ list(self.AllTypes), DomainModel.Separator, DomainModel.CLASSES, DomainModel.ATTRIBUTES, DomainModel.METAS ]) box = gui.vBox(self.controlArea, 'Conditions', stretch=100) self.cond_list = QTableWidget(box, showGrid=False, selectionMode=QTableWidget.NoSelection) box.layout().addWidget(self.cond_list) self.cond_list.setColumnCount(4) self.cond_list.setRowCount(0) self.cond_list.verticalHeader().hide() self.cond_list.horizontalHeader().hide() for i in range(3): self.cond_list.horizontalHeader().setSectionResizeMode( i, QHeaderView.Stretch) self.cond_list.horizontalHeader().resizeSection(3, 30) self.cond_list.viewport().setBackgroundRole(QPalette.Window) box2 = gui.hBox(box) gui.rubber(box2) self.add_button = gui.button(box2, self, "Add Condition", callback=self.add_row) self.add_all_button = gui.button(box2, self, "Add All Variables", callback=self.add_all) self.remove_all_button = gui.button(box2, self, "Remove All", callback=self.remove_all) gui.rubber(box2) boxes = gui.widgetBox(self.controlArea, orientation=QHBoxLayout()) layout = boxes.layout() box_setting = gui.vBox(boxes, addToLayout=False, box=True) self.cb_pa = gui.checkBox(box_setting, self, "purge_attributes", "Remove unused features", callback=self.conditions_changed) gui.separator(box_setting, height=1) self.cb_pc = gui.checkBox(box_setting, self, "purge_classes", "Remove unused classes", callback=self.conditions_changed) layout.addWidget(box_setting, 1) self.report_button.setFixedWidth(120) gui.rubber(self.buttonsArea.layout()) layout.addWidget(self.buttonsArea) acbox = gui.auto_send(None, self, "auto_commit") layout.addWidget(acbox, 1) layout.setAlignment(acbox, Qt.AlignBottom) self.info.set_input_summary(self.info.NoInput) self.info.set_output_summary(self.info.NoOutput) self.set_data(None) self.resize(600, 400)
def __init__(self): super().__init__() self.ca = None self.clusters = None self.data = None self.feature_model = DomainModel(valid_types=DiscreteVariable) self.gene_list = None self.model = None self.pvalues = None self._executor = ThreadExecutor() self._gene_selection_history = (self.gene_selection, self.gene_selection) self._task = None box = gui.vBox(self.controlArea, "Info") self.infobox = gui.widgetLabel(box, self._get_info_string()) box = gui.vBox(self.controlArea, "Cluster Variable") gui.comboBox(box, self, "cluster_var", sendSelectedValue=True, model=self.feature_model, callback=self._run_cluster_analysis) layout = QGridLayout() self.gene_selection_radio_group = gui.radioButtonsInBox( self.controlArea, self, "gene_selection", orientation=layout, box="Gene Selection", callback=self._gene_selection_changed) def conditional_set_gene_selection(id): def f(): if self.gene_selection == id: return self._set_gene_selection() return f layout.addWidget( gui.appendRadioButton(self.gene_selection_radio_group, "", addToLayout=False), 1, 1) cb = gui.hBox(None, margin=0) gui.widgetLabel(cb, "Top") self.n_genes_per_cluster_spin = gui.spin( cb, self, "n_genes_per_cluster", minv=1, maxv=self.N_GENES_PER_CLUSTER_MAX, controlWidth=60, alignment=Qt.AlignRight, callback=conditional_set_gene_selection(0)) gui.widgetLabel(cb, "genes per cluster") gui.rubber(cb) layout.addWidget(cb, 1, 2, Qt.AlignLeft) layout.addWidget( gui.appendRadioButton(self.gene_selection_radio_group, "", addToLayout=False), 2, 1) mb = gui.hBox(None, margin=0) gui.widgetLabel(mb, "Top") self.n_most_enriched_spin = gui.spin( mb, self, "n_most_enriched", minv=1, maxv=self.N_MOST_ENRICHED_MAX, controlWidth=60, alignment=Qt.AlignRight, callback=conditional_set_gene_selection(1)) gui.widgetLabel(mb, "highest enrichments") gui.rubber(mb) layout.addWidget(mb, 2, 2, Qt.AlignLeft) layout.addWidget( gui.appendRadioButton(self.gene_selection_radio_group, "", addToLayout=False, disabled=True), 3, 1) sb = gui.hBox(None, margin=0) gui.widgetLabel(sb, "User-provided list of genes") gui.rubber(sb) layout.addWidget(sb, 3, 2) layout = QGridLayout() self.differential_expression_radio_group = gui.radioButtonsInBox( self.controlArea, self, "differential_expression", orientation=layout, box="Differential Expression", callback=self._set_gene_selection) layout.addWidget( gui.appendRadioButton(self.differential_expression_radio_group, "Overexpressed in cluster", addToLayout=False), 1, 1) layout.addWidget( gui.appendRadioButton(self.differential_expression_radio_group, "Underexpressed in cluster", addToLayout=False), 2, 1) layout.addWidget( gui.appendRadioButton(self.differential_expression_radio_group, "Either", addToLayout=False), 3, 1) box = gui.vBox(self.controlArea, "Sorting and Zoom") gui.checkBox(box, self, "biclustering", "Biclustering of analysis results", callback=self._set_gene_selection) gui.radioButtons(box, self, "cell_size_ix", btnLabels=("S", "M", "L"), callback=lambda: self.tableview.set_cell_size( self.CELL_SIZES[self.cell_size_ix]), orientation=Qt.Horizontal) gui.rubber(self.controlArea) self.apply_button = gui.auto_commit(self.controlArea, self, "auto_apply", "&Apply", box=False) self.tableview = ContingencyTable(self) self.mainArea.layout().addWidget(self.tableview)
class OWLinePlot(OWWidget): name = "Line Plot" description = "Visualization of data profiles (e.g., time series)." icon = "icons/LinePlot.svg" priority = 180 enable_selection = Signal(bool) 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() group_var = ContextSetting(None) show_profiles = Setting(False) show_range = Setting(True) show_mean = Setting(True) show_error = Setting(False) auto_commit = Setting(True) selection = Setting(None, schema_only=True) graph_name = "graph.plotItem" class Error(OWWidget.Error): not_enough_attrs = Msg("Need at least one continuous feature.") no_valid_data = Msg("No plot due to no valid data.") class Warning(OWWidget.Warning): no_display_option = Msg("No display option is selected.") class Information(OWWidget.Information): hidden_instances = Msg("Instances with unknown values are not shown.") too_many_features = Msg("Data has too many features. Only first {}" " are shown.".format(MAX_FEATURES)) def __init__(self, parent=None): super().__init__(parent) self.__groups = [] self.data = None self.valid_data = None self.subset_data = None self.subset_indices = None self.__pending_selection = self.selection self.graph_variables = [] self.setup_gui() self.graph.view_box.selection_changed.connect(self.selection_changed) self.enable_selection.connect(self.graph.view_box.enable_selection) def setup_gui(self): self._add_graph() self._add_controls() def _add_graph(self): box = gui.vBox(self.mainArea, True, margin=0) self.graph = LinePlotGraph(self) box.layout().addWidget(self.graph) def _add_controls(self): infobox = gui.widgetBox(self.controlArea, "Info") self.infoLabel = gui.widgetLabel(infobox, "No data on input.") displaybox = gui.widgetBox(self.controlArea, "Display") gui.checkBox(displaybox, self, "show_profiles", "Lines", callback=self.__show_profiles_changed, tooltip="Plot lines") gui.checkBox(displaybox, self, "show_range", "Range", callback=self.__show_range_changed, tooltip="Plot range between 10th and 90th percentile") gui.checkBox(displaybox, self, "show_mean", "Mean", callback=self.__show_mean_changed, tooltip="Plot mean curve") gui.checkBox(displaybox, self, "show_error", "Error bars", callback=self.__show_error_changed, tooltip="Show standard deviation") self.group_vars = DomainModel( placeholder="None", separators=False, valid_types=DiscreteVariable) self.group_view = gui.listView( self.controlArea, self, "group_var", box="Group by", model=self.group_vars, callback=self.__group_var_changed) self.group_view.setEnabled(False) self.group_view.setMinimumSize(QSize(30, 100)) self.group_view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored) plot_gui = OWPlotGUI(self) plot_gui.box_zoom_select(self.controlArea) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, "auto_commit", "Send Selection", "Send Automatically") def __show_profiles_changed(self): self.check_display_options() self._update_visibility("profiles") def __show_range_changed(self): self.check_display_options() self._update_visibility("range") def __show_mean_changed(self): self.check_display_options() self._update_visibility("mean") def __show_error_changed(self): self._update_visibility("error") def __group_var_changed(self): if self.data is None or not self.graph_variables: return self.plot_groups() self._update_profiles_color() self._update_sel_profiles_and_range() self._update_sel_profiles_color() self._update_sub_profiles() @Inputs.data @check_sql_input def set_data(self, data): self.closeContext() self.data = data self.clear() self.check_data() self.check_display_options() if self.data is not None: self.group_vars.set_domain(self.data.domain) self.group_view.setEnabled(len(self.group_vars) > 1) self.group_var = self.data.domain.class_var \ if self.data.domain.has_discrete_class else None self.openContext(data) self.setup_plot() self.commit() def check_data(self): def error(err): err() self.data = None self.clear_messages() if self.data is not None: self.infoLabel.setText("%i instances on input\n%i features" % ( len(self.data), len(self.data.domain.attributes))) self.graph_variables = [var for var in self.data.domain.attributes if var.is_continuous] self.valid_data = ~countnans(self.data.X, axis=1).astype(bool) if len(self.graph_variables) < 1: error(self.Error.not_enough_attrs) elif not np.sum(self.valid_data): error(self.Error.no_valid_data) else: if not np.all(self.valid_data): self.Information.hidden_instances() if len(self.graph_variables) > MAX_FEATURES: self.Information.too_many_features() self.graph_variables = self.graph_variables[:MAX_FEATURES] def check_display_options(self): self.Warning.no_display_option.clear() if self.data is not None: if not (self.show_profiles or self.show_range or self.show_mean): self.Warning.no_display_option() enable = (self.show_profiles or self.show_range) and \ len(self.data[self.valid_data]) < SEL_MAX_INSTANCES self.enable_selection.emit(enable) @Inputs.data_subset @check_sql_input def set_subset_data(self, subset): self.subset_data = subset def handleNewSignals(self): self.set_subset_ids() if self.data is not None: self._update_profiles_color() self._update_sel_profiles_color() self._update_sub_profiles() def set_subset_ids(self): sub_ids = {e.id for e in self.subset_data} \ if self.subset_data is not None else {} self.subset_indices = None if self.data is not None and sub_ids: self.subset_indices = [x.id for x in self.data[self.valid_data] if x.id in sub_ids] def setup_plot(self): if self.data is None: return ticks = [a.name for a in self.graph_variables] self.graph.getAxis("bottom").set_ticks(ticks) self.plot_groups() self.apply_selection() self.graph.view_box.enableAutoRange() self.graph.view_box.updateAutoRange() def plot_groups(self): self._remove_groups() data = self.data[self.valid_data, self.graph_variables] if self.group_var is None: self._plot_group(data, np.where(self.valid_data)[0]) else: class_col_data, _ = self.data.get_column_view(self.group_var) for index in range(len(self.group_var.values)): mask = np.logical_and(class_col_data == index, self.valid_data) indices = np.flatnonzero(mask) if not len(indices): continue group_data = self.data[indices, self.graph_variables] self._plot_group(group_data, indices, index) self.graph.update_legend(self.group_var) self.graph.view_box.add_profiles(data.X) def _remove_groups(self): for group in self.__groups: group.remove_items() self.graph.view_box.remove_profiles() self.__groups = [] def _plot_group(self, data, indices, index=None): color = self.__get_group_color(index) group = ProfileGroup(data, indices, color, self.graph) kwargs = self.__get_visibility_flags() group.set_visible_error(**kwargs) group.set_visible_mean(**kwargs) group.set_visible_range(**kwargs) group.set_visible_profiles(**kwargs) self.__groups.append(group) def __get_group_color(self, index): if self.group_var is not None: return QColor(*self.group_var.colors[index]) return QColor(LinePlotStyle.DEFAULT_COLOR) def __get_visibility_flags(self): return {"show_profiles": self.show_profiles, "show_range": self.show_range, "show_mean": self.show_mean, "show_error": self.show_error} def _update_profiles_color(self): # color alpha depends on subset and selection; with selection or # subset profiles color has more opacity if not self.show_profiles: return for group in self.__groups: has_sel = bool(self.subset_indices) or bool(self.selection) group.update_profiles_color(has_sel) def _update_sel_profiles_and_range(self): # mark selected instances and selected range if not (self.show_profiles or self.show_range): return for group in self.__groups: inds = [i for i in group.indices if self.__in(i, self.selection)] table = self.data[inds, self.graph_variables].X if inds else None if self.show_profiles: group.update_sel_profiles(table) if self.show_range: group.update_sel_range(table) def _update_sel_profiles_color(self): # color depends on subset; when subset is present, # selected profiles are black if not self.selection or not self.show_profiles: return for group in self.__groups: group.update_sel_profiles_color(bool(self.subset_indices)) def _update_sub_profiles(self): # mark subset instances if not (self.show_profiles or self.show_range): return for group in self.__groups: inds = [i for i, _id in zip(group.indices, group.ids) if self.__in(_id, self.subset_indices)] table = self.data[inds, self.graph_variables].X if inds else None group.update_sub_profiles(table) def _update_visibility(self, obj_name): if not len(self.__groups): return self._update_profiles_color() self._update_sel_profiles_and_range() self._update_sel_profiles_color() kwargs = self.__get_visibility_flags() for group in self.__groups: getattr(group, "set_visible_{}".format(obj_name))(**kwargs) self.graph.view_box.updateAutoRange() def apply_selection(self): if self.data is not None and self.__pending_selection is not None: sel = [i for i in self.__pending_selection if i < len(self.data)] mask = np.zeros(len(self.data), dtype=bool) mask[sel] = True mask = mask[self.valid_data] self.selection_changed(mask) self.__pending_selection = None def selection_changed(self, mask): if self.data is None: return # need indices for self.data: mask refers to self.data[self.valid_data] indices = np.arange(len(self.data))[self.valid_data][mask] self.graph.select(indices) old = self.selection self.selection = None if self.data and isinstance(self.data, SqlTable)\ else list(self.graph.selection) if not old and self.selection or old and not self.selection: self._update_profiles_color() self._update_sel_profiles_and_range() self._update_sel_profiles_color() self.commit() def commit(self): selected = self.data[self.selection] \ if self.data is not None and bool(self.selection) else None annotated = create_annotated_table(self.data, self.selection) self.Outputs.selected_data.send(selected) self.Outputs.annotated_data.send(annotated) def send_report(self): if self.data is None: return caption = report.render_items_vert((("Group by", self.group_var),)) self.report_plot() if caption: self.report_caption(caption) def sizeHint(self): return QSize(1132, 708) def clear(self): self.valid_data = None self.selection = None self.__groups = [] self.graph_variables = [] self.graph.reset() self.infoLabel.setText("No data on input.") self.group_vars.set_domain(None) self.group_view.setEnabled(False) @staticmethod def __in(obj, collection): return collection is not None and obj in collection
class OWTranspose(OWWidget): name = "Transpose" description = "Transpose data table." icon = "icons/Transpose.svg" priority = 2000 class Inputs: data = Input("Data", Table) class Outputs: data = Output("Data", Table, dynamic=False) GENERIC, FROM_META_ATTR = range(2) resizing_enabled = False want_main_area = False DEFAULT_PREFIX = "Feature" settingsHandler = DomainContextHandler() feature_type = ContextSetting(GENERIC) feature_name = ContextSetting("") feature_names_column = ContextSetting(None) auto_apply = Setting(True) class Error(OWWidget.Error): value_error = Msg("{}") def __init__(self): super().__init__() self.data = None box = gui.radioButtons( self.controlArea, self, "feature_type", box="Feature names", callback=lambda: self.apply()) button = gui.appendRadioButton(box, "Generic") edit = gui.lineEdit( gui.indentedBox(box, gui.checkButtonOffsetHint(button)), self, "feature_name", placeholderText="Type a prefix ...", toolTip="Custom feature name") edit.editingFinished.connect(self._apply_editing) self.meta_button = gui.appendRadioButton(box, "From meta attribute:") self.feature_model = DomainModel( order=DomainModel.METAS, valid_types=StringVariable, alphabetical=True) self.feature_combo = gui.comboBox( gui.indentedBox(box, gui.checkButtonOffsetHint(button)), self, "feature_names_column", callback=self._feature_combo_changed, model=self.feature_model) self.apply_button = gui.auto_commit( self.controlArea, self, "auto_apply", "&Apply", box=False, commit=self.apply) self.apply_button.button.setAutoDefault(False) self.set_controls() def _apply_editing(self): self.feature_type = self.GENERIC self.feature_name = self.feature_name.strip() self.apply() def _feature_combo_changed(self): self.feature_type = self.FROM_META_ATTR self.apply() @Inputs.data def set_data(self, data): # Skip the context if the combo is empty: a context with # feature_model == None would then match all domains if self.feature_model: self.closeContext() self.data = data self.set_controls() if self.feature_model: self.openContext(data) self.apply() def set_controls(self): self.feature_model.set_domain(self.data and self.data.domain) self.meta_button.setEnabled(bool(self.feature_model)) if self.feature_model: self.feature_names_column = self.feature_model[0] self.feature_type = self.FROM_META_ATTR else: self.feature_names_column = None def apply(self): self.clear_messages() transposed = None if self.data: try: transposed = Table.transpose( self.data, self.feature_type == self.FROM_META_ATTR and self.feature_names_column, feature_name=self.feature_name or self.DEFAULT_PREFIX) except ValueError as e: self.Error.value_error(e) self.Outputs.data.send(transposed) def send_report(self): if self.feature_type == self.GENERIC: names = self.feature_name or self.DEFAULT_PREFIX else: names = "from meta attribute" if self.feature_names_column: names += " '{}'".format(self.feature_names_column.name) self.report_items("", [("Feature names", names)]) if self.data: self.report_data("Data", self.data)
class OWMosaicDisplay(OWWidget): name = "Mosaic Display" description = "Display data in a mosaic plot." icon = "icons/MosaicDisplay.svg" priority = 220 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) PEARSON, CLASS_DISTRIBUTION = 0, 1 settingsHandler = DomainContextHandler() use_boxes = Setting(True) interior_coloring = Setting(CLASS_DISTRIBUTION) variable1 = ContextSetting("") variable2 = ContextSetting("") variable3 = ContextSetting("") variable4 = ContextSetting("") variable_color = ContextSetting("") 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)] vizrank = SettingProvider(MosaicVizRank) graph_name = "canvas" 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.attr_combos = [ gui.comboBox( box, self, value="variable{}".format(i), orientation=Qt.Horizontal, contentsLength=12, callback=self.reset_graph, sendSelectedValue=True, valueType=str, emptyString="(None)") 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") dmod = DomainModel self.color_model = DomainModel(order=dmod.MIXED, valid_types=dmod.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, sendSelectedValue=True, model=self.color_model, valueType=str) self.bar_button = gui.checkBox( box2, self, 'use_boxes', label='Compare with total', callback=self._compare_with_total) gui.rubber(self.controlArea) def sizeHint(self): return QSize(720, 530) def _compare_with_total(self): if self.data is not None and \ self.data.domain.class_var is not None and \ self.interior_coloring != self.CLASS_DISTRIBUTION: self.interior_coloring = self.CLASS_DISTRIBUTION self.coloring_changed() # This also calls self.update_graph else: self.update_graph() def _get_discrete_data(self, data): """ Discretizes continuous attributes. Returns None when there is no data, no rows, or no discrete or continuous 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): for combo in self.attr_combos: combo.clear() if data is None: self.color_model.set_domain(None) return self.color_model.set_domain(self.data.domain) for combo in self.attr_combos[1:]: combo.addItem("(None)") icons = gui.attributeIconDict for attr in chain(data.domain.variables, data.domain.metas): if attr.is_primitive: for combo in self.attr_combos: combo.addItem(icons[attr], attr.name) if self.attr_combos[0].count() > 0: self.variable1 = self.attr_combos[0].itemText(0) self.variable2 = self.attr_combos[1].itemText( 2 * (self.attr_combos[1].count() > 2)) self.variable3 = self.attr_combos[2].itemText(0) self.variable4 = self.attr_combos[3].itemText(0) if self.data.domain.class_var: self.variable_color = self.data.domain.class_var.name idx = self.cb_attr_color.findText(self.variable_color) else: idx = 0 self.cb_attr_color.setCurrentIndex(idx) def get_attr_list(self): return [ a for a in [self.variable1, self.variable2, self.variable3, self.variable4] if a and a != "(None)"] def set_attr(self, *attrs): self.variable1, self.variable2, self.variable3, self.variable4 = \ [a.name if a else "" for a in attrs] 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 type(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 = 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 or len(self.data) < 2 or len(self.data.domain.attributes) < 1: return if self.cb_attr_color.currentIndex() <= 0: color_var = None self.interior_coloring = self.PEARSON self.bar_button.setEnabled(False) else: color_var = self.data.domain[self.cb_attr_color.currentText()] self.interior_coloring = self.CLASS_DISTRIBUTION self.bar_button.setEnabled(True) attributes = [v for v in self.data.domain.attributes + self.data.domain.class_vars + self.data.domain.metas if v != color_var and v.is_primitive()] domain = Domain(attributes, color_var, None) self.color_data = color_data = self.data.from_table(domain, self.data) self.discrete_data = self._get_discrete_data(color_data) self.vizrank.stop_and_reset() self.vizrank_button.setEnabled(True) self.coloring_changed() def update_selection_rects(self): for i, (_, _, area) in enumerate(self.areas): if i in self.selection: area.setPen(QPen(Qt.black, 3, Qt.DotLine)) else: area.setPen(QPen()) 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): """This function calculates rectangles' widths. If all widths are zero then all widths are set to 1.""" if 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(data.domain[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) # if we are visualizing the third attribute and the first attribute # has the last value, we have to reverse the order in which the # boxes will be 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( data.domain[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) if attr_vals != "": newattrvals = attr_vals + "-" + val else: newattrvals = val tooltip = condition + 4 * " " + attr + \ ": <b>" + htmlval + "</b><br>" attrs = used_attrs + [attr] vals = used_vals + [val] common_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, *common_args) else: draw_data(attr_list[1:], (x0 + start, x0 + end), (y0, y1), side + 1, tooltip, total_attrs, *common_args) else: if len(attr_list) == 1: add_rect(x0, x1, y0 + start, y0 + end, tooltip, *common_args) else: draw_data(attr_list[1:], (x0, x1), (y0 + start, y0 + end), side + 1, tooltip, total_attrs, *common_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(data.domain[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(data.domain[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): perc = counts[i] / float(total) if distributiondict[val] != 0: if side == 0: CanvasText(self.canvas, str(val), x0 + currpos + width * 0.5 * perc, y1 + self.ATTR_VAL_OFFSET, align) elif side == 1: CanvasText(self.canvas, str(val), x0 - self.ATTR_VAL_OFFSET, y0 + currpos + height * 0.5 * perc, align) elif side == 2: CanvasText(self.canvas, str(val), x0 + currpos + width * perc * 0.5, y0 - self.ATTR_VAL_OFFSET, align) else: CanvasText(self.canvas, str(val), x1 + self.ATTR_VAL_OFFSET, y0 + currpos + height * 0.5 * perc, align) if side % 2 == 0: currpos += perc * width + spacing * (total_attrs - side) else: currpos += perc * height + spacing * (total_attrs - side) if side == 0: CanvasText( self.canvas, attr, x0 + (x1 - x0) / 2, y1 + self.ATTR_VAL_OFFSET + self.ATTR_NAME_OFFSET, align, bold=1) elif side == 1: CanvasText( self.canvas, attr, x0 - max_ylabel_w1 - self.ATTR_VAL_OFFSET, y0 + (y1 - y0) / 2, align, bold=1, vertical=True) elif side == 2: CanvasText( self.canvas, attr, x0 + (x1 - x0) / 2, y0 - self.ATTR_VAL_OFFSET - self.ATTR_NAME_OFFSET, align, bold=1) else: CanvasText( self.canvas, attr, x1 + max_ylabel_w2 + self.ATTR_VAL_OFFSET, y0 + (y1 - y0) / 2, align, bold=1, vertical=True) def add_rect(x0, x1, y0, y1, condition, used_attrs, used_vals, attr_vals=""): area_index = len(self.areas) if x0 == x1: x1 += 1 if y0 == y1: y1 += 1 # rectangles of width and height 1 are not shown - increase if x1 - x0 + y1 - y0 == 2: y1 += 1 if class_var: colors = [QColor(*col) for col in class_var.colors] else: colors = None 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.interior_coloring == self.PEARSON: 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 = (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 draw_legend(x0_x1, y0_y1): x0, x1 = x0_x1 _, y1 = y0_y1 if self.interior_coloring == self.PEARSON: names = ["<-8", "-8:-4", "-4:-2", "-2:2", "2:4", "4:8", ">8", "Residuals:"] colors = self.RED_COLORS[::-1] + self.BLUE_COLORS[1:] else: names = get_variable_values_sorted(class_var) + \ [class_var.name + ":"] colors = [QColor(*col) for col in class_var.colors] names = [CanvasText(self.canvas, name, alignment=Qt.AlignVCenter) for name in names] totalwidth = sum(text.boundingRect().width() for text in names) # compute the x position of the center of the legend y = y1 + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35 distance = 30 startx = (x0 + x1) / 2 - (totalwidth + (len(names)) * distance) / 2 names[-1].setPos(startx + 15, y) names[-1].show() xoffset = names[-1].boundingRect().width() + distance size = 8 for i in range(len(names) - 1): if self.interior_coloring == self.PEARSON: edgecolor = Qt.black else: edgecolor = colors[i] CanvasRectangle(self.canvas, startx + xoffset, y - size / 2, size, size, edgecolor, colors[i]) names[i].setPos(startx + xoffset + 10, y) xoffset += distance + names[i].boundingRect().width() self.canvas.clear() self.areas = [] data = self.discrete_data if data is None: return attr_list = self.get_attr_list() class_var = data.domain.class_var if class_var: sql = type(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 data.domain[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.interior_coloring == self.PEARSON: 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(data.domain[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 # get the maximum width of rectangle xoff = 20 width = 20 if len(attr_list) > 1: text = CanvasText(self.canvas, attr_list[1], 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], 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 # get the maximum height of rectangle height = 100 yoff = 45 square_size = min(self.canvas_view.width() - width - 20, self.canvas_view.height() - height - 20) 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), [], []) draw_legend((xoff, xoff + square_size), (yoff, yoff + square_size)) self.update_selection_rects()