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

3D plots #89

Open
ianhi opened this issue Aug 25, 2020 · 7 comments
Open

3D plots #89

ianhi opened this issue Aug 25, 2020 · 7 comments
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@ianhi
Copy link
Collaborator

ianhi commented Aug 25, 2020

This would be dope and at least one perosn has desired this in the past: https://discourse.matplotlib.org/t/how-to-plot-a-dynamic-3d-graph-using-matplotlib/21431

I think that for sanity sake these should get their own interactive_plot3D and associated factory function

Tagging as help wanted because I have no plans to implementing this in the near future

@ianhi ianhi added enhancement New feature or request help wanted Extra attention is needed labels Aug 25, 2020
@redeboer
Copy link
Contributor

Hey @ianhi, this would indeed be cool to have! I wrote a small example of how to do this with both matplotlib widgets and ipywidgets here. Now the challenge is to think of a general way of implementing this in mpl_interactions.

I might get back to that later (would need time to dive through the codebase to think of an interface that matches the existing ones), but I thought in the meantime I'd post a link to that little demo. Perhaps you have feedback to the snippets there? I'm not that familiar yet with both of the widget interfaces.

@ianhi
Copy link
Collaborator Author

ianhi commented Jul 16, 2021

@redeboer I have seen this and love that you're interested! Bit busy at the moment but will try to respond soon (please fell free to ping me if this slips away though)

@ianhi
Copy link
Collaborator Author

ianhi commented Jul 19, 2021

Now the challenge is to think of a general way of implementing this in mpl_interactions.

This shouldn't be too hard! The hard part is always figuring out a good way to update the matplotlib artist (this is super inconsistent across the library) I wrote up how to integrate with mpl-interactions here: #180

Perhaps you have feedback to the snippets there?

1st they look nice - as do your docs. They're also a reasonable way to make an animation. However, there are two downsides to your approach.

  1. using ax.clear will remove anything not added in the update plot function. (see below for strategy using artist.remove()
    So this would remove the ability to mix normal (non-interactive) mpl functions with interactive ones.

  2. It's generally more expensive to create a new artist than too update the data of an existing one.

For example to animate a line you use the set_data method to update an imshow instead of calling a new imshow you can be selective about whether or not norms get rescaled and probably other savings as well:
https://github.com/ianhi/mpl-interactions/blob/74966bcc43bca45890151329f19ca0004b1de538/mpl_interactions/pyplot.py#L745-L748

I think that this is generally the preferred way to animate matplotlib artists (matplotlib/matplotlib#19520). However not every plotting function returns an object with an easy set_data (or equivalent) method.That is what is holding back things like pcolormesh (#59).

Methods without set_data generally

I've been hesitant to add any functions that don't have set_data style ways of updating them because I wanted to do a good job preserving layering of artists (the zorder attribute doesn't strictly define it, order in the draw tree also matters) and also because I was being stubborn. But I think this was a bad tradeoff and I should include all them using artist.remove to eliminate the original. So the update function would look like:

artist = ax.plot_surface(...)`

def update(params, indices, cache):
    nonlocal artist
    artist.remove()
    X = callable_else_value(X, params, cache)
    artist = ax.plot_surface(...)
controls._register_function(update)

set_data for 3D artists

3D in matplotlib is a bit of mess (direct quote from a core dev on one of my 3d related PRs): matplotlib/matplotlib#19573 (review) so no matter what I think we won't get perfect 3D support :( as the interfaces for updating are inconsistent and or non-existant at times. But where we can use this then I would prefer to. For example Line3D is fully animatable using set_data along wiht set_3d_properties.

Some other relevant matplotlib issues/PRs relating to the confusion of updating methods:

matplotlib/matplotlib#19572

@redeboer
Copy link
Contributor

Thanks a lot for your thorough explanation! It's valuable that you document things so well in these issues and pull requests 👍

I indeed read that most 3D objects do not have a set_data method and that ax.clear was some way out (even though you would have to reconfigure the whole axis again). I had expected the performance to be worse btw, but, granted, the example I provided does not have a large number of data points.

I'll gladly work on your PR #201, particularly writing some documentation. It seems like it will be hard to get a nice implementation given mpl's 3D interfaces, but if the interfaces mpl_interaction can remain stable, the implementation can be improved upon later.

I have to say, btw, that I quite liked the idea of those interactive decorators that ipywidgets provides, because it gives the user all flexibility of writing some protocol to perform on ipywidget changes without much boilerplate code. The downside, of course, is that you have to work with ipywidgets, but other than that, this decorator approach looks quite clean to me.

@ianhi
Copy link
Collaborator Author

ianhi commented Jul 20, 2021

I have to say, btw, that I quite liked the idea of those interactive decorators that ipywidgets provides, because it gives the user all flexibility of writing some protocol to perform on ipywidget changes without much boilerplate code. The downside, of course, is that you have to work with ipywidgets, but other than that, this decorator approach looks quite clean to me.

Yeah those are pretty slick. Do note though there are a few ways in which I think they are not optimal for plotting due how they interact with the widget backend and also different semantics for specifying how sliders are created. (e.g. mpl-interactions uses linspace and those decorators use arange) More complete description here: https://mpl-interactions.readthedocs.io/en/stable/compare-to-ipywidgets.html (also see: jupyter-widgets/ipywidgets#2946)

If you have any suggestions for a way mpl-interactions could incorporate decorators or something to make life easier I'd love to hear about it.

@jrussell25
Copy link
Contributor

Hey @redeboer, ian helped me throw this together the last time i wanted to animate a 3d scatter plot, might be helpful for you.

T = 100
N = 50

data = np.random.randn(T,N,3)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X, Y, Z  = data[0].T
scatter = ax.scatter(X,Y,Z)

slider = widgets.IntSlider(val=0, min=0, max =T-1)

def update3d(change):
    X, Y, Z = data[slider.value].T
    scatter._offsets3d = (X,Y,Z) 
    scatter.set_color(...)

slider.observe(update3d, names='value')

@redeboer
Copy link
Contributor

Hey @redeboer, ian helped me throw this together the last time i wanted to animate a 3d scatter plot, might be helpful for you.

(...)

Thanks @jrussell25!
I've summarised some ways to interactively plot 3D surfaces here. Those only use plot_surface though. The snippets also have to be updated so that they doesn't use ax.clear() but rather, as you do for the scatter plot, some way of setting the surface data directly.

I could also write a note on observe there -- for ipywidgets, I only provided some snippets for those interactive decorators.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants