Skip to content

Instantly share code, notes, and snippets.

@hugs
Forked from elidupuis/README.md
Last active December 28, 2016 19:16
Show Gist options
  • Save hugs/9aaa97c20f5dc4aac1a5 to your computer and use it in GitHub Desktop.
Save hugs/9aaa97c20f5dc4aac1a5 to your computer and use it in GitHub Desktop.
Simple Drawing Example

Simple Drawing Example

Click and drag your mouse (or finger on touch device) to draw a line. A new path is created for each touch or click.

Paths are stored in a nested array; you can inspect the variable in console: session (assuming the example is in it's own window).

<!doctype html>
<html lang="en">
<head lang=en>
<meta charset="utf-8">
<title>Tracing a line with d3.js</title>
<style>
svg {
background: #ddd;
font: 10px sans-serif;
cursor: crosshair;
}
.line {
cursor: crosshair;
fill: none;
stroke: #000;
stroke-width: 10px;
stroke-linejoin: round;
stroke-linecap: round;
}
#output {
position: relative;
top: -2em;
left: 0.67em;
font: 12px/1.4 monospace;
}
</style>
</head>
<body>
<div id="sketch"></div>
<div id="output"></div>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="simplify.js"></script>
<script>
// based on http://bl.ocks.org/cloudshapes/5661984 by cloudshapes
var margin = {top: 0, right: 0, bottom: 0, left: 0},
width = 960 - margin.left - margin.right - 200,
height = 500 - margin.top - margin.bottom;
// var npoints = 100;
var ptdata = [];
var session = [];
var path;
var drawing = false;
var output = d3.select('#output');
var line = d3.svg.line()
.interpolate("bundle") // basis, see http://bl.ocks.org/mbostock/4342190
.tension(1)
.x(function(d, i) { return d.x; })
.y(function(d, i) { return d.y; });
var svg = d3.select("#sketch").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg
.on("mousedown", listen)
.on("touchstart", listen)
.on("touchend", ignore)
.on("touchleave", ignore)
.on("mouseup", ignore)
.on("mouseleave", ignore);
// ignore default touch behavior
var touchEvents = ['touchstart', 'touchmove', 'touchend'];
touchEvents.forEach(function (eventName) {
document.body.addEventListener(eventName, function(e){
e.preventDefault();
});
});
function listen () {
drawing = true;
output.text('event: ' + d3.event.type);
ptdata = []; // reset point data
path = svg.append("path") // start a new line
.data([ptdata])
.attr("class", "line")
.attr("d", line);
if (d3.event.type === 'mousedown') {
svg.on("mousemove", onmove);
} else {
svg.on("touchmove", onmove);
}
}
function ignore () {
var before, after;
output.text('event: ' + d3.event.type);
svg.on("mousemove", null);
svg.on("touchmove", null);
// skip out if we're not drawing
if (!drawing) return;
drawing = false;
before = ptdata.length;
console.group('Line Simplification');
console.log("Before simplification:", before)
// simplify
ptdata = simplify(ptdata);
after = ptdata.length;
console.log("After simplification:", ptdata.length)
console.groupEnd();
var percentage = parseInt(100 - (after/before)*100, 10);
output.html('Points: ' + before + ' => ' + after + '. <b>' + percentage + '% simplification.</b>');
// add newly created line to the drawing session
session.push(ptdata);
// redraw the line after simplification
tick();
}
function onmove (e) {
var type = d3.event.type;
var point;
if (type === 'mousemove') {
point = d3.mouse(this);
output.text('event: ' + type + ': ' + d3.mouse(this));
} else {
// only deal with a single touch input
point = d3.touches(this)[0];
output.text('event: ' + type + ': ' + d3.touches(this)[0]);
}
// push a new data point onto the back
ptdata.push({ x: point[0], y: point[1] });
tick();
}
function tick() {
path.attr("d", function(d) { return line(d); }) // Redraw the path:
}
</script>
</body>
</html>
/*
(c) 2013, 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;
}
// simplification using optimized Douglas-Peucker algorithm with recursion elimination
function simplifyDouglasPeucker(points, sqTolerance) {
var len = points.length,
MarkerArray = typeof Uint8Array !== 'undefined' ? Uint8Array : Array,
markers = new MarkerArray(len),
first = 0,
last = len - 1,
stack = [],
newPoints = [],
i, maxSqDist, sqDist, index;
markers[first] = markers[last] = 1;
while (last) {
maxSqDist = 0;
for (i = first + 1; i < last; i++) {
sqDist = getSqSegDist(points[i], points[first], points[last]);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
markers[index] = 1;
stack.push(first, index, index, last);
}
last = stack.pop();
first = stack.pop();
}
for (i = 0; i < len; i++) {
if (markers[i]) newPoints.push(points[i]);
}
return newPoints;
}
// both algorithms combined for awesome performance
function simplify(points, tolerance, highestQuality) {
if (points.length <= 1) 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;
else if (typeof self !== 'undefined') self.simplify = simplify;
else window.simplify = simplify;
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment