Skip to content

Instantly share code, notes, and snippets.

@micahstubbs
Last active June 14, 2016 22:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save micahstubbs/99df29f258860e53c5e2 to your computer and use it in GitHub Desktop.
Save micahstubbs/99df29f258860e53c5e2 to your computer and use it in GitHub Desktop.
Sporthorse Foal Registrations II
license: CC0-1.0
height: 740
border: no

an iteration on Sporthorse Foal Registrations by phoebebright

an example of using d3 + crossfilter together to make an svg map with linked bar charts

these additions and modifications were made made along the way:

  • format code so that it's comfortable for me
  • comment out the title on the page, and use the text for the title of the bl.ock
  • set .style('white-space', 'nowrap') so that for Northern Ireland and 1999 the bar's text fit's within the bar's height
  • on mouseover of the county bar the cf crossfilter object is filtered for only that county. then, updateMap function is called, which re-renders the map with only the selected county shaded. on mouseout of the county bar, the cf object is unfiltered and the updatemMap function is called once again. Now all of the counties on the map have data-driven fill colors.
  • hide the breed code text
  • abstract out the map svg out of index.html and into a separate file, ireland.svg. this is more difficult that you might think. these links proved to be quite helpful:

d3 google group thread on working with external svg files

an external svg example

an iteration on that example

jsfiddle with .each() technique

Ok, we figured it out. the counties of Northern Ireland now show a data-driven fill color when you mouse over the Northern Ireland bar. Cool! hat tip to @lucastimmons, phoebebright, and @DashingD3js for the implementation ideas.

here's the approach I went with:

in ireland.svg

  • move the path for county Fermanagh from <g id="republic"> to <g id="northernireland">
  • add the class northernireland to all of the paths that are children of the g element with `id="northernireland".
  • set fill color for county Fermanagh path to #fff. I'm curious why the fill was originally set #d1d7ab for this county. Maybe the svg was repurposed from another graphic originally about county Fermanagh?
  • remove the styles ;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible from the county Fermanagh path to match the paths for the other Northern Ireland counties.
  • add sodipodi:nodetypes="cssscssscsssssscccssssscsssssssc" to the county Fermanagh path to match the paths for the other Northern Ireland counties. What is this sodipodi:nodetypes? It looks like some artifact from Inkscape

have comments or thoughts on this example? tweet at me or comment on the github gist.

year county mares coverings foals
1999 CARLOW 38 197 162
1999 CAVAN 42 193 102
1999 CLARE 93 348 259
1999 CORK 134 606 389
1999 DONEGAL 76 182 107
1999 DUBLIN 46 76 57
1999 GALWAY 170 726 478
1999 KERRY 73 179 121
1999 KILDARE 64 148 118
1999 KILKENNY 86 353 249
1999 LAOIS 41 168 122
1999 LEITRIM 29 94 60
1999 LIMERICK 39 186 124
1999 LONGFORD 37 109 74
1999 LOUTH 16 102 70
1999 MAYO 60 262 149
1999 MEATH 67 169 116
1999 MONAGHAN 72 345 190
1999 OFFALY 50 171 127
1999 ROSCOMMON 39 198 135
1999 SLIGO 39 151 96
1999 TIPPERARY 86 419 313
1999 WATERFORD 40 149 106
1999 WESTMEATH 56 195 121
1999 WEXFORD 103 418 303
1999 WICKLOW 61 223 163
1999 NORTHERN IRELAND 234 683 403
1999 ABROAD 73 53 18
2004 CARLOW 30 172 139
2004 CAVAN 108 217 150
2004 CLARE 155 505 420
2004 CORK 232 509 405
2004 DONEGAL 87 212 155
2004 DUBLIN 74 98 77
2004 GALWAY 343 856 602
2004 KERRY 105 260 194
2004 KILDARE 104 165 135
2004 KILKENNY 110 351 288
2004 LAOIS 61 226 180
2004 LEITRIM 32 87 58
2004 LIMERICK 70 232 175
2004 LONGFORD 58 159 133
2004 LOUTH 38 88 50
2004 MAYO 123 327 225
2004 MEATH 73 140 98
2004 MONAGHAN 83 329 234
2004 OFFALY 99 165 134
2004 ROSCOMMON 119 226 164
2004 SLIGO 74 177 134
2004 TIPPERARY 220 551 428
2004 WATERFORD 69 181 137
2004 WESTMEATH 94 171 122
2004 WEXFORD 179 462 398
2004 WICKLOW 128 272 226
2006 CARLOW 78 203 155
2006 CAVAN 70 271 195
2006 CLARE 189 637 490
2006 CORK 221 650 472
2006 DONEGAL 91 237 152
2006 DUBLIN 88 129 92
2006 GALWAY 388 1106 760
2006 KERRY 120 248 183
2006 KILDARE 109 126 95
2006 KILKENNY 144 382 313
2006 LAOIS 90 229 168
2006 LEITRIM 43 120 98
2006 LIMERICK 114 271 193
2006 LONGFORD 74 166 115
2006 LOUTH 36 94 66
2006 MAYO 181 435 280
2006 MEATH 100 153 108
2006 MONAGHAN 90 335 247
2006 OFFALY 105 219 147
2006 ROSCOMMON 130 335 241
2006 SLIGO 92 236 185
2006 TIPPERARY 212 678 493
2006 WATERFORD 104 206 155
2006 WESTMEATH 87 214 150
2006 WEXFORD 216 484 393
2006 WICKLOW 116 372 269
2006 NORTHERN IRELAND 399 817 585
2006 ABROAD 188 64 48
2007 CARLOW 58 202 154
2007 CAVAN 68 297 207
2007 CLARE 151 644 508
2007 CORK 200 687 512
2007 DONEGAL 82 271 171
2007 DUBLIN 60 131 87
2007 GALWAY 315 1227 865
2007 KERRY 118 325 236
2007 KILDARE 74 184 140
2007 KILKENNY 129 397 317
2007 LAOIS 67 282 175
2007 LEITRIM 28 137 112
2007 LIMERICK 102 305 209
2007 LONGFORD 50 177 105
2007 LOUTH 29 111 64
2007 MAYO 140 455 307
2007 MEATH 77 196 149
2007 MONAGHAN 87 340 204
2007 OFFALY 78 220 165
2007 ROSCOMMON 98 349 253
2007 SLIGO 75 258 192
2007 TIPPERARY 181 710 544
2007 WATERFORD 69 230 189
2007 WESTMEATH 83 247 187
2007 WEXFORD 191 584 471
2007 WICKLOW 131 405 304
2007 NORTHERN IRELAND 380 870 585
<!DOCTYPE html>
<html lang="en">
<head>
<!-- thanks to http://carisenda.com/sandbox/choropleth/ -->
<meta charset="utf-8" />
<title>Irish Sporthorse Foal Registrations</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.12/crossfilter.min.js"></script>
<link href='https://fonts.googleapis.com/css?family=Carrois+Gothic' rel='stylesheet' type='text/css'>
<style type="text/css" media="screen">
body, html {
font-family: 'Carrois Gothic', sans-serif;
padding: 0;
margin: 0;
background-color: #fff;
}
#container {
width: 800px;
margin-left: auto;
margin-right: auto;
}
h1 {
text-align: center;
}
ul {
list-style: none;
margin-left: 0;
padding-left: 1em;
text-indent: -1em;
}
.year_label, .year_total {
fill: black;
text-align:center;
font-family: 'Carrois Gothic', sans-serif;
font-size: 18px;
}
.year_total {
font-size: 12px;
}
.slide {
fill: rgb(247,251,255);
}
.enabled {
fill: rgb(238,238,238);
color: black;
}
.bar {
background: red;
}
.county_list {
background: #6bdd8d;
font-size: 12px;
margin: 1px;
padding: 2px;
}
#map, #stats, #selectbar {
float:left;
}
#stats {
width: 200px;
}
#nav {
height: 140px;
vertical-align: bottom;
}
.slide_txt_val {
vertical-align: bottom;
font-size: smaller;
}
.big_year {
font: 200 60px "Helvetica Neue";
fill: #ddd;
}
#selectbar {
opacity: 0;
}
</style>
</head>
<body>
<!--<h1>Sporthorse Foal Registrations</h1>-->
<div id="container">
<div id="nav"></div>
<div id="selectbar">
Breed code<br />Sire's Breedcode
</div>
<div id="map">
</div> <!-- map -->
<div id="stats"></div>
</div>
<script>
var w = 700,
bar_h = 100, // height of div holding bars
bar_top_margin = 30, // space above top of highest bar
bar_padding = 10; // width between bars + padding
default_height = 30; // default height of top bar
d3.csv("foalreg.csv", function(csv) {
// load and organise data
// clean data
csv.forEach(function(v) {
v.year = parseInt(v.year);
v.foals = parseInt(v.foals);
v.mares = parseInt(v.mares);
v.coverings = parseInt(v.coverings);
});
// create crossfilter
var cf = crossfilter(csv);
// create dimensions
cf.county = cf.dimension(function(d) { return d.county; });
cf.year = cf.dimension(function(d) { return d.year; });
cf.foals = cf.dimension(function(d) { return d.foals; });
// totals by year
var t1 = cf.year.group()
.reduceSum(function(d) { return d.foals; })
.top(Infinity);
// convert to an associative array
var year_foals = new Array();
t1.forEach(function(v) {
year_foals[v.key] = v.value;
})
console.log('cf.county.top(3)', cf.county.top(3));
// load svg map
d3.xml("ireland.svg", "image/svg+xml", function(xml) {
var importedNode = document.importNode(xml.documentElement, true);
d3.select("div#map")
.each(function() {
this.appendChild(importedNode);
})
drawYearBars(cf);
updateChart(cf, 1999);
});
////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
function drawYearBars(cf) {
// create histogram/tabs at top
var year_range = [1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011];
// get a list of the years for which there is data
var yrs = cf.year.group()
.reduceSum(function(d) { return d.foals; })
.top(Infinity)
var got_years = new Array();
yrs.forEach(function(v) {
got_years[v.key] = v.value;
})
// scale for histogram
var bar_height = d3.scale.linear()
.domain([0, d3.max(yrs, function(d) { return d.value; })])
.range([0, bar_h]);
var bar_width = ( w / (year_range.length-1) ) - bar_padding;
var bar_xpos = d3.scale.linear()
.domain([0, year_range.length])
.range([0, w]);
var tabs = d3.select("#nav")
.append("svg")
.attr("width", w)
.attr("height", bar_h + 40);
tabs.selectAll("rect")
.data(year_range)
.enter().append("rect")
.attr("x", function(d, i) { return bar_xpos(i); })
.attr("y", function(d) {
if (got_years[d] != undefined) {
return bar_h + bar_top_margin - bar_height(got_years[d]);
} else {
return bar_h + bar_top_margin - default_height;}
})
.attr("height", function(d) {
if (got_years[d] != undefined) {
return bar_height(got_years[d]);
} else {
return default_height;}
})
.attr("width", bar_width)
.attr("id", function(d) { return d; })
.attr("class", function(d) {
var cls = "slide";
if (d in got_years) {
cls += " enabled";
}
return cls;
})
.on('mouseover', function() {
if (got_years[this.id] != undefined) {
d3.select(this).style("fill", "#6bdd8d");
updateChart(cf, this.id);
}
})
.on("mouseout", function(){
if (got_years[this.id] != undefined) {
d3.select(this).style("fill", "rgb(238,238,238)");
}
})
// add year labels
tabs.selectAll(".year_label")
.data(year_range)
.enter()
.append("text")
.text(function(d) { return d; })
.attr("class", "year_label")
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return bar_xpos(i+1) - (bar_width/2) - (bar_padding/2);
})
.attr("y", function() {
return (bar_h + bar_top_margin - 10 );
});
// add value labels
tabs.selectAll(".year_total")
.data(year_range)
.enter()
.append("text")
.text(function(d) {
if (got_years[d] != undefined) {
return got_years[d];
} else {
return ''}
})
.attr("class", "year_total")
.attr("text-anchor", "middle")
.attr("x", function(d, i) { return bar_xpos(i+1) - (bar_width/2) - (bar_padding/2); })
.attr("y", function(d) {
if (got_years[d] != undefined) {
return bar_h + bar_top_margin - 5 - bar_height(got_years[d] ) ;
} else {
return 0;}
});
}
function updateChart(cf, year) {
var total_foals,
data,
t,
ext,
color,
map,
countiesRepublic,
countiesNI;
updateMap(cf, year);
var county_data = cf.foals.top(Infinity);
var ext = [0, 900];
var county_scale = d3.scale.linear()
.domain(ext)
.range([0, 150]);
// show top counties list
d3.select("#stats")
.selectAll("div")
.remove();
var list = d3.select("#stats")
.selectAll("div")
.data(county_data)
list
.enter()
.append("div")
.attr("class", "county_list")
.attr("id", function(d) { return d.county + d.year;})
.text(function(d) {
return toProper(d.county); })
.call(div_bar);
list
.exit().remove();
function div_bar() {
this
.style("height", "16px")
.style('white-space', 'nowrap')
.style("width", function(d) {
return county_scale(d.foals)+"px";
})
}
// put the year selected on the map
d3.select(".big_year").remove();
var year = d3.select("#ireland").append("text")
.attr("class", "big_year")
.attr("text-anchor", "end")
.attr("y", 490)
.attr("x", 360)
.text(year);
setCountyBarMouseoverEvent(cf);
}
function updateMap (cf, year) {
cf.year.filterExact(year);
total_foals = cf.county.group()
.reduceSum(function(d) { return d.foals;})
.top(Infinity);
data = cf.county.top(Infinity);
t = cf.foals.groupAll().value();
ext = d3.extent(data, function(d) { return d.foals; });
ext = [0,1000];
color = d3.scale.linear()
.domain(ext)
.range(["white", "green"]);
map = d3.select('#map');
///////////////////////////////////////////////////////////////////////////////////////////
////////////// shade the counties of the Republic of Ireland individually /////////////////
///////////////////////////////////////////////////////////////////////////////////////////
countiesRepublic = map.selectAll('path.republic')
.style('fill', function(d) { // would normally use attr but svg has used style for fill
var clr = "#fff";
// only look at items in the counties layer
if (this.parentNode.id == "republic") {
var county = this.id.toUpperCase();
data.forEach(function(v, i, ar) {
if (v.county == county) {
clr = color(v.foals)
}
});
}
return clr;
});
///////////////////////////////////////////////////////////////////////////////////////////
/////////////////// shade the counties of Northern Ireland together ///////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
countiesNI = map.selectAll('path.northernireland')
.style('fill', function(d) { // would normally use attr but svg has used style for fill
var clr = "#fff";
// only look at items in the counties layer
if (this.parentNode.id == "northernireland") {
var county = this.id.toUpperCase();
data.forEach(function(v, i, ar) {
if (v.county == "NORTHERN IRELAND") {
clr = color(v.foals)
}
});
}
return clr;
});
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
}
function setCountyBarMouseoverEvent (cf) {
d3.selectAll('div.county_list')
.on('mouseover', function(d) {
// style the bar gray
d3.select(this).style("background", "rgb(238,238,238)");
cf.county.filter(d.county);
updateMap(cf, d.year);
})
.on("mouseout", function(d) {
// style the bar green again
d3.select(this).style("background", "#6bdd8d");
var countyID = toProper(d.county);
cf.county.filterAll();
updateMap(cf, d.year);
})
} //setCountyBarMouseoverEvent
});
function toProper(str)
{
return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
}
</script>
</body>
</html>
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@lucastimmons
Copy link

The data for Northern Ireland isn't broken down into counties. The way you have the map shaded requires the data to be broken down by county and not just region.

Also in the SVG you have Fermanagh as Republic of Ireland and not Northern Ireland.

In the code you have counties = map.selectAll('path.republic'). That's only looking at the counties in the republic and not any in Northern Ireland.

If you fixed the SVG and then added in data for each county in Northern Ireland you'd be close. You'd just need to change the list on the right to include the county names instead of "Northern Ireland" or you'd need to write some code which would map.selectAll('path.northernireland') and then go through and colour each county.

@phoebebright
Copy link

In the original data, I couldn't get NI data for some breed types so there was some hacking to get it to work. If the above comment doesn't sort it, let me know and I'll see if I can get my had back it that space.
This was an early d3 for me and good to see it tidied up!

@micahstubbs
Copy link
Author

nice analysis @lucastimmons, will give that a go 👍

In the original data, I couldn't get NI data for some breed types so there was some hacking to get it to work.

ah, it's good to know the history here! glad you found this iteration @phoebebright. even for early d3 work, it still stood out to me as one of the most clear, approachable examples of d3 and crossfilter together :-)

@phoebebright
Copy link

In the svg there are two layers, republic and northernireland. If you merge them into one layer called "ireland" and then change the code at lines 354 and 359 to read "ireland" instead of "republic" it should work.

@phoebebright
Copy link

And thanks for the compliment!

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