Need to convert to Canvas
forked from widged's block: Rapid implementation of a ternary plot with d3js
license: mit |
Need to convert to Canvas
forked from widged's block: Rapid implementation of a ternary plot with d3js
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>D3 Ternary Plot</title> | |
<style> | |
.border { | |
fill: none; | |
stroke-width: 2; | |
stroke: #363636; | |
} | |
line.axis { | |
stroke-width: 2; | |
stroke: #363636; | |
} | |
line.tick { | |
stroke-width: 1; | |
stroke: #c3c3c3; | |
} | |
text.tick-text { | |
font-family: "sans-serif"; | |
font-size: 10px; | |
fill: #000; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="plot"> | |
</div> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script> | |
(function() { | |
console.clear() | |
var width = 600; | |
var height = triangleHeight(width) | |
var margin = 20; | |
var density = 50 | |
var rotateHSL = 0 | |
var scaleMax = 100 | |
var f = scaleMax / 20 | |
var svg = d3.select('#plot').append('svg') | |
.attr("width", 800) | |
.attr("height", 800); | |
var background = svg.append("g") | |
.attr("id", "chart-background") | |
var axes = svg.append("g") | |
.attr("id", "chart-axes") | |
var maxDistanceToCentre = Math.ceil(2 * (height/3)) | |
var centre = { | |
"x": (width/2) + margin, | |
"y": margin + maxDistanceToCentre | |
} | |
var corners = { | |
"left": {}, | |
"top": {}, | |
"right": {} | |
} | |
corners.left.x = margin | |
corners.left.y = height + margin | |
corners.top.x = (width/2) + margin | |
corners.top.y = margin | |
corners.right.x = width + margin | |
corners.right.y = height + margin | |
svg.append('defs') | |
.append('clipPath') | |
.attr('id', "clip") | |
.append('path') | |
.attr("class", "border") | |
.attr('d', "M " + corners.left.x + " " + corners.left.y + ", " | |
+ "L " + corners.top.x + " " + corners.top.y + ", " | |
+ "L " + corners.right.x + " " + corners.right.y + ", " | |
+ "z" | |
) | |
var data = [] | |
for (var i = 0; i <= density; i++ ) { | |
let density2 = density - i | |
for (var j = 0; j <= density2; j++ ) { | |
let k = density2 - j | |
data.push(coord((i * f), (j * f), (k * f))) | |
} | |
} | |
var ticks = [0,20,40,60,80,100] | |
var n = ticks.length; | |
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); | |
if(v !== 0 && v !== 100) { | |
axes.append("line") | |
.attr("x1", coord1.x) | |
.attr("y1", coord1.y) | |
.attr("x2", coord2.x) | |
.attr("y2", coord2.y) | |
.classed('tick', true); | |
axes.append("line") | |
.attr("x1", coord2.x) | |
.attr("y1", coord2.y) | |
.attr("x2", coord3.x) | |
.attr("y2", coord3.y) | |
.classed('tick', true); | |
axes.append("line") | |
.attr("x1", coord3.x) | |
.attr("y1", coord3.y) | |
.attr("x2", coord4.x) | |
.attr("y2", coord4.y) | |
.classed('tick', true); | |
} | |
axes.append("text") | |
.attr("x", coord1.x - 15) | |
.attr("y", coord1.y ) | |
.text( function (d) { return v; }) | |
.classed('tick-text tick-a', true); | |
axes.append("text") | |
.attr("x", coord2.x - 6) | |
.attr("y", coord2.y + 10 ) | |
.text( function (d) { return (100- v); }) | |
.classed('tick-text tick-b', true); | |
axes.append("text") | |
.attr("x", coord3.x + 6) | |
.attr("y", coord3.y ) | |
.text( function (d) { return v; }) | |
.classed('tick-text tick-c', true); | |
}) | |
var circles = background.selectAll("circle") | |
.data(data); | |
circles.enter() | |
.append("circle") | |
.attr("cx", function (d) { return d.x; }) | |
.attr("cy", function (d) { return d.y; }) | |
.style("fill", function(d){ return color(d) }) | |
.attr("clip-path", "url(#clip)") | |
.attr("r", ((width / density))) | |
.attr("title", function(d){ | |
return "" + d.h + ", " + d.l + ", " + d.s | |
}); | |
function color(d) { | |
let x = Math.abs(d.x - centre.x) | |
let y = Math.abs(d.y - centre.y) | |
if (d.y < centre.y && d.x > centre.x) { | |
d.angle = angleTan(x,y) * (180 / Math.PI) | |
} | |
if (d.y <= centre.y && d.x <= centre.x) { | |
d.angle = 360 - (angleTan(x,y) * (180 / Math.PI)) | |
} | |
if (d.y > centre.y && d.x < centre.x) { | |
d.angle = 180 + (angleTan(x,y) * (180 / Math.PI)) | |
} | |
if (d.y >= centre.y && d.x >= centre.x) { | |
d.angle = 180 - (angleTan(x,y) * (180 / Math.PI)) | |
} | |
if (d.angle <= 60 || d.angle >= 300 ) { | |
x = Math.abs(d.x - corners.top.x) | |
y = Math.abs(d.y - corners.top.y) | |
d.distance = distanceRatio(x, y) | |
} else if (d.angle >= 60 && d.angle <= 180 ) { | |
x = Math.abs(d.x - corners.right.x) | |
y = Math.abs(d.y - corners.right.y) | |
d.distance = distanceRatio(x, y) | |
} else if (d.angle >= 180 && d.angle <= 300 ) { | |
x = Math.abs(d.x - corners.left.x) | |
y = Math.abs(d.y - corners.left.y) | |
d.distance = distanceRatio(x, y) | |
} | |
d.hAngle = Math.floor(d.angle + rotateHSL) | |
d.sat = 0.6 - (d.distance / 2) | |
d.lum = 0.2 + (d.distance * 0.8) | |
let hslColor = d3.hsl(d.hAngle, d.sat, d.lum) | |
return hslColor | |
} | |
function angleTan(opposite, adjacent) { | |
return Math.atan(opposite/adjacent); | |
} | |
function triangleHypotenuse(sideA, sideB) { | |
return Math.sqrt(Math.pow(sideA, 2) + Math.pow(sideB, 2)) | |
}; | |
function distanceRatio(x, y){ | |
return triangleHypotenuse(x, y) / maxDistanceToCentre | |
} | |
function coord(a, b, c){ | |
var sum | |
var pos = {}; | |
pos.a = a | |
pos.b = b | |
pos.c = c | |
sum = a + b + c; | |
if(sum !== 0) { | |
a /= sum; | |
b /= sum; | |
c /= sum; | |
pos.x = corners.left.x * a + corners.right.x * b + corners.top.x * c; | |
pos.y = corners.left.y * a + corners.right.y * b + corners.top.y * c; | |
} | |
return pos; | |
} | |
function scale(/* point */ p, factor) { | |
return [p[0] * factor, p[1] * factor]; | |
} | |
function triangleHeight(width){ | |
return Math.sqrt((width * width) - (width/2 * width/2)); | |
} | |
})() | |
</script> | |
</body> | |
</html> |