├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── package.json ├── src ├── getPoints.js ├── index.js └── toPath.js ├── test ├── getPoints.js └── toPath.js └── webpack.config.babel.js /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | lib/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .travis.yml 3 | src/ 4 | test/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: false 8 | node_js: 9 | - '5.9' 10 | before_install: 11 | - npm i -g npm@^3.8.0 12 | before_script: 13 | - npm prune 14 | script: 15 | - npm test 16 | after_success: 17 | - npm run semantic-release 18 | branches: 19 | except: 20 | - "/^v\\d+\\.\\d+\\.\\d+$/" 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Internet Systems Consortium license 2 | =================================== 3 | 4 | Copyright (c) `2016`, `Colin Meinke` 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any purpose 7 | with or without fee is hereby granted, provided that the above copyright notice 8 | and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 11 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 12 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 13 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 14 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 15 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 16 | THIS SOFTWARE. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **DEPRECIATED**: Moved to [svg-points](https://github.com/colinmeinke/svg-points). 2 | All further work will happen over there. 3 | 4 | # SVG shapes 5 | 6 | Get point data from SVG shapes. Convert point data to an SVG path. 7 | 8 | **1.9kb gzipped. No dependencies.** 9 | 10 | Shapes supported: 11 | 12 | - [Circle](#circle) 13 | - [Ellipse](#ellipse) 14 | - [Line](#line) 15 | - [Path](#path) 16 | - [Polygon](#polygon) 17 | - [Polyline](#polyline) 18 | - [Rect](#rect) 19 | - [Rect (with corner radius)](#rect-with-corner-radius) 20 | 21 | ## Installation 22 | 23 | ``` 24 | npm install svg-shapes 25 | ``` 26 | 27 | ## Usage 28 | 29 | ### Circle 30 | 31 | ```js 32 | import { getPoints, toPath } from 'svg-shapes'; 33 | 34 | const points = getPoints( 'circle', { 35 | cx: 50, 36 | cy: 50, 37 | r: 20, 38 | }); 39 | 40 | console.log( points ); 41 | 42 | // [ 43 | // { x: 50, y: 30 }, 44 | // { x: 50, y: 70, curve: { type: 'arc', rx: 20, ry: 20 }}, 45 | // { x: 50, y: 30, curve: { type: 'arc', rx: 20, ry: 20 }}, 46 | // ] 47 | 48 | const path = toPath( points ); 49 | 50 | console.log( path ); 51 | 52 | // 'M50,30A20,20,0,0,0,50,70A20,20,0,0,0,50,30Z' 53 | ``` 54 | 55 | ### Ellipse 56 | 57 | ```js 58 | import { getPoints, toPath } from 'svg-shapes'; 59 | 60 | const points = getPoints( 'ellipse', { 61 | cx: 100, 62 | cy: 300, 63 | rx: 65, 64 | ry: 120, 65 | }); 66 | 67 | console.log( points ); 68 | 69 | // [ 70 | // { x: 100, y: 180 }, 71 | // { x: 100, y: 420, curve: { type: 'arc', rx: 65, ry: 120 }}, 72 | // { x: 100, y: 180, curve: { type: 'arc', rx: 65, ry: 120 }}, 73 | // ] 74 | 75 | const path = toPath( points ); 76 | 77 | console.log( path ); 78 | 79 | // 'M100,180A65,120,0,0,0,100,420A65,120,0,0,0,100,180Z' 80 | ``` 81 | 82 | ### Line 83 | 84 | ```js 85 | import { getPoints, toPath } from 'svg-shapes'; 86 | 87 | const points = getPoints( 'line', { 88 | x1: 10, 89 | x2: 50, 90 | y1: 70, 91 | y2: 200, 92 | }); 93 | 94 | console.log( points ); 95 | 96 | // [ 97 | // { x: 10, y: 70 }, 98 | // { x: 50, y: 200 }, 99 | // ] 100 | 101 | const path = toPath( points ); 102 | 103 | console.log( path ); 104 | 105 | // 'M10,70L50,200' 106 | ``` 107 | 108 | ### Path 109 | 110 | ```js 111 | import { getPoints, toPath } from 'svg-shapes'; 112 | 113 | const points = getPoints( 'path', { 114 | d: 'M20,20h50v20A2,2,0,0,1,80,35L90,30H50V50a5,5,45,1,0,-5,-10l-5,-10Z', 115 | }); 116 | 117 | console.log( points ); 118 | 119 | // [ 120 | // { x: 20, y: 20 }, 121 | // { x: 70, y: 20 }, 122 | // { x: 70, y: 40 }, 123 | // { x: 80, y: 35, curve: { 124 | // type: 'arc', 125 | // rx: 2, 126 | // ry: 2, 127 | // sweepFlag: 1, 128 | // }}, 129 | // { x: 90, y: 30 }, 130 | // { x: 50, y: 30 }, 131 | // { x: 50, y: 50 }, 132 | // { x: 45, y: 40, curve: { 133 | // type: 'arc', 134 | // rx: 5, 135 | // ry: 5, 136 | // largeArcFlag: 1, 137 | // xAxisRotation: 45, 138 | // }}, 139 | // { x: 40, y: 30 }, 140 | // { x: 20, y: 20 }, 141 | // ] 142 | 143 | const path = toPath( points ); 144 | 145 | console.log( path ); 146 | 147 | // 'M20,20H70V40A2,2,0,0,1,80,35L90,30H50V50A5,5,45,1,0,45,40L40,30Z' 148 | ``` 149 | 150 | ### Polygon 151 | 152 | ```js 153 | import { getPoints, toPath } from 'svg-shapes'; 154 | 155 | const points = getPoints( 'polygon', { 156 | points: '20,30 50,90 20,90 50,30', 157 | }); 158 | 159 | console.log( points ); 160 | 161 | // [ 162 | // { x: 20, y: 30 }, 163 | // { x: 50, y: 90 }, 164 | // { x: 20, y: 90 }, 165 | // { x: 50, y: 30 }, 166 | // { x: 20, y: 30 }, 167 | // ] 168 | 169 | const path = toPath( points ); 170 | 171 | console.log( path ); 172 | 173 | // 'M20,30L50,90H20L50,30Z' 174 | ``` 175 | 176 | ### Polyline 177 | 178 | ```js 179 | import { getPoints, toPath } from 'svg-shapes'; 180 | 181 | const points = getPoints( 'polyline', { 182 | points: '20,30 50,90 20,90 50,30', 183 | }); 184 | 185 | console.log( points ); 186 | 187 | // [ 188 | // { x: 20, y: 30 }, 189 | // { x: 50, y: 90 }, 190 | // { x: 20, y: 90 }, 191 | // { x: 50, y: 30 }, 192 | // ] 193 | 194 | const path = toPath( points ); 195 | 196 | console.log( path ); 197 | 198 | // 'M20,30L50,90H20L50,30' 199 | ``` 200 | 201 | ### Rect 202 | 203 | ```js 204 | import { getPoints, toPath } from 'svg-shapes'; 205 | 206 | const points = getPoints( 'rect', { 207 | height: 20, 208 | width: 50, 209 | x: 10, 210 | y: 10, 211 | }); 212 | 213 | console.log( points ); 214 | 215 | // [ 216 | // { x: 10, y: 10 }, 217 | // { x: 60, y: 10 }, 218 | // { x: 60, y: 30 }, 219 | // { x: 10, y: 30 }, 220 | // { x: 10, y: 10 }, 221 | // ] 222 | 223 | const path = toPath( points ); 224 | 225 | console.log( path ); 226 | 227 | // 'M10,10H60V30H10Z' 228 | ``` 229 | 230 | ### Rect (with corner radius) 231 | 232 | ```js 233 | import { getPoints, toPath } from 'svg-shapes'; 234 | 235 | const points = getPoints( 'rect', { 236 | height: 200, 237 | rx: 5, 238 | ry: 10, 239 | width: 500, 240 | x: 50, 241 | y: 50, 242 | }); 243 | 244 | console.log( points ); 245 | 246 | // [ 247 | // { x: 55, y: 50 }, 248 | // { x: 545, y: 50 }, 249 | // { x: 550, y: 60, curve: { 250 | // type: 'arc', 251 | // rx: 5, 252 | // ry: 10, 253 | // sweepFlag: 1, 254 | // }}, 255 | // { x: 550, y: 240 }, 256 | // { x: 545, y: 250, curve: { 257 | // type: 'arc', 258 | // rx: 5, 259 | // ry: 10, 260 | // sweepFlag: 1, 261 | // }}, 262 | // { x: 55, y: 250 }, 263 | // { x: 50, y: 240, curve: { 264 | // type: 'arc', 265 | // rx: 5, 266 | // ry: 10, 267 | // sweepFlag: 1, 268 | // }}, 269 | // { x: 50, y: 60 }, 270 | // { x: 55, y: 50, curve: { 271 | // type: 'arc', 272 | // rx: 5, 273 | // ry: 10, 274 | // sweepFlag: 1, 275 | // }}, 276 | // ] 277 | 278 | const path = toPath( points ); 279 | 280 | console.log( path ); 281 | 282 | // 'M55,50H545A5,10,0,0,1,550,60V240A5,10,0,0,1,545,250H55A5,10,0,0,1,50,240V60A5,10,0,0,1,55,50Z' 283 | ``` 284 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "Colin Meinke", 4 | "email": "hello@colinmeinke.com", 5 | "url": "https://colinmeinke.com" 6 | }, 7 | "babel": { 8 | "plugins": [ 9 | "transform-object-rest-spread" 10 | ], 11 | "presets": [ 12 | "es2015" 13 | ] 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/colinmeinke/svg-shapes/issues" 17 | }, 18 | "config": { 19 | "commitizen": { 20 | "path": "node_modules/cz-conventional-changelog" 21 | } 22 | }, 23 | "description": "Get point data from SVG shapes. Convert point data to an SVG path", 24 | "devDependencies": { 25 | "babel-cli": "^6.6.5", 26 | "babel-core": "^6.7.4", 27 | "babel-loader": "^6.2.4", 28 | "babel-plugin-transform-object-rest-spread": "^6.6.5", 29 | "babel-preset-es2015": "^6.6.0", 30 | "commitizen": "^2.7.3", 31 | "cz-conventional-changelog": "^1.1.5", 32 | "expect": "^1.16.0", 33 | "mocha": "^2.4.5", 34 | "rimraf": "^2.5.2", 35 | "semantic-release": "^4.3.5", 36 | "webpack": "^1.12.14" 37 | }, 38 | "keywords": [ 39 | "convert", 40 | "path", 41 | "points", 42 | "shapes", 43 | "svg" 44 | ], 45 | "license": "ISC", 46 | "main": "lib/index.js", 47 | "name": "svg-shapes", 48 | "repository": { 49 | "type": "git", 50 | "url": "https://github.com/colinmeinke/svg-shapes" 51 | }, 52 | "scripts": { 53 | "build": "npm run build:lib && npm run build:umd", 54 | "build:lib": "babel src --out-dir lib", 55 | "build:umd": "npm run build:umd:dev && npm run build:umd:pro", 56 | "build:umd:dev": "webpack ./src/index.js ./dist/svg-shapes.js --output-library SVGShapes --output-library-target umd --config ./webpack.config.babel.js", 57 | "build:umd:pro": "webpack -p ./src/index.js ./dist/svg-shapes.min.js --output-library SVGShapes --output-library-target umd --config ./webpack.config.babel.js", 58 | "clean": "rimraf lib dist", 59 | "commit": "git-cz", 60 | "prepublish": "npm run clean && npm run build", 61 | "test": "mocha --compilers js:babel-core/register test/*.js", 62 | "semantic-release": "semantic-release pre && npm publish && semantic-release post" 63 | }, 64 | "version": "0.0.0-semantically-released" 65 | } 66 | -------------------------------------------------------------------------------- /src/getPoints.js: -------------------------------------------------------------------------------- 1 | const getPoints = ( type, attributes ) => { 2 | switch ( type ) { 3 | case 'circle': 4 | return getPointsFromCircle( attributes ); 5 | case 'ellipse': 6 | return getPointsFromEllipse( attributes ); 7 | case 'line': 8 | return getPointsFromLine( attributes ); 9 | case 'path': 10 | return getPointsFromPath( attributes ); 11 | case 'polygon': 12 | return getPointsFromPolygon( attributes ); 13 | case 'polyline': 14 | return getPointsFromPolyline( attributes ); 15 | case 'rect': 16 | return getPointsFromRect( attributes ); 17 | default: 18 | throw new Error( 'Not a valid shape type' ); 19 | } 20 | }; 21 | 22 | const getPointsFromCircle = ({ cx, cy, r }) => { 23 | return [ 24 | { x: cx, y: cy - r }, 25 | { x: cx, y: cy + r, curve: { type: 'arc', rx: r, ry: r }}, 26 | { x: cx, y: cy - r, curve: { type: 'arc', rx: r, ry: r }}, 27 | ]; 28 | }; 29 | 30 | const getPointsFromEllipse = ({ cx, cy, rx, ry }) => { 31 | return [ 32 | { x: cx, y: cy - ry }, 33 | { x: cx, y: cy + ry, curve: { type: 'arc', rx, ry }}, 34 | { x: cx, y: cy - ry, curve: { type: 'arc', rx, ry }}, 35 | ]; 36 | }; 37 | 38 | const getPointsFromLine = ({ x1, x2, y1, y2 }) => { 39 | return [ 40 | { x: x1, y: y1 }, 41 | { x: x2, y: y2 }, 42 | ]; 43 | }; 44 | 45 | const getPointsFromPath = ({ d }) => { 46 | const points = []; 47 | 48 | const instructions = d.split( /[^a-zA-Z]+/ ).filter( i => i.length ); 49 | const numbers = d.split( /[^\-0-9.]+/ ).map( parseFloat ).filter( n => !isNaN( n )); 50 | 51 | const optionalArcKeys = [ 'xAxisRotation', 'largeArcFlag', 'sweepFlag' ]; 52 | 53 | for ( let i = 0, l = instructions.length; i < l; i++ ) { 54 | const isFirstPoint = i === 0; 55 | const prevPoint = isFirstPoint ? null : points[ i - 1 ]; 56 | 57 | let relative = false; 58 | 59 | switch ( instructions[ i ]) { 60 | case 'm': 61 | case 'l': 62 | relative = true; 63 | 64 | case 'M': 65 | case 'L': 66 | points.push({ 67 | x: ( relative ? prevPoint.x : 0 ) + numbers.shift(), 68 | y: ( relative ? prevPoint.y : 0 ) + numbers.shift() 69 | }); 70 | 71 | break; 72 | 73 | case 'h': 74 | relative = true; 75 | 76 | case 'H': 77 | points.push({ 78 | x: ( relative ? prevPoint.x : 0 ) + numbers.shift(), 79 | y: prevPoint.y, 80 | }); 81 | 82 | break; 83 | 84 | case 'v': 85 | relative = true; 86 | 87 | case 'V': 88 | points.push({ 89 | x: prevPoint.x, 90 | y: ( relative ? prevPoint.y : 0 ) + numbers.shift(), 91 | }); 92 | 93 | break; 94 | 95 | case 'a': 96 | relative = true; 97 | 98 | case 'A': 99 | points.push({ 100 | curve: { 101 | type: 'arc', 102 | rx: numbers.shift(), 103 | ry: numbers.shift(), 104 | xAxisRotation: numbers.shift(), 105 | largeArcFlag: numbers.shift(), 106 | sweepFlag: numbers.shift(), 107 | }, 108 | x: ( relative ? prevPoint.x : 0 ) + numbers.shift(), 109 | y: ( relative ? prevPoint.y : 0 ) + numbers.shift(), 110 | }); 111 | 112 | for ( let k of optionalArcKeys ) { 113 | if ( points[ i ][ 'curve' ][ k ] === 0 ) { 114 | delete points[ i ][ 'curve' ][ k ]; 115 | } 116 | } 117 | 118 | break; 119 | 120 | case 'c': 121 | relative = true; 122 | 123 | case 'C': 124 | points.push({ 125 | curve: { 126 | type: 'cubic', 127 | x1: ( relative ? prevPoint.x : 0 ) + numbers.shift(), 128 | y1: ( relative ? prevPoint.y : 0 ) + numbers.shift(), 129 | x2: ( relative ? prevPoint.x : 0 ) + numbers.shift(), 130 | y2: ( relative ? prevPoint.y : 0 ) + numbers.shift(), 131 | }, 132 | x: ( relative ? prevPoint.x : 0 ) + numbers.shift(), 133 | y: ( relative ? prevPoint.y : 0 ) + numbers.shift(), 134 | }); 135 | 136 | break; 137 | 138 | case 's': 139 | relative = true; 140 | 141 | case 'S': 142 | const sx2 = ( relative ? prevPoint.x : 0 ) + numbers.shift(); 143 | const sy2 = ( relative ? prevPoint.y : 0 ) + numbers.shift(); 144 | const sx = ( relative ? prevPoint.x : 0 ) + numbers.shift(); 145 | const sy = ( relative ? prevPoint.y : 0 ) + numbers.shift(); 146 | 147 | const diff = {}; 148 | 149 | let sx1; 150 | let sy1; 151 | 152 | if ( prevPoint.curve && prevPoint.curve.type === 'cubic' ) { 153 | diff.x = Math.abs( prevPoint.x - prevPoint.curve.x2 ); 154 | diff.y = Math.abs( prevPoint.y - prevPoint.curve.y2 ); 155 | sx1 = prevPoint.x < prevPoint.curve.x2 ? prevPoint.x - diff.x : prevPoint.x + diff.x; 156 | sy1 = prevPoint.y < prevPoint.curve.y2 ? prevPoint.y - diff.y : prevPoint.y + diff.y; 157 | } else { 158 | diff.x = Math.abs( sx - sx2 ); 159 | diff.y = Math.abs( sy - sy2 ); 160 | sx1 = sx < sx2 ? prevPoint.x - diff.x : prevPoint.x + diff.x; 161 | sy1 = sy < sy2 ? prevPoint.y + diff.y : prevPoint.y - diff.y; 162 | } 163 | 164 | points.push({ curve: { type: 'cubic', x1: sx1, y1: sy1, x2: sx2, y2: sy2 }, x: sx, y: sy }); 165 | 166 | break; 167 | 168 | case 'q': 169 | relative = true; 170 | 171 | case 'Q': 172 | points.push({ 173 | curve: { 174 | type: 'quadratic', 175 | x1: ( relative ? prevPoint.x : 0 ) + numbers.shift(), 176 | y1: ( relative ? prevPoint.y : 0 ) + numbers.shift(), 177 | }, 178 | x: ( relative ? prevPoint.x : 0 ) + numbers.shift(), 179 | y: ( relative ? prevPoint.y : 0 ) + numbers.shift(), 180 | }); 181 | 182 | break; 183 | 184 | case 't': 185 | relative = true; 186 | 187 | case 'T': 188 | const tx = ( relative ? prevPoint.x : 0 ) + numbers.shift(); 189 | const ty = ( relative ? prevPoint.y : 0 ) + numbers.shift(); 190 | 191 | let tx1; 192 | let ty1; 193 | 194 | if ( prevPoint.curve && prevPoint.curve.type === 'quadratic' ) { 195 | const diff = { 196 | x: Math.abs( prevPoint.x - prevPoint.curve.x1 ), 197 | y: Math.abs( prevPoint.y - prevPoint.curve.y1 ), 198 | }; 199 | 200 | tx1 = prevPoint.x < prevPoint.curve.x1 ? prevPoint.x - diff.x : prevPoint.x + diff.x; 201 | ty1 = prevPoint.y < prevPoint.curve.y1 ? prevPoint.y - diff.y : prevPoint.y + diff.y; 202 | } else { 203 | tx1 = prevPoint.x; 204 | ty1 = prevPoint.y; 205 | } 206 | 207 | points.push({ curve: { type: 'quadratic', x1: tx1, y1: ty1 }, x: tx, y: ty }); 208 | 209 | break; 210 | 211 | case 'z': 212 | case 'Z': 213 | points.push({ x: points[ 0 ].x, y: points[ 0 ].y }); 214 | break; 215 | } 216 | } 217 | 218 | return points; 219 | }; 220 | 221 | const getPointsFromPolygon = ({ points }) => { 222 | return getPointsFromPoints({ closed: true, points }); 223 | }; 224 | 225 | const getPointsFromPolyline = ({ points }) => { 226 | return getPointsFromPoints({ closed: false, points }); 227 | }; 228 | 229 | const getPointsFromPoints = ({ closed, points }) => { 230 | const numbers = points.split( /[\s,]+/ ).map( n => parseFloat( n )); 231 | 232 | const p = numbers.reduce(( arr, point, i ) => { 233 | if ( i % 2 === 0 ) { 234 | arr.push({ x: point }); 235 | } else { 236 | arr[( i - 1 ) / 2 ].y = point; 237 | } 238 | 239 | return arr; 240 | }, []); 241 | 242 | if ( closed ) { 243 | p.push( p[ 0 ]); 244 | } 245 | 246 | return p; 247 | }; 248 | 249 | const getPointsFromRect = ({ height, rx, ry, width, x, y }) => { 250 | if ( rx || ry ) { 251 | return getPointsFromRectWithCornerRadius({ 252 | height, 253 | rx: rx ? rx : ry, 254 | ry: ry ? ry : rx, 255 | width, 256 | x, 257 | y, 258 | }); 259 | } 260 | 261 | return getPointsFromBasicRect({ height, width, x, y }); 262 | }; 263 | 264 | const getPointsFromBasicRect = ({ height, width, x, y }) => { 265 | return [ 266 | { x, y }, 267 | { x: x + width, y }, 268 | { x: x + width, y: y + height }, 269 | { x, y: y + height }, 270 | { x, y }, 271 | ]; 272 | }; 273 | 274 | const getPointsFromRectWithCornerRadius = ({ height, rx, ry, width, x, y }) => { 275 | const curve = { type: 'arc', rx, ry, sweepFlag: 1 }; 276 | 277 | return [ 278 | { x: x + rx, y }, 279 | { x: x + width - rx, y }, 280 | { x: x + width, y: y + ry, curve }, 281 | { x: x + width, y: y + height - ry }, 282 | { x: x + width - rx, y: y + height, curve }, 283 | { x: x + rx, y: y + height }, 284 | { x, y: y + height - ry, curve }, 285 | { x, y: y + ry }, 286 | { x: x + rx, y, curve }, 287 | ]; 288 | }; 289 | 290 | export default getPoints; 291 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import getPoints from './getPoints'; 2 | import toPath from './toPath'; 3 | 4 | export { getPoints, toPath }; 5 | -------------------------------------------------------------------------------- /src/toPath.js: -------------------------------------------------------------------------------- 1 | const toPath = points => { 2 | let d = ''; 3 | let i = 0; 4 | 5 | const firstPoint = points[ i ]; 6 | 7 | for ( let point of points ) { 8 | const isFirstPoint = i === 0; 9 | const isLastPoint = i === points.length - 1; 10 | const prevPoint = isFirstPoint ? null : points[ i - 1 ]; 11 | const { curve = false, x, y } = point; 12 | 13 | if ( isFirstPoint ) { 14 | d += `M${ x },${ y }`; 15 | } else if ( curve ) { 16 | switch ( curve.type ) { 17 | case 'arc': 18 | const { largeArcFlag = 0, rx, ry, sweepFlag = 0, xAxisRotation = 0 } = point.curve; 19 | d += `A${ rx },${ ry },${ xAxisRotation },${ largeArcFlag },${ sweepFlag },${ x },${ y }`; 20 | break; 21 | case 'cubic': 22 | const { x1: cx1, y1: cy1, x2: cx2, y2: cy2 } = point.curve; 23 | d += `C${ cx1 },${ cy1 },${ cx2 },${ cy2 },${ x },${ y }`; 24 | break; 25 | case 'quadratic': 26 | const { x1: qx1, y1: qy1 } = point.curve; 27 | d += `Q${ qx1 },${ qy1 },${ x },${ y }`; 28 | break; 29 | } 30 | 31 | if ( isLastPoint && x === firstPoint.x && y === firstPoint.y ) { 32 | d += 'Z'; 33 | } 34 | } else if ( isLastPoint && x === firstPoint.x && y === firstPoint.y ) { 35 | d += 'Z'; 36 | } else if ( x !== prevPoint.x && y !== prevPoint.y ) { 37 | d += `L${ x },${ y }`; 38 | } else if ( x !== prevPoint.x ) { 39 | d += `H${ x }`; 40 | } else if ( y !== prevPoint.y ) { 41 | d += `V${ y }`; 42 | } 43 | 44 | i++; 45 | } 46 | 47 | return d; 48 | }; 49 | 50 | export default toPath; 51 | -------------------------------------------------------------------------------- /test/getPoints.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import getPoints from '../src/getPoints'; 4 | 5 | describe( 'getPoints', () => { 6 | it( 'should return correct points of a circle', () => { 7 | const attributes = { cx: 50, cy: 50, r: 20 }; 8 | 9 | const expectedPoints = [ 10 | { x: 50, y: 30 }, 11 | { x: 50, y: 70, curve: { type: 'arc', rx: 20, ry: 20 }}, 12 | { x: 50, y: 30, curve: { type: 'arc', rx: 20, ry: 20 }}, 13 | ]; 14 | 15 | const points = getPoints( 'circle', attributes ); 16 | 17 | expect( points ).toEqual( expectedPoints ); 18 | }); 19 | 20 | it( 'should return correct points of an ellipse', () => { 21 | const attributes = { cx: 100, cy: 300, rx: 65, ry: 120 }; 22 | 23 | const expectedPoints = [ 24 | { x: 100, y: 180 }, 25 | { x: 100, y: 420, curve: { type: 'arc', rx: 65, ry: 120 }}, 26 | { x: 100, y: 180, curve: { type: 'arc', rx: 65, ry: 120 }}, 27 | ]; 28 | 29 | const points = getPoints( 'ellipse', attributes ); 30 | 31 | expect( points ).toEqual( expectedPoints ); 32 | }); 33 | 34 | it( 'should return correct points of a line', () => { 35 | const attributes = { x1: 10, x2: 50, y1: 70, y2: 200 }; 36 | 37 | const expectedPoints = [ 38 | { x: 10, y: 70 }, 39 | { x: 50, y: 200 }, 40 | ]; 41 | 42 | const points = getPoints( 'line', attributes ); 43 | 44 | expect( points ).toEqual( expectedPoints ); 45 | }); 46 | 47 | it( 'should return correct points of a path', () => { 48 | const attributes = { d: 'M20,20h50v20L90,30H50V50l-10,-20z' }; 49 | 50 | const expectedPoints = [ 51 | { x: 20, y: 20 }, 52 | { x: 70, y: 20 }, 53 | { x: 70, y: 40 }, 54 | { x: 90, y: 30 }, 55 | { x: 50, y: 30 }, 56 | { x: 50, y: 50 }, 57 | { x: 40, y: 30 }, 58 | { x: 20, y: 20 }, 59 | ]; 60 | 61 | const points = getPoints( 'path', attributes ); 62 | 63 | expect( points ).toEqual( expectedPoints ); 64 | }); 65 | 66 | it( 'should return correct points of a path (with arcs)', () => { 67 | const attributes = { d: 'M20,20h50v20A2,2,0,0,1,80,35L90,30H50V50a5,5,45,1,0,-5,-10l-5,-10Z' }; 68 | 69 | const expectedPoints = [ 70 | { x: 20, y: 20 }, 71 | { x: 70, y: 20 }, 72 | { x: 70, y: 40 }, 73 | { x: 80, y: 35, curve: { type: 'arc', rx: 2, ry: 2, sweepFlag: 1 }}, 74 | { x: 90, y: 30 }, 75 | { x: 50, y: 30 }, 76 | { x: 50, y: 50 }, 77 | { x: 45, y: 40, curve: { type: 'arc', rx: 5, ry: 5, largeArcFlag: 1, xAxisRotation: 45 }}, 78 | { x: 40, y: 30 }, 79 | { x: 20, y: 20 }, 80 | ]; 81 | 82 | const points = getPoints( 'path', attributes ); 83 | 84 | expect( points ).toEqual( expectedPoints ); 85 | }); 86 | 87 | it( 'should return correct points of a path (with cubic beziers)', () => { 88 | const attributes = { d: 'M20,20h50v20C70,45,80,40,80,35L90,30H50V50c5,-3,0,-7,-5,-10l-5,-10Z' }; 89 | 90 | const expectedPoints = [ 91 | { x: 20, y: 20 }, 92 | { x: 70, y: 20 }, 93 | { x: 70, y: 40 }, 94 | { x: 80, y: 35, curve: { type: 'cubic', x1: 70, y1: 45, x2: 80, y2: 40 }}, 95 | { x: 90, y: 30 }, 96 | { x: 50, y: 30 }, 97 | { x: 50, y: 50 }, 98 | { x: 45, y: 40, curve: { type: 'cubic', x1: 55, y1: 47, x2: 50, y2: 43 }}, 99 | { x: 40, y: 30 }, 100 | { x: 20, y: 20 }, 101 | ]; 102 | 103 | const points = getPoints( 'path', attributes ); 104 | 105 | expect( points ).toEqual( expectedPoints ); 106 | }); 107 | 108 | it( 'should return correct points of a path (with shorthand cubic beziers)', () => { 109 | const attributes = { d: 'M100,100S175,50,200,100s100,10,100,0' }; 110 | 111 | const expectedPoints = [ 112 | { x: 100, y: 100 }, 113 | { x: 200, y: 100, curve: { type: 'cubic', x1: 125, y1: 50, x2: 175, y2: 50 }}, 114 | { x: 300, y: 100, curve: { type: 'cubic', x1: 225, y1: 150, x2: 300, y2: 110 }}, 115 | ]; 116 | 117 | const points = getPoints( 'path', attributes ); 118 | 119 | expect( points ).toEqual( expectedPoints ); 120 | }); 121 | 122 | it( 'should return correct points of a path (with quadratic beziers)', () => { 123 | const attributes = { d: 'M20,20h50v20Q70,45,80,35L90,30H50V50q5,-3,-5,-10l-5,-10Z' }; 124 | 125 | const expectedPoints = [ 126 | { x: 20, y: 20 }, 127 | { x: 70, y: 20 }, 128 | { x: 70, y: 40 }, 129 | { x: 80, y: 35, curve: { type: 'quadratic', x1: 70, y1: 45 }}, 130 | { x: 90, y: 30 }, 131 | { x: 50, y: 30 }, 132 | { x: 50, y: 50 }, 133 | { x: 45, y: 40, curve: { type: 'quadratic', x1: 55, y1: 47 }}, 134 | { x: 40, y: 30 }, 135 | { x: 20, y: 20 }, 136 | ]; 137 | 138 | const points = getPoints( 'path', attributes ); 139 | 140 | expect( points ).toEqual( expectedPoints ); 141 | }); 142 | 143 | it( 'should return correct points of a path (with shorthand quadratic beziers)', () => { 144 | const attributes = { d: 'M300,400Q450,200,600,400T900,500t100,0' }; 145 | 146 | const expectedPoints = [ 147 | { x: 300, y: 400 }, 148 | { x: 600, y: 400, curve: { type: 'quadratic', x1: 450, y1: 200 }}, 149 | { x: 900, y: 500, curve: { type: 'quadratic', x1: 750, y1: 600 }}, 150 | { x: 1000, y: 500, curve: { type: 'quadratic', x1: 1050, y1: 400 }}, 151 | ]; 152 | 153 | const points = getPoints( 'path', attributes ); 154 | 155 | expect( points ).toEqual( expectedPoints ); 156 | }); 157 | 158 | it( 'should return correct points of a polygon', () => { 159 | const attributes = { points: '20,30 50,90 20,90 50,30' }; 160 | 161 | const expectedPoints = [ 162 | { x: 20, y: 30 }, 163 | { x: 50, y: 90 }, 164 | { x: 20, y: 90 }, 165 | { x: 50, y: 30 }, 166 | { x: 20, y: 30 }, 167 | ]; 168 | 169 | const points = getPoints( 'polygon', attributes ); 170 | 171 | expect( points ).toEqual( expectedPoints ); 172 | }); 173 | 174 | it( 'should return correct points of a polyline', () => { 175 | const attributes = { points: '20,30 50,90 20,90 50,30' }; 176 | 177 | const expectedPoints = [ 178 | { x: 20, y: 30 }, 179 | { x: 50, y: 90 }, 180 | { x: 20, y: 90 }, 181 | { x: 50, y: 30 }, 182 | ]; 183 | 184 | const points = getPoints( 'polyline', attributes ); 185 | 186 | expect( points ).toEqual( expectedPoints ); 187 | }); 188 | 189 | it( 'should return correct points of a rect', () => { 190 | const attributes = { height: 20, width: 50, x: 10, y: 10 }; 191 | 192 | const expectedPoints = [ 193 | { x: 10, y: 10 }, 194 | { x: 60, y: 10 }, 195 | { x: 60, y: 30 }, 196 | { x: 10, y: 30 }, 197 | { x: 10, y: 10 }, 198 | ]; 199 | 200 | const points = getPoints( 'rect', attributes ); 201 | 202 | expect( points ).toEqual( expectedPoints ); 203 | }); 204 | 205 | it( 'should return correct points of a rect (with corner radius)', () => { 206 | const attributes = { height: 200, rx: 5, ry: 10, width: 500, x: 50, y: 50 }; 207 | 208 | const expectedPoints = [ 209 | { x: 55, y: 50 }, 210 | { x: 545, y: 50 }, 211 | { x: 550, y: 60, curve: { type: 'arc', rx: 5, ry: 10, sweepFlag: 1 }}, 212 | { x: 550, y: 240 }, 213 | { x: 545, y: 250, curve: { type: 'arc', rx: 5, ry: 10, sweepFlag: 1 }}, 214 | { x: 55, y: 250 }, 215 | { x: 50, y: 240, curve: { type: 'arc', rx: 5, ry: 10, sweepFlag: 1 }}, 216 | { x: 50, y: 60 }, 217 | { x: 55, y: 50, curve: { type: 'arc', rx: 5, ry: 10, sweepFlag: 1 }}, 218 | ]; 219 | 220 | const points = getPoints( 'rect', attributes ); 221 | 222 | expect( points ).toEqual( expectedPoints ); 223 | }); 224 | }); 225 | -------------------------------------------------------------------------------- /test/toPath.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import toPath from '../src/toPath'; 4 | 5 | describe( 'toPath', () => { 6 | it( 'should return correct path from circle points', () => { 7 | const points = [ 8 | { x: 50, y: 30 }, 9 | { x: 50, y: 70, curve: { type: 'arc', rx: 20, ry: 20 }}, 10 | { x: 50, y: 30, curve: { type: 'arc', rx: 20, ry: 20 }}, 11 | ]; 12 | 13 | const expectedPath = 'M50,30A20,20,0,0,0,50,70A20,20,0,0,0,50,30Z'; 14 | 15 | const path = toPath( points ); 16 | 17 | expect( path ).toEqual( expectedPath ); 18 | }); 19 | 20 | it( 'should return correct path from ellipse points', () => { 21 | const points = [ 22 | { x: 100, y: 180 }, 23 | { x: 100, y: 420, curve: { type: 'arc', rx: 65, ry: 120 }}, 24 | { x: 100, y: 180, curve: { type: 'arc', rx: 65, ry: 120 }}, 25 | ]; 26 | 27 | const expectedPath = 'M100,180A65,120,0,0,0,100,420A65,120,0,0,0,100,180Z'; 28 | 29 | const path = toPath( points ); 30 | 31 | expect( path ).toEqual( expectedPath ); 32 | }); 33 | 34 | it( 'should return correct path from line points', () => { 35 | const points = [ 36 | { x: 10, y: 70 }, 37 | { x: 50, y: 200 }, 38 | ]; 39 | 40 | const expectedPath = 'M10,70L50,200'; 41 | 42 | const path = toPath( points ); 43 | 44 | expect( path ).toEqual( expectedPath ); 45 | }); 46 | 47 | it( 'should return correct path from path points', () => { 48 | const points = [ 49 | { x: 20, y: 20 }, 50 | { x: 70, y: 20 }, 51 | { x: 70, y: 40 }, 52 | { x: 90, y: 30 }, 53 | { x: 50, y: 30 }, 54 | { x: 50, y: 50 }, 55 | { x: 40, y: 30 }, 56 | { x: 20, y: 20 }, 57 | ]; 58 | 59 | const expectedPath = 'M20,20H70V40L90,30H50V50L40,30Z'; 60 | 61 | const path = toPath( points ); 62 | 63 | expect( path ).toEqual( expectedPath ); 64 | }); 65 | 66 | it( 'should return correct path from path points (with arcs)', () => { 67 | const points = [ 68 | { x: 20, y: 20 }, 69 | { x: 70, y: 20 }, 70 | { x: 70, y: 40 }, 71 | { x: 80, y: 35, curve: { type: 'arc', rx: 2, ry: 2, sweepFlag: 1 }}, 72 | { x: 90, y: 30 }, 73 | { x: 50, y: 30 }, 74 | { x: 50, y: 50 }, 75 | { x: 45, y: 40, curve: { type: 'arc', rx: 5, ry: 5, largeArcFlag: 1, xAxisRotation: 45 }}, 76 | { x: 40, y: 30 }, 77 | { x: 20, y: 20 }, 78 | ]; 79 | 80 | const expectedPath = 'M20,20H70V40A2,2,0,0,1,80,35L90,30H50V50A5,5,45,1,0,45,40L40,30Z'; 81 | 82 | const path = toPath( points ); 83 | 84 | expect( path ).toEqual( expectedPath ); 85 | }); 86 | 87 | it( 'should return correct path from path points (with cubic beziers)', () => { 88 | const points = [ 89 | { x: 20, y: 20 }, 90 | { x: 70, y: 20 }, 91 | { x: 70, y: 40 }, 92 | { x: 80, y: 35, curve: { type: 'cubic', x1: 70, y1: 45, x2: 80, y2: 40 }}, 93 | { x: 90, y: 30 }, 94 | { x: 50, y: 30 }, 95 | { x: 50, y: 50 }, 96 | { x: 45, y: 40, curve: { type: 'cubic', x1: 55, y1: 47, x2: 50, y2: 43 }}, 97 | { x: 40, y: 30 }, 98 | { x: 20, y: 20 }, 99 | ]; 100 | 101 | const expectedPath = 'M20,20H70V40C70,45,80,40,80,35L90,30H50V50C55,47,50,43,45,40L40,30Z'; 102 | 103 | const path = toPath( points ); 104 | 105 | expect( path ).toEqual( expectedPath ); 106 | }); 107 | 108 | it( 'should return correct path from path points (with shorthand cubic beziers)', () => { 109 | const points = [ 110 | { x: 100, y: 100 }, 111 | { x: 200, y: 100, curve: { type: 'cubic', x1: 125, y1: 50, x2: 175, y2: 50 }}, 112 | { x: 300, y: 100, curve: { type: 'cubic', x1: 225, y1: 150, x2: 300, y2: 110 }}, 113 | ]; 114 | 115 | const expectedPath = 'M100,100C125,50,175,50,200,100C225,150,300,110,300,100'; 116 | 117 | const path = toPath( points ); 118 | 119 | expect( path ).toEqual( expectedPath ); 120 | }); 121 | 122 | it( 'should return correct path from path points (with quadratic beziers)', () => { 123 | const points = [ 124 | { x: 20, y: 20 }, 125 | { x: 70, y: 20 }, 126 | { x: 70, y: 40 }, 127 | { x: 80, y: 35, curve: { type: 'quadratic', x1: 70, y1: 45 }}, 128 | { x: 90, y: 30 }, 129 | { x: 50, y: 30 }, 130 | { x: 50, y: 50 }, 131 | { x: 45, y: 40, curve: { type: 'quadratic', x1: 55, y1: 47 }}, 132 | { x: 40, y: 30 }, 133 | { x: 20, y: 20 }, 134 | ]; 135 | 136 | const expectedPath = 'M20,20H70V40Q70,45,80,35L90,30H50V50Q55,47,45,40L40,30Z'; 137 | 138 | const path = toPath( points ); 139 | 140 | expect( path ).toEqual( expectedPath ); 141 | }); 142 | 143 | it( 'should return correct path from path points (with shorthand quadratic beziers)', () => { 144 | const points = [ 145 | { x: 300, y: 400 }, 146 | { x: 600, y: 400, curve: { type: 'quadratic', x1: 450, y1: 200 }}, 147 | { x: 900, y: 500, curve: { type: 'quadratic', x1: 750, y1: 600 }}, 148 | { x: 1000, y: 500, curve: { type: 'quadratic', x1: 1050, y1: 400 }}, 149 | ]; 150 | 151 | const expectedPath = 'M300,400Q450,200,600,400Q750,600,900,500Q1050,400,1000,500'; 152 | 153 | const path = toPath( points ); 154 | 155 | expect( path ).toEqual( expectedPath ); 156 | }); 157 | 158 | it( 'should return correct path from polyline points', () => { 159 | const points = [ 160 | { x: 20, y: 30 }, 161 | { x: 50, y: 90 }, 162 | { x: 20, y: 90 }, 163 | { x: 50, y: 30 }, 164 | ]; 165 | 166 | const expectedPath = 'M20,30L50,90H20L50,30'; 167 | 168 | const path = toPath( points ); 169 | 170 | expect( path ).toEqual( expectedPath ); 171 | }); 172 | 173 | it( 'should return correct path from polygon points', () => { 174 | const points = [ 175 | { x: 20, y: 30 }, 176 | { x: 50, y: 90 }, 177 | { x: 20, y: 90 }, 178 | { x: 50, y: 30 }, 179 | { x: 20, y: 30 }, 180 | ]; 181 | 182 | const expectedPath = 'M20,30L50,90H20L50,30Z'; 183 | 184 | const path = toPath( points ); 185 | 186 | expect( path ).toEqual( expectedPath ); 187 | }); 188 | 189 | it( 'should return correct path from rect points', () => { 190 | const points = [ 191 | { x: 10, y: 10 }, 192 | { x: 60, y: 10 }, 193 | { x: 60, y: 30 }, 194 | { x: 10, y: 30 }, 195 | { x: 10, y: 10 }, 196 | ]; 197 | 198 | const expectedPath = 'M10,10H60V30H10Z'; 199 | 200 | const path = toPath( points ); 201 | 202 | expect( path ).toEqual( expectedPath ); 203 | }); 204 | 205 | it( 'should return correct path from rect points (with corner radius)', () => { 206 | const points = [ 207 | { x: 55, y: 50 }, 208 | { x: 545, y: 50 }, 209 | { x: 550, y: 60, curve: { type: 'arc', rx: 5, ry: 10, sweepFlag: 1 }}, 210 | { x: 550, y: 240 }, 211 | { x: 545, y: 250, curve: { type: 'arc', rx: 5, ry: 10, sweepFlag: 1 }}, 212 | { x: 55, y: 250 }, 213 | { x: 50, y: 240, curve: { type: 'arc', rx: 5, ry: 10, sweepFlag: 1 }}, 214 | { x: 50, y: 60 }, 215 | { x: 55, y: 50, curve: { type: 'arc', rx: 5, ry: 10, sweepFlag: 1 }}, 216 | ]; 217 | 218 | const expectedPath = 'M55,50H545A5,10,0,0,1,550,60V240A5,10,0,0,1,545,250H55A5,10,0,0,1,50,240V60A5,10,0,0,1,55,50Z'; 219 | 220 | const path = toPath( points ); 221 | 222 | expect( path ).toEqual( expectedPath ); 223 | }); 224 | }); 225 | -------------------------------------------------------------------------------- /webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | export default { 2 | module: { 3 | loaders: [{ 4 | exclude: /node_modules/, 5 | loader: 'babel', 6 | test: /\.js$/, 7 | }], 8 | }, 9 | }; 10 | --------------------------------------------------------------------------------