Bokehの図形モデル#
import helper.magics
from bokeh.plotting import figure
import pandas as pd
import numpy as np
from bokeh.models import ColumnDataSource
from bokeh.layouts import column, row
from bokeh.io import output_notebook, show, reset_output
output_notebook()
モデルの属性#
図形オブジェクトはモデルクラスModel
を継承しており、図形を描画する際に直接図形オブジェクトを作成する必要はありません。figure
オブジェクトの描画メソッドを呼び出すことで、対応する図形オブジェクトを作成し、それをチャートに追加することができます。例えば、figure.line()
はLine
オブジェクトを作成し、figure.scatter()
はScatter
オブジェクトを作成します。
図形オブジェクトの属性はデータソースの列と接続することができます。例えば、データソースの列を使用して各散布点の塗りつぶし色を指定することができます。本書で提供されているshow_glyph_info()
を使用すると、図形の各属性のタイプを確認することができます。以下のプログラムはScatter
図形の各属性を表示します。
from bokeh.models import Scatter
from helper.bokeh import show_glyph_info
%omit 40 show_glyph_info(Scatter)
angle:
------
AngleSpec(units_default="rad")
angle_units:
------------
NotSerialized(Enum("deg", "rad", "grad", "turn"))
decorations:
------------
List(Instance(Decoration))
fill_alpha:
-----------
AlphaSpec(
String,
Float,
Instance(Value),
Instance(Field),
Instance(Expr),
Struct(value=Float, transform=Instance(Transform)),
Struct(field=String, transform=Instance(Transform)),
Struct(expr=Instance(Expression), transform=Instance(Transform)),
)
fill_color:
-----------
ColorSpec(
String,
Nullable(Color),
Instance(Value),
Instance(Field),
Instance(Expr),
Struct(value=Nullable(Color), transform=Instance(Transform)),
Struct(field=String, transform=Instance(Transform)),
Struct(expr=Instance(Expression), transform=Instance(Transform)),
)
hatch_alpha:
------------
...
上記のプログラムは、塗りつぶし色属性fill_color
のタイプを以下のように表示します:
ColorSpec(
String,
Nullable(Color),
Instance(Value),
Instance(Field),
Instance(Expr),
Struct(value=Nullable(Color), transform=Instance(Transform)),
Struct(field=String, transform=Instance(Transform)),
Struct(expr=Instance(Expression), transform=Instance(Transform)),
)
タイプ名にSpec
が含まれている場合、その属性はデータソースと接続できることを示します。その値は複数のタイプを持つことができます:例えば、文字列タイプString
、色タイプColor
、データソースのフィールドField
等々。
色タイプColor
は、色を表す文字列またはタプルを受け取ることができます。例えば、以下の4つはすべて色を表す値です:
circle.fill_color = "red" #色名
circle.fill_color = "#a240a2" #16進数色値
circle.fill_color = (100, 100, 255) #タプルで色の赤緑青成分を表す
circle.fill_color = (100, 100, 255, 0.5) #最後の浮動小数点数は不透明度を表す
ColorSpec
タイプの属性を文字列で設定する場合、まず文字列を色名として解釈し、失敗した場合はデータソースの列名として解釈します。必ずデータソースの列名をとして解釈させたい場合はfield
オブジェクトを使います。クラスメソッドdataspecs()
を使用して、データソースと接続できるすべての属性を取得することができます。
次のセクションでは、一般的な図形の描画方法を詳細に説明します。
散列点Scatter#
Scatter
は散布図を描画するモデルで、次の属性はデータソースと連携できます。
from bokeh.models import Scatter
Scatter.dataspecs().keys()
dict_keys(['x', 'y', 'size', 'angle', 'line_color', 'line_alpha', 'line_width', 'line_join', 'line_cap', 'line_dash', 'line_dash_offset', 'fill_color', 'fill_alpha', 'hatch_color', 'hatch_alpha', 'hatch_scale', 'hatch_pattern', 'hatch_weight', 'marker'])
Bokehの列挙型の取り得る値はbokeh.core.enums
モジュールで定義されています。例えば、以下はmarker
属性のすべての候補値を取得する方法です:
from bokeh.core import enums
%col 4 list(enums.MarkerType)
['asterisk', 'circle', 'circle_cross', 'circle_dot',
'circle_x', 'circle_y', 'cross', 'dash',
'diamond', 'diamond_cross', 'diamond_dot', 'dot',
'hex', 'hex_dot', 'inverted_triangle', 'plus',
'square', 'square_cross', 'square_dot', 'square_pin',
'square_x', 'star', 'star_dot', 'triangle',
'triangle_dot', 'triangle_pin', 'x', 'y']
散列点を使用してデータを表示する場合、通常データソースには色や散列点の形状を直接表すデータは保存されていません。この場合、データ変換器を使用してデータソースのデータを色や形状などのデータに変換できます。以下では、散列点を使用して3種類のアヤメの花弁サイズの分布を表示します。
❶factor_mark()
は分類データを散列点の形状に変換できます。その3つのパラメータは、データソースの列名、散列点形状リスト、分類データリストです。❷factor_cmap()
は分類データを色に変換できます。ここでは、分類カラーリストCategory10_10
からランダムに3色を選択しています。❸scatter()
メソッドを呼び出す際、パラメータlegend_field
を使用して凡例に対応する列名を指定できます。パラメータcolor
を使用して色を設定すると、line_color
とfill_color
属性が同時に設定されます。パラメータalpha
もcolor
と同様に、line_alpha
とfill_alpha
属性を同時に設定できます。
from bokeh.transform import factor_mark, factor_cmap
from bokeh.palettes import Category10_10
from random import sample
df = pd.read_csv("data/iris.csv")
fig = figure(width=500, height=350)
source = ColumnDataSource(df)
species_types = df.species.unique().tolist()
marker = factor_mark("species", ["diamond", "circle", "triangle"], species_types) # ❶
color = factor_cmap(
"species", sample(Category10_10, k=len(species_types)), species_types
) # ❷
scatter = fig.scatter(
x="sepal_length",
y="sepal_width",
legend_field="species",
marker=marker,
color=color,
source=source,
size=12,
alpha=0.6,
) # ❸
show(fig)
円形Circle#
円形を表す図元Circle
データ空間の長さ単位で円形の半径を指定できます。これにより、描画された円形はチャートビューのズームに合わせて拡大縮小されます。次のテーブルは円形の半径に関連する3つの属性です。radius_units
が’data’で、チャートのmatch_aspect
属性がTrue
の場合、チャートのX軸とY軸の単位長さは同じになり、円形の半径はデータ空間の長さ単位になります。X軸とY軸の単位長さが異なる場合、radius_dimension
属性を使用して半径の単位長さを指定します。’x’はX軸の単位長さを使用し、’y’はY軸の単位長さを使用し、’max’はX軸とY軸の単位長さの大きい方を使用し、’min’は単位長さの小さい方を使用します。
属性名 |
属性類型 |
説明 |
---|---|---|
|
|
円形の半径 |
|
|
半径長さに対応する次元、候補値は’x’, ‘y’, ‘max’, ‘min’ |
|
|
半径の単位、候補値は’screen’, ‘data’、デフォルト値は’data’ |
以下の例では、異なるサイズと塗りつぶし色の円形を使用して絵を構成し、その効果は次のグラフのようになります。ファイル”venus-face.csv”の各行は1つの円形に対応し、xとy列は円の中心座標、radius列は円の半径、r、g、b列は塗りつぶし色の赤、緑、青の3つの成分です。pandas.read_csv()
を使用してこのファイルを読み込んだ後、DataFrame
オブジェクトのassign()
メソッドを呼び出して新しいDataFrame
オブジェクトを作成します。そのcolor列はr、g、b列を16進数に変換した後の色値です。
df = pd.read_csv("data/venus-face.csv")
df2 = df.assign(
color=df.eval("r * 2**16 + g * 2**8 + b").apply("#{:06x}".format),
).drop(["r", "g", "b"], axis=1)
%C df.head();df2.head()
df.head() df2.head()
----------------------------------- -----------------------------
x y radius r g b x y radius color
0 0 500 71.5124 197 212 213 0 0 500 71.5124 #c5d4d5
1 0 363 63.9851 191 209 211 1 0 363 63.9851 #bfd1d3
2 0 139 59.2101 201 215 213 2 0 139 59.2101 #c9d7d5
3 159 500 48.4660 193 209 204 3 159 500 48.4660 #c1d1cc
4 0 249 43.8037 184 203 203 4 0 249 43.8037 #b8cbcb
ColumnDataSource
はDataFrame
オブジェクトを直接データソースのデータに変換することをサポートしています。circle()
を呼び出して円形図元を描画する際、円形の半径radius
属性はデータソースのradius列に対応し、塗りつぶし色属性fill_color
はcolor列に対応します。画像全体の比率を正しく保つために、match_acpect
属性をTrue
に、aspect_ratio
属性を1に設定する必要があります。
fig = figure(match_aspect=True, aspect_ratio=1, x_range=(0, 500), y_range=(0, 500))
source = ColumnDataSource(data=df2)
fig.circle(
"x", "y", radius="radius", fill_color="color", source=source, line_color=None
)
show(fig)
線分Segment#
図元Segment
を使用して、複数の色と太さが異なる線分を描画できます。そのx0, x1, y0, y1, line_color, line_alpha, line_width
などの属性はデータソースに接続できます。以下の例では、Segment
図元を使用して電子ピアノから録音したMIDIデータを表示し、その効果は次のグラフのようになります。
ファイル”for_elise.csv”には電子ピアノのキー情報が保存されています。各行は1つのキーイベントを表し、note列はキーの音符、velocity列はキーの強度、start列とend列はキーが押された時間と離された時間です。DataFrame
オブジェクトとして読み込んだ後、音符をテキスト形式に変換します。例えば76は”E5”に変換されます。キーの強度は0から127の整数であるため、それを2倍してインデックスとしてカラーテーブルViridis256
から対応する色を取得できます。
from bokeh.palettes import Viridis256
notes = "C C# D D# E F F# G G# A A# B".split()
all_notes_name = pd.Series([f"{note}{i-1}" for i in range(0, 15) for note in notes])
df = pd.read_csv("data/for_elise.csv")
df["note_name"] = all_notes_name[df.note].values
df["color"] = [Viridis256[i * 2] for i in df.velocity]
print(df.head(3))
note velocity start end note_name color
0 76 65 0.391 0.506 E5 #1F928C
1 75 74 0.495 0.610 D#5 #1FA386
2 76 71 0.581 0.713 E5 #1E9D88
以下は描画部分です。❶figure()
のパラメータy_range
がリストの場合、Y軸はそのリストを目盛りラベルとして使用します。❷線分図元Segment
を使用してMIDI音符を表します。その属性x0
、y0
、x1
、y1
は線分の始点座標と終点座標に対応します。❸マウスホバーツールを追加します。このツールのtooltip
属性は、ホバー時に表示するヒント情報をカスタマイズできます。その各要素はタプルで、第0要素はヒントラベル、第1要素が@
で始まる場合、データソースの対応する列から情報を取得して表示します。
from bokeh.models import HoverTool
fig = figure(
y_range=all_notes_name.loc[df.note.min() : df.note.max()], width=800, height=400
) # ❶
source = ColumnDataSource(data=df)
fig.segment(
x0="start",
x1="end",
y0="note_name",
y1="note_name",
color="color",
source=source,
line_width=6,
) # ❷
hover = HoverTool(tooltips=[("note", "@note_name"), ("velocity", "@velocity")]) # ❸
fig.tools.append(hover)
show(fig)
多線MultiLine#
Line
図元は単一の折れ線しか表せません。複数の折れ線を描画するには、Figure.line()
をループで呼び出し、各折れ線に個別のデータソースオブジェクトを指定する必要があります。大量の折れ線を描画する場合、この方法は時間がかかります。そのため、Bokehは複数の折れ線を描画できるMultiLine
図元を提供しています。そのxs, ys, line_color, line_width, line_alpha
などの属性はデータソースに接続できます。xs
とys
はネストされたリストで、その各ペアは1つの折れ線の座標リストです。
以下のプログラムは正方形と直角三角形を描画します。2つの形状の線幅と線の色はデータソースで指定されます。その結果は次のグラフ(左)のようになります。
source1 = ColumnDataSource(
data=dict(
xs=[[0, 1, 1, 0, 0], [0.1, 0.9, 0.5, 0.1]],
ys=[[0, 0, 1, 1, 0], [0.1, 0.1, 0.5, 0.1]],
color=["red", "green"],
width=[2, 4],
)
)
fig1 = figure(frame_width=300, frame_height=300, toolbar_location=None)
fig1.multi_line("xs", "ys", line_color="color", line_width="width", source=source1);
折れ線の座標リストにNaNが含まれている場合、折れ線はNaNを区切り点として複数の折れ線に分割されます。以下のsource2
では、xs
とys
リストには1つの要素しかありませんが、折れ線は2つの折れ線に分割されます。これらの折れ線は論理的には1つの折れ線であるため、color
とwidth
リストには1つの要素しかありません。その結果は次のグラフ(右)のようになります。|
source2 = ColumnDataSource(
data=dict(
xs=[[0, 1, 1, 0, 0, np.nan, 0.1, 0.9, 0.5, 0.1]],
ys=[[0, 0, 1, 1, 0, np.nan, 0.1, 0.1, 0.5, 0.1]],
color=["red"],
width=[2],
)
)
fig2 = figure(frame_width=300, frame_height=300, toolbar_location=None)
fig2.multi_line("xs", "ys", line_color="color", line_width="width", source=source2)
layout = row(fig1, fig2)
show(layout)
以下では、Bokeh の MultiLine
図元を使用して等値線図を描画する方法を示します。Bokeh 自体には等値線を計算する機能がないため、contourpy
を利用して等高線を生成します。
等値線をプロットする際、同じレベルの等値線が複数の独立した曲線として表現されることがあります。これらの曲線を適切に処理するために、np.vstack()
を使用して座標データを結合し、曲線の区切りとして NaN
を挿入することで、Bokeh の MultiLine
で適切に描画できるようにします。
データソース (ColumnDataSource
) には以下の 3 つのキーを含めます。xs
と ys
は配列のリストで、それぞれの配列が 1 つのレベルに対応します。各配列内では、NaN
を挿入することで複数の曲線を区切ります。
"xs"
:等値線の X 座標のリスト"ys"
:等値線の Y 座標のリスト"levels"
:各等値線のレベル値(Z 値)
等値線の色は、linear_cmap()
を使用して levels
の数値をカラーマップ viridis
にマッピングし、各等値線に対応する色を設定します。
from bokeh.transform import linear_cmap
from bokeh.palettes import viridis
import contourpy
Y, X = np.mgrid[-6:6:100j, -8:8:150j]
Z = np.sin(X) + np.cos(Y)
contour_gen = contourpy.contour_generator(X, Y, Z, name="serial")
level_values = np.linspace(Z.min(), Z.max(), 10)[1:-1]
levels_lines = contour_gen.multi_lines(level_values)
xs = []
ys = []
for level in levels_lines:
lines = []
for line in level:
lines.append(line)
lines.append([[np.nan, np.nan]])
lines = np.vstack(lines)
xs.append(lines[:, 0])
ys.append(lines[:, 1])
source = ColumnDataSource(data={"xs": xs, "ys": ys, "levels": level_values})
fig = figure(
match_aspect=True,
aspect_ratio=8 / 6,
x_range=(X.min(), X.max()),
y_range=(Y.min(), Y.max()),
)
cmap = linear_cmap("levels", viridis(256), Z.min(), Z.max())
lines = fig.multi_line("xs", "ys", line_color=cmap, source=source, line_width=2)
show(fig)
多角形ブロックPatchとPatches#
Line
とMultiLine
と同様に、Patch
は多角形の塗りつぶし領域を描画し、Patches
は複数の多角形の塗りつぶし領域を描画するために使用されます。Patches
の属性はMultiLine
と似ていますが、塗りつぶしの色やパターンに関連するいくつかの属性が追加されています。例えば、fill_alpha
、fill_color
、hatch_alpha
、hatch_color
などです。以下の例では、Patches
を使用して中国地図の各省级行政区を描画し、その結果を次のグラフに示します。
import json
from bokeh.models import ColumnDataSource, CustomJS, HoverTool, Div
from bokeh.plotting import figure
from bokeh.transform import linear_cmap
from bokeh.palettes import viridis
with open("data/china_simple.json", encoding="utf-8") as f:
geo = json.load(f) # ❶
xs = []
ys = []
population = []
for feature in geo["features"]:
population.append(feature["properties"]["population"]) # ❷
geometry = feature["geometry"]
geo_type = geometry["type"]
coordinates = geometry["coordinates"]
if geo_type == "Polygon":
x, y = zip(*coordinates[0])
elif geo_type == "MultiPolygon":
xys = []
for item in coordinates:
xys.extend(item[0])
xys.append([np.nan, np.nan])
x, y = zip(*xys)
xs.append(x)
ys.append(y)
fig = figure(match_aspect=True, aspect_scale=1, frame_width=500, frame_height=350)
source = ColumnDataSource(data=dict(xs=xs, ys=ys, population=population))
cmap = linear_cmap(
"population", viridis(256), low=np.min(population), high=np.max(population)
)
patches = fig.patches("xs", "ys", source=source, fill_color=cmap, line_color="black")
show(fig)
Bokehは、ブラウザでGeoJSONを解析できるデータソースクラスGeoJSONDataSource
も提供しています。そのgeojson
属性はGeoJSON
データを表す文字列で、ブラウザで実行されるJavaScriptプログラムがこの文字列をデータソースの列に変換します。xs
とys
列はそれぞれ多角形領域のX-Y軸座標を保存し、各領域の属性もデータソースに保存されます。例えば、population
列にはすべての領域のpopulation
属性が保存されます。以下では、GeoJSONDataSource
を使用して上のグラフと同じ地図を描画します。
from bokeh.models import GeoJSONDataSource
with open("data/china_simple.json", encoding="utf-8") as f:
geojson_string = f.read()
source = GeoJSONDataSource(geojson=geojson_string)
fig = figure(match_aspect=True, aspect_scale=1, frame_width=500, frame_height=350)
cmap = linear_cmap("population", viridis(256), low=None, high=None)
patches = fig.patches("xs", "ys", source=source, line_color="black", fill_color=cmap)
show(fig)
GeoJSONDataSource
データソースのデータ列はブラウザで生成されるため、Pythonで直接GeoJSON
データの解析結果を確認することはできません。
Patches
の他に、Bokehは穴のある多角形を描画するためのMultiPolygons
図形も提供しています。これは、多層にネストされたリストを使用して多角形上の各点の座標値を表します。具体的な使用方法については、Bokehのドキュメントを参照してください。
六角形グリッドHexTile#
HexTile
は、蜂の巣状の正六角形グリッドを描画するために使用されます。そのfill_alpha, fill_color, line_alpha, line_color, line_width, q, r, scale
などの属性はデータソースに接続できます。属性q, r
は六角形グリッド座標系の座標です。六角形グリッドには、尖頂(pointytop)と平頂(flattop)の2つの配置方法があります。次のグラフに示すように、青いブロックはQ軸、オレンジのブロックはR軸で、ブロック内の数字はQ-R座標系での座標です。R軸の正方向は下向きであることに注意してください。
from bokeh.palettes import Category10_10
from bokeh.util.hex import axial_to_cartesian
from bokeh.layouts import row
q, r = [a.ravel() for a in np.mgrid[-2:3, -2:3]]
colors = np.array([Category10_10[7]] * len(q))
colors[r == 0] = Category10_10[0]
colors[q == 0] = Category10_10[1]
colors[(r == 0) & (q == 0)] = Category10_10[2]
figs = []
for orientation in ["pointytop", "flattop"]:
fig = figure(match_aspect=True, toolbar_location=None, title=orientation)
fig.grid.visible = False
fig.hex_tile(
q,
r,
size=1,
line_color="white",
fill_color=colors,
alpha=0.5,
orientation=orientation,
)
x, y = axial_to_cartesian(q, r, 1, orientation)
fig.text(
x,
y,
text=["(%d, %d)" % (q, r) for (q, r) in zip(q, r)],
text_baseline="middle",
text_align="center",
font_size="10pt",
)
figs.append(fig)
layout_hex_tile = row(figs)
show(layout_hex_tile)
六角形グリッドの座標を計算するために、Bokehはいくつかのヘルパー関数を提供しています:cartesian_to_axial()
、axial_to_cartesian()
、hexbin()
。cartesian_to_axial()
はデカルト座標系の座標を六角形グリッドの座標に変換し、hexbin()
は散在する点が六角形グリッドに落ちる回数をカウントします。
以下の例では、画像を六角形モザイクに変換し、その効果を次のグラフに示します。❶cartesian_to_axial()
を使用して画像のピクセル座標点を六角形グリッド座標系のグリッド座標に変換します。そのパラメータsize
は六角形の外接円の半径を指定します。❷画像をDataFrame
オブジェクトに変換します。これには5列のデータがあり、q列とr列は各ピクセルに対応する六角形グリッド座標で、red、green、blue列は各ピクセルの3つの色成分です。❸groupby()
メソッドを使用して、各グリッド内のピクセルの3つの色成分の平均値を計算します。❹最後に、平均色を16進数の文字列に変換します。❺hex_tile()
を呼び出して六角形グリッドを描画する際には、cartesian_to_axial()
と同じsize
とorientation
パラメータ値を使用する必要があります。
from imageio.v3 import imread
from bokeh.util.hex import cartesian_to_axial
img = imread("data/vinci_target.png")
h, w, _ = img.shape
size = 8
Y, X = np.mgrid[:h, :w]
Q, R = cartesian_to_axial(X, Y, size, orientation="pointytop") # ❶
df_img = pd.DataFrame(
np.dstack([Q, R, img[::-1]]).reshape(-1, 5),
columns=["q", "r", "red", "green", "blue"],
) # ❷
df_img2 = df_img.groupby(["q", "r"]).mean().astype(np.uint32).reset_index() # ❸
tile_colors = (
df_img2.eval("red*2**16 + green*2**8 + blue").map("#{:06x}".format).tolist()
) # ❹
source = ColumnDataSource(
data=dict(q=df_img2.q.values, r=df_img2.r.values, color=tile_colors)
)
fig = figure(
match_aspect=True,
frame_width=w,
frame_height=h,
x_range=(0, w),
y_range=(0, h),
toolbar_location=None,
)
fig.xaxis.visible = fig.yaxis.visible = False
renderer = fig.hex_tile(
"q",
"r",
fill_color="color",
line_color=None,
size=size,
orientation="pointytop",
source=source,
) # ❺
show(fig);
テキストText#
テキスト図形Text
は、通常、チャートに注釈情報を追加するために使用されます。そのangle, text, text_alpha, text_color, text_font_size, x, x_offset, y_offset
などの属性はデータソースに接続できます。x, y
はテキストの位置を指定し、この座標とテキスト位置の関係はtext_align
属性とtext_baseline
属性によって決定されます。次のグラフは、これらの属性の候補値とそれらがテキストとどのように相対的に配置されるかを示しています。
# %hide
from bokeh.core import enums
from itertools import product
text_align = list(enums.TextAlign)
text_baseline = list(enums.TextBaseline)
fig = figure(
height=400,
x_range=["left", "center", "right"],
y_range=["bottom", "ideographic", "alphabetic", "middle", "hanging", "top"],
toolbar_location=None,
)
fig.xaxis.axis_label = "text_align"
fig.yaxis.axis_label = "text_baseline"
for align, baseline in product(text_align, text_baseline):
fig.text(
[align],
[baseline],
["Py³"],
font_size="30pt",
text_align=align,
text_baseline=baseline,
)
show(fig)
以下の例では、Text
を使用して次のグラフに示すようなワードクラウドを表示します。ファイル”python_words.csv”は、wordcloud
ライブラリを使用して作成されたワードクラウドデータで、Python標準ライブラリのソースコードで頻繁に出現する単語を統計しています。そのデータは次のテーブルに示すように、x、y列はテキストの座標で、テキストの配置は'center'
と'bottom'
、size列はテキストのサイズで、’32px’はテキストサイズが32ピクセルであることを示します。これは単位付きの文字列で、'px'
はピクセル単位であることを示します。angle列はラジアン単位のテキストの方向です。
x |
y |
word |
size |
angle |
color |
---|---|---|---|---|---|
239 |
453 |
return |
32px |
0 |
#84adcd |
201 |
293 |
header |
11px |
0 |
#3975a5 |
158.5 |
213 |
val |
6px |
0 |
#ffe873 |
285 |
424.5 |
process |
6px |
1.5708 |
#3b77a8 |
63 |
348 |
del |
11px |
0 |
#498abd |
テキストサイズがピクセル単位であるため、チャートのデータ空間もピクセル単位である場合にのみ、ワードクラウドを正しく表示できます。figure()
を作成する際に、パラメータframe_height
とframe_width
を使用して、描画領域の高さと幅をピクセル単位で指定します。また、パラメータx_range
とy_range
を使用してX軸とY軸の表示範囲を設定します。表示範囲が描画領域のサイズと一致するため、データ空間の1単位長さは1ピクセルに相当します。
df = pd.read_csv("data/python_words.csv")
from bokeh.core.property.vectorization import Value
fig = figure(
frame_height=500,
frame_width=500,
x_range=(0, 500),
y_range=(0, 500),
toolbar_location=None,
)
fig.grid.visible = False
fig.background_fill_color = "black"
source = ColumnDataSource(data=df)
fig.text(
"x",
"y",
text="word",
angle="angle",
text_font_size="size",
text_color="color",
text_align="center",
text_baseline="bottom",
text_font_style="normal",
text_font=Value("times"),
source=source,
)
fig.xaxis.visible = False
fig.yaxis.visible = False
show(fig)
画像プリミティブ#
Bokehでは、以下の3つのプリミティブを使用して画像を表示することができます。Image
とImageRGBA
のdh, dw, image, x, y
などの属性はデータソースに接続することができ、ImageURL
のangle, h, url, w, x, y
などの属性もデータソースに接続することができます。属性x, y
は画像の左下隅の座標を指定し、dh, dw
はデータ空間における画像の高さと幅です。
Image
: パレットまたはカラーマッパーを使用して2次元配列を画像に変換し、通常は2次元関数を表示するために使用されます。ImageRGBA
: 形状が(height, width, 4)
の配列を4チャンネル(赤、緑、青、α)の画像として表示します。ImageURL
: 画像のデータはURLによって指定されます。
以下では、これらの3つのプリミティブを使用して2次元関数の画像を表示します。まず、NumPy配列のブロードキャスト機能を使用して2次元配列Z
を計算します。Image
プリミティブを使用する場合、パラメータpalette
を使用して数字を色に変換するパレットを指定できます。データ量を減らすために、ここではまず画像を32ビットの単精度浮動小数点数配列に変換します。
from bokeh.palettes import viridis
Y, X = np.ogrid[-3:3:100j, -3:3:100j]
Z = np.sin(X) + np.sin(Y)
x_min, y_min, x_ptp, y_ptp = X.min(), Y.min(), np.ptp(X), np.ptp(Y)
source1 = ColumnDataSource(
data=dict(
image=[Z.astype(np.float32)], x=[x_min], y=[y_min], dw=[x_ptp], dh=[y_ptp]
)
)
fig1 = figure(
frame_width=300, match_aspect=True, aspect_ratio=x_ptp / y_ptp, title="Image"
)
fig1.image("image", "x", "y", "dw", "dh", source=source1, palette=viridis(256));
ImageRGBA
の画像データは、要素タイプが32ビット整数の2次元配列です。以下では、まずmatplotlib.cm
を使用して正規化された画像Zn
をRGBA画像を表す配列Z_img
に変換します。この配列の形状は(100, 100, 4)です。次に、そのview()
を呼び出して、要素タイプがuint32
のビュー配列Z_img_show
を作成します。その形状は(100, 100)
です。
from matplotlib import cm
Zn = (Z - Z.min()) / np.ptp(Z)
Z_img = cm.viridis(Zn, bytes=True)
Z_img_show = Z_img.view(np.uint32)[:, :, 0]
source2 = ColumnDataSource(
data=dict(image=[Z_img_show], x=[x_min], y=[y_min], dw=[x_ptp], dh=[y_ptp])
)
fig2 = figure(
frame_width=300, match_aspect=True, aspect_ratio=x_ptp / y_ptp, title="ImageRGBA"
)
fig2.image_rgba("image", "x", "y", "dw", "dh", source=source2);
Image
とImageRGBA
はどちらもPythonの配列データをブラウザに渡して表示する必要がありますが、このデータは圧縮されていないため、大きな配列を表示する場合、転送されるデータ量が大きくなります。この状況を避けるために、Pythonで画像データを埋め込み画像データのURL文字列に変換し、ImageURL
を使用してその画像を表示することができます。本書で提供されているscpy3.bokehelp.encode_image()
は、この変換を実行します。これはOpenCVライブラリを使用しています。画像の原点が左上にあるため、ここではまずZ_img
を垂直方向に反転してからJPEG画像に変換します。ImageURL
の属性anchor
は、画像のアンカーポイント、つまり原点座標と画像の相対位置を指定できます。3つの方法で描画された画像は次のグラフに示されています。
from helper.bokeh import encode_image
Z_jpeg = encode_image(Z_img[::-1])
source3 = ColumnDataSource(
data=dict(image=[Z_jpeg], x=[x_min], y=[y_min], dw=[x_ptp], dh=[y_ptp])
)
fig3 = figure(
frame_width=300, match_aspect=True, aspect_ratio=x_ptp / y_ptp, title="ImageURL"
)
fig3.image_url("image", "x", "y", "dw", "dh", source=source3, anchor="bottom_left");
from bokeh.layouts import row
layout = row(fig1, fig2, fig3)
show(layout)
以下では、これら3つのチャートをそれぞれHTMLファイルとして保存した場合のサイズを確認します。Image
の画像データは32ビット浮動小数点数配列であり、ImageRGBA
の画像データは32ビット整数配列であるため、それらが出力するHTMLファイルのサイズはほぼ同じです。一方、ImageURL
はJPEG圧縮画像を使用するため、出力するHTMLファイルははるかに小さくなります。
from bokeh import embed
for name, fig in zip(("image", "image_rgba", "image_url"), [fig1, fig2, fig3]):
size = len(embed.file_html(fig, resources="cdn"))
print(f"{name:10s} = {size:>6d}")
image = 67495
image_rgba = 59530
image_url = 8561