Using the `Figure` object in a custom Bokeh extension

I would like to use the Figure object found in BokehJS in @bokeh/bokehjs/build/js/lib/api/plotting.js for a custom Bokeh extension, however, I cannot figure out the way to add the path to it so that the extension renders in JupyterLab. I am currently using Bokeh version 2.4.3 (for both Python and the JS libraries).

I have updated my tsconfig.json file to include the path to the module I would like to use, which does not seem to work. Note, all referenced files are below. The bokeh build command runs without error in the extension directory, but when I load it in a JupyterLab session I get the following error in my browser’s console.

Uncaught Error: Cannot find module '@bokehjs/api/main'

Both the JS and Python files are shown below. Any help regarding how to correctly set up the paths so that I can find the api/plotting.js module would be greatly appreciated.


files

tsconfig.json

{
  "compilerOptions": {
    "alwaysStrict": true,
    "baseUrl": ".",
    "declaration": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "importHelpers": false,
    "lib": ["es2020", "dom", "dom.iterable"],
    "module": "ES2020",
    "moduleResolution": "node",
    "noEmitOnError": false,
    "noErrorTruncation": true,
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "outDir": "./dist/lib",
    "paths": {
      "@bokehjs/*": [
        "node_modules/@bokeh/bokehjs/build/js/lib/api/*",
        "node_modules/@bokeh/bokehjs/build/js/types/api/*",
        "node_modules/@bokeh/bokehjs/build/js/lib/*",
        "node_modules/@bokeh/bokehjs/build/js/types/*"
      ]
    },
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "sourceMap": true,
    "strictBindCallApply": false,
    "strictFunctionTypes": false,
    "strictNullChecks": true,
    "strictPropertyInitialization": false,
    "target": "ES2020"
  },
  "include": ["./**/*.ts"]
}

marginal1d.ts

import {HTMLBox, HTMLBoxView} from '@bokehjs/models/layouts/html_box';
import * as p from '@bokehjs/core/properties';
import {ColumnDataSource} from '@bokehjs/models/sources/column_data_source';
import {build_view} from '@bokehjs/core/build_views';
import {div} from '@bokehjs/core/dom';

import * as Bkapi from '@bokehjs/api/main';

const linearRange = (start: number, stop: number, step: number = 1): number[] => {
  return Array.from({length: stop - step - start / step + 1}, (_, i) => {
    return start + i * step;
  });
};

export class Marginal1dView extends HTMLBoxView {
  // Declare public writeable properties.
  model: Marginal1d;
  containerDiv: HTMLDivElement;
  plotDiv: HTMLDivElement;
  fig: Bkapi.Plotting.Figure;

  // Initialize the properties.
  initialize(): void {
    super.initialize();

    this.containerDiv = div({style: 'height: 100%; width: 100%'});
    this.plotDiv = div({});
    this.containerDiv = div({style: 'height: 100%; width: 100%'}, this.plotDiv);
  }

  // Render the view.
  async render(): Promise<void> {
    super.render();
    this.el.appendChild(this.containerDiv);
    await this.setPlot();
  }

  private async setPlot() {
    // Compute the data for the initial view
    const x = linearRange(0, 300, 10);
    const y: number[] = [];
    for (let i = 0; i < x.length; i += 1) {
      y.push(Math.sin(x[i]));
    }

    // Create the source for the initial view
    const source = new ColumnDataSource({data: {x: x, y: y}});

    // Create the figures
    this.fig = new Bkapi.Plotting.Figure({
      background_fill_color: null,
      border_fill_color: null,
      outline_line_color: null,
      min_border: 0,
      sizing_mode: 'stretch_both',
      toolbar_location: null,
    });

    // Add the glyphs to the figure
    this.fig.line({
      x: {field: 'x'},
      y: {field: 'y'},
      source: source,
    });

    // Render the figure
    const view = await build_view(this.fig);
    this.plotDiv.innerHTML = '';
    view.renderTo(this.plotDiv);
  }
}

export namespace Marginal1d {
  export type Attrs = p.AttrsOf<Props>;
  export type Props = HTMLBox.Props;
}

export interface Marginal1d extends Marginal1d.Attrs {}

export class Marginal1d extends HTMLBox {
  properties: Marginal1d.Props;
  __view_type__: Marginal1dView;
  static __module__ = 'diagnostics.bokeh_extensions.marginal1d';

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

  static {
    this.prototype.default_view = Marginal1dView;
  }
}

marginal1d.py

from bokeh.models import HTMLBox


class Marginal1d(HTMLBox):
    pass

You might have better luck opening a GitHub development discussion. The best people to answer highly technical questions about the JS build system are more likely to see a question there than here.

GitHub disscussion link: Help using the `Figure` object in a custom Bokeh extension · Discussion #12220 · bokeh/bokeh · GitHub

1 Like

Thanks @Bryan if you think I should tag someone specific on GH, let me know.