Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    davila7

    d3-viz

    davila7/d3-viz
    Design
    19,892
    2 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Creating interactive data visualisations using d3.js...

    SKILL.md

    D3.js Visualisation

    Overview

    This skill provides guidance for creating sophisticated, interactive data visualisations using d3.js. D3.js (Data-Driven Documents) excels at binding data to DOM elements and applying data-driven transformations to create custom, publication-quality visualisations with precise control over every visual element. The techniques work across any JavaScript environment, including vanilla JavaScript, React, Vue, Svelte, and other frameworks.

    When to use d3.js

    Use d3.js for:

    • Custom visualisations requiring unique visual encodings or layouts
    • Interactive explorations with complex pan, zoom, or brush behaviours
    • Network/graph visualisations (force-directed layouts, tree diagrams, hierarchies, chord diagrams)
    • Geographic visualisations with custom projections
    • Visualisations requiring smooth, choreographed transitions
    • Publication-quality graphics with fine-grained styling control
    • Novel chart types not available in standard libraries

    Consider alternatives for:

    • 3D visualisations - use Three.js instead

    Core workflow

    1. Set up d3.js

    Import d3 at the top of your script:

    import * as d3 from 'd3';
    

    Or use the CDN version (7.x):

    <script src="https://d3js.org/d3.v7.min.js"></script>
    

    All modules (scales, axes, shapes, transitions, etc.) are accessible through the d3 namespace.

    2. Choose the integration pattern

    Pattern A: Direct DOM manipulation (recommended for most cases) Use d3 to select DOM elements and manipulate them imperatively. This works in any JavaScript environment:

    function drawChart(data) {
      if (!data || data.length === 0) return;
    
      const svg = d3.select('#chart'); // Select by ID, class, or DOM element
    
      // Clear previous content
      svg.selectAll("*").remove();
    
      // Set up dimensions
      const width = 800;
      const height = 400;
      const margin = { top: 20, right: 30, bottom: 40, left: 50 };
    
      // Create scales, axes, and draw visualisation
      // ... d3 code here ...
    }
    
    // Call when data changes
    drawChart(myData);
    

    Pattern B: Declarative rendering (for frameworks with templating) Use d3 for data calculations (scales, layouts) but render elements via your framework:

    function getChartElements(data) {
      const xScale = d3.scaleLinear()
        .domain([0, d3.max(data, d => d.value)])
        .range([0, 400]);
    
      return data.map((d, i) => ({
        x: 50,
        y: i * 30,
        width: xScale(d.value),
        height: 25
      }));
    }
    
    // In React: {getChartElements(data).map((d, i) => <rect key={i} {...d} fill="steelblue" />)}
    // In Vue: v-for directive over the returned array
    // In vanilla JS: Create elements manually from the returned data
    

    Use Pattern A for complex visualisations with transitions, interactions, or when leveraging d3's full capabilities. Use Pattern B for simpler visualisations or when your framework prefers declarative rendering.

    3. Structure the visualisation code

    Follow this standard structure in your drawing function:

    function drawVisualization(data) {
      if (!data || data.length === 0) return;
    
      const svg = d3.select('#chart'); // Or pass a selector/element
      svg.selectAll("*").remove(); // Clear previous render
    
      // 1. Define dimensions
      const width = 800;
      const 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;
    
      // 2. Create main group with margins
      const g = svg.append("g")
        .attr("transform", `translate(${margin.left},${margin.top})`);
    
      // 3. Create 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]); // Note: inverted for SVG coordinates
    
      // 4. Create and append axes
      const xAxis = d3.axisBottom(xScale);
      const yAxis = d3.axisLeft(yScale);
    
      g.append("g")
        .attr("transform", `translate(0,${innerHeight})`)
        .call(xAxis);
    
      g.append("g")
        .call(yAxis);
    
      // 5. Bind data and create visual 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");
    }
    
    // Call when data changes
    drawVisualization(myData);
    

    4. Implement responsive sizing

    Make visualisations responsive to container size:

    function setupResponsiveChart(containerId, data) {
      const container = document.getElementById(containerId);
      const svg = d3.select(`#${containerId}`).append('svg');
    
      function updateChart() {
        const { width, height } = container.getBoundingClientRect();
        svg.attr('width', width).attr('height', height);
    
        // Redraw visualisation with new dimensions
        drawChart(data, svg, width, height);
      }
    
      // Update on initial load
      updateChart();
    
      // Update on window resize
      window.addEventListener('resize', updateChart);
    
      // Return cleanup function
      return () => window.removeEventListener('resize', updateChart);
    }
    
    // Usage:
    // const cleanup = setupResponsiveChart('chart-container', myData);
    // cleanup(); // Call when component unmounts or element removed
    

    Or use ResizeObserver for more direct container monitoring:

    function setupResponsiveChartWithObserver(svgElement, data) {
      const observer = new ResizeObserver(() => {
        const { width, height } = svgElement.getBoundingClientRect();
        d3.select(svgElement)
          .attr('width', width)
          .attr('height', height);
    
        // Redraw visualisation
        drawChart(data, d3.select(svgElement), width, height);
      });
    
      observer.observe(svgElement.parentElement);
      return () => observer.disconnect();
    }
    

    Common visualisation patterns

    Bar chart

    function drawBarChart(data, svgElement) {
      if (!data || data.length === 0) return;
    
      const svg = d3.select(svgElement);
      svg.selectAll("*").remove();
    
      const width = 800;
      const 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})`);
    
      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.append("g")
        .attr("transform", `translate(0,${innerHeight})`)
        .call(d3.axisBottom(xScale));
    
      g.append("g")
        .call(d3.axisLeft(yScale));
    
      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");
    }
    
    // Usage:
    // drawBarChart(myData, document.getElementById('chart'));
    

    Line chart

    const line = d3.line()
      .x(d => xScale(d.date))
      .y(d => yScale(d.value))
      .curve(d3.curveMonotoneX); // Smooth curve
    
    g.append("path")
      .datum(data)
      .attr("fill", "none")
      .attr("stroke", "steelblue")
      .attr("stroke-width", 2)
      .attr("d", line);
    

    Scatter plot

    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)) // Optional: size encoding
      .attr("fill", d => colourScale(d.category)) // Optional: colour encoding
      .attr("opacity", 0.7);
    

    Chord diagram

    A chord diagram shows relationships between entities in a circular layout, with ribbons representing flows between them:

    function drawChordDiagram(data) {
      // data format: array of objects with source, target, and value
      // Example: [{ source: 'A', target: 'B', value: 10 }, ...]
    
      if (!data || data.length === 0) return;
    
      const svg = d3.select('#chart');
      svg.selectAll("*").remove();
    
      const width = 600;
      const height = 600;
      const innerRadius = Math.min(width, height) * 0.3;
      const outerRadius = innerRadius + 30;
    
      // Create matrix from data
      const nodes = Array.from(new Set(data.flatMap(d => [d.source, d.target])));
      const matrix = Array.from({ length: nodes.length }, () => Array(nodes.length).fill(0));
    
      data.forEach(d => {
        const i = nodes.indexOf(d.source);
        const j = nodes.indexOf(d.target);
        matrix[i][j] += d.value;
        matrix[j][i] += d.value;
      });
    
      // Create chord layout
      const chord = d3.chord()
        .padAngle(0.05)
        .sortSubgroups(d3.descending);
    
      const arc = d3.arc()
        .innerRadius(innerRadius)
        .outerRadius(outerRadius);
    
      const ribbon = d3.ribbon()
        .source(d => d.source)
        .target(d => d.target);
    
      const colourScale = d3.scaleOrdinal(d3.schemeCategory10)
        .domain(nodes);
    
      const g = svg.append("g")
        .attr("transform", `translate(${width / 2},${height / 2})`);
    
      const chords = chord(matrix);
    
      // Draw ribbons
      g.append("g")
        .attr("fill-opacity", 0.67)
        .selectAll("path")
        .data(chords)
        .join("path")
        .attr("d", ribbon)
        .attr("fill", d => colourScale(nodes[d.source.index]))
        .attr("stroke", d => d3.rgb(colourScale(nodes[d.source.index])).darker());
    
      // Draw groups (arcs)
      const group = g.append("g")
        .selectAll("g")
        .data(chords.groups)
        .join("g");
    
      group.append("path")
        .attr("d", arc)
        .attr("fill", d => colourScale(nodes[d.index]))
        .attr("stroke", d => d3.rgb(colourScale(nodes[d.index])).darker());
    
      // Add labels
      group.append("text")
        .each(d => { d.angle = (d.startAngle + d.endAngle) / 2; })
        .attr("dy", "0.31em")
        .attr("transform", d => `rotate(${(d.angle * 180 / Math.PI) - 90})translate(${outerRadius + 30})${d.angle > Math.PI ? "rotate(180)" : ""}`)
        .attr("text-anchor", d => d.angle > Math.PI ? "end" : null)
        .text((d, i) => nodes[i])
        .style("font-size", "12px");
    }
    

    Heatmap

    A heatmap uses colour to encode values in a two-dimensional grid, useful for showing patterns across categories:

    function drawHeatmap(data) {
      // data format: array of objects with row, column, and value
      // Example: [{ row: 'A', column: 'X', value: 10 }, ...]
    
      if (!data || data.length === 0) return;
    
      const svg = d3.select('#chart');
      svg.selectAll("*").remove();
    
      const width = 800;
      const height = 600;
      const margin = { top: 100, right: 30, bottom: 30, left: 100 };
      const innerWidth = width - margin.left - margin.right;
      const innerHeight = height - margin.top - margin.bottom;
    
      // Get unique rows and columns
      const rows = Array.from(new Set(data.map(d => d.row)));
      const columns = Array.from(new Set(data.map(d => d.column)));
    
      const g = svg.append("g")
        .attr("transform", `translate(${margin.left},${margin.top})`);
    
      // Create scales
      const xScale = d3.scaleBand()
        .domain(columns)
        .range([0, innerWidth])
        .padding(0.01);
    
      const yScale = d3.scaleBand()
        .domain(rows)
        .range([0, innerHeight])
        .padding(0.01);
    
      // Colour scale for values
      const colourScale = d3.scaleSequential(d3.interpolateYlOrRd)
        .domain([0, d3.max(data, d => d.value)]);
    
      // Draw rectangles
      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 => colourScale(d.value));
    
      // Add x-axis labels
      svg.append("g")
        .attr("transform", `translate(${margin.left},${margin.top})`)
        .selectAll("text")
        .data(columns)
        .join("text")
        .attr("x", d => xScale(d) + xScale.bandwidth() / 2)
        .attr("y", -10)
        .attr("text-anchor", "middle")
        .text(d => d)
        .style("font-size", "12px");
    
      // Add y-axis labels
      svg.append("g")
        .attr("transform", `translate(${margin.left},${margin.top})`)
        .selectAll("text")
        .data(rows)
        .join("text")
        .attr("x", -10)
        .attr("y", d => yScale(d) + yScale.bandwidth() / 2)
        .attr("dy", "0.35em")
        .attr("text-anchor", "end")
        .text(d => d)
        .style("font-size", "12px");
    
      // Add colour legend
      const legendWidth = 20;
      const legendHeight = 200;
      const legend = svg.append("g")
        .attr("transform", `translate(${width - 60},${margin.top})`);
    
      const legendScale = d3.scaleLinear()
        .domain(colourScale.domain())
        .range([legendHeight, 0]);
    
      const legendAxis = d3.axisRight(legendScale)
        .ticks(5);
    
      // Draw colour gradient in legend
      for (let i = 0; i < legendHeight; i++) {
        legend.append("rect")
          .attr("y", i)
          .attr("width", legendWidth)
          .attr("height", 1)
          .attr("fill", colourScale(legendScale.invert(i)));
      }
    
      legend.append("g")
        .attr("transform", `translate(${legendWidth},0)`)
        .call(legendAxis);
    }
    

    Pie chart

    const pie = d3.pie()
      .value(d => d.value)
      .sort(null);
    
    const arc = d3.arc()
      .innerRadius(0)
      .outerRadius(Math.min(width, height) / 2 - 20);
    
    const colourScale = 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) => colourScale(i))
      .attr("stroke", "white")
      .attr("stroke-width", 2);
    

    Force-directed network

    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")
      .attr("stroke-width", 1);
    
    const node = g.selectAll("circle")
      .data(nodes)
      .join("circle")
      .attr("r", 8)
      .attr("fill", "steelblue")
      .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));
    
    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);
    });
    
    function dragstarted(event) {
      if (!event.active) simulation.alphaTarget(0.3).restart();
      event.subject.fx = event.subject.x;
      event.subject.fy = event.subject.y;
    }
    
    function dragged(event) {
      event.subject.fx = event.x;
      event.subject.fy = event.y;
    }
    
    function dragended(event) {
      if (!event.active) simulation.alphaTarget(0);
      event.subject.fx = null;
      event.subject.fy = null;
    }
    

    Adding interactivity

    Tooltips

    // Create tooltip div (outside SVG)
    const tooltip = d3.select("body").append("div")
      .attr("class", "tooltip")
      .style("position", "absolute")
      .style("visibility", "hidden")
      .style("background-color", "white")
      .style("border", "1px solid #ddd")
      .style("padding", "10px")
      .style("border-radius", "4px")
      .style("pointer-events", "none");
    
    // Add to elements
    circles
      .on("mouseover", function(event, d) {
        d3.select(this).attr("opacity", 1);
        tooltip
          .style("visibility", "visible")
          .html(`<strong>${d.label}</strong><br/>Value: ${d.value}`);
      })
      .on("mousemove", function(event) {
        tooltip
          .style("top", (event.pageY - 10) + "px")
          .style("left", (event.pageX + 10) + "px");
      })
      .on("mouseout", function() {
        d3.select(this).attr("opacity", 0.7);
        tooltip.style("visibility", "hidden");
      });
    

    Zoom and pan

    const zoom = d3.zoom()
      .scaleExtent([0.5, 10])
      .on("zoom", (event) => {
        g.attr("transform", event.transform);
      });
    
    svg.call(zoom);
    

    Click interactions

    circles
      .on("click", function(event, d) {
        // Handle click (dispatch event, update app state, etc.)
        console.log("Clicked:", d);
    
        // Visual feedback
        d3.selectAll("circle").attr("fill", "steelblue");
        d3.select(this).attr("fill", "orange");
    
        // Optional: dispatch custom event for your framework/app to listen to
        // window.dispatchEvent(new CustomEvent('chartClick', { detail: d }));
      });
    

    Transitions and animations

    Add smooth transitions to visual changes:

    // Basic transition
    circles
      .transition()
      .duration(750)
      .attr("r", 10);
    
    // Chained transitions
    circles
      .transition()
      .duration(500)
      .attr("fill", "orange")
      .transition()
      .duration(500)
      .attr("r", 15);
    
    // Staggered transitions
    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);
    

    Scales reference

    Quantitative scales

    // Linear scale
    const xScale = d3.scaleLinear()
      .domain([0, 100])
      .range([0, 500]);
    
    // Log scale (for exponential data)
    const logScale = d3.scaleLog()
      .domain([1, 1000])
      .range([0, 500]);
    
    // Power scale
    const powScale = d3.scalePow()
      .exponent(2)
      .domain([0, 100])
      .range([0, 500]);
    
    // Time scale
    const timeScale = d3.scaleTime()
      .domain([new Date(2020, 0, 1), new Date(2024, 0, 1)])
      .range([0, 500]);
    

    Ordinal scales

    // Band scale (for bar charts)
    const bandScale = d3.scaleBand()
      .domain(['A', 'B', 'C', 'D'])
      .range([0, 400])
      .padding(0.1);
    
    // Point scale (for line/scatter categories)
    const pointScale = d3.scalePoint()
      .domain(['A', 'B', 'C', 'D'])
      .range([0, 400]);
    
    // Ordinal scale (for colours)
    const colourScale = d3.scaleOrdinal(d3.schemeCategory10);
    

    Sequential scales

    // Sequential colour scale
    const colourScale = d3.scaleSequential(d3.interpolateBlues)
      .domain([0, 100]);
    
    // Diverging colour scale
    const divScale = d3.scaleDiverging(d3.interpolateRdBu)
      .domain([-10, 0, 10]);
    

    Best practices

    Data preparation

    Always validate and prepare data before visualisation:

    // Filter invalid values
    const cleanData = data.filter(d => d.value != null && !isNaN(d.value));
    
    // Sort data if order matters
    const sortedData = [...data].sort((a, b) => b.value - a.value);
    
    // Parse dates
    const parsedData = data.map(d => ({
      ...d,
      date: d3.timeParse("%Y-%m-%d")(d.date)
    }));
    

    Performance optimisation

    For large datasets (>1000 elements):

    // Use canvas instead of SVG for many elements
    // Use quadtree for collision detection
    // Simplify paths with d3.line().curve(d3.curveStep)
    // Implement virtual scrolling for large lists
    // Use requestAnimationFrame for custom animations
    

    Accessibility

    Make visualisations accessible:

    // Add ARIA labels
    svg.attr("role", "img")
       .attr("aria-label", "Bar chart showing quarterly revenue");
    
    // Add title and description
    svg.append("title").text("Quarterly Revenue 2024");
    svg.append("desc").text("Bar chart showing revenue growth across four quarters");
    
    // Ensure sufficient colour contrast
    // Provide keyboard navigation for interactive elements
    // Include data table alternative
    

    Styling

    Use consistent, professional styling:

    // Define colour palettes upfront
    const colours = {
      primary: '#4A90E2',
      secondary: '#7B68EE',
      background: '#F5F7FA',
      text: '#333333',
      gridLines: '#E0E0E0'
    };
    
    // Apply consistent typography
    svg.selectAll("text")
      .style("font-family", "Inter, sans-serif")
      .style("font-size", "12px");
    
    // Use subtle grid lines
    g.selectAll(".tick line")
      .attr("stroke", colours.gridLines)
      .attr("stroke-dasharray", "2,2");
    

    Common issues and solutions

    Issue: Axes not appearing

    • Ensure scales have valid domains (check for NaN values)
    • Verify axis is appended to correct group
    • Check transform translations are correct

    Issue: Transitions not working

    • Call .transition() before attribute changes
    • Ensure elements have unique keys for proper data binding
    • Check that useEffect dependencies include all changing data

    Issue: Responsive sizing not working

    • Use ResizeObserver or window resize listener
    • Update dimensions in state to trigger re-render
    • Ensure SVG has width/height attributes or viewBox

    Issue: Performance problems

    • Limit number of DOM elements (consider canvas for >1000 items)
    • Debounce resize handlers
    • Use .join() instead of separate enter/update/exit selections
    • Avoid unnecessary re-renders by checking dependencies

    Resources

    references/

    Contains detailed reference materials:

    • d3-patterns.md - Comprehensive collection of visualisation patterns and code examples
    • scale-reference.md - Complete guide to d3 scales with examples
    • colour-schemes.md - D3 colour schemes and palette recommendations

    assets/

    Contains boilerplate templates:

    • chart-template.js - Starter template for basic chart
    • interactive-template.js - Template with tooltips, zoom, and interactions
    • sample-data.json - Example datasets for testing

    These templates work with vanilla JavaScript, React, Vue, Svelte, or any other JavaScript environment. Adapt them as needed for your specific framework.

    To use these resources, read the relevant files when detailed guidance is needed for specific visualisation types or patterns.

    Recommended Servers
    Draw.io
    Draw.io
    Svelte
    Svelte
    Browserbase
    Browserbase
    Repository
    davila7/claude-code-templates
    Files