Agent SkillsAgent Skills
zacharyr0th

d3-geo

@zacharyr0th/d3-geo
zacharyr0th
0
0 forks
Updated 4/6/2026
View on GitHub

Use when creating maps, working with geographic projections, or processing GeoJSON data. Invoke for world maps, choropleth maps, projection types, geo path generators, spherical geometry, or geographic feature manipulation.

Installation

$npx agent-skills-cli install @zacharyr0th/d3-geo
Claude Code
Cursor
Copilot
Codex
Antigravity

Details

Path.claude/skills/d3/d3-geo/SKILL.md
Branchmain
Scoped Name@zacharyr0th/d3-geo

Usage

After installing, this skill will be available to your AI coding assistant.

Verify installation:

npx agent-skills-cli list

Skill Instructions


name: d3-geo description: Use when creating maps, working with geographic projections, or processing GeoJSON data. Invoke for world maps, choropleth maps, projection types, geo path generators, spherical geometry, or geographic feature manipulation. allowed-tools: Read, Grep, Glob

D3 Geographic Visualization Expert

Purpose

Expert knowledge of D3's geographic projections and map rendering. Covers map projections, GeoJSON processing, geographic path generators, spherical geometry, and cartographic features.

When to Use

Invoke this skill when:

  • Creating world maps or regional maps
  • Working with GeoJSON or TopoJSON data
  • Choosing or configuring map projections
  • Building choropleth (colored region) maps
  • Computing geographic centroids or bounds
  • Measuring distances on a sphere
  • Creating globe visualizations
  • Rotating or clipping projections
  • Handling geographic coordinates
  • Debugging map rendering issues

Documentation Available

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

Coverage (266 files):

  • Core Geography (91 files):

    • Path generator for rendering GeoJSON
    • Spherical math (distance, area, length)
    • Geographic bounds and centroids
    • Great circle interpolation
    • Clipping and rotation
  • Projections (151 files):

    • 30+ projection types
    • Azimuthal: orthographic, stereographic, gnomonic
    • Cylindrical: Mercator, equirectangular
    • Conic: Albers, Lambert conformal
    • Composite projections
    • Projection configuration (scale, translate, rotate)
  • Polygon Operations (24 files):

    • Spherical polygon area
    • Polygon contains point
    • Polygon clipping
    • Polygon centroid

Related Skills

  • d3-core-data: For color scales in choropleth maps
  • d3-shapes-paths: For combining geo with other shapes
  • d3-interaction-animation: For interactive maps
  • d3-layouts-hierarchies: For hierarchical geographic data

Best Practices

  • Use appropriate projections - Mercator for web tiles, Albers for US maps
  • Fit projections to bounds - Use .fitSize() or .fitExtent()
  • Simplify TopoJSON - Reduce file size for web delivery
  • Handle dateline wrapping - Use antimeridian clipping
  • Set precision carefully - Balance quality vs. performance
  • Use geographic centroids - More accurate than planar centroids
  • Cache path generator - Reuse for multiple features
  • Consider adaptive sampling - For large datasets

Examples

Example 1: Basic World Map

Question: "How do I create a simple world map?"

Response:

import * as d3 from 'd3';

// Load GeoJSON data
const worldData = await d3.json('https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json');

// Convert TopoJSON to GeoJSON
const countries = topojson.feature(worldData, worldData.objects.countries);

// Create projection
const projection = d3.geoNaturalEarth1()
  .scale(200)
  .translate([400, 300]); // Center of SVG

// Create path generator
const path = d3.geoPath()
  .projection(projection);

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

// Draw countries
svg.selectAll('path')
  .data(countries.features)
  .join('path')
  .attr('d', path)
  .attr('fill', '#ccc')
  .attr('stroke', '#fff')
  .attr('stroke-width', 0.5);

// Add graticule (grid lines)
const graticule = d3.geoGraticule();

svg.append('path')
  .datum(graticule())
  .attr('d', path)
  .attr('fill', 'none')
  .attr('stroke', '#ddd')
  .attr('stroke-width', 0.5);

// Add sphere outline
svg.append('path')
  .datum({ type: 'Sphere' })
  .attr('d', path)
  .attr('fill', 'none')
  .attr('stroke', '#000')
  .attr('stroke-width', 1.5);

Auto-fit projection to data:

const projection = d3.geoNaturalEarth1()
  .fitSize([800, 600], countries);

References:

  • See: docs/d3/geo/path.md
  • See: docs/d3/geo-projection/

Example 2: US State Choropleth Map

Question: "How do I create a choropleth map with state-level data?"

Response:

import * as d3 from 'd3';

// Load US states TopoJSON
const us = await d3.json('https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json');
const states = topojson.feature(us, us.objects.states);

// Sample data (state ID -> value)
const data = new Map([
  ['01', 45], // Alabama
  ['06', 82], // California
  ['36', 68], // New York
  // ... more states
]);

// Create projection optimized for US
const projection = d3.geoAlbersUsa()
  .scale(1000)
  .translate([400, 300]);

const path = d3.geoPath()
  .projection(projection);

// Create color scale
const colorScale = d3.scaleSequential()
  .domain([0, 100])
  .interpolator(d3.interpolateBlues);

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

// Draw states
svg.selectAll('path')
  .data(states.features)
  .join('path')
  .attr('d', path)
  .attr('fill', d => {
    const value = data.get(d.id);
    return value ? colorScale(value) : '#ccc';
  })
  .attr('stroke', '#fff')
  .attr('stroke-width', 0.5)
  .on('mouseenter', function(event, d) {
    d3.select(this)
      .attr('stroke', '#000')
      .attr('stroke-width', 2);

    // Show tooltip
    const value = data.get(d.id);
    console.log(`${d.properties.name}: ${value}`);
  })
  .on('mouseleave', function() {
    d3.select(this)
      .attr('stroke', '#fff')
      .attr('stroke-width', 0.5);
  });

// Add legend
const legendWidth = 300;
const legendHeight = 10;

const legend = svg.append('g')
  .attr('transform', 'translate(250, 550)');

// Create gradient for legend
const defs = svg.append('defs');
const gradient = defs.append('linearGradient')
  .attr('id', 'legend-gradient');

gradient.selectAll('stop')
  .data(d3.range(0, 1.1, 0.1))
  .join('stop')
  .attr('offset', d => `${d * 100}%`)
  .attr('stop-color', d => colorScale(d * 100));

legend.append('rect')
  .attr('width', legendWidth)
  .attr('height', legendHeight)
  .style('fill', 'url(#legend-gradient)');

// Add legend axis
const legendScale = d3.scaleLinear()
  .domain([0, 100])
  .range([0, legendWidth]);

const legendAxis = d3.axisBottom(legendScale)
  .ticks(5);

legend.append('g')
  .attr('transform', `translate(0, ${legendHeight})`)
  .call(legendAxis);

Albers USA Projection:

  • Optimized for US maps
  • Automatically positions Alaska and Hawaii
  • Better area representation than Mercator

References:

  • See: docs/d3/geo-projection/albers-usa.md

Example 3: Interactive Globe with Rotation

Question: "How do I create a draggable globe?"

Response:

import * as d3 from 'd3';

// Load world data
const world = await d3.json('https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json');
const countries = topojson.feature(world, world.objects.countries);

// Create orthographic projection (globe)
const projection = d3.geoOrthographic()
  .scale(250)
  .translate([400, 300])
  .rotate([0, 0]); // [longitude, latitude, roll]

const path = d3.geoPath()
  .projection(projection);

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

// Add ocean (sphere background)
svg.append('path')
  .datum({ type: 'Sphere' })
  .attr('class', 'ocean')
  .attr('d', path)
  .attr('fill', '#a8d5ff');

// Add countries
const countriesGroup = svg.append('g')
  .selectAll('path')
  .data(countries.features)
  .join('path')
  .attr('d', path)
  .attr('fill', '#bbb')
  .attr('stroke', '#fff')
  .attr('stroke-width', 0.5);

// Add graticule
const graticule = d3.geoGraticule();
svg.append('path')
  .datum(graticule())
  .attr('d', path)
  .attr('fill', 'none')
  .attr('stroke', '#ccc')
  .attr('stroke-width', 0.5)
  .attr('opacity', 0.5);

// Add drag behavior
const drag = d3.drag()
  .on('start', function(event) {
    // Store starting rotation
    this._rotation = projection.rotate();
    this._coords = [event.x, event.y];
  })
  .on('drag', function(event) {
    const rotation = this._rotation;
    const coords = this._coords;

    // Calculate rotation delta
    const dx = event.x - coords[0];
    const dy = event.y - coords[1];

    // Update projection rotation
    projection.rotate([
      rotation[0] + dx * 0.5,
      rotation[1] - dy * 0.5,
    ]);

    // Re-render
    svg.selectAll('path').attr('d', path);
  });

svg.call(drag);

// Add zoom behavior
const zoom = d3.zoom()
  .scaleExtent([0.5, 5])
  .on('zoom', (event) => {
    projection.scale(250 * event.transform.k);
    svg.selectAll('path').attr('d', path);
  });

svg.call(zoom);

Other 3D Projections:

  • geoOrthographic() - Globe view
  • geoStereographic() - Perspective from point
  • geoGnomonic() - Great circles as straight lines

References:

  • See: docs/d3/geo-projection/orthographic.md

Example 4: Computing Geographic Measurements

Question: "How do I calculate distances and areas on a sphere?"

Response:

import * as d3 from 'd3';

// Define coordinates [longitude, latitude]
const newYork = [-74.006, 40.7128];
const london = [-0.1278, 51.5074];
const tokyo = [139.6917, 35.6895];

// Calculate great circle distance (in radians)
const distanceRadians = d3.geoDistance(newYork, london);
console.log('Distance (radians):', distanceRadians);

// Convert to kilometers (Earth radius = 6371 km)
const distanceKm = distanceRadians * 6371;
console.log('Distance (km):', distanceKm.toFixed(0)); // ~5,570 km

// Calculate area of polygon (in steradians)
const polygon = {
  type: 'Polygon',
  coordinates: [[
    [-74, 40],
    [-73, 40],
    [-73, 41],
    [-74, 41],
    [-74, 40],
  ]],
};

const areaSteradians = d3.geoArea(polygon);
console.log('Area (steradians):', areaSteradians);

// Convert to square kilometers (Earth surface area = 510M km²)
const areaKm2 = areaSteradians * (6371 * 6371);
console.log('Area (km²):', areaKm2.toFixed(0));

// Calculate path length
const lineString = {
  type: 'LineString',
  coordinates: [newYork, london],
};

const lengthRadians = d3.geoLength(lineString);
const lengthKm = lengthRadians * 6371;
console.log('Path length (km):', lengthKm.toFixed(0));

// Calculate centroid
const centroid = d3.geoCentroid(polygon);
console.log('Centroid [lon, lat]:', centroid);

// Check if point is inside polygon
const point = [-73.5, 40.5];
const contains = d3.geoContains(polygon, point);
console.log('Contains point:', contains); // true or false

// Interpolate along great circle
const interpolate = d3.geoInterpolate(newYork, london);
console.log('25% along:', interpolate(0.25));
console.log('50% along:', interpolate(0.5));
console.log('75% along:', interpolate(0.75));

// Calculate bounds
const bounds = d3.geoBounds(polygon);
console.log('Bounds [[west, south], [east, north]]:', bounds);

Spherical vs. Planar:

  • Use d3.geo* for geographic coordinates (lat/lon)
  • Use d3.polygon* for projected/screen coordinates

References:

  • See: docs/d3/geo/distance.md
  • See: docs/d3/geo/area.md
  • See: docs/d3/geo/centroid.md

Example 5: Custom Projection Configuration

Question: "How do I configure projection for a specific region?"

Response:

import * as d3 from 'd3';

// Load region data (e.g., Europe)
const europe = await d3.json('europe.geojson');

// Method 1: Manual configuration
const projection1 = d3.geoMercator()
  .center([10, 50]) // Center on [lon, lat]
  .scale(500)
  .translate([400, 300]); // SVG center

// Method 2: Fit to bounds (recommended)
const projection2 = d3.geoMercator()
  .fitSize([800, 600], europe);

// Method 3: Fit with padding
const projection3 = d3.geoMercator()
  .fitExtent([[20, 20], [780, 580]], europe); // [[x0, y0], [x1, y1]]

// Method 4: Fit width, maintain aspect ratio
const projection4 = d3.geoMercator()
  .fitWidth(800, europe);

// Method 5: Fit height, maintain aspect ratio
const projection5 = d3.geoMercator()
  .fitHeight(600, europe);

// Projection with rotation
const projection6 = d3.geoMercator()
  .rotate([-10, 0, 0]) // [yaw, pitch, roll]
  .fitSize([800, 600], europe);

// Set clipping
const projection7 = d3.geoMercator()
  .clipAngle(90) // Clip to hemisphere
  .fitSize([800, 600], europe);

// Set precision (adaptive sampling)
const projection8 = d3.geoMercator()
  .precision(0.1) // Lower = more points, higher quality
  .fitSize([800, 600], europe);

// Get projection functions
const path = d3.geoPath(projection2);

// Project coordinate to screen
const [x, y] = projection2([10, 50]); // [lon, lat] -> [x, y]

// Invert screen to coordinate
const [lon, lat] = projection2.invert([400, 300]); // [x, y] -> [lon, lat]

// Check if coordinate is visible
const visible = projection2([10, 50]); // null if clipped

Common Projections:

  • geoMercator() - Web maps (preserves angles)
  • geoAlbers() - US/Europe maps (preserves area)
  • geoEquirectangular() - Simple lat/lon mapping
  • geoConicEqualArea() - Regional maps
  • geoNaturalEarth1() - World maps (compromise)

References:

  • See: docs/d3/geo/projection.md
  • See: docs/d3/geo-projection/

Common Patterns

Zoom to Feature

function zoomToFeature(feature) {
  const [[x0, y0], [x1, y1]] = path.bounds(feature);

  svg.transition()
    .duration(750)
    .call(zoom.transform, d3.zoomIdentity
      .translate(width / 2, height / 2)
      .scale(Math.min(8, 0.9 / Math.max(
        (x1 - x0) / width,
        (y1 - y0) / height
      )))
      .translate(-(x0 + x1) / 2, -(y0 + y1) / 2)
    );
}

Highlight Neighbors

const neighbors = topojson.neighbors(us.objects.states.geometries);

svg.selectAll('path')
  .on('mouseenter', function(event, d, i) {
    svg.selectAll('path')
      .filter((_, j) => neighbors[i].includes(j))
      .attr('fill', 'orange');
  });

Add Labels to Centroids

svg.selectAll('text')
  .data(countries.features)
  .join('text')
  .attr('transform', d => {
    const [x, y] = path.centroid(d);
    return `translate(${x}, ${y})`;
  })
  .attr('text-anchor', 'middle')
  .text(d => d.properties.name);

Search Helpers

# Find projection docs
grep -r "projection\|geoMercator\|geoAlbers" /Users/zach/Documents/cc-skills/docs/d3/geo-projection/

# Find path generator docs
grep -r "geoPath\|path.area\|path.bounds" /Users/zach/Documents/cc-skills/docs/d3/geo/

# Find spherical math docs
grep -r "geoDistance\|geoArea\|geoCentroid" /Users/zach/Documents/cc-skills/docs/d3/geo/

# Find clipping docs
grep -r "clip\|rotate" /Users/zach/Documents/cc-skills/docs/d3/geo/

# List projection types
ls /Users/zach/Documents/cc-skills/docs/d3/geo-projection/

Common Errors

  • Map not centered: Use .fitSize() instead of manual scale/translate

    • Solution: projection.fitSize([width, height], geoJSON)
  • Countries upside down: Y coordinates in wrong direction

    • Solution: Ensure projection range is correct (already handled by D3)
  • Dateline artifacts: Features crossing 180° longitude

    • Solution: Use antimeridian clipping or split features
  • Performance issues: Too many vertices

    • Solution: Simplify TopoJSON with mapshaper or use .precision()
  • Projection returns null: Coordinate outside clip bounds

    • Solution: Check .clipAngle() or handle null values

Performance Tips

  1. Use TopoJSON - Smaller file size, shared boundaries
  2. Simplify geometries - Use mapshaper or topojson-simplify
  3. Set appropriate precision - Balance quality vs. speed
  4. Cache path generator - Reuse for all features
  5. Use canvas rendering - For large/complex maps
  6. Limit feature count - Filter or aggregate for interactive maps

Notes

  • Documentation covers D3 v7 (latest version)
  • All projections support .fitSize(), .fitExtent(), .fitWidth(), .fitHeight()
  • Orthographic projection shows one hemisphere at a time
  • AlbersUsa is composite projection (mainland + Alaska + Hawaii)
  • GeoJSON uses [longitude, latitude] order (not lat/lon!)
  • Spherical calculations use radians (multiply by Earth radius for km)
  • TopoJSON is more efficient than GeoJSON for maps
  • File paths reference local documentation cache
  • For latest updates, check https://d3js.org/d3-geo