4
votes

I'm attempting to make use of twinx() to create a bar/line combo graph with the line visible on top of the bar. Currently this is how it appears:

Bar/Line Combo

I also need the line chart to be plotted on the left vertical axis (ax) and the bar on the right (ax2) as it currently is. If I plot the line on the second axis it does appear on top, but obviously it appears on the wrong axis (right)

Here's my code:

    self.ax2=ax.twinx()
    df[['Opportunities']].plot(kind='bar', stacked=False, title=get_title, color='grey', ax=self.ax2, grid=False)
    ax.plot(ax.get_xticks(),df[['Percentage']].values, linestyle='-', marker='o', color='k', linewidth=1.0)
    lines, labels = ax.get_legend_handles_labels()
    lines2, labels2 = self.ax2.get_legend_handles_labels()
    ax.legend(lines + lines2, labels + labels2, loc='lower right')

Also having trouble with the labels, but one thing at a time.

1

1 Answers

4
votes

It appears, by default, that the artists are drawn on ax first, then the artists on the twin axes ax2 on top. So since in your code the line plot was drawn on ax and the bar plot on ax2, the bar plot sits on top of (and obscures) the line.

(I thought I could change this by specifying zorder, but that attempt did not work... )

So one way to solve the problem is to use ax to draw the bar plot and ax2 to draw the line. That will place the line on top of the bars. It will also, by default, place the ytick labels for ax (the bar plot) on the left, and the ytick labels for ax2 (the line) on the right. However, you can use

ax.yaxis.set_ticks_position("right")
ax2.yaxis.set_ticks_position("left")

to swap the location of the left and right ytick labels.


import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import pandas as pd
np.random.seed(2015)

N = 16
df = pd.DataFrame({'Opportunities': np.random.randint(0, 30, size=N),
                   'Percentage': np.random.randint(0, 100, size=N)},
                  index=pd.date_range('2015-3-15', periods=N, freq='B').date)
fig, ax = plt.subplots()

df[['Opportunities']].plot(kind='bar', stacked=False, title='get_title', 
                           color='grey', ax=ax, grid=False)
ax2 = ax.twinx()
ax2.plot(ax.get_xticks(), df[['Percentage']].values, linestyle='-', marker='o', 
        color='k', linewidth=1.0, label='percentage')

lines, labels = ax.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax.legend(lines + lines2, labels + labels2, loc='best')
ax.yaxis.set_ticks_position("right")
ax2.yaxis.set_ticks_position("left")

fig.autofmt_xdate()
plt.show()

yields

enter image description here


Alternatively, the zorder of the axes can be set so as to draw ax above ax2. Paul Ivanov shows how:

ax.set_zorder(ax2.get_zorder()+1) # put ax in front of ax2
ax.patch.set_visible(False) # hide the 'canvas'
ax2.patch.set_visible(True) # show the 'canvas'

Thus,

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import pandas as pd
np.random.seed(2015)

N = 16
df = pd.DataFrame({'Opportunities': np.random.randint(0, 30, size=N),
               'Percentage': np.random.randint(0, 100, size=N)},
index=pd.date_range('2015-3-15', periods=N, freq='B').date)
fig, ax = plt.subplots()
ax2 = ax.twinx()
df[['Opportunities']].plot(kind='bar', stacked=False, title='get_title', 
                           color='grey', ax=ax2, grid=False)

ax.plot(ax.get_xticks(), df[['Percentage']].values, linestyle='-', marker='o', 
        color='k', linewidth=1.0, label='percentage')

lines, labels = ax.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax.legend(lines + lines2, labels + labels2, loc='best')

ax.set_zorder(ax2.get_zorder()+1) # put ax in front of ax2
ax.patch.set_visible(False) # hide the 'canvas'
ax2.patch.set_visible(True) # show the 'canvas'

fig.autofmt_xdate()
plt.show()

yields the same result without having to swap the roles played by ax and ax2.