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):

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.
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])

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With