Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Plotly - Autorescaling y axis range when range slider used

After a long search, I could not find any thread/discussion helping me to make autoscaling work with plotly.

The idea would be that when I use the x-range slider to go through the data, the y-axis would be dynamically rescaled each time I move the slider.

Currently, this is how it looks like (pretty unuseful): enter image description here

Here is a snippet of my code:

fig = plt.figure()
    
    # Layout format and buttons
    layout = dict(
        xaxis=dict(
            rangeselector=dict(
                buttons=list([
                    dict(count=1,
                         label='1m',
                         step='month',
                         stepmode='backward'),
                    dict(count=6,
                         label='6m',
                         step='month',
                         stepmode='backward'),
                    dict(count=1,
                        label='YTD',
                        step='year',
                        stepmode='todate'),
                    dict(count=1,
                        label='1y',
                        step='year',
                        stepmode='backward'),
                    dict(step='all')
                ])
            ),
            rangeslider=dict(
                visible = True
            ),
            type='date'
        )
    )
    
    # Plot open, high, low, close for each coins
    for c in dic.keys():
        ohlc = dic[c][['open','high','low','close']].copy()
        
        candlestick = go.Candlestick(x = ohlc.index,
                                     open = ohlc['open'],
                                     high = ohlc['high'],
                                     low = ohlc['low'],
                                     close = ohlc['close'],
                                     name = 'OHLC')
        
        fig = go.Figure(data = [candlestick],
                        layout = layout)
        
        fig.update_yaxes(showgrid=True, 
                          zeroline=False, 
                          showticklabels=True,
                          showspikes=True, 
                          spikemode='across', 
                          spikesnap='cursor', 
                          spikethickness=1, 
                          showline=True, 
                          spikedash='dot',
                          spikecolor="#707070",
                          autorange = True,     # DOESN'T WORK....
                          fixedrange= False     # DOESN'T WORK....
                          )
            
        fig.update_xaxes(showgrid=True, 
                          zeroline=False, 
                          showticklabels=True,
                          showspikes=True, 
                          spikemode='across', 
                          spikesnap='cursor', 
                          spikethickness=1, 
                          showline=True, 
                          spikedash='dot',
                          spikecolor="#707070",
                          type='date',
                          )
        
        fig.update_layout(title = 'OHLC for: '+c,
                          height = 750,
                          plot_bgcolor="#FFFFFF",
                          hovermode="x",
                          hoverdistance=100,
                          spikedistance=1000,
                          xaxis_rangeslider_visible=True,
                          dragmode='pan',
                          )
        
        fig.show()

I tried to use autorange = True and fixedrange = False but apparently that does nothing for reasons I don't understand.

like image 713
plonfat Avatar asked May 01 '26 00:05

plonfat


1 Answers

Plotly.py's interactive renderers rely on Plotly.js so we can hook into the plotly_relayout event and rescale the yaxis range when needed by passing the proper javascript code to the method go.Figure.show(), thanks to the post_script parameter. I provide here a JS/Plotly.js solution, and then the Python solution as it consists of embedding the JS code.

Note you might need to adapt the formatDateRange() function (in both cases) according to your actual OHLC data resolution (see comment).

Plotly.js :

Plotly.newPlot('<plot_id>', data, layout).then(gd => {

  gd.on('plotly_relayout', (e) => {
    if (
      !e || e.autosize || e.width || e.height ||  // plot init or resizing
      e['yaxis.range'] || e['yaxis.autorange'] || // yrange already adjusted
      e['yaxis.range[0]'] || e['yaxis.range[1]']  // yrange manually set
    ) {
      // Nothing to do.
      return dash_clientside.no_update;
    }

    if (e['xaxis.autorange']) {
      Plotly.relayout(gd, {'yaxis.autorange': true});
      return;
    }

    // NB. `formatDateRange()` depends on your OHLC data resolution (assuming one 
    // index per day here, formatted as 'yyyy-MM-dd'; but could be per hour, etc.) :
    // - Slider/zoom range uses datetime format 'yyyy-MM-dd hh:mm:ss.SSSS'
    // - need to match the date format 'yyyy-MM-dd'
    const formatDateRange = x => x.replace(/(\d{4}-\d{2}-\d{2}).*/, '$1');
    const xrange = gd._fullLayout.xaxis.range.map(formatDateRange);

    const ohlc = gd._fullData[0];
    let i0 = ohlc.x.indexOf(xrange[0]);
    let i1 = ohlc.x.indexOf(xrange[1]);

    if (i0 === -1) i0 = 0;
    if (i1 === -1) i1 = ohlc.x.length - 1;

    const data = [...ohlc.open.slice(i0, i1), ...ohlc.close.slice(i0, i1)];
    const ymin = Math.min(...data);
    const ymax = Math.max(...data);

    const room = Math.floor((ymax - ymin) / 20);
    const yrange = [ymin - room, ymax + room];

    Plotly.relayout(gd, {'yaxis.range': yrange});
  });
});

Plotly.py :

We use the placeholder '{plot_id}' for identifying the graph div whose id is generated in this case. The javascript snippet(s) are executed just after plot creation.

# ... build `fig`

fig.update_layout(
    xaxis_rangeslider_visible=True,
    xaxis_rangeslider_yaxis_rangemode="auto" # so we can still see the big picture in the rangeslider plot
)

js = '''
const gd = document.getElementById('{plot_id}');

gd.on('plotly_relayout', (e) => {
    if (
      !e || e.autosize || e.width || e.height || 
      e['yaxis.range'] || e['yaxis.autorange'] ||
      e['yaxis.range[0]'] || e['yaxis.range[1]'] 
    ) {
        return dash_clientside.no_update;
    }

    if (e['xaxis.autorange']) {
        Plotly.relayout(gd, {'yaxis.autorange': true});
        return;
    }

    const formatDateRange = x => x.replace(/(\d{4}-\d{2}-\d{2}).*/, '$1');
    const xrange = gd._fullLayout.xaxis.range.map(formatDateRange);

    const ohlc = gd._fullData[0];
    let i0 = ohlc.x.indexOf(xrange[0]);
    let i1 = ohlc.x.indexOf(xrange[1]);

    if (i0 === -1) i0 = 0;
    if (i1 === -1) i1 = ohlc.x.length - 1;

    const data = [...ohlc.open.slice(i0, i1), ...ohlc.close.slice(i0, i1)];
    const ymin = Math.min(...data);
    const ymax = Math.max(...data);

    const room = Math.floor((ymax - ymin) / 20);
    const yrange = [ymin - room, ymax + room];

    Plotly.relayout(gd, {'yaxis.range': yrange});
});
'''

fig.show(post_script=[js])

output-gif

like image 200
EricLavault Avatar answered May 03 '26 15:05

EricLavault