为带时间轴的D3.js散点图添加缩放功能失败,求排查问题
D3.js时间轴散点图缩放交互修复指南
问题排查与修复点
1. 缩放触发区域限制
原代码中给触发缩放的透明矩形添加了translate(padding,0),导致只有SVG右侧区域能响应缩放操作。修复:移除该transform属性,让整个SVG区域都能接收缩放事件。
2. 数据字段引用错误
updateChart函数里误用了示例中的鸢尾花数据字段(d.Sepal_Length、d.Petal_Length),实际你的数据字段是date和values。修复:替换为正确的字段名。
3. 缺失X轴动态时间格式化
原代码固定了X轴格式为%b %e(月日),未实现"缩小时显示年份、放大时显示时分秒"的需求。修复:使用动态格式化函数,根据当前X轴的时间范围自动切换格式:
- 时间跨度超过1年时,显示年份
- 跨度在1天到1年之间,显示月日
- 跨度小于1天,显示时分秒
4. 轴更新未重新渲染
更新轴的scale后,没有重新调用轴的渲染方法,导致轴刻度不会更新。修复:提前保存轴元素的引用,更新scale后重新调用call(xAxis)和call(yAxis)。
5. ClipPath范围错误
原clipPath的矩形覆盖了整个SVG,但实际绘图区域是从padding开始的,导致边缘的点被错误裁剪。修复:调整clipPath的x为padding,宽度为w - 2*padding,高度为h - 2*padding,匹配实际绘图区域。
完整修正代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>D3: Time axis with zoom</title> <script type="text/javascript" src="http://d3js.org/d3.v7.min.js" charset="utf-8"></script> <style type="text/css"> .axis path, .axis line{ stroke: black; shape-rendering: crispEdges; } .axis text{ font-family: Optima, Futura, sans-serif; font-weight: bold; font-size: 8px; fill: black; } </style> </head> <body> <script type="text/javascript"> var w = 500; var h = 300; var padding = 60; var dataset, xScale, yScale, xAxis, yAxis; dataset = {"2022-12-30 07:00:42": [16777216.0, 16777216.0], "2022-12-30 04:58:28": [10977.28, 184483.84], "2022-12-29 05:27:05": [422004.0, 489.0], "2022-12-30 12:23:38": [0.0, 0.0], "2022-12-30 07:29:13": [0.0, 2147483647.0], "2022-12-27 14:04:55": [1638.4, 2686.976], "2022-12-30 12:49:34": [4030.464, 2359.296], "2022-12-30 14:22:16": [0.0], "2022-12-30 14:22:34": [0.0], "2022-12-30 05:17:38": [0.197803, 5898.24], "2022-12-30 13:50:11": [62914560.0, 62914560.0], "2023-01-25 11:37:05": [0.0, 610.0], "2022-11-07 14:07:54": [436142.08, 5701.632]} var parseTime = d3.timeParse("%Y-%m-%d %H:%M:%S"); // 动态时间格式化函数 var dynamicTimeFormat = function(date) { var range = xScale.domain()[1] - xScale.domain()[0]; var yearRange = 365 * 24 * 60 * 60 * 1000; // 1年的毫秒数 var dayRange = 24 * 60 * 60 * 1000; // 1天的毫秒数 if (range > yearRange) { return d3.timeFormat("%Y")(date); } else if (range > dayRange) { return d3.timeFormat("%b %e")(date); } else { return d3.timeFormat("%H:%M:%S")(date); } }; var formattedData = Object.entries(dataset).map(([key, value]) => { return { date: parseTime(key), values: value }; }); var startDate = d3.min(formattedData, function(d) { return d.date; }); var endDate = d3.max(formattedData, function(d) { return d.date; }); xScale = d3.scaleTime() .domain([ d3.timeDay.offset(startDate, -1), d3.timeDay.offset(endDate, 1) ]) .range([padding, w - padding]); yScale = d3.scaleLinear() .domain([ d3.min(formattedData, function(d) { return d3.min(d.values);}), d3.max(formattedData, function(d) { return d3.max(d.values);}) ]) .range([h - padding, padding]); // 定义X轴时使用动态格式化 xAxis = d3.axisBottom() .scale(xScale) .ticks(9) .tickFormat(dynamicTimeFormat); yAxis = d3.axisLeft() .scale(yScale) .ticks(10); var svg = d3.select("body") .append("svg") .attr("width", w) .attr("height", h); // 保存X轴和Y轴的引用,方便后续更新 var xAxisGroup = svg.append("g") .attr("class", "axis") .attr("transform", "translate(0," + (h - padding) + ")") .call(xAxis); var yAxisGroup = svg.append("g") .attr("class", "axis") .attr("transform", "translate(" + padding + ",0)") .call(yAxis); // 修正ClipPath范围 var clip = svg.append("defs").append("clipPath") .attr("id", "clip") .append("rect") .attr("width", w - 2*padding ) .attr("height", h - 2*padding ) .attr("x", padding) .attr("y", padding); var scatter = svg.append('g') .attr("clip-path", "url(#clip)") scatter.selectAll("circle") .data(formattedData) .join("g") .selectAll("circle").data(d => d.values.map(values => ({ date: d.date, values }))) .join("circle") .attr("cx", function(d) { return xScale(d.date); }) .attr("cy", function(d) { return yScale(d.values); }) .attr("r", 2) .style("fill", "#69b3a2"); var zoom = d3.zoom() .scaleExtent([.5, 20]) .extent([[0, 0], [w, h]]) .on("zoom", updateChart); // 移除transform,让整个SVG区域响应缩放 svg.append("rect") .attr("width", w) .attr("height", h) .style("fill", "none") .style("pointer-events", "all") .call(zoom); function updateChart({transform}) { var newX = transform.rescaleX(xScale); var newY = transform.rescaleY(yScale); // 更新X轴的domain,用于动态格式化判断 xScale.domain(newX.domain()); xAxis.scale(newX); yAxis.scale(newY); // 重新渲染轴 xAxisGroup.call(xAxis); yAxisGroup.call(yAxis); // 更新圆点位置,使用正确的字段 scatter .selectAll("circle") .attr('cx', function(d) {return newX(d.date)}) .attr('cy', function(d) {return newY(d.values)}); } </script> </body> </html>
内容的提问来源于stack exchange,提问作者guoqing chen
相关产品推荐
相关产品推荐

