vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

437 lines
12 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. var util = require("../../util");
  2. var DataSet = require('../../DataSet');
  3. var DataView = require('../../DataView');
  4. var Edge = require("./components/Edge").default;
  5. var Label = require("./components/shared/Label").default;
  6. class EdgesHandler {
  7. constructor(body, images, groups) {
  8. this.body = body;
  9. this.images = images;
  10. this.groups = groups;
  11. // create the edge API in the body container
  12. this.body.functions.createEdge = this.create.bind(this);
  13. this.edgesListeners = {
  14. add: (event, params) => {this.add(params.items);},
  15. update: (event, params) => {this.update(params.items);},
  16. remove: (event, params) => {this.remove(params.items);}
  17. };
  18. this.options = {};
  19. this.defaultOptions = {
  20. arrows: {
  21. to: {enabled: false, scaleFactor:1, type: 'arrow'}, // boolean / {arrowScaleFactor:1} / {enabled: false, arrowScaleFactor:1}
  22. middle: {enabled: false, scaleFactor:1, type: 'arrow'},
  23. from: {enabled: false, scaleFactor:1, type: 'arrow'}
  24. },
  25. arrowStrikethrough: true,
  26. color: {
  27. color:'#848484',
  28. highlight:'#848484',
  29. hover: '#848484',
  30. inherit: 'from',
  31. opacity:1.0
  32. },
  33. dashes: false,
  34. font: {
  35. color: '#343434',
  36. size: 14, // px
  37. face: 'arial',
  38. background: 'none',
  39. strokeWidth: 2, // px
  40. strokeColor: '#ffffff',
  41. align:'horizontal',
  42. multi: false,
  43. vadjust: 0,
  44. bold: {
  45. mod: 'bold'
  46. },
  47. boldital: {
  48. mod: 'bold italic'
  49. },
  50. ital: {
  51. mod: 'italic'
  52. },
  53. mono: {
  54. mod: '',
  55. size: 15, // px
  56. face: 'courier new',
  57. vadjust: 2
  58. }
  59. },
  60. hidden: false,
  61. hoverWidth: 1.5,
  62. label: undefined,
  63. labelHighlightBold: true,
  64. length: undefined,
  65. physics: true,
  66. scaling:{
  67. min: 1,
  68. max: 15,
  69. label: {
  70. enabled: true,
  71. min: 14,
  72. max: 30,
  73. maxVisible: 30,
  74. drawThreshold: 5
  75. },
  76. customScalingFunction: function (min,max,total,value) {
  77. if (max === min) {
  78. return 0.5;
  79. }
  80. else {
  81. var scale = 1 / (max - min);
  82. return Math.max(0,(value - min)*scale);
  83. }
  84. }
  85. },
  86. selectionWidth: 1.5,
  87. selfReferenceSize:20,
  88. shadow:{
  89. enabled: false,
  90. color: 'rgba(0,0,0,0.5)',
  91. size:10,
  92. x:5,
  93. y:5
  94. },
  95. smooth: {
  96. enabled: true,
  97. type: "dynamic",
  98. forceDirection:'none',
  99. roundness: 0.5
  100. },
  101. title:undefined,
  102. width: 1,
  103. value: undefined
  104. };
  105. util.extend(this.options, this.defaultOptions);
  106. this.bindEventListeners();
  107. }
  108. bindEventListeners() {
  109. // this allows external modules to force all dynamic curves to turn static.
  110. this.body.emitter.on("_forceDisableDynamicCurves", (type, emit = true) => {
  111. if (type === 'dynamic') {
  112. type = 'continuous';
  113. }
  114. let dataChanged = false;
  115. for (let edgeId in this.body.edges) {
  116. if (this.body.edges.hasOwnProperty(edgeId)) {
  117. let edge = this.body.edges[edgeId];
  118. let edgeData = this.body.data.edges._data[edgeId];
  119. // only forcibly remove the smooth curve if the data has been set of the edge has the smooth curves defined.
  120. // this is because a change in the global would not affect these curves.
  121. if (edgeData !== undefined) {
  122. let edgeOptions = edgeData.smooth;
  123. if (edgeOptions !== undefined) {
  124. if (edgeOptions.enabled === true && edgeOptions.type === 'dynamic') {
  125. if (type === undefined) {
  126. edge.setOptions({smooth: false});
  127. }
  128. else {
  129. edge.setOptions({smooth: {type: type}});
  130. }
  131. dataChanged = true;
  132. }
  133. }
  134. }
  135. }
  136. }
  137. if (emit === true && dataChanged === true) {
  138. this.body.emitter.emit("_dataChanged");
  139. }
  140. });
  141. // this is called when options of EXISTING nodes or edges have changed.
  142. //
  143. // NOTE: Not true, called when options have NOT changed, for both existing as well as new nodes.
  144. // See update() for logic.
  145. // TODO: Verify and examine the consequences of this. It might still trigger when
  146. // non-option fields have changed, but then reconnecting edges is still useless.
  147. // Alternatively, it might also be called when edges are removed.
  148. //
  149. this.body.emitter.on("_dataUpdated", () => {
  150. this.reconnectEdges();
  151. });
  152. // refresh the edges. Used when reverting from hierarchical layout
  153. this.body.emitter.on("refreshEdges", this.refresh.bind(this));
  154. this.body.emitter.on("refresh", this.refresh.bind(this));
  155. this.body.emitter.on("destroy", () => {
  156. util.forEach(this.edgesListeners, (callback, event) => {
  157. if (this.body.data.edges)
  158. this.body.data.edges.off(event, callback);
  159. });
  160. delete this.body.functions.createEdge;
  161. delete this.edgesListeners.add;
  162. delete this.edgesListeners.update;
  163. delete this.edgesListeners.remove;
  164. delete this.edgesListeners;
  165. });
  166. }
  167. setOptions(options) {
  168. this.edgeOptions = options;
  169. if (options !== undefined) {
  170. // use the parser from the Edge class to fill in all shorthand notations
  171. Edge.parseOptions(this.options, options);
  172. // update smooth settings in all edges
  173. let dataChanged = false;
  174. if (options.smooth !== undefined) {
  175. for (let edgeId in this.body.edges) {
  176. if (this.body.edges.hasOwnProperty(edgeId)) {
  177. dataChanged = this.body.edges[edgeId].updateEdgeType() || dataChanged;
  178. }
  179. }
  180. }
  181. // update fonts in all edges
  182. if (options.font !== undefined) {
  183. // use the parser from the Label class to fill in all shorthand notations
  184. Label.parseOptions(this.options.font, options);
  185. for (let edgeId in this.body.edges) {
  186. if (this.body.edges.hasOwnProperty(edgeId)) {
  187. this.body.edges[edgeId].updateLabelModule();
  188. }
  189. }
  190. }
  191. // update the state of the variables if needed
  192. if (options.hidden !== undefined || options.physics !== undefined || dataChanged === true) {
  193. this.body.emitter.emit('_dataChanged');
  194. }
  195. }
  196. }
  197. /**
  198. * Load edges by reading the data table
  199. * @param {Array | DataSet | DataView} edges The data containing the edges.
  200. * @private
  201. * @private
  202. */
  203. setData(edges, doNotEmit = false) {
  204. var oldEdgesData = this.body.data.edges;
  205. if (edges instanceof DataSet || edges instanceof DataView) {
  206. this.body.data.edges = edges;
  207. }
  208. else if (Array.isArray(edges)) {
  209. this.body.data.edges = new DataSet();
  210. this.body.data.edges.add(edges);
  211. }
  212. else if (!edges) {
  213. this.body.data.edges = new DataSet();
  214. }
  215. else {
  216. throw new TypeError('Array or DataSet expected');
  217. }
  218. // TODO: is this null or undefined or false?
  219. if (oldEdgesData) {
  220. // unsubscribe from old dataset
  221. util.forEach(this.edgesListeners, (callback, event) => {oldEdgesData.off(event, callback);});
  222. }
  223. // remove drawn edges
  224. this.body.edges = {};
  225. // TODO: is this null or undefined or false?
  226. if (this.body.data.edges) {
  227. // subscribe to new dataset
  228. util.forEach(this.edgesListeners, (callback, event) => {this.body.data.edges.on(event, callback);});
  229. // draw all new nodes
  230. var ids = this.body.data.edges.getIds();
  231. this.add(ids, true);
  232. }
  233. this.body.emitter.emit('_adjustEdgesForHierarchicalLayout');
  234. if (doNotEmit === false) {
  235. this.body.emitter.emit("_dataChanged");
  236. }
  237. }
  238. /**
  239. * Add edges
  240. * @param {Number[] | String[]} ids
  241. * @private
  242. */
  243. add(ids, doNotEmit = false) {
  244. var edges = this.body.edges;
  245. var edgesData = this.body.data.edges;
  246. for (let i = 0; i < ids.length; i++) {
  247. var id = ids[i];
  248. var oldEdge = edges[id];
  249. if (oldEdge) {
  250. oldEdge.disconnect();
  251. }
  252. var data = edgesData.get(id, {"showInternalIds" : true});
  253. edges[id] = this.create(data);
  254. }
  255. this.body.emitter.emit('_adjustEdgesForHierarchicalLayout');
  256. if (doNotEmit === false) {
  257. this.body.emitter.emit("_dataChanged");
  258. }
  259. }
  260. /**
  261. * Update existing edges, or create them when not yet existing
  262. * @param {Number[] | String[]} ids
  263. * @private
  264. */
  265. update(ids) {
  266. var edges = this.body.edges;
  267. var edgesData = this.body.data.edges;
  268. var dataChanged = false;
  269. for (var i = 0; i < ids.length; i++) {
  270. var id = ids[i];
  271. var data = edgesData.get(id);
  272. var edge = edges[id];
  273. if (edge !== undefined) {
  274. // update edge
  275. edge.disconnect();
  276. dataChanged = edge.setOptions(data) || dataChanged; // if a support node is added, data can be changed.
  277. edge.connect();
  278. }
  279. else {
  280. // create edge
  281. this.body.edges[id] = this.create(data);
  282. dataChanged = true;
  283. }
  284. }
  285. if (dataChanged === true) {
  286. this.body.emitter.emit('_adjustEdgesForHierarchicalLayout');
  287. this.body.emitter.emit("_dataChanged");
  288. }
  289. else {
  290. this.body.emitter.emit("_dataUpdated");
  291. }
  292. }
  293. /**
  294. * Remove existing edges. Non existing ids will be ignored
  295. * @param {Number[] | String[]} ids
  296. * @private
  297. */
  298. remove(ids, emit = true) {
  299. if (ids.length === 0) return; // early out
  300. var edges = this.body.edges;
  301. for (var i = 0; i < ids.length; i++) {
  302. var id = ids[i];
  303. var edge = edges[id];
  304. if (edge !== undefined) {
  305. edge.remove();
  306. }
  307. }
  308. if (emit) {
  309. this.body.emitter.emit("_dataChanged");
  310. }
  311. }
  312. refresh() {
  313. let edges = this.body.edges;
  314. for (let edgeId in edges) {
  315. let edge = undefined;
  316. if (edges.hasOwnProperty(edgeId)) {
  317. edge = edges[edgeId];
  318. }
  319. let data = this.body.data.edges._data[edgeId];
  320. if (edge !== undefined && data !== undefined) {
  321. edge.setOptions(data);
  322. }
  323. }
  324. }
  325. create(properties) {
  326. return new Edge(properties, this.body, this.options, this.defaultOptions, this.edgeOptions)
  327. }
  328. /**
  329. * Reconnect all edges
  330. * @private
  331. */
  332. reconnectEdges() {
  333. var id;
  334. var nodes = this.body.nodes;
  335. var edges = this.body.edges;
  336. for (id in nodes) {
  337. if (nodes.hasOwnProperty(id)) {
  338. nodes[id].edges = [];
  339. }
  340. }
  341. for (id in edges) {
  342. if (edges.hasOwnProperty(id)) {
  343. var edge = edges[id];
  344. edge.from = null;
  345. edge.to = null;
  346. edge.connect();
  347. }
  348. }
  349. }
  350. getConnectedNodes(edgeId) {
  351. let nodeList = [];
  352. if (this.body.edges[edgeId] !== undefined) {
  353. let edge = this.body.edges[edgeId];
  354. if (edge.fromId !== undefined) {nodeList.push(edge.fromId);}
  355. if (edge.toId !== undefined) {nodeList.push(edge.toId);}
  356. }
  357. return nodeList;
  358. }
  359. /**
  360. * Scan for missing nodes and remove corresponding edges, if any.
  361. *
  362. * There is no direct relation between the nodes and the edges DataSet,
  363. * so the right place to do call this is in the handler for event `_dataUpdated`.
  364. */
  365. _updateState() {
  366. let edgesToDelete = [];
  367. for(let id in this.body.edges) {
  368. let edge = this.body.edges[id];
  369. let toNode = this.body.nodes[edge.toId];
  370. let fromNode = this.body.nodes[edge.fromId];
  371. // Skip clustering edges here, let the Clustering module handle those
  372. if ((toNode !== undefined && toNode.isCluster === true)
  373. || (fromNode !== undefined && fromNode.isCluster === true)) {
  374. continue;
  375. }
  376. if (toNode === undefined || fromNode === undefined) {
  377. edgesToDelete.push(id);
  378. }
  379. }
  380. this.remove(edgesToDelete, false);
  381. }
  382. }
  383. export default EdgesHandler;