Streamlitで図表まとめてZIP出力：CSV・Plotly・ネットワーク図を一括保存

# **user**:

Streamlit アプリで出力した図や表を、全てダウンロードするような機能をつけることはできますか？

# **assistant**:

はい、**Streamlitアプリで出力した図や表をすべてまとめてダウンロードできる機能**を実装することは可能です。以下のような方法があります：

---

## ✅ 方法1：ZIPファイルで一括ダウンロード（推奨）

図（PNG, SVG, etc）や表（CSV, Excelなど）を一時的に保存し、それらをZIPファイルにまとめて `st.download_button` で提供する方法です。

### 📦 実装イメージ（概要）：

```python
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`](https://github.com/plotly/Kaleido) が必要です。

```bash
pip install kaleido
```

---

### 🧪 例：Plotly図をPNGとしてStreamlitからダウンロード

```python
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  | `"pdf"`      | `"application/pdf"` |

```python
# 例: SVGで保存
img_bytes = fig.to_image(format="svg")
```

---

## ✅ ZIPに複数Plotly図を保存する（図が複数ある場合）

```python
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` を使う

```python
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を入れたらファイル名とバッファーの組を返す関数を書いてください。

```python
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用）

```python
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` を返す必要があります。以下のように修正するのが正しいです：

```python
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にまとめるなど）

```python
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サイズを使用）

```python
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` を明示的に渡すとエラーになるため、辞書で条件付きにしています。

---

## ✅ 使用例（前と同じ）

```python
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）

```python
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` のインストールが必要**です：

```bash
pip install kaleido
```

---

## ✅ 拡張アイデア（必要であればご支援可能です）

* Excelの複数シートでまとめる
* PDFに図をレイアウトして出力
* ファイル名をユーザーが入力可能にする

---

ご希望があれば、これを使ったブログ記事テンプレートも作成できます！

# **user**:

ありがとうございます。networkx で描いたネットワーク図についても、現在このようにしてダウンロードボタンを設置しているのですが、上記のZIPファイルに含めることはできますか？    

```python
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 に含められます：

1. `.html` ファイルを保存して `html_content = f.read()` とする
2. その文字列（または `BytesIO`）を ZIP の `writestr()` で書き込む

---

## ✅ 拡張用コード（上記テンプレートに追加）

```python
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化部分（そのまま使えます）

```python
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` 関数

```python
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]
```

---

## ✅ 他の関数と統一形式で使えます

```python
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出力にもそのまま使えます

```python
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一括ダウンロード** できる便利機能に関する実践的な話でしたね。以下にタイトル案をいくつか提案します：

---

### 🔷 実用系タイトル案

1. **「Streamlitで図表まとめてZIP出力：CSV・Plotly・ネットワーク図を一括保存」**
2. **「CSVも図もZIPで一括！Streamlitで出力ファイルをスマートに管理する方法」**
3. **「Streamlit × ZIP出力：CSV・グラフ・ネットワーク図をまるごと保存するアプリ構築術」**
4. **「Plotlyやpyvisの図もOK！Streamlitで複数ファイルをZIP一括ダウンロード」**
5. **「Streamlitで複数データを一気にダウンロード：CSV・PNG・HTML対応テンプレート」**

---

### 🔶 開発者目線・技術寄りタイトル案

6. **「get\_buffer関数でスマート出力：Streamlit × ZIPファイル構築のベストプラクティス」**
7. **「Streamlit × ファイル保存の設計パターン：図・表・HTMLをまとめて出力する」**
8. **「Pyvis × Plotly × Pandas をZIP化！Streamlitアプリでデータ出力を最適化」**
9. **「Streamlitの出力設計：図表をZIPにまとめるための関数設計と実装例」**
10. **「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やデータ可視化の話題、とても楽しくご一緒できました。
またいつでもご相談ください。今後ともどうぞよろしくお願いいたします！😊