So I am looking to retrieve the size of a text string in Raphael and I can't seem to do it. Although the documentation says that
.attr('width');
is an option...and I can't set the width either.
Here is a FIDDLE
This is what I have been trying...plus other crude ways (even using Jquery)
var page = Raphael("drawing_board");
// start, move, and up are the drag functions
start = function () {
// storing original coordinates
this.ox = this.attr("x");
this.oy = this.attr("y");
this.attr({opacity: .5});
console.log( this.attr('width') ); //THIS IS WHERE I AM TRYING TO GET THE WIDTH
},
move = function (dx, dy) {
// move will be called with dx and dy
nowX = Math.min(600, this.ox + dx);
nowY = Math.min(400, this.oy + dy);
nowX = Math.max(0, nowX);
nowY = Math.max(0, nowY);
this.attr({x: nowX, y: nowY });
},
up = function () {
// restoring state
this.attr({opacity: 1});
};
page.text(200, 50, "TEXT 1").attr({ cursor: "move", 'font-size': 16 , fill: '#3D6AA2'}).drag(move, start, up);
page.text(200, 200, "TEXT 2").attr({ cursor: "move", 'font-size': 16 , fill: '#3D6AA2'}).drag(move, start, up);
Maybe I need to use something other than
this.attr('width' : 30); //To Set
this.attr('width'); //To Get
You can use:
this.node.getBBox().width
this.node -> gets the SVG element associated with the Raphael element
getBBox() -> bounding box
You can also use Raphael's getBBox method directly:
this.getBBox().width
Your approach doesn't work because the svg text element doesn't have a width attribute.
this.getBBox() is the RaphaelJS method to get the computed bbox thus returning an object with x y x1 x2 width and height properties. It's as cross browser as Raphaeljs is.
SVG Text elements doesn't have the width attribute. Check it on the w3c SVG specification: http://www.w3.org/TR/SVG/text.html#TextElement There's no width on the attributes list, so you can't set it.
But we could get the width of the DOM element with jQuery. It can be done first retrieving the DOM element reference and passing it to the jQuery constructor, then using the width() function. Here's the updated fiddle: http://jsfiddle.net/Rmegm/8/
Related
Im tracking clicks on my website. For each clicks im taking the coordinates of the click on the clicked element ( referenced with the function getPath who return the css path) with the following function:
add_click(event) {
var element = event.target;
var dimensions = element.getBoundingClientRect();
var click = {
width: window.innerWidth,
height: window.innerHeight,
path: getPath(element),
x: event.offsetX / dimensions.width,
y: event.offsetY / dimensions.height,
key: event.which
};
this.clicks.push(click);
}
Then i'm creating visualisation of clicks. For this im reconverting X and Y coordinates of the element into X and Y coordinates of the window. For this im using :
var element = document.querySelector(path); //return the clicked element
var rect = element.getBoundingClientRect();
var X = rect.left + click["x"] * rect.width;
var Y = rect.top + click["y"] * rect.height;
However getBoundingClientRect() is not returning the same value for an element every time i load page. Value returned from getBoundingClientRect() for a same element keep changing and this creating wrong coordinates for my click.
I have tested it on Chrome 87 and Firefox 78 both are returning different value for getBoundingClientRect() for the same exact window size.
Does anyone know why this is happening, have met this bug before or have another function that is not broken to get coordinates of a DOM element?
JSFiddle here: JSFiddle
When dragging a group of objects, the individual objects' location attributes don't seem to be getting updated. This occurs whether I use the default drag() handler or define my own. Even the group BBox operation doesn't seem to update. Code:
var s = Snap("#svg");
var move = function (dx, dy, posx, posy) {
this.attr({
x: posx,
y: posy
});
//this.transform("t" + dx + "," + dy);
};
var block = s.rect(100, 100, 100, 100);
var circle = s.circle(100, 100, 50);
var group = s.g(block, circle);
//group.drag(move, function () {}, function () {});
group.drag();
//block.drag(move, function () {}, function () {});
//just a way to keep info coming w/o an interminable script
document.addEventListener('mousemove', function (e) {
bbox = block.getBBox();
block_x = block.attr("x");
block_y = block.attr("y");
gbbox = group.getBBox();
console.log("block is at " + block_x + "," + block_y,
" Block Bbbox is at " + bbox.x + "," + bbox.y,
" Group Bbbox is at " + gbbox.x + "," + gbbox.y);
}, false);
If I define only one object (say, a rect) and leave it out of a group, and pass my own "move" function to the call to drag, and include setting the "x" and "y" attributes explicitly, then that works. But if I include the rect in a group, then...I can't figure out how to do it, and I've tried a few ways (see the multiple commented-out lines showing things I've tried). I need to know where the rect sub-group element ends up after the drag, or at least the BBox of the whole group. Neither of these seem to be getting updated -- i.e. the console log I put in shows the same numbers forever, no matter where I move the object(s).
Can anyone help?
JSFiddle here: JSFiddle
I think this is because they are two different things, so they aren't actually interchangable.
The drag handler uses transforms. A transform doesn't affect any other attributes, its just an attribute on an element (in this case the group element).
getBBox will work in its current transform space, note this may be different to the clients (eg if the svg were zoomed in/out). So they are two slightly different methods, that do different things.
Use getBoundingClientRect if you need a bounding box relative to the client window. Use getBBox if you need a bounding box in the elements current coordinate space.
Code is using snap.svg.zpd as well, so zoom is possible. Problem is at onStopMove function. Events are fired when group is moved arround. In group is one circle(this.select('#main-inner-circle')) which does not have predefined location inside group. Im trying to get correct cx and cy of that inner circle after moving group.
self.onMove = function (dx, dy, ev, x, y) {
var clientX, clientY;
var tdx, tdy;
if ((typeof dx == 'object') && (dx.type == 'touchmove')) {
clientX = dx.changedTouches[0].clientX;
clientY = dx.changedTouches[0].clientY;
dx = clientX - this.data('ox');
dy = clientY - this.data('oy');
}
var snapInvMatrix = this.transform().diffMatrix.invert();
snapInvMatrix.e = snapInvMatrix.f = 0;
tdx = snapInvMatrix.x(dx, dy);
tdy = snapInvMatrix.y(dx, dy);
this.transform("t" + [tdx, tdy] + this.data('ot'));
}
self.onStartMove = function (x, y, ev) {
if ((typeof x == 'object') && (x.type == 'touchstart')) {
x.preventDefault();
this.data('ox', x.changedTouches[0].clientX);
this.data('oy', x.changedTouches[0].clientY);
}
this.data('ot', this.transform().local);
if (callbacks.onStartMove) {
callbacks.onStartMove();
}
}
self.onStopMove = function () {
var self = this.select('#main-inner-circle');
this.data('ot', this.transform().local);
//self.data('ot', self.transform().local);
console.log(self.getTransformedBBox());
console.log(this.getBBox());
//console.log($(self.node).offset().left - $(self.node).parent().offset().left);
var bBox = this.getBBox();
//var x = bBox.x + $(self.node).offset().left - $(self.node).parent().offset().left + self.getBBox().width / 2;
//var y = bBox.y + $(self.node).offset().top - $(self.node).parent().offset().top + self.getBBox().height / 2;
model.updateElementCoordinates(index, $(this.node).attr("rel"), { x: self.getTransformedBBox().cx, y: self.getTransformedBBox().cy });
if (callbacks.onStopMove) {
callbacks.onStopMove();
}
}
In order to post this question, I'd created the JSFiddle but left out the crucial snap.svg definitions...
<script src="http://snapsvg.io/assets/js/snap.svg-min.js"></script>
...with that, then indeed the group.getBBox() method actually works. However:
Apparently, using getBBox() is incredibly slow -- much slower than just accessing a "x" attribute of something like I was doing before grouping objects. All I know is that my code slows to a crawl if I use getBBox() (I have a lot of objects on the screen).
Further down in the same post mentioned earier ["Get coordinates of svg group on drag with snap.svg"1 recommended getBoundingClientRect(), which also works fine AND is fast enough! My new, working Fiddle showing all of these methods is here: New JSFiddle.
So, future users: use .node.getBoundingClientRect().
I have been struggling to implement drag and drop for a set in raphael.js.
I have managed to do so for sets that are made up of circles, using this code on JSFiddle.
var r = Raphael(10, 50, 600, 600);
var Intermediate = r.set();
var start = function () {
// storing original coordinates
this.ox = this.attr("cx");
this.oy = this.attr("cy");
};
move = function (dx, dy) {
// move will be called with dx and dy
Intermediate.attr({cx: this.ox + dx, cy: this.oy + dy});
};
up = function () {};
Intermediate.drag(move, start, up);
However this does not work for sets made up from rectangles and text, because they do not have cy and cx properties. Replacing cx and cy with x and y respectively does not work as two objects will have different x and y values while concentric circles will have the same cx and cy values.
I found a solution, here, also on JSFiddle, which defines a new method in Raphael.st.
Raphael.st.draggable = function() {};
It then just enables the set to be draggable:
mySet.draggable();
Hoever when I try implement this solution in visual studio I get an error "0x800a1391 - JavaScript runtime error: 'Raphael' is undefined".
I don't understand why this is, I have added the Raphael script to my HTML.
I am in need of some math help. I am trying to dynamically transform my Raphael set of elements to a given bound box within my canvas.
For example, say my canvas (paper) is 600 x 300 and is filled with paths. These paths are all in a set.
Now I want fill my canvas with a given bound box. The bound box is in pixel coordinates. e.g. [[50,10], [100,20]]
So the end result would be a function call that would zoom and position the SVG elements. This would cause the canvas to be cropped to the coordinate bounds.
var bbox = [[50,10], [100,20]]
animateToBoundBox(set, bbox, duration);
function animateToBoundBox(set, bbox, duration) { /* beautiful code */ }
I think the way to accomplish this would be by using the element matrix but I'm not sure. What do you think the most elegant way of handling this would be?
Thanks
The other answers are correct -- you want to use setViewBox.
Here's a version that supports animation. It's not entirely beautiful and you'll have to look at the page source to extract the code, but it should do more or less exactly what you want.
Here's the view box animation as a Raphael extension:
Raphael.fn.animateViewBox = function animateViewBox( x, y, w, h, duration, easing_function, callback )
{
var cx = this._viewBox ? this._viewBox[0] : 0,
dx = x - cx,
cy = this._viewBox ? this._viewBox[1] : 0,
dy = y - cy,
cw = this._viewBox ? this._viewBox[2] : this.width,
dw = w - cw,
ch = this._viewBox ? this._viewBox[3] : this.height,
dh = h - ch,
self = this;;
easing_function = easing_function || "linear";
var interval = 25;
var steps = duration / interval;
var current_step = 0;
var easing_formula = Raphael.easing_formulas[easing_function];
var intervalID = setInterval( function()
{
var ratio = current_step / steps;
self.setViewBox( cx + dx * easing_formula( ratio ),
cy + dy * easing_formula( ratio ),
cw + dw * easing_formula( ratio ),
ch + dh * easing_formula( ratio ), false );
if ( current_step++ >= steps )
{
clearInterval( intervalID );
callback && callback();
}
}, interval );
}
And the (not so beautiful) demonstration is here: http://voidblossom.com/tests/easedViewBox.php
If you're really bent on using transform (which could have a few benefits if leveraged well, but will in general be fragile compared to viewbox manipulation), there's another example using transform located at http://voidblossom.com/tests/zoomByTransform.php.
Not sure if I completely understand what you are looking for, but it sounds like you want to zoom the view to a specific bounding box. Have you looked at the setViewBox function? Basically your function would call it like this:
setViewBox(bbox[0][0], bbox[0][1], bbox[1][0] - bbox[0][1], bbox[1][1] - bbox[0][0])
From what I can tell, everything you want to accomplish would be better handled with Paper.setViewBox() in an animation. See http://raphaeljs.com/reference.html#Paper.setViewBox
I have a firefox extension that needs to get the exact screen co-ordinates of a DOM element and passes it to a native DLL via js/c-types.
Now I have it mostly covered :
var gDomWindowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(nsIDOMWindowUtils);
function getScreenRect(oElem)
{
var rc =
{
x : 0,
y : 0,
w : 0,
h : 0
};
var o = oElement;
while(o != null)
{
rc.y += o.offsetTop;
rc.x += o.offsetLeft;
o = o.offsetParent;
}
var x = {}, y = {};
gDomWindowUtils.getScrollXY(false, x, y);
rc.x -= x.value;
rc.y -= y.value;
var scale = gDomWindowUtils.screenPixelsPerCSSPixel;
rc.x *= scale;
rc.y *= scale;
rc.w *= scale;
rc.h *= scale;
return rc;
};
This handles scrolling and zooming, but the values I get are relative to the browser window, and not the screen.
How do I detect the offset of the client area of the actual rendering area of the browser?
I can even use native code (Win32) via js/ctypes so I tried to see if I could use FindWindow() / GetWindowRect() to get it, but the whole of firefox is a single HWND, the controls are all not native windows.
So one idea I have is, since the UI of firefox is an XUL document, I should be able to get the menubar, tab bar etc etc and find the browser areas absolute offset. But, I have no clue how to access the XUL tree that defines the browser UI.
Can someone give me a pointer?
[Edit]
Ignore rc.w and rc.h being undefined in the above code , it is irrelevant to the question.
You mostly got it already but I would recommend using getBoundingClientRect() instead of offsetLeft/offsetTop:
var rect = oElement.getBoundingClientRect();
var rc = {
x: rect.left,
y: rect.top,
w: rect.width,
h: rect.height
};
getBoundingClientRect() considers scrolling so that you no longer need to add it. You get the coordinates relative to screen using window.mozInnerScreenX and window.mozInnerScreenY:
rc.x += window.mozInnerScreenX;
rc.y += window.mozInnerScreenY;
And after that you multiply the values with screenPixelsPerCSSPixel. That should give you proper screen coordinates.
I will add one more "trick" that helped me with a similar problem.
Whenever my code receives a mousemove event, I capture the mouse-cursor positions in ALL the coordinates that event provides, which includes at least "screen", "client" and "pageXY" coordinates. Then for any OTHER purpose I can compute the difference between these coordinates simply by subtracting the appropriate two of those.
In your case, you'd probably compute an offset like this:
deltaX = event.screenX - event.clientX;
deltaY = event.screenY - event.clientY;
or
offsetX = event.screenX - event.pageX;
offsetY = event.screenY - event.pageY;
Then just add deltaX and deltaY or offsetX and offsetY to those coordinates to get screen coordinates.