class Font: DEFAULT = QFont('Sans', 12, QFont.Normal) SELECTED = QFont('Sans', 12, QFont.DemiBold)
def _init_ui(self): namesBox = gui.vBox(self.controlArea, "Names") hbox = gui.hBox(namesBox, margin=0, spacing=0) gui.lineEdit(hbox, self, "attr1", "Variable X: ", controlWidth=80, orientation=Qt.Horizontal, callback=self._attr_name_changed) gui.separator(hbox, 21) hbox = gui.hBox(namesBox, margin=0, spacing=0) attr2 = gui.lineEdit(hbox, self, "attr2", "Variable Y: ", controlWidth=80, orientation=Qt.Horizontal, callback=self._attr_name_changed) gui.separator(hbox) gui.checkBox(hbox, self, "hasAttr2", '', disables=attr2, labelWidth=0, callback=self.set_dimensions) gui.separator(namesBox) gui.widgetLabel(namesBox, "Labels") self.classValuesView = listView = gui.ListViewWithSizeHint( preferred_size=(-1, 30)) listView.setModel(self.class_model) itemmodels.select_row(listView, 0) namesBox.layout().addWidget(listView) self.addClassLabel = QAction("+", self, toolTip="Add new class label", triggered=self.add_new_class_label) self.removeClassLabel = QAction( unicodedata.lookup("MINUS SIGN"), self, toolTip="Remove selected class label", triggered=self.remove_selected_class_label) actionsWidget = itemmodels.ModelActionsWidget( [self.addClassLabel, self.removeClassLabel], self) actionsWidget.layout().addStretch(10) actionsWidget.layout().setSpacing(1) namesBox.layout().addWidget(actionsWidget) tBox = gui.vBox(self.controlArea, "Tools", addSpace=True) buttonBox = gui.hBox(tBox) toolsBox = gui.widgetBox(buttonBox, orientation=QGridLayout()) self.toolActions = QActionGroup(self) self.toolActions.setExclusive(True) self.toolButtons = [] for i, (name, tooltip, tool, icon) in enumerate(self.TOOLS): action = QAction( name, self, toolTip=tooltip, checkable=tool.checkable, icon=QIcon(icon), ) action.triggered.connect(partial(self.set_current_tool, tool)) button = QToolButton(iconSize=QSize(24, 24), toolButtonStyle=Qt.ToolButtonTextUnderIcon, sizePolicy=QSizePolicy( QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)) button.setDefaultAction(action) self.toolButtons.append((button, tool)) toolsBox.layout().addWidget(button, i / 3, i % 3) self.toolActions.addAction(action) for column in range(3): toolsBox.layout().setColumnMinimumWidth(column, 10) toolsBox.layout().setColumnStretch(column, 1) undo = self.undo_stack.createUndoAction(self) redo = self.undo_stack.createRedoAction(self) undo.setShortcut(QKeySequence.Undo) redo.setShortcut(QKeySequence.Redo) self.addActions([undo, redo]) self.undo_stack.indexChanged.connect(lambda _: self.invalidate()) gui.separator(tBox) indBox = gui.indentedBox(tBox, sep=8) form = QFormLayout(formAlignment=Qt.AlignLeft, labelAlignment=Qt.AlignLeft, fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow) indBox.layout().addLayout(form) slider = gui.hSlider(indBox, self, "brushRadius", minValue=1, maxValue=100, createLabel=False) form.addRow("Radius:", slider) slider = gui.hSlider(indBox, self, "density", None, minValue=1, maxValue=100, createLabel=False) form.addRow("Intensity:", slider) slider = gui.hSlider(indBox, self, "symbol_size", None, minValue=1, maxValue=100, createLabel=False, callback=self.set_symbol_size) form.addRow("Symbol:", slider) self.btResetToInput = gui.button(tBox, self, "Reset to Input Data", self.reset_to_input) self.btResetToInput.setDisabled(True) gui.auto_send(self.controlArea, self, "autocommit") # main area GUI viewbox = PaintViewBox(enableMouse=False) self.plotview = pg.PlotWidget(background="w", viewBox=viewbox) self.plotview.sizeHint = lambda: QSize( 200, 100) # Minimum size for 1-d painting self.plot = self.plotview.getPlotItem() axis_color = self.palette().color(QPalette.Text) axis_pen = QPen(axis_color) tickfont = QFont(self.font()) tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11)) axis = self.plot.getAxis("bottom") axis.setLabel(self.attr1) axis.setPen(axis_pen) axis.setTickFont(tickfont) axis = self.plot.getAxis("left") axis.setLabel(self.attr2) axis.setPen(axis_pen) axis.setTickFont(tickfont) if not self.hasAttr2: self.plot.hideAxis('left') self.plot.hideButtons() self.plot.setXRange(0, 1, padding=0.01) self.mainArea.layout().addWidget(self.plotview) # enable brush tool self.toolActions.actions()[0].setChecked(True) self.set_current_tool(self.TOOLS[0][2]) self.set_dimensions()
class OWBoxPlot(widget.OWWidget): """ Here's how the widget's functions call each other: - `set_data` is a signal handler fills the list boxes and calls `grouping_changed`. - `grouping_changed` handles changes of grouping attribute: it enables or disables the box for ordering, orders attributes and calls `attr_changed`. - `attr_changed` handles changes of attribute. It recomputes box data by calling `compute_box_data`, shows the appropriate display box (discrete/continuous) and then calls`layout_changed` - `layout_changed` constructs all the elements for the scene (as lists of QGraphicsItemGroup) and calls `display_changed`. It is called when the attribute or grouping is changed (by attr_changed) and on resize event. - `display_changed` puts the elements corresponding to the current display settings on the scene. It is called when the elements are reconstructed (layout is changed due to selection of attributes or resize event), or when the user changes display settings or colors. For discrete attributes, the flow is a bit simpler: the elements are not constructed in advance (by layout_changed). Instead, layout_changed and display_changed call display_changed_disc that draws everything. """ name = "Box Plot" description = "Visualize the distribution of feature values in a box plot." icon = "icons/BoxPlot.svg" priority = 100 keywords = ["whisker"] class Inputs: data = Input("Data", Orange.data.Table) class Outputs: selected_data = Output("Selected Data", Orange.data.Table, default=True) annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table) #: Comparison types for continuous variables CompareNone, CompareMedians, CompareMeans = 0, 1, 2 settingsHandler = DomainContextHandler() conditions = ContextSetting([]) attribute = ContextSetting(None) order_by_importance = Setting(False) group_var = ContextSetting(None) show_annotations = Setting(True) compare = Setting(CompareMeans) stattest = Setting(0) sig_threshold = Setting(0.05) stretched = Setting(True) show_labels = Setting(True) sort_freqs = Setting(False) auto_commit = Setting(True) _sorting_criteria_attrs = { CompareNone: "", CompareMedians: "median", CompareMeans: "mean" } _pen_axis_tick = QPen(Qt.white, 5) _pen_axis = QPen(Qt.darkGray, 3) _pen_median = QPen(QBrush(QColor(0xff, 0xff, 0x00)), 2) _pen_paramet = QPen(QBrush(QColor(0x33, 0x00, 0xff)), 2) _pen_dotted = QPen(QBrush(QColor(0x33, 0x00, 0xff)), 1) _pen_dotted.setStyle(Qt.DotLine) _post_line_pen = QPen(Qt.lightGray, 2) _post_grp_pen = QPen(Qt.lightGray, 4) for pen in (_pen_paramet, _pen_median, _pen_dotted, _pen_axis, _pen_axis_tick, _post_line_pen, _post_grp_pen): pen.setCosmetic(True) pen.setCapStyle(Qt.RoundCap) pen.setJoinStyle(Qt.RoundJoin) _pen_axis_tick.setCapStyle(Qt.FlatCap) _box_brush = QBrush(QColor(0x33, 0x88, 0xff, 0xc0)) _axis_font = QFont() _axis_font.setPixelSize(12) _label_font = QFont() _label_font.setPixelSize(11) _attr_brush = QBrush(QColor(0x33, 0x00, 0xff)) graph_name = "box_scene" 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 = 1 self.scene_min_x = self.scene_max_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() def sizeHint(self): return QSize(900, 500) def eventFilter(self, obj, event): if obj is self.box_view.viewport() and \ event.type() == QEvent.Resize: self.layout_changed() return super().eventFilter(obj, event) def reset_attrs(self, domain): self.attrs[:] = [ var for var in chain(domain.class_vars, domain.metas, domain.attributes) if var.is_primitive() ] # noinspection PyTypeChecker @Inputs.data def set_data(self, dataset): if dataset is not None and (not bool(dataset) or not len(dataset.domain) and not any(var.is_primitive() for var in dataset.domain.metas)): dataset = None self.closeContext() self.dataset = dataset self.dist = self.stats = self.conts = [] self.group_var = None self.attribute = None if dataset: domain = dataset.domain self.group_vars.set_domain(domain) self.group_view.setEnabled(len(self.group_vars) > 1) self.reset_attrs(domain) self.select_default_variables(domain) self.openContext(self.dataset) self.grouping_changed() else: self.reset_all_data() self.commit() def select_default_variables(self, domain): # visualize first non-class variable, group by class (if present) if len(self.attrs) > len(domain.class_vars): self.attribute = self.attrs[len(domain.class_vars)] elif self.attrs: self.attribute = self.attrs[0] if domain.class_var and domain.class_var.is_discrete: self.group_var = domain.class_var else: self.group_var = None # Reset to trigger selection via callback def apply_sorting(self): def compute_score(attr): if attr is group_var: return 3 if attr.is_continuous: # One-way ANOVA col = data.get_column_view(attr)[0].astype(float) groups = (col[group_col == i] for i in range(n_groups)) groups = (col[~np.isnan(col)] for col in groups) groups = [group for group in groups if len(group)] p = f_oneway(*groups)[1] if len(groups) > 1 else 2 else: # Chi-square with the given distribution into groups # (see degrees of freedom in computation of the p-value) if not attr.values or not group_var.values: return 2 observed = np.array( contingency.get_contingency(data, group_var, attr)) observed = observed[observed.sum(axis=1) != 0, :] observed = observed[:, observed.sum(axis=0) != 0] if min(observed.shape) < 2: return 2 expected = \ np.outer(observed.sum(axis=1), observed.sum(axis=0)) / \ np.sum(observed) p = chisquare(observed.ravel(), f_exp=expected.ravel(), ddof=n_groups - 1)[1] if math.isnan(p): return 2 return p data = self.dataset if data is None: return domain = data.domain attribute = self.attribute group_var = self.group_var if self.order_by_importance and group_var is not None: n_groups = len(group_var.values) group_col = data.get_column_view(group_var)[0] if \ domain.has_continuous_attributes( include_class=True, include_metas=True) else None self.attrs.sort(key=compute_score) else: self.reset_attrs(domain) self.attribute = attribute def reset_all_data(self): self.clear_scene() self.infot1.setText("") self.attrs.clear() self.group_vars.set_domain(None) self.group_view.setEnabled(False) self.is_continuous = False self.update_display_box() def grouping_changed(self): self.cb_order.setEnabled(self.group_var is not None) self.apply_sorting() self.attr_changed() def select_box_items(self): temp_cond = self.conditions.copy() for box in self.box_scene.items(): if isinstance(box, FilterGraphicsRectItem): box.setSelected( box.filter.conditions in [c.conditions for c in temp_cond]) def attr_changed(self): self.compute_box_data() self.update_display_box() self.layout_changed() if self.is_continuous: heights = 90 if self.show_annotations else 60 self.box_view.centerOn(self.scene_min_x + self.scene_width / 2, -30 - len(self.stats) * heights / 2 + 45) else: self.box_view.centerOn(self.scene_width / 2, -30 - len(self.boxes) * 40 / 2 + 45) def compute_box_data(self): attr = self.attribute if not attr: return dataset = self.dataset self.is_continuous = attr.is_continuous if dataset is None or not self.is_continuous and not attr.values or \ self.group_var and not self.group_var.values: self.stats = self.dist = self.conts = [] return if self.group_var: self.dist = [] self.conts = contingency.get_contingency(dataset, attr, self.group_var) if self.is_continuous: stats, label_texts = [], [] for i, cont in enumerate(self.conts): if np.sum(cont[1]): stats.append(BoxData(cont, attr, i, self.group_var)) label_texts.append(self.group_var.values[i]) self.stats = stats self.label_txts_all = label_texts else: self.label_txts_all = \ [v for v, c in zip(self.group_var.values, self.conts) if np.sum(c) > 0] else: self.dist = distribution.get_distribution(dataset, attr) self.conts = [] if self.is_continuous: self.stats = [BoxData(self.dist, attr, None)] self.label_txts_all = [""] self.label_txts = [ txts for stat, txts in zip(self.stats, self.label_txts_all) if stat.n > 0 ] self.stats = [stat for stat in self.stats if stat.n > 0] def update_display_box(self): if self.is_continuous: self.stretching_box.hide() self.display_box.show() self.compare_rb.setEnabled(self.group_var is not None) else: self.stretching_box.show() self.display_box.hide() self.sort_cb.setEnabled(self.group_var is not None) def clear_scene(self): self.closeContext() self.box_scene.clearSelection() self.box_scene.clear() self.box_view.viewport().update() self.attr_labels = [] self.labels = [] self.boxes = [] self.mean_labels = [] self.posthoc_lines = [] self.openContext(self.dataset) def layout_changed(self): attr = self.attribute if not attr: return self.clear_scene() if self.dataset is None or len(self.conts) == len(self.dist) == 0: return if not self.is_continuous: self.display_changed_disc() return self.mean_labels = [ self.mean_label(stat, attr, lab) for stat, lab in zip(self.stats, self.label_txts) ] self.draw_axis() self.boxes = [self.box_group(stat) for stat in self.stats] self.labels = [ self.label_group(stat, attr, mean_lab) for stat, mean_lab in zip(self.stats, self.mean_labels) ] self.attr_labels = [ QGraphicsSimpleTextItem(lab) for lab in self.label_txts ] for it in chain(self.labels, self.attr_labels): self.box_scene.addItem(it) self.display_changed() def display_changed(self): if self.dataset is None: return if not self.is_continuous: self.display_changed_disc() return self.order = list(range(len(self.stats))) criterion = self._sorting_criteria_attrs[self.compare] if criterion: vals = [getattr(stat, criterion) for stat in self.stats] overmax = max((val for val in vals if val is not None), default=0) \ + 1 vals = [val if val is not None else overmax for val in vals] self.order = sorted(self.order, key=vals.__getitem__) heights = 90 if self.show_annotations else 60 for row, box_index in enumerate(self.order): y = (-len(self.stats) + row) * heights + 10 for item in self.boxes[box_index]: self.box_scene.addItem(item) item.setY(y) labels = self.labels[box_index] if self.show_annotations: labels.show() labels.setY(y) else: labels.hide() label = self.attr_labels[box_index] label.setY(y - 15 - label.boundingRect().height()) if self.show_annotations: label.hide() else: stat = self.stats[box_index] if self.compare == OWBoxPlot.CompareMedians and \ stat.median is not None: pos = stat.median + 5 / self.scale_x elif self.compare == OWBoxPlot.CompareMeans or stat.q25 is None: pos = stat.mean + 5 / self.scale_x else: pos = stat.q25 label.setX(pos * self.scale_x) label.show() r = QRectF(self.scene_min_x, -30 - len(self.stats) * heights, self.scene_width, len(self.stats) * heights + 90) self.box_scene.setSceneRect(r) self.compute_tests() self.show_posthoc() self.select_box_items() def display_changed_disc(self): assert not self.is_continuous self.clear_scene() self.attr_labels = [ QGraphicsSimpleTextItem(lab) for lab in self.label_txts_all ] if not self.stretched: if self.group_var: self.labels = [ QGraphicsTextItem("{}".format(int(sum(cont)))) for cont in self.conts if np.sum(cont) > 0 ] else: self.labels = [QGraphicsTextItem(str(int(sum(self.dist))))] self.order = list(range(len(self.attr_labels))) self.draw_axis_disc() if self.group_var: self.boxes = \ [self.strudel(cont, i) for i, cont in enumerate(self.conts) if np.sum(cont) > 0] self.conts = self.conts[np.sum(np.array(self.conts), axis=1) > 0] if self.sort_freqs: # pylint: disable=invalid-unary-operand-type self.order = sorted( self.order, key=(-np.sum(self.conts, axis=1)).__getitem__) else: self.boxes = [self.strudel(self.dist)] for row, box_index in enumerate(self.order): y = (-len(self.boxes) + row) * 40 + 10 box = self.boxes[box_index] bars, labels = box[::2], box[1::2] self.__draw_group_labels(y, box_index) if not self.stretched: self.__draw_row_counts(y, box_index) if self.show_labels and self.attribute is not self.group_var: self.__draw_bar_labels(y, bars, labels) self.__draw_bars(y, bars) self.box_scene.setSceneRect(-self.label_width - 5, -30 - len(self.boxes) * 40, self.scene_width, len(self.boxes * 40) + 90) self.infot1.setText("") self.select_box_items() def __draw_group_labels(self, y, row): """Draw group labels Parameters ---------- y: int vertical offset of bars row: int row index """ label = self.attr_labels[row] b = label.boundingRect() label.setPos(-b.width() - 10, y - b.height() / 2) self.box_scene.addItem(label) def __draw_row_counts(self, y, row): """Draw row counts Parameters ---------- y: int vertical offset of bars row: int row index """ assert not self.is_continuous label = self.labels[row] b = label.boundingRect() if self.group_var: right = self.scale_x * sum(self.conts[row]) else: right = self.scale_x * sum(self.dist) label.setPos(right + 10, y - b.height() / 2) self.box_scene.addItem(label) def __draw_bar_labels(self, y, bars, labels): """Draw bar labels Parameters ---------- y: int vertical offset of bars bars: List[FilterGraphicsRectItem] list of bars being drawn labels: List[QGraphicsTextItem] list of labels for corresponding bars """ label = bar_part = None for text_item, bar_part in zip(labels, bars): label = self.Label(text_item.toPlainText()) label.setPos(bar_part.boundingRect().x(), y - label.boundingRect().height() - 8) label.setMaxWidth(bar_part.boundingRect().width()) self.box_scene.addItem(label) def __draw_bars(self, y, bars): """Draw bars Parameters ---------- y: int vertical offset of bars bars: List[FilterGraphicsRectItem] list of bars to draw """ for item in bars: item.setPos(0, y) self.box_scene.addItem(item) # noinspection PyPep8Naming def compute_tests(self): # The t-test and ANOVA are implemented here since they efficiently use # the widget-specific data in self.stats. # The non-parametric tests can't do this, so we use statistics.tests # pylint: disable=comparison-with-itself def stat_ttest(): d1, d2 = self.stats if d1.n < 2 or d2.n < 2: return np.nan, np.nan pooled_var = d1.var / d1.n + d2.var / d2.n # pylint: disable=comparison-with-itself if pooled_var == 0 or np.isnan(pooled_var): return np.nan, np.nan df = pooled_var ** 2 / \ ((d1.var / d1.n) ** 2 / (d1.n - 1) + (d2.var / d2.n) ** 2 / (d2.n - 1)) t = abs(d1.mean - d2.mean) / math.sqrt(pooled_var) p = 2 * (1 - scipy.special.stdtr(df, t)) return t, p # TODO: Check this function # noinspection PyPep8Naming def stat_ANOVA(): if any(stat.n == 0 for stat in self.stats): return np.nan, np.nan n = sum(stat.n for stat in self.stats) grand_avg = sum(stat.n * stat.mean for stat in self.stats) / n var_between = sum(stat.n * (stat.mean - grand_avg)**2 for stat in self.stats) df_between = len(self.stats) - 1 var_within = sum(stat.n * stat.var for stat in self.stats) df_within = n - len(self.stats) if var_within == 0 or df_within == 0 or df_between == 0: return np.nan, np.nan F = (var_between / df_between) / (var_within / df_within) p = 1 - scipy.special.fdtr(df_between, df_within, F) return F, p if self.compare == OWBoxPlot.CompareNone or len(self.stats) < 2: t = "" elif any(s.n <= 1 for s in self.stats): t = "At least one group has just one instance, " \ "cannot compute significance" elif len(self.stats) == 2: if self.compare == OWBoxPlot.CompareMedians: t = "" # z, p = tests.wilcoxon_rank_sum( # self.stats[0].dist, self.stats[1].dist) # t = "Mann-Whitney's z: %.1f (p=%.3f)" % (z, p) else: t, p = stat_ttest() t = "" if np.isnan(t) else f"Student's t: {t:.3f} (p={p:.3f})" else: if self.compare == OWBoxPlot.CompareMedians: t = "" # U, p = -1, -1 # t = "Kruskal Wallis's U: %.1f (p=%.3f)" % (U, p) else: F, p = stat_ANOVA() t = "" if np.isnan(F) else f"ANOVA: {F:.3f} (p={p:.3f})" self.infot1.setText("<center>%s</center>" % t) def mean_label(self, stat, attr, val_name): label = QGraphicsItemGroup() t = QGraphicsSimpleTextItem(attr.str_val(stat.mean), label) t.setFont(self._label_font) bbox = t.boundingRect() w2, h = bbox.width() / 2, bbox.height() t.setPos(-w2, -h) tpm = QGraphicsSimpleTextItem( " \u00b1 " + "%.*f" % (attr.number_of_decimals + 1, stat.dev), label) tpm.setFont(self._label_font) tpm.setPos(w2, -h) if val_name: vnm = QGraphicsSimpleTextItem(val_name + ": ", label) vnm.setFont(self._label_font) vnm.setBrush(self._attr_brush) vb = vnm.boundingRect() label.min_x = -w2 - vb.width() vnm.setPos(label.min_x, -h) else: label.min_x = -w2 return label def draw_axis(self): """Draw the horizontal axis and sets self.scale_x""" misssing_stats = not self.stats stats = self.stats or [BoxData(np.array([[0.], [1.]]), self.attribute)] mean_labels = self.mean_labels or [ self.mean_label(stats[0], self.attribute, "") ] bottom = min(stat.a_min for stat in stats) top = max(stat.a_max for stat in stats) first_val, step = compute_scale(bottom, top) while bottom <= first_val: first_val -= step bottom = first_val no_ticks = math.ceil((top - first_val) / step) + 1 top = max(top, first_val + no_ticks * step) gbottom = min(bottom, min(stat.mean - stat.dev for stat in stats)) gtop = max(top, max(stat.mean + stat.dev for stat in stats)) bv = self.box_view viewrect = bv.viewport().rect().adjusted(15, 15, -15, -30) self.scale_x = scale_x = viewrect.width() / (gtop - gbottom) # In principle we should repeat this until convergence since the new # scaling is too conservative. (No chance am I doing this.) mlb = min(stat.mean + mean_lab.min_x / scale_x for stat, mean_lab in zip(stats, mean_labels)) if mlb < gbottom: gbottom = mlb self.scale_x = scale_x = viewrect.width() / (gtop - gbottom) self.scene_min_x = gbottom * scale_x self.scene_max_x = gtop * scale_x self.scene_width = self.scene_max_x - self.scene_min_x val = first_val last_text = self.scene_min_x while True: l = self.box_scene.addLine(val * scale_x, -1, val * scale_x, 1, self._pen_axis_tick) l.setZValue(100) t = QGraphicsSimpleTextItem( self.attribute.str_val(val) if not misssing_stats else "?") t.setFont(self._axis_font) t.setFlags(t.flags() | QGraphicsItem.ItemIgnoresTransformations) r = t.boundingRect() x_start = val * scale_x - r.width() / 2 x_finish = x_start + r.width() if x_start > last_text + 10 and x_finish < self.scene_max_x: t.setPos(x_start, 8) self.box_scene.addItem(t) last_text = x_finish if val >= top: break val += step self.box_scene.addLine(bottom * scale_x - 4, 0, top * scale_x + 4, 0, self._pen_axis) def draw_axis_disc(self): """ Draw the horizontal axis and sets self.scale_x for discrete attributes """ assert not self.is_continuous if self.stretched: if not self.attr_labels: return step = steps = 10 else: if self.group_var: max_box = max(float(np.sum(dist)) for dist in self.conts) else: max_box = float(np.sum(self.dist)) if max_box == 0: self.scale_x = 1 return _, step = compute_scale(0, max_box) step = int(step) if step > 1 else 1 steps = int(math.ceil(max_box / step)) max_box = step * steps bv = self.box_view viewrect = bv.viewport().rect().adjusted(15, 15, -15, -30) self.scene_width = viewrect.width() lab_width = max(lab.boundingRect().width() for lab in self.attr_labels) lab_width = max(lab_width, 40) lab_width = min(lab_width, self.scene_width / 3) self.label_width = lab_width right_offset = 0 # offset for the right label if not self.stretched and self.labels: if self.group_var: rows = list(zip(self.conts, self.labels)) else: rows = [(self.dist, self.labels[0])] # available space left of the 'group labels' available = self.scene_width - lab_width - 10 scale_x = (available - right_offset) / max_box max_right = max( sum(dist) * scale_x + 10 + lbl.boundingRect().width() for dist, lbl in rows) right_offset = max(0, max_right - max_box * scale_x) self.scale_x = scale_x = \ (self.scene_width - lab_width - 10 - right_offset) / max_box self.box_scene.addLine(0, 0, max_box * scale_x, 0, self._pen_axis) for val in range(0, step * steps + 1, step): l = self.box_scene.addLine(val * scale_x, -1, val * scale_x, 1, self._pen_axis_tick) l.setZValue(100) t = self.box_scene.addSimpleText(str(val), self._axis_font) t.setPos(val * scale_x - t.boundingRect().width() / 2, 8) if self.stretched: self.scale_x *= 100 def label_group(self, stat, attr, mean_lab): def centered_text(val, pos): t = QGraphicsSimpleTextItem(attr.str_val(val), labels) t.setFont(self._label_font) bbox = t.boundingRect() t.setPos(pos - bbox.width() / 2, 22) return t def line(x, down=1): QGraphicsLineItem(x, 12 * down, x, 20 * down, labels) def move_label(label, frm, to): label.setX(to) to += t_box.width() / 2 path = QPainterPath() path.lineTo(0, 4) path.lineTo(to - frm, 4) path.lineTo(to - frm, 8) p = QGraphicsPathItem(path) p.setPos(frm, 12) labels.addToGroup(p) labels = QGraphicsItemGroup() labels.addToGroup(mean_lab) m = stat.mean * self.scale_x mean_lab.setPos(m, -22) line(m, -1) if stat.median is not None: msc = stat.median * self.scale_x med_t = centered_text(stat.median, msc) med_box_width2 = med_t.boundingRect().width() / 2 line(msc) if stat.q25 is not None: x = stat.q25 * self.scale_x t = centered_text(stat.q25, x) t_box = t.boundingRect() med_left = msc - med_box_width2 if x + t_box.width() / 2 >= med_left - 5: move_label(t, x, med_left - t_box.width() - 5) else: line(x) if stat.q75 is not None: x = stat.q75 * self.scale_x t = centered_text(stat.q75, x) t_box = t.boundingRect() med_right = msc + med_box_width2 if x - t_box.width() / 2 <= med_right + 5: move_label(t, x, med_right + 5) else: line(x) return labels def box_group(self, stat, height=20): def line(x0, y0, x1, y1, *args): return QGraphicsLineItem(x0 * scale_x, y0, x1 * scale_x, y1, *args) scale_x = self.scale_x box = [] whisker1 = line(stat.a_min, -1.5, stat.a_min, 1.5) whisker2 = line(stat.a_max, -1.5, stat.a_max, 1.5) vert_line = line(stat.a_min, 0, stat.a_max, 0) mean_line = line(stat.mean, -height / 3, stat.mean, height / 3) for it in (whisker1, whisker2, mean_line): it.setPen(self._pen_paramet) vert_line.setPen(self._pen_dotted) var_line = line(stat.mean - stat.dev, 0, stat.mean + stat.dev, 0) var_line.setPen(self._pen_paramet) box.extend([whisker1, whisker2, vert_line, mean_line, var_line]) if stat.q25 is not None and stat.q75 is not None: mbox = FilterGraphicsRectItem(stat.conditions, stat.q25 * scale_x, -height / 2, (stat.q75 - stat.q25) * scale_x, height) mbox.setBrush(self._box_brush) mbox.setPen(QPen(Qt.NoPen)) mbox.setZValue(-200) box.append(mbox) if stat.median is not None: median_line = line(stat.median, -height / 2, stat.median, height / 2) median_line.setPen(self._pen_median) median_line.setZValue(-150) box.append(median_line) return box def strudel(self, dist, group_val_index=None): attr = self.attribute ss = np.sum(dist) box = [] if ss < 1e-6: cond = [FilterDiscrete(attr, None)] if group_val_index is not None: cond.append(FilterDiscrete(self.group_var, [group_val_index])) box.append(FilterGraphicsRectItem(cond, 0, -10, 1, 10)) cum = 0 for i, v in enumerate(dist): if v < 1e-6: continue if self.stretched: v /= ss v *= self.scale_x cond = [FilterDiscrete(attr, [i])] if group_val_index is not None: cond.append(FilterDiscrete(self.group_var, [group_val_index])) rect = FilterGraphicsRectItem(cond, cum + 1, -6, v - 2, 12) rect.setBrush(QBrush(QColor(*attr.colors[i]))) rect.setPen(QPen(Qt.NoPen)) if self.stretched: tooltip = "{}: {:.2f}%".format(attr.values[i], 100 * dist[i] / sum(dist)) else: tooltip = "{}: {}".format(attr.values[i], int(dist[i])) rect.setToolTip(tooltip) text = QGraphicsTextItem(attr.values[i]) box.append(rect) box.append(text) cum += v return box def commit(self): self.conditions = [ item.filter for item in self.box_scene.selectedItems() if item.filter ] selected, selection = None, [] if self.conditions: selected = Values(self.conditions, conjunction=False)(self.dataset) selection = np.in1d(self.dataset.ids, selected.ids, assume_unique=True).nonzero()[0] self.Outputs.selected_data.send(selected) self.Outputs.annotated_data.send( create_annotated_table(self.dataset, selection)) def show_posthoc(self): def line(y0, y1): it = self.box_scene.addLine(x, y0, x, y1, self._post_line_pen) it.setZValue(-100) self.posthoc_lines.append(it) while self.posthoc_lines: self.box_scene.removeItem(self.posthoc_lines.pop()) if self.compare == OWBoxPlot.CompareNone or len(self.stats) < 2: return if self.compare == OWBoxPlot.CompareMedians: crit_line = "median" else: crit_line = "mean" xs = [] height = 90 if self.show_annotations else 60 y_up = -len(self.stats) * height + 10 for pos, box_index in enumerate(self.order): stat = self.stats[box_index] x = getattr(stat, crit_line) if x is None: continue x *= self.scale_x xs.append(x * self.scale_x) by = y_up + pos * height line(by + 12, 3) line(by - 12, by - 25) used_to = [] last_to = to = 0 for frm, frm_x in enumerate(xs[:-1]): for to in range(frm + 1, len(xs)): if xs[to] - frm_x > 1.5: to -= 1 break if to in (last_to, frm): continue for rowi, used in enumerate(used_to): if used < frm: used_to[rowi] = to break else: rowi = len(used_to) used_to.append(to) y = -6 - rowi * 6 it = self.box_scene.addLine(frm_x - 2, y, xs[to] + 2, y, self._post_grp_pen) self.posthoc_lines.append(it) last_to = to def get_widget_name_extension(self): return self.attribute.name if self.attribute else None def send_report(self): self.report_plot() text = "" if self.attribute: text += "Box plot for attribute '{}' ".format(self.attribute.name) if self.group_var: text += "grouped by '{}'".format(self.group_var.name) if text: self.report_caption(text) class Label(QGraphicsSimpleTextItem): """Boxplot Label with settable maxWidth""" # Minimum width to display label text MIN_LABEL_WIDTH = 25 # padding bellow the text PADDING = 3 __max_width = None def maxWidth(self): return self.__max_width def setMaxWidth(self, max_width): self.__max_width = max_width def paint(self, painter, option, widget): """Overrides QGraphicsSimpleTextItem.paint If label text is too long, it is elided to fit into the allowed region """ if self.__max_width is None: width = option.rect.width() else: width = self.__max_width if width < self.MIN_LABEL_WIDTH: # if space is too narrow, no label return fm = painter.fontMetrics() text = fm.elidedText(self.text(), Qt.ElideRight, width) painter.drawText( option.rect.x(), option.rect.y() + self.boundingRect().height() - self.PADDING, text)
def __init__(self): super().__init__() self.results = None self.classifier_names = [] self.perf_line = None self.colors = [] self._curve_data = {} self._plot_curves = {} self._rocch = None self._perf_line = None box = gui.vBox(self.controlArea, "Plot") tbox = gui.vBox(box, "Target Class") tbox.setFlat(True) self.target_cb = gui.comboBox(tbox, self, "target_index", callback=self._on_target_changed, contentsLength=8) cbox = gui.vBox(box, "Classifiers") cbox.setFlat(True) self.classifiers_list_box = gui.listBox( cbox, self, "selected_classifiers", "classifier_names", selectionMode=QListView.MultiSelection, callback=self._on_classifiers_changed) abox = gui.vBox(box, "Combine ROC Curves From Folds") abox.setFlat(True) gui.comboBox(abox, self, "roc_averaging", items=[ "Merge Predictions from Folds", "Mean TP Rate", "Mean TP and FP at Threshold", "Show Individual Curves" ], callback=self._replot) hbox = gui.vBox(box, "ROC Convex Hull") hbox.setFlat(True) gui.checkBox(hbox, self, "display_convex_curve", "Show convex ROC curves", callback=self._replot) gui.checkBox(hbox, self, "display_convex_hull", "Show ROC convex hull", callback=self._replot) box = gui.vBox(self.controlArea, "Analysis") gui.checkBox(box, self, "display_def_threshold", "Default threshold (0.5) point", callback=self._on_display_def_threshold_changed) gui.checkBox(box, self, "display_perf_line", "Show performance line", callback=self._on_display_perf_line_changed) grid = QGridLayout() ibox = gui.indentedBox(box, orientation=grid) sp = gui.spin(box, self, "fp_cost", 1, 1000, 10, callback=self._on_display_perf_line_changed) grid.addWidget(QLabel("FP Cost:"), 0, 0) grid.addWidget(sp, 0, 1) sp = gui.spin(box, self, "fn_cost", 1, 1000, 10, callback=self._on_display_perf_line_changed) grid.addWidget(QLabel("FN Cost:")) grid.addWidget(sp, 1, 1) sp = gui.spin(box, self, "target_prior", 1, 99, callback=self._on_display_perf_line_changed) sp.setSuffix("%") sp.addAction(QAction("Auto", sp)) grid.addWidget(QLabel("Prior target class probability:")) grid.addWidget(sp, 2, 1) self.plotview = pg.GraphicsView(background="w") self.plotview.setFrameStyle(QFrame.StyledPanel) self.plot = pg.PlotItem() self.plot.getViewBox().setMenuEnabled(False) self.plot.getViewBox().setMouseEnabled(False, False) pen = QPen(self.palette().color(QPalette.Text)) tickfont = QFont(self.font()) tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11)) axis = self.plot.getAxis("bottom") axis.setTickFont(tickfont) axis.setPen(pen) axis.setLabel("FP Rate (1-Specificity)") axis = self.plot.getAxis("left") axis.setTickFont(tickfont) axis.setPen(pen) axis.setLabel("TP Rate (Sensitivity)") self.plot.showGrid(True, True, alpha=0.1) self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0)) self.plotview.setCentralItem(self.plot) self.mainArea.layout().addWidget(self.plotview)
def __init__(self): super().__init__() self._axis_font = QFont() self._axis_font.setPixelSize(12) self._label_font = QFont() self._label_font.setPixelSize(11) self.dataset = None self.stats = [] self.dist = self.conts = 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 = 1 self.scene_min_x = self.scene_max_x = self.scene_width = 0 self.label_width = 0 self.attrs = VariableListModel() sorted_model = SortProxyModel(sortRole=Qt.UserRole) sorted_model.setSourceModel(self.attrs) sorted_model.sort(0) box = gui.vBox(self.controlArea, "Variable") view = self.attr_list = ListViewSearch() view.setModel(sorted_model) view.setSelectionMode(view.SingleSelection) view.selectionModel().selectionChanged.connect(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) box.layout().addWidget(view) gui.checkBox( box, self, "order_by_importance", "Order by relevance to subgroups", tooltip="Order by 𝜒² or ANOVA over the subgroups", callback=self.apply_attr_sorting) self.group_vars = VariableListModel(placeholder="None") sorted_model = SortProxyModel(sortRole=Qt.UserRole) sorted_model.setSourceModel(self.group_vars) sorted_model.sort(0) box = gui.vBox(self.controlArea, "Subgroups") view = self.group_list = ListViewSearch() view.setModel(sorted_model) view.selectionModel().selectionChanged.connect(self.grouping_changed) view.setMinimumSize(QSize(30, 30)) # See the comment above view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored) box.layout().addWidget(view) gui.checkBox( box, self, "order_grouping_by_importance", "Order by relevance to variable", tooltip="Order by 𝜒² or ANOVA over the variable values", callback=self.apply_group_sorting) # 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)) gui.checkBox(self.display_box, self, "show_annotations", "Annotate", callback=self.update_graph) self.compare_rb = gui.radioButtonsInBox( self.display_box, self, 'compare', btnLabels=["No comparison", "Compare medians", "Compare means"], callback=self.update_graph) # 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.update_graph, stateWhenDisabled=False) gui.checkBox( box, self, 'show_labels', "Show box labels", callback=self.update_graph) self.sort_cb = gui.checkBox( box, self, 'sort_freqs', "Sort by subgroup frequencies", callback=self.update_graph, stateWhenDisabled=False) gui.vBox(self.mainArea) self.box_scene = QGraphicsScene(self) self.box_scene.selectionChanged.connect(self.on_selection_changed) 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) self.stat_test = "" self.mainArea.setMinimumWidth(300) self.info.set_input_summary(self.info.NoInput) self.info.set_output_summary(self.info.NoOutput) self.update_box_visibilities()
def add_main_layout(self): self.data = None self.learner = None self.scatterplot_item = None self.plot_item = None self.x_label = 'x' self.y_label = 'y' self.rmse = "" self.mae = "" self.regressor_name = self.default_learner_name # info box info_box = gui.vBox(self.controlArea, "Info") self.regressor_label = gui.label( widget=info_box, master=self, label="Regressor: %(regressor_name).30s") gui.label(widget=info_box, master=self, label="Mean absolute error: %(mae).6s") gui.label(widget=info_box, master=self, label="Root mean square error: %(rmse).6s") box = gui.vBox(self.controlArea, "Variables") self.x_var_model = itemmodels.VariableListModel() self.comboBoxAttributesX = gui.comboBox(box, self, value='x_var_index', label="Input: ", orientation=Qt.Horizontal, callback=self.apply) self.comboBoxAttributesX.setModel(self.x_var_model) self.expansion_spin = gui.doubleSpin(gui.indentedBox(box), self, "polynomialexpansion", 0, 10, label="Polynomial expansion:", callback=self.apply) gui.separator(box, height=8) self.y_var_model = itemmodels.VariableListModel() self.comboBoxAttributesY = gui.comboBox(box, self, value="y_var_index", label="Target: ", orientation=Qt.Horizontal, callback=self.apply) self.comboBoxAttributesY.setModel(self.y_var_model) properties_box = gui.vBox(self.controlArea, "Properties") self.error_bars_checkbox = gui.checkBox(widget=properties_box, master=self, value='error_bars_enabled', label="Show error bars", callback=self.apply) gui.rubber(self.controlArea) # main area GUI self.plotview = pg.PlotWidget(background="w") self.plot = self.plotview.getPlotItem() axis_color = self.palette().color(QPalette.Text) axis_pen = QPen(axis_color) tickfont = QFont(self.font()) tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11)) axis = self.plot.getAxis("bottom") axis.setLabel(self.x_label) axis.setPen(axis_pen) axis.setTickFont(tickfont) axis = self.plot.getAxis("left") axis.setLabel(self.y_label) axis.setPen(axis_pen) axis.setTickFont(tickfont) self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0), disableAutoRange=True) self.mainArea.layout().addWidget(self.plotview)
def paint(self, painter, option, index): image_space = 5 icon = index.data(Qt.DecorationRole) gene_obj = index.data(Qt.DisplayRole) bold_font = QFont() bold_font.setBold(True) fm = QFontMetrics(QFont()) input_name_str = 'Input name: ' type_of_match_str = 'Input type: ' gene_id_str = 'Gene ID: ' if not icon.isNull(): # paint icon icon.paint( painter, option.rect.adjusted(image_space, image_space, -image_space, -image_space), Qt.AlignVCenter | Qt.AlignLeft) # if image is set, change space variable image_space = 55 # paint gene object data # input string r = option.rect.adjusted(image_space, 7, 0, 0) # left, top, width and height painter.setFont(bold_font) painter.drawText(r.left(), r.top(), r.width(), r.height(), Qt.AlignLeft, input_name_str) painter.setFont(QFont()) painter.drawText(r.left() + fm.width(input_name_str), r.top(), r.width(), r.height(), Qt.AlignLeft, str(gene_obj.input_name)) # gene id string r = option.rect.adjusted(image_space, 22, 0, 0) # left, top, width and height painter.setFont(bold_font) painter.drawText(r.left(), r.top(), r.width(), r.height(), Qt.AlignLeft, type_of_match_str) painter.setFont(QFont()) painter.drawText( r.left() + fm.width(input_name_str), r.top(), r.width(), r.height(), Qt.AlignLeft, str(gene_obj.type_of_match) if gene_obj.type_of_match else 'Unknown') # type of match string r = option.rect.adjusted(image_space, 37, 0, 0) # left, top, width and height painter.setFont(bold_font) painter.drawText(r.left(), r.top(), r.width(), r.height(), Qt.AlignLeft, gene_id_str) painter.setFont(QFont()) painter.drawText( r.left() + fm.width(input_name_str), r.top(), r.width(), r.height(), Qt.AlignLeft, str(gene_obj.ncbi_id) if gene_obj.ncbi_id else 'Unknown')
def __init__(self): super().__init__() self.results = None self.classifier_names = [] self.perf_line = None self.colors = [] self._curve_data = {} self._plot_curves = {} self._rocch = None self._perf_line = None self._tooltip_cache = None box = gui.vBox(self.controlArea, "绘制") tbox = gui.vBox(box, "目标类别") tbox.setFlat(True) self.target_cb = gui.comboBox(tbox, self, "target_index", callback=self._on_target_changed, contentsLength=8) cbox = gui.vBox(box, "分类器") cbox.setFlat(True) self.classifiers_list_box = gui.listBox( cbox, self, "selected_classifiers", "classifier_names", selectionMode=QListView.MultiSelection, callback=self._on_classifiers_changed) abox = gui.vBox(box, "从折叠(Folds)合并ROC曲线") abox.setFlat(True) gui.comboBox(abox, self, "roc_averaging", items=["从折叠合并预测", "平均真阳性率", "阈值处的平均真阳性率和假阳性率", "显示单个曲线"], callback=self._replot) hbox = gui.vBox(box, "ROC凸包") hbox.setFlat(True) gui.checkBox(hbox, self, "display_convex_curve", "显示凸ROC曲线", callback=self._replot) gui.checkBox(hbox, self, "display_convex_hull", "显示ROC凸包", callback=self._replot) box = gui.vBox(self.controlArea, "分析(Analysis)") gui.checkBox(box, self, "display_def_threshold", "默认阈值(0.5)点", callback=self._on_display_def_threshold_changed) gui.checkBox(box, self, "display_perf_line", "显示性能行", callback=self._on_display_perf_line_changed) grid = QGridLayout() gui.indentedBox(box, orientation=grid) sp = gui.spin(box, self, "fp_cost", 1, 1000, 10, alignment=Qt.AlignRight, callback=self._on_display_perf_line_changed) grid.addWidget(QLabel("假阳性率损失:"), 0, 0) grid.addWidget(sp, 0, 1) sp = gui.spin(box, self, "fn_cost", 1, 1000, 10, alignment=Qt.AlignRight, callback=self._on_display_perf_line_changed) grid.addWidget(QLabel("假阴性率损失:")) grid.addWidget(sp, 1, 1) self.target_prior_sp = gui.spin(box, self, "target_prior", 1, 99, alignment=Qt.AlignRight, callback=self._on_target_prior_changed) self.target_prior_sp.setSuffix(" %") self.target_prior_sp.addAction(QAction("Auto", sp)) grid.addWidget(QLabel("目标类先验概率:")) grid.addWidget(self.target_prior_sp, 2, 1) self.plotview = pg.GraphicsView(background="w") self.plotview.setFrameStyle(QFrame.StyledPanel) self.plotview.scene().sigMouseMoved.connect(self._on_mouse_moved) self.plot = pg.PlotItem(enableMenu=False) self.plot.setMouseEnabled(False, False) self.plot.hideButtons() pen = QPen(self.palette().color(QPalette.Text)) tickfont = QFont(self.font()) tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11)) axis = self.plot.getAxis("bottom") axis.setTickFont(tickfont) axis.setPen(pen) axis.setLabel("假阳性率损失 (1-特异度)") axis = self.plot.getAxis("left") axis.setTickFont(tickfont) axis.setPen(pen) axis.setLabel("真阳性率 (灵敏度)") self.plot.showGrid(True, True, alpha=0.1) self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0), padding=0.05) self.plotview.setCentralItem(self.plot) self.mainArea.layout().addWidget(self.plotview)
def __init__(self): super().__init__() self.results = None self.classifier_names = [] self.colors = [] self._curve_data = {} box = gui.vBox(self.controlArea, "绘制") tbox = gui.vBox(box, "目标类别") tbox.setFlat(True) self.target_cb = gui.comboBox(tbox, self, "target_index", callback=self._on_target_changed, contentsLength=8) cbox = gui.vBox(box, "分类器") cbox.setFlat(True) self.classifiers_list_box = gui.listBox( cbox, self, "selected_classifiers", "classifier_names", selectionMode=QListView.MultiSelection, callback=self._on_classifiers_changed) gui.checkBox(box, self, "display_convex_hull", "显示提升凸包", callback=self._replot) self.plotview = pg.GraphicsView(background="w") self.plotview.setFrameStyle(QFrame.StyledPanel) self.plot = pg.PlotItem(enableMenu=False) self.plot.setMouseEnabled(False, False) self.plot.hideButtons() pen = QPen(self.palette().color(QPalette.Text)) tickfont = QFont(self.font()) tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11)) axis = self.plot.getAxis("bottom") axis.setTickFont(tickfont) axis.setPen(pen) axis.setLabel("正确率(P Rate)") axis = self.plot.getAxis("left") axis.setTickFont(tickfont) axis.setPen(pen) axis.setLabel("真阳性率") self.plot.showGrid(True, True, alpha=0.1) self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0), padding=0.05) self.plotview.setCentralItem(self.plot) self.mainArea.layout().addWidget(self.plotview)
def paintEvent(self, event): painter = QStylePainter(self) rect = self._subControlRect(QStyle.SC_SliderGroove) is_horizontal = self.orientation() == Qt.Horizontal minpos, maxpos = self.minimumPosition(), self.maximumPosition() span = rect.width() if is_horizontal else rect.height() x1 = QStyle.sliderPositionFromValue(self.minimum(), self.maximum(), minpos, span, self.invertedAppearance()) x2 = QStyle.sliderPositionFromValue(self.minimum(), self.maximum(), maxpos, span, self.invertedAppearance()) # Background painter.fillRect(rect, Qt.white) # Highlight painter.setOpacity(.7) if is_horizontal: painter.fillRect(x1, rect.y(), x2 - x1, rect.height(), Qt.yellow) else: painter.fillRect(rect.x(), x1, rect.width(), x2 - x1, Qt.yellow) painter.setOpacity(1) # Histogram if self._pixmap: painter.drawPixmap(rect, self._pixmap, self._pixmap.rect()) # Frame painter.setPen(QPen(QBrush(Qt.darkGray), 2)) painter.drawRect(rect) # Handles painter.setPen(QPen(QBrush(self._HANDLE_COLOR), self._HANDLE_WIDTH)) painter.setOpacity(9) if is_horizontal: painter.drawLine(x1, rect.y(), x1, rect.y() + rect.height()) painter.drawLine(x2, rect.y(), x2, rect.y() + rect.height()) else: painter.drawLine(rect.x(), x1, rect.x() + rect.width(), x1) painter.drawLine(rect.x(), x2, rect.x() + rect.width(), x2) painter.setOpacity(1) if self._show_text: painter.setFont(QFont('sans-serif', 7, QFont.Bold)) strMin, strMax = self.formatValues(minpos, maxpos) widthMin = painter.fontMetrics().width(strMin) widthMax = painter.fontMetrics().width(strMax) height = painter.fontMetrics().height() is_enough_space = x2 - x1 > 2 + (max(widthMax, widthMin) if is_horizontal else (2 * height + self._HANDLE_WIDTH)) if is_enough_space: if is_horizontal: painter.drawText(x1 + 3, rect.y() + height - 2, strMin) painter.drawText(x2 - widthMax - 1, rect.y() + rect.height() - 2, strMax) else: painter.drawText(rect.x() + 1, x1 + height, strMin) painter.drawText(rect.x() + rect.width() - widthMax - 1, x2 - 2, strMax)
def __init__(self): super().__init__() self.libraryListSource = [] for name in self.signal_names: setattr(self, name, {}) self._cachedDocuments = {} self.infoBox = gui.vBox(self.controlArea, 'Info') gui.label( self.infoBox, self, "<p>Execute python script.</p><p>Input variables:<ul><li> " + "<li>".join(map("in_{0}, in_{0}s".format, self.signal_names)) + "</ul></p><p>Output variables:<ul><li>" + "<li>".join(map("out_{0}".format, self.signal_names)) + "</ul></p>") self.libraryList = itemmodels.PyListModel( [], self, flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable) self.libraryList.wrap(self.libraryListSource) self.controlBox = gui.vBox(self.controlArea, 'Library') self.controlBox.layout().setSpacing(1) self.libraryView = QListView( editTriggers=QListView.DoubleClicked | QListView.EditKeyPressed, sizePolicy=QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred)) self.libraryView.setItemDelegate(ScriptItemDelegate(self)) self.libraryView.setModel(self.libraryList) self.libraryView.selectionModel().selectionChanged.connect( self.onSelectedScriptChanged) self.controlBox.layout().addWidget(self.libraryView) w = itemmodels.ModelActionsWidget() self.addNewScriptAction = action = QAction("+", self) action.setToolTip("Add a new script to the library") action.triggered.connect(self.onAddScript) w.addAction(action) action = QAction(unicodedata.lookup("MINUS SIGN"), self) action.setToolTip("Remove script from library") action.triggered.connect(self.onRemoveScript) w.addAction(action) action = QAction("Update", self) action.setToolTip("Save changes in the editor to library") action.setShortcut(QKeySequence(QKeySequence.Save)) action.triggered.connect(self.commitChangesToLibrary) w.addAction(action) action = QAction("More", self, toolTip="More actions") new_from_file = QAction("Import Script from File", self) save_to_file = QAction("Save Selected Script to File", self) restore_saved = QAction("Undo Changes to Selected Script", self) save_to_file.setShortcut(QKeySequence(QKeySequence.SaveAs)) new_from_file.triggered.connect(self.onAddScriptFromFile) save_to_file.triggered.connect(self.saveScript) restore_saved.triggered.connect(self.restoreSaved) menu = QMenu(w) menu.addAction(new_from_file) menu.addAction(save_to_file) menu.addAction(restore_saved) action.setMenu(menu) button = w.addAction(action) button.setPopupMode(QToolButton.InstantPopup) w.layout().setSpacing(1) self.controlBox.layout().addWidget(w) self.execute_button = gui.button(self.controlArea, self, 'Run', callback=self.commit) run = QAction("Run script", self, triggered=self.commit, shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_R)) self.addAction(run) self.splitCanvas = QSplitter(Qt.Vertical, self.mainArea) self.mainArea.layout().addWidget(self.splitCanvas) self.defaultFont = defaultFont = \ "Monaco" if sys.platform == "darwin" else "Courier" self.textBox = gui.vBox(self, 'Python Script') self.splitCanvas.addWidget(self.textBox) self.text = PythonScriptEditor(self) self.textBox.layout().addWidget(self.text) self.textBox.setAlignment(Qt.AlignVCenter) self.text.setTabStopWidth(4) self.text.modificationChanged[bool].connect(self.onModificationChanged) self.saveAction = action = QAction("&Save", self.text) action.setToolTip("Save script to file") action.setShortcut(QKeySequence(QKeySequence.Save)) action.setShortcutContext(Qt.WidgetWithChildrenShortcut) action.triggered.connect(self.saveScript) self.consoleBox = gui.vBox(self, 'Console') self.splitCanvas.addWidget(self.consoleBox) self.console = PythonConsole({}, self) self.consoleBox.layout().addWidget(self.console) self.console.document().setDefaultFont(QFont(defaultFont)) self.consoleBox.setAlignment(Qt.AlignBottom) self.console.setTabStopWidth(4) self.splitCanvas.setSizes([2, 1]) self.setAcceptDrops(True) self.controlArea.layout().addStretch(10) self._restoreState() self.settingsAboutToBePacked.connect(self._saveState)
def test_visual_settings(self): font = QFont() font.setItalic(True) font.setFamily("Helvetica") self.send_signal(self.widget.Inputs.data, self.iris) self.send_signal(self.widget.Inputs.model, self.rf_cls) self.wait_until_finished() setter = self.widget._violin_plot.parameter_setter key, value = ("Fonts", "Font family", "Font family"), "Helvetica" self.widget.set_visual_settings(key, value) key, value = ("Fonts", "Axis title", "Font size"), 14 self.widget.set_visual_settings(key, value) key, value = ("Fonts", "Axis title", "Italic"), True self.widget.set_visual_settings(key, value) font.setPointSize(14) for axis in setter.axis_items: self.assertFontEqual(axis.label.font(), font) key, value = ('Fonts', 'Axis ticks', 'Font size'), 15 self.widget.set_visual_settings(key, value) key, value = ('Fonts', 'Axis ticks', 'Italic'), True self.widget.set_visual_settings(key, value) font.setPointSize(15) for axis in setter.axis_items: self.assertFontEqual(axis.style["tickFont"], font) key, value = ("Fonts", "Legend", "Font size"), 16 self.widget.set_visual_settings(key, value) key, value = ("Fonts", "Legend", "Italic"), True self.widget.set_visual_settings(key, value) font.setPointSize(16) self.assertFontEqual(setter.legend._Legend__high_label.font(), font) self.assertFontEqual(setter.legend._Legend__feature_label.font(), font) self.assertFontEqual(setter.legend._Legend__low_label.font(), font) key, value = ("Fonts", "Variable name", "Font size"), 19 self.widget.set_visual_settings(key, value) key, value = ("Fonts", "Variable name", "Italic"), True self.widget.set_visual_settings(key, value) font.setPointSize(19) self.assertFontEqual(setter.labels[0].item.items[0].font(), font) key, value = ("Fonts", "Variable value", "Font size"), 10 self.widget.set_visual_settings(key, value) key, value = ("Fonts", "Variable value", "Italic"), True self.widget.set_visual_settings(key, value) font.setPointSize(10) self.assertFontEqual(setter.labels[0].item.items[1].font(), font) key, value = ("Figure", "Label length", "Label length"), 50 self.widget.set_visual_settings(key, value) self.assertLessEqual(setter.labels[0].item.boundingRect().width(), 50) key, value = ("Figure", "Legend height", "Legend height"), 225 self.widget.set_visual_settings(key, value) self.assertEqual(setter.legend._bar_item.boundingRect().height(), 225) self.send_signal(self.widget.Inputs.data, None) self.send_signal(self.widget.Inputs.data, self.iris) self.wait_until_finished() setter = self.widget._violin_plot.parameter_setter font.setPointSize(14) for axis in setter.axis_items: self.assertFontEqual(axis.label.font(), font) font.setPointSize(15) for axis in setter.axis_items: self.assertFontEqual(axis.style["tickFont"], font) font.setPointSize(16) self.assertFontEqual(setter.legend._Legend__high_label.font(), font) self.assertFontEqual(setter.legend._Legend__feature_label.font(), font) self.assertFontEqual(setter.legend._Legend__low_label.font(), font) font.setPointSize(19) self.assertFontEqual(setter.labels[0].item.items[0].font(), font) font.setPointSize(10) self.assertFontEqual(setter.labels[0].item.items[1].font(), font) self.assertLessEqual(setter.labels[0].item.boundingRect().width(), 50) key, value = ("Figure", "Legend height", "Legend height"), 225 self.widget.set_visual_settings(key, value) self.assertEqual(setter.legend._bar_item.boundingRect().height(), 225)
def __init__(self): super().__init__() for name in self.signal_names: setattr(self, name, {}) for s in self.libraryListSource: s.flags = 0 self._cachedDocuments = {} self.infoBox = gui.vBox(self.controlArea, '信息') gui.label( self.infoBox, self, "<p>执行Python脚本。</p><p>输入变量:<ul><li> " + "<li>".join(map("in_{0}, in_{0}s".format, self.signal_names)) + "</ul></p><p>输出变量:<ul><li>" + "<li>".join(map("out_{0}".format, self.signal_names)) + "</ul></p>") self.libraryList = itemmodels.PyListModel( [], self, flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable) self.libraryList.wrap(self.libraryListSource) self.controlBox = gui.vBox(self.controlArea, '脚本库') self.controlBox.layout().setSpacing(1) self.libraryView = QListView( editTriggers=QListView.DoubleClicked | QListView.EditKeyPressed, sizePolicy=QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred)) self.libraryView.setItemDelegate(ScriptItemDelegate(self)) self.libraryView.setModel(self.libraryList) self.libraryView.selectionModel().selectionChanged.connect( self.onSelectedScriptChanged) self.controlBox.layout().addWidget(self.libraryView) w = itemmodels.ModelActionsWidget() self.addNewScriptAction = action = QAction("+", self) action.setToolTip("向库中添加新脚本") action.triggered.connect(self.onAddScript) w.addAction(action) action = QAction(unicodedata.lookup("MINUS SIGN"), self) action.setToolTip("从库中删除脚本") action.triggered.connect(self.onRemoveScript) w.addAction(action) action = QAction("保存", self) action.setToolTip("将编辑器中的更改的脚本保存到库中") action.setShortcut(QKeySequence(QKeySequence.Save)) action.triggered.connect(self.commitChangesToLibrary) w.addAction(action) action = QAction("更多", self, toolTip="更多操作") new_from_file = QAction("从文件导入脚本", self) save_to_file = QAction("将所选脚本保存到文件", self) save_to_file.setShortcut(QKeySequence(QKeySequence.SaveAs)) new_from_file.triggered.connect(self.onAddScriptFromFile) save_to_file.triggered.connect(self.saveScript) menu = QMenu(w) menu.addAction(new_from_file) menu.addAction(save_to_file) action.setMenu(menu) button = w.addAction(action) button.setPopupMode(QToolButton.InstantPopup) w.layout().setSpacing(1) self.controlBox.layout().addWidget(w) auto = gui.auto_commit(self.controlArea, self, "auto_execute", "运行", checkbox_label="对新数据自动运行") self.execute_button, self.autobox = auto.button, auto.checkbox self.splitCanvas = QSplitter(Qt.Vertical, self.mainArea) self.mainArea.layout().addWidget(self.splitCanvas) self.defaultFont = defaultFont = \ "Monaco" if sys.platform == "darwin" else "Courier" self.textBox = gui.vBox(self, 'Python脚本') self.splitCanvas.addWidget(self.textBox) self.text = PythonScriptEditor(self) self.textBox.layout().addWidget(self.text) self.textBox.setAlignment(Qt.AlignVCenter) self.text.setTabStopWidth(4) self.text.modificationChanged[bool].connect(self.onModificationChanged) self.saveAction = action = QAction("&Save", self.text) action.setToolTip("Save script to file") action.setShortcut(QKeySequence(QKeySequence.Save)) action.setShortcutContext(Qt.WidgetWithChildrenShortcut) action.triggered.connect(self.saveScript) self.consoleBox = gui.vBox(self, '控制台') self.splitCanvas.addWidget(self.consoleBox) self.console = PythonConsole({}, self) self.consoleBox.layout().addWidget(self.console) self.console.document().setDefaultFont(QFont(defaultFont)) self.consoleBox.setAlignment(Qt.AlignBottom) self.console.setTabStopWidth(4) select_row(self.libraryView, self.currentScriptIndex) self.splitCanvas.setSizes([2, 1]) if self.splitterState is not None: self.splitCanvas.restoreState(QByteArray(self.splitterState)) self.splitCanvas.splitterMoved[int, int].connect(self.onSpliterMoved) self.controlArea.layout().addStretch(1) self.resize(800, 600)
class OWBoxPlot(widget.OWWidget): """ Here's how the widget's functions call each other: - `set_data` is a signal handler fills the list boxes and calls `attr_changed`. - `attr_changed` handles changes of attribute or grouping (callbacks for list boxes). It recomputes box data by calling `compute_box_data`, shows the appropriate display box (discrete/continuous) and then calls `layout_changed` - `layout_changed` constructs all the elements for the scene (as lists of QGraphicsItemGroup) and calls `display_changed`. It is called when the attribute or grouping is changed (by attr_changed) and on resize event. - `display_changed` puts the elements corresponding to the current display settings on the scene. It is called when the elements are reconstructed (layout is changed due to selection of attributes or resize event), or when the user changes display settings or colors. For discrete attributes, the flow is a bit simpler: the elements are not constructed in advance (by layout_changed). Instead, layout_changed and display_changed call display_changed_disc that draws everything. """ name = "Box Plot" description = "Visualize the distribution of feature values in a box plot." icon = "icons/BoxPlot.svg" priority = 100 inputs = [("Data", Orange.data.Table, "set_data")] #: Comparison types for continuous variables CompareNone, CompareMedians, CompareMeans = 0, 1, 2 settingsHandler = DomainContextHandler() attribute = ContextSetting(None) group_var = ContextSetting(None) show_annotations = Setting(True) compare = Setting(CompareMedians) stattest = Setting(0) sig_threshold = Setting(0.05) stretched = Setting(True) _sorting_criteria_attrs = { CompareNone: "", CompareMedians: "median", CompareMeans: "mean" } _pen_axis_tick = QPen(Qt.white, 5) _pen_axis = QPen(Qt.darkGray, 3) _pen_median = QPen(QBrush(QColor(0xff, 0xff, 0x00)), 2) _pen_paramet = QPen(QBrush(QColor(0x33, 0x00, 0xff)), 2) _pen_dotted = QPen(QBrush(QColor(0x33, 0x00, 0xff)), 1) _pen_dotted.setStyle(Qt.DotLine) _post_line_pen = QPen(Qt.lightGray, 2) _post_grp_pen = QPen(Qt.lightGray, 4) for pen in (_pen_paramet, _pen_median, _pen_dotted, _pen_axis, _pen_axis_tick, _post_line_pen, _post_grp_pen): pen.setCosmetic(True) pen.setCapStyle(Qt.RoundCap) pen.setJoinStyle(Qt.RoundJoin) _pen_axis_tick.setCapStyle(Qt.FlatCap) _box_brush = QBrush(QColor(0x33, 0x88, 0xff, 0xc0)) _axis_font = QFont() _axis_font.setPixelSize(12) _label_font = QFont() _label_font.setPixelSize(11) _attr_brush = QBrush(QColor(0x33, 0x00, 0xff)) graph_name = "box_scene" 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.p = -1.0 self.scale_x = self.scene_min_x = self.scene_width = 0 self.label_width = 0 common_options = dict(callback=self.attr_changed, sizeHint=(200, 100)) self.attrs = VariableListModel() gui.listView(self.controlArea, self, "attribute", box="Variable", model=self.attrs, **common_options) self.group_vars = VariableListModel() gui.listView(self.controlArea, self, "group_var", box="Grouping", model=self.group_vars, **common_options) # TODO: move Compare median/mean to grouping box self.display_box = gui.vBox(self.controlArea, "Display") 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.display_changed) self.stretching_box = gui.checkBox(self.controlArea, self, 'stretched', "Stretch bars", box='Display', callback=self.display_changed).box gui.vBox(self.mainArea, addSpace=True) self.box_scene = QGraphicsScene() 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(650) self.stats = self.dist = self.conts = [] self.is_continuous = False self.update_display_box() def eventFilter(self, obj, event): if obj is self.box_view.viewport() and \ event.type() == QEvent.Resize: self.layout_changed() return super().eventFilter(obj, event) # noinspection PyTypeChecker 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.dist = self.stats = self.conts = [] self.group_var = None self.attribute = None if dataset: domain = dataset.domain self.group_vars[:] = \ [None] + \ [a for a in chain(domain.variables, domain.metas) if a.is_discrete] self.attrs[:] = chain(domain.variables, (a for a in domain.metas if a.is_primitive())) if self.attrs: self.attribute = self.attrs[0] if domain.class_var and domain.class_var.is_discrete: self.group_var = domain.class_var else: self.group_var = None # Reset to trigger selection via callback self.openContext(self.dataset) self.attr_changed() else: self.reset_all_data() def reset_all_data(self): self.clear_scene() self.infot1.setText("") self.attrs[:] = [] self.group_vars[:] = [] self.is_continuous = False self.update_display_box() def attr_changed(self): self.compute_box_data() self.update_display_box() self.layout_changed() if self.is_continuous: heights = 90 if self.show_annotations else 60 self.box_view.centerOn(self.scene_min_x + self.scene_width / 2, -30 - len(self.stats) * heights / 2 + 45) else: self.box_view.centerOn(self.scene_width / 2, -30 - len(self.boxes) * 40 / 2 + 45) def compute_box_data(self): attr = self.attribute if not attr: return dataset = self.dataset if dataset is None: self.stats = self.dist = self.conts = [] return self.is_continuous = attr.is_continuous if self.group_var: self.dist = [] self.conts = contingency.get_contingency(dataset, attr, self.group_var) if self.is_continuous: self.stats = [BoxData(cont) for cont in self.conts] self.label_txts_all = self.group_var.values else: self.dist = distribution.get_distribution(dataset, attr) self.conts = [] if self.is_continuous: self.stats = [BoxData(self.dist)] self.label_txts_all = [""] self.label_txts = [ txts for stat, txts in zip(self.stats, self.label_txts_all) if stat.n > 0 ] self.stats = [stat for stat in self.stats if stat.n > 0] def update_display_box(self): if self.is_continuous: self.stretching_box.hide() self.display_box.show() self.compare_rb.setEnabled(self.group_var is not None) else: self.stretching_box.show() self.display_box.hide() def clear_scene(self): self.box_scene.clear() self.attr_labels = [] self.labels = [] self.boxes = [] self.mean_labels = [] self.posthoc_lines = [] def layout_changed(self): attr = self.attribute if not attr: return self.clear_scene() if self.dataset is None or len(self.conts) == len(self.dist) == 0: return if not self.is_continuous: return self.display_changed_disc() self.mean_labels = [ self.mean_label(stat, attr, lab) for stat, lab in zip(self.stats, self.label_txts) ] self.draw_axis() self.boxes = [self.box_group(stat) for stat in self.stats] self.labels = [ self.label_group(stat, attr, mean_lab) for stat, mean_lab in zip(self.stats, self.mean_labels) ] self.attr_labels = [ QGraphicsSimpleTextItem(lab) for lab in self.label_txts ] for it in chain(self.labels, self.boxes, self.attr_labels): self.box_scene.addItem(it) self.display_changed() def display_changed(self): if self.dataset is None: return if not self.is_continuous: return self.display_changed_disc() self.order = list(range(len(self.stats))) criterion = self._sorting_criteria_attrs[self.compare] if criterion: self.order = sorted( self.order, key=lambda i: getattr(self.stats[i], criterion)) heights = 90 if self.show_annotations else 60 for row, box_index in enumerate(self.order): y = (-len(self.stats) + row) * heights + 10 self.boxes[box_index].setY(y) labels = self.labels[box_index] if self.show_annotations: labels.show() labels.setY(y) else: labels.hide() label = self.attr_labels[box_index] label.setY(y - 15 - label.boundingRect().height()) if self.show_annotations: label.hide() else: stat = self.stats[box_index] if self.compare == OWBoxPlot.CompareMedians: pos = stat.median + 5 / self.scale_x elif self.compare == OWBoxPlot.CompareMeans: pos = stat.mean + 5 / self.scale_x else: pos = stat.q25 label.setX(pos * self.scale_x) label.show() r = QRectF(self.scene_min_x, -30 - len(self.stats) * heights, self.scene_width, len(self.stats) * heights + 90) self.box_scene.setSceneRect(r) self.compute_tests() self.show_posthoc() def display_changed_disc(self): self.clear_scene() self.attr_labels = [ QGraphicsSimpleTextItem(lab) for lab in self.label_txts_all ] if not self.stretched: if self.group_var: self.labels = [ QGraphicsTextItem("{}".format(int(sum(cont)))) for cont in self.conts ] else: self.labels = [QGraphicsTextItem(str(int(sum(self.dist))))] self.draw_axis_disc() if self.group_var: self.boxes = [self.strudel(cont) for cont in self.conts] else: self.boxes = [self.strudel(self.dist)] for row, box in enumerate(self.boxes): y = (-len(self.boxes) + row) * 40 + 10 label = self.attr_labels[row] b = label.boundingRect() label.setPos(-b.width() - 10, y - b.height() / 2) self.box_scene.addItem(label) if not self.stretched: label = self.labels[row] b = label.boundingRect() if self.group_var: right = self.scale_x * sum(self.conts[row]) else: right = self.scale_x * sum(self.dist) label.setPos(right + 10, y - b.height() / 2) self.box_scene.addItem(label) if self.attribute is not self.group_var: for text_item, bar_part in zip(box.childItems()[1::2], box.childItems()[::2]): label = QGraphicsSimpleTextItem(text_item.toPlainText()) label.setPos(bar_part.boundingRect().x(), y - label.boundingRect().height() - 8) self.box_scene.addItem(label) for text_item in box.childItems()[1::2]: box.removeFromGroup(text_item) self.box_scene.addItem(box) box.setPos(0, y) self.box_scene.setSceneRect(-self.label_width - 5, -30 - len(self.boxes) * 40, self.scene_width, len(self.boxes * 40) + 90) self.infot1.setText("") # noinspection PyPep8Naming def compute_tests(self): # The t-test and ANOVA are implemented here since they efficiently use # the widget-specific data in self.stats. # The non-parametric tests can't do this, so we use statistics.tests def stat_ttest(): d1, d2 = self.stats pooled_var = d1.var / d1.n + d2.var / d2.n df = pooled_var ** 2 / \ ((d1.var / d1.n) ** 2 / (d1.n - 1) + (d2.var / d2.n) ** 2 / (d2.n - 1)) t = abs(d1.mean - d2.mean) / math.sqrt(pooled_var) p = 2 * (1 - scipy.special.stdtr(df, t)) return t, p # TODO: Check this function # noinspection PyPep8Naming def stat_ANOVA(): n = sum(stat.n for stat in self.stats) grand_avg = sum(stat.n * stat.mean for stat in self.stats) / n var_between = sum(stat.n * (stat.mean - grand_avg)**2 for stat in self.stats) df_between = len(self.stats) - 1 var_within = sum(stat.n * stat.var for stat in self.stats) df_within = n - len(self.stats) F = (var_between / df_between) / (var_within / df_within) p = 1 - scipy.special.fdtr(df_between, df_within, F) return F, p if self.compare == OWBoxPlot.CompareNone or len(self.stats) < 2: t = "" elif any(s.n <= 1 for s in self.stats): t = "At least one group has just one instance, " \ "cannot compute significance" elif len(self.stats) == 2: if self.compare == OWBoxPlot.CompareMedians: t = "" # z, self.p = tests.wilcoxon_rank_sum( # self.stats[0].dist, self.stats[1].dist) # t = "Mann-Whitney's z: %.1f (p=%.3f)" % (z, self.p) else: t, self.p = stat_ttest() t = "Student's t: %.3f (p=%.3f)" % (t, self.p) else: if self.compare == OWBoxPlot.CompareMedians: t = "" # U, self.p = -1, -1 # t = "Kruskal Wallis's U: %.1f (p=%.3f)" % (U, self.p) else: F, self.p = stat_ANOVA() t = "ANOVA: %.3f (p=%.3f)" % (F, self.p) self.infot1.setText("<center>%s</center>" % t) def mean_label(self, stat, attr, val_name): label = QGraphicsItemGroup() t = QGraphicsSimpleTextItem( "%.*f" % (attr.number_of_decimals + 1, stat.mean), label) t.setFont(self._label_font) bbox = t.boundingRect() w2, h = bbox.width() / 2, bbox.height() t.setPos(-w2, -h) tpm = QGraphicsSimpleTextItem( " \u00b1 " + "%.*f" % (attr.number_of_decimals + 1, stat.dev), label) tpm.setFont(self._label_font) tpm.setPos(w2, -h) if val_name: vnm = QGraphicsSimpleTextItem(val_name + ": ", label) vnm.setFont(self._label_font) vnm.setBrush(self._attr_brush) vb = vnm.boundingRect() label.min_x = -w2 - vb.width() vnm.setPos(label.min_x, -h) else: label.min_x = -w2 return label def draw_axis(self): """Draw the horizontal axis and sets self.scale_x""" misssing_stats = not self.stats stats = self.stats or [BoxData(np.array([[0.], [1.]]))] mean_labels = self.mean_labels or [ self.mean_label(stats[0], self.attribute, "") ] bottom = min(stat.a_min for stat in stats) top = max(stat.a_max for stat in stats) first_val, step = compute_scale(bottom, top) while bottom <= first_val: first_val -= step bottom = first_val no_ticks = math.ceil((top - first_val) / step) + 1 top = max(top, first_val + no_ticks * step) gbottom = min(bottom, min(stat.mean - stat.dev for stat in stats)) gtop = max(top, max(stat.mean + stat.dev for stat in stats)) bv = self.box_view viewrect = bv.viewport().rect().adjusted(15, 15, -15, -30) self.scale_x = scale_x = viewrect.width() / (gtop - gbottom) # In principle we should repeat this until convergence since the new # scaling is too conservative. (No chance am I doing this.) mlb = min(stat.mean + mean_lab.min_x / scale_x for stat, mean_lab in zip(stats, mean_labels)) if mlb < gbottom: gbottom = mlb self.scale_x = scale_x = viewrect.width() / (gtop - gbottom) self.scene_min_x = gbottom * scale_x self.scene_width = (gtop - gbottom) * scale_x val = first_val while True: l = self.box_scene.addLine(val * scale_x, -1, val * scale_x, 1, self._pen_axis_tick) l.setZValue(100) t = self.box_scene.addSimpleText( self.attribute.repr_val(val) if not misssing_stats else "?", self._axis_font) t.setFlags(t.flags() | QGraphicsItem.ItemIgnoresTransformations) r = t.boundingRect() t.setPos(val * scale_x - r.width() / 2, 8) if val >= top: break val += step self.box_scene.addLine(bottom * scale_x - 4, 0, top * scale_x + 4, 0, self._pen_axis) def draw_axis_disc(self): """ Draw the horizontal axis and sets self.scale_x for discrete attributes """ if self.stretched: step = steps = 10 else: if self.group_var: max_box = max(float(np.sum(dist)) for dist in self.conts) else: max_box = float(np.sum(self.dist)) if max_box == 0: self.scale_x = 1 return _, step = compute_scale(0, max_box) step = int(step) if step > 1 else 1 steps = int(math.ceil(max_box / step)) max_box = step * steps bv = self.box_view viewrect = bv.viewport().rect().adjusted(15, 15, -15, -30) self.scene_width = viewrect.width() lab_width = max(lab.boundingRect().width() for lab in self.attr_labels) lab_width = max(lab_width, 40) lab_width = min(lab_width, self.scene_width / 3) self.label_width = lab_width right_offset = 0 # offset for the right label if not self.stretched and self.labels: if self.group_var: rows = list(zip(self.conts, self.labels)) else: rows = [(self.dist, self.labels[0])] # available space left of the 'group labels' available = self.scene_width - lab_width - 10 scale_x = (available - right_offset) / max_box max_right = max( sum(dist) * scale_x + 10 + lbl.boundingRect().width() for dist, lbl in rows) right_offset = max(0, max_right - max_box * scale_x) self.scale_x = scale_x = \ (self.scene_width - lab_width - 10 - right_offset) / max_box self.box_scene.addLine(0, 0, max_box * scale_x, 0, self._pen_axis) for val in range(0, step * steps + 1, step): l = self.box_scene.addLine(val * scale_x, -1, val * scale_x, 1, self._pen_axis_tick) l.setZValue(100) t = self.box_scene.addSimpleText(str(val), self._axis_font) t.setPos(val * scale_x - t.boundingRect().width() / 2, 8) if self.stretched: self.scale_x *= 100 def label_group(self, stat, attr, mean_lab): def centered_text(val, pos): t = QGraphicsSimpleTextItem( "%.*f" % (attr.number_of_decimals + 1, val), labels) t.setFont(self._label_font) bbox = t.boundingRect() t.setPos(pos - bbox.width() / 2, 22) return t def line(x, down=1): QGraphicsLineItem(x, 12 * down, x, 20 * down, labels) def move_label(label, frm, to): label.setX(to) to += t_box.width() / 2 path = QPainterPath() path.lineTo(0, 4) path.lineTo(to - frm, 4) path.lineTo(to - frm, 8) p = QGraphicsPathItem(path) p.setPos(frm, 12) labels.addToGroup(p) labels = QGraphicsItemGroup() labels.addToGroup(mean_lab) m = stat.mean * self.scale_x mean_lab.setPos(m, -22) line(m, -1) msc = stat.median * self.scale_x med_t = centered_text(stat.median, msc) med_box_width2 = med_t.boundingRect().width() line(msc) x = stat.q25 * self.scale_x t = centered_text(stat.q25, x) t_box = t.boundingRect() med_left = msc - med_box_width2 if x + t_box.width() / 2 >= med_left - 5: move_label(t, x, med_left - t_box.width() - 5) else: line(x) x = stat.q75 * self.scale_x t = centered_text(stat.q75, x) t_box = t.boundingRect() med_right = msc + med_box_width2 if x - t_box.width() / 2 <= med_right + 5: move_label(t, x, med_right + 5) else: line(x) return labels def box_group(self, stat, height=20): def line(x0, y0, x1, y1, *args): return QGraphicsLineItem(x0 * scale_x, y0, x1 * scale_x, y1, *args) scale_x = self.scale_x box = QGraphicsItemGroup() whisker1 = line(stat.a_min, -1.5, stat.a_min, 1.5, box) whisker2 = line(stat.a_max, -1.5, stat.a_max, 1.5, box) vert_line = line(stat.a_min, 0, stat.a_max, 0, box) mean_line = line(stat.mean, -height / 3, stat.mean, height / 3, box) for it in (whisker1, whisker2, mean_line): it.setPen(self._pen_paramet) vert_line.setPen(self._pen_dotted) var_line = line(stat.mean - stat.dev, 0, stat.mean + stat.dev, 0, box) var_line.setPen(self._pen_paramet) mbox = QGraphicsRectItem(stat.q25 * scale_x, -height / 2, (stat.q75 - stat.q25) * scale_x, height, box) mbox.setBrush(self._box_brush) mbox.setPen(QPen(Qt.NoPen)) mbox.setZValue(-200) median_line = line(stat.median, -height / 2, stat.median, height / 2, box) median_line.setPen(self._pen_median) median_line.setZValue(-150) return box def strudel(self, dist): attr = self.attribute ss = np.sum(dist) box = QGraphicsItemGroup() if ss < 1e-6: QGraphicsRectItem(0, -10, 1, 10, box) cum = 0 for i, v in enumerate(dist): if v < 1e-6: continue if self.stretched: v /= ss v *= self.scale_x rect = QGraphicsRectItem(cum + 1, -6, v - 2, 12, box) rect.setBrush(QBrush(QColor(*attr.colors[i]))) rect.setPen(QPen(Qt.NoPen)) if self.stretched: tooltip = "{}: {:.2f}%".format(attr.values[i], 100 * dist[i] / sum(dist)) else: tooltip = "{}: {}".format(attr.values[i], int(dist[i])) rect.setToolTip(tooltip) text = QGraphicsTextItem(attr.values[i]) box.addToGroup(text) cum += v return box def show_posthoc(self): def line(y0, y1): it = self.box_scene.addLine(x, y0, x, y1, self._post_line_pen) it.setZValue(-100) self.posthoc_lines.append(it) while self.posthoc_lines: self.box_scene.removeItem(self.posthoc_lines.pop()) if self.compare == OWBoxPlot.CompareNone or len(self.stats) < 2: return if self.compare == OWBoxPlot.CompareMedians: crit_line = "median" else: crit_line = "mean" xs = [] height = 90 if self.show_annotations else 60 y_up = -len(self.stats) * height + 10 for pos, box_index in enumerate(self.order): stat = self.stats[box_index] x = getattr(stat, crit_line) * self.scale_x xs.append(x) by = y_up + pos * height line(by + 12, 3) line(by - 12, by - 25) used_to = [] last_to = to = 0 for frm, frm_x in enumerate(xs[:-1]): for to in range(frm + 1, len(xs)): if xs[to] - frm_x > 1.5: to -= 1 break if last_to == to or frm == to: continue for rowi, used in enumerate(used_to): if used < frm: used_to[rowi] = to break else: rowi = len(used_to) used_to.append(to) y = -6 - rowi * 6 it = self.box_scene.addLine(frm_x - 2, y, xs[to] + 2, y, self._post_grp_pen) self.posthoc_lines.append(it) last_to = to def get_widget_name_extension(self): if self.attribute: return self.attribute.name def send_report(self): self.report_plot() text = "" if self.attribute: text += "Box plot for attribute '{}' ".format(self.attribute.name) if self.group_var: text += "grouped by '{}'".format(self.group_var.name) if text: self.report_caption(text)
def __init__(self): def update_font_family(**settings): for label in self.initial_settings[self.LABELS_BOX]: if label != self.FONT_FAMILY_LABEL: setter = self._setters[self.LABELS_BOX][label] setter(**settings) def update_title(**settings): Updater.update_plot_title_font(self.title_item, **settings) def update_label(**settings): self.label_font = Updater.change_font(self.label_font, settings) Updater.update_label_font(self.labels, self.label_font) def update_axes_titles(**settings): Updater.update_axes_titles_font(self.axis_items, **settings) def update_axes_ticks(**settings): Updater.update_axes_ticks_font(self.axis_items, **settings) def update_legend(**settings): self.legend_settings.update(**settings) Updater.update_legend_font(self.legend_items, **settings) def update_title_text(**settings): Updater.update_plot_title_text(self.title_item, settings[self.TITLE_LABEL]) def update_axis(axis, **settings): Updater.update_axis_title_text(self.getAxis(axis), settings[self.TITLE_LABEL]) self.FONT_FAMILY_SETTING: SettingsType = { # pylint: disable=invalid-name Updater.FONT_FAMILY_LABEL: (QFontDatabase().families(), QFont().family()), } self.FONT_SETTING: SettingsType = { # pylint: disable=invalid-name Updater.SIZE_LABEL: (range(4, 50), QFont().pointSize()), Updater.IS_ITALIC_LABEL: (None, False) } self.label_font = QFont() self.legend_settings = {} self._setters = { self.LABELS_BOX: { self.FONT_FAMILY_LABEL: update_font_family, self.TITLE_LABEL: update_title, self.LABEL_LABEL: update_label, self.AXIS_TITLE_LABEL: update_axes_titles, self.AXIS_TICKS_LABEL: update_axes_ticks, self.LEGEND_LABEL: update_legend, }, self.ANNOT_BOX: { self.TITLE_LABEL: update_title_text, self.X_AXIS_LABEL: lambda **kw: update_axis("bottom", **kw), self.Y_AXIS_LABEL: lambda **kw: update_axis("left", **kw), } } self.initial_settings: Dict[str, Dict[str, SettingsType]] = NotImplemented self.update_setters() self._check_setters()
def __init__(self): super().__init__() self.results = None self.classifier_names = [] self.perf_line = None self.colors = [] self._curve_data = {} self._plot_curves = {} self._rocch = None self._perf_line = None self._tooltip_cache = None box = gui.vBox(self.controlArea, "Plot") self.target_cb = gui.comboBox(box, self, "target_index", label="Target", orientation=Qt.Horizontal, callback=self._on_target_changed, contentsLength=8, searchable=True) gui.widgetLabel(box, "Classifiers") line_height = 4 * QFontMetrics(self.font()).lineSpacing() self.classifiers_list_box = gui.listBox( box, self, "selected_classifiers", "classifier_names", selectionMode=QListView.MultiSelection, callback=self._on_classifiers_changed, sizeHint=QSize(0, line_height)) abox = gui.vBox(self.controlArea, "Curves") gui.comboBox(abox, self, "roc_averaging", items=[ "Merge Predictions from Folds", "Mean TP Rate", "Mean TP and FP at Threshold", "Show Individual Curves" ], callback=self._replot) gui.checkBox(abox, self, "display_convex_curve", "Show convex ROC curves", callback=self._replot) gui.checkBox(abox, self, "display_convex_hull", "Show ROC convex hull", callback=self._replot) box = gui.vBox(self.controlArea, "Analysis") gui.checkBox(box, self, "display_def_threshold", "Default threshold (0.5) point", callback=self._on_display_def_threshold_changed) gui.checkBox(box, self, "display_perf_line", "Show performance line", callback=self._on_display_perf_line_changed) grid = QGridLayout() gui.indentedBox(box, orientation=grid) sp = gui.spin(box, self, "fp_cost", 1, 1000, 10, alignment=Qt.AlignRight, callback=self._on_display_perf_line_changed) grid.addWidget(QLabel("FP Cost:"), 0, 0) grid.addWidget(sp, 0, 1) sp = gui.spin(box, self, "fn_cost", 1, 1000, 10, alignment=Qt.AlignRight, callback=self._on_display_perf_line_changed) grid.addWidget(QLabel("FN Cost:")) grid.addWidget(sp, 1, 1) self.target_prior_sp = gui.spin(box, self, "target_prior", 1, 99, alignment=Qt.AlignRight, spinType=float, callback=self._on_target_prior_changed) self.target_prior_sp.setSuffix(" %") self.target_prior_sp.addAction(QAction("Auto", sp)) grid.addWidget(QLabel("Prior probability:")) grid.addWidget(self.target_prior_sp, 2, 1) self.plotview = GraphicsView(background=None) self.plotview.setFrameStyle(QFrame.StyledPanel) self.plotview.scene().sigMouseMoved.connect(self._on_mouse_moved) self.plot = PlotItem(enableMenu=False) self.plot.setMouseEnabled(False, False) self.plot.hideButtons() tickfont = QFont(self.font()) tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11)) axis = self.plot.getAxis("bottom") axis.setTickFont(tickfont) axis.setLabel("FP Rate (1-Specificity)") axis.setGrid(16) axis = self.plot.getAxis("left") axis.setTickFont(tickfont) axis.setLabel("TP Rate (Sensitivity)") axis.setGrid(16) self.plot.showGrid(True, True, alpha=0.2) self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0), padding=0.05) self.plotview.setCentralItem(self.plot) self.mainArea.layout().addWidget(self.plotview)
def __init__(self): super().__init__() self.res = None self.orgnism = 352472 self.server = 'https://dictyexpress.research.bcm.edu' self.headerLabels = [x[1] for x in Labels] self.searchString = "" self.items = [] self.progress_bar = None # threads self.threadpool = QThreadPool() # Login Section box = gui.widgetBox(self.controlArea, 'Login') self.namefield = gui.lineEdit(box, self, "username", "Username:"******"password", "Password:"******"Output", addSpace=True) gui.radioButtonsInBox(box, self, "gene_as_attr_name", ["Genes in rows", "Genes in columns"], callback=self.commit) self.controlArea.layout().addWidget(h_line()) self.commit_button = gui.button(self.controlArea, self, "Commit", callback=self.commit) self.handle_commit_button(False) self.refresh_button = gui.button(self.controlArea, self, "Refresh", callback=self.refresh) self.handle_cache_button(True) gui.rubber(self.controlArea) # Experiment Section label = QLabel("Available projects:") my_font = QFont() my_font.setBold(True) label.setFont(my_font) self.mainArea.layout().addWidget(label) self.mainArea.layout().addWidget(h_line()) self.filter = gui.lineEdit(self.mainArea, self, "searchString", "Filter:", callbackOnType=True, callback=self.search_update) self.experimentsWidget = QTreeWidget(alternatingRowColors=True, rootIsDecorated=False, uniformRowHeights=True, sortingEnabled=True) self.experimentsWidget.setItemDelegateForColumn( 0, gui.IndicatorItemDelegate(self, role=Qt.DisplayRole)) self.experimentsWidget.selectionModel().selectionChanged.connect( self.onSelectionChanged) self.experimentsWidget.setHeaderLabels(self.headerLabels) self.mainArea.layout().addWidget(self.experimentsWidget) self.auth_set() self.connect() self.sizeHint()
def __init__(self): super().__init__() ConcurrentWidgetMixin.__init__(self) self._res: Optional[genapi.GenAPI] = None self.organism = '44689' self.server = 'https://dictyexpress.research.bcm.edu' self.headerLabels = [x[1] for x in Labels] self.searchString = "" self.items = [] self.genapi_pub_auth = { 'url': genapi.DEFAULT_URL, 'username': genapi.DEFAULT_EMAIL, 'password': genapi.DEFAULT_PASSWD, } # Login Section box = gui.widgetBox(self.controlArea, 'Sign in') self.user_info = gui.label(box, self, '') self.server_info = gui.label(box, self, '') box = gui.widgetBox(box, orientation=Qt.Horizontal) self.sign_in_btn = gui.button(box, self, 'Sign In', callback=self.sign_in, autoDefault=False) self.sign_out_btn = gui.button(box, self, 'Sign Out', callback=self.sign_out, autoDefault=False) box = gui.widgetBox(self.controlArea, "Output") gui.radioButtonsInBox(box, self, "gene_as_attr_name", ["Genes in rows", "Genes in columns"], callback=self.invalidate) self.clear_cache_btn = gui.button(self.controlArea, self, "Clear cache", autoDefault=False, callback=self.clear_cache) gui.rubber(self.controlArea) self.commit_button = gui.auto_commit(self.controlArea, self, "auto_commit", "&Commit", box=False) # Experiment Section label = QLabel("Available projects:") my_font = QFont() my_font.setBold(True) label.setFont(my_font) self.mainArea.layout().addWidget(label) self.filter = gui.lineEdit(self.mainArea, self, "searchString", "Filter:", callbackOnType=True, callback=self.search_update) self.experimentsWidget = QTreeWidget(alternatingRowColors=True, rootIsDecorated=False, uniformRowHeights=True, sortingEnabled=True) self.experimentsWidget.setItemDelegateForColumn( 0, gui.IndicatorItemDelegate(self, role=Qt.DisplayRole)) self.experimentsWidget.selectionModel().selectionChanged.connect( self.on_selection_changed) self.experimentsWidget.setHeaderLabels(self.headerLabels) self.mainArea.layout().addWidget(self.experimentsWidget) self.sign_in(silent=True) self.sizeHint()
def __init__(self): super().__init__() self.in_data = None self.in_distance = None self.in_learner = None self.in_classifier = None self.in_object = None self.auto_execute = False for s in self.libraryListSource: s.flags = 0 self._cachedDocuments = {} self.infoBox = gui.vBox(self.controlArea, 'Info') gui.label( self.infoBox, self, "<p>Execute python script.</p><p>Input variables:<ul><li> " + \ "<li>".join(t.name for t in self.inputs) + \ "</ul></p><p>Output variables:<ul><li>" + \ "<li>".join(t.name for t in self.outputs) + \ "</ul></p>" ) self.libraryList = itemmodels.PyListModel( [], self, flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable) self.libraryList.wrap(self.libraryListSource) self.controlBox = gui.vBox(self.controlArea, 'Library') self.controlBox.layout().setSpacing(1) self.libraryView = QListView( editTriggers=QListView.DoubleClicked | QListView.EditKeyPressed, sizePolicy=QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred)) self.libraryView.setItemDelegate(ScriptItemDelegate(self)) self.libraryView.setModel(self.libraryList) self.libraryView.selectionModel().selectionChanged.connect( self.onSelectedScriptChanged) self.controlBox.layout().addWidget(self.libraryView) w = itemmodels.ModelActionsWidget() self.addNewScriptAction = action = QAction("+", self) action.setToolTip("Add a new script to the library") action.triggered.connect(self.onAddScript) w.addAction(action) action = QAction(unicodedata.lookup("MINUS SIGN"), self) action.setToolTip("Remove script from library") action.triggered.connect(self.onRemoveScript) w.addAction(action) action = QAction("Update", self) action.setToolTip("Save changes in the editor to library") action.setShortcut(QKeySequence(QKeySequence.Save)) action.triggered.connect(self.commitChangesToLibrary) w.addAction(action) action = QAction("More", self, toolTip="More actions") new_from_file = QAction("Import Script from File", self) save_to_file = QAction("Save Selected Script to File", self) save_to_file.setShortcut(QKeySequence(QKeySequence.SaveAs)) new_from_file.triggered.connect(self.onAddScriptFromFile) save_to_file.triggered.connect(self.saveScript) menu = QMenu(w) menu.addAction(new_from_file) menu.addAction(save_to_file) action.setMenu(menu) button = w.addAction(action) button.setPopupMode(QToolButton.InstantPopup) w.layout().setSpacing(1) self.controlBox.layout().addWidget(w) self.execute_button = gui.auto_commit(self.controlArea, self, "auto_execute", "Execute", auto_label="Auto Execute") self.splitCanvas = QSplitter(Qt.Vertical, self.mainArea) self.mainArea.layout().addWidget(self.splitCanvas) self.defaultFont = defaultFont = \ "Monaco" if sys.platform == "darwin" else "Courier" self.textBox = gui.vBox(self, 'Python Script') self.splitCanvas.addWidget(self.textBox) self.text = PythonScriptEditor(self) self.textBox.layout().addWidget(self.text) self.textBox.setAlignment(Qt.AlignVCenter) self.text.setTabStopWidth(4) self.text.modificationChanged[bool].connect(self.onModificationChanged) self.saveAction = action = QAction("&Save", self.text) action.setToolTip("Save script to file") action.setShortcut(QKeySequence(QKeySequence.Save)) action.setShortcutContext(Qt.WidgetWithChildrenShortcut) action.triggered.connect(self.saveScript) self.consoleBox = gui.vBox(self, 'Console') self.splitCanvas.addWidget(self.consoleBox) self.console = PythonConsole({}, self) self.consoleBox.layout().addWidget(self.console) self.console.document().setDefaultFont(QFont(defaultFont)) self.consoleBox.setAlignment(Qt.AlignBottom) self.console.setTabStopWidth(4) select_row(self.libraryView, self.currentScriptIndex) self.splitCanvas.setSizes([2, 1]) if self.splitterState is not None: self.splitCanvas.restoreState(QByteArray(self.splitterState)) self.splitCanvas.splitterMoved[int, int].connect(self.onSpliterMoved) self.controlArea.layout().addStretch(1) self.resize(800, 600)
def main(argv=None): if argv is None: argv = sys.argv usage = "usage: %prog [options] [workflow_file]" parser = optparse.OptionParser(usage=usage) parser.add_option("--no-discovery", action="store_true", help="Don't run widget discovery " "(use full cache instead)") parser.add_option("--force-discovery", action="store_true", help="Force full widget discovery " "(invalidate cache)") parser.add_option("--no-welcome", action="store_true", help="Don't show welcome dialog.") parser.add_option("--no-splash", action="store_true", help="Don't show splash screen.") parser.add_option("-l", "--log-level", help="Logging level (0, 1, 2, 3, 4)", type="int", default=1) parser.add_option("--style", help="QStyle to use", type="str", default=None) parser.add_option("--stylesheet", help="Application level CSS style sheet to use", type="str", default=None) parser.add_option("--qt", help="Additional arguments for QApplication", type="str", default=None) parser.add_option("--config", help="Configuration namespace", type="str", default="orangecanvas.example") # -m canvas orange.widgets # -m canvas --config orange.widgets (options, args) = parser.parse_args(argv[1:]) levels = [logging.CRITICAL, logging.ERROR, logging.WARN, logging.INFO, logging.DEBUG] # Fix streams before configuring logging (otherwise it will store # and write to the old file descriptors) fix_win_pythonw_std_stream() # Set http_proxy environment variable(s) for some clients fix_set_proxy_env() # Try to fix macOS automatic window tabbing (Sierra and later) fix_macos_nswindow_tabbing() # File handler should always be at least INFO level so we need # the application root level to be at least at INFO. root_level = min(levels[options.log_level], logging.INFO) rootlogger = logging.getLogger(__package__) rootlogger.setLevel(root_level) # Standard output stream handler at the requested level stream_hander = logging.StreamHandler() stream_hander.setLevel(level=levels[options.log_level]) rootlogger.addHandler(stream_hander) if options.config is not None: try: cfg = utils.name_lookup(options.config) except (ImportError, AttributeError): pass else: config.set_default(cfg()) log.info("activating %s", options.config) log.info("Starting 'Orange Canvas' application.") qt_argv = argv[:1] style = options.style defaultstylesheet = "orange.qss" fusiontheme = None if style is not None: if style.startswith("fusion:"): qt_argv += ["-style", "fusion"] _, _, fusiontheme = style.partition(":") else: qt_argv += ["-style", style] if options.qt is not None: qt_argv += shlex.split(options.qt) qt_argv += args if QT_VERSION >= 0x50600: CanvasApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) log.debug("Starting CanvasApplicaiton with argv = %r.", qt_argv) app = CanvasApplication(qt_argv) if app.style().metaObject().className() == "QFusionStyle": if fusiontheme == "breeze-dark": app.setPalette(breeze_dark()) defaultstylesheet = "darkorange.qss" palette = app.palette() if style is None and palette.color(QPalette.Window).value() < 127: log.info("Switching default stylesheet to darkorange") defaultstylesheet = "darkorange.qss" # NOTE: config.init() must be called after the QApplication constructor config.init() file_handler = logging.FileHandler( filename=os.path.join(config.log_dir(), "canvas.log"), mode="w" ) file_handler.setLevel(root_level) rootlogger.addHandler(file_handler) # intercept any QFileOpenEvent requests until the main window is # fully initialized. # NOTE: The QApplication must have the executable ($0) and filename # arguments passed in argv otherwise the FileOpen events are # triggered for them (this is done by Cocoa, but QApplicaiton filters # them out if passed in argv) open_requests = [] def onrequest(url): log.info("Received an file open request %s", url) open_requests.append(url) app.fileOpenRequest.connect(onrequest) settings = QSettings() stylesheet = options.stylesheet or defaultstylesheet stylesheet_string = None if stylesheet != "none": if os.path.isfile(stylesheet): with io.open(stylesheet, "r") as f: stylesheet_string = f.read() else: if not os.path.splitext(stylesheet)[1]: # no extension stylesheet = os.path.extsep.join([stylesheet, "qss"]) pkg_name = __package__ resource = "styles/" + stylesheet if pkg_resources.resource_exists(pkg_name, resource): stylesheet_string = \ pkg_resources.resource_string(pkg_name, resource).decode("utf-8") base = pkg_resources.resource_filename(pkg_name, "styles") pattern = re.compile( r"^\s@([a-zA-Z0-9_]+?)\s*:\s*([a-zA-Z0-9_/]+?);\s*$", flags=re.MULTILINE ) matches = pattern.findall(stylesheet_string) for prefix, search_path in matches: QDir.addSearchPath(prefix, os.path.join(base, search_path)) log.info("Adding search path %r for prefix, %r", search_path, prefix) stylesheet_string = pattern.sub("", stylesheet_string) else: log.info("%r style sheet not found.", stylesheet) # Add the default canvas_icons search path dirpath = os.path.abspath(os.path.dirname(__file__)) QDir.addSearchPath("canvas_icons", os.path.join(dirpath, "icons")) canvas_window = CanvasMainWindow() canvas_window.setAttribute(Qt.WA_DeleteOnClose) canvas_window.setWindowIcon(config.application_icon()) if stylesheet_string is not None: canvas_window.setStyleSheet(stylesheet_string) if not options.force_discovery: reg_cache = cache.registry_cache() else: reg_cache = None widget_registry = qt.QtWidgetRegistry() widget_discovery = config.widget_discovery( widget_registry, cached_descriptions=reg_cache) want_splash = \ settings.value("startup/show-splash-screen", True, type=bool) and \ not options.no_splash if want_splash: pm, rect = config.splash_screen() splash_screen = SplashScreen(pixmap=pm, textRect=rect) splash_screen.setAttribute(Qt.WA_DeleteOnClose) splash_screen.setFont(QFont("Helvetica", 12)) color = QColor("#FFD39F") def show_message(message): splash_screen.showMessage(message, color=color) widget_registry.category_added.connect(show_message) show_splash = splash_screen.show close_splash = splash_screen.close else: show_splash = close_splash = lambda: None log.info("Running widget discovery process.") cache_filename = os.path.join(config.cache_dir(), "widget-registry.pck") if options.no_discovery: with open(cache_filename, "rb") as f: widget_registry = pickle.load(f) widget_registry = qt.QtWidgetRegistry(widget_registry) else: show_splash() widget_discovery.run(config.widgets_entry_points()) close_splash() # Store cached descriptions cache.save_registry_cache(widget_discovery.cached_descriptions) with open(cache_filename, "wb") as f: pickle.dump(WidgetRegistry(widget_registry), f) set_global_registry(widget_registry) canvas_window.set_widget_registry(widget_registry) canvas_window.show() canvas_window.raise_() want_welcome = \ settings.value("startup/show-welcome-screen", True, type=bool) \ and not options.no_welcome # Process events to make sure the canvas_window layout has # a chance to activate (the welcome dialog is modal and will # block the event queue, plus we need a chance to receive open file # signals when running without a splash screen) app.processEvents() app.fileOpenRequest.connect(canvas_window.open_scheme_file) if want_welcome and not args and not open_requests: canvas_window.welcome_dialog() elif args: log.info("Loading a scheme from the command line argument %r", args[0]) canvas_window.load_scheme(args[0]) elif open_requests: log.info("Loading a scheme from an `QFileOpenEvent` for %r", open_requests[-1]) canvas_window.load_scheme(open_requests[-1].toLocalFile()) # Tee stdout and stderr into Output dock output_view = canvas_window.output_view() stdout = TextStream() stdout.stream.connect(output_view.write) if sys.stdout: stdout.stream.connect(sys.stdout.write) stdout.flushed.connect(sys.stdout.flush) stderr = TextStream() error_writer = output_view.formated(color=Qt.red) stderr.stream.connect(error_writer.write) if sys.stderr: stderr.stream.connect(sys.stderr.write) stderr.flushed.connect(sys.stderr.flush) with ExitStack() as stack: stack.enter_context(closing(stderr)) stack.enter_context(closing(stdout)) stack.enter_context(redirect_stdout(stdout)) stack.enter_context(redirect_stderr(stderr)) log.info("Entering main event loop.") sys.excepthook = ExceptHook(stream=stderr) try: status = app.exec_() finally: sys.excepthook = sys.__excepthook__ del canvas_window app.processEvents() app.flush() # Collect any cycles before deleting the QApplication instance gc.collect() del app return status
def test_visual_settings(self): graph = self.widget.graph font = QFont() font.setItalic(True) font.setFamily("Helvetica") self.send_signal(self.widget.Inputs.data, self.data) key, value = ("Fonts", "Font family", "Font family"), "Helvetica" self.widget.set_visual_settings(key, value) key, value = ("Fonts", "Title", "Font size"), 20 self.widget.set_visual_settings(key, value) key, value = ("Fonts", "Title", "Italic"), True self.widget.set_visual_settings(key, value) font.setPointSize(20) self.assertFontEqual( graph.parameter_setter.title_item.item.font(), font ) key, value = ("Fonts", "Axis title", "Font size"), 16 self.widget.set_visual_settings(key, value) key, value = ("Fonts", "Axis title", "Italic"), True self.widget.set_visual_settings(key, value) font.setPointSize(16) for item in graph.parameter_setter.axis_items: self.assertFontEqual(item.label.font(), font) key, value = ("Fonts", "Axis ticks", "Font size"), 15 self.widget.set_visual_settings(key, value) key, value = ("Fonts", "Axis ticks", "Italic"), True self.widget.set_visual_settings(key, value) font.setPointSize(15) for item in graph.parameter_setter.axis_items: self.assertFontEqual(item.style["tickFont"], font) key, value = ("Fonts", "Legend", "Font size"), 14 self.widget.set_visual_settings(key, value) key, value = ("Fonts", "Legend", "Italic"), True self.widget.set_visual_settings(key, value) font.setPointSize(14) legend_item = list(graph.parameter_setter.legend_items)[0] self.assertFontEqual(legend_item[1].item.font(), font) key, value = ("Annotations", "Title", "Title"), "Foo" self.widget.set_visual_settings(key, value) self.assertEqual( graph.parameter_setter.title_item.item.toPlainText(), "Foo" ) self.assertEqual(graph.parameter_setter.title_item.text, "Foo") key, value = ("Figure", "Gridlines", "Show"), False self.assertTrue(graph.getAxis("left").grid) self.widget.set_visual_settings(key, value) self.assertFalse(graph.getAxis("left").grid) key, value = ("Figure", "Bottom axis", "Vertical ticks"), False self.assertTrue(graph.getAxis("bottom").style["rotateTicks"]) self.widget.set_visual_settings(key, value) self.assertFalse(graph.getAxis("bottom").style["rotateTicks"]) key, value = ("Figure", "Group axis", "Vertical ticks"), True self.assertFalse(graph.group_axis.style["rotateTicks"]) self.widget.set_visual_settings(key, value) self.assertTrue(graph.group_axis.style["rotateTicks"])
def __init__(self, master): super().__init__(master) self.anchor_font = QFont()
def main(argv=None): # Allow termination with CTRL + C signal.signal(signal.SIGINT, signal.SIG_DFL) # Disable pyqtgraph's atexit and QApplication.aboutToQuit cleanup handlers. pyqtgraph.setConfigOption("exitCleanup", False) if argv is None: argv = sys.argv usage = "usage: %prog [options] [workflow_file]" parser = optparse.OptionParser(usage=usage) parser.add_option("--no-discovery", action="store_true", help="Don't run widget discovery " "(use full cache instead)") parser.add_option("--force-discovery", action="store_true", help="Force full widget discovery " "(invalidate cache)") parser.add_option("--clear-widget-settings", action="store_true", help="Remove stored widget setting") parser.add_option("--no-welcome", action="store_true", help="Don't show welcome dialog.") parser.add_option("--no-splash", action="store_true", help="Don't show splash screen.") parser.add_option("-l", "--log-level", help="Logging level (0, 1, 2, 3, 4)", type="int", default=1) parser.add_option("--style", help="QStyle to use", type="str", default=None) parser.add_option("--stylesheet", help="Application level CSS style sheet to use", type="str", default=None) parser.add_option("--qt", help="Additional arguments for QApplication", type="str", default=None) (options, args) = parser.parse_args(argv[1:]) levels = [ logging.CRITICAL, logging.ERROR, logging.WARN, logging.INFO, logging.DEBUG ] # Fix streams before configuring logging (otherwise it will store # and write to the old file descriptors) fix_win_pythonw_std_stream() # Try to fix macOS automatic window tabbing (Sierra and later) fix_macos_nswindow_tabbing() logging.basicConfig( level=levels[options.log_level], handlers=[make_stdout_handler(levels[options.log_level])]) # set default application configuration config_ = config.Config() canvasconfig.set_default(config_) log.info("Starting 'Orange Canvas' application.") qt_argv = argv[:1] style = options.style defaultstylesheet = "orange.qss" fusiontheme = None if style is not None: if style.startswith("fusion:"): qt_argv += ["-style", "fusion"] _, _, fusiontheme = style.partition(":") else: qt_argv += ["-style", style] if options.qt is not None: qt_argv += shlex.split(options.qt) qt_argv += args if QT_VERSION >= 0x50600: CanvasApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) log.debug("Starting CanvasApplicaiton with argv = %r.", qt_argv) app = CanvasApplication(qt_argv) config_.init() if app.style().metaObject().className() == "QFusionStyle": if fusiontheme == "breeze-dark": app.setPalette(breeze_dark()) defaultstylesheet = "darkorange.qss" palette = app.palette() if style is None and palette.color(QPalette.Window).value() < 127: log.info("Switching default stylesheet to darkorange") defaultstylesheet = "darkorange.qss" # Initialize SQL query and execution time logger (in SqlTable) sql_level = min(levels[options.log_level], logging.INFO) make_sql_logger(sql_level) clear_settings_flag = os.path.join(widget_settings_dir(), "DELETE_ON_START") if options.clear_widget_settings or \ os.path.isfile(clear_settings_flag): log.info("Clearing widget settings") shutil.rmtree(widget_settings_dir(), ignore_errors=True) # Set http_proxy environment variables, after (potentially) clearing settings fix_set_proxy_env() # Setup file log handler for the select logger list - this is always # at least INFO level = min(levels[options.log_level], logging.INFO) file_handler = logging.FileHandler(filename=os.path.join( config.log_dir(), "canvas.log"), mode="w") file_handler.setFormatter( logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s")) file_handler.setLevel(level) for namespace in ["orangecanvas", "orangewidget", "Orange"]: logger = logging.getLogger(namespace) logger.setLevel(level) logger.addHandler(file_handler) # intercept any QFileOpenEvent requests until the main window is # fully initialized. # NOTE: The QApplication must have the executable ($0) and filename # arguments passed in argv otherwise the FileOpen events are # triggered for them (this is done by Cocoa, but QApplicaiton filters # them out if passed in argv) open_requests = [] def onrequest(url): log.info("Received an file open request %s", url) open_requests.append(url) app.fileOpenRequest.connect(onrequest) settings = QSettings() settings.setValue('startup/launch-count', settings.value('startup/launch-count', 0, int) + 1) if settings.value("error-reporting/send-statistics", False, type=bool) \ and is_release: UsageStatistics.set_enabled(True) stylesheet = options.stylesheet or defaultstylesheet stylesheet_string = None if stylesheet != "none": if os.path.isfile(stylesheet): with open(stylesheet, "r") as f: stylesheet_string = f.read() else: if not os.path.splitext(stylesheet)[1]: # no extension stylesheet = os.path.extsep.join([stylesheet, "qss"]) pkg_name = orangecanvas.__name__ resource = "styles/" + stylesheet if pkg_resources.resource_exists(pkg_name, resource): stylesheet_string = \ pkg_resources.resource_string(pkg_name, resource).decode() base = pkg_resources.resource_filename(pkg_name, "styles") pattern = re.compile( r"^\s@([a-zA-Z0-9_]+?)\s*:\s*([a-zA-Z0-9_/]+?);\s*$", flags=re.MULTILINE) matches = pattern.findall(stylesheet_string) for prefix, search_path in matches: QDir.addSearchPath(prefix, os.path.join(base, search_path)) log.info("Adding search path %r for prefix, %r", search_path, prefix) stylesheet_string = pattern.sub("", stylesheet_string) else: log.info("%r style sheet not found.", stylesheet) # Add the default canvas_icons search path dirpath = os.path.abspath(os.path.dirname(orangecanvas.__file__)) QDir.addSearchPath("canvas_icons", os.path.join(dirpath, "icons")) canvas_window = MainWindow() canvas_window.setAttribute(Qt.WA_DeleteOnClose) canvas_window.setWindowIcon(config.application_icon()) # initialize notification server, set to initial canvas notif_server = NotificationServer() canvas.notification_server_instance = notif_server canvas_window.set_notification_server(notif_server) if stylesheet_string is not None: canvas_window.setStyleSheet(stylesheet_string) if not options.force_discovery: reg_cache = cache.registry_cache() else: reg_cache = None widget_registry = qt.QtWidgetRegistry() widget_discovery = config_.widget_discovery(widget_registry, cached_descriptions=reg_cache) want_splash = \ settings.value("startup/show-splash-screen", True, type=bool) and \ not options.no_splash if want_splash: pm, rect = config.splash_screen() splash_screen = SplashScreen(pixmap=pm, textRect=rect) splash_screen.setFont(QFont("Helvetica", 12)) color = QColor("#FFD39F") def show_message(message): splash_screen.showMessage(message, color=color) widget_registry.category_added.connect(show_message) log.info("Running widget discovery process.") cache_filename = os.path.join(config.cache_dir(), "widget-registry.pck") if options.no_discovery: with open(cache_filename, "rb") as f: widget_registry = pickle.load(f) widget_registry = qt.QtWidgetRegistry(widget_registry) else: if want_splash: splash_screen.show() widget_discovery.run(config.widgets_entry_points()) if want_splash: splash_screen.hide() splash_screen.deleteLater() # Store cached descriptions cache.save_registry_cache(widget_discovery.cached_descriptions) with open(cache_filename, "wb") as f: pickle.dump(WidgetRegistry(widget_registry), f) set_global_registry(widget_registry) canvas_window.set_widget_registry(widget_registry) canvas_window.show() canvas_window.raise_() want_welcome = \ settings.value("startup/show-welcome-screen", True, type=bool) \ and not options.no_welcome # Process events to make sure the canvas_window layout has # a chance to activate (the welcome dialog is modal and will # block the event queue, plus we need a chance to receive open file # signals when running without a splash screen) app.processEvents() app.fileOpenRequest.connect(canvas_window.open_scheme_file) if want_welcome and not args and not open_requests: canvas_window.welcome_dialog() elif args: log.info("Loading a scheme from the command line argument %r", args[0]) canvas_window.load_scheme(args[0]) elif open_requests: log.info("Loading a scheme from an `QFileOpenEvent` for %r", open_requests[-1]) canvas_window.load_scheme(open_requests[-1].toLocalFile()) # local references prevent destruction update_check = check_for_updates() send_stat = send_usage_statistics() pull_notifs = pull_notifications() # Tee stdout and stderr into Output dock log_view = canvas_window.output_view() stdout = TextStream() stdout.stream.connect(log_view.write) if sys.stdout: stdout.stream.connect(sys.stdout.write) stdout.flushed.connect(sys.stdout.flush) stderr = TextStream() error_writer = log_view.formatted(color=Qt.red) stderr.stream.connect(error_writer.write) if sys.stderr: stderr.stream.connect(sys.stderr.write) stderr.flushed.connect(sys.stderr.flush) log.info("Entering main event loop.") excepthook = ExceptHook(stream=stderr) excepthook.handledException.connect(handle_exception) try: with closing(stdout),\ closing(stderr),\ patch('sys.excepthook', excepthook),\ patch('sys.stderr', stderr),\ patch('sys.stdout', stdout): status = app.exec_() except BaseException: log.error("Error in main event loop.", exc_info=True) status = 42 del canvas_window del update_check del send_stat del pull_notifs app.processEvents() app.flush() # Collect any cycles before deleting the QApplication instance gc.collect() del app return status
def __init__(self): super().__init__() self.results = None self.classifier_names = [] self.colors = [] self._points_hull: Dict[Tuple[int, int], PointsAndHull] = {} box = gui.vBox(self.controlArea, box="曲线") self.target_cb = gui.comboBox(box, self, "target_index", label="目标: ", orientation=Qt.Horizontal, callback=self._on_target_changed, contentsLength=8, searchable=True) gui.radioButtons(box, self, "curve_type", ("Lift Curve", "Cumulative Gains"), callback=self._on_curve_type_changed) self.classifiers_list_box = gui.listBox( self.controlArea, self, "selected_classifiers", "classifier_names", box="Models", selectionMode=QListView.MultiSelection, callback=self._on_classifiers_changed) self.classifiers_list_box.setMaximumHeight(100) gui.checkBox(self.controlArea, self, "display_convex_hull", "Show convex hull", box="Settings", callback=self._replot) gui.rubber(self.controlArea) self.plotview = GraphicsView() self.plotview.setFrameStyle(QFrame.StyledPanel) self.plot = PlotItem(enableMenu=False) self.plot.parameter_setter = ParameterSetter(self.plot) self.plot.curve_items = [] self.plot.hull_items = [] self.plot.default_line_item = None self.plot.display_convex_hull = self.display_convex_hull self.plot.setMouseEnabled(False, False) self.plot.hideButtons() tickfont = QFont(self.font()) tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11)) for pos, label in (("bottom", "P Rate"), ("left", "")): axis = self.plot.getAxis(pos) axis.setTickFont(tickfont) axis.setLabel(label) self._set_left_label() self.plot.showGrid(True, True, alpha=0.1) self.plotview.setCentralItem(self.plot) self.mainArea.layout().addWidget(self.plotview) VisualSettingsDialog(self, self.plot.parameter_setter.initial_settings)
def _load_code_editor_settings(self): """ Load settings on the code editor like, font style, margins, scroll, etc. Based on the example from http://eli.thegreenplace.net/2011/04/01/sample-using-qscintilla-with-pyqt/ """ item = self.form.font_size.currentText() size = int(item) # Set the default font font = QFont() font.setFamily('Courier') font.setFixedPitch(True) font.setPointSize(size) self._code_editor.setFont(font) self._code_editor.setMarginsFont(font) # Margin 0 is used for line numbers fontmetrics = QFontMetrics(font) self._code_editor.setMarginsFont(font) self._code_editor.setMarginWidth(0, fontmetrics.width("00000") + 6) self._code_editor.setMarginLineNumbers(0, True) self._code_editor.setMarginsBackgroundColor(QColor("#cccccc")) # Clickable margin 1 for showing markers self._code_editor.setMarginSensitivity(1, True) self._code_editor.marginClicked.connect(self.on_margin_clicked) self._code_editor.markerDefine(QsciScintilla.RightArrow, self.ARROW_MARKER_NUM) self._code_editor.setMarkerBackgroundColor(QColor("#ee1111"), self.ARROW_MARKER_NUM) # Detect changes to text self._code_editor.modificationChanged.connect( self.on_modification_changed) # Brace matching: enable for a brace immediately before or after the current position self._code_editor.setBraceMatching(QsciScintilla.SloppyBraceMatch) # Current line visible with special background color self._code_editor.setCaretLineVisible(True) self._code_editor.setCaretLineBackgroundColor(QColor("#ffe4e4")) # Set Python lexer # Set style for Python comments (style number 1) to a fixed-width Courier. lexer = self.lexer() lexer.setDefaultFont(font) self._code_editor.setLexer(lexer) self._code_editor.setIndentationWidth(4) # self._code_editor.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1, 'Courier') self._code_editor.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1) # Don't want to see the horizontal scrollbar at all # Use raw message to Scintilla here (all messages are documented here: http://www.scintilla.org/ScintillaDoc.html) self._code_editor.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0) self._lexer_obj = lexer self.qsci_api = QsciAPIs(self._lexer_obj) ## Add autocompletion strings self.qsci_api.add("aLongString") self.qsci_api.add("aLongerString") self.qsci_api.add("aDifferentString") self.qsci_api.add("sOmethingElse") ## Compile the api for use in the lexer self.qsci_api.prepare() self._code_editor.setAutoCompletionThreshold(1) self._code_editor.setAutoCompletionSource(QsciScintilla.AcsAll)
def test_visual_settings(self, timeout=DEFAULT_TIMEOUT): graph = self.widget.graph font = QFont() font.setItalic(True) font.setFamily("Helvetica") self.send_signal(self.widget.Inputs.data, self.data) self.wait_until_finished(timeout=timeout) key, value = ("Fonts", "Font family", "Font family"), "Helvetica" self.widget.set_visual_settings(key, value) key, value = ("Fonts", "Title", "Font size"), 20 self.widget.set_visual_settings(key, value) key, value = ("Fonts", "Title", "Italic"), True self.widget.set_visual_settings(key, value) font.setPointSize(20) self.assertFontEqual(graph.parameter_setter.title_item.item.font(), font) key, value = ("Fonts", "Axis title", "Font size"), 14 self.widget.set_visual_settings(key, value) key, value = ("Fonts", "Axis title", "Italic"), True self.widget.set_visual_settings(key, value) font.setPointSize(14) for ax in ["bottom", "left"]: axis = graph.parameter_setter.getAxis(ax) self.assertFontEqual(axis.label.font(), font) key, value = ('Fonts', 'Axis ticks', 'Font size'), 15 self.widget.set_visual_settings(key, value) key, value = ('Fonts', 'Axis ticks', 'Italic'), True self.widget.set_visual_settings(key, value) font.setPointSize(15) for ax in ["bottom", "left"]: axis = graph.parameter_setter.getAxis(ax) self.assertFontEqual(axis.style["tickFont"], font) key, value = ("Fonts", "Legend", "Font size"), 16 self.widget.set_visual_settings(key, value) key, value = ("Fonts", "Legend", "Italic"), True self.widget.set_visual_settings(key, value) font.setPointSize(16) legend_item = list(graph.parameter_setter.legend_items)[0] self.assertFontEqual(legend_item[1].item.font(), font) key, value = ("Annotations", "Title", "Title"), "Foo" self.widget.set_visual_settings(key, value) self.assertEqual(graph.parameter_setter.title_item.item.toPlainText(), "Foo") self.assertEqual(graph.parameter_setter.title_item.text, "Foo") key, value = ("Annotations", "x-axis title", "Title"), "Foo2" self.widget.set_visual_settings(key, value) axis = graph.parameter_setter.getAxis("bottom") self.assertEqual(axis.label.toPlainText().strip(), "Foo2") self.assertEqual(axis.labelText, "Foo2") key, value = ("Annotations", "y-axis title", "Title"), "Foo3" self.widget.set_visual_settings(key, value) axis = graph.parameter_setter.getAxis("left") self.assertEqual(axis.label.toPlainText().strip(), "Foo3") self.assertEqual(axis.labelText, "Foo3")
def __init__(self, data): icon = QApplication.style().standardIcon(QStyle.SP_MessageBoxWarning) F = self.DataField def _finished(*, key=(data.get(F.MODULE), data.get(F.WIDGET_MODULE)), filename=data.get(F.WIDGET_SCHEME)): self._cache.add(key) try: os.remove(filename) except Exception: pass super().__init__(None, Qt.Window, modal=True, sizeGripEnabled=True, windowIcon=icon, windowTitle='Unexpected Error', finished=_finished) self._data = data layout = QVBoxLayout(self) self.setLayout(layout) labels = QWidget(self) labels_layout = QHBoxLayout(self) labels.setLayout(labels_layout) labels_layout.addWidget(QLabel(pixmap=icon.pixmap(50, 50))) labels_layout.addWidget( QLabel('The program encountered an unexpected error. Please<br>' 'report it anonymously to the developers.<br><br>' 'The following data will be reported:')) labels_layout.addStretch(1) layout.addWidget(labels) font = QFont('Monospace', 10) font.setStyleHint(QFont.Monospace) font.setFixedPitch(True) textbrowser = QTextBrowser(self, font=font, openLinks=False, lineWrapMode=QTextBrowser.NoWrap, anchorClicked=QDesktopServices.openUrl) layout.addWidget(textbrowser) def _reload_text(): add_scheme = cb.isChecked() settings.setValue('error-reporting/add-scheme', add_scheme) lines = ['<table>'] for k, v in data.items(): if k.startswith('_'): continue _v, v = v, escape(str(v)) if k == F.WIDGET_SCHEME: if not add_scheme: continue v = '<a href="{}">{}</a>'.format( urljoin('file:', pathname2url(_v)), v) if k in (F.STACK_TRACE, F.LOCALS): v = v.replace('\n', '<br>').replace(' ', ' ') lines.append( '<tr><th align="left">{}:</th><td>{}</td></tr>'.format( k, v)) lines.append('</table>') textbrowser.setHtml(''.join(lines)) settings = QSettings() cb = QCheckBox('Include workflow (data will NOT be transmitted)', self, checked=settings.value('error-reporting/add-scheme', True, type=bool)) cb.stateChanged.connect(_reload_text) _reload_text() layout.addWidget(cb) buttons = QWidget(self) buttons_layout = QHBoxLayout(self) buttons.setLayout(buttons_layout) buttons_layout.addWidget( QPushButton('Send Report (Thanks!)', default=True, clicked=self.accept)) buttons_layout.addWidget( QPushButton("Don't Send", default=False, clicked=self.reject)) layout.addWidget(buttons)
def generate_panel(self, formset): """ Generate a panel for the module form with all the controls formset format example: [('_video', '_arenas', '_run'), {"Player":['_threshold', "_player", "=", "_results", "_query"], "Background image":[(' ', '_selectBackground', '_paintBackground'), '_image']}, "_progress"] tuple: will display the controls in the same horizontal line list: will display the controls in the same vertical line dict: will display the controls in a tab widget '||': will plit the controls in a horizontal line '=': will plit the controls in a vertical line @param formset: Form configuration @type formset: list """ control = None if '=' in formset or isinstance(formset, hsplitter): control = QSplitter(QtCore.Qt.Vertical) index = list(formset).index('=') first_panel = self.generate_panel(formset[0:index]) second_panel = self.generate_panel(formset[index + 1:]) control.addWidget(first_panel) control.addWidget(second_panel) self._splitters.append(control) return control elif '||' in formset or isinstance(formset, vsplitter): control = QSplitter(QtCore.Qt.Horizontal) index = list(formset).index('||') first_panel = self.generate_panel(formset[0:index]) second_panel = self.generate_panel(formset[index + 1:]) control.addWidget(first_panel) control.addWidget(second_panel) if isinstance(formset, vsplitter): sizes = [formset.left_width, formset.right_width] control.setSizes(sizes) self._splitters.append(control) return control control = QFrame(self) layout = None if type(formset) is tuple: layout = QHBoxLayout() for row in formset: if isinstance(row, (list, tuple, vsplitter, hsplitter)): panel = self.generate_panel(row) layout.addWidget(panel) elif row == " ": spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) layout.addItem(spacer) elif type(row) is dict: c = self.generate_tabs(row) layout.addWidget(c) self._tabs.append(c) else: param = self.controls.get(row, None) if param is None: label = QLabel() label.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) # layout.addWidget( label ) if row.startswith('info:'): label.setText(row[5:]) font = QFont() font.setPointSize(10) label.setFont(font) label.setAccessibleName('info') elif row.startswith('h1:'): label.setText(row[3:]) font = QFont() font.setPointSize(17) font.setBold(True) label.setFont(font) label.setAccessibleName('h1') elif row.startswith('h2:'): label.setText(row[3:]) font = QFont() font.setPointSize(16) font.setBold(True) label.setFont(font) label.setAccessibleName('h2') elif row.startswith('h3:'): label.setText(row[3:]) font = QFont() font.setPointSize(15) font.setBold(True) label.setFont(font) label.setAccessibleName('h3') elif row.startswith('h4:'): label.setText(row[3:]) font = QFont() font.setPointSize(14) font.setBold(True) label.setFont(font) label.setAccessibleName('h4') elif row.startswith('h5:'): label.setText(row[3:]) font = QFont() font.setPointSize(12) font.setBold(True) label.setFont(font) label.setAccessibleName('h5') else: label.setText(row) font = QFont() font.setPointSize(10) label.setFont(font) label.setAccessibleName('msg') label.setToolTip(label.text()) layout.addWidget(label) else: param.parent = self param.name = row layout.addWidget(param.form) elif type(formset) is list: layout = QVBoxLayout() for row in formset: if isinstance(row, (list, tuple, vsplitter, hsplitter)): panel = self.generate_panel(row) layout.addWidget(panel) elif row == " ": spacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) layout.addItem(spacer) elif type(row) is dict: c = self.generate_tabs(row) layout.addWidget(c) self._tabs.append(c) else: param = self.controls.get(row, None) if param is None: label = QLabel() label.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) label.resize(30, 30) # layout.addWidget( label ) if row.startswith('info:'): label.setText(row[5:]) font = QFont() font.setPointSize(10) label.setFont(font) label.setAccessibleName('info') elif row.startswith('h1:'): label.setText(row[3:]) font = QFont() font.setPointSize(17) font.setBold(True) label.setFont(font) label.setAccessibleName('h1') elif row.startswith('h2:'): label.setText(row[3:]) font = QFont() font.setPointSize(16) font.setBold(True) label.setFont(font) label.setAccessibleName('h2') elif row.startswith('h3:'): label.setText(row[3:]) font = QFont() font.setPointSize(15) font.setBold(True) label.setFont(font) label.setAccessibleName('h3') elif row.startswith('h4:'): label.setText(row[3:]) font = QFont() font.setPointSize(14) font.setBold(True) label.setFont(font) label.setAccessibleName('h4') elif row.startswith('h5:'): label.setText(row[3:]) font = QFont() font.setPointSize(12) font.setBold(True) label.setFont(font) label.setAccessibleName('h5') else: label.setText(row) font = QFont() font.setPointSize(10) label.setFont(font) label.setAccessibleName('msg') label.setToolTip(label.text()) layout.addWidget(label) else: param.parent = self param.name = row layout.addWidget(param.form) if _api.USED_API == _api.QT_API_PYQT5: layout.setContentsMargins(0, 0, 0, 0) elif _api.USED_API == _api.QT_API_PYQT4: layout.setMargin(0) control.setLayout(layout) return control
def main(argv=None): if argv is None: argv = sys.argv usage = "usage: %prog [options] [workflow_file]" parser = optparse.OptionParser(usage=usage) parser.add_option("--no-discovery", action="store_true", help="Don't run widget discovery " "(use full cache instead)") parser.add_option("--force-discovery", action="store_true", help="Force full widget discovery " "(invalidate cache)") parser.add_option("--clear-widget-settings", action="store_true", help="Remove stored widget setting") parser.add_option("--no-welcome", action="store_true", help="Don't show welcome dialog.") parser.add_option("--no-splash", action="store_true", help="Don't show splash screen.") parser.add_option("-l", "--log-level", help="Logging level (0, 1, 2, 3, 4)", type="int", default=1) parser.add_option("--style", help="QStyle to use", type="str", default=None) parser.add_option("--stylesheet", help="Application level CSS style sheet to use", type="str", default="orange.qss") parser.add_option("--qt", help="Additional arguments for QApplication", type="str", default=None) (options, args) = parser.parse_args(argv[1:]) levels = [ logging.CRITICAL, logging.ERROR, logging.WARN, logging.INFO, logging.DEBUG ] # Fix streams before configuring logging (otherwise it will store # and write to the old file descriptors) fix_win_pythonw_std_stream() # Try to fix fonts on OSX Mavericks fix_osx_10_9_private_font() # File handler should always be at least INFO level so we need # the application root level to be at least at INFO. root_level = min(levels[options.log_level], logging.INFO) rootlogger = logging.getLogger(canvas.__name__) rootlogger.setLevel(root_level) # Initialize SQL query and execution time logger (in SqlTable) sql_level = min(levels[options.log_level], logging.INFO) make_sql_logger(sql_level) # Standard output stream handler at the requested level stream_hander = logging.StreamHandler() stream_hander.setLevel(level=levels[options.log_level]) rootlogger.addHandler(stream_hander) log.info("Starting 'Orange Canvas' application.") qt_argv = argv[:1] if options.style is not None: qt_argv += ["-style", options.style] if options.qt is not None: qt_argv += shlex.split(options.qt) qt_argv += args log.debug("Starting CanvasApplicaiton with argv = %r.", qt_argv) app = CanvasApplication(qt_argv) # NOTE: config.init() must be called after the QApplication constructor config.init() clear_settings_flag = os.path.join(config.widget_settings_dir(), "DELETE_ON_START") if options.clear_widget_settings or \ os.path.isfile(clear_settings_flag): log.info("Clearing widget settings") shutil.rmtree(config.widget_settings_dir(), ignore_errors=True) file_handler = logging.FileHandler(filename=os.path.join( config.log_dir(), "canvas.log"), mode="w") file_handler.setLevel(root_level) rootlogger.addHandler(file_handler) # intercept any QFileOpenEvent requests until the main window is # fully initialized. # NOTE: The QApplication must have the executable ($0) and filename # arguments passed in argv otherwise the FileOpen events are # triggered for them (this is done by Cocoa, but QApplicaiton filters # them out if passed in argv) open_requests = [] def onrequest(url): log.info("Received an file open request %s", url) open_requests.append(url) app.fileOpenRequest.connect(onrequest) settings = QSettings() stylesheet = options.stylesheet stylesheet_string = None if stylesheet != "none": if os.path.isfile(stylesheet): with open(stylesheet, "r") as f: stylesheet_string = f.read() else: if not os.path.splitext(stylesheet)[1]: # no extension stylesheet = os.path.extsep.join([stylesheet, "qss"]) pkg_name = canvas.__name__ resource = "styles/" + stylesheet if pkg_resources.resource_exists(pkg_name, resource): stylesheet_string = \ pkg_resources.resource_string(pkg_name, resource).decode() base = pkg_resources.resource_filename(pkg_name, "styles") pattern = re.compile( r"^\s@([a-zA-Z0-9_]+?)\s*:\s*([a-zA-Z0-9_/]+?);\s*$", flags=re.MULTILINE) matches = pattern.findall(stylesheet_string) for prefix, search_path in matches: QDir.addSearchPath(prefix, os.path.join(base, search_path)) log.info("Adding search path %r for prefix, %r", search_path, prefix) stylesheet_string = pattern.sub("", stylesheet_string) else: log.info("%r style sheet not found.", stylesheet) # Add the default canvas_icons search path dirpath = os.path.abspath(os.path.dirname(canvas.__file__)) QDir.addSearchPath("canvas_icons", os.path.join(dirpath, "icons")) canvas_window = CanvasMainWindow() canvas_window.setWindowIcon(config.application_icon()) if stylesheet_string is not None: canvas_window.setStyleSheet(stylesheet_string) if not options.force_discovery: reg_cache = cache.registry_cache() else: reg_cache = None widget_discovery = qt.QtWidgetDiscovery(cached_descriptions=reg_cache) widget_registry = qt.QtWidgetRegistry() widget_discovery.found_category.connect(widget_registry.register_category) widget_discovery.found_widget.connect(widget_registry.register_widget) want_splash = \ settings.value("startup/show-splash-screen", True, type=bool) and \ not options.no_splash if want_splash: pm, rect = config.splash_screen() splash_screen = SplashScreen(pixmap=pm, textRect=rect) splash_screen.setFont(QFont("Helvetica", 12)) color = QColor("#FFD39F") def show_message(message): splash_screen.showMessage(message, color=color) widget_discovery.discovery_start.connect(splash_screen.show) widget_discovery.discovery_process.connect(show_message) widget_discovery.discovery_finished.connect(splash_screen.hide) log.info("Running widget discovery process.") cache_filename = os.path.join(cache_dir(), "widget-registry.pck") if options.no_discovery: with open(cache_filename, "rb") as f: widget_registry = pickle.load(f) widget_registry = qt.QtWidgetRegistry(widget_registry) else: widget_discovery.run(config.widgets_entry_points()) # Store cached descriptions cache.save_registry_cache(widget_discovery.cached_descriptions) with open(cache_filename, "wb") as f: pickle.dump(WidgetRegistry(widget_registry), f) set_global_registry(widget_registry) canvas_window.set_widget_registry(widget_registry) canvas_window.show() canvas_window.raise_() want_welcome = \ settings.value("startup/show-welcome-screen", True, type=bool) \ and not options.no_welcome # Process events to make sure the canvas_window layout has # a chance to activate (the welcome dialog is modal and will # block the event queue, plus we need a chance to receive open file # signals when running without a splash screen) app.processEvents() app.fileOpenRequest.connect(canvas_window.open_scheme_file) if want_welcome and not args and not open_requests: canvas_window.welcome_dialog() elif args: log.info("Loading a scheme from the command line argument %r", args[0]) canvas_window.load_scheme(args[0]) elif open_requests: log.info("Loading a scheme from an `QFileOpenEvent` for %r", open_requests[-1]) canvas_window.load_scheme(open_requests[-1].toLocalFile()) # If run for the first time, open a browser tab with a survey show_survey = settings.value("startup/show-survey", True, type=bool) if show_survey: question = QMessageBox( QMessageBox.Question, 'Orange Survey', 'We would like to know more about how our software is used.\n\n' 'Would you care to fill our short 1-minute survey?', QMessageBox.Yes | QMessageBox.No) question.setDefaultButton(QMessageBox.Yes) later = question.addButton('Ask again later', QMessageBox.NoRole) question.setEscapeButton(later) def handle_response(result): if result == QMessageBox.Yes: success = QDesktopServices.openUrl( QUrl("http://orange.biolab.si/survey/short.html")) settings.setValue("startup/show-survey", not success) else: settings.setValue("startup/show-survey", result != QMessageBox.No) question.finished.connect(handle_response) question.show() # Tee stdout and stderr into Output dock log_view = canvas_window.log_view() stdout = TextStream() stdout.stream.connect(log_view.write) if sys.stdout: stdout.stream.connect(sys.stdout.write) stdout.flushed.connect(sys.stdout.flush) stderr = TextStream() error_writer = log_view.formated(color=Qt.red) stderr.stream.connect(error_writer.write) if sys.stderr: stderr.stream.connect(sys.stderr.write) stderr.flushed.connect(sys.stderr.flush) log.info("Entering main event loop.") try: with patch('sys.excepthook', ExceptHook(stream=stderr, canvas=canvas_window, handledException=handle_exception)),\ patch('sys.stderr', stderr),\ patch('sys.stdout', stdout): status = app.exec_() except BaseException: log.error("Error in main event loop.", exc_info=True) canvas_window.deleteLater() app.processEvents() app.flush() del canvas_window # Collect any cycles before deleting the QApplication instance gc.collect() del app return status
class Font: TITLE = QFont('Sans', 14, QFont.Bold) SUBTITLE = QFont('Sans', 10, QFont.Normal)