0

我有一个交互式散景图,其中(为了简单起见)我可以单击多个复选框(将它们视为电灯开关)并绘制它们的总和(房间内的累积亮度)。这是用一组理论的非交互式绘图绘制的。

如何更新图例以说明单击了哪些复选框按钮?(我不擅长JS)更新绘图的JS回调代码如下:

callback = CustomJS(args=dict(source=source), code="""
    const labels = cb_obj.labels;
    const active = cb_obj.active;
    const data = source.data;
    const sourceLen = data.combined.length;
    const combined = Array(sourceLen).fill(undefined);
    if (active.length > 0) {
        const selectedColumns = labels.filter((val, ind) => active.includes(ind));
        for(let i = 0; i < sourceLen; i++) {
            let sum = 0;
            for(col of selectedColumns){
                sum += data[col][i];
            }
            combined[i] = sum;
        }
    }
    data.combined=combined;
    source.change.emit();
""")

checkbox_group = CheckboxButtonGroup(labels=col_names[3:], active=[], callback=callback)

该图的图像如下所示。底部有一些按钮,单击时会添加到绘图中。

在此处输入图像描述

4

1 回答 1

1

这是一种方法。图例(和 LegendItems)是 Bokeh 模型,因此您可以通过 CustomJS 回调更新它们。

我对您希望如何更新图例做了一些假设。在此示例中,它更改了组合行标签的字符串以包含它正在求和的元素。如果您有其他想法,可以将基本回调结构应用于您的想法。

from bokeh.plotting import show, figure
from random import random
from bokeh.models import ColumnDataSource, CustomJS, CheckboxButtonGroup, Legend
from bokeh.layouts import column
import numpy as np
import pandas as pd

x = np.arange(10)
a = np.random.rand(10, ) * 20
b = np.random.rand(10, ) * 40
c = np.random.rand(10, ) * 60
df = pd.DataFrame(data={'x': x, 'a': a, 'b': b, 'c': c, 'combined': [0]*10})

source = ColumnDataSource(df)
button_labels = ['a', 'b', 'c']
p = figure(plot_width=1000, plot_height=500, y_range=(0, max(c)*2))

a_line = p.line('x', 'a', source=source, color='red')
b_line = p.line('x', 'b', source=source, color='blue')
c_line = p.line('x', 'c', source=source, color='orange')
combined_line = p.line('x', 'combined', source=source, color='green', line_dash='dashed')

legend = Legend(items=[
    ('a', [a_line]),
    ('b', [b_line]),
    ('c', [c_line]),
    ('combined (none)', [combined_line]),
], location="center")
p.add_layout(legend, 'right')

callback = CustomJS(args=dict(source=source, legend_item=legend.items[3]), code="""
    const labels = cb_obj.labels;
    const active = cb_obj.active;
    const data = source.data;
    const sourceLen = data.combined.length;
    const combined = Array(sourceLen).fill(undefined);
    var combined_label = ''
    
    if (active.length > 0) {
        const selectedColumns = labels.filter((val, ind) => active.includes(ind));
        for(let i = 0; i < sourceLen; i++) {
            let sum = 0;
            for(var col of selectedColumns){
                sum += data[col][i];
            }
            combined[i] = sum;
        }

        // get index positions of active buttons; use that to retrieve labels to build "combined" label string
        for (let j=0; j < active.length; j++) {
            combined_label += labels[active[j]]+'+';
        } 
        combined_label = '('+combined_label.substring(0, combined_label.length - 1)+')';
    }
    else {  // if there are no active buttons, label as 'none'
      combined_label = '(none)';
    }

    legend_item.label.value = 'combined '+combined_label;
    data.combined=combined;
    source.change.emit();
""")

checkbox_group = CheckboxButtonGroup(labels=button_labels, active=[], callback=callback, width=400)

final_col = column(p, checkbox_group)
show(final_col)

legend_updated_by_button_callback

于 2019-11-19T17:51:53.530 回答