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