0

I am attempting to learn about implementing bokeh custom extensions to create some widgets that I would like for my data project. I was attempting to follow along with this example here but I am having trouble running the example extensions that bokeh has provided. I am currently getting back an error of

ValueError: expected a subclass of HasProps, got class 'bokeh.models.sources.ColumnDataSource'

which is being caused when the DrawTool class calls source = Instance(ColumnDataSource) on line 80. I am not sure exactly what I am doing wrong right now but my first thought is it has something to do with the IDE I'm using? I'm currently using spyder(python 3.6) and have the most recent bokeh update 2.1.0.

This is my first experience with bokeh extensions so I am fairly clueless and I've been scouring the web for help but can't really find much. My current code is exactly what they have on the examples site but I will post it here for convenience,

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 {GestureTool, GestureToolView} from "models/tools/gestures/gesture_tool"
import {ColumnDataSource} from "models/sources/column_data_source"
import {PanEvent} from "core/ui_events"
import * as p from "core/properties"

export class DrawToolView extends GestureToolView {
  model: DrawTool

  //this is executed when the pan/drag event starts
  _pan_start(_ev: PanEvent): void {
    this.model.source.data = {x: [], y: []}
  }

  //this is executed on subsequent mouse/touch moves
  _pan(ev: PanEvent): void {
    const {frame} = this.plot_view

    const {sx, sy} = ev
    if (!frame.bbox.contains(sx, sy))
      return

    const x = frame.xscales.default.invert(sx)
    const y = frame.yscales.default.invert(sy)

    const {source} = this.model
    source.get_array("x").push(x)
    source.get_array("y").push(y)
    source.change.emit()
  }

  // this is executed then the pan/drag ends
  _pan_end(_ev: PanEvent): void {}
}

export namespace DrawTool {
  export type Attrs = p.AttrsOf<Props>

  export type Props = GestureTool.Props & {
    source: p.Property<ColumnDataSource>
  }
}

export interface DrawTool extends DrawTool.Attrs {}

export class DrawTool extends GestureTool {
  properties: DrawTool.Props
  __view_type__: DrawToolView

  constructor(attrs?: Partial<DrawTool.Attrs>) {
    super(attrs)
  }

  tool_name = "Drag Span"
  icon = "bk-tool-icon-lasso-select"
  event_type = "pan" as "pan"
  default_order = 12

  static init_DrawTool(): void {
    this.prototype.default_view = DrawToolView

    this.define<DrawTool.Props>({
      source: [ p.Instance ],
    })
  }
}
"""


class DrawTool(Tool):
    __implementation__ = TypeScript(TS_CODE)
    source = Instance(ColumnDataSource)


source = ColumnDataSource(data=dict(x=[], y=[]))

plot = figure(x_range=(0, 10), y_range=(0, 10), tools=[DrawTool(source=source)])
plot.title.text = "Drag to draw on the plot"
plot.line('x', 'y', source=source)

show(plot)

2 Answers2

0

Your code works just fine for me with Bokeh 2.1.0. And is should not have anything to do with the IDE because it should not affect the runtime properties of your code. To be sure, you can run the script with Python manually.

If that still gives you the error, then maybe your Bokeh installation is corrupted. After all bokeh.models.sources.ColumnDataSource is a subclass of HasProps. Try creating the virtual environment from scratch.

Here's what I see when I run your code and interact with the plot a bit:

example of a working plot

Eugene Pakhomov
  • 9,309
  • 3
  • 27
  • 53
  • Thank's Eugene. It turned out that I had some corrupt packages within my bokeh installation along with some corrupt packages with anaconda. I will post an answer with what I did just in case anyone else runs into this problem in the future! – matthewholden01 Jun 18 '20 at 19:53
0

Just in case anyone runs into this issue in the future I thought I would post what I did here for anyone who is new to creating custom extensions and is having trouble finding a solution.

It turned out that my bokeh installation had corrupted as pointed out by Eugene. I went ahead and did a full rebuild of bokeh following Bokehs documents at Getting Started. After forking the latest bokeh repository I then ran into an issue with the anaconda environment trying to run this step where I found some Openssl corrupt files that were preventing me from creating an environment.

I found this fix on GitHub which worked for me with a thread of other possible solutions. After following the rest of the steps I successfully got the custom extensions running locally.

TLDR: The issue was a corrupt bokeh installation. If you are new to this and plan on doing custom extensions or using bokeh models, fork and clone a local build of bokeh rather than relying on the quick installation method.