Created
February 25, 2024 09:59
-
-
Save richardjkendall/93a7b83a6f785c9afb983ba9c61a67ed to your computer and use it in GitHub Desktop.
Draw ellipse and triangle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<h1>Time/Cost/Complexity Relationship</h1> | |
<label for="rotation">Rotation</label><input type="range" min="0", max="360", step="1", value="1" id="rotation"/> | |
<div id="container"></div> | |
<table border="1"> | |
<tr> | |
<td></td> | |
<td></td> | |
<td>Time</td> | |
<td>Cost</td> | |
<td>Complexity</td> | |
</tr> | |
<tr> | |
<td>Time</td> | |
<td><p id="time"></p></td> | |
<td><p id="timevtime"></p></td> | |
<td><p id="timevcost"></p></td> | |
<td><p id="timevcomplexity"></p></td> | |
</tr> | |
<tr> | |
<td>Cost</td> | |
<td><p id="cost"></p></td> | |
<td><p id="costvtime"></p></td> | |
<td><p id="costvcost"></p></td> | |
<td><p id="costvcomplexity"></p></td> | |
</tr> | |
<tr> | |
<td>Complexity</td> | |
<td><p id="complexity"></p></td> | |
<td><p id="complexityvtime"></p></td> | |
<td><p id="complexityvcost"></p></td> | |
<td><p id="complexityvcomplexity"></p></td> | |
</tr> | |
</table> | |
<script type="module"> | |
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm"; | |
const DegToRad = (angle) => { | |
return angle * (Math.PI / 180); | |
} | |
const PointOnCircle = (radius, theta, centre = {x: 0, y: 0}) => { | |
theta = DegToRad(theta); | |
return [ | |
(radius * Math.sin(theta)) + centre.x, | |
(radius * Math.cos(theta)) + centre.y | |
] | |
} | |
const PointOnEllipse = (radA, radB, theta, centre = {x: 0, y: 0}) => { | |
if(theta <= 90 || theta > 270) { | |
theta = DegToRad(theta); | |
return [ | |
((radA * radB) / Math.sqrt(radB*radB + radA*radA * Math.tan(theta)*Math.tan(theta))) + centre.x, | |
((radA * radB * Math.tan(theta)) / Math.sqrt(radB*radB + radA*radA * Math.tan(theta)*Math.tan(theta))) + centre.y, | |
]; | |
} else { | |
theta = DegToRad(theta); | |
return [ | |
-((radA * radB) / Math.sqrt(radB*radB + radA*radA * Math.tan(theta)*Math.tan(theta))) + centre.x, | |
-((radA * radB * Math.tan(theta)) / Math.sqrt(radB*radB + radA*radA * Math.tan(theta)*Math.tan(theta))) + centre.y, | |
]; | |
} | |
} | |
const FixDegreesToPole = (deg) => { | |
deg = 270 + deg; | |
if(deg > 360) { | |
deg = deg - 360; | |
} | |
return deg; | |
} | |
// Declare the dimensions. | |
const width = 650; | |
const height = 220; | |
const ellipseVerticalRadius = 80; | |
const ellipseHorizontalRadius = 300; | |
const ellipseOriginX = 305; | |
const ellipseOriginY = 100; | |
const trianglePoints = [0, 120, 240]; | |
const triangleColours = ["red", "blue", "green"] | |
// Create the SVG container. | |
const svg = d3.create("svg") | |
.attr("width", width) | |
.attr("height", height); | |
// create ellipse | |
svg.append("ellipse") | |
.attr("cx", ellipseOriginX) | |
.attr("cy", ellipseOriginY) | |
.attr("rx", ellipseHorizontalRadius) | |
.attr("ry", ellipseVerticalRadius) | |
.attr("stroke", "lightgrey") | |
.attr("stroke-width", 1) | |
.attr("fill", "none"); | |
// line helper | |
const line = d3.line() | |
.x(d => d.x) | |
.y(d => d.y); | |
// create point path | |
let path = []; | |
for(let i = 0; i <= 360; i++) { | |
let point = PointOnEllipse(ellipseHorizontalRadius, ellipseVerticalRadius, i, {x: ellipseOriginX, y: ellipseOriginY}); | |
path.push({ | |
x: point[0], | |
y: point[1] | |
}); | |
} | |
//console.log("path", path); | |
const p = svg.append("path") | |
.attr("d", line(path)) | |
.attr("stroke", "none") | |
.attr("stroke-width", 1) | |
.attr("fill", "none"); | |
const triangleLines = trianglePoints.map((deg, i) => { | |
deg = FixDegreesToPole(deg); | |
const next = (i + 1) % trianglePoints.length; | |
const nextDeg = FixDegreesToPole(trianglePoints[next]); | |
return svg.append("line") | |
.style("stroke", triangleColours[i]) | |
.style("stroke-width", 2) | |
.attr("x1", path[deg].x) | |
.attr("y1", path[deg].y) | |
.attr("x2", path[nextDeg].x) | |
.attr("y2", path[nextDeg].y); | |
}); | |
const triangleCircles = trianglePoints.map((deg, i) => { | |
deg = FixDegreesToPole(deg); | |
return svg.append("circle") | |
.attr("fill", triangleColours[i]) | |
.attr("r", 0) | |
.attr("transform", `translate(${path[deg].x}, ${path[deg].y})`); | |
}); | |
/*const translateAlong = (path) => { | |
const length = path.getTotalLength(); | |
return function() { | |
return function(t) { | |
console.log("in translateAlong", t, t*length); | |
const {x, y} = path.getPointAtLength(t * length); | |
return `translate(${x}, ${y})`; | |
} | |
} | |
} | |
function moveCircles() { | |
circle.transition() | |
.ease(d3.easeLinear) | |
.duration(10000) | |
.attrTween("transform", translateAlong(p.node())) | |
} | |
moveCircles();*/ | |
const translateToPointOnPath = (pathin, deg) => { | |
const {x, y} = path[deg]; | |
return `translate(${x}, ${y})`; | |
} | |
d3.select("input[type=range]#rotation").on("input", (ele) => { | |
let time = 0; | |
let cost = 0; | |
let complexity = 0; | |
triangleCircles.forEach((circle, i) => { | |
let deg = trianglePoints[i]; | |
deg = deg + parseInt(ele.target.value); | |
if(deg > 360) { | |
deg = deg - 360; | |
} | |
deg = FixDegreesToPole(deg); | |
// move circles | |
circle.transition() | |
.ease(d3.easeLinear) | |
.duration(0) | |
.attr("transform", translateToPointOnPath(p.node(), deg)); | |
const next = (i + 1) % trianglePoints.length; | |
let nextDeg = trianglePoints[next]; | |
nextDeg = nextDeg + parseInt(ele.target.value); | |
if(nextDeg > 360) { | |
nextDeg = nextDeg - 360; | |
} | |
nextDeg = FixDegreesToPole(nextDeg); | |
// move lines | |
triangleLines[i].transition() | |
.ease(d3.easeLinear) | |
.duration(0) | |
.attr("x1", path[deg].x) | |
.attr("y1", path[deg].y) | |
.attr("x2", path[nextDeg].x) | |
.attr("y2", path[nextDeg].y); | |
// calculate line lengths | |
const b = Math.max(path[deg].x, path[nextDeg].x) - Math.min(path[deg].x, path[nextDeg].x); | |
const c = Math.max(path[deg].y, path[nextDeg].y) - Math.min(path[deg].y, path[nextDeg].y); | |
const lineLength = Math.ceil(Math.sqrt(b*b + c*c)); | |
if(i == 0) { | |
time = lineLength; | |
} else if(i == 1) { | |
cost = lineLength; | |
} else if(i == 2) { | |
complexity = lineLength; | |
} | |
}); | |
d3.select("p#time").text(time); | |
d3.select("p#cost").text(cost); | |
d3.select("p#complexity").text(complexity); | |
d3.select("p#timevtime").text(time / time); | |
d3.select("p#timevcost").text(time / cost); | |
d3.select("p#timevcomplexity").text(time / complexity); | |
d3.select("p#costvtime").text(cost / time); | |
d3.select("p#costvcost").text(cost / cost); | |
d3.select("p#costvcomplexity").text(cost / complexity); | |
d3.select("p#complexityvtime").text(complexity / time); | |
d3.select("p#complexityvcost").text(complexity / cost); | |
d3.select("p#complexityvcomplexity").text(complexity / complexity); | |
}); | |
// Append the SVG element. | |
container.append(svg.node()); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment