Skip to content

Instantly share code, notes, and snippets.

@wakita
Last active July 31, 2020 08:02
Show Gist options
  • Save wakita/7dcd0640bdf3d43600cf526325b133f2 to your computer and use it in GitHub Desktop.
Save wakita/7dcd0640bdf3d43600cf526325b133f2 to your computer and use it in GitHub Desktop.
# CustomJS についてのわかりやすい例題

CustomJS についてのわかりやすい例題

加えて CustomJS を扱うためのツール (snjs.py) を作成した.

JavaScriptによるコールバック関数の定義

Bokeh では Python のファイルのなかに文字列として JavaScript のコードを埋め込みます.でも,二種類の言語の文法が混在するのはやっかいです.

そこで,JavaScript のコードは独立したものを *.js ファイルに関数として定義することにしました.たとえば,以下は普通の JavaScript の関数のように見えますが,本ツールを利用すると slider_on_change という名前で,filtercds という変数によって,Python のデータ構造を受け取るような CustomJS の定義として扱われます.

function slider_on_change(filter, cds) {
  const [start, end] = cb_obj.value;
  filter.booleans = Array.from(cds.data['date']).map(d => (start <= d && d <= end));
  // Needed because of https://github.com/bokeh/bokeh/issues/7273
  cds.change.emit();
}

コールバック関数の利用

このような定義を含んた *.js ファイルを読み込むための API が snjs.load 関数です.たとえば,CustomJS を定義するファイルの名前が callbacks.js ならば snjs.load('callbacks.js') というように使います.

読み込んだ定義を利用するときは,CustomJS に渡す Python のデータ構造を渡します.このための API が snjs.bind です.この関数の第一引数には対象となる CustomJS の関数名を指定します.ここでは *.js のなかで宣言した関数の名前を文字列で与えます.そのあとには,指定した CustomJS に渡す Python のデータ構造を列挙します.該当する関数定義の引数に沿って与えて下さい.

たとえば,上の slider_on_change の場合は,snjs.bind('slider_on_change', date_filter, data_source) のように指定します.

snjs.bind は CustomJS を返すので,それをウィジェットのコールバックとして指定することができます.

function slider_on_change(filter, cds) {
const [start, end] = cb_obj.value;
filter.booleans = Array.from(cds.data['date']).map(d => (start <= d && d <= end));
// Needed because of https://github.com/bokeh/bokeh/issues/7273
cds.change.emit();
}
# CustomJS についてのわかりやすい例題
# samples/bokeh/interaction/20200729-daterange
# Originally created by Eugene Pakhomov on 2020-03-26
# https://stackoverflow.com/questions/60872782/simple-date-rangeslider-in-bokeh-2-0/60873582#60873582
import pandas as pd
from bokeh.io import show
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, BooleanFilter, CDSView, DateRangeSlider, CustomJS
from bokeh.plotting import figure
import snjs
snjs.load('callbacks.js')
df = pd.DataFrame(dict(date=['2020-01-01', '2020-01-02', '2020-01-03'], cases=[1, 2, 3]))
df['date'] = pd.to_datetime(df['date'])
cds = ColumnDataSource(df)
p = figure(plot_width=800, plot_height=350, x_axis_type="datetime")
init_value = (df['date'].min(), df['date'].max())
slider = DateRangeSlider(start=init_value[0], end=init_value[1], value=init_value)
date_filter = BooleanFilter(booleans=[True] * df.shape[0])
slider.js_on_change('value', snjs.bind('slider_on_change', date_filter, cds))
p.circle('date', 'cases', source=cds, view=CDSView(source=cds, filters=[date_filter]),
color='navy', alpha=0.5, legend_label="cases", line_width=2)
show(column(p, slider))
'''
CustomJS を利用するための簡単なツール.
Python のなかに JavaScript を文字列として埋め込む気持ち悪さから,作ってしまった.
主な要件は以下の通り.
- JavaScript は `*.js` なファイルに保存し,JavaScript の文法は nodejs で読み込んで確認できること.
- JavaScript のファイルで宣言した名前を Python 側で指定することで,そのコードの CustomJS を取得できること.
'''
import re
from bokeh.models import CustomJS
db = dict()
Fname = r'(?P<name>\w+)'
Fargs = r'(?P<args>\w+(, *\w+)*)'
FunctionHeader = re.compile(r'function {} *\({}\).*'.format(Fname, Fargs))
#print(FunctionHeader)
Comma = re.compile(' *, *')
# *.js なファイルを読み込み,そのなかに見つかる関数定義を保存していく.
def load(path):
with open(path) as r:
name, args, body = False, [], []
for line in r:
m = FunctionHeader.match(line)
if m:
if name: db[name] = (name, args, ''.join(body))
name, args, body = m.group('name'), Comma.split(m.group('args')), ['{\n']
elif name: body.append(line)
if name: db[name] = (name, args, ''.join(body))
# 指定した JavaScript のコードを CustomJS に変換する.
# bind に与えられた実引数を JavaScript のコードの仮引数に束縛する.
def bind(codename, *args):
_, argnames, jscode = db[codename]
return CustomJS(args=dict(zip(argnames, args)), code=jscode)
# 使用例,兼テスト
if __name__ == '__main__':
load('callbacks.js')
for _, (name, args, body) in db.items():
print(name, args)
print(body)
print(bind('SliderCallback', 'sliceCDS', 'fullCDS', 'indexCDS'))
print(bind('TogglePlayCallback', 'slider', 'indexCDS'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment