jupyter-book/thebe

Failed to render plotly graph

Opened this issue · 3 comments

I'm using thebe core, and already make it work, include matplotlib graph, show dataframe and etc.

However, I got some error when rendering plotly graph.

I'm using thebe in slidev project, and load require/plotly in this way:

import { useScriptTag } from '@vueuse/core'

useScriptTag(
    'https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js',
    (el) => {
        console.log("requirejs is loaded")
    }
)

useScriptTag(
    'https://cdn.plot.ly/plotly-2.35.2.min.js',
    (el) => {
        console.log("plotly is loaded")
    }
)

It's loaded before thebe core is started.

when jupyter get result back and at the rending stage, errors happend as:

Uncaught Error: Mismatched anonymous define() module: function(){return function(){var t={6713:function(t,e,r){"use strict";var n=r(34809),i={"X,X div":'direction:ltr;font-family:"Open Sans",verdana,arial,sans-serif;margin:0;padding:0;',"X input,X button":'font-family:"Open Sans",verdana,arial,sans-serif;',"X input:focus,X button:focus":"outline:none;","X a":"text-decoration:none;","X a:hover":"text-decoration:none;","X .crisp":"shape-rendering:crispEdges;","X .user-select-none":"-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;","X svg":"overflow:hidden;","X svg a":"fill:#447adb;","X svg a:hover":"fill:#3c6dc5;","X .main-svg":"position:absolute;top:0;left:0;pointer-events:none;","X .main-svg .draglayer":"pointer-events:all;","X .cursor-default":"cursor:default;","X .cursor-pointer":"cursor:pointer;","X .cursor-crosshair":"cursor:crosshair;","X .cursor-move":"cursor:move;","X .cursor-col-resize":"cursor:col-resize;","X .cursor-row-resize":"cursor…

is this a plotly.js version error? which version should I use?

Thanks for help in advance!

Hi @stevejpurves , thanks for the instructions.

Unfortunately, I don't understand React very well. For some syntax in the reference documents, I'm not sure how to translate it to vue. So, I will continue using Matplotlib for now. If anyone is interested in using Thebe in Vue, I'd be happy to share this. This code can be used as a Slidev component.

<!--
    see https://thebe.readthedocs.io/en/stable/start.html

Each slide has its own unique notebook. Notebooks are not shared between different slides.

In the code area, hold cmd + click to run the code; otherwise, double-click to zoom in/out.
In the output area, double-click to toggle zoom in/out.

When there are multiple components, please use scale animations to avoid interfering with each other's mouse capture.


<NoteCell init class="w-50% h-full top-10% left-50%">
```python
init_result = 5
print("hello world")
```
</NoteCell>

<NoteCell class="w-50% h-full top-10% left-50%" hideOutput
        :enter="{scale: 0}"
        :click-1="{scale: 1}"
        :click-2="{scale: 0}">

```python
import numpy as np
np.random.seed(78)
if 1:
    print(np.random.rand(3))
    print(init_result)
    print("hello world")
```

</NoteCell>

<NoteCell class="w-50% h-full top-10% left-50%"
        :enter="{scale: 0}"
        :click-2="{scale: 1}">

```python
print("the sceond call")
```
</NoteCell>

-->

<script setup>
import { ThebeCodeCell, ThebeNotebook, ThebeServer, makeConfiguration, makeRenderMimeRegistry, setupThebeCore, shortId } from 'thebe-core';
import { computed, onMounted, onUnmounted, ref } from 'vue';
import { globals } from './utils'
// import { useScriptTag } from '@vueuse/core'

// useScriptTag(
//     'https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js',
//     (el) => {
//         console.log("requirejs is loaded")
//     }
// )

// useScriptTag(
//     'https://cdn.plot.ly/plotly-2.35.2.min.js',
//     (el) => {
//         console.log("plotly is loaded")
//     }
// )

// useScriptTag(
//     'https://unpkg.com/thebe@latest/lib/index.js',
//     (el) => {
//         console.log("thebe client is loaded", el)
//     }
// )

const props = defineProps({
    "init": {
        type: Boolean,
        default: false
    },
    "maxOutput": {
        type: Boolean,
        default: false
    },
    "baseUrl": {
        type: String,
        default: "http://*/teacher_fa/"
    },
    "token": {
        type: String,
        default: "#####"
    },
    "path": {
        type: String,
        default: "/home/teacher_fa/notebooks/"
    },
    "hideOutput": { // show output right after the result returns back, or wait for user's click
        type: Boolean,
        default: false
    }
})

const style = computed(() => {
    return {
        'opacity': props.init ? 0 : 1
    }
})

const isCommandKeyPressed = ref(false);

const code = ref(null)
const outputWrapper = ref(null);
const codeStatus = {

}

// don't allow to run code in presenter mode
const warnPresenterMode = ref(null)

// 监听 keydown event
function checkCommandKey(event) {
    if (event.key === 'Meta') {
        isCommandKeyPressed.value = true;
    }
}

// 监听 keyup event
function uncheckCommandKey(event) {
    if (event.key === 'Meta') {
        isCommandKeyPressed.value = false;
    }
}

const createNotebook = () => {
    const nbid = `p${$page.value}`

    if (globals.jupyter[nbid]) {
        // console.log(`notebook ${nbid} already exists`)
        return
    }

    const notebook_name = `${$slidev.configs.slug}-${nbid}.ipynb`

    const config = makeConfiguration({
        useBinder: false,
        bootstrap: true,
        useJupyterLite: false,
        kernelOptions: {
            kernelName: "python3",
            path: props.path + notebook_name
        },
        serverSettings: {
            appendToken: true,
            baseUrl: props.baseUrl,
            token: props.token
        },
    })

    config.events.on('status',
        (evt, { status, message }) => console.debug(evt, status, message)
    )

    let server = new ThebeServer(config)

    const rendermime = makeRenderMimeRegistry(server.config.mathjax);
    const notebook = new ThebeNotebook(nbid, config, rendermime);
    notebook.cells = []

    globals.jupyter[nbid] = {
        server: server,
        session: null,
        notebook: notebook
    }
}

const onOutputDblClick = (event) => {
    toggleOutput(false)
};

const toggleOutput = (flag) => {
    if (!flag) {//show code
        code.value.style.opacity = 1
        outputWrapper.value.style.display = 'none'
        outputWrapper.value.style.height = 0
    } else {
        console.log("turn on output")
        outputWrapper.value.style.display = 'block'
        outputWrapper.value.style.height = '500px'
        code.value.style.opacity = 0
    }
};

const promptRunInSlide = () => {
    console.log('presenter mode: prompt to run in slide')
    warnPresenterMode.value.style.opacity = 1
    setTimeout(() => {
        warnPresenterMode.value.style.opacity = 0
    }, 3000)
}
const onRunCode = async (event) => {
    console.log('onRunCode', codeStatus)
    // window.thebe.bootstrap()
    if (code.value.id in codeStatus) {
        toggleOutput(true)
        return
    }

    if ($renderContext.value === 'presenter') {
        promptRunInSlide()
        return
    }
    code.value.style.setProperty('--pseudo-before-content', "'running'")
    document.body.style.cursor = 'wait'

    const nbid = `p${$page.value}`
    const cellId = code.value.id
    // console.log(`running cell ${cellId}`, code.value.textContent)

    const notebook = globals.jupyter[nbid].notebook

    const cell = notebook.getCellById(cellId)
    await executeCell(cell)

    if (!props.hideOutput) {
        toggleOutput(true)
    }

    document.body.style.cursor = 'default'
    code.value.style.setProperty('--pseudo-before-content', "'runnable'")

    codeStatus[code.value.id] = true
}

const executeCell = async (cell) => {
    const nbid = `p${$page.value}`

    const server = globals.jupyter[nbid].server

    if (globals.jupyter[nbid].session == null) {
        await server.connectToJupyterServer();
        const rendermime = makeRenderMimeRegistry(server.config.mathjax);
        let session = await server.startNewSession(rendermime);

        if (session == null) {
            console.error('could not start thebe jupyter session')
            return
        }

        // console.log(`started new session ${session.id}, notebook is ${nbid}`)

        globals.jupyter[nbid].session = session
    }

    cell.session = globals.jupyter[nbid].session
    console.log(`executing ${cell.id}:\n${cell.source}`)
    await cell.execute()
}

const initNotebook = async () => {
    const initCellId = `${nbid}-initial-cell`
    const nbid = `p${$page.value}`

    const notebook = globals.jupyter[nbid]
    const cell = notebook.getCellById(initCellId)

    await executeCell(cell)
    setTimeout(() => {
        outputWrapper.value.style.opacity = 0
    }, 5000)
}
const createCodeCell = async (codeEl, outputWrapper, isInitCell) => {
    const pageno = $page.value
    const nbid = `p${pageno}`
    const config = globals.jupyter[nbid].server.config
    const notebook = globals.jupyter[nbid].notebook

    const metadata = {}
    const cid = isInitCell ? `${nbid}-initial-cell` : `${nbid}-${shortId()}`

    codeEl.id = cid

    const cell = new ThebeCodeCell(cid, nbid, codeEl.textContent, config, metadata)

    outputWrapper.id = `${cid}-output`
    cell.attachToDOM(outputWrapper)

    if (isInitCell) {
        setTimeout(() => {
            initNotebook()
        }, 100)
    }

    notebook.cells.push(cell)

    const total = notebook.numCells()
    console.info(`created Cell: ${cid}, total cells: ${total}, code is: \n${codeEl.textContent}`)
}

onMounted(() => {
    if (!globals.jupyter) {
        setupThebeCore();

        globals.jupyter = {
        }
    }

    if ($renderContext.value === 'slide') {
        createNotebook()
        createCodeCell(code.value, outputWrapper.value, props.init)
    }

    document.body.addEventListener('keydown', checkCommandKey)
    document.body.addEventListener('keyup', uncheckCommandKey)
    code.value.style.setProperty('--pseudo-before-content', "'runnable'");

})

onUnmounted(() => {
    document.body.removeEventListener('keydown', checkCommandKey)
    document.body.removeEventListener('keyup', uncheckCommandKey)
})

</script>

<template>
    <div :class="$attrs.class" v-motion>
        <div ref="code" :style="style" class="thebe-code" @dblclick="onRunCode">
            <slot></slot>
        </div>
        <div ref="outputWrapper" class="output-wrapper" @dblclick="onOutputDblClick" />
        <RenderWhen context="presenter">
            <div ref="warnPresenterMode" class="warnPresnterMode">请在演示模式下运行!</div>
        </RenderWhen>
    </div>

</template>
<style scoped>
.thebe-code {
    position: absolute;
    width: 100%;
}

.output-wrapper {
    height: 100%;
    width: 100%;
    background-color: #fefefe;
    font-size: 0.8rem;
    overflow-y: auto;
    overflow-x: auto;
    display: none;
    scrollbar-width: none;
}

.thebe-code:before {
    content: var(--pseudo-before-content, 'runnable');
    background-color: rgba(240, 180, 50);
    padding: 0rem 0.5rem;
    text-align: center;
    border-radius: 10px;
    height: 1.3rem;
    font-size: 0.9rem;
    z-index: 10;
    position: absolute;
    right: 0;
}

.warnPresnterMode {
    width: 100%;
    height: 6rem;
    position: fixed;
    padding: 2rem;
    top: 100%;
    background-color: rgba(0, 0, 0, 0.5);
    color: white;
    opacity: 0;
    text-align: center;
    font-size: 2rem;
}
</style>

@zillionare ok - I think you are close, let me try and take React out of the picture.

  1. don't load plotly.js, load jupyter-plotly's front end code. This happens on this line in myst-theme, you can get that off unpkg if you need it (see unpgkg).
  2. register the jupyter-plotly's renderFactory in your rendermime registry. You can see how that is done here, and in your code you could do this stright after you create the rendermime registry const rendermime = makeRenderMimeRegistry(server.config.mathjax);

Check that it is working by using the debugger in your browser, form a breakpoint dive into the notebook.rendermime object, find the list of factories and check the plotly mimetype ('application/vnd.plotly.v1+json') is there.

ah, one last thing - we are actually patching the plotly package in order to expose the module export here: https://github.com/jupyter-book/myst-theme/blob/3a1b70b6f2a6b827effb60891f0e693c9bf65e05/patches/jupyterlab-plotly%2B5.18.0.patch look into https://www.npmjs.com/package/patch-package to do that in your project.