Changing a Matplotlib Rectangle to Circle in a Legend - python

Consider the following plotting code:
plt.figure(figsize=(10,6))
for k in range(nruns):
plt.plot(Thing1['Data'][:,k],color='Grey',alpha=0.10)
plt.plot(Thing2[:,1],Thing2[:,4],'ko')
a = plt.Rectangle((0, 0), 1, 1, fc="Grey",alpha=0.50)
b = plt.Rectangle((0, 0), 1, 1, fc="Black", alpha=1.00)
plt.legend([a,b], ["Thing1","Thing2"],loc=2,fontsize='small')
plt.xlabel("Time",fontsize=16)
plt.ylabel("Hijinks",fontsize=16)
plt.show()
I'd really like "b" to be a circle, rather than a rectangle. But I'm rather horrid at matplotlib code, and especially the use of proxy artists. Any chance there's a straightforward way to do this?

You're very close. You just need to use a Line2D artist and set its properties like ususal:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10,6))
fakexy = (0, 0)
a = plt.Rectangle(fakexy, 1, 1, fc="Grey",alpha=0.50)
b = plt.Line2D(fakexy, fakexy, linestyle='none', marker='o', markerfacecolor="Black", alpha=1.00)
ax.legend([a, b], ["Thing1", "Thing2"], loc='upper left', fontsize='small')
ax.set_xlabel("Time", fontsize=16)
ax.set_ylabel("Hijinks", fontsize=16)
I get:

There is a much easier way to do this with newer versions of matplotlib.
from pylab import *
p1 = Rectangle((0, 0), 1, 1, fc="r")
p2 = Circle((0, 0), fc="b")
p3 = plot([10,20],'g--')
legend([p1,p2,p3], ["Red Rectangle","Blue Circle","Green-dash"])
show()
Note this is not my work. This is obtained from Matplotlib, legend with multiple different markers with one label.

Related

framing a pie chart in matplotlib

I am desperately trying to add a "dark" border around this pie chart. I have tried the solutions described in plenty of questions here, but none turned out to add anything. You can find part of the attempts in the code:
import matplotlib.pyplot as plt
from cycler import cycler
plt.rc("axes", prop_cycle=cycler("color", ["darkgray", "gray", "lightgray"])
)
plt.rcParams["axes.edgecolor"] = "0.15"
plt.rcParams["axes.linewidth"] = 1.25
labels = ["lab1", "lab2"]
sizes = [2000, 3000]
def make_autopct(values):
def my_autopct(pct):
total = sum(values)
val = int(round(pct*total/100.0))
s = '{p:.2f}%({v:d}%)'.format(p=pct,v=val)
s = f"${val}_{{\\ {pct:.2f}\%}}$"
return s
return my_autopct
fig, ax = plt.subplots(figsize=(10, 3))
ax.pie(sizes, explode=(0,0.02), labels=labels, autopct=make_autopct(sizes))
ax.set_title("title")
ax.patch.set_edgecolor('black')
ax.patch.set_linewidth('1')
plt.savefig("title.png")
If I've understood your question right possible solution is the following:
# pip install matplotlib
import matplotlib.pyplot as plt
import numpy as np
# set chart style
plt.style.use('_mpl-gallery-nogrid')
# set data
x = [5, 2, 3, 4]
# set colors of segments
colors = plt.get_cmap('GnBu')(np.linspace(0.2, 0.7, len(x)))
# plot
fig, ax = plt.subplots()
ax.pie(x, colors=colors, radius=2,
wedgeprops={"linewidth": 2, "edgecolor": "black", 'antialiased': True}, # << HERE
frame=False, startangle=0, autopct='%.1f%%', pctdistance=0.6)
plt.show()
Below, three possibilities:
add a frame around pie patch:
ax.pie(sizes,
explode=(0,0.02),
labels=labels,
autopct=make_autopct(sizes),
frame=True)
add a border using axes coordinates (0, 0) to (1, 1) with fig.add_artist which draw on the fig object:
rect = pt.Rectangle((-0.1, -0.1), 1.2, 1.2,
fill=False, color="blue", lw=3, zorder=-1
transform=ax.transAxes)
fig.add_artist(rect)
add a border using fig coordinates (0, 0) to (1, 1) with fig.add_artist which draw on the fig object:
rect = pt.Rectangle((0.05, 0.05), .9, .9,
fill=False, ec="red", lw=1, zorder=-1,
transform=fig.transFigure)
fig.add_artist(rect)
Result:
Edit This matplotlib's transformations page explains the different coordinate systems

Transform from data to figure coordinates

Similar to this post, I would like to transform my data coordinates to figure coordinates. Unfortunately, the transformation tutorial doesn't seem to talk about it. So I came up with something analogous to the answer by wilywampa, but for some reason, there is something wrong and I can't figure it out:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import ConnectionPatch
t = [
0, 6.297, 39.988, 46.288, 79.989, 86.298, 120.005, 126.314, 159.994,
166.295, 200.012, 206.314, 240.005, 246.301, 280.05, 286.35, 320.032,
326.336, 360.045, 366.345, 480.971, 493.146, 1080.117, 1093.154, 1681.019,
1692.266, 2281.008, 2293.146, 2881.014, 2893.178, 3480.988, 3493.149,
4080.077, 4092.298, 4681.007, 4693.275, 5281.003, 5293.183, 5881.023,
5893.188, 6481.002, 6492.31
]
y = np.zeros(len(t))
fig, (axA, axB) = plt.subplots(2, 1)
fig.tight_layout()
for ax in (axA, axB):
ax.set_frame_on(False)
ax.axes.get_yaxis().set_visible(False)
axA.plot(t[:22], y[:22], c='black')
axA.plot(t[:22], y[:22], 'o', c='#ff4500')
axA.set_ylim((-0.05, 1))
axB.plot(t, y, c='black')
axB.plot(t, y, 'o', c='#ff4500')
axB.set_ylim((-0.05, 1))
pos1 = axB.get_position()
pos2 = [pos1.x0, pos1.y0 + 0.3, pos1.width, pos1.height]
axB.set_position(pos2)
trans = [
# (ax.transAxes + ax.transData.inverted()).inverted().transform for ax in
(fig.transFigure + ax.transData.inverted()).inverted().transform for ax in
(axA, axB)
]
con1 = ConnectionPatch(
xyA=trans[0]((0, 0)), xyB=(0, 0.1), coordsA="figure fraction",
coordsB="data", axesA=axA, axesB=axB, color="black"
)
con2 = ConnectionPatch(
xyA=(500, 0), xyB=(500, 0.1), coordsA="data", coordsB="data",
axesA=axA, axesB=axB, color="black"
)
print(trans[0]((0, 0)))
axB.add_artist(con1)
axB.add_artist(con2)
plt.show()
The line on the left is supposed to go to (0, 0) of the upper axis, but it doesn't. The same happens btw if I try to convert to axes coordinates, so there seems be to something fundamentally wrong.
The reason why I want to use figure coords is because I don't actually want the line to end at (0, 0), but slightly below the '0' tick label. I cannot do that in data coords so I tried to swap to figure coods.
Adapting the second example from this tutorial code, it seems no special combinations of transforms is needed. You can use coordsA=axA.get_xaxis_transform(), if x is in data coordinates and y in figure coordinates. Or coordsA=axA.transData if x and y are both in data coordinates. Note that when using data coordinates you are allowed to give coordinates outside the view window; by default a ConnectionPatch isn't clipped.
The following code uses z-order to put the connection lines behind the rest and adds a semi-transparent background to the tick labels of axA (avoiding that the text gets crossed out by the connection line):
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import ConnectionPatch
t = [0, 6.297, 39.988, 46.288, 79.989, 86.298, 120.005, 126.314, 159.994, 166.295, 200.012, 206.314, 240.005, 246.301, 280.05, 286.35, 320.032, 326.336, 360.045, 366.345, 480.971, 493.146, 1080.117, 1093.154, 1681.019, 1692.266, 2281.008, 2293.146, 2881.014, 2893.178, 3480.988, 3493.149, 4080.077, 4092.298, 4681.007, 4693.275, 5281.003, 5293.183, 5881.023, 5893.188, 6481.002, 6492.31]
y = np.zeros(len(t))
fig, (axA, axB) = plt.subplots(2, 1)
fig.tight_layout()
for ax in (axA, axB):
ax.set_frame_on(False)
ax.axes.get_yaxis().set_visible(False)
axA.plot(t[:22], y[:22], c='black')
axA.plot(t[:22], y[:22], 'o', c='#ff4500')
axA.set_ylim((-0.05, 1))
axB.plot(t, y, c='black')
axB.plot(t, y, 'o', c='#ff4500')
axB.set_ylim((-0.05, 1))
pos1 = axB.get_position()
pos2 = [pos1.x0, pos1.y0 + 0.3, pos1.width, pos1.height]
axB.set_position(pos2)
con1 = ConnectionPatch(xyA=(0, 0.02), coordsA=axA.get_xaxis_transform(),
xyB=(0, 0.05), coordsB=axB.get_xaxis_transform(),
# linestyle='--', color='black', zorder=-1)
linestyle='--', color='darkgrey', zorder=-1)
con2 = ConnectionPatch(xyA=(500, 0.02), coordsA=axA.get_xaxis_transform(),
xyB=(500, 0.05), coordsB=axB.get_xaxis_transform(),
linestyle='--', color='darkgrey', zorder=-1)
fig.add_artist(con1)
fig.add_artist(con2)
for lbl in axA.get_xticklabels():
lbl.set_backgroundcolor((1, 1, 1, 0.8))
plt.show()
Possible answer to your last comment:
As you're dealing with figure coords, these can change depending on your screen resolution. So if your other machine has a different res then this could be why its changing. You'll have to look into using Axes coords instead if you don't want these random changes.

ConnectionPath gets hidden behind subplot

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()

Rotate x axis labels in Matplotlib parasite plot

After Thomas very helpfully fixed my issues making two parasite sub plots in this question, I'm now trying to rotate the x axis labels on the subplots.
Unfortunately, my modification to the example code here seems to have no effect on the x axis labels:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
host = host_subplot(111, axes_class=AA.Axes)
plt.subplots_adjust(right=0.75)
par1 = host.twinx()
par2 = host.twinx()
offset = 60
new_fixed_axis = par2.get_grid_helper().new_fixed_axis
par2.axis["right"] = new_fixed_axis(loc="right",
axes=par2,
offset=(offset, 0))
par1.axis["right"].toggle(all=True)
par2.axis["right"].toggle(all=True)
host.set_xlim(0, 2)
host.set_ylim(0, 2)
host.set_xlabel("Distance")
host.set_ylabel("Density")
par1.set_ylabel("Temperature")
par2.set_ylabel("Velocity")
p1, = host.plot([0, 1, 2], [0, 1, 2], label="Density")
p2, = par1.plot([0, 1, 2], [0, 3, 2], label="Temperature")
p3, = par2.plot([0, 1, 2], [50, 30, 15], label="Velocity")
par1.set_ylim(0, 4)
par2.set_ylim(1, 65)
host.legend()
host.axis["left"].label.set_color(p1.get_color())
par1.axis["right"].label.set_color(p2.get_color())
par2.axis["right"].label.set_color(p3.get_color())
plt.xticks(rotation = 45) #<- The only change from the example
plt.draw()
plt.show()
gives un-rotated x axis labels in:
Although I've shown the plt.xticks(rotation = 45), I've also tried other ways that work with "conventional" matplotlib plots without success. Does anyone know if there is a way to do this, or am I just dealing with too much of a niche case? Maybe I should just figure out a way to live with using sub plots and no parasite axes?
Thanks a lot,
Alex
There are two ways to produce parasite axes:
Using the mpl_toolkits axisartist and axes_grid1 package, examples are
demo_parasite_axes
demo_parasite_axes2
this stackoverflow answer
Using usual subplots and their spines. Examples are
multiple_yaxis_with_spines
this stackoverflow answer
Here you are using the first approach, which may be a bit unintuitive due to it using the special axes provided by the axisartist toolkit.
Solution 1:
Use the usual subplots approach for which all the usual ways of rotating ticklabels work just fine, e.g.
plt.setp(ax.get_xticklabels(), rotation=90)
Solution 2:
In case you want to stick with the mpl_toolkits approach you need to obtain the ticklabels from the axis via axis["right"].major_ticklabels,
plt.setp(par2.axis["bottom"].major_ticklabels, rotation=-135)

Scatterplot in matplotlib with legend and randomized point order

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()

Categories