Plotting points on a map with D3

Learn plotting points on a map with d3 with practical examples, diagrams, and best practices. Covers javascript, svg, d3.js development techniques with visual explanations.

Plotting Geographic Points on a Map with D3.js

Hero image for Plotting points on a map with D3

Learn how to visualize geographic data by plotting points on an interactive map using D3.js, SVG, and GeoJSON.

D3.js (Data-Driven Documents) is a powerful JavaScript library for manipulating documents based on data. While often associated with charts and graphs, D3.js excels at creating interactive geographic visualizations. This article will guide you through the process of plotting individual data points, such as cities or event locations, onto a world map using D3's geo functionalities and SVG elements.

Understanding Geographic Projections and Data

Before plotting points, it's crucial to understand how D3 handles geographic data. D3 uses various geographic projections to transform 3D spherical coordinates (latitude and longitude) into 2D Cartesian coordinates suitable for display on a screen. The choice of projection depends on the area you're mapping and the visual effect you want to achieve. We'll typically work with GeoJSON data, which is a standard format for encoding geographic data structures.

flowchart TD
    A[Raw Geographic Data (Lat/Lon)] --> B{D3 Geo Projection}
    B --> C[2D Screen Coordinates (x, y)]
    C --> D[SVG Circle Element]
    D --> E[Rendered Point on Map]

Workflow for plotting a geographic point with D3.js

Setting Up Your D3 Map Base

The first step is to create an SVG container and load your base map data, typically a GeoJSON file representing countries or continents. D3's d3.geoPath() generator will translate the GeoJSON features into SVG path elements. For this example, we'll assume you have a world.json (TopoJSON or GeoJSON) file available.

const width = 960;
const height = 600;

const svg = d3.select("#map-container")
  .append("svg")
  .attr("width", width)
  .attr("height", height);

const projection = d3.geoMercator()
  .scale(150)
  .center([0, 0])
  .translate([width / 2, height / 2]);

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

d3.json("world.json").then(world => {
  svg.append("g")
    .attr("class", "countries")
    .selectAll("path")
    .data(topojson.feature(world, world.objects.countries).features)
    .enter().append("path")
    .attr("d", pathGenerator)
    .attr("fill", "#ccc")
    .attr("stroke", "#fff");
});

Initializing the SVG and drawing the base map

Plotting Individual Data Points

Once your base map is rendered, you can add your data points. Each point typically has a latitude and longitude. You'll use the same projection function to convert these coordinates into screen x, y coordinates. Then, you can append SVG circles or other shapes at these calculated positions.

const dataPoints = [
  { name: "New York", lat: 40.7128, lon: -74.0060 },
  { name: "London", lat: 51.5074, lon: -0.1278 },
  { name: "Tokyo", lat: 35.6895, lon: 139.6917 },
  { name: "Sydney", lat: -33.8688, lon: 151.2093 }
];

svg.append("g")
  .attr("class", "points")
  .selectAll("circle")
  .data(dataPoints)
  .enter().append("circle")
  .attr("cx", d => projection([d.lon, d.lat])[0])
  .attr("cy", d => projection([d.lon, d.lat])[1])
  .attr("r", 5)
  .attr("fill", "red")
  .attr("stroke", "white")
  .attr("stroke-width", 1)
  .append("title") // Add tooltips for interactivity
  .text(d => d.name);

Adding data points as SVG circles to the map

Adding Interactivity and Styling

D3 allows for rich interactivity. You can add event listeners to your points for hover effects, clicks, or drag functionality. Styling can be done directly with SVG attributes or via CSS classes for better separation of concerns.

.points circle {
  cursor: pointer;
  transition: fill 0.2s ease-in-out;
}

.points circle:hover {
  fill: blue;
  stroke-width: 2;
}

Basic CSS for styling and hover effects on data points

// Extend the previous D3 code to add event listeners
svg.selectAll(".points circle")
  .on("click", function(event, d) {
    alert(`Clicked on: ${d.name}`);
  });

Adding a click event listener to the data points