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
|
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",
|
"name": "chaos",
|
||||||
"version": "1.0.0",
|
"version": "2.0.0",
|
||||||
"description": "Plotting fractals with the chaos game",
|
"description": "Exploring the chaos game",
|
||||||
"repository": {
|
"license": "CC0",
|
||||||
"type": "git",
|
"private": true,
|
||||||
"url": "git+https://github.com/matteodelabre/chaos.git"
|
"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": {
|
"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": {
|
"dependencies": {
|
||||||
"babel-cli": "^6.3.17",
|
"ml-matrix": "^6.2.0"
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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