Skip to content

Instantly share code, notes, and snippets.

@tomgp
Last active August 29, 2015 14:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomgp/1d83cc4b095d2015408d to your computer and use it in GitHub Desktop.
Save tomgp/1d83cc4b095d2015408d to your computer and use it in GitHub Desktop.
New Political Compass
<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>
//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