def __init__(self, parent=None): super().__init__(parent) self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.setContentsMargins(10, 10, 10, 10) self.__layout = QGraphicsGridLayout() self.__layout.setContentsMargins(0, 0, 0, 0) self.__layout.setSpacing(10) self.setLayout(self.__layout)
def __init__(self): super().__init__() self.__violin_column_width = self.VIOLIN_COLUMN_WIDTH # type: int self.__range = None # type: Optional[Tuple[float, float]] self.__violin_items = [] # type: List[ViolinItem] self.__bottom_axis = pg.AxisItem(parent=self, orientation="bottom", maxTickLength=7, pen=QPen(Qt.black)) self.__bottom_axis.setLabel("Impact on model output") self.__vertical_line = QGraphicsLineItem(self.__bottom_axis) self.__vertical_line.setPen(QPen(Qt.gray)) self.__legend = Legend(self) self.__layout = QGraphicsGridLayout() self.__layout.addItem(self.__legend, 0, ViolinPlot.LEGEND_COLUMN) self.__layout.setVerticalSpacing(0) self.setLayout(self.__layout)
def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self.setAcceptHoverEvents(True) self.__groups = [] self.__rowNamesVisible = True self.__barHeight = 3 self.__selectionRect = None self.__selection = np.asarray([], dtype=int) self.__selstate = None self.__pen = QPen(Qt.NoPen) self.__layout = QGraphicsGridLayout() self.__hoveredItem = None self.__topScale = None # type: Optional[pg.AxisItem] self.__bottomScale = None # type: Optional[pg.AxisItem] self.__layout.setColumnSpacing(0, 1.) self.setLayout(self.__layout) self.setFocusPolicy(Qt.StrongFocus)
def __init__(self): super().__init__() self._item_column_width = self.ITEM_COLUMN_WIDTH self._range: Optional[Tuple[float, float]] = None self._items: List[FeatureItem] = [] self._variable_items: List[VariableItem] = [] self._bottom_axis = AxisItem(parent=self, orientation="bottom", maxTickLength=7, pen=QPen(Qt.black)) self._bottom_axis.setLabel(self.BOTTOM_AXIS_LABEL) self._vertical_line = QGraphicsLineItem(self._bottom_axis) self._vertical_line.setPen(QPen(Qt.gray)) self._layout = QGraphicsGridLayout() self._layout.setVerticalSpacing(0) self.setLayout(self._layout) self.parameter_setter = BaseParameterSetter(self)
def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self.__layoutMode = GraphicsThumbnailGrid.AutoReflow self.__columnCount = -1 self.__thumbnails = [] # type: List[GraphicsThumbnailWidget] #: The current 'focused' thumbnail item. This is the item that last #: received the keyboard focus (though it does not necessarily have #: it now) self.__current = None # type: Optional[GraphicsThumbnailWidget] self.__reflowPending = False self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.setContentsMargins(10, 10, 10, 10) # NOTE: Keeping a reference to the layout. self.layout() # returns a QGraphicsLayout wrapper (i.e. strips the # QGraphicsGridLayout-nes of the object). self.__layout = QGraphicsGridLayout() self.__layout.setContentsMargins(0, 0, 0, 0) self.__layout.setSpacing(10) self.setLayout(self.__layout)
def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self.setAcceptHoverEvents(True) self.__groups = [] self.__rowNamesVisible = True self.__barHeight = 3 self.__selectionRect = None self.__selection = numpy.asarray([], dtype=int) self.__selstate = None self.__pen = QPen(Qt.NoPen) self.__brush = QBrush(QColor("#3FCFCF")) self.__layout = QGraphicsGridLayout() self.__hoveredItem = None self.setLayout(self.__layout) self.layout().setColumnSpacing(0, 1.)
def __init__(self, parent=None, direction=Qt.LeftToRight, node=None, icon=None, iconSize=None, **args): super().__init__(parent, **args) self.setAcceptedMouseButtons(Qt.NoButton) self.__direction = direction self.setLayout(QGraphicsLinearLayout(Qt.Horizontal)) # Set the maximum size, otherwise the layout can't grow beyond its # sizeHint (and we need it to grow so the widget can grow and keep the # contents centered vertically. self.layout().setMaximumSize(QSizeF(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.__iconSize = iconSize or QSize(64, 64) self.__icon = icon self.__iconItem = QGraphicsPixmapItem(self) self.__iconLayoutItem = GraphicsItemLayoutItem(item=self.__iconItem) self.__channelLayout = QGraphicsGridLayout() self.channelAnchors = [] if self.__direction == Qt.LeftToRight: self.layout().addItem(self.__iconLayoutItem) self.layout().addItem(self.__channelLayout) channel_alignemnt = Qt.AlignRight else: self.layout().addItem(self.__channelLayout) self.layout().addItem(self.__iconLayoutItem) channel_alignemnt = Qt.AlignLeft self.layout().setAlignment(self.__iconLayoutItem, Qt.AlignCenter) self.layout().setAlignment(self.__channelLayout, Qt.AlignVCenter | channel_alignemnt) self.node: Optional[SchemeNode] = None self.channels: Union[List[InputSignal], List[OutputSignal]] = [] if node is not None: self.setSchemeNode(node)
class SilhouettePlot(QGraphicsWidget): """ A silhouette plot widget. """ #: Emitted when the current selection has changed selectionChanged = Signal() def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self.setAcceptHoverEvents(True) self.__groups = [] self.__rowNamesVisible = True self.__barHeight = 3 self.__selectionRect = None self.__selection = np.asarray([], dtype=int) self.__selstate = None self.__pen = QPen(Qt.NoPen) self.__layout = QGraphicsGridLayout() self.__hoveredItem = None self.__topScale = None # type: Optional[pg.AxisItem] self.__bottomScale = None # type: Optional[pg.AxisItem] self.__layout.setColumnSpacing(0, 1.) self.setLayout(self.__layout) self.setFocusPolicy(Qt.StrongFocus) def setScores(self, scores, labels, values, colors, rownames=None): """ Set the silhouette scores/labels to for display. Arguments --------- scores : (N,) ndarray The silhouette scores. labels : (N,) ndarray A ndarray (dtype=int) of label/clusters indices. values : list of str A list of label/cluster names. colors : (N, 3) ndarray A ndarray of RGB values. rownames : list of str, optional A list (len == N) of row names. """ scores = np.asarray(scores, dtype=float) labels = np.asarray(labels, dtype=int) if rownames is not None: rownames = np.asarray(rownames, dtype=object) if not scores.ndim == labels.ndim == 1: raise ValueError("scores and labels must be 1 dimensional") if scores.shape != labels.shape: raise ValueError("scores and labels must have the same shape") if rownames is not None and rownames.shape != scores.shape: raise ValueError("rownames must have the same size as scores") Ck = np.unique(labels) if not Ck[0] >= 0 and Ck[-1] < len(values): raise ValueError( "All indices in `labels` must be in `range(len(values))`") cluster_indices = [np.flatnonzero(labels == i) for i in range(len(values))] cluster_indices = [indices[np.argsort(scores[indices])[::-1]] for indices in cluster_indices] groups = [ namespace(scores=scores[indices], indices=indices, label=label, rownames=(rownames[indices] if rownames is not None else None), color=color) for indices, label, color in zip(cluster_indices, values, colors) ] self.clear() self.__groups = groups self.__setup() def setRowNames(self, names): if names is not None: names = np.asarray(names, dtype=object) layout = self.__layout assert layout is self.layout() font = self.font() font.setPixelSize(self.__barHeight) for i, grp in enumerate(self.__groups): grp.rownames = names[grp.indices] if names is not None else None item = layout.itemAt(i + 1, 3) assert isinstance(item, TextListWidget) if grp.rownames is not None: item.setItems(grp.rownames) item.setVisible(self.__rowNamesVisible) else: item.setItems([]) item.setVisible(False) layout.activate() def setRowNamesVisible(self, visible): if self.__rowNamesVisible != visible: self.__rowNamesVisible = visible for item in self.__textItems(): item.setVisible(visible) self.updateGeometry() def rowNamesVisible(self): return self.__rowNamesVisible def setBarHeight(self, height): """ Set silhouette bar height (row height). """ if height != self.__barHeight: self.__barHeight = height for item in self.__plotItems(): item.setPreferredBarSize(height) font = self.font() font.setPixelSize(height) for item in self.__textItems(): item.setFont(font) def barHeight(self): """ Return the silhouette bar (row) height. """ return self.__barHeight def clear(self): """ Clear the widget state """ scene = self.scene() for child in self.childItems(): child.setParentItem(None) scene.removeItem(child) self.__groups = [] self.__topScale = None self.__bottomScale = None def __setup(self): # Setup the subwidgets/groups/layout smax = max((np.nanmax(g.scores) for g in self.__groups if g.scores.size), default=1) smax = 1 if np.isnan(smax) else smax smin = min((np.nanmin(g.scores) for g in self.__groups if g.scores.size), default=-1) smin = -1 if np.isnan(smin) else smin smin = min(smin, 0) font = self.font() font.setPixelSize(self.__barHeight) axispen = QPen(Qt.black) ax = pg.AxisItem(parent=self, orientation="top", maxTickLength=7, pen=axispen) ax.setRange(smin, smax) self.__topScale = ax layout = self.__layout assert layout is self.layout() layout.addItem(ax, 0, 2) for i, group in enumerate(self.__groups): silhouettegroup = BarPlotItem(parent=self) silhouettegroup.setBrush(QBrush(QColor(*group.color))) silhouettegroup.setPen(self.__pen) silhouettegroup.setDataRange(smin, smax) silhouettegroup.setPlotData(group.scores) silhouettegroup.setPreferredBarSize(self.__barHeight) silhouettegroup.setData(0, group.indices) layout.addItem(silhouettegroup, i + 1, 2) if group.label: layout.addItem(Line(orientation=Qt.Vertical), i + 1, 1) label = QGraphicsSimpleTextItem( "{} ({})".format(group.label, len(group.scores)), self ) label.setRotation(-90) item = SimpleLayoutItem( label, anchor=(0., 1.0), anchorItem=(0., 0.), ) item.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) layout.addItem(item, i + 1, 0, Qt.AlignCenter) textlist = _SilhouettePlotTextListWidget( self, font=font, elideMode=Qt.ElideRight, alignment=Qt.AlignLeft | Qt.AlignVCenter ) textlist.setMaximumWidth(750) textlist.setFlag(TextListWidget.ItemClipsChildrenToShape, False) sp = textlist.sizePolicy() sp.setVerticalPolicy(QSizePolicy.Ignored) textlist.setSizePolicy(sp) if group.rownames is not None: textlist.setItems(group.items) textlist.setVisible(self.__rowNamesVisible) else: textlist.setVisible(False) layout.addItem(textlist, i + 1, 3) ax = pg.AxisItem(parent=self, orientation="bottom", maxTickLength=7, pen=axispen) ax.setRange(smin, smax) self.__bottomScale = ax layout.addItem(ax, len(self.__groups) + 1, 2) def topScaleItem(self): # type: () -> Optional[QGraphicsWidget] return self.__topScale def bottomScaleItem(self): # type: () -> Optional[QGraphicsWidget] return self.__bottomScale def __updateSizeConstraints(self): # set/update fixed height constraint on the text annotation items so # it matches the silhouette's height for silitem, textitem in zip(self.__plotItems(), self.__textItems()): height = silitem.effectiveSizeHint(Qt.PreferredSize).height() textitem.setMaximumHeight(height) textitem.setMinimumHeight(height) mwidth = max((silitem.effectiveSizeHint(Qt.PreferredSize).width() for silitem in self.__plotItems()), default=300) # match the AxisItem's width to the bars for axis in self.__axisItems(): axis.setMaximumWidth(mwidth) axis.setMinimumWidth(mwidth) def event(self, event: QEvent) -> bool: # Reimplemented if event.type() == QEvent.LayoutRequest and \ self.parentLayoutItem() is None: self.__updateSizeConstraints() self.resize(self.effectiveSizeHint(Qt.PreferredSize)) elif event.type() == QEvent.GraphicsSceneHelp: self.helpEvent(cast(QGraphicsSceneHelpEvent, event)) if event.isAccepted(): return True return super().event(event) def helpEvent(self, event: QGraphicsSceneHelpEvent): pos = self.mapFromScene(event.scenePos()) item = self.__itemDataAtPos(pos) if item is None: return data, index, rect = item if data.rownames is None: return ttip = data.rownames[index] if ttip: view = event.widget().parentWidget() rect = view.mapFromScene(self.mapToScene(rect)).boundingRect() show_tool_tip(event.screenPos(), ttip, event.widget(), rect) event.setAccepted(True) def __setHoveredItem(self, item): # Set the current hovered `item` (:class:`QGraphicsRectItem`) if self.__hoveredItem is not item: if self.__hoveredItem is not None: self.__hoveredItem.setPen(QPen(Qt.NoPen)) self.__hoveredItem = item if item is not None: item.setPen(QPen(Qt.lightGray)) def hoverEnterEvent(self, event): # Reimplemented event.accept() def hoverMoveEvent(self, event): # Reimplemented event.accept() item = self.itemAtPos(event.pos()) self.__setHoveredItem(item) def hoverLeaveEvent(self, event): # Reimplemented self.__setHoveredItem(None) event.accept() def mousePressEvent(self, event): # Reimplemented if event.button() == Qt.LeftButton: if event.modifiers() & Qt.ControlModifier: saction = SelectAction.Toogle elif event.modifiers() & Qt.AltModifier: saction = SelectAction.Deselect elif event.modifiers() & Qt.ShiftModifier: saction = SelectAction.Select else: saction = SelectAction.Clear | SelectAction.Select self.__selstate = namespace( modifiers=event.modifiers(), selection=self.__selection, action=saction, rect=None, ) if saction & SelectAction.Clear: self.__selstate.selection = np.array([], dtype=int) self.setSelection(self.__selstate.selection) event.accept() def keyPressEvent(self, event): if event.key() in (Qt.Key_Up, Qt.Key_Down): if event.key() == Qt.Key_Up: self.__move_selection(self.selection(), -1) elif event.key() == Qt.Key_Down: self.__move_selection(self.selection(), 1) event.accept() return super().keyPressEvent(event) def mouseMoveEvent(self, event): # Reimplemented if event.buttons() & Qt.LeftButton: assert self.__selstate is not None if self.__selectionRect is None: self.__selectionRect = QGraphicsRectItem(self) rect = (QRectF(event.buttonDownPos(Qt.LeftButton), event.pos()).normalized()) if not rect.width(): rect = rect.adjusted(-1e-7, -1e-7, 1e-7, 1e-7) rect = rect.intersected(self.contentsRect()) self.__selectionRect.setRect(rect) self.__selstate.rect = rect self.__selstate.action |= SelectAction.Current self.__setSelectionRect(rect, self.__selstate.action) event.accept() def mouseReleaseEvent(self, event): # Reimplemented if event.button() == Qt.LeftButton: if self.__selectionRect is not None: self.__selectionRect.setParentItem(None) if self.scene() is not None: self.scene().removeItem(self.__selectionRect) self.__selectionRect = None event.accept() rect = (QRectF(event.buttonDownPos(Qt.LeftButton), event.pos()) .normalized()) if not rect.isValid(): rect = rect.adjusted(-1e-7, -1e-7, 1e-7, 1e-7) rect = rect.intersected(self.contentsRect()) action = self.__selstate.action & ~SelectAction.Current self.__setSelectionRect(rect, action) self.__selstate = None def __move_selection(self, selection, offset): ids = np.asarray([pi.data(0) for pi in self.__plotItems()]).ravel() indices = [np.where(ids == i)[0] for i in selection] indices = np.asarray(indices) + offset if min(indices) >= 0 and max(indices) < len(ids): self.setSelection(ids[indices]) def __setSelectionRect(self, rect, action): # Set the current mouse drag selection rectangle if not rect.isValid(): rect = rect.adjusted(-0.01, -0.01, 0.01, 0.01) rect = rect.intersected(self.contentsRect()) indices = self.__selectionIndices(rect) if action & SelectAction.Clear: selection = [] elif self.__selstate is not None: # Mouse drag selection is in progress. Update only the current # selection selection = self.__selstate.selection else: selection = self.__selection if action & SelectAction.Toogle: selection = np.setxor1d(selection, indices) elif action & SelectAction.Deselect: selection = np.setdiff1d(selection, indices) elif action & SelectAction.Select: selection = np.union1d(selection, indices) self.setSelection(selection) def __selectionIndices(self, rect): items = [item for item in self.__plotItems() if item.geometry().intersects(rect)] selection = [np.array([], dtype=int)] for item in items: indices = item.data(0) itemrect = item.geometry().intersected(rect) crect = item.contentsRect() itemrect = (item.mapFromParent(itemrect).boundingRect() .intersected(crect)) assert itemrect.top() >= 0 rowh = crect.height() / item.count() indextop = np.floor(itemrect.top() / rowh) indexbottom = np.ceil(itemrect.bottom() / rowh) selection.append(indices[int(indextop): int(indexbottom)]) return np.hstack(selection) def itemAtPos(self, pos): items = [item for item in self.__plotItems() if item.geometry().contains(pos)] if not items: return None else: item = items[0] crect = item.contentsRect() pos = item.mapFromParent(pos) if not crect.contains(pos): return None assert pos.x() >= 0 rowh = crect.height() / item.count() index = int(np.floor(pos.y() / rowh)) index = min(index, item.count() - 1) if index >= 0: return item.items()[index] else: return None def __itemDataAtPos(self, pos) -> Optional[Tuple[namespace, int, QRectF]]: items = [(sitem, tlist, grp) for sitem, tlist, grp in zip(self.__plotItems(), self.__textItems(), self.__groups) if sitem.geometry().contains(pos) or tlist.isVisible() and tlist.geometry().contains(pos)] if not items: return None else: sitem, _, grp = items[0] indices = grp.indices assert (isinstance(indices, np.ndarray) and indices.shape == (sitem.count(),)) crect = sitem.contentsRect() pos = sitem.mapFromParent(pos) if not crect.top() <= pos.y() <= crect.bottom(): return None rowh = crect.height() / sitem.count() index = int(np.floor(pos.y() / rowh)) index = min(index, indices.size - 1) baritem = sitem.items()[index] rect = self.mapRectFromItem(baritem, baritem.rect()) crect = self.contentsRect() rect.setLeft(crect.left()) rect.setRight(crect.right()) return grp, index, rect def __selectionChanged(self, selected, deselected): for item, grp in zip(self.__plotItems(), self.__groups): select = np.flatnonzero( np.in1d(grp.indices, selected, assume_unique=True)) items = item.items() if select.size: for i in select: color = np.hstack((grp.color, np.array([130]))) items[i].setBrush(QBrush(QColor(*color))) deselect = np.flatnonzero( np.in1d(grp.indices, deselected, assume_unique=True)) if deselect.size: for i in deselect: items[i].setBrush(QBrush(QColor(*grp.color))) def __plotItems(self): for i in range(len(self.__groups)): item = self.layout().itemAt(i + 1, 2) if item is not None: assert isinstance(item, BarPlotItem) yield item def __textItems(self): for i in range(len(self.__groups)): item = self.layout().itemAt(i + 1, 3) if item is not None: assert isinstance(item, TextListWidget) yield item def __axisItems(self): return self.__topScale, self.__bottomScale def setSelection(self, indices): indices = np.unique(np.asarray(indices, dtype=int)) select = np.setdiff1d(indices, self.__selection) deselect = np.setdiff1d(self.__selection, indices) self.__selectionChanged(select, deselect) self.__selection = indices if deselect.size or select.size: self.selectionChanged.emit() def selection(self): return np.asarray(self.__selection, dtype=int)
class FeaturesPlot(QGraphicsWidget): BOTTOM_AXIS_LABEL = "Feature Importance" LABEL_COLUMN, ITEM_COLUMN = range(2) ITEM_COLUMN_WIDTH, OFFSET = 300, 80 selection_cleared = Signal() selection_changed = Signal(object) resized = Signal() def __init__(self): super().__init__() self._item_column_width = self.ITEM_COLUMN_WIDTH self._range: Optional[Tuple[float, float]] = None self._items: List[FeatureItem] = [] self._variable_items: List[VariableItem] = [] self._bottom_axis = AxisItem(parent=self, orientation="bottom", maxTickLength=7, pen=QPen(Qt.black)) self._bottom_axis.setLabel(self.BOTTOM_AXIS_LABEL) self._vertical_line = QGraphicsLineItem(self._bottom_axis) self._vertical_line.setPen(QPen(Qt.gray)) self._layout = QGraphicsGridLayout() self._layout.setVerticalSpacing(0) self.setLayout(self._layout) self.parameter_setter = BaseParameterSetter(self) @property def item_column_width(self) -> int: return self._item_column_width @item_column_width.setter def item_column_width(self, view_width: int): j = FeaturesPlot.LABEL_COLUMN w = max([ self._layout.itemAt(i, j).item.boundingRect().width() for i in range(len(self._items)) ] + [0]) width = view_width - self.OFFSET - w self._item_column_width = max(self.ITEM_COLUMN_WIDTH, width) @property def x0_scaled(self) -> float: min_max = self._range[1] - self._range[0] return -self._range[0] * self.item_column_width / min_max @property def bottom_axis(self) -> AxisItem: return self._bottom_axis @property def labels(self) -> List[VariableItem]: return self._variable_items def set_data(self, x: np.ndarray, names: List[str], n_attrs: int, view_width: int, *plot_args): self.item_column_width = view_width self._set_range(x, *plot_args) self._set_items(x, names, *plot_args) self._set_labels(names) self._set_bottom_axis() self.set_n_visible(n_attrs) def _set_range(self, *_): raise NotImplementedError def _set_items(self, *_): raise NotImplementedError def set_n_visible(self, n: int): for i in range(len(self._items)): item = self._layout.itemAt(i, FeaturesPlot.ITEM_COLUMN) item.setVisible(i < n) text_item = self._layout.itemAt(i, FeaturesPlot.LABEL_COLUMN).item text_item.setVisible(i < n) self.set_vertical_line() def rescale(self, view_width: int): self.item_column_width = view_width for item in self._items: item.rescale(self.item_column_width) self._bottom_axis.setWidth(self.item_column_width) x = self.x0_scaled self._vertical_line.setLine(x, 0, x, self._vertical_line.line().y2()) self.updateGeometry() def set_height(self, height: float): for i in range(len(self._items)): item = self._layout.itemAt(i, FeaturesPlot.ITEM_COLUMN) item.set_height(height) self.set_vertical_line() self.updateGeometry() def _set_labels(self, labels: List[str]): for i, (label, _) in enumerate(zip(labels, self._items)): text = VariableItem(self, label) item = SimpleLayoutItem(text) item.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self._layout.addItem(item, i, FeaturesPlot.LABEL_COLUMN, Qt.AlignRight | Qt.AlignVCenter) self._variable_items.append(item) def _set_bottom_axis(self): self._bottom_axis.setRange(*self._range) self._layout.addItem(self._bottom_axis, len(self._items), FeaturesPlot.ITEM_COLUMN) def set_vertical_line(self): height = 0 for i in range(len(self._items)): item = self._layout.itemAt(i, FeaturesPlot.ITEM_COLUMN) text_item = self._layout.itemAt(i, FeaturesPlot.LABEL_COLUMN).item if item.isVisible(): height += max(text_item.boundingRect().height(), item.preferredSize().height()) self._vertical_line.setLine(self.x0_scaled, 0, self.x0_scaled, -height) def deselect(self): self.selection_cleared.emit() def select(self, *args): self.selection_changed.emit(*args) def select_from_settings(self, *_): raise NotImplementedError def apply_visual_settings(self, settings: Dict): for key, value in settings.items(): self.parameter_setter.set_parameter(key, value)
def __init__(self): super().__init__() self.matrix = None self._tree = None self._ordered_tree = None self._sorted_matrix = None self._sort_indices = None self._selection = None self.sorting_cb = gui.comboBox( self.controlArea, self, "sorting", box="Element Sorting", items=["None", "Clustering", "Clustering with ordered leaves"], callback=self._invalidate_ordering) box = gui.vBox(self.controlArea, "Colors") self.color_box = gui.palette_combo_box(self.palette_name) self.color_box.currentIndexChanged.connect(self._update_color) box.layout().addWidget(self.color_box) form = QFormLayout( formAlignment=Qt.AlignLeft, labelAlignment=Qt.AlignLeft, fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow ) # form.addRow( # "Gamma", # gui.hSlider(box, self, "color_gamma", minValue=0.0, maxValue=1.0, # step=0.05, ticks=True, intOnly=False, # createLabel=False, callback=self._update_color) # ) form.addRow( "Low:", gui.hSlider(box, self, "color_low", minValue=0.0, maxValue=1.0, step=0.05, ticks=True, intOnly=False, createLabel=False, callback=self._update_color) ) form.addRow( "High:", gui.hSlider(box, self, "color_high", minValue=0.0, maxValue=1.0, step=0.05, ticks=True, intOnly=False, createLabel=False, callback=self._update_color) ) box.layout().addLayout(form) self.annot_combo = gui.comboBox( self.controlArea, self, "annotation_idx", box="Annotations", callback=self._invalidate_annotations, contentsLength=12) self.annot_combo.setModel(itemmodels.VariableListModel()) self.annot_combo.model()[:] = ["None", "Enumeration"] self.controlArea.layout().addStretch() gui.auto_send(self.controlArea, self, "autocommit") self.view = pg.GraphicsView(background="w") self.mainArea.layout().addWidget(self.view) self.grid_widget = pg.GraphicsWidget() self.grid = QGraphicsGridLayout() self.grid_widget.setLayout(self.grid) self.viewbox = pg.ViewBox(enableMouse=False, enableMenu=False) self.viewbox.setAcceptedMouseButtons(Qt.NoButton) self.viewbox.setAcceptHoverEvents(False) self.grid.addItem(self.viewbox, 1, 1) self.left_dendrogram = DendrogramWidget( self.grid_widget, orientation=DendrogramWidget.Left, selectionMode=DendrogramWidget.NoSelection, hoverHighlightEnabled=False ) self.left_dendrogram.setAcceptedMouseButtons(Qt.NoButton) self.left_dendrogram.setAcceptHoverEvents(False) self.top_dendrogram = DendrogramWidget( self.grid_widget, orientation=DendrogramWidget.Top, selectionMode=DendrogramWidget.NoSelection, hoverHighlightEnabled=False ) self.top_dendrogram.setAcceptedMouseButtons(Qt.NoButton) self.top_dendrogram.setAcceptHoverEvents(False) self.grid.addItem(self.left_dendrogram, 1, 0) self.grid.addItem(self.top_dendrogram, 0, 1) self.right_labels = TextList( alignment=Qt.AlignLeft | Qt.AlignVCenter, sizePolicy=QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) ) self.bottom_labels = TextList( orientation=Qt.Horizontal, alignment=Qt.AlignRight | Qt.AlignVCenter, sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) ) self.grid.addItem(self.right_labels, 1, 2) self.grid.addItem(self.bottom_labels, 2, 1) self.view.setCentralItem(self.grid_widget) self.left_dendrogram.hide() self.top_dendrogram.hide() self.right_labels.hide() self.bottom_labels.hide() self.matrix_item = None self.dendrogram = None self.grid_widget.scene().installEventFilter(self) self.settingsAboutToBePacked.connect(self.pack_settings)
def __init__(self): super().__init__() self.matrix = None self._matrix_range = 0. self._tree = None self._ordered_tree = None self._sorted_matrix = None self._sort_indices = None self._selection = None self.sorting_cb = gui.comboBox( self.controlArea, self, "sorting", box="Element Sorting", items=["None", "Clustering", "Clustering with ordered leaves"], callback=self._invalidate_ordering) box = gui.vBox(self.controlArea, "Colors") self.color_map_widget = cmw = ColorGradientSelection( thresholds=(self.color_low, self.color_high), ) model = itemmodels.ContinuousPalettesModel(parent=self) cmw.setModel(model) idx = cmw.findData(self.palette_name, model.KeyRole) if idx != -1: cmw.setCurrentIndex(idx) cmw.activated.connect(self._update_color) def _set_thresholds(low, high): self.color_low, self.color_high = low, high self._update_color() cmw.thresholdsChanged.connect(_set_thresholds) box.layout().addWidget(self.color_map_widget) self.annot_combo = gui.comboBox(self.controlArea, self, "annotation_idx", box="Annotations", contentsLength=12, searchable=True, callback=self._invalidate_annotations) self.annot_combo.setModel(itemmodels.VariableListModel()) self.annot_combo.model()[:] = ["None", "Enumeration"] gui.rubber(self.controlArea) gui.auto_send(self.buttonsArea, self, "autocommit") self.view = GraphicsView(background=None) self.mainArea.layout().addWidget(self.view) self.grid_widget = pg.GraphicsWidget() self.grid = QGraphicsGridLayout() self.grid_widget.setLayout(self.grid) self.gradient_legend = GradientLegendWidget(0, 1, self._color_map()) self.gradient_legend.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) self.gradient_legend.setMaximumWidth(250) self.grid.addItem(self.gradient_legend, 0, 1) self.viewbox = pg.ViewBox(enableMouse=False, enableMenu=False) self.viewbox.setAcceptedMouseButtons(Qt.NoButton) self.viewbox.setAcceptHoverEvents(False) self.grid.addItem(self.viewbox, 2, 1) self.left_dendrogram = DendrogramWidget( self.grid_widget, orientation=DendrogramWidget.Left, selectionMode=DendrogramWidget.NoSelection, hoverHighlightEnabled=False) self.left_dendrogram.setAcceptedMouseButtons(Qt.NoButton) self.left_dendrogram.setAcceptHoverEvents(False) self.top_dendrogram = DendrogramWidget( self.grid_widget, orientation=DendrogramWidget.Top, selectionMode=DendrogramWidget.NoSelection, hoverHighlightEnabled=False) self.top_dendrogram.setAcceptedMouseButtons(Qt.NoButton) self.top_dendrogram.setAcceptHoverEvents(False) self.grid.addItem(self.left_dendrogram, 2, 0) self.grid.addItem(self.top_dendrogram, 1, 1) self.right_labels = TextList(alignment=Qt.AlignLeft | Qt.AlignVCenter, sizePolicy=QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Expanding)) self.bottom_labels = TextList( orientation=Qt.Horizontal, alignment=Qt.AlignRight | Qt.AlignVCenter, sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.grid.addItem(self.right_labels, 2, 2) self.grid.addItem(self.bottom_labels, 3, 1) self.view.setCentralItem(self.grid_widget) self.gradient_legend.hide() self.left_dendrogram.hide() self.top_dendrogram.hide() self.right_labels.hide() self.bottom_labels.hide() self.matrix_item = None self.dendrogram = None self.settingsAboutToBePacked.connect(self.pack_settings)
def __init__(self): super().__init__() self.matrix = None self._tree = None self._ordered_tree = None self._sorted_matrix = None self._sort_indices = None self._selection = None self.sorting_cb = gui.comboBox(self.controlArea, self, "sorting", box="排序", items=["无", "聚类", "使用有序叶子进行聚类"], callback=self._invalidate_ordering) box = gui.vBox(self.controlArea, "颜色") self.colormap_cb = gui.comboBox(box, self, "colormap", callback=self._update_color) self.colormap_cb.setIconSize(QSize(64, 16)) self.palettes = list(_color_palettes) init_color_combo(self.colormap_cb, self.palettes, QSize(64, 16)) self.colormap_cb.setCurrentIndex(self.colormap) form = QFormLayout(formAlignment=Qt.AlignLeft, labelAlignment=Qt.AlignLeft, fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow) # form.addRow( # "Gamma", # gui.hSlider(box, self, "color_gamma", minValue=0.0, maxValue=1.0, # step=0.05, ticks=True, intOnly=False, # createLabel=False, callback=self._update_color) # ) form.addRow( "低:", gui.hSlider(box, self, "color_low", minValue=0.0, maxValue=1.0, step=0.05, ticks=True, intOnly=False, createLabel=False, callback=self._update_color)) form.addRow( "高:", gui.hSlider(box, self, "color_high", minValue=0.0, maxValue=1.0, step=0.05, ticks=True, intOnly=False, createLabel=False, callback=self._update_color)) box.layout().addLayout(form) self.annot_combo = gui.comboBox(self.controlArea, self, "annotation_idx", box="注解", callback=self._invalidate_annotations, contentsLength=12) self.annot_combo.setModel(itemmodels.VariableListModel()) self.annot_combo.model()[:] = ["无", "列举"] self.controlArea.layout().addStretch() gui.auto_commit(self.controlArea, self, "autocommit", "选中发送") self.view = pg.GraphicsView(background="w") self.mainArea.layout().addWidget(self.view) self.grid_widget = pg.GraphicsWidget() self.grid = QGraphicsGridLayout() self.grid_widget.setLayout(self.grid) self.viewbox = pg.ViewBox(enableMouse=False, enableMenu=False) self.viewbox.setAcceptedMouseButtons(Qt.NoButton) self.viewbox.setAcceptHoverEvents(False) self.grid.addItem(self.viewbox, 1, 1) self.left_dendrogram = DendrogramWidget( self.grid_widget, orientation=DendrogramWidget.Left, selectionMode=DendrogramWidget.NoSelection, hoverHighlightEnabled=False) self.left_dendrogram.setAcceptedMouseButtons(Qt.NoButton) self.left_dendrogram.setAcceptHoverEvents(False) self.top_dendrogram = DendrogramWidget( self.grid_widget, orientation=DendrogramWidget.Top, selectionMode=DendrogramWidget.NoSelection, hoverHighlightEnabled=False) self.top_dendrogram.setAcceptedMouseButtons(Qt.NoButton) self.top_dendrogram.setAcceptHoverEvents(False) self.grid.addItem(self.left_dendrogram, 1, 0) self.grid.addItem(self.top_dendrogram, 0, 1) self.right_labels = TextList(alignment=Qt.AlignLeft) self.bottom_labels = TextList(orientation=Qt.Horizontal, alignment=Qt.AlignRight) self.grid.addItem(self.right_labels, 1, 2) self.grid.addItem(self.bottom_labels, 2, 1) self.view.setCentralItem(self.grid_widget) self.left_dendrogram.hide() self.top_dendrogram.hide() self.right_labels.hide() self.bottom_labels.hide() self.matrix_item = None self.dendrogram = None self.grid_widget.scene().installEventFilter(self)
class ViolinPlot(QGraphicsWidget): LABEL_COLUMN, VIOLIN_COLUMN, LEGEND_COLUMN = range(3) VIOLIN_COLUMN_WIDTH, OFFSET = 300, 80 MAX_N_ITEMS = 100 selection_cleared = Signal() selection_changed = Signal(float, float, str) resized = Signal() def __init__(self): super().__init__() self.__violin_column_width = self.VIOLIN_COLUMN_WIDTH # type: int self.__range = None # type: Optional[Tuple[float, float]] self.__violin_items = [] # type: List[ViolinItem] self.__variable_items = [] # type: List[VariableItem] self.__bottom_axis = AxisItem(parent=self, orientation="bottom", maxTickLength=7, pen=QPen(Qt.black)) self.__bottom_axis.setLabel("Impact on model output") self.__vertical_line = QGraphicsLineItem(self.__bottom_axis) self.__vertical_line.setPen(QPen(Qt.gray)) self.__legend = Legend(self) self.__layout = QGraphicsGridLayout() self.__layout.addItem(self.__legend, 0, ViolinPlot.LEGEND_COLUMN) self.__layout.setVerticalSpacing(0) self.setLayout(self.__layout) self.parameter_setter = ParameterSetter(self) @property def violin_column_width(self): return self.__violin_column_width @violin_column_width.setter def violin_column_width(self, view_width: int): j = ViolinPlot.LABEL_COLUMN w = max([self.__layout.itemAt(i, j).item.boundingRect().width() for i in range(len(self.__violin_items))] + [0]) width = view_width - self.legend.sizeHint().width() - self.OFFSET - w self.__violin_column_width = max(self.VIOLIN_COLUMN_WIDTH, width) @property def bottom_axis(self): return self.__bottom_axis @property def labels(self): return self.__variable_items @property def legend(self): return self.__legend def set_data(self, x: np.ndarray, colors: np.ndarray, names: List[str], n_attrs: float, view_width: int): self.violin_column_width = view_width abs_max = np.max(np.abs(x)) * 1.05 self.__range = (-abs_max, abs_max) self._set_violin_items(x, colors, names) self._set_labels(names) self._set_bottom_axis() self.set_n_visible(n_attrs) def set_n_visible(self, n: int): for i in range(len(self.__violin_items)): violin_item = self.__layout.itemAt(i, ViolinPlot.VIOLIN_COLUMN) violin_item.setVisible(i < n) text_item = self.__layout.itemAt(i, ViolinPlot.LABEL_COLUMN).item text_item.setVisible(i < n) self.set_vertical_line() def rescale(self, view_width: int): self.violin_column_width = view_width with temp_seed(0): for item in self.__violin_items: item.rescale(self.violin_column_width) self.__bottom_axis.setWidth(self.violin_column_width) x = self.violin_column_width / 2 self.__vertical_line.setLine(x, 0, x, self.__vertical_line.line().y2()) def show_legend(self, show: bool): self.__legend.setVisible(show) self.__bottom_axis.setWidth(self.violin_column_width) x = self.violin_column_width / 2 self.__vertical_line.setLine(x, 0, x, self.__vertical_line.line().y2()) def _set_violin_items(self, x: np.ndarray, colors: np.ndarray, labels: List[str]): with temp_seed(0): for i in range(x.shape[1]): item = ViolinItem(self, labels[i], self.__range, self.violin_column_width) item.set_data(x[:, i], colors[:, i]) item.selection_changed.connect(self.select) self.__violin_items.append(item) self.__layout.addItem(item, i, ViolinPlot.VIOLIN_COLUMN) if i == self.MAX_N_ITEMS: break def _set_labels(self, labels: List[str]): for i, (label, _) in enumerate(zip(labels, self.__violin_items)): text = VariableItem(self, label) item = SimpleLayoutItem(text) item.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.__layout.addItem(item, i, ViolinPlot.LABEL_COLUMN, Qt.AlignRight | Qt.AlignVCenter) self.__variable_items.append(item) def _set_bottom_axis(self): self.__bottom_axis.setRange(*self.__range) self.__layout.addItem(self.__bottom_axis, len(self.__violin_items), ViolinPlot.VIOLIN_COLUMN) def set_vertical_line(self): x = self.violin_column_width / 2 height = 0 for i in range(len(self.__violin_items)): violin_item = self.__layout.itemAt(i, ViolinPlot.VIOLIN_COLUMN) text_item = self.__layout.itemAt(i, ViolinPlot.LABEL_COLUMN).item if violin_item.isVisible(): height += max(text_item.boundingRect().height(), violin_item.preferredSize().height()) self.__vertical_line.setLine(x, 0, x, -height) def deselect(self): self.selection_cleared.emit() def select(self, *args): self.selection_changed.emit(*args) def select_from_settings(self, x1: float, x2: float, attr_name: str): point_r_diff = 2 * self.__range[1] / (self.violin_column_width / 2) for item in self.__violin_items: if item.attr_name == attr_name: item.add_selection_rect(x1 - point_r_diff, x2 + point_r_diff) break self.select(x1, x2, attr_name) def apply_visual_settings(self, settings: Dict): for key, value in settings.items(): self.parameter_setter.set_parameter(key, value)
class ViolinPlot(QGraphicsWidget): LABEL_COLUMN, VIOLIN_COLUMN, LEGEND_COLUMN = range(3) VIOLIN_COLUMN_WIDTH, OFFSET = 300, 250 MAX_N_ITEMS = 100 MAX_ATTR_LEN = 20 selection_cleared = Signal() selection_changed = Signal(float, float, str) def __init__(self): super().__init__() self.__violin_column_width = self.VIOLIN_COLUMN_WIDTH # type: int self.__range = None # type: Optional[Tuple[float, float]] self.__violin_items = [] # type: List[ViolinItem] self.__bottom_axis = pg.AxisItem(parent=self, orientation="bottom", maxTickLength=7, pen=QPen(Qt.black)) self.__bottom_axis.setLabel("Impact on model output") self.__vertical_line = QGraphicsLineItem(self.__bottom_axis) self.__vertical_line.setPen(QPen(Qt.gray)) self.__legend = Legend(self) self.__layout = QGraphicsGridLayout() self.__layout.addItem(self.__legend, 0, ViolinPlot.LEGEND_COLUMN) self.__layout.setVerticalSpacing(0) self.setLayout(self.__layout) @property def violin_column_width(self): return self.__violin_column_width @violin_column_width.setter def violin_column_width(self, view_width: int): self.__violin_column_width = max(self.VIOLIN_COLUMN_WIDTH, view_width - self.OFFSET) @property def bottom_axis(self): return self.__bottom_axis def set_data(self, x: np.ndarray, colors: np.ndarray, names: List[str], n_attrs: float, view_width: int): self.violin_column_width = view_width abs_max = np.max(np.abs(x)) * 1.05 self.__range = (-abs_max, abs_max) self._set_violin_items(x, colors, names) self._set_labels(names) self._set_bottom_axis() self._set_vertical_line() self.set_n_visible(n_attrs) def set_n_visible(self, n: int): for i in range(len(self.__violin_items)): violin_item = self.__layout.itemAt(i, ViolinPlot.VIOLIN_COLUMN) violin_item.setVisible(i < n) text_item = self.__layout.itemAt(i, ViolinPlot.LABEL_COLUMN).item text_item.setVisible(i < n) x = self.__vertical_line.line().x1() n = min(n, len(self.__violin_items)) self.__vertical_line.setLine(x, 0, x, -ViolinItem.HEIGHT * n) def rescale(self, view_width: int): self.violin_column_width = view_width with temp_seed(0): for item in self.__violin_items: item.rescale(self.violin_column_width) self.__bottom_axis.setWidth(self.violin_column_width) x = self.violin_column_width / 2 self.__vertical_line.setLine(x, 0, x, self.__vertical_line.line().y2()) def show_legend(self, show: bool): self.__legend.setVisible(show) self.__bottom_axis.setWidth(self.violin_column_width) x = self.violin_column_width / 2 self.__vertical_line.setLine(x, 0, x, self.__vertical_line.line().y2()) def _set_violin_items(self, x: np.ndarray, colors: np.ndarray, labels: List[str]): with temp_seed(0): for i in range(x.shape[1]): item = ViolinItem(self, labels[i], self.__range, self.violin_column_width) item.set_data(x[:, i], colors[:, i]) item.selection_changed.connect(self.select) self.__violin_items.append(item) self.__layout.addItem(item, i, ViolinPlot.VIOLIN_COLUMN) if i == self.MAX_N_ITEMS: break def _set_labels(self, labels: List[str]): for i, (label, _) in enumerate(zip(labels, self.__violin_items)): short = f"{label[:self.MAX_ATTR_LEN - 1]}..." \ if len(label) > self.MAX_ATTR_LEN else label text = QGraphicsSimpleTextItem(short, self) text.setToolTip(label) item = SimpleLayoutItem(text) item.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.__layout.addItem(item, i, ViolinPlot.LABEL_COLUMN, Qt.AlignRight | Qt.AlignVCenter) def _set_bottom_axis(self): self.__bottom_axis.setRange(*self.__range) self.__layout.addItem(self.__bottom_axis, len(self.__violin_items), ViolinPlot.VIOLIN_COLUMN) def _set_vertical_line(self): x = self.violin_column_width / 2 n = len(self.__violin_items) self.__vertical_line.setLine(x, 0, x, -ViolinItem.HEIGHT * n) def deselect(self): self.selection_cleared.emit() def select(self, *args): self.selection_changed.emit(*args) def select_from_settings(self, x1: float, x2: float, attr_name: str): point_r_diff = 2 * self.__range[1] / (self.violin_column_width / 2) for item in self.__violin_items: if item.attr_name == attr_name: item.add_selection_rect(x1 - point_r_diff, x2 + point_r_diff) break self.select(x1, x2, attr_name)
class OWDistanceMap(widget.OWWidget): name = "Distance Map" description = "Visualize a distance matrix." icon = "icons/DistanceMap.svg" priority = 1200 keywords = [] class Inputs: distances = Input("Distances", Orange.misc.DistMatrix) class Outputs: selected_data = Output("Selected Data", Orange.data.Table, default=True) annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table) features = Output("Features", widget.AttributeList, dynamic=False) settingsHandler = settings.PerfectDomainContextHandler() #: type of ordering to apply to matrix rows/columns NoOrdering, Clustering, OrderedClustering = 0, 1, 2 sorting = settings.Setting(NoOrdering) colormap = settings.Setting(_default_colormap_index) color_gamma = settings.Setting(0.0) color_low = settings.Setting(0.0) color_high = settings.Setting(1.0) annotation_idx = settings.ContextSetting(0) autocommit = settings.Setting(True) graph_name = "grid_widget" # Disable clustering for inputs bigger than this if hierarchical._HAS_NN_CHAIN: _MaxClustering = 25000 else: _MaxClustering = 3000 # Disable cluster leaf ordering for inputs bigger than this _MaxOrderedClustering = 1000 def __init__(self): super().__init__() self.matrix = None self._tree = None self._ordered_tree = None self._sorted_matrix = None self._sort_indices = None self._selection = None self.sorting_cb = gui.comboBox( self.controlArea, self, "sorting", box="Element Sorting", items=["None", "Clustering", "Clustering with ordered leaves"], callback=self._invalidate_ordering) box = gui.vBox(self.controlArea, "Colors") self.colormap_cb = gui.comboBox( box, self, "colormap", callback=self._update_color) self.colormap_cb.setIconSize(QSize(64, 16)) self.palettes = list(_color_palettes) init_color_combo(self.colormap_cb, self.palettes, QSize(64, 16)) self.colormap_cb.setCurrentIndex(self.colormap) form = QFormLayout( formAlignment=Qt.AlignLeft, labelAlignment=Qt.AlignLeft, fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow ) # form.addRow( # "Gamma", # gui.hSlider(box, self, "color_gamma", minValue=0.0, maxValue=1.0, # step=0.05, ticks=True, intOnly=False, # createLabel=False, callback=self._update_color) # ) form.addRow( "Low:", gui.hSlider(box, self, "color_low", minValue=0.0, maxValue=1.0, step=0.05, ticks=True, intOnly=False, createLabel=False, callback=self._update_color) ) form.addRow( "High:", gui.hSlider(box, self, "color_high", minValue=0.0, maxValue=1.0, step=0.05, ticks=True, intOnly=False, createLabel=False, callback=self._update_color) ) box.layout().addLayout(form) self.annot_combo = gui.comboBox( self.controlArea, self, "annotation_idx", box="Annotations", callback=self._invalidate_annotations, contentsLength=12) self.annot_combo.setModel(itemmodels.VariableListModel()) self.annot_combo.model()[:] = ["None", "Enumeration"] self.controlArea.layout().addStretch() gui.auto_commit(self.controlArea, self, "autocommit", "Send Selected") self.view = pg.GraphicsView(background="w") self.mainArea.layout().addWidget(self.view) self.grid_widget = pg.GraphicsWidget() self.grid = QGraphicsGridLayout() self.grid_widget.setLayout(self.grid) self.viewbox = pg.ViewBox(enableMouse=False, enableMenu=False) self.viewbox.setAcceptedMouseButtons(Qt.NoButton) self.viewbox.setAcceptHoverEvents(False) self.grid.addItem(self.viewbox, 1, 1) self.left_dendrogram = DendrogramWidget( self.grid_widget, orientation=DendrogramWidget.Left, selectionMode=DendrogramWidget.NoSelection, hoverHighlightEnabled=False ) self.left_dendrogram.setAcceptedMouseButtons(Qt.NoButton) self.left_dendrogram.setAcceptHoverEvents(False) self.top_dendrogram = DendrogramWidget( self.grid_widget, orientation=DendrogramWidget.Top, selectionMode=DendrogramWidget.NoSelection, hoverHighlightEnabled=False ) self.top_dendrogram.setAcceptedMouseButtons(Qt.NoButton) self.top_dendrogram.setAcceptHoverEvents(False) self.grid.addItem(self.left_dendrogram, 1, 0) self.grid.addItem(self.top_dendrogram, 0, 1) self.right_labels = TextList( alignment=Qt.AlignLeft) self.bottom_labels = TextList( orientation=Qt.Horizontal, alignment=Qt.AlignRight) self.grid.addItem(self.right_labels, 1, 2) self.grid.addItem(self.bottom_labels, 2, 1) self.view.setCentralItem(self.grid_widget) self.left_dendrogram.hide() self.top_dendrogram.hide() self.right_labels.hide() self.bottom_labels.hide() self.matrix_item = None self.dendrogram = None self.grid_widget.scene().installEventFilter(self) @Inputs.distances def set_distances(self, matrix): self.closeContext() self.clear() self.error() if matrix is not None: N, _ = matrix.shape if N < 2: self.error("Empty distance matrix.") matrix = None self.matrix = matrix if matrix is not None: self.set_items(matrix.row_items, matrix.axis) else: self.set_items(None) if matrix is not None: N, _ = matrix.shape else: N = 0 model = self.sorting_cb.model() item = model.item(2) msg = None if N > OWDistanceMap._MaxOrderedClustering: item.setFlags(item.flags() & ~Qt.ItemIsEnabled) if self.sorting == OWDistanceMap.OrderedClustering: self.sorting = OWDistanceMap.Clustering msg = "Cluster ordering was disabled due to the input " \ "matrix being to big" else: item.setFlags(item.flags() | Qt.ItemIsEnabled) item = model.item(1) if N > OWDistanceMap._MaxClustering: item.setFlags(item.flags() & ~Qt.ItemIsEnabled) if self.sorting == OWDistanceMap.Clustering: self.sorting = OWDistanceMap.NoOrdering msg = "Clustering was disabled due to the input " \ "matrix being to big" else: item.setFlags(item.flags() | Qt.ItemIsEnabled) self.information(msg) def set_items(self, items, axis=1): self.items = items model = self.annot_combo.model() if items is None: model[:] = ["None", "Enumeration"] elif not axis: model[:] = ["None", "Enumeration", "Attribute names"] elif isinstance(items, Orange.data.Table): annot_vars = list(filter_visible(items.domain.variables)) + list(items.domain.metas) model[:] = ["None", "Enumeration"] + annot_vars self.annotation_idx = 0 self.openContext(items.domain) elif isinstance(items, list) and \ all(isinstance(item, Orange.data.Variable) for item in items): model[:] = ["None", "Enumeration", "Name"] else: model[:] = ["None", "Enumeration"] self.annotation_idx = min(self.annotation_idx, len(model) - 1) def clear(self): self.matrix = None self.cluster = None self._tree = None self._ordered_tree = None self._sorted_matrix = None self._selection = [] self._clear_plot() def handleNewSignals(self): if self.matrix is not None: self._update_ordering() self._setup_scene() self._update_labels() self.unconditional_commit() def _clear_plot(self): def remove(item): item.setParentItem(None) item.scene().removeItem(item) if self.matrix_item is not None: self.matrix_item.selectionChanged.disconnect( self._invalidate_selection) remove(self.matrix_item) self.matrix_item = None self._set_displayed_dendrogram(None) self._set_labels(None) def _cluster_tree(self): if self._tree is None: self._tree = hierarchical.dist_matrix_clustering(self.matrix) return self._tree def _ordered_cluster_tree(self): if self._ordered_tree is None: tree = self._cluster_tree() self._ordered_tree = \ hierarchical.optimal_leaf_ordering(tree, self.matrix) return self._ordered_tree def _setup_scene(self): self._clear_plot() self.matrix_item = DistanceMapItem(self._sorted_matrix) # Scale the y axis to compensate for pg.ViewBox's y axis invert self.matrix_item.setTransform(QTransform.fromScale(1, -1), ) self.viewbox.addItem(self.matrix_item) # Set fixed view box range. h, w = self._sorted_matrix.shape self.viewbox.setRange(QRectF(0, -h, w, h), padding=0) self.matrix_item.selectionChanged.connect(self._invalidate_selection) if self.sorting == OWDistanceMap.NoOrdering: tree = None elif self.sorting == OWDistanceMap.Clustering: tree = self._cluster_tree() elif self.sorting == OWDistanceMap.OrderedClustering: tree = self._ordered_cluster_tree() self._set_displayed_dendrogram(tree) self._update_color() def _set_displayed_dendrogram(self, root): self.left_dendrogram.set_root(root) self.top_dendrogram.set_root(root) self.left_dendrogram.setVisible(root is not None) self.top_dendrogram.setVisible(root is not None) constraint = 0 if root is None else -1 # 150 self.left_dendrogram.setMaximumWidth(constraint) self.top_dendrogram.setMaximumHeight(constraint) def _invalidate_ordering(self): self._sorted_matrix = None if self.matrix is not None: self._update_ordering() self._setup_scene() self._update_labels() self._invalidate_selection() def _update_ordering(self): if self.sorting == OWDistanceMap.NoOrdering: self._sorted_matrix = self.matrix self._sort_indices = None else: if self.sorting == OWDistanceMap.Clustering: tree = self._cluster_tree() elif self.sorting == OWDistanceMap.OrderedClustering: tree = self._ordered_cluster_tree() leaves = hierarchical.leaves(tree) indices = numpy.array([leaf.value.index for leaf in leaves]) X = self.matrix self._sorted_matrix = X[indices[:, numpy.newaxis], indices[numpy.newaxis, :]] self._sort_indices = indices def _invalidate_annotations(self): if self.matrix is not None: self._update_labels() def _update_labels(self, ): if self.annotation_idx == 0: # None labels = None elif self.annotation_idx == 1: # Enumeration labels = [str(i + 1) for i in range(self.matrix.shape[0])] elif self.annot_combo.model()[self.annotation_idx] == "Attribute names": attr = self.matrix.row_items.domain.attributes labels = [str(attr[i]) for i in range(self.matrix.shape[0])] elif self.annotation_idx == 2 and \ isinstance(self.items, widget.AttributeList): labels = [v.name for v in self.items] elif isinstance(self.items, Orange.data.Table): var = self.annot_combo.model()[self.annotation_idx] column, _ = self.items.get_column_view(var) labels = [var.str_val(value) for value in column] self._set_labels(labels) def _set_labels(self, labels): self._labels = labels if labels and self.sorting != OWDistanceMap.NoOrdering: sortind = self._sort_indices labels = [labels[i] for i in sortind] for textlist in [self.right_labels, self.bottom_labels]: textlist.set_labels(labels or []) textlist.setVisible(bool(labels)) constraint = -1 if labels else 0 self.right_labels.setMaximumWidth(constraint) self.bottom_labels.setMaximumHeight(constraint) def _update_color(self): if self.matrix_item: name, colors = self.palettes[self.colormap] n, colors = max(colors.items()) colors = numpy.array(colors, dtype=numpy.ubyte) low, high = self.color_low * 255, self.color_high * 255 points = numpy.linspace(low, high, n) space = numpy.linspace(0, 255, 255) r = numpy.interp(space, points, colors[:, 0], left=255, right=0) g = numpy.interp(space, points, colors[:, 1], left=255, right=0) b = numpy.interp(space, points, colors[:, 2], left=255, right=0) colortable = numpy.c_[r, g, b] self.matrix_item.setLookupTable(colortable) def _invalidate_selection(self): ranges = self.matrix_item.selections() ranges = reduce(iadd, ranges, []) indices = reduce(iadd, ranges, []) if self.sorting != OWDistanceMap.NoOrdering: sortind = self._sort_indices indices = [sortind[i] for i in indices] self._selection = list(sorted(set(indices))) self.commit() def commit(self): datasubset = None featuresubset = None if not self._selection: pass elif isinstance(self.items, Orange.data.Table): indices = self._selection if self.matrix.axis == 1: datasubset = self.items.from_table_rows(self.items, indices) elif self.matrix.axis == 0: domain = Orange.data.Domain( [self.items.domain[i] for i in indices], self.items.domain.class_vars, self.items.domain.metas) datasubset = self.items.transform(domain) elif isinstance(self.items, widget.AttributeList): subset = [self.items[i] for i in self._selection] featuresubset = widget.AttributeList(subset) self.Outputs.selected_data.send(datasubset) self.Outputs.annotated_data.send(create_annotated_table(self.items, self._selection)) self.Outputs.features.send(featuresubset) def onDeleteWidget(self): super().onDeleteWidget() self.clear() def send_report(self): annot = self.annot_combo.currentText() if self.annotation_idx <= 1: annot = annot.lower() self.report_items(( ("Sorting", self.sorting_cb.currentText().lower()), ("Annotations", annot) )) if self.matrix is not None: self.report_plot()
def __updateState(self): """ Update the widget with the new source/sink node signal descriptions. """ widget = QGraphicsWidget() widget.setLayout(QGraphicsGridLayout()) # Space between left and right anchors widget.layout().setHorizontalSpacing(50) left_node = EditLinksNode(self, direction=Qt.LeftToRight, node=self.source) left_node.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) right_node = EditLinksNode(self, direction=Qt.RightToLeft, node=self.sink) right_node.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) left_node.setMinimumWidth(150) right_node.setMinimumWidth(150) widget.layout().addItem(left_node, 0, 0,) widget.layout().addItem(right_node, 0, 1,) title_template = "<center><b>{0}<b></center>" left_title = GraphicsTextWidget(self) left_title.setHtml(title_template.format(escape(self.source.title))) left_title.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) right_title = GraphicsTextWidget(self) right_title.setHtml(title_template.format(escape(self.sink.title))) right_title.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) widget.layout().addItem(left_title, 1, 0, alignment=Qt.AlignHCenter | Qt.AlignTop) widget.layout().addItem(right_title, 1, 1, alignment=Qt.AlignHCenter | Qt.AlignTop) widget.setParentItem(self) max_w = max(left_node.sizeHint(Qt.PreferredSize).width(), right_node.sizeHint(Qt.PreferredSize).width()) # fix same size left_node.setMinimumWidth(max_w) right_node.setMinimumWidth(max_w) left_title.setMinimumWidth(max_w) right_title.setMinimumWidth(max_w) self.layout().addItem(widget) self.layout().activate() self.sourceNodeWidget = left_node self.sinkNodeWidget = right_node self.sourceNodeTitle = left_title self.sinkNodeTitle = right_title self.__resetAnchorStates() # AnchorHover hover over anchor before hovering over line class AnchorHover(QGraphicsRectItem): def __init__(self, anchor, parent=None): super().__init__(parent=parent) self.setAcceptHoverEvents(True) self.anchor = anchor self.setRect(anchor.boundingRect()) self.setPos(self.mapFromScene(anchor.scenePos())) self.setFlag(QGraphicsItem.ItemHasNoContents, True) def hoverEnterEvent(self, event): if self.anchor.isEnabled(): self.anchor.hoverEnterEvent(event) else: event.ignore() def hoverLeaveEvent(self, event): if self.anchor.isEnabled(): self.anchor.hoverLeaveEvent(event) else: event.ignore() for anchor in left_node.channelAnchors + right_node.channelAnchors: anchor_hover = AnchorHover(anchor, parent=self) anchor_hover.setZValue(2.0)
def __init__(self): super().__init__() self.matrix = None self._tree = None self._ordered_tree = None self._sorted_matrix = None self._sort_indices = None self._selection = None self.sorting_cb = gui.comboBox( self.controlArea, self, "sorting", box="Element Sorting", items=["None", "Clustering", "Clustering with ordered leaves"], callback=self._invalidate_ordering) box = gui.vBox(self.controlArea, "Colors") self.colormap_cb = gui.comboBox( box, self, "colormap", callback=self._update_color) self.colormap_cb.setIconSize(QSize(64, 16)) self.palettes = list(_color_palettes) init_color_combo(self.colormap_cb, self.palettes, QSize(64, 16)) self.colormap_cb.setCurrentIndex(self.colormap) form = QFormLayout( formAlignment=Qt.AlignLeft, labelAlignment=Qt.AlignLeft, fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow ) # form.addRow( # "Gamma", # gui.hSlider(box, self, "color_gamma", minValue=0.0, maxValue=1.0, # step=0.05, ticks=True, intOnly=False, # createLabel=False, callback=self._update_color) # ) form.addRow( "Low:", gui.hSlider(box, self, "color_low", minValue=0.0, maxValue=1.0, step=0.05, ticks=True, intOnly=False, createLabel=False, callback=self._update_color) ) form.addRow( "High:", gui.hSlider(box, self, "color_high", minValue=0.0, maxValue=1.0, step=0.05, ticks=True, intOnly=False, createLabel=False, callback=self._update_color) ) box.layout().addLayout(form) self.annot_combo = gui.comboBox( self.controlArea, self, "annotation_idx", box="Annotations", callback=self._invalidate_annotations, contentsLength=12) self.annot_combo.setModel(itemmodels.VariableListModel()) self.annot_combo.model()[:] = ["None", "Enumeration"] self.controlArea.layout().addStretch() gui.auto_commit(self.controlArea, self, "autocommit", "Send Selected") self.view = pg.GraphicsView(background="w") self.mainArea.layout().addWidget(self.view) self.grid_widget = pg.GraphicsWidget() self.grid = QGraphicsGridLayout() self.grid_widget.setLayout(self.grid) self.viewbox = pg.ViewBox(enableMouse=False, enableMenu=False) self.viewbox.setAcceptedMouseButtons(Qt.NoButton) self.viewbox.setAcceptHoverEvents(False) self.grid.addItem(self.viewbox, 1, 1) self.left_dendrogram = DendrogramWidget( self.grid_widget, orientation=DendrogramWidget.Left, selectionMode=DendrogramWidget.NoSelection, hoverHighlightEnabled=False ) self.left_dendrogram.setAcceptedMouseButtons(Qt.NoButton) self.left_dendrogram.setAcceptHoverEvents(False) self.top_dendrogram = DendrogramWidget( self.grid_widget, orientation=DendrogramWidget.Top, selectionMode=DendrogramWidget.NoSelection, hoverHighlightEnabled=False ) self.top_dendrogram.setAcceptedMouseButtons(Qt.NoButton) self.top_dendrogram.setAcceptHoverEvents(False) self.grid.addItem(self.left_dendrogram, 1, 0) self.grid.addItem(self.top_dendrogram, 0, 1) self.right_labels = TextList( alignment=Qt.AlignLeft) self.bottom_labels = TextList( orientation=Qt.Horizontal, alignment=Qt.AlignRight) self.grid.addItem(self.right_labels, 1, 2) self.grid.addItem(self.bottom_labels, 2, 1) self.view.setCentralItem(self.grid_widget) self.left_dendrogram.hide() self.top_dendrogram.hide() self.right_labels.hide() self.bottom_labels.hide() self.matrix_item = None self.dendrogram = None self.grid_widget.scene().installEventFilter(self)
class OWDistanceMap(widget.OWWidget): name = "Distance Map" description = "Visualize a distance matrix." icon = "icons/DistanceMap.svg" priority = 1200 keywords = [] class Inputs: distances = Input("Distances", Orange.misc.DistMatrix) class Outputs: selected_data = Output("Selected Data", Orange.data.Table, default=True) annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table) features = Output("Features", widget.AttributeList, dynamic=False) settingsHandler = settings.PerfectDomainContextHandler() #: type of ordering to apply to matrix rows/columns NoOrdering, Clustering, OrderedClustering = 0, 1, 2 sorting = settings.Setting(NoOrdering) palette_name = settings.Setting(colorpalettes.DefaultContinuousPaletteName) color_gamma = settings.Setting(0.0) color_low = settings.Setting(0.0) color_high = settings.Setting(1.0) annotation_idx = settings.ContextSetting(0) pending_selection = settings.Setting(None, schema_only=True) autocommit = settings.Setting(True) graph_name = "grid_widget" # Disable clustering for inputs bigger than this _MaxClustering = 25000 # Disable cluster leaf ordering for inputs bigger than this _MaxOrderedClustering = 2000 def __init__(self): super().__init__() self.matrix = None self._tree = None self._ordered_tree = None self._sorted_matrix = None self._sort_indices = None self._selection = None self.sorting_cb = gui.comboBox( self.controlArea, self, "sorting", box="Element Sorting", items=["None", "Clustering", "Clustering with ordered leaves"], callback=self._invalidate_ordering) box = gui.vBox(self.controlArea, "Colors") self.color_box = gui.palette_combo_box(self.palette_name) self.color_box.currentIndexChanged.connect(self._update_color) box.layout().addWidget(self.color_box) form = QFormLayout( formAlignment=Qt.AlignLeft, labelAlignment=Qt.AlignLeft, fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow ) # form.addRow( # "Gamma", # gui.hSlider(box, self, "color_gamma", minValue=0.0, maxValue=1.0, # step=0.05, ticks=True, intOnly=False, # createLabel=False, callback=self._update_color) # ) form.addRow( "Low:", gui.hSlider(box, self, "color_low", minValue=0.0, maxValue=1.0, step=0.05, ticks=True, intOnly=False, createLabel=False, callback=self._update_color) ) form.addRow( "High:", gui.hSlider(box, self, "color_high", minValue=0.0, maxValue=1.0, step=0.05, ticks=True, intOnly=False, createLabel=False, callback=self._update_color) ) box.layout().addLayout(form) self.annot_combo = gui.comboBox( self.controlArea, self, "annotation_idx", box="Annotations", callback=self._invalidate_annotations, contentsLength=12) self.annot_combo.setModel(itemmodels.VariableListModel()) self.annot_combo.model()[:] = ["None", "Enumeration"] self.controlArea.layout().addStretch() gui.auto_send(self.controlArea, self, "autocommit") self.view = pg.GraphicsView(background="w") self.mainArea.layout().addWidget(self.view) self.grid_widget = pg.GraphicsWidget() self.grid = QGraphicsGridLayout() self.grid_widget.setLayout(self.grid) self.viewbox = pg.ViewBox(enableMouse=False, enableMenu=False) self.viewbox.setAcceptedMouseButtons(Qt.NoButton) self.viewbox.setAcceptHoverEvents(False) self.grid.addItem(self.viewbox, 1, 1) self.left_dendrogram = DendrogramWidget( self.grid_widget, orientation=DendrogramWidget.Left, selectionMode=DendrogramWidget.NoSelection, hoverHighlightEnabled=False ) self.left_dendrogram.setAcceptedMouseButtons(Qt.NoButton) self.left_dendrogram.setAcceptHoverEvents(False) self.top_dendrogram = DendrogramWidget( self.grid_widget, orientation=DendrogramWidget.Top, selectionMode=DendrogramWidget.NoSelection, hoverHighlightEnabled=False ) self.top_dendrogram.setAcceptedMouseButtons(Qt.NoButton) self.top_dendrogram.setAcceptHoverEvents(False) self.grid.addItem(self.left_dendrogram, 1, 0) self.grid.addItem(self.top_dendrogram, 0, 1) self.right_labels = TextList( alignment=Qt.AlignLeft | Qt.AlignVCenter, sizePolicy=QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) ) self.bottom_labels = TextList( orientation=Qt.Horizontal, alignment=Qt.AlignRight | Qt.AlignVCenter, sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) ) self.grid.addItem(self.right_labels, 1, 2) self.grid.addItem(self.bottom_labels, 2, 1) self.view.setCentralItem(self.grid_widget) self.left_dendrogram.hide() self.top_dendrogram.hide() self.right_labels.hide() self.bottom_labels.hide() self.matrix_item = None self.dendrogram = None self.grid_widget.scene().installEventFilter(self) self.settingsAboutToBePacked.connect(self.pack_settings) def pack_settings(self): if self.matrix_item is not None: self.pending_selection = self.matrix_item.selections() else: self.pending_selection = None @Inputs.distances def set_distances(self, matrix): self.closeContext() self.clear() self.error() if matrix is not None: N, _ = matrix.shape if N < 2: self.error("Empty distance matrix.") matrix = None self.matrix = matrix if matrix is not None: self.set_items(matrix.row_items, matrix.axis) else: self.set_items(None) if matrix is not None: N, _ = matrix.shape else: N = 0 model = self.sorting_cb.model() item = model.item(2) msg = None if N > OWDistanceMap._MaxOrderedClustering: item.setFlags(item.flags() & ~Qt.ItemIsEnabled) if self.sorting == OWDistanceMap.OrderedClustering: self.sorting = OWDistanceMap.Clustering msg = "Cluster ordering was disabled due to the input " \ "matrix being to big" else: item.setFlags(item.flags() | Qt.ItemIsEnabled) item = model.item(1) if N > OWDistanceMap._MaxClustering: item.setFlags(item.flags() & ~Qt.ItemIsEnabled) if self.sorting == OWDistanceMap.Clustering: self.sorting = OWDistanceMap.NoOrdering msg = "Clustering was disabled due to the input " \ "matrix being to big" else: item.setFlags(item.flags() | Qt.ItemIsEnabled) self.information(msg) def set_items(self, items, axis=1): self.items = items model = self.annot_combo.model() if items is None: model[:] = ["None", "Enumeration"] elif not axis: model[:] = ["None", "Enumeration", "Attribute names"] elif isinstance(items, Orange.data.Table): annot_vars = list(filter_visible(items.domain.variables)) + list(items.domain.metas) model[:] = ["None", "Enumeration"] + annot_vars self.annotation_idx = 0 self.openContext(items.domain) elif isinstance(items, list) and \ all(isinstance(item, Orange.data.Variable) for item in items): model[:] = ["None", "Enumeration", "Name"] else: model[:] = ["None", "Enumeration"] self.annotation_idx = min(self.annotation_idx, len(model) - 1) def clear(self): self.matrix = None self.cluster = None self._tree = None self._ordered_tree = None self._sorted_matrix = None self._selection = [] self._clear_plot() def handleNewSignals(self): if self.matrix is not None: self._update_ordering() self._setup_scene() self._update_labels() if self.pending_selection is not None: self.matrix_item.set_selections(self.pending_selection) self.pending_selection = None self.unconditional_commit() def _clear_plot(self): def remove(item): item.setParentItem(None) item.scene().removeItem(item) if self.matrix_item is not None: self.matrix_item.selectionChanged.disconnect( self._invalidate_selection) remove(self.matrix_item) self.matrix_item = None self._set_displayed_dendrogram(None) self._set_labels(None) def _cluster_tree(self): if self._tree is None: self._tree = hierarchical.dist_matrix_clustering(self.matrix) return self._tree def _ordered_cluster_tree(self): if self._ordered_tree is None: tree = self._cluster_tree() self._ordered_tree = \ hierarchical.optimal_leaf_ordering(tree, self.matrix) return self._ordered_tree def _setup_scene(self): self._clear_plot() self.matrix_item = DistanceMapItem(self._sorted_matrix) # Scale the y axis to compensate for pg.ViewBox's y axis invert self.matrix_item.setTransform(QTransform.fromScale(1, -1), ) self.viewbox.addItem(self.matrix_item) # Set fixed view box range. h, w = self._sorted_matrix.shape self.viewbox.setRange(QRectF(0, -h, w, h), padding=0) self.matrix_item.selectionChanged.connect(self._invalidate_selection) if self.sorting == OWDistanceMap.NoOrdering: tree = None elif self.sorting == OWDistanceMap.Clustering: tree = self._cluster_tree() elif self.sorting == OWDistanceMap.OrderedClustering: tree = self._ordered_cluster_tree() self._set_displayed_dendrogram(tree) self._update_color() def _set_displayed_dendrogram(self, root): self.left_dendrogram.set_root(root) self.top_dendrogram.set_root(root) self.left_dendrogram.setVisible(root is not None) self.top_dendrogram.setVisible(root is not None) constraint = 0 if root is None else -1 # 150 self.left_dendrogram.setMaximumWidth(constraint) self.top_dendrogram.setMaximumHeight(constraint) def _invalidate_ordering(self): self._sorted_matrix = None if self.matrix is not None: self._update_ordering() self._setup_scene() self._update_labels() self._invalidate_selection() def _update_ordering(self): if self.sorting == OWDistanceMap.NoOrdering: self._sorted_matrix = self.matrix self._sort_indices = None else: if self.sorting == OWDistanceMap.Clustering: tree = self._cluster_tree() elif self.sorting == OWDistanceMap.OrderedClustering: tree = self._ordered_cluster_tree() leaves = hierarchical.leaves(tree) indices = numpy.array([leaf.value.index for leaf in leaves]) X = self.matrix self._sorted_matrix = X[indices[:, numpy.newaxis], indices[numpy.newaxis, :]] self._sort_indices = indices def _invalidate_annotations(self): if self.matrix is not None: self._update_labels() def _update_labels(self, ): if self.annotation_idx == 0: # None labels = None elif self.annotation_idx == 1: # Enumeration labels = [str(i + 1) for i in range(self.matrix.shape[0])] elif self.annot_combo.model()[self.annotation_idx] == "Attribute names": attr = self.matrix.row_items.domain.attributes labels = [str(attr[i]) for i in range(self.matrix.shape[0])] elif self.annotation_idx == 2 and \ isinstance(self.items, widget.AttributeList): labels = [v.name for v in self.items] elif isinstance(self.items, Orange.data.Table): var = self.annot_combo.model()[self.annotation_idx] column, _ = self.items.get_column_view(var) labels = [var.str_val(value) for value in column] self._set_labels(labels) def _set_labels(self, labels): self._labels = labels if labels and self.sorting != OWDistanceMap.NoOrdering: sortind = self._sort_indices labels = [labels[i] for i in sortind] for textlist in [self.right_labels, self.bottom_labels]: textlist.setItems(labels or []) textlist.setVisible(bool(labels)) constraint = -1 if labels else 0 self.right_labels.setMaximumWidth(constraint) self.bottom_labels.setMaximumHeight(constraint) def _update_color(self): palette = self.color_box.currentData() self.palette_name = palette.name if self.matrix_item: colors = palette.lookup_table(self.color_low, self.color_high) self.matrix_item.setLookupTable(colors) def _invalidate_selection(self): ranges = self.matrix_item.selections() ranges = reduce(iadd, ranges, []) indices = reduce(iadd, ranges, []) if self.sorting != OWDistanceMap.NoOrdering: sortind = self._sort_indices indices = [sortind[i] for i in indices] self._selection = list(sorted(set(indices))) self.commit() def commit(self): datasubset = None featuresubset = None if not self._selection: pass elif isinstance(self.items, Orange.data.Table): indices = self._selection if self.matrix.axis == 1: datasubset = self.items.from_table_rows(self.items, indices) elif self.matrix.axis == 0: domain = Orange.data.Domain( [self.items.domain[i] for i in indices], self.items.domain.class_vars, self.items.domain.metas) datasubset = self.items.transform(domain) elif isinstance(self.items, widget.AttributeList): subset = [self.items[i] for i in self._selection] featuresubset = widget.AttributeList(subset) self.Outputs.selected_data.send(datasubset) self.Outputs.annotated_data.send(create_annotated_table(self.items, self._selection)) self.Outputs.features.send(featuresubset) def onDeleteWidget(self): super().onDeleteWidget() self.clear() def send_report(self): annot = self.annot_combo.currentText() if self.annotation_idx <= 1: annot = annot.lower() self.report_items(( ("Sorting", self.sorting_cb.currentText().lower()), ("Annotations", annot) )) if self.matrix is not None: self.report_plot()
def __updateState(self): """ Update the widget with the new source/sink node signal descriptions. """ widget = QGraphicsWidget() widget.setLayout(QGraphicsGridLayout()) # Space between left and right anchors widget.layout().setHorizontalSpacing(50) left_node = EditLinksNode(self, direction=Qt.LeftToRight, node=self.source) left_node.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) right_node = EditLinksNode(self, direction=Qt.RightToLeft, node=self.sink) right_node.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) left_node.setMinimumWidth(150) right_node.setMinimumWidth(150) widget.layout().addItem( left_node, 0, 0, ) widget.layout().addItem( right_node, 0, 1, ) title_template = "<center><b>{0}<b></center>" left_title = GraphicsTextWidget(self) left_title.setHtml(title_template.format(escape(self.source.title))) left_title.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) right_title = GraphicsTextWidget(self) right_title.setHtml(title_template.format(escape(self.sink.title))) right_title.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) widget.layout().addItem(left_title, 1, 0, alignment=Qt.AlignHCenter | Qt.AlignTop) widget.layout().addItem(right_title, 1, 1, alignment=Qt.AlignHCenter | Qt.AlignTop) widget.setParentItem(self) max_w = max( left_node.sizeHint(Qt.PreferredSize).width(), right_node.sizeHint(Qt.PreferredSize).width()) # fix same size left_node.setMinimumWidth(max_w) right_node.setMinimumWidth(max_w) left_title.setMinimumWidth(max_w) right_title.setMinimumWidth(max_w) self.layout().addItem(widget) self.layout().activate() self.sourceNodeWidget = left_node self.sinkNodeWidget = right_node self.sourceNodeTitle = left_title self.sinkNodeTitle = right_title
class GraphicsThumbnailGrid(QGraphicsWidget): class LayoutMode(enum.Enum): FixedColumnCount, AutoReflow = 0, 1 FixedColumnCount, AutoReflow = LayoutMode #: Signal emitted when the current (thumbnail) changes currentThumbnailChanged = Signal(object) def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self.__layoutMode = GraphicsThumbnailGrid.AutoReflow self.__columnCount = -1 self.__thumbnails = [] # type: List[GraphicsThumbnailWidget] #: The current 'focused' thumbnail item. This is the item that last #: received the keyboard focus (though it does not necessarily have #: it now) self.__current = None # type: Optional[GraphicsThumbnailWidget] self.__reflowPending = False self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.setContentsMargins(10, 10, 10, 10) # NOTE: Keeping a reference to the layout. self.layout() # returns a QGraphicsLayout wrapper (i.e. strips the # QGraphicsGridLayout-nes of the object). self.__layout = QGraphicsGridLayout() self.__layout.setContentsMargins(0, 0, 0, 0) self.__layout.setSpacing(10) self.setLayout(self.__layout) def resizeEvent(self, event): super().resizeEvent(event) if event.newSize().width() != event.oldSize().width() and \ self.__layoutMode == GraphicsThumbnailGrid.AutoReflow: self.__reflow() def setGeometry(self, rect): self.prepareGeometryChange() super().setGeometry(rect) def count(self): """ Returns ------- count: int Number of thumbnails in the widget """ return len(self.__thumbnails) def addThumbnail(self, thumbnail): """ Add/append a thumbnail to the widget Parameters ---------- thumbnail: Union[GraphicsThumbnailWidget, QPixmap] The thumbnail to insert """ self.insertThumbnail(self.count(), thumbnail) def insertThumbnail(self, index, thumbnail): """ Insert a new thumbnail into a widget. Raise a ValueError if thumbnail is already in the view. Parameters ---------- index : int Index where to insert thumbnail : Union[GraphicsThumbnailWidget, QPixmap] The thumbnail to insert. GraphicsThumbnailGrid takes ownership of the item. """ if isinstance(thumbnail, QPixmap): thumbnail = GraphicsThumbnailWidget(thumbnail, parentItem=self) elif thumbnail in self.__thumbnails: raise ValueError("{!r} is already inserted".format(thumbnail)) elif not isinstance(thumbnail, GraphicsThumbnailWidget): raise TypeError index = max(min(index, self.count()), 0) moved = self.__takeItemsFrom(index) assert moved == self.__thumbnails[index:] self.__thumbnails.insert(index, thumbnail) self.__appendItems([thumbnail] + moved) thumbnail.setParentItem(self) thumbnail.installEventFilter(self) assert self.count() == self.layout().count() self.__scheduleLayout() def removeThumbnail(self, thumbnail): """ Remove a single thumbnail from the grid. Raise a ValueError if thumbnail is not in the grid. Parameters ---------- thumbnail : GraphicsThumbnailWidget Thumbnail to remove. Items ownership is transferred to the caller. """ index = self.__thumbnails.index(thumbnail) moved = self.__takeItemsFrom(index) del self.__thumbnails[index] assert moved[0] is thumbnail and self.__thumbnails[index:] == moved[1:] self.__appendItems(moved[1:]) thumbnail.removeEventFilter(self) if thumbnail.parentItem() is self: thumbnail.setParentItem(None) if self.__current is thumbnail: self.__current = None self.currentThumbnailChanged.emit(None) assert self.count() == self.layout().count() def thumbnailAt(self, index): """ Return the thumbnail widget at `index` Parameters ---------- index : int Returns ------- thumbnail : GraphicsThumbnailWidget """ return self.__thumbnails[index] def clear(self): """ Remove all thumbnails from the grid. """ removed = self.__takeItemsFrom(0) assert removed == self.__thumbnails self.__thumbnails = [] for thumb in removed: thumb.removeEventFilter(self) if thumb.parentItem() is self: thumb.setParentItem(None) if self.__current is not None: self.__current = None self.currentThumbnailChanged.emit(None) def __takeItemsFrom(self, fromindex): # remove all items starting at fromindex from the layout and # return them # NOTE: Operate on layout only layout = self.__layout taken = [] for i in reversed(range(fromindex, layout.count())): item = layout.itemAt(i) layout.removeAt(i) taken.append(item) return list(reversed(taken)) def __appendItems(self, items): # Append/insert items into the layout at the end # NOTE: Operate on layout only layout = self.__layout columns = max(layout.columnCount(), 1) for i, item in enumerate(items, layout.count()): layout.addItem(item, i // columns, i % columns) def __scheduleLayout(self): if not self.__reflowPending: self.__reflowPending = True QApplication.postEvent(self, QEvent(QEvent.LayoutRequest), Qt.HighEventPriority) def event(self, event): if event.type() == QEvent.LayoutRequest: if self.__layoutMode == GraphicsThumbnailGrid.AutoReflow: self.__reflow() else: self.__gridlayout() if self.parentLayoutItem() is None: sh = self.effectiveSizeHint(Qt.PreferredSize) self.resize(sh) if self.layout(): self.layout().activate() return super().event(event) def setFixedColumnCount(self, count): if count < 0: if self.__layoutMode != GraphicsThumbnailGrid.AutoReflow: self.__layoutMode = GraphicsThumbnailGrid.AutoReflow self.__reflow() else: if self.__layoutMode != GraphicsThumbnailGrid.FixedColumnCount: self.__layoutMode = GraphicsThumbnailGrid.FixedColumnCount if self.__columnCount != count: self.__columnCount = count self.__gridlayout() def __reflow(self): self.__reflowPending = False layout = self.__layout width = self.contentsRect().width() hints = [item.effectiveSizeHint(Qt.PreferredSize) for item in self.__thumbnails] widths = [max(24, h.width()) for h in hints] ncol = self._fitncols(widths, layout.horizontalSpacing(), width) self.__relayoutGrid(ncol) def __gridlayout(self): assert self.__layoutMode == GraphicsThumbnailGrid.FixedColumnCount self.__relayoutGrid(self.__columnCount) def __relayoutGrid(self, columnCount): layout = self.__layout if columnCount == layout.columnCount(): return # remove all items from the layout, then re-add them back in # updated positions items = self.__takeItemsFrom(0) for i, item in enumerate(items): layout.addItem(item, i // columnCount, i % columnCount) def items(self): """ Return all thumbnail items. Returns ------- thumbnails : List[GraphicsThumbnailWidget] """ return list(self.__thumbnails) def currentItem(self): """ Return the current (last focused) thumbnail item. """ return self.__current def _fitncols(self, widths, spacing, constraint): def sliced(seq, ncol): return [seq[i:i + ncol] for i in range(0, len(seq), ncol)] def flow_width(widths, spacing, ncol): W = sliced(widths, ncol) col_widths = map(max, zip_longest(*W, fillvalue=0)) return sum(col_widths) + (ncol - 1) * spacing ncol_best = 1 for ncol in range(2, len(widths) + 1): w = flow_width(widths, spacing, ncol) if w <= constraint: ncol_best = ncol else: break return ncol_best def keyPressEvent(self, event): if event.key() in [Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down]: self._moveCurrent(event.key(), event.modifiers()) event.accept() return super().keyPressEvent(event) def eventFilter(self, receiver, event): if isinstance(receiver, GraphicsThumbnailWidget) and \ event.type() == QEvent.FocusIn and \ receiver in self.__thumbnails: self.__current = receiver self.currentThumbnailChanged.emit(receiver) return super().eventFilter(receiver, event) def _moveCurrent(self, key, modifiers=Qt.NoModifier): """ Move the current thumbnail focus (`currentItem`) based on a key press (Qt.Key{Up,Down,Left,Right}) Parameters ---------- key : Qt.Key modifiers : Qt.Modifiers """ current = self.__current layout = self.__layout columns = layout.columnCount() rows = layout.rowCount() itempos = {} for i, j in itertools.product(range(rows), range(columns)): if i * columns + j >= layout.count(): break item = layout.itemAt(i, j) if item is not None: itempos[item] = (i, j) pos = itempos.get(current, None) if pos is None: return False i, j = pos index = i * columns + j if key == Qt.Key_Left: index = index - 1 elif key == Qt.Key_Right: index = index + 1 elif key == Qt.Key_Down: index = index + columns elif key == Qt.Key_Up: index = index - columns index = min(max(index, 0), layout.count() - 1) i = index // columns j = index % columns newcurrent = layout.itemAt(i, j) assert newcurrent is self.__thumbnails[index] if newcurrent is not None: if not modifiers & (Qt.ShiftModifier | Qt.ControlModifier): for item in self.__thumbnails: if item is not newcurrent: item.setSelected(False) # self.scene().clearSelection() newcurrent.setSelected(True) newcurrent.setFocus(Qt.TabFocusReason) newcurrent.ensureVisible() if self.__current is not newcurrent: self.__current = newcurrent self.currentThumbnailChanged.emit(newcurrent)
def __init__(self): super().__init__() self.matrix = None self.items = None self.linkmatrix = None self.root = None self._displayed_root = None self.cutoff_height = 0.0 gui.comboBox(self.controlArea, self, "linkage", items=LINKAGE, box="Linkage", callback=self._invalidate_clustering) model = itemmodels.VariableListModel() model[:] = self.basic_annotations box = gui.widgetBox(self.controlArea, "Annotations") self.label_cb = cb = combobox.ComboBoxSearch( minimumContentsLength=14, sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon) cb.setModel(model) cb.setCurrentIndex(cb.findData(self.annotation, Qt.EditRole)) def on_annotation_activated(): self.annotation = cb.currentData(Qt.EditRole) self._update_labels() cb.activated.connect(on_annotation_activated) def on_annotation_changed(value): cb.setCurrentIndex(cb.findData(value, Qt.EditRole)) self.connect_control("annotation", on_annotation_changed) box.layout().addWidget(self.label_cb) box = gui.radioButtons(self.controlArea, self, "pruning", box="Pruning", callback=self._invalidate_pruning) grid = QGridLayout() box.layout().addLayout(grid) grid.addWidget(gui.appendRadioButton(box, "None", addToLayout=False), 0, 0) self.max_depth_spin = gui.spin(box, self, "max_depth", minv=1, maxv=100, callback=self._invalidate_pruning, keyboardTracking=False, addToLayout=False) grid.addWidget( gui.appendRadioButton(box, "Max depth:", addToLayout=False), 1, 0) grid.addWidget(self.max_depth_spin, 1, 1) self.selection_box = gui.radioButtons( self.controlArea, self, "selection_method", box="Selection", callback=self._selection_method_changed) grid = QGridLayout() self.selection_box.layout().addLayout(grid) grid.addWidget( gui.appendRadioButton(self.selection_box, "Manual", addToLayout=False), 0, 0) grid.addWidget( gui.appendRadioButton(self.selection_box, "Height ratio:", addToLayout=False), 1, 0) self.cut_ratio_spin = gui.spin(self.selection_box, self, "cut_ratio", 0, 100, step=1e-1, spinType=float, callback=self._selection_method_changed, addToLayout=False) self.cut_ratio_spin.setSuffix("%") grid.addWidget(self.cut_ratio_spin, 1, 1) grid.addWidget( gui.appendRadioButton(self.selection_box, "Top N:", addToLayout=False), 2, 0) self.top_n_spin = gui.spin(self.selection_box, self, "top_n", 1, 20, callback=self._selection_method_changed, addToLayout=False) grid.addWidget(self.top_n_spin, 2, 1) self.zoom_slider = gui.hSlider(self.controlArea, self, "zoom_factor", box="Zoom", minValue=-6, maxValue=3, step=1, ticks=True, createLabel=False, callback=self.__update_font_scale) zoom_in = QAction("Zoom in", self, shortcut=QKeySequence.ZoomIn, triggered=self.__zoom_in) zoom_out = QAction("Zoom out", self, shortcut=QKeySequence.ZoomOut, triggered=self.__zoom_out) zoom_reset = QAction("Reset zoom", self, shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0), triggered=self.__zoom_reset) self.addActions([zoom_in, zoom_out, zoom_reset]) self.controlArea.layout().addStretch() gui.auto_send(self.buttonsArea, self, "autocommit") self.scene = QGraphicsScene(self) self.view = StickyGraphicsView( self.scene, horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff, verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn, alignment=Qt.AlignLeft | Qt.AlignVCenter) self.mainArea.layout().setSpacing(1) self.mainArea.layout().addWidget(self.view) def axis_view(orientation): ax = AxisItem(orientation=orientation, maxTickLength=7) ax.mousePressed.connect(self._activate_cut_line) ax.mouseMoved.connect(self._activate_cut_line) ax.mouseReleased.connect(self._activate_cut_line) ax.setRange(1.0, 0.0) return ax self.top_axis = axis_view("top") self.bottom_axis = axis_view("bottom") self._main_graphics = QGraphicsWidget() scenelayout = QGraphicsGridLayout() scenelayout.setHorizontalSpacing(10) scenelayout.setVerticalSpacing(10) self._main_graphics.setLayout(scenelayout) self.scene.addItem(self._main_graphics) self.dendrogram = DendrogramWidget() self.dendrogram.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.dendrogram.selectionChanged.connect(self._invalidate_output) self.dendrogram.selectionEdited.connect(self._selection_edited) self.labels = TextListWidget() self.labels.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) self.labels.setAlignment(Qt.AlignLeft) self.labels.setMaximumWidth(200) scenelayout.addItem(self.top_axis, 0, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter) scenelayout.addItem(self.dendrogram, 1, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter) scenelayout.addItem(self.labels, 1, 1, alignment=Qt.AlignLeft | Qt.AlignVCenter) scenelayout.addItem(self.bottom_axis, 2, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter) self.view.viewport().installEventFilter(self) self._main_graphics.installEventFilter(self) self.top_axis.setZValue(self.dendrogram.zValue() + 10) self.bottom_axis.setZValue(self.dendrogram.zValue() + 10) self.cut_line = SliderLine(self.top_axis, orientation=Qt.Horizontal) self.cut_line.valueChanged.connect(self._dendrogram_slider_changed) self.dendrogram.geometryChanged.connect(self._dendrogram_geom_changed) self._set_cut_line_visible(self.selection_method == 1) self.__update_font_scale()
class OWGrid(QGraphicsWidget): """Responsive grid layout widget. Manages grid items for various window sizes. Accepts grid items as items. Parameters ---------- parent : QGraphicsWidget Examples -------- >>> grid = OWGrid() It's a good idea to define what you want your grid items to do. For this example, we will make them selectable and zoomable, so we define a class that inherits from both: >>> class MyGridItem(SelectableGridItem, ZoomableGridItem): >>> pass We then take a list of items and wrap them into our new `MyGridItem` instances. >>> items = [QGraphicsRectItem(0, 0, 10, 10)] >>> grid_items = [MyGridItem(i, grid) for i in items] We can then set the items to be displayed >>> grid.set_items(grid_items) """ def __init__(self, parent=None): super().__init__(parent) self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.setContentsMargins(10, 10, 10, 10) self.__layout = QGraphicsGridLayout() self.__layout.setContentsMargins(0, 0, 0, 0) self.__layout.setSpacing(10) self.setLayout(self.__layout) def set_items(self, items): for i, item in enumerate(items): # Place the items in some arbitrary order - they will be rearranged # before user sees this ordering self.__layout.addItem(item, i, 0) def setGeometry(self, rect): super().setGeometry(rect) self.reflow(self.size().width()) def reflow(self, width): """Recalculate the layout and reposition the elements so they fit. Parameters ---------- width : int The maximum width of the grid. Returns ------- """ # When setting the geometry when opened, the layout doesn't yet exist if self.layout() is None: return grid = self.__layout left, right, *_ = self.getContentsMargins() width -= left + right # Get size hints with 32 as the minimum size for each cell widths = [max(64, h.width()) for h in self._hints(Qt.PreferredSize)] ncol = self._fit_n_cols(widths, grid.horizontalSpacing(), width) # The number of columns is already optimal if ncol == grid.columnCount(): return # remove all items from the layout, then re-add them back in updated # positions items = self._items() for item in items: grid.removeItem(item) for i, item in enumerate(items): grid.addItem(item, i // ncol, i % ncol) grid.setAlignment(item, Qt.AlignCenter) def clear(self): for item in self._items(): self.__layout.removeItem(item) item.setParent(None) @staticmethod def _fit_n_cols(widths, spacing, constraint): def sliced(seq, n_col): """Slice the widths into n lists that contain their respective widths. E.g. [5, 5, 5], 2 => [[5, 5], [5]]""" return [seq[i:i + n_col] for i in range(0, len(seq), n_col)] def flow_width(widths, spacing, ncol): w = sliced(widths, ncol) col_widths = map(max, zip_longest(*w, fillvalue=0)) return sum(col_widths) + (ncol - 1) * spacing ncol_best = 1 for ncol in range(2, len(widths) + 1): width = flow_width(widths, spacing, ncol) if width <= constraint: ncol_best = ncol else: break return ncol_best def _items(self): if not self.__layout: return [] return [self.__layout.itemAt(i) for i in range(self.__layout.count())] def _hints(self, which): return [item.sizeHint(which) for item in self._items()]
class GraphicsThumbnailGrid(QGraphicsWidget): class LayoutMode(enum.Enum): FixedColumnCount, AutoReflow = 0, 1 FixedColumnCount, AutoReflow = LayoutMode #: Signal emitted when the current (thumbnail) changes currentThumbnailChanged = Signal(object) def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self.__layoutMode = GraphicsThumbnailGrid.AutoReflow self.__columnCount = -1 self.__thumbnails = [] # type: List[GraphicsThumbnailWidget] #: The current 'focused' thumbnail item. This is the item that last #: received the keyboard focus (though it does not necessarily have #: it now) self.__current = None # type: Optional[GraphicsThumbnailWidget] self.__reflowPending = False self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.setContentsMargins(10, 10, 10, 10) # NOTE: Keeping a reference to the layout. self.layout() # returns a QGraphicsLayout wrapper (i.e. strips the # QGraphicsGridLayout-nes of the object). self.__layout = QGraphicsGridLayout() self.__layout.setContentsMargins(0, 0, 0, 0) self.__layout.setSpacing(10) self.setLayout(self.__layout) def resizeEvent(self, event): super().resizeEvent(event) if event.newSize().width() != event.oldSize().width() and \ self.__layoutMode == GraphicsThumbnailGrid.AutoReflow: self.__reflow() def setGeometry(self, rect): self.prepareGeometryChange() super().setGeometry(rect) def count(self): """ Returns ------- count: int Number of thumbnails in the widget """ return len(self.__thumbnails) def addThumbnail(self, thumbnail): """ Add/append a thumbnail to the widget Parameters ---------- thumbnail: Union[GraphicsThumbnailWidget, QPixmap] The thumbnail to insert """ self.insertThumbnail(self.count(), thumbnail) def insertThumbnail(self, index, thumbnail): """ Insert a new thumbnail into a widget. Raise a ValueError if thumbnail is already in the view. Parameters ---------- index : int Index where to insert thumbnail : Union[GraphicsThumbnailWidget, QPixmap] The thumbnail to insert. GraphicsThumbnailGrid takes ownership of the item. """ if isinstance(thumbnail, QPixmap): thumbnail = GraphicsThumbnailWidget(thumbnail, parentItem=self) elif thumbnail in self.__thumbnails: raise ValueError("{!r} is already inserted".format(thumbnail)) elif not isinstance(thumbnail, GraphicsThumbnailWidget): raise TypeError index = max(min(index, self.count()), 0) moved = self.__takeItemsFrom(index) assert moved == self.__thumbnails[index:] self.__thumbnails.insert(index, thumbnail) self.__appendItems([thumbnail] + moved) thumbnail.setParentItem(self) thumbnail.installEventFilter(self) assert self.count() == self.layout().count() self.__scheduleLayout() def removeThumbnail(self, thumbnail): """ Remove a single thumbnail from the grid. Raise a ValueError if thumbnail is not in the grid. Parameters ---------- thumbnail : GraphicsThumbnailWidget Thumbnail to remove. Items ownership is transferred to the caller. """ index = self.__thumbnails.index(thumbnail) moved = self.__takeItemsFrom(index) del self.__thumbnails[index] assert moved[0] is thumbnail and self.__thumbnails[index:] == moved[1:] self.__appendItems(moved[1:]) thumbnail.removeEventFilter(self) if thumbnail.parentItem() is self: thumbnail.setParentItem(None) if self.__current is thumbnail: self.__current = None self.currentThumbnailChanged.emit(None) assert self.count() == self.layout().count() def thumbnailAt(self, index): """ Return the thumbnail widget at `index` Parameters ---------- index : int Returns ------- thumbnail : GraphicsThumbnailWidget """ return self.__thumbnails[index] def clear(self): """ Remove all thumbnails from the grid. """ removed = self.__takeItemsFrom(0) assert removed == self.__thumbnails self.__thumbnails = [] for thumb in removed: thumb.removeEventFilter(self) if thumb.parentItem() is self: thumb.setParentItem(None) if self.__current is not None: self.__current = None self.currentThumbnailChanged.emit(None) def __takeItemsFrom(self, fromindex): # remove all items starting at fromindex from the layout and # return them # NOTE: Operate on layout only layout = self.__layout taken = [] for i in reversed(range(fromindex, layout.count())): item = layout.itemAt(i) layout.removeAt(i) taken.append(item) return list(reversed(taken)) def __appendItems(self, items): # Append/insert items into the layout at the end # NOTE: Operate on layout only layout = self.__layout columns = max(layout.columnCount(), 1) for i, item in enumerate(items, layout.count()): layout.addItem(item, i // columns, i % columns) def __scheduleLayout(self): if not self.__reflowPending: self.__reflowPending = True QApplication.postEvent(self, QEvent(QEvent.LayoutRequest), Qt.HighEventPriority) def event(self, event): if event.type() == QEvent.LayoutRequest: if self.__layoutMode == GraphicsThumbnailGrid.AutoReflow: self.__reflow() else: self.__gridlayout() if self.parentLayoutItem() is None: sh = self.effectiveSizeHint(Qt.PreferredSize) self.resize(sh) if self.layout(): self.layout().activate() return super().event(event) def setFixedColumnCount(self, count): if count < 0: if self.__layoutMode != GraphicsThumbnailGrid.AutoReflow: self.__layoutMode = GraphicsThumbnailGrid.AutoReflow self.__reflow() else: if self.__layoutMode != GraphicsThumbnailGrid.FixedColumnCount: self.__layoutMode = GraphicsThumbnailGrid.FixedColumnCount if self.__columnCount != count: self.__columnCount = count self.__gridlayout() def __reflow(self): self.__reflowPending = False layout = self.__layout width = self.contentsRect().width() hints = [ item.effectiveSizeHint(Qt.PreferredSize) for item in self.__thumbnails ] widths = [max(24, h.width()) for h in hints] ncol = self._fitncols(widths, layout.horizontalSpacing(), width) self.__relayoutGrid(ncol) def __gridlayout(self): assert self.__layoutMode == GraphicsThumbnailGrid.FixedColumnCount self.__relayoutGrid(self.__columnCount) def __relayoutGrid(self, columnCount): layout = self.__layout if columnCount == layout.columnCount(): return # remove all items from the layout, then re-add them back in # updated positions items = self.__takeItemsFrom(0) for i, item in enumerate(items): layout.addItem(item, i // columnCount, i % columnCount) def items(self): """ Return all thumbnail items. Returns ------- thumbnails : List[GraphicsThumbnailWidget] """ return list(self.__thumbnails) def currentItem(self): """ Return the current (last focused) thumbnail item. """ return self.__current def _fitncols(self, widths, spacing, constraint): def sliced(seq, ncol): return [seq[i:i + ncol] for i in range(0, len(seq), ncol)] def flow_width(widths, spacing, ncol): W = sliced(widths, ncol) col_widths = map(max, zip_longest(*W, fillvalue=0)) return sum(col_widths) + (ncol - 1) * spacing ncol_best = 1 for ncol in range(2, len(widths) + 1): w = flow_width(widths, spacing, ncol) if w <= constraint: ncol_best = ncol else: break return ncol_best def keyPressEvent(self, event): if event.key() in [Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down]: self._moveCurrent(event.key(), event.modifiers()) event.accept() return super().keyPressEvent(event) def eventFilter(self, receiver, event): if isinstance(receiver, GraphicsThumbnailWidget) and \ event.type() == QEvent.FocusIn and \ receiver in self.__thumbnails: self.__current = receiver self.currentThumbnailChanged.emit(receiver) return super().eventFilter(receiver, event) def _moveCurrent(self, key, modifiers=Qt.NoModifier): """ Move the current thumbnail focus (`currentItem`) based on a key press (Qt.Key{Up,Down,Left,Right}) Parameters ---------- key : Qt.Key modifiers : Qt.Modifiers """ current = self.__current layout = self.__layout columns = layout.columnCount() rows = layout.rowCount() itempos = {} for i, j in itertools.product(range(rows), range(columns)): if i * columns + j >= layout.count(): break item = layout.itemAt(i, j) if item is not None: itempos[item] = (i, j) pos = itempos.get(current, None) if pos is None: return False i, j = pos index = i * columns + j if key == Qt.Key_Left: index = index - 1 elif key == Qt.Key_Right: index = index + 1 elif key == Qt.Key_Down: index = index + columns elif key == Qt.Key_Up: index = index - columns index = min(max(index, 0), layout.count() - 1) i = index // columns j = index % columns newcurrent = layout.itemAt(i, j) assert newcurrent is self.__thumbnails[index] if newcurrent is not None: if not modifiers & (Qt.ShiftModifier | Qt.ControlModifier): for item in self.__thumbnails: if item is not newcurrent: item.setSelected(False) # self.scene().clearSelection() newcurrent.setSelected(True) newcurrent.setFocus(Qt.TabFocusReason) newcurrent.ensureVisible() if self.__current is not newcurrent: self.__current = newcurrent self.currentThumbnailChanged.emit(newcurrent)
def replot_experiments(self): """Replot the whole quality plot. """ self.scene.clear() labels = [] max_dist = numpy.nanmax(list(filter(None, self.distances))) rug_widgets = [] group_pen = QPen(Qt.black) group_pen.setWidth(2) group_pen.setCapStyle(Qt.RoundCap) background_pen = QPen(QColor(0, 0, 250, 150)) background_pen.setWidth(1) background_pen.setCapStyle(Qt.RoundCap) main_widget = QGraphicsWidget() layout = QGraphicsGridLayout() attributes = self.data.domain.attributes if self.data is not None: for (group, indices), dist_vec in zip(self.groups, self.distances): indices_set = set(indices) rug_items = [] if dist_vec is not None: for i, attr in enumerate(attributes): # Is this a within group distance or background in_group = i in indices_set if in_group: rug_item = ClickableRugItem(dist_vec[i] / max_dist, 1.0, self.on_rug_item_clicked) rug_item.setPen(group_pen) tooltip = experiment_description(attr) rug_item.setToolTip(tooltip) rug_item.group_index = indices.index(i) rug_item.setZValue(rug_item.zValue() + 1) else: rug_item = ClickableRugItem(dist_vec[i] / max_dist, 0.85, self.on_rug_item_clicked) rug_item.setPen(background_pen) tooltip = experiment_description(attr) rug_item.setToolTip(tooltip) rug_item.group = group rug_item.index = i rug_item.in_group = in_group rug_items.append(rug_item) rug_widget = RugGraphicsWidget(parent=main_widget) rug_widget.set_rug(rug_items) rug_widgets.append(rug_widget) label = group_label(self.selected_split_by_labels(), group) label_item = QGraphicsSimpleTextItem(label, main_widget) label_item = GraphicsSimpleTextLayoutItem(label_item, parent=layout) label_item.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) labels.append(label_item) for i, (label, rug_w) in enumerate(zip(labels, rug_widgets)): layout.addItem(label, i, 0, Qt.AlignVCenter) layout.addItem(rug_w, i, 1) layout.setRowMaximumHeight(i, 30) main_widget.setLayout(layout) self.scene.addItem(main_widget) self.main_widget = main_widget self.rug_widgets = rug_widgets self.labels = labels self.on_view_resize(self.scene_view.size())
class OWDistanceMap(widget.OWWidget): name = "距离图(Distance Map)" description = "可视化距离矩阵" icon = "icons/DistanceMap.svg" priority = 1200 keywords = ['juliyingshe', 'yingshe', 'julitu'] category = '非监督(Unsupervised)' class Inputs: distances = Input("距离(Distances)", Orange.misc.DistMatrix, replaces=['Distances']) class Outputs: selected_data = Output("选定的数据(Selected Data)", Orange.data.Table, default=True, replaces=['Selected Data']) annotated_data = Output("数据(Data)", Orange.data.Table, replaces=['Data']) features = Output("特征(Features)", widget.AttributeList, dynamic=False, replaces=['Features']) settingsHandler = settings.PerfectDomainContextHandler() #: type of ordering to apply to matrix rows/columns NoOrdering, Clustering, OrderedClustering = 0, 1, 2 sorting = settings.Setting(NoOrdering) palette_name = settings.Setting(colorpalettes.DefaultContinuousPaletteName) color_gamma = settings.Setting(0.0) color_low = settings.Setting(0.0) color_high = settings.Setting(1.0) annotation_idx = settings.ContextSetting(0) pending_selection = settings.Setting(None, schema_only=True) autocommit = settings.Setting(True) graph_name = "grid_widget" # Disable clustering for inputs bigger than this _MaxClustering = 25000 # Disable cluster leaf ordering for inputs bigger than this _MaxOrderedClustering = 2000 def __init__(self): super().__init__() self.matrix = None self._matrix_range = 0. self._tree = None self._ordered_tree = None self._sorted_matrix = None self._sort_indices = None self._selection = None self.sorting_cb = gui.comboBox( self.controlArea, self, "sorting", box="元素排序", items=["无", "聚类(Clustering)", "有序叶聚类"], callback=self._invalidate_ordering) box = gui.vBox(self.controlArea, "颜色") self.color_map_widget = cmw = ColorGradientSelection( thresholds=(self.color_low, self.color_high), ) model = itemmodels.ContinuousPalettesModel(parent=self) cmw.setModel(model) idx = cmw.findData(self.palette_name, model.KeyRole) if idx != -1: cmw.setCurrentIndex(idx) cmw.activated.connect(self._update_color) def _set_thresholds(low, high): self.color_low, self.color_high = low, high self._update_color() cmw.thresholdsChanged.connect(_set_thresholds) box.layout().addWidget(self.color_map_widget) self.annot_combo = gui.comboBox( self.controlArea, self, "annotation_idx", box="注释", contentsLength=12, searchable=True, callback=self._invalidate_annotations ) self.annot_combo.setModel(itemmodels.VariableListModel()) self.annot_combo.model()[:] = ["无", "枚举"] gui.rubber(self.controlArea) gui.auto_send(self.buttonsArea, self, "autocommit") self.view = GraphicsView(background=None) self.mainArea.layout().addWidget(self.view) self.grid_widget = pg.GraphicsWidget() self.grid = QGraphicsGridLayout() self.grid_widget.setLayout(self.grid) self.gradient_legend = GradientLegendWidget(0, 1, self._color_map()) self.gradient_legend.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) self.gradient_legend.setMaximumWidth(250) self.grid.addItem(self.gradient_legend, 0, 1) self.viewbox = pg.ViewBox(enableMouse=False, enableMenu=False) self.viewbox.setAcceptedMouseButtons(Qt.NoButton) self.viewbox.setAcceptHoverEvents(False) self.grid.addItem(self.viewbox, 2, 1) self.left_dendrogram = DendrogramWidget( self.grid_widget, orientation=DendrogramWidget.Left, selectionMode=DendrogramWidget.NoSelection, hoverHighlightEnabled=False ) self.left_dendrogram.setAcceptedMouseButtons(Qt.NoButton) self.left_dendrogram.setAcceptHoverEvents(False) self.top_dendrogram = DendrogramWidget( self.grid_widget, orientation=DendrogramWidget.Top, selectionMode=DendrogramWidget.NoSelection, hoverHighlightEnabled=False ) self.top_dendrogram.setAcceptedMouseButtons(Qt.NoButton) self.top_dendrogram.setAcceptHoverEvents(False) self.grid.addItem(self.left_dendrogram, 2, 0) self.grid.addItem(self.top_dendrogram, 1, 1) self.right_labels = TextList( alignment=Qt.AlignLeft | Qt.AlignVCenter, sizePolicy=QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) ) self.bottom_labels = TextList( orientation=Qt.Horizontal, alignment=Qt.AlignRight | Qt.AlignVCenter, sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) ) self.grid.addItem(self.right_labels, 2, 2) self.grid.addItem(self.bottom_labels, 3, 1) self.view.setCentralItem(self.grid_widget) self.gradient_legend.hide() self.left_dendrogram.hide() self.top_dendrogram.hide() self.right_labels.hide() self.bottom_labels.hide() self.matrix_item = None self.dendrogram = None self.settingsAboutToBePacked.connect(self.pack_settings) def pack_settings(self): if self.matrix_item is not None: self.pending_selection = self.matrix_item.selections() else: self.pending_selection = None @Inputs.distances def set_distances(self, matrix): self.closeContext() self.clear() self.error() if matrix is not None: N, _ = matrix.shape if N < 2: self.error("Empty distance matrix.") matrix = None self.matrix = matrix if matrix is not None: self._matrix_range = numpy.nanmax(matrix) self.set_items(matrix.row_items, matrix.axis) else: self._matrix_range = 0. self.set_items(None) if matrix is not None: N, _ = matrix.shape else: N = 0 model = self.sorting_cb.model() item = model.item(2) msg = None if N > OWDistanceMap._MaxOrderedClustering: item.setFlags(item.flags() & ~Qt.ItemIsEnabled) if self.sorting == OWDistanceMap.OrderedClustering: self.sorting = OWDistanceMap.Clustering msg = "Cluster ordering was disabled due to the input " \ "matrix being to big" else: item.setFlags(item.flags() | Qt.ItemIsEnabled) item = model.item(1) if N > OWDistanceMap._MaxClustering: item.setFlags(item.flags() & ~Qt.ItemIsEnabled) if self.sorting == OWDistanceMap.Clustering: self.sorting = OWDistanceMap.NoOrdering msg = "Clustering was disabled due to the input " \ "matrix being to big" else: item.setFlags(item.flags() | Qt.ItemIsEnabled) self.information(msg) def set_items(self, items, axis=1): self.items = items model = self.annot_combo.model() if items is None: model[:] = ["无", "枚举"] elif not axis: model[:] = ["无", "枚举", "Attribute names"] elif isinstance(items, Orange.data.Table): annot_vars = list(filter_visible(items.domain.variables)) + list(items.domain.metas) model[:] = ["无", "枚举"] + annot_vars self.annotation_idx = 0 self.openContext(items.domain) elif isinstance(items, list) and \ all(isinstance(item, Orange.data.Variable) for item in items): model[:] = ["无", "枚举", "Name"] else: model[:] = ["无", "枚举"] self.annotation_idx = min(self.annotation_idx, len(model) - 1) def clear(self): self.matrix = None self._tree = None self._ordered_tree = None self._sorted_matrix = None self._selection = [] self._clear_plot() def handleNewSignals(self): if self.matrix is not None: self._update_ordering() self._setup_scene() self._update_labels() if self.pending_selection is not None: self.matrix_item.set_selections(self.pending_selection) self.pending_selection = None self.commit.now() def _clear_plot(self): def remove(item): item.setParentItem(None) item.scene().removeItem(item) if self.matrix_item is not None: self.matrix_item.selectionChanged.disconnect( self._invalidate_selection) remove(self.matrix_item) self.matrix_item = None self._set_displayed_dendrogram(None) self._set_labels(None) self.gradient_legend.hide() def _cluster_tree(self): if self._tree is None: self._tree = hierarchical.dist_matrix_clustering(self.matrix) return self._tree def _ordered_cluster_tree(self): if self._ordered_tree is None: tree = self._cluster_tree() self._ordered_tree = \ hierarchical.optimal_leaf_ordering(tree, self.matrix) return self._ordered_tree def _setup_scene(self): self._clear_plot() self.matrix_item = DistanceMapItem(self._sorted_matrix) # Scale the y axis to compensate for pg.ViewBox's y axis invert self.matrix_item.setTransform(QTransform.fromScale(1, -1), ) self.viewbox.addItem(self.matrix_item) # Set fixed view box range. h, w = self._sorted_matrix.shape self.viewbox.setRange(QRectF(0, -h, w, h), padding=0) self.matrix_item.selectionChanged.connect(self._invalidate_selection) if self.sorting == OWDistanceMap.NoOrdering: tree = None elif self.sorting == OWDistanceMap.Clustering: tree = self._cluster_tree() elif self.sorting == OWDistanceMap.OrderedClustering: tree = self._ordered_cluster_tree() self._set_displayed_dendrogram(tree) self._update_color() def _set_displayed_dendrogram(self, root): self.left_dendrogram.set_root(root) self.top_dendrogram.set_root(root) self.left_dendrogram.setVisible(root is not None) self.top_dendrogram.setVisible(root is not None) constraint = 0 if root is None else -1 # 150 self.left_dendrogram.setMaximumWidth(constraint) self.top_dendrogram.setMaximumHeight(constraint) def _invalidate_ordering(self): self._sorted_matrix = None if self.matrix is not None: self._update_ordering() self._setup_scene() self._update_labels() self._invalidate_selection() def _update_ordering(self): if self.sorting == OWDistanceMap.NoOrdering: self._sorted_matrix = self.matrix self._sort_indices = None else: if self.sorting == OWDistanceMap.Clustering: tree = self._cluster_tree() elif self.sorting == OWDistanceMap.OrderedClustering: tree = self._ordered_cluster_tree() leaves = hierarchical.leaves(tree) indices = numpy.array([leaf.value.index for leaf in leaves]) X = self.matrix self._sorted_matrix = X[indices[:, numpy.newaxis], indices[numpy.newaxis, :]] self._sort_indices = indices def _invalidate_annotations(self): if self.matrix is not None: self._update_labels() def _update_labels(self, ): if self.annotation_idx == 0: # None labels = None elif self.annotation_idx == 1: # Enumeration labels = [str(i + 1) for i in range(self.matrix.shape[0])] elif self.annot_combo.model()[self.annotation_idx] == "Attribute names": attr = self.matrix.row_items.domain.attributes labels = [str(attr[i]) for i in range(self.matrix.shape[0])] elif self.annotation_idx == 2 and \ isinstance(self.items, widget.AttributeList): labels = [v.name for v in self.items] elif isinstance(self.items, Orange.data.Table): var = self.annot_combo.model()[self.annotation_idx] column, _ = self.items.get_column_view(var) labels = [var.str_val(value) for value in column] self._set_labels(labels) def _set_labels(self, labels): self._labels = labels if labels and self.sorting != OWDistanceMap.NoOrdering: sortind = self._sort_indices labels = [labels[i] for i in sortind] for textlist in [self.right_labels, self.bottom_labels]: textlist.setItems(labels or []) textlist.setVisible(bool(labels)) constraint = -1 if labels else 0 self.right_labels.setMaximumWidth(constraint) self.bottom_labels.setMaximumHeight(constraint) def _color_map(self) -> GradientColorMap: palette = self.color_map_widget.currentData() return GradientColorMap( palette.lookup_table(), thresholds=(self.color_low, max(self.color_high, self.color_low)), span=(0., self._matrix_range)) def _update_color(self): palette = self.color_map_widget.currentData() self.palette_name = palette.name if self.matrix_item: cmap = self._color_map().replace(span=(0., 1.)) colors = cmap.apply(numpy.arange(256) / 255.) self.matrix_item.setLookupTable(colors) self.gradient_legend.show() self.gradient_legend.setRange(0, self._matrix_range) self.gradient_legend.setColorMap(self._color_map()) def _invalidate_selection(self): ranges = self.matrix_item.selections() ranges = reduce(iadd, ranges, []) indices = reduce(iadd, ranges, []) if self.sorting != OWDistanceMap.NoOrdering: sortind = self._sort_indices indices = [sortind[i] for i in indices] self._selection = list(sorted(set(indices))) self.commit.deferred() @gui.deferred def commit(self): datasubset = None featuresubset = None if not self._selection: pass elif isinstance(self.items, Orange.data.Table): indices = self._selection if self.matrix.axis == 1: datasubset = self.items.from_table_rows(self.items, indices) elif self.matrix.axis == 0: domain = Orange.data.Domain( [self.items.domain[i] for i in indices], self.items.domain.class_vars, self.items.domain.metas) datasubset = self.items.transform(domain) elif isinstance(self.items, widget.AttributeList): subset = [self.items[i] for i in self._selection] featuresubset = widget.AttributeList(subset) self.Outputs.selected_data.send(datasubset) self.Outputs.annotated_data.send(create_annotated_table(self.items, self._selection)) self.Outputs.features.send(featuresubset) def onDeleteWidget(self): super().onDeleteWidget() self.clear() def send_report(self): annot = self.annot_combo.currentText() if self.annotation_idx <= 1: annot = annot.lower() self.report_items(( ("Sorting", self.sorting_cb.currentText().lower()), ("Annotations", annot) )) if self.matrix is not None: self.report_plot()