def recompute_heatmap(self, points): if self.model is None or self.data is None: self.exposeObject('model_predictions', {}) self.evalJS('draw_heatmap()') return latlons = np.array(points) table = Table(Domain([self.lat_attr, self.lon_attr]), latlons) try: predictions = self.model(table) except Exception as e: self._owwidget.Error.model_error(e) return else: self._owwidget.Error.model_error.clear() class_var = self.model.domain.class_var is_regression = class_var.is_continuous if is_regression: predictions = scale(np.round(predictions, 7)) # Avoid small errors kwargs = dict( extrema=self._legend_values(class_var, [np.nanmin(predictions), np.nanmax(predictions)])) else: colorgen = ColorPaletteGenerator(len(class_var.values), class_var.colors) predictions = colorgen.getRGB(predictions) kwargs = dict( legend_labels=self._legend_values(class_var, range(len(class_var.values))), full_labels=list(class_var.values), colors=[color_to_hex(colorgen.getRGB(i)) for i in range(len(class_var.values))]) self.exposeObject('model_predictions', dict(data=predictions, **kwargs)) self.evalJS('draw_heatmap()')
def set_marker_color(self, attr, update=True): try: self._color_attr = variable = self.data.domain[attr] if len(self.data) == 0: raise Exception except Exception: self._color_attr = None self._legend_colors = [] else: if variable.is_continuous: self._raw_color_values = values = self.data.get_column_view(variable)[0].astype(float) self._scaled_color_values = scale(values) self._colorgen = ContinuousPaletteGenerator(*variable.colors) min = np.nanmin(values) self._legend_colors = (['c', self._legend_values(variable, [min, np.nanmax(values)]), [color_to_hex(i) for i in variable.colors if i]] if not np.isnan(min) else []) elif variable.is_discrete: _values = np.asarray(self.data.domain[attr].values) __values = self.data.get_column_view(variable)[0].astype(np.uint16) self._raw_color_values = _values[__values] # The joke's on you self._scaled_color_values = __values self._colorgen = ColorPaletteGenerator(len(variable.colors), variable.colors) self._legend_colors = ['d', self._legend_values(variable, range(len(_values))), list(_values), [color_to_hex(self._colorgen.getRGB(i)) for i in range(len(_values))]] finally: if update: self.redraw_markers_overlay_image(new_image=True)
def set_marker_color(self, attr, update=True): try: self._color_attr = variable = self.data.domain[attr] if len(self.data) == 0: raise Exception except Exception: self._color_attr = None self._legend_colors = [] else: if variable.is_continuous: self._raw_color_values = values = self.data.get_column_view(variable)[0].astype(float) self._scaled_color_values = scale(values) self._colorgen = ContinuousPaletteGenerator(*variable.colors) min = np.nanmin(values) self._legend_colors = (['c', self._legend_values(variable, [min, np.nanmax(values)]), [color_to_hex(i) for i in variable.colors if i]] if not np.isnan(min) else []) elif variable.is_discrete: _values = np.asarray(self.data.domain[attr].values) __values = self.data.get_column_view(variable)[0].astype(np.uint16) self._raw_color_values = _values[__values] # The joke's on you self._scaled_color_values = __values self._colorgen = ColorPaletteGenerator(len(variable.colors), variable.colors) self._legend_colors = ['d', self._legend_values(variable, range(len(_values))), list(_values), [color_to_hex(self._colorgen.getRGB(i)) for i in range(len(_values))]] finally: if update: self.redraw_markers_overlay_image(new_image=True)
def recompute_heatmap(self, points): if self.model is None or self.data is None: self.exposeObject('model_predictions', {}) self.evalJS('draw_heatmap()') return latlons = np.array(points) table = Table(Domain([self.lat_attr, self.lon_attr]), latlons) try: predictions = self.model(table) except Exception as e: self._owwidget.Error.model_error(e) return else: self._owwidget.Error.model_error.clear() class_var = self.model.domain.class_var is_regression = class_var.is_continuous if is_regression: predictions = scale(np.round(predictions, 7)) # Avoid small errors kwargs = dict( extrema=self._legend_values(class_var, [np.nanmin(predictions), np.nanmax(predictions)])) else: colorgen = ColorPaletteGenerator(len(class_var.values), class_var.colors) predictions = colorgen.getRGB(predictions) kwargs = dict( legend_labels=self._legend_values(class_var, range(len(class_var.values))), full_labels=list(class_var.values), colors=[color_to_hex(colorgen.getRGB(i)) for i in range(len(class_var.values))]) self.exposeObject('model_predictions', dict(data=predictions, **kwargs)) self.evalJS('draw_heatmap()')
def __init__(self, scatter_widget, parent=None, _="None"): gui.OWComponent.__init__(self, scatter_widget) self.view_box = InteractiveViewBox(self) self.plot_widget = pg.PlotWidget(viewBox=self.view_box, parent=parent, background="w") self.plot_widget.getPlotItem().buttonsHidden = True self.plot_widget.setAntialiasing(True) self.plot_widget.sizeHint = lambda: QtCore.QSize(500, 500) self.replot = self.plot_widget.replot ScaleScatterPlotData.__init__(self) self.density_img = None self.scatterplot_item = None self.scatterplot_item_sel = None self.labels = [] self.master = scatter_widget self.shown_attribute_indices = [] self.shown_x = "" self.shown_y = "" self.pen_colors = self.brush_colors = None self.valid_data = None # np.ndarray self.selection = None # np.ndarray self.n_points = 0 self.gui = OWPlotGUI(self) self.continuous_palette = ContinuousPaletteGenerator( QColor(255, 255, 0), QColor(0, 0, 255), True) self.discrete_palette = ColorPaletteGenerator() self.selection_behavior = 0 self.legend = self.color_legend = None self.__legend_anchor = (1, 0), (1, 0) self.__color_legend_anchor = (1, 1), (1, 1) self.scale = None # DiscretizedScale self.subset_indices = None # self.setMouseTracking(True) # self.grabGesture(QPinchGesture) # self.grabGesture(QPanGesture) self.update_grid() self._tooltip_delegate = HelpEventDelegate(self.help_event) self.plot_widget.scene().installEventFilter(self._tooltip_delegate)
def get_colors_sel(self): """ Return pens and brushes for selection markers. A pen can is set to `Qt.NoPen` if a point is not selected. All brushes are completely transparent whites. Returns: (tuple): a list of pens and a list of brushes """ nopen = QPen(Qt.NoPen) if self.selection is None: pen = [nopen] * self.n_shown else: sels = np.max(self.selection) if sels == 1: pen = np.where( self._filter_visible(self.selection), _make_pen(QColor(255, 190, 0, 255), SELECTION_WIDTH + 1), nopen) else: palette = ColorPaletteGenerator(number_of_colors=sels + 1) pen = np.choose(self._filter_visible(self.selection), [nopen] + [ _make_pen(palette[i], SELECTION_WIDTH + 1) for i in range(sels) ]) return pen, [QBrush(QColor(255, 255, 255, 0))] * self.n_shown
def redraw_selection(self, marks=None): if self.grid_cells is None: return sel_pen = QPen(QBrush(QColor(128, 128, 128)), 2) sel_pen.setCosmetic(True) mark_pen = QPen(QBrush(QColor(128, 128, 128)), 4) mark_pen.setCosmetic(True) pens = [self._grid_pen, sel_pen] mark_brush = QBrush(QColor(224, 255, 255)) sels = self.selection is not None and np.max(self.selection) palette = ColorPaletteGenerator(number_of_colors=sels + 1) brushes = [QBrush(Qt.NoBrush)] + \ [QBrush(palette[i].lighter(165)) for i in range(sels)] for y in range(self.size_y): for x in range(self.size_x - (y % 2) * self.hexagonal): cell = self.grid_cells[y, x] marked = marks is not None and marks[x, y] sel_group = self.selection is not None and self.selection[x, y] if marked: cell.setBrush(mark_brush) cell.setPen(mark_pen) else: cell.setBrush(brushes[sel_group]) cell.setPen(pens[bool(sel_group)]) cell.setZValue(marked or sel_group)
def set_node_colors(self): if not self.graph: return self.lastColorColumn = self.colorCombo.currentText() attribute = self.colorCombo.itemData(self.colorCombo.currentIndex()) assert not attribute or isinstance(attribute, Orange.data.Variable) if not attribute: for node in self.view.nodes: node.setColor(None) return table = self.graph.items() if not table: return if attribute in table.domain.class_vars: values = table[:, attribute].Y if values.ndim > 1: values = values.T elif attribute in table.domain.metas: values = table[:, attribute].metas[:, 0] elif attribute in table.domain.attributes: values = table[:, attribute].X[:, 0] else: raise RuntimeError("Shouldn't be able to select this column") if attribute.is_continuous: colors = CONTINUOUS_PALETTE[scale(values)] elif attribute.is_discrete: DISCRETE_PALETTE = ColorPaletteGenerator(len(attribute.values)) colors = DISCRETE_PALETTE[values] for node, color in zip(self.view.nodes, colors): node.setColor(color)
def get_palette(self): if not self.color_by_cluster or not self.clusters.table: return super().get_palette() colors = self.clusters.table.domain["Clusters"].colors return ColorPaletteGenerator(number_of_colors=len(colors), rgb_colors=colors)
def compute_colors_sel(self, keep_colors=False): if not keep_colors: self.pen_colors_sel = self.brush_colors_sel = None nopen = QPen(Qt.NoPen) if self.selection is not None: sels = np.max(self.selection) if sels == 1: pens = [ nopen, _make_pen(QColor(255, 190, 0, 255), SELECTION_WIDTH + 1.) ] else: # Start with the first color so that the colors of the # additional attribute in annotation (which start with 0, # unselected) will match these colors palette = ColorPaletteGenerator(number_of_colors=sels + 1) pens = [nopen] + \ [_make_pen(palette[i + 1], SELECTION_WIDTH + 1.) for i in range(sels)] pen = [pens[a] for a in self.selection[self.valid_data]] else: pen = [nopen] * self.n_points brush = [QBrush(QColor(255, 255, 255, 0))] * self.n_points return pen, brush
def colors(self): if self._colors is None: from Orange.widgets.utils.colorpalette import ColorPaletteGenerator self._colors = ColorPaletteGenerator.palette(self) colors = self.attributes.get('colors') if colors: self._colors[:len(colors)] = [hex_to_color(color) for color in colors] self._colors.flags.writeable = False return self._colors
def colors(self): if self._colors is None: from Orange.widgets.utils.colorpalette import ColorPaletteGenerator self._colors = ColorPaletteGenerator.palette(self) colors = self.attributes.get('colors') if colors: self._colors[:len(colors)] = [hex_to_color(color) for color in colors] self._colors.flags.writeable = False return self._colors
def __init__(self, scatter_widget, parent=None, _="None"): gui.OWComponent.__init__(self, scatter_widget) svb = ScatterViewBox(self) self.plot_widget = pg.PlotWidget(viewBox=svb, parent=parent) self.plot_widget.setAntialiasing(True) self.replot = self.plot_widget ScaleScatterPlotData.__init__(self) self.scatterplot_item = None self.tooltip_data = [] self.tooltip = pg.TextItem(border=pg.mkPen(200, 200, 200), fill=pg.mkBrush(250, 250, 200, 220)) self.tooltip.hide() self.labels = [] self.master = scatter_widget self.inside_colors = None self.shown_attribute_indices = [] self.shown_x = "" self.shown_y = "" self.valid_data = None # np.array self.n_points = 0 self.gui = OWPlotGUI(self) self.continuous_palette = \ ContinuousPaletteGenerator(QColor(200, 200, 200), QColor(0, 0, 0), True) self.discrete_palette = ColorPaletteGenerator() self.selection_behavior = 0 self.legend = None self.legend_position = None self.tips = TooltipManager(self) # self.setMouseTracking(True) # self.grabGesture(QPinchGesture) # self.grabGesture(QPanGesture) self.state = NOTHING self._pressed_mouse_button = 0 # Qt.NoButton self._pressed_point = None self.selection_items = [] self._current_rs_item = None self._current_ps_item = None self.polygon_close_treshold = 10 self.auto_send_selection_callback = None self.data_range = {} self.map_transform = QTransform() self.graph_area = QRectF() self.selected_points = [] self.update_grid()
def colors(self): if self._colors is None: if "colors" in self.attributes: self._colors = np.array([hex_to_color(col) for col in self.attributes["colors"]], dtype=np.uint8) else: from Orange.widgets.utils.colorpalette import ColorPaletteGenerator self._colors = ColorPaletteGenerator.palette(self) self._colors.flags.writeable = False return self._colors
def get_color(self): if self.attr_color is None: return None colors = self.attr_color.colors if self.attr_color.is_discrete: self.discrete_palette = ColorPaletteGenerator( number_of_colors=len(colors), rgb_colors=colors) else: self.continuous_palette = ContinuousPaletteGenerator(*colors) return self.attr_color
def get_palette(self): if not self.color_by_cluster or not self.clusters.table: return super().get_palette() colors = self.clusters.table.domain["Clusters"].colors # the second option is to keep widget backward compatible (Orange < 3.25) return (self.clusters.table.domain["Clusters"].palette if hasattr( self.clusters.table.domain["Clusters"], "palette") else ColorPaletteGenerator(number_of_colors=len(colors), rgb_colors=colors))
def get_color(self): if self.attr_color is None: return None colors = self.attr_color.colors if self.attr_color.is_discrete: self.discrete_palette = ColorPaletteGenerator( number_of_colors=min(len(colors), MAX), rgb_colors=colors if len(colors) <= MAX else DefaultRGBColors, ) else: self.continuous_palette = ContinuousPaletteGenerator(*colors) return self.attr_color
def compute_colors(self): no_brush = DEFAULT_SELECTION_BRUSH sels = np.max(self.selection) if sels == 1: brushes = [no_brush, no_brush] else: palette = ColorPaletteGenerator(number_of_colors=sels + 1) brushes = [no_brush] + [QBrush(palette[i]) for i in range(sels)] brush = [brushes[a] for a in self.selection] pen = [DEFAULT_SELECTION_PEN] * len(self.items) return pen, brush
def colors(self): if self._colors is None: if "colors" in self.attributes: self._colors = np.array( [hex_to_color(col) for col in self.attributes["colors"]], dtype=np.uint8) else: from Orange.widgets.utils.colorpalette import \ ColorPaletteGenerator self._colors = ColorPaletteGenerator.palette(self) self._colors.flags.writeable = False return self._colors
def get_color_index(self): color_index = -1 attr_color = self.attr_color if attr_color != "" and attr_color != "(Same color)": color_index = self.attribute_name_index[attr_color] color_var = self.data_domain[attr_color] colors = color_var.colors if color_var.is_discrete: self.discrete_palette = ColorPaletteGenerator( number_of_colors=len(colors), rgb_colors=colors) else: self.continuous_palette = ContinuousPaletteGenerator(*colors) return color_index
def colors(self): if self._colors is not None: colors = np.array(self._colors) elif not self.values: colors = np.zeros((0, 3)) # to match additional colors in vstacks else: from Orange.widgets.utils.colorpalette import ColorPaletteGenerator default = tuple(ColorPaletteGenerator.palette(self)) colors = self.attributes.get('colors', ()) colors = tuple(hex_to_color(color) for color in colors) \ + default[len(colors):] colors = np.array(colors) colors.flags.writeable = False return colors
def __init__(self, scatter_widget, parent=None, _="None"): gui.OWComponent.__init__(self, scatter_widget) self.view_box = InteractiveViewBox(self) self.plot_widget = pg.PlotWidget(viewBox=self.view_box, parent=parent, background="w") self.plot_widget.getPlotItem().buttonsHidden = True self.plot_widget.setAntialiasing(True) self.plot_widget.sizeHint = lambda: QtCore.QSize(500,500) self.replot = self.plot_widget.replot ScaleScatterPlotData.__init__(self) self.density_img = None self.scatterplot_item = None self.scatterplot_item_sel = None self.labels = [] self.master = scatter_widget self.shown_attribute_indices = [] self.shown_x = "" self.shown_y = "" self.pen_colors = self.brush_colors = None self.valid_data = None # np.ndarray self.selection = None # np.ndarray self.n_points = 0 self.gui = OWPlotGUI(self) self.continuous_palette = ContinuousPaletteGenerator( QColor(255, 255, 0), QColor(0, 0, 255), True) self.discrete_palette = ColorPaletteGenerator() self.selection_behavior = 0 self.legend = self.color_legend = None self.__legend_anchor = (1, 0), (1, 0) self.__color_legend_anchor = (1, 1), (1, 1) self.scale = None # DiscretizedScale self.subset_indices = None # self.setMouseTracking(True) # self.grabGesture(QPinchGesture) # self.grabGesture(QPanGesture) self.update_grid() self._tooltip_delegate = HelpEventDelegate(self.help_event) self.plot_widget.scene().installEventFilter(self._tooltip_delegate)
def set_node_colors(self): if not self.graph: return attribute = self.attr_color assert not attribute or isinstance(attribute, Orange.data.Variable) if self.view.legend is not None: self.view.scene().removeItem(self.view.legend) self.view.legend.clear() else: self.view.legend = LegendItem() self.view.legend.set_parent(self.view) if not attribute: for node in self.view.nodes: node.setColor(None) return table = self.graph.items() if not table: return if attribute in table.domain.class_vars: values = table[:, attribute].Y if values.ndim > 1: values = values.T elif attribute in table.domain.metas: values = table[:, attribute].metas[:, 0] elif attribute in table.domain.attributes: values = table[:, attribute].X[:, 0] else: raise RuntimeError("Shouldn't be able to select this column") if attribute.is_continuous: colors = CONTINUOUS_PALETTE[scale(values)] label = PaletteItemSample( CONTINUOUS_PALETTE, DiscretizedScale(np.nanmin(values), np.nanmax(values))) self.view.legend.addItem(label, "") self.view.legend.setGeometry(label.boundingRect()) elif attribute.is_discrete: DISCRETE_PALETTE = ColorPaletteGenerator(len(attribute.values)) colors = DISCRETE_PALETTE[values] for value, color in zip(attribute.values, DISCRETE_PALETTE): self.view.legend.addItem( ScatterPlotItem(pen=Node.Pen.DEFAULT, brush=QBrush(QColor(color)), size=10, symbol="o"), escape(value)) for node, color in zip(self.view.nodes, colors): node.setColor(color) self.view.scene().addItem(self.view.legend) self.view.legend.geometry_changed()
def get_palette(self): """ Return a palette suitable for the current `attr_color` This method must be overridden if the widget offers coloring that is not based on attribute values. """ if self.attr_color is None: return None colors = self.attr_color.colors if self.attr_color.is_discrete: return ColorPaletteGenerator( number_of_colors=min(len(colors), MAX_CATEGORIES), rgb_colors=colors if len(colors) <= MAX_CATEGORIES else DefaultRGBColors) else: return ContinuousPaletteGenerator(*colors)
def set_pen_colors(self): self.pen_normal.clear() self.pen_subset.clear() self.pen_selected.clear() color_var = self._current_color_var() if color_var != "(Same color)": colors = color_var.colors discrete_palette = ColorPaletteGenerator( number_of_colors=len(colors), rgb_colors=colors) for v in color_var.values: basecolor = discrete_palette[color_var.to_val(v)] basecolor = QColor(basecolor) basecolor.setAlphaF(0.9) self.pen_subset[v] = pg.mkPen(color=basecolor, width=1) self.pen_selected[v] = pg.mkPen(color=basecolor, width=2, style=Qt.DotLine) notselcolor = basecolor.lighter(150) notselcolor.setAlphaF(0.5) self.pen_normal[v] = pg.mkPen(color=notselcolor, width=1)
def on_changed(self): if not self.attrs or not self.all_attrs: return series = [] options = dict(series=series) plotlines = [] for i, (attr, color) in enumerate( zip(self.attrs, ColorPaletteGenerator(len(self.all_attrs))[self.attrs])): attr_name = self.all_attrs[attr][0] pac = self.acf(attr_name, self.use_pacf, False) if self.use_confint: # Confidence intervals, from: # https://www.mathworks.com/help/econ/autocorrelation-and-partial-autocorrelation.html # https://www.mathworks.com/help/signal/ug/confidence-intervals-for-sample-autocorrelation.html std = 1.96 * ( (1 + 2 * (pac[:, 1]**2).sum()) / len(self.data))**.5 # = more precise than 1.96/sqrt(N) color = '/**/ Highcharts.getOptions().colors[{}] /**/'.format( i) line = dict(color=color, width=1.5, dashStyle='dash') plotlines.append(dict(line, value=std)) plotlines.append(dict(line, value=-std)) series.append( dict( # TODO: set units to something more readable than #periods (e.g. days) data=pac, type='column', name=attr_name, zIndex=2, )) # TODO: give periods meaning (datetime names) plotlines.append(dict(value=0, color='black', width=2, zIndex=3)) if series: self.plot.chart(options, yAxis_plotLines=plotlines, xAxis_type='linear') else: self.plot.clear()
def __init__(self, scatter_widget, parent=None, _="None"): gui.OWComponent.__init__(self, scatter_widget) self.view_box = InteractiveViewBox(self) self.plot_widget = pg.PlotWidget(viewBox=self.view_box, parent=parent) self.plot_widget.setAntialiasing(True) self.replot = self.plot_widget ScaleScatterPlotData.__init__(self) self.scatterplot_item = None self.tooltip_data = [] self.tooltip = TextItem( border=pg.mkPen(200, 200, 200), fill=pg.mkBrush(250, 250, 200, 220)) self.tooltip.hide() self.labels = [] self.master = scatter_widget self.shown_attribute_indices = [] self.shown_x = "" self.shown_y = "" self.pen_colors = self.brush_colors = None self.valid_data = None # np.ndarray self.selection = None # np.ndarray self.n_points = 0 self.gui = OWPlotGUI(self) self.continuous_palette = ContinuousPaletteGenerator( QColor(255, 255, 0), QColor(0, 0, 255), True) self.discrete_palette = ColorPaletteGenerator() self.selection_behavior = 0 self.legend = self.color_legend = None self.scale = None # DiscretizedScale self.tips = TooltipManager(self) # self.setMouseTracking(True) # self.grabGesture(QPinchGesture) # self.grabGesture(QPanGesture) self.update_grid()
def compute_colors_sel(self, keep_colors=False): if not keep_colors: self.pen_colors_sel = self.brush_colors_sel = None nopen = QPen(Qt.NoPen) if self.selection is not None: sels = np.max(self.selection) if sels == 1: pens = [ nopen, _make_pen(QColor(255, 190, 0, 255), SELECTION_WIDTH + 1.) ] else: palette = ColorPaletteGenerator(number_of_colors=sels + 1) pens = [nopen] + \ [_make_pen(palette[i], SELECTION_WIDTH + 1.) for i in range(sels)] pen = [pens[a] for a in self.selection[self.valid_data]] else: pen = [nopen] * self.n_points brush = [QBrush(QColor(255, 255, 255, 0))] * self.n_points return pen, brush
def _get_colors(self): """ Defines colors for values. If colors match in all models use the union otherwise use standard colors. """ all_colors_values = self._all_color_values() base_color, base_values = all_colors_values[0] for c, v in all_colors_values[1:]: if not self._colors_match(base_color, base_values, c, v): base_color = [] break # replace base_color if longer if len(v) > len(base_color): base_color = c base_values = v if len(base_color) != len(self.class_values): return ColorPaletteGenerator.palette(len(self.class_values)) # reorder colors to widgets order colors = [None] * len(self.class_values) for c, v in zip(base_color, base_values): colors[self.class_values.index(v)] = c return colors
class OWScatterPlotGraph(gui.OWComponent, ScaleScatterPlotData): attr_color = ContextSetting("", ContextSetting.OPTIONAL) attr_label = ContextSetting("", ContextSetting.OPTIONAL) attr_shape = ContextSetting("", ContextSetting.OPTIONAL) attr_size = ContextSetting("", ContextSetting.OPTIONAL) point_width = Setting(10) alpha_value = Setting(255) show_grid = Setting(False) show_legend = Setting(True) tooltip_shows_all = Setting(False) square_granularity = Setting(3) space_between_cells = Setting(True) CurveSymbols = np.array("o x t + d s ?".split()) MinShapeSize = 6 DarkerValue = 120 UnknownColor = (168, 50, 168) def __init__(self, scatter_widget, parent=None, _="None"): gui.OWComponent.__init__(self, scatter_widget) self.view_box = InteractiveViewBox(self) self.plot_widget = pg.PlotWidget(viewBox=self.view_box, parent=parent, background="w") self.plot_widget.getPlotItem().buttonsHidden = True self.plot_widget.setAntialiasing(True) self.plot_widget.sizeHint = lambda: QtCore.QSize(500,500) self.replot = self.plot_widget.replot ScaleScatterPlotData.__init__(self) self.scatterplot_item = None self.scatterplot_item_sel = None self.labels = [] self.master = scatter_widget self.shown_attribute_indices = [] self.shown_x = "" self.shown_y = "" self.pen_colors = self.brush_colors = None self.valid_data = None # np.ndarray self.selection = None # np.ndarray self.n_points = 0 self.gui = OWPlotGUI(self) self.continuous_palette = ContinuousPaletteGenerator( QColor(255, 255, 0), QColor(0, 0, 255), True) self.discrete_palette = ColorPaletteGenerator() self.selection_behavior = 0 self.legend = self.color_legend = None self.__legend_anchor = (1, 0), (1, 0) self.__color_legend_anchor = (1, 1), (1, 1) self.scale = None # DiscretizedScale self.subset_indices = None # self.setMouseTracking(True) # self.grabGesture(QPinchGesture) # self.grabGesture(QPanGesture) self.update_grid() self._tooltip_delegate = HelpEventDelegate(self.help_event) self.plot_widget.scene().installEventFilter(self._tooltip_delegate) def new_data(self, data, subset_data=None, **args): self.plot_widget.clear() self.subset_indices = set(e.id for e in subset_data) if subset_data else None self.set_data(data, **args) def update_data(self, attr_x, attr_y): self.shown_x = attr_x self.shown_y = attr_y self.remove_legend() if self.scatterplot_item: self.plot_widget.removeItem(self.scatterplot_item) if self.scatterplot_item_sel: self.plot_widget.removeItem(self.scatterplot_item_sel) for label in self.labels: self.plot_widget.removeItem(label) self.labels = [] self.set_axis_title("bottom", "") self.set_axis_title("left", "") if self.scaled_data is None or not len(self.scaled_data): self.valid_data = None self.n_points = 0 return index_x = self.attribute_name_index[attr_x] index_y = self.attribute_name_index[attr_y] self.valid_data = self.get_valid_list([index_x, index_y]) x_data, y_data = self.get_xy_data_positions( attr_x, attr_y, self.valid_data) x_data = x_data[self.valid_data] y_data = y_data[self.valid_data] self.n_points = len(x_data) for axis, name, index in (("bottom", attr_x, index_x), ("left", attr_y, index_y)): self.set_axis_title(axis, name) var = self.data_domain[index] if isinstance(var, DiscreteVariable): self.set_labels(axis, get_variable_values_sorted(var)) else: self.set_labels(axis, None) color_data, brush_data = self.compute_colors() color_data_sel, brush_data_sel = self.compute_colors_sel() size_data = self.compute_sizes() shape_data = self.compute_symbols() self.scatterplot_item = ScatterPlotItem( x=x_data, y=y_data, data=np.arange(self.n_points), symbol=shape_data, size=size_data, pen=color_data, brush=brush_data ) self.scatterplot_item_sel = ScatterPlotItem( x=x_data, y=y_data, data=np.arange(self.n_points), symbol=shape_data, size=size_data + SELECTION_WIDTH, pen=color_data_sel, brush=brush_data_sel ) self.plot_widget.addItem(self.scatterplot_item) self.plot_widget.addItem(self.scatterplot_item_sel) self.scatterplot_item.selected_points = [] self.scatterplot_item.sigClicked.connect(self.select_by_click) self.update_labels() self.make_legend() self.view_box.init_history() self.plot_widget.replot() min_x, max_x = np.nanmin(x_data), np.nanmax(x_data) min_y, max_y = np.nanmin(y_data), np.nanmax(y_data) self.view_box.setRange( QRectF(min_x, min_y, max_x - min_x, max_y - min_y), padding=0.025) self.view_box.tag_history() def set_labels(self, axis, labels): axis = self.plot_widget.getAxis(axis) if labels: ticks = [[(i, labels[i]) for i in range(len(labels))]] axis.setTicks(ticks) else: axis.setTicks(None) def set_axis_title(self, axis, title): self.plot_widget.setLabel(axis=axis, text=title) def get_size_index(self): size_index = -1 attr_size = self.attr_size if attr_size != "" and attr_size != "(Same size)": size_index = self.attribute_name_index[attr_size] return size_index def compute_sizes(self): size_index = self.get_size_index() if size_index == -1: size_data = np.full((self.n_points,), self.point_width) else: size_data = \ self.MinShapeSize + \ self.no_jittering_scaled_data[size_index] * self.point_width size_data[np.isnan(size_data)] = self.MinShapeSize - 2 return size_data def update_sizes(self): if self.scatterplot_item: size_data = self.compute_sizes() self.scatterplot_item.setSize(size_data) self.scatterplot_item_sel.setSize(size_data + SELECTION_WIDTH) update_point_size = update_sizes def get_color_index(self): color_index = -1 attr_color = self.attr_color if attr_color != "" and attr_color != "(Same color)": color_index = self.attribute_name_index[attr_color] color_var = self.data_domain[attr_color] if isinstance(color_var, DiscreteVariable): self.discrete_palette.set_number_of_colors( len(color_var.values)) return color_index def compute_colors_sel(self, keep_colors=False): if not keep_colors: self.pen_colors_sel = self.brush_colors_sel = None def make_pen(color, width): p = QPen(color, width) p.setCosmetic(True) return p pens = [ QPen(Qt.NoPen), make_pen(QColor(255, 190, 0, 255), SELECTION_WIDTH + 1.) ] if self.selection is not None: pen = [ pens[a] for a in self.selection ] else: pen = [pens[0]] * self.n_points brush = [QBrush(QColor(255, 255, 255, 0))] * self.n_points return pen, brush def compute_colors(self, keep_colors=False): if not keep_colors: self.pen_colors = self.brush_colors = None color_index = self.get_color_index() def make_pen(color, width): p = QPen(color, width) p.setCosmetic(True) return p subset = None if self.subset_indices: subset = np.array([ ex.id in self.subset_indices for ex in self.raw_data[self.valid_data] ]) if color_index == -1: #color = "Same color" color = self.plot_widget.palette().color(OWPalette.Data) pen = [make_pen(color, 1.5)] * self.n_points if subset is not None: brush = [(QBrush(QColor(128, 128, 128, 0)), QBrush(QColor(128, 128, 128, self.alpha_value)))[s] for s in subset] else: brush = [QBrush(QColor(128, 128, 128))] * self.n_points return pen, brush c_data = self.original_data[color_index, self.valid_data] if isinstance(self.data_domain[color_index], ContinuousVariable): if self.pen_colors is None: self.scale = DiscretizedScale(np.nanmin(c_data), np.nanmax(c_data)) c_data -= self.scale.offset c_data /= self.scale.width c_data = np.floor(c_data) + 0.5 c_data /= self.scale.bins c_data = np.clip(c_data, 0, 1) palette = self.continuous_palette self.pen_colors = palette.getRGB(c_data) self.brush_colors = np.hstack( [self.pen_colors, np.full((self.n_points, 1), self.alpha_value)]) self.pen_colors *= 100 / self.DarkerValue self.pen_colors = [make_pen(QColor(*col), 1.5) for col in self.pen_colors.tolist()] if subset is not None: self.brush_colors[:, 3] = 0 self.brush_colors[subset, 3] = self.alpha_value else: self.brush_colors[:, 3] = self.alpha_value pen = self.pen_colors brush = np.array([QBrush(QColor(*col)) for col in self.brush_colors.tolist()]) else: if self.pen_colors is None: palette = self.discrete_palette n_colors = palette.number_of_colors c_data = c_data.copy() c_data[np.isnan(c_data)] = n_colors c_data = c_data.astype(int) colors = np.r_[palette.getRGB(np.arange(n_colors)), [[128, 128, 128]]] pens = np.array( [make_pen(QColor(*col).darker(self.DarkerValue), 1.5) for col in colors]) self.pen_colors = pens[c_data] self.brush_colors = np.array([ [QBrush(QColor(0, 0, 0, 0)), QBrush(QColor(col[0], col[1], col[2], self.alpha_value))] for col in colors]) self.brush_colors = self.brush_colors[c_data] if subset is not None: brush = np.where( subset, self.brush_colors[:, 1], self.brush_colors[:, 0]) else: brush = self.brush_colors[:, 1] pen = self.pen_colors return pen, brush def update_colors(self, keep_colors=False): if self.scatterplot_item: pen_data, brush_data = self.compute_colors(keep_colors) pen_data_sel, brush_data_sel = self.compute_colors_sel(keep_colors) self.scatterplot_item.setPen(pen_data, update=False, mask=None) self.scatterplot_item.setBrush(brush_data, mask=None) self.scatterplot_item_sel.setPen(pen_data_sel, update=False, mask=None) self.scatterplot_item_sel.setBrush(brush_data_sel, mask=None) if not keep_colors: self.make_legend() update_alpha_value = update_colors def create_labels(self): for x, y in zip(*self.scatterplot_item.getData()): ti = TextItem() self.plot_widget.addItem(ti) ti.setPos(x, y) self.labels.append(ti) def update_labels(self): if not self.attr_label: for label in self.labels: label.setText("") return if not self.labels: self.create_labels() label_column = self.raw_data.get_column_view(self.attr_label)[0] formatter = self.raw_data.domain[self.attr_label].str_val label_data = map(formatter, label_column) black = pg.mkColor(0, 0, 0) for label, text in zip(self.labels, label_data): label.setText(text, black) def get_shape_index(self): shape_index = -1 attr_shape = self.attr_shape if attr_shape and attr_shape != "(Same shape)" and \ len(self.data_domain[attr_shape].values) <= \ len(self.CurveSymbols): shape_index = self.attribute_name_index[attr_shape] return shape_index def compute_symbols(self): shape_index = self.get_shape_index() if shape_index == -1: shape_data = self.CurveSymbols[np.zeros(self.n_points, dtype=int)] else: shape_data = self.original_data[shape_index] shape_data[np.isnan(shape_data)] = len(self.CurveSymbols) - 1 shape_data = self.CurveSymbols[shape_data.astype(int)] return shape_data def update_shapes(self): if self.scatterplot_item: shape_data = self.compute_symbols() self.scatterplot_item.setSymbol(shape_data) self.make_legend() def update_grid(self): self.plot_widget.showGrid(x=self.show_grid, y=self.show_grid) def update_legend(self): if self.legend: self.legend.setVisible(self.show_legend) def create_legend(self): self.legend = LegendItem() self.legend.setParentItem(self.plot_widget.getViewBox()) self.legend.anchor(*self.__legend_anchor) def remove_legend(self): if self.legend: self.__legend_anchor = legend_anchor_pos(self.legend) self.legend.setParent(None) self.legend = None if self.color_legend: self.__color_legend_anchor = legend_anchor_pos(self.color_legend) self.color_legend.setParent(None) self.color_legend = None def make_legend(self): self.remove_legend() self.make_color_legend() self.make_shape_legend() self.update_legend() def make_color_legend(self): color_index = self.get_color_index() if color_index == -1: return color_var = self.data_domain[color_index] use_shape = self.get_shape_index() == color_index if isinstance(color_var, DiscreteVariable): if not self.legend: self.create_legend() palette = self.discrete_palette for i, value in enumerate(color_var.values): color = QColor(*palette.getRGB(i)) brush = color.lighter(self.DarkerValue) self.legend.addItem( ScatterPlotItem( pen=color, brush=brush, size=10, symbol=self.CurveSymbols[i] if use_shape else "o"), value) else: legend = self.color_legend = LegendItem() legend.setParentItem(self.plot_widget.getViewBox()) legend.anchor(*self.__color_legend_anchor) label = PaletteItemSample(self.continuous_palette, self.scale) legend.addItem(label, "") legend.setGeometry(label.boundingRect()) def make_shape_legend(self): shape_index = self.get_shape_index() if shape_index == -1 or shape_index == self.get_color_index(): return if not self.legend: self.create_legend() shape_var = self.data_domain[shape_index] color = self.plot_widget.palette().color(OWPalette.Data) pen = QPen(color.darker(self.DarkerValue)) color.setAlpha(self.alpha_value) for i, value in enumerate(shape_var.values): self.legend.addItem( ScatterPlotItem(pen=pen, brush=color, size=10, symbol=self.CurveSymbols[i]), value) def zoom_button_clicked(self): self.scatterplot_item.getViewBox().setMouseMode( self.scatterplot_item.getViewBox().RectMode) def pan_button_clicked(self): self.scatterplot_item.getViewBox().setMouseMode( self.scatterplot_item.getViewBox().PanMode) def select_button_clicked(self): self.scatterplot_item.getViewBox().setMouseMode( self.scatterplot_item.getViewBox().RectMode) def reset_button_clicked(self): self.view_box.autoRange() def select_by_click(self, _, points): self.select(points) def select_by_rectangle(self, value_rect): points = [point for point in self.scatterplot_item.points() if value_rect.contains(QPointF(point.pos()))] self.select(points) def unselect_all(self): self.selection = None self.update_colors(keep_colors=True) def select(self, points): # noinspection PyArgumentList keys = QApplication.keyboardModifiers() if self.selection is None or not keys & ( Qt.ShiftModifier + Qt.ControlModifier + Qt.AltModifier): self.selection = np.full(self.n_points, False, dtype=np.bool) indices = [p.data() for p in points] if keys & Qt.ControlModifier: self.selection[indices] = False elif keys & Qt.AltModifier: self.selection[indices] = 1 - self.selection[indices] else: # Handle shift and no modifiers self.selection[indices] = True self.update_colors(keep_colors=True) self.master.selection_changed() def get_selection(self): if self.selection is None: return np.array([], dtype=int) else: return np.arange(len(self.raw_data) )[self.valid_data][self.selection] def set_palette(self, p): self.plot_widget.setPalette(p) def save_to_file(self, size): pass def help_event(self, event): if self.scatterplot_item is None: return False act_pos = self.scatterplot_item.mapFromScene(event.scenePos()) points = self.scatterplot_item.pointsAt(act_pos) text = "" if len(points): for i, p in enumerate(points): index = p.data() text += "Attributes:\n" if self.tooltip_shows_all and \ len(self.data_domain.attributes) < 30: text += "".join( ' {} = {}\n'.format(attr.name, self.raw_data[index][attr]) for attr in self.data_domain.attributes) else: text += ' {} = {}\n {} = {}\n'.format( self.shown_x, self.raw_data[index][self.shown_x], self.shown_y, self.raw_data[index][self.shown_y]) if self.tooltip_shows_all: text += " ... and {} others\n\n".format( len(self.data_domain.attributes) - 2) if self.data_domain.class_var: text += 'Class:\n {} = {}\n'.format( self.data_domain.class_var.name, self.raw_data[index][self.raw_data.domain.class_var]) if i < len(points) - 1: text += '------------------\n' text = ('<span style="white-space:pre">{}</span>' .format(escape(text))) QToolTip.showText(event.screenPos(), text, widget=self.plot_widget) return True else: return False
class OWScatterPlotGraph(gui.OWComponent, ScaleScatterPlotData): selection_changed = QtCore.Signal() attr_color = ContextSetting("", ContextSetting.OPTIONAL) attr_label = ContextSetting("", ContextSetting.OPTIONAL) attr_shape = ContextSetting("", ContextSetting.OPTIONAL) attr_size = ContextSetting("", ContextSetting.OPTIONAL) point_width = Setting(10) alpha_value = Setting(255) show_grid = Setting(False) show_legend = Setting(True) send_selection_on_update = Setting(True) tooltip_shows_all = Setting(False) square_granularity = Setting(3) space_between_cells = Setting(True) CurveSymbols = np.array("o x t + d s ?".split()) MinShapeSize = 6 LighterValue = 160 def __init__(self, scatter_widget, parent=None, _="None"): gui.OWComponent.__init__(self, scatter_widget) svb = ScatterViewBox(self) self.plot_widget = pg.PlotWidget(viewBox=svb, parent=parent) self.plot_widget.setAntialiasing(True) self.replot = self.plot_widget ScaleScatterPlotData.__init__(self) self.scatterplot_item = None self.tooltip_data = [] self.tooltip = pg.TextItem(border=pg.mkPen(200, 200, 200), fill=pg.mkBrush(250, 250, 200, 220)) self.tooltip.hide() self.labels = [] self.master = scatter_widget self.inside_colors = None self.shown_attribute_indices = [] self.shown_x = "" self.shown_y = "" self.valid_data = None # np.array self.n_points = 0 self.gui = OWPlotGUI(self) self.continuous_palette = \ ContinuousPaletteGenerator(QColor(200, 200, 200), QColor(0, 0, 0), True) self.discrete_palette = ColorPaletteGenerator() self.selection_behavior = 0 self.legend = None self.legend_position = None self.tips = TooltipManager(self) # self.setMouseTracking(True) # self.grabGesture(QPinchGesture) # self.grabGesture(QPanGesture) self.state = NOTHING self._pressed_mouse_button = 0 # Qt.NoButton self._pressed_point = None self.selection_items = [] self._current_rs_item = None self._current_ps_item = None self.polygon_close_treshold = 10 self.auto_send_selection_callback = None self.data_range = {} self.map_transform = QTransform() self.graph_area = QRectF() self.selected_points = [] self.update_grid() def spot_item_clicked(self, plot, points): self.scatterplot_item.getViewBox().unselect_all() for p in points: to_selected_color(p) self.selected_points.append(p) self.selection_changed.emit() # noinspection PyPep8Naming def mouseMoved(self, pos): act_pos = self.scatterplot_item.mapFromScene(pos) points = self.scatterplot_item.pointsAt(act_pos) text = "" if len(points): for i, p in enumerate(points): index = p.data() text += "Attributes:\n" if self.tooltip_shows_all: text += "".join( ' {} = {}\n'.format(attr.name, self.raw_data[index][attr]) for attr in self.data_domain.attributes) else: text += ' {} = {}\n {} = {}\n'.format( self.shown_x, self.raw_data[index][self.shown_x], self.shown_y, self.raw_data[index][self.shown_y]) if self.data_domain.class_var: text += 'Class:\n {} = {}\n'.format( self.data_domain.class_var.name, self.raw_data[index][self.raw_data.domain.class_var]) if i < len(points) - 1: text += '------------------\n' self.tooltip.setText(text, color=(0, 0, 0)) self.tooltip.setPos(act_pos) self.tooltip.show() self.tooltip.setZValue(10) else: self.tooltip.hide() def zoom_button_clicked(self): self.scatterplot_item.getViewBox().setMouseMode( self.scatterplot_item.getViewBox().RectMode) def pan_button_clicked(self): self.scatterplot_item.getViewBox().setMouseMode( self.scatterplot_item.getViewBox().PanMode) def select_button_clicked(self): self.scatterplot_item.getViewBox().setMouseMode( self.scatterplot_item.getViewBox().RectMode) def set_data(self, data, subset_data=None, **args): self.plot_widget.clear() ScaleScatterPlotData.set_data(self, data, subset_data, **args) def update_data(self, attr_x, attr_y): self.shown_x = attr_x self.shown_y = attr_y self.remove_legend() if self.scatterplot_item: self.plot_widget.removeItem(self.scatterplot_item) for label in self.labels: self.plot_widget.removeItem(label) self.labels = [] self.tooltip_data = [] self.set_axis_title("bottom", "") self.set_axis_title("left", "") if self.scaled_data is None or not len(self.scaled_data): self.valid_data = None self.n_points = 0 return index_x = self.attribute_name_index[attr_x] index_y = self.attribute_name_index[attr_y] x_data, y_data = self.get_xy_data_positions(attr_x, attr_y) self.valid_data = self.get_valid_list([index_x, index_y]) x_data = x_data[self.valid_data] y_data = y_data[self.valid_data] self.n_points = len(x_data) for axis, name, index in (("bottom", attr_x, index_x), ("left", attr_y, index_y)): self.set_axis_title(axis, name) var = self.data_domain[index] if isinstance(var, DiscreteVariable): self.set_labels(axis, get_variable_values_sorted(var)) color_data, brush_data = self.compute_colors() size_data = self.compute_sizes() shape_data = self.compute_symbols() self.scatterplot_item = pg.ScatterPlotItem( x=x_data, y=y_data, symbol=shape_data, size=size_data, pen=color_data, brush=brush_data, data=np.arange(self.n_points)) self.plot_widget.addItem(self.scatterplot_item) self.plot_widget.addItem(self.tooltip) self.scatterplot_item.selected_points = [] self.scatterplot_item.sigClicked.connect(self.spot_item_clicked) self.scatterplot_item.scene().sigMouseMoved.connect(self.mouseMoved) self.update_labels() self.make_legend() self.plot_widget.replot() def set_labels(self, axis, labels): axis = self.plot_widget.getAxis(axis) if labels: ticks = [[(i, labels[i]) for i in range(len(labels))]] axis.setTicks(ticks) else: axis.setTicks(None) def set_axis_title(self, axis, title): self.plot_widget.setLabel(axis=axis, text=title) def get_size_index(self): size_index = -1 attr_size = self.attr_size if attr_size != "" and attr_size != "(Same size)": size_index = self.attribute_name_index[attr_size] return size_index def compute_sizes(self): size_index = self.get_size_index() if size_index == -1: size_data = np.full((self.n_points,), self.point_width) else: size_data = \ self.MinShapeSize + \ self.no_jittering_scaled_data[size_index] * self.point_width size_data[np.isnan(size_data)] = self.MinShapeSize - 2 return size_data def update_sizes(self): if self.scatterplot_item: size_data = self.compute_sizes() self.scatterplot_item.setSize(size_data) update_point_size = update_sizes def get_color_index(self): color_index = -1 attr_color = self.attr_color if attr_color != "" and attr_color != "(Same color)": color_index = self.attribute_name_index[attr_color] if isinstance(self.data_domain[attr_color], DiscreteVariable): self.discrete_palette.setNumberOfColors( len(self.data_domain[attr_color].values)) return color_index def compute_colors(self): # if self.have_subset_data: # subset_ids = [example.id for example in self.raw_subset_data] # marked_data = [example.id in subset_ids # for example in self.raw_data] # FIX! # else: # marked_data = [] color_index = self.get_color_index() if color_index == -1: color = self.color(OWPalette.Data) pen = [QPen(QBrush(color), 1.5)] * self.n_points brush = [QBrush(QColor(128, 128, 128))] * self.n_points else: if isinstance(self.data_domain[color_index], ContinuousVariable): c_data = self.no_jittering_scaled_data[color_index, self.valid_data] palette = self.continuous_palette color = [QColor(*palette.getRGB(i)) for i in c_data] pen = np.array([QPen(QBrush(col), 1.5) for col in color]) for col in color: col.setAlpha(self.alpha_value) brush = [QBrush(col.lighter(self.LighterValue)) for col in color] else: palette = self.discrete_palette n_colors = palette.numberOfColors c_data = self.original_data[color_index, self.valid_data] c_data[np.isnan(c_data)] = n_colors c_data = c_data.astype(int) colors = [palette[i] for i in range(n_colors)] + \ [QColor(128, 128, 128)] pens = np.array([QPen(QBrush(col), 1.5) for col in colors]) pen = pens[c_data] for color in colors: color.setAlpha(self.alpha_value) brushes = np.array( [QBrush(col.lighter(self.LighterValue)) for col in colors]) brush = brushes[c_data] return pen, brush def update_colors(self): if self.scatterplot_item: color_data, brush_data = self.compute_colors() self.scatterplot_item.setPen(color_data, update=False, mask=None) self.scatterplot_item.setBrush(brush_data, mask=None) self.make_legend() update_alpha_value = update_colors def create_labels(self): for x, y in zip(*self.scatterplot_item.getData()): ti = pg.TextItem() self.plot_widget.addItem(ti) ti.setPos(x, y) self.labels.append(ti) def update_labels(self): if not self.attr_label: for label in self.labels: label.setText("") return if not self.labels: self.create_labels() label_column = self.raw_data.get_column_view(self.attr_label)[0] formatter = self.raw_data.domain[self.attr_label].str_val label_data = map(formatter, label_column) black = pg.mkColor(0, 0, 0) for label, text in zip(self.labels, label_data): label.setText(text, black) def get_shape_index(self): shape_index = -1 attr_shape = self.attr_shape if attr_shape and attr_shape != "(Same shape)" and \ len(self.data_domain[attr_shape].values) <= \ len(self.CurveSymbols): shape_index = self.attribute_name_index[attr_shape] return shape_index def compute_symbols(self): shape_index = self.get_shape_index() if shape_index == -1: shape_data = self.CurveSymbols[np.zeros(self.n_points, dtype=int)] else: shape_data = self.original_data[shape_index] shape_data[np.isnan(shape_data)] = len(self.CurveSymbols) - 1 shape_data = self.CurveSymbols[shape_data.astype(int)] return shape_data def update_shapes(self): if self.scatterplot_item: shape_data = self.compute_symbols() self.scatterplot_item.setSymbol(shape_data) self.make_legend() def update_grid(self): self.plot_widget.showGrid(x=self.show_grid, y=self.show_grid) def update_legend(self): if self.legend: self.legend.setVisible(self.show_legend) def create_legend(self): legend = self.legend = pg.graphicsItems.LegendItem.LegendItem() legend.layout.setHorizontalSpacing(15) legend.setParentItem(self.plot_widget.plotItem) if self.legend_position: legend.anchor(itemPos=(0, 0), parentPos=(0, 0), offset=self.legend_position) else: legend.anchor(itemPos=(1, 0), parentPos=(1, 0), offset=(-10, 10)) def remove_legend(self): if self.legend: self.legend_position = self.legend.pos() self.legend.setParent(None) self.legend = None def make_legend(self): self.remove_legend() self.make_color_legend() self.make_shape_legend() self.update_legend() def make_color_legend(self): color_index = self.get_color_index() if color_index == -1: return if not self.legend: self.create_legend() color_var = self.data_domain[color_index] use_shape = self.get_shape_index() == color_index if isinstance(color_var, DiscreteVariable): palette = self.discrete_palette for i, value in enumerate(color_var.values): color = QColor(*palette.getRGB(i)) brush = color.lighter(self.LighterValue) self.legend.addItem( pg.ScatterPlotItem( pen=color, brush=brush, size=10, symbol=self.CurveSymbols[i] if use_shape else "o"), value) # else: # amin, amax = self.attr_values[self.attr_color] # values = [color_var.valstr(v) for v in np.arange(amin, amax, (amin - amax) / 10)] # GradientLegendItem("X", self.continuous_palette, values, self.legend) def make_shape_legend(self): shape_index = self.get_shape_index() # Also don't create if same as color if shape_index == -1 or shape_index == self.get_color_index(): return if not self.legend: self.create_legend() shape_var = self.data_domain[shape_index] color = self.color(OWPalette.Data) brush = color.lighter(self.LighterValue) brush.setAlpha(self.alpha_value) for i, value in enumerate(shape_var.values): self.legend.addItem( pg.ScatterPlotItem(pen=color, brush=brush, size=10, symbol=self.CurveSymbols[i]), value) def get_selections_as_tables(self, attr_list): attr_x, attr_y = attr_list if not self.have_data: return None, None sel_indices, unsel_indices = self.get_selections_as_indices(attr_list) if type(self.raw_data) is SqlTable: selected = [self.raw_data[i] for i, val in enumerate(sel_indices) if val] unselected = [self.raw_data[i] for (i, val) in enumerate(unsel_indices) if val] else: selected = self.raw_data[np.array(sel_indices)] unselected = self.raw_data[np.array(unsel_indices)] if len(selected) == 0: selected = None if len(unselected) == 0: unselected = None return selected, unselected def get_selections_as_indices(self, attr_list, valid_data=None): attr_x, attr_y = attr_list if not self.have_data: return [], [] attr_indices = [self.attribute_name_index[attr] for attr in attr_list] if valid_data is None: valid_data = self.get_valid_list(attr_indices) x_array, y_array = self.get_xy_data_positions(attr_x, attr_y) return self.get_selected_points(x_array, y_array, valid_data) def get_selected_points(self, xData, yData, validData): # hoping that the indices will be in same order as raw_data ## TODO check if actually selecting the right points selectedIndices = [p.data() for p in self.selected_points] selected = [i in selectedIndices for i in range(len(self.raw_data))] unselected = [i not in selectedIndices for i in range(len(self.raw_data))] return selected, unselected def color(self, role, group=None): if group: return self.plot_widget.palette().color(group, role) else: return self.plot_widget.palette().color(role) def set_palette(self, p): self.plot_widget.setPalette(p) def send_selection(self): if self.auto_send_selection_callback: self.auto_send_selection_callback() def clear_selection(self): # called from zoom/select toolbar button 'clear selection' # self.scatterplot_item.getViewBox().unselect_all() pass def update_animations(self, use_animations=None): if use_animations is not None: self.animate_plot = use_animations self.animate_points = use_animations def save_to_file(self, size): pass
class LeafletMap(WebviewWidget): selectionChanged = pyqtSignal(list) def __init__(self, parent=None): class Bridge(QObject): @pyqtSlot() def fit_to_bounds(_): return self.fit_to_bounds() @pyqtSlot(float, float, float, float) def selected_area(_, *args): return self.selected_area(*args) @pyqtSlot('QVariantList') def recompute_heatmap(_, *args): return self.recompute_heatmap(*args) @pyqtSlot(float, float, float, float, int, int, float, 'QVariantList', 'QVariantList') def redraw_markers_overlay_image(_, *args): return self.redraw_markers_overlay_image(*args) super().__init__(parent, bridge=Bridge(), url=QUrl(self.toFileURL( os.path.join(os.path.dirname(__file__), '_leaflet', 'owmap.html'))), debug=True,) self.jittering = 0 self._jittering_offsets = None self._owwidget = parent self._opacity = 255 self._sizes = None self._selected_indices = None self.lat_attr = None self.lon_attr = None self.data = None self.model = None self._domain = None self._latlon_data = None self._jittering = None self._color_attr = None self._label_attr = None self._shape_attr = None self._size_attr = None self._legend_colors = [] self._legend_shapes = [] self._legend_sizes = [] self._drawing_args = None self._image_token = None self._prev_map_pane_pos = None self._prev_origin = None self._overlay_image_path = mkstemp(prefix='orange-Map-', suffix='.png')[1] self._subset_ids = np.array([]) self.is_js_path = None def __del__(self): os.remove(self._overlay_image_path) self._image_token = np.nan def set_data(self, data, lat_attr, lon_attr): self.data = data self._image_token = np.nan # Stop drawing previous image self._owwidget.progressBarFinished(None) if (data is None or not len(data) or lat_attr not in data.domain or lon_attr not in data.domain): self.data = None self.evalJS('clear_markers_js(); clear_markers_overlay_image();') self._legend_colors = [] self._legend_shapes = [] self._legend_sizes = [] self._update_legend() return lat_attr = data.domain[lat_attr] lon_attr = data.domain[lon_attr] fit_bounds = (self._domain != data.domain or self.lat_attr is not lat_attr or self.lon_attr is not lon_attr) self.lat_attr = lat_attr self.lon_attr = lon_attr self._domain = data.domain self._latlon_data = np.array([ self.data.get_column_view(self.lat_attr)[0], self.data.get_column_view(self.lon_attr)[0]], dtype=float, order='F').T self._recompute_jittering_offsets() if fit_bounds: QTimer.singleShot(1, self.fit_to_bounds) else: self.redraw_markers_overlay_image(new_image=True) def fit_to_bounds(self, fly=True): if self.data is None: return lat_data, lon_data = self._latlon_data.T north, south = np.nanmax(lat_data), np.nanmin(lat_data) east, west = np.nanmin(lon_data), np.nanmax(lon_data) script = ('map.%sBounds([[%f, %f], [%f, %f]], {padding: [0,0], minZoom: 2, maxZoom: 13})' % ('flyTo' if fly else 'fit', south, west, north, east)) self.evalJS(script) # Sometimes on first data, it doesn't zoom in enough. So let do it # once more for good measure! self.evalJS(script) def selected_area(self, north, east, south, west): indices = np.array([]) prev_selected_indices = self._selected_indices if self.data is not None and (north != south and east != west): lat, lon = self._latlon_data.T indices = ((lat <= north) & (lat >= south) & (lon <= east) & (lon >= west)) if self._selected_indices is not None: indices |= self._selected_indices self._selected_indices = indices else: self._selected_indices = None if np.any(self._selected_indices != prev_selected_indices): self.selectionChanged.emit(indices.nonzero()[0].tolist()) self.redraw_markers_overlay_image(new_image=True) def set_map_provider(self, provider): self.evalJS('set_map_provider("{}");'.format(provider)) def set_clustering(self, cluster_points): self.evalJS(''' window.cluster_points = {}; set_cluster_points(); '''.format(int(cluster_points))) def _recompute_jittering_offsets(self): if not self._jittering: self._jittering_offsets = None elif self.data: # Calculate offsets randomly distributed within a circle screen_size = max(100, min(qApp.desktop().screenGeometry().width(), qApp.desktop().screenGeometry().height())) n = len(self.data) r = np.random.random(n) theta = np.random.uniform(0, 2*np.pi, n) xy_offsets = screen_size * self._jittering * np.c_[r * np.cos(theta), r * np.sin(theta)] self._jittering_offsets = xy_offsets def set_jittering(self, jittering): """ In percent, i.e. jittering=3 means 3% of screen height and width """ self._jittering = jittering / 100 self._recompute_jittering_offsets() self.redraw_markers_overlay_image(new_image=True) @staticmethod def _legend_values(variable, values): strs = [variable.repr_val(val) for val in values] if any(len(val) > 10 for val in strs): if isinstance(variable, TimeVariable): strs = [s.replace(' ', '<br>') for s in strs] elif variable.is_continuous: strs = ['{:.4e}'.format(val) for val in values] elif variable.is_discrete: strs = [s if len(s) <= 12 else (s[:8] + '…' + s[-3:]) for s in strs] return strs def set_marker_color(self, attr, update=True): try: self._color_attr = variable = self.data.domain[attr] if len(self.data) == 0: raise Exception except Exception: self._color_attr = None self._legend_colors = [] else: if variable.is_continuous: self._raw_color_values = values = self.data.get_column_view(variable)[0].astype(float) self._scaled_color_values = scale(values) self._colorgen = ContinuousPaletteGenerator(*variable.colors) min = np.nanmin(values) self._legend_colors = (['c', self._legend_values(variable, [min, np.nanmax(values)]), [color_to_hex(i) for i in variable.colors if i]] if not np.isnan(min) else []) elif variable.is_discrete: _values = np.asarray(self.data.domain[attr].values) __values = self.data.get_column_view(variable)[0].astype(np.uint16) self._raw_color_values = _values[__values] # The joke's on you self._scaled_color_values = __values self._colorgen = ColorPaletteGenerator(len(variable.colors), variable.colors) self._legend_colors = ['d', self._legend_values(variable, range(len(_values))), list(_values), [color_to_hex(self._colorgen.getRGB(i)) for i in range(len(_values))]] finally: if update: self.redraw_markers_overlay_image(new_image=True) def set_marker_label(self, attr, update=True): try: self._label_attr = variable = self.data.domain[attr] if len(self.data) == 0: raise Exception except Exception: self._label_attr = None else: if variable.is_continuous or variable.is_string: self._label_values = self.data.get_column_view(variable)[0] elif variable.is_discrete: _values = np.asarray(self.data.domain[attr].values) __values = self.data.get_column_view(variable)[0].astype(np.uint16) self._label_values = _values[__values] # The design had lead to poor code for ages finally: if update: self.redraw_markers_overlay_image(new_image=True) def set_marker_shape(self, attr, update=True): try: self._shape_attr = variable = self.data.domain[attr] if len(self.data) == 0: raise Exception except Exception: self._shape_attr = None self._legend_shapes = [] else: assert variable.is_discrete _values = np.asarray(self.data.domain[attr].values) self._shape_values = __values = self.data.get_column_view(variable)[0].astype(np.uint16) self._raw_shape_values = _values[__values] self._legend_shapes = [self._legend_values(variable, range(len(_values))), list(_values)] finally: if update: self.redraw_markers_overlay_image(new_image=True) def set_marker_size(self, attr, update=True): try: self._size_attr = variable = self.data.domain[attr] if len(self.data) == 0: raise Exception except Exception: self._size_attr = None self._legend_sizes = [] else: assert variable.is_continuous self._raw_sizes = values = self.data.get_column_view(variable)[0].astype(float) # Note, [5, 60] is also hardcoded in legend-size-indicator.svg self._sizes = scale(values, 5, 60).astype(np.uint8) min = np.nanmin(values) self._legend_sizes = self._legend_values(variable, [min, np.nanmax(values)]) if not np.isnan(min) else [] finally: if update: self.redraw_markers_overlay_image(new_image=True) def set_marker_size_coefficient(self, size): self._size_coef = size / 100 self.evalJS('''set_marker_size_coefficient({});'''.format(size / 100)) if not self.is_js_path: self.redraw_markers_overlay_image(new_image=True) def set_marker_opacity(self, opacity): self._opacity = 255 * opacity // 100 self.evalJS('''set_marker_opacity({});'''.format(opacity / 100)) if not self.is_js_path: self.redraw_markers_overlay_image(new_image=True) def set_model(self, model): self.model = model self.evalJS('clear_heatmap()' if model is None else 'reset_heatmap()') def recompute_heatmap(self, points): if self.model is None or self.data is None: self.exposeObject('model_predictions', {}) self.evalJS('draw_heatmap()') return latlons = np.array(points) table = Table(Domain([self.lat_attr, self.lon_attr]), latlons) try: predictions = self.model(table) except Exception as e: self._owwidget.Error.model_error(e) return else: self._owwidget.Error.model_error.clear() class_var = self.model.domain.class_var is_regression = class_var.is_continuous if is_regression: predictions = scale(np.round(predictions, 7)) # Avoid small errors kwargs = dict( extrema=self._legend_values(class_var, [np.nanmin(predictions), np.nanmax(predictions)])) else: colorgen = ColorPaletteGenerator(len(class_var.values), class_var.colors) predictions = colorgen.getRGB(predictions) kwargs = dict( legend_labels=self._legend_values(class_var, range(len(class_var.values))), full_labels=list(class_var.values), colors=[color_to_hex(colorgen.getRGB(i)) for i in range(len(class_var.values))]) self.exposeObject('model_predictions', dict(data=predictions, **kwargs)) self.evalJS('draw_heatmap()') def _update_legend(self, is_js_path=False): self.evalJS(''' window.legend_colors = %s; window.legend_shapes = %s; window.legend_sizes = %s; legendControl.remove(); legendControl.addTo(map); ''' % (self._legend_colors, self._legend_shapes if is_js_path else [], self._legend_sizes)) def _update_js_markers(self, visible, in_subset): self._visible = visible latlon = self._latlon_data self.exposeObject('latlon_data', dict(data=latlon[visible])) self.exposeObject('jittering_offsets', self._jittering_offsets[visible] if self._jittering_offsets is not None else []) self.exposeObject('selected_markers', dict(data=(self._selected_indices[visible] if self._selected_indices is not None else 0))) self.exposeObject('in_subset', in_subset.astype(np.int8)) if not self._color_attr: self.exposeObject('color_attr', dict()) else: colors = [color_to_hex(rgb) for rgb in self._colorgen.getRGB(self._scaled_color_values[visible])] self.exposeObject('color_attr', dict(name=str(self._color_attr), values=colors, raw_values=self._raw_color_values[visible])) if not self._label_attr: self.exposeObject('label_attr', dict()) else: self.exposeObject('label_attr', dict(name=str(self._label_attr), values=self._label_values[visible])) if not self._shape_attr: self.exposeObject('shape_attr', dict()) else: self.exposeObject('shape_attr', dict(name=str(self._shape_attr), values=self._shape_values[visible], raw_values=self._raw_shape_values[visible])) if not self._size_attr: self.exposeObject('size_attr', dict()) else: self.exposeObject('size_attr', dict(name=str(self._size_attr), values=self._sizes[visible], raw_values=self._raw_sizes[visible])) self.evalJS(''' window.latlon_data = latlon_data.data; window.selected_markers = selected_markers.data; add_markers(latlon_data); ''') class Projection: """This should somewhat model Leaflet's Web Mercator (EPSG:3857). Reverse-engineered from L.Map.latlngToContainerPoint(). """ @staticmethod def latlon_to_easting_northing(lat, lon): R = 6378137 MAX_LATITUDE = 85.0511287798 DEG = np.pi / 180 lat = np.clip(lat, -MAX_LATITUDE, MAX_LATITUDE) sin = np.sin(DEG * lat) x = R * DEG * lon y = R / 2 * np.log((1 + sin) / (1 - sin)) return x, y @staticmethod def easting_northing_to_pixel(x, y, zoom_level, pixel_origin, map_pane_pos): R = 6378137 PROJ_SCALE = .5 / (np.pi * R) zoom_scale = 256 * (2 ** zoom_level) x = (zoom_scale * (PROJ_SCALE * x + .5)).round() + (map_pane_pos[0] - pixel_origin[0]) y = (zoom_scale * (-PROJ_SCALE * y + .5)).round() + (map_pane_pos[1] - pixel_origin[1]) return x, y N_POINTS_PER_ITER = 666 def redraw_markers_overlay_image(self, *args, new_image=False): if not args and not self._drawing_args or self.data is None: return if args: self._drawing_args = args north, east, south, west, width, height, zoom, origin, map_pane_pos = self._drawing_args lat, lon = self._latlon_data.T visible = ((lat <= north) & (lat >= south) & (lon <= east) & (lon >= west)).nonzero()[0] in_subset = (np.in1d(self.data.ids, self._subset_ids) if self._subset_ids.size else np.tile(True, len(lon))) is_js_path = self.is_js_path = len(visible) < self.N_POINTS_PER_ITER self._update_legend(is_js_path) np.random.shuffle(visible) # Sort points in subset to be painted last visible = visible[np.lexsort((in_subset[visible],))] if is_js_path: self.evalJS('clear_markers_overlay_image()') self._update_js_markers(visible, in_subset[visible]) self._owwidget.disable_some_controls(False) return self.evalJS('clear_markers_js();') self._owwidget.disable_some_controls(True) selected = (self._selected_indices if self._selected_indices is not None else np.zeros(len(lat), dtype=bool)) cur = 0 im = QImage(self._overlay_image_path) if im.isNull() or self._prev_origin != origin or new_image: im = QImage(width, height, QImage.Format_ARGB32) im.fill(Qt.transparent) else: dx, dy = self._prev_map_pane_pos - map_pane_pos im = im.copy(dx, dy, width, height) self._prev_map_pane_pos = np.array(map_pane_pos) self._prev_origin = origin painter = QPainter(im) painter.setRenderHint(QPainter.Antialiasing, True) self.evalJS('clear_markers_overlay_image(); markersImageLayer.setBounds(map.getBounds());0') self._image_token = image_token = np.random.random() n_iters = np.ceil(len(visible) / self.N_POINTS_PER_ITER) def add_points(): nonlocal cur, image_token if image_token != self._image_token: return batch = visible[cur:cur + self.N_POINTS_PER_ITER] batch_lat = lat[batch] batch_lon = lon[batch] x, y = self.Projection.latlon_to_easting_northing(batch_lat, batch_lon) x, y = self.Projection.easting_northing_to_pixel(x, y, zoom, origin, map_pane_pos) if self._jittering: dx, dy = self._jittering_offsets[batch].T x, y = x + dx, y + dy colors = (self._colorgen.getRGB(self._scaled_color_values[batch]).tolist() if self._color_attr else repeat((0xff, 0, 0))) sizes = self._size_coef * \ (self._sizes[batch] if self._size_attr else np.tile(10, len(batch))) opacity_subset, opacity_rest = self._opacity, int(.8 * self._opacity) for x, y, is_selected, size, color, _in_subset in \ zip(x, y, selected[batch], sizes, colors, in_subset[batch]): pensize2, selpensize2 = (.35, 1.5) if size >= 5 else (.15, .7) pensize2 *= self._size_coef selpensize2 *= self._size_coef size2 = size / 2 if is_selected: painter.setPen(QPen(QBrush(Qt.green), 2 * selpensize2)) painter.drawEllipse(x - size2 - selpensize2, y - size2 - selpensize2, size + selpensize2, size + selpensize2) color = QColor(*color) if _in_subset: color.setAlpha(opacity_subset) painter.setBrush(QBrush(color)) painter.setPen(QPen(QBrush(color.darker(180)), 2 * pensize2)) else: color.setAlpha(opacity_rest) painter.setBrush(Qt.NoBrush) painter.setPen(QPen(QBrush(color.lighter(120)), 2 * pensize2)) painter.drawEllipse(x - size2 - pensize2, y - size2 - pensize2, size + pensize2, size + pensize2) im.save(self._overlay_image_path, 'PNG') self.evalJS('markersImageLayer.setUrl("{}#{}"); 0;' .format(self.toFileURL(self._overlay_image_path), np.random.random())) cur += self.N_POINTS_PER_ITER if cur < len(visible): QTimer.singleShot(10, add_points) self._owwidget.progressBarAdvance(100 / n_iters, None) else: self._owwidget.progressBarFinished(None) self._owwidget.progressBarFinished(None) self._owwidget.progressBarInit(None) QTimer.singleShot(10, add_points) def set_subset_ids(self, ids): self._subset_ids = ids self.redraw_markers_overlay_image(new_image=True) def toggle_legend(self, visible): self.evalJS(''' $(".legend").{0}(); window.legend_hidden = "{0}"; '''.format('show' if visible else 'hide'))
c.createColorButton(box, CANVAS_COLOR, "Canvas color", self.graph.color(OWPalette.Canvas)) c.setColorSchemas(self.color_settings, self.selected_schema_index) return c def attributes_changed(self): if not self.__ignore_updates: self.graph.removeAllSelections() self.update_graph() self.send_shown_attributes() #test widget appearance if __name__ == "__main__": a = QApplication(sys.argv) ow = OWParallelCoordinates() ow.show() ow.graph.discrete_palette = ColorPaletteGenerator( rgb_colors=[(127, 201, 127), (190, 174, 212), (253, 192, 134)]) ow.graph.group_lines = True ow.graph.number_of_groups = 10 ow.graph.number_of_steps = 30 data = Orange.data.Table("iris") ow.set_data(data) ow.handleNewSignals() a.exec_() ow.saveSettings()
class LeafletMap(WebviewWidget): selectionChanged = pyqtSignal(list) def __init__(self, parent=None): class Bridge(QObject): @pyqtSlot() def fit_to_bounds(_): return self.fit_to_bounds() @pyqtSlot(float, float, float, float) def selected_area(_, *args): return self.selected_area(*args) @pyqtSlot('QVariantList') def recompute_heatmap(_, *args): return self.recompute_heatmap(*args) @pyqtSlot(float, float, float, float, int, int, float, 'QVariantList', 'QVariantList') def redraw_markers_overlay_image(_, *args): return self.redraw_markers_overlay_image(*args) super().__init__(parent, bridge=Bridge(), url=QUrl(self.toFileURL( os.path.join(os.path.dirname(__file__), '_owmap', 'owmap.html'))), debug=True,) self.jittering = 0 self._jittering_offsets = None self._owwidget = parent self._opacity = 255 self._sizes = None self._selected_indices = None self.lat_attr = None self.lon_attr = None self.data = None self.model = None self._domain = None self._latlon_data = None self._jittering = None self._color_attr = None self._label_attr = None self._shape_attr = None self._size_attr = None self._legend_colors = [] self._legend_shapes = [] self._legend_sizes = [] self._drawing_args = None self._image_token = None self._prev_map_pane_pos = None self._prev_origin = None self._overlay_image_path = mkstemp(prefix='orange-Map-', suffix='.png')[1] self._subset_ids = np.array([]) self.is_js_path = None def __del__(self): os.remove(self._overlay_image_path) self._image_token = np.nan def set_data(self, data, lat_attr, lon_attr): self.data = data self._image_token = np.nan # Stop drawing previous image self._owwidget.progressBarFinished(None) if (data is None or not len(data) or lat_attr not in data.domain or lon_attr not in data.domain): self.data = None self.evalJS('clear_markers_js(); clear_markers_overlay_image();') self._legend_colors = [] self._legend_shapes = [] self._legend_sizes = [] self._update_legend() return lat_attr = data.domain[lat_attr] lon_attr = data.domain[lon_attr] fit_bounds = (self._domain != data.domain or self.lat_attr is not lat_attr or self.lon_attr is not lon_attr) self.lat_attr = lat_attr self.lon_attr = lon_attr self._domain = data.domain self._latlon_data = np.array([ self.data.get_column_view(self.lat_attr)[0], self.data.get_column_view(self.lon_attr)[0]], dtype=float, order='F').T self._recompute_jittering_offsets() if fit_bounds: QTimer.singleShot(1, self.fit_to_bounds) else: self.redraw_markers_overlay_image(new_image=True) def fit_to_bounds(self, fly=True): if self.data is None: return lat_data, lon_data = self._latlon_data.T north, south = np.nanmax(lat_data), np.nanmin(lat_data) east, west = np.nanmin(lon_data), np.nanmax(lon_data) script = ('map.%sBounds([[%f, %f], [%f, %f]], {padding: [0,0], minZoom: 2, maxZoom: 13})' % ('flyTo' if fly else 'fit', south, west, north, east)) self.evalJS(script) # Sometimes on first data, it doesn't zoom in enough. So let do it # once more for good measure! self.evalJS(script) def selected_area(self, north, east, south, west): indices = np.array([]) prev_selected_indices = self._selected_indices if self.data is not None and (north != south and east != west): lat, lon = self._latlon_data.T indices = ((lat <= north) & (lat >= south) & (lon <= east) & (lon >= west)) if self._selected_indices is not None: indices |= self._selected_indices self._selected_indices = indices else: self._selected_indices = None if np.any(self._selected_indices != prev_selected_indices): self.selectionChanged.emit(indices.nonzero()[0].tolist()) self.redraw_markers_overlay_image(new_image=True) def set_map_provider(self, provider): self.evalJS('set_map_provider("{}");'.format(provider)) def set_clustering(self, cluster_points): self.evalJS(''' window.cluster_points = {}; set_cluster_points(); '''.format(int(cluster_points))) def _recompute_jittering_offsets(self): if not self._jittering: self._jittering_offsets = None elif self.data: # Calculate offsets randomly distributed within a circle screen_size = max(100, min(qApp.desktop().screenGeometry().width(), qApp.desktop().screenGeometry().height())) n = len(self.data) r = np.random.random(n) theta = np.random.uniform(0, 2*np.pi, n) xy_offsets = screen_size * self._jittering * np.c_[r * np.cos(theta), r * np.sin(theta)] self._jittering_offsets = xy_offsets def set_jittering(self, jittering): """ In percent, i.e. jittering=3 means 3% of screen height and width """ self._jittering = jittering / 100 self._recompute_jittering_offsets() self.redraw_markers_overlay_image(new_image=True) @staticmethod def _legend_values(variable, values): strs = [variable.repr_val(val) for val in values] if any(len(val) > 10 for val in strs): if isinstance(variable, TimeVariable): strs = [s.replace(' ', '<br>') for s in strs] elif variable.is_continuous: strs = ['{:.4e}'.format(val) for val in values] elif variable.is_discrete: strs = [s if len(s) <= 12 else (s[:8] + '…' + s[-3:]) for s in strs] return strs def set_marker_color(self, attr, update=True): try: self._color_attr = variable = self.data.domain[attr] if len(self.data) == 0: raise Exception except Exception: self._color_attr = None self._legend_colors = [] else: if variable.is_continuous: self._raw_color_values = values = self.data.get_column_view(variable)[0].astype(float) self._scaled_color_values = scale(values) self._colorgen = ContinuousPaletteGenerator(*variable.colors) min = np.nanmin(values) self._legend_colors = (['c', self._legend_values(variable, [min, np.nanmax(values)]), [color_to_hex(i) for i in variable.colors if i]] if not np.isnan(min) else []) elif variable.is_discrete: _values = np.asarray(self.data.domain[attr].values) __values = self.data.get_column_view(variable)[0].astype(np.uint16) self._raw_color_values = _values[__values] # The joke's on you self._scaled_color_values = __values self._colorgen = ColorPaletteGenerator(len(variable.colors), variable.colors) self._legend_colors = ['d', self._legend_values(variable, range(len(_values))), list(_values), [color_to_hex(self._colorgen.getRGB(i)) for i in range(len(_values))]] finally: if update: self.redraw_markers_overlay_image(new_image=True) def set_marker_label(self, attr, update=True): try: self._label_attr = variable = self.data.domain[attr] if len(self.data) == 0: raise Exception except Exception: self._label_attr = None else: if variable.is_continuous or variable.is_string: self._label_values = self.data.get_column_view(variable)[0] elif variable.is_discrete: _values = np.asarray(self.data.domain[attr].values) __values = self.data.get_column_view(variable)[0].astype(np.uint16) self._label_values = _values[__values] # The design had lead to poor code for ages finally: if update: self.redraw_markers_overlay_image(new_image=True) def set_marker_shape(self, attr, update=True): try: self._shape_attr = variable = self.data.domain[attr] if len(self.data) == 0: raise Exception except Exception: self._shape_attr = None self._legend_shapes = [] else: assert variable.is_discrete _values = np.asarray(self.data.domain[attr].values) self._shape_values = __values = self.data.get_column_view(variable)[0].astype(np.uint16) self._raw_shape_values = _values[__values] self._legend_shapes = [self._legend_values(variable, range(len(_values))), list(_values)] finally: if update: self.redraw_markers_overlay_image(new_image=True) def set_marker_size(self, attr, update=True): try: self._size_attr = variable = self.data.domain[attr] if len(self.data) == 0: raise Exception except Exception: self._size_attr = None self._legend_sizes = [] else: assert variable.is_continuous self._raw_sizes = values = self.data.get_column_view(variable)[0].astype(float) # Note, [5, 60] is also hardcoded in legend-size-indicator.svg self._sizes = scale(values, 5, 60).astype(np.uint8) min = np.nanmin(values) self._legend_sizes = self._legend_values(variable, [min, np.nanmax(values)]) if not np.isnan(min) else [] finally: if update: self.redraw_markers_overlay_image(new_image=True) def set_marker_size_coefficient(self, size): self._size_coef = size / 100 self.evalJS('''set_marker_size_coefficient({});'''.format(size / 100)) if not self.is_js_path: self.redraw_markers_overlay_image(new_image=True) def set_marker_opacity(self, opacity): self._opacity = 255 * opacity // 100 self.evalJS('''set_marker_opacity({});'''.format(opacity / 100)) if not self.is_js_path: self.redraw_markers_overlay_image(new_image=True) def set_model(self, model): self.model = model self.evalJS('clear_heatmap()' if model is None else 'reset_heatmap()') def recompute_heatmap(self, points): if self.model is None or self.data is None: self.exposeObject('model_predictions', {}) self.evalJS('draw_heatmap()') return latlons = np.array(points) table = Table(Domain([self.lat_attr, self.lon_attr]), latlons) try: predictions = self.model(table) except Exception as e: self._owwidget.Error.model_error(e) return else: self._owwidget.Error.model_error.clear() class_var = self.model.domain.class_var is_regression = class_var.is_continuous if is_regression: predictions = scale(np.round(predictions, 7)) # Avoid small errors kwargs = dict( extrema=self._legend_values(class_var, [np.nanmin(predictions), np.nanmax(predictions)])) else: colorgen = ColorPaletteGenerator(len(class_var.values), class_var.colors) predictions = colorgen.getRGB(predictions) kwargs = dict( legend_labels=self._legend_values(class_var, range(len(class_var.values))), full_labels=list(class_var.values), colors=[color_to_hex(colorgen.getRGB(i)) for i in range(len(class_var.values))]) self.exposeObject('model_predictions', dict(data=predictions, **kwargs)) self.evalJS('draw_heatmap()') def _update_legend(self, is_js_path=False): self.evalJS(''' window.legend_colors = %s; window.legend_shapes = %s; window.legend_sizes = %s; legendControl.remove(); legendControl.addTo(map); ''' % (self._legend_colors, self._legend_shapes if is_js_path else [], self._legend_sizes)) def _update_js_markers(self, visible, in_subset): self._visible = visible latlon = self._latlon_data self.exposeObject('latlon_data', dict(data=latlon[visible])) self.exposeObject('jittering_offsets', self._jittering_offsets[visible] if self._jittering_offsets is not None else []) self.exposeObject('selected_markers', dict(data=(self._selected_indices[visible] if self._selected_indices is not None else 0))) self.exposeObject('in_subset', in_subset.astype(np.int8)) if not self._color_attr: self.exposeObject('color_attr', dict()) else: colors = [color_to_hex(rgb) for rgb in self._colorgen.getRGB(self._scaled_color_values[visible])] self.exposeObject('color_attr', dict(name=str(self._color_attr), values=colors, raw_values=self._raw_color_values[visible])) if not self._label_attr: self.exposeObject('label_attr', dict()) else: self.exposeObject('label_attr', dict(name=str(self._label_attr), values=self._label_values[visible])) if not self._shape_attr: self.exposeObject('shape_attr', dict()) else: self.exposeObject('shape_attr', dict(name=str(self._shape_attr), values=self._shape_values[visible], raw_values=self._raw_shape_values[visible])) if not self._size_attr: self.exposeObject('size_attr', dict()) else: self.exposeObject('size_attr', dict(name=str(self._size_attr), values=self._sizes[visible], raw_values=self._raw_sizes[visible])) self.evalJS(''' window.latlon_data = latlon_data.data; window.selected_markers = selected_markers.data; add_markers(latlon_data); ''') class Projection: """This should somewhat model Leaflet's Web Mercator (EPSG:3857). Reverse-engineered from L.Map.latlngToContainerPoint(). """ @staticmethod def latlon_to_easting_northing(lat, lon): R = 6378137 MAX_LATITUDE = 85.0511287798 DEG = np.pi / 180 lat = np.clip(lat, -MAX_LATITUDE, MAX_LATITUDE) sin = np.sin(DEG * lat) x = R * DEG * lon y = R / 2 * np.log((1 + sin) / (1 - sin)) return x, y @staticmethod def easting_northing_to_pixel(x, y, zoom_level, pixel_origin, map_pane_pos): R = 6378137 PROJ_SCALE = .5 / (np.pi * R) zoom_scale = 256 * (2 ** zoom_level) x = (zoom_scale * (PROJ_SCALE * x + .5)).round() + (map_pane_pos[0] - pixel_origin[0]) y = (zoom_scale * (-PROJ_SCALE * y + .5)).round() + (map_pane_pos[1] - pixel_origin[1]) return x, y N_POINTS_PER_ITER = 666 def redraw_markers_overlay_image(self, *args, new_image=False): if not args and not self._drawing_args or self.data is None: return if args: self._drawing_args = args north, east, south, west, width, height, zoom, origin, map_pane_pos = self._drawing_args lat, lon = self._latlon_data.T visible = ((lat <= north) & (lat >= south) & (lon <= east) & (lon >= west)).nonzero()[0] in_subset = (np.in1d(self.data.ids, self._subset_ids) if self._subset_ids.size else np.tile(True, len(lon))) is_js_path = self.is_js_path = len(visible) < self.N_POINTS_PER_ITER self._update_legend(is_js_path) np.random.shuffle(visible) # Sort points in subset to be painted last visible = visible[np.lexsort((in_subset[visible],))] if is_js_path: self.evalJS('clear_markers_overlay_image()') self._update_js_markers(visible, in_subset[visible]) self._owwidget.disable_some_controls(False) return self.evalJS('clear_markers_js();') self._owwidget.disable_some_controls(True) selected = (self._selected_indices if self._selected_indices is not None else np.zeros(len(lat), dtype=bool)) cur = 0 im = QImage(self._overlay_image_path) if im.isNull() or self._prev_origin != origin or new_image: im = QImage(width, height, QImage.Format_ARGB32) im.fill(Qt.transparent) else: dx, dy = self._prev_map_pane_pos - map_pane_pos im = im.copy(dx, dy, width, height) self._prev_map_pane_pos = np.array(map_pane_pos) self._prev_origin = origin painter = QPainter(im) painter.setRenderHint(QPainter.Antialiasing, True) self.evalJS('clear_markers_overlay_image(); markersImageLayer.setBounds(map.getBounds());0') self._image_token = image_token = np.random.random() n_iters = np.ceil(len(visible) / self.N_POINTS_PER_ITER) def add_points(): nonlocal cur, image_token if image_token != self._image_token: return batch = visible[cur:cur + self.N_POINTS_PER_ITER] batch_lat = lat[batch] batch_lon = lon[batch] x, y = self.Projection.latlon_to_easting_northing(batch_lat, batch_lon) x, y = self.Projection.easting_northing_to_pixel(x, y, zoom, origin, map_pane_pos) if self._jittering: dx, dy = self._jittering_offsets[batch].T x, y = x + dx, y + dy colors = (self._colorgen.getRGB(self._scaled_color_values[batch]).tolist() if self._color_attr else repeat((0xff, 0, 0))) sizes = self._size_coef * \ (self._sizes[batch] if self._size_attr else np.tile(10, len(batch))) opacity_subset, opacity_rest = self._opacity, int(.8 * self._opacity) for x, y, is_selected, size, color, _in_subset in \ zip(x, y, selected[batch], sizes, colors, in_subset[batch]): pensize2, selpensize2 = (.35, 1.5) if size >= 5 else (.15, .7) pensize2 *= self._size_coef selpensize2 *= self._size_coef size2 = size / 2 if is_selected: painter.setPen(QPen(QBrush(Qt.green), 2 * selpensize2)) painter.drawEllipse(x - size2 - selpensize2, y - size2 - selpensize2, size + selpensize2, size + selpensize2) color = QColor(*color) if _in_subset: color.setAlpha(opacity_subset) painter.setBrush(QBrush(color)) painter.setPen(QPen(QBrush(color.darker(180)), 2 * pensize2)) else: color.setAlpha(opacity_rest) painter.setBrush(Qt.NoBrush) painter.setPen(QPen(QBrush(color.lighter(120)), 2 * pensize2)) painter.drawEllipse(x - size2 - pensize2, y - size2 - pensize2, size + pensize2, size + pensize2) im.save(self._overlay_image_path, 'PNG') self.evalJS('markersImageLayer.setUrl("{}#{}"); 0;' .format(self.toFileURL(self._overlay_image_path), np.random.random())) cur += self.N_POINTS_PER_ITER if cur < len(visible): QTimer.singleShot(10, add_points) self._owwidget.progressBarAdvance(100 / n_iters, None) else: self._owwidget.progressBarFinished(None) self._image_token = None self._owwidget.progressBarFinished(None) self._owwidget.progressBarInit(None) QTimer.singleShot(10, add_points) def set_subset_ids(self, ids): self._subset_ids = ids self.redraw_markers_overlay_image(new_image=True) def toggle_legend(self, visible): self.evalJS(''' $(".legend").{0}(); window.legend_hidden = "{0}"; '''.format('show' if visible else 'hide'))
class LeafletMap(WebviewWidget): selectionChanged = pyqtSignal(list) def __init__(self, parent=None): super().__init__( parent, url=QUrl( self.toFileURL( os.path.join(os.path.dirname(__file__), '_owmap', 'owmap.html'))), debug=True, ) self.jittering = 0 self._owwidget = parent self._opacity = 255 self._sizes = None self._selected_indices = None self.lat_attr = None self.lon_attr = None self.data = None self.model = None self._color_attr = None self._label_attr = None self._shape_attr = None self._size_attr = None self._legend_colors = [] self._legend_shapes = [] self._legend_sizes = [] self._drawing_args = None self._image_token = None self._prev_map_pane_pos = None self._prev_origin = None self._overlay_image_path = mkstemp(prefix='orange-Map-', suffix='.png')[1] def __del__(self): os.remove(self._overlay_image_path) self._image_token = np.nan def set_data(self, data, lat_attr, lon_attr): self.data = data self.lat_attr = None self.lon_attr = None if data is None or not (len(data) and lat_attr and lon_attr): self.evalJS('clear_markers_js(); clear_markers_overlay_image();') return self.lat_attr = data.domain[lat_attr] self.lon_attr = data.domain[lon_attr] self.fit_to_bounds(False) def showEvent(self, *args): super().showEvent(*args) QTimer.singleShot(10, self.fit_to_bounds) @pyqtSlot() def fit_to_bounds(self, fly=True): if self.data is None: return lat_data = self.data.get_column_view(self.lat_attr)[0] lon_data = self.data.get_column_view(self.lon_attr)[0] north, south = np.nanmax(lat_data), np.nanmin(lat_data) east, west = np.nanmin(lon_data), np.nanmax(lon_data) self.evalJS( 'map.%sBounds([[%f, %f], [%f, %f]], {padding: [0, 0], maxZoom: 9})' % ('flyTo' if fly else 'fit', south, west, north, east)) @pyqtSlot(float, float, float, float) def selected_area(self, north, east, south, west): indices = np.array([]) if north != south and east != west: lat = self.data.get_column_view(self.lat_attr)[0] lon = self.data.get_column_view(self.lon_attr)[0] indices = ((lat <= north) & (lat >= south) & (lon <= east) & (lon >= west)) if self._selected_indices is not None: indices |= self._selected_indices self._selected_indices = indices else: self._selected_indices = None self.selectionChanged.emit(indices.nonzero()[0].tolist()) self.redraw_markers_overlay_image(new_image=True) def set_map_provider(self, provider): self.evalJS('set_map_provider("{}");'.format(provider)) def set_clustering(self, cluster_points): self.evalJS(''' window.cluster_points = {}; set_cluster_points(); '''.format(int(cluster_points))) def set_jittering(self, jittering): """ In percent, i.e. jittering=3 means 3% of screen height and width """ self._jittering = jittering / 100 self.evalJS(''' window.jittering_percent = {}; set_jittering(); if (window.jittering_percent == 0) clear_jittering(); '''.format(jittering)) self.redraw_markers_overlay_image(new_image=True) def _legend_values(self, variable, values): strs = [variable.repr_val(val) for val in values] if any(len(val) > 10 for val in strs): if isinstance(variable, TimeVariable): strs = [s.replace(' ', '<br>') for s in strs] elif variable.is_continuous: strs = ['{:.4e}'.format(val) for val in values] elif variable.is_discrete: strs = [ s if len(s) <= 12 else (s[:6] + '…' + s[-5:]) for s in strs ] return strs def set_marker_color(self, attr, update=True): try: self._color_attr = variable = self.data.domain[attr] except Exception: self._color_attr = None self._legend_colors = [] else: if variable.is_continuous: self._raw_color_values = values = self.data.get_column_view( variable)[0] self._scaled_color_values = scale(values) self._colorgen = ContinuousPaletteGenerator(*variable.colors) min = np.nanmin(values) self._legend_colors = ([ 'c', self._legend_values(variable, [min, np.nanmax(values)]), [color_to_hex(i) for i in variable.colors if i] ] if not np.isnan(min) else []) elif variable.is_discrete: _values = np.asarray(self.data.domain[attr].values) __values = self.data.get_column_view(variable)[0].astype( np.uint16) self._raw_color_values = _values[__values] # The joke's on you self._scaled_color_values = __values self._colorgen = ColorPaletteGenerator(len(variable.colors), variable.colors) self._legend_colors = [ 'd', self._legend_values(variable, range(len(_values))), [ color_to_hex(self._colorgen.getRGB(i)) for i in range(len(_values)) ] ] finally: if update: self.redraw_markers_overlay_image(new_image=True) def set_marker_label(self, attr, update=True): try: self._label_attr = variable = self.data.domain[attr] except Exception: self._label_attr = None else: if variable.is_continuous: self._label_values = self.data.get_column_view(variable)[0] elif variable.is_discrete: _values = np.asarray(self.data.domain[attr].values) __values = self.data.get_column_view(variable)[0].astype( np.uint16) self._label_values = _values[ __values] # The design had lead to poor code for ages finally: if update: self.redraw_markers_overlay_image(new_image=True) def set_marker_shape(self, attr, update=True): try: self._shape_attr = variable = self.data.domain[attr] except Exception: self._shape_attr = None self._legend_shapes = [] else: assert variable.is_discrete _values = np.asarray(self.data.domain[attr].values) self._shape_values = __values = self.data.get_column_view( variable)[0].astype(np.uint16) self._raw_shape_values = _values[__values] self._legend_shapes = self._legend_values(variable, range(len(_values))) finally: if update: self.redraw_markers_overlay_image(new_image=True) def set_marker_size(self, attr, update=True): try: self._size_attr = variable = self.data.domain[attr] except Exception: self._size_attr = None self._legend_sizes = [] else: assert variable.is_continuous self._raw_sizes = values = self.data.get_column_view(variable)[0] # Note, [5, 60] is also hardcoded in legend-size-indicator.svg self._sizes = scale(values, 5, 60).astype(np.uint8) min = np.nanmin(values) self._legend_sizes = self._legend_values( variable, [min, np.nanmax(values)]) if not np.isnan(min) else [] finally: if update: self.redraw_markers_overlay_image(new_image=True) def set_marker_size_coefficient(self, size): self._size_coef = size / 100 self.evalJS('''set_marker_size_coefficient({});'''.format(size / 100)) self.redraw_markers_overlay_image(new_image=True) def set_marker_opacity(self, opacity): self._opacity = 255 * opacity // 100 self.evalJS('''set_marker_opacity({});'''.format(opacity / 100)) self.redraw_markers_overlay_image(new_image=True) def set_model(self, model): self.model = model self.evalJS('clear_heatmap()' if model is None else 'reset_heatmap()') @pyqtSlot('QVariantList') def recompute_heatmap(self, points): if self.model is None or not self.data or not self.lat_attr or not self.lon_attr: return latlons = np.array(points) table = Table(Domain([self.lat_attr, self.lon_attr]), latlons) try: predictions = self.model(table) except Exception as e: self._owwidget.Error.model_error(e) return else: self._owwidget.Error.model_error.clear() predictions = scale(np.round(predictions, 7)) # Avoid small errors self.exposeObject('model_predictions', dict(data=predictions)) self.evalJS('draw_heatmap()') def _update_js_markers(self, visible): self._visible = visible data = Table(Domain([self.lat_attr, self.lon_attr]), self.data) self.exposeObject('latlon_data', dict(data=data.X[visible])) self.exposeObject( 'selected_markers', dict(data=(self._selected_indices[visible] if self. _selected_indices is not None else 0))) if not self._color_attr: self.exposeObject('color_attr', dict()) else: colors = [ color_to_hex(rgb) for rgb in self._colorgen.getRGB( self._scaled_color_values[visible]) ] self.exposeObject( 'color_attr', dict(name=str(self._color_attr), values=colors, raw_values=self._raw_color_values[visible])) if not self._label_attr: self.exposeObject('label_attr', dict()) else: self.exposeObject( 'label_attr', dict(name=str(self._label_attr), values=self._label_values[visible])) if not self._shape_attr: self.exposeObject('shape_attr', dict()) else: self.exposeObject( 'shape_attr', dict(name=str(self._shape_attr), values=self._shape_values[visible], raw_values=self._raw_shape_values[visible])) if not self._size_attr: self.exposeObject('size_attr', dict()) else: self.exposeObject( 'size_attr', dict(name=str(self._size_attr), values=self._sizes[visible], raw_values=self._raw_sizes[visible])) self.evalJS(''' window.latlon_data = latlon_data.data; window.selected_markers = selected_markers.data add_markers(latlon_data); ''') class Projection: """This should somewhat model Leaflet's Web Mercator (EPSG:3857). Reverse-engineered from L.Map.latlngToContainerPoint(). """ @staticmethod def latlon_to_easting_northing(lat, lon): R = 6378137 MAX_LATITUDE = 85.0511287798 DEG = np.pi / 180 lat = np.clip(lat, -MAX_LATITUDE, MAX_LATITUDE) sin = np.sin(DEG * lat) x = R * DEG * lon y = R / 2 * np.log((1 + sin) / (1 - sin)) return x, y @staticmethod def easting_northing_to_pixel(x, y, zoom_level, pixel_origin, map_pane_pos): R = 6378137 PROJ_SCALE = .5 / (np.pi * R) zoom_scale = 256 * (2**zoom_level) x = (zoom_scale * (PROJ_SCALE * x + .5)).round() + (map_pane_pos[0] - pixel_origin[0]) y = (zoom_scale * (-PROJ_SCALE * y + .5)).round() + (map_pane_pos[1] - pixel_origin[1]) return x, y @pyqtSlot(float, float, float, float, int, int, float, 'QVariantList', 'QVariantList') def redraw_markers_overlay_image(self, *args, new_image=False): if (not args and not self._drawing_args or self.lat_attr is None or self.lon_attr is None): return if args: self._drawing_args = args north, east, south, west, width, height, zoom, origin, map_pane_pos = self._drawing_args lat = self.data.get_column_view(self.lat_attr)[0] lon = self.data.get_column_view(self.lon_attr)[0] visible = ((lat <= north) & (lat >= south) & (lon <= east) & (lon >= west)).nonzero()[0] is_js_path = len(visible) <= 500 self.evalJS(''' window.legend_colors = %s; window.legend_shapes = %s; window.legend_sizes = %s; legendControl.remove(); legendControl.addTo(map); ''' % (self._legend_colors, self._legend_shapes if is_js_path else [], self._legend_sizes)) if is_js_path: self.evalJS('clear_markers_overlay_image()') self._update_js_markers(visible) self._owwidget.disable_some_controls(False) return self.evalJS('clear_markers_js();') self._owwidget.disable_some_controls(True) np.random.shuffle(visible) selected = (self._selected_indices if self._selected_indices is not None else np.zeros(len(lat), dtype=bool)) N_POINTS_PER_ITER = 1000 cur = 0 im = QImage(self._overlay_image_path) if im.isNull() or self._prev_origin != origin or new_image: im = QImage(width, height, QImage.Format_ARGB32) im.fill(Qt.transparent) else: dx, dy = self._prev_map_pane_pos - map_pane_pos im = im.copy(dx, dy, width, height) self._prev_map_pane_pos = np.array(map_pane_pos) self._prev_origin = origin painter = QPainter(im) painter.setRenderHint(QPainter.Antialiasing, True) self.evalJS( 'clear_markers_overlay_image(); markersImageLayer.setBounds(map.getBounds());0' ) self._image_token = image_token = np.random.random() n_iters = np.ceil(len(visible) / N_POINTS_PER_ITER) def add_points(): nonlocal cur, image_token if image_token != self._image_token: return batch = visible[cur:cur + N_POINTS_PER_ITER] batch_lat = lat[batch] batch_lon = lon[batch] batch_selected = selected[batch] x, y = self.Projection.latlon_to_easting_northing( batch_lat, batch_lon) x, y = self.Projection.easting_northing_to_pixel( x, y, zoom, origin, map_pane_pos) if self._jittering: x += (np.random.random(len(x)) - .5) * (self._jittering * width) y += (np.random.random(len(x)) - .5) * (self._jittering * height) colors = (self._colorgen.getRGB( self._scaled_color_values[batch]).tolist() if self._color_attr else repeat((0xff, 0, 0))) sizes = self._sizes[batch] if self._size_attr else repeat(10) zipped = zip(x, y, batch_selected, sizes, colors) sortkey, penkey, sizekey, brushkey = itemgetter( 2, 3, 4), itemgetter(2), itemgetter(3), itemgetter(4) for is_selected, points in groupby(sorted(zipped, key=sortkey), key=penkey): for size, points in groupby(points, key=sizekey): pensize, pencolor = ((3, Qt.green) if is_selected else (.7, QColor(0, 0, 0, self._opacity))) size *= self._size_coef if size < 5: pensize /= 3 size += pensize size2 = size / 2 painter.setPen(Qt.NoPen if size < 5 and not is_selected else QPen(QBrush(pencolor), pensize)) for color, points in groupby(points, key=brushkey): color = tuple(color) + (self._opacity, ) painter.setBrush(QBrush(QColor(*color))) for x, y, *_ in points: painter.drawEllipse(x - size2, y - size2, size, size) im.save(self._overlay_image_path, 'PNG') self.evalJS('markersImageLayer.setUrl("{}#{}"); 0;'.format( self._overlay_image_path, np.random.random())) cur += N_POINTS_PER_ITER if cur < len(visible): QTimer.singleShot(10, add_points) self._owwidget.progressBarAdvance(100 / n_iters, None) else: self._owwidget.progressBarFinished(None) self._owwidget.progressBarFinished(None) self._owwidget.progressBarInit(None) QTimer.singleShot(10, add_points)
def get_palette(self): if self.is_continuous_color(): return ContinuousPaletteGenerator(Qt.white, Qt.black, False) else: return ColorPaletteGenerator(12)
def __call__(self, n): return ColorPaletteGenerator(n)
def __init__(self, scatter_widget, parent=None, _="None", view_box=InteractiveViewBox): gui.OWComponent.__init__(self, scatter_widget) self.view_box = view_box(self) self.plot_widget = pg.PlotWidget(viewBox=self.view_box, parent=parent, background="w") self.plot_widget.getPlotItem().buttonsHidden = True self.plot_widget.setAntialiasing(True) self.plot_widget.sizeHint = lambda: QSize(500, 500) scene = self.plot_widget.scene() self._create_drag_tooltip(scene) self._data = None # Original Table as passed from widget to new_data before transformations self.replot = self.plot_widget.replot ScaleScatterPlotData.__init__(self) self.density_img = None self.scatterplot_item = None self.scatterplot_item_sel = None self.reg_line_item = None self.labels = [] self.master = scatter_widget self.master.Warning.add_message( "missing_coords", "Plot cannot be displayed because '{}' or '{}' is missing for " "all data points") self.master.Information.add_message( "missing_coords", "Points with missing '{}' or '{}' are not displayed") self.master.Information.add_message( "missing_size", "Points with undefined '{}' are shown in smaller size") self.master.Information.add_message( "missing_shape", "Points with undefined '{}' are shown as crossed circles") self.shown_attribute_indices = [] self.shown_x = self.shown_y = None self.pen_colors = self.brush_colors = None self.valid_data = None # np.ndarray self.selection = None # np.ndarray self.n_points = 0 self.gui = OWPlotGUI(self) self.continuous_palette = ContinuousPaletteGenerator( QColor(255, 255, 0), QColor(0, 0, 255), True) self.discrete_palette = ColorPaletteGenerator() self.selection_behavior = 0 self.legend = self.color_legend = None self.__legend_anchor = (1, 0), (1, 0) self.__color_legend_anchor = (1, 1), (1, 1) self.scale = None # DiscretizedScale self.subset_indices = None # self.setMouseTracking(True) # self.grabGesture(QPinchGesture) # self.grabGesture(QPanGesture) self.update_grid() self._tooltip_delegate = HelpEventDelegate(self.help_event) self.plot_widget.scene().installEventFilter(self._tooltip_delegate)
class OWScatterPlotGraph(gui.OWComponent, ScaleScatterPlotData): attr_color = ContextSetting("", ContextSetting.OPTIONAL) attr_label = ContextSetting("", ContextSetting.OPTIONAL) attr_shape = ContextSetting("", ContextSetting.OPTIONAL) attr_size = ContextSetting("", ContextSetting.OPTIONAL) point_width = Setting(10) alpha_value = Setting(255) show_grid = Setting(False) show_legend = Setting(True) tooltip_shows_all = Setting(False) square_granularity = Setting(3) space_between_cells = Setting(True) CurveSymbols = np.array("o x t + d s ?".split()) MinShapeSize = 6 DarkerValue = 120 UnknownColor = (168, 50, 168) def __init__(self, scatter_widget, parent=None, _="None"): gui.OWComponent.__init__(self, scatter_widget) self.view_box = InteractiveViewBox(self) self.plot_widget = pg.PlotWidget(viewBox=self.view_box, parent=parent, background="w") self.plot_widget.setAntialiasing(True) self.plot_widget.sizeHint = lambda: QtCore.QSize(500, 500) self.replot = self.plot_widget.replot ScaleScatterPlotData.__init__(self) self.scatterplot_item = None self.labels = [] self.master = scatter_widget self.shown_attribute_indices = [] self.shown_x = "" self.shown_y = "" self.pen_colors = self.brush_colors = None self.valid_data = None # np.ndarray self.selection = None # np.ndarray self.n_points = 0 self.gui = OWPlotGUI(self) self.continuous_palette = ContinuousPaletteGenerator( QColor(255, 255, 0), QColor(0, 0, 255), True) self.discrete_palette = ColorPaletteGenerator() self.selection_behavior = 0 self.legend = self.color_legend = None self.scale = None # DiscretizedScale # self.setMouseTracking(True) # self.grabGesture(QPinchGesture) # self.grabGesture(QPanGesture) self.update_grid() self._tooltip_delegate = HelpEventDelegate(self.help_event) self.plot_widget.scene().installEventFilter(self._tooltip_delegate) def set_data(self, data, subset_data=None, **args): self.plot_widget.clear() ScaleScatterPlotData.set_data(self, data, subset_data, **args) def update_data(self, attr_x, attr_y): self.shown_x = attr_x self.shown_y = attr_y self.remove_legend() if self.scatterplot_item: self.plot_widget.removeItem(self.scatterplot_item) for label in self.labels: self.plot_widget.removeItem(label) self.labels = [] self.set_axis_title("bottom", "") self.set_axis_title("left", "") if self.scaled_data is None or not len(self.scaled_data): self.valid_data = None self.n_points = 0 return index_x = self.attribute_name_index[attr_x] index_y = self.attribute_name_index[attr_y] self.valid_data = self.get_valid_list([index_x, index_y]) x_data, y_data = self.get_xy_data_positions(attr_x, attr_y, self.valid_data) x_data = x_data[self.valid_data] y_data = y_data[self.valid_data] self.n_points = len(x_data) for axis, name, index in (("bottom", attr_x, index_x), ("left", attr_y, index_y)): self.set_axis_title(axis, name) var = self.data_domain[index] if isinstance(var, DiscreteVariable): self.set_labels(axis, get_variable_values_sorted(var)) else: self.set_labels(axis, None) color_data, brush_data = self.compute_colors() size_data = self.compute_sizes() shape_data = self.compute_symbols() self.scatterplot_item = ScatterPlotItem(x=x_data, y=y_data, data=np.arange(self.n_points), symbol=shape_data, size=size_data, pen=color_data, brush=brush_data) self.plot_widget.addItem(self.scatterplot_item) self.scatterplot_item.selected_points = [] self.scatterplot_item.sigClicked.connect(self.select_by_click) self.update_labels() self.make_legend() self.plot_widget.replot() def set_labels(self, axis, labels): axis = self.plot_widget.getAxis(axis) if labels: ticks = [[(i, labels[i]) for i in range(len(labels))]] axis.setTicks(ticks) else: axis.setTicks(None) def set_axis_title(self, axis, title): self.plot_widget.setLabel(axis=axis, text=title) def get_size_index(self): size_index = -1 attr_size = self.attr_size if attr_size != "" and attr_size != "(Same size)": size_index = self.attribute_name_index[attr_size] return size_index def compute_sizes(self): size_index = self.get_size_index() if size_index == -1: size_data = np.full((self.n_points, ), self.point_width) else: size_data = \ self.MinShapeSize + \ self.no_jittering_scaled_data[size_index] * self.point_width size_data[np.isnan(size_data)] = self.MinShapeSize - 2 return size_data def update_sizes(self): if self.scatterplot_item: size_data = self.compute_sizes() self.scatterplot_item.setSize(size_data) update_point_size = update_sizes def get_color_index(self): color_index = -1 attr_color = self.attr_color if attr_color != "" and attr_color != "(Same color)": color_index = self.attribute_name_index[attr_color] color_var = self.data_domain[attr_color] if isinstance(color_var, DiscreteVariable): self.discrete_palette.set_number_of_colors( len(color_var.values)) return color_index def compute_colors(self, keep_colors=False): if not keep_colors: self.pen_colors = self.brush_colors = None color_index = self.get_color_index() if color_index == -1: color = self.plot_widget.palette().color(OWPalette.Data) pen = [QPen(QBrush(color), 1.5)] * self.n_points if self.selection is not None: brush = [(QBrush(QColor(128, 128, 128, 255)), QBrush(QColor(128, 128, 128)))[s] for s in self.selection] else: brush = [QBrush(QColor(128, 128, 128))] * self.n_points return pen, brush c_data = self.original_data[color_index, self.valid_data] if isinstance(self.data_domain[color_index], ContinuousVariable): if self.pen_colors is None: self.scale = DiscretizedScale(np.min(c_data), np.max(c_data)) c_data -= self.scale.offset c_data /= self.scale.width c_data = np.floor(c_data) + 0.5 c_data /= self.scale.bins c_data = np.clip(c_data, 0, 1) palette = self.continuous_palette self.pen_colors = palette.getRGB(c_data) self.brush_colors = np.hstack([ self.pen_colors, np.full((self.n_points, 1), self.alpha_value) ]) self.pen_colors *= 100 / self.DarkerValue self.pen_colors = [ QPen(QBrush(QColor(*col)), 1.5) for col in self.pen_colors.tolist() ] if self.selection is not None: self.brush_colors[:, 3] = 0 self.brush_colors[self.selection, 3] = self.alpha_value else: self.brush_colors[:, 3] = self.alpha_value pen = self.pen_colors brush = np.array( [QBrush(QColor(*col)) for col in self.brush_colors.tolist()]) else: if self.pen_colors is None: palette = self.discrete_palette n_colors = palette.number_of_colors c_data = c_data.copy() c_data[np.isnan(c_data)] = n_colors c_data = c_data.astype(int) colors = palette.getRGB(np.arange(n_colors + 1)) colors[n_colors] = (128, 128, 128) pens = np.array([ QPen(QBrush(QColor(*col).darker(self.DarkerValue)), 1.5) for col in colors ]) self.pen_colors = pens[c_data] self.brush_colors = np.array([[ QBrush(QColor(0, 0, 0, 0)), QBrush(QColor(col[0], col[1], col[2], self.alpha_value)) ] for col in colors]) self.brush_colors = self.brush_colors[c_data] if self.selection is not None: brush = np.where(self.selection, self.brush_colors[:, 1], self.brush_colors[:, 0]) else: brush = self.brush_colors[:, 1] pen = self.pen_colors return pen, brush def update_colors(self, keep_colors=False): if self.scatterplot_item: pen_data, brush_data = self.compute_colors(keep_colors) self.scatterplot_item.setPen(pen_data, update=False, mask=None) self.scatterplot_item.setBrush(brush_data, mask=None) if not keep_colors: self.make_legend() update_alpha_value = update_colors def create_labels(self): for x, y in zip(*self.scatterplot_item.getData()): ti = TextItem() self.plot_widget.addItem(ti) ti.setPos(x, y) self.labels.append(ti) def update_labels(self): if not self.attr_label: for label in self.labels: label.setText("") return if not self.labels: self.create_labels() label_column = self.raw_data.get_column_view(self.attr_label)[0] formatter = self.raw_data.domain[self.attr_label].str_val label_data = map(formatter, label_column) black = pg.mkColor(0, 0, 0) for label, text in zip(self.labels, label_data): label.setText(text, black) def get_shape_index(self): shape_index = -1 attr_shape = self.attr_shape if attr_shape and attr_shape != "(Same shape)" and \ len(self.data_domain[attr_shape].values) <= \ len(self.CurveSymbols): shape_index = self.attribute_name_index[attr_shape] return shape_index def compute_symbols(self): shape_index = self.get_shape_index() if shape_index == -1: shape_data = self.CurveSymbols[np.zeros(self.n_points, dtype=int)] else: shape_data = self.original_data[shape_index] shape_data[np.isnan(shape_data)] = len(self.CurveSymbols) - 1 shape_data = self.CurveSymbols[shape_data.astype(int)] return shape_data def update_shapes(self): if self.scatterplot_item: shape_data = self.compute_symbols() self.scatterplot_item.setSymbol(shape_data) self.make_legend() def update_grid(self): self.plot_widget.showGrid(x=self.show_grid, y=self.show_grid) def update_legend(self): if self.legend: self.legend.setVisible(self.show_legend) def create_legend(self): self.legend = PositionedLegendItem(self.plot_widget.plotItem, self) def remove_legend(self): if self.legend: self.legend.setParent(None) self.legend = None if self.color_legend: self.color_legend.setParent(None) self.color_legend = None def make_legend(self): self.remove_legend() self.make_color_legend() self.make_shape_legend() self.update_legend() def make_color_legend(self): color_index = self.get_color_index() if color_index == -1: return color_var = self.data_domain[color_index] use_shape = self.get_shape_index() == color_index if isinstance(color_var, DiscreteVariable): if not self.legend: self.create_legend() palette = self.discrete_palette for i, value in enumerate(color_var.values): color = QColor(*palette.getRGB(i)) brush = color.lighter(self.DarkerValue) self.legend.addItem( ScatterPlotItem( pen=color, brush=brush, size=10, symbol=self.CurveSymbols[i] if use_shape else "o"), value) else: legend = self.color_legend = PositionedLegendItem( self.plot_widget.plotItem, self, legend_id="colors", at_bottom=True) label = PaletteItemSample(self.continuous_palette, self.scale) legend.addItem(label, "") legend.setGeometry(label.boundingRect()) def make_shape_legend(self): shape_index = self.get_shape_index() if shape_index == -1 or shape_index == self.get_color_index(): return if not self.legend: self.create_legend() shape_var = self.data_domain[shape_index] color = self.plot_widget.palette().color(OWPalette.Data) pen = QPen(color.darker(self.DarkerValue)) color.setAlpha(self.alpha_value) for i, value in enumerate(shape_var.values): self.legend.addItem( ScatterPlotItem(pen=pen, brush=color, size=10, symbol=self.CurveSymbols[i]), value) def zoom_button_clicked(self): self.scatterplot_item.getViewBox().setMouseMode( self.scatterplot_item.getViewBox().RectMode) def pan_button_clicked(self): self.scatterplot_item.getViewBox().setMouseMode( self.scatterplot_item.getViewBox().PanMode) def select_button_clicked(self): self.scatterplot_item.getViewBox().setMouseMode( self.scatterplot_item.getViewBox().RectMode) def reset_button_clicked(self): self.view_box.autoRange() def select_by_click(self, _, points): self.select(points) def select_by_rectangle(self, value_rect): points = [ point for point in self.scatterplot_item.points() if value_rect.contains(QPointF(point.pos())) ] self.select(points) def unselect_all(self): self.selection = None self.update_colors(keep_colors=True) def select(self, points): # noinspection PyArgumentList keys = QApplication.keyboardModifiers() if self.selection is None or not keys & ( Qt.ShiftModifier + Qt.ControlModifier + Qt.AltModifier): self.selection = np.full(self.n_points, False, dtype=np.bool) indices = [p.data() for p in points] if keys & Qt.ControlModifier: self.selection[indices] = False elif keys & Qt.AltModifier: self.selection[indices] = 1 - self.selection[indices] else: # Handle shift and no modifiers self.selection[indices] = True self.update_colors(keep_colors=True) self.master.selection_changed() def get_selection(self): if self.selection is None: return np.array([], dtype=int) else: return np.arange(len( self.raw_data))[self.valid_data][self.selection] def set_palette(self, p): self.plot_widget.setPalette(p) def save_to_file(self, size): pass def help_event(self, event): if self.scatterplot_item is None: return False act_pos = self.scatterplot_item.mapFromScene(event.scenePos()) points = self.scatterplot_item.pointsAt(act_pos) text = "" if len(points): for i, p in enumerate(points): index = p.data() text += "Attributes:\n" if self.tooltip_shows_all: text += "".join(' {} = {}\n'.format( attr.name, self.raw_data[index][attr]) for attr in self.data_domain.attributes) else: text += ' {} = {}\n {} = {}\n'.format( self.shown_x, self.raw_data[index][self.shown_x], self.shown_y, self.raw_data[index][self.shown_y]) if self.data_domain.class_var: text += 'Class:\n {} = {}\n'.format( self.data_domain.class_var.name, self.raw_data[index][self.raw_data.domain.class_var]) if i < len(points) - 1: text += '------------------\n' text = ('<span style="white-space:pre">{}</span>'.format( escape(text))) QToolTip.showText(event.screenPos(), text, widget=self.plot_widget) return True else: return False
def test_colors_diff_domain(self): """ Test whether the color selection for values is correct. """ # pylint: disable=protected-access self.send_signal(self.widget.Inputs.data, self.iris) # case 1: two domains one subset other idom = self.iris.domain dom1 = Domain( idom.attributes, DiscreteVariable(idom.class_var.name, idom.class_var.values)) dom2 = Domain( idom.attributes, DiscreteVariable(idom.class_var.name, idom.class_var.values[:2])) iris1 = self.iris[:100].transform(dom1) iris2 = self.iris[:100].transform(dom2) predictor_iris1 = ConstantLearner()(iris1) predictor_iris2 = ConstantLearner()(iris2) self.send_signal(self.widget.Inputs.predictors, predictor_iris1) self.send_signal(self.widget.Inputs.predictors, predictor_iris2, 1) colors = self.widget._get_colors() np.testing.assert_array_equal(colors, iris1.domain.class_var.colors) # case 2: two domains one subset other - different color order idom = self.iris.domain colors = idom.class_var.colors[::-1] dom1 = Domain( idom.attributes, DiscreteVariable(idom.class_var.name, idom.class_var.values)) dom2 = Domain( idom.attributes, DiscreteVariable(idom.class_var.name, idom.class_var.values[:2])) dom1.class_var.colors = colors dom2.class_var.colors = colors[:2] iris1 = self.iris[:100].transform(dom1) iris2 = self.iris[:100].transform(dom2) predictor_iris1 = ConstantLearner()(iris1) predictor_iris2 = ConstantLearner()(iris2) self.send_signal(self.widget.Inputs.predictors, predictor_iris1) self.send_signal(self.widget.Inputs.predictors, predictor_iris2, 1) colors = self.widget._get_colors() np.testing.assert_array_equal(colors, iris1.domain.class_var.colors) # case 3: domain color, values miss-match - use default colors idom = self.iris.domain dom1 = Domain( idom.attributes, DiscreteVariable(idom.class_var.name, idom.class_var.values)) dom2 = Domain( idom.attributes, DiscreteVariable(idom.class_var.name, idom.class_var.values)) dom1.class_var.colors = dom1.class_var.colors[::-1] iris1 = self.iris.transform(dom1) iris2 = self.iris.transform(dom2) predictor_iris1 = ConstantLearner()(iris1) predictor_iris2 = ConstantLearner()(iris2) self.send_signal(self.widget.Inputs.predictors, predictor_iris1) self.send_signal(self.widget.Inputs.predictors, predictor_iris2, 1) colors = self.widget._get_colors() np.testing.assert_array_equal(colors, ColorPaletteGenerator.palette(3)) # case 4: two domains different values order, matching colors idom = self.iris.domain # this way we know that default colors are not used colors = ColorPaletteGenerator.palette(5)[2:] dom1 = Domain( idom.attributes, DiscreteVariable(idom.class_var.name, idom.class_var.values)) dom2 = Domain( idom.attributes, DiscreteVariable(idom.class_var.name, idom.class_var.values[::-1])) dom1.class_var.colors = colors dom2.class_var.colors = colors[::-1] # colors mixed same than values iris1 = self.iris[:100].transform(dom1) iris2 = self.iris[:100].transform(dom2) predictor_iris1 = ConstantLearner()(iris1) predictor_iris2 = ConstantLearner()(iris2) self.send_signal(self.widget.Inputs.predictors, predictor_iris1) self.send_signal(self.widget.Inputs.predictors, predictor_iris2, 1) colors = self.widget._get_colors() np.testing.assert_array_equal(colors, iris1.domain.class_var.colors)