PlotlyJS.jl icon indicating copy to clipboard operation
PlotlyJS.jl copied to clipboard

The State of PlotlyJS + WebIO/JupyterLab

Open twavv opened this issue 6 years ago • 10 comments
trafficstars

PlotlyJS tries to access the global WebIO object - but it can't since that's not defined in JupyterLab (because JupyterLab opens multiple notebooks in one Javascript context and doing so leads to "cross-talk").

This works (everywhere, but notably in JupyterLab):

trace1 = scatter(x=1:4, y=[10, 15, 13, 17], mode="markers")
plot([trace1])

This does NOT work (in JupyterLab):

using Interact

@manipulate for m in [1, 2, 3]
    x = range(1, stop=5, length=5)
    trace1 = scatter(x=x, y=m*x, mode="markers")
    plot([trace1])
end

There are two ways to fix this.

  • Easy: Define a global PlotlyJS object to define commands on.
  • Hard(er): Figure out a good API for WebIO to forward "libraries" over to the client.

We've discussed the latter API (in part of a discussion about making RPC's from JS into Julia) here (second comment). I'm not sure what the timeframe is on that though; we (well mostly @shashi at this point) are currently trying to implement request-response messaging which would be a pre-req.

twavv avatar Feb 06 '19 19:02 twavv

Thank you for posting this here. I would be totally happy to define a global object and add my extra js routines there.

sglyon avatar Feb 07 '19 01:02 sglyon

How about having a command registry that is global?

shashi avatar Feb 07 '19 13:02 shashi

How about having a command registry that is global?

As in like window.CommandRegistry? I don't think that's a great idea, personally, because global variables are bad™. I can think of a few cons.

  • There is a possibility for naming conflicts. For example, two packages could define a plot method; this is somewhat solved by having namespaces (à la CommandRegistry.Plotly) but still an issue (at least in principle).
  • There's a possibility for version conflicts. If I have a Julia 0.6 notebook in one pane (it just got EOL'd, I know, but not everything is updated yet!) and a Julia 1.1 notebook in a different pane, each using PlotlyJS, there's a decent chance that they'd have different commands defined but in the same namespace.
  • It encourages global state which could lead to the kind of cross-talk we're trying to avoid.
  • It requires code to make assumptions about the environment it will be running in (e.g. the code assumes that window.CommandRegistry.PlotlyJS.scatter is defined instead of having an immediate handle to the correct method).

Additionally, this kind of thing can be hacked without using globals provided by WebIO (e.g. if you really want to, define window.PlotlyJSCommands with all your commands on it; it still has most of the issues described above but at least it's not prescribed by library code).

twavv avatar Feb 07 '19 15:02 twavv

In that case, how about allowing a mechanism to inject a bunch of functions as commands? We'll need some kind of way for PlotlyJS to expose a function that does that?

function PlotlyStuff(webioInstance) {
   this.WebIOCommandSet = true;
   ...

   webioInstance.command1 = function () {..} // etc
}

module.exports = PlotlyStuff

when we load a js asset, we check to see if the object it exports has this.WebIOCommandSet, if yes, we call it with the webIO instance... This will be per-scope however.

shashi avatar Feb 07 '19 16:02 shashi

Or actually we can just have PlotlyJS tach on a bunch of commands, such as:

this.CommandSets.Plotly = {command1: ...}

inside onimport

shashi avatar Feb 07 '19 16:02 shashi

I don't know implications of all the different options and am happy to defer to @travigd and @shashi expertise here.

lol in other words I am happy to do whatever I'm told, as long as I can access my functions!

sglyon avatar Feb 07 '19 17:02 sglyon

This conversation should probably be had at https://github.com/JuliaGizmos/WebIO.jl/issues/229.

But since I'm here, I think (once that commit is fully implemented) the idiomatic way to do things would simply be

Plotly = JSAsset("...")

w = Scope(...)
onjs(w["image"], @js function(value)
    $w["image"] = @await (@await Plotly).toImage(...)
end)

# equivalently
w = Scope(...)
onjs(w["image"], @js function(value)
    @var Plotly = @await $Plotly
    $w["image"] = @await Plotly.toImage(...)
end)

I'm still definitely not a fan of having global (either global across everything or just global with respect to a single WebIO instance) command sets.

twavv avatar Feb 07 '19 18:02 twavv

In that case I guess we can just delete the CommandSets feature, and allow PlotlyJS.jl itself to simply load its own JS module and use it.

shashi avatar Feb 09 '19 21:02 shashi

That might be easier than trying to inject/attach it. I could just have my few functions be another dependency like plotly.js

sglyon avatar Feb 09 '19 22:02 sglyon

Now that JupyterLab 1.0 is out more people will want to switch from Jupyter notebook to lab (me included). Is there something new on this issue?

pfarndt avatar Jul 08 '19 10:07 pfarndt