pythonpandasplotlyplotly.graph-objects

Can yaxis be dynamically added into an graph_objects Layout in plotly?


In my practical training that I'm undergoing right now there is a python script that reads a CSV file and plots against selected columns of that file. However the selection of headers is hardcoded, so if anybody wants to use the script they have to manipulate the code. My task is to make it all dynamic, e. g. the user of the script can select any number of columns via console (argparse) and the script automatically creates the traces, creates the layout, adds both to a figure and exports it into a html file.

I have managed to accomplish all of that except for the Layout part. In the current (hardcoded) state of the script, there are these arguments that are passed to the graph_objects.Layout function:

 layout = go.Layout(title=inFile,
                       plot_bgcolor='rgb(230, 230,230)', showlegend=True,
                       yaxis=dict(
                           title=df.columns[y1graph] # Note: 'ygraph' contains the index of the column
                       ),
                       yaxis2=dict(
                            title=df.columns[y2graph],
                            side='right',
                            overlaying='y'
                       ),
                       yaxis3=dict(
                            title=df.columns[y3graph],
                            side='right',
                            overlaying='y'
                       )
                    )

Unfortunately I neither have found a way to make that all dynamic, so that the "yaxis"-arguments are added according the number of selected columns. Nor have I found a way to add titles to the graphs, make them overlay each other and put them to the right side the same way go.Layout does. There is a way of course to add titles with plotly express, but it kinda doesn't do the same thing for me in respect to the overlaying and side arguments.

Any ideas?

Please note: It's my very first question here on stackoverflow so if I did do anything wrong, please advice! Also, if I have left out crucial information please let me know.


Solution

  • import numpy as np
    import plotly.express as px
    import pandas as pd
    
    # simulate a CSV, 20 columns...
    df = pd.DataFrame(
        {
            chr(ord("A") + i): np.random.uniform(miny, miny + 200, 30)
            for i, miny in zip(range(20), np.random.randint(30, 3000, 20))
        }
    )
    
    # simulate user passing columns to plot...
    cols = pd.Series(df.columns).sample(4).tolist()
    
    # build figure with each trace using it's own yaxis
    fig = px.line(df, y=cols).for_each_trace(
        lambda t: t.update(yaxis=f"y{cols.index(t.name)+1 if cols.index(t.name)>0 else ''}")
    ).update_layout(yaxis={"title":cols[0]})
    
    # dynamically update yaxes...
    fig.update_layout(
        {
            t.yaxis.replace("y", "yaxis"): {
                "title": t.name,
                "overlaying": "y",
                "side": "right",
                "position":1-(i/15)
            }
            for i,t in enumerate(fig.data)
            if t.yaxis != "y"
        }
    ).update_layout(xaxis={"domain":[0,1-(len(cols)*.05)]}) # give some space for additional yaxes
    

    enter image description here