Position of translated elements - javascript

I am currently working on an aplication that supports drag&drop using this code for the drag&drop. the issue is that i need to move connected lines accordingly, and i can't figure out how to get the X and Y coordinates to change either the x1/y1 pair or the x2/y2 pair to update the line.
I have already tried asking the element for its position, using its bounding box, and the numbers do not match the actual position after the drag&drop.
Any ideas how can i achieve that? (Given the information calculated in the code, linked above)

Rather than BBox of a transformed element, you can insert the element into an SVG "Wrapper", then get the bbox of that.
I have reworked your example and added to an HTML5 document. I included an svg wrapper element, and three lines attached the the center of each circle. When it its dragged the lines follow.
This is shown below. (Note: Chrome Hangs on text element)
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Drag And Drop</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body style='padding:10px;font-family:arial'>
<center>
<h4>Drag And Drop</h4>
<div style='width:90%;background-color:gainsboro;text-align:justify;padding:10px;border-radius:6px;'>
A nice little demo of drag-and-drop functionality in SVG,
written by Doug Schepers on February 16, 2004.
Use or misuse this code however you wish.</div>
<table><tr>
<td>
<div id="svgDiv" style='background-color:lightgreen;width:600px;height:300px;'>
<svg width='100%' height='100%' xmlns='http://www.w3.org/2000/svg' id="mySVG"
onload='Init(evt)'
onmousedown='Grab(evt)'
onmousemove='Drag(evt)'
onmouseup='Drop(evt)'>
<rect id='BackDrop' x='-10%' y='-10%' width='110%' height='110%' fill='none' pointer-events='all' />
<circle id='BlueCircle' cx='25' cy='25' r='20' style='fill:blue; '/>
<circle id='RedCircle' cx='125' cy='25' r='20' style='fill:red; '/>
<circle id='OrangeCircle' cx='225' cy='25' r='20' style='fill:orange; '/>
<text id='DraggableText' x='20' y='200' style='fill:red; font-size:18px; font-weight:bold;'>Draggable Text</text>
<rect id='GreenRectangle' x='50' y='70' width='100' height='100' style='fill:green; '/>
<g id='Folder'>
<rect id='FolderRectangle' x='300' y='100' width='200' height='150' style='fill:tan; stroke:brown; stroke-width:3;'/>
</g>
<svg id="Wrapper" />
<line id=blueLine stroke=blue stroke=3 x1=25 y1=25 x2=25 y2=25 />
<line id=redLine stroke=red stroke=3 x1=125 y1=25 x2=125 y2=25 />
<line id=orangeLine stroke=orange stroke=3 x1=225 y1=25 x2=225 y2=25 />
</svg>
</div>
</td>
</tr></table>
<br />SVG Source:<br />
<textarea id=svgSourceValue style='font-size:110%;font-family:lucida console;width:90%;height:200px'></textarea>
<br />Javascript:<br />
<textarea id=jsValue style='border-radius:26px;font-size:110%;font-weight:bold;color:midnightblue;padding:16px;background-color:beige;border-width:0px;font-size:100%;font-family:lucida console;width:90%;height:400px'></textarea>
</center>
<div id='browserDiv' style='padding:5px;position:absolute;top:5px;left:5px;background-color:gainsboro;'>OK in:IE11/FF23...*CH hangs<br /></div>
<script id=myScript>
//var SVGDocument = null;
//var SVGRoot = null;
var TrueCoords = null;
var GrabPoint = null;
var BackDrop = null;
var DragTarget = null;
var DragLine = null;
function Init(evt)
{
//SVGDocument = evt.target.ownerDocument;
//SVGRoot = SVGDocument.documentElement;
// these svg points hold x and y values...
// very handy, but they do not display on the screen (just so you know)
TrueCoords = mySVG.createSVGPoint();
GrabPoint = mySVG.createSVGPoint();
// this will serve as the canvas over which items are dragged.
// having the drag events occur on the mousemove over a backdrop
// (instead of the dragged element) prevents the dragged element
// from being inadvertantly dropped when the mouse is moved rapidly
// BackDrop = SVGDocument.getElementById('BackDrop');
}
function Grab(evt)
{
// find out which element we moused down on
var targetElement = evt.target;
// you cannot drag the background itself, so ignore any attempts to mouse down on it
if ( BackDrop != targetElement )
{
//set the item moused down on as the element to be dragged
DragTarget = targetElement;
Wrapper.appendChild(DragTarget)
if(DragTarget.id=="BlueCircle")
DragLine=blueLine
if(DragTarget.id=="RedCircle")
DragLine=redLine
if(DragTarget.id=="OrangeCircle")
DragLine=orangeLine
// move this element to the "top" of the display, so it is (almost)
// always over other elements (exception: in this case, elements that are
// "in the folder" (children of the folder group) with only maintain
// hierarchy within that group
DragTarget.parentNode.appendChild( DragTarget );
// turn off all pointer events to the dragged element, this does 2 things:
// 1) allows us to drag text elements without selecting the text
// 2) allows us to find out where the dragged element is dropped (see Drop)
DragTarget.setAttributeNS(null, 'pointer-events', 'none');
// we need to find the current position and translation of the grabbed element,
// so that we only apply the differential between the current location
// and the new location
var transMatrix = DragTarget.getCTM();
GrabPoint.x = TrueCoords.x - Number(transMatrix.e);
GrabPoint.y = TrueCoords.y - Number(transMatrix.f);
}
};
function Drag(evt)
{
// account for zooming and panning
GetTrueCoords(evt);
// if we don't currently have an element in tow, don't do anything
if (DragTarget)
{
// account for the offset between the element's origin and the
// exact place we grabbed it... this way, the drag will look more natural
var newX = TrueCoords.x - GrabPoint.x;
var newY = TrueCoords.y - GrabPoint.y;
BB=Wrapper.getBBox()
var bbx=BB.x
var bby=BB.y
var bbw=BB.width
var bbh=BB.height
Cx=bbx+.5*bbw
Cy=bby+.5*bbh
DragLine.x2.baseVal.value=Cx
DragLine.y2.baseVal.value=Cy
// apply a new tranform translation to the dragged element, to display
// it in its new location
DragTarget.setAttributeNS(null, 'transform', 'translate(' + newX + ',' + newY + ')');
}
};
function Drop(evt)
{
// if we aren't currently dragging an element, don't do anything
if ( DragTarget )
{ mySVG.appendChild(DragTarget) //--remove from Wrapper---
// since the element currently being dragged has its pointer-events turned off,
// we are afforded the opportunity to find out the element it's being dropped on
var targetElement = evt.target;
// turn the pointer-events back on, so we can grab this item later
DragTarget.setAttributeNS(null, 'pointer-events', 'all');
if ( 'Folder' == targetElement.parentNode.id )
{
// if the dragged element is dropped on an element that is a child
// of the folder group, it is inserted as a child of that group
targetElement.parentNode.appendChild( DragTarget );
//alert(DragTarget.id + ' has been dropped into a folder, and has been inserted as a child of the containing group.');
}
else
{
// for this example, you cannot drag an item out of the folder once it's in there;
// however, you could just as easily do so here
//alert(DragTarget.id + ' has been dropped on top of ' + targetElement.id);
}
// set the global variable to null, so nothing will be dragged until we
// grab the next element
DragTarget = null;
}
svgSourceValue.value=svgDiv.innerHTML
};
function GetTrueCoords(evt)
{
// find the current zoom level and pan setting, and adjust the reported
// mouse position accordingly
var newScale = mySVG.currentScale;
var translation = mySVG.currentTranslate;
TrueCoords.x = (evt.clientX - translation.x)/newScale;
TrueCoords.y = (evt.clientY - translation.y)/newScale;
};
</script>
<script>
document.addEventListener("onload",init(),false)
function init()
{
svgSourceValue.value=svgDiv.innerHTML
jsValue.value=myScript.text
}
</script>
</body>
</html>

Related

Smoothly moving SVG element on event from current position to a given point

I'd like to move an SVG element (image) slowly from its current position (wherever it is) to a given point when an event is handled.
Since I don't know what the position is, I cannot prepare CSS animation based on keyframes.
So I tried doing the task via DOM manipulating:
var curX = parseFloat(image.getAttribute('x'));
var curY = parseFloat(image.getAttribute('y')); // Not used yet.
var curOpacity = parseFloat(image.getAttribute('opacity')); // Not used yet.
var animation = document.createElementNS(svgNS, 'animate');
animation.setAttribute('attributeName', 'x');
animation.setAttribute('from', curX);
animation.setAttribute('to', 0); // Move the image to the left border.
animation.setAttribute('dur', '2s'); // '3s', '5s'
animation.setAttribute('repeatCount', 1);
image.appendChild(animation);
If I set duration to 3s or 5s, it starts moving as soon as the animate element is appended, but when the animation is over the image is returned to the original position. (As I understand this is by design, but I'd like the image to keep its new position).
The second problem is when I set duration to 2s or less, the image stops moving smoothly, it just flickers to the left border and back (I don't know why).
Maybe, there is a better approach?
UPDATE
As it's answered below, the image keeps its position if fill is set to freeze. To reproduce the second problem it is enough to set timeout:
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" class="inline-block mb-1">
<image href="https://yari-demos.prod.mdn.mozit.cloud/en-US/docs/Web/SVG/Element/image/mdn_logo_only_color.png"
height="200" width="200" x="200" y="200"/>
<script>
// <![CDATA[
var startAnimation = function ()
{
const svgNS = "http://www.w3.org/2000/svg";
var image = document.querySelector('image');
var curX = parseFloat(image.getAttribute('x'));
var curY = parseFloat(image.getAttribute('y')); // Not used yet.
var curOpacity = parseFloat(image.getAttribute('opacity')); // Not used yet.
var animation = document.createElementNS(svgNS, 'animate');
animation.setAttribute('attributeName', 'x');
animation.setAttribute('from', curX);
animation.setAttribute('to', 0); // Move the image to the left border.
animation.setAttribute('dur', '2s'); // '3s', '5s'
animation.setAttribute('repeatCount', 1);
animation.setAttribute('fill', 'freeze');
image.appendChild(animation);
};
document.addEventListener('DOMContentLoaded', () => window.setTimeout(startAnimation, 1000));
// ]]>
</script>
</svg>
The image initially twitches and starts moving smoothly only after a half of the way. Chrome 102, x64. As for my computer performance, an animation based on window.requestAnimationFrame works with no glitches.
Set the 'fill' attribute of your animation:
animation.setAttribute('fill', 'freeze');
See here (MDN docs) for more details.
As for the jumpy animation behaviour, it hasn't happened in my tests, neither in the live example below nor on a standalone file.
Live example
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" class="inline-block mb-1">
<image href="https://yari-demos.prod.mdn.mozit.cloud/en-US/docs/Web/SVG/Element/image/mdn_logo_only_color.png" height="200" width="200" x="200" y="200"/>
<script>
// <![CDATA[
document.addEventListener ( 'DOMContentLoaded', function () {
const svgNS = "http://www.w3.org/2000/svg";
var image = document.querySelector('image');
var curX = parseFloat(image.getAttribute('x'));
var curY = parseFloat(image.getAttribute('y')); // Not used yet.
var curOpacity = parseFloat(image.getAttribute('opacity')); // Not used yet.
var animation = document.createElementNS(svgNS, 'animate');
animation.setAttribute('attributeName', 'x');
animation.setAttribute('from', curX);
animation.setAttribute('to', 0); // Move the image to the left border.
animation.setAttribute('dur', '2s'); // '3s', '5s'
animation.setAttribute('repeatCount', 1);
animation.setAttribute('fill', 'freeze');
image.appendChild(animation);
});
// ]]>
</script>
</svg>
UPDATE
The OP's bumpy animation problem when starting the animation through a timeout callback can be remedied by setting the animation element's begin attribute to the timeout delay:
animation.setAttribute('begin', '1s');

JavaScript works for one id but not another?

I have a JavaScript file which essentially draws a line when the user scrolls down the page. It draws a line for the first SVG but not for the other (which uses the same class). For example, I have the following code block in both text-repeater.php and text-block.php:
<svg height="100" width="200">
<line class="line__svg" id="line" x1="0" y1="0" x2="0" y2="200" />
</svg>
And here is my parallax.js file:
var scrollLine = document.getElementById("line");
var length = scrollLine.getTotalLength();
// Start position of drawing
scrollLine.style.strokeDasharray = length;
// Find scroll percentage on scroll
window.addEventListener("scroll", myFunction);
function myFunction() {
var scrollpercent = (document.body.scrollTop + document.documentElement.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
var draw = length * scrollpercent;
// Reverse drawing (when scrolling upwards)
scrollLine.style.strokeDashoffset = length - draw;
}
The line draws in text-block.php but not in text-repeater.php
Ideas why?
Also, on a related note. How can I get the line to draw longer? It's very subtle at the moment.
That's how IDs work. You only can have one instance of an Id (kind of - there are tricks but you don't want to do that).
Instead use classes...
<tagName class="line">
...and loop over it:
// Selecting all lines
const scrollLineCollection = document.querySelectorAll(".line");
for ( const scrollLine of scrollLineCollection ) {
// do something with scrollLine
}

draggable objects and canvas in loop

I have three questions. The first question is the most important but I appreciate to get answers to the others.
First question, see this jsfiddle:
https://jsfiddle.net/owLdgrdq/10
//copia nariz
$(document).on('click', '#add-nariz', function(){
var a = $('#add-nariz');
var src = a.attr('src');
var elem = $('<img class="objetos" src="' + src + '" width="30px" height="30px" style="positon: relative;" />');
$('#fotoAlterada').append(elem);
elem.draggable();
});
//copia bolinha azul
$(document).on('click', '#add-bb', function(){
var a = $('#add-bb');
var src = a.find('img:first').attr('src');
var elem = $('<img class="objetos" src="' + src + '" width="30px" height="30px" style="positon: relative;" />');
$('#fotoAlterada').append(elem);
elem.draggable();
});
$(document).on('click', '#salvaImagem', function(){
var m = $('#foto');
var totX = parseInt(m.css('width'));
var totY = parseInt(m.css('height'));
var c = document.getElementById('myCanvas');
c.width = totX;
c.height = totY;
var ctx = c.getContext('2d');
alert(totX + '\n' + totY);
var base = document.getElementById('foto');
ctx.drawImage(base,0,0,totX,totY,0,0,totX,totY);
var posicoes = [];
$(".objetos").each(function(){
var img = $(this);
x = parseInt(img.css("left"))+totX;
y = parseInt(img.css("top"))+totY;
altura = parseInt(img.css("width"));
largura = parseInt(img.css("height"));
posicoes.push([
x,
y,
largura,
altura
]);
});
alert( JSON.stringify(posicoes));
var j;
var numAderecos = posicoes.length;
for(j = 0; j < numAderecos; j++){
ctx.drawImage(base,posicoes[j][0],posicoes[j][1],posicoes[j][2],posicoes[j][3]);
}
});
I have some icons (Adicionar isto:) what I want add to a photo (Editar isto:). When the user clicks on the icons, a copy is made beside the photo. More click make more copies. These copies are draggable. The user choose the position of the objects and click in a button to save ("Salvar Mudanças"). This button makes a copy of the original photo with the new objects on it (inside a canvas).
At final of the JS code I put little photos (same as original) as little objects added to the photo in the canvas, but just because I don't know how to copy each object added. How can I do that?
Second question, when click on the button, the positions are not the same in the canvas, they are pull to down a little (and left too). Why is this happening if I put padding and margin 0px in CSS code?
Third question, the variable 'base' I used $('#foto') to get by id, but I can't use it at canvas arguments. When I use document.getElementById('foto') I can. What is the difference among them?
----editing ----
My aim with this is to make a mobile application, through cordova/phonegap. The user must be able to get a picture from the device or a selfie with the cam (it's already ok). When the picture/photo is gotten, some new objects like a crown's nose, colorful hat and other silly things appear on the window (it's ok, just change style="display: none;" to be displayed), when the user clicks on these things they make a copy at the bottom of the photo and these copies must be draggable (it's nice too). Then, with jQuery-UI I'm getting the position of all dragged objects into the photo, the user clicks on a button "Salvar Mudanças" ("save changes"), and this action copy the photo plus objects to a canvas area (this is the way I got to make the app). After, the user clicks on another button to share the modified image (in canvas) through whatsapp, facebook, email and any other way which the device could do.
Here is an alternate way of appending icons onto an image using just a canvas
Carve out a toolbar area at the top of an html5 canvas and fill it with your desired icons.
Put the destination image below the toolbar.
Listen for mouse events.
Allow the user to icons from the toolbar onto the image below.
When the user "drops" the dragging icon by releasing the mouse, create a duplicate of the dragged icon at the dropped position.
Here is annotated code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
var isDown=false;
var startX,startY;
ctx.lineWidth=0.50;
ctx.strokeStyle='lightgray';
ctx.fillStyle='white';
// vars for icons, etc
var tbar={width:cw,height:35}
var tw=30;
var th=30;
var dragging=null;
var dropped=[];
var icons=[
{name:'Flower',x:tw*0,y:2,url:'https://dl.dropboxusercontent.com/u/139992952/multple/flower.png'},
{name:'Star',x:tw*1,y:2,url:'https://dl.dropboxusercontent.com/u/139992952/multple/star.png'},
{name:'Plane',x:tw*2,y:2,url:'https://dl.dropboxusercontent.com/u/139992952/multple/plane2.png'},
{name:'Mario',x:tw*3,y:2,url:'https://dl.dropboxusercontent.com/u/139992952/multple/marioStanding.png'},
];
var thumbs=[];
var mainImg=new Image();
mainImg.crossOrigin='anonymous';
mainImg.onload=start;
mainImg.src='https://dl.dropboxusercontent.com/u/139992952/multple/husky.jpg';
var imgCount=icons.length+1;
for(var i=0;i<icons.length;i++){
var icon=icons[i];
icon.index=i;
icon.img=new Image();
icon.img.crossOrigin='anonymous';
icon.img.onload=start;
icon.img.src=icon.url;
}
// start is called when each image is fully loaded
function start(){
// wait for all images to load
if(--imgCount>0){return;}
// create
for(var i=0;i<icons.length;i++){
var icon=icons[i];
thumbs.push(thumb(icon.img,tw,th));
}
// draw the toolbar & image
draw();
// listen for mouse events
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUpOut(e);});
$("#canvas").mouseout(function(e){handleMouseUpOut(e);});
}
// create thumbnails for each image (to be used as icons in the toolbar)
function thumb(img,w,h){
var iw=img.width;
var ih=img.height;
var s=Math.min((w/iw),(h/ih))
var c=document.createElement('canvas');
c.width=iw*s;
c.height=ih*s;
c.getContext('2d').drawImage(img,0,0,iw,ih,0,0,iw*s,ih*s);
return(c);
}
// draw the toolbar, image & any clone thumbnails added to the image
function draw(){
ctx.clearRect(0,0,cw,ch);
ctx.fillRect(0,0,cw,tbar.height);
ctx.strokeRect(0,0,cw,tbar.height);
ctx.drawImage(mainImg,0,tbar.height);
for(var i=0;i<icons.length;i++){
var icon=icons[i];
ctx.drawImage(thumbs[icon.index],icon.x,icon.y);
}
for(var i=0;i<dropped.length;i++){
var icon=dropped[i];
ctx.drawImage(thumbs[icon.thumbIndex],icon.x,icon.y);
}
if(dragging){
ctx.drawImage(thumbs[dragging.thumbIndex],dragging.x,dragging.y);
}
}
//
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get the mouse position
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
// hit test the toolbar icons and any dropped icons
var mx=startX;
var my=startY;
// hit test toolbar icons
if(mx>0 && mx<tw*icons.length && my>2 && my<2+tw){
var icon=icons[parseInt(mx/tw)];
dragging={thumbIndex:icon.index,x:icon.x,y:icon.y};
dragging.source='icons';
return;
}
// hit test dropped icons
for(var i=0;i<dropped.length;i++){
var icon=dropped[i];
if(mx>icon.x && mx<icon.x+tw && my>icon.y && my<icon.y+th){
dragging=dropped[i];
dragging.source='dropped';
dragging.droppedIndex=i;
return;
}
}
}
// Add any
function handleMouseUpOut(e){
if(!dragging){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
//
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// add the icon to its dropped position
if(dragging.source=='icons'){
if(dragging.y>tbar.height){
dropped.push(dragging);
}
// remove the dropped icon if it's back in the toolbar
}else if(dragging.y<tbar.height){
dropped.splice(dragging.droppedIndex,1);
}
// clear dragging
dragging=null;
// redraw
draw();
}
// move any icon that's being dragged
function handleMouseMove(e){
if(!dragging){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// move the dragged icon by the distance the mouse
// has moved since the last mousemove event
var dx=mouseX-startX;
var dy=mouseY-startY;
startX=mouseX;
startY=mouseY;
dragging.x+=dx;
dragging.y+=dy;
// redraw
draw();
}
// save just the image with dropped icons
$('#save').click(function(){
var c=document.createElement('canvas');
c.width=cw;
c.height=ch-tbar.height;
c.getContext('2d').drawImage(canvas,0,tbar.height,cw,ch-tbar.height,0,0,c.width,c.height);
var img=new Image();
img.onload=function(){
document.body.appendChild(img);
}
img.src=c.toDataURL();
});
body{ background-color: ivory; }
#canvas{border:1px solid lightgray; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id='save'>Save</button>
<h4>Drag from top toolbar & icon will be drop-copied<br>Drag dropped icons around image<br>Drag dropped icon back to toolbar to delete.</h4>
<canvas id="canvas" width=300 height=300></canvas>
It's a little tough to follow, but I suspect you're asking about compositing all of the existing DOM images onto a single canvas context. For that you'll probably want to look at the globalCompositeOperation property on the 2dcanvas:
globalCompositeOperation - the property
Compositing Tutorial - basic outline of how the different types work
Also your 3rd question: $('#foto') returns a jquery object, and document.getElementById returns a DOM object. To get the actual DOM element you would do something like: var theEl = $('#foto')[0]

Create a group that doesnt depend on a Snap instance

Here is what I want to do :
var a = Snap("#id");
var group = new SnapGroup(); // unfortunatly didnt find how to do it
// for some reasons i dont want to do a.group();
group.circle(5,5,5);
a.add(group);
Here is what I did :
var a = Snap("#id");
s = Snap(); // creates a SVG element instead of a group
s.circle(5,5,5);
a.add(s);
It works, the circle is rendered, but then I cannot move the group :
s.attr({"x":60}); // the group doesnt move
Actually it looks like that when we embed and <svg> elements into an other one. Then it becomes impossible to move the embeded svg element in the parent one.
I would like to know how to create a group element without doing snapInstance.group() ? And then add it to a Snap instance.
I'm still not quite sure from your description what you are after, as I suspect it may depend how you are generating the original group (if its just a bit of svg markup or imported).
Snap.parse('<g></g>'); may be enough to fiddle with, to parse into a fragment.
See if this helps...its an example with two separate SVG elements and Snap instances. It will draw a rect from the original SVG markup string with a group,add a circle from Snap, and on the 2nd instance it will translate the group by 75 as well.
<svg id="svg1" width="200" height="200"></svg><br />
<svg id="svg2" width="200" height="200"></svg>
...
var paper1 = Snap("#svg1");
var paper2 = Snap("#svg2");
var groupMarkup = '<g><rect x="0" y="0" width="70" height="70" opacity="0.3"/><text x="0" y="15">original</text></g>';
var parsedMarkup1 = Snap.parse( groupMarkup ); //parse from a string derived elsewhere
var parsedMarkup2 = Snap.parse( groupMarkup );
// example1 just use the markup with its original group
paper1.add( parsedMarkup1 )
.add( paper1.circle(100,50,50)
.attr('fill', 'red' ) );
// example2, will create a new group and add the existing group to it, and then move it
var outerG = paper2.g()
.transform('t75,0'); //create a group and move it
outerG.add( parsedMarkup2 ); //add the original group/square
outerG.add( paper2.circle(70,50,50) //add a circle
.attr('fill', 'blue' ) );
var clone = outerG.clone(); //lets create a clone
clone.transform('r45t50,50'); //translate and rotate the clone
fiddle here http://jsfiddle.net/v4bJa/

Diagram creater using KineticJS

I'm working on a simple diagram editor using KineticJS. I would like to use two separate canvases for the palette area (that contains a number of Kinetic.Groups that represent the different nodes of the network I might create), and the diagramming area where I can add nodes from the palette via drag and drop, and then add connections between the various nodes as specific anchor points. I'm having trouble figuring out the drag and drop process from the palette canvas of (stationary) Kinetic.Groups over to the other canvas containing diagramming area. I'm guessing that I need to fire off of the dragstart event for the palette objects (although I don't want these themselves to be draggable), and then do something like create an opague copy of the palette object that can be dragged around, and finally dropped into the diagramming area (with full opacity).
Can groups be dragged outside of a the staging canvases boundaries? Maybe I need to generate an image when I start to drag from the palette, drag that image over, and then create another group when dropping into the diagramming area.
Does any one know of any examples that might point me in the right direction, or who can offer some insight (even code) into the required process. I've searched the KineticJS examples but can't quite find enough to get me going.
Here’s one way to drag nodes from a source palette into a destination group:
A Fiddle: http://jsfiddle.net/m1erickson/xtVyL/
Network nodes are represented by small icons (which are really small kinetic image objects).
The user can drag any icon from the source palette to any destination group.
The groups are just defined areas on the canvas, but could be Kinetc.Groups for more flexibility.
During the dragend event, a new duplicate copy of the dragged icon is created in the destination group.
After the dragend event is complete, the original palette icon is automatically moved from
The newly created duplicate icon can be dragged around the destination group (but not outside that group).
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/xtVyL/
<!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.5.5.min.js"></script>
<style>
#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);
// image loader
var imageURLs=[];
var imagesOK=0;
var imgs=[];
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/tempPC.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/tempServer.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/tempRouter.png");
loadAllImages();
function loadAllImages(callback){
for (var i = 0; i < imageURLs.length; i++) {
var img = new Image();
imgs.push(img);
img.onload = function(){
imagesOK++;
if (imagesOK==imageURLs.length ) {
start();
}
};
img.src = imageURLs[i];
}
}
// top icon positions
var nextIconX=20;
var nextIconY=20;
// define groups
var groups=[];
groups.push({x:0,y:100,w:175,h:250,fill:"skyblue"});
groups.push({x:175,y:100,w:175,h:250,fill:"cornsilk"});
// add boundary info to each group
// draw colored rect to show group area
for(var i=0;i<groups.length;i++){
var g=groups[i];
g.left=g.x;
g.right=g.x+g.w;
g.top=g.y;
g.bottom=g.y+g.h;
var rect=new Kinetic.Rect({
x:g.x,
y:g.y,
width:g.w,
height:g.h,
fill:g.fill,
stroke:"gray"
});
layer.add(rect);
}
// hittest for each group
function groupHit(x,y){
for(var i=0;i<groups.length;i++){
var g=groups[i];
if(x>g.left && x<g.right && y>g.top && y<g.bottom){return(i);}
}
return(-1);
}
function start(){
makePaletteIcon(imgs[0]);
makePaletteIcon(imgs[1]);
makePaletteIcon(imgs[2]);
layer.draw();
}
function makePaletteIcon(img){
// make an icon that stays in the pallette tray
var fixedIcon=newImage(nextIconX,nextIconY,img,false);
layer.add(fixedIcon);
// make an icon that is dragged from the tray to a group
var dragIcon=makeDraggableIcon(nextIconX,nextIconY,img);
layer.add(dragIcon);
// calc the next icon position
nextIconX+=(img.width+20);
}
function makeDraggableIcon(x,y,img){
var i=newImage(x,y,img,true);
//
i.trayX=x;
i.trayY=y;
//
i.setOpacity(0.50);
i.on("dragend",function(){
var x=this.getX();
var y=this.getY();
// if this pallette icon was not dropped in a group
// put the icon back in the tray and return
var hit=groupHit(x,y);
if(hit==-1){
this.setPosition(this.trayX,this.trayY);
return;
}
// add a copy of this icon to the drop group
var component=newImage(x,y,this.getImage(),true);
// set drag limits
var group=groups[hit];
component.maxDragLeft=group.left;
component.maxDragRight=group.right;
component.maxDragTop=group.top;
component.maxDragBottom=group.bottom;
// limit component dragging to inside the assigned group
component.setDragBoundFunc(function(pos) {
var xx=pos.x;
var yy=pos.y;
var w=this.getWidth();
var h=this.getHeight();
if(pos.x<this.maxDragLeft){xx=this.maxDragLeft;}
if(pos.x+w>this.maxDragRight){xx=this.maxDragRight-w;}
if(pos.y<this.maxDragTop){yy=this.maxDragTop;}
if(pos.y+h>this.maxDragBottom){yy=this.maxDragBottom-h;}
return{ x:xx, y:yy };
});
layer.add(component);
// move the dragIcon back into the pallette tray
this.setPosition(this.trayX,this.trayY);
layer.draw();
});
return(i);
}
// make a new Kinetic.Image
function newImage(x,y,img,isDraggable){
var i=new Kinetic.Image({
image:img,
x: x,
y: y,
width: img.width,
height: img.height,
draggable:isDraggable
});
return(i);
}
}); // end $(function(){});
</script>
</head>
<body>
<p>Drag any icon from top into blue or yellow group</p>
<div id="container"></div>
</body>
</html>

Categories