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.

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