finos/perspective

Exporting PerspectiveWidget to Static HTML Fails

Opened this issue · 0 comments

I am experiencing issues when converting the PerspectiveWidget object into a static HTML file using the following code:

import perspective
from perspective import widget
import pandas as pd

widget.set_jupyter_html_export(True)

df = pd.DataFrame(dict(
    x=[1.0, 2.0, 3.0],
    y=[1.0, 3.0, 2.0]
))

w = widget.PerspectiveWidget(df)

with open("tmp.html", "w") as f:
    f.write("<html><body>")
    f.write(w._repr_mimebundle_()['text/html'])
    f.write("</body></html>")

However, the output tmp.html file does not display the Perspective view correctly due to the following issues:

  • viewerId is not assigned as a string.
  • viewerAttrs is not a properly formatted JSON object.
  • The call to await perspective.worker().table(data.buffer) raises TypeError.
  • The href attribute for the stylesheet is incorrect.
<html><body><script type="module" src="https://cdn.jsdelivr.net/npm/@finos/perspective@3.1.4/dist/cdn/perspective.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer@3.1.4/dist/cdn/perspective-viewer.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer-datagrid@3.1.4/dist/cdn/perspective-viewer-datagrid.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer-d3fc@3.1.4/dist/cdn/perspective-viewer-d3fc.js"></script>
<link rel="stylesheet" crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer-themes@3.1.4/dist/css/themes.css" />

<div class="perspective-envelope" id="perspective-envelope-a512580a2efc414e960615bc991de7a9">
    <script type="application/vnd.apache.arrow.file">
    /////9gAAAAQAAAAAAAKAAwABgAFAAgACgAAAAABBAAMAAAACAAIAAAABAAIAAAABAAAAAMAAABw
AAAAMAAAAAQAAACs////AAABAxAAAAAUAAAABAAAAAAAAAABAAAAeQAAANr///8AAAIA1P///wAA
AQMQAAAAGAAAAAQAAAAAAAAAAQAAAHgABgAIAAYABgAAAAAAAgAQABQACAAGAAcADAAAABAAEAAA
AAAAAQIQAAAAIAAAAAQAAAAAAAAABQAAAGluZGV4AAAACAAMAAgABwAIAAAAAAAAAUAAAAD/////
6AAAABQAAAAAAAAADAAWAAYABQAIAAwADAAAAAADBAAYAAAASAAAAAAAAAAAAAoAGAAMAAQACAAK
AAAAfAAAABAAAAADAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAA
AAAYAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAYAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAAAAA
ABgAAAAAAAAAAAAAAAMAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAgAAAAAAAAAAAAAAAADwPwAAAAAAAABAAAAAAAAACEAA
AAAAAADwPwAAAAAAAAhAAAAAAAAAAED/////AAAAAA==

    </script>
    <perspective-viewer style="height: 690px;"></perspective-viewer>
    <script type="module">
        // from MDN
        function base64ToBytes(base64) {
            const binString = atob(base64);
            return Uint8Array.from(binString, (m) => m.codePointAt(0));
        }

        import * as perspective from "https://cdn.jsdelivr.net/npm/@finos/perspective@3.1.4/dist/cdn/perspective.js";
        const viewerId = a512580a2efc414e960615bc991de7a9;
        const currentScript = document.scripts[document.scripts.length - 1];
        const envelope = document.getElementById(`perspective-envelope-${viewerId}`);
        const dataScript = envelope.querySelector('script[type="application/vnd.apache.arrow.file"]');;
        if (!dataScript)
            throw new Error('data script missing for viewer', viewerId);
        const data = base64ToBytes(dataScript.textContent);
        const viewerAttrs = {'group_by': [], 'split_by': [], 'filter': [], 'sort': [], 'aggregates': {}, 'columns': ['index', 'x', 'y'],
        	'expressions': {}, 'plugin': 'Datagrid', 'plugin_config': {}, 'theme': None, 'settings': True, 'title': None, 'version': '3.1.4'};

        // Create a new worker, then a new table promise on that worker.
        const table = await perspective.worker().table(data.buffer);
        const viewer = envelope.querySelector('perspective-viewer');
        viewer.load(table);
        viewer.restore(viewerAttrs);
    </script>
</div>
</body></html>

After applying the modifications below, the output HTML file renders correctly:

widget/__init__.py
***************
*** 17,19 ****
  import importlib
! 
  from string import Template
--- 17,19 ----
  import importlib
! import json
  from string import Template
***************
*** 259,264 ****
                  psp_cdn_perspective_viewer_themes=psp_cdn(
!                     "perspective-viewer-themes", "css/themes.css"
                  ),
                  viewer_id=self.model_id,
!                 viewer_attrs=viewer_attrs,
                  b64_data=b64_data.decode("utf-8"),
--- 259,264 ----
                  psp_cdn_perspective_viewer_themes=psp_cdn(
!                     "perspective-viewer", "css/themes.css"
                  ),
                  viewer_id=self.model_id,
!                 viewer_attrs=json.dumps(viewer_attrs),
                  b64_data=b64_data.decode("utf-8"),
                  
templates/exported_widget.html.template
***************
*** 19,21 ****
          import * as perspective from "$psp_cdn_perspective";
!         const viewerId = $viewer_id;
          const currentScript = document.scripts[document.scripts.length - 1];
--- 19,21 ----
          import * as perspective from "$psp_cdn_perspective";
!         const viewerId = "$viewer_id";
          const currentScript = document.scripts[document.scripts.length - 1];
***************
*** 29,31 ****
          // Create a new worker, then a new table promise on that worker.
!         const table = await perspective.worker().table(data.buffer);
          const viewer = envelope.querySelector('perspective-viewer');
--- 29,32 ----
          // Create a new worker, then a new table promise on that worker.
!         const worker = await perspective.worker();
!         const table = worker.table(data.buffer);
          const viewer = envelope.querySelector('perspective-viewer');