是否有使用D3.js在折线图上找到近似的x和y坐标值的函数?

问题描述 投票:0回答:1

我已经使用D3生成了折线图。我知道我可以使用biject功能来获取我在数据中指定的x和y坐标的精确值,但是我有兴趣查找与x或y值相关的近似x和y值。我的数据集的最小-最大范围。使用D3.js可以做到吗?

javascript d3.js svg linegraph
1个回答
0
投票

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>
© www.soinside.com 2019 - 2024. All rights reserved.