3

I'm having issues with individual elements with an imported SVG and was hoping somehow could advise.

I have an SVG created in Illustrator, with several layers, which end up being group elements. When I retrieve the SVG from my server, I get something similar to.

<svg>
  <g>
  <g>
</svg>

I do not want to place the image as one, so I break it up by the groups, and surround each by their own svg tag and then place on the page.

<svg>
  <g>
</svg>

<svg>
  <g>
</svg>

This is great, works like I want.

My issue lies, where the paths of each of these items is drawn in the original file. They are all drawn off (0,0) from the Illustrator file, so when I try to place them, they all have a ton of white space area on the left, where the other elements once existed.

I've tried using transform="translate(-50,-50)" or whatever, which does shift the elements, but since they do not have x,y properties, I do not know where to shift them.

Does anyone know of a way to offset a path drawn? Or if there is a way to read the SVG and break it up into each individual element and work with?

When using firebug or chrome, they show me the individual element with correct sizes, but they are placed with lots of white space because of position of the paths drawn in Illustrator.

I've tried contentDocument, documentElement and both of these come up as null. Maybe I'm using them wrong?

I'm a heavy Actionscript developer, and now working with Javascript and jQuery so I'm use to x,y coord system and placing elements, but this is not seeming the way it should work :/

Phrogz
  • 296,393
  • 112
  • 651
  • 745
iamface
  • 673
  • 1
  • 9
  • 27

1 Answers1

5

If you really want to get the XY parts of a path I have written a function to convert paths to use all-absolute commands. With this you could then run through the path commands (using the pathSegList DOM interface, not the raw string attribute) and pull out all the X/Y values and do with them what you will.

However, far easier is to simply calculate the bounding box of the path and set the viewBox on your <svg> element to fit around that directly:

// In case you want to leave the old SVG document unchanged
var newGroup = oldGroup.cloneNode(true);
var newSVG = document.createElementNS('http://www.w3.org/2000/svg','svg');
newSVG.appendChild(newGroup);

var bbox = newGroup.getBBox();
newSVG.setAttribute(
  'viewBox',
  [bbox.x,bbox.y,bbox.width,bbox.height].join(' ')
);

The above answer does not properly account for the case where a transform has already been applied to the group you want to move. (The bounding box is returned in the untransformed space of the element.) I've created a demo that accounts for this, calculating the correct bounding box for transformed elements.

Demo: http://phrogz.net/SVG/explode_svg_components.xhtml

// Find all the root groups in the original SVG file
var rootGroups = document.querySelectorAll('svg > g');
for (var i=rootGroups.length;i--;){
  var newSVG = elementToSVG(rootGroups[i]);
  document.body.appendChild(newSVG);
}

// Create a new SVG wrapping around a copy of the element
// with the viewBox set to encompass the element exactly
function elementToSVG(el){
  var old = el.ownerSVGElement,
      svg = document.createElementNS(old.namespaceURI,'svg'),
      css = old.querySelectorAll('style,defs');

  // Preserve elements likely needed for correct appearance
  [].forEach.call(css,copyToNewSVG);

  copyToNewSVG(el);

  var bb = globalBoundingBox(el);
  svg.setAttribute('viewBox',[bb.x,bb.y,bb.width,bb.height].join(' '));
  return svg;

  function copyToNewSVG(e){
    svg.appendChild(e.cloneNode(true));
  }
}

// Calculate the bounding box of an element in global SVG space
// accounting for transforms applied to the element
function globalBoundingBox(el){
  var bb  = el.getBBox(),
      svg = el.ownerSVGElement,
      m   = el.getTransformToElement(svg);
  var pts = [
    svg.createSVGPoint(), svg.createSVGPoint(),
    svg.createSVGPoint(), svg.createSVGPoint()
  ];
  pts[0].x=bb.x;          pts[0].y=bb.y;
  pts[1].x=bb.x+bb.width; pts[1].y=bb.y;
  pts[2].x=bb.x+bb.width; pts[2].y=bb.y+bb.height;
  pts[3].x=bb.x;          pts[3].y=bb.y+bb.height;

  var xMin=Infinity,xMax=-Infinity,yMin=Infinity,yMax=-Infinity;
  pts.forEach(function(pt){
    pt = pt.matrixTransform(m);
    xMin = Math.min(xMin,pt.x);
    xMax = Math.max(xMax,pt.x);
    yMin = Math.min(yMin,pt.y);
    yMax = Math.max(yMax,pt.y);
  });

  bb = {}; //IE9 disallows mutation of the original bbox
  bb.x = xMin; bb.width  = xMax-xMin;
  bb.y = yMin; bb.height = yMax-yMin;
  return bb;
}
Community
  • 1
  • 1
Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • I saw the `getBBox()` but it would never work or return anything. I was using it off a jQuery element `$('#box').getBBox()` rather than JS `document.getElementById('box').getBBox()`. This allows me to get the data I need to size and shift the SVG element where I need using `transform` as well. Also, thanks for that absolute function! I may end up using that instead, if my application process does not like my offset values for processing, but converting to absolute should fix. Thanks again! – iamface May 16 '12 at 17:35
  • The problem you had is that `$('#box')` returns you a "jQuery object" (an array of DOM elements, with special methods available on the array). You can use `$('#box')[0].getBBox()` or `$('#box').get(0).getBBox()` to operate upon the selected DOM element. – Phrogz May 16 '12 at 17:44
  • I tried using the above example, and every bbox comes back 0,0,0,0. Any idea why this would be? – iamface May 25 '12 at 17:57
  • 1
    Your group must be in an SVG document, and that SVG document must be on a page, or else `getBox()` will always return 0's. – Phrogz May 25 '12 at 18:01
  • Yes, that was it, not on the page just stored in a var :p Constantly finding differences with how you can interact and retrieve properties from the DOM and Flash's DisplayList. Thanks again for the help! – iamface May 25 '12 at 18:46
  • I noticed this exploding example does not work in IE9 however :/ – iamface May 31 '12 at 17:57
  • IE9 will throw an error `SCRIPT5022: DOM Exception: NO_MODIFICATION_ALLOWED_ERR (7)` when it tries to set the x,y,width,height properties of `bb` or the element's BBox. If you create a new Object then set the properties, it will work. – iamface May 31 '12 at 18:15