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.

340 lines
8.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. selfReferenceSize:20,
  71. shadow:{
  72. enabled: false,
  73. size:10,
  74. x:5,
  75. y:5
  76. },
  77. smooth: {
  78. enabled: true,
  79. dynamic: true,
  80. type: "continuous",
  81. roundness: 0.5
  82. },
  83. title:undefined,
  84. width: 1,
  85. widthSelectionMultiplier: 2,
  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 edgeOptions = this.body.edges[edgeId].options.smooth;
  95. if (edgeOptions.enabled === true && edgeOptions.dynamic === true) {
  96. if (type === undefined) {
  97. edge.setOptions({smooth:false});
  98. }
  99. else {
  100. edge.setOptions({smooth:{dynamic:false, type:type}});
  101. }
  102. emitChange = true;
  103. }
  104. }
  105. }
  106. if (emitChange === true) {
  107. this.body.emitter.emit("_dataChanged");
  108. }
  109. });
  110. // this is called when options of EXISTING nodes or edges have changed.
  111. this.body.emitter.on("_dataUpdated", () => {
  112. this.reconnectEdges();
  113. this.markAllEdgesAsDirty();
  114. });
  115. }
  116. setOptions(options) {
  117. if (options !== undefined) {
  118. // use the parser from the Edge class to fill in all shorthand notations
  119. Edge.parseOptions(this.options, options);
  120. // hanlde multiple input cases for color
  121. if (options.color !== undefined) {
  122. this.markAllEdgesAsDirty();
  123. }
  124. // update smooth settings in all edges
  125. let dataChanged = false;
  126. if (options.smooth !== undefined) {
  127. for (let edgeId in this.body.edges) {
  128. if (this.body.edges.hasOwnProperty(edgeId)) {
  129. dataChanged = this.body.edges[edgeId].updateEdgeType() || dataChanged;
  130. }
  131. }
  132. }
  133. // update fonts in all edges
  134. if (options.font !== undefined) {
  135. // use the parser from the Label class to fill in all shorthand notations
  136. Label.parseOptions(this.options,options);
  137. for (let edgeId in this.body.edges) {
  138. if (this.body.edges.hasOwnProperty(edgeId)) {
  139. this.body.edges[edgeId].updateLabelModule();
  140. }
  141. }
  142. }
  143. // update the state of the variables if needed
  144. if (options.hidden !== undefined || options.physics !== undefined || dataChanged === true) {
  145. this.body.emitter.emit('_dataChanged');
  146. }
  147. }
  148. }
  149. /**
  150. * Load edges by reading the data table
  151. * @param {Array | DataSet | DataView} edges The data containing the edges.
  152. * @private
  153. * @private
  154. */
  155. setData(edges, doNotEmit = false) {
  156. var oldEdgesData = this.body.data.edges;
  157. if (edges instanceof DataSet || edges instanceof DataView) {
  158. this.body.data.edges = edges;
  159. }
  160. else if (Array.isArray(edges)) {
  161. this.body.data.edges = new DataSet();
  162. this.body.data.edges.add(edges);
  163. }
  164. else if (!edges) {
  165. this.body.data.edges = new DataSet();
  166. }
  167. else {
  168. throw new TypeError('Array or DataSet expected');
  169. }
  170. // TODO: is this null or undefined or false?
  171. if (oldEdgesData) {
  172. // unsubscribe from old dataset
  173. util.forEach(this.edgesListeners, (callback, event) => {oldEdgesData.off(event, callback);});
  174. }
  175. // remove drawn edges
  176. this.body.edges = {};
  177. // TODO: is this null or undefined or false?
  178. if (this.body.data.edges) {
  179. // subscribe to new dataset
  180. util.forEach(this.edgesListeners, (callback, event) => {this.body.data.edges.on(event, callback);});
  181. // draw all new nodes
  182. var ids = this.body.data.edges.getIds();
  183. this.add(ids, true);
  184. }
  185. if (doNotEmit === false) {
  186. this.body.emitter.emit("_dataChanged");
  187. }
  188. }
  189. /**
  190. * Add edges
  191. * @param {Number[] | String[]} ids
  192. * @private
  193. */
  194. add(ids, doNotEmit = false) {
  195. var edges = this.body.edges;
  196. var edgesData = this.body.data.edges;
  197. for (let i = 0; i < ids.length; i++) {
  198. var id = ids[i];
  199. var oldEdge = edges[id];
  200. if (oldEdge) {
  201. oldEdge.disconnect();
  202. }
  203. var data = edgesData.get(id, {"showInternalIds" : true});
  204. edges[id] = this.create(data);
  205. }
  206. if (doNotEmit === false) {
  207. this.body.emitter.emit("_dataChanged");
  208. }
  209. }
  210. /**
  211. * Update existing edges, or create them when not yet existing
  212. * @param {Number[] | String[]} ids
  213. * @private
  214. */
  215. update(ids) {
  216. var edges = this.body.edges;
  217. var edgesData = this.body.data.edges;
  218. var dataChanged = false;
  219. for (var i = 0; i < ids.length; i++) {
  220. var id = ids[i];
  221. var data = edgesData.get(id);
  222. var edge = edges[id];
  223. if (edge === null) {
  224. // update edge
  225. edge.disconnect();
  226. dataChanged = edge.setOptions(data) || dataChanged; // if a support node is added, data can be changed.
  227. edge.connect();
  228. }
  229. else {
  230. // create edge
  231. this.body.edges[id] = this.create(data);
  232. dataChanged = true;
  233. }
  234. }
  235. if (dataChanged === true) {
  236. this.body.emitter.emit("_dataChanged");
  237. }
  238. else {
  239. this.body.emitter.emit("_dataUpdated");
  240. }
  241. }
  242. /**
  243. * Remove existing edges. Non existing ids will be ignored
  244. * @param {Number[] | String[]} ids
  245. * @private
  246. */
  247. remove(ids) {
  248. var edges = this.body.edges;
  249. for (var i = 0; i < ids.length; i++) {
  250. var id = ids[i];
  251. var edge = edges[id];
  252. if (edge !== undefined) {
  253. if (edge.via != null) {
  254. delete this.body.supportNodes[edge.via.id];
  255. }
  256. edge.disconnect();
  257. delete edges[id];
  258. }
  259. }
  260. this.body.emitter.emit("_dataChanged");
  261. }
  262. create(properties) {
  263. return new Edge(properties, this.body, this.options)
  264. }
  265. markAllEdgesAsDirty() {
  266. for (var edgeId in this.body.edges) {
  267. this.body.edges[edgeId].edgeType.colorDirty = true;
  268. }
  269. }
  270. /**
  271. * Reconnect all edges
  272. * @private
  273. */
  274. reconnectEdges() {
  275. var id;
  276. var nodes = this.body.nodes;
  277. var edges = this.body.edges;
  278. for (id in nodes) {
  279. if (nodes.hasOwnProperty(id)) {
  280. nodes[id].edges = [];
  281. }
  282. }
  283. for (id in edges) {
  284. if (edges.hasOwnProperty(id)) {
  285. var edge = edges[id];
  286. edge.from = null;
  287. edge.to = null;
  288. edge.connect();
  289. }
  290. }
  291. }
  292. }
  293. export default EdgesHandler;