How can I create a figure with optimal resolution for printing? - python

I'm rather fond of The Logistic Map' Period Doubling Bifurcation and would like to print it on a canvas.
I can create the plot in python, but need some help preparing figure properties so that it has suitable resolution to be printed. My code right ow produces some jagged lines.
Here is my code:
import numpy as np
import matplotlib.pyplot as plt
# overall image properties
width, height, dpi = 2560, 1440, 96
picture_background = 'white'
aspect_ratio = width / height
plt.close('all')
R = np.linspace(3.5,4,5001)
fig = plt.figure(figsize=(width / dpi, height / dpi), frameon=False)
ylim = -0.1,1.1
ax = plt.Axes(fig, [0, 0, 1, 1], xlim = (3.4,4))
ax.set_axis_off()
fig.add_axes(ax)
for r in R:
x = np.zeros(5001)
x[0] = 0.1
for i in range(1,len(x)):
x[i] = r*x[i-1]*(1-x[i-1])
ax.plot(r*np.ones(2500),x[-2500:],marker = '.', markersize= 0.01,color = 'grey', linestyle = 'none')
plt.show()
plt.savefig('figure.eps', dpi=dpi, bbox_inches=0, pad_inches=0, facecolor=picture_background)
Here is what the code produces:
As you can see, some of the lines to the far left of the plot are rather jagged.
How can I create this figure so that the resolution is suitable to be printed on a variety of frame dimensions?

I think the source of the jaggies is underlying pixel size + that you are drawing this using very small 'point' markers. The pixels that the line are going through are getting fully saturated so you get the 'jaggy'.
A somewhat better way to plot this data is to do the binning ahead of time and then have mpl plot a heat map:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
plt.ion()
width, height, dpi = 2560 / 2, 1440 / 2, 96 # cut to so SO will let me upload result
aspect_ratio = width / height
fig, ax = plt.subplots(figsize=(width / dpi, height / dpi), frameon=False,
tight_layout=True)
ylim = -0.1, 1.1
ax.axis('off')
# make heatmap at double resolution
accumulator = np.zeros((height, width), dtype=np.uint64)
burn_in_count = 25000
N = 25000
R = np.linspace(3.5, 4, width)
x = 0.1 * np.ones_like(R)
row_indx = np.arange(len(R), dtype='uint')
# do all of the r values in parallel
for j in range(burn_in_count):
x = R * x * (1 - x)
for j in range(N):
x = R * x * (1 - x)
col_indx = (height * x).astype('int')
accumulator[col_indx, row_indx] += 1
im = ax.imshow(accumulator, cmap='gray_r',
norm=mcolors.LogNorm(), interpolation='none')
Note that this is log-scaled, if you just want to see what pixels are hit
use
im = ax.imshow(accumulator>0, cmap='gray_r', interpolation='nearest')
but these still have issues of the jaggies and (possibly worse) sometimes the narrow lines get aliased out.
This is the sort of problem that datashader or rasterized scatter is intended to solve by re-binning the data at draw time in an intelligent way (see this PR for a prototype datashader/mpl integartion). Both of those are still prototype/proof-of-concept, but usable.
http://matplotlib.org/examples/event_handling/viewlims.html which re-compute the Mandelbrot set on zoom might also be of interest to you.

Related

How can I add images to bars in axes (matplotlib)

I want to add flag images such as below to my bar chart:
I have tried AnnotationBbox but that shows with a square outline. Can anyone tell how to achieve this exactly as above image?
Edit:
Below is my code
ax.barh(y = y, width = values, color = r, height = 0.8)
height = 0.8
for i, (value, url) in enumerate(zip(values, image_urls)):
response = requests.get(url)
img = Image.open(BytesIO(response.content))
width, height = img.size
left = 10
top = 10
right = width-10
bottom = height-10
im1 = img.crop((left, top, right, bottom))
print(im1.size)
im1
ax.imshow(im1, extent = [value - 6, value, i - height / 2, i + height / 2], aspect = 'auto', zorder = 2)
Edit 2:
height = 0.8
for j, (value, url) in enumerate(zip(ww, image_urls)):
response = requests.get(url)
img = Image.open(BytesIO(response.content))
ax.imshow(img, extent = [value - 6, value - 2, j - height / 2, j + height / 2], aspect = 'auto', zorder = 2)
ax.set_xlim(0, max(ww)*1.05)
ax.set_ylim(-0.5, len(yy) - 0.5)
plt.tight_layout()
You need the images in a .png format with a transparent background. (Software such as Gimp or ImageMagick could help in case the images don't already have the desired background.)
With such an image, plt.imshow() can place it in the plot. The location is given via extent=[x0, x1, y0, y1]. To prevent imshow to force an equal aspect ratio, add aspect='auto'. zorder=2 helps to get the image on top of the bars. Afterwards, the plt.xlim and plt.ylim need to be set explicitly (also because imshow messes with them.)
The example code below used 'ada.png' as that comes standard with matplotlib, so the code can be tested standalone. Now it is loading flags from countryflags.io, following this post.
Note that the image gets placed into a box in data coordinates (6 wide and 0.9 high in this case). This box will get stretched, for example when the plot gets resized. You might want to change the 6 to another value, depending on the x-scale and on the figure size.
import numpy as np
import matplotlib.pyplot as plt
# import matplotlib.cbook as cbook
import requests
from io import BytesIO
labels = ['CW', 'CV', 'GW', 'SX', 'DO']
colors = ['crimson', 'dodgerblue', 'teal', 'limegreen', 'gold']
values = 30 + np.random.randint(5, 20, len(labels)).cumsum()
height = 0.9
plt.barh(y=labels, width=values, height=height, color=colors, align='center')
for i, (label, value) in enumerate(zip(labels, values)):
# load the image corresponding to label into img
# with cbook.get_sample_data('ada.png') as image_file:
# img = plt.imread(image_file)
response = requests.get(f'https://www.countryflags.io/{label}/flat/64.png')
img = plt.imread(BytesIO(response.content))
plt.imshow(img, extent=[value - 8, value - 2, i - height / 2, i + height / 2], aspect='auto', zorder=2)
plt.xlim(0, max(values) * 1.05)
plt.ylim(-0.5, len(labels) - 0.5)
plt.tight_layout()
plt.show()
PS: As explained by Ernest in the comments and in this post, using OffsetImage the aspect ratio of the image stays intact. (Also, the xlim and ylim stay intact.) The image will not shrink when there are more bars, so you might need to experiment with the factor in OffsetImage(img, zoom=0.65) and the x-offset in AnnotationBbox(..., xybox=(-25, 0)).
An extra option could place the flags outside the bar for bars that are too short. Or at the left of the y-axis.
The code adapted for horizontal bars could look like:
import numpy as np
import requests
from io import BytesIO
import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
def offset_image(x, y, label, bar_is_too_short, ax):
response = requests.get(f'https://www.countryflags.io/{label}/flat/64.png')
img = plt.imread(BytesIO(response.content))
im = OffsetImage(img, zoom=0.65)
im.image.axes = ax
x_offset = -25
if bar_is_too_short:
x = 0
ab = AnnotationBbox(im, (x, y), xybox=(x_offset, 0), frameon=False,
xycoords='data', boxcoords="offset points", pad=0)
ax.add_artist(ab)
labels = ['CW', 'CV', 'GW', 'SX', 'DO']
colors = ['crimson', 'dodgerblue', 'teal', 'limegreen', 'gold']
values = 2 ** np.random.randint(2, 10, len(labels))
height = 0.9
plt.barh(y=labels, width=values, height=height, color=colors, align='center', alpha=0.8)
max_value = values.max()
for i, (label, value) in enumerate(zip(labels, values)):
offset_image(value, i, label, bar_is_too_short=value < max_value / 10, ax=plt.gca())
plt.subplots_adjust(left=0.15)
plt.show()
To complete #johanC answer, it's possible to use flags from iso-flags-png under GNU/linux and the iso3166 python package:
import matplotlib.pyplot as plt
from iso3166 import countries
import matplotlib.image as mpimg
def pos_image(x, y, pays, haut):
pays = countries.get(pays).alpha2.lower()
fichier = "/usr/share/iso-flags-png-320x240"
fichier += f"/{pays}.png"
im = mpimg.imread(fichier)
ratio = 4 / 3
w = ratio * haut
ax.imshow(im,
extent=(x - w, x, y, y + haut),
zorder=2)
plt.style.use('seaborn')
fig, ax = plt.subplots()
liste_pays = [('France', 10), ('USA', 9), ('Spain', 5), ('Italy', 5)]
X = [p[1] for p in liste_pays]
Y = [p[0] for p in liste_pays]
haut = .8
r = ax.barh(y=Y, width=X, height=haut, zorder=1)
y_bar = [rectangle.get_y() for rectangle in r]
for pays, y in zip(liste_pays, y_bar):
pos_image(pays[1], y, pays[0], haut)
plt.show()
which gives:

Matplotlib automatically scale vertical height of subplots for shared x-axis figure

I want to automatically scale the vertical height of subplots for shared x-axis figures based on their data span! I want to compare the relative intensity of the displayed data. If i use the sharey=True kwarg for the subbplots the data is displayed in a way that the relative intensity is recognizable:
import matplotlib.pyplot as plt
from matplotlib import gridspec
import numpy as np
SIZE = (12, 8) #desired overall figure size
# Simple data to display in various forms
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x ** 2)
y2 = 2*(np.sin(x ** 2))
y3 = 3*(np.sin(x ** 2))
fig, ax = plt.subplots(3,ncols=1, sharex=True, sharey=True)
fig.set_size_inches(SIZE[1], SIZE[0])
fig.subplots_adjust(hspace=0.001)
ax[0].plot(x, y)
ax[1].plot(x, y2)
ax[2].plot(x, y3)
plt.show()
All subplots have the same height now and the data span in the y-Axis is recognizable as the data is displayed with the correct relative proportion.
What i would like to achieve is that the scales of each plot end where the data ends. Essentially eliminating the not used white space. The size of the subplot would than represent the relative height ratios of the data. They should still have the same scaling on the Y axis in order for the viewer to estimate the relative data height ( which cold be a countrate for example).
I found the following links to similar problems but none really helped me to solve my issue:
Link1 Link2
Here an example that determines the ratio for you and creates the subplots accordingly:
import matplotlib.pyplot as plt
from matplotlib import gridspec
import numpy as np
SIZE = (12, 8) #desired overall figure size
# Simple data to display in various forms
x = np.linspace(0, 2 * np.pi, 400)
# the maximum multiplier for the function
N = 3
# the y-ranges:
ys = [i * np.sin(x**2) for i in range(1,N+1)]
# the maximum extent of the plot in y-direction (cast as int)
hs = [int(np.ceil(np.max(np.abs(y)))) for y in ys]
# determining the size of the GridSpec:
gs_size = np.sum(hs)
gs = gridspec.GridSpec(gs_size,1)
# the figure
fig = plt.figure(figsize = SIZE)
# creating the subplots
base = 0
ax = []
for y,h in zip(ys,hs):
ax.append(fig.add_subplot(gs[base:h+base,:]))
base += h
ax[-1].plot(x,y)
##fig, ax = plt.subplots(3,ncols=1, sharex=True, sharey=True)
##fig.set_size_inches(SIZE[1], SIZE[0])
fig.subplots_adjust(hspace=0.001)
##ax[0].plot(x, ys[0])
##ax[1].plot(x, ys[1])
##ax[2].plot(x, ys[2])
plt.show()
The code determines the maximum y-extend for each set of data, casts it into an integer and then divides the figure into subplots using the sum of these extends as scale for the GridSpec.
The resulting figure looks like this:
Tested on Python 3.5
EDIT:
If the maximum and minimum extents of your data are not comparable, it may be better to change the way hs is calculated into
hs = [int(np.ceil(np.max(y))) - int(np.floor(np.min(y))) for y in ys]

How to plot confusion matrix with gridding without color using matplotlib?

I found a similar quesion on How to plot confusion matrix with string axis rather than integer in python. But the answer is not exact what I want. Because it doesn't contain gridding (e.g., the numbers are not in little squares) and there is background color to show the number which is not what I want.
import numpy as np
import matplotlib.pyplot as plt
conf_arr = [[33,2,0,0,0,0,0,0,0,1,3],
[3,31,0,0,0,0,0,0,0,0,0],
[0,4,41,0,0,0,0,0,0,0,1],
[0,1,0,30,0,6,0,0,0,0,1],
[0,0,0,0,38,10,0,0,0,0,0],
[0,0,0,3,1,39,0,0,0,0,4],
[0,2,2,0,4,1,31,0,0,0,2],
[0,1,0,0,0,0,0,36,0,2,0],
[0,0,0,0,0,0,1,5,37,5,1],
[3,0,0,0,0,0,0,0,0,39,0],
[0,0,0,0,0,0,0,0,0,0,38]]
norm_conf = []
for i in conf_arr:
a = 0
tmp_arr = []
a = sum(i, 0)
for j in i:
tmp_arr.append(float(j)/float(a))
norm_conf.append(tmp_arr)
fig = plt.figure()
plt.clf()
ax = fig.add_subplot(111)
ax.set_aspect(1)
res = ax.imshow(np.array(norm_conf), cmap=plt.cm.jet,
interpolation='nearest')
width, height = conf_arr.shape
for x in xrange(width):
for y in xrange(height):
ax.annotate(str(conf_arr[x][y]), xy=(y, x),
horizontalalignment='center',
verticalalignment='center')
cb = fig.colorbar(res)
alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
plt.xticks(range(width), alphabet[:width])
plt.yticks(range(height), alphabet[:height])
plt.savefig('confusion_matrix.png', format='png')
By making only a few changes to that rather excellent code proposal (I upvoted it, consider doing that too), you can get the figure you're describing.
You'll get gridding by calling the hlines and vlines methods of the ax object, which will add horizontal and vertical lines respectively.
When you then also remove the call to imshow, the colors are gone. Like this:
import numpy as np
import matplotlib.pyplot as plt
conf_arr = np.array([[33,2,0,0,0,0,0,0,0,1,3],
[3,31,0,0,0,0,0,0,0,0,0],
[0,4,41,0,0,0,0,0,0,0,1],
[0,1,0,30,0,6,0,0,0,0,1],
[0,0,0,0,38,10,0,0,0,0,0],
[0,0,0,3,1,39,0,0,0,0,4],
[0,2,2,0,4,1,31,0,0,0,2],
[0,1,0,0,0,0,0,36,0,2,0],
[0,0,0,0,0,0,1,5,37,5,1],
[3,0,0,0,0,0,0,0,0,39,0],
[0,0,0,0,0,0,0,0,0,0,38]])
height, width = conf_arr.shape
fig = plt.figure('confusion matrix')
ax = fig.add_subplot(111, aspect='equal')
for x in range(width):
for y in range(height):
ax.annotate(str(conf_arr[x][y]), xy=(y, x), ha='center', va='center')
offset = .5
ax.set_xlim(-offset, width - offset)
ax.set_ylim(-offset, height - offset)
ax.hlines(y=np.arange(height+1)- offset, xmin=-offset, xmax=width-offset)
ax.vlines(x=np.arange(width+1) - offset, ymin=-offset, ymax=height-offset)
alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
plt.xticks(range(width), alphabet[:width])
plt.yticks(range(height), alphabet[:height])
plt.savefig('confusion_matrix.png', format='png')
Remark that when you remove the call to imshow, you'll need to set the x- and y-limits explicitly, as shown above, otherwise you'll only see the lower left region (imshow updates the limits automatically depending on what you pass to it).

How to create Matplotlib figure with image and profile plots that fit together?

I would like to plot 2d data as an image, with profile plots through along the x and y axis displayed below and to the side. It's a pretty common way to display data so there may be an easier way to approach this. I would like to find the most simple and robust way that does so correctly, and without using anything outside of matplotlib (though I would be interested in knowing of other packages that may be particularly relevant). In particular, the method should work without changing anything if the shape (aspect ratio) of the data changes.
My main issue is getting the side plots to scale correctly so their borders match up with main plot.
Example code:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
# generate grid and test data
x, y = np.linspace(-3,3,300), np.linspace(-1,1,100)
X, Y = np.meshgrid(x,y)
def f(x,y) :
return np.exp(-(x**2/4+y**2)/.2)*np.cos((x**2+y**2)*10)**2
data = f(X,Y)
# 2d image plot with profiles
h, w = data.shape
gs = gridspec.GridSpec(2, 2,width_ratios=[w,w*.2], height_ratios=[h,h*.2])
ax = [plt.subplot(gs[0]),plt.subplot(gs[1]),plt.subplot(gs[2])]
bounds = [x.min(),x.max(),y.min(),y.max()]
ax[0].imshow(data, cmap='gray', extent = bounds, origin='lower')
ax[1].plot(data[:,w/2],Y[:,w/2],'.',data[:,w/2],Y[:,w/2])
ax[1].axis([data[:,w/2].max(), data[:,w/2].min(), Y.min(), Y.max()])
ax[2].plot(X[h/2,:],data[h/2,:],'.',X[h/2,:],data[h/2,:])
plt.show()
As you can see from the output below, the way things are scaled the image to the right does not properly match the boundaries.
Partial solutions:
1) Manually play with the figure size to find the right aspect ratio so that it appears correctly (could do automatically using the image ratio + padding + the width ratios used?). Seems tacky when there are already so many options for packing that are supposed to take care of these things automatically. EDIT: plt.gcf().set_figheight(f.get_figwidth()*h/w) seems to work if padding is not changed.
2) Add ax[0].set_aspect('auto') , which then makes boundaries line up, but the image no longer has the correct aspect ratio.
Output from code sample above:
you can use sharex and sharey to do this, replace your ax= line with this:
ax = [plt.subplot(gs[0]),]
ax.append(plt.subplot(gs[1], sharey=ax[0]))
ax.append(plt.subplot(gs[2], sharex=ax[0]))
I haven't been able to generate your layout by using subplot and gridspec, while still preserving (1) the ratio of the axes and (2) the limits imposed on the axis. An alternative solution would be to place your axes manually in your figure instead and to control the size of the figure accordingly (as you already mentioned in your OP). Although this requires more work than using subplot and gridspec, this approach remains quite simple and can be very powerful and flexible to produce complex layouts where a fine control over the margins and the placement of the axes is desired.
Below is an example that shows how this can be achieve by setting the size of the figure accordingly to the size given to the axes. Inversely, it would also be possible to fit the axes within a figure of a predefined size. The aspect ratio of the axes would then be kept by using the figure margins as a buffer.
import numpy as np
import matplotlib.pyplot as plt
plt.close('all')
#------------------------------------------------------------ generate data ----
# generate grid and test data
x, y = np.linspace(-3, 3, 300), np.linspace(-1, 1, 100)
X, Y = np.meshgrid(x,y)
def f(x,y) :
return np.exp(-(x**2/4+y**2)/.2)*np.cos((x**2+y**2)*10)**2
data = f(X,Y)
# 2d image plot with profiles
h, w = data.shape
data_ratio = h / float(w)
#------------------------------------------------------------ create figure ----
#--- define axes lenght in inches ----
width_ax0 = 8.
width_ax1 = 2.
height_ax2 = 2.
height_ax0 = width_ax0 * data_ratio
#---- define margins size in inches ----
left_margin = 0.65
right_margin = 0.2
bottom_margin = 0.5
top_margin = 0.25
inter_margin = 0.5
#--- calculate total figure size in inches ----
fwidth = left_margin + right_margin + inter_margin + width_ax0 + width_ax1
fheight = bottom_margin + top_margin + inter_margin + height_ax0 + height_ax2
fig = plt.figure(figsize=(fwidth, fheight))
fig.patch.set_facecolor('white')
#---------------------------------------------------------------- create axe----
ax0 = fig.add_axes([left_margin / fwidth,
(bottom_margin + inter_margin + height_ax2) / fheight,
width_ax0 / fwidth, height_ax0 / fheight])
ax1 = fig.add_axes([(left_margin + width_ax0 + inter_margin) / fwidth,
(bottom_margin + inter_margin + height_ax2) / fheight,
width_ax1 / fwidth, height_ax0 / fheight])
ax2 = fig.add_axes([left_margin / fwidth, bottom_margin / fheight,
width_ax0 / fwidth, height_ax2 / fheight])
#---------------------------------------------------------------- plot data ----
bounds = [x.min(),x.max(),y.min(),y.max()]
ax0.imshow(data, cmap='gray', extent = bounds, origin='lower')
ax1.plot(data[:,w/2],Y[:,w/2],'.',data[:,w/2],Y[:,w/2])
ax1.invert_xaxis()
ax2.plot(X[h/2,:], data[h/2,:], '.', X[h/2,:], data[h/2,:])
plt.show(block=False)
fig.savefig('subplot_layout.png')
Which results in:
Interestingly the solution with sharex and sharey do not work for me. They align the axis ranges but not the axis lengths!
To have them aligned reliably I added:
pos = ax[0].get_position()
pos1 = ax[1].get_position()
pos2 = ax[2].get_position()
ax[1].set_position([pos1.x0,pos.y0,pos1.width,pos.height])
ax[2].set_position([pos.x0,pos2.y0,pos.width,pos2.height])
So in context with the earlier answer from CT Zhu this makes:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
# generate grid and test data
x, y = np.linspace(-3,3,300), np.linspace(-1,1,100)
X, Y = np.meshgrid(x,y)
def f(x,y) :
return np.exp(-(x**2/4+y**2)/.2)*np.cos((x**2+y**2)*10)**2
data = f(X,Y)
# 2d image plot with profiles
h, w = data.shape
gs = gridspec.GridSpec(2, 2,width_ratios=[w,w*.2], height_ratios=[h,h*.2])
ax = [plt.subplot(gs[0]),]
ax.append(plt.subplot(gs[1], sharey=ax[0]))
ax.append(plt.subplot(gs[2], sharex=ax[0]))
bounds = [x.min(),x.max(),y.min(),y.max()]
ax[0].imshow(data, cmap='gray', extent = bounds, origin='lower')
ax[1].plot(data[:,int(w/2)],Y[:,int(w/2)],'.',data[:,int(w/2)],Y[:,int(w/2)])
ax[1].axis([data[:,int(w/2)].max(), data[:,int(w/2)].min(), Y.min(), Y.max()])
ax[2].plot(X[int(h/2),:],data[int(h/2),:],'.',X[int(h/2),:],data[int(h/2),:])
pos = ax[0].get_position()
pos1 = ax[1].get_position()
pos2 = ax[2].get_position()
ax[1].set_position([pos1.x0,pos.y0,pos1.width,pos.height])
ax[2].set_position([pos.x0,pos2.y0,pos.width,pos2.height])
plt.show()

Creating a colour bar for a plot made with plt.fill

I'm new to Python (was an IDL user before hand) so I hope that I'm asking this in an understandable way. I've been trying to create a polar plot with x number of bins where the data in the bin is averaged and given a colour associated with that value. This seems to work fine while using the plt.fill command where I can define the bin and then the fill colour. The problem comes when I then try to make a colour bar to go with it. I keep getting errors that state AttributeError: 'Figure' object has no attribute 'autoscale_None'
Any advice would be helpful thanks.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib.pyplot import figure, show, rc, grid
import pylab
r = np.arange(50)/5.
rstep = r[1] - r[0]
theta = np.arange(50)/50.*2.*np.pi
tstep = theta[1] - theta[0]
colorv = np.arange(50)/50.
# force square figure and square axes looks better for polar, IMO
width, height = mpl.rcParams['figure.figsize']
size = min(width, height)
# make a square figure
fig = figure(figsize=(size, size))
ax = fig.add_axes([0.1, 0.1, .8, .8])#, polar=True)
my_cmap = cm.jet
for j in range(len(r)):
rbox = np.array([r[j], r[j], r[j]+ rstep, r[j] + rstep])
for i in range(len(theta)):
thetabox = np.array([theta[i], theta[i] + tstep, theta[i] + tstep, theta[i]])
x = rbox*np.cos(thetabox)
y = rbox*np.sin(thetabox)
plt.fill(x,y, facecolor = my_cmap(colorv[j]))
# Add colorbar, make sure to specify tick locations to match desired ticklabels
cbar = fig.colorbar(fig, ticks=[np.min(colorv), np.max(colorv)])
cb = plt.colorbar()
plt.show()
* here is a slightly better example of my real data, there are holes missing everywhere, so in this example I've just made a big one in a quarter of the circle. When I've tried meshing, the code seems to try to interpolate over these regions.
r = np.arange(50)/50.*7. + 3.
rstep = r[1] - r[0]
theta = np.arange(50)/50.*1.5*np.pi - np.pi
tstep = theta[1] - theta[0]
colorv = np.sin(r/10.*np.pi)
# force square figure and square axes looks better for polar, IMO
width, height = mpl.rcParams['figure.figsize']
size = min(width, height)
# make a square figure
fig = figure(figsize=(size, size))
ax = fig.add_axes([0.1, 0.1, .8, .8])#, polar=True)
my_cmap = cm.jet
for j in range(len(r)):
rbox = np.array([r[j], r[j], r[j]+ rstep, r[j] + rstep])
for i in range(len(theta)):
thetabox = np.array([theta[i], theta[i] + tstep, theta[i] + tstep, theta[i]])
x = rbox*np.cos(thetabox)
y = rbox*np.sin(thetabox)
plt.fill(x,y, facecolor = my_cmap(colorv[j]))
# Add colorbar, make sure to specify tick locations to match desired ticklabels
#cbar = fig.colorbar(fig, ticks=[np.min(colorv), np.max(colorv)])
#cb = plt.colorbar()
plt.show()
And then with a meshing involved...
from matplotlib.mlab import griddata
r = np.arange(50)/5.
rstep = r[1] - r[0]
theta = np.arange(50)/50.*1.5*np.pi - np.pi
tstep = theta[1] - theta[0]
colorv = np.sin(r/10.*np.pi)
# force square figure and square axes looks better for polar, IMO
width, height = mpl.rcParams['figure.figsize']
size = min(width, height)
# make a square figure
fig = figure(figsize=(size, size))
ax = fig.add_axes([0.1, 0.1, .8, .8])#, polar=True)
my_cmap = cm.jet
x = r*np.cos(theta)
y = r*np.sin(theta)
X,Y = np.meshgrid(x,y)
data = griddata(x,y,colorv,X,Y)
cax = plt.contourf(X,Y, data)
plt.colorbar()
# Add colorbar, make sure to specify tick locations to match desired ticklabels
#cbar = fig.colorbar(fig, ticks=[np.min(colorv), np.max(colorv)])
#cb = plt.colorbar()
plt.show()
colorbar needs things to be an instance of ScalarMappable in order to make a colorbar from them.
Because you're manually setting each tile, there's nothing that essentially has a colorbar.
There are a number of ways to fake it from your colormap, but in this case there's a much simpler solution.
pcolormesh does exactly what you want, and will be much faster.
As an example:
import numpy as np
import matplotlib.pyplot as plt
# Linspace makes what you're doing _much_ easier (and includes endpoints)
r = np.linspace(0, 10, 50)
theta = np.linspace(0, 2*np.pi, 50)
fig = plt.figure()
ax = fig.add_subplot(111, projection='polar')
# "Grid" r and theta into 2D arrays (see the docs for meshgrid)
r, theta = np.meshgrid(r, theta)
cax = ax.pcolormesh(theta, r, r, edgecolors='black', antialiased=True)
# We could just call `plt.colorbar`, but I prefer to be more explicit
# and pass in the artist that I want it to extract colors from.
fig.colorbar(cax)
plt.show()
Or, if you'd prefer non-polar axes, as in your example code:
import numpy as np
import matplotlib.pyplot as plt
r = np.linspace(0, 10, 50)
theta = np.linspace(0, 2*np.pi, 50)
# "Grid" r and theta and convert them to cartesian coords...
r, theta = np.meshgrid(r, theta)
x, y = r * np.cos(theta), r * np.sin(theta)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.axis('equal')
cax = ax.pcolormesh(x, y, r, edgecolors='black', antialiased=True)
fig.colorbar(cax)
plt.show()
Note: If you'd prefer the boundary lines a bit less dark, just specify linewidth=0.5 or something similar to pcolormesh.
Finally, if you did want to directly make the colorbar from the colormap in your original code, you'd create an instance of ScalarMappable from it and pass this to colorbar. It's easier than it sounds, but it's a bit verbose.
As an example, in your original code, if you do something like the following:
cax = cm.ScalarMappable(cmap=my_cmap)
cax.set_array(colorv)
fig.colorbar(cax)
It should do what you want.
So I've found a workaround. Since I know of a region where I definitely won't have data, I've plotted some there. I've made sure that the data covers the entire range of what I'm potting. I then cover it up (this region was going to be covered anyway, it shows where the "earth" is located). Now I can go ahead and use plt.fill as I had originally and use the colour bar from the randomly potted data. I know this isn't probably the correct way, but it works and doesn't try to interpolate my data.
Thanks so much for helping get this sorted. and if you know of a better way, I'd be happy to hear it!
hid = plt.pcolormesh(X,Y, data, antialiased=True)
#here we cover up the region that we just plotted in
r3 = [1 for i in range(360)]
theta3 = np.arange(360)*np.pi/180.
plt.fill(theta3, r3, 'w')
#now we can go through and fill in all the regions
for j in range(len(r)):
rbox = np.array([r[j], r[j], r[j]+ rstep, r[j] + rstep])
for i in range(len(theta)):
thetabox = np.array([theta[i], theta[i] + tstep, theta[i] + tstep, theta[i]])
x = rbox*np.cos(thetabox)
y = rbox*np.sin(thetabox)
colorv = np.sin(r[j]/10.*np.pi)
plt.fill(thetabox,rbox, facecolor = my_cmap(colorv))
#And now we can plot the color bar that fits the data Tada :)
plt.colorbar()
plt.show()

Categories

Resources