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!
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.