Bokehの主な目標は、ブラウザで純粋にPythonから豊富なインタラクティブな可視化効果を作成する経路を提供することですが、常にコアライブラリの機能範囲を超えている専用の用例があります。このため、Bokehは、ユーザが属性変更および他のイベントに応答するためにカスタムまたは専用のアクションを追加することができるように、必要に応じてカスタムJavaScriptを提供するために、ユーザに異なる方法を提供する。
1つの機構は,前述したように,新たなカスタム拡張モデル全体を追加することができることである. Bokehを伸ばす それがそうです。しかしながら、例えば、属性値が変更された場合、またはUIまたは他のイベントが発生した場合など、小さなJavaScriptセグメントをコールバックとして提供することもできる。このコールバックは、Bokehサーバを必要とすることなく、Bokeh文書に面白いインタラクションを追加するために使用することができる(ただし、Bokehサーバと組み合わせて使用することもできる)。
警告
これらのコールバックの明確な目的は埋め込みです 原始JavaScriptコード ブラウザが実行するためのものですコードの任意の部分が信頼されていないユーザ入力から派生する場合、Bokehに渡す前に、ユーザ入力を清掃することに適切に注意しなければならない。
あるイベントが発生した場合(ブラウザで)実行すべきJavaScriptコード片を提供する場合は、ご利用ください CustomJS モデル:
CustomJS
from bokeh.models.callbacks import CustomJS callback = CustomJS(args=dict(xr=plot.x_range), code=""" // JavaScript code goes here var a = 10; // the model that triggered the callback is cb_obj: var b = cb_obj.value; // models passed as args are automagically available xr.start = a; xr.end = b; """)
注意してください code 財産。 CustomJS まだ受け入れています args 文字列名をBokehモデルの属性にマッピングする.中で構成されている任意のBokehモデル args (“Python側”)では、対応する名前でJavaScriptコードを自動的に利用できます。また,コールバックをトリガするモデル(すなわちコールバックが付加されたモデル)は以下のようになる. cb_obj それがそうです。
code
args
cb_obj
これらは CustomJS コールバックは、任意のBokehモデルに付加可能な属性変更イベント js_on_change Bokehモデルの方法:
js_on_change
p = figure() # execute a callback whenever p.x_range.start changes p.x_range.js_on_change('start', callback)
なお最初のパラメータは js_on_change 実際にはBokehJS事件の名称である。属性変更イベントの完全なフォーマットは、例えば、 "change:start" しかし、Bokehは、任意の属性名をこれらのBoehJS変更イベントのうちの1つに自動的に変換します。しかも、いくつかのBokehモデルは追加的な特別な活動を持っている。例えば ColumnDataSource 支持もしています "patch" そして "stream" イベントを実行するために CustomJS データソースに対してパッチやストリーミング処理を行う際のコールバック.
"change:start"
ColumnDataSource
"patch"
"stream"
次は例を挙げてみましょう CustomJS 逆戻りする. Slider 小さな部品は、スライダの値が更新されると、いくつかのデータを更新するコールバックを実行します。
Slider
from bokeh.layouts import column from bokeh.models import ColumnDataSource, CustomJS, Slider from bokeh.plotting import Figure, output_file, show output_file("js_on_change.html") x = [x*0.005 for x in range(0, 200)] y = x source = ColumnDataSource(data=dict(x=x, y=y)) plot = Figure(plot_width=400, plot_height=400) plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6) callback = CustomJS(args=dict(source=source), code=""" var data = source.data; var f = cb_obj.value var x = data['x'] var y = data['y'] for (var i = 0; i < x.length; i++) { y[i] = Math.pow(x[i], f) } source.change.emit(); """) slider = Slider(start=0.1, end=4, value=1, step=.1, title="power") slider.js_on_change('value', callback) layout = column(slider, plot) show(layout)
Bokehは、js_on_changeを使用して属性変更イベントに応答するほか、描画キャンバスとの特定のインタラクションイベント、ボタンクリックイベント、およびLODイベントによってCustomJSコールバックをトリガすることを可能にします。
これらのイベントコールバックは,js_on_eventメソッドを用いてモデル上で定義され,コールバックはイベントオブジェクトをローカルに定義されたcb_obj変数として受信する:
from bokeh.models.callbacks import CustomJS callback = CustomJS(code=""" // the event that triggered the callback is cb_obj: // The event type determines the relevant attributes console.log('Tap event occurred at x-position: ' + cb_obj.x) """) p = figure() # execute a callback whenever the plot canvas is tapped p.js_on_event('tap', callback)
イベントを文字列として指定することができ,以下のようになる. 'tap' 上のイベント類を導入、または従 bokeh.events モジュール(すなわち from bokeh.events import Tap )。
'tap'
bokeh.events
from bokeh.events import Tap
以下のコード導入 bokeh.events クラスは、利用可能なすべてのイベントクラスを登録します。 display_event 関数を生成する CustomJS 物体です。この関数は更新に用いられる Div イベント名を使用する(常に従えることができる) event_name 属性)および他に適用されるすべてのイベント属性.その結果、ユーザがそれと対話すると、描画は、対応するイベントを右側に表示する描画である。
display_event
Div
event_name
""" Demonstration of how to register event callbacks using an adaptation of the color_scatter example from the Bokeh gallery """ import numpy as np from bokeh import events from bokeh.io import output_file, show from bokeh.layouts import column, row from bokeh.models import Button, CustomJS, Div from bokeh.plotting import figure def display_event(div, attributes=[], style = 'float:left;clear:left;font_size=13px'): "Build a suitable CustomJS to display the current event in the div model." return CustomJS(args=dict(div=div), code=""" var attrs = %s; var args = []; for (var i = 0; i<attrs.length; i++) { args.push(attrs[i] + '=' + Number(cb_obj[attrs[i]]).toFixed(2)); } var line = "<span style=%r><b>" + cb_obj.event_name + "</b>(" + args.join(", ") + ")</span>\\n"; var text = div.text.concat(line); var lines = text.split("\\n") if (lines.length > 35) lines.shift(); div.text = lines.join("\\n"); """ % (attributes, style)) x = np.random.random(size=4000) * 100 y = np.random.random(size=4000) * 100 radii = np.random.random(size=4000) * 1.5 colors = ["#%02x%02x%02x" % (int(r), int(g), 150) for r, g in zip(50+2*x, 30+2*y)] p = figure(tools="pan,wheel_zoom,zoom_in,zoom_out,reset") p.scatter(x, y, radius=np.random.random(size=4000) * 1.5, fill_color=colors, fill_alpha=0.6, line_color=None) div = Div(width=400, height=p.plot_height, height_policy="fixed") button = Button(label="Button", button_type="success") layout = column(button, row(p, div)) ## Events with no attributes button.js_on_event(events.ButtonClick, display_event(div)) # Button click p.js_on_event(events.LODStart, display_event(div)) # Start of LOD display p.js_on_event(events.LODEnd, display_event(div)) # End of LOD display ## Events with attributes point_attributes = ['x', 'y', 'sx', 'sy'] # Point events wheel_attributes = point_attributes + ['delta'] # Mouse wheel event pan_attributes = point_attributes + ['delta_x', 'delta_y'] # Pan event pinch_attributes = point_attributes + ['scale'] # Pinch event point_events = [ events.Tap, events.DoubleTap, events.Press, events.PressUp, events.MouseMove, events.MouseEnter, events.MouseLeave, events.PanStart, events.PanEnd, events.PinchStart, events.PinchEnd, ] for event in point_events: p.js_on_event(event, display_event(div, attributes=point_attributes)) p.js_on_event(events.MouseWheel, display_event(div, attributes=wheel_attributes)) p.js_on_event(events.Pan, display_event(div, attributes=pan_attributes)) p.js_on_event(events.Pinch, display_event(div, attributes=pinch_attributes)) output_file("js_events.html", title="JS Events Example") show(layout)
属性コールバックの一般的な用例の1つは、小さなコンポーネントの変更に応答することです。次のコードは CustomJS スライダ使用時に描画源を変更するスライダガに設置した。
from bokeh.layouts import column from bokeh.models import ColumnDataSource, CustomJS, Slider from bokeh.plotting import figure, output_file, show output_file("callback.html") x = [x*0.005 for x in range(0, 200)] y = x source = ColumnDataSource(data=dict(x=x, y=y)) plot = figure(plot_width=400, plot_height=400) plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6) callback = CustomJS(args=dict(source=source), code=""" var data = source.data; var f = cb_obj.value var x = data['x'] var y = data['y'] for (var i = 0; i < x.length; i++) { y[i] = Math.pow(x[i], f) } source.change.emit(); """) slider = Slider(start=0.1, end=4, value=1, step=.1, title="power") slider.js_on_change('value', callback) layout = column(slider, plot) show(layout)
別の一般的なシナリオは、変更を選択する際に実行される同じタイプのコールバックを指定したいことである。簡単なプレゼンテーションとして、以下の例は、第1の描画上の選択点を第2の描画に簡単にコピーする。しかし,より複雑な操作や計算は類似した方式で構築することは容易である.
from random import random from bokeh.layouts import row from bokeh.models import ColumnDataSource, CustomJS from bokeh.plotting import figure, output_file, show output_file("callback.html") x = [random() for x in range(500)] y = [random() for y in range(500)] s1 = ColumnDataSource(data=dict(x=x, y=y)) p1 = figure(plot_width=400, plot_height=400, tools="lasso_select", title="Select Here") p1.circle('x', 'y', source=s1, alpha=0.6) s2 = ColumnDataSource(data=dict(x=[], y=[])) p2 = figure(plot_width=400, plot_height=400, x_range=(0, 1), y_range=(0, 1), tools="", title="Watch Here") p2.circle('x', 'y', source=s2, alpha=0.6) s1.selected.js_on_change('indices', CustomJS(args=dict(s1=s1, s2=s2), code=""" var inds = cb_obj.indices; var d1 = s1.data; var d2 = s2.data; d2['x'] = [] d2['y'] = [] for (var i = 0; i < inds.length; i++) { d2['x'].push(d1['x'][inds[i]]) d2['y'].push(d1['y'][inds[i]]) } s2.change.emit(); """) ) layout = row(p1, p2) show(layout)
以下にもう1つのより複雑な例を示す.平均値を計算しています y 任意の選択点の値(複数の交差しない選択を含む)は、その値によって線をプロットする。
from random import random from bokeh.models import ColumnDataSource, CustomJS from bokeh.plotting import figure, output_file, show output_file("callback.html") x = [random() for x in range(500)] y = [random() for y in range(500)] color = ["navy"] * len(x) s = ColumnDataSource(data=dict(x=x, y=y, color=color)) p = figure(plot_width=400, plot_height=400, tools="lasso_select", title="Select Here") p.circle('x', 'y', color='color', size=8, source=s, alpha=0.4) s2 = ColumnDataSource(data=dict(x=[0, 1], ym=[0.5, 0.5])) p.line(x='x', y='ym', color="orange", line_width=5, alpha=0.6, source=s2) s.selected.js_on_change('indices', CustomJS(args=dict(s=s, s2=s2), code=""" const inds = s.selected.indices; const d = s.data; var ym = 0 if (inds.length == 0) return; for (var i = 0; i < d['color'].length; i++) { d['color'][i] = "navy" } for (var i = 0; i < inds.length; i++) { d['color'][inds[i]] = "firebrick" ym += d['y'][inds[i]] } ym /= inds.length s2.data['ym'] = [ym, ym] s.change.emit(); s2.change.emit(); """)) show(p)
Rangeオブジェクトの属性も接続可能 CustomJS 範囲変更時に専用の作業を実行するためにコールバックします。
import numpy as np from bokeh.layouts import row from bokeh.models import ColumnDataSource, CustomJS, Rect from bokeh.plotting import figure, output_file, show output_file('range_update_callback.html') N = 4000 x = np.random.random(size=N) * 100 y = np.random.random(size=N) * 100 radii = np.random.random(size=N) * 1.5 colors = [ "#%02x%02x%02x" % (int(r), int(g), 150) for r, g in zip(50+2*x, 30+2*y) ] source = ColumnDataSource({'x': [], 'y': [], 'width': [], 'height': []}) jscode = """ const data = source.data const start = cb_obj.start const end = cb_obj.end data[%r] = [start + (end - start) / 2] data[%r] = [end - start] source.change.emit() """ p1 = figure(title='Pan and Zoom Here', x_range=(0, 100), y_range=(0, 100), tools='box_zoom,wheel_zoom,pan,reset', plot_width=400, plot_height=400) p1.scatter(x, y, radius=radii, fill_color=colors, fill_alpha=0.6, line_color=None) xcb = CustomJS(args=dict(source=source), code=jscode % ('x', 'width')) ycb = CustomJS(args=dict(source=source), code=jscode % ('y', 'height')) p1.x_range.js_on_change('start', xcb) p1.x_range.js_on_change('end', xcb) p1.y_range.js_on_change('start', ycb) p1.y_range.js_on_change('end', ycb) p2 = figure(title='See Zoom Window Here', x_range=(0, 100), y_range=(0, 100), tools='', plot_width=400, plot_height=400) p2.scatter(x, y, radius=radii, fill_color=colors, fill_alpha=0.6, line_color=None) rect = Rect(x='x', y='y', width='width', height='height', fill_alpha=0.1, line_color='black', fill_color='black') p2.add_glyph(source, rect) layout = row(p1, p2) show(layout)
選択ツールは,有用なコールバックを駆動できるイベントを発行する.以下に以下のコールバックを示す. SelectionGeometry 使用 BoxSelectTool 幾何学的図形(通過する幾何学的図形フィールドへのアクセス cb_data 更新するためにオブジェクトを戻します Rect 字形です。
SelectionGeometry
BoxSelectTool
cb_data
Rect
from bokeh.events import SelectionGeometry from bokeh.models import ColumnDataSource, CustomJS, Rect from bokeh.plotting import figure, output_file, show output_file("box_select_tool_callback.html") source = ColumnDataSource(data=dict(x=[], y=[], width=[], height=[])) callback = CustomJS(args=dict(source=source), code=""" const geometry = cb_obj['geometry'] const data = source.data // calculate Rect attributes const width = geometry['x1'] - geometry['x0'] const height = geometry['y1'] - geometry['y0'] const x = geometry['x0'] + width/2 const y = geometry['y0'] + height/2 // update data source with new Rect attributes data['x'].push(x) data['y'].push(y) data['width'].push(width); data['height'].push(height) // emit update of data source source.change.emit() """) p = figure(plot_width=400, plot_height=400, tools="box_select", title="Select Below", x_range=(0, 1), y_range=(0, 1)) rect = Rect(x='x', y='y', width='width', height='height', fill_alpha=0.3, fill_color='#009933') p.add_glyph(source, rect, selection_glyph=rect, nonselection_glyph=rect) p.js_on_event(SelectionGeometry, callback) show(p)
上記を除いて追加する CustomJS BokehモデルもありますBokehモデルもあります .callback 実行に特化した属性 CustomJS 特定のイベントや状況に反応する。
.callback
以下に説明するコールバックは、Bokehに特別な方法で事前に追加される。それらの多くは、上述した汎用機構を使用して実装することができ、したがって、将来的には、汎用機構の使用に反対し、汎用機構をサポートする可能性がある。
♪the HoverTool 2つの内蔵データを持ったコールバックがあります index そして geometry それがそうです。♪the index ホバリングツールが存在する任意の点のインデックス。
HoverTool
index
geometry
from bokeh.models import ColumnDataSource, CustomJS, HoverTool from bokeh.plotting import figure, output_file, show output_file("hover_callback.html") # define some points and a little graph between them x = [2, 3, 5, 6, 8, 7] y = [6, 4, 3, 8, 7, 5] links = { 0: [1, 2], 1: [0, 3, 4], 2: [0, 5], 3: [1, 4], 4: [1, 3], 5: [2, 3, 4] } p = figure(plot_width=400, plot_height=400, tools="", toolbar_location=None, title='Hover over points') source = ColumnDataSource({'x0': [], 'y0': [], 'x1': [], 'y1': []}) sr = p.segment(x0='x0', y0='y0', x1='x1', y1='y1', color='olive', alpha=0.6, line_width=3, source=source, ) cr = p.circle(x, y, color='olive', size=30, alpha=0.4, hover_color='olive', hover_alpha=1.0) # add a hover tool that sets the link data for a hovered circle code = """ const links = %s const data = {'x0': [], 'y0': [], 'x1': [], 'y1': []} const indices = cb_data.index.indices for (var i = 0; i < indices.length; i++) { const start = indices[i] for (var j = 0; j < links[start].length; j++) { const end = links[start][j] data['x0'].push(circle.data.x[start]) data['y0'].push(circle.data.y[start]) data['x1'].push(circle.data.x[end]) data['y1'].push(circle.data.y[end]) } } segment.data = data """ % links callback = CustomJS(args={'circle': cr.data_source, 'segment': sr.data_source}, code=code) p.add_tools(HoverTool(tooltips=None, callback=callback, renderers=[cr])) show(p)
ユーザが字形(例えば丸印)をクリックすると、URLを開くことは非常に流行的な機能である。Bokehにより、ユーザは、ユーザがフォントをクリックしたときに操作を呼び出すためにTapツールに渡すことができるOpenURLコールバックオブジェクトを公開することによって、この機能を有効にすることができる。
以下のコードは,OpenURL操作とTapToolを用いてユーザが円をクリックする際にURLを開く方法を示している.
from bokeh.models import ColumnDataSource, OpenURL, TapTool from bokeh.plotting import figure, output_file, show output_file("openurl.html") p = figure(plot_width=400, plot_height=400, tools="tap", title="Click the Dots") source = ColumnDataSource(data=dict( x=[1, 2, 3, 4, 5], y=[2, 5, 8, 2, 7], color=["navy", "orange", "olive", "firebrick", "gold"] )) p.circle('x', 'y', color='color', size=20, source=source) # use the "color" column of the CDS to complete the URL # e.g. if the glyph at index 10 is selected, then @color # will be replaced with source.data['color'][10] url = "http://www.html-color-names.com/@color.php" taptool = p.select(type=TapTool) taptool.callback = OpenURL(url=url) show(p)
ご注意ください OpenURL コールバックは特定されており、適用されるのは TapTool また,ヒット字形の場合にのみ呼び出される.すなわち,クリックごとに実行されるわけではない.マウスをクリックするたびにコールバックを実行したい場合は、参照してください ユーザインタラクションイベントのCustomJS それがそうです。
OpenURL
TapTool