How can i add curve to Text Path line in konva?

90 Views Asked by At

I am trying to draw a curved Freeline using Konva’s text path object with in a Group.The group consist of freeline with anchors, back arrow and front arrow placed within a group.

Here is my demo - as codepen

function getSqDist(p1, p2) {

  var dx = p1.x - p2.x,
    dy = p1.y - p2.y;

  return dx * dx + dy * dy;
}

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 > 4) {
      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;
}

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);
  }
}

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;
}

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;
}


/*
 * From here onwards we set up the stage and its contents.
 */
const stage = new Konva.Stage({
    container: 'container',
    width: window.innerWidth,
    height: window.innerHeight
  }),

  layer = new Konva.Layer();

var group = new Konva.Group({
  x: 0,
  y: 0,
  draggable: true,
  id: 'freeline-group'
});

var anchor = new Konva.Circle({
  radius: 8,
  fill: 'red',
  visible: true,
  draggable: true,
})

var front = new Konva.Text({
  text: '▶',
  fill: 'black',
  fontSize: 30,
  offsetY: 25 / 2,
  offsetX: 15 / 2,
})

// var group;

anchor.on('mouseenter', function() {
  group.draggable(false);
});

anchor.on('mouseout', function() {
  group.draggable(true);
});

stage.add(layer);

let
  info = document.querySelector("#info"), // het the handle to the html info element for showing point stats
  lineCnt = -1, // global used to could lines and generate anchor names
  points = [], // list of raw points
  simplifiedPts = [], // simplified list of points
  drawingState = '', // we will need to know when we are drawing and when dragging
  selectedObject = "",
  moving_anchor, // global to hold the current anchor when dragging
  mouseOffset = {
    x: 0,
    y: 0
  }, // from corner of dragging anchor
  lines = [],
  currentLine = 0;

/*
 * This function draws a path based on a list of points.
 */
function drawPoints() {
  let myStage = stage;
  // If we drew this path before then remove the current point markers ready to redraw
  // Note we have to do this because the simplification process could move the points
  const kids = layer.find('.anchor' + currentLine);
  for (let i = 0; i < kids.length; i++) {
    kids[i].remove();
  }
  console.log(lines[currentLine].points);

  let pathData = "";
  for (let i = 0; i < lines[currentLine].points.length; i++) {

    let pt = lines[currentLine].points[i];


    switch (i) {
      case 0:
        pathData += "M " + pt.x + " " + pt.y;
        break;
      case 1:
        pathData += "C " + pt.x + " " + pt.y;
        break;
      default:
        pathData += " " + pt.x + " " + pt.y;
        break;
    }



    const newAnchor = anchor.clone({
      x: pt.x,
      y: pt.y,
      name: 'anchor' + currentLine,
      dragBoundFunc: function(pos) {
        var newX = 0;
        var newY = 0;
        var maxWidth = stage.width() - 10;
        var maxHeight = stage.height() - 10;

        if (pos.x < 10 || pos.y < 10) {
          newX = pos.x < 10 ? 10 : pos.x;
          newY = pos.y < 10 ? 10 : pos.y;
        } else if (pos.x > maxWidth || pos.y > maxHeight) {
          newX = pos.x > maxWidth ? maxWidth : pos.x;
          newY = pos.y > maxHeight ? maxHeight : pos.y;
        } else {
          newX = pos.x;
          newY = pos.y;
        }

        return {
          x: newX,
          y: newY
        }
      }
    });
    const newArrow = front.setAttrs({
      x: pt.x - 0,
      y: pt.y - 0,
      name: 'anchor' + currentLine
    });
    newAnchor.setAttrs({
      lineNo: currentLine,
      anchorNo: i
    }); // store the point sequence in the shape so we can get it on mousedown

    group.add(newAnchor, newArrow); // add anchor circle to the layer
  }

  // set the new path data into the paths objects.
  lines[currentLine].path.data(pathData);
  lines[currentLine].backA.data(pathData);
}

/*
 * This function is called each time the mousemove event is fired.
 * Note that before we draw the points we will simplify them, removing any unneeded points.
 */
function addPoint(pt) {

  // store the new raw point
  points.push(pt);

  // simplify the raw points to make an equiv line with fewer points.
  lines[currentLine].points = simplify(points);

  drawPoints();

}

stage.on('mousedown', function(evt) {
  // console.log("mousedown started")
  let myStage = stage;

  let shape = evt.target;
  const pt = stage.getPointerPosition();

  if (shape.name().length === 0) {
    drawLine();
  }

  if (drawingState === "beforeDraw") {
    // console.log("if")
    drawingState = 'drawing';
  } else {
    // console.log("else")
    if (shape.name().length > 0) {
      drawingState = 'moving';
      moving_anchor = shape;
      let anchor_no = moving_anchor.getAttr('anchorNo');
      let pt = lines[currentLine].points[anchor_no];
      mouseOffset = {
        x: shape.x() - pt.x,
        y: shape.y() - pt.y
      };

    }
  }
})

// user draws with mouse held down and moving
stage.on('mousemove', function(e) {
  let shape = stage;
  const pt = stage.getPointerPosition();

  if (drawingState === 'drawing') {
    addPoint(pt)
    // console.log('points adding')
  }
  if (drawingState === 'moving') {
    // console.log('points moving')
    currentLine = moving_anchor.getAttr('lineNo')
    let anchor_no = moving_anchor.getAttr('anchorNo');
    // moving_anchor.draggable(true)
    moving_anchor.position({
      x: pt.x,
      y: pt.y
    });
    lines[currentLine].points[anchor_no] = {
      x: pt.x - group.x(),
      y: pt.y - group.y()
    };
    drawPoints();
  }

})

// user ends drawing with mouse up  
stage.on('mouseup', function(e) {
  drawingState = "";
  reset(false)
})

// reset the stage and points lists
function reset(clear) {
  if (clear) {
    group.removeChildren();
  }
  points = [];
}
reset(true);


let backA = null;

function drawLine() {


  // console.log(1 + " runs drawLine function")
  drawingState = 'beforeDraw';
  lineCnt++;

  let dData = '';
  for (let i = 0; i < 500; i++) {
    dData += "-"
  }
  path = new Konva.TextPath({
    text: dData,
    fill: 'black',
    bezier: true,
    tension: 0.5
  })

  backA = new Konva.TextPath({
    text: '<',
    fill: 'black',
    fontSize: 45,
  });


  group.add(backA, path);
  layer.add(group);

  currentLine = lineCnt;
  lines[lineCnt] = {
    path: path,
    backA: backA,
    points: []
  };

  path.on('mouseenter', function() {
    document.body.style.cursor = "pointer"
  })

  path.on('mouseout', function() {
    document.body.style.cursor = "default"
  })
}

document.getElementById('changeArrow').addEventListener('click', function() {
  if (backA === null) {
    return
  }
  backA.text("‹")
})

document.getElementById('changeArrow2').addEventListener('click', function() {
  if (backA === null) {
    return
  }
  backA.text("«")
})

document.getElementById('changeNoarrow').addEventListener('click', function() {
  if (backA === null) {
    return
  }
  backA.text(" ")
})
body {
  margin: 20px;
  padding: 0;
  overflow: hidden;
  background-color: #f0f0f0;
}

#container {
  border: 1px solid red;
  width: 1000px;
  height: 400px;
}
<script src="https://unpkg.com/konva@8/konva.min.js"></script>
<div>
  <button id='reset' onclick="reset(true)"> Remove </button>
</div>
<div id="container"></div>

Could you please guide me on how to give curve to this line. Thanks!

1

There are 1 best solutions below

0
Vanquished Wombat On

Konva.TextPath uses the data attribute to define the path. This uses a cut-d0wn set of SVG commands to define the path. The currently supported SVG data commands are M, m, L, l, H, h, V, v, Q, q, T, t, C, c, S, s, A, a, Z, z.

See official docs here.

Use your computed curve points to create the SVG command to have the path match your curve.