サンバーストチャートライブラリ比較
4つのライブラリで階層データを同心円で表示し、扇のクリックで掘り下げる操作と実装方法を比較
このページでは、ApexCharts・Apache ECharts・Nivo・Mantine Charts(@mantine/charts)を使ったサンバーストチャート(Sunburst Chart)の実装パターンを、Webサイトの流入経路(チャネル → 参照元 → 詳細)という同じ階層データのインタラクティブなデモとコードで確認できます。扇のクリックで階層を掘り下げるドリルダウンは、ApexCharts と ECharts は組み込みの機能で、Nivo と Mantine は onClick と state で実装しています。
階層データは、入れ子の長方形の面積で表すこともできます。ツリーマップ(Treemap)の実装比較 →
1. ApexCharts (react-apexcharts)
ApexCharts 6.7 で加わったチャート種別。chart.type: 'sunburst' で、children を入れ子にした階層データをそのまま渡す。扇をクリックするとその枝に拡大し、左上のパンくずで戻る操作が組み込まれている(zoomOnClick、既定で有効)。
チャートを読み込み中...
操作:扇をクリックで拡大/左上のパンくずか中心の輪のクリックで戻る/凡例のクリックで1階層目の枝を外す
dataLabels.style.colors)ので、このデモでは塗りをすべて明るめにして濃い文字にしている。既定のツールチップは割合を英語(of parent / of total)で足すため、tooltip.custom で受け取れる percentOfParent・percentOfTotal で日本語にしている。v8 から既定のバンドルに入っていないため、import 'apexcharts/sunburst' で登録する(無いとコンソールにエラーが出て、凡例だけが描かれる)。react-apexcharts の型の type にはまだ sunburst が無いので、値は渡したうえで型だけ外している(コード例を参照)。 CSS変数(--apx-*)を読む仕組みはありますが、再読み込みの呼び出しが別途必要なため、サイトのダークモードには追従しません(ライト表示で固定しています)。'use client';
// ApexSunburstChart.tsx(apexcharts 8.7 / react-apexcharts 2.2。ページ側では next/dynamic の ssr: false で読み込む)
// v8 から sunburst は既定のバンドルに入っていない。この import で登録する
// (無いとコンソールにエラーが出て、凡例だけが描かれる)
import 'apexcharts/sunburst';
import Chart from 'react-apexcharts';
import type { ApexOptions } from 'apexcharts';
// 階層は children で入れ子にする(name / value は x / y の別名として読まれる)
const data = [
{
name: '自然検索', color: '#60a5fa',
children: [
{
name: 'Google', color: '#93c5fd',
children: [
{ name: 'Google 検索', value: 7200, color: '#bfdbfe' },
{ name: 'Google Discover', value: 1100, color: '#bfdbfe' },
],
},
{ name: 'Yahoo!', value: 1300, color: '#93c5fd' },
],
},
{ name: '直接', value: 2700, color: '#94a3b8' },
{
name: 'SNS', color: '#a78bfa',
children: [
{ name: 'X', value: 1500, color: '#c4b5fd' },
{ name: 'Instagram', value: 700, color: '#c4b5fd' },
],
},
];
const options: ApexOptions = {
chart: { type: 'sunburst', toolbar: { show: false } },
plotOptions: {
sunburst: {
innerSize: '24%',
borderRadius: 4, // 扇の角丸
spacing: 2, // 扇どうしの隙間(px)
zoomOnClick: true, // 扇のクリックでその枝に拡大し、パンくずで戻る(既定で true)
// ラベルの色は1色だけ。塗りを明るめにそろえて濃い文字にする
dataLabels: { minAngleToShow: 18, style: { colors: ['#1e293b'], fontSize: '11px' } },
},
},
drilldown: { breadcrumb: { rootLabel: '全体' } }, // パンくずの先頭(既定は All)
legend: { position: 'bottom' }, // 凡例のクリックで1階層目の枝を外せる
tooltip: {
// 既定は割合を英語(of parent / of total)で足す。custom は hover した扇と割合を受け取る
custom: ({ node, percentOfParent, percentOfTotal }) =>
node
? `<div style="padding:6px 10px">${node.name}:<b>${node.value.toLocaleString()}</b><br/>親の ${percentOfParent}%・全体の ${percentOfTotal}%</div>`
: null,
},
};
export default function ApexSunburstChart() {
// react-apexcharts の型では type に sunburst が無い(v8 の新しい種別に型が追いついていない)。
// type を省くと既定の line で上書きされるので、値は渡したうえで型だけ外す
return <Chart type={'sunburst' as never} series={[{ data }]} options={options} height={360} width="100%" />;
}AIプロンプトテンプレート
React + TypeScript で、ApexCharts(apexcharts 8.7・react-apexcharts 2.2)を使ったサンバーストチャートを実装してください。
- 登録: v8 では sunburst が既定のバンドルに入っていないため、import 'apexcharts/sunburst' で登録すること
- 読み込み: react-apexcharts の Chart を使うコンポーネントを next/dynamic の ssr: false で読み込むこと
- 型: react-apexcharts の type に sunburst が無いため、type={'sunburst' as never} として値だけ渡すこと
- データ: Webサイトの月間セッション数を「流入チャネル → 参照元 → 詳細」の階層にし、series: [{ data }] に children の入れ子で渡すこと(name・value・color)
- 見た目: plotOptions.sunburst の innerSize・borderRadius・spacing で中心の穴・扇の角丸・隙間を付けること
- ドリルダウン: plotOptions.sunburst.zoomOnClick を有効にし、drilldown.breadcrumb.rootLabel でパンくずの先頭を「全体」にすること
- ラベル: dataLabels.minAngleToShow で細い扇のラベルを省き、dataLabels.style.colors で読める文字色を1色指定すること
- ツールチップ: tooltip.custom で node・percentOfParent・percentOfTotal を受け取り、「名前:値 セッション/親の◯%・全体の◯%」を日本語で表示することこのプロンプトはあくまでたたき台です。AIの回答はモデルやバージョン、会話の文脈によって毎回異なります。意図通りに動かない場合は、条件を追記・修正してお使いください。
2. Apache ECharts (echarts-for-react)
series の type: 'sunburst' に、children を入れ子にした階層データを渡す。扇をクリックするとその扇が中心に移り(nodeClick: 'rootToNode'、既定)、中心に出る円をクリックすると1つ上に戻る。
チャートを読み込み中...
操作:扇をクリックで中心へ/中心の「戻る」のクリックで1つ上へ/hover で祖先の扇だけを強調
formatter に渡る treePathInfo で、根からの経路(自然検索 › Google › Google 検索)を出せる。emphasis.focus: 'ancestor' で hover した扇の祖先以外を薄くでき、label.rotate: 'radial' で細い扇にも名前が収まりやすい。掘り下げたときに中心に出る円は levels[0] で見た目を変えられる(既定は名前の無いグレーの円)。兄弟は既定で値の大きい順に並べ替えられる(sort: 'desc')。 テーマは init() の引数で、切り替えるとインスタンスの再生成が必要なため、サイトのダークモードには追従しません(ライト表示で固定しています)。'use client';
// EChartsSunburstChart.tsx(echarts 6.1 / echarts-for-react 3.0)
import ReactECharts from 'echarts-for-react';
// 階層は children で入れ子にする。色は itemStyle.color で渡す
const data = [
{
name: '自然検索', itemStyle: { color: '#60a5fa' },
children: [
{
name: 'Google', itemStyle: { color: '#93c5fd' },
children: [
{ name: 'Google 検索', value: 7200, itemStyle: { color: '#bfdbfe' } },
{ name: 'Google Discover', value: 1100, itemStyle: { color: '#bfdbfe' } },
],
},
{ name: 'Yahoo!', value: 1300, itemStyle: { color: '#93c5fd' } },
],
},
{ name: '直接', value: 2700, itemStyle: { color: '#94a3b8' } },
{
name: 'SNS', itemStyle: { color: '#a78bfa' },
children: [
{ name: 'X', value: 1500, itemStyle: { color: '#c4b5fd' } },
{ name: 'Instagram', value: 700, itemStyle: { color: '#c4b5fd' } },
],
},
];
const option = {
tooltip: {
confine: true, // グラフの枠の中に収める
// treePathInfo に根からの経路が入る(先頭は名前の無い仮想の根)
formatter: (p: { value: number; treePathInfo: { name: string }[] }) =>
`${p.treePathInfo.slice(1).map((n) => n.name).join(' › ')}<br/><b>${p.value.toLocaleString()}</b> セッション`,
},
series: [
{
type: 'sunburst',
data,
radius: ['14%', '92%'],
nodeClick: 'rootToNode', // 扇のクリックでその扇を中心に(既定)。中心の円のクリックで1つ上へ
emphasis: { focus: 'ancestor' }, // hover した扇の祖先だけを強調し、ほかは薄くする
itemStyle: { borderRadius: 4, borderWidth: 2, borderColor: '#fafafa' },
label: { color: '#1e293b', fontSize: 11, minAngle: 12, rotate: 'radial' },
labelLayout: { hideOverlap: true }, // 重なったラベルは隠す
// levels[0] は掘り下げたときに中心に出る「戻る」円(既定は名前の無いグレーの円)
levels: [{ itemStyle: { color: '#e4e4e7' }, label: { formatter: '戻る', color: '#3f3f46', rotate: 0 } }],
},
],
};
export default function EChartsSunburstChart() {
return <ReactECharts option={option} style={{ height: 360 }} />;
}AIプロンプトテンプレート
React + TypeScript で、Apache ECharts(echarts 6.1・echarts-for-react 3.0)を使ったサンバーストチャートを実装してください。 - 使用ライブラリ: echarts-for-react の ReactECharts(next/dynamic の ssr: false で読み込む) - データ: Webサイトの月間セッション数を「流入チャネル → 参照元 → 詳細」の階層にし、series の type: 'sunburst' の data に children の入れ子で渡すこと。色は itemStyle.color で指定すること - ドリルダウン: nodeClick: 'rootToNode' で、クリックした扇を中心にすること。levels[0] で、掘り下げたときに中心に出る円を「戻る」と表示すること - 強調: emphasis.focus: 'ancestor' で、hover した扇の祖先だけを強調すること - ラベル: label.rotate: 'radial'・label.minAngle で細い扇でも読めるようにし、labelLayout.hideOverlap で重なったラベルを隠すこと - ツールチップ: tooltip.formatter で treePathInfo から「自然検索 › Google › Google 検索」のような経路と値を表示し、confine: true で枠の中に収めること - 角丸: itemStyle.borderRadius と borderWidth・borderColor で扇の角丸と隙間を付けること
このプロンプトはあくまでたたき台です。AIの回答はモデルやバージョン、会話の文脈によって毎回異なります。意図通りに動かない場合は、条件を追記・修正してお使いください。
3. Nivo (@nivo/sunburst)
ResponsiveSunburst に、children を持つ1つの根オブジェクトを渡す。ドリルダウンは組み込まれていないので、onClick で受け取った扇を state に入れ、data をその枝に差し替えて実装する(transitionMode="pushIn" で押し広げるように切り替わる)。
チャートを読み込み中...
操作:扇をクリックで掘り下げ/上のパンくずか中心のクリックで戻る
d.path に自分から根までの名前の並びが入るので、掘り下げる経路をそのまま作れる。layers に関数コンポーネントを足すと SVG の中に自由に描けるため、このデモでは中心の穴に表示中のノードの名前と合計を描き、クリックで1つ上に戻れるようにしている。輪は面積が揃うように内側ほど太くなる。ラベルは扇の中心に水平に置くだけで、収まらない名前を切り詰めたり回したりはしないので、arcLabelsSkipAngle で細い扇のラベルを省いている。色は colors にデータの色を返す関数を渡し、inheritColorFromParent={false} で親からの継承を切っている(根を差し替えても色が変わらないように)。 SVG の色を CSS で解決できる値で渡せるので、中心の文字(currentColor)・ツールチップ・扇の隙間をサイトのダークモードに追従させている。'use client';
// NivoSunburstChart.tsx(@nivo/sunburst 0.99)
import { useState } from 'react';
import { ResponsiveSunburst, type SunburstCustomLayerProps } from '@nivo/sunburst';
type Node = { name: string; value?: number; color: string; children?: Node[] };
// Nivo には根を1つだけ渡す
const root: Node = {
name: '全体', color: '#94a3b8',
children: [
{
name: '自然検索', color: '#60a5fa',
children: [
{
name: 'Google', color: '#93c5fd',
children: [
{ name: 'Google 検索', value: 7200, color: '#bfdbfe' },
{ name: 'Google Discover', value: 1100, color: '#bfdbfe' },
],
},
{ name: 'Yahoo!', value: 1300, color: '#93c5fd' },
],
},
{ name: '直接', value: 2700, color: '#94a3b8' },
{ name: 'SNS', color: '#a78bfa', children: [{ name: 'X', value: 1500, color: '#c4b5fd' }] },
],
};
// 根からの name の並びをたどって、表示中のノードを返す
const nodeAt = (path: string[]) =>
path.reduce<Node>((node, name) => node.children?.find((c) => c.name === name) ?? node, root);
export default function NivoSunburstChart() {
const [path, setPath] = useState<string[]>([]); // [] のときは全体を表示する
const current = nodeAt(path);
// 中心の穴に、表示中のノードの名前を描くレイヤー(fill: currentColor で文字色を継承する)
const Center = ({ centerX, centerY }: SunburstCustomLayerProps<Node>) => (
<text x={centerX} y={centerY} textAnchor="middle" dominantBaseline="central" style={{ fill: 'currentColor', fontSize: 13 }}>
{current.name}
</text>
);
return (
<div style={{ height: 360 }}>
{path.length > 0 && <button onClick={() => setPath(path.slice(0, -1))}>← 1つ上へ</button>}
<ResponsiveSunburst<Node>
data={current}
id="name"
value="value"
valueFormat={(v) => `${v.toLocaleString()} セッション`}
cornerRadius={4}
borderWidth={2}
borderColor="#ffffff" // 背景と同じ色にすると、扇の間が隙間に見える
// 根を差し替えても色が変わらないよう、データの色を使い、親からの継承を切る
colors={(d) => d.data.color}
inheritColorFromParent={false}
enableArcLabels
arcLabel="id"
arcLabelsSkipAngle={24} // ラベルは切り詰めも回転もしないので、細い扇では出さない
arcLabelsTextColor="#1e293b"
// d.path は自分から根までの name の並び。子を持つ扇なら、その扇を根にする
onClick={(d) => {
if (d.data.children) setPath([...path, ...d.path.slice(0, -1).reverse().map(String)]);
}}
transitionMode="pushIn"
layers={['arcs', 'arcLabels', Center]}
/>
</div>
);
}AIプロンプトテンプレート
React + TypeScript で、Nivo(@nivo/sunburst 0.99)を使ったサンバーストチャートを実装してください。
- 使用ライブラリ: @nivo/sunburst の ResponsiveSunburst(next/dynamic の ssr: false で読み込む)
- データ: Webサイトの月間セッション数を「流入チャネル → 参照元 → 詳細」の階層にし、children を持つ1つの根オブジェクトとして渡すこと(id="name"・value="value")
- ドリルダウン: Nivo には組み込まれていないので、onClick で受け取った扇の d.path(自分から根までの name の並び)を state に入れ、data をその枝に差し替えること。transitionMode="pushIn" で切り替えること
- 戻る: パンくず(押した階層まで戻る)と、中心の穴のクリックで1つ上に戻る操作を付けること
- 中心: layers にカスタムレイヤーを足し、中心の穴に表示中のノードの名前と合計を描くこと
- 色: 根を差し替えても色が変わらないよう、colors={(d) => d.data.color} と inheritColorFromParent={false} でデータの色を使うこと
- ラベル: enableArcLabels・arcLabel="id"・arcLabelsSkipAngle で、太い扇だけに名前を出すこと
- ダークモード: 中心の文字は fill: currentColor、ツールチップの背景は CSS 変数にして、サイトのダークモードに追従させることこのプロンプトはあくまでたたき台です。AIの回答はモデルやバージョン、会話の文脈によって毎回異なります。意図通りに動かない場合は、条件を追記・修正してお使いください。
4. @mantine/charts (SunburstChart)
Mantine 9.5 で加わった SunburstChart。children を持つ配列を渡す(中身は Recharts の SunburstChart)。ドリルダウンは組み込まれていないので、sunburstChartProps で Recharts の onClick を受け取り、表示する枝を state で切り替える。
チャートを読み込み中...
操作:扇をクリックで掘り下げ/上のパンくずか中心のクリックで戻る
withLabels で扇に出せるのは名前ではなく値で、ドキュメントにある valueFormatter もラベルには効かない(9.7.4 時点。ツールチップにだけ効く)。このデモではラベルを出さず、children に渡した SVG で中心に表示中のノードを描いている(children はグラフの SVG の中に描かれる)。並びは既定で3時の位置から反時計回りで、12時から時計回りにするには startAngle={90}・endAngle={-360} とする(endAngle は終わりの角度ではなく、startAngle から回す量として扱われる)。size は px の固定値なので、@mantine/hooks の useElementSize で置き場所の大きさに合わせている。 配色はMantine独自のカラースキーム(MantineProvider)で決まり、サイトのダークモード切替とは連動しないため、追従しません(ライト表示で固定しています)。'use client';
// MantineSunburstChart.tsx(@mantine/charts 9.7。SunburstChart は 9.5 から)
import { useState } from 'react';
import { MantineProvider } from '@mantine/core';
import { SunburstChart, type SunburstChartData } from '@mantine/charts';
import '@mantine/core/styles.css';
import '@mantine/charts/styles.css';
const data: SunburstChartData[] = [
{
name: '自然検索', color: '#60a5fa',
children: [
{
name: 'Google', color: '#93c5fd',
children: [
{ name: 'Google 検索', value: 7200, color: '#bfdbfe' },
{ name: 'Google Discover', value: 1100, color: '#bfdbfe' },
],
},
{ name: 'Yahoo!', value: 1300, color: '#93c5fd' },
],
},
{ name: '直接', value: 2700, color: '#94a3b8' },
{ name: 'SNS', color: '#a78bfa', children: [{ name: 'X', value: 1500, color: '#c4b5fd' }] },
];
// name までの経路(根からの name の並び)を探す。name はツリー全体で一意にしておく
const findPath = (list: SunburstChartData[], name: string): string[] => {
for (const node of list) {
if (node.name === name) return [node.name];
const rest = findPath(node.children ?? [], name);
if (rest.length) return [node.name, ...rest];
}
return [];
};
// 経路をたどって、表示する枝(子の配列)を返す
const childrenAt = (path: string[]) =>
path.reduce<SunburstChartData[]>((list, name) => list.find((n) => n.name === name)?.children ?? list, data);
export default function MantineSunburstChart() {
const [path, setPath] = useState<string[]>([]); // [] のときは全体を表示する
return (
<MantineProvider>
{path.length > 0 && <button onClick={() => setPath(path.slice(0, -1))}>← 1つ上へ</button>}
<SunburstChart
data={childrenAt(path)}
size={300}
gap={2}
// 12時の位置から時計回り。endAngle は終わりの角度ではなく、startAngle から回す量として扱われる
startAngle={90}
endAngle={-360}
valueFormatter={(v) => `${v.toLocaleString()} セッション`} // ツールチップにだけ効く
// クリックは recharts の SunburstChart に渡す(node は data の要素)
sunburstChartProps={{
onClick: (node) => {
if (node.children?.length) setPath(findPath(data, node.name));
},
}}
/>
</MantineProvider>
);
}AIプロンプトテンプレート
React + TypeScript で、@mantine/charts(9.7)の SunburstChart を使ったサンバーストチャートを実装してください。
- 使用ライブラリ: @mantine/charts の SunburstChart(9.5 から)。MantineProvider でラップし、@mantine/core/styles.css と @mantine/charts/styles.css を読み込むこと
- データ: Webサイトの月間セッション数を「流入チャネル → 参照元 → 詳細」の階層にし、children を持つ配列で渡すこと(name・value・color)
- ドリルダウン: 組み込まれていないので、sunburstChartProps の onClick(Recharts の SunburstChart に渡る)でクリックされた扇を受け取り、表示する枝を state で切り替えること
- 戻る: パンくずと、中心のクリックで1つ上に戻る操作を付けること(children に渡した SVG はグラフの中に描かれる)
- 向き: startAngle={90}・endAngle={-360} で12時の位置から時計回りに並べること(endAngle は startAngle から回す量として扱われる)
- 大きさ: size は px の固定値なので、@mantine/hooks の useElementSize で置き場所の幅に合わせること
- ツールチップ: valueFormatter で「7,200 セッション」と表示することこのプロンプトはあくまでたたき台です。AIの回答はモデルやバージョン、会話の文脈によって毎回異なります。意図通りに動かない場合は、条件を追記・修正してお使いください。
ライブラリ比較
| ライブラリ | レンダリング | ドリルダウン(戻り方) | 扇のラベル | ダークモード |
|---|---|---|---|---|
| ApexCharts | SVG | ◎ 組み込み(パンくず・中心の輪) | 扇に沿って曲げる・切り詰める | ライト固定 |
| ECharts | Canvas | ◎ 組み込み(中心の円) | 放射状・接線方向に回せる | ライト固定 |
| Nivo | SVG | ◯ onClick + state で作る | 水平のみ(細い扇は省く) | ✓ 追従 |
| @mantine/charts | SVG | ◯ onClick + state で作る | 値のみ(withLabels) | ライト固定 |
選択のポイント
- •ApexCharts: 拡大・パンくず・凡例での枝の出し入れまで組み込まれていて、設定だけでドリルダウンできる。ラベルが扇に沿って曲がり、角丸・隙間も付けやすい。v8 では
apexcharts/sunburstの import を忘れないこと。 - •ECharts: ドリルダウンが組み込みで、ツールチップに経路を出す・祖先だけを強調するなど、階層をたどる表現が豊富。Canvas 描画なので扇の数が多くても軽い。
- •Nivo: ドリルダウンは自分で作るが、
onClickとlayersで中心の表示やパンくずを自由に組める。SVG で色を CSS から渡せるので、サイトのダークモードに合わせたい場合に向く。 - •@mantine/charts: Mantine を導入済みなら追加の設定がほぼ要らない。ドリルダウンと名前のラベルは無いので、扇の名前はツールチップか自前の表示で補う。
典型的な使用例
- 🌐流入経路の内訳:チャネル → 参照元 → ページの順に、どこからの訪問が多いかをたどる
- 💰予算・コストの内訳:部門 → 費目 → 明細の順に、支出の大きいところを掘り下げる
- 💾ストレージ使用量:フォルダ → サブフォルダ → ファイルの順に、容量を食っている場所を探す
- 🏢組織・売上の構成:事業部 → 部署 → チームなど、入れ子の構成比を一枚で見せる
⚠️ 使用時の注意点
- •階層が深いほど外側の輪が細くなり、扇もラベルも読みにくくなる。一度に見せるのは3階層程度にし、それより深い部分はドリルダウンで見せる
- •細い扇のラベルは重なりやすく、狭い画面では外側の輪に収まらない。ラベルは太い扇だけにして、詳細はツールチップに任せる
- •Recharts(Mantine の中身)と Nivo は name を React の key に使うため、別の枝に同じ名前(例:「その他」)があると重複 key の警告が出る。Mantine ではツールチップも同じ名前の別の扇の位置に出る。名前はツリー全体で重複させない
- •角度の比較は長さや位置の比較より読み取りにくい。値を正確に比べたい場合は棒グラフ、全体の構成比だけなら円グラフやツリーマップと使い分ける
サンバーストチャート(Sunburst Chart)は、階層データを同心円で表すグラフ。中心から外へ1階層ずつ輪を重ね、扇の角度で値の大きさを、親の扇の外側に子の扇を並べることで階層の関係を示す。
Webサイトの流入経路・予算やコストの内訳・ストレージの使用量・組織の構成など、全体に占める割合を階層ごとにたどりたい場面で使われる。
- •基本型(全階層を表示)
- •ドリルダウン型(クリックで掘り下げ)
- •ドーナツ型(中心に合計を表示)
- •ラベル付き型
- •扇形型(半円などに開く)
📊 使用しているサンプルデータ(クリックで表示)
あるWebサイトの1か月のセッション数(合計 20,000)を、流入チャネル → 参照元 → 詳細の3階層にしたデータです。「直接」だけは子を持たないため、階層の深さが枝ごとに違います。4つのデモで同じデータ・同じ色を使っています。