2

I am trying to do a simple plot interface that allows me to click to add points to a list and then with another key or another click, call the triangulation of those points.

Matplotlib offers a small example for adding points to a line but I dont know how to make it so that I simply add points to a list and then call a function to triangulate

from matplotlib import pyplot as plt

class LineBuilder:
    def __init__(self, line):
        self.line = line
        self.xs = list(line.get_xdata())
        self.ys = list(line.get_ydata())
        self.cid = line.figure.canvas.mpl_connect('button_press_event', self)

    def __call__(self, event):
        print('click', event)
        if event.inaxes!=self.line.axes: return
        self.xs.append(event.xdata)
        self.ys.append(event.ydata)
        self.line.set_data(self.xs, self.ys)
        self.line.figure.canvas.draw()

fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_title('click to build line segments')
line, = ax.plot([0], [0])  # empty line
linebuilder = LineBuilder(line)

plt.show()

I use scikit delunay triangulation

import numpy as np
from scipy.spatial import Delaunay
points=np.array([[134,30],[215,114],[160,212],[56,181],[41,78]])
tri = Delaunay(points)
plt.triplot(points[:,0], points[:,1], tri.simplices.copy())
plt.plot(points[:,0], points[:,1], 'o')
plt.show()

Thank you

2
  • Those seem to be very different questions. I suggest to have a look at the user guide for anything related to the objects in use and keep this question clean on the actual problem. Commented Jan 18, 2018 at 11:09
  • Thank you, I removed second question because it is very different as you say. Commented Jan 18, 2018 at 11:13

1 Answer 1

5

I think you already have everything you need in place to get the desired triplot of the clicked points. You just need to move the second code into the __call__ of the first one and adapt it to use the points that had previously been selected.

import numpy as np
from scipy.spatial import Delaunay
from matplotlib import pyplot as plt

class LineBuilder:
    def __init__(self, line):
        self.line = line
        self.xs = []
        self.ys = []
        self.cid = line.figure.canvas.mpl_connect('button_press_event', self)

    def __call__(self, event):
        if event.inaxes!=self.line.axes: return
        if event.button == 1:
            self.xs.append(event.xdata)
            self.ys.append(event.ydata)
            self.line.set_data(self.xs, self.ys)
        elif event.button == 3:
            points=np.c_[self.xs, self.ys]
            tri = Delaunay(points)
            self.line.axes.triplot(points[:,0], points[:,1], tri.simplices.copy())
        self.line.figure.canvas.draw()

fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_title('left click to choose points, \n \
             right click to plot delaunay triangulation')
line, = ax.plot([], [], marker="o", ms=10, ls="")  # empty line
linebuilder = LineBuilder(line)

plt.show()

enter image description here

In order to show the tiangulation at each new point being drawn as well as being able to restart the whole interaction the solution would be a little more involved. One would then need to check if there already is a plot in the axes and remove it before plotting a new one.

import numpy as np
from scipy.spatial import Delaunay
from matplotlib import pyplot as plt

class LineBuilder:
    def __init__(self, line):
        self.line = line
        self.cid = line.figure.canvas.mpl_connect('button_press_event', self)
        self.trip = None
        self.reset()

    def reset(self):
        self.xs = []
        self.ys = []
        if self.trip: 
            for t in self.trip:
                t.remove()
        self.trip = None
        self.line.set_data(self.xs, self.ys)

    def __call__(self, event):
        if event.inaxes!=self.line.axes: return
        if event.button == 1:
            self.xs.append(event.xdata)
            self.ys.append(event.ydata)
            self.line.set_data(self.xs, self.ys)
            points=np.c_[self.xs, self.ys]
            if len(self.xs) >= 3:
                tri = Delaunay(points)
                if self.trip: 
                    for t in self.trip:
                        t.remove()
                self.trip = self.line.axes.triplot(points[:,0], points[:,1], 
                                                 tri.simplices.copy(), color="C0")
        elif event.button==3:
            self.reset()

        self.line.figure.canvas.draw()

fig = plt.figure()
ax = fig.add_subplot(111)
ax.axis([0,1,0,1])
ax.set_title('left click to choose points,right click restart')
line, = ax.plot([], [], marker="o", ms=10, ls="")  # empty line
linebuilder = LineBuilder(line)

plt.show()
Sign up to request clarification or add additional context in comments.

2 Comments

Nice! Thank you, you are right, everything was already there. I still have to learn to use the pyplot. If I see it correctly, it is a global element that will allow me to make calls from any class or definition in my script? What about having the triangulation re done with every point? do you know how could I clear or update the figure every time?
I updated the answer with a solution to paint the triangulation as soon as it is possible (having selected 3 points). I also removed that "global" element plt, such that this is now completety object oriented.

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.