refactoring van de cmap structuren bijna compleet
This commit is contained in:
@@ -0,0 +1,524 @@
|
||||
/**
|
||||
* Render the node, link and connector primitives of a diagram.
|
||||
*
|
||||
* These classes contain geometry and DOM presentation only. Application code
|
||||
* reaches them through DiagramNode and DiagramLink handles.
|
||||
* Derived from ionstage/cmap 0.1.3, (c) 2015 iOnStage, MIT License.
|
||||
*/
|
||||
import { Component, dom, helper } from "./drawing-support.js";
|
||||
|
||||
class Node extends Component {
|
||||
constructor(props) {
|
||||
super();
|
||||
this.visible = true;
|
||||
this.content = this.prop(props.content, '', helper.toString);
|
||||
this.contentType = this.prop(props.contentType, helper.CONTENT_TYPE_TEXT, helper.toContentType);
|
||||
this.x = this.prop(props.x, 0, helper.toNumber);
|
||||
this.y = this.prop(props.y, 0, helper.toNumber);
|
||||
this.width = this.prop(props.width, 75, helper.toNumber);
|
||||
this.height = this.prop(props.height, 30, helper.toNumber);
|
||||
this.backgroundColor = this.prop(props.backgroundColor, '#a7cbe6', helper.toString);
|
||||
this.borderColor = this.prop(props.borderColor, '#333', helper.toString);
|
||||
this.borderWidth = this.prop(props.borderWidth, 2, helper.toNumber);
|
||||
this.textColor = this.prop(props.textColor, '#333', helper.toString);
|
||||
this.zIndex = this.prop('auto');
|
||||
this.element = this.prop(null);
|
||||
this.parentElement = this.prop(null);
|
||||
this.cache = this.prop({});
|
||||
this.relations = this.prop([]);
|
||||
this.moveHandler = null;
|
||||
}
|
||||
|
||||
cx() {
|
||||
return this.x() + this.width() / 2;
|
||||
}
|
||||
|
||||
cy() {
|
||||
return this.y() + this.height() / 2;
|
||||
}
|
||||
|
||||
borderRadius() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
contains(x, y, tolerance) {
|
||||
var nx = this.x();
|
||||
var ny = this.y();
|
||||
var nwidth = this.width();
|
||||
var nheight = this.height();
|
||||
|
||||
return (nx - tolerance <= x && x <= nx + nwidth + tolerance &&
|
||||
ny - tolerance <= y && y <= ny + nheight + tolerance);
|
||||
}
|
||||
|
||||
style() {
|
||||
var contentType = this.contentType();
|
||||
var lineHeight = (contentType === helper.CONTENT_TYPE_TEXT) ? this.height() : 14;
|
||||
var textAlign = (contentType === helper.CONTENT_TYPE_TEXT) ? 'center' : 'left';
|
||||
var translate = 'translate(' + this.x() + 'px, ' + this.y() + 'px)';
|
||||
var borderWidthOffset = this.borderWidth() * 2;
|
||||
|
||||
return {
|
||||
backgroundColor: this.backgroundColor(),
|
||||
border: this.borderWidth() + 'px solid ' + this.borderColor(),
|
||||
borderRadius: this.borderRadius() + 'px',
|
||||
color: this.textColor(),
|
||||
display: this.visible ? '' : 'none',
|
||||
height: (this.height() - borderWidthOffset) + 'px',
|
||||
lineHeight: (lineHeight - borderWidthOffset) + 'px',
|
||||
msTransform: translate,
|
||||
overflow: 'hidden',
|
||||
pointerEvents: 'auto',
|
||||
position: 'absolute',
|
||||
textAlign: textAlign,
|
||||
textOverflow: 'ellipsis',
|
||||
transform: translate,
|
||||
webkitTransform: translate,
|
||||
whiteSpace: 'nowrap',
|
||||
width: (this.width() - borderWidthOffset) + 'px',
|
||||
zIndex: this.zIndex()
|
||||
};
|
||||
}
|
||||
|
||||
redraw() {
|
||||
var element = this.element();
|
||||
var parentElement = this.parentElement();
|
||||
|
||||
if (!parentElement && !element)
|
||||
return;
|
||||
|
||||
// add element
|
||||
if (parentElement && !element) {
|
||||
element = dom.el('<div>');
|
||||
this.element(element);
|
||||
dom.append(parentElement, element);
|
||||
this.redraw();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// remove element
|
||||
if (!parentElement && element) {
|
||||
dom.remove(element);
|
||||
this.element(null);
|
||||
this.cache({});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var cache = this.cache();
|
||||
|
||||
// update element
|
||||
var content = this.content();
|
||||
|
||||
if (content !== cache.content) {
|
||||
var contentType = this.contentType();
|
||||
|
||||
if (contentType === helper.CONTENT_TYPE_TEXT)
|
||||
dom.text(element, content);
|
||||
else if (contentType === helper.CONTENT_TYPE_HTML)
|
||||
dom.html(element, content);
|
||||
|
||||
cache.content = content;
|
||||
}
|
||||
|
||||
var style = this.style();
|
||||
|
||||
dom.css(element, helper.diffObj(style, cache.style));
|
||||
cache.style = style;
|
||||
this.notifyRendered();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Link extends Component {
|
||||
constructor(props) {
|
||||
super();
|
||||
this.visible = true;
|
||||
this.content = this.prop(props.content, '', helper.toString);
|
||||
this.contentType = this.prop(props.contentType, helper.CONTENT_TYPE_TEXT, helper.toContentType);
|
||||
this.cx = this.prop(props.cx, 100, helper.toNumber);
|
||||
this.cy = this.prop(props.cy, 40, helper.toNumber);
|
||||
this.width = this.prop(props.width, 50, helper.toNumber);
|
||||
this.height = this.prop(props.height, 20, helper.toNumber);
|
||||
this.backgroundColor = this.prop(props.backgroundColor, 'white', helper.toString);
|
||||
this.borderColor = this.prop(props.borderColor, '#333', helper.toString);
|
||||
this.borderWidth = this.prop(props.borderWidth, 2, helper.toNumber);
|
||||
this.textColor = this.prop(props.textColor, '#333', helper.toString);
|
||||
this.sourceX = this.prop(props.sourceX, this.cx() - 70, helper.toNumber);
|
||||
this.sourceY = this.prop(props.sourceY, this.cy(), helper.toNumber);
|
||||
this.targetX = this.prop(props.targetX, this.cx() + 70, helper.toNumber);
|
||||
this.targetY = this.prop(props.targetY, this.cy(), helper.toNumber);
|
||||
this.lineColor = this.prop(props.lineColor, '#333', helper.toString);
|
||||
this.lineWidth = this.prop(props.lineWidth, 2, helper.toNumber);
|
||||
this.hasArrow = this.prop(props.hasArrow, true, helper.toBoolean);
|
||||
this.zIndex = this.prop('auto');
|
||||
this.element = this.prop(null);
|
||||
this.parentElement = this.prop(null);
|
||||
this.cache = this.prop({});
|
||||
this.relations = this.prop([]);
|
||||
this.connectionChangeHandler = null;
|
||||
}
|
||||
|
||||
straighten(sx, sy, tx, ty) {
|
||||
if (arguments.length === 0) {
|
||||
this.cx((this.sourceX() + this.targetX()) / 2);
|
||||
this.cy((this.sourceY() + this.targetY()) / 2);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.cx((sx + tx) / 2);
|
||||
this.cy((sy + ty) / 2);
|
||||
this.sourceX(sx);
|
||||
this.sourceY(sy);
|
||||
this.targetX(tx);
|
||||
this.targetY(ty);
|
||||
}
|
||||
|
||||
contains(x, y, tolerance) {
|
||||
var content = this.content();
|
||||
var lcx = this.cx();
|
||||
var lcy = this.cy();
|
||||
|
||||
// content area
|
||||
if (content) {
|
||||
var lwidth = this.width();
|
||||
var lheight = this.height();
|
||||
|
||||
var lx = lcx - lwidth / 2;
|
||||
var ly = lcy - lheight / 2;
|
||||
|
||||
if (lx - tolerance <= x && x <= lx + lwidth + tolerance &&
|
||||
ly - tolerance <= y && y <= ly + lheight + tolerance) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var lineWidth = this.lineWidth();
|
||||
|
||||
// source path
|
||||
if (this.containsPath(this.sourceX(), this.sourceY(), lcx, lcy, x, y, lineWidth / 2 + tolerance))
|
||||
return true;
|
||||
|
||||
// target path
|
||||
if (this.containsPath(this.targetX(), this.targetY(), lcx, lcy, x, y, lineWidth / 2 + tolerance))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
containsPath(x0, y0, x1, y1, x, y, d) {
|
||||
var ax = x1 - x0;
|
||||
var ay = y1 - y0;
|
||||
|
||||
var bx = x - x0;
|
||||
var by = y - y0;
|
||||
|
||||
var r = (ax * bx + ay * by) / (ax * ax + ay * ay);
|
||||
|
||||
if (0 <= r && r <= 1) {
|
||||
var px = x0 + r * ax;
|
||||
var py = y0 + r * ay;
|
||||
|
||||
var dx = px - x;
|
||||
var dy = py - y;
|
||||
|
||||
if (dx * dx + dy * dy <= d * d)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
style() {
|
||||
return {
|
||||
display: this.visible ? '' : 'none',
|
||||
pointerEvents: 'none',
|
||||
position: 'absolute',
|
||||
zIndex: this.zIndex()
|
||||
};
|
||||
}
|
||||
|
||||
pathContainerStyle() {
|
||||
var width = Math.max(this.cx(), this.sourceX(), this.targetX());
|
||||
var height = Math.max(this.cy(), this.sourceY(), this.targetY());
|
||||
|
||||
return {
|
||||
height: height + 'px',
|
||||
overflow: 'visible',
|
||||
position: 'absolute',
|
||||
width: width + 'px'
|
||||
};
|
||||
}
|
||||
|
||||
lineAttributes() {
|
||||
var d = [
|
||||
'M', this.sourceX(), this.sourceY(),
|
||||
'L', this.cx(), this.cy(),
|
||||
'L', this.targetX(), this.targetY()
|
||||
].join(' ');
|
||||
|
||||
return {
|
||||
d: d,
|
||||
fill: 'none',
|
||||
stroke: this.lineColor(),
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-width': this.lineWidth()
|
||||
};
|
||||
}
|
||||
|
||||
arrowAttributes() {
|
||||
var cx = this.cx();
|
||||
var cy = this.cy();
|
||||
var tx = this.targetX();
|
||||
var ty = this.targetY();
|
||||
|
||||
var radians = Math.atan2(ty - cy, tx - cx);
|
||||
|
||||
var p0 = {
|
||||
x: 15 * Math.cos(radians - 26 * Math.PI / 180),
|
||||
y: 15 * Math.sin(radians - 26 * Math.PI / 180)
|
||||
};
|
||||
|
||||
var p1 = {
|
||||
x: 15 * Math.cos(radians + 26 * Math.PI / 180),
|
||||
y: 15 * Math.sin(radians + 26 * Math.PI / 180)
|
||||
};
|
||||
|
||||
var p2 = {
|
||||
x: 7 * Math.cos(radians),
|
||||
y: 7 * Math.sin(radians)
|
||||
};
|
||||
|
||||
var d = [
|
||||
'M', tx - p0.x, ty - p0.y,
|
||||
'L', tx, ty,
|
||||
'L', tx - p1.x, ty - p1.y,
|
||||
'Q', tx - p2.x, ty - p2.y, tx - p0.x, ty - p0.y,
|
||||
'Z'
|
||||
].join(' ');
|
||||
|
||||
return {
|
||||
d: d,
|
||||
fill: this.lineColor(),
|
||||
stroke: this.lineColor(),
|
||||
'stroke-linejoin': 'round',
|
||||
'stroke-width': this.lineWidth(),
|
||||
visibility: this.hasArrow() ? 'visible' : 'hidden'
|
||||
};
|
||||
}
|
||||
|
||||
contentStyle() {
|
||||
var contentType = this.contentType();
|
||||
var lineHeight = (contentType === helper.CONTENT_TYPE_TEXT) ? this.height() : 14;
|
||||
var textAlign = (contentType === helper.CONTENT_TYPE_TEXT) ? 'center' : 'left';
|
||||
var x = this.cx() - this.width() / 2;
|
||||
var y = this.cy() - this.height() / 2;
|
||||
var translate = 'translate(' + x + 'px, ' + y + 'px)';
|
||||
var borderWidthOffset = this.borderWidth() * 2;
|
||||
|
||||
return {
|
||||
backgroundColor: this.backgroundColor(),
|
||||
border: this.borderWidth() + 'px solid ' + this.borderColor(),
|
||||
borderRadius: '4px',
|
||||
color: this.textColor(),
|
||||
height: (this.height() - borderWidthOffset) + 'px',
|
||||
lineHeight: (lineHeight - borderWidthOffset) + 'px',
|
||||
msTransform: translate,
|
||||
overflow: 'hidden',
|
||||
position: 'absolute',
|
||||
textAlign: textAlign,
|
||||
textOverflow: 'ellipsis',
|
||||
transform: translate,
|
||||
visibility: this.content() ? 'visible' : 'hidden',
|
||||
webkitTransform: translate,
|
||||
whiteSpace: 'nowrap',
|
||||
width: (this.width() - borderWidthOffset) + 'px'
|
||||
};
|
||||
}
|
||||
|
||||
redraw() {
|
||||
var element = this.element();
|
||||
var parentElement = this.parentElement();
|
||||
|
||||
if (!parentElement && !element)
|
||||
return;
|
||||
|
||||
// add element
|
||||
if (parentElement && !element) {
|
||||
element = dom.el('<div>');
|
||||
dom.html(element, '<svg><path></path><path></path></svg><div></div>');
|
||||
this.element(element);
|
||||
dom.append(parentElement, element);
|
||||
this.redraw();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// remove element
|
||||
if (!parentElement && element) {
|
||||
dom.remove(element);
|
||||
this.element(null);
|
||||
this.cache({});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var cache = this.cache();
|
||||
|
||||
// update path container element
|
||||
var pathContainerStyle = this.pathContainerStyle();
|
||||
var pathContainerElement = dom.child(element, 0);
|
||||
|
||||
dom.css(pathContainerElement, helper.diffObj(pathContainerStyle, cache.pathContainerElementStyle));
|
||||
cache.pathContainerElementStyle = contentStyle;
|
||||
|
||||
// update line element
|
||||
var lineAttributes = this.lineAttributes();
|
||||
var lineElement = dom.child(pathContainerElement, 0);
|
||||
|
||||
dom.attr(lineElement, helper.diffObj(lineAttributes, cache.lineAttributes));
|
||||
cache.lineAttributes = lineAttributes;
|
||||
|
||||
// update arrow element
|
||||
var arrowAttributes = this.arrowAttributes();
|
||||
var arrowElement = dom.child(pathContainerElement, 1);
|
||||
|
||||
dom.attr(arrowElement, helper.diffObj(arrowAttributes, cache.arrowAttributes));
|
||||
cache.arrowAttributes = arrowAttributes;
|
||||
|
||||
// update content element
|
||||
var content = this.content();
|
||||
var contentStyle = this.contentStyle();
|
||||
var contentElement = dom.child(element, 1);
|
||||
|
||||
if (content !== cache.content) {
|
||||
var contentType = this.contentType();
|
||||
|
||||
if (contentType === helper.CONTENT_TYPE_TEXT)
|
||||
dom.text(contentElement, content);
|
||||
else if (contentType === helper.CONTENT_TYPE_HTML)
|
||||
dom.html(contentElement, content);
|
||||
|
||||
cache.content = content;
|
||||
}
|
||||
|
||||
dom.css(contentElement, helper.diffObj(contentStyle, cache.contentStyle));
|
||||
cache.contentStyle = contentStyle;
|
||||
|
||||
// update container element
|
||||
var style = this.style();
|
||||
|
||||
dom.css(element, helper.diffObj(style, cache.style));
|
||||
cache.style = style;
|
||||
this.notifyRendered();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Connector extends Component {
|
||||
constructor(props) {
|
||||
super();
|
||||
this.x = this.prop(props.x, 0, helper.toNumber);
|
||||
this.y = this.prop(props.y, 0, helper.toNumber);
|
||||
this.color = this.prop(Connector.COLOR_UNCONNECTED);
|
||||
this.zIndex = this.prop('auto');
|
||||
this.element = this.prop(null);
|
||||
this.parentElement = this.prop(null);
|
||||
this.cache = this.prop({});
|
||||
this.relations = this.prop([]);
|
||||
}
|
||||
|
||||
r() {
|
||||
return 16;
|
||||
}
|
||||
|
||||
contains(x, y, tolerance) {
|
||||
var dx = x - this.x();
|
||||
var dy = y - this.y();
|
||||
var r = this.r() + tolerance;
|
||||
|
||||
return (dx * dx + dy * dy <= r * r);
|
||||
}
|
||||
|
||||
style() {
|
||||
var r = this.r();
|
||||
var x = this.x() - r;
|
||||
var y = this.y() - r;
|
||||
var translate = 'translate(' + x + 'px, ' + y + 'px)';
|
||||
|
||||
return {
|
||||
backgroundColor: this.color(),
|
||||
border: '2px solid lightgray',
|
||||
borderRadius: '50%',
|
||||
boxSizing: 'border-box',
|
||||
height: r * 2 + 'px',
|
||||
msTransform: translate,
|
||||
opacity: 0.6,
|
||||
pointerEvents: 'none',
|
||||
position: 'absolute',
|
||||
transform: translate,
|
||||
webkitTransform: translate,
|
||||
width: r * 2 + 'px',
|
||||
zIndex: this.zIndex()
|
||||
};
|
||||
}
|
||||
|
||||
redraw() {
|
||||
var element = this.element();
|
||||
var parentElement = this.parentElement();
|
||||
|
||||
if (!parentElement && !element)
|
||||
return;
|
||||
|
||||
// add element
|
||||
if (parentElement && !element) {
|
||||
element = dom.el('<div>');
|
||||
this.element(element);
|
||||
dom.append(parentElement, element);
|
||||
this.redraw();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// remove element
|
||||
if (!parentElement && element) {
|
||||
dom.remove(element);
|
||||
this.element(null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var cache = this.cache();
|
||||
|
||||
// update element
|
||||
var style = this.style();
|
||||
|
||||
dom.css(element, helper.diffObj(style, cache.style));
|
||||
cache.style = style;
|
||||
this.notifyRendered();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Connector.COLOR_CONNECTED = 'lightgreen';
|
||||
Connector.COLOR_UNCONNECTED = 'pink';
|
||||
|
||||
export { Connector, Link as DrawingLink, Node as DrawingNode };
|
||||
Reference in New Issue
Block a user