I want to have some grid lines on a plot, but actually full-length lines are too much/distracting, even dashed light grey lines. I went and manually did some editing of the SVG output to get the effect I was looking for. Can this be done with matplotlib? I had a look at the pyplot api for grid, and the only thing I can see that might be able to get near it are the xdata and ydata Line2D kwargs.
This cannot be done through the basic API, because the grid lines are created using only two points. The grid lines would need a 'data' point at every tick mark for there to be a marker drawn. This is shown in the following example:
import matplotlib.pyplot as plt
ax = plt.subplot(111)
ax.grid(clip_on=False, marker='o', markersize=10)
plt.savefig('crosses.png')
plt.show()
This results in:
Notice how the 'o' markers are only at the beginning and the end of the Axes edges, because the grid lines only involve two points.
You could write a method to emulate what you want, creating the cross marks using a series of Artists, but it's quicker to just leverage the basic plotting capabilities to draw the cross pattern.
This is what I do in the following example:
import matplotlib.pyplot as plt
import numpy as np
NPOINTS=100
def set_grid_cross(ax, in_back=True):
xticks = ax.get_xticks()
yticks = ax.get_yticks()
xgrid, ygrid = np.meshgrid(xticks, yticks)
kywds = dict()
if in_back:
kywds['zorder'] = 0
grid_lines = ax.plot(xgrid, ygrid, 'k+', **kywds)
xvals = np.arange(NPOINTS)
yvals = np.random.random(NPOINTS) * NPOINTS
ax1 = plt.subplot(121)
ax2 = plt.subplot(122)
ax1.plot(xvals, yvals, linewidth=4)
ax1.plot(xvals, xvals, linewidth=7)
set_grid_cross(ax1)
ax2.plot(xvals, yvals, linewidth=4)
ax2.plot(xvals, xvals, linewidth=7)
set_grid_cross(ax2, in_back=False)
plt.savefig('gridpoints.png')
plt.show()
This results in the following figure:
As you can see, I take the tick marks in x and y to define a series of points where I want grid marks ('+'). I use meshgrid to take two 1D arrays and make 2 2D arrays corresponding to the double loop over each grid point. I plot this with the mark style as '+', and I'm done... almost. This plots the crosses on top, and I added an extra keyword to reorder the list of lines associated with the plot. I adjust the zorder of the grid marks if they are to be drawn behind everything.*****
The example shows the left subplot where by default the grid is placed in back, and the right subplot disables this option. You can notice the difference if you follow the green line in each plot.
If you are bothered by having grid crosses on the boarder, you can remove the first and last tick marks for both x and y before you define the grid in set_grid_cross, like so:
xticks = ax.get_xticks()[1:-1] #< notice the slicing
yticks = ax.get_yticks()[1:-1] #< notice the slicing
xgrid, ygrid = np.meshgrid(xticks, yticks)
I do this in the following example, using a larger, different marker to make my point:
***** Thanks to the answer by #fraxel for pointing this out.
You can draw on line segments at every intersection of the tickpoints. Its pretty easy to do, just grab the tick locations get_ticklocs() for both axis, then loop through all combinations, drawing short line segments using axhline and axvline, thus creating a cross hair at every intersection. I've set zorder=0 so the cross-hairs are drawn first, so that they are behind the plot data. Its easy to control the color/alpha and cross-hair size. Couple of slight 'gotchas'... do the plot before you get the tick locations.. and also the xmin and xmax parameters seem to require normalisation.
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.plot((0,2,3,5,5,5,6,7,8,6,6,4,3,32,7,99), 'r-',linewidth=4)
x_ticks = ax.xaxis.get_ticklocs()
y_ticks = ax.yaxis.get_ticklocs()
for yy in y_ticks[1:-1]:
for xx in x_ticks[1:-1]:
plt.axhline(y=yy, xmin=xx / max(x_ticks) - 0.02,
xmax=xx / max(x_ticks) + 0.02, color='gray', alpha=0.5, zorder=0)
plt.axvline(x=xx, ymin=yy / max(y_ticks) - 0.02,
ymax=yy / max(y_ticks) + 0.02, color='gray', alpha=0.5, zorder=0)
plt.show()
Related
I want to plot a grid onto the background of 2D plot line, similar as it is for ECG presentations, i.e. at specific points in regular interval dots are shown, e.g. as in this image
In this example there are precisely 4 dots spaced between to major dots. Want I don't want is sth as this Plotting a grid with Matplotlib, i.e. just dotted grid lines
What I did so far (coming from ancient matlab knowledge) is this:
xg = np.linspace(iStart/fs, iEnd/fs, len(y))
yrange = ax.get_ylim()
yg = np.linspace(yrange[0], yrange[1], 4)
xx, yy = np.meshgrid(xg, yg)
gridpoints, = plt.plot(xx.reshape(1,-1),yy.reshape(1,-1),linewidth=0.3,color='0.75',marker=".",markersize=10)
But it gets me this:
What am I not getting right?
As said by jpnadas you can use plt.grid()
here an example on how you can put and customize grid
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator
img = plt.imread(imagename)
_, ax = plt.subplots(ncols=1,nrows=1)
ax.imshow(img)
plt.gca().xaxis.set_major_locator(MultipleLocator(16))
plt.gca().yaxis.set_major_locator(MultipleLocator(16))
plt.gca().xaxis.set_minor_locator(MultipleLocator(32))
plt.gca().yaxis.set_minor_locator(MultipleLocator(32))
# Don't allow the axis to be on top of your data
ax.set_axisbelow(True)
# Turn on the minor TICKS, which are required for the minor GRID
ax.minorticks_on()
# Customize the major grid
ax.grid(which='major', linestyle='-', linewidth='4', color='yellow')
# Customize the minor grid
ax.grid(which='minor', linestyle=':', linewidth='2', color='blue')
plt.show()
I found my mistake. It was not an error in my thinking, but that my len(y) in the linspace of x was referencing the wrong vector, hencing producing a too fine grid, which looked like a line.
I am trying to create a nice plot which joins a 4x4 grid of subplots (placed with gridspec, each subplot is 8x8 pixels ). I constantly struggle getting the spacing between the plots to match what I am trying to tell it to do. I imagine the problem is arising from plotting a color bar on the right side of the figure, and adjusting the location of the plots in the figure to accommodate. However, it appears that this issue crops up even without the color bar included, which has further confused me. It may also have to do with the margin spacing. The images shown below are produced by the associated code. As you can see, I am trying to get the space between the plots to go to zero, but it doesn't seem to be working. Can anyone advise?
fig = plt.figure('W Heat Map', (18., 15.))
gs = gridspec.GridSpec(4,4)
gs.update(wspace=0., hspace=0.)
for index in indices:
loc = (i,j) #determined by the code
ax = plt.subplot(gs[loc])
c = ax.pcolor(physHeatArr[index,:,:], vmin=0, vmax=1500)
# take off axes
ax.axis('off')
ax.set_aspect('equal')
fig.subplots_adjust(right=0.8,top=0.9,bottom=0.1)
cbar_ax = heatFig.add_axes([0.85, 0.15, 0.05, 0.7])
cbar = heatFig.colorbar(c, cax=cbar_ax)
cbar_ax.tick_params(labelsize=16)
fig.savefig("heatMap.jpg")
Similarly, in making a square figure without the color bar:
fig = plt.figure('W Heat Map', (15., 15.))
gs = gridspec.GridSpec(4,4)
gs.update(wspace=0., hspace=0.)
for index in indices:
loc = (i,j) #determined by the code
ax = plt.subplot(gs[loc])
c = ax.pcolor(physHeatArr[index,:,:], vmin=0, vmax=400, cmap=plt.get_cmap("Reds_r"))
# take off axes
ax.axis('off')
ax.set_aspect('equal')
fig.savefig("heatMap.jpg")
When the axes aspect ratio is set to not automatically adjust (e.g. using set_aspect("equal") or a numeric aspect, or in general using imshow), there might be some white space between the subplots, even if wspace and hspaceare set to 0. In order to eliminate white space between figures, you may have a look at the following questions
How to remove gaps between *images* in matplotlib?
How to combine gridspec with plt.subplots() to eliminate space between rows of subplots
How to remove the space between subplots in matplotlib.pyplot?
You may first consider this answer to the first question, where the solution is to build a single array out of the individual arrays and then plot this single array using pcolor, pcolormesh or imshow. This makes it especially comfortable to add a colorbar later on.
Otherwise consider setting the figuresize and subplot parameters such that no whitespae will remain. Formulas for that calculation are found in this answer to the second question.
An adapted version with colorbar would look like this:
import matplotlib.pyplot as plt
import matplotlib.colors
import matplotlib.cm
import numpy as np
image = np.random.rand(16,8,8)
aspect = 1.
n = 4 # number of rows
m = 4 # numberof columns
bottom = 0.1; left=0.05
top=1.-bottom; right = 1.-0.18
fisasp = (1-bottom-(1-top))/float( 1-left-(1-right) )
#widthspace, relative to subplot size
wspace=0 # set to zero for no spacing
hspace=wspace/float(aspect)
#fix the figure height
figheight= 4 # inch
figwidth = (m + (m-1)*wspace)/float((n+(n-1)*hspace)*aspect)*figheight*fisasp
fig, axes = plt.subplots(nrows=n, ncols=m, figsize=(figwidth, figheight))
plt.subplots_adjust(top=top, bottom=bottom, left=left, right=right,
wspace=wspace, hspace=hspace)
#use a normalization to make sure the colormapping is the same for all subplots
norm=matplotlib.colors.Normalize(vmin=0, vmax=1 )
for i, ax in enumerate(axes.flatten()):
ax.imshow(image[i, :,:], cmap = "RdBu", norm=norm)
ax.axis('off')
# use a scalarmappable derived from the norm instance to create colorbar
sm = matplotlib.cm.ScalarMappable(cmap="RdBu", norm=norm)
sm.set_array([])
cax = fig.add_axes([right+0.035, bottom, 0.035, top-bottom])
fig.colorbar(sm, cax=cax)
plt.show()
Python beginner so apologies if incorrect terminology at any point.
I am using the legend(loc='best', ...) method and it works 99% of the time. However, when stacking more than 9 plots (i.e. i>9 in example below) on a single figure, with individual labels, it defaults to center and covers the data.
Is there a way to run a test in the script that will give a true/false value if the legend is covering any data points?
Very simplified code:
fig = plt.figure()
for i in data:
plt.plot(i[x, y], label=LABEL)
fig.legend(loc='best')
fig.savefig()
Example of legend covering data
One way is to add some extra space at the bottom/top/left or right side of the axis (in your case I would prefer top or bottom), by changing the limits slightly. Doing so makes the legend fit below the data. Add extra space by setting a different y-limit with ax.set_ylim(-3e-4, 1.5e-4) (the upper limit is approximately what it is in your figure and -3 is a estimate of what you need).
What you also need to do is to add split the legend into more columns, with the keyword ncol=N when creating the legend.
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
x = np.linspace(0, 1, 100)
y = 3.5 * x - 2
for i in range(9):
ax.plot(x, y + i / 10., label='iiiiiiiiiiii={}'.format(i))
ax.set_ylim(-3, 1.5)
ax.legend(loc='lower center', ncol=3) # ncol=3 looked nice for me, maybe you need to change this
plt.show()
EDIT
Another solution is to put the legend in a separate axis like I do in the code below. The data-plot does not need to care about making space for the legend or anything and you should have enough space in the axis below to put all your line-labels. If you need more space, you can easily change the ratio of the upper axis to the lower axis.
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(211)
ax_leg = fig.add_subplot(212)
x = np.linspace(0, 1, 100)
y = 3.5 * x - 2
lines = []
for i in range(9): #for plotting the actual data
li, = ax.plot(x, y + i / 10., label='iiiiiiiiiiii={}'.format(i))
lines.append(li)
for line in lines: # just to make the legend plot
ax_leg.plot([], [], line.get_color(), label=line.get_label())
ax_leg.legend(loc='center', ncol=3, ) # ncol=3 looked nice for me, maybe you need to change this
ax_leg.axis('off')
fig.show()
I'm creating a plot using python 3.5.1 and matplotlib 1.5.1 that has two subplots (side by side) with a shared Y axis. A sample output image is shown below:
Notice the extra white space at the top and bottom of each set of axes. Try as I might I can't seem to get rid of it. The overall goal of the figure is to have a waterfall type plot on the left with a shared Y axes with the plot on the right.
Here's some sample code to reproduce the image above.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
%matplotlib inline
# create some X values
periods = np.linspace(1/1440, 1, 1000)
# create some Y values (will be datetimes, not necessarily evenly spaced
# like they are in this example)
day_ints = np.linspace(1, 100, 100)
days = pd.to_timedelta(day_ints, 'D') + pd.to_datetime('2016-01-01')
# create some fake data for the number of points
points = np.random.random(len(day_ints))
# create some fake data for the color mesh
Sxx = np.random.random((len(days), len(periods)))
# Create the plots
fig = plt.figure(figsize=(8, 6))
# create first plot
ax1 = plt.subplot2grid((1,5), (0,0), colspan=4)
im = ax1.pcolormesh(periods, days, Sxx, cmap='viridis', vmin=0, vmax=1)
ax1.invert_yaxis()
ax1.autoscale(enable=True, axis='Y', tight=True)
# create second plot and use the same y axis as the first one
ax2 = plt.subplot2grid((1,5), (0,4), sharey=ax1)
ax2.scatter(points, days)
ax2.autoscale(enable=True, axis='Y', tight=True)
# Hide the Y axis scale on the second plot
plt.setp(ax2.get_yticklabels(), visible=False)
#ax1.set_adjustable('box-forced')
#ax2.set_adjustable('box-forced')
fig.colorbar(im, ax=ax1)
As you can see in the commented out code I've tried a number of approaches, as suggested by posts like https://github.com/matplotlib/matplotlib/issues/1789/ and Matplotlib: set axis tight only to x or y axis.
As soon as I remove the sharey=ax1 part of the second subplot2grid call the problem goes away, but then I also don't have a common Y axis.
Autoscale tends to add a buffer to the data so that all of the data points are easily visible and not part-way cut off by the axes.
Change:
ax1.autoscale(enable=True, axis='Y', tight=True)
to:
ax1.set_ylim(days.min(),days.max())
and
ax2.autoscale(enable=True, axis='Y', tight=True)
to:
ax2.set_ylim(days.min(),days.max())
To get:
I'm trying to have an imshow plot inset into another in matplotlib.
I have a figure that has an plot on top and an imshow on the bottom:
fig = plt.figure()
ax1 = fig.add_subplot(2,1,1)
plt.plot()
ax2 = fig.add_subplot(2,1,2)
plt.imshow()
and then I have another figure, which also is comprised of a plot on top and an imshow below. I want this second figure to be inset into the top plot on the first figure.
I tried follow this example. The code ran without an error but I had an empty axis in the position I wanted it.
My problem is just that I'm not sure where to put the plt.setp() command If that's what I am supposed to use.
First, I don't think you can put a figure into a figure in matplotlib. You will have to arrange your Axes objects (subplots) to achieve the look you want.
The example you provided uses absolute positioning to do that. setp there is not related to positioning, though — it just removes axis ticks from insets. An example of code that does what you want:
import numpy
import matplotlib.pyplot as plt
x = numpy.linspace(0, 1)
xx, yy = numpy.meshgrid(x, x)
im = numpy.sin(xx) + numpy.cos(yy)**2
fig = plt.figure()
ax1 = fig.add_subplot(2,1,1)
ax1.plot(x, x**2)
ax2 = fig.add_subplot(2,1,2)
ax2.imshow(im)
inset1 = fig.add_axes([.15, .72, .15, .15])
inset1.plot(x, x**2)
plt.setp(inset1, xticks=[], yticks=[])
inset2 = fig.add_axes([0.15, 0.55, .15, .15])
inset2.imshow(im)
plt.setp(inset2, xticks=[], yticks=[])
fig.savefig('tt.png')
Here the insets use explicit positioning with coordinates given in "figure units" (the whole figure has size 1 by 1).
Now, of course, there's plenty of room for improvement. You probably want the widths of your plots to be equal, so you'll have to:
specify the positioning of all subplots explicitly; or
play with aspect ratios; or
use two GridSpec objects (this way you'll have the least amount of magic numbers and manual adjustment)