commit 75f83aaa80c9baf67f15478938e4b544473b9e14
parent dfb792fdd21ea53d4c7cafae74dba075d19d0212
Author: Yuval Langer <yuval.langer@gmail.com>
Date: Fri, 19 Jan 2024 15:25:23 +0200
Merge kaka.farm repository with the dabbling repository.
Diffstat:
42 files changed, 4087 insertions(+), 0 deletions(-)
diff --git a/dabbling/.gitignore b/dabbling/.gitignore
@@ -0,0 +1,4 @@
+dabbling.egg-info
+dabbling/__pycache__
+*.png
+*.svg
diff --git a/dabbling/.gitlab-ci.yml b/dabbling/.gitlab-ci.yml
@@ -0,0 +1,11 @@
+image: alpine:latest
+
+pages:
+ stage: deploy
+ script:
+ - mv html public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/dabbling/README.rst b/dabbling/README.rst
diff --git a/dabbling/html/annoying-sawtooth-alarm.html b/dabbling/html/annoying-sawtooth-alarm.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script type="text/javascript">
+'use strict';
+
+
+(function() {
+ function main() {
+ let f = 440;
+ let v = 1;
+ let audio = new window.AudioContext;
+
+ let oscillator = audio.createOscillator();
+
+ oscillator.type = 'sine';
+ oscillator.frequency.setValueAtTime(f, audio.currentTime);
+ oscillator.connect(audio.destination);
+ oscillator.start();
+
+ function changeFrequency() {
+ //alert(f);
+ if ((f > 880) || (f < 440)) {
+ f = Math.min(Math.max(f, 440), 880);
+ v *= -1;
+ };
+
+ f += 10 * v;
+
+ oscillator.type = 'sawtooth';
+ oscillator.frequency.setValueAtTime(f, audio.currentTime);
+ oscillator.connect(audio.destination);
+ //oscillator.start();
+
+ window.setTimeout(changeFrequency, 10);
+ };
+
+ window.setTimeout(changeFrequency, 10);
+ };
+
+ window.addEventListener('load', main);
+})();
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/dabbling/html/index.html b/dabbling/html/index.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Dabbling in the hidden arts of programming. A useless crap repository.</title>
+ </head>
+ <body>
+ <h1>Dabbling</h1>
+ <h2>"projects":</h2>
+ <ul>
+ <li>
+ <a href="tau/2020-darts.html">τ Day 2020 darts</a>
+ </li>
+ <li>
+ <a href="sokoban/sokoban.html">Sokoban</a>
+ </li>
+ <li>
+ <a href="tau-mandala/">τ mandala</a>
+ </li>
+ <li>
+ <a href="wtc_plaza_2001-09-11/">WTC plaza circa 2001-09-11</a>
+ </li>
+ <li>
+ <a href="the-dancing-polygon-screensaver/">The Dancing Polygon Screensaver</a>
+ </li>
+ <li>
+ <a href="quadratic-bezier-curves/">Quadratic Bezier Curves</a>
+ </li>
+ <li>
+ <a href="shades-of-grey/">Shades of Grey</a>
+ </li>
+ <li>
+ <a href="spinning-shapes/">Spinning Shapes</a>
+ </li>
+ </ul>
+ <h2>Dabbling code repositories:</h2>
+ <h3>Main repository:</h3>
+ <a href="https://codeberg.org/yuvallangerontheroad/dabbling">codeberg.org</a>
+ <h3>Mirror repositories:</h3>
+ <ul>
+ <li><a href="https://gitlab.com/yuvallangerontheroad/dabbling">gitlab.com</a></li>
+ <li><a href="https://framagit.org/yuvallangerontheroad/dabbling/-/import">Framagit</a></li>
+ </ul>
+ </body>
+</html>
diff --git a/dabbling/html/kakalog.js b/dabbling/html/kakalog.js
@@ -0,0 +1,33 @@
+'use strict';
+
+
+console.log('kakalog.js start.');
+
+
+(function() {
+ function change_console() {
+ if (window.kakalog_activated) {
+ [
+ 'debug',
+ 'error',
+ 'info',
+ 'log',
+ 'trace',
+ ].forEach(function_name=>{
+ let old_function = console[function_name];
+ let new_function = function(...stuff) {
+ old_function(...stuff);
+ let log_element = document.getElementById('log');
+ let log_text = `${function_name}: ${stuff}`;
+ log_element.innerText = `${log_element.innerText}\n${log_text}`;
+ };
+ console[function_name] = new_function;
+ });
+ };
+ };
+
+ window.addEventListener('load', change_console);
+})();
+
+
+console.log('kakalog.js end.');
diff --git a/dabbling/html/quadratic-bezier-curves/index.html b/dabbling/html/quadratic-bezier-curves/index.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <script src="main.js" type="text/javascript"></script>
+ <link rel="stylesheet" href="style.css"></link>
+ </head>
+ <body>
+ <canvas id="canvas" width="800" height="600"></canvas>
+ </body>
+</html>
diff --git a/dabbling/html/quadratic-bezier-curves/main.js b/dabbling/html/quadratic-bezier-curves/main.js
@@ -0,0 +1,153 @@
+'use strict;'
+
+Math.TAU = 2 * Math.PI;
+
+(function() {
+ let line_a = [
+ [Math.random(), Math.random()],
+ [Math.random(), Math.random()],
+ ];
+
+ let line_b = [
+ [Math.random(), Math.random()],
+ [Math.random(), Math.random()],
+ ];
+
+ const HSV_VALUE_VALUE = 0.7;
+ const HSV_SATURATION_VALUE = 0.7;
+ const WANTED_NUMBER_OF_LINES = 50;
+
+ function lerp(p, a, b) {
+ return p * (b - a) + a;
+ }
+
+ function lerp_2d(p, a, b) {
+ return [
+ lerp(p, a[0], b[0]),
+ lerp(p, a[1], b[1]),
+ ];
+ }
+
+ function hsv_to_rgb(hue, saturation, value) {
+ // https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB
+ // H in [0, 1] (position on the color wheel)
+ // S in [0, 1]
+ // V in [0, 1]
+
+ let chroma = value * saturation;
+
+ let hue_tag = hue * 6;
+
+ let x = chroma * (1 - Math.abs((hue_tag % 2) - 1));
+
+ let red_1, green_1, blue_1;
+
+ if (hue_tag >= 0 && hue_tag < 1) {
+ [red_1, green_1, blue_1] = [chroma, x, 0];
+ } else if (hue_tag >= 1 && hue_tag < 2) {
+ [red_1, green_1, blue_1] = [x, chroma, 0];
+ } else if (hue_tag >= 2 && hue_tag < 3) {
+ [red_1, green_1, blue_1] = [0, chroma, x];
+ } else if (hue_tag >= 3 && hue_tag < 4) {
+ [red_1, green_1, blue_1] = [0, x, chroma];
+ } else if (hue_tag >= 4 && hue_tag < 5) {
+ [red_1, green_1, blue_1] = [x, 0, chroma];
+ } else if (hue_tag >= 5 && hue_tag < 6) {
+ [red_1, green_1, blue_1] = [chroma, 0, x];
+ };
+
+ let m = value - chroma;
+
+ return [red_1 + m, green_1 + m, blue_1 + m];
+ }
+
+ function draw_line(line, rgb) {
+ let canvas = document.getElementById('canvas');
+ let ctx = canvas.getContext('2d');
+
+ ctx.beginPath();
+
+ ctx.strokeStyle = `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
+
+ ctx.moveTo(canvas.width * line[0][0], canvas.height * line[0][1]);
+ ctx.lineTo(canvas.width * line[1][0], canvas.height * line[1][1]);
+
+ ctx.stroke();
+ }
+
+ function uniform_random_direction() {
+ let uniform = Math.random();
+ let direction_vector = [
+ Math.cos(uniform * Math.TAU),
+ Math.sin(uniform * Math.TAU),
+ ];
+ return direction_vector;
+ }
+
+ function draw() {
+ let canvas = document.getElementById('canvas');
+ let ctx = canvas.getContext('2d');
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ //draw_line(line_a, [255, 255, 255]);
+ //draw_line(line_b, [255, 255, 255]);
+
+
+ for (let current_line_number = 0; current_line_number < WANTED_NUMBER_OF_LINES; current_line_number++) {
+ let bezier_curve_portion = current_line_number / (WANTED_NUMBER_OF_LINES - 1);
+ let rgb = hsv_to_rgb(
+ bezier_curve_portion,
+ HSV_SATURATION_VALUE,
+ HSV_VALUE_VALUE,
+ ).map(x => 255 * x);
+
+ let bezier_line = [
+ lerp_2d(bezier_curve_portion, line_a[0], line_a[1]),
+ lerp_2d(bezier_curve_portion, line_b[0], line_b[1]),
+ ];
+
+ draw_line(bezier_line, rgb);
+ }
+ }
+
+ function resize_window() {
+ // https://stackoverflow.com/a/32119392
+
+ let canvas = document.getElementById('canvas');
+
+ canvas.width = window.innerWidth;
+ canvas.style.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ canvas.style.height = window.innerHeight;
+
+ draw();
+ }
+
+ function make_new_world() {
+ line_a = [
+ [Math.random(), Math.random()],
+ [Math.random(), Math.random()],
+ ];
+ line_b = [
+ [Math.random(), Math.random()],
+ [Math.random(), Math.random()],
+ ];
+ }
+
+ function main() {
+ let canvas = document.getElementById('canvas');
+
+ resize_window();
+ make_new_world();
+
+ canvas.addEventListener('click', function() {
+ make_new_world();
+ draw();
+ });
+
+ window.addEventListener('resize', resize_window)
+ }
+
+ window.addEventListener('load', main);
+})();
diff --git a/dabbling/html/quadratic-bezier-curves/style.css b/dabbling/html/quadratic-bezier-curves/style.css
@@ -0,0 +1,11 @@
+/* Stolen from https://developer.mozilla.org/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Create_the_Canvas_and_draw_on_it */
+
+* {
+ padding: 0;
+ margin: 0;
+}
+canvas {
+ background: #000;
+ display: block;
+ margin: 0 auto;
+}
diff --git a/dabbling/html/shades-of-grey/index.html b/dabbling/html/shades-of-grey/index.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <script src="main.js" type="text/javascript"></script>
+ <link rel="stylesheet" href="style.css"></link>
+ </head>
+ <body>
+ <canvas id="canvas" width="800" height="600"></canvas>
+ </body>
+</html>
diff --git a/dabbling/html/shades-of-grey/main.js b/dabbling/html/shades-of-grey/main.js
@@ -0,0 +1,101 @@
+(function() {
+ let number_of_rectangles = 2;
+ let factor = 2;
+ const INTERVAL = 750;
+
+ function hsv_to_rgb(hue, saturation, value) {
+ // https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB
+ // H in [0, 1] (position on the color wheel)
+ // S in [0, 1]
+ // V in [0, 1]
+
+ let chroma = value * saturation;
+
+ let hue_tag = hue * 6;
+
+ let x = chroma * (1 - Math.abs((hue_tag % 2) - 1));
+
+ let [red_1, green_1, blue_1] = ((hue_tag, chroma, x)=>{
+ if (hue_tag >= 0 && hue_tag < 1) { return [chroma, x, 0];
+ } else if (hue_tag >= 1 && hue_tag < 2) { return [x, chroma, 0];
+ } else if (hue_tag >= 2 && hue_tag < 3) { return [0, chroma, x];
+ } else if (hue_tag >= 3 && hue_tag < 4) { return [0, x, chroma];
+ } else if (hue_tag >= 4 && hue_tag < 5) { return [x, 0, chroma];
+ } else if (hue_tag >= 5 && hue_tag < 6) { return [chroma, 0, x];
+ }
+ })(hue_tag, chroma, x);
+
+ let m = value - chroma;
+
+ return [red_1 + m, green_1 + m, blue_1 + m];
+ }
+
+ function draw() {
+ let canvas = document.getElementById('canvas');
+ let ctx = canvas.getContext('2d');
+
+ console.info(canvas.width, number_of_rectangles);
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ let rectangle_width = canvas.width / number_of_rectangles;
+
+ for (
+ let rectangle_number = 0;
+ rectangle_number < number_of_rectangles;
+ rectangle_number++
+ ) {
+ ctx.beginPath();
+
+ let rect_hsv_value = (number_of_rectangles - rectangle_number - 1) / (number_of_rectangles - 1);
+ let rgb = hsv_to_rgb(0, 0, rect_hsv_value).map(x=>255 * x);
+ ctx.fillStyle = `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
+
+ let rectangle_x = rectangle_number * rectangle_width;
+
+ ctx.fillRect(Math.floor(rectangle_x), 0, Math.ceil(rectangle_width), canvas.height);
+ }
+ }
+
+ function resize_window() {
+ // https://stackoverflow.com/a/32119392
+
+ let canvas = document.getElementById('canvas');
+
+ canvas.width = window.innerWidth;
+ canvas.style.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ canvas.style.height = window.innerHeight;
+ }
+
+ function main() {
+ let canvas = document.getElementById('canvas');
+ let ctx = canvas.getContext('2d');
+
+ resize_window();
+ //reset_world();
+
+ //canvas.addEventListener('click', reset_world);
+
+ window.addEventListener('resize', ()=>{resize_window(); draw();})
+
+ draw();
+
+ setInterval(
+ ()=>{
+ if (number_of_rectangles > canvas.width) {
+ factor = 0.5;
+ number_of_rectangles = 2**Math.floor(Math.log2(canvas.width));
+ } else if (number_of_rectangles <= 2) {
+ factor = 2;
+ number_of_rectangles = 2;
+ }
+ number_of_rectangles = factor * number_of_rectangles;
+ draw();
+ },
+ INTERVAL,
+ );
+ }
+
+ window.addEventListener('load', main);
+})();
diff --git a/dabbling/html/shades-of-grey/style.css b/dabbling/html/shades-of-grey/style.css
@@ -0,0 +1,11 @@
+/* Stolen from https://developer.mozilla.org/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Create_the_Canvas_and_draw_on_it */
+
+* {
+ padding: 0;
+ margin: 0;
+}
+canvas {
+ background: #000;
+ display: block;
+ margin: 0 auto;
+}
diff --git a/dabbling/html/sokoban/kakalog-state.js b/dabbling/html/sokoban/kakalog-state.js
@@ -0,0 +1,4 @@
+'use strict';
+
+
+window.kakalog_activated = false;
diff --git a/dabbling/html/sokoban/sokoban-web.js b/dabbling/html/sokoban/sokoban-web.js
@@ -0,0 +1,57 @@
+'use strict';
+
+
+(function() {
+ function load_game() {
+ return JSON.parse(window.localStorage.getItem('game'));
+ };
+
+
+ function save_game(game) {
+ window.localStorage.setItem('game', JSON.stringify(game));
+ };
+
+
+ function main() {
+ let game = load_game() || sokoban.new_game();
+
+ let text_element = document.getElementById('text');
+ text_element.innerText = sokoban.game_to_text(game);
+
+ [
+ ['up', sokoban.action_step_up],
+ ['down', sokoban.action_step_down],
+ ['left', sokoban.action_step_left],
+ ['right', sokoban.action_step_right],
+ ['next_level', sokoban.action_next_level],
+ ['restart_level', sokoban.action_restart_level],
+ ['undo', sokoban.action_undo],
+ ['save', save_game],
+ ].forEach(([element_id, action_function])=>{
+ let button = document.getElementById(element_id);
+ console.debug(button)
+ function click_handler() {
+ console.debug('click: ' + element_id);
+ action_function(game);
+ text_element.innerText = sokoban.game_to_text(game);
+ };
+ button.addEventListener('click', click_handler);
+ });
+
+ [
+ ['new', sokoban.new_game],
+ ['load', load_game],
+ ].forEach(([element_id, action_function])=>{
+ let button = document.getElementById(element_id);
+ console.debug(button)
+ function click_handler() {
+ console.debug('click: ' + element_id);
+ game = action_function();
+ text_element.innerText = sokoban.game_to_text(game);
+ };
+ button.addEventListener('click', click_handler);
+ });
+ };
+
+ window.addEventListener('load', main);
+})();
diff --git a/dabbling/html/sokoban/sokoban.css b/dabbling/html/sokoban/sokoban.css
@@ -0,0 +1,4 @@
+body {
+ color: grey;
+ background: black;
+}
diff --git a/dabbling/html/sokoban/sokoban.html b/dabbling/html/sokoban/sokoban.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Kaka Farm's Sokoban clone: no bells no whistles no glory.</title>
+ <link href="sokoban.css" rel="stylesheet"></style>
+ <script type="text/javascript" src="kakalog-state.js"></script>
+ <script type="text/javascript" src="../kakalog.js"></script>
+ <script type="text/javascript" src="sokoban.js"></script>
+ <script type="text/javascript" src="sokoban-web.js"></script>
+ </head>
+ <body>
+ <pre id="text"></pre>
+ <button id="up" type="button">↑</button>
+ <button id="down" type="button">↓</button>
+ <button id="left" type="button">←</button>
+ <button id="right" type="button">→</button>
+ <button id="next_level" type="button">continue</button>
+ <button id="restart_level" type="button">restart</button>
+ <button id="undo" type="button">undo</button>
+ <button id="save" type="button">save game</button>
+ <button id="load" type="button">load game</button>
+ <button id="new" type="button">new game</button>
+ <pre id="log"></pre>
+ </body>
+</html>
diff --git a/dabbling/html/sokoban/sokoban.js b/dabbling/html/sokoban/sokoban.js
@@ -0,0 +1,279 @@
+'use strict';
+
+
+console.log('sokoban.js start.');
+
+
+let sokoban = (function() {
+ let levels = [
+ `
+##############
+#.@...*....z.#
+##############
+`,
+ `
+ #####
+ #...#
+ #*..#
+ ###..*##
+ #..*.*.#
+###.#.##.# ######
+#...#.##.#####..zz#
+#.*..*..........zz#
+#####.###.#@##..zz#
+ #.....#########
+ #######
+`,
+ ];
+ let level_win_test_not_all_endings = `
+ #####
+ #...#
+ #...#
+ ###...##
+ #......#
+###.#.##.# ######
+#...#.##.#####..zz#
+#..z.*..........zz#
+#####.###.#@##..zz#
+ #.....#########
+ #######
+`;
+ let level_win_test_all_endings = `
+ #####
+ #...#
+ #...#
+ ###...##
+ #......#
+###.#.##.# ######
+#...#.##.#####....#
+#..z.*............#
+#####.###.#@##....#
+ #.....#########
+ #######
+`;
+
+ function read_level_text(s) {
+ let state = {
+ rocks: [],
+ walls: [],
+ floors: [],
+ endings: [],
+ };
+ let lines = s.split('\n');
+ let max_x = Math.max(...lines.map(line=>line.length));
+ for (let y = 0; y < lines.length; y++) {
+ state.walls[y] = [];
+ state.rocks[y] = [];
+ state.floors[y] = [];
+ state.endings[y] = [];
+ for (let x = 0; x < lines[y].length; x++) {
+ if (lines[y][x] == '@') {
+ state.player_position = [x, y];
+ state.floors[y][x] = '.';
+ } else if (lines[y][x] == '*') {
+ state.rocks[y][x] = '*';
+ state.floors[y][x] = '.';
+ } else if (lines[y][x] == '#') {
+ state.walls[y][x] = '#';
+ } else if (lines[y][x] == 'z') {
+ state.endings[y][x] = 'z';
+ } else if (lines[y][x] == '.') {
+ state.floors[y][x] = '.';
+ };
+ };
+ };
+
+ return state;
+ };
+
+ function here_player(state, [x, y]) {
+ return (
+ state.player_position[0] == x &&
+ state.player_position[1] == y
+ );
+ }
+ function here_wall(state, [x, y]) {
+ return state.walls[y][x] == '#';
+ }
+ function here_rock(state, [x, y]) {
+ return state.rocks[y][x] == '*';
+ }
+ function here_ending(state, [x, y]) {
+ return state.endings[y][x] == 'z';
+ }
+ function here_floor(state, [x, y]) {
+ return state.floors[y][x] == '.';
+ }
+ function move_rock(state, [fx, fy], [tx, ty]) {
+ delete state.rocks[fy][fx];
+ state.rocks[ty][tx] = '*';
+ }
+
+
+ function is_won(game) {
+ for (let y = 0; y < game.level_state.rocks.length; y++) {
+ if (game.level_state.rocks[y] != undefined) {
+ for (let x = 0; x < game.level_state.rocks[y].length; x++) {
+ if (here_rock(game.level_state, [x, y]) != here_ending(game.level_state, [x, y])) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ };
+
+
+ function copy_level_state(game) {
+ let state_copy = [
+ {},
+ 'rocks',
+ 'floors',
+ 'walls',
+ 'endings',
+ ].reduce((acc, entity_name)=>{
+ acc[entity_name] = (
+ game
+ .level_state[entity_name]
+ .map(line=>line.map(c=>c))
+ );
+ return acc;
+ });
+ state_copy.player_position = game.level_state.player_position.map(p=>p);
+ return state_copy;
+ };
+
+
+ function action_step(game, direction) {
+ if (!is_won(game)) {
+ let p0 = game.level_state.player_position;
+ let p1 = [
+ p0[0] + direction[0],
+ p0[1] + direction[1]];
+ let p2 = [
+ p1[0] + direction[0],
+ p1[1] + direction[1]];
+
+ let is_blocked = (
+ here_wall(game.level_state, p1) || (
+ here_rock(game.level_state, p1) && (
+ here_rock(game.level_state, p2) ||
+ here_wall(game.level_state, p2))));
+ let is_pushing = (
+ here_rock(game.level_state, p1) &&
+ !here_rock(game.level_state, p2));
+
+ if (!is_blocked) {
+ game.number_of_steps += 1;
+ let old_state = copy_level_state(game);
+ game.undo_stack.push(old_state);
+ if (is_pushing) {
+ move_rock(game.level_state, p1, p2);
+ };
+ game.level_state.player_position = p1;
+ };
+ };
+ };
+
+ function action_step_up(game) { action_step(game, [0, -1]); };
+ function action_step_down(game) { action_step(game, [0, 1]); };
+ function action_step_left(game) { action_step(game, [-1, 0]); };
+ function action_step_right(game) { action_step(game, [1, 0]); };
+
+
+ function action_next_level(game) {
+ if (is_won(game) && game.current_level_number < levels.length) {
+ game.number_of_steps = 0;
+ game.undo_stack = [];
+ game.current_level_number += 1;
+ game.level_state = read_level_text(levels[game.current_level_number]);
+ };
+ };
+
+
+ function action_restart_level(game) {
+ game.number_of_steps = 0;
+ game.undo_stack = [];
+ game.level_state = read_level_text(levels[game.current_level_number]);
+ };
+
+
+ function action_undo(game) {
+ if (game.undo_stack.length > 0) {
+ game.number_of_steps -= 1;
+ game.level_state = game.undo_stack.pop();
+ }
+ }
+
+
+ function game_to_text(game) {
+ console.log(game.undo_state);
+ let s = '';
+
+ if (is_won(game)) {
+ s = s.concat('Won! Press c to continue.');
+ }
+ s = s.concat(`Level ${game.current_level_number + 1}. Step ${game.number_of_steps}.`);
+
+ let max_y = Math.max(
+ game.level_state.rocks.length,
+ game.level_state.endings.length,
+ game.level_state.floors.length,
+ game.level_state.walls.length,
+ game.level_state.player_position[1],
+ );
+ for (let y = 0; y < max_y; y++) {
+ let max_x = Math.max(
+ game.level_state.rocks[y].length,
+ game.level_state.endings[y].length,
+ game.level_state.floors[y].length,
+ game.level_state.walls[y].length,
+ game.level_state.player_position[0],
+ );
+ for (let x = 0; x < max_x; x++) {
+ if (here_player(game.level_state, [x, y])) {
+ s = s.concat('@');
+ } else if (here_wall(game.level_state, [x, y])) {
+ s = s.concat('#');
+ } else if (here_rock(game.level_state, [x, y])) {
+ s = s.concat('*');
+ } else if (here_floor(game.level_state, [x, y])) {
+ s = s.concat('.');
+ } else if (here_ending(game.level_state, [x, y])) {
+ s = s.concat('z');
+ } else {
+ s = s.concat(' ');
+ }
+ };
+ s = s.concat('\n');
+ };
+
+ return s.trimEnd();
+ };
+
+
+ function new_game() {
+ return {
+ current_level_number: 0,
+ level_state: read_level_text(levels[0]),
+ undo_stack: [],
+ number_of_steps: 0,
+ };
+ };
+
+
+ return {
+ action_next_level,
+ action_restart_level,
+ action_step_down,
+ action_step_left,
+ action_step_right,
+ action_step_up,
+ action_undo,
+ game_to_text,
+ new_game,
+ };
+})();
+
+
+console.log('sokoban.js end.');
diff --git a/dabbling/html/sokoban/sokoban_tests.js b/dabbling/html/sokoban/sokoban_tests.js
@@ -0,0 +1,34 @@
+console.log('tests start');
+
+console.log(sokoban);
+
+let game = sokoban.new_game();
+[
+ sokoban.action_step_right,
+ sokoban.action_step_right,
+ sokoban.action_step_right,
+ sokoban.action_step_right,
+ sokoban.action_step_right,
+ sokoban.action_step_right,
+ sokoban.action_step_right,
+ sokoban.action_step_right,
+ sokoban.action_next_level,
+].forEach(action_function=>{
+ let s = sokoban.game_to_text(game);
+ console.log(`before:
+${s}
+KAKAKAKAKAKAKAKAKAKAKAKAKAKAKAKA`
+ );
+ action_function(game);
+ s = sokoban.game_to_text(game);
+ console.log(`after:
+${s}
+KAKAKAKAKAKAKAKAKAKAKAKAKAKAKAKA`
+ );
+})
+sokoban.action_next_level(game);
+let s = sokoban.game_to_text(game);
+console.log(`after: ${s}`)
+sokoban.action_next_level(game);
+s = sokoban.game_to_text(game);
+console.log(`after: ${s}`)
diff --git a/dabbling/html/spinning-shapes/index.html b/dabbling/html/spinning-shapes/index.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+ <head>
+ <script src="main.js" type="text/javascript"></script>
+ <meta charset="utf-8" />
+ <link rel="stylesheet" href="style.css" />
+ </head>
+ <body>
+ <canvas id="canvas" width=1000 height=500></canvas>
+ </body>
+</html>
diff --git a/dabbling/html/spinning-shapes/main.js b/dabbling/html/spinning-shapes/main.js
@@ -0,0 +1,305 @@
+Math.TAU = 2 * Math.PI;
+
+(function() {
+ let turn = 0;
+ let last_time = 0;
+ let math_ts;
+ let math_yss;
+ let math_polygon_radiuses;
+ let canvas_ts;
+ let canvas_yss;
+ let canvas_polygon_radiuses;
+
+ const NUMBER_OF_POLYGONS = 7;
+ const POLYGON_HEIGHT_MARGIN = 0.075;
+ const TURN_PER_MILLISECOND = 0.0005;
+ const CYCLE_LENGTH = 20;
+ const NUMBER_OF_SAMPLES = 500;
+ const START_T = 0;
+ const END_T = CYCLE_LENGTH;
+ const math_graph_y_extents = [
+ [-1.2, 1.2],
+ [-1.2, 1.2],
+ [-1.2, 1.2],
+ [-1.2, 1.2],
+ [-1.2, 1.2],
+ [-1.2, 1.2],
+ [-1.2, 1.2],
+ ];
+
+
+
+ const functions = [
+ /*
+ turn => step_on(turn % CYCLE_LENGTH, 1, 1.5) - step_on(turn % CYCLE_LENGTH, 2, 2.5) +
+ step_on(turn % CYCLE_LENGTH, 3, 3.5) - step_on(turn % CYCLE_LENGTH, 4, 4.5) +
+ step_on(turn % CYCLE_LENGTH, 5, 5.5) - step_on(turn % CYCLE_LENGTH, 6, 6.5) +
+
+ step_on(turn % CYCLE_LENGTH, 7, 7.5) - step_on(turn % CYCLE_LENGTH, 8.5, 9) +
+ step_on(turn % CYCLE_LENGTH, 9.5, 10) - step_on(turn % CYCLE_LENGTH, 11, 11.5) +
+ step_on(turn % CYCLE_LENGTH, 12, 12.5) - step_on(turn % CYCLE_LENGTH, 13.5, 14) +
+
+ step_on(turn % CYCLE_LENGTH, 14.5, 15) - step_on(turn % CYCLE_LENGTH, 15.5, 16) +
+ step_on(turn % CYCLE_LENGTH, 16.5, 17) - step_on(turn % CYCLE_LENGTH, 17.5, 18) +
+ step_on(turn % CYCLE_LENGTH, 18.5, 19) - step_on(turn % CYCLE_LENGTH, 19.5, 20),
+ */
+ turn => Math.sin(turn * Math.TAU / CYCLE_LENGTH * 7),
+ turn => Math.sin(turn * Math.TAU / CYCLE_LENGTH * 6),
+ turn => Math.sin(turn * Math.TAU / CYCLE_LENGTH * 5),
+ turn => Math.sin(turn * Math.TAU / CYCLE_LENGTH * 4),
+ turn => Math.sin(turn * Math.TAU / CYCLE_LENGTH * 3),
+ turn => Math.sin(turn * Math.TAU / CYCLE_LENGTH * 2),
+ turn => Math.sin(turn * Math.TAU / CYCLE_LENGTH),
+ ];
+
+ function quadratic_stepper(t) {
+ if (t < 0) {
+ return 0;
+ } else if (t < 0.5) {
+ return 2 * t**2;
+ } else if (t < 1) {
+ return (-2*((t-1)**2) + 1);
+ } else {
+ return 1;
+ }
+ }
+
+ function lerp(t, a, b) {
+ return (b - a) * t + a;
+ }
+
+ function superlerp(t, a, b, c, d) {
+ // [a, b] ; -a
+ // [0, b-a] ; /(b-a)
+ // [0, 1] ; *(d-c)
+ // [0, d-c] ; +c
+ // [c, d]
+ return (t - a) / (b - a) * (d - c) + c;
+ }
+
+ function step_on(t, a, b) {
+ return quadratic_stepper(superlerp(t, a, b, 0, 1));
+ }
+
+ function step_on_step_off(t, a, b, c, d) {
+ return step_on(t, a, b) - step_on(t, c, d);
+ }
+
+ function from_math_coordinates_to_canvas_coordinates(x, y, x_extents, y_extents) {
+ let canvas = document.getElementById('canvas');
+
+ // a, b / - a
+ // 0, b-a / * w
+ // 0, (b-a)w / /(b-a)
+ // 0, w
+
+ // a, b / *(-1)
+ // -b, -a / +b
+ // 0, (b-a) / * h
+ // 0, (b-a)h / /(b-a)
+ // 0, h
+
+ return [
+ (x - x_extents[0]) * canvas.width / (x_extents[1] - x_extents[0]),
+ ((-y) + y_extents[1]) * canvas.height / (y_extents[1] - y_extents[0]),
+ ];
+ }
+
+ function make_regular_polygon_path2d(x, y, radius, number_of_sides, turn) {
+ // `x` and `y` are the center of the triangle.
+ // `radius` is the distance from the center to a vertex.
+ // `number_of_sides` of the polygon.
+ // `turn` is a value [0, 1] where 1 is a full turn.
+
+ let regular_polygon = new Path2D();
+
+ regular_polygon.moveTo(
+ x + Math.cos(turn * Math.TAU) * radius,
+ y + Math.sin(turn * Math.TAU) * radius,
+ );
+
+ for (let vertex_i = 1; vertex_i < number_of_sides; vertex_i++) {
+ regular_polygon.lineTo(
+ x + Math.cos((vertex_i / number_of_sides + turn) * Math.TAU) * radius,
+ y + Math.sin((vertex_i / number_of_sides + turn) * Math.TAU) * radius,
+ );
+ }
+
+ regular_polygon.closePath();
+
+ return regular_polygon;
+ }
+
+ function linspace(start, stop, number_of_points) {
+ let l = [];
+ for (let point_i = 0; point_i < number_of_points; point_i++) {
+ l.push(superlerp(point_i, 0, number_of_points - 1, start, stop));
+ }
+ return l;
+ }
+
+ function draw_polygon(number_of_sides, turn, polygon_center_y, polygon_radius_addition) {
+ let canvas = document.getElementById('canvas');
+ let ctx = canvas.getContext('2d');
+
+ ctx.beginPath()
+ ctx.fillStyle = '#f0f'; // Fuchia from https://www.w3schools.com/colors/colors_names.asp
+ ctx.strokeStyle = '#f0f';
+ ctx.fill(
+ make_regular_polygon_path2d(
+ canvas.width / 2,
+ polygon_center_y,
+ 20 * canvas.height / 500 + polygon_radius_addition,
+ number_of_sides,
+ turn,
+ ),
+ );
+ ctx.stroke(
+ make_regular_polygon_path2d(
+ canvas.width / 2,
+ polygon_center_y,
+ 20 * canvas.height / 500 + polygon_radius_addition,
+ number_of_sides,
+ turn,
+ ),
+ );
+ }
+
+ function draw_graph(polygon_i) {
+ let canvas = document.getElementById('canvas');
+ let ctx = canvas.getContext('2d');
+
+ ctx.beginPath()
+ ctx.strokeStyle = 'white';
+ ctx.moveTo(canvas_ts[0], canvas_yss[polygon_i][0]);
+ for (let point_i = 1; point_i < NUMBER_OF_SAMPLES; point_i++) {
+ ctx.lineTo(canvas_ts[point_i], canvas_yss[polygon_i][point_i]);
+ }
+ ctx.stroke();
+ }
+
+ function draw_position_circle(x, y) {
+ let canvas = document.getElementById('canvas');
+ let ctx = canvas.getContext('2d');
+
+ ctx.beginPath();
+ ctx.fillStyle = "#00bfff"; // DeepSkyBlue from https://www.w3schools.com/colors/colors_names.asp
+ ctx.arc(x, y, canvas.height / 80, 0, Math.TAU, false);
+ ctx.fill();
+ }
+
+ function draw() {
+ let canvas = document.getElementById('canvas');
+ let ctx = canvas.getContext('2d');
+
+ ctx.beginPath();
+ ctx.fillStyle='black';
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ ctx.beginPath();
+ ctx.strokeStyle = 'white';
+
+ //ctx.moveTo(START_T, functions[polygon_i](-1) * canvas.width);
+
+ ctx.lineWidth = 2;
+
+ // [50, h - 50]
+ // 0, 1, 2 / /n-1
+ // 0, 0.5, 1 / *(h-h0.1)
+ // 0, 0.5(0.9h), 0.9h / +h0.05
+ // 50, 0.5(h-100)+0.05h, 0.9h+0.05h
+ for (let polygon_i = 0; polygon_i < NUMBER_OF_POLYGONS; polygon_i++) {
+ // top and bottom of the are dedicated to this graph.
+ let y_top = polygon_i * canvas.height / NUMBER_OF_POLYGONS;
+ let y_bottom = (polygon_i + 1) * canvas.height / NUMBER_OF_POLYGONS;
+
+ // the vertical value of the center of the current polygon.
+ let polygon_y = superlerp(
+ polygon_i,
+ 0,
+ NUMBER_OF_POLYGONS - 1,
+ POLYGON_HEIGHT_MARGIN * canvas.height,
+ canvas.height * (1 - POLYGON_HEIGHT_MARGIN),
+ );
+
+ // Get all cached values of the mathematical and graphical
+ let current_sample_i = Math.floor(turn / CYCLE_LENGTH * canvas_ts.length) % canvas_ts.length;
+ let current_math_t = math_ts[current_sample_i]; //superlerp(current_math_t, START_T, END_T, 0, canvas.width);
+ let current_canvas_t = canvas_ts[current_sample_i];
+ let current_math_y = math_yss[polygon_i][current_sample_i];
+ //let current_math_y = functions[1](current_math_t);
+ let current_canvas_y = canvas_yss[polygon_i][current_sample_i]; // superlerp(current_math_y, -1.2, 1.2, y_bottom, y_top);
+
+ draw_graph(polygon_i);
+
+ draw_polygon(polygon_i + 3, current_math_y, polygon_y, canvas_polygon_radiuses[polygon_i][current_sample_i]);
+
+ draw_position_circle(current_canvas_t, current_canvas_y);
+ }
+ }
+
+ function step(time_since_start) {
+ draw();
+
+ turn = TURN_PER_MILLISECOND * time_since_start;
+
+ window.requestAnimationFrame(step);
+ }
+
+ function resize_window() {
+ // https://stackoverflow.com/a/32119392
+
+ let canvas = document.getElementById('canvas');
+
+ canvas.width = window.innerWidth;
+ canvas.style.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ canvas.style.height = window.innerHeight;
+
+ cache_canvas_data();
+ }
+
+ function cache_math_data() {
+ math_ts = Float32Array.from(linspace(START_T, END_T, NUMBER_OF_SAMPLES));
+ math_yss = [];
+ math_polygon_radiuses = [];
+ for (let polygon_i = 0; polygon_i < NUMBER_OF_POLYGONS; polygon_i++) {
+ math_yss.push(Float32Array.from(math_ts));
+ for (let t_i = 0; t_i < math_ts.length; t_i++) {
+ let y = functions[polygon_i](math_ts[t_i]);
+ math_yss[polygon_i][t_i] = y;
+ }
+ math_polygon_radiuses[polygon_i] = math_yss[polygon_i].map(y => (y > 0.7) ? (y - 0.7) : 0);
+ }
+ }
+
+ function cache_canvas_data() {
+ let canvas = document.getElementById('canvas');
+
+ canvas_yss = [];
+ canvas_polygon_radiuses = [];
+ for (let polygon_i = 0; polygon_i < NUMBER_OF_POLYGONS; polygon_i++) {
+ let y_top = polygon_i * canvas.height / NUMBER_OF_POLYGONS;
+ let y_bottom = (polygon_i + 1) * canvas.height / NUMBER_OF_POLYGONS;
+
+ canvas_ts = math_ts.map(t => superlerp(t, START_T, END_T, 0, canvas.width));
+ canvas_yss[polygon_i] = math_yss[polygon_i].map(
+ y => superlerp(
+ y,
+ math_graph_y_extents[polygon_i][0],
+ math_graph_y_extents[polygon_i][1],
+ y_bottom, y_top));
+ canvas_polygon_radiuses[polygon_i] = math_polygon_radiuses[polygon_i].map(x => x * (canvas.height / 500) * 100)
+ }
+ }
+
+ function main() {
+ cache_math_data();
+ resize_window();
+
+ window.addEventListener('resize', resize_window)
+ window.requestAnimationFrame(step);
+ }
+
+ window.addEventListener('load', main);
+})();
diff --git a/dabbling/html/spinning-shapes/style.css b/dabbling/html/spinning-shapes/style.css
@@ -0,0 +1,11 @@
+/* Stolen from https://developer.mozilla.org/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Create_the_Canvas_and_draw_on_it */
+
+* {
+ padding: 0;
+ margin: 0;
+}
+canvas {
+ background: #000;
+ display: block;
+ margin: 0 auto;
+}
diff --git a/dabbling/html/tau-mandala/index.css b/dabbling/html/tau-mandala/index.css
@@ -0,0 +1,11 @@
+object {
+ /*
+ width: 100vw;
+ */
+ /*
+ height: 100vh;
+ */
+ width: 100%;
+ height: 100%;
+ border: 5px dotted red;
+}
diff --git a/dabbling/html/tau-mandala/index.html b/dabbling/html/tau-mandala/index.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>τ mandala</title>
+ <link href="index.css" rel="stylesheet">
+ </head>
+ <body>
+ <object data="tau-mandala.svg" type="image/svg+xml"></object>
+
+ <p>
+ Tau mandala idea stolen from <a href="https://youtube.com/c/Mathologer">Mathologer</a>'s T-shirt in his <a href="https://youtu.be/O1sPvUr0YC0">doubling cubes and squaring circles</a> video.
+ </p>
+
+ <p>
+ Used one of <a href="http://www.petercollingridge.co.uk/">Peter Collingridge</a>'s example SVG files (<a href="https://github.com/petercollingridge/code-for-blog/">up on his github repo</a>) from his <a href="http://www.petercollingridge.co.uk/tutorials/svg/interactive/javascript/">interactive SVG with javascript tutorial</a> as a skeleton for my SVG file. It is not much of code, but credit is due.
+ </p>
+ </body>
+</html>
diff --git a/dabbling/html/tau-mandala/tau-mandala.svg b/dabbling/html/tau-mandala/tau-mandala.svg
@@ -0,0 +1,83 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -10 20 20">
+ <script type="text/javascript"><![CDATA[
+'use strict';
+
+
+Math.TAU = 2 * Math.PI;
+
+function internal_angle(n) {
+ return (n - 2) * Math.TAU / (n * 2);
+};
+
+function set_view_box(svg_element, radius, margin) {
+ let high_margin = radius * (1 + margin);
+ let low_margin = -high_margin;
+ let width = high_margin - low_margin;
+ svg.setAttributeNS('http://www.w3.org/2000/svg', 'viewBox', `${low_margin} ${low_margin} ${width} ${width}`);
+};
+
+
+function rotate(point, angle) {
+ let [x, y] = point;
+ let [c_a, s_a] = [Math.cos(angle), Math.sin(angle)];
+ return [
+ x * c_a - y * s_a,
+ x * s_a + y * c_a,
+ ];
+};
+
+
+let radiuses = [1.0];
+let polygon_paths = [];
+for (let nagon = 3; nagon < 100; nagon++) {
+ let radius = radiuses[radiuses.length - 1];
+ let internal_angle = (nagon - 2) * Math.TAU / (2 * nagon);
+ radius = radius / Math.sin(internal_angle / 2);
+ radiuses.push(radius);
+ let polygon_path = [];
+ for (let i = 0; i < nagon; i++) {
+ let angle = Math.TAU * i / nagon;
+ let point = rotate([0, -radius], angle);
+ polygon_path.push(point);
+ };
+ polygon_paths.push(polygon_path);
+};
+
+
+let svg = document.getElementsByTagName('svg')[0];
+set_view_box(svg, radiuses[radiuses.length - 1], 0.1);
+radiuses.forEach((r, i, radiuses)=>{
+ let element = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
+ element.setAttributeNS(null, 'cx', 0);
+ element.setAttributeNS(null, 'cy', 0);
+ element.setAttributeNS(null, 'r', r);
+ element.setAttributeNS(null, 'fill', 'none');
+ element.setAttributeNS(null, 'stroke', 'black');
+ element.setAttributeNS(null, 'stroke-width', 0.1 / (i + 1));
+ svg.appendChild(element);
+});
+
+polygon_paths.forEach((polygon_path, i, polygon_paths)=>{
+ let polygon_element = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
+ let points = (
+ polygon_path
+ .map(point=>`${point[0]} ${point[1]}`)
+ .reduce((acc, x)=>acc.concat(' ').concat(x))
+ );
+ polygon_element.setAttributeNS(null, 'points', points);
+ polygon_element.setAttributeNS(null, 'fill', 'none');
+ polygon_element.setAttributeNS(null, 'stroke', 'black');
+ polygon_element.setAttributeNS(null, 'stroke-width', 0.2 / (i + 1));
+ let animate_transform_element = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
+ animate_transform_element.setAttributeNS(null, 'attributeName', 'transform');
+ animate_transform_element.setAttributeNS(null, 'attributeType', 'XML');
+ animate_transform_element.setAttributeNS(null, 'type', 'rotate');
+ animate_transform_element.setAttributeNS(null, 'from', '0 0 0');
+ animate_transform_element.setAttributeNS(null, 'to', '360 0 0');
+ animate_transform_element.setAttributeNS(null, 'dur', `${Math.log(i+10, 2)}s`);
+ animate_transform_element.setAttributeNS(null, 'repeatCount', 'indefinite');
+ polygon_element.appendChild(animate_transform_element);
+ svg.appendChild(polygon_element);
+});
+]]></script>
+</svg>
diff --git a/dabbling/html/tau/2020-darts.html b/dabbling/html/tau/2020-darts.html
@@ -0,0 +1,83 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>Tau Day 2020</title>
+ <style tyle="text/css">
+canvas {
+ border: 5px dotted red;
+}
+ </style>
+ <script type="text/javascript">
+'use strict';
+
+(
+ function() {
+ // COME ON ECMA!
+ // It is that easy!
+ // Get to it!
+ Math.TAU = 2 * Math.PI;
+
+ let number_of_points = 0;
+ let number_of_points_inside = 0;
+ function lerp(from, to, t) {
+ return to * t + from * (1 - t);
+ }
+ function unit_coordinate_to_canvas_coordinate(unit_x, canvas_length, margin) {
+ return lerp(canvas_length * margin, canvas_length * (1 - margin), (unit_x + 1)/2);
+ }
+ function main() {
+ let canvas = document.querySelector('#canvas');
+ let c = canvas.getContext('2d');
+ let margin = 0.1;
+
+ c.beginPath();
+ c.arc(canvas.width / 2, canvas.height / 2, 0.5 * canvas.width - margin * canvas.width, 0, Math.TAU, false);
+ c.stroke();
+ let unit_x = lerp(-1, 1, Math.random());
+ let unit_y = lerp(-1, 1, Math.random());
+ let canvas_x = unit_coordinate_to_canvas_coordinate(unit_x, canvas.width, margin);
+ let canvas_y = unit_coordinate_to_canvas_coordinate(unit_y, canvas.height, margin);
+
+ number_of_points += 1
+
+ if ((unit_x * unit_x + unit_y * unit_y) < 1) {
+ c.fillStyle = 'red';
+ number_of_points_inside += 1;
+ } else {
+ c.fillStyle = 'black';
+ };
+ c.beginPath();
+ c.arc(canvas_x, canvas_y, 1, 0, Math.TAU, false);
+ c.fill();
+ let tau_approximation = 8 * number_of_points_inside / number_of_points;
+ let tau_error = Math.TAU - tau_approximation;
+ let tau_text = `τ approximation =
+= ${Math.floor(tau_approximation * 100) / 100}
+ ${number_of_points_inside}
+= 8 * ------
+ ${number_of_points}
+
+error = ${tau_error}`;
+ let tau_text_element = document.querySelector('#text');
+ tau_text_element.innerText = tau_text;
+ if (Math.abs(tau_error) < 0.001) {
+ tau_text = `${tau_text}
+HAPPY τ DAY!`;
+ tau_text_element.innerText = tau_text;
+ return;
+ }
+
+ window.requestAnimationFrame(main);
+ };
+
+ window.addEventListener('load', main);
+ }
+)();
+ </script>
+ </head>
+ <body>
+ <pre id="text"></pre>
+ <canvas id="canvas" width="500" height="500"></canvas>
+ </body>
+</html>
diff --git a/dabbling/html/tau/2020.html b/dabbling/html/tau/2020.html
@@ -0,0 +1,100 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>Tau Day 2020</title>
+ <style tyle="text/css">
+canvas {
+ border: 5px dotted red;
+}
+ </style>
+ <script type="text/javascript">
+'use strict';
+
+(
+ function() {
+ let min_size_factor = 1;
+ let max_size_factor = 100;
+ let current_size_factor_phase = 0.0;
+ let size_factor_phase_increment = 0.01;
+ function lerp(from, to, t) {
+ return to * t + from * (1 - t);
+ }
+ function saw(phase) {
+ let new_phase = phase % 1.0;
+ let signal = 0;
+ if (new_phase < 0.25) {
+ signal = lerp(0, 1, new_phase / 0.25);
+ } else if (new_phase < 0.75) {
+ signal = lerp(1, -1, (new_phase - 0.25) / 0.5);
+ } else {
+ signal = lerp(-1, 0, (new_phase - 0.75) / 0.25);
+ };
+ return signal;
+ }
+ function unit_coordinate_to_canvas_coordinate(unit_x, canvas_length, margin) {
+ return lerp(canvas_length * margin, canvas_length * (1 - margin), (unit_x + 1)/2);
+ }
+ function index_to_unit_coordinate(i, points_on_edge, size_factor) {
+ return (2 * i / (points_on_edge - 1) - 1) * size_factor;
+ }
+ function main() {
+ let canvas = document.querySelector('#canvas');
+ let c = canvas.getContext('2d');
+ c.fillStyle = 'white';
+ c.fillRect(0, 0, canvas.width, canvas.height);
+ let margin = 0.1;
+
+ let size_factor_signal = saw(current_size_factor_phase);
+ let current_size_factor = lerp(max_size_factor, min_size_factor, (size_factor_signal + 1) / 2);
+
+ c.beginPath();
+ c.arc(canvas.width / 2, canvas.height / 2, 0.5 * canvas.width - margin * canvas.width, 0, 2 * Math.PI, false);
+ c.stroke();
+
+ let points_on_edge = 100;
+ let number_of_points = 0;
+ let number_of_points_inside = 0;
+ for (let i = 0; i < points_on_edge; ++i) {
+ for (let j = 0; j < points_on_edge; ++j) {
+ let unit_x = index_to_unit_coordinate(i, points_on_edge, current_size_factor);
+ let unit_y = index_to_unit_coordinate(j, points_on_edge, current_size_factor);
+ if ((unit_x > 1) || (unit_y > 1) || (unit_x < -1) || (unit_y < -1)) {
+ continue;
+ }
+
+ let canvas_x = unit_coordinate_to_canvas_coordinate(unit_x, canvas.width, margin);
+ let canvas_y = unit_coordinate_to_canvas_coordinate(unit_y, canvas.height, margin);
+
+ number_of_points += 1
+
+ if ((unit_x * unit_x + unit_y * unit_y) < 1) {
+ c.fillStyle = 'red';
+ number_of_points_inside += 1;
+ } else {
+ c.fillStyle = 'black';
+ };
+ c.beginPath();
+ c.arc(canvas_x, canvas_y, 1, 0, 2 * Math.PI, false);
+ c.fill();
+ }
+ };
+
+ current_size_factor_phase = (current_size_factor_phase + size_factor_phase_increment) % 1.0;
+
+ c.fillStyle = 'blue';
+ c.font = '10px helvetica';
+ c.fillText(`τ (approx): ${8 * number_of_points_inside / number_of_points} #points: ${number_of_points} #inside: ${number_of_points_inside}`, 10, 10);
+ // window.requestAnimationFrame(main);
+ };
+ window.setInterval(main, 1000);
+
+ window.addEventListener('load', main);
+ }
+)();
+ </script>
+ </head>
+ <body>
+ <canvas id="canvas" width="500" height="500"></canvas>
+ </body>
+</html>
diff --git a/dabbling/html/the-dancing-polygon-screensaver/index.html b/dabbling/html/the-dancing-polygon-screensaver/index.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <script src="main.js" type="text/javascript"></script>
+ <link rel="stylesheet" href="style.css"></link>
+ </head>
+ <body>
+ <canvas id="canvas" width="800" height="600"></canvas>
+ </body>
+</html>
diff --git a/dabbling/html/the-dancing-polygon-screensaver/main.js b/dabbling/html/the-dancing-polygon-screensaver/main.js
@@ -0,0 +1,217 @@
+'use strict;'
+
+Math.TAU = 2 * Math.PI;
+
+(function() {
+ let start_time;
+ let elapsed_time;
+ let previous_timestamp;
+
+ let polygons_vertices = [];
+ let polygon_velocity = [];
+
+ let hsv_values = [[0, 1, 0.7]];
+ let color_wheel_velocity = 0.01;
+
+ let wanted_number_of_polygons = 50;
+ let pixels_per_step = 10;
+
+ function hsv_to_rgb(hue, saturation, value) {
+ // https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB
+ // H in [0, 1] (position on the color wheel)
+ // S in [0, 1]
+ // V in [0, 1]
+
+ let chroma = value * saturation;
+
+ let hue_tag = hue * 6;
+
+ let x = chroma * (1 - Math.abs((hue_tag % 2) - 1));
+
+ let [red_1, green_1, blue_1] = ((hue_tag, chroma, x)=>{
+ if (hue_tag >= 0 && hue_tag < 1) { return [chroma, x, 0];
+ } else if (hue_tag >= 1 && hue_tag < 2) { return [x, chroma, 0];
+ } else if (hue_tag >= 2 && hue_tag < 3) { return [0, chroma, x];
+ } else if (hue_tag >= 3 && hue_tag < 4) { return [0, x, chroma];
+ } else if (hue_tag >= 4 && hue_tag < 5) { return [x, 0, chroma];
+ } else if (hue_tag >= 5 && hue_tag < 6) { return [chroma, 0, x];
+ }
+ })(hue_tag, chroma, x);
+
+ let m = value - chroma;
+
+ return [red_1 + m, green_1 + m, blue_1 + m];
+ }
+
+ function move_and_bounce_1d(position, velocity, maximum_value) {
+ let new_position = position + velocity;
+
+ if (new_position > maximum_value) {
+ return [2 * maximum_value - new_position, -Math.abs(velocity)];
+ } else if (new_position < 0) {
+ return [Math.abs(new_position), Math.abs(velocity)];
+ } else {
+ return [new_position, velocity];
+ };
+
+ }
+
+ function move_and_bounce_2d(position, velocity, width, height) {
+ let [position_x, velocity_x] = move_and_bounce_1d(position[0], velocity[0], width);
+ let [position_y, velocity_y] = move_and_bounce_1d(position[1], velocity[1], height);
+
+ return [
+ [position_x, position_y],
+ [velocity_x, velocity_y],
+ ];
+ }
+
+ function move_polygon(polygon_vertices, polygon_velocity) {
+ for (let vertex_number = 0; vertex_number < polygon_vertices.length; vertex_number++) {
+ let [new_position, new_velocity] = move_and_bounce_2d(
+ polygon_vertices[vertex_number],
+ polygon_velocity[vertex_number],
+ canvas.width,
+ canvas.height,
+ );
+
+ polygon_vertices[vertex_number] = new_position;
+ polygon_velocity[vertex_number] = new_velocity;
+ }
+ };
+
+ function draw_polygon(ctx, polygon_vertices, rgb) {
+ ctx.beginPath();
+
+ ctx.strokeStyle = `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
+
+ ctx.moveTo(polygon_vertices[0][0], polygon_vertices[0][1]);
+
+ for (let vertex_number = 1; vertex_number < polygon_vertices.length; vertex_number++) {
+ ctx.lineTo(polygon_vertices[vertex_number][0], polygon_vertices[vertex_number][1]);
+ }
+
+ ctx.closePath();
+ ctx.stroke();
+ }
+
+ function uniform_random_direction() {
+ let uniform = Math.random();
+ let direction_vector = [
+ Math.cos(uniform * Math.TAU),
+ Math.sin(uniform * Math.TAU),
+ ];
+ return direction_vector;
+ }
+
+ function step_polygons() {
+ if (polygons_vertices.length >= wanted_number_of_polygons) {
+ // Remove oldest polygon, the one at position 0.
+ polygons_vertices = polygons_vertices.slice(1, polygons_vertices.length);
+ }
+
+ // Get the newest polygon at position n-1.
+ let current_polygon_vertices = polygons_vertices[polygons_vertices.length - 1];
+
+ // Clone our latest polygon.
+ let new_polygon_vertices = current_polygon_vertices.map(vertex => vertex.map(axis => axis));
+
+ move_polygon(new_polygon_vertices, polygon_velocity);
+
+ polygons_vertices.push(new_polygon_vertices);
+ }
+
+ function step_hsv_values() {
+ if (hsv_values.length >= wanted_number_of_polygons) {
+ // Remove oldest hsv value, the one at position 0.
+ hsv_values = hsv_values.slice(1, hsv_values.length);
+ }
+
+ // Get the newest hsv_value at position n-1.
+ let current_hsv_value = hsv_values[hsv_values.length - 1];
+
+ // Clone.
+ let new_hsv_value = current_hsv_value.map(axis => axis);
+
+ // Spin the hue around in the color wheel.
+ new_hsv_value[0] = (new_hsv_value[0] + color_wheel_velocity) % 1;
+
+ hsv_values.push(new_hsv_value);
+ }
+
+ function draw_polygons(ctx, polygons_vertices, hsv_values) {
+ for (let current_polygon = 0; current_polygon < polygons_vertices.length; current_polygon++) {
+ let rgb = hsv_to_rgb(
+ hsv_values[current_polygon][0],
+ hsv_values[current_polygon][1],
+ hsv_values[current_polygon][2],
+ ).map(x => 255 * x);
+ draw_polygon(ctx, polygons_vertices[current_polygon], rgb);
+ }
+ }
+
+ function step(timestamp) {
+ if (start_time === undefined) {
+ start_time = timestamp;
+ }
+
+ elapsed_time = timestamp - start_time;
+
+ delta_time = timestamp - previous_timestamp;
+
+ let canvas = document.getElementById('canvas');
+ let ctx = canvas.getContext('2d');
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ step_polygons();
+ step_hsv_values();
+
+ draw_polygons(ctx, polygons_vertices, hsv_values);
+
+ previous_timestamp = timestamp;
+
+ window.requestAnimationFrame(step)
+ }
+
+ function resize_window() {
+ // https://stackoverflow.com/a/32119392
+
+ let canvas = document.getElementById('canvas');
+
+ canvas.width = window.innerWidth;
+ canvas.style.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ canvas.style.height = window.innerHeight;
+ }
+
+ function reset_world() {
+ polygons_vertices = [[
+ [canvas.width / 2, canvas.height / 3],
+ [2 * canvas.width / 3, 2 * canvas.height / 3],
+ [canvas.width / 3, 2 * canvas.height / 3],
+ ]];
+
+ polygon_velocity = [
+ uniform_random_direction(),
+ uniform_random_direction(),
+ uniform_random_direction(),
+ ].map(vertex => vertex.map(axis => pixels_per_step * axis));
+ }
+
+ function main() {
+ let canvas = document.getElementById('canvas');
+ let ctx = canvas.getContext('2d');
+
+ resize_window();
+ reset_world();
+
+ canvas.addEventListener('click', reset_world);
+
+ window.addEventListener('resize', resize_window)
+
+ window.requestAnimationFrame(step);
+ }
+
+ window.addEventListener('load', main);
+})();
diff --git a/dabbling/html/the-dancing-polygon-screensaver/style.css b/dabbling/html/the-dancing-polygon-screensaver/style.css
@@ -0,0 +1,11 @@
+/* Stolen from https://developer.mozilla.org/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Create_the_Canvas_and_draw_on_it */
+
+* {
+ padding: 0;
+ margin: 0;
+}
+canvas {
+ background: #000;
+ display: block;
+ margin: 0 auto;
+}
diff --git a/dabbling/html/wtc_plaza_2001-09-11/freesound.org/data/previews/239/239900_4079949-lq.mp3 b/dabbling/html/wtc_plaza_2001-09-11/freesound.org/data/previews/239/239900_4079949-lq.mp3
Binary files differ.
diff --git a/dabbling/html/wtc_plaza_2001-09-11/freesound.org/data/previews/344/344150_6179115-lq.mp3 b/dabbling/html/wtc_plaza_2001-09-11/freesound.org/data/previews/344/344150_6179115-lq.mp3
Binary files differ.
diff --git a/dabbling/html/wtc_plaza_2001-09-11/index.html b/dabbling/html/wtc_plaza_2001-09-11/index.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <title>WTC Plaza circa 2001-09-11.</title>
+ <script type="text/javascript" src="wtc_plaza_2001-09-11.js"></script>
+ </head>
+ <body>
+ WTC Plaza circa 2001-09-11:
+ <canvas id="canvas" width="500" height="500"></canvas>
+ <audio id="scream_audio" src="freesound.org/data/previews/239/239900_4079949-lq.mp3"></audio>
+ <audio id="thump_audio" src="freesound.org/data/previews/344/344150_6179115-lq.mp3"></audio>
+ <button id="jump_button">Jump</button>
+ <button id="clear_button">Clear</button>
+ <hr>
+ <aside>
+ <a href="https://freesound.org/people/TheSubber13/sounds/239900/">The Scream</a> is licensed <a href="http://creativecommons.org/licenses/by-nc/3.0/">CC-By-NC 3.0 (boo!)</a> by <a href="https://freesound.org/people/TheSubber13/">TheSubber13</a>.
+ </aside>
+ <aside>
+ <a href="https://freesound.org/people/Brokenphono/sounds/344150/">The Thump</a> is licensed <a href="http://creativecommons.org/publicdomain/zero/1.0/">CC-Zero 1.0</a> by <a href="https://freesound.org/people/Brokenphono/">Brokenphono</a>.
+ </aside>
+ </body>
+</html>
diff --git a/dabbling/html/wtc_plaza_2001-09-11/wtc_plaza_2001-09-11.js b/dabbling/html/wtc_plaza_2001-09-11/wtc_plaza_2001-09-11.js
@@ -0,0 +1,290 @@
+'use strict';
+
+// Why is it not part of the standard yet?
+Math.TAU = 2 * Math.PI;
+
+(function() {
+ let audio_context;
+ let scream_audio_data;
+ let thump_audio_data;
+
+ let BLOOD_COLOR = (0, 0, 255, 255);
+ let STICK_FIGURE_COLOR = (0, 0, 0, 255);
+
+ function lerp(portion, start, stop) {
+ return portion * (stop - start) + start;
+ }
+
+ function make_random_direction() {
+ let uniform = Math.random();
+ return [
+ Math.cos(Math.TAU * uniform),
+ Math.sin(Math.TAU * uniform),
+ ];
+ };
+
+ function add_random_position(original_position, distance) {
+ let new_direction = make_random_direction();
+
+ return [
+ original_position[0] + distance * new_direction[0],
+ original_position[1] + distance * new_direction[1],
+ ];
+ }
+
+ function make_random_stick_figure_positions() {
+ let head_neck_distance = 0.05;
+ let elbow_neck_distance = 0.050;
+ let arm_tip_elbow_distance = 0.050;
+ let pelvis_neck_distance = 0.125;
+ let knee_pelvis_distance = 0.075;
+ let leg_tip_knee_distance = 0.075;
+
+ let head_position = [Math.random(), Math.random()];
+
+ let neck_position = add_random_position(head_position, head_neck_distance);
+
+ let left_elbow_position = add_random_position(neck_position, elbow_neck_distance);
+ let right_elbow_position = add_random_position(neck_position, elbow_neck_distance);
+
+ let left_arm_tip_position = add_random_position(left_elbow_position, arm_tip_elbow_distance);
+ let right_arm_tip_position = add_random_position(right_elbow_position, arm_tip_elbow_distance);
+
+ let pelvis_position = add_random_position(neck_position, pelvis_neck_distance);
+
+ let left_knee_position = add_random_position(pelvis_position, knee_pelvis_distance);
+ let right_knee_position = add_random_position(pelvis_position, knee_pelvis_distance);
+
+ let left_leg_top_position = add_random_position(left_knee_position, leg_tip_knee_distance);
+ let right_leg_top_position = add_random_position(right_knee_position, leg_tip_knee_distance);
+
+ return [
+ head_position,
+ neck_position,
+ left_elbow_position,
+ right_elbow_position,
+ left_arm_tip_position,
+ right_arm_tip_position,
+ pelvis_position,
+ left_knee_position,
+ right_knee_position,
+ left_leg_top_position,
+ right_leg_top_position,
+ ];
+ }
+
+
+
+
+ function make_blood_splatter_positions(stick_figure_positions) {
+ let blood_splatter_positions = [];
+
+ for (let i = 0; i < 3; i++) {
+ blood_splatter_positions.push(
+ add_random_position(stick_figure_positions[1], 0.05 * (i + 1))
+ );
+ }
+
+ for (let i = 0; i < 3; i++) {
+ blood_splatter_positions.push(
+ add_random_position(stick_figure_positions[6], 0.05 * (i + 1))
+ );
+ }
+
+ return blood_splatter_positions;
+ }
+
+ function draw_blood_splatter(blood_splatter_positions) {
+ let canvas = document.getElementById('canvas');
+ let ctx = canvas.getContext('2d');
+
+ let image_width = ctx.canvas.width;
+ let image_height = ctx.canvas.height;
+
+ let blood_splatter_positions_in_image = blood_splatter_positions.map(
+ position => [
+ Math.floor(position[0] * image_width),
+ Math.floor(position[1] * image_height),
+ ]
+ );
+
+
+ ctx.fillStyle = '#f00';
+
+ ctx.beginPath();
+ ctx.arc(blood_splatter_positions_in_image[0][0], blood_splatter_positions_in_image[0][1], 20, 0, Math.TAU);
+ ctx.fill();
+
+ ctx.beginPath();
+ ctx.arc(blood_splatter_positions_in_image[1][0], blood_splatter_positions_in_image[1][1], 10, 0, Math.TAU);
+ ctx.fill();
+
+ ctx.beginPath();
+ ctx.arc(blood_splatter_positions_in_image[2][0], blood_splatter_positions_in_image[2][1], 5, 0, Math.TAU);
+ ctx.fill();
+
+ ctx.beginPath();
+ ctx.arc(blood_splatter_positions_in_image[3][0], blood_splatter_positions_in_image[3][1], 20, 0, Math.TAU);
+ ctx.fill();
+
+ ctx.beginPath();
+ ctx.arc(blood_splatter_positions_in_image[4][0], blood_splatter_positions_in_image[4][1], 10, 0, Math.TAU);
+ ctx.fill();
+
+ ctx.beginPath();
+ ctx.arc(blood_splatter_positions_in_image[5][0], blood_splatter_positions_in_image[5][1], 5, 0, Math.TAU);
+ ctx.fill();
+
+ ctx.fill()
+ }
+
+
+ function draw_stick_figure(stick_figure_positions) {
+ let canvas = document.getElementById('canvas');
+ let ctx = canvas.getContext('2d');
+
+ let image_width = ctx.canvas.width;
+ let image_height = ctx.canvas.height;
+
+ let stick_figure_positions_in_image = stick_figure_positions.map(
+ x => [
+ Math.floor(x[0] * image_width),
+ Math.floor(x[1] * image_height),
+ ]
+ );
+ console.debug(stick_figure_positions_in_image)
+
+ ctx.fillStyle = '#000';
+ ctx.strokeStyle = '#000';
+ ctx.lineWidth = 2;
+
+ ctx.beginPath();
+ ctx.arc(
+ stick_figure_positions_in_image[0][0],
+ stick_figure_positions_in_image[0][1],
+ 15,
+ 0,
+ Math.TAU,
+ false);
+ ctx.fill();
+
+ let connections = [
+ [0, 1],
+ [1, 2],
+ [1, 3],
+ [2, 4],
+ [3, 5],
+ [1, 6],
+ [7, 6],
+ [8, 6],
+ [7, 9],
+ [8, 10],
+ ];
+ for (let i = 0; i < connections.length; i++) {
+ ctx.beginPath();
+ let origin_coordinates = stick_figure_positions_in_image[connections[i][0]];
+ let destination_coordinates = stick_figure_positions_in_image[connections[i][1]];
+ console.debug([origin_coordinates, destination_coordinates])
+ ctx.moveTo(origin_coordinates[0], origin_coordinates[1]);
+ ctx.lineTo(destination_coordinates[0], destination_coordinates[1]);
+ ctx.stroke();
+ }
+ }
+
+ function draw_jumper() {
+ let stick_figure_positions = make_random_stick_figure_positions();
+ let blood_splatter_positions = make_blood_splatter_positions(stick_figure_positions);
+
+ console.debug(stick_figure_positions);
+ console.debug(blood_splatter_positions);
+
+ draw_blood_splatter(blood_splatter_positions);
+ draw_stick_figure(stick_figure_positions);
+ }
+
+
+ function jump() {
+ let scream_rate_fraction = lerp(Math.random(), 0.90, 1.45);
+ let scream_audio_buffer = make_audio_buffer(scream_audio_data, scream_rate_fraction);
+ let scream_source = make_audio_buffer_source(scream_audio_buffer);
+ scream_source.start();
+
+ setTimeout(function() {
+ scream_source.stop();
+
+ let thump_audio_buffer = make_audio_buffer(thump_audio_data, 1);
+ let thump_audio_source = make_audio_buffer_source(thump_audio_buffer);
+
+ thump_audio_source.currentTime = 0.2;
+
+ thump_audio_source.start()
+
+ draw_jumper();
+ }, 1700.0 / scream_rate_fraction);
+ }
+
+ function clear_canvas() {
+ let canvas = document.getElementById('canvas');
+ let ctx = canvas.getContext('2d');
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ }
+
+ async function get_audio_data(audio_file_path) {
+ let response = await fetch(audio_file_path);
+
+ let array_buffer = await response.arrayBuffer();
+
+ return await audio_context.decodeAudioData(array_buffer);
+ }
+
+ function make_audio_buffer(audio_data, sample_rate_fraction) {
+ let audio_buffer = audio_context.createBuffer(
+ audio_data.numberOfChannels,
+ audio_data.getChannelData(0).length,
+ audio_data.sampleRate * sample_rate_fraction,
+ );
+
+ for (let channel_number = 0; channel_number < audio_buffer.numberOfChannels; channel_number++) {
+ let current_buffer = audio_buffer.getChannelData(channel_number);
+ let audio_data_channel = audio_data.getChannelData(channel_number);
+
+ for (let sample_number = 0; sample_number < current_buffer.length; sample_number++) {
+ current_buffer[sample_number] = audio_data_channel[sample_number];
+ }
+ }
+
+ return audio_buffer;
+ }
+
+ function make_audio_buffer_source(audio_buffer) {
+ let buffer_source = audio_context.createBufferSource();
+
+ buffer_source.buffer = audio_buffer;
+
+ buffer_source.connect(audio_context.destination);
+
+ return buffer_source;
+ }
+
+ async function main() {
+ audio_context = new AudioContext();
+
+ let scream_audio_element = document.getElementById('scream_audio');
+ let thump_audio_element = document.getElementById('thump_audio');
+
+ scream_audio_data = await get_audio_data(scream_audio_element.src);
+ thump_audio_data = await get_audio_data(thump_audio_element.src);
+
+ let canvas = document.getElementById('canvas');
+ canvas.addEventListener('click', jump);
+
+ let jump_button = document.getElementById('jump_button');
+ jump_button.addEventListener('click', jump);
+
+ let clear_button = document.getElementById('clear_button');
+ clear_button.addEventListener('click', clear_canvas);
+ }
+
+ window.addEventListener('load', main);
+})();
diff --git a/dabbling/poetry.lock b/dabbling/poetry.lock
@@ -0,0 +1,1561 @@
+[[package]]
+category = "dev"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+name = "appdirs"
+optional = false
+python-versions = "*"
+version = "1.4.4"
+
+[[package]]
+category = "main"
+description = "Disable App Nap on OS X 10.9"
+marker = "python_version >= \"3.3\" and sys_platform == \"darwin\" or platform_system == \"Darwin\""
+name = "appnope"
+optional = false
+python-versions = "*"
+version = "0.1.0"
+
+[[package]]
+category = "dev"
+description = "Atomic file writes."
+marker = "sys_platform == \"win32\""
+name = "atomicwrites"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "1.4.0"
+
+[[package]]
+category = "main"
+description = "Classes Without Boilerplate"
+name = "attrs"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "19.3.0"
+
+[package.extras]
+azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"]
+dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"]
+docs = ["sphinx", "zope.interface"]
+tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
+
+[[package]]
+category = "main"
+description = "Specifications for callback functions passed in to an API"
+marker = "python_version >= \"3.3\""
+name = "backcall"
+optional = false
+python-versions = "*"
+version = "0.2.0"
+
+[[package]]
+category = "main"
+description = "Screen-scraping library"
+name = "beautifulsoup4"
+optional = false
+python-versions = "*"
+version = "4.9.1"
+
+[package.dependencies]
+soupsieve = [">1.2", "<2.0"]
+
+[package.extras]
+html5lib = ["html5lib"]
+lxml = ["lxml"]
+
+[[package]]
+category = "dev"
+description = "The uncompromising code formatter."
+name = "black"
+optional = false
+python-versions = ">=3.6"
+version = "19.10b0"
+
+[package.dependencies]
+appdirs = "*"
+attrs = ">=18.1.0"
+click = ">=6.5"
+pathspec = ">=0.6,<1"
+regex = "*"
+toml = ">=0.9.4"
+typed-ast = ">=1.4.0"
+
+[package.extras]
+d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
+
+[[package]]
+category = "main"
+description = "An easy safelist-based HTML-sanitizing tool."
+name = "bleach"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "3.1.5"
+
+[package.dependencies]
+packaging = "*"
+six = ">=1.9.0"
+webencodings = "*"
+
+[[package]]
+category = "main"
+description = "Python package for providing Mozilla's CA Bundle."
+name = "certifi"
+optional = false
+python-versions = "*"
+version = "2020.6.20"
+
+[[package]]
+category = "main"
+description = "Universal encoding detector for Python 2 and 3"
+name = "chardet"
+optional = false
+python-versions = "*"
+version = "3.0.4"
+
+[[package]]
+category = "main"
+description = "Composable command line interface toolkit"
+name = "click"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "7.1.2"
+
+[[package]]
+category = "main"
+description = "Cross-platform colored terminal text."
+marker = "python_version >= \"3.3\" and sys_platform == \"win32\" or sys_platform == \"win32\""
+name = "colorama"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "0.4.3"
+
+[[package]]
+category = "dev"
+description = "Code coverage measurement for Python"
+name = "coverage"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+version = "5.1"
+
+[package.dependencies]
+[package.dependencies.toml]
+optional = true
+version = "*"
+
+[package.extras]
+toml = ["toml"]
+
+[[package]]
+category = "main"
+description = "Composable style cycles"
+name = "cycler"
+optional = false
+python-versions = "*"
+version = "0.10.0"
+
+[package.dependencies]
+six = "*"
+
+[[package]]
+category = "main"
+description = "Decorators for Humans"
+name = "decorator"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*"
+version = "4.4.2"
+
+[[package]]
+category = "main"
+description = "XML bomb protection for Python stdlib modules"
+name = "defusedxml"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "0.6.0"
+
+[[package]]
+category = "main"
+description = "Discover and load entry points from installed packages."
+name = "entrypoints"
+optional = false
+python-versions = ">=2.7"
+version = "0.3"
+
+[[package]]
+category = "main"
+description = "Internationalized Domain Names in Applications (IDNA)"
+name = "idna"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "2.9"
+
+[[package]]
+category = "main"
+description = "Read metadata from Python packages"
+marker = "python_version < \"3.8\""
+name = "importlib-metadata"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+version = "1.6.1"
+
+[package.dependencies]
+zipp = ">=0.5"
+
+[package.extras]
+docs = ["sphinx", "rst.linker"]
+testing = ["packaging", "pep517", "importlib-resources (>=1.3)"]
+
+[[package]]
+category = "main"
+description = "IPython Kernel for Jupyter"
+name = "ipykernel"
+optional = false
+python-versions = ">=3.5"
+version = "5.3.0"
+
+[package.dependencies]
+appnope = "*"
+ipython = ">=5.0.0"
+jupyter-client = "*"
+tornado = ">=4.2"
+traitlets = ">=4.1.0"
+
+[package.extras]
+test = ["pytest (!=5.3.4)", "pytest-cov", "flaky", "nose"]
+
+[[package]]
+category = "main"
+description = "IPython: Productive Interactive Computing"
+name = "ipython"
+optional = false
+python-versions = ">=3.6"
+version = "7.15.0"
+
+[package.dependencies]
+appnope = "*"
+backcall = "*"
+colorama = "*"
+decorator = "*"
+jedi = ">=0.10"
+pexpect = "*"
+pickleshare = "*"
+prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0"
+pygments = "*"
+setuptools = ">=18.5"
+traitlets = ">=4.2"
+
+[package.extras]
+all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.14)", "pygments", "qtconsole", "requests", "testpath"]
+doc = ["Sphinx (>=1.3)"]
+kernel = ["ipykernel"]
+nbconvert = ["nbconvert"]
+nbformat = ["nbformat"]
+notebook = ["notebook", "ipywidgets"]
+parallel = ["ipyparallel"]
+qtconsole = ["qtconsole"]
+test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"]
+
+[[package]]
+category = "main"
+description = "Vestigial utilities from IPython"
+name = "ipython-genutils"
+optional = false
+python-versions = "*"
+version = "0.2.0"
+
+[[package]]
+category = "main"
+description = "IPython HTML widgets for Jupyter"
+name = "ipywidgets"
+optional = false
+python-versions = "*"
+version = "7.5.1"
+
+[package.dependencies]
+ipykernel = ">=4.5.1"
+nbformat = ">=4.2.0"
+traitlets = ">=4.3.1"
+widgetsnbextension = ">=3.5.0,<3.6.0"
+
+[package.dependencies.ipython]
+python = ">=3.3"
+version = ">=4.0.0"
+
+[package.extras]
+test = ["pytest (>=3.6.0)", "pytest-cov", "mock"]
+
+[[package]]
+category = "main"
+description = "An autocompletion tool for Python that can be used for text editors."
+marker = "python_version >= \"3.3\""
+name = "jedi"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "0.17.1"
+
+[package.dependencies]
+parso = ">=0.7.0,<0.8.0"
+
+[package.extras]
+qa = ["flake8 (3.7.9)"]
+testing = ["Django (<3.1)", "colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"]
+
+[[package]]
+category = "main"
+description = "A very fast and expressive template engine."
+name = "jinja2"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "2.11.2"
+
+[package.dependencies]
+MarkupSafe = ">=0.23"
+
+[package.extras]
+i18n = ["Babel (>=0.8)"]
+
+[[package]]
+category = "main"
+description = "An implementation of JSON Schema validation for Python"
+name = "jsonschema"
+optional = false
+python-versions = "*"
+version = "3.2.0"
+
+[package.dependencies]
+attrs = ">=17.4.0"
+pyrsistent = ">=0.14.0"
+setuptools = "*"
+six = ">=1.11.0"
+
+[package.dependencies.importlib-metadata]
+python = "<3.8"
+version = "*"
+
+[package.extras]
+format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"]
+format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator (>0.1.0)", "rfc3339-validator"]
+
+[[package]]
+category = "main"
+description = "Jupyter metapackage. Install all the Jupyter components in one go."
+name = "jupyter"
+optional = false
+python-versions = "*"
+version = "1.0.0"
+
+[package.dependencies]
+ipykernel = "*"
+ipywidgets = "*"
+jupyter-console = "*"
+nbconvert = "*"
+notebook = "*"
+qtconsole = "*"
+
+[[package]]
+category = "main"
+description = "Jupyter protocol implementation and client libraries"
+name = "jupyter-client"
+optional = false
+python-versions = ">=3.5"
+version = "6.1.3"
+
+[package.dependencies]
+jupyter-core = ">=4.6.0"
+python-dateutil = ">=2.1"
+pyzmq = ">=13"
+tornado = ">=4.1"
+traitlets = "*"
+
+[package.extras]
+test = ["ipykernel", "ipython", "mock", "pytest"]
+
+[[package]]
+category = "main"
+description = "Jupyter terminal console"
+name = "jupyter-console"
+optional = false
+python-versions = ">=3.5"
+version = "6.1.0"
+
+[package.dependencies]
+ipykernel = "*"
+ipython = "*"
+jupyter-client = "*"
+prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0"
+pygments = "*"
+
+[package.extras]
+test = ["pexpect"]
+
+[[package]]
+category = "main"
+description = "Jupyter core package. A base package on which Jupyter projects rely."
+name = "jupyter-core"
+optional = false
+python-versions = "!=3.0,!=3.1,!=3.2,!=3.3,!=3.4,>=2.7"
+version = "4.6.3"
+
+[package.dependencies]
+pywin32 = ">=1.0"
+traitlets = "*"
+
+[[package]]
+category = "main"
+description = "A fast implementation of the Cassowary constraint solver"
+name = "kiwisolver"
+optional = false
+python-versions = ">=3.6"
+version = "1.2.0"
+
+[[package]]
+category = "main"
+description = "Safely add untrusted strings to HTML/XML markup."
+name = "markupsafe"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
+version = "1.1.1"
+
+[[package]]
+category = "main"
+description = "Python plotting package"
+name = "matplotlib"
+optional = false
+python-versions = ">=3.6"
+version = "3.2.2"
+
+[package.dependencies]
+cycler = ">=0.10"
+kiwisolver = ">=1.0.1"
+numpy = ">=1.11"
+pyparsing = ">=2.0.1,<2.0.4 || >2.0.4,<2.1.2 || >2.1.2,<2.1.6 || >2.1.6"
+python-dateutil = ">=2.1"
+
+[[package]]
+category = "main"
+description = "The fastest markdown parser in pure Python"
+name = "mistune"
+optional = false
+python-versions = "*"
+version = "0.8.4"
+
+[[package]]
+category = "dev"
+description = "More routines for operating on iterables, beyond itertools"
+name = "more-itertools"
+optional = false
+python-versions = ">=3.5"
+version = "8.4.0"
+
+[[package]]
+category = "dev"
+description = "Optional static typing for Python"
+name = "mypy"
+optional = false
+python-versions = ">=3.5"
+version = "0.780"
+
+[package.dependencies]
+mypy-extensions = ">=0.4.3,<0.5.0"
+typed-ast = ">=1.4.0,<1.5.0"
+typing-extensions = ">=3.7.4"
+
+[package.extras]
+dmypy = ["psutil (>=4.0)"]
+
+[[package]]
+category = "dev"
+description = "Experimental type system extensions for programs checked with the mypy typechecker."
+name = "mypy-extensions"
+optional = false
+python-versions = "*"
+version = "0.4.3"
+
+[[package]]
+category = "main"
+description = "Converting Jupyter Notebooks"
+name = "nbconvert"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "5.6.1"
+
+[package.dependencies]
+bleach = "*"
+defusedxml = "*"
+entrypoints = ">=0.2.2"
+jinja2 = ">=2.4"
+jupyter-core = "*"
+mistune = ">=0.8.1,<2"
+nbformat = ">=4.4"
+pandocfilters = ">=1.4.1"
+pygments = "*"
+testpath = "*"
+traitlets = ">=4.2"
+
+[package.extras]
+all = ["pytest", "pytest-cov", "ipykernel", "jupyter-client (>=5.3.1)", "ipywidgets (>=7)", "pebble", "tornado (>=4.0)", "sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "sphinxcontrib-github-alt", "ipython", "mock"]
+docs = ["sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "sphinxcontrib-github-alt", "ipython", "jupyter-client (>=5.3.1)"]
+execute = ["jupyter-client (>=5.3.1)"]
+serve = ["tornado (>=4.0)"]
+test = ["pytest", "pytest-cov", "ipykernel", "jupyter-client (>=5.3.1)", "ipywidgets (>=7)", "pebble", "mock"]
+
+[[package]]
+category = "main"
+description = "The Jupyter Notebook format"
+name = "nbformat"
+optional = false
+python-versions = ">=3.5"
+version = "5.0.7"
+
+[package.dependencies]
+ipython-genutils = "*"
+jsonschema = ">=2.4,<2.5.0 || >2.5.0"
+jupyter-core = "*"
+traitlets = ">=4.1"
+
+[package.extras]
+test = ["pytest", "pytest-cov", "testpath"]
+
+[[package]]
+category = "main"
+description = "A web-based notebook environment for interactive computing"
+name = "notebook"
+optional = false
+python-versions = ">=3.5"
+version = "6.0.3"
+
+[package.dependencies]
+Send2Trash = "*"
+ipykernel = "*"
+ipython-genutils = "*"
+jinja2 = "*"
+jupyter-client = ">=5.3.4"
+jupyter-core = ">=4.6.1"
+nbconvert = "*"
+nbformat = "*"
+prometheus-client = "*"
+pyzmq = ">=17"
+terminado = ">=0.8.1"
+tornado = ">=5.0"
+traitlets = ">=4.2.1"
+
+[package.extras]
+test = ["nose", "coverage", "requests", "nose-warnings-filters", "nbval", "nose-exclude", "selenium", "pytest", "pytest-cov", "nose-exclude"]
+
+[[package]]
+category = "main"
+description = "NumPy is the fundamental package for array computing with Python."
+name = "numpy"
+optional = false
+python-versions = ">=3.6"
+version = "1.19.0"
+
+[[package]]
+category = "main"
+description = "Core utilities for Python packages"
+name = "packaging"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "20.4"
+
+[package.dependencies]
+pyparsing = ">=2.0.2"
+six = "*"
+
+[[package]]
+category = "main"
+description = "Powerful data structures for data analysis, time series, and statistics"
+name = "pandas"
+optional = false
+python-versions = ">=3.6.1"
+version = "1.0.5"
+
+[package.dependencies]
+numpy = ">=1.13.3"
+python-dateutil = ">=2.6.1"
+pytz = ">=2017.2"
+
+[package.extras]
+test = ["pytest (>=4.0.2)", "pytest-xdist", "hypothesis (>=3.58)"]
+
+[[package]]
+category = "main"
+description = "Utilities for writing pandoc filters in python"
+name = "pandocfilters"
+optional = false
+python-versions = "*"
+version = "1.4.2"
+
+[[package]]
+category = "main"
+description = "A Python Parser"
+marker = "python_version >= \"3.3\""
+name = "parso"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "0.7.0"
+
+[package.extras]
+testing = ["docopt", "pytest (>=3.0.7)"]
+
+[[package]]
+category = "dev"
+description = "Utility library for gitignore style pattern matching of file paths."
+name = "pathspec"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "0.8.0"
+
+[[package]]
+category = "main"
+description = "Pexpect allows easy control of interactive console applications."
+marker = "python_version >= \"3.3\" and sys_platform != \"win32\""
+name = "pexpect"
+optional = false
+python-versions = "*"
+version = "4.8.0"
+
+[package.dependencies]
+ptyprocess = ">=0.5"
+
+[[package]]
+category = "main"
+description = "Tiny 'shelve'-like database with concurrency support"
+marker = "python_version >= \"3.3\""
+name = "pickleshare"
+optional = false
+python-versions = "*"
+version = "0.7.5"
+
+[[package]]
+category = "dev"
+description = "plugin and hook calling mechanisms for python"
+name = "pluggy"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "0.13.1"
+
+[package.dependencies]
+[package.dependencies.importlib-metadata]
+python = "<3.8"
+version = ">=0.12"
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+
+[[package]]
+category = "main"
+description = "Python client for the Prometheus monitoring system."
+name = "prometheus-client"
+optional = false
+python-versions = "*"
+version = "0.8.0"
+
+[package.extras]
+twisted = ["twisted"]
+
+[[package]]
+category = "main"
+description = "Library for building powerful interactive command lines in Python"
+name = "prompt-toolkit"
+optional = false
+python-versions = ">=3.6.1"
+version = "3.0.5"
+
+[package.dependencies]
+wcwidth = "*"
+
+[[package]]
+category = "main"
+description = "Run a subprocess in a pseudo terminal"
+marker = "python_version >= \"3.3\" and sys_platform != \"win32\" or os_name != \"nt\""
+name = "ptyprocess"
+optional = false
+python-versions = "*"
+version = "0.6.0"
+
+[[package]]
+category = "dev"
+description = "library with cross-python path, ini-parsing, io, code, log facilities"
+name = "py"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "1.8.2"
+
+[[package]]
+category = "main"
+description = "Pygments is a syntax highlighting package written in Python."
+name = "pygments"
+optional = false
+python-versions = ">=3.5"
+version = "2.6.1"
+
+[[package]]
+category = "main"
+description = "Python parsing module"
+name = "pyparsing"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+version = "2.4.7"
+
+[[package]]
+category = "main"
+description = "Persistent/Functional/Immutable data structures"
+name = "pyrsistent"
+optional = false
+python-versions = "*"
+version = "0.16.0"
+
+[package.dependencies]
+six = "*"
+
+[[package]]
+category = "dev"
+description = "pytest: simple powerful testing with Python"
+name = "pytest"
+optional = false
+python-versions = ">=3.5"
+version = "5.4.3"
+
+[package.dependencies]
+atomicwrites = ">=1.0"
+attrs = ">=17.4.0"
+colorama = "*"
+more-itertools = ">=4.0.0"
+packaging = "*"
+pluggy = ">=0.12,<1.0"
+py = ">=1.5.0"
+wcwidth = "*"
+
+[package.dependencies.importlib-metadata]
+python = "<3.8"
+version = ">=0.12"
+
+[package.extras]
+checkqa-mypy = ["mypy (v0.761)"]
+testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
+
+[[package]]
+category = "dev"
+description = "Pytest plugin for measuring coverage."
+name = "pytest-cov"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "2.10.0"
+
+[package.dependencies]
+coverage = ">=4.4"
+pytest = ">=4.6"
+
+[package.extras]
+testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"]
+
+[[package]]
+category = "main"
+description = "Extensions to the standard Python datetime module"
+name = "python-dateutil"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+version = "2.8.1"
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+category = "main"
+description = "World timezone definitions, modern and historical"
+name = "pytz"
+optional = false
+python-versions = "*"
+version = "2020.1"
+
+[[package]]
+category = "main"
+description = "Python for Window Extensions"
+marker = "sys_platform == \"win32\""
+name = "pywin32"
+optional = false
+python-versions = "*"
+version = "228"
+
+[[package]]
+category = "main"
+description = "Python bindings for the winpty library"
+marker = "os_name == \"nt\""
+name = "pywinpty"
+optional = false
+python-versions = "*"
+version = "0.5.7"
+
+[[package]]
+category = "main"
+description = "Python bindings for 0MQ"
+name = "pyzmq"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*"
+version = "19.0.1"
+
+[[package]]
+category = "main"
+description = "Jupyter Qt console"
+name = "qtconsole"
+optional = false
+python-versions = "*"
+version = "4.7.4"
+
+[package.dependencies]
+ipykernel = ">=4.1"
+ipython-genutils = "*"
+jupyter-client = ">=4.1"
+jupyter-core = "*"
+pygments = "*"
+pyzmq = ">=17.1"
+qtpy = "*"
+traitlets = "*"
+
+[package.extras]
+doc = ["Sphinx (>=1.3)"]
+test = ["pytest", "mock"]
+
+[[package]]
+category = "main"
+description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5, PyQt4 and PySide) and additional custom QWidgets."
+name = "qtpy"
+optional = false
+python-versions = "*"
+version = "1.9.0"
+
+[[package]]
+category = "dev"
+description = "Alternative regular expression module, to replace re."
+name = "regex"
+optional = false
+python-versions = "*"
+version = "2020.6.8"
+
+[[package]]
+category = "main"
+description = "Python HTTP for Humans."
+name = "requests"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "2.24.0"
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+chardet = ">=3.0.2,<4"
+idna = ">=2.5,<3"
+urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
+
+[package.extras]
+security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
+socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
+
+[[package]]
+category = "main"
+description = "Send file to trash natively under Mac OS X, Windows and Linux."
+name = "send2trash"
+optional = false
+python-versions = "*"
+version = "1.5.0"
+
+[[package]]
+category = "main"
+description = "Python 2 and 3 compatibility utilities"
+name = "six"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+version = "1.15.0"
+
+[[package]]
+category = "main"
+description = "A modern CSS selector implementation for Beautiful Soup."
+name = "soupsieve"
+optional = false
+python-versions = "*"
+version = "1.9.6"
+
+[[package]]
+category = "main"
+description = "Terminals served to xterm.js using Tornado websockets"
+name = "terminado"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "0.8.3"
+
+[package.dependencies]
+ptyprocess = "*"
+pywinpty = ">=0.5"
+tornado = ">=4"
+
+[[package]]
+category = "main"
+description = "Test utilities for code working with files and commands"
+name = "testpath"
+optional = false
+python-versions = "*"
+version = "0.4.4"
+
+[package.extras]
+test = ["pathlib2"]
+
+[[package]]
+category = "dev"
+description = "Python Library for Tom's Obvious, Minimal Language"
+name = "toml"
+optional = false
+python-versions = "*"
+version = "0.10.1"
+
+[[package]]
+category = "main"
+description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
+name = "tornado"
+optional = false
+python-versions = ">= 3.5"
+version = "6.0.4"
+
+[[package]]
+category = "main"
+description = "Traitlets Python config system"
+name = "traitlets"
+optional = false
+python-versions = "*"
+version = "4.3.3"
+
+[package.dependencies]
+decorator = "*"
+ipython-genutils = "*"
+six = "*"
+
+[package.extras]
+test = ["pytest", "mock"]
+
+[[package]]
+category = "dev"
+description = "a fork of Python 2 and 3 ast modules with type comment support"
+name = "typed-ast"
+optional = false
+python-versions = "*"
+version = "1.4.1"
+
+[[package]]
+category = "dev"
+description = "Backported and Experimental Type Hints for Python 3.5+"
+name = "typing-extensions"
+optional = false
+python-versions = "*"
+version = "3.7.4.2"
+
+[[package]]
+category = "main"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+name = "urllib3"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+version = "1.25.9"
+
+[package.extras]
+brotli = ["brotlipy (>=0.6.0)"]
+secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"]
+socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
+
+[[package]]
+category = "main"
+description = "Measures the displayed width of unicode strings in a terminal"
+name = "wcwidth"
+optional = false
+python-versions = "*"
+version = "0.2.4"
+
+[[package]]
+category = "main"
+description = "Character encoding aliases for legacy web content"
+name = "webencodings"
+optional = false
+python-versions = "*"
+version = "0.5.1"
+
+[[package]]
+category = "main"
+description = "IPython HTML widgets for Jupyter"
+name = "widgetsnbextension"
+optional = false
+python-versions = "*"
+version = "3.5.1"
+
+[package.dependencies]
+notebook = ">=4.4.1"
+
+[[package]]
+category = "main"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+marker = "python_version < \"3.8\""
+name = "zipp"
+optional = false
+python-versions = ">=3.6"
+version = "3.1.0"
+
+[package.extras]
+docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
+testing = ["jaraco.itertools", "func-timeout"]
+
+[metadata]
+content-hash = "c205bfedb77cae261819851755bd1943d571e7e00cb9df898ede7a8052902301"
+python-versions = ">=3.6.1,<4"
+
+[metadata.files]
+appdirs = [
+ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
+ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
+]
+appnope = [
+ {file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"},
+ {file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"},
+]
+atomicwrites = [
+ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
+ {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
+]
+attrs = [
+ {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
+ {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
+]
+backcall = [
+ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
+ {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
+]
+beautifulsoup4 = [
+ {file = "beautifulsoup4-4.9.1-py2-none-any.whl", hash = "sha256:e718f2342e2e099b640a34ab782407b7b676f47ee272d6739e60b8ea23829f2c"},
+ {file = "beautifulsoup4-4.9.1-py3-none-any.whl", hash = "sha256:a6237df3c32ccfaee4fd201c8f5f9d9df619b93121d01353a64a73ce8c6ef9a8"},
+ {file = "beautifulsoup4-4.9.1.tar.gz", hash = "sha256:73cc4d115b96f79c7d77c1c7f7a0a8d4c57860d1041df407dd1aae7f07a77fd7"},
+]
+black = [
+ {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"},
+ {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"},
+]
+bleach = [
+ {file = "bleach-3.1.5-py2.py3-none-any.whl", hash = "sha256:2bce3d8fab545a6528c8fa5d9f9ae8ebc85a56da365c7f85180bfe96a35ef22f"},
+ {file = "bleach-3.1.5.tar.gz", hash = "sha256:3c4c520fdb9db59ef139915a5db79f8b51bc2a7257ea0389f30c846883430a4b"},
+]
+certifi = [
+ {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"},
+ {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"},
+]
+chardet = [
+ {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
+ {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
+]
+click = [
+ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
+ {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
+]
+colorama = [
+ {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
+ {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
+]
+coverage = [
+ {file = "coverage-5.1-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65"},
+ {file = "coverage-5.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2"},
+ {file = "coverage-5.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04"},
+ {file = "coverage-5.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6"},
+ {file = "coverage-5.1-cp27-cp27m-win32.whl", hash = "sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796"},
+ {file = "coverage-5.1-cp27-cp27m-win_amd64.whl", hash = "sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730"},
+ {file = "coverage-5.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0"},
+ {file = "coverage-5.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a"},
+ {file = "coverage-5.1-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf"},
+ {file = "coverage-5.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9"},
+ {file = "coverage-5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768"},
+ {file = "coverage-5.1-cp35-cp35m-win32.whl", hash = "sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2"},
+ {file = "coverage-5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7"},
+ {file = "coverage-5.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0"},
+ {file = "coverage-5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019"},
+ {file = "coverage-5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c"},
+ {file = "coverage-5.1-cp36-cp36m-win32.whl", hash = "sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1"},
+ {file = "coverage-5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7"},
+ {file = "coverage-5.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355"},
+ {file = "coverage-5.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489"},
+ {file = "coverage-5.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd"},
+ {file = "coverage-5.1-cp37-cp37m-win32.whl", hash = "sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e"},
+ {file = "coverage-5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a"},
+ {file = "coverage-5.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55"},
+ {file = "coverage-5.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c"},
+ {file = "coverage-5.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef"},
+ {file = "coverage-5.1-cp38-cp38-win32.whl", hash = "sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24"},
+ {file = "coverage-5.1-cp38-cp38-win_amd64.whl", hash = "sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0"},
+ {file = "coverage-5.1-cp39-cp39-win32.whl", hash = "sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4"},
+ {file = "coverage-5.1-cp39-cp39-win_amd64.whl", hash = "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e"},
+ {file = "coverage-5.1.tar.gz", hash = "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052"},
+]
+cycler = [
+ {file = "cycler-0.10.0-py2.py3-none-any.whl", hash = "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d"},
+ {file = "cycler-0.10.0.tar.gz", hash = "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"},
+]
+decorator = [
+ {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"},
+ {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"},
+]
+defusedxml = [
+ {file = "defusedxml-0.6.0-py2.py3-none-any.whl", hash = "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93"},
+ {file = "defusedxml-0.6.0.tar.gz", hash = "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"},
+]
+entrypoints = [
+ {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"},
+ {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"},
+]
+idna = [
+ {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"},
+ {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"},
+]
+importlib-metadata = [
+ {file = "importlib_metadata-1.6.1-py2.py3-none-any.whl", hash = "sha256:15ec6c0fd909e893e3a08b3a7c76ecb149122fb14b7efe1199ddd4c7c57ea958"},
+ {file = "importlib_metadata-1.6.1.tar.gz", hash = "sha256:0505dd08068cfec00f53a74a0ad927676d7757da81b7436a6eefe4c7cf75c545"},
+]
+ipykernel = [
+ {file = "ipykernel-5.3.0-py3-none-any.whl", hash = "sha256:a8362e3ae365023ca458effe93b026b8cdadc0b73ff3031472128dd8a2cf0289"},
+ {file = "ipykernel-5.3.0.tar.gz", hash = "sha256:731adb3f2c4ebcaff52e10a855ddc87670359a89c9c784d711e62d66fccdafae"},
+]
+ipython = [
+ {file = "ipython-7.15.0-py3-none-any.whl", hash = "sha256:1b85d65632211bf5d3e6f1406f3393c8c429a47d7b947b9a87812aa5bce6595c"},
+ {file = "ipython-7.15.0.tar.gz", hash = "sha256:0ef1433879816a960cd3ae1ae1dc82c64732ca75cec8dab5a4e29783fb571d0e"},
+]
+ipython-genutils = [
+ {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"},
+ {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"},
+]
+ipywidgets = [
+ {file = "ipywidgets-7.5.1-py2.py3-none-any.whl", hash = "sha256:13ffeca438e0c0f91ae583dc22f50379b9d6b28390ac7be8b757140e9a771516"},
+ {file = "ipywidgets-7.5.1.tar.gz", hash = "sha256:e945f6e02854a74994c596d9db83444a1850c01648f1574adf144fbbabe05c97"},
+]
+jedi = [
+ {file = "jedi-0.17.1-py2.py3-none-any.whl", hash = "sha256:1ddb0ec78059e8e27ec9eb5098360b4ea0a3dd840bedf21415ea820c21b40a22"},
+ {file = "jedi-0.17.1.tar.gz", hash = "sha256:807d5d4f96711a2bcfdd5dfa3b1ae6d09aa53832b182090b222b5efb81f52f63"},
+]
+jinja2 = [
+ {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"},
+ {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"},
+]
+jsonschema = [
+ {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"},
+ {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"},
+]
+jupyter = [
+ {file = "jupyter-1.0.0-py2.py3-none-any.whl", hash = "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78"},
+ {file = "jupyter-1.0.0.tar.gz", hash = "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"},
+ {file = "jupyter-1.0.0.zip", hash = "sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7"},
+]
+jupyter-client = [
+ {file = "jupyter_client-6.1.3-py3-none-any.whl", hash = "sha256:cde8e83aab3ec1c614f221ae54713a9a46d3bf28292609d2db1b439bef5a8c8e"},
+ {file = "jupyter_client-6.1.3.tar.gz", hash = "sha256:3a32fa4d0b16d1c626b30c3002a62dfd86d6863ed39eaba3f537fade197bb756"},
+]
+jupyter-console = [
+ {file = "jupyter_console-6.1.0-py2.py3-none-any.whl", hash = "sha256:b392155112ec86a329df03b225749a0fa903aa80811e8eda55796a40b5e470d8"},
+ {file = "jupyter_console-6.1.0.tar.gz", hash = "sha256:6f6ead433b0534909df789ea64f0a14cdf9b6b2360757756f08182be4b9e431b"},
+]
+jupyter-core = [
+ {file = "jupyter_core-4.6.3-py2.py3-none-any.whl", hash = "sha256:a4ee613c060fe5697d913416fc9d553599c05e4492d58fac1192c9a6844abb21"},
+ {file = "jupyter_core-4.6.3.tar.gz", hash = "sha256:394fd5dd787e7c8861741880bdf8a00ce39f95de5d18e579c74b882522219e7e"},
+]
+kiwisolver = [
+ {file = "kiwisolver-1.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:443c2320520eda0a5b930b2725b26f6175ca4453c61f739fef7a5847bd262f74"},
+ {file = "kiwisolver-1.2.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:efcf3397ae1e3c3a4a0a0636542bcad5adad3b1dd3e8e629d0b6e201347176c8"},
+ {file = "kiwisolver-1.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fccefc0d36a38c57b7bd233a9b485e2f1eb71903ca7ad7adacad6c28a56d62d2"},
+ {file = "kiwisolver-1.2.0-cp36-none-win32.whl", hash = "sha256:60a78858580761fe611d22127868f3dc9f98871e6fdf0a15cc4203ed9ba6179b"},
+ {file = "kiwisolver-1.2.0-cp36-none-win_amd64.whl", hash = "sha256:556da0a5f60f6486ec4969abbc1dd83cf9b5c2deadc8288508e55c0f5f87d29c"},
+ {file = "kiwisolver-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7cc095a4661bdd8a5742aaf7c10ea9fac142d76ff1770a0f84394038126d8fc7"},
+ {file = "kiwisolver-1.2.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c955791d80e464da3b471ab41eb65cf5a40c15ce9b001fdc5bbc241170de58ec"},
+ {file = "kiwisolver-1.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:603162139684ee56bcd57acc74035fceed7dd8d732f38c0959c8bd157f913fec"},
+ {file = "kiwisolver-1.2.0-cp37-none-win32.whl", hash = "sha256:03662cbd3e6729f341a97dd2690b271e51a67a68322affab12a5b011344b973c"},
+ {file = "kiwisolver-1.2.0-cp37-none-win_amd64.whl", hash = "sha256:4eadb361baf3069f278b055e3bb53fa189cea2fd02cb2c353b7a99ebb4477ef1"},
+ {file = "kiwisolver-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c31bc3c8e903d60a1ea31a754c72559398d91b5929fcb329b1c3a3d3f6e72113"},
+ {file = "kiwisolver-1.2.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d52b989dc23cdaa92582ceb4af8d5bcc94d74b2c3e64cd6785558ec6a879793e"},
+ {file = "kiwisolver-1.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:e586b28354d7b6584d8973656a7954b1c69c93f708c0c07b77884f91640b7657"},
+ {file = "kiwisolver-1.2.0-cp38-none-win32.whl", hash = "sha256:d069ef4b20b1e6b19f790d00097a5d5d2c50871b66d10075dab78938dc2ee2cf"},
+ {file = "kiwisolver-1.2.0-cp38-none-win_amd64.whl", hash = "sha256:18d749f3e56c0480dccd1714230da0f328e6e4accf188dd4e6884bdd06bf02dd"},
+ {file = "kiwisolver-1.2.0.tar.gz", hash = "sha256:247800260cd38160c362d211dcaf4ed0f7816afb5efe56544748b21d6ad6d17f"},
+]
+markupsafe = [
+ {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
+ {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
+ {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"},
+ {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"},
+ {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"},
+ {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"},
+ {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"},
+ {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"},
+ {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"},
+ {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"},
+ {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"},
+ {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"},
+ {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"},
+ {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"},
+ {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"},
+ {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
+ {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
+ {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
+ {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
+ {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
+ {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
+ {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
+ {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
+ {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
+ {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
+ {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
+ {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
+ {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
+ {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
+ {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
+ {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
+ {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
+ {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
+]
+matplotlib = [
+ {file = "matplotlib-3.2.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a47abc48c7b81fe6e636dde8a58e49b13d87d140e0f448213a4879f4a3f73345"},
+ {file = "matplotlib-3.2.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:20bcd11efe194cd302bd0653cb025b8d16bcd80442359bfca8d49dc805f35ec8"},
+ {file = "matplotlib-3.2.2-cp36-cp36m-win32.whl", hash = "sha256:2a6d64336b547e25730b6221e7aadfb01a391a065d43b5f51f0b9d7f673d2dd2"},
+ {file = "matplotlib-3.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:4416825ebc9c1f135027a30e8d8aea0edcf45078ce767c7f7386737413cfb98f"},
+ {file = "matplotlib-3.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:465c752278d27895e23f1379d6fcfa3a2990643b803c25e3bc16a10641d2346a"},
+ {file = "matplotlib-3.2.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:81de040403a33bf3c68e9d4a40e26c8d24da00f7e3fadd845003b7e106785da7"},
+ {file = "matplotlib-3.2.2-cp37-cp37m-win32.whl", hash = "sha256:006413f08ba5db1f5b1e0d6fbdc2ac9058b062ccf552f57182563a78579c34b4"},
+ {file = "matplotlib-3.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:da06fa530591a141ffbe1712bbeec784734c3436b40c942d21652f305199b5d9"},
+ {file = "matplotlib-3.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:894dd47c0a6ce38dc19bc87d1f7e2b0608310b2a18d1572291157450b05ce874"},
+ {file = "matplotlib-3.2.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:1ab264770e7cf2cf4feb99f22c737066aef21ddf1ec402dc255450ac15eacb7b"},
+ {file = "matplotlib-3.2.2-cp38-cp38-win32.whl", hash = "sha256:91c153f4318e3c67c035fd1185f5ea2613f15008b73b66985033033f6fe54bbd"},
+ {file = "matplotlib-3.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:a68e42e22f7fd190a532e4215e142276970c2d54040a0c46842fcb3db8b6ec5b"},
+ {file = "matplotlib-3.2.2-cp39-cp39-win32.whl", hash = "sha256:647cf232ccf6265d2ba1ac4103e8c8b6ac7b03a40da3421234ffb03dda217f59"},
+ {file = "matplotlib-3.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:31d32c83bb2b617377c6156f75e88b9ec2ded289e47ad4ff0f263dc1019d88b1"},
+ {file = "matplotlib-3.2.2-pp36-pypy36_pp73-win32.whl", hash = "sha256:67065d938df34478451af62fbd0670d2b51c4d859fb66673064eb5de8660dd7c"},
+ {file = "matplotlib-3.2.2.tar.gz", hash = "sha256:3d77a6630d093d74cbbfebaa0571d00790966be1ed204e4a8239f5cbd6835c5d"},
+]
+mistune = [
+ {file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"},
+ {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"},
+]
+more-itertools = [
+ {file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"},
+ {file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"},
+]
+mypy = [
+ {file = "mypy-0.780-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:d3b4941de44341227ece1caaf5b08b23e42ad4eeb8b603219afb11e9d4cfb437"},
+ {file = "mypy-0.780-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1f3976a945ad7f0a0727aafdc5651c2d3278e3c88dee94e2bf75cd3386b7b2f4"},
+ {file = "mypy-0.780-cp35-cp35m-win_amd64.whl", hash = "sha256:eadb865126da4e3c4c95bdb47fe1bb087a3e3ea14d39a3b13224b8a4d9f9a102"},
+ {file = "mypy-0.780-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:75eed74d2faf2759f79c5f56f17388defd2fc994222312ec54ee921e37b31ad4"},
+ {file = "mypy-0.780-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2f8c098f12b402c19b735aec724cc9105cc1a9eea405d08814eb4b14a6fb1a41"},
+ {file = "mypy-0.780-cp36-cp36m-win_amd64.whl", hash = "sha256:62eb5dd4ea86bda8ce386f26684f7f26e4bfe6283c9f2b6ca6d17faf704dcfad"},
+ {file = "mypy-0.780-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:00cb1964a7476e871d6108341ac9c1a857d6bd20bf5877f4773ac5e9d92cd3cd"},
+ {file = "mypy-0.780-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:974bebe3699b9b46278a7f076635d219183da26e1a675c1f8243a69221758273"},
+ {file = "mypy-0.780-cp37-cp37m-win_amd64.whl", hash = "sha256:64c36eb0936d0bfb7d8da49f92c18e312ad2e3ed46e5548ae4ca997b0d33bd59"},
+ {file = "mypy-0.780-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d31291df31bafb997952dc0a17ebb2737f802c754aed31dd155a8bfe75112c57"},
+ {file = "mypy-0.780-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a5e5bb12b7982b179af513dddb06fca12285f0316d74f3964078acbfcf4c68f2"},
+ {file = "mypy-0.780-cp38-cp38-win_amd64.whl", hash = "sha256:5d142f219bf8c7894dfa79ebfb7d352c4c63a325e75f10dfb4c3db9417dcd135"},
+ {file = "mypy-0.780-py3-none-any.whl", hash = "sha256:127de5a9b817a03a98c5ae8a0c46a20dc44442af6dcfa2ae7f96cb519b312efa"},
+ {file = "mypy-0.780.tar.gz", hash = "sha256:4ef13b619a289aa025f2273e05e755f8049bb4eaba6d703a425de37d495d178d"},
+]
+mypy-extensions = [
+ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
+ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
+]
+nbconvert = [
+ {file = "nbconvert-5.6.1-py2.py3-none-any.whl", hash = "sha256:f0d6ec03875f96df45aa13e21fd9b8450c42d7e1830418cccc008c0df725fcee"},
+ {file = "nbconvert-5.6.1.tar.gz", hash = "sha256:21fb48e700b43e82ba0e3142421a659d7739b65568cc832a13976a77be16b523"},
+]
+nbformat = [
+ {file = "nbformat-5.0.7-py3-none-any.whl", hash = "sha256:ea55c9b817855e2dfcd3f66d74857342612a60b1f09653440f4a5845e6e3523f"},
+ {file = "nbformat-5.0.7.tar.gz", hash = "sha256:54d4d6354835a936bad7e8182dcd003ca3dc0cedfee5a306090e04854343b340"},
+]
+notebook = [
+ {file = "notebook-6.0.3-py3-none-any.whl", hash = "sha256:3edc616c684214292994a3af05eaea4cc043f6b4247d830f3a2f209fa7639a80"},
+ {file = "notebook-6.0.3.tar.gz", hash = "sha256:47a9092975c9e7965ada00b9a20f0cf637d001db60d241d479f53c0be117ad48"},
+]
+numpy = [
+ {file = "numpy-1.19.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:63d971bb211ad3ca37b2adecdd5365f40f3b741a455beecba70fd0dde8b2a4cb"},
+ {file = "numpy-1.19.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b6aaeadf1e4866ca0fdf7bb4eed25e521ae21a7947c59f78154b24fc7abbe1dd"},
+ {file = "numpy-1.19.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:13af0184177469192d80db9bd02619f6fa8b922f9f327e077d6f2a6acb1ce1c0"},
+ {file = "numpy-1.19.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:356f96c9fbec59974a592452ab6a036cd6f180822a60b529a975c9467fcd5f23"},
+ {file = "numpy-1.19.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa1fe75b4a9e18b66ae7f0b122543c42debcf800aaafa0212aaff3ad273c2596"},
+ {file = "numpy-1.19.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:cbe326f6d364375a8e5a8ccb7e9cd73f4b2f6dc3b2ed205633a0db8243e2a96a"},
+ {file = "numpy-1.19.0-cp36-cp36m-win32.whl", hash = "sha256:a2e3a39f43f0ce95204beb8fe0831199542ccab1e0c6e486a0b4947256215632"},
+ {file = "numpy-1.19.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7b852817800eb02e109ae4a9cef2beda8dd50d98b76b6cfb7b5c0099d27b52d4"},
+ {file = "numpy-1.19.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d97a86937cf9970453c3b62abb55a6475f173347b4cde7f8dcdb48c8e1b9952d"},
+ {file = "numpy-1.19.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a86c962e211f37edd61d6e11bb4df7eddc4a519a38a856e20a6498c319efa6b0"},
+ {file = "numpy-1.19.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d34fbb98ad0d6b563b95de852a284074514331e6b9da0a9fc894fb1cdae7a79e"},
+ {file = "numpy-1.19.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:658624a11f6e1c252b2cd170d94bf28c8f9410acab9f2fd4369e11e1cd4e1aaf"},
+ {file = "numpy-1.19.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:4d054f013a1983551254e2379385e359884e5af105e3efe00418977d02f634a7"},
+ {file = "numpy-1.19.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:26a45798ca2a4e168d00de75d4a524abf5907949231512f372b217ede3429e98"},
+ {file = "numpy-1.19.0-cp37-cp37m-win32.whl", hash = "sha256:3c40c827d36c6d1c3cf413694d7dc843d50997ebffbc7c87d888a203ed6403a7"},
+ {file = "numpy-1.19.0-cp37-cp37m-win_amd64.whl", hash = "sha256:be62aeff8f2f054eff7725f502f6228298891fd648dc2630e03e44bf63e8cee0"},
+ {file = "numpy-1.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dd53d7c4a69e766e4900f29db5872f5824a06827d594427cf1a4aa542818b796"},
+ {file = "numpy-1.19.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:30a59fb41bb6b8c465ab50d60a1b298d1cd7b85274e71f38af5a75d6c475d2d2"},
+ {file = "numpy-1.19.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:df1889701e2dfd8ba4dc9b1a010f0a60950077fb5242bb92c8b5c7f1a6f2668a"},
+ {file = "numpy-1.19.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:33c623ef9ca5e19e05991f127c1be5aeb1ab5cdf30cb1c5cf3960752e58b599b"},
+ {file = "numpy-1.19.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:26f509450db547e4dfa3ec739419b31edad646d21fb8d0ed0734188b35ff6b27"},
+ {file = "numpy-1.19.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7b57f26e5e6ee2f14f960db46bd58ffdca25ca06dd997729b1b179fddd35f5a3"},
+ {file = "numpy-1.19.0-cp38-cp38-win32.whl", hash = "sha256:a8705c5073fe3fcc297fb8e0b31aa794e05af6a329e81b7ca4ffecab7f2b95ef"},
+ {file = "numpy-1.19.0-cp38-cp38-win_amd64.whl", hash = "sha256:c2edbb783c841e36ca0fa159f0ae97a88ce8137fb3a6cd82eae77349ba4b607b"},
+ {file = "numpy-1.19.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:8cde829f14bd38f6da7b2954be0f2837043e8b8d7a9110ec5e318ae6bf706610"},
+ {file = "numpy-1.19.0.zip", hash = "sha256:76766cc80d6128750075378d3bb7812cf146415bd29b588616f72c943c00d598"},
+]
+packaging = [
+ {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"},
+ {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"},
+]
+pandas = [
+ {file = "pandas-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:faa42a78d1350b02a7d2f0dbe3c80791cf785663d6997891549d0f86dc49125e"},
+ {file = "pandas-1.0.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9c31d52f1a7dd2bb4681d9f62646c7aa554f19e8e9addc17e8b1b20011d7522d"},
+ {file = "pandas-1.0.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8778a5cc5a8437a561e3276b85367412e10ae9fff07db1eed986e427d9a674f8"},
+ {file = "pandas-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:9871ef5ee17f388f1cb35f76dc6106d40cb8165c562d573470672f4cdefa59ef"},
+ {file = "pandas-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:35b670b0abcfed7cad76f2834041dcf7ae47fd9b22b63622d67cdc933d79f453"},
+ {file = "pandas-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c9410ce8a3dee77653bc0684cfa1535a7f9c291663bd7ad79e39f5ab58f67ab3"},
+ {file = "pandas-1.0.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:02f1e8f71cd994ed7fcb9a35b6ddddeb4314822a0e09a9c5b2d278f8cb5d4096"},
+ {file = "pandas-1.0.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b3c4f93fcb6e97d993bf87cdd917883b7dab7d20c627699f360a8fb49e9e0b91"},
+ {file = "pandas-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:5759edf0b686b6f25a5d4a447ea588983a33afc8a0081a0954184a4a87fd0dd7"},
+ {file = "pandas-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab8173a8efe5418bbe50e43f321994ac6673afc5c7c4839014cf6401bbdd0705"},
+ {file = "pandas-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:13f75fb18486759da3ff40f5345d9dd20e7d78f2a39c5884d013456cec9876f0"},
+ {file = "pandas-1.0.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5a7cf6044467c1356b2b49ef69e50bf4d231e773c3ca0558807cdba56b76820b"},
+ {file = "pandas-1.0.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ae961f1f0e270f1e4e2273f6a539b2ea33248e0e3a11ffb479d757918a5e03a9"},
+ {file = "pandas-1.0.5-cp38-cp38-win32.whl", hash = "sha256:f69e0f7b7c09f1f612b1f8f59e2df72faa8a6b41c5a436dde5b615aaf948f107"},
+ {file = "pandas-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:4c73f373b0800eb3062ffd13d4a7a2a6d522792fa6eb204d67a4fad0a40f03dc"},
+ {file = "pandas-1.0.5.tar.gz", hash = "sha256:69c5d920a0b2a9838e677f78f4dde506b95ea8e4d30da25859db6469ded84fa8"},
+]
+pandocfilters = [
+ {file = "pandocfilters-1.4.2.tar.gz", hash = "sha256:b3dd70e169bb5449e6bc6ff96aea89c5eea8c5f6ab5e207fc2f521a2cf4a0da9"},
+]
+parso = [
+ {file = "parso-0.7.0-py2.py3-none-any.whl", hash = "sha256:158c140fc04112dc45bca311633ae5033c2c2a7b732fa33d0955bad8152a8dd0"},
+ {file = "parso-0.7.0.tar.gz", hash = "sha256:908e9fae2144a076d72ae4e25539143d40b8e3eafbaeae03c1bfe226f4cdf12c"},
+]
+pathspec = [
+ {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"},
+ {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"},
+]
+pexpect = [
+ {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
+ {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
+]
+pickleshare = [
+ {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
+ {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
+]
+pluggy = [
+ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
+ {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
+]
+prometheus-client = [
+ {file = "prometheus_client-0.8.0-py2.py3-none-any.whl", hash = "sha256:983c7ac4b47478720db338f1491ef67a100b474e3bc7dafcbaefb7d0b8f9b01c"},
+ {file = "prometheus_client-0.8.0.tar.gz", hash = "sha256:c6e6b706833a6bd1fd51711299edee907857be10ece535126a158f911ee80915"},
+]
+prompt-toolkit = [
+ {file = "prompt_toolkit-3.0.5-py3-none-any.whl", hash = "sha256:df7e9e63aea609b1da3a65641ceaf5bc7d05e0a04de5bd45d05dbeffbabf9e04"},
+ {file = "prompt_toolkit-3.0.5.tar.gz", hash = "sha256:563d1a4140b63ff9dd587bda9557cffb2fe73650205ab6f4383092fb882e7dc8"},
+]
+ptyprocess = [
+ {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"},
+ {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"},
+]
+py = [
+ {file = "py-1.8.2-py2.py3-none-any.whl", hash = "sha256:a673fa23d7000440cc885c17dbd34fafcb7d7a6e230b29f6766400de36a33c44"},
+ {file = "py-1.8.2.tar.gz", hash = "sha256:f3b3a4c36512a4c4f024041ab51866f11761cc169670204b235f6b20523d4e6b"},
+]
+pygments = [
+ {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"},
+ {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"},
+]
+pyparsing = [
+ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
+ {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
+]
+pyrsistent = [
+ {file = "pyrsistent-0.16.0.tar.gz", hash = "sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3"},
+]
+pytest = [
+ {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
+ {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
+]
+pytest-cov = [
+ {file = "pytest-cov-2.10.0.tar.gz", hash = "sha256:1a629dc9f48e53512fcbfda6b07de490c374b0c83c55ff7a1720b3fccff0ac87"},
+ {file = "pytest_cov-2.10.0-py2.py3-none-any.whl", hash = "sha256:6e6d18092dce6fad667cd7020deed816f858ad3b49d5b5e2b1cc1c97a4dba65c"},
+]
+python-dateutil = [
+ {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"},
+ {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"},
+]
+pytz = [
+ {file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"},
+ {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"},
+]
+pywin32 = [
+ {file = "pywin32-228-cp27-cp27m-win32.whl", hash = "sha256:37dc9935f6a383cc744315ae0c2882ba1768d9b06700a70f35dc1ce73cd4ba9c"},
+ {file = "pywin32-228-cp27-cp27m-win_amd64.whl", hash = "sha256:11cb6610efc2f078c9e6d8f5d0f957620c333f4b23466931a247fb945ed35e89"},
+ {file = "pywin32-228-cp35-cp35m-win32.whl", hash = "sha256:1f45db18af5d36195447b2cffacd182fe2d296849ba0aecdab24d3852fbf3f80"},
+ {file = "pywin32-228-cp35-cp35m-win_amd64.whl", hash = "sha256:6e38c44097a834a4707c1b63efa9c2435f5a42afabff634a17f563bc478dfcc8"},
+ {file = "pywin32-228-cp36-cp36m-win32.whl", hash = "sha256:ec16d44b49b5f34e99eb97cf270806fdc560dff6f84d281eb2fcb89a014a56a9"},
+ {file = "pywin32-228-cp36-cp36m-win_amd64.whl", hash = "sha256:a60d795c6590a5b6baeacd16c583d91cce8038f959bd80c53bd9a68f40130f2d"},
+ {file = "pywin32-228-cp37-cp37m-win32.whl", hash = "sha256:af40887b6fc200eafe4d7742c48417529a8702dcc1a60bf89eee152d1d11209f"},
+ {file = "pywin32-228-cp37-cp37m-win_amd64.whl", hash = "sha256:00eaf43dbd05ba6a9b0080c77e161e0b7a601f9a3f660727a952e40140537de7"},
+ {file = "pywin32-228-cp38-cp38-win32.whl", hash = "sha256:fa6ba028909cfc64ce9e24bcf22f588b14871980d9787f1e2002c99af8f1850c"},
+ {file = "pywin32-228-cp38-cp38-win_amd64.whl", hash = "sha256:9b3466083f8271e1a5eb0329f4e0d61925d46b40b195a33413e0905dccb285e8"},
+ {file = "pywin32-228-cp39-cp39-win32.whl", hash = "sha256:ed74b72d8059a6606f64842e7917aeee99159ebd6b8d6261c518d002837be298"},
+ {file = "pywin32-228-cp39-cp39-win_amd64.whl", hash = "sha256:8319bafdcd90b7202c50d6014efdfe4fde9311b3ff15fd6f893a45c0868de203"},
+]
+pywinpty = [
+ {file = "pywinpty-0.5.7-cp27-cp27m-win32.whl", hash = "sha256:b358cb552c0f6baf790de375fab96524a0498c9df83489b8c23f7f08795e966b"},
+ {file = "pywinpty-0.5.7-cp27-cp27m-win_amd64.whl", hash = "sha256:1e525a4de05e72016a7af27836d512db67d06a015aeaf2fa0180f8e6a039b3c2"},
+ {file = "pywinpty-0.5.7-cp35-cp35m-win32.whl", hash = "sha256:2740eeeb59297593a0d3f762269b01d0285c1b829d6827445fcd348fb47f7e70"},
+ {file = "pywinpty-0.5.7-cp35-cp35m-win_amd64.whl", hash = "sha256:33df97f79843b2b8b8bc5c7aaf54adec08cc1bae94ee99dfb1a93c7a67704d95"},
+ {file = "pywinpty-0.5.7-cp36-cp36m-win32.whl", hash = "sha256:e854211df55d107f0edfda8a80b39dfc87015bef52a8fe6594eb379240d81df2"},
+ {file = "pywinpty-0.5.7-cp36-cp36m-win_amd64.whl", hash = "sha256:dbd838de92de1d4ebf0dce9d4d5e4fc38d0b7b1de837947a18b57a882f219139"},
+ {file = "pywinpty-0.5.7-cp37-cp37m-win32.whl", hash = "sha256:5fb2c6c6819491b216f78acc2c521b9df21e0f53b9a399d58a5c151a3c4e2a2d"},
+ {file = "pywinpty-0.5.7-cp37-cp37m-win_amd64.whl", hash = "sha256:dd22c8efacf600730abe4a46c1388355ce0d4ab75dc79b15d23a7bd87bf05b48"},
+ {file = "pywinpty-0.5.7-cp38-cp38-win_amd64.whl", hash = "sha256:8fc5019ff3efb4f13708bd3b5ad327589c1a554cb516d792527361525a7cb78c"},
+ {file = "pywinpty-0.5.7.tar.gz", hash = "sha256:2d7e9c881638a72ffdca3f5417dd1563b60f603e1b43e5895674c2a1b01f95a0"},
+]
+pyzmq = [
+ {file = "pyzmq-19.0.1-cp27-cp27m-macosx_10_9_intel.whl", hash = "sha256:58688a2dfa044fad608a8e70ba8d019d0b872ec2acd75b7b5e37da8905605891"},
+ {file = "pyzmq-19.0.1-cp27-cp27m-win32.whl", hash = "sha256:87c78f6936e2654397ca2979c1d323ee4a889eef536cc77a938c6b5be33351a7"},
+ {file = "pyzmq-19.0.1-cp27-cp27m-win_amd64.whl", hash = "sha256:97b6255ae77328d0e80593681826a0479cb7bac0ba8251b4dd882f5145a2293a"},
+ {file = "pyzmq-19.0.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:15b4cb21118f4589c4db8be4ac12b21c8b4d0d42b3ee435d47f686c32fe2e91f"},
+ {file = "pyzmq-19.0.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:931339ac2000d12fe212e64f98ce291e81a7ec6c73b125f17cf08415b753c087"},
+ {file = "pyzmq-19.0.1-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:2a88b8fabd9cc35bd59194a7723f3122166811ece8b74018147a4ed8489e6421"},
+ {file = "pyzmq-19.0.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:bafd651b557dd81d89bd5f9c678872f3e7b7255c1c751b78d520df2caac80230"},
+ {file = "pyzmq-19.0.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:8952f6ba6ae598e792703f3134af5a01af8f5c7cf07e9a148f05a12b02412cea"},
+ {file = "pyzmq-19.0.1-cp35-cp35m-win32.whl", hash = "sha256:54aa24fd60c4262286fc64ca632f9e747c7cc3a3a1144827490e1dc9b8a3a960"},
+ {file = "pyzmq-19.0.1-cp35-cp35m-win_amd64.whl", hash = "sha256:dcbc3f30c11c60d709c30a213dc56e88ac016fe76ac6768e64717bd976072566"},
+ {file = "pyzmq-19.0.1-cp36-cp36m-macosx_10_9_intel.whl", hash = "sha256:6ca519309703e95d55965735a667809bbb65f52beda2fdb6312385d3e7a6d234"},
+ {file = "pyzmq-19.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4ee0bfd82077a3ff11c985369529b12853a4064320523f8e5079b630f9551448"},
+ {file = "pyzmq-19.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ba6f24431b569aec674ede49cad197cad59571c12deed6ad8e3c596da8288217"},
+ {file = "pyzmq-19.0.1-cp36-cp36m-win32.whl", hash = "sha256:956775444d01331c7eb412c5fb9bb62130dfaac77e09f32764ea1865234e2ca9"},
+ {file = "pyzmq-19.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b08780e3a55215873b3b8e6e7ca8987f14c902a24b6ac081b344fd430d6ca7cd"},
+ {file = "pyzmq-19.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21f7d91f3536f480cb2c10d0756bfa717927090b7fb863e6323f766e5461ee1c"},
+ {file = "pyzmq-19.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bfff5ffff051f5aa47ba3b379d87bd051c3196b0c8a603e8b7ed68a6b4f217ec"},
+ {file = "pyzmq-19.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:07fb8fe6826a229dada876956590135871de60dbc7de5a18c3bcce2ed1f03c98"},
+ {file = "pyzmq-19.0.1-cp37-cp37m-win32.whl", hash = "sha256:342fb8a1dddc569bc361387782e8088071593e7eaf3e3ecf7d6bd4976edff112"},
+ {file = "pyzmq-19.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:faee2604f279d31312bc455f3d024f160b6168b9c1dde22bf62d8c88a4deca8e"},
+ {file = "pyzmq-19.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b9d21fc56c8aacd2e6d14738021a9d64f3f69b30578a99325a728e38a349f85"},
+ {file = "pyzmq-19.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:af0c02cf49f4f9eedf38edb4f3b6bb621d83026e7e5d76eb5526cc5333782fd6"},
+ {file = "pyzmq-19.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5f1f2eb22aab606f808163eb1d537ac9a0ba4283fbeb7a62eb48d9103cf015c2"},
+ {file = "pyzmq-19.0.1-cp38-cp38-win32.whl", hash = "sha256:f9d7e742fb0196992477415bb34366c12e9bb9a0699b8b3f221ff93b213d7bec"},
+ {file = "pyzmq-19.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:5b99c2ae8089ef50223c28bac57510c163bfdff158c9e90764f812b94e69a0e6"},
+ {file = "pyzmq-19.0.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:cf5d689ba9513b9753959164cf500079383bc18859f58bf8ce06d8d4bef2b054"},
+ {file = "pyzmq-19.0.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:aaa8b40b676576fd7806839a5de8e6d5d1b74981e6376d862af6c117af2a3c10"},
+ {file = "pyzmq-19.0.1.tar.gz", hash = "sha256:13a5638ab24d628a6ade8f794195e1a1acd573496c3b85af2f1183603b7bf5e0"},
+]
+qtconsole = [
+ {file = "qtconsole-4.7.4-py2.py3-none-any.whl", hash = "sha256:89442727940126c65c2f94a058f1b4693a0f5d4c4b192fd6518ba3b11f4791aa"},
+ {file = "qtconsole-4.7.4.tar.gz", hash = "sha256:fd48bf1051d6e69cec1f9e2596cfaa94e3c726c70c5d848681ebce10c029f5fd"},
+]
+qtpy = [
+ {file = "QtPy-1.9.0-py2.py3-none-any.whl", hash = "sha256:fa0b8363b363e89b2a6f49eddc162a04c0699ae95e109a6be3bb145a913190ea"},
+ {file = "QtPy-1.9.0.tar.gz", hash = "sha256:2db72c44b55d0fe1407be8fba35c838ad0d6d3bb81f23007886dc1fc0f459c8d"},
+]
+regex = [
+ {file = "regex-2020.6.8-cp27-cp27m-win32.whl", hash = "sha256:fbff901c54c22425a5b809b914a3bfaf4b9570eee0e5ce8186ac71eb2025191c"},
+ {file = "regex-2020.6.8-cp27-cp27m-win_amd64.whl", hash = "sha256:112e34adf95e45158c597feea65d06a8124898bdeac975c9087fe71b572bd938"},
+ {file = "regex-2020.6.8-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:92d8a043a4241a710c1cf7593f5577fbb832cf6c3a00ff3fc1ff2052aff5dd89"},
+ {file = "regex-2020.6.8-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bae83f2a56ab30d5353b47f9b2a33e4aac4de9401fb582b55c42b132a8ac3868"},
+ {file = "regex-2020.6.8-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:b2ba0f78b3ef375114856cbdaa30559914d081c416b431f2437f83ce4f8b7f2f"},
+ {file = "regex-2020.6.8-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:95fa7726d073c87141f7bbfb04c284901f8328e2d430eeb71b8ffdd5742a5ded"},
+ {file = "regex-2020.6.8-cp36-cp36m-win32.whl", hash = "sha256:e3cdc9423808f7e1bb9c2e0bdb1c9dc37b0607b30d646ff6faf0d4e41ee8fee3"},
+ {file = "regex-2020.6.8-cp36-cp36m-win_amd64.whl", hash = "sha256:c78e66a922de1c95a208e4ec02e2e5cf0bb83a36ceececc10a72841e53fbf2bd"},
+ {file = "regex-2020.6.8-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:08997a37b221a3e27d68ffb601e45abfb0093d39ee770e4257bd2f5115e8cb0a"},
+ {file = "regex-2020.6.8-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2f6f211633ee8d3f7706953e9d3edc7ce63a1d6aad0be5dcee1ece127eea13ae"},
+ {file = "regex-2020.6.8-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:55b4c25cbb3b29f8d5e63aeed27b49fa0f8476b0d4e1b3171d85db891938cc3a"},
+ {file = "regex-2020.6.8-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:89cda1a5d3e33ec9e231ece7307afc101b5217523d55ef4dc7fb2abd6de71ba3"},
+ {file = "regex-2020.6.8-cp37-cp37m-win32.whl", hash = "sha256:690f858d9a94d903cf5cada62ce069b5d93b313d7d05456dbcd99420856562d9"},
+ {file = "regex-2020.6.8-cp37-cp37m-win_amd64.whl", hash = "sha256:1700419d8a18c26ff396b3b06ace315b5f2a6e780dad387e4c48717a12a22c29"},
+ {file = "regex-2020.6.8-cp38-cp38-manylinux1_i686.whl", hash = "sha256:654cb773b2792e50151f0e22be0f2b6e1c3a04c5328ff1d9d59c0398d37ef610"},
+ {file = "regex-2020.6.8-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:52e1b4bef02f4040b2fd547357a170fc1146e60ab310cdbdd098db86e929b387"},
+ {file = "regex-2020.6.8-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:cf59bbf282b627130f5ba68b7fa3abdb96372b24b66bdf72a4920e8153fc7910"},
+ {file = "regex-2020.6.8-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:5aaa5928b039ae440d775acea11d01e42ff26e1561c0ffcd3d805750973c6baf"},
+ {file = "regex-2020.6.8-cp38-cp38-win32.whl", hash = "sha256:97712e0d0af05febd8ab63d2ef0ab2d0cd9deddf4476f7aa153f76feef4b2754"},
+ {file = "regex-2020.6.8-cp38-cp38-win_amd64.whl", hash = "sha256:6ad8663c17db4c5ef438141f99e291c4d4edfeaacc0ce28b5bba2b0bf273d9b5"},
+ {file = "regex-2020.6.8.tar.gz", hash = "sha256:e9b64e609d37438f7d6e68c2546d2cb8062f3adb27e6336bc129b51be20773ac"},
+]
+requests = [
+ {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"},
+ {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"},
+]
+send2trash = [
+ {file = "Send2Trash-1.5.0-py3-none-any.whl", hash = "sha256:f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b"},
+ {file = "Send2Trash-1.5.0.tar.gz", hash = "sha256:60001cc07d707fe247c94f74ca6ac0d3255aabcb930529690897ca2a39db28b2"},
+]
+six = [
+ {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
+ {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
+]
+soupsieve = [
+ {file = "soupsieve-1.9.6-py2.py3-none-any.whl", hash = "sha256:feb1e937fa26a69e08436aad4a9037cd7e1d4c7212909502ba30701247ff8abd"},
+ {file = "soupsieve-1.9.6.tar.gz", hash = "sha256:7985bacc98c34923a439967c1a602dc4f1e15f923b6fcf02344184f86cc7efaa"},
+]
+terminado = [
+ {file = "terminado-0.8.3-py2.py3-none-any.whl", hash = "sha256:a43dcb3e353bc680dd0783b1d9c3fc28d529f190bc54ba9a229f72fe6e7a54d7"},
+ {file = "terminado-0.8.3.tar.gz", hash = "sha256:4804a774f802306a7d9af7322193c5390f1da0abb429e082a10ef1d46e6fb2c2"},
+]
+testpath = [
+ {file = "testpath-0.4.4-py2.py3-none-any.whl", hash = "sha256:bfcf9411ef4bf3db7579063e0546938b1edda3d69f4e1fb8756991f5951f85d4"},
+ {file = "testpath-0.4.4.tar.gz", hash = "sha256:60e0a3261c149755f4399a1fff7d37523179a70fdc3abdf78de9fc2604aeec7e"},
+]
+toml = [
+ {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"},
+ {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"},
+]
+tornado = [
+ {file = "tornado-6.0.4-cp35-cp35m-win32.whl", hash = "sha256:5217e601700f24e966ddab689f90b7ea4bd91ff3357c3600fa1045e26d68e55d"},
+ {file = "tornado-6.0.4-cp35-cp35m-win_amd64.whl", hash = "sha256:c98232a3ac391f5faea6821b53db8db461157baa788f5d6222a193e9456e1740"},
+ {file = "tornado-6.0.4-cp36-cp36m-win32.whl", hash = "sha256:5f6a07e62e799be5d2330e68d808c8ac41d4a259b9cea61da4101b83cb5dc673"},
+ {file = "tornado-6.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:c952975c8ba74f546ae6de2e226ab3cc3cc11ae47baf607459a6728585bb542a"},
+ {file = "tornado-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:2c027eb2a393d964b22b5c154d1a23a5f8727db6fda837118a776b29e2b8ebc6"},
+ {file = "tornado-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:5618f72e947533832cbc3dec54e1dffc1747a5cb17d1fd91577ed14fa0dc081b"},
+ {file = "tornado-6.0.4-cp38-cp38-win32.whl", hash = "sha256:22aed82c2ea340c3771e3babc5ef220272f6fd06b5108a53b4976d0d722bcd52"},
+ {file = "tornado-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:c58d56003daf1b616336781b26d184023ea4af13ae143d9dda65e31e534940b9"},
+ {file = "tornado-6.0.4.tar.gz", hash = "sha256:0fe2d45ba43b00a41cd73f8be321a44936dc1aba233dee979f17a042b83eb6dc"},
+]
+traitlets = [
+ {file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"},
+ {file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"},
+]
+typed-ast = [
+ {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
+ {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"},
+ {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"},
+ {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"},
+ {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
+ {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
+ {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
+ {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
+ {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
+ {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
+ {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
+ {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
+ {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
+ {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
+ {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
+ {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
+ {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
+ {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
+ {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
+ {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
+ {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
+]
+typing-extensions = [
+ {file = "typing_extensions-3.7.4.2-py2-none-any.whl", hash = "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392"},
+ {file = "typing_extensions-3.7.4.2-py3-none-any.whl", hash = "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5"},
+ {file = "typing_extensions-3.7.4.2.tar.gz", hash = "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae"},
+]
+urllib3 = [
+ {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"},
+ {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"},
+]
+wcwidth = [
+ {file = "wcwidth-0.2.4-py2.py3-none-any.whl", hash = "sha256:79375666b9954d4a1a10739315816324c3e73110af9d0e102d906fdb0aec009f"},
+ {file = "wcwidth-0.2.4.tar.gz", hash = "sha256:8c6b5b6ee1360b842645f336d9e5d68c55817c26d3050f46b235ef2bc650e48f"},
+]
+webencodings = [
+ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
+ {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
+]
+widgetsnbextension = [
+ {file = "widgetsnbextension-3.5.1-py2.py3-none-any.whl", hash = "sha256:bd314f8ceb488571a5ffea6cc5b9fc6cba0adaf88a9d2386b93a489751938bcd"},
+ {file = "widgetsnbextension-3.5.1.tar.gz", hash = "sha256:079f87d87270bce047512400efd70238820751a11d2d8cb137a5a5bdbaf255c7"},
+]
+zipp = [
+ {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"},
+ {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"},
+]
diff --git a/dabbling/pyproject.toml b/dabbling/pyproject.toml
@@ -0,0 +1,32 @@
+[tool.poetry]
+name = "dabbling"
+version = "0.1.0"
+description = ""
+authors = ["Yuval Langer"]
+license = "agpl-3.0-or-later"
+
+[tool.poetry.dependencies]
+python = ">=3.6.1,<4"
+beautifulsoup4 = ">=4,<5"
+jupyter = ">=1,<2"
+matplotlib = ">=3.2,<4"
+#manimlib = ">=0.1,<0.2"
+requests = ">=2,<3"
+pandas = ">=1,<2"
+click = "^7.1"
+
+[tool.poetry.dev-dependencies]
+pytest = "^"
+mypy = "^0.780"
+coverage = {extras = ["toml"], version = "^5.1"}
+pytest-cov = "^2.9.0"
+black = "^19.10b0"
+
+[tool.poetry.scripts]
+dragon-curve = "src.dabbling.console:main_dragon"
+hilbert-curve = "src.dabbling.console:main_hilbert"
+
+[build-system]
+requires = ["poetry>=0.12"]
+build-backend = "poetry.masonry.api"
+
diff --git a/dabbling/src/dabbling/WTC_plaza_2001-09-11.py b/dabbling/src/dabbling/WTC_plaza_2001-09-11.py
@@ -0,0 +1,178 @@
+import math
+import random
+from subprocess import Popen
+
+import cv2
+import numpy as np
+
+
+BLOOD_COLOR = (0, 0, 255, 255)
+STICK_FIGURE_COLOR = (0, 0, 0, 255)
+
+
+def make_random_direction_exp():
+ return math.e ** (1j * math.tau * random.random())
+
+
+def make_random_direction_sin_cos():
+ uniform = random.random()
+ return complex(
+ cos(math.tau * uniform),
+ math.sin(math.tau * uniform),
+ )
+
+
+def make_random_stick_figure_positions():
+ head_neck_distance = 0.05
+ elbow_neck_distance = arm_tip_elbow_distance = 0.1
+ pelvis_neck_distance = 0.15
+ knee_pelvis_distance = 0.1
+ leg_tip_knee_distance = 0.1
+
+ head_position = random.random() + random.random() * 1j
+
+ neck_position = head_position + head_neck_distance * math.e ** (
+ math.tau * 1j * random.random()
+ )
+
+ left_elbow_position = neck_position + elbow_neck_distance * math.e ** (
+ math.tau * 1j * random.random()
+ )
+ right_elbow_position = neck_position + elbow_neck_distance * math.e ** (
+ math.tau * 1j * random.random()
+ )
+
+ left_arm_tip_position = left_elbow_position + arm_tip_elbow_distance * math.e ** (
+ math.tau * 1j * random.random()
+ )
+ right_arm_tip_position = right_elbow_position + arm_tip_elbow_distance * math.e ** (
+ math.tau * 1j * random.random()
+ )
+
+ pelvis_position = neck_position + pelvis_neck_distance * math.e ** (
+ math.tau * 1j * random.random()
+ )
+
+ left_knee_position = pelvis_position + knee_pelvis_distance * math.e ** (
+ math.tau * 1j * random.random()
+ )
+ right_knee_position = pelvis_position + knee_pelvis_distance * math.e ** (
+ math.tau * 1j * random.random()
+ )
+
+ left_leg_top_position = left_knee_position + leg_tip_knee_distance * math.e ** (
+ math.tau * 1j * random.random()
+ )
+ right_leg_top_position = right_knee_position + leg_tip_knee_distance * math.e ** (
+ math.tau * 1j * random.random()
+ )
+
+ return [
+ (x.real, x.imag)
+ for x in (
+ head_position,
+ neck_position,
+ left_elbow_position,
+ right_elbow_position,
+ left_arm_tip_position,
+ right_arm_tip_position,
+ pelvis_position,
+ left_knee_position,
+ right_knee_position,
+ left_leg_top_position,
+ right_leg_top_position,
+ )
+ ]
+
+
+def make_blood_splatter_positions(stick_figure_positions):
+ return [
+ (x.real, x.imag)
+ for x in [
+ complex(*stick_figure_positions[1])
+ + 0.05 * i * math.e ** (math.tau * 1j * random.random())
+ for i in range(3)
+ ] + [
+ complex(*stick_figure_positions[6])
+ + 0.05 * i * math.e ** (math.tau * 1j * random.random())
+ for i in range(3)
+ ]]
+
+
+def draw_blood_splatter(image, blood_splatter_positions):
+ image_height, image_width = image.shape[:2]
+
+ blood_splatter_positions = [
+ (int(x * image_width), int(y * image_height))
+ for x, y in blood_splatter_positions
+ ]
+
+
+ cv2.circle(image, blood_splatter_positions[0], 20, BLOOD_COLOR, -1)
+ cv2.circle(image, blood_splatter_positions[1], 10, BLOOD_COLOR, -1)
+ cv2.circle(image, blood_splatter_positions[2], 5, BLOOD_COLOR, -1)
+
+ cv2.circle(image, blood_splatter_positions[3], 20, BLOOD_COLOR, -1)
+ cv2.circle(image, blood_splatter_positions[4], 10, BLOOD_COLOR, -1)
+ cv2.circle(image, blood_splatter_positions[5], 5, BLOOD_COLOR, -1)
+
+
+def draw_stick_figure(image, stick_figure_positions):
+ # https://docs.opencv.org/4.x/d6/d6e/group__imgproc__draw.html#ga7078a9fae8c7e7d13d24dac2520ae4a2
+ # https://docs.opencv.org/4.x/dc/da5/tutorial_py_drawing_functions.html
+
+ image_height, image_width = image.shape[:2]
+
+ stick_figure_positions = [
+ (int(x * image_width), int(y * image_height)) for x, y in stick_figure_positions
+ ]
+
+ # create blank image
+
+ cv2.circle(image, stick_figure_positions[0], 15, STICK_FIGURE_COLOR, -1)
+
+ for index_a, index_b in [
+ (0, 1),
+ (1, 2),
+ (1, 3),
+ (2, 4),
+ (3, 5),
+ (1, 6),
+ (7, 6),
+ (8, 6),
+ (7, 9),
+ (8, 10),
+ ]:
+ cv2.line(
+ image,
+ stick_figure_positions[index_a],
+ stick_figure_positions[index_b],
+ STICK_FIGURE_COLOR,
+ 5,
+ )
+
+
+def main():
+ random.seed(0)
+
+ image_width, image_height = 500, 500
+
+ image = 255 * np.ones(shape=[image_width, image_height, 4], dtype=np.uint8)
+
+ for i in range(100):
+ stick_figure_positions = make_random_stick_figure_positions()
+
+ blood_splatter_positions = make_blood_splatter_positions(stick_figure_positions)
+
+ draw_blood_splatter(image, blood_splatter_positions)
+
+ draw_stick_figure(image, stick_figure_positions)
+
+ cv2.imwrite(f"prakdan_{i:02d}.png", image)
+
+ convert_process = Popen(['convert', 'prakdan_*.png', 'prakdan.gif'])
+
+ convert_process.wait()
+
+if __name__ == "__main__":
+ main()
diff --git a/dabbling/src/dabbling/__init__.py b/dabbling/src/dabbling/__init__.py
@@ -0,0 +1 @@
+__version__ = '0.1.0'
diff --git a/dabbling/src/dabbling/console.py b/dabbling/src/dabbling/console.py
@@ -0,0 +1,49 @@
+import click
+
+from . import __version__
+from .dragon_curve import dragon_curve, path_to_curvy_svg_no_clue, path_to_svg_file_content, write_matplotlib_png
+from .hilbert_curve import hilbert_curve, make_svg_content
+
+
+@click.command()
+@click.option(
+ '--iterations',
+ '-n',
+ default='5',
+ help='Number of iterations',
+ metavar='ITERS',
+ show_default=True,
+)
+@click.version_option(version=__version__)
+def main_dragon(iterations: str):
+ l = dragon_curve(int(iterations))
+
+ svg_content = path_to_svg_file_content(l)
+ with open('dragon-curve.svg', 'w') as f:
+ f.write(svg_content)
+
+ svg_content = path_to_curvy_svg_no_clue(l)
+ with open('dragon-curvy-no-clue.svg', 'w') as f:
+ f.write(svg_content)
+
+ write_matplotlib_png(l)
+
+ click.echo(l)
+
+
+@click.command()
+@click.option(
+ '--iterations',
+ '-n',
+ default='5',
+ help='Number of iterations',
+ metavar='ITERS',
+ show_default=True,
+)
+@click.version_option(version=__version__)
+def main_hilbert(iterations: str):
+ l = hilbert_curve(int(iterations))
+
+ svg_content = make_svg_content(l)
+ with open('hilbert-curve.svg', 'w') as f:
+ f.write(svg_content)
diff --git a/dabbling/src/dabbling/dragon_curve.py b/dabbling/src/dabbling/dragon_curve.py
@@ -0,0 +1,114 @@
+__version__ = '0.1.0'
+
+
+from typing import List, Tuple
+
+
+PathType = List[Tuple[int, int]]
+
+
+def ccw(p: Tuple[int, int]) -> Tuple[int, int]:
+ return [-p[1], p[0]]
+
+
+def dragon_curve(generation_number: int) -> PathType:
+ l = [[0, 0], [1, 0]]
+
+ for i in range(generation_number):
+ new_portion = [ccw(n) for n in l[1:]]
+ new_pivot = new_portion[-1]
+ l = new_portion[::-1] + l
+ l = [
+ [n[0] - new_pivot[0],
+ n[1] - new_pivot[1]]
+ for n in l]
+
+ return l
+
+
+def write_matplotlib_png(dragon_curve_path: PathType):
+ import matplotlib.pyplot as plt
+
+ plt.scatter(
+ [n[0] for n in dragon_curve_path],
+ [n[1] for n in dragon_curve_path])
+ plt.show()
+ plt.savefig('dragon-curve.png')
+
+
+def transform(values, low, high):
+ min_value = min(values)
+ max_value = max(values)
+ old_difference = max_value - min_value
+ new_difference = high - low
+ # values = [n - min_value for n in values] # [0, max-min = old_difference]
+ # values = [n / old_difference for n in values] # [0, 1]
+ # values = [n * new_difference for n in values] # [0, new_difference = high - low]
+ # values = [n + low for n in values] # [low, high]
+ return [(n - min_value) * new_difference / old_difference + low
+ for n in values]
+
+
+def path_to_svg_file_content(
+ path: PathType,
+ image_width=500,
+ image_height=500,
+ left_border=50,
+ right_border=50,
+ bottom_border=50,
+ top_border=50,
+ ) -> str:
+ path = list(zip(
+ transform([p[0] for p in path], left_border, image_width - right_border),
+ transform([p[1] for p in path], top_border, image_width - bottom_border)))
+
+ svg_path = ''.join(f'L{p[0]} {p[1]}' for p in path)
+
+ return f'''
+<svg width="{image_width}" height="{image_height}" fill="white" xmlns="http://www.w3.org/2000/svg">
+
+ <path fill="none" stroke="black" stroke-linejoin="round" stroke-width="0.7" d="M{path[0][0]} {path[0][1]}{svg_path}"/>
+
+</svg>
+ '''.strip()
+
+
+def path_to_curvy_svg_no_clue(
+ path: PathType,
+ image_width=500,
+ image_height=500,
+ left_border=50,
+ right_border=50,
+ bottom_border=50,
+ top_border=50,
+ ) -> str:
+ '''
+ path = list(zip(
+ transform([p[0] for p in path], left_border, image_width - right_border),
+ transform([p[1] for p in path], top_border, image_width - bottom_border)))
+ '''
+
+ min_x = min(p[0] for p in path)
+ min_y = min(p[1] for p in path)
+ max_x = max(p[0] for p in path)
+ max_y = max(p[1] for p in path)
+
+ stroke_width = (max_x - min_x) * 0.01
+
+ consecutives = list(zip(path[:-1], path[1:]))
+
+ svg_path = ''.join(f'L{p[0]} {p[1]}' for p in path)
+
+ svg_path += (
+ ''.join(
+ f'M{pair[0][0]} {pair[0][1]}'
+ f'A2 2 0 0 0 {pair[1][0]} {pair[1][1]}'
+ for pair in consecutives))
+
+ return f'''
+<svg width="{image_width}" height="{image_height}" viewBox="{min_x-1} {min_y-1} {max_x-min_x+1} {max_y-min_y+1}" fill="white" xmlns="http://www.w3.org/2000/svg">
+
+ <path fill="none" stroke="black" stroke-linejoin="round" stroke-width="{stroke_width}" d="M{path[0][0]} {path[0][1]}{svg_path}"/>
+
+</svg>
+ '''.strip()
diff --git a/dabbling/src/dabbling/hilbert_curve/hilbert_curve.py b/dabbling/src/dabbling/hilbert_curve/hilbert_curve.py
@@ -0,0 +1,129 @@
+def mirror_on_y(path):
+ max_x = max(p[0] for p in path)
+ return [(abs(p[0] - max_x), p[1]) for p in path]
+
+
+def mirror_on_x(path):
+ max_y = max(p[1] for p in path)
+ return [(p[0], abs(p[1] - max_y)) for p in path]
+
+
+def mirror_on_xy(path):
+ return [(p[1], p[0]) for p in path]
+
+
+def right_flip(path):
+ path = mirror_on_y(path)
+ path = mirror_on_xy(path)
+ path = mirror_on_y(path)
+
+ return path
+
+
+def left_flip(path):
+ return mirror_on_xy(path)
+
+
+def hilbert_curve(iterations):
+ curve_coordinates = [
+ (0, 0),
+ (0, 1),
+ (1, 1),
+ (1, 0),
+ ]
+
+ for i in range(iterations):
+ curve_coordinates = (
+ left_flip(curve_coordinates) +
+ [(p[0], p[1] + 2 ** (i + 1)) for p in curve_coordinates] +
+ [(p[0] + 2 ** (i + 1), p[1] + 2 ** (i + 1)) for p in curve_coordinates] +
+ [(p[0] + 2 ** (i + 1), p[1]) for p in right_flip(curve_coordinates)]
+ )
+
+ return curve_coordinates
+
+
+def hilbert_curve_complex(iterations):
+ path = [
+ 0 + 0j,
+ 0 + 1j,
+ 1 + 1j,
+ 1 + 0j,
+ ]
+
+ def lower_left(path, iteration):
+ path = [p * (-1j) for p in path]
+ path = [p.conjugate() for p in path]
+ return path
+
+ def upper_left(path, iteration):
+ path = [p + 1j * 2**iteration for p in path]
+ return path
+
+ def upper_right(path, iteration):
+ path = [p + (1 + 1j) * 2**iteration for p in path]
+ return path
+
+ def lower_right(path, iteration):
+ path = [p - 1j for p in path]
+ path = [p.conjugate() for p in path]
+ path = [p * (-1j) for p in path]
+ path = [p + 1j for p in path]
+ path = [p + 2**iteration for p in path]
+ return path
+
+ normalizer = 1
+ for i in range(iterations):
+ path = (
+ lower_left(path, i + 1) +
+ upper_left(path, i + 1) +
+ upper_right(path, i + 1) +
+ lower_right(path, i + 1)
+ )
+
+ print(path)
+
+ normalizer *= 2**(i + 1) - 1
+
+ path = [p / normalizer for p in path]
+
+ return path
+
+
+def make_svg_content(
+ path,
+ image_width = 500,
+ image_height = 500,
+ ):
+ min_x = min(p[0] for p in path)
+ min_y = min(p[1] for p in path)
+ max_x = max(p[0] for p in path)
+ max_y = max(p[1] for p in path)
+ margins = ' '.join(
+ map(
+ str,
+ (
+ min_x - 0.05 * (max_x - min_x),
+ min_y - 0.05 * (max_y - min_y),
+ max_x + 1.05 * (max_x - min_x),
+ max_y + 1.05 * (max_y - min_y),
+ )))
+ path = mirror_on_x(path)
+ stroke_width = min(0.001 * (max_x - min_x), 0.1)
+ svg_path = f'M{path[0][0]} {path[0][1]}' + ''.join(f'L{p[0]} {p[1]}' for p in path[1:])
+ return f'''
+<svg width="{image_width}" height="{image_height}" viewBox="{margins}" fill="white" xmlns="http://www.w3.org/2000/svg">
+ <path stroke="black" stroke-width="{stroke_width}" d="{svg_path}"/>
+</svg>'''
+
+
+if __name__ == '__main__':
+ import sys
+ path = hilbert_curve(int(sys.argv[1]))
+ svg_content = make_svg_content(path)
+ with open('hilbert-curve.svg', 'w') as f:
+ f.write(svg_content)
+ path = hilbert_curve_complex(int(sys.argv[1]))
+ svg_content = make_svg_content([(p.real, p.imag) for p in path])
+ with open('hilbert-curve-complex.svg', 'w') as f:
+ f.write(svg_content)
diff --git a/dabbling/tests/__init__.py b/dabbling/tests/__init__.py
diff --git a/dabbling/tests/test_dabbling.py b/dabbling/tests/test_dabbling.py
@@ -0,0 +1,5 @@
+from dabbling import __version__
+
+
+def test_version():
+ assert __version__ == '0.1.0'