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.

392 lines
10 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. import Edge from "./components/Edge"
  5. import Label from "./components/shared/Label"
  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}, // boolean / {arrowScaleFactor:1} / {enabled: false, arrowScaleFactor:1}
  22. middle: {enabled: false, scaleFactor:1},
  23. from: {enabled: false, scaleFactor:1}
  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. },
  43. hidden: false,
  44. hoverWidth: 1.5,
  45. label: undefined,
  46. labelHighlightBold: true,
  47. length: undefined,
  48. physics: true,
  49. scaling:{
  50. min: 1,
  51. max: 15,
  52. label: {
  53. enabled: true,
  54. min: 14,
  55. max: 30,
  56. maxVisible: 30,
  57. drawThreshold: 5
  58. },
  59. customScalingFunction: function (min,max,total,value) {
  60. if (max === min) {
  61. return 0.5;
  62. }
  63. else {
  64. var scale = 1 / (max - min);
  65. return Math.max(0,(value - min)*scale);
  66. }
  67. }
  68. },
  69. selectionWidth: 1.5,
  70. selfReferenceSize:20,
  71. shadow:{
  72. enabled: false,
  73. color: 'rgba(0,0,0,0.5)',
  74. size:10,
  75. x:5,
  76. y:5
  77. },
  78. smooth: {
  79. enabled: true,
  80. type: "dynamic",
  81. forceDirection:'none',
  82. roundness: 0.5
  83. },
  84. title:undefined,
  85. width: 1,
  86. value: undefined
  87. };
  88. util.extend(this.options, this.defaultOptions);
  89. this.bindEventListeners();
  90. }
  91. bindEventListeners() {
  92. // this allows external modules to force all dynamic curves to turn static.
  93. this.body.emitter.on("_forceDisableDynamicCurves", (type) => {
  94. if (type === 'dynamic') {
  95. type = 'continuous';
  96. }
  97. let emitChange = false;
  98. for (let edgeId in this.body.edges) {
  99. if (this.body.edges.hasOwnProperty(edgeId)) {
  100. let edge = this.body.edges[edgeId];
  101. let edgeData = this.body.data.edges._data[edgeId];
  102. // only forcibly remove the smooth curve if the data has been set of the edge has the smooth curves defined.
  103. // this is because a change in the global would not affect these curves.
  104. if (edgeData !== undefined) {
  105. let edgeOptions = edgeData.smooth;
  106. if (edgeOptions !== undefined) {
  107. if (edgeOptions.enabled === true && edgeOptions.type === 'dynamic') {
  108. if (type === undefined) {
  109. edge.setOptions({smooth: false});
  110. }
  111. else {
  112. edge.setOptions({smooth: {type: type}});
  113. }
  114. emitChange = true;
  115. }
  116. }
  117. }
  118. }
  119. }
  120. if (emitChange === true) {
  121. this.body.emitter.emit("_dataChanged");
  122. }
  123. });
  124. // this is called when options of EXISTING nodes or edges have changed.
  125. this.body.emitter.on("_dataUpdated", () => {
  126. this.reconnectEdges();
  127. this.markAllEdgesAsDirty();
  128. });
  129. // refresh the edges. Used when reverting from hierarchical layout
  130. this.body.emitter.on("refreshEdges", this.refresh.bind(this));
  131. this.body.emitter.on("refresh", this.refresh.bind(this));
  132. this.body.emitter.on("destroy", () => {
  133. util.forEach(this.edgesListeners, (callback, event) => {
  134. if (this.body.data.edges)
  135. this.body.data.edges.off(event, callback);
  136. });
  137. delete this.body.functions.createEdge;
  138. delete this.edgesListeners.add;
  139. delete this.edgesListeners.update;
  140. delete this.edgesListeners.remove;
  141. delete this.edgesListeners;
  142. });
  143. }
  144. setOptions(options) {
  145. if (options !== undefined) {
  146. // use the parser from the Edge class to fill in all shorthand notations
  147. Edge.parseOptions(this.options, options);
  148. // handle multiple input cases for color
  149. if (options.color !== undefined) {
  150. this.markAllEdgesAsDirty();
  151. }
  152. // update smooth settings in all edges
  153. let dataChanged = false;
  154. if (options.smooth !== undefined) {
  155. for (let edgeId in this.body.edges) {
  156. if (this.body.edges.hasOwnProperty(edgeId)) {
  157. dataChanged = this.body.edges[edgeId].updateEdgeType() || dataChanged;
  158. }
  159. }
  160. }
  161. // update fonts in all edges
  162. if (options.font !== undefined) {
  163. // use the parser from the Label class to fill in all shorthand notations
  164. Label.parseOptions(this.options.font, options);
  165. for (let edgeId in this.body.edges) {
  166. if (this.body.edges.hasOwnProperty(edgeId)) {
  167. this.body.edges[edgeId].updateLabelModule();
  168. }
  169. }
  170. }
  171. // update the state of the variables if needed
  172. if (options.hidden !== undefined || options.physics !== undefined || dataChanged === true) {
  173. this.body.emitter.emit('_dataChanged');
  174. }
  175. }
  176. }
  177. /**
  178. * Load edges by reading the data table
  179. * @param {Array | DataSet | DataView} edges The data containing the edges.
  180. * @private
  181. * @private
  182. */
  183. setData(edges, doNotEmit = false) {
  184. var oldEdgesData = this.body.data.edges;
  185. if (edges instanceof DataSet || edges instanceof DataView) {
  186. this.body.data.edges = edges;
  187. }
  188. else if (Array.isArray(edges)) {
  189. this.body.data.edges = new DataSet();
  190. this.body.data.edges.add(edges);
  191. }
  192. else if (!edges) {
  193. this.body.data.edges = new DataSet();
  194. }
  195. else {
  196. throw new TypeError('Array or DataSet expected');
  197. }
  198. // TODO: is this null or undefined or false?
  199. if (oldEdgesData) {
  200. // unsubscribe from old dataset
  201. util.forEach(this.edgesListeners, (callback, event) => {oldEdgesData.off(event, callback);});
  202. }
  203. // remove drawn edges
  204. this.body.edges = {};
  205. // TODO: is this null or undefined or false?
  206. if (this.body.data.edges) {
  207. // subscribe to new dataset
  208. util.forEach(this.edgesListeners, (callback, event) => {this.body.data.edges.on(event, callback);});
  209. // draw all new nodes
  210. var ids = this.body.data.edges.getIds();
  211. this.add(ids, true);
  212. }
  213. if (doNotEmit === false) {
  214. this.body.emitter.emit("_dataChanged");
  215. }
  216. }
  217. /**
  218. * Add edges
  219. * @param {Number[] | String[]} ids
  220. * @private
  221. */
  222. add(ids, doNotEmit = false) {
  223. var edges = this.body.edges;
  224. var edgesData = this.body.data.edges;
  225. for (let i = 0; i < ids.length; i++) {
  226. var id = ids[i];
  227. var oldEdge = edges[id];
  228. if (oldEdge) {
  229. oldEdge.disconnect();
  230. }
  231. var data = edgesData.get(id, {"showInternalIds" : true});
  232. edges[id] = this.create(data);
  233. }
  234. if (doNotEmit === false) {
  235. this.body.emitter.emit("_dataChanged");
  236. }
  237. }
  238. /**
  239. * Update existing edges, or create them when not yet existing
  240. * @param {Number[] | String[]} ids
  241. * @private
  242. */
  243. update(ids) {
  244. var edges = this.body.edges;
  245. var edgesData = this.body.data.edges;
  246. var dataChanged = false;
  247. for (var i = 0; i < ids.length; i++) {
  248. var id = ids[i];
  249. var data = edgesData.get(id);
  250. var edge = edges[id];
  251. if (edge !== undefined) {
  252. // update edge
  253. edge.disconnect();
  254. dataChanged = edge.setOptions(data) || dataChanged; // if a support node is added, data can be changed.
  255. edge.connect();
  256. }
  257. else {
  258. // create edge
  259. this.body.edges[id] = this.create(data);
  260. dataChanged = true;
  261. }
  262. }
  263. if (dataChanged === true) {
  264. this.body.emitter.emit("_dataChanged");
  265. }
  266. else {
  267. this.body.emitter.emit("_dataUpdated");
  268. }
  269. }
  270. /**
  271. * Remove existing edges. Non existing ids will be ignored
  272. * @param {Number[] | String[]} ids
  273. * @private
  274. */
  275. remove(ids) {
  276. var edges = this.body.edges;
  277. for (var i = 0; i < ids.length; i++) {
  278. var id = ids[i];
  279. var edge = edges[id];
  280. if (edge !== undefined) {
  281. edge.cleanup();
  282. edge.disconnect();
  283. delete edges[id];
  284. }
  285. }
  286. this.body.emitter.emit("_dataChanged");
  287. }
  288. refresh() {
  289. let edges = this.body.edges;
  290. for (let edgeId in edges) {
  291. let edge = undefined;
  292. if (edges.hasOwnProperty(edgeId)) {
  293. edge = edges[edgeId];
  294. }
  295. let data = this.body.data.edges._data[edgeId];
  296. if (edge !== undefined && data !== undefined) {
  297. edge.setOptions(data);
  298. }
  299. }
  300. }
  301. create(properties) {
  302. var idField = this.body.data.edges._fieldId;
  303. var id = properties[idField];
  304. return new Edge(id, properties, this.body, this.options)
  305. }
  306. markAllEdgesAsDirty() {
  307. for (var edgeId in this.body.edges) {
  308. this.body.edges[edgeId].edgeType.colorDirty = true;
  309. }
  310. }
  311. /**
  312. * Reconnect all edges
  313. * @private
  314. */
  315. reconnectEdges() {
  316. var id;
  317. var nodes = this.body.nodes;
  318. var edges = this.body.edges;
  319. for (id in nodes) {
  320. if (nodes.hasOwnProperty(id)) {
  321. nodes[id].edges = [];
  322. }
  323. }
  324. for (id in edges) {
  325. if (edges.hasOwnProperty(id)) {
  326. var edge = edges[id];
  327. edge.from = null;
  328. edge.to = null;
  329. edge.connect();
  330. }
  331. }
  332. }
  333. getConnectedNodes(edgeId) {
  334. let nodeList = [];
  335. if (this.body.edges[edgeId] !== undefined) {
  336. let edge = this.body.edges[edgeId];
  337. if (edge.fromId) {nodeList.push(edge.fromId);}
  338. if (edge.toId) {nodeList.push(edge.toId);}
  339. }
  340. return nodeList;
  341. }
  342. }
  343. export default EdgesHandler;