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.

408 lines
11 KiB

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