Simplifying calculations, adding inverse of plot, working sensibly with D3 scales etc.
data reverse engineered from here... http://thegrowingseason.wordpress.com/2013/05/10/soils-management-the-old-jar-test/
notional2005result.csv | |
notional2005result.json |
Simplifying calculations, adding inverse of plot, working sensibly with D3 scales etc.
data reverse engineered from here... http://thegrowingseason.wordpress.com/2013/05/10/soils-management-the-old-jar-test/
{ | |
"sand":[ | |
{ | |
"clay":0, | |
"sand":100, | |
"silt":0 | |
}, | |
{ | |
"clay":10, | |
"sand":90, | |
"silt":0 | |
}, | |
{ | |
"clay":0, | |
"sand":90, | |
"silt":10 | |
} | |
], | |
"loamy sand":[ | |
{ | |
"clay":0, | |
"sand":90, | |
"silt":10 | |
}, | |
{ | |
"clay":10, | |
"sand":90, | |
"silt":0 | |
}, | |
{ | |
"clay":15, | |
"sand":85, | |
"silt":0 | |
}, | |
{ | |
"clay":0, | |
"sand":70, | |
"silt":30 | |
} | |
], | |
"sandy loam":[ | |
{ | |
"clay":0, | |
"sand":70, | |
"silt":30 | |
}, | |
{ | |
"clay":15, | |
"sand":85, | |
"silt":0 | |
}, | |
{ | |
"clay":20, | |
"sand":80, | |
"silt":0 | |
}, | |
{ | |
"clay":20, | |
"sand":53, | |
"silt":32 | |
}, | |
{ | |
"clay":5, | |
"sand":53, | |
"silt":42 | |
}, | |
{ | |
"clay":5, | |
"sand":45, | |
"silt":50 | |
}, | |
{ | |
"clay":0, | |
"sand":50, | |
"silt":50 | |
} | |
], | |
"sandy clay loam":[ | |
{ | |
"clay":20, | |
"sand":80, | |
"silt":0 | |
}, | |
{ | |
"clay":35, | |
"sand":65, | |
"silt":0 | |
}, | |
{ | |
"clay":35, | |
"sand":45, | |
"silt":20 | |
}, | |
{ | |
"clay":28, | |
"sand":45, | |
"silt":27 | |
}, | |
{ | |
"clay":20, | |
"sand":53, | |
"silt":32 | |
} | |
], | |
"sandy clay":[ | |
{ | |
"clay":35, | |
"sand":65, | |
"silt":0 | |
}, | |
{ | |
"clay":35, | |
"sand":45, | |
"silt":20 | |
}, | |
{ | |
"clay":55, | |
"sand":45, | |
"silt":0 | |
}], | |
"clay":[ | |
{ | |
"clay":55, | |
"sand":45, | |
"silt":0 | |
}, | |
{ | |
"clay":100, | |
"sand":0, | |
"silt":0 | |
}, | |
{ | |
"clay":60, | |
"sand":0, | |
"silt":40 | |
}, | |
{ | |
"clay":40, | |
"sand":20, | |
"silt":40 | |
}, | |
{ | |
"clay":40, | |
"sand":45, | |
"silt":15 | |
} | |
], | |
"clay loam":[ | |
{ | |
"clay":40, | |
"sand":45, | |
"silt":15 | |
}, | |
{ | |
"clay":40, | |
"sand":20, | |
"silt":40 | |
}, | |
{ | |
"clay":28, | |
"sand":20, | |
"silt":52 | |
}, | |
{ | |
"clay":28, | |
"sand":45, | |
"silt":27 | |
} | |
], | |
"silty clay":[ | |
{ | |
"clay":60, | |
"sand":0, | |
"silt":40 | |
}, | |
{ | |
"clay":40, | |
"sand":0, | |
"silt":60 | |
}, | |
{ | |
"clay":40, | |
"sand":20, | |
"silt":40 | |
} | |
], | |
"silty clay loam":[ | |
{ | |
"clay":28, | |
"sand":0, | |
"silt":72 | |
}, | |
{ | |
"clay":28, | |
"sand":20, | |
"silt":52 | |
}, | |
{ | |
"clay":40, | |
"sand":20, | |
"silt":40 | |
}, | |
{ | |
"clay":40, | |
"sand":0, | |
"silt":60 | |
} | |
], | |
"silty loam":[ | |
{ | |
"clay":0, | |
"sand":50, | |
"silt":50 | |
}, | |
{ | |
"clay":28, | |
"sand":22, | |
"silt":50 | |
}, | |
{ | |
"clay":28, | |
"sand":0, | |
"silt":72 | |
}, | |
{ | |
"clay":12, | |
"sand":0, | |
"silt":88 | |
}, | |
{ | |
"clay":12, | |
"sand":8, | |
"silt":80 | |
}, | |
{ | |
"clay":0, | |
"sand":20, | |
"silt":80 | |
} | |
], | |
"silt":[ | |
{ | |
"clay":0, | |
"sand":0, | |
"silt":100 | |
}, | |
{ | |
"clay":0, | |
"sand":20, | |
"silt":80 | |
}, | |
{ | |
"clay":12, | |
"sand":8, | |
"silt":80 | |
}, | |
{ | |
"clay":12, | |
"sand":0, | |
"silt":88 | |
} | |
], | |
"loam":[ | |
{ | |
"clay":28, | |
"sand":45, | |
"silt":27 | |
}, | |
{ | |
"clay":28, | |
"sand":22, | |
"silt":50 | |
}, | |
{ | |
"clay":5, | |
"sand":45, | |
"silt":50 | |
}, | |
{ | |
"clay":5, | |
"sand":53, | |
"silt":42 | |
}, | |
{ | |
"clay":20, | |
"sand":53, | |
"silt":32 | |
} | |
] | |
} |
<html> | |
<head> | |
<title>ternary 2</title> | |
<script charset="UTF-8" src="http://cdnjs.cloudflare.com/ajax/libs/d3/3.1.6/d3.min.js"></script> | |
<style type="text/css"> | |
.ternary-circle{ | |
stroke:#c00; | |
fill:#fff; | |
} | |
.ternary-line{ | |
fill:none; | |
stroke:#c00; | |
} | |
.ternary-tick{ | |
fill:none; | |
stroke:#aaa; | |
} | |
.minor{ | |
stroke:#fefefe; | |
} | |
</style> | |
</head> | |
<body> | |
</body> | |
<script type="text/javascript"> | |
var test_data = [ | |
{a:100,b:0,c:0,color:'#F00'}, | |
{a:0,b:100,c:0,color:'#0F0'}, | |
{a:0,b:0,c:100,color:'#00F'}, | |
{a:33,b:33,c:33,color:'#999'} | |
]; | |
function ternaryPlot(){ | |
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]); | |
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){ | |
console.log(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; | |
} | |
//// | |
function ternaryAxes(plot){ | |
var axes = {}; | |
var parent = d3.select('svg'); | |
var defaultTicks = d3.range(0,101,25); | |
var ticks = [defaultTicks, defaultTicks, defaultTicks]; | |
var minorTicks = [[],[],[]]; | |
axes.draw = function(parentSelector){ | |
if(parentSelector) parent = d3.select(parentSelector); | |
var minor = parent.append('g').attr('id','minor-ticks'); | |
var major = parent.append('g').attr('id','major-ticks') | |
//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) | |
}) | |
} | |
} | |
} | |
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; | |
} | |
return axes; | |
} | |
//// | |
var svg = d3.select('body').append('svg').attr({ width:500, height:500 }); | |
var axes = svg.append('g').attr('id','axes'); | |
var plot = svg.append('g').attr('id','plot'); | |
var myTernary = ternaryPlot().range([0,400]); | |
/*plot.append('path').attr( | |
{ | |
d:function(){ | |
return myTernary.line(test_data, function(d){ return [d.a, d.b, d.c]; }, 'basis') | |
}, | |
'class':'ternary-line' | |
}); | |
plot.selectAll('circle') | |
.data(test_data) | |
.enter() | |
.append('circle') | |
.attr({ | |
class:'ternary-circle', | |
r:4, | |
transform:function(d){ | |
var point = myTernary.point([d.a, d.b, d.c]); | |
return ("translate(" + point[0] + ", " + point[1] + ")"); | |
}, | |
fill:function(d){ return d.color } | |
}); | |
*/ | |
var myAxes = ternaryAxes(myTernary); | |
myAxes.ticks().minorTicks([d3.range(0,101,5)]).draw('#axes'); | |
d3.json('data.json', gotData); | |
function gotData(d){ | |
for (var type in d){ | |
var f = function (){ | |
return type | |
} | |
plot.append('path').attr( | |
{ | |
d:function(){ | |
return myTernary.line(d[type], function(d){ return [d.sand, d.silt, d.clay]; }) + "Z"; | |
}, | |
'class':'ternary-line', | |
'id':type.replace(' ','-') | |
}) | |
.on('click', function(d){ | |
console.log(this.id); | |
}); | |
} | |
} | |
</script> | |
</html> |