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.

438 lines
9.8 KiB

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