I'm trying to make an interactive line-chart with d3.js. I get the datas from two csv files (emissions.csv and gdp.csv) and I would like that when i pass with the mouse on the graph, it shows a kind of label with information on the corresponding point on the line. Now I have to pass the mouse ON the line to show the label and I can't figure out how to do what i want to do. I've found this example that shows what I want but I cant understand some of the code-lines and I can't understand how to use it on mine graph.
Here my code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
/*display: none;*/
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
</style>
<body>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<script>
var margin = {top: 20, right: 80, bottom: 30, left: 150},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
//.interpolate("basis")
.x(function(d) { return x(d.year); })
.y(function(d) { return y(d.emission); });
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 + ")");
d3.csv("europe_emission.csv", function(error, data) {
if (error) throw error;
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "year"; }));
var cities = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {year: d.year, emission: +d[name]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.year; }));
y.domain([
0,
d3.max(cities, function(c) { return d3.max(c.values, function(v) { return v.emission; }); })
]);
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("dy", ".71em")
.style("text-anchor", "end")
.text("Emission (thousand metric tons of CO2)");
var city = svg.selectAll(".city")
.data(cities)
.enter().append("g")
.attr("class", "city");
city.append("path")
.attr("class", "line")
.attr("d", function(d) {
return line(d.values);
})
.style("stroke", function(d) {
return color(d.name);
});
city.append("text")
.datum(function(d) {
return {
name: d.name,
value: d.values[d.values.length - 1]
};
})
.attr("transform", function(d) {
return "translate(" + x(d.value.year) + "," + y(d.value.emission) + ")";
})
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) {
return d.name;
});
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(cities)
.enter()
.append("g")
.attr("class", "mouse-per-line");
mousePerLine.append("circle")
.attr("r", 7)
.style("stroke", function(d) {
return color(d.name);
})
.style("fill", "none")
.style("stroke-width", "1px")
.style("opacity", "0");
mousePerLine.append("text")
.attr("transform", "translate(10,3)");
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 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");
})
.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.year; }).right;
idx = bisect(d.values, xDate);
var beginning = 0,
end = lines[i].getTotalLength(),
target = null;
while (true){
target = Math.floor((beginning + end) / 2);
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('text')
.text(y.invert(pos.y).toFixed(2));
return "translate(" + mouse[0] + "," + pos.y +")";
});
});
});
</script>
europe_emission.csv:
year,UnitedKingdom,Italy,France,Spain,Germany
2012,483423,386666,368845,276636,821717
2011,464036,413379,364819,280922,810441
2010,504997,424993,391075,280377,829401
2009,487442,414809,381992,293732,785602
2008,536733,463695,400720,333181,851111
2007,554251,475436,407254,363744,848548
2006,561128,483533,416414,356712,873246
2005,561101,488078,425740,365478,861733
2004,563962,489367,422201,350071,881743
2003,562296,486559,420492,333168,893599
2002,551553,470530,412019,328878,890875
2001,567904,468283,416267,308786,907541
2000,556667,462277,415079,308026,891515
1999,548047,458824,418193,294901,887890
1998,555499,453524,426564,271515,915176
1997,550525,442371,404884,263303,923080
1996,575026,438303,411302,250543,951863
1995,553701,444943,398480,262860,930857
1994,562061,419903,391681,249451,932485
1993,568100,427170,391484,237253,948683
1992,581828,433867,413020,245814,957561
1991,598323,434156,423121,237179,1004735
1990,591499,434656,398769,227508,1042065
This took me way too long to figure out, but it's your data sorting. My link above assumed an ascending x axis values, you have descending, so your paths are draw backwards right to left. Either sort your data, or change the search function to go right to left:
d3.selectAll(".mouse-per-line")
.attr("transform", function(d, i) {
var beginning = 0,
end = lines[i].getTotalLength(),
target = null;
while (true) {
target = Math.floor((beginning + end) / 2);
pos = lines[i].getPointAtLength(target);
if ((target === end || target === beginning) && pos.x !== mouse[0]) {
break;
}
if (pos.x > mouse[0]) beginning = target; //<-- this was end = target
else if (pos.x < mouse[0]) end = target; //<-- this was beginning = target
else break;
}
d3.select(this).select('text')
.text(y.invert(pos.y).toFixed(2));
return "translate(" + mouse[0] + "," + pos.y + ")";
});
Fixed code here.
Related
I have made a graph using D3 but the problem is that I want its tooltip to appear on the graph as per nearest mouse point over in the graph. I have also seen one example on StackOverflow here on this link - D3: Get nearest value from ordinal axis on mouseover. Which is doing the exact what I want but when I implemented the same code on my graph then it is not working. Please take a look at my code and suggest me changes.
IMPORTANT!: I also want when someone double click on the graph then tooltip x'axis & y'axis coordinated stay there. Your valuable time is highly appreciated. Please Help!!!
<div id='chartdiv'></div>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
function getData(){
data1 = [{x:1,y:15},{x:2,y:26},{x:3,y:17},{x:4,y:21},];
return data1};
function drawChart(data) {
var coreheight = 720
var corewidth = 1280
var margin = {top: 10, right: 30, bottom: 100, left: 60}
, width = corewidth - margin.left - margin.right
, height = coreheight - margin.top - margin.bottom;
var xExtent = d3.extent(data, function(d) { return d.x; }),
xRange = xExtent[1] - xExtent[0],
yExtent = d3.extent(data, function(d) { return d.y; }).reverse(),
yRange = yExtent[1] - yExtent[0];
var xScale = d3.scaleLinear().range([50, width]).domain([xExtent[0] - (xRange * .05), xExtent[1] + (xRange * .05)]);;
var yScale = d3.scaleLinear().range([0, height]).domain([yExtent[0] - (yRange * .1), yExtent[1] + (yRange * .05)]);;
var line = d3.line()
.x(function(d, x) { return xScale(d.x); })
.y(function(d, y) { return yScale(d.y); })
.curve(d3.curveMonotoneX);
d3.select('#chartdiv')
.append('svg')
.attr('class','graph')
.style('background-color','#fff')
.attr("viewBox", "0 0 "+ corewidth +" "+ coreheight +"");
var svg = d3.select(".graph")
.append("g")
.attr("class", "dchart")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale).ticks(7))
.attr("transform", "translate(50, 0)")
.attr('font-size','25px');
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale).ticks(7))
.attr('font-size','25px');
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
svg.selectAll(".dot")
.data(data)
.enter()
.append("circle")
.attr("class", "dot")
.attr("cx", function(d) { return xScale(d.x) })
.attr("cy", function(d) { return yScale(d.y) })
.attr("r", 8)
.on("mouseover", mousehover)
.on("mouseout", mousehoverout)
function mousehover(d) {
svg.append("text")
.attr('class','annot')
.attr('x', xScale(d.x) - 20)
.attr('y', yScale(d.y) - 23)
.text(d.x+','+d.y)
.style('font-size','30px')
svg.append('line')
.attr('class','annot')
.style("stroke", "black")
.attr('stroke-width','3')
.attr("x1", xScale(d.x))
.attr("y1", yScale(d.y))
.attr("x2", xScale(d.x))
.attr("y2", height);
}
function mousehoverout() {
d3.selectAll('.annot').remove()
}
}drawChart(getData());
</script>
<style>
.line {fill: none;stroke: darkblue;stroke-width: 3}
.dot {fill: darkblue;stroke-width:0}
</style>
You need to track where the mouse is on the chart, you're currently only checking if the mouse is hovering on a dot.
I've added a couple of event handlers to the entire chart:
.on("mousemove", mousemove)
.on("mouseout", mousehoverout);
One you have already implemented, mousehoverout, and the other, mousemove, which tracks where the mouse is relative to the points.
var lastIndex = -1;
function mousemove()
{
let x = d3.mouse(this)[0];
let closest = data.reduce((best, value, i) =>
{
let absx = Math.abs(xScale(value.x) - x)
if(absx < best.value)
{
return {index: i, value:absx};
}
else
{
return best;
}
}, {index:0, value:Number.MAX_SAFE_INTEGER});
if(lastIndex != closest.index)
{
d3.selectAll('.annot').remove();
lastIndex = closest.index;
}
mousehover(data[closest.index]);
}
Then to add the double click functionality, you can do as you have shown in the w3schools link, but add the handler to the chart:
.on('dblclick', function(){/* your code in here */});
function getData(){
data1 = [{x:1,y:15},{x:2,y:26},{x:3,y:17},{x:4,y:21},];
return data1};
function drawChart(data) {
var coreheight = 720
var corewidth = 1280
var margin = {top: 10, right: 30, bottom: 100, left: 60}
, width = corewidth - margin.left - margin.right
, height = coreheight - margin.top - margin.bottom;
var xExtent = d3.extent(data, function(d) { return d.x; }),
xRange = xExtent[1] - xExtent[0],
yExtent = d3.extent(data, function(d) { return d.y; }).reverse(),
yRange = yExtent[1] - yExtent[0];
var xScale = d3.scaleLinear().range([50, width]).domain([xExtent[0] - (xRange * .05), xExtent[1] + (xRange * .05)]);;
var yScale = d3.scaleLinear().range([0, height]).domain([yExtent[0] - (yRange * .1), yExtent[1] + (yRange * .05)]);;
var line = d3.line()
.x(function(d, x) { return xScale(d.x); })
.y(function(d, y) { return yScale(d.y); })
.curve(d3.curveMonotoneX);
d3.select('#chartdiv')
.append('svg')
.attr('class','graph')
.style('background-color','#fff')
.attr("viewBox", "0 0 "+ corewidth +" "+ coreheight +"")
.on('dblclick', function(){if(lastIndex > -1) { createpeak(data[lastIndex]); } })
.on("mousemove", mousemove)
.on("mouseout", mousehoverout);
var svg = d3.select(".graph")
.append("g")
.attr("class", "dchart")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale).ticks(7))
.attr("transform", "translate(50, 0)")
.attr('font-size','25px');
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale).ticks(7))
.attr('font-size','25px');
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
svg.selectAll(".dot")
.data(data)
.enter()
.append("circle")
.attr("class", "dot")
.attr("cx", function(d) { return xScale(d.x) })
.attr("cy", function(d) { return yScale(d.y) })
.attr("r", 8);
var lastIndex = -1;
function mousemove()
{
let x = d3.mouse(this)[0];
let closest = data.reduce((best, value, i) =>
{
let absx = Math.abs(xScale(value.x) - x)
if(absx < best.value)
{
return {index: i, value:absx};
}
else
{
return best;
}
}, {index:0, value:Number.MAX_SAFE_INTEGER});
d3.selectAll('.annot').remove();
lastIndex = closest.index;
mousehover(data[closest.index]);
}
function mousehover(d) {
svg.append("text")
.attr('class','annot')
.attr('x', xScale(d.x) - 20)
.attr('y', yScale(d.y) - 23)
.text(d.x+','+d.y)
.style('font-size','30px')
svg.append('line')
.attr('class','annot')
.style("stroke", "black")
.attr('stroke-width','3')
.attr("x1", xScale(d.x))
.attr("y1", yScale(d.y))
.attr("x2", xScale(d.x))
.attr("y2", height);
}
var i = 1;
var m = 1;
function createpeak(d) {
console.log('redacted');
}
function dragstarted(d,e){
d3.select(this).raise().classed("active", true);
var current = d3.select(this);
deltaX = current.attr("x") - d3.event.x;
deltaY = current.attr("y") - d3.event.y;
}
function dragged(d,e){
d3.select(this).attr("x",d3.event.x + deltaX)
d3.select(this).attr("y",d3.event.y + deltaY)
for (var i=1; i <= 100; i++){
d3.select('.line'+i).attr("x2", function (d) {
return d3.select('.peak'+i).attr("x") - deltaX
});
d3.select('.line'+i).attr("y2", function (d) {
return d3.select('.peak'+i).attr("y") - deltaY
})}}
function dragended(d,e){}
function mousehoverout() {
}
}drawChart(getData());
.line {fill: none;stroke: darkblue;stroke-width: 3}
.dot {fill: darkblue;stroke-width:0}
<div id='chartdiv'></div>
<script src="https://d3js.org/d3.v5.min.js"></script>
i have plotted dynamic waves based on randomly generated data in d3.js. I am using "dot" (svg.selectAll("dot")) element to represent the data point(x and y axis) on the waves. Based on setinterval method my data is getting updated every 200ms and i am transforming the data from right to left. But the data points(dots) that i added to the waves are not moving along with the waves, they are fixed(not moving) and only waves are moving.
here's the code:
https://jsfiddle.net/rajatmehta/tm5166e1/4/
function updateData() {
var newData = GenData(N,lastUpdateTime);
lastUpdateTime = newData[newData.length-1].timestamp;
var newData2 = GenData2(N,lastUpdateTimeNew);
lastUpdateTimeNew = newData2[newData2.length-1].timestamp;
for (var i=0; i<newData.length; i++){
console.log(globalData.length);
if(globalData.length>99){
globalData.shift();
}
globalData.push(newData[i]);
}
for (var i=0; i<newData2.length; i++){
console.log(globalDataNew.length);
if(globalDataNew.length>99){
globalDataNew.shift();
}
globalDataNew.push(newData2[i]);
}
//code for transition start
x1 = newData[0].timestamp;
x2 = newData[newData.length - 1].timestamp;
dx = dx + (x(x1) - x(x2)); // dx needs to be cummulative
x1New = newData2[0].timestamp;
x2New = newData2[newData2.length - 1].timestamp;
dxNew = dxNew + (x(x1New) - x(x2New)); // dx needs to be cummulative
d3.select("path#path1")
.datum(globalData)
.attr("class", "line")
.attr("d", valueline(globalData))
.transition()
.ease("linear")
.attr("transform", "translate(" + String(dx) + ")");
d3.select("path#path2")
.datum(globalDataNew)
.attr("class", "line")
.attr("d", valueline2(globalDataNew))
.transition()
.ease("linear")
.attr("transform", "translate(" + String(dxNew) + ")");
svg.select(".x.axis").call(xAxis);
}
I am new to d3.js, so dont have much idea about this.
Two problems:
You are not appending the circles correctly: you cannot append a <circle> element to a <path>element. You have to use an "enter" selection, appending them to the SVG (or a group element):
chartBody.selectAll(null)
.data(globalData)
.enter()
.append("circle")
.attr("class", "dot1")
.attr("r", 3)
.attr("cx", function(d) {
console.log(d)
return x(d.timestamp);
})
.attr("cy", function(d) {
return y(d.value);
});
In the update function, select those circles by class:
d3.selectAll(".dot1")
.data(globalData)
.transition()
.ease("linear")
.attr("transform", "translate(" + String(dx) + ")");
Here is your code with those changes:
var lastUpdateTime = +new Date();
var lastUpdateTimeNew = +new Date();
var GenData = function(N, lastTime) {
var output = [];
for (var i = 0; i < N; i++) {
output.push({
value: Math.random() * 10,
timestamp: lastTime
});
lastTime = lastTime + 1000;
}
return output;
}
var GenData2 = function(N, lastTime) {
var output = [];
for (var i = 0; i < N; i++) {
output.push({
value: Math.random() * 20,
timestamp: lastTime
});
lastTime = lastTime + 1000;
}
return output;
}
var globalData;
var globalDataNew;
// plot the original data by retrieving everything from time 0
data = GenData(50, lastUpdateTime);
dataNew = GenData2(50, lastUpdateTimeNew);
lastUpdateTime = data[data.length - 1].timestamp;
lastUpdateTimeNew = dataNew[dataNew.length - 1].timestamp;
globalData = data;
globalDataNew = dataNew;
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
},
width = 800 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
x.domain(d3.extent(globalDataNew, function(d) {
return d.timestamp;
}));
y.domain(d3.extent(globalDataNew, function(d) {
return d.value;
}));
var xAxis = d3.svg.axis().scale(x)
.orient("bottom")
.ticks(d3.time.seconds, 20)
.tickFormat(d3.time.format('%X'))
.tickSize(5)
.tickPadding(8);
var xAxisTop = d3.svg.axis().scale(x)
.orient("bottom").tickFormat("").tickSize(0);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
var yAxisRight = d3.svg.axis().scale(y)
.orient("right").tickFormat("").tickSize(0);
var valueline = d3.svg.line()
.x(function(d) {
return x(d.timestamp);
})
.y(function(d) {
return y(d.value);
});
var valueline2 = d3.svg.line()
.x(function(d) {
return x(d.timestamp);
})
.y(function(d) {
return y(d.value);
});
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 + ")");
svg.append("rect")
.attr("width", width)
.attr("height", height)
.attr("class", "plot");
var clip = svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height);
var chartBody = svg.append("g")
.attr("clip-path", "url(#clip)");
/* .on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div .html(formatTime(d.timestamp) + "<br/>" + d.close)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
})*/
chartBody.append("path") // Add the valueline path
.datum(globalData)
.attr("id", "path1")
.attr("class", "line")
.attr("d", valueline);
chartBody.selectAll(null)
.data(globalData)
.enter()
.append("circle")
.attr("class", "dot1")
.attr("r", 3)
.attr("cx", function(d) {
return x(d.timestamp);
})
.attr("cy", function(d) {
return y(d.value);
});
chartBody.selectAll(null)
.data(globalDataNew)
.enter()
.append("circle")
.attr("class", "dot2")
.attr("r", 3)
.attr("cx", function(d) {
return x(d.timestamp);
})
.attr("cy", function(d) {
return y(d.value);
});
chartBody.append("path") // Add the valueline path
.datum(globalDataNew)
.attr("id", "path2")
.attr("class", "line")
.attr("d", valueline2);
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g") // Add the Y Axis
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ",0)")
.call(yAxisRight);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + String(0) + ")")
.call(xAxisTop);
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", (0 - (height / 2)))
.attr("dy", "1em")
.style("text-anchor", "middle")
.style("font-weight", "bold")
.text("Return (%)");
var inter = setInterval(function() {
updateData();
}, 1000);
//////////////////////////////////////////////////////////////
var N = 3;
var dx = 0;
var dxNew = 0;
function updateData() {
var newData = GenData(N, lastUpdateTime);
lastUpdateTime = newData[newData.length - 1].timestamp;
var newData2 = GenData2(N, lastUpdateTimeNew);
lastUpdateTimeNew = newData2[newData2.length - 1].timestamp;
for (var i = 0; i < newData.length; i++) {
if (globalData.length > 99) {
globalData.shift();
}
globalData.push(newData[i]);
}
for (var i = 0; i < newData2.length; i++) {
if (globalDataNew.length > 99) {
globalDataNew.shift();
}
globalDataNew.push(newData2[i]);
}
//code for transition start
x1 = newData[0].timestamp;
x2 = newData[newData.length - 1].timestamp;
dx = dx + (x(x1) - x(x2)); // dx needs to be cummulative
x1New = newData2[0].timestamp;
x2New = newData2[newData2.length - 1].timestamp;
dxNew = dxNew + (x(x1New) - x(x2New)); // dx needs to be cummulative
d3.select("path#path1")
.datum(globalData)
.attr("class", "line")
.attr("d", valueline(globalData))
.transition()
.ease("linear")
.attr("transform", "translate(" + String(dx) + ")");
d3.select("path#path2")
.datum(globalDataNew)
.attr("class", "line")
.attr("d", valueline2(globalDataNew))
.transition()
.ease("linear")
.attr("transform", "translate(" + String(dxNew) + ")");
d3.selectAll(".dot1")
.data(globalData)
.transition()
.ease("linear")
.attr("transform", "translate(" + String(dx) + ")");
d3.selectAll(".dot2")
.data(globalDataNew)
.transition()
.ease("linear")
.attr("transform", "translate(" + String(dx) + ")");
svg.select(".x.axis").call(xAxis);
}
body {
font: 12px Arial;
}
path {
stroke: black;
stroke-width: 1;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: black;
stroke-width: 2;
shape-rendering: crispEdges;
}
text {
fill: black;
}
rect {
fill: #add8e6;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
PS: You have to append new circles as the lines move to the left. However, this is another issue.
In the following snippet (based on the answer of #Gerardo Furtado d3 multi line with mouse over cursor for both y AND x value) the mouse over cursor is interpolating the values between data points.
How is it possible that the cursor only shows values for the real data points?
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<script>
var myData = "date New York San Francisco Austin\n\
20111001 63.4 62.7 72.2\n\
20111002 58.0 59.9 67.7\n\
20111006 58.8 57.0 77.0\n\
20111007 57.9 56.7 82.3\n\
20111008 61.8 56.8 78.9\n\
20111009 69.3 56.7 68.8\n\
20111010 71.2 60.1 68.7\n\
20111015 61.7 64.6 68.0\n\
20111016 61.8 61.6 70.6\n\
20111017 62.8 61.1 71.1\n\
20111018 60.8 59.2 70.0\n\
20111022 54.4 60.7 72.4\n";
var margin = {
top: 20,
right: 80,
bottom: 30,
left: 50
},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y%m%d").parse;
var parseDate2 = d3.time.format("%Y/%m/%d");
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.interpolate("linear")
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.temperature);
});
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 data = d3.tsv.parse(myData);
color.domain(d3.keys(data[0]).filter(function(key) {
return key !== "date";
}));
data.forEach(function(d) {
d.date = parseDate(d.date);
});
var cities = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {
date: d.date,
temperature: +d[name]
};
})
};
});
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([
d3.min(cities, function(c) {
return d3.min(c.values, function(v) {
return v.temperature;
});
}),
d3.max(cities, function(c) {
return d3.max(c.values, function(v) {
return v.temperature;
});
})
]);
var legend = svg.selectAll('g')
.data(cities)
.enter()
.append('g')
.attr('class', 'legend');
legend.append('rect')
.attr('x', width - 20)
.attr('y', function(d, i) {
return i * 20;
})
.attr('width', 10)
.attr('height', 10)
.style('fill', function(d) {
return color(d.name);
});
legend.append('text')
.attr('x', width - 8)
.attr('y', function(d, i) {
return (i * 20) + 9;
})
.text(function(d) {
return d.name;
});
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("dy", ".71em")
.style("text-anchor", "end")
.text("Temperature (ºF)");
var city = svg.selectAll(".city")
.data(cities)
.enter().append("g")
.attr("class", "city");
city.append("path")
.attr("class", "line")
.attr("d", function(d) {
return line(d.values);
})
.style("stroke", function(d) {
return color(d.name);
});
city.append("text")
.datum(function(d) {
return {
name: d.name,
value: d.values[d.values.length - 1]
};
})
.attr("transform", function(d) {
return "translate(" + x(d.value.date) + "," + y(d.value.temperature) + ")";
})
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) {
return d.name;
});
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(cities)
.enter()
.append("g")
.attr("class", "mouse-per-line");
mousePerLine.append("circle")
.attr("r", 7)
.style("stroke", function(d) {
return color(d.name);
})
.style("fill", "none")
.style("stroke-width", "1px")
.style("opacity", "0");
mousePerLine.append("text")
.attr("transform", "translate(10,3)");
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 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");
})
.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) {
var xDate = x.invert(mouse[0]),
bisect = d3.bisector(function(d) { return d.date; }).right;
idx = bisect(d.values, xDate);
var beginning = 0,
end = lines[i].getTotalLength(),
target = null;
while (true){
target = Math.floor((beginning + end) / 2);
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('text')
.text(y.invert(pos.y).toFixed(2) + " - "
+ parseDate2(xDate));
return "translate(" + mouse[0] + "," + pos.y +")";
});
});
</script>
</body>
</html>
For this working example (see Multiseries line chart with mouseover tooltip), I would need to display also the x axis value (and not only the y value) next to the circles.
How can this be achieved?
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<script>
var myData = "date New York San Francisco Austin\n\
20111001 63.4 62.7 72.2\n\
20111002 58.0 59.9 67.7\n\
20111003 53.3 59.1 69.4\n\
20111004 55.7 58.8 68.0\n\
20111005 64.2 58.7 72.4\n\
20111006 58.8 57.0 77.0\n\
20111007 57.9 56.7 82.3\n\
20111008 61.8 56.8 78.9\n\
20111009 69.3 56.7 68.8\n\
20111010 71.2 60.1 68.7\n\
20111011 68.7 61.1 70.3\n\
20111012 61.8 61.5 75.3\n\
20111013 63.0 64.3 76.6\n\
20111014 66.9 67.1 66.6\n\
20111015 61.7 64.6 68.0\n\
20111016 61.8 61.6 70.6\n\
20111017 62.8 61.1 71.1\n\
20111018 60.8 59.2 70.0\n\
20111019 62.1 58.9 61.6\n\
20111020 65.1 57.2 57.4\n\
20111021 55.6 56.4 64.3\n\
20111022 54.4 60.7 72.4\n";
var margin = {
top: 20,
right: 80,
bottom: 30,
left: 50
},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y%m%d").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.temperature);
});
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 data = d3.tsv.parse(myData);
color.domain(d3.keys(data[0]).filter(function(key) {
return key !== "date";
}));
data.forEach(function(d) {
d.date = parseDate(d.date);
});
var cities = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {
date: d.date,
temperature: +d[name]
};
})
};
});
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([
d3.min(cities, function(c) {
return d3.min(c.values, function(v) {
return v.temperature;
});
}),
d3.max(cities, function(c) {
return d3.max(c.values, function(v) {
return v.temperature;
});
})
]);
var legend = svg.selectAll('g')
.data(cities)
.enter()
.append('g')
.attr('class', 'legend');
legend.append('rect')
.attr('x', width - 20)
.attr('y', function(d, i) {
return i * 20;
})
.attr('width', 10)
.attr('height', 10)
.style('fill', function(d) {
return color(d.name);
});
legend.append('text')
.attr('x', width - 8)
.attr('y', function(d, i) {
return (i * 20) + 9;
})
.text(function(d) {
return d.name;
});
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("dy", ".71em")
.style("text-anchor", "end")
.text("Temperature (ºF)");
var city = svg.selectAll(".city")
.data(cities)
.enter().append("g")
.attr("class", "city");
city.append("path")
.attr("class", "line")
.attr("d", function(d) {
return line(d.values);
})
.style("stroke", function(d) {
return color(d.name);
});
city.append("text")
.datum(function(d) {
return {
name: d.name,
value: d.values[d.values.length - 1]
};
})
.attr("transform", function(d) {
return "translate(" + x(d.value.date) + "," + y(d.value.temperature) + ")";
})
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) {
return d.name;
});
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(cities)
.enter()
.append("g")
.attr("class", "mouse-per-line");
mousePerLine.append("circle")
.attr("r", 7)
.style("stroke", function(d) {
return color(d.name);
})
.style("fill", "none")
.style("stroke-width", "1px")
.style("opacity", "0");
mousePerLine.append("text")
.attr("transform", "translate(10,3)");
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 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");
})
.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.date; }).right;
idx = bisect(d.values, xDate);
var beginning = 0,
end = lines[i].getTotalLength(),
target = null;
while (true){
target = Math.floor((beginning + end) / 2);
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('text')
.text(y.invert(pos.y).toFixed(2));
return "translate(" + mouse[0] + "," + pos.y +")";
});
});
</script>
</body>
</html>
Inside your mousemove function, you already have the date in the variable xDate.
Thus, all you need is a new time format:
var parseDate2 = d3.time.format("%Y/%m/%d");
And use xDate in the text function:
.text(y.invert(pos.y).toFixed(2) + " - " + parseDate2(xDate));
Here is the modified code:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<script>
var myData = "date New York San Francisco Austin\n\
20111001 63.4 62.7 72.2\n\
20111002 58.0 59.9 67.7\n\
20111003 53.3 59.1 69.4\n\
20111004 55.7 58.8 68.0\n\
20111005 64.2 58.7 72.4\n\
20111006 58.8 57.0 77.0\n\
20111007 57.9 56.7 82.3\n\
20111008 61.8 56.8 78.9\n\
20111009 69.3 56.7 68.8\n\
20111010 71.2 60.1 68.7\n\
20111011 68.7 61.1 70.3\n\
20111012 61.8 61.5 75.3\n\
20111013 63.0 64.3 76.6\n\
20111014 66.9 67.1 66.6\n\
20111015 61.7 64.6 68.0\n\
20111016 61.8 61.6 70.6\n\
20111017 62.8 61.1 71.1\n\
20111018 60.8 59.2 70.0\n\
20111019 62.1 58.9 61.6\n\
20111020 65.1 57.2 57.4\n\
20111021 55.6 56.4 64.3\n\
20111022 54.4 60.7 72.4\n";
var margin = {
top: 20,
right: 80,
bottom: 30,
left: 50
},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y%m%d").parse;
var parseDate2 = d3.time.format("%Y/%m/%d");
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.temperature);
});
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 data = d3.tsv.parse(myData);
color.domain(d3.keys(data[0]).filter(function(key) {
return key !== "date";
}));
data.forEach(function(d) {
d.date = parseDate(d.date);
});
var cities = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {
date: d.date,
temperature: +d[name]
};
})
};
});
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([
d3.min(cities, function(c) {
return d3.min(c.values, function(v) {
return v.temperature;
});
}),
d3.max(cities, function(c) {
return d3.max(c.values, function(v) {
return v.temperature;
});
})
]);
var legend = svg.selectAll('g')
.data(cities)
.enter()
.append('g')
.attr('class', 'legend');
legend.append('rect')
.attr('x', width - 20)
.attr('y', function(d, i) {
return i * 20;
})
.attr('width', 10)
.attr('height', 10)
.style('fill', function(d) {
return color(d.name);
});
legend.append('text')
.attr('x', width - 8)
.attr('y', function(d, i) {
return (i * 20) + 9;
})
.text(function(d) {
return d.name;
});
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("dy", ".71em")
.style("text-anchor", "end")
.text("Temperature (ºF)");
var city = svg.selectAll(".city")
.data(cities)
.enter().append("g")
.attr("class", "city");
city.append("path")
.attr("class", "line")
.attr("d", function(d) {
return line(d.values);
})
.style("stroke", function(d) {
return color(d.name);
});
city.append("text")
.datum(function(d) {
return {
name: d.name,
value: d.values[d.values.length - 1]
};
})
.attr("transform", function(d) {
return "translate(" + x(d.value.date) + "," + y(d.value.temperature) + ")";
})
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) {
return d.name;
});
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(cities)
.enter()
.append("g")
.attr("class", "mouse-per-line");
mousePerLine.append("circle")
.attr("r", 7)
.style("stroke", function(d) {
return color(d.name);
})
.style("fill", "none")
.style("stroke-width", "1px")
.style("opacity", "0");
mousePerLine.append("text")
.attr("transform", "translate(10,3)");
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 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");
})
.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) {
var xDate = x.invert(mouse[0]),
bisect = d3.bisector(function(d) { return d.date; }).right;
idx = bisect(d.values, xDate);
var beginning = 0,
end = lines[i].getTotalLength(),
target = null;
while (true){
target = Math.floor((beginning + end) / 2);
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('text')
.text(y.invert(pos.y).toFixed(2) + " - "
+ parseDate2(xDate));
return "translate(" + mouse[0] + "," + pos.y +")";
});
});
</script>
</body>
</html>
I am trying to add tooltip on a multiple lines chart following this example.
I want the tooltips show on both lines at the same time when mouse over either of them, like the example here. In my real case, I have four lines.
Here is my jsbins https://jsbin.com/budonapeki/edit?html,js,console,output
Hit "Run with JS" on the top right corner to see the chart.
My questions are...
In my jsbins, the tooltips are only showing on the first point of the line and the other tooltips do not show when mouse moves. Can anyone help to point out why?
How can I make the tooltips showing on the lines at the same time without repeating too many codes? Since I am using d3.nest()to convert the array, I am not sure if this affect the work of tooltips.
Appreciate!
Updated
Following the link in Mark's comment, I get the tooltips done finally.
But I have some other questions...
This is my updated JSbins https://jsbin.com/hoceneneso/edit?html,js,console,output
The first question is.. In my chart, there are two buttons on the right side, the line can disappear or appear when click the buttons. And the corresponding tooltip of the line should disappear or appear with the line together. But in my chart, the tooltips are still there, even I click the button.
I was trying to remove and change the opacity of the tooltips, but I am still not able to make it work. Does anyone have idea about this?
Second question is ..I was trying to make the tooltip start showing at "name1" which is the start point of the lines. I know the grey rectangle is to catch mouse movements on canvas, so I was trying to move the rectangle by .attr("transform", "translate(180,3)") but tooltips still show when mouseover y-axis. Can any explain why and suggestions?
Thanks a lot!
First question, set a unique id to each "mouse-per-line" so you can toggle it's opacity:
var mousePerLine = mouseG.selectAll('.mouse-per-line')
.data(dataNest1)
.enter()
.append("g")
.attr("class", "mouse-per-line")
.attr("id", function(d){
return "mouse-per-line-" + d.key;
});
In your legend click handler:
d3.select("#mouse-per-line-" + d.key)
.style("opacity", newOpacity);
Second question, instead of transform to move the rect, set the x and width attributes. You can make it dynamic by:
mouseG.append('svg:rect') // append a rect to catch mouse movements on canvas
.attr('x', x(dataNest1[0].values[0].x))
.attr('width', x(dataNest1[0].values[dataNest1[0].values.length - 1].x) - x(dataNest1[0].values[0].x))
...
Updated code:
var data1 = [
{x: "Name1", y: 2.5, label: "A"},
{x: "Name2", y: 3.5, label: "A"},
{x: "Name3", y: 4.7, label: "A"},
{x: "Name1", y: 4.7, label: "B"},
{x: "Name2", y: 3.5, label: "B"},
{x: "Name3", y: 4.9, label: "B"},
];
var margin = {top: 20, right: 150, bottom: 60, left: 80},
width = 1160 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal().
rangeBands([0, width], 0.4, 0.8);
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("basis")
.x(function(d) { return x(d.x); })
.y(function(d) { return y(d.y); });
var svg = d3.select("#lineChart").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 + ")");
x.domain(data1.map(function(d) { return d.x; }));
y.domain([0, d3.max(data1, function(d) { return d.y; })]);
svg.append("g").
attr("class", "x axis").
attr("transform", "translate(-70," + height + ")").
call(xAxis);
svg.append("g").
attr("class", "y axis").
call(yAxis);
var dataNest1 = d3.nest()
.key(function(d) {return d.label;})
.entries(data1);
//console.log(dataNest1)
var color = d3.
scale.
ordinal().
range(['red', 'blue']).
domain(d3.keys(data1[0]).
filter(function(key) {return key === 'label';}));
var legendSpace = width/dataNest1.length;
dataNest1.forEach(function(d,i) {
svg.append("path")
.attr("class", "line1")
.style("stroke", function() {
return d.color = color(d.key); })
.attr("id", 'tag'+d.key.replace(/\s+/g, '')) // assign ID **
.attr("d", line(d.values));
svg.append("text")
.attr("x", width - margin.left + 50)
.attr("y", legendSpace/4 + i*(legendSpace/6))
.attr("class", "lineLegend1")
.attr("id", 'tagLegend'+d.key.replace(/\s+/g, '')) // assign ID **
.style("fill", function() {
return d.color = color(d.key); })
.on("click", function(){
console.log(d);
// Determine if current line is visible
var active = d.active ? false : true,
newOpacity = active ? 0 : 1;
// Hide or show the elements based on the ID
d3.select("#tag"+d.key.replace(/\s+/g, ''))
//.remove();
.transition().duration(500)
.style("opacity", newOpacity);
//d3.selectAll(".mouse-per-line circle")
// .style("opacity", newOpacity);
//d3.selectAll(".mouse-per-line text")
// .style("opacity", newOpacity);
// Update whether or not the elements are active
d.active = active;
d3.select("#mouse-per-line-" + d.key)
.style("opacity", newOpacity);
})
.text(d.key);
});
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('line1');
var mousePerLine = mouseG.selectAll('.mouse-per-line')
.data(dataNest1)
.enter()
.append("g")
.attr("class", "mouse-per-line")
.attr("id", function(d){
return "mouse-per-line-" + d.key;
});
mousePerLine.append("circle")
.attr("r", 7)
.style("stroke", function(d) {
return color(d.key);
})
.style("fill", "none")
.style("stroke-width", "1px")
.style("opacity", "0");
mousePerLine.append("text")
.attr("transform", "translate(10,3)");
mouseG.append('svg:rect') // append a rect to catch mouse movements on canvas
.attr('x', x(dataNest1[0].values[0].x))
.attr('width', x(dataNest1[0].values[dataNest1[0].values.length - 1].x) - x(dataNest1[0].values[0].x))
.attr('height', height)
.attr('fill', 'grey')
.style('opacity', '0.4')
.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 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");
})
.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 xQuater = y.invert(d3.mouse(this)[0]),
bisect = d3.bisector(function(d) { return d.x; }).right;
idx = bisect(d.values, xQuater);
var beginning = 0,
end = lines[i].getTotalLength(),
target = null;
while (true){
target = Math.floor((beginning + end) / 2);
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('text')
.text(y.invert(pos.y).toFixed(2));
return "translate(" + mouse[0] + "," + pos.y +")";
});
});
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<meta charset='utf-8'>
<title>Charts</title>
</head>
<body>
<div align="center" id="lineChart">
</div>
<style>
.axis {
font-family: Helvetica;
font-size: 1em;
font-weight: bold;
color: #444444;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.line2{
fill: none;
stroke: red;
stroke-width: 1.5px;
}
.line1{
fill: none;
stroke: blue;
stroke-width: 1.5px;
}
</style>
</body>
</html>