├── .gitignore ├── README.md ├── growing-pythagoras-tree.gif ├── package.json ├── public ├── favicon.ico └── index.html ├── react-tree.gif ├── src ├── App.css ├── App.js ├── App.test.js ├── Pythagoras.js ├── index.css ├── index.js └── logo.svg └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 2 | 3 | # React Fractals 4 | 5 | An experiment in fractalization and component recursion. 6 | 7 | ![](/react-tree.gif) 8 | -------------------------------------------------------------------------------- /growing-pythagoras-tree.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Swizec/react-fractals/628a8a16da8a1836aad0e3c8c55f44a456ab0906/growing-pythagoras-tree.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-fractals", 3 | "version": "0.0.1", 4 | "private": true, 5 | "devDependencies": { 6 | "gh-pages": "^0.12.0", 7 | "react-scripts": "0.7.0" 8 | }, 9 | "dependencies": { 10 | "d3-scale": "^1.0.4", 11 | "d3-selection": "^1.0.3", 12 | "react": "^16.3.0-rc.0", 13 | "react-dom": "^16.3.0-rc.0" 14 | }, 15 | "homepage": "https://swizec.github.io/react-fractals", 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test --env=jsdom", 20 | "eject": "react-scripts eject", 21 | "deploy": "gh-pages -d build" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Swizec/react-fractals/628a8a16da8a1836aad0e3c8c55f44a456ab0906/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | React App 17 | 18 | 19 |
20 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /react-tree.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Swizec/react-fractals/628a8a16da8a1836aad0e3c8c55f44a456ab0906/react-tree.gif -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .App-header { 11 | background-color: #222; 12 | height: 150px; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .App-intro { 18 | font-size: large; 19 | } 20 | 21 | @keyframes App-logo-spin { 22 | from { transform: rotate(0deg); } 23 | to { transform: rotate(360deg); } 24 | } 25 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import logo from "./logo.svg"; 3 | import "./App.css"; 4 | import { select as d3select, mouse as d3mouse } from "d3-selection"; 5 | import { scaleLinear } from "d3-scale"; 6 | 7 | import Pythagoras from "./Pythagoras"; 8 | 9 | class App extends Component { 10 | svg = { 11 | width: 1280, 12 | height: 600 13 | }; 14 | state = { 15 | currentMax: 0, 16 | baseW: 80, 17 | heightFactor: 0, 18 | lean: 0 19 | }; 20 | running = false; 21 | realMax = 11; 22 | 23 | componentDidMount() { 24 | d3select(this.refs.svg).on("mousemove", this.onMouseMove.bind(this)); 25 | 26 | this.next(); 27 | } 28 | 29 | next() { 30 | const { currentMax } = this.state; 31 | 32 | if (currentMax < this.realMax) { 33 | this.setState({ currentMax: currentMax + 1 }); 34 | setTimeout(this.next.bind(this), 500); 35 | } 36 | } 37 | 38 | // Throttling approach borrowed from Vue fork 39 | // https://github.com/yyx990803/vue-fractal/blob/master/src/App.vue 40 | // rAF makes it slower than just throttling on React update 41 | onMouseMove(event) { 42 | if (this.running) return; 43 | this.running = true; 44 | 45 | const [x, y] = d3mouse(this.refs.svg), 46 | scaleFactor = scaleLinear() 47 | .domain([this.svg.height, 0]) 48 | .range([0, 0.8]), 49 | scaleLean = scaleLinear() 50 | .domain([0, this.svg.width / 2, this.svg.width]) 51 | .range([0.5, 0, -0.5]); 52 | 53 | this.setState({ 54 | heightFactor: scaleFactor(y), 55 | lean: scaleLean(x) 56 | }); 57 | this.running = false; 58 | } 59 | 60 | render() { 61 | return ( 62 |
63 |
64 | logo 65 |

This is a dancing Pythagoras tree

66 |
67 |

68 | 74 | 84 | 85 |

86 |
87 | ); 88 | } 89 | } 90 | 91 | export default App; 92 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /src/Pythagoras.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { interpolateViridis } from "d3-scale"; 3 | 4 | Math.deg = function(radians) { 5 | return radians * (180 / Math.PI); 6 | }; 7 | 8 | const memoizedCalc = (function() { 9 | const memo = {}; 10 | 11 | const key = ({ w, heightFactor, lean }) => 12 | [w, heightFactor, lean].join("-"); 13 | 14 | return args => { 15 | const memoKey = key(args); 16 | 17 | if (memo[memoKey]) { 18 | return memo[memoKey]; 19 | } else { 20 | const { w, heightFactor, lean } = args; 21 | 22 | const trigH = heightFactor * w; 23 | 24 | const result = { 25 | nextRight: Math.sqrt(trigH ** 2 + (w * (0.5 - lean)) ** 2), 26 | nextLeft: Math.sqrt(trigH ** 2 + (w * (0.5 + lean)) ** 2), 27 | A: Math.deg(Math.atan(trigH / ((0.5 + lean) * w))), 28 | B: Math.deg(Math.atan(trigH / ((0.5 - lean) * w))) 29 | }; 30 | 31 | memo[memoKey] = result; 32 | return result; 33 | } 34 | }; 35 | })(); 36 | 37 | const Pythagoras = ({ 38 | w, 39 | x, 40 | y, 41 | heightFactor, 42 | lean, 43 | left, 44 | right, 45 | lvl, 46 | maxlvl 47 | }) => { 48 | if (lvl >= maxlvl || w < 1) { 49 | return null; 50 | } 51 | 52 | const { nextRight, nextLeft, A, B } = memoizedCalc({ 53 | w: w, 54 | heightFactor: heightFactor, 55 | lean: lean 56 | }); 57 | 58 | let rotate = ""; 59 | 60 | if (left) { 61 | rotate = `rotate(${-A} 0 ${w})`; 62 | } else if (right) { 63 | rotate = `rotate(${B} ${w} ${w})`; 64 | } 65 | 66 | return ( 67 | 68 | 75 | 76 | 86 | 87 | 97 | 98 | ); 99 | }; 100 | 101 | export default Pythagoras; 102 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import './index.css'; 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('root') 9 | ); 10 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | --------------------------------------------------------------------------------