Skip to content

Instantly share code, notes, and snippets.

@tomgp
Forked from widged/index.html
Last active December 29, 2015 12:59
Show Gist options
  • Save tomgp/7674234 to your computer and use it in GitHub Desktop.
Save tomgp/7674234 to your computer and use it in GitHub Desktop.
Ternary plot
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3 Ternary Plot</title>
<style>
a{
font-family: sans-serif;
color: #DB7365;
padding: 0.3rem;
}
a:hover{
background-color: #DB7365;
color: #fff1e0;
}
body{
background: #fff1e0;
}
line.tick {
stroke-width: 0.5;
}
line.minor-tick {
stroke-width: 1;
stroke-opacity:0.1;
}
.a-axis{
stroke: #333;
}
.b-axis{
stroke: #333;
}
.c-axis{
stroke: #333;
}
.axis-title{
font-family: sans-serif;
font-size: 1.5rem;
}
text.tick-text {
font-family: sans-serif;
font-weight: lighter;
font-size: 1rem;
fill: #333;
stroke:none;
}
circle {
fill: #fff1e0;
stroke: #DB7365;
stroke-width: 2px;
}
</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,20,40,60,80,100],
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
//axis names
axes.selectAll('.axis-title')
.data(opt.axis_labels)
.enter()
.append('g')
.attr('class', 'axis-title')
.attr('transform',function(d,i){
return 'translate('+corners[i][0]+','+corners[i][1]+')';
})
.append('text')
.text(function(d){ return d; })
.attr('text-anchor', function(d,i){
if(i===0) return 'end';
if(i===2) return 'middle';
return 'start';
})
.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;
if(opt.minor_axis_ticks){
opt.minor_axis_ticks.forEach(function(v) {
var coord1 = coord( [v, 0, 100-v] );
var coord2 = coord( [v, 100-v, 0] );
var coord3 = coord( [0, 100-v, v] );
var coord4 = coord( [100-v, 0, v] );
axes.append("line")
.attr( lineAttributes(coord1, coord2) )
.classed('a-axis minor-tick', true);
axes.append("line")
.attr( lineAttributes(coord2, coord3) )
.classed('b-axis minor-tick', true);
axes.append("line")
.attr( lineAttributes(coord3, coord4) )
.classed('c-axis minor-tick', true);
});
}
opt.axis_ticks.forEach(function(v) {
var coord1 = coord( [v, 0, 100-v] );
var coord2 = coord( [v, 100-v, 0] );
var coord3 = coord( [0, 100-v, v] );
var coord4 = coord( [100-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(60)')
.attr('text-anchor','end')
.attr('x',-opt.tickLabelMargin)
.text( function (d) { return 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(-60)')
.attr('text-anchor','end')
.attr('x',-opt.tickLabelMargin)
.text( function (d) { return (100- v); } )
.classed('b-axis tick-text', true);
axes.append('g')
.attr('transform',function(d){
return 'translate(' + coord3[0] + ',' + coord3[1] + ')'
})
.append("text")
.text( function (d) { return 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;
}
function scale(p, factor) {
return [p[0] * factor, p[1] * factor];
}
plot.data = function(data, accessor, bindBy){ //bind by is the dataset property used as an id for the join
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: 400,
margin: {top:70,left:150,bottom:150,right:150},
axis_labels:['Journalist','Developer','Designer'],
axis_ticks:d3.range(0, 101, 20),
minor_axis_ticks:d3.range(0, 101, 5)
}
var tp = ternaryPlot( '#plot', plot_opts );
/*.data([
{journalist:75,developer:25,designer:0,label:'point 1'},
{journalist:70,developer:10,designer:20,label:'point 2'},
{journalist:75,developer:20,designer:5,label:'point 3'},
{journalist:5,developer:60,designer:35,label:'point 4'},
{journalist:10,developer:80,designer:10,label:'point 5'},
{journalist:10,developer:90,designer:0,label:'point 6'},
{journalist:20,developer:70,designer:10,label:'point 7'},
{journalist:10,developer:20,designer:70,label:'point 8'},
{journalist:15,developer:5,designer:80,label:'point 9'},
{journalist:10,developer:10,designer:80,label:'point 10'},
{journalist:20,developer:10,designer:70,label:'point 11'},
], function(d){ return [d.journalist, d.developer, d.designer]}, 'label');*/
function next(){
var d = []
for(var i = 0; i < 100; i++){
d.push({
journalist:Math.random(),
developer:Math.random(),
designer:Math.random(),
label:'point'+i
})
}
tp.data(d, function(d){ return [d.journalist, d.developer, d.designer]}, 'label');
}
next();
d3.select('#nextbutton').on('click', function(e){
next(); d3.event.preventDefault(); });
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment