D3は1つのhierarchyからtreemapとsunburstを描き分ける
Treemapとsunburstは、同じ階層データを違う形で見せることが多いです。
ここで、矩形用と円弧用に別々のdata pipelineを作ると、filter、sort、集計、drill-downが少しずつズレます。最初は動いても、片方だけ修正されて、2つのviewが違う答えを返すようになります。
階層構築は1回の共通関数にまとめ、layoutだけを分けます。
症状: 2つのchartが食い違う
よくある症状です。
- treemapとsunburstの合計値が違う
- filterが片方にしか効かない
- breadcrumbが一致しない
- export結果のnode数が違う
- bug fixを2箇所に入れる必要がある
同じtreeを表すなら、tree constructionは共有すべきです。
hierarchy構築とlayoutを分ける
viewに依存しないbuilderを作ります。
import { hierarchy, type HierarchyNode } from "d3-hierarchy";
type TreeItem = {
name: string;
value?: number;
children?: TreeItem[];
};
export function buildHierarchy(data: TreeItem[]): HierarchyNode<TreeItem> {
return hierarchy({ name: "root", children: data })
.sum((node) => node.value ?? 0)
.sort((a, b) => (b.value ?? 0) - (a.value ?? 0));
}
この関数には、filter、grouping、label、value計算を入れます。treemapの座標やsunburstの角度は入れません。
layoutはviewごとに適用する
Treemap側です。
import { treemap } from "d3-hierarchy";
export function buildTreemap(data: TreeItem[], width: number, height: number) {
const root = buildHierarchy(data);
return treemap<TreeItem>()
.size([width, height])
.paddingInner(2)(root);
}
Sunburst側です。
import { partition } from "d3-hierarchy";
export function buildSunburst(data: TreeItem[], radius: number) {
const root = buildHierarchy(data);
return partition<TreeItem>()
.size([2 * Math.PI, radius])(root);
}
D3 layoutはnodeに座標情報をmutateします。だから同じroot objectを共有せず、viewごとにrootを作ります。
drill-downはpathで保持する
D3 node objectをそのままずっと保持すると、filterやdata更新後にstaleになります。
安定したpathを持ちます。
type DrillState = {
path: string[];
};
再描画時に、新しいhierarchyから探し直します。
function findByPath(root: HierarchyNode<TreeItem>, path: string[]) {
let current: HierarchyNode<TreeItem> | undefined = root;
for (const name of path) {
current = current.children?.find((child) => child.data.name === name);
if (!current) return null;
}
return current;
}
これで、filter変更後もbreadcrumbとdrill targetが一致します。
exportではCSS変数を解決する
SVGが var(--color-section) のようなCSS変数を使っている場合、アプリ外で開いたSVGの色が失われることがあります。
export前に次を行います。
- SVGをcloneする
- descendantを走査する
getComputedStyle()を読むfill、stroke、colorをinline化するwidth、height、xmlnsを明示するXMLSerializerでserializeする
可視化ツールでは、export品質も製品価値の一部です。
検証チェック
- treemapとsunburstの合計値が一致する
- 同じfilterが両方に効く
- filter後のnode数が一致する
- data更新後もbreadcrumbが壊れない
- SVG exportの色がアプリ外でも残る
- PNG exportがドキュメント貼り付けに耐える解像度になる