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.

440 lines
9.9 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 === "" || typeof str != "string") {
  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.onclick_callback = null;
  190. dst.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window?
  191. }
  192. function setOptions(options, dst) {
  193. if (options === undefined) {
  194. return;
  195. }
  196. if (dst === undefined) {
  197. throw new Error('No dst passed');
  198. }
  199. if (DEFAULTS === undefined || isEmpty(DEFAULTS)) {
  200. throw new Error('DEFAULTS not set for module Settings');
  201. }
  202. // Handle the parameters which can be simply copied over
  203. safeCopy(options, dst, OPTIONKEYS);
  204. safeCopy(options, dst, PREFIXEDOPTIONKEYS, 'default');
  205. // Handle the more complex ('special') fields
  206. setSpecialSettings(options, dst);
  207. }
  208. /**
  209. * Special handling for certain parameters
  210. *
  211. * 'Special' here means: setting requires more than a simple copy
  212. */
  213. function setSpecialSettings(src, dst) {
  214. if (src.backgroundColor !== undefined) {
  215. setBackgroundColor(src.backgroundColor, dst);
  216. }
  217. setDataColor(src.dataColor, dst);
  218. setStyle(src.style, dst);
  219. setShowLegend(src.showLegend, dst);
  220. setCameraPosition(src.cameraPosition, dst);
  221. // As special fields go, this is an easy one; just a translation of the name.
  222. // Can't use this.tooltip directly, because that field exists internally
  223. if (src.tooltip !== undefined) {
  224. dst.showTooltip = src.tooltip;
  225. }
  226. if (src.onclick != undefined) {
  227. dst.onclick_callback = src.onclick;
  228. }
  229. }
  230. /**
  231. * Set the value of setting 'showLegend'
  232. *
  233. * This depends on the value of the style fields, so it must be called
  234. * after the style field has been initialized.
  235. */
  236. function setShowLegend(showLegend, dst) {
  237. if (showLegend === undefined) {
  238. // If the default was auto, make a choice for this field
  239. var isAutoByDefault = (DEFAULTS.showLegend === undefined);
  240. if (isAutoByDefault) {
  241. // these styles default to having legends
  242. var isLegendGraphStyle = dst.style === STYLE.DOTCOLOR
  243. || dst.style === STYLE.DOTSIZE;
  244. dst.showLegend = isLegendGraphStyle;
  245. } else {
  246. // Leave current value as is
  247. }
  248. } else {
  249. dst.showLegend = showLegend;
  250. }
  251. }
  252. /**
  253. * Retrieve the style index from given styleName
  254. * @param {string} styleName Style name such as 'dot', 'grid', 'dot-line'
  255. * @return {Number} styleNumber Enumeration value representing the style, or -1
  256. * when not found
  257. */
  258. function getStyleNumberByName(styleName) {
  259. var number = STYLENAME[styleName];
  260. if (number === undefined) {
  261. return -1;
  262. }
  263. return number;
  264. }
  265. /**
  266. * Check if given number is a valid style number.
  267. *
  268. * @return true if valid, false otherwise
  269. */
  270. function checkStyleNumber(style) {
  271. var valid = false;
  272. for (var n in STYLE) {
  273. if (STYLE[n] === style) {
  274. valid = true;
  275. break;
  276. }
  277. }
  278. return valid;
  279. }
  280. function setStyle(style, dst) {
  281. if (style === undefined) {
  282. return; // Nothing to do
  283. }
  284. var styleNumber;
  285. if (typeof style === 'string') {
  286. styleNumber = getStyleNumberByName(style);
  287. if (styleNumber === -1 ) {
  288. throw new Error('Style \'' + style + '\' is invalid');
  289. }
  290. } else {
  291. // Do a pedantic check on style number value
  292. if (!checkStyleNumber(style)) {
  293. throw new Error('Style \'' + style + '\' is invalid');
  294. }
  295. styleNumber = style;
  296. }
  297. dst.style = styleNumber;
  298. }
  299. /**
  300. * Set the background styling for the graph
  301. * @param {string | {fill: string, stroke: string, strokeWidth: string}} backgroundColor
  302. */
  303. function setBackgroundColor(backgroundColor, dst) {
  304. var fill = 'white';
  305. var stroke = 'gray';
  306. var strokeWidth = 1;
  307. if (typeof(backgroundColor) === 'string') {
  308. fill = backgroundColor;
  309. stroke = 'none';
  310. strokeWidth = 0;
  311. }
  312. else if (typeof(backgroundColor) === 'object') {
  313. if (backgroundColor.fill !== undefined) fill = backgroundColor.fill;
  314. if (backgroundColor.stroke !== undefined) stroke = backgroundColor.stroke;
  315. if (backgroundColor.strokeWidth !== undefined) strokeWidth = backgroundColor.strokeWidth;
  316. }
  317. else {
  318. throw new Error('Unsupported type of backgroundColor');
  319. }
  320. dst.frame.style.backgroundColor = fill;
  321. dst.frame.style.borderColor = stroke;
  322. dst.frame.style.borderWidth = strokeWidth + 'px';
  323. dst.frame.style.borderStyle = 'solid';
  324. }
  325. function setDataColor(dataColor, dst) {
  326. if (dataColor === undefined) {
  327. return; // Nothing to do
  328. }
  329. if (dst.dataColor === undefined) {
  330. dst.dataColor = {};
  331. }
  332. if (typeof dataColor === 'string') {
  333. dst.dataColor.fill = dataColor;
  334. dst.dataColor.stroke = dataColor;
  335. }
  336. else {
  337. if (dataColor.fill) {
  338. dst.dataColor.fill = dataColor.fill;
  339. }
  340. if (dataColor.stroke) {
  341. dst.dataColor.stroke = dataColor.stroke;
  342. }
  343. if (dataColor.strokeWidth !== undefined) {
  344. dst.dataColor.strokeWidth = dataColor.strokeWidth;
  345. }
  346. }
  347. }
  348. function setCameraPosition(cameraPosition, dst) {
  349. var camPos = cameraPosition;
  350. if (camPos === undefined) {
  351. return;
  352. }
  353. if (dst.camera === undefined) {
  354. dst.camera = new Camera();
  355. }
  356. dst.camera.setArmRotation(camPos.horizontal, camPos.vertical);
  357. dst.camera.setArmLength(camPos.distance);
  358. }
  359. module.exports.STYLE = STYLE;
  360. module.exports.setDefaults = setDefaults;
  361. module.exports.setOptions = setOptions;
  362. module.exports.setCameraPosition = setCameraPosition;