Custom Markers/Extensions and Registering New Marker Types

Related to:

I too want to be able to able add custom markers types (without using images), and started poking around. I got things working, but there are some key things I still need to figure out, and I am not sure if these are dead ends or just tricky to figure out.

Below is an example drawing little suns (I also created a number of others just for the sake of messing around with the canvas context).

Using bokeh==3.6.0

sun.py

from bokeh.models import Marker
from bokeh.core.properties import Override
from bokeh.util.compiler import TypeScript

CODE = """
import { Marker, MarkerView } from "models/glyphs/marker";

export class SunView extends MarkerView {
    declare model: Sun

  _render_one = (ctx: any, i: number, r: number, visuals: any): void => {
    const x = this.x[i];
    const y = this.y[i];
    const angle = Math.PI / 4.0;
    
    ctx.beginPath();
    ctx.arc(x, y, r, 0.0, 2.0 * Math.PI);
    ctx.closePath();

    visuals.line.apply(ctx, i)
    visuals.fill.apply(ctx, i)
    visuals.hatch.apply(ctx, i)
    
    ctx.beginPath();
    for (let j = 0; j < 8; j++) {
      const cos = Math.cos(j * angle);
      const sin = Math.sin(j * angle);
      const ray_radius = r * (j % 2 == 0 ? 1.25 : 1.5);
      const lx1 = x + r * cos;
      const ly1 = y + r * sin;
      const lx2 = x + ray_radius * cos;
      const ly2 = y + ray_radius * sin;
      ctx.moveTo(lx1, ly1);
      ctx.lineTo(lx2, ly2);
    }
    ctx.closePath();

    visuals.line.apply(ctx, i)
  }
}

export class Sun extends Marker {
  declare __view_type__: SunView

    static {
        this.prototype.default_view = SunView;
    }
}
"""

class Sun(Marker):
    __implementation__ = TypeScript(CODE)  # Link to the compiled JavaScript file
"""

main.py

from bokeh.io import output_file
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, show

from sun import Sun

p = figure(width=400, height=400)

source = ColumnDataSource(data=dict(x=[1, 2, 3], y=[1, 2, 3]))
p.add_glyph(source, Sun(x="x", y="y", size=20, fill_color='yellow', line_color='red'))

output_file("example.html")
show(p)

This works great:

However, this requires me to add_glyph - I would love to be able to use scatter and specify the marker type as sun. I ultimately an using holoviews/hvplot, but obviously need to figure it out here before I tackle that side.

Can anyone provide any guidance on getting this to work? I know that there are no guarantees on the stability of the underlying API’s, but that is OK. Aside, if there are some odd things in the code provided and you have suggestions on how to do some of this better, I am very open to ideas! I am not familiar with lower level Bokeh code at all.

It’s possible to extend Bokeh with entirely new models, as you have seen. However, there is no mechanism to extend existing models, and in particular, Scatter has all of its marker types “baked in”, so to speak. You could subclass Scatter and add your new marker, but you’d still need to use the add_glyph nethod to add your new SpecialScatter glyph to a plot.

All that said, if the image above is your only requirement, it looks like you could accomplish it using two existing scatter glyphs. Plot an "asterisk" scatter first, then plot a "circle" scatter on top of those.

I appreciate information! That is unfortunately what I thought you would say - I had poked around more and found myself much deeper into into the plumbing and it didn’t feel possible. Comments in the code even say as much that (i.e. referring to supporting built-in glyphs).

I am actually creating much more complicated markers (NATO Joint Military Symbology - Wikipedia), I just figured a sun was a fun and simple marker to create.

Can you elaborate on what is the issue with creating a new custom extension glyph class and using add_glyph? Ultimately a Scatter is also added using the same add_glyph method, all glyphs are added to a plot that way under the hood. Does it cause some tension with the Holoviews integration you are trying to achieve?

I think there have been passing discussions about adding a CustomJSMarker or similar in the past, but I don’t think it ever got as far as anyone submitting an actual issue for it. Feel free to open a feature request on GitHub about it. It seems like it ought to be doable. Currently Scatter calls the pre-defined marker funcs like this:

marker_funcs[marker_i](ctx, i, r, this.visuals)

so I’d imagine a CustomJSMarker snippet would just need to conform to that same interface and then draw whatever it needs onto the canvas ctx. Most of the thought will have to go into what “registration” looks like (i.e. do we just support users adding one single CustomJSMarker that handles all their new custom markers however, or do we make CustomJSMarker more fine grained and register one per new marker type, or something like that.) There will also need to be some consideration to mitigate any performance implications in common dase case when no custom markers are registered, and I think we would punt entirely on the idea of webgl custom markers.

All that said, I am afraid it’s not an immediate solution for you. Although I might actually be interested to work on this myself, my “Bokeh time” is rather limited these days, so it could easily be a few release cycles (i.e. several months) before it actually lands, even in the best case.

@Lnk2past FYI it looks like another core dev happened to take an interest in this:

I’d encourage you to comment on the PR with any input relevant to your use-case.