I'm creating an animation using Matplotlib and I'm using a Fill object. I'd like to be able to change the fill data from a function. For other plot types there's usually a set_data() function or set_offsets(). Fill doesn't seem to have one. I would expect to do something like the code below but this doesn't work. Any ideas?
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.axes(xlim=(-1, 1), ylim=(-1, 1),)
triangle, = ax.fill([0,1,1],[0,0,1])
# I want to change the datapoints later in my code, like this:
triangle.set_data([0,2,2],[0,0,2])
This is not particularly well documented, but Polygon objects have a pair of methods get_xy and set_xy. The method get_xy() returns an ndarray with shape (N + 1, 2). The extra point is a repeat of the first point (so that the polygon is closed). Putting the desired data into the expected format (which is what is specified in the documentation to construct a Polygon object):
triangle.set_xy(np.array([[0, 2, 2, 0],[0, 0, 2, 0]]).T)
ax = plt.gca()
ax.set_xlim([0, 3])
ax.set_ylim([0, 3])
plt.draw()
This even lets you change the number of vertices in your patch.
Added issue to clarify the documentation https://github.com/matplotlib/matplotlib/issues/3035.
Related
I'd like to plot a line that goes to infinity, but starting from a finite point. For simplicity, let's say that the line can be horizontal. I would like to plot a line from (0, 0) to (inf, 0).
Using hlines:
>>> fig, ax = plt.subplots()
>>> ax.hlines(0, 0, np.inf)
.../python3.8/site-packages/matplotlib/axes/_base.py:2480: UserWarning: Warning: converting a masked element to nan.
xys = np.asarray(xys)
The result is an empty plot.
axhline has a starting parameter, but it is in axis coordinates rather than data. Similar problem for axline. Is there a way to plot a (horizontal) line with one end in data coordinates and the other at infinity?
The motivation behind this is that I'd like to be able to plot some cumulative probabilities without setting data past the last bin to zero, as here: Matplotlib cumulative histogram - vertical line placement bug or misinterpretation?. Rather than simply ending the histogram, I'd like to be able to extend the line from the last bin to infinity at y=1.0.
There's no built-in function for this, but you can re-draw the line to the axis limit on each change of the x limits.
From Axes:
The events you can connect to are 'xlim_changed' and 'ylim_changed'
and the callback will be called with func(ax) where ax is the Axes
instance.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
def hline_to_inf(ax, x, y):
line = ax.hlines(0, 0, ax.get_xlim()[1])
ax.callbacks.connect('xlim_changed',
lambda ax: line.set_paths([[[x, y], [ax.get_xlim()[1], y]]]))
hline_to_inf(ax, 0, 0)
plt.show()
Part of the issue is that normal plotting methods apply the same transform to the input data. What is required here is to apply a data transform to the start point, and a blended transform to the endpoint. It seems that there may be an answer using existing tools with ConnectionPatch, as explained in the Annotations Guide. The idea is to make the left point use data coordinates and the right point have a blended transform with x in axes coordinates and y in data.
from matplotlib import pyplot as plt
from matplotlib.patches import ConnectionPatch
fig, ax = plt.subplots()
line, = ax.plot([1, 2], [1, 2])
ax.add_artist(ConnectionPatch([2, 2], [1, 2], coordsA=ax.transData, coordsB=ax.get_yaxis_transform(), color=line.get_color(), linewidth=line.get_linewidth(), clip_on=True))
Turning on clipping is necessary, otherwise you could end up with artifacts that look like this:
Following this I know that I can extract the xticks labels and positions using:
import matplotlib.pyplot as plt
plt.scatter(x_data, y_data)
locs, labels=plt.xticks()
the new variable labels is a matplotlib.cbook.silent_list, which
doesn't behave like a normal list.
Is there a way to access and modify any attribute value of the labels elements?
Specifically I would like to know if I can select a subset of the labels (i.e. slice the silent_list) and modify a particular attribute for that subset.
Here is a toy example:
import numpy as np
import matplotlib.pyplot as plt
x=np.array([1,2,3,4,5,6,7,8])
y=np.random.normal(0, 1, (8, 1))
plt.scatter(x, y)
locs, labels=plt.xticks()
As an example, let say I want to change the labels color to red for all but the first and last element of labels; if I open one of the elements of the variable I can see that there is the attribute _color with value k, which I would like to change in r:
I tried to slice it:
labels[1:-1]
But it returns:
Out[]: [Text(2,0,'2'), Text(4,0,'4'), Text(6,0,'6'), Text(8,0,'8')]
and this is as far as I managed to go.
I couldn't figure out a way to access the attribute and change its value.
NB: I am looking for a general way to access these attributes and change the value, I do not care about changing the labels color specifically. That's just an example.
You might be interested in an alternative solution where you can choose which specific ticks you want to color. Here I have to loop from [1:-1] because the first and the last ticks do not appear on the graph here but they appear in the labels
import numpy as np; np.random.seed(134)
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x=np.array([1,2,3,4,5,6,7,8])
y=np.random.normal(0, 1, (8, 1))
plt.scatter(x, y)
fig.canvas.draw()
xticks = ax.get_xticklabels()
target_ticks = [1, 3, 6, len(xticks)-2]
for i, lab in enumerate(xticks[1:-1]):
if i+1 in target_ticks:
lab.set_color('r')
I am using QuTiP for the Bloch sphere plotting in Python. If I have several points on the Bloch sphere then I can connect them with a line using the command
b.add_points(pnts,meth='l')|
I wanted to know how can I change the linewidth of the line connecting these points.
There isn't a direct way to do this, since by default no linewidth parameter is passed while making this plot, but you can always plot the lines manually.
The points need to be passed in as a list of numpy.ndarray objects.
The only catch is that to be consistent with what the Bloch class does, you need to make sure that the convention you are using to define the points is the same. It seems like the l method will only plot an connect the first three points that you feed in.
The following script reproduces this behaviour using a function that is similar to the one defined in Bloch:
import matplotlib.pyplot as plt
import qutip
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
pts = [np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]])]
fig, ax = plt.subplots(figsize=(5, 5), subplot_kw=dict(projection='3d'))
ax.axis('square')
b = qutip.Bloch(fig=fig, axes=ax)
for p in pts:
b.axes.plot(p[1], -p[0], p[2],
alpha=1, zdir='z', color='r',
linewidth=5)
b.render(fig=fig, axes=ax)
plt.show()
The output figure is here:
There are several related questions (here, here, and here), but the suggested solutions don't work in my case.
I'm creating subplots iteratively, so I don't know ahead of time the width of each one (it gets calculated AFTER plt.subplots() is called), which means I can't set the size of each subplot when I initially create them.
I would like to set the size of the subplot x axis after it has already been created.
Imagine something like:
items = [A,B,C] #this could have any number of items in it
f,ax = plt.subplots(len(items),1, figsize=(10,10)) #figsize is arbitrary and could be anything
for i in range(len(items)):
#calculate x and y data for current item
#calculate width of x axis for current item
plt.sca(ax[i])
cax = plt.gca()
cax.plot(x,y)
#here is where I would like to set the x axis size
#something like cax.set_xlim(), but for the size, not the limit
Note 1: The units don't matter, but the relative size does, so it could be size in pixels, or centimeters, or even a ratio calculated based on the relative widths.
Note 2: The width of the x axis is NOT related in this case to the x limit, so I can't just set the x limit and expect the axis to scale correctly.
Also, I'm trying to keep this code short, since it's to be shared with people unfamiliar with Python, so if the only solution involves adding a bunch of lines, it's not worth it and I'll live with incorrectly scaled axes. This is an aesthetic preference but not a requirement.
Thanks!
EDIT: Here's what I'm aiming for
You can create a new GridSpec specifying the height_ratios and then updating each axs position:
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
# create figure
f, ax = plt.subplots(3, 1, figsize=(10,10))
# plot some data
ax[0].plot([1, 2, 3])
ax[1].plot([1, 0, 1])
ax[2].plot([1, 2, 20])
# adjust subplot sizes
gs = GridSpec(3, 1, height_ratios=[5, 2, 1])
for i in range(3):
ax[i].set_position(gs[i].get_position(f))
plt.show()
I asked a similar question before here. The use case was slightly different, but it might still be helpful.
Surely now you got the answer or this problem is deprecated but if someone else is searching, I solved this problem using "Bbox". The idea is something like this:
from matplotlib.transforms import Bbox
fig, ax = plt.subplots(3,1, figsize = (11,15))
ax[0].set_position(Bbox([[0.125, 0.6579411764705883], [0.745, 0.88]]))
ax[2].set_position(Bbox([[0.125, 0.125], [0.745, 0.34705882352941175]]))
For more information, check https://matplotlib.org/api/transformations.html#matplotlib.transforms.Bbox
I'm doing simulations where multiple variables vary in time. Occasionally, it is useful to plot variables not against the time axis (x(t) versus t) but against each other (x(t) versus y(t)).
In these cases, it'd be nice if I could add some sort of arrows (overlaid on the curve) indicating the direction of time flow.
My question: does anybody know of an easy or built-in method to do this, or should I hack something together myself?
Try this (from the matplotlib cookbook http://www.scipy.org/Cookbook/Matplotlib/Arrows):
from pylab import *
from numarray import *
x = arange(10)
y = x
# Plot junk and then a filled region
plot(x, y)
# Now lets make an arrow object
arr = Arrow(2, 2, 1, 1, edgecolor='white')
# Get the subplot that we are currently working on
ax = gca()
# Now add the arrow
ax.add_patch(arr)
# We should be able to make modifications to the arrow.
# Lets make it green.
arr.set_facecolor('g')