Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
1bbbd15
Narrow the escape-decoding warning filter and record Phase 3 manual c…
Oct 8, 2026
75ea175
Add settings schema v3 for data import options
Oct 8, 2026
e4f1dc5
Add sheet, preamble, thousands/decimal and comment options to the loa…
Oct 8, 2026
bd1703a
Add load option inputs, sheet selector, preamble view and stale-code …
Oct 8, 2026
2e74046
Add E2E tests and consistency cases for load options and stale-code note
Oct 8, 2026
c996b8e
Support .tsv and mark pasted sources in the loader and generated script
Oct 8, 2026
2da7056
Add page-wide file drop, table paste and pasted-data save, with E2E a…
Oct 8, 2026
a715528
Recognize datetime columns in the loader and draw date axes in genera…
Oct 8, 2026
9b3f3b2
Add datetime UI (parse-dates option, date format selector, preview ta…
Oct 8, 2026
9acd75a
Relax datetime recognition: pick the best format from 90% of the firs…
Oct 8, 2026
d6733a4
Support several data sources in the Python layer: per-id session, sou…
Oct 8, 2026
c668457
Add multi-file loading: add/replace/remove, per-series data source, f…
Oct 8, 2026
e513f9f
Record Phase 4 decisions in the requirements
Oct 8, 2026
0389033
Measure Phase 4 performance: paste, sheet switch, datetime and two-fi…
Oct 8, 2026
2ff893e
Warn on decimal==separator, split lines like pandas, name data source…
Oct 8, 2026
3cac0f1
Reset to the initial state when the last file is removed, count only …
Oct 8, 2026
3aa27c4
Ask for the paste area when data is loaded; keep the drop overlay fro…
Oct 8, 2026
9e1c550
Document Phase 4 (data import) in AGENTS.md and README
Oct 8, 2026
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
118 changes: 86 additions & 32 deletions AGENTS.md

Large diffs are not rendered by default.

20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
# Matplotlib GUI

CSV / TXT / XLSX のデータから、ブラウザ上の操作だけで matplotlib の図を作るツールです。サーバーを使わず、Python(PyScript / Pyodide)をブラウザの中で実行します。
CSV / TSV / TXT / XLSX のデータから、ブラウザ上の操作だけで matplotlib の図を作るツールです。サーバーを使わず、Python(PyScript / Pyodide)をブラウザの中で実行します。

## 使い方

https://mioupa.github.io/matplotlib_gui/ を開いてください。`index.html` を `file://` で直接開く使い方には対応していません。

1. 「入力ファイル」で `.csv` / `.txt` / `.xlsx` を選びます。
1. 「入力ファイル」で `.csv` / `.txt` / `.tsv` / `.xlsx` を選ぶか、ページにファイルをドラッグ&ドロップします。Excel などでコピーした表は、「表を貼り付け」欄に貼り付けても読み込めます。
2. 読み込みと描画は自動で行われます(「描画する」ボタンはありません)。設定を変えると、すぐ再描画されます。
3. 「描画系列」でX列・Y列・色などを選び、必要なら系列を追加します。
3. 「描画系列」でX列・Y列・色などを選び、必要なら系列を追加します。複数のファイルを読み込んだときは、系列ごとに「データ元」を選びます。
4. 「体裁」でフォント・図のサイズ・プリセットなどを整えます。
5. 「保存」でファイル名・形式・DPI を選び、「保存する」で書き出すか、「クリップボードにコピー」で他のアプリに貼り付けます。

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

### データの読み込み

- ドラッグ&ドロップ: ページのどこにでも落とせます。すでにデータを読み込んでいるときは、「置き換えて読み込む」「追加して読み込む」のどちらかの枠に落として選びます。
- 貼り付け: 貼り付けた表は `pasted_data.tsv`(UTF-8、タブ区切り)として扱います。「貼り付けたデータを保存(.tsv)」で保存でき、生成した `.py` を動かすときは、このファイルを同じフォルダに置きます。「表を貼り付け」欄に貼り付けると、読み込み済みのデータは置き換わります。
- `.xlsx` は、複数のシートがあるとき「シート」で選べます(ファイルごとに選べます)。
- 「ヘッダより前に読み飛ばす行数」: ファイルの先頭にある説明の行など、ヘッダ行より前の行を読み飛ばします。読み飛ばした行は、データ確認タブの表の上に枠で表示します。これとは別に、「系列」の「描画から除外する先頭行数」は、ヘッダの後のデータ行を描画から除外します(データ確認タブでグレーになります)。
- 「桁区切り」「小数点」「コメント記号」: `1,234` や `1.234,5`(ヨーロッパ形式)、`#` で始まる説明の行などを読めます。数値に変換できない値があるときは、該当しそうな設定を警告で案内します。区切り文字は自動判定しないので、`;` やタブ区切りのファイルは「区切り文字」で指定してください。
- 日時の列: `2026-01-31`、`2026/01/31 13:45`、`2026年1月31日`、ISO 8601(`2026-01-31T13:45:00+09:00`)など、年が先頭の書式の列を自動で日時として認識します(「日時の列を自動で認識する」で切り替え)。X に日時の列を選ぶと日時の軸になり、軸・目盛の区分で表示書式(`2024-01-31`、`01/31 13:45` など。任意の書式も可)を選べます。X が日時のときは、X軸の対数と範囲の指定はできません。棒グラフでは、日時を文字にして並べます。月日の順が曖昧な `01/02/2024` は認識しません。
- 複数ファイル: 最大 10 個まで読み込めます(同じ名前のファイルは置き換え)。読込設定は全ファイル共通で、シートだけファイルごとに選びます。棒グラフは、すべての系列を同じデータ元にしたときだけ描けます。

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

## 主な機能
Expand All @@ -29,8 +39,8 @@ https://mioupa.github.io/matplotlib_gui/ を開いてください。`index.html`
- 欧文フォント: 標準(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 のスクリプトを表示(下記)
- データ確認タブ: 先頭100行を表示し、描画から除外される先頭行をグレーで、読み飛ばした行(ヘッダより前)を枠で表示。複数ファイルのときは表示するファイルを切り替えられます
- Pythonコードタブ: 設定から生成した matplotlib のスクリプトを表示(下記)。エラーで最新の設定のコードを作れないときは、表示中のコードが最新でないことをタブ内に表示します
- 数値に変換できず除外した値の警告、日本語のエラーメッセージ

## Pythonコードタブ
Expand Down
178 changes: 178 additions & 0 deletions css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,43 @@ button.secondary:hover {
margin-bottom: 8px;
font-size: 13px;
}
#dataPreamble {
margin-bottom: 8px;
padding: 8px 10px;
border: 1px dashed var(--line);
border-radius: 8px;
background: var(--surface);
font-size: 12px;
}
#dataPreamble .preamble-title {
font-weight: 700;
margin-bottom: 4px;
}
#dataPreamble .preamble-lines {
margin: 0;
max-height: 9em;
overflow: auto;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
white-space: pre;
color: var(--text-muted);
}
#dataPreamble .preamble-more {
margin-top: 4px;
color: var(--text-muted);
}
.code-stale-note {
margin-bottom: 8px;
padding: 6px 10px;
border-left: 3px solid var(--status-warning);
border-radius: 6px;
background: var(--warning-bg);
color: var(--status-warning);
font-size: 12px;
font-weight: 700;
}
.code-stale-note[hidden] {
display: none;
}
#codePanel {
overflow: auto;
}
Expand Down Expand Up @@ -873,3 +910,144 @@ button.series-color-chip.active {
font-size: 13px;
white-space: nowrap;
}

/* 表の貼り付け欄とファイルのドロップ */
.paste-area {
width: 100%;
box-sizing: border-box;
resize: vertical;
padding: 8px;
border: 2px dashed var(--line);
border-radius: 8px;
background: var(--surface);
color: var(--text);
font-size: 13px;
}
.paste-area:focus {
border-color: var(--accent);
outline: none;
}
#savePastedBtn {
margin-top: 8px;
}
.drop-overlay {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
align-items: stretch;
justify-content: center;
padding: 24px;
box-sizing: border-box;
background: var(--busy-bg);
}
.drop-overlay[hidden] {
display: none;
}
.drop-zones {
display: flex;
flex: 1;
gap: 24px;
}
.drop-zone {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
border: 3px dashed var(--accent);
border-radius: 16px;
background: var(--card);
color: var(--accent);
font-size: 22px;
font-weight: 700;
text-align: center;
}

.col-kind-tag {
display: inline-block;
margin-left: 6px;
padding: 0 5px;
border: 1px solid var(--accent);
border-radius: 8px;
color: var(--accent);
font-size: 10px;
font-weight: normal;
line-height: 1.5;
vertical-align: middle;
}

.drop-zone[hidden] {
display: none;
}
.drop-zone.drop-zone-active {
background: var(--accent);
color: var(--accent-contrast);
}
.add-file-row {
margin-top: 8px;
}
.file-list {
list-style: none;
margin: 8px 0 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.file-list[hidden] {
display: none;
}
.file-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px 8px;
padding: 6px 8px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--card);
font-size: 13px;
}
.file-row[data-state="error"] {
border-color: var(--error-border);
}
.file-row-no {
font-weight: 700;
color: var(--accent);
}
.file-row-name {
flex: 1 1 120px;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-row-kind {
color: var(--text-muted);
font-size: 12px;
}
.file-row select {
max-width: 100%;
}
.file-row-error {
flex-basis: 100%;
color: var(--error-text);
font-size: 12px;
overflow-wrap: anywhere;
}
.file-row-loading {
color: var(--text-muted);
font-size: 12px;
}
.preview-source {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.preview-source[hidden] {
display: none;
}
.series-source-wrap[hidden] {
display: none;
}
69 changes: 69 additions & 0 deletions docs/PERFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,72 @@ Phase 1 後より悪化した項目はなかった。このため、悪化の内
- どれも、メモリ不足やクラッシュは起きなかった。コンソールエラーもない。画像の大きさは、ファイルを開いて指定どおりであることを確かめた。
- 上限付近では、RGBA の画素バッファ(1画素 4 バイト、100 MP で約 400 MB)が wasm のメモリに載るため、wasm メモリは約 600 MB まで広がった。wasm のメモリは縮まないので、保存後もこの大きさのまま残る。JS ヒープ(`performance.memory`)は約 93〜98 MB で変わらない。
- この Mac(24 GB)の結果。メモリの少ない端末(スマートフォンなど)では、600 MB 級の確保が失敗する可能性があるが、今回は確かめていない。上限(1億画素)の変更は不要と判断し、触っていない。

## Phase 4 後

### 計測条件

| 項目 | 内容 |
|---|---|
| 日付 | 2026-10-08 |
| 対象 | `c668457`(Phase 4: 読込設定(シート、ヘッダより前の読み飛ばし、桁区切り・小数点・コメント)、ドラッグ&ドロップと貼り付け、日時の列と日時軸、複数ファイル)に、Phase 4 の計測行を追加した `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、ヘッドレス) |
| ネットワーク | 実ネットワーク(Phase 3 後と同じ) |
| 配信 | ローカルの静的 HTTP サーバー(127.0.0.1、サブパスなし) |
| 反復 | 各項目 N=5。既存項目の計測方法は Phase 3 後と同じ |

追加した項目(既存項目の方法は変えていない。データはすべて実行時に生成し、コミットしない):

- 「貼り付け → 最初のプロット」: `#pasteArea` に `ClipboardEvent('paste')`(`DataTransfer` に text/plain)を送り、最初のプロットが出るまで(終了条件は「初回ファイル読込 → 最初のプロット」と同じ)。(a) 10,000 行 × 6 列(X + Y 5列)のタブ区切り、改行は CRLF(Excel からのコピーと同じ形)、(b) 30 行 × 3 列。
- 「xlsx(2 シート × 10,000 行)」: openpyxl で作った 2 シート(各 10,000 行 × 6 列、約 0.8 MB)の xlsx。(a) 初回読込 → 最初のプロット(Excel 用ライブラリの wheel は HTTP キャッシュを温めた状態)、(b) 系列5本を設定してから `#sheetSelect` で 2 枚目に切り替え → 新しい画像(`change` イベントから。そのファイルの読み直しと描画を含む)。
- 「日時の列を含む 10,000 行」: `%Y/%m/%d %H:%M:%S` の日時の列 + 数値 5 列の CSV。(a) 読込 → 最初のプロット(既定の系列で描く。日時の認識と変換を含む)、(b) X = 日時の列で系列5本を設定してから、既存の「設定変更」と同じ方法(タイトルの `input` → 新しい画像。デバウンス 250 ms 込み)。
- 「2ファイル」: 10,000 行の CSV 2つを `#fileInput`(multiple)で同時に選ぶ → 最初のプロット。設定変更は、系列1 をファイル1、系列2 をファイル2(2 系列)にしてから、既存の「設定変更」と同じ方法で測る(5 系列の既存項目とは系列数が違うので、直接は比べられない)。

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

| 項目 | Phase 3 後 | Phase 4 後 | Phase 3 後比 |
|---|---:|---:|---|
| コールド起動 | 3,564 / 3,526 / 7,098 | 3,605 / 3,544 / 6,444 | 同等(+1%) |
| ウォーム起動 | 3,441 / 3,424 / 3,484 | 3,429 / 3,418 / 3,437 | 同等(-0%) |
| 初回ファイル読込 → 最初のプロット | 177 / 175 / 179 | 178 / 176 / 179 | 同等(+1%) |
| 設定変更 → プロット更新(デバウンス込み) | 471 / 468 / 473 | 465 / 457 / 470 | 同等(-1%) |
| 同上(デバウンス 250ms 除く) | 221 / 218 / 223 | 215 / 207 / 220 | 同等(-3%) |
| 保存(PNG、クリック → ダウンロード開始。既定の 300 dpi) | 405 / 364 / 408 | 384 / 377 / 387 | 同等(-5%) |
| 初回 .xlsx 読込 → 最初のプロット | 329 / 325 / 330 | 325 / 323 / 325 | 同等(-1%) |
| 編集モードでの実行(Ctrl/Cmd+Enter → プロット更新) | 579 / 575 / 596 | 571 / 561 / 582 | 同等(-1%) |
| 保存(PNG、120 dpi) | 246 / 242 / 257 | 252 / 215 / 258 | 同等(+2%) |
| 保存(PNG、300 dpi。別測定) | 403 / 369 / 412 | 403 / 393 / 409 | 同等(-0%) |
| 欧文フォント選択 → プロット更新、初回(Arimo を jsDelivr から取得) | 468 / 456 / 472 | 459 / 452 / 471 | 同等(-2%) |
| 同上、Cache Storage にあるとき | 467 / 462 / 469 | 466 / 453 / 471 | 同等(-0%) |
| 参考: 欧文フォント初回の、選択 → `data-latin-font-state=ready` | 23 / 21 / 25 | 34 / 24 / 38 | 参考値(+48%。下記) |
| クリップボードへのコピー(クリック → done、300 dpi) | 422 / 406 / 424 | 406 / 390 / 407 | 同等(-4%) |
| 日本語フォント取得、コールド(ナビゲーション開始 → `font-state=ready`) | 3,622 / 3,606 / 3,623 | 3,692 / 3,687 / 3,721 | 同等(+2%) |
| 同上、Python 準備完了からの追加待ち | 67 / 56 / 77 | 68 / 61 / 83 | 同等(+1%) |
| 貼り付け(10,000 行 × 6 列)→ 最初のプロット【新】 | - | 180 / 180 / 182 | - |
| 貼り付け(30 行 × 3 列)→ 最初のプロット【新】 | - | 132 / 131 / 135 | - |
| xlsx(2 シート × 10,000 行)初回読込 → 最初のプロット【新】 | - | 843 / 840 / 843 | - |
| xlsx のシート切り替え → プロット更新(系列5本)【新】 | - | 465 / 461 / 466 | - |
| 日時の列を含む 10,000 行の読込 → 最初のプロット【新】 | - | 238 / 234 / 240 | - |
| 同、X = 日時で設定変更 → プロット更新(デバウンス込み、系列5本)【新】 | - | 478 / 468 / 481 | - |
| 同上(デバウンス 250ms 除く)【新】 | - | 228 / 218 / 231 | - |
| 2ファイル(各 10,000 行)の読込 → 最初のプロット【新】 | - | 208 / 206 / 209 | - |
| 2ファイルで設定変更 → プロット更新(デバウンス込み、系列2本)【新】 | - | 342 / 337 / 345 | - |
| 同上(デバウンス 250ms 除く)【新】 | - | 92 / 87 / 95 | - |

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

### 要件の確認

要件「設定変更後 1 秒以内の再描画(10,000 行 × 5 系列)」: 設定変更 → プロット更新は、デバウンス 250 ms を含めて中央値 465 ms(最大 470 ms)で、**満たしている**。X が日時の列のときも 478 ms(最大 481 ms)、xlsx のシート切り替え(読み直しを含む)も 465 ms で、どちらも 1 秒以内。

### 分析

Phase 3 後より 5% を超えて悪くなった既存項目は無い(日本語フォントのコールド取得の +2% は、jsDelivr からの実ダウンロードを測っているためのぶれの範囲)。Phase 4 の読込設定(読み飛ばし・桁区切り・小数点・コメント)は `read_csv` の引数が増えるだけで、日時の認識は文字列の列だけを調べるため、数値だけの CSV の読込(「初回ファイル読込」177 → 178 ms)には影響していない。

- **参考値「欧文フォント初回の選択 → ready」+48%(23 → 34 ms)**: jsDelivr から約 0.3 MB を取得する時間で、ネットワークのぶれ。デバウンス 250 ms の間に終わるため、プロット更新(459 ms)には影響していない(Phase 4 で欧文フォントの処理は変えていない)。
- **日時の列を含む読込(238 ms)**: 数値だけの同じ大きさの CSV(178 ms)より約 60 ms 長い。日時の認識(文字列の列ごとに、先頭 100 個の値で書式の候補を試す)と、10,000 個の文字列の日時への変換の分。設定変更からの更新(228 ms、デバウンス除く)は数値の X(215 ms)より約 13 ms 長い程度で、日時軸の目盛の計算の分とみられる。
- **貼り付け(10,000 行)180 ms**: 同じ大きさのファイルの読込(178 ms)とほぼ同じ。貼り付けたテキストは `pasted_data.tsv` というファイルとして、ファイルと同じ経路で読むため。
- **xlsx(2 シート × 10,000 行)の初回読込 843 ms**: ワークブックの解析(openpyxl)が大半。シートの切り替えでは、開いた `ExcelFile` を同じファイルの間だけ使い回すので、ワークブックを開き直さず、そのシートの読込と系列5本の描画だけで 465 ms になる。
- **2ファイル**: 読込はファイルごとに順に行う(208 ms。1ファイルの 178 ms に、2つ目の読込が約 30 ms 加わる)。設定変更からの更新は系列が2本なので、5本の既存項目より短い。
Loading
Loading