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

Failed to render plotly graph #777

Open
zillionare opened this issue Oct 7, 2024 · 3 comments
Open

Failed to render plotly graph #777

zillionare opened this issue Oct 7, 2024 · 3 comments

Comments

@zillionare
Copy link

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!

@stevejpurves
Copy link
Collaborator

stevejpurves commented Oct 8, 2024

Hi @zillionare - you can look here https://github.com/jupyter-book/myst-theme/blob/3a1b70b6f2a6b827effb60891f0e693c9bf65e05/packages/jupyter/src/jupyter.tsx#L84-L85 and here https://github.com/jupyter-book/myst-theme/blob/3a1b70b6f2a6b827effb60891f0e693c9bf65e05/packages/jupyter/src/plotly.ts to see what we are doing in react. Basically, you need to load the plotly extension and register it in the rendermime registry properly.

@zillionare
Copy link
Author

zillionare commented Oct 9, 2024

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>

@stevejpurves
Copy link
Collaborator

stevejpurves commented Oct 9, 2024

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants