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

D3.js新手求助:如何将现有多折线图代码修改为响应式版本

D3.js折线图响应式改造实现

前置CSS配置

首先给图表容器添加自适应样式,保证容器能跟随父级宽度变化:

#lineChart {
  width: 100%;
  max-width: 850px; /* 可按需设置最大宽度,避免宽屏时显示异常 */
}

核心改动说明

  • 移除硬编码的SVG宽高,改为动态读取父容器实际宽度,高度可按需求调整固定值或宽高比例
  • 新增窗口resize事件监听,搭配防抖逻辑避免频繁触发重绘,提升性能
  • 每次渲染前清空SVG已有内容,避免多次重绘出现元素叠加
  • 原来写死的圆点定位偏移量改为动态读取margin值,适配不同宽高下的定位逻辑
  • 优化了原有yScale赋值的冗余循环逻辑,新增数组reduce初始值避免空数据报错

修改后完整代码

// 全局存储图表数据,resize时无需重复请求接口
let chartData = null;
const margin = { top: 60, right: 40, bottom: 88, left: 105 };

// 防抖函数,控制resize重绘频率
function debounce(func, delay = 300) {
  let timer = null;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => func.apply(this, args), delay);
  }
}

const render = () => {
  if (!chartData) return;
  
  // 动态获取容器尺寸
  const container = d3.select("#lineChart");
  const width = container.node().clientWidth;
  const height = 400; // 高度可自行修改,也可按比例计算例如 width * 0.47
  const innerWidth = width - margin.left - margin.right;
  const innerHeight = height - margin.top - margin.bottom;
  
  // 复用/初始化SVG,清空旧内容
  const svg = container.selectAll("svg").data([null]).join("svg")
    .attr("width", width)
    .attr("height", height);
  svg.selectAll("*").remove();
  
  const xValue = (d) => d.dateTest;
  const yValue = (d) => d.total;

  const xScale = d3
    .scaleTime()
    .domain(d3.extent(chartData, xValue))
    .range([0, innerWidth])
    .nice();
  let yScale;

  const totalArray = chartData.map(element => element.total);
  const reducer = (accumulator, currentValue) => accumulator + currentValue;
  const tc = totalArray.reduce(reducer, 0); // 加初始值避免空数组报错

  if (tc === 0) {
    yScale = d3
      .scaleLinear()
      .domain([0, 51])
      .range([innerHeight, 0])
      .nice();
  } else {
    yScale = d3
      .scaleLinear()
      .domain(d3.extent(chartData, yValue))
      .range([innerHeight, 0])
      .nice();
  }

  const g = svg
    .append("g")
    .attr("transform", `translate(${margin.left},${margin.top})`);

  const xAxis = d3
    .axisBottom(xScale)
    .tickPadding(15)
    .ticks(d3.timeDay)
    .tickFormat(d3.timeFormat("%a %d %B %y"));

  const yAxis = d3.axisLeft(yScale).tickSize(-innerWidth).tickPadding(10);

  const yAxisG = g.append("g").call(yAxis);
  yAxisG.selectAll(".domain").remove();

  yAxisG
    .append("text")
    .attr("class", "axis-label")
    .attr("y", -60)
    .attr("x", -innerHeight / 2)
    .attr("fill", "black")
    .attr("transform", `rotate(-90)`)
    .attr("text-anchor", "middle");

  const xAxisG = g
    .append("g")
    .call(xAxis)
    .attr("transform", `translate(0,${innerHeight})`);

  xAxisG.select(".domain").remove();

  xAxisG
    .append("text")
    .attr("class", "axis-label")
    .attr("y", 80)
    .attr("x", innerWidth / 2)
    .attr("fill", "black");

  const areaGenerator = d3
    .area()
    .x((d) => xScale(xValue(d)))
    .y0(innerHeight)
    .y1((d) => yScale(yValue(d)))
    .curve(d3.curveMonotoneX);

  g.append("path")
    .attr("class", "area-path")
    .attr("d", areaGenerator(chartData));

  const lineGenerator = d3
    .line()
    .x((d) => xScale(xValue(d)))
    .y((d) => yScale(yValue(d)))
    .curve(d3.curveMonotoneX);

  g.append("path")
    .attr("class", "line-path")
    .attr("d", lineGenerator(chartData));

  svg.append("line").attr("class", "line-dashet");

  svg
    .selectAll("circle")
    .data(chartData)
    .join("circle")
    .attr("cx", function (d) {
      return xScale(xValue(d)) + margin.left; // 动态取margin值不硬编码
    })
    .attr("cy", function (d) {
      return yScale(yValue(d)) + margin.top; // 动态取margin值不硬编码
    })
    .attr("r", 4)
    .on("mouseout", function (e) {
      const toolTip = document.getElementsByClassName("tooltip")[0];
      toolTip.classList.remove("tooltip-open");
    })
    .on("mouseover", function (e) {
      const selectPoint = d3.select(this);
      const xLineAnim = selectPoint._groups[0][0].cx.animVal.value;
      const yLineAnim = selectPoint._groups[0][0].cy.animVal.value;
      const selectPointData = selectPoint._groups[0][0].__data__;
      const dateFormated = formatDate(selectPointData.dateTest, a, "-"); // 保留原有自定义方法
      const toolTip = document.getElementsByClassName("tooltip")[0];

      toolTip.style.top = yLineAnim - 55 + "px";
      toolTip.style.left = xLineAnim + 5 + "px";
      toolTip.innerHTML =
        "<div><span>Day: </span>" +
        dateFormated +
        "</div><div><span>Total: </span>" +
        selectPointData.total +
        "</div>";
      toolTip.classList.add("tooltip-open");

      svg
        .select(".line-dashet")
        .attr("x1", xLineAnim)
        .attr("y1", margin.top)
        .attr("x2", xLineAnim)
        .attr("y2", innerHeight + margin.top);
    });
};

// 原有数据请求逻辑,拿到数据后赋值给全局chartData再触发渲染
function getTimeData() {
  // 此处保留你原有请求数据的逻辑,请求成功后执行以下代码即可:
  // chartData = 接口返回的图表数据
  // render();
}

// 初始化加载
getTimeData();

// 监听窗口大小变化,防抖触发重绘
window.addEventListener('resize', debounce(render));

注:代码中formatDate、getTimeData为原有自定义逻辑,无需修改直接沿用即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 17:45:03