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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more