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