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.

585 lines
16 KiB

9 years ago
9 years ago
  1. var util = require('../util');
  2. import ColorPicker from './../network/modules/components/ColorPicker'
  3. /**
  4. * The way this works is for all properties of this.possible options, you can supply the property name in any form to list the options.
  5. * Boolean options are recognised as Boolean
  6. * Number options should be written as array: [default value, min value, max value, stepsize]
  7. * Colors should be written as array: ['color', '#ffffff']
  8. * Strings with should be written as array: [option1, option2, option3, ..]
  9. *
  10. * The options are matched with their counterparts in each of the modules and the values used in the configuration are
  11. *
  12. * @param parentModule | the location where parentModule.setOptions() can be called
  13. * @param defaultContainer | the default container of the module
  14. * @param configureOptions | the fully configured and predefined options set found in allOptions.js
  15. * @param pixelRatio | canvas pixel ratio
  16. */
  17. class ConfigurationSystem {
  18. constructor(parentModule, defaultContainer, configureOptions, pixelRatio = 1) {
  19. this.parent = parentModule;
  20. this.changedOptions = [];
  21. this.container = defaultContainer;
  22. this.allowCreation = false;
  23. this.options = {};
  24. this.defaultOptions = {
  25. enabled: false,
  26. filter: true,
  27. container: undefined
  28. };
  29. util.extend(this.options, this.defaultOptions);
  30. this.configureOptions = configureOptions;
  31. this.moduleOptions = {};
  32. this.domElements = [];
  33. this.colorPicker = new ColorPicker(pixelRatio);
  34. this.wrapper;
  35. }
  36. /**
  37. * refresh all options.
  38. * Because all modules parse their options by themselves, we just use their options. We copy them here.
  39. *
  40. * @param options
  41. */
  42. setOptions(options) {
  43. if (options !== undefined) {
  44. let enabled = true;
  45. if (typeof options === 'string') {
  46. this.options.filter = options;
  47. }
  48. else if (options instanceof Array) {
  49. this.options.filter = options.join();
  50. }
  51. else if (typeof options === 'object') {
  52. if (options.container !== undefined) {
  53. this.options.container = options.container;
  54. }
  55. if (options.filter !== undefined) {
  56. this.options.filter = options.filter;
  57. }
  58. if (options.enabled !== undefined) {
  59. enabled = options.enabled;
  60. }
  61. }
  62. else if (typeof options === 'boolean') {
  63. this.options.filter = true;
  64. enabled = options;
  65. }
  66. else if (typeof options === 'function') {
  67. this.options.filter = options;
  68. enabled = true;
  69. }
  70. this.options.enabled = enabled;
  71. }
  72. this._clean();
  73. }
  74. setModuleOptions(moduleOptions) {
  75. this.moduleOptions = moduleOptions;
  76. if (this.options.enabled === true) {
  77. this._clean();
  78. if (this.options.container !== undefined) {
  79. this.container = this.options.container;
  80. }
  81. this._create();
  82. }
  83. }
  84. /**
  85. * Create all DOM elements
  86. * @param {Boolean | String} config
  87. * @private
  88. */
  89. _create() {
  90. this._clean();
  91. this.changedOptions = [];
  92. let config = this.options.filter;
  93. let counter = 0;
  94. let show = false;
  95. for (let option in this.configureOptions) {
  96. if (this.configureOptions.hasOwnProperty(option)) {
  97. this.allowCreation = false;
  98. show = false;
  99. if (typeof config === 'function') {
  100. show = config(option,[]);
  101. show = show || this._handleObject(this.configureOptions[option], [option]);
  102. }
  103. else if (config === true || config.indexOf(option) !== -1) {
  104. show = true;
  105. }
  106. if (show !== false) {
  107. this.allowCreation = true;
  108. // linebreak between categories
  109. if (counter > 0) {
  110. this._makeItem([]);
  111. }
  112. // a header for the category
  113. this._makeHeader(option);
  114. // get the suboptions
  115. this._handleObject(this.configureOptions[option], [option]);
  116. }
  117. counter++;
  118. }
  119. }
  120. let generateButton = document.createElement('div');
  121. generateButton.className = 'vis-network-configuration button';
  122. generateButton.innerHTML = 'generate options';
  123. generateButton.onclick = () => {this._printOptions();};
  124. generateButton.onmouseover = () => {generateButton.className = 'vis-network-configuration button hover';};
  125. generateButton.onmouseout = () => {generateButton.className = 'vis-network-configuration button';};
  126. this.optionsContainer = document.createElement('div');
  127. this.optionsContainer.className = 'vis-network-configuration vis-option-container';
  128. this.domElements.push(this.optionsContainer);
  129. this.domElements.push(generateButton);
  130. this._push();
  131. this.colorPicker.insertTo(this.container);
  132. }
  133. /**
  134. * draw all DOM elements on the screen
  135. * @private
  136. */
  137. _push() {
  138. this.wrapper = document.createElement('div');
  139. this.wrapper.className = 'vis-network-configuration-wrapper';
  140. this.container.appendChild(this.wrapper);
  141. for (var i = 0; i < this.domElements.length; i++) {
  142. this.wrapper.appendChild(this.domElements[i]);
  143. }
  144. }
  145. /**
  146. * delete all DOM elements
  147. * @private
  148. */
  149. _clean() {
  150. for (var i = 0; i < this.domElements.length; i++) {
  151. this.wrapper.removeChild(this.domElements[i]);
  152. }
  153. if (this.wrapper !== undefined) {
  154. this.container.removeChild(this.wrapper);
  155. this.wrapper = undefined;
  156. }
  157. this.domElements = [];
  158. }
  159. /**
  160. * get the value from the actualOptions if it exists
  161. * @param {array} path | where to look for the actual option
  162. * @returns {*}
  163. * @private
  164. */
  165. _getValue(path) {
  166. let base = this.moduleOptions;
  167. for (let i = 0; i < path.length; i++) {
  168. if (base[path[i]] !== undefined) {
  169. base = base[path[i]];
  170. }
  171. else {
  172. base = undefined;
  173. break;
  174. }
  175. }
  176. return base;
  177. }
  178. /**
  179. * all option elements are wrapped in an item
  180. * @param path
  181. * @param domElements
  182. * @private
  183. */
  184. _makeItem(path, ...domElements) {
  185. if (this.allowCreation === true) {
  186. let item = document.createElement('div');
  187. item.className = 'vis-network-configuration item s' + path.length;
  188. domElements.forEach((element) => {
  189. item.appendChild(element);
  190. });
  191. this.domElements.push(item);
  192. }
  193. }
  194. /**
  195. * header for major subjects
  196. * @param name
  197. * @private
  198. */
  199. _makeHeader(name) {
  200. let div = document.createElement('div');
  201. div.className = 'vis-network-configuration header';
  202. div.innerHTML = name;
  203. this._makeItem([],div);
  204. }
  205. /**
  206. * make a label, if it is an object label, it gets different styling.
  207. * @param name
  208. * @param path
  209. * @param objectLabel
  210. * @returns {HTMLElement}
  211. * @private
  212. */
  213. _makeLabel(name, path, objectLabel = false) {
  214. let div = document.createElement('div');
  215. div.className = 'vis-network-configuration label s' + path.length;
  216. if (objectLabel === true) {
  217. div.innerHTML = '<i><b>' + name + ':</b></i>';
  218. }
  219. else {
  220. div.innerHTML = name + ':';
  221. }
  222. return div;
  223. }
  224. /**
  225. * make a dropdown list for multiple possible string optoins
  226. * @param arr
  227. * @param value
  228. * @param path
  229. * @private
  230. */
  231. _makeDropdown(arr, value, path) {
  232. let select = document.createElement('select');
  233. select.className = 'vis-network-configuration select';
  234. let selectedValue = 0;
  235. if (value !== undefined) {
  236. if (arr.indexOf(value) !== -1) {
  237. selectedValue = arr.indexOf(value);
  238. }
  239. }
  240. for (let i = 0; i < arr.length; i++) {
  241. let option = document.createElement('option');
  242. option.value = arr[i];
  243. if (i === selectedValue) {
  244. option.selected = 'selected';
  245. }
  246. option.innerHTML = arr[i];
  247. select.appendChild(option);
  248. }
  249. let me = this;
  250. select.onchange = function () {me._update(this.value, path);};
  251. let label = this._makeLabel(path[path.length-1], path);
  252. this._makeItem(path, label, select);
  253. }
  254. /**
  255. * make a range object for numeric options
  256. * @param arr
  257. * @param value
  258. * @param path
  259. * @private
  260. */
  261. _makeRange(arr, value, path) {
  262. let defaultValue = arr[0];
  263. let min = arr[1];
  264. let max = arr[2];
  265. let step = arr[3];
  266. let range = document.createElement('input');
  267. range.type = 'range';
  268. range.className = 'vis-network-configuration range';
  269. range.min = min;
  270. range.max = max;
  271. range.step = step;
  272. if (value !== undefined) {
  273. if (value * 0.1 < min) {
  274. range.min = value / 10;
  275. }
  276. if (value * 2 > max && max !== 1) {
  277. range.max = value * 2;
  278. }
  279. range.value = value;
  280. }
  281. else {
  282. range.value = defaultValue;
  283. }
  284. let input = document.createElement('input');
  285. input.className = 'vis-network-configuration rangeinput';
  286. input.value = range.value;
  287. var me = this;
  288. range.onchange = function () {input.value = this.value; me._update(Number(this.value), path);};
  289. range.oninput = function () {input.value = this.value; };
  290. let label = this._makeLabel(path[path.length-1], path);
  291. this._makeItem(path, label, range, input);
  292. }
  293. /**
  294. * make a checkbox for boolean options.
  295. * @param defaultValue
  296. * @param value
  297. * @param path
  298. * @private
  299. */
  300. _makeCheckbox(defaultValue, value, path) {
  301. var checkbox = document.createElement('input');
  302. checkbox.type = 'checkbox';
  303. checkbox.className = 'vis-network-configuration checkbox';
  304. checkbox.checked = defaultValue;
  305. if (value !== undefined) {
  306. checkbox.checked = value;
  307. if (value !== defaultValue) {
  308. if (typeof defaultValue === 'object') {
  309. if (value !== defaultValue.enabled) {
  310. this.changedOptions.push({path:path, value:value});
  311. }
  312. }
  313. else {
  314. this.changedOptions.push({path:path, value:value});
  315. }
  316. }
  317. }
  318. let me = this;
  319. checkbox.onchange = function() {me._update(this.checked, path)};
  320. let label = this._makeLabel(path[path.length-1], path);
  321. this._makeItem(path, label, checkbox);
  322. }
  323. /**
  324. * make a text input field for string options.
  325. * @param defaultValue
  326. * @param value
  327. * @param path
  328. * @private
  329. */
  330. _makeTextInput(defaultValue, value, path) {
  331. var checkbox = document.createElement('input');
  332. checkbox.type = 'text';
  333. checkbox.className = 'vis-network-configuration text';
  334. checkbox.value = value;
  335. if (value !== defaultValue) {
  336. this.changedOptions.push({path:path, value:value});
  337. }
  338. let me = this;
  339. checkbox.onchange = function() {me._update(this.value, path)};
  340. let label = this._makeLabel(path[path.length-1], path);
  341. this._makeItem(path, label, checkbox);
  342. }
  343. /**
  344. * make a color field with a color picker for color fields
  345. * @param arr
  346. * @param value
  347. * @param path
  348. * @private
  349. */
  350. _makeColorField(arr, value, path) {
  351. let defaultColor = arr[1];
  352. let div = document.createElement('div');
  353. value = value === undefined ? defaultColor : value;
  354. if (value !== 'none') {
  355. div.className = 'vis-network-configuration colorBlock';
  356. div.style.backgroundColor = value;
  357. }
  358. else {
  359. div.className = 'vis-network-configuration colorBlock none';
  360. }
  361. value = value === undefined ? defaultColor : value;
  362. div.onclick = () => {
  363. this._showColorPicker(value,div,path);
  364. };
  365. let label = this._makeLabel(path[path.length-1], path);
  366. this._makeItem(path,label, div);
  367. }
  368. /**
  369. * used by the color buttons to call the color picker.
  370. * @param event
  371. * @param value
  372. * @param div
  373. * @param path
  374. * @private
  375. */
  376. _showColorPicker(value, div, path) {
  377. let rect = div.getBoundingClientRect();
  378. let bodyRect = document.body.getBoundingClientRect();
  379. let pickerX = rect.left + rect.width + 5;
  380. let pickerY = rect.top - bodyRect.top + rect.height*0.5;
  381. this.colorPicker.show(pickerX,pickerY);
  382. this.colorPicker.setColor(value);
  383. this.colorPicker.setCallback((color) => {
  384. let colorString = 'rgba(' + color.r + ',' + color.g + ',' + color.b + ',' + color.a + ')';
  385. div.style.backgroundColor = colorString;
  386. this._update(colorString,path);
  387. })
  388. }
  389. /**
  390. * parse an object and draw the correct items
  391. * @param obj
  392. * @param path
  393. * @private
  394. */
  395. _handleObject(obj, path = []) {
  396. let show = false;
  397. let config = this.options.filter;
  398. let visibleInSet = false;
  399. for (let subObj in obj) {
  400. if (obj.hasOwnProperty(subObj)) {
  401. show = false;
  402. if (typeof config === 'function') {
  403. show = config(subObj,path);
  404. }
  405. if (show !== false) {
  406. visibleInSet = true;
  407. let item = obj[subObj];
  408. let newPath = util.copyAndExtendArray(path, subObj);
  409. let value = this._getValue(newPath);
  410. if (item instanceof Array) {
  411. this._handleArray(item, value, newPath);
  412. }
  413. else if (typeof item === 'string') {
  414. this._makeTextInput(item, value, newPath);
  415. }
  416. else if (typeof item === 'boolean') {
  417. this._makeCheckbox(item, value, newPath);
  418. }
  419. else if (item instanceof Object) {
  420. // collapse the physics options that are not enabled
  421. let draw = true;
  422. if (path.indexOf('physics') !== -1) {
  423. if (this.moduleOptions.physics.solver !== subObj) {
  424. draw = false;
  425. }
  426. }
  427. if (draw === true) {
  428. // initially collapse options with an disabled enabled option.
  429. if (item.enabled !== undefined) {
  430. let enabledPath = util.copyAndExtendArray(newPath, 'enabled');
  431. let enabledValue = this._getValue(enabledPath);
  432. if (enabledValue === true) {
  433. let label = this._makeLabel(subObj, newPath, true);
  434. this._makeItem(newPath, label);
  435. visibleInSet = this._handleObject(item, newPath) || visibleInSet;
  436. }
  437. else {
  438. this._makeCheckbox(item, enabledValue, newPath);
  439. }
  440. }
  441. else {
  442. let label = this._makeLabel(subObj, newPath, true);
  443. this._makeItem(newPath, label);
  444. visibleInSet = this._handleObject(item, newPath) || visibleInSet;
  445. }
  446. }
  447. }
  448. else {
  449. console.error('dont know how to handle', item, subObj, newPath);
  450. }
  451. }
  452. }
  453. }
  454. return visibleInSet;
  455. }
  456. /**
  457. * handle the array type of option
  458. * @param optionName
  459. * @param arr
  460. * @param value
  461. * @param path
  462. * @private
  463. */
  464. _handleArray(arr, value, path) {
  465. if (typeof arr[0] === 'string' && arr[0] === 'color') {
  466. this._makeColorField(arr, value, path);
  467. if (arr[1] !== value) {this.changedOptions.push({path:path, value:value});}
  468. }
  469. else if (typeof arr[0] === 'string') {
  470. this._makeDropdown(arr, value, path);
  471. if (arr[0] !== value) {this.changedOptions.push({path:path, value:value});}
  472. }
  473. else if (typeof arr[0] === 'number') {
  474. this._makeRange(arr, value, path);
  475. if (arr[0] !== value) {this.changedOptions.push({path:path, value:Number(value)});}
  476. }
  477. }
  478. /**
  479. * called to update the network with the new settings.
  480. * @param value
  481. * @param path
  482. * @private
  483. */
  484. _update(value, path) {
  485. let options = this._constructOptions(value,path);
  486. this.parent.setOptions(options);
  487. }
  488. _constructOptions(value, path, optionsObj = {}) {
  489. let pointer = optionsObj;
  490. // when dropdown boxes can be string or boolean, we typecast it into correct types
  491. value = value === 'true' ? true : value;
  492. value = value === 'false' ? false : value;
  493. for (let i = 0; i < path.length; i++) {
  494. if (path[i] !== 'global') {
  495. if (pointer[path[i]] === undefined) {
  496. pointer[path[i]] = {};
  497. }
  498. if (i !== path.length - 1) {
  499. pointer = pointer[path[i]];
  500. }
  501. else {
  502. pointer[path[i]] = value;
  503. }
  504. }
  505. }
  506. return optionsObj;
  507. }
  508. _printOptions() {
  509. let options = {};
  510. for (var i = 0; i < this.changedOptions.length; i++) {
  511. this._constructOptions(this.changedOptions[i].value, this.changedOptions[i].path, options)
  512. }
  513. this.optionsContainer.innerHTML = '<pre>var options = ' + JSON.stringify(options, null, 2) + '</pre>';
  514. }
  515. }
  516. export default ConfigurationSystem;