Scatter marker style update using matplotlib

I am working on an interactive graphics application that requires users to select data points from the matplotlib markup graph. For clarity, I would like to be able to change the color and shape of the drawn point when it is clicked (or selected in any way).

Since the matplotlib.collections.PathCollection class has a set_facecolors method, changing the color of the dots is relatively simple. However, I do not see a similar way to update the shape of the marker.

Is there any way to do this?

Babe-shaped illustration of the problem:

 import numpy as np import matplotlib.pyplot as plt x = np.random.normal(0,1.0,100) y = np.random.normal(0,1.0,100) scatter_plot = plt.scatter(x, y, facecolor="b", marker="o") #update the colour new_facecolors = ["r","g"]*50 scatter_plot.set_facecolors(new_facecolors) #update the marker? #new_marker = ["o","s"]*50 #scatter_plot.???(new_marker) #<--how do I access the marker shapes? plt.show() 

Any ideas?

+4
source share
2 answers

If you really want to highlight a point selected by the user, you can overlay another point (with dot = ax.scatter(...) ) on top of the selected point. Later, in response to user clicks, you can use dot.set_offsets((x, y)) to change the location of the point.

Joe Kington wrote a great example ( DataCursor ) on how to add an annotation that displays the coordinates of the data when the user clicks on the artist (for example, a scatter plot).

Here is an example derivative ( FollowDotCursor ) that selects and annotates data points when the user hovers the mouse over the point.

Using the DataCursor , the displayed coordinates of the data that the user clicks on are displayed โ€” these may not be the same coordinates as the underlying data.

With FollowDotCursor displayed data coordinate is always the point in the reference data that is closest to the mouse.


 import numpy as np import matplotlib.pyplot as plt import scipy.spatial as spatial def fmt(x, y): return 'x: {x:0.2f}\ny: {y:0.2f}'.format(x=x, y=y) class FollowDotCursor(object): """Display the x,y location of the nearest data point. """ def __init__(self, ax, x, y, tolerance=5, formatter=fmt, offsets=(-20, 20)): try: x = np.asarray(x, dtype='float') except (TypeError, ValueError): x = np.asarray(mdates.date2num(x), dtype='float') y = np.asarray(y, dtype='float') self._points = np.column_stack((x, y)) self.offsets = offsets self.scale = x.ptp() self.scale = y.ptp() / self.scale if self.scale else 1 self.tree = spatial.cKDTree(self.scaled(self._points)) self.formatter = formatter self.tolerance = tolerance self.ax = ax self.fig = ax.figure self.ax.xaxis.set_label_position('top') self.dot = ax.scatter( [x.min()], [y.min()], s=130, color='green', alpha=0.7) self.annotation = self.setup_annotation() plt.connect('motion_notify_event', self) def scaled(self, points): points = np.asarray(points) return points * (self.scale, 1) def __call__(self, event): ax = self.ax # event.inaxes is always the current axis. If you use twinx, ax could be # a different axis. if event.inaxes == ax: x, y = event.xdata, event.ydata elif event.inaxes is None: return else: inv = ax.transData.inverted() x, y = inv.transform([(event.x, event.y)]).ravel() annotation = self.annotation x, y = self.snap(x, y) annotation.xy = x, y annotation.set_text(self.formatter(x, y)) self.dot.set_offsets((x, y)) bbox = ax.viewLim event.canvas.draw() def setup_annotation(self): """Draw and hide the annotation box.""" annotation = self.ax.annotate( '', xy=(0, 0), ha = 'right', xytext = self.offsets, textcoords = 'offset points', va = 'bottom', bbox = dict( boxstyle='round,pad=0.5', fc='yellow', alpha=0.75), arrowprops = dict( arrowstyle='->', connectionstyle='arc3,rad=0')) return annotation def snap(self, x, y): """Return the value in self.tree closest to x, y.""" dist, idx = self.tree.query(self.scaled((x, y)), k=1, p=1) try: return self._points[idx] except IndexError: # IndexError: index out of bounds return self._points[0] x = np.random.normal(0,1.0,100) y = np.random.normal(0,1.0,100) fig, ax = plt.subplots() cursor = FollowDotCursor(ax, x, y, formatter=fmt, tolerance=20) scatter_plot = plt.scatter(x, y, facecolor="b", marker="o") #update the colour new_facecolors = ["r","g"]*50 scatter_plot.set_facecolors(new_facecolors) plt.show() 

enter image description here

+5
source

Pretty sure there is no way to do this. scatter turned your data into a collection of paths and no longer has metadata that you will need to do (i.e., it does not know anything about the semantics of why it draws a shape, it just has a list of shapes to draw).

You can also update colors with set_array (since PathCollection is a subclass of ScalerMappable ).

If you want to do this (and have a fairly small number of points), you can manage the paths manually.

Another (simpler) option is to use two (or several, one for each combination of shape and color) Line2D objects (as you do not scale the size of the markers in this example) with linestyle='none' , the picker event on Line2D objects will return you the moment you were closest.

Sorry, this is commonplace.

+1
source

All Articles