如何在D3.js聚焦上下文多线图中限制画笔对齐离散日期?
D3.js聚焦-上下文多线图:实现画笔吸附离散日期需求
我基于D3.js实现了带有聚焦区(focus)和上下文区(context)的多线图,可通过上下文区的画笔(brush)选择不同日期范围。但我的数据仅包含每月1日的日期,当前画笔允许在任意日期间选择,我需要让画笔吸附到数据中的离散日期上,这样后续才能方便地将选中数据导出到表格。之前找到过一个基于D3v3的示例,但无法适配我的D3v7项目,寻求技术解决方案。
HTML代码
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script> <div id="timeseries-container"></div>
CSS代码
.line { stroke: #333; fill: none; clip-path: url(#clip); } .zoom { cursor: move; fill: none; pointer-events: all; } .handle { fill: #4D4E56; stroke: #fff; stroke-opacity: 0.5; stroke-width: 1.25px; }
JavaScript代码
let timeW = 960, timeH = 500 let timeMargin = { top: 20, right: 250, bottom: 130, left: 60 }, timeMargin2 = { top: 410, right: 250, bottom: 30, left: 60 }, timeWidth = timeW - timeMargin.left - timeMargin.right, timeHeight = timeH - timeMargin.top - timeMargin.bottom, timeHeight2 = timeH - timeMargin2.top - timeMargin2.bottom; var parseDate = d3.timeParse("%Y-%m-%d"); let timeseries = d3.select("#timeseries-container").append('svg') .attr('id', 'timeseries') .attr("width", timeWidth + timeMargin.left + timeMargin.right) .attr("height", timeHeight + timeMargin.top + timeMargin.bottom) var graph = timeseries.append('g').attr('transform', 'translate(' + timeMargin.left + ',' + timeMargin.top + ')'); var x2 = d3.scaleTime().range([0, timeWidth]), x3 = d3.scaleTime().range([0, timeWidth]), y2 = d3.scaleLinear().range([timeHeight, 0]), y3 = d3.scaleLinear().range([timeHeight2, 0]); var xAxis = d3.axisBottom(x2), xAxis2 = d3.axisBottom(x3), yAxis = d3.axisLeft(y2); var brush = d3.brushX() .extent([[0, 0], [timeWidth, timeHeight2]]) .on("brush end", brushed); var zoom = d3.zoom() .scaleExtent([1, Infinity]) .translateExtent([[0, 0], [timeWidth, timeHeight]]) .extent([[0, 0], [timeWidth, timeHeight]]) .on("zoom", zoomed); var line = d3.line() .x(function (d) { return x2(d.date); }) .y(function (d) { return y2(d.value); }); var line2 = d3.line() .x(function (d) { return x3(d.date); }) .y(function (d) { return y3(d.value); }); timeseries.append("defs").append("clipPath") .attr("id", "clip") .append("rect") .attr("width", timeWidth) .attr("height", timeHeight); var focus = timeseries.append("g") .attr("class", "focus") .attr("transform", "translate(" + timeMargin.left + "," + timeMargin.top + ")"); var context = timeseries.append("g") .attr("class", "context") .attr("transform", "translate(" + timeMargin2.left + "," + timeMargin2.top + ")"); d3.csv("https://raw.githubusercontent.com/sprucegoose1/sample-data/main/sample.csv").then(function (data) { data.forEach((d) => { d.date = parseDate(d.date); d.value = +d.value; }) x2.domain(d3.extent(data, function (d) { return d.date; })); y2.domain([0, d3.max(data, function (d) { return d.value; })]); x3.domain(x2.domain()); y3.domain(y2.domain()); const dataNest = d3.group(data, d => d.state) const seriesColors = ["#003F5C", "#38CFE8"] var color = d3.scaleOrdinal() .range(seriesColors); focus .selectAll("path") .data(dataNest) .enter().append("path") .attr('class', 'groups') .attr("d", d => { d.line = this; return line(d[1]); }) .style("stroke", d => color(d[0])) .style("stroke-width", 1) .style('fill', 'none') .attr("clip-path", "url(#clip)") focus.append("g") .attr("class", "axis axis--x") .attr("transform", "translate(0," + timeHeight + ")") .call(xAxis); focus.append("g") .attr("class", "axis axis--y") .call(yAxis); context .selectAll("path") .data(dataNest) .enter().append("path") .attr('class', 'groups') .attr("d", d => { d.line = this; return line2(d[1]); }) .style("stroke", d => color(d[0])) .style("stroke-width", 1) .style('fill', 'none') .attr("clip-path", "url(#clip)") context.append("g") .attr("class", "axis axis--x") .attr("transform", "translate(0," + timeHeight2 + ")") .call(xAxis2); context.append("g") .attr("class", "brush") .call(brush) .call(brush.move, x2.range()); }); let zoombrush; function brushed(event) { var s = event.selection || x2.range(); if (s[1] === s[0]) s[1] += .5; if (event.mode === "move") { var extentlength = Math.round(s[1] - s[0]) event.target.extent([Math.round(s[0] + 0.5) - 0.5, Math.round(s[0] + 0.5) - 0.5 + extentlength]) } else { event.target.extent([Math.round(s[0] + 0.5) - 0.5, Math.round(s[1] + 0.5) - 0.5]) } event.target(d3.select(this)) x2.domain(s.map(x3.invert, x3)); focus.selectAll(".groups") .attr("d", d => { d.line = this; return line(d[1]); }) focus.select(".axis--x").call(xAxis); timeseries.select(".zoom").call(zoom.transform, d3.zoomIdentity .scale(timeWidth / (s[1] - s[0])) .translate(-s[0], 0)); zoombrush = 0; } brush.on('end', (event, d) => { }) function zoomed(event) { if (zoombrush) return; // ignore zoom-by-brush zoombrush = 1; var t = event.transform; x.domain(t.rescaleX(x2).domain()); focus.select(".groups").attr("d", line); focus.select(".axis--x").call(xAxis); context.select(".brush").call(brush.move, x.range().map(t.invertX, t)); zoombrush = 0; }
内容的提问来源于stack exchange,提问作者sprucegoose
相关产品推荐
相关产品推荐

