Как удалить / добавить строки в тепловой карте Bokeh и сохранить высоту строки? - PullRequest
1 голос
/ 10 марта 2020

Я создал тепловую карту Bokeh, связанную с CheckBoxGroup, чтобы активные элементы в CheckBoxGroup соответствовали строкам, отображаемым в тепловой карте. т.е. флажки / снятия флажков в CheckBoxGroup добавляют или удаляют строки в тепловой карте. Все работает нормально, за исключением того, что я хотел бы, чтобы строки тепловой карты оставались одинаковыми по высоте, независимо от того, сколько строк в тепловой карте. Что на самом деле происходит, так это то, что исходная высота тепловой карты сохраняется, а размеры строк изменяются в соответствии с исходной высотой.

У меня здесь MWE:

        from bokeh.io import output_file, show
        from bokeh.models import ColorBar, ColumnDataSource, LinearColorMapper
        from bokeh.plotting import figure
        from bokeh.transform import transform
        from bokeh.layouts import row, widgetbox
        from bokeh.models.callbacks import CustomJS
        from bokeh.models.widgets import CheckboxGroup
        import pandas as pd


        output_file("test.html")

        # set up data
        df = pd.DataFrame([["1", "1", 0.09], ["2", "1", 0.21], ["3", "1", 0.31], ["4", "1", 0.41],
                           ["1", "2", 0.5], ["2", "2", 0.61], ["3", "2", 0.71], ["4", "2", 0.81]],
                          columns=["x", "y", "values"])

        # source data for plot
        source = ColumnDataSource(df)

        # original source dataset, does not get changed
        savedsource = ColumnDataSource(df)

        # set up plot
        colors = ["#5A736F", "#75968f", "#a5bab7", "#c9d9d3", "#e2e2e2", "#dfccce", "#ddb7b1", "#cc7878", "#933b41",
                  "#550b1d"]
        mapper = LinearColorMapper(palette=colors, low=0, high=1)

        p = figure(title="Test", plot_width=200, plot_height=240,
                   x_range=["1", "2", "3", "4"], y_range=["1", "2"],
                   toolbar_location=None, tools="", x_axis_location="above")

        p.rect(x="x", y="y", width=1, height=1, source=source,
               line_color=None, fill_color=transform('values', mapper))

        p.axis.axis_line_color = None
        p.axis.major_tick_line_color = None
        p.axis.major_label_text_font_size = "9pt"
        p.axis.major_label_standoff = 0
        p.xaxis.major_label_orientation = 1.0

        # Create the checkbox selection element
        rows = ["1", "2"]
        selection = CheckboxGroup(labels=rows,
                                  active=[i for i in range(0, len(rows))])

        callback = CustomJS(args=dict(source=source, savedsource=savedsource, plot=p),
                            code="""

                    // get selected checkboxes
                    var active = cb_obj.active;

                    // get full original dataset
                    var origdata = savedsource.data;

                    // number of x-values
                    var numxs = plot.x_range.factors.length;

                    // this will be the new dataset
                    var newdata = {"index": [], "values": [], "x": [], "y": []};

                    // new y labels
                    var newlabels = [];

                    // slice out the data we want and put it into newdata
                    var i, j;
                    for (j=0; j<active.length; j++)
                    {
                        i = active[j]; // next active checkbox

                        newdata.index.push(...origdata.index.slice(i*numxs, i*numxs + numxs));
                        newdata.values.push(...origdata.values.slice(i*numxs, i*numxs + numxs));
                        newdata.x.push(...origdata.x.slice(i*numxs, i*numxs + numxs));
                        newdata.y.push(...origdata.y.slice(i*numxs, i*numxs + numxs));

                        newlabels.push(...origdata.y.slice(i*numxs, i*numxs + 1));
                    }

                    // replace the plot source data with newdata
                    source.data = newdata;

                    // update the yrange to reflect the deleted data
                    plot.y_range.factors = newlabels;
                    plot.y_range.end = newlabels.length;
                    source.change.emit();
                """)

        selection.js_on_change('active', callback)

        layout = row(widgetbox(selection), p)

        show(layout)

Я попытался изменить график. plot_height и plot.height_policy, но, похоже, ни один из них не дал никакого эффекта.

Ответы [ 2 ]

0 голосов
/ 30 марта 2020

Здесь нужно было настроить frame_height в обратном вызове и позвонить plot.properties.height.change.emit() - ответил с некоторой помощью отсюда: https://discourse.bokeh.org/t/how-to-delete-add-rows-in-bokeh-heatmap-and-maintain-row-height/4917/10

Окончательный MWE:

from bokeh.io import output_file, show
from bokeh.models import ColorBar, ColumnDataSource, LinearColorMapper
from bokeh.plotting import figure
from bokeh.transform import transform
from bokeh.layouts import row, widgetbox
from bokeh.models.callbacks import CustomJS
from bokeh.models.widgets import CheckboxGroup
import pandas as pd

output_file("test.html")

# set up data
df = pd.DataFrame([["1", "1", 0.09], ["2", "1", 0.21], ["3", "1", 0.31], ["4", "1", 0.41],
                   ["1", "2", 0.5], ["2", "2", 0.61], ["3", "2", 0.71], ["4", "2", 0.81]],
                  columns=["x", "y", "values"])

# source data for plot
source = ColumnDataSource(df)

# original source dataset, does not get changed
savedsource = ColumnDataSource(df)

# set up plot
colors = ["#5A736F", "#75968f", "#a5bab7", "#c9d9d3", "#e2e2e2", "#dfccce", "#ddb7b1", "#cc7878", "#933b41",
          "#550b1d"]
mapper = LinearColorMapper(palette=colors, low=0, high=1)

p = figure(title="Test", plot_width=200, plot_height=240,
           x_range=["1", "2", "3", "4"], y_range=["1", "2"],
           toolbar_location=None, tools="", x_axis_location="above")

p.frame_height = 240  # 2 rows of height 120

p.rect(x="x", y="y", width=1, height=1, source=source,
       line_color=None, fill_color=transform('values', mapper))

p.axis.axis_line_color = None
p.axis.major_tick_line_color = None
p.axis.major_label_text_font_size = "9pt"
p.axis.major_label_standoff = 0
p.xaxis.major_label_orientation = 1.0

# Create the checkbox selection element
rows = ["1", "2"]
selection = CheckboxGroup(labels=rows,
                          active=[i for i in range(0, len(rows))])

callback = CustomJS(args=dict(source=source, savedsource=savedsource, plot=p),
                    code="""

                    // get selected checkboxes
                    var active = cb_obj.active;

                    // get full original dataset
                    var origdata = savedsource.data;

                    // number of x-values
                    var numxs = plot.x_range.factors.length;

                    // this will be the new dataset
                    var newdata = {"index": [], "values": [], "x": [], "y": []};

                    // new y labels
                    var newlabels = [];

                    // slice out the data we want and put it into newdata
                    var i, j;
                    for (j=0; j<active.length; j++)
                    {
                        i = active[j]; // next active checkbox

                        newdata.index.push(...origdata.index.slice(i*numxs, i*numxs + numxs));
                        newdata.values.push(...origdata.values.slice(i*numxs, i*numxs + numxs));
                        newdata.x.push(...origdata.x.slice(i*numxs, i*numxs + numxs));
                        newdata.y.push(...origdata.y.slice(i*numxs, i*numxs + numxs));

                        newlabels.push(...origdata.y.slice(i*numxs, i*numxs + 1));
                    }

                    // replace the plot source data with newdata
                    source.data = newdata;

                    // update the yrange to reflect the deleted data
                    plot.y_range.factors = newlabels;
                    plot.y_range.end = newlabels.length;

                    // update plot height
                    new_height = newlabels.length * 120; //rowheight is 120
                    plot.frame_height = new_height;
                    plot.properties.height.change.emit();
                """)

selection.js_on_change('active', callback)

layout = row(widgetbox(selection), p)

show(layout)
0 голосов
/ 12 марта 2020

В последних строках обратного вызова CustomJS, сразу после комментария update the yrange to reflect the deleted data, вы явно меняете диапазон - и это именно то, что заставляет график вести себя так, как он делает.

Просто удалите все после source.data = newdata; - вам также не нужно source.change.emit();, поскольку вы меняете атрибут данных целиком.

...