Skip to content

Commit dadcb31

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

File tree

2 files changed

+192
-24
lines changed

2 files changed

+192
-24
lines changed

Orange/widgets/visualize/owlineplot.py

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

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

220343
# Customizable plot widget
221344
class LinePlotGraph(pg.PlotWidget, ParameterSetter):
222345
def __init__(self, parent):
346+
self.groups: List[ProfileGroup] = []
223347
self.bottom_axis = BottomAxisItem(orientation="bottom")
224348
self.bottom_axis.setLabel("")
225349
left_axis = AxisItem(orientation="left")
@@ -270,6 +394,7 @@ def reset(self):
270394
self.clear()
271395
self.getAxis('bottom').set_ticks(None)
272396
self.legend.hide()
397+
self.groups = []
273398

274399
def select_button_clicked(self):
275400
self.view_box.set_graph_state(SELECT)
@@ -322,20 +447,19 @@ def __create_curves(self):
322447

323448
def _get_profiles_curve(self):
324449
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)
450+
pen = self.make_pen(self.color)
451+
curve = pg.PlotCurveItem(x=x, y=y, connect=con, pen=pen)
452+
Updater.update_lines([curve], **self.graph.line_settings)
453+
return curve
329454

330455
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)
456+
curve = pg.PlotCurveItem(x=None, y=None, pen=self.make_pen(self.color))
457+
Updater.update_lines([curve], **self.graph.sel_line_settings)
458+
return curve
335459

336460
def _get_range_curve(self):
337461
color = QColor(self.color)
338-
color.setAlpha(LinePlotStyle.RANGE_ALPHA)
462+
color.setAlpha(self.graph.range_settings[Updater.ALPHA_LABEL])
339463
bottom, top = nanmin(self.y_data, axis=0), nanmax(self.y_data, axis=0)
340464
return pg.FillBetweenItem(
341465
pg.PlotDataItem(x=self.x_data, y=bottom),
@@ -344,15 +468,15 @@ def _get_range_curve(self):
344468

345469
def _get_sel_range_curve(self):
346470
color = QColor(self.color)
347-
color.setAlpha(LinePlotStyle.SELECTED_RANGE_ALPHA)
471+
color.setAlpha(self.graph.sel_range_settings[Updater.ALPHA_LABEL])
348472
curve1 = curve2 = pg.PlotDataItem(x=self.x_data, y=self.__mean)
349473
return pg.FillBetweenItem(curve1, curve2, brush=color)
350474

351475
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)
476+
pen = self.make_pen(self.color.darker(LinePlotStyle.MEAN_DARK_FACTOR))
477+
curve = pg.PlotCurveItem(x=self.x_data, y=self.__mean, pen=pen)
478+
Updater.update_lines([curve], **self.graph.mean_settings)
479+
return curve
356480

357481
def _get_error_bar(self):
358482
std = nanstd(self.y_data, axis=0)
@@ -402,11 +526,12 @@ def set_visible_error(self, show_error=True, **_):
402526

403527
def update_profiles_color(self, selection):
404528
color = QColor(self.color)
405-
alpha = LinePlotStyle.UNSELECTED_LINE_ALPHA if not selection \
406-
else LinePlotStyle.UNSELECTED_LINE_ALPHA_SEL
529+
alpha = self.graph.line_settings[Updater.ALPHA_LABEL] \
530+
if not selection else LinePlotStyle.UNSELECTED_LINE_ALPHA_SEL
407531
color.setAlpha(alpha)
408-
x, y = self.profiles.getData()
409-
self.profiles.setData(x=x, y=y, pen=self.make_pen(color))
532+
pen = self.profiles.opts["pen"]
533+
pen.setColor(color)
534+
self.profiles.setPen(pen)
410535

411536
def update_sel_profiles(self, y_data):
412537
x, y, connect = self.__get_disconnected_curve_data(y_data) \
@@ -415,10 +540,10 @@ def update_sel_profiles(self, y_data):
415540

416541
def update_sel_profiles_color(self, subset):
417542
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)
543+
color.setAlpha(self.graph.sel_line_settings[Updater.ALPHA_LABEL])
544+
pen = self.sel_profiles.opts["pen"]
545+
pen.setColor(color)
546+
self.sel_profiles.setPen(pen)
422547

423548
def update_sub_profiles(self, y_data):
424549
x, y, connect = self.__get_disconnected_curve_data(y_data) \
@@ -674,12 +799,14 @@ def plot_groups(self):
674799
group_data = self.data[indices, self.graph_variables]
675800
self._plot_group(group_data, indices, index)
676801
self.graph.update_legend(self.group_var)
802+
self.graph.groups = self.__groups
677803
self.graph.view_box.add_profiles(data.X)
678804

679805
def _remove_groups(self):
680806
for group in self.__groups:
681807
group.remove_items()
682808
self.graph.view_box.remove_profiles()
809+
self.graph.groups = []
683810
self.__groups = []
684811

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

Orange/widgets/visualize/utils/customizableplot.py

Lines changed: 42 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,36 @@ 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+
for item in items:
186+
antialias = settings.get(Updater.ANTIALIAS_LABEL)
187+
if antialias is not None:
188+
item.setData(item.xData, item.yData, antialias=antialias)
189+
190+
pen = item.opts["pen"]
191+
alpha = settings.get(Updater.ALPHA_LABEL)
192+
if alpha is not None:
193+
color = pen.color()
194+
color.setAlpha(alpha)
195+
pen.setColor(color)
196+
197+
style = settings.get(Updater.STYLE_LABEL)
198+
if style is not None:
199+
pen.setStyle(Updater.LINE_STYLES[style])
200+
201+
width = settings.get(Updater.WIDTH_LABEL)
202+
if width is not None:
203+
pen.setWidth(width)
204+
205+
item.setPen(pen)
206+
167207

168208
class BaseParameterSetter:
169209
""" Subclass to add 'setter' functionality to a plot. """
170210
LABELS_BOX = "Fonts"
171211
ANNOT_BOX = "Annotations"
212+
PLOT_BOX = "Figure"
172213

173214
FONT_FAMILY_LABEL = "Font family"
174215
AXIS_TITLE_LABEL = "Axis title"

0 commit comments

Comments
 (0)