simple_graph.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. // jQuery UI style "widget" for displaying a graph
  2. ////////////////////////////////////////////////////////////////////////////////
  3. //
  4. // graph
  5. //
  6. $(function()
  7. {
  8. // the widget definition, where "itop" is the namespace,
  9. // "dashboard" the widget name
  10. $.widget( "itop.simple_graph",
  11. {
  12. // default options
  13. options:
  14. {
  15. xmin: 0,
  16. xmax: 0,
  17. ymin: 0,
  18. ymax: 0,
  19. align: 'center',
  20. 'vertical-align': 'middle'
  21. },
  22. // the constructor
  23. _create: function()
  24. {
  25. var me = this;
  26. this.aNodes = [];
  27. this.aEdges = [];
  28. this.fZoom = 1.0;
  29. this.xOffset = 0;
  30. this.yOffset = 0;
  31. this.iTextHeight = 12;
  32. //this.element.height(this.element.parent().height());
  33. this.oPaper = Raphael(this.element.get(0), this.element.width(), this.element.height());
  34. this.auto_scale();
  35. this.element
  36. .addClass('itop-simple-graph');
  37. this._create_toolkit_menu();
  38. },
  39. // called when created, and later when changing options
  40. _refresh: function()
  41. {
  42. this.draw();
  43. },
  44. // events bound via _bind are removed automatically
  45. // revert other modifications here
  46. _destroy: function()
  47. {
  48. var sId = this.element.attr('id');
  49. this.element
  50. .removeClass('itop-simple-graph');
  51. $('#tk_graph'+sId).remove();
  52. },
  53. // _setOptions is called with a hash of all options that are changing
  54. _setOptions: function()
  55. {
  56. this._superApply(arguments);
  57. },
  58. // _setOption is called for each individual option that is changing
  59. _setOption: function( key, value )
  60. {
  61. this._superApply(arguments);
  62. },
  63. draw: function()
  64. {
  65. this.oPaper.clear();
  66. for(var k in this.aNodes)
  67. {
  68. this._draw_node(this.aNodes[k]);
  69. }
  70. for(var k in this.aEdges)
  71. {
  72. this._draw_edge(this.aEdges[k]);
  73. }
  74. },
  75. _draw_node: function(oNode)
  76. {
  77. var iWidth = oNode.width;
  78. var iHeight = 32;
  79. var xPos = Math.round(oNode.x * this.fZoom + this.xOffset);
  80. var yPos = Math.round(oNode.y * this.fZoom + this.yOffset);
  81. oNode.tx = 0;
  82. oNode.ty = 0;
  83. switch(oNode.shape)
  84. {
  85. case 'disc':
  86. oNode.aElements.push(this.oPaper.circle(xPos, yPos, iWidth*this.fZoom / 2).attr(oNode.disc_attr));
  87. var oText = this.oPaper.text(xPos, yPos, oNode.label);
  88. oText.attr(oNode.text_attr);
  89. oText.transform('s'+this.fZoom);
  90. oNode.aElements.push(oText);
  91. break;
  92. case 'group':
  93. oNode.aElements.push(this.oPaper.circle(xPos, yPos, iWidth*this.fZoom / 2).attr({fill: '#fff', 'stroke-width':0}));
  94. oNode.aElements.push(this.oPaper.circle(xPos, yPos, iWidth*this.fZoom / 2).attr(oNode.disc_attr));
  95. var xIcon = xPos - 18 * this.fZoom;
  96. var yIcon = yPos - 18 * this.fZoom;
  97. oNode.aElements.push(this.oPaper.image(oNode.icon_url, xIcon, yIcon, 16*this.fZoom, 16*this.fZoom).attr(oNode.icon_attr));
  98. oNode.aElements.push(this.oPaper.image(oNode.icon_url, xIcon + 18*this.fZoom, yIcon, 16*this.fZoom, 16*this.fZoom).attr(oNode.icon_attr));
  99. oNode.aElements.push(this.oPaper.image(oNode.icon_url, xIcon + 9*this.fZoom, yIcon + 18*this.fZoom, 16*this.fZoom, 16*this.fZoom).attr(oNode.icon_attr));
  100. var oText = this.oPaper.text(xPos, yPos +2, oNode.label);
  101. oText.attr(oNode.text_attr);
  102. oText.transform('s'+this.fZoom);
  103. var oBB = oText.getBBox();
  104. var dy = iHeight/2*this.fZoom + oBB.height/2;
  105. oText.remove();
  106. oText = this.oPaper.text(xPos, yPos +dy +2, oNode.label);
  107. oText.attr(oNode.text_attr);
  108. oText.transform('s'+this.fZoom);
  109. oNode.aElements.push(oText);
  110. oNode.aElements.push(this.oPaper.rect( xPos - oBB.width/2 -2, yPos - oBB.height/2 + dy, oBB.width +4, oBB.height).attr({fill: '#fff', stroke: '#fff', opacity: 0.9}));
  111. oText.toFront();
  112. break;
  113. case 'icon':
  114. if(Raphael.svg)
  115. {
  116. // the colorShift plugin works only in SVG
  117. oNode.aElements.push(this.oPaper.image(oNode.icon_url, xPos - iWidth * this.fZoom/2, yPos - iHeight * this.fZoom/2, iWidth*this.fZoom, iHeight*this.fZoom).colorShift('#fff', 1));
  118. }
  119. oNode.aElements.push(this.oPaper.image(oNode.icon_url, xPos - iWidth * this.fZoom/2, yPos - iHeight * this.fZoom/2, iWidth*this.fZoom, iHeight*this.fZoom).attr(oNode.icon_attr));
  120. var oText = this.oPaper.text( xPos, yPos, oNode.label);
  121. oText.attr(oNode.text_attr);
  122. oText.transform('s'+this.fZoom);
  123. var oBB = oText.getBBox();
  124. var dy = iHeight/2*this.fZoom + oBB.height/2;
  125. oText.remove();
  126. oText = this.oPaper.text( xPos, yPos + dy, oNode.label);
  127. oText.attr(oNode.text_attr);
  128. oText.transform('s'+this.fZoom);
  129. oNode.aElements.push(oText);
  130. oNode.aElements.push(this.oPaper.rect( xPos - oBB.width/2 -2, yPos - oBB.height/2 + dy, oBB.width +4, oBB.height).attr({fill: '#fff', stroke: '#fff', opacity: 0.9}).toBack());
  131. break;
  132. }
  133. if (oNode.source)
  134. {
  135. oNode.aElements.push(this.oPaper.circle(xPos, yPos, 1.25*iWidth*this.fZoom / 2).attr({stroke: '#c33', 'stroke-width': 3*this.fZoom }).toBack());
  136. }
  137. if (oNode.sink)
  138. {
  139. oNode.aElements.push(this.oPaper.circle(xPos, yPos, 1.25*iWidth*this.fZoom / 2).attr({stroke: '#33c', 'stroke-width': 3*this.fZoom }).toBack());
  140. }
  141. var me = this;
  142. for(k in oNode.aElements)
  143. {
  144. var sNodeId = oNode.id;
  145. oNode.aElements[k].drag(function(dx, dy, x, y, event) { me._move(sNodeId, dx, dy, x, y, event); }, function(x, y, event) { me._drag_start(sNodeId, x, y, event); }, function (event) { me._drag_end(sNodeId, event); });
  146. }
  147. },
  148. _move: function(sNodeId, dx, dy, x, y, event)
  149. {
  150. var origDx = dx / this.fZoom;
  151. var origDy = dy / this.fZoom;
  152. var oNode = this._find_node(sNodeId);
  153. oNode.x = oNode.xOrig + origDx;
  154. oNode.y = oNode.yOrig + origDy;
  155. for(k in oNode.aElements)
  156. {
  157. oNode.aElements[k].transform('t'+(oNode.tx + dx)+', '+(oNode.ty + dy));
  158. for(j in this.aEdges)
  159. {
  160. var oEdge = this.aEdges[j];
  161. if ((oEdge.source_node_id == sNodeId) || (oEdge.sink_node_id == sNodeId))
  162. {
  163. var sPath = this._get_edge_path(oEdge);
  164. oEdge.aElements[0].attr({path: sPath});
  165. }
  166. }
  167. }
  168. },
  169. _drag_start: function(sNodeId, x, y, event)
  170. {
  171. var oNode = this._find_node(sNodeId);
  172. oNode.xOrig = oNode.x;
  173. oNode.yOrig = oNode.y;
  174. },
  175. _drag_end: function(sNodeId, event)
  176. {
  177. var oNode = this._find_node(sNodeId);
  178. oNode.tx += (oNode.x - oNode.xOrig) * this.fZoom;
  179. oNode.ty += (oNode.y - oNode.yOrig) * this.fZoom;
  180. oNode.xOrig = oNode.x;
  181. oNode.yOrig = oNode.y;
  182. },
  183. _get_edge_path: function(oEdge)
  184. {
  185. var oStart = this._find_node(oEdge.source_node_id);
  186. var oEnd = this._find_node(oEdge.sink_node_id);
  187. var iArrowSize = 5;
  188. if ((oStart == null) || (oEnd == null)) return '';
  189. var xStart = Math.round(oStart.x * this.fZoom + this.xOffset);
  190. var yStart = Math.round(oStart.y * this.fZoom + this.yOffset);
  191. var xEnd = Math.round(oEnd.x * this.fZoom + this.xOffset);
  192. var yEnd = Math.round(oEnd.y * this.fZoom + this.yOffset);
  193. var sPath = Raphael.format('M{0},{1}L{2},{3}', xStart, yStart, xEnd, yEnd);
  194. var vx = (xEnd - xStart);
  195. var vy = (yEnd - yStart);
  196. var l = Math.sqrt(vx*vx+vy*vy);
  197. vx = vx / l;
  198. vy = vy / l;
  199. var ux = -vy;
  200. var uy = vx;
  201. var lPos = Math.max(l/2, l - 40*this.fZoom);
  202. var xArrow = xStart + vx * lPos;
  203. var yArrow = yStart + vy * lPos;
  204. sPath += Raphael.format('M{0},{1}l{2},{3}M{4},{5}l{6},{7}', xArrow, yArrow, this.fZoom * iArrowSize *(-vx + ux), this.fZoom * iArrowSize *(-vy + uy), xArrow, yArrow, this.fZoom * iArrowSize *(-vx - ux), this.fZoom * iArrowSize *(-vy - uy));
  205. return sPath;
  206. },
  207. _draw_edge: function(oEdge)
  208. {
  209. var fStrokeSize = Math.max(1, 2 * this.fZoom);
  210. var sPath = this._get_edge_path(oEdge);
  211. var oAttr = $.extend(oEdge.attr);
  212. oAttr['stroke-linecap'] = 'round';
  213. oAttr['stroke-width'] = fStrokeSize;
  214. oEdge.aElements.push(this.oPaper.path(sPath).attr(oAttr).toBack());
  215. },
  216. _find_node: function(sId)
  217. {
  218. for(var k in this.aNodes)
  219. {
  220. if (this.aNodes[k].id == sId) return this.aNodes[k];
  221. }
  222. return null;
  223. },
  224. auto_scale: function()
  225. {
  226. var fMaxZoom = 1.5;
  227. iMargin = 10;
  228. xmin = this.options.xmin - iMargin;
  229. xmax = this.options.xmax + iMargin;
  230. ymin = this.options.ymin - iMargin;
  231. ymax = this.options.ymax + iMargin;
  232. var xScale = this.element.width() / (xmax - xmin);
  233. var yScale = this.element.height() / (ymax - ymin + this.iTextHeight);
  234. this.fZoom = Math.min(xScale, yScale, fMaxZoom);
  235. switch(this.options.align)
  236. {
  237. case 'left':
  238. this.xOffset = -xmin * this.fZoom;
  239. break;
  240. case 'right':
  241. this.xOffset = (this.element.width() - (xmax - xmin) * this.fZoom);
  242. break;
  243. case 'center':
  244. this.xOffset = (this.element.width() - (xmax - xmin) * this.fZoom) / 2;
  245. break;
  246. }
  247. switch(this.options['vertical-align'])
  248. {
  249. case 'top':
  250. this.yOffset = -ymin * this.fZoom;
  251. break;
  252. case 'bottom':
  253. this.yOffset = this.element.height() - (ymax + this.iTextHeight) * this.fZoom;
  254. break;
  255. case 'middle':
  256. this.yOffset = (this.element.height() - (ymax - ymin + this.iTextHeight) * this.fZoom) / 2;
  257. break;
  258. }
  259. },
  260. add_node: function(oNode)
  261. {
  262. oNode.aElements = [];
  263. this.aNodes.push(oNode);
  264. },
  265. add_edge: function(oEdge)
  266. {
  267. oEdge.aElements = [];
  268. this.aEdges.push(oEdge);
  269. },
  270. _create_toolkit_menu: function()
  271. {
  272. var sPopupMenuId = 'tk_graph'+this.element.attr('id');
  273. var sHtml = '<div class="itop_popup toolkit_menu" style="font-size: 12px;" id="'+sPopupMenuId+'"><ul><li><img src="../images/toolkit_menu.png"><ul>';
  274. sHtml += '<li><a href="#" id="'+sPopupMenuId+'_pdf">Export as PDF</a></li>';
  275. sHtml += '<li><a href="#" id="'+sPopupMenuId+'_document">Export as document...</a></li>';
  276. sHtml += '<li><a href="#" id="'+sPopupMenuId+'_reload">Refresh</a></li>';
  277. sHtml += '</ul></li></ul></div>';
  278. this.element.before(sHtml);
  279. $('#'+sPopupMenuId).popupmenu();
  280. var me = this;
  281. $('#'+sPopupMenuId+'_pdf').click(function() { me.export_as_pdf(); });
  282. $('#'+sPopupMenuId+'_document').click(function() { me.export_as_document(); });
  283. $('#'+sPopupMenuId+'_reload').click(function() { me.reload(); });
  284. },
  285. export_as_pdf: function()
  286. {
  287. alert('Export as PDF: not yet implemented');
  288. },
  289. export_as_document: function()
  290. {
  291. alert('Export as document: not yet implemented');
  292. },
  293. reload: function()
  294. {
  295. alert('Reload: not yet implemented');
  296. }
  297. });
  298. });