添加自定义小部件#
此示例展示了如何在绘图中添加双端滑块小部件。
单个普通 Bokeh 滑块控制线的功率。双端滑块控制线的 x 范围。
Python 脚本
"""Example implementation of two double ended sliders as extension widgets"""
from bokeh.core.properties import Bool, Float, Tuple
from bokeh.io import show
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, CustomJS, InputWidget, Slider
from bokeh.plotting import figure
class IonRangeSlider(InputWidget):
# The special class attribute ``__implementation__`` should contain a string
# of JavaScript or TypeScript code that implements the web browser
# side of the custom extension model or a string name of a file with the implementation.
__implementation__ = "ion_range_slider.ts"
__javascript__ = [
"https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.1.4/js/ion.rangeSlider.js",
]
# Below are all the "properties" for this model. Bokeh properties are
# class attributes that define the fields (and their types) that can be
# communicated automatically between Python and the browser. Properties
# also support type validation. More information about properties in
# can be found here:
#
# https://docs.bokeh.org.cn/en/latest/docs/reference/core/properties.html#bokeh-core-properties
disable = Bool(default=True, help="""
Enable or disable the slider.
""")
grid = Bool(default=True, help="""
Show or hide the grid beneath the slider.
""")
start = Float(default=0, help="""
The minimum allowable value.
""")
end = Float(default=1, help="""
The maximum allowable value.
""")
range = Tuple(Float, Float, help="""
The start and end values for the range.
""")
step = Float(default=0.1, help="""
The step between consecutive values.
""")
x = [x*0.005 for x in range(2, 198)]
y = x
source = ColumnDataSource(data=dict(x=x, y=y))
plot = figure(width=400, height=400)
plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6, color='#ed5565')
callback_single = CustomJS(args=dict(source=source), code="""
const f = cb_obj.value
const x = source.data.x
const y = Array.from(x, (x) => Math.pow(x, f))
source.data = {x, y}
""")
callback_ion = CustomJS(args=dict(source=source), code="""
const {data} = source
const f = cb_obj.range
const pow = (Math.log(data.y[100]) / Math.log(data.x[100]))
const delta = (f[1] - f[0]) / data.x.length
const x = Array.from(data.x, (x, i) => delta*i + f[0])
const y = Array.from(x, (x) => Math.pow(x, pow))
source.data = {x, y}
""")
slider = Slider(start=0, end=5, step=0.1, value=1, title="Bokeh Slider - Power")
slider.js_on_change('value', callback_single)
ion_range_slider = IonRangeSlider(start=0.01, end=0.99, step=0.01, range=(min(x), max(x)),
title='Ion Range Slider - Range')
ion_range_slider.js_on_change('range', callback_ion)
show(column(plot, slider, ion_range_slider))
IonRangeSlider 的 TypeScript
// The "core/properties" module has all the property types
import * as p from "core/properties"
// HTML construction and manipulation functions
import {div, input, StyleSheetLike, ImportedStyleSheet} from "core/dom"
// We will subclass in JavaScript from the same class that was subclassed
// from in Python
import {InputWidget, InputWidgetView} from "models/widgets/input_widget"
declare function jQuery(...args: any[]): any
export type SliderData = {from: number, to: number}
// This model will actually need to render things, so we must provide
// view. The LayoutDOM model has a view already, so we will start with that
export class IonRangeSliderView extends InputWidgetView {
declare model: IonRangeSlider
override stylesheets(): StyleSheetLike[] {
return [
...super.stylesheets(),
new ImportedStyleSheet("https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.1.4/css/ion.rangeSlider.css"),
new ImportedStyleSheet("https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.1.4/css/ion.rangeSlider.skinFlat.min.css"),
]
}
private value_el?: HTMLInputElement
protected _render_input(): HTMLElement {
this.input_el = input({type: "text"})
return div({style: {width: "100%"}}, this.input_el)
}
override render(): void {
// BokehJS Views create <div> elements by default, accessible as this.el.
// Many Bokeh views ignore this default <div>, and instead do things
// like draw to the HTML canvas. In this case though, we change the
// contents of the <div>, based on the current slider value.
super.render()
if (this.model.title != null) {
this.value_el = input({type: "text", class: "bk-input", readonly: true, style: {marginBottom: "5px"}})
this.group_el.appendChild(this.value_el)
}
// Set up parameters
const max = this.model.end
const min = this.model.start
const [from, to] = this.model.range ?? [max, min]
const opts = {
type: "double",
grid: this.model.grid,
min,
max,
from,
to,
step: this.model.step ?? (max - min)/50,
disable: this.model.disabled,
onChange: (data: SliderData) => this.slide(data),
onFinish: (data: SliderData) => this.slidestop(data),
}
jQuery(this.input_el).ionRangeSlider(opts)
if (this.value_el != null)
this.value_el.value = `${from} - ${to}`
}
slidestop(_data: SliderData): void {
}
slide({from, to}: SliderData): void {
if (this.value_el != null)
this.value_el.value = `${from} - ${to}`
this.model.range = [from, to]
}
}
export namespace IonRangeSlider {
export type Attrs = p.AttrsOf<Props>
export type Props = InputWidget.Props & {
range: p.Property<[number, number] | null>
start: p.Property<number>
end: p.Property<number>
step: p.Property<number | null>
grid: p.Property<boolean>
}
}
export interface IonRangeSlider extends IonRangeSlider.Attrs {}
export class IonRangeSlider extends InputWidget {
declare properties: IonRangeSlider.Props
declare __view_type__: IonRangeSliderView
constructor(attrs?: Partial<IonRangeSlider.Attrs>) {
super(attrs)
}
static {
// If there is an associated view, this is boilerplate.
this.prototype.default_view = IonRangeSliderView
// The this.define block adds corresponding "properties" to the JS model. These
// should basically line up 1-1 with the Python model class. Most property
// types have counterparts, e.g. bokeh.core.properties.String will be
// String in the JS implementation. Where the JS type system is not yet
// as rich, you can use p.Any as a "wildcard" property type.
this.define<IonRangeSlider.Props>(({Bool, Float, Tuple, Nullable}) => ({
range: [ Nullable(Tuple(Float, Float)), null ],
start: [ Float, 0 ],
end: [ Float, 1 ],
step: [ Nullable(Float), 0.1 ],
grid: [ Bool, true ],
}))
}
}