I am almost brand-new to Python and matplotlib, and so have been working on adapting an example from the Python documentation for a graph that I need to complete. However, I get undefined name errors for the rect1 and rect2 calls and for ax in the ax.text. I have a feeling that it has something to do with the values not transferring across function definitions, but I can't figure out the proper syntax. Any ideas?
P.S. I can supply additional information if necessary; this is my first post of this sort.
from inventoryClass import stockItem
import numpy as np
import matplotlib.pyplot as plt
def plotInventory(itemRecords):
stockBegin = []
stockFinish = []
stockID = []
stockItems = []
for rec in itemRecords.values() :
stockBegin.append(rec.getStockStart)
stockFinish.append(rec.getStockOnHand)
stockID.append(rec.getID)
stockItems.append(rec.getName)
N = len(stockBegin)
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars
fig, ax = plt.subplots()
rects1 = ax.bar(ind, stockBegin, width, color='r')
rects2 = ax.bar(ind + width, stockFinish, width, color='y')
# add some text for labels, title and axes ticks
ax.set_ylabel('Inventory')
ax.set_title('Stock start and end inventory, by item')
ax.set_xticks(ind + width)
ax.set_xticklabels((str(stockID[0]), str(stockID[1]), str(stockID[1])))
ax.legend((rects1[0], rects2[0]), ('Start', 'End'))
def autolabel(rects) :
for rect in rects :
height = rect.get_height()
ax.text(rect.get_x() + rect.get_width()/2., 1.05*height,
'%d' % int(height),
ha='center', va='bottom')
autolabel(rects1)
autolabel(rects2)
plt.show()
The variables rects1 and rects2 only exist in the scope of plotInventory and so python doesn't know what you're referring to by 'rects1'. There are two possible ways to fix this:
You can return the values so they are available in the global scope:
def plotInventory(itemRecords):
# ... code ... #
return rects1, rects2
rects1, rects2 = plotInventory(records)
autolabel(rects1)
autolabel(rects2)
plt.show()
You can just call autolabel from inside plotInventory:
def plotInventory(itemRecords):
# ... code ... #
autolabel(rects1)
autolabel(rects2)
As for ax, you have the same problem and the same solutions, except that you need to pass ax into autolabel, eg:
def autolabel(ax, rects):
# ... code ... #
ax, rects1, rects2 = plotInventory(records)
autolabel(ax, rects1)
autolabel(ax, rects2)
Remember to return ax from plotInventory as well!
Related
i want visualyze with seaborn and add the text. this my code:
# barplot price by body-style
fig, ax = plt.subplots(figsize = (12,8))
g = data[['body-style','price']].groupby(by = 'body-
style').sum().reset_index().sort_values(by='price')
x = g['body-style']
y = g['price']
ok = sns.barplot(x,y, ci = None)
ax.set_title('Price By Body Style')
def autolabel(rects):
for idx,rect in enumerate(ok):
height = rect.get_height()
g.text(rect.get_x() + rect.get_width()/2., 0.2*height,
g['price'].unique().tolist()[idx],
ha='center', va='bottom', rotation=90)
autolabel(ok)
but i go error:
You need a few changes:
As you already created the ax, you need sns.barplot(..., ax=ax).
autolabel() needs to be called with the list of bars as argument. With seaborn you get this list via ax.patches.
for idx,rect in enumerate(ok): shouldn't use ok but rects.
You can't use g.text. g is a dataframe and doesn't have a .text function. You need ax.text.
Using g['price'].unique().tolist()[idx] as the text to print doesn't have any relationship with the plotted bars. You could use height instead.
Here is some test code with toy data:
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
fig, ax = plt.subplots(figsize=(12, 8))
g = data[['body-style','price']].groupby(by = 'body-style').sum().reset_index().sort_values(by='price')
x = g['body-style']
y = g['price']
# x = list('abcdefghij')
# y = np.random.randint(20, 100, len(x))
sns.barplot(x, y, ci=None, ax=ax)
ax.set_title('Price By Body Style')
def autolabel(rects):
for rect in rects:
height = rect.get_height()
ax.text(rect.get_x() + rect.get_width() / 2., 0.2 * height,
height,
ha='center', va='bottom', rotation=90, color='white')
autolabel(ax.patches)
plt.show()
PS: You can change the fontsize of the text via a parameter to ax.text: ax.text(..., fontsize=14).
This question already has answers here:
Saving a figure after invoking pyplot.show() results in an empty file
(4 answers)
Closed 5 years ago.
me very new programmer, I having problem with saving bar chart to png, bars aren't showing up.
My Code:
import numpy as np
import matplotlib.pyplot as plt
N = 3
ind = np.arange(N)
width = 0.35
Start_means = (100, 50, 50)
Start_std = (2, 3, 4)
End_means = (80, 30, 30)
End_std = (3, 5, 2)
fig, ax = plt.subplots()
rects1 = ax.bar(ind, Start_means, width, color='xkcd:red', yerr=Start_std)
rects2 = ax.bar(ind+width, End_means, width, color='xkcd:black', yerr=End_std)
ax.legend((rects1[0], rects2[0]), ('Start', 'End'))
ax.set_ylabel('Available')
ax.set_title('Travel availability, by tour')
ax.set_xticks(ind + width/2)
countries = ['Italy', 'China', 'France']
ids = ['ID:12345', 'ID:13579', 'ID:24680']
xlabels = []
for i, j in zip(countries, ids):
xlabels.append(i + '\n' + j)
ax.set_xticklabels(xlabels)
def autolabel(rects):
for rect in rects:
height = rect.get_height()
ax.text(rect.get_x() + rect.get_width()/2., 1.05*height,
'%d' % int(height),
ha='center', va='bottom')
autolabel(rects1)
autolabel(rects2)
plt.show()
plt.savefig('barchart.png')
What it should look like: here
I'd like to save it as a png file, but it just comes up blank without the bars.
You simply need to swap the order in which the plt.show() appears and the plt.savefig('barchart.png')
plt.savefig('barchart.png')
plt.show()
The reason that plt.savefig doesn't work after calling show is that
the current figure has been reset.
Source: https://stackoverflow.com/a/21884187/1577947
I am trying to plot groups of data which have different bar sizes and may have different group sizes. How can I group the bars that belong to the same groups (shown as the same color) so that they are side by side? (Similar to this, except the same colors should be side-by-side)
width = 0.50
groupgap=2
y1=[20,80]
y2=[60,30,10]
x1 = np.arange(len(y1))
x2 = np.arange(len(y2))+groupgap
ind = np.concatenate((x1,x2))
fig, ax = plt.subplots()
rects1 = ax.bar(x1, y1, width, color='r', ecolor= "black",label="Gender")
rects2 = ax.bar(x2, y2, width, color='b', ecolor= "black",label="Type")
ax.set_ylabel('Population',fontsize=14)
ax.set_xticks(ind)
ax.set_xticklabels(('Male', 'Female','Student', 'Faculty','Others'),fontsize=14)
ax.legend()
The idea of using a gap between the categories (groupgap) is indeed a way to go. You would just have to add the length of the first group as well:
x2 = np.arange(len(y2))+groupgap+len(y1)
Here is the complete example where I used groupgap=1:
import matplotlib.pyplot as plt
import numpy as np
width = 1
groupgap=1
y1=[20,80]
y2=[60,30,10]
x1 = np.arange(len(y1))
x2 = np.arange(len(y2))+groupgap+len(y1)
ind = np.concatenate((x1,x2))
fig, ax = plt.subplots()
rects1 = ax.bar(x1, y1, width, color='r', edgecolor= "black",label="Gender")
rects2 = ax.bar(x2, y2, width, color='b', edgecolor= "black",label="Type")
ax.set_ylabel('Population',fontsize=14)
ax.set_xticks(ind)
ax.set_xticklabels(('Male', 'Female','Student', 'Faculty','Others'),fontsize=14)
plt.show()
I finally forced the 3 plots I want into one plot with 3 subplots...now I need to add a common colorbar, preferably horizontally oriented. Also, now that I have them as subplots, I have lost the labels that were there in a previous iteration.
It seems that the examples suggest I add an axes, but I don't quite get what the numbers in the arguments are.
def plot_that_2(x_vals, y_vals, z_1_vals, z_2_vals, z_3_vals, figname, units, efficiency_or_not):
global letter_pic_width
plt.close() #I moved this up from the end of the file because it solved my QTagg problem
UI = [uniformity_calc(z_1_vals), uniformity_calc(z_2_vals), uniformity_calc(z_3_vals)]
ranges = [ str(int(np.max(z_1_vals) - np.min(z_1_vals))), str(int(np.max(z_2_vals) - np.min(z_2_vals))), str(int(np.max(z_3_vals) - np.min(z_3_vals)))]
z_vals = [z_1_vals, z_2_vals, z_3_vals]
fig = plt.figure(figsize = (letter_pic_width, letter_pic_width/3 ))
ax0 = fig.add_subplot(1,3,1, aspect = 1)
ax1 = fig.add_subplot(1,3,2, aspect = 1)
ax2 = fig.add_subplot(1,3,3, aspect = 1)
axenames = [ax0, ax1, ax2]
for z_val, unif, rangenum, ax in zip(z_vals, UI, ranges, axenames):
ax.scatter(x_vals, y_vals, c = z_val, s = 100, cmap = 'rainbow')
if efficiency_or_not:
ax.vmin = 0
ax.vmax = 1
ax.xlabel = 'Uniformity: ' + unif
else:
ax.xlabel = 'Uniformity: ' + unif + ' ' + rangenum + ' ppm'
plt.savefig('./'+ figname + '.jpg', dpi = 100)
To set the xlabel, use ax.set_xlabel('Uniformity: ' + unif) See more information here in the documentation for axes.
The example you linked to uses the add_axes method of a figure as an alternative to add_subplot. The documentation for figures explains what the numbers in add_axes are: "Add an axes at position rect [left, bottom, width, height] where all quantities are in fractions of figure width and height."
rect = l,b,w,h
fig.add_axes(rect)
To answer your question about the colorbar axis, the numbers represent
[bottom_left_x_coord, bottom_left_y_coord, width, height]
An appropriate colorbar might be
# x y w h
[0.2, 0.1, 0.6, 0.05]
Here's your code, somewhat reworked which adds a colorbar:
import numpy as np
import matplotlib.pyplot as plt
WIDTH = 9
def uniformity_calc(x):
return x.mean()
def plotter(x, y, zs, name, units, efficiency=True):
fig, axarr = plt.subplots(1, 3, figsize=(WIDTH, WIDTH/3),
subplot_kw={'aspect':1})
fig.suptitle(name)
UI = map(uniformity_calc, zs)
ranges = map(lambda x: int(np.max(x)-np.min(x)), zs)
for ax, z, unif, rangenum in zip(axarr, zs, UI, ranges):
scat = ax.scatter(x, y, c=z, s=100, cmap='rainbow')
label = 'Uniformity: %i'%unif
if not efficiency:
label += ' %i ppm'%rangenum
ax.set_xlabel(label)
# Colorbar [left, bottom, width, height
cax = fig.add_axes([0.2, 0.1, 0.6, 0.05])
cbar = fig.colorbar(scat, cax, orientation='horizontal')
cbar.set_label('This is a colorbar')
plt.show()
def main():
x, y = np.meshgrid(np.arange(10), np.arange(10))
zs = [np.random.rand(*y.shape) for _ in range(3)]
plotter(x.flatten(), y.flatten(), zs, 'name', None)
if __name__ == "__main__":
main()
In MATLAB, one can use datacursormode to add annotation to a graph when user mouses over. Is there such thing in matplotlib? Or I need to write my own event using matplotlib.text.Annotation?
Late Edit / Shameless Plug: This is now available (with much more functionality) as mpldatacursor. Calling mpldatacursor.datacursor() will enable it for all matplotlib artists (including basic support for z-values in images, etc).
As far as I know, there isn't one already implemented, but it's not too hard to write something similar:
import matplotlib.pyplot as plt
class DataCursor(object):
text_template = 'x: %0.2f\ny: %0.2f'
x, y = 0.0, 0.0
xoffset, yoffset = -20, 20
text_template = 'x: %0.2f\ny: %0.2f'
def __init__(self, ax):
self.ax = ax
self.annotation = ax.annotate(self.text_template,
xy=(self.x, self.y), xytext=(self.xoffset, self.yoffset),
textcoords='offset points', ha='right', va='bottom',
bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5),
arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0')
)
self.annotation.set_visible(False)
def __call__(self, event):
self.event = event
# xdata, ydata = event.artist.get_data()
# self.x, self.y = xdata[event.ind], ydata[event.ind]
self.x, self.y = event.mouseevent.xdata, event.mouseevent.ydata
if self.x is not None:
self.annotation.xy = self.x, self.y
self.annotation.set_text(self.text_template % (self.x, self.y))
self.annotation.set_visible(True)
event.canvas.draw()
fig = plt.figure()
line, = plt.plot(range(10), 'ro-')
fig.canvas.mpl_connect('pick_event', DataCursor(plt.gca()))
line.set_picker(5) # Tolerance in points
As it seems like at least a few people are using this, I've added an updated version below.
The new version has a simpler usage and a lot more documentation (i.e. a tiny bit, at least).
Basically you'd use it similar to this:
plt.figure()
plt.subplot(2,1,1)
line1, = plt.plot(range(10), 'ro-')
plt.subplot(2,1,2)
line2, = plt.plot(range(10), 'bo-')
DataCursor([line1, line2])
plt.show()
The main differences are that a) there's no need to manually call line.set_picker(...), b) there's no need to manually call fig.canvas.mpl_connect, and c) this version handles multiple axes and multiple figures.
from matplotlib import cbook
class DataCursor(object):
"""A simple data cursor widget that displays the x,y location of a
matplotlib artist when it is selected."""
def __init__(self, artists, tolerance=5, offsets=(-20, 20),
template='x: %0.2f\ny: %0.2f', display_all=False):
"""Create the data cursor and connect it to the relevant figure.
"artists" is the matplotlib artist or sequence of artists that will be
selected.
"tolerance" is the radius (in points) that the mouse click must be
within to select the artist.
"offsets" is a tuple of (x,y) offsets in points from the selected
point to the displayed annotation box
"template" is the format string to be used. Note: For compatibility
with older versions of python, this uses the old-style (%)
formatting specification.
"display_all" controls whether more than one annotation box will
be shown if there are multiple axes. Only one will be shown
per-axis, regardless.
"""
self.template = template
self.offsets = offsets
self.display_all = display_all
if not cbook.iterable(artists):
artists = [artists]
self.artists = artists
self.axes = tuple(set(art.axes for art in self.artists))
self.figures = tuple(set(ax.figure for ax in self.axes))
self.annotations = {}
for ax in self.axes:
self.annotations[ax] = self.annotate(ax)
for artist in self.artists:
artist.set_picker(tolerance)
for fig in self.figures:
fig.canvas.mpl_connect('pick_event', self)
def annotate(self, ax):
"""Draws and hides the annotation box for the given axis "ax"."""
annotation = ax.annotate(self.template, xy=(0, 0), ha='right',
xytext=self.offsets, textcoords='offset points', va='bottom',
bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5),
arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0')
)
annotation.set_visible(False)
return annotation
def __call__(self, event):
"""Intended to be called through "mpl_connect"."""
# Rather than trying to interpolate, just display the clicked coords
# This will only be called if it's within "tolerance", anyway.
x, y = event.mouseevent.xdata, event.mouseevent.ydata
annotation = self.annotations[event.artist.axes]
if x is not None:
if not self.display_all:
# Hide any other annotation boxes...
for ann in self.annotations.values():
ann.set_visible(False)
# Update the annotation in the current axis..
annotation.xy = x, y
annotation.set_text(self.template % (x, y))
annotation.set_visible(True)
event.canvas.draw()
if __name__ == '__main__':
import matplotlib.pyplot as plt
plt.figure()
plt.subplot(2,1,1)
line1, = plt.plot(range(10), 'ro-')
plt.subplot(2,1,2)
line2, = plt.plot(range(10), 'bo-')
DataCursor([line1, line2])
plt.show()