Last active
August 29, 2015 14:20
-
-
Save tomgp/1d83cc4b095d2015408d to your computer and use it in GitHub Desktop.
New Political Compass
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
<html> | |
<head> | |
<title>new political compas</title> | |
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
<script src="ternary.js"></script> | |
<style media="screen"> | |
sup{ | |
vertical-align: top; | |
font-size: 0.8rem; | |
} | |
body{ | |
font-family:sans-serif; | |
} | |
.column{ | |
display: block; | |
margin-left: auto; | |
margin-right: auto; | |
max-width:800px; | |
} | |
img{ | |
width:100%; | |
padding-top:1rem; | |
padding-bottom:1rem; | |
} | |
.note img{ | |
display:block; | |
width:30%; | |
} | |
h2{ | |
font-size:1.3rem; | |
} | |
blockquote{ | |
font-family:serif; | |
} | |
hr{ | |
border: 0; | |
height: 0; | |
border-top:1px solid #999; | |
} | |
.date{ | |
border-top:1px solid #999; | |
color:#999; | |
padding-bottom:10px; | |
padding-top:10px; | |
} | |
.content{ | |
max-width:600px; | |
line-height:1.5rem; | |
font-size:1.1rem; | |
} | |
.note{ | |
max-width:600px; | |
font-size:0.8rem; | |
line-height:1.2rem; | |
} | |
.ternary-circle{ | |
stroke:#c00; | |
fill:#fff; | |
} | |
.ternary-line{ | |
fill:none; | |
stroke:#c00; | |
} | |
.ternary-tick{ | |
fill:none; | |
stroke:#000; | |
} | |
.major-ticks{ | |
font-size:12; | |
} | |
.minor{ | |
stroke:#eee; | |
} | |
.axis-label{ | |
font-size:20; | |
fill:#000; | |
font-weight:bold; | |
stroke:none; | |
} | |
.marker{ | |
cursor: move; | |
} | |
.marker-text{ | |
stroke:none; | |
font-size:12; | |
} | |
.marker-text-outline{ | |
stroke-width:5; | |
font-size:12; | |
stroke:#FFF; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="content"> | |
<h1>New Political Compass</h1> | |
<p>Hours poring over and analysing the manifestos and policy statements of | |
the major parties has led to the development of a new ternary political compass covering the full political landscape | |
and placing the choices available to today's voter in an understandable context.</p></div> | |
<hr> | |
<div id='viz'></div> | |
<div class="date">May 2015</div> | |
</body> | |
<script type="text/javascript"> | |
// Health Goth, Norm Core, Soft Grunge | |
var markerCircleRadius = 5; | |
var markers = [ | |
{ | |
name:'SNP' ,value:[3,3,8] | |
}, | |
{ | |
name:'Labour' ,value:[3,5,3] | |
}, | |
{ | |
name:'Tory' ,value:[3,8,2] | |
}, | |
{ | |
name:'UKIP' ,value:[2,8,2] | |
}, | |
{ | |
name:'Plaid' ,value:[4,3,8] | |
}, | |
{ | |
name:'Green' ,value:[7,3,5] | |
}, | |
{ | |
name:'Lib Dem' ,value:[2,7,6] | |
} | |
]; | |
var width = 600, height = 700; | |
var margin = {top:30, left:30, bottom:100, right:30}; | |
var chart = d3.select('#viz').append('svg') | |
.attr({ | |
width:width, | |
height:height, | |
viewBox:'0 0 '+width+' '+ height | |
}) | |
.append('g') | |
.attr('transform','translate('+margin.left+','+margin.top+')'); | |
var axes = chart.append('g').attr('id','axes'); | |
var plot = chart.append('g').attr('id','plot'); | |
var ternaryPlot = ternary.plot().range([0,width-(margin.left+margin.right)]); | |
var ternaryAxes = ternary.axes(ternaryPlot); | |
ternaryAxes | |
.ticks() | |
.minorTicks([d3.range(0,101,5)]) | |
.axisLabels(['Health Goth','Norm Core','Soft Grunge']); | |
var drag = d3.behavior.drag() | |
.origin(function(d) { | |
var coord = ternaryPlot.point(d.value); | |
return { | |
x:coord[0], | |
y:coord[1] | |
}; | |
}) | |
.on('drag',function(){ | |
d3.select(this) | |
.attr({ | |
'transform':'translate('+d3.event.x+','+d3.event.y+')' | |
}); | |
}); | |
var markerGroupEnter = plot.selectAll('.marker').data(markers) | |
.enter() | |
.append('g') | |
.attr({ | |
'class':'marker', | |
'transform':function(d){ | |
var coord = ternaryPlot.point(d.value); | |
return 'translate('+coord[0]+','+coord[1]+')'; | |
} | |
}).call(drag) | |
markerGroupEnter.append('circle') | |
.attr({ | |
r:markerCircleRadius | |
}); | |
markerGroupEnter.append('text') | |
.attr({ | |
'class':'marker-text-outline' | |
}).text(function(d){ return d.name; }); | |
markerGroupEnter.append('text') | |
.attr({ | |
'class':'marker-text' | |
}).text(function(d){ return d.name; }); | |
plot.selectAll('.marker text') | |
.attr({ | |
'transform':function(){ | |
return 'translate('+ (markerCircleRadius + 5) +','+3+')' | |
} | |
}) | |
axes.call(ternaryAxes.draw); | |
</script> | |
</html> |
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
//URGH old code | |
var ternary = { | |
plot:function(){ | |
var ternary = {}; | |
var height = Math.sqrt( 1*1 - (1/2)*(1/2)); | |
var path; | |
function rescale(range){ | |
if(!range.length) range = [0, 1]; | |
ternary.scale = d3.scale.linear().domain([0, 1]).range(range); | |
} | |
function line(interpolator){ | |
if(!interpolator) interpolator = 'linear' | |
path = d3.svg.line() | |
.x(function(d) { return d[0]; }) | |
.y(function(d) { return d[1]; }) | |
.interpolate(interpolator); | |
} | |
rescale([0, 400]); //the default sidelength for the triangle is 400px | |
line(); | |
ternary.range = function(range){ | |
rescale(range); | |
return ternary; | |
}; | |
//map teranry coordinate [a, b, c] to an [x, y] position | |
ternary.point = function(coords){ | |
var pos = [0,0]; | |
var sum = d3.sum(coords); | |
if(sum !== 0) { | |
var normalized = coords.map( function(d){ return d/sum; } ); | |
pos[0] = ternary.scale ( normalized[1] + normalized[2] / 2 ); | |
pos[1] = ternary.scale ( height * normalized[0] + height * normalized[1] ); | |
} | |
return pos; | |
}; | |
//create an SVG path from a set of points | |
ternary.line = function(coordsList, accessor, interpolator){ //path generator wrapper | |
if(interpolator) line(interpolator) | |
if(!accessor) accessor = function(d){ return d; } | |
var positions = coordsList.map( function(d){ | |
return ternary.point( accessor(d) ); | |
}); | |
return path(positions); | |
}; | |
ternary.rule = function(value, axis){ | |
var ends = []; | |
if(axis == 0){ | |
ends = [ | |
[value, 0, 100-value], | |
[value, 100-value, 0] | |
]; | |
}else if(axis == 1){ | |
ends = [ | |
[0, value, 100-value], | |
[100-value, value, 0] | |
]; | |
}else if(axis == 2){ | |
ends = [ | |
[0, 100-value, value], | |
[100-value, 0, value] | |
]; | |
} | |
return ternary.line(ends); | |
} | |
// this inverse of point i.e. take an x,y positon and get the ternary coordinate | |
ternary.getValues = function(pos){ //NOTE! haven't checked if this works yet | |
pos = pos.map(ternary.scale.inverse); | |
var c = 1 - pos[1]; | |
var b = pos[0] - c/2; | |
var a = y - b; | |
return [a, b, c]; | |
}; | |
return ternary; | |
}, | |
//// | |
axes:function(plot){ | |
var axes = {}; | |
var defaultTicks = d3.range(0,101,25); | |
var ticks = [defaultTicks, defaultTicks, defaultTicks]; | |
var minorTicks = [[],[],[]]; | |
var axisLabels = ['A','B','C']; | |
axes.draw = function(parent){ | |
var minor = parent.append('g').attr('class','minor-ticks'); | |
var major = parent.append('g').attr('class','major-ticks'); | |
var labels = parent.append('g').attr('class','axis-labels'); | |
for (i = 0; i<axisLabels.length; i++){ | |
labels.append('text') | |
.text(function(){ | |
return axisLabels[i]; | |
}).attr({ | |
'class':'axis-label', | |
'transform':function(){ | |
var coord; | |
var gap = 50; | |
if(i===0){ | |
coord = plot.point([0, 50, 50]); | |
rotation = 60*i; | |
}else if(i==1){ | |
coord = plot.point([50, 0, 50]); | |
var gap = -gap; | |
rotation = 60*i; | |
}else if(i==2){ | |
coord = plot.point([50, 50, 0]); | |
var gap = -gap; | |
rotation = 60*i + 180; | |
} | |
return('translate('+ coord[0] +','+coord[1]+') rotate('+rotation+') translate('+gap+',0)'); | |
}, | |
'text-anchor':function(){ | |
if(i==0) return 'start'; | |
return 'end'; | |
} | |
}) | |
} | |
//minor ticks | |
for (i = 0; i<minorTicks.length; i++){ | |
for (j = 0; j<minorTicks[i].length; j++){ | |
minor.append('path').attr({ | |
'class':'ternary-tick minor', | |
'd':plot.rule(minorTicks[i][j], i) | |
}); | |
} | |
} | |
//major ticks | |
for (var i=0; i<ticks.length; i++){ | |
for (var j=0; j<ticks[i].length; j++){ | |
major.append('path').attr({ | |
'class':'ternary-tick', | |
'd':plot.rule(ticks[i][j], i) | |
}); | |
major.append('text') | |
.text(ticks[i][j]) | |
.attr({ | |
'transform':function(){ | |
var coord; | |
var gap = 10; | |
if(i===0){ | |
coord = plot.point([0, 100 - ticks[i][j], ticks[i][j]]); | |
rotation = 60*i; | |
}else if(i==1){ | |
coord = plot.point([ticks[i][j], 0, 100 - ticks[i][j]]); | |
var gap = -gap; | |
rotation = 60*i; | |
}else if(i==2){ | |
coord = plot.point([100 - ticks[i][j], ticks[i][j], 0]); | |
var gap = -gap; | |
rotation = 60*i + 180; | |
} | |
return('translate('+ coord[0] +','+coord[1]+') rotate('+rotation+') translate('+gap+',0)'); | |
}, | |
'text-anchor':function(){ | |
if(i==0) return 'start'; | |
return 'end'; | |
} | |
}); | |
} | |
} | |
}; | |
axes.ticks = function(tickArrays){ // an array containing 1 - 3 three arrays the first array will be copied over empty spaces at the end | |
if(!tickArrays) tickArrays = [defaultTicks,defaultTicks,defaultTicks]; | |
if(!tickArrays[1]) tickArrays[1] = tickArrays[0]; | |
if(!tickArrays[2]) tickArrays[2] = tickArrays[0]; | |
ticks = tickArrays; | |
return axes; | |
}; | |
axes.minorTicks = function(tickArrays){ | |
if(!tickArrays) tickArrays = [[],[],[]]; | |
if(!tickArrays[1]) tickArrays[1] = tickArrays[0]; | |
if(!tickArrays[2]) tickArrays[2] = tickArrays[0]; | |
minorTicks = tickArrays; | |
return axes; | |
}; | |
axes.axisLabels = function(l){ | |
axisLabels = l; | |
} | |
return axes; | |
} | |
}; | |
//// |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment