Commit | Line | Data |
---|---|---|
6a1aa64f DV |
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 | ||
6a1aa64f DV |
127 | // -------------------------------------------------------------------- |
128 | // Evaluates the layout for the current data and style. | |
129 | // -------------------------------------------------------------------- | |
130 | ||
131 | PlotKit.Layout.prototype.evaluate = function() { | |
132 | this._evaluateLimits(); | |
133 | this._evaluateScales(); | |
9148174b | 134 | if (this.style == "line") { |
6a1aa64f DV |
135 | this._evaluateLineCharts(); |
136 | this._evaluateLineTicks(); | |
137 | } | |
6a1aa64f DV |
138 | }; |
139 | ||
140 | ||
141 | ||
6a1aa64f DV |
142 | |
143 | // -------------------------------------------------------------------- | |
144 | // START Internal Functions | |
145 | // -------------------------------------------------------------------- | |
146 | ||
147 | PlotKit.Layout.prototype._evaluateLimits = function() { | |
148 | // take all values from all datasets and find max and min | |
149 | var map = PlotKit.Base.map; | |
150 | var items = PlotKit.Base.items; | |
151 | var itemgetter = MochiKit.Base.itemgetter; | |
152 | var collapse = PlotKit.Base.collapse; | |
153 | var listMin = MochiKit.Base.listMin; | |
154 | var listMax = MochiKit.Base.listMax; | |
155 | var isNil = MochiKit.Base.isUndefinedOrNull; | |
156 | ||
157 | ||
158 | var all = collapse(map(itemgetter(1), items(this.datasets))); | |
159 | if (isNil(this.options.xAxis)) { | |
160 | if (this.options.xOriginIsZero) | |
161 | this.minxval = 0; | |
162 | else | |
163 | this.minxval = listMin(map(parseFloat, map(itemgetter(0), all))); | |
164 | ||
165 | this.maxxval = listMax(map(parseFloat, map(itemgetter(0), all))); | |
166 | } | |
167 | else { | |
168 | this.minxval = this.options.xAxis[0]; | |
169 | this.maxxval = this.options.xAxis[1]; | |
170 | this.xscale = this.maxval - this.minxval; | |
171 | } | |
172 | ||
173 | if (isNil(this.options.yAxis)) { | |
174 | if (this.options.yOriginIsZero) | |
175 | this.minyval = 0; | |
176 | else | |
177 | this.minyval = listMin(map(parseFloat, map(itemgetter(1), all))); | |
178 | ||
179 | this.maxyval = listMax(map(parseFloat, map(itemgetter(1), all))); | |
180 | } | |
181 | else { | |
182 | this.minyval = this.options.yAxis[0]; | |
183 | this.maxyval = this.options.yAxis[1]; | |
184 | this.yscale = this.maxyval - this.minyval; | |
185 | } | |
186 | ||
187 | }; | |
188 | ||
189 | PlotKit.Layout.prototype._evaluateScales = function() { | |
190 | var isNil = MochiKit.Base.isUndefinedOrNull; | |
191 | ||
192 | this.xrange = this.maxxval - this.minxval; | |
193 | if (this.xrange == 0) | |
194 | this.xscale = 1.0; | |
195 | else | |
196 | this.xscale = 1/this.xrange; | |
197 | ||
198 | this.yrange = this.maxyval - this.minyval; | |
199 | if (this.yrange == 0) | |
200 | this.yscale = 1.0; | |
201 | else | |
202 | this.yscale = 1/this.yrange; | |
203 | }; | |
204 | ||
205 | PlotKit.Layout.prototype._uniqueXValues = function() { | |
206 | var collapse = PlotKit.Base.collapse; | |
207 | var map = PlotKit.Base.map; | |
208 | var uniq = PlotKit.Base.uniq; | |
209 | var getter = MochiKit.Base.itemgetter; | |
210 | var items = PlotKit.Base.items; | |
211 | ||
212 | var xvalues = map(parseFloat, map(getter(0), collapse(map(getter(1), items(this.datasets))))); | |
213 | xvalues.sort(MochiKit.Base.compare); | |
214 | return uniq(xvalues); | |
215 | }; | |
216 | ||
6a1aa64f DV |
217 | |
218 | // Create the line charts | |
219 | PlotKit.Layout.prototype._evaluateLineCharts = function() { | |
220 | var items = PlotKit.Base.items; | |
221 | ||
222 | var setCount = items(this.datasets).length; | |
223 | ||
224 | // add all the rects | |
225 | this.points = new Array(); | |
226 | var i = 0; | |
227 | for (var setName in this.datasets) { | |
228 | var dataset = this.datasets[setName]; | |
229 | if (PlotKit.Base.isFuncLike(dataset)) continue; | |
230 | dataset.sort(function(a, b) { return compare(parseFloat(a[0]), parseFloat(b[0])); }); | |
231 | for (var j = 0; j < dataset.length; j++) { | |
232 | var item = dataset[j]; | |
233 | var point = { | |
234 | x: ((parseFloat(item[0]) - this.minxval) * this.xscale), | |
235 | y: 1.0 - ((parseFloat(item[1]) - this.minyval) * this.yscale), | |
236 | xval: parseFloat(item[0]), | |
237 | yval: parseFloat(item[1]), | |
238 | name: setName | |
239 | }; | |
240 | ||
241 | // limit the x, y values so they do not overdraw | |
242 | if (point.y <= 0.0) { | |
243 | point.y = 0.0; | |
244 | } | |
245 | if (point.y >= 1.0) { | |
246 | point.y = 1.0; | |
247 | } | |
248 | if ((point.x >= 0.0) && (point.x <= 1.0)) { | |
249 | this.points.push(point); | |
250 | } | |
251 | } | |
252 | i++; | |
253 | } | |
254 | }; | |
255 | ||
6a1aa64f DV |
256 | |
257 | PlotKit.Layout.prototype._evaluateLineTicksForXAxis = function() { | |
258 | var isNil = MochiKit.Base.isUndefinedOrNull; | |
259 | ||
260 | if (this.options.xTicks) { | |
261 | // we use use specified ticks with optional labels | |
262 | ||
263 | this.xticks = new Array(); | |
264 | var makeTicks = function(tick) { | |
265 | var label = tick.label; | |
266 | if (isNil(label)) | |
267 | label = tick.v.toString(); | |
268 | var pos = this.xscale * (tick.v - this.minxval); | |
269 | if ((pos >= 0.0) && (pos <= 1.0)) { | |
270 | this.xticks.push([pos, label]); | |
271 | } | |
272 | }; | |
273 | MochiKit.Iter.forEach(this.options.xTicks, bind(makeTicks, this)); | |
274 | } | |
275 | else if (this.options.xNumberOfTicks) { | |
276 | // we use defined number of ticks as hint to auto generate | |
277 | var xvalues = this._uniqueXValues(); | |
278 | var roughSeparation = this.xrange / this.options.xNumberOfTicks; | |
279 | var tickCount = 0; | |
280 | ||
281 | this.xticks = new Array(); | |
282 | for (var i = 0; i <= xvalues.length; i++) { | |
283 | if ((xvalues[i] - this.minxval) >= (tickCount * roughSeparation)) { | |
284 | var pos = this.xscale * (xvalues[i] - this.minxval); | |
285 | if ((pos > 1.0) || (pos < 0.0)) | |
286 | continue; | |
287 | this.xticks.push([pos, xvalues[i]]); | |
288 | tickCount++; | |
289 | } | |
290 | if (tickCount > this.options.xNumberOfTicks) | |
291 | break; | |
292 | } | |
293 | } | |
294 | }; | |
295 | ||
296 | PlotKit.Layout.prototype._evaluateLineTicksForYAxis = function() { | |
297 | var isNil = MochiKit.Base.isUndefinedOrNull; | |
298 | ||
299 | ||
300 | if (this.options.yTicks) { | |
301 | this.yticks = new Array(); | |
302 | var makeTicks = function(tick) { | |
303 | var label = tick.label; | |
304 | if (isNil(label)) | |
305 | label = tick.v.toString(); | |
306 | var pos = 1.0 - (this.yscale * (tick.v - this.minyval)); | |
307 | if ((pos >= 0.0) && (pos <= 1.0)) { | |
308 | this.yticks.push([pos, label]); | |
309 | } | |
310 | }; | |
311 | MochiKit.Iter.forEach(this.options.yTicks, bind(makeTicks, this)); | |
312 | } | |
6a1aa64f DV |
313 | }; |
314 | ||
315 | PlotKit.Layout.prototype._evaluateLineTicks = function() { | |
316 | this._evaluateLineTicksForXAxis(); | |
317 | this._evaluateLineTicksForYAxis(); | |
318 | }; | |
319 | ||
6a1aa64f DV |
320 | |
321 | // -------------------------------------------------------------------- | |
322 | // END Internal Functions | |
323 | // -------------------------------------------------------------------- | |
324 | ||
325 | ||
326 | // Namespace Iniitialisation | |
327 | ||
328 | PlotKit.LayoutModule = {}; | |
329 | PlotKit.LayoutModule.Layout = PlotKit.Layout; | |
330 | ||
331 | PlotKit.LayoutModule.EXPORT = [ | |
332 | "Layout" | |
333 | ]; | |
334 | ||
335 | PlotKit.LayoutModule.EXPORT_OK = []; | |
336 | ||
337 | PlotKit.LayoutModule.__new__ = function() { | |
338 | var m = MochiKit.Base; | |
339 | ||
340 | m.nameFunctions(this); | |
341 | ||
342 | this.EXPORT_TAGS = { | |
343 | ":common": this.EXPORT, | |
344 | ":all": m.concat(this.EXPORT, this.EXPORT_OK) | |
345 | }; | |
346 | }; | |
347 | ||
348 | PlotKit.LayoutModule.__new__(); | |
349 | MochiKit.Base._exportSymbols(this, PlotKit.LayoutModule); | |
350 | ||
351 |