I want to plot a time-series area plot, where positive (>= 0) values are filled in one colour and negative (< 0) values are filled in another.
Taking this example:
import pandas as pd
import numpy as np
import plotly.express as px
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv').assign(
PnL = lambda x: x['AAPL.Close'] - 100
)
px.area(
data_frame = df,
x = 'Date',
y = 'PnL',
width = 500,
height = 300
)
I want the parts where PnL goes below 0 to be filled red.
So this is what I tried:
import pandas as pd
import numpy as np
import plotly.express as px
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv').assign(
PnL = lambda x: x['AAPL.Close'] - 100
)
df['sign'] = np.where(df['PnL'] >= 0, 'positive', 'negative')
px.area(
data_frame = df,
x = 'Date',
y = 'PnL',
color = 'sign',
color_discrete_map = {
'positive': 'steelblue',
'negative': 'crimson'
},
width = 500,
height = 300
)
But this gives me:
Which is not exactly what I'm looking for. What's the best way to do this?
This is the best I could do in the time I had patience for it:
import plotly.graph_objects as go
mask = df['PnL'] >= 0
df['PnL_above'] = np.where(mask, df['PnL'], 0)
df['PnL_below'] = np.where(mask, 0, df['PnL'])
fig = go.Figure()
fig.add_trace(go.Scatter(x=df['Date'], y=df['PnL_above'], fill='tozeroy'))
fig.add_trace(go.Scatter(x=df['Date'], y=df['PnL_below'], fill='tozeroy'))
Result:
Obviously not ideal, but gets you most of the way there. There are some slight artifacts where the two traces meet, and obviously the line color is still visible when the value is zero.
By adding mode='none'
to the two traces, you can remove the line and only render the filled area: