🫶
@ -1,13 +1,13 @@
|
||||
<!DOCTYPE html><html lang="en" dir="ltr"><head><title>Cubic Polynomial ↔︎ Equilateral Triangle</title><meta charset="utf-8"><meta name="application-name" content="Cubic Polynomial ↔︎ Equilateral Triangle">
|
||||
<!DOCTYPE html><html lang="en" dir="ltr"><head><title>Cubic Roots ↔︎ Equilateral Triangle</title><meta charset="utf-8"><meta name="application-name" content="Cubic Roots ↔︎ Equilateral Triangle">
|
||||
<meta name="subject" content="An interactive reproduction of a diagram by Freya Holmér">
|
||||
<meta name="abstract" content="An interactive reproduction of a diagram by Freya Holmér">
|
||||
<meta name="twitter:title" content="Cubic Polynomial ↔︎ Equilateral Triangle">
|
||||
<meta name="twitter:title" content="Cubic Roots ↔︎ Equilateral Triangle">
|
||||
<meta name="description" content="An interactive reproduction of a diagram by Freya Holmér">
|
||||
<meta name="twitter:description" content="An interactive reproduction of a diagram by Freya Holmér">
|
||||
<meta name="author" content="Ricky Reusser">
|
||||
<meta name="twitter:creator" content="Ricky Reusser">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta property="og:title" content="Cubic Polynomial ↔︎ Equilateral Triangle">
|
||||
<meta property="og:title" content="Cubic Roots ↔︎ Equilateral Triangle">
|
||||
<meta property="og:description" content="An interactive reproduction of a diagram by Freya Holmér">
|
||||
<meta property="article:author" content="Ricky Reusser">
|
||||
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" /></head><body><script src="bundle.js"></script><script src="../nav.bundle.js"></script></body></html>
|
||||
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
BIN
sketches/static/cubic-roots-thumbnail.png
Normal file
|
After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 55 KiB |
@ -6,7 +6,7 @@ const glslify = require('glslify');
|
||||
const babelify = require('babelify');
|
||||
const assert = require('assert');
|
||||
const mkdirp = require('mkdirp');
|
||||
//const Idyll = require('idyll');
|
||||
const Idyll = require('idyll');
|
||||
const path = require('path');
|
||||
const budo = require('budo');
|
||||
const brfs = require('brfs');
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
|
||||
class Gallery extends React.Component {
|
||||
render () {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import projectsIndex from '../../src/sketches/index.json';
|
||||
const React = require('react');
|
||||
const projectsIndex = require('../../src/sketches/index.json');
|
||||
|
||||
class ProjectIndex extends React.Component {
|
||||
render () {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import projectsIndex from '../../src/sketches/index.json';
|
||||
const React = require('react');
|
||||
const projectsIndex = require('../../src/sketches/index.json');
|
||||
|
||||
class Thumbnail extends React.Component {
|
||||
render () {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
|
||||
class Action extends React.PureComponent {
|
||||
render() {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
|
||||
class Analytics extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
|
||||
class Aside extends React.PureComponent {
|
||||
render() {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
|
||||
class Boolean extends React.PureComponent {
|
||||
constructor(props) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
|
||||
class Button extends React.PureComponent {
|
||||
render() {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
const V = require('victory');
|
||||
const d3Arr = require('d3-array');
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
import SyntaxHighlighter from "react-syntax-highlighter/dist/light";
|
||||
import style from 'react-syntax-highlighter/dist/styles/atom-one-dark';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
const Format = require('d3-format');
|
||||
|
||||
class Display extends React.PureComponent {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
const Format = require('d3-format');
|
||||
const Drag = require('d3-drag');
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
const Latex = require('react-latex-patched');
|
||||
const select = require('d3-selection').select;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
import regl from 'regl';
|
||||
import mat4lookAt from 'gl-mat4/lookAt'
|
||||
import mat4perspective from 'gl-mat4/perspective'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
const stateClasses = [
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
|
||||
class Fixed extends React.PureComponent {
|
||||
render() {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
|
||||
class Float extends React.PureComponent {
|
||||
render() {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
|
||||
class Footer extends React.PureComponent {
|
||||
render () {
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
import ReactDOM from 'react-dom';
|
||||
import Screen from './utils/screen';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
|
||||
class EmbeddedGist extends React.PureComponent {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
import classNames from 'classnames';
|
||||
import resl from 'resl';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
|
||||
class Inline extends React.PureComponent {
|
||||
render() {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
|
||||
class Link extends React.PureComponent {
|
||||
constructor(props) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
const React = require('react');
|
||||
const classNames = require('classnames');
|
||||
|
||||
class Menu extends React.Component {
|
||||
constructor (props) {
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
|
||||
class Panel extends React.PureComponent {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
const imageCache = [];
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
let id = 0;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
|
||||
class Range extends React.PureComponent {
|
||||
constructor(props) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
|
||||
class Select extends React.PureComponent {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
|
||||
class Slide extends React.PureComponent {
|
||||
render() {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
const Slide = require('./slide');
|
||||
|
||||
class Slideshow extends React.PureComponent {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
|
||||
class SVG extends React.PureComponent {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
|
||||
class TextContainer extends React.PureComponent {
|
||||
render() {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
|
||||
class TextInput extends React.PureComponent {
|
||||
|
||||
@ -89,6 +89,7 @@
|
||||
"util-extend": "^1.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"color-parse": "^1.3.7",
|
||||
"color-rgba": "^2.1.0",
|
||||
"color-stringify": "^1.2.1",
|
||||
|
||||
@ -21,6 +21,24 @@ const regl = require('regl')({
|
||||
onDone: require('fail-nicely')(run)
|
||||
});
|
||||
|
||||
const exp = document.createElement('div');
|
||||
exp.innerHTML = `
|
||||
A reproduction of a <a href="https://mathstodon.xyz/@acegikmo@mastodon.social/109404591773876307">diagram by Freya Holmér</a> showing the relation between cubic polynomials and an equilateral triangle.
|
||||
`;
|
||||
document.body.appendChild(exp);
|
||||
exp.style.cssText = `
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 5px;
|
||||
pointer-events: none;
|
||||
background-color: rgb(237 247 255/50%);
|
||||
width: 450px;
|
||||
max-width: 100%;
|
||||
font-family: sans-serif;
|
||||
line-height: 1.4;
|
||||
`;
|
||||
|
||||
|
||||
function run (regl) {
|
||||
let dirty = true;
|
||||
@ -117,10 +135,10 @@ function run (regl) {
|
||||
return state.scale * (x - x0) * (x - x1) * (x - x2);
|
||||
}
|
||||
|
||||
function updatePoints () {
|
||||
function updatePoints (newEvents=true) {
|
||||
const yInflec = f(state.center);
|
||||
const [x0, x1, x2] = computeRoots(state);
|
||||
const { center } = state;
|
||||
const { center, scale } = state;
|
||||
|
||||
poly.vertexAttributes.xy.subdata([...Array(poly.vertexCount).keys()].map(i => {
|
||||
const x = xScale.invert((i - 1) / (poly.vertexCount - 3) * window.innerWidth);
|
||||
@ -179,7 +197,7 @@ function run (regl) {
|
||||
inflectionHandle
|
||||
];
|
||||
|
||||
pointGrp
|
||||
const join = pointGrp
|
||||
.selectAll('circle')
|
||||
.data(handles)
|
||||
.join(
|
||||
@ -196,96 +214,104 @@ function run (regl) {
|
||||
.attr('cx', ({x}) => xScale(x))
|
||||
.attr('cy', ({y}) => yScale(y)),
|
||||
exit => exit.remove()
|
||||
)
|
||||
.call(d3.drag()
|
||||
.on('start', function (event) {
|
||||
event.sourceEvent.stopPropagation();
|
||||
})
|
||||
.on('drag', function (event, d) {
|
||||
event.sourceEvent.stopPropagation();
|
||||
event.sourceEvent.preventDefault();
|
||||
const src = event.sourceEvent.touches ? event.sourceEvent.touches[0] : event.sourceEvent;
|
||||
const x = xScale.invert(src.clientX);
|
||||
const y = yScale.invert(src.clientY);
|
||||
switch(d.type) {
|
||||
case 'root': {
|
||||
d.x = x;
|
||||
Object.assign(state, positionCircle(rootHandles.map(({x}) => x)));
|
||||
);
|
||||
|
||||
if (newEvents) {
|
||||
join.call(
|
||||
d3.drag()
|
||||
.on('start', function (event) {
|
||||
event.sourceEvent.stopPropagation();
|
||||
})
|
||||
.on('drag', function (event, d) {
|
||||
event.sourceEvent.stopPropagation();
|
||||
event.sourceEvent.preventDefault();
|
||||
const src = event.sourceEvent.touches ? event.sourceEvent.touches[0] : event.sourceEvent;
|
||||
const x = xScale.invert(src.clientX);
|
||||
const y = yScale.invert(src.clientY);
|
||||
switch(d.type) {
|
||||
case 'root': {
|
||||
d.x = x;
|
||||
Object.assign(state, positionCircle(rootHandles.map(({x}) => x)));
|
||||
break;
|
||||
}
|
||||
case 'extremum': {
|
||||
const e1 = d;
|
||||
const e2 = extremaHandles[0] === d ? extremaHandles[1] : extremaHandles[0];
|
||||
|
||||
e1.x = x;
|
||||
e1.y = y;
|
||||
|
||||
const r = 0.5 * (e1.y - e2.y);
|
||||
const p = 0.5 * (e1.x - e2.x);
|
||||
const x0 = 0.5 * (e1.x + e2.x);
|
||||
const y0 = 0.5 * (e1.y + e2.y);
|
||||
|
||||
const a = -r / (2 * p ** 3);
|
||||
const b = 0;
|
||||
const c = 1.5 * r / p;
|
||||
const dd = y0;
|
||||
|
||||
const A = b / a;
|
||||
const B = c / a;
|
||||
const C = dd / a;
|
||||
|
||||
const Q = Math.min(0, (3 * B - A ** 2) / 9);
|
||||
const R = (9 * A * B - 27 * C - 2 * A ** 3) / 54;
|
||||
const D = Math.min(0, Q ** 3 + R ** 2);
|
||||
|
||||
const theta = Math.acos(Math.max(-0.99999, Math.min(0.99999, R / (-Q) ** 1.5)));
|
||||
|
||||
state.center = x0;
|
||||
state.radius = 2 * Math.sqrt(-Q);
|
||||
state.alpha = theta / 3;
|
||||
state.scale = a;
|
||||
|
||||
break;
|
||||
}
|
||||
case 'inflection': {
|
||||
const dx = x - center;
|
||||
const dy = (y - yInflec) / scale;
|
||||
|
||||
const a = 1;
|
||||
const b = -(x0 + x1 + x2);
|
||||
const c = x1 * x2 + x0 * x2 + x0 * x1;
|
||||
const d = -x0 * x1 * x2 + dy;
|
||||
|
||||
const A = b / a;
|
||||
const B = c / a;
|
||||
const C = d / a;
|
||||
|
||||
const Q = Math.min(0, (3 * B - A ** 2) / 9);
|
||||
const R = (9 * A * B - 27 * C - 2 * A ** 3) / 54;
|
||||
const D = Math.min(0, Q ** 3 + R ** 2);
|
||||
|
||||
const theta = Math.acos(Math.max(-0.99999, Math.min(0.99999, R / (-Q) ** 1.5)));
|
||||
|
||||
state.center = x;
|
||||
state.radius = 2 * Math.sqrt(-Q);
|
||||
state.alpha = theta / 3;
|
||||
|
||||
break;
|
||||
}
|
||||
case 'tri':
|
||||
const dy = y - yScale.invert(state.yOffset);
|
||||
const dx = x - state.center;
|
||||
state.alpha = Math.atan2(dy, dx);
|
||||
break;
|
||||
}
|
||||
case 'extremum': {
|
||||
const e1 = d;
|
||||
const e2 = extremaHandles[0] === d ? extremaHandles[1] : extremaHandles[0];
|
||||
|
||||
e1.x = x;
|
||||
e1.y = y;
|
||||
|
||||
const r = 0.5 * (e1.y - e2.y);
|
||||
const p = 0.5 * (e1.x - e2.x);
|
||||
const x0 = 0.5 * (e1.x + e2.x);
|
||||
const y0 = 0.5 * (e1.y + e2.y);
|
||||
|
||||
const a = -r / (2 * p ** 3);
|
||||
const b = 0;
|
||||
const c = 1.5 * r / p;
|
||||
const dd = y0;
|
||||
|
||||
const A = b / a;
|
||||
const B = c / a;
|
||||
const C = dd / a;
|
||||
|
||||
const Q = Math.min(0, (3 * B - A ** 2) / 9);
|
||||
const R = (9 * A * B - 27 * C - 2 * A ** 3) / 54;
|
||||
const D = Math.min(0, Q ** 3 + R ** 2);
|
||||
|
||||
const theta = Math.acos(Math.max(-0.99999, Math.min(0.99999, R / (-Q) ** 1.5)));
|
||||
|
||||
state.center = x0;
|
||||
state.radius = 2 * Math.sqrt(-Q);
|
||||
state.alpha = theta / 3;
|
||||
state.scale = a;
|
||||
|
||||
break;
|
||||
}
|
||||
case 'inflection': {
|
||||
const dx = x - center;
|
||||
const dy = y - yInflec;
|
||||
|
||||
const a = 1;
|
||||
const b = -(x0 + x1 + x2);
|
||||
const c = x1 * x2 + x0 * x2 + x0 * x1;
|
||||
const d = -x0 * x1 * x2 + dy;
|
||||
|
||||
const A = b / a;
|
||||
const B = c / a;
|
||||
const C = d / a;
|
||||
|
||||
const Q = Math.min(0, (3 * B - A ** 2) / 9);
|
||||
const R = (9 * A * B - 27 * C - 2 * A ** 3) / 54;
|
||||
const D = Math.min(0, Q ** 3 + R ** 2);
|
||||
|
||||
const theta = Math.acos(Math.max(-0.99999, Math.min(0.99999, R / (-Q) ** 1.5)));
|
||||
|
||||
case 'center':
|
||||
state.center = x;
|
||||
state.radius = 2 * Math.sqrt(-Q);
|
||||
state.alpha = theta / 3;
|
||||
|
||||
state.yOffset = yScale(y);
|
||||
break;
|
||||
}
|
||||
case 'tri':
|
||||
const dy = y - yScale.invert(state.yOffset);
|
||||
const dx = x - state.center;
|
||||
state.alpha = Math.atan2(dy, dx);
|
||||
break;
|
||||
case 'center':
|
||||
state.center = x;
|
||||
state.yOffset = yScale(y);
|
||||
break;
|
||||
}
|
||||
updatePoints();
|
||||
repaint();
|
||||
})
|
||||
);
|
||||
}
|
||||
updatePoints(false);
|
||||
repaint();
|
||||
})
|
||||
.on('end', function () {
|
||||
updatePoints();
|
||||
repaint();
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
updatePoints();
|
||||
|
||||
6
src/src/cubic-roots/metadata.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"title": "Cubic Roots ↔︎ Equilateral Triangle",
|
||||
"description": "An interactive reproduction of a diagram by Freya Holmér",
|
||||
"order": 3300 ,
|
||||
"image": "http://rreusser.github.io/src/src/cubic-roots/thumbnail.jpg"
|
||||
}
|
||||
BIN
src/src/cubic-roots/thumbnail.jpg
Normal file
|
After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
@ -1,6 +0,0 @@
|
||||
{
|
||||
"title": "Cubic Polynomial ↔︎ Equilateral Triangle",
|
||||
"description": "An interactive reproduction of a diagram by Freya Holmér",
|
||||
"order": 3300 ,
|
||||
"image": "http://rreusser.github.io/src/src/polynomial-roots/thumbnail.jpg"
|
||||
}
|
||||
BIN
src/src/sketches/static/cubic-roots-thumbnail.jpg
Normal file
|
After Width: | Height: | Size: 61 KiB |
@ -923,7 +923,7 @@
|
||||
"@babel/types" "^7.4.4"
|
||||
esutils "^2.0.2"
|
||||
|
||||
"@babel/preset-react@^7.16.7":
|
||||
"@babel/preset-react@^7.16.7", "@babel/preset-react@^7.18.6":
|
||||
version "7.18.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.18.6.tgz#979f76d6277048dc19094c217b507f3ad517dd2d"
|
||||
integrity sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==
|
||||
|
||||