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 | "use strict";
|
66 |
|
67 | import * as utils from './dygraph-utils';
|
68 |
|
69 |
|
70 | var TickList = undefined;
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 | var Ticker = undefined;
|
82 |
|
83 |
|
84 | export var numericLinearTicks = function(a, b, pixels, opts, dygraph, vals) {
|
85 | var nonLogscaleOpts = function(opt) {
|
86 | if (opt === 'logscale') return false;
|
87 | return opts(opt);
|
88 | };
|
89 | return numericTicks(a, b, pixels, nonLogscaleOpts, dygraph, vals);
|
90 | };
|
91 |
|
92 |
|
93 | export var numericTicks = function(a, b, pixels, opts, dygraph, vals) {
|
94 | var pixels_per_tick = (opts('pixelsPerLabel'));
|
95 | var ticks = [];
|
96 | var i, j, tickV, nTicks;
|
97 | if (vals) {
|
98 | for (i = 0; i < vals.length; i++) {
|
99 | ticks.push({v: vals[i]});
|
100 | }
|
101 | } else {
|
102 |
|
103 | if (opts("logscale")) {
|
104 | nTicks = Math.floor(pixels / pixels_per_tick);
|
105 | var minIdx = utils.binarySearch(a, PREFERRED_LOG_TICK_VALUES, 1);
|
106 | var maxIdx = utils.binarySearch(b, PREFERRED_LOG_TICK_VALUES, -1);
|
107 | if (minIdx == -1) {
|
108 | minIdx = 0;
|
109 | }
|
110 | if (maxIdx == -1) {
|
111 | maxIdx = PREFERRED_LOG_TICK_VALUES.length - 1;
|
112 | }
|
113 |
|
114 |
|
115 | var lastDisplayed = null;
|
116 | if (maxIdx - minIdx >= nTicks / 4) {
|
117 | for (var idx = maxIdx; idx >= minIdx; idx--) {
|
118 | var tickValue = PREFERRED_LOG_TICK_VALUES[idx];
|
119 | var pixel_coord = Math.log(tickValue / a) / Math.log(b / a) * pixels;
|
120 | var tick = { v: tickValue };
|
121 | if (lastDisplayed === null) {
|
122 | lastDisplayed = {
|
123 | tickValue : tickValue,
|
124 | pixel_coord : pixel_coord
|
125 | };
|
126 | } else {
|
127 | if (Math.abs(pixel_coord - lastDisplayed.pixel_coord) >= pixels_per_tick) {
|
128 | lastDisplayed = {
|
129 | tickValue : tickValue,
|
130 | pixel_coord : pixel_coord
|
131 | };
|
132 | } else {
|
133 | tick.label = "";
|
134 | }
|
135 | }
|
136 | ticks.push(tick);
|
137 | }
|
138 |
|
139 | ticks.reverse();
|
140 | }
|
141 | }
|
142 |
|
143 |
|
144 | if (ticks.length === 0) {
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 | var kmg2 = opts("labelsKMG2");
|
151 | var mults, base;
|
152 | if (kmg2) {
|
153 | mults = [1, 2, 4, 8, 16, 32, 64, 128, 256];
|
154 | base = 16;
|
155 | } else {
|
156 | mults = [1, 2, 5, 10, 20, 50, 100];
|
157 | base = 10;
|
158 | }
|
159 |
|
160 |
|
161 |
|
162 | var max_ticks = Math.ceil(pixels / pixels_per_tick);
|
163 |
|
164 |
|
165 |
|
166 | var units_per_tick = Math.abs(b - a) / max_ticks;
|
167 |
|
168 |
|
169 |
|
170 |
|
171 | var base_power = Math.floor(Math.log(units_per_tick) / Math.log(base));
|
172 | var base_scale = Math.pow(base, base_power);
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 | var scale, low_val, high_val, spacing;
|
179 | for (j = 0; j < mults.length; j++) {
|
180 | scale = base_scale * mults[j];
|
181 | low_val = Math.floor(a / scale) * scale;
|
182 | high_val = Math.ceil(b / scale) * scale;
|
183 | nTicks = Math.abs(high_val - low_val) / scale;
|
184 | spacing = pixels / nTicks;
|
185 | if (spacing > pixels_per_tick) break;
|
186 | }
|
187 |
|
188 |
|
189 |
|
190 | if (low_val > high_val) scale *= -1;
|
191 | for (i = 0; i <= nTicks; i++) {
|
192 | tickV = low_val + i * scale;
|
193 | ticks.push( {v: tickV} );
|
194 | }
|
195 | }
|
196 | }
|
197 |
|
198 | var formatter = (opts('axisLabelFormatter'));
|
199 |
|
200 |
|
201 | for (i = 0; i < ticks.length; i++) {
|
202 | if (ticks[i].label !== undefined) continue;
|
203 |
|
204 | ticks[i].label = formatter.call(dygraph, ticks[i].v, 0, opts, dygraph);
|
205 | }
|
206 |
|
207 | return ticks;
|
208 | };
|
209 |
|
210 |
|
211 | export var dateTicker = function(a, b, pixels, opts, dygraph, vals) {
|
212 | var chosen = pickDateTickGranularity(a, b, pixels, opts);
|
213 |
|
214 | if (chosen >= 0) {
|
215 | return getDateAxis(a, b, chosen, opts, dygraph);
|
216 | } else {
|
217 |
|
218 | return [];
|
219 | }
|
220 | };
|
221 |
|
222 |
|
223 | export var Granularity = {
|
224 | MILLISECONDLY: 0,
|
225 | TWO_MILLISECONDLY: 1,
|
226 | FIVE_MILLISECONDLY: 2,
|
227 | TEN_MILLISECONDLY: 3,
|
228 | FIFTY_MILLISECONDLY: 4,
|
229 | HUNDRED_MILLISECONDLY: 5,
|
230 | FIVE_HUNDRED_MILLISECONDLY: 6,
|
231 | SECONDLY: 7,
|
232 | TWO_SECONDLY: 8,
|
233 | FIVE_SECONDLY: 9,
|
234 | TEN_SECONDLY: 10,
|
235 | THIRTY_SECONDLY: 11,
|
236 | MINUTELY: 12,
|
237 | TWO_MINUTELY: 13,
|
238 | FIVE_MINUTELY: 14,
|
239 | TEN_MINUTELY: 15,
|
240 | THIRTY_MINUTELY: 16,
|
241 | HOURLY: 17,
|
242 | TWO_HOURLY: 18,
|
243 | SIX_HOURLY: 19,
|
244 | DAILY: 20,
|
245 | TWO_DAILY: 21,
|
246 | WEEKLY: 22,
|
247 | MONTHLY: 23,
|
248 | QUARTERLY: 24,
|
249 | BIANNUAL: 25,
|
250 | ANNUAL: 26,
|
251 | DECADAL: 27,
|
252 | CENTENNIAL: 28,
|
253 | NUM_GRANULARITIES: 29
|
254 | }
|
255 |
|
256 |
|
257 |
|
258 | var DateField = {
|
259 | DATEFIELD_Y: 0,
|
260 | DATEFIELD_M: 1,
|
261 | DATEFIELD_D: 2,
|
262 | DATEFIELD_HH: 3,
|
263 | DATEFIELD_MM: 4,
|
264 | DATEFIELD_SS: 5,
|
265 | DATEFIELD_MS: 6,
|
266 | NUM_DATEFIELDS: 7
|
267 | };
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 | var TICK_PLACEMENT = [];
|
283 | TICK_PLACEMENT[Granularity.MILLISECONDLY] = {datefield: DateField.DATEFIELD_MS, step: 1, spacing: 1};
|
284 | TICK_PLACEMENT[Granularity.TWO_MILLISECONDLY] = {datefield: DateField.DATEFIELD_MS, step: 2, spacing: 2};
|
285 | TICK_PLACEMENT[Granularity.FIVE_MILLISECONDLY] = {datefield: DateField.DATEFIELD_MS, step: 5, spacing: 5};
|
286 | TICK_PLACEMENT[Granularity.TEN_MILLISECONDLY] = {datefield: DateField.DATEFIELD_MS, step: 10, spacing: 10};
|
287 | TICK_PLACEMENT[Granularity.FIFTY_MILLISECONDLY] = {datefield: DateField.DATEFIELD_MS, step: 50, spacing: 50};
|
288 | TICK_PLACEMENT[Granularity.HUNDRED_MILLISECONDLY] = {datefield: DateField.DATEFIELD_MS, step: 100, spacing: 100};
|
289 | TICK_PLACEMENT[Granularity.FIVE_HUNDRED_MILLISECONDLY] = {datefield: DateField.DATEFIELD_MS, step: 500, spacing: 500};
|
290 | TICK_PLACEMENT[Granularity.SECONDLY] = {datefield: DateField.DATEFIELD_SS, step: 1, spacing: 1000 * 1};
|
291 | TICK_PLACEMENT[Granularity.TWO_SECONDLY] = {datefield: DateField.DATEFIELD_SS, step: 2, spacing: 1000 * 2};
|
292 | TICK_PLACEMENT[Granularity.FIVE_SECONDLY] = {datefield: DateField.DATEFIELD_SS, step: 5, spacing: 1000 * 5};
|
293 | TICK_PLACEMENT[Granularity.TEN_SECONDLY] = {datefield: DateField.DATEFIELD_SS, step: 10, spacing: 1000 * 10};
|
294 | TICK_PLACEMENT[Granularity.THIRTY_SECONDLY] = {datefield: DateField.DATEFIELD_SS, step: 30, spacing: 1000 * 30};
|
295 | TICK_PLACEMENT[Granularity.MINUTELY] = {datefield: DateField.DATEFIELD_MM, step: 1, spacing: 1000 * 60};
|
296 | TICK_PLACEMENT[Granularity.TWO_MINUTELY] = {datefield: DateField.DATEFIELD_MM, step: 2, spacing: 1000 * 60 * 2};
|
297 | TICK_PLACEMENT[Granularity.FIVE_MINUTELY] = {datefield: DateField.DATEFIELD_MM, step: 5, spacing: 1000 * 60 * 5};
|
298 | TICK_PLACEMENT[Granularity.TEN_MINUTELY] = {datefield: DateField.DATEFIELD_MM, step: 10, spacing: 1000 * 60 * 10};
|
299 | TICK_PLACEMENT[Granularity.THIRTY_MINUTELY] = {datefield: DateField.DATEFIELD_MM, step: 30, spacing: 1000 * 60 * 30};
|
300 | TICK_PLACEMENT[Granularity.HOURLY] = {datefield: DateField.DATEFIELD_HH, step: 1, spacing: 1000 * 3600};
|
301 | TICK_PLACEMENT[Granularity.TWO_HOURLY] = {datefield: DateField.DATEFIELD_HH, step: 2, spacing: 1000 * 3600 * 2};
|
302 | TICK_PLACEMENT[Granularity.SIX_HOURLY] = {datefield: DateField.DATEFIELD_HH, step: 6, spacing: 1000 * 3600 * 6};
|
303 | TICK_PLACEMENT[Granularity.DAILY] = {datefield: DateField.DATEFIELD_D, step: 1, spacing: 1000 * 86400};
|
304 | TICK_PLACEMENT[Granularity.TWO_DAILY] = {datefield: DateField.DATEFIELD_D, step: 2, spacing: 1000 * 86400 * 2};
|
305 | TICK_PLACEMENT[Granularity.WEEKLY] = {datefield: DateField.DATEFIELD_D, step: 7, spacing: 1000 * 604800};
|
306 | TICK_PLACEMENT[Granularity.MONTHLY] = {datefield: DateField.DATEFIELD_M, step: 1, spacing: 1000 * 7200 * 365.2425};
|
307 | TICK_PLACEMENT[Granularity.QUARTERLY] = {datefield: DateField.DATEFIELD_M, step: 3, spacing: 1000 * 21600 * 365.2425};
|
308 | TICK_PLACEMENT[Granularity.BIANNUAL] = {datefield: DateField.DATEFIELD_M, step: 6, spacing: 1000 * 43200 * 365.2425};
|
309 | TICK_PLACEMENT[Granularity.ANNUAL] = {datefield: DateField.DATEFIELD_Y, step: 1, spacing: 1000 * 86400 * 365.2425};
|
310 | TICK_PLACEMENT[Granularity.DECADAL] = {datefield: DateField.DATEFIELD_Y, step: 10, spacing: 1000 * 864000 * 365.2425};
|
311 | TICK_PLACEMENT[Granularity.CENTENNIAL] = {datefield: DateField.DATEFIELD_Y, step: 100, spacing: 1000 * 8640000 * 365.2425};
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 | var PREFERRED_LOG_TICK_VALUES = (function() {
|
321 | var vals = [];
|
322 | for (var power = -39; power <= 39; power++) {
|
323 | var range = Math.pow(10, power);
|
324 | for (var mult = 1; mult <= 9; mult++) {
|
325 | var val = range * mult;
|
326 | vals.push(val);
|
327 | }
|
328 | }
|
329 | return vals;
|
330 | })();
|
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 |
|
337 |
|
338 |
|
339 |
|
340 |
|
341 |
|
342 | export var pickDateTickGranularity = function(a, b, pixels, opts) {
|
343 | var pixels_per_tick = (opts('pixelsPerLabel'));
|
344 | for (var i = 0; i < Granularity.NUM_GRANULARITIES; i++) {
|
345 | var num_ticks = numDateTicks(a, b, i);
|
346 | if (pixels / num_ticks >= pixels_per_tick) {
|
347 | return i;
|
348 | }
|
349 | }
|
350 | return -1;
|
351 | };
|
352 |
|
353 |
|
354 |
|
355 |
|
356 |
|
357 |
|
358 |
|
359 |
|
360 | var numDateTicks = function(start_time, end_time, granularity) {
|
361 | var spacing = TICK_PLACEMENT[granularity].spacing;
|
362 | return Math.round(1.0 * (end_time - start_time) / spacing);
|
363 | };
|
364 |
|
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 |
|
372 |
|
373 |
|
374 | export var getDateAxis = function(start_time, end_time, granularity, opts, dg) {
|
375 | var formatter = (
|
376 | opts("axisLabelFormatter"));
|
377 | var utc = opts("labelsUTC");
|
378 | var accessors = utc ? utils.DateAccessorsUTC : utils.DateAccessorsLocal;
|
379 |
|
380 | var datefield = TICK_PLACEMENT[granularity].datefield;
|
381 | var step = TICK_PLACEMENT[granularity].step;
|
382 | var spacing = TICK_PLACEMENT[granularity].spacing;
|
383 |
|
384 |
|
385 |
|
386 |
|
387 |
|
388 |
|
389 | var start_date = new Date(start_time);
|
390 | var date_array = [];
|
391 | date_array[DateField.DATEFIELD_Y] = accessors.getFullYear(start_date);
|
392 | date_array[DateField.DATEFIELD_M] = accessors.getMonth(start_date);
|
393 | date_array[DateField.DATEFIELD_D] = accessors.getDate(start_date);
|
394 | date_array[DateField.DATEFIELD_HH] = accessors.getHours(start_date);
|
395 | date_array[DateField.DATEFIELD_MM] = accessors.getMinutes(start_date);
|
396 | date_array[DateField.DATEFIELD_SS] = accessors.getSeconds(start_date);
|
397 | date_array[DateField.DATEFIELD_MS] = accessors.getMilliseconds(start_date);
|
398 |
|
399 | var start_date_offset = date_array[datefield] % step;
|
400 | if (granularity == Granularity.WEEKLY) {
|
401 |
|
402 | start_date_offset = accessors.getDay(start_date);
|
403 | }
|
404 |
|
405 | date_array[datefield] -= start_date_offset;
|
406 | for (var df = datefield + 1; df < DateField.NUM_DATEFIELDS; df++) {
|
407 |
|
408 | date_array[df] = (df === DateField.DATEFIELD_D) ? 1 : 0;
|
409 | }
|
410 |
|
411 |
|
412 |
|
413 |
|
414 |
|
415 |
|
416 |
|
417 |
|
418 |
|
419 |
|
420 |
|
421 |
|
422 |
|
423 |
|
424 | var ticks = [];
|
425 | var tick_date = accessors.makeDate.apply(null, date_array);
|
426 | var tick_time = tick_date.getTime();
|
427 | if (granularity <= Granularity.HOURLY) {
|
428 | if (tick_time < start_time) {
|
429 | tick_time += spacing;
|
430 | tick_date = new Date(tick_time);
|
431 | }
|
432 | while (tick_time <= end_time) {
|
433 | ticks.push({ v: tick_time,
|
434 | label: formatter.call(dg, tick_date, granularity, opts, dg)
|
435 | });
|
436 | tick_time += spacing;
|
437 | tick_date = new Date(tick_time);
|
438 | }
|
439 | } else {
|
440 | if (tick_time < start_time) {
|
441 | date_array[datefield] += step;
|
442 | tick_date = accessors.makeDate.apply(null, date_array);
|
443 | tick_time = tick_date.getTime();
|
444 | }
|
445 | while (tick_time <= end_time) {
|
446 | if (granularity >= Granularity.DAILY ||
|
447 | accessors.getHours(tick_date) % step === 0) {
|
448 | ticks.push({ v: tick_time,
|
449 | label: formatter.call(dg, tick_date, granularity, opts, dg)
|
450 | });
|
451 | }
|
452 | date_array[datefield] += step;
|
453 | tick_date = accessors.makeDate.apply(null, date_array);
|
454 | tick_time = tick_date.getTime();
|
455 | }
|
456 | }
|
457 | return ticks;
|
458 | };
|