Public
Edited
Feb 18, 2022
2 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
/**
* Draws all of the elements onto the provided g based on the data supplied
* @param {g} g
* @param {Array[Object]} data
*/
draw_shapes = function(g, data) {
for (let i=0; i<data.length; i++) {
let info = data[i];
let add_base = info.shapes[0].drawing_start;
let add_expand = info.shapes[0].drawing_end;

let target_delay = info.step * delay;
let target_g = g.append("g");
target_g.selectAll(info.shape)
.data(info.shapes)
.enter()
.append(info.shape)
.attr("class", info.class)
.call(add_base)
.transition()
.delay(target_delay)
.duration(duration)
.call(add_expand);

let delay_offset = (info.class == "construction") ? duration : 2*delay;
target_g.transition()
.delay(data.length*delay + delay_offset)
.duration(duration)
.style("opacity", 0)
.remove();
}
}
Insert cell
/**
* Instructions
*
* @class Instructions
*/
class Instructions {
constructor(instructions) {
this.data = [];

for (let i=0; i<instructions.length; i++) {
this.process(instructions[i]);
}
this.data.forEach((d,i) => d.step = i);
}

process(instruction) {
let shapes = [];

for (let i=0; i<instruction.num; i++) {
let shape;
if (!("step_a1" in instruction)) {
shape = this.get_base(instruction.draw, i/instruction.num);
} else {
shape = this.get_main(instruction, i);
}

if ("extend" in instruction && shape instanceof Line) {
shape.extend(extend_amount, instruction.extend);
}

shapes.push(shape);
}

this.data.push({shape: instruction.draw, class: instruction.class, shapes: shapes});
}

get_base(draw, increment) {
let point_1 = {x: 0, y: 0};

let point_2;
if (draw == "circle") {
point_2 = {x: 0, y: r};
} else {
point_2 = {x: r*Math.sin((2*Math.PI*increment)),
y: r*Math.cos((2*Math.PI*increment))};
}

return this.get_shape(point_1, point_2, draw);
}

get_main(instruction, i) {
let point_1 = new Intersect(this.access_shape(instruction.step_a1, instruction.point_a1(i)),
this.access_shape(instruction.step_a2, instruction.point_a2(i)));
let point_2 = new Intersect(this.access_shape(instruction.step_b1, instruction.point_b1(i)),
this.access_shape(instruction.step_b2, instruction.point_b2(i)));

return this.get_shape(point_1, point_2, instruction.draw);
}

get_shape(point_1, point_2, draw) {
if (draw == "circle") {
return new Circle(point_1, point_2);
}
return new Line(point_1, point_2);
}

access_shape(step, i) {
let row = this.data[step].shapes;
return row[i % row.length];
}
}
Insert cell
/**
* Abstract Class Shape
*
* @class Shape
*/
class Shape {
constructor() {
if (this.constructor === Shape) {
throw new Error("Can't instantiate abstract class!");
}
}

get_formula() {
throw new Error("Method 'get_formula' must be implemented");
}

drawing_start() {
throw new Error("Method 'drawing_start' must be implemented");
}

drawing_end() {
throw new Error("Method 'drawing_end' must be implemented");
}
}

Insert cell
/**
* Circle
*
* @class Circle
* @extends {Shape}
*/
class Circle extends Shape {
constructor(centre, point) {
super();

this.cx = centre.x;
this.cy = centre.y;

this.r = Math.sqrt(Math.pow(point.x - centre.x, 2) + Math.pow(point.y - centre.y, 2));
}

get_formula() {
return {h: this.cx, k: this.cy, r: this.r};
}

drawing_start(selection) {
selection
.attr("cx", d => d.cx)
.attr("cy", d => d.cy)
.attr("r", 0);
}

drawing_end(selection) {
selection.attr("r", d => d.r);
}
}
Insert cell
/**
* Line
*
* @class Line
* @extends {Shape}
*/
class Line extends Shape {
constructor(point1, point2) {
super();

this.x1 = point1.x;
this.y1 = point1.y;
this.x2 = point2.x;
this.y2 = point2.y;
}

get_rise() {
return this.y2 - this.y1;
}

get_run() {
let run = this.x2 - this.x1;

// can't divide by zero
run = Math.abs(run) < 0.0001 ? 0.0001 : run;
return run;
}

get_formula() {

let m = this.get_rise()/this.get_run();
let c = this.y1 - m*this.x1;

return {m: m, c: c};
}

get_points() {
return {x1: this.x1, y1: this.y1, x2: this.x2, y2: this.y2};
}

get_in_x(val_1, val_2) {
let x = val_2;
if (x < Math.min(this.x1, this.x2) || Math.max(this.x1, this.x2) < x) {
x = val_1;
}
return x;
}

extend(extension, type) {
let run = this.get_run()

let m = this.get_rise()/run;
let angle = Math.atan(m);

let shift_x = extension * Math.cos(angle);
let shift_y = extension * Math.sin(angle);

let direction = (run > 0) ? -1 : 1;
shift_x *= direction;
shift_y *= direction;

if (type == "1" || type == "both") {
this.x1 += shift_x;
this.y1 += shift_y;
}
if (type == "2" || type == "both") {
this.x2 -= shift_x;
this.y2 -= shift_y;
}

return this.get_points();
}

drawing_start(selection) {
selection
.attr("x1", d => d.x1)
.attr("y1", d => d.y1)
.attr("x2", d => d.x1)
.attr("y2", d => d.y1);
}

drawing_end(selection) {
selection
.attr("x2", d => d.x2)
.attr("y2", d => d.y2);
}
}
Insert cell
/**
* Intersect
*
* @class Intersect
*/
class Intersect {
constructor(shape_1, shape_2) {
if (shape_1 instanceof Line) {
if (shape_2 instanceof Line) {
this._find_line_line(shape_1, shape_2);
} else {
this._find_line_circle(shape_1, shape_2);
}
} else {
if (shape_2 instanceof Line) {
this._find_line_circle(shape_2, shape_1);
} else {
throw new Error("Find intersect of two circles hasn't been implemented");
}
}
}

get_point() {
return {x: this.x, y: this.y};
}

_find_line_circle(line, circle) {
let circle_formula = circle.get_formula();
let line_formula = line.get_formula();

// 1 + m^2
let a = 1 + Math.pow(line_formula.m, 2);
// 2mc - 2mk - 2h
let b = 2*line_formula.m*line_formula.c - 2*line_formula.m*circle_formula.k - 2*circle_formula.h;
// h^2 + c^2 + k^2 - r^2 - 2ck
let c = Math.pow(circle_formula.h, 2) + Math.pow(line_formula.c, 2) + Math.pow(circle_formula.k, 2)
- Math.pow(circle_formula.r, 2) - 2*line_formula.c*circle_formula.k;

let discriminant = Math.pow(b, 2) - 4*a*c;

let x1 = (-b + Math.sqrt(discriminant))/(2*a);
let x2 = (-b - Math.sqrt(discriminant))/(2*a);

this.x = line.get_in_x(x1, x2);
this.y = line_formula.m*this.x + line_formula.c;
}

_find_line_line(line_1, line_2) {
let formula_1 = line_1.get_formula();
let formula_2 = line_2.get_formula();

this.x = (formula_2.c - formula_1.c)/(formula_1.m - formula_2.m);
this.y = formula_1.m*this.x + formula_1.c;
}
}
Insert cell
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