├── LICENSE ├── README.md ├── img ├── add.png ├── burgessshale.png ├── clear.png ├── darkknight.png ├── delete.png ├── drawing.png ├── favicon.gif ├── gh.png ├── habitrail.png ├── hide.png ├── lilypad.png ├── link.png ├── logo.png ├── open.png ├── pufferfish.png ├── reset.png ├── restart.png ├── rotate.png ├── truelove.png ├── tubular.png ├── zoomIn.png └── zoomOut.png ├── index.html ├── sgn.css └── sgn.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 SeedCode 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spirographⁿ 2 | Based on the classic Spirograph toy. 3 | 4 | [Visit Spirographⁿ at SeedCode](https://seedcode.com/SpirographN/sgn.html) 5 | 6 | A Javascript drawing app, using canvas, where you can add/nest *n* number of gears (rotors) leading to endless possibilities of complex shapes and patterns. 7 | 8 | Unfortunately, the canvas line rendering on Safari is not precise enough to get all the details in the more complex shapes. Chrome gives the best results. 9 | 10 | ## Spirographⁿ in action 11 | 12 | [![truelove](img/drawing.png)](img/drawing.png) 13 | 14 | ## Sample Drawings Created with Spirographⁿ 15 | 16 | [![truelove](img/truelove.png)](img/truelove.png) 17 | [https://seedcode.com/SpirographN/sgn.html?pre=true%20love](https://seedcode.com/SpirographN/sgn.html?pre=true%20love) 18 | [![truelove](img/lilypad.png)](img/lilypad.png) 19 | [https://seedcode.com/SpirographN/sgn.html?pre=lily%20pad](https://seedcode.com/SpirographN/sgn.html?pre=lily%20pad) 20 | [![habitrail](img/habitrail.png)](img/habitrail.png) 21 | [https://seedcode.com/SpirographN/sgn.html?pre=habitrail](https://seedcode.com/SpirographN/sgn.html?pre=habitrail) 22 | [![puffesfish](img/pufferfish.png)](img/pufferfish.png) 23 | [https://seedcode.com/SpirographN/sgn.html?pre=pufferfish](https://seedcode.com/SpirographN/sgn.html?pre=pufferfish) 24 | [![puffesfish](img/tubular.png)](img/tubular.png) 25 | [https://seedcode.com/SpirographN/sgn.html?pre=tubular](https://seedcode.com/SpirographN/sgn.html?pre=tubular) 26 | [![truelove](img/burgessshale.png)](img/burgessshale.png) 27 | [https://seedcode.com/SpirographN/sgn.html?pre=burgess%20shale](https://seedcode.com/SpirographN/sgn.html?pre=burgess%20shale) 28 | 29 | 30 | -------------------------------------------------------------------------------- /img/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/add.png -------------------------------------------------------------------------------- /img/burgessshale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/burgessshale.png -------------------------------------------------------------------------------- /img/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/clear.png -------------------------------------------------------------------------------- /img/darkknight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/darkknight.png -------------------------------------------------------------------------------- /img/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/delete.png -------------------------------------------------------------------------------- /img/drawing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/drawing.png -------------------------------------------------------------------------------- /img/favicon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/favicon.gif -------------------------------------------------------------------------------- /img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/gh.png -------------------------------------------------------------------------------- /img/habitrail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/habitrail.png -------------------------------------------------------------------------------- /img/hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/hide.png -------------------------------------------------------------------------------- /img/lilypad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/lilypad.png -------------------------------------------------------------------------------- /img/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/link.png -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/logo.png -------------------------------------------------------------------------------- /img/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/open.png -------------------------------------------------------------------------------- /img/pufferfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/pufferfish.png -------------------------------------------------------------------------------- /img/reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/reset.png -------------------------------------------------------------------------------- /img/restart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/restart.png -------------------------------------------------------------------------------- /img/rotate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/rotate.png -------------------------------------------------------------------------------- /img/truelove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/truelove.png -------------------------------------------------------------------------------- /img/tubular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/tubular.png -------------------------------------------------------------------------------- /img/zoomIn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/zoomIn.png -------------------------------------------------------------------------------- /img/zoomOut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/SpirographN/a197359a26b9b75bb44960f50bbb601be180a307/img/zoomOut.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spirographⁿ 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 |
18 | 19 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /sgn.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: rgb(88,93,118); 3 | font-family: Cabin,Arial,Verdana,sans-serif; 4 | } 5 | 6 | div.divSeparator { 7 | margin-top: 6px; 8 | margin-bottom: 6px; 9 | border-bottom: 1px; 10 | border-bottom-color: rgba(200, 200, 200, 0.7 ); 11 | border-bottom-style: solid; 12 | } 13 | 14 | div.logo { 15 | float: right; 16 | margin-right: 18px; 17 | margin-top: 12px; 18 | } 19 | 20 | div.pad { 21 | position: absolute; 22 | border-radius: 4px 4px 4px 4px; 23 | z-index: -1; 24 | padding-top: 12px; 25 | margin-left: 2px; 26 | } 27 | 28 | div.rotors { 29 | margin-left: -6px; 30 | padding-left: 6px; 31 | overflow-y: auto; 32 | } 33 | 34 | div.sidebar { 35 | border-right: .1em; 36 | border-right-style: solid; 37 | border-right-color: rgba(98,103,170,0.5); 38 | position: absolute; 39 | float: left; 40 | background: rgba(240, 240, 240, 1); 41 | border-radius: 4px 0px 0px 4px; 42 | padding-left: 14px; 43 | padding-right: 12px; 44 | padding-top: 12px; 45 | width: 200px; 46 | z-index: 5; 47 | margin-left: 2px; 48 | -webkit-box-shadow: 1px 0px .5px rgba(220,220,220,0.8); 49 | -moz-box-shadow: 1px 0px .5px rgba(220,220,220,0.8); 50 | box-shadow: 1px 0px .5px rgba(220,220,220,0.8); 51 | } 52 | 53 | div.title { 54 | margin-top: 5px; 55 | font-size: 13pt; 56 | } 57 | 58 | 59 | 60 | span.addLabel { 61 | cursor: pointer; 62 | font-size: 10pt; 63 | margin: 0px; 64 | vertical-align: 3%; 65 | color: #004d00; 66 | margin-left: 2px; 67 | } 68 | 69 | span.addLabel:hover { 70 | text-decoration: underline; 71 | } 72 | 73 | span.buttonLabel { 74 | cursor: pointer; 75 | font-size: 10pt; 76 | margin: 0px; 77 | color: #000000; 78 | margin-left: 1px; 79 | } 80 | 81 | span.buttonLabel:hover { 82 | text-decoration: underline; 83 | } 84 | 85 | span.buttonText { 86 | font-size: 10pt; 87 | vertical-align: middle; 88 | } 89 | 90 | span.buttonTextSmall { 91 | font-size: 9pt; 92 | vertical-align:middle; 93 | } 94 | 95 | span.deleteLabel { 96 | cursor: pointer; 97 | font-size: 10pt; 98 | margin: 0px; 99 | vertical-align: 10%; 100 | color: #660000; 101 | } 102 | 103 | span.deleteLabel:hover { 104 | text-decoration: underline; 105 | } 106 | 107 | span.label { 108 | margin-left: -4px; 109 | display: inline-block; 110 | margin-bottom: 3px; 111 | font-size: 13pt; 112 | vertical-align: top; 113 | } 114 | 115 | span.letterLabel { 116 | display: inline-block; 117 | font-size: 9pt; 118 | width: 13px; 119 | margin-bottom: 0px; 120 | } 121 | 122 | span.n { 123 | color: #6267AA; 124 | font-style: italic; 125 | font-weight: bolder; 126 | } 127 | 128 | span.title { 129 | margin-left: -4px; 130 | display: inline-block; 131 | margin-bottom: 3px; 132 | font-size: 19pt; 133 | } 134 | 135 | span.typelabel { 136 | padding-left: 4px; 137 | font-size: 9pt; 138 | } 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | input.number { 147 | font-size: 9pt; 148 | border: 1px; 149 | border-color: rgba(98,103,170,0.3); 150 | border-style: solid; 151 | border-radius: 4px 4px 4px 4px; 152 | padding-left: 4pt; 153 | margin-bottom: -1px; 154 | margin-left: 4px; 155 | margin-top: 3px; 156 | margin-right: 3px; 157 | width: 38px; 158 | outline: none; 159 | } 160 | 161 | input.angle { 162 | font-size: 9pt; 163 | border: 1px; 164 | border-style: solid; 165 | border-color: rgba(98,103,170,0.5); 166 | border-radius: 4px 4px 4px 4px; 167 | padding-top: 6px; 168 | padding-bottom: 7px; 169 | margin-left: 0px; 170 | margin-right: 0px; 171 | width: 17%; 172 | outline: none; 173 | vertical-align: 40%; 174 | text-align: center; 175 | } 176 | input.radio { 177 | margin-left: 3px; 178 | margin-top: -2px; 179 | margin-bottom: 0px; 180 | margin-right: 4px; 181 | } 182 | 183 | input.color { 184 | width: 43px; 185 | padding: 0px; 186 | height: 16px; 187 | margin-left: 4px; 188 | border-radius: 4px 4px 4px 4px; 189 | margin-top: 4px; 190 | } 191 | 192 | select { 193 | color: black; 194 | background: white; 195 | font-size: 9pt; 196 | border: 1px; 197 | border-color: rgba(98,103,170,0.3); 198 | border-style: solid; 199 | border-radius: 4px 4px 4px 4px; 200 | padding-left: 4pt; 201 | margin-bottom: 4px; 202 | margin-top: 3px; 203 | outline: 0px; 204 | } 205 | 206 | select.preset { 207 | width: 100%; 208 | } 209 | 210 | select.speed { 211 | width: 75%; 212 | margin-left: 4px; 213 | } 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | button { 226 | border: 1px; 227 | border-style: solid; 228 | border-color: rgba(98,103,170,0.5); 229 | outline: 0px; 230 | background: none; 231 | font-size: 9pt; 232 | padding-top: 4px; 233 | padding-bottom: 1px; 234 | margin-bottom: 3px; 235 | border-radius: 4px 4px 4px 4px; 236 | cursor: pointer; 237 | width:20%; 238 | } 239 | 240 | button:hover { 241 | background: lightgrey; 242 | } 243 | 244 | button:active { 245 | color: white; 246 | background: gray; 247 | } 248 | 249 | button.add { 250 | border:0; 251 | padding-top: 2px; 252 | padding-bottom: 2px; 253 | margin-bottom: -1px; 254 | margin-top: -1px; 255 | text-align:left; 256 | float:right; 257 | width:55%; 258 | } 259 | 260 | button.delete { 261 | border:0; 262 | padding-left:0px; 263 | padding-top: 2px; 264 | padding-bottom: 2px; 265 | margin-bottom: -1px; 266 | margin-top: -1px; 267 | text-align:left; 268 | width:100%; 269 | } 270 | 271 | button.draw { 272 | border: 0px; 273 | width: 100%; 274 | background: rgba(0, 153, 51, .9); 275 | color: rgba(240, 240, 220, 1); 276 | font-size: 12pt; 277 | padding-top: 6px; 278 | padding-bottom: 6px; 279 | margin-bottom: 0px; 280 | } 281 | 282 | button.draw:hover { 283 | background: rgba(0, 153, 51, .7); 284 | color: white; 285 | } 286 | button.draw:active { 287 | color: rgba(240, 240, 240, 1); 288 | background: rgba(0, 153, 51, 1); 289 | } 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | img.logo { 299 | width: 200px; 300 | height: 73px; 301 | display: block; 302 | } 303 | 304 | img.add { 305 | width: 18px; 306 | height: 18px; 307 | float: left; 308 | margin-bottom:0px; 309 | margin-top:0px; 310 | margin-right:8px; 311 | } 312 | 313 | img.delete { 314 | width: 18px; 315 | height: 18px; 316 | float: left; 317 | margin-bottom: 0px; 318 | margin-top: 0px; 319 | margin-right:4px; 320 | } 321 | 322 | img.git { 323 | width: 21px; 324 | height: 21px; 325 | float: left; 326 | margin-bottom: 2px; 327 | } 328 | 329 | img.edit { 330 | width: 20px; 331 | height: 20px; 332 | } 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | -------------------------------------------------------------------------------- /sgn.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2016 SeedCode 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | var sgn = (function(settings) { 26 | 'use strict' 27 | 28 | return { 29 | load: load, 30 | spot: spot, 31 | } 32 | 33 | //public functions 34 | 35 | function load(sidebarId, canvasDivId) { 36 | 37 | var v; 38 | var r; 39 | var t; 40 | var n; 41 | var c; 42 | var a; 43 | var d; 44 | var o; 45 | var i; 46 | 47 | //establish sidebar and pad 48 | if (!sidebarId || !canvasDivId) { 49 | alert("Error:Canvas and Sidebar not specified"); 50 | return; 51 | } 52 | 53 | settings.sidebarDiv = document.getElementById(sidebarId); 54 | settings.divCanvas = document.getElementById(canvasDivId); 55 | 56 | //preserve ids specified in ininitial call 57 | settings.idNames.sidebar = sidebarId; 58 | settings.idNames.pad = canvasDivId; 59 | 60 | //set classes 61 | settings.sidebarDiv.className = "sidebar"; 62 | settings.divCanvas.className = "pad"; 63 | 64 | //create data object from url 65 | //load preset/favorote from settings if none retrieved from the URL 66 | var parms = location.search.substring(1).split("&"); 67 | if (parms[0]) { 68 | d = {}; 69 | for (c in parms) { 70 | a = parms[c].split("="); 71 | d[a[0]] = decodeURIComponent(a[1]); 72 | } 73 | if (d["cl"]) { 74 | //append hash to color 75 | d["cl"] = "#" + d["cl"]; 76 | } 77 | 78 | if (d["pre"]) { 79 | //a preset was passed, so use that 80 | for (c in settings.presets) { 81 | if (settings.presets[c].name === d["pre"]) { 82 | d = settings.presets[c]; 83 | } 84 | } 85 | } 86 | if (!d["st"] || !d["r1"] || !d["pen"]) { 87 | //validate object properties or else load pre-set 88 | var preSet = Math.round(Math.random() * (settings.presets.length - 1)); 89 | d = settings.presets[preSet]; 90 | } 91 | if (!d["sp"]) { 92 | //set speed if not specified 93 | d["sp"] = 1; 94 | } 95 | } else { 96 | //no parms specified 97 | var preSet = Math.round(Math.random() * (settings.presets.length - 1)); 98 | d = settings.presets[preSet]; 99 | } 100 | 101 | //remove rotors to start clean 102 | while (settings.sidebarDiv.firstChild) { 103 | settings.sidebarDiv.removeChild(settings.sidebarDiv.firstChild); 104 | } 105 | 106 | //Begin creating sidebar elements 107 | 108 | //add static elements and values from preset 109 | //add events to inputs 110 | var e = document.createElement("SPAN"); 111 | e.className = "title"; 112 | e.innerHTML = "Spirograph"; 113 | e.innerHTML += ''; 114 | settings.sidebarDiv.appendChild(e); 115 | 116 | e = document.createElement("DIV"); 117 | e.className = "divSeparator"; 118 | settings.sidebarDiv.appendChild(e); 119 | 120 | 121 | e = document.createElement("SPAN"); 122 | e.className = "label"; 123 | e.innerHTML = "presets"; 124 | settings.sidebarDiv.appendChild(e); 125 | 126 | var sel = document.createElement("SELECT"); 127 | sel.className = "preset"; 128 | sel.setAttribute("name", "presets"); 129 | sel.setAttribute("id", settings.idNames.presets); 130 | for (c in settings.presets) { 131 | o = document.createElement("OPTION"); 132 | o.setAttribute("value", settings.presets[c].name); 133 | o.innerHTML = settings.presets[c].name; 134 | sel.appendChild(o); 135 | } 136 | settings.sidebarDiv.appendChild(sel) 137 | presetEvent(settings.idNames.presets, "change"); 138 | 139 | e = document.createElement("DIV"); 140 | e.className = "divSeparator"; 141 | settings.sidebarDiv.appendChild(e); 142 | 143 | e = document.createElement("SPAN"); 144 | e.className = "label"; 145 | e.innerHTML = "stator"; 146 | settings.sidebarDiv.appendChild(e); 147 | 148 | e = document.createElement("BR"); 149 | settings.sidebarDiv.appendChild(e); 150 | 151 | e = document.createElement("SPAN"); 152 | e.className = "letterLabel"; 153 | e.innerHTML = "sr"; 154 | settings.sidebarDiv.appendChild(e); 155 | 156 | e = document.createElement("INPUT"); 157 | e.className = "number"; 158 | e.setAttribute("type", "text"); 159 | e.setAttribute("value", d.st); 160 | e.setAttribute("id", settings.idNames.stator); 161 | settings.sidebarDiv.appendChild(e); 162 | inputEvent(settings.idNames.stator, "input"); 163 | 164 | e = document.createElement("DIV"); 165 | e.className = "divSeparator"; 166 | settings.sidebarDiv.appendChild(e); 167 | 168 | e = document.createElement("SPAN"); 169 | e.className = "label"; 170 | e.innerHTML = "rotors"; 171 | settings.sidebarDiv.appendChild(e); 172 | 173 | addButton(); 174 | 175 | e = document.createElement("BR"); 176 | settings.sidebarDiv.appendChild(e); 177 | 178 | e = document.createElement("DIV"); 179 | e.className = "rotors"; 180 | e.setAttribute("id", settings.idNames.rotors); 181 | settings.sidebarDiv.appendChild(e); 182 | 183 | //add delete block 184 | e = document.createElement("DIV"); 185 | e.setAttribute("id", settings.idNames.deleteBlock); 186 | settings.sidebarDiv.appendChild(e); 187 | 188 | e = document.createElement("DIV"); 189 | e.className = "divSeparator"; 190 | settings.sidebarDiv.appendChild(e); 191 | 192 | //add pen block first as rotor routine appends values; 193 | e = document.createElement("SPAN"); 194 | e.className = "label"; 195 | e.innerHTML = "pen"; 196 | settings.sidebarDiv.appendChild(e); 197 | 198 | e = document.createElement("BR"); 199 | settings.sidebarDiv.appendChild(e); 200 | 201 | e = document.createElement("SPAN"); 202 | e.className = "letterLabel"; 203 | e.innerHTML = "r"; 204 | settings.sidebarDiv.appendChild(e); 205 | 206 | e = document.createElement("INPUT"); 207 | e.className = "number"; 208 | e.setAttribute("type", "text"); 209 | e.setAttribute("id", settings.idNames.pen); 210 | settings.sidebarDiv.appendChild(e); 211 | inputEvent(settings.idNames.pen, "input"); 212 | 213 | e = document.createElement("BR"); 214 | settings.sidebarDiv.appendChild(e); 215 | 216 | e = document.createElement("SPAN"); 217 | e.className = "letterLabel"; 218 | e.innerHTML = "w"; 219 | settings.sidebarDiv.appendChild(e); 220 | 221 | e = document.createElement("INPUT"); 222 | e.className = "number"; 223 | e.setAttribute("type", "text"); 224 | e.setAttribute("id", settings.idNames.width); 225 | e.setAttribute("step", ".01"); 226 | settings.sidebarDiv.appendChild(e); 227 | inputEvent(settings.idNames.width, "input"); 228 | 229 | e = document.createElement("BR"); 230 | settings.sidebarDiv.appendChild(e); 231 | 232 | e = document.createElement("SPAN"); 233 | e.className = "letterLabel"; 234 | e.innerHTML = "c"; 235 | settings.sidebarDiv.appendChild(e); 236 | 237 | if (colorInputOK()) { 238 | //load color input 239 | e = document.createElement("INPUT"); 240 | e.className = "color"; 241 | e.setAttribute("type", "color"); 242 | e.setAttribute("id", settings.idNames.color); 243 | settings.sidebarDiv.appendChild(e); 244 | inputEvent(settings.idNames.color, "input"); 245 | } else { 246 | //load select color list if color input unavailable 247 | var e = document.createElement("SELECT"); 248 | e.className = "text"; 249 | e.setAttribute("name", "colors"); 250 | e.setAttribute("id", settings.idNames.color); 251 | for (c in settings.penColors) { 252 | o = document.createElement("OPTION"); 253 | o.setAttribute("value", settings.penColors[c].hex); 254 | o.innerHTML = settings.penColors[c].name; 255 | e.appendChild(o); 256 | } 257 | settings.sidebarDiv.appendChild(e) 258 | inputEvent(settings.idNames.color, "change"); 259 | } 260 | 261 | settings.sidebarDiv.appendChild(e); 262 | 263 | e = document.createElement("DIV"); 264 | e.className = "divSeparator"; 265 | settings.sidebarDiv.appendChild(e); 266 | 267 | e = document.createElement("SPAN"); 268 | e.className = "label"; 269 | e.innerHTML = "speed"; 270 | settings.sidebarDiv.appendChild(e); 271 | 272 | e = document.createElement("BR"); 273 | settings.sidebarDiv.appendChild(e); 274 | 275 | var e = document.createElement("SELECT"); 276 | e.className = "preset"; 277 | e.setAttribute("name", "speeds"); 278 | e.setAttribute("id", settings.idNames.speed); 279 | for (c in settings.speedSettings) { 280 | o = document.createElement("OPTION"); 281 | o.setAttribute("value", settings.speedSettings[c].speed); 282 | o.innerHTML = settings.speedSettings[c].name; 283 | e.appendChild(o); 284 | } 285 | settings.sidebarDiv.appendChild(e) 286 | inputEvent(settings.idNames.speed, "change"); 287 | 288 | e = document.createElement("DIV"); 289 | e.className = "divSeparator"; 290 | settings.sidebarDiv.appendChild(e); 291 | 292 | drawButton(); 293 | 294 | e = document.createElement("DIV"); 295 | e.className = "divSeparator"; 296 | settings.sidebarDiv.appendChild(e); 297 | 298 | //utility buttons 299 | 300 | clearButton(); 301 | 302 | restartButton(); 303 | 304 | hideButton(); 305 | 306 | openImageButton(); 307 | 308 | settingsURLButton(); 309 | 310 | e = document.createElement("BR"); 311 | settings.sidebarDiv.appendChild(e); 312 | 313 | zoomInButton(); 314 | 315 | zoomOutButton(); 316 | 317 | e = document.createElement("INPUT"); 318 | e.className = "angle"; 319 | e.setAttribute("type", "text"); 320 | e.setAttribute("id", settings.idNames.angle); 321 | e.setAttribute("value", settings.iOffset); 322 | e.setAttribute("title", "rotation increment"); 323 | settings.sidebarDiv.appendChild(e); 324 | inputEvent(settings.idNames.angle, "input"); 325 | 326 | rotateButton(); 327 | 328 | resetButton(); 329 | 330 | e = document.createElement("DIV"); 331 | e.className = "divSeparator"; 332 | settings.sidebarDiv.appendChild(e); 333 | 334 | gitButton(); 335 | 336 | e = document.createElement("DIV"); 337 | e.className = "divSeparator"; 338 | settings.sidebarDiv.appendChild(e); 339 | 340 | 341 | //load canvas 342 | setValues(); 343 | 344 | //add canvas 345 | drawCanvas(); 346 | 347 | //load values into inputs 348 | var lr = loadValues(d); 349 | 350 | window.onresize = function(e) { 351 | resizeCanvas(e); 352 | }; 353 | 354 | var rotorsDiv = document.getElementById('rotors'); 355 | rotorsDiv.style.height = (settings.numRotors * 27) + (settings.numRotors * 4.3 ) + 'px'; 356 | } 357 | 358 | function spot(d) { 359 | var d = { 360 | "a": "600", 361 | "b": "600", 362 | "sz": "600", 363 | "canvasClass": "pad", 364 | "closeFunction": "close", 365 | "sr": "250", 366 | "r1": "125h", 367 | "pen": "125", 368 | 369 | 370 | 371 | } 372 | 373 | } 374 | 375 | // buttons 376 | 377 | function addButton() { 378 | 379 | var b = document.createElement("BUTTON"); 380 | b.setAttribute("class", "button add"); 381 | b.setAttribute("id", settings.idNames.add); 382 | var i = document.createElement("IMG"); 383 | i.className = "add"; 384 | i.setAttribute("src", "img/add.png"); 385 | b.appendChild(i); 386 | var s = document.createElement("SPAN"); 387 | s.className = "buttonTextSmall" 388 | s.innerHTML += "add rotor"; 389 | b.appendChild(s); 390 | settings.sidebarDiv.appendChild(b); 391 | 392 | 393 | b.addEventListener("click", function() { 394 | 395 | //get current last rotor radius 396 | var newRad = document.getElementById(settings.idNames.rotor + settings.numRotors).value / 2; 397 | 398 | //toggle types 399 | if (document.getElementById(settings.idNames.e + settings.numRotors).checked) { 400 | var type = "h"; 401 | } else { 402 | var type = "e"; 403 | } 404 | 405 | settings.numRotors++; 406 | 407 | addRotor(settings.numRotors, newRad, type); 408 | 409 | //update pen radius 410 | document.getElementById(settings.idNames.pen).value = newRad; 411 | 412 | var rotorsDiv = document.getElementById('rotors'); 413 | rotorsDiv.style.height = (settings.numRotors * 27) + (settings.numRotors * 4.3 ) + 'px'; 414 | 415 | //if this is the second rotor we need our delete button. 416 | if (settings.numRotors === 2) { 417 | deleteButton(); 418 | } 419 | 420 | //scroll to the bottom 421 | document.getElementById(settings.idNames.rotors).scrollTop = document.getElementById(settings.idNames.rotors).scrollHeight; 422 | 423 | setValues(); 424 | if (!settings.draw) { 425 | drawCircles(); 426 | } 427 | 428 | }); 429 | 430 | } 431 | 432 | function deleteButton() { 433 | var div = document.getElementById(settings.idNames.deleteBlock); 434 | var e = document.createElement("DIV"); 435 | e.className = "divSeparator"; 436 | e.setAttribute("id", settings.idNames.deleteSep); 437 | div.appendChild(e); 438 | var span = document.createElement("SPAN"); 439 | span.setAttribute("id", settings.idNames.deleteLine); 440 | div.appendChild(span); 441 | var b = document.createElement("BUTTON"); 442 | b.className = "delete"; 443 | b.setAttribute("id", settings.idNames.delete); 444 | span.appendChild(b); 445 | e = document.createElement("IMG"); 446 | e.className = "delete"; 447 | e.setAttribute("src", "img/delete.png"); 448 | b.appendChild(e); 449 | e = document.createElement("SPAN"); 450 | e.className = "buttonTextSmall" 451 | e.innerHTML = "delete last rotor" 452 | e.setAttribute("id", settings.idNames.deleteLabel) 453 | b.appendChild(e); 454 | b.addEventListener("click", function() { 455 | var element = document.getElementById(settings.idNames.item + settings.numRotors); 456 | element.parentNode.removeChild(element); 457 | if (settings.numRotors === 2) { 458 | var element = document.getElementById(settings.idNames.deleteLine); 459 | element.parentNode.removeChild(element); 460 | var element = document.getElementById(settings.idNames.deleteSep); 461 | element.parentNode.removeChild(element); 462 | }; 463 | settings.numRotors--; 464 | document.getElementById(settings.idNames.pen).value = document.getElementById(settings.idNames.rotor + settings.numRotors).value; 465 | setValues(); 466 | if (!settings.draw) { 467 | drawCircles(); 468 | } 469 | //scroll to the bottom 470 | var div = document.getElementById(settings.idNames.rotors); 471 | div.scrollTop = div.scrollHeight; 472 | 473 | var rotorsDiv = document.getElementById('rotors'); 474 | rotorsDiv.style.height = (settings.numRotors * 27) + (settings.numRotors * 4.3 ) + 'px'; 475 | }); 476 | 477 | } 478 | 479 | function makeButton(id, className, title, text) { 480 | var b = document.createElement("BUTTON"); 481 | if (className) { 482 | b.setAttribute("class", className); 483 | } 484 | if (id) { 485 | b.setAttribute("id", id); 486 | } 487 | if (title) { 488 | b.setAttribute("title", title); 489 | } 490 | if (text) { 491 | b.innerHTML = text; 492 | } 493 | return b; 494 | } 495 | 496 | function makeImage(id, className, src) { 497 | var i = document.createElement("IMG"); 498 | if (className) { 499 | i.setAttribute("class", className); 500 | } 501 | if (id) { 502 | i.setAttribute("id", id); 503 | } 504 | if (src) { 505 | i.setAttribute("src", src); 506 | } 507 | return i; 508 | } 509 | 510 | function makeSpan(id, className, content) { 511 | var s = document.createElement("SPAN"); 512 | if (className) { 513 | s.setAttribute("class", className); 514 | } 515 | if (content) { 516 | s.innerHTML = content; 517 | } 518 | if (id) { 519 | s.setAttribute("id", id); 520 | } 521 | return s; 522 | } 523 | 524 | function drawButton() { 525 | var button = makeButton(settings.idNames.draw, "draw", false, "draw"); 526 | settings.sidebarDiv.appendChild(button); 527 | button.addEventListener("click", function() { 528 | if (!settings.draw) { 529 | if (settings.i === 0) { 530 | settings.timer = new Date().getTime() / 1000; 531 | } 532 | settings.draw = true; 533 | button.innerHTML = "pause"; 534 | setValues(); 535 | requestAnimationFrame(draw); 536 | } else { 537 | settings.draw = false; 538 | button.innerHTML = "draw"; 539 | } 540 | }); 541 | } 542 | 543 | function clearButton() { 544 | var button = makeButton(settings.idNames.clear, false, "clear drawing", false); 545 | var image = makeImage(settings.idNames.clearIcon, "edit", "img/clear.png"); 546 | button.appendChild(image); 547 | settings.sidebarDiv.appendChild(button); 548 | button.addEventListener("click", clearCanvas); 549 | } 550 | 551 | function restartButton() { 552 | var button = makeButton(settings.idNames.restart, false, "reset pen to beginning position", false); 553 | var image = makeImage(settings.idNames.clearIcon, "edit", "img/restart.png"); 554 | button.appendChild(image); 555 | settings.sidebarDiv.appendChild(button); 556 | button.addEventListener("click", restart); 557 | } 558 | 559 | function hideButton() { 560 | var button = makeButton(settings.idNames.hide, false, "hide circles", false); 561 | var image = makeImage(settings.idNames.hideIcon, "edit", "img/hide.png"); 562 | button.appendChild(image); 563 | settings.sidebarDiv.appendChild(button); 564 | button.addEventListener("click", function() { 565 | if (settings.circles === "show") { 566 | settings.circles = "hide"; 567 | button.setAttribute("title", "show circles"); 568 | setValues(); 569 | drawCircles(); 570 | } else { 571 | settings.circles = "show"; 572 | button.setAttribute("title", "hide circles"); 573 | setValues(); 574 | drawCircles(); 575 | } 576 | }); 577 | } 578 | 579 | function openImageButton() { 580 | var button = makeButton(settings.idNames.open, false, "open drawing in new tab", false); 581 | var image = makeImage(settings.idNames.openIcon, "edit", "img/open.png"); 582 | button.appendChild(image); 583 | settings.sidebarDiv.appendChild(button); 584 | button.addEventListener("click", function() { 585 | var d = settings.canvasPen.toDataURL(); 586 | var newTab = window.open(); 587 | var img = newTab.document.createElement('img'); 588 | img.src = d; 589 | var title = newTab.document.createElement('title'); 590 | title.innerHTML = 'Spirographⁿ Image'; 591 | newTab.document.head.appendChild(title); 592 | newTab.document.body.appendChild(img); 593 | }); 594 | } 595 | 596 | function settingsURLButton() { 597 | var button = makeButton(settings.idNames.link, false, "URL for drawing settings", false); 598 | var image = makeImage(settings.idNames.linkIcon, "edit", "img/link.png"); 599 | button.appendChild(image); 600 | settings.sidebarDiv.appendChild(button); 601 | button.addEventListener("click", function() { 602 | window.open(settings.url); 603 | }); 604 | } 605 | 606 | function zoomInButton() { 607 | var button = makeButton(settings.idNames.zoomIn, false, "zoom drawing tools in", false); 608 | var image = makeImage(settings.idNames.zoomInIcon, "edit", "img/zoomIn.png"); 609 | button.appendChild(image); 610 | settings.sidebarDiv.appendChild(button); 611 | button.addEventListener("click", function() { 612 | zoom("in"); 613 | }); 614 | } 615 | 616 | function zoomOutButton() { 617 | var button = makeButton(settings.idNames.zoomOut, false, "zoom drawing tools out", false); 618 | var image = makeImage(settings.idNames.zoomOutIcon, "edit", "img/zoomOut.png"); 619 | button.appendChild(image); 620 | settings.sidebarDiv.appendChild(button); 621 | button.addEventListener("click", function() { 622 | zoom("out"); 623 | }); 624 | } 625 | 626 | function rotateButton() { 627 | var button = makeButton(settings.idNames.rotate, false, "rotate drawing tools", false); 628 | var image = makeImage(settings.idNames.rotateIcon, "edit", "img/rotate.png"); 629 | button.appendChild(image); 630 | settings.sidebarDiv.appendChild(button); 631 | button.addEventListener("click", rotateDrawing); 632 | } 633 | 634 | function resetButton() { 635 | var button = makeButton(settings.idNames.reset, false, "clear zoom and rotation", false); 636 | var image = makeImage(settings.idNames.resetIcon, "edit", "img/reset.png"); 637 | button.appendChild(image); 638 | settings.sidebarDiv.appendChild(button); 639 | button.addEventListener("click", reset); 640 | } 641 | 642 | function gitButton() { 643 | var button = makeButton(settings.idNames.git, false, "clear zoom and rotation", false); 644 | button.setAttribute("style", "width:100%"); 645 | var image = makeImage(settings.idNames.gitIcon, "git", "img/gh.png"); 646 | var span = makeSpan(settings.idNames.gitLabel, "buttonText", "download at github"); 647 | button.appendChild(image); 648 | button.appendChild(span); 649 | settings.sidebarDiv.appendChild(button); 650 | button.addEventListener("click", function() { 651 | window.open("https://github.com/seedcode/SpirographN"); 652 | }); 653 | } 654 | 655 | // private functions 656 | 657 | function loadValues(d) { 658 | 659 | //set values from d. d is either a loaded preset or created from the url 660 | document.getElementById(settings.idNames.stator).value = d.st; 661 | document.getElementById(settings.idNames.pen).value = d.pen; 662 | document.getElementById(settings.idNames.width).value = d.wd; 663 | document.getElementById(settings.idNames.color).value = d.cl; 664 | document.getElementById(settings.idNames.speed).value = d.sp; 665 | document.getElementById(settings.idNames.presets).value = d.name; 666 | 667 | //remove rotors to start clean 668 | var rotors = document.getElementById(settings.idNames.rotors); 669 | while (rotors.firstChild) { 670 | rotors.removeChild(rotors.firstChild); 671 | } 672 | 673 | //loop to add rotors 674 | var c = 1; 675 | var v; 676 | var r; 677 | var t; 678 | while (d["r" + c]) { 679 | v = d["r" + c]; 680 | r = v.substring(0, v.length - 1); 681 | t = v.substring(v.length - 1); 682 | addRotor(c, r, t); 683 | c++; 684 | } 685 | 686 | //track (and maintain) number of rotors 687 | settings.numRotors = c - 1; 688 | 689 | //if we only have one rotor, then we don't want the delete button. 690 | if (settings.numRotors === 1 && document.getElementById(settings.idNames.deleteLine)) { 691 | var element = document.getElementById(settings.idNames.deleteLine); 692 | element.parentNode.removeChild(element); 693 | var element = document.getElementById(settings.idNames.deleteSep); 694 | element.parentNode.removeChild(element); 695 | }; 696 | 697 | //create delete button if we need and don't have 698 | if (settings.numRotors > 1 && document.getElementById(settings.idNames.delete) === null) { 699 | deleteButton() 700 | } 701 | 702 | //write values from inputs to settings 703 | setValues(); 704 | 705 | //if we're not drawing, then we'll show the circles on change 706 | if (!settings.draw) { 707 | settings.circles = "show"; 708 | document.getElementById(settings.idNames.hide).setAttribute("title", "hide circles"); 709 | } 710 | 711 | //reset if we're not drawing 712 | if (!settings.draw) { 713 | reset(); 714 | drawCircles(); 715 | } 716 | 717 | //return number of rotors created 718 | return c - 1; 719 | } 720 | 721 | function addRotor(num, r, type) { 722 | 723 | var div = document.getElementById(settings.idNames.rotors); 724 | 725 | var item = document.createElement("DIV"); 726 | item.setAttribute("id", settings.idNames.item + num); 727 | div.appendChild(item); 728 | 729 | if (num > 1) { 730 | var e = document.createElement("DIV"); 731 | e.className = "divSeparator"; 732 | item.appendChild(e); 733 | } 734 | 735 | var e = document.createElement("SPAN"); 736 | e.className = "letterLabel"; 737 | e.setAttribute("style", "margin:0px"); 738 | e.innerHTML = "r" + num; 739 | item.appendChild(e); 740 | 741 | e = document.createElement("INPUT"); 742 | e.className = "number"; 743 | e.setAttribute("type", "text"); 744 | e.setAttribute("id", settings.idNames.rotor + num); 745 | e.setAttribute("value", r); 746 | item.appendChild(e); 747 | inputEvent(settings.idNames.rotor + num, "input"); 748 | 749 | //type 750 | e = document.createElement("INPUT"); 751 | e.setAttribute("type", "radio"); 752 | e.setAttribute("class", "radio"); 753 | e.setAttribute("id", settings.idNames.h + num); 754 | e.setAttribute("name", "type" + num); 755 | e.setAttribute("value", "hypotrochoid"); 756 | if (type === "h") { 757 | e.setAttribute("checked", true); 758 | } 759 | item.appendChild(e); 760 | inputEvent(settings.idNames.h + num, "click"); 761 | 762 | e = document.createElement("SPAN"); 763 | e.className = "letterLabel"; 764 | e.innerHTML = "h"; 765 | item.appendChild(e); 766 | 767 | e = document.createElement("INPUT"); 768 | e.setAttribute("type", "radio"); 769 | e.setAttribute("class", "radio"); 770 | e.setAttribute("id", settings.idNames.e + num); 771 | e.setAttribute("name", "type" + num); 772 | e.setAttribute("value", "epitrochoid"); 773 | if (type === "e") { 774 | e.setAttribute("checked", true); 775 | } 776 | item.appendChild(e); 777 | inputEvent(settings.idNames.e + num, "click"); 778 | 779 | e = document.createElement("SPAN"); 780 | e.className = "letterLabel"; 781 | e.innerHTML = "e"; 782 | item.appendChild(e); 783 | } 784 | 785 | function setValues() { 786 | 787 | settings.radii = [document.getElementById(settings.idNames.stator).value]; 788 | settings.curveColor = document.getElementById(settings.idNames.color).value; 789 | settings.curveWidth = document.getElementById(settings.idNames.width).value; 790 | settings.penRad = [document.getElementById(settings.idNames.pen).value]; 791 | settings.speed = document.getElementById(settings.idNames.speed).value; 792 | settings.iOffset = document.getElementById(settings.idNames.angle).value; 793 | 794 | settings.types = [""]; 795 | settings.pitches = [1]; 796 | settings.drawPitches = []; 797 | settings.spinPitches = [] 798 | 799 | var c = 1; 800 | var thisId = settings.idNames.rotor + c; 801 | var thisHId = settings.idNames.h + c; 802 | var thisEId = settings.idNames.e + c; 803 | 804 | var thisRotor; 805 | var thisType; 806 | var thisHId; 807 | var thisEId; 808 | 809 | //build arrays 810 | while (document.getElementById(thisId)) { 811 | thisRotor = document.getElementById(thisId).value; 812 | settings.radii.push(thisRotor); 813 | 814 | if (document.getElementById(thisHId).checked) { 815 | settings.types.push("h"); 816 | if (c > 1) { 817 | settings.drawPitches.push(settings.spinPitches[c - 2]); 818 | settings.spinPitches.push((settings.radii[c - 1] / thisRotor) - 1); 819 | if (settings.types[c - 1] === "h") { 820 | settings.directions.push(settings.directions[c - 1]); 821 | } else { 822 | settings.directions.push(settings.directions[c - 1] * -1); 823 | } 824 | } else { 825 | settings.directions = [1, 1]; 826 | settings.drawPitches.push(1); 827 | settings.spinPitches.push((settings.radii[c - 1] / thisRotor) - 1); 828 | } 829 | } else { 830 | settings.types.push("e"); 831 | if (c > 1) { 832 | settings.drawPitches.push(settings.spinPitches[c - 2]); 833 | settings.spinPitches.push((settings.radii[c - 1] / thisRotor) + 1); 834 | if (settings.types[c - 1] === "h") { 835 | settings.directions.push(settings.directions[c - 1]); 836 | } else( 837 | settings.directions.push(settings.directions[c - 1] * -1) 838 | ) 839 | } else { 840 | settings.directions = [1, 1]; 841 | settings.drawPitches.push(1); 842 | settings.spinPitches.push((settings.radii[c - 1] / thisRotor) + 1); 843 | 844 | } 845 | } 846 | c++; 847 | thisId = settings.idNames.rotor + c; 848 | thisHId = settings.idNames.h + c; 849 | thisEId = settings.idNames.e + c; 850 | } 851 | settings.numRotors = c - 1; 852 | 853 | //create url string for this config 854 | c = 0; 855 | var u = window.location.origin + window.location.pathname; 856 | for (c in settings.radii) { 857 | if (c == 0) { 858 | u += "?st=" + settings.radii[c]; 859 | } else { 860 | u += "&r" + c + "=" + settings.radii[c] + settings.types[c]; 861 | } 862 | } 863 | u += "&pen=" + settings.penRad; 864 | u += "&wd=" + settings.curveWidth; 865 | u += "&cl=" + settings.curveColor.substring(1); 866 | settings.url = u; 867 | } 868 | 869 | function drawCanvas() { 870 | //create canvas elements then call resize routine 871 | settings.canvasCircles = document.createElement("canvas"); 872 | settings.canvasCircles.id = settings.idNames.canvasCircles; 873 | settings.canvasPen = document.createElement("canvas"); 874 | settings.canvasPen.id = settings.idNames.canvasPen; 875 | settings.divCanvas.appendChild(settings.canvasCircles); 876 | settings.divCanvas.appendChild(settings.canvasPen); 877 | resizeCanvas(); 878 | } 879 | 880 | function resizeCanvas() { 881 | 882 | var offscreen = 100; 883 | 884 | //capture current draw state and pause drawing. 885 | var drawing = settings.draw; 886 | settings.draw = false; 887 | 888 | //we need to capture the current drawing and redraw when set 889 | var ctx = settings.canvasPen.getContext("2d"); 890 | var ctxCircles = settings.canvasCircles.getContext("2d"); 891 | var cd = ctx.getImageData(0, 0, settings.canvasCircles.width, settings.canvasCircles.height); 892 | 893 | ctx.save(); 894 | ctxCircles.save(); 895 | 896 | //fill window 897 | settings.windowWidth = window.innerWidth; 898 | settings.windowHeight = window.innerHeight; 899 | settings.divCanvasWidth = 98.5; 900 | settings.divCanvasHeight = settings.windowHeight - 28; 901 | settings.sidebarWidth = settings.sidebarDiv.clientWidth; 902 | 903 | //set sidebar and pad sizes and store in settings 904 | settings.divCanvas.setAttribute("style", "width:" + settings.divCanvasWidth + "%;height:" + settings.divCanvasHeight + "px;background:white"); 905 | settings.sidebarDiv.setAttribute("style", "min-height:" + settings.divCanvasHeight + "px;"); 906 | settings.left = settings.divCanvas.offsetLeft + settings.sidebarWidth - offscreen; 907 | settings.top = settings.divCanvas.offsetTop - offscreen; 908 | 909 | //set rotor list height 910 | var rotors = document.getElementById(settings.idNames.rotors); 911 | rotors.setAttribute("style", "max-height:" + settings.divCanvasHeight * .23 + "px;"); 912 | 913 | //if we're resizing and we have a previous poisition, then track the offset, so we can redraw our canvases in position 914 | //round the coordinates for the center, otherwise the redraws are off. 915 | if (settings.a) { 916 | settings.offsetL = (Math.round((settings.divCanvas.clientWidth - settings.sidebarWidth) / 2)) + offscreen - settings.a; 917 | settings.offsetT = Math.round((settings.divCanvasHeight / 2)) + offscreen - settings.b; 918 | } 919 | 920 | //now recenter 921 | settings.a = Math.round(((settings.divCanvas.clientWidth - settings.sidebarWidth) / 2)) + offscreen; 922 | settings.b = Math.round((settings.divCanvasHeight / 2)) + offscreen; 923 | 924 | //resize canvases 925 | settings.canvasCircles.height = settings.divCanvasHeight + offscreen * 2; 926 | settings.canvasCircles.width = settings.divCanvas.clientWidth - settings.sidebarWidth + offscreen * 2; 927 | settings.canvasCircles.setAttribute("Style", "left:" + settings.left + "px;top:" + settings.top + "px;position:absolute;z-index:10"); 928 | settings.canvasPen.height = settings.divCanvasHeight + offscreen * 2; 929 | settings.canvasPen.width = settings.divCanvas.clientWidth - settings.sidebarWidth + offscreen * 2; 930 | settings.canvasPen.setAttribute("Style", "left:" + settings.left + "px;top:" + settings.top + "px;position:absolute;z-index:20"); 931 | 932 | 933 | //update coordinates based on new position 934 | if (settings.penStart.x != 0) { 935 | settings.penStart.x = settings.penStart.x + settings.offsetL; 936 | settings.penStart.y = settings.penStart.y + settings.offsetT; 937 | settings.curvePoints[0].x = settings.curvePoints[0].x + settings.offsetL; 938 | settings.curvePoints[0].y = settings.curvePoints[0].y + settings.offsetT; 939 | } 940 | 941 | //restore rotation 942 | if (settings.iPosition != 0) { 943 | var posHolder = settings.iPosition; 944 | var offSetHolder = settings.iOffset; 945 | settings.iOffset = settings.iPosition * -1; 946 | rotateDrawing(); 947 | settings.iOffset = offSetHolder; 948 | settings.iPosition = posHolder; 949 | } 950 | 951 | //redraw circles 952 | if (!settings.draw) { 953 | drawCircles(); 954 | } 955 | 956 | //redraw canvas 957 | ctx.putImageData(cd, settings.offsetL, settings.offsetT); 958 | 959 | //restore drawing state 960 | settings.draw = drawing; 961 | 962 | } 963 | 964 | function circlePoint(a, b, r, ng) { 965 | var rad = ng * (Math.PI / 180); 966 | var y = r * Math.sin(rad); 967 | var x = r * Math.cos(rad); 968 | x = a + x; 969 | y = b - y; 970 | return { 971 | "x": x, 972 | "y": y 973 | } 974 | } 975 | 976 | function drawCircles() { 977 | 978 | var c = 1; 979 | var i = settings.i; 980 | 981 | var thisRad = 0; 982 | var prevRad = 0; 983 | var centerRad = 0; 984 | var thisPitch = 0; 985 | var prevPitch = 0; 986 | var prevSpinPitch = 0; 987 | var prevDrawPitch = 0; 988 | var pen; 989 | 990 | 991 | var zoom = settings.currentZoom; 992 | 993 | //clear circles canvas 994 | var ctx = settings.canvasCircles.getContext("2d"); 995 | ctx.clearRect(0, 0, settings.canvasCircles.width, settings.canvasCircles.height); 996 | 997 | //draw Stator 998 | if (settings.circles === "show") { 999 | drawOneCircle(settings.canvasCircles, settings.a, settings.b, settings.radii[0] * zoom ); 1000 | } 1001 | 1002 | //start at the center 1003 | var pt = { 1004 | "x": settings.a, 1005 | "y": settings.b, 1006 | }; 1007 | 1008 | c = 1; 1009 | //draw rotor Circles 1010 | while (c < (settings.radii.length)) { 1011 | 1012 | 1013 | //set radii, applying zoom 1014 | thisRad = Number(settings.radii[c]) * zoom; 1015 | prevRad = Number(settings.radii[c - 1]) * zoom; 1016 | if (settings.types[c] === "h") { 1017 | //hypitrochoid: circle inside 1018 | centerRad = prevRad - thisRad; 1019 | } else { 1020 | //eptrochoid: circle outside 1021 | centerRad = prevRad + thisRad; 1022 | } 1023 | 1024 | //pitches are cumulative, so extract previous from array. 1025 | if (c > 1) { 1026 | prevPitch = prevPitch + settings.pitches[c - 2]; 1027 | prevSpinPitch = prevSpinPitch + settings.spinPitches[c - 2]; 1028 | prevDrawPitch = prevDrawPitch + settings.drawPitches[c - 2]; 1029 | } else { 1030 | prevPitch = 0; 1031 | prevSpinPitch = 0; 1032 | prevDrawPitch = 0; 1033 | } 1034 | 1035 | //set travel direction 1036 | var mult = settings.directions[c]; 1037 | 1038 | //set draw pitch 1039 | var thisPitch = (settings.drawPitches[c - 1] + prevDrawPitch) * mult; 1040 | 1041 | //set pen pitch 1042 | //physics here is subjective 1043 | var os = (c > 1) ? 1 : 0; 1044 | if (settings.types[c] === "h") { 1045 | var penPitch = (settings.spinPitches[c - 1] + prevSpinPitch) * mult * -1; 1046 | } else { 1047 | var penPitch = (settings.spinPitches[c - 1] + prevSpinPitch) * mult; 1048 | } 1049 | 1050 | //draw this rotor 1051 | var pt = circlePoint(pt.x, pt.y, centerRad, i * thisPitch); 1052 | if (settings.circles === "show") { 1053 | drawOneCircle(settings.canvasCircles, pt.x, pt.y, thisRad); 1054 | } 1055 | 1056 | //draw Pen 1057 | //pen pitch set in last circle iteration 1058 | var penPt = circlePoint(pt.x, pt.y, thisRad, i * penPitch); 1059 | if (settings.circles === "show") { 1060 | var ctx = settings.canvasCircles.getContext("2d"); 1061 | ctx.lineWidth = .3; 1062 | ctx.lineStyle = settings.circleColor; 1063 | ctx.beginPath(); 1064 | ctx.moveTo(pt.x, pt.y); 1065 | ctx.lineTo(penPt.x, penPt.y); 1066 | ctx.stroke(); 1067 | ctx.closePath(); 1068 | //circle for pen Point 1069 | } 1070 | c++; 1071 | } 1072 | 1073 | //draw Pen 1074 | //pen pitch set in last circle iteration 1075 | var penPt = circlePoint(pt.x, pt.y, settings.penRad * zoom, i * penPitch); 1076 | 1077 | //mark our starting point 1078 | if (settings.i === 0) { 1079 | settings.penStart = penPt; 1080 | } 1081 | 1082 | //line from center to pen 1083 | if (settings.circles === "show") { 1084 | var ctx = settings.canvasCircles.getContext("2d"); 1085 | ctx.lineWidth = .2; 1086 | ctx.lineStyle = settings.circleColor; 1087 | ctx.beginPath(); 1088 | ctx.moveTo(pt.x, pt.y); 1089 | ctx.lineTo(penPt.x, penPt.y); 1090 | ctx.stroke(); 1091 | ctx.closePath(); 1092 | 1093 | //circle for pen Point 1094 | drawOneCircle(settings.canvasCircles, penPt.x, penPt.y, 1, true); 1095 | } 1096 | 1097 | //update curve points for drawCurve() 1098 | //only maintain previous point, so we'll always plot previous to current. 1099 | settings.curvePoints.push(penPt); 1100 | if (settings.curvePoints.length > 2) { 1101 | settings.curvePoints.shift(); 1102 | } 1103 | } 1104 | 1105 | function drawOneCircle(canvas, a, b, r, fill) { 1106 | var ctx = canvas.getContext("2d"); 1107 | ctx.beginPath(); 1108 | ctx.arc(a, b, r, 0, 2 * Math.PI); 1109 | var currentColor = ctx.strokeStyle; 1110 | var currentWidth = ctx.lineWidth; 1111 | ctx.strokeStyle = settings.circleColor; 1112 | ctx.lineWidth = settings.circleStroke; 1113 | if (fill) { 1114 | ctx.fillStyle = settings.curveColor; 1115 | ctx.fill(); 1116 | ctx.strokeStyle = settings.curveColor; 1117 | } 1118 | ctx.stroke(); 1119 | ctx.closePath(); 1120 | ctx.strokeStyle = currentColor; 1121 | ctx.strokeStyle = currentWidth; 1122 | } 1123 | 1124 | function drawCurve() { 1125 | var ctx = settings.canvasPen.getContext("2d"); 1126 | ctx.beginPath(); 1127 | ctx.strokeStyle = settings.curveColor; 1128 | //x 1129 | ctx.lineWidth = parseFloat(settings.curveWidth) + 0.001; 1130 | ctx.moveTo(settings.curvePoints[0].x, settings.curvePoints[0].y); 1131 | ctx.lineTo(settings.curvePoints[1].x, settings.curvePoints[1].y); 1132 | ctx.stroke(); 1133 | ctx.closePath(); 1134 | } 1135 | 1136 | function draw() { 1137 | 1138 | //if we've cycled back to the beginning, then pause 1139 | if ( 1140 | settings.curvePoints[1] && settings.draw && settings.i > settings.iterator && 1141 | settings.curvePoints[1].x === settings.penStart.x && 1142 | settings.curvePoints[1].y.toFixed(1) === settings.penStart.y.toFixed(1) 1143 | ) { 1144 | var nd = new Date().getTime() / 1000; 1145 | settings.timer = nd - settings.timer; 1146 | //console.log(settings.timer); 1147 | var button = document.getElementById(settings.idNames.draw); 1148 | settings.draw = false; 1149 | button.innerHTML = "draw"; 1150 | settings.i = 0; 1151 | if (settings.circleReset) { 1152 | settings.circles = "show"; 1153 | settings.circleReset = false; 1154 | } 1155 | drawCircles(); 1156 | return; 1157 | } 1158 | 1159 | //button hs been toggled 1160 | if (!settings.draw) { 1161 | if (settings.circleReset) { 1162 | settings.circles = "show"; 1163 | settings.circleReset = false; 1164 | } 1165 | drawCircles(); 1166 | return; 1167 | } 1168 | 1169 | var c = 0; 1170 | var stu; 1171 | 1172 | //adjust speed so 1 iteration per frame is the smallest we use 1173 | //if decimal is specified we add a timeout to the frame below. 1174 | if (settings.speed < 1) { 1175 | stu = 1 1176 | } else { 1177 | stu = settings.speed; 1178 | } 1179 | 1180 | //hide circles if we're going too fast 1181 | if (settings.speed > 50 && settings.circles === "show") { 1182 | settings.circles = "hide"; 1183 | settings.circleReset = true; 1184 | } else if (settings.speed <= 50 && settings.circleReset && settings.circles === "hide") { 1185 | //we've slowed down, so we can show again 1186 | settings.circles = "show"; 1187 | settings.circleReset = false; 1188 | } 1189 | 1190 | //run circles off for internal loop 1191 | if (settings.circles === "show") { 1192 | settings.circles = "hide"; 1193 | var circles = true; 1194 | } 1195 | 1196 | //flag that a drawing exists in settings 1197 | //hard to detect if drawing is present on canvas otherwise. 1198 | settings.drawing = true; 1199 | 1200 | //loop through the speed iterations without a frame 1201 | //this should run at least once 1202 | while (c < stu) { 1203 | //if we've cycled back to the beginning, then pause 1204 | if ( 1205 | settings.curvePoints[1] && settings.draw && settings.i > settings.iterator && 1206 | settings.curvePoints[1].x === settings.penStart.x && 1207 | settings.curvePoints[1].y.toFixed(1) === settings.penStart.y.toFixed(1) 1208 | ) { 1209 | var nd = new Date().getTime() / 1000; 1210 | settings.timer = nd - settings.timer; 1211 | //console.log(settings.timer); 1212 | var button = document.getElementById(settings.idNames.draw); 1213 | settings.draw = false; 1214 | button.innerHTML = "draw"; 1215 | settings.i = 0; 1216 | if (settings.circleReset) { 1217 | settings.circles = "show"; 1218 | settings.circleReset = false; 1219 | } 1220 | break; 1221 | } 1222 | if (!settings.draw) { 1223 | if (settings.circleReset) { 1224 | settings.circles = "show"; 1225 | settings.circleReset = false; 1226 | } 1227 | break; 1228 | } 1229 | 1230 | if (circles) { 1231 | settings.circles = "show"; 1232 | } 1233 | 1234 | drawCircles(); 1235 | drawCurve(); 1236 | //if we've done 1000 iterations, then call frame here, so there's some initial feedback 1237 | settings.i = settings.i + settings.iterator; 1238 | c = c + settings.iterator; 1239 | } 1240 | 1241 | //draw 1242 | drawCircles(); 1243 | drawCurve(); 1244 | 1245 | //if we're decimal on speed then create timeout 1246 | if (settings.speed < 1) { 1247 | setTimeout(draw, 10 / settings.speed); 1248 | } else { 1249 | //or just request frame 1250 | requestAnimationFrame(draw); 1251 | } 1252 | 1253 | } 1254 | 1255 | function colorInputOK() { 1256 | var test = document.createElement("input"); 1257 | //throws an error on IE, so test in try block. 1258 | try { 1259 | test.type = "color"; 1260 | } catch (e) { 1261 | return false; 1262 | } 1263 | test.value = "Hello World"; 1264 | return (test.value !== "Hello World"); 1265 | } 1266 | 1267 | function inputEvent(inputId, event) { 1268 | var input = document.getElementById(inputId); 1269 | input.addEventListener(event, function(e) { 1270 | setValues(); 1271 | if (!settings.draw) { 1272 | drawCircles(); 1273 | } 1274 | }); 1275 | input.addEventListener('keydown',function(e){ 1276 | if(e.code==='Minus'){ 1277 | e.preventDefault(); 1278 | } 1279 | }); 1280 | input.addEventListener('paste',function(e){ 1281 | e.preventDefault(); 1282 | }); 1283 | } 1284 | 1285 | function presetEvent(inputId, event) { 1286 | var input = document.getElementById(inputId); 1287 | input.addEventListener(event, function(event) { 1288 | var val = this.value; 1289 | var c; 1290 | var i; 1291 | for (c in settings.presets) { 1292 | if (settings.presets[c].name === val) { 1293 | loadValues(settings.presets[c]); 1294 | } 1295 | }; 1296 | document.getElementById(settings.idNames.stator).focus(); 1297 | var rotorsDiv = document.getElementById('rotors'); 1298 | if(rotorsDiv){ 1299 | rotorsDiv.style.height = (settings.numRotors * 27) + (settings.numRotors * 4.3 ) + 'px'; 1300 | } 1301 | }); 1302 | } 1303 | 1304 | function clearCanvas() { 1305 | //if we're zoomed out then clear the zoom to clear bigger drawings 1306 | if (settings.zoomStack[0] < 1) { 1307 | zoomTempClear() 1308 | var z = true; 1309 | } 1310 | 1311 | //clear 1312 | var ctx = settings.canvasPen.getContext("2d"); 1313 | ctx.clearRect(0, 0, settings.canvasPen.width, settings.canvasPen.height); 1314 | 1315 | //restore Zoom 1316 | if (z) { 1317 | zoomTempRestore() 1318 | } 1319 | settings.drawing = false; 1320 | } 1321 | 1322 | function restart() { 1323 | settings.i = 0; 1324 | setValues(); 1325 | drawCircles(); 1326 | } 1327 | 1328 | function reset() { 1329 | if (settings.iPosition !== 0) { 1330 | //set rotation position to 0 1331 | var temp = settings.iOffset; 1332 | settings.iOffset = settings.iPosition; 1333 | rotateDrawing(); 1334 | settings.iOffset = temp; 1335 | settings.iPosition = 0; 1336 | } 1337 | //reset zoom 1338 | settings.zoomStack = [1]; 1339 | settings.currentZoom = 1; 1340 | drawCircles(); 1341 | } 1342 | 1343 | function rotateDrawing() { 1344 | 1345 | var degrees = settings.iOffset * -1; 1346 | settings.iPosition += degrees; 1347 | 1348 | var ctx = settings.canvasCircles.getContext("2d"); 1349 | var ctxPen = settings.canvasPen.getContext("2d"); 1350 | 1351 | //angle from center to upper left, we're going to translate this point 1352 | var ang = ((Math.PI / 180) * 180) - Math.atan((settings.canvasPen.clientHeight / 2) / (settings.canvasPen.clientWidth / 2)); 1353 | 1354 | var ang = ang - (degrees * (Math.PI / 180)); 1355 | 1356 | var hyp = Math.sqrt(Math.pow(settings.canvasPen.clientHeight / 2, 2) + Math.pow(settings.canvasPen.clientWidth / 2, 2)); 1357 | 1358 | var pt = circlePoint(settings.a, settings.b, hyp, ang / (Math.PI / 180)); 1359 | 1360 | //rotate pen Canvas 1361 | //ctxPen.save(); 1362 | ctxPen.translate(pt.x, pt.y); 1363 | ctxPen.rotate(degrees * (Math.PI / 180)); 1364 | 1365 | ctx.translate(pt.x, pt.y); 1366 | ctx.rotate(degrees * (Math.PI / 180)); 1367 | 1368 | //draw image from circles and restore 1369 | //ctxPen.drawImage(settings.canvasCircles,0, 0); 1370 | //ctxPen.restore(); 1371 | 1372 | drawCircles(); 1373 | 1374 | } 1375 | 1376 | function zoom(inOut) { 1377 | 1378 | if(settings.zoomStack.length>1 && inOut==="in" && settings.zoomStack[0]<1) { 1379 | //we're changing directions from going out to in 1380 | var removed = settings.zoomStack.shift(); 1381 | } 1382 | else if(settings.zoomStack.length>1 && inOut==="out" && settings.zoomStack[0]>1) { 1383 | //we're changing directions from going in to out 1384 | var removed = settings.zoomStack.shift(); 1385 | } 1386 | else if (inOut === "in"){ 1387 | //same direction or first move 1388 | settings.zoomStack.unshift(1 + settings.zoom); 1389 | 1390 | } 1391 | else if (inOut === "out"){ 1392 | //same direction or first move 1393 | settings.zoomStack.unshift( 1 - settings.zoom); 1394 | } 1395 | else{ 1396 | return; 1397 | } 1398 | 1399 | settings.currentZoom = Math.pow(settings.zoomStack[0],settings.zoomStack.length-1); 1400 | 1401 | drawCircles(); 1402 | 1403 | 1404 | /* 1405 | 1406 | 1407 | 1408 | if (inOut === "in") { 1409 | settings.currentZoom = 1 + settings.zoom; 1410 | } else if (inOut === "out") { 1411 | settings.currentZoom = 1 - settings.zoom; 1412 | } 1413 | 1414 | var restore; 1415 | var ztu; 1416 | 1417 | //update stack if moving forward 1418 | if (settings.zoomStack[0] < 1 && settings.currentZoom > 1 || settings.zoomStack[0] > 1 && settings.currentZoom < 1) { 1419 | //we're changing directions so restore to last zoom 1420 | restore = settings.zoomStack.shift(); 1421 | } else { 1422 | settings.zoomStack.unshift(settings.currentZoom); 1423 | } 1424 | 1425 | if (restore) { 1426 | ztu = 1 / restore; 1427 | } else { 1428 | ztu = settings.currentZoom; 1429 | } 1430 | 1431 | */ 1432 | 1433 | //clear circles canvas 1434 | //var ctx = settings.canvasCircles.getContext("2d"); 1435 | //ctx.clearRect(0, 0, settings.canvasCircles.width, settings.canvasCircles.height); 1436 | 1437 | 1438 | //performZoom(ztu); 1439 | 1440 | } 1441 | 1442 | 1443 | })( 1444 | //settings object 1445 | { 1446 | "draw": false, 1447 | "i": 0, 1448 | "iOffset": 90, 1449 | "iPosition": 0, 1450 | "iterator": .25, 1451 | "zoom": .25, 1452 | "zoomStack": [1], 1453 | "currentZoom": 1, 1454 | "curvePoints": [], 1455 | "url": "#", 1456 | "circles": "show", 1457 | "circleColor": "LightGrey", 1458 | "circleStroke": 3, 1459 | "circleReset": false, 1460 | "offsetT": 0, 1461 | "offsetL": 0, 1462 | "penStart": { 1463 | "x": 0, 1464 | "y": 0 1465 | }, 1466 | "drawing": false, 1467 | "idNames": { 1468 | "linkId": "linkId", 1469 | "sidebar": "sidebar", 1470 | "pad": "pad", 1471 | "stator": "stator", 1472 | "rotors": "rotors", 1473 | "item": "item", 1474 | "rotor": "rotor", 1475 | "e": "e", 1476 | "h": "h", 1477 | "pen": "pen", 1478 | "width": "width", 1479 | "speed": "speed", 1480 | "color": "color", 1481 | "colors": "colors", 1482 | "type": "type", 1483 | "delete": "delete", 1484 | "deleteLabel": "deleteLabel", 1485 | "add": "add", 1486 | "addLabel": "addLabel", 1487 | "draw": "draw", 1488 | "clear": "clear", 1489 | "clearLabel": "clearLabel", 1490 | "clearIcon": "clearIcon", 1491 | "reset": "reset", 1492 | "resetLabel": "resetLabel", 1493 | "resetIcon": "resetIcon", 1494 | "restart": "restart", 1495 | "restartLabel": "restartLabel", 1496 | "restartIcon": "restartIcon", 1497 | "hide": "hide", 1498 | "hideLabel": "hideLabel", 1499 | "hideIcon": "hideIcon", 1500 | "preset": "preset", 1501 | "canvasCircles": "canvasCircles", 1502 | "canvasPen": "canvasPen", 1503 | "deleteLine": "deleteLine", 1504 | "deleteSep": "deleteSep", 1505 | "deleteBlock": "deleteBlock", 1506 | "download": "download", 1507 | "presets": "presets", 1508 | "rotate": "rotate", 1509 | "rotateLabel": "rotateLabel", 1510 | "rotateIcon": "rotateIcon", 1511 | "open": "open", 1512 | "openLabel": "openLabel", 1513 | "openIcon": "openIcon", 1514 | "git": "git", 1515 | "gitLabel": "gitLabel", 1516 | "gitIcon": "gitIcon", 1517 | "link": "link", 1518 | "linkLabel": "linkLabel", 1519 | "linkIcon": "linkIcon", 1520 | "zoomIn": "zoomIn", 1521 | "zoomInLabel": "zoomInLabel", 1522 | "zoomInIcon": "zoomInIcon", 1523 | "zoomOut": "zoomOut", 1524 | "zoomOutLabel": "zoomOutnLabel", 1525 | "zoomOutIcon": "zoomOutnIcon", 1526 | "angle": "angle", 1527 | }, 1528 | "presets": [ 1529 | 1530 | { 1531 | "name": "liner", 1532 | "st": "300", 1533 | "r1": "150h", 1534 | "pen": "150", 1535 | "wd": "1", 1536 | "cl": "#008000", 1537 | "sp": "1", 1538 | }, 1539 | 1540 | { 1541 | "name": "manta", 1542 | "st": "300", 1543 | "r1": "150h", 1544 | "r2": "75h", 1545 | "r3": "37.5e", 1546 | "r4": "18.75e", 1547 | "pen": "18.75", 1548 | "wd": "1", 1549 | "cl": "#2E8B57", 1550 | "sp": "1", 1551 | }, 1552 | 1553 | { 1554 | "name": "classic plus", 1555 | "st": "77", 1556 | "r1": "11e", 1557 | "r2": "72e", 1558 | "pen": "72", 1559 | "wd": ".1", 1560 | "cl": "#2E8B57", 1561 | "sp": "1", 1562 | }, 1563 | 1564 | { 1565 | "name": "lily pad", 1566 | "st": "250", 1567 | "r1": "25h", 1568 | "r2": "66e", 1569 | "r3": "33e", 1570 | "pen": "33", 1571 | "wd": ".2", 1572 | "cl": "#8FBC8F", 1573 | "sp": "1", 1574 | }, 1575 | 1576 | { 1577 | "name": "kaleidoscope", 1578 | "st": "250", 1579 | "r1": "12.5h", 1580 | "r2": "72e", 1581 | "pen": "36", 1582 | "wd": ".05", 1583 | "cl": "#4B0082", 1584 | "sp": "1", 1585 | }, 1586 | 1587 | { 1588 | "name": "tubular", 1589 | "st": "120", 1590 | "r1": "60e", 1591 | "r2": "120h", 1592 | "r3": "22h", 1593 | "pen": "5.5", 1594 | "wd": ".1", 1595 | "cl": "#006400", 1596 | "sp": "1", 1597 | }, 1598 | 1599 | { 1600 | "name": "drop", 1601 | "st": "100", 1602 | "r1": "99e", 1603 | "r2": "66e", 1604 | "r3": "33e", 1605 | "pen": "33", 1606 | "wd": ".2", 1607 | "cl": "#d21d00", 1608 | "sp": "200", 1609 | }, 1610 | 1611 | { 1612 | "name": "habitrail", 1613 | "st": "300", 1614 | "r1": "150h", 1615 | "r2": "125h", 1616 | "r3": "54h", 1617 | "pen": "13.5", 1618 | "wd": ".08", 1619 | "cl": "#000000", 1620 | "sp": "1", 1621 | }, 1622 | 1623 | { 1624 | "name": "pods", 1625 | "st": "250", 1626 | "r1": "66h", 1627 | "r2": "11e", 1628 | "pen": "66", 1629 | "wd": ".2", 1630 | "cl": "#000080", 1631 | "sp": "1", 1632 | }, 1633 | 1634 | { 1635 | "name": "arclight", 1636 | "st": "125", 1637 | "r1": "132e", 1638 | "r2": "66h", 1639 | "r3": "3e", 1640 | "pen": "24", 1641 | "wd": ".1", 1642 | "cl": "#a0631c", 1643 | "sp": "1", 1644 | }, 1645 | 1646 | { 1647 | "name": "the bloom", 1648 | "st": "100", 1649 | "r1": "36e", 1650 | "r2": "100e", 1651 | "pen": "100", 1652 | "wd": ".1", 1653 | "cl": "#000080", 1654 | "sp": "1", 1655 | }, 1656 | 1657 | { 1658 | "name": "emerald", 1659 | "st": "150", 1660 | "r1": "4e", 1661 | "r2": "75e", 1662 | "r3": "25h", 1663 | "pen": "75", 1664 | "wd": ".04", 1665 | "cl": "#007071", 1666 | "sp": "1000", 1667 | }, 1668 | 1669 | { 1670 | "name": "points", 1671 | "st": "75", 1672 | "r1": "205h", 1673 | "r2": "152e", 1674 | "r3": "76h", 1675 | "pen": "76", 1676 | "wd": ".02", 1677 | "cl": "#0000ff", 1678 | "sp": "10", 1679 | }, 1680 | 1681 | { 1682 | "name": "the touch", 1683 | "st": "6.25", 1684 | "r1": "198e", 1685 | "r2": "99h", 1686 | "r3": "9e", 1687 | "pen": "9", 1688 | "wd": ".03", 1689 | "cl": "#800080", 1690 | "sp": "1000", 1691 | }, 1692 | 1693 | { 1694 | "name": "red cloud", 1695 | "st": "332", 1696 | "r1": "207.5h", 1697 | "r2": "20.75h", 1698 | "r3": "77h", 1699 | "pen": "77", 1700 | "wd": ".02", 1701 | "cl": "#ed0707", 1702 | "sp": "1000", 1703 | }, 1704 | 1705 | 1706 | { 1707 | "name": "wormhole", 1708 | "st": "25", 1709 | "r1": "198h", 1710 | "r2": "66h", 1711 | "r3": "5.5e", 1712 | "pen": "5.5", 1713 | "wd": ".2", 1714 | "cl": "#942193", 1715 | "sp": "1", 1716 | }, 1717 | 1718 | { 1719 | "name": "sails", 1720 | "st": "250", 1721 | "r1": "25h", 1722 | "r2": "66h", 1723 | "r3": "5.5h", 1724 | "pen": "66", 1725 | "wd": ".1", 1726 | "cl": "#ff2600", 1727 | "sp": "1", 1728 | }, 1729 | 1730 | { 1731 | "name": "ouroboros", 1732 | "st": "332", 1733 | "r1": "166h", 1734 | "r2": "20.75h", 1735 | "r3": "77h", 1736 | "pen": "77", 1737 | "wd": ".1", 1738 | "cl": "#008000", 1739 | "sp": "1", 1740 | }, 1741 | 1742 | { 1743 | "name": "true love", 1744 | "st": "250", 1745 | "r1": "99h", 1746 | "r2": "66e", 1747 | "r3": "22h", 1748 | "pen": "33", 1749 | "wd": ".2", 1750 | "cl": "#8B0000", 1751 | "sp": "1", 1752 | }, 1753 | 1754 | { 1755 | "name": "the bug", 1756 | "st": "150", 1757 | "r1": "90e", 1758 | "r2": "45e", 1759 | "r3": "22.5e", 1760 | "r4": "17e", 1761 | "r5": "22.5e", 1762 | "pen": "22.5", 1763 | "wd": ".02", 1764 | "cl": "#945200", 1765 | "sp": "1000", 1766 | }, 1767 | 1768 | { 1769 | "name": "ribbons", 1770 | "st": "332", 1771 | "r1": "207.5h", 1772 | "r2": "20.75h", 1773 | "r3": "38.5e", 1774 | "r4": "19.25h", 1775 | "pen": "19.25", 1776 | "wd": ".04", 1777 | "cl": "#0000ff", 1778 | "sp": "1000", 1779 | }, 1780 | 1781 | { 1782 | "name": "burgess shale", 1783 | "st": "250", 1784 | "r1": "99h", 1785 | "r2": "75h", 1786 | "r3": "50e", 1787 | "r4": "25e", 1788 | "pen": "25", 1789 | "wd": ".01", 1790 | "cl": "#6A5ACD", 1791 | "sp": "1000", 1792 | }, 1793 | 1794 | { 1795 | "name": "amethyst", 1796 | "st": "350", 1797 | "r1": "187h", 1798 | "r2": "66h", 1799 | "r3": "33e", 1800 | "r4": "11h", 1801 | "pen": "5.5", 1802 | "wd": ".05", 1803 | "cl": "#4b0082", 1804 | "sp": "1000", 1805 | }, 1806 | 1807 | { 1808 | "name": "magnets", 1809 | "st": "9", 1810 | "r1": "250e", 1811 | "r2": "180h", 1812 | "r3": "90e", 1813 | "pen": "90", 1814 | "wd": ".05", 1815 | "cl": "#7f7f7f", 1816 | "sp": "1000", 1817 | }, 1818 | 1819 | { 1820 | "name": "sand dollar", 1821 | "st": "250", 1822 | "r1": "25h", 1823 | "r2": "67e", 1824 | "r3": "33e", 1825 | "pen": "33", 1826 | "wd": ".03", 1827 | "cl": "#289528", 1828 | "sp": "1000", 1829 | }, 1830 | 1831 | { 1832 | "name": "super star", 1833 | "st": "250", 1834 | "r1": "90h", 1835 | "r2": "45e", 1836 | "r3": "45e", 1837 | "r4": "22.5h", 1838 | "r5": "17h", 1839 | "pen": "17", 1840 | "wd": ".03", 1841 | "cl": "#011993", 1842 | "sp": "1000", 1843 | }, 1844 | 1845 | { 1846 | "name": "fader", 1847 | "st": "300", 1848 | "r1": "90h", 1849 | "r2": "45e", 1850 | "r3": "30e", 1851 | "r4": "22.5e", 1852 | "r5": "19.3e", 1853 | "pen": "19.3", 1854 | "wd": ".01", 1855 | "cl": "#cc1470", 1856 | "sp": "1000", 1857 | }, 1858 | 1859 | { 1860 | "name": "prince", 1861 | "st": "12.5", 1862 | "r1": "198e", 1863 | "r2": "66h", 1864 | "r3": "11h", 1865 | "r4": "5.5e", 1866 | "pen": "33", 1867 | "wd": ".1", 1868 | "cl": "#942193", 1869 | "sp": "1", 1870 | }, 1871 | 1872 | { 1873 | "name": "art deco", 1874 | "st": "75", 1875 | "r1": "25e", 1876 | "r2": "50e", 1877 | "r3": "100e", 1878 | "r4": "199h", 1879 | "r5": "99.5h", 1880 | "pen": "99.5", 1881 | "wd": ".05", 1882 | "cl": "#033333", 1883 | "sp": "1", 1884 | }, 1885 | 1886 | { 1887 | "name": "mandala", 1888 | "st": "75", 1889 | "r1": "160e", 1890 | "r2": "3h", 1891 | "pen": "48", 1892 | "wd": ".1", 1893 | "cl": "#007071", 1894 | "sp": "1", 1895 | }, 1896 | 1897 | { 1898 | "name": "pufferfish", 1899 | "st": "160", 1900 | "r1": "16h", 1901 | "r2": "77h", 1902 | "r3": "33h", 1903 | "r4": "16.5e", 1904 | "r5": "8.25e", 1905 | "pen": "77", 1906 | "wd": ".1", 1907 | "cl": "#0000FF", 1908 | "sp": "1", 1909 | }, 1910 | 1911 | { 1912 | "name": "sauron", 1913 | "st": "300", 1914 | "r1": "98h", 1915 | "r2": "37.5h", 1916 | "r3": "37.5e", 1917 | "r4": "18.75e", 1918 | "pen": "18.75", 1919 | "wd": ".02", 1920 | "cl": "#8a2e2e", 1921 | "sp": "1000", 1922 | }, 1923 | 1924 | { 1925 | "name": "reverb", 1926 | "st": "9", 1927 | "r1": "250h", 1928 | "r2": "180h", 1929 | "r3": "15h", 1930 | "pen": "15", 1931 | "wd": ".07", 1932 | "cl": "#2a9bb2", 1933 | "sp": "1000", 1934 | }, 1935 | 1936 | { 1937 | "name": "bentley", 1938 | "st": "9", 1939 | "r1": "250e", 1940 | "r2": "180h", 1941 | "r3": "15h", 1942 | "pen": "15", 1943 | "wd": ".06", 1944 | "cl": "#004080", 1945 | "sp": "1000", 1946 | }, 1947 | 1948 | 1949 | ], 1950 | "penColors": [{ 1951 | "name": "black", 1952 | "hex": "#000000", 1953 | }, { 1954 | "name": "blue", 1955 | "hex": "#0000FF", 1956 | }, { 1957 | "name": "cornflowerblue", 1958 | "hex": "#6495ED", 1959 | }, { 1960 | "name": "crimson", 1961 | "hex": "#DC143C", 1962 | }, { 1963 | "name": "darkgreen", 1964 | "hex": "#006400", 1965 | }, { 1966 | "name": "darkred", 1967 | "hex": "#8B0000", 1968 | }, { 1969 | "name": "darkseagreen", 1970 | "hex": "#8FBC8F", 1971 | }, { 1972 | "name": "gold", 1973 | "hex": "#FFD700", 1974 | }, { 1975 | "name": "green", 1976 | "hex": "#008000", 1977 | }, { 1978 | "name": "indigo", 1979 | "hex": "#4B0082", 1980 | }, { 1981 | "name": "magenta", 1982 | "hex": "#FF00FF", 1983 | }, { 1984 | "name": "navy", 1985 | "hex": "#000080", 1986 | }, { 1987 | "name": "orange", 1988 | "hex": "#FFA500", 1989 | }, { 1990 | "name": "powderblue", 1991 | "hex": "#B0E0E6", 1992 | }, { 1993 | "name": "purple", 1994 | "hex": "#800080", 1995 | }, { 1996 | "name": "red", 1997 | "hex": "#FF0000", 1998 | }, { 1999 | "name": "seagreen", 2000 | "hex": "#2E8B57", 2001 | }, { 2002 | "name": "slateblue", 2003 | "hex": "#6A5ACD", 2004 | }, { 2005 | "name": "steelblue", 2006 | "hex": "#6A5ACD", 2007 | }, { 2008 | "name": "violet", 2009 | "hex": "#EE82EE", 2010 | }, { 2011 | "name": "yellow", 2012 | "hex": "#FFFF00", 2013 | }], 2014 | "speedSettings": [{ 2015 | "name": "slowest", 2016 | "speed": .01, 2017 | }, { 2018 | "name": "slower", 2019 | "speed": .1, 2020 | }, { 2021 | "name": "slow", 2022 | "speed": 1, 2023 | }, { 2024 | "name": "fast", 2025 | "speed": 10, 2026 | }, { 2027 | "name": "faster", 2028 | "speed": 200, 2029 | }, { 2030 | "name": "fastest", 2031 | "speed": 1000, 2032 | }, ] 2033 | } 2034 | ); 2035 | --------------------------------------------------------------------------------