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

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. 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;