ワンクリックで
dataviz
Build D3 visualizations with Bostock's patterns, Tufte's integrity, Few's clarity, and production-grade frontend craft.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Build D3 visualizations with Bostock's patterns, Tufte's integrity, Few's clarity, and production-grade frontend craft.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | dataviz |
| description | Build D3 visualizations with Bostock's patterns, Tufte's integrity, Few's clarity, and production-grade frontend craft. |
Build data visualizations that are technically correct, visually honest, and worth looking at. Combines Bostock (D3 patterns), Tufte (graphical integrity), Few (information design), and frontend craft into one workflow.
No arguments? Describe this skill and stop. Do not execute.
Answer these questions. They determine everything.
If you can't name the decision, you don't need a visualization — you need a table. Every chart exists to help someone decide or understand something specific.
| Relationship | Best Display | Never Use |
|---|---|---|
| Comparison | Horizontal bar (sorted) | Pie, radar |
| Trend over time | Line, area, sparkline | Bar (unless discrete periods) |
| Distribution | Histogram, strip plot, box plot | Pie |
| Part-to-whole | Stacked bar, treemap | 3D pie, donut |
| Correlation | Scatter plot | Bubble (unless 3rd variable) |
| KPI vs target | Bullet graph | Gauge, dial, speedometer |
| Geographic | Choropleth, proportional symbol | 3D globe |
| Current status | Indicator + value | Animation |
Pick the simplest display that answers the question. If a table works, use a table.
| Audience | Precision | Context | Density |
|---|---|---|---|
| Executive | Trends, not decimals ($1.2M) | vs target, vs prior period | Low — 5 metrics max |
| Analyst | Full precision ($1,234,567) | Drill-down, filter, compare | High — coordinated views |
| Operations | Current state, real-time | Alerts, thresholds | Medium — status indicators |
Define the data shape before touching SVG. The data drives everything.
// Define your data contract
const data = [
{ id: "q1", date: new Date("2024-01"), value: 42, category: "A" }
];
// Derive domains from data, never hardcode
const x = d3.scaleTime()
.domain(d3.extent(data, d => d.date))
.range([0, width]);
d3.extent, d3.max), not magic numbers.data(data, d => d.id))scaleSqrt() for area encodings — never scaleLinear() on radiusEvery chart starts here:
const margin = { top: 20, right: 30, bottom: 40, left: 50 };
const width = containerWidth - margin.left - margin.right;
const height = containerHeight - margin.top - margin.bottom;
const svg = d3.select(el).append("svg")
.attr("viewBox", `0 0 ${containerWidth} ${containerHeight}`)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
viewBox for responsive sizing, not fixed width/height attributeswidth/heightBinddata to elements. Handle all three states.
svg.selectAll("rect")
.data(data, d => d.id) // Key function — always
.join(
enter => enter.append("rect")
.attr("x", d => x(d.date))
.attr("height", 0)
.call(enter => enter.transition()
.attr("height", d => height - y(d.value))),
update => update
.call(update => update.transition()
.attr("x", d => x(d.date))
.attr("height", d => height - y(d.value))),
exit => exit
.call(exit => exit.transition()
.attr("height", 0)
.remove())
);
.data() call when data changesselectAll("*").remove() redrawsLie Factor must be 0.95–1.05. Check these:
| Rule | Violation | Fix |
|---|---|---|
| Bar charts start at zero | Truncated Y-axis exaggerates | domain([0, max]) |
| Area = sqrt encoding | Linear radius on bubbles → 4x visual error | d3.scaleSqrt() |
| Consistent scales | Small multiples with different Y ranges | Shared domain across panels |
| No dual Y-axes | Misleading correlation | Two charts or normalize |
Data-ink ratio > 70%. Remove:
| Remove | Replace with |
|---|---|
| Heavy gridlines | Light gray (#e5e5e5) at 0.5px, or none |
| Axis domain lines | Just ticks, or nothing |
| Borders/boxes | White space |
| Legends | Direct labels on data |
| Background colors | White/transparent |
| 3D effects | Nothing (2D always) |
Direct labeling over legends. Put the label at the data point:
// Label on the line, not in a box somewhere
const lastPoint = data[data.length - 1];
svg.append("text")
.attr("x", x(lastPoint.date) + 4)
.attr("y", y(lastPoint.value))
.attr("dy", "0.35em")
.text(lastPoint.category);
Default: grayscale. Color is a scarce resource.
// Base palette: grays
const base = "#333"; // text, primary data
const muted = "#999"; // secondary, axes
const light = "#e5e5e5"; // gridlines, borders
// Color ONLY for meaning
const alert = "#d32f2f"; // bad / below target
const success = "#388e3c"; // good / above target
const highlight = "#1565c0"; // selected / focus
Charts are read, not just seen. Typography matters.
d3.format("$.2s") for executives (→ $1.2M), d3.format("$,.0f") for analysts (→ $1,234,568).// Tick formatting
xAxis.tickFormat(d3.timeFormat("%b")); // "Jan", "Feb"
yAxis.tickFormat(d3.format("$.2s")); // "$1.2M"
yAxis.ticks(5); // Don't crowd
viewBox on SVG, no fixed dimensionsrole="img" + aria-label on SVG container<title> and <desc> elements inside SVGprefers-reduced-motion check before transitionsprefers-color-scheme for dark mode supportconst prefersReducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)"
).matches;
const t = svg.transition()
.duration(prefersReducedMotion ? 0 : 750);
If this chart will be used more than once, extract it:
function bulletChart() {
let width = 300;
let height = 30;
let ranges = [0.5, 0.75, 1.0];
function chart(selection) {
selection.each(function(d) {
// Build chart using width, height, ranges, d
});
}
chart.width = function(v) {
return arguments.length ? (width = v, chart) : width;
};
chart.height = function(v) {
return arguments.length ? (height = v, chart) : height;
};
chart.ranges = function(v) {
return arguments.length ? (ranges = v, chart) : ranges;
};
return chart;
}
// Usage
d3.selectAll(".bullet").datum(d => d).call(bulletChart().width(400));
Closure pattern, not classes. Getter-setter methods. Callable via selection.call().
If building a dashboard, not a single chart:
| Anti-Pattern | Why | Do Instead |
|---|---|---|
| Pie charts | Humans are bad at comparing angles | Horizontal bar, sorted |
| Gauges/dials | One number, massive footprint | Bullet graph |
| 3D anything | Distorts perception, always | 2D |
| Rainbow color scales | No perceptual ordering | Sequential single-hue or diverging |
| Dual Y-axes | Implies false correlation | Two charts or normalize |
| Full redraws on update | Destroys transitions, wastes DOM | Data joins with enter/update/exit |
| Index-based data joins | Elements track position, not data | Key functions: .data(d, d => d.id) |
| Decorative animation | Slows comprehension | Transition only on data change |
| Legend for <8 series | Forces cognitive round-trip | Direct labels |
| Area encoding with linear scale | 4x visual exaggeration | scaleSqrt() |
Integrity
Clarity
Craft
prefers-reduced-motion respectedDashboard (if applicable)