3

I want to create re-usable shapes that will automatically scale to fit the size of the given viewPort when used.

My approach is to enclose the shape in a 'symbol' element, and give it a viewBox with the same size as the shape itself.

This seems to work with a circle and a rectangle, but I am having trouble with a diamond shape, drawn using a path.

I have found a solution by creating a viewBox of (-1, -1, width+2, height+2), but I would like to know if this is officially supported, or if there is a better solution.

In the following example, the first shape is drawn directly, the second shape is derived from a 'use' element. If the viewBox starts with '0, 0', the left and top pixels are missing.

<html>

<svg width="200" height="200"
  style="margin:20px; border: 1px solid gray">
<path d="M 80 0 L 0 80 L 80 160 L 160 80 Z"
  stroke="black" stroke-width="2"
  stroke-linejoin="round" fill="transparent"
  transform="translate(20, 20)"/> 
</svg>

<svg style="display:none">
<symbol id="gw" viewBox="-1 -1 162 162">
  <path d="M 80 0 L 0 80 L 80 160 L 160 80 Z"
  stroke="black" stroke-width="2"
  stroke-linejoin="round" fill="transparent"/> 
</symbol>
</svg>

<svg width="200" height="200"
  style="margin:20px; border: 1px solid gray">
 <use href="#gw" width="160" height="160" transform="translate(20, 20)"/>
</svg>
  
</html>
S.Orioli
  • 443
  • 6
  • 21
Frank Millman
  • 653
  • 1
  • 7
  • 13
  • In this case you need to add `viewBox="0 0 160 160"` to the svg element and remove the `transform` attribute. In need to give the viewBox the same sixe as the lozenge's bounding box. To get the bounding box or an svg element you may use the `getBBox()` method in JavaScript.: `lozenge.getBBox()` – enxaneta Jun 03 '19 at 07:46
  • Thanks. The 'transform' was just to position the shape nicely. Regarding getBBox(), it seems to return dimensions based on the centre of any surrounding path, not the outer edge, so I have to compensate for that depending on the stroke width. – Frank Millman Jun 03 '19 at 07:58
  • I realise that I did not express myself clearly, I spoke about 'scaling' the shape to fit the viewPort, and that is my objective. But in my example, I am not trying to scale, I am just trying to make the second shape identical to the first one, using a viewBox. – Frank Millman Jun 03 '19 at 08:09
  • In the absence of further comments, I am proceeding on the basis that my approach is ok. It works on Chrome, Edge, and Firefox. The issue seems to be that getBBox() does not include the surrounding stroke, so if you create a viewBox using the dimensions returned by getBBox(), when you try to use it the shape is clipped. Allowing the viewBox to start with a negative value, and extend beyond the given width and height, (by a value dependent on the stroke width) solves the problem. – Frank Millman Jun 04 '19 at 06:46
  • 2
    Alternatively you may try `svg{overflow:visible}` – enxaneta Jun 04 '19 at 07:27
  • 1
    @enxaneta Thank you because I was strangling to fix loads of sub-svgs at the same time and your solution just worked out of the box simple and affective. – Athinodoros Sgouromallis Jun 19 '19 at 12:35

2 Answers2

4

This took me a while to debug - my issue was that I specified the viewbox as viewbox and not viewBox, so the viewBox wasn't even being applied. Check your capitalization!

anden-akkio
  • 1,019
  • 6
  • 5
2

It seems that negative coords for the origin are supported: https://www.w3.org/TR/SVG/coords.html implies that there is no restriction on the first two parts of a 'viewbox'. I've seen elsewhere that people sometimes use negative coords on a viewbox.

Tim Cooper
  • 10,023
  • 5
  • 61
  • 77
  • I agree, given "A negative value for or is an error and invalidates the ‘viewBox’ attribute" and no mention of negative values for min-x/min-y. Also, I see specification of negative min-y here, as a reference: https://observablehq.com/@d3/selection-join – Joshua Richardson Mar 20 '22 at 19:51