Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*! @license Copyright 2014 Dan Vanderkam (danvdk@gmail.com) MIT-licensed (http://opensource.org/licenses/MIT) */
// Console-polyfill. MIT license.
// https://github.com/paulmillr/console-polyfill
// Make it safe to do console.log() always.
(function(con) {
'use strict';
var prop, method;
var empty = {};
var dummy = function() {};
var properties = 'memory'.split(',');
var methods = ('assert,clear,count,debug,dir,dirxml,error,exception,group,' +
'groupCollapsed,groupEnd,info,log,markTimeline,profile,profiles,profileEnd,' +
'show,table,time,timeEnd,timeline,timelineEnd,timeStamp,trace,warn').split(',');
while (prop = properties.pop()) con[prop] = con[prop] || empty;
while (method = methods.pop()) con[method] = con[method] || dummy;
})(this.console = this.console || {}); // Using `this` for web workers.
/**
* @license
* Copyright 2012 Dan Vanderkam (danvdk@gmail.com)
* MIT-licensed (http://opensource.org/licenses/MIT)
*/
(function() {
'use strict';
/**
* @fileoverview Adds support for dashed lines to the HTML5 canvas.
*
* Usage:
* var ctx = canvas.getContext("2d");
* ctx.installPattern([10, 5]) // draw 10 pixels, skip 5 pixels, repeat.
* ctx.beginPath();
* ctx.moveTo(100, 100); // start the first line segment.
* ctx.lineTo(150, 200);
* ctx.lineTo(200, 100);
* ctx.moveTo(300, 150); // start a second, unconnected line
* ctx.lineTo(400, 250);
* ...
* ctx.stroke(); // draw the dashed line.
* ctx.uninstallPattern();
*
* This is designed to leave the canvas untouched when it's not used.
* If you never install a pattern, or call uninstallPattern(), then the canvas
* will be exactly as it would have if you'd never used this library. The only
* difference from the standard canvas will be the "installPattern" method of
* the drawing context.
*/
/**
* Change the stroking style of the canvas drawing context from a solid line to
* a pattern (e.g. dashes, dash-dot-dash, etc.)
*
* Once you've installed the pattern, you can draw with it by using the
* beginPath(), moveTo(), lineTo() and stroke() method calls. Note that some
* more advanced methods (e.g. quadraticCurveTo() and bezierCurveTo()) are not
* supported. See file overview for a working example.
*
* Side effects of calling this method include adding an "isPatternInstalled"
* property and "uninstallPattern" method to this particular canvas context.
* You must call uninstallPattern() before calling installPattern() again.
*
* @param {Array.<number>} pattern A description of the stroke pattern. Even
* indices indicate a draw and odd indices indicate a gap (in pixels). The
* array should have a even length as any odd lengthed array could be expressed
* as a smaller even length array.
*/
CanvasRenderingContext2D.prototype.installPattern = function(pattern) {
if (typeof(this.isPatternInstalled) !== 'undefined') {
throw "Must un-install old line pattern before installing a new one.";
}
this.isPatternInstalled = true;
var dashedLineToHistory = [0, 0];
// list of connected line segements:
// [ [x1, y1], ..., [xn, yn] ], [ [x1, y1], ..., [xn, yn] ]
var segments = [];
// Stash away copies of the unmodified line-drawing functions.
var realBeginPath = this.beginPath;
var realLineTo = this.lineTo;
var realMoveTo = this.moveTo;
var realStroke = this.stroke;
/** @type {function()|undefined} */
this.uninstallPattern = function() {
this.beginPath = realBeginPath;
this.lineTo = realLineTo;
this.moveTo = realMoveTo;
this.stroke = realStroke;
this.uninstallPattern = undefined;
this.isPatternInstalled = undefined;
};
// Keep our own copies of the line segments as they're drawn.
this.beginPath = function() {
segments = [];
realBeginPath.call(this);
};
this.moveTo = function(x, y) {
segments.push([[x, y]]);
realMoveTo.call(this, x, y);
};
this.lineTo = function(x, y) {
var last = segments[segments.length - 1];
last.push([x, y]);
};
this.stroke = function() {
if (segments.length === 0) {
// Maybe the user is drawing something other than a line.
// TODO(danvk): test this case.
realStroke.call(this);
return;
}
for (var i = 0; i < segments.length; i++) {
var seg = segments[i];
var x1 = seg[0][0], y1 = seg[0][1];
for (var j = 1; j < seg.length; j++) {
// Draw a dashed line from (x1, y1) - (x2, y2)
var x2 = seg[j][0], y2 = seg[j][1];
this.save();
// Calculate transformation parameters
var dx = (x2-x1);
var dy = (y2-y1);
var len = Math.sqrt(dx*dx + dy*dy);
var rot = Math.atan2(dy, dx);
// Set transformation
this.translate(x1, y1);
realMoveTo.call(this, 0, 0);
this.rotate(rot);
// Set last pattern index we used for this pattern.
var patternIndex = dashedLineToHistory[0];
var x = 0;
while (len > x) {
// Get the length of the pattern segment we are dealing with.
var segment = pattern[patternIndex];
// If our last draw didn't complete the pattern segment all the way
// we will try to finish it. Otherwise we will try to do the whole
// segment.
if (dashedLineToHistory[1]) {
x += dashedLineToHistory[1];
} else {
x += segment;
}
if (x > len) {
// We were unable to complete this pattern index all the way, keep
// where we are the history so our next draw continues where we
// left off in the pattern.
dashedLineToHistory = [patternIndex, x-len];
x = len;
} else {
// We completed this patternIndex, we put in the history that we
// are on the beginning of the next segment.
dashedLineToHistory = [(patternIndex+1)%pattern.length, 0];
}
// We do a line on a even pattern index and just move on a odd
// pattern index. The move is the empty space in the dash.
if (patternIndex % 2 === 0) {
realLineTo.call(this, x, 0);
} else {
realMoveTo.call(this, x, 0);
}
// If we are not done, next loop process the next pattern segment, or
// the first segment again if we are at the end of the pattern.
patternIndex = (patternIndex+1) % pattern.length;
}
this.restore();
x1 = x2;
y1 = y2;
}
}
realStroke.call(this);
segments = [];
};
};
/**
* Removes the previously-installed pattern.
* You must call installPattern() before calling this. You can install at most
* one pattern at a time--there is no pattern stack.
*/
CanvasRenderingContext2D.prototype.uninstallPattern = function() {
// This will be replaced by a non-error version when a pattern is installed.
throw "Must install a line pattern before uninstalling it.";
};
})();
/**
* @license
* Copyright 2011 Dan Vanderkam (danvdk@gmail.com)
* MIT-licensed (http://opensource.org/licenses/MIT)
*/
/**
* @fileoverview DygraphOptions is responsible for parsing and returning information about options.
*
* Still tightly coupled to Dygraphs, we could remove some of that, you know.
*/
var DygraphOptions = (function() {
/*jshint strict:false */
// For "production" code, this gets set to false by uglifyjs.
// Need to define it outside of "use strict", hence the nested IIFEs.
if (typeof(DEBUG) === 'undefined') DEBUG=true;
return (function() {
// TODO: remove this jshint directive & fix the warnings.
/*jshint sub:true */
/*global Dygraph:false */
"use strict";
/*
* Interesting member variables: (REMOVING THIS LIST AS I CLOSURIZE)
* global_ - global attributes (common among all graphs, AIUI)
* user - attributes set by the user
* series_ - { seriesName -> { idx, yAxis, options }}
*/
/**
* This parses attributes into an object that can be easily queried.
*
* It doesn't necessarily mean that all options are available, specifically
* if labels are not yet available, since those drive details of the per-series
* and per-axis options.
*
* @param {Dygraph} dygraph The chart to which these options belong.
* @constructor
*/
var DygraphOptions = function(dygraph) {
/**
* The dygraph.
* @type {!Dygraph}
*/
this.dygraph_ = dygraph;
/**
* Array of axis index to { series : [ series names ] , options : { axis-specific options. }
* @type {Array.<{series : Array.<string>, options : Object}>} @private
*/
this.yAxes_ = [];
/**
* Contains x-axis specific options, which are stored in the options key.
* This matches the yAxes_ object structure (by being a dictionary with an
* options element) allowing for shared code.
* @type {options: Object} @private
*/
this.xAxis_ = {};
this.series_ = {};
// Once these two objects are initialized, you can call get();
this.global_ = this.dygraph_.attrs_;
this.user_ = this.dygraph_.user_attrs_ || {};
/**
* A list of series in columnar order.
* @type {Array.<string>}
*/
this.labels_ = [];
this.highlightSeries_ = this.get("highlightSeriesOpts") || {};
this.reparseSeries();
};
/**
* Not optimal, but does the trick when you're only using two axes.
* If we move to more axes, this can just become a function.
*
* @type {Object.<number>}
* @private
*/
DygraphOptions.AXIS_STRING_MAPPINGS_ = {
'y' : 0,
'Y' : 0,
'y1' : 0,
'Y1' : 0,
'y2' : 1,
'Y2' : 1
};
/**
* @param {string|number} axis
* @private
*/
DygraphOptions.axisToIndex_ = function(axis) {
if (typeof(axis) == "string") {
if (DygraphOptions.AXIS_STRING_MAPPINGS_.hasOwnProperty(axis)) {
return DygraphOptions.AXIS_STRING_MAPPINGS_[axis];
}
throw "Unknown axis : " + axis;
}
if (typeof(axis) == "number") {
if (axis === 0 || axis === 1) {
return axis;
}
throw "Dygraphs only supports two y-axes, indexed from 0-1.";
}
if (axis) {
throw "Unknown axis : " + axis;
}
// No axis specification means axis 0.
return 0;
};
/**
* Reparses options that are all related to series. This typically occurs when
* options are either updated, or source data has been made available.
*
* TODO(konigsberg): The method name is kind of weak; fix.
*/
DygraphOptions.prototype.reparseSeries = function() {
var labels = this.get("labels");
if (!labels) {
return; // -- can't do more for now, will parse after getting the labels.
}
this.labels_ = labels.slice(1);
this.yAxes_ = [ { series : [], options : {}} ]; // Always one axis at least.
this.xAxis_ = { options : {} };
this.series_ = {};
// Traditionally, per-series options were specified right up there with the options. For instance
// {
// labels: [ "X", "foo", "bar" ],
// pointSize: 3,
// foo : {}, // options for foo
// bar : {} // options for bar
// }
//
// Moving forward, series really should be specified in the series element, separating them.
// like so:
//
// {
// labels: [ "X", "foo", "bar" ],
// pointSize: 3,
// series : {
// foo : {}, // options for foo
// bar : {} // options for bar
// }
// }
//
// So, if series is found, it's expected to contain per-series data, otherwise we fall
// back.
var oldStyleSeries = !this.user_["series"];
if (oldStyleSeries) {
var axisId = 0; // 0-offset; there's always one.
// Go through once, add all the series, and for those with {} axis options, add a new axis.
for (var idx = 0; idx < this.labels_.length; idx++) {
var seriesName = this.labels_[idx];
var optionsForSeries = this.user_[seriesName] || {};
var yAxis = 0;
var axis = optionsForSeries["axis"];
if (typeof(axis) == 'object') {
yAxis = ++axisId;
this.yAxes_[yAxis] = { series : [ seriesName ], options : axis };
}
// Associate series without axis options with axis 0.
if (!axis) { // undefined
this.yAxes_[0].series.push(seriesName);
}
this.series_[seriesName] = { idx: idx, yAxis: yAxis, options : optionsForSeries };
}
// Go through one more time and assign series to an axis defined by another
// series, e.g. { 'Y1: { axis: {} }, 'Y2': { axis: 'Y1' } }
for (var idx = 0; idx < this.labels_.length; idx++) {
var seriesName = this.labels_[idx];
var optionsForSeries = this.series_[seriesName]["options"];
var axis = optionsForSeries["axis"];
if (typeof(axis) == 'string') {
if (!this.series_.hasOwnProperty(axis)) {
console.error("Series " + seriesName + " wants to share a y-axis with " +
"series " + axis + ", which does not define its own axis.");
return;
}
var yAxis = this.series_[axis].yAxis;
this.series_[seriesName].yAxis = yAxis;
this.yAxes_[yAxis].series.push(seriesName);
}
}
} else {
for (var idx = 0; idx < this.labels_.length; idx++) {
var seriesName = this.labels_[idx];
var optionsForSeries = this.user_.series[seriesName] || {};
var yAxis = DygraphOptions.axisToIndex_(optionsForSeries["axis"]);
this.series_[seriesName] = {
idx: idx,
yAxis: yAxis,
options : optionsForSeries };
if (!this.yAxes_[yAxis]) {
this.yAxes_[yAxis] = { series : [ seriesName ], options : {} };
} else {
this.yAxes_[yAxis].series.push(seriesName);
}
}
}
var axis_opts = this.user_["axes"] || {};
Dygraph.update(this.yAxes_[0].options, axis_opts["y"] || {});
if (this.yAxes_.length > 1) {
Dygraph.update(this.yAxes_[1].options, axis_opts["y2"] || {});
}
Dygraph.update(this.xAxis_.options, axis_opts["x"] || {});
if (DEBUG) this.validateOptions_();
};
/**
* Get a global value.
*
* @param {string} name the name of the option.
*/
DygraphOptions.prototype.get = function(name) {
var result = this.getGlobalUser_(name);
if (result !== null) {
return result;
}
return this.getGlobalDefault_(name);
};
DygraphOptions.prototype.getGlobalUser_ = function(name) {
if (this.user_.hasOwnProperty(name)) {
return this.user_[name];
}
return null;
};
DygraphOptions.prototype.getGlobalDefault_ = function(name) {
if (this.global_.hasOwnProperty(name)) {
return this.global_[name];
}
if (Dygraph.DEFAULT_ATTRS.hasOwnProperty(name)) {
return Dygraph.DEFAULT_ATTRS[name];
}
return null;
};
/**
* Get a value for a specific axis. If there is no specific value for the axis,
* the global value is returned.
*
* @param {string} name the name of the option.
* @param {string|number} axis the axis to search. Can be the string representation
* ("y", "y2") or the axis number (0, 1).
*/
DygraphOptions.prototype.getForAxis = function(name, axis) {
var axisIdx;
var axisString;
// Since axis can be a number or a string, straighten everything out here.
if (typeof(axis) == 'number') {
axisIdx = axis;
axisString = axisIdx === 0 ? "y" : "y2";
} else {
if (axis == "y1") { axis = "y"; } // Standardize on 'y'. Is this bad? I think so.
if (axis == "y") {
axisIdx = 0;
} else if (axis == "y2") {
axisIdx = 1;
} else if (axis == "x") {
axisIdx = -1; // simply a placeholder for below.
} else {
throw "Unknown axis " + axis;
}
axisString = axis;
}
var userAxis = (axisIdx == -1) ? this.xAxis_ : this.yAxes_[axisIdx];
// Search the user-specified axis option first.
if (userAxis) { // This condition could be removed if we always set up this.yAxes_ for y2.
var axisOptions = userAxis.options;
if (axisOptions.hasOwnProperty(name)) {
return axisOptions[name];
}
}
// User-specified global options second.
// But, hack, ignore globally-specified 'logscale' for 'x' axis declaration.
if (!(axis === 'x' && name === 'logscale')) {
var result = this.getGlobalUser_(name);
if (result !== null) {
return result;
}
}
// Default axis options third.
var defaultAxisOptions = Dygraph.DEFAULT_ATTRS.axes[axisString];
if (defaultAxisOptions.hasOwnProperty(name)) {
return defaultAxisOptions[name];
}
// Default global options last.
return this.getGlobalDefault_(name);
};
/**
* Get a value for a specific series. If there is no specific value for the series,
* the value for the axis is returned (and afterwards, the global value.)
*
* @param {string} name the name of the option.
* @param {string} series the series to search.
*/
DygraphOptions.prototype.getForSeries = function(name, series) {
// Honors indexes as series.
if (series === this.dygraph_.getHighlightSeries()) {
if (this.highlightSeries_.hasOwnProperty(name)) {
return this.highlightSeries_[name];
}
}
if (!this.series_.hasOwnProperty(series)) {
throw "Unknown series: " + series;
}
var seriesObj = this.series_[series];
var seriesOptions = seriesObj["options"];
if (seriesOptions.hasOwnProperty(name)) {
return seriesOptions[name];
}
return this.getForAxis(name, seriesObj["yAxis"]);
};
/**
* Returns the number of y-axes on the chart.
* @return {number} the number of axes.
*/
DygraphOptions.prototype.numAxes = function() {
return this.yAxes_.length;
};
/**
* Return the y-axis for a given series, specified by name.
*/
DygraphOptions.prototype.axisForSeries = function(series) {
return this.series_[series].yAxis;
};
/**
* Returns the options for the specified axis.
*/
// TODO(konigsberg): this is y-axis specific. Support the x axis.
DygraphOptions.prototype.axisOptions = function(yAxis) {
return this.yAxes_[yAxis].options;
};
/**
* Return the series associated with an axis.
*/
DygraphOptions.prototype.seriesForAxis = function(yAxis) {
return this.yAxes_[yAxis].series;
};
/**
* Return the list of all series, in their columnar order.
*/
DygraphOptions.prototype.seriesNames = function() {
return this.labels_;
};
if (DEBUG) {
/**
* Validate all options.
* This requires Dygraph.OPTIONS_REFERENCE, which is only available in debug builds.
* @private
*/
DygraphOptions.prototype.validateOptions_ = function() {
if (typeof Dygraph.OPTIONS_REFERENCE === 'undefined') {
throw 'Called validateOptions_ in prod build.';
}
var that = this;
var validateOption = function(optionName) {
if (!Dygraph.OPTIONS_REFERENCE[optionName]) {
that.warnInvalidOption_(optionName);
}
};
var optionsDicts = [this.xAxis_.options,
this.yAxes_[0].options,
this.yAxes_[1] && this.yAxes_[1].options,
this.global_,
this.user_,
this.highlightSeries_];
var names = this.seriesNames();
for (var i = 0; i < names.length; i++) {
var name = names[i];
if (this.series_.hasOwnProperty(name)) {
optionsDicts.push(this.series_[name].options);
}
}
for (var i = 0; i < optionsDicts.length; i++) {
var dict = optionsDicts[i];
if (!dict) continue;
for (var optionName in dict) {
if (dict.hasOwnProperty(optionName)) {
validateOption(optionName);
}
}
}
};
var WARNINGS = {}; // Only show any particular warning once.
/**
* Logs a warning about invalid options.
* TODO: make this throw for testing
* @private
*/
DygraphOptions.prototype.warnInvalidOption_ = function(optionName) {
if (!WARNINGS[optionName]) {
WARNINGS[optionName] = true;
var isSeries = (this.labels_.indexOf(optionName) >= 0);
if (isSeries) {
console.warn('Use new-style per-series options (saw ' + optionName + ' as top-level options key). See http://bit.ly/1tceaJs');
} else {
console.warn('Unknown option ' + optionName + ' (full list of options at dygraphs.com/options.html');
throw "invalid option " + optionName;
}
}
};
// Reset list of previously-shown warnings. Used for testing.
DygraphOptions.resetWarnings_ = function() {
WARNINGS = {};
};
}
return DygraphOptions;
})();
})();
/**
* @license
* Copyright 2011 Dan Vanderkam (danvdk@gmail.com)
* MIT-licensed (http://opensource.org/licenses/MIT)
*/
/**
* @fileoverview Based on PlotKitLayout, but modified to meet the needs of
* dygraphs.
*/
var DygraphLayout = (function() {
/*global Dygraph:false */
"use strict";
/**
* Creates a new DygraphLayout object.
*
* This class contains all the data to be charted.
* It uses data coordinates, but also records the chart range (in data
* coordinates) and hence is able to calculate percentage positions ('In this
* view, Point A lies 25% down the x-axis.')
*
* Two things that it does not do are:
* 1. Record pixel coordinates for anything.
* 2. (oddly) determine anything about the layout of chart elements.
*
* The naming is a vestige of Dygraph's original PlotKit roots.
*
* @constructor
*/
var DygraphLayout = function(dygraph) {
this.dygraph_ = dygraph;
/**
* Array of points for each series.
*
* [series index][row index in series] = |Point| structure,
* where series index refers to visible series only, and the
* point index is for the reduced set of points for the current
* zoom region (including one point just outside the window).
* All points in the same row index share the same X value.
*
* @type {Array.<Array.<Dygraph.PointType>>}
*/
this.points = [];
this.setNames = [];
this.annotations = [];
this.yAxes_ = null;
// TODO(danvk): it's odd that xTicks_ and yTicks_ are inputs, but xticks and
// yticks are outputs. Clean this up.
this.xTicks_ = null;
this.yTicks_ = null;
};
/**
* Add points for a single series.
*
* @param {string} setname Name of the series.
* @param {Array.<Dygraph.PointType>} set_xy Points for the series.
*/
DygraphLayout.prototype.addDataset = function(setname, set_xy) {
this.points.push(set_xy);
this.setNames.push(setname);
};
/**
* Returns the box which the chart should be drawn in. This is the canvas's
* box, less space needed for the axis and chart labels.
*
* @return {{x: number, y: number, w: number, h: number}}
*/
DygraphLayout.prototype.getPlotArea = function() {
return this.area_;
};
// Compute the box which the chart should be drawn in. This is the canvas's
// box, less space needed for axis, chart labels, and other plug-ins.
// NOTE: This should only be called by Dygraph.predraw_().
DygraphLayout.prototype.computePlotArea = function() {
var area = {
// TODO(danvk): per-axis setting.
x: 0,
y: 0
};
area.w = this.dygraph_.width_ - area.x - this.dygraph_.getOption('rightGap');
area.h = this.dygraph_.height_;
// Let plugins reserve space.
var e = {
chart_div: this.dygraph_.graphDiv,
reserveSpaceLeft: function(px) {
var r = {
x: area.x,
y: area.y,
w: px,
h: area.h
};
area.x += px;
area.w -= px;
return r;
},
reserveSpaceRight: function(px) {
var r = {
x: area.x + area.w - px,
y: area.y,
w: px,
h: area.h
};
area.w -= px;
return r;
},
reserveSpaceTop: function(px) {
var r = {
x: area.x,
y: area.y,
w: area.w,
h: px
};
area.y += px;
area.h -= px;
return r;
},
reserveSpaceBottom: function(px) {
var r = {
x: area.x,
y: area.y + area.h - px,
w: area.w,
h: px
};
area.h -= px;
return r;
},
chartRect: function() {
return {x:area.x, y:area.y, w:area.w, h:area.h};
}
};
this.dygraph_.cascadeEvents_('layout', e);
this.area_ = area;
};
DygraphLayout.prototype.setAnnotations = function(ann) {
// The Dygraph object's annotations aren't parsed. We parse them here and
// save a copy. If there is no parser, then the user must be using raw format.
this.annotations = [];
var parse = this.dygraph_.getOption('xValueParser') || function(x) { return x; };
for (var i = 0; i < ann.length; i++) {
var a = {};
if (!ann[i].xval && ann[i].x === undefined) {
console.error("Annotations must have an 'x' property");
return;
}
if (ann[i].icon &&
!(ann[i].hasOwnProperty('width') &&
ann[i].hasOwnProperty('height'))) {
console.error("Must set width and height when setting " +
"annotation.icon property");
return;
}
Dygraph.update(a, ann[i]);
if (!a.xval) a.xval = parse(a.x);
this.annotations.push(a);
}
};
DygraphLayout.prototype.setXTicks = function(xTicks) {
this.xTicks_ = xTicks;
};
// TODO(danvk): add this to the Dygraph object's API or move it into Layout.
DygraphLayout.prototype.setYAxes = function (yAxes) {
this.yAxes_ = yAxes;
};
DygraphLayout.prototype.evaluate = function() {
this._xAxis = {};
this._evaluateLimits();
this._evaluateLineCharts();
this._evaluateLineTicks();
this._evaluateAnnotations();
};
DygraphLayout.prototype._evaluateLimits = function() {
var xlimits = this.dygraph_.xAxisRange();
this._xAxis.minval = xlimits[0];
this._xAxis.maxval = xlimits[1];
var xrange = xlimits[1] - xlimits[0];
this._xAxis.scale = (xrange !== 0 ? 1 / xrange : 1.0);
if (this.dygraph_.getOptionForAxis("logscale", 'x')) {
this._xAxis.xlogrange = Dygraph.log10(this._xAxis.maxval) - Dygraph.log10(this._xAxis.minval);
this._xAxis.xlogscale = (this._xAxis.xlogrange !== 0 ? 1.0 / this._xAxis.xlogrange : 1.0);
}
for (var i = 0; i < this.yAxes_.length; i++) {
var axis = this.yAxes_[i];
axis.minyval = axis.computedValueRange[0];
axis.maxyval = axis.computedValueRange[1];
axis.yrange = axis.maxyval - axis.minyval;
axis.yscale = (axis.yrange !== 0 ? 1.0 / axis.yrange : 1.0);
if (this.dygraph_.getOption("logscale")) {
axis.ylogrange = Dygraph.log10(axis.maxyval) - Dygraph.log10(axis.minyval);
axis.ylogscale = (axis.ylogrange !== 0 ? 1.0 / axis.ylogrange : 1.0);
if (!isFinite(axis.ylogrange) || isNaN(axis.ylogrange)) {
console.error('axis ' + i + ' of graph at ' + axis.g +
' can\'t be displayed in log scale for range [' +
axis.minyval + ' - ' + axis.maxyval + ']');
}
}
}
};
DygraphLayout.calcXNormal_ = function(value, xAxis, logscale) {
if (logscale) {
return ((Dygraph.log10(value) - Dygraph.log10(xAxis.minval)) * xAxis.xlogscale);
} else {
return (value - xAxis.minval) * xAxis.scale;
}
};
/**
* @param {DygraphAxisType} axis
* @param {number} value
* @param {boolean} logscale
* @return {number}
*/
DygraphLayout.calcYNormal_ = function(axis, value, logscale) {
if (logscale) {
var x = 1.0 - ((Dygraph.log10(value) - Dygraph.log10(axis.minyval)) * axis.ylogscale);
return isFinite(x) ? x : NaN; // shim for v8 issue; see pull request 276
} else {
return 1.0 - ((value - axis.minyval) * axis.yscale);
}
};
DygraphLayout.prototype._evaluateLineCharts = function() {
var isStacked = this.dygraph_.getOption("stackedGraph");
var isLogscaleForX = this.dygraph_.getOptionForAxis("logscale", 'x');
for (var setIdx = 0; setIdx < this.points.length; setIdx++) {
var points = this.points[setIdx];
var setName = this.setNames[setIdx];
var connectSeparated = this.dygraph_.getOption('connectSeparatedPoints', setName);
var axis = this.dygraph_.axisPropertiesForSeries(setName);
// TODO (konigsberg): use optionsForAxis instead.
var logscale = this.dygraph_.attributes_.getForSeries("logscale", setName);
for (var j = 0; j < points.length; j++) {
var point = points[j];
// Range from 0-1 where 0 represents left and 1 represents right.
point.x = DygraphLayout.calcXNormal_(point.xval, this._xAxis, isLogscaleForX);
// Range from 0-1 where 0 represents top and 1 represents bottom
var yval = point.yval;
if (isStacked) {
point.y_stacked = DygraphLayout.calcYNormal_(
axis, point.yval_stacked, logscale);
if (yval !== null && !isNaN(yval)) {
yval = point.yval_stacked;
}
}
if (yval === null) {
yval = NaN;
if (!connectSeparated) {
point.yval = NaN;
}
}
point.y = DygraphLayout.calcYNormal_(axis, yval, logscale);
}
this.dygraph_.dataHandler_.onLineEvaluated(points, axis, logscale);
}
};
DygraphLayout.prototype._evaluateLineTicks = function() {
var i, tick, label, pos;
this.xticks = [];
for (i = 0; i < this.xTicks_.length; i++) {
tick = this.xTicks_[i];
label = tick.label;
pos = this.dygraph_.toPercentXCoord(tick.v);
if ((pos >= 0.0) && (pos < 1.0)) {
this.xticks.push([pos, label]);
}
}
this.yticks = [];
for (i = 0; i < this.yAxes_.length; i++ ) {
var axis = this.yAxes_[i];
for (var j = 0; j < axis.ticks.length; j++) {
tick = axis.ticks[j];
label = tick.label;
pos = this.dygraph_.toPercentYCoord(tick.v, i);
if ((pos > 0.0) && (pos <= 1.0)) {
this.yticks.push([i, pos, label]);
}
}
}
};
DygraphLayout.prototype._evaluateAnnotations = function() {
// Add the annotations to the point to which they belong.
// Make a map from (setName, xval) to annotation for quick lookups.
var i;
var annotations = {};
for (i = 0; i < this.annotations.length; i++) {
var a = this.annotations[i];
annotations[a.xval + "," + a.series] = a;
}
this.annotated_points = [];
// Exit the function early if there are no annotations.
if (!this.annotations || !this.annotations.length) {
return;
}
// TODO(antrob): loop through annotations not points.
for (var setIdx = 0; setIdx < this.points.length; setIdx++) {
var points = this.points[setIdx];
for (i = 0; i < points.length; i++) {
var p = points[i];
var k = p.xval + "," + p.name;
if (k in annotations) {
p.annotation = annotations[k];
this.annotated_points.push(p);
}
}
}
};
/**
* Convenience function to remove all the data sets from a graph
*/
DygraphLayout.prototype.removeAllDatasets = function() {
delete this.points;
delete this.setNames;
delete this.setPointsLengths;
delete this.setPointsOffsets;
this.points = [];
this.setNames = [];
this.setPointsLengths = [];
this.setPointsOffsets = [];