Search code examples
pythonplotlypython-polarsfacet

How to add horizontal lines dynamically to a facet plot in plotly python?


I am trying to add upper and lower limits red dashed horizontal lines to each facet Line plot based on respective column values but dashed lines are not formed at correct yaxis values of the facets.

I have tried below code and made several attempts but it is not creating the right yaxis dashed lines for the facets and gives a wrong chart output.

Code I have tried:

dataframe

import polars as pl
import datetime
import plotly.express as px

df_stack = pl.DataFrame({'Category': ['CATG1',   'CATG1',   'CATG1',   'CATG2',   'CATG2',   'CATG2',   'CATG3',   'CATG3',   'CATG3',   'CATG4',   'CATG4',   'CATG4',   'CATG5',   'CATG5',   'CATG5',   'CATG6',   'CATG6',   'CATG6',   'CATG7',   'CATG7'],
 'Value': [39.7,   29.0,   32.7,   19.0,   14.0,   15.0,   1.28,   1.2,   1.14,   5.6,   6.9,   4.9,   31.02,   24.17,   28.68,   14.49,   11.29,   13.4,   3.81,   3.5],
 'Lower Range': [12.0,   12.0,   12.0,   6.0,   6.0,   6.0,   0.9,   0.9,   0.9,   3.5,   3.5,   3.5,   23.0,   23.0,   23.0,   5.5,   5.5,   5.5,   2.5,   2.5],
 'Upper Range': [43.0,   43.0,   43.0,   21.0,   21.0,   21.0,   1.3,   1.3,   1.3,   7.2,   7.2,   7.2,   33.0,   33.0,   33.0,   19.2,   19.2,   19.2,   4.5,   4.5],
 'Date': [datetime.date(2022, 4, 1),
  datetime.date(2022, 9, 9),
  datetime.date(2023, 5, 15),
  datetime.date(2022, 4, 1),
  datetime.date(2022, 9, 9),
  datetime.date(2023, 5, 15),
  datetime.date(2022, 4, 1),
  datetime.date(2022, 9, 9),
  datetime.date(2023, 5, 15),
  datetime.date(2022, 4, 1),
  datetime.date(2022, 9, 9),
  datetime.date(2023, 5, 15),
  datetime.date(2022, 4, 1),
  datetime.date(2022, 9, 9),
  datetime.date(2023, 5, 15),
  datetime.date(2022, 4, 1),
  datetime.date(2022, 9, 9),
  datetime.date(2023, 5, 15),
  datetime.date(2022, 4, 1),
  datetime.date(2022, 9, 9)],
 'Color_flag': ['Normal',   'Normal',   'Normal',   'Normal',   'Normal',   'Normal',   'Normal',   'Normal',   'Normal',   'Normal',   'Normal',   'Normal',   'Normal',   'Normal',   'Normal',   'Normal',   'Normal',   'Normal',   'Normal',   'Normal']}
 )

Plot Code

# creating line plot
fig_facet_catg_31 = px.line(df_stack.to_pandas(), x='Date', y='Value', facet_row="Category", 
                            facet_col_wrap=2,facet_row_spacing=0.035)

# creating scatter plot
fig_facet_catg_3 = (px.scatter(df_stack.to_pandas(), 
                             x='Date', y='Value', color = 'Color_flag', 
                             color_discrete_sequence=["red", "green"], facet_row="Category"
                            , facet_row_spacing=0.035 )
                            .add_traces(fig_facet_catg_31.data)
                            .update_yaxes(matches=None,showticklabels=True)
                            .update_layout(height=600)
                            .for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))
            )

# Getting unique values of each category & converting it into a list
category_list = df_stack.sort('Category').get_column('Category').unique(maintain_order=True).to_list()

# Looping with index & value of category list
for i,elem in enumerate(category_list):
    print(i,elem)

    # filter for each category 1 by 1 in loop
    data = df_stack.filter(pl.col('Category')==elem)

    # Extract Upper & lower range values for respective category
    upper = data.item(0,"Upper Range")
    lower = data.item(0,"Lower Range")

    print(lower,upper)

    # Adding h_line() to each facet based on row index & range values inside loop.
    fig_facet_catg_3 = (fig_facet_catg_3.add_hline(y=upper, row=i, line_dash="dash", line_color = 'red')
                                        .add_hline(y=lower, row=i, line_dash="dash", line_color = 'red'))

fig_facet_catg_3

Output Incorrect Plot as each facet is not aligned to its upper lower limits:

enter image description here

Desired output: Ideally the lines should be within the dashed marking red lines but dashed lines are not using the correct y values.


Solution

  • Annotations for facet plots are created from left to right, and bottom to top (as explained in my answer here). In your case, since there's only one row, then your annotations are created bottom to top, so you need to index by n-i where n is the number of categories.

    # Looping with index & value of category list
    # annotations for facet plots are created left to right
    n_categories = len(category_list)
    for i,elem in enumerate(category_list):
        print(i,elem)
    
        # filter for each category 1 by 1 in loop
        data = df_stack.filter(pl.col('Category')==elem)
    
        # Extract Upper & lower range values for respective category
        upper = data.item(0,"Upper Range")
        lower = data.item(0,"Lower Range")
    
        print(lower,upper)
    
        # Adding h_line() to each facet based on row index & range values inside loop.
        fig_facet_catg_3 = (fig_facet_catg_3.add_hline(y=upper, row=n_categories-i, line_dash="dash", line_color = 'red')
                                            .add_hline(y=lower, row=n_categories-i, line_dash="dash", line_color = 'red'))
    

    enter image description here