Пользовательский инструмент BokehJS для переключения видимости легенды

Мое приложение боке имеет дело с сеткой из нескольких фигур, каждая из которых показывает несколько глифов. Чтобы улучшить читаемость, я хотел бы иметь возможность скрывать/показывать легенды цифр при нажатии на кнопку. Хотя это показалось мне идеальным примером для кнопки инструмента на панели инструментов, такой как «сохранить» и «сбросить», эта функция еще не реализована в боке.

Я нашел несколько советов о том, как самостоятельно реализовать собственный инструмент, см. здесь, здесь, здесь, здесь или здесь. Пример того, как добавить собственный значок, показан здесь.

Это то, что я получил до сих пор: main.py:

from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource, Tool
from bokeh.plotting import figure

from os.path import dirname
from jinja2 import FileSystemLoader, Environment
from bokeh.embed.standalone import file_html
from bokeh.resources import CDN

class LegendToggleTool(Tool):
    __implementation__ = """
        import {ActionTool, ActionToolView} from "models/tools/actions/action_tool"
        import * as p from "core/properties"
        
        export class LegendToggleToolView extends ActionToolView {
            model: LegendToggleTool
            
            doit(): void {
                for(const r of this.plot_view.model.panels){
                    if (r.type=="Legend"){
                        r.visible = !r.visible
                    }
                }
            }
        }     
        
        export namespace LegendToggleTool {
          export type Attrs = p.AttrsOf<Props>
        
          export type Props = ActionTool.Props
        }
        
        export interface LegendToggleTool extends LegendToggleTool.Attrs {}
        
        export class LegendToggleTool extends ActionTool {
            properties: LegendToggleTool.Props
            __view_type__: LegendToggleToolView
            
            constructor(attrs?: Partial<LegendToggleTool.Attrs>) {
                super(attrs)
            }
            
            static init_LegendToggleTool(): void {
                this.prototype.default_view = LegendToggleToolView
                this.register_alias("legendtoggle", () => new LegendToggleTool())
            }
        
            tool_name = "LegendToggle"
            icon      = "legend-toggle-icon"
        }
        """

env = Environment(loader=FileSystemLoader(dirname(__file__)))
template = env.get_template('template.html')

source01 = ColumnDataSource(data=dict(x=[0,1,2,3,4], y=[0,1,2,3,4],z=[4,3,2,1,0]))
source02 = ColumnDataSource(data=dict(x=[0,3,1,6,1], y=[0,1,2,3,4],z=[4,3,2,1,0]))

fig01 = figure(x_range=(0, 10), y_range=(0, 10),tools=[LegendToggleTool()])
fig01.line('x', 'y', source=source01, legend_label = 'line_01')
fig01.line('x', 'z', source=source01, legend_label = 'line_02')

fig02 = figure(x_range=(0, 10), y_range=(0, 10),tools=[LegendToggleTool()])
fig02.line('x', 'y', source=source02, legend_label = 'line_03')
fig02.line('x', 'z', source=source02, legend_label = 'line_04')

with open('out.html', 'w') as f:
    f.write(file_html(gridplot([[fig01,fig02]]), resources=CDN, template=template))

template.html (в том же каталоге, скопировано из здесь ):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>{{ title|e if title else "Bokeh Plot" }}</title>
    {{ bokeh_css }}
    {{ bokeh_js }}
    <style>
        html {
            width: 100%;
            height: 100%;
        }

        body {
            width: 90%;
            height: 100%;
            margin: auto;
        }

        .legend-toggle-icon {
            background-image: url(data:image/png;base64,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);
        }
    </style>
</head>
<body>
{{ plot_div|indent(8) }}
{{ plot_script|indent(8) }}
</body>
</html>

Запуск main.py приводит к созданию выходного файла «out.html». Переключение видимости легенд работает должным образом, но значок остается пустым. Итак, вот мои вопросы:

  1. Как сделать, чтобы иконка отображалась?
  2. Допустим, я хотел запустить это через bokeh serve --show <dirname>, как мне организовать приведенный выше код внутри папки <dirname>?

person Alperino    schedule 23.09.2020    source источник
comment
в консоли есть ошибки?   -  person Void Spirit    schedule 23.09.2020
comment
Хорошая мысль, есть одна: DevTools failed to load SourceMap: Could not load content for file:///D:/python/bokeh/legend_tool/main.py:LegendToggleTool.js.map: System error: net::ERR_FILE_NOT_FOUND   -  person Alperino    schedule 23.09.2020