I've the below code that is functioning properly, create the path, and rotate it upon the click.
I want to rotate the path about specific point, when i use the rotate(45 50 50) below instead of the rotate(x) i get this error: VM267:85 Uncaught SyntaxError: missing ) after argument list what shall I do?
Note NOt interested to use any ready library to handle the task, need to so it using the standard API only. thanks
var NS="http://www.w3.org/2000/svg";
var SVG=function(el){
return document.createElementNS(NS,el);
}
svg = SVG('svg');
svg.setAttribute("width", "100%");
svg.setAttribute("height", "100%");
// svg.width='50em'; // Not working
document.body.appendChild(svg);
var bbox = svg.getBoundingClientRect();
var center = {
x: bbox.left + bbox.width/2,
y: bbox.top + bbox.height/2
};
class myPath {
constructor(cx,cy) {
this.path=SVG('path');
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
// https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths
var d="M" + cx + " " + cy;
d = d + "L175 120 L125 120 Z";
this.path.setAttribute("d", d);
this.path.setAttribute("fill", "#F7931E");
this.path.addEventListener("click",this,false);
}
get draw(){
return this.path;
}
}
myPath.prototype.rotate = function(x) {
/*
var path = this.path.getBoundingClientRect();
var Pc = {
x: bbox.left + bbox.width/2,
y: bbox.top + bbox.height/2
}; */
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix().rotate(x));
// https://developer.mozilla.org/en/docs/Web/SVG/Attribute/transform
}
myPath.prototype.animate = function() {
self = this.path;
self.transform.baseVal.appendItem(this.rotate(5));
};
myPath.prototype.handleEvent= function(evt){
self = evt.target;
console.log(self.getAttribute('d'));
self.move = setInterval(()=>this.animate(),100);
}
svg.appendChild(new myPath(center.x,center.y).draw);
rotate(45 50 50) is the format for the transform XML attribute. For example:
<path d="..." transform="rotate(45 50 50)" .../>
But you are using the Javascript rotate() function on the SVGTransform object. JS functions require commas between parameters. Try:
rotate(45, 50, 50)
https://developer.mozilla.org/en/docs/Web/API/SVGTransform
I was able to solve it using translate(<x>, <y>) rotate(<a>) translate(-<x>, -<y>) as per this link
var NS="http://www.w3.org/2000/svg";
var SVG=function(el){
return document.createElementNS(NS,el);
}
svg = SVG('svg');
svg.setAttribute("width", "100%");
svg.setAttribute("height", "100%");
svg.setAttribute("fill", "green");
document.body.appendChild(svg);
bbox = svg.getBoundingClientRect();
center = {
x: this.bbox.left + this.bbox.width/2,
y: this.bbox.top + this.bbox.height/2
};
class myPath {
constructor(cx,cy) {
this.path=SVG('path');
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
// https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths
var d="M" + cx + " " + cy;
d = d + "h75 v75 h-75 z";
this.path.setAttribute("d", d);
this.path.setAttribute("fill", "#F7931E");
this.path.addEventListener("click",this,false);
this.Pbox = svg.getBoundingClientRect();
this.Pc = {
x: this.Pbox.left + this.Pbox.width/2,
y: this.Pbox.top + this.Pbox.height/2
};
}
get draw(){
return this.path;
}
}
myPath.prototype.rotate = function(x) {
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix().rotate(x));
// https://developer.mozilla.org/en/docs/Web/SVG/Attribute/transform
}
myPath.prototype.move = function(x,y) {
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix().translate(x,y));
// https://developer.mozilla.org/en/docs/Web/SVG/Attribute/transform
}
myPath.prototype.animate = function() {
self = this.path;
self.transform.baseVal.appendItem(this.move(this.Pc.x,this.Pc.y));
self.transform.baseVal.appendItem(this.rotate(5));
self.transform.baseVal.appendItem(this.move(-this.Pc.x,-this.Pc.y));
};
myPath.prototype.handleEvent= function(evt){
self = evt.target;
console.log(self.getAttribute('d'));
self.move = setInterval(()=>this.animate(),100);
}
svg.appendChild(new myPath(center.x,center.y).draw);
Related
I stucked a little.
I have a code that is working in codepen and if I paste it in opened F12 window it will work fine, so it is ok.
The idea of this svg code was that I can hover not exactly on path, but somewhere near, for it to change color.
But I need to generate exact same code using javascipt, I wrote 2 classes, used them, they now generate same code as in codepen link, I can see it in F12 dev window, but it doesn't work.
My javascript code:
class Polyline {
// https://codepen.io/francoisromain/pen/dzoZZj
constructor(points, smoothing, fill, color) {
this.points = points;
this.smoothing = (smoothing === undefined) ? 0.2 : smoothing;
this.fill = fill;
this.color = color;
}
line (pointA, pointB) {
const lengthX = pointB[0] - pointA[0];
const lengthY = pointB[1] - pointA[1];
return {
length: Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)),
angle: Math.atan2(lengthY, lengthX)
}
}
controlPoint (current, previous, next, reverse) {
const p = previous || current;
const n = next || current;
const o = this.line(p, n);
const angle = o.angle + (reverse ? Math.PI : 0);
const length = o.length * this.smoothing;
const x = current[0] + Math.cos(angle) * length;
const y = current[1] + Math.sin(angle) * length;
return [x, y];
}
bezierCommand(point, i, a) {
const cps = this.controlPoint(a[i - 1], a[i - 2], point);
const cpe = this.controlPoint(point, a[i - 1], a[i + 1], true);
return `C ${cps[0]},${cps[1]} ${cpe[0]},${cpe[1]} ${point[0]},${point[1]}`;
}
svgPath(points, svg) {
const d = points.reduce((acc, point, i, a) => i === 0
? `M ${point[0]},${point[1]}`
: `${acc} ${this.bezierCommand(point, i, a)}`
, '');
const defs = document.createElement( 'defs' );
defs.innerHTML = "<path id='poly-1' d='" + d + "' fill=" + this.fill + "></path>";
svg.appendChild(defs);
const g = document.createElement( 'g' );
g.setAttribute("id", "group");
svg.appendChild(g);
const use1 = document.createElement( 'use' );
use1.setAttribute("xlink:href", "#poly-1");
use1.setAttribute("stroke-width", "15");
use1.setAttribute("pointer-events", "stroke");
g.appendChild(use1);
const use2 = document.createElement( 'use' );
use2.setAttribute("class", "use");
use2.setAttribute("xlink:href", "#poly-1");
g.appendChild(use2);
}
draw() {
const svg = document.getElementById("chart-main-canvas");
this.svgPath(this.points, svg);
}
}
class Canvas {
constructor(w, h, color) {
this.width = w;
this.height = h;
this.color = color;
// this.scale = 1;
}
canvas() {
return `<svg width="${this.width}" height="${this.height}" id="chart-main-canvas" style="background-color: ${this.color}; z-index:5000;" ></svg>`;
}
draw() {
const c = document.getElementById("lifespan-chart-content");
c.innerHTML = this.canvas();
}
}
window.addEventListener('load', function() {
c1 = new Canvas(1000, 500, "bisque");
c1.draw();
var smoothing = 0.2;
var fill = "green";
var color = "red";
const points = [
[5, 10],
[100, 400],
[200, 400],
[355, 50],
[500, 500]
]
p1 = new Polyline(points, smoothing, fill, color);
p1.draw();
});
Why and how I can fix it?
Another additional link on working svg example:link
PS I pretty sure that the problem method is svgPath in Polyline class
edit 1
edited function:
svgPath(points, svg) {
const d = points.reduce((acc, point, i, a) => i === 0
? `M ${point[0]},${point[1]}`
: `${acc} ${this.bezierCommand(point, i, a)}`
, '');
const defs = document.createElementNS("http://www.w3.org/2000/svg", 'defs' );
defs.innerHTML = "<path id='poly-1' d='" + d + "' fill=" + this.fill + "></path>";
svg.appendChild(defs);
const g = document.createElementNS("http://www.w3.org/2000/svg", 'g' );
g.setAttribute("id", "group");
svg.appendChild(g);
const use1 = document.createElementNS("http://www.w3.org/2000/svg",'use' );
use1.setAttribute("xlink:href", "#poly-1");
use1.setAttribute("stroke-width", "15");
use1.setAttribute("pointer-events", "stroke");
g.appendChild(use1);
const use2 = document.createElementNS("http://www.w3.org/2000/svg", 'use' );
use2.setAttribute("class", "use");
use2.setAttribute("xlink:href", "#poly-1");
g.appendChild(use2);
}
still doesnt work
You can also use this technique to draw SVG, because it is not that simple to just innerHTML them
// Your SVG code (formed as you wish)
const svgFromJS = `<svg width="1000" height="500" id="chart-main-canvas" style="background-color: bisque; z-index:5000;">
<defs>
<path id="poly-1" d="M 5,10 C 24,88 60.99999999999998,322 100,400 C 139,478 149,470 200,400 C 251,330 295,30.000000000000007 355,50 C 415,70 470.99999999999994,410 500,500" fill="none"></path>
</defs>
<g id="group">
<use xlink:href="#poly-1" stroke-width="15" pointer-events="stroke"></use>
<use class="use" xlink:href="#poly-1"></use>
</g>
</svg>`;
// Create a tmp container
const c = document.createElement('div');
// Add SVG to tmp container
c.innerHTML = '' + svgFromJS;
// Move childs of the SVG inside tmp container to target SVG at the body
Array.prototype.slice.call(c.childNodes[0].childNodes).forEach(function(el) {
document.getElementById('swgtodraw').appendChild(el);
});
#group .use{
stroke:red
}
#group:hover .use{
stroke: green;
}
<svg id="swgtodraw" width="1000" height="500" style="background-color: bisque; z-index:5000;"></svg>
I would like to draw directed arcs between rectangles (nodes that are represented by rectangles) in such a way that the arrow-tip always hits the edge in a graceful way. I have seen plenty of SO posts on how to do this for circles (nodes represented by circles). Quite interestingly, most d3 examples deal with circles and squares (though squares to a lesser extent).
I have an example code here. Right now my best attempt can only draw from center-point to center-point. I can shift the end point (where the arrow should be), but upon experimenting with dragging the rectangles around, the arcs don't behave as intended.
Here's what I've got.
But I need something like this.
Any ideas on how I can easily do this in d3? Is there some built-in library/function that can help with this type of thing (like with the dragging capabilities)?
A simple algorithm to solve your problem is
when a node is dragged do the following for each of its incoming/outgoing edges
let a be the node dragged and b the node reached through the outgoing/incoming edge
let lineSegment be a line segment between the centers of a and b
compute the intersection point of a and lineSegment, this is done by iterating the 4 segments that make the box and checking the intersection of each of them with lineSegment, let ia be the intersection point of one of the segments of a and lineSegment, find ib in a similar fashion
Corner cases that I have considered but haven't solved
when a box's center is inside the other box there won't be 2 segment intersections
when both intersections points are the same! (solved this one in an edit)
when your graph is a multigraph edges would render on top of each other
plunkr demo
EDIT: added the check ia === ib to avoid creating an edge from the top left corner, you can see this on the plunkr demo
$(document).ready(function() {
var graph = {
nodes: [
{ id: 'n1', x: 10, y: 10, width: 200, height: 200 },
{ id: 'n2', x: 10, y: 270, width: 200, height: 250 },
{ id: 'n3', x: 400, y: 270, width: 200, height: 300 }
],
edges: [
{ start: 'n1', stop: 'n2' },
{ start: 'n2', stop: 'n3' }
],
node: function(id) {
if(!this.nmap) {
this.nmap = { };
for(var i=0; i < this.nodes.length; i++) {
var node = this.nodes[i];
this.nmap[node.id] = node;
}
}
return this.nmap[id];
},
mid: function(id) {
var node = this.node(id);
var x = node.width / 2.0 + node.x,
y = node.height / 2.0 + node.y;
return { x: x, y: y };
}
};
var arcs = d3.select('#mysvg')
.selectAll('line')
.data(graph.edges)
.enter()
.append('line')
.attr({
'data-start': function(d) { return d.start; },
'data-stop': function(d) { return d.stop; },
x1: function(d) { return graph.mid(d.start).x; },
y1: function(d) { return graph.mid(d.start).y; },
x2: function(d) { return graph.mid(d.stop).x; },
y2: function(d) { return graph.mid(d.stop).y },
style: 'stroke:rgb(255,0,0);stroke-width:2',
'marker-end': 'url(#arrow)'
});
var g = d3.select('#mysvg')
.selectAll('g')
.data(graph.nodes)
.enter()
.append('g')
.attr({
id: function(d) { return d.id; },
transform: function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
}
});
g.append('rect')
.attr({
id: function(d) { return d.id; },
x: 0,
y: 0,
style: 'stroke:#000000; fill:none;',
width: function(d) { return d.width; },
height: function(d) { return d.height; },
'pointer-events': 'visible'
});
function Point(x, y) {
if (!(this instanceof Point)) {
return new Point(x, y)
}
this.x = x
this.y = y
}
Point.add = function (a, b) {
return Point(a.x + b.x, a.y + b.y)
}
Point.sub = function (a, b) {
return Point(a.x - b.x, a.y - b.y)
}
Point.cross = function (a, b) {
return a.x * b.y - a.y * b.x;
}
Point.scale = function (a, k) {
return Point(a.x * k, a.y * k)
}
Point.unit = function (a) {
return Point.scale(a, 1 / Point.norm(a))
}
Point.norm = function (a) {
return Math.sqrt(a.x * a.x + a.y * a.y)
}
Point.neg = function (a) {
return Point(-a.x, -a.y)
}
function pointInSegment(s, p) {
var a = s[0]
var b = s[1]
return Math.abs(Point.cross(Point.sub(p, a), Point.sub(b, a))) < 1e-6 &&
Math.min(a.x, b.x) <= p.x && p.x <= Math.max(a.x, b.x) &&
Math.min(a.y, b.y) <= p.y && p.y <= Math.max(a.y, b.y)
}
function lineLineIntersection(s1, s2) {
var a = s1[0]
var b = s1[1]
var c = s2[0]
var d = s2[1]
var v1 = Point.sub(b, a)
var v2 = Point.sub(d, c)
//if (Math.abs(Point.cross(v1, v2)) < 1e-6) {
// // collinear
// return null
//}
var kNum = Point.cross(
Point.sub(c, a),
Point.sub(d, c)
)
var kDen = Point.cross(
Point.sub(b, a),
Point.sub(d, c)
)
var ip = Point.add(
a,
Point.scale(
Point.sub(b, a),
Math.abs(kNum / kDen)
)
)
return ip
}
function segmentSegmentIntersection(s1, s2) {
var ip = lineLineIntersection(s1, s2)
if (ip && pointInSegment(s1, ip) && pointInSegment(s2, ip)) {
return ip
}
}
function boxSegmentIntersection(box, lineSegment) {
var data = box.data()[0]
var topLeft = Point(data.x, data.y)
var topRight = Point(data.x + data.width, data.y)
var botLeft = Point(data.x, data.y + data.height)
var botRight = Point(data.x + data.width, data.y + data.height)
var boxSegments = [
// top
[topLeft, topRight],
// bot
[botLeft, botRight],
// left
[topLeft, botLeft],
// right
[topRight, botRight]
]
var ip
for (var i = 0; !ip && i < 4; i += 1) {
ip = segmentSegmentIntersection(boxSegments[i], lineSegment)
}
return ip
}
function boxCenter(a) {
var data = a.data()[0]
return Point(
data.x + data.width / 2,
data.y + data.height / 2
)
}
function buildSegmentThroughCenters(a, b) {
return [boxCenter(a), boxCenter(b)]
}
// should return {x1, y1, x2, y2}
function getIntersection(a, b) {
var segment = buildSegmentThroughCenters(a, b)
console.log(segment[0], segment[1])
var ia = boxSegmentIntersection(a, segment)
var ib = boxSegmentIntersection(b, segment)
if (ia && ib) {
// problem: the arrows are drawn after the intersection with the box
// solution: move the arrow toward the other end
var unitV = Point.unit(Point.sub(ib, ia))
// k = the width of the marker
var k = 18
ib = Point.sub(ib, Point.scale(unitV, k))
return {
x1: ia.x,
y1: ia.y,
x2: ib.x,
y2: ib.y
}
}
}
var drag = d3.behavior.drag()
.origin(function(d) {
return d;
})
.on('dragstart', function(e) {
d3.event.sourceEvent.stopPropagation();
})
.on('drag', function(e) {
e.x = d3.event.x;
e.y = d3.event.y;
var id = 'g#' + e.id
var target = d3.select(id)
target.data().x = e.x
target.data().y = e.y
target.attr({
transform: 'translate(' + e.x + ',' + e.y + ')'
});
d3.selectAll('line[data-start=' + e.id + ']')
.each(function (d) {
var line = d3.select(this)
var other = d3.select('g#' + line.attr('data-stop'))
var intersection = getIntersection(target, other)
intersection && line.attr(intersection)
})
d3.selectAll('line[data-stop=' + e.id + ']')
.each(function (d) {
var line = d3.select(this)
var other = d3.select('g#' + line.attr('data-start'))
var intersection = getIntersection(other, target)
intersection && line.attr(intersection)
})
})
.on('dragend', function(e) {
});
g.call(drag);
})
svg#mysvg { border: 1px solid black;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg id="mysvg" width="800" height="800">
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refx="0" refy="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,6 L9,3 z" fill="#f00" />
</marker>
</defs>
</svg>
Here's the result: https://jsfiddle.net/he0f4u23/2/
For source arrows I just filled rectangles with white to paint the arrow.
For target it is a little bit more trickier than you think. You have to calculate source and target rectangles positions and draw your arrow accordingly.
I've made a tarmid function with addition to you mid function. Your mid function calculates the arrows source point which is fine. But for target point I used the tarmid function which is:
tarmid: function(d) {
var startnode = this.node(d.start);
var endnode = this.node(d.stop);
if(startnode.x == endnode.x && startnode.y <= endnode.y){
var x = endnode.width / 2.0 + endnode.x,
y = endnode.y -17;
}else if(startnode.x < endnode.x && startnode.y <= endnode.y){
var x = endnode.x-17,
y = endnode.y + startnode.height / 2.0;
}
return { x: x, y: y };
}
see how I calculated the target point according to the rectangle placement.
Also notice that these two are not the only cases for all rectangle placement and you must update your function accordingly so I'm leaving the rest to you.
I'm attempting to have a draggable element snap back to the position of another element in Rapheal after dragging it. The problem I'm experiencing is that the .mouseup() function only executes the functions within it once. After you drag or move the element again, it will not longer execute the positioning functions I have within it.
My end goal is:
Drag the red square
When the red square is let go off (mouseup), snap square back to the blue square position.
Here is the code I've tried using, but I can't seem to get it to function correctly:
JSfiddle: http://jsfiddle.net/4GWEU/3/
Javascript:
//Makes elements Draggable.
Raphael.st.draggable = function() {
var me = this,
lx = 0,
ly = 0,
ox = 0,
oy = 0,
moveFnc = function(dx, dy) {
lx = dx + ox;
ly = dy + oy;
me.transform('t' + lx + ',' + ly);
},
startFnc = function() {
//window.draggedElement = this;
},
endFnc = function() {
ox = lx;
oy = ly;
};
this.drag(moveFnc, startFnc, endFnc);
};
var container = document.getElementById('container');
var paper = Raphael(container, '539', '537');
var shape1 = paper.rect(50,50, 50,50);
shape1.attr({x: '50',y: '50',fill: 'red','stroke-width': '0','stroke-opacity': '1'});
shape1Set = paper.set(shape1);
shape1Set.draggable();
var shape2 = paper.rect(50,50, 50,50);
shape2.attr({x: '150',y: '50',fill: 'blue','stroke-width': '0','stroke-opacity': '1'});
shape1Set.mousedown(function(event) {
console.log('mousedown');
});
shape1Set.mouseup(function(event) {
console.log('mouseup');
positionElementToElement(shape1, shape2);
});
$('#runPosition').click(function () {
positionElementToElement(shape1, shape2);
});
$('#runPosition2').click(function () {
positionElementToElement2(shape1, shape2);
});
function positionElementToElement(element, positionTargetElement)
{
var parentBBox = positionTargetElement.getBBox();
parent_x = parentBBox.x;
parent_y = parentBBox.y;
parent_width = parentBBox.width;
parent_height = parentBBox.height;
var elementBBox = element.getBBox();
element_width = elementBBox.width;
element_height = elementBBox.height;
var x_pos = parent_x + (parent_width / 2) - (element_width / 2) + 100;
var y_pos = parent_y + (parent_height / 2) - (element_height / 2) + 100;
console.log('Positioning element to: '+x_pos+' '+y_pos);
element.animate({'x' : x_pos, 'y' : y_pos}, 100);
}
function positionElementToElement2(element, positionTargetElement)
{
var parentBBox = positionTargetElement.getBBox();
parent_x = parentBBox.x;
parent_y = parentBBox.y;
parent_width = parentBBox.width;
parent_height = parentBBox.height;
var elementBBox = element.getBBox();
element_width = elementBBox.width;
element_height = elementBBox.height;
var x_pos = parent_x + (parent_width / 2) - (element_width / 2);
var y_pos = parent_y + (parent_height / 2) - (element_height / 2);
console.log('Positioning element to: '+x_pos+' '+y_pos);
element.animate({'x' : x_pos, 'y' : y_pos}, 100);
}
HTML:
Run Position
Run Position2
<div id="container"></div>
Notes:
I've duplicated the positionElementToElement() function and set one of them with an offset. I've binded both functions to the Run Position 1 and Run Position 2 links.
After dragging the item, clicking the Run Position 1 link no longer sets the square back where it should go (even though the function is logging the same x/y coordinates as when it worked.
I've figured out how to do this properly.
You have to modify the x and y attributes of the element directly.
It's also important to note that when retrieving the x and y attributes from an element using element.attr('x'); or element.attr('y'); it returns a string value, not an integer. Because of this, you have to use parseInt() on these returned values to properly add up the movement x and y values to apply to the element when it moves.
The following code will snap the red square to the blue square, when the red square is moved.
Working Example: http://jsfiddle.net/naQQ2/2/
window.onload = function () {
var R = Raphael(0, 0, "100%", "100%"),
shape1 = R.rect(50,50, 50,50);
shape1.attr({x:'50',y:'50',fill: 'red','stroke-width': '0','stroke-opacity': '1'});
shape2 = R.rect(50,50, 50,50);
shape2.attr({x:'150',y:'50',fill: 'blue','stroke-width': '0','stroke-opacity': '1'});
var start = function () {
console.log(this);
this.ox = parseInt(this.attr('x'));
this.oy = parseInt(this.attr('y'));
this.animate({opacity: .25}, 500, ">");
},
move = function (dx, dy) {
this.attr({x: this.ox + dx, y: this.oy + dy});
},
up = function () {
//Snap to shape2 on mouseup.
var snapx = parseInt(shape2.attr("x"));
snapy = parseInt(shape2.attr("y"));
this.animate({x: snapx, y: snapy}, 100);
this.animate({opacity: 1}, 500, ">");
};
R.set(shape1, shape2).drag(move, start, up);
};
i have an image with 8 anchor points. Thanks to an example, i've managed to get those on the 4 corners to only scale the picture. But i am having difficulty in making the other 4 to stretch the image ONLY.
The midTop & midBottom anchors shall stretch vertically; the midLeft and midRight anchors shall stretch horizontally. I think it might concern the bounds those anchors can move but i don't know how to proceed.
http://jsfiddle.net/Dppm7/3/ (sorry can't get this to work in jsFiddle)..
The output looks like this.
Please if anyone can help. :)
Some code for the anchors (not all codes for the middle anchors have been implemented):
// Update the positions of handles during drag.
// This needs to happen so the dimension calculation can use the
// handle positions to determine the new width/height.
switch (activeHandleName) {
case "topLeft":
topRight.setY(activeHandle.getY());
midRight.setY(activeHandle.getY());
midTop.setY(activeHandle.getY());
bottomLeft.setX(activeHandle.getX());
midLeft.setX(activeHandle.getX());
midBottom.setX(activeHandle.getX());
break;
case "topRight":
topLeft.setY(activeHandle.getY());
midLeft.setY(activeHandle.getY());
midTop.setY(activeHandle.getY());
bottomRight.setX(activeHandle.getX());
midBottom.setX(activeHandle.getX());
midRight.setX(activeHandle.getX());
break;
case "bottomRight":
bottomLeft.setY(activeHandle.getY());
midBottom.setY(activeHandle.getY());
midLeft.setY(activeHandle.getY());
topRight.setX(activeHandle.getX());
midTop.setX(activeHandle.getX());
midRight.setX(activeHandle.getX());
break;
case "bottomLeft":
bottomRight.setY(activeHandle.getY());
midBottom.setY(activeHandle.getY());
midRight.setY(activeHandle.getY());
topLeft.setX(activeHandle.getX());
midTop.setX(activeHandle.getX());
midLeft.setX(activeHandle.getX());
break;
case "midTop":
topRight.setY(activeHandle.getY());
topLeft.setY(activeHandle.getY());
midRight.setY(activeHandle.getY());
midLeft.setY(activeHandle.getY());
break;
case "midBottom":
bottomRight.setY(activeHandle.getY());
bottomLeft.setY(activeHandle.getY());
midRight.setY(activeHandle.getY());
midLeft.setY(activeHandle.getY());
break;
case "midRight":
topRight.setX(activeHandle.getX());
bottomRight.setX(activeHandle.getX());
midTop.setX(activeHandle.getX());
midBottom.setX(activeHandle.getX());
break;
case "midLeft":
topLeft.setX(activeHandle.getX());
bottomLeft.setX(activeHandle.getX());
midTop.setX(activeHandle.getX());
midBottom.setX(activeHandle.getX());
break;
}
// Calculate new dimensions. Height is simply the dy of the handles.
// Width is increased/decreased by a factor of how much the height changed.
newHeight = bottomLeft.getY() - topLeft.getY();
newWidth = image.getWidth() * newHeight / image.getHeight();
// Move the image to adjust for the new dimensions.
// The position calculation changes depending on where it is anchored.
// ie. When dragging on the right, it is anchored to the top left,
// when dragging on the left, it is anchored to the top right.
if (activeHandleName === "topRight" || activeHandleName === "bottomRight") {
image.setPosition(topLeft.getX(), topLeft.getY());
} else if (activeHandleName === "topLeft" || activeHandleName === "bottomLeft") {
image.setPosition(topRight.getX() - newWidth, topRight.getY());
}
imageX = image.getX();
imageY = image.getY();
// Update handle positions to reflect new image dimensions
topLeft.setPosition(imageX, imageY);
topRight.setPosition(imageX + newWidth, imageY);
bottomRight.setPosition(imageX + newWidth, imageY + newHeight);
bottomLeft.setPosition(imageX, imageY + newHeight);
midTop.setPosition(imageX + image.getWidth() / 2, imageY);
midBottom.setPosition(imageX + image.getWidth() / 2, imageY + newHeight);
midRight.setPosition(imageX + image.getWidth(), imageY + image.getHeight() / 2);
midLeft.setPosition(imageX, imageY + image.getHeight() / 2);
// Set the image's size to the newly calculated dimensions
if (newWidth && newHeight) {
image.setSize(newWidth, newHeight);
}
}
<script>
var imWidth;
var imHeight;
var topRight;
var topLeft;
var bottomLeft;
var width;
var height;
var group;
var bottomRight;
var image;
var aspectRatio;
var oldwidth;
var oldheight;
var oldtopleftX;
var oldtopleftY;
var shrinkLimitBound;
function update(activeAnchor) {
group = activeAnchor.getParent();
var anchorX = activeAnchor.getX();
var anchorY = activeAnchor.getY();
actRatio=imWidth/imHeight;
shrinkLimitBound=100;
height=bottomLeft.getY() - topLeft.getY();
width=topRight.getX() - topLeft.getX();
newRatio=(width)/(height);
width=actRatio*height;
switch (activeAnchor.getName()) {
case 'topLeft':
if(height<shrinkLimitBound)
{
height=shrinkLimitBound;
width=actRatio*height;;
topRight.setY(bottomRight.getY()-height);
}
else
{
if(anchorY < bottomRight.getY())
topRight.setY(anchorY);
else
topRight.setY(bottomRight.getY()-height);
}
topRight.setX(bottomRight.getX());
bottomLeft.setX(bottomRight.getX()-width);
bottomLeft.setY(bottomRight.getY());
topLeft.setX(bottomRight.getX()-width);
topLeft.setY(bottomRight.getY()-height);
break;
case 'topRight':
if(height<shrinkLimitBound)
{
height=shrinkLimitBound;
width=actRatio*height;;
topLeft.setY(bottomLeft.getY()-height);
}
else
{
if(anchorY < bottomLeft.getY()-shrinkLimitBound)
{
topLeft.setY(anchorY)
}
else
{
topLeft.setY(bottomLeft.getY()-height);
}
}
topLeft.setX(bottomLeft.getX());
bottomRight.setX(bottomLeft.getX()+width);
bottomRight.setY(bottomLeft.getY());
topRight.setX(bottomLeft.getX()+width);
topRight.setY(bottomLeft.getY()-height);
break;
case 'bottomRight':
if(height<shrinkLimitBound)
{
height=shrinkLimitBound;
width=actRatio*height;;
bottomLeft.setY(topLeft.getY()+height);
}
else
{
if(anchorY > topLeft.getY()+shrinkLimitBound)
{
bottomLeft.setY(anchorY);
}
else
bottomLeft.setY(topLeft.getY()+height);
}
bottomLeft.setX(topLeft.getX());
topRight.setX(topLeft.getX()+width);
topRight.setY(topLeft.getY());
bottomRight.setX(topLeft.getX()+width);
bottomRight.setY(topLeft.getY()+height);
break;
case 'bottomLeft':
if(height<shrinkLimitBound)
{
height=shrinkLimitBound;
width=actRatio*height;;
bottomRight.setY(topRight.getY()+height);
}
else
{
if(anchorY > topRight.getY())
bottomRight.setY(anchorY);
else
bottomRight.setY(topRight.getY()+height);
}
bottomRight.setX(topRight.getX());
topLeft.setX(topRight.getX()-width);
topLeft.setY(topRight.getY());
bottomLeft.setX(topRight.getX()-width);
bottomLeft.setY(topLeft.getY()+height);
break;
}
image.setPosition(topLeft.getPosition());
if(width>0 && height>0)
{
image.setSize(width,height);
}
oldwidth=width;
oldheight=height;
oldtopleftX=topLeft.getX();
oldtopleftY=topLeft.getY();
}
function addAnchor(group, x, y, name) {
var stage = group.getStage();
var layer = group.getLayer();
var anchor = new Kinetic.Circle({
x: x,
y: y,
stroke: '#666',
fill: '#ddd',
strokeWidth: 0,
radius: 4,
name: name,
draggable: true,
dragOnTop: false
});
anchor.on('dragmove', function() {
update(this);
layer.draw();
});
anchor.on('mousedown touchstart', function() {
group.setDraggable(false);
this.moveToTop();
});
anchor.on('dragend', function() {
group.setDraggable(true);
layer.draw();
});
// add hover styling
anchor.on('mouseover', function() {
var layer = this.getLayer();
document.body.style.cursor = 'pointer';
this.setStrokeWidth(4);
layer.draw();
});
anchor.on('mouseout', function() {
var layer = this.getLayer();
document.body.style.cursor = 'default';
this.setStrokeWidth(2);
layer.draw();
});
group.add(anchor);
}
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
function initStage(images) {
var conWidth = 578; //container Width.
var conHeight = 400; //container Heitgh.
imWidth = images.dressTrailImage.width;
imHeight = images.dressTrailImage.height;
if (imWidth > conWidth)
{
imHeight = (imHeight/imWidth)*conWidth;
imWidth = conWidth;
}
if (imHeight > conHeight)
{
imWidth = (imWidth/imHeight)*conHeight;
imHeight = conHeight;
}
if ((imHeight < conHeight) && (imWidth < conWidth))
{
var diffX = conWidth - imWidth;
var diffY = conHeight - imHeight;
var diffY2 = (imHeight/imWidth)*diffX;
if (diffY2 > diffY)
{
imWidth = (imWidth/imHeight)*conHeight;
imHeight = conHeight;
}
else
{
imHeight = (imHeight/imWidth)*conWidth;
imWidth = conWidth;
}
}
images.UsrTrail.width = imWidth;
images.UsrTrail.height = imHeight;
var stage = new Kinetic.Stage({
container: 'container',
width: imWidth,
height: imHeight
});
var dressTrailImageGroup = new Kinetic.Group({
x: 0,
y: 0,
draggable: true,
//dragBoundFunc: function(pos) {
// console.log(pos);
// // var newX;
// // var newY;
// console.log(topLeft.x+","+bottomRight.y);
// x1=topLeft.x;
// x2=bottomRight.x;
// y1=topLeft.y;
// y2=bottomRight.y;
// x=pos.x;
// y=pos.y;
// var calsign = ((x-x1)*(y-y2))-((y-y1)*(x-x2))
// if (calsign < 0){
// return {
// x : pos.x,
// y : pos.y
// }
// }else {
// return {
// x : 50,
// y : 50
// }
// }
//}
// if (pos.x < ){
// newX=10;
// }
// else if ((pos.x+dressTrailImage.getWidth()) > stage.getWidth()-50){
// newX = stage.getWidth()-dressTrailImage.getWidth()-50;
// }
// else{
// newX = pos.x;
// };
// if(pos.y < 10){
// newY = 10;
// }
// else if((pos.y + dressTrailImage.getHeight()) > stage.getHeight()-50){
// newY = stage.getHeight()-dressTrailImage.getHeight()-50;
// }
// else {
// newY = pos.y;
// }
//console.log("newX:"+newX+", newY:"+newY);
// return {
// x : newX,
// y : newY,
// };
//}
});
// UsrTrail
var UsrTrailImg = new Kinetic.Image({
x: 0,
y: 0,
image: images.UsrTrail,
width: images.UsrTrail.width,
height: images.UsrTrail.height,
name: 'image'
});
var layer = new Kinetic.Layer();
/*
* go ahead and add the groups
* to the layer and the layer to the
* stage so that the groups have knowledge
* of its layer and stage
*/
layer.add(dressTrailImageGroup);
layer.add(UsrTrailImg);
stage.add(layer);
UsrTrailImg.moveToBottom();
intialAspRatio = images.dressTrailImage.width/images.dressTrailImage.height;
console.log("aspectRatio is :"+intialAspRatio);
// dress Trail Image
var inith=200; //set this to the desired height of the dress that shows up initially
var initw=intialAspRatio*inith;
var neck_user_x=50;//from backend
var neck_user_y=20;//from backend
var neck_dress_x=50;//from backend
var neck_dress_y=5;//from backend
//for getting the actual width and height of the User's Image
var UsrImgObjActual= new Image();
UsrImgObjActual.src=sources.UsrTrail;
UsrimgWidth=UsrImgObjActual.width;
UsrimgHeight=UsrImgObjActual.height;
//////////////////////////////////////////
// var UsrimgWidth= 180;
// var UsrimgHeight=270;
console.log("height Should Be 270 and is:"+UsrimgHeight);
console.log("Width Should Be 180 and is:"+UsrimgWidth);
var dressimgWidth=initw;
var dressimgHeight=inith;
console.log("usertrail image width adn height"+images.UsrTrail.width+"::"+images.UsrTrail.height);
console.log("neck user and dress resp"+neck_user_x+","+neck_user_y+','+neck_dress_x+','+neck_dress_y);
var x_draw=((neck_user_x*UsrimgWidth)-(neck_dress_x*dressimgWidth));
var y_draw=((neck_user_y*UsrimgHeight)-(neck_dress_y*dressimgHeight));
x_draw=x_draw/100;
y_draw=y_draw/100;
console.log("xdraw and ydraw:"+x_draw+','+y_draw);
//top left corner coordinates of the dress image.
var initx=x_draw;
var inity=y_draw;
var dressTrailImage = new Kinetic.Image({
x: initx,
y: inity,
image: images.dressTrailImage,
name: 'image',
width: initw,// images.dressTrailImage.width,
height: inith //images.dressTrailImage.height
});
// dressTrailImage.width = 50;
// dressTrailImage.height = dressTrailImage.width / intialAspRatio;
// console.log(dressTrailImage.height);
dressTrailImageGroup.add(dressTrailImage);
addAnchor(dressTrailImageGroup, initx , inity, 'topLeft');
addAnchor(dressTrailImageGroup, initx + initw , inity , 'topRight');
addAnchor(dressTrailImageGroup, initx + initw , inity + inith, 'bottomRight');
addAnchor(dressTrailImageGroup, initx , inity + inith, 'bottomLeft');
topLeft = dressTrailImageGroup.get('.topLeft')[0];
topRight = dressTrailImageGroup.get('.topRight')[0];
bottomRight = dressTrailImageGroup.get('.bottomRight')[0];
bottomLeft = dressTrailImageGroup.get('.bottomLeft')[0];
image = dressTrailImageGroup.get('.image')[0];
dressTrailImageGroup.on('dragstart', function() {
this.moveToTop();
});
stage.draw();
}
var sources = {
dressTrailImage: "http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg",
UsrTrail: "http://www.html5canvastutorials.com/demos/assets/yoda.jpg"
};
loadImages(sources, initStage);
//function called on clicking the submit button.
function Sunmit_fn(){
//neck positions of the trail dress in percentages (assumed for now!).
neckDressX=50;//in percentage.
neckDressY=5;//in percentage.
//height and width of the user image used in the canvas.
//original here here is refered to the height and width used in the canavs
var originalUserImgWidth = imWidth;
var originalUserImgHeight = imHeight;
var trailDressWidth = image.getWidth();//Final Image Width
var trailDressHeight = image.getHeight();//Final Image Height
imageX=topLeft.getParent().getX()+topLeft.getX()+1;//upper right anchor X Position
imageY=topLeft.getParent().getY()+topLeft.getY()+1;//upper right anchor Y Position
//formula for calculating the final neck positions of the resized and dragged dress
//with respect to the user image in percentages
neckFinalX=(imageX+(trailDressWidth*(neckDressX/100)))/originalUserImgWidth;
neckFinalY=(imageY+(trailDressHeight*(neckDressY/100)))/originalUserImgHeight;
//neck in percentages trail pic neck x,y.
neckFinalX=neckFinalX*100;
neckFinalY=neckFinalY*100;
//Just for testing.
console.log("neck position updated by User:");
console.log("X:"+neckFinalX+", Y:"+neckFinalY+")");
console.log("Resized Size of the dress is:");
console.log("Width:"+trailDressWidth+", Height:"+trailDressHeight);
}
</script>
This Script resizes only along one axis for four points.You can figure out the same for all the points
Raphael seems to lack shape hierarchies?
I can't create a smaller circle "attached" to a larger circle, and know that it will be scaled/translated when the larger one is.
Similarly, if i put elements into a set, the drag handler connects to shapes individually and in its callbacks i have no handle on the set.
Am i overlooking something?
This the current behaviour as Raphael does not create any real element for a "set".
If you want to enable Drag'nDrop on a set, you can use the following code :
Raphael.el.set_drag = function (aSet) {
// Enable drag'n drop in a Set
var startAll = function () {
// storing original coordinates
for (var i in this.set.items) {
var comp = this.set.items[i];
try {
comp.attr({opacity: 0.3});
} catch (ex) {;}
if (comp.type == "path") {
comp.ox = comp.getBBox().x;
comp.oy = comp.getBBox().y;
}
else {
comp.ox = comp.attrs.cx || comp.attrs.x;
comp.oy = comp.attrs.cy || comp.attrs.y;
}
}
},
moveAll = function (dx, dy) {
for (var i in this.set.items) {
var comp = this.set.items[i];
if (comp.attrs.cx) // ellipse
comp.attr({cx: comp.ox + dx, cy: comp.oy + dy});
else if (comp.attrs.x)
comp.attr({x: comp.ox + dx, y: comp.oy + dy});
else // path
comp.translate(comp.ox - comp.getBBox().x + dx, comp.oy - comp.getBBox().y + dy);
}
},
upAll = function () {
for (var i in this.set.items) {
var comp = this.set.items[i];
if (comp.attrs.cx) // ellipse
comp.attr({cx: comp.ox, cy: comp.oy + dy});
else if (comp.attrs.x)
comp.attr({x: comp.ox, y: comp.oy + dy});
else // path
comp.translate(comp.ox , comp.oy - comp.getBBox().y + dy);
this.set.items[i].attr({opacity: 1});
}
};
this.set = aSet; //create a "set" property on the element
this.drag(moveAll,startAll,upAll);
return this;
}
// Create your elements
var first_element = paper.rect(10,10,100,50).attr({fill:'#f00'});
var second_element = paper.rect(30,300,100,50).attr({fill:'#0f0'});
// Add elements to your set
var myset = paper.set();
myset.push(first_element);
myset.push(second_element);
// Add handler on the elements
first_element.set_drag(myset);
second_element.set_drag(book_set);