Dynamically flexible path between two points in SVG - javascript

Hi can some one help me bend path like here
here u can see it in action (it's almost what i need, but it on canvas)
QUESTION
how i can calculate it?
which formula describes this
and how correctly to describe the parameters 'd' of path
here's my code (maybe it needs some improvements?)
var app = angular.module('app', []);
app.controller("ctrl", function ($scope) {
var lineGraph = d3.select("#container").append("svg:svg").attr("width", '100%').attr("height", '100%');
$scope.linesArr = [];
$scope.blocksArr = [{
id: 0,
x: 0,
y: 0,
lineToID: [2]
},{
id: 1,
x: 0,
y: 0,
lineToID: [0,2]
},{
id: 2,
x: 0,
y: 0,
lineToID: []
}];
$scope.createLines = function(){
for(var i = 0; i < $scope.blocksArr.length; i++){
if($scope.blocksArr[i].lineToID.length){
for(var j = 0; j < $scope.blocksArr[i].lineToID.length; j++){
$scope.linesArr[$scope.blocksArr[i].id + ":"+j] = (lineGraph.append("svg:line"));
}
}
}
};
$scope.createLines();
$scope.checkPoints = function(){
for(var i = 0; i < $scope.blocksArr.length; i++){
$scope.blocksArr[i].x = parseInt(document.querySelector('#b' + i).style.left) + (document.querySelector('#b' + i).offsetWidth / 2);
$scope.blocksArr[i].y = parseInt(document.querySelector('#b' + i).style.top) + (document.querySelector('#b' + i).offsetHeight / 2);
if($scope.blocksArr[i].lineToID.length){
for(var j = 0; j < $scope.blocksArr[i].lineToID.length; j++){
$scope.linesArr[$scope.blocksArr[i].id+":"+j]
.attr("x1", $scope.blocksArr[$scope.blocksArr[i].id].x)
.attr("y1", $scope.blocksArr[$scope.blocksArr[i].id].y)
.attr("x2", $scope.blocksArr[$scope.blocksArr[i].lineToID[j]].x)
.attr("y2", $scope.blocksArr[$scope.blocksArr[i].lineToID[j]].y)
.style("stroke", "rgb(6,120,155)");
//console.log();
}
}
}
};
$scope.dragOptions = {
start: function(e) {
//console.log("STARTING");
},
drag: function(e) {
$scope.checkPoints();
//console.log("DRAGGING");
},
stop: function(e) {
//console.log("STOPPING");
},
container: 'container'
}
});
app.directive('ngDraggable', function($document) {
return {
restrict: 'A',
scope: {
dragOptions: '=ngDraggable'
},
link: function(scope, elem, attr) {
var startX, startY, x = 0, y = 0,
start, stop, drag, container;
var width = elem[0].offsetWidth,
height = elem[0].offsetHeight;
// Obtain drag options
if (scope.dragOptions) {
start = scope.dragOptions.start;
drag = scope.dragOptions.drag;
stop = scope.dragOptions.stop;
var id = scope.dragOptions.container;
if (id) {
container = document.getElementById(id).getBoundingClientRect();
}
}
// Bind mousedown event
elem.on('mousedown', function(e) {
e.preventDefault();
startX = e.clientX - elem[0].offsetLeft;
startY = e.clientY - elem[0].offsetTop;
$document.on('mousemove', mousemove);
$document.on('mouseup', mouseup);
if (start) start(e);
});
// Handle drag event
function mousemove(e) {
y = e.clientY - startY;
x = e.clientX - startX;
setPosition();
if (drag) drag(e);
}
// Unbind drag events
function mouseup(e) {
$document.unbind('mousemove', mousemove);
$document.unbind('mouseup', mouseup);
if (stop) stop(e);
}
// Move element, within container if provided
function setPosition() {
if (container) {
if (x < container.left) {
x = container.left;
} else if (x > container.right - width) {
x = container.right - width;
}
if (y < container.top) {
y = container.top;
} else if (y > container.bottom - height) {
y = container.bottom - height;
}
}
elem.css({
top: y + 'px',
left: x + 'px'
});
}
}
}
})
html,body, #container{
height: 100%;
margin: 0;
}
.box{
position: absolute;
width: 100px;
height: 30px;
line-height: 30px;
border: 1px solid #c07f7f;
text-align: center;
background: #f3f4ff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div ng-controller="ctrl" ng-app="app" id="container">
<div class="box" id="b{{$index}}" ng-repeat="i in blocksArr" ng-draggable='dragOptions' ng-style="{top: blocksArr[$index].y, left: blocksArr[$index].x}">{{$index}}</div>
</div>

Guessing, I'm thinking that what you want is to:
Show two points on screen.
Calculate an axis-aligned right triangle that touches those points.
Draw a triangle for the fill and colored lines for the edges of the triangles.
Allow the user to use the mouse to click and drag the points to new locations.
Dynamically update the right triangle based on those points.
It is unclear which part of the above you are having trouble with (other than, perhaps, "all of it"). In general, computer programming is about identifying what you want to do, breaking it down into simple steps (as I did above) and then working on those steps one at a time.
Can you calculate two 'random' points on screen? (Hint: Math.random() might be appropriate, or else you can just pick two fixed starting locations.)
Can you draw two points on screen? (Hint: You can use a SVG <circle> and adjust the cx and cy attributes.)
Can you calculate where the third point should be? (Hint: one way is to use the 'x' value of one point and the 'y' value of the other point.)
Can you draw a filled triangle between these points? (Hint: an easy way is to use an SVG <polygon> and adjust the points attribute.)
Can you draw three lines for the edges? (Hint: use <line> or <polyline> elements that are later in the document than the <polygon> so that they draw on top...but have the <circle> elements even lower in the document so that the circles draw on top of everything else.)
Can you make it so that when the user clicks and drags on the circles they stay under the mouse? (Hint: see this answer and example of mine, or go Google about making SVG elements draggable.)
During your drag handler, can you recalculate your triangle and points and lines and update them all? (Hint: you can either use setAttribute() to update attributes of SVG elements, e.g. setAttribute(myPoly,'points',arrayOfPoints.join()), or you can use SVG DOM bindings—e.g. myPoly.getItem(0).x = 43.)
Your question is too broad and vague currently. Either edit this question to make it specific to your exact desire and the exact code that is not working for you, or create a new question that is similarly targeted. Your code snippet does basically nothing useful for all the code you have in there.

Related

Resizing with handles in interact.js + SVG

In this jsFiddle I have an SVG rect that is resized with interact.js. This works fine, however I need to add resize handles n/ne/e/se/s/sw/w/nw, 8x8 pixel squares at each point. These handles should be used to resize the rect (instead of dragging the rect sides).
I found examples in HTML, not SVG, for example here, but I couldn't figure out how to make this work in SVG instead of HTML. Any help will be greatly appreciated.
var svg = document.getElementById('mysvg');
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
svg.appendChild(rect);
rect.setAttribute('x', 100);
rect.setAttribute('y', 100);
rect.setAttribute('width', 100);
rect.setAttribute('height', 100);
rect.setAttribute('class', 'resize-me');
rect.setAttribute('stroke-width', 2);
rect.setAttribute('stroke', 'white');
rect.setAttribute('fill', 'grey')
interact('.resize-me')
.resizable({
edges: { left: true, right: true, bottom: true, top: true }
})
.on('resizemove', function(event) {
var target = event.target;
var x = (parseFloat(target.getAttribute('endx')) || 0)
var y = (parseFloat(target.getAttribute('endy')) || 0)
target.setAttribute('width', event.rect.width);
target.setAttribute('height', event.rect.height);
x += event.deltaRect.left
y += event.deltaRect.top
target.setAttribute('transform', 'translate(' + x + ', ' + y + ')')
target.setAttribute('endx', x)
target.setAttribute('endy', y)
});
Okay, done. Take a look:
const svg = document.getElementById('mysvg');
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
svg.appendChild(group);
group.appendChild(rect);
group.setAttribute('class', 'resize-me');
rect.setAttribute('x', 100);
rect.setAttribute('y', 100);
rect.setAttribute('width', 100);
rect.setAttribute('height', 100);
rect.setAttribute('stroke-width', 2);
rect.setAttribute('stroke', 'white');
rect.setAttribute('fill', 'grey');
// Create the handles
const handles = [];
for (let i = 0; i < 8; i++) {
const handle = document.createElementNS("http://www.w3.org/2000/svg", "rect");
handle.setAttribute('width', 8);
handle.setAttribute('height', 8);
handle.setAttribute('stroke-width', 1);
handle.setAttribute('stroke', 'white');
handle.setAttribute('fill', 'black');
handles.push(handle);
group.appendChild(handle);
}
// Manually assign them their resize duties (R->L, T->B)
handles[0].classList.add('resize-top', 'resize-left');
handles[1].classList.add('resize-top');
handles[2].classList.add('resize-top', 'resize-right');
handles[3].classList.add('resize-left');
handles[4].classList.add('resize-right');
handles[5].classList.add('resize-bottom', 'resize-left');
handles[6].classList.add('resize-bottom');
handles[7].classList.add('resize-bottom', 'resize-right');
// This function takes the rect and the list of handles and positions
// the handles accordingly
const findLocations = (r, h) => {
const x = Number(r.getAttribute('x'));
const y = Number(r.getAttribute('y'));
const width = Number(r.getAttribute('width'));
const height = Number(r.getAttribute('height'));
// Important these are in the same order as the classes above
let locations = [
[0, 0],
[width / 2, 0],
[width, 0],
[0, height / 2],
[width, height / 2],
[0, height],
[width / 2, height],
[width, height]
];
// Move each location such that it's relative to the (x,y) of the rect,
// and also subtract half the width of the handles to make up for their
// own size.
locations = locations.map(subarr => [
subarr[0] + x - 4,
subarr[1] + y - 4
]);
for (let i = 0; i < locations.length; i++) {
h[i].setAttribute('x', locations[i][0]);
h[i].setAttribute('y', locations[i][1]);
}
}
interact('.resize-me')
.resizable({
edges: {
left: '.resize-left',
right: '.resize-right',
bottom: '.resize-bottom',
top: '.resize-top'
}
})
.on('resizemove', function(event) {
// Resize the rect, not the group, it will resize automatically
const target = event.target.querySelector('rect');
for (const attr of ['width', 'height']) {
let v = Number(target.getAttribute(attr));
v += event.deltaRect[attr];
target.setAttribute(attr, v);
}
for (const attr of ['top', 'left']) {
const a = attr == 'left' ? 'x' : 'y';
let v = Number(target.getAttribute(a));
v += event.deltaRect[attr];
target.setAttribute(a, v);
}
findLocations(rect, handles);
});
findLocations(rect, handles);
svg {
width: 100%;
height: 240px;
background-color: #2e9;
-ms-touch-action: none;
touch-action: none;
}
body {
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/interactjs#latest/dist/interact.min.js"></script>
<svg id="mysvg"></svg>
This works by taking advantage of Interact.js's support for using separate elements as handles, as mentioned in their docs here, as well as the nifty fact that you're using SVG instead of HTML. This means that you don't have to worry about using a CSS transform. Instead, you can just move the rect's x and y, and the calculations become quite a bit simpler.
The only easy-to-miss change I had to make was that I had to put the rectangle and the handles into a group. This is because Interact.js will only let you use a child element as a handle for resizing. This means the listener is on the group, which resizes the rectangle within itself (and then, of course, causes the group to grow, since they match the size of their children).
Let me know if I've missed anything.

KineticJS, Paint like program, brush gaps

I am trying to do something like paint with KineticJS. I am trying to draw the color with circles that originate from the mouse position. However the eventlistener of the mouse position seems too slow and when I move the mouse too fast the circles drawn are far from each other resulting this:
I have seen people filling array with points drawing lines between them, but I thought thats very bad for optimization because after dubbing the screen too much the canvas starts lagging because it has too much lines that it redraws every frame. I decided to cancel the cleaning of the layer and I am adding new circle at the current mouse position and I remove the old one for optimization. However since Im not drawing lines on fast mouse movement it leaves huge gaps. I would be very grateful if anyone can help me with this.
Here is my code:
(function() {
var stage = new Kinetic.Stage({
container: 'main-drawing-window',
width: 920,
height: 750
}),
workplace = document.getElementById('main-drawing-window'),
layer = new Kinetic.Layer({
clearBeforeDraw: false
}),
border = new Kinetic.Rect({
stroke: "black",
strokeWidth: 2,
x: 0,
y: 0,
width: stage.getWidth(),
height: stage.getHeight()
}),
brush = new Kinetic.Circle({
radius: 20,
fill: 'red',
strokeWidth: 2,
x: 100,
y: 300
});
Input = function() {
this.mouseIsDown = false;
this.mouseX = 0;
this.mouseY = 0;
this.offsetX = 0;
this.offsetY = 0;
};
var input = new Input();
document.documentElement.onmousedown = function(ev) {
input.mouseIsDown = true;
};
document.documentElement.onmouseup = function(ev) {
input.mouseIsDown = false;
};
document.documentElement.onmousemove = function(ev) {
ev = ev || window.event;
// input.mouseX = (ev.clientX - workplace.offsetLeft);
// input.mouseY = (ev.clientY - workplace.offsetTop);
input.mouseX = (ev.offsetX);
input.mouseY = (ev.offsetY);
};
function DistanceBetweenPoints(x1, y1, x2, y2) {
return Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)));
}
var canvasDraw = setInterval(function() {
// console.log(input);
if (input.mouseIsDown) {
workplace.style.cursor = "crosshair";
var currentBrushPosition = brush.clone();
currentBrushPosition.setX(input.mouseX);
currentBrushPosition.setY(input.mouseY);
// var distance = DistanceBetweenPoints(brush.getX(), brush.getY(), currentBrushPosition.getX(), currentBrushPosition.getY());
// if (distance > brush.getRadius() * 2) {
// var fillingLine = new Kinetic.Line({
// points: [brush.getX(), brush.getY(), currentBrushPosition.getX(), currentBrushPosition.getY()],
// stroke: 'yellow',
// strokeWidth: brush.getRadius()*2,
// lineJoin: 'round'
// });
// // layer.add(fillingLine);
// }
layer.add(currentBrushPosition);
brush.remove();
brush = currentBrushPosition;
layer.draw();
// if (fillingLine) {
// fillingLine.remove();
// }
}
if (!input.mouseIsDown) {
workplace.style.cursor = 'default';
}
}, 16);
layer.add(border);
stage.add(layer);
})();
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Coloring Game</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/kineticjs/5.2.0/kinetic.min.js"></script>
</head>
<body>
<div id="main-drawing-window"></div>
<script type="text/javascript" src="./JS files/canvas-draw.js"></script>
</body>
</html>
Don't use individual Kinetic.Circles for each mousemove. Every Kinetic object is a "managed" object and that management takes up a lot of resources. KineticJS will slow to a crawl as the number of circles increases with every mousemove.
Instead, use a Kinetic.Shape and draw you circles onto the canvas with
// This is Pseudo-code since I haven't worked with KineticJS in a while
shapeContext.beginPath();
shapeContext.arc(mouseX,mouseY,20,0,Math.PI*2);
shapeContext.fillStrokeShape(this);
This will probably clear your problem, but if the mouse is moved very far in a single mousemove then you might have to draw a lineTo (instead of arc) between the last mouse point and the current far-away mouse point.

Tweened Kinetic.js shape doesn't fire most of clicks

Purpose
I'm making a simple "shoot the word" game, where user needs to click on some moving rectangles with words to "shoot" them.
Problem
So i create some objects and move them using simple kinetic.js tweening.
Word creation
function createWord(value){
//here comes some word object construction
var wordGroup = new Kinetic.Group({
x: 0,
y: 0
});
var padding = 10;
wordGroup.label = new Kinetic.Text({
x: padding,
y: padding,
text: value,
fontFamily: 'Times New Roman',
fontSize: 30,
fill: 'white'
});
wordGroup.tag = new Kinetic.Rect({
x: 0,
y: 0,
width: wordGroup.label.width() + (padding << 1),
height: wordGroup.label.height() + (padding << 1),
fill: 'black',
shadowColor: 'black',
shadowBlur: 10,
shadowOffset: {x:10,y:20},
shadowOpacity: 0.5,
cornerRadius: 10
});
wordGroup.add(wordGroup.tag);
wordGroup.add(wordGroup.label);
wordGroup.shoot = function(){ //shooting mechanism (simple stop from moving and remove from scene)
wordGroup.tween.pause();
wordGroup.clean();
dropNextWord(); //drops fresh blood! (new word instead of shooted)
}
wordGroup.clean = function(){ //remove from scene and set it free to drop again
wordGroup.remove();
wordGroup.isActive = false;
}
wordGroup.move = function(callback){ //animates word
wordLayer.add(wordGroup);
moveToSide(wordGroup, callback); //calls moving function
}
wordGroup.on('click', function(e){
wordGroup.shoot();
});
return wordGroup;
}
Tweening part
//move word to opposite side
function moveToSide(word, callback){
var side = Math.random();
var d = 100;
spawnFromSide(word, side); //set random side word position
tweenPosition = {
x: word.x(),
y: word.y()
}
if(side < 0.25){ //left
tweenPosition.x = - d;
} else if(side > 0.25 && side < 0.5){ //right
tweenPosition.x = defaultStageWidth + d;
} else if(side > 0.5 && side < 0.75){ //up
tweenPosition.y = - d;
} else { //down
tweenPosition.y = defaultStageHeight + d;
}
word.tween = new Kinetic.Tween({
node: word,
duration: 4,
easing: Kinetic.Easings.Linear,
x: tweenPosition.x,
y: tweenPosition.y,
onFinish: function(){
word.clean();
callback();
}
});
word.tween.play();
}
But the problem is that click event doesn't fire on large amount of user clicks. As i think, this caused by delayed drawHit() calls inside tweening mechanism, that draws new object position before updating the hit area, so when we shoot object thinking that we hit its current position we miss because its hit area still have the same old position.
Live example
http://jsfiddle.net/hd6z21de/7/
Take a minute on shooting to see this effect in action
Solved this weird behavior by listening canvas touches and check if pointer collide some target word-rect by myself instead of using their own onclick events.
//i listen to canvas because of my app specific, you could simple listen your own layer or even document
$("canvas").bind('click', function(event){
var x = (event.pageX) / stage.scaleX(); //you don't need to divide by scale if your stage isn't scaled as mine does
var y = (event.pageY) / stage.scaleY();
var wordArray = wordGroup.getChildren();
for(var i = 0; i < wordArray.length; i++){ //go through all words and check if we shoot someone (is mouse position included in some word rect)
if(x > wordArray[i].x() &&
y > wordArray[i].y() &&
x < (wordArray[i].x() + wordArray[i].width()) &&
y < (wordArray[i].y() + wordArray[i].height())){
wordArray[i].shoot(); //shoot that word
break;
}
}
}

How can I constrain drawing on the <canvas>?

I want to create a mobile web page where a shape appears on the screen, the user can only traces over the outline of the shape with his/her finger and then a new shape will appear. This library has a few good examples of what I am looking to do, just with more shapes. I have already found a couple of good examples for drawing on the canvas on a touch device here and here. The thing I don't know is how to constrain the line so you are only drawing on the path with a single continuous line. Is there something built in that will let me specify the only path you can draw, or do I have to write that logic by hand?
We can split the issue into two parts :
1) knowing if the user is on the path.
2) knowing if the user went on all path parts.
For 1), we can use the isPointInPath context2D method to know if the mouse/touch point (x,y) is on the curve. The constraint here is that you must build a closed surface, meaning a surface drawn by a fill(), not one built with a stroke(). So in case you are stroking thick lines, you have to do some little math to build the corresponding figures out of moveTo+lineTo+fill.
For 2) : build a list of 'check-points' for your shape. You might have, for instance 8 control points for a circle. Then decide of a distance at which the user will 'activate' the check point. Now the algorithm is, in pseudo-code:
mouseDown => check()
mouseMove => if mouse is down, check()
checkPointList = [ [ 10, 40, false ] , [ centerX, centerY, isChecked], ... ] ;
checked = 0;
function check() {
clear screen
draw the path
if (mouse down and mouse point on path) {
for ( checkPoint in CheckPointList) {
if (checkPoint near enough of mouse) {
checkPoint[3]=true;
checked++;
}
}
if (checked == checkPointList.length) ==>>> User DID draw all the shape.
} else
clear the flags of the checkPointList;
checked=0;
}
I did a moooost simple demo here, which quites work.
The control points are shown in red when disactivated, green when activated :
http://jsbin.com/wekaxiguwiyo/1/edit?js,output
// boilerplate
var cv = document.getElementById('cv');
var ctx = cv.getContext('2d');
function draw() {
ctx.clearRect(0,0,300,300);
drawShape();
drawCP();
}
// Shape
function drawShape() {
ctx.beginPath();
ctx.moveTo(30,5);
ctx.lineTo(80,5);
ctx.lineTo(80, 300);
ctx.lineTo(30,300);
ctx.closePath();
ctx.lineWidth= 16;
ctx.fillStyle='#000';
ctx.fill();
}
// Control points
var points = [ [50, 50, false], [50,120, false], [50, 190, false],[50,260, false ] ];
var pointsCount = 0;
function drawCP() {
for (var i=0; i<points.length; i++) {
var p = points[i];
ctx.fillStyle=p[2]?'#0F0':'#F00';
ctx.fillRect(p[0]-1, p[1]-1, 2, 2);
}
}
function resetCP() {
for (var i=0; i<points.length; i++) {
points[i][2]=false;
}
pointsCount=0;
}
function testCP(x,y) {
var d=30;
d=sq(d);
for (var i=0; i<points.length; i++) {
if (sq(points[i][0]-x)+sq(points[i][1]-y)<d) {
if (!points[i][2]) pointsCount++;
points[i][2]=true
};
}
}
function sq(x) { return x*x; }
//
draw();
// most simple event handling
addEventListener('mousemove', mouseMove);
var r = cv.getBoundingClientRect();
function mouseMove(e) {
var x = e.pageX-r.left;
var y = e.pageY-r.top;
draw();
ctx.fillStyle='#000';
if (ctx.isPointInPath(x,y)) { 
ctx.fillStyle='#F00';
testCP(x,y);
} else {
resetCP();
}
ctx.fillRect(x-3,y-3,6,6);
var pathDrawn = (pointsCount == points.length);
if (pathDrawn) ctx.fillText('Shape drawn!!', 150, 150);
}

KineticJS Canvas - Scale group from center point

I want to scale my group (image and something).
group.setScale(zoom, zoom);
http://jsfiddle.net/K8nK3/
But when i scale my group, I think it's not scale from center of my group. i think it like this
I want it scale from center like
I try more but it's not success. How can i do that, thanks.
[Edited to better fit questioner's needs]
Here’s how to scale a Kinetic group from the centerpoint
First we store the centerX and centerY of the original group so we can keep the group centered there:
group.cx=group.getX()+group.getWidth()/2;
group.cy=group.getY()+group.getHeight()/2;
Then we write a custom scaling method that both scales the group and re-centers it:
group.scale=function(x,y){
group.setScale(x,y);
group.setPosition(
group.cx-group.getWidth()/2*group.getScale().x,
group.cy-group.getHeight()/2*group.getScale().y);
group.draw();
}
Here’s code and a Fiddle: http://jsfiddle.net/m1erickson/dVGGz/
<!DOCTYPE HTML>
<html>
<head></head>
<body>
<button id="larger">Larger</button>
<div id="container"></div>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.4.0.min.js"></script>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width: 400,
height: 400
});
var layer = new Kinetic.Layer();
// Be sure to set width and height
// They are required for this method to work
var group = new Kinetic.Group({
x: 100,
y: 100,
width:100,
height:100
});
// store the original group center
// so we can center the group there
group.cx=group.getX()+group.getWidth()/2;
group.cy=group.getY()+group.getHeight()/2;
// custom scale function to both
// scale the group and center the results
group.scale=function(x,y){
group.setScale(x,y);
group.setPosition(
group.cx-group.getWidth()/2*group.getScale().x,
group.cy-group.getHeight()/2*group.getScale().y);
group.draw();
}
var box1 = new Kinetic.Rect({
x: 0,
y: 0,
width: 50,
height: 50,
fill: "blue",
stroke: 'black',
strokeWidth: 4
});
group.add(box1);
var box2 = new Kinetic.Rect({
x: 50,
y: 50,
width: 50,
height: 50,
fill: "green",
stroke: 'black',
strokeWidth: 4
});
group.add(box2);
layer.add(group);
stage.add(layer);
var scaleFactor=1;
$("#larger").click(function(){
scaleFactor+=0.10;
group.scale(scaleFactor,scaleFactor);
console.log(scaleFactor);
});
});
</script>
</body>
</html>
Have you tried to use offset property? i.e. offset: [width/2, height/2]
Problem #1: I need to scale a group from its center.
Problem #2: Kinetic JS group.getWidth() and group.getHeight() don't work unless you explicitly set the width at creation.
Problem #3: I don't know the width and height, I'm dynamically changing the shapes.
This means in order to try your code out, I'll have to write my own getWidth and getHeight functions. Then I can use the method you propose.
Solution below.
(There may be a better way, but this is my level of programming right now . . . if anyone can add recursion to this to look through groups that contain groups that contain groups etc, that would be great.)
var results = getGroupMinsAndMaxes(myKineticGroup);
console.log('results: '+ results.groupMinX, results.groupMaxX,
results.groupMinY, results.groupMaxY);
function getGroupMinsAndMaxes( group ) {
// note this does not handle children that are groups,
// it expects children to be shapes
var item;
var groupMinX = 99999;
var groupMaxX = -99999;
var groupMinY = 99999;
var groupMaxY = -99999;
var shapeFunctionDefinition = '';
var bounds = {};
for (var i = 0; i < group.children.length; i++ ) {
item = group.children[ i ];
if ( item.getType() !== 'Shape' ) {
alert('The getGroupMinsAndMaxes function is not designed to handle groups that contain groups.');
break;
} else {
shapeFunctionDefinition = item.attrs.drawFunc.toString();
bounds = getShapeMinsAndMaxes( shapeFunctionDefinition );
if ( bounds.shapeMinX < groupMinX ) { groupMinX = bounds.shapeMinX; }
if ( bounds.shapeMaxX > groupMaxX ) { groupMaxX = bounds.shapeMaxX; }
if ( bounds.shapeMinY < groupMinY ) { groupMinY = bounds.shapeMinY; }
if ( bounds.shapeMaxY > groupMaxY ) { groupMaxY = bounds.shapeMaxY; }
}
}
return {
groupMinX: groupMinX,
groupMaxX: groupMaxX,
groupMinY: groupMinY,
groupMaxY: groupMaxY
};
}
function getShapeMinsAndMaxes( shape ) {
// the shape definition is passed as a string
var regex = /[+-]?\d+\.\d+/g;
var floats = shape.match(regex).map(function (v) {
return parseFloat(v);
});
// preset some unrealistically large numbers which
// we will never encounter
var shapeMinX = 99999;
var shapeMaxX = -99999;
var shapeMinY = 99999;
var shapeMaxY = -99999;
for (var i = 0; i < floats.length; i++) {
if (isOdd(i)) {
// Check Y values
if (floats[i] < shapeMinY) {
shapeMinY = floats[i];
}
if (floats[i] > shapeMaxY) {
shapeMaxY = floats[i];
}
} else {
// Check X values
if (floats[i] < shapeMinX) {
shapeMinX = floats[i];
}
if (floats[i] > shapeMaxX) {
shapeMaxX = floats[i];
}
}
}
return {
shapeMinX: shapeMinX,
shapeMaxX: shapeMaxX,
shapeMinY: shapeMinY,
shapeMaxY: shapeMaxY
};
}
function isOdd(num) {
return num % 2;
}
the easiest way would be to reposition / center the offset. all scaling and moving will be based on your new offset position.
group.offset( { x: group.width() * .5, y: group.height() * .5 } );
http://kineticjs.com/docs/Kinetic.Group.html

Categories