trees.js 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442
  1. /**
  2. * Mindmaps plugin.
  3. *
  4. * Todo:
  5. * - Make cursor key selection more generic
  6. * - Handle single-cell movement on touch
  7. * - Move multiple cells without subtrees
  8. * - Make offset subtrees more generic
  9. */
  10. Draw.loadPlugin(function(ui)
  11. {
  12. if (ui.editor.chromeless)
  13. {
  14. return;
  15. }
  16. var spacing = 10;
  17. var level = 40;
  18. var graph = ui.editor.graph;
  19. var model = graph.getModel();
  20. // Adds resources for actions
  21. mxResources.parse('selectChildren=Select Children');
  22. mxResources.parse('selectSiblings=Select Siblings');
  23. mxResources.parse('selectSubtree=Select Subtree');
  24. mxResources.parse('selectParent=Select Parent');
  25. function isTreeCell(cell)
  26. {
  27. var result = false;
  28. if (cell != null)
  29. {
  30. graph.traverse(cell, true, function(vertex)
  31. {
  32. result = vertex.getAttribute('treeRoot') == '1';
  33. return !result;
  34. }, null, null, true);
  35. }
  36. return result;
  37. };
  38. var uiCreatePopupMenu = ui.menus.createPopupMenu;
  39. ui.menus.createPopupMenu = function(menu, cell, evt)
  40. {
  41. uiCreatePopupMenu.apply(this, arguments);
  42. if (isTreeCell(graph.getSelectionCell()) && graph.getSelectionCount() == 1)
  43. {
  44. var cell = graph.getSelectionCell();
  45. var sib = graph.getOutgoingEdges(cell);
  46. menu.addSeparator();
  47. if (sib != null && sib.length > 0)
  48. {
  49. this.addMenuItems(menu, ['selectChildren', 'selectSubtree'], null, evt);
  50. }
  51. menu.addSeparator();
  52. if (cell.getAttribute('treeRoot') != '1')
  53. {
  54. this.addMenuItems(menu, ['selectSiblings', 'selectParent'], null, evt);
  55. }
  56. }
  57. };
  58. // Adds actions
  59. ui.actions.addAction('selectChildren', function()
  60. {
  61. if (graph.isEnabled() && graph.getSelectionCount() == 1)
  62. {
  63. var cell = graph.getSelectionCell();
  64. var sib = graph.getOutgoingEdges(cell);
  65. if (sib != null)
  66. {
  67. var tmp = [];
  68. for (var i = 0; i < sib.length; i++)
  69. {
  70. tmp.push(graph.model.getTerminal(sib[i], false));
  71. }
  72. graph.setSelectionCells(tmp);
  73. }
  74. }
  75. }, null, null, 'Alt+Shift+X');
  76. // Adds actions
  77. ui.actions.addAction('selectSiblings', function()
  78. {
  79. if (graph.isEnabled() && graph.getSelectionCount() == 1)
  80. {
  81. var cell = graph.getSelectionCell();
  82. var edges = graph.getIncomingEdges(cell);
  83. if (edges != null && edges.length > 0)
  84. {
  85. var sib = graph.getOutgoingEdges(graph.model.getTerminal(edges[0], true));
  86. if (sib != null)
  87. {
  88. var tmp = [];
  89. for (var i = 0; i < sib.length; i++)
  90. {
  91. tmp.push(graph.model.getTerminal(sib[i], false));
  92. }
  93. graph.setSelectionCells(tmp);
  94. }
  95. }
  96. }
  97. }, null, null, 'Alt+Shift+S');
  98. // Adds actions
  99. ui.actions.addAction('selectParent', function()
  100. {
  101. if (graph.isEnabled() && graph.getSelectionCount() == 1)
  102. {
  103. var cell = graph.getSelectionCell();
  104. var edges = graph.getIncomingEdges(cell);
  105. if (edges != null && edges.length > 0)
  106. {
  107. graph.setSelectionCell(graph.model.getTerminal(edges[0], true));
  108. }
  109. }
  110. }, null, null, 'Alt+Shift+P');
  111. ui.actions.addAction('selectSubtree', function()
  112. {
  113. if (graph.isEnabled() && graph.getSelectionCount() == 1)
  114. {
  115. var cell = graph.getSelectionCell();
  116. // Makes space for new parent
  117. var subtree = [];
  118. graph.traverse(cell, true, function(vertex, edge)
  119. {
  120. if (edge != null)
  121. {
  122. subtree.push(edge);
  123. }
  124. subtree.push(vertex);
  125. return true;
  126. });
  127. graph.setSelectionCells(subtree);
  128. }
  129. }, null, null, 'Alt+Shift+T');
  130. /**
  131. * Overriddes
  132. */
  133. var graphFoldCells = graph.foldCells;
  134. graph.foldCells = function(collapse, recurse, cells, checkFoldable, evt)
  135. {
  136. //console.log('cells', cells, collapse);
  137. this.stopEditing();
  138. this.model.beginUpdate();
  139. try
  140. {
  141. var newCells = cells.splice();
  142. var tmp = [];
  143. for (var i = 0; i < cells.length; i++)
  144. {
  145. if (isTreeCell(cells[i]))
  146. {
  147. graph.traverse(cells[i], true, function(vertex, edge)
  148. {
  149. if (edge != null)
  150. {
  151. tmp.push(edge);
  152. }
  153. if (vertex != cells[i])
  154. {
  155. tmp.push(vertex);
  156. }
  157. // Stop traversal on collapsed vertices
  158. return vertex == cells[i] || !graph.model.isCollapsed(vertex);
  159. });
  160. graph.model.setCollapsed(cells[i], collapse);
  161. }
  162. }
  163. for (var i = 0; i < tmp.length; i++)
  164. {
  165. graph.model.setVisible(tmp[i], !collapse);
  166. }
  167. cells = newCells;
  168. graphFoldCells.apply(this, arguments);
  169. }
  170. finally
  171. {
  172. this.model.endUpdate();
  173. }
  174. };
  175. var graphRemoveCells = graph.removeCells;
  176. graph.removeCells = function(cells, includeEdges)
  177. {
  178. var tmp = [];
  179. for (var i = 0; i < cells.length; i++)
  180. {
  181. if (isTreeCell(cells[i]))
  182. {
  183. graph.traverse(cells[i], true, function(vertex, edge)
  184. {
  185. if (edge != null)
  186. {
  187. tmp.push(edge);
  188. }
  189. tmp.push(vertex);
  190. return true;
  191. });
  192. var edges = graph.getIncomingEdges(cells[i]);
  193. cells = cells.concat(edges);
  194. }
  195. else
  196. {
  197. tmp.push(cells[i]);
  198. }
  199. }
  200. cells = tmp;
  201. graphRemoveCells.apply(this, arguments);
  202. };
  203. ui.hoverIcons.getStateAt = function(state, x, y)
  204. {
  205. return (isTreeCell(state.cell)) ? null : this.graph.view.getState(this.graph.getCellAt(x, y));
  206. };
  207. var graphDuplicateCells = graph.duplicateCells;
  208. graph.duplicateCells = function(cells, append)
  209. {
  210. cells = (cells != null) ? cells : this.getSelectionCells();
  211. var temp = cells.slice(0);
  212. for (var i = 0; i < temp.length; i++)
  213. {
  214. var cell = temp[i];
  215. var state = graph.view.getState(cell);
  216. if (state != null && isTreeCell(state.cell))
  217. {
  218. // Avoids disconnecting subtree by removing all incoming edges
  219. var edges = graph.getIncomingEdges(state.cell);
  220. for (var j = 0; j < edges.length; j++)
  221. {
  222. mxUtils.remove(edges[j], cells);
  223. }
  224. }
  225. }
  226. this.model.beginUpdate();
  227. try
  228. {
  229. var result = graphDuplicateCells.call(this, cells, append);
  230. if (result.length == cells.length)
  231. {
  232. for (var i = 0; i < cells.length; i++)
  233. {
  234. if (isTreeCell(cells[i]))
  235. {
  236. var newEdges = graph.getIncomingEdges(result[i]);
  237. var edges = graph.getIncomingEdges(cells[i]);
  238. if (newEdges.length == 0 && edges.length > 0)
  239. {
  240. var clone = this.cloneCells([edges[0]])[0];
  241. this.addEdge(clone, graph.getDefaultParent(),
  242. this.model.getTerminal(edges[0], true), result[i]);
  243. }
  244. }
  245. }
  246. }
  247. }
  248. finally
  249. {
  250. this.model.endUpdate();
  251. }
  252. return result;
  253. };
  254. var graphMoveCells = graph.moveCells;
  255. graph.moveCells = function(cells, dx, dy, clone, target, evt, mapping)
  256. {
  257. var result = null;
  258. this.model.beginUpdate();
  259. try
  260. {
  261. var newSource = target;
  262. if (isTreeCell(target))
  263. {
  264. // Handles only drag from tree or from sidebar with dangling edges
  265. for (var i = 0; i < cells.length; i++)
  266. {
  267. if (isTreeCell(cells[i]) || (graph.model.isEdge(cells[i]) &&
  268. graph.model.getTerminal(cells[i], true) == null))
  269. {
  270. target = null;
  271. break;
  272. }
  273. }
  274. // Applies distance between previous and current parent for non-sidebar drags
  275. if (newSource != null && target == null && this.view.getState(cells[0]) != null)
  276. {
  277. var edges = graph.getIncomingEdges(cells[0]);
  278. if (edges.length > 0)
  279. {
  280. var state1 = graph.view.getState(graph.model.getTerminal(edges[0], true));
  281. if (state1 != null)
  282. {
  283. var state2 = graph.view.getState(newSource);
  284. if (state2 != null)
  285. {
  286. dx = state2.getCenterX() - state1.getCenterX();
  287. dy = state2.getCenterY() - state1.getCenterY();
  288. }
  289. }
  290. }
  291. }
  292. }
  293. result = graphMoveCells.apply(this, arguments);
  294. if (result.length == cells.length)
  295. {
  296. for (var i = 0; i < result.length; i++)
  297. {
  298. // Connects all dangling edges from the sidebar when dropped into drop target (not hover icon)
  299. if (this.model.isEdge(result[i]))
  300. {
  301. if (isTreeCell(newSource) && mxUtils.indexOf(result, this.model.getTerminal(result[i], true)) < 0)
  302. {
  303. this.model.setTerminal(result[i], newSource, true);
  304. }
  305. }
  306. else if (isTreeCell(cells[i]))
  307. {
  308. var edges = graph.getIncomingEdges(cells[i]);
  309. if (edges.length > 0)
  310. {
  311. if (!clone)
  312. {
  313. if (isTreeCell(newSource) && mxUtils.indexOf(cells, this.model.getTerminal(edges[0], true)) < 0)
  314. {
  315. this.model.setTerminal(edges[0], newSource, true);
  316. }
  317. }
  318. else
  319. {
  320. var newEdges = graph.getIncomingEdges(result[i]);
  321. if (newEdges.length == 0)
  322. {
  323. var temp = newSource;
  324. if (temp == null)
  325. {
  326. temp = graph.model.getTerminal(edges[0], true);
  327. }
  328. var clone = this.cloneCells([edges[0]])[0];
  329. this.addEdge(clone, graph.getDefaultParent(), temp, result[i]);
  330. }
  331. }
  332. }
  333. }
  334. }
  335. }
  336. }
  337. finally
  338. {
  339. this.model.endUpdate();
  340. }
  341. return result;
  342. };
  343. // Connects all dangling edges from the sidebar (by
  344. // default only first dangling edge gets connected)
  345. var sidebarDropAndConnect = ui.sidebar.dropAndConnect;
  346. ui.sidebar.dropAndConnect = function(source, targets, direction, dropCellIndex)
  347. {
  348. var model = graph.model;
  349. var result = null;
  350. model.beginUpdate();
  351. try
  352. {
  353. result = sidebarDropAndConnect.apply(this, arguments);
  354. if (isTreeCell(source))
  355. {
  356. for (var i = 0; i < result.length; i++)
  357. {
  358. if (model.isEdge(result[i]) && model.getTerminal(result[i], true) == null)
  359. {
  360. model.setTerminal(result[i], source, true);
  361. var geo = graph.getCellGeometry(result[i]);
  362. geo.points = null;
  363. if (geo.getTerminalPoint(true) != null)
  364. {
  365. geo.setTerminalPoint(null, true);
  366. }
  367. }
  368. }
  369. }
  370. }
  371. finally
  372. {
  373. model.endUpdate();
  374. }
  375. return result;
  376. };
  377. /**
  378. * Checks source point of incoming edge relative to target terminal.
  379. */
  380. function getTreeDirection(cell)
  381. {
  382. var state = graph.view.getState(cell);
  383. if (state != null)
  384. {
  385. var edges = graph.getIncomingEdges(state.cell);
  386. if (edges.length > 0)
  387. {
  388. var edgeState = graph.view.getState(edges[0]);
  389. if (edgeState != null)
  390. {
  391. var abs = edgeState.absolutePoints;
  392. if (abs != null && abs.length > 0)
  393. {
  394. var pt = abs[abs.length - 1];
  395. if (pt != null)
  396. {
  397. if (pt.y == state.y && Math.abs(pt.x - state.getCenterX()) < state.width / 2)
  398. {
  399. return mxConstants.DIRECTION_SOUTH;
  400. }
  401. else if (pt.y == state.y + state.height && Math.abs(pt.x - state.getCenterX()) < state.width / 2)
  402. {
  403. return mxConstants.DIRECTION_NORTH;
  404. }
  405. else if (pt.x > state.getCenterX())
  406. {
  407. return mxConstants.DIRECTION_WEST;
  408. }
  409. }
  410. }
  411. }
  412. }
  413. }
  414. return mxConstants.DIRECTION_EAST;
  415. };
  416. function addSibling(cell, after)
  417. {
  418. after = (after != null) ? after : true;
  419. graph.model.beginUpdate();
  420. try
  421. {
  422. var edges = graph.getIncomingEdges(cell);
  423. var clones = graph.cloneCells([edges[0], cell]);
  424. graph.model.setTerminal(clones[0], graph.model.getTerminal(edges[0], true), true);
  425. var dir = getTreeDirection(cell);
  426. if (dir == mxConstants.DIRECTION_SOUTH || dir == mxConstants.DIRECTION_NORTH)
  427. {
  428. clones[1].geometry.x += (after) ? cell.geometry.width + spacing :
  429. -clones[1].geometry.width - spacing;
  430. }
  431. else
  432. {
  433. clones[1].geometry.y += (after) ? cell.geometry.height + spacing :
  434. -clones[1].geometry.height - spacing;
  435. }
  436. if (dir == mxConstants.DIRECTION_WEST)
  437. {
  438. clones[1].geometry.x = cell.geometry.x + cell.geometry.width - clones[1].geometry.width;
  439. }
  440. // Moves existing siblings
  441. var state = graph.view.getState(cell);
  442. var s = graph.view.scale;
  443. if (state != null)
  444. {
  445. var bbox = mxRectangle.fromRectangle(state);
  446. if (dir == mxConstants.DIRECTION_SOUTH ||
  447. dir == mxConstants.DIRECTION_NORTH)
  448. {
  449. bbox.x += ((after) ? cell.geometry.width + spacing :
  450. -clones[1].geometry.width - spacing) * s;
  451. }
  452. else
  453. {
  454. bbox.y += ((after) ? cell.geometry.height + spacing :
  455. -clones[1].geometry.height - spacing) * s;
  456. }
  457. var sib = graph.getOutgoingEdges(graph.model.getTerminal(edges[0], true));
  458. if (sib != null)
  459. {
  460. var hor = (dir == mxConstants.DIRECTION_SOUTH || dir == mxConstants.DIRECTION_NORTH);
  461. var dx = 0;
  462. var dy = 0;
  463. for (var i = 0; i < sib.length; i++)
  464. {
  465. var temp = graph.model.getTerminal(sib[i], false);
  466. if (dir == getTreeDirection(temp))
  467. {
  468. var sibling = graph.view.getState(temp);
  469. if (temp != cell && sibling != null)
  470. {
  471. if ((hor && after != sibling.getCenterX() < state.getCenterX()) ||
  472. (!hor && after != sibling.getCenterY() < state.getCenterY()))
  473. {
  474. if (mxUtils.intersects(bbox, sibling))
  475. {
  476. dx = spacing + Math.max(dx, (Math.min(bbox.x + bbox.width,
  477. sibling.x + sibling.width) - Math.max(bbox.x, sibling.x)) / s);
  478. dy = spacing + Math.max(dy, (Math.min(bbox.y + bbox.height,
  479. sibling.y + sibling.height) - Math.max(bbox.y, sibling.y)) / s);
  480. }
  481. }
  482. }
  483. }
  484. }
  485. if (hor)
  486. {
  487. dy = 0;
  488. }
  489. else
  490. {
  491. dx = 0;
  492. }
  493. for (var i = 0; i < sib.length; i++)
  494. {
  495. var temp = graph.model.getTerminal(sib[i], false);
  496. if (dir == getTreeDirection(temp))
  497. {
  498. var sibling = graph.view.getState(temp);
  499. if (temp != cell && sibling != null)
  500. {
  501. if ((hor && after != sibling.getCenterX() < state.getCenterX()) ||
  502. (!hor && after != sibling.getCenterY() < state.getCenterY()))
  503. {
  504. var subtree = [];
  505. graph.traverse(sibling.cell, true, function(vertex, edge)
  506. {
  507. if (edge != null)
  508. {
  509. subtree.push(edge);
  510. }
  511. subtree.push(vertex);
  512. return true;
  513. });
  514. graph.moveCells(subtree, ((after) ? 1 : -1) * dx, ((after) ? 1 : -1) * dy);
  515. }
  516. }
  517. }
  518. }
  519. }
  520. }
  521. return graph.addCells(clones);
  522. }
  523. finally
  524. {
  525. graph.model.endUpdate();
  526. }
  527. };
  528. function addParent(cell)
  529. {
  530. graph.model.beginUpdate();
  531. try
  532. {
  533. var dir = getTreeDirection(cell);
  534. var edges = graph.getIncomingEdges(cell);
  535. var clones = graph.cloneCells([edges[0], cell]);
  536. graph.model.setTerminal(edges[0], clones[1], false);
  537. graph.model.setTerminal(clones[0], clones[1], true);
  538. graph.model.setTerminal(clones[0], cell, false);
  539. // Makes space for new parent
  540. var subtree = [];
  541. graph.traverse(cell, true, function(vertex, edge)
  542. {
  543. if (edge != null)
  544. {
  545. subtree.push(edge);
  546. }
  547. subtree.push(vertex);
  548. return true;
  549. });
  550. var dx = cell.geometry.width + level;
  551. var dy = cell.geometry.height + level;
  552. if (dir == mxConstants.DIRECTION_SOUTH)
  553. {
  554. dx = 0;
  555. }
  556. else if (dir == mxConstants.DIRECTION_NORTH)
  557. {
  558. dx = 0;
  559. dy = -level;
  560. }
  561. else if (dir == mxConstants.DIRECTION_WEST)
  562. {
  563. dx = -level;
  564. dy = 0;
  565. }
  566. else if (dir == mxConstants.DIRECTION_EAST)
  567. {
  568. dy = 0;
  569. }
  570. graph.moveCells(subtree, dx, dy);
  571. return graph.addCells(clones);
  572. }
  573. finally
  574. {
  575. graph.model.endUpdate();
  576. }
  577. };
  578. function addChild(cell)
  579. {
  580. graph.model.beginUpdate();
  581. try
  582. {
  583. var edges = graph.getIncomingEdges(cell);
  584. var clones = graph.cloneCells([edges[0], cell]);
  585. graph.model.setTerminal(clones[0], cell, true);
  586. // Finds free space
  587. var edges = graph.getOutgoingEdges(cell);
  588. var targets = [];
  589. for (var i = 0; i < edges.length; i++)
  590. {
  591. var target = graph.model.getTerminal(edges[i], false);
  592. if (target != null)
  593. {
  594. targets.push(target);
  595. }
  596. }
  597. var bbox = graph.view.getBounds(targets);
  598. var dir = getTreeDirection(cell);
  599. var tr = graph.view.translate;
  600. var s = graph.view.scale;
  601. if (dir == mxConstants.DIRECTION_SOUTH)
  602. {
  603. clones[1].geometry.x = (bbox == null) ? cell.geometry.x + (cell.geometry.width -
  604. clones[1].geometry.width) / 2 : (bbox.x + bbox.width) / s - tr.x + spacing;
  605. clones[1].geometry.y += cell.geometry.height + level;
  606. }
  607. else if (dir == mxConstants.DIRECTION_NORTH)
  608. {
  609. clones[1].geometry.x = (bbox == null) ? cell.geometry.x + (cell.geometry.width -
  610. clones[1].geometry.width) / 2 : (bbox.x + bbox.width) / s - tr.x + spacing;
  611. clones[1].geometry.y -= clones[1].geometry.height + level;
  612. }
  613. else if (dir == mxConstants.DIRECTION_WEST)
  614. {
  615. clones[1].geometry.x -= clones[1].geometry.width + level;
  616. clones[1].geometry.y = (bbox == null) ? cell.geometry.y + (cell.geometry.height -
  617. clones[1].geometry.height) / 2 : (bbox.y + bbox.height) / s - tr.y + spacing;
  618. }
  619. else
  620. {
  621. clones[1].geometry.x += cell.geometry.width + level;
  622. clones[1].geometry.y = (bbox == null) ? cell.geometry.y + (cell.geometry.height -
  623. clones[1].geometry.height) / 2 : (bbox.y + bbox.height) / s - tr.y + spacing;
  624. }
  625. return graph.addCells(clones);
  626. }
  627. finally
  628. {
  629. graph.model.endUpdate();
  630. }
  631. };
  632. function getOrderedTargets(cell, horizontal, ref)
  633. {
  634. var sib = graph.getOutgoingEdges(cell);
  635. var state = graph.view.getState(ref);
  636. var targets = [];
  637. if (state != null && sib != null)
  638. {
  639. for (var i = 0; i < sib.length; i++)
  640. {
  641. var temp = graph.view.getState(graph.model.getTerminal(sib[i], false));
  642. if (temp != null && ((!horizontal && (Math.min(temp.x + temp.width,
  643. state.x + state.width) >= Math.max(temp.x, state.x))) ||
  644. (horizontal && (Math.min(temp.y + temp.height, state.y + state.height) >=
  645. Math.max(temp.y, state.y)))))
  646. {
  647. targets.push(temp);
  648. }
  649. }
  650. targets.sort(function(a, b)
  651. {
  652. return (horizontal) ? a.x + a.width - b.x - b.width : a.y + a.height - b.y - b.height;
  653. });
  654. }
  655. return targets;
  656. };
  657. function selectCell(cell, direction)
  658. {
  659. var dir = getTreeDirection(cell);
  660. var h1 = dir == mxConstants.DIRECTION_EAST || dir == mxConstants.DIRECTION_WEST;
  661. var h2 = direction == mxConstants.DIRECTION_EAST || direction == mxConstants.DIRECTION_WEST;
  662. if (h1 == h2 && dir != direction)
  663. {
  664. ui.actions.get('selectParent').funct();
  665. }
  666. else if (dir == direction)
  667. {
  668. var sib = graph.getOutgoingEdges(cell);
  669. if (sib != null && sib.length > 0)
  670. {
  671. graph.setSelectionCell(graph.model.getTerminal(sib[0], false));
  672. }
  673. }
  674. else
  675. {
  676. var edges = graph.getIncomingEdges(cell);
  677. if (edges != null && edges.length > 0)
  678. {
  679. var targets = getOrderedTargets(graph.model.getTerminal(edges[0], true), h2, cell);
  680. var state = graph.view.getState(cell);
  681. if (state != null)
  682. {
  683. var idx = mxUtils.indexOf(targets, state);
  684. if (idx >= 0)
  685. {
  686. idx += (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_WEST) ? -1 : 1;
  687. if (idx >= 0 && idx <= targets.length - 1)
  688. {
  689. graph.setSelectionCell(targets[idx].cell);
  690. }
  691. }
  692. }
  693. }
  694. }
  695. };
  696. // Overrides keyboard shortcuts
  697. var altShiftActions = {88: ui.actions.get('selectChildren'), // Alt+Shift+X
  698. 84: ui.actions.get('selectSubtree'), // Alt+Shift+T
  699. 80: ui.actions.get('selectParent'), // Alt+Shift+P
  700. 83: ui.actions.get('selectSiblings')} // Alt+Shift+S
  701. var editorUiOnKeyDown = ui.onKeyDown;
  702. ui.onKeyDown = function(evt)
  703. {
  704. try
  705. {
  706. if (graph.isEnabled() && !graph.isEditing() && graph.getSelectionCount() == 1 &&
  707. isTreeCell(graph.getSelectionCell()))
  708. {
  709. var cells = null;
  710. if (graph.getSelectionCell().getAttribute('treeRoot') != '1')
  711. {
  712. if (evt.which == 9) // Tab adds child
  713. {
  714. cells = (mxEvent.isShiftDown(evt)) ?
  715. addParent(graph.getSelectionCell()) :
  716. addChild(graph.getSelectionCell());
  717. }
  718. else if (evt.which == 13) // Enter adds sibling
  719. {
  720. cells = addSibling(graph.getSelectionCell(), !mxEvent.isShiftDown(evt));
  721. }
  722. }
  723. if (cells != null && cells.length > 0)
  724. {
  725. if (cells.length == 1 && graph.model.isEdge(cells[0]))
  726. {
  727. graph.setSelectionCell(graph.model.getTerminal(cells[0], false));
  728. }
  729. else
  730. {
  731. graph.setSelectionCell(cells[cells.length - 1]);
  732. }
  733. if (ui.hoverIcons != null)
  734. {
  735. ui.hoverIcons.update(graph.view.getState(graph.getSelectionCell()));
  736. }
  737. graph.startEditingAtCell(graph.getSelectionCell());
  738. mxEvent.consume(evt);
  739. }
  740. else
  741. {
  742. if (mxEvent.isAltDown(evt) && mxEvent.isShiftDown(evt))
  743. {
  744. var action = altShiftActions[evt.keyCode];
  745. if (action != null)
  746. {
  747. action.funct(evt);
  748. mxEvent.consume(evt);
  749. }
  750. }
  751. else
  752. {
  753. if (evt.keyCode == 37) // left
  754. {
  755. selectCell(graph.getSelectionCell(), mxConstants.DIRECTION_WEST);
  756. mxEvent.consume(evt);
  757. }
  758. else if (evt.keyCode == 38) // up
  759. {
  760. selectCell(graph.getSelectionCell(), mxConstants.DIRECTION_NORTH);
  761. mxEvent.consume(evt);
  762. }
  763. else if (evt.keyCode == 39) // right
  764. {
  765. selectCell(graph.getSelectionCell(), mxConstants.DIRECTION_EAST);
  766. mxEvent.consume(evt);
  767. }
  768. else if (evt.keyCode == 40) // down
  769. {
  770. selectCell(graph.getSelectionCell(), mxConstants.DIRECTION_SOUTH);
  771. mxEvent.consume(evt);
  772. }
  773. }
  774. }
  775. }
  776. }
  777. catch (e)
  778. {
  779. console.log('error', e);
  780. }
  781. if (!mxEvent.isConsumed(evt))
  782. {
  783. editorUiOnKeyDown.apply(this, arguments);
  784. }
  785. };
  786. var graphConnectVertex = graph.connectVertex;
  787. graph.connectVertex = function(source, direction, length, evt, forceClone, ignoreCellAt)
  788. {
  789. if (isTreeCell(source) && source.getAttribute('treeRoot') != '1')
  790. {
  791. var dir = getTreeDirection(source);
  792. var h1 = dir == mxConstants.DIRECTION_EAST || dir == mxConstants.DIRECTION_WEST;
  793. var h2 = direction == mxConstants.DIRECTION_EAST || direction == mxConstants.DIRECTION_WEST;
  794. if (dir == direction)
  795. {
  796. return addChild(source);
  797. }
  798. else if (h1 == h2)
  799. {
  800. return addParent(source);
  801. }
  802. else
  803. {
  804. return addSibling(source, direction != mxConstants.DIRECTION_NORTH &&
  805. direction != mxConstants.DIRECTION_WEST);
  806. }
  807. return [];
  808. }
  809. else
  810. {
  811. this.model.beginUpdate();
  812. try
  813. {
  814. var cells = graphConnectVertex.call(this, source, direction, length, evt, forceClone,
  815. ignoreCellAt || source.getAttribute('treeRoot') == '1');
  816. // Removes treeRoot flag in clones
  817. if (source.getAttribute('treeRoot') == '1')
  818. {
  819. for (var i = 0; i < cells.length; i++)
  820. {
  821. if (cells[i].getAttribute('treeRoot') == '1')
  822. {
  823. graph.setAttributeForCell(cells[i], 'treeRoot', null);
  824. }
  825. }
  826. }
  827. }
  828. finally
  829. {
  830. this.model.endUpdate();
  831. }
  832. return cells;
  833. }
  834. };
  835. var graphHandlerGetCells = graph.graphHandler.getCells;
  836. graph.graphHandler.getCells = function(initialCell)
  837. {
  838. var cells = graphHandlerGetCells.apply(this, arguments);
  839. var temp = cells.slice(0);
  840. // Removes all edges first
  841. for (var i = 0; i < temp.length; i++)
  842. {
  843. if (isTreeCell(temp[i]))
  844. {
  845. // Avoids disconnecting subtree by removing all incoming edges
  846. var edges = graph.getIncomingEdges(temp[i]);
  847. for (var j = 0; j < edges.length; j++)
  848. {
  849. mxUtils.remove(edges[j], cells);
  850. }
  851. }
  852. }
  853. for (var i = 0; i < temp.length; i++)
  854. {
  855. if (isTreeCell(temp[i]))
  856. {
  857. // Gets the subtree from cell downwards
  858. graph.traverse(temp[i], true, function(vertex, edge)
  859. {
  860. // TODO: Use dictionary to avoid duplicates
  861. if (edge != null && mxUtils.indexOf(cells, edge) < 0)
  862. {
  863. cells.push(edge);
  864. }
  865. if (mxUtils.indexOf(cells, vertex) < 0)
  866. {
  867. cells.push(vertex);
  868. }
  869. return true;
  870. });
  871. }
  872. }
  873. return cells;
  874. };
  875. // var ignoreMove = false;
  876. //
  877. // graph.addListener(mxEvent.MOVE_CELLS, function(sender, evt)
  878. // {
  879. // if (!ignoreMove)
  880. // {
  881. // var cells = evt.getProperty('cells');
  882. // var dx = evt.getProperty('dx');
  883. // var dy = evt.getProperty('dy');
  884. // ignoreMove = true;
  885. //
  886. // for (var i = 0; i < cells.length; i++)
  887. // {
  888. // var state = graph.view.getState(cells[i]);
  889. //
  890. // if (state != null && state.style['mindmapRoot'] == '1')
  891. // {
  892. // // TODO: Move subtree by same dx/dy
  893. // //layout.execute(model.getParent(state.cell), state.cell);
  894. //
  895. // // Gets the subtree from cell downwards
  896. // var tmp = [];
  897. // graph.traverse(cells[i], true, function(vertex)
  898. // {
  899. // tmp.push(vertex);
  900. //
  901. // return true;
  902. // });
  903. //
  904. // mxUtils.remove(cells[i], tmp);
  905. // graph.moveCells(tmp, dx, dy);
  906. // }
  907. // }
  908. //
  909. // ignoreMove = false;
  910. // }
  911. // });
  912. // Defines a new class for all icons
  913. function mxIconSet(state)
  914. {
  915. this.images = [];
  916. var graph = state.view.graph;
  917. // Icon1
  918. // var img = mxUtils.createImage('images/handle-connect.png');
  919. // img.setAttribute('title', 'Duplicate');
  920. // img.style.position = 'absolute';
  921. // img.style.cursor = 'pointer';
  922. // img.style.width = '26px';
  923. // img.style.height = '26px';
  924. // img.style.left = (state.x - 13) + 'px';
  925. // img.style.top = (state.getCenterY() - 13) + 'px';
  926. //
  927. // mxEvent.addGestureListeners(img,
  928. // mxUtils.bind(this, function(evt)
  929. // {
  930. // var s = graph.gridSize;
  931. // graph.setSelectionCells(graph.moveCells([state.cell], s, s, true));
  932. // mxEvent.consume(evt);
  933. // this.destroy();
  934. // })
  935. // );
  936. //
  937. // state.view.graph.container.appendChild(img);
  938. // this.images.push(img);
  939. // Delete
  940. var img = mxUtils.createImage('plugins/trees/handle-move.gif');
  941. img.setAttribute('title', 'Move Cell without Subtree');
  942. img.style.position = 'absolute';
  943. img.style.cursor = 'pointer';
  944. img.style.width = '26px';
  945. img.style.height = '26px';
  946. img.style.left = (state.getCenterX() - 13) + 'px';
  947. img.style.top = (state.getCenterY() - 13) + 'px';
  948. mxEvent.addGestureListeners(img, mxUtils.bind(this, function(evt)
  949. {
  950. graph.stopEditing(false);
  951. ui.hoverIcons.reset();
  952. if (!graph.isCellSelected(state.cell))
  953. {
  954. graph.setSelectionCell(state.cell);
  955. }
  956. graph.graphHandler.start(state.cell, mxEvent.getClientX(evt), mxEvent.getClientY(evt));
  957. graph.graphHandler.cells = [state.cell];
  958. graph.graphHandler.bounds = graph.graphHandler.graph.getView().getBounds(graph.graphHandler.cells);
  959. graph.graphHandler.pBounds = graph.graphHandler.getPreviewBounds(graph.graphHandler.cells);
  960. graph.graphHandler.cellWasClicked = true;
  961. graph.isMouseDown = true;
  962. graph.isMouseTrigger = mxEvent.isMouseEvent(evt);
  963. mxEvent.consume(evt);
  964. // Disables dragging the image
  965. mxEvent.consume(evt);
  966. this.destroy();
  967. }));
  968. // mxEvent.addListener(img, 'click',
  969. // mxUtils.bind(this, function(evt)
  970. // {
  971. // console.log('here', graph.graphHandler.dx);
  972. //
  973. // if (Math.abs(graph.graphHandler.currentDx) < graph.tolerance)
  974. // {
  975. // graph.setSelectionCell(state.cell);
  976. // }
  977. // })
  978. // );
  979. state.view.graph.container.appendChild(img);
  980. this.images.push(img);
  981. };
  982. mxIconSet.prototype.destroy = function()
  983. {
  984. if (this.images != null)
  985. {
  986. for (var i = 0; i < this.images.length; i++)
  987. {
  988. var img = this.images[i];
  989. img.parentNode.removeChild(img);
  990. }
  991. }
  992. this.images = null;
  993. };
  994. // Defines the tolerance before removing the icons
  995. var iconTolerance = 20;
  996. // Shows icons if the mouse is over a cell
  997. graph.addMouseListener(
  998. {
  999. currentState: null,
  1000. currentIconSet: null,
  1001. mouseDown: function(sender, me)
  1002. {
  1003. // Hides icons on mouse down
  1004. if (this.currentState != null)
  1005. {
  1006. this.dragLeave(me.getEvent(), this.currentState);
  1007. this.currentState = null;
  1008. }
  1009. // TODO: Fix single cell movement on touch devices
  1010. // if (mxEvent.isTouchEvent(me.getEvent()))
  1011. // {
  1012. // this.mouseMove(sender, me);
  1013. // }
  1014. },
  1015. mouseMove: function(sender, me)
  1016. {
  1017. if (this.currentState != null && (me.getState() == this.currentState ||
  1018. me.getState() == null))
  1019. {
  1020. var tol = iconTolerance;
  1021. var tmp = new mxRectangle(me.getGraphX() - tol,
  1022. me.getGraphY() - tol, 2 * tol, 2 * tol);
  1023. if (mxUtils.intersects(tmp, this.currentState))
  1024. {
  1025. return;
  1026. }
  1027. }
  1028. var tmp = me.getState();
  1029. // Ignores everything but vertices
  1030. if ((graph.isMouseDown && !mxEvent.isTouchEvent(me.getEvent())) ||
  1031. graph.isEditing() || (tmp != null &&
  1032. (!graph.getModel().isVertex(tmp.cell) || !isTreeCell(me.getCell()))))
  1033. {
  1034. tmp = null;
  1035. }
  1036. if (tmp != this.currentState)
  1037. {
  1038. if (this.currentState != null)
  1039. {
  1040. this.dragLeave(me.getEvent(), this.currentState);
  1041. }
  1042. this.currentState = tmp;
  1043. if (this.currentState != null)
  1044. {
  1045. this.dragEnter(me.getEvent(), this.currentState);
  1046. }
  1047. }
  1048. },
  1049. mouseUp: function(sender, me) { },
  1050. dragEnter: function(evt, state)
  1051. {
  1052. if (this.currentIconSet == null)
  1053. {
  1054. this.currentIconSet = new mxIconSet(state);
  1055. }
  1056. },
  1057. dragLeave: function(evt, state)
  1058. {
  1059. if (this.currentIconSet != null)
  1060. {
  1061. this.currentIconSet.destroy();
  1062. this.currentIconSet = null;
  1063. }
  1064. }
  1065. });
  1066. // Adds sidebar entries
  1067. var sb = ui.sidebar;
  1068. sb.addPalette('trees', 'Trees', true, function(content)
  1069. {
  1070. (function()
  1071. {
  1072. var cell = new mxCell('Central Idea', new mxGeometry(0, 20, 100, 40),
  1073. 'ellipse;whiteSpace=wrap;html=1;align=center;' +
  1074. 'collapsible=0;container=1;recursiveResize=0;');
  1075. graph.setAttributeForCell(cell, 'treeRoot', '1');
  1076. cell.vertex = true;
  1077. var cell2 = new mxCell('Branch', new mxGeometry(160, 0, 80, 20),
  1078. 'whiteSpace=wrap;html=1;shape=partialRectangle;top=0;left=0;bottom=1;right=0;points=[[0,1],[1,1]];' +
  1079. 'strokeColor=#000000;fillColor=none;align=center;verticalAlign=bottom;routingCenterY=0.5;' +
  1080. 'snapToPoint=1;collapsible=0;container=1;recursiveResize=0;autosize=1;');
  1081. cell2.vertex = true;
  1082. var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=entityRelationEdgeStyle;' +
  1083. 'startArrow=none;endArrow=none;segment=10;curved=1;');
  1084. edge.geometry.relative = true;
  1085. edge.edge = true;
  1086. cell.insertEdge(edge, true);
  1087. cell2.insertEdge(edge, false);
  1088. var cell3 = new mxCell('Sub Topic', new mxGeometry(160, 40, 72, 26),
  1089. 'whiteSpace=wrap;html=1;rounded=1;arcSize=50;align=center;verticalAlign=middle;' +
  1090. 'collapsible=0;container=1;recursiveResize=0;strokeWidth=1;autosize=1;spacing=4;');
  1091. cell3.vertex = true;
  1092. var edge2 = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=entityRelationEdgeStyle;' +
  1093. 'startArrow=none;endArrow=none;segment=10;curved=1;');
  1094. edge2.geometry.setTerminalPoint(new mxPoint(-40, 40), true);
  1095. edge2.geometry.relative = true;
  1096. edge2.edge = true;
  1097. cell.insertEdge(edge2, true);
  1098. cell3.insertEdge(edge2, false);
  1099. content.appendChild(sb.createVertexTemplateFromCells([edge, edge2, cell, cell2, cell3], 240, 66, 'Mindmap'));
  1100. })();
  1101. (function()
  1102. {
  1103. var cell = new mxCell('Central Idea', new mxGeometry(0, 0, 100, 40),
  1104. 'ellipse;whiteSpace=wrap;html=1;align=center;' +
  1105. 'collapsible=0;container=1;recursiveResize=0;');
  1106. graph.setAttributeForCell(cell, 'treeRoot', '1');
  1107. cell.vertex = true;
  1108. content.appendChild(sb.createVertexTemplateFromCells([cell], 100, 40, 'Central Idea'));
  1109. })();
  1110. (function()
  1111. {
  1112. var cell = new mxCell('Branch', new mxGeometry(0, 0, 80, 20),
  1113. 'whiteSpace=wrap;html=1;shape=partialRectangle;top=0;left=0;bottom=1;right=0;points=[[0,1],[1,1]];' +
  1114. 'strokeColor=#000000;fillColor=none;align=center;verticalAlign=bottom;routingCenterY=0.5;' +
  1115. 'snapToPoint=1;collapsible=0;container=1;recursiveResize=0;autosize=1;');
  1116. cell.vertex = true;
  1117. var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=entityRelationEdgeStyle;' +
  1118. 'startArrow=none;endArrow=none;segment=10;curved=1;');
  1119. edge.geometry.setTerminalPoint(new mxPoint(-40, 40), true);
  1120. edge.geometry.relative = true;
  1121. edge.edge = true;
  1122. cell.insertEdge(edge, false);
  1123. content.appendChild(sb.createVertexTemplateFromCells([edge, cell], 80, 20, 'Branch'));
  1124. })();
  1125. (function()
  1126. {
  1127. var cell = new mxCell('Sub Topic', new mxGeometry(0, 0, 72, 26),
  1128. 'whiteSpace=wrap;html=1;rounded=1;arcSize=50;align=center;verticalAlign=middle;' +
  1129. 'collapsible=0;container=1;recursiveResize=0;strokeWidth=1;autosize=1;spacing=4;');
  1130. cell.vertex = true;
  1131. var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=entityRelationEdgeStyle;' +
  1132. 'startArrow=none;endArrow=none;segment=10;curved=1;');
  1133. edge.geometry.setTerminalPoint(new mxPoint(-40, 40), true);
  1134. edge.geometry.relative = true;
  1135. edge.edge = true;
  1136. cell.insertEdge(edge, false);
  1137. content.appendChild(sb.createVertexTemplateFromCells([edge, cell], 72, 26, 'Sub Topic'));
  1138. })();
  1139. (function()
  1140. {
  1141. var cell = new mxCell('Organization', new mxGeometry(60, 0, 120, 60),
  1142. 'whiteSpace=wrap;html=1;align=center;' +
  1143. 'collapsible=0;container=1;recursiveResize=0;');
  1144. graph.setAttributeForCell(cell, 'treeRoot', '1');
  1145. cell.vertex = true;
  1146. var cell2 = new mxCell('Division', new mxGeometry(0, 100, 100, 60),
  1147. 'whiteSpace=wrap;html=1;align=center;verticalAlign=middle;' +
  1148. 'collapsible=0;container=1;recursiveResize=0;');
  1149. cell2.vertex = true;
  1150. var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=elbowEdgeStyle;elbow=vertical;' +
  1151. 'startArrow=none;endArrow=none;rounded=0;');
  1152. edge.geometry.relative = true;
  1153. edge.edge = true;
  1154. cell.insertEdge(edge, true);
  1155. cell2.insertEdge(edge, false);
  1156. var cell3 = new mxCell('Division', new mxGeometry(140, 100, 100, 60),
  1157. 'whiteSpace=wrap;html=1;align=center;verticalAlign=middle;' +
  1158. 'collapsible=0;container=1;recursiveResize=0;');
  1159. cell3.vertex = true;
  1160. var edge2 = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=elbowEdgeStyle;elbow=vertical;' +
  1161. 'startArrow=none;endArrow=none;rounded=0;');
  1162. edge2.geometry.relative = true;
  1163. edge2.edge = true;
  1164. cell.insertEdge(edge2, true);
  1165. cell3.insertEdge(edge2, false);
  1166. content.appendChild(sb.createVertexTemplateFromCells([edge, edge2, cell, cell2, cell3], 240, 160, 'Orgchart'));
  1167. })();
  1168. (function()
  1169. {
  1170. var cell = new mxCell('Tree Root', new mxGeometry(0, 0, 120, 60),
  1171. 'whiteSpace=wrap;html=1;align=center;' +
  1172. 'collapsible=0;container=1;recursiveResize=0;');
  1173. graph.setAttributeForCell(cell, 'treeRoot', '1');
  1174. cell.vertex = true;
  1175. content.appendChild(sb.createVertexTemplateFromCells([cell], 120, 60, 'Tree Root'));
  1176. })();
  1177. (function()
  1178. {
  1179. var cell = new mxCell('Sub Tree', new mxGeometry(0, 0, 100, 60),
  1180. 'whiteSpace=wrap;html=1;align=center;verticalAlign=middle;' +
  1181. 'collapsible=0;container=1;recursiveResize=0;');
  1182. cell.vertex = true;
  1183. var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=elbowEdgeStyle;elbow=vertical;' +
  1184. 'startArrow=none;endArrow=none;rounded=0;');
  1185. edge.geometry.setTerminalPoint(new mxPoint(0, -40), true);
  1186. edge.geometry.relative = true;
  1187. edge.edge = true;
  1188. cell.insertEdge(edge, false);
  1189. content.appendChild(sb.createVertexTemplateFromCells([edge, cell], 100, 60, 'Sub Tree'));
  1190. })();
  1191. (function()
  1192. {
  1193. var cell = new mxCell('Sub Section', new mxGeometry(0, 0, 100, 60),
  1194. 'whiteSpace=wrap;html=1;align=center;verticalAlign=middle;' +
  1195. 'collapsible=0;container=1;recursiveResize=0;');
  1196. cell.vertex = true;
  1197. var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=orthogonalEdgeStyle;' +
  1198. 'startArrow=none;endArrow=none;rounded=0;targetPortConstraint=eastwest;sourcePortConstraint=northsouth;');
  1199. edge.geometry.setTerminalPoint(new mxPoint(110, -40), true);
  1200. edge.geometry.relative = true;
  1201. edge.edge = true;
  1202. cell.insertEdge(edge, false);
  1203. var cell2 = new mxCell('Sub Section', new mxGeometry(120, 0, 100, 60),
  1204. 'whiteSpace=wrap;html=1;align=center;verticalAlign=middle;' +
  1205. 'collapsible=0;container=1;recursiveResize=0;');
  1206. cell2.vertex = true;
  1207. var edge2 = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=orthogonalEdgeStyle;' +
  1208. 'startArrow=none;endArrow=none;rounded=0;targetPortConstraint=eastwest;sourcePortConstraint=northsouth;');
  1209. edge2.geometry.setTerminalPoint(new mxPoint(110, -40), true);
  1210. edge2.geometry.relative = true;
  1211. edge2.edge = true;
  1212. cell2.insertEdge(edge2, false);
  1213. content.appendChild(sb.createVertexTemplateFromCells([edge, edge2, cell, cell2], 220, 60, 'Sub Sections'));
  1214. })();
  1215. });
  1216. // Collapses default sidebar entry and inserts this before
  1217. var c = ui.sidebar.container;
  1218. var general = c.getElementsByTagName('a')[0];
  1219. general.click();
  1220. c.insertBefore(c.lastChild.previousSibling, general);
  1221. c.insertBefore(c.lastChild, general);
  1222. });