Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add waterfall chart example #2621

Merged
merged 3 commits into from
Dec 17, 2022
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions altair/examples/waterfall_chart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""
Waterfall Chart
-------------
This example shows how to recreate a vega lite implementation of a waterfall chart.
Original inspiration is from https://vega.github.io/vega-lite/examples/waterfall_chart.html
"""
# category: other charts

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




Comment on lines +14 to +15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

## Compared to the original example, we have to add some more fields to help with color labeling and ordering the bars
the_data=[
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
the_data=[
the_data = [

Could you add spaces around all variable assignment = like this?

{"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)


## This is a 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
)
## This is where you use the "order" field in the_data to define the sort order
sort_x_by_order=alt.EncodingSortField(field="order", op="max", order='ascending')


## The "base_chart" defines the transform_window, transform_calculate, and X axis
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"
Comment on lines +56 to +57
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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_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
"

Could you make this change everywhere (having comma last instead of first) so that the style is consistent with the rest of the Altair documentation?

, 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=sort_x_by_order)
)

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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
rule=base_chart.mark_rule(color='#404040', opacity=0.9, strokeWidth=2, xOffset=-25, x2Offset=25, strokeDash=[3,3]).encode(
rule = base_chart.mark_rule(
color='#404040',
opacity=0.9,
strokeWidth=2,
xOffset=-25,
x2Offset=25,
strokeDash=[3,3]
).encode(

I would break this up since the line is really long

#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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this line is so long I would write it like this instead:

alt.layer(
    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
)