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.

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