Skip to content

Commit 8e29156

Browse files
committed
Line plot: Customizable figure
1 parent 57889a3 commit 8e29156

File tree

2 files changed

+188
-24
lines changed

2 files changed

+188
-24
lines changed

Orange/widgets/visualize/owlineplot.py

Lines changed: 152 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from typing import List
12
from xml.sax.saxutils import escape
23

34
import numpy as np
@@ -84,8 +85,9 @@ class LinePlotStyle:
8485
SELECTION_LINE_COLOR = QColor(Qt.black)
8586
SELECTION_LINE_WIDTH = 2
8687

88+
UNSELECTED_LINE_WIDTH = 1
8789
UNSELECTED_LINE_ALPHA = 100
88-
UNSELECTED_LINE_ALPHA_SEL = 50
90+
UNSELECTED_LINE_ALPHA_SEL = 50 # unselected lines, when selection exists
8991

9092
SELECTED_LINE_WIDTH = 3
9193
SELECTED_LINE_ALPHA = 170
@@ -189,6 +191,11 @@ def reset(self):
189191

190192

191193
class ParameterSetter(Setter):
194+
MEAN_LABEL = "Mean"
195+
LINE_LABEL = "Lines"
196+
SEL_LINE_LABEL = "Selected lines"
197+
RANGE_LABEL = "Range"
198+
SEL_RANGE_LABEL = "Selected range"
192199
initial_settings = {
193200
Setter.LABELS_BOX: {
194201
Setter.FONT_FAMILY_LABEL: Updater.FONT_FAMILY_SETTING,
@@ -201,9 +208,106 @@ class ParameterSetter(Setter):
201208
Setter.TITLE_LABEL: {Setter.TITLE_LABEL: ("", "")},
202209
Setter.X_AXIS_LABEL: {Setter.TITLE_LABEL: ("", "")},
203210
Setter.Y_AXIS_LABEL: {Setter.TITLE_LABEL: ("", "")},
211+
},
212+
Setter.PLOT_BOX: {
213+
MEAN_LABEL: {
214+
Updater.WIDTH_LABEL: (range(1, 15), LinePlotStyle.MEAN_WIDTH),
215+
Updater.STYLE_LABEL: (list(Updater.LINE_STYLES),
216+
Updater.DEFAULT_LINE_STYLE),
217+
},
218+
LINE_LABEL: {
219+
Updater.WIDTH_LABEL: (range(1, 15),
220+
LinePlotStyle.UNSELECTED_LINE_WIDTH),
221+
Updater.STYLE_LABEL: (list(Updater.LINE_STYLES),
222+
Updater.DEFAULT_LINE_STYLE),
223+
Updater.ALPHA_LABEL: (range(0, 255, 5),
224+
LinePlotStyle.UNSELECTED_LINE_ALPHA),
225+
Updater.ANTIALIAS_LABEL: (None, True),
226+
},
227+
SEL_LINE_LABEL: {
228+
Updater.WIDTH_LABEL: (range(1, 15),
229+
LinePlotStyle.SELECTED_LINE_WIDTH),
230+
Updater.STYLE_LABEL: (list(Updater.LINE_STYLES),
231+
Updater.DEFAULT_LINE_STYLE),
232+
Updater.ALPHA_LABEL: (range(0, 255, 5),
233+
LinePlotStyle.SELECTED_LINE_ALPHA),
234+
Updater.ANTIALIAS_LABEL: (None, False),
235+
},
236+
RANGE_LABEL: {
237+
Updater.ALPHA_LABEL: (range(0, 255, 5),
238+
LinePlotStyle.RANGE_ALPHA),
239+
},
240+
SEL_RANGE_LABEL: {
241+
Updater.ALPHA_LABEL: (range(0, 255, 5),
242+
LinePlotStyle.SELECTED_RANGE_ALPHA),
243+
},
204244
}
205245
}
206246

247+
def __init__(self):
248+
self.mean_settings = {
249+
Updater.WIDTH_LABEL: LinePlotStyle.MEAN_WIDTH,
250+
Updater.ALPHA_LABEL: 255,
251+
Updater.STYLE_LABEL: Updater.DEFAULT_LINE_STYLE,
252+
Updater.ANTIALIAS_LABEL: True,
253+
}
254+
self.line_settings = {
255+
Updater.WIDTH_LABEL: LinePlotStyle.UNSELECTED_LINE_WIDTH,
256+
Updater.ALPHA_LABEL: LinePlotStyle.UNSELECTED_LINE_ALPHA,
257+
Updater.STYLE_LABEL: Updater.DEFAULT_LINE_STYLE,
258+
Updater.ANTIALIAS_LABEL: True,
259+
}
260+
self.sel_line_settings = {
261+
Updater.WIDTH_LABEL: LinePlotStyle.SELECTED_LINE_WIDTH,
262+
Updater.ALPHA_LABEL: LinePlotStyle.SELECTED_LINE_ALPHA,
263+
Updater.STYLE_LABEL: Updater.DEFAULT_LINE_STYLE,
264+
Updater.ANTIALIAS_LABEL: False,
265+
}
266+
self.range_settings = {
267+
Updater.ALPHA_LABEL: LinePlotStyle.RANGE_ALPHA,
268+
}
269+
self.sel_range_settings = {
270+
Updater.ALPHA_LABEL: LinePlotStyle.SELECTED_RANGE_ALPHA,
271+
}
272+
super().__init__()
273+
274+
def update_setters(self):
275+
def update_mean(**settings):
276+
self.mean_settings.update(**settings)
277+
Updater.update_lines(self.mean_lines_items, **self.mean_settings)
278+
279+
def update_lines(**settings):
280+
self.line_settings.update(**settings)
281+
Updater.update_lines(self.lines_items, **self.line_settings)
282+
283+
def update_sel_lines(**settings):
284+
self.sel_line_settings.update(**settings)
285+
Updater.update_lines(self.sel_lines_items, **self.sel_line_settings)
286+
287+
def _update_brush(items, **settings):
288+
for item in items:
289+
brush = item.brush()
290+
color = brush.color()
291+
color.setAlpha(settings[Updater.ALPHA_LABEL])
292+
brush.setColor(color)
293+
item.setBrush(brush)
294+
295+
def update_range(**settings):
296+
self.range_settings.update(**settings)
297+
_update_brush(self.range_items, **settings)
298+
299+
def update_sel_range(**settings):
300+
self.sel_range_settings.update(**settings)
301+
_update_brush(self.sel_range_items, **settings)
302+
303+
self._setters[self.PLOT_BOX] = {
304+
self.MEAN_LABEL: update_mean,
305+
self.LINE_LABEL: update_lines,
306+
self.SEL_LINE_LABEL: update_sel_lines,
307+
self.RANGE_LABEL: update_range,
308+
self.SEL_RANGE_LABEL: update_sel_range,
309+
}
310+
207311
@property
208312
def title_item(self):
209313
return self.getPlotItem().titleLabel
@@ -216,10 +320,32 @@ def axis_items(self):
216320
def legend_items(self):
217321
return self.legend.items
218322

323+
@property
324+
def mean_lines_items(self):
325+
return [group.mean for group in self.groups]
326+
327+
@property
328+
def lines_items(self):
329+
return [group.profiles for group in self.groups]
330+
331+
@property
332+
def sel_lines_items(self):
333+
return [group.sel_profiles for group in self.groups] + \
334+
[group.sub_profiles for group in self.groups]
335+
336+
@property
337+
def range_items(self):
338+
return [group.range for group in self.groups]
339+
340+
@property
341+
def sel_range_items(self):
342+
return [group.sel_range for group in self.groups]
343+
219344

220345
# Customizable plot widget
221346
class LinePlotGraph(pg.PlotWidget, ParameterSetter):
222347
def __init__(self, parent):
348+
self.groups: List[ProfileGroup] = []
223349
self.bottom_axis = BottomAxisItem(orientation="bottom")
224350
self.bottom_axis.setLabel("")
225351
left_axis = AxisItem(orientation="left")
@@ -270,6 +396,7 @@ def reset(self):
270396
self.clear()
271397
self.getAxis('bottom').set_ticks(None)
272398
self.legend.hide()
399+
self.groups = []
273400

274401
def select_button_clicked(self):
275402
self.view_box.set_graph_state(SELECT)
@@ -322,20 +449,19 @@ def __create_curves(self):
322449

323450
def _get_profiles_curve(self):
324451
x, y, con = self.__get_disconnected_curve_data(self.y_data)
325-
color = QColor(self.color)
326-
color.setAlpha(LinePlotStyle.UNSELECTED_LINE_ALPHA)
327-
pen = self.make_pen(color)
328-
return pg.PlotCurveItem(x=x, y=y, connect=con, pen=pen, antialias=True)
452+
pen = self.make_pen(self.color)
453+
curve = pg.PlotCurveItem(x=x, y=y, connect=con, pen=pen)
454+
Updater.update_lines([curve], **self.graph.line_settings)
455+
return curve
329456

330457
def _get_sel_profiles_curve(self):
331-
color = QColor(self.color)
332-
color.setAlpha(LinePlotStyle.SELECTED_LINE_ALPHA)
333-
pen = self.make_pen(color, LinePlotStyle.SELECTED_LINE_WIDTH)
334-
return pg.PlotCurveItem(x=None, y=None, pen=pen, antialias=False)
458+
curve = pg.PlotCurveItem(x=None, y=None, pen=self.make_pen(self.color))
459+
Updater.update_lines([curve], **self.graph.sel_line_settings)
460+
return curve
335461

336462
def _get_range_curve(self):
337463
color = QColor(self.color)
338-
color.setAlpha(LinePlotStyle.RANGE_ALPHA)
464+
color.setAlpha(self.graph.range_settings[Updater.ALPHA_LABEL])
339465
bottom, top = nanmin(self.y_data, axis=0), nanmax(self.y_data, axis=0)
340466
return pg.FillBetweenItem(
341467
pg.PlotDataItem(x=self.x_data, y=bottom),
@@ -344,15 +470,15 @@ def _get_range_curve(self):
344470

345471
def _get_sel_range_curve(self):
346472
color = QColor(self.color)
347-
color.setAlpha(LinePlotStyle.SELECTED_RANGE_ALPHA)
473+
color.setAlpha(self.graph.sel_range_settings[Updater.ALPHA_LABEL])
348474
curve1 = curve2 = pg.PlotDataItem(x=self.x_data, y=self.__mean)
349475
return pg.FillBetweenItem(curve1, curve2, brush=color)
350476

351477
def _get_mean_curve(self):
352-
pen = self.make_pen(self.color.darker(LinePlotStyle.MEAN_DARK_FACTOR),
353-
LinePlotStyle.MEAN_WIDTH)
354-
return pg.PlotCurveItem(x=self.x_data, y=self.__mean,
355-
pen=pen, antialias=True)
478+
pen = self.make_pen(self.color.darker(LinePlotStyle.MEAN_DARK_FACTOR))
479+
curve = pg.PlotCurveItem(x=self.x_data, y=self.__mean, pen=pen)
480+
Updater.update_lines([curve], **self.graph.mean_settings)
481+
return curve
356482

357483
def _get_error_bar(self):
358484
std = nanstd(self.y_data, axis=0)
@@ -402,11 +528,12 @@ def set_visible_error(self, show_error=True, **_):
402528

403529
def update_profiles_color(self, selection):
404530
color = QColor(self.color)
405-
alpha = LinePlotStyle.UNSELECTED_LINE_ALPHA if not selection \
406-
else LinePlotStyle.UNSELECTED_LINE_ALPHA_SEL
531+
alpha = self.graph.line_settings[Updater.ALPHA_LABEL] \
532+
if not selection else LinePlotStyle.UNSELECTED_LINE_ALPHA_SEL
407533
color.setAlpha(alpha)
408-
x, y = self.profiles.getData()
409-
self.profiles.setData(x=x, y=y, pen=self.make_pen(color))
534+
pen = self.profiles.opts["pen"]
535+
pen.setColor(color)
536+
self.profiles.setPen(pen)
410537

411538
def update_sel_profiles(self, y_data):
412539
x, y, connect = self.__get_disconnected_curve_data(y_data) \
@@ -415,10 +542,10 @@ def update_sel_profiles(self, y_data):
415542

416543
def update_sel_profiles_color(self, subset):
417544
color = QColor(Qt.black) if subset else QColor(self.color)
418-
color.setAlpha(LinePlotStyle.SELECTED_LINE_ALPHA)
419-
pen = self.make_pen(color, LinePlotStyle.SELECTED_LINE_WIDTH)
420-
x, y = self.sel_profiles.getData()
421-
self.sel_profiles.setData(x=x, y=y, pen=pen)
545+
color.setAlpha(self.graph.sel_line_settings[Updater.ALPHA_LABEL])
546+
pen = self.sel_profiles.opts["pen"]
547+
pen.setColor(color)
548+
self.sel_profiles.setPen(pen)
422549

423550
def update_sub_profiles(self, y_data):
424551
x, y, connect = self.__get_disconnected_curve_data(y_data) \
@@ -674,12 +801,14 @@ def plot_groups(self):
674801
group_data = self.data[indices, self.graph_variables]
675802
self._plot_group(group_data, indices, index)
676803
self.graph.update_legend(self.group_var)
804+
self.graph.groups = self.__groups
677805
self.graph.view_box.add_profiles(data.X)
678806

679807
def _remove_groups(self):
680808
for group in self.__groups:
681809
group.remove_items()
682810
self.graph.view_box.remove_profiles()
811+
self.graph.groups = []
683812
self.__groups = []
684813

685814
def _plot_group(self, data, indices, index=None):

Orange/widgets/visualize/utils/customizableplot.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import sys
2-
from typing import Tuple, List, Dict, Callable, Iterable
2+
from typing import Tuple, List, Dict, Iterable
33

4+
from AnyQt.QtCore import Qt
45
from AnyQt.QtGui import QFont, QFontDatabase
56
from AnyQt.QtWidgets import QApplication
67

@@ -71,6 +72,21 @@ class Updater:
7172
IS_ITALIC_LABEL: (None, False)
7273
}
7374

75+
WIDTH_LABEL, ALPHA_LABEL, STYLE_LABEL, ANTIALIAS_LABEL = \
76+
"Width", "Opacity", "Style", "Antialias"
77+
LINE_STYLES = {"Solid line": Qt.SolidLine,
78+
"Dash line": Qt.DashLine,
79+
"Dot line": Qt.DotLine,
80+
"Dash dot line": Qt.DashDotLine,
81+
"Dash dot dot line": Qt.DashDotDotLine}
82+
DEFAULT_LINE_STYLE = "Solid line"
83+
LINE_SETTING: SettingsType = {
84+
WIDTH_LABEL: (range(1, 15), 1),
85+
ALPHA_LABEL: (range(0, 255, 5), 255),
86+
STYLE_LABEL: (list(LINE_STYLES), DEFAULT_LINE_STYLE),
87+
ANTIALIAS_LABEL: (None, False),
88+
}
89+
7490
@staticmethod
7591
def update_plot_title_text(title_item: pg.LabelItem, text: str):
7692
title_item.text = text
@@ -164,11 +180,30 @@ def change_font(font: QFont, settings: _SettingType) -> QFont:
164180
font.setItalic(italic)
165181
return font
166182

183+
@staticmethod
184+
def update_lines(items: List[pg.PlotCurveItem], **settings: _SettingType):
185+
assert all(s in settings for s in (Updater.ANTIALIAS_LABEL,
186+
Updater.WIDTH_LABEL,
187+
Updater.STYLE_LABEL,
188+
Updater.ALPHA_LABEL)), settings
189+
190+
for item in items:
191+
item.setData(item.xData, item.yData,
192+
antialias=settings[Updater.ANTIALIAS_LABEL])
193+
pen = item.opts["pen"]
194+
color = pen.color()
195+
color.setAlpha(settings[Updater.ALPHA_LABEL])
196+
pen.setColor(color)
197+
pen.setStyle(Updater.LINE_STYLES[settings[Updater.STYLE_LABEL]])
198+
pen.setWidth(settings[Updater.WIDTH_LABEL])
199+
item.setPen(pen)
200+
167201

168202
class BaseParameterSetter:
169203
""" Subclass to add 'setter' functionality to a plot. """
170204
LABELS_BOX = "Fonts"
171205
ANNOT_BOX = "Annotations"
206+
PLOT_BOX = "Figure"
172207

173208
FONT_FAMILY_LABEL = "Font family"
174209
AXIS_TITLE_LABEL = "Axis title"

0 commit comments

Comments
 (0)