Support multiline titles
This commit is contained in:
parent
94b41000bb
commit
d76c42bf5e
|
@ -1,4 +1,12 @@
|
||||||
define(['./ArrayUtilities', './SVGUtilities'], (array, svg) => {
|
define([
|
||||||
|
'./ArrayUtilities',
|
||||||
|
'./SVGUtilities',
|
||||||
|
'./SVGTextBlock',
|
||||||
|
], (
|
||||||
|
array,
|
||||||
|
svg,
|
||||||
|
SVGTextBlock
|
||||||
|
) => {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function boxRenderer(attrs, position) {
|
function boxRenderer(attrs, position) {
|
||||||
|
@ -312,13 +320,6 @@ define(['./ArrayUtilities', './SVGUtilities'], (array, svg) => {
|
||||||
'height': '100%',
|
'height': '100%',
|
||||||
});
|
});
|
||||||
|
|
||||||
this.title = svg.make('text', Object.assign({
|
|
||||||
'y': ATTRS.TITLE['font-size'] + OUTER_MARGIN,
|
|
||||||
}, ATTRS.TITLE));
|
|
||||||
this.titleText = svg.makeText();
|
|
||||||
this.title.appendChild(this.titleText);
|
|
||||||
this.base.appendChild(this.title);
|
|
||||||
|
|
||||||
this.agentLines = svg.make('g');
|
this.agentLines = svg.make('g');
|
||||||
this.blocks = svg.make('g');
|
this.blocks = svg.make('g');
|
||||||
this.sections = svg.make('g');
|
this.sections = svg.make('g');
|
||||||
|
@ -329,6 +330,7 @@ define(['./ArrayUtilities', './SVGUtilities'], (array, svg) => {
|
||||||
this.base.appendChild(this.sections);
|
this.base.appendChild(this.sections);
|
||||||
this.base.appendChild(this.agentDecor);
|
this.base.appendChild(this.agentDecor);
|
||||||
this.base.appendChild(this.actions);
|
this.base.appendChild(this.actions);
|
||||||
|
this.title = new SVGTextBlock(this.base, ATTRS.TITLE, LINE_HEIGHT);
|
||||||
|
|
||||||
this.testers = svg.make('g');
|
this.testers = svg.make('g');
|
||||||
this.testersCache = new Map();
|
this.testersCache = new Map();
|
||||||
|
@ -975,19 +977,16 @@ define(['./ArrayUtilities', './SVGUtilities'], (array, svg) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBounds(stagesHeight) {
|
updateBounds(stagesHeight) {
|
||||||
const titleWidth = this.title.getComputedTextLength();
|
|
||||||
|
|
||||||
const cx = (this.minX + this.maxX) / 2;
|
const cx = (this.minX + this.maxX) / 2;
|
||||||
this.title.setAttribute('x', cx);
|
const titleY = ((this.title.height > 0) ?
|
||||||
this.title.setAttribute('y', -TITLE_MARGIN);
|
(-TITLE_MARGIN - this.title.height) : 0
|
||||||
|
|
||||||
const x0 = Math.min(this.minX, cx - titleWidth / 2) - OUTER_MARGIN;
|
|
||||||
const x1 = Math.max(this.maxX, cx + titleWidth / 2) + OUTER_MARGIN;
|
|
||||||
const y0 = (
|
|
||||||
-TITLE_MARGIN
|
|
||||||
- ATTRS.TITLE['font-size'] * LINE_HEIGHT
|
|
||||||
- OUTER_MARGIN
|
|
||||||
);
|
);
|
||||||
|
this.title.reanchor(cx, titleY);
|
||||||
|
|
||||||
|
const halfTitleWidth = this.title.width / 2;
|
||||||
|
const x0 = Math.min(this.minX, cx - halfTitleWidth) - OUTER_MARGIN;
|
||||||
|
const x1 = Math.max(this.maxX, cx + halfTitleWidth) + OUTER_MARGIN;
|
||||||
|
const y0 = titleY - OUTER_MARGIN;
|
||||||
const y1 = stagesHeight + OUTER_MARGIN;
|
const y1 = stagesHeight + OUTER_MARGIN;
|
||||||
|
|
||||||
this.base.setAttribute('viewBox', (
|
this.base.setAttribute('viewBox', (
|
||||||
|
@ -1005,7 +1004,7 @@ define(['./ArrayUtilities', './SVGUtilities'], (array, svg) => {
|
||||||
svg.empty(this.agentDecor);
|
svg.empty(this.agentDecor);
|
||||||
svg.empty(this.actions);
|
svg.empty(this.actions);
|
||||||
|
|
||||||
this.titleText.nodeValue = meta.title || '';
|
this.title.setText(meta.title);
|
||||||
|
|
||||||
this.minX = 0;
|
this.minX = 0;
|
||||||
this.maxX = 0;
|
this.maxX = 0;
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
define(['./SVGUtilities'], (svg) => {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
return class SVGTextBlock {
|
||||||
|
constructor(
|
||||||
|
container,
|
||||||
|
attrs,
|
||||||
|
lineHeight,
|
||||||
|
{text = '', x = 0, y = 0} = {}
|
||||||
|
) {
|
||||||
|
this.container = container;
|
||||||
|
this.attrs = attrs;
|
||||||
|
this.lineHeight = lineHeight;
|
||||||
|
this.text = '';
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.width = 0;
|
||||||
|
this.height = 0;
|
||||||
|
this.nodes = [];
|
||||||
|
this.setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateY() {
|
||||||
|
const sz = Number(this.attrs['font-size']);
|
||||||
|
const space = sz * this.lineHeight;
|
||||||
|
this.nodes.forEach(({element}, i) => {
|
||||||
|
element.setAttribute('y', this.y + i * space + sz);
|
||||||
|
});
|
||||||
|
this.height = space * this.nodes.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
_rebuildNodes(count) {
|
||||||
|
if(count === this.nodes.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(count > this.nodes.length) {
|
||||||
|
const attrs = Object.assign({'x': this.x}, this.attrs);
|
||||||
|
while(this.nodes.length < count) {
|
||||||
|
const element = svg.make('text', attrs);
|
||||||
|
const text = svg.makeText();
|
||||||
|
element.appendChild(text);
|
||||||
|
this.container.appendChild(element);
|
||||||
|
this.nodes.push({element, text});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while(this.nodes.length > count) {
|
||||||
|
const {element} = this.nodes.pop();
|
||||||
|
this.container.removeChild(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._updateY();
|
||||||
|
}
|
||||||
|
|
||||||
|
setText(newText) {
|
||||||
|
if(newText === this.text) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!newText) {
|
||||||
|
this.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.text = newText;
|
||||||
|
const lines = this.text.split('\n');
|
||||||
|
|
||||||
|
this._rebuildNodes(lines.length);
|
||||||
|
let maxWidth = 0;
|
||||||
|
this.nodes.forEach(({text, element}, i) => {
|
||||||
|
if(text.nodeValue !== lines[i]) {
|
||||||
|
text.nodeValue = lines[i];
|
||||||
|
}
|
||||||
|
maxWidth = Math.max(maxWidth, element.getComputedTextLength());
|
||||||
|
});
|
||||||
|
this.width = maxWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
reanchor(newX, newY) {
|
||||||
|
if(newX !== this.x) {
|
||||||
|
this.x = newX;
|
||||||
|
this.nodes.forEach(({element}) => {
|
||||||
|
element.setAttribute('x', this.x);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(newY !== this.y) {
|
||||||
|
this.y = newY;
|
||||||
|
this._updateY();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this._rebuildNodes(0);
|
||||||
|
this.text = '';
|
||||||
|
this.width = 0;
|
||||||
|
this.height = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
|
@ -0,0 +1,113 @@
|
||||||
|
defineDescribe('SVGTextBlock', ['./SVGTextBlock'], (SVGTextBlock) => {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const attrs = {'font-size': 10};
|
||||||
|
let hold = null;
|
||||||
|
let block = null;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
hold = document.createElement('p');
|
||||||
|
document.body.appendChild(hold);
|
||||||
|
block = new SVGTextBlock(hold, attrs, 1.5);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
document.body.removeChild(hold);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('defaults to blank text at 0, 0', () => {
|
||||||
|
expect(block.text).toEqual('');
|
||||||
|
expect(block.x).toEqual(0);
|
||||||
|
expect(block.y).toEqual(0);
|
||||||
|
expect(hold.children.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds the given text if specified', () => {
|
||||||
|
block = new SVGTextBlock(hold, attrs, 1.5, {text: 'abc'});
|
||||||
|
expect(block.text).toEqual('abc');
|
||||||
|
expect(hold.children.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the given coordinates if specified', () => {
|
||||||
|
block = new SVGTextBlock(hold, attrs, 1.5, {x: 5, y: 7});
|
||||||
|
expect(block.x).toEqual(5);
|
||||||
|
expect(block.y).toEqual(7);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setText', () => {
|
||||||
|
it('sets the text to the given content', () => {
|
||||||
|
block.setText('foo');
|
||||||
|
expect(block.text).toEqual('foo');
|
||||||
|
expect(hold.children.length).toEqual(1);
|
||||||
|
expect(hold.children[0].innerHTML).toEqual('foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders multiline text', () => {
|
||||||
|
block.setText('foo\nbar');
|
||||||
|
expect(hold.children.length).toEqual(2);
|
||||||
|
expect(hold.children[0].innerHTML).toEqual('foo');
|
||||||
|
expect(hold.children[1].innerHTML).toEqual('bar');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('re-uses text nodes when possible, adding more if needed', () => {
|
||||||
|
block.setText('foo\nbar');
|
||||||
|
const line0 = hold.children[0];
|
||||||
|
const line1 = hold.children[1];
|
||||||
|
|
||||||
|
block.setText('zig\nzag\nbaz');
|
||||||
|
|
||||||
|
expect(hold.children.length).toEqual(3);
|
||||||
|
expect(hold.children[0]).toEqual(line0);
|
||||||
|
expect(hold.children[0].innerHTML).toEqual('zig');
|
||||||
|
expect(hold.children[1]).toEqual(line1);
|
||||||
|
expect(hold.children[1].innerHTML).toEqual('zag');
|
||||||
|
expect(hold.children[2].innerHTML).toEqual('baz');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('re-uses text nodes when possible, removing extra if needed', () => {
|
||||||
|
block.setText('foo\nbar');
|
||||||
|
const line0 = hold.children[0];
|
||||||
|
|
||||||
|
block.setText('zig');
|
||||||
|
|
||||||
|
expect(hold.children.length).toEqual(1);
|
||||||
|
expect(hold.children[0]).toEqual(line0);
|
||||||
|
expect(hold.children[0].innerHTML).toEqual('zig');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('positions text nodes and applies attributes', () => {
|
||||||
|
block.setText('foo\nbar');
|
||||||
|
expect(hold.children.length).toEqual(2);
|
||||||
|
expect(hold.children[0].getAttribute('x')).toEqual('0');
|
||||||
|
expect(hold.children[0].getAttribute('y')).toEqual('10');
|
||||||
|
expect(hold.children[0].getAttribute('font-size')).toEqual('10');
|
||||||
|
expect(hold.children[1].getAttribute('x')).toEqual('0');
|
||||||
|
expect(hold.children[1].getAttribute('y')).toEqual('25');
|
||||||
|
expect(hold.children[1].getAttribute('font-size')).toEqual('10');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('reanchor', () => {
|
||||||
|
it('moves all nodes', () => {
|
||||||
|
block.setText('foo\nbaz');
|
||||||
|
block.reanchor(5, 7);
|
||||||
|
expect(hold.children[0].getAttribute('x')).toEqual('5');
|
||||||
|
expect(hold.children[0].getAttribute('y')).toEqual('17');
|
||||||
|
expect(hold.children[1].getAttribute('x')).toEqual('5');
|
||||||
|
expect(hold.children[1].getAttribute('y')).toEqual('32');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('clear', () => {
|
||||||
|
it('resets the text empty', () => {
|
||||||
|
block.setText('foo\nbaz');
|
||||||
|
block.setText('');
|
||||||
|
expect(hold.children.length).toEqual(0);
|
||||||
|
expect(block.text).toEqual('');
|
||||||
|
expect(block.width).toEqual(0);
|
||||||
|
expect(block.height).toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -5,4 +5,5 @@ define([
|
||||||
'sequence/Renderer_spec',
|
'sequence/Renderer_spec',
|
||||||
'sequence/ArrayUtilities_spec',
|
'sequence/ArrayUtilities_spec',
|
||||||
'sequence/SVGUtilities_spec',
|
'sequence/SVGUtilities_spec',
|
||||||
|
'sequence/SVGTextBlock_spec',
|
||||||
]);
|
]);
|
||||||
|
|
Loading…
Reference in New Issue