A ternary plot forked from this one by Marielle Lange
forked from tomgp's block: Ternary plot
forked from anonymous's block: Ternary plot
license: mit |
A ternary plot forked from this one by Marielle Lange
forked from tomgp's block: Ternary plot
forked from anonymous's block: Ternary plot
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Data Viz as 3 Spectrums</title> | |
<style> | |
a{ | |
font-family: sans-serif; | |
color: #DB7365; | |
padding: 0.3rem; | |
} | |
a:hover{ | |
background-color: #DB7365; | |
color: #fff1e0; | |
} | |
body{ | |
background: #fff; | |
} | |
line.tick { | |
stroke-width: 0.5; | |
stroke-opacity: 0.5 | |
} | |
line.minor-tick { | |
stroke-width: 1; | |
stroke-opacity:0; | |
} | |
.a-axis, | |
.b-axis, | |
.c-axis { | |
stroke: #484848; | |
} | |
.axis-title{ | |
font-family: sans-serif; | |
font-size: 1.5rem; | |
} | |
text.tick-text { | |
font-family: sans-serif; | |
font-weight: 200; | |
font-size: 1rem; | |
fill: #484848; | |
stroke:none; | |
} | |
circle { | |
fill:#00A699; | |
fill-opacity: 0.5; | |
stroke: none; | |
} | |
</style> | |
</head> | |
<a id="nextbutton" href="#">Data change</a> | |
<div id="plot"> | |
</div> | |
<script charset="UTF-8" src="http://cdnjs.cloudflare.com/ajax/libs/d3/3.1.6/d3.min.js"></script> | |
<script> | |
function ternaryPlot(selector, userOpt ) { | |
var plot = { | |
dataset:[] | |
}; | |
var opt = { | |
width:900, | |
height:900, | |
side: 700, | |
margin: {top: 50, left: 50, bottom: 50, right: 50}, | |
axis_labels:['A','B','C'], | |
axis_ticks:[0, 2, 4, 6, 8, 10], | |
tickLabelMargin: 10, | |
axisLabelMargin: 40 } | |
for(var o in userOpt){ | |
opt[o] = userOpt[o]; | |
} | |
var svg = d3.select(selector).append('svg') | |
.attr("width", opt.width) | |
.attr("height", opt.height); | |
var axes = svg.append('g').attr('class','axes'); | |
var w = opt.side; | |
var h = Math.sqrt( opt.side*opt.side - (opt.side/2)*(opt.side/2)); | |
var corners = [ | |
[opt.margin.left, h + opt.margin.top], // a | |
[ w + opt.margin.left, h + opt.margin.top], //b | |
[(w/2) + opt.margin.left, opt.margin.top], //c | |
] | |
var labels = [ | |
[opt.margin.left + 20, (h/2) + opt.margin.top - 10, -60], // data | |
[(w/2) + opt.margin.left + 5, h + opt.margin.top + 50, 0], // eng | |
[opt.margin.left + w -20, (h/2) + opt.margin.top -10, +60], // designer | |
] | |
//axis names | |
axes.selectAll('.axis-title') | |
.data(opt.axis_labels) | |
.enter() | |
.append('g') | |
.attr('class', 'axis-title') | |
.append('text') | |
.text(function(d){ return d; }) | |
.attr('transform',function(d,i){ | |
return 'translate('+labels[i][0]+','+labels[i][1]+')rotate('+labels[i][2]+')'; | |
//return 'translate('+corners[i][0]+','+corners[i][1]+')'; | |
}) | |
.attr('text-anchor', 'middle') | |
// .attr('transform', function(d,i){ | |
// var theta = 0; | |
// if(i===0) theta = 120; | |
// if(i===1) theta = 60; | |
// if(i===2) theta = -90; | |
// var x = opt.axisLabelMargin * Math.cos(theta * 0.0174532925), | |
// y = opt.axisLabelMargin * Math.sin(theta * 0.0174532925); | |
// return 'translate('+x+','+y+')' | |
// }); | |
//ticks | |
//(TODO: this seems a bit verbose/ repetitive!); | |
var n = opt.axis_ticks.length; | |
var tickLabelLookup = { | |
0: 'low', | |
10: 'high', | |
}; | |
opt.axis_ticks.forEach(function(v) { | |
var coord1 = coord( [v, 0, 10-v] ); | |
var coord2 = coord( [v, 10-v, 0] ); | |
var coord3 = coord( [0, 10-v, v] ); | |
var coord4 = coord( [10-v, 0, v] ); | |
axes.append("line") | |
.attr( lineAttributes(coord1, coord2) ) | |
.classed('a-axis tick', true); | |
axes.append("line") | |
.attr( lineAttributes(coord2, coord3) ) | |
.classed('b-axis tick', true); | |
axes.append("line") | |
.attr( lineAttributes(coord3, coord4) ) | |
.classed('c-axis tick', true); | |
//tick labels | |
axes.append('g') | |
.attr('transform',function(d){ | |
return 'translate(' + coord1[0] + ',' + coord1[1] + ')' | |
}) | |
.append("text") | |
.attr('transform','rotate(0)translate(-10,0)') | |
.attr('text-anchor','middle') | |
.attr('x',-opt.tickLabelMargin) | |
.text( function (d) { return tickLabelLookup[v]; } ) | |
.classed('a-axis tick-text', true ); | |
axes.append('g') | |
.attr('transform',function(d){ | |
return 'translate(' + coord2[0] + ',' + coord2[1] + ')' | |
}) | |
.append("text") | |
.attr('transform','rotate(0)translate(10,20)') | |
.attr('text-anchor','middle') | |
.attr('x',-opt.tickLabelMargin) | |
.text( function (d) { return tickLabelLookup[10 - v]; } ) | |
.classed('b-axis tick-text', true); | |
axes.append('g') | |
.attr('transform',function(d){ | |
return 'translate(' + coord3[0] + ',' + coord3[1] + ')' | |
}) | |
.append("text") | |
.attr('transform','rotate(0)translate(5,0)') | |
.text( function (d) { return tickLabelLookup[v]; } ) | |
.attr('x',opt.tickLabelMargin) | |
.classed('c-axis tick-text', true); | |
}) | |
function lineAttributes(p1, p2){ | |
return { x1:p1[0], y1:p1[1], | |
x2:p2[0], y2:p2[1] }; | |
} | |
function coord(arr){ | |
var a = arr[0], b=arr[1], c=arr[2]; | |
var sum, pos = [0,0]; | |
sum = a + b + c; | |
if(sum !== 0) { | |
a /= sum; | |
b /= sum; | |
c /= sum; | |
pos[0] = corners[0][0] * a + corners[1][0] * b + corners[2][0] * c; | |
pos[1] = corners[0][1] * a + corners[1][1] * b + corners[2][1] * c; | |
} | |
return pos; | |
} | |
//bind by is the dataset property used as an id for the join | |
plot.data = function(data, accessor, bindBy){ | |
plot.dataset = data; | |
var circles = svg.selectAll("circle") | |
.data( data.map( function(d){ return coord(accessor(d)); }), function(d,i){ | |
if(bindBy){ | |
return plot.dataset[i][bindBy]; | |
} | |
return i; | |
} ); | |
circles.enter().append("circle"); | |
circles.transition().attr("cx", function (d) { return d[0]; }) | |
.attr("cy", function (d) { return d[1]; }) | |
.attr("r", 6); | |
return this; | |
} | |
plot.getPosition = coord; | |
plot.getTripple = function(x, y){ | |
//TODO, get percentages for a give x, y | |
} | |
return plot; | |
} | |
//ACTIVATE! | |
var plot_opts = { | |
side: 300, | |
margin: { top: 70, left: 150, bottom: 150, right: 150 }, | |
axis_labels:['Data Analysis','Engineer','Designer'], | |
axis_ticks: d3.range(0, 11, 2), | |
} | |
var tp = ternaryPlot( '#plot', plot_opts ); | |
function next(){ | |
var d = [] | |
for (var i = 0; i < 100; i++) { | |
d.push({ | |
data: Math.random(), | |
engineer: Math.random()/2, | |
designer: Math.random()*5, | |
label:'point' + i | |
}) | |
} | |
tp.data(d, function(d){ return [d.data, d.engineer, d.designer]}, 'label'); | |
} | |
next(); | |
d3.select('#nextbutton').on('click', function(e){ | |
next(); d3.event.preventDefault(); }); | |
</script> | |
</body> | |
</html> |