user:
Streamlit アプリで出力した図や表を、全てダウンロードするような機能をつけることはできますか?
assistant:
はい、Streamlitアプリで出力した図や表をすべてまとめてダウンロードできる機能を実装することは可能です。以下のような方法があります:
✅ 方法1:ZIPファイルで一括ダウンロード(推奨)
図(PNG, SVG, etc)や表(CSV, Excelなど)を一時的に保存し、それらをZIPファイルにまとめて st.download_button
で提供する方法です。
📦 実装イメージ(概要):
import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
import io
import zipfile
# 表の作成
df = pd.DataFrame({
'A': [1, 2, 3],
'B': [4, 5, 6]
})
# グラフの作成
fig, ax = plt.subplots()
ax.plot(df['A'], df['B'])
img_buffer = io.BytesIO()
fig.savefig(img_buffer, format='png')
img_buffer.seek(0)
# CSV出力
csv_buffer = io.StringIO()
df.to_csv(csv_buffer, index=False)
csv_buffer.seek(0)
# ZIPにまとめる
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w') as zf:
zf.writestr("table.csv", csv_buffer.getvalue())
zf.writestr("plot.png", img_buffer.read())
zip_buffer.seek(0)
# ダウンロードボタン
st.download_button(
label="📦 結果を一括ダウンロード(ZIP)",
data=zip_buffer,
file_name="results.zip",
mime="application/zip"
)
✅ 方法2:個別に st.download_button
を使う(シンプル)
各表・図に対して個別にダウンロードボタンを表示することもできます。ただし、出力が多くなるとUIが煩雑になる可能性があります。
✅ 方法3:Excelの複数シート or PDFにまとめる(応用)
- 複数の表を Excelのシートに分けて保存:
pandas.ExcelWriter
を活用。 - 図や表を PDFファイルにまとめる:
reportlab
やmatplotlib.backends.backend_pdf.PdfPages
などで対応。
📝 補足
- 一時ファイルではなく
BytesIO
などのメモリバッファを使うと、サーバー上のファイル管理が不要です。 -
matplotlib
,plotly
,altair
いずれの図もBytesIO
に保存可能です(形式に応じて変更)。
ご希望に応じて、Streamlitアプリ用の具体的なコードテンプレートもお作りします。どの形式の図や表を出力しているか教えていただければ、最適なコードを提案します。
user:
ありがとうございます。Plotlyで作画した図はどう保存できますか?
assistant:
どういたしまして!
Plotlyで作成した図を保存する方法について説明します。Streamlit上で扱うには、通常 plotly.graph_objects.Figure
を BytesIO
に一時的に保存してから、st.download_button
で提供する形になります。
✅ Plotly図をPNGとして保存する方法
🔧 事前準備(必要に応じて)
Plotlyの画像保存には kaleido
が必要です。
pip install kaleido
🧪 例:Plotly図をPNGとしてStreamlitからダウンロード
import streamlit as st
import plotly.graph_objects as go
import io
# プロット作成
fig = go.Figure()
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 1, 6], mode='lines+markers'))
# 図をBytesIOに保存(PNG形式)
img_bytes = fig.to_image(format="png", width=800, height=600, engine="kaleido")
img_buffer = io.BytesIO(img_bytes)
# ダウンロードボタン
st.download_button(
label="📷 Plotly図をダウンロード(PNG)",
data=img_buffer,
file_name="plotly_figure.png",
mime="image/png"
)
# Streamlitに図も表示
st.plotly_chart(fig)
✅ 他の保存形式も対応可能
形式 |
format= の値 |
MIMEタイプ |
---|---|---|
PNG | "png" |
"image/png" |
JPEG | "jpeg" |
"image/jpeg" |
SVG | "svg" |
"image/svg+xml" |
"pdf" |
"application/pdf" |
# 例: SVGで保存
img_bytes = fig.to_image(format="svg")
✅ ZIPに複数Plotly図を保存する(図が複数ある場合)
import zipfile
# 複数のPlotly図を保存してZIP化
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w') as zf:
for i in range(3):
fig = go.Figure()
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[i, i+1, i+2]))
img_bytes = fig.to_image(format="png")
zf.writestr(f"plot_{i+1}.png", img_bytes)
zip_buffer.seek(0)
st.download_button("📦 すべての図をZIPでダウンロード", zip_buffer, "all_plots.zip", "application/zip")
📝 注意点
-
fig.to_image()
を使うにはkaleido
がインストールされている必要があります。 - Streamlit Cloudでも
requirements.txt
にkaleido
を記載しておくことで動作します。
必要であれば、表と図をまとめてZIPで出力するサンプルコードも作成しますので、お気軽にどうぞ!
user:
plotly の図を保存するときに、width=800, height=600のように指定せず、もともと設定してあるwidthとheightを使うことはできますか?
assistant:
はい、もともとPlotly図 (fig
) に設定されている width
や height
を使って to_image()
で保存することは可能です。
ただし注意点があります:
✅ 結論:fig.to_image()
は明示的に width
/ height
を指定しないと、既存の layout からは自動で引き継がれません。
Plotlyの fig.layout.width
や fig.layout.height
に値が入っていても、to_image()
はデフォルトでそれを参照しません。
そのため、必要であれば以下のように自分で取得して渡す必要があります。
✅ 実装例:もともと設定された width
/ height
を使う
import plotly.graph_objects as go
import streamlit as st
import io
# width と height を layout に設定しておく
fig = go.Figure()
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 5, 6]))
fig.update_layout(width=700, height=500)
# layout から width / height を取得
width = fig.layout.width or 800 # デフォルト fallback も設定
height = fig.layout.height or 600
# 画像保存
img_bytes = fig.to_image(format="png", width=width, height=height, engine="kaleido")
img_buffer = io.BytesIO(img_bytes)
# Streamlitに表示
st.plotly_chart(fig)
st.download_button("ダウンロード(layoutサイズ)", data=img_buffer, file_name="figure.png", mime="image/png")
✅ ポイント
-
fig.layout.width
やfig.layout.height
はNone
の可能性があるので、or
でデフォルト値を入れておくと安全です。 -
to_image()
を呼ぶときにwidth
やheight
を指定しなかった場合、Plotlyは 独自のデフォルト(700x450) を使用します。
必要であれば、複数図をこの方法で一括ダウンロードする例も用意します!
user:
ありがとうございます。csvについて、次のような関数を自作しましたが、この要領で、plotlyのfigureを入れたらファイル名とバッファーの組を返す関数を書いてください。
def get_buffer_for_csv(file_name, df):
csv_buffer = io.StringIO()
df.to_csv(csv_buffer, index=False)
csv_buffer.seek(0)
return [file_name, csv_buffer.seek)
assistant:
どういたしまして!
ご提示の get_buffer_for_csv
関数と同じ形式で、PlotlyのFigureを画像ファイルとして保存し、ファイル名とバッファーの組を返す関数を以下に示します。
✅ 目的に沿った関数(Plotly用)
import io
def get_buffer_for_plotly(file_name, fig, format="png"):
"""
Plotly Figureを指定形式でバッファに保存し、[ファイル名, バッファ] を返す。
Parameters:
file_name (str): 保存するファイル名(例: 'plot.png')
fig (go.Figure): PlotlyのFigureオブジェクト
format (str): 保存形式('png', 'svg', 'jpeg', 'pdf' など)
Returns:
list: [file_name, io.BytesIOオブジェクト]
"""
img_bytes = fig.to_image(format=format)
img_buffer = io.BytesIO(img_bytes)
img_buffer.seek(0)
return [file_name, img_buffer]
✅ CSV版も修正すると良い点があります
csv_buffer.seek
ではなく csv_buffer
を返す必要があります。以下のように修正するのが正しいです:
import io
def get_buffer_for_csv(file_name, df):
csv_buffer = io.StringIO()
df.to_csv(csv_buffer, index=False)
csv_buffer.seek(0)
return [file_name, csv_buffer]
✅ 使用例(ZIPにまとめるなど)
import zipfile
buffers = []
# CSV
buffers.append(get_buffer_for_csv("data.csv", df))
# Plotly
buffers.append(get_buffer_for_plotly("plot.png", fig))
# ZIPにまとめる
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w') as zf:
for file_name, buffer in buffers:
zf.writestr(file_name, buffer.getvalue() if isinstance(buffer, io.StringIO) else buffer.read())
zip_buffer.seek(0)
ご希望に応じて、この関数群を使って「表+図をまとめてダウンロードするStreamlitアプリ」のテンプレートもご用意できます。必要ですか?
user:
ありがとうございます。get_buffer_for_plotly を、もともと設定された width / height を使う仕様に変更してください。
assistant:
こちらこそありがとうございます!
ご要望に応じて、Plotlyの layout.width
/ layout.height
を自動的に取得して to_image()
に渡すようにした関数に書き換えます。
✅ 修正版:get_buffer_for_plotly
(layoutサイズを使用)
import io
def get_buffer_for_plotly(file_name, fig, format="png"):
"""
Plotly Figureを指定形式でバッファに保存し、[ファイル名, バッファ] を返す。
layout に設定された width / height を使う(未設定ならデフォルト使用)。
Parameters:
file_name (str): 保存するファイル名(例: 'plot.png')
fig (go.Figure): PlotlyのFigureオブジェクト
format (str): 保存形式('png', 'svg', 'jpeg', 'pdf' など)
Returns:
list: [file_name, io.BytesIOオブジェクト]
"""
# layout から width / height を取得(未設定時は None)
width = fig.layout.width
height = fig.layout.height
# キーワード引数を組み立てる(Noneは渡さない)
kwargs = {"format": format}
if width:
kwargs["width"] = width
if height:
kwargs["height"] = height
# 画像化してバッファに保存
img_bytes = fig.to_image(**kwargs)
img_buffer = io.BytesIO(img_bytes)
img_buffer.seek(0)
return [file_name, img_buffer]
✅ この関数の特徴
-
layout.width
/layout.height
に値があればそれを使い、なければto_image()
のデフォルト(700×450)が使用されます。 -
None
を明示的に渡すとエラーになるため、辞書で条件付きにしています。
✅ 使用例(前と同じ)
buffers.append(get_buffer_for_plotly("figure1.png", fig))
ご希望であれば、CSV・Plotly図を複数まとめてZIPでダウンロードするStreamlitテンプレートもご提供します。必要であればお申し付けください。
user:
CSV・Plotly図を複数まとめてZIPでダウンロードするStreamlitテンプレートを、お願いします。
assistant:
了解しました!
以下は、複数のCSVとPlotly図を一括でZIPファイルとしてダウンロードできるStreamlitアプリのテンプレートです。
✅ Streamlitテンプレートコード(CSV + Plotly図 → ZIP)
import streamlit as st
import pandas as pd
import plotly.graph_objects as go
import io
import zipfile
# --- ヘルパー関数定義 ---
def get_buffer_for_csv(file_name, df):
csv_buffer = io.StringIO()
df.to_csv(csv_buffer, index=False)
csv_buffer.seek(0)
return [file_name, csv_buffer]
def get_buffer_for_plotly(file_name, fig, format="png"):
width = fig.layout.width
height = fig.layout.height
kwargs = {"format": format}
if width: kwargs["width"] = width
if height: kwargs["height"] = height
img_bytes = fig.to_image(**kwargs)
img_buffer = io.BytesIO(img_bytes)
img_buffer.seek(0)
return [file_name, img_buffer]
# --- サンプルデータ作成 ---
df1 = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
df2 = pd.DataFrame({"X": [10, 20, 30], "Y": [7, 8, 9]})
fig1 = go.Figure()
fig1.add_trace(go.Scatter(x=df1["A"], y=df1["B"]))
fig1.update_layout(title="Figure 1", width=600, height=400)
fig2 = go.Figure()
fig2.add_trace(go.Bar(x=df2["X"], y=df2["Y"]))
fig2.update_layout(title="Figure 2", width=500, height=300)
# --- 表示 ---
st.write("### 表とグラフの表示")
st.dataframe(df1)
st.plotly_chart(fig1)
st.dataframe(df2)
st.plotly_chart(fig2)
# --- バッファをリストにまとめる ---
buffers = []
buffers.append(get_buffer_for_csv("data1.csv", df1))
buffers.append(get_buffer_for_csv("data2.csv", df2))
buffers.append(get_buffer_for_plotly("figure1.png", fig1))
buffers.append(get_buffer_for_plotly("figure2.png", fig2))
# --- ZIPにまとめる ---
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, "w") as zf:
for file_name, buffer in buffers:
if isinstance(buffer, io.StringIO):
zf.writestr(file_name, buffer.getvalue())
else: # BytesIO
zf.writestr(file_name, buffer.read())
zip_buffer.seek(0)
# --- ダウンロードボタン ---
st.download_button(
label="📦 結果をZIPで一括ダウンロード",
data=zip_buffer,
file_name="all_results.zip",
mime="application/zip"
)
✅ 特徴
- CSVは
StringIO
、画像はBytesIO
で処理。 -
layout.width
/height
に従ってPlotly図を保存。 - ファイル名は
"data1.csv"
,"figure1.png"
など。 -
to_image()
を使っているため、事前にkaleido
のインストールが必要です:
pip install kaleido
✅ 拡張アイデア(必要であればご支援可能です)
- Excelの複数シートでまとめる
- PDFに図をレイアウトして出力
- ファイル名をユーザーが入力可能にする
ご希望があれば、これを使ったブログ記事テンプレートも作成できます!
user:
ありがとうございます。networkx で描いたネットワーク図についても、現在このようにしてダウンロードボタンを設置しているのですが、上記のZIPファイルに含めることはできますか?
with tempfile.NamedTemporaryFile(delete=False, suffix=".html") as tmp_file:
path = tmp_file.name
net.save_graph(path)
with open(path, "r", encoding="utf-8") as f:
html_content = f.read()
st.components.v1.html(html_content, height=700, scrolling=True)
with open(path, "rb") as f:
btn = st.download_button(
label="ダウンロード",
data=f,
file_name=file_name,
mime="text/html",
key=f"{file_name}_{random.random()}",
)
assistant:
はい、networkx + pyvis で生成した .html
ファイルをZIPに含めることは可能です。
現在の処理で .html
ファイルを一時保存して表示・ダウンロードしていますが、同じ内容を read()
して zipfile
に追加すればOKです。
✅ 修正方針
以下を行えば ZIP に含められます:
-
.html
ファイルを保存してhtml_content = f.read()
とする - その文字列(または
BytesIO
)を ZIP のwritestr()
で書き込む
✅ 拡張用コード(上記テンプレートに追加)
from pyvis.network import Network
import networkx as nx
import tempfile
import random
# --- pyvis ネットワーク作成 ---
G = nx.erdos_renyi_graph(n=10, p=0.3, seed=42)
net = Network(notebook=False)
net.from_nx(G)
# --- 一時ファイルに保存(この部分は表示用と共通) ---
with tempfile.NamedTemporaryFile(delete=False, suffix=".html") as tmp_file:
path = tmp_file.name
net.save_graph(path)
# --- 表示(そのまま) ---
with open(path, "r", encoding="utf-8") as f:
html_content = f.read()
st.components.v1.html(html_content, height=700, scrolling=True)
# --- ダウンロードボタン(そのまま) ---
with open(path, "rb") as f:
st.download_button(
label="ネットワーク図をHTMLでダウンロード",
data=f,
file_name="network.html",
mime="text/html",
key=f"network_{random.random()}"
)
# --- ZIPに追加用:バッファとして格納 ---
with open(path, "r", encoding="utf-8") as f:
network_html_str = f.read()
buffers.append(["network.html", io.StringIO(network_html_str)])
✅ 最終的にZIP化部分(そのまま使えます)
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, "w") as zf:
for file_name, buffer in buffers:
if isinstance(buffer, io.StringIO):
zf.writestr(file_name, buffer.getvalue())
else: # BytesIO
zf.writestr(file_name, buffer.read())
zip_buffer.seek(0)
st.download_button(
label="📦 全出力をZIPで一括ダウンロード",
data=zip_buffer,
file_name="all_results.zip",
mime="application/zip"
)
✅ ポイントまとめ
-
pyvis
のsave_graph()
は.html
を生成するので、それをread()
してStringIO
に入れればZIPに含められる。 -
StringIO
は.writestr()
に渡すことでZIPに文字ファイルとして書き込み可能。 - 一時ファイルで保存 → 表示・個別DL → ZIPに含める、という流れは問題なし。
必要であれば、「CSV+図+ネットワーク図を含むZIP出力アプリ」の完成版もひとつの関数でまとめます。お気軽にどうぞ。
user:
ありがとうございます。get_buffer_for_csv や get_buffer_for_plotly のような関数にしてください。
assistant:
承知しました!
以下に、get_buffer_for_pyvis_html
関数として、get_buffer_for_csv
や get_buffer_for_plotly
と同様の形式で定義したものを示します。
✅ get_buffer_for_pyvis_html
関数
import io
import tempfile
def get_buffer_for_pyvis_html(file_name, net):
"""
Pyvisネットワーク(Networkオブジェクト)をHTMLとして保存し、
[ファイル名, io.StringIOオブジェクト] の形式で返す。
Parameters:
file_name (str): 保存するHTMLファイル名(例: 'network.html')
net (pyvis.network.Network): PyvisのNetworkオブジェクト
Returns:
list: [file_name, io.StringIO]
"""
# 一時ファイルにHTMLとして保存
with tempfile.NamedTemporaryFile(delete=False, suffix=".html") as tmp_file:
path = tmp_file.name
net.save_graph(path)
# HTML内容を読み込み、StringIO に格納
with open(path, "r", encoding="utf-8") as f:
html_content = f.read()
html_buffer = io.StringIO(html_content)
html_buffer.seek(0)
return [file_name, html_buffer]
✅ 他の関数と統一形式で使えます
buffers = []
buffers.append(get_buffer_for_csv("data.csv", df))
buffers.append(get_buffer_for_plotly("plot.png", fig))
buffers.append(get_buffer_for_pyvis_html("network.html", net))
✅ ZIP出力にもそのまま使えます
with zipfile.ZipFile(zip_buffer, "w") as zf:
for file_name, buffer in buffers:
if isinstance(buffer, io.StringIO):
zf.writestr(file_name, buffer.getvalue())
else: # io.BytesIO
zf.writestr(file_name, buffer.read())
必要であれば、networkx.Graph
→ pyvis.Network
への変換や、レイアウト指定の方法もご案内できます。お気軽にお知らせください!
user:
ありがとうございます!今回の会話をブログにまとめたいので、タイトル案をいくつか提案してください。
assistant:
こちらこそありがとうございます!
今回の内容は StreamlitアプリでCSV・Plotly図・Pyvisネットワーク図をZIP一括ダウンロード できる便利機能に関する実践的な話でしたね。以下にタイトル案をいくつか提案します:
🔷 実用系タイトル案
- 「Streamlitで図表まとめてZIP出力:CSV・Plotly・ネットワーク図を一括保存」
- 「CSVも図もZIPで一括!Streamlitで出力ファイルをスマートに管理する方法」
- 「Streamlit × ZIP出力:CSV・グラフ・ネットワーク図をまるごと保存するアプリ構築術」
- 「Plotlyやpyvisの図もOK!Streamlitで複数ファイルをZIP一括ダウンロード」
- 「Streamlitで複数データを一気にダウンロード:CSV・PNG・HTML対応テンプレート」
🔶 開発者目線・技術寄りタイトル案
- 「get_buffer関数でスマート出力:Streamlit × ZIPファイル構築のベストプラクティス」
- 「Streamlit × ファイル保存の設計パターン:図・表・HTMLをまとめて出力する」
- 「Pyvis × Plotly × Pandas をZIP化!Streamlitアプリでデータ出力を最適化」
- 「Streamlitの出力設計:図表をZIPにまとめるための関数設計と実装例」
- 「Streamlitで実現する出力自動化:CSVもグラフもネットワークもワンクリック保存」
ご希望があれば、記事の導入文や構成案もご提案できます。
お気軽にどうぞ!
user:
「Streamlitで図表まとめてZIP出力:CSV・Plotly・ネットワーク図を一括保存」でいきたいと思います。最後に、まとめを書いてください。
assistant:
素晴らしいタイトルのご選定ありがとうございます!
以下に、記事の締めくくりとしてふさわしい「まとめ」をご提案します:
✅ まとめ
本記事では、Streamlitアプリで複数の出力ファイル(CSV・Plotly図・ネットワーク図)をZIP形式で一括ダウンロードできる機能を実装する方法を紹介しました。
具体的には以下のポイントを解説しました:
-
get_buffer_for_csv
やget_buffer_for_plotly
のような再利用可能な関数設計 -
pyvis
を使ったNetworkXグラフのHTML出力とZIPへの組み込み方法 -
StringIO
/BytesIO
を使った柔軟なファイル操作 -
zipfile
モジュールでの一括アーカイブとst.download_button
による出力
このように、Streamlitアプリにダウンロード機能を整理して実装することで、ユーザーは可視化やデータ分析の結果を簡単に持ち帰ることが可能になります。教育用アプリやデータレポートツール、プロトタイピング用途にも非常に有用です。
今後は、以下のような拡張も視野に入れるとさらに実用性が高まります:
- 複数CSVを1つのExcelファイルにまとめて保存
- 図表をPDFレポート化
- ファイル選択・命名のインターフェース追加
Streamlitでの出力管理を一歩進めたい方は、ぜひ本記事を参考にカスタマイズしてみてください!
ご希望があれば、冒頭文や導入ストーリーの草案も作成できますので、お気軽にお申し付けください。
user:
ありがとうございました。今日はこれで大丈夫です。今後もよろしくお願いします!
assistant:
こちらこそ、ありがとうございました!
Streamlitやデータ可視化の話題、とても楽しくご一緒できました。
またいつでもご相談ください。今後ともどうぞよろしくお願いいたします!😊