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.

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