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