本文へ移動
cccskills
無料GitHub で公開

web-dataviz-d3

D3.js data visualization — selections, data joins, scales, axes, shapes, transitions, force layouts, geo projections, framework integration

インストール方法を見る

含まれるファイル(6)

  • SKILL.md15.2 KB
  • examples/advanced.md11.4 KB
  • examples/core.md12.2 KB
  • examples/interaction.md9.7 KB
  • metadata.yaml480 B
  • reference.md6.3 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

D3.js Patterns

Quick Guide: D3 v7 is pure ES modules, so import from the individual packages (d3-selection, d3-scale, d3-shape) rather than the d3 bundle. selection.join() replaces manual enter/update/exit chains. Scales map a data domain to a visual range, axes render tick marks from a scale, and shape generators turn data arrays into SVG path strings. d3-transition extends the selection prototype by side effect, so .transition() does not exist until it is imported. The largest decision is who owns the DOM — D3, or the component framework around it.

Detailed Resources:

  • examples/core.md — selections, data joins, scales, axes, shape generators, responsive SVG, the margin convention
  • examples/interaction.md — transitions, zoom, brush, drag, tooltips
  • examples/advanced.md — force layouts, geo projections, framework integration, TypeScript typing
  • reference.md — module table, scale selection guide, shape generator signatures

Which path applies

  • D3 owns the DOM. Needed for zoom, brush, drag and force tick, which all attach listeners and write attributes on their own schedule. Hand D3 a ref to one SVG element, call it from a mount hook, and return a cleanup that stops simulations and behaviours. Follow examples/core.md and examples/interaction.md.
  • D3 computes, the framework renders. For static charts, D3 supplies scales, layouts and path strings while the framework emits the SVG declaratively. Cleaner, and nothing has to be cleaned up. Follow examples/advanced.md Pattern 3.

Two systems writing the same elements is the failure both branches avoid — pick one owner per SVG subtree.


<critical_requirements>

Before writing D3 code

Join data with selection.join(). One call covers enter, update and exit, where the manual enter().append().merge() chain silently drops updates whenever .merge() is forgotten.

Import from the individual modules — d3-selection, d3-scale, d3-shape. import * as d3 from "d3" pulls 240KB+ of packages the chart never calls, and none of it tree-shakes.

Pass a key function to .data(array, key) whenever elements have identity. Without one D3 binds by index, so a sort or a removal re-binds every element to the wrong datum.

Type selections and scales through their generics — Selection<SVGRectElement, Datum, ...>, ScaleLinear<number, number>. D3's defaults widen to any at the first untyped selectAll, and the datum type is what makes accessor callbacks checkable.

</critical_requirements>


Auto-detection: d3, d3-selection, d3-scale, d3-shape, d3-axis, d3-transition, d3-force, d3-geo, d3-zoom, d3-brush, d3-drag, d3-array, d3-scale-chromatic, d3-hierarchy, selection.join, scaleLinear, scaleBand, scaleTime, scaleOrdinal, axisBottom, axisLeft, forceSimulation, forceManyBody, geoPath, geoMercator, curveMonotoneX, PieArcDatum, D3ZoomEvent

Applies to:

  • Custom SVG and Canvas visualizations built from primitives
  • Binding data arrays to DOM elements — the data join
  • Mapping data domains to pixel ranges, and rendering axes from those scales
  • Generating SVG path strings from data: lines, areas, arcs, pies, stacks
  • Animating attribute changes with interpolation and easing
  • Force-directed graph layouts and geographic projections
  • Zoom, brush and drag behaviours
  • Giving a component framework computed geometry to render

Handled elsewhere:

  • Standard bar, line and pie charts with little customization — a component layer that ships chart types out of the box settles those, and this skill is the primitive toolkit underneath it
  • Dashboard composition and widget layout — arranging many charts on a page is not a visualization primitive
  • Which colours a product uses — d3-scale-chromatic supplies interpolators and schemes, and the palette they are fed is settled by whatever owns the visual language
  • Accessibility conformance targets — an SVG takes role, aria-label and <title> like any element, and which level a product must meet is settled elsewhere

<philosophy>

D3 is a visualization grammar rather than a charting library: primitives for binding data to elements and deriving visual attributes from it. Maximum control, more code.

The pipeline is select → bind → join → encode → annotate → animate. Scales are the hinge — everything visual is a function of data through a scale, so a chart that hardcodes pixel arithmetic has skipped the one abstraction D3 exists to provide.

</philosophy>

<decision_framework>

Which modules to install

Bar / line / area chart  -> d3-selection, d3-scale, d3-axis, d3-shape, d3-array
Pie / donut chart        -> d3-shape (pie + arc), d3-scale
Force-directed graph     -> d3-force, d3-selection, d3-drag
Geographic map           -> d3-geo, d3-selection, d3-scale
Animated updates         -> d3-transition, d3-ease, d3-interpolate
Zoom or brush            -> d3-zoom or d3-brush, d3-selection
Tree / treemap / pack    -> d3-hierarchy, d3-selection

Scale-by-data-type is a lookup rather than a decision — see reference.md.

</decision_framework>


<patterns>

Core patterns

Pattern 1: Selections and the data join

Bind an array to elements, then let .join() create, update and remove them as the array changes.

import { select } from "d3-selection";

select(svgElement)
  .selectAll<SVGRectElement, BarData>("rect")
  .data(data, (d) => d.id) // key function binds by identity, not index
  .join("rect")
  .attr("y", (_, i) => i * (BAR_HEIGHT + BAR_GAP))
  .attr("width", (d) => xScale(d.value))
  .attr("height", BAR_HEIGHT);

.join() also takes three callbacks when enter, update and exit need distinct animations.

Full code: examples/core.md


Pattern 2: Scales — mapping data to pixels

A scale is a function from a data domain to a visual range. Continuous data takes scaleLinear, categories take scaleBand, dates take scaleTime.

import { scaleLinear, scaleBand } from "d3-scale";
import { max } from "d3-array";

const x = scaleLinear<number>()
  .domain([0, max(data, (d) => d.value) ?? 0])
  .range([0, innerWidth])
  .nice(); // rounds the domain to clean tick values

const y = scaleBand<string>()
  .domain(data.map((d) => d.label))
  .range([0, innerHeight])
  .padding(0.2); // y.bandwidth() is then the computed bar width

Full code: examples/core.md


Pattern 3: Axes — tick marks generated from a scale

An axis is a generator that renders into a <g> via selection.call(), so it stays in sync with the scale it was built from.

import { axisBottom, axisLeft } from "d3-axis";
import { format } from "d3-format";

const xAxisGroup = svg
  .append("g")
  .attr("transform", `translate(0,${innerHeight})`)
  .call(axisBottom(xScale).ticks(TICK_COUNT).tickFormat(format(",.0f")));

// On update, call the axis on the SAME group — appending a new one duplicates ticks
xAxisGroup
  .transition()
  .duration(TRANSITION_DURATION_MS)
  .call(axisBottom(xScale));

Full code: examples/core.md


Pattern 4: Shape generators — SVG paths from data

line, area, arc, pie and stack are configured once with accessors, then called with data to produce a d string.

import { line, arc, pie, curveMonotoneX } from "d3-shape";
import type { PieArcDatum } from "d3-shape";

const lineGen = line<TimeSeriesPoint>()
  .x((d) => xScale(d.date))
  .y((d) => yScale(d.value))
  .curve(curveMonotoneX);

const pieGen = pie<SliceData>()
  .value((d) => d.value)
  .sort(null);
const arcGen = arc<PieArcDatum<SliceData>>()
  .innerRadius(INNER_RADIUS)
  .outerRadius(OUTER_RADIUS);

Full code: examples/core.md


Pattern 5: The margin convention and responsive sizing

Reserve space for axes with a margin object and an offset inner <g>; size with viewBox so the SVG scales without a resize listener.

const MARGIN = { top: 20, right: 30, bottom: 40, left: 50 } as const;
const innerWidth = SVG_WIDTH - MARGIN.left - MARGIN.right;
const innerHeight = SVG_HEIGHT - MARGIN.top - MARGIN.bottom;

const svg = select(container)
  .append("svg")
  .attr("viewBox", `0 0 ${SVG_WIDTH} ${SVG_HEIGHT}`)
  .style("width", "100%");

const chart = svg
  .append("g")
  .attr("transform", `translate(${MARGIN.left},${MARGIN.top})`);

ResizeObserver is the escalation, for charts that must re-layout rather than scale.

Full code: examples/core.md


Pattern 6: Transitions — animated updates

Transitions interpolate numeric attributes and colours over a duration. Give enter, update and exit their own behaviour.

import "d3-transition"; // side-effect import: adds .transition() to selections
import { easeCubicOut } from "d3-ease";

selection.join(
  (enter) =>
    enter
      .append("rect")
      .attr("height", 0)
      .call((s) =>
        s
          .transition()
          .duration(TRANSITION_DURATION_MS)
          .ease(easeCubicOut)
          .delay((_, i) => i * STAGGER_DELAY_MS)
          .attr("height", (d) => innerHeight - yScale(d.value)),
      ),
  (update) =>
    update.call((s) =>
      s
        .transition()
        .duration(TRANSITION_DURATION_MS)
        .attr("height", (d) => innerHeight - yScale(d.value)),
    ),
  (exit) =>
    exit.call((s) =>
      s
        .transition()
        .duration(TRANSITION_DURATION_MS)
        .attr("height", 0)
        .remove(),
    ),
);

Full code: examples/interaction.md


Pattern 7: Zoom and pan

zoom() writes a transform on every wheel and drag. Apply it to an inner group — transforming the SVG root moves the viewport and clips panned content.

import { zoom } from "d3-zoom";
import type { D3ZoomEvent } from "d3-zoom";

const zoomBehavior = zoom<SVGSVGElement, unknown>()
  .scaleExtent([MIN_ZOOM, MAX_ZOOM])
  .on("zoom", (event: D3ZoomEvent<SVGSVGElement, unknown>) => {
    select(chartGroup).attr("transform", event.transform.toString());
  });

select(svgEl).call(zoomBehavior);

Semantic zoom rescales the axes instead — event.transform.rescaleX(xScale).

Full code: examples/interaction.md


Pattern 8: Brush and drag

brushX returns a pixel range; .invert() turns it back into data values. drag reports positions and needs function rather than an arrow when the handler uses this.

import { brushX } from "d3-brush";
import type { D3BrushEvent } from "d3-brush";

const brush = brushX<unknown>()
  .extent([
    [0, 0],
    [innerWidth, innerHeight],
  ])
  .on("end", (event: D3BrushEvent<unknown>) => {
    if (!event.selection) return; // brush was cleared
    const [x0, x1] = event.selection as [number, number];
    onBrush([xScale.invert(x0), xScale.invert(x1)]);
  });

Full code: examples/interaction.md


Pattern 9: Force-directed graph layout

A simulation mutates x/y on the node objects each tick; the tick handler copies them onto elements. It runs on requestAnimationFrame until stopped.

import {
  forceSimulation,
  forceLink,
  forceManyBody,
  forceCenter,
  forceCollide,
} from "d3-force";

const simulation = forceSimulation<GraphNode>(nodes)
  .force(
    "link",
    forceLink<GraphNode, GraphLink>(links)
      .id((d) => d.id)
      .distance(LINK_DISTANCE),
  )
  .force("charge", forceManyBody().strength(CHARGE_STRENGTH))
  .force("center", forceCenter(width / 2, height / 2))
  .force("collide", forceCollide(COLLISION_RADIUS))
  .on("tick", () => {
    nodeElements.attr("cx", (d) => d.x!).attr("cy", (d) => d.y!);
  });

return () => simulation.stop(); // cleanup, called on unmount

Full code: examples/advanced.md


Pattern 10: Geographic projections

A projection converts longitude/latitude to pixels; geoPath turns projected GeoJSON into d strings. fitSize derives scale and translate from the data.

import { geoNaturalEarth1, geoPath } from "d3-geo";

const projection = geoNaturalEarth1().fitSize(
  [CHART_WIDTH, CHART_HEIGHT],
  geoData,
);
const pathGenerator = geoPath().projection(projection);

svg
  .selectAll("path")
  .data(geoData.features)
  .join("path")
  .attr("d", pathGenerator)
  .attr("fill", (d) => colorScale(dataByRegion.get(d.properties.id) ?? 0));

Full code: examples/advanced.md


Pattern 11: Handing computed geometry to a framework

For static charts, expose the scales and path strings and let the framework emit the SVG. Nothing to clean up, and the datum types flow into the template.

function chartGeometry(data: DataPoint[], width: number, height: number) {
  const xScale = scaleBand<string>()
    .domain(data.map((d) => d.label))
    .range([0, width])
    .padding(0.2);
  const yScale = scaleLinear<number>()
    .domain([0, max(data, (d) => d.value) ?? 0])
    .range([height, 0])
    .nice();
  return { xScale, yScale };
}

// The framework then renders: <rect x={xScale(d.label)} width={xScale.bandwidth()} />

Full code: examples/advanced.md

</patterns>

<red_flags>

Red flags

Breaks at runtime:

  • .transition() without import "d3-transition" — TypeError, the method is added by side effect and does not exist otherwise
  • Mutating an array already bound to a selection — D3 stores the reference, so the next render reads values that were never joined
  • svg.append("g").call(axis) on every update — a new axis group each time, overlapping tick marks
  • A force simulation left running after unmount — requestAnimationFrame keeps writing to detached nodes; return simulation.stop() as cleanup
  • An arrow function in a handler that calls select(this) — this is the enclosing scope, not the element; use function or event.currentTarget
  • Exit transitions without .remove() — elements finish animating and stay in the DOM

Surprising behaviour:

  • .join("rect") returns the merged enter+update selection, so attributes chained after it apply to new and existing elements alike
  • max() and extent() return undefined for an empty array — guard with ?? 0 before feeding a domain
  • .duration() is per element, not total: 750ms across 100 elements is still 750ms
  • A zoom transform on the SVG root moves the viewport and clips panned content; it belongs on an inner <g>
  • geoPath() with no projection renders coordinates as-is, which is correct only for pre-projected GeoJSON
  • .datum() binds one object to one element and computes no join; .data() is for arrays
  • scaleBand().bandwidth() is the computed band width — reading it beats hardcoding a bar width that stops matching when the category count changes
  • Without .nice() a linear domain ends wherever the data does, giving ticks like [0, 473]
  • Only numbers and colours interpolate; class names and boolean attributes jump at the end of the transition

</red_flags>

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Hugging Face Inference SDK patterns for TypeScript/Node.js — InferenceClient setup, chat completion, text generation, streaming, embeddings, image generation, audio transcription, translation, summarization, and Inference Endpoints

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

LiteLLM proxy server setup, TypeScript client patterns via OpenAI SDK, model routing, fallbacks, load balancing, spend tracking, virtual keys, and production deployment

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

Serverless GPU compute platform for AI model deployment — web endpoints, GPU functions, model serving, and TypeScript client patterns

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

Local LLM inference with the Ollama JavaScript client -- chat, streaming, tool calling, vision, embeddings, structured output, model management, and OpenAI-compatible endpoint

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

Replicate SDK patterns for TypeScript/Node.js -- client setup, predictions, streaming, webhooks, file handling, model versioning, deployments, and training

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

Together AI SDK patterns for TypeScript — client setup, chat completions, streaming, structured output, function calling, embeddings, image generation, fine-tuning, and OpenAI-compatible endpoints

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

agents-inc のスキルをすべて見る

このスキルの問題を報告する