d3js-data-visualization — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited d3js-data-visualization (Agent Skill) and scored it 96/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 1 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Build sophisticated, interactive data visualizations using d3.js (Data-Driven Documents). D3 binds data to DOM elements and applies data-driven transformations to produce publication-quality, fully customizable visuals.
Consider alternatives for:
No backend required. Runs entirely in the browser or Node.js (with jsdom/canvas).
# Install via npm
npm install d3
# Or use CDN in HTML
<script src="https://d3js.org/d3.v7.min.js"></script>import * as d3 from 'd3';Every d3 visualization follows this pattern:
function drawChart(data) {
if (!data || data.length === 0) return;
const svg = d3.select('#chart');
svg.selectAll("*").remove(); // clear previous render
const width = 800, height = 400;
const margin = { top: 20, right: 30, bottom: 40, left: 50 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// Define scales
const xScale = d3.scaleLinear().domain([0, d3.max(data, d => d.x)]).range([0, innerWidth]);
const yScale = d3.scaleLinear().domain([0, d3.max(data, d => d.y)]).range([innerHeight, 0]);
// Axes
g.append("g").attr("transform", `translate(0,${innerHeight})`).call(d3.axisBottom(xScale));
g.append("g").call(d3.axisLeft(yScale));
// Data elements
g.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => xScale(d.x))
.attr("cy", d => yScale(d.y))
.attr("r", 5)
.attr("fill", "steelblue");
}const xScale = d3.scaleBand().domain(data.map(d => d.category)).range([0, innerWidth]).padding(0.1);
const yScale = d3.scaleLinear().domain([0, d3.max(data, d => d.value)]).range([innerHeight, 0]);
g.selectAll("rect")
.data(data)
.join("rect")
.attr("x", d => xScale(d.category))
.attr("y", d => yScale(d.value))
.attr("width", xScale.bandwidth())
.attr("height", d => innerHeight - yScale(d.value))
.attr("fill", "steelblue");const line = d3.line()
.x(d => xScale(d.date))
.y(d => yScale(d.value))
.curve(d3.curveMonotoneX);
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 2)
.attr("d", line);g.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => xScale(d.x))
.attr("cy", d => yScale(d.y))
.attr("r", d => sizeScale(d.size))
.attr("fill", d => colorScale(d.category))
.attr("opacity", 0.7);const pie = d3.pie().value(d => d.value).sort(null);
const arc = d3.arc().innerRadius(0).outerRadius(Math.min(width, height) / 2 - 20);
const colorScale = d3.scaleOrdinal(d3.schemeCategory10);
const g = svg.append("g").attr("transform", `translate(${width / 2},${height / 2})`);
g.selectAll("path")
.data(pie(data))
.join("path")
.attr("d", arc)
.attr("fill", (d, i) => colorScale(i))
.attr("stroke", "white")
.attr("stroke-width", 2);const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id).distance(100))
.force("charge", d3.forceManyBody().strength(-300))
.force("center", d3.forceCenter(width / 2, height / 2));
const link = g.selectAll("line").data(links).join("line").attr("stroke", "#999");
const node = g.selectAll("circle").data(nodes).join("circle")
.attr("r", 8).attr("fill", "steelblue")
.call(d3.drag()
.on("start", (e) => { if (!e.active) simulation.alphaTarget(0.3).restart(); e.subject.fx = e.subject.x; e.subject.fy = e.subject.y; })
.on("drag", (e) => { e.subject.fx = e.x; e.subject.fy = e.y; })
.on("end", (e) => { if (!e.active) simulation.alphaTarget(0); e.subject.fx = null; e.subject.fy = null; }));
simulation.on("tick", () => {
link.attr("x1", d => d.source.x).attr("y1", d => d.source.y)
.attr("x2", d => d.target.x).attr("y2", d => d.target.y);
node.attr("cx", d => d.x).attr("cy", d => d.y);
});// data: [{ row, column, value }, ...]
const rows = [...new Set(data.map(d => d.row))];
const cols = [...new Set(data.map(d => d.column))];
const xScale = d3.scaleBand().domain(cols).range([0, innerWidth]).padding(0.01);
const yScale = d3.scaleBand().domain(rows).range([0, innerHeight]).padding(0.01);
const colorScale = d3.scaleSequential(d3.interpolateYlOrRd).domain([0, d3.max(data, d => d.value)]);
g.selectAll("rect")
.data(data)
.join("rect")
.attr("x", d => xScale(d.column))
.attr("y", d => yScale(d.row))
.attr("width", xScale.bandwidth())
.attr("height", yScale.bandwidth())
.attr("fill", d => colorScale(d.value));const tooltip = d3.select("body").append("div")
.style("position", "absolute")
.style("visibility", "hidden")
.style("background", "white")
.style("border", "1px solid #ddd")
.style("padding", "10px")
.style("border-radius", "4px")
.style("pointer-events", "none");
elements
.on("mouseover", (event, d) => tooltip.style("visibility", "visible").html(`<strong>${d.label}</strong><br/>Value: ${d.value}`))
.on("mousemove", (event) => tooltip.style("top", (event.pageY - 10) + "px").style("left", (event.pageX + 10) + "px"))
.on("mouseout", () => tooltip.style("visibility", "hidden"));const zoom = d3.zoom()
.scaleExtent([0.5, 10])
.on("zoom", (event) => g.attr("transform", event.transform));
svg.call(zoom);// Basic
circles.transition().duration(750).attr("r", 10);
// Staggered
circles.transition().delay((d, i) => i * 50).duration(500).attr("cy", d => yScale(d.value));
// Custom easing
circles.transition().duration(1000).ease(d3.easeBounceOut).attr("r", 10);function setupResponsiveChart(containerId, data) {
const container = document.getElementById(containerId);
const svg = d3.select(`#${containerId}`).append('svg');
const updateChart = () => {
const { width, height } = container.getBoundingClientRect();
svg.attr('width', width).attr('height', height);
drawChart(data, svg, width, height);
};
updateChart();
window.addEventListener('resize', updateChart);
return () => window.removeEventListener('resize', updateChart);
}| Scale | Use case |
|---|---|
d3.scaleLinear() | Continuous numeric data |
d3.scaleLog() | Exponential/logarithmic data |
d3.scaleTime() | Date/time axes |
d3.scaleBand() | Bar chart categories |
d3.scaleOrdinal() | Categorical colors |
d3.scaleSequential() | Single-hue color gradients |
d3.scaleDiverging() | Diverging color scales |
svg.selectAll("*").remove() before redrawing.join() (enter/update/exit in one call) instead of separate selectionsrole="img", aria-label) for accessibility| Problem | Solution |
|---|---|
| Axes not appearing | Check for NaN in scale domain; verify group transform |
| Transitions not working | Call .transition() before attribute changes |
| Responsive sizing broken | Use ResizeObserver or update SVG width/height on resize |
| Performance issues | Switch to Canvas, debounce resize, use .join() |
generate-asset-price-chart — OHLC candlestick chart generationtrading-indicators-from-price-data — Compute indicators to feed into chartsfree-geocoding-and-maps — Geographic data for map visualizations~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.