Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you transform event coordinates to SVG coordinates despite bogus getBoundingClientRect()?

Tags:

javascript

svg

I'm experimenting with dynamically drawing things on an SVG element based on the position of the mouse. Unfortunately, I'm having difficulty translating the mouse coordinates from the mousemove event into the coordinate space of the SVG element.

Here is a buggy function I've been testing:

CylinderDemo.prototype.handleMouseMove = function(evt)
{
    debugEvent = evt;

    var bcr = evt.target.getBoundingClientRect();
    var x2 = evt.clientX - bcr.left;
    var y2 = evt.clientY - bcr.top;

    console.log(evt.target);
    //console.log(evt.clientY+" - "+evt.target.getBBox());

    var d = this.pathForCylinder(this.x0, this.y0, x2, y2, 30);
    this.cap.setAttributeNS(null, "d", d);
}

canvas.onmousemove = function(evt) {
self.handleMouseMove(evt);
}

The problem is that (at least in Firefox) getBoundingClientRect() does not give me the bounds of the SVG object. It gives me the bounds of the drawable objects inside the SVG object. It becomes painfully obvious when you mouse over the log lines in firebug and it highlights the paltry subrectangle of drawable elements. That means that my transformation of the coordinates gives defective results.

What technique should I use to transform from the event coordinate system into the coordinate system of the SVG container?

I just cobbled together http://jsfiddle.net/7kvkq/ to illustrate the problem.

like image 627
Mutant Bob Avatar asked Dec 15 '22 02:12

Mutant Bob


1 Answers

Don't use getBoundingClientRect(). Instead, transform the point from screen space into global SVG space by using getScreenCTM():

var pt = demo.createSVGPoint(); // demo is an SVGElement
demo.addEventListener('mousemove',function(evt) {
  pt.x = evt.clientX;
  pt.y = evt.clientY;
  var svgGlobal = pt.matrixTransform(demo.getScreenCTM().inverse());
  // svgGlobal.x and svgGlobal.y are now in SVG coordinates
},false);

Fixed Demo: http://jsfiddle.net/7kvkq/3/

If you need to transform from screen space into the local transform for an element, use getTransformToElement() to transform the point further:

var elTransform = demo.getTransformToElement(someElement);
var elLocal     = svgGlobal.matrixTransform(elTransform );

Demo of getTransformToElement(): http://jsfiddle.net/7kvkq/4/

For better performance, instead of transforming the point twice and creating an intermediary point, combine the matrices into one and use that to transform your coordinates:

var demo = document.querySelector('svg'),
    pt   = demo.createSVGPoint(),
    g    = demo.querySelector('#myGroup');

// Assumes that the group does not move with respect to the SVG;
// if so, re-calculate this as appropriate.
var groupXForm = demo.getTransformToElement(g);
demo.addEventListener('mousemove',function(evt) {
    pt.x = evt.clientX;
    pt.y = evt.clientY;
    var xform = groupXForm.multiply(demo.getScreenCTM().inverse());
    var localPoint = pt.matrixTransform(xform);
    // localPoint.x/localPoint.y are the equivalent of your mouse position
},false);

You can see a demo using these techniques on my site:
http://phrogz.net/svg/drag_under_transformation.xhtml

like image 80
Phrogz Avatar answered Dec 21 '22 23:12

Phrogz