Add a matplotlib legend that covers the lines of the twinx axis - python

I have this python code.
it twinx the axis ax and plots some function on both axis
I plot the legend on ax1
The problem is that the legend is not covering the curves of ax2
It is possible to automatically positioning the legend on ax by covering the lines of ax2.
Note that in fig.legend the option loc="best" is not available.
And I need the automatic positioning inside the area of the plot.
Tnx
import matplotlib.pyplot as plt
import numpy as np
# Set the x values for the sine and cosine functions
x = np.linspace(0, 2*np.pi, 100)
# Create the figure and an axis
fig, ax = plt.subplots()
ax2 = ax.twinx()
# Plot the sine and cosine functions on the axis
ax.plot(x, np.sin(x), label='Sine')
ax.plot(x, np.cos(x), label='Cosine')
ax2.plot(x, np.cos(x+1), label='Cosine 2', color="red")
ax2.plot(x, x, label='Cosine 2', color="green")
# Add a title and labels to the axis
ax.set_title('Sine and Cosine Functions')
ax.set_xlabel('X')
ax.set_ylabel('Y')
# Get the line legends from the axis
lines, labels = ax.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
# Add a legend to the figure
ax.legend(lines + lines2, labels + labels2, framealpha=1.0)
ax.get_legend().set_zorder(10)
# Display the plot
plt.show()
Bellow is the output of the code:

References:
https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.legend.html
https://matplotlib.org/stable/gallery/misc/zorder_demo.html
The zorder of a legend is highest by default, so you don't need to modify it if you want the legend on the top of everything.
Option 1:
You can accomplish this in terms of a Figure object instead of an Axes object with plt.legend(lines + lines2, labels + labels2, framealpha=1.0, loc='lower left').
Option 2:
Or you can set the legend on ax2 instead of ax with ax2.legend(lines + lines2, labels + labels2, framealpha=1.0, loc='lower left'). This returns the same result as in option 1.
import matplotlib.pyplot as plt
import numpy as np
# Set the x values for the sine and cosine functions
x = np.linspace(0, 2 * np.pi, 100)
# Create the figure and an axis
fig, ax = plt.subplots()
ax2 = ax.twinx()
# Plot the sine and cosine functions on the axis
ax.plot(x, np.sin(x), label='Sine')
ax.plot(x, np.cos(x), label='Cosine')
ax2.plot(x, np.cos(x + 1), label='Cosine 2', color="red")
ax2.plot(x, x, label='Cosine 2', color="green")
# Add a title and labels to the axis
ax.set_title('Sine and Cosine Functions')
ax.set_xlabel('X')
ax.set_ylabel('Y')
# Get the line legends from the axis
lines, labels = ax.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
# Add a legend to the figure
plt.legend(lines + lines2, labels + labels2, framealpha=1.0, loc='lower left')
# Display the plot
plt.show()
Results (loc='lower left' and loc='lower center'):
Updated:
Reference: https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.twinx.html
With option 2 in my previous answer, I edited line 36 to ax2.legend(lines + lines2, labels + labels2, framealpha=1.0, loc='lower right') and line 43 to ax22.legend(lines1 + lines22, labels1 + labels22, framealpha=1.0, loc='lower left') in your newly provided code.
If you have multiple subfigures, using Axes objects are much more flexible.
It is now working correctly:

Thanks for the answer.
But my problem is more complex: I need to do this job in tiled subplots where every plot area is twinx.
By using the plt.label it seems to be impossible to select the plot-tile where to put the legend.
With the code below the problem is there. The lines of the twined are not covered by the legend.
Any suggestion?
import matplotlib.pyplot as plt
import numpy as np
# Set the x values for the sine and cosine functions
x = np.linspace(0, 2 * np.pi, 100)
# Create the figure and an axis
fig, ax = plt.subplots(2, 1)
ax2 = ax[0].twinx()
ax22 = ax[1].twinx()
# Plot the sine and cosine functions on the axis
ax[0].plot(x, np.sin(x), label='Sine 0')
ax[0].plot(x, np.cos(x), label='Cosine 0')
# Plot the sine and cosine functions on the axis
ax[1].plot(x, np.sin(x), label='Sine')
ax[1].plot(x, np.cos(x), label='Cosine')
ax2.plot(x, np.cos(x + 1), label='Cosine 2', color="red")
ax2.plot(x, x, label='Cosine B', color="green")
ax22.plot(x, np.cos(x + 2), label='Line 2', color="red")
ax22.plot(x, x, label='Cosine 2', color="green")
# Add a title and labels to the axis
ax[0].set_title('Sine and Cosine Functions')
ax[0].set_xlabel('X')
ax[0].set_ylabel('Y')
# Get the line legends from the axis
lines, labels = ax[0].get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
# Add a legend to the figure
ax[0].legend(lines + lines2, labels + labels2, framealpha=1.0, loc='lower right')
lines1, labels1 = ax[1].get_legend_handles_labels()
lines22, labels22 = ax22.get_legend_handles_labels()
# Add a legend to the figure
ax[1].legend(lines1 + lines22, labels1 + labels22, framealpha=1.0, loc='lower left')
# Display the plot
plt.show()

I finally found the solution:
That is controlling the z-order of the Axis and the alpha of the axis with the highest priority.
the matplotlib functions are:
Axes.set_zorder
https://matplotlib.org/3.2.2/api/_as_gen/matplotlib.axes.Axes.set_zorder.html
&
Patch.set_alpha
https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Patch.html
Replace the last part of the code in the third answer with the above code, and it produces the desired result.
# Get the line legends from the axis
lines, labels = ax[0].get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
##############################################################################
############################################################################## 
# Set the z-order of axis 2 to a low "priority"
ax2.set_zorder(0)
# Set the z-order of the axis 0 to a higher priority.
# And to make visible axis 2 set the alpha of the background at 0.
ax[0].set_zorder(1)
ax[0].patch.set_alpha(0.0)
 
ax[0].legend(lines + lines2, labels + labels2, framealpha=1.0, loc='lower left')
##############################################################################
##############################################################################
 
lines1, labels1 = ax[1].get_legend_handles_labels()
lines22, labels22 = ax22.get_legend_handles_labels()
 
# Add a legend to the figure
ax22.legend(lines1 + lines22, labels1 + labels22, framealpha=1.0, loc='lower left')
 
# Display the plot
plt.show()

Related

How to get the line's label if multiple graphs have been plotted?

plt.plot(x, y, label = name1)
plt.plot(x, y, label = name2)
plt.plot(x, y, label = name3)
plt.show()
How to get the label when I click the line or better if I can get this information directly in the graph window like I get the x and y axis values on bottom right.
The fastest way would be to add a legend to your graph with plt.legend() right before plt.show()
For more interactivity, maybe try bokeh instead of matplotlib.
Not sure exactly what you are asking for but if you want to represent each line with a name, or the series of the x-values, you could use legend() and input a string or series name as label name in the plot-line:
plt.plot(x1, y, label = "name1") # Show the string name1
plt.plot(x2, y, label = x2) # Shows the array x2
plt.legend() # Displays the legends
If you want to add title or labels for the axis you could use:
plt.xlabel('X-axis Label')
plt.ylabel('Y-axis label')
plt.title('Title')
I am not sure if this is what you are looking for? But you can easily name your graphs by using the Legend. the first graph will be the first in your Legendlist. The important code is between the slash :-)
import matplotlib.pyplot as plt
import numpy as np
# Select length of axes and the space between tick labels
xmin, xmax, ymin, ymax = -10, 10, -10, 10
ticks_frequency = 1
# Plot points
fig, ax = plt.subplots(figsize=(10, 10))
#//////////////////////////////////////////////////////////////////////////////
# x range
x = np.arange(-5, 5., 0.025)
# f1
y1 = 3*x+4
f1 = ax.plot(x, y1, lw = 3, alpha = 0.5, color="blue")
# f2
y2 = 1*x+1
f2 = ax.plot(x, y2, lw = 3, alpha = 0.5, color="orange")
# f3
y3 = -2*x+8
f3 = ax.plot(x, y3, lw = 3, alpha = 0.5, color="red")
# legend
ax.legend(["Gerade 1", "Gerade 2", "Gerade 3"])
#//////////////////////////////////////////////////////////////////////////////
# Set identical scales for both axes
ax.set(xlim=(xmin-1, xmax+1), ylim=(ymin-1, ymax+1), aspect='equal')
# Set bottom and left spines as x and y axes of coordinate system
ax.spines['bottom'].set_position('zero')
ax.spines['left'].set_position('zero')
# Remove top and right spines
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
# Create 'x' and 'y' labels placed at the end of the axes
ax.set_xlabel('x', size=14, labelpad=-24, x=1.03)
ax.set_ylabel('y', size=14, labelpad=-21, y=1.02, rotation=0)
# Create custom major ticks to determine position of tick labels
x_ticks = np.arange(xmin, xmax+1, ticks_frequency)
y_ticks = np.arange(ymin, ymax+1, ticks_frequency)
ax.set_xticks(x_ticks[x_ticks != 0])
ax.set_yticks(y_ticks[y_ticks != 0])
# Create minor ticks placed at each integer to enable drawing of minor grid
# lines: note that this has no effect in this example with ticks_frequency=1
ax.set_xticks(np.arange(xmin, xmax+1), minor=True)
ax.set_yticks(np.arange(ymin, ymax+1), minor=True)
# Draw major and minor grid lines
ax.grid(which='both', color='grey', linewidth=1, linestyle='-', alpha=0.2)
# Draw arrows
arrow_fmt = dict(markersize=4, color='black', clip_on=False)
ax.plot((1), (0), marker='>', transform=ax.get_yaxis_transform(), **arrow_fmt)
ax.plot((0), (1), marker='^', transform=ax.get_xaxis_transform(), **arrow_fmt)
plt.show()

Python 3d scatter plot legend for colors shows only first color

I want to create a 3D scatter plot with legends for the sizes and the colors. However, the legend for the colors only shows the first color in the list.
import matplotlib.pyplot as plt
import matplotlib.colors
# Visualizing 5-D mix data using bubble charts
# leveraging the concepts of hue, size and depth
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
t = fig.suptitle('Wine Residual Sugar - Alcohol Content - Acidity - Total Sulfur Dioxide - Type', fontsize=14)
xs = [1,2,3,5,4]
ys = [6,7,3,5,4]
zs = [1,5,3,9,4]
data_points = [(x, y, z) for x, y, z in zip(xs, ys, zs)]
ss = [100,200,390,500,400]
colors = ['red','red','blue','yellow','yellow']
scatter = ax.scatter(xs, ys, zs, alpha=0.4, c=colors, s=ss)
ax.set_xlabel('Residual Sugar')
ax.set_ylabel('Alcohol')
ax.set_zlabel('Fixed Acidity')
legend1 = ax.legend(*scatter.legend_elements()[0],
loc="upper right", title="Classes", labels=colors, bbox_to_anchor=(1.5, 1),prop={'size': 20})
ax.add_artist(legend1)
# produce a legend with a cross section of sizes from the scatter
handles, labels = scatter.legend_elements(prop="sizes", alpha=0.6)
legend2 = ax.legend(handles, labels, loc="upper right", title="Sizes", bbox_to_anchor=(1.5, 0.5), prop={'size': 20})
The issue might result from the fact that matplotlib only receives one series to plot and thus assumes that one legend entry suffices. If I make scatter plots of the red, blue and yellow series individually, then all three classes are displayed correctly in the legend (but it causes issues when plotting the legend with sizes).
It's perhaps not the most elegant solution, but the legend with classes can be created manually:
import matplotlib.pyplot as plt
import matplotlib.colors
from matplotlib.lines import Line2D
# Visualizing 5-D mix data using bubble charts
# leveraging the concepts of hue, size and depth
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
t = fig.suptitle('Wine Residual Sugar - Alcohol Content - Acidity - Total Sulfur Dioxide - Type', fontsize=14)
xs = [1,2,3,5,4]
ys = [6,7,3,5,4]
zs = [1,5,3,9,4]
data_points = [(x, y, z) for x, y, z in zip(xs, ys, zs)]
ss = [100,200,390,500,400]
colors = ['red','red','blue','yellow','yellow']
scatter = ax.scatter(xs, ys, zs, alpha=0.4, c=colors, s=ss)
ax.set_xlabel('Residual Sugar')
ax.set_ylabel('Alcohol')
ax.set_zlabel('Fixed Acidity')
# Create additional legend
UniqueColors = list(dict.fromkeys(colors))
Legend2Add = []
for color in UniqueColors:
Legend2Add.append( Line2D([0], [0], marker='o', color='w', label=color,
markerfacecolor=color, markersize=15, alpha=0.4) )
# Produce a legend with a cross section of sizes from the scatter
handles, labels = scatter.legend_elements(prop="sizes", alpha=0.6)
legend1 = ax.legend(handles,
loc="upper right", title="Classes", handles=Legend2Add, bbox_to_anchor=(1.5, 1),prop={'size': 20})
ax.add_artist(legend1)
legend2 = ax.legend(handles, labels, loc="upper right", title="Sizes", bbox_to_anchor=(1.5, 0.5), prop={'size': 20})
plt.show()

My graph doesn't display entirely x-axis and grid in the background (Matplotlib/Python)

I'm trying to plot some revenues against EBIT on a chart with a secondary Y-axis.
Everything seems to be ok except
the grid doesn't display entirely.
the x-axis doesn't display entirely either.
Does anyone have a solution to display entirely these 2 features ?
Here is a screen cap of my graph :
graph
Here is the data :
data
And here is my code :
x = msft_ebit_revenue_new['period']
y1 = msft_ebit_revenue_new['EBIT']
y2 = msft_ebit_revenue_new['Revenues']
Plot Line1 (Left Y Axis)
fig, ax1 = plt.subplots(1,1,figsize=(16,9), dpi= 80)
ax1.plot(x, y1, color='tab:red')
# Plot Line2 (Right Y Axis)
ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis
ax2.plot(x, y2, color='tab:blue')
Decorations
ax1 (left Y axis)
ax1.set_xlabel('Period', fontsize=10)
ax1.tick_params(axis='x', rotation=0, labelsize=12)
ax1.set_ylabel('EBIT', color='tab:red', fontsize=20)
ax1.tick_params(axis='y', rotation=0, labelcolor='tab:red' )
ax1.grid(alpha=.4)
ax2 (right Y axis)
ax2.set_ylabel("Revenues ($ in millions)", color='tab:blue', fontsize=20)
ax2.tick_params(axis='y', labelcolor='tab:blue')
ax2.set_xticks(np.arange(0, len(x), 60))
ax2.set_xticklabels(x[::60], rotation=90, fontdict={'fontsize':10})
ax2.set_title("EBIT vs Revenues (MSFT)", fontsize=22)
fig.tight_layout()
plt.show()
Thank you !
Alex
Problem
Display x-axis labels
Display grid
Solution
Comment the following lines:
ax2.set_xticks(np.arange(0, len(x), 60))
ax2.set_xticklabels(x[::60], rotation=90, fontdict={'fontsize':10})

One legend for all subplots in pyplot

I am currently plotting the same data but visualizing it differently in two subplots (see figure):
Code snippet used for producing the above figure:
# Figure
plt.figure(figsize=(14,8), dpi=72)
plt.gcf().suptitle(r'Difference between TI and $\lambda$D', size=16)
# Subplot 1
ax1 = plt.subplot2grid((1,3),(0,0),colspan=2)
# Plot scattered data in first subplot
plt.scatter(LE_x, LE_y, s=40, lw=0, color='gold', marker='o', label=r'$\lambda$D')
plt.scatter(MD_x, MD_y, s=40, lw=0, color='blue', marker='^', label=r'TI')
# Subplot 2
ax2 = plt.subplot2grid((1,3),(0,2))
plt.barh(vpos1, LE_hist, height=4, color='gold', label=r'$\lambda$D')
plt.barh(vpos2, MD_hist, height=4, color='blue', label=r'TI')
# Legend
legend = plt.legend()
Is there any way to make the legend show both the scatter dots and the bars? Would this also go per dummy as described here? Could somebody then please post a minimal working example for this, since I'm not able to wrap my head around this.
This worked for me, you essentially capture the patch handles for each graph plotted and manually create a legend at the end.
import pylab as plt
import numpy as NP
plt.figure(figsize=(14,8), dpi=72)
plt.gcf().suptitle(r'Difference between TI and $\lambda$D', size=16)
# Subplot 1
ax1 = plt.subplot2grid((1,3),(0,0),colspan=2)
N = 100
LE_x = NP.random.rand(N)
LE_y = NP.random.rand(N)
MD_x = NP.random.rand(N)
MD_y = NP.random.rand(N)
# Plot scattered data in first subplot
s1 = plt.scatter(LE_x, LE_y, s=40, lw=0, color='gold', marker='o', label=r'$\lambda$D')
s2 = plt.scatter(MD_x, MD_y, s=40, lw=0, color='blue', marker='^', label=r'TI')
data = NP.random.randn(1000)
LE_hist, bins2 = NP.histogram(data, 50)
data = NP.random.randn(1000)
MD_hist, bins2 = NP.histogram(data, 50)
# Subplot 2
ax2 = plt.subplot2grid((1,3),(0,2))
vpos1 = NP.arange(0, len(LE_hist))
vpos2 = NP.arange(0, len(MD_hist)) + 0.5
h1 = plt.barh(vpos1, LE_hist, height=0.5, color='gold', label=r'$\lambda$D')
h2 = plt.barh(vpos2, MD_hist, height=0.5, color='blue', label=r'TI')
# Legend
#legend = plt.legend()
lgd = plt.legend((s1, s2, h1, h2), (r'$\lambda$D', r'TI', r'$\lambda$D', r'TI'), loc='upper center')
plt.show()

changing axis weight in matplotlib

How to change axis weight in matplotlib (make the axis much bolder)?
from pylab import *
x = [5,7,5,9,11,14]
y = [4,5,3,11,15,14]
scatter(x, y, s=50, color='green',marker='h')
show()
You can set the width of whats called a spine (a side of the axes) in Matplotlib:
fig, ax = plt.subplots()
ax.plot(np.random.randn(100).cumsum())
# The spines
plt.setp(ax.spines.values(), linewidth=3)
# The ticks
ax.xaxis.set_tick_params(width=3)
ax.yaxis.set_tick_params(width=3)
Use axhline, axvline:
axhline(linewidth=5, color='black')
axvline(linewidth=5, color='black')
axhline(linewidth=5, y=max(y)*1.1, color='black')
axvline(linewidth=5, x=max(x)*1.1, color='black')

Categories

Resources