Hi there,
I’ve been tinkering around with wrapping js-libraries and so far it worked great.
However, now that I embedded them into my tool, I’m starting to get problems with dissappearing elements.
In this example I used the Surface3d-wrapper example.
I am adding a new, empty Div
on button-click; I would expect everything to look the same, but the surface3d-DOM disappears…
I looked through the bokehjs source a bit and am now thinking that I should maybe subclass widget
instead of LayoutDOM
? I thought I could use the Surface3d example and adapt it for my own needs easily.
Edit: On further investigation I found that, obviously, Surface3dView’s initialize
does not fire, when the layout is updated (only on page-load). How should I modify the code to make it work?
Thank you in advance
# SURFACE-3D CODE
from bokeh.core.properties import Instance, String
from bokeh.models import LayoutDOM, ColumnDataSource
from bokeh.util.compiler import TypeScript
TS_CODE = """
import {LayoutDOM, LayoutDOMView} from "models/layouts/layout_dom"
import {ColumnDataSource} from "models/sources/column_data_source"
import {LayoutItem} from "core/layout"
import * as p from "core/properties"
declare namespace vis {
class Graph3d {
constructor(el: HTMLElement, data: object, OPTIONS: object)
setData(data: vis.DataSet): void
}
class DataSet {
add(data: unknown): void
}
}
const OPTIONS = {
width: '600px',
height: '600px',
style: 'surface',
showPerspective: true,
showGrid: true,
keepAspectRatio: true,
verticalRatio: 1.0,
legendLabel: 'stuff',
cameraPosition: {
horizontal: -0.35,
vertical: 0.22,
distance: 1.8,
},
}
export class Surface3dView extends LayoutDOMView {
model: Surface3d
private _graph: vis.Graph3d
initialize(): void {
super.initialize()
const url = "https://cdnjs.cloudflare.com/ajax/libs/vis/4.16.1/vis.min.js"
const script = document.createElement("script")
script.onload = () => this._init()
script.async = false
script.src = url
document.head.appendChild(script)
}
private _init(): void {
this._graph = new vis.Graph3d(this.el, this.get_data(), OPTIONS)
this.connect(this.model.data_source.change, () => {
this._graph.setData(this.get_data())
})
}
get_data(): vis.DataSet {
const data = new vis.DataSet()
const source = this.model.data_source
for (let i = 0; i < source.get_length()!; i++) {
data.add({
x: source.data[this.model.x][i],
y: source.data[this.model.y][i],
z: source.data[this.model.z][i],
})
}
return data
}
get child_models(): LayoutDOM[] {
return []
}
_update_layout(): void {
this.layout = new LayoutItem()
this.layout.set_sizing(this.box_sizing())
}
}
export namespace Surface3d {
export type Attrs = p.AttrsOf<Props>
export type Props = LayoutDOM.Props & {
x: p.Property<string>
y: p.Property<string>
z: p.Property<string>
data_source: p.Property<ColumnDataSource>
}
}
export interface Surface3d extends Surface3d.Attrs {}
export class Surface3d extends LayoutDOM {
properties: Surface3d.Props
__view_type__: Surface3dView
constructor(attrs?: Partial<Surface3d.Attrs>) {
super(attrs)
}
static __name__ = "Surface3d"
static init_Surface3d() {
this.prototype.default_view = Surface3dView
this.define<Surface3d.Props>(({String, Ref}) => ({
x: [ String ],
y: [ String ],
z: [ String ],
data_source: [ Ref(ColumnDataSource) ],
}))
}
}
"""
class Surface3d(LayoutDOM):
__implementation__ = TypeScript(TS_CODE)
data_source = Instance(ColumnDataSource)
x = String
y = String
z = String
#########################################
############## Actual MRE ###############
#########################################
from bokeh.events import ButtonClick
from bokeh.io import curdoc
from bokeh.layouts import row
from bokeh.models import Div, Button, ColumnDataSource
import numpy as np
x = np.arange(0, 300, 10)
y = np.arange(0, 300, 10)
xx, yy = np.meshgrid(x, y)
xx = xx.ravel()
yy = yy.ravel()
value = np.sin(xx / 50) * np.cos(yy / 50) * 50 + 50
source = ColumnDataSource(data=dict(x=xx, y=yy, z=value))
surface = Surface3d(x="x", y="y", z="z", data_source=source, width=600, height=600)
button = Button()
def callback(event):
layout.children.append(Div())
print(layout.children)
button.on_event(ButtonClick, callback)
layout = row(surface, button)
curdoc().add_root(layout)