Weird behavior when creating a custom PointDrawTool

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

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.