A simple example of using the layout and scale functions of my d3 icon array plugin
Last active
March 3, 2016 19:07
-
-
Save tomgp/eb4079086278ae34844a to your computer and use it in GitHub Desktop.
Modal transport share
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function (global, factory) { | |
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-scale')) : | |
typeof define === 'function' && define.amd ? define(['exports', 'd3-scale'], factory) : | |
(factory((global.d3_iconarray = global.d3_iconarray || {}),global.d3)); | |
}(this, function (exports,d3) { 'use strict'; | |
function iconArrayLayout() { | |
var width = undefined; | |
var height = undefined; | |
var widthFirst = true; | |
var maxDimension = undefined; | |
function layout(data){ | |
//work our missing height, width stuff | |
setDimensions(data.length); | |
return data.map(function(d,i){ | |
return { | |
data:d, | |
position:position(i) | |
}; | |
}); | |
} | |
function position(i){ | |
if(isNaN(width) || isNaN(height)){ | |
console.log('Warning: width/height undefined') | |
return 0; | |
} | |
if(widthFirst){ | |
return { | |
x: i % width, | |
y: Math.floor( i/width ) | |
}; | |
}else{ | |
return { | |
x: Math.floor( i/height ), | |
y: i % height | |
}; | |
} | |
} | |
function setDimensions(l){ | |
//neither width or height is defined | |
if(isNaN(width) && isNaN(height)){ | |
console.log('no width or height'); | |
if(widthFirst){ | |
width = Math.ceil( Math.sqrt(l) ); | |
height = Math.ceil( l / width ); | |
}else{ | |
height = Math.ceil( Math.sqrt(l) ); | |
width = Math.ceil( l / height ); | |
} | |
}else if(isNaN(width)){ //width undefined | |
width = Math.ceil( l / height ); | |
}else if(isNaN(height)){ //height undefined | |
height = Math.ceil( l / width ); | |
} | |
} | |
layout.maxDimension = function(x){ | |
var itemPosition = position(x); | |
if(widthFirst){ | |
var x = Math.max(itemPosition.x, width); | |
return Math.max(x, itemPosition.y); | |
} | |
var y = Math.max(itemPosition.y, height); | |
return Math.max(y, itemPosition.x); | |
} | |
layout.position = function(x){ | |
return position(x); | |
} | |
layout.width = function(x){ | |
if(x === undefined) return width; | |
width = x; | |
return layout; | |
}; | |
layout.height = function(x){ | |
if(x === undefined) return height; | |
height = x; | |
return layout; | |
}; | |
layout.widthFirst = function(b){ | |
if(b === undefined) return widthFirst; | |
widthFirst = b; | |
return layout; | |
}; | |
return layout; | |
}; | |
function iconArrayScale(){ | |
var domain = [0,100]; | |
var range = [0,100]; | |
var gapInterval = 10; | |
var gapSize = 0; //default no change | |
var notionalScale = d3.scaleLinear() | |
.domain(domain) | |
.range(range); | |
function scale(domainValue){ | |
var rangeValue = 20; | |
var adjustedDomainValue = domainValue + Math.floor(domainValue/gapInterval)*gapSize; | |
//console.log(notionalScale.domain()); | |
return rangeValue = notionalScale(adjustedDomainValue); | |
} | |
function rescale(){ | |
//calculate an adjusted domain | |
var domainLength = (domain[1] - domain[0]) * gapSize; | |
var gaps = Math.ceil( domainLength/ gapInterval ); | |
var adjustedDomain = [ domain[0], domain[1] + gaps ]; | |
//calculate an adjusted range | |
notionalScale.domain(adjustedDomain) | |
.range(range); | |
} | |
scale.gapInterval = function(x){ | |
if(!x) return gapInterval; | |
gapInterval = x; | |
rescale(); | |
return scale; | |
}; | |
scale.gapSize = function(x){ | |
if(isNaN(x)) return gapSize; | |
gapSize = x; | |
rescale(); | |
return scale; | |
} | |
scale.domain = function(array){ | |
if(!array) return domain; | |
domain = array; | |
rescale(); | |
return scale; | |
}; | |
scale.range = function(array){ | |
if(!array) return range; | |
range = array; | |
rescale(); | |
return scale; | |
}; | |
rescale(); | |
return scale; | |
} | |
var version = "0.0.1"; | |
exports.version = version; | |
exports.layout = iconArrayLayout; | |
exports.scale = iconArrayScale; | |
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Transport modal share</title> | |
<script src="//d3js.org/d3.v4.0.0-alpha.18.min.js" charset="utf-8"></script> | |
<script type="text/javascript" src="d3-iconarray.js"></script> | |
<style type="text/css"> | |
*{ | |
font-family: sans-serif; | |
} | |
button{ | |
cursor: pointer; | |
} | |
svg{display:block;} | |
.zero{display: none;} | |
text{ | |
font-size: 20px; | |
} | |
hr{ | |
border:none; | |
border-bottom: 1px solid black; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Modal transport share in cities with more than a million people</h1> | |
<div id="nav"> | |
</div> | |
<hr> | |
<h2 id="chart-title"></h2> | |
<div id="viz"></div> | |
<p><a href="https://en.wikipedia.org/wiki/Modal_share">Source: Wikipedia</a></p> | |
</body> | |
<script type="text/javascript"> | |
var picto = { | |
'walking':'🚶', | |
'cycling':'🚴', | |
'public transport':'🚌', | |
'private motor vehicle':'🚗' | |
}; | |
var categories = ['walking', 'cycling', 'public transport', 'private motor vehicle']; | |
var width = 600, height = 150, margin = {top:20,left:10,bottom:0,right:10}; | |
var layout = d3_iconarray.layout() | |
.height(5) | |
.widthFirst(false); | |
var xScale = d3_iconarray.scale() | |
.domain([0,20]) | |
.range([0, width-(margin.left+margin.right)]) | |
.gapSize(1) | |
.gapInterval(5); | |
var yScale = d3.scaleLinear() | |
.domain([0,5]) | |
.range([0,height-(margin.top + margin.bottom)]) | |
d3.tsv('modal-share-data.tsv',function(data){ | |
console.log(data.columns) | |
d3.select('#nav').selectAll('span.city-button') | |
.data(data).enter() | |
.append('span').attr('class','city-button') | |
.append('button') | |
.html(function(d){return d.City; }) | |
.on('click', function(d){ | |
draw(d); | |
}) | |
draw(data.find(function(d){ | |
return (d.City == 'London'); | |
})); | |
}) | |
function draw(data){ | |
d3.select('#chart-title').text(function(){ | |
return data.City + ' ('+data.year+')'; | |
}); | |
d3.select('#viz') | |
.selectAll('svg') | |
.data(categories) | |
.enter() | |
.append('svg') | |
.attr('width', width) | |
.attr('height', height) | |
.attr('class','category') | |
.append('g') | |
.attr('class','plot') | |
.attr('transform','translate('+margin.left+','+margin.top+')') | |
d3.select('#viz') | |
.selectAll('svg') | |
.classed('zero', function(d){ | |
return (data[d] == 0); | |
}); | |
d3.selectAll('svg g.plot') | |
.call(function(parent){ | |
var join = parent.selectAll('.icon') | |
.data(function(d){ return layout( expand(data[d], d) ); }); | |
join.exit().remove(); | |
join.enter() | |
.append('text').attr('class','icon'); | |
join.attr('transform', function(d){ | |
return 'translate(' + xScale(d.position.x) + ',' + yScale(d.position.y) + ')' | |
}).text(function(d){ return picto[d.data]; }) | |
}) | |
} | |
function expand(length, category){ | |
return d3.range(0,length,1).map(function(){ return category; }) | |
} | |
</script> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment