How to delete/add rows in Bokeh heatmap and maintain row height?

冷暖自知 提交于 2021-01-28 22:52:37

问题


I've made a Bokeh heatmap linked to a CheckBoxGroup, so that the active items in the CheckBoxGroup correspond to the rows displayed in the heatmap. i.e. checking/unchecking boxes in the CheckBoxGroup adds or deletes rows in the heatmap. It all works fine except that I would like the rows of the heatmap to stay the same height regardless of how many rows are in the heatmap. What actually happens is the original height of the heatmap is retained and the rows resize to fit the original height.

I have a MWE here:

        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)

I've tried changing plot.plot_height and plot.height_policy but neither seemed to have any effect.


回答1:


In the last lines of the CustomJS callback, right after the update the yrange to reflect the deleted data comment, you explicitly change the range - and that's exactly what makes the plot behave the way it does.

Just remove everything after source.data = newdata; - you don't need source.change.emit(); either since you change the whole data attribute.




回答2:


What was needed here was to adjust frame_height in the callback and call plot.properties.height.change.emit() - answered with some help from here: https://discourse.bokeh.org/t/how-to-delete-add-rows-in-bokeh-heatmap-and-maintain-row-height/4917/10

Final 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)


来源:https://stackoverflow.com/questions/60614866/how-to-delete-add-rows-in-bokeh-heatmap-and-maintain-row-height

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!