nikunj2512 nikunj2512 - 7 months ago 339
Javascript Question

Styling tooltip in multi-line D3.js line chart

i have made a multi-line chart using D3.js with tooltips. Everything is working fine in this graph but i want to add styling to tooltips for all the charts in the graph but unable to do so.



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("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[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('text')
.text(y.invert(pos.y).toFixed(2));

return "translate(" + mouse[0] + "," + pos.y + ")";
});
});

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;
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>





I want to add a small white box at the top of each chart line with both x and y values. Please help me out.

JSFIDDLE

Answer

You can do it like this:

First make an text with id x-text and y-text append it to the group as shown below(as you want to display both x and y values).

mousePerLine.append("text").attr("id", "x-text")
  .attr("transform", "translate(10,3)");
mousePerLine.append("text").attr("id", "y-text")
  .attr("transform", "translate(10,23)");

Next you can show the values in the text created above on mouse move like this:

 //for y text on tooltip 
 d3.select(this).select('#y-text')
  .text("y: " + y.invert(pos.y).toFixed(2));
//make date in the format %H:%M:%S for x tooltip
d3.select(this).select('#x-text')
  .text("x: " + d3.time.format("%X")(xDate));

Next make a rectangle around the tooltip for styling:

mousePerLine.append("rect")
    .attr("x", 0)
    .attr("y", -10)
  .attr("width", 70)//width of rectangle
    .attr("height", 35)//height of rectangle
  .style("stroke", function(d, i) {
    return z(i);
  })
  .attr("class", "tooltip-container")//add class for styling rectangle
  .style("fill", "red")//give color red to it.
    .style("opacity", "0")
  .style("stroke-width", "1px");

working code here