tech-book-labs
可視化(チャート / 図 / 表) · 最終検証 2026-09-14 · pptxgenjs 4.0.1 · 初公開 2026-09-14

Recharts のグラフを PowerPoint に出力する — ネイティブグラフと画像、2 通りの書き出しを比べる

React(Recharts)で画面に描いた棒グラフを、PptxGenJS で .pptx に書き出す 2 つの方法。同じデータを addChart で PowerPoint 側で編集できるグラフとして置く方法と、SVG を PNG にして addImage で貼る方法を、同じデモで実測して比べる。CSS 変数の解決、HTML の凡例が画像に入らない問題、フォント非埋め込みまで実装メモ。

recharts pptxgenjs powerpoint react svg report-generation

検証日: 2026-09-14

使用バージョン: recharts@3.8.1 / pptxgenjs@4.0.1 / React 19

対象: ダッシュボードに出しているグラフを「そのまま PowerPoint に貼りたい」と言われた人。データを PowerPoint のグラフに写すか、画面を画像にするかで迷っている人

画面に Recharts で描いた棒グラフを、ブラウザだけで .pptx に書き出す。方法は 2 つある。同じデータを PptxGenJS の addChart に渡して PowerPoint 側のグラフとして置くか、描画済みの SVG を PNG にして addImage で貼るか。前者は受け取った人が PowerPoint で数値も色も直せる。後者は画面と同じ見た目になるが画像なので直せない。両方を 1 つのデモに入れて、サイズと時間を測った。

触って試す

CSV を書き換えるとグラフが変わる。2 つのボタンでそれぞれ .pptx を生成してダウンロードし、下の表にサイズと所要時間が出る。生成した 2 つのファイルを PowerPoint で開き、グラフをダブルクリックすると違いが分かる(ネイティブは「データの編集」が開く、画像は「図の書式設定」になる)。

どちらを選ぶか

ネイティブグラフ(addChart)
受
け
P
o
デ
ー
対
応
PNG 画像(addImage)
画
面
編
集
描
画
解
像

判断の軸は「渡した先で直すか」。経営会議の資料のように数字が後から動く用途はネイティブ、監査や報告書のように見た目を固定したい用途は画像、と分けるとぶれない。

1. ネイティブグラフ: データを写す

Recharts に渡している配列を、PptxGenJS の系列形式({ name, labels, values }[])に組み替えて slide.addChart に渡すだけでよい。画面の DOM は使わない。

const { default: PptxGenJS } = await import("pptxgenjs");
const pptx = new PptxGenJS();
pptx.layout = "LAYOUT_WIDE";
const slide = pptx.addSlide();

// Recharts の data: [{ label: "Jan", visits: 120, signups: 28 }, ...]
const series = ["visits", "signups"].map((key) => ({
  name: key,
  labels: data.map((d) => d.label),
  values: data.map((d) => Number(d[key])),
}));

slide.addChart(pptx.ChartType.bar, series, {
  x: 0.6, y: 0.6, w: 12.1, h: 6.3,
  barDir: "col",                 // 縦棒
  grouping: "clustered",         // 系列を横に並べる(積み上げは "stacked")
  showLegend: true, legendPos: "b",
  chartColors: ["0F6E63", "5A574F"],   // 系列色は HEX 6 桁(# なし)
  valGridLine: { color: "D9D4C8", width: 1 },
});
const blob = await pptx.write({ outputType: "blob" });

画面と色を揃えたい場合、Recharts 側の色が CSS 変数(var(--color-accent) など)だと PptxGenJS には渡せない。デモでは <span style="color: var(--color-accent)"> を一瞬 DOM に置いて getComputedStyle で rgb() を取り、canvas に塗って HEX に変換している。テーマ切替(ライト / ダーク)にそのまま追従する。

2. 画像: SVG を PNG にして貼る

Recharts の描画は <svg class="recharts-surface">。これを XMLSerializer で文字列にし、<img> に data URL として読み込み、canvas に描いて toDataURL("image/png") で PNG にする。手順は 4 段で、詰まりどころは 2 つ目にある。

SVG を複製し、計算済みスタイルを属性に焼き込む getComputedStyle(el).getPropertyValue('fill') 外部 CSS と CSS 変数は data URL の中では効かない。fill / stroke / font 系を要素ごとに書き戻す
凡例を SVG に含める <Legend content={() => <svg data-export-legend>…</svg>} /> Recharts の Legend は HTML(div)なので SVG に入らない。SVG で自作した凡例を差し込む
data:image/svg+xml を <img> に読み込み、canvas に描く canvas.width = w * devicePixelRatio * 2 倍率はここで決まる。等倍だと PowerPoint 上でぼやける
PNG の data URL を addImage に渡す slide.addImage({ data, x, y, w, h }) 縦横比は SVG の bounding box から計算して合わせる
const surface = root.querySelector<SVGSVGElement>("svg.recharts-surface")!;
const clone = surface.cloneNode(true) as SVGSVGElement;
// 元要素と複製を同じ順に走査し、計算済みの fill / stroke / font を style 属性へ
[surface, ...surface.querySelectorAll("*")].forEach((el, i) => {
  const copy = [clone, ...clone.querySelectorAll("*")][i] as SVGElement;
  const cs = getComputedStyle(el);
  for (const p of ["fill", "stroke", "stroke-width", "font-family", "font-size", "opacity"]) {
    copy.style.setProperty(p, cs.getPropertyValue(p));
  }
});
const svgText = new XMLSerializer().serializeToString(clone);
const img = new Image();
await new Promise((ok, ng) => { img.onload = ok; img.onerror = ng; img.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svgText); });
const scale = (devicePixelRatio || 1) * 2;
const canvas = Object.assign(document.createElement("canvas"), { width: Math.ceil(w * scale), height: Math.ceil(h * scale) });
canvas.getContext("2d")!.drawImage(img, 0, 0, canvas.width, canvas.height);
slide.addImage({ data: canvas.toDataURL("image/png"), x: 0.6, y: 0.6, w: 12.1, h: 12.1 * h / w });

3. 実測: サイズと時間

同じ 6 行 × 2 系列のデータで両方を生成し、Blob のサイズと、ライブラリ読み込み後から Blob 完成までの時間を測った(Chromium、pptxgenjs@4.0.1、2026-09-14、3 回の中央値、倍率 2、計測スクリプト。数字はデモの表と同じ計測)。

つまずいたポイント

  • chartColors は HEX 6 桁で # なし。rgb() や CSS 変数は無視されて既定色になる。
  • grouping: "stacked" にすると積み上げになる。Recharts で stackId を付けているグラフを写すときはここを合わせる。
  • addImage の w / h は inch。ピクセルの縦横比だけ保てばよく、絶対値は LAYOUT_WIDE の 13.333 × 7.5 inch に収まるように決める。
  • canvas の倍率を上げるとサイズが増える。倍率 2 で十分に見え、4 にすると PNG が 4 倍近くになる。

向く / 向かないケース

  • 向く: 週次・月次で同じ形のグラフを PowerPoint に流し込む運用。ネイティブなら受け取り側が数字を直せるので、差し戻しが減る。
  • 向く: Recharts のカスタム描画(独自のラベルや注釈)をそのまま資料に載せたい場合は画像方式。
  • 向かない: 画像方式をサーバで作りたい場合。DOM と canvas が要るので、サーバなら Recharts を使わずネイティブ方式でデータから作る。

関連