Skip to content

Commit

Permalink
Scatterplot: Use opacity for contrast
Browse files Browse the repository at this point in the history
  • Loading branch information
janezd committed Nov 25, 2021
1 parent 3aae878 commit df2369d
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 48 deletions.
95 changes: 53 additions & 42 deletions Orange/widgets/visualize/owscatterplotgraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from Orange.widgets import gui
from Orange.widgets.settings import Setting
from Orange.widgets.utils import classdensity, colorpalettes
from Orange.widgets.utils.plot import OWPalette
from Orange.widgets.visualize.utils.customizableplot import Updater, \
CommonParameterSetter
from Orange.widgets.visualize.utils.plotutils import (
Expand Down Expand Up @@ -534,9 +533,7 @@ def get_size_data(self):
DarkerValue = 120
UnknownColor = (168, 50, 168)

COLOR_NOT_SUBSET = (128, 128, 128, 0)
COLOR_SUBSET = (128, 128, 128, 255)
COLOR_DEFAULT = (128, 128, 128, 255)
COLOR_DEFAULT = (128, 128, 128)

MAX_VISIBLE_LABELS = 500

Expand Down Expand Up @@ -1025,7 +1022,7 @@ def get_colors(self):
else:
return self._get_discrete_colors(c_data, subset)

def _get_same_colors(self, subset):
def _get_same_colors(self, subset, color=COLOR_DEFAULT):
"""
Return the same pen for all points while the brush color depends
upon whether the point is in the subset or not
Expand All @@ -1038,21 +1035,17 @@ def _get_same_colors(self, subset):
Returns:
(tuple): a list of pens and list of brushes
"""
color = self.plot_widget.palette().color(OWPalette.Data)
pen = [_make_pen(color, 1.5)] * self.n_shown # use a single QPen instance

# Prepare all brushes; we use the first two or the last
brushes = []
for c in (self.COLOR_SUBSET, self.COLOR_NOT_SUBSET, self.COLOR_DEFAULT):
color = QColor(*c)
if color.alpha():
color.setAlpha(self.alpha_value)
brushes.append(QBrush(color))

if subset is not None:
brush = np.where(subset, *brushes[:2])
colors = [QColor(*color, alpha)
for alpha in self._alpha_for_subsets()]
brushes = [QBrush(color) for color in colors]
brush = np.where(subset, *brushes)
else:
brush = brushes[-1:] * self.n_shown # use a single QBrush instance
qcolor = QColor(*color, self.alpha_value)
brush = np.full(self.n_shown, QBrush(qcolor))
qcolor = QColor(*color, self.alpha_value)
pen = [_make_pen(qcolor, 1.5)] * self.n_shown
return pen, brush

def _get_continuous_colors(self, c_data, subset):
Expand All @@ -1066,18 +1059,18 @@ def _get_continuous_colors(self, c_data, subset):

if np.isnan(c_data).all():
self.palette = palette
return self._get_continuous_nan_colors(len(c_data))
return self._get_same_colors(subset, self.palette.nan_color)

self.scale = DiscretizedScale(np.nanmin(c_data), np.nanmax(c_data))
bins = self.scale.get_bins()
self.palette = \
colorpalettes.BinnedContinuousPalette.from_palette(palette, bins)
colors = self.palette.values_to_colors(c_data)
brush = np.hstack(
(colors,
np.full((len(c_data), 1), self.alpha_value, dtype=np.ubyte)))
pen = (colors.astype(dtype=float) * 100 / self.DarkerValue
).astype(np.ubyte)
alphas = np.full((len(c_data), 1), self.alpha_value, dtype=np.ubyte)
brush = np.hstack((colors, alphas))
pen = np.hstack(
((colors.astype(dtype=float) * 100 / self.DarkerValue).astype(np.ubyte),
alphas))

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

cached_pens = {}
pen = [reuse(cached_pens, create_pen, *col) for col in pen.tolist()]

if subset is not None:
alpha_subset, alpha_unset = self._alpha_for_subsets()
brush[:, 3] = 0
brush[subset, 3] = self.alpha_value
brush[subset, 3] = alpha_subset
pen[:, 3] = alpha_unset
brush[subset, 3] = alpha_subset

cached_pens = {}
pen = [reuse(cached_pens, create_pen, *col) for col in pen.tolist()]
cached_brushes = {}
brush = np.array([reuse(cached_brushes, create_brush, *col)
for col in brush.tolist()])

return pen, brush

def _get_continuous_nan_colors(self, n):
nan_color = QColor(*self.palette.nan_color)
nan_pen = _make_pen(nan_color.darker(1.2), 1.5)
pen = np.full(n, nan_pen)
nan_brush = QBrush(nan_color)
brush = np.full(n, nan_brush)
return pen, brush

def _get_discrete_colors(self, c_data, subset):
"""
Return the pens and colors whose color represent an index into
Expand All @@ -1126,20 +1113,44 @@ def _get_discrete_colors(self, c_data, subset):
c_data[np.isnan(c_data)] = len(self.palette)
c_data = c_data.astype(int)
colors = self.palette.qcolors_w_nan
pens = np.array(
[_make_pen(col.darker(self.DarkerValue), 1.5) for col in colors])
pen = pens[c_data]
if self.alpha_value < 255:
if subset is None:
for col in colors:
col.setAlpha(self.alpha_value)
brushes = np.array([QBrush(col) for col in colors])
brush = brushes[c_data]
pens = np.array(
[_make_pen(col.darker(self.DarkerValue), 1.5)
for col in colors])
pen = pens[c_data]
brushes = np.array([QBrush(col) for col in colors])
brush = brushes[c_data]
else:
subset_colors = [QColor(col) for col in colors]
alpha_subset, alpha_unset = self._alpha_for_subsets()
for col in subset_colors:
col.setAlpha(alpha_subset)
for col in colors:
col.setAlpha(alpha_unset)

if subset is not None:
pens, subset_pens = (
np.array(
[_make_pen(col.darker(self.DarkerValue), 1.5)
for col in cols])
for cols in (colors, subset_colors))
pen = np.where(subset, subset_pens[c_data], pens[c_data])

brushes = np.array([QBrush(col) for col in subset_colors])
brush = brushes[c_data]
black = np.full(len(brush), QBrush(QColor(0, 0, 0, 0)))
brush = np.where(subset, brush, black)
return pen, brush

def _alpha_for_subsets(self):
a, b, c = 1.2, -3.2, 3
x = self.alpha_value / 255
alpha_subset = 31 + int(224 * (a * x ** 3 + b * x ** 2 + c * x))
x = 1 - x
alpha_unset = int(255 - 224 * (a * x ** 3 + b * x ** 2 + c * x))
return alpha_subset, alpha_unset

def update_colors(self):
"""
Trigger an update of point colors
Expand Down
12 changes: 6 additions & 6 deletions Orange/widgets/visualize/tests/test_owscatterplotbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -635,12 +635,12 @@ def run_tests():
self.master.get_subset_mask = lambda: np.arange(10) >= 5
graph.update_colors()
brushes = graph.scatterplot_item.data["brush"]
self.assertEqual(brushes[0].color().alpha(), 0)
self.assertEqual(brushes[1].color().alpha(), 0)
self.assertEqual(brushes[4].color().alpha(), 0)
self.assertEqual(brushes[5].color().alpha(), 123)
self.assertEqual(brushes[6].color().alpha(), 123)
self.assertEqual(brushes[7].color().alpha(), 123)
a0 = brushes[0].color().alpha()
self.assertEqual(brushes[1].color().alpha(), a0)
self.assertEqual(brushes[4].color().alpha(), a0)
self.assertGreater(brushes[5].color().alpha(), a0)
self.assertGreater(brushes[6].color().alpha(), a0)
self.assertGreater(brushes[7].color().alpha(), a0)

graph = self.graph

Expand Down

0 comments on commit df2369d

Please sign in to comment.