Skip to content Skip to sidebar Skip to footer

How To Draw Non-scalable Circle In Svg With Javascript

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 ma

Solution 1:

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

  1. Include this script in your page (along with the SVGPan.js script), e.g. <script xlink:href="SVGPanUnscale.js"></script>
  2. 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");
  3. Use transform="translate(…,…)" to place each element on the diagram, notcx="…" 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 viewportfunctionunscaleEach(selector){
  if (!selector) selector = "g.non-scaling > *";
  window.addEventListener('mousewheel',     unzoom, false);
  window.addEventListener('DOMMouseScroll', unzoom, false);
  functionunzoom(evt){
    // getRoot is a global function exposed by SVGPanvar 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 positionfunctionunscale(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

<svgxmlns="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><gid="viewport"transform="translate(500,300)"><polylinepoints="-100,-50 50,75 100,50" /><gclass="non-scaling"><circletransform="translate(-100,-50)"r="10" /><polygontransform="translate(100,50)"points="0,-10 10,0 0,10 -10,0" /></g><circleclass="non-scaling"transform="translate(50,75)"r="10" /></g><scriptxlink:href="SVGPan.js"></script><scriptxlink:href="SVGPanUnscale.js"></script><script>unscaleEach("g.non-scaling > *, circle.non-scaling");
  </script></svg>

Solution 2:

If you are looking for a fully static way of doing this, you might be able to combine non-scaling-stroke with markers to get this, since the markers can be relative to the stroke-width.

In other words, you could wrap the circles in a <marker> element and then use those markers where you need them.

<svgwidth="500"height="500"xmlns="http://www.w3.org/2000/svg"viewBox="0 0 2000 2000"><markerid="Triangle"viewBox="0 0 10 10"refX="0"refY="5"markerUnits="strokeWidth"markerWidth="4"markerHeight="3"orient="auto"><pathd="M 0 0 L 10 5 L 0 10 z" /></marker><pathd="M 100 100 l 200 0"vector-effect="non-scaling-stroke"fill="none"stroke="black"stroke-width="10"marker-end="url(#Triangle)"  /><pathd="M 100 200 l 200 0"fill="none"stroke="black"stroke-width="10"marker-end="url(#Triangle)"  /></svg>

The same can also be viewed and tweaked here. The svg spec isn't fully explicit about what should happen in this case (since markers are not in SVG Tiny 1.2, and vector-effect isn't in SVG 1.1). My current line of thinking was that it should probably affect the size of the marker, but it seems no viewers do that at the moment (try in a viewer that supports vector-effect, e.g Opera or Chrome).

Solution 3:

Looks like some work was done in webkit (maybe related to this bug: 320635) and the new transform doesn't stick around when simply appended like that

transform.baseVal.appendItem

This seems to work better. Even works in IE 10.

EDIT: Fixed the code for more general case of multiple translate transformations in the front and possible other transformations after. First matrix transformation after all translates must be reserved for unscale though.

translate(1718.07 839.711) translate(0 0) matrix(0.287175 0 0 0.287175 0 0) rotate(45 100 100)

function unscale()
{
    var xf = this.ownerSVGElement.createSVGTransform();
    var m = this.ownerSVGElement.getTransformToElement(this.parentNode);
    m.e = m.f = 0; // Ignore (preserve) any translations done up to this point
    xf.setMatrix(m);

    // Keep a single transform matrix in the stack for fighting transformations// Be sure to apply this transform after existing transforms (translate)var SVG_TRANSFORM_MATRIX = 1;
    var SVG_TRANSFORM_TRANSLATE = 2;
    var baseVal = this.transform.baseVal;
    if(baseVal.numberOfItems == 0)
        baseVal.appendItem(xf);
    else
    {
        for(var i = 0; i < baseVal.numberOfItems; ++i)
        {
            if(baseVal.getItem(i).type == SVG_TRANSFORM_TRANSLATE && i == baseVal.numberOfItems - 1)
        {
                baseVal.appendItem(xf);
            }

            if(baseVal.getItem(i).type != SVG_TRANSFORM_TRANSLATE)
            {
                if(baseVal.getItem(i).type == SVG_TRANSFORM_MATRIX)
                    baseVal.replaceItem(xf, i);
                else
                    baseVal.insertItemBefore(xf, i);
                break;
            }
        }
    }
}

EDIT2: Chrome killed getTransformToElement for some reason, so the matrix needs to be retrieved manually:

var m = this.parentNode.getScreenCTM().inverse().multiply(this.ownerSVGElement.getScreenCTM());

Solution 4:

It's discussed here and here

It looks like current browsers don't do the expected thing, so one needs to apply the inverse transform of the zoom (scale) on the contents of the <marker>, eg. transorm: scaleX(5) on the user of the <marker> etc. will need to be accompanied by a transform: translate(...) scaleX(0.2) inside the <pattern>, also factoring in possible x/y/width/height/transform-origin values inside the pattern if needed

Post a Comment for "How To Draw Non-scalable Circle In Svg With Javascript"