Published
Edited
Mar 10, 2018
Fork of Angle
1 fork
1 star
Insert cell
Insert cell
Insert cell
viewof display = angle(height, controls.includes('showBeta'))
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: 'showTheta', label: 'Show ' }
],
value: []
})
Insert cell
controls
Insert cell
Insert cell
// The Current Theta Angle
display
Insert cell
currentThetaDeg = rad2deg(display)
Insert cell
Insert cell
Insert cell
Insert cell
// π shorthand constant helper
pi = Math.PI
Insert cell
Insert cell
angle = (height = 400, showBeta = false, showTheta = true, initialTheta = pi / 4) => {
let currentTheta = initialTheta;

/* "Canvas" setup */
// Create the canvas
const svg = DOM.svg(width, height);
const canvas = d3.select(svg);
// Style the canvas
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;
// 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]);
// Arc Transformation
// Arcs are offset by 90 degrees with transform
const arcTransform = `translate(${origin.x}, ${origin.y}), rotate(90)`
/* Helpers */
// Convert radians to arc units
// @see https://github.com/d3/d3-shape#arc_endAngle
const angle2Arc = (angle) => - angle;
// Calculate Radius (Ray) Endpoint Coordinates
const radiusEndpoint = (angle) => [scaleX(Math.cos(angle)), scaleY(Math.sin(angle))];
// Calculate Radius (Ray) Coordinates
const radiusCoords = (angle) => [[origin.x, origin.y ], radiusEndpoint(angle)];
/* Variables */
const arcStartAngle = 0;
/* Elements */
// Create the visualization group element
const vis = canvas.append('g').attr('class', 'visualization');

// Draw the initial point / vertex
const point = vis.append('circle')
.style("fill", 'black')
.attr("cx", origin.x)
.attr("cy", origin.y)
.attr("r", 2);
// Arcs
// Angle Arc Generator
const arcGen = d3.arc()
.outerRadius(39)
.innerRadius(40)
.startAngle(arcStartAngle)

// Theta angle arc
const thetaArc = vis.append('path')
.attr('class', 'arc arc-theta')
.attr('fill', 'transparent')
.attr('stroke', showTheta ? colors.thetaArc : 'transparent')
.attr('transform', arcTransform)
// Beta angle arc
const betaArc = vis.append('path')
.attr('class', 'arc arc-beta')
.attr('fill', 'transparent')
.attr('stroke', showBeta ? colors.betaArc : 'transparent')
.attr('transform', arcTransform);
// Rays
const rayGen = d3.line();
// Static Ray
const staticRay = vis.append('path')
.attr('class', 'ray ray--static')
.attr('stroke', 'black')
.attr('marker-end', (d) => "url(#arrow)")
.attr('d', rayGen(radiusCoords(arcStartAngle)));
// Dynamic Ray
const dynamicRay = vis.append('path')
.attr('class', 'ray ray--dynamic')
.attr("stroke", "black")
.attr('marker-end', (d) => "url(#arrow)");
/* Annotations */
// Create Annotations Group
const annotations = canvas.append('g')
.attr('class', 'annotations')
// Angle Labels
const labelArcGen = d3.arc()
.outerRadius(55)
.innerRadius(55)
.startAngle(arcStartAngle)
// Theta label
const thetaLabel = annotations.append("text")
.attr('class', 'annotation annotation-theta')
.attr('fill', showTheta ? colors.thetaArc : 'transparent')
.attr('dx','-4')
.attr('dy','4')
.text('θ');
// Beta label
const betaLabel = annotations.append("text")
.attr('class', 'annotation annotation-beta')
.attr('fill', showBeta ? colors.betaArc : 'transparent')
.attr('dx','-4')
.attr('dy','4')
.text('β');
// For debugging
/*
const labelArc = vis.append('path')
.attr('class', 'arc arc-label')
.attr('fill', 'transparent')
.attr('stroke', 'blue')
.attr('transform', arcTransform);
const labelPoint = vis.append('circle')
.style("fill", 'red')
.attr("r", 2);
*/
// Point label
annotations.append('text')
.attr('class', 'annotation annotation-point')
.attr('dx', -5)
.attr('dy', 20)
.attr('transform', `translate(${origin.x}, ${origin.y})`)
.text('O')
.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");
/* Methods */
const updatePositions = (angle) => {
// Arcs
// Theta Arc
thetaArc.attr('d', arcGen.endAngle(angle2Arc(angle)));
// Beta Arc
betaArc.attr('d', arcGen.endAngle(angle2Arc(angle) + 2 * pi ));
// Ray
dynamicRay.attr('d', rayGen(radiusCoords(angle)));

// Labels

// Theta Label
const thetaLabelCentroid = labelArcGen.endAngle(angle2Arc(angle) + pi).centroid();
thetaLabel.attr('transform', `translate(${thetaLabelCentroid[0] + origin.x}, ${thetaLabelCentroid[1] + origin.y})`)
// Beta Label
const betaLabelCentroid = labelArcGen.endAngle(angle2Arc(angle) + 3 * pi ).centroid();
betaLabel.attr('transform', `translate(${betaLabelCentroid[0] + origin.x}, ${betaLabelCentroid[1] + origin.y})`)
// For Debugging
//labelArc.attr('d', labelArcGen.endAngle(angle2Arc(angle)));
//labelPoint.attr('cx', thetaLabelCentroid[0] + origin.x).attr('cy', thetaLabelCentroid[1] + origin.y);
} // END updatePositions
const handleMouse = (x,y) => {
// Get the current angle using the arctangent
// Calculate the arctangent
const a = Math.atan2( y - origin.y, x - origin.x);
// Calculate the actual angle
const angle = a > 0 ? 2 * pi - a : - a;
// Set the external theta angle
currentTheta = angle;
svg.value = currentTheta;
svg.dispatchEvent(new CustomEvent('input'));

// Update the visualization
updatePositions(angle);
}
// Mouse position listener
canvas.on("mousemove", function() {
const mouse = d3.mouse(this);
// Update element positions
handleMouse(mouse[0], mouse[1]);
});
/* Initialization */
updatePositions(currentTheta);
svg.value = currentTheta;
return svg;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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