ワッフルチャートライブラリ比較
3つの実装で同じ割合(サイトを訪れた100人の端末)をマス目で描き、「100人中64人」と内訳を切り替えて比較
このページでは、Mantine Charts の WaffleChart・Nivo の @nivo/waffle・自前の SVG を使ったワッフルチャート(Waffle Chart)の実装パターンを、サイトを訪れた100人の端末(スマホ 64人・PC 31人・タブレット 5人)という同じデータのインタラクティブなデモとコードで確認できます。どのデモも1マスが1人の 10×10 で、ボタンで「100人中64人」の割合と3つの内訳を切り替えられます。人の形のアイコン(ピクトグラム)は、Nivo の cellComponent と自前の SVG で描きます。
円グラフが割合を角度で見せるのに対し、ワッフルチャートは数えられるマスで見せる図です。「100人中64人」のように、割合を人数や回数として伝えたいときに向きます。円グラフ(Pie Chart)の実装比較 →
ApexCharts 8 にも、マス目や人の形を並べる unit チャート(chart.type: 'unit')がありますが、有料(Premium)の機能です。ライセンスが無いと、グラフに透かしが入り、コンソールに警告が出るので、このページでは扱っていません。
1. @mantine/charts (WaffleChart)
Mantine 9.6 で加わった WaffleChart。data に { name, value, color } を渡すと、値に比例した数のマスを塗る。total を値の合計より大きくすると、残りが空のマスになる。マスは SVG の rect で、凡例は HTML。
チャートを読み込み中...
操作:ボタンで割合/内訳を切り替え/マスに hover でその端末の人数を表示/凡例に hover でその端末のマスを強調
size(px)で決まり、省くと1マス 20px になる。親の幅に合わせては縮まないので、このデモでは @mantine/hooks の useElementSize で親の幅を測って渡している。マスの数は、値の比で割り振ったうえで端数を大きい順に足す(最大剰余法)ので、合計がマスの数とずれない。塗る向き(fillDirection)は left-to-right(既定)・right-to-left・top-to-bottom・bottom-to-top から選べる。ツールチップ(withTooltip)はポインターについて動く Mantine の Tooltip.Floating で、getTooltipLabel には data の1件と、割り当てられたマスの数(count)が渡る。マスの形は四角だけで、人の形などには変えられない。SVG には role="img" と、「スマホ: 64 of 100」のような英語の aria-label が自動で付く。 配色はMantine独自のカラースキーム(MantineProvider)で決まり、サイトのダークモード切替とは連動しないため、追従しません(ライト表示で固定しています)。'use client';
// MantineWaffle.tsx(@mantine/core・@mantine/hooks・@mantine/charts 9.7)
import { useState } from 'react';
import { MantineProvider } from '@mantine/core';
import { WaffleChart } from '@mantine/charts';
import { useElementSize } from '@mantine/hooks';
import '@mantine/core/styles.css';
import '@mantine/charts/styles.css';
// サンプルデータ:サイトを訪れた100人の端末。1マス=1人の 10×10。並びはマスを塗る順
const DEVICES = [
{ name: 'スマホ', value: 64, color: '#3987e5' },
{ name: 'PC', value: 31, color: '#d95926' },
{ name: 'タブレット', value: 5, color: '#199e70' },
];
const TOTAL = 100;
export default function MantineWaffle() {
const [mode, setMode] = useState<'share' | 'breakdown'>('share');
// WaffleChart の幅は size(px)で固定され、親の幅に合わせて縮まない。親の幅を測って渡す
const { ref, width } = useElementSize();
// 割合はスマホの1件だけを渡し、total で残りの36人を空のマスにする
const data = mode === 'share' ? DEVICES.slice(0, 1) : DEVICES;
return (
<MantineProvider>
{(['share', 'breakdown'] as const).map((m) => (
<button key={m} type="button" onClick={() => setMode(m)} style={{ marginRight: 4, fontWeight: mode === m ? 700 : 400 }}>{m === 'share' ? '割合' : '内訳'}</button>
))}
<p style={{ fontWeight: 600 }}>{mode === 'share' ? '100人中 64人がスマホ' : '100人の端末の内訳'}</p>
<div ref={ref} style={{ maxWidth: 320 }}>
{width > 0 && (
<WaffleChart
data={data}
rows={10}
columns={10}
total={TOTAL}
size={width}
gap={3}
cellRadius={3}
// 左上から右へ、1行ずつ下へ塗る(既定)。ほかに right-to-left・top-to-bottom・bottom-to-top
fillDirection="left-to-right"
withTooltip
// data の1件と、割り当てられたマスの数(count)が渡る
getTooltipLabel={(cell) => `${cell.name} ${cell.value}人(${Math.round((cell.value / TOTAL) * 100)}%)`}
legendPosition="bottom"
/>
)}
</div>
</MantineProvider>
);
}AIプロンプトテンプレート
React + TypeScript で、@mantine/charts(9.7)の WaffleChart を使ったワッフルチャートを実装してください。
- データ:サイトを訪れた100人の端末(スマホ 64人・PC 31人・タブレット 5人)。1マスを1人にした 10×10 で、色は端末ごとに固定すること(スマホ #3987e5・PC #d95926・タブレット #199e70)
- 切り替え:ボタンで「割合」(スマホだけを塗り、残りの36人を空のマスにする。見出しは「100人中 64人がスマホ」)と「内訳」(3つを色分けする)を切り替えること。マスは左上から右へ、1行ずつ下へ塗ること
- データ: data に { name, value, color } の配列を渡し、rows={10}・columns={10}・total={100} にすること。割合ではスマホの1件だけを渡し、total で残りを空のマスにすること
- 幅: WaffleChart の幅は size(px)で固定され、親の幅に合わせて縮まないので、@mantine/hooks の useElementSize で親の幅を測って size に渡すこと(最大 320px)
- 見た目: gap={3}・cellRadius={3}、fillDirection は "left-to-right" にすること
- ツールチップ: withTooltip を付け、getTooltipLabel で「スマホ 64人(64%)」の形にすること
- 凡例: legendPosition="bottom" にすること(凡例に hover すると、その端末のマスだけが濃く残る)このプロンプトはあくまでたたき台です。AIの回答はモデルやバージョン、会話の文脈によって毎回異なります。意図通りに動かない場合は、条件を追記・修正してお使いください。
2. Nivo (@nivo/waffle)
@nivo/waffle の Waffle(ResponsiveWaffle)。data に { id, label, value }、total・rows・columns を渡すと、1マスの値(total ÷ マスの数)で割ったぶんのマスを塗る。親の大きさに合わせて、正方形のマスを並べる。SVG のほかに、HTML 版(WaffleHtml)と Canvas 版(WaffleCanvas)もある。
チャートを読み込み中...
操作:ボタンで割合/内訳、マス/人の形を切り替え/マスに hover でその端末のマスを強調し、人数を表示
fillDirection)の既定は 'top' で、右下のマスから左へ、1行ずつ上へ塗る。ほかの2つと同じく左上から塗るため、'bottom' にしている。割合の切り替えは、スマホ以外を hiddenIds に入れている(入れた id にはマスを割り当てないので、そのぶん空のマスになる)。motionStagger(ms)で、マスが1つずつ遅れて動く。マスの形は cellComponent で差し替えられ、渡される animatedProps(x・y・size・color)を @react-spring/web の animated の要素に渡せば、アニメーションも残る。人の形は、アイコンの path に translate と scale を掛けて描いている。hover は、端末ごとのマスをまとめた外形(areas レイヤー)で受けるので、ツールチップは端末ごとに1つになる。hover している端末を部品に渡すには React の context を使う(部品を作り直すと、マスが付け直されてアニメーションが最初からになる)。 空のマス(emptyColor)・ツールチップ・凡例の色を CSS で解決できる値で渡しているので、サイトのダークモードに追従する。'use client';
// NivoWaffle.tsx(@nivo/waffle 0.99 / @react-spring/web 10)
import { createContext, useContext, useState } from 'react';
import { ResponsiveWaffle, isDataCell, type CellComponentProps, type Datum, type TooltipProps } from '@nivo/waffle';
import { animated, to } from '@react-spring/web';
// サンプルデータ:サイトを訪れた100人の端末。1マス=1人の 10×10。並びはマスを塗る順
const DEVICES = [
{ id: 'スマホ', label: 'スマホ', value: 64, color: '#3987e5' },
{ id: 'PC', label: 'PC', value: 31, color: '#d95926' },
{ id: 'タブレット', label: 'タブレット', value: 5, color: '#199e70' },
];
const TOTAL = 100;
// 空のマスの色。'var(--empty)' のような CSS 変数も渡せる(サイトのダークモードに合わせるとき)
const EMPTY = '#e5e7eb';
// 人の形のアイコン(24×24)。頭の円と、肩の丸い胴
const PERSON = 'M12 0.5a5 5 0 1 1 0 10a5 5 0 0 1 0-10ZM2 24v-3a7 7 0 0 1 7-7h6a7 7 0 0 1 7 7v3Z';
// hover している端末。マスの部品に context で渡す
// (部品を作り直すと、マスが付け直されてアニメーションが最初からになるので、部品は固定にする)
const Active = createContext<string | null>(null);
// hover している端末があれば、それ以外のマス(空のマスを含む)を薄くする
function useOpacity({ cell, animatedProps }: CellComponentProps<Datum>) {
const active = useContext(Active);
const dim = active !== null && !(isDataCell(cell) && cell.data.id === active);
return to(animatedProps.opacity, (o) => (dim ? o * 0.3 : o));
}
// マス:既定の部品と同じ rect に、薄くする処理を足したもの。animatedProps を animated の要素に渡すと、アニメーションも残る
function SquareCell(props: CellComponentProps<Datum>) {
const { animatedProps: a, borderRadius } = props;
return <animated.rect x={a.x} y={a.y} width={a.size} height={a.size} rx={borderRadius} fill={a.color} opacity={useOpacity(props)} />;
}
// 人の形:アイコンをマスの位置へ動かし、マスの大きさに拡大する
function PersonCell(props: CellComponentProps<Datum>) {
const { animatedProps: a } = props;
return (
<animated.path
d={PERSON}
fill={a.color}
opacity={useOpacity(props)}
transform={to([a.x, a.y, a.size], (x, y, s) => `translate(${x} ${y}) scale(${s / 24})`)}
/>
);
}
const Tooltip = ({ data }: TooltipProps<Datum>) => (
<div style={{ background: '#fff', border: '1px solid #e5e7eb', padding: '4px 10px', fontSize: 12, whiteSpace: 'nowrap' }}>
<span style={{ color: data.color }}>■</span> {data.label} {data.value}人({Math.round((data.value / TOTAL) * 100)}%)
</div>
);
export default function NivoWaffle() {
const [mode, setMode] = useState<'share' | 'breakdown'>('share');
const [shape, setShape] = useState<'square' | 'person'>('square');
const [active, setActive] = useState<string | null>(null);
// 凡例:割合では、スマホと「それ以外」(空のマス)
const legend = mode === 'share' ? [DEVICES[0], { id: 'それ以外', value: TOTAL - DEVICES[0].value, color: EMPTY }] : DEVICES;
return (
<div>
{(['share', 'breakdown'] as const).map((m) => (
<button key={m} type="button" onClick={() => setMode(m)} style={{ marginRight: 4, fontWeight: mode === m ? 700 : 400 }}>{m === 'share' ? '割合' : '内訳'}</button>
))}
{(['square', 'person'] as const).map((s) => (
<button key={s} type="button" onClick={() => setShape(s)} style={{ marginRight: 4, fontWeight: shape === s ? 700 : 400 }}>{s === 'square' ? 'マス' : '人の形'}</button>
))}
<p style={{ fontWeight: 600 }}>{mode === 'share' ? '100人中 64人がスマホ' : '100人の端末の内訳'}</p>
<div style={{ width: '100%', maxWidth: 320, aspectRatio: '1' }}>
<Active.Provider value={active}>
<ResponsiveWaffle
data={DEVICES}
// 割合は、スマホ以外を隠す(隠した id にはマスを割り当てないので、そのぶん空のマスになる)
hiddenIds={mode === 'share' ? ['PC', 'タブレット'] : []}
total={TOTAL}
rows={10}
columns={10}
// 左上から右へ、1行ずつ下へ塗る(既定の 'top' は、右下のマスから左へ、1行ずつ上へ塗る)
fillDirection="bottom"
padding={3}
borderRadius={3}
colors={({ id }) => DEVICES.find((d) => d.id === id)!.color}
emptyColor={EMPTY}
// マスが1つずつ 4ms 遅れて動く。割合と内訳を切り替えると、順に塗り替わる
motionStagger={4}
cellComponent={shape === 'square' ? SquareCell : PersonCell}
tooltip={Tooltip}
// hover は、端末ごとのマスをまとめた外形(areas レイヤー)で受ける
onMouseEnter={(d) => setActive(String(d.id))}
onMouseLeave={() => setActive(null)}
/>
</Active.Provider>
</div>
<ul style={{ display: 'flex', gap: 16, listStyle: 'none', padding: 0, fontSize: 12 }}>
{legend.map((d) => (
<li key={d.id} style={{ opacity: active && active !== d.id ? 0.4 : 1 }}><span style={{ color: d.color }}>■</span> {d.id} {d.value}人</li>
))}
</ul>
</div>
);
}AIプロンプトテンプレート
React + TypeScript で、Nivo(@nivo/waffle 0.99)の ResponsiveWaffle を使ったワッフルチャートを実装してください。
- データ:サイトを訪れた100人の端末(スマホ 64人・PC 31人・タブレット 5人)。1マスを1人にした 10×10 で、色は端末ごとに固定すること(スマホ #3987e5・PC #d95926・タブレット #199e70)
- 切り替え:ボタンで「割合」(スマホだけを塗り、残りの36人を空のマスにする。見出しは「100人中 64人がスマホ」)と「内訳」(3つを色分けする)を切り替えること。マスは左上から右へ、1行ずつ下へ塗ること
- データ: data に { id, label, value } の配列を渡し、total={100}・rows={10}・columns={10} にすること。割合では、スマホ以外を hiddenIds に入れて空のマスにすること
- 並べ方: fillDirection="bottom"(左上から右へ、1行ずつ下へ)にすること(既定の 'top' は右下から上へ塗る)
- 見た目: padding={3}・borderRadius={3}、colors は関数で端末の色を返すこと。motionStagger={4} で、切り替えたときにマスが順に塗り替わるようにすること
- 形の切り替え: ボタンで「マス」と「人の形」を切り替え、cellComponent に、@react-spring/web の animated.rect と、24×24 の人のアイコンを animated.path で描く部品を渡すこと。人の形は animatedProps の x・y・size から transform(translate と scale)を作ること
- hover: onMouseEnter・onMouseLeave で hover している端末を覚え、React の context でマスの部品に渡して、それ以外のマスを薄くすること(部品を作り直すとアニメーションが最初からになるので、部品は固定にする)
- ツールチップ: tooltip に自前の部品を渡し、「スマホ 64人(64%)」の形にすること
- 凡例: HTML で、端末の色と人数を並べること(割合では「スマホ 64人」「それ以外 36人」)
- ダークモード: 空のマスの色(emptyColor)とツールチップの色を CSS 変数で渡して、サイトのダークモードに追従させることこのプロンプトはあくまでたたき台です。AIの回答はモデルやバージョン、会話の文脈によって毎回異なります。意図通りに動かない場合は、条件を追記・修正してお使いください。
3. 自前の SVG(ライブラリなし)
10×10 のマスを、SVG の rect で自分で並べる。マスの番号から行と列を計算し、端末ごとの人数だけ色を割り当てるだけなので、ライブラリを使わなくても数十行で書ける。人の形は、同じ位置にアイコンの path を置く。
チャートを読み込み中...
操作:ボタンで割合/内訳、マス/人の形を切り替え/マスに hover(タッチではタップ)でその端末のマスを強調し、人数を表示
transition で変え、マスの番号ごとに 6ms ずつ遅らせて、左上から順に塗り替わるようにしている(prefers-reduced-motion では切る)。viewBox を使うので、表示の幅に合わせてマスごと縮む。ツールチップは、ポインターの位置に HTML で出し、枠の左右の端では内側へ寄せている。タッチでは指を離すと pointerleave が来るので、表示を消すのはマウスのときだけにして、タップしたマスの表示を残している。svg には、日本語の aria-label(「サイトを訪れた100人のうち、64人がスマホ」)を付けた。 空のマスとツールチップの色を CSS 変数で渡しているので、サイトのダークモードに追従する。'use client';
// SvgWaffle.tsx(ライブラリなし)
import { useRef, useState } from 'react';
// サンプルデータ:サイトを訪れた100人の端末。1マス=1人の 10×10。並びはマスを塗る順
const DEVICES = [
{ name: 'スマホ', count: 64, color: '#3987e5' },
{ name: 'PC', count: 31, color: '#d95926' },
{ name: 'タブレット', count: 5, color: '#199e70' },
];
type Device = (typeof DEVICES)[number];
const TOTAL = 100;
const COLUMNS = 10;
const CELL = 24; // 1マスの大きさ(viewBox の単位。人のアイコンと同じ 24)
const GAP = 4;
const SIZE = COLUMNS * CELL + (COLUMNS - 1) * GAP;
const EMPTY = '#e5e7eb';
// 人の形のアイコン(24×24)。頭の円と、肩の丸い胴
const PERSON = 'M12 0.5a5 5 0 1 1 0 10a5 5 0 0 1 0-10ZM2 24v-3a7 7 0 0 1 7-7h6a7 7 0 0 1 7 7v3Z';
const label = (d: Device) => `${d.name} ${d.count}人(${Math.round((d.count / TOTAL) * 100)}%)`;
export default function SvgWaffle() {
const [mode, setMode] = useState<'share' | 'breakdown'>('share');
const [shape, setShape] = useState<'square' | 'person'>('square');
const [hover, setHover] = useState<{ device: Device; x: number; y: number; side: 'left' | 'center' | 'right' } | null>(null);
const boxRef = useRef<HTMLDivElement>(null);
// マスごとの端末(左上から右へ、1行ずつ下へ)。割合ではスマホ以外を空(null)にする
const cells = DEVICES.flatMap((d, i) => Array.from({ length: d.count }, () => (mode === 'share' && i > 0 ? null : d)));
// ポインターの下のマスの端末と、ツールチップの位置(枠の左右の端では、内側へ寄せる)
const show = (device: Device | null, e: React.PointerEvent) => {
if (!device) return setHover(null);
const box = boxRef.current!.getBoundingClientRect();
const x = e.clientX - box.left;
const side = x < box.width / 3 ? 'left' : x > (box.width * 2) / 3 ? 'right' : 'center';
setHover({ device, x, y: e.clientY - box.top, side });
};
return (
<div>
{(['share', 'breakdown'] as const).map((m) => (
<button key={m} type="button" onClick={() => setMode(m)} style={{ marginRight: 4, fontWeight: mode === m ? 700 : 400 }}>{m === 'share' ? '割合' : '内訳'}</button>
))}
{(['square', 'person'] as const).map((s) => (
<button key={s} type="button" onClick={() => setShape(s)} style={{ marginRight: 4, fontWeight: shape === s ? 700 : 400 }}>{s === 'square' ? 'マス' : '人の形'}</button>
))}
<p style={{ fontWeight: 600 }}>{mode === 'share' ? '100人中 64人がスマホ' : '100人の端末の内訳'}</p>
<div ref={boxRef} style={{ position: 'relative', maxWidth: 320 }}>
<svg
viewBox={`0 0 ${SIZE} ${SIZE}`}
width="100%"
role="img"
aria-label={mode === 'share' ? 'サイトを訪れた100人のうち、64人がスマホ' : 'サイトを訪れた100人の端末:スマホ 64人、PC 31人、タブレット 5人'}
// タッチでは指を離すと pointerleave が来るので、消すのはマウスのときだけ(タップしたマスの表示を残す)
onPointerLeave={(e) => e.pointerType === 'mouse' && setHover(null)}
>
{cells.map((d, i) => {
const x = (i % COLUMNS) * (CELL + GAP);
const y = Math.floor(i / COLUMNS) * (CELL + GAP);
const props = {
fill: d ? d.color : EMPTY,
// hover している端末があれば、それ以外を薄くする
opacity: hover && hover.device !== d ? 0.3 : 1,
// 色は1マスずつ 6ms 遅らせて変える(割合と内訳を切り替えると、左上から順に塗り替わる)
style: { transition: `fill 300ms ease ${i * 6}ms, opacity 150ms` },
// Tailwind CSS v4:prefers-reduced-motion では transition を切る
className: 'motion-reduce:transition-none!',
onPointerEnter: (e: React.PointerEvent) => show(d, e),
onPointerMove: (e: React.PointerEvent) => show(d, e),
};
return shape === 'square' ? (
<rect key={i} x={x} y={y} width={CELL} height={CELL} rx={3} {...props} />
) : (
<path key={i} d={PERSON} transform={`translate(${x} ${y})`} {...props} />
);
})}
</svg>
{hover && (
<div
style={{
position: 'absolute', left: hover.x, top: hover.y - 12, pointerEvents: 'none', whiteSpace: 'nowrap',
transform: `translate(${hover.side === 'left' ? '-12px' : hover.side === 'right' ? 'calc(-100% + 12px)' : '-50%'}, -100%)`,
background: '#fff', border: '1px solid #e5e7eb', padding: '4px 10px', fontSize: 12,
}}
>
<span style={{ color: hover.device.color }}>■</span> {label(hover.device)}
</div>
)}
</div>
<ul style={{ display: 'flex', gap: 16, listStyle: 'none', padding: 0, fontSize: 12 }}>
{(mode === 'share' ? [DEVICES[0], { name: 'それ以外', count: TOTAL - DEVICES[0].count, color: EMPTY }] : DEVICES).map((d) => (
<li key={d.name} style={{ opacity: hover && hover.device.name !== d.name ? 0.4 : 1 }}><span style={{ color: d.color }}>■</span> {d.name} {d.count}人</li>
))}
</ul>
</div>
);
}AIプロンプトテンプレート
React + TypeScript で、ライブラリを使わずに、SVG を自分で描いてワッフルチャートを実装してください。 - データ:サイトを訪れた100人の端末(スマホ 64人・PC 31人・タブレット 5人)。1マスを1人にした 10×10 で、色は端末ごとに固定すること(スマホ #3987e5・PC #d95926・タブレット #199e70) - 切り替え:ボタンで「割合」(スマホだけを塗り、残りの36人を空のマスにする。見出しは「100人中 64人がスマホ」)と「内訳」(3つを色分けする)を切り替えること。マスは左上から右へ、1行ずつ下へ塗ること - 描画: 1マス 24・間隔 4 の viewBox に、100個のマスを rect で並べること。width="100%" で、表示の幅に合わせて縮むようにすること(最大 320px) - 形の切り替え: ボタンで「マス」と「人の形」を切り替え、人の形は 24×24 のアイコンの path を translate でマスの位置に置くこと - アニメーション: マスの fill を CSS の transition で変え、マスの番号ごとに 6ms ずつ遅らせて、左上から順に塗り替わるようにすること。prefers-reduced-motion では transition を切ること - hover: マスの onPointerEnter で端末を覚え、それ以外のマスを薄くし、ポインターの上にツールチップ(「スマホ 64人(64%)」)を出すこと。枠の左右の端では、ツールチップを内側へ寄せること。タッチではタップしたマスの表示を残すこと - 凡例: HTML で、端末の色と人数を並べること(割合では「スマホ 64人」「それ以外 36人」) - 読み上げ: svg に role="img" と aria-label(「サイトを訪れた100人のうち、64人がスマホ」)を付けること - ダークモード: 空のマスとツールチップの色を CSS 変数で渡して、サイトのダークモードに追従させること
このプロンプトはあくまでたたき台です。AIの回答はモデルやバージョン、会話の文脈によって毎回異なります。意図通りに動かない場合は、条件を追記・修正してお使いください。
ライブラリ比較
| ライブラリ | 書き方 | 大きさ | マスの形 | ツールチップ | ダークモード |
|---|---|---|---|---|---|
| Mantine | WaffleChart | size(px)で固定 | 四角だけ | マスごと(ポインターについて動く) | ライト固定 |
| Nivo | @nivo/waffle の Waffle | 親に合わせる | cellComponent で差し替え | 端末ごとの外形 | ✓ 追従 |
| 自前の SVG | rect・path を並べる | viewBox で縮む | 自由 | 自分で書く | ✓ 追従 |
選択のポイント
- •Mantine: すでに Mantine を使っていれば、data を渡すだけで、ツールチップと凡例(hover で強調)までそろう。幅は px で決まるので、レスポンシブにするなら親の幅を測って
sizeに渡す。 - •Nivo: 親の大きさに合わせて並び、切り替えのアニメーション(
motionStagger)もある。cellComponentで人の形などのピクトグラムにでき、SVG で色を CSS から渡せるので、サイトのダークモードにも合わせやすい。 - •自前の SVG: マスを並べるだけなので、ライブラリを足したくない場合に向く。形・並べ方・アニメーションを自由に決められるが、ツールチップと読み上げは自分で書く。
典型的な使用例
- 👥アンケートや利用者の割合:「100人中64人がスマホ」「10人に3人が利用」のように、割合を人数として伝える
- 🎯目標の達成率・進み具合:100マスのうち何マス埋まったかで、達成率を見せる(進捗バーの代わり)
- 🗳️得票や議席の内訳:1マスを1票・1議席にして、数えられる形で見せる
- 📰記事・レポートのインフォグラフィック:人の形のアイコンを並べて、数字を直感的に伝える
⚠️ 使用時の注意点
- •1マスの意味(1人・1%・1,000円など)を、見出しや凡例に書く。書かないと、マスの数が何を表すか分からない
- •マスの数は整数なので、割合は丸められる(10×10 なら 1% 単位)。細かい差を見せたいときは、棒グラフや数字を添える
- •色分けする項目は少なめにする(3〜5 くらい)。多いと、どのマスがどの項目か見分けにくい。小さい項目は「その他」にまとめる
- •隣り合うマスの色が似ていると、境目が分からない。隣になる項目どうしを見分けやすい色にし(このページの色は、色覚の違いでも見分けられることを確かめてある)、マスの間に隙間を空ける
- •人の形のアイコンは、四角より塗る面が小さいので、色の違いが見えにくくなる。アイコンを大きめにするか、項目を減らす
ワッフルチャート(Waffle Chart)は、10×10 などのマス目を並べ、項目の割合に応じた数のマスを塗って、全体に対する割合を見せる図。マスを人やモノの形のアイコンにしたものは、ピクトグラム(アイソタイプ)とも呼ばれる。
アンケートや利用者の割合、目標の達成率、得票や議席の内訳など、割合を「100人中◯人」のように数えられる形で伝えたい場面で使われる。記事やレポートのインフォグラフィックにも多い。
- •割合型(1つの項目だけを塗り、残りを空のマスにする)
- •内訳型(項目ごとに色分けして、すべてのマスを塗る)
- •ピクトグラム型(マスを人やモノの形のアイコンにする)
- •ユニット型(マスの数で量を見せる。全体が決まっていない)
📊 使用しているサンプルデータ(クリックで表示)
あるサイトを訪れた100人の端末を、スマホ 64人・PC 31人・タブレット 5人として用意しました。1マスを1人にしているので、マスの数がそのまま人数(と割合)になります。3つのデモとコード例は同じ値・同じ色です。