JSGraphWidget.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. define([
  2. "dojo/_base/declare",
  3. "dojo/_base/lang",
  4. "src/nlsHPCC",
  5. "dojo/_base/array",
  6. "dojo/Evented",
  7. "@hpcc-js/common",
  8. "@hpcc-js/graph",
  9. "@hpcc-js/layout",
  10. "hpcc/GraphWidget",
  11. "src/ESPGraph",
  12. "src/Utility",
  13. "css!font-awesome/css/font-awesome.css"
  14. ], function (declare, lang, nlsHPCCMod, arrayUtil, Evented,
  15. hpccCommon, hpccGraph, hpccLayout,
  16. GraphWidget, ESPGraph, Utility) {
  17. var nlsHPCC = nlsHPCCMod.default;
  18. var faCharFactory = function (kind) {
  19. switch (kind) {
  20. case "2": return "\uf0c7"; // Disk Write
  21. case "3": return "\uf15d"; // sort
  22. case "5": return "\uf0b0"; // Filter
  23. case "6": return "\uf1e0"; // Split
  24. case "12": return "\uf039"; // First N
  25. case "15": return "\uf126"; // Lightweight Join
  26. case "17": return "\uf126"; // Lookup Join
  27. case "22": return "\uf1e6"; // Pipe Output
  28. case "23": return "\uf078"; // Funnel
  29. case "25": return "\uf0ce"; // Inline Dataset
  30. case "26": return "\uf074"; // distribute
  31. case "29": return "\uf005"; // Store Internal Result
  32. case "36": return "\uf128"; // If
  33. case "44": return "\uf0c7"; // write csv
  34. case "47": return "\uf0c7"; // write
  35. case "54": return "\uf013"; // Workunit Read
  36. case "56": return "\uf0c7"; // Spill
  37. case "59": return "\uf126"; // Merge
  38. case "61": return "\uf0c7"; // write xml
  39. case "82": return "\uf1c0"; // Projected Disk Read Spill
  40. case "88": return "\uf1c0"; // Projected Disk Read Spill
  41. case "92": return "\uf129"; // Limted Index Read
  42. case "93": return "\uf129"; // Limted Index Read
  43. case "99": return "\uf1c0"; // CSV Read
  44. case "105": return "\uf1c0"; // CSV Read
  45. case "7": return "\uf090"; // Project
  46. case "9": return "\uf0e2"; // Local Iterate
  47. case "16": return "\uf005"; // Output Internal
  48. case "19": return "\uf074"; // Hash Distribute
  49. case "21": return "\uf275"; // Normalize
  50. case "35": return "\uf0c7"; // CSV Write
  51. case "37": return "\uf0c7"; // Index Write
  52. case "71": return "\uf1c0"; // Disk Read Spill
  53. case "133": return "\uf0ce"; // Inline Dataset
  54. case "148": return "\uf0ce"; // Inline Dataset
  55. case "168": return "\uf275"; // Local Denormalize
  56. }
  57. return "\uf063";
  58. };
  59. var JSPlugin = declare([Evented], {
  60. KeyState_None: 0,
  61. KeyState_Shift: 1,
  62. KeyState_Control: 2,
  63. KeyState_Menu: 4,
  64. constructor: function (domNode) {
  65. this.graphData = new ESPGraph.Graph();
  66. this.graphWidget = new hpccGraph.Graph()
  67. .allowDragging(false)
  68. .showToolbar(false)
  69. ;
  70. var context = this;
  71. this.graphWidget.vertex_click = function (item, event) {
  72. context.emit("SelectionChanged", [item]);
  73. }
  74. this.graphWidget.edge_click = function (item, event) {
  75. context.emit("SelectionChanged", [item]);
  76. }
  77. this.graphWidget.vertex_dblclick = function (item, event) {
  78. context.emit("MouseDoubleClick", item, (event.shiftKey ? context.KeyState_Shift : 0) + (event.ctrlKey ? context.KeyState_Control : 0) + (event.altKey ? context.KeyState_Menu : 0));
  79. }
  80. this.messageWidget = new hpccCommon.TextBox()
  81. .shape_colorFill("#006CCC")
  82. .shape_colorStroke("#003666")
  83. .text_colorFill("#FFFFFF")
  84. ;
  85. this.layout = new hpccLayout.Layered()
  86. .target(domNode.id)
  87. .addLayer(this.messageWidget)
  88. .addLayer(this.graphWidget)
  89. .render()
  90. ;
  91. this._options = {};
  92. },
  93. option: function (key, _) {
  94. if (arguments.length < 1) throw Error("Invalid Call: option");
  95. if (arguments.length === 1) return this._options[key];
  96. this._options[key] = _ instanceof Array ? _.length > 0 : _;
  97. return this;
  98. },
  99. optionsReset: function (options) {
  100. options = options || this._optionsDefault;
  101. for (var key in options) {
  102. this.option(key, options[key]);
  103. }
  104. },
  105. setMessage: function (msg) {
  106. if (msg !== this._prevMsg) {
  107. this.messageWidget
  108. .text(msg)
  109. .visible(msg ? true : false)
  110. .render()
  111. ;
  112. if ((msg && this.graphWidget.visible()) || (!msg && !this.graphWidget.visible())) {
  113. this.graphWidget.visible(msg ? false : true).render();
  114. }
  115. this._prevMsg = msg;
  116. }
  117. },
  118. setScale: function (scale) {
  119. this.graphWidget.zoomTo(undefined, scale / 100);
  120. },
  121. centerOnItem: function (item, scaleToFit, widthOnly) {
  122. if (item) {
  123. if (scaleToFit) {
  124. var bbox = item.__widget.getBBox();
  125. this.graphWidget.zoomToBBox(bbox);
  126. } else {
  127. var bounds = this.graphWidget.getBounds([item.__widget]);
  128. this.graphWidget.centerOn(bounds);
  129. }
  130. } else {
  131. if (scaleToFit) {
  132. this.graphWidget.zoomToFit();
  133. } else {
  134. var bounds = this.graphWidget.getVertexBounds();
  135. this.graphWidget.centerOn(bounds);
  136. }
  137. }
  138. },
  139. getSelectionAsGlobalID: function () {
  140. var selection = this.graphWidget.selection();
  141. return selection.map(function (item) {
  142. return item.__hpcc_globalID;
  143. });
  144. },
  145. setSelectedAsGlobalID: function (globalIDs) {
  146. var selection = [];
  147. globalIDs.forEach(function (globalID, idx) {
  148. var item = this.getItem(globalID);
  149. if (item && item.__widget) {
  150. selection.push(item.__widget);
  151. }
  152. }, this);
  153. this.graphWidget.selection(selection);
  154. },
  155. getGlobalType: function (item) {
  156. return this.graphData.getGlobalTypeString(item);
  157. },
  158. getGlobalID: function (item) {
  159. return item.__hpcc_id;
  160. },
  161. getItem: function (globalID) {
  162. return this.graphData.idx[globalID];
  163. },
  164. setSelected: function (items) {
  165. this.graphWidget.selection(items);
  166. },
  167. getSelection: function () {
  168. return this.graphWidget.selection();
  169. },
  170. getSVG: function () {
  171. return ""; //TODO - Should be Serialized Layout to prevent re-calculation on prev/next ---
  172. },
  173. getDOT: function () {
  174. return "";
  175. },
  176. getVertices: function () {
  177. return this.graphData.vertices;
  178. },
  179. find: function (findText) {
  180. var findProp = "";
  181. var findTerm = findText;
  182. var findTextParts = findText.split(":");
  183. if (findTextParts.length > 1) {
  184. findProp = findTextParts[0];
  185. findTextParts.splice(0, 1);
  186. findTerm = findTextParts.join(":");
  187. }
  188. return arrayUtil.filter(this.graphData.vertices, function (item) {
  189. if (findProp) {
  190. if (item.hasOwnProperty(findProp)) {
  191. return (item[findProp].toString().toLowerCase().indexOf(findTerm.toLowerCase()) >= 0);
  192. }
  193. } else {
  194. for (var key in item) {
  195. if (item.hasOwnProperty(key) && item[key].toString().toLowerCase().indexOf(findTerm.toLowerCase()) >= 0) {
  196. return true;
  197. }
  198. }
  199. }
  200. return false;
  201. });
  202. },
  203. cleanObject: function (object) {
  204. var retVal = {};
  205. for (var key in object) {
  206. if (object.hasOwnProperty(key) && typeof object[key] !== "function") {
  207. retVal[key] = object[key];
  208. }
  209. }
  210. return retVal;
  211. },
  212. cleanObjects: function (objects) {
  213. return objects.map(function (object) {
  214. return this.cleanObject(object);
  215. }, this);
  216. },
  217. gatherTreeWithProperties: function (subgraph) {
  218. subgraph = subgraph || this.graphData.subgraphs[0];
  219. var retVal = subgraph.getProperties();
  220. retVal._children = [];
  221. arrayUtil.forEach(subgraph.__hpcc_subgraphs, function (subgraph, idx) {
  222. retVal._children.push(this.gatherTreeWithProperties(subgraph));
  223. }, this);
  224. arrayUtil.forEach(subgraph.__hpcc_vertices, function (vertex, idx) {
  225. retVal._children.push(vertex.getProperties());
  226. }, this);
  227. return retVal;
  228. },
  229. getProperties: function (item) {
  230. return item.getProperties();
  231. },
  232. getTreeWithProperties: function () {
  233. return [this.gatherTreeWithProperties()];
  234. },
  235. getSubgraphsWithProperties: function () {
  236. return this.cleanObjects(this.graphData.subgraphs);
  237. },
  238. getVerticesWithProperties: function () {
  239. return this.cleanObjects(this.graphData.vertices);
  240. },
  241. getEdgesWithProperties: function () {
  242. return this.cleanObjects(this.graphData.edges);
  243. },
  244. getLocalisedXGMML2: function (selectedItems, depth, distance, noSpills) {
  245. return this.graphData.getLocalisedXGMML(selectedItems, depth, distance, noSpills);
  246. },
  247. startLayout: function (layout) {
  248. var context = this;
  249. setTimeout(function (layout) {
  250. context.graphWidget
  251. .layout("Hierarchy")
  252. .render()
  253. ;
  254. context.emit("LayoutFinished", {});
  255. }, 100);
  256. },
  257. clear: function () {
  258. this.graphData.clear();
  259. this.graphWidget.clear();
  260. },
  261. mergeXGMML: function (xgmml) {
  262. this._loadXGMML(xgmml, true);
  263. },
  264. loadXGMML: function (xgmml) {
  265. this._loadXGMML(xgmml, false);
  266. },
  267. _loadXGMML: function (xgmml, merge) {
  268. if (merge) {
  269. this.graphData.merge(xgmml, {});
  270. } else {
  271. this.graphData.load(xgmml, {});
  272. }
  273. if (!this._skipRender) {
  274. this.rebuild(merge);
  275. }
  276. },
  277. format: function (labelTpl, obj) {
  278. var retVal = "";
  279. var lpos = labelTpl.indexOf("%");
  280. var rpos = -1;
  281. while (lpos >= 0) {
  282. retVal += labelTpl.substring(rpos + 1, lpos);
  283. rpos = labelTpl.indexOf("%", lpos + 1);
  284. if (rpos < 0) {
  285. console.log("Invalid Label Template");
  286. break;
  287. }
  288. var key = labelTpl.substring(lpos + 1, rpos);
  289. retVal += !key ? "%" : (obj[labelTpl.substring(lpos + 1, rpos)] || "");
  290. lpos = labelTpl.indexOf("%", rpos + 1);
  291. }
  292. retVal += labelTpl.substring(rpos + 1, labelTpl.length);
  293. return retVal.split("\\n").filter(function (line) {
  294. return !!line;
  295. }).join("\n");
  296. },
  297. rebuild: function (merge) {
  298. merge = merge || false;
  299. var vertices = [];
  300. var edges = [];
  301. var hierarchy = [];
  302. if (this.option("subgraph")) {
  303. arrayUtil.forEach(this.graphData.subgraphs, function (subgraph, idx) {
  304. if (!merge || !subgraph.__widget) {
  305. subgraph.__widget = new hpccGraph.Subgraph()
  306. .title(subgraph.__hpcc_id)
  307. ;
  308. subgraph.__widget.__hpcc_globalID = subgraph.__hpcc_id;
  309. }
  310. vertices.push(subgraph.__widget);
  311. }, this);
  312. }
  313. var labelTpl = this.option("vlabel");
  314. var tooltipTpl = this.option("vtooltip");
  315. arrayUtil.forEach(this.graphData.vertices, function (item, idx) {
  316. if (!this.option("vhidespills") || !item.isSpill()) {
  317. if (!merge || !item.__widget) {
  318. switch (item._kind) {
  319. case "point":
  320. item.__widget = new hpccCommon.Shape()
  321. .radius(7)
  322. ;
  323. break;
  324. default:
  325. if (this.option("vicon") && this.option("vlabel")) {
  326. item.__widget = new hpccGraph.Vertex()
  327. .faChar(faCharFactory(item._kind))
  328. ;
  329. } else if (this.option("vicon")) {
  330. item.__widget = new hpccCommon.Icon()
  331. .faChar(faCharFactory(item._kind))
  332. ;
  333. } else if (this.option("vlabel")) {
  334. item.__widget = new hpccCommon.TextBox()
  335. ;
  336. } else {
  337. item.__widget = new hpccCommon.Shape()
  338. .radius(7)
  339. ;
  340. }
  341. break;
  342. }
  343. item.__widget.__hpcc_globalID = item.__hpcc_id;
  344. }
  345. if (item.__widget.text) {
  346. var label = this.format(labelTpl, item);
  347. item.__widget.text(label);
  348. }
  349. if (item.__widget.tooltip) {
  350. var tooltip = this.format(tooltipTpl, item);
  351. item.__widget.tooltip(tooltip);
  352. }
  353. vertices.push(item.__widget);
  354. }
  355. }, this);
  356. labelTpl = this.option("elabel");
  357. tooltipTpl = this.option("etooltip");
  358. arrayUtil.forEach(this.graphData.edges, function (item, idx) {
  359. var source = item.getSource();
  360. var target = item.getTarget();
  361. if (source && target && (!this.option("vhidespills") || !target.isSpill())) {
  362. var label = this.format(labelTpl, item);
  363. var tooltip = this.format(tooltipTpl, item);
  364. var numSlaves = parseInt(item.NumSlaves);
  365. var numStarts = parseInt(item.NumStarts);
  366. var numStops = parseInt(item.NumStops);
  367. var started = numStarts > 0;
  368. var finished = numStops === numSlaves;
  369. var active = started && !finished;
  370. var strokeDasharray = null;
  371. var weight = 100;
  372. if (item._dependsOn) {
  373. weight = 10;
  374. strokeDasharray = "1,5";
  375. } else if (item._childGraph) {
  376. strokeDasharray = "5,5";
  377. } else if (item._isSpill) {
  378. weight = 25;
  379. strokeDasharray = "5,5,10,5";
  380. }
  381. if (this.option("vhidespills") && source.isSpill()) {
  382. label += "\n(" + nlsHPCC.Spill + ")";
  383. weight = 25;
  384. strokeDasharray = "5,5,10,5";
  385. while (source && source.isSpill()) {
  386. var inputs = source.getInVertices();
  387. source = inputs[0];
  388. }
  389. }
  390. if (source) {
  391. if (!merge || !item.__widget) {
  392. item.__widget = new hpccGraph.Edge()
  393. .sourceVertex(source.__widget)
  394. .targetVertex(target.__widget)
  395. .targetMarker("arrow")
  396. .weight(weight)
  397. .strokeDasharray(strokeDasharray)
  398. ;
  399. item.__widget.__hpcc_globalID = item.__hpcc_id;
  400. }
  401. item.__widget.text(label);
  402. item.__widget.tooltip(tooltip);
  403. item.__widget.classed({
  404. started: started && !finished && !active,
  405. finished: finished && !active,
  406. active: active
  407. });
  408. edges.push(item.__widget);
  409. }
  410. }
  411. }, this);
  412. if (this.option("subgraph")) {
  413. arrayUtil.forEach(this.graphData.subgraphs, function (subgraph, idx) {
  414. arrayUtil.forEach(subgraph.__hpcc_subgraphs, function (item, idx) {
  415. if (subgraph.__widget && item.__widget) {
  416. hierarchy.push({ parent: subgraph.__widget, child: item.__widget });
  417. }
  418. }, this);
  419. arrayUtil.forEach(subgraph.__hpcc_vertices, function (item, idx) {
  420. if (subgraph.__widget && item.__widget) {
  421. hierarchy.push({ parent: subgraph.__widget, child: item.__widget });
  422. }
  423. }, this);
  424. }, this);
  425. }
  426. this.graphWidget.data({ vertices: vertices, edges: edges, hierarchy: hierarchy, merge: merge });
  427. }
  428. });
  429. return declare("JSGraphWidget", [GraphWidget], {
  430. baseClass: "JSGraphWidget",
  431. constructor: function () {
  432. this.graphData = new ESPGraph.Graph();
  433. },
  434. hasOptions: function (key, val) {
  435. return this.hasPlugin();
  436. },
  437. _onOptionsApply: function () {
  438. var optionsValues = this.optionsForm.getValues();
  439. this.persist.setObj("options", optionsValues);
  440. this.optionsDropDown.closeDropDown();
  441. this._plugin.optionsReset(optionsValues);
  442. this.refreshRootState();
  443. delete this.xgmml;
  444. this._onRefreshScope();
  445. },
  446. _onOptionsReset: function () {
  447. this.optionsForm.setValues(this._plugin._optionsDefault);
  448. this._plugin.optionsReset(this._plugin._optionsDefault);
  449. },
  450. option: function (key, val) {
  451. return this._plugin.option.apply(this._plugin, arguments);
  452. },
  453. resize: function (size) {
  454. this.inherited(arguments);
  455. if (this.hasPlugin()) {
  456. this._plugin.layout
  457. .resize()
  458. .render()
  459. ;
  460. }
  461. },
  462. createPlugin: function () {
  463. if (!this.hasPlugin()) {
  464. this.persist = new Utility.Persist(this._persistID || "");
  465. var context = this;
  466. context._plugin = new JSPlugin(context.graphContentPane.domNode);
  467. context._plugin._optionsDefault = context.optionsForm.getValues();
  468. switch (context._persistID) {
  469. case "overview":
  470. context._plugin._optionsDefault.subgraph = ["on"];
  471. context._plugin._optionsDefault.vlabel = "";
  472. break;
  473. case "local":
  474. context._plugin._optionsDefault.subgraph = ["on"];
  475. context._plugin._optionsDefault.vhidespills = ["off"];
  476. break;
  477. default:
  478. context._plugin._optionsDefault.vhidespills = ["on"];
  479. break;
  480. }
  481. var optionsValues = lang.mixin({}, context._plugin._optionsDefault, context.persist.getObj("options"));
  482. context._plugin.optionsReset(optionsValues);
  483. context.optionsForm.setValues(optionsValues);
  484. context.version = {
  485. major: 6,
  486. minor: 0
  487. };
  488. context.registerEvents();
  489. context.refreshRootState();
  490. context.emit("ready");
  491. }
  492. },
  493. watchSplitter: function (splitter) {
  494. },
  495. watchSelect: function (select) {
  496. }
  497. });
  498. });