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

Export Jupyterlab notebook into HTML #1393

Closed
tomsej opened this issue Apr 25, 2021 · 5 comments
Closed

Export Jupyterlab notebook into HTML #1393

tomsej opened this issue Apr 25, 2021 · 5 comments
Labels
Jupyter question Questions about use, potential features, or improvements

Comments

@tomsej
Copy link

tomsej commented Apr 25, 2021

Support Question

Hi I have tried to export jupyter notebook into html with nbconvert. Unfortunately, it does not export the perspective visualization (e.g. like plotly). Do you have any idea how to make it work? I thought to make a custom nbconvert exporter and include perspective javascript, but all I can see in jupyter notebook is this:

  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "3087f274-eacd-4ee7-bfb7-ec2da2eb31b9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "2b0beb31b8f94e64beb2bba5adeb4d60",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "PerspectiveWidget(columns=['int', 'float'], plugin='y_line', row_pivots=['str'])"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "widget = PerspectiveWidget(data, plugin=\"y_line\", row_pivots=[\"str\"], columns=[\"int\", \"float\"])\n",
    "widget"
   ]
  }
@timkpaine
Copy link
Member

Plotly embeds all the information necessary to reconstruct the visualization, whereas currently for perspective we do not. In default mode, all the data is only on the Python side so its not feasible to reconstruct completely (though @texodus might disagree and have thoughts or a strategy on how to do this, e.g. by shipping all the data to the client in some form or another), though in client mode the perspective instance is fully self contained including data so its possible to fully reconstruct.

@timkpaine timkpaine added Jupyter question Questions about use, potential features, or improvements labels Apr 26, 2021
@timkpaine
Copy link
Member

maybe @vidartf has some thoughts and can help implement this for perspective ;-)

@texodus
Copy link
Member

texodus commented Apr 26, 2021

This is very doable, but comes with a specific caveat: there is no easy way to get just a picture or otherwise stripped-down version of a PerspectiveWidget without also the engine, full perspective frontend and all of the original data serialized. Any quick implementation of this feature will get a full, interactive PerspectiveWidget in the resulting file, but the asset size may become enormous if the data size is large, and it will not embed cleanly in non-browsers e.g. PDF, email, GitHub, etc.

That being said, perspective excels (np) at data serialization, it should be trivial to dump the entire Table to a base-64-string-encoded Arrow and later re-hydrate it. I don't know enough about the Jupyter APIs for this feature yet, but I imagine the complexity will come from embedding/fetching the @finos/perspective-* libraries themselves? If the correct approach is to replace these with CDN links, we need to make sure these resolve; if the intended use case is to consume the resulting HTML via file:// protocol, there may be some CORS issues to resolve as well. Otherwise, embedding these libraries does work, but will add ~5mb of text to the resulting .html file (in addition to the serialized Arrow data), and we'll need to add the scripts to build this singe-file template version of the JS libraries.

It is possible to implement full serialization (for PDFs, e-mails, GitHub, etc) now that we've ported to regular-table which supports non-virtualized rendering, but we would need to implement a custom serialization method for each plugin and the resulting widgets would not be interactive.

@tomsej
Copy link
Author

tomsej commented Apr 27, 2021

Thank you both @texodus and @timkpaine. I have been discussing some use-cases with other team members for HTML exports:

  1. sending HTML reports to managers/stakeholders/salesperson e.g. with Papermill or manually.
  2. A lot of users are using knowledge-repo, Jupterbook, or some other static generator and for them, it would be awesome to have Perspective in it.

I do not think that it is necessary to implement full serialization e.g. to PDF, since the be the best feature of Perspective is interactivity and people can use some other plotting library for this purpose (e.g. Plotly) or make a printscreen of the current state and paste it to notebook :).

I think that for case 1) it definitely makes sense to include the entire Table in the resulting HTML file and have all data in client (I do not expect that people will use some large data). But for case 2) it would be beneficial to allow also client/server and server-only bindings.

Honestly, I do not fully understand what you mean by including PerspectiveWidget in the resulting file. I thought PerspectiveWidget was a Python object. I was considering something similar to what Altair/Vega is doing:

<div id="altair-viz-033ce8343a904cba8ef37fea260ac22e"></div>
<script type="text/javascript">
  (function(spec, embedOpt){
    let outputDiv = document.currentScript.previousElementSibling;
    if (outputDiv.id !== "altair-viz-033ce8343a904cba8ef37fea260ac22e") {
      outputDiv = document.getElementById("altair-viz-033ce8343a904cba8ef37fea260ac22e");
    }
    const paths = {
      "vega": "https://cdn.jsdelivr.net/npm//vega@5?noext",
      "vega-lib": "https://cdn.jsdelivr.net/npm//vega-lib?noext",
      "vega-lite": "https://cdn.jsdelivr.net/npm//[email protected]?noext",
      "vega-embed": "https://cdn.jsdelivr.net/npm//vega-embed@6?noext",
    };

    function loadScript(lib) {
      return new Promise(function(resolve, reject) {
        var s = document.createElement('script');
        s.src = paths[lib];
        s.async = true;
        s.onload = () => resolve(paths[lib]);
        s.onerror = () => reject(`Error loading script: ${paths[lib]}`);
        document.getElementsByTagName("head")[0].appendChild(s);
      });
    }

    function showError(err) {
      outputDiv.innerHTML = `<div class="error" style="color:red;">${err}</div>`;
      throw err;
    }

    function displayChart(vegaEmbed) {
      vegaEmbed(outputDiv, spec, embedOpt)
        .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));
    }

    if(typeof define === "function" && define.amd) {
      requirejs.config({paths});
      require(["vega-embed"], displayChart, err => showError(`Error loading script: ${err.message}`));
    } else if (typeof vegaEmbed === "function") {
      displayChart(vegaEmbed);
    } else {
      loadScript("vega")
        .then(() => loadScript("vega-lite"))
        .then(() => loadScript("vega-embed"))
        .catch(showError)
        .then(() => displayChart(vegaEmbed));
    }
  })({"config": {"view": {"continuousWidth": 400, "continuousHeight": 300}}, "data": {"url": "https://cdn.jsdelivr.net/npm/[email protected]/data/us-10m.json", "format": {"feature": "counties", "type": "topojson"}}, "mark": "geoshape", "encoding": {"color": {"type": "quantitative", "field": "rate"}}, "height": "container", "projection": {"type": "albersUsa"}, "transform": [{"lookup": "id", "from": {"data": {"url": "https://cdn.jsdelivr.net/npm/[email protected]/data/unemployment.tsv"}, "key": "id", "fields": ["rate"]}}], "width": "container", "$schema": "https://vega.github.io/schema/vega-lite/v4.8.1.json"}, {"actions": false, "mode": "vega-lite"});
</script>

Think the first and easier step could be implementing PerspectiveWidget.save() method that would just print HTML like here. And than somewhat force Jupyterhub to include that (I do not know Jupyter API :( )

@tomjakubowski
Copy link
Contributor

Initial support for notebook HTML export landed in 2.7.0, with the caveats @texodus mentioned about ipynb file sizes #2418

To enable this either:

  • set PSP_JUPYTER_HTML_EXPORT=1 in the jupyterlab server's environment, which will make all perspectives on the lab server export HTML when saved
  • call perspective.set_jupyter_html_export(True) in your notebook, which will make any viewers rendered afterwards export HTML

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Jupyter question Questions about use, potential features, or improvements
Projects
None yet
Development

No branches or pull requests

4 participants