0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

文部科学省の「日本食品標準成分表2023年版(八訂)」をもとに、全2,538食品・53栄養素を比較できる無料ツール、NutriGraphを公開しました。数値はすべて可食部100gあたりです。

この記事では、プロダクトの紹介と、ビルド不要のバニラJS構成、SVGで作る統計セル、プログラマティックSEOの実装ポイントをまとめます。

作った理由

栄養を紹介するサイトの多くはグラフがなく、文章か数値の羅列だけです。数値を出されても、それが多いのか少ないのか、ほかの食品と比べてどうなのかがわかりません。

さらに、「〇〇に良い」と書かれていても、実際にはほとんど入っていないこともあります。
例えば、「レタスは栄養が意外と豊富?!」と紹介しているサイトは結構あります。しかしながら、食品成分表のデータを野菜の中で見比べると、葉酸以外の全ての栄養素が全野菜中下位25%でした。

数値の大小と、ほかの食品の中での位置の両方がわかるツールがほしくて作りました。後述するマトリクスの統計セルは、このために用意した機能です。

できること

4つのタブで比較できます。

  1. 栄養素ランキング: 1つの栄養素で全食品を多い順・少ない順に並べる
  2. 食品プロファイル比較: 複数の食品を選んで相対比較する
  3. 食品群内で全比較: 食品群を選び、その群の全食品を栄養素ごとに比較する
  4. 食品×栄養素マトリクス: 行が食品、列が栄養素。各セルに統計を重ね、色でその食品群内の豊富さを表す

構成

項目 採用したもの
言語・構成 バニラJavaScript。ES Modules、依存パッケージなし
グラフ Chart.js v4
マトリクス描画 インラインSVG
データ data.json 約1.8MB
ホスティング Vercel。環境変数なし

データは2,538食品・53栄養素で固定です。公式データの更新は年単位なので、サーバーやDBを置く必要はありません。data.json を1回 fetch して、フィルタもソートもクライアント側で完結させています。

// src/store.js
export async function loadData() {
  const url = new URL('../data.json', import.meta.url);
  const res = await fetch(url);
  if (!res.ok) throw new Error(`data.json の読み込みに失敗しました (${res.status})`);
  const d = await res.json();
  COLS = d.columns; FOODS = d.foods; GROUPS = d.groups;
  return d;
}

ビルドステップがないので、ローカルは npx serve . で起動でき、デプロイはVercelに上げるだけです。フレームワークの更新やビルド設定のメンテが不要です。

なお、ES Modulesと fetch を使うため、file:// で直接開かず、簡易サーバー経由で表示します。

実装ポイント1: SVGで統計入りのセルを描く

マトリクスタブのポイントは、各セルが数値ではなく、その食品群の中でその値がどの位置かを表すところです。Chart.jsではなくインラインSVGで1セルずつ描画しています。

  • 値の棒を5段階の色で塗り分ける。Q1未満はグレー、中央値以上で濃いティール、スケール超で赤
  • Q1・Q3・平均の縦線と、中央値の点を重ねる
// src/tabs/matrix.js(抜粋)
function cellBox(v, st) {
  if (v == null) return '<td><div class="cb empty">—</div></td>';
  s += `<rect ... fill="${TIERS[tier].color}" />`;
  s += `<line x1="${X(st.q1)}"  ... stroke="${STATC.q1}"/>`;
  s += `<line x1="${X(st.q3)}"  ... stroke="${STATC.q3}"/>`;
  s += `<line x1="${X(st.mean)}"... stroke-dasharray="2,1.5"/>`;
  s += `<circle cx="${X(st.med)}" ... fill="${STATC.med}"/>`;
  return `<td><div class="cb"><svg ...>${s}</svg></div></td>`;
}

統計は外れ値の影響を抑えるため、上位・下位5%を除外してから四分位を計算しています。

// src/utils.js
export function quantile(sorted, q) {
  const p = (sorted.length - 1) * q, b = Math.floor(p), r = p - b;
  return sorted[b + 1] !== undefined ? sorted[b] + r * (sorted[b + 1] - sorted[b]) : sorted[b];
}

実装ポイント2: プログラマティックSEOで約2,600ページ

SPAは検索流入が弱点になりやすいので、scripts/generate.mjs で約2,600ページの静的HTMLを生成しています。

  • /foods/番号: 食品詳細。2,538ページ
  • /nutrients/栄養素: 栄養素ランキング。53ページ
  • /groups/番号: 食品群一覧。18ページ
  • /collections/テーマ: 高たんぱく・低カロリーなどの特集
  • /compare/a-b: 1対1比較

各ページに固有のtitle、description、canonical、OGP、パンくずJSON-LDを付け、sitemap.xmlに全URLを載せています。npm run generate で再生成できます。

実装ポイント3: 地味に工夫したUX

元データでは名称に( )が多く入っており、名称が長すぎます。これを表示しないようにしています。

// 表示名はかっこ書きを省略。検索は元の名前で効く
export const dispName = n =>
  n.replace(/[((][^()()]*[))]/g, '').replace(/\s+/g, ' ').trim() || n;

// 数値は桁に応じて整形
export const fmt = n =>
  n == null ? ''
  : (Math.abs(n) >= 100 ? n.toFixed(0)
    : Math.abs(n) >= 10 ? n.toFixed(1)
    : n.toFixed(2)).replace(/\.0+$/, '');

開発について

実装はAIコーディングエージェントのClaude Codeと相談しながら進めました。データ整形からSVG描画、SEOページの生成まで、設計判断を会話で詰めつつ短い期間で仕上げられました。

まとめ

  • 公式の食品成分表を、全食品・全栄養素まとめて比較できる無料ツールを公開しました
  • ビルド不要のバニラJSとChart.js、静的ホスティングで、運用コストはほぼゼロです
  • SVGの統計セルと、約2,600ページのSEOページ生成が技術的な見どころです

よければ触ってみてください。
https://nutri-graph.vercel.app/

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?