Simplify and extract wavy line rendering

This commit is contained in:
David Evans 2018-01-12 23:23:49 +00:00
parent 3553c66c9c
commit 4eb9ec21d6
11 changed files with 433 additions and 258 deletions

View File

@ -4065,113 +4065,6 @@ define('sequence/components/Connect',[
new Arrowhead('double'),
];
function makeWavyLineHeights(height) {
return [
0,
-height * 2 / 3,
-height,
-height * 2 / 3,
0,
height * 2 / 3,
height,
height * 2 / 3,
];
}
function renderFlat({x1, dx1, x2, dx2, y}, attrs) {
const ww = attrs['wave-width'];
const hh = attrs['wave-height'];
if(!ww || !hh) {
return {
shape: svg.make('line', Object.assign({
'x1': x1 + dx1,
'y1': y,
'x2': x2 + dx2,
'y2': y,
}, attrs)),
p1: {x: x1, y},
p2: {x: x2, y},
};
}
const heights = makeWavyLineHeights(hh);
const dw = ww / heights.length;
let p = 0;
let points = '';
const xL = Math.min(x1 + dx1, x2 + dx2);
const xR = Math.max(x1 + dx1, x2 + dx2);
for(let x = xL; x + dw <= xR; x += dw) {
points += (
x + ' ' +
(y + heights[(p ++) % heights.length]) + ' '
);
}
points += xR + ' ' + y;
return {
shape: svg.make('polyline', Object.assign({points}, attrs)),
p1: {x: x1, y},
p2: {x: x2, y},
};
}
function renderRev({xL, dx1, dx2, y1, y2, xR}, attrs) {
const r = (y2 - y1) / 2;
const ww = attrs['wave-width'];
const hh = attrs['wave-height'];
if(!ww || !hh) {
return {
shape: svg.make('path', Object.assign({
'd': (
'M' + (xL + dx1) + ' ' + y1 +
'L' + xR + ' ' + y1 +
'A' + r + ' ' + r + ' 0 0 1 ' + xR + ' ' + y2 +
'L' + (xL + dx2) + ' ' + y2
),
}, attrs)),
p1: {x: xL, y: y1},
p2: {x: xL, y: y2},
};
}
const heights = makeWavyLineHeights(hh);
const dw = ww / heights.length;
let p = 0;
let points = '';
for(let x = xL + dx1; x + dw <= xR; x += dw) {
points += (
x + ' ' +
(y1 + heights[(p ++) % heights.length]) + ' '
);
}
const ym = (y1 + y2) / 2;
for(let t = 0; t + dw / r <= Math.PI; t += dw / r) {
const h = heights[(p ++) % heights.length];
points += (
(xR + Math.sin(t) * (r - h)) + ' ' +
(ym - Math.cos(t) * (r - h)) + ' '
);
}
for(let x = xR; x - dw >= xL + dx2; x -= dw) {
points += (
x + ' ' +
(y2 - heights[(p ++) % heights.length]) + ' '
);
}
points += (xL + dx2) + ' ' + y2;
return {
shape: svg.make('polyline', Object.assign({points}, attrs)),
p1: {x: xL, y: y1},
p2: {x: xL, y: y2},
};
}
class Connect extends BaseComponent {
separation({label, agentNames, options}, env) {
const config = env.theme.connect;
@ -4261,14 +4154,14 @@ define('sequence/components/Connect',[
const y1 = y0 + r * 2;
const line = config.line[options.line];
const rendered = (line.renderRev || renderRev)({
const rendered = line.renderRev(line.attrs, {
xL: lineX,
dx1: lArrow.lineGap(env.theme, line.attrs),
dx2: rArrow.lineGap(env.theme, line.attrs),
y1: y0,
y2: y1,
xR: x1,
}, line.attrs);
});
env.shapeLayer.appendChild(rendered.shape);
lArrow.render(env.shapeLayer, env.theme, rendered.p1, 1);
@ -4325,13 +4218,13 @@ define('sequence/components/Connect',[
});
const line = config.line[options.line];
const rendered = (line.render || renderFlat)({
const rendered = line.renderFlat(line.attrs, {
x1: x0,
dx1: lArrow.lineGap(env.theme, line.attrs) * dir,
x2: x1,
dx2: -rArrow.lineGap(env.theme, line.attrs) * dir,
y,
}, line.attrs);
});
env.shapeLayer.appendChild(rendered.shape);
lArrow.render(env.shapeLayer, env.theme, rendered.p1, dir);
@ -5325,7 +5218,122 @@ define('sequence/Exporter',[],() => {
};
});
define('sequence/themes/BaseTheme',['svg/SVGUtilities'], (svg) => {
define('svg/PatternedLine',[],() => {
'use strict';
return class PatternedLine {
constructor(pattern = null, phase = 0) {
this.pattern = pattern;
this.dw = pattern && pattern.partWidth;
this.points = [];
this.phase = phase;
this.x = null;
this.y = null;
this.disconnect = false;
}
_link() {
if(this.disconnect) {
this.points.push(this.x + ' ' + this.y);
this.disconnect = false;
}
}
_nextDelta() {
return this.pattern.getDelta(this.phase ++);
}
cap() {
if(this.x !== null) {
this.points.push(this.x + ' ' + this.y);
this.x = null;
this.y = null;
this.disconnect = false;
}
return this;
}
move(x, y) {
this.cap();
this.x = x;
this.y = y;
this.disconnect = true;
return this;
}
line(x, y) {
this._link();
if(this.pattern) {
const len = Math.sqrt(
(x - this.x) * (x - this.x) +
(y - this.y) * (y - this.y)
);
const dx1 = (x - this.x) / len;
const dy1 = (y - this.y) / len;
const dx2 = -dy1;
const dy2 = dx1;
for(let pos = 0; pos + this.dw <= len; pos += this.dw) {
const delta = this._nextDelta();
this.points.push(
(this.x + pos * dx1 + delta * dx2) + ' ' +
(this.y + pos * dy1 + delta * dy2)
);
}
} else {
this.disconnect = true;
}
this.x = x;
this.y = y;
return this;
}
arc(cx, cy, theta) {
this._link();
const radius = Math.sqrt(
(cx - this.x) * (cx - this.x) +
(cy - this.y) * (cy - this.y)
);
const theta1 = Math.atan2(cx - this.x, cy - this.y);
this.x = cx + Math.sin(theta1 + theta) * radius;
this.y = cy - Math.cos(theta1 + theta) * radius;
if(this.pattern) {
const dir = (theta < 0 ? 1 : -1);
const dt = this.dw / radius;
for(let t = theta1; t + dt <= theta1 + theta; t += dt) {
const delta = this._nextDelta() * dir;
this.points.push(
(cx + Math.sin(t) * (radius + delta)) + ' ' +
(cy - Math.cos(t) * (radius + delta))
);
}
} else {
this.points.push(
(cx + Math.sin(theta1) * radius) + ' ' +
(cy - Math.cos(theta1) * radius) +
'A' + radius + ' ' + radius + ' 0 ' +
((theta < 0) ? '0 ' : '1 ') +
'1 ' +
this.x + ' ' + this.y
);
}
return this;
}
asPath() {
this._link();
return 'M' + this.points.join('L');
}
};
});
define('sequence/themes/BaseTheme',['svg/SVGUtilities', 'svg/PatternedLine'], (svg, PatternedLine) => {
'use strict';
function deepCopy(o) {
@ -5436,6 +5444,60 @@ define('sequence/themes/BaseTheme',['svg/SVGUtilities'], (svg) => {
}, attrs));
};
BaseTheme.WavePattern = class WavePattern {
constructor(width, height) {
this.deltas = [
0,
-height * 2 / 3,
-height,
-height * 2 / 3,
0,
height * 2 / 3,
height,
height * 2 / 3,
];
this.partWidth = width / this.deltas.length;
}
getDelta(p) {
return this.deltas[p % this.deltas.length];
}
};
BaseTheme.renderFlatConnector = (pattern, attrs, {x1, dx1, x2, dx2, y}) => {
return {
shape: svg.make('path', Object.assign({
d: new PatternedLine(pattern)
.move(x1 + dx1, y)
.line(x2 + dx2, y)
.cap()
.asPath(),
}, attrs)),
p1: {x: x1, y},
p2: {x: x2, y},
};
};
BaseTheme.renderRevConnector = (
pattern,
attrs,
{xL, dx1, dx2, y1, y2, xR}
) => {
return {
shape: svg.make('path', Object.assign({
d: new PatternedLine(pattern)
.move(xL + dx1, y1)
.line(xR, y1)
.arc(xR, (y1 + y2) / 2, Math.PI)
.line(xL + dx2, y2)
.cap()
.asPath(),
}, attrs)),
p1: {x: xL, y: y1},
p2: {x: xL, y: y2},
};
};
return BaseTheme;
});
@ -5453,6 +5515,8 @@ define('sequence/themes/Basic',[
const FONT = 'sans-serif';
const LINE_HEIGHT = 1.3;
const WAVE = new BaseTheme.WavePattern(6, 0.5);
const SETTINGS = {
titleMargin: 10,
outerMargin: 5,
@ -5517,6 +5581,8 @@ define('sequence/themes/Basic',[
'stroke': '#000000',
'stroke-width': 1,
},
renderFlat: BaseTheme.renderFlatConnector.bind(null, null),
renderRev: BaseTheme.renderRevConnector.bind(null, null),
},
'dash': {
attrs: {
@ -5525,6 +5591,8 @@ define('sequence/themes/Basic',[
'stroke-width': 1,
'stroke-dasharray': '4, 2',
},
renderFlat: BaseTheme.renderFlatConnector.bind(null, null),
renderRev: BaseTheme.renderRevConnector.bind(null, null),
},
'wave': {
attrs: {
@ -5533,9 +5601,9 @@ define('sequence/themes/Basic',[
'stroke-width': 1,
'stroke-linejoin': 'round',
'stroke-linecap': 'round',
'wave-width': 6,
'wave-height': 0.5,
},
renderFlat: BaseTheme.renderFlatConnector.bind(null, WAVE),
renderRev: BaseTheme.renderRevConnector.bind(null, WAVE),
},
},
arrow: {
@ -5752,6 +5820,8 @@ define('sequence/themes/Chunky',[
const FONT = 'sans-serif';
const LINE_HEIGHT = 1.3;
const WAVE = new BaseTheme.WavePattern(10, 1);
const SETTINGS = {
titleMargin: 12,
outerMargin: 5,
@ -5822,6 +5892,8 @@ define('sequence/themes/Chunky',[
'stroke': '#000000',
'stroke-width': 3,
},
renderFlat: BaseTheme.renderFlatConnector.bind(null, null),
renderRev: BaseTheme.renderRevConnector.bind(null, null),
},
'dash': {
attrs: {
@ -5830,6 +5902,8 @@ define('sequence/themes/Chunky',[
'stroke-width': 3,
'stroke-dasharray': '10, 4',
},
renderFlat: BaseTheme.renderFlatConnector.bind(null, null),
renderRev: BaseTheme.renderRevConnector.bind(null, null),
},
'wave': {
attrs: {
@ -5838,9 +5912,9 @@ define('sequence/themes/Chunky',[
'stroke-width': 3,
'stroke-linejoin': 'round',
'stroke-linecap': 'round',
'wave-width': 10,
'wave-height': 1,
},
renderFlat: BaseTheme.renderFlatConnector.bind(null, WAVE),
renderRev: BaseTheme.renderRevConnector.bind(null, WAVE),
},
},
arrow: {
@ -6514,6 +6588,8 @@ define('sequence/themes/Sketch',[
'stroke-linecap': 'round',
};
const WAVE = new BaseTheme.WavePattern(6, 0.5);
const SETTINGS = {
titleMargin: 10,
outerMargin: 5,
@ -6564,27 +6640,25 @@ define('sequence/themes/Sketch',[
attrs: Object.assign({
'fill': 'none',
}, PENCIL),
render: null,
renderRev: null,
renderFlat: null,
renderRev: BaseTheme.renderRevConnector.bind(null, null),
},
'dash': {
attrs: Object.assign({
'fill': 'none',
'stroke-dasharray': '4, 2',
}, PENCIL),
render: null,
renderRev: null,
renderFlat: null,
renderRev: BaseTheme.renderRevConnector.bind(null, null),
},
'wave': {
attrs: Object.assign({
'fill': 'none',
'stroke-linejoin': 'round',
'stroke-linecap': 'round',
'wave-width': 6,
'wave-height': 0.5,
}, PENCIL),
render: null,
renderRev: null,
renderFlat: BaseTheme.renderFlatConnector.bind(null, WAVE),
renderRev: BaseTheme.renderRevConnector.bind(null, WAVE),
},
},
arrow: {
@ -6800,16 +6874,19 @@ define('sequence/themes/Sketch',[
this.renderBar = this.renderBar.bind(this);
this.renderBox = this.renderBox.bind(this);
this.renderArrowHead = this.renderArrowHead.bind(this);
this.renderConnect = this.renderConnect.bind(this);
this.renderFlatConnector = this.renderFlatConnector.bind(this);
this.renderTag = this.renderTag.bind(this);
this.agentCap.cross.render = this.renderCross.bind(this);
this.agentCap.bar.render = this.renderBar;
this.agentCap.box.boxRenderer = this.renderBox;
this.connect.arrow.single.render = this.renderArrowHead;
this.connect.arrow.double.render = this.renderArrowHead;
this.connect.line.solid.render = this.renderConnect;
this.connect.line.dash.render = this.renderConnect;
this.connect.line.solid.renderFlat = this.renderFlatConnector;
this.connect.line.dash.renderFlat = this.renderFlatConnector;
this.notes.note.boxRenderer = this.renderNote.bind(this);
this.notes.state.boxRenderer = this.renderState.bind(this);
this.blocks.ref.boxRenderer = this.renderRefBlock.bind(this);
@ -6998,7 +7075,7 @@ define('sequence/themes/Sketch',[
return g;
}
renderConnect({x1, dx1, x2, dx2, y}, attrs) {
renderFlatConnector(attrs, {x1, dx1, x2, dx2, y}) {
const ln = this.lineNodes(
{x: x1 + dx1, y},
{x: x2 + dx2, y},

File diff suppressed because one or more lines are too long

View File

@ -118,7 +118,7 @@ defineDescribe('SequenceDiagram', [
// Arrow
expect(content).toContain(
'<line x1="20.5" y1="26" x2="48.5" y2="26"'
'<path d="M20.5 26L48.5 26"'
);
expect(content).toContain(
'<polygon points="46 21 51 26 46 31"'

View File

@ -76,113 +76,6 @@ define([
new Arrowhead('double'),
];
function makeWavyLineHeights(height) {
return [
0,
-height * 2 / 3,
-height,
-height * 2 / 3,
0,
height * 2 / 3,
height,
height * 2 / 3,
];
}
function renderFlat({x1, dx1, x2, dx2, y}, attrs) {
const ww = attrs['wave-width'];
const hh = attrs['wave-height'];
if(!ww || !hh) {
return {
shape: svg.make('line', Object.assign({
'x1': x1 + dx1,
'y1': y,
'x2': x2 + dx2,
'y2': y,
}, attrs)),
p1: {x: x1, y},
p2: {x: x2, y},
};
}
const heights = makeWavyLineHeights(hh);
const dw = ww / heights.length;
let p = 0;
let points = '';
const xL = Math.min(x1 + dx1, x2 + dx2);
const xR = Math.max(x1 + dx1, x2 + dx2);
for(let x = xL; x + dw <= xR; x += dw) {
points += (
x + ' ' +
(y + heights[(p ++) % heights.length]) + ' '
);
}
points += xR + ' ' + y;
return {
shape: svg.make('polyline', Object.assign({points}, attrs)),
p1: {x: x1, y},
p2: {x: x2, y},
};
}
function renderRev({xL, dx1, dx2, y1, y2, xR}, attrs) {
const r = (y2 - y1) / 2;
const ww = attrs['wave-width'];
const hh = attrs['wave-height'];
if(!ww || !hh) {
return {
shape: svg.make('path', Object.assign({
'd': (
'M' + (xL + dx1) + ' ' + y1 +
'L' + xR + ' ' + y1 +
'A' + r + ' ' + r + ' 0 0 1 ' + xR + ' ' + y2 +
'L' + (xL + dx2) + ' ' + y2
),
}, attrs)),
p1: {x: xL, y: y1},
p2: {x: xL, y: y2},
};
}
const heights = makeWavyLineHeights(hh);
const dw = ww / heights.length;
let p = 0;
let points = '';
for(let x = xL + dx1; x + dw <= xR; x += dw) {
points += (
x + ' ' +
(y1 + heights[(p ++) % heights.length]) + ' '
);
}
const ym = (y1 + y2) / 2;
for(let t = 0; t + dw / r <= Math.PI; t += dw / r) {
const h = heights[(p ++) % heights.length];
points += (
(xR + Math.sin(t) * (r - h)) + ' ' +
(ym - Math.cos(t) * (r - h)) + ' '
);
}
for(let x = xR; x - dw >= xL + dx2; x -= dw) {
points += (
x + ' ' +
(y2 - heights[(p ++) % heights.length]) + ' '
);
}
points += (xL + dx2) + ' ' + y2;
return {
shape: svg.make('polyline', Object.assign({points}, attrs)),
p1: {x: xL, y: y1},
p2: {x: xL, y: y2},
};
}
class Connect extends BaseComponent {
separation({label, agentNames, options}, env) {
const config = env.theme.connect;
@ -272,14 +165,14 @@ define([
const y1 = y0 + r * 2;
const line = config.line[options.line];
const rendered = (line.renderRev || renderRev)({
const rendered = line.renderRev(line.attrs, {
xL: lineX,
dx1: lArrow.lineGap(env.theme, line.attrs),
dx2: rArrow.lineGap(env.theme, line.attrs),
y1: y0,
y2: y1,
xR: x1,
}, line.attrs);
});
env.shapeLayer.appendChild(rendered.shape);
lArrow.render(env.shapeLayer, env.theme, rendered.p1, 1);
@ -336,13 +229,13 @@ define([
});
const line = config.line[options.line];
const rendered = (line.render || renderFlat)({
const rendered = line.renderFlat(line.attrs, {
x1: x0,
dx1: lArrow.lineGap(env.theme, line.attrs) * dir,
x2: x1,
dx2: -rArrow.lineGap(env.theme, line.attrs) * dir,
y,
}, line.attrs);
});
env.shapeLayer.appendChild(rendered.shape);
lArrow.render(env.shapeLayer, env.theme, rendered.p1, dir);

View File

@ -1,4 +1,4 @@
define(['svg/SVGUtilities'], (svg) => {
define(['svg/SVGUtilities', 'svg/PatternedLine'], (svg, PatternedLine) => {
'use strict';
function deepCopy(o) {
@ -109,5 +109,59 @@ define(['svg/SVGUtilities'], (svg) => {
}, attrs));
};
BaseTheme.WavePattern = class WavePattern {
constructor(width, height) {
this.deltas = [
0,
-height * 2 / 3,
-height,
-height * 2 / 3,
0,
height * 2 / 3,
height,
height * 2 / 3,
];
this.partWidth = width / this.deltas.length;
}
getDelta(p) {
return this.deltas[p % this.deltas.length];
}
};
BaseTheme.renderFlatConnector = (pattern, attrs, {x1, dx1, x2, dx2, y}) => {
return {
shape: svg.make('path', Object.assign({
d: new PatternedLine(pattern)
.move(x1 + dx1, y)
.line(x2 + dx2, y)
.cap()
.asPath(),
}, attrs)),
p1: {x: x1, y},
p2: {x: x2, y},
};
};
BaseTheme.renderRevConnector = (
pattern,
attrs,
{xL, dx1, dx2, y1, y2, xR}
) => {
return {
shape: svg.make('path', Object.assign({
d: new PatternedLine(pattern)
.move(xL + dx1, y1)
.line(xR, y1)
.arc(xR, (y1 + y2) / 2, Math.PI)
.line(xL + dx2, y2)
.cap()
.asPath(),
}, attrs)),
p1: {x: xL, y: y1},
p2: {x: xL, y: y2},
};
};
return BaseTheme;
});

View File

@ -12,6 +12,8 @@ define([
const FONT = 'sans-serif';
const LINE_HEIGHT = 1.3;
const WAVE = new BaseTheme.WavePattern(6, 0.5);
const SETTINGS = {
titleMargin: 10,
outerMargin: 5,
@ -76,6 +78,8 @@ define([
'stroke': '#000000',
'stroke-width': 1,
},
renderFlat: BaseTheme.renderFlatConnector.bind(null, null),
renderRev: BaseTheme.renderRevConnector.bind(null, null),
},
'dash': {
attrs: {
@ -84,6 +88,8 @@ define([
'stroke-width': 1,
'stroke-dasharray': '4, 2',
},
renderFlat: BaseTheme.renderFlatConnector.bind(null, null),
renderRev: BaseTheme.renderRevConnector.bind(null, null),
},
'wave': {
attrs: {
@ -92,9 +98,9 @@ define([
'stroke-width': 1,
'stroke-linejoin': 'round',
'stroke-linecap': 'round',
'wave-width': 6,
'wave-height': 0.5,
},
renderFlat: BaseTheme.renderFlatConnector.bind(null, WAVE),
renderRev: BaseTheme.renderRevConnector.bind(null, WAVE),
},
},
arrow: {

View File

@ -12,6 +12,8 @@ define([
const FONT = 'sans-serif';
const LINE_HEIGHT = 1.3;
const WAVE = new BaseTheme.WavePattern(10, 1);
const SETTINGS = {
titleMargin: 12,
outerMargin: 5,
@ -82,6 +84,8 @@ define([
'stroke': '#000000',
'stroke-width': 3,
},
renderFlat: BaseTheme.renderFlatConnector.bind(null, null),
renderRev: BaseTheme.renderRevConnector.bind(null, null),
},
'dash': {
attrs: {
@ -90,6 +94,8 @@ define([
'stroke-width': 3,
'stroke-dasharray': '10, 4',
},
renderFlat: BaseTheme.renderFlatConnector.bind(null, null),
renderRev: BaseTheme.renderRevConnector.bind(null, null),
},
'wave': {
attrs: {
@ -98,9 +104,9 @@ define([
'stroke-width': 3,
'stroke-linejoin': 'round',
'stroke-linecap': 'round',
'wave-width': 10,
'wave-height': 1,
},
renderFlat: BaseTheme.renderFlatConnector.bind(null, WAVE),
renderRev: BaseTheme.renderRevConnector.bind(null, WAVE),
},
},
arrow: {

View File

@ -30,6 +30,8 @@ define([
'stroke-linecap': 'round',
};
const WAVE = new BaseTheme.WavePattern(6, 0.5);
const SETTINGS = {
titleMargin: 10,
outerMargin: 5,
@ -80,27 +82,25 @@ define([
attrs: Object.assign({
'fill': 'none',
}, PENCIL),
render: null,
renderRev: null,
renderFlat: null,
renderRev: BaseTheme.renderRevConnector.bind(null, null),
},
'dash': {
attrs: Object.assign({
'fill': 'none',
'stroke-dasharray': '4, 2',
}, PENCIL),
render: null,
renderRev: null,
renderFlat: null,
renderRev: BaseTheme.renderRevConnector.bind(null, null),
},
'wave': {
attrs: Object.assign({
'fill': 'none',
'stroke-linejoin': 'round',
'stroke-linecap': 'round',
'wave-width': 6,
'wave-height': 0.5,
}, PENCIL),
render: null,
renderRev: null,
renderFlat: BaseTheme.renderFlatConnector.bind(null, WAVE),
renderRev: BaseTheme.renderRevConnector.bind(null, WAVE),
},
},
arrow: {
@ -316,16 +316,19 @@ define([
this.renderBar = this.renderBar.bind(this);
this.renderBox = this.renderBox.bind(this);
this.renderArrowHead = this.renderArrowHead.bind(this);
this.renderConnect = this.renderConnect.bind(this);
this.renderFlatConnector = this.renderFlatConnector.bind(this);
this.renderTag = this.renderTag.bind(this);
this.agentCap.cross.render = this.renderCross.bind(this);
this.agentCap.bar.render = this.renderBar;
this.agentCap.box.boxRenderer = this.renderBox;
this.connect.arrow.single.render = this.renderArrowHead;
this.connect.arrow.double.render = this.renderArrowHead;
this.connect.line.solid.render = this.renderConnect;
this.connect.line.dash.render = this.renderConnect;
this.connect.line.solid.renderFlat = this.renderFlatConnector;
this.connect.line.dash.renderFlat = this.renderFlatConnector;
this.notes.note.boxRenderer = this.renderNote.bind(this);
this.notes.state.boxRenderer = this.renderState.bind(this);
this.blocks.ref.boxRenderer = this.renderRefBlock.bind(this);
@ -514,7 +517,7 @@ define([
return g;
}
renderConnect({x1, dx1, x2, dx2, y}, attrs) {
renderFlatConnector(attrs, {x1, dx1, x2, dx2, y}) {
const ln = this.lineNodes(
{x: x1 + dx1, y},
{x: x2 + dx2, y},

View File

@ -4,6 +4,7 @@ define([
'svg/SVGUtilities_spec',
'svg/SVGTextBlock_spec',
'svg/SVGShapes_spec',
'svg/PatternedLine_spec',
'interface/Interface_spec',
'sequence/SequenceDiagram_spec',
'sequence/Tokeniser_spec',

View File

@ -0,0 +1,114 @@
define(() => {
'use strict';
return class PatternedLine {
constructor(pattern = null, phase = 0) {
this.pattern = pattern;
this.dw = pattern && pattern.partWidth;
this.points = [];
this.phase = phase;
this.x = null;
this.y = null;
this.disconnect = false;
}
_link() {
if(this.disconnect) {
this.points.push(this.x + ' ' + this.y);
this.disconnect = false;
}
}
_nextDelta() {
return this.pattern.getDelta(this.phase ++);
}
cap() {
if(this.x !== null) {
this.points.push(this.x + ' ' + this.y);
this.x = null;
this.y = null;
this.disconnect = false;
}
return this;
}
move(x, y) {
this.cap();
this.x = x;
this.y = y;
this.disconnect = true;
return this;
}
line(x, y) {
this._link();
if(this.pattern) {
const len = Math.sqrt(
(x - this.x) * (x - this.x) +
(y - this.y) * (y - this.y)
);
const dx1 = (x - this.x) / len;
const dy1 = (y - this.y) / len;
const dx2 = -dy1;
const dy2 = dx1;
for(let pos = 0; pos + this.dw <= len; pos += this.dw) {
const delta = this._nextDelta();
this.points.push(
(this.x + pos * dx1 + delta * dx2) + ' ' +
(this.y + pos * dy1 + delta * dy2)
);
}
} else {
this.disconnect = true;
}
this.x = x;
this.y = y;
return this;
}
arc(cx, cy, theta) {
this._link();
const radius = Math.sqrt(
(cx - this.x) * (cx - this.x) +
(cy - this.y) * (cy - this.y)
);
const theta1 = Math.atan2(cx - this.x, cy - this.y);
this.x = cx + Math.sin(theta1 + theta) * radius;
this.y = cy - Math.cos(theta1 + theta) * radius;
if(this.pattern) {
const dir = (theta < 0 ? 1 : -1);
const dt = this.dw / radius;
for(let t = theta1; t + dt <= theta1 + theta; t += dt) {
const delta = this._nextDelta() * dir;
this.points.push(
(cx + Math.sin(t) * (radius + delta)) + ' ' +
(cy - Math.cos(t) * (radius + delta))
);
}
} else {
this.points.push(
(cx + Math.sin(theta1) * radius) + ' ' +
(cy - Math.cos(theta1) * radius) +
'A' + radius + ' ' + radius + ' 0 ' +
((theta < 0) ? '0 ' : '1 ') +
'1 ' +
this.x + ' ' + this.y
);
}
return this;
}
asPath() {
this._link();
return 'M' + this.points.join('L');
}
};
});

View File

@ -0,0 +1,21 @@
defineDescribe('PatternedLine', ['./PatternedLine'], (PatternedLine) => {
'use strict';
describe('unpatterned', () => {
describe('line', () => {
it('connects points with lines', () => {
const ln = new PatternedLine()
.move(10, 20)
.line(30, 50)
.line(1, 2)
.cap();
expect(ln.asPath()).toEqual(
'M10 20' +
'L30 50' +
'L1 2'
);
});
});
});
});