I want to mark a line over two aligned subplots. Therefore, I use matplotlib.patches.ConnectionPatch as suggested in other answers. It worked already in other examples, but here for the second time, the line just is cut off at the second plot area.
How do I assure that the ConnectionPatch is plotted in the front?
I tried playing around with zorder, but did not find a solution yet.
from matplotlib.patches import ConnectionPatch
import matplotlib.pyplot as plt
xes=[-2, 0, 2]
field=[0, -10, 0]
potential=[-20, 0, 20]
fig, axs = plt.subplots(2, 1, sharex=True)
axs[0].plot(xes, field)
axs[1].plot(xes, potential)
# line over both plots
_, ytop = axs[0].get_ylim()
ybot, _ = axs[1].get_ylim()
n_p_border = ConnectionPatch(xyA=(0., ytop), xyB=(0., ybot),
coordsA='data', coordsB='data',
axesA=axs[0], axesB=axs[1], lw=3)
print(n_p_border)
axs[0].add_artist(n_p_border)
You would need to inverse the role of the two axes. This is also shown in Drawing lines between two plots in Matplotlib.
from matplotlib.patches import ConnectionPatch
import matplotlib.pyplot as plt
xes=[-2, 0, 2]
field=[0, -10, 0]
potential=[-20, 0, 20]
fig, axs = plt.subplots(2, 1, sharex=True)
axs[0].plot(xes, field)
axs[1].plot(xes, potential)
# line over both plots
_, ytop = axs[0].get_ylim()
ybot, _ = axs[1].get_ylim()
n_p_border = ConnectionPatch(xyA=(0., ybot), xyB=(0., ytop),
coordsA='data', coordsB='data',
axesA=axs[1], axesB=axs[0], lw=3)
axs[1].add_artist(n_p_border)
plt.show()
Related
these are my codes, mostly taken from the example here https://matplotlib.org/2.0.2/examples/axes_grid/demo_parasite_axes2.html
the output graph is shown below
from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
sns.set()
import numpy as np
host = host_subplot(111, axes_class=AA.Axes)
plt.subplots_adjust(left=.15)
par1 = host.twinx()
par2 = host.twinx()
host.tick_params(width=0)
par1.tick_params(width=0)
par2.tick_params(width=0)
offset = -60
new_fixed_axis = par2.get_grid_helper().new_fixed_axis
par2.axis["left"] = new_fixed_axis(loc="left", axes=par2,offset=(offset, 0))
par2.set_ylabel("V3")
par2.axis["left"].toggle(all=True)
xx = np.random.randint(1,10,100)
yy = np.random.randint(-100,10,100)
p1 = host.scatter(xx,yy)
ax = plt.gca()
ax.invert_yaxis()
rolling_period = 3
plt.xlabel('V2')
plt.ylabel('V1')
x1, y1 = [250, 0], [0, 0]
x2, y2 = [0, 0], [-30, 0]
plt.plot(x1,y1,x2,y2,color='black')
plt.xlim([-50, 250])
plt.ylim([0, -30])
plt.tick_params(axis='x',which='both',bottom=False,top=False)
p2, = par1.plot([0, 1, 2], [0, 3, 2], linestyle = 'None', label="V3")
plt.draw()
plt.show()
as you can see if I use seaborn I cant see the axis line for the parasite axis (the additional y axis on the left)
however if I remove the seaborn I do see that, but I do need to use seaborn for visualisation purpose so how do I fix the problem?
the graph without seaborn
Seaborn sets axis line colors to white. Just reset it to black by:
par2.axis['left'].line.set_ec((0, 0, 0, 1))
I'm plotting a large number of data points with errors using Matplotlib (version 2.2.5), and I'm rasterizing the data because there are a few thousand data points. I've found that when I rasterize the data and save as a PDF, however, the error bars produce an ugly white outline that isn't acceptable for publication. I've constructed a MWE that shows the problem:
import numpy as np
import random as rand
import matplotlib.pyplot as plt
rand.seed(10)
seeds = range(0, 1000)
data = np.empty((len(seeds), 2))
for n in seeds:
data[n, 0] = rand.gauss(1, 0.01)
data[n, 1] = rand.gauss(1, 0.01)
fig, ax = plt.subplots(1, 1, figsize=(6, 6))
ax.scatter(data[:, 0], data[:, 1], s=10, facecolors="k", rasterized=True, zorder=1)
ax.errorbar(data[:, 0], data[:, 1], xerr=0.01, yerr=0.01, color="k", fmt="none", rasterized=True, zorder=2)
fig.savefig("Test.pdf", dpi=250)
This looks fine in the Jupyter Notebook output, and also as a saved PNG file. The output PDF file, however, looks like this:
How do I get rid of that white fuzz caused by the error bars? If I don't rasterize, the problem vanishes, but then the file takes annoyingly long to load in my paper, and the last thing I want to do is annoy my reader.
I found the solution thanks to an older question: I needed to add ax.set_rasterization_zorder(0) to the code and change the zorder of the plotted points to be below 0. This produced a perfect graph that has no ugly outlines of the data and retains a vectorized axis, exactly what I wanted. The working code is:
import numpy as np
import random as rand
import matplotlib.pyplot as plt
rand.seed(10)
seeds = range(0, 1000)
data = np.empty((len(seeds), 2))
for n in seeds:
data[n, 0] = rand.gauss(1, 0.01)
data[n, 1] = rand.gauss(1, 0.01)
fig, ax = plt.subplots(1, 1, figsize=(6, 6))
ax.scatter(data[:, 0], data[:, 1], s=10, facecolors="k", rasterized=True, zorder=-2)
ax.errorbar(data[:, 0], data[:, 1], xerr=0.01, yerr=0.01, color="k", fmt="none", rasterized=True, zorder=-1)
ax.set_rasterization_zorder(0)
fig.savefig("Test.pdf", dpi=250)
and the output is:
I'm trying to build a scatterplot of a large amount of data from multiple classes in python/matplotlib. Unfortunately, it appears that I have to choose between having my data randomised and having legend labels. Is there a way I can have both (preferably without manually coding the labels?)
Minimum reproducible example:
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
X = np.random.normal(0, 1, [5000, 2])
Y = np.random.normal(0.5, 1, [5000, 2])
data = np.concatenate([X,Y])
classes = np.concatenate([np.repeat('X', X.shape[0]),
np.repeat('Y', Y.shape[0])])
Plotting with randomized points:
plot_idx = np.random.permutation(data.shape[0])
colors = pd.factorize(classes)
fig, ax = plt.subplots()
ax.scatter(data[plot_idx, 0],
data[plot_idx, 1],
c=colors[plot_idx],
label=classes[plot_idx],
alpha=0.4)
plt.legend()
plt.show()
This gives me the wrong legend.
Plotting with the correct legend:
from matplotlib import cm
unique_classes = np.unique(classes)
colors = cm.Set1(np.linspace(0, 1, len(unique_classes)))
for i, class in enumerate(unique_classes):
ax.scatter(data[classes == class, 0],
data[classes == class, 1],
c=colors[i],
label=class,
alpha=0.4)
plt.legend()
plt.show()
But now the points are not randomized and the resulting plot is not representative of the data.
I'm looking for something that would give me a result like I get as follows in R:
library(ggplot2)
X <- matrix(rnorm(10000, 0, 1), ncol=2)
Y <- matrix(rnorm(10000, 0.5, 1), ncol=2)
data <- as.data.frame(rbind(X, Y))
data$classes <- rep(c('X', 'Y'), times=nrow(X))
plot_idx <- sample(nrow(data))
ggplot(data[plot_idx,], aes(x=V1, y=V2, color=classes)) +
geom_point(alpha=0.4, size=3)
You need to create the legend manually. This is not a big problem though. You can loop over the labels and create a legend entry for each. Here one may use a Line2D with a marker similar to the scatter as handle.
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
X = np.random.normal(0, 1, [5000, 2])
Y = np.random.normal(0.5, 1, [5000, 2])
data = np.concatenate([X,Y])
classes = np.concatenate([np.repeat('X', X.shape[0]),
np.repeat('Y', Y.shape[0])])
plot_idx = np.random.permutation(data.shape[0])
colors,labels = pd.factorize(classes)
fig, ax = plt.subplots()
sc = ax.scatter(data[plot_idx, 0],
data[plot_idx, 1],
c=colors[plot_idx],
alpha=0.4)
h = lambda c: plt.Line2D([],[],color=c, ls="",marker="o")
plt.legend(handles=[h(sc.cmap(sc.norm(i))) for i in range(len(labels))],
labels=list(labels))
plt.show()
Alternatively you can use a special scatter handler, as shown in the quesiton Why doesn't the color of the points in a scatter plot match the color of the points in the corresponding legend? but that seems a bit overkill here.
It's a bit of a hack, but you can save the axis limits, set the labels by drawing points well outside the limits of the plot, and then resetting the axis limits as follows:
plot_idx = np.random.permutation(data.shape[0])
color_idx, unique_classes = pd.factorize(classes)
colors = cm.Set1(np.linspace(0, 1, len(unique_classes)))
fig, ax = plt.subplots()
ax.scatter(data[plot_idx, 0],
data[plot_idx, 1],
c=colors[color_idx[plot_idx]],
alpha=0.4)
xlim = ax.get_xlim()
ylim = ax.get_ylim()
for i in range(len(unique_classes)):
ax.scatter(xlim[1]*10,
ylim[1]*10,
c=colors[i],
label=unique_classes[i])
ax.set_xlim(xlim)
ax.set_ylim(ylim)
plt.legend()
plt.show()
I'm drawing a polygon in matplotlib and exporting it in .svg so I can use it in eg. Inkscape. I know you can specify the image size in inches, but I'd like to have a line with length "1" to be mapped to a line with length 1 inch when opened in Inkscape.
import matplotlib.pyplot as plt
plt.close('all')
f,ax = plt.subplots(figsize=(3,3))
plt.plot([0,1],[0,0])
ax.axis('off')
plt.gca().set_position([0, 0, 1, 1])
ax.set_aspect('equal')
plt.savefig("line.svg")
It is unclear what ax.set_aspect('equal') would do here. So I'd remove that.
Next, what you need to do is to have the axis coordinate system synchronized to the figure size.
At this point it's unclear from the question what exactly you are trying to do.
Create a 3 inch figure with a 3 inch long line
Let the coordinate system go from 0 to 1, remove any margin inside the axes.
import matplotlib.pyplot as plt
f,ax = plt.subplots(figsize=(3,3))
plt.plot([0,1],[0,0])
ax.axis('off')
plt.gca().set_position([0, 0, 1, 1])
ax.margins(0)
plt.savefig("line.svg")
or let the coordinate system go from 0 to 3, make the line 3 units long.
import matplotlib.pyplot as plt
f,ax = plt.subplots(figsize=(3,3))
plt.plot([0,3],[0,0])
ax.axis('off')
plt.gca().set_position([0, 0, 1, 1])
ax.set_xlim(0,3)
plt.savefig("line.svg")
Create a 3 inch figure with a 1 inch long line
Let the coordinate system go from 0 to 1, create a line of 1/3 units in length
import matplotlib.pyplot as plt
f,ax = plt.subplots(figsize=(3,3))
plt.plot([0,1/.3],[0,0])
ax.axis('off')
plt.gca().set_position([0, 0, 1, 1])
ax.set_xlim(0,1)
plt.savefig("line.svg")
or let the coordinate system go from 0 to 3 and create a line of 1 unit length.
import matplotlib.pyplot as plt
f,ax = plt.subplots(figsize=(3,3))
plt.plot([0,1],[0,0])
ax.axis('off')
plt.gca().set_position([0, 0, 1, 1])
ax.set_xlim(0,3)
plt.savefig("line.svg")
This last approach seems to be the most intuitive one, but since it's not too clear from the question how the size in inches of the figure should relate to the axes units, I provided all possible solutions.
In matplotlib, I would like draw an filled arc which looks like this:
The following code results in an unfilled line arc:
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
fg, ax = plt.subplots(1, 1)
pac = mpatches.Arc([0, -2.5], 5, 5, angle=0, theta1=45, theta2=135)
ax.add_patch(pac)
ax.axis([-2, 2, -2, 2])
ax.set_aspect("equal")
fg.canvas.draw()
The documentation says that filled arcs are not possible.
What would be the best way to draw one?
#jeanrjc's solution almost gets you there, but it adds a completely unnecessary white triangle, which will hide other objects as well (see figure below, version 1).
This is a simpler approach, which only adds a polygon of the arc:
Basically we create a series of points (points) along the edge of the circle (from theta1 to theta2). This is already enough, as we can set the close flag in the Polygon constructor which will add the line from the last to the first point (creating a closed arc).
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np
def arc_patch(center, radius, theta1, theta2, ax=None, resolution=50, **kwargs):
# make sure ax is not empty
if ax is None:
ax = plt.gca()
# generate the points
theta = np.linspace(np.radians(theta1), np.radians(theta2), resolution)
points = np.vstack((radius*np.cos(theta) + center[0],
radius*np.sin(theta) + center[1]))
# build the polygon and add it to the axes
poly = mpatches.Polygon(points.T, closed=True, **kwargs)
ax.add_patch(poly)
return poly
And then we apply it:
fig, ax = plt.subplots(1,2)
# #jeanrjc solution, which might hide other objects in your plot
ax[0].plot([-1,1],[1,-1], 'r', zorder = -10)
filled_arc((0.,0.3), 1, 90, 180, ax[0], 'blue')
ax[0].set_title('version 1')
# simpler approach, which really is just the arc
ax[1].plot([-1,1],[1,-1], 'r', zorder = -10)
arc_patch((0.,0.3), 1, 90, 180, ax=ax[1], fill=True, color='blue')
ax[1].set_title('version 2')
# axis settings
for a in ax:
a.set_aspect('equal')
a.set_xlim(-1.5, 1.5)
a.set_ylim(-1.5, 1.5)
plt.show()
Result (version 2):
You can use fill_between to achieve this
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np
fg, ax = plt.subplots(1, 1)
r=2.
yoff=-1
x=np.arange(-1.,1.05,0.05)
y=np.sqrt(r-x**2)+yoff
ax.fill_between(x,y,0)
ax.axis([-2, 2, -2, 2])
ax.set_aspect("equal")
fg.canvas.draw()
Play around with r and yoff to move the arc
EDIT:
OK, so you want to be able to plot arbitrary angles? You just need to find the equation of the chord, rather than using a flat line like above. Here's a function to do just that:
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np
fg, ax = plt.subplots(1, 1)
col='rgbkmcyk'
def filled_arc(center,r,theta1,theta2):
# Range of angles
phi=np.linspace(theta1,theta2,100)
# x values
x=center[0]+r*np.sin(np.radians(phi))
# y values. need to correct for negative values in range theta=90--270
yy = np.sqrt(r-x**2)
yy = [-yy[i] if phi[i] > 90 and phi[i] < 270 else yy[i] for i in range(len(yy))]
y = center[1] + np.array(yy)
# Equation of the chord
m=(y[-1]-y[0])/(x[-1]-x[0])
c=y[0]-m*x[0]
y2=m*x+c
# Plot the filled arc
ax.fill_between(x,y,y2,color=col[theta1/45])
# Lets plot a whole range of arcs
for i in [0,45,90,135,180,225,270,315]:
filled_arc([0,0],1,i,i+45)
ax.axis([-2, 2, -2, 2])
ax.set_aspect("equal")
fg.savefig('filled_arc.png')
And here's the output:
Here's a simpler workaround. Use the hatch argument in your mpatches.Arc command. If you repeat symbols with the hatch argument it increases the density of the patterning. I find that if you use 6 dashes, '-', or 6 dots, '.' (others probably also work), then it solidly fills in the arc as desired. When I run this
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
plt.axes()
pac = mpatches.Arc([0, -2.5], 5, 5, 45, theta1=45, theta2=135, hatch = '......')
plt.gca().add_patch(pac)
pac.set_color('cyan')
plt.axis('equal')
plt.show()
I get this:
Arc filled with dense dot hatch and rotated 45 degrees just for show
You can draw a wedge, and then hide part of it with a triangle:
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np
def filled_arc(center, radius, theta1, theta2, ax, color):
circ = mpatches.Wedge(center, radius, theta1, theta2, fill=True, color=color)
pt1 = (radius * (np.cos(theta1*np.pi/180.)) + center[0],
radius * (np.sin(theta1*np.pi/180.)) + center[1])
pt2 = (radius * (np.cos(theta2*np.pi/180.)) + center[0],
radius * (np.sin(theta2*np.pi/180.)) + center[1])
pt3 = center
pol = mpatches.Polygon([pt1, pt2, pt3], color=ax.get_axis_bgcolor(),
ec=ax.get_axis_bgcolor(), lw=2 )
ax.add_patch(circ)
ax.add_patch(pol)
and then you can call it:
fig, ax = plt.subplots(1,2)
filled_arc((0,0), 1, 45, 135, ax[0], "blue")
filled_arc((0,0), 1, 0, 40, ax[1], "blue")
and you get:
or:
fig, ax = plt.subplots(1, 1)
for i in range(0,360,45):
filled_arc((0,0), 1, i, i+45, ax, plt.cm.jet(i))
and you get:
HTH
The command ax.get_axis_bgcolor() needs to be replaced by ax.get_fc() for newer matplotlib.