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.

414 lines
11 KiB

  1. var util = require("../../util");
  2. var DataSet = require('../../DataSet');
  3. var DataView = require('../../DataView');
  4. import Edge from "./components/Edge"
  5. class EdgesHandler {
  6. constructor(body, images, groups) {
  7. this.body = body;
  8. this.images = images;
  9. this.groups = groups;
  10. // create the edge API in the body container
  11. this.body.functions.createEdge = this.create.bind(this);
  12. this.edgesListeners = {
  13. 'add': (event, params) => {this.add(params.items);},
  14. 'update': (event, params) => {this.update(params.items);},
  15. 'remove': (event, params) => {this.remove(params.items);}
  16. };
  17. this.options = {};
  18. this.defaultOptions = {
  19. arrows: {
  20. to: {enabled: false, scaleFactor:1}, // boolean / {arrowScaleFactor:1} / {enabled: false, arrowScaleFactor:1}
  21. middle: {enabled: false, scaleFactor:1},
  22. from: {enabled: false, scaleFactor:1}
  23. },
  24. color: {
  25. color:'#848484',
  26. highlight:'#848484',
  27. hover: '#848484',
  28. inherit: {
  29. enabled: true,
  30. source: 'from', // from / true
  31. useGradients: false // release in 4.0
  32. },
  33. opacity:1.0
  34. },
  35. dashes:{
  36. enabled: false,
  37. preset: 'dotted',
  38. length: 10,
  39. gap: 5,
  40. altLength: undefined
  41. },
  42. font: {
  43. color: '#343434',
  44. size: 14, // px
  45. face: 'arial',
  46. background: 'none',
  47. stroke: 1, // px
  48. strokeColor: '#ffffff',
  49. align:'horizontal'
  50. },
  51. hidden: false,
  52. hoverWidth: 1.5,
  53. label: undefined,
  54. length: undefined,
  55. physics: true,
  56. scaling:{
  57. min: 1,
  58. max: 15,
  59. label: {
  60. enabled: true,
  61. min: 14,
  62. max: 30,
  63. maxVisible: 30,
  64. drawThreshold: 3
  65. },
  66. customScalingFunction: function (min,max,total,value) {
  67. if (max === min) {
  68. return 0.5;
  69. }
  70. else {
  71. var scale = 1 / (max - min);
  72. return Math.max(0,(value - min)*scale);
  73. }
  74. }
  75. },
  76. selfReferenceSize:20,
  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:1
  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. var fields = [
  119. 'font',
  120. 'from',
  121. 'hidden',
  122. 'hoverWidth',
  123. 'label',
  124. 'length',
  125. 'line',
  126. 'opacity',
  127. 'physics',
  128. 'selfReferenceSize',
  129. 'to',
  130. 'title',
  131. 'value',
  132. 'width',
  133. 'widthMin',
  134. 'widthMax',
  135. 'widthSelectionMultiplier'
  136. ];
  137. util.selectiveExtend(fields, this.options, options);
  138. util.mergeOptions(this.options, options, 'smooth');
  139. util.mergeOptions(this.options, options, 'dashes');
  140. // set the scaling options
  141. if (options.scaling !== undefined) {
  142. if (options.scaling.min !== undefined) {this.options.scaling.min = options.scaling.min;}
  143. if (options.scaling.max !== undefined) {this.options.scaling.max = options.scaling.max;}
  144. util.mergeOptions(this.options.scaling, options.scaling, 'label');
  145. }
  146. // hanlde multiple input cases for arrows
  147. if (options.arrows !== undefined) {
  148. if (typeof options.arrows === 'string') {
  149. let arrows = options.arrows.toLowerCase();
  150. if (arrows.indexOf("to") != -1) {this.options.arrows.to.enabled = true;}
  151. if (arrows.indexOf("middle") != -1) {this.options.arrows.middle.enabled = true;}
  152. if (arrows.indexOf("from") != -1) {this.options.arrows.from.enabled = true;}
  153. }
  154. else if (typeof options.arrows === 'object') {
  155. util.mergeOptions(this.options.arrows, options.arrows, 'to');
  156. util.mergeOptions(this.options.arrows, options.arrows, 'middle');
  157. util.mergeOptions(this.options.arrows, options.arrows, 'from');
  158. }
  159. else {
  160. throw new Error("The arrow options can only be an object or a string. Refer to the documentation. You used:" + JSON.stringify(options.arrows));
  161. }
  162. }
  163. // hanlde multiple input cases for color
  164. if (options.color !== undefined) {
  165. if (util.isString(options.color)) {
  166. util.assignAllKeys(this.options.color, options.color);
  167. this.options.color.inherit.enabled = false;
  168. }
  169. else {
  170. if (typeof options.color === 'object') {
  171. let colorsDefined = false;
  172. if (options.color.color !== undefined) {this.options.color.color = options.color.color; colorsDefined = true;}
  173. if (options.color.highlight !== undefined) {this.options.color.highlight = options.color.highlight; colorsDefined = true;}
  174. if (options.color.hover !== undefined) {this.options.color.hover = options.color.hover; colorsDefined = true;}
  175. if (options.color.opacity !== undefined) {this.options.color.opacity = options.color.opacity;}
  176. if (options.color.inherit === undefined && colorsDefined === true) {
  177. this.options.color.inherit.enabled = false;
  178. }
  179. }
  180. }
  181. util.mergeOptions(this.options.color, options.color, 'inherit');
  182. this.markAllEdgesAsDirty();
  183. }
  184. // update smooth settings in all edges
  185. let dataChanged = false;
  186. if (options.smooth !== undefined) {
  187. for (let edgeId in this.body.edges) {
  188. if (this.body.edges.hasOwnProperty(edgeId)) {
  189. dataChanged = this.body.edges[edgeId].updateEdgeType() || dataChanged;
  190. }
  191. }
  192. }
  193. // update fonts in all edges
  194. if (options.font) {
  195. if (typeof options.font === 'string') {
  196. let optionsArray = options.font.split(" ");
  197. this.options.font.size = optionsArray[0].replace("px",'');
  198. this.options.font.face = optionsArray[1];
  199. this.options.font.color = optionsArray[2];
  200. }
  201. else if (typeof options.font === 'object') {
  202. this.options.font = util.bridgeObject(options.font);
  203. }
  204. this.options.font.size = Number(this.options.font.size);
  205. for (let edgeId in this.body.edges) {
  206. if (this.body.edges.hasOwnProperty(edgeId)) {
  207. this.body.edges[edgeId].updateLabelModule();
  208. }
  209. }
  210. }
  211. // update the state of the variables if needed
  212. if (options.hidden !== undefined || options.physics !== undefined || dataChanged === true) {
  213. this.body.emitter.emit('_dataChanged');
  214. }
  215. }
  216. }
  217. /**
  218. * Load edges by reading the data table
  219. * @param {Array | DataSet | DataView} edges The data containing the edges.
  220. * @private
  221. * @private
  222. */
  223. setData(edges, doNotEmit = false) {
  224. var oldEdgesData = this.body.data.edges;
  225. if (edges instanceof DataSet || edges instanceof DataView) {
  226. this.body.data.edges = edges;
  227. }
  228. else if (Array.isArray(edges)) {
  229. this.body.data.edges = new DataSet();
  230. this.body.data.edges.add(edges);
  231. }
  232. else if (!edges) {
  233. this.body.data.edges = new DataSet();
  234. }
  235. else {
  236. throw new TypeError('Array or DataSet expected');
  237. }
  238. // TODO: is this null or undefined or false?
  239. if (oldEdgesData) {
  240. // unsubscribe from old dataset
  241. util.forEach(this.edgesListeners, (callback, event) => {oldEdgesData.off(event, callback);});
  242. }
  243. // remove drawn edges
  244. this.body.edges = {};
  245. // TODO: is this null or undefined or false?
  246. if (this.body.data.edges) {
  247. // subscribe to new dataset
  248. util.forEach(this.edgesListeners, (callback, event) => {this.body.data.edges.on(event, callback);});
  249. // draw all new nodes
  250. var ids = this.body.data.edges.getIds();
  251. this.add(ids, true);
  252. }
  253. if (doNotEmit === false) {
  254. this.body.emitter.emit("_dataChanged");
  255. }
  256. }
  257. /**
  258. * Add edges
  259. * @param {Number[] | String[]} ids
  260. * @private
  261. */
  262. add(ids, doNotEmit = false) {
  263. var edges = this.body.edges;
  264. var edgesData = this.body.data.edges;
  265. for (let i = 0; i < ids.length; i++) {
  266. var id = ids[i];
  267. var oldEdge = edges[id];
  268. if (oldEdge) {
  269. oldEdge.disconnect();
  270. }
  271. var data = edgesData.get(id, {"showInternalIds" : true});
  272. edges[id] = this.create(data);
  273. }
  274. if (doNotEmit === false) {
  275. this.body.emitter.emit("_dataChanged");
  276. }
  277. }
  278. /**
  279. * Update existing edges, or create them when not yet existing
  280. * @param {Number[] | String[]} ids
  281. * @private
  282. */
  283. update(ids) {
  284. var edges = this.body.edges;
  285. var edgesData = this.body.data.edges;
  286. var dataChanged = false;
  287. for (var i = 0; i < ids.length; i++) {
  288. var id = ids[i];
  289. var data = edgesData.get(id);
  290. var edge = edges[id];
  291. if (edge === null) {
  292. // update edge
  293. edge.disconnect();
  294. dataChanged = edge.setOptions(data) || dataChanged; // if a support node is added, data can be changed.
  295. edge.connect();
  296. }
  297. else {
  298. // create edge
  299. this.body.edges[id] = this.create(data);
  300. dataChanged = true;
  301. }
  302. }
  303. if (dataChanged === true) {
  304. this.body.emitter.emit("_dataChanged");
  305. }
  306. else {
  307. this.body.emitter.emit("_dataUpdated");
  308. }
  309. }
  310. /**
  311. * Remove existing edges. Non existing ids will be ignored
  312. * @param {Number[] | String[]} ids
  313. * @private
  314. */
  315. remove(ids) {
  316. var edges = this.body.edges;
  317. for (var i = 0; i < ids.length; i++) {
  318. var id = ids[i];
  319. var edge = edges[id];
  320. if (edge !== undefined) {
  321. if (edge.via != null) {
  322. delete this.body.supportNodes[edge.via.id];
  323. }
  324. edge.disconnect();
  325. delete edges[id];
  326. }
  327. }
  328. this.body.emitter.emit("_dataChanged");
  329. }
  330. create(properties) {
  331. return new Edge(properties, this.body, this.options)
  332. }
  333. markAllEdgesAsDirty() {
  334. for (var edgeId in this.body.edges) {
  335. this.body.edges[edgeId].edgeType.colorDirty = true;
  336. }
  337. }
  338. /**
  339. * Reconnect all edges
  340. * @private
  341. */
  342. reconnectEdges() {
  343. var id;
  344. var nodes = this.body.nodes;
  345. var edges = this.body.edges;
  346. for (id in nodes) {
  347. if (nodes.hasOwnProperty(id)) {
  348. nodes[id].edges = [];
  349. }
  350. }
  351. for (id in edges) {
  352. if (edges.hasOwnProperty(id)) {
  353. var edge = edges[id];
  354. edge.from = null;
  355. edge.to = null;
  356. edge.connect();
  357. }
  358. }
  359. }
  360. }
  361. export default EdgesHandler;