How to draw non-scalable circle in SVG with Javascript

javascript, svg

Solution

It took me a while, but I finally got the math clean. This solution requires three things:

- Include this script in your page (along with the SVGPan.js script), e.g. `<script xlink:href="SVGPanUnscale.js"></script>`

- Identify the items you want not to scale (e.g. place them in a group with a special class or ID, or put a particular class on each element) and then tell the script how to find those items, e.g. `unscaleEach("g.non-scaling > *, circle.non-scaling");`

- Use `transform="translate(…,…)"` to place each element on the diagram, not `cx="…" cy="…"`.

With just those steps, zooming and panning using SVGPan will not affect the scale (or rotation, or skew) of marked elements.

Demo: http://phrogz.net/svg/scale-independent-elements.svg

Library

// Copyright 2012 © Gavin Kistner, !@phrogz.net
// License: http://phrogz.net/JS/_ReuseLicense.txt

// Undo the scaling to selected elements inside an SVGPan viewport
function unscaleEach(selector){
  if (!selector) selector = "g.non-scaling > *";
  window.addEventListener('mousewheel',     unzoom, false);
  window.addEventListener('DOMMouseScroll', unzoom, false);
  function unzoom(evt){
    // getRoot is a global function exposed by SVGPan
    var r = getRoot(evt.target.ownerDocument);
    [].forEach.call(r.querySelectorAll(selector), unscale);
  }
}

// Counteract all transforms applied above an element.
// Apply a translation to the element to have it remain at a local position
function unscale(el){
  var svg = el.ownerSVGElement;
  var xf = el.scaleIndependentXForm;
  if (!xf){
    // Keep a single transform matrix in the stack for fighting transformations
    // Be sure to apply this transform after existing transforms (translate)
    xf = el.scaleIndependentXForm = svg.createSVGTransform();
    el.transform.baseVal.appendItem(xf);
  }
  var m = svg.getTransformToElement(el.parentNode);
  m.e = m.f = 0; // Ignore (preserve) any translations done up to this point
  xf.setMatrix(m);
}

Demo Code

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <title>Scale-Independent Elements</title>
  <style>
    polyline { fill:none; stroke:#000; vector-effect:non-scaling-stroke; }
    circle, polygon { fill:#ff9; stroke:#f00; opacity:0.5 }
  </style>
  <g id="viewport" transform="translate(500,300)">
    <polyline points="-100,-50 50,75 100,50" />
    <g class="non-scaling">
      <circle  transform="translate(-100,-50)" r="10" />
      <polygon transform="translate(100,50)" points="0,-10 10,0 0,10 -10,0" />
    </g>
    <circle class="non-scaling" transform="translate(50,75)" r="10" />
  </g>
  <script xlink:href="SVGPan.js"></script>
  <script xlink:href="SVGPanUnscale.js"></script>
  <script>
    unscaleEach("g.non-scaling > *, circle.non-scaling");
  </script>
</svg>

Problem

I'm developing a map, in Javascript using SVG to draw the lines. I would like to add a feature where you can search for a road, and if the road is found, a circle appears on the map. I know i can draw a circle in SVG, but my problem is that, the size of the circle should not change depending on the zoom-level. In other words the circle must have the same size at all times. The roads on my map have this feature, all i had to do was add ``` vector-effect="non-scaling-stroke" ``` to the line attributes.. A line looks like this. ``` <line vector-effect="non-scaling-stroke" stroke-width="3" id = 'line1' x1 = '0' y1 = '0' x2 = '0' y2 = '0' style = 'stroke:rgb(255,215,0);'/> ``` The circle looks like this. ``` <circle id = "pointCircle" cx="0" cy="0" r="10" stroke="red" stroke-width="1" fill = "red"/> ``` Is it possible to define the circle as "non-scaling" somehow?

Original source