Enable x-zoom in altair chart

121 Views Asked by At

I'd like to be able to zoom into my time series in altair. I understand that this is currently still work in progress, but I hoped to be able to circumvent this restriction by the following idea

import numpy as np
import altair as alt
import polars as pl


n = 1000
x = np.linspace(0, 10, 1000)
y = np.sin(x) + np.sin(10 * x) * 0.2
df = pl.DataFrame({"x": x, "y": y})

brush = alt.selection_interval(encodings=["x"])

(
    alt.Chart(df)  # type: ignore
    .mark_point()
    .encode(
        x=alt.X("x:Q").scale(domain=brush.ref()),
        y="y:Q",
        color=alt.condition(brush, alt.value("red"), alt.value("lightgray")),  # type: ignore
    )
    .add_params(brush)
    .properties(width=1200, height=600)
)

The idea was to apply the brush's ref as the x-domain. However, the update of the x-axis triggers immediately upon open up the selection. It would need to only trigger upon the subsequent release of the mouse. I know that there's an on field for the selection_interval, but I am unable to get this working.

Is there a away to apply the update for the x-domain only after releasing the selection?

2

There are 2 best solutions below

1
Alexander Braekevelt On

Does it need to be in Altair? There are other python libraries which are more optimized for zooming in on timeseries data. For example Plotly + Plotly Resampler.

demo of zooming in on timeseries data in 3 panels

0
Steven Davis On

There is a way to do implement zoom on an Altair chart. The easiest way is to accept the default zoom that is built inside of the select_interval() method.

n = 1000
x = np.linspace(0, 10, n)
y = np.sin(x) + np.sin(10 * x) * 0.2

df = pl.DataFrame({"x": x, "y": y})

# Define an interval selection for interactive brushing
brush = alt.selection_interval(encodings=['x'],
                               bind='scales'
                              )

# Create the chart
chart = alt.Chart(df).mark_point().encode(
    x='x',
    y='y',
    color=alt.condition(brush, alt.value('red'), alt.value('lightgray'))
).add_params(brush).properties(width=800, height=400)

This is simply just binding the scale to the brush and lets you pan with left mouse and use the mouse wheel to zoom in and out of the chart. This is the default for Altair zoom.

But what this doesn't achieve is grabbing the interval you are wanting to select and zoom into the Altair chart on the selected area. The issue with achieving this way of zooming into the chart is because of the way Altair Vega/Vega-lite zoom method works.

At a high level you will have limited abilities to zoom as the attributes the method select_interval method imposes which can be seen here. Zoom is only supported by the mouse wheel for zooming into a chart. There is a reason for this, and it makes trying to zoom by selecting an interval with any brush method and zooming in on region incredibly difficult.

The difficulty comes from the way Vega operates versus the high-level API. This is also why the "on" field doesn't work as would be expected. Vega operates in a json format and actually allows for fine grained control. The easiest way to find what the chart looks like in json is to use the Vega Editor.

When you make a chart you will have a button in the top right of the chart allowing you to open the chart in the html Vega Editor. Options

This is where all of the chart logic takes place. The high-level API just generates this json instead of having to write it ourselves. This is also where the event steams take place like you referenced earlier, but on a lower level. This lets us actually edit the event streams to change the chart at an incredibly fine level.

The most important changes we can make here are of course the event-stream, and the expressions or methods of the charts. Here we can take a closer look at how the "zoom" method is called and why it is difficult to even change the zoom in Altair.

When inspecting any charts zoom you will find it calls zoomLinear method on the chart. zoomLinear(domain, anchor, scaleFactor). The way Altair/Vega calculates how to zoom it calls the domain (X-axis), the anchor (the x coordinate your mouse is on), and the scale factor (This is where the problem is). This "scale factor" that is generated by the API only works with a mouse wheel. And the logic is written that way, here is the Vega editor for this specific chart. After scrolling past the data that has been generated we will come across the methods that have to deal with zoom and panning these are all contained in the signals attribute of the chart.

By altering the logic of translate_delta (panning). I was able to switch the way the chart pans. Instead of left mouse click for panning I was able to change it so that you need to hold shift to pan. Unfortunately, I didn't save this and only have this screenshot.

Altered: panning

Original: panning2

But if we look at the zoom_delta (zooming), which is the were the scaling factor is calculated, we can see the issue with rewriting this.

Original: zoomingog

My attempt that didn't go well.

Altered: zooming

Because the zoomLinear method needs this scaling factor you can't just throw in 2 x coordinates for the method to zoom into. This means you would need to write your own custom zoom logic in order to the interval zoom to work like how you would like. I was attempting to do this by making a start_coord and end_coords to try and write this custom logic. But came up with nothing. Best of luck and I am sure you can write this form of zoom logic into a chart it will just take time to find a way to accomplish it. You would need to take the 2 X coordinates and turn them into a singular scaling factor that would zoom in to the given interval.