Skip to content

Instantly share code, notes, and snippets.

@richardjkendall
Created February 25, 2024 09:59
Show Gist options
  • Save richardjkendall/93a7b83a6f785c9afb983ba9c61a67ed to your computer and use it in GitHub Desktop.
Save richardjkendall/93a7b83a6f785c9afb983ba9c61a67ed to your computer and use it in GitHub Desktop.
Draw ellipse and triangle
<!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