Jupyter widget: zoomable histogram

Hi Jon,

first of all congratulations on the new features which you brought to the python version of plotly! I really love all the widget interactivity and callbacks. This opens a new universe! Your effort has been really great. Maybe you want to further advertise this on the github readme? Because at first sight, I did not read the change log because I did not expect such profound changes! Afterwards, I stumbled upon the release Medium post and, boy, this was a great day :smiley:

Playing around with the callbacks, I was wondering on how to create a zoomable histogram.
Something similar to https://uwdata.github.io/falcon/flights/
Here you can zoom into the histogram with the mouse wheel and the histogram will recalculate the bins.

So, the bin size should stay the same but we want to subscribe on a change of the xaxis of the figure layout.
If the user zoom, then the bin width should be reduced to still show the same number of bins. Also, the y-axis should be adjusted

What is the best way to implement this for the Jupyter lab/notebook platform?
Should the on_change callback create a new trace and hide the old one?
The user should still be able to reset the zoom with the double click.

Best regards,
Florian

Hi @kite_and_code,

Thanks a lot for the kind words! This is a pretty neat example, and yes it is possible to do now with the FigureWidget.

The general idea is to display the data using a plotly histogram trace type. This trace has built-in logic for bin selection based on the number of samples and the spread. Then, listen for changes in the xaxis range and update the data passed to the histogram to include only the points that are present within the current view. This will cause the trace to recompute binning and set the appropriate y-axis range. To get double-click to restore the original view, make sure you explicitly specify the xaxis range of the initial trace.

Hereโ€™s an example

# imports
import plotly.plotly as py
import plotly.graph_objs as go
import numpy as np

# Generate dataset
x = np.random.randn(5000)

# Create figure and get reference to histogram trace
fig = go.FigureWidget(
    [go.Histogram(x=x)],
    go.Layout(
        title='Dynamic Histogram',
        xaxis={'range': [-4, 4],
               'title': 'x'},
        yaxis={'title': 'count'},
        bargap=0.05))
hist = fig.data[0]

# Install xaxis zoom callback
def handle_zoom(xaxis, xrange):
    filtered_x = x[np.logical_and(xrange[0] <= x, x <= xrange[1])]
    hist.x = filtered_x
fig.layout.xaxis.on_change(handle_zoom, 'range')

# Display FigureWidget
fig

Hope that helps, and have fun! :slightly_smiling_face:
-Jon

2 Likes

Hi Jon,

thank you so much for your answer.
It works great.

However, I noticed one issue with the go.Histogram.
And this is, that the nbinsx cannot be changed arbitrarily?
For example when I set

fig.data[0].nbinsx = 19

Then the figure will only update for some specific values. Currently it changes similar to those breakpoints: 2, 4, 5, 10, 20, 50, 75
Is there a way to prevent this arbitrary binning?
Or is the only solution to calculate the histogram yourself with pandas and then just let plotly show the result as a bar chart?

Currently, I use the histogram with ipywidgets which should allow the user to set the binning. And, there I also have this problem. However, the problem also exists if I set the value manually as described aboveโ€ฆ

Best regards,
Florian

Hi @kite_and_code,

Yeah, the nbinsx property only sets the max number of bins it doesnโ€™t force the exact number. You can be more precise by using the xbins.size and xbins.start properties. Hereโ€™s an updated example that will always show exactly 9 bins (You could also set num_bins in an ipywidget callback to change it interactively)

# imports
import plotly.plotly as py
import plotly.graph_objs as go
import numpy as np

# Generate dataset
x = np.random.randn(5000)

# Create figure and get reference to histogram trace
fig = go.FigureWidget(
    [go.Histogram(x=x)],
    go.Layout(
        title='Dynamic Histogram',
        xaxis={'range': [-4, 4],
               'title': 'x'},
        yaxis={'title': 'count'},
        bargap=0.05))
hist = fig.data[0]

num_bins = 9
# Install xaxis zoom callback
def handle_zoom(xaxis, xrange):
    filtered_x = x[np.logical_and(xrange[0] <= x, x <= xrange[1])]
    with fig.batch_update():
        hist.x = filtered_x
        hist.xbins.start = xrange[0]
        hist.xbins.size = (xrange[1] - xrange[0])/9
    
fig.layout.xaxis.on_change(handle_zoom, 'range')

# Display FigureWidget
fig

DynamicHist2

Hope that helps!
-Jon

1 Like

Hi Jon!
I was wondering if it is possible to save a graph like this from jupyter notebook to the cloud. I notice that every time I try to do so I lose the rebinning feature that I like so much.
Thanks!
Erica

Hi @ericasaw,

Unfortunately this isnโ€™t possible, because the rebinning relies on executing Python code which isnโ€™t available in the chart studio context. If you need to host this kind of functionality outside of the jupyter notebook, it would be possible to implement it using Dash (See https://dash.plot.ly/interactive-graphing for info on figure callbacks in Dash).

-Jon

First of all, thanks a lot for all the help!

I am trying to achieve the same kind of dynamic-zoomable histogram inside a Dash app. Do you have any reference or example that I can check out?

I can try to post here a minimal working example but unfortunately I couldnโ€™t make it work yet :slight_smile:

Thanks again

Hi @jmmease,

Thanks for your effort first. My question is how can i use Dynamic Hitogram in my Dash App. Actually i tried a lot to write a callback but fail to write. Can you please help me out.

Thanks in advance.