【问题标题】:Is there a function to find the approximate x and y co-ordinate values on a line chart using D3.js?是否有使用 D3.js 在折线图上查找近似 x 和 y 坐标值的功能?
【发布时间】:2020-04-04 07:20:44
【问题描述】:

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

【问题讨论】:

  • 你用什么代码来绘制图表?
  • 为什么需要 D3 ——你想直观地显示它吗?将鼠标悬停在线上吗?

标签: javascript d3.js svg linegraph


【解决方案1】:

方法 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;
}
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"&gt;&lt;/script&gt;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-12-13
    • 1970-01-01
    • 1970-01-01
    • 2012-07-07
    • 1970-01-01
    • 2012-12-18
    • 1970-01-01
    相关资源
    最近更新 更多