D3 - 单折线图和多折线图工具提示

Jam*_*Lim 2 javascript charts svg d3.js

我对 D3 很陌生,只是将以下工具提示合并到我的应用程序中。我有一个单折线图和一个多折线图。

单行:https : //bl.ocks.org/alandunning/cfb7dcd7951826b9eacd54f0647f48d3

Multi Line:带有鼠标悬停工具提示的多系列折线图

如您所见,这两个工具提示的功能是不同的。Single Line 工具提示从每个数据点跳出,而 Multi Line 则继续跟随图表路径。我想更改多行功能以模仿单行工具提示的工作方式。

任何帮助将不胜感激。如果我需要提供更多信息,请告诉我。另请注意,我正在处理的数据是一组数组

下面是我的代码:

单线图:

let g = svg.append('g');
    g.append("path")
    .datum(this.dataObj)
    .attr("class",`line-${this.yAxisData} line`)
    .attr('d', line)
    .attr("stroke",`${this.color(this.dataObj.label)}`)
    .attr("fill",'none')
    .attr("transform",       `translate(${this.margin.left},${this.margin.top})`);

var focus = g.append("g")
    .attr("class", "focus")
    .style("display", "none");

    focus.append("line")
    .datum(this.dataObj)
    .attr("class", "x-hover-line hover-line")
    .attr("transform",`translate(${this.margin.left},${this.margin.top})`)
    .attr("stroke",`${this.color(this.dataObj.label)}`)
    .attr("y1", 0)
    .attr("y2", height);

    focus.append("circle")
    .datum(this.dataObj)
    .attr("transform",`translate(${this.margin.left},${this.margin.top})`)
    .attr("stroke",`${this.color(this.dataObj.label)}`)
    .attr("r", 7.5);

    focus.append("text")
    .attr("class","linetip")
    .attr("x", 40)
    .attr("dy", "0.5em");

    svg.append("rect")
    .attr("transform", `translate(${this.margin.left},${this.margin.top})`)
    .attr("class", "overlay")
    .attr("width", width)
    .attr("height", height)
    .on("mouseover", function() { focus.style("display", null); })
    .on("mouseout", function() { focus.style("display", "none"); })
    .on("mousemove", this.mousemove);

mousemove() {
    var bisectDate = d3.bisector(function(d) { return d.date; }).left;
    let mouse = d3.mouse(d3.event.currentTarget);
    let svg = d3.select(this.container);
    var x0 = this.x.invert(mouse[0]);
    var i = bisectDate(this.dataObj, x0);
    var d0 = this.dataObj[i - 1];
    var d1 = this.dataObj[i];
    var d = x0 - d0.date > d1.date - x0 ? d1 : d0;
    var focus = svg.select(".focus");
    focus.attr("transform", "translate(" + this.x(d[this.xAxisData]) + "," + this.y(d[this.yAxisData]) + ")");
    focus.select("text").text(`[${d[this.yAxisData]}]`);
    focus.select(".x-hover-line").attr("y2", this.height - this.y(d[this.yAxisData]));
    focus.select(".y-hover-line").attr("x2", this.width + this.width);
}
Run Code Online (Sandbox Code Playgroud)

多线图:

    //append paths
    let g = svg.append('g');
    let chartLines = g.selectAll('.lines')
    .data(this.dataObj)
    .enter()
    .append('g')
    .attr('class', 'lines');

    chartLines.append('path')
    .attr('class','line')
    .attr('d', d => {
        return line(d);
    })
    .attr('stroke', (d) => color(d[0].label))
    .attr('fill','none')
    .attr("transform", `translate(${this.margin.left},0)`);

    var mouseG = svg.append("g")
    .attr("class", "mouse-over-effects")

    mouseG.append("path") // this is the black vertical line to follow mouse
    .attr("class", "mouse-line")
    .style("stroke", "black")
    .style("stroke-width", "2px")
    .style("stroke-dasharray", "3,3")
    .style("opacity", "0");

    var mousePerLine = mouseG.selectAll('.mouse-per-line')
    .data(this.dataObj)
    .enter()
    .append("g")
    .attr("class", "mouse-per-line");

    mousePerLine.append("circle")
    .datum(d=>{return d})
    .attr("r", 7)
    .attr("stroke", (d,i) => {
        console.log(d)
        return `${this.color(d[i].label)}`
    })
    .style("fill", "none")
    .style("opacity", "0");

    mousePerLine.append("text")
    .datum(d=>{return d})
    .attr("transform", "translate(10,3)");

    mouseG.append('svg:rect') // append a rect to catch mouse movements on canvas
    .attr("transform", `translate(${this.margin.left},0)`)
    .attr('width', width) // can't catch mouse events on a g element
    .attr('height', height)
    .attr('fill', 'none')
    .attr('pointer-events', 'all')
    .on('mouseout', () => { // on mouse out hide line, circles and text
        d3.select(".mouse-line")
        .style("opacity", "0");
        d3.selectAll(".mouse-per-line circle")
        .style("opacity", "0");
        d3.selectAll(".mouse-per-line text")
        .style("opacity", "0");
    })
    .on('mouseover', () => { // on mouse in show line, circles and text
        d3.select(".mouse-line")
        .style("opacity", "1");
        d3.selectAll(".mouse-per-line circle")
        .style("opacity", "1");
        d3.selectAll(".mouse-per-line text")
        .style("opacity", "1");
    })
    .on('mousemove', () => {
        let mouse = d3.mouse(d3.event.currentTarget);
        d3.select(".mouse-line")
        .attr("d", () => {
            var d = "M" + mouse[0] + "," + height;
            d += " " + mouse[0] + "," + 0;
            return d;
        });
        d3.selectAll(".mouse-per-line")
        .attr("transform", (d, i) => {
            var lines = document.getElementsByClassName('line')
            var xDate = this.x.invert(mouse[0])
            var bisect = d3.bisector(function(d) { return d.date; }).right;
            var idx = bisect(this.dataObj, xDate);
            var beginning = 0,
            end = lines[i].getTotalLength()
            var target = null;

            while (true){
                var target = Math.floor((beginning + end) / 2);
                var pos = lines[i].getPointAtLength(target);
                if ((target === end || target === beginning) && pos.x !== mouse[0]) {
                    break;
                }
                if (pos.x > mouse[0])      end = target;
                else if (pos.x < mouse[0]) beginning = target;
                else break; //position found
            }

            d3.select('text')
            .text(this.y.invert(pos.y));

            return "translate(" + mouse[0] + "," + pos.y +")";
        });
    });
Run Code Online (Sandbox Code Playgroud)

Lui*_*dez 5

我将Mark的答案作为Multiseries 折线图的参考,其中包含您提供的鼠标悬停工具提示

基本上,您需要做的是将工具提示设置为在 x 轴数据的每个刻度上显示,因此与其用鼠标 [0] 抓取鼠标的位置并移动工具提示,您应该将其移动到x 轴数据是。

这是我所做更改的详细信息:

mouseG.append('svg:rect')
    .attr('width', width)
    .attr('height', height)
    .attr('fill', 'none')
    .attr('pointer-events', 'all')
    .on('mouseout', () => {
        d3.select(".mouse-line")
        .style("opacity", "0");
        d3.selectAll(".mouse-per-line circle")
        .style("opacity", "0");
        d3.selectAll(".mouse-per-line text")
        .style("opacity", "0");
    })
    .on('mouseover', () => {
        d3.select(".mouse-line")
        .style("opacity", "1");
        d3.selectAll(".mouse-per-line circle")
        .style("opacity", "1");
        d3.selectAll(".mouse-per-line text")
        .style("opacity", "1");
    })
    .on('mousemove', () => {
        let mouse = d3.mouse(d3.event.currentTarget);
        // MOVE THIS BEFORE THE RETURN
        // d3.select(".mouse-line")
        // .attr("d", () => {
        //     var d = "M" + mouse[0] + "," + height;
        //     d += " " + mouse[0] + "," + 0;
        //     return d;
        // });
        d3.selectAll(".mouse-per-line")
        .attr("transform", (d, i) => {
            var lines = document.getElementsByClassName('line')
            var xDate = this.x.invert(mouse[0])
            var bisect = d3.bisector(function(d) { return d.date; }).right;
            var idx = bisect(this.dataObj, xDate);

            // GET RID OF THIS
            // var beginning = 0,
            // end = lines[i].getTotalLength()
            // var target = null;

            // while (true){
            //     var target = Math.floor((beginning + end) / 2);
            //     var pos = lines[i].getPointAtLength(target);
            //     if ((target === end || target === beginning) && pos.x !== mouse[0]) {
            //         break;
            //     }
            //     if (pos.x > mouse[0])      end = target;
            //     else if (pos.x < mouse[0]) beginning = target;
            //     else break; //position found
            // }

            // REPLACE pos.y WITH y(d.values[idx].temperature)
            // AND mouse[0] WITH x(d.values[idx].date)
            d3.select('text')
            .text(this.y.invert(pos.y));

            return "translate(" + mouse[0] + "," + pos.y +")";
        });
    });
Run Code Online (Sandbox Code Playgroud)

下面是应用了更改的完整工作代码。对于这个片段,我曾经interpolate('linear')正确地显示值;如果使用interpolate('basis'),则工具提示和线条将无法正确匹配:

mouseG.append('svg:rect')
    .attr('width', width)
    .attr('height', height)
    .attr('fill', 'none')
    .attr('pointer-events', 'all')
    .on('mouseout', () => {
        d3.select(".mouse-line")
        .style("opacity", "0");
        d3.selectAll(".mouse-per-line circle")
        .style("opacity", "0");
        d3.selectAll(".mouse-per-line text")
        .style("opacity", "0");
    })
    .on('mouseover', () => {
        d3.select(".mouse-line")
        .style("opacity", "1");
        d3.selectAll(".mouse-per-line circle")
        .style("opacity", "1");
        d3.selectAll(".mouse-per-line text")
        .style("opacity", "1");
    })
    .on('mousemove', () => {
        let mouse = d3.mouse(d3.event.currentTarget);
        // MOVE THIS BEFORE THE RETURN
        // d3.select(".mouse-line")
        // .attr("d", () => {
        //     var d = "M" + mouse[0] + "," + height;
        //     d += " " + mouse[0] + "," + 0;
        //     return d;
        // });
        d3.selectAll(".mouse-per-line")
        .attr("transform", (d, i) => {
            var lines = document.getElementsByClassName('line')
            var xDate = this.x.invert(mouse[0])
            var bisect = d3.bisector(function(d) { return d.date; }).right;
            var idx = bisect(this.dataObj, xDate);

            // GET RID OF THIS
            // var beginning = 0,
            // end = lines[i].getTotalLength()
            // var target = null;

            // while (true){
            //     var target = Math.floor((beginning + end) / 2);
            //     var pos = lines[i].getPointAtLength(target);
            //     if ((target === end || target === beginning) && pos.x !== mouse[0]) {
            //         break;
            //     }
            //     if (pos.x > mouse[0])      end = target;
            //     else if (pos.x < mouse[0]) beginning = target;
            //     else break; //position found
            // }

            // REPLACE pos.y WITH y(d.values[idx].temperature)
            // AND mouse[0] WITH x(d.values[idx].date)
            d3.select('text')
            .text(this.y.invert(pos.y));

            return "translate(" + mouse[0] + "," + pos.y +")";
        });
    });
Run Code Online (Sandbox Code Playgroud)