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.

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