Y-axis autoscaling with x-range sliders

Afaik, y-axis cant be made to auto scale when using x-range sliders. Y range is chosen with respect to the y values of the whole x range and does not change after zooming-in. This is especially annoying with candlestick charts in volatile periods. When you zoom-in using x-range slider, you essentially get flat candlesticks as their fluctuations only cover a very small part of the initial range. After doing some research it seems that some progress has been made here: https://github.com/plotly/plotly.js/pull/2364. Anyone knows if there is a working solution for plotly.py ? I think the team/devs should really look into it, candlestick demo is the first example on the official site, yet y-axis autoscale does not work which makes the whole demonstration look kinda weird.

Thank you for your time.

1 Like

Did you find a way to resolve this issue?

@TOTORO @chriddyp or anyone else, is there any working solution? setting animate=False did not work… :confused:

Hi @TOTORO and @s1kor,

I came up with a Python solution to this using the new FigureWidget class. The basic idea is the register a Python callback function to run whenever the xaxis range changes. In response, the callback function computes and applies a new yaxis range.

First construct a simple scatter plot line plot with a range slider. Make sure to set the

import plotly.graph_objs as go 

from datetime import datetime
import pandas_datareader.data as web

df = web.DataReader('AAPL.US', 'quandl',
                    datetime(2007, 10, 1),
                    datetime(2009, 4, 1))

# Make sure dates are in ascending order
# We need this for slicing in the callback below
df.sort_index(ascending=True, inplace=True)

trace = go.Scatter(x=list(df.index),
                   y=list(df.High))

data = [trace]
layout = dict(
    title='Time series with range slider and selectors',
    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'
    )
)

fig = go.FigureWidget(data=data, layout=layout)
fig

Then install a property change callback to update the yaxis range

def zoom(layout, xrange):
    in_view = df.loc[fig.layout.xaxis.range[0]:fig.layout.xaxis.range[1]]
    fig.layout.yaxis.range = [in_view.High.min() - 10, in_view.High.max() + 10]

fig.layout.on_change(zoom, 'xaxis.range')

Now, when you manipulate the range slider, the yaxis will automatically adjust to the data in view (plus a 10 unit buffer on each side, you should adjust that to what looks right for your data.

Hope that helps!
-Jon

3 Likes

is there any other solution? like change something in rangeslider?

1 Like

Not that I’m aware of.
-Jon

Hi thank you for your solution it exactly what i need it…
However i was unable to implement correctly the code
could someone figure it out what wrong in the last part for this code
Thank

fig = go.FigureWidget(data=data, layout=go.layout)
fig

#Y-axis autoscaling with x-range sliders

def zoom(layout, xrange):
in_view = df.loc[fig.layout.xaxis.range[0]:fig.layout.xaxis.range[1]]
fig.layout.yaxis.range = [in_view.trace_low.min() - 2, in_view.trace_high.max() + 2]

fig.layout.on_change(zoom, ‘xaxis.range’)

fig

plotly.offline.plot(fig, filename = “Time Series Forecast Multivariable (61) MultiStep (3).html”)

Hi @elaliberte,

For the on_change callbacks to be triggered you need to let the FigureWidget display itself (make fig the last statement in a notebook output cell). And these callbacks won’t work on the HTML result of plotly.offline.plot unfortunately because the callbacks require a running Python kernel.

Hope that helps clear things up,
-Jon

我也碰到相同的问题,请问:plotly.offline.plot 应该怎样做呢?
谢谢!

Hi Jon, Thank you for sharing this solution and this what I am looking for. I tried the code, but I got error after I play the slider frequently.

The code is below: (I just tried the code that you shared):
import plotly.graph_objs as go

from datetime import datetime
import pandas_datareader.data as web

df = web.DataReader(‘AAPL’, ‘yahoo’,
datetime(2012, 10, 1),
datetime(2015, 4, 1))

df.sort_index(ascending=True, inplace=True)

trace = go.Scatter(x=list(df.index),
y=list(df.High))

data = [trace]
layout = dict(
title=‘Time series with range slider and selectors’,
xaxis=dict(
rangeslider=dict(
visible = True
),
type=‘date’
)
)

fig = go.FigureWidget(data=data, layout=layout)

def zoom(layout, xrange):
in_view = df.loc[fig.layout.xaxis.range[0]:fig.layout.xaxis.range[1]]
fig.layout.yaxis.range = [in_view.High.min() - 10, in_view.High.max() + 10]

fig.layout.on_change(zoom, ‘xaxis.range’)

fig

The error message is like this (When I got this error, I still can use the slider. Thus I do not know how to fix it. If you have any idea, please let me know. Thank you a lot!!):

KeyError Traceback (most recent call last)
/anaconda3/lib/python3.7/site-packages/pandas/core/indexes/datetimes.py in slice_indexer(self, start, end, step, kind)
1105 try:
-> 1106 return Index.slice_indexer(self, start, end, step, kind=kind)
1107 except KeyError:

/anaconda3/lib/python3.7/site-packages/pandas/core/indexes/base.py in slice_indexer(self, start, end, step, kind)
4672 start_slice, end_slice = self.slice_locs(start, end, step=step,
-> 4673 kind=kind)
4674

/anaconda3/lib/python3.7/site-packages/pandas/core/indexes/base.py in slice_locs(self, start, end, step, kind)
4877 if end is not None:
-> 4878 end_slice = self.get_slice_bound(end, ‘right’, kind)
4879 if end_slice is None:

/anaconda3/lib/python3.7/site-packages/pandas/core/indexes/base.py in get_slice_bound(self, label, side, kind)
4797 # to datetime boundary according to its resolution.
-> 4798 label = self._maybe_cast_slice_bound(label, side, kind)
4799

/anaconda3/lib/python3.7/site-packages/pandas/core/indexes/datetimes.py in _maybe_cast_slice_bound(self, label, side, kind)
1058 _, parsed, reso = parsing.parse_time_string(label, freq)
-> 1059 lower, upper = self._parsed_string_to_bounds(reso, parsed)
1060 # lower, upper form the half-open interval:

/anaconda3/lib/python3.7/site-packages/pandas/core/indexes/datetimes.py in _parsed_string_to_bounds(self, reso, parsed)
875 else:
–> 876 raise KeyError
877

KeyError:

During handling of the above exception, another exception occurred:

KeyError Traceback (most recent call last)
/anaconda3/lib/python3.7/site-packages/ipywidgets/widgets/widget.py in _handle_msg(self, msg)
672 if ‘buffer_paths’ in data:
673 _put_buffers(state, data[‘buffer_paths’], msg[‘buffers’])
–> 674 self.set_state(state)
675
676 # Handle a state request.

Hello @jmmease
I tried to print something inside the callback function.

def zoom(layout, xrange):
    in_view = df.loc[fig.layout.xaxis.range[0] : fig.layout.xaxis.range[1]]
    fig.layout.yaxis.range = [in_view.High.min() - 10, in_view.High.max() + 10]
    print(in_view.High.max())

but nothing is printed in jupyterlab. The error message isn’t printed either.
however, I tested at jupyter notebook, it works fine.

Is it a problem with jupyterlab extension? If there is any solution, what should I do?

Thank you.

three reasons why you should buy plotly pro: support open source, get great support, host your plots and dashboards online