Initial check-in
[dygraphs.git] / plotkit_v091 / PlotKit / Layout.js
1 /*
2 PlotKit Layout
3 ==============
4
5 Handles laying out data on to a virtual canvas square canvas between 0.0
6 and 1.0. If you want to add new chart/plot types such as point plots,
7 you need to add them here.
8
9 Copyright
10 ---------
11 Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net>
12 For use under the BSD license. <http://www.liquidx.net/plotkit>
13
14 */
15
16 try {
17 if (typeof(PlotKit.Base) == 'undefined')
18 {
19 throw ""
20 }
21 }
22 catch (e) {
23 throw "PlotKit.Layout depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.Base"
24 }
25
26 // --------------------------------------------------------------------
27 // Start of Layout definition
28 // --------------------------------------------------------------------
29
30 if (typeof(PlotKit.Layout) == 'undefined') {
31 PlotKit.Layout = {};
32 }
33
34 PlotKit.Layout.NAME = "PlotKit.Layout";
35 PlotKit.Layout.VERSION = PlotKit.VERSION;
36
37 PlotKit.Layout.__repr__ = function() {
38 return "[" + this.NAME + " " + this.VERSION + "]";
39 };
40
41 PlotKit.Layout.toString = function() {
42 return this.__repr__();
43 }
44
45 PlotKit.Layout.valid_styles = ["bar", "line", "pie", "point"];
46
47 // --------------------------------------------------------------------
48 // Start of Layout definition
49 // --------------------------------------------------------------------
50
51 PlotKit.Layout = function(style, options) {
52
53 this.options = {
54 "barWidthFillFraction": 0.75,
55 "barOrientation": "vertical",
56 "xOriginIsZero": true,
57 "yOriginIsZero": true,
58 "xAxis": null, // [xmin, xmax]
59 "yAxis": null, // [ymin, ymax]
60 "xTicks": null, // [{label: "somelabel", v: value}, ..] (label opt.)
61 "yTicks": null, // [{label: "somelabel", v: value}, ..] (label opt.)
62 "xNumberOfTicks": 10,
63 "yNumberOfTicks": 5,
64 "xTickPrecision": 1,
65 "yTickPrecision": 1,
66 "pieRadius": 0.4
67 };
68
69 // valid external options : TODO: input verification
70 this.style = style;
71 MochiKit.Base.update(this.options, options ? options : {});
72
73 // externally visible states
74 // overriden if xAxis and yAxis are set in options
75 if (!MochiKit.Base.isUndefinedOrNull(this.options.xAxis)) {
76 this.minxval = this.options.xAxis[0];
77 this.maxxval = this.options.xAxis[1];
78 this.xscale = this.maxxval - this.minxval;
79 }
80 else {
81 this.minxval = 0;
82 this.maxxval = null;
83 this.xscale = null; // val -> pos factor (eg, xval * xscale = xpos)
84 }
85
86 if (!MochiKit.Base.isUndefinedOrNull(this.options.yAxis)) {
87 this.minyval = this.options.yAxis[0];
88 this.maxyval = this.options.yAxis[1];
89 this.yscale = this.maxyval - this.minyval;
90 }
91 else {
92 this.minyval = 0;
93 this.maxyval = null;
94 this.yscale = null;
95 }
96
97 this.bars = new Array(); // array of bars to plot for bar charts
98 this.points = new Array(); // array of points to plot for line plots
99 this.slices = new Array(); // array of slices to draw for pie charts
100
101 this.xticks = new Array();
102 this.yticks = new Array();
103
104 // internal states
105 this.datasets = new Array();
106 this.minxdelta = 0;
107 this.xrange = 1;
108 this.yrange = 1;
109
110 this.hitTestCache = {x2maxy: null};
111
112 };
113
114 // --------------------------------------------------------------------
115 // Dataset Manipulation
116 // --------------------------------------------------------------------
117
118
119 PlotKit.Layout.prototype.addDataset = function(setname, set_xy) {
120 this.datasets[setname] = set_xy;
121 };
122
123 PlotKit.Layout.prototype.removeDataset = function(setname, set_xy) {
124 delete this.datasets[setname];
125 };
126
127 PlotKit.Layout.prototype.addDatasetFromTable = function(name, tableElement, xcol, ycol, lcol) {
128 var isNil = MochiKit.Base.isUndefinedOrNull;
129 var scrapeText = MochiKit.DOM.scrapeText;
130 var strip = MochiKit.Format.strip;
131
132 if (isNil(xcol))
133 xcol = 0;
134 if (isNil(ycol))
135 ycol = 1;
136 if (isNil(lcol))
137 lcol = -1;
138
139 var rows = tableElement.tBodies[0].rows;
140 var data = new Array();
141 var labels = new Array();
142
143 if (!isNil(rows)) {
144 for (var i = 0; i < rows.length; i++) {
145 data.push([parseFloat(strip(scrapeText(rows[i].cells[xcol]))),
146 parseFloat(strip(scrapeText(rows[i].cells[ycol])))]);
147 if (lcol >= 0){
148 labels.push({v: parseFloat(strip(scrapeText(rows[i].cells[xcol]))),
149 label: strip(scrapeText(rows[i].cells[lcol]))});
150 }
151 }
152 this.addDataset(name, data);
153 if (lcol >= 0) {
154 this.options.xTicks = labels;
155 }
156 return true;
157 }
158 return false;
159 };
160
161 // --------------------------------------------------------------------
162 // Evaluates the layout for the current data and style.
163 // --------------------------------------------------------------------
164
165 PlotKit.Layout.prototype.evaluate = function() {
166 this._evaluateLimits();
167 this._evaluateScales();
168 if (this.style == "bar") {
169 if (this.options.barOrientation == "horizontal") {
170 this._evaluateHorizBarCharts();
171 }
172 else {
173 this._evaluateBarCharts();
174 }
175 this._evaluateBarTicks();
176 }
177 else if (this.style == "line") {
178 this._evaluateLineCharts();
179 this._evaluateLineTicks();
180 }
181 else if (this.style == "pie") {
182 this._evaluatePieCharts();
183 this._evaluatePieTicks();
184 }
185 };
186
187
188
189 // Given the fractional x, y positions, report the corresponding
190 // x, y values.
191 PlotKit.Layout.prototype.hitTest = function(x, y) {
192 // TODO: make this more efficient with better datastructures
193 // for this.bars, this.points and this.slices
194
195 var f = MochiKit.Format.twoDigitFloat;
196
197 if ((this.style == "bar") && this.bars && (this.bars.length > 0)) {
198 for (var i = 0; i < this.bars.length; i++) {
199 var bar = this.bars[i];
200 if ((x >= bar.x) && (x <= bar.x + bar.w)
201 && (y >= bar.y) && (y - bar.y <= bar.h))
202 return bar;
203 }
204 }
205
206 else if (this.style == "line") {
207 if (this.hitTestCache.x2maxy == null) {
208 this._regenerateHitTestCache();
209 }
210
211 // 1. find the xvalues that equal or closest to the give x
212 var xval = x / this.xscale;
213 var xvalues = this.hitTestCache.xvalues;
214 var xbefore = null;
215 var xafter = null;
216
217 for (var i = 1; i < xvalues.length; i++) {
218 if (xvalues[i] > xval) {
219 xbefore = xvalues[i-1];
220 xafter = xvalues[i];
221 break;
222 }
223 }
224
225 if ((xbefore != null)) {
226 var ybefore = this.hitTestCache.x2maxy[xbefore];
227 var yafter = this.hitTestCache.x2maxy[xafter];
228 var yval = (1.0 - y)/this.yscale;
229
230 // interpolate whether we will fall inside or outside
231 var gradient = (yafter - ybefore) / (xafter - xbefore);
232 var projmaxy = ybefore + gradient * (xval - xbefore);
233 if (projmaxy >= yval) {
234 // inside the highest curve (roughly)
235 var obj = {xval: xval, yval: yval,
236 xafter: xafter, yafter: yafter,
237 xbefore: xbefore, ybefore: ybefore,
238 yprojected: projmaxy
239 };
240 return obj;
241 }
242 }
243 }
244
245 else if (this.style == "pie") {
246 var dist = Math.sqrt((y-0.5)*(y-0.5) + (x-0.5)*(x-0.5));
247 if (dist > this.options.pieRadius)
248 return null;
249
250 // TODO: actually doesn't work if we don't know how the Canvas
251 // lays it out, need to fix!
252 var angle = Math.atan2(y - 0.5, x - 0.5) - Math.PI/2;
253 for (var i = 0; i < this.slices.length; i++) {
254 var slice = this.slices[i];
255 if (slice.startAngle < angle && slice.endAngle >= angle)
256 return slice;
257 }
258 }
259
260 return null;
261 };
262
263 // Reports valid position rectangle for X value (only valid for bar charts)
264 PlotKit.Layout.prototype.rectForX = function(x) {
265 return null;
266 };
267
268 // Reports valid angles through which X value encloses (only valid for pie charts)
269 PlotKit.Layout.prototype.angleRangeForX = function(x) {
270 return null;
271 };
272
273 // --------------------------------------------------------------------
274 // START Internal Functions
275 // --------------------------------------------------------------------
276
277 PlotKit.Layout.prototype._evaluateLimits = function() {
278 // take all values from all datasets and find max and min
279 var map = PlotKit.Base.map;
280 var items = PlotKit.Base.items;
281 var itemgetter = MochiKit.Base.itemgetter;
282 var collapse = PlotKit.Base.collapse;
283 var listMin = MochiKit.Base.listMin;
284 var listMax = MochiKit.Base.listMax;
285 var isNil = MochiKit.Base.isUndefinedOrNull;
286
287
288 var all = collapse(map(itemgetter(1), items(this.datasets)));
289 if (isNil(this.options.xAxis)) {
290 if (this.options.xOriginIsZero)
291 this.minxval = 0;
292 else
293 this.minxval = listMin(map(parseFloat, map(itemgetter(0), all)));
294
295 this.maxxval = listMax(map(parseFloat, map(itemgetter(0), all)));
296 }
297 else {
298 this.minxval = this.options.xAxis[0];
299 this.maxxval = this.options.xAxis[1];
300 this.xscale = this.maxval - this.minxval;
301 }
302
303 if (isNil(this.options.yAxis)) {
304 if (this.options.yOriginIsZero)
305 this.minyval = 0;
306 else
307 this.minyval = listMin(map(parseFloat, map(itemgetter(1), all)));
308
309 this.maxyval = listMax(map(parseFloat, map(itemgetter(1), all)));
310 }
311 else {
312 this.minyval = this.options.yAxis[0];
313 this.maxyval = this.options.yAxis[1];
314 this.yscale = this.maxyval - this.minyval;
315 }
316
317 };
318
319 PlotKit.Layout.prototype._evaluateScales = function() {
320 var isNil = MochiKit.Base.isUndefinedOrNull;
321
322 this.xrange = this.maxxval - this.minxval;
323 if (this.xrange == 0)
324 this.xscale = 1.0;
325 else
326 this.xscale = 1/this.xrange;
327
328 this.yrange = this.maxyval - this.minyval;
329 if (this.yrange == 0)
330 this.yscale = 1.0;
331 else
332 this.yscale = 1/this.yrange;
333 };
334
335 PlotKit.Layout.prototype._uniqueXValues = function() {
336 var collapse = PlotKit.Base.collapse;
337 var map = PlotKit.Base.map;
338 var uniq = PlotKit.Base.uniq;
339 var getter = MochiKit.Base.itemgetter;
340 var items = PlotKit.Base.items;
341
342 var xvalues = map(parseFloat, map(getter(0), collapse(map(getter(1), items(this.datasets)))));
343 xvalues.sort(MochiKit.Base.compare);
344 return uniq(xvalues);
345 };
346
347 // Create the bars
348 PlotKit.Layout.prototype._evaluateBarCharts = function() {
349 var items = PlotKit.Base.items;
350
351 var setCount = items(this.datasets).length;
352
353 // work out how far separated values are
354 var xdelta = 10000000;
355 var xvalues = this._uniqueXValues();
356 for (var i = 1; i < xvalues.length; i++) {
357 xdelta = Math.min(Math.abs(xvalues[i] - xvalues[i-1]), xdelta);
358 }
359
360 var barWidth = 0;
361 var barWidthForSet = 0;
362 var barMargin = 0;
363 if (xvalues.length == 1) {
364 // note we have to do something smarter if we only plot one value
365 xdelta = 1.0;
366 this.xscale = 1.0;
367 this.minxval = xvalues[0];
368 barWidth = 1.0 * this.options.barWidthFillFraction;
369 barWidthForSet = barWidth/setCount;
370 barMargin = (1.0 - this.options.barWidthFillFraction)/2;
371 }
372 else {
373 // readjust xscale to fix with bar charts
374 if (this.xrange == 1) {
375 this.xscale = 0.5;
376 }
377 else if (this.xrange == 2) {
378 this.xscale = 1/3.0;
379 }
380 else {
381 this.xscale = (1.0 - xdelta/this.xrange)/this.xrange;
382 }
383 barWidth = xdelta * this.xscale * this.options.barWidthFillFraction;
384 barWidthForSet = barWidth / setCount;
385 barMargin = xdelta * this.xscale * (1.0 - this.options.barWidthFillFraction)/2;
386 }
387
388 this.minxdelta = xdelta; // need this for tick positions
389
390 // add all the rects
391 this.bars = new Array();
392 var i = 0;
393 for (var setName in this.datasets) {
394 var dataset = this.datasets[setName];
395 if (PlotKit.Base.isFuncLike(dataset)) continue;
396 for (var j = 0; j < dataset.length; j++) {
397 var item = dataset[j];
398 var rect = {
399 x: ((parseFloat(item[0]) - this.minxval) * this.xscale) + (i * barWidthForSet) + barMargin,
400 y: 1.0 - ((parseFloat(item[1]) - this.minyval) * this.yscale),
401 w: barWidthForSet,
402 h: ((parseFloat(item[1]) - this.minyval) * this.yscale),
403 xval: parseFloat(item[0]),
404 yval: parseFloat(item[1]),
405 name: setName
406 };
407 if ((rect.x >= 0.0) && (rect.x <= 1.0) &&
408 (rect.y >= 0.0) && (rect.y <= 1.0)) {
409 this.bars.push(rect);
410 }
411 }
412 i++;
413 }
414 };
415
416 // Create the horizontal bars
417 PlotKit.Layout.prototype._evaluateHorizBarCharts = function() {
418 var items = PlotKit.Base.items;
419
420 var setCount = items(this.datasets).length;
421
422 // work out how far separated values are
423 var xdelta = 10000000;
424 var xvalues = this._uniqueXValues();
425 for (var i = 1; i < xvalues.length; i++) {
426 xdelta = Math.min(Math.abs(xvalues[i] - xvalues[i-1]), xdelta);
427 }
428
429 var barWidth = 0;
430 var barWidthForSet = 0;
431 var barMargin = 0;
432
433 // work out how far each far each bar is separated
434 if (xvalues.length == 1) {
435 // do something smarter if we only plot one value
436 xdelta = 1.0;
437 this.xscale = 1.0;
438 this.minxval = xvalues[0];
439 barWidth = 1.0 * this.options.barWidthFillFraction;
440 barWidthForSet = barWidth/setCount;
441 barMargin = (1.0 - this.options.barWidthFillFraction)/2;
442 }
443 else {
444 // readjust yscale to fix with bar charts
445 this.xscale = (1.0 - xdelta/this.xrange)/this.xrange;
446 barWidth = xdelta * this.xscale * this.options.barWidthFillFraction;
447 barWidthForSet = barWidth / setCount;
448 barMargin = xdelta * this.xscale * (1.0 - this.options.barWidthFillFraction)/2;
449 }
450
451 this.minxdelta = xdelta; // need this for tick positions
452
453 // add all the rects
454 this.bars = new Array();
455 var i = 0;
456 for (var setName in this.datasets) {
457 var dataset = this.datasets[setName];
458 if (PlotKit.Base.isFuncLike(dataset)) continue;
459 for (var j = 0; j < dataset.length; j++) {
460 var item = dataset[j];
461 var rect = {
462 y: ((parseFloat(item[0]) - this.minxval) * this.xscale) + (i * barWidthForSet) + barMargin,
463 x: 0.0,
464 h: barWidthForSet,
465 w: ((parseFloat(item[1]) - this.minyval) * this.yscale),
466 xval: parseFloat(item[0]),
467 yval: parseFloat(item[1]),
468 name: setName
469 };
470
471 // limit the x, y values so they do not overdraw
472 if (rect.y <= 0.0) {
473 rect.y = 0.0;
474 }
475 if (rect.y >= 1.0) {
476 rect.y = 1.0;
477 }
478 if ((rect.x >= 0.0) && (rect.x <= 1.0)) {
479 this.bars.push(rect);
480 }
481 }
482 i++;
483 }
484 };
485
486
487 // Create the line charts
488 PlotKit.Layout.prototype._evaluateLineCharts = function() {
489 var items = PlotKit.Base.items;
490
491 var setCount = items(this.datasets).length;
492
493 // add all the rects
494 this.points = new Array();
495 var i = 0;
496 for (var setName in this.datasets) {
497 var dataset = this.datasets[setName];
498 if (PlotKit.Base.isFuncLike(dataset)) continue;
499 dataset.sort(function(a, b) { return compare(parseFloat(a[0]), parseFloat(b[0])); });
500 for (var j = 0; j < dataset.length; j++) {
501 var item = dataset[j];
502 var point = {
503 x: ((parseFloat(item[0]) - this.minxval) * this.xscale),
504 y: 1.0 - ((parseFloat(item[1]) - this.minyval) * this.yscale),
505 xval: parseFloat(item[0]),
506 yval: parseFloat(item[1]),
507 name: setName
508 };
509
510 // limit the x, y values so they do not overdraw
511 if (point.y <= 0.0) {
512 point.y = 0.0;
513 }
514 if (point.y >= 1.0) {
515 point.y = 1.0;
516 }
517 if ((point.x >= 0.0) && (point.x <= 1.0)) {
518 this.points.push(point);
519 }
520 }
521 i++;
522 }
523 };
524
525 // Create the pie charts
526 PlotKit.Layout.prototype._evaluatePieCharts = function() {
527 var items = PlotKit.Base.items;
528 var sum = MochiKit.Iter.sum;
529 var getter = MochiKit.Base.itemgetter;
530
531 var setCount = items(this.datasets).length;
532
533 // we plot the y values of the first dataset
534 var dataset = items(this.datasets)[0][1];
535 var total = sum(map(getter(1), dataset));
536
537 this.slices = new Array();
538 var currentAngle = 0.0;
539 for (var i = 0; i < dataset.length; i++) {
540 var fraction = dataset[i][1] / total;
541 var startAngle = currentAngle * Math.PI * 2;
542 var endAngle = (currentAngle + fraction) * Math.PI * 2;
543
544 var slice = {fraction: fraction,
545 xval: dataset[i][0],
546 yval: dataset[i][1],
547 startAngle: startAngle,
548 endAngle: endAngle
549 };
550 if (dataset[i][1] != 0) {
551 this.slices.push(slice);
552 }
553 currentAngle += fraction;
554 }
555 };
556
557 PlotKit.Layout.prototype._evaluateLineTicksForXAxis = function() {
558 var isNil = MochiKit.Base.isUndefinedOrNull;
559
560 if (this.options.xTicks) {
561 // we use use specified ticks with optional labels
562
563 this.xticks = new Array();
564 var makeTicks = function(tick) {
565 var label = tick.label;
566 if (isNil(label))
567 label = tick.v.toString();
568 var pos = this.xscale * (tick.v - this.minxval);
569 if ((pos >= 0.0) && (pos <= 1.0)) {
570 this.xticks.push([pos, label]);
571 }
572 };
573 MochiKit.Iter.forEach(this.options.xTicks, bind(makeTicks, this));
574 }
575 else if (this.options.xNumberOfTicks) {
576 // we use defined number of ticks as hint to auto generate
577 var xvalues = this._uniqueXValues();
578 var roughSeparation = this.xrange / this.options.xNumberOfTicks;
579 var tickCount = 0;
580
581 this.xticks = new Array();
582 for (var i = 0; i <= xvalues.length; i++) {
583 if ((xvalues[i] - this.minxval) >= (tickCount * roughSeparation)) {
584 var pos = this.xscale * (xvalues[i] - this.minxval);
585 if ((pos > 1.0) || (pos < 0.0))
586 continue;
587 this.xticks.push([pos, xvalues[i]]);
588 tickCount++;
589 }
590 if (tickCount > this.options.xNumberOfTicks)
591 break;
592 }
593 }
594 };
595
596 PlotKit.Layout.prototype._evaluateLineTicksForYAxis = function() {
597 var isNil = MochiKit.Base.isUndefinedOrNull;
598
599
600 if (this.options.yTicks) {
601 this.yticks = new Array();
602 var makeTicks = function(tick) {
603 var label = tick.label;
604 if (isNil(label))
605 label = tick.v.toString();
606 var pos = 1.0 - (this.yscale * (tick.v - this.minyval));
607 if ((pos >= 0.0) && (pos <= 1.0)) {
608 this.yticks.push([pos, label]);
609 }
610 };
611 MochiKit.Iter.forEach(this.options.yTicks, bind(makeTicks, this));
612 }
613 else if (this.options.yNumberOfTicks) {
614 // We use the optionally defined number of ticks as a guide
615 this.yticks = new Array();
616
617 // if we get this separation right, we'll have good looking graphs
618 var roundInt = PlotKit.Base.roundInterval;
619 var prec = this.options.yTickPrecision;
620 var roughSeparation = roundInt(this.yrange,
621 this.options.yNumberOfTicks, prec);
622
623 // round off each value of the y-axis to the precision
624 // eg. 1.3333 at precision 1 -> 1.3
625 for (var i = 0; i <= this.options.yNumberOfTicks; i++) {
626 var yval = this.minyval + (i * roughSeparation);
627 var pos = 1.0 - ((yval - this.minyval) * this.yscale);
628 if ((pos > 1.0) || (pos < 0.0))
629 continue;
630 this.yticks.push([pos, MochiKit.Format.roundToFixed(yval, prec)]);
631 }
632 }
633 };
634
635 PlotKit.Layout.prototype._evaluateLineTicks = function() {
636 this._evaluateLineTicksForXAxis();
637 this._evaluateLineTicksForYAxis();
638 };
639
640 PlotKit.Layout.prototype._evaluateBarTicks = function() {
641 this._evaluateLineTicks();
642 var centerInBar = function(tick) {
643 return [tick[0] + (this.minxdelta * this.xscale)/2, tick[1]];
644 };
645 this.xticks = MochiKit.Base.map(bind(centerInBar, this), this.xticks);
646
647 if (this.options.barOrientation == "horizontal") {
648 // swap scales
649 var tempticks = this.xticks;
650 this.xticks = this.yticks;
651 this.yticks = tempticks;
652
653 // we need to invert the "yaxis" (which is now the xaxis when drawn)
654 var invert = function(tick) {
655 return [1.0 - tick[0], tick[1]];
656 }
657 this.xticks = MochiKit.Base.map(invert, this.xticks);
658 }
659 };
660
661 PlotKit.Layout.prototype._evaluatePieTicks = function() {
662 var isNil = MochiKit.Base.isUndefinedOrNull;
663 var formatter = MochiKit.Format.numberFormatter("#%");
664
665 this.xticks = new Array();
666 if (this.options.xTicks) {
667 // make a lookup dict for x->slice values
668 var lookup = new Array();
669 for (var i = 0; i < this.slices.length; i++) {
670 lookup[this.slices[i].xval] = this.slices[i];
671 }
672
673 for (var i =0; i < this.options.xTicks.length; i++) {
674 var tick = this.options.xTicks[i];
675 var slice = lookup[tick.v];
676 var label = tick.label;
677 if (slice) {
678 if (isNil(label))
679 label = tick.v.toString();
680 label += " (" + formatter(slice.fraction) + ")";
681 this.xticks.push([tick.v, label]);
682 }
683 }
684 }
685 else {
686 // we make our own labels from all the slices
687 for (var i =0; i < this.slices.length; i++) {
688 var slice = this.slices[i];
689 var label = slice.xval + " (" + formatter(slice.fraction) + ")";
690 this.xticks.push([slice.xval, label]);
691 }
692 }
693 };
694
695 PlotKit.Layout.prototype._regenerateHitTestCache = function() {
696 this.hitTestCache.xvalues = this._uniqueXValues();
697 this.hitTestCache.xlookup = new Array();
698 this.hitTestCache.x2maxy = new Array();
699
700 var listMax = MochiKit.Base.listMax;
701 var itemgetter = MochiKit.Base.itemgetter;
702 var map = MochiKit.Base.map;
703
704 // generate a lookup table for x values to y values
705 var setNames = keys(this.datasets);
706 for (var i = 0; i < setNames.length; i++) {
707 var dataset = this.datasets[setNames[i]];
708 for (var j = 0; j < dataset.length; j++) {
709 var xval = dataset[j][0];
710 var yval = dataset[j][1];
711 if (this.hitTestCache.xlookup[xval])
712 this.hitTestCache.xlookup[xval].push([yval, setNames[i]]);
713 else
714 this.hitTestCache.xlookup[xval] = [[yval, setNames[i]]];
715 }
716 }
717
718 for (var x in this.hitTestCache.xlookup) {
719 var yvals = this.hitTestCache.xlookup[x];
720 this.hitTestCache.x2maxy[x] = listMax(map(itemgetter(0), yvals));
721 }
722
723
724 };
725
726 // --------------------------------------------------------------------
727 // END Internal Functions
728 // --------------------------------------------------------------------
729
730
731 // Namespace Iniitialisation
732
733 PlotKit.LayoutModule = {};
734 PlotKit.LayoutModule.Layout = PlotKit.Layout;
735
736 PlotKit.LayoutModule.EXPORT = [
737 "Layout"
738 ];
739
740 PlotKit.LayoutModule.EXPORT_OK = [];
741
742 PlotKit.LayoutModule.__new__ = function() {
743 var m = MochiKit.Base;
744
745 m.nameFunctions(this);
746
747 this.EXPORT_TAGS = {
748 ":common": this.EXPORT,
749 ":all": m.concat(this.EXPORT, this.EXPORT_OK)
750 };
751 };
752
753 PlotKit.LayoutModule.__new__();
754 MochiKit.Base._exportSymbols(this, PlotKit.LayoutModule);
755
756