# Relative dimensions

## Relative dimensions with calc function

One of the most common requests when working with SVG is to set the dimensions of SVGElements relatively. JointJS provides a `calc()`

function which lets you perform calculations when specifying SVG attributes values. This allows you to size subelements relative to the size of the shape's model. Moreover, since all the calculations are programmatic and do not rely on the browser's bbox measurements, using this function does not impact performance of your app. (There is also a view-based method if you need to work with text; it is explained here in more detail.).

As `calc()`

works with normal SVG attributes, you can perform relative sizing easily using the SVG attributes you are familiar with. For example, `'calc(w)'`

and `'calc(h)'`

performs calculations using the shape model width and height respectively. If we wanted to set the `x`

coordinate of the top-left corner of a subelement relative to the top-left corner of the shape model bbox, we could use `x: 'calc(w)'`

on the subelement.

`ref`

attributes

JointJS also contains a suite of `ref`

attributes to work with relative dimensions. However, we now recommend using `calc()`

for relative sizing.

`refWidth`

and`refHeight`

- sets the width of the subelement relative to model bbox.`refX`

and`refY`

- sets the coordinates of the top-left corner of the subelement relative to the top-left corner of model bbox. Percentages are interpreted relative to model bbox. Stacks with the native`x`

/`y`

attribute.`refCx`

and`refCy`

- sets the coordinates of the circle/ellipse center. Percentages are interpreted relative to model bbox. Can be used alongside`refX`

/`refY`

.`refRx`

and`refRy`

- sets the radius of the ellipse relative to model bbox dimensions. Percentages are interpreted relative to model bbox. Note that for backwards compatibility, setting`'100%'`

here means that the*radius*will be 100% of model size while the diameter of the ellipse in that direction will be 200% of model size. Thus, if you want the ellipse to fit into the model, use`'50%'`

.`refR`

- sets the radius of the circle relative to the length of the shorter side of model bbox. Percentages are interpreted relative to model bbox. Note that for backwards compatibility, setting`'100%'`

here means that the*radius*will be 100% of the length of model side. If you want the circle to fit inside the model, use`'50%'`

. There is also`refRCircumscribed`

, which sets the radius of the circle relative to the longest diagonal of model bbox.

### Introduction to calc

The `calc()`

function lets you perform calculations when specifying SVG attributes values.

#### Syntax:

The `calc()`

function takes a simple expression as its parameter, with the expression's result used as the value. The expression can be any simple expression in one of the following forms:

- Variable

`'calc(w)'`

- Variable Addition or Subtraction

`'calc(w + 5)'`

'calc(h - 10)'

- Multiplication Variable

`'calc(2 * w)'`

'calc(0.5 * h)'

- Variable Division

`'calc(w / 2)'`

'calc(h / 3)'

- Variable Division

`'calc(w / 2)'`

'calc(h / 3)'

- Multiplication Variable Addition or Subtraction

`'calc(2 * w + 5)'`

'calc(0.5 * h - 10)'

- Variable Division Addition or Subtraction

`'calc(w / 2 + 5)'`

'calc(h / 3 - 10)'

Where:

- Variable is a symbol representing a value that can change, when the model attributes change (size, attrs).

variable | name | description |
---|---|---|

`w` | width | The current width of the model (`model.prop('size/width')` ). The value can be bound to an SVGElement's size instead by using ref attribute. |

`h` | height | The current height of the model (`model.prop('size/height')` ). The value can be bound to an SVGElement's size instead by using ref attribute. |

`x` | x | The current x coordinate of the SVGElement in the element's coordinate system. If the attribute is not bound to a specific SVGElement with ref attribute, the value of x is always zero. |

`y` | y | The current y coordinate of the SVGElement in the element's coordinate system. If the attribute is not bound to a specific SVGElement with ref attribute, the value of y is always zero. |

`s` | shortest | The shortest side of the rectangle. The minimum of width and height. |

`l` | longest | The longest side of the rectangle. The maximum of width and height. |

`d` | diagonal | The length of the diagonal of the rectangle of size width and height. |

- Multiplication is an optional floating number factor of the variable. It's a number followed by the
`*`

symbol.

`1.5 *`

- Division is an optional floating number divisor of the variable. It's the
`/`

symbol followed by a number.

`/ 2`

- Addition or Subtraction is an optional floating number added or subtracted from the variable. It's a
`+`

or`-`

symbol followed by a number.

`+ 5`

##### Notes:

- Expression is case-sensitive.
- The
`+`

,`-`

and`*`

operators do not require whitespace. - No extra parentheses are allowed.
- It is permitted to nest
`calc()`

functions, in which case the inner ones are evaluated first. e.g.

`'M 0 0 H calc(w - calc(h))'`

It can be used with the following attributes:

- SVGRectElement
- SVGImageElement
- SVGLineElement
- SVGEllipseElement
- SVGCircleElement
- SVGPolygonElement
- SVGPolylineElement,
- SVGPathElement
- SVGTextElement
- Any SVG element (e.g. SVGGElement )

#### Calc examples:

`el.resize(200, 100); // dia.Element`

// <rect joint-selector="myRect" width="200" height="100" rx="20" ry="10" />

el.attr('myRect', {

width: 'calc(w)',

height: 'calc(h)',

rx: 'calc(0.1*w)',

ry: 'calc(0.1*h)'

});

// <image joint-selector="myImage" x="105" y="55" />

el.attr('myImage/x', 'calc(0.5*w+5)');

el.attr('myImage/y', 'calc(0.5*h+5)');

// <path joint-selector="myPath" d="M 10 50 190 50" />

el.attr('myPath/d', 'M 10 calc(0.5*h) calc(w-10) calc(0.5*h)')

// <polygon joint-selector="myPolygon" points="0,0 200,0 200,100 0,100" />

el.attr('myPolygon/d', '0,0 calc(w),0 calc(w),calc(h) 0,calc(h)');

// Resize the rectangle to match the text size with extra 5 pixels of padding

// <rect joint-selector="myTextBackground" />

// <text joint-selector="myText" >Some text</text>

el.attr('myTextBackground', {

ref: 'myText',

x: 'calc(x - 5)',

y: 'calc(y - 5)'

width: 'calc(w + 10)',

height: 'calc(h + 10)',

});

### Example

Let's see relative sizing with `calc()`

in action. We define a custom element type named `CustomElement`

as a subtype of `dia.Element`

. We want it to have three SVGElements - a red-tinted `ellipse`

named `e`

, a green-tinted `rect`

named `r`

, and a blue-tinted `circle`

named `c`

, respectively. The `outline`

SVGRectElement shows us the reference bbox of the element model. In the example, we use JointJS transitions to vary the dimensions of `element`

from (40,40) to (270,100). (We also adjust position to make sure the element stays in the visible area of our paper.) Notice that the subelements of the shape adjust automatically as the size of the reference model changes:

`import { dia, util } from '@joint/core';`

const CustomElement = dia.Element.define('examples.CustomElement', {

attrs: {

e: {

strokeWidth: 1,

stroke: '#000000',

fill: 'rgba(255,0,0,0.3)'

},

r: {

strokeWidth: 1,

stroke: '#000000',

fill: 'rgba(0,255,0,0.3)'

},

c: {

strokeWidth: 1,

stroke: '#000000',

fill: 'rgba(0,0,255,0.3)'

},

outline: {

x: 0,

y: 0,

width: 'calc(w)',

height: 'calc(h)',

strokeWidth: 1,

stroke: '#000000',

strokeDasharray: '5 5',

strokeDashoffset: 2.5,

fill: 'none'

}

}

}, {

markup: util.svg`

<ellipse @selector="e"/>

<rect @selector="r"/>

<circle @selector="c"/>

<rect @selector="outline"/>

`

});

const element = new CustomElement();

element.attr({

e: {

rx: 'calc(0.5*w)',

ry: 'calc(0.25*h)',

cx: 0,

cy: 'calc(0.25*h)'

},

r: {

// additional x offset

x: 'calc(w-10)',

// additional y offset

y: 'calc(h-10)',

width: 'calc(0.5*w)',

height: 'calc(0.5*h)'

},

c: {

r: 'calc(0.5*d)',

cx: 'calc(0.5*w)',

cy: 'calc(0.5*h)'

}

});

Here is the example in action:

## Relative dimensions based on text

An advanced application of relative sizing is setting the dimensions of shape subelements based on the dimensions of bboxes of rendered JointJS views. This is especially valuable when you need to base the position and size of shape components on `<text>`

subelements since JointJS is not able to work with them programmatically. Note that because this method relies on browser measurements, it is noticeably slower and less precise than the model-based method mentioned above; you should use that method for subelements that can be modeled by JointJS.

The key is the `ref`

special attribute:

`ref`

- a selector reference to the subelement whose measured bbox should be used as the base of relative calculations.

We define a custom element type named `CustomTextElement`

as a subtype of `dia.Element`

. It is very similar to `CustomElement`

defined above, except this time, all subelements refer to a new `text`

component named `label`

. In the example, we use JointJS transitions to vary the text content of `label`

. Notice that the subelements of the shape adjust automatically as the size of label changes due to the added characters:

`const CustomTextElement = dia.Element.define('examples.CustomTextElement', {`

attrs: {

label: {

textAnchor: 'middle',

textVerticalAnchor: 'middle',

fontSize: 48

},

e: {

strokeWidth: 1,

stroke: '#000000',

fill: 'rgba(255,0,0,0.3)'

},

r: {

strokeWidth: 1,

stroke: '#000000',

fill: 'rgba(0,255,0,0.3)'

},

c: {

strokeWidth: 1,

stroke: '#000000',

fill: 'rgba(0,0,255,0.3)'

},

outline: {

ref: 'label',

x: '-calc(0.5*w)',

y: '-calc(0.5*h)',

width: 'calc(w)',

height: 'calc(h)',

strokeWidth: 1,

stroke: '#000000',

strokeDasharray: '5 5',

strokeDashoffset: 2.5,

fill: 'none'

}

}

}, {

markup: util.svg`

<ellipse @selector="e"/>

<rect @selector="r"/>

<circle @selector="c"/>

<text @selector="label"/>

<rect @selector="outline"/>

`

});

const element = new CustomTextElement();

element.attr({

label: {

text: 'Hello, World!'

},

e: {

ref: 'label',

rx: 'calc(0.5*w)',

ry: 'calc(0.25*h)',

cx: '-calc(0.5*w)',

cy: '-calc(0.25*h)'

},

r: {

ref: 'label',

// additional x offset

x: 'calc(0.5*w-10)',

// additional y offset

y: 'calc(0.5*h-10)',

width: 'calc(0.5*w)',

height: 'calc(0.5*h)'

},

c: {

ref: 'label',

r: 'calc(0.5*d)'

// c is already centered at label anchor

}

});

Here is the example in action:

## Frequently asked questions

##### How can I automatically adjust an element's height and width to accommodate the text provided by the user?

You can define a custom element type with a `<text>`

subelement to which other subelements refer via the `ref`

attribute - see above.

## Learn more...

You can explore more examples of this feature on our demo page - https://www.jointjs.com/demos?feature=Content-driven+shapes.