matplotlib interactive graphing (manually drawing lines on a graph) - python

I have succesfully plotted a set of date sequenced data (X axis is date) using matplotlib. However, I want to be able to manually draw lines from one (date1, y1) to another (date2, y2) on the plotted graph.
I can't seem to find any examples that show how to do this - or indeed if it is even posible.
To summarize, this is what I want to do:
Draw a set of lines on the plotted graph
Save the manually drawn line data to file
Load the manually drawn line data from file (to recreate the graph)
Ideally, I would like to store 'meta data' about the drawn lines (e.g. color, line-width etc)
Can someone post a skeleton snippet (preferably with links to further info), to show how I may get started with implementing this (the main requirements being the ability to manually draw lines on a graph and then to save/load the lines into a plot).
Note: By 'manually', I mean to be able to draw the lines by clicking on a point, and then clicking on another point in the plotted graph. to draw a line between the two points (or simply clicking on a point and dragging and releasing the mouse at another point on the plotted graph)
[[Update]]
dawe, thanks very much for the snippet you provided. This allows me to do what I am trying to do - however, as soon as the line is drawn on the canvas (after the second mouse click), the GUI crashes and I get this warning message on the console:
/usr/local/lib/python2.6/dist-packages/matplotlib/backend_bases.py:2192: DeprecationWarning: Using default event loop until function specific to this GUI is implemented
warnings.warn(str,DeprecationWarning)
Do you know what is causing this warning and the abrupt program termination?
Also, is it possible to draw more than one line on the graph? (I'm guessing this will involve writing some kind of event handler that will instantiate a linedrawer variable). At the moment , I get the chance to draw only one line before the 'app' abruptly terminates.

I would write something like this:
import matplotlib.pyplot as plt
class LineDrawer(object):
lines = []
def draw_line(self):
ax = plt.gca()
xy = plt.ginput(2)
x = [p[0] for p in xy]
y = [p[1] for p in xy]
line = plt.plot(x,y)
ax.figure.canvas.draw()
self.lines.append(line)
Using ginput() you can avoid more complicated event handling. The way it 'works' is you plot something:
plt.plot([1,2,3,4,5])
ld = LineDrawer()
ld.draw_line() # here you click on the plot
For saving/loading the line data to a file you can easily implement a method using pickle or shelve. You can also pass the necessary metadata by the method draw_line()

Related

How could I display the y-value of 2 different curves when clicked with Tkinter and Matplotlib?

I want to be able to click anywhere on the graph and a vertical line would appear along the x-value that was clicked. I wish to be able to display the values of each of the graph's curves at that x-value. I've made a diagram that shows what I mean.
I've been Googling how to do this, but haven't really found anything relevant to what I'm looking for. The only thing I can think to do is to store the coordinates where the user clicked their mouse pull the y-values at that x-value. I would then manually draw the line over the graph. I think this would work, but it's a lot messier than using a built-in function if such a function exists. Does Matplotlib have this capability?
I could not find any sort of built in method for achieving this functionality. Fortunately, it was not that difficult to make manually.
On your FigureCanvasTkAgg object you can call:
figurecanvas.callbacks.connect('button_press_event', onclick)
You will also want to define a variable that stores the index of the line you will move around.
line_index = None
Doing this defines a behavior to occur when when the canvas is clicked. The onclick method must have only one parameter, which is the click event.
def onclick(event):
if event.xdata and event.ydata: # This checks if click is outside the chart or not
# line_index is initially None, so we need to check if there is a
# previous line to delete
if line_index:
del self.ax0.lines[line_index] # this removes the previous line
self.line = self.ax0.axvline(x=event.xdata) # draw a new line where clicked
self.ax0.figure.canvas.draw() # update the canvas
line_index = len(axis.lines) - 1 # store index of the line for future deletion
In the onclick method, you can also include functionality that outputs the value of a line at the point clicked. You can access the x-value of the location clicked with event.xdata. This can be used in conjunction with the data you are graphing to output the y-value at the point clicked.

Interactive matplotlib plot: Define and plot a polygon

I have created a grid of hexagons using matplotlib.patches library. The figure that is shown by my program is interactive: It allows for picking a hexagon with the left mouse button, which results in filling the hexagon black. This way, I am marking the circumference of a polygon:
Now, I want to connect the filled hexagons, such that I can see the circumference of the polygon. I want the polygon to be drawn upon pressing a key. Here is my idea:
def draw_circumference(event):
if event.key == 'd':
print(circumference)
plt.Polygon(circumference, fill=False, edgecolor='k')
#fig.canvas.draw()
fig.canvas.mpl_connect("key_press_event", draw_circumference)
The variable circumference contains the (x,y) coordinates of the respective centers of the marked hexagons as a list of tuples: [(x1,y1), x2,y2), ..., (xn,yn)]. I have commented the fig.canvas.draw() because I think it should work without this line. But so far it does not work, neither with, nor without fig.canvas.draw().
The function gets called, however. I know this as the print-statement is executed.
Any ideas what I am doing wrong?
Hard to debug without the full code, but usually you have to add a patch artist such as a polygon explicitly to the axis. Also, you probably do need the redraw call as the contents of the axis have changed.
p = plt.Polygon(circumference, ...)
ax.add_patch(p) # or ax.add_artist(p)
fig.canvas.draw()

Dynamically updated plots in python, how to save image at the end

Background
With the help of the following, I have created a dynamically updating plot
Dynamically updating plot in matplotlib
Updating a plot in python's matplotlib
How to update a plot in matplotlib?
Plot code
plt.ion()
fig_analysis = plt.figure()
for i in range(5):
ax = fig_analysis.add_subplot(211)
l1, = ax.plot(np.linspace(0, 200, simulation_time), variable_1)
ax.set_ylabel(r'$Variable\ 1$')
ax = fig_analysis.add_subplot(212)
l2, = ax.plot(np.linspace(0, 200, simulation_time), variable_2)
ax.set_ylabel(r'$Variable\ 2$')
ax.set_xlabel(r'$years$')
fig_analysis.canvas.draw()
plt.pause(0.5)
Behaviour
This code creates a plot and updates it. However, it closes the final plot after completion of the loop
Question
How should I modify the code to ensure that at the end of the loop, the program doesn't close the plot window, and I can save the image as I want.
Manual solution
One of the ways of achieving this is to manually pause the program. However, as runtime of my program is not fixed, it is difficult to implement this strategy.
Add
plt.ioff() # turn interactive mode off
plt.show()
at the end of the code.

Python matplotlib: how to plot realtime over plotted graph

Here I have plotted a line chart with two lists
import random as random
import matplotlib.pyplot as plt
lis1=random.sample(range(1, 100), 10)
lis2 = random.sample(range(1, 100), 10)
plt.plot(range(0,len(lis1), 1), lis1)
plt.plot(range(0,len(lis2), 1), lis2)
plt.show()
Now, I'm getting a third list from Arduino in realtime. My question is how to plot that third list/line over this plot without redrawing entire chart.
EDIT: Third list is something like this
import time
lis3 =[]
for i in range(10):
lis3.append(i)
time.sleep(1)
plt.show() will display the current chart that you're working on whereas plt.draw() will re-draw the figure. This essentially allows you to change the graph as your data changes
The plt.draw docs state:
This is used in interactive mode to update a figure that has been altered using one or more plot object method calls; it is not needed if figure modification is done entirely with pyplot functions, if a sequence of modifications ends with a pyplot function, or if matplotlib is in non-interactive mode and the sequence of modifications ends with show() or savefig().
have a look at the following post: When to use cla(), clf() or close() for clearing a plot in matplotlib?
i think you can clear the figure by using plt.clear() in an timer event. Re-drawing can be done by using the plt.draw() function. Because of the realtime data you have to have a function which is called after a certain delay. There i would call this plt.clear() or plt.draw() function.
Afterwards you have to re fill the lists or make a new list to draw the third line.
I don't know a better solution and maybe that's not what you want, because it's some keind of re-drawing but i hope that this is useful for you!
Also have a look at:
Dynamically updating plot in matplotlib
How to update a plot in matplotlib?

Plot in python that updates on keyboard hit

I've been struggling to achieve something that is trivial in Octave: to produce a series of plots, that change when I hit a key. Here's my octave code sample.
x = [1:10];
for c=1:3
plot(x,c.*x);
hold off;
input('a');
end
When I try to do the same in python, I realized that python matplotlib has the save function, which puts it in non-blocking mode, and so I have to close the figure using a mouse, for the next figure to be produced. And the next figure is at a random other location on the screen. How can I get python to imitate the above behavior? I've tried various combinations of ion(), ioff(), plt.show(), plt.draw(), but haven't succeeded.
You can do something fancier if you want, using mpl_connect
First import pylab
from pylab import *
Then define an updateData function that can be connected to the figure canvas.
i = 1
def updateData(event):
global i,x
i +=1
y = i*x
data.set_data(x,y)
ylim(y[0],y[-1])
draw()
i and x are global variables in this case. (This could be treated in better way, this is just an example!)
Then, create you plot and connect with your defined function.
f = figure()
data, = plot(x,x)
i=1
f.canvas.mpl_connect("key_press_event",updateData)
show()
Whenever you hit any key in the keyboard (When the figure window is selected) the function updateData is called, i is incremented and the plot updated.
Have fun!

Categories