Updating marker style in scatter plot with matplotlib

后端 未结 2 1202
借酒劲吻你
借酒劲吻你 2020-12-17 01:51

I am working on an interactive plotting application which requires users to select data points from a matplotlib scatter plot. For clarity, I would like to be able to alter

相关标签:
2条回答
  • 2020-12-17 02:22

    Pretty sure there is no way to do this. scatter has turned your data into a collection of paths and no longer has the meta-data you would need to do this (ie, it knows nothing about the semantics of why it is drawing a shape, it just has a list of shapes to draw).

    You can also update the colors with set_array (as PathCollection is a sub-class of ScalerMappable).

    If you want to do this (and have a reasonably small number of points) you can manage the paths by hand.

    The other (simpler) option is to use two (or several, one for each shape/color combination you want) Line2D objects (as you are not in this example scaling the size of the markers) with linestyle='none'. The picker event on Line2D objects will give you back which point you were nearest.

    Sorry this is rambley.

    0 讨论(0)
  • 2020-12-17 02:23

    If what you are really after is highlighting the point selected by the user, then you could superimpose another dot (with dot = ax.scatter(...)) on top of the point selected. Later, in response to user clicks, you could then use dot.set_offsets((x, y)) to change the location of the dot.

    Joe Kington has written a wonderful example (DataCursor) of how to add an annotation displaying the data coordinates when a user clicks on on artist (such as a scatter plot).

    Here is a derivative example (FollowDotCursor) which highlights and annotates data points when a user hovers the mouse over a point.

    With the DataCursor the data coordinates displayed are where the user clicks -- which might not be exactly the same coordinates as the underlying data.

    With the FollowDotCursor the data coordinate displayed is always a point in the underlying data which is nearest 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

    0 讨论(0)
提交回复
热议问题