UNPKG

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
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