Toggling annotation on and off in matplotlib - python

I am trying to write a python script that uses matplotlib. The idea is that when the user runs the script, an interactive window pops up in which they can toggle certain plots on and off with the CheckButtons that matplotlib provides. I managed to figure out how to change the visibility of the plots themselves, however, I am struggling to do the same for the annotation. For the lines I have the following code:
def plotsetlines(lines,toggle):
""" plot vertical, labeled lines """
x = []
lmin = 4
lmax = 6
for name in lines:
x.append(lines[name])
plt.annotate(s=name, xy=(lines[name], lmax), xytext=(lines[name], lmax+1.1), rotation=90,size='large', visible=toggle)
print x
return plt.vlines(x, lmin, lmax, lw=2,visible=toggle)
Here lines is a dictionary of the form:
lines1 = {"a":115.27, "b":115.0, "c":112.0}
and toggle is a boolean. Once this function has been called, I can change the visibility of the lines as follows:
lns1 = plotsetlines(lines1,True)
lns1.set_visible(not lns1.get_visible())
The problem is, I have no idea how I can do the same thing for my annotations easily. I know that the Annotate object has the get/set_visible methods as well, but the function I wrote doesn't return the annotations in the same way that it returns my lines, so I'm not sure what to call the methods on. Any suggestions and ideas are welcome.
Also, since this is my first question posted here, please let me know if you have suggestions about the layout/wording etc. of the question itself. Thanks!

Related

How are you supposed to know which parameters you can pass into a class?

Lately I've been following a tutorial and I need to recreate a project. In this instance it's a map made with openstreetmap and folium. On the example map i'm to recreate is a map with small circle markers that are all of a different color.
The first thing I do is open jupyter notebook python session and enter this:
>>> import folium
>>> dir(folium)
from here I see all the things that I can do with folium. One of them is called "Circle.Marker" and seems to be exactly what I need.
>>> help(folium.CircleMarker)
and I'm greeted with this:
"Help on class CircleMarker in module folium.vector_layers:
class CircleMarker(folium.map.Marker)
CircleMarker(location=None, radius=10, popup=None, tooltip=None, **kwargs)"
I see that the parameters that I can pass into CircleMarker are location, radios, popup, tootip, and **kwargs)
unfortunately none of them seem to be what I need. Then I go to the code example and see that they've passed
folium.CircleMarker(location= foo, popup = foo , radius = foo, fill_color = foo)
How was i ever supposed to know that "fill_color" was a parameter that circlemarker would accept? I looked at the "**kwargs" part and that also doesn't seem to be the answer. This must mean that I'm missing some fundamental step in the learning process.
If anyone could point me in the right direction, I'd really appreciate.

Show text annotations on selection in Bokeh

I have a little Bokeh plot with data points and associated text labels. What I want is for the text labels to only appear when the user selects points with the box select tool. This gets me close:
from bokeh.plotting import ColumnDataSource,figure,show
source = ColumnDataSource(
data=dict(
x=test[:,0],
y=test[:,1],
label=[unquote_plus(vocab_idx[i]) for i in range(len(test))]))
TOOLS="box_zoom,pan,reset,box_select"
p = figure(plot_width=400, plot_height=400,tools=TOOLS)
p.circle(x='x',y='y', size=10, color="red", alpha=0.25,source=source)
renderer = p.text(x='x',y='y',text='label',source=source)
renderer.nonselection_glyph.text_alpha=0.
show(p)
This is close, in that if I draw a box around some points, those text labels are shown and the rest are hidden, but the problem is that it renders all the text labels to start (which is not what I want). The initial plot should have all labels hidden, and they should only appear upon a box_select.
I thought I could start by rendering everything with alpha=0.0, and then setting a selection_glyph parameter, like this:
...
renderer = p.text(x='x',y='y',text='label',source=source,alpha=0.)
renderer.nonselection_glyph.text_alpha=0.
renderer.selection_glyph.text_alpha=1.
...
But this throws an error:
AttributeError: 'NoneType' object has no attribute 'text_alpha'
When trying to access the text_alpha attribute of selection_glyph.
I know I could use a hover effect here or similar, but need the labels to default to not being visible. An alternative, but not ideal, solution would be to have a toggle button that switches the labels on and off, but I'm not sure how to do that either.
What am I doing wrong here?
As of version 0.11.1, the value of selection_glyph is None by default. This is interpreted by BokehJS as "don't do anything different, just draw the glyph as normal". So you need to actually create a selection_glyph. There are two ways to do this, both demonstrated here:
http://docs.bokeh.org/en/latest/docs/user_guide/styling.html#selected-and-unselected-glyphs
Basically, they are
by hand
Create an actual Circle Bokeh model, something like:
selected_circle = Circle(fill_alpha=1, fill_color="firebrick", line_color=None)
renderer.selection_glyph = selected_circle
OR
using glyph method parameters
Alternatively, as a convenience Figure.circle accepts paramters like selection_fill_alpha or selection_color (basically any line or fill or text property, prefixed with selection_) :
p.circle(..., selection_color="firebrick")
Then a Circle will be created automatically and used for renderer.selection_glyph
I hope this is useful information. If so, it highlights that there are two possible ways that the project could be improved:
updating the docs to be explicit and highlight that renderer.selection_glyph is None by default
changing code so that renderer.selection_glyph is just a copy of renderer.glyph by default (then your original code would work)
Either would be small in scope and ideal for a new contributor. If you would be interested in working up a Pull Request to do either of these tasks, we (and other users) would certainly be grateful for the contribution. In which case, please just make an issue first at
https://github.com/bokeh/bokeh/issues
that references this discussion, and we can provide more details or answer any questions.

Matplotlib unexpectedly hidden polygons

I am using matplotlib 1.4.3 with python 3.3. I would like to draw multiple figures with multiples sub-plot in it. I am facing some kind of bug that is really boring me:
When I use fill() and boxplot() methods whithin a figure, results of those functions are hidden if the figure is not the first one created.
This bug seems to be related somehow to polygon display and matplotlib environment state.
When a parse only one single figure, everything is working fine. When I parse multiple figures, the first one is ok. But, in every other subsequent figures, everything is all-right except wiskerbox and polygons that are hidden.
Each plot code is wrapped into a function, which accepts positional arguments, *args and **kwargs. Lets say signature are:
def myplot(t, x, *args, *kwargs):
# [...]
hFig = plt.figure()
# [...]
return hFig
As far as I understand python mechanisms, after the function call is resolved, there must be nothing alive (I do not use global variables) except what matplotlib environment has stored into its global namespace variables.
In every call, I close() my figure, I also have tried hFig.clf() in addition before leaving function, but it does not solve the problem.
Each plot is wrapped into printer (decorator) to add generic functionalities:
def myprint(func):
def inner(*args, **kwargs)
# [...]
hFig = func(*args, **kwargs)
# [...]
return inner
What I have tried so far:
Increased zscore of wiskerbox and polygons, not working;
Execute plot generation in different threads, not working;
Execute plot generation in different processes, working but I have to change my function signature because it can be pickled.
I do not want use dill and pathos, even if I would I cannot.
It looks like it is a matplotlib environment bug, because when I run different processes, this environment is recreated from scratch and it works the way it should. I would like to know if there is a way to reset matplotlib environment state within a python script. If not, what can I do for solving this issue.
Obs.: I am using GridSpecs object and subplot() method to create my figures. The problem was not present when I computed boxes myself and used add_axes() method.
Update: Here you can find a MCVE of my problem. By doing this simple example, I found the line which makes my bug happens (looks like I have old bad Matlab behaviours). It seems that plt.hold(False) alters the way of polygons and boxplot are displayed. And, as I pointed out, it was related to matplotlib global namespace variable. I just misunderstood the way this method works, and in each sub-process, it was reset.
import datetime
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gspec
def bloodyplotit(t_, x_):
hFig = plt.figure()
gs = gspec.GridSpec(1, 4, height_ratios=[1], width_ratios=[15, 2, 3, 1])
gs.update(left=0.10, right=0.90, top=0.90, bottom=0.25, hspace=0.05, wspace=0.05)
plt.hold(True)
hAxe = plt.subplot(gs[0,0])
hAxe.plot(t_, x_)
#plt.hold(False) # <------------------------ This line make the scirpt bug
hAxe = plt.subplot(gs[0,1])
hAxe.hist(x_, orientation='horizontal')
hAxe = plt.subplot(gs[0,3])
hAxe.boxplot(x_)
plt.show()
n = 1000
t = datetime.datetime.utcnow() + np.arange(n)*datetime.timedelta(minutes=1)
x = np.random.randn(1000,1)
for i in range(10):
bloodyplotit(t, x)
Here's an even more minimal script that produces the error:
x = np.random.randn(1000)
fig, ax = plt.subplots(1, 2)
ax[0].hold(True)
ax[0].boxplot(x);
ax[1].hold(False)
ax[1].boxplot(x);
As far as I can tell, this is expected behavior. According to the documentation of plt.hold,
When hold is True, subsequent plot commands will be added to the current axes. When hold is False, the current axes and figure will be cleared on the next plot command.
Boxplot is a compound object: it is created by calling multiple plotting commands. If hold is False, the axes are cleared between each of those commands, so parts of the boxplot don't show up.
Personally, I've never found a reason to toggle the hold state in 10+ years of using matplotlib. My experience is that doing it (especially globally) just causes confusion, and I'd recommend avoiding it.

Showing plots if checkbox is checked, on python (with PyQt4)

I'm brand new to Python and I'm trying to make my first program with PyQt4. My problem is basically the following: I have two checkboxes (Plot1 and Plot2), and a "End" push button, inside my class. When I press End, I would like to see only the plots that the user checks, using matplotlib. I'm not being able to do that. My first idea was:
self.endButton.clicked.connect(self.PlotandEnd)
self.plot1Checkbox.clicked.connect(self.Plot1)
self.plot2Checkbox.clicked.conncet(self.Plot2)
def PlotandEnd(self)
plot1=self.Plot1()
pyplot.show(plot1)
plot2=self.Plot2()
pyplot.show(plot2)
def Plot1(self)
plot1=pyplot.pie([1,2,5,3,2])
return plot1
def Plot2(self)
plot2=pyplot.plot([5,3,5,8,2])
return plot2
This doesn't work, of course, because "PlotandEnd" will plot both figures, regardless of the respective checkbox. How can I do what I'm trying to?
Wrap the plot creation in an if statement that looks at the state of the check boxes. For example:
def PlotandEnd(self)
if self.plot1Checkbox.isChecked():
plot1=self.Plot1()
pyplot.show(plot1)
if self.plot2Checkbox.isChecked():
plot2=self.Plot2()
pyplot.show(plot2)
You also don't need the following lines:
self.plot1Checkbox.clicked.connect(self.Plot1)
self.plot2Checkbox.clicked.conncet(self.Plot2)
This does nothing useful at the moment! Qt never uses the return value of your PlotX() methods, and you only want things to happen when you click the End button, not when you click a checkbox. The PlotX() methods are only currently useful for your PlotandEnd() method.

Clearing graph before replotting matplotlib

I have a little app that allows me to change an input value with a tKinter scale widget and see how a graph reacts to different changes in inputs. Every time I move the scale, it's bound to an event that redoes the calculations for a list and replots. It's kind of slow.
Now, I'm replotting the entire thing, but it's stacking one axis on top of the other, hundreds after a few minutes of use.
deltaPlot = Figure(figsize=(4,3.5), dpi=75, frameon=False)
c = deltaPlot.add_subplot(111)
c.set_title('Delta')
deltaDataPlot = FigureCanvasTkAgg(deltaPlot, master=master)
deltaDataPlot.get_tk_widget().grid(row=0,rowspan=2)
and the main loop runs
c.cla()
c.plot(timeSpread,tdeltas,'g-')
deltaDataPlot.show()
It's clearing the initial plot, but like I said the axes are stacking (because it's redrawing one each time, corresponding to the slightly altered data points). Anyone know a fix?
To improve speed there are a couple of things you could do:
Either Run the remove method on the line produced by plot:
# inside the loop
line, = c.plot(timeSpread,tdeltas,'g-')
deltaDataPlot.show()
...
line.remove()
Or Re-use the line, updating its coordinates appropriately:
# outside the loop
line, = c.plot(timeSpread,tdeltas,'g-')
# inside the loop
deltaDataPlot.show()
line.set_data(timeSpread,tdeltas)
The documentation of Line2d can be found here.
You might also like to read the cookbook article on animation.
HTH

Categories

Resources