mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 08:38:09 -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) {
|
||||
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]
|
||||
]
|
||||
// Hint: try increasing the iteration count
|
||||
const iterations = 10000;
|
||||
|
||||
function chaosGame(image) {
|
||||
var [x, y] = [randomBiUnit(), randomBiUnit()];
|
||||
// Hint: negating `x` and `y` also creates some interesting images
|
||||
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++) {
|
||||
const f = functions[randomInteger(0, functions.length)];
|
||||
[x, y] = f(x, y);
|
||||
function chaosGame(image) {
|
||||
var [x, y] = [randomBiUnit(), randomBiUnit()];
|
||||
|
||||
if (i > 20) {
|
||||
plot(x, y, image);
|
||||
}
|
||||
for (var count = 0; count < iterations; count++) {
|
||||
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:
|
||||
|
||||
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>
|
||||
<Plot
|
||||
data={[
|
||||
{
|
||||
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>
|
||||
<VictoryChart theme={VictoryTheme.clean}>
|
||||
<VictoryScatter data={simpleData} size={5} style={{data: {fill: "blue"}}}/>
|
||||
</VictoryChart>
|
||||
|
||||
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.
|
||||
@ -95,74 +83,21 @@ $$
|
||||
|
||||
That is, for an input point $(x, y)$, the output point will be $(x + 1, y)$:
|
||||
|
||||
<center>
|
||||
<Plot
|
||||
export const shiftData = simpleData.map(({x, y}) => { return {x: x + 1, y} })
|
||||
|
||||
<VictoryChart theme={VictoryTheme.clean}>
|
||||
<VictoryScatter data={simpleData} size={5} style={{data: {fill: "blue"}}}/>
|
||||
<VictoryScatter data={shiftData} size={5} style={{data: {fill: "orange"}}}/>
|
||||
<VictoryLegend
|
||||
data={[
|
||||
{
|
||||
x: [0, 1, 2],
|
||||
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
|
||||
}
|
||||
{name: "(x,y)", symbol: {fill: "blue"}},
|
||||
{name: "F(x,y)", symbol: {fill: "orange"}}
|
||||
]}
|
||||
layout={{
|
||||
plot_bgcolor: 'rgba(0,0,0,0)',
|
||||
paper_bgcolor: 'rgba(0,0,0,0)'
|
||||
}}
|
||||
config={{
|
||||
staticPlot: true
|
||||
}}
|
||||
orientation={"vertical"}
|
||||
x={75}
|
||||
y={10}
|
||||
/>
|
||||
</center>
|
||||
</VictoryChart>
|
||||
|
||||
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'
|
||||
|
||||
<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 plot from './plot';
|
||||
import randomInteger from './randint';
|
||||
|
||||
import Canvas from './Canvas';
|
||||
|
||||
const Scope = {
|
||||
React,
|
||||
plot,
|
||||
randomBiUnit,
|
||||
randomInteger
|
||||
randomInteger,
|
||||
Canvas
|
||||
}
|
||||
export default Scope;
|
@ -23,7 +23,10 @@
|
||||
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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user