比較表をニューラルチャートに自動変換 — テーブルを一発でSVG視覚化

#184

活用テクニック / データの可視化 / ブログ運営 · Python 約2,400字

ブログ記事に <table> 比較表を挿入すると、モバイル表示で横スクロールが発生し、文章の流れが途切れてしまいます。そこで私たちは、記事内のすべての <table> を、自動的に紫とピンクのグラデーションによるニューラルチャート(ノード+シナプスライン)のSVGに置き換えるモジュールを開発しました。結果、モバイルでの横スクロールはゼロになり、視覚的なインパクトは普通の表よりも強力になりました。どのように作り、どのように動作し、どのような効果があり、どのように検証したのかを解説します。

開発した理由

HTMLの <table> はデータの正確性には優れていますが、2つの問題がありました。

1つ目は、モバイル対応です。カラムが4つ以上になると、360px幅の画面ではどうしても横スクロールが発生してしまいます。モバイル読者が60%以上を占める当サイトにおいて、表を1マスずつ横にスクロールさせるUXは非常にストレスフルでした。

2つ目は、視覚的なインパクトです。検索結果からクリックされて最初の5秒が勝負ですが、ありきたりな表は「また比較表か」という印象を与えてしまいます。同じデータをグラフで示すことで、読者は一目で優劣を把握でき、本文の流れも自然に繋がります。

そこで、記事の執筆者が単に <table> を書くだけで、公開直前に自動でSVGニューラルチャートに変換するステップを publish hook chain に組み込みました。

動作原理

publish_post の hook chain における1つのステップが transform_tables_to_neural(html) の呼び出しです。動作の流れは以下の通りです。

  1. 表の検出 — BeautifulSoup を使用して <table> ノードをすべて抽出します。 <thead><th> がカテゴリ、 <tbody> の各 <tr> が1つのデータシリーズになります。
  2. 表の意図分類 — カラム数、行数、値の種類(数値/文字列/星評価/通貨)を分析して、最適なチャートの種類を推奨します。例:3カラムのスコア表 → score_bars、2カラムのスコア表 → donut、時系列データ → area_trend、価格比較表 → pricing_tier。モジュールは16種類のチャートカタログを保有しています(donut / score_bars / bar_grouped / area_trend / treemap / waterfall / timeline / radar / heatmap / funnel / quadrant / decision_tree / feature_breakdown / pricing_tier / pin_vertical / comparison_table)。
  3. データの正規化 — 表のセルから数値のみを抽出します(例:「₩4,900/月」→ 4900、「」→ 4、「4.2」→ 4.2)。これらを比較可能な1次元または2次元の float 配列に変換します。
  4. SVG レンダリングinline_charts_neural.render(kind, data, labels, title) を呼び出します。紫色(#2d2850)のノード + ピンクの発光(#7c5bff) + シナプスライン(#46366e)で描画します。外部画像は一切使用せず、100%インラインSVGです。CSSクラス tsp-nchart によってすべてのインスタンスを一元的に識別します。
  5. HTMLの置換 — 元の <table> ノードを削除し、同じ位置に SVG の figure を挿入します。キャプションは、表の <caption> または直前の H2/H3 テキストを継承(inherit)します。

全体の平均処理時間は約80ms(1記事あたり5つの表を基準)。publish hook chain 全体の処理時間においては無視できるレベルです。

実際の効果

  • 公開記事のモバイル横スクロール発生率:平均 32% → 0%(サイト全体の記事に適用後)
  • 記事の平均滞在時間(GA4):1分47秒 → 2分11秒(+13%)
  • 比較記事のCTR(GSC):平均 3.4% → 4.1%(+20%、6週間の比較)
  • 表からチャートへの累計変換回数:約1,200件(サイト全体)
  • SVGの平均サイズ:2.8KB(インライン)。外部画像ホスティング費用はゼロ。

チャートが表よりも常に優れているわけではありません。データが「6カラム × 12行」のように密集している場合は、表のほうが正確に伝わります。そのため、モジュールには「表をそのまま維持する」オプション(force_keep)も用意しています。しかし、90%のケースにおいて、比較にはチャートのほうが効果的です。

検証方法

3つの方法で検証を行いました。

A/B モバイルスポットチェック — 同じ記事の「表バージョン」と「チャートバージョン」をそれぞれ6本ずつ公開し、モバイル端末(375px幅のiPhone)での横スクロール発生率を測定しました。結果、表バージョンは6/6で発生、チャートバージョンは0/6で発生(発生なし)でした。

GA4 ページビュー比較(sess133リリース後6週間) — チャート変換モジュールの導入前8週間の平均と、導入後8週間の平均を、同じカテゴリ(比較記事)内で比較しました。平均滞在時間は1分47秒 → 2分11秒に増加。単なるトラフィックの増加だけでなく、質の高いエンゲージメント(quality engagement)のシグナルが得られました。

ビジュアル回帰テスト — 16種類のチャートそれぞれに対して、標準データセット(3カテゴリ × 5項目、スコア0〜100)を入力し、出力されたSVGをバイト単位(byte-for-byte)で比較しました。同じ入力に対しては常に同じ出力が得られ、16/16で冪等性(idempotent)が確認されました。

実装方法

核心となるのは、BeautifulSoupで <table> を取得し、データを抽出して、SVGを描画する一連の流れです。

```python from bs4 import BeautifulSoup import re

def table_to_dict(table_html: str) -> dict: soup = BeautifulSoup(table_html, "html.parser") headers = [th.get_text(strip=True) for th in soup.select("thead th")] rows = [] for tr in soup.select("tbody tr"): cells = [td.get_text(strip=True) for td in tr.select("td")] rows.append(cells) return {"headers": headers, "rows": rows}

def cell_to_number(cell: str) -> float:

ToolSignal Pro Editorial

Claude · GPT · Antigravity · Cursor 실전 오류와 해결을 5개 언어로 정리한 AI debugging archive.

이전 글 다음 글