├── .nvmrc ├── .gitignore ├── example.png ├── docs ├── demo │ ├── Demo.svelte.css │ ├── index.js │ ├── Draggable.svelte.css │ ├── Control.svelte.css │ ├── Demo.svelte.css.proxy.js │ ├── Draggable.svelte.css.proxy.js │ ├── is-mobile-device.js │ ├── Control.svelte.css.proxy.js │ ├── generate-polygon.js │ ├── style.css │ ├── Draggable.svelte.js │ ├── Control.svelte.js │ └── Demo.svelte.js ├── example.png ├── snowpack │ └── pkg │ │ ├── svelte.js │ │ ├── import-map.json │ │ ├── svelte │ │ └── internal.js │ │ └── common │ │ └── index-fbcaf5b0.js ├── snowpack.config.js ├── package.json ├── index.html └── src │ └── offset-polygon.js ├── demo ├── index.js ├── is-mobile-device.js ├── generate-polygon.js ├── Control.svelte ├── Draggable.svelte ├── style.css └── Demo.svelte ├── lib ├── offset-polygon.d.ts ├── offset-polygon.js └── offset-polygon.js.map ├── .vscode └── settings.json ├── snowpack.config.mjs ├── LICENSE.md ├── package.json ├── README.md ├── index.html └── src └── offset-polygon.ts /.nvmrc: -------------------------------------------------------------------------------- 1 | 14.15.4 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stanko/offset-polygon/HEAD/example.png -------------------------------------------------------------------------------- /docs/demo/Demo.svelte.css: -------------------------------------------------------------------------------- 1 | .note.svelte-zlj7hl{color:#777;text-align:center;font-size:14px} -------------------------------------------------------------------------------- /docs/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stanko/offset-polygon/HEAD/docs/example.png -------------------------------------------------------------------------------- /docs/snowpack/pkg/svelte.js: -------------------------------------------------------------------------------- 1 | export { o as onMount } from './common/index-fbcaf5b0.js'; 2 | -------------------------------------------------------------------------------- /demo/index.js: -------------------------------------------------------------------------------- 1 | import Demo from './Demo.svelte'; 2 | 3 | new Demo({ 4 | target: document.querySelector('.wrapper'), 5 | }); 6 | -------------------------------------------------------------------------------- /docs/demo/index.js: -------------------------------------------------------------------------------- 1 | import Demo from './Demo.svelte.js'; 2 | 3 | new Demo({ 4 | target: document.querySelector('.wrapper'), 5 | }); 6 | -------------------------------------------------------------------------------- /docs/snowpack/pkg/import-map.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "svelte": "./svelte.js", 4 | "svelte/internal": "./svelte/internal.js" 5 | } 6 | } -------------------------------------------------------------------------------- /docs/demo/Draggable.svelte.css: -------------------------------------------------------------------------------- 1 | .handle.svelte-x5qldl{user-select:none;cursor:move;background:black;opacity:0;transition:opacity 200ms;border-radius:100px;position:absolute;z-index:10}.handle.svelte-x5qldl:hover{opacity:0.1} -------------------------------------------------------------------------------- /lib/offset-polygon.d.ts: -------------------------------------------------------------------------------- 1 | declare type Vector = { 2 | x: number; 3 | y: number; 4 | }; 5 | export default function offsetPolygon(vertices: Vector[], offset: number, arcSegments?: number): Vector[]; 6 | export {}; 7 | -------------------------------------------------------------------------------- /docs/demo/Control.svelte.css: -------------------------------------------------------------------------------- 1 | .control.svelte-q28g67{font-size:14px;display:flex;flex-wrap:wrap;margin-bottom:5px}.control-label.svelte-q28g67{min-width:180px;margin-right:10px}.control-input.svelte-q28g67{margin-right:10px}.control-right.svelte-q28g67{white-space:nowrap;display:flex}.control-value.svelte-q28g67{min-width:30px} -------------------------------------------------------------------------------- /docs/demo/Demo.svelte.css.proxy.js: -------------------------------------------------------------------------------- 1 | // [snowpack] add styles to the page (skip if no document exists) 2 | if (typeof document !== 'undefined') { 3 | const code = ".note.svelte-zlj7hl{color:#777;text-align:center;font-size:14px}"; 4 | 5 | const styleEl = document.createElement("style"); 6 | const codeEl = document.createTextNode(code); 7 | styleEl.type = 'text/css'; 8 | styleEl.appendChild(codeEl); 9 | document.head.appendChild(styleEl); 10 | } -------------------------------------------------------------------------------- /docs/snowpack.config.js: -------------------------------------------------------------------------------- 1 | const root = process.cwd(); 2 | export default { 3 | exclude: [ 4 | `${root}/node_modules/**/*`, 5 | `${root}/*.md`, 6 | `${root}/.vscode/**/*`, 7 | `${root}/lib/**/*`, 8 | `${root}/.gitignore`, 9 | `${root}/.nvmrc` 10 | ], 11 | mount: {}, 12 | plugins: ["@snowpack/plugin-svelte"], 13 | routes: [], 14 | optimize: {}, 15 | packageOptions: {}, 16 | devOptions: {}, 17 | buildOptions: { 18 | out: "docs", 19 | baseUrl: "/offset-polygon", 20 | metaUrlPath: "snowpack" 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /demo/is-mobile-device.js: -------------------------------------------------------------------------------- 1 | function iOS() { 2 | return ( 3 | [ 4 | 'iPad Simulator', 5 | 'iPhone Simulator', 6 | 'iPod Simulator', 7 | 'iPad', 8 | 'iPhone', 9 | 'iPod', 10 | 'Android', 11 | ].includes(navigator.platform) || 12 | // iPad on iOS 13 detection 13 | (navigator.userAgent.includes('Mac') && 'ontouchend' in document) 14 | ); 15 | } 16 | 17 | const isAndroid = navigator.userAgent.toLowerCase().indexOf('android'); 18 | 19 | const isMobileDevice = isAndroid && iOS(); 20 | 21 | export default isMobileDevice; 22 | -------------------------------------------------------------------------------- /docs/demo/Draggable.svelte.css.proxy.js: -------------------------------------------------------------------------------- 1 | // [snowpack] add styles to the page (skip if no document exists) 2 | if (typeof document !== 'undefined') { 3 | const code = ".handle.svelte-x5qldl{user-select:none;cursor:move;background:black;opacity:0;transition:opacity 200ms;border-radius:100px;position:absolute;z-index:10}.handle.svelte-x5qldl:hover{opacity:0.1}"; 4 | 5 | const styleEl = document.createElement("style"); 6 | const codeEl = document.createTextNode(code); 7 | styleEl.type = 'text/css'; 8 | styleEl.appendChild(codeEl); 9 | document.head.appendChild(styleEl); 10 | } -------------------------------------------------------------------------------- /docs/demo/is-mobile-device.js: -------------------------------------------------------------------------------- 1 | function iOS() { 2 | return ( 3 | [ 4 | 'iPad Simulator', 5 | 'iPhone Simulator', 6 | 'iPod Simulator', 7 | 'iPad', 8 | 'iPhone', 9 | 'iPod', 10 | 'Android', 11 | ].includes(navigator.platform) || 12 | // iPad on iOS 13 detection 13 | (navigator.userAgent.includes('Mac') && 'ontouchend' in document) 14 | ); 15 | } 16 | 17 | const isAndroid = navigator.userAgent.toLowerCase().indexOf('android'); 18 | 19 | const isMobileDevice = isAndroid && iOS(); 20 | 21 | export default isMobileDevice; 22 | -------------------------------------------------------------------------------- /docs/snowpack/pkg/svelte/internal.js: -------------------------------------------------------------------------------- 1 | export { S as SvelteComponent, a as append, b as attr, j as binding_callbacks, k as check_outros, m as create_component, B as current_component, p as destroy_component, q as destroy_each, d as detach, e as element, r as empty, A as get_current_component, u as group_outros, i as init, c as insert, f as is_function, l as listen, v as mount_component, n as noop, z as run_all, s as safe_not_equal, C as set_current_component, g as set_data, h as space, w as svg_element, t as text, x as transition_in, y as transition_out } from '../common/index-fbcaf5b0.js'; 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "python.languageServer": "Pylance", 4 | "python.linting.pylintEnabled": false, 5 | "python.analysis.diagnosticSeverityOverrides": { 6 | "reportMissingModuleSource": "none" 7 | }, 8 | "python.analysis.extraPaths": [ 9 | null, 10 | "/Users/stanko/.vscode/extensions/joedevivo.vscode-circuitpython-0.1.13/stubs", 11 | "/Users/stanko/Library/Application Support/Code/User/globalStorage/joedevivo.vscode-circuitpython/bundle/20210606/adafruit-circuitpython-bundle-py-20210606/lib" 12 | ], 13 | "circuitpython.board.version": null 14 | } -------------------------------------------------------------------------------- /docs/demo/Control.svelte.css.proxy.js: -------------------------------------------------------------------------------- 1 | // [snowpack] add styles to the page (skip if no document exists) 2 | if (typeof document !== 'undefined') { 3 | const code = ".control.svelte-q28g67{font-size:14px;display:flex;flex-wrap:wrap;margin-bottom:5px}.control-label.svelte-q28g67{min-width:180px;margin-right:10px}.control-input.svelte-q28g67{margin-right:10px}.control-right.svelte-q28g67{white-space:nowrap;display:flex}.control-value.svelte-q28g67{min-width:30px}"; 4 | 5 | const styleEl = document.createElement("style"); 6 | const codeEl = document.createTextNode(code); 7 | styleEl.type = 'text/css'; 8 | styleEl.appendChild(codeEl); 9 | document.head.appendChild(styleEl); 10 | } -------------------------------------------------------------------------------- /snowpack.config.mjs: -------------------------------------------------------------------------------- 1 | const root = process.cwd(); 2 | 3 | /** @type {import("snowpack").SnowpackUserConfig } */ 4 | export default { 5 | exclude: [ 6 | `${root}/node_modules/**/*`, 7 | `${root}/*.md`, 8 | `${root}/.vscode/**/*`, 9 | `${root}/lib/**/*`, 10 | `${root}/.gitignore`, 11 | `${root}/.nvmrc`, 12 | ], 13 | mount: { 14 | /* ... */ 15 | }, 16 | plugins: ['@snowpack/plugin-svelte'], 17 | routes: [ 18 | /* Enable an SPA Fallback in development: */ 19 | // {"match": "routes", "src": ".*", "dest": "/index.html"}, 20 | ], 21 | optimize: { 22 | /* Example: Bundle your final build: */ 23 | // "bundle": true, 24 | }, 25 | packageOptions: { 26 | /* ... */ 27 | }, 28 | devOptions: { 29 | /* ... */ 30 | }, 31 | buildOptions: { 32 | out: 'docs', 33 | baseUrl: '/offset-polygon', 34 | metaUrlPath: 'snowpack', 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /demo/generate-polygon.js: -------------------------------------------------------------------------------- 1 | export default function generatePolygon( 2 | maxPolygonPoints = 5, 3 | r = 50, 4 | center = { x: 50, y: 50 } 5 | ) { 6 | const startAngle = Math.random() * Math.PI * 2; 7 | 8 | let angleLeft = Math.PI * 2; 9 | let totalAngle = startAngle; 10 | 11 | const angles = [startAngle]; 12 | 13 | for (let i = maxPolygonPoints; i > 1; i--) { 14 | const averageAngle = angleLeft / i; 15 | const angle = averageAngle * 0.4 + 1.1 * Math.random() * averageAngle; 16 | 17 | angleLeft -= angle; 18 | totalAngle += angle; 19 | 20 | angles.push(totalAngle); 21 | } 22 | 23 | return angles.map((angle, index) => { 24 | // Create a single vertex closer to the center 25 | const radius = index === Math.floor(maxPolygonPoints * 0.5) ? r * 0.2 : r; 26 | 27 | return { 28 | x: Math.cos(angle) * radius + center.x, 29 | y: Math.sin(angle) * radius + center.y, 30 | }; 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /docs/demo/generate-polygon.js: -------------------------------------------------------------------------------- 1 | export default function generatePolygon( 2 | maxPolygonPoints = 5, 3 | r = 50, 4 | center = { x: 50, y: 50 } 5 | ) { 6 | const startAngle = Math.random() * Math.PI * 2; 7 | 8 | let angleLeft = Math.PI * 2; 9 | let totalAngle = startAngle; 10 | 11 | const angles = [startAngle]; 12 | 13 | for (let i = maxPolygonPoints; i > 1; i--) { 14 | const averageAngle = angleLeft / i; 15 | const angle = averageAngle * 0.4 + 1.1 * Math.random() * averageAngle; 16 | 17 | angleLeft -= angle; 18 | totalAngle += angle; 19 | 20 | angles.push(totalAngle); 21 | } 22 | 23 | return angles.map((angle, index) => { 24 | // Create a single vertex closer to the center 25 | const radius = index === Math.floor(maxPolygonPoints * 0.5) ? r * 0.2 : r; 26 | 27 | return { 28 | x: Math.cos(angle) * radius + center.x, 29 | y: Math.sin(angle) * radius + center.y, 30 | }; 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /demo/Control.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 15 |
16 | 26 |
{value}
27 |
28 |
29 | 30 | 56 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Stanko Tadić 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "offset-polygon", 3 | "version": "0.9.2", 4 | "description": "", 5 | "main": "lib/offset-polygon.js", 6 | "module": "lib/offset-polygon.js", 7 | "types": "lib/offset-polygon.d.ts", 8 | "scripts": { 9 | "start": "snowpack dev", 10 | "build-demo": "rm -rf ./docs && snowpack build && git add ./docs/", 11 | "types": "tsc ./src/offset-polygon.ts --declaration --emitDeclarationOnly --outDir lib", 12 | "build": "esbuild --bundle --sourcemap --format=esm ./src/offset-polygon.ts --outfile=lib/offset-polygon.js", 13 | "build-all": "npm run types && npm run build", 14 | "prepublish": "npm run build-all" 15 | }, 16 | "keywords": [ 17 | "polygon", 18 | "offset", 19 | "padding", 20 | "margin" 21 | ], 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/Stanko/offset-polygon.git" 25 | }, 26 | "author": "Stanko", 27 | "license": "MIT", 28 | "devDependencies": { 29 | "@snowpack/plugin-svelte": "^3.7.0", 30 | "esbuild": "^0.13.8", 31 | "snowpack": "^3.8.8", 32 | "svelte": "^3.44.0", 33 | "typescript": "^4.4.4" 34 | }, 35 | "publishConfig": { 36 | "access": "public" 37 | } 38 | } -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "offset-polygon", 3 | "version": "0.9.2", 4 | "description": "", 5 | "main": "lib/offset-polygon.js", 6 | "module": "lib/offset-polygon.js", 7 | "types": "lib/offset-polygon.d.ts", 8 | "scripts": { 9 | "start": "snowpack dev", 10 | "build-demo": "rm -rf ./docs && snowpack build && git add ./docs/", 11 | "types": "tsc ./src/offset-polygon.ts --declaration --emitDeclarationOnly --outDir lib", 12 | "build": "esbuild --bundle --sourcemap --format=esm ./src/offset-polygon.ts --outfile=lib/offset-polygon.js", 13 | "build-all": "npm run types && npm run build", 14 | "prepublish": "npm run build-all" 15 | }, 16 | "keywords": [ 17 | "polygon", 18 | "offset", 19 | "padding", 20 | "margin" 21 | ], 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/Stanko/offset-polygon.git" 25 | }, 26 | "author": "Stanko", 27 | "license": "MIT", 28 | "devDependencies": { 29 | "@snowpack/plugin-svelte": "^3.7.0", 30 | "esbuild": "^0.13.8", 31 | "snowpack": "^3.8.8", 32 | "svelte": "^3.44.0", 33 | "typescript": "^4.4.4" 34 | }, 35 | "publishConfig": { 36 | "access": "public" 37 | } 38 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Offset Polygon 2 | 3 | Small, no dependency library for offsetting polygons. Heavily based on this [CodePen](https://codepen.io/HansMuller/pen/lDfzt) by Hans Muller. 4 | 5 | [Interactive demo](https://muffinman.io/offset-polygon/) 6 | 7 | [![Example polygons](example.png)](https://muffinman.io/offset-polygon/) 8 | 9 | ## Params 10 | 11 | ```ts 12 | function offsetPolygon(vertices: Vector[], offset: number, arcSegments?: number): Vector[] 13 | ``` 14 | 15 | * `vertices` - array of vector objects `{ x: number, y: number }` 16 | * `offset` - number, how much should the polygon be offset. Positive values will create add margin, and negative padding. 17 | * `arcSegments` - number, default `0`. When set, corners of the generated polygon will be rounded by an arc formed of straight line segments. 18 | 19 | Returns newly generated polygon vertices as an array of vector objects `{ x: number, y: number }`. 20 | 21 | ## Usage 22 | 23 | Get it from npm: 24 | 25 | ``` 26 | npm install offset-polygon 27 | ``` 28 | 29 | ```js 30 | import offsetPolygon from "offset-polygon"; 31 | 32 | const polygon = [ 33 | { "x": 413, "y": 123 }, 34 | { "x": 510, "y": 299 }, 35 | { "x": 395, "y": 487 }, 36 | { "x": 292, "y": 341 }, 37 | { "x": 92, "y": 327 }, 38 | { "x": 146, "y": 158 }, 39 | ]; 40 | 41 | // Padding 42 | const smallerPolygon = offsetPolygon(polygon, -10, 5); 43 | 44 | // Margin 45 | const largerPolygon = offsetPolygon(polygon, 10, 5); 46 | ``` -------------------------------------------------------------------------------- /demo/Draggable.svelte: -------------------------------------------------------------------------------- 1 | 42 | 43 | 49 | 50 | 58 | 59 | 75 | -------------------------------------------------------------------------------- /demo/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | html, 8 | body { 9 | width: 100%; 10 | overflow-x: hidden; 11 | } 12 | 13 | body { 14 | background: #f0f4f5; 15 | color: #333; 16 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 17 | 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'sans-serif'; 18 | } 19 | 20 | h1, 21 | h2 { 22 | font-weight: 400; 23 | color: #007aff; 24 | } 25 | 26 | h1 { 27 | font-size: 2rem; 28 | margin-bottom: 1.5rem; 29 | } 30 | 31 | h2 { 32 | font-size: 1.7rem; 33 | margin-bottom: 1rem; 34 | } 35 | 36 | p { 37 | margin: 0.8rem 0; 38 | } 39 | 40 | a { 41 | color: #333; 42 | text-decoration: none; 43 | border-bottom: 1px solid #e1e1e1; 44 | transition: color 250ms, border-color 250ms; 45 | } 46 | 47 | a:hover { 48 | color: #007aff; 49 | border-bottom-color: #007aff; 50 | } 51 | 52 | .links { 53 | border-bottom: 1px solid #e1e1e1; 54 | } 55 | 56 | .links-content { 57 | max-width: 600px; 58 | padding: 15px 20px; 59 | margin: 0 auto; 60 | } 61 | 62 | pre { 63 | overflow: auto; 64 | color: #445; 65 | background-color: rgba(0, 0, 0, 0.02); 66 | border: 1px solid #e1e1e1; 67 | padding: 10px; 68 | border-radius: 4px; 69 | margin: 0.8rem 0; 70 | } 71 | 72 | .c1 { 73 | color: #2980b9; 74 | } 75 | 76 | .c2 { 77 | color: #c0392b; 78 | } 79 | 80 | .c3 { 81 | color: #16a085; 82 | } 83 | 84 | .c4 { 85 | color: #888; 86 | } 87 | 88 | .wrapper { 89 | max-width: 600px; 90 | margin: 0 auto 100px; 91 | } 92 | 93 | .content { 94 | padding: 20px; 95 | } 96 | 97 | svg { 98 | width: 100%; 99 | background: white; 100 | box-shadow: 0 0 5px #e1e1e1; 101 | overflow: visible; 102 | } 103 | 104 | @media (pointer: none) { 105 | .handle { 106 | opacity: 0.1; 107 | } 108 | } 109 | 110 | @media (min-width: 800px) { 111 | .content, 112 | .links-content { 113 | padding: 20px 50px; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /docs/demo/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | html, 8 | body { 9 | width: 100%; 10 | overflow-x: hidden; 11 | } 12 | 13 | body { 14 | background: #f0f4f5; 15 | color: #333; 16 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 17 | 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'sans-serif'; 18 | } 19 | 20 | h1, 21 | h2 { 22 | font-weight: 400; 23 | color: #007aff; 24 | } 25 | 26 | h1 { 27 | font-size: 2rem; 28 | margin-bottom: 1.5rem; 29 | } 30 | 31 | h2 { 32 | font-size: 1.7rem; 33 | margin-bottom: 1rem; 34 | } 35 | 36 | p { 37 | margin: 0.8rem 0; 38 | } 39 | 40 | a { 41 | color: #333; 42 | text-decoration: none; 43 | border-bottom: 1px solid #e1e1e1; 44 | transition: color 250ms, border-color 250ms; 45 | } 46 | 47 | a:hover { 48 | color: #007aff; 49 | border-bottom-color: #007aff; 50 | } 51 | 52 | .links { 53 | border-bottom: 1px solid #e1e1e1; 54 | } 55 | 56 | .links-content { 57 | max-width: 600px; 58 | padding: 15px 20px; 59 | margin: 0 auto; 60 | } 61 | 62 | pre { 63 | overflow: auto; 64 | color: #445; 65 | background-color: rgba(0, 0, 0, 0.02); 66 | border: 1px solid #e1e1e1; 67 | padding: 10px; 68 | border-radius: 4px; 69 | margin: 0.8rem 0; 70 | } 71 | 72 | .c1 { 73 | color: #2980b9; 74 | } 75 | 76 | .c2 { 77 | color: #c0392b; 78 | } 79 | 80 | .c3 { 81 | color: #16a085; 82 | } 83 | 84 | .c4 { 85 | color: #888; 86 | } 87 | 88 | .wrapper { 89 | max-width: 600px; 90 | margin: 0 auto 100px; 91 | } 92 | 93 | .content { 94 | padding: 20px; 95 | } 96 | 97 | svg { 98 | width: 100%; 99 | background: white; 100 | box-shadow: 0 0 5px #e1e1e1; 101 | overflow: visible; 102 | } 103 | 104 | @media (pointer: none) { 105 | .handle { 106 | opacity: 0.1; 107 | } 108 | } 109 | 110 | @media (min-width: 800px) { 111 | .content, 112 | .links-content { 113 | padding: 20px 50px; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /docs/demo/Draggable.svelte.js: -------------------------------------------------------------------------------- 1 | import './Draggable.svelte.css.proxy.js'; 2 | /* demo/Draggable.svelte generated by Svelte v3.44.0 */ 3 | import { 4 | SvelteComponent, 5 | attr, 6 | detach, 7 | init, 8 | insert, 9 | listen, 10 | noop, 11 | run_all, 12 | safe_not_equal, 13 | svg_element 14 | } from "../snowpack/pkg/svelte/internal.js"; 15 | 16 | function create_fragment(ctx) { 17 | let circle; 18 | let mounted; 19 | let dispose; 20 | 21 | return { 22 | c() { 23 | circle = svg_element("circle"); 24 | attr(circle, "cx", /*left*/ ctx[0]); 25 | attr(circle, "cy", /*top*/ ctx[1]); 26 | attr(circle, "class", "handle svelte-x5qldl"); 27 | attr(circle, "r", "15"); 28 | }, 29 | m(target, anchor) { 30 | insert(target, circle, anchor); 31 | 32 | if (!mounted) { 33 | dispose = [ 34 | listen(window, "mousemove", /*move*/ ctx[5]), 35 | listen(window, "mouseup", /*stop*/ ctx[3]), 36 | listen(window, "touchmove", /*touchmove*/ ctx[4], { passive: false }), 37 | listen(window, "touchend", /*stop*/ ctx[3]), 38 | listen(circle, "mousedown", /*start*/ ctx[2]), 39 | listen(circle, "touchstart", /*start*/ ctx[2]) 40 | ]; 41 | 42 | mounted = true; 43 | } 44 | }, 45 | p(ctx, [dirty]) { 46 | if (dirty & /*left*/ 1) { 47 | attr(circle, "cx", /*left*/ ctx[0]); 48 | } 49 | 50 | if (dirty & /*top*/ 2) { 51 | attr(circle, "cy", /*top*/ ctx[1]); 52 | } 53 | }, 54 | i: noop, 55 | o: noop, 56 | d(detaching) { 57 | if (detaching) detach(circle); 58 | mounted = false; 59 | run_all(dispose); 60 | } 61 | }; 62 | } 63 | 64 | function instance($$self, $$props, $$invalidate) { 65 | let { left } = $$props; 66 | let { top } = $$props; 67 | let { onChange } = $$props; 68 | let moving = false; 69 | let previousTouch; 70 | 71 | function start(e) { 72 | previousTouch = e.touches && e.touches[0]; 73 | moving = true; 74 | } 75 | 76 | function stop() { 77 | moving = false; 78 | } 79 | 80 | function touchmove(e) { 81 | if (moving) { 82 | e.preventDefault(); 83 | const touch = e.touches[0]; 84 | const movementX = touch.pageX - previousTouch.pageX; 85 | const movementY = touch.pageY - previousTouch.pageY; 86 | console.log(movementX, movementY); 87 | onChange({ movementX, movementY }); 88 | previousTouch = touch; 89 | } 90 | } 91 | 92 | function move(e) { 93 | if (moving) { 94 | onChange(e); 95 | } 96 | } 97 | 98 | $$self.$$set = $$props => { 99 | if ('left' in $$props) $$invalidate(0, left = $$props.left); 100 | if ('top' in $$props) $$invalidate(1, top = $$props.top); 101 | if ('onChange' in $$props) $$invalidate(6, onChange = $$props.onChange); 102 | }; 103 | 104 | return [left, top, start, stop, touchmove, move, onChange]; 105 | } 106 | 107 | class Draggable extends SvelteComponent { 108 | constructor(options) { 109 | super(); 110 | init(this, options, instance, create_fragment, safe_not_equal, { left: 0, top: 1, onChange: 6 }); 111 | } 112 | } 113 | 114 | export default Draggable; -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Offset Polygon 7 | 11 | 12 | 13 | 14 | 19 | 25 | 31 | 35 | 40 | 44 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 61 |
62 |
63 |

Offset Polygon

64 |

Small, no dependency library for offsetting polygons.

65 | 66 |

Get it from npm:

67 |
npm install offset-polygon
68 | 69 |

Usage:

70 |
71 | import offsetPolygon from "offset-polygon";
72 | 
73 | const polygon = [
74 |   { "x": 413, "y": 123 },
75 |   { "x": 510, "y": 299 },
76 |   { "x": 395, "y": 487 },
77 |   { "x": 292, "y": 341 },
78 |   { "x": 92,  "y": 327 },
79 |   { "x": 146, "y": 158 },
80 | ];
81 | 
82 | // Padding
83 | const smallerPolygon = offsetPolygon(polygon, -10, 5);
84 | 
85 | // Margin
86 | const largerPolygon = offsetPolygon(polygon, 10, 5);
87 | 
88 |
89 |
90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Offset Polygon 7 | 11 | 12 | 13 | 14 | 19 | 25 | 31 | 35 | 40 | 44 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 61 |
62 |
63 |

Offset Polygon

64 |

Small, no dependency library for offsetting polygons.

65 | 66 |

Get it from npm:

67 |
npm install offset-polygon
68 | 69 |

Usage:

70 |
71 | import offsetPolygon from "offset-polygon";
72 | 
73 | const polygon = [
74 |   { "x": 413, "y": 123 },
75 |   { "x": 510, "y": 299 },
76 |   { "x": 395, "y": 487 },
77 |   { "x": 292, "y": 341 },
78 |   { "x": 92,  "y": 327 },
79 |   { "x": 146, "y": 158 },
80 | ];
81 | 
82 | // Padding
83 | const smallerPolygon = offsetPolygon(polygon, -10, 5);
84 | 
85 | // Margin
86 | const largerPolygon = offsetPolygon(polygon, 10, 5);
87 | 
88 |
89 |
90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /docs/demo/Control.svelte.js: -------------------------------------------------------------------------------- 1 | import './Control.svelte.css.proxy.js'; 2 | /* demo/Control.svelte generated by Svelte v3.44.0 */ 3 | import { 4 | SvelteComponent, 5 | append, 6 | attr, 7 | detach, 8 | element, 9 | init, 10 | insert, 11 | is_function, 12 | listen, 13 | noop, 14 | safe_not_equal, 15 | set_data, 16 | space, 17 | text 18 | } from "../snowpack/pkg/svelte/internal.js"; 19 | 20 | function create_fragment(ctx) { 21 | let div2; 22 | let label_1; 23 | let t0; 24 | let t1; 25 | let t2; 26 | let div1; 27 | let input; 28 | let t3; 29 | let div0; 30 | let t4; 31 | let mounted; 32 | let dispose; 33 | 34 | return { 35 | c() { 36 | div2 = element("div"); 37 | label_1 = element("label"); 38 | t0 = text(/*label*/ ctx[3]); 39 | t1 = text(":"); 40 | t2 = space(); 41 | div1 = element("div"); 42 | input = element("input"); 43 | t3 = space(); 44 | div0 = element("div"); 45 | t4 = text(/*value*/ ctx[0]); 46 | attr(label_1, "class", "control-label svelte-q28g67"); 47 | attr(label_1, "for", /*name*/ ctx[1]); 48 | attr(input, "class", "control-input svelte-q28g67"); 49 | attr(input, "name", /*name*/ ctx[1]); 50 | attr(input, "type", "range"); 51 | input.value = /*value*/ ctx[0]; 52 | attr(input, "step", /*step*/ ctx[6]); 53 | attr(input, "min", /*min*/ ctx[4]); 54 | attr(input, "max", /*max*/ ctx[5]); 55 | attr(div0, "class", "control-value svelte-q28g67"); 56 | attr(div1, "class", "control-right svelte-q28g67"); 57 | attr(div2, "class", "control svelte-q28g67"); 58 | }, 59 | m(target, anchor) { 60 | insert(target, div2, anchor); 61 | append(div2, label_1); 62 | append(label_1, t0); 63 | append(label_1, t1); 64 | append(div2, t2); 65 | append(div2, div1); 66 | append(div1, input); 67 | append(div1, t3); 68 | append(div1, div0); 69 | append(div0, t4); 70 | 71 | if (!mounted) { 72 | dispose = listen(input, "change", function () { 73 | if (is_function(/*onChange*/ ctx[2])) /*onChange*/ ctx[2].apply(this, arguments); 74 | }); 75 | 76 | mounted = true; 77 | } 78 | }, 79 | p(new_ctx, [dirty]) { 80 | ctx = new_ctx; 81 | if (dirty & /*label*/ 8) set_data(t0, /*label*/ ctx[3]); 82 | 83 | if (dirty & /*name*/ 2) { 84 | attr(label_1, "for", /*name*/ ctx[1]); 85 | } 86 | 87 | if (dirty & /*name*/ 2) { 88 | attr(input, "name", /*name*/ ctx[1]); 89 | } 90 | 91 | if (dirty & /*value*/ 1) { 92 | input.value = /*value*/ ctx[0]; 93 | } 94 | 95 | if (dirty & /*step*/ 64) { 96 | attr(input, "step", /*step*/ ctx[6]); 97 | } 98 | 99 | if (dirty & /*min*/ 16) { 100 | attr(input, "min", /*min*/ ctx[4]); 101 | } 102 | 103 | if (dirty & /*max*/ 32) { 104 | attr(input, "max", /*max*/ ctx[5]); 105 | } 106 | 107 | if (dirty & /*value*/ 1) set_data(t4, /*value*/ ctx[0]); 108 | }, 109 | i: noop, 110 | o: noop, 111 | d(detaching) { 112 | if (detaching) detach(div2); 113 | mounted = false; 114 | dispose(); 115 | } 116 | }; 117 | } 118 | 119 | function instance($$self, $$props, $$invalidate) { 120 | let { value } = $$props; 121 | let { name } = $$props; 122 | let { onChange } = $$props; 123 | let { label } = $$props; 124 | let { min = 0 } = $$props; 125 | let { max = 10 } = $$props; 126 | let { step = 1 } = $$props; 127 | 128 | $$self.$$set = $$props => { 129 | if ('value' in $$props) $$invalidate(0, value = $$props.value); 130 | if ('name' in $$props) $$invalidate(1, name = $$props.name); 131 | if ('onChange' in $$props) $$invalidate(2, onChange = $$props.onChange); 132 | if ('label' in $$props) $$invalidate(3, label = $$props.label); 133 | if ('min' in $$props) $$invalidate(4, min = $$props.min); 134 | if ('max' in $$props) $$invalidate(5, max = $$props.max); 135 | if ('step' in $$props) $$invalidate(6, step = $$props.step); 136 | }; 137 | 138 | return [value, name, onChange, label, min, max, step]; 139 | } 140 | 141 | class Control extends SvelteComponent { 142 | constructor(options) { 143 | super(); 144 | 145 | init(this, options, instance, create_fragment, safe_not_equal, { 146 | value: 0, 147 | name: 1, 148 | onChange: 2, 149 | label: 3, 150 | min: 4, 151 | max: 5, 152 | step: 6 153 | }); 154 | } 155 | } 156 | 157 | export default Control; -------------------------------------------------------------------------------- /demo/Demo.svelte: -------------------------------------------------------------------------------- 1 | 92 | 93 |
94 |

Interactive demo

95 | 102 | 108 | 115 | 121 |
122 | 123 | {#if size} 124 | 129 | 130 | {#if padding > 0} 131 | 136 | {/if} 137 | 138 | {#if margin > 0} 139 | 144 | {/if} 145 | 146 | {#each p as point} 147 | 148 | {/each} 149 | 150 | {#each p as point, index} 151 | onDrag(e, index)} 155 | /> 156 | {/each} 157 | {/if} 158 | 159 | 160 |

Try dragging the vertices.

161 | 162 | 169 | -------------------------------------------------------------------------------- /docs/src/offset-polygon.js: -------------------------------------------------------------------------------- 1 | const TWO_PI = Math.PI * 2; 2 | function inwardEdgeNormal(vertex1, vertex2) { 3 | const dx = vertex2.x - vertex1.x; 4 | const dy = vertex2.y - vertex1.y; 5 | const edgeLength = Math.sqrt(dx * dx + dy * dy); 6 | return { 7 | x: -dy / edgeLength, 8 | y: dx / edgeLength 9 | }; 10 | } 11 | function outwardEdgeNormal(vertex1, vertex2) { 12 | var n = inwardEdgeNormal(vertex1, vertex2); 13 | return { 14 | x: -n.x, 15 | y: -n.y 16 | }; 17 | } 18 | function createPolygon(vertices) { 19 | const edges = []; 20 | let minX = vertices.length > 0 ? vertices[0].x : void 0; 21 | let minY = vertices.length > 0 ? vertices[0].y : void 0; 22 | let maxX = minX; 23 | let maxY = minY; 24 | for (let i = 0; i < vertices.length; i++) { 25 | const vertex1 = vertices[i]; 26 | const vertex2 = vertices[(i + 1) % vertices.length]; 27 | const outwardNormal = outwardEdgeNormal(vertex1, vertex2); 28 | const inwardNormal = inwardEdgeNormal(vertex1, vertex2); 29 | const edge = { 30 | vertex1, 31 | vertex2, 32 | index: i, 33 | outwardNormal, 34 | inwardNormal 35 | }; 36 | edges.push(edge); 37 | const x = vertices[i].x; 38 | const y = vertices[i].y; 39 | minX = Math.min(x, minX); 40 | minY = Math.min(y, minY); 41 | maxX = Math.max(x, maxX); 42 | maxY = Math.max(y, maxY); 43 | } 44 | const polygon = { 45 | vertices, 46 | edges, 47 | minX, 48 | minY, 49 | maxX, 50 | maxY 51 | }; 52 | return polygon; 53 | } 54 | function edgesIntersection(edgeA, edgeB) { 55 | const den = (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex2.x - edgeA.vertex1.x) - (edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex2.y - edgeA.vertex1.y); 56 | if (den == 0) { 57 | return null; 58 | } 59 | const ua = ((edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) - (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) / den; 60 | const ub = ((edgeA.vertex2.x - edgeA.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) - (edgeA.vertex2.y - edgeA.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) / den; 61 | const isIntersectionOutside = ua < 0 || ub < 0 || ua > 1 || ub > 1; 62 | return { 63 | x: edgeA.vertex1.x + ua * (edgeA.vertex2.x - edgeA.vertex1.x), 64 | y: edgeA.vertex1.y + ua * (edgeA.vertex2.y - edgeA.vertex1.y), 65 | isIntersectionOutside 66 | }; 67 | } 68 | function appendArc(arcSegments, vertices, center, radius, startVertex, endVertex, isPaddingBoundary) { 69 | var startAngle = Math.atan2(startVertex.y - center.y, startVertex.x - center.x); 70 | var endAngle = Math.atan2(endVertex.y - center.y, endVertex.x - center.x); 71 | if (startAngle < 0) { 72 | startAngle += TWO_PI; 73 | } 74 | if (endAngle < 0) { 75 | endAngle += TWO_PI; 76 | } 77 | const angle = startAngle > endAngle ? startAngle - endAngle : startAngle + TWO_PI - endAngle; 78 | const angleStep = (isPaddingBoundary ? -angle : TWO_PI - angle) / arcSegments; 79 | vertices.push(startVertex); 80 | for (let i = 1; i < arcSegments; ++i) { 81 | const angle2 = startAngle + angleStep * i; 82 | const vertex = { 83 | x: center.x + Math.cos(angle2) * radius, 84 | y: center.y + Math.sin(angle2) * radius 85 | }; 86 | vertices.push(vertex); 87 | } 88 | vertices.push(endVertex); 89 | } 90 | function createOffsetEdge(edge, dx, dy) { 91 | return { 92 | vertex1: { 93 | x: edge.vertex1.x + dx, 94 | y: edge.vertex1.y + dy 95 | }, 96 | vertex2: { 97 | x: edge.vertex2.x + dx, 98 | y: edge.vertex2.y + dy 99 | } 100 | }; 101 | } 102 | function createMarginPolygon(polygon, offset, arcSegments) { 103 | const offsetEdges = []; 104 | for (let i = 0; i < polygon.edges.length; i++) { 105 | const edge = polygon.edges[i]; 106 | const dx = edge.outwardNormal.x * offset; 107 | const dy = edge.outwardNormal.y * offset; 108 | offsetEdges.push(createOffsetEdge(edge, dx, dy)); 109 | } 110 | const vertices = []; 111 | for (let i = 0; i < offsetEdges.length; i++) { 112 | const thisEdge = offsetEdges[i]; 113 | const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]; 114 | const vertex = edgesIntersection(prevEdge, thisEdge); 115 | if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) { 116 | vertices.push({ 117 | x: vertex.x, 118 | y: vertex.y 119 | }); 120 | } else { 121 | const arcCenter = polygon.edges[i].vertex1; 122 | appendArc(arcSegments, vertices, arcCenter, offset, prevEdge.vertex2, thisEdge.vertex1, false); 123 | } 124 | } 125 | const marginPolygon = createPolygon(vertices); 126 | marginPolygon.offsetEdges = offsetEdges; 127 | return marginPolygon; 128 | } 129 | function createPaddingPolygon(polygon, offset, arcSegments) { 130 | const offsetEdges = []; 131 | for (let i = 0; i < polygon.edges.length; i++) { 132 | const edge = polygon.edges[i]; 133 | const dx = edge.inwardNormal.x * offset; 134 | const dy = edge.inwardNormal.y * offset; 135 | offsetEdges.push(createOffsetEdge(edge, dx, dy)); 136 | } 137 | const vertices = []; 138 | for (let i = 0; i < offsetEdges.length; i++) { 139 | const thisEdge = offsetEdges[i]; 140 | const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]; 141 | const vertex = edgesIntersection(prevEdge, thisEdge); 142 | if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) { 143 | vertices.push({ 144 | x: vertex.x, 145 | y: vertex.y 146 | }); 147 | } else { 148 | const arcCenter = polygon.edges[i].vertex1; 149 | appendArc(arcSegments, vertices, arcCenter, offset, prevEdge.vertex2, thisEdge.vertex1, true); 150 | } 151 | } 152 | const paddingPolygon = createPolygon(vertices); 153 | paddingPolygon.offsetEdges = offsetEdges; 154 | return paddingPolygon; 155 | } 156 | export default function offsetPolygon(vertices, offset, arcSegments = 0) { 157 | const polygon = createPolygon(vertices); 158 | if (offset > 0) { 159 | return createMarginPolygon(polygon, offset, arcSegments).vertices; 160 | } else { 161 | return createPaddingPolygon(polygon, -offset, arcSegments).vertices; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /lib/offset-polygon.js: -------------------------------------------------------------------------------- 1 | // src/offset-polygon.ts 2 | var TWO_PI = Math.PI * 2; 3 | function inwardEdgeNormal(vertex1, vertex2) { 4 | const dx = vertex2.x - vertex1.x; 5 | const dy = vertex2.y - vertex1.y; 6 | const edgeLength = Math.sqrt(dx * dx + dy * dy); 7 | return { 8 | x: -dy / edgeLength, 9 | y: dx / edgeLength 10 | }; 11 | } 12 | function outwardEdgeNormal(vertex1, vertex2) { 13 | var n = inwardEdgeNormal(vertex1, vertex2); 14 | return { 15 | x: -n.x, 16 | y: -n.y 17 | }; 18 | } 19 | function createPolygon(vertices) { 20 | const edges = []; 21 | let minX = vertices.length > 0 ? vertices[0].x : void 0; 22 | let minY = vertices.length > 0 ? vertices[0].y : void 0; 23 | let maxX = minX; 24 | let maxY = minY; 25 | for (let i = 0; i < vertices.length; i++) { 26 | const vertex1 = vertices[i]; 27 | const vertex2 = vertices[(i + 1) % vertices.length]; 28 | const outwardNormal = outwardEdgeNormal(vertex1, vertex2); 29 | const inwardNormal = inwardEdgeNormal(vertex1, vertex2); 30 | const edge = { 31 | vertex1, 32 | vertex2, 33 | index: i, 34 | outwardNormal, 35 | inwardNormal 36 | }; 37 | edges.push(edge); 38 | const x = vertices[i].x; 39 | const y = vertices[i].y; 40 | minX = Math.min(x, minX); 41 | minY = Math.min(y, minY); 42 | maxX = Math.max(x, maxX); 43 | maxY = Math.max(y, maxY); 44 | } 45 | const polygon = { 46 | vertices, 47 | edges, 48 | minX, 49 | minY, 50 | maxX, 51 | maxY 52 | }; 53 | return polygon; 54 | } 55 | function edgesIntersection(edgeA, edgeB) { 56 | const den = (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex2.x - edgeA.vertex1.x) - (edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex2.y - edgeA.vertex1.y); 57 | if (den == 0) { 58 | return null; 59 | } 60 | const ua = ((edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) - (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) / den; 61 | const ub = ((edgeA.vertex2.x - edgeA.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) - (edgeA.vertex2.y - edgeA.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) / den; 62 | const isIntersectionOutside = ua < 0 || ub < 0 || ua > 1 || ub > 1; 63 | return { 64 | x: edgeA.vertex1.x + ua * (edgeA.vertex2.x - edgeA.vertex1.x), 65 | y: edgeA.vertex1.y + ua * (edgeA.vertex2.y - edgeA.vertex1.y), 66 | isIntersectionOutside 67 | }; 68 | } 69 | function appendArc(arcSegments, vertices, center, radius, startVertex, endVertex, isPaddingBoundary) { 70 | var startAngle = Math.atan2(startVertex.y - center.y, startVertex.x - center.x); 71 | var endAngle = Math.atan2(endVertex.y - center.y, endVertex.x - center.x); 72 | if (startAngle < 0) { 73 | startAngle += TWO_PI; 74 | } 75 | if (endAngle < 0) { 76 | endAngle += TWO_PI; 77 | } 78 | const angle = startAngle > endAngle ? startAngle - endAngle : startAngle + TWO_PI - endAngle; 79 | const angleStep = (isPaddingBoundary ? -angle : TWO_PI - angle) / arcSegments; 80 | vertices.push(startVertex); 81 | for (let i = 1; i < arcSegments; ++i) { 82 | const angle2 = startAngle + angleStep * i; 83 | const vertex = { 84 | x: center.x + Math.cos(angle2) * radius, 85 | y: center.y + Math.sin(angle2) * radius 86 | }; 87 | vertices.push(vertex); 88 | } 89 | vertices.push(endVertex); 90 | } 91 | function createOffsetEdge(edge, dx, dy) { 92 | return { 93 | vertex1: { 94 | x: edge.vertex1.x + dx, 95 | y: edge.vertex1.y + dy 96 | }, 97 | vertex2: { 98 | x: edge.vertex2.x + dx, 99 | y: edge.vertex2.y + dy 100 | } 101 | }; 102 | } 103 | function createMarginPolygon(polygon, offset, arcSegments) { 104 | const offsetEdges = []; 105 | for (let i = 0; i < polygon.edges.length; i++) { 106 | const edge = polygon.edges[i]; 107 | const dx = edge.outwardNormal.x * offset; 108 | const dy = edge.outwardNormal.y * offset; 109 | offsetEdges.push(createOffsetEdge(edge, dx, dy)); 110 | } 111 | const vertices = []; 112 | for (let i = 0; i < offsetEdges.length; i++) { 113 | const thisEdge = offsetEdges[i]; 114 | const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]; 115 | const vertex = edgesIntersection(prevEdge, thisEdge); 116 | if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) { 117 | vertices.push({ 118 | x: vertex.x, 119 | y: vertex.y 120 | }); 121 | } else { 122 | const arcCenter = polygon.edges[i].vertex1; 123 | appendArc(arcSegments, vertices, arcCenter, offset, prevEdge.vertex2, thisEdge.vertex1, false); 124 | } 125 | } 126 | const marginPolygon = createPolygon(vertices); 127 | marginPolygon.offsetEdges = offsetEdges; 128 | return marginPolygon; 129 | } 130 | function createPaddingPolygon(polygon, offset, arcSegments) { 131 | const offsetEdges = []; 132 | for (let i = 0; i < polygon.edges.length; i++) { 133 | const edge = polygon.edges[i]; 134 | const dx = edge.inwardNormal.x * offset; 135 | const dy = edge.inwardNormal.y * offset; 136 | offsetEdges.push(createOffsetEdge(edge, dx, dy)); 137 | } 138 | const vertices = []; 139 | for (let i = 0; i < offsetEdges.length; i++) { 140 | const thisEdge = offsetEdges[i]; 141 | const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]; 142 | const vertex = edgesIntersection(prevEdge, thisEdge); 143 | if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) { 144 | vertices.push({ 145 | x: vertex.x, 146 | y: vertex.y 147 | }); 148 | } else { 149 | const arcCenter = polygon.edges[i].vertex1; 150 | appendArc(arcSegments, vertices, arcCenter, offset, prevEdge.vertex2, thisEdge.vertex1, true); 151 | } 152 | } 153 | const paddingPolygon = createPolygon(vertices); 154 | paddingPolygon.offsetEdges = offsetEdges; 155 | return paddingPolygon; 156 | } 157 | function offsetPolygon(vertices, offset, arcSegments = 0) { 158 | const polygon = createPolygon(vertices); 159 | if (offset > 0) { 160 | return createMarginPolygon(polygon, offset, arcSegments).vertices; 161 | } else { 162 | return createPaddingPolygon(polygon, -offset, arcSegments).vertices; 163 | } 164 | } 165 | export { 166 | offsetPolygon as default 167 | }; 168 | //# sourceMappingURL=offset-polygon.js.map 169 | -------------------------------------------------------------------------------- /src/offset-polygon.ts: -------------------------------------------------------------------------------- 1 | // TODO check these comments: 2 | // Assuming that polygon vertices are in clockwise order 3 | 4 | type Vector = { 5 | x: number; 6 | y: number; 7 | }; 8 | 9 | type Edge = { 10 | index: number; 11 | inwardNormal: Vector; 12 | outwardNormal: Vector; 13 | vertex1: Vector; 14 | vertex2: Vector; 15 | }; 16 | 17 | type OffsetEdge = { 18 | vertex1: Vector; 19 | vertex2: Vector; 20 | }; 21 | 22 | type Polygon = { 23 | edges: Edge[]; 24 | offsetEdges?: OffsetEdge[]; 25 | maxX: number; 26 | maxY: number; 27 | minX: number; 28 | minY: number; 29 | vertices: Vector[]; 30 | }; 31 | 32 | const TWO_PI = Math.PI * 2; 33 | 34 | // See http://paulbourke.net/geometry/pointlineplane/ 35 | function inwardEdgeNormal(vertex1: Vector, vertex2: Vector): Vector { 36 | // Assuming that polygon vertices are in clockwise order 37 | const dx = vertex2.x - vertex1.x; 38 | const dy = vertex2.y - vertex1.y; 39 | const edgeLength = Math.sqrt(dx * dx + dy * dy); 40 | 41 | return { 42 | x: -dy / edgeLength, 43 | y: dx / edgeLength, 44 | }; 45 | } 46 | 47 | function outwardEdgeNormal(vertex1: Vector, vertex2: Vector): Vector { 48 | var n = inwardEdgeNormal(vertex1, vertex2); 49 | 50 | return { 51 | x: -n.x, 52 | y: -n.y, 53 | }; 54 | } 55 | 56 | function createPolygon(vertices: Vector[]): Polygon { 57 | const edges: Edge[] = []; 58 | let minX = vertices.length > 0 ? vertices[0].x : undefined; 59 | let minY = vertices.length > 0 ? vertices[0].y : undefined; 60 | let maxX = minX; 61 | let maxY = minY; 62 | 63 | for (let i = 0; i < vertices.length; i++) { 64 | const vertex1 = vertices[i]; 65 | const vertex2 = vertices[(i + 1) % vertices.length]; 66 | 67 | const outwardNormal = outwardEdgeNormal(vertex1, vertex2); 68 | 69 | const inwardNormal = inwardEdgeNormal(vertex1, vertex2); 70 | 71 | const edge: Edge = { 72 | vertex1, 73 | vertex2, 74 | index: i, 75 | outwardNormal, 76 | inwardNormal, 77 | }; 78 | 79 | edges.push(edge); 80 | 81 | const x = vertices[i].x; 82 | const y = vertices[i].y; 83 | minX = Math.min(x, minX); 84 | minY = Math.min(y, minY); 85 | maxX = Math.max(x, maxX); 86 | maxY = Math.max(y, maxY); 87 | } 88 | 89 | const polygon: Polygon = { 90 | vertices, 91 | edges, 92 | minX, 93 | minY, 94 | maxX, 95 | maxY, 96 | }; 97 | 98 | return polygon; 99 | } 100 | 101 | // based on http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/, edgeA => "line a", edgeB => "line b" 102 | 103 | function edgesIntersection(edgeA: Edge | OffsetEdge, edgeB: Edge | OffsetEdge) { 104 | const den = 105 | (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex2.x - edgeA.vertex1.x) - 106 | (edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex2.y - edgeA.vertex1.y); 107 | 108 | if (den == 0) { 109 | return null; // lines are parallel or coincident 110 | } 111 | 112 | const ua = 113 | ((edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) - 114 | (edgeB.vertex2.y - edgeB.vertex1.y) * 115 | (edgeA.vertex1.x - edgeB.vertex1.x)) / 116 | den; 117 | 118 | const ub = 119 | ((edgeA.vertex2.x - edgeA.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) - 120 | (edgeA.vertex2.y - edgeA.vertex1.y) * 121 | (edgeA.vertex1.x - edgeB.vertex1.x)) / 122 | den; 123 | 124 | // Edges are not intersecting but the lines defined by them are 125 | const isIntersectionOutside = ua < 0 || ub < 0 || ua > 1 || ub > 1; 126 | 127 | return { 128 | x: edgeA.vertex1.x + ua * (edgeA.vertex2.x - edgeA.vertex1.x), 129 | y: edgeA.vertex1.y + ua * (edgeA.vertex2.y - edgeA.vertex1.y), 130 | isIntersectionOutside, 131 | }; 132 | } 133 | 134 | function appendArc( 135 | arcSegments: number, 136 | vertices: Vector[], 137 | center: Vector, 138 | radius: number, 139 | startVertex: Vector, 140 | endVertex: Vector, 141 | isPaddingBoundary: boolean 142 | ) { 143 | var startAngle = Math.atan2( 144 | startVertex.y - center.y, 145 | startVertex.x - center.x 146 | ); 147 | var endAngle = Math.atan2(endVertex.y - center.y, endVertex.x - center.x); 148 | 149 | if (startAngle < 0) { 150 | startAngle += TWO_PI; 151 | } 152 | 153 | if (endAngle < 0) { 154 | endAngle += TWO_PI; 155 | } 156 | 157 | const angle = 158 | startAngle > endAngle 159 | ? startAngle - endAngle 160 | : startAngle + TWO_PI - endAngle; 161 | const angleStep = (isPaddingBoundary ? -angle : TWO_PI - angle) / arcSegments; 162 | 163 | vertices.push(startVertex); 164 | 165 | for (let i = 1; i < arcSegments; ++i) { 166 | const angle = startAngle + angleStep * i; 167 | 168 | const vertex = { 169 | x: center.x + Math.cos(angle) * radius, 170 | y: center.y + Math.sin(angle) * radius, 171 | }; 172 | 173 | vertices.push(vertex); 174 | } 175 | 176 | vertices.push(endVertex); 177 | } 178 | 179 | function createOffsetEdge(edge: Edge, dx: number, dy: number): OffsetEdge { 180 | return { 181 | vertex1: { 182 | x: edge.vertex1.x + dx, 183 | y: edge.vertex1.y + dy, 184 | }, 185 | vertex2: { 186 | x: edge.vertex2.x + dx, 187 | y: edge.vertex2.y + dy, 188 | }, 189 | }; 190 | } 191 | 192 | function createMarginPolygon( 193 | polygon: Polygon, 194 | offset: number, 195 | arcSegments: number 196 | ): Polygon { 197 | const offsetEdges: OffsetEdge[] = []; 198 | 199 | for (let i = 0; i < polygon.edges.length; i++) { 200 | const edge = polygon.edges[i]; 201 | const dx = edge.outwardNormal.x * offset; 202 | const dy = edge.outwardNormal.y * offset; 203 | offsetEdges.push(createOffsetEdge(edge, dx, dy)); 204 | } 205 | 206 | const vertices: Vector[] = []; 207 | 208 | for (let i = 0; i < offsetEdges.length; i++) { 209 | const thisEdge = offsetEdges[i]; 210 | const prevEdge = 211 | offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]; 212 | const vertex = edgesIntersection(prevEdge, thisEdge); 213 | 214 | if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) { 215 | vertices.push({ 216 | x: vertex.x, 217 | y: vertex.y, 218 | }); 219 | } else { 220 | const arcCenter = polygon.edges[i].vertex1; 221 | 222 | appendArc( 223 | arcSegments, 224 | vertices, 225 | arcCenter, 226 | offset, 227 | prevEdge.vertex2, 228 | thisEdge.vertex1, 229 | false 230 | ); 231 | } 232 | } 233 | 234 | const marginPolygon = createPolygon(vertices); 235 | 236 | marginPolygon.offsetEdges = offsetEdges; 237 | 238 | return marginPolygon; 239 | } 240 | 241 | function createPaddingPolygon( 242 | polygon: Polygon, 243 | offset: number, 244 | arcSegments: number 245 | ): Polygon { 246 | const offsetEdges: OffsetEdge[] = []; 247 | 248 | for (let i = 0; i < polygon.edges.length; i++) { 249 | const edge = polygon.edges[i]; 250 | const dx = edge.inwardNormal.x * offset; 251 | const dy = edge.inwardNormal.y * offset; 252 | offsetEdges.push(createOffsetEdge(edge, dx, dy)); 253 | } 254 | 255 | const vertices: Vector[] = []; 256 | 257 | for (let i = 0; i < offsetEdges.length; i++) { 258 | const thisEdge = offsetEdges[i]; 259 | const prevEdge = 260 | offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]; 261 | const vertex = edgesIntersection(prevEdge, thisEdge); 262 | if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) { 263 | vertices.push({ 264 | x: vertex.x, 265 | y: vertex.y, 266 | }); 267 | } else { 268 | const arcCenter = polygon.edges[i].vertex1; 269 | 270 | appendArc( 271 | arcSegments, 272 | vertices, 273 | arcCenter, 274 | offset, 275 | prevEdge.vertex2, 276 | thisEdge.vertex1, 277 | true 278 | ); 279 | } 280 | } 281 | 282 | const paddingPolygon = createPolygon(vertices); 283 | 284 | paddingPolygon.offsetEdges = offsetEdges; 285 | 286 | return paddingPolygon; 287 | } 288 | 289 | export default function offsetPolygon( 290 | vertices: Vector[], 291 | offset: number, 292 | arcSegments: number = 0 293 | ): Vector[] { 294 | const polygon = createPolygon(vertices); 295 | 296 | if (offset > 0) { 297 | return createMarginPolygon(polygon, offset, arcSegments).vertices; 298 | } else { 299 | return createPaddingPolygon(polygon, -offset, arcSegments).vertices; 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /docs/snowpack/pkg/common/index-fbcaf5b0.js: -------------------------------------------------------------------------------- 1 | function noop() { 2 | } 3 | function run(fn) { 4 | return fn(); 5 | } 6 | function blank_object() { 7 | return Object.create(null); 8 | } 9 | function run_all(fns) { 10 | fns.forEach(run); 11 | } 12 | function is_function(thing) { 13 | return typeof thing === "function"; 14 | } 15 | function safe_not_equal(a, b) { 16 | return a != a ? b == b : a !== b || (a && typeof a === "object" || typeof a === "function"); 17 | } 18 | function is_empty(obj) { 19 | return Object.keys(obj).length === 0; 20 | } 21 | function append(target, node) { 22 | target.appendChild(node); 23 | } 24 | function insert(target, node, anchor) { 25 | target.insertBefore(node, anchor || null); 26 | } 27 | function detach(node) { 28 | node.parentNode.removeChild(node); 29 | } 30 | function destroy_each(iterations, detaching) { 31 | for (let i = 0; i < iterations.length; i += 1) { 32 | if (iterations[i]) 33 | iterations[i].d(detaching); 34 | } 35 | } 36 | function element(name) { 37 | return document.createElement(name); 38 | } 39 | function svg_element(name) { 40 | return document.createElementNS("http://www.w3.org/2000/svg", name); 41 | } 42 | function text(data) { 43 | return document.createTextNode(data); 44 | } 45 | function space() { 46 | return text(" "); 47 | } 48 | function empty() { 49 | return text(""); 50 | } 51 | function listen(node, event, handler, options) { 52 | node.addEventListener(event, handler, options); 53 | return () => node.removeEventListener(event, handler, options); 54 | } 55 | function attr(node, attribute, value) { 56 | if (value == null) 57 | node.removeAttribute(attribute); 58 | else if (node.getAttribute(attribute) !== value) 59 | node.setAttribute(attribute, value); 60 | } 61 | function children(element2) { 62 | return Array.from(element2.childNodes); 63 | } 64 | function set_data(text2, data) { 65 | data = "" + data; 66 | if (text2.wholeText !== data) 67 | text2.data = data; 68 | } 69 | let current_component; 70 | function set_current_component(component) { 71 | current_component = component; 72 | } 73 | function get_current_component() { 74 | if (!current_component) 75 | throw new Error("Function called outside component initialization"); 76 | return current_component; 77 | } 78 | function onMount(fn) { 79 | get_current_component().$$.on_mount.push(fn); 80 | } 81 | const dirty_components = []; 82 | const binding_callbacks = []; 83 | const render_callbacks = []; 84 | const flush_callbacks = []; 85 | const resolved_promise = Promise.resolve(); 86 | let update_scheduled = false; 87 | function schedule_update() { 88 | if (!update_scheduled) { 89 | update_scheduled = true; 90 | resolved_promise.then(flush); 91 | } 92 | } 93 | function add_render_callback(fn) { 94 | render_callbacks.push(fn); 95 | } 96 | let flushing = false; 97 | const seen_callbacks = new Set(); 98 | function flush() { 99 | if (flushing) 100 | return; 101 | flushing = true; 102 | do { 103 | for (let i = 0; i < dirty_components.length; i += 1) { 104 | const component = dirty_components[i]; 105 | set_current_component(component); 106 | update(component.$$); 107 | } 108 | set_current_component(null); 109 | dirty_components.length = 0; 110 | while (binding_callbacks.length) 111 | binding_callbacks.pop()(); 112 | for (let i = 0; i < render_callbacks.length; i += 1) { 113 | const callback = render_callbacks[i]; 114 | if (!seen_callbacks.has(callback)) { 115 | seen_callbacks.add(callback); 116 | callback(); 117 | } 118 | } 119 | render_callbacks.length = 0; 120 | } while (dirty_components.length); 121 | while (flush_callbacks.length) { 122 | flush_callbacks.pop()(); 123 | } 124 | update_scheduled = false; 125 | flushing = false; 126 | seen_callbacks.clear(); 127 | } 128 | function update($$) { 129 | if ($$.fragment !== null) { 130 | $$.update(); 131 | run_all($$.before_update); 132 | const dirty = $$.dirty; 133 | $$.dirty = [-1]; 134 | $$.fragment && $$.fragment.p($$.ctx, dirty); 135 | $$.after_update.forEach(add_render_callback); 136 | } 137 | } 138 | const outroing = new Set(); 139 | let outros; 140 | function group_outros() { 141 | outros = { 142 | r: 0, 143 | c: [], 144 | p: outros 145 | }; 146 | } 147 | function check_outros() { 148 | if (!outros.r) { 149 | run_all(outros.c); 150 | } 151 | outros = outros.p; 152 | } 153 | function transition_in(block, local) { 154 | if (block && block.i) { 155 | outroing.delete(block); 156 | block.i(local); 157 | } 158 | } 159 | function transition_out(block, local, detach2, callback) { 160 | if (block && block.o) { 161 | if (outroing.has(block)) 162 | return; 163 | outroing.add(block); 164 | outros.c.push(() => { 165 | outroing.delete(block); 166 | if (callback) { 167 | if (detach2) 168 | block.d(1); 169 | callback(); 170 | } 171 | }); 172 | block.o(local); 173 | } 174 | } 175 | function create_component(block) { 176 | block && block.c(); 177 | } 178 | function mount_component(component, target, anchor, customElement) { 179 | const {fragment, on_mount, on_destroy: on_destroy2, after_update} = component.$$; 180 | fragment && fragment.m(target, anchor); 181 | if (!customElement) { 182 | add_render_callback(() => { 183 | const new_on_destroy = on_mount.map(run).filter(is_function); 184 | if (on_destroy2) { 185 | on_destroy2.push(...new_on_destroy); 186 | } else { 187 | run_all(new_on_destroy); 188 | } 189 | component.$$.on_mount = []; 190 | }); 191 | } 192 | after_update.forEach(add_render_callback); 193 | } 194 | function destroy_component(component, detaching) { 195 | const $$ = component.$$; 196 | if ($$.fragment !== null) { 197 | run_all($$.on_destroy); 198 | $$.fragment && $$.fragment.d(detaching); 199 | $$.on_destroy = $$.fragment = null; 200 | $$.ctx = []; 201 | } 202 | } 203 | function make_dirty(component, i) { 204 | if (component.$$.dirty[0] === -1) { 205 | dirty_components.push(component); 206 | schedule_update(); 207 | component.$$.dirty.fill(0); 208 | } 209 | component.$$.dirty[i / 31 | 0] |= 1 << i % 31; 210 | } 211 | function init(component, options, instance, create_fragment, not_equal2, props, append_styles2, dirty = [-1]) { 212 | const parent_component = current_component; 213 | set_current_component(component); 214 | const $$ = component.$$ = { 215 | fragment: null, 216 | ctx: null, 217 | props, 218 | update: noop, 219 | not_equal: not_equal2, 220 | bound: blank_object(), 221 | on_mount: [], 222 | on_destroy: [], 223 | on_disconnect: [], 224 | before_update: [], 225 | after_update: [], 226 | context: new Map(options.context || (parent_component ? parent_component.$$.context : [])), 227 | callbacks: blank_object(), 228 | dirty, 229 | skip_bound: false, 230 | root: options.target || parent_component.$$.root 231 | }; 232 | append_styles2 && append_styles2($$.root); 233 | let ready = false; 234 | $$.ctx = instance ? instance(component, options.props || {}, (i, ret, ...rest) => { 235 | const value = rest.length ? rest[0] : ret; 236 | if ($$.ctx && not_equal2($$.ctx[i], $$.ctx[i] = value)) { 237 | if (!$$.skip_bound && $$.bound[i]) 238 | $$.bound[i](value); 239 | if (ready) 240 | make_dirty(component, i); 241 | } 242 | return ret; 243 | }) : []; 244 | $$.update(); 245 | ready = true; 246 | run_all($$.before_update); 247 | $$.fragment = create_fragment ? create_fragment($$.ctx) : false; 248 | if (options.target) { 249 | if (options.hydrate) { 250 | const nodes = children(options.target); 251 | $$.fragment && $$.fragment.l(nodes); 252 | nodes.forEach(detach); 253 | } else { 254 | $$.fragment && $$.fragment.c(); 255 | } 256 | if (options.intro) 257 | transition_in(component.$$.fragment); 258 | mount_component(component, options.target, options.anchor, options.customElement); 259 | flush(); 260 | } 261 | set_current_component(parent_component); 262 | } 263 | class SvelteComponent { 264 | $destroy() { 265 | destroy_component(this, 1); 266 | this.$destroy = noop; 267 | } 268 | $on(type, callback) { 269 | const callbacks = this.$$.callbacks[type] || (this.$$.callbacks[type] = []); 270 | callbacks.push(callback); 271 | return () => { 272 | const index = callbacks.indexOf(callback); 273 | if (index !== -1) 274 | callbacks.splice(index, 1); 275 | }; 276 | } 277 | $set($$props) { 278 | if (this.$$set && !is_empty($$props)) { 279 | this.$$.skip_bound = true; 280 | this.$$set($$props); 281 | this.$$.skip_bound = false; 282 | } 283 | } 284 | } 285 | 286 | export { get_current_component as A, current_component as B, set_current_component as C, SvelteComponent as S, append as a, attr as b, insert as c, detach as d, element as e, is_function as f, set_data as g, space as h, init as i, binding_callbacks as j, check_outros as k, listen as l, create_component as m, noop as n, onMount as o, destroy_component as p, destroy_each as q, empty as r, safe_not_equal as s, text as t, group_outros as u, mount_component as v, svg_element as w, transition_in as x, transition_out as y, run_all as z }; 287 | -------------------------------------------------------------------------------- /lib/offset-polygon.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../src/offset-polygon.ts"], 4 | "sourcesContent": ["// TODO check these comments:\n// Assuming that polygon vertices are in clockwise order\n\ntype Vector = {\n x: number;\n y: number;\n};\n\ntype Edge = {\n index: number;\n inwardNormal: Vector;\n outwardNormal: Vector;\n vertex1: Vector;\n vertex2: Vector;\n};\n\ntype OffsetEdge = {\n vertex1: Vector;\n vertex2: Vector;\n};\n\ntype Polygon = {\n edges: Edge[];\n offsetEdges?: OffsetEdge[];\n maxX: number;\n maxY: number;\n minX: number;\n minY: number;\n vertices: Vector[];\n};\n\nconst TWO_PI = Math.PI * 2;\n\n// See http://paulbourke.net/geometry/pointlineplane/\nfunction inwardEdgeNormal(vertex1: Vector, vertex2: Vector): Vector {\n // Assuming that polygon vertices are in clockwise order\n const dx = vertex2.x - vertex1.x;\n const dy = vertex2.y - vertex1.y;\n const edgeLength = Math.sqrt(dx * dx + dy * dy);\n\n return {\n x: -dy / edgeLength,\n y: dx / edgeLength,\n };\n}\n\nfunction outwardEdgeNormal(vertex1: Vector, vertex2: Vector): Vector {\n var n = inwardEdgeNormal(vertex1, vertex2);\n\n return {\n x: -n.x,\n y: -n.y,\n };\n}\n\nfunction createPolygon(vertices: Vector[]): Polygon {\n const edges: Edge[] = [];\n let minX = vertices.length > 0 ? vertices[0].x : undefined;\n let minY = vertices.length > 0 ? vertices[0].y : undefined;\n let maxX = minX;\n let maxY = minY;\n\n for (let i = 0; i < vertices.length; i++) {\n const vertex1 = vertices[i];\n const vertex2 = vertices[(i + 1) % vertices.length];\n\n const outwardNormal = outwardEdgeNormal(vertex1, vertex2);\n\n const inwardNormal = inwardEdgeNormal(vertex1, vertex2);\n\n const edge: Edge = {\n vertex1,\n vertex2,\n index: i,\n outwardNormal,\n inwardNormal,\n };\n\n edges.push(edge);\n\n const x = vertices[i].x;\n const y = vertices[i].y;\n minX = Math.min(x, minX);\n minY = Math.min(y, minY);\n maxX = Math.max(x, maxX);\n maxY = Math.max(y, maxY);\n }\n\n const polygon: Polygon = {\n vertices,\n edges,\n minX,\n minY,\n maxX,\n maxY,\n };\n\n return polygon;\n}\n\n// based on http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/, edgeA => \"line a\", edgeB => \"line b\"\n\nfunction edgesIntersection(edgeA: Edge | OffsetEdge, edgeB: Edge | OffsetEdge) {\n const den =\n (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex2.x - edgeA.vertex1.x) -\n (edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex2.y - edgeA.vertex1.y);\n\n if (den == 0) {\n return null; // lines are parallel or coincident\n }\n\n const ua =\n ((edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) -\n (edgeB.vertex2.y - edgeB.vertex1.y) *\n (edgeA.vertex1.x - edgeB.vertex1.x)) /\n den;\n\n const ub =\n ((edgeA.vertex2.x - edgeA.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) -\n (edgeA.vertex2.y - edgeA.vertex1.y) *\n (edgeA.vertex1.x - edgeB.vertex1.x)) /\n den;\n\n // Edges are not intersecting but the lines defined by them are\n const isIntersectionOutside = ua < 0 || ub < 0 || ua > 1 || ub > 1;\n\n return {\n x: edgeA.vertex1.x + ua * (edgeA.vertex2.x - edgeA.vertex1.x),\n y: edgeA.vertex1.y + ua * (edgeA.vertex2.y - edgeA.vertex1.y),\n isIntersectionOutside,\n };\n}\n\nfunction appendArc(\n arcSegments: number,\n vertices: Vector[],\n center: Vector,\n radius: number,\n startVertex: Vector,\n endVertex: Vector,\n isPaddingBoundary: boolean\n) {\n var startAngle = Math.atan2(\n startVertex.y - center.y,\n startVertex.x - center.x\n );\n var endAngle = Math.atan2(endVertex.y - center.y, endVertex.x - center.x);\n\n if (startAngle < 0) {\n startAngle += TWO_PI;\n }\n\n if (endAngle < 0) {\n endAngle += TWO_PI;\n }\n\n const angle =\n startAngle > endAngle\n ? startAngle - endAngle\n : startAngle + TWO_PI - endAngle;\n const angleStep = (isPaddingBoundary ? -angle : TWO_PI - angle) / arcSegments;\n\n vertices.push(startVertex);\n\n for (let i = 1; i < arcSegments; ++i) {\n const angle = startAngle + angleStep * i;\n\n const vertex = {\n x: center.x + Math.cos(angle) * radius,\n y: center.y + Math.sin(angle) * radius,\n };\n\n vertices.push(vertex);\n }\n\n vertices.push(endVertex);\n}\n\nfunction createOffsetEdge(edge: Edge, dx: number, dy: number): OffsetEdge {\n return {\n vertex1: {\n x: edge.vertex1.x + dx,\n y: edge.vertex1.y + dy,\n },\n vertex2: {\n x: edge.vertex2.x + dx,\n y: edge.vertex2.y + dy,\n },\n };\n}\n\nfunction createMarginPolygon(\n polygon: Polygon,\n offset: number,\n arcSegments: number\n): Polygon {\n const offsetEdges: OffsetEdge[] = [];\n\n for (let i = 0; i < polygon.edges.length; i++) {\n const edge = polygon.edges[i];\n const dx = edge.outwardNormal.x * offset;\n const dy = edge.outwardNormal.y * offset;\n offsetEdges.push(createOffsetEdge(edge, dx, dy));\n }\n\n const vertices: Vector[] = [];\n\n for (let i = 0; i < offsetEdges.length; i++) {\n const thisEdge = offsetEdges[i];\n const prevEdge =\n offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length];\n const vertex = edgesIntersection(prevEdge, thisEdge);\n\n if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {\n vertices.push({\n x: vertex.x,\n y: vertex.y,\n });\n } else {\n const arcCenter = polygon.edges[i].vertex1;\n\n appendArc(\n arcSegments,\n vertices,\n arcCenter,\n offset,\n prevEdge.vertex2,\n thisEdge.vertex1,\n false\n );\n }\n }\n\n const marginPolygon = createPolygon(vertices);\n\n marginPolygon.offsetEdges = offsetEdges;\n\n return marginPolygon;\n}\n\nfunction createPaddingPolygon(\n polygon: Polygon,\n offset: number,\n arcSegments: number\n): Polygon {\n const offsetEdges: OffsetEdge[] = [];\n\n for (let i = 0; i < polygon.edges.length; i++) {\n const edge = polygon.edges[i];\n const dx = edge.inwardNormal.x * offset;\n const dy = edge.inwardNormal.y * offset;\n offsetEdges.push(createOffsetEdge(edge, dx, dy));\n }\n\n const vertices: Vector[] = [];\n\n for (let i = 0; i < offsetEdges.length; i++) {\n const thisEdge = offsetEdges[i];\n const prevEdge =\n offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length];\n const vertex = edgesIntersection(prevEdge, thisEdge);\n if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {\n vertices.push({\n x: vertex.x,\n y: vertex.y,\n });\n } else {\n const arcCenter = polygon.edges[i].vertex1;\n\n appendArc(\n arcSegments,\n vertices,\n arcCenter,\n offset,\n prevEdge.vertex2,\n thisEdge.vertex1,\n true\n );\n }\n }\n\n const paddingPolygon = createPolygon(vertices);\n\n paddingPolygon.offsetEdges = offsetEdges;\n\n return paddingPolygon;\n}\n\nexport default function offsetPolygon(\n vertices: Vector[],\n offset: number,\n arcSegments: number = 0\n): Vector[] {\n const polygon = createPolygon(vertices);\n\n if (offset > 0) {\n return createMarginPolygon(polygon, offset, arcSegments).vertices;\n } else {\n return createPaddingPolygon(polygon, -offset, arcSegments).vertices;\n }\n}\n"], 5 | "mappings": "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", 6 | "names": [] 7 | } 8 | -------------------------------------------------------------------------------- /docs/demo/Demo.svelte.js: -------------------------------------------------------------------------------- 1 | import './Demo.svelte.css.proxy.js'; 2 | /* demo/Demo.svelte generated by Svelte v3.44.0 */ 3 | import { 4 | SvelteComponent, 5 | append, 6 | attr, 7 | binding_callbacks, 8 | check_outros, 9 | create_component, 10 | destroy_component, 11 | destroy_each, 12 | detach, 13 | element, 14 | empty, 15 | group_outros, 16 | init as init_1, 17 | insert, 18 | mount_component, 19 | safe_not_equal, 20 | space, 21 | svg_element, 22 | transition_in, 23 | transition_out 24 | } from "../snowpack/pkg/svelte/internal.js"; 25 | 26 | import { onMount } from '../snowpack/pkg/svelte.js'; 27 | import Draggable from './Draggable.svelte.js'; 28 | import Control from './Control.svelte.js'; 29 | import generatePolygon from './generate-polygon.js'; 30 | import isMobileDevice from './is-mobile-device.js'; 31 | import offsetPolygon from '../src/offset-polygon.js'; 32 | 33 | function get_each_context(ctx, list, i) { 34 | const child_ctx = ctx.slice(); 35 | child_ctx[21] = list[i]; 36 | child_ctx[23] = i; 37 | return child_ctx; 38 | } 39 | 40 | function get_each_context_1(ctx, list, i) { 41 | const child_ctx = ctx.slice(); 42 | child_ctx[21] = list[i]; 43 | return child_ctx; 44 | } 45 | 46 | // (123:2) {#if size} 47 | function create_if_block(ctx) { 48 | let path; 49 | let path_d_value; 50 | let if_block0_anchor; 51 | let if_block1_anchor; 52 | let each0_anchor; 53 | let each1_anchor; 54 | let current; 55 | let if_block0 = /*padding*/ ctx[5] > 0 && create_if_block_2(ctx); 56 | let if_block1 = /*margin*/ ctx[7] > 0 && create_if_block_1(ctx); 57 | let each_value_1 = /*p*/ ctx[2]; 58 | let each_blocks_1 = []; 59 | 60 | for (let i = 0; i < each_value_1.length; i += 1) { 61 | each_blocks_1[i] = create_each_block_1(get_each_context_1(ctx, each_value_1, i)); 62 | } 63 | 64 | let each_value = /*p*/ ctx[2]; 65 | let each_blocks = []; 66 | 67 | for (let i = 0; i < each_value.length; i += 1) { 68 | each_blocks[i] = create_each_block(get_each_context(ctx, each_value, i)); 69 | } 70 | 71 | const out = i => transition_out(each_blocks[i], 1, 1, () => { 72 | each_blocks[i] = null; 73 | }); 74 | 75 | return { 76 | c() { 77 | path = svg_element("path"); 78 | if (if_block0) if_block0.c(); 79 | if_block0_anchor = empty(); 80 | if (if_block1) if_block1.c(); 81 | if_block1_anchor = empty(); 82 | 83 | for (let i = 0; i < each_blocks_1.length; i += 1) { 84 | each_blocks_1[i].c(); 85 | } 86 | 87 | each0_anchor = empty(); 88 | 89 | for (let i = 0; i < each_blocks.length; i += 1) { 90 | each_blocks[i].c(); 91 | } 92 | 93 | each1_anchor = empty(); 94 | attr(path, "fill", "none"); 95 | attr(path, "d", path_d_value = "M " + /*p*/ ctx[2].map(func).join(' L ') + " Z"); 96 | attr(path, "stroke", "silver"); 97 | }, 98 | m(target, anchor) { 99 | insert(target, path, anchor); 100 | if (if_block0) if_block0.m(target, anchor); 101 | insert(target, if_block0_anchor, anchor); 102 | if (if_block1) if_block1.m(target, anchor); 103 | insert(target, if_block1_anchor, anchor); 104 | 105 | for (let i = 0; i < each_blocks_1.length; i += 1) { 106 | each_blocks_1[i].m(target, anchor); 107 | } 108 | 109 | insert(target, each0_anchor, anchor); 110 | 111 | for (let i = 0; i < each_blocks.length; i += 1) { 112 | each_blocks[i].m(target, anchor); 113 | } 114 | 115 | insert(target, each1_anchor, anchor); 116 | current = true; 117 | }, 118 | p(ctx, dirty) { 119 | if (!current || dirty & /*p*/ 4 && path_d_value !== (path_d_value = "M " + /*p*/ ctx[2].map(func).join(' L ') + " Z")) { 120 | attr(path, "d", path_d_value); 121 | } 122 | 123 | if (/*padding*/ ctx[5] > 0) { 124 | if (if_block0) { 125 | if_block0.p(ctx, dirty); 126 | } else { 127 | if_block0 = create_if_block_2(ctx); 128 | if_block0.c(); 129 | if_block0.m(if_block0_anchor.parentNode, if_block0_anchor); 130 | } 131 | } else if (if_block0) { 132 | if_block0.d(1); 133 | if_block0 = null; 134 | } 135 | 136 | if (/*margin*/ ctx[7] > 0) { 137 | if (if_block1) { 138 | if_block1.p(ctx, dirty); 139 | } else { 140 | if_block1 = create_if_block_1(ctx); 141 | if_block1.c(); 142 | if_block1.m(if_block1_anchor.parentNode, if_block1_anchor); 143 | } 144 | } else if (if_block1) { 145 | if_block1.d(1); 146 | if_block1 = null; 147 | } 148 | 149 | if (dirty & /*p*/ 4) { 150 | each_value_1 = /*p*/ ctx[2]; 151 | let i; 152 | 153 | for (i = 0; i < each_value_1.length; i += 1) { 154 | const child_ctx = get_each_context_1(ctx, each_value_1, i); 155 | 156 | if (each_blocks_1[i]) { 157 | each_blocks_1[i].p(child_ctx, dirty); 158 | } else { 159 | each_blocks_1[i] = create_each_block_1(child_ctx); 160 | each_blocks_1[i].c(); 161 | each_blocks_1[i].m(each0_anchor.parentNode, each0_anchor); 162 | } 163 | } 164 | 165 | for (; i < each_blocks_1.length; i += 1) { 166 | each_blocks_1[i].d(1); 167 | } 168 | 169 | each_blocks_1.length = each_value_1.length; 170 | } 171 | 172 | if (dirty & /*p, onDrag*/ 516) { 173 | each_value = /*p*/ ctx[2]; 174 | let i; 175 | 176 | for (i = 0; i < each_value.length; i += 1) { 177 | const child_ctx = get_each_context(ctx, each_value, i); 178 | 179 | if (each_blocks[i]) { 180 | each_blocks[i].p(child_ctx, dirty); 181 | transition_in(each_blocks[i], 1); 182 | } else { 183 | each_blocks[i] = create_each_block(child_ctx); 184 | each_blocks[i].c(); 185 | transition_in(each_blocks[i], 1); 186 | each_blocks[i].m(each1_anchor.parentNode, each1_anchor); 187 | } 188 | } 189 | 190 | group_outros(); 191 | 192 | for (i = each_value.length; i < each_blocks.length; i += 1) { 193 | out(i); 194 | } 195 | 196 | check_outros(); 197 | } 198 | }, 199 | i(local) { 200 | if (current) return; 201 | 202 | for (let i = 0; i < each_value.length; i += 1) { 203 | transition_in(each_blocks[i]); 204 | } 205 | 206 | current = true; 207 | }, 208 | o(local) { 209 | each_blocks = each_blocks.filter(Boolean); 210 | 211 | for (let i = 0; i < each_blocks.length; i += 1) { 212 | transition_out(each_blocks[i]); 213 | } 214 | 215 | current = false; 216 | }, 217 | d(detaching) { 218 | if (detaching) detach(path); 219 | if (if_block0) if_block0.d(detaching); 220 | if (detaching) detach(if_block0_anchor); 221 | if (if_block1) if_block1.d(detaching); 222 | if (detaching) detach(if_block1_anchor); 223 | destroy_each(each_blocks_1, detaching); 224 | if (detaching) detach(each0_anchor); 225 | destroy_each(each_blocks, detaching); 226 | if (detaching) detach(each1_anchor); 227 | } 228 | }; 229 | } 230 | 231 | // (130:4) {#if padding > 0} 232 | function create_if_block_2(ctx) { 233 | let path; 234 | let path_d_value; 235 | 236 | return { 237 | c() { 238 | path = svg_element("path"); 239 | attr(path, "fill", "none"); 240 | attr(path, "d", path_d_value = "M " + /*p1*/ ctx[3].map(func_1).join(' L ') + " Z"); 241 | attr(path, "stroke", "#e74c3c"); 242 | }, 243 | m(target, anchor) { 244 | insert(target, path, anchor); 245 | }, 246 | p(ctx, dirty) { 247 | if (dirty & /*p1*/ 8 && path_d_value !== (path_d_value = "M " + /*p1*/ ctx[3].map(func_1).join(' L ') + " Z")) { 248 | attr(path, "d", path_d_value); 249 | } 250 | }, 251 | d(detaching) { 252 | if (detaching) detach(path); 253 | } 254 | }; 255 | } 256 | 257 | // (138:4) {#if margin > 0} 258 | function create_if_block_1(ctx) { 259 | let path; 260 | let path_d_value; 261 | 262 | return { 263 | c() { 264 | path = svg_element("path"); 265 | attr(path, "fill", "none"); 266 | attr(path, "d", path_d_value = "M " + /*p2*/ ctx[4].map(func_2).join(' L ') + " Z"); 267 | attr(path, "stroke", "#3498db"); 268 | }, 269 | m(target, anchor) { 270 | insert(target, path, anchor); 271 | }, 272 | p(ctx, dirty) { 273 | if (dirty & /*p2*/ 16 && path_d_value !== (path_d_value = "M " + /*p2*/ ctx[4].map(func_2).join(' L ') + " Z")) { 274 | attr(path, "d", path_d_value); 275 | } 276 | }, 277 | d(detaching) { 278 | if (detaching) detach(path); 279 | } 280 | }; 281 | } 282 | 283 | // (146:4) {#each p as point} 284 | function create_each_block_1(ctx) { 285 | let circle; 286 | let circle_cx_value; 287 | let circle_cy_value; 288 | 289 | return { 290 | c() { 291 | circle = svg_element("circle"); 292 | attr(circle, "fill", "silver"); 293 | attr(circle, "r", "3"); 294 | attr(circle, "cx", circle_cx_value = /*point*/ ctx[21].x); 295 | attr(circle, "cy", circle_cy_value = /*point*/ ctx[21].y); 296 | }, 297 | m(target, anchor) { 298 | insert(target, circle, anchor); 299 | }, 300 | p(ctx, dirty) { 301 | if (dirty & /*p*/ 4 && circle_cx_value !== (circle_cx_value = /*point*/ ctx[21].x)) { 302 | attr(circle, "cx", circle_cx_value); 303 | } 304 | 305 | if (dirty & /*p*/ 4 && circle_cy_value !== (circle_cy_value = /*point*/ ctx[21].y)) { 306 | attr(circle, "cy", circle_cy_value); 307 | } 308 | }, 309 | d(detaching) { 310 | if (detaching) detach(circle); 311 | } 312 | }; 313 | } 314 | 315 | // (150:4) {#each p as point, index} 316 | function create_each_block(ctx) { 317 | let draggable; 318 | let current; 319 | 320 | function func_3(...args) { 321 | return /*func_3*/ ctx[14](/*index*/ ctx[23], ...args); 322 | } 323 | 324 | draggable = new Draggable({ 325 | props: { 326 | left: /*point*/ ctx[21].x, 327 | top: /*point*/ ctx[21].y, 328 | onChange: func_3 329 | } 330 | }); 331 | 332 | return { 333 | c() { 334 | create_component(draggable.$$.fragment); 335 | }, 336 | m(target, anchor) { 337 | mount_component(draggable, target, anchor); 338 | current = true; 339 | }, 340 | p(new_ctx, dirty) { 341 | ctx = new_ctx; 342 | const draggable_changes = {}; 343 | if (dirty & /*p*/ 4) draggable_changes.left = /*point*/ ctx[21].x; 344 | if (dirty & /*p*/ 4) draggable_changes.top = /*point*/ ctx[21].y; 345 | draggable.$set(draggable_changes); 346 | }, 347 | i(local) { 348 | if (current) return; 349 | transition_in(draggable.$$.fragment, local); 350 | current = true; 351 | }, 352 | o(local) { 353 | transition_out(draggable.$$.fragment, local); 354 | current = false; 355 | }, 356 | d(detaching) { 357 | destroy_component(draggable, detaching); 358 | } 359 | }; 360 | } 361 | 362 | function create_fragment(ctx) { 363 | let div; 364 | let h2; 365 | let t1; 366 | let control0; 367 | let t2; 368 | let control1; 369 | let t3; 370 | let control2; 371 | let t4; 372 | let control3; 373 | let t5; 374 | let svg; 375 | let svg_viewBox_value; 376 | let t6; 377 | let p_1; 378 | let current; 379 | 380 | control0 = new Control({ 381 | props: { 382 | max: "100", 383 | label: "Padding", 384 | value: /*padding*/ ctx[5], 385 | name: "padding", 386 | onChange: /*onPaddingChange*/ ctx[10] 387 | } 388 | }); 389 | 390 | control1 = new Control({ 391 | props: { 392 | label: "Padding arc segments", 393 | value: /*paddingArcSegments*/ ctx[6], 394 | name: "padding-arc-segments", 395 | onChange: /*onPaddingArcSegmentsChange*/ ctx[11] 396 | } 397 | }); 398 | 399 | control2 = new Control({ 400 | props: { 401 | max: "100", 402 | label: "Margin", 403 | value: /*margin*/ ctx[7], 404 | name: "margin", 405 | onChange: /*onMarginChange*/ ctx[12] 406 | } 407 | }); 408 | 409 | control3 = new Control({ 410 | props: { 411 | label: "Margin arc segments", 412 | value: /*marginArcSegments*/ ctx[8], 413 | name: "margin-arc-segments", 414 | onChange: /*onMarginArcSegmentsChange*/ ctx[13] 415 | } 416 | }); 417 | 418 | let if_block = /*size*/ ctx[0] && create_if_block(ctx); 419 | 420 | return { 421 | c() { 422 | div = element("div"); 423 | h2 = element("h2"); 424 | h2.textContent = "Interactive demo"; 425 | t1 = space(); 426 | create_component(control0.$$.fragment); 427 | t2 = space(); 428 | create_component(control1.$$.fragment); 429 | t3 = space(); 430 | create_component(control2.$$.fragment); 431 | t4 = space(); 432 | create_component(control3.$$.fragment); 433 | t5 = space(); 434 | svg = svg_element("svg"); 435 | if (if_block) if_block.c(); 436 | t6 = space(); 437 | p_1 = element("p"); 438 | p_1.textContent = "Try dragging the vertices."; 439 | attr(div, "class", "content"); 440 | attr(svg, "viewBox", svg_viewBox_value = "0 0 " + /*size*/ ctx[0] + " " + /*size*/ ctx[0]); 441 | attr(p_1, "class", "note svelte-zlj7hl"); 442 | }, 443 | m(target, anchor) { 444 | insert(target, div, anchor); 445 | append(div, h2); 446 | append(div, t1); 447 | mount_component(control0, div, null); 448 | append(div, t2); 449 | mount_component(control1, div, null); 450 | append(div, t3); 451 | mount_component(control2, div, null); 452 | append(div, t4); 453 | mount_component(control3, div, null); 454 | insert(target, t5, anchor); 455 | insert(target, svg, anchor); 456 | if (if_block) if_block.m(svg, null); 457 | /*svg_binding*/ ctx[15](svg); 458 | insert(target, t6, anchor); 459 | insert(target, p_1, anchor); 460 | current = true; 461 | }, 462 | p(ctx, [dirty]) { 463 | const control0_changes = {}; 464 | if (dirty & /*padding*/ 32) control0_changes.value = /*padding*/ ctx[5]; 465 | control0.$set(control0_changes); 466 | const control1_changes = {}; 467 | if (dirty & /*paddingArcSegments*/ 64) control1_changes.value = /*paddingArcSegments*/ ctx[6]; 468 | control1.$set(control1_changes); 469 | const control2_changes = {}; 470 | if (dirty & /*margin*/ 128) control2_changes.value = /*margin*/ ctx[7]; 471 | control2.$set(control2_changes); 472 | const control3_changes = {}; 473 | if (dirty & /*marginArcSegments*/ 256) control3_changes.value = /*marginArcSegments*/ ctx[8]; 474 | control3.$set(control3_changes); 475 | 476 | if (/*size*/ ctx[0]) { 477 | if (if_block) { 478 | if_block.p(ctx, dirty); 479 | 480 | if (dirty & /*size*/ 1) { 481 | transition_in(if_block, 1); 482 | } 483 | } else { 484 | if_block = create_if_block(ctx); 485 | if_block.c(); 486 | transition_in(if_block, 1); 487 | if_block.m(svg, null); 488 | } 489 | } else if (if_block) { 490 | group_outros(); 491 | 492 | transition_out(if_block, 1, 1, () => { 493 | if_block = null; 494 | }); 495 | 496 | check_outros(); 497 | } 498 | 499 | if (!current || dirty & /*size*/ 1 && svg_viewBox_value !== (svg_viewBox_value = "0 0 " + /*size*/ ctx[0] + " " + /*size*/ ctx[0])) { 500 | attr(svg, "viewBox", svg_viewBox_value); 501 | } 502 | }, 503 | i(local) { 504 | if (current) return; 505 | transition_in(control0.$$.fragment, local); 506 | transition_in(control1.$$.fragment, local); 507 | transition_in(control2.$$.fragment, local); 508 | transition_in(control3.$$.fragment, local); 509 | transition_in(if_block); 510 | current = true; 511 | }, 512 | o(local) { 513 | transition_out(control0.$$.fragment, local); 514 | transition_out(control1.$$.fragment, local); 515 | transition_out(control2.$$.fragment, local); 516 | transition_out(control3.$$.fragment, local); 517 | transition_out(if_block); 518 | current = false; 519 | }, 520 | d(detaching) { 521 | if (detaching) detach(div); 522 | destroy_component(control0); 523 | destroy_component(control1); 524 | destroy_component(control2); 525 | destroy_component(control3); 526 | if (detaching) detach(t5); 527 | if (detaching) detach(svg); 528 | if (if_block) if_block.d(); 529 | /*svg_binding*/ ctx[15](null); 530 | if (detaching) detach(t6); 531 | if (detaching) detach(p_1); 532 | } 533 | }; 534 | } 535 | 536 | const func = p => `${p.x} ${p.y}`; 537 | const func_1 = p => `${p.x} ${p.y}`; 538 | const func_2 = p => `${p.x} ${p.y}`; 539 | 540 | function instance($$self, $$props, $$invalidate) { 541 | let size = 0; 542 | let r; 543 | let center; 544 | let svgElement; 545 | let p; 546 | let p1; 547 | let p2; 548 | let padding = 15; 549 | let paddingArcSegments = 5; 550 | let margin = 15; 551 | let marginArcSegments = 5; 552 | 553 | const init = () => { 554 | $$invalidate(0, size = svgElement.clientWidth); 555 | r = size * 0.35; 556 | center = { x: size / 2, y: size / 2 }; 557 | $$invalidate(2, p = generatePolygon(6, r, center)); 558 | $$invalidate(3, p1 = offsetPolygon(p, -padding, paddingArcSegments)); 559 | $$invalidate(4, p2 = offsetPolygon(p, margin, marginArcSegments)); 560 | }; 561 | 562 | let windowHeight = window.innerHeight; 563 | let windowWidth = window.innerWidth; 564 | 565 | onMount(() => { 566 | init(); 567 | let resizeTimeout; 568 | 569 | function onResize() { 570 | if (!isMobileDevice || windowHeight !== window.innerHeight && windowWidth !== window.innerWidth) { 571 | clearTimeout(resizeTimeout); 572 | resizeTimeout = setTimeout(init, 250); 573 | } 574 | 575 | windowHeight = window.innerHeight; 576 | windowWidth = window.innerWidth; 577 | } 578 | 579 | window.addEventListener('resize', onResize); 580 | 581 | return () => { 582 | clearTimeout(resizeTimeout); 583 | window.removeEventListener('resize', onResize); 584 | }; 585 | }); 586 | 587 | let onDrag = (e, index) => { 588 | $$invalidate(2, p[index].x += e.movementX, p); 589 | $$invalidate(2, p[index].y += e.movementY, p); 590 | $$invalidate(3, p1 = offsetPolygon(p, -padding, paddingArcSegments)); 591 | $$invalidate(4, p2 = offsetPolygon(p, margin, marginArcSegments)); 592 | }; 593 | 594 | const onPaddingChange = e => { 595 | $$invalidate(5, padding = e.target.value); 596 | $$invalidate(3, p1 = offsetPolygon(p, -padding, paddingArcSegments)); 597 | }; 598 | 599 | const onPaddingArcSegmentsChange = e => { 600 | $$invalidate(6, paddingArcSegments = e.target.value); 601 | $$invalidate(3, p1 = offsetPolygon(p, -padding, paddingArcSegments)); 602 | }; 603 | 604 | const onMarginChange = e => { 605 | $$invalidate(7, margin = e.target.value); 606 | $$invalidate(4, p2 = offsetPolygon(p, margin, marginArcSegments)); 607 | }; 608 | 609 | const onMarginArcSegmentsChange = e => { 610 | $$invalidate(8, marginArcSegments = e.target.value); 611 | $$invalidate(4, p2 = offsetPolygon(p, margin, marginArcSegments)); 612 | }; 613 | 614 | const func_3 = (index, e) => onDrag(e, index); 615 | 616 | function svg_binding($$value) { 617 | binding_callbacks[$$value ? 'unshift' : 'push'](() => { 618 | svgElement = $$value; 619 | $$invalidate(1, svgElement); 620 | }); 621 | } 622 | 623 | return [ 624 | size, 625 | svgElement, 626 | p, 627 | p1, 628 | p2, 629 | padding, 630 | paddingArcSegments, 631 | margin, 632 | marginArcSegments, 633 | onDrag, 634 | onPaddingChange, 635 | onPaddingArcSegmentsChange, 636 | onMarginChange, 637 | onMarginArcSegmentsChange, 638 | func_3, 639 | svg_binding 640 | ]; 641 | } 642 | 643 | class Demo extends SvelteComponent { 644 | constructor(options) { 645 | super(); 646 | init_1(this, options, instance, create_fragment, safe_not_equal, {}); 647 | } 648 | } 649 | 650 | export default Demo; --------------------------------------------------------------------------------