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.

661 lines
18 KiB

  1. /**
  2. * Created by Alex on 3/26/2015.
  3. */
  4. var util = require('../../util');
  5. import ColorPicker from './components/ColorPicker'
  6. class ConfigurationSystem {
  7. constructor(network) {
  8. this.network = network;
  9. this.possibleOptions = {
  10. nodes: {
  11. borderWidth: [1, 0, 10, 1],
  12. borderWidthSelected: [2, 0, 10, 1],
  13. color: {
  14. border: ['color','#2B7CE9'],
  15. background: ['color','#97C2FC'],
  16. highlight: {
  17. border: ['color','#2B7CE9'],
  18. background: ['color','#D2E5FF']
  19. },
  20. hover: {
  21. border: ['color','#2B7CE9'],
  22. background: ['color','#D2E5FF']
  23. }
  24. },
  25. fixed: {
  26. x: false,
  27. y: false
  28. },
  29. font: {
  30. color: ['color','#343434'],
  31. size: [14, 0, 100, 1], // px
  32. face: ['arial', 'verdana', 'tahoma'],
  33. background: ['color','none'],
  34. stroke: [0, 0, 50, 1], // px
  35. strokeColor: ['color','#ffffff']
  36. },
  37. group: 'string',
  38. hidden: false,
  39. icon: {
  40. face: 'string', //'FontAwesome',
  41. code: 'string', //'\uf007',
  42. size: [50, 0, 200, 1], //50,
  43. color: ['color','#2B7CE9'] //'#aa00ff'
  44. },
  45. image: 'string', // --> URL
  46. physics: true,
  47. scaling: {
  48. min: [10, 0, 200, 1],
  49. max: [30, 0, 200, 1],
  50. label: {
  51. enabled: true,
  52. min: [14, 0, 200, 1],
  53. max: [30, 0, 200, 1],
  54. maxVisible: [30, 0, 200, 1],
  55. drawThreshold: [3, 0, 20, 1]
  56. }
  57. },
  58. shape: ['ellipse', 'box', 'circle', 'database', 'diamond', 'dot', 'icon', 'square', 'star', 'text', 'triangle', 'triangleDown'],
  59. size: [25, 0, 200, 1]
  60. },
  61. edges: {
  62. arrows: {
  63. to: {enabled: false, scaleFactor: [1, 0, 3, 0.05]}, // boolean / {arrowScaleFactor:1} / {enabled: false, arrowScaleFactor:1}
  64. middle: {enabled: false, scaleFactor: [1, 0, 3, 0.05]},
  65. from: {enabled: false, scaleFactor: [1, 0, 3, 0.05]}
  66. },
  67. color: {
  68. color: ['color','#848484'],
  69. highlight: ['color','#848484'],
  70. hover: ['color','#848484'],
  71. inherit: {
  72. enabled: true,
  73. source: ['from', 'to'], // from / to
  74. useGradients: false
  75. },
  76. opacity: [1, 0, 1, 0.05]
  77. },
  78. dashes: {
  79. enabled: false,
  80. length: [5, 0, 50, 1],
  81. gap: [5, 0, 50, 1],
  82. altLength: [5, 0, 50, 1]
  83. },
  84. font: {
  85. color: ['color','#343434'],
  86. size: [14, 0, 100, 1], // px
  87. face: ['arial', 'verdana', 'tahoma'],
  88. background: ['color','none'],
  89. stroke: [0, 0, 50, 1], // px
  90. strokeColor: ['color','#ffffff'],
  91. align: ['horizontal', 'top', 'middle', 'bottom']
  92. },
  93. hidden: false,
  94. hoverWidth: [1.5, 0, 10, 0.1],
  95. physics: true,
  96. scaling: {
  97. min: [10, 0, 200, 1],
  98. max: [30, 0, 200, 1],
  99. label: {
  100. enabled: true,
  101. min: [14, 0, 200, 1],
  102. max: [30, 0, 200, 1],
  103. maxVisible: [30, 0, 200, 1],
  104. drawThreshold: [3, 0, 20, 1]
  105. }
  106. },
  107. selfReferenceSize: [20, 0, 200, 1],
  108. smooth: {
  109. enabled: true,
  110. dynamic: true,
  111. type: ['continuous', 'discrete', 'diagonalCross', 'straightCross', 'horizontal', 'vertical', 'curvedCW', 'curvedCCW'],
  112. roundness: [0.5, 0, 1, 0.05]
  113. },
  114. width: [1, 0, 30, 1],
  115. widthSelectionMultiplier: [2, 0, 5, 0.1]
  116. },
  117. layout: {
  118. randomSeed: [0, 0, 500, 1],
  119. hierarchical: {
  120. enabled: false,
  121. levelSeparation: [150, 20, 500, 5],
  122. direction: ['UD', 'DU', 'LR', 'RL'], // UD, DU, LR, RL
  123. sortMethod: ['hubsize', 'directed'] // hubsize, directed
  124. }
  125. },
  126. interaction: {
  127. dragNodes: true,
  128. dragView: true,
  129. zoomView: true,
  130. hoverEnabled: false,
  131. showNavigationIcons: false,
  132. tooltip: {
  133. delay: [300, 0, 1000, 25],
  134. fontColor: ['color','#000000'],
  135. fontSize: [14, 0, 40, 1], // px
  136. fontFace: ['arial', 'verdana', 'tahoma'],
  137. color: {
  138. border: ['color','#666666'],
  139. background: ['color','#FFFFC6']
  140. }
  141. },
  142. keyboard: {
  143. enabled: false,
  144. speed: {x: [10, 0, 40, 1], y: [10, 0, 40, 1], zoom: [0.02, 0, 0.1, 0.005]},
  145. bindToWindow: true
  146. }
  147. },
  148. manipulation: {
  149. enabled: false,
  150. initiallyVisible: false,
  151. locale: ['en', 'nl'],
  152. functionality: {
  153. addNode: true,
  154. addEdge: true,
  155. editNode: true,
  156. editEdge: true,
  157. deleteNode: true,
  158. deleteEdge: true
  159. }
  160. },
  161. physics: {
  162. barnesHut: {
  163. theta: [0.5, 0.1, 1, 0.05],
  164. gravitationalConstant: [-2000, -30000, 0, 50],
  165. centralGravity: [0.3, 0, 10, 0.05],
  166. springLength: [95, 0, 500, 5],
  167. springConstant: [0.04, 0, 5, 0.005],
  168. damping: [0.09, 0, 1, 0.01]
  169. },
  170. repulsion: {
  171. centralGravity: [0.2, 0, 10, 0.05],
  172. springLength: [200, 0, 500, 5],
  173. springConstant: [0.05, 0, 5, 0.005],
  174. nodeDistance: [100, 0, 500, 5],
  175. damping: [0.09, 0, 1, 0.01]
  176. },
  177. hierarchicalRepulsion: {
  178. centralGravity: [0.2, 0, 10, 0.05],
  179. springLength: [100, 0, 500, 5],
  180. springConstant: [0.01, 0, 5, 0.005],
  181. nodeDistance: [120, 0, 500, 5],
  182. damping: [0.09, 0, 1, 0.01]
  183. },
  184. maxVelocity: [50, 0, 150, 1],
  185. minVelocity: [0.1, 0.01, 0.5, 0.01],
  186. solver: ['barnesHut', 'repulsion', 'hierarchicalRepulsion'],
  187. timestep: [0.5, 0, 1, 0.05]
  188. },
  189. selection: {
  190. select: true,
  191. selectConnectedEdges: true
  192. },
  193. renderer: {
  194. hideEdgesOnDrag: false,
  195. hideNodesOnDrag: false
  196. }
  197. };
  198. this.actualOptions = {
  199. nodes:{},
  200. edges:{},
  201. layout:{},
  202. interaction:{},
  203. manipulation:{},
  204. physics:{},
  205. selection:{},
  206. renderer:{},
  207. configure: false,
  208. configureContainer: undefined
  209. };
  210. this.domElements = [];
  211. this.colorPicker = new ColorPicker(this.network.canvas.pixelRatio);
  212. }
  213. /**
  214. * refresh all options.
  215. * Because all modules parse their options by themselves, we just use their options. We copy them here.
  216. *
  217. * @param options
  218. */
  219. setOptions(options) {
  220. if (options !== undefined) {
  221. util.extend(this.actualOptions, options);
  222. }
  223. this._clean();
  224. if (this.actualOptions.configure !== undefined && this.actualOptions.configure !== false) {
  225. util.deepExtend(this.actualOptions.nodes, this.network.nodesHandler.options, true);
  226. util.deepExtend(this.actualOptions.edges, this.network.edgesHandler.options, true);
  227. util.deepExtend(this.actualOptions.layout, this.network.layoutEngine.options, true);
  228. util.deepExtend(this.actualOptions.interaction, this.network.interactionHandler.options, true);
  229. util.deepExtend(this.actualOptions.manipulation, this.network.manipulation.options, true);
  230. util.deepExtend(this.actualOptions.physics, this.network.nodesHandler.physics, true);
  231. util.deepExtend(this.actualOptions.selection, this.network.selectionHandler.selection, true);
  232. util.deepExtend(this.actualOptions.renderer, this.network.renderer.selection, true);
  233. if (this.actualOptions.configurationContainer !== undefined) {
  234. this.container = this.actualOptions.configurationContainer;
  235. }
  236. else {
  237. this.container = this.network.body.container;
  238. }
  239. let config;
  240. if (this.actualOptions.configure instanceof Array) {
  241. config = this.actualOptions.configure.join();
  242. }
  243. else if (typeof this.actualOptions.configure === 'string') {
  244. config = this.actualOptions.configure;
  245. }
  246. else if (typeof this.actualOptions.configure === 'boolean') {
  247. config = this.actualOptions.configure;
  248. }
  249. else {
  250. this._clean();
  251. throw new Error('the option for configure has to be either a string, boolean or an array. Supplied:' + this.options.configure);
  252. return;
  253. }
  254. this._create(config);
  255. }
  256. }
  257. /**
  258. * Create all DOM elements
  259. * @param {Boolean | String} config
  260. * @private
  261. */
  262. _create(config) {
  263. this._clean();
  264. let counter = 0;
  265. for (let option in this.possibleOptions) {
  266. if (this.possibleOptions.hasOwnProperty(option)) {
  267. if (config === true || config.indexOf(option) !== -1) {
  268. let optionObj = this.possibleOptions[option];
  269. // linebreak between categories
  270. if (counter > 0) {
  271. this._makeEntree([]);
  272. }
  273. // a header for the category
  274. this._makeHeader(option);
  275. // get the suboptions
  276. let path = [option];
  277. this._handleObject(optionObj, path);
  278. }
  279. counter++;
  280. }
  281. }
  282. this._push();
  283. this.colorPicker.insertTo(this.container);
  284. }
  285. /**
  286. * draw all DOM elements on the screen
  287. * @private
  288. */
  289. _push() {
  290. for (var i = 0; i < this.domElements.length; i++) {
  291. this.container.appendChild(this.domElements[i]);
  292. }
  293. }
  294. /**
  295. * delete all DOM elements
  296. * @private
  297. */
  298. _clean() {
  299. for (var i = 0; i < this.domElements.length; i++) {
  300. this.container.removeChild(this.domElements[i]);
  301. }
  302. this.domElements = [];
  303. }
  304. /**
  305. * get the value from the actualOptions if it exists
  306. * @param {array} path | where to look for the actual option
  307. * @returns {*}
  308. * @private
  309. */
  310. _getValue(path) {
  311. let base = this.actualOptions;
  312. for (let i = 0; i < path.length; i++) {
  313. if (base[path[i]] !== undefined) {
  314. base = base[path[i]];
  315. }
  316. else {
  317. base = undefined;
  318. break;
  319. }
  320. }
  321. return base;
  322. }
  323. /**
  324. * Copy the path and add a step. It needs to copy because the path will keep stacking otherwise.
  325. * @param path
  326. * @param newValue
  327. * @returns {Array}
  328. * @private
  329. */
  330. _addToPath(path, newValue) {
  331. let newPath = [];
  332. for (let i = 0; i < path.length; i++) {
  333. newPath.push(path[i]);
  334. }
  335. newPath.push(newValue);
  336. return newPath;
  337. }
  338. /**
  339. * all option elements are wrapped in an entree
  340. * @param path
  341. * @param domElements
  342. * @private
  343. */
  344. _makeEntree(path,...domElements) {
  345. let entree = document.createElement('div');
  346. entree.className = 'vis-network-configuration entree s' + path.length;
  347. domElements.forEach((element) => {
  348. entree.appendChild(element);
  349. });
  350. this.domElements.push(entree);
  351. }
  352. /**
  353. * header for major subjects
  354. * @param name
  355. * @private
  356. */
  357. _makeHeader(name) {
  358. let div = document.createElement('div');
  359. div.className = 'vis-network-configuration header';
  360. div.innerHTML = name;
  361. this._makeEntree([],div);
  362. }
  363. /**
  364. * make a label, if it is an object label, it gets different styling.
  365. * @param name
  366. * @param path
  367. * @param objectLabel
  368. * @returns {HTMLElement}
  369. * @private
  370. */
  371. _makeLabel(name, path, objectLabel = false) {
  372. let div = document.createElement('div');
  373. div.className = 'vis-network-configuration label s' + path.length;
  374. if (objectLabel === true) {
  375. div.innerHTML = "<i><b>" + name + ":</b></i>";
  376. }
  377. else {
  378. div.innerHTML = name + ':';
  379. }
  380. return div;
  381. }
  382. /**
  383. * make a dropdown list for multiple possible string optoins
  384. * @param arr
  385. * @param value
  386. * @param path
  387. * @private
  388. */
  389. _makeDropdown(arr, value, path) {
  390. let select = document.createElement('select');
  391. select.className = 'vis-network-configuration select';
  392. let selectedValue = 0;
  393. if (value !== undefined) {
  394. if (arr.indexOf(value) !== -1) {
  395. selectedValue = arr.indexOf(value);
  396. }
  397. }
  398. for (let i = 0; i < arr.length; i++) {
  399. let option = document.createElement('option');
  400. option.value = arr[i];
  401. if (i == selectedValue) {
  402. option.selected = 'selected';
  403. }
  404. option.innerHTML = arr[i];
  405. select.appendChild(option);
  406. }
  407. let me = this;
  408. select.onchange = function () {me._update(this.value, path);};
  409. let label = this._makeLabel(path[path.length-1], path);
  410. this._makeEntree(path, label, select);
  411. }
  412. /**
  413. * make a range object for numeric options
  414. * @param arr
  415. * @param value
  416. * @param path
  417. * @private
  418. */
  419. _makeRange(arr, value, path) {
  420. let defaultValue = arr[0];
  421. let min = arr[1];
  422. let max = arr[2];
  423. let step = arr[3];
  424. let range = document.createElement('input');
  425. range.type = 'range';
  426. range.className = 'vis-network-configuration range';
  427. range.min = min;
  428. range.max = max;
  429. range.step = step;
  430. if (value !== undefined) {
  431. if (value * 0.1 < min) {
  432. range.min = value / 10;
  433. }
  434. if (value * 10 > max && max !== 1) {
  435. range.max = value * 10;
  436. }
  437. range.value = value;
  438. }
  439. else {
  440. range.value = defaultValue;
  441. }
  442. let input = document.createElement('input');
  443. input.className = 'vis-network-configuration rangeinput';
  444. input.value = range.value;
  445. var me = this;
  446. range.onchange = function () {input.value = this.value; me._update(this.value, path);};
  447. range.oninput = function () {input.value = this.value;};
  448. let label = this._makeLabel(path[path.length-1], path);
  449. this._makeEntree(path, label, range, input);
  450. }
  451. /**
  452. * make a checkbox for boolean options.
  453. * @param defaultValue
  454. * @param value
  455. * @param path
  456. * @private
  457. */
  458. _makeCheckbox(defaultValue, value, path) {
  459. var checkbox = document.createElement('input');
  460. checkbox.type = 'checkbox';
  461. checkbox.className = 'vis-network-configuration checkbox';
  462. checkbox.checked = defaultValue;
  463. if (value !== undefined) {
  464. checkbox.checked = value;
  465. }
  466. let me = this;
  467. checkbox.onchange = function() {me._update(this.value, path)};
  468. let label = this._makeLabel(path[path.length-1], path);
  469. this._makeEntree(path, label, checkbox);
  470. }
  471. /**
  472. * make a color field with a color picker for color fields
  473. * @param arr
  474. * @param value
  475. * @param path
  476. * @private
  477. */
  478. _makeColorField(arr, value, path) {
  479. let defaultColor = arr[1];
  480. let div = document.createElement('div');
  481. value = value === undefined ? defaultColor : value;
  482. if (value !== 'none') {
  483. div.className = 'vis-network-configuration colorBlock';
  484. div.style.backgroundColor = value;
  485. }
  486. else {
  487. div.className = 'vis-network-configuration colorBlock none';
  488. }
  489. value = value === undefined ? defaultColor : value;
  490. div.onclick = (event) => {this._showColorPicker(event,value,div,path);}
  491. let label = this._makeLabel(path[path.length-1], path);
  492. this._makeEntree(path,label, div);
  493. }
  494. /**
  495. * used by the color buttons to call the color picker.
  496. * @param event
  497. * @param value
  498. * @param div
  499. * @param path
  500. * @private
  501. */
  502. _showColorPicker(event, value, div, path) {
  503. this.colorPicker.show(event.pageX, event.pageY);
  504. this.colorPicker.setColor(value);
  505. this.colorPicker.setCallback((color) => {
  506. let colorString = 'rgba(' + color.r + ',' + color.g + ',' + color.b + ',' + color.a + ')';
  507. div.style.backgroundColor = colorString;
  508. this._update(colorString,path);
  509. })
  510. }
  511. /**
  512. * parse an object and draw the correct entrees
  513. * @param obj
  514. * @param path
  515. * @private
  516. */
  517. _handleObject(obj, path = []) {
  518. for (let subObj in obj) {
  519. if (obj.hasOwnProperty(subObj)) {
  520. let item = obj[subObj];
  521. let newPath = this._addToPath(path, subObj);
  522. let value = this._getValue(newPath);
  523. if (item instanceof Array) {
  524. this._handleArray(subObj, item, value, newPath);
  525. }
  526. else if (typeof item === 'string') {
  527. this._handleString(subObj, item, value, newPath);
  528. }
  529. else if (typeof item === 'boolean') {
  530. this._makeCheckbox(item, value, newPath);
  531. }
  532. else if (item instanceof Object) {
  533. let label = this._makeLabel(subObj, newPath, true);
  534. this._makeEntree(newPath, label);
  535. this._handleObject(item, newPath);
  536. }
  537. else {
  538. console.error('dont know how to handle', item, subObj, newPath);
  539. }
  540. }
  541. }
  542. }
  543. /**
  544. * handle the array type of option
  545. * @param optionName
  546. * @param arr
  547. * @param value
  548. * @param path
  549. * @private
  550. */
  551. _handleArray(optionName, arr, value, path) {
  552. if (typeof arr[0] === 'string' && arr[0] === 'color') {
  553. this._makeColorField(arr, value, path);
  554. }
  555. else if (typeof arr[0] === 'string') {
  556. this._makeDropdown(arr, value, path);
  557. }
  558. else if (typeof arr[0] === 'number') {
  559. this._makeRange(arr, value, path);
  560. }
  561. }
  562. /**
  563. * handle the string type of option.
  564. * TODO: Not sure what to do with this
  565. * @param optionName
  566. * @param string
  567. * @param value
  568. * @param path
  569. * @private
  570. */
  571. _handleString(optionName, string, value, path) {
  572. if (string === 'string') {
  573. }
  574. else {
  575. //this._makeLabel(optionName, path);
  576. //console.log('string', string, value, path);
  577. }
  578. }
  579. /**
  580. * called to update the network with the new settings.
  581. * @param value
  582. * @param path
  583. * @private
  584. */
  585. _update(value, path) {
  586. let options = {};
  587. let pointer = options;
  588. for (let i = 0; i < path.length; i++) {
  589. pointer[path[i]] = {};
  590. if (i !== path.length -1) {
  591. pointer = pointer[path[i]];
  592. }
  593. else {
  594. pointer[path[i]] = value;
  595. }
  596. }
  597. console.log('options',options)
  598. this.network.setOptions(options);
  599. }
  600. }
  601. export default ConfigurationSystem;