D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
jamazel
Full window
Github gist
Test zoomable treemap
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Adding Tooltips</title> <script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script> </head> <style> #graphicHeading { margin-top: 5px; color: #6A6D68; font-family: "BentonSans"; font-size: 24px; } #graphicSunHead { color: #6A6D68; font-family: "BentonSans"; font-size: 14px; line-height: 21px; } #chart { height: 650px; background: #fff1e0; font-family: "BentonSans"; font-size: 14px; margin-bottom: 4px; } .buttons { float:left; margin-top: 8px; margin-bottom: 4px; color: #6A6D68; font-family: "BentonSans"; font-size: 14px; line-height: 21px; } #instructions { margin-top: 8px; margin-bottom: 4px; float: right; font-family: "BentonSans"; font-size: 14px; color: #6A6D68; } text { pointer-events: none; } .grandparent text { font-weight: bold; } rect { stroke: #fff1e0; stroke-width: 1px; } rect.parent, .grandparent rect { stroke: #fff1e0; stroke-width: 2px; } .grandparent rect { fill: #B1B3B5; } .grandparent:hover rect { fill: #939597; } .children rect.parent, .grandparent rect { cursor: pointer; } .children rect.parent { stroke: #fff1e0; fill-opacity: .1; } .children:hover rect.child { } .tooltip { background: #eee; box-shadow: 0 0 5px #999999; color: #333; font-size: 14px; line-height: 15px; left: 130px; width: 250px; border-style: solid; border-color: #76787A; border-width: 2px; border-radius: 7px; position: absolute; z-index: 1000; } #notes { font-family: "BentonSans"; font-size: 11px; line-height: 12px; color: #6A6D68; margin-bottom: 8px; } </style> <div id="main-content"> <div id="graphicHeading">Political party funding in Britain</div> <div id="graphicSunHead">Donations to registered parties since May 2010*</div> <form class="buttons"> <label><input type="radio" name="mode" value="party" checked> By party</label> <label><input type="radio" name="mode" value="donor"> By donor</label> </form> <div id="chart"></div> <div id="notes">*Donations to MPs, candidates and members associations are attributed to their respective party. Excludes contributions to parties that have de-registered during this parliament and local political organisations. Contributions are named as they appear on the electoral register so individual donors may appear more than once (Lord J. Smith, John Smith, John A. Smith, J. Smith etc)</div> </div> <script> var chartWidth=document.getElementById('main-content').getBoundingClientRect().width; var margin = {top: 25, right: 0, bottom: 0, left: 0}; var width = chartWidth; var height = 620 - margin.top - margin.bottom, //formatNumber = d3.format(",d"), formatNumber = d3.format(","), transitioning; var color = d3.scale.ordinal() .domain(["BNP","Conservative","Labour","UKIP","Green Party","Scottish Green Party","SNP","Liberal Democrats","Plaid Cymru","Socialist Party of Great Britain","Christian Peoples Alliance","Scotish Socialist Party","Focus on Scotland","Yes in May 2011 Ltd","No Campaign Limited","Grey","Movement for Change"]) .range(["#546A7E","#6da8e1","#e25050","#ca6dbf","#65a68c","#65a68c","#F2E24D","#ffc660","#99d2d0","#A50409","#813887","#EF4123","#4588FF","#9B3E97","#D7E025","#D1D2D4","#636466"]); var x = d3.scale.linear() .domain([0, width]) .range([0, width]); var y = d3.scale.linear() .domain([0, height]) .range([0, height]); var firstRun=false var svg = d3.select("#chart").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.bottom + margin.top) .style("margin-left", -margin.left + "px") .style("margin.right", -margin.right + "px") .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")") .style("shape-rendering", "crispEdges"); var tip=d3.select ('#chart').append('div') .attr('class', 'tooltip') .style('position','absolute') .style('padding','5px 10px') .style('background','white') .style('opacity',0) var grandparent = svg.append("g") .attr("id","menuBar") .attr("class", "grandparent"); grandparent.append("rect") .attr("y", -margin.top) .attr("width", width) .attr("height", margin.top); grandparent.append("text") .attr("x", 6) .attr("y", 6 - margin.top) .attr("dy", ".75em"); var root={} var treemap = d3.layout.treemap() //.children(function(d, depth) { return depth ? null : d.children; }) .children(function(d, depth) { return depth ? null : d.values; }) //.text(function(d) { return d.key; }) .value(function(d) { return d.value; }) .sort(function(a, b) { return a.value - b.value; }) .ratio(height / width * 0.5 * (1 + Math.sqrt(5))) .round(false); var button="party"; d3.csv("2010_onward.csv",function(data) { root = {"key":"Party", "values":d3.nest() .key(function (d) { return d.entity_name;}) .key(function (d) { return d.donor_type;}) .key(function (d) { return d.donor_name;}) .entries(data) } d3.selectAll("input").on("click", function change() { //console.log("func: change"); var g = d3.selectAll(".depth").remove(); switch (this.value) { case "party": //console.log("party"); root = {"key":"Party", "values":d3.nest() .key(function (d) { return d.entity_name;}) .key(function (d) { return d.donor_type;}) .key(function (d) { return d.donor_name;}) .entries(data) } button="party" initialize(root); accumulate(root); layout(root); display(root); break; case "donor": //console.log("donor"); root = {"key":"Donor", "values":d3.nest() .key(function (d) { return d.donor_type;}) .key(function (d) { return d.donor_name;}) .key(function (d) { return d.entity_name;}) .entries(data) } button="donor"; initialize(root); accumulate(root); layout(root); display(root); break; } }); initialize(root); accumulate(root); layout(root); display(root); function initialize(root) { root.x = root.y = 0; root.dx = width; root.dy = height; root.depth = 0; } // Aggregate the values for internal nodes. This is normally done by the // treemap layout, but not here because of our custom implementation. function accumulate(d) { //console.log(d.children); return d.values ? d.value = d.values.reduce(function(p, v) { return p + accumulate(v); }, 0) : +d.value; } // Compute the treemap layout recursively such that each group of siblings // uses the same size (1×1) rather than the dimensions of the parent cell. // This optimizes the layout for the current zoom state. Note that a wrapper // object is created for the parent node for each group of siblings so that // the parent’s dimensions are not discarded as we recurse. Since each group // of sibling was laid out in 1×1, we must rescale to fit using absolute // coordinates. This lets us use a viewport to zoom. function layout(d) { if (d.values) { treemap.nodes({values: d.values}); d.values.forEach(function(c) { c.x = d.x + c.x * d.dx; c.y = d.y + c.y * d.dy; c.dx *= d.dx; c.dy *= d.dy; c.parent = d; layout(c); }); } } function display(d) { grandparent .datum(d.parent) .on("click", transition) .select("text") .text(boxNames(d,'long')); var g1 = svg.insert("g", ".grandparent") .datum(d) .attr("class", "depth"); var g = g1.selectAll("g") .data(d.values) .enter().append("g") g.filter(function(d) { return d.values; }) .classed("children", true) .on("click", transition); g.selectAll(".child") .data(function(d) { return d.values || [d]; }) .enter().append("rect") .attr("class", "child") .call(rect); g.append("rect") .attr("class", "parent") .on('mousemove', function(d){return toolTip(d)}) .on('mouseout', function(d){return toolTipOff(d)}) .call(rect); // .append("title") // .text(function(d) { return formatNumber(+d.value); }); g.append("text") .attr("dy", ".75em") .text(function(d) { return boxNames(d,'short');}) .style("opacity", function(d) { d.w = this.getComputedTextLength(); return d.dx > d.w*.42 ? 1 : 0; }) .call(text); function transition(d) { if (transitioning || !d) return; transitioning = true; var g2 = display(d), t1 = g1.transition().duration(750), t2 = g2.transition().duration(750); // Update the domain only after entering new elements. x.domain([d.x, d.x + d.dx]); y.domain([d.y, d.y + d.dy]); // Enable anti-aliasing during the transition. svg.style("shape-rendering", null); // Draw child nodes on top of parent nodes. svg.selectAll(".depth").sort(function(a, b) { return a.depth - b.depth; }); // Fade-in entering text. g2.selectAll("text").style("fill-opacity", 0); // Transition to the new view. t1.selectAll("text").call(text).style("fill-opacity", 0); t2.selectAll("text").call(text).style("fill-opacity", 1); t1.selectAll("rect").call(rect); t2.selectAll("rect").call(rect); // Remove the old node when the transition is finished. t1.remove().each("end", function() { svg.style("shape-rendering", "crispEdges"); transitioning = false; }); } return g; } function text(text) { //console.log(text.node().getBoundingClientRect().width); text.attr("x", function(d) { return x(d.x) + 6; }) .attr("y", function(d) { return y(d.y) + 6; }); } function rect(rect) { rect.attr("x", function(d) { return x(d.x); }) .attr("y", function(d) { return y(d.y); }) .attr("width", function(d) { return x(d.x + d.dx) - x(d.x); }) .attr("height", function(d) { return y(d.y + d.dy) - y(d.y); }) .style("fill", function(d) { return (color(findProperty(d, 'entity_name')))}); } function findProperty(d, propertyName){ if (button=="party"){ if(d[propertyName]) { //console.log("button= ",button) return d[propertyName]; }; if(d.values) { return findProperty(d.values[0], propertyName); } return 'error: couldnt find ' + propertyName; } ///////////////// if (button=="donor"){ if(d[propertyName]) { return d[propertyName]; }; if(d.values) { try{ if (d.parent.parent.key=="Donor" && d.values.length>1){ //console.log(d) return "Grey" } else{return findProperty(d.values[0], propertyName)} return 'error: couldnt find ' + propertyName; } catch(err) {} } } } function toolTip(d) { var xPosition = d3.event.pageX + 5; var yPosition = d3.event.pageY + 5; if (xPosition>width/2) { xPosition=xPosition-tip.style("width").replace("px", "")-5; } if (yPosition>height){ yPosition=yPosition-tip.style("height").replace("px", "")-10; } if(d.key) { tip.style('opacity',.9) .html("<b>"+d.key+ "</b> </br>Total £"+formatNumber(+d.value.toFixed(2))) .style("left", xPosition + "px") .style("top", yPosition + "px"); } else { tip.style('opacity',.9) .html("<b>£"+formatNumber(+d.value.toFixed(2))+"</b> to "+d.entity_name+"</br><b>Donated on </b>"+d.received_date+"</br><b>Donated type </b>"+d.type_of_donation+" "+d.nature_provision+"</br><b>Reported on </b>"+d.reported_date+"</br><b>Reference </b>"+d.ec_reference+"</br><b>Made via </b>"+d.made_via) .style("left", xPosition + "px") .style("top", yPosition + "px"); } } function toolTipOff(d) { tip.style('opacity',0); }; function boxNames(d, length) { //console.log(d); return d.key; // if(length == 'long') return boxNames(d.parent) + " > " + d.key; // if(length == 'short') return d.key // return d.parent // ? boxNames(d.parent) + " > " + d.key // : d.key; } }); </script>
Modified
http://d3js.org/d3.v3.min.js
to a secure url
https://d3js.org/d3.v3.min.js