matplotlib: Don't compress plot in the horizontal direction - python

I am very new to matplotlib and I am having some difficulty with this figure:
I have a text file with x y point groups that I should plot. However, the x points overlap in each group, so I add an offset to each x axis point for each group.
Usually, the single groups look like this:
Note that the x axis in the first image ends where the x-axis in the second image begins.
My problem is that the resulting image is squelched/compressed and not really "readable".
I tried increasing the value that is added to the x-axis for each group/image, but it just compresses each group even more.
I tried suggestions to use rcParams or set the dpi value of the resulting image, but nothing does the job:
from pylab import rcParams
rcParams['figure.figsize'] = 50, 100
plt.savefig('result.png', dpi=200,pad_inches=5)
What am I doing wrong or looking for?
PS: The data and code is here. To see what my problem is, call python2.7 plotit.py text.txt

If I understand your question, you don't like your image being too compressed in the horizontal direction. It happens because by default matplotlib chooses the aspect ratio necessary to fill the given figure size. You were on the right track with changing changing figsize, but if you want to change it in rcParams, you have to put this call somewhere before you start plotting. The other approach is to use the stateless API, that is fig = plt.figure(figsize=(8,2)); s = fig.add_subplot(111); s.plot(...). That's what you get:
from pylab import rcParams
rcParams['figure.figsize'] = 8, 2
Note that I shrunk the circle sizes to make the lines more distinguishable:
plt.scatter(x,y,s=1)
if px!='':
plt.plot([px,x],[py,y],'-o',markersize=1)
For a more accurate control you can actually set the aspect ratio directly:
plt.axes().set_aspect(1)
or use some of the predefined modes, e.g.
plt.axis('equal')
plt.tight_layout()
Edit: for the reference, full code for the final picture:
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import sys
from pylab import rcParams
rcParams['figure.figsize'] = 8, 2
def parsexydata(fdata):
keys=[]
xy=[]
with open(fdata,'r') as f:
pre=''
for idx, i in enumerate(f.read().replace('\n','').split(',')[2:]):
if idx%2==0:
pre=i
continue
tmp = pre.split('.')
if len(tmp)!=3: continue
[before,key,after] = pre.split('.')
pre = before+'.'+after
if key not in keys: keys.append(key)
xy.append([pre,i,key])
return [xy,keys]
[xydata, keys] = parsexydata(sys.argv[1])
for idx, k in enumerate(keys):
px=py=''
for [x,y,key] in xydata:
if key!=k: continue
x=float(x)+float(k)
if key=='01': print(x)
plt.scatter(x,y,s=1)
if px!='':
plt.plot([px,x],[py,y],'-o',markersize=1)
px,py=x,y
plt.axis('equal')
plt.tight_layout()
plt.savefig('result.png', dpi=200)

Related

Displaying minor grid lines for wide x axis ranges (log)

I noticed a 'strange' behaviour when running the following code:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import (MultipleLocator, AutoMinorLocator)
freqs = np.logspace(2,4)
freqs_ext = np.logspace(2, 10)
fig, ax = plt.subplots(1,2)
ax [0].plot(freqs , freqs**2)
#ax[0].xaxis.set_minor_locator(AutoMinorLocator(5))
ax[0].grid(which='both')
#ax[0].minorticks_on()
ax[0].set_xscale( 'log')
ax[1].plot(freqs_ext,freqs_ext**2)
#ax[l].xaxis.set_minor_locator(AutoMinorLocator(5))
ax[1].grid(which='both')
#ax[1].minorticks on()
ax[1].set_xscale('log')
The output is the following:
I have tried more variants than I care to report, (some are commented out in the code above), but I cannot get matplotlib to draw minor gridlines for the plot on the right side, as it does for the one on the left.
I think I have understood that the "problem" lies in where the ticks are located for the second plot, which has a much larger span. They are every two decades and I believe this might be the source of the minor grid lines not displaying.
I have played with xaxis.set_xticks and obtained ticks every decade, but still cannot get this to correctly produce the gridlines.
It is probably something stupid but I can't see it.
NOTE : I know that matplotlib doesn't turn the minor ticks on by default, and in this case this action is "triggered" by changing the scale to log (that's why axis.grid(which='both') actually only acts on the x axis)
OK, I have found this answer:
Matplotlib: strange double-decade axis ticks in log plot
which actually shows how the issue is a design choice for matplotlib starting with v2. Answer was given in 2017 so, not the newest issue :)
The following code correctly plots the minor grids as wanted:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import LogLocator
freqs = np.logspace(2,4)
freqs_ext = np.logspace(2, 10)
fig, ax = plt.subplots(1,2)
ax[0].plot(freqs , freqs**2)
ax[0].grid(which='both')
ax[0].set_xscale( 'log')
ax[1].plot(freqs_ext,freqs_ext**2)
ax[1].set_xscale('log')
ax[1].xaxis.set_major_locator(LogLocator(numticks=15))
ax[1].xaxis.set_minor_locator(LogLocator(numticks=15,subs=np.arange(2,10)))
ax[1].grid(which='both')

plt.savefig() partially crops subscript character from colorbar label, despite using bbox_inches="tight"

I have a figure which contains a labelled colourbar below the x axis of the main plot. When I attempt to save this using plt.savefig(), the very bottom of the subscript character in the label is cropped from the saved image, like this, despite using bbox_inches="tight". However, if I simply save the figure manually in the pop-up window, the subscript character is not cropped, like this.
Although the latter image could be manually cropped, or cropped using additional lines in the code, I would be grateful for any advice on how to resolve this issue without the need for this additional work.
I have tried to add a line break to the colourbar label like so:
label="$U/U_{"+(u"\u221e")+"}$\n"
But this simply adds white space below the label; the bottom of the subscript character is still cropped.
I have also tried to add the line:
cb.set_label(label,labelpad=5)
But this simply offsets the label from the bottom of the colourbar; no additional padding is provided below the label to fully display the subscript character.
The code is below:
import numpy
import random
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.colors as mcolors
import matplotlib.colorbar as cbar
from matplotlib import cm
##########################################################
# Centre colourmap so 0=white
class MidpointNormalize(mpl.colors.Normalize):
def __init__(self,vmin=None,vmax=None,midpoint=None,clip=False):
self.midpoint=midpoint
mpl.colors.Normalize.__init__(self,vmin,vmax,clip)
def __call__(self,value,clip=None):
x,y=[self.vmin,self.midpoint,self.vmax],[0,0.5,1]
return numpy.ma.masked_array(numpy.interp(value,x,y),numpy.isnan(value))
##########################################################
# Set min and max values
xymin=0
xymax=10
valmin=-5
valmax=5
val=numpy.zeros((xymax,xymax),dtype=float)
# Configure plot
fig,ax=plt.subplots()
ax.set_xlim([xymin,xymax])
ax.set_ylim([xymin,xymax])
# Configure colour bar
colours=plt.cm.RdBu(numpy.linspace(0,1,256))
colourmap=mcolors.LinearSegmentedColormap.from_list('colourmap',colours)
normalisecolors=mpl.colors.Normalize(vmin=valmin,vmax=valmax)
scalecolors=cm.ScalarMappable(norm=normalisecolors,cmap=colourmap)
label="$U/U_{"+(u"\u221e")+"}$"
for ix in range(xymin,xymax):
for iy in range(xymin,xymax):
xlow=ix*+1 # Calculate vertices of patch
xhigh=(ix*1)+1
ylow=iy*1
yhigh=(iy*1)+1
val[ix][iy]=random.randint(valmin,valmax) # Patch value
rgbacolor=scalecolors.to_rgba(val[ix][iy]) # Calculate RGBA colour for value
ax.add_patch(patches.Polygon([(xlow,ylow),(xlow,yhigh),(xhigh,yhigh),(xhigh,ylow)],fill=True,facecolor=rgbacolor)) # Add value as polygon patch
cax,_=cbar.make_axes(ax,orientation="horizontal")
cb=cbar.ColorbarBase(cax,cmap=colourmap,norm=MidpointNormalize(midpoint=0,vmin=valmin,vmax=valmax),orientation="horizontal",label=label)
plt.savefig("C:/Users/Christopher/Desktop/test.png",dpi=1200,bbox_inches="tight")
plt.clf
plt.close()
I'm afraid I don't really have a good answer for you. This appears to be related to this bug https://github.com/matplotlib/matplotlib/issues/15313
The good news is that it is being worked on, the bad news is that there is no fix as of yet.
Two points to consider anyway (based on reading the thread on github):
the higher the dpi, the worst it is. So you may want to save at a lower dpi (300 works fine for me)
the problem is not present on the pdf backend, so you could save your plot in pdf (and eventually convert to png if needed)
BTW (this is unrelated to the bug in question): I'm confused by the complexity of your code. It seems to me the following code produces the same output:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import TwoSlopeNorm
N=10
valmin=-5
valmax=5
valmid=0
val=np.random.randint(low=valmin, high=valmax, size=(N,N))
cmap = 'RdBu'
norm = TwoSlopeNorm(vcenter=valmid, vmin=valmin, vmax=valmax)
label="$U/U_{"+(u"\u221e")+"}$"
# Configure plot
fig, ax=plt.subplots()
im = ax.imshow(val, cmap=cmap, norm=norm, aspect='auto', origin='lower')
cbar = fig.colorbar(im, orientation='horizontal', label=label)
fig.savefig('./test-1200.png',dpi=1200,bbox_inches="tight") # subscript is cut
fig.savefig('./test-300.png',dpi=300,bbox_inches="tight") # subscript is not cut
fig.savefig('./test-pdf.pdf',dpi=1200,bbox_inches="tight") # subscript is not cut
1200 dpi:
300 dpi:
pdf:

How to update 3D arrow animation in matplotlib

I am trying to reproduce the left plot of this animation in python using matplotlib.
I am able to generate the vector arrows using the 3D quiver function, but as I read here, it does not seem possible to set the lengths of the arrows. So, my plot does not look quite right:
So, the question is: how do I generate a number of 3D arrows with different lengths? Importantly, can I generate them in such a way so that I can easily modify for each frame of the animation?
Here's my code so far, with the not-so-promising 3D quiver approach:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d
ax1 = plt.subplot(111,projection='3d')
t = np.linspace(0,10,40)
y = np.sin(t)
z = np.sin(t)
line, = ax1.plot(t,y,z,color='r',lw=2)
ax1.quiver(t,y,z, t*0,y,z)
plt.show()
As Azad suggests, an inelegant, but effective, solution is to simply edit the mpl_toolkits/mplot3d/axes3d.py to remove the normalization. Since I didn't want to mess with my actual matplotlib installation, I simply copied the axes3d.py file to the same directory as my other script and modified the line
norm = math.sqrt(u ** 2 + v ** 2 + w ** 2)
to
norm = 1
(Be sure to change the correct line. There is another use of "norm" a few lines higher.) Also, to get axes3d.py to function correctly when it's outside of the mpl directory, I changed
from . import art3d
from . import proj3d
from . import axis3d
to
from mpl_toolkits.mplot3d import art3d
from mpl_toolkits.mplot3d import proj3d
from mpl_toolkits.mplot3d import axis3d
And here is the nice animation that I was able to generate (not sure what's going wrong with the colors, it looks fine before I uploaded to SO).
And the code to generate the animation:
import numpy as np
import matplotlib.pyplot as plt
import axes3d_hacked
ax1 = plt.subplot(111,projection='3d')
plt.ion()
plt.show()
t = np.linspace(0,10,40)
for index,delay in enumerate(np.linspace(0,1,20)):
y = np.sin(t+delay)
z = np.sin(t+delay)
if delay > 0:
line.remove()
ax1.collections.remove(linecol)
line, = ax1.plot(t,y,z,color='r',lw=2)
linecol = ax1.quiver(t,y,z, t*0,y,z)
plt.savefig('images/Frame%03i.gif'%index)
plt.draw()
plt.ioff()
plt.show()
Now, if I could only get those arrows to look prettier, with nice filled heads. But that's a separate question...
EDIT: In the future, matplotlib will not automatically normalize the arrow lengths in the 3D quiver per this pull request.
Another solution is to call ax.quiever on each arrow, individually - with each call having an own length attribute. This is not very efficient but it will get you going.
And there's no need to change MPL-code

Using an image for tick labels in matplotlib [duplicate]

This question already has answers here:
How can I make the xtick labels of a plot be simple drawings using matplotlib?
(2 answers)
Closed 5 years ago.
I have a series of small, fixed width images and I want to replace the tick labels with them. For example, consider the following minimal working example:
import numpy as np
import pylab as plt
A = np.random.random(size=(5,5))
fig, ax = plt.subplots(1, 1)
ax.matshow(A)
plt.show()
I would like to replace the "0" with a custom image. I can turn off the labels, load an image into an array and display it just fine. However, I'm unsure of
Where the locations of the tick labels are, since they lie outside the plot.
Use imshow to display that image when it it will be "clipped" if put into an axis.
My thought were to use set_clip_on somehow or a custom artist, but I haven't made much progress.
Interesting question, and potentially has many possible solutions. Here is my approach, basically first calculate where the label '0' is, then draw a new axis there using absolute coordinates, and finally put the image there:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import pylab as pl
A = np.random.random(size=(5,5))
fig, ax = plt.subplots(1, 1)
xl, yl, xh, yh=np.array(ax.get_position()).ravel()
w=xh-xl
h=yh-yl
xp=xl+w*0.1 #if replace '0' label, can also be calculated systematically using xlim()
size=0.05
img=mpimg.imread('microblog.png')
ax.matshow(A)
ax1=fig.add_axes([xp-size*0.5, yh, size, size])
ax1.axison = False
imgplot = ax1.imshow(img,transform=ax.transAxes)
plt.savefig('temp.png')

Subplots: tight_layout changes figure size

Changing the vertical distance between two subplot using tight_layout(h_pad=-1) changes the total figuresize. How can I define the figuresize using tight_layout?
Here is the code:
#define figure
pl.figure(figsize=(10, 6.25))
ax1=subplot(211)
img=pl.imshow(np.random.random((10,50)), interpolation='none')
ax1.set_xticklabels(()) #hides the tickslabels of the first plot
subplot(212)
x=linspace(0,50)
pl.plot(x,x,'k-')
xlim( ax1.get_xlim() ) #same x-axis for both plots
And here is the results:
If I write
pl.tight_layout(h_pad=-2)
in the last line, then I get this:
As you can see, the figure is bigger...
You can use a GridSpec object to control precisely width and height ratios, as answered on this thread and documented here.
Experimenting with your code, I could produce something like what you want, by using a height_ratio that assigns twice the space to the upper subplot, and increasing the h_pad parameter to the tight_layout call. This does not sound completely right, but maybe you can adjust this further ...
import numpy as np
from matplotlib.pyplot import *
import matplotlib.pyplot as pl
import matplotlib.gridspec as gridspec
#define figure
fig = pl.figure(figsize=(10, 6.25))
gs = gridspec.GridSpec(2, 1, height_ratios=[2,1])
ax1=subplot(gs[0])
img=pl.imshow(np.random.random((10,50)), interpolation='none')
ax1.set_xticklabels(()) #hides the tickslabels of the first plot
ax2=subplot(gs[1])
x=np.linspace(0,50)
ax2.plot(x,x,'k-')
xlim( ax1.get_xlim() ) #same x-axis for both plots
fig.tight_layout(h_pad=-5)
show()
There were other issues, like correcting the imports, adding numpy, and plotting to ax2 instead of directly with pl. The output I see is this:
This case is peculiar because of the fact that the default aspect ratios of images and plots are not the same. So it is worth noting for people looking to remove the spaces in a grid of subplots consisting of images only or of plots only that you may find an appropriate solution among the answers to this question (and those linked to it): How to remove the space between subplots in matplotlib.pyplot?.
The aspect ratios of the subplots in this particular example are as follows:
# Default aspect ratio of images:
ax1.get_aspect()
# 1.0
# Which is as it is expected based on the default settings in rcParams file:
matplotlib.rcParams['image.aspect']
# 'equal'
# Default aspect ratio of plots:
ax2.get_aspect()
# 'auto'
The size of ax1 and the space beneath it are adjusted automatically based on the number of pixels along the x-axis (i.e. width) so as to preserve the 'equal' aspect ratio while fitting both subplots within the figure. As you mentioned, using fig.tight_layout(h_pad=xxx) or the similar fig.set_constrained_layout_pads(hspace=xxx) is not a good option as this makes the figure larger.
To remove the gap while preserving the original figure size, you can use fig.subplots_adjust(hspace=xxx) or the equivalent plt.subplots(gridspec_kw=dict(hspace=xxx)), as shown in the following example:
import numpy as np # v 1.19.2
import matplotlib.pyplot as plt # v 3.3.2
np.random.seed(1)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6.25),
gridspec_kw=dict(hspace=-0.206))
# For those not using plt.subplots, you can use this instead:
# fig.subplots_adjust(hspace=-0.206)
size = 50
ax1.imshow(np.random.random((10, size)))
ax1.xaxis.set_visible(False)
# Create plot of a line that is aligned with the image above
x = np.arange(0, size)
ax2.plot(x, x, 'k-')
ax2.set_xlim(ax1.get_xlim())
plt.show()
I am not aware of any way to define the appropriate hspace automatically so that the gap can be removed for any image width. As stated in the docstring for fig.subplots_adjust(), it corresponds to the height of the padding between subplots, as a fraction of the average axes height. So I attempted to compute hspace by dividing the gap between the subplots by the average height of both subplots like this:
# Extract axes positions in figure coordinates
ax1_x0, ax1_y0, ax1_x1, ax1_y1 = np.ravel(ax1.get_position())
ax2_x0, ax2_y0, ax2_x1, ax2_y1 = np.ravel(ax2.get_position())
# Compute negative hspace to close the vertical gap between subplots
ax1_h = ax1_y1-ax1_y0
ax2_h = ax2_y1-ax2_y0
avg_h = (ax1_h+ax2_h)/2
gap = ax1_y0-ax2_y1
hspace=-(gap/avg_h) # this divided by 2 also does not work
fig.subplots_adjust(hspace=hspace)
Unfortunately, this does not work. Maybe someone else has a solution for this.
It is also worth mentioning that I tried removing the gap between subplots by editing the y positions like in this example:
# Extract axes positions in figure coordinates
ax1_x0, ax1_y0, ax1_x1, ax1_y1 = np.ravel(ax1.get_position())
ax2_x0, ax2_y0, ax2_x1, ax2_y1 = np.ravel(ax2.get_position())
# Set new y positions: shift ax1 down over gap
gap = ax1_y0-ax2_y1
ax1.set_position([ax1_x0, ax1_y0-gap, ax1_x1, ax1_y1-gap])
ax2.set_position([ax2_x0, ax2_y0, ax2_x1, ax2_y1])
Unfortunately, this (and variations of this) produces seemingly unpredictable results, including a figure resizing similar to when using fig.tight_layout(). Maybe someone else has an explanation for what is happening here behind the scenes.

Categories