<script src="https://unpkg.com/konva@8/konva.min.js"></script>
<p>Hold left mouse button and drag to add a line with multiple points <br />
Red line shows mouse points, blue line shows simplified points. </p>
<p><b>Try a long stright line to see the simplification effect</b></p>
<p><button id='reset'>Reset</button></p>
<p><span id='info'>.</span></p>
<div id="container"></div>
body {
margin: 20px;
padding: 0;
overflow: hidden;
background-color: #f0f0f0;
}
#container {
border: 1px solid silver;
width: 600px;
height: 200px;
}
/*
(c) 2017, Vladimir Agafonkin
Simplify.js, a high-performance JS polyline simplification library
mourner.github.io/simplify-js
*/
(function () { 'use strict';
// to suit your point format, run search/replace for '.x' and '.y';
// for 3D version, see 3d branch (configurability would draw significant performance overhead)
// square distance between 2 points
function getSqDist(p1, p2) {
var dx = p1.x - p2.x,
dy = p1.y - p2.y;
return dx * dx + dy * dy;
}
// square distance from a point to a segment
function getSqSegDist(p, p1, p2) {
var x = p1.x,
y = p1.y,
dx = p2.x - x,
dy = p2.y - y;
if (dx !== 0 || dy !== 0) {
var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);
if (t > 1) {
x = p2.x;
y = p2.y;
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}
dx = p.x - x;
dy = p.y - y;
return dx * dx + dy * dy;
}
// rest of the code doesn't care about point format
// basic distance-based simplification
function simplifyRadialDist(points, sqTolerance) {
var prevPoint = points[0],
newPoints = [prevPoint],
point;
for (var i = 1, len = points.length; i < len; i++) {
point = points[i];
if (getSqDist(point, prevPoint) > sqTolerance) {
newPoints.push(point);
prevPoint = point;
}
}
if (prevPoint !== point) newPoints.push(point);
return newPoints;
}
function simplifyDPStep(points, first, last, sqTolerance, simplified) {
var maxSqDist = sqTolerance,
index;
for (var i = first + 1; i < last; i++) {
var sqDist = getSqSegDist(points[i], points[first], points[last]);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
if (index - first > 1) simplifyDPStep(points, first, index, sqTolerance, simplified);
simplified.push(points[index]);
if (last - index > 1) simplifyDPStep(points, index, last, sqTolerance, simplified);
}
}
// simplification using Ramer-Douglas-Peucker algorithm
function simplifyDouglasPeucker(points, sqTolerance) {
var last = points.length - 1;
var simplified = [points[0]];
simplifyDPStep(points, 0, last, sqTolerance, simplified);
simplified.push(points[last]);
return simplified;
}
// both algorithms combined for awesome performance
function simplify(points, tolerance, highestQuality) {
if (points.length <= 2) return points;
var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;
points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);
points = simplifyDouglasPeucker(points, sqTolerance);
return points;
}
// export as AMD module / Node module / browser or worker variable
if (typeof define === 'function' && define.amd) define(function() { return simplify; });
else if (typeof module !== 'undefined') {
module.exports = simplify;
module.exports.default = simplify;
} else if (typeof self !== 'undefined') self.simplify = simplify;
else window.simplify = simplify;
})();
/*
* From here onwards we set up the stage and its contents.
*/
const stage = new Konva.Stage({
container: 'container',
width: $('#container').innerWidth(),
height: $('#container').innerHeight()
}),
layer = new Konva.Layer(),
layer1 = new Konva.Layer();
let path, path1, dot, info = $('#info'), lineCnt = 0;
stage.add(layer, layer1);
let points = [], segments = [];
function addPoint(pt){
// store the new raw point
points.push(pt);
// Add a visible marker for the raw pt
const newDot = dot.clone({x: pt.x, y: pt.y});
layer.add(newDot)
// Make the drawing commands for the raw line
// convert to drawing commands, supported commands are M, m, L, l, H, h, V, v, Q, q, T, t, C, c, S, s, A, a, Z, z
let pathData = "", pathData1 = "";
for (let i = 0; i < points.length; i++){
let pt = points[i];
switch (i){
case 0:
pathData+="M " + pt.x + " " + pt.y;
break;
default:
pathData+="L " + pt.x + " " + pt.y;
break;
}
}
// remove the current simplified point markers ready to redraw after simplification
const kids = layer1.find('.dotline' + lineCnt);
for (let i = 0; i < kids.length; i++){
kids[i].remove();
}
// simplify the raw points to make an equiv line with fewer points.
const simplifiedPts = simplify(points);
info.html('Raw points ' + points.length + ', simplified to ' + simplifiedPts.length)
// Make the drawing commands for the simplified line
for (let i = 0; i < simplifiedPts.length; i++){
let pt = simplifiedPts[i];
switch (i){
case 0:
pathData1+="M " + pt.x + " " + pt.y;
break;
default:
pathData1+="L " + pt.x + " " + pt.y;
break;
}
const newDot1 = dot.clone({x: pt.x - 25, y: pt.y, name: 'dotline' + lineCnt});
layer1.add(newDot1)
}
// set the new path data into the paths objects.
path.data(pathData);
path1.data(pathData1);
}
// user ends drawing with mouse up
stage.on('mouseup', function (e) {
reset(false)
})
// user draws with mouse held down and moving
stage.on('mousemove', function (e) {
if (e.evt.buttons == 1){ // left btn only
const pt = stage.getPointerPosition();
addPoint(pt)
}
})
// reset button
$('#reset').on('click', function(){
reset(true)
})
// reset the stage and points lists
function reset(clear){
if (clear){
layer.removeChildren();
layer1.removeChildren();
}
points = [];
segments = [];
path = new Konva.Path({stroke: 'red', strokeWidth: 4});
dot = new Konva.Circle({strokeWidth: 2, stroke: 'lime', radius: 5});
path1 = new Konva.Path({stroke: 'blue', strokeWidth: 4, x: -25});
layer.add(path, path1);
lineCnt++;
}
reset(true);
This Pen doesn't use any external CSS resources.