Graph.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. /**
  2. *$id:Action 。JS,V 2017-12-19
  3. *$author shen.zhi
  4. */
  5. /**
  6. * Constructs a new graph instance. Note that the constructor does not take a
  7. * container because the graph instance is needed for creating the UI, which
  8. * in turn will create the container for the graph. Hence, the container is
  9. * assigned later in EditorUi.
  10. */
  11. Graph = function(container, model, renderHint, stylesheet)
  12. {
  13. mxGraph.call(this, container, model, renderHint, stylesheet);
  14. this.setConnectable(false);
  15. this.setDropEnabled(true);
  16. this.setPanning(true);
  17. this.setTooltips(!mxClient.IS_TOUCH);
  18. this.setAllowLoops(true);
  19. this.allowAutoPanning = true;
  20. this.connectionHandler.setCreateTarget(true);
  21. // Sets the style to be used when an elbow edge is double clicked
  22. this.alternateEdgeStyle = 'vertical';
  23. if (stylesheet == null)
  24. {
  25. this.loadStylesheet();
  26. }
  27. // Creates rubberband selection
  28. var rubberband = new mxRubberband(this);
  29. this.getRubberband = function()
  30. {
  31. return rubberband;
  32. };
  33. // Shows hand cursor while panning
  34. this.panningHandler.addListener(mxEvent.PAN_START, mxUtils.bind(this, function()
  35. {
  36. this.container.style.cursor = 'pointer';
  37. }));
  38. this.panningHandler.addListener(mxEvent.PAN_END, mxUtils.bind(this, function()
  39. {
  40. this.container.style.cursor = 'default';
  41. }));
  42. // Adds support for HTML labels via style. Note: Currently, only the Java
  43. // backend supports HTML labels but CSS support is limited to the following:
  44. // http://docs.oracle.com/javase/6/docs/api/index.html?javax/swing/text/html/CSS.html
  45. this.isHtmlLabel = function(cell)
  46. {
  47. var state = this.view.getState(cell);
  48. var style = (state != null) ? state.style : this.getCellStyle(cell);
  49. return style['html'] == '1';
  50. };
  51. // Unlocks all cells
  52. this.isCellLocked = function(cell)
  53. {
  54. return false;
  55. };
  56. // Tap and hold brings up context menu.
  57. // Tolerance slightly below graph tolerance is better.
  58. this.connectionHandler.tapAndHoldTolerance = 16;
  59. // Tap and hold on background starts rubberband on cell starts connecting
  60. var connectionHandlerTapAndHold = this.connectionHandler.tapAndHold;
  61. this.connectionHandler.tapAndHold = function(me, state)
  62. {
  63. if (state == null)
  64. {
  65. if (!this.graph.panningHandler.active)
  66. {
  67. rubberband.start(me.getGraphX(), me.getGraphY());
  68. this.graph.panningHandler.panningTrigger = false;
  69. }
  70. }
  71. else if (tapAndHoldStartsConnection)
  72. {
  73. connectionHandlerTapAndHold.apply(this, arguments);
  74. }
  75. else if (this.graph.isCellSelected(state.cell) && this.graph.getSelectionCount() > 1)
  76. {
  77. this.graph.removeSelectionCell(state.cell);
  78. }
  79. };
  80. if (touchStyle)
  81. {
  82. this.initTouch();
  83. }
  84. };
  85. // Graph inherits from mxGraph
  86. mxUtils.extend(Graph, mxGraph);
  87. /**
  88. * Allows to all values in fit.
  89. */
  90. Graph.prototype.minFitScale = null;
  91. /**
  92. * Allows to all values in fit.
  93. */
  94. Graph.prototype.maxFitScale = null;
  95. /**
  96. * Loads the stylesheet for this graph.
  97. */
  98. Graph.prototype.loadStylesheet = function()
  99. {
  100. var node = mxUtils.load(STYLE_PATH + '/default.xml').getDocumentElement();
  101. var dec = new mxCodec(node.ownerDocument);
  102. dec.decode(node, this.getStylesheet());
  103. };
  104. /**
  105. * Inverts the elbow edge style without removing existing styles.
  106. */
  107. Graph.prototype.flipEdge = function(edge)
  108. {
  109. if (edge != null)
  110. {
  111. var state = this.view.getState(edge);
  112. var style = (state != null) ? state.style : this.getCellStyle(edge);
  113. if (style != null)
  114. {
  115. var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW,
  116. mxConstants.ELBOW_HORIZONTAL);
  117. var value = (elbow == mxConstants.ELBOW_HORIZONTAL) ?
  118. mxConstants.ELBOW_VERTICAL : mxConstants.ELBOW_HORIZONTAL;
  119. this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]);
  120. }
  121. }
  122. };
  123. /**
  124. * Disables folding for non-swimlanes.
  125. */
  126. Graph.prototype.isCellFoldable = function(cell)
  127. {
  128. return this.foldingEnabled && this.isSwimlane(cell);
  129. };
  130. /**
  131. * Disables drill-down for non-swimlanes.
  132. */
  133. Graph.prototype.isValidRoot = function(cell)
  134. {
  135. return this.isSwimlane(cell);
  136. };
  137. /**
  138. * Overrides createGroupCell to set the group style for new groups to 'group'.
  139. */
  140. Graph.prototype.createGroupCell = function()
  141. {
  142. var group = mxGraph.prototype.createGroupCell.apply(this, arguments);
  143. group.setStyle('group');
  144. return group;
  145. };
  146. /**
  147. * Overrides tooltips to show position and size
  148. */
  149. Graph.prototype.getTooltipForCell = function(cell)
  150. {
  151. var tip = '';
  152. if (this.getModel().isVertex(cell))
  153. {
  154. var geo = this.getCellGeometry(cell);
  155. var f2 = function(x)
  156. {
  157. return Math.round(parseFloat(x) * 100) / 100;
  158. };
  159. var f3 = function(val){
  160. if(val==null||val==""){
  161. return "暂无绑定设备";
  162. }
  163. var i = val.indexOf("\n");
  164. if(i>0){
  165. val = val.substr(0,i);
  166. }
  167. i = val.indexOf("—");
  168. if(i>0){
  169. val = val.substr(0,i);
  170. }
  171. return val;
  172. };
  173. if (geo != null)
  174. {
  175. if (tip == null)
  176. {
  177. tip = '';
  178. }
  179. else if (tip.length > 0)
  180. {
  181. tip += '\n';
  182. }
  183. if($("#displayIp").val()=="1"){
  184. tip += f3(cell.getText());
  185. }else{
  186. tip += 'X: ' + f2(geo.x) + '\nY: ' + f2(geo.y) + '\nW: ' + f2(geo.width) + '\nH: ' + f2(geo.height);
  187. }
  188. }
  189. }
  190. else if (this.getModel().isEdge(cell))
  191. {
  192. tip = mxGraph.prototype.getTooltipForCell.apply(this, arguments);
  193. }
  194. return tip;
  195. };
  196. /**
  197. * Returns the label for the given cell.
  198. */
  199. Graph.prototype.convertValueToString = function(cell)
  200. {
  201. if (cell.value != null && typeof(cell.value) == 'object')
  202. {
  203. return cell.value.getAttribute('label');
  204. }
  205. return mxGraph.prototype.convertValueToString.apply(this, arguments);
  206. };
  207. /**
  208. * Handles label changes for XML user objects.
  209. */
  210. Graph.prototype.cellLabelChanged = function(cell, value, autoSize)
  211. {
  212. if (cell.value != null && typeof(cell.value) == 'object')
  213. {
  214. var tmp = cell.value.cloneNode(true);
  215. tmp.setAttribute('label', value);
  216. value = tmp;
  217. }
  218. mxGraph.prototype.cellLabelChanged.apply(this, arguments);
  219. };
  220. /**
  221. * Sets the link for the given cell.
  222. */
  223. Graph.prototype.setLinkForCell = function(cell, link)
  224. {
  225. var value = null;
  226. if (cell.value != null && typeof(cell.value) == 'object')
  227. {
  228. value = cell.value.cloneNode(true);
  229. }
  230. else
  231. {
  232. var doc = mxUtils.createXmlDocument();
  233. value = doc.createElement('UserObject');
  234. value.setAttribute('label', cell.value);
  235. }
  236. if (link != null && link.length > 0)
  237. {
  238. value.setAttribute('link', link);
  239. }
  240. else
  241. {
  242. value.removeAttribute('link');
  243. }
  244. this.model.setValue(cell, value);
  245. };
  246. /**
  247. * Returns the link for the given cell.
  248. */
  249. Graph.prototype.getLinkForCell = function(cell)
  250. {
  251. if (cell.value != null && typeof(cell.value) == 'object')
  252. {
  253. return cell.value.getAttribute('link');
  254. }
  255. return null;
  256. };
  257. /**
  258. * Customized graph for touch devices.
  259. */
  260. Graph.prototype.initTouch = function()
  261. {
  262. // Disables new connections via "hotspot"
  263. this.connectionHandler.marker.isEnabled = function()
  264. {
  265. return this.graph.connectionHandler.first != null;
  266. };
  267. // Hides menu when editing starts
  268. this.addListener(mxEvent.START_EDITING, function(sender, evt)
  269. {
  270. this.panningHandler.hideMenu();
  271. });
  272. // Context menu for touchstyle
  273. var showMenu = false;
  274. var menuCell = null;
  275. // Checks if native hit detection did not return anything and does custom
  276. // hit detection for edges to take into account the tolerance
  277. this.updateMouseEvent = function(me)
  278. {
  279. mxGraph.prototype.updateMouseEvent.apply(this, arguments);
  280. if (me.getState() == null)
  281. {
  282. var cell = this.getCellAt(me.graphX, me.graphY);
  283. if (this.getModel().isEdge(cell))
  284. {
  285. me.state = this.view.getState(cell);
  286. if (me.state != null && me.state.shape != null)
  287. {
  288. this.container.style.cursor = me.state.shape.node.style.cursor;
  289. }
  290. }
  291. }
  292. if (me.getState() == null)
  293. {
  294. this.container.style.cursor = 'default';
  295. }
  296. };
  297. // Handles popup menu on touch devices (tap selected cell)
  298. this.fireMouseEvent = function(evtName, me, sender)
  299. {
  300. if (evtName == mxEvent.MOUSE_DOWN)
  301. {
  302. if (!this.panningHandler.isMenuShowing())
  303. {
  304. menuCell = me.getCell();
  305. showMenu = (menuCell != null) ? this.isCellSelected(menuCell) : this.isSelectionEmpty();
  306. }
  307. else
  308. {
  309. showMenu = false;
  310. menuCell = null;
  311. }
  312. }
  313. else if (evtName == mxEvent.MOUSE_UP)
  314. {
  315. if (showMenu && !this.isEditing())
  316. {
  317. if (!this.panningHandler.isMenuShowing())
  318. {
  319. var x = mxEvent.getClientX(me.getEvent());
  320. var y = mxEvent.getClientY(me.getEvent());
  321. this.panningHandler.popup(x + 16, y, menuCell, me.getEvent());
  322. }
  323. showMenu = false;
  324. menuCell = null;
  325. me.consume();
  326. return;
  327. }
  328. showMenu = false;
  329. menuCell = null;
  330. }
  331. mxGraph.prototype.fireMouseEvent.apply(this, arguments);
  332. if (evtName == mxEvent.MOUSE_MOVE && me.isConsumed())
  333. {
  334. showMenu = false;
  335. menuCell = null;
  336. }
  337. };
  338. };
  339. /**
  340. * Implements touch devices.
  341. */
  342. (function()
  343. {
  344. // Touch-specific static overrides
  345. if (touchStyle)
  346. {
  347. // Sets constants for touch style
  348. mxConstants.HANDLE_SIZE = 16;
  349. mxConstants.LABEL_HANDLE_SIZE = 7;
  350. // Larger tolerance and grid for real touch devices
  351. if (mxClient.IS_TOUCH)
  352. {
  353. mxVertexHandler.prototype.tolerance = 4;
  354. mxEdgeHandler.prototype.tolerance = 6;
  355. Graph.prototype.tolerance = 14;
  356. Graph.prototype.gridSize = 20;
  357. // One finger pans (no rubberband selection) must start regardless of mouse button
  358. mxPanningHandler.prototype.selectOnPopup = false;
  359. mxPanningHandler.prototype.useLeftButtonForPanning = true;
  360. mxPanningHandler.prototype.isPanningTrigger = function(me)
  361. {
  362. var evt = me.getEvent();
  363. return (this.useLeftButtonForPanning && (this.ignoreCell || me.getState() == null)/* &&
  364. mxEvent.isLeftMouseButton(evt)*/) || (mxEvent.isControlDown(evt) &&
  365. mxEvent.isShiftDown(evt)) || (this.usePopupTrigger &&
  366. mxEvent.isPopupTrigger(evt));
  367. };
  368. }
  369. // Don't clear selection if multiple cells selected
  370. var graphHandlerMouseDown = mxGraphHandler.prototype.mouseDown;
  371. mxGraphHandler.prototype.mouseDown = function(sender, me)
  372. {
  373. graphHandlerMouseDown.apply(this, arguments);
  374. if (this.graph.isCellSelected(me.getCell()) && this.graph.getSelectionCount() > 1)
  375. {
  376. this.delayedSelection = false;
  377. }
  378. };
  379. // Changes order of panninghandler
  380. Graph.prototype.createHandlers = function(container)
  381. {
  382. this.tooltipHandler = new mxTooltipHandler(this);
  383. this.tooltipHandler.setEnabled(false);
  384. // Selection cells first
  385. this.selectionCellsHandler = new mxSelectionCellsHandler(this);
  386. this.panningHandler = new mxPanningHandler(this);
  387. this.panningHandler.panningEnabled = false;
  388. this.connectionHandler = new mxConnectionHandler(this);
  389. this.connectionHandler.setEnabled(false);
  390. this.graphHandler = new mxGraphHandler(this);
  391. };
  392. // On connect the target is selected and we clone the cell of the preview edge for insert
  393. mxConnectionHandler.prototype.selectCells = function(edge, target)
  394. {
  395. if (touchStyle && target != null)
  396. {
  397. this.graph.setSelectionCell(target);
  398. }
  399. else
  400. {
  401. this.graph.setSelectionCell(edge);
  402. }
  403. };
  404. // Overrides double click handling to use the tolerance
  405. // FIXME: Double click on edges in iPad needs focus on textarea
  406. var graphDblClick = mxGraph.prototype.dblClick;
  407. Graph.prototype.dblClick = function(evt, cell)
  408. {
  409. if (cell == null)
  410. {
  411. var pt = mxUtils.convertPoint(this.container,
  412. mxEvent.getClientX(evt), mxEvent.getClientY(evt));
  413. cell = this.getCellAt(pt.x, pt.y);
  414. }
  415. graphDblClick.call(this, evt, cell);
  416. };
  417. // Rounded edge and vertex handles
  418. var touchHandle = new mxImage(IMAGE_PATH + '/touch-handle.png', 16, 16);
  419. mxVertexHandler.prototype.handleImage = touchHandle;
  420. mxEdgeHandler.prototype.handleImage = touchHandle;
  421. mxOutline.prototype.sizerImage = touchHandle;
  422. // Pre-fetches touch handle
  423. new Image().src = touchHandle.src;
  424. // Adds connect icon to selected vertices
  425. var connectorSrc = IMAGE_PATH + '/touch-connector.png';
  426. var vertexHandlerInit = mxVertexHandler.prototype.init;
  427. mxVertexHandler.prototype.init = function()
  428. {
  429. vertexHandlerInit.apply(this, arguments);
  430. var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
  431. // Only show connector image on one cell and do not show on containers
  432. if (showConnectorImg && this.graph.connectionHandler.isEnabled() &&
  433. this.graph.isCellConnectable(this.state.cell) &&
  434. !this.graph.isValidRoot(this.state.cell) &&
  435. this.graph.getSelectionCount() == 1)
  436. {
  437. this.connectorImg = mxUtils.createImage(connectorSrc);
  438. this.connectorImg.style.cursor = 'pointer';
  439. this.connectorImg.style.width = '29px';
  440. this.connectorImg.style.height = '29px';
  441. this.connectorImg.style.position = 'absolute';
  442. if (!mxClient.IS_TOUCH)
  443. {
  444. this.connectorImg.setAttribute('title', mxResources.get('connect'));
  445. mxEvent.redirectMouseEvents(this.connectorImg, this.graph, this.state);
  446. }
  447. // Adds 2px tolerance
  448. this.connectorImg.style.padding = '2px';
  449. // Starts connecting on touch/mouse down
  450. mxEvent.addListener(this.connectorImg, md,
  451. mxUtils.bind(this, function(evt)
  452. {
  453. this.graph.panningHandler.hideMenu();
  454. var pt = mxUtils.convertPoint(this.graph.container,
  455. mxEvent.getClientX(evt), mxEvent.getClientY(evt));
  456. this.graph.connectionHandler.start(this.state, pt.x, pt.y);
  457. this.graph.isMouseDown = true;
  458. mxEvent.consume(evt);
  459. })
  460. );
  461. this.graph.container.appendChild(this.connectorImg);
  462. }
  463. this.redrawTools();
  464. };
  465. var vertexHandlerRedraw = mxVertexHandler.prototype.redraw;
  466. mxVertexHandler.prototype.redraw = function()
  467. {
  468. vertexHandlerRedraw.apply(this);
  469. this.redrawTools();
  470. };
  471. mxVertexHandler.prototype.redrawTools = function()
  472. {
  473. if (this.state != null && this.connectorImg != null)
  474. {
  475. // Top right for single-sizer
  476. if (mxVertexHandler.prototype.singleSizer)
  477. {
  478. this.connectorImg.style.left = (this.state.x + this.state.width - this.connectorImg.offsetWidth / 2) + 'px';
  479. this.connectorImg.style.top = (this.state.y - this.connectorImg.offsetHeight / 2) + 'px';
  480. }
  481. else
  482. {
  483. this.connectorImg.style.left = (this.state.x + this.state.width + mxConstants.HANDLE_SIZE / 2 + 4/* - 2 padding*/) + 'px';
  484. this.connectorImg.style.top = (this.state.y + (this.state.height - this.connectorImg.offsetHeight) / 2) + 'px';
  485. }
  486. }
  487. };
  488. var vertexHandlerDestroy = mxVertexHandler.prototype.destroy;
  489. mxVertexHandler.prototype.destroy = function(sender, me)
  490. {
  491. vertexHandlerDestroy.apply(this, arguments);
  492. if (this.connectorImg != null)
  493. {
  494. this.connectorImg.parentNode.removeChild(this.connectorImg);
  495. this.connectorImg = null;
  496. }
  497. };
  498. // Pre-fetches touch connector
  499. new Image().src = connectorSrc;
  500. }
  501. else // not touchStyle
  502. {
  503. mxConnectionHandler.prototype.connectImage = new mxImage(IMAGE_PATH + '/connector.png', 15, 15);
  504. }
  505. })();