Learn how to use Dash Bio for next-gen sequencing & quality control. 🧬Register for the Oct 27 webinar.

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

4 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 应该怎样做呢?
谢谢!

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.

1 Like

Hello @jmmease is it possible that this solution does not work for Jupyter Lab?

Hello everyone, this is the exact solution i need. But i work with plotly js. Is there any similar solution in js ??

1 Like

Could this be used with the figure returned with make_subplots? i have a rather complicated chart with n number of subplots…and have been trying to find a solution to this problem.

fig = make_subplots(rows=len(self.panes), cols=1, shared_xaxes=True,
vertical_spacing=0.020, row_heights=row_height, specs=secondary_axis)

        fig.update_xaxes(showline=True, linewidth=1, linecolor='darkgray', mirror=True, ticks='outside', 
                showspikes=True, spikemode='across', spikesnap='cursor',
                rangebreaks=[dict(values = self._get_missing_dates(self.data))])

        fig.update_yaxes(showline=True, linewidth=1, linecolor='darkgray', mirror=True, ticks='outside', side='right',
                showspikes=True, spikemode='across', spikesnap='cursor')
        
        fig.update_layout(margin=dict(l=20, b=50, t=50, pad=10), font_size=10, 
                        hovermode='x unified',dragmode='zoom', 
                        yaxis2=dict(side='left', overlaying="y",constrain='domain',
                        xaxis_rangeslider_visible=False, xaxis_type="date")) 

Will all the above settings work with FigureWidget? lastly, is it possible to add horizontal lines using figurewidget? horizonal lines don’t seem to be drawn the same way (not treated as a ‘trace’) and is a method on the figure object itself

same here…did you find a solution to this?

Is it possible to do this in Dash’s dcc.Graph?