pythonpandasplotlythreshold

Plotly: How to display different color segments on a line chart for specified thresholds?


I have a multi-line graph that displays percent increase over time. I'd like to set a threshold in my code to have an upper and lower bound. If the line falls outside these bounds, I'd like that specific part of the line graph to be a different color than its parent.

This is what I am doing:

import plotly.express as px
import plotly.graph_objects as go



fig = px.line(df14, x = "Date", y = "Percent", color = "id", 
          title = "id Growth in Percentage (US)", 
          labels = {"Percent": "Percent Growth"})

fig.update_layout(
font_family="Arial",
font_color="black",
title_font_family="Arial",
title_font_color="black",
legend_title_font_color="black"                                           #style the text (legend, title etc)
)


fig.update_xaxes(title_font_family="Arial")                               #style ance center title
fig.update_layout(
title={
    'text': "id Growth Percentage in US (Line Graph)",
    'y':0.9,
    'x':0.5,
    'xanchor': 'center',
    'yanchor': 'top'})


fig.update_traces(mode='markers+lines')                                    #add dots to line

fig.show()

This is the visual result:

![image|690x328](upload://hujUQbdPtbCAiXQoTTwLmzAAi02.png)

Let me zoom in on one line to better explain:

I would like a threshold set for each id, and if the line goes above or below this threshold, the color will be different for that part of the line graph. For instance:

The upper bound for id IAA may be 5 and the lower bound for IAA may be 0. Any value that is over 5 , or below 0, show be highlighted a specific color.

The upper bound for id SSS may be 10 and the lower bound for SSS may be 3 Any value that is over 10, or below 3, should be highlighted a specific color.

I am wanting the thresholds to be for each id

Please see below:

enter image description here

The highlighted yellow parts of the line reflect where the line graph has exceeded or decreased a set threshold. Is this possible to do this using Plotly?

Here is the raw data example: Updated:

    id       Start      End         Diff        Percent     Date        
    IAA     4/1/2019    5/1/2019    160.4279    11.10809    04-01-2019 to 05-01-2019
    IAA 5/1/2019    6/1/2019    136.0248    8.476798    05-01-2019 to 06-01-2019
    IAA     6/1/2019    7/1/2019    174.0513    9.998946    06-01-2019 to 07-01-2019
    IAA     7/1/2019    8/1/2019    112.0424    5.851551    07-01-2019 to 08-01-2019
    IAA     8/1/2019    9/1/2019    141.8488    6.998691    08-01-2019 to 09-01-2019
    IAA     9/1/2019    10/1/2019   103.5522    4.774984    09-01-2019 to 10-01-2019
    IAA     10/1/2019   11/1/2019   125.6087    5.528085    10-01-2019 to 11-01-2019
    IAA     11/1/2019   12/1/2019   145.2591    6.058016    11-01-2019 to 12-01-2019
    IAA     12/1/2019   1/1/2020    115.5121    4.542251    12-01-2019 to 01-01-2020
    IAA     1/1/2020    2/1/2020    185.7191    6.985673    01-01-2020 to 02-01-2020
    IAA     2/1/2020    3/1/2020    126.7386    4.455896    02-01-2020 to 03-01-2020
    IAA     3/1/2020    4/1/2020    231.3461    7.786734    03-01-2020 to 04-01-2020
    IAA     4/1/2020    5/1/2020    97.02587      3.02981   04-01-2020 to 05-01-2020
    IAA     5/1/2020    6/1/2020    42.85235      1.298792  05-01-2020 to 06-01-2020
    IAA     6/1/2020    7/1/2020    124.666    3.729997     06-01-2020 to 07-01-2020
    IAA     7/1/2020    8/1/2020    357.9974    10.32609    07-01-2020 to 08-01-2020
    IAA     8/1/2020    9/1/2020    490.9587      12.8358   08-01-2020 to 09-01-2020
    IAA     9/1/2020    10/1/2020   204.5478    4.739428    09-01-2020 to 10-01-2020
    IAA     10/1/2020   11/1/2020   287.6025    6.362292    10-01-2020 to 11-01-2020
   SSStest  4/1/2019    5/1/2019    12.38486    5.780551    04-01-2019 to 05-01-2019
   SSStest  5/1/2019    6/1/2019    -2.61735    -1.15487    05-01-2019 to 06-01-2019
   SSStest  6/1/2019    7/1/2019    -5.6187    -2.50814     06-01-2019 to 07-01-2019
   SSStest  7/1/2019    8/1/2019    3.204252    1.467153    07-01-2019 to 08-01-2019
   SSStest  8/1/2019    9/1/2019    -25.3782    -11.4521    08-01-2019 to 09-01-2019
   SSStest  9/1/2019    10/1/2019   -10.9717    -5.59137    09-01-2019 to 10-01-2019

Any suggestions is appreciated

Update

Is there a way to make the full line display the marker dots on them? I have tried this: mode = 'markers+lines' but did not get the desired result:

**Update, I have figured this out:** 
fig.update_traces(mode='markers+lines')    

enter image description here

Updated Question:

Also,is there a way to add the Date and Percent titles on the hover annotation here?. I am researching the Plotly docs.

[![enter image description here][4]][4]


Solution

  • I've put together a suggestion that should do exactly what you're asking for. The following figure is produced by the code sample below. The code uses a dictionary that contains the different upper and lower limits for your different PODs along with a possibility to set different colors for different pods:

    lim = {'IAD': {'lower': 90,'upper': 350, 'color':'yellow'},
           'SJCtest': {'lower': 10,'upper': 12, 'color':'green'}}
    

    Plot

    enter image description here

    I was tempted to filter your dataframe first, and then add new traces to the figure based on that. But my solution actually goes through each datapoint for each trace, and colors the points based on the dictionary above. So no need for any datamunging. Take a look, run a few tests, and let me know how it works out for you.

    Complete code

    import pandas as pd
    import plotly.express as px
    import plotly.graph_objects as go
    
    df = pd.DataFrame({'POD': {0: 'IAD',
                              1: 'IAD',
                              2: 'IAD',
                              3: 'IAD',
                              4: 'IAD',
                              5: 'IAD',
                              6: 'IAD',
                              7: 'IAD',
                              8: 'IAD',
                              9: 'IAD',
                              10: 'IAD',
                              11: 'IAD',
                              12: 'IAD',
                              13: 'IAD',
                              14: 'IAD',
                              15: 'IAD',
                              16: 'IAD',
                              17: 'IAD',
                              18: 'IAD',
                              19: 'SJCtest',
                              20: 'SJCtest',
                              21: 'SJCtest',
                              22: 'SJCtest',
                              23: 'SJCtest',
                              24: 'SJCtest'},
                             'Start': {0: '4/1/2019',
                              1: '5/1/2019',
                              2: '6/1/2019',
                              3: '7/1/2019',
                              4: '8/1/2019',
                              5: '9/1/2019',
                              6: '10/1/2019',
                              7: '11/1/2019',
                              8: '12/1/2019',
                              9: '1/1/2020',
                              10: '2/1/2020',
                              11: '3/1/2020',
                              12: '4/1/2020',
                              13: '5/1/2020',
                              14: '6/1/2020',
                              15: '7/1/2020',
                              16: '8/1/2020',
                              17: '9/1/2020',
                              18: '10/1/2020',
                              19: '4/1/2019',
                              20: '5/1/2019',
                              21: '6/1/2019',
                              22: '7/1/2019',
                              23: '8/1/2019',
                              24: '9/1/2019'},
                             'End': {0: '5/1/2019',
                              1: '6/1/2019',
                              2: '7/1/2019',
                              3: '8/1/2019',
                              4: '9/1/2019',
                              5: '10/1/2019',
                              6: '11/1/2019',
                              7: '12/1/2019',
                              8: '1/1/2020',
                              9: '2/1/2020',
                              10: '3/1/2020',
                              11: '4/1/2020',
                              12: '5/1/2020',
                              13: '6/1/2020',
                              14: '7/1/2020',
                              15: '8/1/2020',
                              16: '9/1/2020',
                              17: '10/1/2020',
                              18: '11/1/2020',
                              19: '5/1/2019',
                              20: '6/1/2019',
                              21: '7/1/2019',
                              22: '8/1/2019',
                              23: '9/1/2019',
                              24: '10/1/2019'},
                             'Diff': {0: 160.4279,
                              1: 136.0248,
                              2: 174.0513,
                              3: 112.0424,
                              4: 141.8488,
                              5: 103.5522,
                              6: 125.6087,
                              7: 145.2591,
                              8: 115.5121,
                              9: 185.7191,
                              10: 126.7386,
                              11: 231.3461,
                              12: 97.02587,
                              13: 42.85235,
                              14: 124.666,
                              15: 357.9974,
                              16: 490.9587,
                              17: 204.5478,
                              18: 287.6025,
                              19: 12.38486,
                              20: -2.61735,
                              21: -5.6187,
                              22: 3.204252,
                              23: -25.3782,
                              24: -10.9717},
                             'Percent': {0: 11.108089999999999,
                              1: 8.476797999999999,
                              2: 9.998946,
                              3: 5.851551000000001,
                              4: 6.998691,
                              5: 4.774984,
                              6: 5.528085,
                              7: 6.058016,
                              8: 4.542251,
                              9: 6.985672999999999,
                              10: 4.455896,
                              11: 7.786734,
                              12: 3.02981,
                              13: 1.298792,
                              14: 3.729997,
                              15: 10.326089999999999,
                              16: 12.8358,
                              17: 4.739428,
                              18: 6.362292,
                              19: 5.780551,
                              20: -1.15487,
                              21: -2.50814,
                              22: 1.4671530000000002,
                              23: -11.4521,
                              24: -5.5913699999999995},
                             'Date': {0: '04-01-2019 to 05-01-2019',
                              1: '05-01-2019 to 06-01-2019',
                              2: '06-01-2019 to 07-01-2019',
                              3: '07-01-2019 to 08-01-2019',
                              4: '08-01-2019 to 09-01-2019',
                              5: '09-01-2019 to 10-01-2019',
                              6: '10-01-2019 to 11-01-2019',
                              7: '11-01-2019 to 12-01-2019',
                              8: '12-01-2019 to 01-01-2020',
                              9: '01-01-2020 to 02-01-2020',
                              10: '02-01-2020 to 03-01-2020',
                              11: '03-01-2020 to 04-01-2020',
                              12: '04-01-2020 to 05-01-2020',
                              13: '05-01-2020 to 06-01-2020',
                              14: '06-01-2020 to 07-01-2020',
                              15: '07-01-2020 to 08-01-2020',
                              16: '08-01-2020 to 09-01-2020',
                              17: '09-01-2020 to 10-01-2020',
                              18: '10-01-2020 to 11-01-2020',
                              19: '04-01-2019 to 05-01-2019',
                              20: '05-01-2019 to 06-01-2019',
                              21: '06-01-2019 to 07-01-2019',
                              22: '07-01-2019 to 08-01-2019',
                              23: '08-01-2019 to 09-01-2019',
                              24: '09-01-2019 to 10-01-2019'}})
    
    fig = px.line(df, x="Date", y="Diff", color = 'POD')
    
    import plotly.graph_objects as go
    included = 0
    
    lim = {'IAD': {'lower': 90,'upper': 350, 'color':'yellow'},
           'SJCtest': {'lower': 10,'upper': 12, 'color':'green'}}
    
    for i, d in enumerate(fig.data):
        for j, y in enumerate(d.y):
            if y < lim[d.name]['lower'] or y > lim[d.name]['upper']:
                 
                 if j == 0:
                    fig.add_traces(go.Scatter(x=[fig.data[i]['x'][j]],
                                              y=[fig.data[i]['y'][j]],
                                              mode = 'markers',
                                              marker = dict(color=lim[d.name]['color']),
                                              name = d.name + ' threshold',
                                              legendgroup = d.name + ' threshold'))
                    included = included + 1
                 else:
                    fig.add_traces(go.Scatter(x=[fig.data[i]['x'][j-1], fig.data[i]['x'][j]],
                                              y=[fig.data[i]['y'][j-1], fig.data[i]['y'][j]],
                                              mode = 'lines',
                                              # marker = dict(color='yellow'),
                                              line = dict(width = 6, color = lim[d.name]['color']),
                                              name = d.name + ' threshold',
                                              legendgroup = d.name + ' threshold',
                                              showlegend = False if included > 0 else True,
                                             ))
                    included = included + 1
                
    fig.show()