Published
Edited
Mar 7, 2018
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof controls = checkbox({
title: 'Angle Options',
//description: 'Just a single checkbox to toggle',
options: [
{ value: 'showBeta', label: 'Show Interior and Exterior Angles' }
],
value: 'showBeta'
})
Insert cell
showBeta = controls
Insert cell
showBeta
Insert cell
// Angle Visualization
angle = {
console.log('');
console.log('eval angle');
/* "Canvas" setup */
const height = 400;
// Setup canvas
const canvas = d3.select(DOM.svg(width, height));
// Canvas styling
canvas.style('border', '1px solid rgba(0,0,0,0.05)')
.style('font-family', 'monospace')
/* Constants */
// Center of the canvas
const origin = {x: width/2, y: height/2}
// Length of the ray
const rayLength = width > height / 3 ? height / 3 : width / 3;
// Static Ray End Coordinate
const staticRayEndCoord = [origin.x + rayLength, origin.y]
// The x scale for the ray
const scaleX = d3.scaleLinear()
.domain([-1, 1])
.range([origin.x - rayLength, origin.x + rayLength]);
// The y scale for the ray
const scaleY = d3.scaleLinear()
.domain([1, -1])
.range([origin.y + rayLength, origin.y -rayLength]);
// Start angle for arcs
const thetaStartAngle = pi / 2;
const thetaEndAngle = pi/4;
/* Visualization Elements */
// Create the visualization element
const vis = canvas.append('g')
.attr('class', 'visualization');
// TODO: maybe just translate the viz to the middle instead of everything?
// Theta Label Arc Generator
const labelArcGen = d3.arc()
.outerRadius(60)
.innerRadius(60)
.startAngle(thetaStartAngle)
.endAngle(thetaEndAngle);
// Draw the initial exterior angle Arc
/*
const labelArc = vis.append('path')
.attr('class', 'arc arc-label')
.attr('fill', 'transparent')
.attr('stroke', 'blue')
.attr('transform', `translate(${origin.x}, ${origin.y})`)
.attr('d', labelArcGen);
*/
// Angle Arc Generator
const arcGen = d3.arc()
.outerRadius(39)
.innerRadius(40)
//.padAngle(0.03490658504)
.startAngle(thetaStartAngle) // Default for theta angle arc
.endAngle(thetaEndAngle); // Default for theta angle arc
// Draw the initial exterior angle Arc
const thetaArc = vis.append('path')
.attr('class', 'arc arc-theta')
.attr('fill', 'transparent')
.attr('stroke', colors.thetaArc)
.attr('transform', `translate(${origin.x}, ${origin.y})`)
.attr('d', arcGen);
let betaArc;
if (showBeta) {
// Draw the initial beta angle arc
betaArc = vis.append('path')
.attr('class', 'arc arc-beta')
.attr('fill', 'transparent')
.attr('stroke', colors.betaArc)
.attr('transform', `translate(${origin.x}, ${origin.y})`)
.attr('d', arcGen.startAngle(thetaEndAngle).endAngle(-3/2 * pi));
}
// Use the d3 line generator
const lineGenerator = d3.line();
// Static angle ray
const side = vis.append('path')
.attr('class', 'ray ray--static')
.attr('stroke', 'black')
.attr('marker-end', (d) => "url(#arrow)")
.attr('d', lineGenerator([[origin.x, origin.y], staticRayEndCoord]));
// Create the ray line
const line = vis.append('path')
.attr('class', 'ray ray--dynamic')
.attr("stroke", "black")
.attr('d', lineGenerator([[origin.x, origin.y], [Math.cos(thetaEndAngle) * rayLength + origin.x , origin.y - Math.sin(thetaEndAngle) * rayLength]]))
.attr('marker-end', (d) => "url(#arrow)");
// Draw the initial point / vertex
vis.append('circle')
.style("fill", 'black')
.attr("cx", origin.x)
.attr("cy", origin.y)
.attr("r", 2);
/* Update Functions */
// Update position of ray and arcs
function updatePositions(x,y){
// Calculate the arctangent
const a = Math.atan2( y - origin.y, x - origin.x);
// Calculate the ray endpoint coordinates
const rayX = scaleX(Math.cos(a));
const rayY = scaleY(Math.sin(a));
// Calculate the arc end angle
// @see https://github.com/d3/d3-shape#arc_endAngle
const thetaAngle = a < 0 ? a + pi/2 : (- 2* pi + a) + pi / 2 ;
// Generate the line using the input coordinates
const lineData = lineGenerator([[origin.x, origin.y], [rayX, rayY]]);
// Update the exterior angle arc data
const thetaArcArcData = arcGen.endAngle(thetaAngle);
thetaArc.attr('d', thetaArcArcData);
// Update the theta label position
labelArcGen.endAngle(thetaAngle);
// labelArc.attr('d', labelArcGen); // For debugging label position
const thetaLabelCentroid = labelArcGen.centroid();
thetaLabel.attr('transform', `translate(${thetaLabelCentroid[0] + origin.x}, ${thetaLabelCentroid[1] + origin.y})`);
// Update the interior angle arc data
if (betaArc) {
const endAngle = thetaAngle + 2 * pi
const betaArcData = arcGen.endAngle(endAngle).startAngle(thetaStartAngle);
betaArc.attr('d', betaArcData);
const labelArcData = labelArcGen.endAngle(endAngle).startAngle(thetaStartAngle);
const betaLabelCentroid = labelArcData.centroid();
betaLabel.attr('transform', `translate(${betaLabelCentroid[0] + origin.x}, ${betaLabelCentroid[1] + origin.y})`);
}

// Update the ray position
line.attr('d', lineData);
}
// Mouse position listener
canvas.on("mousemove", function() {
const mouse = d3.mouse(this);
// Update element positions
updatePositions(mouse[0], mouse[1]);
});
/* Annotations */
// Create Annotations Group
const annotations = canvas.append('g')
.attr('class', 'annotations')
// Point label
annotations.append('text')
.attr('class', 'annotation annotation-point')
.attr('transform', `translate(${origin.x - 5}, ${origin.y + 20})`)
.text('O')
.attr('fill','black')

// Theta label
const thetaLabel = annotations.append("text")
.attr('class', 'annotation annotation-theta')
.attr('fill', colors.thetaArc)
.attr('dx','-4')
.attr('dy','4')
.attr('transform', `translate(${labelArcGen.centroid()[0] + origin.x}, ${labelArcGen.centroid()[1] + origin.y})`)
.text('θ');
let betaLabel;
if (showBeta) {
betaLabel = annotations.append("text")
.attr('class', 'annotation annotation-theta')
.attr('fill', colors.betaArc)
.attr('dx','-4')
.attr('dy','4')
.attr('transform', `translate(${labelArcGen.startAngle(thetaEndAngle).endAngle(-3/2 * pi).centroid()[0] + origin.x}, ${labelArcGen.startAngle(thetaEndAngle).endAngle(-3/2 * pi).centroid()[1] + origin.y})`)
.text('β');
}
const description = showBeta ? "Angles θ and β with vertex at point O": 'An angle θ with vertex at point O'
// Description text
annotations.append('text')
.attr('font-size', '14')
.attr('class', 'annotation annotation-description')
.attr('transform', `translate(${20}, ${20})`)
.text(`Fig 2: ${description}`)
.attr('fill','black')

// Add an arrow-head marker to the ray
canvas.append("svg:defs").append("svg:marker")
.attr("id", "arrow")
.attr("viewBox", "0 -5 10 10")
//.attr('refX', 0) //Adjust the arrow head position along the line
.attr("markerWidth", 5)
.attr("markerHeight", 5)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
return canvas.node();
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more