Hey guys,
I am currently working on a bokeh application with continuous AND categorical data in the same Dataframe. Since the PointDrawTool of bokeh only comes with support for continuous data, I need to write a custom PointDrawTool myself. I followed the custom tools tutorial (A new custom tool — Bokeh 2.4.1 Documentation) and changed the TypeScript code. I just copied the source code of the PointDrawTool (bokeh/point_draw_tool.ts at branch-3.0 · bokeh/bokeh · GitHub) and fixed all the imports so that I do not run into any TypeScript Errors anymore. However, I always get a weird console error when loading the page. Additionally, the page remains completely blank and none of my widgets are showing.
Error in console: “Failed to repull session TypeError: this.properties[e] is undefined”
Code in draw_tool.py:
from bokeh.core.properties import Instance
from bokeh.io import output_file, show
from bokeh.models import ColumnDataSource, Tool
from bokeh.plotting import figure
from bokeh.util.compiler import TypeScript
output_file('tool.html')
TS_CODE = """
import {Keys} from "core/dom"
import {PanEvent, TapEvent, KeyEvent} from "core/ui_events"
import * as p from "core/properties"
import {GlyphRenderer} from "models/renderers/glyph_renderer"
import {EditTool, EditToolView, HasXYGlyph} from "models/tools/edit/edit_tool"
import {tool_icon_point_draw} from "styles/icons.css"
export class CustomDrawToolView extends EditToolView {
override model: CustomDrawTool
override _tap(ev: TapEvent): void {
const renderers = this._select_event(ev, this._select_mode(ev), this.model.renderers)
if (renderers.length || !this.model.add) {
return
}
const renderer = this.model.renderers[0]
const point = this._map_drag(ev.sx, ev.sy, renderer)
if (point == null)
return
// Type once dataspecs are typed
const glyph: any = renderer.glyph
const cds = renderer.data_source
const [xkey, ykey] = [glyph.x.field, glyph.y.field]
const [x, y] = point
this._pop_glyphs(cds, this.model.num_objects)
if (xkey) cds.get_array(xkey).push(x)
if (ykey) cds.get_array(ykey).push(y)
this._pad_empty_columns(cds, [xkey, ykey])
const {data} = cds
cds.setv({data}, {check_eq: false}) // XXX: inplace updates
}
override _keyup(ev: KeyEvent): void {
if (!this.model.active || !this._mouse_in_frame)
return
for (const renderer of this.model.renderers) {
if (ev.keyCode === Keys.Backspace) {
this._delete_selected(renderer)
} else if (ev.keyCode == Keys.Esc) {
renderer.data_source.selection_manager.clear()
}
}
}
override _pan_start(ev: PanEvent): void {
if (!this.model.drag)
return
this._select_event(ev, "append", this.model.renderers)
this._basepoint = [ev.sx, ev.sy]
}
override _pan(ev: PanEvent): void {
if (!this.model.drag || this._basepoint == null)
return
this._drag_points(ev, this.model.renderers)
}
override _pan_end(ev: PanEvent): void {
if (!this.model.drag)
return
this._pan(ev)
for (const renderer of this.model.renderers)
this._emit_cds_changes(renderer.data_source, false, true, true)
this._basepoint = null
}
}
export namespace CustomDrawTool {
export type Attrs = p.AttrsOf<Props>
export type Props = EditTool.Props & {
add: p.Property<boolean>
drag: p.Property<boolean>
num_objects: p.Property<number>
renderers: p.Property<(GlyphRenderer & HasXYGlyph)[]>
}
}
export interface CustomDrawTool extends CustomDrawTool.Attrs {}
export class CustomDrawTool extends EditTool {
override properties: CustomDrawTool.Props
override __view_type__: CustomDrawToolView
override renderers: (GlyphRenderer & HasXYGlyph)[]
constructor(attrs?: Partial<CustomDrawTool.Attrs>) {
super(attrs)
}
static {
this.prototype.default_view = CustomDrawToolView
this.define<CustomDrawTool.Props>(({Boolean, Int}) => ({
add: [ Boolean, true ],
drag: [ Boolean, true ],
num_objects: [ Int, 0 ],
}))
}
override tool_name = "Point Draw Tool XX"
tool_icon = tool_icon_point_draw
override event_type = ["tap" as "tap", "pan" as "pan", "move" as "move"]
override default_order = 2
}
"""
class CustomDrawTool(Tool):
__implementation__ = TypeScript(TS_CODE)
source = Instance(ColumnDataSource)
Usage in main.py
from custom_tools.draw_tool import CustomDrawTool
tools = config.TOOLS + [CustomDrawTool(source=data_model.data_X)]
p = figure(height=config.PLOT_HEIGHT, width=config.PLOT_WIDTH, tools=tools, output_backend="webgl",
**kw_figure)
Note: data_model.data_X is just a ColumnDataSource.
Hopefully someone can give me a hint on how to fix the problem and continue with writing my own Tool.
Thanks & best regards