diff --git a/lib/sequence-diagram.js b/lib/sequence-diagram.js
index 70776f9..bbdfa16 100644
--- a/lib/sequence-diagram.js
+++ b/lib/sequence-diagram.js
@@ -465,6 +465,16 @@ define('core/EventObject',[],() => {
}
}
+ on(type, fn) {
+ this.addEventListener(type, fn);
+ return this;
+ }
+
+ off(type, fn) {
+ this.removeEventListener(type, fn);
+ return this;
+ }
+
countEventListeners(type) {
return (this.listeners.get(type) || []).length;
}
@@ -3451,69 +3461,221 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
};
});
-define('svg/SVGUtilities',[],() => {
+define('core/DOMWrapper',[],() => {
'use strict';
- const NS = 'http://www.w3.org/2000/svg';
-
- function makeText(text = '') {
- return document.createTextNode(text);
+ function make(value, document) {
+ if(typeof value === 'string') {
+ return document.createTextNode(value);
+ } else if(typeof value === 'number') {
+ return document.createTextNode(value.toString(10));
+ } else if(typeof value === 'object' && value.element) {
+ return value.element;
+ } else {
+ return value;
+ }
}
- function setAttributes(target, attrs) {
- for(const k in attrs) {
- if(attrs.hasOwnProperty(k)) {
- target.setAttribute(k, attrs[k]);
+ function unwrap(node) {
+ if(node === null) {
+ return null;
+ } else if(node.element) {
+ return node.element;
+ } else {
+ return node;
+ }
+ }
+
+ class WrappedElement {
+ constructor(element) {
+ this.element = element;
+ }
+
+ addBefore(child = null, before = null) {
+ if(child === null) {
+ return this;
+ } else if(Array.isArray(child)) {
+ for(const c of child) {
+ this.addBefore(c, before);
+ }
+ } else {
+ const childElement = make(child, this.element.ownerDocument);
+ this.element.insertBefore(childElement, unwrap(before));
+ }
+ return this;
+ }
+
+ add(...child) {
+ return this.addBefore(child, null);
+ }
+
+ del(child = null) {
+ if(child !== null) {
+ this.element.removeChild(unwrap(child));
+ }
+ return this;
+ }
+
+ attr(key, value) {
+ this.element.setAttribute(key, value);
+ return this;
+ }
+
+ attrs(attrs) {
+ for(const k in attrs) {
+ if(attrs.hasOwnProperty(k)) {
+ this.element.setAttribute(k, attrs[k]);
+ }
+ }
+ return this;
+ }
+
+ styles(styles) {
+ for(const k in styles) {
+ if(styles.hasOwnProperty(k)) {
+ this.element.style[k] = styles[k];
+ }
+ }
+ return this;
+ }
+
+ setClass(cls) {
+ return this.attr('class', cls);
+ }
+
+ addClass(cls) {
+ const classes = this.element.getAttribute('class');
+ if(!classes) {
+ return this.setClass(cls);
+ }
+ const list = classes.split(' ');
+ if(list.includes(cls)) {
+ return this;
+ }
+ list.push(cls);
+ return this.attr('class', list.join(' '));
+ }
+
+ delClass(cls) {
+ const classes = this.element.getAttribute('class');
+ if(!classes) {
+ return this;
+ }
+ const list = classes.split(' ');
+ const p = list.indexOf(cls);
+ if(p !== -1) {
+ list.splice(p, 1);
+ this.attr('class', list.join(' '));
+ }
+ return this;
+ }
+
+ text(text) {
+ this.element.textContent = text;
+ return this;
+ }
+
+ on(event, callback, options = {}) {
+ if(Array.isArray(event)) {
+ for(const e of event) {
+ this.on(e, callback, options);
+ }
+ } else {
+ this.element.addEventListener(event, callback, options);
+ }
+ return this;
+ }
+
+ off(event, callback, options = {}) {
+ if(Array.isArray(event)) {
+ for(const e of event) {
+ this.off(e, callback, options);
+ }
+ } else {
+ this.element.removeEventListener(event, callback, options);
+ }
+ return this;
+ }
+
+ val(value) {
+ this.element.value = value;
+ return this;
+ }
+
+ select(start, end = null) {
+ this.element.selectionStart = start;
+ this.element.selectionEnd = (end === null) ? start : end;
+ return this;
+ }
+
+ focus() {
+ this.element.focus();
+ return this;
+ }
+
+ focussed() {
+ return this.element === this.element.ownerDocument.activeElement;
+ }
+
+ empty() {
+ while(this.element.childNodes.length > 0) {
+ this.element.removeChild(this.element.lastChild);
+ }
+ return this;
+ }
+
+ attach(parent) {
+ unwrap(parent).appendChild(this.element);
+ return this;
+ }
+
+ detach() {
+ this.element.parentNode.removeChild(this.element);
+ return this;
+ }
+ }
+
+ return class DOMWrapper {
+ constructor(document) {
+ if(!document) {
+ throw new Error('Missing document!');
+ }
+ this.document = document;
+ this.wrap = this.wrap.bind(this);
+ this.el = this.el.bind(this);
+ this.txt = this.txt.bind(this);
+ }
+
+ wrap(element) {
+ if(element.element) {
+ return element;
+ } else {
+ return new WrappedElement(element);
}
}
- }
- function make(type, attrs = {}, children = []) {
- const o = document.createElementNS(NS, type);
- setAttributes(o, attrs);
- for(const c of children) {
- o.appendChild(c);
+ el(tag, namespace = null) {
+ let element = null;
+ if(namespace === null) {
+ element = this.document.createElement(tag);
+ } else {
+ element = this.document.createElementNS(namespace, tag);
+ }
+ return new WrappedElement(element);
}
- return o;
- }
- function makeContainer(attrs = {}) {
- return make('svg', Object.assign({
- 'xmlns': NS,
- 'version': '1.1',
- }, attrs));
- }
-
- function empty(node) {
- while(node.childNodes.length > 0) {
- node.removeChild(node.lastChild);
+ txt(content = '') {
+ return this.document.createTextNode(content);
}
- }
-
- return {
- makeText,
- make,
- makeContainer,
- setAttributes,
- empty,
};
});
-define('svg/SVGTextBlock',['./SVGUtilities'], (svg) => {
+define('svg/SVGTextBlock',[],() => {
'use strict';
// Thanks, https://stackoverflow.com/a/9851769/1180785
const firefox = (typeof window.InstallTrigger !== 'undefined');
- function fontDetails(attrs) {
- const size = Number(attrs['font-size']);
- const lineHeight = size * (Number(attrs['line-height']) || 1);
- return {
- size,
- lineHeight,
- };
- }
-
function merge(state, newState) {
for(const k in state) {
if(state.hasOwnProperty(k)) {
@@ -3524,17 +3686,15 @@ define('svg/SVGTextBlock',['./SVGUtilities'], (svg) => {
}
}
- function populateSvgTextLine(node, formattedLine) {
+ function populateSvgTextLine(svg, node, formattedLine) {
if(!Array.isArray(formattedLine)) {
throw new Error('Invalid formatted text line: ' + formattedLine);
}
formattedLine.forEach(({text, attrs}) => {
- const textNode = svg.makeText(text);
if(attrs) {
- const span = svg.make('tspan', attrs, [textNode]);
- node.appendChild(span);
+ node.add(svg.el('tspan').attrs(attrs).add(text));
} else {
- node.appendChild(textNode);
+ node.add(text);
}
});
}
@@ -3542,8 +3702,9 @@ define('svg/SVGTextBlock',['./SVGUtilities'], (svg) => {
const EMPTY = [];
class SVGTextBlock {
- constructor(container, initialState = {}) {
+ constructor(container, svg, initialState = {}) {
this.container = container;
+ this.svg = svg;
this.state = {
attrs: {},
formatted: EMPTY,
@@ -3556,19 +3717,18 @@ define('svg/SVGTextBlock',['./SVGUtilities'], (svg) => {
_rebuildLines(count) {
if(count > this.lines.length) {
- const attrs = Object.assign({
- 'x': this.state.x,
- }, this.state.attrs);
-
while(this.lines.length < count) {
- const node = svg.make('text', attrs);
- this.container.appendChild(node);
- this.lines.push({node, latest: ''});
+ this.lines.push({
+ node: this.svg.el('text')
+ .attr('x', this.state.x)
+ .attrs(this.state.attrs)
+ .attach(this.container),
+ latest: '',
+ });
}
} else {
while(this.lines.length > count) {
- const {node} = this.lines.pop();
- this.container.removeChild(node);
+ this.lines.pop().node.detach();
}
}
}
@@ -3593,8 +3753,8 @@ define('svg/SVGTextBlock',['./SVGUtilities'], (svg) => {
this.lines.forEach((ln, i) => {
const id = JSON.stringify(formatted[i]);
if(id !== ln.latest) {
- svg.empty(ln.node);
- populateSvgTextLine(ln.node, formatted[i]);
+ ln.node.empty();
+ populateSvgTextLine(this.svg, ln.node, formatted[i]);
ln.latest = id;
}
});
@@ -3602,22 +3762,18 @@ define('svg/SVGTextBlock',['./SVGUtilities'], (svg) => {
_updateX() {
this.lines.forEach(({node}) => {
- node.setAttribute('x', this.state.x);
+ node.attr('x', this.state.x);
});
}
_updateY() {
- const {size, lineHeight} = fontDetails(this.state.attrs);
- this.lines.forEach(({node}, i) => {
- node.setAttribute('y', this.state.y + i * lineHeight + size);
- });
- }
-
- firstLine() {
- if(this.lines.length > 0) {
- return this.lines[0].node;
- } else {
- return null;
+ const sizer = this.svg.textSizer;
+ let y = this.state.y;
+ for(let i = 0; i < this.lines.length; ++ i) {
+ const line = [this.state.formatted[i]];
+ const baseline = sizer.baseline(this.state.attrs, line);
+ this.lines[i].node.attr('y', y + baseline);
+ y += sizer.measureHeight(this.state.attrs, line);
}
}
@@ -3646,160 +3802,47 @@ define('svg/SVGTextBlock',['./SVGUtilities'], (svg) => {
}
}
- class SizeTester {
- constructor(container) {
- this.testers = svg.make('g', {
+ SVGTextBlock.TextSizer = class TextSizer {
+ constructor(svg) {
+ this.svg = svg;
+ this.testers = this.svg.el('g').attrs({
// Firefox fails to measure non-displayed text
'display': firefox ? 'block' : 'none',
'visibility': 'hidden',
});
- this.container = container;
- this.cache = new Map();
- this.nodes = null;
+ this.container = svg.body;
}
- _expectMeasure({attrs, formatted}) {
- if(!formatted.length) {
- return;
- }
-
- const attrKey = JSON.stringify(attrs);
- let attrCache = this.cache.get(attrKey);
- if(!attrCache) {
- attrCache = {
- attrs,
- lines: new Map(),
- };
- this.cache.set(attrKey, attrCache);
- }
-
- formatted.forEach((line) => {
- if(!line.length) {
- return;
- }
-
- const labelKey = JSON.stringify(line);
- if(!attrCache.lines.has(labelKey)) {
- attrCache.lines.set(labelKey, {
- formatted: line,
- width: null,
- });
- }
- });
-
- return attrCache;
+ baseline({attrs}) {
+ return Number(attrs['font-size']);
}
- _measureHeight({attrs, formatted}) {
- return formatted.length * fontDetails(attrs).lineHeight;
+ measureHeight({attrs, formatted}) {
+ const size = this.baseline({attrs, formatted});
+ const lineHeight = size * (Number(attrs['line-height']) || 1);
+ return formatted.length * lineHeight;
}
- _measureLine(attrCache, line) {
- if(!line.length) {
- return 0;
- }
-
- const labelKey = JSON.stringify(line);
- const cache = attrCache.lines.get(labelKey);
- if(cache.width === null) {
- window.console.warn('Performing unexpected measurement', line);
- this.performMeasurements();
- }
- return cache.width;
+ prepMeasurement(attrs, formatted) {
+ const node = this.svg.el('text')
+ .attrs(attrs)
+ .attach(this.testers);
+ populateSvgTextLine(this.svg, node, formatted);
+ return node;
}
- _measureWidth(opts) {
- if(!opts.formatted.length) {
- return 0;
- }
-
- const attrCache = this._expectMeasure(opts);
-
- return (opts.formatted
- .map((line) => this._measureLine(attrCache, line))
- .reduce((a, b) => Math.max(a, b), 0)
- );
+ prepComplete() {
+ this.container.add(this.testers);
}
- _getMeasurementOpts(attrs, formatted) {
- if(!formatted) {
- if(typeof attrs === 'object' && attrs.state) {
- formatted = attrs.state.formatted || [];
- attrs = attrs.state.attrs;
- } else {
- formatted = [];
- }
- } else if(!Array.isArray(formatted)) {
- throw new Error('Invalid formatted text: ' + formatted);
- }
- return {attrs, formatted};
+ performMeasurement(node) {
+ return node.element.getComputedTextLength();
}
- expectMeasure(attrs, formatted) {
- const opts = this._getMeasurementOpts(attrs, formatted);
- this._expectMeasure(opts);
+ teardown() {
+ this.container.del(this.testers.empty());
}
-
- performMeasurementsPre() {
- this.nodes = [];
- this.cache.forEach(({attrs, lines}) => {
- lines.forEach((cacheLine) => {
- if(cacheLine.width === null) {
- const node = svg.make('text', attrs);
- populateSvgTextLine(node, cacheLine.formatted);
- this.testers.appendChild(node);
- this.nodes.push({node, cacheLine});
- }
- });
- });
-
- if(this.nodes.length) {
- this.container.appendChild(this.testers);
- }
- }
-
- performMeasurementsAct() {
- this.nodes.forEach(({node, cacheLine}) => {
- cacheLine.width = node.getComputedTextLength();
- });
- }
-
- performMeasurementsPost() {
- if(this.nodes.length) {
- this.container.removeChild(this.testers);
- svg.empty(this.testers);
- }
- this.nodes = null;
- }
-
- performMeasurements() {
- // getComputedTextLength forces a reflow, so we try to batch as
- // many measurements as possible into a single DOM change
-
- this.performMeasurementsPre();
- this.performMeasurementsAct();
- this.performMeasurementsPost();
- }
-
- measure(attrs, formatted) {
- const opts = this._getMeasurementOpts(attrs, formatted);
- return {
- width: this._measureWidth(opts),
- height: this._measureHeight(opts),
- };
- }
-
- measureHeight(attrs, formatted) {
- const opts = this._getMeasurementOpts(attrs, formatted);
- return this._measureHeight(opts);
- }
-
- resetCache() {
- this.cache.clear();
- }
- }
-
- SVGTextBlock.SizeTester = SizeTester;
+ };
return SVGTextBlock;
});
@@ -3919,51 +3962,16 @@ define('svg/PatternedLine',[],() => {
};
});
-define('svg/SVGShapes',[
- './SVGUtilities',
+define('svg/SVG',[
'./SVGTextBlock',
'./PatternedLine',
], (
- svg,
SVGTextBlock,
PatternedLine
) => {
'use strict';
- function renderBox(attrs, position) {
- return svg.make('rect', Object.assign({}, position, attrs));
- }
-
- function renderLine(attrs, position) {
- return svg.make('line', Object.assign({}, position, attrs));
- }
-
- function renderNote(attrs, flickAttrs, position) {
- const x0 = position.x;
- const x1 = position.x + position.width;
- const y0 = position.y;
- const y1 = position.y + position.height;
- const flick = 7;
-
- return svg.make('g', {}, [
- svg.make('polygon', Object.assign({
- 'points': (
- x0 + ' ' + y0 + ' ' +
- (x1 - flick) + ' ' + y0 + ' ' +
- x1 + ' ' + (y0 + flick) + ' ' +
- x1 + ' ' + y1 + ' ' +
- x0 + ' ' + y1
- ),
- }, attrs)),
- svg.make('polyline', Object.assign({
- 'points': (
- (x1 - flick) + ' ' + y0 + ' ' +
- (x1 - flick) + ' ' + (y0 + flick) + ' ' +
- x1 + ' ' + (y0 + flick)
- ),
- }, flickAttrs)),
- ]);
- }
+ const NS = 'http://www.w3.org/2000/svg';
function calculateAnchor(x, attrs, padding) {
let shift = 0;
@@ -3985,68 +3993,333 @@ define('svg/SVGShapes',[
return {shift, anchorX};
}
- function renderBoxedText(formatted, {
- x,
- y,
- padding,
- boxAttrs,
- labelAttrs,
- boxLayer,
- labelLayer,
- boxRenderer = null,
- SVGTextBlockClass = SVGTextBlock,
- textSizer,
- }) {
- if(!formatted || !formatted.length) {
- return {width: 0, height: 0, label: null, box: null};
+ const defaultTextSizerFactory = (svg) => new SVGTextBlock.TextSizer(svg);
+
+ class TextSizerWrapper {
+ constructor(sizer) {
+ this.sizer = sizer;
+ this.cache = new Map();
+ this.active = null;
}
- const {shift, anchorX} = calculateAnchor(x, labelAttrs, padding);
+ _expectMeasure({attrs, formatted}) {
+ if(!formatted.length) {
+ return;
+ }
- const label = new SVGTextBlockClass(labelLayer, {
- attrs: labelAttrs,
- formatted,
- x: anchorX,
- y: y + padding.top,
- });
+ const attrKey = JSON.stringify(attrs);
+ let attrCache = this.cache.get(attrKey);
+ if(!attrCache) {
+ attrCache = {
+ attrs,
+ lines: new Map(),
+ };
+ this.cache.set(attrKey, attrCache);
+ }
- const size = textSizer.measure(label);
- const width = (size.width + padding.left + padding.right);
- const height = (size.height + padding.top + padding.bottom);
+ formatted.forEach((line) => {
+ if(!line.length) {
+ return;
+ }
- let box = null;
- if(boxRenderer) {
- box = boxRenderer({
- 'x': anchorX - size.width * shift - padding.left,
- 'y': y,
- 'width': width,
- 'height': height,
+ const labelKey = JSON.stringify(line);
+ if(!attrCache.lines.has(labelKey)) {
+ attrCache.lines.set(labelKey, {
+ formatted: line,
+ width: null,
+ });
+ }
});
- } else {
- box = renderBox(boxAttrs, {
- 'x': anchorX - size.width * shift - padding.left,
- 'y': y,
- 'width': width,
- 'height': height,
+
+ return attrCache;
+ }
+
+ _measureLine(attrCache, line) {
+ if(!line.length) {
+ return 0;
+ }
+
+ const labelKey = JSON.stringify(line);
+ const cache = attrCache.lines.get(labelKey);
+ if(cache.width === null) {
+ window.console.warn('Performing unexpected measurement', line);
+ this.performMeasurements();
+ }
+ return cache.width;
+ }
+
+ _measureWidth(opts) {
+ if(!opts.formatted.length) {
+ return 0;
+ }
+
+ const attrCache = this._expectMeasure(opts);
+
+ return (opts.formatted
+ .map((line) => this._measureLine(attrCache, line))
+ .reduce((a, b) => Math.max(a, b), 0)
+ );
+ }
+
+ _getMeasurementOpts(attrs, formatted) {
+ if(!formatted) {
+ formatted = [];
+ if(attrs.textBlock) {
+ attrs = attrs.textBlock;
+ }
+ if(attrs.state) {
+ formatted = attrs.state.formatted || [];
+ attrs = attrs.state.attrs;
+ }
+ }
+ if(!Array.isArray(formatted)) {
+ throw new Error('Invalid formatted text: ' + formatted);
+ }
+ return {attrs, formatted};
+ }
+
+ expectMeasure(attrs, formatted) {
+ const opts = this._getMeasurementOpts(attrs, formatted);
+ this._expectMeasure(opts);
+ }
+
+ performMeasurementsPre() {
+ this.active = [];
+ this.cache.forEach(({attrs, lines}) => {
+ lines.forEach((cacheLine) => {
+ if(cacheLine.width === null) {
+ this.active.push({
+ data: this.sizer.prepMeasurement(
+ attrs,
+ cacheLine.formatted
+ ),
+ cacheLine,
+ });
+ }
+ });
+ });
+
+ if(this.active.length) {
+ this.sizer.prepComplete();
+ }
+ }
+
+ performMeasurementsAct() {
+ this.active.forEach(({data, cacheLine}) => {
+ cacheLine.width = this.sizer.performMeasurement(data);
});
}
- if(boxLayer === labelLayer) {
- boxLayer.insertBefore(box, label.firstLine());
- } else {
- boxLayer.appendChild(box);
+ performMeasurementsPost() {
+ if(this.active.length) {
+ this.sizer.teardown();
+ }
+ this.active = null;
}
- return {width, height, label, box};
+ performMeasurements() {
+ // getComputedTextLength forces a reflow, so we try to batch as
+ // many measurements as possible into a single DOM change
+
+ try {
+ this.performMeasurementsPre();
+ this.performMeasurementsAct();
+ } finally {
+ this.performMeasurementsPost();
+ }
+ }
+
+ measure(attrs, formatted) {
+ const opts = this._getMeasurementOpts(attrs, formatted);
+ return {
+ width: this._measureWidth(opts),
+ height: this.sizer.measureHeight(opts),
+ };
+ }
+
+ baseline(attrs, formatted) {
+ const opts = this._getMeasurementOpts(attrs, formatted);
+ return this.sizer.baseline(opts);
+ }
+
+ measureHeight(attrs, formatted) {
+ const opts = this._getMeasurementOpts(attrs, formatted);
+ return this.sizer.measureHeight(opts);
+ }
+
+ resetCache() {
+ this.cache.clear();
+ }
}
- return {
- renderBox,
- renderLine,
- renderNote,
- renderBoxedText,
- TextBlock: SVGTextBlock,
- PatternedLine,
+ return class SVG {
+ constructor(domWrapper, textSizerFactory = null) {
+ this.dom = domWrapper;
+ this.body = this.el('svg').attrs({'xmlns': NS, 'version': '1.1'});
+ const fn = (textSizerFactory || defaultTextSizerFactory);
+ this.textSizer = new TextSizerWrapper(fn(this));
+
+ this.txt = this.txt.bind(this);
+ this.el = this.el.bind(this);
+ }
+
+ linearGradient(attrs, stops) {
+ return this.el('linearGradient')
+ .attrs(attrs)
+ .add(stops.map((stop) => this.el('stop').attrs(stop)));
+ }
+
+ patternedLine(pattern = null, phase = 0) {
+ return new PatternedLine(pattern, phase);
+ }
+
+ txt(content) {
+ return this.dom.txt(content);
+ }
+
+ el(tag, namespace = NS) {
+ return this.dom.el(tag, namespace);
+ }
+
+ box(attrs, position) {
+ return this.el('rect').attrs(attrs).attrs(position);
+ }
+
+ boxFactory(attrs) {
+ return this.box.bind(this, attrs);
+ }
+
+ line(attrs, position) {
+ return this.el('line').attrs(attrs).attrs(position);
+ }
+
+ lineFactory(attrs) {
+ return this.line.bind(this, attrs);
+ }
+
+ circle(attrs, {x, y, radius}) {
+ return this.el('circle')
+ .attrs({
+ 'cx': x,
+ 'cy': y,
+ 'r': radius,
+ })
+ .attrs(attrs);
+ }
+
+ circleFactory(attrs) {
+ return this.circle.bind(this, attrs);
+ }
+
+ cross(attrs, {x, y, radius}) {
+ return this.el('path')
+ .attr('d', (
+ 'M' + (x - radius) + ' ' + (y - radius) +
+ 'l' + (radius * 2) + ' ' + (radius * 2) +
+ 'm0 ' + (-radius * 2) +
+ 'l' + (-radius * 2) + ' ' + (radius * 2)
+ ))
+ .attrs(attrs);
+ }
+
+ crossFactory(attrs) {
+ return this.cross.bind(this, attrs);
+ }
+
+ note(attrs, flickAttrs, position) {
+ const x0 = position.x;
+ const x1 = position.x + position.width;
+ const y0 = position.y;
+ const y1 = position.y + position.height;
+ const flick = 7;
+
+ return this.el('g').add(
+ this.el('polygon')
+ .attr('points', (
+ x0 + ' ' + y0 + ' ' +
+ (x1 - flick) + ' ' + y0 + ' ' +
+ x1 + ' ' + (y0 + flick) + ' ' +
+ x1 + ' ' + y1 + ' ' +
+ x0 + ' ' + y1
+ ))
+ .attrs(attrs),
+ this.el('polyline')
+ .attr('points', (
+ (x1 - flick) + ' ' + y0 + ' ' +
+ (x1 - flick) + ' ' + (y0 + flick) + ' ' +
+ x1 + ' ' + (y0 + flick)
+ ))
+ .attrs(flickAttrs)
+ );
+ }
+
+ noteFactory(attrs, flickAttrs) {
+ return this.note.bind(this, attrs, flickAttrs);
+ }
+
+ formattedText(attrs = {}, formatted = [], position = {}) {
+ const container = this.el('g');
+ const txt = new SVGTextBlock(container, this, {
+ attrs,
+ formatted,
+ x: position.x,
+ y: position.y,
+ });
+ return Object.assign(container, {
+ set: (state) => txt.set(state),
+ textBlock: txt,
+ });
+ }
+
+ formattedTextFactory(attrs) {
+ return this.formattedText.bind(this, attrs);
+ }
+
+ boxedText({
+ padding,
+ labelAttrs,
+ boxAttrs = {},
+ boxRenderer = null,
+ }, formatted, {x, y}) {
+ if(!formatted || !formatted.length) {
+ return Object.assign(this.el('g'), {
+ width: 0,
+ height: 0,
+ box: null,
+ label: null,
+ });
+ }
+
+ const {shift, anchorX} = calculateAnchor(x, labelAttrs, padding);
+
+ const label = this.formattedText(labelAttrs, formatted, {
+ x: anchorX,
+ y: y + padding.top,
+ });
+
+ const size = this.textSizer.measure(label);
+ const width = (size.width + padding.left + padding.right);
+ const height = (size.height + padding.top + padding.bottom);
+
+ const boxFn = boxRenderer || this.boxFactory(boxAttrs);
+ const box = boxFn({
+ 'x': anchorX - size.width * shift - padding.left,
+ 'y': y,
+ 'width': width,
+ 'height': height,
+ });
+
+ return Object.assign(this.el('g').add(box, label), {
+ width,
+ height,
+ box,
+ label,
+ });
+ }
+
+ boxedTextFactory(options) {
+ return this.boxedText.bind(this, options);
+ }
};
});
@@ -4118,6 +4391,7 @@ define('sequence/components/BaseComponent',[],() => {
blockLayer,
theme,
agentInfos,
+ svg,
textSizer,
SVGTextBlockClass,
addDef,
@@ -4178,16 +4452,17 @@ define('sequence/components/BaseComponent',[],() => {
define('sequence/components/Block',[
'./BaseComponent',
'core/ArrayUtilities',
- 'svg/SVGUtilities',
- 'svg/SVGShapes',
], (
BaseComponent,
- array,
- svg,
- SVGShapes
+ array
) => {
'use strict';
+ const OUTLINE_ATTRS = {
+ 'fill': 'transparent',
+ 'class': 'outline',
+ };
+
class BlockSplit extends BaseComponent {
prepareMeasurements({left, tag, label}, env) {
const blockInfo = env.state.blocks.get(left);
@@ -4230,58 +4505,46 @@ define('sequence/components/Block',[
const clickable = env.makeRegion();
- const tagRender = SVGShapes.renderBoxedText(tag, {
- x: agentInfoL.x,
- y,
+ const tagRender = env.svg.boxedText({
padding: config.section.tag.padding,
boxAttrs: config.section.tag.boxAttrs,
boxRenderer: config.section.tag.boxRenderer,
labelAttrs: config.section.tag.labelAttrs,
- boxLayer: blockInfo.hold,
- labelLayer: clickable,
- SVGTextBlockClass: env.SVGTextBlockClass,
- textSizer: env.textSizer,
- });
+ }, tag, {x: agentInfoL.x, y});
- const labelRender = SVGShapes.renderBoxedText(label, {
- x: agentInfoL.x + tagRender.width,
- y,
+ const labelRender = env.svg.boxedText({
padding: config.section.label.padding,
boxAttrs: {'fill': '#000000'},
labelAttrs: config.section.label.labelAttrs,
- boxLayer: env.lineMaskLayer,
- labelLayer: clickable,
- SVGTextBlockClass: env.SVGTextBlockClass,
- textSizer: env.textSizer,
- });
+ }, label, {x: agentInfoL.x + tagRender.width, y});
const labelHeight = Math.max(
Math.max(tagRender.height, labelRender.height),
config.section.label.minHeight
);
- clickable.insertBefore(svg.make('rect', {
- 'x': agentInfoL.x,
- 'y': y,
- 'width': agentInfoR.x - agentInfoL.x,
- 'height': labelHeight,
- 'fill': 'transparent',
- 'class': 'outline',
- }), clickable.firstChild);
+ blockInfo.hold.add(tagRender.box);
+ env.lineMaskLayer.add(labelRender.box);
+ clickable.add(
+ env.svg.box(OUTLINE_ATTRS, {
+ 'x': agentInfoL.x,
+ 'y': y,
+ 'width': agentInfoR.x - agentInfoL.x,
+ 'height': labelHeight,
+ }),
+ tagRender.label,
+ labelRender.label
+ );
if(!first) {
- blockInfo.hold.appendChild(config.sepRenderer({
+ blockInfo.hold.add(config.sepRenderer({
'x1': agentInfoL.x,
'y1': y,
'x2': agentInfoR.x,
'y2': y,
}));
} else if(blockInfo.canHide) {
- clickable.setAttribute(
- 'class',
- clickable.getAttribute('class') +
- (blockInfo.hide ? ' collapsed' : ' expanded')
- );
+ clickable.addClass(blockInfo.hide ? 'collapsed' : 'expanded');
}
return y + labelHeight + config.section.padding.top;
@@ -4337,8 +4600,8 @@ define('sequence/components/Block',[
}
render(stage, env) {
- const hold = svg.make('g');
- env.blockLayer.appendChild(hold);
+ const hold = env.svg.el('g');
+ env.blockLayer.add(hold);
const blockInfo = env.state.blocks.get(stage.left);
blockInfo.hold = hold;
@@ -4392,13 +4655,9 @@ define('sequence/components/Block',[
shapes = {shape: shapes};
}
- blockInfo.hold.appendChild(shapes.shape);
- if(shapes.fill) {
- env.fillLayer.appendChild(shapes.fill);
- }
- if(shapes.mask) {
- env.lineMaskLayer.appendChild(shapes.mask);
- }
+ blockInfo.hold.add(shapes.shape);
+ env.fillLayer.add(shapes.fill);
+ env.lineMaskLayer.add(shapes.mask);
return env.primaryY + config.margin.bottom + env.theme.actionMargin;
}
@@ -4569,16 +4828,17 @@ define('sequence/components/Marker',['./BaseComponent'], (BaseComponent) => {
define('sequence/components/AgentCap',[
'./BaseComponent',
'core/ArrayUtilities',
- 'svg/SVGUtilities',
- 'svg/SVGShapes',
], (
BaseComponent,
- array,
- svg,
- SVGShapes
+ array
) => {
'use strict';
+ const OUTLINE_ATTRS = {
+ 'fill': 'transparent',
+ 'class': 'outline',
+ };
+
class CapBox {
getConfig(options, env) {
let config = null;
@@ -4620,27 +4880,18 @@ define('sequence/components/AgentCap',[
render(y, {x, formattedLabel, options}, env) {
const config = this.getConfig(options, env);
- const clickable = env.makeRegion();
- const text = SVGShapes.renderBoxedText(formattedLabel, {
- x,
- y,
- padding: config.padding,
- boxAttrs: config.boxAttrs,
- boxRenderer: config.boxRenderer,
- labelAttrs: config.labelAttrs,
- boxLayer: clickable,
- labelLayer: clickable,
- SVGTextBlockClass: env.SVGTextBlockClass,
- textSizer: env.textSizer,
- });
- clickable.insertBefore(svg.make('rect', {
- 'x': x - text.width / 2,
- 'y': y,
- 'width': text.width,
- 'height': text.height,
- 'fill': 'transparent',
- 'class': 'outline',
- }), text.label.firstLine());
+
+ const text = env.svg.boxedText(config, formattedLabel, {x, y});
+
+ env.makeRegion().add(
+ text,
+ env.svg.box(OUTLINE_ATTRS, {
+ 'x': x - text.width / 2,
+ 'y': y,
+ 'width': text.width,
+ 'height': text.height,
+ })
+ );
return {
lineTop: 0,
@@ -4672,22 +4923,20 @@ define('sequence/components/AgentCap',[
const config = env.theme.agentCap.cross;
const d = config.size / 2;
- const clickable = env.makeRegion();
-
- clickable.appendChild(config.render({
- x,
- y: y + d,
- radius: d,
- options,
- }));
- clickable.appendChild(svg.make('rect', {
- 'x': x - d,
- 'y': y,
- 'width': d * 2,
- 'height': d * 2,
- 'fill': 'transparent',
- 'class': 'outline',
- }));
+ env.makeRegion().add(
+ config.render({
+ x,
+ y: y + d,
+ radius: d,
+ options,
+ }),
+ env.svg.box(OUTLINE_ATTRS, {
+ 'x': x - d,
+ 'y': y,
+ 'width': d * 2,
+ 'height': d * 2,
+ })
+ );
return {
lineTop: d,
@@ -4733,22 +4982,21 @@ define('sequence/components/AgentCap',[
);
const height = barCfg.height;
- const clickable = env.makeRegion();
- clickable.appendChild(barCfg.render({
- x: x - width / 2,
- y,
- width,
- height,
- options,
- }));
- clickable.appendChild(svg.make('rect', {
- 'x': x - width / 2,
- 'y': y,
- 'width': width,
- 'height': height,
- 'fill': 'transparent',
- 'class': 'outline',
- }));
+ env.makeRegion().add(
+ barCfg.render({
+ x: x - width / 2,
+ y,
+ width,
+ height,
+ options,
+ }),
+ env.svg.box(OUTLINE_ATTRS, {
+ 'x': x - width / 2,
+ 'y': y,
+ 'width': width,
+ 'height': height,
+ })
+ );
return {
lineTop: 0,
@@ -4780,38 +5028,37 @@ define('sequence/components/AgentCap',[
const ratio = config.height / (config.height + config.extend);
const gradID = env.addDef(isBegin ? 'FadeIn' : 'FadeOut', () => {
- return svg.make('linearGradient', {
+ return env.svg.linearGradient({
'x1': '0%',
'y1': isBegin ? '100%' : '0%',
'x2': '0%',
'y2': isBegin ? '0%' : '100%',
}, [
- svg.make('stop', {
+ {
'offset': '0%',
'stop-color': '#FFFFFF',
- }),
- svg.make('stop', {
+ },
+ {
'offset': (100 * ratio).toFixed(3) + '%',
'stop-color': '#000000',
- }),
+ },
]);
});
- env.lineMaskLayer.appendChild(svg.make('rect', {
+ env.lineMaskLayer.add(env.svg.box({
+ 'fill': 'url(#' + gradID + ')',
+ }, {
'x': x - config.width / 2,
'y': y - (isBegin ? config.extend : 0),
'width': config.width,
'height': config.height + config.extend,
- 'fill': 'url(#' + gradID + ')',
}));
- env.makeRegion().appendChild(svg.make('rect', {
+ env.makeRegion().add(env.svg.box(OUTLINE_ATTRS, {
'x': x - config.width / 2,
'y': y,
'width': config.width,
'height': config.height,
- 'fill': 'transparent',
- 'class': 'outline',
}));
return {
@@ -4843,13 +5090,11 @@ define('sequence/components/AgentCap',[
const config = env.theme.agentCap.none;
const w = 10;
- env.makeRegion().appendChild(svg.make('rect', {
+ env.makeRegion().add(env.svg.box(OUTLINE_ATTRS, {
'x': x - w / 2,
'y': y,
'width': w,
'height': config.height,
- 'fill': 'transparent',
- 'class': 'outline',
}));
return {
@@ -4927,12 +5172,7 @@ define('sequence/components/AgentCap',[
const cap = AGENT_CAPS[mode];
const topShift = cap.topShift(agentInfo, env, this.begin);
const y0 = env.primaryY - topShift;
- const shifts = cap.render(
- y0,
- agentInfo,
- env,
- this.begin
- );
+ const shifts = cap.render(y0, agentInfo, env, this.begin);
maxEnd = Math.max(maxEnd, y0 + shifts.height);
if(this.begin) {
env.drawAgentLine(id, y0 + shifts.lineBottom);
@@ -5003,16 +5243,17 @@ define('sequence/components/AgentHighlight',['./BaseComponent'], (BaseComponent)
define('sequence/components/Connect',[
'core/ArrayUtilities',
'./BaseComponent',
- 'svg/SVGUtilities',
- 'svg/SVGShapes',
], (
array,
- BaseComponent,
- svg,
- SVGShapes
+ BaseComponent
) => {
'use strict';
+ const OUTLINE_ATTRS = {
+ 'fill': 'transparent',
+ 'class': 'outline',
+ };
+
class Arrowhead {
constructor(propName) {
this.propName = propName;
@@ -5040,7 +5281,7 @@ define('sequence/components/Connect',[
render(layer, theme, pt, dir) {
const config = this.getConfig(theme);
const short = this.short(theme);
- layer.appendChild(config.render(config.attrs, {
+ layer.add(config.render(config.attrs, {
x: pt.x + short * dir.dx,
y: pt.y + short * dir.dy,
width: config.width,
@@ -5078,7 +5319,7 @@ define('sequence/components/Connect',[
render(layer, theme, pt, dir) {
const config = this.getConfig(theme);
- layer.appendChild(config.render({
+ layer.add(config.render({
x: pt.x + config.short * dir.dx,
y: pt.y + config.short * dir.dy,
radius: config.radius,
@@ -5196,7 +5437,7 @@ define('sequence/components/Connect',[
xR,
rad: config.loopbackRadius,
});
- clickable.appendChild(rendered.shape);
+ clickable.add(rendered.shape);
lArrow.render(clickable, env.theme, {
x: rendered.p1.x - dx1,
@@ -5229,19 +5470,15 @@ define('sequence/components/Connect',[
(label ? config.label.padding : 0)
);
- const clickable = env.makeRegion();
-
- const renderedText = SVGShapes.renderBoxedText(label, {
- x: xL - config.mask.padding.left,
- y: yBegin - height + config.label.margin.top,
+ const renderedText = env.svg.boxedText({
padding: config.mask.padding,
boxAttrs: {'fill': '#000000'},
labelAttrs: config.label.loopbackAttrs,
- boxLayer: env.lineMaskLayer,
- labelLayer: clickable,
- SVGTextBlockClass: env.SVGTextBlockClass,
- textSizer: env.textSizer,
+ }, label, {
+ x: xL - config.mask.padding.left,
+ y: yBegin - height + config.label.margin.top,
});
+
const labelW = (label ? (
renderedText.width +
config.label.padding -
@@ -5254,6 +5491,20 @@ define('sequence/components/Connect',[
xL + labelW
);
+ const raise = Math.max(height, lArrow.height(env.theme) / 2);
+ const arrowDip = rArrow.height(env.theme) / 2;
+
+ env.lineMaskLayer.add(renderedText.box);
+ const clickable = env.makeRegion().add(
+ env.svg.box(OUTLINE_ATTRS, {
+ 'x': from.x,
+ 'y': yBegin - raise,
+ 'width': xR + config.loopbackRadius - from.x,
+ 'height': raise + env.primaryY - yBegin + arrowDip,
+ }),
+ renderedText.label
+ );
+
this.renderRevArrowLine({
x1: from.x + from.currentMaxRad,
y1: yBegin,
@@ -5262,18 +5513,6 @@ define('sequence/components/Connect',[
xR,
}, options, env, clickable);
- const raise = Math.max(height, lArrow.height(env.theme) / 2);
- const arrowDip = rArrow.height(env.theme) / 2;
-
- clickable.insertBefore(svg.make('rect', {
- 'x': from.x,
- 'y': yBegin - raise,
- 'width': xR + config.loopbackRadius - from.x,
- 'height': raise + env.primaryY - yBegin + arrowDip,
- 'fill': 'transparent',
- 'class': 'outline',
- }), clickable.firstChild);
-
return (
env.primaryY +
Math.max(arrowDip, 0) +
@@ -5302,7 +5541,7 @@ define('sequence/components/Connect',[
x2: x2 - d2 * dx,
y2: y2 - d2 * dy,
});
- clickable.appendChild(rendered.shape);
+ clickable.add(rendered.shape);
const p1 = {x: rendered.p1.x - d1 * dx, y: rendered.p1.y - d1 * dy};
const p2 = {x: rendered.p2.x + d2 * dx, y: rendered.p2.y + d2 * dy};
@@ -5322,14 +5561,14 @@ define('sequence/components/Connect',[
const config = env.theme.connect.source;
if(from.isVirtualSource) {
- clickable.appendChild(config.render({
+ clickable.add(config.render({
x: rendered.p1.x - config.radius,
y: rendered.p1.y,
radius: config.radius,
}));
}
if(to.isVirtualSource) {
- clickable.appendChild(config.render({
+ clickable.add(config.render({
x: rendered.p2.x + config.radius,
y: rendered.p2.y,
radius: config.radius,
@@ -5354,21 +5593,20 @@ define('sequence/components/Connect',[
')'
);
boxAttrs.transform = transform;
- labelLayer = svg.make('g', {'transform': transform});
- layer.appendChild(labelLayer);
+ labelLayer = env.svg.el('g').attrs({'transform': transform});
+ layer.add(labelLayer);
}
- SVGShapes.renderBoxedText(label, {
- x: midX,
- y: midY + config.label.margin.top - height,
+ const text = env.svg.boxedText({
padding: config.mask.padding,
boxAttrs,
labelAttrs: config.label.attrs,
- boxLayer: env.lineMaskLayer,
- labelLayer,
- SVGTextBlockClass: env.SVGTextBlockClass,
- textSizer: env.textSizer,
+ }, label, {
+ x: midX,
+ y: midY + config.label.margin.top - height,
});
+ env.lineMaskLayer.add(text.box);
+ labelLayer.add(text.label);
}
renderSimpleConnect({label, agentIDs, options}, env, from, yBegin) {
@@ -5404,17 +5642,16 @@ define('sequence/components/Connect',[
this.renderVirtualSources({from, to, rendered}, env, clickable);
- clickable.appendChild(svg.make('path', {
- 'd': (
+ clickable.add(env.svg.el('path')
+ .attrs(OUTLINE_ATTRS)
+ .attr('d', (
'M' + x1 + ',' + (yBegin - lift) +
'L' + x2 + ',' + (env.primaryY - lift) +
'L' + x2 + ',' + (env.primaryY + arrowSpread) +
'L' + x1 + ',' + (yBegin + arrowSpread) +
'Z'
- ),
- 'fill': 'transparent',
- 'class': 'outline',
- }));
+ ))
+ );
this.renderSimpleLabel(label, {
layer: clickable,
@@ -5545,9 +5782,14 @@ define('sequence/components/Connect',[
};
});
-define('sequence/components/Note',['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
+define('sequence/components/Note',['./BaseComponent'], (BaseComponent) => {
'use strict';
+ const OUTLINE_ATTRS = {
+ 'fill': 'transparent',
+ 'class': 'outline',
+ };
+
function findExtremes(agentInfos, agentIDs) {
let min = null;
let max = null;
@@ -5586,14 +5828,8 @@ define('sequence/components/Note',['./BaseComponent', 'svg/SVGUtilities'], (Base
}, env) {
const config = env.theme.getNote(mode);
- const clickable = env.makeRegion();
-
const y = env.topY + config.margin.top + config.padding.top;
- const labelNode = new env.SVGTextBlockClass(clickable, {
- attrs: config.labelAttrs,
- formatted: label,
- y,
- });
+ const labelNode = env.svg.formattedText(config.labelAttrs, label);
const size = env.textSizer.measure(labelNode);
const fullW = (
@@ -5632,21 +5868,21 @@ define('sequence/components/Note',['./BaseComponent', 'svg/SVGUtilities'], (Base
break;
}
- clickable.insertBefore(svg.make('rect', {
- 'x': x0,
- 'y': env.topY + config.margin.top,
- 'width': x1 - x0,
- 'height': fullH,
- 'fill': 'transparent',
- 'class': 'outline',
- }), clickable.firstChild);
-
- clickable.insertBefore(config.boxRenderer({
- x: x0,
- y: env.topY + config.margin.top,
- width: x1 - x0,
- height: fullH,
- }), clickable.firstChild);
+ env.makeRegion().add(
+ config.boxRenderer({
+ x: x0,
+ y: env.topY + config.margin.top,
+ width: x1 - x0,
+ height: fullH,
+ }),
+ env.svg.box(OUTLINE_ATTRS, {
+ 'x': x0,
+ 'y': env.topY + config.margin.top,
+ 'width': x1 - x0,
+ 'height': fullH,
+ }),
+ labelNode
+ );
return (
env.topY +
@@ -5822,15 +6058,16 @@ define('sequence/components/Note',['./BaseComponent', 'svg/SVGUtilities'], (Base
define('sequence/components/Divider',[
'./BaseComponent',
- 'svg/SVGUtilities',
- 'svg/SVGShapes',
], (
- BaseComponent,
- svg,
- SVGShapes
+ BaseComponent
) => {
'use strict';
+ const OUTLINE_ATTRS = {
+ 'fill': 'transparent',
+ 'class': 'outline',
+ };
+
class Divider extends BaseComponent {
prepareMeasurements({mode, formattedLabel}, env) {
const config = env.theme.getDivider(mode);
@@ -5863,8 +6100,6 @@ define('sequence/components/Divider',[
const left = env.agentInfos.get('[');
const right = env.agentInfos.get(']');
- const clickable = env.makeRegion({unmasked: true});
-
let labelWidth = 0;
let labelHeight = 0;
if(formattedLabel) {
@@ -5876,22 +6111,22 @@ define('sequence/components/Divider',[
const fullHeight = Math.max(height, labelHeight) + config.margin;
+ let labelText = null;
if(formattedLabel) {
- const boxed = SVGShapes.renderBoxedText(formattedLabel, {
+ const boxed = env.svg.boxedText({
+ padding: config.padding,
+ boxAttrs: {'fill': '#000000'},
+ labelAttrs: config.labelAttrs,
+ }, formattedLabel, {
x: (left.x + right.x) / 2,
y: (
env.primaryY +
(fullHeight - labelHeight) / 2 -
config.padding.top
),
- padding: config.padding,
- boxAttrs: {'fill': '#000000'},
- labelAttrs: config.labelAttrs,
- boxLayer: env.fullMaskLayer,
- labelLayer: clickable,
- SVGTextBlockClass: env.SVGTextBlockClass,
- textSizer: env.textSizer,
});
+ env.fullMaskLayer.add(boxed.box);
+ labelText = boxed.label;
labelWidth = boxed.width;
}
@@ -5904,20 +6139,18 @@ define('sequence/components/Divider',[
height,
env,
});
- if(shape) {
- clickable.insertBefore(shape, clickable.firstChild);
- }
- if(mask) {
- env.fullMaskLayer.appendChild(mask);
- }
- clickable.insertBefore(svg.make('rect', {
- 'x': left.x - config.extend,
- 'y': env.primaryY,
- 'width': right.x - left.x + config.extend * 2,
- 'height': fullHeight,
- 'fill': 'transparent',
- 'class': 'outline',
- }), clickable.firstChild);
+ env.fullMaskLayer.add(mask);
+
+ env.makeRegion({unmasked: true}).add(
+ env.svg.box(OUTLINE_ATTRS, {
+ 'x': left.x - config.extend,
+ 'y': env.primaryY,
+ 'width': right.x - left.x + config.extend * 2,
+ 'height': fullHeight,
+ }),
+ shape,
+ labelText
+ );
return env.primaryY + fullHeight + env.theme.actionMargin;
}
@@ -5932,8 +6165,8 @@ define('sequence/components/Divider',[
define('sequence/Renderer',[
'core/ArrayUtilities',
'core/EventObject',
- 'svg/SVGUtilities',
- 'svg/SVGShapes',
+ 'core/DOMWrapper',
+ 'svg/SVG',
'./components/BaseComponent',
'./components/Block',
'./components/Parallel',
@@ -5946,8 +6179,8 @@ define('sequence/Renderer',[
], (
array,
EventObject,
- svg,
- SVGShapes,
+ DOMWrapper,
+ SVG,
BaseComponent
) => {
/* jshint +W072 */
@@ -5998,7 +6231,8 @@ define('sequence/Renderer',[
themes = [],
namespace = null,
components = null,
- SVGTextBlockClass = SVGShapes.TextBlock,
+ document,
+ textSizerFactory = null,
} = {}) {
super();
@@ -6012,10 +6246,11 @@ define('sequence/Renderer',[
this.width = 0;
this.height = 0;
this.themes = makeThemes(themes);
+ this.themeBuilder = null;
this.theme = null;
this.namespace = parseNamespace(namespace);
this.components = components;
- this.SVGTextBlockClass = SVGTextBlockClass;
+ this.svg = new SVG(new DOMWrapper(document), textSizerFactory);
this.knownThemeDefs = new Set();
this.knownDefs = new Set();
this.highlights = new Map();
@@ -6040,61 +6275,54 @@ define('sequence/Renderer',[
this.themes.set(theme.name, theme);
}
- buildMetadata() {
- this.metaCode = svg.makeText();
- return svg.make('metadata', {}, [this.metaCode]);
- }
-
buildStaticElements() {
- this.base = svg.makeContainer();
+ const el = this.svg.el;
- this.themeDefs = svg.make('defs');
- this.defs = svg.make('defs');
- this.fullMask = svg.make('mask', {
+ this.metaCode = this.svg.txt();
+ this.themeDefs = el('defs');
+ this.defs = el('defs');
+ this.fullMask = el('mask').attrs({
'id': this.namespace + 'FullMask',
'maskUnits': 'userSpaceOnUse',
});
- this.lineMask = svg.make('mask', {
+ this.lineMask = el('mask').attrs({
'id': this.namespace + 'LineMask',
'maskUnits': 'userSpaceOnUse',
});
- this.fullMaskReveal = svg.make('rect', {'fill': '#FFFFFF'});
- this.lineMaskReveal = svg.make('rect', {'fill': '#FFFFFF'});
- this.backgroundFills = svg.make('g');
- this.agentLines = svg.make('g', {
- 'mask': 'url(#' + this.namespace + 'LineMask)',
- });
- this.blocks = svg.make('g');
- this.shapes = svg.make('g');
- this.unmaskedShapes = svg.make('g');
- this.base.appendChild(this.buildMetadata());
- this.base.appendChild(this.themeDefs);
- this.base.appendChild(this.defs);
- this.base.appendChild(this.backgroundFills);
- this.base.appendChild(
- svg.make('g', {
- 'mask': 'url(#' + this.namespace + 'FullMask)',
- }, [
- this.agentLines,
- this.blocks,
- this.shapes,
- ])
- );
- this.base.appendChild(this.unmaskedShapes);
- this.title = new this.SVGTextBlockClass(this.base);
+ this.fullMaskReveal = el('rect').attr('fill', '#FFFFFF');
+ this.lineMaskReveal = el('rect').attr('fill', '#FFFFFF');
+ this.backgroundFills = el('g');
+ this.agentLines = el('g')
+ .attr('mask', 'url(#' + this.namespace + 'LineMask)');
+ this.blocks = el('g');
+ this.shapes = el('g');
+ this.unmaskedShapes = el('g');
+ this.title = this.svg.formattedText();
- this.sizer = new this.SVGTextBlockClass.SizeTester(this.base);
+ this.svg.body.add(
+ this.svg.el('metadata')
+ .add(this.metaCode),
+ this.themeDefs,
+ this.defs,
+ this.backgroundFills,
+ el('g')
+ .attr('mask', 'url(#' + this.namespace + 'FullMask)')
+ .add(
+ this.agentLines,
+ this.blocks,
+ this.shapes
+ ),
+ this.unmaskedShapes,
+ this.title
+ );
}
addThemeDef(name, generator) {
const namespacedName = this.namespace + name;
- if(this.knownThemeDefs.has(name)) {
- return namespacedName;
+ if(!this.knownThemeDefs.has(name)) {
+ this.knownThemeDefs.add(name);
+ this.themeDefs.add(generator().attr('id', namespacedName));
}
- this.knownThemeDefs.add(name);
- const def = generator();
- def.setAttribute('id', namespacedName);
- this.themeDefs.appendChild(def);
return namespacedName;
}
@@ -6106,13 +6334,10 @@ define('sequence/Renderer',[
}
const namespacedName = this.namespace + name;
- if(this.knownDefs.has(name)) {
- return namespacedName;
+ if(!this.knownDefs.has(name)) {
+ this.knownDefs.add(name);
+ this.defs.add(generator().attr('id', namespacedName));
}
- this.knownDefs.add(name);
- const def = generator();
- def.setAttribute('id', namespacedName);
- this.defs.appendChild(def);
return namespacedName;
}
@@ -6133,7 +6358,7 @@ define('sequence/Renderer',[
renderer: this,
theme: this.theme,
agentInfos: this.agentInfos,
- textSizer: this.sizer,
+ textSizer: this.svg.textSizer,
state: this.state,
components: this.components,
};
@@ -6181,7 +6406,7 @@ define('sequence/Renderer',[
agentInfos: this.agentInfos,
visibleAgentIDs: this.visibleAgentIDs,
momentaryAgentIDs: agentIDs,
- textSizer: this.sizer,
+ textSizer: this.svg.textSizer,
addSpacing,
addSeparation,
state: this.state,
@@ -6231,7 +6456,7 @@ define('sequence/Renderer',[
renderer: this,
theme: this.theme,
agentInfos: this.agentInfos,
- textSizer: this.sizer,
+ textSizer: this.svg.textSizer,
state: this.state,
components: this.components,
};
@@ -6276,20 +6501,18 @@ define('sequence/Renderer',[
drawAgentLine(agentInfo, toY) {
if(
- agentInfo.latestYStart === null ||
- toY <= agentInfo.latestYStart
+ agentInfo.latestYStart !== null &&
+ toY > agentInfo.latestYStart
) {
- return;
+ this.agentLines.add(this.theme.renderAgentLine({
+ x: agentInfo.x,
+ y0: agentInfo.latestYStart,
+ y1: toY,
+ width: agentInfo.currentRad * 2,
+ className: 'agent-' + agentInfo.index + '-line',
+ options: agentInfo.options,
+ }));
}
-
- this.agentLines.appendChild(this.theme.renderAgentLine({
- x: agentInfo.x,
- y0: agentInfo.latestYStart,
- y1: toY,
- width: agentInfo.currentRad * 2,
- className: 'agent-' + agentInfo.index + '-line',
- options: agentInfo.options,
- }));
}
addHighlightObject(line, o) {
@@ -6302,7 +6525,7 @@ define('sequence/Renderer',[
}
forwardEvent(source, sourceEvent, forwardEvent, forwardArgs) {
- source.addEventListener(
+ source.on(
sourceEvent,
this.trigger.bind(this, forwardEvent, forwardArgs)
);
@@ -6318,7 +6541,7 @@ define('sequence/Renderer',[
renderer: this,
theme: this.theme,
agentInfos: this.agentInfos,
- textSizer: this.sizer,
+ textSizer: this.svg.textSizer,
state: this.state,
components: this.components,
};
@@ -6333,16 +6556,14 @@ define('sequence/Renderer',[
stageOverride = null,
unmasked = false,
} = {}) => {
- const o = svg.make('g');
+ const o = this.svg.el('g').setClass('region');
const targetStage = (stageOverride || stage);
this.addHighlightObject(targetStage.ln, o);
- o.setAttribute('class', 'region');
this.forwardEvent(o, 'mouseenter', 'mouseover', [targetStage]);
this.forwardEvent(o, 'mouseleave', 'mouseout', [targetStage]);
this.forwardEvent(o, 'click', 'click', [targetStage]);
this.forwardEvent(o, 'dblclick', 'dblclick', [targetStage]);
- (unmasked ? this.unmaskedShapes : this.shapes).appendChild(o);
- return o;
+ return o.attach(unmasked ? this.unmaskedShapes : this.shapes);
};
const env = {
@@ -6355,8 +6576,7 @@ define('sequence/Renderer',[
lineMaskLayer: this.lineMask,
theme: this.theme,
agentInfos: this.agentInfos,
- textSizer: this.sizer,
- SVGTextBlockClass: this.SVGTextBlockClass,
+ textSizer: this.svg.textSizer,
state: this.state,
drawAgentLine: (agentID, toY, andStop = false) => {
const agentInfo = this.agentInfos.get(agentID);
@@ -6366,6 +6586,7 @@ define('sequence/Renderer',[
addDef: this.addDef,
makeRegion,
components: this.components,
+ svg: this.svg,
};
let bottomY = topY;
@@ -6441,7 +6662,7 @@ define('sequence/Renderer',[
updateBounds(stagesHeight) {
const cx = (this.minX + this.maxX) / 2;
- const titleSize = this.sizer.measure(this.title);
+ const titleSize = this.svg.textSizer.measure(this.title);
const titleY = ((titleSize.height > 0) ?
(-this.theme.titleMargin - titleSize.height) : 0
);
@@ -6464,10 +6685,10 @@ define('sequence/Renderer',[
'height': this.height,
};
- svg.setAttributes(this.fullMaskReveal, fullSize);
- svg.setAttributes(this.lineMaskReveal, fullSize);
+ this.fullMaskReveal.attrs(fullSize);
+ this.lineMaskReveal.attrs(fullSize);
- this.base.setAttribute('viewBox', (
+ this.svg.body.attr('viewBox', (
x0 + ' ' + y0 + ' ' +
this.width + ' ' + this.height
));
@@ -6484,23 +6705,23 @@ define('sequence/Renderer',[
_reset(theme) {
if(theme) {
this.knownThemeDefs.clear();
- svg.empty(this.themeDefs);
+ this.themeDefs.empty();
}
this.knownDefs.clear();
this.highlights.clear();
- svg.empty(this.defs);
- svg.empty(this.fullMask);
- svg.empty(this.lineMask);
- svg.empty(this.backgroundFills);
- svg.empty(this.agentLines);
- svg.empty(this.blocks);
- svg.empty(this.shapes);
- svg.empty(this.unmaskedShapes);
- this.fullMask.appendChild(this.fullMaskReveal);
- this.lineMask.appendChild(this.lineMaskReveal);
- this.defs.appendChild(this.fullMask);
- this.defs.appendChild(this.lineMask);
+ this.defs.empty();
+ this.fullMask.empty();
+ this.lineMask.empty();
+ this.backgroundFills.empty();
+ this.agentLines.empty();
+ this.blocks.empty();
+ this.shapes.empty();
+ this.unmaskedShapes.empty();
+ this.defs.add(
+ this.fullMask.add(this.fullMaskReveal),
+ this.lineMask.add(this.lineMaskReveal)
+ );
this._resetState();
}
@@ -6513,12 +6734,12 @@ define('sequence/Renderer',[
}
if(this.highlights.has(this.currentHighlight)) {
this.highlights.get(this.currentHighlight).forEach((o) => {
- o.setAttribute('class', 'region');
+ o.delClass('focus');
});
}
if(this.highlights.has(line)) {
this.highlights.get(line).forEach((o) => {
- o.setAttribute('class', 'region focus');
+ o.addClass('focus');
});
}
this.currentHighlight = line;
@@ -6568,11 +6789,14 @@ define('sequence/Renderer',[
}
_switchTheme(name) {
- const oldTheme = this.theme;
- this.theme = this.getThemeNamed(name);
+ const oldThemeBuilder = this.themeBuilder;
+ this.themeBuilder = this.getThemeNamed(name);
+ if(this.themeBuilder !== oldThemeBuilder) {
+ this.theme = this.themeBuilder.build(this.svg);
+ }
this.theme.reset();
- return (this.theme !== oldTheme);
+ return (this.themeBuilder !== oldThemeBuilder);
}
optimisedRenderPreReflow(sequence) {
@@ -6586,7 +6810,7 @@ define('sequence/Renderer',[
attrs: this.theme.titleAttrs,
formatted: sequence.meta.title,
});
- this.sizer.expectMeasure(this.title);
+ this.svg.textSizer.expectMeasure(this.title);
this.minX = 0;
this.maxX = 0;
@@ -6595,11 +6819,11 @@ define('sequence/Renderer',[
sequence.stages.forEach(this.prepareMeasurementsStage);
this._resetState();
- this.sizer.performMeasurementsPre();
+ this.svg.textSizer.performMeasurementsPre();
}
optimisedRenderReflow() {
- this.sizer.performMeasurementsAct();
+ this.svg.textSizer.performMeasurementsAct();
}
optimisedRenderPostReflow(sequence) {
@@ -6619,8 +6843,8 @@ define('sequence/Renderer',[
this.currentHighlight = -1;
this.setHighlight(prevHighlight);
- this.sizer.performMeasurementsPost();
- this.sizer.resetCache();
+ this.svg.textSizer.performMeasurementsPost();
+ this.svg.textSizer.resetCache();
}
render(sequence) {
@@ -6651,8 +6875,8 @@ define('sequence/Renderer',[
return this.agentInfos.get(id).x;
}
- svg() {
- return this.base;
+ dom() {
+ return this.svg.body.element;
}
};
});
@@ -6675,7 +6899,7 @@ define('sequence/Exporter',[],() => {
}
getSVGContent(renderer) {
- let code = renderer.svg().outerHTML;
+ let code = renderer.dom().outerHTML;
// Firefox fails to render SVGs as
unless they have size
// attributes on the