我最初可能没有很好地解释自己要完成的事情。我想将https://jsfiddle.net/qvf8hn0u/的工具提示功能(代码如下)合并到我的多系列折线图中。 (见下面的代码)。在将鼠标悬停在图表上时在浏览器中查看工具提示代码时,会出现2个工具提示,其中x和y值与图表上的数据相关联。我的目标是根据所选类别的数量在多系列折线图中完成相同的功能。在多系列图表中,可以从图表右侧的类别列表中进行选择。应用工具提示功能后,它将显示x和y数据的值。如果我选择一个类别,它只会显示所选类别的数据,如果我选择2个或更多,它将显示所选类别的数据。我希望这可以解决任何最初的困惑。我们将非常感谢任何协助。
工具提示:
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>Tooltip</title>
<style>
text.inner-circle {
font-weight: 400;
font-size: 12px;
text-transform: uppercase;
}
text.inner-text {
font-weight: 400;
font-size: 36px;
font-family: 'Metric Regular', 'Metric';
text-align: center;
font-style: normal;
text-transform: uppercase;
}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 2;
shape-rendering: crispEdges;
}
.grid .tick {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.js"></script>
<script>
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var z = d3.scale.category20c();
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var parseDate = d3.time.format("%Y-%m-%dT%H:%M:%S.%LZ");
var data = [{
data: [
["2016-01-20T05:31:17.000Z", 95.9, {}],
["2016-01-20T05:31:47.000Z", 95.9, {}],
["2016-01-20T05:32:17.000Z", 95.4, {}],
["2016-01-20T05:32:47.000Z", 96.1, {}],
["2016-01-20T05:33:17.000Z", 95.7, {}],
["2016-01-20T05:33:47.000Z", 95.9, {}],
["2016-01-20T05:34:17.000Z", 95.5, {}],
["2016-01-20T05:34:47.000Z", 95.9, {}],
["2016-01-20T05:35:17.000Z", 95.8, {}],
["2016-01-20T05:35:47.000Z", 95.9, {}],
["2016-01-20T05:36:17.000Z", 95.7, {}],
["2016-01-20T05:36:47.000Z", 95.7, {}],
["2016-01-20T05:37:17.000Z", 95.9, {}],
["2016-01-20T05:37:47.000Z", 95.5, {}],
["2016-01-20T05:38:17.000Z", 95.4, {}],
["2016-01-20T05:38:47.000Z", 95.8, {}],
["2016-01-20T05:39:17.000Z", 96.0, {}],
["2016-01-20T05:39:47.000Z", 96.1, {}],
["2016-01-20T05:40:17.000Z", 95.8, {}],
["2016-01-20T05:40:47.000Z", 96.0, {}],
["2016-01-20T05:41:17.000Z", 95.9, {}],
["2016-01-20T05:41:47.000Z", 94.9, {}],
["2016-01-20T05:42:17.000Z", 95.8, {}],
["2016-01-20T05:42:47.000Z", 95.9, {}],
["2016-01-20T05:43:17.000Z", 95.8, {}],
["2016-01-20T05:43:47.000Z", 96.0, {}],
["2016-01-20T05:44:17.000Z", 95.7, {}],
["2016-01-20T05:44:47.000Z", 96.0, {}],
["2016-01-20T05:45:17.000Z", 95.9, {}],
["2016-01-20T05:45:47.000Z", 96.0, {}],
["2016-01-20T05:46:17.000Z", 95.8, {}],
["2016-01-20T05:46:47.000Z", 96.0, {}],
["2016-01-20T05:47:17.000Z", 95.7, {}],
["2016-01-20T05:47:47.000Z", 96.2, {}],
["2016-01-20T05:48:17.000Z", 95.8, {}],
["2016-01-20T05:48:47.000Z", 95.9, {}],
["2016-01-20T05:49:17.000Z", 95.7, {}],
["2016-01-20T05:49:47.000Z", 95.9, {}],
["2016-01-20T05:50:18.000Z", 95.7, {}],
["2016-01-20T05:50:48.000Z", 95.8, {}],
["2016-01-20T05:51:18.000Z", 95.7, {}],
["2016-01-20T05:51:48.000Z", 95.9, {}],
["2016-01-20T05:52:18.000Z", 95.5, {}],
["2016-01-20T05:52:48.000Z", 95.9, {}],
["2016-01-20T05:53:18.000Z", 95.8, {}],
["2016-01-20T05:53:48.000Z", 95.9, {}],
["2016-01-20T05:54:18.000Z", 95.7, {}],
["2016-01-20T05:54:48.000Z", 95.9, {}],
["2016-01-20T05:55:18.000Z", 95.8, {}],
["2016-01-20T05:55:48.000Z", 95.8, {}],
["2016-01-20T05:56:18.000Z", 95.6, {}],
["2016-01-20T05:56:48.000Z", 95.7, {}],
["2016-01-20T05:57:18.000Z", 95.7, {}],
["2016-01-20T05:57:48.000Z", 95.8, {}],
["2016-01-20T05:58:18.000Z", 95.7, {}],
["2016-01-20T05:58:48.000Z", 95.7, {}],
["2016-01-20T05:59:18.000Z", 95.6, {}],
["2016-01-20T05:59:48.000Z", 95.8, {}],
["2016-01-20T06:00:18.000Z", 95.7, {}],
["2016-01-20T06:00:48.000Z", 95.7, {}],
["2016-01-20T06:01:18.000Z", 95.6, {}],
["2016-01-20T06:01:48.000Z", 95.7, {}],
["2016-01-20T06:02:18.000Z", 95.8, {}],
["2016-01-20T06:02:48.000Z", 95.8, {}],
["2016-01-20T06:03:18.000Z", 95.8, {}],
["2016-01-20T06:03:48.000Z", 95.8, {}],
["2016-01-20T06:04:18.000Z", 95.8, {}],
["2016-01-20T06:04:48.000Z", 95.8, {}],
["2016-01-20T06:05:18.000Z", 95.7, {}],
["2016-01-20T06:05:48.000Z", 95.7, {}]
],
label: "a"
}, {
data: [
["2016-01-20T05:31:17.000Z", 90.9, {}],
["2016-01-20T05:31:47.000Z", 91.9, {}],
["2016-01-20T05:32:17.000Z", 92.4, {}],
["2016-01-20T05:32:47.000Z", 90.1, {}],
["2016-01-20T05:33:17.000Z", 89.7, {}],
["2016-01-20T05:33:47.000Z", 91.9, {}],
["2016-01-20T05:34:17.000Z", 85.5, {}],
["2016-01-20T05:34:47.000Z", 93.9, {}],
["2016-01-20T05:35:17.000Z", 94.8, {}],
["2016-01-20T05:35:47.000Z", 93.9, {}],
["2016-01-20T05:36:17.000Z", 92.7, {}],
["2016-01-20T05:36:47.000Z", 95.7, {}],
["2016-01-20T05:37:17.000Z", 92.9, {}],
["2016-01-20T05:37:47.000Z", 93.5, {}],
["2016-01-20T05:38:17.000Z", 93.4, {}],
["2016-01-20T05:38:47.000Z", 93.8, {}],
["2016-01-20T05:39:17.000Z", 93.0, {}],
["2016-01-20T05:39:47.000Z", 93.1, {}],
["2016-01-20T05:40:17.000Z", 93.8, {}],
["2016-01-20T05:40:47.000Z", 93.0, {}],
["2016-01-20T05:41:17.000Z", 93.9, {}],
["2016-01-20T05:41:47.000Z", 93.9, {}],
["2016-01-20T05:42:17.000Z", 92.8, {}],
["2016-01-20T05:42:47.000Z", 92.9, {}],
["2016-01-20T05:43:17.000Z", 93.8, {}],
["2016-01-20T05:43:47.000Z", 93.0, {}],
["2016-01-20T05:44:17.000Z", 93.7, {}],
["2016-01-20T05:44:47.000Z", 93.0, {}],
["2016-01-20T05:45:17.000Z", 93.9, {}],
["2016-01-20T05:45:47.000Z", 93.0, {}],
["2016-01-20T05:46:17.000Z", 93.8, {}],
["2016-01-20T05:46:47.000Z", 96.0, {}],
["2016-01-20T05:47:17.000Z", 92.7, {}],
["2016-01-20T05:47:47.000Z", 92.2, {}],
["2016-01-20T05:48:17.000Z", 92.8, {}],
["2016-01-20T05:48:47.000Z", 92.9, {}],
["2016-01-20T05:49:17.000Z", 92.7, {}],
["2016-01-20T05:49:47.000Z", 92.9, {}],
["2016-01-20T05:50:18.000Z", 93.7, {}],
["2016-01-20T05:50:48.000Z", 93.8, {}],
["2016-01-20T05:51:18.000Z", 92.7, {}],
["2016-01-20T05:51:48.000Z", 92.9, {}],
["2016-01-20T05:52:18.000Z", 92.5, {}],
["2016-01-20T05:52:48.000Z", 94.9, {}],
["2016-01-20T05:53:18.000Z", 94.8, {}],
["2016-01-20T05:53:48.000Z", 94.9, {}],
["2016-01-20T05:54:18.000Z", 94.7, {}],
["2016-01-20T05:54:48.000Z", 94.9, {}],
["2016-01-20T05:55:18.000Z", 94.8, {}],
["2016-01-20T05:55:48.000Z", 93.8, {}],
["2016-01-20T05:56:18.000Z", 94.6, {}],
["2016-01-20T05:56:48.000Z", 94.7, {}],
["2016-01-20T05:57:18.000Z", 93.7, {}],
["2016-01-20T05:57:48.000Z", 93.8, {}],
["2016-01-20T05:58:18.000Z", 93.7, {}],
["2016-01-20T05:58:48.000Z", 93.7, {}],
["2016-01-20T05:59:18.000Z", 93.6, {}],
["2016-01-20T05:59:48.000Z", 93.8, {}],
["2016-01-20T06:00:18.000Z", 93.7, {}],
["2016-01-20T06:00:48.000Z", 93.7, {}],
["2016-01-20T06:01:18.000Z", 93.6, {}],
["2016-01-20T06:01:48.000Z", 94.7, {}],
["2016-01-20T06:02:18.000Z", 94.8, {}],
["2016-01-20T06:02:48.000Z", 94.8, {}],
["2016-01-20T06:03:18.000Z", 94.8, {}],
["2016-01-20T06:03:48.000Z", 94.8, {}],
["2016-01-20T06:04:18.000Z", 94.8, {}],
["2016-01-20T06:04:48.000Z", 94.8, {}],
["2016-01-20T06:05:18.000Z", 94.7, {}],
["2016-01-20T06:05:48.000Z", 94.7, {}]
],
label: "b"
}]
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.interpolate("monotone")
.x(function(d) {
return x(parseDate.parse(d[0]));
})
.y(function(d) {
return y(d[1]);
});
var ary = [];
data.forEach(function(d) {
ary.push(d.data);
});
x.domain(d3.extent(d3.merge(ary), function(d) {
return parseDate.parse(d[0]);
}));
y.domain([
d3.min(data, function(c) {
return d3.min(c.data, function(v) {
return v[1];
});
}),
d3.max(data, function(c) {
return d3.max(c.data, function(v) {
return v[1];
});
})
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
var series = svg.selectAll(".series")
.data(data)
.enter().append("g")
.attr("class", "series");
series.append("path")
.attr("class", "line")
.attr("d", function(d) {
return line(d.data);
})
.style("stroke", function(d, i) {
return z(i);
});
series.append("text")
.datum(function(d) {
return {
label: d.label,
data: d.data[d.data.length - 1]
};
})
.attr("transform", function(d) {
return "translate(" + x(parseDate.parse(d.data[0])) + "," + y(d.data[1]) + ")";
})
.attr("x", 3)
.attr("dy", ".35em");
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", "1px")
.style("opacity", "0");
var lines = document.getElementsByClassName('line');
var mousePerLine = mouseG.selectAll('.mouse-per-line')
.data(data)
.enter()
.append("g")
.attr("class", "mouse-per-line");
mousePerLine.append("circle")
.attr("r", 7)
.style("stroke", function(d, i) {
return z(i);
})
.style("fill", "none")
.style("stroke-width", "1px")
.style("opacity", "0");
mousePerLine.append("rect")
.attr("x", 0)
.attr("y", -10)
.attr("width", 85)
.attr("height", 35)
.style("stroke", function(d, i) {
return z(i);
})
.attr("class", "tooltip-container")
.style("fill", "red")
.style("opacity", "0")
.style("stroke-width", "1px");
mousePerLine.append("text").attr("id", "x-text")
.attr("transform", "translate(10,3)");
mousePerLine.append("text").attr("id", "y-text")
.attr("transform", "translate(10,23)");
mouseG.append('svg:rect') // append a rect to catch mouse movements on canvas
.attr('width', width) // can't catch mouse events on a g element
.attr('height', height)
.attr('fill', 'none')
.attr('pointer-events', 'all')
.on('mouseout', function() { // 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 rect")
.style("opacity", "0");
d3.selectAll(".mouse-per-line text")
.style("opacity", "0");
})
.on('mouseover', function() { // 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");
d3.selectAll(".mouse-per-line rect")
.style("opacity", "0.2");
})
.on('mousemove', function() { // mouse moving over canvas
var mouse = d3.mouse(this);
d3.select(".mouse-line")
.attr("d", function() {
var d = "M" + mouse[0] + "," + height;
d += " " + mouse[0] + "," + 0;
return d;
});
d3.selectAll(".mouse-per-line")
.attr("transform", function(d, i) {
console.log(width / mouse[0])
var xDate = x.invert(mouse[0]),
bisect = d3.bisector(function(d) {
return d[0];
}).right;
var idx = bisect(d.data, xDate);
var beginning = 0,
end = lines[i].getTotalLength(),
target = null;
while (true) {
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(this).select('#y-text')
.text("y: " + y.invert(pos.y).toFixed(2));
d3.select(this).select('#x-text')
.text("x: " + d3.time.format("%X")(xDate));
return "translate(" + mouse[0] + "," + pos.y + ")";
});
});
</script>
</html>
多系列折线图:
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>Issues Ratings</title>
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line,
.axis1 path,
.axis1 line {
fill: none;
stroke: #E6E7E8;
shape-rendering: crispEdges;
}
.x.axis path, .x.axis1 path {
display: none;
}
.line {
fill: none;
stroke-width: 1.5px;
}
.legend-box {
cursor: pointer;
}
#mouse-tracker {
stroke: #E6E7E8;
stroke-width: 1px;
}
.hover-line {
stroke: #E6E7E8;
fill: none;
stroke-width: 1px;
left: 10px;
shape-rendering: crispEdges;
opacity: 1e-6;
}
.hover-text {
stroke: none;
font-size: 30px;
font-weight: bold;
fill: #000000;
}
.tooltip {
font-weight: normal;
}
.brush .extent {
stroke: #FFF;
shape-rendering: crispEdges;
}
text.inner-circle {
font-weight: 400;
font-size: 12px;
text-transform: uppercase;
}
text.inner-text {
font-weight: 400;
font-size: 36px;
font-family: 'Metric Regular', 'Metric';
text-align: center;
font-style: normal;
text-transform: uppercase;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.js"></script>
<script>
var margin = {top: 20, right: 200, bottom: 100, left: 50},
margin2 = { top: 430, right: 10, bottom: 20, left: 40 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
height2 = 500 - margin2.top - margin2.bottom;
var parseDate = d3.time.format("%Y%m%d").parse;
var bisectDate = d3.bisector(function(d) { return d.date; }).left;
var xScale = d3.time.scale()
.range([0, width]),
xScale2 = d3.time.scale()
.range([0, width]); // Duplicate xScale for brushing ref later
var yScale = d3.scale.linear()
.range([height, 0]);
// 40 Custom DDV colors
var color = d3.scale.ordinal().range(["#48A36D", "#56AE7C", "#64B98C", "#72C39B", "#80CEAA", "#80CCB3",
"#7FC9BD", "#7FC7C6", "#7EC4CF", "#7FBBCF", "#7FB1CF", "#80A8CE", "#809ECE", "#8897CE", "#8F90CD",
"#9788CD", "#9E81CC", "#AA81C5", "#B681BE", "#C280B7", "#CE80B0", "#D3779F", "#D76D8F", "#DC647E",
"#E05A6D", "#E16167", "#E26962", "#E2705C", "#E37756", "#E38457", "#E39158", "#E29D58", "#E2AA59",
"#E0B15B", "#DFB95C", "#DDC05E", "#DBC75F", "#E3CF6D", "#EAD67C", "#F2DE8A"]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(10),
xAxis2 = d3.svg.axis() // xAxis for brush slider
.scale(xScale2)
.orient("bottom")
.ticks(10);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
var yAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(15)
.tickSize(-height, 0, 0)
.tickFormat("");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(15)
.tickSize(-width, 0, 0)
.tickFormat("");
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) { return xScale(d.date); })
.y(function(d) { return yScale(d.rating); })
//.defined(function(d) { return d.rating; }); // Hiding line value defaults of 0 for missing data
var maxY; // Defined later to update yAxis
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom) //height + margin.top + margin.bottom
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Create invisible rect for mouse tracking
svg.append("rect")
.attr("width", width)
.attr("height", height)
.attr("x", 0)
.attr("y", 0)
//.attr("id", "mouse-tracker")
.style("fill", "white");
//for slider part-----------------------------------------------------------------------------------
//end slider part-----------------------------------------------------------------------------------
d3.tsv("http://bl.ocks.org/DStruths/raw/9c042e3a6b66048b5bd4/data.tsv", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { // Set the domain of the color ordinal scale to be all the csv headers except "date", matching a color to an issue
return key !== "date";
}));
data.forEach(function(d) { // Make every date in the csv data a javascript date object format
d.date = parseDate(d.date);
});
var categories = color.domain().map(function(name) { // Nest the data into an array of objects with new keys
return {
name: name, // "name": the csv headers except date
values: data.map(function(d) { // "values": which has an array of the dates and ratings
return {
date:d.date,
rating:+(d[name]),
};
}),
visible: (name === "Unemployment" ? true : false) // "visible": all false except for economy which is true.
};
});
xScale.domain(d3.extent(data, function(d) { return d.date; })); // extent = highest and lowest points, domain is data, range is bouding box
yScale.domain([0, 70
//d3.max(categories, function(c) { return d3.max(c.values, function(v) { return v.rating; }); })
]);
//for slider part-----------------------------------------------------------------------------------
//end slider part-----------------------------------------------------------------------------------
// draw line graph
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("x", -10)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Issues Rating");
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(xGrid);
svg.append("g")
.attr("class", "grid")
.call(yGrid)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("x", -10)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Issues Rating");
var issue = svg.selectAll(".issue")
.data(categories) // Select nested data and append to new svg group elements
.enter().append("g")
.attr("class", "issue");
issue.append("path")
.attr("class", "line")
.style("pointer-events", "none") // Stop line interferring with cursor
.attr("id", function(d) {
return "line-" + d.name.replace(" ", "").replace("/", ""); // Give line id of line-(insert issue name, with any spaces replaced with no spaces)
})
.attr("d", function(d) {
return d.visible ? line(d.values) : null; // If array key "visible" = true then draw line, if not then don't
})
.attr("clip-path", "url(#clip)")//use clip path to make irrelevant part invisible
.style("stroke", function(d) { return color(d.name); });
// draw legend
var legendSpace = 650 / categories.length; // 450/number of issues (ex. 40)
issue.append("rect")
.attr("width", 10)
.attr("height", 10)
.attr("x", width + (margin.right/3) - 15)
.attr("y", function (d, i) { return (legendSpace)+i*(legendSpace) - 8; }) // spacing
.attr("fill",function(d) {
return d.visible ? color(d.name) : "#F1F1F2"; // If array key "visible" = true then color rect, if not then make it grey
})
.attr("class", "legend-box")
.on("click", function(d){ // On click make d.visible
d.visible = !d.visible; // If array key for this data selection is "visible" = true then make it false, if false then make it true
maxY = findMaxY(categories); // Find max Y rating value categories data with "visible"; true
yScale.domain([0,maxY]); // Redefine yAxis domain based on highest y value of categories data with "visible"; true
svg.select(".y.axis")
.transition()
.call(yAxis);
issue.select("path")
.transition()
.attr("d", function(d){
return d.visible ? line(d.values) : null; // If d.visible is true then draw line for this d selection
})
issue.select("rect")
.transition()
.attr("fill", function(d) {
return d.visible ? color(d.name) : "#F1F1F2";
});
})
issue.append("text")
.attr("x", width + (margin.right/3))
.attr("y", function (d, i) { return (legendSpace)+i*(legendSpace); }) // (return (11.25/2 =) 5.625) + i * (5.625)
.text(function(d) { return d.name; });
}); // End Data callback function
function findMaxY(data){ // Define function "findMaxY"
var maxYValues = data.map(function(d) {
if (d.visible){
return d3.max(d.values, function(value) { // Return max rating value
return value.rating; })
}
});
return d3.max(maxYValues);
}
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", "1px")
.style("opacity", "0");
var lines = document.getElementsByClassName('line');
var mousePerLine = mouseG.selectAll('.mouse-per-line')
.data(data)
.enter()
.append("g")
.attr("class", "mouse-per-line");
mousePerLine.append("circle")
.attr("r", 7)
.style("stroke", function(d, i) {
return z(i);
})
.style("fill", "none")
.style("stroke-width", "1px")
.style("opacity", "0");
mousePerLine.append("rect")
.attr("x", 0)
.attr("y", -10)
.attr("width", 85)
.attr("height", 35)
.style("stroke", function(d, i) {
return z(i);
})
.attr("class", "tooltip-container")
.style("fill", "red")
.style("opacity", "0")
.style("stroke-width", "1px");
mousePerLine.append("text").attr("id", "x-text")
.attr("transform", "translate(10,3)");
mousePerLine.append("text").attr("id", "y-text")
.attr("transform", "translate(10,23)");
mouseG.append('svg:rect') // append a rect to catch mouse movements on canvas
.attr('width', width) // can't catch mouse events on a g element
.attr('height', height)
.attr('fill', 'none')
.attr('pointer-events', 'all')
.on('mouseout', function() { // 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 rect")
.style("opacity", "0");
d3.selectAll(".mouse-per-line text")
.style("opacity", "0");
})
.on('mouseover', function() { // 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");
d3.selectAll(".mouse-per-line rect")
.style("opacity", "0.2");
})
.on('mousemove', function() { // mouse moving over canvas
var mouse = d3.mouse(this);
d3.select(".mouse-line")
.attr("d", function() {
var d = "M" + mouse[0] + "," + height;
d += " " + mouse[0] + "," + 0;
return d;
});
d3.selectAll(".mouse-per-line")
.attr("transform", function(d, i) {
console.log(width / mouse[0])
var xDate = x.invert(mouse[0]),
bisect = d3.bisector(function(d) {
return d[0];
}).right;
var idx = bisect(d.data, xDate);
var beginning = 0,
end = lines[i].getTotalLength(),
target = null;
while (true) {
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(this).select('#y-text')
.text("y: " + y.invert(pos.y).toFixed(2));
d3.select(this).select('#x-text')
.text("x: " + d3.time.format("%X")(xDate));
return "translate(" + mouse[0] + "," + pos.y + ")";
});
});
</script>
</html>