library ejemplos javascript svg html5-canvas

javascript - ejemplos - Dibujar un polígono festoneado entre múltiples puntos



svg web (2)

Estoy tratando de dibujar un camino festoneado usando SVG entre varios puntos, como se dibuja para el rectángulo aquí, pero entre múltiples puntos. Esperando que dos o más dos o más puntos seleccionados estén conectados por una línea festoneada.

Pero los problemas que estoy enfrentando son

  1. Las vieiras no son simétricas ni tienen tamaños aleatorios. - Resolví esto
  2. Después de hacer clic en varias direcciones de las vieiras y hacia arriba. Como en la imagen de abajo

Estoy completamente bien, incluso si la respuesta se da en el contexto del lienzo html5. Haré ajustes. Me falta algo de cálculo adicional, pero no pude entender qué.

Haga clic varias veces en la página de resultados para ver las escalopes dibujadas actualmente

var strokeWidth = 3; function distance(x1, y1, x2, y2) { return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); } function findNewPoint(x, y, angle, distance) { var result = {}; result.x = Math.round(Math.cos(angle) * distance + x); result.y = Math.round(Math.sin(angle) * distance + y); return result; } function getAngle(x1, y1, x2, y2) { return Math.atan2(y2 - y1, x2 - x1); } function scapolledLine(points, strokeWidth) { var that = this; var scallopSize = strokeWidth * 8; var path = [], newP = null; path.push("M", points[0].x, points[0].y); points.forEach(function(s, i) { var stepW = scallopSize, lsw = 0; var e = points[i + 1]; if (!e) { path.push(''A'', stepW / 2, stepW / 2, "0 0 1", s.x, s.y); return; } var args = [s.x, s.y, e.x, e.y]; var dist = that.distance.apply(that, args); if (dist === 0) return; var angle = that.getAngle.apply(that, args); newP = s; // Number of possible scallops between current points var n = dist / stepW, crumb; if (dist < (stepW * 2)) { stepW = (dist - stepW) > (stepW * 0.38) ? (dist / 2) : dist; } else { n = (n - (n % 1)); crumb = dist - (n * stepW); /*if((stepW - crumb) > (stepW * 0.7)) { lsw = crumb; } else { stepW += (crumb / n); }*/ stepW += (crumb / n); } // Recalculate possible scallops. n = dist / stepW; var aw = stepW / 2; for (var i = 0; i < n; i++) { newP = that.findNewPoint(newP.x, newP.y, angle, stepW); if (i === (n - 1)) { aw = (lsw > 0 ? lsw : stepW) / 2; } path.push(''A'', aw, aw, "0 0 1", newP.x, newP.y); } // scallopSize = stepW; }); return path.join('' ''); // return path.join('' '') + (points.length > 3 ? ''z'' : ''''); } var points = []; var mouse = null; var dblclick = null, doneEnding = false; window.test.setAttribute(''stroke-width'', strokeWidth); function feed() { if (dblclick && doneEnding) return; if (!dblclick && (points.length > 0 && mouse)) { var arr = points.slice(0); arr.push(mouse); var str = scapolledLine(arr, strokeWidth); window.test.setAttribute(''d'', str); } else if (dblclick) { points.push(points[0]); doneEnding = true; var str = scapolledLine(points, strokeWidth); window.test.setAttribute(''d'', str); } } document.addEventListener(''mousedown'', function(event) { points.push({ x: event.clientX, y: event.clientY }); feed(); }); document.addEventListener(''dblclick'', function(event) { dblclick = true; feed(); }); document.addEventListener(''mousemove'', function(event) { if (points.length > 0) { mouse = { x: event.clientX, y: event.clientY } feed(); } });

body, html { height: 100%; width: 100%; margin: 0; padding: 0 } svg { height: 100%; width: 100% }

<svg id="svgP"> <path id="test" style="stroke: RGBA(212, 50, 105, 1.00); fill: none" /> </svg>


Encontrar círculo para caber 3 puntos

Este método usa una función que encuentra un círculo que se ajusta a 3 puntos. Dos de los puntos son el conjunto de puntos que tienes. El 3er punto se toma perpendicular a la línea entre los puntos y se mueve por un factor de la longitud de la línea seg.

Cuando se encuentra el círculo, se encuentra que el ángulo de inicio y fin del punto central del círculo hace que el segmento de arco y todo esté hecho. Simplemente dibuja los arcos con ctx.arc(

No estoy seguro exactamente de lo que quieres. Lo tengo así que todos los arcos se doblan, pero es fácil dar vueltas.

Si quieres que todos tengan el mismo tamaño, debes separar los puntos de la distancia igual, lo cual es muy simple, pero significa que es difícil adaptarse a un área determinada.

Manifestación

La demostración le permite agregar y arrastrar punto. La rueda del mouse cambia la profundidad del arco.

La const en la parte superior de arcDepth determina qué tan profundo se compara cada arco con la longitud del segmento de línea. Es una fracción

Puede hacer que sea una constante en píxeles. Consulte, calcArc , sobre cómo cambiar.

Cada arco tiene una profundidad independiente, por lo que si no te gustan los arcos superpuestos, reduce la profundidad de ese arco (en el código de curso).

Espero que ayude.

const pointSize = 4; const pointCol = "#4AF"; var arcDepth = -0.5; // depth of arc as a factor of line seg length // Note to have arc go the other (positive) way you have // to change the ctx.arc draw call by adding anticlockwise flag // see drawArc for more const arcCol = "#4FA"; const arcWidth = 3; // Find a circle that fits 3 points. function fitCircleTo3P(p1x, p1y, p2x, p2y, p3x, p3y, arc) { var vx, vy, c, c1, u; c = (p2x - p1x) / (p1y - p2y); // slope of vector from vec 1 to vec 2 c1 = (p3x - p2x) / (p2y - p3y); // slope of vector from vec 2 to vec 3 // This will not happen in this example if (c === c1) { // if slope is the same they must be on the same line return null; // points are in a line } // locate the center if (p1y === p2y) { // special case with p1 and p2 have same y vx = (p1x + p2x) / 2; vy = c1 * vx + (((p2y + p3y) / 2) - c1 * ((p2x + p3x) / 2)); } else if (p2y === p3y) { // special case with p2 and p3 have same y vx = (p2x + p3x) / 2; vy = c * vx + (((p1y + p2y) / 2) - c * ((p1x + p2x) / 2)); } else { vx = ((((p2y + p3y) / 2) - c1 * ((p2x + p3x) / 2)) - (u = ((p1y + p2y) / 2) - c * ((p1x + p2x) / 2))) / (c - c1); vy = c * vx + u; } arc.x = vx; arc.y = vy; vx = p1x - vx; vy = p1y - vy; arc.rad = Math.sqrt(vx * vx + vy * vy); return arc; } var points = []; var arcs = []; function addArc(p1, p2, depth) { // remove next 5 line if you dont want all arcs to face the same way. if(points[p1][0] > points[p2][0]){ var temp = p1; p1 = p2; p2 = temp; } var arc = { p1 : p1, p2 : p2, depth : depth, rad : null, // radius a1 : null, // angle from a2 : null, // angle to x : null, y : null, } arcs.push(arc); return arc; } function calcArc(arc, depth) { var p = points[arc.p1]; // get points var pp = points[arc.p2]; // change depth if needed depth = arc.depth = depth !== undefined ? depth : arc.depth; var vx = pp[0] - p[0]; // vector from p to pp var vy = pp[1] - p[1]; var cx = (pp[0] + p[0]) / 2; // center point var cy = (pp[1] + p[1]) / 2; // center point var len = Math.sqrt(vx * vx + vy * vy); // get length cx -= vy * depth; // find 3 point at 90 deg to line and dist depth cy += vx * depth; // To have depth as a fixed length uncomment 4 lines below and comment out 2 lines above. //var nx = vx / len; // normalise vector //var ny = vy / len; //cx -= ny * depth; // find 3 point at 90 deg to line and dist depth //cy += nx * depth; fitCircleTo3P(p[0], p[1], cx, cy, pp[0], pp[1], arc); // get the circle that fits arc.a1 = Math.atan2(p[1] - arc.y, p[0] - arc.x); // get angle from circle center to first point arc.a2 = Math.atan2(pp[1] - arc.y, pp[0] - arc.x); // get angle from circle center to second point } function addPoint(x, y) { points.push([x, y]); } function drawPoint(x, y, size, col) { ctx.fillStyle = col; ctx.beginPath(); ctx.arc(x, y, size, 0, Math.PI * 2); ctx.fill(); } function drawArc(arc, width, col) { ctx.lineCap = "round"; ctx.strokeStyle = col; ctx.lineWidth = width; ctx.beginPath(); ctx.arc(arc.x, arc.y, arc.rad, arc.a1, arc.a2,false); // true for anti clock wise ctx.stroke(); } function findClosestPoint(x, y, dist) { var index = -1; for (var i = 0; i < points.length; i++) { var p = points[i]; var vx = x - p[0]; var vy = y - p[1]; var d = Math.sqrt(vx * vx + vy * vy); if (d < dist) { dist = d; index = i; } } return index; } var dragging = false; var drag = -1; var dragX, dragY; var recalcArcs = false; function display() { ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform ctx.globalAlpha = 1; // reset alpha ctx.clearRect(0, 0, w, h); if(mouse.w > 0){ arcDepth *= 1.05; mouse.w = 0; recalcArcs = true; } if(mouse.w < 0){ arcDepth *= 1/1.05; mouse.w = 0; recalcArcs = true; } if (mouse.buttonRaw & 1) { if (!dragging) { var i = findClosestPoint(mouse.x, mouse.y, pointSize * 3); if (i > -1) { drag = i; dragging = true; dragX = mouse.x - points[drag][0]; dragY = mouse.y - points[drag][1]; } } if (dragging) { points[drag][0] = mouse.x - dragX points[drag][1] = mouse.y - dragY recalcArcs = true; } else { addPoint(mouse.x, mouse.y); if (points.length > 1) { calcArc(addArc(points.length - 2, points.length - 1, arcDepth)); } mouse.buttonRaw = 0; } } else { if (dragging) { dragging = false; drag = -1; recalcArcs = true; } var i = findClosestPoint(mouse.x, mouse.y, pointSize * 3); if (i > -1) { canvas.style.cursor = "move"; } else { canvas.style.cursor = "default"; } } for (var i = 0; i < arcs.length; i++) { if (recalcArcs) { calcArc(arcs[i],arcDepth); } drawArc(arcs[i], arcWidth, arcCol); } recalcArcs = false; for (var i = 0; i < points.length; i++) { var p = points[i]; drawPoint(p[0], p[1], pointSize, pointCol); } } //=========================================================================================== // END OF ANSWER // Boiler plate code from here down. Does mouse,canvas,resize and what not var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0, firstRun = true; ; (function () { const RESIZE_DEBOUNCE_TIME = 100; var createCanvas, resizeCanvas, setGlobals, resizeCount = 0; createCanvas = function () { var c, cs; cs = (c = document.createElement("canvas")).style; cs.position = "absolute"; cs.top = cs.left = "0px"; cs.zIndex = 1000; document.body.appendChild(c); return c; } resizeCanvas = function () { if (canvas === undefined) { canvas = createCanvas(); } canvas.width = innerWidth; canvas.height = innerHeight; ctx = canvas.getContext("2d"); if (typeof setGlobals === "function") { setGlobals(); } if (typeof onResize === "function") { if (firstRun) { onResize(); firstRun = false; } else { resizeCount += 1; setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME); } } } function debounceResize() { resizeCount -= 1; if (resizeCount <= 0) { onResize(); } } setGlobals = function () { cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2; } mouse = (function () { function preventDefault(e) { e.preventDefault(); } var mouse = { x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0, over : false, bm : [1, 2, 4, 6, 5, 3], active : false, bounds : null, crashRecover : null, mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",") }; var m = mouse; function mouseMove(e) { var t = e.type; m.bounds = m.element.getBoundingClientRect(); m.x = e.pageX - m.bounds.left; m.y = e.pageY - m.bounds.top; m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey; if (t === "mousedown") { m.buttonRaw |= m.bm[e.which - 1]; } else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; } else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; } else if (t === "mouseover") { m.over = true; } else if (t === "mousewheel") { m.w = e.wheelDelta; } else if (t === "DOMMouseScroll") { m.w = -e.detail; } e.preventDefault(); } m.start = function (element) { if (m.element !== undefined) { m.removeMouse(); } m.element = element === undefined ? document : element; m.mouseEvents.forEach(n => { m.element.addEventListener(n, mouseMove); }); m.element.addEventListener("contextmenu", preventDefault, false); m.active = true; } m.remove = function () { if (m.element !== undefined) { m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); }); m.element.removeEventListener("contextmenu", preventDefault); m.element = m.callbacks = undefined; m.active = false; } } return mouse; })(); function update(timer) { // Main update loop if (ctx === undefined) { return; } globalTime = timer; display(); // call demo code requestAnimationFrame(update); } setTimeout(function () { resizeCanvas(); mouse.start(canvas, true); window.addEventListener("resize", resizeCanvas); requestAnimationFrame(update); }, 0); })();

Left click to add point. Left click drag to move points.<br> Mouse wheel changes arc depth.

Toma dos...

Tal vez esto es lo que estás buscando ... Lo siento, es un desastre ya que tengo poco tiempo en este momento.

El mismo código que antes solo agrega los puntos al exterior de la caja, asegurándote de que los pasos de ancho y altura estén igualmente espaciados desde el borde.

const pointSize = 4; const pointCol = "#4AF"; var arcDepth = -0.5; // depth of arc as a factor of line seg length // Note to have arc go the other (positive) way you have // to change the ctx.arc draw call by adding anticlockwise flag // see drawArc for more const arcCol = "#F92"; const arcWidth = 8; // Find a circle that fits 3 points. function fitCircleTo3P(p1x, p1y, p2x, p2y, p3x, p3y, arc) { var vx, vy, c, c1, u; c = (p2x - p1x) / (p1y - p2y); // slope of vector from vec 1 to vec 2 c1 = (p3x - p2x) / (p2y - p3y); // slope of vector from vec 2 to vec 3 // This will not happen in this example if (c === c1) { // if slope is the same they must be on the same line return null; // points are in a line } // locate the center if (p1y === p2y) { // special case with p1 and p2 have same y vx = (p1x + p2x) / 2; vy = c1 * vx + (((p2y + p3y) / 2) - c1 * ((p2x + p3x) / 2)); } else if (p2y === p3y) { // special case with p2 and p3 have same y vx = (p2x + p3x) / 2; vy = c * vx + (((p1y + p2y) / 2) - c * ((p1x + p2x) / 2)); } else { vx = ((((p2y + p3y) / 2) - c1 * ((p2x + p3x) / 2)) - (u = ((p1y + p2y) / 2) - c * ((p1x + p2x) / 2))) / (c - c1); vy = c * vx + u; } arc.x = vx; arc.y = vy; vx = p1x - vx; vy = p1y - vy; arc.rad = Math.sqrt(vx * vx + vy * vy); return arc; } var points = []; var arcs = []; function addArc(p1, p2, depth) { var arc = { p1 : p1, p2 : p2, depth : depth, rad : null, // radius a1 : null, // angle from a2 : null, // angle to x : null, y : null, } arcs.push(arc); return arc; } function calcArc(arc, depth) { var p = points[arc.p1]; // get points var pp = points[arc.p2]; // change depth if needed depth = arc.depth = depth !== undefined ? depth : arc.depth; var vx = pp[0] - p[0]; // vector from p to pp var vy = pp[1] - p[1]; var cx = (pp[0] + p[0]) / 2; // center point var cy = (pp[1] + p[1]) / 2; // center point var len = Math.sqrt(vx * vx + vy * vy); // get length cx -= vy * depth; // find 3 point at 90 deg to line and dist depth cy += vx * depth; // To have depth as a fixed length uncomment 4 lines below and comment out 2 lines above. //var nx = vx / len; // normalise vector //var ny = vy / len; //cx -= ny * depth; // find 3 point at 90 deg to line and dist depth //cy += nx * depth; fitCircleTo3P(p[0], p[1], cx, cy, pp[0], pp[1], arc); // get the circle that fits arc.a1 = Math.atan2(p[1] - arc.y, p[0] - arc.x); // get angle from circle center to first point arc.a2 = Math.atan2(pp[1] - arc.y, pp[0] - arc.x); // get angle from circle center to second point } function addPoint(x, y) { points.push([x, y]); } function drawPoint(x, y, size, col) { ctx.fillStyle = col; ctx.beginPath(); ctx.arc(x, y, size, 0, Math.PI * 2); ctx.fill(); } function drawArcStart(width,col){ ctx.lineCap = "round"; ctx.strokeStyle = col; ctx.lineJoin = "round"; ctx.lineWidth = width; ctx.beginPath(); } function drawArc(arc){ ctx.arc(arc.x,arc.y,arc.rad,arc.a1,arc.a2); } function drawArcDone(){ ctx.closePath(); ctx.stroke(); } function findClosestPoint(x, y, dist) { var index = -1; for (var i = 0; i < points.length; i++) { var p = points[i]; var vx = x - p[0]; var vy = y - p[1]; var d = Math.sqrt(vx * vx + vy * vy); if (d < dist) { dist = d; index = i; } } return index; } var dragging = false; var drag = -1; var dragX, dragY; var recalcArcs = false; var box; //======================================================================== // New box code from her down // creates the box when canvas is ready var onResize = function(){ box = { x : canvas.width * (1/8), y : canvas.height * (1/8), w : canvas.width * (6/8), h : canvas.height * (6/8), recalculate : true, arcCount : 20, // number of arcs to try and fit. Does not mean that it will happen } } function display() { ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform ctx.globalAlpha = 1; // reset alpha ctx.clearRect(0, 0, w, h); if(mouse.w !== 0){ if(mouse.buttonRaw & 4){ // change arc depth if(mouse.w < 0){ arcDepth *= 1/1.05; }else{ arcDepth *= 1.05; } recalcArcs = true; }else{ // change arc count box.arcCount += Math.sign(mouse.w); box.arcCount = Math.max(4,box.arcCount); box.recalculate = true; } mouse.w = 0; } // drag out box; if(mouse.buttonRaw & 1){ if(!dragging){ box.x = mouse.x; box.y = mouse.y; dragging = true; } box.w = mouse.x - box.x; box.h = mouse.y - box.y; box.recalculate = true; if(box.w <0){ box.x = box.x + box.w; box.w = - box.w; } if(box.h <0){ box.y = box.y + box.h; box.h = - box.h; } }else{ dragging = false; } // stop error if(box.w === 0 || box.h === 0){ box.recaculate = false; } // caculate box arcs if(box.recalculate){ // reset arrays points.length = 0; arcs.length = 0; // get perimiter length var perimLen = (box.w + box.h)* 2; // get estimated step size var step = perimLen / box.arcCount; // get inset size for width and hight var wInStep = (box.w - (Math.floor(box.w/step)-1)*step) / 2; var hInStep = (box.h - (Math.floor(box.h/step)-1)*step) / 2; // fix if box to narrow if(box.w < step){ wInStep = 0; hInStep = 0; step = box.h / (Math.floor(box.h/step)); }else if(box.h < step){ wInStep = 0; hInStep = 0; step = box.w / (Math.floor(box.w/step)); } // Add points clock wise var x = box.x + wInStep; while(x < box.x + box.w){ // across top addPoint(x,box.y); x += step; } var y = box.y + hInStep; while(y < box.y + box.h){ // down right side addPoint(box.x + box.w,y); y += step; } x = box.x + box.w - wInStep; while(x > box.x){ // left along bottom addPoint(x,box.y + box.h); x -= step; } var y = box.y + box.h - hInStep; while(y > box.y){ // up along left side addPoint(box.x,y); y -= step; } // caculate arcs. for(var i =0; i <points.length; i++){ calcArc(addArc(i,(i + 1) % points.length,arcDepth)); } box.recalculate = false; } // recaculate arcs if needed for(var i = 0; i < arcs.length; i ++){ if(recalcArcs){ calcArc(arcs[i],arcDepth); } } // draw arcs drawArcStart(arcWidth,arcCol) for(var i = 0; i < arcs.length; i ++){ drawArc(arcs[i]); } drawArcDone(); recalcArcs = false; } //=========================================================================================== // END OF ANSWER // Boiler plate code from here down. Does mouse,canvas,resize and what not var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0, firstRun = true; ; (function () { const RESIZE_DEBOUNCE_TIME = 100; var createCanvas, resizeCanvas, setGlobals, resizeCount = 0; createCanvas = function () { var c, cs; cs = (c = document.createElement("canvas")).style; cs.position = "absolute"; cs.top = cs.left = "0px"; cs.zIndex = 1000; document.body.appendChild(c); return c; } resizeCanvas = function () { if (canvas === undefined) { canvas = createCanvas(); } canvas.width = innerWidth; canvas.height = innerHeight; ctx = canvas.getContext("2d"); if (typeof setGlobals === "function") { setGlobals(); } if (typeof onResize === "function") { if (firstRun) { onResize(); firstRun = false; } else { resizeCount += 1; setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME); } } } function debounceResize() { resizeCount -= 1; if (resizeCount <= 0) { onResize(); } } setGlobals = function () { cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2; } mouse = (function () { function preventDefault(e) { e.preventDefault(); } var mouse = { x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0, over : false, bm : [1, 2, 4, 6, 5, 3], active : false, bounds : null, crashRecover : null, mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",") }; var m = mouse; function mouseMove(e) { var t = e.type; m.bounds = m.element.getBoundingClientRect(); m.x = e.pageX - m.bounds.left; m.y = e.pageY - m.bounds.top; m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey; if (t === "mousedown") { m.buttonRaw |= m.bm[e.which - 1]; } else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; } else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; } else if (t === "mouseover") { m.over = true; } else if (t === "mousewheel") { m.w = e.wheelDelta; } else if (t === "DOMMouseScroll") { m.w = -e.detail; } e.preventDefault(); } m.start = function (element) { if (m.element !== undefined) { m.removeMouse(); } m.element = element === undefined ? document : element; m.mouseEvents.forEach(n => { m.element.addEventListener(n, mouseMove); }); m.element.addEventListener("contextmenu", preventDefault, false); m.active = true; } m.remove = function () { if (m.element !== undefined) { m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); }); m.element.removeEventListener("contextmenu", preventDefault); m.element = m.callbacks = undefined; m.active = false; } } return mouse; })(); function update(timer) { // Main update loop if (ctx === undefined) { return; } globalTime = timer; display(); // call demo code requestAnimationFrame(update); } setTimeout(function () { resizeCanvas(); mouse.start(canvas, true); window.addEventListener("resize", resizeCanvas); requestAnimationFrame(update); }, 0); })();

Left click drag to create a box<br>Mouse wheel to change arc count<br>Hold right button down and wheel to change arc depth.<br>


El siguiente fragmento de código determina la dirección (CW, CCW) de cada segmento analizando los segmentos adyacentes. Para el segmento A, si ambos segmentos adyacentes están en el mismo lado de A (o si A tiene solo un segmento adyacente), no hay ambigüedad y las escalopas del segmento A están en el exterior de la forma convexa formada por estos segmentos. Sin embargo, si los segmentos adyacentes están en lados opuestos de A, en un patrón de zigzag, el segmento adyacente que se extiende más lejos del segmento A se elige para establecer la dirección del segmento A.

function distance(x1, y1, x2, y2) { return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); } function findNewPoint(x, y, angle, distance) { var result = {}; result.x = Math.round(Math.cos(angle) * distance + x); result.y = Math.round(Math.sin(angle) * distance + y); return result; } function getAngle(x1, y1, x2, y2) { return Math.atan2(y2 - y1, x2 - x1); } function getSeparationFromLine(lineOrigin, lineAngle, pt) { x = pt.x - lineOrigin.x; y = pt.y - lineOrigin.y; return -x * Math.sin(lineAngle) + y * Math.cos(lineAngle); } function getDirection(pts, index, closed) { var last = pts.length - 1; var start = index; var end = (closed && start == last) ? 0 : index + 1; var prev = (closed && start == 0) ? last : start - 1; var next = (closed && end == last) ? 0 : end + 1; var isValidSegment = 0 <= start && start <= last && 0 <= end && end <= last && end !== start; if (!isValidSegment) { return 1; } var pt1 = pts[start]; var pt2 = pts[end]; var pt, x, y; var ccw = 0.0; var theta = Math.atan2(pt2.y - pt1.y, pt2.x - pt1.x); if (0 <= prev && prev <= last) { ccw += getSeparationFromLine(pt1, theta, pts[prev]); } if (0 <= next && next <= last) { ccw += getSeparationFromLine(pt1, theta, pts[next]); } return ccw > 0 ? "1" : "0"; } function scapolledLine(pts, closed, strokeWidth) { var that = this; var scallopSize = strokeWidth * 8; var lastIndex = pts.length - 1; var path = [], newP = null; path.push("M", pts[0].x, pts[0].y); pts.forEach(function (s, currentIndex) { var stepW = scallopSize, lsw = 0; var isClosingSegment = closed && currentIndex == lastIndex; var nextIndex = isClosingSegment ? 0 : currentIndex + 1; var e = pts[nextIndex]; if (!e) { return; } var direction = getDirection(pts, currentIndex, closed); var args = [s.x, s.y, e.x, e.y]; var dist = that.distance.apply(that, args); if (dist === 0) { return; } var angle = that.getAngle.apply(that, args); newP = s; // Number of possible scallops between current pts var n = dist / stepW, crumb; if (dist < (stepW * 2)) { stepW = (dist - stepW) > (stepW * 0.38) ? (dist / 2) : dist; } else { n = (n - (n % 1)); crumb = dist - (n * stepW); stepW += (crumb / n); } // Recalculate possible scallops. n = dist / stepW; var aw = stepW / 2; for (var i = 0; i < n; i++) { newP = that.findNewPoint(newP.x, newP.y, angle, stepW); if (i === (n - 1)) { aw = (lsw > 0 ? lsw : stepW) / 2; } path.push(''A'', aw, aw, "0 0 " + direction, newP.x, newP.y); } if (isClosingSegment) { path.push(''A'', stepW / 2, stepW / 2, "0 0 " + direction, e.x, e.y); } }); return path.join('' ''); } var strokeWidth = 3; var points = []; var mouse = null; var isClosed = false; window.test.setAttribute(''stroke-width'', strokeWidth); function feed(isDoubleClick) { if (isClosed) { return; } if (!isDoubleClick && (points.length > 0 && mouse)) { var arr = points.slice(0); arr.push(mouse); var str = scapolledLine(arr, isClosed, strokeWidth); window.test.setAttribute(''d'', str); } else if (isDoubleClick) { isClosed = true; points.pop(); var str = scapolledLine(points, isClosed, strokeWidth); window.test.setAttribute(''d'', str); } } document.addEventListener(''mousedown'', function (event) { points.push({ x: event.clientX, y: event.clientY }); feed(false); }); document.addEventListener(''dblclick'', function (event) { feed(true); }); document.addEventListener(''mousemove'', function (event) { if (points.length > 0) { mouse = { x: event.clientX, y: event.clientY } feed(false); } });

body, html { height: 100%; width: 100%; margin: 0; padding: 0 } svg { height: 100%; width: 100% }

<svg id="svgP"> <path id="test" style="stroke: RGBA(212, 50, 105, 1.00); fill: none" /> </svg>