Custom Component Shapes

Fully customize appearance of components in diagrams.

Usage

Shape registration in the Appmixer SDK:

var appmixer = new Appmixer({
    componentShapes: {
        action: myCustomComponentShape,
        trigger: myCustomComponentShape,
        myUniqueShape: myCustomComponentShape
    },
});

Use "action" and "trigger" keys to override defaults. You can define a custom shape for any component in the system, shape reference in a component manifest looks like this:

{
    shape: "myUniqueShape"
}

Built-in shapes are action, trigger, actionVertical and triggerVertical.

Manifest

Definition

Key
Description

options.updateCallback

An optional method called before the component is updated. Accepts element argument.

attributes

Element attributes, see: jointjs.dia.Cell.define

states

Definition for particular states of the component. The object structure is the same as the current scope (except for the "states" entry).

@active- the component is being modified

@invalid- the component configuration is invalid

@referenced- the component is highlighted

@unchecked- the component is selected

@checked - the component is deselected

@running - the flow is in a running state

ports.attributes

ports.states

Definition for particular states of individual ports.

The object structure is the same as the current scope (except for the "states" entry).

@connected - the port is connected

link.attributes

Link attributes, see: jointjs.dia.Link

link.tools

Link attributes, see: jointjs.linkTools

Special selectors

Dynamic properties of the component, such as label and icon, are mapped to optional selectors in the markup.

Selector
tagName
Description

label

text

Contains a label of the component.

icon

image

Contains an icon of the component.

element-halo-copy-tooltip

title

"Copy" button tooltip.

element-halo-cut-tooltip

title

"Cut" button tooltip.

element-halo-remove-tooltip

title

"Remove" button tooltip.

Special attributes

Key
Value
Description

event

"remove"

The entry acts as a remove button.

var customComponentShape = {
    attributes: {
        size: {
            height: 72,
            width: 72
        },
        markup: [
            {
                tagName: 'rect',
                selector: 'body'
            },
            {
                tagName: 'text',
                selector: 'label'
            },
            {
                tagName: 'image',
                selector: 'icon'
            },
            {
                tagName: 'g',
                selector: 'element-halo',
                children: [
                    {
                        tagName: 'image',
                        selector: 'element-halo-copy',
                        children: [
                            {
                                tagName: 'title',
                                selector: 'element-halo-copy-tooltip'
                            }
                        ]
                    },
                    {
                        tagName: 'image',
                        selector: 'element-halo-cut',
                        children: [
                            {
                                tagName: 'title',
                                selector: 'element-halo-cut-tooltip'
                            }
                        ]
                    },
                    {
                        tagName: 'image',
                        selector: 'element-halo-remove',
                        children: [
                            {
                                tagName: 'title',
                                selector: 'element-halo-remove-tooltip'
                            }
                        ]
                    }
                ]
            }
        ],
        attrs: {
            body: {
                fill: 'white',
                stroke: 'black',
                strokeWidth: 1,
                refWidth: 1,
                refHeight: 1
            },
            icon: {
                ref: 'body',
                refX: 0.5,
                refY: 0.5,
                xAlignment: 'middle',
                yAlignment: 'middle',
                width: 24,
                height: 24,
                clipPath: 'url(#icon-clip)'
            },
            label: {
                ref: 'body',
                textAnchor: 'middle',
                refX: 0.5,
                refY: '100%',
                refY2: 12,
                xAlignment: 'middle',
                fontFamily: 'sans-serif',
                fontWeight: 'bold',
                fontSize: 14,
                fill: 'black'
            },
            'element-halo': {
                display: 'none',
                ref: 'body',
                width: 68,
                height: 44,
                refX: 0.5,
                refY: 0,
                refY2: -24,
                xAlignment: 'middle'
            },
            'element-halo-copy': {
                event: 'element-copy',
                x: 0,
                y: 0,
                width: 20,
                height: 20,
                xlinkHref: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii01IC0yIDI0IDI0IiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIiBjbGFzcz0iaWNvbl9faWNvbiI+PHBhdGggZD0iTTUgMnYyaDRWMkg1em02IDBoMWEyIDIgMCAwIDEgMiAydjE0YTIgMiAwIDAgMS0yIDJIMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmgxYTIgMiAwIDAgMSAyLTJoNGEyIDIgMCAwIDEgMiAyem0wIDJhMiAyIDAgMCAxLTIgMkg1YTIgMiAwIDAgMS0yLTJIMnYxNGgxMFY0aC0xek00IDhoNmExIDEgMCAwIDEgMCAySDRhMSAxIDAgMSAxIDAtMnptMCA1aDZhMSAxIDAgMCAxIDAgMkg0YTEgMSAwIDAgMSAwLTJ6Ij48L3BhdGg+PC9zdmc+',
                cursor: 'pointer'
            },
            'element-halo-cut': {
                event: 'element-cut',
                x: 24,
                y: 0,
                width: 20,
                height: 20,
                cursor: 'pointer',
                xlinkHref: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0yLjUgLTIuNSAyNCAyNCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiIgY2xhc3M9Imljb25fX2ljb24iPjxwYXRoIGQ9Ik05LjEwNyAxMC41NTRMNy4zNjMgMTIuNjNhMy45OTMgMy45OTMgMCAwIDEtLjE5MiA0Ljg5IDQuMDA0IDQuMDA0IDAgMCAxLTUuNjM1LjQ5MiAzLjk5MSAzLjk5MSAwIDAgMS0uNDkzLTUuNjI4IDQuMDA1IDQuMDA1IDAgMCAxIDQuNzg4LTEuMDM4TDcuODAxIDkgMi40MTMgMi41ODdhLjk5OC45OTggMCAwIDEgLjEyMy0xLjQwNyAxIDEgMCAwIDEgMS40MDkuMTIzbDUuMTYyIDYuMTQ0IDUuMTYxLTYuMTQ0YTEgMSAwIDAgMSAxLjQxLS4xMjMuOTk4Ljk5OCAwIDAgMSAuMTIzIDEuNDA3TDEwLjQxMiA5bDEuOTcgMi4zNDVhNC4wMDUgNC4wMDUgMCAwIDEgNC43ODggMS4wMzggMy45OTEgMy45OTEgMCAwIDEtLjQ5MyA1LjYyOCA0LjAwNCA0LjAwNCAwIDAgMS01LjYzNS0uNDkyIDMuOTkzIDMuOTkzIDAgMCAxLS4xOTItNC44OWwtMS43NDMtMi4wNzV6bS02LjI4NSA1LjkyN2EyIDIgMCAwIDAgMi41NzEtMy4wNiAyLjAwMiAyLjAwMiAwIDAgMC0yLjgxOC4yNDZjLS43MS44NDUtLjYgMi4xMDUuMjQ3IDIuODE0em0xMi41NyAwYTEuOTk2IDEuOTk2IDAgMCAwIC4yNDYtMi44MTQgMi4wMDIgMi4wMDIgMCAwIDAtMi44MTctLjI0NiAxLjk5NiAxLjk5NiAwIDAgMC0uMjQ3IDIuODE0Yy43MS44NDUgMS45NzIuOTU1IDIuODE4LjI0NnoiPjwvcGF0aD48L3N2Zz4'
            },
            'element-halo-remove': {
                event: 'element-remove',
                x: 48,
                y: 0,
                width: 20,
                height: 20,
                cursor: 'pointer',
                xlinkHref: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0zIC0yIDI0IDI0IiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIiBjbGFzcz0iaWNvbl9faWNvbiI+PHBhdGggZD0iTTYgMlYxYTEgMSAwIDAgMSAxLTFoNGExIDEgMCAwIDEgMSAxdjFoNGEyIDIgMCAwIDEgMiAydjFhMiAyIDAgMCAxLTIgMmgtLjEzM2wtLjY4IDEwLjJhMyAzIDAgMCAxLTIuOTkzIDIuOEg1LjgyNmEzIDMgMCAwIDEtMi45OTMtMi43OTZMMi4xMzcgN0gyYTIgMiAwIDAgMS0yLTJWNGEyIDIgMCAwIDEgMi0yaDR6bTEwIDJIMnYxaDE0VjR6TTQuMTQxIDdsLjY4NyAxMC4wNjhhMSAxIDAgMCAwIC45OTguOTMyaDYuMzY4YTEgMSAwIDAgMCAuOTk4LS45MzRMMTMuODYyIDdoLTkuNzJ6TTcgOGExIDEgMCAwIDEgMSAxdjdhMSAxIDAgMCAxLTIgMFY5YTEgMSAwIDAgMSAxLTF6bTQgMGExIDEgMCAwIDEgMSAxdjdhMSAxIDAgMCAxLTIgMFY5YTEgMSAwIDAgMSAxLTF6Ij48L3BhdGg+PC9zdmc+'
            },
            'element-halo-copy-tooltip': {
                fontFamily: 'nunitosans-regular, Helvetica, Arial, sans-serif',
                fontSize: 13
            },
            'element-halo-cut-tooltip': {
                fontFamily: 'nunitosans-regular, Helvetica, Arial, sans-serif',
                fontSize: 13
            },
            'element-halo-remove-tooltip': {
                fontFamily: 'nunitosans-regular, Helvetica, Arial, sans-serif',
                fontSize: 13
            }
        }
    },
    ports: {
        attributes: {
            in: {
                position: {
                    name: 'left'
                },
                attrs: {
                    '.port-body': {
                        r: 5,
                        strokeWidth: 2,
                        stroke: 'black',
                        fill: 'white'
                    },
                    'connection-port-label': {
                        fill: 'black',
                        fontFamily: 'sans-serif',
                        fontSize: 12
                    }
                },
                label: {
                    position: {
                        name: 'left',
                        args: {
                            x: -8,
                            y: 14
                        }
                    },
                    markup: [
                        {
                            tagName: 'text',
                            selector: 'connection-port-label'
                        }
                    ]
                }
            },
            out: {
                position: {
                    name: 'right'
                },
                attrs: {
                    '.port-body': {
                        r: 5,
                        strokeWidth: 2,
                        stroke: 'black',
                        fill: 'white'
                    },
                    'connection-port-label': {
                        fill: 'black',
                        fontFamily: 'sans-serif',
                        fontSize: 12
                    }
                },
                label: {
                    position: {
                        name: 'right',
                        args: {
                            x: 8,
                            y: 14
                        }
                    },
                    markup: [
                        {
                            tagName: 'text',
                            selector: 'connection-port-label'
                        }
                    ]
                }
            }
        }
    },
    link: {
        attributes: {
            router: {
                name: 'metro'
            },
            connector: {
                name: 'rounded',
                args: {
                    radius: 8
                }
            },
            markup: [
                {
                    tagName: 'path',
                    selector: 'wrapper'
                },
                {
                    tagName: 'path',
                    selector: 'line'
                }
            ],
            tools: [
                {
                    z: 100,
                    distance: '50%',
                    event: 'link-remove',
                    markup: [
                        {
                            tagName: 'circle',
                            selector: 'button',
                            attributes: {
                                cx: -0.5,
                                cy: -0.5,
                                r: 10,
                                fill: config.colors.bodyStrokeActive,
                                cursor: 'pointer'
                            }
                        },
                        {
                            tagName: 'path',
                            selector: 'icon',
                            attributes: {
                                pointerEvents: 'none',
                                transform: 'translate(-6.4 -7)',
                                d: 'M8,1.333h3.333A.667.667,0,0,1,12,2V3.333A.667.667,0,0,1,11.333,4H.667A.667.667,0,0,1,0,3.333V2a.667.667,0,0,1,.667-.667H4V.667A.667.667,0,0,1,4.667,0H7.333A.667.667,0,0,1,8,.667Zm2.533,4-.409,6.133a2,2,0,0,1-2,1.867H3.884a2,2,0,0,1-2-1.864L1.47,5.333ZM4.667,6A.667.667,0,0,0,4,6.667v4.667a.667.667,0,0,0,1.333,0V6.667A.667.667,0,0,0,4.667,6ZM7.333,6a.667.667,0,0,0-.667.667v4.667a.667.667,0,0,0,1.333,0V6.667A.667.667,0,0,0,7.333,6Z',
                                fill: 'white'
                            }
                        }
                    ]
                }
            ],
            attrs: {
                line: {
                    pointerEvents: 'none',
                    connection: true,
                    stroke: 'black',
                    strokeWidth: 1.5,
                    strokeDasharray: '4 4',
                    fill: 'transparent'
                },
                wrapper: {
                    connection: true,
                    cursor: 'pointer',
                    stroke: 'transparent',
                    strokeWidth: 24,
                    fill: 'transparent'
                }
            }
        }
    },
    states: {
        '@active': {
            attributes: {
                attrs: {
                    body: {
                        stroke: 'blue',
                        strokeWidth: 3
                    },
                    label: {
                        fill: 'black'
                    },
                    'element-halo': {
                        display: 'initial'
                    }
                }
            },
            ports: {
                attributes: {
                    in: {
                        attrs: {
                            '.port-body': {
                                stroke: 'blue'
                            }
                        }
                    },
                    out: {
                        attrs: {
                            '.port-body': {
                                stroke: 'blue'
                            }
                        }
                    }
                }
            },
            link: {
                attributes: {
                    attrs: {
                        line: {
                            strokeDasharray: 0,
                            stroke: 'blue'
                        }
                    }
                }
            }
        },
        '@invalid': {
            attributes: {
                attrs: {
                    body: {
                        stroke: 'red',
                        strokeWidth: 2
                    },
                    label: {
                        fill: 'red'
                    }
                }
            },
            ports: {
                attributes: {
                    in: {
                        attrs: {
                            '.port-body': {
                                stroke: 'red'
                            }
                        }
                    },
                    out: {
                        attrs: {
                            '.port-body': {
                                stroke: 'red'
                            }
                        }
                    }
                }
            }
        }
    }
};

var customSelectionShape = {
    attributes: {
        markup: [
            {
                tagName: 'rect',
                selector: 'body'
            },
            {
                tagName: 'g',
                selector: 'halo',
                children: [
                    {
                        tagName: 'image',
                        selector: 'halo-copy'
                    },
                    {
                        tagName: 'image',
                        selector: 'halo-cut'
                    },
                    {
                        tagName: 'image',
                        selector: 'halo-remove'
                    }
                ]
            }
        ],
        attrs: {
            body: {
                pointerEvents: 'none',
                fill: 'rgba(0, 0, 0, 0.1)',
                stroke: 'black',
                strokeWidth: 1
            },
            halo: {
                ref: 'body',
                width: 68,
                height: 44,
                refX: 0.5,
                refY: 0,
                refY2: -24,
                xAlignment: 'middle'
            },
            'halo-copy': {
                event: 'element-copy',
                x: 0,
                y: 0,
                width: 20,
                height: 20,
                xlinkHref: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii01IC0yIDI0IDI0IiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIiBjbGFzcz0iaWNvbl9faWNvbiI+PHBhdGggZD0iTTUgMnYyaDRWMkg1em02IDBoMWEyIDIgMCAwIDEgMiAydjE0YTIgMiAwIDAgMS0yIDJIMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmgxYTIgMiAwIDAgMSAyLTJoNGEyIDIgMCAwIDEgMiAyem0wIDJhMiAyIDAgMCAxLTIgMkg1YTIgMiAwIDAgMS0yLTJIMnYxNGgxMFY0aC0xek00IDhoNmExIDEgMCAwIDEgMCAySDRhMSAxIDAgMSAxIDAtMnptMCA1aDZhMSAxIDAgMCAxIDAgMkg0YTEgMSAwIDAgMSAwLTJ6Ij48L3BhdGg+PC9zdmc+',
                cursor: 'pointer'
            },
            'halo-cut': {
                event: 'element-cut',
                x: 24,
                y: 0,
                width: 20,
                height: 20,
                cursor: 'pointer',
                xlinkHref: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0yLjUgLTIuNSAyNCAyNCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiIgY2xhc3M9Imljb25fX2ljb24iPjxwYXRoIGQ9Ik05LjEwNyAxMC41NTRMNy4zNjMgMTIuNjNhMy45OTMgMy45OTMgMCAwIDEtLjE5MiA0Ljg5IDQuMDA0IDQuMDA0IDAgMCAxLTUuNjM1LjQ5MiAzLjk5MSAzLjk5MSAwIDAgMS0uNDkzLTUuNjI4IDQuMDA1IDQuMDA1IDAgMCAxIDQuNzg4LTEuMDM4TDcuODAxIDkgMi40MTMgMi41ODdhLjk5OC45OTggMCAwIDEgLjEyMy0xLjQwNyAxIDEgMCAwIDEgMS40MDkuMTIzbDUuMTYyIDYuMTQ0IDUuMTYxLTYuMTQ0YTEgMSAwIDAgMSAxLjQxLS4xMjMuOTk4Ljk5OCAwIDAgMSAuMTIzIDEuNDA3TDEwLjQxMiA5bDEuOTcgMi4zNDVhNC4wMDUgNC4wMDUgMCAwIDEgNC43ODggMS4wMzggMy45OTEgMy45OTEgMCAwIDEtLjQ5MyA1LjYyOCA0LjAwNCA0LjAwNCAwIDAgMS01LjYzNS0uNDkyIDMuOTkzIDMuOTkzIDAgMCAxLS4xOTItNC44OWwtMS43NDMtMi4wNzV6bS02LjI4NSA1LjkyN2EyIDIgMCAwIDAgMi41NzEtMy4wNiAyLjAwMiAyLjAwMiAwIDAgMC0yLjgxOC4yNDZjLS43MS44NDUtLjYgMi4xMDUuMjQ3IDIuODE0em0xMi41NyAwYTEuOTk2IDEuOTk2IDAgMCAwIC4yNDYtMi44MTQgMi4wMDIgMi4wMDIgMCAwIDAtMi44MTctLjI0NiAxLjk5NiAxLjk5NiAwIDAgMC0uMjQ3IDIuODE0Yy43MS44NDUgMS45NzIuOTU1IDIuODE4LjI0NnoiPjwvcGF0aD48L3N2Zz4'
            },
            'halo-remove': {
                event: 'element-remove',
                x: 48,
                y: 0,
                width: 20,
                height: 20,
                cursor: 'pointer',
                xlinkHref: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0zIC0yIDI0IDI0IiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIiBjbGFzcz0iaWNvbl9faWNvbiI+PHBhdGggZD0iTTYgMlYxYTEgMSAwIDAgMSAxLTFoNGExIDEgMCAwIDEgMSAxdjFoNGEyIDIgMCAwIDEgMiAydjFhMiAyIDAgMCAxLTIgMmgtLjEzM2wtLjY4IDEwLjJhMyAzIDAgMCAxLTIuOTkzIDIuOEg1LjgyNmEzIDMgMCAwIDEtMi45OTMtMi43OTZMMi4xMzcgN0gyYTIgMiAwIDAgMS0yLTJWNGEyIDIgMCAwIDEgMi0yaDR6bTEwIDJIMnYxaDE0VjR6TTQuMTQxIDdsLjY4NyAxMC4wNjhhMSAxIDAgMCAwIC45OTguOTMyaDYuMzY4YTEgMSAwIDAgMCAuOTk4LS45MzRMMTMuODYyIDdoLTkuNzJ6TTcgOGExIDEgMCAwIDEgMSAxdjdhMSAxIDAgMCAxLTIgMFY5YTEgMSAwIDAgMSAxLTF6bTQgMGExIDEgMCAwIDEgMSAxdjdhMSAxIDAgMCAxLTIgMFY5YTEgMSAwIDAgMSAxLTF6Ij48L3BhdGg+PC9zdmc+'
            }
        }
    }
};

new Appmixer({
    componentShapes: {
        action: customComponentShape,
        trigger: customComponentShape,
        selection: customSelectionShape        
    }
});

Last updated