mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 16:48:10 -05:00
Updated gasket, fixed charts
This commit is contained in:
parent
3a41d5c81a
commit
f26afcccf9
34
blog/2024-11-15-playing-with-fire/1-introduction/Canvas.tsx
Normal file
34
blog/2024-11-15-playing-with-fire/1-introduction/Canvas.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import {useEffect, useRef} from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
renderFn: (ImageData) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Canvas: React.FC<Props> = ({renderFn}: Props) => {
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const ctx = canvasRef.current?.getContext("2d");
|
||||||
|
if (!ctx) {
|
||||||
|
console.log("Canvas not ready");
|
||||||
|
}
|
||||||
|
|
||||||
|
const image = ctx.createImageData(canvasRef.current.width, canvasRef.current.height);
|
||||||
|
renderFn(image);
|
||||||
|
ctx.putImageData(image, 0, 0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<canvas
|
||||||
|
ref={canvasRef}
|
||||||
|
width={600}
|
||||||
|
height={600}
|
||||||
|
style={{
|
||||||
|
aspectRatio: '1 / 1',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Canvas;
|
@ -1,43 +1,24 @@
|
|||||||
function Gasket(props) {
|
// Hint: try increasing the iteration count
|
||||||
const iterations = 1000;
|
const iterations = 10000;
|
||||||
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) {
|
// Hint: negating `x` and `y` also creates some interesting images
|
||||||
var [x, y] = [randomBiUnit(), randomBiUnit()];
|
const functions = [
|
||||||
|
(x, y) => [x / 2, y / 2],
|
||||||
|
(x, y) => [(x + 1) / 2, y / 2],
|
||||||
|
(x, y) => [x / 2, (y + 1) / 2]
|
||||||
|
]
|
||||||
|
|
||||||
for (var i = 0; i < iterations; i++) {
|
function chaosGame(image) {
|
||||||
const f = functions[randomInteger(0, functions.length)];
|
var [x, y] = [randomBiUnit(), randomBiUnit()];
|
||||||
[x, y] = f(x, y);
|
|
||||||
|
|
||||||
if (i > 20) {
|
for (var count = 0; count < iterations; count++) {
|
||||||
plot(x, y, image);
|
const i = randomInteger(0, functions.length);
|
||||||
}
|
[x, y] = functions[i](x, y);
|
||||||
|
|
||||||
|
if (count > 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>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render(<Canvas renderFn={chaosGame}/>)
|
||||||
|
@ -53,28 +53,16 @@ like a coordinate chart.
|
|||||||
|
|
||||||
For example, if we say $S = \{(0,0), (1, 1), (2, 2)\}$, there are three points to plot:
|
For example, if we say $S = \{(0,0), (1, 1), (2, 2)\}$, there are three points to plot:
|
||||||
|
|
||||||
import Plot from "react-plotly.js"
|
import {VictoryChart, VictoryTheme, VictoryScatter, VictoryLegend} from "victory";
|
||||||
|
export const simpleData = [
|
||||||
|
{x: 0, y: 0},
|
||||||
|
{x: 1, y: 1},
|
||||||
|
{x: 2, y: 2}
|
||||||
|
]
|
||||||
|
|
||||||
<center>
|
<VictoryChart theme={VictoryTheme.clean}>
|
||||||
<Plot
|
<VictoryScatter data={simpleData} size={5} style={{data: {fill: "blue"}}}/>
|
||||||
data={[
|
</VictoryChart>
|
||||||
{
|
|
||||||
x: [0, 1, 2],
|
|
||||||
y: [0, 1, 2],
|
|
||||||
type: 'scatter',
|
|
||||||
mode: 'markers',
|
|
||||||
marker: { size: 15 }
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
layout={{
|
|
||||||
plot_bgcolor: 'rgba(0,0,0,0)',
|
|
||||||
paper_bgcolor: 'rgba(0,0,0,0)'
|
|
||||||
}}
|
|
||||||
config={{
|
|
||||||
staticPlot: true
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</center>
|
|
||||||
|
|
||||||
For fractal flames, we just need to figure out which points are in $S$ and plot them. While there are
|
For fractal flames, we just need to figure out which points are in $S$ and plot them. While there are
|
||||||
technically an infinite number of points, if we find _enough_ points and plot them, we end up with a nice picture.
|
technically an infinite number of points, if we find _enough_ points and plot them, we end up with a nice picture.
|
||||||
@ -95,74 +83,21 @@ $$
|
|||||||
|
|
||||||
That is, for an input point $(x, y)$, the output point will be $(x + 1, y)$:
|
That is, for an input point $(x, y)$, the output point will be $(x + 1, y)$:
|
||||||
|
|
||||||
<center>
|
export const shiftData = simpleData.map(({x, y}) => { return {x: x + 1, y} })
|
||||||
<Plot
|
|
||||||
|
<VictoryChart theme={VictoryTheme.clean}>
|
||||||
|
<VictoryScatter data={simpleData} size={5} style={{data: {fill: "blue"}}}/>
|
||||||
|
<VictoryScatter data={shiftData} size={5} style={{data: {fill: "orange"}}}/>
|
||||||
|
<VictoryLegend
|
||||||
data={[
|
data={[
|
||||||
{
|
{name: "(x,y)", symbol: {fill: "blue"}},
|
||||||
x: [0, 1, 2],
|
{name: "F(x,y)", symbol: {fill: "orange"}}
|
||||||
y: [0, 1, 2],
|
|
||||||
type: 'scatter',
|
|
||||||
mode: 'markers',
|
|
||||||
marker: { size: 12 },
|
|
||||||
name: "(x, y)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
x: [1, 2, 3],
|
|
||||||
y: [0, 1, 2],
|
|
||||||
type: 'scatter',
|
|
||||||
mode: 'markers',
|
|
||||||
marker: { size: 12 },
|
|
||||||
name: "(x+1, y)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
x: [0, 1],
|
|
||||||
y: [0, 0],
|
|
||||||
mode: 'lines+markers',
|
|
||||||
marker: {
|
|
||||||
size: 12,
|
|
||||||
symbol: 'arrow-bar-up',
|
|
||||||
angleref: 'previous',
|
|
||||||
color: 'rgb(0,0,0)'
|
|
||||||
},
|
|
||||||
type: 'scatter',
|
|
||||||
showlegend: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
x: [1, 2],
|
|
||||||
y: [1, 1],
|
|
||||||
mode: 'lines+markers',
|
|
||||||
marker: {
|
|
||||||
size: 12,
|
|
||||||
symbol: 'arrow-bar-up',
|
|
||||||
angleref: 'previous',
|
|
||||||
color: 'rgb(0,0,0)'
|
|
||||||
},
|
|
||||||
type: 'scatter',
|
|
||||||
showlegend: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
x: [2, 3],
|
|
||||||
y: [2, 2],
|
|
||||||
mode: 'lines+markers',
|
|
||||||
marker: {
|
|
||||||
size: 12,
|
|
||||||
symbol: 'arrow-bar-up',
|
|
||||||
angleref: 'previous',
|
|
||||||
color: 'rgb(0,0,0)'
|
|
||||||
},
|
|
||||||
type: 'scatter',
|
|
||||||
showlegend: false
|
|
||||||
}
|
|
||||||
]}
|
]}
|
||||||
layout={{
|
orientation={"vertical"}
|
||||||
plot_bgcolor: 'rgba(0,0,0,0)',
|
x={75}
|
||||||
paper_bgcolor: 'rgba(0,0,0,0)'
|
y={10}
|
||||||
}}
|
|
||||||
config={{
|
|
||||||
staticPlot: true
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</center>
|
</VictoryChart>
|
||||||
|
|
||||||
This is a simple example designed to illustrate the principle. In general, $F_i$ functions have the form:
|
This is a simple example designed to illustrate the principle. In general, $F_i$ functions have the form:
|
||||||
|
|
||||||
@ -243,4 +178,11 @@ import Scope from './scope'
|
|||||||
|
|
||||||
import Gasket from '!!raw-loader!./Gasket'
|
import Gasket from '!!raw-loader!./Gasket'
|
||||||
|
|
||||||
<Playground scope={Scope}>{Gasket}</Playground>
|
<Playground scope={Scope} noInline={true}>{Gasket}</Playground>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<small>
|
||||||
|
Note: The image our chaos game generates is different than the fractal flame paper, but I think the version displayed
|
||||||
|
here is correct. As confirmation, the next post will re-create the same image using a different method.
|
||||||
|
</small>
|
@ -3,12 +3,13 @@ import React from 'react';
|
|||||||
import randomBiUnit from './biunit';
|
import randomBiUnit from './biunit';
|
||||||
import plot from './plot';
|
import plot from './plot';
|
||||||
import randomInteger from './randint';
|
import randomInteger from './randint';
|
||||||
|
import Canvas from './Canvas';
|
||||||
|
|
||||||
const Scope = {
|
const Scope = {
|
||||||
React,
|
React,
|
||||||
plot,
|
plot,
|
||||||
randomBiUnit,
|
randomBiUnit,
|
||||||
randomInteger
|
randomInteger,
|
||||||
|
Canvas
|
||||||
}
|
}
|
||||||
export default Scope;
|
export default Scope;
|
@ -23,7 +23,10 @@
|
|||||||
no-repeat;
|
no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark mode for Plotly, copied from https://github.com/plotly/plotly.js/issues/2006#issuecomment-2081535168 */
|
/*
|
||||||
[data-theme='dark'] .plot-container {
|
Inspired by https://github.com/plotly/plotly.js/issues/2006#issuecomment-2081535168,
|
||||||
|
adapted for Victory charts
|
||||||
|
*/
|
||||||
|
[data-theme='dark'] .VictoryContainer {
|
||||||
filter: invert(75%) hue-rotate(180deg);
|
filter: invert(75%) hue-rotate(180deg);
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user