You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

绘制D3图表后更新数据元素:日历视图逐年切换实现

Solution: Single-Year Calendar View with Year Switching Buttons

Here's how to modify your D3 Calendar View to display one year at a time and add seamless year-switching functionality:

Step 1: Load and Prepare Your Data Upfront

First, we'll load all your CSV data at once so we can filter it by year later. We'll parse dates and calculate global min/max values to keep color scaling consistent across all years:

// Define core dimensions and cell size
var width = 960,
    height = 136,
    cellSize = 17;

// Adjust date format to match your CSV's structure (e.g., "%m/%d/%Y" for MM/DD/YYYY)
var parseDate = d3.timeParse("%Y-%m-%d");

// Load and process CSV data
d3.csv("your-data-file.csv").then(function(rawData) {
  // Convert raw data into usable format: parse dates and numeric values
  var data = rawData.map(function(d) {
    return {
      date: parseDate(d.date),
      value: +d.value // Replace "value" with your CSV's actual value column name
    };
  });

  // Calculate global min/max values for consistent color scaling
  var minValue = d3.min(data, d => d.value);
  var maxValue = d3.max(data, d => d.value);

  // Create a color scale (swap interpolator for different schemes: e.g., d3.interpolateBlues)
  var colorScale = d3.scaleSequential(d3.interpolateViridis)
    .domain([minValue, maxValue]);

  // Initialize buttons and first calendar render
  setupYearButtons(data, colorScale);
  renderCalendar(data, 1990, colorScale);
});

Step 2: Add Year-Switching Buttons

Generate buttons for each year (1990-2010) that trigger a calendar update when clicked. We'll also add a visual highlight for the active year:

function setupYearButtons(data, colorScale) {
  var buttonContainer = d3.select("body").append("div")
    .style("margin-bottom", "10px");

  // Create a button for each year in your range
  d3.range(1990, 2011).forEach(function(year) {
    buttonContainer.append("button")
      .text(year)
      .style("margin-right", "5px")
      .on("click", function() {
        // Update calendar with the selected year
        renderCalendar(data, year, colorScale);
        // Highlight the active button
        d3.selectAll("button").classed("active", false);
        d3.select(this).classed("active", true);
      });
  });

  // Set initial active button to 1990
  d3.select("button:first-child").classed("active", true);
}

Add this CSS to style the active button:

button.active {
  background-color: #2196F3;
  color: white;
  border: none;
  padding: 4px 8px;
  border-radius: 4px;
}

rect {
  stroke: #ffffff;
}

Step 3: Render/Update the Calendar

Replace your original multi-SVG code with a single SVG that updates dynamically when the year changes. We'll use D3's enter/update/exit pattern to efficiently refresh only the necessary elements:

function renderCalendar(data, targetYear, colorScale) {
  // Filter full dataset to only the selected year
  var yearData = data.filter(d => d.date.getFullYear() === targetYear);
  
  // Create a quick-lookup map for values by date string
  var valueMap = d3.map(yearData, d => d3.timeFormat("%Y-%m-%d")(d.date));
  
  // Get every day in the target year
  var days = d3.timeDays(new Date(targetYear, 0, 1), new Date(targetYear + 1, 0, 1));

  // Select or create the main SVG container (ensures only one SVG exists)
  var svg = d3.select("body").selectAll("svg.calendar").data([0]);
  
  // Enter: create SVG if it doesn't already exist
  var svgEnter = svg.enter()
    .append("svg")
    .attr("class", "calendar")
    .attr("width", width)
    .attr("height", height)
    .append("g")
    .attr("transform", `translate(${(width - cellSize * 53) / 2}, ${height - cellSize * 7 - 1})`);

  // Merge enter and existing SVG groups
  var calendarGroup = svgEnter.merge(svg.select("g"));

  // Bind days to rectangle elements
  var dayCells = calendarGroup.selectAll("rect")
    .data(days);

  // Enter: create new day cells if needed
  dayCells.enter()
    .append("rect")
    .attr("width", cellSize)
    .attr("height", cellSize)
    .attr("x", d => d3.timeWeek.count(d3.timeYear(d), d) * cellSize)
    .attr("y", d => d.getDay() * cellSize)
    .merge(dayCells) // Merge enter and existing cells to update styles
    .attr("fill", d => {
      var value = valueMap.get(d3.timeFormat("%Y-%m-%d")(d));
      return value ? colorScale(value) : "#eeeeee"; // Light gray for days with no data
    });

  // Exit: remove any extra cells (rare, since most years have ~365 days)
  dayCells.exit().remove();
}

Key Changes From Your Original Code

  • Single SVG Container: Instead of creating an SVG per year, we use one reusable SVG that updates dynamically.
  • Data Filtering: We filter the full dataset to the selected year on each button click, avoiding redundant data requests.
  • Efficient Updates: The enter/update/exit pattern ensures we only modify elements that need to change, making the UI responsive.
  • Consistent Scaling: Using global min/max values keeps color interpretation consistent across all years, so values are comparable.

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:57:26