🤪

Bokehでユーザー操作結果のファイル出力

2021/11/17に公開

https://bokeh.org/
bokehはpython&javascriptでインタラクティブな可視化を実装できます。
そして、インタラクティブなユーザー操作が可能であればさらにその操作結果が欲しくなってきたりします。

今回はbokehを使用した、

  • 3色(赤、青、緑)の点描画をランダムプロット
  • ユーザーがダブルクリックで色変換(赤→青→緑→赤...)
  • 色変換結果をjson形式でダウンロード

以上の機能が揃っている「ユーザー操作結果のファイル出力」の実装をやってみます。
プロットエリアは大きく分けて「保存ボタン(上)」と「点描画(下)」の2エリアです。

BokehJS 2.4.0

jupyter notebookでのコードはこちらにあります。
https://github.com/akariiijima/VisBokeh/tree/main/DownloadUserActionResult
ポイントは「ColumnDataSource」と「ダブルクリック処理」と「saveボタン」の3つです。


ポイントその1 「ColumnDataSource」

https://docs.bokeh.org/en/latest/docs/reference/models/sources.html#columndatasource

ColumnDataSourceはpythonでいうPandasのDataFrameに該当すると思っても良いです。

  • 「プロット後もデータ保持」
  • 「他プロットエリアへのデータ共有」

が最大の特徴として挙げられます。
これにより、保持データの値を変更してプロット形状を変化させたり、ブラウザ上の操作ログを蓄積したりできます。※リロードすると初期データでプロットされます

X = [random.randint(0, POINTS_COUNT) for _ in range(POINTS_COUNT)]
Y = [random.randint(0, POINTS_COUNT) for _ in range(POINTS_COUNT)]
color = [random.choice(COLORS) for _ in range(POINTS_COUNT)]
source = ColumnDataSource(dict(X=X, Y=Y, color=color))

今回の実装では、ランダムに座標情報(X, Y)と色情報(color)をColumnDataSourceで作成し、2つのエリア(「保存ボタン(上)」と「点描画(下)」)に共有データとして渡しています。

ポイントその2 「ダブルクリック処理」

def doubleTapCallback(colors, source, size):
    return CustomJS(args=dict(colors=colors, source=source, size=size), code="""
    ...
"""

def scatterPlot(source):
    plot = figure(tools=TOOLS, plot_width=500, plot_height=300)
    plot.scatter(x='X', y='Y', color='color', source=source, size=POINTS_SIZE)
    plot.js_on_event('doubletap', doubleTapCallback(COLORS, source, POINTS_SIZE/10))
    return plot

プロットエリアにjs_on_eventを生やすことによってjsイベントを発火させることができます。
js_on_eventは、第一引数にイベントタイプ、第二引数にCustomJSを指定して、javacsriptの実装を反映させることができます。
今回は点描画をダブルクリックすることによって、

  1. クリック範囲に入っている点描画検索
  2. 現在の色から次の色へと変換
  3. ColumnDataSourceに変更を適応し保存

という流れで色変更&データ保持しています。
1に関しては単純な線形探索なので、データ量が多い場合は他の最適な探索アルゴリズムを実装すると良さそうです。
また、doubletap以外にもその他jsイベント対応が豊富ですので下記を参照にしてみてください。

https://docs.bokeh.org/en/latest/_modules/bokeh/events.html#ButtonClick

jsイベント対応一覧
#-----------------------------------------------------------------------------
# Globals and constants
#-----------------------------------------------------------------------------

__all__ = (
    'ButtonClick',
    'DocumentEvent',
    'DocumentReady',
    'DoubleTap',
    'Event',
    'LODStart',
    'LODEnd',
    'MenuItemClick',
    'ModelEvent',
    'MouseEnter',
    'MouseLeave',
    'MouseMove',
    'MouseWheel',
    'Pan',
    'PanEnd',
    'PanStart',
    'Pinch',
    'PinchEnd',
    'PinchStart',
    'RangesUpdate',
    'Rotate',
    'RotateEnd',
    'RotateStart',
    'PlotEvent',
    'PointEvent',
    'Press',
    'PressUp',
    'Reset',
    'SelectionGeometry',
    'Tap',
)

ポイントその3 「saveボタン」

そもそも、bokehは可視化に強いライブラリですので、操作ログ・操作結果がゲット・ダウンロードできるような機能までは実装されていません。(2021年11月時点)
ブラウザ上で動かせる利点を最大限に活かすことで操作結果をダウンロードできるようにします。

  1. bokehによってplotされるhtmlからaタグを生成します。
  2. それをクリック処理により保存済みColumnDataSourceをjson形式で出力します。
  3. 最後にはaタグを削除します。

単純な話、無理やりaタグを生成&クリックしているだけです。(研究のみ使用するなどしてセキュアに使いましょう!)

const a = document.createElement("a");
document.body.appendChild(a);
a.download = 'color_test.json';
a.href = "data:text/plain," + encodeURIComponent(JSON.stringify(savedData));
a.click();
a.remove();

最後に

この実装を応用することで「ユーザー操作結果」だけではなく「ユーザー操作ログ」も出力することもできます。他にも、ブラウザ上(javascript)でできるようなことは大体実装可能です。ぜひぜひたくさん実装してみてください!素敵なbokehライフを〜!
https://github.com/akariiijima/VisBokeh/tree/main/DownloadUserActionResult

Discussion