Plotlyで色を指定する方法は、何に色を割り当てるかで決まります。カテゴリなら color_discrete_sequence か color_discrete_map、数値なら color_continuous_scale、何も指定しないときの既定色はテンプレートの colorway です。この記事では2026年10月時点の最新版 plotly 7.1.0 を前提に、色の指定方法とパレット一覧、v7で解釈が変わった色の書き方、画像出力とReact連携までをコード付きで整理します。v7の色の変化は、同じ色文字列を 6.9.0 と 7.1.0 で描画して比べた結果を載せています。
まとめ:Plotlyの色指定と使い方の要点
- 現行版:plotly 7.1.0(2026年9月15日)。同梱の plotly.js は 4.1.1。7.0.0(2026年8月25日)がメジャー更新でした。
- 色は3層:カテゴリ=
color_discrete_sequence/color_discrete_map、数値=color_continuous_scale、既定色=テンプレートのcolorway。 - v7で色の書き方が変わった:
rgba(0.5,0.2,0.1,1)のような0〜1の小数はほぼ黒になり、hsv()はエラーになります。代わりにoklch()、空白区切りのrgb(255 0 0)、8桁の16進が使えます。 - 透明は
rgba(0,0,0,0):"transparent"は 6.9.0 でも 7.1.0 でも Python 側でエラーになります。 - 画像出力:Kaleido v1 は Chrome を同梱しないので
plotly_get_chromeで取得します。EPS は非対応で、7.0 からはengine引数も渡せません。
Plotly 7系のインストールと依存関係
インストールは pip で完結します。本体の実行時依存は narwhals>=1.15.1 と packaging だけで、用途に応じて extras を足します(PyPI の plotly の依存定義による)。
# 本体(依存は narwhals と packaging)
pip install plotly
# Plotly Express を使う(numpy が入る)
pip install "plotly[express]"
# PNG・SVG・PDF を書き出す(kaleido>=1.3.0 が入る)
pip install "plotly[kaleido]"
# この記事のサンプルは px.data の組み込みデータを使うので pandas も入れる
pip install pandas
pandas は必須依存ではありません。Plotly Express は v6 から Narwhals 経由でデータフレームを扱うため、Polars や PyArrow のデータもそのまま渡せます。ただし px.data.gapminder() などの組み込みデータは pandas のデータフレームを返すので、サンプルを動かすときだけ pandas が要ります。対応Pythonは 3.8 以上、Jupyter Notebook は 7.0.0 以上です(Notebook 6 以下のサポートは 6.0.0 で終了)。Plotly そのものの位置づけや他ライブラリとの違いから知りたい場合は、Plotlyとは?Pythonでインタラクティブなグラフを作る基本と使い方を先に読むと全体像がつかめます。
Plotlyの色指定:カテゴリ・数値・既定色
色指定は、関数名よりも色を割り当てる対象で整理すると、使う引数を選びやすくなります。国名や製品名のようなカテゴリには離散色、売上や人口のような数値には連続カラースケール、指定しないときの既定色はテンプレートの colorway が決めます。
カテゴリの色分け(color_discrete_sequence / color_discrete_map)
Plotly Express の color にカテゴリ列を渡すと自動で色分けされます。色を自分で決めるなら、順番に当てるのが color_discrete_sequence、値ごとに固定するのが color_discrete_map です。ブランドカラーを特定のカテゴリに必ず当てたい場合は後者を使います。
import plotly.express as px
df = px.data.gapminder().query("year == 2007")
fig = px.scatter(
df, x="gdpPercap", y="lifeExp", color="continent",
color_discrete_sequence=px.colors.qualitative.G10, # 順番に割り当てる
color_discrete_map={"Asia": "#EF553B", "Europe": "#636EFA"}, # 値ごとに固定
category_orders={"continent": ["Asia", "Europe", "Africa", "Americas", "Oceania"]},
)
fig.show()
両方を指定すると color_discrete_map が優先されます。注意点は、マップで固定したカテゴリもシーケンスの順番を消費することです。上の例を plotly 7.1.0 で描くと、Africa に G10 の3色目(#FF9900)、Americas に4色目(#109618)が当たり、G10 の先頭2色(#3366CC・#DC3912)はどこにも使われませんでした。公式の Discrete Colors ページにはこの併用時の規則が書かれていないので、配色を厳密に決めたいなら全カテゴリを color_discrete_map に書き切ってください。凡例の並び順は色とは別に category_orders で決まります。データ列に "red" や "#FF0000" のような色そのものが入っている場合は、color_discrete_map="identity" でその値を色として使えます。
数値に応じた色と範囲の指定(color_continuous_scale と range_color)
数値列を color に渡すと連続カラースケールに切り替わり、カラーバーが付きます。スケールは color_continuous_scale、色を張る範囲は range_color、基準値で色を分けたいときは color_continuous_midpoint で決めます。
import plotly.express as px
df = px.data.gapminder().query("year == 2007")
fig = px.bar(
df.head(5), x="country", y="pop", color="pop",
color_continuous_scale="Viridis", # px.colors.sequential.Viridis でも可
range_color=[0, 50_000_000], # 色を張る数値範囲を固定
)
fig.update_layout(coloraxis_colorbar_title="人口")
fig.show()
range_color を固定すると、複数のグラフを並べたときに同じ数値が同じ色になります。指定しないとグラフごとにスケールが伸び縮みし、色の意味がグラフ間でずれます。範囲はデータの実レンジに合わせてください。上の5か国の人口は最大で約4,030万人なので、範囲を10億に取ると全ての棒がスケール下端の同じような色に潰れます。カラースケールの色を値で取り出したいときは px.colors.sample_colorscale("Viridis", [0, 0.5, 1]) が使え、7.1.0 では rgb(68, 1, 84)・rgb(34, 144, 140)・rgb(253, 231, 37) が返りました。
正負の条件による棒の色分け(プラスは緑・マイナスは赤)
Plotlyフォーラムには、折れ線グラフの塗りを正の値では緑、負の値では赤にしたいという質問があります。ここでは、まず棒グラフの色分けを示します。Graph Objects なら marker_color に棒の数と同じ長さの色リストを渡すのが最短です。凡例で「黒字」「赤字」を出したいなら、区分の列を作って Plotly Express の color_discrete_map に渡します。
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
df = pd.DataFrame({"月": ["1月", "2月", "3月", "4月", "5月", "6月"],
"損益": [120, -40, 75, -15, 60, 90]})
# 方法1:色のリストを直接渡す(1トレースで、既定では凡例を表示しない)
colors = ["#2CA02C" if v >= 0 else "#D62728" for v in df["損益"]]
fig1 = go.Figure(go.Bar(x=df["月"], y=df["損益"], marker_color=colors))
# 方法2:区分の列を作って色を割り当てる(凡例が2つ出る)
df["区分"] = ["黒字" if v >= 0 else "赤字" for v in df["損益"]]
fig2 = px.bar(df, x="月", y="損益", color="区分",
color_discrete_map={"黒字": "#2CA02C", "赤字": "#D62728"},
category_orders={"月": df["月"].tolist()})
fig2.show()
折れ線や面グラフで正負を塗り分ける場合は、1本の線の途中で色は変えられないため、プラス側とマイナス側の2系列に分けて fill="tozeroy" と fillcolor をそれぞれ指定します。
既存グラフの色変更(update_traces・marker_color・line_color)
作った Figure の色だけ差し替えるなら、作り直さずに update_traces() で上書きします。selector で対象の系列を絞れます。棒グラフは marker_color、線は line_color と、トレースの種類で属性が違う点に注意してください。棒グラフに line_color を渡すと、7.1.0 では「Invalid property specified for object of type plotly.graph_objs.Bar: ‘line’」という ValueError になります。棒の縁取りは marker_line_color です。
import plotly.express as px
fig = px.line(px.data.stocks(), x="date", y=["GOOG", "AAPL"])
fig.update_traces(line_color="#D62728", selector=dict(name="AAPL")) # AAPL の線だけ赤に
bar = px.bar(x=["A", "B", "C"], y=[3, 5, 2])
bar.update_traces(marker_color="#0F62FE",
marker_line_color="#000000", marker_line_width=1) # 塗りと縁取り
bar.show()
背景・グリッド・テンプレートの色(plot_bgcolor・gridcolor・colorway)
グラフの外側の背景は paper_bgcolor、描画領域の背景は plot_bgcolor、目盛り線は軸ごとの gridcolor です。白背景が欲しいだけなら、テンプレートを plotly_white にするのが手早い方法です。7.1.0 に入っているテンプレートは ggplot2・seaborn・simple_white・plotly(既定)・plotly_white・plotly_dark・presentation・xgridoff・ygridoff・gridon・none の11種でした。
import plotly.express as px
import plotly.io as pio
fig = px.line(px.data.stocks(), x="date", y="GOOG", template="plotly_white")
fig.update_layout(paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="#FAFAFA") # 外側は透明
fig.update_xaxes(gridcolor="#E5E5E5")
fig.update_yaxes(gridcolor="#E5E5E5", zerolinecolor="#999999")
# 自社の配色をテンプレートに登録し、全グラフの既定色にする
pio.templates["corp"] = dict(layout=dict(colorway=["#0F62FE", "#FF832B", "#24A148", "#8A3FFC"]))
pio.templates.default = "plotly_white+corp" # + で既存テンプレートと合成できる
グラフごとに色を指定して回ると、レポート全体で配色がばらつきます。既定色は layout.colorway が持っているので、自社の配色を1回テンプレートにして既定に設定すれば、以後に作るpxやgoの図でも、個別の色や別テンプレートを指定しない限り、この既定色順が使われます。
Plotlyの色一覧:パレット・カラースケール・CSS色名
主要パレットとカラースケールの色数
ここでは標準の配色から、カテゴリ用(qualitative)・順次型(sequential)・発散型(diverging)を紹介します。このほか、角度や時刻などに使う循環型(cyclical)もあります。色数は plotly 7.1.0 の px.colors から取得した値です。
| グループ | 名前 | 色数 | 向いている場面 |
|---|---|---|---|
| qualitative | Plotly(既定) | 10 | 一般的なカテゴリ |
| qualitative | D3 / G10 / T10 | 各10 | 他ツールと配色を揃える |
| qualitative | Set1 / Set2 / Set3 | 9 / 8 / 12 | ColorBrewer系 |
| qualitative | Pastel / Bold / Vivid / Safe | 各11 | Safeは色覚多様性に配慮 |
| qualitative | Dark24 / Light24 / Alphabet | 24 / 24 / 26 | カテゴリが多いとき |
| sequential | Viridis / Plasma / Cividis | 各10 | 大小を読ませる |
| sequential | Blues / Turbo | 9 / 15 | 単色の濃淡・広い色域 |
| diverging | RdBu / Picnic | 各11 | 基準値からの正負 |
カテゴリ数がパレットの色数を超えると色が循環して重複します。10系列を超える場合はDark24やAlphabetが候補ですが、色だけで判別しにくい場合はラベル・記号・グラフの分割も併用してください。名前で指定できる連続カラースケールは px.colors.named_colorscales() で一覧でき、7.1.0 では94個の名前が返りました。末尾に _r を付けると色の向きが反転します(例:"Viridis_r")。
色見本の出し方と既定の10色
一覧を目で確かめるなら、swatches() で色見本を描かせます。連続カラースケールは名前の文字列で指定できます。カテゴリ用パレットは px.colors.qualitative.G10 のように色のリストで渡します(color_discrete_sequence="G10" と文字列で書くと 7.1.0 では ValueError)。
import plotly.express as px
px.colors.qualitative.swatches().show() # カテゴリ用パレット
px.colors.sequential.swatches().show() # 連続カラースケール
px.colors.diverging.swatches().show() # 発散型カラースケール
print(px.colors.qualitative.Plotly)
# ['#636EFA', '#EF553B', '#00CC96', '#AB63FA', '#FFA15A',
# '#19D3F3', '#FF6692', '#B6E880', '#FF97FF', '#FECB52']
Plotly の見慣れた青・赤・緑の正体がこの10色で、既定テンプレート plotly の colorway も同じ順です。Matplotlib の既定色(tab10)とは別物なので、両方のグラフを並べる資料では色を揃える必要があります。
148種類のCSS色名と透明色の指定
色は16進やRGBのほか、"tomato" や "rebeccapurple" のようなCSSの色名でも指定できます。plotly 7.1.0 の色検証が受け付ける名前は148個で、一覧は CSS Color Module Level 4 の named colors と同じです。ひとつ例外があり、"transparent" は 6.9.0 でも 7.1.0 でも ValueError になります。背景を透明にしたいときは "rgba(0,0,0,0)" と書いてください。
Plotly 7で変わった色の書き方(6.9.0と描画比較)
2026年8月の plotly.js 4.0.0 で、色の解釈を担うライブラリが TinyColor から culori に替わりました(plotly.js CHANGELOG 4.0.0)。plotly.py は 7.0.0 からこの版を同梱しており、Python側の色検証もCSSの新しい書式を受け付けるように変わっています。描画結果を数値で比較するため、同じ色文字列で棒を1本描き、白背景で塗られた色を 6.9.0 と 7.1.0 で比べました(kaleido 1.5.0 でPNGに書き出して画素を読んだ値)。
| 色の文字列 | 6.9.0 | 7.1.0 |
|---|---|---|
| rgba(0.5,0.2,0.1,1) | 茶色(128,51,26) | ほぼ黒(1,0,0) |
| rgb(255, 0, 0, 0.5) | 不透明な赤 | 半透明な赤(255,127,127) |
| hsl(120, 50% 50%) | Pythonでエラー | エラーなし・既定の青で描画 |
| hsv(0,100%,100%) | 赤で描画 | Pythonでエラー |
| oklch(0.7 0.15 30) | Pythonでエラー | 描画(237,118,101) |
| rgb(255 0 0) | Pythonでエラー | 赤で描画 |
| #ff000080 | Pythonでエラー | 半透明な赤 |
| transparent | Pythonでエラー | Pythonでエラー |
危ないのは表の上から3行です。1行目と2行目はエラーにならずに描画色や透明度が変わり、3行目は 6.9.0 ではPython側でエラーだったものが、7.1.0 では受理されたうえで描画時に既定色へ置き換わります。どれも 7.1.0 では警告が出ません。
- 0〜1の小数で書いたRGB:6.9.0 までは255倍して解釈されていましたが、7.1.0 では数値どおり0.5/255として扱われ、ほぼ黒になります。Matplotlib の感覚で
rgba(0.5,0.2,0.1,1)と書いたコードが該当します。0〜255の整数に直してください。 - 4つ目の値を持つ
rgb():CSS Color 4 ではrgbとrgbaが同じ扱いなので、4つ目の値が透明度として効きます。 - 不正なCSS:カンマと空白が混ざった
hsl(120, 50% 50%)は、Python 側を通過したうえで plotly.js が拒否し、既定の1色目(#636EFA)で描かれました。
逆に、7系では oklch()・lab()・hwb()・color(display-p3 1 0 0) のようなCSSの新しい書式がそのまま使えます。デザインシステムの色を oklch で管理しているなら、変換せずに渡せるようになりました。ほかに、棒の上の文字色はWCAGのコントラスト比で選ばれるようになり、中間の明るさの塗りでは文字が黒から白に変わることがあります。Python の補助関数 plotly.colors.hex_to_rgb も、7.0.0 で "#FFF" のような3桁表記に対応し、7.1.0 では長さが不正な16進でエラー、8桁は末尾2桁(アルファ)を捨てて警告を出すようになりました。
Plotly Express と Graph Objects の使い分け
2つのAPIは対立するものではありません。px.scatter() などが返すのも plotly.graph_objects.Figure なので、px で作った図にそのまま Graph Objects のトレースを足せます。
import plotly.express as px
import plotly.graph_objects as go
df = px.data.gapminder().query("year == 2007")
fig = px.scatter(df, x="gdpPercap", y="lifeExp", color="continent")
fig.add_trace(go.Scatter(x=[40000], y=[82], mode="markers+text",
text=["目標水準"], marker_color="black", name="目標"))
fig.update_layout(title="1人あたりGDPと平均寿命")
fig.show()
データフレームの列を軸・色・大きさに割り当てるだけで描ける図は Plotly Express、二軸や種類の違うグラフの重ね合わせのように1関数で表せない図は Graph Objects で組み立てます。迷ったら px で骨格を作り、add_trace() と update_layout() で足りない部分を補ってください。統計的な分布を手早く描くだけなら PythonのSeabornとは?使い方・グラフの種類・インストールを解説【2026年最新】のほうが記述は短く済みます。Plotly を選ぶのは、ホバーやズームなどブラウザ上の操作が要るときです。
レイアウト・サイズ・日本語表示の設定(update_layout)
タイトル、軸ラベル、フォント、余白、サイズは update_layout() にまとまっています。軸は update_xaxes() / update_yaxes() でも設定できます。
import plotly.express as px
fig = px.bar(x=["4月", "5月", "6月"], y=[1200000, 1350000, 980000])
fig.update_layout(
title="月次売上の推移",
font=dict(family="Noto Sans JP, sans-serif", size=14),
margin=dict(l=40, r=20, t=60, b=40),
legend=dict(orientation="h", y=1.05, x=0),
)
fig.update_yaxes(tickformat=",", ticksuffix=" 円", rangemode="tozero")
# サイズを固定したいときだけ width と height を指定する
fig.update_layout(width=900, height=450)
width と height を指定しなければ、write_html() の出力は親要素の幅いっぱいに広がります(default_width の既定値が "100%")。固定サイズの画像やレポートに使う図だけ width / height を指定してください。なお npm の plotly.js 4.1.2 では、レスポンシブ表示の再描画がウィンドウのリサイズではなく要素の大きさの変化(ResizeObserver)で行われるようになりました。
日本語の表示に使うフォントは、グラフを表示するブラウザ(静的画像なら書き出しに使う Chrome)がその端末のフォントから選びます。日本語の字形を持つフォントが端末にあれば、特別な設定なしで表示されます。font.family は、候補のフォントを並べて見た目を揃えるための指定です。文字が豆腐(□)になるのは、閲覧側でも書き出し側でも、日本語フォントが見つからないときです。サーバーやCIコンテナで画像を書き出す場合は、Chrome と一緒に日本語フォント(Noto Sans CJK など)を入れてください。Python 側で日本語フォントを指定する必要がある Matplotlibとは?使い方・インストール・日本語化を3.11対応で解説とは、設定する場所が違います。
PNG・SVG・PDF・HTMLへの出力(Kaleido v1・plotly_get_chrome)
静的画像の書き出しには Kaleido 1.0.0 以上が必要で、7.0.0 で Kaleido 1.0 未満と Orca のサポートは削除されました。Kaleido v1 は Chrome を同梱せず、マシンにある Chrome / Chromium を探して使います。ブラウザが入っていないサーバーやCIコンテナでは、先に取得してください(公式の Static Image Export)。
pip install "plotly[kaleido]"
plotly_get_chrome # Chrome を取得(Python からは plotly.io.get_chrome())
Chrome 以外の Chromium 系ブラウザを使うなら、環境変数 BROWSER_PATH でパスを指定します。形式は拡張子で決まります。
fig.write_image("chart.png") # PNG(.jpeg と .webp も可)
fig.write_image("chart.svg") # SVG
fig.write_image("chart.pdf") # PDF
fig.write_html("chart.html", include_plotlyjs="cdn") # 操作できるHTML
7.1.0 で確かめたつまずきどころは3つです。
- EPS:
write_image("chart.eps")は「EPS export is no longer supported by Kaleido. Please use SVG or PDF instead.」という ValueError になります。入稿用のベクターは PDF か SVG にします。 - engine 引数:v5時代の
write_image(..., engine="kaleido")は TypeError になります。引数ごと消してください。 - 点が多い散布図:Plotly Express は1,000点を超える散布図を WebGL(scattergl)で描くため、SVG・PDF でもマーカー部分が画像として埋め込まれます。全部をベクターにしたいなら
render_mode="svg"を渡します。
include_plotlyjs は出力サイズを大きく変えます。棒2本のグラフで比べると、"cdn" では7,565バイト、既定(plotly.js を丸ごと埋め込む)では4,823,229バイト(約4.8MB)でした(UTF-8 で書き出したときの値)。7.1.0 のCDN参照先は plotly-4.1.1.min.js で、出力の先頭には7.0.0から <!doctype html> が付きます。Webに置くならCDN参照、オフラインで配るなら埋め込みと使い分けてください。グラフをそのままWebアプリに載せたいなら、Streamlitとは?メリット・デメリットと他フレームワーク比較・料金を解説のように Figure を受け取れるフレームワークに渡すのが近道です。
JavaScript・Reactでの導入と色指定(plotly.js 4系 / react-plotly.js 4.1)
Plotly の本体は JavaScript の plotly.js で、Python版はそのラッパーです。npm の最新は plotly.js 4.1.2(2026年10月5日)で、パッケージの動作要件は Node.js 22 以上です。React用の react-plotly.js は 4.1.0 で、peer dependency は React 18 または 19、plotly.js 3.0.0 以上です(いずれも npm registry の値)。
npm install plotly.js react-plotly.js
import Plot from "react-plotly.js";
export default function Chart() {
return (
<Plot
data={[{ type: "bar", x: ["A", "B"], y: [3, 5],
marker: { color: ["#636EFA", "oklch(0.7 0.15 30)"] } }]}
layout={{ title: { text: "売上" }, colorway: ["#0F62FE", "#FF832B"] }}
config={{ responsive: true }}
/>
);
}
色の考え方は Python版と同じで、トレース単位なら marker.color、図全体の既定色なら layout.colorway です。前章の色書式の変更は plotly.js 4.0.0 で入ったものなので、JavaScript から直接使う場合も同じように効きます。4.0.0 ではほかに scattermapbox・choroplethmapbox・densitymapbox と mapbox サブプロットが削除され(scattermap などの MapLibre 系に移行)、Chart Studio 由来の showLink・sendData などの config も消えました。Python だけで操作できるダッシュボードまで作るなら、Plotly の開発元が提供する Python Dashとは?4.4対応の使い方・コールバック・デプロイを動くコードで解説のフレームワークが選択肢になります。
v6からv7へ移行するときのチェックリスト
古いコードや古い記事の手順を持ち込んだときに、エラーや見た目の変化が起きる箇所です(plotly.py CHANGELOG 7.0.0rc0〜7.1.0 と実行結果による)。
| 古い書き方 | 7系での扱い |
|---|---|
| ff.create_gantt など | 削除。px.timeline などへ |
| px.scatter_mapbox / go.Scattermapbox | 削除。scatter_map など |
| write_image(engine=…) | TypeError。引数を消す |
| Orca・Kaleido 0.x | 削除。Kaleido 1.0以上 |
| EPS で書き出し | 非対応。PDF か SVG |
| rgba(0.5,0.2,0.1,1) | ほぼ黒になる。0〜255で書く |
| hsv(…) | エラー。hsl か16進へ |
| MathJax v2 | 非対応。v3 / v4 |
削除された Figure Factory は create_gantt・create_distplot・create_annotated_heatmap・create_candlestick など12関数で、create_table や create_dendrogram は 7.1.0 でも残っています。影響が見えにくいのは色の変更です。エラーにならないため、移行後はグラフを目で見比べてください。地図は Mapbox 系の削除に加えて、geo と map の初期表示がデータに合わせて自動で寄るようになりました(fitbounds の既定が 'locations')。データの絞り込みを前処理で済ませる書き方に不慣れなら、ilocの使い方|pandasで行・列を抽出、locとの違いも解説で押さえておくと移行作業が楽になります。社内のダッシュボードを Plotly で作るか、BIツールで作るかで迷っている場合は、BIツール導入支援の観点で要件を整理するのも手です。
よくある質問
Plotlyの色を指定しても反映されないのはなぜですか?
色の種類とデータ型が食い違っていることが多いです。カテゴリ列に color_continuous_scale を指定しても効かず、数値列に color_discrete_sequence を渡しても無視されます。数値を区分として色分けしたいなら、列を文字列型に変えてから color に渡してください。棒グラフの色を line_color で変えようとしている(棒は marker_color)、v7で rgba(0.5,0.2,0.1,1) のような小数表記を使っている、という原因もあります。
Plotlyの既定の色(デフォルトカラー)は何色ですか?
既定テンプレート plotly の colorway は10色で、#636EFA・#EF553B・#00CC96・#AB63FA・#FFA15A・#19D3F3・#FF6692・#B6E880・#FF97FF・#FECB52 の順に使われます。px.colors.qualitative.Plotly で取り出せます。11系列目からは1色目に戻ります。
Plotlyで背景を透明にするには?
fig.update_layout(paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)") と書きます。"transparent" は Python 側の色検証で ValueError になります。PNGで透明背景を残したいときも、この指定のまま write_image() で書き出せます。
PlotlyでPythonのグラフを画像として保存できないときは?
Kaleido v1 は Chrome を同梱しないため、ブラウザが無い環境では fig.write_image() が失敗します。pip install "plotly[kaleido]" のあとに plotly_get_chrome を実行してください。engine= を渡している古いコードは 7.0.0 から TypeError になります。書き出した画像で日本語が豆腐になるなら、Chrome の実行環境に日本語フォントが入っていません。
PlotlyとMatplotlibはどう使い分けますか?
ホバー表示・ズーム・凡例クリックによる系列の出し分けなど、読者が操作する図なら Plotly です。論文やPDFに貼る静的な図や、細かい描画制御が要る図は Matplotlib が扱いやすく、資料も豊富です。Plotly も write_image() で静止画にできるので、まず Plotly で作って必要なときに画像にする進め方もできます。どちらも商用利用でき、plotly.py と plotly.js は MIT ライセンスです。