3
votes

I want to add a fill colour between the black and blue line on my Plotly chart. I am aware this can be accomplished already with Plotly but I am not sure how to fill the chart with two colours based on conditions.This is my Plotly chartThis is what I want to achieve

The chart with the blue background is my Plotly chart. I want to make it look like the chart with the white background. (Ignore the red and green bars on the white chart)

The conditions I want it to pass is:

Fill the area between the two lines GREEN, if the black line is above the blue line.

Fill the area between the two lines RED, if the black line is below the blue line.

How can this be done with Plotly? If this is not possible with Plotly can it be accomplished with other graphing tools that work with Python.

1

1 Answers

4
votes

For a number of reasons (that I'm willing to explain further if you're interested) the best approach seems to be to add two traces to a go.Figure() object for each time your averages cross eachother, and then define the fill using fill='tonexty' for the second trace using:

for df in dfs:
    fig.add_traces(go.Scatter(x=df.index, y = df.ma1,
                              line = dict(color='rgba(0,0,0,0)')))
    
    fig.add_traces(go.Scatter(x=df.index, y = df.ma2,
                              line = dict(color='rgba(0,0,0,0)'),
                              fill='tonexty', 
                              fillcolor = fillcol(df['label'].iloc[0])))

fillcolor is a simple custom function described in the full snippet below. And I've used the approach described in How to split a dataframe each time a string value changes in a column? to produce the necessary splits in the dataframe each time your averages cross eachother.

Plot

enter image description here

Complete code:

import plotly.graph_objects as go
import numpy as np

import pandas as pd
from datetime import datetime
pd.options.plotting.backend = "plotly"

# sample data
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv')
df.index = df.Date
df = df[['AAPL.Close', 'mavg']]
df['mavg2'] = df['AAPL.Close'].rolling(window=50).mean()
df.columns = ['y', 'ma1', 'ma2']
df=df.tail(250).dropna()
df1 = df.copy()

# split data into chunks where averages cross each other
df['label'] = np.where(df['ma1']>df['ma2'], 1, 0)
df['group'] = df['label'].ne(df['label'].shift()).cumsum()
df = df.groupby('group')
dfs = []
for name, data in df:
    dfs.append(data)

# custom function to set fill color
def fillcol(label):
    if label >= 1:
        return 'rgba(0,250,0,0.4)'
    else:
        return 'rgba(250,0,0,0.4)'

fig = go.Figure()

for df in dfs:
    fig.add_traces(go.Scatter(x=df.index, y = df.ma1,
                              line = dict(color='rgba(0,0,0,0)')))
    
    fig.add_traces(go.Scatter(x=df.index, y = df.ma2,
                              line = dict(color='rgba(0,0,0,0)'),
                              fill='tonexty', 
                              fillcolor = fillcol(df['label'].iloc[0])))

# include averages
fig.add_traces(go.Scatter(x=df1.index, y = df1.ma1,
                          line = dict(color = 'blue', width=1)))

fig.add_traces(go.Scatter(x=df1.index, y = df1.ma2,
                          line = dict(color = 'red', width=1)))

# include main time-series
fig.add_traces(go.Scatter(x=df1.index, y = df1.y,
                          line = dict(color = 'black', width=2)))

fig.update_layout(showlegend=False)
fig.show()