Method for zooming on part of graph without losing overview

I am looking for a method to zoom on a certain part of a graph without losing the overview of the complete graph, like shown on the image. Dash doesn’t have a build in function for this, so i was wondering if anyone had an idea how to accomplish this functionality, with maybe another library or something? Any help is welcome!

My guess is there’s not an easy way to do that. But, i think you could probably cobble together something that looks similar. Grabbing the selection from the graph, make a new plot and put it on top of the main one with a hidden div or something that only shows up if there is a selection.

Thanks, but as someone who has literally zero experience with Dash, I have no idea on how to even use the selected part of the graph as input to make a new graph. Any usefull links/functions for this?

Sure, something like this might do what you want. Depending on your experience with plotly, this may or may not be easy for you to modify to your own uses.

import numpy as np
import plotly.express as px
import dash
from dash import dcc, html, Input, Output, State

# Set up dash
app = dash.Dash()

# get random data
x = np.arange(100)
y = np.random.rand(1, 100)[0]*10

#make figure
figure = px.line(x=x, y=y)

# set default plotly dragmode to select so we can select without automatically zooming in. To deselect, double click.
figure.update_layout(dragmode="select")

# height and width of the full graph
full_height=500
full_width=800

# the size modifier for zooming
zoom_perc = 0.2
zoom_height = full_height * zoom_perc
zoom_width = full_width * zoom_perc

# style to use when zoomed. Using z-indexs to push to background or bring to front. But, you can also use "display":"none" on the hidden_style if you want.
zoomed_style = {
    "z-index":'100',
    "position":"absolute", 
    "top":"10%", 
    "right":"10%", 
    "height":zoom_height, 
    "width":zoom_width, 
    "border": "2px solid black"
}
#the style to use when not zoomed. Z-index pushes it to background
hidden_style = {
    "z-index":"-1", 
    "position":"absolute", 
    "top":"10%", 
    "right":"10%", 
    "height":zoom_height, 
    "width":zoom_width,
    "border": "2px solid black"
}

# define the app layout
app.layout = html.Div(
    [
        # full graph with initial figure
        dcc.Graph(
            id="graph",
            figure=figure,
            style={"width":"100%", "height":"100%"}
        ),
        # div to put zoom graph in
        html.Div(
            # zoom graph with no figure, going to make it in callback. Turn off menu bar and make plot static (optional)
            dcc.Graph(
                id="zoom-graph", 
                style={"width":"100%", "height":"100%"},
                config={"displayModeBar":False, 'staticPlot': True}
            ),
            id="zoom-div",
            # initial style is hidden
            style=hidden_style
        )
    ],
    # style to use for full div. "position": "relative" is important
    style={"width":full_width, "height":full_height, "position":"relative"}
)


# callback 
@app.callback(
    # output is the zoom-graph figure and zoom-div style
    Output("zoom-graph", "figure"),
    Output("zoom-div", "style"),
    # input is selectedData from the full graph
    Input("graph", "selectedData"),
    # state is the full figure, you could probably do it without this but this is how i chose to do it. I had trouble updating the x,y axes to zoom in with plotly.graph_objects.
    State("graph", "figure")
)
def f(selectedData, full_figure):
    # if there is no selection or the full_figure is empty, return an empty graph and the hidden style
    if selectedData is None or full_figure is None:
        return px.line(), hidden_style

    # get the zoomed range from selectedData
    x_zoom = selectedData['range']['x']
    y_zoom = selectedData['range']['y']

    # get the data from the full_graph
    x = full_figure['data'][0]['x']
    y = full_figure['data'][0]['y']
    
    #make a new graph.
    figure = px.line(x=x, y=y)

    # zoom in, turn off the x and y labels, and remove margins around the graph to have less border.
    figure.update_layout(
        xaxis={"range": x_zoom, "title": None},
        yaxis={"range": y_zoom, "title": None},
        margin={"l":0, "r":0,"t":0,"b":0},
    )

    # return new zoomed figure and the zoom style
    return figure, zoomed_style
    



if __name__ == "__main__":
    app.run_server(debug=True)
2 Likes