Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 88 additions & 31 deletions AGENTS.md

Large diffs are not rendered by default.

32 changes: 27 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ https://mioupa.github.io/matplotlib_gui/ を開いてください。`index.html`
1. 「入力ファイル」で `.csv` / `.txt` / `.xlsx` を選びます。
2. 読み込みと描画は自動で行われます(「描画する」ボタンはありません)。設定を変えると、すぐ再描画されます。
3. 「描画系列」でX列・Y列・色などを選び、必要なら系列を追加します。
4. 「保存設定」でファイル名と形式を選び、「保存する」で書き出します。
4. 「体裁」でフォント・図のサイズ・プリセットなどを整えます。
5. 「保存」でファイル名・形式・DPI を選び、「保存する」で書き出すか、「クリップボードにコピー」で他のアプリに貼り付けます。

左の設定パネルは「データ読み込み」「系列」「軸・目盛」「体裁」「保存」の区分に分かれていて、見出しを押すと折りたためます(折りたたみの状態は、このブラウザに覚えておきます)。

対応する文字コードは UTF-8、UTF-8(BOM付き)、Shift_JIS(CP932)、EUC-JP です。判定した文字コードは、読み込み後に画面へ表示されます。

Expand All @@ -18,7 +21,14 @@ https://mioupa.github.io/matplotlib_gui/ を開いてください。`index.html`
- 系列カード(複数系列、X/Y列、色、線幅、線種、点サイズ、凡例名)と、第2Y軸
- プロット種別: line / scatter / bar
- 軸の対数表示と範囲指定、余白指定、主目盛線・副目盛線、凡例位置
- 保存: png / jpg / svg / pdf、背景透過
- 保存: png / jpg / svg / pdf、背景透過。PNG・JPG は保存 DPI(72 / 150 / 300 / 600 または任意の値。既定は 300)を指定できます。プレビューは 100 dpi で表示します
- PDF は文字をフォントとして埋め込みます(Type 42)。Illustrator などで文字を編集できます。SVG は、文字を「パスに変換」(既定。どの環境でも同じ見た目)か「テキストのまま」(編集できる。開く環境に同じフォントが無いと見た目が変わります)から選べます
- 図のサイズ: inch / cm / mm で指定(単位を切り替えると、図の大きさを変えずに数値を換算します)
- スタイルプリセット(標準 / 論文1段組 / 論文2段組 / スライド16:9): 「適用」で、図のサイズ・フォントサイズ・線幅・点サイズをまとめて設定します
- カラーパレット(UD カラー / tab10 / グレースケール): 切り替えると、すべての系列の色を、系列の順に新しいパレットの色へ塗り直します
- 欧文フォント: 標準(Noto Sans JP)、Arimo(Arial 互換)、Tinos(Times 互換)。英数字と記号だけを選んだフォントで書き、日本語は Noto Sans JP で書きます(Tinos のときは数式も Times 系の字形になります)
- クリップボードへのコピー: プレビュー中の図を、保存 DPI の PNG として画像でコピーします(背景透過は保存設定に従います)。対応ブラウザは Chrome / Edge 98 以降、Safari 13.1 以降、Firefox 127 以降で、https で開いている必要があります。非対応のときは「保存する」で PNG を保存してください
- 数式・上付き・下付き: タイトル・軸ラベル・凡例名で、`$` で囲んだ部分が数式になります。例: `m$^2$`(上付き)、`H$_2$O`(下付き)、`$\alpha$`(ギリシャ文字)。`$` を文字として書くときは `\$` とします。書き方を間違えると、日本語のエラーが表示されます
- データ確認タブ: 先頭100行を表示し、描画から除外される先頭行をグレーで表示
- Pythonコードタブ: 設定から生成した matplotlib のスクリプトを表示(下記)
- 数値に変換できず除外した値の警告、日本語のエラーメッセージ
Expand All @@ -38,7 +48,7 @@ https://mioupa.github.io/matplotlib_gui/ を開いてください。`index.html`
2. `pip install pandas matplotlib numpy` を実行します。`.xlsx` を読むときは `openpyxl` も入れます。
3. `python plot.py` で実行します(ファイル名は書き出した名前に読み替えてください)。

日本語フォントは、見つかったものを使います。無い場合も実行はできますが、日本語は豆腐(□)で表示されます。
日本語フォントは、見つかったものを使います。無い場合も実行はできますが、日本語は豆腐(□)で表示されます。欧文フォントを選んだ図は、Arimo / Tinos(または Arial / Times New Roman、Liberation)が入っていればそれを使い、無ければ日本語フォントで書きます。PDF の文字は TrueType 形式のフォントなら正しく埋め込まれます。macOS のヒラギノなど OpenType(CFF)形式のフォントでは、PDF の文字が正しく表示されないことがあります。そのときは Noto Sans JP(TrueType 版)を入れてください。

## プライバシー

Expand All @@ -47,10 +57,22 @@ https://mioupa.github.io/matplotlib_gui/ を開いてください。`index.html`
| ホスト | 取得するもの |
|---|---|
| `pyscript.net` | PyScript 本体 |
| `cdn.jsdelivr.net` | Pyodide 本体と同梱パッケージ(pandas / matplotlib など)、画面用フォント(Noto Sans JP)、グラフ用の日本語フォント(Noto Sans CJK JP) |
| `cdn.jsdelivr.net` | Pyodide 本体と同梱パッケージ(pandas / matplotlib など)、画面用フォント(Noto Sans JP)、グラフ用のフォント(日本語の Noto Sans JP、選んだときだけ Arimo / Tinos) |
| `files.pythonhosted.org` | openpyxl の wheel(`.xlsx` を初めて開くときだけ) |

初回の訪問では、Pyodide とライブラリ(数十 MB)と、日本語フォント(約 16 MB)をダウンロードするため、時間がかかります。2回目以降はキャッシュを使います(フォントはブラウザの Cache Storage に保存し、再ダウンロードしません)。
初回の訪問では、Pyodide とライブラリ(数十 MB)と、日本語フォント(約 5.7 MB)をダウンロードするため、時間がかかります。欧文フォントは、選んだときだけ取得します(各 約 0.3〜0.5 MB)。2回目以降はキャッシュを使います(フォントはブラウザの Cache Storage に保存し、再ダウンロードしません)。

## フォントとライセンス

グラフに使うフォントは、実行時に jsDelivr から取得します(このリポジトリには含めていません)。いずれも SIL Open Font License 1.1 です。

| フォント | 用途 | ライセンス全文 |
|---|---|---|
| Noto Sans JP(TrueType 版) | グラフの日本語、画面用のフォント | https://cdn.jsdelivr.net/npm/@expo-google-fonts/noto-sans-jp@0.4.4/LICENSE_FONT |
| Arimo | 欧文フォント(Arial 互換) | https://cdn.jsdelivr.net/npm/@expo-google-fonts/arimo@0.4.3/LICENSE_FONT |
| Tinos | 欧文フォント(Times 互換) | https://cdn.jsdelivr.net/npm/@expo-google-fonts/tinos@0.4.2/LICENSE_FONT |

画面用の Web フォントは、`@fontsource/noto-sans-jp@5.3.0`(Noto Sans JP、同じく SIL OFL 1.1)を jsDelivr から読み込みます。グラフの日本語に TrueType 版を使うのは、PDF に文字をフォントとして埋め込む(Type 42)には TrueType 形式が必要なためです。

## 開発

Expand Down
87 changes: 81 additions & 6 deletions css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -612,15 +612,47 @@ button.secondary:hover {
font-size: 12px;
color: var(--text-muted);
}
.section-header {
margin: 14px 0 10px;
padding-top: 10px;
.panel-section + .panel-section {
margin-top: 14px;
border-top: 1px solid var(--line);
}
.section-header:first-of-type {
margin-top: 4px;
.section-header {
display: flex;
align-items: center;
gap: 6px;
margin: 0;
padding: 10px 2px;
cursor: pointer;
list-style: none;
border-radius: 6px;
user-select: none;
}
.panel-section:first-of-type > .section-header {
padding-top: 0;
border-top: none;
}
.section-header::-webkit-details-marker {
display: none;
}
.section-header::before {
content: "";
flex: none;
width: 6px;
height: 6px;
margin: 0 3px 0 2px;
border-right: 2px solid var(--text-muted);
border-bottom: 2px solid var(--text-muted);
transform: rotate(-45deg);
transition: transform 0.12s ease;
}
.panel-section[open] > .section-header::before {
transform: rotate(45deg);
}
.section-header:hover .section-title {
color: var(--text);
}
.section-header:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.section-title {
font-size: 12px;
Expand Down Expand Up @@ -798,3 +830,46 @@ button.series-color-chip.active {
font-size: 13px;
}
}

.preset-row {
display: grid;
grid-template-columns: 1fr auto;
gap: 8px;
}
.preset-row button {
width: auto;
padding-left: 16px;
padding-right: 16px;
}
.row-size {
grid-template-columns: 1fr 1fr 5.5rem;
}
#mathtextHelp a {
color: var(--accent, inherit);
text-decoration: underline;
}

[hidden] {
display: none !important;
}
.dpi-row {
display: grid;
grid-template-columns: 1fr;
gap: 8px;
}
.dpi-row.has-custom {
grid-template-columns: 1fr 1fr;
}
.dpi-row input {
margin-top: 0;
}
.save-actions {
grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
align-items: stretch;
}
.save-actions button {
padding-left: 6px;
padding-right: 6px;
font-size: 13px;
white-space: nowrap;
}
78 changes: 78 additions & 0 deletions docs/PERFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,81 @@ Phase 1 後より悪化した項目はなかった。このため、悪化の内

編集モードの実行(438 ms)は、設定変更の描画(デバウンス除く 216 ms)より約 220 ms 長い。仮想 FS からのファイル再読込と、生成スクリプトの全体実行(読込・整形・描画)を含むためとみられるが、内訳は未計測。短縮が必要になったら、Pyodide 内で読込・コンパイル・実行・PNG 変換の時間を分けて測ることから始める。


## Phase 3 後

### 計測条件

| 項目 | 内容 |
|---|---|
| 日付 | 2026-10-08 |
| 対象 | `020896b`(Phase 3: 日本語フォントを TrueType の Noto Sans JP へ変更、保存 DPI の選択(既定 300)、欧文フォント、クリップボードへのコピー、保存設定の変更でコードだけ更新)に、Phase 3 の計測行を追加した `tests/perf/bench.py` を組み合わせたもの。PyScript 2026.7.3 / Pyodide 314.0.3 |
| マシン | Mac16,10 / Apple M4 / メモリ 24 GB |
| OS | macOS 27.0.1 |
| ブラウザ | Chromium 153.0.8010.12(Playwright、ヘッドレス) |
| ネットワーク | 実ネットワーク(ベースラインと同じ。欧文フォントの初回取得と日本語フォントのコールド取得は jsDelivr からの実ダウンロード) |
| 配信 | ローカルの静的 HTTP サーバー(127.0.0.1、サブパスなし) |
| 反復 | 各項目 N=5。既存項目の計測方法は Phase 2 後と同じ(描画系の項目は 10,000 行 × 系列 5 本) |

追加した項目(既存項目の方法は変えていない。新項目は、起動 → CSV 読込 → 系列5本設定 → 3秒待機の後に測る):

- 「保存(PNG、120 dpi)」: `#saveDpi` を「任意」、`#saveDpiCustom` を 120 にして、既存の「保存」と同じ方法(クリック → ダウンロード開始)で測る。Phase 2 の固定 120 dpi と直接比較できる。
- 「保存(PNG、300 dpi 既定)」: 既存の「保存」項目の計測条件。Phase 3 では既定が 300 dpi になったので、既存行の値は 300 dpi の値になる(Phase 2 後の値は 120 dpi)。別ページ・別コンテキストでも測り直して一致を確認した(下表の2行)。
- 「欧文フォント選択 → 最初のプロット更新」: `#latinFont` を Arimo にして(「設定変更」と同じ `CHANGE_JS`。`input` イベント発行から新しい画像の表示まで。250 ms のデバウンスを含む)。(a) 初回は反復ごとに新規コンテキスト(Cache Storage も HTTP キャッシュも空)で jsDelivr から実取得、(b) 2回目以降は Cache Storage に Arimo がある状態。
- 「クリップボードへのコピー」: `#copyPlotBtn` のクリックから `data-copy-state` が `done` になるまで(300 dpi 既定)。`clipboard-read` / `clipboard-write` の権限を付けたコンテキストで測る。
- 「日本語フォント取得(コールドキャッシュ)」: 新規コンテキストで、ナビゲーション開始から `data-font-state` が `ready` になるまで。あわせて、Python の準備完了(`data-app-state=ready`)から `ready` までの追加待ち時間も記録する。

### 結果(ミリ秒、中央値 / 最小 / 最大)

| 項目 | Phase 2 後 | Phase 3 後 | Phase 2 後比 |
|---|---:|---:|---|
| コールド起動 | 3,607 / 3,578 / 6,407 | 3,564 / 3,526 / 7,098 | 同等(-1%) |
| ウォーム起動 | 3,452 / 3,440 / 3,458 | 3,441 / 3,424 / 3,484 | 同等(-0%) |
| 初回ファイル読込 → 最初のプロット | 178 / 175 / 181 | 177 / 175 / 179 | 同等(-1%) |
| 設定変更 → プロット更新(デバウンス込み) | 466 / 460 / 477 | 471 / 468 / 473 | 同等(+1%) |
| 同上(デバウンス 250ms 除く) | 216 / 210 / 227 | 221 / 218 / 223 | 同等(+2%) |
| 保存(PNG、クリック → ダウンロード開始。既定の 300 dpi) | 246 / 211 / 253(120 dpi) | 405 / 364 / 408 | 悪い(+65%)。dpi の違い |
| 初回 .xlsx 読込 → 最初のプロット | 329 / 327 / 332 | 329 / 325 / 330 | 同等(0%) |
| 編集モードでの実行(Ctrl/Cmd+Enter → プロット更新) | 438 / 431 / 464 | 579 / 575 / 596 | 悪い(+32%)。dpi の違い |
| 保存(PNG、120 dpi)【新】 | 246 / 211 / 253 | 246 / 242 / 257 | 同等(0%) |
| 保存(PNG、300 dpi)【新。別測定】 | - | 403 / 369 / 412 | - |
| 欧文フォント選択 → プロット更新、初回(Arimo を jsDelivr から取得)【新】 | - | 468 / 456 / 472 | - |
| 同上、Cache Storage にあるとき【新】 | - | 467 / 462 / 469 | - |
| 参考: 欧文フォント初回の、選択 → `data-latin-font-state=ready`【新】 | - | 23 / 21 / 25 | - |
| クリップボードへのコピー(クリック → done、300 dpi)【新】 | - | 422 / 406 / 424 | - |
| 日本語フォント取得、コールド(ナビゲーション開始 → `font-state=ready`)【新】 | - | 3,622 / 3,606 / 3,623 | - |
| 同上、Python 準備完了からの追加待ち【新】 | - | 67 / 56 / 77 | - |

コールド起動の最大値は1回目の反復のみ(キャッシュが冷えていたため)。2回目以降は約3.6秒。

### 要件の確認

要件「設定変更後 1 秒以内の再描画(10,000 行 × 5 系列)」: 設定変更 → プロット更新は、デバウンス 250 ms を含めて中央値 471 ms(最大 473 ms)で、**満たしている**。編集モードの実行も、300 dpi の既定で中央値 579 ms(最大 596 ms)で 1 秒以内。欧文フォントの選択からの更新も 468 ms で、同様に 1 秒以内。

### 分析

悪化した項目は2つで、どちらも保存 DPI の既定が 120 → 300 になったことによる(起動・再描画・xlsx 読込は Phase 2 後と同等)。

- **保存(PNG)+65%**: 300 dpi は 120 dpi の 6.25 倍の画素数(8×6 inch で 2400×1800 = 4.3 MP に対し 960×720 = 0.7 MP)。同じ設定で 120 dpi に戻した値は 246 ms(Phase 2 後の 246 ms と同じ)なので、ランタイムや生成スクリプトの増加による劣化はない。画素数が 6.25 倍になっても時間は 1.6 倍にとどまる。固定費(スクリプトの生成と実行、系列の描画)が大きく、画素数に比例する部分(Agg のラスタライズと PNG のエンコード)は一部のため。
- **編集モードの実行 +32%**: 編集モードのスクリプトは、末尾の `fig.savefig(...)` を含めて全体を実行する。Phase 3 から生成コードの `savefig` が既定 300 dpi になったので、その分の PNG 書き出しが増えた。DPI を 120 にして同じ計測をすると 439 / 425 / 443 ms で、Phase 2 後(438 ms)と同じ(確認用に3回測った値)。

悪化していない項目の補足:

- **日本語フォントの変更(16.5 MB の OTF → 5.7 MB の TTF、転送は約 3.2 MB の brotli)**: コールドキャッシュでも、Python の準備完了までにフォント取得がほぼ終わる(ナビゲーション開始 → `ready` が 3,622 ms、準備完了からの追加待ちは 67 ms)。取得は Pyodide の起動と並行して進むため、起動の体感を延ばさない(コールド起動 3,564 ms は Phase 2 後と同等)。以前は 16.5 MB を取得していたので、初回訪問のダウンロード量は約 1/3 になった。2回目以降は Cache Storage から読む点は変わらない。
- **欧文フォントの初回選択(Arimo)**: 初回(取得あり)と2回目以降(Cache Storage から)で差がない(468 ms と 467 ms)。選択からフォントの登録完了までが約 23 ms で、250 ms のデバウンスの間に終わるため、描画は取得を待たない。フォントが約 0.3〜0.5 MB と小さいことと、デバウンスと並行して取得を始める作りのため。
- **クリップボードへのコピー**: 422 ms で、300 dpi の保存(405 ms)とほぼ同じ。PNG の生成が大半で、クリップボードへの書き込みは十数 ms 程度とみられる。
- 保存設定(`save.*`)の変更は画像を再描画せず、コードだけ更新する(計測行は設けていない)。

### 一回限りの確認: ラスタ画像の画素数の上限(`formats.MAX_RASTER_PIXELS` = 100,000,000)

実アプリ(Chromium、Pyodide)で、上限付近の PNG 保存を試した。10,000 行 × 5 系列のデータで、図の大きさ(inch)と DPI を設定してから保存ボタンを押した。メモリは、Pyodide の WebAssembly メモリのバイト数(`WebAssembly.instantiate` の結果から `exports.memory` を捕まえて `buffer.byteLength` を読んだ)。保存前と保存直後の値。

| 設定 | 画素数 | 結果 | 時間 | 出力ファイル | wasm メモリ(保存前 → 後) |
|---|---:|---|---:|---|---:|
| 8×6 inch、1200 dpi(9600×7200) | 69.1 MP | 成功 | 2.1 秒 | 4.2 MB | 108 MB → 442 MB |
| 10×10 inch、999 dpi(9990×9990) | 99.8 MP | 成功 | 3.2〜3.3 秒 | 6.8 MB | 130 MB → 599 MB |
| 10×10 inch、1000 dpi(10000×10000) | 100.0 MP(上限ちょうど) | 成功 | 3.2 秒 | 6.8 MB | 130 MB → 600 MB |

- どれも、メモリ不足やクラッシュは起きなかった。コンソールエラーもない。画像の大きさは、ファイルを開いて指定どおりであることを確かめた。
- 上限付近では、RGBA の画素バッファ(1画素 4 バイト、100 MP で約 400 MB)が wasm のメモリに載るため、wasm メモリは約 600 MB まで広がった。wasm のメモリは縮まないので、保存後もこの大きさのまま残る。JS ヒープ(`performance.memory`)は約 93〜98 MB で変わらない。
- この Mac(24 GB)の結果。メモリの少ない端末(スマートフォンなど)では、600 MB 級の確保が失敗する可能性があるが、今回は確かめていない。上限(1億画素)の変更は不要と判断し、触っていない。
Loading
Loading