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.

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