我已经使用D3生成了折线图。我知道我可以使用biject功能来获取我在数据中指定的x和y坐标的精确值,但是我有兴趣查找与x或y值相关的近似x和y值。我的数据集的最小-最大范围。使用D3.js可以做到吗?
path.getPointAtLength方法可用于获取路径上的x和y坐标,并且可以递归调用此方法以获取与指定x坐标相等或非常接近的y坐标。
在下面的示例中,我已经预先计算了图表宽度中每个x的所有y坐标。通过每次每次将鼠标移到图表上时都查找y值,可以提高鼠标悬停的性能(悬停是使用不可见的rect元素捕获的。)
const height = 500 const width = 500 const margin = { "top": 20, "bottom": 20, "left": 20, "right": 20 } const data = [2, 5, 6, 7, 3, 8, 3, 4] let xScale = d3.scaleLinear() .domain([0, data.length - 1]) .range([0, width]) let yScale = d3.scaleLinear() .domain([0, 10]) .range([height, 0]) let xAxis = d3.axisBottom(xScale) let yAxis = d3.axisLeft(yScale) let curve = d3.curveCatmullRom.alpha(0.5) let line = d3.line() .x(function (d, i) { return xScale(i) }) .y(function (d) { return yScale(d) }) .curve(curve); let svg = d3.select("body").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) let g = svg.append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")") g.append("g") .attr("transform", "translate(0," + height + ")") .call(xAxis) g.append("g").call(yAxis) let path = g.append("path") .datum(data) .attr("d", line) let pathNode = path.node() let pathNodeLength = pathNode.getTotalLength() //for every x coordinate, get the y coordinates for the line //and store for use later on let allCoordinates = [] let x = 0; for (x; x < width; x++) { let obj = {} obj.y = findY(pathNode, pathNodeLength, x, width) allCoordinates.push(obj) } let dots = g.selectAll(".dot") .data([1]) //create one circle for later use .enter() .append("g") .style("opacity", 0) dots.append("circle") .attr("r", 8) dotsBgdText = dots.append("text") .attr("class", "text-bgd") .attr("x", 0) dotsText = dots.append("text") .attr("class", "text-fgd") .attr("x", 0) //Add a rect to handle mouse events let rect = g.append("rect") .attr("width", width - 1) // minus 1 so that it doesn't return an x = width, as the coordinates is 0 based. .attr("height", height) .style("opacity", 0) .on("mousemove", function (d) { let mouseX = d3.mouse(this)[0] let dotsData = [ { "cx": mouseX, "cy": allCoordinates[mouseX].y} ] dots.data(dotsData) .attr("transform", function (d) { return "translate(" + d.cx + "," + d.cy + ")" }) .style("opacity", 1) dotsText.data(dotsData) .text(function (d) { return roundNumber(yScale.invert(d.cy)); }) .attr("y", function (d) { let maxY = d3.max(dotsData, function (e) { return e.cx == d.cx ? e.cy : 0 }) return d.cy == maxY ? 27 : -15; }) dotsBgdText.data(dotsData) .text(function (d) { return roundNumber(yScale.invert(d.cy)); }) .attr("y", function (d) { let maxY = d3.max(dotsData, function (e) { return e.cx == d.cx ? e.cy : 0 }) return d.cy == maxY ? 27 : -15; }) }) //iteratively search a path to get a point close to a desired x coordinate function findY(path, pathLength, x, width) { const accuracy = 1 //px const iterations = Math.ceil(Math.log10(accuracy/width) / Math.log10(0.5)); //for width (w), get the # iterations to get to the desired accuracy, generally 1px let i = 0; let nextLengthChange = pathLength / 2; let nextLength = pathLength / 2; let y = 0; for (i; i < iterations; i++) { let pos = path.getPointAtLength(nextLength) y = pos.y nextLength = x < pos.x ? nextLength - nextLengthChange : nextLength + nextLengthChange nextLengthChange = nextLengthChange / 2 } return y } function roundNumber(n) { return Math.round(n * 100) / 100 }
path { stroke: black; fill: none; } .text-bgd { stroke: white; fill: white; stroke-width: 3; text-anchor: middle; } .text-fgd { stroke: none; text-anchor: middle; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>