├── .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 /.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/gaearon/react-fractals/533cad46ea817a272a497aeac93ba8ad6ce6e8c7/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": "^15.4.0", 13 | "react-dom": "^15.4.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/gaearon/react-fractals/533cad46ea817a272a497aeac93ba8ad6ce6e8c7/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/gaearon/react-fractals/533cad46ea817a272a497aeac93ba8ad6ce6e8c7/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 | 10 | // borrowed from Vue fork https://github.com/yyx990803/vue-fractal/blob/master/src/App.vue 11 | function throttleWithRAF (fn) { 12 | let running = false 13 | return function () { 14 | if (running) return 15 | running = true 16 | window.requestAnimationFrame(() => { 17 | fn.apply(this, arguments) 18 | running = false 19 | }) 20 | } 21 | } 22 | 23 | class App extends Component { 24 | svg = { 25 | width: 1280, 26 | height: 600 27 | }; 28 | state = { 29 | currentMax: 0, 30 | baseW: 80, 31 | heightFactor: 0, 32 | lean: 0 33 | }; 34 | running = false; 35 | realMax = 11; 36 | 37 | componentDidMount() { 38 | d3select(this.refs.svg).on("mousemove", this.onMouseMove.bind(this)); 39 | 40 | this.next(); 41 | } 42 | 43 | next() { 44 | const { currentMax } = this.state; 45 | 46 | if (currentMax < this.realMax) { 47 | this.setState({currentMax: currentMax + 1}); 48 | setTimeout(this.next.bind(this), 500); 49 | } 50 | } 51 | 52 | // Throttling approach borrowed from Vue fork 53 | // https://github.com/yyx990803/vue-fractal/blob/master/src/App.vue 54 | // rAF makes it slower than just throttling on React update 55 | onMouseMove(event) { 56 | if (this.running) return; 57 | this.running = true; 58 | 59 | const [x, y] = d3mouse(this.refs.svg), 60 | 61 | scaleFactor = scaleLinear().domain([this.svg.height, 0]) 62 | .range([0, .8]), 63 | 64 | scaleLean = scaleLinear().domain([0, this.svg.width/2, this.svg.width]) 65 | .range([.5, 0, -.5]); 66 | 67 | this.setState({ 68 | heightFactor: scaleFactor(y), 69 | lean: scaleLean(x) 70 | }); 71 | this.running = false; 72 | } 73 | 74 | render() { 75 | return ( 76 |
77 |
78 | logo 79 |

This is a dancing Pythagoras tree

80 |
81 |

82 | 84 | 85 | 93 | 94 | 95 |

96 |
97 | ); 98 | } 99 | } 100 | 101 | export default App; 102 | -------------------------------------------------------------------------------- /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 | 2 | import React from 'react'; 3 | import { interpolateViridis } from 'd3-scale'; 4 | 5 | Math.deg = function(radians) { 6 | return radians * (180 / Math.PI); 7 | }; 8 | 9 | const memoizedCalc = function () { 10 | const memo = {}; 11 | 12 | const key = ({ w, heightFactor, lean }) => [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 * (.5+lean))**2), 26 | nextLeft: Math.sqrt(trigH**2 + (w * (.5-lean))**2), 27 | A: Math.deg(Math.atan(trigH / ((.5-lean) * w))), 28 | B: Math.deg(Math.atan(trigH / ((.5+lean) * w))) 29 | }; 30 | 31 | memo[memoKey] = result; 32 | return result; 33 | } 34 | } 35 | }(); 36 | 37 | const Pythagoras = ({ w,x, y, heightFactor, lean, left, right, lvl, maxlvl }) => { 38 | if (lvl >= maxlvl || w < 1) { 39 | return null; 40 | } 41 | 42 | const { nextRight, nextLeft, A, B } = memoizedCalc({ 43 | w: w, 44 | heightFactor: heightFactor, 45 | lean: lean 46 | }); 47 | 48 | let rotate = ''; 49 | 50 | if (left) { 51 | rotate = `rotate(${-A} 0 ${w})`; 52 | }else if (right) { 53 | rotate = `rotate(${B} ${w} ${w})`; 54 | } 55 | 56 | return ( 57 | 58 | 61 | 62 | 68 | 69 | 75 | 76 | 77 | ); 78 | }; 79 | 80 | export default Pythagoras; 81 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------