Interactive matplotlib plot: Define and plot a polygon - python

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()

Related

Circular / arc spine in custom matplotlib Axes class

So I am trying to build a class to create a radialplot, also known as
Galbraith plot (https://en.wikipedia.org/wiki/Galbraith_plot).
It's essentially a cartesian plot of standardised estimates vs associated errors. The estimates values are represented by the slope of the line that goes through the origin.
here is what it looks like.
Now I want reproduce that with matplotlib so I thought I would build a
class that inherits from Axes...
So far so good...
Now I am wondering what to do with the right spine.
I could obviously plot a line and create ticks manually in the plotting area but I would rather do it properly and use the matplotlib machinery...
I saw that there are options to create a circular spine, or even arc spine.
https://matplotlib.org/3.1.0/api/spines_api.html
Looking at the spine documentation a Spine object requires, an axes, a type and a path... I am not sure what to do with that.
If I you give a path to the Spine class, what's the point of having the arc_spine or circular_spine method...
I thought I could do something like this (self refers to an Axis instance):
x, y = self.zscale_coordinates()
verts = list(zip(x,y))
codes = [Path.LINETO for i in range(len(verts))]
codes[0] = Path.MOVETO
spine = Spine(self, "right", Path(verts, codes))
self.spines["right"] = spine
Any help would be really appreciated.
Maybe someone has already done this? I found some R or Java packages but nothing with matplotlib and I would like to include it in some python code.
Thanks!

Changing the order of axes in Mayavi

I am generating STL files for 3D printing and then using mlab/mayavi to display them. I would like the Z axis to remain vertical as I rotate the image. According to the mayavi documentation, this can be achieved using the following incantation:
fig = mlab.gcf()
from tvtk.api import tvtk
fig.scene.interactor.interactor_style = tvtk.InteractorStyleTerrain()
Unfortunately, as you can see from this screenshot of my app, it is not the Z axis but the Y axis that gets maintained vertical.
This is an issue because 3D printers always think of Z as the vertical axis, so I really need Z and not Y to be oriented vertically. Is there a way in which this can be achieved?
I have experienced the same problem, vertically aligned along the y-axis.
To get the scene to align vertically along the z-axis (which you want), you can simply add a scene.mlab.view() call before setting the interactor.
The scene.mlab.view() call aligns the camera correctly (with z up), before the interactor is set. I have found this solution by simply testing a bunch of stuff, I could not find this "hack" in the documentation.
New code:
fig = mlab.gcf()
from tvtk.api import tvtk
fig.scene.mlab.view(0, 90)
fig.scene.interactor.interactor_style = tvtk.InteractorStyleTerrain()
You can rename them using the Mayavi button to access the pipeline, then selecting the appropiate axes object and changing the labels.
With code, you can add the xlabel, ylabel and zlabel keyword arguments to specify them. This is specified in the axes API

Is there an ordering to user supplied matplotlib canvas callbacks via mpl_connect?

I register 2 callbacks - 1 for clicking and 1 for picking.
import matplotlib.pyplot as plt
fig = plt.figure()
fig.canvas.mpl_connect('button_release_event', onclick)
fig.canvas.mpl_connect('pick_event', onpick)
...
def onclick(event): ...
def onpick(event): ...
I wanted to know in my click event whether or not it occurs with a pick event, (knowing that clicking on a pickable object triggers both events). I noticed that the pick event always comes first, so I check / set a variable like clicked_point = True # or False in onpick and onclick.
This works fine so far but seems sketchy. I couldn't find an answer in the docs, but perhaps I have simply have not found the right section yet.
My alternative would be to only register 'button_release_event', and calculate Euclidean distances between click coordinates and data points on my scatter plot to determine if the 'pick_event' behavior should be evoked. I'd rather rely on a call order for these functions registered with mpl_connect if it does indeed exist.
I've come across code that checks if an event is in a set of artists (ax.collections.contains(event) or something similar). However, since I use a single call to scatter with the full array of data points (plt.scatter(X, Y, ..)), I am returned a single PathCollection object, which I understand represents a path for all the points in the order they came in. However I could never quite figure out to get indivual artists from this collection, so when I check for event containment in [PathCollection], it always returns false. Not sure if something like this was even doable or if it makes sense, but I figured I'd document it here in case it was relevant.

Matplotlib Patches not matching supplied arguments.

been struggling with python and the matplotlib module. I am trying to draw some circles that are not filled and outlined in black. I am putting the correct arguments in the artist circle but it seems to ignore it and put blue. any ideas? Also the figure shows up automatically without me stating draw or show. How can i block that and control when the graph pops up? Thanks in advance.
my code
def draw_lattice(self,chart):
patches = []
for x in range(1,4):
for y in range (1,4):
circle = Circle((x,y), .25,color='k',fill=False)
# chart.add_patch(circle)
patches.append(circle)
p = PatchCollection(patches)
chart.add_collection(p)
Thanks in advance.
* UPDATE *
if i add each circle individually to the axes it will be formatted properly. If i add the collection it does not work. I have many more shapes to add and i would like to go the collection route. Any reason why one way would work and another wouldn't? I read somewhere that you need to add the artist but i tried that and got an error.
This is a rather late answer but I just came across the same problem and here is how to solve it:
What you need to do is tell the PatchCollection to match the original patches. To do this simply add match_original=True, like so:
p = PatchCollection(my_patches, match_original=True)
You can set the color of the circles when you create the patch collection:
p = PatchCollection(patches,facecolors='w',edgecolor='k')
From the Collection documentation:
"If any of edgecolors, facecolors, linewidths, antialiaseds are None, they default to their matplotlib.rcParams patch setting, in sequence form."

matplotlib interactive graphing (manually drawing lines on a graph)

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()

Categories