How to remove matplotlib text border, while making the text be in the first plane, in front of the plotted line?
import matplotlib.pyplot as plt
x = [1, 2, 3]
y = [1, 2, 3]
plt.plot(x, y)
plt.text(2.85, 2.9, 'label', bbox={'facecolor':'white', 'alpha':1, 'pad':10})
plt.show()
Are you asking how to make the text more visible without adding the box behind it? If so, have a look at the last couple of examples.
Controlling the drawing order
The text is already in front of the line, it's just hard to distinguish the two. However, in general, the order of the elements is controlled by the zorder kwarg.
To demonstrate this, I'll change the colors and size of the font in your example to make things a touch more clear:
import matplotlib.pyplot as plt
x = [1, 2, 3]
y =[1, 2, 3]
fig, ax = plt.subplots()
ax.plot(x, y, linewidth=10, color='yellow')
ax.text(2, 2, 'label', ha='center', size=72)
# For the moment, hide everything else...
ax.axis('off')
fig.tight_layout()
plt.show()
If we decrease the z-order of the text below that of the line or increase the zorder of the line above that of the text, the line will be in front. By default, most plotted data types have a zorder of 1, while annotations such as text have a zorder of 3, if I recall correctly. It's just the relative values of zorder that matter, though. In other words, it doesn't matter whether we do ax.text(..., zorder=0) or ax.plot(..., zorder=4), we'll get the same result.
import matplotlib.pyplot as plt
x = [1, 2, 3]
y =[1, 2, 3]
fig, ax = plt.subplots()
ax.plot(x, y, linewidth=10, color='yellow')
ax.text(2, 2, 'label', ha='center', size=72, zorder=0)
# For the moment, hide everything else...
ax.axis('off')
fig.tight_layout()
plt.show()
A more subtle box for clearer labels
However, what you're probably wanting to accomplish is a cleaner way to display the label and the line together.
In that case, you have several different options.
Let's go back to your original example. You can display the box, behind the text, but remove the edge color on the box. So, if you add 'edgecolor':'none' to the dict in the bbox kwarg, you'll get something similar to this:
import matplotlib.pyplot as plt
x = [1, 2, 3]
y =[1, 2, 3]
plt.plot(x, y)
plt.text(2.85, 2.9, 'label',
bbox={'facecolor':'white', 'edgecolor':'none', 'pad':10})
plt.show()
Or as an example of what it would look like using the earlier code snippet with a yellow line:
Using a stroke effect for clear labels
However, this doesn't look as nice if we have more than just a simple line. Therefore, you might also want to consider using a stroke path effect:
import matplotlib.pyplot as plt
import matplotlib.patheffects as pe
x = [1, 2, 3]
y =[1, 2, 3]
fig, ax = plt.subplots()
ax.plot(x, y, linewidth=10, color='yellow')
ax.text(2, 2, 'label', ha='center', size=72,
path_effects=[pe.withStroke(linewidth=10, foreground='w')])
# For the moment, hide everything else...
ax.axis('off')
fig.tight_layout()
fig.set(facecolor='white')
plt.show()
Related
The problem is a bit hard to describe, but very easy to show. I create a grid with subplots on it, where the right column is filled by a tall subplot (approximately following this) which I want to use for the colourbar. Creating a new axis of a given size and using it for a colourbar is done in many code samples (see for example here), but it's not working for me.
Here's an example with a plot layout the same as my real plot that reproduces the problem:
import matplotlib.pyplot as plt
import matplotlib.colors as clt
import numpy as np
fig, axes = plt.subplots(3, 2, figsize=(15,8), tight_layout=True,
gridspec_kw={'width_ratios': [1, 0.02],
'height_ratios': [2, 1, 1]})
x, y = np.random.rand(500000), np.random.rand(500000)
counts, xedges, yedges, im = axes[0, 0].hist2d(x, y, bins=(149, 336), norm=clt.LogNorm(), cmap='inferno_r')
axes[1, 0].plot(np.random.rand(2184))
axes[2, 0].plot(np.random.rand(2184))
gs = axes[0, 1].get_gridspec()
for ax in axes[:, 1]:
ax.remove()
axbig = fig.add_subplot(gs[0:, -1])
bar = fig.colorbar(im, ax=axbig)
axes[0, 0].set_ylabel("2D histogram")
axes[1, 0].set_ylabel("unrelated data")
axes[2, 0].set_ylabel("other unrelated")
bar.set_label("colourbar")
(note that I use add_subplot(gs[0:, -1]) to make the tall subplot, but something like add_axes([0.8, 0.1, 0.03, 0.8]) has the same effect)
And the output:
Notice how the colourbar is added as a tiny little new axis, onto the existing axis which I created for it. I would expect it to fill in the existing axis, as in this or this example. What's going wrong? I'm running matplotlib 3.3.1 from inside spyder 5.0.0 with python 3.8.
Your original problem, that you didn't want one of three axes squished is explicitly taken care of with constrained_layout. https://matplotlib.org/stable/tutorials/intermediate/constrainedlayout_guide.html#suptitle
I think people are for some reason scared off by the warning on the CL guide, but that is really for folks running production code that must be pixel identical each run. For most users CL is a better option than tight_layout.
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np
fig, axs = plt.subplots(3, 1, figsize=(7,4), constrained_layout=True,
gridspec_kw={'height_ratios': [2, 1, 1]})
x, y = np.random.rand(500000), np.random.rand(500000)
res = axs[0].hist2d(x, y, bins=(149, 336),
norm=mcolors.LogNorm(), cmap='inferno_r')
axs[1].plot(np.random.rand(2184))
axs[2].plot(np.random.rand(2184))
fig.colorbar(res[3], ax=axs[0])
plt.show()
Lets say you have a plot in matplotlib, something like that:
figure = Figure()
figureCanvas = FigureCanvas(figure)
axes = figure.add_subplot(111)
axes.plot([1, 2, 3], [2, 3, 1], linestyle = "None", marker = "o", color = '#1f77b4', markersize = 3)
This would give you a plot with 3 points. How do I remove a specific point from plot, without redrawing the whole thing again?
First of all, you need to redraw at least the plot (the Line2D object), otherwise there will be no change in the plot.
Without knowing the purpose of not redrawing, it's hard to judge on an acceptable solution. However, usually you would just redraw the whole canvas.
To set new data, the Line2D.set_data() method can be used as shown in the following. You may press the number key (0,1,2) of the point to remove in the plot.
import matplotlib.pyplot as plt
x = [1, 2, 3]
y = [2, 3, 1]
fig, ax = plt.subplots()
line, = ax.plot(x, y, ls="None", marker="o", color='#1f77b4', ms=10)
def remove_point(event):
try:
key = int(event.key)
xvals = x[:]
xvals.pop(key)
yvals = y[:]
yvals.pop(key)
line.set_data(xvals,yvals)
fig.canvas.draw_idle()
except:
pass
fig.canvas.mpl_connect('key_press_event', remove_point)
ax.set_title("Press number of point to remove")
plt.show()
I'm trying to shade points in a scatter plot based on a set of values (from 0 to 1) picked from one of the already defined color maps, like Blues or Reds. I tried this:
import matplotlib
import matplotlib.pyplot as plt
from numpy import *
from scipy import *
fig = plt.figure()
mymap = plt.get_cmap("Reds")
x = [8.4808517662594909, 11.749082788323497, 5.9075039082855652, 3.6156231827873615, 12.536817102137768, 11.749082788323497, 5.9075039082855652, 3.6156231827873615, 12.536817102137768]
spaced_colors = linspace(0, 1, 10)
print spaced_colors
plt.scatter(x, x,
color=spaced_colors,
cmap=mymap)
# this does not work either
plt.scatter(x, x,
color=spaced_colors,
cmap=plt.get_cmap("gray"))
But it does not work, using either the Reds or gray color map. How can this be done?
edit: if I want to plot each point separately so it can have a separate legend, how can I do it? I tried:
fig = plt.figure()
mymap = plt.get_cmap("Reds")
data = np.random.random([10, 2])
colors = list(linspace(0.1, 1, 5)) + list(linspace(0.1, 1, 5))
print "colors: ", colors
plt.subplot(1, 2, 1)
plt.scatter(data[:, 0], data[:, 1],
c=colors,
cmap=mymap)
plt.subplot(1, 2, 2)
# attempt to plot first five points in five shades of red,
# with a separate legend for each point
for n in range(5):
plt.scatter([data[n, 0]], [data[n, 1]],
c=[colors[n]],
cmap=mymap,
label="point %d" %(n))
plt.legend()
but it fails. I need to make a call to scatter for each point so that it can have a separate label=, but still want each point to have a different shade of the color map as its color.
thanks.
If you really want to do this (what you describe in your edit), you have to "pull" the colors from your colormap (I have commented all changes I made to your code):
import numpy as np
import matplotlib.pyplot as plt
# plt.subplots instead of plt.subplot
# create a figure and two subplots side by side, they share the
# x and the y-axis
fig, axes = plt.subplots(ncols=2, sharey=True, sharex=True)
data = np.random.random([10, 2])
# np.r_ instead of lists
colors = np.r_[np.linspace(0.1, 1, 5), np.linspace(0.1, 1, 5)]
mymap = plt.get_cmap("Reds")
# get the colors from the color map
my_colors = mymap(colors)
# here you give floats as color to scatter and a color map
# scatter "translates" this
axes[0].scatter(data[:, 0], data[:, 1], s=40,
c=colors, edgecolors='None',
cmap=mymap)
for n in range(5):
# here you give a color to scatter
axes[1].scatter(data[n, 0], data[n, 1], s=40,
color=my_colors[n], edgecolors='None',
label="point %d" %(n))
# by default legend would show multiple scatterpoints (as you would normally
# plot multiple points with scatter)
# I reduce the number to one here
plt.legend(scatterpoints=1)
plt.tight_layout()
plt.show()
However, if you only want to plot 10 values and want to name every single one,
you should consider using something different, for instance a bar chart as in this
example. Another opportunity would be to use plt.plot with a custom color cycle, like in this example.
As per the documentation, you want the c keyword argument instead of color. (I agree that this is a bit confusing, but the "c" and "s" terminology is inherited from matlab, in this case.)
E.g.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
x, y, colors = np.random.random((3,10))
fig, ax = plt.subplots()
ax.scatter(x, y, c=colors, s=50, cmap=mpl.cm.Reds)
plt.show()
How about:
import matplotlib.pyplot as plt
import numpy as np
reds = plt.get_cmap("Reds")
x = np.linspace(0, 10, 10)
y = np.log(x)
# color by value given a cmap
plt.subplot(121)
plt.scatter(x, y, c=x, s=100, cmap=reds)
# color by value, and add a legend for each
plt.subplot(122)
norm = plt.normalize()
norm.autoscale(x)
for i, (x_val, y_val) in enumerate(zip(x, y)):
plt.plot(x_val, y_val, 'o', markersize=10,
color=reds(norm(x_val)),
label='Point %s' % i
)
plt.legend(numpoints=1, loc='lower right')
plt.show()
The code should all be fairly self explanatory, but if you want me to go over anything, just shout.
I'm attempting to create a plot with a legend to the side of it using matplotlib. I can see that the plot is being created, but the image bounds do not allow the entire legend to be displayed.
lines = []
ax = plt.subplot(111)
for filename in args:
lines.append(plt.plot(y_axis, x_axis, colors[colorcycle], linestyle='steps-pre', label=filename))
ax.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
This produces:
Eventhough that it is late, I want to refer to a nice recently introduced alternative:
New matplotlib feature: The tight bounding box
If you are interested in the output file of plt.savefig: in this case the flag bbox_inches='tight' is your friend!
import matplotlib.pyplot as plt
fig = plt.figure(1)
plt.plot([1, 2, 3], [1, 0, 1], label='A')
plt.plot([1, 2, 3], [1, 2, 2], label='B')
plt.legend(loc='center left', bbox_to_anchor=(1, 0))
fig.savefig('samplefigure', bbox_inches='tight')
I want to refer also to a more detailed answer: Moving matplotlib legend outside of the axis makes it cutoff by the figure box
Advantages
There is no need to adjust the actual data/picture.
It is compatible with plt.subplots as-well where as the others are not!
It applies at least to the mostly used output files, e.g. png, pdf.
As pointed by Adam, you need to make space on the side of your graph.
If you want to fine tune the needed space, you may want to look at the add_axes method of matplotlib.pyplot.artist.
Below is a rapid example:
import matplotlib.pyplot as plt
import numpy as np
# some data
x = np.arange(0, 10, 0.1)
y1 = np.sin(x)
y2 = np.cos(x)
# plot of the data
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.6, 0.75])
ax.plot(x, y1,'-k', lw=2, label='black sin(x)')
ax.plot(x, y2,'-r', lw=2, label='red cos(x)')
ax.set_xlabel('x', size=22)
ax.set_ylabel('y', size=22)
ax.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
plt.show()
and the resulting image:
Just use plt.tight_layout()
import matplotlib.pyplot as plt
fig = plt.figure(1)
plt.plot([1, 2, 3], [1, 0, 1], label='A')
plt.plot([1, 2, 3], [1, 2, 2], label='B')
plt.legend(loc='center left', bbox_to_anchor=(1, 0))
plt.tight_layout()
This is probably introduced in the newer matplotlib version and neatly does the job.
Here is another way of making space (shrinking an axis):
# get the current axis
ax = plt.gca()
# Shink current axis by 20%
box = ax.get_position()
ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])
where 0.8 scales the width of the axis by 20%. On my win7 64 machine, using a factor greater than 1 will make room for the legend if it's outside the plot.
This code was referenced from: How to put the legend out of the plot
Edit: #gcalmettes posted a better answer.
His solution should probably be used instead of the method shown below.
Nonetheless I'll leave this since it sometimes helps to see different ways of doing things.
As shown in the legend plotting guide, you can make room for another subplot and place the legend there.
import matplotlib.pyplot as plt
ax = plt.subplot(121) # <- with 2 we tell mpl to make room for an extra subplot
ax.plot([1,2,3], color='red', label='thin red line')
ax.plot([1.5,2.5,3.5], color='blue', label='thin blue line')
ax.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
plt.show()
Produces:
Store your legend call instance to a variable. e.g:
rr = sine_curve_plot.legend(loc=(0.0,1.1))
Then, include the bbox_extra_artists, bbox_inches keyword argument to plt.savefig. i.e:
plt.savefig('output.pdf', bbox_inches='tight', bbox_extra_artists=(rr))
bbox_extra_artists accepts an iterable, so you can include as many legend instances into it. The bbox_extra_artists automatically tells plt to cover every extra info passed into bbox_extra_artists.
DISCLAIMER: The loc variable simply defines the position of the legend, you can tweak the values for better flexibility in positioning. Of course, strings like upper left, upper right, etc. are also valid.
I am plotting a double plot with two y-axes. The second axis ax2 is created by twinx. The problem is that the coloring of the second y-axis via yticks is not working anymore. Instead I have to set_color the labels individually. Here is the relevant code:
fig = plt.figure()
fill_between(data[:,0], 0, (data[:,2]), color='yellow')
yticks(arange(0.2,1.2,0.2), ['.2', '.4', '.6', '.8', ' 1'], color='yellow')
ax2 = twinx()
ax2.plot(data[:,0], (data[:,1]), 'green')
yticks(arange(0.1,0.6,0.1), ['.1 ', '.2', '.3', '.4', '.5'], color='green')
# color='green' has no effect here ?!
# instead this is needed:
for t in ax2.yaxis.get_ticklabels(): t.set_color('green')
show()
Resulting in:
This issue only occurs if I set the tick strings.
yticks(arange(0.1,0.6,0.1), ['.1 ', '.2', '.3', '.4', '.5'], color='green')
Omit it, like here
yticks(arange(0.1,0.6,0.1), color='green')
and the coloring works fine.
Is that a bug (could not find any reports to this), a feature (?!) or
am I missing something here? I am using python 2.6.5 with matplotlib 0.99.1.1 on ubuntu.
For whatever it's worth, you code works fine on my system even without the for loop to set the label colors. Just as a reference, here's a stand-alone example trying to follow essentially exactly what you posted:
import matplotlib.pyplot as plt
import numpy as np
# Generate some data
num = 200
x = np.linspace(501, 1200, num)
yellow_data, green_data = np.random.random((2,num))
green_data -= np.linspace(0, 3, yellow_data.size)
# Plot the yellow data
plt.fill_between(x, yellow_data, 0, color='yellow')
plt.yticks([0.0, 0.5, 1.0], color='yellow')
# Plot the green data
ax2 = plt.twinx()
ax2.plot(x, green_data, 'g-')
plt.yticks([-4, -3, -2, -1, 0, 1], color='green')
plt.show()
My guess is that your problem is mostly coming from mixing up references to different objects. I'm guessing that your code is a bit more complex, and that when you call plt.yticks, ax2 is not the current axis. You can test that idea by explicitly calling sca(ax2) (set the current axis to ax2) before calling yticks and see if that changes things.
Generally speaking, it's best to stick to either entirely the matlab-ish state machine interface or the OO interface, and don't mix them too much. (Personally, I prefer just sticking to the OO interface. Use pyplot to set up figure objects and for show, and use the axes methods otherwise. To each his own, though.)
At any rate, with matplotlib >= 1.0, the tick_params function makes this a bit more convenient. (I'm also using plt.subplots here, which is only in >= 1.0, as well.)
import matplotlib.pyplot as plt
import numpy as np
# Generate some data
yellow_data, green_data = np.random.random((2,2000))
yellow_data += np.linspace(0, 3, yellow_data.size)
green_data -= np.linspace(0, 3, yellow_data.size)
# Plot the data
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.plot(yellow_data, 'y-')
ax2.plot(green_data, 'g-')
# Change the axis colors...
ax1.tick_params(axis='y', labelcolor='yellow')
ax2.tick_params(axis='y', labelcolor='green')
plt.show()
The equivalent code for older versions of matplotlib would look more like this:
import matplotlib.pyplot as plt
import numpy as np
# Generate some data
yellow_data, green_data = np.random.random((2,2000))
yellow_data += np.linspace(0, 3, yellow_data.size)
green_data -= np.linspace(0, 3, yellow_data.size)
# Plot the data
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
ax2 = ax1.twinx()
ax1.plot(yellow_data, 'y-')
ax2.plot(green_data, 'g-')
# Change the axis colors...
for ax, color in zip([ax1, ax2], ['yellow', 'green']):
for label in ax.yaxis.get_ticklabels():
label.set_color(color)
plt.show()