Could not resolve type 'Vertices', which could be due to a widget or a custom model not being registered before first usage

I’m experimenting a little bit with glyphs and wanted to inherit from Scatter to see what kinds of things I can do.

This is my code

p = figure(title="Click to add points. Spline will update.",
    tools="pan,wheel_zoom,box_zoom,tap,reset",
    width=1024, height=1024
)
p_vertices_glyph = Vertices(x='x', y='y')
p_points = p.add_glyph(points_source, p_vertices_glyph)
p_points.selection_glyph = p_vertices_glyph.clone()
p_points.selection_glyph.size = 16

### vertices.py
from typing import Any
from bokeh.models import Scatter
from bokeh.util.compiler import JavaScript

class Vertices(Scatter):
    __implementation__ = JavaScript("""
import {Scatter, ScatterView} from "models/glyphs/scatter";

export class VerticesView extends ScatterView {
  initialize() {
    super.initialize();
    console.log("[Bokeh] VerticesView loaded and initialized");
  }
}
export class Vertices extends Scatter {
  static __module__ = "Vertices";
  static __view_type__ = VerticesView;
}
""")

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)

When starting the sample I get in the browser in the console the following error: y: could not resolve type 'Vertices', which could be due to a widget or a custom model not being registered before first usage
`
Complete error message:

bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:186 [bokeh 3.7.2] setting log level to: 'info'
bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:234 [bokeh 3.7.2] Websocket connection 0 is now open
bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:234 y: could not resolve type 'Vertices', which could be due to a widget or a custom model not being registered before first usage
    at p.error (http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:213:4858)
    at p._resolve_type (http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:213:4966)
    at p._decode_object_ref (http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:213:4559)
    at p._decode (http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:213:1739)
    at p._decode_plain_object (http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:213:2300)
    at p._decode (http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:213:1026)
    at p._decode_object_ref (http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:213:4623)
    at p._decode (http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:213:1739)
    at http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:213:2209
    at d (http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:179:3619)
_repull_session_doc @ bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:234
await in _repull_session_doc
_awaiting_ack_handler @ bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:234
_current_handler @ bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:234
_on_message @ bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:234
Promise.socket.onmessage @ bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:234
bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:234  [bokeh 3.7.2] Failed to repull session Error: could not resolve type 'Vertices', which could be due to a widget or a custom model not being registered before first usage
_repull_session_doc @ bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:234
await in _repull_session_doc
_awaiting_ack_handler @ bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:234
_current_handler @ bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:234
_on_message @ bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:234
Promise.socket.onmessage @ bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:234
[NEW] Explain Console errors by using Copilot in Edge: click
         
         to explain an error. 
        Learn more
        Don't show again
bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:233  [bokeh 3.7.2] Failed to load Bokeh session Cmxe0Ag4cLSXJ6mnA6Km0yzQEryNzd7P03xREgHzklQN: Error: could not resolve type 'Vertices', which could be due to a widget or a custom model not being registered before first usage
n.add_document_from_session @ bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:233
await in n.add_document_from_session
k @ bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:164
t.embed_items @ bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:164
await in t.embed_items
embed_document @ sample_spline:82
(anonymous) @ sample_spline:85
(anonymous) @ sample_spline:101
o.safely @ bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:241
fn @ sample_spline:77
bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:164  Error rendering Bokeh items: y: could not resolve type 'Vertices', which could be due to a widget or a custom model not being registered before first usage
    at p.error (http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:213:4858)
    at p._resolve_type (http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:213:4966)
    at p._decode_object_ref (http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:213:4559)
    at p._decode (http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:213:1739)
    at p._decode_plain_object (http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:213:2300)
    at p._decode (http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:213:1026)
    at p._decode_object_ref (http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:213:4623)
    at p._decode (http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:213:1739)
    at http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:213:2209
    at d (http://localhost:5006/static/js/bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:179:3619)
k @ bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:164
await in k
t.embed_items @ bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:164
await in t.embed_items
embed_document @ sample_spline:82
(anonymous) @ sample_spline:85
(anonymous) @ sample_spline:101
o.safely @ bokeh.min.js?v=5b74dbd22e38c51171f871973702aa79f54ff23ea2f60323446d118e71a0b292cc81d26415b4248908d4788b79b741b805ccbd6c9639e8a4921454cb1b44d480:241
fn @ sample_spline:77

I found a similar question but could not reply to it since its already closed.

Bokeh custom model registration - Community Support - Bokeh Discourse

Does anybody know how to register the vertice class such that the type can be resolved?

HI @mfe more information is needed:

  • A complete Minimal Reproducible Example that can actually be run, as is
  • More details about how you are generating the output, e.g output_file? Inline in a notebook? Using an embedding API like components, file_html, or json_items?

Hi @Bryan,
I attached a MRE.


from bokeh.io import curdoc
from bokeh.models import ColumnDataSource, WheelZoomTool
from bokeh.models.glyphs import Marker
from custom_scatter import CustomScatter
from bokeh.plotting import figure
from bokeh.layouts import column
from bokeh.util.compiler import JavaScript

class CustomScatter(Marker):
    __implementation__ = JavaScript('''
    import {Marker, MarkerView} from "bokehjs/models/glyphs/marker";
    export class CustomScatterView extends MarkerView {
        render(ctx, indices, data) {
            super.render(ctx, indices, data);
            // Custom rendering logic can go here
            console.log("CustomScatterView render called");
        }
    }
    export class CustomScatter extends Marker {
        static __module__ = "custom_scatter";
        static __view_type__ = CustomScatterView;
    }
    ''')
    def __init__(self, **kwargs):
        defaults = dict(
            marker='circle',
            size=8,
            fill_color="navy",
            fill_alpha=0.8,
            line_color=None
        )
        defaults.update(kwargs)
        super().__init__(**defaults)

# Data sources for user points and the spline
points_source = ColumnDataSource(data=dict(x=[], y=[]))
spline_source = ColumnDataSource(data=dict(x=[], y=[]))

# Create the plot with wheel zoom and pan tool enabled
p = figure(title="Click to add points. Spline will update.",
    tools="pan,wheel_zoom,box_zoom,tap,reset",
    #sizing_mode="stretch_both",
    #match_aspect=True,
    width=1024, height=1024
)
p.xgrid.visible = True
p.ygrid.visible = True

p.x_range.start = 0
p.x_range.end = 1024
p.y_range.start = 0
p.y_range.end = 1024

# Set wheel zoom as the active scroll tool (do not set pan as active drag tool)
p.toolbar.active_scroll = p.select_one(WheelZoomTool)

p_vertices_glyph = CustomScatter(x='x', y='y')
p_points = p.add_glyph(points_source, p_vertices_glyph)


p.line('x', 'y', source=spline_source, line_width=3, color="orange", legend_label="Spline")
# Make selected points larger and easier to select
p_points.selection_glyph = p_points.glyph.clone()
p_points.selection_glyph.size = 16

curdoc().add_root(column(p))
curdoc().title = "Interactive Spline Trace"

However I run now into a different problem with bokeh serve .\src\sample_customscatter.py
Output:

PS D:\SampleApp> bokeh serve .\src\sample_customscatter.py
2025-07-27 07:55:49,533 Starting Bokeh server version 3.7.0 (running on Tornado 6.4.2)
2025-07-27 07:55:49,535 User authentication hooks NOT provided (default user enabled)
2025-07-27 07:55:49,572 Bokeh app running at: http://localhost:5006/sample_customscatter
2025-07-27 07:55:49,573 Starting Bokeh server with process id: 18100
2025-07-27 07:55:55,512 Error running application handler <bokeh.application.handlers.script.ScriptHandler object at 0x000001ECF5E4A630>: unexpected attribute 'marker' to CustomScatter, possible attributes are angle, angle_units, decorations, fill_alpha, fill_color, hatch_alpha, hatch_color, hatch_extra, hatch_pattern, hatch_scale, hatch_weight, hit_dilation, js_event_callbacks, js_property_callbacks, line_alpha, line_cap, line_color, line_dash, line_dash_offset, line_join, line_width, name, size, subscribed_events, syncable, tags, x or y
File 'has_props.py', line 377, in _raise_attribute_error_with_matches:
raise AttributeError(f"unexpected attribute {name!r} to {self.__class__.__name__}, {text} attributes are {nice_join(matches)}") Traceback (most recent call last):
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\bokeh\application\handlers\code_runner.py", line 229, in run
    exec(self._code, module.__dict__)
  File "D:\SampleApp\src\sample_customscatter.py", line 58, in <module>
    p_vertices_glyph = CustomScatter(x='x', y='y')
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\SampleApp\src\sample_customscatter.py", line 34, in __init__
    super().__init__(**defaults)
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\bokeh\models\glyphs.py", line 178, in __init__
    super().__init__(*args, **kwargs)
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\bokeh\models\glyph.py", line 88, in __init__ 
    super().__init__(*args, **kwargs)
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\bokeh\models\glyph.py", line 66, in __init__ 
    super().__init__(*args, **kwargs)
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\bokeh\model\model.py", line 119, in __init__ 
    super().__init__(**kwargs)
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\bokeh\models\glyph.py", line 120, in __init__
    super().__init__(*args, **kwargs)
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\bokeh\models\glyph.py", line 130, in __init__
    super().__init__(*args, **kwargs)
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\bokeh\models\glyph.py", line 150, in __init__
    super().__init__(*args, **kwargs)
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\bokeh\core\has_props.py", line 304, in __init__
    setattr(self, name, value)
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\bokeh\core\has_props.py", line 342, in __setattr__
    self._raise_attribute_error_with_matches(name, properties)
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\bokeh\core\has_props.py", line 377, in _raise_attribute_error_with_matches
    raise AttributeError(f"unexpected attribute {name!r} to {self.__class__.__name__}, {text} attributes are {nice_join(matches)}")
AttributeError: unexpected attribute 'marker' to CustomScatter, possible attributes are angle, angle_units, decorations, fill_alpha, fill_color, hatch_alpha, hatch_color, hatch_extra, hatch_pattern, hatch_scale, hatch_weight, hit_dilation, js_event_callbacks, js_property_callbacks, line_alpha, line_cap, line_color, line_dash, line_dash_offset, line_join, line_width, name, size, subscribed_events, syncable, tags, x or y

2025-07-27 07:55:57,202 Uncaught exception GET /sample_customscatter (::1)
HTTPServerRequest(protocol='http', host='localhost:5006', method='GET', uri='/sample_customscatter', version='HTTP/1.1', remote_ip='::1')
Traceback (most recent call last):
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\tornado\web.py", line 1790, in _execute      
    result = await result
             ^^^^^^^^^^^^
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\bokeh\server\views\doc_handler.py", line 56, in get
    page = server_html_page_for_session(session,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\bokeh\embed\server.py", line 278, in server_html_page_for_session
    bundle = bundle_for_objs_and_resources(None, resources)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\bokeh\embed\bundle.py", line 208, in bundle_for_objs_and_resources
    ext = bundle_models(models)
          ^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\bokeh\util\compiler.py", line 309, in bundle_models
    _bundle_cache[key] = bundle = _bundle_models(custom_models)
                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\bokeh\util\compiler.py", line 569, in _bundle_models
    deps_map = resolve_deps(compiled.deps, model.path)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\bokeh\util\compiler.py", line 565, in resolve_deps
    return resolve_modules(missing, root)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mfe\AppData\Local\Programs\Python\Python312\Lib\site-packages\bokeh\util\compiler.py", line 558, in resolve_modules
    raise RuntimeError(f"no such module: {module}")
RuntimeError: no such module: bokehjs/models/glyphs/marker
2025-07-27 07:55:57,286 500 GET /sample_customscatter (::1) 2302.80ms

I tried different versions of import {Marker, MarkerView} from "bokehjs/models/glyphs/marker";
for example import {Marker, MarkerView} from "bokeh/models/glyphs/marker"; but the result is the same :crying_cat:

It would be great to have a working sample here. Any help appreciated, Thanks.

I could resolve the error using the correct docu.

Here is the working sample:


from bokeh.io import curdoc
from bokeh.models import ColumnDataSource, WheelZoomTool, Scatter
from bokeh.models.glyphs import Marker
from bokeh.plotting import figure
from bokeh.layouts import column
from bokeh.util.compiler import JavaScript
from bokeh.util.compiler import TypeScript

class CustomScatter(Scatter):
    __implementation__ = TypeScript(
        """
import {Scatter, ScatterView} from "models/glyphs/scatter"

export class CustomScatterView extends ScatterView {
  declare model: CustomScatter
  // You can override _paint or other methods here if needed
}

export namespace CustomScatter {
  export type Attrs = Scatter.Attrs
  export type Props = Scatter.Props
}

export interface CustomScatter extends CustomScatter.Attrs {}

export class CustomScatter extends Scatter {
  declare properties: CustomScatter.Props
  declare __view_type__: CustomScatterView

  static {
    this.prototype.default_view = CustomScatterView
    this.define<CustomScatter.Props>(({}) => ({}))
  }
}
        """)
    def __init__(self, **kwargs):
        defaults = dict(
            size=8,
            fill_color="navy",
            fill_alpha=0.8,
            line_color=None
        )
        defaults.update(kwargs)
        super().__init__(**defaults)

# Data sources for user points and the spline
points_source = ColumnDataSource(data=dict(x=[], y=[]))
spline_source = ColumnDataSource(data=dict(x=[], y=[]))

# Create the plot with wheel zoom and pan tool enabled
p = figure(title="Click to add points. Spline will update.",
    tools="pan,wheel_zoom,box_zoom,tap,reset",
    #sizing_mode="stretch_both",
    #match_aspect=True,
    width=1024, height=1024
)
p.xgrid.visible = True
p.ygrid.visible = True

p.x_range.start = 0
p.x_range.end = 1024
p.y_range.start = 0
p.y_range.end = 1024

# Set wheel zoom as the active scroll tool (do not set pan as active drag tool)
p.toolbar.active_scroll = p.select_one(WheelZoomTool)

p_vertices_glyph = CustomScatter(x='x', y='y')
p_points = p.add_glyph(points_source, p_vertices_glyph)


p.line('x', 'y', source=spline_source, line_width=3, color="orange", legend_label="Spline")
# Make selected points larger and easier to select
p_points.selection_glyph = p_points.glyph.clone()
p_points.selection_glyph.size = 16

curdoc().add_root(column(p))
curdoc().title = "Interactive Spline Trace"

Still one little issue:
When I outsource the typescript to customscatter.ts the import don’t work. I get in visual code the error: Cannot find module 'models/glyphs/scatter' or its corresponding type declarations.ts(2307. I can look up the definition on github but it would be great if this would work out of the box with visual code.

I was just about to run your MRE @mfe but I am glad you already got it working

Unfortunately I have no idea how to make that happen. All I can suggest is filing a GitHub Issue about it in case someone else is able to take a look or knows a good solution offhand.

1 Like