A Content Driven Element
The size of this custom element is derived from its content (the size of the text and an image if present).
by Roman Bruckner
HTML
<html>
<head>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jointjs/3.4.4/joint.css" />
</head>
<body>
<!-- content -->
<div id="paper"></div>
<!-- dependencies -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.4.0/backbone.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jointjs/3.4.4/joint.js"></script>
</body>
</html>
JavaScript
const { dia, shapes } = joint;
const graph = new dia.Graph({}, {
cellNamespace: shapes
});
const paper = new dia.Paper({
el: document.getElementById('paper'),
width: 600,
height: 600,
gridSize: 20,
model: graph,
async: true,
sorting: joint.dia.Paper.sorting.APPROX,
cellViewNamespace: joint.shapes
});
const svg = paper.svg;
function measureText(svgDocument, text, attrs) {
const vText = V('text').attr(attrs).text(text);
vText.appendTo(svgDocument);
const bbox = vText.getBBox();
vText.remove();
return bbox;
}
class Shape extends joint.dia.Element {
defaults() {
return {
...super.defaults,
type: 'custom.Shape',
fillColor: 'red',
outlineColor: 'blue',
label: '',
image: ''
};
}
preinitialize() {
this.spacing = 10;
this.labelAttributes = {
'font-size': 14,
'font-family': 'sans-serif',
};
this.imageAttributes = {
'width': 50,
'height': 50,
'preserveAspectRatio': 'none'
};
this.cache = {};
}
initialize() {
super.initialize();
this.on('change', this.onAttributeChange);
this.setSizeFromContent();
}
/* Attributes that affects the size of the model. */
onAttributeChange() {
const {
changed,
cache
} = this;
if ('label' in changed) {
// invalidate the cache only if the text of the `label` has changed
delete cache.label;
}
if ('label' in changed || 'image' in changed) {
this.setSizeFromContent();
}
}
setSizeFromContent() {
delete this.cache.layout;
const {
width,
height
} = this.layout();
this.resize(width, height);
}
layout() {
const {
cache
} = this;
let {
layout
} = cache;
if (layout) {
return layout;
} else {
const layout = this.calcLayout();
cache.layout = layout;
return layout;
}
}
calcLayout() {
const {
attributes,
labelAttributes,
...