ayajs
Version:
A flexible JavaScript library for building any kind of diagrams quickly and in a programmatic way
1,637 lines (1,399 loc) • 96.2 kB
JavaScript
class _Register {
static store = {};
static add(object) {
_Register.store[object.uuid] = object;
}
static find(uuid) {
return _Register.store[uuid];
}
static clear(uuid) {
delete _Register.store[uuid];
}
static findAllLink(component) {
var result = [];
Object.keys(_Register.store).map((id) => {
var obj = _Register.find(id);
if (obj.type == "link") {
if (component.uuid == obj.source.ref || component.uuid == obj.destination.ref)
result.push(obj);
}
});
return result;
}
static findAllComponents() {
var result = [];
Object.keys(_Register.store).map((id) => {
var obj = _Register.find(id);
if (obj.type != 'point' && obj.type != 'link') // it means the obj is a component
result.push(obj);
});
return result;
}
}
class _uuid
{
static generate()
{
return Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15);
}
}
/**
*
* @class Point
* @param {number} x
* @param {number} y
*
*/
class Point {
constructor(uuid, x = 0, y = 0, r = 5, config) {
this.ref = uuid ? uuid : null;
this.uuid = _uuid.generate();
this.x = x;
this.y = y;
this.r = r;
this.scale = 1;
this.events = {};
this.config = config;
this.type = "point";
this.c_svg = "";
this.svg = this.config.svg;
_Register.add(this);
}
addEvent(event, callback){
this.c_svg.addEventListener(event, callback);
this.events[event] = callback;
}
deleteEvent(event){
var callback = this.events[event];
this.c_svg.removeEventListener(event, callback);
delete this.events[event];
}
deleteAllEvents(){
Object.keys(this.events).map((event) => {
this.deleteEvent(event);
});
}
setScale(sc){
this.scale = sc;
}
getScale(){
return this.scale;
}
setStyles(o){
if (o.fill)
this.c_svg.setAttribute("fill", o.fill);
if (o.stroke)
this.c_svg.setAttribute("stroke", o.stroke);
if (o.strokewidth)
this.c_svg.setAttribute("stroke-width", o.strokewidth);
if (o.fillopacity)
this.c_svg.setAttribute("fill-opacity", o.fillopacity);
if (o.strokeopacity)
this.c_svg.setAttribute("stroke-opacity", o.strokeopacity);
if (o.strokedasharray)
this.c_svg.setAttribute("stroke-dasharray", o.strokedasharray);
if (o.strokedashoffset)
this.c_svg.setAttribute("stroke-dashoffset", o.strokedashoffset);
}
draw() {
var ns = "http://www.w3.org/2000/svg";
this.c_svg = document.createElementNS(ns, "circle");
this.c_svg.setAttribute("id", this.uuid);
this.c_svg.setAttribute("cx", this.x);
this.c_svg.setAttribute("cy", this.y);
this.c_svg.setAttribute("r", this.config.point.radius * this.scale);
// this.c_svg.setAttribute("class", "point");
this.c_svg.setAttribute("class", "hidden_point");
this.addEvent("mousedown", (e)=>{Events.mousedowncb(e, this.config);});
this.addEvent("mouseover", (e)=>{
this.r += 4;
this.c_svg.setAttribute("r", this.r);
});
this.addEvent("mouseleave", (e)=>{
this.r = this.config.point.radius;
this.c_svg.setAttribute("r", this.r);
});
this.svg.appendChild(this.c_svg);
}
removeFromDOM(){
this.svg.removeChild(this.c_svg);
}
shift(dx, dy) {
this.x += dx;
this.y += dy;
}
redraw() {
this.c_svg.setAttribute("cx", this.x);
this.c_svg.setAttribute("cy", this.y);
this.c_svg.setAttribute("r", this.config.point.radius * this.scale);
}
}
class Component
{
/**
* @param { Object } props
*/
constructor(props)
{
this.uuid = props.uuid ? props.uuid : _uuid.generate();
/**
* @description
* Represents the svg dom element created.
*
* @type { String }
*/
this.c_svg = "";
this.config = props.config;
/**
* @description
*
* @type { DomElement}
*/
this.svg = this.config.svg;
/**
* @description
* Dictionary object to record events and their respective callbacks associated with the form.
*
* @type { Object }
*/
this.events = {};
/**
* @description
* A table listing all children of the shape.
*
* @type { Array.<(Shape | null)>}
*/
this.children = [];
/**
* @description
* The offsetX represents the x-offset to be applied to the rectangle.
* to position it at {this. x + this.offSetX} on the x-axis.
*
@type { Number }
*/
this.offsetX = 0;
/**
* @description
* The offsetY represents the y-offset to be applied to the rectangle.
* to position it at {this. y + this.offSetX} on the y-axis.
*
@type { Number }
*/
this.offsetY = 0;
/**
* @description
* The ScaleX represents the scale to be applied to the size of the
* shape on the x-axis.
*
* @type { Number }
*/
this.scaleX = 1;
/**
* @description
* The ScaleX represents the scale to be applied to the size of the
* shape on the x-axis.
*
* @type { Number }
*/
this.scaleY = 1;
/**
* @description
* .This variable represents the value of the rotation angle to be
* applied to rotate the shape.
*
* @type { Number } - The angle is given in radian.
*/
this.angle = 0;
/**
* @description
* The center of rotation is defined by defining centerX.
*
* @type { Number } - centerX
*/
this.centerX = 0;
/**
* @description
* The center of rotation is defined by defining centerY.
*
* @type { Number } - centerY
*/
this.centerY = 0;
/**
* @description
* The variable c_points represents all the connection
* points of the form. These are the points from which one
* can establish a link with other forms having also these
* connection points.
*
* @type { Array<(Point | Null)> }
*/
this.c_points = [
new Point(this.uuid, 0, 0, 5, this.config),
new Point(this.uuid, 0, 0, 5, this.config),
new Point(this.uuid, 0, 0, 5, this.config),
new Point(this.uuid, 0, 0, 5, this.config),
];
/**
*
* @description
* The vertex variable represents the set of points from
* which we can resize the shape.
*
* @type { Array<(Point | Null)> }
*/
this.vertex = [
new Point(this.uuid, 0, 0, 5, this.config),
new Point(this.uuid, 0, 0, 5, this.config),
new Point(this.uuid, 0, 0, 5, this.config),
new Point(this.uuid, 0, 0, 5, this.config),
];
if (props.isSave)
_Register.add(this);
}
/**
* @description
* This method allows us to add an event to this shape.
* We record the event and the associated callback for easy removal after.
*
* @param { String } event - the event
* @param { Function } callback - {} This callback is either defined by the user when
* adding other custom events, or a callback already defined in event.js.
*/
addEvent(event, callback){
this.c_svg.addEventListener(event, callback);
this.events[event] = callback;
}
/**
* *@description
* This method allows us to delete a specific event passed as a string parameter.
*
* @param { String } event - The event.
*/
deleteEvent(event){
var callback = this.events[event];
this.c_svg.removeEventListener(event, callback);
delete this.events[event];
}
/**
* *@description
* This method allows us to delete all events defined on the c_svg property.
*/
deleteAllEvents(){
Object.keys(this.events).map((event) => {
this.deleteEvent(event);
});
}
setStyles(o){
if (o.fill)
this.c_svg.setAttribute("fill", o.fill);
if (o.stroke)
this.c_svg.setAttribute("stroke", o.stroke);
if (o.strokewidth)
this.c_svg.setAttribute("stroke-width", o.strokewidth);
if (o.fillopacity)
this.c_svg.setAttribute("fill-opacity", o.fillopacity);
if (o.strokeopacity)
this.c_svg.setAttribute("stroke-opacity", o.strokeopacity);
if (o.strokedasharray)
this.c_svg.setAttribute("stroke-dasharray", o.strokedasharray);
if (o.strokedashoffset)
this.c_svg.setAttribute("stroke-dashoffset", o.strokedashoffset);
}
makeHiddenCpoints(){
this.c_points.map((pt) => {
pt.c_svg.setAttribute("fill", "none");
});
}
makeVisibleCpoints(){
this.c_points.map((pt) => {
pt.c_svg.setAttribute("fill", "black");
});
}
makeHiddenVertex(){
this.vertex.map((vt) => {
vt.c_svg.setAttribute("fill", "none");
});
}
makeVisibleVertex(){
this.vertex.map((vt) => {
vt.c_svg.setAttribute("fill", "black");
});
}
removeChildren(){
this.children.map(({child}) => {
child.removeFromDOM();
});
}
removeFromDOM(){
this.c_points.map((pt)=>{
pt.removeFromDOM();
});
this.vertex.map((vt)=>{
vt.removeFromDOM();
});
this.children.map(({child}) => {
child.removeFromDOM();
});
this.svg.removeChild(this.c_svg);
}
move(dx,dy){
this.shape.x += dx;
this.shape.y += dy;
this.redraw();
var lk = _Register.findAllLink(this);
lk.map((link) => {
link.redraw();
});
}
/**
* @description
* We can build any shape by adding to a basic component a children shape.
*
* @param { (Rectangle | Lozenge | Triangle | Circle | Line | Text) } child - This shape ( @extend Component) is added
* as a child to a component with a shape.
* @param { Object } translate - { x:, y: } This object allows us to position the child relative to its parent.
* @param {Object } rotate - { x:, y: , angle: } This object allows us to apply a rotation of the child taking into
* account its relative position and the center of rotation.
*/
addChild(child, translate = null, rotate = null, drawing = true){
/* resizing and connection to child isn't possible */
child.vertex = [];
child.c_points = [];
if(translate != null){
child.offsetX = translate.x;
child.offsetY = translate.y;
}
if(rotate != null){
child.centerX = rotate.x;
child.centerY = rotate.y;
child.angle = rotate.angle;
}
if(drawing == true)
child.draw();
this.children.push({child});
}
remove(){
this.removeFromDOM();
_Register.clear(this.uuid);
}
}
/**
* @class Line
*/
class Line extends Component{
/**
* @param {String} id
* @param {Number} x
* @param {Number} y
* @param {Number} dest_x
* @param {Number} dest_y
*/
constructor(x=0, y=0, dest_x = x, dest_y = y, isdrawing = true, save = true, id = undefined, config){
super({uuid: id, isSave: save, config: config});
this.x = x;
this.y = y;
this.c1 = {
x: this.x,
y: this.y
};
this.c2 = {
x: this.x,
y: this.y
};
this.dest_x = dest_x;
this.dest_y = dest_y;
this.type = "line";
this.p = null;
this.path_is_set = false;
this.vertex = [
new Point(this.uuid, 0, 0, 3, config),
new Point(this.uuid, 0, 0, 3, config),
];
this.c_points = [];
if (isdrawing)
this.draw();
}
drawVertex(){
if(this.vertex.length == 0)
return;
this.vertex[0].x = this.x + this.offsetX;
this.vertex[0].y = this.y + this.offsetY;
this.vertex[1].x = (this.dest_x + this.offsetX) * this.scaleX;
this.vertex[1].y = (this.dest_y + this.offsetY) * this.scaleY;
}
drawConnector(){
if(this.c_points.length == 0)
return;
}
setPath(points = [{}]){
this.path_is_set = true;
this.p = "M "+ (this.x) + ","+ (this.y) + " ";
points.map((pt)=>{
this.p += "L " + (pt.x) + ","+ (pt.y) + " ";
});
this.p += "L " + (this.dest_x) + "," + (this.dest_y);
}
setPathCur(points = [{}]){
this.path_is_set = true;
var vertical = false;
var y_inverse = 1;
var x_inverse = 1;
var i_inverse = 1;
var i_xinverse = 1;
this.p = "M "+ (this.x) + ","+ (this.y) + " ";
if ((this.y < points[0].y && this.dest_y < points[1].y) ||
(this.y > points[0].y && this.dest_y > points[1].y))
i_inverse = -1;
if ((this.x < points[0].x && this.dest_x < points[1].x) ||
(this.x > points[0].x && this.dest_x > points[1].x))
i_xinverse = -1;
if (this.x > this.dest_x)
x_inverse = -1;
if (this.dest_y > this.y)
y_inverse = -1;
if (points[0].x == points[1].x)
vertical = true;
for (var i = 0; i < points.length; i++){
if (vertical){
this.p += "L " + (points[i].x - 5 * (i == 0) * x_inverse * i_xinverse) + "," + (points[i].y + 5 * i * y_inverse) + " ";
this.p += "C " + (points[i].x - 2.5 * (i == 0) * x_inverse * i_xinverse) + "," + (points[i].y + 2.5 * i * y_inverse) + " , " +
(points[i].x + 2.5 * i * x_inverse) + "," + (points[i].y - 2.5 * (i == 0) * y_inverse) + " , " +
(points[i].x + 5 * i * x_inverse) + "," + (points[i].y - 5 * (i == 0) * y_inverse);
}
else {
this.p += "L " + (points[i].x - 5 * i * x_inverse) + "," + (points[i].y + 5 * y_inverse * !i) + " ";
this.p += "C " + (points[i].x - 2.5 * i * x_inverse) + "," + (points[i].y + 2.5 * y_inverse * !i) + " , " +
(points[i].x + 2.5 * x_inverse * !i) + "," + (points[i].y -( 2.5 * y_inverse * i * i_inverse)) + " , " +
(points[i].x + 5 * x_inverse * !i) + "," + (points[i].y - 5 * y_inverse * i * i_inverse);
}
}
this.p += "L " + (this.dest_x) + "," + (this.dest_y) + " ";
}
draw(){
const ns = "http://www.w3.org/2000/svg";
this.c_svg = document.createElementNS(ns,'path');
if (this.p == null)
this.p = "M "+ (this.x + this.offsetX) + ","+ (this.y + this.offsetY) + " " + ((this.dest_x + this.offsetX ) * this.scaleX) + "," + ((this.dest_y + this.offsetY) * this.scaleY);
this.c_svg.setAttribute("id", this.uuid);
this.c_svg.setAttribute("d", this.p);
this.c_svg.setAttribute("fill", this.config.line.fill);
this.c_svg.setAttribute("stroke", this.config.shape.stroke);
this.c_svg.setAttributeNS(null, "stroke-width", this.config.line.strokeWidth);
this.svg.appendChild(this.c_svg);
this.drawVertex();
this.vertex.map((vertex) => {
vertex.draw();
});
this.children.map(({child}) =>{
child.draw();
});
this.addEvent("mousedown", (e) => {
Events.mousedowncb(e, this.config);
});
this.addEvent("mouseleave", (e) => {
Events.mouseleavecb(e, this.config);
});
this.addEvent("mouseover", (e) => {
Events.mouseovercb(e, this.config);
this.c_svg.setAttribute("class","move");
});
}
shift(dx,dy){
this.x += dx;
this.y += dy;
this.dest_x += dx;
this.dest_y += dy;
this.children.map(({child}, index) => {
child.shift(dx, dy);
});
}
redraw(){
this.drawVertex();
this.vertex.map((vertex) => {
vertex.redraw();
});
if (!this.path_is_set)
this.p = "M "+ (this.x + this.offsetX) + ","+ (this.y + this.offsetY) + " " + ((this.dest_x + this.offsetX ) * this.scaleX) + "," + ((this.dest_y + this.offsetY) * this.scaleY);
else
this.path_is_set = false;
this.c_svg.setAttribute("d", this.p);
this.children.map(({child}) => {
child.redraw();
});
}
calculateAngle(){
var angle = 0;
// we can ommit the slope
var pente = (this.dest_y - this.y) / (this.dest_x - this.x);
if(this.dest_x == this.x)
angle = Math.PI/2;
if(pente == 0)
angle = 0;
if( pente >= 0 && (this.x < this.dest_x && this.y < this.dest_y))
angle = Math.asin( (Math.sqrt( Math.pow((this.x - this.x), 2) +
Math.pow((this.y - this.dest_y), 2)) ) / ( Math.sqrt( Math.pow((this.x - this.dest_x), 2) +
Math.pow((this.y - this.dest_y), 2))) );
else if(pente >= 0 && (this.x > this.dest_x && this.y > this.dest_y))
angle = Math.PI + Math.asin( (Math.sqrt( Math.pow((this.x - this.x), 2) +
Math.pow((this.dest_y - this.y), 2)) ) / ( Math.sqrt( Math.pow((this.x - this.dest_x), 2) +
Math.pow((this.y - this.dest_y), 2))) );
else if( pente <= 0 && (this.x < this.dest_x && this.y > this.dest_y))
angle = 2 * Math.PI - Math.asin( (Math.sqrt( Math.pow((this.x - this.x), 2) + Math.pow((this.dest_y - this.y), 2)) ) / ( Math.sqrt( Math.pow((this.x - this.dest_x), 2) + Math.pow((this.y - this.dest_y), 2))) );
else if(pente <= 0 && (this.x > this.dest_x && this.y < this.dest_y))
angle = Math.PI - Math.asin( (Math.sqrt( Math.pow((this.x - this.x), 2) + Math.pow((this.dest_y - this.y), 2)) ) / ( Math.sqrt( Math.pow((this.x - this.dest_x), 2) + Math.pow((this.y - this.dest_y), 2))) );
return angle;
}
inclination(){
var angle = 0;
// we can ommit the slope
var slope = (this.dest_y - this.y) / (this.dest_x - this.x);
if(this.dest_x == this.x)
angle = Math.PI/2;
else if(slope == 0)
angle = 0;
else
angle = Math.asin( (Math.sqrt( Math.pow((this.x - this.x), 2) +
Math.pow((this.y - this.dest_y), 2)) ) / ( Math.sqrt( Math.pow((this.x - this.dest_x), 2) +
Math.pow((this.y - this.dest_y), 2))) );
if (slope > 0)
angle *= -1;
return angle;
}
resize(pos, dx, dy){
if(pos == 0){
this.x += dx;
this.y += dy;
if (this.children[0])
this.children[0].child.shift(dx, dy);
}
else {
this.dest_x += dx;
this.dest_y += dy;
if (this.children[1])
this.children[1].child.shift(dx, dy);
}
this.children.map(({child}, index) => {
child.redraw();
});
}
}
/**
* @class
*
* @description
*
*/
class Text{
constructor(x = 0, y = 0, text = "text", size = 0, dest_x, dest_y, isdrawing = true, config){
this.uuid = _uuid.generate();
this.x = x;
this.y = y;
this.config = config;
if (dest_x && dest_y){
this.dest_x = dest_x;
this.dest_y =dest_y;
}
else if (size)
this.size = size;
else
this.size = this.config.text.size;
this.text = text;
this.type = 'text';
this.svg = this.config.svg;
this.events = {};
this.offsetX = 0;
this.offsetY = 0;
this.centerX = 0;
this.centerY = 0;
this.angle = 0;
this.title = "";
this.c_svg = null;
this.textPath = null;
this.path_text = null;
if (isdrawing)
this.draw();
};
addEvent(event, callback){
this.c_svg.addEventListener(event, callback);
this.events[event] = callback;
}
deleteEvent(event){
var callback = this.events[event];
this.c_svg.removeEventListener(event, callback);
delete this.events[event];
}
/**
* *@description
* This method allows us to delete all events defined on the c_svg property.
*/
deleteAllEvents(){
Object.keys(this.events).map((event) => {
this.deleteEvent(event);
});
}
setStyles(o){
if (o.fill)
this.c_svg.setAttribute("fill", o.fill);
if (o.stroke)
this.c_svg.setAttribute("stroke", o.stroke);
if (o.strokewidth)
this.c_svg.setAttribute("stroke-width", o.strokewidth);
if (o.fillopacity)
this.c_svg.setAttribute("fill-opacity", o.fillopacity);
if (o.strokeopacity)
this.c_svg.setAttribute("stroke-opacity", o.strokeopacity);
if (o.strokedasharray)
this.c_svg.setAttribute("stroke-dasharray", o.strokedasharray);
if (o.strokedashoffset)
this.c_svg.setAttribute("stroke-dashoffset", o.strokedashoffset);
if (o.fontsize)
this.c_svg.setAttribute("font-size", o.fontsize);
if (o.fontfamily)
this.c_svg.setAttribute("font-family", o.fontfamily);
if (o.fontstyle)
this.c_svg.setAttribute("font-style", o.fontstyle);
if (o.wordspacing)
this.c_svg.setAttribute("word-spacing", o.wordspacing);
if (o.letterspacing)
this.c_svg.setAttribute("letter-spacing", o.letterspacing);
if (o.textlength)
this.c_svg.setAttributeNS(null, "textLength", o.textlength);
}
draw(){
const ns = "http://www.w3.org/2000/svg";
var p = "M " + this.x + "," + this.y + " ";
if (this.dest_x)
p += this.dest_x + "," + this.dest_y;
else
p += this.x + this.size + "," + this.y;
this.path_text = document.createElementNS(ns,'path');
this.path_text.setAttribute("id", _uuid.generate());
this.path_text.setAttribute("d", p);
this.svg.appendChild(this.path_text);
this.c_svg = document.createElementNS(ns, "text");
this.c_svg.setAttribute("id", _uuid.generate());
this.c_svg.setAttributeNS(null, "letter-spacing", this.config.text.letterspacing);
this.c_svg.setAttributeNS(null, "font-family", this.config.text.fontfamily);
this.c_svg.setAttributeNS(null, "font-size", this.config.text.fontsize);
this.c_svg.setAttributeNS(null, "font-style", this.config.text.fontstyle);
this.textPath = document.createElementNS(ns, "textPath");
this.textPath.setAttribute("id", _uuid.generate());
this.textPath.setAttribute("href", "#" + this.path_text.getAttribute("id"));
this.textPath.setAttribute("startOffset", this.config.text.startoffset);
this.textPath.setAttribute("text-anchor", this.config.text.textanchor);
this.textPath.textContent = this.text;
this.c_svg.appendChild(this.textPath);
this.svg.appendChild(this.c_svg);
this.title = document.createElementNS(ns, "title");
this.title.textContent = this.text;
this.c_svg.appendChild(this.title);
this.svg.appendChild(this.c_svg);
}
redraw(){
this.path_text.remove();
this.c_svg.remove();
this.textPath.remove();
this.draw();
}
shift(dx, dy){
this.x += dx;
this.y += dy;
if (this.dest_x){
this.dest_x += dx;
this.dest_y += dy;
}
}
removeFromDOM(){
this.path_text.remove();
this.c_svg.remove();
this.textPath.remove();
}
setText(text){
this.text = text;
}
}
/**
* @class Link
*/
class Link
{
constructor(src_id, dest_id, userconfig = {}, config)
{
this.config = config;
var obj = {};
this.uuid = _uuid.generate();
var src = _Register.find(src_id);
var dest = _Register.find(dest_id);
if (!src || !dest)
throw new Error("component is missing");
this.type = "link";
this.src_end_csvg = null;
this.dest_end_csvg = null;
if (userconfig.subtype)
this.subtype = userconfig.subtype;
else
this.subtype = config.link.type;
if (userconfig.end_start)
this.end_start = userconfig.end_start;
else
this.end_start = config.link.end_start;
if (userconfig.end_dest)
this.end_dest = userconfig.end_dest;
else
this.end_dest = config.link.end_dest;
this.altpath = userconfig.altpath ? true : false;
if (this.subtype != "broke")
obj = this.optimal(src, dest);
else
obj = this.breakline(src, dest);
/* reference on connexion points*/
this.source = src.c_points[obj.src];
this.destination = dest.c_points[obj.dest];
this.line = new Line(this.source.x,
this.source.y,
this.destination.x,
this.destination.y,
false,
false,
null,
config
);
if (this.subtype == "broke"){
this.line.c1.x = obj.c1.x;
this.line.c1.y = obj.c1.y;
this.line.c2.x = obj.c2.x;
this.line.c2.y = obj.c2.y;
this.line.setPath([this.line.c1, this.line.c2]);
}
this.line.draw();
this.line.setStyles({fill: "none"});
if (this.end_start)
this.addEnd(this.end_start, "source");
if (this.end_dest)
this.addEnd(this.end_dest, "destination");
this.text = null;
this.text_c_svg = null;
_Register.add(this);
}
addEnd(type, target){
var x, y, line_x, line_y, line_dest_x, line_dest_y, obj = {}, angle, c_svg = null;
var r = this.config.ends.circle.r, h = this.config.ends.tri.h;
const ns = "http://www.w3.org/2000/svg";
line_x = this.line.x;
line_y = this.line.y;
line_dest_x = this.line.dest_x;
line_dest_y = this.line.dest_y;
angle = this.line.inclination();
if (type != "triangle" && type != "circle")
return;
if (target == "source"){
x = this.line.x;
y = this.line.y;
if (this.src_end_csvg)
this.src_end_csvg.remove();
if (this.line.x != this.line.c1.x &&
this.line.y != this.line.c1.y){
line_dest_x = this.line.c1.x;
line_dest_y = this.line.c1.y;
}
}
else if (target == "destination"){
x = this.line.dest_x;
y = this.line.dest_y;
h = -h;
r = -r;
if (this.dest_end_csvg)
this.dest_end_csvg.remove();
if (this.line.x != this.line.c2.x &&
this.line.y != this.line.c2.y){
line_x = this.line.c2.x;
line_y = this.line.c2.y;
}
}
if (type == "triangle"){
var base, dxa, dya, dx, dy;
base = this.config.ends.tri.base;
if (line_y == line_dest_y){
if (line_x < line_dest_x){
obj.x1 = x;
obj.y1 = y;
obj.x2 = x + h;
obj.y2 = y - base / 2;
obj.x3 = x + h;
obj.y3 = y + base / 2;
}
else {
obj.x1 = x;
obj.y1 = y;
obj.x2 = x - h;
obj.y2 = y + base / 2;
obj.x3 = x - h;
obj.y3 = y - base / 2;
}
}
else if (line_x == line_dest_x){
if (line_y < line_dest_y){
obj.x1 = x;
obj.y1 = y;
obj.x2 = x + base / 2;
obj.y2 = y + h;
obj.x3 = x - base / 2;
obj.y3 = y + h;
}
else {
obj.x1 = x;
obj.y1 = y;
obj.x2 = x - base / 2;
obj.y2 = y - h;
obj.x3 = x + base / 2;
obj.y3 = y - h;
}
}
else {
dxa = h * Math.cos(angle);
dya = h * Math.sin(angle < 0 ? - angle : angle);
dy = (base / 2) * Math.cos(angle);
dx = (base / 2) * Math.sin(angle < 0 ? - angle : angle);
if (angle < 0){
if (line_x < line_dest_x){
obj.x1 = x;
obj.y1 = y;
obj.x2 = x + dxa + dx;
obj.y2 = y + dya - dy;
obj.x3 = x + dxa - dx;
obj.y3 = y + dya + dy;
}
else if (line_x > line_dest_x){
obj.x1 = x;
obj.y1 = y;
obj.x2 = x - dxa + dx;
obj.y2 = y - dya - dy;
obj.x3 = x - dxa - dx;
obj.y3 = y - dya + dy;
}
}
else {
if (line_x < line_dest_x){
obj.x1 = x;
obj.y1 = y;
obj.x2 = x + dxa - dx;
obj.y2 = y - dya - dy;
obj.x3 = x + dxa + dx;
obj.y3 = y - dya + dy;
}
else if (line_x > line_dest_x){
obj.x1 = x;
obj.y1 = y;
obj.x2 = x - dxa + dx;
obj.y2 = y + dya + dy;
obj.x3 = x - dxa - dx;
obj.y3 = y + dya - dy;
}
}
}
c_svg = document.createElementNS(ns, "path");
var p = "M " + obj.x1 + "," + obj.y1 + " " + "L " + obj.x2 + "," + obj.y2 + " " + "L " + obj.x3 + "," + obj.y3 + " Z";
c_svg.setAttribute("d", p);
c_svg.setAttribute("id", _uuid.generate());
c_svg.setAttribute("fill", this.config.ends.tri.fill);
c_svg.setAttribute("stroke", this.config.ends.tri.stroke);
c_svg.setAttribute("stroke-width", this.config.ends.tri.strokeWidth);
}
else if (type == "circle"){
if (line_y == line_dest_y){
if (line_x < line_dest_x){
obj.x = x + r;
obj.y = y;
}
else {
obj.x = x - r;
obj.y = y;
}
}
else if (line_x == line_dest_x){
if (line_y < line_dest_y){
obj.x = x;
obj.y = y + r;
}
else {
obj.x = x;
obj.y = y - r;
}
}
else {
var slope = (line_dest_y - line_y) / (line_dest_x - line_x);
if (angle < 0){
if (line_x < line_dest_x){
obj.x = x + r * Math.cos(angle);
obj.y = slope * obj.x + (line_y - slope * line_x);
}
else if (line_x > line_dest_x){
obj.x = x - r * Math.cos(angle);
obj.y = slope * obj.x + (line_y - slope * line_x);
}
}
else {
if (line_x < line_dest_x){
obj.x = x + r * Math.cos(angle);
obj.y = slope * obj.x + (line_y - slope * line_x);
}
else if (line_x > line_dest_x){
obj.x = x - r * Math.cos(angle);
obj.y = slope * obj.x + (line_y - slope * line_x);
}
}
}
c_svg = document.createElementNS(ns, "circle");
c_svg.setAttribute("cx", obj.x);
c_svg.setAttribute("cy", obj.y);
c_svg.setAttribute("r", this.config.ends.circle.r);
c_svg.setAttribute("fill", this.config.ends.circle.fill);
c_svg.setAttribute("stroke", this.config.ends.circle.stroke);
c_svg.setAttribute("stroke-width", this.config.ends.circle.strokeWidth);
c_svg.setAttribute("id", _uuid.generate());
}
this.config.svg.appendChild(c_svg);
if (target == "source")
this.src_end_csvg = c_svg;
if (target == "destination")
this.dest_end_csvg = c_svg;
}
addText(text, position){
if (!text)
return;
this.text = text;
if (position != "top" && position != "bottom" && position != "middle")
position = "top";
this.position = position;
if (!this.text_c_svg){
var coordonate = this.textCoordonate();
this.text_c_svg = new Text(_uuid.generate(), coordonate.x, coordonate.y,
this.text, 0, coordonate.dest_x, coordonate.dest_y);
this.text_c_svg.draw();
}
else {
var coordonate = this.textCoordonate();
this.text_c_svg.x = coordonate.x;
this.text_c_svg.y = coordonate.y;
this.text_c_svg.dest_x = coordonate.dest_x;
this.text_c_svg.dest_y = coordonate.dest_y;
this.text_c_svg.text = this.text;
this.text_c_svg.redraw();
}
}
textCoordonate(){
var c = {
x: 0,
y: 0,
dest_x: 0,
dest_y: 0
};
if (this.subtype == "broke"){
if (this.line.c2.y == this.line.dest_y && this.line.dest_x > this.line.c2.x){
//path c2 dest
c.x = this.line.c2.x;
c.y = this.line.c2.y;
c.dest_x = this.line.dest_x;
c.dest_y = this.line.dest_y;
}
else if (this.line.c2.y == this.line.dest_y && this.line.dest_x < this.line.c2.x){
//path c2 dest
c.x = this.line.dest_x;
c.y = this.line.dest_y;
c.dest_x = this.line.c2.x;
c.dest_y = this.line.c2.y;
}
else if (this.line.c1.x < this.line.c2.x){
// path c1 c2
c.x = this.line.c1.x;
c.y = this.line.c1.y;
c.dest_x = this.line.c2.x;
c.dest_y = this.line.c2.y;
}
else {
//path c2 c1
c.x = this.line.c2.x;
c.y = this.line.c2.y;
c.dest_x = this.line.c1.x;
c.dest_y = this.line.c1.y;
}
if (this.position == "top"){
c.y -= 10;
c.dest_y -= 10;
}
else if (this.position == "bottom"){
c.y += 10 + 12;
c.dest_y += 10 + 12; // 12 for text height
}
else {
c.y += 15/2 - 3;
c.dest_y += 15/2 - 3;
}
}
return c;
}
removeFromDOM(){
this.line.removeFromDOM();
if (this.src_end_csvg)
this.config.svg.removeChild(this.src_end_csvg);
if (this.dest_end_csvg)
this.config.svg.removeChild(this.dest_end_csvg);
if (this.text)
this.text_c_svg.removeFromDOM();
var lk = _Register.find(this.uuid);
_Register.clear(lk.uuid);
}
breakline(source, destination){
var obj = {
src: 1,
dest: 3,
c1: {},
c2: {},
};
var inflexion = "horizontal";
if ((source.c_points[1].y == destination.c_points[3].y && (obj.src = 1) && (obj.dest = 3)) ||
(source.c_points[3].y == destination.c_points[1].y && (obj.src = 3) && (obj.dest = 1)) ||
(source.c_points[0].x == destination.c_points[2].x && (obj.src = 0) && (obj.dest = 2)) ||
(source.c_points[2].x == destination.c_points[0].x && (obj.src = 2) && (obj.dest = 0)))
inflexion = false;
else {
if (source.c_points[obj.src].x > destination.c_points[obj.dest].x){
obj.src = 3;
obj.dest = 1;
}
if (source.c_points[obj.src].y > destination.c_points[obj.dest].y){
if ((Math.abs(destination.c_points[obj.dest].x - source.c_points[obj.src].x) <= 2 * this.config.ends.minspace)){
obj.src = 0;
obj.dest = 2;
inflexion = "vertical";
}
}
else {
if (Math.abs(destination.c_points[obj.dest].x - source.c_points[obj.src].x) <= 2 * this.config.ends.minspace){
obj.src = 2;
obj.dest = 0;
inflexion = "vertical";
}
}
}
if (this.altpath){
if ((obj.src == 1 && obj.dest == 3) ||
(obj.src == 3 && obj.dest == 1)){
if (source.c_points[obj.src].y < destination.c_points[obj.dest].y){
obj.src = 2;
obj.dest = 2;
}
else {
obj.src = 0;
obj.dest = 0;
}
}
else if ((obj.src == 0 && obj.dest == 2) ||
(obj.src == 2 && obj.dest == 0)){
obj.src = 3;
obj.dest = 3;
}
inflexion = "altpath";
}
if (inflexion == "vertical"){
obj.c1.x = source.c_points[obj.src].x;
obj.c1.y = (source.c_points[obj.src].y + destination.c_points[obj.dest].y) / 2;
obj.c2.x = destination.c_points[obj.dest].x;
obj.c2.y = (source.c_points[obj.src].y + destination.c_points[obj.dest].y) / 2;
}
else if (inflexion == "horizontal"){
obj.c1.x = (source.c_points[obj.src].x + destination.c_points[obj.dest].x) / 2;
obj.c1.y = source.c_points[obj.src].y;
obj.c2.x = (source.c_points[obj.src].x + destination.c_points[obj.dest].x) / 2;
obj.c2.y = destination.c_points[obj.dest].y;
}
else if (inflexion == "altpath"){
if(obj.src == 0){
obj.c1.x = source.c_points[obj.src].x;
obj.c1.y = destination.c_points[obj.dest].y - 2 * this.config.ends.minspace;
obj.c2.x = destination.c_points[obj.dest].x;
obj.c2.y = destination.c_points[obj.dest].y - 2 * this.config.ends.minspace;
}
else if (obj.src == 2){
obj.c1.x = source.c_points[obj.src].x;
obj.c1.y = destination.c_points[obj.dest].y + 2 * this.config.ends.minspace;
obj.c2.x = destination.c_points[obj.dest].x;
obj.c2.y = destination.c_points[obj.dest].y + 2 * this.config.ends.minspace;
}
else {
obj.c1.x = source.c_points[obj.src].x - 3 * this.config.ends.minspace;
obj.c1.y = source.c_points[obj.src].y;
obj.c2.x = source.c_points[obj.src].x - 3 * this.config.ends.minspace;
obj.c2.y = destination.c_points[obj.dest].y;
}
}
else {
obj.c1.x = source.c_points[obj.src].x;
obj.c1.y = source.c_points[obj.src].y;
obj.c2.x = source.c_points[obj.src].x;
obj.c2.y = source.c_points[obj.src].y;
}
return obj;
}
redraw(){
var source = _Register.find(this.source.ref);
var destination = _Register.find(this.destination.ref);
var obj = {};
if (this.subtype != "broke")
obj = this.optimal(source, destination);
else
obj = this.breakline(source, destination);
/* reference on connexion points*/
this.source = source.c_points[obj.src];
this.destination = destination.c_points[obj.dest];
this.line.x = this.source.x;
this.line.y = this.source.y;
this.line.dest_x = this.destination.x;
this.line.dest_y = this.destination.y;
if (this.subtype == "broke"){
this.line.c1.x = obj.c1.x;
this.line.c1.y = obj.c1.y;
this.line.c2.x = obj.c2.x;
this.line.c2.y = obj.c2.y;
if (Math.abs(this.line.y - this.line.dest_y) > 9 &&
(obj.c1.x != obj.c2.x || obj.c1.y != obj.c2.y))
this.line.setPathCur([this.line.c1, this.line.c2]);
else
this.line.setPath([this.line.c1, this.line.c2]);
}
this.line.redraw();
if (this.end_start)
this.addEnd(this.end_start, "source");
if (this.end_dest)
this.addEnd(this.end_dest, "destination");
if (this.text != undefined){ // text must be different of ""
var c = this.textCoordonate();
this.text_c_svg.x = c.x;
this.text_c_svg.y = c.y;
this.text_c_svg.dest_x = c.dest_x;
this.text_c_svg.dest_y = c.dest_y;
this.text_c_svg.redraw();
}
}
optimal(src, dest){
var obj = {}, dmin;
var i,j, d;
for (i = 0; i < 4; i++){
for (j = 0; j < 4; j++){
d = (src.c_points[i].x - dest.c_points[j].x) *
(src.c_points[i].x - dest.c_points[j].x) +
(src.c_points[i].y - dest.c_points[j].y) *
(src.c_points[i].y - dest.c_points[j].y);
if (!dmin ||
(d < dmin)){
obj.src = i;
obj.dest = j;
dmin = d;
}
}
}
return obj;
}
}
class Events {
static source = null;
static line = null;
static current_vertex;
static current_cpoint;
static state = null;
static dx = null;
static dy = null;
static mousedowncb(e, _config) {
var id, cp = null;
id = e.srcElement.id;
Events.dx = e.offsetX;
Events.dy = e.offsetY;
cp = _Register.find(id);
// Only the points have the ref property to refer to the shape that instantiates them.
// In source we have the component instance created.
if (cp)
Events.source = cp != undefined && cp.ref != undefined ? _Register.find(cp.ref) : cp;
if(cp == undefined)
return;
// The displacement of the shape is triggered
// when the mousedown is done on the shape, and neither on the point nor the svg.
if ((cp != undefined && cp.ref == undefined))
Events.state = "moving";
else {
// Resizing is triggered when the mousedown takes place on one of the summits.
if ((Events.source.vertex != undefined) && (Events.current_vertex = Events.source.vertex.indexOf(cp)) >= 0) {
Events.state = "resizing";
}
else {
/**
* If the mousedown was not done on the svg, neither on a top nor on the shape,
* then it was certainly done on a connection point.
* In this case, we start tracing a link.
*/
Events.state = "drawing_link";
Events.current_cpoint = {x: cp.x, y: cp.y};
Events.line = document.createElementNS("http://www.w3.org/2000/svg", "line");
Events.line.setAttribute("x1", Events.current_cpoint.x);
Events.line.setAttribute("y1", Events.current_cpoint.y);
Events.line.setAttribute("x2", Events.current_cpoint.x);
Events.line.setAttribute("y2", Events.current_cpoint.y);
Events.line.setAttribute("stroke", "black");
Events.line.setAttribute("id", _uuid.generate());
_config.svg.appendChild(Events.line);
}
}
return Events.state;
}
static mousemovecb(e, _config) {
var deltaX = e.offsetX - Events.dx;
var deltaY = e.offsetY - Events.dy;
Events.dx = e.offsetX;
Events.dy = e.offsetY;
var lk;
if (Events.state == "moving") {
var src, dest;
lk = _Register.findAllLink(Events.source);
// Ensure Events.source is a component
if(Events.source != undefined){
lk.map((link) => {
Events.source.c_points.map( (point) => {
if(point == link.source)
src = point;
else if(point == link.destination)
dest = point;
});
if(dest) {
link.line.dest_x = dest.x;
link.line.dest_y = dest.y;
link.redraw();
}
else {
link.line.x = src.x;
link.line.y = src.y;
link.redraw();
}
});
Events.source.shift(deltaX, deltaY);
Events.source.redraw();
lk.map((link) => {
link.redraw();
});
}
}
else if (Events.state == "drawing_link") {
Events.line.setAttribute("x2", e.clientX);
Events.line.setAttribute("y2", e.clientY);
}
else if (Events.state == "resizing") {
lk = _Register.findAllLink(Events.source);
Events.source.resize(Events.current_vertex, deltaX, deltaY);
Events.source.redraw();
lk.map((link) => {
link.redraw();
});
}
}
static mouseupcb(e, _config) {
var id = e.srcElement.id;
if (Events.state == "drawing_link") {
var pnt = _Register.find(id);
if (pnt && pnt.ref == Events.source.uuid){
Events.line.remove();
return;
}
if (pnt && pnt.ref) {
Events.line.setAttribute("x2", pnt.x);
Events.line.setAttribute("y2", pnt.y);
var destination = _Register.find(pnt.ref);
new Link(
Events.source.uuid,
destination.uuid,
{},
_config
);
}
Events.line.remove();
Events.line = null;
Events.source = null;
}
Events.state = "";
}
static mouseovercb(e){
var id = e.srcElement.id;
var local_cp = _Register.find(id);
if(local_cp == undefined)
return;
if(local_cp.type == "line"){
local_cp.c_svg.setAttribute("class", "move");
local_cp.vertex.map((vt) =>{
vt.c_svg.setAttribute("class", "default");
});
}
else {
if(local_cp != undefined){
local_cp.c_svg.setAttribute("class", "move");
local_cp.c_points.map( (point) => {
point.c_svg.setAttribute("class", "show_point");
});
local_cp.vertex.map( (vertex, index) => {
vertex.c_svg.setAttribute("class", "show_point");
if(index == 0)
vertex.c_svg.setAttribute("class", "resize_left_top");
else if(index == 1)
vertex.c_svg.setAttribute("class", "resize_right_top");
else if(index == 2)
vertex.c_svg.setAttribute("class", "resize_right_bottom");
else if(index == 3)
vertex.c_svg.setAttribute("class", "resize_left_bottom");
});
}
}
}
static mouseleavecb(e, _config){
_Register.findAllComponents();
// components.map( async (component) => {
// component.c_points.map( (point) => {
// point.c_svg.setAttribute("class", "hidden_point");
// });
// component.vertex.map( (vertex) => {
// vertex.c_svg.setAttribute("class", "hidden_point");
// });
// });
}
}
/**
* @class Circle
*/
class Circle extends Component{
/**
*
* @param {string} uuid
* @param {number} x
* @param {number} y
* @param {number} r
*/
constructor(x = 0, y = 0, r = 3, isdrawing = true, save = true, id = undefined, config){
super({uuid: id, isSave: save, config: config});
this.x = x;
this.y = y;
this.r = r;
this.box = "";
this.c_svg = "";
this.type = "circle";
this.scale = 1;
this.angle = 0;
if (isdrawing)
this.draw();
}
drawVertex(){
if(this.vertex.length == 0)
return;
this.vertex[0].x = this.x + this.offsetX - this.r * this.scale;
this.vertex[0].y = this.y + this.offsetY - this.r * this.scale;
this.vertex[1].x = this.x + this.offsetX + this.r * this.scale;
this.vertex[1].y = this.y + this.offsetY - this.r * this.scale;
this.vertex[2].x = this.x + this.offsetX + this.r * this.scale;
this.vertex[2].y = this.y + this.offsetY + this.r * this.scale;
this.vertex[3].x = this.x + this.offsetX - this.r * this.scale;
this.vertex[3].y = this.y + this.offsetY + this.r * thi