Updated gasket, fixed charts

This commit is contained in:
Bradlee Speice 2024-11-17 20:42:42 -05:00
parent 3a41d5c81a
commit f26afcccf9
5 changed files with 89 additions and 128 deletions

View 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;

View File

@ -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}/>)

View File

@ -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>

View File

@ -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;

View File

@ -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);
}