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.

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