Skip to content

Instantly share code, notes, and snippets.

@splarty
Last active June 8, 2017 01:20
Show Gist options
  • Save splarty/0190cad3d0ad22a3daa2e869481f0c88 to your computer and use it in GitHub Desktop.
Save splarty/0190cad3d0ad22a3daa2e869481f0c88 to your computer and use it in GitHub Desktop.
circlePacking_tooltips_d3.js
<html>
<head>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.5/d3.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.6.7/d3-tip.min.js"></script>
<style>
body {
width: 100vw;
height: 100vh;
text-align: center;
}
#container {
margin: auto;
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
}
#chart, #text1, #text2 {
flex: 1 100%;
text-align: justify;
}
#chart {
flex: 4 0px;
}
#text1 {
order: 1;
}
#chart {
order: 2;
}
#text2 {
order: 3;
}
.node {
cursor: pointer;
}
.node:hover {
stroke: #000;
stroke-width: 1.5px;
}
.node--leaf {
fill: yellow;
}
.label {
font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
text-anchor: middle;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff;
}
.label, .node--root, .node--leaf {
pointer-events: none;
}
circle {
pointer-events: all;
}
</style>
</head>
<body>
<h1> Assists Per Year - La Liga</h1>
<h2> A Zoomable Circle Packing Visualization</h2>
<div id='container'>
<div id='text1'>
This visualization employs <a href="https://en.wikipedia.org/wiki/Circle_packing#Unequal_circles">Circle Packing of Circles of Unequal Sizes</a>. Where the sizes of the circles are determined by the size of the contents. In this case, the contents are the number of assists per year by the top players in La Liga.
<br>
<br>
For example, from the visualization, we can see that 2015 was the year with the most assists and 2010 was the year with the fewest. The yellow circles signify players and the size of the circles signifies the number of assists that each player had in that year.
</div>
<div id='chart'></div>
<div id='text2'>
Click on the inner circles to zoom in
</div>
</div>
<script>
// This is a more complicated version.
// The Dataset consists of:
// year,player,appearance,goals,goals_per_game, assists,assists_per_game
// If you click on the year, it will zoom in.
// Basic Options
var margin = 20,
diameter = 960;
var color = d3.scale.category20c();
var pack = d3.layout.pack().padding(2).size([diameter - margin, diameter - margin]).value(function(d) {
return d.assists;
});
var svg = d3.select("#chart").append("svg").attr("width", diameter).attr("height", diameter).append("g").attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
//*************************************************
// GET THE CSV DATA
//*************************************************
d3.csv("https://s3-us-west-2.amazonaws.com/s.cdpn.io/565729/Football_Data.csv", function(error, data) {
_.each(data, function(element, index, list) {
element.pop = +element.pop;
});
//*************************************************
// THE FUNCTION to turn your data from CSV into
// nested data object (this does the work for you)
//*************************************************
function genJSON(csvData, groups) {
var genGroups = function(data) {
return _.map(data, function(element, index) {
return {
name : index,
children : element
};
});
};
var nest = function(node, curIndex) {
if (curIndex === 0) {
node.children = genGroups(_.groupBy(csvData, groups[0]));
_.each(node.children, function(child) {
nest(child, curIndex + 1);
});
} else {
if (curIndex < groups.length) {
node.children = genGroups(_.groupBy(node.children, groups[curIndex]));
_.each(node.children, function(child) {
nest(child, curIndex + 1);
});
}
}
return node;
};
return nest({}, 0);
}
//*************************************************
// CALL FUNCTION WITH ARRAY OF GROUPS
//*************************************************
// Unlike before, this controls the circles.
// You don't change any other options.
var preppedData = genJSON(data, ['year', 'assists', 'player']);
//*************************************************
// YOUR DATA VISUALIZATION CODE HERE
//*************************************************
var focus = preppedData,
nodes = pack.nodes(preppedData),
view;
console.log(nodes);
// Init tooltip
var tipCirclePack = d3.tip().attr('class', 'd3-tip').offset([-10, 0]).html(function(d) {
if ( typeof d.player != 'undefined') {
return "<strong>" + d.name + ": </strong> <span style='color:grey'>" + (d.assists) + "</span>";
}else if ( typeof d.name != 'undefined') {
return "<span style='color:white'> Circle ID: </span><strong>" + d.name + " </strong> ";
} else {
return "<span style='color:white'><strong> Circle Packing - La Liga </strong> </span>";
}
});
svg.call(tipCirclePack);
var circle = svg.selectAll("circle").data(nodes).enter().append("circle").attr("class", function(d) {
return d.parent ? d.children ? "node" : "node node--leaf" : "node node--preppedData";
}).style("fill", function(d) {
return d.children ? color(d.depth) : null;
}).on("click", function(d) {
if (focus !== d)
zoom(d), d3.event.stopPropagation();
}).on('mouseover', tipCirclePack.show).on('mouseout', tipCirclePack.hide);
;
var text = svg.selectAll("text").data(nodes).enter().append("text").attr("class", "label").style("fill-opacity", function(d) {
return d.parent === preppedData ? 1 : 0.15;
}).style("display", function(d) {
return d.parent === preppedData ? "inline" : "inline";
}).style("font-size", function(d) {
return d.parent === preppedData ? "24px" : "6px";
}).text(function(d) {
if ( typeof d.player != 'undefined') {
var textName = d.player;
} else {
var textName = d.name;
}
return textName;
});
var node = svg.selectAll("circle,text");
d3.select("body").style("background", color(-1)).on("click", function() {
zoom(preppedData);
});
zoomTo([preppedData.x, preppedData.y, preppedData.r * 2 + margin]);
function zoom(d) {
var focus0 = focus;
focus = d;
var transition = d3.transition().duration(d3.event.altKey ? 7500 : 750).tween("zoom", function(d) {
var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]);
return function(t) {
zoomTo(i(t));
};
});
transition.selectAll("text").filter(function(d) {
return d.parent === focus || this.style.display === "inline";
}).style("fill-opacity", function(d) {
return d.parent === focus ? 1 : 0.15;
}).style("font-size", function(d) {
return d.parent === focus ? "24px" : "6px";
}).each("start", function(d) {
if (d.parent === focus)
this.style.display = "inline";
}).each("end", function(d) {
if (d.parent !== focus)
this.style.display = "inline";
});
}
function zoomTo(v) {
var k = diameter / v[2];
view = v;
node.attr("transform", function(d) {
return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")";
});
circle.attr("r", function(d) {
return d.r * k;
});
}
});
d3.select(self.frameElement).style("height", diameter + "px");
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment