An example using my d3 icon array plugin illustrating when you may want to to use widthFirst(true)
vs widthFirst(false)
Last active
May 28, 2019 04:32
-
-
Save tomgp/a2c34418f2341f078624 to your computer and use it in GitHub Desktop.
icon array widthFirst or not?
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>Simple icon array example</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; | |
} | |
p.bar-label{ | |
margin:0px; | |
} | |
.Conservative{ | |
fill:#00F; | |
} | |
.Labour{ | |
fill:#F00; | |
} | |
.Liberal{ | |
fill:#EA0; | |
} | |
.SNP{ | |
fill:#DD0; | |
} | |
.DUP, .Other{ | |
fill:#AAA; | |
} | |
p{ | |
max-width: 600px; | |
} | |
</style> | |
</head> | |
<body> | |
<p>By default the icon array is built width first. Whilst this has some nice properties like using the full width available, leading to a percieved compactness, it doesn't allow easy comparison between arrays. In this case the Conservative and Labour arrays look about the same ... </p> | |
<div id="width-first"> | |
</div> | |
<p>A height first construction allows a better approximation of a bar chart (comparisons of length are much easier than comparisons of area so we want to emphasise those differences) and the difference between the front runners becomes more apparent.</p> | |
<div id="height-first"> | |
</div> | |
<p>Note the smaller values become a little tricker to compare but that the ease of countability (is that a word?) which a icon array provides can help here providing more legible information than an equivalent bar chart would at a similar scale. | |
</body> | |
<script type="text/javascript"> | |
var results = [ | |
{party:'Conservative', seats:331 }, | |
{party:'Labour', seats:232}, | |
{party:'SNP', seats:56}, | |
{party:'Liberal Democats', seats:8}, | |
{party:'DUP', seats:8}, | |
{party:'Other', seats:15} | |
]; | |
var gridWidth = 80; | |
var gridHeight = 5; | |
var layout = d3_iconarray.layout() | |
.width(gridWidth) | |
.height(gridHeight); | |
var width = 600; | |
var height = 40; | |
var radius = 2.5; | |
var margin = { top:radius*2, left:radius*2, bottom:radius*2, right:radius*2 } | |
var scale = d3.scaleLinear() | |
.range([0, (width-(margin.left + margin.right))]) | |
.domain([0, gridWidth]); | |
console.log(scale.range()) | |
d3.select('#width-first') | |
.selectAll('div.result') | |
.data(results) | |
.enter() | |
.append('div').attr('class','result') | |
.call(arrayBars, true); | |
d3.select('#height-first') | |
.selectAll('div.result') | |
.data(results) | |
.enter() | |
.append('div').attr('class','result') | |
.call(arrayBars, false); | |
function arrayBars(parent, widthFirst){ | |
layout.widthFirst(widthFirst); | |
parent.append('p') | |
.attr('class','bar-label') | |
.html(function(d){ | |
return d.party; | |
}); | |
parent.append('svg') | |
.attr('width', width).attr('height', height) | |
.append('g') | |
.attr('transform','translate('+margin.left+','+margin.top+')') | |
.attr('class',function(d){return d.party}) | |
.selectAll('circle') | |
.data(function(d){ return layout( d3.range(0, d.seats, 1) ); }) | |
.enter() | |
.append('circle') | |
.attr('cx',function(d){ return scale(d.position.x); }) | |
.attr('cy',function(d){ return scale(d.position.y); }) | |
.attr('r', radius) | |
} | |
</script> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment