Skip to content

Instantly share code, notes, and snippets.

@alandunning
Last active December 28, 2018 19:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save alandunning/cfb7dcd7951826b9eacd54f0647f48d3 to your computer and use it in GitHub Desktop.
Save alandunning/cfb7dcd7951826b9eacd54f0647f48d3 to your computer and use it in GitHub Desktop.
Line Chart with Circle Tooltip D3 V4
license: gpl-3.0
[
{"year" : "2005", "value": 771900},
{"year" : "2006", "value": 771500},
{"year" : "2007", "value": 770500},
{"year" : "2008", "value": 770400},
{"year" : "2009", "value": 771000},
{"year" : "2010", "value": 772400},
{"year" : "2011", "value": 774100},
{"year" : "2012", "value": 776700},
{"year" : "2013", "value": 777100},
{"year" : "2014", "value": 779200},
{"year" : "2015", "value": 782300}
]
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
background-color: #F1F3F3
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #D4D8DA;
stroke-width: 2px;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke: #6F257F;
stroke-width: 5px;
}
.overlay {
fill: none;
pointer-events: all;
}
.focus circle {
fill: #F1F3F3;
stroke: #6F257F;
stroke-width: 5px;
}
.hover-line {
stroke: #6F257F;
stroke-width: 2px;
stroke-dasharray: 3,3;
}
</style>
<body>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var parseTime = d3.timeParse("%Y")
bisectDate = d3.bisector(function(d) { return d.year; }).left;
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var line = d3.line()
.x(function(d) { return x(d.year); })
.y(function(d) { return y(d.value); });
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("data.json", function(error, data) {
if (error) throw error;
data.forEach(function(d) {
d.year = parseTime(d.year);
d.value = +d.value;
});
x.domain(d3.extent(data, function(d) { return d.year; }));
y.domain([d3.min(data, function(d) { return d.value; }) / 1.005, d3.max(data, function(d) { return d.value; }) * 1.005]);
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y).ticks(6).tickFormat(function(d) { return parseInt(d / 1000) + "k"; }))
.append("text")
.attr("class", "axis-title")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.attr("fill", "#5D6971")
.text("Population)");
g.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
var focus = g.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("line")
.attr("class", "x-hover-line hover-line")
.attr("y1", 0)
.attr("y2", height);
focus.append("line")
.attr("class", "y-hover-line hover-line")
.attr("x1", width)
.attr("x2", width);
focus.append("circle")
.attr("r", 7.5);
focus.append("text")
.attr("x", 15)
.attr("dy", ".31em");
svg.append("rect")
.attr("transform", "translate(" + margin.left + "," + 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", mousemove);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.year > d1.year - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.year) + "," + y(d.value) + ")");
focus.select("text").text(function() { return d.value; });
focus.select(".x-hover-line").attr("y2", height - y(d.value));
focus.select(".y-hover-line").attr("x2", width + width);
}
});
</script>
@fxck
Copy link

fxck commented Nov 8, 2018

Not sure what I'm doing wrong, but

const d = [
  {'year' : '2005', 'value': 771900},
  {'year' : '2006', 'value': 771500},
  {'year' : '2007', 'value': 770500},
  {'year' : '2008', 'value': 770400},
  {'year' : '2009', 'value': 771000},
  {'year' : '2010', 'value': 772400},
  {'year' : '2011', 'value': 774100},
  {'year' : '2012', 'value': 776700},
  {'year' : '2013', 'value': 777100},
  {'year' : '2014', 'value': 779200},
  {'year' : '2015', 'value': 782300}
];

const b = d3.bisector(function(t: any) { return t.year; }).left;

console.log(b(d, 3.5, 1));

this always logs 1..

@deadandhallowed
Copy link

deadandhallowed commented Dec 28, 2018

Hey, I'm pretty new to d3. Would you mind explaining how you make the overlay work? I've been trying to follow your code and I'm not sure which part keeps the appended rectangle from either blocking the whole graph or not being visible at all. Specifically, if I set the overlay fill to none with .style("display", "none") then the focus parts don't show up, but not setting it results in the graph being covered in a black rectangle of course with the edge-most focus parts peaking out from under it.
EDIT: Ah, that's what the pointer-events: all attribute does, makes them work even with the fill set to none. Putting this here in case someone else wonders one day.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment