---
name: d3-shapes-paths
description: Use when creating SVG shapes, paths, or generators for charts. Invoke for line/area/arc generators, curves, symbols, pie/donut charts, stacks, ribbons, or path manipulation operations.
allowed-tools: Read, Grep, Glob
---

# D3 Shapes & Paths Expert

## Purpose

Expert knowledge of D3's shape generators and path manipulation. Covers line, area, arc, pie, symbol, stack, ribbon generators, curve interpolation, and SVG path construction.

## When to Use

Invoke this skill when:
- Creating line or area charts
- Building pie or donut charts
- Generating arc segments (gauges, progress indicators)
- Creating stacked charts (bar, area)
- Working with curve interpolation
- Drawing symbols (circles, squares, diamonds)
- Building chord diagrams
- Generating contour plots
- Manipulating SVG paths
- Debugging shape rendering issues

## Documentation Available

**Location**: `/Users/zach/Documents/cc-skills/docs/d3/`

**Coverage** (203 files):
- **Shapes** (146 files):
  - Lines: line generator, curve interpolation
  - Areas: area generator, radial areas
  - Arcs: arc generator, pie layout
  - Symbols: symbol types and custom symbols
  - Stacks: stack layout, stack order
  - Links: link generators (orthogonal, radial)
  - Curves: 15+ interpolation types

- **Paths** (12 files):
  - Path construction and serialization
  - Path data parsing
  - Geometric operations

- **Contours** (17 files):
  - Contour generation
  - Density estimation
  - Marching squares algorithm

- **Chords** (22 files):
  - Chord layout
  - Ribbon generator
  - Chord diagrams

- **Polygons** (6 files):
  - Polygon operations
  - Hull, centroid, area calculations

## Related Skills

- **d3-core-data**: For scales used with shapes
- **d3-geo**: For geographic path generators
- **d3-interaction-animation**: For animating shapes
- **d3-layouts-hierarchies**: For tree/network shapes

## Best Practices

- **Define accessors consistently** - Use x/y accessors for data binding
- **Use curve interpolators** - Choose appropriate curve type for data
- **Handle null data** - Set .defined() to skip gaps in lines
- **Use radial variants** - For circular/polar charts
- **Optimize path rendering** - Simplify paths for large datasets
- **Set arc padding** - Add spacing in pie charts
- **Use stack keys** - Define series for stacked charts
- **Apply proper transforms** - Translate arcs to center

## Examples

### Example 1: Line Chart with Multiple Series

**Question**: "How do I create a multi-series line chart?"

**Response**:
```javascript
import * as d3 from 'd3';

// Sample data
const data = [
  { date: new Date('2024-01-01'), series1: 30, series2: 20 },
  { date: new Date('2024-01-02'), series1: 45, series2: 35 },
  { date: new Date('2024-01-03'), series1: 40, series2: 30 },
  { date: new Date('2024-01-04'), series1: null, series2: 40 }, // Gap in series1
  { date: new Date('2024-01-05'), series1: 60, series2: 50 },
];

// Create scales
const xScale = d3.scaleTime()
  .domain(d3.extent(data, d => d.date))
  .range([0, 800]);

const yScale = d3.scaleLinear()
  .domain([0, d3.max(data, d => Math.max(d.series1 || 0, d.series2))])
  .range([400, 0])
  .nice();

// Create line generator
const line = d3.line()
  .x(d => xScale(d.date))
  .y(d => yScale(d.value))
  .curve(d3.curveMonotoneX) // Smooth curve
  .defined(d => d.value != null); // Skip null values

// Transform data for each series
const series1Data = data.map(d => ({ date: d.date, value: d.series1 }));
const series2Data = data.map(d => ({ date: d.date, value: d.series2 }));

// Generate path data
const path1 = line(series1Data);
const path2 = line(series2Data);

// Create SVG
const svg = d3.create('svg')
  .attr('width', 800)
  .attr('height', 400);

// Add lines
svg.append('path')
  .attr('d', path1)
  .attr('fill', 'none')
  .attr('stroke', 'steelblue')
  .attr('stroke-width', 2);

svg.append('path')
  .attr('d', path2)
  .attr('fill', 'none')
  .attr('stroke', 'orange')
  .attr('stroke-width', 2);

// Add axes
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);

svg.append('g')
  .attr('transform', 'translate(0, 400)')
  .call(xAxis);

svg.append('g')
  .call(yAxis);
```

**Curve Types**:
- `curveLinear` - Straight lines (default)
- `curveMonotoneX` - Smooth, no overshoot
- `curveCatmullRom` - Smooth, passes through points
- `curveBasis` - Smooth B-spline
- `curveStep` - Step function

**References**:
- See: `docs/d3/shape/line.md`
- See: `docs/d3/shape/curve.md`

### Example 2: Area Chart with Gradient

**Question**: "How do I create an area chart with gradient fill?"

**Response**:
```javascript
import * as d3 from 'd3';

const data = [
  { x: 0, y0: 0, y1: 30 },
  { x: 1, y0: 0, y1: 45 },
  { x: 2, y0: 0, y1: 40 },
  { x: 3, y0: 0, y1: 60 },
  { x: 4, y0: 0, y1: 50 },
];

// Create scales
const xScale = d3.scaleLinear()
  .domain([0, 4])
  .range([0, 800]);

const yScale = d3.scaleLinear()
  .domain([0, 60])
  .range([400, 0]);

// Create area generator
const area = d3.area()
  .x(d => xScale(d.x))
  .y0(d => yScale(d.y0)) // Baseline
  .y1(d => yScale(d.y1)) // Top line
  .curve(d3.curveMonotoneX);

// Create SVG
const svg = d3.create('svg')
  .attr('width', 800)
  .attr('height', 400);

// Define gradient
const gradient = svg.append('defs')
  .append('linearGradient')
  .attr('id', 'area-gradient')
  .attr('x1', '0%')
  .attr('y1', '0%')
  .attr('x2', '0%')
  .attr('y2', '100%');

gradient.append('stop')
  .attr('offset', '0%')
  .attr('stop-color', 'steelblue')
  .attr('stop-opacity', 0.8);

gradient.append('stop')
  .attr('offset', '100%')
  .attr('stop-color', 'steelblue')
  .attr('stop-opacity', 0.1);

// Add area
svg.append('path')
  .datum(data)
  .attr('d', area)
  .attr('fill', 'url(#area-gradient)');

// Add top line
const line = d3.line()
  .x(d => xScale(d.x))
  .y(d => yScale(d.y1))
  .curve(d3.curveMonotoneX);

svg.append('path')
  .datum(data)
  .attr('d', line)
  .attr('fill', 'none')
  .attr('stroke', 'steelblue')
  .attr('stroke-width', 2);
```

**Radial Area (for circular charts)**:
```javascript
const radialArea = d3.areaRadial()
  .angle(d => angleScale(d.angle))
  .innerRadius(d => radiusScale(d.inner))
  .outerRadius(d => radiusScale(d.outer));
```

**References**:
- See: `docs/d3/shape/area.md`
- See: `docs/d3/shape/radial.md`

### Example 3: Pie and Donut Charts

**Question**: "How do I create a pie chart with labels?"

**Response**:
```javascript
import * as d3 from 'd3';

const data = [
  { category: 'A', value: 30 },
  { category: 'B', value: 80 },
  { category: 'C', value: 45 },
  { category: 'D', value: 60 },
];

// Create pie layout
const pie = d3.pie()
  .value(d => d.value)
  .sort(null) // Keep original order
  .padAngle(0.02); // Spacing between slices

// Create arc generator
const arc = d3.arc()
  .innerRadius(0) // Use innerRadius > 0 for donut
  .outerRadius(150);

// Arc for labels (slightly outside)
const labelArc = d3.arc()
  .innerRadius(170)
  .outerRadius(170);

// Color scale
const color = d3.scaleOrdinal()
  .domain(data.map(d => d.category))
  .range(d3.schemeCategory10);

// Create SVG
const svg = d3.create('svg')
  .attr('width', 400)
  .attr('height', 400);

const g = svg.append('g')
  .attr('transform', 'translate(200, 200)'); // Center

// Generate pie data
const pieData = pie(data);

// Add arcs
g.selectAll('path')
  .data(pieData)
  .join('path')
  .attr('d', arc)
  .attr('fill', d => color(d.data.category))
  .attr('stroke', 'white')
  .attr('stroke-width', 2);

// Add labels
g.selectAll('text')
  .data(pieData)
  .join('text')
  .attr('transform', d => `translate(${labelArc.centroid(d)})`)
  .attr('text-anchor', 'middle')
  .text(d => d.data.category);

// Add percentage labels
g.selectAll('.percentage')
  .data(pieData)
  .join('text')
  .attr('class', 'percentage')
  .attr('transform', d => `translate(${arc.centroid(d)})`)
  .attr('text-anchor', 'middle')
  .attr('fill', 'white')
  .attr('font-weight', 'bold')
  .text(d => {
    const percent = ((d.endAngle - d.startAngle) / (2 * Math.PI) * 100).toFixed(1);
    return `${percent}%`;
  });
```

**Donut Chart** (add inner radius):
```javascript
const arc = d3.arc()
  .innerRadius(80)  // Creates donut hole
  .outerRadius(150);
```

**Corner Radius** (rounded corners):
```javascript
const arc = d3.arc()
  .innerRadius(80)
  .outerRadius(150)
  .cornerRadius(5);
```

**References**:
- See: `docs/d3/shape/pie.md`
- See: `docs/d3/shape/arc.md`

### Example 4: Stacked Area Chart

**Question**: "How do I create a stacked area chart?"

**Response**:
```javascript
import * as d3 from 'd3';

const data = [
  { date: new Date('2024-01-01'), apples: 30, bananas: 20, oranges: 10 },
  { date: new Date('2024-01-02'), apples: 45, bananas: 35, oranges: 15 },
  { date: new Date('2024-01-03'), apples: 40, bananas: 30, oranges: 20 },
  { date: new Date('2024-01-04'), apples: 60, bananas: 40, oranges: 25 },
];

// Define stack keys (series)
const keys = ['apples', 'bananas', 'oranges'];

// Create stack layout
const stack = d3.stack()
  .keys(keys)
  .order(d3.stackOrderNone) // Keep original order
  .offset(d3.stackOffsetNone); // Start from zero

// Generate stack data
const stackedData = stack(data);
// [
//   [[0, 30], [0, 45], ...],     // apples layer
//   [[30, 50], [45, 80], ...],   // bananas layer
//   [[50, 60], [80, 95], ...],   // oranges layer
// ]

// Create scales
const xScale = d3.scaleTime()
  .domain(d3.extent(data, d => d.date))
  .range([0, 800]);

const yScale = d3.scaleLinear()
  .domain([0, d3.max(stackedData[stackedData.length - 1], d => d[1])])
  .range([400, 0])
  .nice();

// Create area generator
const area = d3.area()
  .x(d => xScale(d.data.date))
  .y0(d => yScale(d[0])) // Bottom of stack
  .y1(d => yScale(d[1])) // Top of stack
  .curve(d3.curveMonotoneX);

// Color scale
const color = d3.scaleOrdinal()
  .domain(keys)
  .range(d3.schemeCategory10);

// Create SVG
const svg = d3.create('svg')
  .attr('width', 800)
  .attr('height', 400);

// Add layers
svg.selectAll('path')
  .data(stackedData)
  .join('path')
  .attr('d', area)
  .attr('fill', d => color(d.key))
  .attr('stroke', 'white')
  .attr('stroke-width', 1);

// Add legend
const legend = svg.append('g')
  .attr('transform', 'translate(700, 20)');

legend.selectAll('rect')
  .data(keys)
  .join('rect')
  .attr('y', (d, i) => i * 25)
  .attr('width', 20)
  .attr('height', 20)
  .attr('fill', d => color(d));

legend.selectAll('text')
  .data(keys)
  .join('text')
  .attr('x', 25)
  .attr('y', (d, i) => i * 25 + 15)
  .text(d => d);
```

**Stack Orders**:
- `stackOrderNone` - Original order
- `stackOrderAscending` - Smallest on top
- `stackOrderDescending` - Largest on top
- `stackOrderInsideOut` - Largest in middle

**Stack Offsets**:
- `stackOffsetNone` - Zero baseline
- `stackOffsetExpand` - Normalize to [0, 1]
- `stackOffsetSilhouette` - Center around zero
- `stackOffsetWiggle` - Minimize changes in slope

**References**:
- See: `docs/d3/shape/stack.md`

### Example 5: Symbols and Custom Shapes

**Question**: "How do I use D3 symbols for scatter plots?"

**Response**:
```javascript
import * as d3 from 'd3';

const data = [
  { x: 30, y: 20, type: 'A' },
  { x: 50, y: 80, type: 'B' },
  { x: 80, y: 50, type: 'A' },
  { x: 120, y: 90, type: 'C' },
];

// Create scales
const xScale = d3.scaleLinear()
  .domain([0, 150])
  .range([0, 800]);

const yScale = d3.scaleLinear()
  .domain([0, 100])
  .range([400, 0]);

// Symbol types
const symbolTypes = [
  d3.symbolCircle,
  d3.symbolSquare,
  d3.symbolTriangle,
  d3.symbolStar,
  d3.symbolDiamond,
  d3.symbolCross,
];

// Symbol scale
const symbolScale = d3.scaleOrdinal()
  .domain(['A', 'B', 'C'])
  .range([d3.symbolCircle, d3.symbolSquare, d3.symbolTriangle]);

// Create symbol generator
const symbol = d3.symbol()
  .size(200); // Area in square pixels

// Create SVG
const svg = d3.create('svg')
  .attr('width', 800)
  .attr('height', 400);

// Add symbols
svg.selectAll('path')
  .data(data)
  .join('path')
  .attr('d', d => {
    symbol.type(symbolScale(d.type));
    return symbol();
  })
  .attr('transform', d => `translate(${xScale(d.x)}, ${yScale(d.y)})`)
  .attr('fill', 'steelblue')
  .attr('stroke', 'white')
  .attr('stroke-width', 2);
```

**Available Symbol Types**:
- `symbolCircle` - Circle
- `symbolSquare` - Square
- `symbolTriangle` - Upward triangle
- `symbolStar` - Five-pointed star
- `symbolDiamond` - Diamond
- `symbolCross` - Plus sign
- `symbolWye` - Y shape

**Custom Symbol**:
```javascript
const customSymbol = {
  draw(context, size) {
    const r = Math.sqrt(size / 5) / 2;
    context.moveTo(-3 * r, -r);
    context.lineTo(-r, -r);
    context.lineTo(-r, -3 * r);
    context.lineTo(r, -3 * r);
    context.lineTo(r, -r);
    context.lineTo(3 * r, -r);
    context.lineTo(3 * r, r);
    context.lineTo(r, r);
    context.lineTo(r, 3 * r);
    context.lineTo(-r, 3 * r);
    context.lineTo(-r, r);
    context.lineTo(-3 * r, r);
    context.closePath();
  },
};

const symbol = d3.symbol()
  .type(customSymbol)
  .size(200);
```

**References**:
- See: `docs/d3/shape/symbol.md`

## Common Patterns

### Responsive Line Chart
```javascript
const line = d3.line()
  .x(d => xScale(d.x))
  .y(d => yScale(d.y))
  .curve(d3.curveMonotoneX);

svg.append('path')
  .datum(data)
  .attr('d', line)
  .attr('fill', 'none')
  .attr('stroke', 'steelblue')
  .attr('stroke-width', 2);
```

### Arc with Hover Effect
```javascript
const arc = d3.arc()
  .innerRadius(80)
  .outerRadius(150);

const arcHover = d3.arc()
  .innerRadius(80)
  .outerRadius(160); // Larger

svg.selectAll('path')
  .on('mouseenter', function() {
    d3.select(this).transition()
      .attr('d', arcHover);
  })
  .on('mouseleave', function() {
    d3.select(this).transition()
      .attr('d', arc);
  });
```

### Link Generators (for node diagrams)
```javascript
const link = d3.linkHorizontal()
  .x(d => d.x)
  .y(d => d.y);

const linkData = {
  source: { x: 0, y: 100 },
  target: { x: 200, y: 150 },
};

svg.append('path')
  .attr('d', link(linkData))
  .attr('fill', 'none')
  .attr('stroke', 'black');
```

## Search Helpers

```bash
# Find line/area docs
grep -r "line\|area\|curve" /Users/zach/Documents/cc-skills/docs/d3/shape/

# Find pie/arc docs
grep -r "pie\|arc\|donut" /Users/zach/Documents/cc-skills/docs/d3/shape/

# Find stack docs
grep -r "stack\|layer" /Users/zach/Documents/cc-skills/docs/d3/shape/

# Find symbol docs
grep -r "symbol\|scatter" /Users/zach/Documents/cc-skills/docs/d3/shape/

# Find path operations
grep -r "path\|serialize" /Users/zach/Documents/cc-skills/docs/d3/path/

# List shape generators
ls /Users/zach/Documents/cc-skills/docs/d3/shape/
```

## Common Errors

- **Path not rendering**: Check scale domains match data range
  - Solution: Use d3.extent() to compute correct domain

- **Area chart inverted**: Y scale range is backwards
  - Solution: Set range as [height, 0] (SVG y grows downward)

- **Pie chart off-center**: Missing transform translate
  - Solution: Add `transform="translate(cx, cy)"` to group

- **Gaps in line**: Null/undefined values in data
  - Solution: Set `.defined(d => d.value != null)`

- **Stack negative values**: stackOffsetNone doesn't handle negatives
  - Solution: Use stackOffsetDiverging for +/- values

## Performance Tips

1. **Use .curve(d3.curveLinear)** - Fastest rendering
2. **Simplify paths** - Use fewer data points for large datasets
3. **Avoid recomputing generators** - Create once, reuse
4. **Use context rendering** - For Canvas instead of SVG
5. **Batch path updates** - Use .datum() instead of .data() when possible
6. **Optimize arc rendering** - Use fewer padAngle for large datasets

## Notes

- Documentation covers D3 v7 (latest version)
- All generators support both SVG and Canvas contexts
- Curve interpolators are interchangeable across line/area generators
- Pie layout computes startAngle and endAngle for each datum
- Stack layout adds [y0, y1] arrays to each data point
- Symbol size is in square pixels (area, not radius)
- Path generators are immutable - methods return new generators
- File paths reference local documentation cache
- For latest updates, check https://d3js.org/d3-shape
