Search code examples
pythonmatplotlibgraphz-order

How to set zorder across axis in matplotlib


I want to make a graph that contains bars, the value of the bars noted above them and a line on the secondary axis, but I can't configure the order of the elements the way I want where the bars would be the furthest element, followed by the line and then the texts, but so far I can only change the position of an entire axis and either the line goes over the text or is behind the bars.

import matplotlib.pyplot as plt
import seaborn as sns

years = [2019, 2020, 2021, 2022, 2023]
values1 = [1350, 1360, 1420, 1480, 1650]
values2 = [57, 62, 60.5, 59.7, 62.3]

fig, ax = plt.subplots(figsize=(6.2, 7.36), dpi = 150)
bars = ax.bar(years, values1)

heights = []
for bar in bars:
    bar.set_zorder(2)
    height = bar.get_height()
    heights.append(height)
    ax.annotate('{}'.format(round(height, 1)),
                xy=(bar.get_x() + bar.get_width() / 2, height),
                xytext=(0, 10),  # 3 points vertical offset
                textcoords="offset points",
                ha='center', va='bottom',
                fontsize = 20).set_zorder(1)
    

ax2 = ax.twinx()
lineplot = sns.lineplot(x = years, y = values2, ax = ax2, color="gray")

the graph that the code produces: graph with line in fort of text annotation

I tried changing the zorder on both axes and directly on the charts


Solution

  • Unfortunately respecting zorder across twinned axes is not possible. A workaround for annotations is to add them to the second axes, but using the data transform of the first. See the xycoords parameter added in the call to annotate here:

    import matplotlib.pyplot as plt
    
    years = [2019, 2020, 2021, 2022, 2023]
    values1 = [1350, 1360, 1420, 1480, 1650]
    values2 = [57, 62, 60.5, 59.7, 62.3]
    
    fig, ax = plt.subplots(figsize=(6.2, 7.36), dpi = 150)
    bars = ax.bar(years, values1)
    
    ax2 = ax.twinx()
    lineplot = ax2.plot(years, values2, ls="-", color="gray")
    
    for bar in bars:
        height = bar.get_height()
        ax2.annotate('{}'.format(round(height, 1)),
                     xy=(bar.get_x() + bar.get_width() / 2, height),
                     xytext=(0, 10),  # 3 points vertical offset
                     textcoords="offset points",
                     ha='center', va='bottom',
                     fontsize = 20,
                     xycoords=ax.transData)
    
    plt.show()
    

    enter image description here

    Note that this can be simplified by using the bar_label method, which is a convenience wrapper for adding annotations to bars:

    import matplotlib.pyplot as plt
    
    years = [2019, 2020, 2021, 2022, 2023]
    values1 = [1350, 1360, 1420, 1480, 1650]
    values2 = [57, 62, 60.5, 59.7, 62.3]
    
    fig, ax = plt.subplots(figsize=(6.2, 7.36), dpi = 150)
    bars = ax.bar(years, values1)
    
    ax2 = ax.twinx()
    lineplot = ax2.plot(years, values2, ls="-", color="gray")
    ax2.bar_label(bars, fontsize=20, padding=10, xycoords=ax.transData) 
    
    plt.show()