Regression test for issue 430: Incorrect rendering when all values are negative and...
[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 h_row = row;
39 h_pts = pts;
40 };
41
42 var graph = document.getElementById("graph");
43 var g = new Dygraph(graph, data,
44 {
45 width: 100,
46 height: 100,
47 visibility: [false, true, true],
48 highlightCallback: highlightCallback
49 });
50
51 DygraphOps.dispatchMouseMove(g, 13, 10);
52
53 //check correct row is returned
54 assertEquals(3, h_row);
55 //check there are only two points (because first series is hidden)
56 assertEquals(2, h_pts.length);
57 };
58
59
60 /**
61 * Test that drawPointCallback isn't called when drawPoints is false
62 */
63 CallbackTestCase.prototype.testDrawPointCallback_disabled = function() {
64 var called = false;
65
66 var callback = function() {
67 called = true;
68 };
69
70 var graph = document.getElementById("graph");
71 var g = new Dygraph(graph, data, {
72 drawPointCallback : callback,
73 });
74
75 assertFalse(called);
76 };
77
78 /**
79 * Test that drawPointCallback is called when drawPoints is true
80 */
81 CallbackTestCase.prototype.testDrawPointCallback_enabled = function() {
82 var called = false;
83
84 var callback = function() {
85 called = true;
86 };
87
88 var graph = document.getElementById("graph");
89 var g = new Dygraph(graph, data, {
90 drawPoints : true,
91 drawPointCallback : callback
92 });
93
94 assertTrue(called);
95 };
96
97 /**
98 * Test that drawPointCallback is called when drawPoints is true
99 */
100 CallbackTestCase.prototype.testDrawPointCallback_pointSize = function() {
101 var pointSize = 0;
102 var count = 0;
103
104 var callback = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam) {
105 pointSize = pointSizeParam;
106 count++;
107 };
108
109 var graph = document.getElementById("graph");
110 var g = new Dygraph(graph, data, {
111 drawPoints : true,
112 drawPointCallback : callback
113 });
114
115 assertEquals(1.5, pointSize);
116 assertEquals(12, count); // one call per data point.
117
118 var g = new Dygraph(graph, data, {
119 drawPoints : true,
120 drawPointCallback : callback,
121 pointSize : 8
122 });
123
124 assertEquals(8, pointSize);
125 };
126
127 /**
128 * Test that drawPointCallback is called for isolated points when
129 * drawPoints is false, and also for gap points if that's enabled.
130 */
131 CallbackTestCase.prototype.testDrawPointCallback_isolated = function() {
132 var xvalues = [];
133
134 var g;
135 var callback = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam) {
136 var dx = g.toDataXCoord(cx);
137 xvalues.push(dx);
138 Dygraph.Circles.DEFAULT.apply(this, arguments);
139 };
140
141 var graph = document.getElementById("graph");
142 var testdata = [[10, 2], [11, 3], [12, NaN], [13, 2], [14, NaN], [15, 3]];
143 var graphOpts = {
144 labels: ['X', 'Y'],
145 valueRange: [0, 4],
146 drawPoints : false,
147 drawPointCallback : callback,
148 pointSize : 8
149 };
150
151 // Test that isolated points get drawn
152 g = new Dygraph(graph, testdata, graphOpts);
153 assertEquals(2, xvalues.length);
154 assertEquals(13, xvalues[0]);
155 assertEquals(15, xvalues[1]);
156
157 // Test that isolated points + gap points get drawn when
158 // drawGapEdgePoints is set. This should add one point at the right
159 // edge of the segment at x=11, but not at the graph edge at x=10.
160 xvalues = []; // Reset for new test
161 graphOpts.drawGapEdgePoints = true;
162 g = new Dygraph(graph, testdata, graphOpts);
163 assertEquals(3, xvalues.length);
164 assertEquals(11, xvalues[0]);
165 assertEquals(13, xvalues[1]);
166 assertEquals(15, xvalues[2]);
167 };
168
169 /**
170 * This tests that when the function idxToRow_ returns the proper row and the onHiglightCallback
171 * is properly called when the first series is hidden (setVisibility = false)
172 *
173 */
174 CallbackTestCase.prototype.testDrawHighlightPointCallbackIsCalled = function() {
175 var called = false;
176
177 var drawHighlightPointCallback = function() {
178 called = true;
179 };
180
181 var graph = document.getElementById("graph");
182 var g = new Dygraph(graph, data,
183 {
184 width: 100,
185 height : 100,
186 drawHighlightPointCallback : drawHighlightPointCallback
187 });
188
189 assertFalse(called);
190 DygraphOps.dispatchMouseMove(g, 13, 10);
191 assertTrue(called);
192 };
193
194 /**
195 * Test the closest-series highlighting methods for normal and stacked modes.
196 * Also pass in line widths for plain and highlighted lines for easier visual
197 * confirmation that the highlighted line is drawn on top of the others.
198 */
199 var runClosestTest = function(isStacked, widthNormal, widthHighlighted) {
200 var h_row;
201 var h_pts;
202 var h_series;
203
204 var graph = document.getElementById("graph");
205 var g = new Dygraph(graph, data,
206 {
207 width: 600,
208 height: 400,
209 visibility: [false, true, true],
210 stackedGraph: isStacked,
211 strokeWidth: widthNormal,
212 strokeBorderWidth: 2,
213 highlightCircleSize: widthNormal * 2,
214 highlightSeriesBackgroundAlpha: 0.3,
215
216 highlightSeriesOpts: {
217 strokeWidth: widthHighlighted,
218 highlightCircleSize: widthHighlighted * 2
219 }
220 });
221
222 var highlightCallback = function(e, x, pts, row, set) {
223 h_row = row;
224 h_pts = pts;
225 h_series = set;
226 document.getElementById('selection').innerHTML='row=' + row + ', set=' + set;
227 };
228
229 g.updateOptions({highlightCallback: highlightCallback}, true);
230
231 if (isStacked) {
232 DygraphOps.dispatchMouseMove(g, 11.45, 1.4);
233 assertEquals(1, h_row);
234 assertEquals('c', h_series);
235
236 //now move up in the same row
237 DygraphOps.dispatchMouseMove(g, 11.45, 1.5);
238 assertEquals(1, h_row);
239 assertEquals('b', h_series);
240
241 //and a bit to the right
242 DygraphOps.dispatchMouseMove(g, 11.55, 1.5);
243 assertEquals(2, h_row);
244 assertEquals('c', h_series);
245 } else {
246 DygraphOps.dispatchMouseMove(g, 11, 1.5);
247 assertEquals(1, h_row);
248 assertEquals('c', h_series);
249
250 //now move up in the same row
251 DygraphOps.dispatchMouseMove(g, 11, 2.5);
252 assertEquals(1, h_row);
253 assertEquals('b', h_series);
254 }
255
256 return g;
257 };
258
259 /**
260 * Test basic closest-point highlighting.
261 */
262 CallbackTestCase.prototype.testClosestPointCallback = function() {
263 runClosestTest(false, 1, 3);
264 }
265
266 /**
267 * Test setSelection() with series name
268 */
269 CallbackTestCase.prototype.testSetSelection = function() {
270 var g = runClosestTest(false, 1, 3);
271 assertEquals(1, g.attr_('strokeWidth', 'c'));
272 g.setSelection(false, 'c');
273 assertEquals(3, g.attr_('strokeWidth', 'c'));
274 }
275
276 /**
277 * Test closest-point highlighting for stacked graph
278 */
279 CallbackTestCase.prototype.testClosestPointStackedCallback = function() {
280 runClosestTest(true, 1, 3);
281 }
282
283 /**
284 * Closest-point highlighting with legend CSS - border around active series.
285 */
286 CallbackTestCase.prototype.testClosestPointCallbackCss1 = function() {
287 var css = "div.dygraph-legend > span { display: block; }\n" +
288 "div.dygraph-legend > span.highlight { border: 1px solid grey; }\n";
289 this.styleSheet.innerHTML = css;
290 runClosestTest(false, 2, 4);
291 this.styleSheet.innerHTML = '';
292 }
293
294 /**
295 * Closest-point highlighting with legend CSS - show only closest series.
296 */
297 CallbackTestCase.prototype.testClosestPointCallbackCss2 = function() {
298 var css = "div.dygraph-legend > span { display: none; }\n" +
299 "div.dygraph-legend > span.highlight { display: inline; }\n";
300 this.styleSheet.innerHTML = css;
301 runClosestTest(false, 10, 15);
302 this.styleSheet.innerHTML = '';
303 // TODO(klausw): verify that the highlighted line is drawn on top?
304 }
305
306 /**
307 * Closest-point highlighting with locked series.
308 */
309 CallbackTestCase.prototype.testSetSelectionLocking = function() {
310 var g = runClosestTest(false, 2, 4);
311
312 // Default behavior, 'b' is closest
313 DygraphOps.dispatchMouseMove(g, 11, 4);
314 assertEquals('b', g.getHighlightSeries());
315
316 // Now lock selection to 'c'
317 g.setSelection(false, 'c', true);
318 DygraphOps.dispatchMouseMove(g, 11, 4);
319 assertEquals('c', g.getHighlightSeries());
320
321 // Unlock, should be back to 'b'
322 g.clearSelection();
323 DygraphOps.dispatchMouseMove(g, 11, 4);
324 assertEquals('b', g.getHighlightSeries());
325 }
326
327 /**
328 * This tests that closest point searches work for data containing NaNs.
329 *
330 * It's intended to catch a regression where a NaN Y value confuses the
331 * closest-point algorithm, treating it as closer as any previous point.
332 */
333 CallbackTestCase.prototype.testNaNData = function() {
334 var dataNaN = [
335 [9, -1, NaN, NaN],
336 [10, -1, 1, 2],
337 [11, 0, 3, 1],
338 [12, 1, 4, NaN],
339 [13, 0, 2, 3],
340 [14, -1, 1, 4]];
341
342 var h_row;
343 var h_pts;
344
345 var highlightCallback = function(e, x, pts, row) {
346 h_row = row;
347 h_pts = pts;
348 };
349
350 var graph = document.getElementById("graph");
351 var g = new Dygraph(graph, dataNaN,
352 {
353 width: 600,
354 height: 400,
355 labels: ['x', 'a', 'b', 'c'],
356 visibility: [false, true, true],
357 highlightCallback: highlightCallback
358 });
359
360 DygraphOps.dispatchMouseMove(g, 10.1, 0.9);
361 //check correct row is returned
362 assertEquals(1, h_row);
363
364 // Explicitly test closest point algorithms
365 var dom = g.toDomCoords(10.1, 0.9);
366 assertEquals(1, g.findClosestRow(dom[0]));
367
368 var res = g.findClosestPoint(dom[0], dom[1]);
369 assertEquals(1, res.row);
370 assertEquals('b', res.seriesName);
371
372 res = g.findStackedPoint(dom[0], dom[1]);
373 assertEquals(1, res.row);
374 assertEquals('c', res.seriesName);
375 };
376
377 /**
378 * This tests that stacked point searches work for data containing NaNs.
379 */
380 CallbackTestCase.prototype.testNaNDataStack = function() {
381 var dataNaN = [
382 [9, -1, NaN, NaN],
383 [10, -1, 1, 2],
384 [11, 0, 3, 1],
385 [12, 1, NaN, 2],
386 [13, 0, 2, 3],
387 [14, -1, 1, 4],
388 [15, 0, 2, NaN],
389 [16, 1, 1, 3],
390 [17, 1, NaN, 3],
391 [18, 0, 2, 5],
392 [19, 0, 1, 4]];
393
394 var h_row;
395 var h_pts;
396
397 var highlightCallback = function(e, x, pts, row) {
398 h_row = row;
399 h_pts = pts;
400 };
401
402 var graph = document.getElementById("graph");
403 var g = new Dygraph(graph, dataNaN,
404 {
405 width: 600,
406 height: 400,
407 labels: ['x', 'a', 'b', 'c'],
408 visibility: [false, true, true],
409 stackedGraph: true,
410 highlightCallback: highlightCallback
411 });
412
413 DygraphOps.dispatchMouseMove(g, 10.1, 0.9);
414 //check correct row is returned
415 assertEquals(1, h_row);
416
417 // Explicitly test stacked point algorithm.
418 var dom = g.toDomCoords(10.1, 0.9);
419 var res = g.findStackedPoint(dom[0], dom[1]);
420 assertEquals(1, res.row);
421 assertEquals('c', res.seriesName);
422
423 // First gap, no data due to NaN contagion.
424 dom = g.toDomCoords(12.1, 0.9);
425 res = g.findStackedPoint(dom[0], dom[1]);
426 assertEquals(3, res.row);
427 assertEquals(undefined, res.seriesName);
428
429 // Second gap, no data due to NaN contagion.
430 dom = g.toDomCoords(15.1, 0.9);
431 res = g.findStackedPoint(dom[0], dom[1]);
432 assertEquals(6, res.row);
433 assertEquals(undefined, res.seriesName);
434
435 // Isolated points should work, finding series b in this case.
436 dom = g.toDomCoords(15.9, 3.1);
437 res = g.findStackedPoint(dom[0], dom[1]);
438 assertEquals(7, res.row);
439 assertEquals('b', res.seriesName);
440 };
441
442 CallbackTestCase.prototype.testGapHighlight = function() {
443 var dataGap = [
444 [1, null, 3],
445 [2, 2, null],
446 [3, null, 5],
447 [4, 4, null],
448 [5, null, 7],
449 [6, NaN, null],
450 [8, 8, null],
451 [10, 10, null]];
452
453 var h_row;
454 var h_pts;
455
456 var highlightCallback = function(e, x, pts, row) {
457 h_row = row;
458 h_pts = pts;
459 };
460
461 var graph = document.getElementById("graph");
462 var g = new Dygraph(graph, dataGap, {
463 width: 400,
464 height: 300,
465 //stackedGraph: true,
466 connectSeparatedPoints: true,
467 drawPoints: true,
468 labels: ['x', 'A', 'B'],
469 highlightCallback : highlightCallback
470 });
471
472 DygraphOps.dispatchMouseMove(g, 1.1, 10);
473 //point from series B
474 assertEquals(0, h_row);
475 assertEquals(1, h_pts.length);
476 assertEquals(3, h_pts[0].yval);
477 assertEquals('B', h_pts[0].name);
478
479 DygraphOps.dispatchMouseMove(g, 6.1, 10);
480 // A is NaN at x=6
481 assertEquals(1, h_pts.length);
482 assert(isNaN(h_pts[0].yval));
483 assertEquals('A', h_pts[0].name);
484
485 DygraphOps.dispatchMouseMove(g, 8.1, 10);
486 //point from series A
487 assertEquals(6, h_row);
488 assertEquals(1, h_pts.length);
489 assertEquals(8, h_pts[0].yval);
490 assertEquals('A', h_pts[0].name);
491 };
492
493 CallbackTestCase.prototype.testFailedResponse = function() {
494
495 // Fake out the XMLHttpRequest so it doesn't do anything.
496 XMLHttpRequest = function () {};
497 XMLHttpRequest.prototype.open = function () {};
498 XMLHttpRequest.prototype.send = function () {};
499
500 var highlightCallback = function(e, x, pts, row) {
501 fail("should not reach here");
502 };
503
504 var graph = document.getElementById("graph");
505 graph.style.border = "2px solid black";
506 var g = new Dygraph(graph, "data.csv", { // fake name
507 width: 400,
508 height: 300,
509 highlightCallback : highlightCallback
510 });
511
512 DygraphOps.dispatchMouseOver_Point(g, 800, 800);
513 DygraphOps.dispatchMouseMove_Point(g, 100, 100);
514 DygraphOps.dispatchMouseMove_Point(g, 800, 800);
515
516 var oldOnerror = window.onerror;
517 var failed = false;
518 window.onerror = function() { failed = true; return false; }
519
520 DygraphOps.dispatchMouseOut_Point(g, 800, 800); // This call should not throw an exception.
521
522 assertFalse("exception thrown during mouseout", failed);
523 };