How to control mouseover text in matplotlib - matplotlib

When you mouseover an image shown with imshow, you can mouseover the image to inspect its RGB values. The bottom-right corner of the matplotlib window (sharing space with the toolbar), shows the image coordinates and RGB values of the pixel being pointed at:
x = 274.99 y = 235.584 [.241, .213, .203]
However, when I mouseover a quiver plot, it only shows the x and y coords of the pointer, but not the value of the 2D vector being pointed at. Is there a way to get the vector values to show up?
I would be fine with writing a custom mouse event handler, if I only knew how to set that bit of text in the matplotlib window.

There were times when the information about the color value was not present by default. In fact I think the current version is based on some code that came up in Stackoverflow on some question about that feature.
I quickly found those two questions:
matplotlib values under cursor
Interactive pixel information of an image in Python?
The idea would be to change the function that is called when the mouse hovers the axes. This function is stored in ax.format_coord. So a possible solution is to write your custom function to return the desired output based on the input coordinates, e.g. something like
def format_coord(x, y):
try:
z = # get value depending on x,y, e.g. via interpolation on grid
# I can't fill this because the kind of data is unknown here
return "x: {}, y: {}, z: {}".format(x,y,z)
except:
return "x: {}, y: {}".format(x,y)
ax.format_coord = format_coord

Related

Making plot with subfigure created elsewhere (matplotlib)

I have a function "makeGrid(tensor)" that takes as an argument a pytorch tensor of shape (B,3,H,W), and uses ImageGrid to make a figure that displays the batch of figures in a grid.
Now, the model that outputs "tensor", depends on one parameter, "alpha". I would like to include a slider on the figure such that I can modify alpha "live". I am using the "Slider" widget from matplotlib roughly as such :
result = model(tensor)
images,grid = makeGrid(result)
ifig = plt.figure()# Figure with slider
axalpha = ifig.add_axes([0.25, 0.1, 0.65, 0.03])
# How to add the "images" to ifig ???
alpha_slider = Slider(
ax=axsmol,
valmin=-2,
valmax=2,
valinit=1,
)
def update(val):
model.alpha = alpha_slider.val
result= model(img_batch)
images,grid = makeGrid(result)
# Same problem, need to update ifig with new images
alpha_slider.on_changed(update)
plt.show()
So, my main problem is I have no idea how to use the already created figure (images) and/or grid (which is an ImageGrid object, roughly a list of axes afaik) as a subplot of "ifig", the interactive figure which contains slider and images.
Very sorry as this seems to be a basic question, but searching for "how to add already created figure as subplot of figure" or other things didn't yield solutions to my problem (or at least, in my limited point of view).

Matplotlib widget, secondary y axis, twinx

i use jupyterlab together with matplotlib widgets. I have ipywidgets installed.
My goal is to choose which y-axis data is displayed in the bottom of the figure.
When i use the interactive tool to see the coordinates i get only the data of the right y-axis displayed. Both would be really nice^^ My minimal code example:
import matplotlib.pyplot as plt
import numpy as np
%matplotlib widgets
x=np.linspace(0,100)
y=x**2
y2=x**3
fig,ax=plt.subplots()
ax2=ax.twinx()
ax.plot(x,y)
ax2.plot(x,y2)
plt.show()
With this example you might ask why not to plot them to the same y-axis but thats why it is a minimal example. I would like to plot data of different units.
To choose which y-axis is used, you can set the zorder property of the axes containing this y-axis to a higher value than that of the other axes (0 is the default):
ax.zorder = 1
However, that will cause this Axes to obscure the other Axes. To counteract this, use
ax.set_facecolor((0, 0, 0, 0))
to make the background color of this Axes transparent.
Alternatively, use the grab_mouse function of the figure canvas:
fig.canvas.grab_mouse(ax)
See here for the (minimal) documentation for grab_mouse.
The reason this works is this:
The coordinate line shown below the figure is obtained by an event callback which ultimately calls matplotlib.Axes.format_coord() on the axes instance returned by the inaxes property of the matplotlib events that are being generated by your mouse movement. This Axes is the one returned by FigureCanvasBase.inaxes() which uses the Axes zorder, and in case of ties, chooses the last Axes created.
However, you can tell the figure canvas that one Axes should receive all mouse events, in which case this Axes is also set as the inaxes property of generated events (see the code).
I have not found a clean way to make the display show data from both Axes. The only solution I have found would be to monkey-patch NavigationToolbar2._mouse_event_to_message (also here) to do what you want.

TramineR legend position and axis

I'm working with TraMineR and I don't know how to arrange my plot. So basically what i would like to have the legend under the plot and to remove the space between the x and y axis. Any help is welcomed.
The plot:
Sample code:
seqdplot(Activities.seq, with.legend=FALSE)
legend("bottom", legend=attr(Activities.seq, "labels"),
fill=attr(Activities.seq, "cpal"),
inset=-.1, bty="o", xpd=NA, cex=.75,ncol=3)
The family of seqplot functions offers a series of arguments to control the legend as well as the axes. Look at the help page of seqplot (and of plot.stslist.statd for specific seqdplot parameters).
For instance, you can suppress the x-axis with axes=FALSE, and the y-axis with yaxis=FALSE.
To print the legend you can let seqdplot display it automatically using the default with.legend=TRUE option and control it with for examples cex.legend for the font size, ltext for the text. You can also use the ncol argument to set the number of columns in the legend.
The seqplot functions use by default layout to organize the graphic area between the plots and the legend. If you need more fine tuning (e.g. to change the default par(mar=c(5.1,4.1,4.1,2.1)) margins around the plot and the legend), you should create separately the plot(s) and the legend and then organize them yourself using e.g. layout or par(mfrow=...). In that case, the separate graphics should be created by setting with.legend=FALSE, which prevents the display of the legend and disables the automatic use of layout.
The color legend is easiest obtained with seqlegend.
I illustrate with the mvad data that ships with TraMineR. First the default plot with the legend. Note the use of border=NA to suppress the too many vertical black lines.
library(TraMineR)
data(mvad)
mvad.scode <- c("EM", "FE", "HE", "JL", "SC", "TR")
mvad.seq <- seqdef(mvad, 17:86,
states = mvad.scode,
xtstep = 6)
# Default plot with the legend,
seqdplot(mvad.seq, border=NA)
Now, we suppress the x and y axes and modify the display of the legend
seqdplot(mvad.seq, border=NA,
axes=FALSE, yaxis=FALSE, ylab="",
cex.legend=1.3, ncol=6, legend.prop=.11)
Here is how you can control the space between the plot and the x and y axes
seqdplot(mvad.seq, border=NA, yaxis=FALSE, xaxis=FALSE, with.legend=FALSE)
axis(2, line=-1)
axis(1, line=0)
Creating the legend separately and reducing the left, top, and right margins around the legend
op <- par(mar=c(5.1,0.1,0.1,0.1))
seqlegend(mvad.seq, ncol=2, cex=2)
par(op)

pandas.plot and pyplot.save_fig create different sized PNGs for same figsize

When I call the same function that uses pandas.plot with the same figsize, I get different sized PNG files. The width is same but the height in pixels changes. I suspect that the length of the x-axis labels changes the height.I have not yet tried directly calling the matplotlib functions.
I have also tried plt.rcParams['figure.figsize'] = (7,4). The problem does not appear to be in how figsize is set. My print_fig_info always produces the desire values.
# Primitive way that confirmed that the figure size does not change
def print_fig_info(label=""):
print(label,str(plt.gcf().get_size_inches()))
def my_plot(df):
global c
print_fig_info("Before plot")
df.plot(kind='bar', figsize=(7,4))
print_fig_info("After plot")
# want to make output files unique
c += 1
plt.savefig("output"+str(c), bbox_inches='tight', dpi='figure')
In your call to savefig you explicitely ask matplotlib to change the figsize to the minimal size that still fits all the elements in via bbox_inches='tight'.
Or in other words, bbox_inches='tight' is especially designed for changing the figure size to the minimum bounding box, and matplotlib is therefore doing what it's being asked for.
Solution: Don't use bbox_inches='tight'.

Matplotlib - transform bbox

I printed some text into a plot. Now I want to make a copy of this text and move it to different coordinates. I guess I'll have to do this with tranform, but did not find a solution yet.
here is the code:
props = dict( facecolor='#DDDDDD', alpha=1,edgecolor='#FFFFFF',boxstyle="Square,pad=0.5")
text2=plt.text(4, 4, "text",va='top', ha='left',bbox=props)
plt.draw()
bb2=text2.get_bbox_patch().get_window_extent().transformed(ax.transData.inverted()).get_points()
To move the text to different coordinates you only need:
text2.set_position((new_x,new_y))
you could also use:
text2.set_x(new_x)
text2.set_y(new_y)