xraypy / xraylarch

Larch: Applications and Python Library for Data Analysis of X-ray Absorption Spectroscopy (XAS, XANES, XAFS, EXAFS), X-ray Fluorescence (XRF) Spectroscopy and Imaging, and more.
https://xraypy.github.io/xraylarch
Other
127 stars 62 forks source link

xafs plots with plotly for Jupyter #425

Closed newville closed 1 year ago

newville commented 1 year ago

This is a request for comments, and to continue the discussion in #411.

This PR adds larch/plot/plotly_xafsplots.py. Ideally, this will eventually have plotting routines that more or less match those in larch/wxlib/xafsplots.py. Currently, only plot_mu() is implemented, with an example notebook in examples/Jupyter. I think it would not be hard to do the remaining plotting routines, just some time.

Any thoughts or suggestions are welcome. Basically, I think we want to have simple and decent XAFS plots for Jupyter -- is this use of plotly good enough?

maurov commented 1 year ago

@newville I have reviewed this pull request and I am fully in flavor of it.

My only suggestion is to handle the case when one want to plot more data in the same plot. In #411 I was proposing to have a controller object handling the data loaded in a Larch session, independently of Wx, but I think for now we could simply give a list of groups to the plot functions. In your example this would be:

plot_mu([cu], show_pre=True, show_post=True)

Furthermore, I would suggest building on top of a base XY plot function, let's say

def plot_xy(data: list, xattr : str = None, yattr: str = None):
   """base XY array(s) plot
        data: list of Group
    """
    ...

For plot_mu I would add the possibility to specify if raw/norm/flat.

This said, I like the idea of having the same plots of xas_viewer as simple Plotly-based functions.

newville commented 1 year ago

@maurov Thanks! I don't disagree with the suggestions. I was mostly just trying to get one working, and then see how to best expand and abstract that.

In the wx code, there is a "get_display()" function that handles the "state / controller" part, and then the plotting commands can use new=True/False to clear or overplot current traces. So, one would do

plot_mu(group1, norm=True, new=True) plot_mu(group2, norm=True, new=False) # with new=False being the default plot_mu(group2, normt=True)

or maybe write a wrapper that effectively does: [plot_mu(g, norm=True, new=(i==0)) for i,g in enumerate([g1, g2, g3, g4]))

I might suggest considering such an approach.

But one thing I found is that plotly handles some things differently. For example, you have to use a different creator function if you want to use both left and right y axes. And currently, I don't know of a way to adjust colors, linestyles, etc after the plot is done. It would be nice to at least have a way to select theming.

I expect we'll run into some more of those kinds of "every plotting library is just kind of different" differences too.

maurov commented 1 year ago

@newville personally I would not go too deep in making a full wrapper on top of Plotly.

The user of Jupyter is a Python user, so we do not need to reproduce the domain specific language commands used for Larch console or in the Wx GUI. For a more pythonic approach, I recommend let all the plot functions return the fig object. This way one can do further fine tuning of the figure, if wanted.

I think it would be much easier (and fast) to code functions that plot a list of groups and let the user or a "controller"-like object to select the groups to plot instead of repeating calls to a plot* function with the new parameter.

newville commented 1 year ago

@maurov I think that getting basic plotly plotting functions is a good idea, but having them be more "work like a plain function seems more consistent with existing code and easier to me.

I generally view a function taking a list of objects and then acting on all of them as sort of a fragile design. If we can provide "plot an XAFS Group as type xxx.", then a user can figure out how to run that for a list of XAFS Groups. Like, why support "list of Groups" but not "dict of Groups", "set of Groups", or "Group of Groups"?

I think I don't quite get what a "controller" would do from Python or Jupyter. I think of Controller as the code that does the real work with the data Model and updates to Viewer in a GUI. What would a Controller do in a Jupyter session? Would the user have to create a controller object and then route all data processing calls through that instance of a Controller?

maurov commented 1 year ago

@maurov I think that getting basic plotly plotting functions is a good idea, but having them be more "work like a plain function seems more consistent with existing code and easier to me.

@newville thank you very much indeed for the latest changes. I like the tiny wrapper layer of PlotlyFigure and I am in favor of the specific plotting functions. Those are very good examples for building others specific plots in Jupyter.

I generally view a function taking a list of objects and then acting on all of them as sort of a fragile design. If we can provide "plot an XAFS Group as type xxx.", then a user can figure out how to run that for a list of XAFS Groups. Like, why support "list of Groups" but not "dict of Groups", "set of Groups", or "Group of Groups"?

OK, fine with that.

I think I don't quite get what a "controller" would do from Python or Jupyter. I think of Controller as the code that does the real work with the data Model and updates to Viewer in a GUI. What would a Controller do in a Jupyter session? Would the user have to create a controller object and then route all data processing calls through that instance of a Controller?

I think I am misusing the world "Controller" as it is in the MCV paradigm. I see it as an object with utility methods that could help the user handling the selection of data loaded in during a Larch session. I keep #411 in mind and provide and example whenever I get some time to work on this.