I'm pretty new to D3 and just incorporated the below tooltips into my application. I have both a single line chart as well as a multi line chart.
Single line: https://bl.ocks.org/alandunning/cfb7dcd7951826b9eacd54f0647f48d3
Multi Line: Multiseries line chart with mouseover tooltip
As you can see, the functionality of the two tooltips are different. The Single Line tooltip jumps from each data point whereas the Multi Line continually follows the chart path. I want to change the Multi Line functionality to mimic how the Single Line tooltip works.
Any help would be greatly appreciated. Please let me know if I need to provide more information. Also note that the data I'm working with is an array of arrays
Below is my code:
Single Line Chart:
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);
}
Multi Line Chart:
//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 +")";
});
});