Display panel w Custom save tool in jupyter notebook

Following the example described here javascript - Bokeh Custom Save Tool - Stack Overflow I am hoping to be able to add a custom save tool to my panel app and display in the jupyter notebook

The following code block works but only via .show() and will not display in the jupyter lab output window. Note : I will eventually be running on a jupyterhub instance so ‘localhost’ address via ,show() is unavailable

Self contained example :

import xarray as xr
import panel as pn
import numpy as np
import hvplot
import hvplot.xarray
import panel as pn
import holoviews as hv
from bokeh.models import ActionTool
from bokeh.util.compiler import TypeScript
from bokeh.core.properties import String


CUSTOM_SAVE_TS = """
import {ActionTool, ActionToolView} from "models/tools/actions/action_tool"
import * as p from "core/properties"
import {tool_icon_save} from "styles/icons.css"

export class CustomSaveToolView extends ActionToolView {
  model: CustomSaveTool

  async save(name: string): Promise<void> {
    const blob = await this.plot_view.to_blob()
    const link = document.createElement("a")
    link.href = URL.createObjectURL(blob)
    link.download = name // + ".png" | "svg" (inferred from MIME type)
    link.target = "_blank"
    link.dispatchEvent(new MouseEvent("click"))
  }

  doit(): void {
    this.save(this.model.save_name)
  }
}

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

  export type Props = ActionTool.Props & {
    save_name: p.Property<string>
  }
}

export interface CustomSaveTool extends CustomSaveTool.Attrs {}

export class CustomSaveTool extends ActionTool {
  properties: CustomSaveTool.Props
  __view_type__: CustomSaveToolView

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

  static init_CustomSaveTool(): void {
    this.prototype.default_view = CustomSaveToolView

    this.register_alias("save", () => new CustomSaveTool())

    this.define<CustomSaveTool.Props>(({String}) => ({
      save_name: [ String ],
    }))
  }

  tool_name = "Custom Save"
  icon = tool_icon_save

}
"""

class CustomSaveTool(ActionTool):
    """Modified save tool allowing custom file names"""
    __implementation__ = TypeScript(CUSTOM_SAVE_TS)
    save_name = String()
pn.extension()
save_tool = CustomSaveTool(save_name='custom_filename')
xy = xr.merge((xr.DataArray(np.random.rand(100,1), name='x'),xr.DataArray(np.random.rand(100,1), name='y')))
test = pn.Column(pn.pane.Markdown("## CUSTOM TITLE"),xy.hvplot.scatter(x='x',y='y', tools=[save_tool]))
display(test) ## does not display in notebook
test.show() ## this works
1 Like

A few ideas for you to find a solution

  • Have you looked in the browser console for any errors?
  • As this tool is build in typescript it needs to be transpiled by node.js. Could some errors be coming from that?
  • Try looking or asking in the Bokeh community forum as well.
  • Maybe eventually you would have to precompile this into a bokeh extension. The Bokeh documentation contains some info on this. I also have a little bit of documentation here Bokeh Extensions — Awesome Panel documentation.

I just tested. It also with with panel serve ... if I change the final line to test.servable().

I can confirm that this does not work in the notebook for me either and that I get the error

Uncaught Error: Model 'CustomSaveTool' does not exist. This could be due to a widget or a custom model not being registered before first usage.

If I 1) add hv.extension("bokeh") and 2) just want to show the hvplot/ holoviews` plot its the same problem.

@philippjfr

  • Should we expect this to work in a notebook using Holoviews?
  • Should we expect this to work in a notebook using Panel?
  • Should we report this as a bug? To HoloViews or Panel?

It’s the same problem if I convert to a bokeh figure and show that one.

@Marc Thanks for your help and suggestions on this. I have to admit I am a bit out of my comfort zone but will try to follow and help if possible. Basically I am hoping to eventually be able to create the savetool with a custom output filename within a @pn.depends function. Exported filename is then based on panel widget values and not simply bokeh_plot.png

I see some ways still to go

  • Start testing if the custom tools works with a normal Bokeh figure. I also had problems getting that to work.
  • Reaching out in the Bokeh forum for help.
  • “Prebuilding” the custom tool. Eventually you would like to do this in order to not wait for Bokeh/ Node.js to build it everytime you use it.
1 Like