├── .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 |
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 | [](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 |
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;
--------------------------------------------------------------------------------