D3 in jupyterlab

Hi All,
I have tried to clarify the situation with embedding javacript codes into jupyter lab notebooks and also jupyter classic notebooks.
Sorry to admit that things are unclear for me today.
Using require method works with the classic jupyter but not with the jupyter lab.

Older technic such as described in

does not work anymore.
HTML('<script src="https://d3js.org/d3.v7.min.js"></script>')

I think many of us would like to mix python codes with js codes (D3.js) either from jupyter lab or the classic jutyper notebook.

Let me know if I have missed an explanation or a tutorial that works.

Regards
Patrick

Checkout this notebook from Grant Nestor

JupyterLab includes a Javascript extension that exposes a few globals you can use to begin embedding D3 animations (and other JS) in the notebook output.

In this notebook, there’s even an example of how to build a D3 chart from a Pandas DataFrame.

1 Like

Hi,

Thank you for answering.
Yes it works in a jupyter lab but not in a “classic” jupyter notebook.

I would like a method for both because we have a community of users that has not fully migrated to jupyter-lab.

Rgds
Patrick

For IPython solution one could use iframes which will work consistently in both Classic and JupyterLab. (example below) But I would encourage migration to JupyterLab or RetroLab (if the classic layout is preferred).

from urllib.parse import quote
from IPython.core.magic import register_cell_magic
from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring
from IPython.display import HTML


@magic_arguments()
@argument('-h', '--height', default='350px', help='Height of the output area')
@argument('-w', '--width', default='100%', help='Width of the output area')
@argument('-s', '--style', default='border: none', help='Style of the output area')
@argument('-v', '--version', default='4.13.0', help='D3.js version')
@argument('-i', '--integrity', default=None, help='Integrity hash for d3.js; when changing version provide one for security')
@argument('-c', '--cdn', default='https://cdnjs.cloudflare.com/ajax/libs/d3', help='CDN address')
@register_cell_magic
def d3(line, cell):
    args = parse_argstring(d3, line)
    known_versions = {
        '4.13.0': 'sha512-RJJ1NNC88QhN7dwpCY8rm/6OxI+YdQP48DrLGe/eSAd+n+s1PXwQkkpzzAgoJe4cZFW2GALQoxox61gSY2yQfg=='
    }
    if not args.integrity:
        args.integrity = known_versions.get(args.version, '')
    integrity = 'integrity="{}"'.format(args.integrity) if args.integrity else ''

    content = quote(
        """
        <html>
            <body>
            <script src='{args.cdn}/{args.version}/d3.min.js' {integrity} crossorigin="anonymous"></script>
            <script>document.body.onload = function () {{ {code} }}</script>
            </body>
        </html>
        """.format(code=cell, args=args, integrity=integrity),
        safe=''
    )
    html = """
    <iframe
        style="{args.style}"
        width="{args.width}"
        height="{args.height}"
        sandbox="allow-scripts allow-modals"
        referrerpolicy="no-referrer"
        src="data:text/html;charset=UTF-8,{content}"
    ></iframe>
    """.format(content=content, args=args)
    return HTML(html)

jupyterlab
classic notebook

4 Likes

The cool thing about iframe approach is that it allows multiple versions of D3.js to co-exist in the same notebook.

3 Likes

That is nice and indeed works for both interface.
Many thanks @krassowski

2 Likes

A very lovely hack (in the best possible sense of the word)!

<iframe

One thing to think about with iframes: by default, they aren’t particularly “secure” and could very well go off and mess with the parent code, especially if pulling from unknown sources.

If one does not need access to the outer parent scope, there are the sandbox and referrerpolicy attributes which can provide “best-effort” reduction in scope to a given frame. In the lab core IFrame, we’ve tried to bring some of them together to further normalize scope:

So updating the above with some sensible defaults, and exposing additional switches, might further improve the reusability of the above.

<script src='https://cdnjs.cloudflare.com

Another mechanism for enabling browsers’ ability to more safely use rando stuff from the internet are script tags’ crossdomain and integrity attributes. Along with the sensible default for the d3 version, one could stuff in the accompanying hash from cdnjs.

1 Like

Thanks for highlighting the security features of the iframe. I updated the snippet in my answer (I had sandbox in there initially but removed it for simplicity, but it indeed is more responsible to only share solutions that are secure by default rather than expect users to know about this stuff).

Maybe we should promote this approach - I think it is more secure than either of the solutions previously proposed for Notebook of Lab (i.e. using require.js or script tag injection). It might be also beneficial for dealing with large visualizations (if it crashes and the crash is on a different renderer it will not take down the entire Lab/Notebook with it).

I am not sure how to proceed with this, there are already four Python packages that implement d3 magic, and each does it slightly differently. Maybe we should propose to refurbish one of the packages and migrate it ipython-contrib? @carreau do you think that this is an option? It could alleviate the migration from Notebook and increase interoperability…

1 Like

Yes we can migrate a package to a more active location that is not an issue.

I would also suggest to ask @tonyfast that might have ideas of where this could fit.

1 Like

It’s been … a while since i did a lot of new d3 in anger, but it of course remains quite a powerful environment. The last time I did work with it, porting some old requirejs stuff to es modules/typescript, it was really quite fantastic to see how well the typings flow through the system for completion, etc. down to know what this will be, and what tag the current node will have, which always got a touch tenuous after coming back to some code.

I guess i’d love a d3 magic that started getting us into that place with one of the language servers… i guess i’m imagining something the opposite of bqplot, ipyd3: given i’ve invested in learning how to write modern d3, how might i easily the .data(), .on() etc. into the rest of the jupyter bus, including widgets? It seems like it would be feasible to ship something that had the lastest of each major versions of d3 with it (it’s not really that many), and would be a nice bridge vs “throw away your python and do everything in js”.