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. 'showAnimationControls',
  56. 'animationInterval',
  57. 'animationPreload',
  58. 'animationAutoStart',
  59. 'axisColor',
  60. 'gridColor',
  61. 'xCenter',
  62. 'yCenter'
  63. ];
  64. /**
  65. * Field names in the options hash which are of relevance to the user.
  66. *
  67. * Same as OPTIONKEYS, but internally these fields are stored with
  68. * prefix 'default' in the name.
  69. */
  70. var PREFIXEDOPTIONKEYS = [
  71. 'xBarWidth',
  72. 'yBarWidth',
  73. 'valueMin',
  74. 'valueMax',
  75. 'xMin',
  76. 'xMax',
  77. 'xStep',
  78. 'yMin',
  79. 'yMax',
  80. 'yStep',
  81. 'zMin',
  82. 'zMax',
  83. 'zStep'
  84. ];
  85. // Placeholder for DEFAULTS reference
  86. var DEFAULTS = undefined;
  87. /**
  88. * Check if given hash is empty.
  89. *
  90. * Source: http://stackoverflow.com/a/679937
  91. */
  92. function isEmpty(obj) {
  93. for(var prop in obj) {
  94. if (obj.hasOwnProperty(prop))
  95. return false;
  96. }
  97. return true;
  98. }
  99. /**
  100. * Make first letter of parameter upper case.
  101. *
  102. * Source: http://stackoverflow.com/a/1026087
  103. */
  104. function capitalize(str) {
  105. if (str === undefined || str === "") {
  106. return str;
  107. }
  108. return str.charAt(0).toUpperCase() + str.slice(1);
  109. }
  110. /**
  111. * Add a prefix to a field name, taking style guide into account
  112. */
  113. function prefixFieldName(prefix, fieldName) {
  114. if (prefix === undefined || prefix === "") {
  115. return fieldName;
  116. }
  117. return prefix + capitalize(fieldName);
  118. }
  119. /**
  120. * Forcibly copy fields from src to dst in a controlled manner.
  121. *
  122. * A given field in dst will always be overwitten. If this field
  123. * is undefined or not present in src, the field in dst will
  124. * be explicitly set to undefined.
  125. *
  126. * The intention here is to be able to reset all option fields.
  127. *
  128. * Only the fields mentioned in array 'fields' will be handled.
  129. *
  130. * @param fields array with names of fields to copy
  131. * @param prefix optional; prefix to use for the target fields.
  132. */
  133. function forceCopy(src, dst, fields, prefix) {
  134. var srcKey;
  135. var dstKey;
  136. for (var i in fields) {
  137. srcKey = fields[i];
  138. dstKey = prefixFieldName(prefix, srcKey);
  139. dst[dstKey] = src[srcKey];
  140. }
  141. }
  142. /**
  143. * Copy fields from src to dst in a safe and controlled manner.
  144. *
  145. * Only the fields mentioned in array 'fields' will be copied over,
  146. * and only if these are actually defined.
  147. *
  148. * @param fields array with names of fields to copy
  149. * @param prefix optional; prefix to use for the target fields.
  150. */
  151. function safeCopy(src, dst, fields, prefix) {
  152. var srcKey;
  153. var dstKey;
  154. for (var i in fields) {
  155. srcKey = fields[i];
  156. if (src[srcKey] === undefined) continue;
  157. dstKey = prefixFieldName(prefix, srcKey);
  158. dst[dstKey] = src[srcKey];
  159. }
  160. }
  161. /**
  162. * Initialize dst with the values in src.
  163. *
  164. * src is the hash with the default values.
  165. * A reference DEFAULTS to this hash is stored locally for
  166. * further handling.
  167. *
  168. * For now, dst is assumed to be a Graph3d instance.
  169. */
  170. function setDefaults(src, dst) {
  171. if (src === undefined || isEmpty(src)) {
  172. throw new Error('No DEFAULTS passed');
  173. }
  174. if (dst === undefined) {
  175. throw new Error('No dst passed');
  176. }
  177. // Remember defaults for future reference
  178. DEFAULTS = src;
  179. // Handle the defaults which can be simply copied over
  180. forceCopy(src, dst, OPTIONKEYS);
  181. forceCopy(src, dst, PREFIXEDOPTIONKEYS, 'default');
  182. // Handle the more complex ('special') fields
  183. setSpecialSettings(src, dst);
  184. // Following are internal fields, not part of the user settings
  185. dst.margin = 10; // px
  186. dst.showGrayBottom = false; // TODO: this does not work correctly
  187. dst.showTooltip = false;
  188. dst.dotSizeRatio = 0.02; // size of the dots as a fraction of the graph width
  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;