Graphics issues when combining matplotlib widgets: Spanselector, cursor, fill_between: - matplotlib

I have found minor graphical issues while using the spanselector, cursor and fill_between widgets, which I would like to share with you.
All of them, can be experienced in this code (which I took from the matplolib example)
"""
The SpanSelector is a mouse widget to select a xmin/xmax range and plot the
detail view of the selected region in the lower axes
"""
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector
import matplotlib.widgets as widgets
Fig = plt.figure(figsize=(8,6))
Fig.set_facecolor('w')
Fig.set
Ax = Fig.add_subplot(211)
x = np.arange(0.0, 5.0, 0.01)
y = np.sin(2*np.pi*x) + 0.5*np.random.randn(len(x))
Ax.plot(x, y, '-')
Ax.set_ylim(-2,2)
Ax.set_title('Press left mouse button and drag to test')
RegionIndices = []
ax2 = Fig.add_subplot(212)
line2, = ax2.plot(x, y, '-')
def onselect(xmin, xmax):
if len(RegionIndices) == 2:
Ax.fill_between(x[:], 0.0, y[:],facecolor='White',alpha=1)
del RegionIndices[:]
indmin, indmax = np.searchsorted(x, (xmin, xmax))
indmax = min(len(x)-1, indmax)
Ax.fill_between(x[indmin:indmax], 0.0, y[indmin:indmax],facecolor='Blue',alpha=0.30)
thisx = x[indmin:indmax]
thisy = y[indmin:indmax]
line2.set_data(thisx, thisy)
ax2.set_xlim(thisx[0], thisx[-1])
ax2.set_ylim(thisy.min(), thisy.max())
Fig.canvas.draw()
RegionIndices.append(xmin)
RegionIndices.append(xmax)
# set useblit True on gtkagg for enhanced performance
span = SpanSelector(Ax, onselect, 'horizontal', useblit = True,rectprops=dict(alpha=0.5, facecolor='purple') )
cursor = widgets.Cursor(Ax, color="red", linewidth = 1, useblit = True)
plt.show()
I wonder if there is some way to avoid these two small issues:
1) You can see that when you select a region the spanselector box (purple) glitches. In this code the effect is barely noticeable but on plots with many lines is quite annoying (I have tried all the trueblit combinations to not effect)
2) In this code when you select a region, the area in the upper plot between the line and the horizontal axis is filled in blue. When you select a new region the old area is filled in white (to clear it) and the new one is filled with blue again. However, when I do that the line plotted, as well as, the horizontal axis, become thicker... Is there a way to clear such a region (generated with fill_between) without this happening... Or is it necessary to replot the graph? Initially, I am against doing this since I have a well structured code and importing all the data again into the spanselector method seems a bit messy... Which is the right way in python to delete selected regions of a plot?
Any advice would be most welcome

Related

Matplotlib: Multiple plots with same layout (no automatic layout)

I am trying to make several pie charts that I can then transition between in a presentation. For this, it would be very useful for the automatic layouting to... get out of the way. The problem is that whenever I change a label, the whole plot moves around on the canvas so that it fits perfectly. I'd like the plot to stay centered, so it occupies the same area every time. I have tried adding center=(0,0) to ax.pie(), but to no avail.
Two examples:
Image smaller, left
Image larger, right
Instead of that effect, I'd like the pie chart to be in the middle of the canvas and have the same size in both cases (and I'd then manually make sure that the labels are on canvas by setting large margins).
The code I use to generate these two images is:
import matplotlib.pyplot as plt
import numpy as np
# Draw labels, from
# https://matplotlib.org/3.2.2/gallery/pie_and_polar_charts/pie_and_donut_labels.html#sphx-glr-gallery-pie-and-polar-charts-pie-and-donut-labels-py
def make_labels(ax, wedges, labs):
bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.72)
kw = dict(arrowprops=dict(arrowstyle="-"),
bbox=bbox_props,
zorder=0, va="center")
for i, p in enumerate(wedges):
if p.theta2-p.theta1 < 5:
continue
ang = (p.theta2 - p.theta1) / 2. + p.theta1
y = np.sin(np.deg2rad(ang))
x = np.cos(np.deg2rad(ang))
horizontalalignment = {-1: "right", 1: "left"}[int(np.sign(x))]
connectionstyle = "angle,angleA=0,angleB={}".format(ang)
kw["arrowprops"].update({"connectionstyle": connectionstyle})
ax.annotate(labs[i], xy=(x, y),
xytext=(1.1*x,1.1*y),
horizontalalignment=horizontalalignment, **kw)
kw=dict(autoscale_on=False, in_layout=False, xmargin=1, ymargin=1)
fig, ax = plt.subplots(figsize=(3, 3), dpi=100, subplot_kw=kw)
wedges, texts = ax.pie(x=[1,2,3], radius=1,
wedgeprops=dict(width=1),
pctdistance=0.7,
startangle=90,
textprops=dict(fontsize=8),
center=(0, 0))
make_labels(ax, wedges, ["long text", "b", "c"])
#make_labels(ax, wedges, ["a", "b", "long text"])
plt.show()
Thanks a lot in advance!
How are you saving your figures? It looks like you may be using savefig(..., bbox_inches='tight') which automatically resized the figure to include all the artists.
If I run your code with fig.savefig(..., bbox_inches=None), I get the following output

subplots_adjust moves axes unpredictably?

I'm working on a python module that creates a matplotlib figure with an on_resize listener. The listener forces the height of the lower axes to a specific number of pixels (rather than scaling relative to figure size). It works. However, if (in matplotlib interactive mode) after creating the plot the user calls fig.subplots_adjust() it messes up subplot sizes. Here's a radically simplified version of what the module does:
import matplotlib.pyplot as plt
plt.ion()
def make_plot():
fig = plt.figure()
gs = plt.GridSpec(10, 1, figure=fig)
ax_upper = fig.add_subplot(gs[:-1])
ax_lower = fig.add_subplot(gs[-1])
ax_upper.plot([0, 1])
ax_lower.plot([0, 1])
fig.canvas.mpl_connect('resize_event', on_resize)
return fig
def on_resize(event):
fig = event.canvas.figure
# get the current position
ax_lower_pos = list(fig.axes[1].get_position().bounds) # L,B,W,H
# compute desired height in figure-relative coords
desired_height_px = 40
xform = fig.transFigure.inverted()
desired_height_rel = xform.transform([0, desired_height_px])[1]
# set the new height
ax_lower_pos[-1] = desired_height_rel
fig.axes[1].set_position(ax_lower_pos)
# adjust ax_upper accordingly
ax_lower_top = fig.axes[1].get_position().extents[-1] # L,B,R,T
ax_upper_pos = list(fig.axes[0].get_position().bounds) # L,B,W,H
# new bottom
new_upper_bottom = ax_lower_top + desired_height_rel
ax_upper_pos[1] = new_upper_bottom
# new height
ax_upper_top = fig.axes[0].get_position().extents[-1] # L,B,R,T
new_upper_height = ax_upper_top - new_upper_bottom
ax_upper_pos[-1] = new_upper_height
# set the new position
fig.axes[0].set_position(ax_upper_pos)
fig.canvas.draw()
Here's the output if the user calls fig = make_plot():
Now if the user calls fig.subplots_adjust, the bottom axis is squished and the space between bottom and top axes is even more squished (the on_resize listener had set them both to 40px):
fig.subplots_adjust(top=0.7)
At this point, grabbing the corner of the window and dragging even a tiny bit is enough to trigger the on_resize listener and restore what I want (fixed pixel height for bottom axes and space between axes) while keeping the newly-added wide top margin intact:
How can I get that result without having to manually trigger a resize event? As far as I can tell, subplots_adjust does not fire off any events that I could listen for.
I think the problem lies in ax.update_params() updating the axes position with a figbox taken from the underlying subplotspec (which as far as I can tell doesn't get updated after initial figure creation?). (note: update_params is called from within subplots_adjust, see here).
The underlying problem seems to be to make an axes with a specific height in pixels. An easy solution to this is to use mpl_toolkits.axes_grid1's make_axes_locatable.
This allows to get rid of any callback and hence of the complete problem of the race condition in the events.
A note: The plot seems to be part of a bigger library. Since it is always nice not to patronize the users of such packages, one would usually allow them to specify the axes to plot to, such that they can put the plot into a bigger figure with other elements. The below solution makes this particularly easy.
Of course, also calling plt.subplots_adjust is still possible at any time.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable
desired_height_px = 40 #pixel
def make_plot(ax=None):
if not ax:
fig, ax = plt.subplots()
else:
fig = ax.figure
div = make_axes_locatable(ax)
cax = div.append_axes("bottom", desired_height_px/fig.dpi, pad=0.25)
sc1 = ax.scatter([2,1,3], [2,3,1], c=[1,2,3])
sc2 = cax.scatter([3,2,1],[2,3,1], c=[3,1,2])
return fig, ax, cax, (sc1, sc2)
fig, (ax1, ax2) = plt.subplots(1,2)
make_plot(ax=ax1)
#user plot on ax2
ax2.plot([1,3])
fig.subplots_adjust(top=0.7)
plt.show()

How to expand matplolib window without stretching the plot?

I want to increase the grey area around the plot, but keeping the plot the same size. I've already tried changing the figure size, which ends up stretching the plot.
The axes inside the figure is positionned relative to the figure. Per default you have e.g. a fraction of 0.125 of figure width as space at the left. This means that resizing the figure, scales the axes as well.
You may calculate how much the spacings need to change such that if the figure is rescaled, the axes size remains constant. The new spacings then need to be set using fig.subplots_adjust.
import matplotlib.pyplot as plt
def set_figsize(figw,figh, fig=None):
if not fig: fig=plt.gcf()
w, h = fig.get_size_inches()
l = fig.subplotpars.left
r = fig.subplotpars.right
t = fig.subplotpars.top
b = fig.subplotpars.bottom
hor = 1.-w/float(figw)*(r-l)
ver = 1.-h/float(figh)*(t-b)
fig.subplots_adjust(left=hor/2., right=1.-hor/2., top=1.-ver/2., bottom=ver/2.)
fig, ax=plt.subplots()
ax.plot([1,3,2])
set_figsize(9,7)
plt.show()
You may then also use this function to update the subplot params when the figure window is resized.
import matplotlib.pyplot as plt
class Resizer():
def __init__(self,fig=None):
if not fig: fig=plt.gcf()
self.fig=fig
self.w, self.h = self.fig.get_size_inches()
self.l = self.fig.subplotpars.left
self.r = self.fig.subplotpars.right
self.t = self.fig.subplotpars.top
self.b = self.fig.subplotpars.bottom
def set_figsize(self, figw,figh):
hor = 1.-self.w/float(figw)*(self.r-self.l)
ver = 1.-self.h/float(figh)*(self.t-self.b)
self.fig.subplots_adjust(left=hor/2., right=1.-hor/2., top=1.-ver/2., bottom=ver/2.)
def resize(self, event):
figw = event.width/self.fig.dpi
figh = event.height/self.fig.dpi
self.set_figsize( figw,figh)
fig, ax=plt.subplots()
ax.plot([1,3,2])
r = Resizer()
cid = fig.canvas.mpl_connect("resize_event", r.resize)
plt.show()
In the window of a matplotlib figure, there's a button called 'Configure subplots' (see below picture, screenshot on Windows 10 with matplotlib version 1.5.2). Try to change the parameters 'left' and 'right'. You can also change these parameters with plt.subplots_adjust(left=..., bottom=..., right=..., top=..., wspace=..., hspace=...).

How to hide contour lines / data from a specific area on Basemap

I am working some meteorological data to plot contour lines on a basemap. The full working example code I have done earlier is here How to remove/omit smaller contour lines using matplotlib. All works fine and I don’t complain with the contour plot. However there is a special case that I have to hide all contour lines over a specific region (irregular lat & lon) on a Basemap.
The only possible solution I can think of is to draw a ploygon lines over a desired region and fill with the color of same as Basemap. After lot of search I found this link How to draw rectangles on a Basemap (code below)
from mpl_toolkits.basemap import Basemap
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
def draw_screen_poly( lats, lons, m):
x, y = m( lons, lats )
xy = zip(x,y)
poly = Polygon( xy, facecolor='red', alpha=0.4 )
plt.gca().add_patch(poly)
lats = [ -30, 30, 30, -30 ]
lons = [ -50, -50, 50, 50 ]
m = Basemap(projection='sinu',lon_0=0)
m.drawcoastlines()
m.drawmapboundary()
draw_screen_poly( lats, lons, m )
plt.show()
It seems to work partially. However, I want to draw a region which is irregular.
Any solution is appreciated.
Edit: 1
I have understood where the problem is. It seems that any colour (facecolor) filled within the polygon region does not make it hide anything below. Always it is transparent only, irrespective of alpha value used or not. To illustrate the problem, I have cropped the image which has all three regions ie. contour, basemap region and polygon region. Polygon region is filled with red colour but as you can see, the contour lines are always visible. The particular line I have used in the above code is :-
poly = Polygon(xy, facecolor='red', edgecolor='b')
Therefore the problem is not with the code above. It seem the problem with the polygon fill. But still no solution for this issue. The resulting image (cropped image) is below (See my 2nd edit below the attached image):-
Edit 2:
Taking clue from this http://matplotlib.1069221.n5.nabble.com/Clipping-a-plot-inside-a-polygon-td41950.html which has the similar requirement of mine, I am able to remove some the data. However, the removed data is only from outside of polygon region instead of within. Here is the code I have taken clue from:-
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import RegularPolygon
data = np.arange(100).reshape(10, 10)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.contourf(data)
poly = RegularPolygon([ 0.5, 0.5], 6, 0.4, fc='none',
ec='k', transform=ax.transAxes)
for artist in ax.get_children():
artist.set_clip_path(poly)
Now my question is that what command is used for removing the data within the polygon region?
Didn't noticed there was a claim on this so I might just give the solution already proposed here. You can tinker with the zorder to hide stuff behind your polygon:
import matplotlib
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
matplotlib.rcParams['xtick.direction'] = 'out'
matplotlib.rcParams['ytick.direction'] = 'out'
delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
# difference of Gaussians
Z = 10.0 * (Z2 - Z1)
# Create a simple contour plot with labels using default colors. The
# inline argument to clabel will control whether the labels are draw
# over the line segments of the contour, removing the lines beneath
# the label
fig = plt.figure()
ax = fig.add_subplot(111)
CS = plt.contour(X, Y, Z,zorder=3)
plt.clabel(CS, inline=1, fontsize=10)
plt.title('Simplest default with labels')
rect1 = matplotlib.patches.Rectangle((0,0), 2, 1, color='white',zorder=5)
ax.add_patch(rect1)
plt.show()
, the result is:

automatically changing view bounds on resize matlotlib

Is it possible to setup plot to show more data when expanded?
Matplotlib plots scale when resized. To show specific area one can use set_xlim and such on axes. I have an ecg-like plot showing realtime data, its y limits are predefined, but I want to see more data along x if I expand window or just have big monitor.
Im using it in a pyside app and I could just change xlim on resize but I want more clean and generic solution.
One way to do this is to implement a handler for resize_event. Here is a short example how this might be done. You can modify it for your needs:
import numpy as np
import matplotlib.pyplot as plt
def onresize(event):
width = event.width
scale_factor = 100.
data_range = width/scale_factor
start, end = plt.xlim()
new_end = start+data_range
plt.xlim((start, new_end))
if __name__ == "__main__":
fig = plt.figure()
ax = fig.add_subplot(111)
t = np.arange(100)
y = np.random.rand(100)
ax.plot(t,y)
plt.xlim((0, 10))
cid = fig.canvas.mpl_connect('resize_event', onresize)
plt.show()