Just started using Kinetic.js yesterday. I want to draw some text (a label) inside a wedge so that it's placed inside the wedge with a rotation relative to the wedges' angle.
Like so:
Here's my code:
var numSegments = 5; // Number of wedges in circle
var endPos = 0;
//Center of the div container
var center = document.getElementById('canvas_container').offsetWidth * 0.5;
var centerY = document.getElementById('canvas_container').offsetHeight * 0.5;
for (var i = 0; i < numSegments; ++i) {
//Wedge + corresponding label stored in their own group
var group = new Kinetic.Group();
var wedge = new Kinetic.Wedge({
radius: center,
x: center,
y: centerY,
fill: colors[i],
stroke: '#aaaaff',
strokeWidth: 2,
angleDeg: 360 / numSegments,
rotationDeg: endPos,
});
var label = new Kinetic.Label({
x : wedge.getX(),
y : wedge.getY(),
//The rotation value is where I assume I'm going wrong, this
//Is one of many values i've tried.
rotation : (endPos == 0) ? (360 / numSegments) * 0.5 : endPos
});
label.add(new Kinetic.Text({
text : titles[i],
fontFamily: 'Calibri',
fontSize: 26,
fill : 'white',
align: 'center',
listening: false
}));
group.add(wedge);
group.add(label);
WedgeLayer.add(group);
endPos += 360 / numSegments;
}
I've hit a brick wall with this and am looking for anyone to share any insight into how to achieve the desired outcome..
So far the above results are producing this:
Any help would be greatly appreciated, cheers.
A Demo: http://jsfiddle.net/m1erickson/fu5LP/
You calculate the text offset and rotation angle like this:
Calculating the text rotation angle
Track the accumulated angle for each new wedge you add and use that accum. angle to set the text angle.
Adjusting the angle for various accumulated angles helps keep the text from appearing upside down.
If the accumulated angle is between 90 & 270 degrees then set the text angle as the accumulated angle minus 180.
If the accumulated angle is <90 or >270 then set the text angle as the accumulated angle.
Setting the text offset
The offset depends on the radius of the wedge.
But again the offset is adjusted based on the accumulated angle
If the accumulated angle is between 90 & 270 degrees then set the text offsetX to approximately the wedge radius minus 10.
If the accumulated angle is <90 or >270 then set the text offset to approximately negative half of the wedge radius.
Example code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Prototype</title>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v5.1.0.min.js"></script>
<style>
body{padding:20px;}
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:350px;
height:350px;
}
</style>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width: 350,
height: 350
});
var layer = new Kinetic.Layer();
stage.add(layer);
var cx=175;
var cy=175;
var wedgeRadius=140;
var accumAngle=0;
var center = new Kinetic.Circle({
x:cx,
y:cy,
radius:5,
fill: 'red'
});
layer.add(center);
for(var i=0;i<12;i++){
newTextWedge(30,"Element # "+i);
}
function newTextWedge(angle,text){
var wedge = new Kinetic.Wedge({
x: cx,
y: cy,
radius: wedgeRadius,
angleDeg: angle,
stroke: 'gray',
strokeWidth: 1,
rotationDeg:-accumAngle+angle/2
});
layer.add(wedge);
wedge.moveToBottom();
if(accumAngle>90 && accumAngle<270){
var offset={x:wedgeRadius-10,y:7};
var textAngle=accumAngle-180;
}else{
var offset={x:-50,y:7};
var textAngle=accumAngle;
}
var text = new Kinetic.Text({
x:cx,
y:cy,
text:text,
fill: 'red',
offset:offset,
rotationDeg:textAngle
});
layer.add(text);
layer.draw();
accumAngle+=angle;
}
}); // end $(function(){});
</script>
</head>
<body>
<div id="container"></div>
</body>
</html>
Related
This is probably just maths.
I am using Konva to dynamically generate shapes, which I'm storing as a label. So there's a label which contains a textElement and a rectangle. I want to make sure text in that rectangle is always a) Centered horizontally and vertically and b) facing the right way up.
So a rectangle could have any rotation, but I always want the text centered and facing the right way up.
The code for creation; width, height, rotation, x and y all have values pulled from a database.
var table = new Konva.Label({
x: pos_x,
y: pos_y,
width: tableWidth,
height: tableHeight,
draggable:true
});
table.add(new Konva.Rect({
width: tableWidth,
height: tableHeight,
rotation: rotation,
fill: fillColor,
stroke: strokeColor,
strokeWidth: 4
}));
table.add(new Konva.Text({
width: tableWidth,
height: tableHeight,
x: pos_x, //Defaults to zero
y: pos_y, //Default to zero
text: tableNumber,
verticalAlign: 'middle',
align: 'center',
fontSize: 30,
fontFamily: 'Calibri',
fill: 'black'
}))
tableLayer.add(table);
The problem is, if rotation is in place, text is off center, as in this image:
I do manually correct in some circumstances - for example if rotation = 45 degrees:
pos_x = -tableWidth/2;
pos_y = tableHeight/5;
but that is not a permanent solution. I want the x and y co-ordinates of the text to be at the centerpoint of the shape itself.
I've tried a few approaches (such as applying rotation to the Label itself and then negative rotation value to the text)
This code snippet illustrates a solution. It is copied & modified from my other self-answer when I was looking for a robust approach to rotation around an arbitrary point - note that I consider this a slightly different question than my original so I have not suggested this is a dup. The difference is the need to work with a more complex grouped shape and to keep some element within that group unrotated.
Not in the OP's question, but I set a background rectangle into the text by making the text a group. The purpose of this was to show that the text rectangle will extend outside the label rectangle in some points of rotation. This is not a critical issue but it is useful to see it happen.
The fundamental challenge for the coder is to understand how the shapes move when rotated since we usually want to spin them around their centre but the fundamental 2D canvas pattern that Konva (and all HTML5 canvas wrappers) follow is to rotate from the top-left corner, al least for rectangles as per shapes in the question. It 'is' possible to move the rotation point (known as the offset) but again that is a conceptual challenge for the dev and a nice trap for anyone trying to support the code later.
There's a lot of code in this answer that is here to set up something dynamic that you can use to visualise what is going on. However, the crux is in this:
// This is the important call ! Cross is the rotation point as illustrated by crosshairs.
rotateAroundPoint(shape, rotateBy, {x: cross.x(), y: cross.y()});
// The label is a special case because we need to keep the text unrotated.
if (shape.name() === 'label'){
let text = shape.find('.text')[0];
rotateAroundPoint(text, -1 * rotateBy, {x: text.getClientRect().width/2, y: text.getClientRect().height/2});
}
The rotateAroundPoint() function takes as parameters the Konva shape to rotate, the clockwise rotation angle (not radians, good ole degrees), and the x & y position of the rotation point on the canvas / parent.
I constructed a group of shapes as my label, composing it from a rectangle and a text shape. I named this 'label'. Actually I switched the text shape to be another group of rect + text to that I could show the rectangle the text sits within. You could leave out the extra group. I named this 'text'.
The first call to rotateAroundPoint() rotates the group named 'label'. So the group rotates on the canvas. Since the 'text' is a child of the 'label' group, that would leave the 'text' rotated, so the next line checks if we are working with the 'label' group, and if so we need to get hold of the 'text' shape which is what this line does:
let text = shape.find('.text')[0];
In Konva the result of a find() is a list so we take the first in the list. Now all that remains for me to do is rotate the text on the 'label' group back again by applying the negative rotation degrees to its center point. The line below achieves this.
rotateAroundPoint(text, -1 * rotateBy, {x: text.getClientRect().width/2, y: text.getClientRect().height/2});
One note worthy of mention - I used a group for my 'text' shape. A Konva group does not naturally have a width or height - it is more of a means to collect shapes together but without a 'physical' container. So to get its width and height for the centre point calculations I use the group.getClientRect() method which gives the size of the minimum bounding box that would contain all shapes in the group, and yields an object formed as {width: , height: }.
Second note - the first use of rotateAroundPoint() affects the 'label' group which has as its parent the canvas. The second use of that function affects the 'text' group which has the 'label' group as its parent. Its subtle but worth knowing.
Here is the snippet. I urge you to run it fullscreen and spin a few shapes around a few different points.
// Code to illustrate rotation of a shape around any given point. The important functions here is rotateAroundPoint() which does the rotation and movement math !
let
angle = 0, // display value of angle
startPos = {x: 80, y: 45},
shapes = [], // array of shape ghosts / tails
rotateBy = 20, // per-step angle of rotation
shapeName = $('#shapeName').val(), // what shape are we drawing
shape = null,
ghostLimit = 10,
// Set up a stage
stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
}),
// add a layer to draw on
layer = new Konva.Layer(),
// create the rotation target point cross-hair marker
lineV = new Konva.Line({points: [0, -20, 0, 20], stroke: 'lime', strokeWidth: 1}),
lineH = new Konva.Line({points: [-20, 0, 20, 0], stroke: 'lime', strokeWidth: 1}),
circle = new Konva.Circle({x: 0, y: 0, radius: 10, fill: 'transparent', stroke: 'lime', strokeWidth: 1}),
cross = new Konva.Group({draggable: true, x: startPos.x, y: startPos.y}),
labelRect, labelText;
// Add the elements to the cross-hair group
cross.add(lineV, lineH, circle);
layer.add(cross);
// Add the layer to the stage
stage.add(layer);
$('#shapeName').on('change', function(){
shapeName = $('#shapeName').val();
shape.destroy();
shape = null;
reset();
})
// Draw whatever shape the user selected
function drawShape(){
// Add a shape to rotate
if (shape !== null){
shape.destroy();
}
switch (shapeName){
case "rectangle":
shape = new Konva.Rect({x: startPos.x, y: startPos.y, width: 120, height: 80, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "hexagon":
shape = new Konva.RegularPolygon({x: startPos.x, y: startPos.y, sides: 6, radius: 40, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "ellipse":
shape = new Konva.Ellipse({x: startPos.x, y: startPos.y, radiusX: 40, radiusY: 20, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "circle":
shape = new Konva.Ellipse({x: startPos.x, y: startPos.y, radiusX: 40, radiusY: 40, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "star":
shape = new Konva.Star({x: startPos.x, y: startPos.y, numPoints: 5, innerRadius: 20, outerRadius: 40, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "label":
shape = new Konva.Group({name: 'label'});
labelRect = new Konva.Rect({x: 0, y: 0, width: 120, height: 80, fill: 'magenta', stroke: 'black', strokeWidth: 4, name: 'rect'})
shape.add(labelRect);
labelText = new Konva.Group({name: 'text'});
labelText.add(new Konva.Rect({x: 0, y: 0, width: 100, height: 40, fill: 'cyan', stroke: 'black', strokeWidth: 2}))
labelText.add(new Konva.Text({x: 0, y: 0, width: 100, height: 40, text: 'Wombat',fontSize: 20, fontFamily: 'Calibri', align: 'center', padding: 10}))
shape.add(labelText)
labelText.position({x: (labelRect.width() - labelText.getClientRect().width) /2, y: (labelRect.height() - labelText.getClientRect().height) /2})
break;
};
layer.add(shape);
cross.moveToTop();
}
// Reset the shape position etc.
function reset(){
drawShape(); // draw the current shape
// Set to starting position, etc.
shape.position(startPos)
cross.position(startPos);
angle = 0;
$('#angle').html(angle);
$('#position').html('(' + shape.x() + ', ' + shape.y() + ')');
clearTails(); // clear the tail shapes
stage.draw(); // refresh / draw the stage.
}
// Click the stage to move the rotation point
stage.on('click', function (e) {
cross.position(stage.getPointerPosition());
stage.draw();
});
// Rotate a shape around any point.
// shape is a Konva shape
// angleRadians is the angle to rotate by, in radians
// point is an object {x: posX, y: posY}
function rotateAroundPoint(shape, angleDegrees, point) {
let angleRadians = angleDegrees * Math.PI / 180; // sin + cos require radians
const x =
point.x +
(shape.x() - point.x) * Math.cos(angleRadians) -
(shape.y() - point.y) * Math.sin(angleRadians);
const y =
point.y +
(shape.x() - point.x) * Math.sin(angleRadians) +
(shape.y() - point.y) * Math.cos(angleRadians);
shape.rotation(shape.rotation() + angleDegrees); // rotate the shape in place
shape.x(x); // move the rotated shape in relation to the rotation point.
shape.y(y);
shape.moveToTop(); //
}
$('#rotate').on('click', function(){
let newShape = shape.clone();
shapes.push(newShape);
layer.add(newShape);
// This ghost / tails stuff is just for fun.
if (shapes.length >= ghostLimit){
shapes[0].destroy();
shapes = shapes.slice(1);
}
for (var i = shapes.length - 1; i >= 0; i--){
shapes[i].opacity((i + 1) * (1/(shapes.length + 2)))
};
// This is the important call ! Cross is the rotation point as illustrated by crosshairs.
rotateAroundPoint(shape, rotateBy, {x: cross.x(), y: cross.y()});
// The label is a special case because we need to keep the text unrotated.
if (shape.name() === 'label'){
let text = shape.find('.text')[0];
rotateAroundPoint(text, -1 * rotateBy, {x: text.getClientRect().width/2, y: text.getClientRect().height/2});
}
cross.moveToTop();
stage.draw();
angle = angle + 10;
$('#angle').html(angle);
$('#position').html('(' + Math.round(shape.x() * 10) / 10 + ', ' + Math.round(shape.y() * 10) / 10 + ')');
})
// Function to clear the ghost / tail shapes
function clearTails(){
for (var i = shapes.length - 1; i >= 0; i--){
shapes[i].destroy();
};
shapes = [];
}
// User cicks the reset button.
$('#reset').on('click', function(){
reset();
})
// Force first draw!
reset();
body {
margin: 10;
padding: 10;
overflow: hidden;
background-color: #f0f0f0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://unpkg.com/konva#^3/konva.min.js"></script>
<p>1. Click the rotate button to see what happens when rotating around shape origin.</p>
<p>2. Reset then click stage to move rotation point and click rotate button again - rinse & repeat</p>
<p>
<button id = 'rotate'>Rotate</button>
<button id = 'reset'>Reset</button>
<select id='shapeName'>
<option value='label' selected='selected'>Label</option>
<option value='rectangle'>Rectangle</option>
<option value='hexagon'>Polygon</option>
<option value='ellipse' >Ellipse</option>
<option value='circle' >Circle</option>
<option value='star'>Star</option>
</select>
Angle : <span id='angle'>0</span>
Position : <span id='position'></span>
</p>
<div id="container"></div>
could any one show my how to drag my object but still keep its previous offset in rotate animation. I try so many times but still not find the solution. When i drag an object, it didn't rotate in orbit :( .
Here is my code
HTML
<!DOCTYPE HTML>
<html>
<body>
<div id="container"></div>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v5.0.2.min.js"></script>
</body>
</html>
CSS
body {
margin: 0px;
padding: 0px;
}
canvas {
border: 1px solid #9C9898;
}
JS
window.onload = function() {
var stage = new Kinetic.Stage({
container: 'container',
width: 500,
height: 500
});
var layer = new Kinetic.Layer(),
cx = stage.getWidth() / 2,
cy = stage.getWidth() / 2;
var bigCircle = new Kinetic.Circle({
x: cx ,
y: cy,
radius: 200,
stroke: 'lightgray',
strokeWidth: 8,
});
/*
* move center point outside of the rectangle
* with offset
*/
var smallCirlce = new Kinetic.Circle({
x: cx,
y: cy,
radius: 20,
fill:'rgb(0,102,204)',
strokeWidth: 4,
offset: {x:getRandom(-150,150), y:getRandom(-150,150)}
});
function getRandom(minNum, maxNum){
return Math.random() * (maxNum - minNum) + minNum;
};
layer.add(bigCircle);
layer.add(smallCirlce);
stage.add(layer);
// one revolution per 4 seconds
var angularSpeed = 360 / 4;
var anim = new Kinetic.Animation(function(frame) {
var angleDiff = frame.timeDiff * angularSpeed / 1000;
smallCirlce.rotate(angleDiff);
}, layer);
anim.start();
};
Not sure if this is what you are after. What I took from your description was that you want to be able to drag the blue ball and have it continue orbiting around its initial point but at a greater radius.
Firstly, here is a fiddle:
http://jsfiddle.net/Paul_Smith/206meq0a/
What this solution does is on mousedown, convert the circle's position, offset and rotation into a single coordinate representing the actual position of the circle in space.
It then allows you to drag the circle, and resets its offset to the new one by subtracting its actual position from its original position.
Probably not the best solution out there but I am not a very math oriented programmer :)
The important functions are as below:
//Calculate an angle in radians given degrees
function degreesToRadians(degrees) {
return degrees * Math.PI / 180;
}
//Transform a coordinate by way of a rotation in degrees.
//Rotated about { x: 0, y: 0 }
function translateCoordinate(coordinate, degrees) {
var radians = degreesToRadians(degrees);
return {
x: (coordinate.x * Math.cos(radians)) - (coordinate.y * Math.sin(radians)),
y: (coordinate.x * Math.sin(radians)) + (coordinate.y * Math.cos(radians))
};
}
smallCircle.on("mousedown", function () {
var rotation = smallCircle.rotation(),
offset = smallCircle.offset(),
position = smallCircle.position(),
translatedOffset;
//Calculate how the offset has changed due to the rotation
translatedOffset = translateCoordinate(offset, rotation);
//Set the position to that of the current position - the offset
smallCircle.position({
x: position.x - translatedOffset.x,
y: position.y - translatedOffset.y
});
//set the offset to 0 as it has now been added to the position
smallCircle.offset({
x: 0,
y: 0
});
//Set the rotation to 0 as it is now part of the position due to the
//translation which has taken place
smallCircle.rotation(0);
//Reset the drag position
smallCircle.stopDrag();
smallCircle.startDrag();
});
smallCircle.on("mouseup", function () {
var position = smallCircle.position();
//Calculate the new offset (original position - new position)
smallCircle.offset({
x: cx - position.x,
y: cy - position.y
});
//Set the position back to the original position
smallCircle.position({
x: cx,
y: cy
});
});
Hope this helps
I have 2 images that are the upper arm and forearm. The upper arms rotates around a point and the forearm rotates round this same point.
How can i rotate the forearm when i rotate the upper arm?
if i rotate the upperarm 12 degress,then i must move the forearm to the rotation point of the upper arm and then rotate it but i cant seem to get the forearm to move back into postion after the rotation. What am i doing wrong.
code im using for a kinectjs event;
//upper rotate
upperArmImg.on('mousedown',function(evt) {
upperArmImg.rotateDeg(12);
p.x=upperArmImg.x();
p.y=upperArmImg.y();
//move the formarm to center of rotation
foreArmImg.setPosition(p);
foreArmImg.rotateDeg(12);
//move it back some ???
foreArmImg.move(100,100);
stage.draw();
});
The key to joint movements is putting all arm parts in a group and then setting proper offset points.
The importance of an offset point is that they set where any arm part will rotate (==rotation point)
Here's an outline of how to add kinetic motion to an upper and lower arm:
Put the upper and lower arm in a group
Set the group, upper & lower offsets (==rotation points) to { x:0, y:armDiameter/2 }
To rotate the upper arm, change the groups rotation
To rotate the lower arm, change the lower arm rotation
Group Rotation == 0 degrees, Lower rotation == 0 degrees
Group Rotation == 50 degrees, Lower rotation == 0 degrees
Group Rotation == 50 degrees, Lower rotation == 65 degrees
Here's example code and a Demo:
var stage = new Kinetic.Stage({
container: 'container',
width: 350,
height: 350
});
var layer = new Kinetic.Layer();
stage.add(layer);
var lastWidth = 0;
var baseGroup = new Kinetic.Group({
x: 50,
y: 200,
draggable: true
});
layer.add(baseGroup);
var lastParent = baseGroup;
var appendages = [];
appendages.push({
name: 'shoulder',
width: 50,
diameter: 20,
fill: 'brown'
});
appendages.push({
name: 'elbow',
width: 80,
diameter: 20,
fill: 'peru'
});
appendages.push({
name: 'hand',
width: 30,
diameter: 20,
fill: 'tan'
});
appendages.push({
name: 'finger',
width: 15,
diameter: 20,
fill: 'wheat'
});
for (var i = 0; i < appendages.length; i++) {
lastParent = addAppendage(i);
appendages[i].group = lastParent;
var appendage = appendages[i];
var html = "Rotate " + appendage.name + ":";
html += "<input id=" + appendage.name;
html += " data-apindex='" + i + "'";
html += " class='apRange' type=range min=0 max=90 value=0><br>";
$('body').append(html);
}
$('.apRange').change(function() {
var appendageIndex = parseInt($(this).data('apindex'));
appendages[appendageIndex].group.rotation(-parseInt($(this).val()));
layer.draw();
});
function addAppendage(i) {
var appendage = appendages[i];
var d2 = appendage.diameter / 2;
// new group for this appendage
var newGroup = new Kinetic.Group({
x: lastWidth,
y: 0,
rotation: 0,
offset: {
x: 0,
y: 0
},
});
newGroup.appendageIndex = i;
lastParent.add(newGroup);
// new appendage
var a = new Kinetic.Rect({
x: 0,
y: 0,
width: appendage.width,
height: d2 * 2,
fill: appendage.fill,
stroke: 'black',
offset: {
x: 0,
y: d2
},
});
newGroup.add(a);
// new joint indicator
var j = new Kinetic.Circle({
x: 0,
y: 0,
radius: d2 / 2,
fill: "red",
offset: {
x: 0,
y: 0
},
});
newGroup.add(j);
// display new objects
layer.draw();
// save width for next loop
lastWidth = appendage.width;
// return latest group
return (newGroup);
}
body {
padding: 20px;
}
#container {
border: solid 1px #ccc;
margin-top: 10px;
width: 350px;
height: 350px;
}
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v5.1.0.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div id="container"></div>
I am working to put rectangles representing solar panels on roof top of a given house. User can rotate a rectangle to align it with roof. Code is working fine until an adjacent rectangle is added without rotating the selected panel. However, I am facing problem in correctly calculating the position of a new rectangles adjacent to the rotated rectangle. Here is the code:
<!DOCTYPE html>
<html>
<head>
<title>Panel Positioner</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.0.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.4.0/fabric.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var solar_panel = {"width": 57, "height": 94}; // Default pixles on 5M zoom
var offset = 58;
var rads = 0;
var position = {};
var id = 1;
$('#add-panel').click(function() {
addPanel();
});
function addPanel() {
if (id === 1) {
canvas = new fabric.Canvas('c1');
canvas.on("after:render", function() {
canvas.calcOffset();
});
canvas.on('object:moving', function(e) {
var activeObject = e.target;
position.x = activeObject.get('left');
position.y = activeObject.get('top');
});
canvas.on('object:rotating', function(e) {
var activeObject = e.target;
var angle = activeObject.get('angle');
rads = (activeObject.get('angle') * Math.PI / 180.0);
});
}
var markerPt2 = position;
markerPt2.x += offset * Math.cos(-rads);
markerPt2.y -= offset * Math.sin(-rads);
rect = new fabric.Rect({
id: id,
left: markerPt2.x,
top: markerPt2.y,
fill: 'grey',
width: solar_panel.width,
height: solar_panel.height,
borderColor: 'black',
stroke: '#000',
lockScalingX: true,
lockScalingY: true,
hasRotatingPoint: true,
});
rect.set('angle', rads * 180.0 / Math.PI);
id++;
canvas.add(rect);
canvas.renderAll();
}
});
</script>
</head>
<body>
<canvas id="c1" width="800" height="600" style="z-index:1000; border: 2px solid black;" ></canvas>
<input type="button" id="add-panel" name="add-panel" value="Add Panel" />
</body>
</html>
OK, after a bit of work around I found out what as wrong with my own script.
There are two important things involved:
When an object is rotated, only the radians change (NOT the x,y position). So, after rotating an object, we only have to calculate its radians to find out where the next adjacent object will be laid out. So, in my code what I am doing wrong is that after either object movement or rotation, I am re-calculating the new object's x,y coordinates. So, what needs to be done is a boolean flag that will decided whether the object was rotated. If yes, then do not re-calculate the x,y position for new adjacent object. Just re-calculate the radians (which is already being implemented in "object:rotating" block). So the code should look like:
if(rotated == false) {
markerPt2.x += offset * Math.cos(-rads);
markerPt2.y -= offset * Math.sin(-rads);
} else {
rotated = false;
}
Additionally another fact that has been annoying for me was when an object is rendered or moved in Fabric.js, its x,y position is top-left corner of surrounding rectangle, but as an object is rotated, Fabric.js returns the center point of object as its x,y position. So in this case when the object was rotated, the Fabric.js translated point jumped to center point of rectangle. So when implementing "object:rotating" only lastly recorded (before rotation) x,y should be used.
If I have a kinetic line
for example:
line = new Kinetic.Line({
x: 80,
y:80,
points: [10, 10, 60, 60, 60-headlen*Math.cos(angle-Math.PI/6),60-headlen*Math.sin(angle-Math.PI/6),60, 60, 60-headlen*Math.cos(angle+Math.PI/6),60-headlen*Math.sin(angle+Math.PI/6)],
stroke: "green",
// draggable: true,
strokeWidth: 4,
name: "letter_arrow",
offset: [140,140]
});
and say some circles
var anchor = new Kinetic.Circle({
x: x,
y: y,
stroke: 'red',
fill: 'white',
strokeWidth: 2,
radius: 8,
name: name,
draggable: false,
dragOnTop: false,
//TODO SET THIS TO 0!!!!!
opacity: 1
});
where x and y will be the first 2 points of the line (10,10) and (60,60)
how can i make the circles drag bound, so that they can only be dragged on a imaginal line made through the first two points (10,10) and (60,60) i read the tutorials about drag bounds but i have no clue maybe you can help me
regards
You can add a custom dragBoundFunc to limit your anchors movement to a line:
// dragBoundFunc will constrain your shape to your desired x/y
// you receive the current mouse position in pos.x and pos.y
// you return the x/y where you want the shape to be located
dragBoundFunc: function(pos) {
// get the mouse x position
var x=pos.x;
// if the mouse is left or right of the line
// force the anchor to the far-left or far-right of the line
if(x<lineMinX){ return({ x:lineMinX, y:lineMinY}); }
if(x>lineMaxX){ return({ x:lineMaxX, y:lineMaxY}); }
// if the mouse between the left and right points of the line
// calc the desired y-position based on the formula: y = lineSlope * x + lineB
return({x:x,y:lineSlope*x+lineB});
}
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/UHwW9/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Prototype</title>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.7.2.min.js"></script>
<style>
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:300px;
height:300px;
}
</style>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width: 300,
height: 300
});
var layer = new Kinetic.Layer();
stage.add(layer);
var x1=10;
var y1=10;
var x2=60;
var y2=60;
var line=new Kinetic.Line({
points:[x1,y1,x2,y2],
stroke:"gray"
});
layer.add(line);
var anchor1=newAnchor(x1,y1,x2,y2);
function newAnchor(x1,y1,x2,y2){
var anchor = new Kinetic.Circle({
x: x1,
y: y1,
radius: 8,
fill: 'skyblue',
stroke: 'lightgray',
strokeWidth: 3,
draggable:true,
dragBoundFunc: function(pos) {
var x=pos.x;
if(x<this.minX){
return({x:this.minX,y:this.minY});
}
if(x>this.maxX){
return({x:this.maxX,y:this.maxY});
}
return({x:x,y:this.slope*x+this.b});
}
});
// dragBoundFunc stuff
anchor.minX=x1;
anchor.minY=y1;
anchor.maxX=x2;
anchor.maxY=y2;
anchor.slope=(y1-y2)/(x1-x2);
anchor.b=y1/(anchor.slope*x1);
//
layer.add(anchor);
layer.draw();
return(anchor);
}
}); // end $(function(){});
</script>
</head>
<body>
<button id="rotateBtn">rotate</button>
<div id="container"></div>
</body>
</html>
In MarkE's code, I believe the way to calculate anchor.b should be
anchor.b = y1 - anchor.slope * x1;
instead of
anchor.b = y1 / (anchor.slope * x1);
The formula is y = ax + b.
That could cause the bad behavior. http://jsfiddle.net/UHwW9/26/