Rework of the app using Svelte
This commit is contained in:
		
							parent
							
								
									b31eb91864
								
							
						
					
					
						commit
						c367880ec8
					
				
							
								
								
									
										44
									
								
								.eslintrc
								
								
								
								
							
							
						
						
									
										44
									
								
								.eslintrc
								
								
								
								
							|  | @ -1,44 +0,0 @@ | |||
| { | ||||
|     "extends": "eslint:recommended", | ||||
| 
 | ||||
|     "rules": { | ||||
|         "no-shadow": 2, | ||||
|         "no-catch-shadow": 2, | ||||
|         "no-shadow-restricted-names": 2, | ||||
|         "radix": 2, | ||||
| 
 | ||||
|         "wrap-iife": 2, | ||||
|         "yoda": 2, | ||||
|         "semi": 2, | ||||
|         "indent": 2, | ||||
|         "camelcase": 2, | ||||
|         "brace-style": 2, | ||||
|         "comma-spacing": 2, | ||||
|         "comma-style": 2, | ||||
|         "quotes": [2, "single", "avoid-escape"], | ||||
|         "no-spaced-func": 2, | ||||
|         "space-after-keywords": 2, | ||||
|         "space-before-blocks": 2, | ||||
|         "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}], | ||||
|         "space-in-parens": 2, | ||||
|         "space-infix-ops": 2, | ||||
|         "space-return-throw-case": 2, | ||||
|         "space-unary-ops": 2, | ||||
|         "no-trailing-spaces": 0, | ||||
|         "no-underscore-dangle": 0 | ||||
|     }, | ||||
| 
 | ||||
|     "ecmaFeatures": { | ||||
|         "modules": true, | ||||
|         "jsx": true | ||||
|     }, | ||||
| 
 | ||||
|     "plugins": [ | ||||
|         "react" | ||||
|     ], | ||||
| 
 | ||||
|     "env": { | ||||
|         "es6": true, | ||||
|         "browser": true | ||||
|     } | ||||
| } | ||||
|  | @ -1 +1,3 @@ | |||
| *.map | ||||
| node_modules | ||||
| public/bundle.* | ||||
|  |  | |||
|  | @ -1,56 +0,0 @@ | |||
| # Contribuer | ||||
| 
 | ||||
| [Looking for the english version?](CONTRIBUTING.md) | ||||
| 
 | ||||
| Merci de votre intérêt à contribuer à ce code !   | ||||
| Toutes les contributions (même les plus petites) sont les bienvenues. | ||||
| Pour garder une certaine cohérence dans le code, merci de suivre | ||||
| ces quelques règles. | ||||
| 
 | ||||
| ## 1. Commit tags | ||||
| 
 | ||||
| Tous les commits doivent être précédés d'emojis dans la mesure | ||||
| du possible pour que la liste des commits soit plus lisible. | ||||
| 
 | ||||
| | Emoji      | Type de commit                   | | ||||
| |:----------:|:---------------------------------| | ||||
| | :book:     | Changement dans la documentation | | ||||
| | :bug:      | Correction de bug                | | ||||
| | :ledger:   | Déplacement de fichiers          | | ||||
| | :bulb:     | Nouvelles fonctionnalités        | | ||||
| | :lipstick: | Correction du style de code      | | ||||
| 
 | ||||
| ## 2. Branches | ||||
| 
 | ||||
| Merci d'utiliser un nom de branche différent de | ||||
| `master` pour vos pull requests, pour que l'historique | ||||
| soit plus lisible. | ||||
| 
 | ||||
| Par exemple, pour améliorer la documentation, vous | ||||
| pourriez choisir le nom `improve-docs`. | ||||
| 
 | ||||
| ## 3. Conventions de style | ||||
| 
 | ||||
| Le code Javascript peut être écrit suivant | ||||
| [beaucoup](https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml) | ||||
| [de](https://github.com/airbnb/javascript) | ||||
| [styles](https://github.com/felixge/node-style-guide) | ||||
| [différents](https://contribute.jquery.org/style-guide/js/) | ||||
| mais nous ne sommes pas aussi durs que ceux-ci. | ||||
| 
 | ||||
| La règle principale est d'utiliser [ESLint](http://eslint.org/) pour | ||||
| vérifier si votre code s'accorde avec nos conventions de style. | ||||
| Voici quelques unes des règles : | ||||
| 
 | ||||
| * utiliser le paramètre de base dans `parseInt()`; | ||||
| * utiliser le *one true brace style;* | ||||
| * mettre un espace après les virgules, pas d'espace avant; | ||||
| * mettre les virgules à la fin des lignes de préférence; | ||||
| * utiliser des guillemets simples; | ||||
| * écrire en camelcase; | ||||
| * utiliser 4 espaces pour l'indentation. | ||||
| 
 | ||||
| ## 4. Langue | ||||
| 
 | ||||
| De préférence, les noms de variables, fonctions, le texte des commentaires, | ||||
| les descriptions de commits doivent être écrits en *anglais.* | ||||
|  | @ -1,52 +0,0 @@ | |||
| # Contributing | ||||
| 
 | ||||
| [Voir ceci en français](CONTRIBUTING.fr.md) | ||||
| 
 | ||||
| Thank you for your interest in contributing to this repo!   | ||||
| All contributions (even small ones) are welcome. | ||||
| In order to keep this repo consistent, please | ||||
| try to follow these rules. | ||||
| 
 | ||||
| ## 1. Commit tags | ||||
| 
 | ||||
| All commits should be tagged with emojis whenever possible | ||||
| to make the commit list more readable. | ||||
| 
 | ||||
| | Emoji      | Commit content        | | ||||
| |:----------:|:--------------------- | | ||||
| | :book:     | Documentation updates | | ||||
| | :bug:      | Bug fixes             | | ||||
| | :ledger:   | Moving files          | | ||||
| | :bulb:     | New features          | | ||||
| | :lipstick: | Fixing coding style   | | ||||
| 
 | ||||
| ## 2. Branches | ||||
| 
 | ||||
| Please use a branch name that differs from `master` | ||||
| when making pull requests, so that the network | ||||
| history is more readable. | ||||
| 
 | ||||
| For example, if you wanted to fix the issue | ||||
| "improve documentation", you could have | ||||
| chosen the following branch name: `improve-docs`. | ||||
| 
 | ||||
| ## 3. Coding style | ||||
| 
 | ||||
| Javascript can be authored by following | ||||
| [a](https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml) | ||||
| [lot](https://github.com/airbnb/javascript) | ||||
| [of](https://github.com/felixge/node-style-guide) | ||||
| [different](https://contribute.jquery.org/style-guide/js/) | ||||
| [style guides](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Coding_Style) | ||||
| but we decided to be a bit soft on that. | ||||
| 
 | ||||
| As a rule of thumb, use [ESLint](http://eslint.org/) to check if your code complies | ||||
| with our style conventions. Here are some of the rules: | ||||
| 
 | ||||
| * use the radix parameter in `parseInt()` calls; | ||||
| * use the *one true brace style;* | ||||
| * put one space after commas, and no space before; | ||||
| * put your comma at the end of the lines; | ||||
| * use simple quotes; | ||||
| * use camelcase; | ||||
| * use 4 spaces for indentation. | ||||
							
								
								
									
										121
									
								
								COPYING
								
								
								
								
							
							
						
						
									
										121
									
								
								COPYING
								
								
								
								
							|  | @ -1,121 +0,0 @@ | |||
| Creative Commons Legal Code | ||||
| 
 | ||||
| CC0 1.0 Universal | ||||
| 
 | ||||
|     CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE | ||||
|     LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN | ||||
|     ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS | ||||
|     INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES | ||||
|     REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS | ||||
|     PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM | ||||
|     THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED | ||||
|     HEREUNDER. | ||||
| 
 | ||||
| Statement of Purpose | ||||
| 
 | ||||
| The laws of most jurisdictions throughout the world automatically confer | ||||
| exclusive Copyright and Related Rights (defined below) upon the creator | ||||
| and subsequent owner(s) (each and all, an "owner") of an original work of | ||||
| authorship and/or a database (each, a "Work"). | ||||
| 
 | ||||
| Certain owners wish to permanently relinquish those rights to a Work for | ||||
| the purpose of contributing to a commons of creative, cultural and | ||||
| scientific works ("Commons") that the public can reliably and without fear | ||||
| of later claims of infringement build upon, modify, incorporate in other | ||||
| works, reuse and redistribute as freely as possible in any form whatsoever | ||||
| and for any purposes, including without limitation commercial purposes. | ||||
| These owners may contribute to the Commons to promote the ideal of a free | ||||
| culture and the further production of creative, cultural and scientific | ||||
| works, or to gain reputation or greater distribution for their Work in | ||||
| part through the use and efforts of others. | ||||
| 
 | ||||
| For these and/or other purposes and motivations, and without any | ||||
| expectation of additional consideration or compensation, the person | ||||
| associating CC0 with a Work (the "Affirmer"), to the extent that he or she | ||||
| is an owner of Copyright and Related Rights in the Work, voluntarily | ||||
| elects to apply CC0 to the Work and publicly distribute the Work under its | ||||
| terms, with knowledge of his or her Copyright and Related Rights in the | ||||
| Work and the meaning and intended legal effect of CC0 on those rights. | ||||
| 
 | ||||
| 1. Copyright and Related Rights. A Work made available under CC0 may be | ||||
| protected by copyright and related or neighboring rights ("Copyright and | ||||
| Related Rights"). Copyright and Related Rights include, but are not | ||||
| limited to, the following: | ||||
| 
 | ||||
|   i. the right to reproduce, adapt, distribute, perform, display, | ||||
|      communicate, and translate a Work; | ||||
|  ii. moral rights retained by the original author(s) and/or performer(s); | ||||
| iii. publicity and privacy rights pertaining to a person's image or | ||||
|      likeness depicted in a Work; | ||||
|  iv. rights protecting against unfair competition in regards to a Work, | ||||
|      subject to the limitations in paragraph 4(a), below; | ||||
|   v. rights protecting the extraction, dissemination, use and reuse of data | ||||
|      in a Work; | ||||
|  vi. database rights (such as those arising under Directive 96/9/EC of the | ||||
|      European Parliament and of the Council of 11 March 1996 on the legal | ||||
|      protection of databases, and under any national implementation | ||||
|      thereof, including any amended or successor version of such | ||||
|      directive); and | ||||
| vii. other similar, equivalent or corresponding rights throughout the | ||||
|      world based on applicable law or treaty, and any national | ||||
|      implementations thereof. | ||||
| 
 | ||||
| 2. Waiver. To the greatest extent permitted by, but not in contravention | ||||
| of, applicable law, Affirmer hereby overtly, fully, permanently, | ||||
| irrevocably and unconditionally waives, abandons, and surrenders all of | ||||
| Affirmer's Copyright and Related Rights and associated claims and causes | ||||
| of action, whether now known or unknown (including existing as well as | ||||
| future claims and causes of action), in the Work (i) in all territories | ||||
| worldwide, (ii) for the maximum duration provided by applicable law or | ||||
| treaty (including future time extensions), (iii) in any current or future | ||||
| medium and for any number of copies, and (iv) for any purpose whatsoever, | ||||
| including without limitation commercial, advertising or promotional | ||||
| purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each | ||||
| member of the public at large and to the detriment of Affirmer's heirs and | ||||
| successors, fully intending that such Waiver shall not be subject to | ||||
| revocation, rescission, cancellation, termination, or any other legal or | ||||
| equitable action to disrupt the quiet enjoyment of the Work by the public | ||||
| as contemplated by Affirmer's express Statement of Purpose. | ||||
| 
 | ||||
| 3. Public License Fallback. Should any part of the Waiver for any reason | ||||
| be judged legally invalid or ineffective under applicable law, then the | ||||
| Waiver shall be preserved to the maximum extent permitted taking into | ||||
| account Affirmer's express Statement of Purpose. In addition, to the | ||||
| extent the Waiver is so judged Affirmer hereby grants to each affected | ||||
| person a royalty-free, non transferable, non sublicensable, non exclusive, | ||||
| irrevocable and unconditional license to exercise Affirmer's Copyright and | ||||
| Related Rights in the Work (i) in all territories worldwide, (ii) for the | ||||
| maximum duration provided by applicable law or treaty (including future | ||||
| time extensions), (iii) in any current or future medium and for any number | ||||
| of copies, and (iv) for any purpose whatsoever, including without | ||||
| limitation commercial, advertising or promotional purposes (the | ||||
| "License"). The License shall be deemed effective as of the date CC0 was | ||||
| applied by Affirmer to the Work. Should any part of the License for any | ||||
| reason be judged legally invalid or ineffective under applicable law, such | ||||
| partial invalidity or ineffectiveness shall not invalidate the remainder | ||||
| of the License, and in such case Affirmer hereby affirms that he or she | ||||
| will not (i) exercise any of his or her remaining Copyright and Related | ||||
| Rights in the Work or (ii) assert any associated claims and causes of | ||||
| action with respect to the Work, in either case contrary to Affirmer's | ||||
| express Statement of Purpose. | ||||
| 
 | ||||
| 4. Limitations and Disclaimers. | ||||
| 
 | ||||
|  a. No trademark or patent rights held by Affirmer are waived, abandoned, | ||||
|     surrendered, licensed or otherwise affected by this document. | ||||
|  b. Affirmer offers the Work as-is and makes no representations or | ||||
|     warranties of any kind concerning the Work, express, implied, | ||||
|     statutory or otherwise, including without limitation warranties of | ||||
|     title, merchantability, fitness for a particular purpose, non | ||||
|     infringement, or the absence of latent or other defects, accuracy, or | ||||
|     the present or absence of errors, whether or not discoverable, all to | ||||
|     the greatest extent permissible under applicable law. | ||||
|  c. Affirmer disclaims responsibility for clearing rights of other persons | ||||
|     that may apply to the Work or any use thereof, including without | ||||
|     limitation any person's Copyright and Related Rights in the Work. | ||||
|     Further, Affirmer disclaims responsibility for obtaining any necessary | ||||
|     consents, permissions or other rights required for any use of the | ||||
|     Work. | ||||
|  d. Affirmer understands and acknowledges that Creative Commons is not a | ||||
|     party to this document and has no duty or obligation with respect to | ||||
|     this CC0 or use of the Work. | ||||
							
								
								
									
										19
									
								
								README.fr.md
								
								
								
								
							
							
						
						
									
										19
									
								
								README.fr.md
								
								
								
								
							|  | @ -1,19 +0,0 @@ | |||
| # Chaos | ||||
| 
 | ||||
| [Looking for the english version?](README.md) | ||||
| 
 | ||||
| Créer des fractales avec le jeu du chaos. | ||||
| 
 | ||||
| == À FAIRE == | ||||
| 
 | ||||
| ## Contribuer | ||||
| 
 | ||||
| Toutes les informations sont dans le | ||||
| [guide du contributeur.](https://github.com/matteodelabre/chaos/blob/master/CONTRIBUTING.fr.md) | ||||
| 
 | ||||
| ## Licence | ||||
| 
 | ||||
| Chaos ― Créer des fractales avec le jeu du chaos.   | ||||
| Écrit en 2013 ― 2015 par Mattéo Delabre ([bonjour@matteodelabre.me](mailto:bonjour@matteodelabre.me)).   | ||||
| Dans la mesure permise par la loi, l'auteur dédie mondialement tous ses droits d'auteur et droits voisins sur ce logiciel au **domaine public.** Ce logiciel est distribué sans aucune garantie.   | ||||
| Vous devriez avoir reçu une copie du *CC0 Domain Dedication* avec ce logiciel. Sinon, voir http://creativecommons.org/publicdomain/zero/1.0/. | ||||
							
								
								
									
										18
									
								
								README.md
								
								
								
								
							
							
						
						
									
										18
									
								
								README.md
								
								
								
								
							|  | @ -1,18 +0,0 @@ | |||
| # Chaos | ||||
| 
 | ||||
| [Voir ceci en français](README.fr.md) | ||||
| 
 | ||||
| Creating fractals with the chaos game. | ||||
| 
 | ||||
| == TODO == | ||||
| 
 | ||||
| ## Contributing | ||||
| 
 | ||||
| Check out the [contribution guide.](https://github.com/matteodelabre/chaos/blob/master/CONTRIBUTING.md) | ||||
| 
 | ||||
| ## License | ||||
| 
 | ||||
| Chaos ― Creating fractals with the chaos game.   | ||||
| Written in 2013 ― 2015 by Mattéo Delabre ([bonjour@matteodelabre.me](mailto:bonjour@matteodelabre.me)).   | ||||
| To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the **public domain** worldwide. This software is distributed without any warranty. | ||||
| You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see http://creativecommons.org/publicdomain/zero/1.0/. | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 71 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 1.3 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 1.8 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 1.7 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 1.8 KiB | 
							
								
								
									
										17
									
								
								index.html
								
								
								
								
							
							
						
						
									
										17
									
								
								index.html
								
								
								
								
							|  | @ -1,17 +0,0 @@ | |||
| <!DOCTYPE html> | ||||
| <html lang="fr"> | ||||
| 	<head> | ||||
| 		<meta charset="utf-8"> | ||||
| 		<title>Chaos game</title> | ||||
| 
 | ||||
|         <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700,400italic|Playfair+Display:400,400italic,700" rel="stylesheet"> | ||||
|         <link href="https://matteodelabre.me/styles/common.css" rel="stylesheet"> | ||||
|         <link href="https://matteodelabre.me/styles/demos.css" rel="stylesheet"> | ||||
| 		<link href="styles/index.css" rel="stylesheet"> | ||||
| 	</head> | ||||
| 	<body> | ||||
|         <div id="react"></div> | ||||
| 
 | ||||
|         <script src="bundle.js"></script> | ||||
| 	</body> | ||||
| </html> | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										40
									
								
								package.json
								
								
								
								
							
							
						
						
									
										40
									
								
								package.json
								
								
								
								
							|  | @ -1,33 +1,23 @@ | |||
| { | ||||
|   "name": "chaos", | ||||
|   "version": "1.0.0", | ||||
|   "description": "Plotting fractals with the chaos game", | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "git+https://github.com/matteodelabre/chaos.git" | ||||
|   "version": "2.0.0", | ||||
|   "description": "Exploring the chaos game", | ||||
|   "license": "CC0", | ||||
|   "private": true, | ||||
|   "devDependencies": { | ||||
|     "rollup": "^1.17.0", | ||||
|     "rollup-plugin-commonjs": "^10.0.1", | ||||
|     "rollup-plugin-livereload": "^1.0.1", | ||||
|     "rollup-plugin-node-resolve": "^5.2.0", | ||||
|     "rollup-plugin-svelte": "^5.1.0", | ||||
|     "rollup-plugin-terser": "^5.1.1", | ||||
|     "svelte": "^3.6.9" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "build": "browserify -t [ babelify --presets [ es2015 react ] ] scripts/index.js | babel --presets es2015 > bundle.js" | ||||
|     "build": "rollup -c", | ||||
|     "autobuild": "rollup -c -w" | ||||
|   }, | ||||
|   "keywords": [ | ||||
|     "chaos", | ||||
|     "fractals", | ||||
|     "game" | ||||
|   ], | ||||
|   "author": "Mattéo Delabre", | ||||
|   "license": "CC0-1.0", | ||||
|   "bugs": { | ||||
|     "url": "https://github.com/matteodelabre/chaos/issues" | ||||
|   }, | ||||
|   "homepage": "https://github.com/matteodelabre/chaos#readme", | ||||
|   "dependencies": { | ||||
|     "babel-cli": "^6.3.17", | ||||
|     "babel-preset-es2015": "^6.3.13", | ||||
|     "babel-preset-react": "^6.3.13", | ||||
|     "babelify": "^7.2.0", | ||||
|     "browserify": "^12.0.1", | ||||
|     "react": "^0.14.3", | ||||
|     "react-dom": "^0.14.3", | ||||
|     "the-dom": "^0.1.0" | ||||
|     "ml-matrix": "^6.2.0" | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,16 @@ | |||
| body, html | ||||
| { | ||||
|     height: 100%; | ||||
|     overflow: hidden; | ||||
| 
 | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
|     outline: 0; | ||||
| 
 | ||||
|     --easing: cubic-bezier(0.4, 0.0, 0.2, 1); | ||||
| } | ||||
| 
 | ||||
| *, *::before, *::after | ||||
| { | ||||
|     box-sizing: border-box; | ||||
| } | ||||
|  | @ -0,0 +1,17 @@ | |||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <head> | ||||
|     <meta charset="utf8"> | ||||
|     <meta name="viewport" content="width=device-width"> | ||||
| 
 | ||||
|     <title>Chaos game</title> | ||||
| 
 | ||||
|     <link rel="stylesheet" href="/global.css"> | ||||
|     <link rel="stylesheet" href="/bundle.css"> | ||||
| 
 | ||||
|     <script defer src="/bundle.js"></script> | ||||
| </head> | ||||
| 
 | ||||
| <body> | ||||
| </body> | ||||
| </html> | ||||
|  | @ -0,0 +1,42 @@ | |||
| import svelte from 'rollup-plugin-svelte'; | ||||
| import resolve from 'rollup-plugin-node-resolve'; | ||||
| import commonjs from 'rollup-plugin-commonjs'; | ||||
| import livereload from 'rollup-plugin-livereload'; | ||||
| import { terser } from 'rollup-plugin-terser'; | ||||
| 
 | ||||
| const production = !process.env.ROLLUP_WATCH; | ||||
| 
 | ||||
| export default { | ||||
|     input: 'src/main.js', | ||||
|     output: { | ||||
|         sourcemap: true, | ||||
|         format: 'iife', | ||||
|         name: 'app', | ||||
|         file: 'public/bundle.js' | ||||
|     }, | ||||
|     plugins: [ | ||||
|         svelte({ | ||||
|             dev: !production, | ||||
|             css: css => css.write('public/bundle.css') | ||||
|         }), | ||||
| 
 | ||||
|         resolve({ | ||||
|             browser: true, | ||||
|             dedupe: importee => importee === 'svelte' | ||||
|                  || importee.startsWith('svelte/') | ||||
|         }), | ||||
| 
 | ||||
|         commonjs(), | ||||
| 
 | ||||
|         // In development mode, watch the `public` directory
 | ||||
|         // and refresh the browser on changes
 | ||||
|         !production && livereload('public'), | ||||
| 
 | ||||
|         // In production, minify
 | ||||
|         production && terser() | ||||
|     ], | ||||
|     watch: { | ||||
|         chokidar: false, | ||||
|         clearScreen: false | ||||
|     } | ||||
| }; | ||||
|  | @ -1,26 +0,0 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| import * as React from 'react'; | ||||
| import { Controls } from './controls'; | ||||
| import { Fractal } from './fractal'; | ||||
| import { sierpinski } from './ifs'; | ||||
| 
 | ||||
| /** | ||||
|  * Render the app sidebar and fractal display | ||||
|  */ | ||||
| export class App extends React.Component { | ||||
|     constructor(props) { | ||||
|         super(props); | ||||
| 
 | ||||
|         this.state = { | ||||
| 
 | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         return <div className="split"> | ||||
|             <Controls /> | ||||
|             <Fractal system={sierpinski} /> | ||||
|         </div>; | ||||
|     } | ||||
| } | ||||
|  | @ -1,48 +0,0 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| /** | ||||
|  * Choose an index at random among a list of weights, | ||||
|  * more weighted indices have a greater proability to be chosen | ||||
|  * | ||||
|  * @param {Array} weights List of weights | ||||
|  * @return {number} Selected index | ||||
|  */ | ||||
| const chooseIndex = weights => { | ||||
|     const number = Math.random(); | ||||
|     let sum = 0, index = 0; | ||||
| 
 | ||||
|     while (number >= sum) { | ||||
|         sum += weights[index]; | ||||
|         index += 1; | ||||
|     } | ||||
| 
 | ||||
|     return index - 1; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Starting from `point`, generate `iterations` points | ||||
|  * by applying randomly-chosen transformations | ||||
|  * | ||||
|  * @param {Array} point Starting point | ||||
|  * @param {number} iterations Number of points to plot | ||||
|  * @param {Array} transforms List of available transforms | ||||
|  * @param {Array} weights Probability weights for each transform | ||||
|  * @return {Array} Generated points | ||||
|  */ | ||||
| export const applyChaos = (point, iterations, transforms, weights) => { | ||||
|     const points = []; | ||||
| 
 | ||||
|     if (weights === undefined) { | ||||
|         weights = Array.apply(null, Array(transforms.length)).map( | ||||
|             () => 1 / transforms.length | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     while (iterations--) { | ||||
|         const index = chooseIndex(weights); | ||||
|         point = transforms[index](point); | ||||
|         points.push(point); | ||||
|     } | ||||
| 
 | ||||
|     return points; | ||||
| }; | ||||
|  | @ -1,34 +0,0 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| import * as React from 'react'; | ||||
| 
 | ||||
| /** | ||||
|  * Render app controls | ||||
|  */ | ||||
| export class Controls extends React.Component { | ||||
|     constructor(props) { | ||||
|         super(props); | ||||
| 
 | ||||
|         this.state = { | ||||
| 
 | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         return <aside> | ||||
|             <header> | ||||
|                 <a href="/"> | ||||
|                     <img src="images/avatar.jpg" alt="Photo de Mattéo" /> | ||||
|                     <span>Mattéo Delabre ✏️</span><br /> | ||||
|                     Back to home | ||||
|                 </a> | ||||
|             </header> | ||||
| 
 | ||||
|             <h1>The Chaos Game</h1> | ||||
|             <h3>Creating fractals with the chaos game</h3> | ||||
| 
 | ||||
|             Sommets <input id="vertices" type="range" min="3" max="12" step="1" defaultValue="3" /><br /> | ||||
|             Fraction 1/<input id="fraction" type="range" min="1" max="6" step="0.01" defaultValue="2" /> | ||||
|         </aside>; | ||||
|     } | ||||
| } | ||||
|  | @ -1,246 +0,0 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| import * as React from 'react'; | ||||
| import { applyChaos } from './chaos'; | ||||
| 
 | ||||
| /** | ||||
|  * Render a canvas element that draws a fractal out of a | ||||
|  * given iterated function system | ||||
|  */ | ||||
| class Fractal extends React.Component { | ||||
|     constructor(props) { | ||||
|         super(props); | ||||
| 
 | ||||
|         this.state = { | ||||
|             zoom: 30, | ||||
|             dragging: false, | ||||
|             center: null, | ||||
|             points: null | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Adjust zooming level and center so that wheeling the mouse | ||||
|      * on given point zooms around it | ||||
|      * | ||||
|      * @param {WheelEvent} event Mouse event | ||||
|      * @return {null} | ||||
|      */ | ||||
|     wheel(event) { | ||||
|         const zoom = this.state.zoom; | ||||
|         const center = this.state.center; | ||||
|         const height = this.ctx.canvas.height; | ||||
| 
 | ||||
|         const delta = event.deltaMode === 0 ? event.deltaY / 53 : event.deltaY; | ||||
|         const newZoom = zoom * Math.max(0, 1 - delta * .035); | ||||
| 
 | ||||
|         // which (unprojected) point does the mouse point on?
 | ||||
|         const mouse = [ | ||||
|             (event.nativeEvent.offsetX - center[0]) / zoom, | ||||
|             (height - event.nativeEvent.offsetY - center[1]) / zoom | ||||
|         ]; | ||||
| 
 | ||||
|         // we need to set the center so that `mouse` stays at
 | ||||
|         // the same position on screen, i.e (vectorially):
 | ||||
|         //    mouse * newZoom + newCenter = mouse * zoom + center
 | ||||
|         // => newCenter = mouse * zoom - mouse * newZoom + center
 | ||||
|         this.setState({ | ||||
|             zoom: newZoom, | ||||
|             center: [ | ||||
|                 mouse[0] * zoom - mouse[0] * newZoom + center[0], | ||||
|                 mouse[1] * zoom - mouse[1] * newZoom + center[1] | ||||
|             ] | ||||
|         }, this.draw); | ||||
| 
 | ||||
|         event.preventDefault(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Save the mouse coordinates when we click down, | ||||
|      * for use by `mouseMove()` | ||||
|      * | ||||
|      * @param {MouseEvent} event Mouse event | ||||
|      * @return {null} | ||||
|      */ | ||||
|     mouseDown(event) { | ||||
|         this.setState({ | ||||
|             dragging: [ | ||||
|                 event.nativeEvent.offsetX, | ||||
|                 event.nativeEvent.offsetY | ||||
|             ] | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Save the fact that the mouse was released | ||||
|      * | ||||
|      * @return {null} | ||||
|      */ | ||||
|     mouseUp() { | ||||
|         this.setState({ | ||||
|             dragging: false | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * When moving the mouse while clicking, pan the figure | ||||
|      * | ||||
|      * @param {MouseEvent} event Mouse event | ||||
|      * @return {null} | ||||
|      */ | ||||
|     mouseMove(event) { | ||||
|         const dragging = this.state.dragging; | ||||
|         const center = this.state.center; | ||||
| 
 | ||||
|         if (dragging !== false) { | ||||
|             const newMouse = [ | ||||
|                 event.nativeEvent.offsetX, | ||||
|                 event.nativeEvent.offsetY | ||||
|             ]; | ||||
| 
 | ||||
|             const movement = [ | ||||
|                 newMouse[0] - dragging[0], | ||||
|                 newMouse[1] - dragging[1] | ||||
|             ]; | ||||
| 
 | ||||
|             // move the center by given offset and redraw
 | ||||
|             this.setState({ | ||||
|                 dragging: newMouse, | ||||
|                 center: [ | ||||
|                     center[0] + movement[0], | ||||
|                     center[1] - movement[1] | ||||
|                 ] | ||||
|             }, this.draw); | ||||
| 
 | ||||
|             event.preventDefault(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Redraw `points` on the canvas, scaled with given | ||||
|      * `zoom` and based on current `center` | ||||
|      * | ||||
|      * @return {null} | ||||
|      */ | ||||
|     draw() { | ||||
|         const width = this.ctx.canvas.width; | ||||
|         const height = this.ctx.canvas.height; | ||||
|         const zoom = this.state.zoom; | ||||
|         const center = this.state.center; | ||||
|         const points = this.state.points; | ||||
| 
 | ||||
|         // do not plot (very) small sizes
 | ||||
|         if (width < 1) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.ctx.clearRect(0, 0, width, height); | ||||
| 
 | ||||
|         // fill each point in `points` skipping the first 50 ones
 | ||||
|         const image = this.ctx.getImageData(0, 0, width, height); | ||||
|         const length = points.length; | ||||
|         const color = [0, 0, 0]; | ||||
| 
 | ||||
|         for (let i = 50; i < length; i += 1) { | ||||
|             const x = Math.floor(points[i][0] * zoom + center[0]); | ||||
|             const y = height - Math.floor(points[i][1] * zoom + center[1]); | ||||
| 
 | ||||
|             if (x >= 0 && x < width && y >= 0 && y < height) { | ||||
|                 const index = (y * width + x) * 4; | ||||
| 
 | ||||
|                 image.data[index] = color[0]; | ||||
|                 image.data[index + 1] = color[1]; | ||||
|                 image.data[index + 2] = color[2]; | ||||
|                 image.data[index + 3] = 255; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         this.ctx.putImageData(image, 0, 0); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the container size | ||||
|      * | ||||
|      * @return {Array} Width and height of the container | ||||
|      */ | ||||
|     getSize() { | ||||
|         return [this.container.clientWidth, this.container.clientHeight]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Update the canvas size to its parent size | ||||
|      * and redraw | ||||
|      * | ||||
|      * @return {null} | ||||
|      */ | ||||
|     resize() { | ||||
|         [this.ctx.canvas.width, this.ctx.canvas.height] = this.getSize(); | ||||
|         this.draw(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Calculate points with current system | ||||
|      * | ||||
|      * @return {Array} Points to be drawn | ||||
|      */ | ||||
|     calculate() { | ||||
|         return applyChaos( | ||||
|             [0, 0], | ||||
|             this.props.iterations, | ||||
|             ...this.props.system | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Setup resize listener and make initial drawing | ||||
|      * when the component has been mounted | ||||
|      * | ||||
|      * @return {null} | ||||
|      */ | ||||
|     componentDidMount() { | ||||
|         window.addEventListener('resize', this.resize.bind(this)); | ||||
| 
 | ||||
|         this.setState({ | ||||
|             center: this.getSize().map(x => Math.floor(x / 2)), | ||||
|             points: this.calculate() | ||||
|         }, this.resize.bind(this)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Remove the resize listener before unmounting the component | ||||
|      * | ||||
|      * @return {null} | ||||
|      */ | ||||
|     componentWillUnmount() { | ||||
|         window.removeEventListener('resize', this.resize.bind(this)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Never create a new canvas | ||||
|      */ | ||||
|     shouldComponentUpdate() { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Create a canvas with correct listeners | ||||
|      */ | ||||
|     render() { | ||||
|         return <div id="content" ref={div => this.container = div}> | ||||
|             <canvas | ||||
|                 ref={canvas => this.ctx = canvas.getContext('2d')} | ||||
|                 onWheel={this.wheel.bind(this)} | ||||
|                 onMouseDown={this.mouseDown.bind(this)} | ||||
|                 onMouseUp={this.mouseUp.bind(this)} | ||||
|                 onMouseMove={this.mouseMove.bind(this)} | ||||
|             ></canvas> | ||||
|         </div>; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| Fractal.defaultProps = { | ||||
|     iterations: 200000, | ||||
|     system: [[], []] | ||||
| }; | ||||
| 
 | ||||
| export { Fractal }; | ||||
|  | @ -1,41 +0,0 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| const linearTransform = (a, b, c, d, e, f) => point => [ | ||||
|     a * point[0] + b * point[1] + e, | ||||
|     c * point[0] + d * point[1] + f | ||||
| ]; | ||||
| 
 | ||||
| const polygonTransforms = (vertices, frac) => vertices.map( | ||||
|     vertex => linearTransform( | ||||
|         frac, 0, 0, frac, | ||||
|         vertex[0] * (frac - 1), vertex[1] * (frac - 1) | ||||
|     ) | ||||
| ); | ||||
| 
 | ||||
| const regularVertices = count => { | ||||
|     var step = 2 * Math.PI / count; | ||||
|     var initial = -Math.atan(Math.sin(step) / (Math.cos(step) - 1)); | ||||
|     var result = []; | ||||
| 
 | ||||
|     for (var i = 0; i < count; i += 1) { | ||||
|         var current = step * i + initial; | ||||
| 
 | ||||
|         result.push([Math.cos(current) * 10, Math.sin(current) * 10]); | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| }; | ||||
| 
 | ||||
| export const barnsley = [[ | ||||
|     linearTransform(0, 0, 0, 0.16, 0, 0), | ||||
|     linearTransform(.85, .04, -.04, .85, 0, 1.6), | ||||
|     linearTransform(.20, -.26, .23, .22, 0, 1.6), | ||||
|     linearTransform(-.15, .28, .26, .24, 0, .44) | ||||
| ], [ | ||||
|     .01, .85, .07, .07 | ||||
| ]]; | ||||
| 
 | ||||
| export const sierpinski = [ | ||||
|     polygonTransforms(regularVertices(3), 1 / 2), | ||||
|     [1 / 3, 1 / 3, 1 / 3] | ||||
| ]; | ||||
|  | @ -1,10 +0,0 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| import { App } from './app'; | ||||
| import * as React from 'react'; // eslint-disable-line no-unused-vars
 | ||||
| import { render } from 'react-dom'; | ||||
| 
 | ||||
| render( | ||||
|     <App />, | ||||
|     document.querySelector('#react') | ||||
| ); | ||||
|  | @ -0,0 +1,224 @@ | |||
| <script> | ||||
|     import { onMount } from 'svelte'; | ||||
|     import { writable, derived } from 'svelte/store'; | ||||
|     import { Matrix } from 'ml-matrix'; | ||||
| 
 | ||||
|     import Chaos from './Chaos.svelte'; | ||||
|     import Point from './Point.svelte'; | ||||
| 
 | ||||
|     // Page dimensions | ||||
|     let pageWidth; | ||||
|     let pageHeight; | ||||
| 
 | ||||
|     // Controls dimensions | ||||
|     const controlsWidth = 200; | ||||
| 
 | ||||
|     // Render dimensions | ||||
|     $: renderWidth = pageWidth - controlsWidth; | ||||
|     $: renderHeight = pageHeight; | ||||
| 
 | ||||
|     // Number of points | ||||
|     let count = 3; | ||||
| 
 | ||||
|     // Whether controls are shown | ||||
|     let controls = true; | ||||
| 
 | ||||
|     // Fraction | ||||
|     const ratios = writable([]); | ||||
| 
 | ||||
|     // Bounding points of the fractal | ||||
|     const points = writable([]); | ||||
| 
 | ||||
|     // Center of the fractal | ||||
|     const center = derived(points, ($points, set) => | ||||
|     { | ||||
|         set(Matrix.div( | ||||
|             $points.reduce( | ||||
|                 (prev, point) => Matrix.add(prev, point), | ||||
|                 Matrix.zeros(3, 1) | ||||
|             ), | ||||
|             $points.length | ||||
|         )); | ||||
|     }); | ||||
| 
 | ||||
|     // Derived IFS | ||||
|     const ifs = derived([ratios, points], ([$ratios, $points], set) => | ||||
|     { | ||||
|         set($points.map((point, index) => [ | ||||
|             1, | ||||
|             new Matrix([ | ||||
|                 [$ratios[index], 0, point.get(0, 0) * (1 - $ratios[index])], | ||||
|                 [0, $ratios[index], point.get(1, 0) * (1 - $ratios[index])], | ||||
|                 [0, 0, 1] | ||||
|             ]) | ||||
|         ])) | ||||
|     }); | ||||
| 
 | ||||
|     const myifs = [ | ||||
|         [1, new Matrix([ | ||||
|             [0, 0, 0], | ||||
|             [0, 0.16, 0], | ||||
|             [0, 0, 1] | ||||
|         ])], | ||||
|         [85, new Matrix([ | ||||
|             [0.85, 0.04, 0], | ||||
|             [-0.04, 0.85, 160], | ||||
|             [0, 0, 1] | ||||
|         ])], | ||||
|         [7, new Matrix([ | ||||
|             [0.20, -0.26, 0], | ||||
|             [0.23, 0.22, 160], | ||||
|             [0, 0, 1] | ||||
|         ])], | ||||
|         [7, new Matrix([ | ||||
|             [-0.15, 0.28, 0], | ||||
|             [0.26, 0.24, 44], | ||||
|             [0, 0, 1] | ||||
|         ])] | ||||
|     ]; | ||||
| 
 | ||||
|     for (let f of myifs) | ||||
|     { | ||||
|         /* f[1] = new Matrix([ */ | ||||
|         /*     [1, 0, 100], */ | ||||
|         /*     [0, 1, 100], */ | ||||
|         /*     [0, 0, 1] */ | ||||
|         /* ]).mmul(f[1]); */ | ||||
|     } | ||||
| 
 | ||||
|     console.log(myifs); | ||||
| 
 | ||||
|     $: { | ||||
|         // Generate a regular polygon with the given number of vertices | ||||
|         // and half ratio for each vertex | ||||
|         const pageCenter = [renderWidth / 2, renderHeight / 2]; | ||||
|         const radius = Math.min(renderWidth, renderHeight) * 0.4; | ||||
| 
 | ||||
|         points.set( | ||||
|             Array.from({length: count}).map( | ||||
|                 (_, index) => index * 2 * Math.PI / count - Math.PI / 2 | ||||
|             ).map(angle => Matrix.columnVector([ | ||||
|                 pageCenter[0] + Math.cos(angle) * radius, | ||||
|                 pageCenter[1] + Math.sin(angle) * radius, | ||||
|                 1 | ||||
|             ])) | ||||
|         ); | ||||
| 
 | ||||
|         ratios.set( | ||||
|             Array.from({length: count}).fill(0.5) | ||||
|         ); | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
| <style> | ||||
|     .app | ||||
|     { | ||||
|         display: flex; | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|     } | ||||
| 
 | ||||
|     .controls | ||||
|     { | ||||
|         flex: 1; | ||||
|     } | ||||
| 
 | ||||
|     .render | ||||
|     { | ||||
|         flex: 1; | ||||
|         position: relative; | ||||
|     } | ||||
| 
 | ||||
|     .render .overlay | ||||
|     { | ||||
|         position: absolute; | ||||
|         top: 0; | ||||
|         left: 0; | ||||
|     } | ||||
| </style> | ||||
| 
 | ||||
| <div class="app" bind:clientWidth={pageWidth} bind:clientHeight={pageHeight}> | ||||
|     <aside class="controls" style="width: {controlsWidth}px"> | ||||
|         <p> | ||||
|             <button on:click={() => ++count}>Add point</button> | ||||
|             <button on:click={() => --count}>Remove point</button> | ||||
|         </p> | ||||
| 
 | ||||
|         <p> | ||||
|             {#if controls} | ||||
|             Controls visible. | ||||
|             <button on:click={() => controls = false}>Hide</button> | ||||
|             {:else} | ||||
|             Controls hidden. | ||||
|             <button on:click={() => controls = true}>Show</button> | ||||
|             {/if} | ||||
|         </p> | ||||
|     </aside> | ||||
| 
 | ||||
|     <main class="render" style="width: {renderWidth}px"> | ||||
|         <Chaos | ||||
|             width={renderWidth} height={renderHeight} | ||||
|             start={$center} ifs={$ifs} | ||||
|         /> | ||||
| 
 | ||||
|         {#if controls} | ||||
|             <svg | ||||
|                 class="overlay" | ||||
|                 width={renderWidth} height={renderHeight} | ||||
|                 viewBox="0 0 {renderWidth} {renderHeight}" | ||||
|             > | ||||
|                 {#each $points as point} | ||||
|                     <line | ||||
|                         stroke="#333" | ||||
|                         x1={$center.get(0, 0)} | ||||
|                         y1={$center.get(1, 0)} | ||||
|                         x2={point.get(0, 0)} | ||||
|                         y2={point.get(1, 0)} | ||||
|                     /> | ||||
|                 {/each} | ||||
|             </svg> | ||||
| 
 | ||||
|             {#each $points as point, index} | ||||
|                 <Point | ||||
|                     x={point.get(0, 0)} | ||||
|                     y={point.get(1, 0)} | ||||
|                     on:move={evt => points.set([ | ||||
|                         ...$points.slice(0, index), | ||||
|                         Matrix.columnVector([evt.detail.x, evt.detail.y, 1]), | ||||
|                         ...$points.slice(index + 1) | ||||
|                     ])} | ||||
|                 /> | ||||
|             {/each} | ||||
| 
 | ||||
|             {#each $ifs as current, index} | ||||
|                 <Point | ||||
|                     x={current[1].mmul($center).get(0, 0)} | ||||
|                     y={current[1].mmul($center).get(1, 0)} | ||||
|                     on:move={evt => { | ||||
|                         const point = Matrix.columnVector([evt.detail.x, evt.detail.y, 1]); | ||||
| 
 | ||||
|                         const vec1 = Matrix.sub($points[index], $center); | ||||
|                         const vec2 = Matrix.sub(point, $center); | ||||
| 
 | ||||
|                         const nextRatio = 1 - vec1.dot(vec2) / vec1.dot(vec1); | ||||
| 
 | ||||
|                         if (evt.detail.shift) | ||||
|                         { | ||||
|                             ratios.set( | ||||
|                                 Array.from({length: $ratios.length}).fill(nextRatio) | ||||
|                             ); | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             ratios.set([ | ||||
|                                 ...$ratios.slice(0, index), | ||||
|                                 nextRatio, | ||||
|                                 ...$ratios.slice(index + 1) | ||||
|                             ]); | ||||
|                         } | ||||
|                     }} | ||||
|                 /> | ||||
|             {/each} | ||||
|         {/if} | ||||
|     </main> | ||||
| </div> | ||||
|  | @ -0,0 +1,147 @@ | |||
| <!-- | ||||
|     Simulate a chaos game on an iterated function set (IFS) | ||||
|     and plot the result on a canvas of given dimensions. | ||||
| --> | ||||
| 
 | ||||
| <script> | ||||
|     import { onMount, onDestroy, tick, afterUpdate } from 'svelte'; | ||||
|     import { Matrix } from 'ml-matrix'; | ||||
|     import { choose } from './util.js'; | ||||
| 
 | ||||
|     /** Width of the canvas on which the chaos figure is rendered. */ | ||||
|     export let width = 500; | ||||
| 
 | ||||
|     /** Height of the canvas on which the chaos figure is rendered. */ | ||||
|     export let height = 500; | ||||
| 
 | ||||
|     /** Total number of iterations to perform. */ | ||||
|     export let totalIterations = 100000; | ||||
| 
 | ||||
|     /** Number of iterations to perform in one frame. */ | ||||
|     export let iterationsPerFrame = 1000; | ||||
| 
 | ||||
|     /** | ||||
|      * Iterated function set to use for rendering the figure as a list of | ||||
|      * pairs, each containing the weight of the function and the function | ||||
|      * as a 3×3 matrix. | ||||
|      */ | ||||
|     export let ifs = [[1, Matrix.identity(3)]]; | ||||
| 
 | ||||
|     /** Initial point for the iteration, in homogeneous coordinates. */ | ||||
|     export let start = Matrix.columnVector([0, 0, 1]); | ||||
| 
 | ||||
|     // Canvas and drawing context on which the figure is rendered | ||||
|     let canvas; | ||||
|     let ctx; | ||||
| 
 | ||||
|     // Handle to the next scheduled frame | ||||
|     let nextFrame = null; | ||||
| 
 | ||||
|     /** | ||||
|      * Perform random chaos iteration on the given point, leaving traces | ||||
|      * behind as the iteration goes. | ||||
|      * | ||||
|      * @param point Starting point for the iteration. | ||||
|      * @param ifs Iterated function set to use. | ||||
|      * @param iterations Number of iterations to perform. | ||||
|      */ | ||||
|     const randomIterate = (point, ifs, iterations) => | ||||
|     { | ||||
|         // Only perform a limited amount of iterations and | ||||
|         // defer the remaining ones to the next frame | ||||
|         for (let i = 0; i < iterationsPerFrame; ++i) | ||||
|         { | ||||
|             const [_, matrix] = choose(ifs); | ||||
|             point = matrix.mmul(point); | ||||
| 
 | ||||
|             const x = Math.floor(point.get(0, 0)); | ||||
|             const y = Math.floor(point.get(1, 0)); | ||||
| 
 | ||||
|             if (x >= 0 && x < width && y >= 0 && y < height) | ||||
|             { | ||||
|                 ctx.fillRect(x, y, 1, 1); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (iterations > iterationsPerFrame) | ||||
|         { | ||||
|             nextFrame = window.requestAnimationFrame(() => randomIterate( | ||||
|                 point, ifs, | ||||
|                 iterations - iterationsPerFrame | ||||
|             )); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * Perform deterministic chaos iteration on each of the given points, | ||||
|      * leaving traces behind as the iteration goes. | ||||
|      * | ||||
|      * @param points Seed points, modified in-place. | ||||
|      * @param index Index from which to iterate in the points array. | ||||
|      * @param ifs Iterated function set to use. | ||||
|      * @param iterations Number of iterations to perform. | ||||
|      */ | ||||
|     const deterministicIterate = (points, index, ifs, iterations) => | ||||
|     { | ||||
|         let i = index; | ||||
| 
 | ||||
|         for ( | ||||
|             let frameIterations = 0; | ||||
|             frameIterations < iterationsPerFrame; | ||||
|             frameIterations += ifs.length, | ||||
|             i = (i + ifs.length) % points.length | ||||
|         ) | ||||
|         { | ||||
|             points.splice( | ||||
|                 i, 1, | ||||
|                 ...ifs.map(([_, matrix]) => | ||||
|                 { | ||||
|                     const nextPoint = matrix.mmul(points[i]); | ||||
|                     const x = Math.floor(nextPoint.get(0, 0)); | ||||
|                     const y = Math.floor(nextPoint.get(1, 0)); | ||||
| 
 | ||||
|                     if (x >= 0 && x < width && y >= 0 && y < height) | ||||
|                     { | ||||
|                         ctx.fillRect(x, y, 1, 1); | ||||
|                     } | ||||
| 
 | ||||
|                     return nextPoint; | ||||
|                 }) | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         if (iterations > iterationsPerFrame) | ||||
|         { | ||||
|             nextFrame = window.requestAnimationFrame(() => deterministicIterate( | ||||
|                 points, i, ifs, | ||||
|                 iterations - iterationsPerFrame | ||||
|             )); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     /** Interrupt the iteration process. */ | ||||
|     const stopLoop = () => | ||||
|     { | ||||
|         window.cancelAnimationFrame(nextFrame); | ||||
|     }; | ||||
| 
 | ||||
|     $: { | ||||
|         stopLoop(); | ||||
| 
 | ||||
|         if (ctx && ifs.length) | ||||
|         { | ||||
|             ctx.clearRect(0, 0, width, height); | ||||
|             ctx.fillStyle = 'black'; | ||||
|             deterministicIterate([start], 0, ifs, totalIterations); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     onMount(() => ctx = canvas.getContext('2d')); | ||||
|     onDestroy(() => stopLoop()); | ||||
| </script> | ||||
| 
 | ||||
| <canvas | ||||
|     bind:this={canvas} | ||||
|     draggable=false | ||||
|     width={width} height={height} | ||||
| /> | ||||
|  | @ -0,0 +1,119 @@ | |||
| <!-- | ||||
|     Represent a point on the screen that can optionally be moved by the user. | ||||
| --> | ||||
| 
 | ||||
| <script> | ||||
|     import { onMount, onDestroy, createEventDispatcher } from 'svelte'; | ||||
| 
 | ||||
|     /** Horizontal position of the point. */ | ||||
|     export let x = 0; | ||||
| 
 | ||||
|     /** Vertical position of the point. */ | ||||
|     export let y = 0; | ||||
| 
 | ||||
|     /** Radius of the circle that represents the point. */ | ||||
|     export let size = 15; | ||||
| 
 | ||||
|     /** Whether the user is allowed to move the point. */ | ||||
|     export let movable = true; | ||||
| 
 | ||||
|     // Reference to the <div> element that represents the point | ||||
|     let element = null; | ||||
| 
 | ||||
|     // Bounds of the parent node, computed only once when the user | ||||
|     // starts dragging | ||||
|     let bounds; | ||||
| 
 | ||||
|     // Whether the user is currently dragging the point | ||||
|     let dragging = false; | ||||
| 
 | ||||
|     let dispatch = createEventDispatcher(); | ||||
| 
 | ||||
|     const start = evt => | ||||
|     { | ||||
|         if (!movable) return; | ||||
| 
 | ||||
|         bounds = element.parentNode.getBoundingClientRect(); | ||||
|         dragging = true; | ||||
| 
 | ||||
|         window.addEventListener('mousemove', move); | ||||
|         window.addEventListener('touchmove', move); | ||||
|         window.addEventListener('mouseup', end); | ||||
|         window.addEventListener('touchend', end); | ||||
|     }; | ||||
| 
 | ||||
|     const clamp = (value, min, max) => Math.max(min, Math.min(max, value)); | ||||
| 
 | ||||
|     const move = evt => | ||||
|     { | ||||
|         const [userX, userY] = evt.changedTouches | ||||
|             ? [evt.changedTouches[0].clientX, evt.changedTouches[0].clientY] | ||||
|             : [evt.clientX, evt.clientY]; | ||||
| 
 | ||||
|         dispatch('move', { | ||||
|             x: clamp(userX - bounds.left, 0, bounds.width), | ||||
|             y: clamp(userY - bounds.top, 0, bounds.height), | ||||
|             shift: evt.shiftKey | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     const end = evt => | ||||
|     { | ||||
|         dragging = false; | ||||
|         window.removeEventListener('mousemove', move); | ||||
|         window.removeEventListener('touchmove', move); | ||||
|         window.removeEventListener('mouseup', end); | ||||
|         window.removeEventListener('touchend', end); | ||||
|     }; | ||||
| 
 | ||||
|     onMount(() => | ||||
|     { | ||||
|         element.addEventListener('mousedown', start); | ||||
|         element.addEventListener('touchstart', start); | ||||
|     }); | ||||
| 
 | ||||
|     onDestroy(() => | ||||
|     { | ||||
|         element.removeEventListener('mousedown', start); | ||||
|         element.removeEventListener('touchstart', start); | ||||
|         window.removeEventListener('mousemove', move); | ||||
|         window.removeEventListener('touchmove', move); | ||||
|         window.removeEventListener('mouseup', end); | ||||
|         window.removeEventListener('touchend', end); | ||||
|     }); | ||||
| </script> | ||||
| 
 | ||||
| <style> | ||||
|     div | ||||
|     { | ||||
|         display: block; | ||||
|         position: absolute; | ||||
|         border-radius: 100%; | ||||
|         background: #333; | ||||
| 
 | ||||
|         box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1); | ||||
|         transition: | ||||
|             box-shadow .15s var(--easing), | ||||
|             transform .15s var(--easing), | ||||
|             opacity .15s var(--easing); | ||||
|     } | ||||
| 
 | ||||
|     div.movable | ||||
|     { | ||||
|         cursor: pointer; | ||||
|     } | ||||
| 
 | ||||
|     div.movable.dragging | ||||
|     { | ||||
|         box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2); | ||||
|         transform: scale(1.5); | ||||
|     } | ||||
| </style> | ||||
| 
 | ||||
| <div | ||||
|     style="width: {size}px; height: {size}px; left: {x - size / 2}px; top: {y - size / 2}px;" | ||||
|     class:dragging | ||||
|     class:movable | ||||
|     draggable="false" | ||||
|     bind:this={element} | ||||
| /> | ||||
|  | @ -0,0 +1,4 @@ | |||
| import App from './App.svelte'; | ||||
| 
 | ||||
| const app = new App({target: document.body}); | ||||
| export default app; | ||||
|  | @ -0,0 +1,27 @@ | |||
| /** | ||||
|  * Choose a pair at random among a list of weighted pairs. | ||||
|  * | ||||
|  * @param pairs List of weighted pairs, with the weight coming first. | ||||
|  * @return Selected pair, or null if there is nothing to choose from. | ||||
|  */ | ||||
| export const choose = pairs => | ||||
| { | ||||
|     const total = pairs.reduce((prev, [weight, _]) => prev + weight, 0); | ||||
| 
 | ||||
|     if (total === 0) | ||||
|     { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     const value = Math.random() * total; | ||||
|     let sum = 0; | ||||
|     let index = 0; | ||||
| 
 | ||||
|     while (value >= sum) | ||||
|     { | ||||
|         sum += pairs[index][0]; | ||||
|         index += 1; | ||||
|     } | ||||
| 
 | ||||
|     return pairs[index - 1]; | ||||
| }; | ||||
|  | @ -1,11 +0,0 @@ | |||
| /** | ||||
|  * Styles for chaos game | ||||
|  */ | ||||
| 
 | ||||
| #react { | ||||
|     height: 100%; | ||||
| } | ||||
| 
 | ||||
| #content { | ||||
|     overflow: hidden; | ||||
| } | ||||
		Loading…
	
		Reference in New Issue