D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
nadinagray
Full window
Github gist
Constraint relaxation labels
Built with
blockbuilder.org
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <style> body { font: 10px sans-serif; margin: 0; background: #FFF; } .line { fill: none; stroke-width: 1.5px; } .hover { cursor: pointer; } .label { pointer-events: none; font-weight: bold; } .voronoi path { fill: none; pointer-events: all; } .voronoi--show path { stroke: red; stroke-opacity: 0.2; } </style> <script src="d3.v4.min.js"></script> </head> <body> <script> var margin = { top: 20, right: 80, bottom: 20, left: 15 }, width = 960 - margin.left - margin.right, height = 600 - margin.top - margin.bottom; var parseDate = d3.timeParse("%b %Y"); var x = d3.scaleTime() .range([0, width]); d3.tsv("stocks.tsv", function(d) { d.price = +d.price; d.date = parseDate(d.date); return d; }, function(error, data) { var symbols = d3.nest() .key(function(d) { return d.symbol; }) .entries(data); var y = d3.scaleLinear() .domain([0, d3.max(symbols, function(d) { return d3.max(d.values, function(d) { return d.price; }) })]) .range([height, 0]); x.domain([ d3.min(symbols, function(symbol) { return symbol.values[0].date; }), d3.max(symbols, function(symbol) { return symbol.values[symbol.values.length - 1].date; }) ]); var xAxis = d3.axisBottom(x) .ticks(d3.timeYear); var colors = d3.scaleOrdinal() .domain(["MSFT", "S&P 500", "AMZN", "IBM", "GOOG", "10 Year T-Note", "AAPL"]) .range(["#fc4a34", "#A60911", "#F49831", "#1D6AB2", "#34A853", "#e2bb19", "#666666"]) var lastData = symbols.map(function(symbol) { return { "key": symbol.key, "date": symbol.values[symbol.values.length - 1].date, "value": y(symbol.values[symbol.values.length - 1].price), "m": y(symbol.values[symbol.values.length - 1].price), "color": colors(symbol.key) } }); 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 voronoi = d3.voronoi() .x(function(d) { return x(d.date); }) .y(function(d) { return y(d.price); }) .extent([[-margin.left, -margin.top], [width + margin.right, height + margin.bottom]]); svg.selectAll("line") .data(symbols, function(d) { return d.values; }) .enter() .append("path") .attr("class", "line hover") .attr('stroke', function(d) { return colors(d.key) }) .attr("d", function(symbol) { symbol.line = this; return d3.line() .x(function(d) { return x(d.date); }) .y(function(d) { return y(d.price); }) (symbol.values); }) var voronoiGroup = svg.append("g") .attr("class", "voronoi"); voronoiGroup.selectAll("path") .data(voronoi.polygons(d3.merge(symbols.map(function(d) { return d.values; })))) .enter().append("path") .attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; }) .on("mouseover", mouseover) .on("mouseout", mouseout); svg.append("g") .attr("transform", "translate(0," + height + ")") .call(xAxis); svg.selectAll("point") .data(lastData) .enter() .append("circle") .attr('class', 'label hover') .attr("cx", function(d) { return x(d.date); }) .attr("cy", function(d) { return d.value; }) .attr('fill', function(d) { return d.color }) .attr('r', '4'); var alpha = 0.5, spacing = 14 function relax() { var again = false; lastData.forEach(function(a,i) { lastData.slice(i+1).forEach(function(b) { var dy = a.m - b.m; if (Math.abs(dy) < spacing) { again = true; var sign = dy > 0 ? 5 : -5; a.m += sign * alpha; b.m -= sign * alpha; } }); }); if (again) setTimeout(relax, 20) else { svg.selectAll("label") .data(lastData) .enter() .append("text") .attr('class', 'label hover') .attr("x", function(d) { return x(d.date); }) .attr("y", function(d) { return d.m; }) .attr('dx', '1em') .attr('dy', '0.3em') .attr('fill', function(d) { return d.color }) .style("text-anchor", "start") .text(function(d) { return d.key; }) } } relax() function mouseover(d){ d3.selectAll(".line") .style("opacity", 0.12) .filter(function(z) { return z.key == d.data.symbol; }) .style("opacity", 1) .style("stroke-width", 2.5); d3.selectAll(".label") .style("opacity", 0) .filter(function(z) { return z.key == d.data.symbol; }) .attr("y", function(d){ return d.value}) .style("opacity", 1); } function mouseout(d){ d3.selectAll(".line") .style("opacity", 1) .style("stroke-width", null); d3.selectAll(".label") .attr("y", function(d){ return d.m}) .style("opacity", 1); } }) </script> </body> </html>