Skip to content

Commit df2369d

Browse files
committed
Scatterplot: Use opacity for contrast
1 parent 3aae878 commit df2369d

File tree

2 files changed

+59
-48
lines changed

2 files changed

+59
-48
lines changed

Orange/widgets/visualize/owscatterplotgraph.py

Lines changed: 53 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
from Orange.widgets import gui
2424
from Orange.widgets.settings import Setting
2525
from Orange.widgets.utils import classdensity, colorpalettes
26-
from Orange.widgets.utils.plot import OWPalette
2726
from Orange.widgets.visualize.utils.customizableplot import Updater, \
2827
CommonParameterSetter
2928
from Orange.widgets.visualize.utils.plotutils import (
@@ -534,9 +533,7 @@ def get_size_data(self):
534533
DarkerValue = 120
535534
UnknownColor = (168, 50, 168)
536535

537-
COLOR_NOT_SUBSET = (128, 128, 128, 0)
538-
COLOR_SUBSET = (128, 128, 128, 255)
539-
COLOR_DEFAULT = (128, 128, 128, 255)
536+
COLOR_DEFAULT = (128, 128, 128)
540537

541538
MAX_VISIBLE_LABELS = 500
542539

@@ -1025,7 +1022,7 @@ def get_colors(self):
10251022
else:
10261023
return self._get_discrete_colors(c_data, subset)
10271024

1028-
def _get_same_colors(self, subset):
1025+
def _get_same_colors(self, subset, color=COLOR_DEFAULT):
10291026
"""
10301027
Return the same pen for all points while the brush color depends
10311028
upon whether the point is in the subset or not
@@ -1038,21 +1035,17 @@ def _get_same_colors(self, subset):
10381035
Returns:
10391036
(tuple): a list of pens and list of brushes
10401037
"""
1041-
color = self.plot_widget.palette().color(OWPalette.Data)
1042-
pen = [_make_pen(color, 1.5)] * self.n_shown # use a single QPen instance
1043-
1044-
# Prepare all brushes; we use the first two or the last
1045-
brushes = []
1046-
for c in (self.COLOR_SUBSET, self.COLOR_NOT_SUBSET, self.COLOR_DEFAULT):
1047-
color = QColor(*c)
1048-
if color.alpha():
1049-
color.setAlpha(self.alpha_value)
1050-
brushes.append(QBrush(color))
10511038

10521039
if subset is not None:
1053-
brush = np.where(subset, *brushes[:2])
1040+
colors = [QColor(*color, alpha)
1041+
for alpha in self._alpha_for_subsets()]
1042+
brushes = [QBrush(color) for color in colors]
1043+
brush = np.where(subset, *brushes)
10541044
else:
1055-
brush = brushes[-1:] * self.n_shown # use a single QBrush instance
1045+
qcolor = QColor(*color, self.alpha_value)
1046+
brush = np.full(self.n_shown, QBrush(qcolor))
1047+
qcolor = QColor(*color, self.alpha_value)
1048+
pen = [_make_pen(qcolor, 1.5)] * self.n_shown
10561049
return pen, brush
10571050

10581051
def _get_continuous_colors(self, c_data, subset):
@@ -1066,18 +1059,18 @@ def _get_continuous_colors(self, c_data, subset):
10661059

10671060
if np.isnan(c_data).all():
10681061
self.palette = palette
1069-
return self._get_continuous_nan_colors(len(c_data))
1062+
return self._get_same_colors(subset, self.palette.nan_color)
10701063

10711064
self.scale = DiscretizedScale(np.nanmin(c_data), np.nanmax(c_data))
10721065
bins = self.scale.get_bins()
10731066
self.palette = \
10741067
colorpalettes.BinnedContinuousPalette.from_palette(palette, bins)
10751068
colors = self.palette.values_to_colors(c_data)
1076-
brush = np.hstack(
1077-
(colors,
1078-
np.full((len(c_data), 1), self.alpha_value, dtype=np.ubyte)))
1079-
pen = (colors.astype(dtype=float) * 100 / self.DarkerValue
1080-
).astype(np.ubyte)
1069+
alphas = np.full((len(c_data), 1), self.alpha_value, dtype=np.ubyte)
1070+
brush = np.hstack((colors, alphas))
1071+
pen = np.hstack(
1072+
((colors.astype(dtype=float) * 100 / self.DarkerValue).astype(np.ubyte),
1073+
alphas))
10811074

10821075
# Reuse pens and brushes with the same colors because PyQtGraph then
10831076
# builds smaller pixmap atlas, which makes the drawing faster
@@ -1093,27 +1086,21 @@ def create_pen(col):
10931086
def create_brush(col):
10941087
return QBrush(QColor(*col))
10951088

1096-
cached_pens = {}
1097-
pen = [reuse(cached_pens, create_pen, *col) for col in pen.tolist()]
1098-
10991089
if subset is not None:
1090+
alpha_subset, alpha_unset = self._alpha_for_subsets()
11001091
brush[:, 3] = 0
1101-
brush[subset, 3] = self.alpha_value
1092+
brush[subset, 3] = alpha_subset
1093+
pen[:, 3] = alpha_unset
1094+
brush[subset, 3] = alpha_subset
11021095

1096+
cached_pens = {}
1097+
pen = [reuse(cached_pens, create_pen, *col) for col in pen.tolist()]
11031098
cached_brushes = {}
11041099
brush = np.array([reuse(cached_brushes, create_brush, *col)
11051100
for col in brush.tolist()])
11061101

11071102
return pen, brush
11081103

1109-
def _get_continuous_nan_colors(self, n):
1110-
nan_color = QColor(*self.palette.nan_color)
1111-
nan_pen = _make_pen(nan_color.darker(1.2), 1.5)
1112-
pen = np.full(n, nan_pen)
1113-
nan_brush = QBrush(nan_color)
1114-
brush = np.full(n, nan_brush)
1115-
return pen, brush
1116-
11171104
def _get_discrete_colors(self, c_data, subset):
11181105
"""
11191106
Return the pens and colors whose color represent an index into
@@ -1126,20 +1113,44 @@ def _get_discrete_colors(self, c_data, subset):
11261113
c_data[np.isnan(c_data)] = len(self.palette)
11271114
c_data = c_data.astype(int)
11281115
colors = self.palette.qcolors_w_nan
1129-
pens = np.array(
1130-
[_make_pen(col.darker(self.DarkerValue), 1.5) for col in colors])
1131-
pen = pens[c_data]
1132-
if self.alpha_value < 255:
1116+
if subset is None:
11331117
for col in colors:
11341118
col.setAlpha(self.alpha_value)
1135-
brushes = np.array([QBrush(col) for col in colors])
1136-
brush = brushes[c_data]
1119+
pens = np.array(
1120+
[_make_pen(col.darker(self.DarkerValue), 1.5)
1121+
for col in colors])
1122+
pen = pens[c_data]
1123+
brushes = np.array([QBrush(col) for col in colors])
1124+
brush = brushes[c_data]
1125+
else:
1126+
subset_colors = [QColor(col) for col in colors]
1127+
alpha_subset, alpha_unset = self._alpha_for_subsets()
1128+
for col in subset_colors:
1129+
col.setAlpha(alpha_subset)
1130+
for col in colors:
1131+
col.setAlpha(alpha_unset)
11371132

1138-
if subset is not None:
1133+
pens, subset_pens = (
1134+
np.array(
1135+
[_make_pen(col.darker(self.DarkerValue), 1.5)
1136+
for col in cols])
1137+
for cols in (colors, subset_colors))
1138+
pen = np.where(subset, subset_pens[c_data], pens[c_data])
1139+
1140+
brushes = np.array([QBrush(col) for col in subset_colors])
1141+
brush = brushes[c_data]
11391142
black = np.full(len(brush), QBrush(QColor(0, 0, 0, 0)))
11401143
brush = np.where(subset, brush, black)
11411144
return pen, brush
11421145

1146+
def _alpha_for_subsets(self):
1147+
a, b, c = 1.2, -3.2, 3
1148+
x = self.alpha_value / 255
1149+
alpha_subset = 31 + int(224 * (a * x ** 3 + b * x ** 2 + c * x))
1150+
x = 1 - x
1151+
alpha_unset = int(255 - 224 * (a * x ** 3 + b * x ** 2 + c * x))
1152+
return alpha_subset, alpha_unset
1153+
11431154
def update_colors(self):
11441155
"""
11451156
Trigger an update of point colors

Orange/widgets/visualize/tests/test_owscatterplotbase.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -635,12 +635,12 @@ def run_tests():
635635
self.master.get_subset_mask = lambda: np.arange(10) >= 5
636636
graph.update_colors()
637637
brushes = graph.scatterplot_item.data["brush"]
638-
self.assertEqual(brushes[0].color().alpha(), 0)
639-
self.assertEqual(brushes[1].color().alpha(), 0)
640-
self.assertEqual(brushes[4].color().alpha(), 0)
641-
self.assertEqual(brushes[5].color().alpha(), 123)
642-
self.assertEqual(brushes[6].color().alpha(), 123)
643-
self.assertEqual(brushes[7].color().alpha(), 123)
638+
a0 = brushes[0].color().alpha()
639+
self.assertEqual(brushes[1].color().alpha(), a0)
640+
self.assertEqual(brushes[4].color().alpha(), a0)
641+
self.assertGreater(brushes[5].color().alpha(), a0)
642+
self.assertGreater(brushes[6].color().alpha(), a0)
643+
self.assertGreater(brushes[7].color().alpha(), a0)
644644

645645
graph = self.graph
646646

0 commit comments

Comments
 (0)