fastplotlib icon indicating copy to clipboard operation
fastplotlib copied to clipboard

Share axes between subplots

Open asmunder opened this issue 8 months ago • 7 comments

I'm using fastplotlib to visualize a set of timeseries that has a few hundred sensors measuring two distinct variables with different units. This works fine with two scatter subplots stacked vertically, one for each variable.

Since the x-axis has identical range between my subplots (but the y-axis is very different), it would be really nice to have a linked pan/zoom/etc. between them. This corresponds exactly to the "sharex" (or "sharey") arguments that can be passed to plt.subplots() in matplotlib.

Is there a way to achieve this in the current version of fastplotlib?

I'm also interested to know if there is a way to get something resembling timestamps on the x-axis (but I would guess this is future work).

asmunder avatar May 16 '25 14:05 asmunder

Hi @asmunder

Thanks for posting. I recommend looking at our examples gallery, where some examples show you how you can sync the controllers of different subplots. See here under "Controller examples".

I believe this should be what you are looking for. If not, please let us know and we can provide more help :D

clewis7 avatar May 16 '25 19:05 clewis7

And yes, timestamps (or in general, more detailed/flexible axes) are something we hope to add in the future.

clewis7 avatar May 16 '25 19:05 clewis7

If by timestamps you just mean adding an "s" to the tick value, you can set a function to map tick values to any output string, just note that the exact API for axes things might change in the future:

fig[0, 0].axes.x.tick_format = lambda v, min_v, max_v: f"{v} sec"

kushalkolar avatar May 16 '25 21:05 kushalkolar

Hi @clewis7 , @kushalkolar , thanks for the quick responses.

I followed the suggestion to try the "sync subplots" example. This approach would be fine if I wanted to sync both the x- and y-axis. But the scale of my data on the y-axis is different between the subplots, in my case something like [0 .. 1] and [-30 .. 50]. With the "sync subplots" option of sharing the controller, the y-axis range is forced to be identical on both subplots, so I can't really see both datasets at the same time.

What I was able to do was to first normalize my data such that everything is [0 .. 1] and then pass functions to tick_format which undo the normalization in the tick label formatting. It's a hack for sure, and the zoom is still linked on both axes (which I don't want), but at least I can see all the data with sensible ranges.

I had a look in the source for fastplotlib and in pygfx at the panzoom controller. In order to get equivalent functionality to the sharex = True, sharey = False setting in matplotlib, would this require a new PanZoomSharexController (and similar for Sharey), so that you'd get multiple controllers with one linked axis that you pass to the controller_ids argument of the Figure constructor? Or could it be possible to do something like this using several of the existing PanZoomController and synchronizing them using bidirectional events?

asmunder avatar May 20 '25 07:05 asmunder

Ah I see, this is a WIP, relevant issue: #783 . This should allow customizable linking of specific axes.

also somewhat related, #772 and #809

kushalkolar avatar May 20 '25 07:05 kushalkolar

Thanks for clarifying! Will follow the developments on this.

As for the timestamp formatting on the axis, below is a minimal example that works quite nicely for a Pandas dataframe with a DatetimeIndex, in case it is useful for others. Here I'm using the date2num and num2date functions from matplotlib.dates.

The only gripe here is that sometimes the label texts overlap at some zoom levels. Is there any way to adjust that?

import numpy as np
import pandas as pd
import fastplotlib as fpl
import matplotlib.dates as mdates

# Generate random dataframe with DatetimeIndex
N_periods = 10000
df = pd.DataFrame(
    data={"values": np.random.uniform(high=42, size=N_periods)},
    index=pd.date_range(start="2025-01-01", periods=N_periods, freq="h"),
)
print(df)

# Convert to numpy array that fastplotlib can use
data = np.vstack(
    [
        mdates.date2num(df.index),
        df["values"].to_numpy(),
    ]
).T

# Create the plot
fig = fpl.Figure()
fig[0, 0].add_scatter(data=data)

# Define datetime formatter
def axis_datetime(v, min_v, max_v):
    w = mdates.num2date(v)
    label = w.strftime("%Y-%m-%d")
    return label

# Apply datetime formatter and set free aspect ratio on the axes
fig[0, 0].axes.x.tick_format = axis_datetime
fig[0, 0].auto_scale(maintain_aspect=False)

# Show the fig
fig.show()
fpl.loop.run()

asmunder avatar May 20 '25 08:05 asmunder

If you're on fastplotlib and pygfx main branch you could try creating a fig with synced controllers and add the camera of one subplot to another subplot's controller and specify the include state.

Not sure if it'll work but I think something along those lines and the issue I posted is almost the general solution.

On Tue, May 20, 2025, 04:28 Åsmund Ervik @.***> wrote:

asmunder left a comment (fastplotlib/fastplotlib#821) https://github.com/fastplotlib/fastplotlib/issues/821#issuecomment-2893433603

Thanks for clarifying! Will follow the developments on this.

As for the timestamp formatting on the axis, below is a minimal example that works quite nicely for a Pandas dataframe with a DatetimeIndex, in case it is useful for others. Here I'm using the date2num and num2date functions from matplotlib.dates.

The only gripe here is that sometimes the label texts overlap at some zoom levels. Is there any way to adjust that?

import numpy as npimport pandas as pdimport fastplotlib as fplimport matplotlib.dates as mdates

Generate random dataframe with DatetimeIndexN_periods = 10000df = pd.DataFrame(

data={"values": np.random.uniform(high=42, size=N_periods)},
index=pd.date_range(start="2025-01-01", periods=N_periods, freq="H"),

)print(df)

Convert to numpy array that fastplotlib can usedata = np.vstack(

[
    mdates.date2num(df.index),
    df["values"].to_numpy(),
]

).T

Create the plotfig = fpl.Figure()fig[0, 0].add_scatter(data=data)

Define datetime formatterdef axis_datetime(v, min_v, max_v):

w = mdates.num2date(v)
label = w.strftime("%Y-%m-%d")
return label

Apply datetime formatter and set free aspect ratio on the axesfig[0, 0].axes.x.tick_format = axis_datetimefig[0, 0].auto_scale(maintain_aspect=False)

Show the figfig.show()fpl.loop.run()

— Reply to this email directly, view it on GitHub https://github.com/fastplotlib/fastplotlib/issues/821#issuecomment-2893433603, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACHXXREW4IVRSCZ655RV26327LRRXAVCNFSM6AAAAAB5I4WYWCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDQOJTGQZTGNRQGM . You are receiving this because you were mentioned.Message ID: @.***>

kushalkolar avatar May 20 '25 08:05 kushalkolar