altair icon indicating copy to clipboard operation
altair copied to clipboard

waterfall chart

Open anaveenan opened this issue 6 years ago • 7 comments

https://vega.github.io/vega-lite/examples/waterfall_chart.html

How to create waterfall chart in altair with data from pandas.thanks

anaveenan avatar Oct 18 '19 02:10 anaveenan

It's possible, but takes a lot of tweaking (it would basically mirror the vega-lite at the link you shared, including the nine transform definitions).

jakevdp avatar Oct 18 '19 03:10 jakevdp

Thanks

anaveenan avatar Oct 21 '19 14:10 anaveenan

For future people that arrive here via Google, here is an implementation of the vega lite waterfall chart in Altair.

There were some changes I needed to make on the input data to make sure things looked right (i.e. setting a custom "order" field, and also a custom "plus minus" field for the bar colors since Altair doesn't seem to allow you to chain the conditions together.

The original example also named different transformed calculations and window fields and raw data the same (i.e. there's a calcualted field called "amount" and also in the raw data the field is "amount"), which was confusing to me, so I made my names more verbose and explicit to reduce confusion.

### Trying to recreate this vega lite implementation in Altair 
### https://vega.github.io/vega-lite/examples/waterfall_chart.html

import altair as alt
from altair import datum
import pandas as pd


## had to add some more fields from the original example data in the vega-lite implementation
the_data=[
      {"label": "Begin", "amount": 4000, "order":0, "color_label":'Begin'},
      {"label": "Jan", "amount": 1707, "order":1, "color_label":'+'},
      {"label": "Feb", "amount": -1425, "order":2, "color_label":'-'},
      {"label": "Mar", "amount": -1030, "order":3, "color_label":'-'},
      {"label": "Apr", "amount": 1812, "order":4, "color_label":'+'},
      {"label": "May", "amount": -1067, "order":5, "color_label":'-'},
      {"label": "Jun", "amount": -1481, "order":6, "color_label":'-'},
      {"label": "Jul", "amount": 1228, "order":7, "color_label":'+'},
      {"label": "Aug", "amount": 1176, "order":8, "color_label":'+'},
      {"label": "Sep", "amount": 1146, "order":9, "color_label":'+'},
      {"label": "Oct", "amount": 1205, "order":10, "color_label":'+'},
      {"label": "Nov", "amount": -1388, "order":11, "color_label":'-'},
      {"label": "Dec", "amount": 1492, "order":12, "color_label":'+'},
      {"label": "End", "amount": 0, "order":13, "color_label":'End'}
    ]
df=pd.DataFrame(the_data)


## workaround to enable 3 different colors for the bars
color_coding=alt.Color(
    'color_label'
    , scale=alt.Scale(
        domain=['Begin','End','+','-']
        , range=['#878d96', '#878d96', '#24a148', '#fa4d56']
    )
    , legend=None
)


## the "base_chart" defines the transform_window, transform_calculate, and encode X and color coding
base_chart=alt.Chart(df).transform_window(
    sort=[{'field': 'order'}],
    frame=[None, 0],
    window_sum_amount='sum(amount)',
    window_lead_label='lead(label)'
)\
.transform_calculate(
    calc_lead="datum.window_lead_label === null ? datum.label : datum.window_lead_label"
    , calc_prev_sum="datum.label === 'End' ? 0 : datum.window_sum_amount - datum.amount"
    , calc_amount="datum.label === 'End' ? datum.window_sum_amount : datum.amount"
    , calc_text_amount="(datum.label !== 'Begin' && datum.label !== 'End' && datum.calc_amount > 0 ? '+' : '') + datum.calc_amount"
    , calc_center="(datum.window_sum_amount + datum.calc_prev_sum) / 2"
    , calc_sum_dec="datum.window_sum_amount < datum.calc_prev_sum ? datum.window_sum_amount : ''"
    , calc_sum_inc="datum.window_sum_amount > datum.calc_prev_sum ? datum.window_sum_amount : ''"
)\
.encode(
    x=alt.X('label:O', title='Months', sort=alt.EncodingSortField(field="order", op="max", order='ascending'))
)

bar=base_chart.mark_bar().encode(
    y=alt.Y('calc_prev_sum:Q',title='Amount')
    , y2=alt.Y2('window_sum_amount:Q')
    , color=color_coding
)

## the "rule" chart is for the lines that connect bars each month
rule=base_chart.mark_rule(color='#404040', opacity=0.9, strokeWidth=2, xOffset=-25, x2Offset=25, strokeDash=[3,3]).encode(
    #draw a horizontal line where the height (y) is at window_sum_amount, and the ending width (x2) is at calc_lead
    y='window_sum_amount:Q'
    , x2='calc_lead'
)

## create text values to display
text_pos_values_top_of_bar=base_chart.mark_text(baseline='bottom', dy=-4).encode(
    text=alt.Text('calc_sum_inc:N')
    , y='calc_sum_inc:Q'
)
text_neg_values_bot_of_bar=base_chart.mark_text(baseline='top', dy=4).encode(
    text=alt.Text('calc_sum_dec:N')
    , y='calc_sum_dec:Q'
)
text_bar_values_mid_of_bar=base_chart.mark_text(baseline='middle').encode(
    text=alt.Text('calc_text_amount:N')
    , y='calc_center:Q'
     , color=alt.condition("datum.label ==='Begin'||datum.label === 'End'", alt.value("white"), alt.value("white"))
)

## layer everything together
(bar+rule+text_pos_values_top_of_bar+text_neg_values_bot_of_bar+text_bar_values_mid_of_bar).properties(width=800, height=450)

You will end up with a chart that looks like image

yanghung avatar Jun 16 '22 17:06 yanghung

Nice! Feel free to submit a PR adding this to the example gallery if you want @yanghung. I'm reopening for considering adding this to the gallery.

joelostblom avatar Jun 16 '22 18:06 joelostblom

@joelostblom can you link me to the repo location where gallery examples are?

yang-hung-techlabs avatar Jun 16 '22 18:06 yang-hung-techlabs

Here is an sample PR of how to add an example to the gallery https://github.com/altair-viz/altair/pull/2519/files

joelostblom avatar Jun 16 '22 22:06 joelostblom

@joelostblom first time contributing to this community (thank you for all the work you do btw!), let me know if there's anything off formatting wise, but I tried to follow your example PR

https://github.com/altair-viz/altair/pull/2621

yanghung avatar Jun 21 '22 19:06 yanghung

Closing this as #2621 got merged

binste avatar May 26 '23 16:05 binste