1 (function(global){ // BEGIN CLOSURE 2 3 var Joint = global.Joint, 4 Element = Joint.dia.Element, 5 point = Joint.point; 6 7 /** 8 * @name Joint.dia.uml 9 * @namespace Holds functionality related to UML diagrams. 10 */ 11 var uml = Joint.dia.uml = {}; 12 13 Joint.arrows.aggregation = function(size){ 14 return { 15 path: ["M","7","0","L","0","5","L","-7","0", "L", "0", "-5", "z"], 16 dx: 9, 17 dy: 9, 18 attrs: { 19 stroke: "black", 20 "stroke-width": 2.0, 21 fill: "black" 22 } 23 }; 24 }; 25 26 /** 27 * Predefined aggregation arrow for Class diagram. 28 * @name aggregationArrow 29 * @memberOf Joint.dia.uml 30 * @example c1.joint(c2, Joint.dia.uml.aggregationArrow); 31 */ 32 uml.aggregationArrow = { 33 endArrow: { type: "aggregation" }, 34 startArrow: {type: "none"}, 35 attrs: { "stroke-dasharray": "none" } 36 }; 37 /** 38 * Predefined dependency arrow for Class diagram. 39 * @name dependencyArrow 40 * @memberOf Joint.dia.uml 41 * @example c1.joint(c2, Joint.dia.uml.dependencyArrow); 42 */ 43 uml.dependencyArrow = { 44 endArrow: { type: "basic", size: 5 }, 45 startArrow: {type: "none"}, 46 attrs: { "stroke-dasharray": "none" } 47 }; 48 /** 49 * Predefined generalization arrow for Class diagram. 50 * @name generalizationArrow 51 * @memberOf Joint.dia.uml 52 * @example c1.joint(c2, Joint.dia.uml.generalizationArrow); 53 */ 54 uml.generalizationArrow = { 55 endArrow: { type: "basic", size: 10, attrs: {fill: "white"} }, 56 startArrow: {type: "none"}, 57 attrs: { "stroke-dasharray": "none" } 58 }; 59 /** 60 * Predefined arrow for StateChart. 61 * @name Joint.dia.uml.arrow 62 * @memberOf Joint.dia.uml 63 * @example s1.joint(s2, Joint.dia.uml.arrow); 64 */ 65 uml.arrow = { 66 startArrow: {type: "none"}, 67 endArrow: {type: "basic", size: 5}, 68 attrs: {"stroke-dasharray": "none"} 69 }; 70 71 /** 72 * UML StateChart state. 73 * @name State.create 74 * @methodOf Joint.dia.uml 75 * @param {Object} properties 76 * @param {Object} properties.rect Bounding box of the State (e.g. {x: 50, y: 100, width: 100, height: 80}). 77 * @param {Number} [properties.radius] Radius of the corners of the state rectangle. 78 * @param {String} [properties.label] The name of the state. 79 * @param {Number} [properties.labelOffsetX] Offset in x-axis of the label from the state rectangle origin. 80 * @param {Number} [properties.labelOffsetY] Offset in y-axis of the label from the state rectangle origin. 81 * @param {Number} [properties.swimlaneOffsetY] Offset in y-axis of the swimlane shown after the state label. 82 * @param {Object} [properties.attrs] SVG attributes of the appearance of the state. 83 * @param {Object} [properties.actions] Actions of the state. 84 * @param {String} [properties.actions.entry] Entry action of the state. 85 * @param {String} [properties.actions.exit] Exit action of the state. 86 * @param {array} [properties.actions.inner] Actions of the state (e.g. ["Evt1", "Action1()", "Evt2", "Action2()"]) 87 * @param {Number} [properties.actionsOffsetX] Offset in x-axis of the actions. 88 * @param {Number} [properties.actionsOffsetY] Offset in y-axis of the actions. 89 * @example 90 var s1 = Joint.dia.uml.State.create({ 91 rect: {x: 120, y: 70, width: 100, height: 60}, 92 label: "state 1", 93 attrs: { 94 fill: "90-#000-green:1-#fff" 95 }, 96 actions: { 97 entry: "init()", 98 exit: "destroy()", 99 inner: ["Evt1", "foo()", "Evt2", "bar()"] 100 } 101 }); 102 */ 103 uml.State = Element.extend({ 104 object: "State", 105 module: "uml", 106 init: function(properties){ 107 // options 108 var p = Joint.DeepSupplement(this.properties, properties, { 109 radius: 15, 110 attrs: { fill: 'white' }, 111 label: '', 112 labelOffsetX: 20, 113 labelOffsetY: 5, 114 swimlaneOffsetY: 18, 115 actions: { 116 entry: null, 117 exit: null, 118 inner: [] 119 }, 120 actionsOffsetX: 5, 121 actionsOffsetY: 5 122 }); 123 // wrapper 124 this.setWrapper(this.paper.rect(p.rect.x, p.rect.y, p.rect.width, p.rect.height, p.radius).attr(p.attrs)); 125 // inner 126 this.addInner(this.getLabelElement()); 127 this.addInner(this.getSwimlaneElement()); 128 this.addInner(this.getActionsElement()); 129 }, 130 getLabelElement: function(){ 131 var 132 p = this.properties, 133 bb = this.wrapper.getBBox(), 134 t = this.paper.text(bb.x, bb.y, p.label).attr(p.labelAttrs || {}), 135 tbb = t.getBBox(); 136 t.translate(bb.x - tbb.x + p.labelOffsetX, 137 bb.y - tbb.y + p.labelOffsetY); 138 return t; 139 }, 140 getSwimlaneElement: function(){ 141 var bb = this.wrapper.getBBox(), p = this.properties; 142 return this.paper.path(["M", bb.x, bb.y + p.labelOffsetY + p.swimlaneOffsetY, "L", bb.x + bb.width, bb.y + p.labelOffsetY + p.swimlaneOffsetY].join(" ")); 143 }, 144 getActionsElement: function(){ 145 // collect all actions 146 var p = this.properties; 147 var str = (p.actions.entry) ? "entry/ " + p.actions.entry + "\n" : ""; 148 str += (p.actions.exit) ? "exit/ " + p.actions.exit + "\n" : ""; 149 var l = p.actions.inner.length; 150 for (var i = 0; i < l; i += 2){ 151 str += p.actions.inner[i] + "/ " + p.actions.inner[i+1] + "\n"; 152 } 153 // trim 154 str = str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 155 156 // draw text with actions 157 var 158 bb = this.wrapper.getBBox(), 159 t = this.paper.text(bb.x + p.actionsOffsetX, bb.y + p.labelOffsetY + p.swimlaneOffsetY + p.actionsOffsetY, str), 160 tbb = t.getBBox(); 161 t.attr("text-anchor", "start"); 162 t.translate(0, tbb.height/2); // tune the y position 163 return t; 164 }, 165 zoom: function(){ 166 this.wrapper.attr("r", this.properties.radius); // set wrapper's radius back to its initial value (it deformates after scaling) 167 this.shadow && this.shadow.attr("r", this.properties.radius); // update shadow as well if there is one 168 this.inner[0].remove(); // label 169 this.inner[1].remove(); // swimlane 170 this.inner[2].remove(); // actions 171 this.inner[0] = this.getLabelElement(); 172 this.inner[1] = this.getSwimlaneElement(); 173 this.inner[2] = this.getActionsElement(); 174 } 175 }); 176 177 178 /** 179 * UML StateChart start state. 180 * @name StartState.create 181 * @methodOf Joint.dia.uml 182 * @param {Object} properties 183 * @param {Object} properties.position Position of the start state (e.g. {x: 50, y: 100}). 184 * @param {Number} [properties.radius] Radius of the circle of the start state. 185 * @param {Object} [properties.attrs] SVG attributes of the appearance of the start state. 186 * @example 187 var s0 = Joint.dia.uml.StartState.create({ 188 position: {x: 120, y: 70}, 189 radius: 15, 190 attrs: { 191 stroke: "blue", 192 fill: "yellow" 193 } 194 }); 195 */ 196 uml.StartState = Element.extend({ 197 object: "StartState", 198 module: "uml", 199 init: function(properties){ 200 // options 201 var p = Joint.DeepSupplement(this.properties, properties, { 202 position: point(0,0), 203 radius: 10, 204 attrs: { fill: 'black' } 205 }); 206 // wrapper 207 this.setWrapper(this.paper.circle(p.position.x, p.position.y, p.radius).attr(p.attrs)); 208 } 209 }); 210 211 212 /** 213 * UML StateChart end state. 214 * @name EndState.create 215 * @methodOf Joint.dia.uml 216 * @param {Object} properties 217 * @param {Object} properties.position Position of the end state (e.g. {x: 50, y: 100}). 218 * @param {Number} [properties.radius] Radius of the circle of the end state. 219 * @param {Number} [properties.innerRadius] Radius of the inner circle of the end state. 220 * @param {Object} [properties.attrs] SVG attributes of the appearance of the end state. 221 * @param {Object} [properties.innerAttrs] SVG attributes of the appearance of the inner circle of the end state. 222 * @example 223 var s0 = Joint.dia.uml.EndState.create({ 224 position: {x: 120, y: 70}, 225 radius: 15, 226 innerRadius: 8, 227 attrs: { 228 stroke: "blue", 229 fill: "yellow" 230 }, 231 innerAttrs: { 232 fill: "red" 233 } 234 }); 235 */ 236 uml.EndState = Element.extend({ 237 object: "EndState", 238 module: "uml", 239 init: function(properties){ 240 // options 241 var p = Joint.DeepSupplement(this.properties, properties, { 242 position: point(0,0), 243 radius: 10, 244 innerRadius: (properties.radius && properties.radius / 2) || 5, 245 attrs: { fill: 'white' }, 246 innerAttrs: { fill: 'black' } 247 }); 248 // wrapper 249 this.setWrapper(this.paper.circle(p.position.x, p.position.y, p.radius).attr(p.attrs)); 250 // inner 251 this.addInner(this.paper.circle(p.position.x, p.position.y, p.innerRadius).attr(p.innerAttrs)); 252 }, 253 zoom: function(){ 254 this.inner[0].scale.apply(this.inner[0], arguments); 255 } 256 }); 257 258 259 /** 260 * UML StateChart class. 261 * @name Class.create 262 * @methodOf Joint.dia.uml 263 * @param {Object} properties 264 * @param {Object} properties.rect Bounding box of the Class (e.g. {x: 50, y: 100, width: 100, height: 80}). 265 * @param {String} [properties.label] The name of the class. 266 * @param {Number} [properties.labelOffsetX] Offset in x-axis of the label from the class rectangle origin. 267 * @param {Number} [properties.labelOffsetY] Offset in y-axis of the label from the class rectangle origin. 268 * @param {Number} [properties.swimlane1OffsetY] Offset in y-axis of the swimlane shown after the class label. 269 * @param {Number} [properties.swimlane2OffsetY] Offset in y-axis of the swimlane shown after the class attributes. 270 * @param {Object} [properties.attrs] SVG attributes of the appearance of the state. 271 * @param {array} [properties.attributes] Attributes of the class. 272 * @param {array} [properties.methods] Methods of the class. 273 * @param {Number} [properties.attributesOffsetX] Offset in x-axis of the attributes. 274 * @param {Number} [properties.attributesOffsetY] Offset in y-axis of the attributes. 275 * @param {Number} [properties.methodsOffsetX] Offset in x-axis of the methods. 276 * @param {Number} [properties.methodsOffsetY] Offset in y-axis of the methods. 277 * @example 278 var c1 = Joint.dia.uml.Class.create({ 279 rect: {x: 120, y: 70, width: 120, height: 80}, 280 label: "MyClass", 281 attrs: { 282 fill: "90-#000-yellow:1-#fff" 283 }, 284 attributes: ["-position"], 285 methods: ["+createIterator()"] 286 }); 287 */ 288 uml.Class = Element.extend({ 289 object: "Class", 290 module: "uml", 291 init: function(properties){ 292 var p = Joint.DeepSupplement(this.properties, properties, { 293 attrs: { fill: 'white' }, 294 label: '', 295 labelOffsetX: 20, 296 labelOffsetY: 5, 297 swimlane1OffsetY: 18, 298 swimlane2OffsetY: 18, 299 attributes: [], 300 attributesOffsetX: 5, 301 attributesOffsetY: 5, 302 methods: [], 303 methodsOffsetX: 5, 304 methodsOffsetY: 5 305 }); 306 // wrapper 307 this.setWrapper(this.paper.rect(p.rect.x, p.rect.y, p.rect.width, p.rect.height).attr(p.attrs)); 308 // inner 309 this.addInner(this.getLabelElement()); 310 this.addInner(this.getSwimlane1Element()); 311 this.addInner(this.getAttributesElement()); 312 this.addInner(this.getSwimlane2Element()); 313 this.addInner(this.getMethodsElement()); 314 }, 315 getLabelElement: function(){ 316 var 317 p = this.properties, 318 bb = this.wrapper.getBBox(), 319 t = this.paper.text(bb.x, bb.y, p.label).attr(p.labelAttrs || {}), 320 tbb = t.getBBox(); 321 t.translate(bb.x - tbb.x + p.labelOffsetX, bb.y - tbb.y + p.labelOffsetY); 322 return t; 323 }, 324 getSwimlane1Element: function(){ 325 var bb = this.wrapper.getBBox(), p = this.properties; 326 return this.paper.path(["M", bb.x, bb.y + p.labelOffsetY + p.swimlane1OffsetY, "L", bb.x + bb.width, bb.y + p.labelOffsetY + p.swimlane1OffsetY].join(" ")); 327 }, 328 getSwimlane2Element: function(){ 329 var 330 p = this.properties, 331 bb = this.wrapper.getBBox(), 332 bbAtrrs = this.inner[2].getBBox(); // attributes 333 return this.paper.path(["M", bb.x, bb.y + p.labelOffsetY + p.swimlane1OffsetY + bbAtrrs.height + p.swimlane2OffsetY, "L", bb.x + bb.width, bb.y + p.labelOffsetY + p.swimlane1OffsetY + bbAtrrs.height + p.swimlane2OffsetY].join(" ")); 334 }, 335 getAttributesElement: function(){ 336 var str = " ", p = this.properties; 337 for (var i = 0, len = p.attributes.length; i < len; i++){ 338 str += p.attributes[i] + "\n"; 339 } 340 // trim 341 str = str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 342 343 var 344 bb = this.wrapper.getBBox(), 345 t = this.paper.text(bb.x + p.attributesOffsetX, bb.y + p.labelOffsetY + p.swimlane1OffsetY + p.attributesOffsetY, str), 346 tbb = t.getBBox(); 347 t.attr("text-anchor", "start"); 348 t.translate(0, tbb.height/2); // tune the y-position 349 return t; 350 }, 351 getMethodsElement: function(){ 352 var str = " ", p = this.properties; 353 for (var i = 0, len = p.methods.length; i < len; i++){ 354 str += p.methods[i] + "\n"; 355 } 356 // trim 357 str = str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 358 var 359 bb = this.wrapper.getBBox(), 360 bbAtrrs = this.inner[2].getBBox(), // attributes 361 t = this.paper.text(bb.x + p.methodsOffsetX, bb.y + p.labelOffsetY + p.swimlane1OffsetY + p.attributesOffsetY + bbAtrrs.height + p.swimlane2OffsetY + p.methodsOffsetY, str), 362 tbb = t.getBBox(); 363 t.attr("text-anchor", "start"); 364 t.translate(0, tbb.height/2); // tune the y-position 365 return t; 366 }, 367 zoom: function(){ 368 this.inner[0].remove(); // label 369 this.inner[1].remove(); // swimlane1 370 this.inner[2].remove(); // attributes 371 this.inner[3].remove(); // swimlane2 372 this.inner[4].remove(); // methods 373 this.inner[0] = this.getLabelElement(); 374 this.inner[1] = this.getSwimlane1Element(); 375 this.inner[2] = this.getAttributesElement(); 376 this.inner[3] = this.getSwimlane2Element(); 377 this.inner[4] = this.getMethodsElement(); 378 } 379 }); 380 381 })(this); // END CLOSURE