Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Matplotlib, vertical space between legend symbols

I have an issue with customizing the legend of my plot. I did lot's of customizing but couldnt get my head around this one. I want the symbols (not the labels) to be equally spaced in the legend. As you can see in the example, the space between the circles in the legend, gets smaller as the circles get bigger. any ideas? Also, how can I also add a color bar (in addition to the size), with smaller circles being light red (for example) and bigger circle being blue (for example) here is my code so far:

import pandas as pd
import matplotlib.pyplot as plt
from vega_datasets import data as vega_data
gap = pd.read_json(vega_data.gapminder.url)

df = gap.loc[gap['year'] == 2000]

fig, ax = plt.subplots(1, 1,figsize=[14,12])
ax=ax.scatter(df['life_expect'], df['fertility'], 
            s = df['pop']/100000,alpha=0.7, edgecolor="black",cmap="viridis")

plt.xlabel("X")
plt.ylabel("Y");
kw = dict(prop="sizes", num=6, color="lightgrey", markeredgecolor='black',markeredgewidth=2)
plt.legend(*ax.legend_elements(**kw),bbox_to_anchor=(1, 0),frameon=False,
                    loc="lower left",markerscale=1,ncol=1,borderpad=2,labelspacing=4,handletextpad=2)

plt.grid()
plt.show()

enter image description here

like image 625
Seji Avatar asked Aug 22 '20 07:08

Seji


People also ask

How do you change legend spacing?

If you want to adjust the line space between lines in the legend, you can right-click the legend to select Properties... from the context menu to open the Text Object dialog. In the Text tab of this dialog, for the Line Spacing(%) item, select a value from the drop-down list or enter a value in the combo box directly.

How do I wrap a legend in Matplotlib?

import textwrap [...] renames = {c: textwrap. fill(c, 15) for c in df. columns} df. rename(renames, inplace=True) [...]

What is Bbox_to_anchor?

bbox_to_anchor=[x0, y0] will create a bounding box with lower left corner at position [x0, y0] . The extend of the bounding box is zero - being equivalent to bbox_to_anchor=[x0, y0, 0, 0] . The legend will then be placed 'inside' this box and overlapp it according to the specified loc parameter.

Can I have two legends in Matplotlib?

Multiple Legends. Sometimes when designing a plot you'd like to add multiple legends to the same axes. Unfortunately, Matplotlib does not make this easy: via the standard legend interface, it is only possible to create a single legend for the entire plot. If you try to create a second legend using plt.

How to change the vertical space between labels in Matplotlib?

Display plot. In this example, we will draw different lines with the help of matplotlib and Use the labelspacing argument to plt.legend () to change the vertical space between labels.

How to adjust the space between legend markers and labels?

To adjust the space between legend markers and labels, we can use labelspacing in legend method. Plot lines with label1, label2 and label3. Initialize a space variable to increase or decrease the space between legend markers and label.

How do I make a legend for existing plot elements?

Labeling existing plot elements To make a legend for lines which already exist on the Axes (via plot for instance), simply call this function with an iterable of strings, one for each legend item. For example:

What is the vertical offset for a scatter plot legend entry?

The vertical offset (relative to the font size) for the markers created for a scatter plot legend entry. 0.0 is at the base the legend text, and 1.0 is at the top. To draw all markers at the same height, set to [0.5].


Video Answer


1 Answers

It's a bit tricky, but you could measure the legend elements and reposition them to have a constant inbetween distance. Due to the pixel positioning, the plot can't be resized afterwards.

I tested the code inside PyCharm with the 'Qt5Agg' backend. And in a Jupyter notebook, both with %matplotlib inline and with %matplotlib notebook. I'm not sure whether it would work well in all environments.

Note that ax.scatter doesn't return an ax (countrary to e.g. sns.scatterplot) but a list of the created scatter dots.

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.transforms import IdentityTransform
from vega_datasets import data as vega_data

gap = pd.read_json(vega_data.gapminder.url)
df = gap.loc[gap['year'] == 2000]

fig, ax = plt.subplots(1, 1, figsize=[14, 12])
fig.subplots_adjust(right=0.8)
scat = ax.scatter(df['life_expect'], df['fertility'],
                  s=df['pop'] / 100000, alpha=0.7, edgecolor="black", cmap="viridis")

plt.xlabel("X")
plt.ylabel("Y")
x = 1.1
y = 0.1
is_first = True
kw = dict(prop="sizes", num=6, color="lightgrey", markeredgecolor='black', markeredgewidth=2)
handles, labels = scat.legend_elements(**kw)

inverted_transData = ax.transData.inverted()
for handle, label in zip(handles[::-1], labels[::-1]):
    plt.setp(handle, clip_on=False)
    for _ in range(1 if is_first else 2):
        plt.setp(handle, transform=ax.transAxes)
        if is_first:
            xd, yd = x, y
        else:
            xd, yd = inverted_transData.transform((x, y))
        handle.set_xdata([xd])
        handle.set_ydata([yd])
        ax.add_artist(handle)
        bbox = handle.get_window_extent(fig.canvas.get_renderer())
        y += y - bbox.y0 + 15  # 15 pixels inbetween
    x = (bbox.x0 + bbox.x1) / 2
    if is_first:
        xd_text, _ = inverted_transData.transform((bbox.x1+10, y))
    ax.text(xd_text, yd, label, transform=ax.transAxes, ha='left', va='center')
    y = bbox.y1
    is_first = False
plt.show()

resulting plot

like image 109
JohanC Avatar answered Oct 24 '22 00:10

JohanC