0

I am setting up a class that creates a simple XY plot of a line with some markers (X,Y).

The idea is to give the user the option of updating the line by dragging points.

I have added a "Reset" button that is supposed to revert the line back to its original values.

If I drag one point and click on the "Reset" button, it works fine but if try to drag a point again the button is unresponsive.

I am fairly new with matplotlib widgets, I must be missing something obvious...

Note that I tried using different backends, I also tried inside a notebook cell using (%matplotlib notebook)

Here is the code of my "Path" class:

import matplotlib.pyplot as plt
from matplotlib.widgets import Button
import matplotlib as mpl
import numpy as np
from copy import copy

class Path(object):

def __init__(self, xcoords, ycoords):

    self.xcoords = xcoords
    self.ycoords = ycoords
    self.original_xcoords = copy(xcoords)
    self.original_ycoords = copy(ycoords)
    self.pind = None  # active point
    self.epsilon = 5  # max pixel distance

    # figure.subplot.right
    mpl.rcParams['figure.subplot.right'] = 0.8

    # set up a plot
    self.fig, self.ax1 = plt.subplots(1, 1, figsize=(9.0, 8.0), sharex=True)

    self.ax1.plot(self.xcoords, self.ycoords, 'k--', label='original')

    self.l, = self.ax1.plot(self.xcoords, self.ycoords,
        color='k', linestyle='none',
        marker='o', markersize=8)

    self.m, = self.ax1.plot(self.xcoords, self.ycoords, 'r-')

    self.ax1.set_yscale('linear')
    self.ax1.legend(loc=4, prop={'size': 10})

    self.axres = plt.axes([0.84, 0.8-((self.original_xcoords.shape[0])*0.05), 0.12, 0.02])
    self.bres = Button(self.axres, 'Test')
    self.bres.on_clicked(self.reset)

    self.fig.canvas.mpl_connect('button_press_event', self.button_press_callback)
    self.fig.canvas.mpl_connect('button_release_event', self.button_release_callback)
    self.fig.canvas.mpl_connect('motion_notify_event', self.motion_notify_callback)

    plt.show()

def reset(self, event):
    """ Reset the values """
    self.xcoords = self.original_xcoords
    self.ycoords = self.original_ycoords
    self.l.set_xdata(self.xcoords)
    self.m.set_xdata(self.xcoords)
    self.l.set_ydata(self.ycoords)
    self.m.set_ydata(self.ycoords)
    plt.draw()

def button_press_callback(self, event):
    if event.inaxes is None:
        return
    if event.button != 1:
        return
    self.pind = self.get_ind_under_point(event)

def button_release_callback(self, event):
    if event.button != 1:
        return
    self.pind = None

def get_ind_under_point(self, event):
    """
       Get the index of the vertex under point if within epsilon tolerance
    """
    tinv = self.ax1.transData
    xr = np.reshape(self.xcoords, (np.shape(self.xcoords)[0], 1))
    yr = np.reshape(self.ycoords, (np.shape(self.ycoords)[0], 1))
    xy_vals = np.append(xr, yr, 1)
    xyt = tinv.transform(xy_vals)
    xt, yt = xyt[:, 0], xyt[:, 1]
    d = np.hypot(xt - event.x, yt - event.y)
    indseq, = np.nonzero(d == d.min())
    ind = indseq[0]

    if d[ind] >= self.epsilon:
        ind = None

    return ind

def motion_notify_callback(self, event):
    'on mouse movement'
    if self.pind is None:
        return
    if event.inaxes is None:
        return
    if event.button != 1:
        return

    self.ycoords[self.pind] = event.ydata
    self.xcoords[self.pind] = event.xdata
    self.l.set_xdata(self.xcoords)
    self.m.set_xdata(self.xcoords)
    self.l.set_ydata(self.ycoords)
    self.m.set_ydata(self.ycoords)
    self.fig.canvas.draw_idle()


x = np.array([1,2,3,4])
y = np.array([5, 6, 2, 3])

#%matplotlib notebook
A = Path(x, y)

2 Answers 2

1

A minimal example of the problem is this:

import numpy as np

a = np.array([1,2,3])
b = a

b[1] = 1000
print(a)

it prints [1, 1000, 3], even though you seemingly have only modified b, a is modified as well. This is because they are the same array.

So instead of duplicating the references to the same array, one might copy the array, or use only its values.

import numpy as np

a = np.array([1,2,3])
b = np.copy(a)

b[1] = 1000
print(a)

In your case,

self.xcoords[:] = self.original_xcoords
self.ycoords[:] = self.original_ycoords

or

self.xcoords = np.copy(self.original_xcoords)
self.ycoords = np.copy(self.original_ycoords)
Sign up to request clarification or add additional context in comments.

1 Comment

Oh yes of course! Thanks for that. I knew it was something stupid.
0

Long story short: you should change your reset function:

self.xcoords = np.copy(self.original_xcoords)
self.ycoords = np.copy(self.original_ycoords)

If to be short, all variables in Python are something like links to objects. When you write a = 5, a gets link to object "5", when you write b = a, b gets link to "5" as a. The same situation takes place with your self.coords and self.original_coords. You had to copy them to make different objects they are linked to. They link to the same object after the first invocation of your reset() function. You saved bad coords there, so since this moment your self.xcoords and self.original_coords objects linked to the same object

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.