Merge pull request #469 from danvk/re-smooth-plotter
[dygraphs.git] / auto_tests / tests / callback.js
1 /**
2 * @fileoverview Test cases for the callbacks.
3 *
4 * @author uemit.seren@gmail.com (Ümit Seren)
5 */
6
7 var CallbackTestCase = TestCase("callback");
8
9 CallbackTestCase.prototype.setUp = function() {
10 document.body.innerHTML = "<div id='graph'></div><div id='selection'></div>";
11 this.xhr = XMLHttpRequest;
12 this.styleSheet = document.createElement("style");
13 this.styleSheet.type = "text/css";
14 document.getElementsByTagName("head")[0].appendChild(this.styleSheet);
15 };
16
17 CallbackTestCase.prototype.tearDown = function() {
18 XMLHttpRequest = this.xhr;
19 };
20
21 var data = "X,a\,b,c\n" +
22 "10,-1,1,2\n" +
23 "11,0,3,1\n" +
24 "12,1,4,2\n" +
25 "13,0,2,3\n";
26
27
28 /**
29 * This tests that when the function idxToRow_ returns the proper row and the onHiglightCallback
30 * is properly called when the first series is hidden (setVisibility = false)
31 *
32 */
33 CallbackTestCase.prototype.testHighlightCallbackIsCalled = function() {
34 var h_row;
35 var h_pts;
36
37 var highlightCallback = function(e, x, pts, row) {
38 assertEquals(g, this);
39 h_row = row;
40 h_pts = pts;
41 };
42
43 var graph = document.getElementById("graph");
44 var g = new Dygraph(graph, data,
45 {
46 width: 100,
47 height: 100,
48 visibility: [false, true, true],
49 highlightCallback: highlightCallback
50 });
51
52 DygraphOps.dispatchMouseMove(g, 13, 10);
53
54 //check correct row is returned
55 assertEquals(3, h_row);
56 //check there are only two points (because first series is hidden)
57 assertEquals(2, h_pts.length);
58 };
59
60
61 /**
62 * Test that drawPointCallback isn't called when drawPoints is false
63 */
64 CallbackTestCase.prototype.testDrawPointCallback_disabled = function() {
65 var called = false;
66
67 var callback = function() {
68 assertEquals(g, this);
69 called = true;
70 };
71
72 var graph = document.getElementById("graph");
73 var g = new Dygraph(graph, data, {
74 drawPointCallback: callback,
75 });
76
77 assertFalse(called);
78 };
79
80 /**
81 * Test that drawPointCallback is called when drawPoints is true
82 */
83 CallbackTestCase.prototype.testDrawPointCallback_enabled = function() {
84 var called = false;
85 var callbackThis = null;
86
87 var callback = function() {
88 callbackThis = this;
89 called = true;
90 };
91
92 var graph = document.getElementById("graph");
93 var g = new Dygraph(graph, data, {
94 drawPoints: true,
95 drawPointCallback: callback
96 });
97
98 assertTrue(called);
99 assertEquals(g, callbackThis);
100 };
101
102 /**
103 * Test that drawPointCallback is called when drawPoints is true
104 */
105 CallbackTestCase.prototype.testDrawPointCallback_pointSize = function() {
106 var pointSize = 0;
107 var count = 0;
108
109 var callback = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam) {
110 assertEquals(g, this);
111 pointSize = pointSizeParam;
112 count++;
113 };
114
115 var graph = document.getElementById("graph");
116 var g = new Dygraph(graph, data, {
117 drawPoints: true,
118 drawPointCallback: callback
119 });
120
121 assertEquals(1.5, pointSize);
122 assertEquals(12, count); // one call per data point.
123
124 var g = new Dygraph(graph, data, {
125 drawPoints: true,
126 drawPointCallback: callback,
127 pointSize: 8
128 });
129
130 assertEquals(8, pointSize);
131 };
132
133 /**
134 * Test that drawPointCallback is called for isolated points when
135 * drawPoints is false, and also for gap points if that's enabled.
136 */
137 CallbackTestCase.prototype.testDrawPointCallback_isolated = function() {
138 var xvalues = [];
139
140 var g;
141 var callback = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam) {
142 assertEquals(g, this);
143 var dx = g.toDataXCoord(cx);
144 xvalues.push(dx);
145 Dygraph.Circles.DEFAULT.apply(this, arguments);
146 };
147
148 var graph = document.getElementById("graph");
149 var testdata = [[10, 2], [11, 3], [12, NaN], [13, 2], [14, NaN], [15, 3]];
150 var graphOpts = {
151 labels: ['X', 'Y'],
152 valueRange: [0, 4],
153 drawPoints : false,
154 drawPointCallback : callback,
155 pointSize : 8
156 };
157
158 // Test that isolated points get drawn
159 g = new Dygraph(graph, testdata, graphOpts);
160 assertEquals(2, xvalues.length);
161 assertEquals(13, xvalues[0]);
162 assertEquals(15, xvalues[1]);
163
164 // Test that isolated points + gap points get drawn when
165 // drawGapEdgePoints is set. This should add one point at the right
166 // edge of the segment at x=11, but not at the graph edge at x=10.
167 xvalues = []; // Reset for new test
168 graphOpts.drawGapEdgePoints = true;
169 g = new Dygraph(graph, testdata, graphOpts);
170 assertEquals(3, xvalues.length);
171 assertEquals(11, xvalues[0]);
172 assertEquals(13, xvalues[1]);
173 assertEquals(15, xvalues[2]);
174 };
175
176 /**
177 * This tests that when the function idxToRow_ returns the proper row and the onHiglightCallback
178 * is properly called when the first series is hidden (setVisibility = false)
179 *
180 */
181 CallbackTestCase.prototype.testDrawHighlightPointCallbackIsCalled = function() {
182 var called = false;
183
184 var drawHighlightPointCallback = function() {
185 assertEquals(g, this);
186 called = true;
187 };
188
189 var graph = document.getElementById("graph");
190 var g = new Dygraph(graph, data,
191 {
192 width: 100,
193 height: 100,
194 drawHighlightPointCallback: drawHighlightPointCallback
195 });
196
197 assertFalse(called);
198 DygraphOps.dispatchMouseMove(g, 13, 10);
199 assertTrue(called);
200 };
201
202 /**
203 * Test the closest-series highlighting methods for normal and stacked modes.
204 * Also pass in line widths for plain and highlighted lines for easier visual
205 * confirmation that the highlighted line is drawn on top of the others.
206 */
207 var runClosestTest = function(isStacked, widthNormal, widthHighlighted) {
208 var h_row;
209 var h_pts;
210 var h_series;
211
212 var graph = document.getElementById("graph");
213 var g = new Dygraph(graph, data,
214 {
215 width: 600,
216 height: 400,
217 visibility: [false, true, true],
218 stackedGraph: isStacked,
219 strokeWidth: widthNormal,
220 strokeBorderWidth: 2,
221 highlightCircleSize: widthNormal * 2,
222 highlightSeriesBackgroundAlpha: 0.3,
223
224 highlightSeriesOpts: {
225 strokeWidth: widthHighlighted,
226 highlightCircleSize: widthHighlighted * 2
227 }
228 });
229
230 var highlightCallback = function(e, x, pts, row, set) {
231 assertEquals(g, this);
232 h_row = row;
233 h_pts = pts;
234 h_series = set;
235 document.getElementById('selection').innerHTML='row=' + row + ', set=' + set;
236 };
237
238 g.updateOptions({highlightCallback: highlightCallback}, true);
239
240 if (isStacked) {
241 DygraphOps.dispatchMouseMove(g, 11.45, 1.4);
242 assertEquals(1, h_row);
243 assertEquals('c', h_series);
244
245 //now move up in the same row
246 DygraphOps.dispatchMouseMove(g, 11.45, 1.5);
247 assertEquals(1, h_row);
248 assertEquals('b', h_series);
249
250 //and a bit to the right
251 DygraphOps.dispatchMouseMove(g, 11.55, 1.5);
252 assertEquals(2, h_row);
253 assertEquals('c', h_series);
254 } else {
255 DygraphOps.dispatchMouseMove(g, 11, 1.5);
256 assertEquals(1, h_row);
257 assertEquals('c', h_series);
258
259 //now move up in the same row
260 DygraphOps.dispatchMouseMove(g, 11, 2.5);
261 assertEquals(1, h_row);
262 assertEquals('b', h_series);
263 }
264
265 return g;
266 };
267
268 /**
269 * Test basic closest-point highlighting.
270 */
271 CallbackTestCase.prototype.testClosestPointCallback = function() {
272 runClosestTest(false, 1, 3);
273 }
274
275 /**
276 * Test setSelection() with series name
277 */
278 CallbackTestCase.prototype.testSetSelection = function() {
279 var g = runClosestTest(false, 1, 3);
280 assertEquals(1, g.attr_('strokeWidth', 'c'));
281 g.setSelection(false, 'c');
282 assertEquals(3, g.attr_('strokeWidth', 'c'));
283 }
284
285 /**
286 * Test closest-point highlighting for stacked graph
287 */
288 CallbackTestCase.prototype.testClosestPointStackedCallback = function() {
289 runClosestTest(true, 1, 3);
290 }
291
292 /**
293 * Closest-point highlighting with legend CSS - border around active series.
294 */
295 CallbackTestCase.prototype.testClosestPointCallbackCss1 = function() {
296 var css = "div.dygraph-legend > span { display: block; }\n" +
297 "div.dygraph-legend > span.highlight { border: 1px solid grey; }\n";
298 this.styleSheet.innerHTML = css;
299 runClosestTest(false, 2, 4);
300 this.styleSheet.innerHTML = '';
301 }
302
303 /**
304 * Closest-point highlighting with legend CSS - show only closest series.
305 */
306 CallbackTestCase.prototype.testClosestPointCallbackCss2 = function() {
307 var css = "div.dygraph-legend > span { display: none; }\n" +
308 "div.dygraph-legend > span.highlight { display: inline; }\n";
309 this.styleSheet.innerHTML = css;
310 runClosestTest(false, 10, 15);
311 this.styleSheet.innerHTML = '';
312 // TODO(klausw): verify that the highlighted line is drawn on top?
313 }
314
315 /**
316 * Closest-point highlighting with locked series.
317 */
318 CallbackTestCase.prototype.testSetSelectionLocking = function() {
319 var g = runClosestTest(false, 2, 4);
320
321 // Default behavior, 'b' is closest
322 DygraphOps.dispatchMouseMove(g, 11, 4);
323 assertEquals('b', g.getHighlightSeries());
324
325 // Now lock selection to 'c'
326 g.setSelection(false, 'c', true);
327 DygraphOps.dispatchMouseMove(g, 11, 4);
328 assertEquals('c', g.getHighlightSeries());
329
330 // Unlock, should be back to 'b'
331 g.clearSelection();
332 DygraphOps.dispatchMouseMove(g, 11, 4);
333 assertEquals('b', g.getHighlightSeries());
334 }
335
336 /**
337 * This tests that closest point searches work for data containing NaNs.
338 *
339 * It's intended to catch a regression where a NaN Y value confuses the
340 * closest-point algorithm, treating it as closer as any previous point.
341 */
342 CallbackTestCase.prototype.testNaNData = function() {
343 var dataNaN = [
344 [9, -1, NaN, NaN],
345 [10, -1, 1, 2],
346 [11, 0, 3, 1],
347 [12, 1, 4, NaN],
348 [13, 0, 2, 3],
349 [14, -1, 1, 4]];
350
351 var h_row;
352 var h_pts;
353
354 var highlightCallback = function(e, x, pts, row) {
355 assertEquals(g, this);
356 h_row = row;
357 h_pts = pts;
358 };
359
360 var graph = document.getElementById("graph");
361 var g = new Dygraph(graph, dataNaN,
362 {
363 width: 600,
364 height: 400,
365 labels: ['x', 'a', 'b', 'c'],
366 visibility: [false, true, true],
367 highlightCallback: highlightCallback
368 });
369
370 DygraphOps.dispatchMouseMove(g, 10.1, 0.9);
371 //check correct row is returned
372 assertEquals(1, h_row);
373
374 // Explicitly test closest point algorithms
375 var dom = g.toDomCoords(10.1, 0.9);
376 assertEquals(1, g.findClosestRow(dom[0]));
377
378 var res = g.findClosestPoint(dom[0], dom[1]);
379 assertEquals(1, res.row);
380 assertEquals('b', res.seriesName);
381
382 res = g.findStackedPoint(dom[0], dom[1]);
383 assertEquals(1, res.row);
384 assertEquals('c', res.seriesName);
385 };
386
387 /**
388 * This tests that stacked point searches work for data containing NaNs.
389 */
390 CallbackTestCase.prototype.testNaNDataStack = function() {
391 var dataNaN = [
392 [9, -1, NaN, NaN],
393 [10, -1, 1, 2],
394 [11, 0, 3, 1],
395 [12, 1, NaN, 2],
396 [13, 0, 2, 3],
397 [14, -1, 1, 4],
398 [15, 0, 2, NaN],
399 [16, 1, 1, 3],
400 [17, 1, NaN, 3],
401 [18, 0, 2, 5],
402 [19, 0, 1, 4]];
403
404 var h_row;
405 var h_pts;
406
407 var highlightCallback = function(e, x, pts, row) {
408 assertEquals(g, this);
409 h_row = row;
410 h_pts = pts;
411 };
412
413 var graph = document.getElementById("graph");
414 var g = new Dygraph(graph, dataNaN,
415 {
416 width: 600,
417 height: 400,
418 labels: ['x', 'a', 'b', 'c'],
419 visibility: [false, true, true],
420 stackedGraph: true,
421 highlightCallback: highlightCallback
422 });
423
424 DygraphOps.dispatchMouseMove(g, 10.1, 0.9);
425 //check correct row is returned
426 assertEquals(1, h_row);
427
428 // Explicitly test stacked point algorithm.
429 var dom = g.toDomCoords(10.1, 0.9);
430 var res = g.findStackedPoint(dom[0], dom[1]);
431 assertEquals(1, res.row);
432 assertEquals('c', res.seriesName);
433
434 // All-NaN area at left, should get no points.
435 dom = g.toDomCoords(9.1, 0.9);
436 res = g.findStackedPoint(dom[0], dom[1]);
437 assertEquals(0, res.row);
438 assertEquals(undefined, res.seriesName);
439
440 // First gap, get 'c' since it's non-NaN.
441 dom = g.toDomCoords(12.1, 0.9);
442 res = g.findStackedPoint(dom[0], dom[1]);
443 assertEquals(3, res.row);
444 assertEquals('c', res.seriesName);
445
446 // Second gap, get 'b' since 'c' is NaN.
447 dom = g.toDomCoords(15.1, 0.9);
448 res = g.findStackedPoint(dom[0], dom[1]);
449 assertEquals(6, res.row);
450 assertEquals('b', res.seriesName);
451
452 // Isolated points should work, finding series b in this case.
453 dom = g.toDomCoords(15.9, 3.1);
454 res = g.findStackedPoint(dom[0], dom[1]);
455 assertEquals(7, res.row);
456 assertEquals('b', res.seriesName);
457 };
458
459 CallbackTestCase.prototype.testGapHighlight = function() {
460 var dataGap = [
461 [1, null, 3],
462 [2, 2, null],
463 [3, null, 5],
464 [4, 4, null],
465 [5, null, 7],
466 [6, NaN, null],
467 [8, 8, null],
468 [10, 10, null]];
469
470 var h_row;
471 var h_pts;
472
473 var highlightCallback = function(e, x, pts, row) {
474 assertEquals(g, this);
475 h_row = row;
476 h_pts = pts;
477 };
478
479 var graph = document.getElementById("graph");
480 var g = new Dygraph(graph, dataGap, {
481 width: 400,
482 height: 300,
483 //stackedGraph: true,
484 connectSeparatedPoints: true,
485 drawPoints: true,
486 labels: ['x', 'A', 'B'],
487 highlightCallback: highlightCallback
488 });
489
490 DygraphOps.dispatchMouseMove(g, 1.1, 10);
491 //point from series B
492 assertEquals(0, h_row);
493 assertEquals(1, h_pts.length);
494 assertEquals(3, h_pts[0].yval);
495 assertEquals('B', h_pts[0].name);
496
497 DygraphOps.dispatchMouseMove(g, 6.1, 10);
498 // A is NaN at x=6
499 assertEquals(1, h_pts.length);
500 assert(isNaN(h_pts[0].yval));
501 assertEquals('A', h_pts[0].name);
502
503 DygraphOps.dispatchMouseMove(g, 8.1, 10);
504 //point from series A
505 assertEquals(6, h_row);
506 assertEquals(1, h_pts.length);
507 assertEquals(8, h_pts[0].yval);
508 assertEquals('A', h_pts[0].name);
509 };
510
511 CallbackTestCase.prototype.testFailedResponse = function() {
512
513 // Fake out the XMLHttpRequest so it doesn't do anything.
514 XMLHttpRequest = function () {};
515 XMLHttpRequest.prototype.open = function () {};
516 XMLHttpRequest.prototype.send = function () {};
517
518 var highlightCallback = function(e, x, pts, row) {
519 fail("should not reach here");
520 };
521
522 var graph = document.getElementById("graph");
523 graph.style.border = "2px solid black";
524 var g = new Dygraph(graph, "data.csv", { // fake name
525 width: 400,
526 height: 300,
527 highlightCallback : highlightCallback
528 });
529
530 DygraphOps.dispatchMouseOver_Point(g, 800, 800);
531 DygraphOps.dispatchMouseMove_Point(g, 100, 100);
532 DygraphOps.dispatchMouseMove_Point(g, 800, 800);
533
534 var oldOnerror = window.onerror;
535 var failed = false;
536 window.onerror = function() { failed = true; return false; }
537
538 DygraphOps.dispatchMouseOut_Point(g, 800, 800); // This call should not throw an exception.
539
540 assertFalse("exception thrown during mouseout", failed);
541 };
542
543
544 // Regression test for http://code.google.com/p/dygraphs/issues/detail?id=355
545 CallbackTestCase.prototype.testHighlightCallbackRow = function() {
546 var highlightRow;
547 var highlightCallback = function(e, x, pts, row) {
548 assertEquals(g, this);
549 highlightRow = row;
550 };
551
552 var graph = document.getElementById("graph");
553 var g = new Dygraph(graph,
554 "X,Y,Z\n" +
555 "0,1,2\n" + // 0
556 "1,2,3\n" + // 100
557 "2,3,4\n" + // 200
558 "3,4,5\n" + // 300
559 "4,5,6\n", // 400
560 { // fake name
561 width: 400,
562 height: 300,
563 highlightCallback : highlightCallback
564 });
565
566 // Mouse over each of the points
567 DygraphOps.dispatchMouseOver_Point(g, 0, 0);
568 DygraphOps.dispatchMouseMove_Point(g, 0, 0);
569 assertEquals(0, highlightRow);
570 DygraphOps.dispatchMouseMove_Point(g, 100, 0);
571 assertEquals(1, highlightRow);
572 DygraphOps.dispatchMouseMove_Point(g, 200, 0);
573 assertEquals(2, highlightRow);
574 DygraphOps.dispatchMouseMove_Point(g, 300, 0);
575 assertEquals(3, highlightRow);
576 DygraphOps.dispatchMouseMove_Point(g, 400, 0);
577 assertEquals(4, highlightRow);
578
579 // Now zoom and verify that the row numbers still refer to rows in the data
580 // array.
581 g.updateOptions({dateWindow: [2, 4]});
582 DygraphOps.dispatchMouseOver_Point(g, 0, 0);
583 DygraphOps.dispatchMouseMove_Point(g, 0, 0);
584 assertEquals(2, highlightRow);
585 assertEquals('2: Y: 3 Z: 4', Util.getLegend());
586 };
587
588 /**
589 * Test that underlay callback is called even when there are no series,
590 * and that the y axis ranges are not NaN.
591 */
592 CallbackTestCase.prototype.underlayCallback_noSeries = function() {
593 var called = false;
594 var yMin, yMax;
595
596 var callback = function(canvas, area, g) {
597 assertEquals(g, this);
598 called = true;
599 yMin = g.yAxisRange(0)[0];
600 yMax = g.yAxisRange(0)[1];
601 };
602
603 var graph = document.getElementById("graph");
604 var g = new Dygraph(graph, "\n", {
605 underlayCallback: callback
606 });
607
608 assertTrue(called);
609 assertFalse(isNaN(yMin));
610 assertFalse(isNaN(yMax));
611 };
612
613 /**
614 * Test that underlay callback receives the correct y-axis range.
615 */
616 CallbackTestCase.prototype.underlayCallback_yAxisRange = function() {
617 var called = false;
618 var yMin, yMax;
619
620 var callback = function(canvas, area, g) {
621 assertEquals(g, this);
622 yMin = g.yAxisRange(0)[0];
623 yMax = g.yAxisRange(0)[1];
624 };
625
626 var graph = document.getElementById("graph");
627 var g = new Dygraph(graph, "\n", {
628 valueRange: [0,10],
629 underlayCallback: callback
630 });
631
632 assertEquals(0, yMin);
633 assertEquals(10, yMax);
634 };
635
636 /**
637 * Test that drawPointCallback is called for isolated points and correct idx for the point is returned.
638 */
639 CallbackTestCase.prototype.testDrawPointCallback_idx = function() {
640 var indices = [];
641
642 var g;
643 var callback = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam,idx) {
644 assertEquals(g, this);
645 indices.push(idx);
646 Dygraph.Circles.DEFAULT.apply(this, arguments);
647 };
648
649 var graph = document.getElementById("graph");
650
651 var testdata = [[10, 2], [11, 3], [12, NaN], [13, 2], [14, NaN], [15, 3]];
652 var graphOpts = {
653 labels: ['X', 'Y'],
654 valueRange: [0, 4],
655 drawPoints : false,
656 drawPointCallback : callback,
657 pointSize : 8
658 };
659
660 // Test that correct idx for isolated points are passed to the callback.
661 g = new Dygraph(graph, testdata, graphOpts);
662 assertEquals(2, indices.length);
663 assertEquals([3, 5],indices);
664
665 // Test that correct indices for isolated points + gap points are passed to the callback when
666 // drawGapEdgePoints is set. This should add one point at the right
667 // edge of the segment at x=11, but not at the graph edge at x=10.
668 indices = []; // Reset for new test
669 graphOpts.drawGapEdgePoints = true;
670 g = new Dygraph(graph, testdata, graphOpts);
671 assertEquals(3, indices.length);
672 assertEquals([1, 3, 5],indices);
673
674
675 //Test that correct indices are passed to the callback when zoomed in.
676 indices = []; // Reset for new test
677 graphOpts.dateWindow = [12.5,13.5]
678 graphOpts.drawPoints = true;
679 testdata = [[10, 2], [11, 3], [12, 4], [13, 2], [14, 5], [15, 3]];
680 g = new Dygraph(graph, testdata, graphOpts);
681 assertEquals(3, indices.length);
682 assertEquals([2, 3, 4],indices);
683 };
684
685 /**
686 * Test that the correct idx is returned for the point in the onHiglightCallback.
687 */
688 CallbackTestCase.prototype.testDrawHighlightPointCallback_idx = function() {
689 var idxToCheck = null;
690
691 var drawHighlightPointCallback = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam,idx) {
692 assertEquals(g, this);
693 idxToCheck = idx;
694 };
695 var testdata = [[1, 2], [2, 3], [3, NaN], [4, 2], [5, NaN], [6, 3]];
696 var graph = document.getElementById("graph");
697 var g = new Dygraph(graph, testdata,
698 {
699 drawHighlightPointCallback : drawHighlightPointCallback
700 });
701
702 assertNull(idxToCheck);
703 DygraphOps.dispatchMouseMove(g, 3, 0);
704 // check that NaN point is not highlighted
705 assertNull(idxToCheck);
706 DygraphOps.dispatchMouseMove(g, 1, 2);
707 // check that correct index is returned
708 assertEquals(0,idxToCheck);
709 DygraphOps.dispatchMouseMove(g, 6, 3);
710 assertEquals(5,idxToCheck);
711 };