## Special Attributes

This is the first article of the intermediate section of the JointJS tutorial. Return to index.

Special attributes are JointJS-specific attributes that offer functionality beyond that of native SVG attributes. We have already mentioned them when talking about element styling, and seen them in action when adding link arrowheads. They become much more important when we talk about creating custom elements, links and link labels.

The main way to define the styling of diagram elements, links, and labels in JointJS is through `attrs` objects. If the attributes passed are standard SVG attributes, they are merely passed down to the individual SVGElements of the shape; then it is the job of the browser to apply them to the elements and render the shapes in the requested manner when asked to do so by JointJS View classes. However, if JointJS encounters one of its special attributes, it takes over with custom logic in order to offer advanced functionality; the results are then encoded back as standard SVG attributes.

All JointJS special attributes use camelCase naming. For consistency, it is thus very strongly recommended that you make use JointJS's ability to translate camelCase into native SVG's kebab-case, and use camelCase when setting native SVG attributes as well (i.e. `strokeWidth` instead of native `'stroke-width'`).

A list of native SVG attributes can be found elsewhere on the Internet; have a look, for example, at MDN's SVG Attribute reference. In this section of the tutorial, we want to show you what additional things JointJS allows you to do.

### Relative Dimensions

One of the most common requests when working with SVG is to set the dimensions of SVGElements relatively. JointJS allows you to do that with a suite of `ref` attributes. These attributes allow you to size shape subelements as a percentage of the dimensions of the shape's model. Moreover, since all calculations are programmatic and do not rely on browser's bbox measurements, using these attributes does not impact performance of your app. (There is also a view-based method if you need to work with text; it is explained in the following section.)

- `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.

Let us see the relative attributes in action. We define a custom element type named `CustomElement` as a subtype of `joint.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:

    var CustomElement = joint.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: {
                refX: 0,
                refY: 0,
                refWidth: '100%',
                refHeight: '100%',
                strokeWidth: 1,
                stroke: '#000000',
                strokeDasharray: '5 5',
                strokeDashoffset: 2.5,
                fill: 'none'
            }
        }
    }, {
        markup: [{
            tagName: 'ellipse',
            selector: 'e'
        }, {
            tagName: 'rect',
            selector: 'r'
        }, {
            tagName: 'circle',
            selector: 'c'
        }, {
            tagName: 'rect',
            selector: 'outline'
        }]
    });

    var element = new CustomElement();
    element.attr({
        e: {
            refRx: '50%',
            refRy: '25%',
            refCx: '50%',
            refCy: 0,
            refX: '-50%',
            refY: '25%'
        },
        r: {
            refX: '100%',
            x: -10, // additional x offset
            refY: '100%',
            y: -10, // additional y offset
            refWidth: '50%',
            refHeight: '50%',
        },
        c: {
            refRCircumscribed: '50%',
            refCx: '50%',
            refCy: '50%'
        }
    });

JointJS source code: special-attributes-relative-dimensions.js

### Relative Dimensions Based on Text

An advanced application of relative attributes is using them to set 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 attributes.

We define a custom element type named `CustomTextElement` as a subtype of `joint.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:

    var CustomTextElement = joint.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',
                refX: 0,
                refY: 0,
                refWidth: '100%',
                refHeight: '100%',
                strokeWidth: 1,
                stroke: '#000000',
                strokeDasharray: '5 5',
                strokeDashoffset: 2.5,
                fill: 'none'
            }
        }
    }, {
        markup: [{
            tagName: 'ellipse',
            selector: 'e'
        }, {
            tagName: 'rect',
            selector: 'r'
        }, {
            tagName: 'circle',
            selector: 'c'
        }, {
            tagName: 'text',
            selector: 'label'
        }, {
            tagName: 'rect',
            selector: 'outline'
        }]
    });

    var element = new CustomTextElement();
    element.attr({
        label: {
            text: 'Hello, World!'
        },
        e: {
            ref: 'label',
            refRx: '50%',
            refRy: '25%',
            refCx: '50%',
            refCy: 0,
            refX: '-50%',
            refY: '25%'
        },
        r: {
            ref: 'label',
            refX: '100%',
            x: -10, // additional x offset
            refY: '100%',
            y: -10, // additional y offset
            refWidth: '50%',
            refHeight: '50%',
        },
        c: {
            ref: 'label',
            refRCircumscribed: '50%',
            // c is already centered at label anchor
        }
    });

JointJS source code: special-attributes-text-relative-dimensions.js

### Link Arrowheads

Two special attributes are in charge of link arrowheads. We already mentioned them in the basic tutorial:

- `sourceMarker` and `targetMarker` - sets an arrowhead of requested SVGElement `'type'` to the start/end of the link. The other properties passed in the object are expected to be SVG attributes for that SVGElement.

Link arrowhead special attributes expect an object with native SVG attributes. This means that they are not able to understand JointJS special attributes, and they cannot make use of JointJS's camelCase translation. In order to better communicate these restrictions to programmers, we strongly encourage the use of quotation marks around all arrowhead marker properties (i.e. `'type'` and not `type`; `'stroke-width'` and not `strokeWidth`).

The `'type'` of the arrowhead can be any valid SVGElement type. The following example shows how to create a link with two simple `<path>` arrowheads.

The two arrowheads have the same path data commands, despite pointing in opposite directions; this is because all `targetMarker` values are automatically rotated by 180 degrees. The path commands' coordinate system has origin at the tip of the link and is rotated according to the slope of the link at that point. This means that you can design all your arrowheads as if they were pointing left and towards the point `0,0` in local coordinates, and then rely on JointJS's automatic arrowhead rotation.

Note that, in general, if the `'fill'` and `'stroke'` colors are not specified, they are adopted from the `line.stroke` attribute.

    link.attr({
        line: {
            sourceMarker: { // hour hand
                'type': 'path',
                'd': 'M 20 -10 0 0 20 10 Z'
            },
            targetMarker: { // minute hand
                'type': 'path',
                'stroke': 'green',
                'stroke-width': 2
                'fill': 'yellow',
                'd': 'M 20 -10 0 0 20 10 Z'
            }
        }
    });

JointJS source code: links-arrowheads-path.js

To create an `<image>` arrowhead, you need to provide an URL with the path to your image to the `'xlink:href'` property, and then specify the desired `'width'` and `'height'`. Keep in mind that both of your markers should reference images pointing left; the image for `targetMarker` will be automatically rotated by 180 degrees by JointJS, and then both markers will match the gradient of the link path. Remember to reposition the image in the `'y'` axis (by `-1/2` the value of `'height'`) if you need the arrowhead to be centered.

    link.attr({
        line: {
            sourceMarker: {
                'type': 'image',
                'xlink:href': 'http://cdn3.iconfinder.com/data/icons/49handdrawing/24x24/left.png',
                'width': 24,
                'height': 24,
                'y': -12
            },
            targetMarker: {
                'type': 'image',
                'xlink:href': 'http://cdn3.iconfinder.com/data/icons/49handdrawing/24x24/left.png',
                'width': 24,
                'height': 24,
                'y': -12
            }
        }
    });

JointJS source code: links-arrowheads-image.js

To create an arrowhead of arbitrary SVGElement type, simply specify the `'type'` appropriately, and then provide native SVG attributes to style it. Keep in mind that `'circle'` and `'ellipse'` SVG elements have origin in their center; they need to be repositioned with the `'cx'` attribute (by the value of `'r'`) if you do not want them to overflow the end of the link.

    link.attr({
        line: {
            sourceMarker: {
                'type': 'rect',
                'width': 50,
                'height': 10,
                'y': -5,
                'fill': 'rgba(255,0,0,0.3)',
                'stroke': 'black'
            },
            targetMarker: {
                'type': 'circle',
                'r': 10,
                'cx': 10,
                'fill': 'rgba(0,255,0,0.3)',
                'stroke': 'black'
            }
        }
    });

JointJS source code: special-attributes-link-arrowheads.js

### Relative Position on Links

Several special attributes on links allow you to position subelements relatively to the link's connection path:

- `connection` - if set to `true`, the subelement will follow the link connection path. Applicable only to `<path>` subelements.
- `atConnectionLength` - sets the absolute distance from the beginning of the path at which the anchor of the subelement should be placed. Negative distances are counted from the end of the connection path. Rotates subelement according to link gradient at requested length.
- `atConnectionRatio` - sets the relative distance from the beginning of the path at which the anchor of the subelement should be placed. Accepts numbers between `0` and `1`. Rotates subelement according to link gradient at requested ratio.

These attributes are ideal for adding symbols and arrowheads on the connection path and have them rotate according to path slope. Let us illustrate with a custom link type:

    var CustomLink = joint.dia.Link.define('examples.CustomLink', {
        attrs: {
            line: {
                connection: true,
                fill: 'none',
                stroke: 'orange',
                strokeWidth: 2,
                sourceMarker: {
                    'type': 'circle',
                    'r': 4,
                    'fill': 'white',
                    'stroke': 'orange',
                    'stroke-width': '2'
                },
                targetMarker: {
                    'type': 'circle',
                    'r': 4,
                    'fill': 'white',
                    'stroke': 'orange',
                    'stroke-width': '2'
                }
            },
            arrowhead: {
                d: 'M -20 -10 0 0 -20 10 Z',
                fill: 'orange',
                stroke: 'none'
            },
            symbol: {
                d: 'M -20 -20 20 20',
                stroke: 'black',
                targetMarker: {
                    'type': 'path',
                    'd': 'M 0 0 10 -5 10 5 Z',
                    'fill': 'black',
                    'stroke': 'none'
                }
            }
        }
    }, {
        markup: [{
            tagName: 'path',
            selector: 'line'
        }, {
            tagName: 'path',
            selector: 'arrowhead'
        }, {
            tagName: 'path',
            selector: 'symbol'
        }]
    });

    var link = new CustomLink();
    link.attr({
        symbol: {
            atConnectionRatio: 0.25
        },
        arrowhead: {
            atConnectionRatio: 0.75,
        }
    });

JointJS source code: special-attributes-link-relative-position.js

### Link Subelement Labels

Special attributes also allow us to create custom labeling subelements - both those that adjust their rotation according to the underlying link path and those that always keep the same angle. However, keep in mind that link subelements are more difficult to set up and use dynamically than link labels (JointJS does not come with a built-in API to do this), and that they have a very important limitation when used this way:

- They cannot use the `ref` attribute. This means that they are unable to automatically set their width and height values based on the browser-calculated dimensions of the text bbox. It is up to the programmer to provide approximate width and height values to accommodate the label text.

For this reason, we recommend everyone to use the link label API.

Link subelement labels that do not keep link connection gradient are enabled with the following two special attributes:

- `atConnectionLengthIgnoreGradient` - sets the absolute distance from the beginning of the path at which the anchor of the subelement should be placed. Negative distances are counted from the end of the connection path. Does not rotate subelement according to path gradient.
- `atConnectionRatioIgnoreGradient` - sets the relative distance from the beginning of the path at which the anchor of the subelement should be placed. Accepts numbers between `0` and `1`. Does not rotate subelement according to path gradient.

(The previously introduced `atConnectionLength`/`atConnectionRatio` attributes are actually aliases for more verbose names `atConnectionLengthKeepGradient`/`atConnectionRatioKeepGradient`.) In both cases (that is, whether we keep link gradient or not), offsetting of link subelement labels is achieved by clever use of the native `x` and `y` SVG attributes.

Let us illustrate with the following demo. A custom link type is defined with link label subelements positioned in such a way as to emulate the functionality shown in the link label position and offset examples. The red asterisk marks the reference point of the offset subelements on the link connection path.

    var CustomLink = joint.dia.Link.define('examples.CustomLink', {
        attrs: {
            line: {
                connection: true,
                fill: 'none',
                stroke: '#333333',
                strokeWidth: 2,
                strokeLinejoin: 'round',
                targetMarker: {
                    'type': 'path',
                    'd': 'M 10 -5 0 0 10 5 z'
                }
            },
            relativeLabel: {
                textAnchor: 'middle',
                textVerticalAnchor: 'middle',
                fill: 'black',
                fontSize: 12
            },
            relativeLabelBody: {
                x: -15,
                y: -10,
                width: 30,
                height: 20,
                fill: 'white',
                stroke: 'black'
            },
            absoluteLabel: {
                textAnchor: 'middle',
                textVerticalAnchor: 'middle',
                fill: 'black',
                fontSize: 12
            },
            absoluteLabelBody: {
                x: -15,
                y: -10,
                width: 30,
                height: 20,
                fill: 'white',
                stroke: 'black'
            },
            absoluteReverseLabel: {
                textAnchor: 'middle',
                textVerticalAnchor: 'middle',
                fill: 'black',
                fontSize: 12
            },
            absoluteReverseLabelBody: {
                x: -15,
                y: -10,
                width: 30,
                height: 20,
                fill: 'white',
                stroke: 'black'
            },
            offsetLabelPositive: {
                textAnchor: 'middle',
                textVerticalAnchor: 'middle',
                fill: 'black',
                fontSize: 12
            },
            offsetLabelPositiveBody: {
                width: 120,
                height: 20,
                fill: 'white',
                stroke: 'black'
            },
            offsetLabelNegative: {
                textAnchor: 'middle',
                textVerticalAnchor: 'middle',
                fill: 'black',
                fontSize: 12
            },
            offsetLabelNegativeBody: {
                width: 120,
                height: 20,
                fill: 'white',
                stroke: 'black'
            },
            offsetLabelAbsolute: {
                textAnchor: 'middle',
                textVerticalAnchor: 'middle',
                fill: 'black',
                fontSize: 12
            },
            offsetLabelAbsoluteBody: {
                width: 140,
                height: 20,
                fill: 'white',
                stroke: 'black'
            }
        }
    }, {
        markup: [{
            tagName: 'path',
            selector: 'line'
        }, {
            tagName: 'rect',
            selector: 'relativeLabelBody'
        }, {
            tagName: 'text',
            selector: 'relativeLabel'
        }, {
            tagName: 'rect',
            selector: 'absoluteLabelBody'
        }, {
            tagName: 'text',
            selector: 'absoluteLabel'
        }, {
            tagName: 'rect',
            selector: 'absoluteReverseLabelBody'
        }, {
            tagName: 'text',
            selector: 'absoluteReverseLabel'
        }, {
            tagName: 'rect',
            selector: 'offsetLabelPositiveBody'
        }, {
            tagName: 'text',
            selector: 'offsetLabelPositive'
        }, {
            tagName: 'rect',
            selector: 'offsetLabelNegativeBody'
        }, {
            tagName: 'text',
            selector: 'offsetLabelNegative'
        }, {
            tagName: 'rect',
            selector: 'offsetLabelAbsoluteBody'
        }, {
            tagName: 'text',
            selector: 'offsetLabelAbsolute'
        }]
    });

    var link = new CustomLink();
    link.attr({
        relativeLabel: {
            atConnectionRatio: 0.25,
            text: '0.25'
        },
        relativeLabelBody: {
            atConnectionRatio: 0.25
        },
        absoluteLabel: {
            atConnectionLength: 150,
            text: '150'
        },
        absoluteLabelBody: {
            atConnectionLength: 150
        },
        absoluteReverseLabel: {
            atConnectionLength: -100,
            text: '-100'
        },
        absoluteReverseLabelBody: {
            atConnectionLength: -100
        },
        offsetLabelPositive: {
            atConnectionRatio: 0.66,
            y: 40,
            text: 'keepGradient: 0,40'
        },
        offsetLabelPositiveBody: {
            atConnectionRatio: 0.66,
            x: -60, // 0 + -60
            y: 30 // 40 + -10
        },
        offsetLabelNegative: {
            atConnectionRatio: 0.66,
            y: -40,
            text: 'keepGradient: 0,-40'
        },
        offsetLabelNegativeBody: {
            atConnectionRatio: 0.66,
            x: -60, // 0 + -60
            y: -50 // -40 + -10
        },
        offsetLabelAbsolute: {
            atConnectionRatioIgnoreGradient: 0.66,
            x: -40,
            y: 80,
            text: 'ignoreGradient: -40,80'
        },
        offsetLabelAbsoluteBody: {
            atConnectionRatioIgnoreGradient: 0.66,
            x: -110, // -40 + -70
            y: 70 // 80 + -10
        }
    });

JointJS source code: special-attributes-link-subelement-labels.js

In the next section of the intermediate tutorial, we will find out how to make use of JointJS events to support user interaction.