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.

390 lines
10 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. 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. return new Edge(properties, this.body, this.options)
  303. }
  304. markAllEdgesAsDirty() {
  305. for (var edgeId in this.body.edges) {
  306. this.body.edges[edgeId].edgeType.colorDirty = true;
  307. }
  308. }
  309. /**
  310. * Reconnect all edges
  311. * @private
  312. */
  313. reconnectEdges() {
  314. var id;
  315. var nodes = this.body.nodes;
  316. var edges = this.body.edges;
  317. for (id in nodes) {
  318. if (nodes.hasOwnProperty(id)) {
  319. nodes[id].edges = [];
  320. }
  321. }
  322. for (id in edges) {
  323. if (edges.hasOwnProperty(id)) {
  324. var edge = edges[id];
  325. edge.from = null;
  326. edge.to = null;
  327. edge.connect();
  328. }
  329. }
  330. }
  331. getConnectedNodes(edgeId) {
  332. let nodeList = [];
  333. if (this.body.edges[edgeId] !== undefined) {
  334. let edge = this.body.edges[edgeId];
  335. if (edge.fromId) {nodeList.push(edge.fromId);}
  336. if (edge.toId) {nodeList.push(edge.toId);}
  337. }
  338. return nodeList;
  339. }
  340. }
  341. export default EdgesHandler;