---
title: "Bokehとは？Pythonでの使い方と2軸グラフの作り方【3.10.1対応】"
url: "https://www.issoh.co.jp/tech/details/3371/"
published: 2024-08-22
updated: 2026-10-28
categories: ["Python"]
publisher: "株式会社一創"
---

# Bokehとは？Pythonでの使い方と2軸グラフの作り方【3.10.1対応】

**Bokeh（ボケ）**は、Pythonで書いたグラフをWebブラウザ上で拡大・移動・ホバー表示できるようにするデータ可視化ライブラリです。出力はHTMLとJavaScript（BokehJS）なので、グラフをHTMLファイル1つで配ったり、スライダーで条件を変えるダッシュボードをWebアプリとして動かしたりできます。2026年10月時点の最新版は**3.10.1**（2026年10月13日公開）で、Python 3.12以上が必要です。この記事では、インストールから最初のグラフ、ColumnDataSource、検索の多い2軸グラフ（右側に2本目のY軸を持つグラフ）、Bokehサーバー、FastAPIへの組み込みまでを、3.10.1で実行して確かめたコードで説明します。

## まとめ：Bokehの使い方と2軸グラフの要点

- Bokehは操作できるグラフをブラウザに出すPythonライブラリ。写真用語の「bokeh（ボケ）」と綴りは同じだが別物。
- インストールは `pip install bokeh`。3.10系はPython 3.12以上が必要で、Python 3.10・3.11ではエラーにならず3.9.2が入る。3.9.0からpandasは自動では入らない。
- 2軸グラフは、`extra_y_ranges` に名前付きの範囲を登録し、同じ名前を `LinearAxis` と右軸側の系列の `y_range_name` に指定して作る。
- 範囲を自動に任せると左軸が右の系列まで含んで潰れる。範囲を固定するか、`DataRange1d(renderers=[...])` で系列ごとに自動調整させる。
- ブラウザ内で済む操作は `CustomJS`、Pythonの処理が要る操作は `bokeh serve` のBokehサーバーで動かす。3.10.0からはFastAPIなどのASGIアプリにもマウントできる。

## Bokehとは：写真用語の「ボケ」との違いと向いている用途

「bokeh」で検索すると、上位は写真のぼけ（背景をぼかす表現）の解説が占めます。この記事で扱うのは同じ綴りのPythonライブラリです。Pythonで `figure` にグラフを描くと、ブラウザ側のJavaScriptライブラリBokehJSが描画を担当し、マウスで拡大・移動・値の確認ができる状態で表示されます。

GitHubのリポジトリ（bokeh/bokeh）は2012年3月に作られ、PyPIで確認できる最古の版は2013年10月の0.2で、作者はContinuum Analytics（現Anaconda）と記録されています。現在は非営利団体NumFOCUSの支援を受けるコミュニティ開発のプロジェクトで、ライセンスはBSD-3-Clauseです。商用のシステムに組み込んで使えます。

向いているのは、分析結果をブラウザで触れる形で渡したい場面です。散布図の点にマウスを当てて値を読む、複数のグラフで同じ点を同時に選択する、スライダーで条件を変えて再描画する、といった操作をPythonだけで組めます。論文やスライド用の静止画だけが目的なら[Matplotlib](/tech/details/3125/)を選んでください。Bokehで画像を書き出すにはブラウザの準備が要ります。Qtのデスクトップアプリで毎秒何十回も描き直す用途も、ブラウザを介さない[PyQtGraph](/tech/details/6110/)のほうが向いています。

## 最新バージョン3.10.1と動作要件

[PyPIの公開情報](https://pypi.org/project/bokeh/)から、直近の版と要件を並べます。既存の記事やコードとの差になりやすいのは、3.10.0で必要なPythonが3.12以上へ上がったことと、3.9.0でpandasが必須の依存から外れたことです。

| 版      | 公開日（PyPI）   | 必要なPython | pandas |
| ------ | ----------- | --------- | ------ |
| 3.9.0  | 2026年3月11日  | 3.10以上    | 任意     |
| 3.9.2  | 2026年7月25日  | 3.10以上    | 任意     |
| 3.10.0 | 2026年8月18日  | 3.12以上    | 任意     |
| 3.10.1 | 2026年10月13日 | 3.12以上    | 任意     |

次のメジャー版4.0は開発版（4.0.0.dev8、2026年10月16日）の段階で、安定版の公開日は決まっていません。3.10.2の候補版（3.10.2rc1）も10月26日に出ています。

### 3.9.0：pandasが必須の依存から外れた変更

[公式のリリースノート](https://docs.bokeh.org/en/latest/docs/releases.html)には、3.9.0の変更として「Removed pandas as a required dependency」とあります。3.7.0でデータフレームを扱うNarwhalsが依存に加わり、`ColumnDataSource` はpandasのほかPolarsやPyArrowのデータフレームも受け取れます。3.10.1を新しい仮想環境に入れても、入るのはnumpy・narwhals・tornado・jinja2・pillow・pyyaml・packaging・xyzservicesで、pandasは入りません。サンプルデータも別パッケージになり、`from bokeh.sampledata.iris import flowers` は `RuntimeError: The separate bokeh_sampledata is needed in order to use the sampledata module.` で止まります。チュートリアルを試すときは `pip install bokeh_sampledata` を先に実行してください。

### 3.10系：ASGI対応・Playwright出力・3.10.1の修正

3.10.0では、FastAPIやStarletteなどのASGIアプリでBokehアプリを動かす `BokehASGI`、PNG・SVG出力の手段としてのPlaywright、`pip install "bokeh[all]"` などの追加依存グループ（`extra`・`export`・`sampledata`・`all`）が加わりました。3.10.1は不具合修正のみの版で、WebGL描画の精度、`diverging_palette()` が要求した数の色を返さない問題、Playwrightでの画像出力のスレッド処理などが直っています。

## インストールと版の確認

仮想環境を作ってからpipで入れ、`bokeh info` で実際に入った版とPython・Tornadoの版を確認します。

```
python3 -m venv .venv
source .venv/bin/activate
pip install bokeh
bokeh info
```

Anacondaなら `conda install bokeh`、[uv](/tech/details/4384/)なら `uv add bokeh` でも入ります。DataFrameを使うなら `pip install pandas` を別に実行してください。

### Python 3.10・3.11で3.9.2が入る理由

3.10系の `requires-python` は `>=3.12` です。Python 3.11の仮想環境で `pip install bokeh` を実行すると、エラーは出ずに要件を満たす最新の3.9.2が入ります。`BokehASGI` など3.10の機能を使うコードが `ModuleNotFoundError` で動かないときは、まず `python --version` を確認します。

### Panel・HoloViewsと一緒に入れるときの版の組み合わせ

Bokehの上に作られたダッシュボード用ライブラリPanelは、[2026年10月23日公開の1.10.0](https://pypi.org/project/panel/)で依存を `bokeh<3.11.0,>=3.10.0` に変え、Python 3.12以上を必須にしました。1つ前の1.9.4は `bokeh<3.10.0` に固定していたため、Pythonの版によって入る組み合わせが分かれます（uvの依存解決で確認）。

| Python    | pip install panel | 入るBokeh |
| --------- | ----------------- | ------- |
| 3.12以上    | Panel 1.10.0      | 3.10.1  |
| 3.10・3.11 | Panel 1.9.4       | 3.9.2   |

Panelを依存に持つHoloViews 1.23.2も、Python 3.12以上ではPanel 1.10.0とBokeh 3.10.1の組み合わせになります。PanelはBokehのマイナー版ごとに上限を付けるので、Bokeh 3.11が出てもPanelが対応するまでは3.10系に留まります。`requirements.txt` でBokehだけ先に上げると依存解決に失敗するので、Panelの版と合わせて上げてください。

## 最初のグラフ：figure・output\_file・save

`figure()` で描画領域を作り、`line()` や `scatter()` でグラフを重ね、`save()` でHTMLに書き出します。`show()` を使うと、保存したうえで既定のブラウザで開きます。

```
from bokeh.plotting import figure, output_file, save

x = [1, 2, 3, 4, 5]
y = [6, 7, 2, 4, 5]

p = figure(title="最初のグラフ", x_axis_label="x", y_axis_label="y",
           width=600, height=350)
p.line(x, y, legend_label="売上", line_width=2)
p.scatter(x, y, size=8)

output_file("first.html")
save(p)
```

Python 3.12・Bokeh 3.10.1の環境で生成した first.html は7,669バイトでした。グラフ本体のJavaScriptは含まれず、`https://cdn.bokeh.org/bokeh/release/bokeh-3.10.1.min.js` を読みに行きます。インターネットに出られない環境ではグラフが表示されないので、`from bokeh.resources import INLINE` を読み込んで `save(p, resources=INLINE)` とします。JavaScriptを埋め込む分、このグラフでは約1.4MBに増えました。Jupyter Notebookでは、先頭で `from bokeh.io import output_notebook` と `output_notebook()` を実行しておくと、`show(p)` の結果がセルの下に表示されます。

## ColumnDataSourceとホバーツールによるデータ共有

`ColumnDataSource` は、列名をキーにしたデータの入れ物です。複数のグラフに同じ `source` を渡すと、片方で点を選択したとき、もう片方の同じ行も選択状態になります。ホバー時に出す値も `@列名` で同じ入れ物から引けます。

```
from bokeh.layouts import row
from bokeh.models import ColumnDataSource, HoverTool
from bokeh.plotting import figure, output_file, save

source = ColumnDataSource(data=dict(
    month=["1月", "2月", "3月", "4月"],
    sales=[120, 150, 90, 180],
    visits=[3000, 3600, 2500, 4100],
))

p1 = figure(x_range=source.data["month"], title="売上", width=350, height=300,
            tools="tap,reset")
p1.vbar(x="month", top="sales", width=0.8, source=source)
p1.add_tools(HoverTool(tooltips=[("月", "@month"), ("売上", "@sales")]))

p2 = figure(title="訪問数と売上", width=350, height=300, tools="tap,reset")
p2.scatter(x="visits", y="sales", size=12, source=source)

output_file("cds.html")
save(row(p1, p2))
```

[Pandas](/tech/details/3123/)やPolarsのDataFrameを `ColumnDataSource(df)` に渡すと、インデックスに相当する列が追加されます。通常は `index` ですが、pandasの名前付きインデックスではその名前になり、既存の列名によっては `level_0` になります。1つの `ColumnDataSource` を別々に保存する2つのグラフで使い回すと、2回目の `save()` が `RuntimeError: Models must be owned by only a single document` で止まります。別ファイルに出すグラフにはデータソースを作り直して渡してください。

## 2軸グラフの作り方：extra\_y\_rangesとLinearAxis

気温と降水量のように単位の違う2系列を1枚に重ねるときは、右側に2本目のY軸を足します。手順は3つです。(1) `extra_y_ranges` に名前付きの範囲を登録する、(2) 同じ名前を `y_range_name` に指定した `LinearAxis` を右に置く、(3) 右軸で描く系列にも同じ `y_range_name` を指定する。[公式ユーザーガイドのTwin axesの節](https://docs.bokeh.org/en/latest/docs/user%5Fguide/basic/axes.html)も同じ構成で、X軸側は `extra_x_ranges` と `x_range_name` で同じように増やせます。

### 棒グラフと折れ線を右軸・左軸に分ける基本形

```
from bokeh.models import ColumnDataSource, HoverTool, LinearAxis, Range1d
from bokeh.plotting import figure, output_file, save

src = ColumnDataSource(data=dict(
    month=[1, 2, 3, 4, 5, 6],
    temp=[5.2, 6.1, 9.4, 14.3, 18.8, 21.9],
    rain=[52, 56, 118, 125, 138, 168],
))

p = figure(title="気温と降水量", width=600, height=350,
           y_range=Range1d(0, 30), y_axis_label="気温(℃)")
p.extra_y_ranges = {"rain": Range1d(0, 240)}
p.add_layout(LinearAxis(y_range_name="rain", axis_label="降水量(mm)"), "right")

bars = p.vbar(x="month", top="rain", width=0.6, alpha=0.4, source=src,
              y_range_name="rain", legend_label="降水量")
p.line(x="month", y="temp", source=src, line_width=2, color="firebrick",
       legend_label="気温")

# 棒に重ねたときに気温と降水量を1つのツールチップで出す
p.add_tools(HoverTool(renderers=[bars], mode="vline",
                      tooltips=[("月", "@month"), ("気温", "@temp{0.0} ℃"),
                                ("降水量", "@rain mm")]))

output_file("twin.html")
save(p)
```

(3) を忘れると、棒グラフが左軸（0〜30）の目盛りで描かれて上に突き抜けます。`HoverTool` に `renderers` を指定しないと、棒と折れ線の両方が反応して同じ内容のツールチップが2つ縦に並びました。上のように片方の系列（ここでは棒）にだけ付け、同じ `ColumnDataSource` から両方の列を引くと1つにまとまります。`{0.0}` は表示書式で、付けないと気温が「14.300」のように小数3桁で出ます。3本目の軸が要るときは、`extra_y_ranges` に別の名前を足して `LinearAxis` をもう1本 `"right"` に置けば、外側に並びます。

### DataRange1dのrenderersによる系列別の範囲自動調整

範囲を `Range1d` で固定せず自動に任せたい場合、既定の `DataRange1d` には落とし穴があります。`DataRange1d` は特に指定しないと図の中のすべての系列を対象に範囲を決めるため、左軸も右軸の系列（降水量）まで含んでしまいます。上の気温と降水量で両軸とも既定のままにすると、3.10.1では左右とも -8.4〜176.4 になり、気温の折れ線が底に張り付きました（ヘッドレスのChromiumで描画後の範囲を取得して確認）。

対策は、[DataRange1dの renderers 属性](https://docs.bokeh.org/en/latest/docs/reference/models/ranges.html)で、範囲を決める対象の系列を軸ごとに指定することです。

```
from bokeh.models import ColumnDataSource, DataRange1d, LinearAxis
from bokeh.plotting import figure

# 基本形で保存したデータソースを別の文書に流用しない
src = ColumnDataSource(data=dict(src.data))

p = figure(width=600, height=350)
temp_line = p.line(x="month", y="temp", source=src, line_width=2)
rain_bars = p.vbar(x="month", top="rain", width=0.6, source=src,
                   y_range_name="rain")

p.y_range.renderers = [temp_line]   # 左軸は気温だけを見る
p.y_range.start = 0
p.extra_y_ranges = {"rain": DataRange1d(renderers=[rain_bars], start=0)}
p.add_layout(LinearAxis(y_range_name="rain"), "right")
```

この指定で、左軸は0〜22.99、右軸は0〜176.4になりました。`start=0` を付けたのは、棒グラフの下端が0のとき、自動の余白で範囲が -8.4 まで下がるのを防ぐためです。

### 左右の目盛りとグリッド線の位置合わせ

`figure()` が作るグリッド線は左軸と下軸の2本分だけで、右軸の目盛りはグリッド線と無関係に決まります。そのため右軸の目盛りが横線とずれて見えます。Bokehの開発者も[公式フォーラムで](https://discourse.bokeh.org/t/when-adding-secondary-vertical-axes-the-range-of-primary-axes-is-too-wide/9380)、左右の「きりのよい」目盛りが一致する保証は無く、自動では揃えられないと説明しています。

揃えるには、両軸の範囲を同じ比率で固定し、目盛りの間隔も同じ比率にします。左軸0〜30を5刻み、右軸0〜240を40刻みにすると、どちらも7本の目盛りになり、右軸の目盛りが左軸のグリッド線にそろいます。

```
from bokeh.models import Range1d, SingleIntervalTicker

# 直前の自動調整例から続けても左右を同じ比率にする
p.y_range = Range1d(0, 30)
p.extra_y_ranges["rain"] = Range1d(0, 240)

# 左 0〜30 を5刻み、右 0〜240 を40刻み（どちらも6等分）
p.yaxis[0].ticker = SingleIntervalTicker(interval=5)
p.yaxis[1].ticker = SingleIntervalTicker(interval=40)
```

`p.yaxis` は左軸・右軸の順に並びます。この方法は拡大・移動すると比率が崩れるので、目盛りの一致が要る資料用のグラフでは、ツールバーから `pan` と `wheel_zoom` を外しておくのが確実です。

### extra\_y\_scalesによる右軸の対数目盛り設定

アクセス数と売上のように桁が大きく違う系列は、右軸だけを対数にできます。`extra_y_scales` に範囲と同じ名前で `LogScale` を登録し、軸を `LogAxis` にします。範囲の下限に0は使えません。

```
from bokeh.models import ColumnDataSource, LogAxis, LogScale, Range1d
from bokeh.plotting import figure

# 基本形のsrcからデータをコピーして別のグラフを作る
p = figure(width=600, height=350, y_range=Range1d(0, 30))
p.line(x="month", y="temp",
       source=ColumnDataSource(data=dict(src.data)), line_width=2)

p.extra_y_ranges = {"log": Range1d(10, 10000)}
p.extra_y_scales = {"log": LogScale()}
p.add_layout(LogAxis(y_range_name="log"), "right")
p.scatter([1, 2, 3, 4, 5, 6], [20, 150, 900, 1200, 4000, 8000],
          size=8, y_range_name="log")
```

3.10.1で描画すると、右軸は10から10の4乗までの対数目盛りになり、左軸の線形目盛り（0〜30）はそのまま残りました。

### E-1020によるグラフ非表示の原因と対処

`extra_y_ranges` への登録を忘れたまま `y_range_name="rain"` を指定すると、`save()` の時点で次のエラーがログに出ます。

```
E-1020 (BAD_EXTRA_RANGE_NAME): An extra range name is configured with a name that does not correspond to any range: y_range_name='rain' [LinearAxis(...)], y_range_name='rain' [GlyphRenderer(...)]
```

HTMLファイルは書き出されますが、ブラウザで開くと「missing ‘rain’ range」のエラーで止まり、グラフが表示されません。名前の綴り違いでも同じことが起きるので、範囲名は変数に入れて使い回すと防げます。

### 2段の項目軸（FactorRange）は2軸グラフと別物

「年」と「上期・下期」のように横軸の項目を2段に並べるグラフは、Y軸を2本持つグラフとは仕組みが違います。こちらは `FactorRange` に項目の組を渡して作ります。

```
from bokeh.models import FactorRange

factors = [("2025", "上期"), ("2025", "下期"), ("2026", "上期"), ("2026", "下期")]
p = figure(x_range=FactorRange(*factors), width=600, height=300)
p.vbar(x=factors, top=[10, 12, 15, 18], width=0.8)
```

## ウィジェットで操作するグラフ：CustomJSとBokehサーバーの選び分け

スライダーなどでグラフを動かす方法は2つあり、値を変えたときにPythonの処理が要るかどうかで選びます。配列の掛け算程度ならCustomJSでHTML1枚に収め、データベースへの問い合わせや機械学習モデルの推論が要るならBokehサーバーを使います。

### CustomJS：サーバー不要でHTML1枚に収める方法

`js_on_change` にJavaScriptのコードを渡すと、ブラウザ内だけでグラフが更新されます。サーバーが要らないので、HTMLファイルをメールや社内Wikiで配れます。

```
import numpy as np
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, CustomJS, Slider
from bokeh.plotting import figure, output_file, save

x = np.linspace(0, 10, 200)
source = ColumnDataSource(data=dict(x=x, y=np.sin(x)))

p = figure(width=600, height=300, y_range=(-3, 3))
p.line("x", "y", source=source, line_width=2)

slider = Slider(start=0.1, end=3, value=1, step=0.1, title="振幅")
slider.js_on_change("value", CustomJS(args=dict(source=source), code="""
    const a = cb_obj.value
    const x = source.data.x
    source.data = {x: x, y: x.map(v => a * Math.sin(v))}
"""))

output_file("slider.html")
save(column(slider, p))
```

### bokeh serve：Pythonのコールバックとリアルタイム更新

Bokehサーバー向けのアプリは、`curdoc()`（現在の文書）にグラフを登録するスクリプトとして書きます。`on_change` に渡したPython関数が操作ごとにサーバー側で実行され、`add_periodic_callback` で一定間隔の更新も組めます。

```
import numpy as np
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, Slider
from bokeh.plotting import curdoc, figure

x = np.linspace(0, 10, 200)
source = ColumnDataSource(data=dict(x=x, y=np.sin(x)))

p = figure(width=600, height=300, y_range=(-3, 3))
p.line("x", "y", source=source, line_width=2)
freq = Slider(start=0.1, end=5, value=1, step=0.1, title="周波数")

def update(attr, old, new):
    source.data = dict(x=x, y=np.sin(new * x))

freq.on_change("value", update)

def tick():
    # 1秒ごとに波形をずらす（リアルタイム更新の例）
    y = np.roll(source.data["y"], -2)
    source.data = dict(x=x, y=y)

curdoc().add_periodic_callback(tick, 1000)
curdoc().add_root(column(freq, p))
curdoc().title = "Bokehサーバーの例"
```

app.py として保存し、`bokeh serve --show app.py` で起動すると、`http://localhost:5006/app` がブラウザで開きます。ブラウザとサーバーはWebSocketでつながり、スクリプトは接続（セッション）ごとに実行されます。利用者ごとに別の状態を持てる反面、同時接続が増えるとその分サーバーのメモリを使います。

## Webアプリへの組み込み：components・server\_document・FastAPI

既存のWebアプリにBokehのグラフを載せる方法は、サーバーが要るかどうかで分かれます。

| 方法                   | Bokehサーバー  | 向いている場面                      |
| -------------------- | ---------- | ---------------------------- |
| `components()`       | 不要         | 静的なグラフをテンプレートに埋め込む           |
| `server_document()`  | 別に起動       | bokeh serveのアプリを既存ページに載せる    |
| `BokehASGI`（3.10.0〜） | ASGIアプリに同居 | FastAPI・Starletteと同じプロセスで動かす |

`components(p)` は、グラフを描く `<script>` と表示先の `<div>` を文字列で返します。使用中のBokehと同じ版のBokehJSを先に読み込み、返されたscriptとdivをFlaskやDjangoのテンプレートに差し込みます。

### BokehASGIによるFastAPIへのマウント

`BokehASGI` を使うと、先ほどのapp.pyを[FastAPI](/tech/details/3859/)のアプリに `/bokeh` としてマウントできます。`pip install fastapi "uvicorn[standard]"` を実行し、app.pyと同じディレクトリに、[公式ドキュメントの例](https://docs.bokeh.org/en/latest/docs/user%5Fguide/server/library.html)に沿った次のmain.pyを保存します。

```
from contextlib import asynccontextmanager
from pathlib import Path

from fastapi import FastAPI

from bokeh.server.asgi import BokehASGI

bokeh_app = BokehASGI({"/": Path("app.py")})

@asynccontextmanager
async def lifespan(site):
    # マウントしたアプリにはlifespanイベントが届かないため、ここで起動・停止する
    await bokeh_app.core.start()
    try:
        yield
    finally:
        await bokeh_app.core.stop()

site = FastAPI(lifespan=lifespan)
site.mount("/bokeh", bokeh_app)
```

`uvicorn main:site` で起動すると `/bokeh/` でページは返りますが、`pip install uvicorn` だけの環境ではグラフが表示されません。ログに「No supported WebSocket library detected」と出て、WebSocketの接続（`/bokeh/ws`）が404になります。`"uvicorn[standard]"` でWebSocketのライブラリを入れると接続が通ります（3.10.0で確認）。リバースプロキシの内側に置く場合は、`Upgrade`・`Connection` に加えてBokehの接続に使う `Sec-WebSocket-Protocol` も転送し、公開側の `Host` とブラウザの `Origin` を保持します。

### export\_pngによるPNG出力とブラウザの準備

`export_png()`・`export_svg()` はブラウザを使って画像を書き出します。SVGはベクター形式で出すため、対象グラフに `p.output_backend = "svg"` を設定します。Seleniumを使う場合は `selenium` パッケージとChromeDriver（Firefoxならgeckodriver）が、Playwrightを使う場合は `pip install playwright` と `playwright install chromium` が必要です。3.10.0からは `backend="playwright"` を指定できます。既定の `"auto"` はSeleniumを先に試し、使えなければPlaywrightを試します。

## Plotly・Matplotlib・Streamlitとの使い分け

「python グラフ ライブラリ」で比べられることの多いライブラリを、出力と得意な用途で並べます。

| ライブラリ                            | 出力          | 得意な用途                |
| -------------------------------- | ----------- | -------------------- |
| Bokeh                            | HTML・JS     | グラフ間の連動、サーバー連携の細かい制御 |
| [Plotly](/tech/details/3140/)    | HTML・JS     | 少ないコードで多種類のグラフ       |
| Matplotlib                       | PNG・PDF・SVG | 論文・資料用の静止画           |
| [Seaborn](/tech/details/3136/)   | PNG・PDF・SVG | 統計グラフを短いコードで         |
| [Streamlit](/tech/details/3427/) | Webアプリ      | 画面全体をPythonで組む       |
| PyQtGraph                        | Qtウィンドウ     | デスクトップでの高頻度描画        |

迷いやすいのは、どちらもブラウザで操作できるグラフを出すBokehとPlotlyです。1枚のグラフを早く出したいならPlotlyを選びます。`plotly.express` なら散布図も箱ひげ図も1行で描けます（使い方は[Plotlyの色指定と使い方の記事](/tech/details/7512/)）。Bokehを選ぶのは、`ColumnDataSource` で複数のグラフとウィジェットを連動させたい場合と、Pythonのコールバックを持つアプリを既存のFastAPIに同居させたい場合です。Plotlyでアプリまで作るなら[Dash](/tech/details/1342/)という別フレームワークを使います。社内の指標を部門をまたいで見せるダッシュボードで、Pythonのコードを書く人を置けないなら、[BIツール導入支援](/service/ai/bi/)のようにBIツールで組む選択肢もあります。

大量の点で描画が重くなったときは、`figure(output_backend="webgl")` でWebGL描画に切り替えます。WebGLで速くなるかはブラウザとGPUの対応に左右されるため、実際に表示して操作の応答を確かめてください。WebGLでも操作が重い場合は、点を画像に集計してから渡すDatashaderと組み合わせます。

## よくある質問

### Bokehとは何の意味ですか？写真用語のボケと同じですか？

綴りは写真用語の「bokeh（ボケ）」と同じですが、この記事で扱うのはPythonのデータ可視化ライブラリです。検索結果に写真の解説が多く出るときは「bokeh python」で検索すると、ライブラリの情報に絞れます。

### Bokehで2軸グラフを作ると左軸の範囲が広がりすぎるのはなぜですか？

既定の `DataRange1d` が右軸の系列も含めて範囲を決めるためです。左軸を `Range1d` で固定するか、`p.y_range.renderers` に左軸の系列だけを指定します。

### pip install bokehを実行したのにpandasが使えないのはなぜですか？

3.9.0からpandasが必須の依存ではなくなったためです。DataFrameを使うなら `pip install pandas` を別に実行します。サンプルデータを使うなら `pip install bokeh_sampledata` も必要です。

### BokehとPlotlyはどちらを選べばよいですか？

単体のグラフを少ないコードで出すならPlotly、複数のグラフとウィジェットの連動や、FastAPIに同居させるアプリを作るならBokehが向いています。どちらもブラウザで拡大・ホバーができる点は同じです。

### Jupyter NotebookでBokehのグラフを表示するには？

`from bokeh.io import output_notebook` を読み込んで、先頭で `output_notebook()` を1回実行します。以降は `show(p)` でセルの下にグラフが表示されます。

## 関連記事

- [Matplotlibとは？使い方・インストール・日本語化を3.11対応で解説](/tech/details/3125/)
- [Plotlyとは？読み方・できること・インストールと使い方【7.1対応】](/tech/details/3140/)
- [Seabornとは？pipインストールと使い方・Matplotlibとの違い【0.13.2対応】](/tech/details/3136/)
- [Streamlitとは？読み方・デメリットとできないこと、使い方とライセンス](/tech/details/3427/)
- [Pandasとは？できること・使い方とPandas 3.1の変更点【2026年10月】](/tech/details/3123/)

---

出典: [Bokehとは？Pythonでの使い方と2軸グラフの作り方【3.10.1対応】](<https://www.issoh.co.jp/tech/details/3371/>)（株式会社一創）
