├── .DS_Store ├── .babelrc ├── .gitignore ├── .npmrc ├── README.md ├── example ├── app.jsx └── index.html ├── index.jsx ├── package-lock.json ├── package.json └── webpack.config.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexSugak/react-svg-pathline/7b143d02bc9cf29a577169aea5af0e938f7e9031/.DS_Store -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015", {"modules": false}], 4 | "stage-2", 5 | "react" 6 | ], 7 | "plugins": [ 8 | "react-hot-loader/babel" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | bundle.js 40 | index.js -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry = https://registry.npmjs.org/ 2 | email=aleksandr.sugak@gmail.com 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-svg-pathline 2 | 3 | React component for drawing SVG path through set of points, smoothing the corners 4 | 5 | ## Why? 6 | 7 | SVG `polyline` is the simplest option for rendering "path" line through set of known points but it gives you a "broken" line with sharp corners. 8 | Using SVG `path` you can get smooth corners but it requires adding more points to original set. 9 | This component helps with rendering SVG path by generating correct SVG data from original set of points, producing "smooth path line" as result. 10 | 11 | So instead of this: 12 | 13 | ```html 14 | 15 | 20 | 21 | ``` 22 | 23 | ![polyline](https://cloud.githubusercontent.com/assets/2222587/16547319/27903e50-4172-11e6-86b6-4c4e3d3d6484.png) 24 | 25 | You get this: 26 | 27 | ```javascript 28 | import React from 'react' 29 | import {PathLine} from 'react-svg-pathline' 30 | 31 | export class MyComponent extends React.Component { 32 | render() ( 33 | 34 | 41 | 42 | ) 43 | } 44 | ``` 45 | 46 | ![pathline](https://cloud.githubusercontent.com/assets/2222587/16547326/5a1f4c80-4172-11e6-9892-6dbd9c6f27f1.png) 47 | 48 | ## Installation 49 | 50 | Requires [nodejs](http://nodejs.org/). 51 | 52 | ```sh 53 | $ npm install react-svg-pathline 54 | ``` 55 | 56 | ## Live Example 57 | 58 | ```sh 59 | $ npm i && npm i react react-dom && npm start 60 | ``` 61 | 62 | Open a browser at localhost:8080 -------------------------------------------------------------------------------- /example/app.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import {PathLine} from '../index' 4 | 5 | export const Example = () => ( 6 |
7 | 8 | 13 | 14 | 15 | 16 | 23 | 24 |
25 | ); 26 | 27 | ReactDOM.render( 28 | , 29 | document.getElementById('root'), 30 | ) -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React SVG path line example 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types'; 3 | 4 | const isCollinear = (p1, p2, p3) => { 5 | return (p1.y - p2.y) * (p1.x - p3.x) == (p1.y - p3.y) * (p1.x - p2.x); 6 | } 7 | 8 | const moveTo = (b, a, r) => { 9 | const vector = {x: b.x - a.x, y: b.y - a.y}; 10 | const length = Math.sqrt((vector.x * vector.x) + (vector.y * vector.y)); 11 | const unitVector = {x: vector.x / length, y: vector.y / length}; 12 | return {x: a.x + unitVector.x * r, y: a.y + unitVector.y * r}; 13 | } 14 | 15 | export const PathLine = ({ points, r, ...other }) => { 16 | const path = points 17 | .slice(1) 18 | .reduce((acc, p, i, points) => { 19 | let next = points[i + 1]; 20 | let prev = acc[acc.length - 1]; 21 | 22 | if (next && !isCollinear(prev.point, p, next)) { 23 | let before = moveTo(prev.point, p, r); 24 | let after = moveTo(next, p, r); 25 | return acc.concat({ 26 | point:p, 27 | s:`L ${before.x} ${before.y} S ${p.x} ${p.y} ${after.x} ${after.y} ` 28 | }) 29 | } else { 30 | return acc.concat({ 31 | point:p, 32 | s:`L ${p.x} ${p.y} ` 33 | }) 34 | }; 35 | } 36 | , [{ 37 | point: points[0], 38 | s: `M ${points[0].x} ${points[0].y} ` 39 | }]) 40 | .map(p => p.s) 41 | .join(''); 42 | return ( 43 | 44 | ) 45 | }; 46 | 47 | PathLine.propTypes = { 48 | points: PropTypes.array.isRequired, 49 | r: PropTypes.number.isRequired 50 | } 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-svg-pathline", 3 | "version": "0.6.0", 4 | "description": "React component for drawing SVG path through set of points, rendering straight lines where possible and smooth corners in other cases", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server", 8 | "build": "babel index.jsx -o index.js", 9 | "prepublish": "npm run build" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/AlexSugak/react-svg-pathline.git" 14 | }, 15 | "files": [ 16 | "index.js" 17 | ], 18 | "keywords": [ 19 | "react", 20 | "svg", 21 | "path", 22 | "line", 23 | "smooth" 24 | ], 25 | "author": "Alexandr Sugak", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/AlexSugak/react-svg-pathline/issues" 29 | }, 30 | "homepage": "https://github.com/AlexSugak/react-svg-pathline#readme", 31 | "devDependencies": { 32 | "babel-cli": "^6.10.1", 33 | "babel-core": "^6.10.4", 34 | "babel-loader": "^6.2.4", 35 | "babel-preset-es2015": "^6.9.0", 36 | "babel-preset-react": "^6.11.1", 37 | "babel-preset-stage-2": "^6.11.0", 38 | "json-loader": "^0.5.4", 39 | "react-hot-loader": "^3.1.3", 40 | "webpack": "^2.2.0", 41 | "webpack-dev-server": "^2.2.0" 42 | }, 43 | "peerDependencies": { 44 | "react": "^16.2.0 || ^17.0.0 || ^18.0.0", 45 | "react-dom": "^16.2.0 || ^17.0.0 || ^18.0.0" 46 | }, 47 | "dependencies": { 48 | "prop-types": "^15.5.8" 49 | } 50 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | 4 | module.exports = { 5 | entry: [ 6 | 'react-hot-loader/patch', 7 | 'webpack-dev-server/client?http://localhost:8080', 8 | 'webpack/hot/only-dev-server', 9 | './example/app.jsx' 10 | ], 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx?$/, 15 | use: [ 16 | 'babel-loader', 17 | ], 18 | exclude: /node_modules/, 19 | }, 20 | ], 21 | }, 22 | output: { 23 | path: __dirname + "/example", 24 | publicPath: '/', 25 | filename: 'bundle.js' 26 | }, 27 | devServer: { 28 | contentBase: './example', 29 | hot: true 30 | }, 31 | plugins: [ 32 | new webpack.HotModuleReplacementPlugin() 33 | ] 34 | }; --------------------------------------------------------------------------------