mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 16:48:10 -05:00
Render the gasket
Need to get rid of Plotly, unfortuantely - causes issues with hydration. Seems like Victory is better able to handle what I need.
This commit is contained in:
parent
1b4d190906
commit
3a41d5c81a
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
slug: 2024/11/playing-with-fire
|
slug: 2024/11/playing-with-fire
|
||||||
title: "Playing with fire: Introduction"
|
title: "Playing with fire: The fractal flame algorithm"
|
||||||
date: 2024-11-15 12:00:00
|
date: 2024-11-15 12:00:00
|
||||||
authors: [bspeice]
|
authors: [bspeice]
|
||||||
tags: []
|
tags: []
|
||||||
@ -12,9 +12,9 @@ Wikipedia [describes](https://en.wikipedia.org/wiki/Fractal_flame) fractal flame
|
|||||||
|
|
||||||
I think of them a different way: beauty in mathematics.
|
I think of them a different way: beauty in mathematics.
|
||||||
|
|
||||||
import isDarkMode from "@site/src/isDarkMode";
|
import isDarkMode from '@site/src/isDarkMode'
|
||||||
import bannerDark from "./banner-dark.png"
|
import bannerDark from '../banner-dark.png'
|
||||||
import bannerLight from "./banner-light.png"
|
import bannerLight from '../banner-light.png'
|
||||||
|
|
||||||
<center>
|
<center>
|
||||||
<!-- Why are these backwards? -->
|
<!-- Why are these backwards? -->
|
||||||
@ -201,6 +201,8 @@ $$
|
|||||||
|
|
||||||
### The chaos game
|
### The chaos game
|
||||||
|
|
||||||
|
import CodeBlock from '@theme/CodeBlock'
|
||||||
|
|
||||||
Next, how do we find out all the points in $S$? The paper lays out an algorithm called the "chaos game":
|
Next, how do we find out all the points in $S$? The paper lays out an algorithm called the "chaos game":
|
||||||
|
|
||||||
$$
|
$$
|
||||||
@ -213,3 +215,32 @@ $$
|
|||||||
\}
|
\}
|
||||||
\end{align*}
|
\end{align*}
|
||||||
$$
|
$$
|
||||||
|
|
||||||
|
Let's turn this into code, one piece at a time.
|
||||||
|
|
||||||
|
First, the "bi-unit square" is the range $[-1, 1]$. We can pick a random point like this:
|
||||||
|
|
||||||
|
import biunitSource from '!!raw-loader!./biunit'
|
||||||
|
|
||||||
|
<CodeBlock language="typescript">{biunitSource}</CodeBlock>
|
||||||
|
|
||||||
|
Next, we need to choose a random integer from $0$ to $n - 1$:
|
||||||
|
|
||||||
|
import randintSource from '!!raw-loader!./randint'
|
||||||
|
|
||||||
|
<CodeBlock language="typescript">{randintSource}</CodeBlock>
|
||||||
|
|
||||||
|
Finally, implementing the `plot` function. Web browsers have a [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)
|
||||||
|
we can use for 2D graphics. In our case, the plot function will take an $(x,y)$ coordinate and plot it by
|
||||||
|
coloring the corresponding pixel in an [ImageData](https://developer.mozilla.org/en-US/docs/Web/API/ImageData):
|
||||||
|
|
||||||
|
import plotSource from '!!raw-loader!./plot'
|
||||||
|
|
||||||
|
<CodeBlock language="typescript">{plotSource}</CodeBlock>
|
||||||
|
|
||||||
|
import Playground from '@theme/Playground'
|
||||||
|
import Scope from './scope'
|
||||||
|
|
||||||
|
import Gasket from '!!raw-loader!./Gasket'
|
||||||
|
|
||||||
|
<Playground scope={Scope}>{Gasket}</Playground>
|
43
blog/2024-11-15-playing-with-fire/1-introduction/Gasket.jsx
Normal file
43
blog/2024-11-15-playing-with-fire/1-introduction/Gasket.jsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
function Gasket(props) {
|
||||||
|
const iterations = 1000;
|
||||||
|
const functions = [
|
||||||
|
(x, y) => [x / 2, y / 2],
|
||||||
|
(x, y) => [(x + 1) / 2, y / 2],
|
||||||
|
(x, y) => [x / 2, (y + 1) / 2]
|
||||||
|
]
|
||||||
|
|
||||||
|
function chaosGame(image) {
|
||||||
|
var [x, y] = [randomBiUnit(), randomBiUnit()];
|
||||||
|
|
||||||
|
for (var i = 0; i < iterations; i++) {
|
||||||
|
const f = functions[randomInteger(0, functions.length)];
|
||||||
|
[x, y] = f(x, y);
|
||||||
|
|
||||||
|
if (i > 20) {
|
||||||
|
plot(x, y, image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClickRender() {
|
||||||
|
/** @type{HTMLCanvasElement} */
|
||||||
|
const canvas = document.getElementById('canvas-gasket');
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
const image = context.createImageData(canvas.width, canvas.height);
|
||||||
|
chaosGame(image);
|
||||||
|
context.putImageData(image, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div style={{width: '100%'}}>
|
||||||
|
<center>
|
||||||
|
<button onClick={onClickRender}>Play chaos game</button>
|
||||||
|
<hr/>
|
||||||
|
</center>
|
||||||
|
<div>
|
||||||
|
<canvas
|
||||||
|
id={'canvas-gasket'}
|
||||||
|
style={{width: '100%', aspectRatio: '1 / 1'}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
export default function randomBiUnit(): number {
|
||||||
|
return Math.random() * 2 - 1;
|
||||||
|
}
|
32
blog/2024-11-15-playing-with-fire/1-introduction/plot.ts
Normal file
32
blog/2024-11-15-playing-with-fire/1-introduction/plot.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
export default function plot(x: number, y: number, image: ImageData) {
|
||||||
|
// Translate (x,y) coordinates to pixel coordinates.
|
||||||
|
// The display range we care about is x=[0, 1], y=[0, 1],
|
||||||
|
// so our pixelX and pixelY coordinates are easy to calculate:
|
||||||
|
const pixelX = Math.floor(x * image.width);
|
||||||
|
const pixelY = Math.floor(y * image.height);
|
||||||
|
|
||||||
|
// If we have an (x,y) coordinate outside the display range,
|
||||||
|
// skip it
|
||||||
|
if (
|
||||||
|
pixelX < 0 ||
|
||||||
|
pixelX > image.width ||
|
||||||
|
pixelY < 0 ||
|
||||||
|
pixelY > image.height
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImageData is an array that contains four bytes per pixel
|
||||||
|
// (one for each of the red, green, blue, and alpha values).
|
||||||
|
// The (pixelX, pixelY) coordinates are used to find where
|
||||||
|
// in the image we need to write.
|
||||||
|
const index = pixelY * (image.width * 4) + pixelX * 4;
|
||||||
|
|
||||||
|
// Set the pixel to black by writing a 0 to the first three
|
||||||
|
// bytes (red, green, blue), and 256 to the last byte (alpha),
|
||||||
|
// starting at our index:
|
||||||
|
image.data[index] = 0;
|
||||||
|
image.data[index + 1] = 0;
|
||||||
|
image.data[index + 2] = 0;
|
||||||
|
image.data[index + 3] = 0xff;
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
export default function randomInteger(min: number, max: number): number {
|
||||||
|
return Math.floor(Math.random() * (max - min)) + min;
|
||||||
|
}
|
14
blog/2024-11-15-playing-with-fire/1-introduction/scope.ts
Normal file
14
blog/2024-11-15-playing-with-fire/1-introduction/scope.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import randomBiUnit from './biunit';
|
||||||
|
import plot from './plot';
|
||||||
|
import randomInteger from './randint';
|
||||||
|
|
||||||
|
|
||||||
|
const Scope = {
|
||||||
|
React,
|
||||||
|
plot,
|
||||||
|
randomBiUnit,
|
||||||
|
randomInteger
|
||||||
|
}
|
||||||
|
export default Scope;
|
@ -1,29 +0,0 @@
|
|||||||
/**
|
|
||||||
* Generate a uniform random number in the range (-1, 1)
|
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function randomBiUnit() {
|
|
||||||
return Math.random() * 2 - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a uniform random integer in the range [min, max)
|
|
||||||
*
|
|
||||||
* @param min
|
|
||||||
* @param max
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function randomInteger(min: number, max: number) {
|
|
||||||
return Math.floor(Math.random() * (max - min)) + min;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://stackoverflow.com/a/34356351
|
|
||||||
export function hexToBytes(hex: string) {
|
|
||||||
var bytes = [];
|
|
||||||
for (var i = 0; i < hex.length; i += 2) {
|
|
||||||
bytes.push(parseInt(hex.substring(i, i + 2), 16));
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytes;
|
|
||||||
}
|
|
@ -28,13 +28,14 @@ const config: Config = {
|
|||||||
locales: ['en'],
|
locales: ['en'],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
themes: ['@docusaurus/theme-live-codeblock'],
|
||||||
presets: [
|
presets: [
|
||||||
[
|
[
|
||||||
'classic',
|
'classic',
|
||||||
{
|
{
|
||||||
docs: false,
|
docs: false,
|
||||||
blog: {
|
blog: {
|
||||||
routeBasePath: "/",
|
routeBasePath: '/',
|
||||||
blogSidebarTitle: 'All posts',
|
blogSidebarTitle: 'All posts',
|
||||||
blogSidebarCount: 'ALL',
|
blogSidebarCount: 'ALL',
|
||||||
showReadingTime: true,
|
showReadingTime: true,
|
||||||
|
2008
package-lock.json
generated
2008
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@ -15,19 +15,22 @@
|
|||||||
"typecheck": "tsc"
|
"typecheck": "tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "^3.6.0",
|
"@docusaurus/core": "^3.6.1",
|
||||||
"@docusaurus/faster": "^3.5.2",
|
"@docusaurus/faster": "^3.6.1",
|
||||||
"@docusaurus/preset-classic": "^3.6.0",
|
"@docusaurus/preset-classic": "^3.6.1",
|
||||||
|
"@docusaurus/theme-live-codeblock": "^3.6.1",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"docusaurus-lunr-search": "^3.5.0",
|
"docusaurus-lunr-search": "^3.5.0",
|
||||||
"plotly.js": "^2.35.2",
|
"plotly.js": "^2.35.2",
|
||||||
"prism-react-renderer": "^2.3.0",
|
"prism-react-renderer": "^2.3.0",
|
||||||
|
"raw-loader": "^4.0.2",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-dom": "^18.0.0",
|
"react-dom": "^18.0.0",
|
||||||
"react-plotly.js": "^2.6.0",
|
"react-plotly.js": "^2.6.0",
|
||||||
"rehype-katex": "^7.0.1",
|
"rehype-katex": "^7.0.1",
|
||||||
"remark-math": "^6.0.0"
|
"remark-math": "^6.0.0",
|
||||||
|
"victory": "^37.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "^3.6.0",
|
"@docusaurus/module-type-aliases": "^3.6.0",
|
||||||
|
Loading…
Reference in New Issue
Block a user