What I'm trying to achieve: a plot with two axhline horizontal lines, with the area between them shaded.
The best so far:
ax.hline(y1, color=c)
ax.hline(y2, color=c)
ax.fill_between(ax.get_xlim(), y1, y2, color=c, alpha=0.5)
The problem is that this leaves a small amount of blank space to the left and right of the shaded area.
I understand that this is likely due to the plot creating a margin around the used/data area of the plot. So, how do I get the fill_between to actually cover the entire plot without matplotlib rescaling the x-axis after drawing? Is there an alternative to get_xlim that would give me appropriate limits of the plot, or an alternative to fill_between?
This is the current result:
Note that this is part of a larger grid layout with several plots, but they all leave a similar margin around these shaded areas.
Not strictly speaking an answer to the question of getting the outer limits, but it does solve the problem. Instead of using fill_between, I should have used:
ax.axhspan(y1, y2, facecolor=c, alpha=0.5)
Result:
ax.get_xlim() does return the limits of the axis, not that of the data:
Axes.get_xlim()
Returns the current x-axis limits as the tuple (left, right).
But Matplotlib simply rescales the x-axis after drawing the fill_between:
import matplotlib.pylab as pl
import numpy as np
pl.figure()
ax=pl.subplot(111)
pl.plot(np.random.random(10))
print(ax.get_xlim())
pl.fill_between(ax.get_xlim(), 0.5, 1)
print(ax.get_xlim())
This results in:
(-0.45000000000000001, 9.4499999999999993)
(-0.94499999999999995, 9.9449999999999985)
If you don't want to manually set the x-limits, you could use something like:
import matplotlib.pylab as pl
import numpy as np
pl.figure()
ax=pl.subplot(111)
pl.plot(np.random.random(10))
xlim = ax.get_xlim()
pl.fill_between(xlim, 0.5, 1)
ax.set_xlim(xlim)
Related
The following happens, when I plot a histogram using matplotlib's hist function with horizontal orientation: Sometimes the bins with very low values, which are present at the default orientation, are not shown for the horizontal orientation.
Executing the following code in a notebook cell (sadly I cannot upload/should generate two histograms, where you can see the difference at most right/left bins of the distribution. For default there are small bins at ~3 and -3, which are not present for the horizontal orientation.
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(20)
y = np.random.normal(size=1000000)
plt.hist(y, bins=20)
plt.show()
plt.hist(y, bins=20, orientation='horizontal')
plt.show()
I also linked the plots here:
default -
horizontal
Does anybody have an idea what is the issue here?
I had similar issues with matplotlib's hist function. Sadly, I reverted to using barh and numpy.histogram() by hand.
Your code could then look like:
y = np.random.normal(size=1000000)
yhist = np.histogram(y, bins=20)
plt.barh(y=yhist[1][:20], x=yhist[0])
plt.show()
I'm trying to create a visualization that varies color (specifically the H and V values of an HSV color scheme while keeping S constant), while representing the response of a given function to those colors.
Effectively, it's a heat map where the x and y axes are colors rather than numbers. Hunting through the matplotlib gallery I can find a lot of examples based on colorbars such as those found here, and here.
The colorbar implementation is close to what I'm looking for, with these important caveats:
I'm looking to align the colors as ticks on the main figure, rather than adding ticks to the colorbar itself. Principally this calls for making sure the plot and the colorbar are aligned, and I haven't found any way of actually guaranteeing this.
I'm trying to ensure that the color bar will display on the left of the figure (in place of the standard x-axis) rather than to the right.
The second point sounds trivial, but I haven't found any documented way of achieving it unfortunately.
Is there any way of creating a plot like this in matplotlib that would be considerably less effort than creating it from scratch in d3 or a similar lower-level visualization library?
I'm still not quite sure about it; but I'll give a try. Sorry if I misunderstood it.
Major thoughts are using GridSpec to solve your two requirements: aligning the "color axes" and put them beside the classic axes. The alignment should be correct because corresponding axes between ax_x/ax_y and the main ax are the same.
import matplotlib.pyplot as plt
from matplotlib.colors import hsv_to_rgb
from matplotlib.gridspec import GridSpec
import numpy as np
# Create a spectrum sample
# Convert HSV to RGB so that matplotlib can plot;
# hsv_to_rgb assumes values to be in range [0, 1]
N = 0.001
v_y, h_x = np.mgrid[0:1:N, 0:1:N]
c = hsv_to_rgb(np.stack([h_x, np.ones(h_x.shape), v_y], axis=2))
c_x = hsv_to_rgb(np.stack([h_x, np.ones(h_x.shape), np.zeros(v_y.shape)], axis=2))
c_y = hsv_to_rgb(np.stack([np.zeros(h_x.shape), np.ones(h_x.shape), v_y], axis=2))
fig = plt.figure()
# Ratio to adjust width for "x axis" and "y axis"
fig_ratio = np.divide(*fig.get_size_inches())
gs = GridSpec(2, 2, wspace=0.0, hspace=0.0,
width_ratios=[1, 20], height_ratios=[20/fig_ratio, 1])
# Lower-left corner is ignored
ax_y = plt.subplot(gs[0])
ax = plt.subplot(gs[1])
ax_x = plt.subplot(gs[3])
# Image are stretched to fit the ax since numbers are hided or not important in this figure.
img = ax.imshow(c, aspect='auto', origin='lower')
# Colorbar on img won't give correct results since it is plot with raw RGB values
img_x = ax_x.imshow(c_x, aspect='auto', origin='lower')
img_y = ax_y.imshow(c_y, aspect='auto', origin='lower')
# Remove ticks and ticklabels
for ax in [ax_y, ax, ax_x]:
ax.tick_params(left=False, bottom=False,
labelleft=False, labelbottom=False)
plt.show()
Response to the comment:
To clarify, you're making three plots, and using imshow plots as axes by assigning them to quadrants of the grid?
Yes, it's a 2x2 grid and I ignored the lower-left one. The documentation might not be great but what I did is similar to this part.
And presumably if I wanted to add space between the axes here and the main plot I would increase wspace and hspace?
Yes, it is briefly demonstrated in this part of documentation. Besides, I adjusted it with width_ratios and height_ratios so that 3 parts of the figure are not the same size, like this.
Also, just to confirm, there is a fully black axis on the bottom of this image, and it's not a misalignment of the left axis.
The bottom is the colored x axis. It is black because I thought it corresponds to v=0. If you change
c_x = hsv_to_rgb(np.stack([h_x, np.ones(h_x.shape), np.zeros(v_y.shape)], axis=2))
to
c_x = hsv_to_rgb(np.stack([h_x, np.ones(h_x.shape), np.ones(v_y.shape)], axis=2))
You would get this figure, proving it's not misaligned:
If it's easier, you can also ignore the whole hsv thing, use a gray box or something as the central image.
I'm sorry but I'm really slow on this. I'm still having no idea what you want to show in the figure. So I don't know how to help. If you remove or comment out the line
img = ax.imshow(c, aspect='auto', origin='lower')
You got this:
I am generating plots like this one:
When using less ticks, the plot fits nicely and the bars are wide enough to see them correctly. Nevertheless, when there are lots of ticks, instead of making the plot larger, it just compress the y axe, resulting in thin bars and overlapping tick text.
This is happening both for plt.show() and plt.save_fig().
Is there any solution so it plots the figure in a scale which guarantees that bars have the specified width, not more (if too few ticks) and not less (too many, overlapping)?
EDIT:
Yes, I'm using barh, and yes, I'm setting height to a fixed value (8):
height = 8
ax.barh(yvalues-width/2, xvalues, height=height, color='blue', align='center')
ax.barh(yvalues+width/2, xvalues, height=height, color='red', align='center')
I don't quite understand your code, it seems you do two plots with the same (only shifted) yvalues, but the image doesn't look so. And are you sure you want to shift by width/2 if you have align=center? Anyways, to changing the image size:
No, I am not sure there is no other way, but I don't see anything in the manual at a glance. To set image size by hand:
fig = plt.figure(figsize=(5, 80))
ax = fig.add_subplot(111)
...your_code
the size is in cm. You can compute it beforehand, try for example
import numpy as np
fig_height = (max(yvalues) - min(yvalues)) / np.diff(yvalue)
this would (approximately) set the minimum distance between ticks to a centimeter, which is too much, but try to adjust it.
I think of two solutions for your case:
If you are trying to plot a histogram, use hist function [1]. This will automatically bin your data. You can even plot multiple overlapping histograms as long as you set alpha value lower than 1. See this post
import matplotlib.pyplot as plt
import numpy as np
x = mu + sigma*np.random.randn(10000)
plt.hist(x, 50, normed=1, facecolor='green',
alpha=0.75, orientation='horizontal')
You can also identify interval of your axis ticks. This will place a tick every 10 items. But I doubt this will solve your problem.
import matplotlib.ticker as ticker
...
ax.yaxis.set_major_locator(ticker.MultipleLocator(10))
I am trying to create a color wheel in Python, preferably using Matplotlib. The following works OK:
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
xval = np.arange(0, 2*pi, 0.01)
yval = np.ones_like(xval)
colormap = plt.get_cmap('hsv')
norm = mpl.colors.Normalize(0.0, 2*np.pi)
ax = plt.subplot(1, 1, 1, polar=True)
ax.scatter(xval, yval, c=xval, s=300, cmap=colormap, norm=norm, linewidths=0)
ax.set_yticks([])
However, this attempt has two serious drawbacks.
First, when saving the resulting figure as a vector (figure_1.svg), the color wheel consists (as expected) of 621 different shapes, corresponding to the different (x,y) values being plotted. Although the result looks like a circle, it isn't really. I would greatly prefer to use an actual circle, defined by a few path points and Bezier curves between them, as in e.g. matplotlib.patches.Circle. This seems to me the 'proper' way of doing it, and the result would look nicer (no banding, better gradient, better anti-aliasing).
Second (relatedly), the final plotted markers (the last few before 2*pi) overlap the first few. It's very hard to see in the pixel rendering, but if you zoom in on the vector-based rendering you can clearly see the last disc overlap the first few.
I tried using different markers (. or |), but none of them go around the second issue.
Bottom line: can I draw a circle in Python/Matplotlib which is defined in the proper vector/Bezier curve way, and which has an edge color defined according to a colormap (or, failing that, an arbitrary color gradient)?
One way I have found is to produce a colormap and then project it onto a polar axis. Here is a working example - it includes a nasty hack, though (clearly commented). I'm sure there's a way to either adjust limits or (harder) write your own Transform to get around it, but I haven't quite managed that yet. I thought the bounds on the call to Normalize would do that, but apparently not.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
import matplotlib as mpl
fig = plt.figure()
display_axes = fig.add_axes([0.1,0.1,0.8,0.8], projection='polar')
display_axes._direction = 2*np.pi ## This is a nasty hack - using the hidden field to
## multiply the values such that 1 become 2*pi
## this field is supposed to take values 1 or -1 only!!
norm = mpl.colors.Normalize(0.0, 2*np.pi)
# Plot the colorbar onto the polar axis
# note - use orientation horizontal so that the gradient goes around
# the wheel rather than centre out
quant_steps = 2056
cb = mpl.colorbar.ColorbarBase(display_axes, cmap=cm.get_cmap('hsv',quant_steps),
norm=norm,
orientation='horizontal')
# aesthetics - get rid of border and axis labels
cb.outline.set_visible(False)
display_axes.set_axis_off()
plt.show() # Replace with plt.savefig if you want to save a file
This produces
If you want a ring rather than a wheel, use this before plt.show() or plt.savefig
display_axes.set_rlim([-1,1])
This gives
As per #EelkeSpaak in comments - if you save the graphic as an SVG as per the OP, here is a tip for working with the resulting graphic: The little elements of the resulting SVG image are touching and non-overlapping. This leads to faint grey lines in some renderers (Inkscape, Adobe Reader, probably not in print). A simple solution to this is to apply a small (e.g. 120%) scaling to each of the individual gradient elements, using e.g. Inkscape or Illustrator. Note you'll have to apply the transform to each element separately (the mentioned software provides functionality to do this automatically), rather than to the whole drawing, otherwise it has no effect.
I just needed to make a color wheel and decided to update rsnape's solution to be compatible with matplotlib 2.1. Rather than place a colorbar object on an axis, you can instead plot a polar colored mesh on a polar plot.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
import matplotlib as mpl
# If displaying in a Jupyter notebook:
# %matplotlib inline
# Generate a figure with a polar projection
fg = plt.figure(figsize=(8,8))
ax = fg.add_axes([0.1,0.1,0.8,0.8], projection='polar')
# Define colormap normalization for 0 to 2*pi
norm = mpl.colors.Normalize(0, 2*np.pi)
# Plot a color mesh on the polar plot
# with the color set by the angle
n = 200 #the number of secants for the mesh
t = np.linspace(0,2*np.pi,n) #theta values
r = np.linspace(.6,1,2) #radius values change 0.6 to 0 for full circle
rg, tg = np.meshgrid(r,t) #create a r,theta meshgrid
c = tg #define color values as theta value
im = ax.pcolormesh(t, r, c.T,norm=norm) #plot the colormesh on axis with colormap
ax.set_yticklabels([]) #turn of radial tick labels (yticks)
ax.tick_params(pad=15,labelsize=24) #cosmetic changes to tick labels
ax.spines['polar'].set_visible(False) #turn off the axis spine.
It gives this:
I would like to plot a circle on an auto-scaled pyplot-generated graphic. When I run
ax.get_aspect()
hoping for a value with which I could manipulate the axes of a ellipse, pyplot returns:
auto
which is less than useful. What methods would you suggest for plotting a circle on a pyplot plot with unequal axes?
This question is more than one year old, but I too just had this question. I needed to add circles to a matplotlib plot and I wanted to be able to specify the circle's location in the plot using data coordinates, and I didn't want the circle radius to change with panning/zooming (or worse the circle turning into an ellipse).
The best and most simple solution that I've found is simply plot a curve with a single point and include a circle marker:
ax.plot(center_x,center_y,'bo',fillstyle='none',markersize=5)
which gives a nice, fixed-size blue circle with no fill!
It really does depend what you want it for.
The problem with defining a circle in data coordinates when aspect ratio is auto, is that you will be able to resize the figure (or its window), and the data scales will stretch nicely. Unfortunately, this would also mean that your circle is no longer a circle, but an ellipse.
There are several ways of addressing this. Firstly, and most simply, you could fix your aspect ratio and then put a circle on the plot in data coordinates:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = plt.axes()
ax.set_aspect(1)
theta = np.linspace(-np.pi, np.pi, 200)
plt.plot(np.sin(theta), np.cos(theta))
plt.show()
With this, you will be able to zoom and pan around as per usual, but the shape will always be a circle.
If you just want to put a circle on a figure, independent of the data coordinates, such that panning and zooming of an axes did not effect the position and zoom on the circle, then you could do something like:
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = plt.axes()
patch = mpatches.Circle((325, 245), 180, alpha=0.5, transform=None)
fig.artists.append(patch)
plt.show()
This is fairly advanced mpl, but even so, I think it is fairly readable.
HTH,
Building on #user3208430, if you want the circle to always appear at the same place in the axes (regardless of data ranges), you can position it using axes coordinates via transform:
ax.plot(.94, .94, 'ro', fillstyle='full', markersize=5, transform=ax.transAxes)
Where x and y are between [0 and 1]. This example places the marker in the upper right-hand corner of the axes.