Initial OpenECOMP policy/engine commit
[policy/engine.git] / ecomp-sdk-app / src / main / webapp / static / fusion / raptor / dy3 / js / dygraph-options.js
1 /**
2  * @license
3  * Copyright 2011 Dan Vanderkam (danvdk@gmail.com)
4  * MIT-licensed (http://opensource.org/licenses/MIT)
5  */
6
7 /**
8  * @fileoverview DygraphOptions is responsible for parsing and returning information about options.
9  *
10  * Still tightly coupled to Dygraphs, we could remove some of that, you know.
11  */
12
13 var DygraphOptions = (function() {
14
15 /*jshint sub:true */
16 /*global Dygraph:false */
17 "use strict";
18
19 /*
20  * Interesting member variables: (REMOVING THIS LIST AS I CLOSURIZE)
21  * global_ - global attributes (common among all graphs, AIUI)
22  * user - attributes set by the user
23  * series_ - { seriesName -> { idx, yAxis, options }}
24  */
25
26 /**
27  * This parses attributes into an object that can be easily queried.
28  *
29  * It doesn't necessarily mean that all options are available, specifically
30  * if labels are not yet available, since those drive details of the per-series
31  * and per-axis options.
32  *
33  * @param {Dygraph} dygraph The chart to which these options belong.
34  * @constructor
35  */
36 var DygraphOptions = function(dygraph) {
37   /**
38    * The dygraph.
39    * @type {!Dygraph}
40    */
41   this.dygraph_ = dygraph;
42
43   /**
44    * Array of axis index to { series : [ series names ] , options : { axis-specific options. }
45    * @type {Array.<{series : Array.<string>, options : Object}>} @private
46    */
47   this.yAxes_ = [];
48
49   /**
50    * Contains x-axis specific options, which are stored in the options key.
51    * This matches the yAxes_ object structure (by being a dictionary with an
52    * options element) allowing for shared code.
53    * @type {options: Object} @private
54    */
55   this.xAxis_ = {};
56   this.series_ = {};
57
58   // Once these two objects are initialized, you can call get();
59   this.global_ = this.dygraph_.attrs_;
60   this.user_ = this.dygraph_.user_attrs_ || {};
61
62   /**
63    * A list of series in columnar order.
64    * @type {Array.<string>}
65    */
66   this.labels_ = [];
67
68   this.highlightSeries_ = this.get("highlightSeriesOpts") || {};
69   this.reparseSeries();
70 };
71
72 /**
73  * Not optimal, but does the trick when you're only using two axes.
74  * If we move to more axes, this can just become a function.
75  *
76  * @type {Object.<number>}
77  * @private
78  */
79 DygraphOptions.AXIS_STRING_MAPPINGS_ = {
80   'y' : 0,
81   'Y' : 0,
82   'y1' : 0,
83   'Y1' : 0,
84   'y2' : 1,
85   'Y2' : 1
86 };
87
88 /**
89  * @param {string|number} axis
90  * @private
91  */
92 DygraphOptions.axisToIndex_ = function(axis) {
93   if (typeof(axis) == "string") {
94     if (DygraphOptions.AXIS_STRING_MAPPINGS_.hasOwnProperty(axis)) {
95       return DygraphOptions.AXIS_STRING_MAPPINGS_[axis];
96     }
97     throw "Unknown axis : " + axis;
98   }
99   if (typeof(axis) == "number") {
100     if (axis === 0 || axis === 1) {
101       return axis;
102     }
103     throw "Dygraphs only supports two y-axes, indexed from 0-1.";
104   }
105   if (axis) {
106     throw "Unknown axis : " + axis;
107   }
108   // No axis specification means axis 0.
109   return 0;
110 };
111
112 /**
113  * Reparses options that are all related to series. This typically occurs when
114  * options are either updated, or source data has been made available.
115  *
116  * TODO(konigsberg): The method name is kind of weak; fix.
117  */
118 DygraphOptions.prototype.reparseSeries = function() {
119   var labels = this.get("labels");
120   if (!labels) {
121     return; // -- can't do more for now, will parse after getting the labels.
122   }
123
124   this.labels_ = labels.slice(1);
125
126   this.yAxes_ = [ { series : [], options : {}} ]; // Always one axis at least.
127   this.xAxis_ = { options : {} };
128   this.series_ = {};
129
130   // Traditionally, per-series options were specified right up there with the options. For instance
131   // {
132   //   labels: [ "X", "foo", "bar" ],
133   //   pointSize: 3,
134   //   foo : {}, // options for foo
135   //   bar : {} // options for bar
136   // }
137   //
138   // Moving forward, series really should be specified in the series element, separating them.
139   // like so:
140   //
141   // {
142   //   labels: [ "X", "foo", "bar" ],
143   //   pointSize: 3,
144   //   series : {
145   //     foo : {}, // options for foo
146   //     bar : {} // options for bar
147   //   }
148   // }
149   //
150   // So, if series is found, it's expected to contain per-series data, otherwise we fall
151   // back.
152   var oldStyleSeries = !this.user_["series"];
153
154   if (oldStyleSeries) {
155     var axisId = 0; // 0-offset; there's always one.
156     // Go through once, add all the series, and for those with {} axis options, add a new axis.
157     for (var idx = 0; idx < this.labels_.length; idx++) {
158       var seriesName = this.labels_[idx];
159
160       var optionsForSeries = this.user_[seriesName] || {};
161
162       var yAxis = 0;
163       var axis = optionsForSeries["axis"];
164       if (typeof(axis) == 'object') {
165         yAxis = ++axisId;
166         this.yAxes_[yAxis] = { series : [ seriesName ], options : axis };
167       }
168
169       // Associate series without axis options with axis 0.
170       if (!axis) { // undefined
171         this.yAxes_[0].series.push(seriesName);
172       }
173
174       this.series_[seriesName] = { idx: idx, yAxis: yAxis, options : optionsForSeries };
175     }
176
177     // Go through one more time and assign series to an axis defined by another
178     // series, e.g. { 'Y1: { axis: {} }, 'Y2': { axis: 'Y1' } }
179     for (var idx = 0; idx < this.labels_.length; idx++) {
180       var seriesName = this.labels_[idx];
181       var optionsForSeries = this.series_[seriesName]["options"];
182       var axis = optionsForSeries["axis"];
183
184       if (typeof(axis) == 'string') {
185         if (!this.series_.hasOwnProperty(axis)) {
186           Dygraph.error("Series " + seriesName + " wants to share a y-axis with " +
187                      "series " + axis + ", which does not define its own axis.");
188           return;
189         }
190         var yAxis = this.series_[axis].yAxis;
191         this.series_[seriesName].yAxis = yAxis;
192         this.yAxes_[yAxis].series.push(seriesName);
193       }
194     }
195   } else {
196     for (var idx = 0; idx < this.labels_.length; idx++) {
197       var seriesName = this.labels_[idx];
198       var optionsForSeries = this.user_.series[seriesName] || {};
199       var yAxis = DygraphOptions.axisToIndex_(optionsForSeries["axis"]);
200
201       this.series_[seriesName] = {
202         idx: idx,
203         yAxis: yAxis,
204         options : optionsForSeries };
205
206       if (!this.yAxes_[yAxis]) {
207         this.yAxes_[yAxis] =  { series : [ seriesName ], options : {} };
208       } else {
209         this.yAxes_[yAxis].series.push(seriesName);
210       }
211     }
212   }
213
214   var axis_opts = this.user_["axes"] || {};
215   Dygraph.update(this.yAxes_[0].options, axis_opts["y"] || {});
216   if (this.yAxes_.length > 1) {
217     Dygraph.update(this.yAxes_[1].options, axis_opts["y2"] || {});
218   }
219   Dygraph.update(this.xAxis_.options, axis_opts["x"] || {});
220 };
221
222 /**
223  * Get a global value.
224  *
225  * @param {string} name the name of the option.
226  */
227 DygraphOptions.prototype.get = function(name) {
228   var result = this.getGlobalUser_(name);
229   if (result !== null) {
230     return result;
231   }
232   return this.getGlobalDefault_(name);
233 };
234
235 DygraphOptions.prototype.getGlobalUser_ = function(name) {
236   if (this.user_.hasOwnProperty(name)) {
237     return this.user_[name];
238   }
239   return null;
240 };
241
242 DygraphOptions.prototype.getGlobalDefault_ = function(name) {
243   if (this.global_.hasOwnProperty(name)) {
244     return this.global_[name];
245   }
246   if (Dygraph.DEFAULT_ATTRS.hasOwnProperty(name)) {
247     return Dygraph.DEFAULT_ATTRS[name];
248   }
249   return null;
250 };
251
252 /**
253  * Get a value for a specific axis. If there is no specific value for the axis,
254  * the global value is returned.
255  *
256  * @param {string} name the name of the option.
257  * @param {string|number} axis the axis to search. Can be the string representation
258  * ("y", "y2") or the axis number (0, 1).
259  */
260 DygraphOptions.prototype.getForAxis = function(name, axis) {
261   var axisIdx;
262   var axisString;
263
264   // Since axis can be a number or a string, straighten everything out here.
265   if (typeof(axis) == 'number') {
266     axisIdx = axis;
267     axisString = axisIdx === 0 ? "y" : "y2";
268   } else {
269     if (axis == "y1") { axis = "y"; } // Standardize on 'y'. Is this bad? I think so.
270     if (axis == "y") {
271       axisIdx = 0;
272     } else if (axis == "y2") {
273       axisIdx = 1;
274     } else if (axis == "x") {
275       axisIdx = -1; // simply a placeholder for below.
276     } else {
277       throw "Unknown axis " + axis;
278     }
279     axisString = axis;
280   }
281
282   var userAxis = (axisIdx == -1) ? this.xAxis_ : this.yAxes_[axisIdx];
283
284   // Search the user-specified axis option first.
285   if (userAxis) { // This condition could be removed if we always set up this.yAxes_ for y2.
286     var axisOptions = userAxis.options;
287     if (axisOptions.hasOwnProperty(name)) {
288       return axisOptions[name];
289     }
290   }
291
292   // User-specified global options second.
293   var result = this.getGlobalUser_(name);
294   if (result !== null) {
295     return result;
296   }
297
298   // Default axis options third.
299   var defaultAxisOptions = Dygraph.DEFAULT_ATTRS.axes[axisString];
300   if (defaultAxisOptions.hasOwnProperty(name)) {
301     return defaultAxisOptions[name];
302   }
303
304   // Default global options last.
305   return this.getGlobalDefault_(name);
306 };
307
308 /**
309  * Get a value for a specific series. If there is no specific value for the series,
310  * the value for the axis is returned (and afterwards, the global value.)
311  *
312  * @param {string} name the name of the option.
313  * @param {string} series the series to search.
314  */
315 DygraphOptions.prototype.getForSeries = function(name, series) {
316   // Honors indexes as series.
317   if (series === this.dygraph_.getHighlightSeries()) {
318     if (this.highlightSeries_.hasOwnProperty(name)) {
319       return this.highlightSeries_[name];
320     }
321   }
322
323   if (!this.series_.hasOwnProperty(series)) {
324     throw "Unknown series: " + series;
325   }
326
327   var seriesObj = this.series_[series];
328   var seriesOptions = seriesObj["options"];
329   if (seriesOptions.hasOwnProperty(name)) {
330     return seriesOptions[name];
331   }
332
333   return this.getForAxis(name, seriesObj["yAxis"]);
334 };
335
336 /**
337  * Returns the number of y-axes on the chart.
338  * @return {number} the number of axes.
339  */
340 DygraphOptions.prototype.numAxes = function() {
341   return this.yAxes_.length;
342 };
343
344 /**
345  * Return the y-axis for a given series, specified by name.
346  */
347 DygraphOptions.prototype.axisForSeries = function(series) {
348   return this.series_[series].yAxis;
349 };
350
351 /**
352  * Returns the options for the specified axis.
353  */
354 // TODO(konigsberg): this is y-axis specific. Support the x axis.
355 DygraphOptions.prototype.axisOptions = function(yAxis) {
356   return this.yAxes_[yAxis].options;
357 };
358
359 /**
360  * Return the series associated with an axis.
361  */
362 DygraphOptions.prototype.seriesForAxis = function(yAxis) {
363   return this.yAxes_[yAxis].series;
364 };
365
366 /**
367  * Return the list of all series, in their columnar order.
368  */
369 DygraphOptions.prototype.seriesNames = function() {
370   return this.labels_;
371 };
372
373 /* Are we using this? */
374 /**
375  * Return the index of the specified series.
376  * @param {string} series the series name.
377  */
378 DygraphOptions.prototype.indexOfSeries = function(series) {
379   return this.series_[series].idx;
380 };
381
382 return DygraphOptions;
383
384 })();