问题
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