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.

407 lines
10 KiB

9 years ago
  1. let util = require("../../util");
  2. let DataSet = require('../../DataSet');
  3. let DataView = require('../../DataView');
  4. import Node from "./components/Node";
  5. import Label from "./components/shared/Label";
  6. class NodesHandler {
  7. constructor(body, images, groups, layoutEngine) {
  8. this.body = body;
  9. this.images = images;
  10. this.groups = groups;
  11. this.layoutEngine = layoutEngine;
  12. // create the node API in the body container
  13. this.body.functions.createNode = this.create.bind(this);
  14. this.nodesListeners = {
  15. 'add': (event, params) => {this.add(params.items);},
  16. 'update': (event, params) => {this.update(params.items, params.data);},
  17. 'remove': (event, params) => {this.remove(params.items);}
  18. };
  19. // refresh the nodes. Used when reverting from hierarchical layout
  20. this.body.emitter.on('refreshNodes', this.refresh.bind(this));
  21. this.body.emitter.on('refresh', this.refresh.bind(this));
  22. this.options = {};
  23. this.defaultOptions = {
  24. borderWidth: 1,
  25. borderWidthSelected: undefined,
  26. brokenImage:undefined,
  27. color: {
  28. border: '#2B7CE9',
  29. background: '#97C2FC',
  30. highlight: {
  31. border: '#2B7CE9',
  32. background: '#D2E5FF'
  33. },
  34. hover: {
  35. border: '#2B7CE9',
  36. background: '#D2E5FF'
  37. }
  38. },
  39. fixed: {
  40. x:false,
  41. y:false
  42. },
  43. font: {
  44. color: '#343434',
  45. size: 14, // px
  46. face: 'arial',
  47. background: 'none',
  48. stroke: 0, // px
  49. strokeColor: '#ffffff',
  50. align: 'horizontal'
  51. },
  52. group: undefined,
  53. hidden: false,
  54. icon: {
  55. face: 'FontAwesome', //'FontAwesome',
  56. code: undefined, //'\uf007',
  57. size: 50, //50,
  58. color:'#2B7CE9' //'#aa00ff'
  59. },
  60. image: undefined, // --> URL
  61. label: undefined,
  62. level: undefined,
  63. mass: 1,
  64. physics: true,
  65. scaling: {
  66. min: 10,
  67. max: 30,
  68. label: {
  69. enabled: false,
  70. min: 14,
  71. max: 30,
  72. maxVisible: 30,
  73. drawThreshold: 3
  74. },
  75. customScalingFunction: function (min,max,total,value) {
  76. if (max === min) {
  77. return 0.5;
  78. }
  79. else {
  80. let scale = 1 / (max - min);
  81. return Math.max(0,(value - min)*scale);
  82. }
  83. }
  84. },
  85. shadow:{
  86. enabled: false,
  87. size:10,
  88. x:5,
  89. y:5
  90. },
  91. shape: 'ellipse',
  92. size: 25,
  93. title: undefined,
  94. value: undefined,
  95. x: undefined,
  96. y: undefined
  97. };
  98. util.extend(this.options, this.defaultOptions);
  99. }
  100. setOptions(options) {
  101. if (options !== undefined) {
  102. Node.parseOptions(this.options, options);
  103. // update the shape in all nodes
  104. if (options.shape !== undefined) {
  105. for (let nodeId in this.body.nodes) {
  106. if (this.body.nodes.hasOwnProperty(nodeId)) {
  107. this.body.nodes[nodeId].updateShape();
  108. }
  109. }
  110. }
  111. // update the shape size in all nodes
  112. if (options.font !== undefined) {
  113. Label.parseOptions(this.options.font,options);
  114. for (let nodeId in this.body.nodes) {
  115. if (this.body.nodes.hasOwnProperty(nodeId)) {
  116. this.body.nodes[nodeId].updateLabelModule();
  117. this.body.nodes[nodeId]._reset();
  118. }
  119. }
  120. }
  121. // update the shape size in all nodes
  122. if (options.size !== undefined) {
  123. for (let nodeId in this.body.nodes) {
  124. if (this.body.nodes.hasOwnProperty(nodeId)) {
  125. this.body.nodes[nodeId]._reset();
  126. }
  127. }
  128. }
  129. // update the state of the letiables if needed
  130. if (options.hidden !== undefined || options.physics !== undefined) {
  131. this.body.emitter.emit('_dataChanged');
  132. }
  133. }
  134. }
  135. /**
  136. * Set a data set with nodes for the network
  137. * @param {Array | DataSet | DataView} nodes The data containing the nodes.
  138. * @private
  139. */
  140. setData(nodes, doNotEmit = false) {
  141. let oldNodesData = this.body.data.nodes;
  142. if (nodes instanceof DataSet || nodes instanceof DataView) {
  143. this.body.data.nodes = nodes;
  144. }
  145. else if (Array.isArray(nodes)) {
  146. this.body.data.nodes = new DataSet();
  147. this.body.data.nodes.add(nodes);
  148. }
  149. else if (!nodes) {
  150. this.body.data.nodes = new DataSet();
  151. }
  152. else {
  153. throw new TypeError('Array or DataSet expected');
  154. }
  155. if (oldNodesData) {
  156. // unsubscribe from old dataset
  157. util.forEach(this.nodesListeners, function (callback, event) {
  158. oldNodesData.off(event, callback);
  159. });
  160. }
  161. // remove drawn nodes
  162. this.body.nodes = {};
  163. if (this.body.data.nodes) {
  164. // subscribe to new dataset
  165. let me = this;
  166. util.forEach(this.nodesListeners, function (callback, event) {
  167. me.body.data.nodes.on(event, callback);
  168. });
  169. // draw all new nodes
  170. let ids = this.body.data.nodes.getIds();
  171. this.add(ids, true);
  172. }
  173. if (doNotEmit === false) {
  174. this.body.emitter.emit("_dataChanged");
  175. }
  176. }
  177. /**
  178. * Add nodes
  179. * @param {Number[] | String[]} ids
  180. * @private
  181. */
  182. add(ids, doNotEmit = false) {
  183. let id;
  184. let newNodes = [];
  185. for (let i = 0; i < ids.length; i++) {
  186. id = ids[i];
  187. let properties = this.body.data.nodes.get(id);
  188. let node = this.create(properties);;
  189. newNodes.push(node);
  190. this.body.nodes[id] = node; // note: this may replace an existing node
  191. }
  192. this.layoutEngine.positionInitially(newNodes);
  193. if (doNotEmit === false) {
  194. this.body.emitter.emit("_dataChanged");
  195. }
  196. }
  197. /**
  198. * Update existing nodes, or create them when not yet existing
  199. * @param {Number[] | String[]} ids
  200. * @private
  201. */
  202. update(ids, changedData) {
  203. let nodes = this.body.nodes;
  204. let dataChanged = false;
  205. for (let i = 0; i < ids.length; i++) {
  206. let id = ids[i];
  207. let node = nodes[id];
  208. let data = changedData[i];
  209. if (node !== undefined) {
  210. // update node
  211. node.setOptions(data, this.constants);
  212. }
  213. else {
  214. dataChanged = true;
  215. // create node
  216. node = this.create(properties);
  217. nodes[id] = node;
  218. }
  219. }
  220. if (dataChanged === true) {
  221. this.body.emitter.emit("_dataChanged");
  222. }
  223. else {
  224. this.body.emitter.emit("_dataUpdated");
  225. }
  226. }
  227. /**
  228. * Remove existing nodes. If nodes do not exist, the method will just ignore it.
  229. * @param {Number[] | String[]} ids
  230. * @private
  231. */
  232. remove(ids) {
  233. let nodes = this.body.nodes;
  234. for (let i = 0; i < ids.length; i++) {
  235. let id = ids[i];
  236. delete nodes[id];
  237. }
  238. this.body.emitter.emit("_dataChanged");
  239. }
  240. /**
  241. * create a node
  242. * @param properties
  243. * @param constructorClass
  244. */
  245. create(properties, constructorClass = Node) {
  246. return new constructorClass(properties, this.body, this.images, this.groups, this.options)
  247. }
  248. refresh() {
  249. let nodes = this.body.nodes;
  250. for (let nodeId in nodes) {
  251. let node = undefined;
  252. if (nodes.hasOwnProperty(nodeId)) {
  253. node = nodes[nodeId];
  254. }
  255. let data = this.body.data.nodes._data[nodeId];
  256. if (node !== undefined && data !== undefined) {
  257. node.setOptions({fixed:false});
  258. node.setOptions(data);
  259. }
  260. }
  261. }
  262. /**
  263. * Returns the positions of the nodes.
  264. * @param ids --> optional, can be array of nodeIds, can be string
  265. * @returns {{}}
  266. */
  267. getPositions(ids) {
  268. let dataArray = {};
  269. if (ids !== undefined) {
  270. if (Array.isArray(ids) === true) {
  271. for (let i = 0; i < ids.length; i++) {
  272. if (this.body.nodes[ids[i]] !== undefined) {
  273. let node = this.body.nodes[ids[i]];
  274. dataArray[ids[i]] = {x: Math.round(node.x), y: Math.round(node.y)};
  275. }
  276. }
  277. }
  278. else {
  279. if (this.body.nodes[ids] !== undefined) {
  280. let node = this.body.nodes[ids];
  281. dataArray[ids] = {x: Math.round(node.x), y: Math.round(node.y)};
  282. }
  283. }
  284. }
  285. else {
  286. for (let nodeId in this.body.nodes) {
  287. if (this.body.nodes.hasOwnProperty(nodeId)) {
  288. let node = this.body.nodes[nodeId];
  289. dataArray[nodeId] = {x: Math.round(node.x), y: Math.round(node.y)};
  290. }
  291. }
  292. }
  293. return dataArray;
  294. }
  295. /**
  296. * Load the XY positions of the nodes into the dataset.
  297. */
  298. storePositions() {
  299. // todo: add support for clusters and hierarchical.
  300. let dataArray = [];
  301. for (let nodeId in this.body.nodes) {
  302. if (this.body.nodes.hasOwnProperty(nodeId)) {
  303. let node = this.body.nodes[nodeId];
  304. if (this.body.data.nodes._data[nodeId].x != Math.round(node.x) || this.body.data.nodes._data[nodeId].y != Math.round(node.y)) {
  305. dataArray.push({id:nodeId,x:Math.round(node.x),y:Math.round(node.y)});
  306. }
  307. }
  308. }
  309. this.body.data.nodes.update(dataArray);
  310. }
  311. /**
  312. * get the bounding box of a node.
  313. * @param nodeId
  314. * @returns {j|*}
  315. */
  316. getBoundingBox(nodeId) {
  317. if (this.body.nodes[nodeId] !== undefined) {
  318. return this.body.nodes[nodeId].shape.boundingBox;
  319. }
  320. }
  321. /**
  322. * Get the Ids of nodes connected to this node.
  323. * @param nodeId
  324. * @returns {Array}
  325. */
  326. getConnectedNodes(nodeId) {
  327. let nodeList = [];
  328. if (this.body.nodes[nodeId] !== undefined) {
  329. let node = this.body.nodes[nodeId];
  330. let nodeObj = {}; // used to quickly check if node already exists
  331. for (let i = 0; i < node.edges.length; i++) {
  332. let edge = node.edges[i];
  333. if (edge.toId === nodeId) {
  334. if (nodeObj[edge.fromId] === undefined) {
  335. nodeList.push(edge.fromId);
  336. nodeObj[edge.fromId] = true;
  337. }
  338. }
  339. else if (edge.fromId === nodeId) {
  340. if (nodeObj[edge.toId] === undefined) {
  341. nodeList.push(edge.toId)
  342. nodeObj[edge.toId] = true;
  343. }
  344. }
  345. }
  346. }
  347. return nodeList;
  348. }
  349. /**
  350. * Get the ids of the edges connected to this node.
  351. * @param nodeId
  352. * @returns {*}
  353. */
  354. getEdges(nodeId) {
  355. let edgeList = [];
  356. if (this.body.nodes[nodeId] !== undefined) {
  357. let node = this.body.nodes[nodeId];
  358. for (let i = 0; i < node.edges.length; i++) {
  359. edgeList.push(node.edges[i].id)
  360. }
  361. }
  362. return nodeList;
  363. }
  364. }
  365. export default NodesHandler;