def _draw_border(point_1, point_2, border_width, parent): pen = QPen(QColor(self.border_color)) pen.setCosmetic(True) pen.setWidth(border_width) line = QGraphicsLineItem(QLineF(point_1, point_2), parent) line.setPen(pen) return line
def mouseMoveEvent(self, event): if event.buttons() & Qt.LeftButton: downPos = event.buttonDownPos(Qt.LeftButton) if not self.__tmpLine and self.__dragStartItem and \ (downPos - event.pos()).manhattanLength() > \ QApplication.instance().startDragDistance(): # Start a line drag line = QGraphicsLineItem(self) start = self.__dragStartItem.boundingRect().center() start = self.mapFromItem(self.__dragStartItem, start) line.setLine(start.x(), start.y(), event.pos().x(), event.pos().y()) pen = QPen(Qt.black, 4) pen.setCapStyle(Qt.RoundCap) line.setPen(pen) line.show() self.__tmpLine = line if self.__tmpLine: # Update the temp line line = self.__tmpLine.line() line.setP2(event.pos()) self.__tmpLine.setLine(line) QGraphicsWidget.mouseMoveEvent(self, event)
def create_main_nomogram(self, attributes, attr_inds, name_items, points, max_width, point_text, name_offset): cls_index = self.target_class_index min_p = min(p.min() for p in points) max_p = max(p.max() for p in points) values = self.get_ruler_values(min_p, max_p, max_width) min_p, max_p = min(values), max(values) diff_ = np.nan_to_num(max_p - min_p) scale_x = max_width / diff_ if diff_ else max_width nomogram_header = NomogramItem() point_item = RulerItem(point_text, values, scale_x, name_offset, - scale_x * min_p) point_item.setPreferredSize(point_item.preferredWidth(), 35) nomogram_header.add_items([point_item]) self.nomogram_main = NomogramItem() cont_feature_item_class = ContinuousFeature2DItem if \ self.cont_feature_dim_index else ContinuousFeatureItem feature_items = [ DiscreteFeatureItem( name_item, attr.values, point, scale_x, name_offset, - scale_x * min_p) if attr.is_discrete else cont_feature_item_class( name_item, self.log_reg_cont_data_extremes[i][cls_index], self.get_ruler_values( point.min(), point.max(), scale_x * point.ptp(), False), scale_x, name_offset, - scale_x * min_p) for i, attr, name_item, point in zip(attr_inds, attributes, name_items, points)] self.nomogram_main.add_items(feature_items) self.feature_items = OrderedDict(sorted(zip(attr_inds, feature_items))) x = - scale_x * min_p y = self.nomogram_main.layout().preferredHeight() + 10 self.vertical_line = QGraphicsLineItem(x, -6, x, y) self.vertical_line.setPen(QPen(Qt.DotLine)) self.vertical_line.setParentItem(point_item) self.hidden_vertical_line = QGraphicsLineItem(x, -6, x, y) pen = QPen(Qt.DashLine) pen.setBrush(QColor(Qt.red)) self.hidden_vertical_line.setPen(pen) self.hidden_vertical_line.setParentItem(point_item) return point_item, nomogram_header
def __init__(self, parent=None): QGraphicsLineItem.__init__(self, parent) self.setAcceptHoverEvents(True) self.__shape = None self.__default_pen = QPen(QColor('#383838'), 4) self.__default_pen.setCapStyle(Qt.RoundCap) self.__hover_pen = QPen(QColor('#000000'), 4) self.__hover_pen.setCapStyle(Qt.RoundCap) self.setPen(self.__default_pen) self.__shadow = QGraphicsDropShadowEffect( blurRadius=10, color=QColor('#9CACB4'), offset=QPointF(0, 0) ) self.setGraphicsEffect(self.__shadow) self.prepareGeometryChange() self.__shadow.setEnabled(False)
def test_controlpointline(self): control = ControlPointLine() line = QGraphicsLineItem(10, 10, 200, 200) self.scene.addItem(line) self.scene.addItem(control) control.setLine(line.line()) control.setFocus() control.lineChanged.connect(line.setLine) control.setLine(QLineF(30, 30, 180, 180)) self.assertEqual(control.line(), line.line()) self.assertEqual(line.line(), QLineF(30, 30, 180, 180)) control.lineEdited.connect(line.setLine) self.view.show() self.app.exec_() self.assertEqual(control.line(), line.line())
def addLink(self, output, input): """ Add a link between `output` (:class:`OutputSignal`) and `input` (:class:`InputSignal`). """ if not compatible_channels(output, input): return if output not in self.source.output_channels(): raise ValueError("%r is not an output channel of %r" % \ (output, self.source)) if input not in self.sink.input_channels(): raise ValueError("%r is not an input channel of %r" % \ (input, self.sink)) if input.single: # Remove existing link if it exists. for s1, s2, _ in self.__links: if s2 == input: self.removeLink(s1, s2) line = QGraphicsLineItem(self) source_anchor = self.sourceNodeWidget.anchor(output) sink_anchor = self.sinkNodeWidget.anchor(input) source_pos = source_anchor.boundingRect().center() source_pos = self.mapFromItem(source_anchor, source_pos) sink_pos = sink_anchor.boundingRect().center() sink_pos = self.mapFromItem(sink_anchor, sink_pos) line.setLine(source_pos.x(), source_pos.y(), sink_pos.x(), sink_pos.y()) pen = QPen(Qt.black, 4) pen.setCapStyle(Qt.RoundCap) line.setPen(pen) self.__links.append(_Link(output, input, line))
def __init__(self): super().__init__(enableMenu=False) self._profile_items = None self._can_select = True self._graph_state = SELECT self.setMouseMode(self.PanMode) pen = mkPen(LinePlotStyle.SELECTION_LINE_COLOR, width=LinePlotStyle.SELECTION_LINE_WIDTH) self.selection_line = QGraphicsLineItem() self.selection_line.setPen(pen) self.selection_line.setZValue(1e9) self.addItem(self.selection_line, ignoreBounds=True)
def __init__(self, id, title='', title_above=False, title_location=AxisMiddle, line=None, arrows=0, plot=None, bounds=None): QGraphicsItem.__init__(self) self.setFlag(QGraphicsItem.ItemHasNoContents) self.setZValue(AxisZValue) self.id = id self.title = title self.title_location = title_location self.data_line = line self.plot = plot self.graph_line = None self.size = None self.scale = None self.tick_length = (10, 5, 0) self.arrows = arrows self.title_above = title_above self.line_item = QGraphicsLineItem(self) self.title_item = QGraphicsTextItem(self) self.end_arrow_item = None self.start_arrow_item = None self.show_title = False self.scale = None path = QPainterPath() path.setFillRule(Qt.WindingFill) path.moveTo(0, 3.09) path.lineTo(0, -3.09) path.lineTo(9.51, 0) path.closeSubpath() self.arrow_path = path self.label_items = [] self.label_bg_items = [] self.tick_items = [] self._ticks = [] self.zoom_transform = QTransform() self.labels = None self.values = None self._bounds = bounds self.auto_range = None self.auto_scale = True self.zoomable = False self.update_callback = None self.max_text_width = 50 self.text_margin = 5 self.always_horizontal_text = False
def __init__(self, parent=None, line=QLineF(), text="", **kwargs): super().__init__(parent, **kwargs) self._text = text self.setFlag(pg.GraphicsObject.ItemHasNoContents) self._spine = QGraphicsLineItem(line, self) angle = line.angle() self._arrow = pg.ArrowItem(parent=self, angle=0) self._arrow.setPos(self._spine.line().p2()) self._arrow.setRotation(angle) self._arrow.setStyle(headLen=10) self._label = TextItem(text=text, color=(10, 10, 10)) self._label.setParentItem(self) self._label.setPos(*self.get_xy()) if parent is not None: self.setParentItem(parent)
def shape(self): if self.__shape is None: return QGraphicsLineItem.shape(self) return self.__shape
def line(x1, y1, x2, y2): r = QGraphicsLineItem(x1, y1, x2, y2, None) self.canvas.addItem(r) r.setPen(QPen(Qt.white, 2)) r.setZValue(30)
def _offseted_line(ax, ay): r = QGraphicsLineItem(x + ax, y + ay, x + (ax or w), y + (ay or h)) self.canvas.addItem(r) r.setPen(pen)
class AnchorItem(pg.GraphicsObject): def __init__(self, parent=None, line=QLineF(), text="", **kwargs): super().__init__(parent, **kwargs) self._text = text self.setFlag(pg.GraphicsObject.ItemHasNoContents) self._spine = QGraphicsLineItem(line, self) angle = line.angle() self._arrow = pg.ArrowItem(parent=self, angle=0) self._arrow.setPos(self._spine.line().p2()) self._arrow.setRotation(angle) self._arrow.setStyle(headLen=10) self._label = TextItem(text=text, color=(10, 10, 10)) self._label.setParentItem(self) self._label.setPos(*self.get_xy()) if parent is not None: self.setParentItem(parent) def get_xy(self): point = self._spine.line().p2() return point.x(), point.y() def setText(self, text): if text != self._text: self._text = text self._label.setText(text) self._label.setVisible(bool(text)) def text(self): return self._text def setLine(self, *line): line = QLineF(*line) if line != self._spine.line(): self._spine.setLine(line) self.__updateLayout() def line(self): return self._spine.line() def setPen(self, pen): self._spine.setPen(pen) def setArrowVisible(self, visible): self._arrow.setVisible(visible) def paint(self, painter, option, widget): pass def boundingRect(self): return QRectF() def viewTransformChanged(self): self.__updateLayout() def __updateLayout(self): T = self.sceneTransform() if T is None: T = QTransform() # map the axis spine to scene coord. system. viewbox_line = T.map(self._spine.line()) angle = viewbox_line.angle() assert not np.isnan(angle) # note in Qt the y axis is inverted (90 degree angle 'points' down) left_quad = 270 < angle <= 360 or -0.0 <= angle < 90 # position the text label along the viewbox_line label_pos = self._spine.line().pointAt(0.90) if left_quad: # Anchor the text under the axis spine anchor = (0.5, -0.1) else: # Anchor the text over the axis spine anchor = (0.5, 1.1) self._label.setPos(label_pos) self._label.setAnchor(pg.Point(*anchor)) self._label.setRotation(-angle if left_quad else 180 - angle) self._arrow.setPos(self._spine.line().p2()) self._arrow.setRotation(180 - angle)
def line(x, down=1): QGraphicsLineItem(x, 12 * down, x, 20 * down, labels)
def __init__(self, *args, **kwargs): QGraphicsLineItem.__init__(self, *args) GraphEdge.__init__(self, **kwargs) self.setZValue(-30)
def hoverEnterEvent(self, event): self.prepareGeometryChange() self.__shadow.setEnabled(True) self.setPen(self.__hover_pen) self.setZValue(1.0) QGraphicsLineItem.hoverEnterEvent(self, event)
def hoverLeaveEvent(self, event): self.prepareGeometryChange() self.__shadow.setEnabled(False) self.setPen(self.__default_pen) self.setZValue(0.0) QGraphicsLineItem.hoverLeaveEvent(self, event)
class OWAxis(QGraphicsItem): Role = OWPalette.Axis def __init__(self, id, title='', title_above=False, title_location=AxisMiddle, line=None, arrows=0, plot=None, bounds=None): QGraphicsItem.__init__(self) self.setFlag(QGraphicsItem.ItemHasNoContents) self.setZValue(AxisZValue) self.id = id self.title = title self.title_location = title_location self.data_line = line self.plot = plot self.graph_line = None self.size = None self.scale = None self.tick_length = (10, 5, 0) self.arrows = arrows self.title_above = title_above self.line_item = QGraphicsLineItem(self) self.title_item = QGraphicsTextItem(self) self.end_arrow_item = None self.start_arrow_item = None self.show_title = False self.scale = None path = QPainterPath() path.setFillRule(Qt.WindingFill) path.moveTo(0, 3.09) path.lineTo(0, -3.09) path.lineTo(9.51, 0) path.closeSubpath() self.arrow_path = path self.label_items = [] self.label_bg_items = [] self.tick_items = [] self._ticks = [] self.zoom_transform = QTransform() self.labels = None self.values = None self._bounds = bounds self.auto_range = None self.auto_scale = True self.zoomable = False self.update_callback = None self.max_text_width = 50 self.text_margin = 5 self.always_horizontal_text = False @staticmethod def compute_scale(min, max): magnitude = int(3 * log10(abs(max - min)) + 1) if magnitude % 3 == 0: first_place = 1 elif magnitude % 3 == 1: first_place = 2 else: first_place = 5 magnitude = magnitude // 3 - 1 step = first_place * pow(10, magnitude) first_val = ceil(min / step) * step return first_val, step def update_ticks(self): self._ticks = [] major, medium, minor = self.tick_length if self.labels is not None and not self.auto_scale: values = self.values or range(len(self.labels)) for i, text in zip(values, self.labels): self._ticks.append((i, text, medium, 1)) else: if self.scale and not self.auto_scale: min, max, step = self.scale elif self.auto_range: min, max = self.auto_range if min is not None and max is not None: step = (max - min) / 10 else: return else: return if max == min: return val, step = self.compute_scale(min, max) while val <= max: self._ticks.append((val, "%.4g" % val, medium, step)) val += step def update_graph(self): if self.update_callback: self.update_callback() def update(self, zoom_only=False): self.update_ticks() line_color = self.plot.color(OWPalette.Axis) text_color = self.plot.color(OWPalette.Text) if not self.graph_line or not self.scene(): return self.line_item.setLine(self.graph_line) self.line_item.setPen(line_color) if self.title: self.title_item.setHtml('<b>' + self.title + '</b>') self.title_item.setDefaultTextColor(text_color) if self.title_location == AxisMiddle: title_p = 0.5 elif self.title_location == AxisEnd: title_p = 0.95 else: title_p = 0.05 title_pos = self.graph_line.pointAt(title_p) v = self.graph_line.normalVector().unitVector() dense_text = False if hasattr(self, 'title_margin'): offset = self.title_margin elif self._ticks: if self.should_be_expanded(): offset = 55 dense_text = True else: offset = 35 else: offset = 10 if self.title_above: title_pos += (v.p2() - v.p1()) * (offset + QFontMetrics(self.title_item.font()).height()) else: title_pos -= (v.p2() - v.p1()) * offset ## TODO: Move it according to self.label_pos self.title_item.setVisible(self.show_title) self.title_item.setRotation(-self.graph_line.angle()) c = self.title_item.mapToParent(self.title_item.boundingRect().center()) tl = self.title_item.mapToParent(self.title_item.boundingRect().topLeft()) self.title_item.setPos(title_pos - c + tl) ## Arrows if not zoom_only: if self.start_arrow_item: self.scene().removeItem(self.start_arrow_item) self.start_arrow_item = None if self.end_arrow_item: self.scene().removeItem(self.end_arrow_item) self.end_arrow_item = None if self.arrows & AxisStart: if not zoom_only or not self.start_arrow_item: self.start_arrow_item = QGraphicsPathItem(self.arrow_path, self) self.start_arrow_item.setPos(self.graph_line.p1()) self.start_arrow_item.setRotation(-self.graph_line.angle() + 180) self.start_arrow_item.setBrush(line_color) self.start_arrow_item.setPen(line_color) if self.arrows & AxisEnd: if not zoom_only or not self.end_arrow_item: self.end_arrow_item = QGraphicsPathItem(self.arrow_path, self) self.end_arrow_item.setPos(self.graph_line.p2()) self.end_arrow_item.setRotation(-self.graph_line.angle()) self.end_arrow_item.setBrush(line_color) self.end_arrow_item.setPen(line_color) ## Labels n = len(self._ticks) resize_plot_item_list(self.label_items, n, QGraphicsTextItem, self) resize_plot_item_list(self.label_bg_items, n, QGraphicsRectItem, self) resize_plot_item_list(self.tick_items, n, QGraphicsLineItem, self) test_rect = QRectF(self.graph_line.p1(), self.graph_line.p2()).normalized() test_rect.adjust(-1, -1, 1, 1) n_v = self.graph_line.normalVector().unitVector() if self.title_above: n_p = n_v.p2() - n_v.p1() else: n_p = n_v.p1() - n_v.p2() l_v = self.graph_line.unitVector() l_p = l_v.p2() - l_v.p1() for i in range(n): pos, text, size, step = self._ticks[i] hs = 0.5 * step tick_pos = self.map_to_graph(pos) if not test_rect.contains(tick_pos): self.tick_items[i].setVisible(False) self.label_items[i].setVisible(False) continue item = self.label_items[i] item.setVisible(True) if not zoom_only: if self.id in XAxes or getattr(self, 'is_horizontal', False): item.setHtml('<center>' + Qt.escape(text.strip()) + '</center>') else: item.setHtml(Qt.escape(text.strip())) item.setTextWidth(-1) text_angle = 0 if dense_text: w = min(item.boundingRect().width(), self.max_text_width) item.setTextWidth(w) if self.title_above: label_pos = tick_pos + n_p * (w + self.text_margin) + l_p * item.boundingRect().height() / 2 else: label_pos = tick_pos + n_p * self.text_margin + l_p * item.boundingRect().height() / 2 text_angle = -90 if self.title_above else 90 else: w = min(item.boundingRect().width(), QLineF(self.map_to_graph(pos - hs), self.map_to_graph(pos + hs)).length()) label_pos = tick_pos + n_p * self.text_margin + l_p * item.boundingRect().height() / 2 item.setTextWidth(w) if not self.always_horizontal_text: if self.title_above: item.setRotation(-self.graph_line.angle() - text_angle) else: item.setRotation(self.graph_line.angle() - text_angle) item.setPos(label_pos) item.setDefaultTextColor(text_color) self.label_bg_items[i].setRect(item.boundingRect()) self.label_bg_items[i].setPen(QPen(Qt.NoPen)) self.label_bg_items[i].setBrush(self.plot.color(OWPalette.Canvas)) item = self.tick_items[i] item.setVisible(True) tick_line = QLineF(v) tick_line.translate(-tick_line.p1()) tick_line.setLength(size) if self.title_above: tick_line.setAngle(tick_line.angle() + 180) item.setLine(tick_line) item.setPen(line_color) item.setPos(self.map_to_graph(pos)) @staticmethod def make_title(label, unit=None): lab = '<i>' + label + '</i>' if unit: lab = lab + ' [' + unit + ']' return lab def set_line(self, line): self.graph_line = line self.update() def set_title(self, title): self.title = title self.update() def set_show_title(self, b): self.show_title = b self.update() def set_labels(self, labels, values): self.labels = labels self.values = values self.graph_line = None self.auto_scale = False self.update_ticks() self.update_graph() def set_scale(self, min, max, step_size): self.scale = (min, max, step_size) self.graph_line = None self.auto_scale = False self.update_ticks() self.update_graph() def set_tick_length(self, minor, medium, major): self.tick_length = (minor, medium, major) self.update() def map_to_graph(self, x): min, max = self.plot.bounds_for_axis(self.id) if min == max: return QPointF() line_point = self.graph_line.pointAt((x - min) / (max - min)) end_point = line_point * self.zoom_transform return self.projection(end_point, self.graph_line) @staticmethod def projection(point, line): norm = line.normalVector() norm.translate(point - norm.p1()) p = QPointF() type = line.intersect(norm, p) return p def continuous_labels(self): min, max, step = self.scale magnitude = log10(abs(max - min)) def paint(self, painter, option, widget): pass def boundingRect(self): return QRectF() def ticks(self): if not self._ticks: self.update_ticks() return self._ticks def bounds(self): if self._bounds: return self._bounds if self.labels: return -0.2, len(self.labels) - 0.8 elif self.scale: min, max, _step = self.scale return min, max elif self.auto_range: return self.auto_range else: return 0, 1 def set_bounds(self, value): self._bounds = value def should_be_expanded(self): self.update_ticks() return self.id in YAxes or self.always_horizontal_text or sum( len(t[1]) for t in self._ticks) * 12 > self.plot.width()
def line(x0, y0, x1, y1, *args): return QGraphicsLineItem(x0 * scale_x, y0, x1 * scale_x, y1, *args)
def __init__(self, name, data_extremes, values, scale, name_offset, offset): super().__init__() data_start, data_stop = data_extremes[0], data_extremes[1] labels = [str(np.round(data_start + (data_stop - data_start) * i / (self.n_tck - 1), 1)) for i in range(self.n_tck)] # leading label font = name.document().defaultFont() name.setFont(font) name.setPos(name_offset, -10) name.setParentItem(self) # labels ascending = data_start < data_stop y_start, y_stop = (self.y_diff, 0) if ascending else (0, self.y_diff) for i in range(self.n_tck): text = QGraphicsSimpleTextItem(labels[i], self) w = text.boundingRect().width() y = y_start + (y_stop - y_start) / (self.n_tck - 1) * i text.setPos(-5 - w, y - 8) tick = QGraphicsLineItem(-2, y, 2, y, self) # prediction marker self.dot = Continuous2DMovableDotItem( self.DOT_RADIUS, scale, offset, values[0], values[-1], y_start, y_stop) self.dot.tooltip_labels = labels self.dot.tooltip_values = values self.dot.setParentItem(self) h_line = QGraphicsLineItem(values[0] * scale + offset, self.y_diff / 2, values[-1] * scale + offset, self.y_diff / 2, self) pen = QPen(Qt.DashLine) pen.setBrush(QColor(Qt.red)) h_line.setPen(pen) self.dot.horizontal_line = h_line # pylint: disable=unused-variable # line line = QGraphicsLineItem(values[0] * scale + offset, y_start, values[-1] * scale + offset, y_stop, self) # ticks for value in values: diff_ = np.nan_to_num(values[-1] - values[0]) k = (value - values[0]) / diff_ if diff_ else 0 y_tick = (y_stop - y_start) * k + y_start - self.tick_height / 2 x_tick = value * scale - self.tick_width / 2 + offset tick = QGraphicsRectItem( x_tick, y_tick, self.tick_width, self.tick_height, self) tick.setBrush(QColor(Qt.black)) # rect rect = QGraphicsRectItem( values[0] * scale + offset, -self.y_diff * 0.125, values[-1] * scale + offset, self.y_diff * 1.25, self) pen = QPen(Qt.DotLine) pen.setBrush(QColor(50, 150, 200, 255)) rect.setPen(pen) self.setPreferredSize(self.preferredWidth(), self.y_diff * 1.5)
class LinePlotViewBox(ViewBox): selection_changed = Signal(np.ndarray) def __init__(self): super().__init__(enableMenu=False) self._profile_items = None self._can_select = True self._graph_state = SELECT self.setMouseMode(self.PanMode) pen = mkPen(LinePlotStyle.SELECTION_LINE_COLOR, width=LinePlotStyle.SELECTION_LINE_WIDTH) self.selection_line = QGraphicsLineItem() self.selection_line.setPen(pen) self.selection_line.setZValue(1e9) self.addItem(self.selection_line, ignoreBounds=True) def update_selection_line(self, button_down_pos, current_pos): p1 = self.childGroup.mapFromParent(button_down_pos) p2 = self.childGroup.mapFromParent(current_pos) self.selection_line.setLine(QLineF(p1, p2)) self.selection_line.resetTransform() self.selection_line.show() def set_graph_state(self, state): self._graph_state = state def enable_selection(self, enable): self._can_select = enable def get_selected(self, p1, p2): if self._profile_items is None: return np.array(False) return line_intersects_profiles(np.array([p1.x(), p1.y()]), np.array([p2.x(), p2.y()]), self._profile_items) def add_profiles(self, y): if sp.issparse(y): y = y.todense() self._profile_items = np.array( [np.vstack((np.full((1, y.shape[0]), i + 1), y[:, i].flatten())).T for i in range(y.shape[1])]) def remove_profiles(self): self._profile_items = None def mouseDragEvent(self, event, axis=None): if self._graph_state == SELECT and axis is None and self._can_select: event.accept() if event.button() == Qt.LeftButton: self.update_selection_line(event.buttonDownPos(), event.pos()) if event.isFinish(): self.selection_line.hide() p1 = self.childGroup.mapFromParent( event.buttonDownPos(event.button())) p2 = self.childGroup.mapFromParent(event.pos()) self.selection_changed.emit(self.get_selected(p1, p2)) elif self._graph_state == ZOOMING or self._graph_state == PANNING: event.ignore() super().mouseDragEvent(event, axis=axis) else: event.ignore() def mouseClickEvent(self, event): if event.button() == Qt.RightButton: self.autoRange() self.enableAutoRange() else: event.accept() self.selection_changed.emit(np.array(False)) def reset(self): self._profile_items = None self._can_select = True self._graph_state = SELECT
class OWNomogram(OWWidget): name = "Nomogram" description = " Nomograms for Visualization of Naive Bayesian" \ " and Logistic Regression Classifiers." icon = "icons/Nomogram.svg" priority = 2000 class Inputs: classifier = Input("Classifier", Model) data = Input("Data", Table) MAX_N_ATTRS = 1000 POINT_SCALE = 0 ALIGN_LEFT = 0 ALIGN_ZERO = 1 ACCEPTABLE = (NaiveBayesModel, LogisticRegressionClassifier) settingsHandler = ClassValuesContextHandler() target_class_index = ContextSetting(0) normalize_probabilities = Setting(False) scale = Setting(1) display_index = Setting(1) n_attributes = Setting(10) sort_index = Setting(SortBy.ABSOLUTE) cont_feature_dim_index = Setting(0) graph_name = "scene" class Error(OWWidget.Error): invalid_classifier = Msg("Nomogram accepts only Naive Bayes and " "Logistic Regression classifiers.") def __init__(self): super().__init__() self.instances = None self.domain = None self.data = None self.classifier = None self.align = OWNomogram.ALIGN_ZERO self.log_odds_ratios = [] self.log_reg_coeffs = [] self.log_reg_coeffs_orig = [] self.log_reg_cont_data_extremes = [] self.p = None self.b0 = None self.points = [] self.feature_items = {} self.feature_marker_values = [] self.scale_marker_values = lambda x: x self.nomogram_main = None self.vertical_line = None self.hidden_vertical_line = None self.old_target_class_index = self.target_class_index self.repaint = False # GUI box = gui.vBox(self.controlArea, "Target class") self.class_combo = gui.comboBox( box, self, "target_class_index", callback=self._class_combo_changed, contentsLength=12) self.norm_check = gui.checkBox( box, self, "normalize_probabilities", "Normalize probabilities", hidden=True, callback=self.update_scene, tooltip="For multiclass data 1 vs. all probabilities do not" " sum to 1 and therefore could be normalized.") self.scale_radio = gui.radioButtons( self.controlArea, self, "scale", ["Point scale", "Log odds ratios"], box="Scale", callback=self.update_scene) box = gui.vBox(self.controlArea, "Display features") grid = QGridLayout() radio_group = gui.radioButtonsInBox( box, self, "display_index", [], orientation=grid, callback=self.update_scene) radio_all = gui.appendRadioButton( radio_group, "All", addToLayout=False) radio_best = gui.appendRadioButton( radio_group, "Best ranked:", addToLayout=False) spin_box = gui.hBox(None, margin=0) self.n_spin = gui.spin( spin_box, self, "n_attributes", 1, self.MAX_N_ATTRS, label=" ", controlWidth=60, callback=self._n_spin_changed) grid.addWidget(radio_all, 1, 1) grid.addWidget(radio_best, 2, 1) grid.addWidget(spin_box, 2, 2) self.sort_combo = gui.comboBox( box, self, "sort_index", label="Rank by:", items=SortBy.items(), orientation=Qt.Horizontal, callback=self.update_scene) self.cont_feature_dim_combo = gui.comboBox( box, self, "cont_feature_dim_index", label="Numeric features: ", items=["1D projection", "2D curve"], orientation=Qt.Horizontal, callback=self.update_scene) gui.rubber(self.controlArea) class _GraphicsView(QGraphicsView): def __init__(self, scene, parent, **kwargs): for k, v in dict(verticalScrollBarPolicy=Qt.ScrollBarAlwaysOff, horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff, viewportUpdateMode=QGraphicsView.BoundingRectViewportUpdate, renderHints=(QPainter.Antialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform), alignment=(Qt.AlignTop | Qt.AlignLeft), sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)).items(): kwargs.setdefault(k, v) super().__init__(scene, parent, **kwargs) class GraphicsView(_GraphicsView): def __init__(self, scene, parent): super().__init__(scene, parent, verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn, styleSheet='QGraphicsView {background: white}') self.viewport().setMinimumWidth(300) # XXX: This prevents some tests failing self._is_resizing = False w = self def resizeEvent(self, resizeEvent): # Recompute main scene on window width change if resizeEvent.size().width() != resizeEvent.oldSize().width(): self._is_resizing = True self.w.update_scene() self._is_resizing = False return super().resizeEvent(resizeEvent) def is_resizing(self): return self._is_resizing def sizeHint(self): return QSize(400, 200) class FixedSizeGraphicsView(_GraphicsView): def __init__(self, scene, parent): super().__init__(scene, parent, sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)) def sizeHint(self): return QSize(400, 85) scene = self.scene = QGraphicsScene(self) top_view = self.top_view = FixedSizeGraphicsView(scene, self) mid_view = self.view = GraphicsView(scene, self) bottom_view = self.bottom_view = FixedSizeGraphicsView(scene, self) for view in (top_view, mid_view, bottom_view): self.mainArea.layout().addWidget(view) def _class_combo_changed(self): with np.errstate(invalid='ignore'): coeffs = [np.nan_to_num(p[self.target_class_index] / p[self.old_target_class_index]) for p in self.points] points = [p[self.old_target_class_index] for p in self.points] self.feature_marker_values = [ self.get_points_from_coeffs(v, c, p) for (v, c, p) in zip(self.feature_marker_values, coeffs, points)] self.feature_marker_values = np.asarray(self.feature_marker_values) self.update_scene() self.old_target_class_index = self.target_class_index def _n_spin_changed(self): self.display_index = 1 self.update_scene() def update_controls(self): self.class_combo.clear() self.norm_check.setHidden(True) self.cont_feature_dim_combo.setEnabled(True) if self.domain is not None: self.class_combo.addItems(self.domain.class_vars[0].values) if len(self.domain.attributes) > self.MAX_N_ATTRS: self.display_index = 1 if len(self.domain.class_vars[0].values) > 2: self.norm_check.setHidden(False) if not self.domain.has_continuous_attributes(): self.cont_feature_dim_combo.setEnabled(False) self.cont_feature_dim_index = 0 model = self.sort_combo.model() item = model.item(SortBy.POSITIVE) item.setFlags(item.flags() | Qt.ItemIsEnabled) item = model.item(SortBy.NEGATIVE) item.setFlags(item.flags() | Qt.ItemIsEnabled) self.align = OWNomogram.ALIGN_ZERO if self.classifier and isinstance(self.classifier, LogisticRegressionClassifier): self.align = OWNomogram.ALIGN_LEFT @Inputs.data def set_data(self, data): self.instances = data self.feature_marker_values = [] self.set_feature_marker_values() self.update_scene() @Inputs.classifier def set_classifier(self, classifier): self.closeContext() self.classifier = classifier self.Error.clear() if self.classifier and not isinstance(self.classifier, self.ACCEPTABLE): self.Error.invalid_classifier() self.classifier = None self.domain = self.classifier.domain if self.classifier else None self.data = None self.calculate_log_odds_ratios() self.calculate_log_reg_coefficients() self.update_controls() self.target_class_index = 0 self.openContext(self.domain.class_var if self.domain is not None else None) self.points = self.log_odds_ratios or self.log_reg_coeffs self.feature_marker_values = [] self.old_target_class_index = self.target_class_index self.update_scene() def calculate_log_odds_ratios(self): self.log_odds_ratios = [] self.p = None if self.classifier is None or self.domain is None: return if not isinstance(self.classifier, NaiveBayesModel): return log_cont_prob = self.classifier.log_cont_prob class_prob = self.classifier.class_prob for i in range(len(self.domain.attributes)): ca = np.exp(log_cont_prob[i]) * class_prob[:, None] _or = (ca / (1 - ca)) / (class_prob / (1 - class_prob))[:, None] self.log_odds_ratios.append(np.log(_or)) self.p = class_prob def calculate_log_reg_coefficients(self): self.log_reg_coeffs = [] self.log_reg_cont_data_extremes = [] self.b0 = None if self.classifier is None or self.domain is None: return if not isinstance(self.classifier, LogisticRegressionClassifier): return self.domain = self.reconstruct_domain(self.classifier.original_domain, self.domain) self.data = self.classifier.original_data.transform(self.domain) attrs, ranges, start = self.domain.attributes, [], 0 for attr in attrs: stop = start + len(attr.values) if attr.is_discrete else start + 1 ranges.append(slice(start, stop)) start = stop self.b0 = self.classifier.intercept coeffs = self.classifier.coefficients if len(self.domain.class_var.values) == 2: self.b0 = np.hstack((self.b0 * (-1), self.b0)) coeffs = np.vstack((coeffs * (-1), coeffs)) self.log_reg_coeffs = [coeffs[:, ranges[i]] for i in range(len(attrs))] self.log_reg_coeffs_orig = self.log_reg_coeffs.copy() min_values = nanmin(self.data.X, axis=0) max_values = nanmax(self.data.X, axis=0) for i, min_t, max_t in zip(range(len(self.log_reg_coeffs)), min_values, max_values): if self.log_reg_coeffs[i].shape[1] == 1: coef = self.log_reg_coeffs[i] self.log_reg_coeffs[i] = np.hstack((coef * min_t, coef * max_t)) self.log_reg_cont_data_extremes.append( [sorted([min_t, max_t], reverse=(c < 0)) for c in coef]) else: self.log_reg_cont_data_extremes.append([None]) def update_scene(self): self.clear_scene() if self.domain is None or not len(self.points[0]): return n_attrs = self.n_attributes if self.display_index else int(1e10) attr_inds, attributes = zip(*self.get_ordered_attributes()[:n_attrs]) name_items = [QGraphicsTextItem(attr.name) for attr in attributes] point_text = QGraphicsTextItem("Points") probs_text = QGraphicsTextItem("Probabilities (%)") all_items = name_items + [point_text, probs_text] name_offset = -max(t.boundingRect().width() for t in all_items) - 10 w = self.view.viewport().rect().width() max_width = w + name_offset - 30 points = [self.points[i][self.target_class_index] for i in attr_inds] if self.align == OWNomogram.ALIGN_LEFT: points = [p - p.min() for p in points] max_ = np.nan_to_num(max(max(abs(p)) for p in points)) d = 100 / max_ if max_ else 1 minimums = [p[self.target_class_index].min() for p in self.points] if self.scale == OWNomogram.POINT_SCALE: points = [p * d for p in points] if self.align == OWNomogram.ALIGN_LEFT: self.scale_marker_values = lambda x: (x - minimums) * d else: self.scale_marker_values = lambda x: x * d else: if self.align == OWNomogram.ALIGN_LEFT: self.scale_marker_values = lambda x: x - minimums else: self.scale_marker_values = lambda x: x point_item, nomogram_head = self.create_main_nomogram( attributes, attr_inds, name_items, points, max_width, point_text, name_offset) probs_item, nomogram_foot = self.create_footer_nomogram( probs_text, d, minimums, max_width, name_offset) for item in self.feature_items.values(): item.dot.point_dot = point_item.dot item.dot.probs_dot = probs_item.dot item.dot.vertical_line = self.hidden_vertical_line self.nomogram = nomogram = NomogramItem() nomogram.add_items([nomogram_head, self.nomogram_main, nomogram_foot]) self.scene.addItem(nomogram) self.set_feature_marker_values() rect = QRectF(self.scene.itemsBoundingRect().x(), self.scene.itemsBoundingRect().y(), self.scene.itemsBoundingRect().width(), self.nomogram.preferredSize().height()).adjusted(10, 0, 20, 0) self.scene.setSceneRect(rect) # Clip top and bottom (60 and 150) parts from the main view self.view.setSceneRect(rect.x(), rect.y() + 80, rect.width() - 10, rect.height() - 160) self.view.viewport().setMaximumHeight(rect.height() - 160) # Clip main part from top/bottom views # below point values are imprecise (less/more than required) but this # is not a problem due to clipped scene content still being drawn self.top_view.setSceneRect(rect.x(), rect.y() + 3, rect.width() - 10, 20) self.bottom_view.setSceneRect(rect.x(), rect.height() - 110, rect.width() - 10, 30) def create_main_nomogram(self, attributes, attr_inds, name_items, points, max_width, point_text, name_offset): cls_index = self.target_class_index min_p = min(p.min() for p in points) max_p = max(p.max() for p in points) values = self.get_ruler_values(min_p, max_p, max_width) min_p, max_p = min(values), max(values) diff_ = np.nan_to_num(max_p - min_p) scale_x = max_width / diff_ if diff_ else max_width nomogram_header = NomogramItem() point_item = RulerItem(point_text, values, scale_x, name_offset, - scale_x * min_p) point_item.setPreferredSize(point_item.preferredWidth(), 35) nomogram_header.add_items([point_item]) self.nomogram_main = NomogramItem() cont_feature_item_class = ContinuousFeature2DItem if \ self.cont_feature_dim_index else ContinuousFeatureItem feature_items = [ DiscreteFeatureItem( name_item, attr.values, point, scale_x, name_offset, - scale_x * min_p) if attr.is_discrete else cont_feature_item_class( name_item, self.log_reg_cont_data_extremes[i][cls_index], self.get_ruler_values( point.min(), point.max(), scale_x * point.ptp(), False), scale_x, name_offset, - scale_x * min_p) for i, attr, name_item, point in zip(attr_inds, attributes, name_items, points)] self.nomogram_main.add_items(feature_items) self.feature_items = OrderedDict(sorted(zip(attr_inds, feature_items))) x = - scale_x * min_p y = self.nomogram_main.layout().preferredHeight() + 10 self.vertical_line = QGraphicsLineItem(x, -6, x, y) self.vertical_line.setPen(QPen(Qt.DotLine)) self.vertical_line.setParentItem(point_item) self.hidden_vertical_line = QGraphicsLineItem(x, -6, x, y) pen = QPen(Qt.DashLine) pen.setBrush(QColor(Qt.red)) self.hidden_vertical_line.setPen(pen) self.hidden_vertical_line.setParentItem(point_item) return point_item, nomogram_header def get_ordered_attributes(self): """Return (in_domain_index, attr) pairs, ordered by method in SortBy combo""" if self.domain is None or not self.domain.attributes: return [] attrs = self.domain.attributes sort_by = self.sort_index class_value = self.target_class_index if sort_by == SortBy.NO_SORTING: return list(enumerate(attrs)) elif sort_by == SortBy.NAME: def key(x): _, attr = x return attr.name.lower() elif sort_by == SortBy.ABSOLUTE: def key(x): i, attr = x if attr.is_discrete: ptp = self.points[i][class_value].ptp() else: coef = np.abs(self.log_reg_coeffs_orig[i][class_value]).mean() ptp = coef * np.ptp(self.log_reg_cont_data_extremes[i][class_value]) return -ptp elif sort_by == SortBy.POSITIVE: def key(x): i, attr = x max_value = (self.points[i][class_value].max() if attr.is_discrete else np.mean(self.log_reg_cont_data_extremes[i][class_value])) return -max_value elif sort_by == SortBy.NEGATIVE: def key(x): i, attr = x min_value = (self.points[i][class_value].min() if attr.is_discrete else np.mean(self.log_reg_cont_data_extremes[i][class_value])) return min_value return sorted(enumerate(attrs), key=key) def create_footer_nomogram(self, probs_text, d, minimums, max_width, name_offset): eps, d_ = 0.05, 1 k = - np.log(self.p / (1 - self.p)) if self.p is not None else - self.b0 min_sum = k[self.target_class_index] - np.log((1 - eps) / eps) max_sum = k[self.target_class_index] - np.log(eps / (1 - eps)) if self.align == OWNomogram.ALIGN_LEFT: max_sum = max_sum - sum(minimums) min_sum = min_sum - sum(minimums) for i in range(len(k)): k[i] = k[i] - sum([min(q) for q in [p[i] for p in self.points]]) if self.scale == OWNomogram.POINT_SCALE: min_sum *= d max_sum *= d d_ = d values = self.get_ruler_values(min_sum, max_sum, max_width) min_sum, max_sum = min(values), max(values) diff_ = np.nan_to_num(max_sum - min_sum) scale_x = max_width / diff_ if diff_ else max_width cls_var, cls_index = self.domain.class_var, self.target_class_index nomogram_footer = NomogramItem() def get_normalized_probabilities(val): if not self.normalize_probabilities: return 1 / (1 + np.exp(k[cls_index] - val / d_)) totals = self.__get_totals_for_class_values(minimums) p_sum = np.sum(1 / (1 + np.exp(k - totals / d_))) return 1 / (1 + np.exp(k[cls_index] - val / d_)) / p_sum def get_points(prob): if not self.normalize_probabilities: return (k[cls_index] - np.log(1 / prob - 1)) * d_ totals = self.__get_totals_for_class_values(minimums) p_sum = np.sum(1 / (1 + np.exp(k - totals / d_))) return (k[cls_index] - np.log(1 / (prob * p_sum) - 1)) * d_ probs_item = ProbabilitiesRulerItem( probs_text, values, scale_x, name_offset, - scale_x * min_sum, get_points=get_points, title="{}='{}'".format(cls_var.name, cls_var.values[cls_index]), get_probabilities=get_normalized_probabilities) nomogram_footer.add_items([probs_item]) return probs_item, nomogram_footer def __get_totals_for_class_values(self, minimums): cls_index = self.target_class_index marker_values = self.scale_marker_values(self.feature_marker_values) totals = np.full(len(self.domain.class_var.values), np.nan) totals[cls_index] = marker_values.sum() for i in range(len(self.domain.class_var.values)): if i == cls_index: continue coeffs = [np.nan_to_num(p[i] / p[cls_index]) for p in self.points] points = [p[cls_index] for p in self.points] total = sum([self.get_points_from_coeffs(v, c, p) for (v, c, p) in zip(self.feature_marker_values, coeffs, points)]) if self.align == OWNomogram.ALIGN_LEFT: points = [p - m for m, p in zip(minimums, points)] total -= sum([min(p) for p in [p[i] for p in self.points]]) d = 100 / max(max(abs(p)) for p in points) if self.scale == OWNomogram.POINT_SCALE: total *= d totals[i] = total assert not np.any(np.isnan(totals)) return totals def set_feature_marker_values(self): if not (len(self.points) and len(self.feature_items)): return if not len(self.feature_marker_values): self._init_feature_marker_values() marker_values = self.scale_marker_values(self.feature_marker_values) invisible_sum = 0 for i in range(len(marker_values)): try: item = self.feature_items[i] except KeyError: invisible_sum += marker_values[i] else: item.dot.move_to_val(marker_values[i]) item.dot.probs_dot.move_to_sum(invisible_sum) def _init_feature_marker_values(self): self.feature_marker_values = [] cls_index = self.target_class_index instances = Table(self.domain, self.instances) \ if self.instances else None values = [] for i, attr in enumerate(self.domain.attributes): value, feature_val = 0, None if len(self.log_reg_coeffs): if attr.is_discrete: ind, n = unique(self.data.X[:, i], return_counts=True) feature_val = np.nan_to_num(ind[np.argmax(n)]) else: feature_val = nanmean(self.data.X[:, i]) # If data is provided on a separate signal, use the first data # instance to position the points instead of the mean inst_in_dom = instances and attr in instances.domain if inst_in_dom and not np.isnan(instances[0][attr]): feature_val = instances[0][attr] if feature_val is not None: value = (self.points[i][cls_index][int(feature_val)] if attr.is_discrete else self.log_reg_coeffs_orig[i][cls_index][0] * feature_val) values.append(value) self.feature_marker_values = np.asarray(values) def clear_scene(self): self.feature_items = {} self.scale_marker_values = lambda x: x self.nomogram = None self.nomogram_main = None self.vertical_line = None self.hidden_vertical_line = None self.scene.clear() def send_report(self): self.report_plot() @staticmethod def reconstruct_domain(original, preprocessed): # abuse dict to make "in" comparisons faster attrs = OrderedDict() for attr in preprocessed.attributes: cv = attr._compute_value.variable._compute_value var = cv.variable if cv else original[attr.name] if var in attrs: # the reason for OrderedDict continue attrs[var] = None # we only need keys attrs = list(attrs.keys()) return Domain(attrs, original.class_var, original.metas) @staticmethod def get_ruler_values(start, stop, max_width, round_to_nearest=True): if max_width == 0: return [0] diff = np.nan_to_num((stop - start) / max_width) if diff <= 0: return [0] decimals = int(np.floor(np.log10(diff))) if diff > 4 * pow(10, decimals): step = 5 * pow(10, decimals + 2) elif diff > 2 * pow(10, decimals): step = 2 * pow(10, decimals + 2) elif diff > 1 * pow(10, decimals): step = 1 * pow(10, decimals + 2) else: step = 5 * pow(10, decimals + 1) round_by = int(- np.floor(np.log10(step))) r = start % step if not round_to_nearest: _range = np.arange(start + step, stop + r, step) - r start, stop = np.floor(start * 100) / 100, np.ceil(stop * 100) / 100 return np.round(np.hstack((start, _range, stop)), 2) return np.round(np.arange(start, stop + r + step, step) - r, round_by) @staticmethod def get_points_from_coeffs(current_value, coefficients, possible_values): if np.isnan(possible_values).any(): return 0 indices = np.argsort(possible_values) sorted_values = possible_values[indices] sorted_coefficients = coefficients[indices] for i, val in enumerate(sorted_values): if current_value < val: break diff = sorted_values[i] - sorted_values[i - 1] k = 0 if diff < 1e-6 else (sorted_values[i] - current_value) / \ (sorted_values[i] - sorted_values[i - 1]) return sorted_coefficients[i - 1] * sorted_values[i - 1] * k + \ sorted_coefficients[i] * sorted_values[i] * (1 - k)
class AnchorItem(pg.GraphicsObject): def __init__(self, parent=None, line=QLineF(), text="", **kwargs): super().__init__(parent, **kwargs) self._text = text self.setFlag(pg.GraphicsObject.ItemHasNoContents) self._spine = QGraphicsLineItem(line, self) angle = line.angle() self._arrow = pg.ArrowItem(parent=self, angle=0) self._arrow.setPos(self._spine.line().p2()) self._arrow.setRotation(angle) self._arrow.setStyle(headLen=10) self._label = TextItem(text=text, color=(10, 10, 10)) self._label.setParentItem(self) self._label.setPos(*self.get_xy()) if parent is not None: self.setParentItem(parent) def get_xy(self): point = self._spine.line().p2() return point.x(), point.y() def setFont(self, font): self._label.setFont(font) def setText(self, text): if text != self._text: self._text = text self._label.setText(text) self._label.setVisible(bool(text)) def text(self): return self._text def setLine(self, *line): line = QLineF(*line) if line != self._spine.line(): self._spine.setLine(line) self.__updateLayout() def line(self): return self._spine.line() def setPen(self, pen): self._spine.setPen(pen) def setArrowVisible(self, visible): self._arrow.setVisible(visible) def paint(self, painter, option, widget): pass def boundingRect(self): return QRectF() def viewTransformChanged(self): self.__updateLayout() def __updateLayout(self): T = self.sceneTransform() if T is None: T = QTransform() # map the axis spine to scene coord. system. viewbox_line = T.map(self._spine.line()) angle = viewbox_line.angle() assert not np.isnan(angle) # note in Qt the y axis is inverted (90 degree angle 'points' down) left_quad = 270 < angle <= 360 or -0.0 <= angle < 90 # position the text label along the viewbox_line label_pos = self._spine.line().pointAt(0.90) if left_quad: # Anchor the text under the axis spine anchor = (0.5, -0.1) else: # Anchor the text over the axis spine anchor = (0.5, 1.1) self._label.setPos(label_pos) self._label.setAnchor(pg.Point(*anchor)) self._label.setRotation(-angle if left_quad else 180 - angle) self._arrow.setPos(self._spine.line().p2()) self._arrow.setRotation(180 - angle)
def __init__(self, value, height): QGraphicsLineItem.__init__(self) self.value = value self.height = height
class LinePlotViewBox(ViewBox): selection_changed = Signal(np.ndarray) def __init__(self): super().__init__(enableMenu=False) self._profile_items = None self._can_select = True self._graph_state = SELECT self.setMouseMode(self.PanMode) pen = mkPen(LinePlotStyle.SELECTION_LINE_COLOR, width=LinePlotStyle.SELECTION_LINE_WIDTH) self.selection_line = QGraphicsLineItem() self.selection_line.setPen(pen) self.selection_line.setZValue(1e9) self.addItem(self.selection_line, ignoreBounds=True) def update_selection_line(self, button_down_pos, current_pos): p1 = self.childGroup.mapFromParent(button_down_pos) p2 = self.childGroup.mapFromParent(current_pos) self.selection_line.setLine(QLineF(p1, p2)) self.selection_line.resetTransform() self.selection_line.show() def set_graph_state(self, state): self._graph_state = state def enable_selection(self, enable): self._can_select = enable def get_selected(self, p1, p2): if self._profile_items is None: return np.array(False) return line_intersects_profiles(np.array([p1.x(), p1.y()]), np.array([p2.x(), p2.y()]), self._profile_items) def add_profiles(self, y): if sp.issparse(y): y = y.todense() self._profile_items = np.array([ np.vstack((np.full((1, y.shape[0]), i + 1), y[:, i].flatten())).T for i in range(y.shape[1]) ]) def remove_profiles(self): self._profile_items = None def mouseDragEvent(self, ev, axis=None): if self._graph_state == SELECT and axis is None and self._can_select: ev.accept() if ev.button() == Qt.LeftButton: self.update_selection_line(ev.buttonDownPos(), ev.pos()) if ev.isFinish(): self.selection_line.hide() p1 = self.childGroup.mapFromParent( ev.buttonDownPos(ev.button())) p2 = self.childGroup.mapFromParent(ev.pos()) self.selection_changed.emit(self.get_selected(p1, p2)) elif self._graph_state == ZOOMING or self._graph_state == PANNING: ev.ignore() super().mouseDragEvent(ev, axis=axis) else: ev.ignore() def mouseClickEvent(self, ev): if ev.button() == Qt.RightButton: self.autoRange() self.enableAutoRange() else: ev.accept() self.selection_changed.emit(np.array(False)) def reset(self): self._profile_items = None self._can_select = True self._graph_state = SELECT