UNPKG

11.2 kBJavaScriptView Raw
1////////////////////////////////////////////////////////////////////////////////
2// This modules handles the options for Graph3d.
3//
4////////////////////////////////////////////////////////////////////////////////
5var util = require('vis-util');
6var Camera = require('./Camera');
7var Point3d = require('./Point3d');
8
9
10// enumerate the available styles
11var STYLE = {
12 BAR : 0,
13 BARCOLOR: 1,
14 BARSIZE : 2,
15 DOT : 3,
16 DOTLINE : 4,
17 DOTCOLOR: 5,
18 DOTSIZE : 6,
19 GRID : 7,
20 LINE : 8,
21 SURFACE : 9
22};
23
24
25// The string representations of the styles
26var STYLENAME = {
27 'dot' : STYLE.DOT,
28 'dot-line' : STYLE.DOTLINE,
29 'dot-color': STYLE.DOTCOLOR,
30 'dot-size' : STYLE.DOTSIZE,
31 'line' : STYLE.LINE,
32 'grid' : STYLE.GRID,
33 'surface' : STYLE.SURFACE,
34 'bar' : STYLE.BAR,
35 'bar-color': STYLE.BARCOLOR,
36 'bar-size' : STYLE.BARSIZE
37};
38
39
40/**
41 * Field names in the options hash which are of relevance to the user.
42 *
43 * Specifically, these are the fields which require no special handling,
44 * and can be directly copied over.
45 */
46var OPTIONKEYS = [
47 'width',
48 'height',
49 'filterLabel',
50 'legendLabel',
51 'xLabel',
52 'yLabel',
53 'zLabel',
54 'xValueLabel',
55 'yValueLabel',
56 'zValueLabel',
57 'showXAxis',
58 'showYAxis',
59 'showZAxis',
60 'showGrid',
61 'showPerspective',
62 'showShadow',
63 'keepAspectRatio',
64 'verticalRatio',
65 'dotSizeRatio',
66 'dotSizeMinFraction',
67 'dotSizeMaxFraction',
68 'showAnimationControls',
69 'animationInterval',
70 'animationPreload',
71 'animationAutoStart',
72 'axisColor',
73 'gridColor',
74 'xCenter',
75 'yCenter',
76 'zoomable',
77 'ctrlToZoom'
78];
79
80
81/**
82 * Field names in the options hash which are of relevance to the user.
83 *
84 * Same as OPTIONKEYS, but internally these fields are stored with
85 * prefix 'default' in the name.
86 */
87var PREFIXEDOPTIONKEYS = [
88 'xBarWidth',
89 'yBarWidth',
90 'valueMin',
91 'valueMax',
92 'xMin',
93 'xMax',
94 'xStep',
95 'yMin',
96 'yMax',
97 'yStep',
98 'zMin',
99 'zMax',
100 'zStep'
101];
102
103
104// Placeholder for DEFAULTS reference
105var DEFAULTS = undefined;
106
107
108/**
109 * Check if given hash is empty.
110 *
111 * Source: http://stackoverflow.com/a/679937
112 *
113 * @param {object} obj
114 * @returns {boolean}
115 */
116function isEmpty(obj) {
117 for(var prop in obj) {
118 if (obj.hasOwnProperty(prop))
119 return false;
120 }
121
122 return true;
123}
124
125
126/**
127 * Make first letter of parameter upper case.
128 *
129 * Source: http://stackoverflow.com/a/1026087
130 *
131 * @param {string} str
132 * @returns {string}
133 */
134function capitalize(str) {
135 if (str === undefined || str === "" || typeof str != "string") {
136 return str;
137 }
138
139 return str.charAt(0).toUpperCase() + str.slice(1);
140}
141
142
143/**
144 * Add a prefix to a field name, taking style guide into account
145 *
146 * @param {string} prefix
147 * @param {string} fieldName
148 * @returns {string}
149 */
150function prefixFieldName(prefix, fieldName) {
151 if (prefix === undefined || prefix === "") {
152 return fieldName;
153 }
154
155 return prefix + capitalize(fieldName);
156}
157
158
159/**
160 * Forcibly copy fields from src to dst in a controlled manner.
161 *
162 * A given field in dst will always be overwitten. If this field
163 * is undefined or not present in src, the field in dst will
164 * be explicitly set to undefined.
165 *
166 * The intention here is to be able to reset all option fields.
167 *
168 * Only the fields mentioned in array 'fields' will be handled.
169 *
170 * @param {object} src
171 * @param {object} dst
172 * @param {array<string>} fields array with names of fields to copy
173 * @param {string} [prefix] prefix to use for the target fields.
174 */
175function forceCopy(src, dst, fields, prefix) {
176 var srcKey;
177 var dstKey;
178
179 for (var i = 0; i < fields.length; ++i) {
180 srcKey = fields[i];
181 dstKey = prefixFieldName(prefix, srcKey);
182
183 dst[dstKey] = src[srcKey];
184 }
185}
186
187
188/**
189 * Copy fields from src to dst in a safe and controlled manner.
190 *
191 * Only the fields mentioned in array 'fields' will be copied over,
192 * and only if these are actually defined.
193 *
194 * @param {object} src
195 * @param {object} dst
196 * @param {array<string>} fields array with names of fields to copy
197 * @param {string} [prefix] prefix to use for the target fields.
198 */
199function safeCopy(src, dst, fields, prefix) {
200 var srcKey;
201 var dstKey;
202
203 for (var i = 0; i < fields.length; ++i) {
204 srcKey = fields[i];
205 if (src[srcKey] === undefined) continue;
206
207 dstKey = prefixFieldName(prefix, srcKey);
208
209 dst[dstKey] = src[srcKey];
210 }
211}
212
213
214/**
215 * Initialize dst with the values in src.
216 *
217 * src is the hash with the default values.
218 * A reference DEFAULTS to this hash is stored locally for
219 * further handling.
220 *
221 * For now, dst is assumed to be a Graph3d instance.
222 * @param {object} src
223 * @param {object} dst
224 */
225function setDefaults(src, dst) {
226 if (src === undefined || isEmpty(src)) {
227 throw new Error('No DEFAULTS passed');
228 }
229 if (dst === undefined) {
230 throw new Error('No dst passed');
231 }
232
233 // Remember defaults for future reference
234 DEFAULTS = src;
235
236 // Handle the defaults which can be simply copied over
237 forceCopy(src, dst, OPTIONKEYS);
238 forceCopy(src, dst, PREFIXEDOPTIONKEYS, 'default');
239
240 // Handle the more complex ('special') fields
241 setSpecialSettings(src, dst);
242
243 // Following are internal fields, not part of the user settings
244 dst.margin = 10; // px
245 dst.showGrayBottom = false; // TODO: this does not work correctly
246 dst.showTooltip = false;
247 dst.onclick_callback = null;
248 dst.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window?
249}
250
251/**
252 *
253 * @param {object} options
254 * @param {object} dst
255 */
256function setOptions(options, dst) {
257 if (options === undefined) {
258 return;
259 }
260 if (dst === undefined) {
261 throw new Error('No dst passed');
262 }
263
264 if (DEFAULTS === undefined || isEmpty(DEFAULTS)) {
265 throw new Error('DEFAULTS not set for module Settings');
266 }
267
268 // Handle the parameters which can be simply copied over
269 safeCopy(options, dst, OPTIONKEYS);
270 safeCopy(options, dst, PREFIXEDOPTIONKEYS, 'default');
271
272 // Handle the more complex ('special') fields
273 setSpecialSettings(options, dst);
274}
275
276/**
277 * Special handling for certain parameters
278 *
279 * 'Special' here means: setting requires more than a simple copy
280 *
281 * @param {object} src
282 * @param {object} dst
283 */
284function setSpecialSettings(src, dst) {
285 if (src.backgroundColor !== undefined) {
286 setBackgroundColor(src.backgroundColor, dst);
287 }
288
289 setDataColor(src.dataColor, dst);
290 setStyle(src.style, dst);
291 setShowLegend(src.showLegend, dst);
292 setCameraPosition(src.cameraPosition, dst);
293
294 // As special fields go, this is an easy one; just a translation of the name.
295 // Can't use this.tooltip directly, because that field exists internally
296 if (src.tooltip !== undefined) {
297 dst.showTooltip = src.tooltip;
298 }
299 if (src.onclick != undefined) {
300 dst.onclick_callback = src.onclick;
301 }
302
303 if (src.tooltipStyle !== undefined) {
304 util.selectiveDeepExtend(['tooltipStyle'], dst, src);
305 }
306}
307
308
309/**
310 * Set the value of setting 'showLegend'
311 *
312 * This depends on the value of the style fields, so it must be called
313 * after the style field has been initialized.
314 *
315 * @param {boolean} showLegend
316 * @param {object} dst
317 */
318function setShowLegend(showLegend, dst) {
319 if (showLegend === undefined) {
320 // If the default was auto, make a choice for this field
321 var isAutoByDefault = (DEFAULTS.showLegend === undefined);
322
323 if (isAutoByDefault) {
324 // these styles default to having legends
325 var isLegendGraphStyle = dst.style === STYLE.DOTCOLOR
326 || dst.style === STYLE.DOTSIZE;
327
328 dst.showLegend = isLegendGraphStyle;
329 } else {
330 // Leave current value as is
331 }
332 } else {
333 dst.showLegend = showLegend;
334 }
335}
336
337
338/**
339 * Retrieve the style index from given styleName
340 * @param {string} styleName Style name such as 'dot', 'grid', 'dot-line'
341 * @return {number} styleNumber Enumeration value representing the style, or -1
342 * when not found
343 */
344function getStyleNumberByName(styleName) {
345 var number = STYLENAME[styleName];
346
347 if (number === undefined) {
348 return -1;
349 }
350
351 return number;
352}
353
354
355/**
356 * Check if given number is a valid style number.
357 *
358 * @param {string | number} style
359 * @return {boolean} true if valid, false otherwise
360 */
361function checkStyleNumber(style) {
362 var valid = false;
363
364 for (var n in STYLE) {
365 if (STYLE[n] === style) {
366 valid = true;
367 break;
368 }
369 }
370
371 return valid;
372}
373
374/**
375 *
376 * @param {string | number} style
377 * @param {Object} dst
378 */
379function setStyle(style, dst) {
380 if (style === undefined) {
381 return; // Nothing to do
382 }
383
384 var styleNumber;
385
386 if (typeof style === 'string') {
387 styleNumber = getStyleNumberByName(style);
388
389 if (styleNumber === -1 ) {
390 throw new Error('Style \'' + style + '\' is invalid');
391 }
392 } else {
393 // Do a pedantic check on style number value
394 if (!checkStyleNumber(style)) {
395 throw new Error('Style \'' + style + '\' is invalid');
396 }
397
398 styleNumber = style;
399 }
400
401 dst.style = styleNumber;
402}
403
404
405/**
406 * Set the background styling for the graph
407 * @param {string | {fill: string, stroke: string, strokeWidth: string}} backgroundColor
408 * @param {Object} dst
409 */
410function setBackgroundColor(backgroundColor, dst) {
411 var fill = 'white';
412 var stroke = 'gray';
413 var strokeWidth = 1;
414
415 if (typeof(backgroundColor) === 'string') {
416 fill = backgroundColor;
417 stroke = 'none';
418 strokeWidth = 0;
419 }
420 else if (typeof(backgroundColor) === 'object') {
421 if (backgroundColor.fill !== undefined) fill = backgroundColor.fill;
422 if (backgroundColor.stroke !== undefined) stroke = backgroundColor.stroke;
423 if (backgroundColor.strokeWidth !== undefined) strokeWidth = backgroundColor.strokeWidth;
424 }
425 else {
426 throw new Error('Unsupported type of backgroundColor');
427 }
428
429 dst.frame.style.backgroundColor = fill;
430 dst.frame.style.borderColor = stroke;
431 dst.frame.style.borderWidth = strokeWidth + 'px';
432 dst.frame.style.borderStyle = 'solid';
433}
434
435/**
436 *
437 * @param {string | Object} dataColor
438 * @param {Object} dst
439 */
440function setDataColor(dataColor, dst) {
441 if (dataColor === undefined) {
442 return; // Nothing to do
443 }
444
445 if (dst.dataColor === undefined) {
446 dst.dataColor = {};
447 }
448
449 if (typeof dataColor === 'string') {
450 dst.dataColor.fill = dataColor;
451 dst.dataColor.stroke = dataColor;
452 }
453 else {
454 if (dataColor.fill) {
455 dst.dataColor.fill = dataColor.fill;
456 }
457 if (dataColor.stroke) {
458 dst.dataColor.stroke = dataColor.stroke;
459 }
460 if (dataColor.strokeWidth !== undefined) {
461 dst.dataColor.strokeWidth = dataColor.strokeWidth;
462 }
463 }
464}
465
466/**
467 *
468 * @param {Object} cameraPosition
469 * @param {Object} dst
470 */
471function setCameraPosition(cameraPosition, dst) {
472 var camPos = cameraPosition;
473 if (camPos === undefined) {
474 return;
475 }
476
477 if (dst.camera === undefined) {
478 dst.camera = new Camera();
479 }
480
481 dst.camera.setArmRotation(camPos.horizontal, camPos.vertical);
482 dst.camera.setArmLength(camPos.distance);
483}
484
485
486module.exports.STYLE = STYLE;
487module.exports.setDefaults = setDefaults;
488module.exports.setOptions = setOptions;
489module.exports.setCameraPosition = setCameraPosition;