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.
11 Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net>
12 For use under the BSD license. <http://www.liquidx.net/plotkit>
17 if (typeof(PlotKit
.Base
) == 'undefined')
23 throw "PlotKit.Layout depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.Base"
26 // --------------------------------------------------------------------
27 // Start of Layout definition
28 // --------------------------------------------------------------------
30 if (typeof(PlotKit
.Layout
) == 'undefined') {
34 PlotKit
.Layout
.NAME
= "PlotKit.Layout";
35 PlotKit
.Layout
.VERSION
= PlotKit
.VERSION
;
37 PlotKit
.Layout
.__repr__
= function() {
38 return "[" + this.NAME
+ " " + this.VERSION
+ "]";
41 PlotKit
.Layout
.toString
= function() {
42 return this.__repr__();
45 PlotKit
.Layout
.valid_styles
= ["bar", "line", "pie", "point"];
47 // --------------------------------------------------------------------
48 // Start of Layout definition
49 // --------------------------------------------------------------------
51 PlotKit
.Layout
= function(style
, 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.)
69 // valid external options : TODO: input verification
71 MochiKit
.Base
.update(this.options
, options
? options
: {});
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
;
83 this.xscale
= null; // val -> pos factor (eg, xval * xscale = xpos)
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
;
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
101 this.xticks
= new Array();
102 this.yticks
= new Array();
105 this.datasets
= new Array();
110 this.hitTestCache
= {x2maxy
: null};
114 // --------------------------------------------------------------------
115 // Dataset Manipulation
116 // --------------------------------------------------------------------
119 PlotKit
.Layout
.prototype.addDataset
= function(setname
, set_xy
) {
120 this.datasets
[setname
] = set_xy
;
123 PlotKit
.Layout
.prototype.removeDataset
= function(setname
, set_xy
) {
124 delete this.datasets
[setname
];
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
;
139 var rows
= tableElement
.tBodies
[0].rows
;
140 var data
= new Array();
141 var labels
= new Array();
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
])))]);
148 labels
.push({v
: parseFloat(strip(scrapeText(rows
[i
].cells
[xcol
]))),
149 label
: strip(scrapeText(rows
[i
].cells
[lcol
]))});
152 this.addDataset(name
, data
);
154 this.options
.xTicks
= labels
;
161 // --------------------------------------------------------------------
162 // Evaluates the layout for the current data and style.
163 // --------------------------------------------------------------------
165 PlotKit
.Layout
.prototype.evaluate
= function() {
166 this._evaluateLimits();
167 this._evaluateScales();
168 if (this.style
== "line") {
169 this._evaluateLineCharts();
170 this._evaluateLineTicks();
177 // --------------------------------------------------------------------
178 // START Internal Functions
179 // --------------------------------------------------------------------
181 PlotKit
.Layout
.prototype._evaluateLimits
= function() {
182 // take all values from all datasets and find max and min
183 var map
= PlotKit
.Base
.map
;
184 var items
= PlotKit
.Base
.items
;
185 var itemgetter
= MochiKit
.Base
.itemgetter
;
186 var collapse
= PlotKit
.Base
.collapse
;
187 var listMin
= MochiKit
.Base
.listMin
;
188 var listMax
= MochiKit
.Base
.listMax
;
189 var isNil
= MochiKit
.Base
.isUndefinedOrNull
;
192 var all
= collapse(map(itemgetter(1), items(this.datasets
)));
193 if (isNil(this.options
.xAxis
)) {
194 if (this.options
.xOriginIsZero
)
197 this.minxval
= listMin(map(parseFloat
, map(itemgetter(0), all
)));
199 this.maxxval
= listMax(map(parseFloat
, map(itemgetter(0), all
)));
202 this.minxval
= this.options
.xAxis
[0];
203 this.maxxval
= this.options
.xAxis
[1];
204 this.xscale
= this.maxval
- this.minxval
;
207 if (isNil(this.options
.yAxis
)) {
208 if (this.options
.yOriginIsZero
)
211 this.minyval
= listMin(map(parseFloat
, map(itemgetter(1), all
)));
213 this.maxyval
= listMax(map(parseFloat
, map(itemgetter(1), all
)));
216 this.minyval
= this.options
.yAxis
[0];
217 this.maxyval
= this.options
.yAxis
[1];
218 this.yscale
= this.maxyval
- this.minyval
;
223 PlotKit
.Layout
.prototype._evaluateScales
= function() {
224 var isNil
= MochiKit
.Base
.isUndefinedOrNull
;
226 this.xrange
= this.maxxval
- this.minxval
;
227 if (this.xrange
== 0)
230 this.xscale
= 1/this.xrange
;
232 this.yrange
= this.maxyval
- this.minyval
;
233 if (this.yrange
== 0)
236 this.yscale
= 1/this.yrange
;
239 PlotKit
.Layout
.prototype._uniqueXValues
= function() {
240 var collapse
= PlotKit
.Base
.collapse
;
241 var map
= PlotKit
.Base
.map
;
242 var uniq
= PlotKit
.Base
.uniq
;
243 var getter
= MochiKit
.Base
.itemgetter
;
244 var items
= PlotKit
.Base
.items
;
246 var xvalues
= map(parseFloat
, map(getter(0), collapse(map(getter(1), items(this.datasets
)))));
247 xvalues
.sort(MochiKit
.Base
.compare
);
248 return uniq(xvalues
);
252 // Create the line charts
253 PlotKit
.Layout
.prototype._evaluateLineCharts
= function() {
254 var items
= PlotKit
.Base
.items
;
256 var setCount
= items(this.datasets
).length
;
259 this.points
= new Array();
261 for (var setName
in this.datasets
) {
262 var dataset
= this.datasets
[setName
];
263 if (PlotKit
.Base
.isFuncLike(dataset
)) continue;
264 dataset
.sort(function(a
, b
) { return compare(parseFloat(a
[0]), parseFloat(b
[0])); });
265 for (var j
= 0; j
< dataset
.length
; j
++) {
266 var item
= dataset
[j
];
268 x
: ((parseFloat(item
[0]) - this.minxval
) * this.xscale
),
269 y
: 1.0 - ((parseFloat(item
[1]) - this.minyval
) * this.yscale
),
270 xval
: parseFloat(item
[0]),
271 yval
: parseFloat(item
[1]),
275 // limit the x, y values so they do not overdraw
276 if (point
.y
<= 0.0) {
279 if (point
.y
>= 1.0) {
282 if ((point
.x
>= 0.0) && (point
.x
<= 1.0)) {
283 this.points
.push(point
);
291 PlotKit
.Layout
.prototype._evaluateLineTicksForXAxis
= function() {
292 var isNil
= MochiKit
.Base
.isUndefinedOrNull
;
294 if (this.options
.xTicks
) {
295 // we use use specified ticks with optional labels
297 this.xticks
= new Array();
298 var makeTicks
= function(tick
) {
299 var label
= tick
.label
;
301 label
= tick
.v
.toString();
302 var pos
= this.xscale
* (tick
.v
- this.minxval
);
303 if ((pos
>= 0.0) && (pos
<= 1.0)) {
304 this.xticks
.push([pos
, label
]);
307 MochiKit
.Iter
.forEach(this.options
.xTicks
, bind(makeTicks
, this));
309 else if (this.options
.xNumberOfTicks
) {
310 // we use defined number of ticks as hint to auto generate
311 var xvalues
= this._uniqueXValues();
312 var roughSeparation
= this.xrange
/ this.options
.xNumberOfTicks
;
315 this.xticks
= new Array();
316 for (var i
= 0; i
<= xvalues
.length
; i
++) {
317 if ((xvalues
[i
] - this.minxval
) >= (tickCount
* roughSeparation
)) {
318 var pos
= this.xscale
* (xvalues
[i
] - this.minxval
);
319 if ((pos
> 1.0) || (pos
< 0.0))
321 this.xticks
.push([pos
, xvalues
[i
]]);
324 if (tickCount
> this.options
.xNumberOfTicks
)
330 PlotKit
.Layout
.prototype._evaluateLineTicksForYAxis
= function() {
331 var isNil
= MochiKit
.Base
.isUndefinedOrNull
;
334 if (this.options
.yTicks
) {
335 this.yticks
= new Array();
336 var makeTicks
= function(tick
) {
337 var label
= tick
.label
;
339 label
= tick
.v
.toString();
340 var pos
= 1.0 - (this.yscale
* (tick
.v
- this.minyval
));
341 if ((pos
>= 0.0) && (pos
<= 1.0)) {
342 this.yticks
.push([pos
, label
]);
345 MochiKit
.Iter
.forEach(this.options
.yTicks
, bind(makeTicks
, this));
347 else if (this.options
.yNumberOfTicks
) {
348 // We use the optionally defined number of ticks as a guide
349 this.yticks
= new Array();
351 // if we get this separation right, we'll have good looking graphs
352 var roundInt
= PlotKit
.Base
.roundInterval
;
353 var prec
= this.options
.yTickPrecision
;
354 var roughSeparation
= roundInt(this.yrange
,
355 this.options
.yNumberOfTicks
, prec
);
357 // round off each value of the y-axis to the precision
358 // eg. 1.3333 at precision 1 -> 1.3
359 for (var i
= 0; i
<= this.options
.yNumberOfTicks
; i
++) {
360 var yval
= this.minyval
+ (i
* roughSeparation
);
361 var pos
= 1.0 - ((yval
- this.minyval
) * this.yscale
);
362 if ((pos
> 1.0) || (pos
< 0.0))
364 this.yticks
.push([pos
, MochiKit
.Format
.roundToFixed(yval
, prec
)]);
369 PlotKit
.Layout
.prototype._evaluateLineTicks
= function() {
370 this._evaluateLineTicksForXAxis();
371 this._evaluateLineTicksForYAxis();
375 // --------------------------------------------------------------------
376 // END Internal Functions
377 // --------------------------------------------------------------------
380 // Namespace Iniitialisation
382 PlotKit
.LayoutModule
= {};
383 PlotKit
.LayoutModule
.Layout
= PlotKit
.Layout
;
385 PlotKit
.LayoutModule
.EXPORT
= [
389 PlotKit
.LayoutModule
.EXPORT_OK
= [];
391 PlotKit
.LayoutModule
.__new__
= function() {
392 var m
= MochiKit
.Base
;
394 m
.nameFunctions(this);
397 ":common": this.EXPORT
,
398 ":all": m
.concat(this.EXPORT
, this.EXPORT_OK
)
402 PlotKit
.LayoutModule
.__new__();
403 MochiKit
.Base
._exportSymbols(this, PlotKit
.LayoutModule
);