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
相关产品推荐
相关产品推荐

