From 5a46b6cd39e42f510d61ae4691689a88d83356aa Mon Sep 17 00:00:00 2001 From: janezd Date: Fri, 19 Nov 2021 13:58:54 +0100 Subject: [PATCH] Scatterplot: Use opacity for contrast --- .../widgets/visualize/owscatterplotgraph.py | 94 +++++++++++-------- 1 file changed, 53 insertions(+), 41 deletions(-) diff --git a/Orange/widgets/visualize/owscatterplotgraph.py b/Orange/widgets/visualize/owscatterplotgraph.py index a1f5d437d10..9cc2edd16b1 100644 --- a/Orange/widgets/visualize/owscatterplotgraph.py +++ b/Orange/widgets/visualize/owscatterplotgraph.py @@ -534,9 +534,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 @@ -1025,7 +1023,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 @@ -1038,21 +1036,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): @@ -1066,18 +1060,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 @@ -1093,27 +1087,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 @@ -1126,20 +1114,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