You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何为D3.js制作的Choropleth Map添加年份滑块更新数据

Adding a Year Slider to Your D3.js Choropleth Map

Hey there! Let's get that year slider working so you can toggle between your crime data years smoothly. Here's a step-by-step breakdown of how to modify your existing code:

Step 1: Set Up Slider & Year Mapping

First, we'll create a slider (plus a label to show the active year) and map each year to its corresponding data property name in your CSV:

// Define year options and their matching data keys
const yearOptions = [2010, 2011, 2012, 2013, 2014, 2015];
const yearToDataKey = {
  2010: 'ten',
  2011: 'eleven',
  2012: 'twelve',
  2013: 'thirteen',
  2014: 'fourteen',
  2015: 'fifteen'
};

// Create slider container in the DOM
const sliderWrapper = d3.select("body").append("div")
  .style("margin", "2rem auto")
  .style("width", "350px")
  .style("text-align", "center");

// Add year display label
const currentYearLabel = sliderWrapper.append("span")
  .attr("id", "active-year")
  .style("font-size", "1.2rem")
  .style("margin-right", "10px")
  .text("2010");

// Add the range slider input
const yearSlider = sliderWrapper.append("input")
  .attr("type", "range")
  .attr("min", 0)
  .attr("max", yearOptions.length - 1)
  .attr("value", 0)
  .style("width", "280px");

Step 2: Create a Reusable Map Update Function

Instead of hardcoding i.ten everywhere, we'll make a function that refreshes the map colors and tooltips based on the selected year:

function refreshMap(selectedDataKey) {
  // Update color scale to match the selected year's data range
  const maxCrimes = d3.max(data, d => d[selectedDataKey]);
  color.domain([1, maxCrimes]);

  // Update state fill colors
  svg.selectAll(".states path")
    .attr("fill", function(d) {
      let fillColor = "black";
      data.forEach(row => {
        if (row.states === d.properties.name) {
          fillColor = color(row[selectedDataKey]);
        }
      });
      return fillColor;
    });

  // Update tooltip content to reflect selected year
  tip.html(function(d) {
    let region = "";
    let crimeCount = 0;
    data.forEach(row => {
      if (row.states === d.properties.name) {
        region = row.region;
        crimeCount = row[selectedDataKey];
      }
    });
    return `<div>State: ${d.properties.name}</div>
            <div>Region: ${region}</div>
            <div>NumCrimes: ${crimeCount}</div>`;
  });
}

Step 3: Initialize Map & Bind Slider Events

Modify your ready function to start with 2010 data, then add an event listener to the slider to trigger updates when it's adjusted:

function ready([us]) {
  // Initialize tooltip
  tip = d3.tip().attr('class', 'd3-tip');
  svg.call(tip);

  // Add state paths (remove hardcoded fill logic — refreshMap handles this)
  svg.append("g")
    .attr("class", "states")
    .selectAll("path")
    .data(topojson.feature(us, us.objects.states).features)
    .enter().append("path")
    .attr("d", path)
    .on('mouseover', tip.show)
    .on('mouseout', tip.hide);

  // Add state borders (keep your existing code here)
  svg.append("path").attr("class", "state-borders")
    .datum(topojson.mesh(us, us.objects.states, (a, b) => a !== b))
    .attr("d", path);

  // Initialize map with 2010 data
  refreshMap(yearToDataKey[2010]);

  // Slider change event handler
  yearSlider.on("input", function() {
    const selectedIndex = +this.value;
    const selectedYear = yearOptions[selectedIndex];
    const dataKey = yearToDataKey[selectedYear];

    // Update the year label
    currentYearLabel.text(selectedYear);
    // Refresh the map with selected year's data
    refreshMap(dataKey);
  });
}

Step 4: Simplify Data Loading

Clean up your CSV loading callback to avoid repetitive code:

var promises = [
  d3.json("stateborder.json"),
  d3.dsv(",", "stateInfo.csv", d => ({
    states: d.States,
    region: d.Region,
    ten: d['2010'],
    eleven: d['2011'],
    twelve: d['2012'],
    thirteen: d['2013'],
    fourteen: d['2014'],
    fifteen: d['2015']
  }))
];

// Store CSV data correctly and initialize the map
Promise.all(promises).then(([us, csvData]) => {
  data = csvData;
  ready([us]);
});

Quick Notes:

  • The yearToDataKey object makes it easy to link human-readable years to your data's property names, so you don't have to hardcode values everywhere.
  • Centralizing the map update logic in refreshMap keeps your code DRY (Don't Repeat Yourself) and ensures the map and tooltips stay in sync.
  • The slider uses index values to map to your year array, making it easy to add/remove years later if needed.

内容的提问来源于stack exchange,提问作者Poskaz

火山引擎 最新活动