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.

368 lines
9.4 KiB

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