0

The only references I can find online is placing a percentage on top of the barchart here, but none with connecting lines and placing some text between bars at the center of the connecting line.

Currently my chart looks like this:

enter image description here

df_revenue = compiled_data[compiled_data['Fee Name'] == "Item Price Credit"]
df_revenue = df_revenue.groupby(['Year']).sum()
df_revenue = df_revenue.reset_index()

sns.set_theme(font_scale=1.5, style="whitegrid")
g = sns.catplot(data=df_revenue, x='Year', y='Amount', kind='bar', height=6, aspect=12/6)
g.set(title="Yearly Revenues", xlabel = 'Year', ylabel='Amount')
sns.despine(left=True)

for ax in g.axes[:,0]:
    ax.yaxis.set_major_formatter(ticker.StrMethodFormatter('{x:,.0f}'))
plt.show()

But I am hoping it to look like this and I am not sure what to work with:

enter image description here

Pherdindy
  • 1,168
  • 7
  • 23
  • 52
  • I would think you could just `lineplot` to get the line and then use `annotate` to add the labels. – BigBen Oct 23 '20 at 13:22
  • @BigBen right that sounds good although I just realized it would look better if the lineplot was located at the right of the current bar to the left of the next bar instead of at the center to center of the bars. That makes it much trickier to actually plot – Pherdindy Oct 23 '20 at 13:29
  • [Similar](https://stackoverflow.com/questions/38161161/matplotlib-how-to-combine-multiple-bars-with-lines). – BigBen Oct 23 '20 at 13:30
  • @Pherdindy while it looks good connecting the rights to lefts of the bars, the slopes would be wrong, so your plot will be a little deceiving. – Quang Hoang Oct 23 '20 at 13:37
  • @BigBen Thanks i'll take a look and tweak a bit – Pherdindy Oct 23 '20 at 13:42
  • @QuangHoang right I am trying to find ways to visualize the relative change in a single chart, but this is the closest I can come up with. I was thinking of a secondary axis, but that would be based on the magnitude of the change so it may be far above the bars which would be hard to gauge as well – Pherdindy Oct 23 '20 at 13:43

1 Answers1

2

You could loop through the bars, extract their dimensions and use those for the lines and the texts:

import seaborn as sns
import pandas as pd
from matplotlib import ticker
from matplotlib import pyplot as plt

df_revenue = pd.DataFrame({'Year': [2018, 2019, 2020], 'Amount': [100000, 1200000, 3200000]})

sns.set_theme(font_scale=1.5, style="whitegrid")
g = sns.catplot(data=df_revenue, x='Year', y='Amount', kind='bar', height=6, aspect=12 / 6)
g.set(title="Yearly Revenues", xlabel='Year', ylabel='Amount')
sns.despine(left=True)

for ax in g.axes[:, 0]:
    ax.yaxis.set_major_formatter(ticker.StrMethodFormatter('{x:,.0f}'))
for ax in g.axes.ravel():
    for bar_group in ax.containers:
        prev = None
        for bar in bar_group:
            if prev != None:
                x0 = prev.get_x() + prev.get_width()
                x1 = bar.get_x()
                y0 = prev.get_y() + prev.get_height()
                y1 = bar.get_y() + bar.get_height()
                ax.plot([x0, x1], [y0, y1], color='black', lw=2)
                if y0 > 0 and y0 != y1:
                    ax.text((x0 + x1) / 2, (y0 + y1) / 2, f' {(y1 - y0) / y0 * 100:.1f} % ',
                            ha='right' if y0 < y1 else 'left', va='center')
            prev = bar
plt.tight_layout()
plt.show()

example plot

JohanC
  • 71,591
  • 8
  • 33
  • 66