diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/Canvas.tsx b/blog/2024-11-15-playing-with-fire/1-introduction/Canvas.tsx new file mode 100644 index 0000000..262c50d --- /dev/null +++ b/blog/2024-11-15-playing-with-fire/1-introduction/Canvas.tsx @@ -0,0 +1,34 @@ +import {useEffect, useRef} from "react"; + +interface Props { + renderFn: (ImageData) => void +} + +export const Canvas: React.FC = ({renderFn}: Props) => { + const canvasRef = useRef(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 ( + + ); +}; + +export default Canvas; \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/Gasket.jsx b/blog/2024-11-15-playing-with-fire/1-introduction/Gasket.jsx index 59f25f0..70b21aa 100644 --- a/blog/2024-11-15-playing-with-fire/1-introduction/Gasket.jsx +++ b/blog/2024-11-15-playing-with-fire/1-introduction/Gasket.jsx @@ -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
-
- -
-
-
- -
-
} + +render() diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/1-introduction.mdx b/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx similarity index 70% rename from blog/2024-11-15-playing-with-fire/1-introduction/1-introduction.mdx rename to blog/2024-11-15-playing-with-fire/1-introduction/index.mdx index 8331205..efd01b5 100644 --- a/blog/2024-11-15-playing-with-fire/1-introduction/1-introduction.mdx +++ b/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx @@ -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} +] -
- -
+ + + 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)$: -
- { return {x: x + 1, y} }) + + + + + -
+ 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' -{Gasket} +{Gasket} + +
+ + +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. + \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/scope.ts b/blog/2024-11-15-playing-with-fire/1-introduction/scope.ts index 2d4c411..f020b05 100644 --- a/blog/2024-11-15-playing-with-fire/1-introduction/scope.ts +++ b/blog/2024-11-15-playing-with-fire/1-introduction/scope.ts @@ -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; \ No newline at end of file diff --git a/src/css/custom.css b/src/css/custom.css index 56086c9..7d691f0 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -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); } \ No newline at end of file