Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How do you show multiple plots in threads concurrently? #280

Open
bionicles opened this issue Feb 21, 2025 · 3 comments
Open

How do you show multiple plots in threads concurrently? #280

bionicles opened this issue Feb 21, 2025 · 3 comments

Comments

@bionicles
Copy link

bionicles commented Feb 21, 2025

TLDR: Ultimately, my question is, would it be possible to adapt the Plot type to be Send so we could more easily show multiple plots at once? (or is there another way to achieve that? should we serialize the plot?)

I love Plotly and use it a lot, one thing I noticed is plot.show() is blocking, and the natural way to show multiple plots at once would be to send them to threads, but something about the Traces prevents us from being able to show multiple plots at the same time:

Image

Image

This is workaroundable if we create threads to both create the plot and show it, like this:

Image

but then we can't return the plots made in those threads to our main thread:

Image

also, this approach might trip up if we want to create and show multiple different kinds of plots from multiple different kinds of plot_configs, although i guess we could use an enum like this:

Image

just curious how y'all handle this, right now I'm doing the workaround but thought it would be cool if we could just make a bunch of plots and send em around without issues

@bionicles
Copy link
Author

https://gist.github.com/bionicles/76775dd1feaf9149132149f0048d820b if anybody wants the code here

@andrei-ng
Copy link
Collaborator

Hi @bionicles,
To be honest, I personally never needed something like this. And for that reason I don't have good suggestions at this moment. Feel free to submit a PR if you have an idea about it.

Also, if you want you could turn your gist into examples in the examples folder.

@bionicles
Copy link
Author

Compiler Diagnostic for "Future Not Send"

this screwed me up today on server side rendering regarding clippy::future_not_send because i'm trying to cache the jsons so I can skip re-building them for every request to my server

taking a closer look, here's a compiler diagnostic about why Plot is not Send

Image

Image

Idea: PlotBuilder

not a big async expert, but maybe it's Box? what if we used Arc instead of Box for Traces? I tried swapping Box for Arc but it conflicts with FieldSetter bc Arc is meant to be immutable. One idea would be to switch to more of a PlotBuilder pattern which could be the mutable version, and then switch Plot to be the final finished immutable version, and use Arc inside of Plot to make it cheap to clone and compatible send / sync in multithreading environments. That might enable the crate to scale better in the future.

I've had insane speedups from doing this in the past, because every time you clone something, if it's an Arc the clone is like taking a reference, and if it's not, then you have to copy all the memory, and that's particularly expensive for big objects like Plot.

TLDR: Do we want Plot to be cheap to clone and work really well in multithreaded environment? If so, then a mutable PlotBuilder -> immutable Plot pattern instead of mutable Plot: FieldSetter might enable big cloning speedups and thread safety (for Plot, not PlotBuilder).

just an idea, i know we're all busy, no expectation or anything

Future Not Send Workaround

As a workaround, working with Plot inside a scope can fix the linter warning:

/// example of using a scope to avoid holding a `Plot` across an await point
async fn plot_future_not_send_workaround() -> AnyhowResult<serde_json::Value> {
    //                           VVV make a "scope"
    let (plot_json, other_data) = {
        let (plot, other_data) = crate::PlotAndOtherData::try_new(/* args */)?;
        let plot_json_string = plot.to_json();
        let plot_json = serde_json::from_str::<'_, serde_json::Value>(&plot_json_string)?;
        // return the plot_json to the caller
        (plot_json, other_data)
    }; // <--- plot dropped here 

    // now we can await Futures without carrying something which is not Send:
    some_future(plot_json.clone(), other_data).await?;

    Ok(plot_json)
}

For some reason, a manual drop(plot) didn't work for me to inform Rustc I was done with that thing, but these block scopes did!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants