Search code examples
python-3.xplotly

Python Plotly barchart - how do I add a horizontal line on the chart?


I have a barchart as displayed; enter image description here

The code that produced this is as follows;

def create_bar_chart(_dict, _title, _title_text, titles, image_paths):
    _fig = go.Figure()
    df = pd.DataFrame(dict(score=_dict.values(), trait=_dict.keys()))
    _image = f"{_title_text}_sm.jpg"
    _width=600
    _height=400

    _fig = px.bar(df, x="trait", y="score")
    _fig.update_layout(xaxis_title=None)

I want to put a horizontal line (with a width the same as the bar) for each bar which will indicate the average value. This value may be above the bar or within the bar. So for example with analytical thinking the average value may be 0.2 or 0.4, so I need a horizontal line with that score. How do I do this?


Solution

  • Below is my interpretation of the demand in the question, where a horizontal line of the same width as each bar is drawn at the height of some average score

    import plotly.express as px
    import pandas as pd
    import numpy as np
    
    
    data_dict = {
        'trait': ['analytical thinking', 'cognitive processes', 'certainty', 'insight', 'abstract', 'concrete'],
        'score': [0.35, 0.15, 0.02, 0.05, 0.42, 0.2]
    }
    
    X_COUNT = len(data_dict['trait'])
    # The x-axis of bar chart is divided evenly by the number of x elements.
    # This width is not necessarily the bar width, but the entire width for
    # x element, which includes the bar and the two gaps on either side.
    TOTAL_WIDTH = 1 / X_COUNT
    # Set the gap between bars. It is a percentage of TOTAL_WIDTH. In other
    # words, if we have TOTAL_WIDTH = 1 and BAR_GAP = 0.2, then the bar width
    # would be 0.8 and the gaps between two bars would be 0.2
    BAR_GAP = 0.2
    
    fig = px.bar(pd.DataFrame.from_dict(data_dict), x='trait', y='score')
    fig.update_layout(
        width=600,
        height=400,
        bargap=BAR_GAP,
    )
    
    # The relative starting position of each bar (i.e., considering the width
    # of the chart as 1)
    bar_starts = np.cumsum([TOTAL_WIDTH] * X_COUNT) - 1 / X_COUNT
    
    averages = [0.2, 0.3, 0.1, 0.02, 0.35, 0.25]  # whatever average score to plot
    for bs, ave in zip(bar_starts, averages):
        line_st = bs + TOTAL_WIDTH * BAR_GAP / 2
        fig.add_shape(
            type='line',
            x0=line_st,
            y0=ave,
            x1=line_st + TOTAL_WIDTH * (1 - BAR_GAP),
            y1=ave,
            xref='paper',  # relative x position
            yref='y'  # exact y position as shown on the y-axis
        )
    fig.show()
    
    

    The plot looks like this

    enter image description here

    Limitation

    This solution does not work if the plot needs to zoom in or out. The line size does not scale with the zoom.