From 12b39c44599821489820b6e9a9b2b3ac46391433 Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Thu, 28 May 2020 07:34:28 +0200 Subject: [PATCH 1/8] Projection plots: Customize labels --- Orange/widgets/visualize/owfreeviz.py | 2 + .../widgets/visualize/owlinearprojection.py | 2 + Orange/widgets/visualize/owradviz.py | 1 + Orange/widgets/visualize/owscatterplot.py | 33 ++- .../widgets/visualize/owscatterplotgraph.py | 172 ++++++++------- .../visualize/tests/test_owscatterplotbase.py | 7 + Orange/widgets/visualize/utils/component.py | 28 ++- .../visualize/utils/customizableplot.py | 200 ++++++++++++++++++ Orange/widgets/visualize/utils/plotutils.py | 98 ++++++++- .../visualize/utils/tests/test_plotutils.py | 14 ++ Orange/widgets/visualize/utils/widget.py | 7 + 11 files changed, 485 insertions(+), 79 deletions(-) create mode 100644 Orange/widgets/visualize/utils/customizableplot.py create mode 100644 Orange/widgets/visualize/utils/tests/test_plotutils.py diff --git a/Orange/widgets/visualize/owfreeviz.py b/Orange/widgets/visualize/owfreeviz.py index 34d583fb803..180cdbc168f 100644 --- a/Orange/widgets/visualize/owfreeviz.py +++ b/Orange/widgets/visualize/owfreeviz.py @@ -90,6 +90,7 @@ def update_anchors(self): anchor = AnchorItem(line=QLineF(0, 0, *point), text=label) anchor.setVisible(np.linalg.norm(point) > r) anchor.setPen(pg.mkPen((100, 100, 100))) + anchor.setFont(self.anchor_font) self.plot_widget.addItem(anchor) self.anchor_items.append(anchor) else: @@ -97,6 +98,7 @@ def update_anchors(self): anchor.setLine(QLineF(0, 0, *point)) anchor.setText(label) anchor.setVisible(np.linalg.norm(point) > r) + anchor.setFont(self.anchor_font) def update_circle(self): super().update_circle() diff --git a/Orange/widgets/visualize/owlinearprojection.py b/Orange/widgets/visualize/owlinearprojection.py index 54356234719..4d3bf0315fd 100644 --- a/Orange/widgets/visualize/owlinearprojection.py +++ b/Orange/widgets/visualize/owlinearprojection.py @@ -225,6 +225,7 @@ def update_anchors(self): anchor._label.setToolTip(f"{label}") label = label[:MAX_LABEL_LEN - 3] + "..." if len(label) > MAX_LABEL_LEN else label anchor.setText(label) + anchor.setFont(self.anchor_font) visible = self.always_show_axes or np.linalg.norm(point) > r anchor.setVisible(visible) @@ -236,6 +237,7 @@ def update_anchors(self): anchor.setLine(QLineF(0, 0, *point)) visible = self.always_show_axes or np.linalg.norm(point) > r anchor.setVisible(visible) + anchor.setFont(self.anchor_font) def update_circle(self): super().update_circle() diff --git a/Orange/widgets/visualize/owradviz.py b/Orange/widgets/visualize/owradviz.py index c97a6ce2354..180bc494780 100644 --- a/Orange/widgets/visualize/owradviz.py +++ b/Orange/widgets/visualize/owradviz.py @@ -264,6 +264,7 @@ def update_anchors(self): label = label[:MAX_LABEL_LEN - 3] + "..." anchor.setText(label) + anchor.setFont(self.anchor_font) label_len = min(MAX_LABEL_LEN, len(label)) anchor.setColor(QColor(0, 0, 0)) diff --git a/Orange/widgets/visualize/owscatterplot.py b/Orange/widgets/visualize/owscatterplot.py index f4735de27df..0c41c683f48 100644 --- a/Orange/widgets/visualize/owscatterplot.py +++ b/Orange/widgets/visualize/owscatterplot.py @@ -1,3 +1,4 @@ +import copy from itertools import chain from xml.sax.saxutils import escape @@ -22,8 +23,10 @@ Setting, ContextSetting, SettingProvider, IncompatibleContext) from Orange.widgets.utils.itemmodels import DomainModel from Orange.widgets.utils.widgetpreview import WidgetPreview -from Orange.widgets.visualize.owscatterplotgraph import OWScatterPlotBase +from Orange.widgets.visualize.owscatterplotgraph import OWScatterPlotBase, \ + ParameterSetter as Setter from Orange.widgets.visualize.utils import VizRankDialogAttrPair +from Orange.widgets.visualize.utils.customizableplot import Updater from Orange.widgets.visualize.utils.widget import OWDataProjectionWidget from Orange.widgets.widget import AttributeList, Msg, Input, Output @@ -95,7 +98,33 @@ def score_heuristic(self): return [a for _, a in attrs] -class OWScatterPlotGraph(OWScatterPlotBase): +class ParameterSetter(Setter): + initial_settings = copy.deepcopy(Setter.initial_settings) + initial_settings[Setter.LABELS_BOX].update({ + Setter.AXIS_TITLE_LABEL: Updater.FONT_SETTING, + Setter.AXIS_TICKS_LABEL: Updater.FONT_SETTING + }) + + def __init__(self): + super().__init__() + + def update_axes_titles(**settings): + Updater.update_axes_titles_font(self.axis_items, **settings) + + def update_axes_ticks(**settings): + Updater.update_axes_ticks_font(self.axis_items, **settings) + + labels = self.LABELS_BOX + self.setters[labels][self.AXIS_TITLE_LABEL] = update_axes_titles + self.setters[labels][self.AXIS_TICKS_LABEL] = update_axes_ticks + + @property + def axis_items(self): + return [value["item"] for value in + self.plot_widget.plotItem.axes.values()] + + +class OWScatterPlotGraph(OWScatterPlotBase, ParameterSetter): show_reg_line = Setting(False) orthonormal_regression = Setting(False) diff --git a/Orange/widgets/visualize/owscatterplotgraph.py b/Orange/widgets/visualize/owscatterplotgraph.py index 51343bae0ff..4fe4a616e90 100644 --- a/Orange/widgets/visualize/owscatterplotgraph.py +++ b/Orange/widgets/visualize/owscatterplotgraph.py @@ -9,19 +9,14 @@ import numpy as np from AnyQt.QtCore import Qt, QRectF, QSize, QTimer, pyqtSignal as Signal, \ QObject -from AnyQt.QtGui import ( - QStaticText, QColor, QPen, QBrush, QPainterPath, QTransform, QPainter -) -from AnyQt.QtWidgets import ( - QApplication, QToolTip, QGraphicsTextItem, QGraphicsRectItem -) +from AnyQt.QtGui import QColor, QPen, QBrush, QPainterPath, QTransform, \ + QPainter, QFont +from AnyQt.QtWidgets import QApplication, QToolTip, QGraphicsTextItem, \ + QGraphicsRectItem import pyqtgraph as pg -import pyqtgraph.functions as fn -from pyqtgraph.graphicsItems.ScatterPlotItem import Symbols, drawSymbol -from pyqtgraph.graphicsItems.LegendItem import ( - LegendItem as PgLegendItem, ItemSample -) +from pyqtgraph.graphicsItems.ScatterPlotItem import Symbols +from pyqtgraph.graphicsItems.LegendItem import LegendItem as PgLegendItem from pyqtgraph.graphicsItems.TextItem import TextItem from Orange.preprocess.discretize import _time_binnings @@ -34,9 +29,11 @@ from Orange.widgets.visualize.owscatterplotgraph_obsolete import ( OWScatterPlotGraph as OWScatterPlotGraphObs ) +from Orange.widgets.visualize.utils.customizableplot import Updater, \ + BaseParameterSetter as Setter from Orange.widgets.visualize.utils.plotutils import ( - HelpEventDelegate as EventDelegate, - InteractiveViewBox as ViewBox + HelpEventDelegate as EventDelegate, InteractiveViewBox as ViewBox, + PaletteItemSample, SymbolItemSample, AxisItem ) @@ -44,64 +41,6 @@ MAX_N_VALID_SIZE_ANIMATE = 1000 -class PaletteItemSample(ItemSample): - """A color strip to insert into legends for discretized continuous values""" - - def __init__(self, palette, scale, label_formatter=None): - """ - :param palette: palette used for showing continuous values - :type palette: BinnedContinuousPalette - :param scale: an instance of DiscretizedScale that defines the - conversion of values into bins - :type scale: DiscretizedScale - """ - super().__init__(None) - self.palette = palette - self.scale = scale - if label_formatter is None: - label_formatter = "{{:.{}f}}".format(scale.decimals).format - cuts = [label_formatter(scale.offset + i * scale.width) - for i in range(scale.bins + 1)] - self.labels = [QStaticText("{} - {}".format(fr, to)) - for fr, to in zip(cuts, cuts[1:])] - font = self.font() - font.setPixelSize(11) - for label in self.labels: - label.prepare(font=font) - self.text_width = max(label.size().width() for label in self.labels) - - def boundingRect(self): - return QRectF(0, 0, 40 + self.text_width, 20 + self.scale.bins * 15) - - def paint(self, p, *args): - p.setRenderHint(p.Antialiasing) - p.translate(5, 5) - font = p.font() - font.setPixelSize(11) - p.setFont(font) - colors = self.palette.qcolors - for i, color, label in zip(itertools.count(), colors, self.labels): - p.setPen(Qt.NoPen) - p.setBrush(QBrush(color)) - p.drawRect(0, i * 15, 15, 15) - p.setPen(QPen(Qt.black)) - p.drawStaticText(20, i * 15 + 1, label) - - -class SymbolItemSample(ItemSample): - """Adjust position for symbols""" - def __init__(self, pen, brush, size, symbol): - super().__init__(None) - self.__pen = fn.mkPen(pen) - self.__brush = fn.mkBrush(brush) - self.__size = size - self.__symbol = symbol - - def paint(self, p, *args): - p.translate(8, 12) - drawSymbol(p, self.__symbol, self.__size, self.__pen, self.__brush) - - class LegendItem(PgLegendItem): def __init__(self, size=None, offset=None, pen=None, brush=None): super().__init__(size, offset) @@ -328,7 +267,7 @@ def _make_pen(color, width): return p -class AxisItem(pg.AxisItem): +class AxisItem(AxisItem): """ Axis that if needed displays ticks appropriate for time data. """ @@ -399,7 +338,89 @@ def tickStrings(self, values, scale, spacing): for x in values] -class OWScatterPlotBase(gui.OWComponent, QObject): +class ParameterSetter(Setter): + CAT_LEGEND_LABEL = "Categorical legend" + NUM_LEGEND_LABEL = "Numerical legend" + NUM_LEGEND_SETTING = { + Updater.SIZE_LABEL: (range(4, 50), 11), + Updater.IS_ITALIC_LABEL: (None, False), + } + + initial_settings = { + Setter.LABELS_BOX: { + Setter.FONT_FAMILY_LABEL: Updater.FONT_FAMILY_SETTING, + Setter.TITLE_LABEL: Updater.FONT_SETTING, + Setter.LABEL_LABEL: Updater.FONT_SETTING, + CAT_LEGEND_LABEL: Updater.FONT_SETTING, + NUM_LEGEND_LABEL: NUM_LEGEND_SETTING, + }, + Setter.ANNOT_BOX: { + Setter.TITLE_LABEL: {Setter.TITLE_LABEL: ("", "")}, + } + } + + def __init__(self): + self.label_font = QFont() + self.cat_legend_settings = {} + self.num_legend_settings = {} + + def update_font_family(**settings): + for label, setter in self.setters[self.LABELS_BOX].items(): + if label != self.FONT_FAMILY_LABEL: + setter(**settings) + + def update_title(**settings): + Updater.update_plot_title_font(self.title_item, **settings) + + def update_label(**settings): + self.label_font = Updater.change_font(self.label_font, settings) + Updater.update_label_font(self.labels, self.label_font) + + def update_cat_legend(**settings): + self.cat_legend_settings.update(**settings) + Updater.update_legend_font(self.cat_legend_items, **settings) + + def update_num_legend(**settings): + self.num_legend_settings.update(**settings) + Updater.update_num_legend_font(self.num_legend, **settings) + + def update_title_text(**settings): + Updater.update_plot_title_text( + self.title_item, settings[self.TITLE_LABEL]) + + self._setters = { + self.LABELS_BOX: { + self.FONT_FAMILY_LABEL: update_font_family, + self.TITLE_LABEL: update_title, + self.LABEL_LABEL: update_label, + self.CAT_LEGEND_LABEL: update_cat_legend, + self.NUM_LEGEND_LABEL: update_num_legend, + }, + self.ANNOT_BOX: { + self.TITLE_LABEL: update_title_text, + } + } + + @property + def title_item(self): + return self.plot_widget.getPlotItem().titleLabel + + @property + def cat_legend_items(self): + items = self.color_legend.items + if items and items[0] and isinstance(items[0][0], PaletteItemSample): + items = [] + return itertools.chain(self.shape_legend.items, items) + + @property + def num_legend(self): + items = self.color_legend.items + if items and items[0] and isinstance(items[0][0], PaletteItemSample): + return self.color_legend + return None + + +class OWScatterPlotBase(gui.OWComponent, QObject, ParameterSetter): """ Provide a graph component for widgets that show any kind of point plot @@ -1257,6 +1278,7 @@ def update_labels(self): ti.setPos(xp, yp) self.plot_widget.addItem(ti) self.labels.append(ti) + ti.setFont(self.label_font) def _signal_too_many_labels(self, too_many): if self._too_many_labels != too_many: @@ -1423,6 +1445,10 @@ def update_legends(self): else: self._update_color_legend(color_labels) self.update_legend_visibility() + Updater.update_legend_font(self.cat_legend_items, + **self.cat_legend_settings) + Updater.update_num_legend_font(self.num_legend, + **self.num_legend_settings) def _update_shape_legend(self, labels): self.shape_legend.clear() diff --git a/Orange/widgets/visualize/tests/test_owscatterplotbase.py b/Orange/widgets/visualize/tests/test_owscatterplotbase.py index 4c3dacfde26..3a05168497d 100644 --- a/Orange/widgets/visualize/tests/test_owscatterplotbase.py +++ b/Orange/widgets/visualize/tests/test_owscatterplotbase.py @@ -1540,6 +1540,13 @@ def test_self_data(this, *_, **_1): np.testing.assert_equal(x, np.arange(10, 15)) np.testing.assert_equal(y, np.arange(20, 25)) + def test_scalable_axis_item(self): + from pyqtgraph import __version__ + # When upgraded to 0.11.1 check if resizing AxisItem font size works + # and overwritten functions generateDrawSpecs and _updateMaxTextSize + # in AxisItem (owscatterplotgraph) can be removed. + self.assertLess(__version__, "0.11.1") + if __name__ == "__main__": import unittest diff --git a/Orange/widgets/visualize/utils/component.py b/Orange/widgets/visualize/utils/component.py index a34cdd811f9..edfcd78d614 100644 --- a/Orange/widgets/visualize/utils/component.py +++ b/Orange/widgets/visualize/utils/component.py @@ -1,16 +1,38 @@ """Common gui.OWComponent components.""" +import copy + from AnyQt.QtCore import Qt, QRectF -from AnyQt.QtGui import QColor +from AnyQt.QtGui import QColor, QFont from AnyQt.QtWidgets import QGraphicsEllipseItem import pyqtgraph as pg -from Orange.widgets.visualize.owscatterplotgraph import OWScatterPlotBase +from Orange.widgets.visualize.owscatterplotgraph import ( + OWScatterPlotBase, ParameterSetter as Setter +) +from Orange.widgets.visualize.utils.customizableplot import Updater from Orange.widgets.visualize.utils.plotutils import ( MouseEventDelegate, DraggableItemsViewBox ) -class OWGraphWithAnchors(OWScatterPlotBase): +class ParameterSetter(Setter): + ANCHOR_LABEL = "Anchor" + initial_settings = copy.deepcopy(Setter.initial_settings) + initial_settings[Setter.LABELS_BOX].update({ + ANCHOR_LABEL: Updater.FONT_SETTING + }) + + def __init__(self): + def update_anchors(**settings): + self.anchor_font = Updater.change_font(self.anchor_font, settings) + self.update_anchors() + + super().__init__() + self.anchor_font = QFont() + self.setters[self.LABELS_BOX][self.ANCHOR_LABEL] = update_anchors + + +class OWGraphWithAnchors(OWScatterPlotBase, ParameterSetter): """ Graph for projections in which dimensions can be manually moved diff --git a/Orange/widgets/visualize/utils/customizableplot.py b/Orange/widgets/visualize/utils/customizableplot.py new file mode 100644 index 00000000000..3f9bff7c588 --- /dev/null +++ b/Orange/widgets/visualize/utils/customizableplot.py @@ -0,0 +1,200 @@ +import sys +from typing import Tuple, List, Dict, Callable, Iterable + +from AnyQt.QtGui import QFont, QFontDatabase +from AnyQt.QtWidgets import QApplication + +import pyqtgraph as pg +from pyqtgraph.graphicsItems.LegendItem import ItemSample + +from orangewidget.utils.visual_settings_dlg import KeyType, ValueType, \ + SettingsType + +_SettingType = Dict[str, ValueType] +_LegendItemType = Tuple[ItemSample, pg.LabelItem] + + +def available_font_families() -> List: + """ + Function returns list of available font families. + Can be used to instantiate font combo boxes. + + Returns + ------- + fonts: list + List of available font families. + """ + if not QApplication.instance(): + _ = QApplication(sys.argv) + return QFontDatabase().families() + + +def default_font_family() -> str: + """ + Function returns default font family used in Qt application. + Can be used to instantiate initial dialog state. + + Returns + ------- + font: str + Default font family. + """ + if not QApplication.instance(): + _ = QApplication(sys.argv) + return QFont().family() + + +def default_font_size() -> int: + """ + Function returns default font size in points used in Qt application. + Can be used to instantiate initial dialog state. + + Returns + ------- + size: int + Default font size in points. + """ + if not QApplication.instance(): + _ = QApplication(sys.argv) + return QFont().pointSize() + + +class Updater: + """ Class with helper functions and constants. """ + FONT_FAMILY_LABEL, SIZE_LABEL, IS_ITALIC_LABEL = \ + "Font family", "Font size", "Italic" + FONT_FAMILY_SETTING: SettingsType = { + FONT_FAMILY_LABEL: (available_font_families(), default_font_family()), + } + FONT_SETTING: SettingsType = { + SIZE_LABEL: (range(4, 50), default_font_size()), + IS_ITALIC_LABEL: (None, False) + } + + @staticmethod + def update_plot_title_text(title_item: pg.LabelItem, text: str): + title_item.text = text + title_item.setVisible(bool(text)) + title_item.item.setPlainText(text) + Updater.plot_title_resize(title_item) + + @staticmethod + def update_plot_title_font(title_item: pg.LabelItem, + **settings: _SettingType): + font = Updater.change_font(title_item.item.font(), settings) + title_item.item.setFont(font) + title_item.item.setPlainText(title_item.text) + Updater.plot_title_resize(title_item) + + @staticmethod + def plot_title_resize(title_item): + height = title_item.item.boundingRect().height() + 6 \ + if title_item.text else 0 + title_item.setMaximumHeight(height) + title_item.parentItem().layout.setRowFixedHeight(0, height) + title_item.resizeEvent(None) + + @staticmethod + def update_axis_title_text(item: pg.AxisItem, text: str): + item.setLabel(text) + item.resizeEvent(None) + + @staticmethod + def update_axes_titles_font(items: List[pg.AxisItem], + **settings: _SettingType): + for item in items: + font = Updater.change_font(item.label.font(), settings) + item.label.setFont(font) + fstyle = ["normal", "italic"][font.italic()] + style = {"font-size": f"{font.pointSize()}pt", + "font-family": f"{font.family()}", + "font-style": f"{fstyle}"} + item.setLabel(None, None, None, **style) + + @staticmethod + def update_axes_ticks_font(items: List[pg.AxisItem], + **settings: _SettingType): + for item in items: + font = item.style["tickFont"] or QFont() + # remove when contained in setTickFont() - version 0.11.0 + item.style['tickFont'] = font + item.setTickFont(Updater.change_font(font, settings)) + + @staticmethod + def update_legend_font(items: Iterable[_LegendItemType], + **settings: _SettingType): + for sample, label in items: + sample.setFixedHeight(sample.height()) + sample.setFixedWidth(sample.width()) + label.item.setFont(Updater.change_font(label.item.font(), settings)) + bounds = label.itemRect() + label.setMaximumWidth(bounds.width()) + label.setMaximumHeight(bounds.height()) + label.updateMin() + label.resizeEvent(None) + label.updateGeometry() + + @staticmethod + def update_num_legend_font(legend: pg.LegendItem, + **settings: _SettingType): + if not legend: + return + for sample, label in legend.items: + sample.set_font(Updater.change_font(sample.font, settings)) + legend.setGeometry(sample.boundingRect()) + + @staticmethod + def update_label_font(items: List[pg.TextItem], font: QFont): + for item in items: + item.setFont(font) + + @staticmethod + def change_font(font: QFont, settings: _SettingType) -> QFont: + assert all(s in (Updater.FONT_FAMILY_LABEL, Updater.SIZE_LABEL, + Updater.IS_ITALIC_LABEL) for s in settings), settings + + family = settings.get(Updater.FONT_FAMILY_LABEL) + if family is not None: + font.setFamily(family) + size = settings.get(Updater.SIZE_LABEL) + if size is not None: + font.setPointSize(size) + italic = settings.get(Updater.IS_ITALIC_LABEL) + if italic is not None: + font.setItalic(italic) + return font + + +class BaseParameterSetter: + """ Subclass to add 'setter' functionality to a plot. """ + LABELS_BOX = "Fonts" + ANNOT_BOX = "Annotations" + + FONT_FAMILY_LABEL = "Font family" + AXIS_TITLE_LABEL = "Axis title" + AXIS_TICKS_LABEL = "Axis ticks" + LEGEND_LABEL = "Legend" + LABEL_LABEL = "Label" + X_AXIS_LABEL = "x-axis title" + Y_AXIS_LABEL = "y-axis title" + TITLE_LABEL = "Title" + + initial_settings: Dict[str, Dict[str, SettingsType]] = NotImplemented + + def __init__(self): + self._setters: Dict[str, Dict[str, Callable]] = NotImplemented + + @property + def setters(self) -> Dict: + return self._setters + + @setters.setter + def setters(self, setters: Dict[str, Dict[str, Callable]]): + assert setters.keys() == self.initial_settings.keys() + assert all(setters[key].keys() == self.initial_settings[key].keys() + for key in setters.keys()) + + self._setters = setters + + def set_parameter(self, key: KeyType, value: ValueType): + self.setters[key[0]][key[1]](**{key[2]: value}) diff --git a/Orange/widgets/visualize/utils/plotutils.py b/Orange/widgets/visualize/utils/plotutils.py index f4084a59bc8..f5adbb20b62 100644 --- a/Orange/widgets/visualize/utils/plotutils.py +++ b/Orange/widgets/visualize/utils/plotutils.py @@ -1,14 +1,20 @@ +import itertools + import numpy as np from AnyQt.QtCore import ( QRectF, QLineF, QObject, QEvent, Qt, pyqtSignal as Signal ) -from AnyQt.QtGui import QTransform, QFontMetrics +from AnyQt.QtGui import QTransform, QFontMetrics, QStaticText, QBrush, QPen, \ + QFont from AnyQt.QtWidgets import ( QGraphicsLineItem, QGraphicsSceneMouseEvent, QPinchGesture, QGraphicsItemGroup, QWidget) import pyqtgraph as pg +import pyqtgraph.functions as fn +from pyqtgraph.graphicsItems.LegendItem import ItemSample +from pyqtgraph.graphicsItems.ScatterPlotItem import drawSymbol from Orange.widgets.utils.plot import SELECT, PANNING, ZOOMING @@ -51,6 +57,9 @@ def get_xy(self): point = self._spine.line().p2() return point.x(), point.y() + def setFont(self, font): + self._label.setFont(font) + def setText(self, text): if text != self._text: self._text = text @@ -403,3 +412,90 @@ def generateDrawSpecs(self, p): text_specs = [(rect, flags, elide(text, Qt.ElideRight, max_width)) for rect, flags, text in text_specs] return axis_spec, tick_specs, text_specs + + +class PaletteItemSample(ItemSample): + """A color strip to insert into legends for discretized continuous values""" + + def __init__(self, palette, scale, label_formatter=None): + """ + :param palette: palette used for showing continuous values + :type palette: BinnedContinuousPalette + :param scale: an instance of DiscretizedScale that defines the + conversion of values into bins + :type scale: DiscretizedScale + """ + super().__init__(None) + self.palette = palette + self.scale = scale + if label_formatter is None: + label_formatter = "{{:.{}f}}".format(scale.decimals).format + cuts = [label_formatter(scale.offset + i * scale.width) + for i in range(scale.bins + 1)] + self.labels = [QStaticText("{} - {}".format(fr, to)) + for fr, to in zip(cuts, cuts[1:])] + self.font = self.font() + self.font.setPointSize(11) + + @property + def bin_height(self): + return self.font.pointSize() + 4 + + @property + def text_width(self): + for label in self.labels: + label.prepare(font=self.font) + return max(label.size().width() for label in self.labels) + + def set_font(self, font: QFont): + self.font = font + self.update() + + def boundingRect(self): + return QRectF(0, 0, + 25 + self.text_width + self.bin_height, + 20 + self.scale.bins * self.bin_height) + + def paint(self, p, *args): + p.setRenderHint(p.Antialiasing) + p.translate(5, 5) + p.setFont(self.font) + colors = self.palette.qcolors + h = self.bin_height + for i, color, label in zip(itertools.count(), colors, self.labels): + p.setPen(Qt.NoPen) + p.setBrush(QBrush(color)) + p.drawRect(0, i * h, h, h) + p.setPen(QPen(Qt.black)) + p.drawStaticText(h + 5, i * h + 1, label) + + +class SymbolItemSample(ItemSample): + """Adjust position for symbols""" + def __init__(self, pen, brush, size, symbol): + super().__init__(None) + self.__pen = fn.mkPen(pen) + self.__brush = fn.mkBrush(brush) + self.__size = size + self.__symbol = symbol + + def paint(self, p, *args): + p.translate(8, 12) + drawSymbol(p, self.__symbol, self.__size, self.__pen, self.__brush) + + +class AxisItem(pg.AxisItem): + def generateDrawSpecs(self, p): + if self.style["tickFont"]: + p.setFont(self.style["tickFont"]) + return super().generateDrawSpecs(p) + + def _updateMaxTextSize(self, x): + if self.orientation in ["left", "right"]: + self.textWidth = x + if self.style["autoExpandTextSpace"] is True: + self._updateWidth() + else: + self.textHeight = x + if self.style["autoExpandTextSpace"] is True: + self._updateHeight() diff --git a/Orange/widgets/visualize/utils/tests/test_plotutils.py b/Orange/widgets/visualize/utils/tests/test_plotutils.py new file mode 100644 index 00000000000..67f9bc323c7 --- /dev/null +++ b/Orange/widgets/visualize/utils/tests/test_plotutils.py @@ -0,0 +1,14 @@ +import unittest + + +class TestAxisItem(unittest.TestCase): + def test_scalable_axis_item(self): + from pyqtgraph import __version__ + # When upgraded to 0.11.1 (or bigger) check if resizing AxisItem font + # works and overwritten functions generateDrawSpecs and + # _updateMaxTextSize are no longer needed. + self.assertLess(__version__, "0.11.1") + + +if __name__ == '__main__': + unittest.main() diff --git a/Orange/widgets/visualize/utils/widget.py b/Orange/widgets/visualize/utils/widget.py index e2c57a36a75..87bd19d8821 100644 --- a/Orange/widgets/visualize/utils/widget.py +++ b/Orange/widgets/visualize/utils/widget.py @@ -5,6 +5,8 @@ from AnyQt.QtCore import QSize, Signal from AnyQt.QtWidgets import QApplication +from orangewidget.utils.visual_settings_dlg import VisualSettingsDialog + from Orange.data import ( Table, ContinuousVariable, Domain, Variable, StringVariable ) @@ -400,6 +402,7 @@ def __init__(self): self.input_changed.connect(self.set_input_summary) self.output_changed.connect(self.set_output_summary) self.setup_gui() + VisualSettingsDialog(self, self.GRAPH_CLASS.initial_settings) # GUI def setup_gui(self): @@ -621,6 +624,10 @@ def _get_send_report_caption(self): ("Jittering", self.graph.jitter_size != 0 and "{} %".format(self.graph.jitter_size)))) + # Customize plot + def set_visual_settings(self, *args): + self.graph.set_parameter(*args) + @staticmethod def _get_caption_var_name(var): return var.name if isinstance(var, Variable) else var From 1e265a298990b976cece301d8d6f879fac568ac9 Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Fri, 5 Jun 2020 13:21:40 +0200 Subject: [PATCH 2/8] Calibration Plot: Customizable labels --- Orange/widgets/evaluate/owcalibrationplot.py | 71 +++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/Orange/widgets/evaluate/owcalibrationplot.py b/Orange/widgets/evaluate/owcalibrationplot.py index 6fa95e86620..951951c588a 100644 --- a/Orange/widgets/evaluate/owcalibrationplot.py +++ b/Orange/widgets/evaluate/owcalibrationplot.py @@ -7,6 +7,8 @@ import pyqtgraph as pg +from orangewidget.utils.visual_settings_dlg import VisualSettingsDialog + from Orange.base import Model from Orange.classification import ThresholdClassifier, CalibratedLearner from Orange.evaluation import Results @@ -17,6 +19,9 @@ from Orange.widgets.evaluate.utils import results_for_preview from Orange.widgets.utils import colorpalettes from Orange.widgets.utils.widgetpreview import WidgetPreview +from Orange.widgets.visualize.utils.customizableplot import Updater, \ + BaseParameterSetter as Setter +from Orange.widgets.visualize.utils.plotutils import AxisItem from Orange.widgets.widget import Input, Output, Msg from Orange.widgets import report @@ -58,6 +63,63 @@ )] +class ParameterSetter(Setter): + initial_settings = { + Setter.LABELS_BOX: { + Setter.FONT_FAMILY_LABEL: Updater.FONT_FAMILY_SETTING, + Setter.TITLE_LABEL: Updater.FONT_SETTING, + Setter.AXIS_TITLE_LABEL: Updater.FONT_SETTING, + Setter.AXIS_TICKS_LABEL: Updater.FONT_SETTING, + }, + Setter.ANNOT_BOX: { + Setter.TITLE_LABEL: {Setter.TITLE_LABEL: ("", "")}, + } + } + + def __init__(self): + def update_font_family(**settings): + for label, setter in self.setters[self.LABELS_BOX].items(): + if label != self.FONT_FAMILY_LABEL: + setter(**settings) + + def update_title(**settings): + Updater.update_plot_title_font(self.title_item, **settings) + + def update_axes_titles(**settings): + Updater.update_axes_titles_font(self.axis_items, **settings) + + def update_axes_ticks(**settings): + Updater.update_axes_ticks_font(self.axis_items, **settings) + + def update_title_text(**settings): + Updater.update_plot_title_text( + self.title_item, settings[self.TITLE_LABEL]) + + self.setters = { + self.LABELS_BOX: { + self.FONT_FAMILY_LABEL: update_font_family, + self.TITLE_LABEL: update_title, + self.AXIS_TITLE_LABEL: update_axes_titles, + self.AXIS_TICKS_LABEL: update_axes_ticks, + }, + self.ANNOT_BOX: { + self.TITLE_LABEL: update_title_text, + } + } + + @property + def title_item(self): + return self.titleLabel + + @property + def axis_items(self): + return [value["item"] for value in self.axes.values()] + + +class PlotItem(pg.PlotItem, ParameterSetter): + pass + + class OWCalibrationPlot(widget.OWWidget): name = "Calibration Plot" description = "Calibration plot based on evaluation of classifiers." @@ -157,7 +219,9 @@ def __init__(self): gui.auto_apply(self.controlArea, self, "auto_commit", commit=self.apply) self.plotview = pg.GraphicsView(background="w") - self.plot = pg.PlotItem(enableMenu=False) + axes = {"bottom": AxisItem(orientation="bottom"), + "left": AxisItem(orientation="left")} + self.plot = PlotItem(enableMenu=False, axisItems=axes) self.plot.setMouseEnabled(False, False) self.plot.hideButtons() @@ -177,6 +241,8 @@ def __init__(self): self.mainArea.layout().addWidget(self.plotview) self._set_explanation() + VisualSettingsDialog(self, PlotItem.initial_settings) + @Inputs.evaluation_results def set_results(self, results): self.closeContext() @@ -498,6 +564,9 @@ def send_report(self): if self.score != 0: self.report_raw(self.get_info_text(short=False)) + def set_visual_settings(self, *args): + self.plot.set_parameter(*args) + def gaussian_smoother(x, y, sigma=1.0): x = np.asarray(x) From b64b5b4182b5c70ad9394bcf866927f6342be74a Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Fri, 5 Jun 2020 13:23:03 +0200 Subject: [PATCH 3/8] Line plot: Customizable labels --- Orange/widgets/visualize/owlineplot.py | 95 ++++++++++++++++++- .../visualize/tests/test_owlineplot.py | 6 +- 2 files changed, 94 insertions(+), 7 deletions(-) diff --git a/Orange/widgets/visualize/owlineplot.py b/Orange/widgets/visualize/owlineplot.py index f7a268093ef..3bd4317bdf2 100644 --- a/Orange/widgets/visualize/owlineplot.py +++ b/Orange/widgets/visualize/owlineplot.py @@ -11,6 +11,8 @@ from pyqtgraph.functions import mkPen from pyqtgraph.graphicsItems.ViewBox import ViewBox +from orangewidget.utils.visual_settings_dlg import VisualSettingsDialog + from Orange.data import Table, DiscreteVariable from Orange.data.sql.table import SqlTable from Orange.statistics.util import countnans, nanmean, nanmin, nanmax, nanstd @@ -27,6 +29,9 @@ from Orange.widgets.utils.widgetpreview import WidgetPreview from Orange.widgets.utils.state_summary import format_summary_details from Orange.widgets.visualize.owdistributions import LegendItem +from Orange.widgets.visualize.utils.customizableplot import Updater, \ + BaseParameterSetter as Setter +from Orange.widgets.visualize.utils.plotutils import AxisItem from Orange.widgets.widget import OWWidget, Input, Output, Msg @@ -92,7 +97,7 @@ class LinePlotStyle: MEAN_DARK_FACTOR = 110 -class LinePlotAxisItem(pg.AxisItem): +class BottomAxisItem(AxisItem): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._ticks = {} @@ -183,12 +188,89 @@ def reset(self): self._graph_state = SELECT -class LinePlotGraph(pg.PlotWidget): +class ParameterSetter(Setter): + initial_settings = { + Setter.LABELS_BOX: { + Setter.FONT_FAMILY_LABEL: Updater.FONT_FAMILY_SETTING, + Setter.TITLE_LABEL: Updater.FONT_SETTING, + Setter.AXIS_TITLE_LABEL: Updater.FONT_SETTING, + Setter.AXIS_TICKS_LABEL: Updater.FONT_SETTING, + Setter.LEGEND_LABEL: Updater.FONT_SETTING, + }, + Setter.ANNOT_BOX: { + Setter.TITLE_LABEL: {Setter.TITLE_LABEL: ("", "")}, + Setter.X_AXIS_LABEL: {Setter.TITLE_LABEL: ("", "")}, + Setter.Y_AXIS_LABEL: {Setter.TITLE_LABEL: ("", "")}, + } + } + + def __init__(self): + def update_font_family(**settings): + for label, setter in self.setters[self.LABELS_BOX].items(): + if label != self.FONT_FAMILY_LABEL: + setter(**settings) + + def update_title(**settings): + Updater.update_plot_title_font(self.title_item, **settings) + + def update_axes_titles(**settings): + Updater.update_axes_titles_font(self.axis_items, **settings) + + def update_axes_ticks(**settings): + Updater.update_axes_ticks_font(self.axis_items, **settings) + + def update_legend(**settings): + self.legend_settings.update(**settings) + Updater.update_legend_font(self.legend_items, **settings) + + def update_title_text(**settings): + Updater.update_plot_title_text( + self.title_item, settings[self.TITLE_LABEL]) + + def update_axis(axis, **settings): + Updater.update_axis_title_text( + self.getAxis(axis), settings[self.TITLE_LABEL]) + + self.legend_settings = {} + self.setters = { + self.LABELS_BOX: { + self.FONT_FAMILY_LABEL: update_font_family, + self.TITLE_LABEL: update_title, + self.AXIS_TITLE_LABEL: update_axes_titles, + self.AXIS_TICKS_LABEL: update_axes_ticks, + self.LEGEND_LABEL: update_legend, + }, + self.ANNOT_BOX: { + self.TITLE_LABEL: update_title_text, + self.X_AXIS_LABEL: lambda **kw: update_axis("bottom", **kw), + self.Y_AXIS_LABEL: lambda **kw: update_axis("left", **kw), + } + } + + @property + def title_item(self): + return self.getPlotItem().titleLabel + + @property + def axis_items(self): + return [value["item"] for value in self.getPlotItem().axes.values()] + + @property + def legend_items(self): + return self.legend.items + + +# Customizable plot widget +class LinePlotGraph(pg.PlotWidget, ParameterSetter): def __init__(self, parent): - self.bottom_axis = LinePlotAxisItem(orientation="bottom") + self.bottom_axis = BottomAxisItem(orientation="bottom") + self.bottom_axis.setLabel("") + left_axis = AxisItem(orientation="left") + left_axis.setLabel("") super().__init__(parent, viewBox=LinePlotViewBox(), background="w", enableMenu=False, - axisItems={"bottom": self.bottom_axis}) + axisItems={"bottom": self.bottom_axis, + "left": left_axis}) self.view_box = self.getViewBox() self.selection = set() self.legend = self._create_legend(((1, 0), (1, 0))) @@ -211,6 +293,7 @@ def update_legend(self, variable): dots = pg.ScatterPlotItem(pen=c, brush=c, size=10, shape="s") self.legend.addItem(dots, escape(name)) self.legend.show() + Updater.update_legend_font(self.legend_items, **self.legend_settings) def select(self, indices): keys = QApplication.keyboardModifiers() @@ -463,6 +546,7 @@ def __init__(self, parent=None): self.graph_variables = [] self.setup_gui() + VisualSettingsDialog(self, LinePlotGraph.initial_settings) self.graph.view_box.selection_changed.connect(self.selection_changed) self.enable_selection.connect(self.graph.view_box.enable_selection) @@ -771,6 +855,9 @@ def clear(self): def __in(obj, collection): return collection is not None and obj in collection + def set_visual_settings(self, *args): + self.graph.set_parameter(*args) + if __name__ == "__main__": data = Table("brown-selected") diff --git a/Orange/widgets/visualize/tests/test_owlineplot.py b/Orange/widgets/visualize/tests/test_owlineplot.py index 219c661935b..fc8d5f91994 100644 --- a/Orange/widgets/visualize/tests/test_owlineplot.py +++ b/Orange/widgets/visualize/tests/test_owlineplot.py @@ -7,7 +7,7 @@ import numpy as np import scipy.sparse as sp -from AnyQt.QtCore import Qt +from AnyQt.QtCore import Qt, QPointF from pyqtgraph import PlotCurveItem from pyqtgraph.Point import Point @@ -152,8 +152,8 @@ def test_selection_line(self): # set view-dependent click coordinates vb = self.widget.graph.view_box - event.buttonDownPos.return_value = vb.mapFromView(Point(2.49, 5.79)) - event.pos.return_value = vb.mapFromView(Point(2.99, 4.69)) + event.buttonDownPos.return_value = QPointF(2.38, 4.84) + event.pos.return_value = QPointF(3.58, 4.76) self.widget.graph.view_box.mouseDragEvent(event) line = self.widget.graph.view_box.selection_line From ffcfb8e58f98865f7fee1e8217877813fbc821ba Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Wed, 10 Jun 2020 13:30:10 +0200 Subject: [PATCH 4/8] Visual settings dialog: Save parameters as schema-only settings --- Orange/widgets/evaluate/owcalibrationplot.py | 6 ++++-- Orange/widgets/visualize/owlineplot.py | 6 ++++-- Orange/widgets/visualize/utils/widget.py | 8 +++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Orange/widgets/evaluate/owcalibrationplot.py b/Orange/widgets/evaluate/owcalibrationplot.py index 951951c588a..874ddd65eb4 100644 --- a/Orange/widgets/evaluate/owcalibrationplot.py +++ b/Orange/widgets/evaluate/owcalibrationplot.py @@ -162,6 +162,7 @@ class Information(widget.OWWidget.Information): fold_curves = settings.Setting(False) display_rug = settings.Setting(True) threshold = settings.Setting(0.5) + visual_settings = settings.Setting({}, schema_only=True) auto_commit = settings.Setting(True) graph_name = "plot" @@ -564,8 +565,9 @@ def send_report(self): if self.score != 0: self.report_raw(self.get_info_text(short=False)) - def set_visual_settings(self, *args): - self.plot.set_parameter(*args) + def set_visual_settings(self, key, value): + self.plot.set_parameter(key, value) + self.visual_settings[key] = value def gaussian_smoother(x, y, sigma=1.0): diff --git a/Orange/widgets/visualize/owlineplot.py b/Orange/widgets/visualize/owlineplot.py index 3bd4317bdf2..6a0447452a5 100644 --- a/Orange/widgets/visualize/owlineplot.py +++ b/Orange/widgets/visualize/owlineplot.py @@ -520,6 +520,7 @@ class Outputs: show_error = Setting(False) auto_commit = Setting(True) selection = Setting(None, schema_only=True) + visual_settings = Setting({}, schema_only=True) graph_name = "graph.plotItem" @@ -855,8 +856,9 @@ def clear(self): def __in(obj, collection): return collection is not None and obj in collection - def set_visual_settings(self, *args): - self.graph.set_parameter(*args) + def set_visual_settings(self, key, value): + self.graph.set_parameter(key, value) + self.visual_settings[key] = value if __name__ == "__main__": diff --git a/Orange/widgets/visualize/utils/widget.py b/Orange/widgets/visualize/utils/widget.py index 87bd19d8821..7c35c6726f6 100644 --- a/Orange/widgets/visualize/utils/widget.py +++ b/Orange/widgets/visualize/utils/widget.py @@ -381,6 +381,7 @@ class Warning(OWProjectionWidgetBase.Warning): settingsHandler = DomainContextHandler() selection = Setting(None, schema_only=True) + visual_settings = Setting({}, schema_only=True) auto_commit = Setting(True) GRAPH_CLASS = OWScatterPlotBase @@ -625,8 +626,9 @@ def _get_send_report_caption(self): "{} %".format(self.graph.jitter_size)))) # Customize plot - def set_visual_settings(self, *args): - self.graph.set_parameter(*args) + def set_visual_settings(self, key, value): + self.graph.set_parameter(key, value) + self.visual_settings[key] = value @staticmethod def _get_caption_var_name(var): @@ -664,8 +666,8 @@ class Error(OWDataProjectionWidget.Error): proj_error = Msg("An error occurred while projecting data.\n{}") def __init__(self): - super().__init__() self.projector = self.projection = None + super().__init__() self.graph.view_box.started.connect(self._manual_move_start) self.graph.view_box.moved.connect(self._manual_move) self.graph.view_box.finished.connect(self._manual_move_finish) From e5d4f3aefdb23c5657b16e76519ec71c3e127d86 Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Thu, 18 Jun 2020 13:44:33 +0200 Subject: [PATCH 5/8] BaseParameterSetter: Move duplicated code to base class --- Orange/widgets/evaluate/owcalibrationplot.py | 31 -------- Orange/widgets/visualize/owlineplot.py | 43 ----------- Orange/widgets/visualize/owscatterplot.py | 13 ---- .../widgets/visualize/owscatterplotgraph.py | 34 ++------- Orange/widgets/visualize/utils/component.py | 9 ++- .../visualize/utils/customizableplot.py | 75 +++++++++++++++---- 6 files changed, 72 insertions(+), 133 deletions(-) diff --git a/Orange/widgets/evaluate/owcalibrationplot.py b/Orange/widgets/evaluate/owcalibrationplot.py index 874ddd65eb4..9869123b9c5 100644 --- a/Orange/widgets/evaluate/owcalibrationplot.py +++ b/Orange/widgets/evaluate/owcalibrationplot.py @@ -76,37 +76,6 @@ class ParameterSetter(Setter): } } - def __init__(self): - def update_font_family(**settings): - for label, setter in self.setters[self.LABELS_BOX].items(): - if label != self.FONT_FAMILY_LABEL: - setter(**settings) - - def update_title(**settings): - Updater.update_plot_title_font(self.title_item, **settings) - - def update_axes_titles(**settings): - Updater.update_axes_titles_font(self.axis_items, **settings) - - def update_axes_ticks(**settings): - Updater.update_axes_ticks_font(self.axis_items, **settings) - - def update_title_text(**settings): - Updater.update_plot_title_text( - self.title_item, settings[self.TITLE_LABEL]) - - self.setters = { - self.LABELS_BOX: { - self.FONT_FAMILY_LABEL: update_font_family, - self.TITLE_LABEL: update_title, - self.AXIS_TITLE_LABEL: update_axes_titles, - self.AXIS_TICKS_LABEL: update_axes_ticks, - }, - self.ANNOT_BOX: { - self.TITLE_LABEL: update_title_text, - } - } - @property def title_item(self): return self.titleLabel diff --git a/Orange/widgets/visualize/owlineplot.py b/Orange/widgets/visualize/owlineplot.py index 6a0447452a5..eb238fcba11 100644 --- a/Orange/widgets/visualize/owlineplot.py +++ b/Orange/widgets/visualize/owlineplot.py @@ -204,49 +204,6 @@ class ParameterSetter(Setter): } } - def __init__(self): - def update_font_family(**settings): - for label, setter in self.setters[self.LABELS_BOX].items(): - if label != self.FONT_FAMILY_LABEL: - setter(**settings) - - def update_title(**settings): - Updater.update_plot_title_font(self.title_item, **settings) - - def update_axes_titles(**settings): - Updater.update_axes_titles_font(self.axis_items, **settings) - - def update_axes_ticks(**settings): - Updater.update_axes_ticks_font(self.axis_items, **settings) - - def update_legend(**settings): - self.legend_settings.update(**settings) - Updater.update_legend_font(self.legend_items, **settings) - - def update_title_text(**settings): - Updater.update_plot_title_text( - self.title_item, settings[self.TITLE_LABEL]) - - def update_axis(axis, **settings): - Updater.update_axis_title_text( - self.getAxis(axis), settings[self.TITLE_LABEL]) - - self.legend_settings = {} - self.setters = { - self.LABELS_BOX: { - self.FONT_FAMILY_LABEL: update_font_family, - self.TITLE_LABEL: update_title, - self.AXIS_TITLE_LABEL: update_axes_titles, - self.AXIS_TICKS_LABEL: update_axes_ticks, - self.LEGEND_LABEL: update_legend, - }, - self.ANNOT_BOX: { - self.TITLE_LABEL: update_title_text, - self.X_AXIS_LABEL: lambda **kw: update_axis("bottom", **kw), - self.Y_AXIS_LABEL: lambda **kw: update_axis("left", **kw), - } - } - @property def title_item(self): return self.getPlotItem().titleLabel diff --git a/Orange/widgets/visualize/owscatterplot.py b/Orange/widgets/visualize/owscatterplot.py index 0c41c683f48..46029c063e0 100644 --- a/Orange/widgets/visualize/owscatterplot.py +++ b/Orange/widgets/visualize/owscatterplot.py @@ -105,19 +105,6 @@ class ParameterSetter(Setter): Setter.AXIS_TICKS_LABEL: Updater.FONT_SETTING }) - def __init__(self): - super().__init__() - - def update_axes_titles(**settings): - Updater.update_axes_titles_font(self.axis_items, **settings) - - def update_axes_ticks(**settings): - Updater.update_axes_ticks_font(self.axis_items, **settings) - - labels = self.LABELS_BOX - self.setters[labels][self.AXIS_TITLE_LABEL] = update_axes_titles - self.setters[labels][self.AXIS_TICKS_LABEL] = update_axes_ticks - @property def axis_items(self): return [value["item"] for value in diff --git a/Orange/widgets/visualize/owscatterplotgraph.py b/Orange/widgets/visualize/owscatterplotgraph.py index 4fe4a616e90..82bf2a21a87 100644 --- a/Orange/widgets/visualize/owscatterplotgraph.py +++ b/Orange/widgets/visualize/owscatterplotgraph.py @@ -360,22 +360,11 @@ class ParameterSetter(Setter): } def __init__(self): - self.label_font = QFont() + super().__init__() self.cat_legend_settings = {} self.num_legend_settings = {} - def update_font_family(**settings): - for label, setter in self.setters[self.LABELS_BOX].items(): - if label != self.FONT_FAMILY_LABEL: - setter(**settings) - - def update_title(**settings): - Updater.update_plot_title_font(self.title_item, **settings) - - def update_label(**settings): - self.label_font = Updater.change_font(self.label_font, settings) - Updater.update_label_font(self.labels, self.label_font) - + def update_setters(self): def update_cat_legend(**settings): self.cat_legend_settings.update(**settings) Updater.update_legend_font(self.cat_legend_items, **settings) @@ -384,22 +373,9 @@ def update_num_legend(**settings): self.num_legend_settings.update(**settings) Updater.update_num_legend_font(self.num_legend, **settings) - def update_title_text(**settings): - Updater.update_plot_title_text( - self.title_item, settings[self.TITLE_LABEL]) - - self._setters = { - self.LABELS_BOX: { - self.FONT_FAMILY_LABEL: update_font_family, - self.TITLE_LABEL: update_title, - self.LABEL_LABEL: update_label, - self.CAT_LEGEND_LABEL: update_cat_legend, - self.NUM_LEGEND_LABEL: update_num_legend, - }, - self.ANNOT_BOX: { - self.TITLE_LABEL: update_title_text, - } - } + labels = self.LABELS_BOX + self._setters[labels][self.CAT_LEGEND_LABEL] = update_cat_legend + self._setters[labels][self.NUM_LEGEND_LABEL] = update_num_legend @property def title_item(self): diff --git a/Orange/widgets/visualize/utils/component.py b/Orange/widgets/visualize/utils/component.py index edfcd78d614..e31359ffe74 100644 --- a/Orange/widgets/visualize/utils/component.py +++ b/Orange/widgets/visualize/utils/component.py @@ -23,13 +23,16 @@ class ParameterSetter(Setter): }) def __init__(self): + super().__init__() + self.anchor_font = QFont() + + def update_setters(self): def update_anchors(**settings): self.anchor_font = Updater.change_font(self.anchor_font, settings) self.update_anchors() - super().__init__() - self.anchor_font = QFont() - self.setters[self.LABELS_BOX][self.ANCHOR_LABEL] = update_anchors + super().update_setters() + self._setters[self.LABELS_BOX][self.ANCHOR_LABEL] = update_anchors class OWGraphWithAnchors(OWScatterPlotBase, ParameterSetter): diff --git a/Orange/widgets/visualize/utils/customizableplot.py b/Orange/widgets/visualize/utils/customizableplot.py index 3f9bff7c588..b0d9f9cc38a 100644 --- a/Orange/widgets/visualize/utils/customizableplot.py +++ b/Orange/widgets/visualize/utils/customizableplot.py @@ -182,19 +182,66 @@ class BaseParameterSetter: initial_settings: Dict[str, Dict[str, SettingsType]] = NotImplemented def __init__(self): - self._setters: Dict[str, Dict[str, Callable]] = NotImplemented - - @property - def setters(self) -> Dict: - return self._setters - - @setters.setter - def setters(self, setters: Dict[str, Dict[str, Callable]]): - assert setters.keys() == self.initial_settings.keys() - assert all(setters[key].keys() == self.initial_settings[key].keys() - for key in setters.keys()) - - self._setters = setters + def update_font_family(**settings): + for label in self.initial_settings[self.LABELS_BOX]: + if label != self.FONT_FAMILY_LABEL: + setter = self._setters[self.LABELS_BOX][label] + setter(**settings) + + def update_title(**settings): + Updater.update_plot_title_font(self.title_item, **settings) + + def update_label(**settings): + self.label_font = Updater.change_font(self.label_font, settings) + Updater.update_label_font(self.labels, self.label_font) + + def update_axes_titles(**settings): + Updater.update_axes_titles_font(self.axis_items, **settings) + + def update_axes_ticks(**settings): + Updater.update_axes_ticks_font(self.axis_items, **settings) + + def update_legend(**settings): + self.legend_settings.update(**settings) + Updater.update_legend_font(self.legend_items, **settings) + + def update_title_text(**settings): + Updater.update_plot_title_text( + self.title_item, settings[self.TITLE_LABEL]) + + def update_axis(axis, **settings): + Updater.update_axis_title_text( + self.getAxis(axis), settings[self.TITLE_LABEL]) + + self.label_font = QFont() + self.legend_settings = {} + + self._setters = { + self.LABELS_BOX: { + self.FONT_FAMILY_LABEL: update_font_family, + self.TITLE_LABEL: update_title, + self.LABEL_LABEL: update_label, + self.AXIS_TITLE_LABEL: update_axes_titles, + self.AXIS_TICKS_LABEL: update_axes_ticks, + self.LEGEND_LABEL: update_legend, + }, + self.ANNOT_BOX: { + self.TITLE_LABEL: update_title_text, + self.X_AXIS_LABEL: lambda **kw: update_axis("bottom", **kw), + self.Y_AXIS_LABEL: lambda **kw: update_axis("left", **kw), + } + } + + self.update_setters() + self._check_setters() + + def update_setters(self): + pass + + def _check_setters(self): + assert all(key in self._setters for key in self.initial_settings) + for k, inner in self.initial_settings.items(): + assert all(key in self._setters[k] for key in inner) def set_parameter(self, key: KeyType, value: ValueType): - self.setters[key[0]][key[1]](**{key[2]: value}) + self._setters[key[0]][key[1]](**{key[2]: value}) From 400f05c7e61f6c8309371b85cd4904d6aa1d95d6 Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Fri, 19 Jun 2020 12:32:17 +0200 Subject: [PATCH 6/8] Line plot: Customizable figure --- Orange/widgets/visualize/owlineplot.py | 173 +++++++++++++++--- .../visualize/utils/customizableplot.py | 43 ++++- 2 files changed, 192 insertions(+), 24 deletions(-) diff --git a/Orange/widgets/visualize/owlineplot.py b/Orange/widgets/visualize/owlineplot.py index eb238fcba11..be3952dd9e6 100644 --- a/Orange/widgets/visualize/owlineplot.py +++ b/Orange/widgets/visualize/owlineplot.py @@ -1,3 +1,4 @@ +from typing import List from xml.sax.saxutils import escape import numpy as np @@ -84,8 +85,9 @@ class LinePlotStyle: SELECTION_LINE_COLOR = QColor(Qt.black) SELECTION_LINE_WIDTH = 2 + UNSELECTED_LINE_WIDTH = 1 UNSELECTED_LINE_ALPHA = 100 - UNSELECTED_LINE_ALPHA_SEL = 50 + UNSELECTED_LINE_ALPHA_SEL = 50 # unselected lines, when selection exists SELECTED_LINE_WIDTH = 3 SELECTED_LINE_ALPHA = 170 @@ -189,6 +191,11 @@ def reset(self): class ParameterSetter(Setter): + MEAN_LABEL = "Mean" + LINE_LABEL = "Lines" + SEL_LINE_LABEL = "Selected lines" + RANGE_LABEL = "Range" + SEL_RANGE_LABEL = "Selected range" initial_settings = { Setter.LABELS_BOX: { Setter.FONT_FAMILY_LABEL: Updater.FONT_FAMILY_SETTING, @@ -201,9 +208,104 @@ class ParameterSetter(Setter): Setter.TITLE_LABEL: {Setter.TITLE_LABEL: ("", "")}, Setter.X_AXIS_LABEL: {Setter.TITLE_LABEL: ("", "")}, Setter.Y_AXIS_LABEL: {Setter.TITLE_LABEL: ("", "")}, + }, + Setter.PLOT_BOX: { + MEAN_LABEL: { + Updater.WIDTH_LABEL: (range(1, 15), LinePlotStyle.MEAN_WIDTH), + Updater.STYLE_LABEL: (list(Updater.LINE_STYLES), + Updater.DEFAULT_LINE_STYLE), + }, + LINE_LABEL: { + Updater.WIDTH_LABEL: (range(1, 15), + LinePlotStyle.UNSELECTED_LINE_WIDTH), + Updater.STYLE_LABEL: (list(Updater.LINE_STYLES), + Updater.DEFAULT_LINE_STYLE), + Updater.ALPHA_LABEL: (range(0, 255, 5), + LinePlotStyle.UNSELECTED_LINE_ALPHA), + Updater.ANTIALIAS_LABEL: (None, True), + }, + SEL_LINE_LABEL: { + Updater.WIDTH_LABEL: (range(1, 15), + LinePlotStyle.SELECTED_LINE_WIDTH), + Updater.STYLE_LABEL: (list(Updater.LINE_STYLES), + Updater.DEFAULT_LINE_STYLE), + Updater.ALPHA_LABEL: (range(0, 255, 5), + LinePlotStyle.SELECTED_LINE_ALPHA), + Updater.ANTIALIAS_LABEL: (None, False), + }, + RANGE_LABEL: { + Updater.ALPHA_LABEL: (range(0, 255, 5), + LinePlotStyle.RANGE_ALPHA), + }, + SEL_RANGE_LABEL: { + Updater.ALPHA_LABEL: (range(0, 255, 5), + LinePlotStyle.SELECTED_RANGE_ALPHA), + }, } } + def __init__(self): + self.mean_settings = { + Updater.WIDTH_LABEL: LinePlotStyle.MEAN_WIDTH, + Updater.STYLE_LABEL: Updater.DEFAULT_LINE_STYLE, + } + self.line_settings = { + Updater.WIDTH_LABEL: LinePlotStyle.UNSELECTED_LINE_WIDTH, + Updater.ALPHA_LABEL: LinePlotStyle.UNSELECTED_LINE_ALPHA, + Updater.STYLE_LABEL: Updater.DEFAULT_LINE_STYLE, + Updater.ANTIALIAS_LABEL: True, + } + self.sel_line_settings = { + Updater.WIDTH_LABEL: LinePlotStyle.SELECTED_LINE_WIDTH, + Updater.ALPHA_LABEL: LinePlotStyle.SELECTED_LINE_ALPHA, + Updater.STYLE_LABEL: Updater.DEFAULT_LINE_STYLE, + Updater.ANTIALIAS_LABEL: False, + } + self.range_settings = { + Updater.ALPHA_LABEL: LinePlotStyle.RANGE_ALPHA, + } + self.sel_range_settings = { + Updater.ALPHA_LABEL: LinePlotStyle.SELECTED_RANGE_ALPHA, + } + super().__init__() + + def update_setters(self): + def update_mean(**settings): + self.mean_settings.update(**settings) + Updater.update_lines(self.mean_lines_items, **self.mean_settings) + + def update_lines(**settings): + self.line_settings.update(**settings) + Updater.update_lines(self.lines_items, **self.line_settings) + + def update_sel_lines(**settings): + self.sel_line_settings.update(**settings) + Updater.update_lines(self.sel_lines_items, **self.sel_line_settings) + + def _update_brush(items, **settings): + for item in items: + brush = item.brush() + color = brush.color() + color.setAlpha(settings[Updater.ALPHA_LABEL]) + brush.setColor(color) + item.setBrush(brush) + + def update_range(**settings): + self.range_settings.update(**settings) + _update_brush(self.range_items, **settings) + + def update_sel_range(**settings): + self.sel_range_settings.update(**settings) + _update_brush(self.sel_range_items, **settings) + + self._setters[self.PLOT_BOX] = { + self.MEAN_LABEL: update_mean, + self.LINE_LABEL: update_lines, + self.SEL_LINE_LABEL: update_sel_lines, + self.RANGE_LABEL: update_range, + self.SEL_RANGE_LABEL: update_sel_range, + } + @property def title_item(self): return self.getPlotItem().titleLabel @@ -216,10 +318,32 @@ def axis_items(self): def legend_items(self): return self.legend.items + @property + def mean_lines_items(self): + return [group.mean for group in self.groups] + + @property + def lines_items(self): + return [group.profiles for group in self.groups] + + @property + def sel_lines_items(self): + return [group.sel_profiles for group in self.groups] + \ + [group.sub_profiles for group in self.groups] + + @property + def range_items(self): + return [group.range for group in self.groups] + + @property + def sel_range_items(self): + return [group.sel_range for group in self.groups] + # Customizable plot widget class LinePlotGraph(pg.PlotWidget, ParameterSetter): def __init__(self, parent): + self.groups: List[ProfileGroup] = [] self.bottom_axis = BottomAxisItem(orientation="bottom") self.bottom_axis.setLabel("") left_axis = AxisItem(orientation="left") @@ -270,6 +394,7 @@ def reset(self): self.clear() self.getAxis('bottom').set_ticks(None) self.legend.hide() + self.groups = [] def select_button_clicked(self): self.view_box.set_graph_state(SELECT) @@ -322,20 +447,19 @@ def __create_curves(self): def _get_profiles_curve(self): x, y, con = self.__get_disconnected_curve_data(self.y_data) - color = QColor(self.color) - color.setAlpha(LinePlotStyle.UNSELECTED_LINE_ALPHA) - pen = self.make_pen(color) - return pg.PlotCurveItem(x=x, y=y, connect=con, pen=pen, antialias=True) + pen = self.make_pen(self.color) + curve = pg.PlotCurveItem(x=x, y=y, connect=con, pen=pen) + Updater.update_lines([curve], **self.graph.line_settings) + return curve def _get_sel_profiles_curve(self): - color = QColor(self.color) - color.setAlpha(LinePlotStyle.SELECTED_LINE_ALPHA) - pen = self.make_pen(color, LinePlotStyle.SELECTED_LINE_WIDTH) - return pg.PlotCurveItem(x=None, y=None, pen=pen, antialias=False) + curve = pg.PlotCurveItem(x=None, y=None, pen=self.make_pen(self.color)) + Updater.update_lines([curve], **self.graph.sel_line_settings) + return curve def _get_range_curve(self): color = QColor(self.color) - color.setAlpha(LinePlotStyle.RANGE_ALPHA) + color.setAlpha(self.graph.range_settings[Updater.ALPHA_LABEL]) bottom, top = nanmin(self.y_data, axis=0), nanmax(self.y_data, axis=0) return pg.FillBetweenItem( pg.PlotDataItem(x=self.x_data, y=bottom), @@ -344,15 +468,15 @@ def _get_range_curve(self): def _get_sel_range_curve(self): color = QColor(self.color) - color.setAlpha(LinePlotStyle.SELECTED_RANGE_ALPHA) + color.setAlpha(self.graph.sel_range_settings[Updater.ALPHA_LABEL]) curve1 = curve2 = pg.PlotDataItem(x=self.x_data, y=self.__mean) return pg.FillBetweenItem(curve1, curve2, brush=color) def _get_mean_curve(self): - pen = self.make_pen(self.color.darker(LinePlotStyle.MEAN_DARK_FACTOR), - LinePlotStyle.MEAN_WIDTH) - return pg.PlotCurveItem(x=self.x_data, y=self.__mean, - pen=pen, antialias=True) + pen = self.make_pen(self.color.darker(LinePlotStyle.MEAN_DARK_FACTOR)) + curve = pg.PlotCurveItem(x=self.x_data, y=self.__mean, pen=pen) + Updater.update_lines([curve], **self.graph.mean_settings) + return curve def _get_error_bar(self): std = nanstd(self.y_data, axis=0) @@ -402,11 +526,12 @@ def set_visible_error(self, show_error=True, **_): def update_profiles_color(self, selection): color = QColor(self.color) - alpha = LinePlotStyle.UNSELECTED_LINE_ALPHA if not selection \ - else LinePlotStyle.UNSELECTED_LINE_ALPHA_SEL + alpha = self.graph.line_settings[Updater.ALPHA_LABEL] \ + if not selection else LinePlotStyle.UNSELECTED_LINE_ALPHA_SEL color.setAlpha(alpha) - x, y = self.profiles.getData() - self.profiles.setData(x=x, y=y, pen=self.make_pen(color)) + pen = self.profiles.opts["pen"] + pen.setColor(color) + self.profiles.setPen(pen) def update_sel_profiles(self, y_data): x, y, connect = self.__get_disconnected_curve_data(y_data) \ @@ -415,10 +540,10 @@ def update_sel_profiles(self, y_data): def update_sel_profiles_color(self, subset): color = QColor(Qt.black) if subset else QColor(self.color) - color.setAlpha(LinePlotStyle.SELECTED_LINE_ALPHA) - pen = self.make_pen(color, LinePlotStyle.SELECTED_LINE_WIDTH) - x, y = self.sel_profiles.getData() - self.sel_profiles.setData(x=x, y=y, pen=pen) + color.setAlpha(self.graph.sel_line_settings[Updater.ALPHA_LABEL]) + pen = self.sel_profiles.opts["pen"] + pen.setColor(color) + self.sel_profiles.setPen(pen) def update_sub_profiles(self, y_data): x, y, connect = self.__get_disconnected_curve_data(y_data) \ @@ -674,12 +799,14 @@ def plot_groups(self): group_data = self.data[indices, self.graph_variables] self._plot_group(group_data, indices, index) self.graph.update_legend(self.group_var) + self.graph.groups = self.__groups self.graph.view_box.add_profiles(data.X) def _remove_groups(self): for group in self.__groups: group.remove_items() self.graph.view_box.remove_profiles() + self.graph.groups = [] self.__groups = [] def _plot_group(self, data, indices, index=None): diff --git a/Orange/widgets/visualize/utils/customizableplot.py b/Orange/widgets/visualize/utils/customizableplot.py index b0d9f9cc38a..d5bda45c572 100644 --- a/Orange/widgets/visualize/utils/customizableplot.py +++ b/Orange/widgets/visualize/utils/customizableplot.py @@ -1,6 +1,7 @@ import sys -from typing import Tuple, List, Dict, Callable, Iterable +from typing import Tuple, List, Dict, Iterable +from AnyQt.QtCore import Qt from AnyQt.QtGui import QFont, QFontDatabase from AnyQt.QtWidgets import QApplication @@ -71,6 +72,21 @@ class Updater: IS_ITALIC_LABEL: (None, False) } + WIDTH_LABEL, ALPHA_LABEL, STYLE_LABEL, ANTIALIAS_LABEL = \ + "Width", "Opacity", "Style", "Antialias" + LINE_STYLES = {"Solid line": Qt.SolidLine, + "Dash line": Qt.DashLine, + "Dot line": Qt.DotLine, + "Dash dot line": Qt.DashDotLine, + "Dash dot dot line": Qt.DashDotDotLine} + DEFAULT_LINE_STYLE = "Solid line" + LINE_SETTING: SettingsType = { + WIDTH_LABEL: (range(1, 15), 1), + ALPHA_LABEL: (range(0, 255, 5), 255), + STYLE_LABEL: (list(LINE_STYLES), DEFAULT_LINE_STYLE), + ANTIALIAS_LABEL: (None, False), + } + @staticmethod def update_plot_title_text(title_item: pg.LabelItem, text: str): title_item.text = text @@ -164,11 +180,36 @@ def change_font(font: QFont, settings: _SettingType) -> QFont: font.setItalic(italic) return font + @staticmethod + def update_lines(items: List[pg.PlotCurveItem], **settings: _SettingType): + for item in items: + antialias = settings.get(Updater.ANTIALIAS_LABEL) + if antialias is not None: + item.setData(item.xData, item.yData, antialias=antialias) + + pen = item.opts["pen"] + alpha = settings.get(Updater.ALPHA_LABEL) + if alpha is not None: + color = pen.color() + color.setAlpha(alpha) + pen.setColor(color) + + style = settings.get(Updater.STYLE_LABEL) + if style is not None: + pen.setStyle(Updater.LINE_STYLES[style]) + + width = settings.get(Updater.WIDTH_LABEL) + if width is not None: + pen.setWidth(width) + + item.setPen(pen) + class BaseParameterSetter: """ Subclass to add 'setter' functionality to a plot. """ LABELS_BOX = "Fonts" ANNOT_BOX = "Annotations" + PLOT_BOX = "Figure" FONT_FAMILY_LABEL = "Font family" AXIS_TITLE_LABEL = "Axis title" From 242972f6b868025c50977d617a665ce01e13f289 Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Fri, 3 Jul 2020 14:26:55 +0200 Subject: [PATCH 7/8] Lint --- Orange/widgets/evaluate/owcalibrationplot.py | 1 + .../widgets/visualize/owlinearprojection.py | 5 +-- Orange/widgets/visualize/owlineplot.py | 35 ++++++++++--------- .../visualize/tests/test_owlineplot.py | 3 +- .../visualize/tests/test_owscatterplotbase.py | 7 ---- .../visualize/utils/customizableplot.py | 4 +-- Orange/widgets/visualize/utils/plotutils.py | 15 ++++---- 7 files changed, 34 insertions(+), 36 deletions(-) diff --git a/Orange/widgets/evaluate/owcalibrationplot.py b/Orange/widgets/evaluate/owcalibrationplot.py index 9869123b9c5..f30db57cc9f 100644 --- a/Orange/widgets/evaluate/owcalibrationplot.py +++ b/Orange/widgets/evaluate/owcalibrationplot.py @@ -471,6 +471,7 @@ def elided(s): text += "" text += "" return text + return None def _update_info(self): self.info_label.setText(self.get_info_text(short=True)) diff --git a/Orange/widgets/visualize/owlinearprojection.py b/Orange/widgets/visualize/owlinearprojection.py index 4d3bf0315fd..ad00d51791a 100644 --- a/Orange/widgets/visualize/owlinearprojection.py +++ b/Orange/widgets/visualize/owlinearprojection.py @@ -546,5 +546,6 @@ def get_components(self, X, Y): if __name__ == "__main__": # pragma: no cover - data = Table("iris") - WidgetPreview(OWLinearProjection).run(set_data=data, set_subset_data=data[::10]) + iris = Table("iris") + WidgetPreview(OWLinearProjection).run(set_data=iris, + set_subset_data=iris[::10]) diff --git a/Orange/widgets/visualize/owlineplot.py b/Orange/widgets/visualize/owlineplot.py index be3952dd9e6..4cacaea033d 100644 --- a/Orange/widgets/visualize/owlineplot.py +++ b/Orange/widgets/visualize/owlineplot.py @@ -107,7 +107,7 @@ def __init__(self, *args, **kwargs): def set_ticks(self, ticks): self._ticks = dict(enumerate(ticks, 1)) if ticks else {} - def tickStrings(self, values, scale, spacing): + def tickStrings(self, values, scale, _): return [self._ticks.get(v * scale, "") for v in values] @@ -159,29 +159,29 @@ def add_profiles(self, y): def remove_profiles(self): self._profile_items = None - def mouseDragEvent(self, event, axis=None): + def mouseDragEvent(self, ev, axis=None): if self._graph_state == SELECT and axis is None and self._can_select: - event.accept() - if event.button() == Qt.LeftButton: - self.update_selection_line(event.buttonDownPos(), event.pos()) - if event.isFinish(): + ev.accept() + if ev.button() == Qt.LeftButton: + self.update_selection_line(ev.buttonDownPos(), ev.pos()) + if ev.isFinish(): self.selection_line.hide() p1 = self.childGroup.mapFromParent( - event.buttonDownPos(event.button())) - p2 = self.childGroup.mapFromParent(event.pos()) + ev.buttonDownPos(ev.button())) + p2 = self.childGroup.mapFromParent(ev.pos()) self.selection_changed.emit(self.get_selected(p1, p2)) elif self._graph_state == ZOOMING or self._graph_state == PANNING: - event.ignore() - super().mouseDragEvent(event, axis=axis) + ev.ignore() + super().mouseDragEvent(ev, axis=axis) else: - event.ignore() + ev.ignore() - def mouseClickEvent(self, event): - if event.button() == Qt.RightButton: + def mouseClickEvent(self, ev): + if ev.button() == Qt.RightButton: self.autoRange() self.enableAutoRange() else: - event.accept() + ev.accept() self.selection_changed.emit(np.array(False)) def reset(self): @@ -627,6 +627,9 @@ def __init__(self, parent=None): self.subset_indices = None self.__pending_selection = self.selection self.graph_variables = [] + self.graph = None + self.group_vars = None + self.group_view = None self.setup_gui() VisualSettingsDialog(self, LinePlotGraph.initial_settings) @@ -946,5 +949,5 @@ def set_visual_settings(self, key, value): if __name__ == "__main__": - data = Table("brown-selected") - WidgetPreview(OWLinePlot).run(set_data=data, set_subset_data=data[:30]) + brown = Table("brown-selected") + WidgetPreview(OWLinePlot).run(set_data=brown, set_subset_data=brown[:30]) diff --git a/Orange/widgets/visualize/tests/test_owlineplot.py b/Orange/widgets/visualize/tests/test_owlineplot.py index fc8d5f91994..732b7802f76 100644 --- a/Orange/widgets/visualize/tests/test_owlineplot.py +++ b/Orange/widgets/visualize/tests/test_owlineplot.py @@ -151,7 +151,6 @@ def test_selection_line(self): self.send_signal(self.widget.Inputs.data, self.data) # set view-dependent click coordinates - vb = self.widget.graph.view_box event.buttonDownPos.return_value = QPointF(2.38, 4.84) event.pos.return_value = QPointF(3.58, 4.76) @@ -159,7 +158,7 @@ def test_selection_line(self): line = self.widget.graph.view_box.selection_line self.assertFalse(line.line().isNull()) - # click oon the plot resets selection + # click on the plot resets selection self.assertEqual(len(self.widget.selection), 55) self.widget.graph.view_box.mouseClickEvent(event) self.assertListEqual(self.widget.selection, []) diff --git a/Orange/widgets/visualize/tests/test_owscatterplotbase.py b/Orange/widgets/visualize/tests/test_owscatterplotbase.py index 3a05168497d..4c3dacfde26 100644 --- a/Orange/widgets/visualize/tests/test_owscatterplotbase.py +++ b/Orange/widgets/visualize/tests/test_owscatterplotbase.py @@ -1540,13 +1540,6 @@ def test_self_data(this, *_, **_1): np.testing.assert_equal(x, np.arange(10, 15)) np.testing.assert_equal(y, np.arange(20, 25)) - def test_scalable_axis_item(self): - from pyqtgraph import __version__ - # When upgraded to 0.11.1 check if resizing AxisItem font size works - # and overwritten functions generateDrawSpecs and _updateMaxTextSize - # in AxisItem (owscatterplotgraph) can be removed. - self.assertLess(__version__, "0.11.1") - if __name__ == "__main__": import unittest diff --git a/Orange/widgets/visualize/utils/customizableplot.py b/Orange/widgets/visualize/utils/customizableplot.py index d5bda45c572..a120d5e5d05 100644 --- a/Orange/widgets/visualize/utils/customizableplot.py +++ b/Orange/widgets/visualize/utils/customizableplot.py @@ -155,7 +155,7 @@ def update_num_legend_font(legend: pg.LegendItem, **settings: _SettingType): if not legend: return - for sample, label in legend.items: + for sample, _ in legend.items: sample.set_font(Updater.change_font(sample.font, settings)) legend.setGeometry(sample.boundingRect()) @@ -275,7 +275,7 @@ def update_axis(axis, **settings): self.update_setters() self._check_setters() - + def update_setters(self): pass diff --git a/Orange/widgets/visualize/utils/plotutils.py b/Orange/widgets/visualize/utils/plotutils.py index f5adbb20b62..926922ea850 100644 --- a/Orange/widgets/visualize/utils/plotutils.py +++ b/Orange/widgets/visualize/utils/plotutils.py @@ -128,7 +128,7 @@ def __init__(self, delegate, parent=None): super().__init__(parent) self.delegate = delegate - def eventFilter(self, obj, event): + def eventFilter(self, _, event): if event.type() == QEvent.GraphicsSceneHelp: return self.delegate(event) else: @@ -140,10 +140,10 @@ def __init__(self, delegate, delegate2, parent=None): self.delegate2 = delegate2 super().__init__(delegate, parent=parent) - def eventFilter(self, obj, ev): - if isinstance(ev, QGraphicsSceneMouseEvent): - self.delegate2(ev) - return super().eventFilter(obj, ev) + def eventFilter(self, obj, event): + if isinstance(event, QGraphicsSceneMouseEvent): + self.delegate2(event) + return super().eventFilter(obj, event) class InteractiveViewBox(pg.ViewBox): @@ -154,7 +154,8 @@ def __init__(self, graph, enable_menu=False): self.setMouseMode(self.PanMode) self.grabGesture(Qt.PinchGesture) - def _dragtip_pos(self): + @staticmethod + def _dragtip_pos(): return 10, 10 def updateScaleBox(self, p1, p2): @@ -259,7 +260,7 @@ def autoRange(self, padding=None, items=None, item=None): super().autoRange(padding=padding, items=items, item=item) self.tag_history() - def suggestPadding(self, axis): #no padding so that undo works correcty + def suggestPadding(self, _): # no padding so that undo works correcty return 0. def scaleHistory(self, d): From 0f0ae8eba9275010fe67fbf3c50bba89f8a1574f Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Mon, 6 Jul 2020 09:49:04 +0200 Subject: [PATCH 8/8] test_report: Fix falling import --- Orange/widgets/visualize/owboxplot.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Orange/widgets/visualize/owboxplot.py b/Orange/widgets/visualize/owboxplot.py index 558179b325f..eb0cd149f67 100644 --- a/Orange/widgets/visualize/owboxplot.py +++ b/Orange/widgets/visualize/owboxplot.py @@ -174,17 +174,16 @@ class Warning(widget.OWWidget.Warning): _pen_axis_tick.setCapStyle(Qt.FlatCap) _box_brush = QBrush(QColor(0x33, 0x88, 0xff, 0xc0)) - - _axis_font = QFont() - _axis_font.setPixelSize(12) - _label_font = QFont() - _label_font.setPixelSize(11) _attr_brush = QBrush(QColor(0x33, 0x00, 0xff)) graph_name = "box_scene" def __init__(self): super().__init__() + self._axis_font = QFont() + self._axis_font.setPixelSize(12) + self._label_font = QFont() + self._label_font.setPixelSize(11) self.dataset = None self.stats = [] self.dist = self.conts = None