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.

489 lines
11 KiB

  1. ////////////////////////////////////////////////////////////////////////////////
  2. // This modules handles the options for Graph3d.
  3. //
  4. ////////////////////////////////////////////////////////////////////////////////
  5. var util = require('../util');
  6. var Camera = require('./Camera');
  7. var Point3d = require('./Point3d');
  8. // enumerate the available styles
  9. var STYLE = {
  10. BAR : 0,
  11. BARCOLOR: 1,
  12. BARSIZE : 2,
  13. DOT : 3,
  14. DOTLINE : 4,
  15. DOTCOLOR: 5,
  16. DOTSIZE : 6,
  17. GRID : 7,
  18. LINE : 8,
  19. SURFACE : 9
  20. };
  21. // The string representations of the styles
  22. var STYLENAME = {
  23. 'dot' : STYLE.DOT,
  24. 'dot-line' : STYLE.DOTLINE,
  25. 'dot-color': STYLE.DOTCOLOR,
  26. 'dot-size' : STYLE.DOTSIZE,
  27. 'line' : STYLE.LINE,
  28. 'grid' : STYLE.GRID,
  29. 'surface' : STYLE.SURFACE,
  30. 'bar' : STYLE.BAR,
  31. 'bar-color': STYLE.BARCOLOR,
  32. 'bar-size' : STYLE.BARSIZE
  33. };
  34. /**
  35. * Field names in the options hash which are of relevance to the user.
  36. *
  37. * Specifically, these are the fields which require no special handling,
  38. * and can be directly copied over.
  39. */
  40. var OPTIONKEYS = [
  41. 'width',
  42. 'height',
  43. 'filterLabel',
  44. 'legendLabel',
  45. 'xLabel',
  46. 'yLabel',
  47. 'zLabel',
  48. 'xValueLabel',
  49. 'yValueLabel',
  50. 'zValueLabel',
  51. 'showXAxis',
  52. 'showYAxis',
  53. 'showZAxis',
  54. 'showGrid',
  55. 'showPerspective',
  56. 'showShadow',
  57. 'keepAspectRatio',
  58. 'verticalRatio',
  59. 'dotSizeRatio',
  60. 'dotSizeMinFraction',
  61. 'dotSizeMaxFraction',
  62. 'showAnimationControls',
  63. 'animationInterval',
  64. 'animationPreload',
  65. 'animationAutoStart',
  66. 'axisColor',
  67. 'gridColor',
  68. 'xCenter',
  69. 'yCenter',
  70. 'zoomable',
  71. 'ctrlToZoom'
  72. ];
  73. /**
  74. * Field names in the options hash which are of relevance to the user.
  75. *
  76. * Same as OPTIONKEYS, but internally these fields are stored with
  77. * prefix 'default' in the name.
  78. */
  79. var PREFIXEDOPTIONKEYS = [
  80. 'xBarWidth',
  81. 'yBarWidth',
  82. 'valueMin',
  83. 'valueMax',
  84. 'xMin',
  85. 'xMax',
  86. 'xStep',
  87. 'yMin',
  88. 'yMax',
  89. 'yStep',
  90. 'zMin',
  91. 'zMax',
  92. 'zStep'
  93. ];
  94. // Placeholder for DEFAULTS reference
  95. var DEFAULTS = undefined;
  96. /**
  97. * Check if given hash is empty.
  98. *
  99. * Source: http://stackoverflow.com/a/679937
  100. *
  101. * @param {object} obj
  102. * @returns {boolean}
  103. */
  104. function isEmpty(obj) {
  105. for(var prop in obj) {
  106. if (obj.hasOwnProperty(prop))
  107. return false;
  108. }
  109. return true;
  110. }
  111. /**
  112. * Make first letter of parameter upper case.
  113. *
  114. * Source: http://stackoverflow.com/a/1026087
  115. *
  116. * @param {string} str
  117. * @returns {string}
  118. */
  119. function capitalize(str) {
  120. if (str === undefined || str === "" || typeof str != "string") {
  121. return str;
  122. }
  123. return str.charAt(0).toUpperCase() + str.slice(1);
  124. }
  125. /**
  126. * Add a prefix to a field name, taking style guide into account
  127. *
  128. * @param {string} prefix
  129. * @param {string} fieldName
  130. * @returns {string}
  131. */
  132. function prefixFieldName(prefix, fieldName) {
  133. if (prefix === undefined || prefix === "") {
  134. return fieldName;
  135. }
  136. return prefix + capitalize(fieldName);
  137. }
  138. /**
  139. * Forcibly copy fields from src to dst in a controlled manner.
  140. *
  141. * A given field in dst will always be overwitten. If this field
  142. * is undefined or not present in src, the field in dst will
  143. * be explicitly set to undefined.
  144. *
  145. * The intention here is to be able to reset all option fields.
  146. *
  147. * Only the fields mentioned in array 'fields' will be handled.
  148. *
  149. * @param {object} src
  150. * @param {object} dst
  151. * @param {array<string>} fields array with names of fields to copy
  152. * @param {string} [prefix] prefix to use for the target fields.
  153. */
  154. function forceCopy(src, dst, fields, prefix) {
  155. var srcKey;
  156. var dstKey;
  157. for (var i = 0; i < fields.length; ++i) {
  158. srcKey = fields[i];
  159. dstKey = prefixFieldName(prefix, srcKey);
  160. dst[dstKey] = src[srcKey];
  161. }
  162. }
  163. /**
  164. * Copy fields from src to dst in a safe and controlled manner.
  165. *
  166. * Only the fields mentioned in array 'fields' will be copied over,
  167. * and only if these are actually defined.
  168. *
  169. * @param {object} src
  170. * @param {object} dst
  171. * @param {array<string>} fields array with names of fields to copy
  172. * @param {string} [prefix] prefix to use for the target fields.
  173. */
  174. function safeCopy(src, dst, fields, prefix) {
  175. var srcKey;
  176. var dstKey;
  177. for (var i = 0; i < fields.length; ++i) {
  178. srcKey = fields[i];
  179. if (src[srcKey] === undefined) continue;
  180. dstKey = prefixFieldName(prefix, srcKey);
  181. dst[dstKey] = src[srcKey];
  182. }
  183. }
  184. /**
  185. * Initialize dst with the values in src.
  186. *
  187. * src is the hash with the default values.
  188. * A reference DEFAULTS to this hash is stored locally for
  189. * further handling.
  190. *
  191. * For now, dst is assumed to be a Graph3d instance.
  192. * @param {object} src
  193. * @param {object} dst
  194. */
  195. function setDefaults(src, dst) {
  196. if (src === undefined || isEmpty(src)) {
  197. throw new Error('No DEFAULTS passed');
  198. }
  199. if (dst === undefined) {
  200. throw new Error('No dst passed');
  201. }
  202. // Remember defaults for future reference
  203. DEFAULTS = src;
  204. // Handle the defaults which can be simply copied over
  205. forceCopy(src, dst, OPTIONKEYS);
  206. forceCopy(src, dst, PREFIXEDOPTIONKEYS, 'default');
  207. // Handle the more complex ('special') fields
  208. setSpecialSettings(src, dst);
  209. // Following are internal fields, not part of the user settings
  210. dst.margin = 10; // px
  211. dst.showGrayBottom = false; // TODO: this does not work correctly
  212. dst.showTooltip = false;
  213. dst.onclick_callback = null;
  214. dst.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window?
  215. }
  216. /**
  217. *
  218. * @param {object} options
  219. * @param {object} dst
  220. */
  221. function setOptions(options, dst) {
  222. if (options === undefined) {
  223. return;
  224. }
  225. if (dst === undefined) {
  226. throw new Error('No dst passed');
  227. }
  228. if (DEFAULTS === undefined || isEmpty(DEFAULTS)) {
  229. throw new Error('DEFAULTS not set for module Settings');
  230. }
  231. // Handle the parameters which can be simply copied over
  232. safeCopy(options, dst, OPTIONKEYS);
  233. safeCopy(options, dst, PREFIXEDOPTIONKEYS, 'default');
  234. // Handle the more complex ('special') fields
  235. setSpecialSettings(options, dst);
  236. }
  237. /**
  238. * Special handling for certain parameters
  239. *
  240. * 'Special' here means: setting requires more than a simple copy
  241. *
  242. * @param {object} src
  243. * @param {object} dst
  244. */
  245. function setSpecialSettings(src, dst) {
  246. if (src.backgroundColor !== undefined) {
  247. setBackgroundColor(src.backgroundColor, dst);
  248. }
  249. setDataColor(src.dataColor, dst);
  250. setStyle(src.style, dst);
  251. setShowLegend(src.showLegend, dst);
  252. setCameraPosition(src.cameraPosition, dst);
  253. // As special fields go, this is an easy one; just a translation of the name.
  254. // Can't use this.tooltip directly, because that field exists internally
  255. if (src.tooltip !== undefined) {
  256. dst.showTooltip = src.tooltip;
  257. }
  258. if (src.onclick != undefined) {
  259. dst.onclick_callback = src.onclick;
  260. }
  261. if (src.tooltipStyle !== undefined) {
  262. util.selectiveDeepExtend(['tooltipStyle'], dst, src);
  263. }
  264. }
  265. /**
  266. * Set the value of setting 'showLegend'
  267. *
  268. * This depends on the value of the style fields, so it must be called
  269. * after the style field has been initialized.
  270. *
  271. * @param {boolean} showLegend
  272. * @param {object} dst
  273. */
  274. function setShowLegend(showLegend, dst) {
  275. if (showLegend === undefined) {
  276. // If the default was auto, make a choice for this field
  277. var isAutoByDefault = (DEFAULTS.showLegend === undefined);
  278. if (isAutoByDefault) {
  279. // these styles default to having legends
  280. var isLegendGraphStyle = dst.style === STYLE.DOTCOLOR
  281. || dst.style === STYLE.DOTSIZE;
  282. dst.showLegend = isLegendGraphStyle;
  283. } else {
  284. // Leave current value as is
  285. }
  286. } else {
  287. dst.showLegend = showLegend;
  288. }
  289. }
  290. /**
  291. * Retrieve the style index from given styleName
  292. * @param {string} styleName Style name such as 'dot', 'grid', 'dot-line'
  293. * @return {number} styleNumber Enumeration value representing the style, or -1
  294. * when not found
  295. */
  296. function getStyleNumberByName(styleName) {
  297. var number = STYLENAME[styleName];
  298. if (number === undefined) {
  299. return -1;
  300. }
  301. return number;
  302. }
  303. /**
  304. * Check if given number is a valid style number.
  305. *
  306. * @param {string | number} style
  307. * @return {boolean} true if valid, false otherwise
  308. */
  309. function checkStyleNumber(style) {
  310. var valid = false;
  311. for (var n in STYLE) {
  312. if (STYLE[n] === style) {
  313. valid = true;
  314. break;
  315. }
  316. }
  317. return valid;
  318. }
  319. /**
  320. *
  321. * @param {string | number} style
  322. * @param {Object} dst
  323. */
  324. function setStyle(style, dst) {
  325. if (style === undefined) {
  326. return; // Nothing to do
  327. }
  328. var styleNumber;
  329. if (typeof style === 'string') {
  330. styleNumber = getStyleNumberByName(style);
  331. if (styleNumber === -1 ) {
  332. throw new Error('Style \'' + style + '\' is invalid');
  333. }
  334. } else {
  335. // Do a pedantic check on style number value
  336. if (!checkStyleNumber(style)) {
  337. throw new Error('Style \'' + style + '\' is invalid');
  338. }
  339. styleNumber = style;
  340. }
  341. dst.style = styleNumber;
  342. }
  343. /**
  344. * Set the background styling for the graph
  345. * @param {string | {fill: string, stroke: string, strokeWidth: string}} backgroundColor
  346. * @param {Object} dst
  347. */
  348. function setBackgroundColor(backgroundColor, dst) {
  349. var fill = 'white';
  350. var stroke = 'gray';
  351. var strokeWidth = 1;
  352. if (typeof(backgroundColor) === 'string') {
  353. fill = backgroundColor;
  354. stroke = 'none';
  355. strokeWidth = 0;
  356. }
  357. else if (typeof(backgroundColor) === 'object') {
  358. if (backgroundColor.fill !== undefined) fill = backgroundColor.fill;
  359. if (backgroundColor.stroke !== undefined) stroke = backgroundColor.stroke;
  360. if (backgroundColor.strokeWidth !== undefined) strokeWidth = backgroundColor.strokeWidth;
  361. }
  362. else {
  363. throw new Error('Unsupported type of backgroundColor');
  364. }
  365. dst.frame.style.backgroundColor = fill;
  366. dst.frame.style.borderColor = stroke;
  367. dst.frame.style.borderWidth = strokeWidth + 'px';
  368. dst.frame.style.borderStyle = 'solid';
  369. }
  370. /**
  371. *
  372. * @param {string | Object} dataColor
  373. * @param {Object} dst
  374. */
  375. function setDataColor(dataColor, dst) {
  376. if (dataColor === undefined) {
  377. return; // Nothing to do
  378. }
  379. if (dst.dataColor === undefined) {
  380. dst.dataColor = {};
  381. }
  382. if (typeof dataColor === 'string') {
  383. dst.dataColor.fill = dataColor;
  384. dst.dataColor.stroke = dataColor;
  385. }
  386. else {
  387. if (dataColor.fill) {
  388. dst.dataColor.fill = dataColor.fill;
  389. }
  390. if (dataColor.stroke) {
  391. dst.dataColor.stroke = dataColor.stroke;
  392. }
  393. if (dataColor.strokeWidth !== undefined) {
  394. dst.dataColor.strokeWidth = dataColor.strokeWidth;
  395. }
  396. }
  397. }
  398. /**
  399. *
  400. * @param {Object} cameraPosition
  401. * @param {Object} dst
  402. */
  403. function setCameraPosition(cameraPosition, dst) {
  404. var camPos = cameraPosition;
  405. if (camPos === undefined) {
  406. return;
  407. }
  408. if (dst.camera === undefined) {
  409. dst.camera = new Camera();
  410. }
  411. dst.camera.setArmRotation(camPos.horizontal, camPos.vertical);
  412. dst.camera.setArmLength(camPos.distance);
  413. }
  414. module.exports.STYLE = STYLE;
  415. module.exports.setDefaults = setDefaults;
  416. module.exports.setOptions = setOptions;
  417. module.exports.setCameraPosition = setCameraPosition;