keigan 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/NEWS.markdown +10 -0
- data/README.markdown +49 -2
- data/Rakefile +3 -2
- data/TODO.markdown +9 -0
- data/bin/keigan +2 -1
- data/keigan.gemspec +3 -5
- data/lib/keigan/cli/application.rb +215 -0
- data/lib/keigan/cli.rb +32 -0
- data/lib/keigan/web/application.rb +66 -0
- data/lib/keigan/web/public/css/layout.css +380 -0
- data/lib/keigan/web/public/images/risks_by_severity.png +0 -0
- data/lib/keigan/web/public/js/bluff-min.js +1 -0
- data/lib/keigan/web/public/js/bluff-src.js +2990 -0
- data/lib/keigan/web/public/js/excanvas.js +35 -0
- data/lib/keigan/web/public/js/js-class.js +1 -0
- data/lib/keigan/web/views/header.haml +16 -0
- data/lib/keigan/web/views/host.haml +47 -0
- data/lib/keigan/web/views/hosts.haml +34 -0
- data/lib/keigan/web/views/index.haml +72 -0
- data/lib/keigan/web/views/items.haml +34 -0
- data/lib/keigan/web/views/layout.haml +19 -0
- data/lib/keigan/web/views/not_implemented.haml +2 -0
- data/lib/keigan/web/views/report.haml +8 -0
- data/lib/keigan/web/views/reports.haml +9 -0
- data/lib/keigan/web.rb +32 -0
- data/lib/keigan.rb +16 -1
- metadata +72 -3
@@ -0,0 +1,2990 @@
|
|
1
|
+
/**
|
2
|
+
* Bluff - beautiful graphs in JavaScript
|
3
|
+
* ======================================
|
4
|
+
*
|
5
|
+
* Get the latest version and docs at http://bluff.jcoglan.com
|
6
|
+
* Based on Gruff by Geoffrey Grosenbach: http://github.com/topfunky/gruff
|
7
|
+
*
|
8
|
+
* Copyright (C) 2008-2010 James Coglan
|
9
|
+
*
|
10
|
+
* Released under the MIT license and the GPL v2.
|
11
|
+
* http://www.opensource.org/licenses/mit-license.php
|
12
|
+
* http://www.gnu.org/licenses/gpl-2.0.txt
|
13
|
+
**/
|
14
|
+
|
15
|
+
Bluff = {
|
16
|
+
// This is the version of Bluff you are using.
|
17
|
+
VERSION: '0.3.6',
|
18
|
+
|
19
|
+
array: function(list) {
|
20
|
+
if (list.length === undefined) return [list];
|
21
|
+
var ary = [], i = list.length;
|
22
|
+
while (i--) ary[i] = list[i];
|
23
|
+
return ary;
|
24
|
+
},
|
25
|
+
|
26
|
+
array_new: function(length, filler) {
|
27
|
+
var ary = [];
|
28
|
+
while (length--) ary.push(filler);
|
29
|
+
return ary;
|
30
|
+
},
|
31
|
+
|
32
|
+
each: function(list, block, context) {
|
33
|
+
for (var i = 0, n = list.length; i < n; i++) {
|
34
|
+
block.call(context || null, list[i], i);
|
35
|
+
}
|
36
|
+
},
|
37
|
+
|
38
|
+
index: function(list, needle) {
|
39
|
+
for (var i = 0, n = list.length; i < n; i++) {
|
40
|
+
if (list[i] === needle) return i;
|
41
|
+
}
|
42
|
+
return -1;
|
43
|
+
},
|
44
|
+
|
45
|
+
keys: function(object) {
|
46
|
+
var ary = [], key;
|
47
|
+
for (key in object) ary.push(key);
|
48
|
+
return ary;
|
49
|
+
},
|
50
|
+
|
51
|
+
map: function(list, block, context) {
|
52
|
+
var results = [];
|
53
|
+
this.each(list, function(item) {
|
54
|
+
results.push(block.call(context || null, item));
|
55
|
+
});
|
56
|
+
return results;
|
57
|
+
},
|
58
|
+
|
59
|
+
reverse_each: function(list, block, context) {
|
60
|
+
var i = list.length;
|
61
|
+
while (i--) block.call(context || null, list[i], i);
|
62
|
+
},
|
63
|
+
|
64
|
+
sum: function(list) {
|
65
|
+
var sum = 0, i = list.length;
|
66
|
+
while (i--) sum += list[i];
|
67
|
+
return sum;
|
68
|
+
},
|
69
|
+
|
70
|
+
Mini: {}
|
71
|
+
};
|
72
|
+
|
73
|
+
Bluff.Base = new JS.Class({
|
74
|
+
extend: {
|
75
|
+
// Draw extra lines showing where the margins and text centers are
|
76
|
+
DEBUG: false,
|
77
|
+
|
78
|
+
// Used for navigating the array of data to plot
|
79
|
+
DATA_LABEL_INDEX: 0,
|
80
|
+
DATA_VALUES_INDEX: 1,
|
81
|
+
DATA_COLOR_INDEX: 2,
|
82
|
+
|
83
|
+
// Space around text elements. Mostly used for vertical spacing
|
84
|
+
LEGEND_MARGIN: 20,
|
85
|
+
TITLE_MARGIN: 20,
|
86
|
+
LABEL_MARGIN: 10,
|
87
|
+
DEFAULT_MARGIN: 20,
|
88
|
+
|
89
|
+
DEFAULT_TARGET_WIDTH: 800,
|
90
|
+
|
91
|
+
THOUSAND_SEPARATOR: ','
|
92
|
+
},
|
93
|
+
|
94
|
+
// Blank space above the graph
|
95
|
+
top_margin: null,
|
96
|
+
|
97
|
+
// Blank space below the graph
|
98
|
+
bottom_margin: null,
|
99
|
+
|
100
|
+
// Blank space to the right of the graph
|
101
|
+
right_margin: null,
|
102
|
+
|
103
|
+
// Blank space to the left of the graph
|
104
|
+
left_margin: null,
|
105
|
+
|
106
|
+
// Blank space below the title
|
107
|
+
title_margin: null,
|
108
|
+
|
109
|
+
// Blank space below the legend
|
110
|
+
legend_margin: null,
|
111
|
+
|
112
|
+
// A hash of names for the individual columns, where the key is the array
|
113
|
+
// index for the column this label represents.
|
114
|
+
//
|
115
|
+
// Not all columns need to be named.
|
116
|
+
//
|
117
|
+
// Example: {0: 2005, 3: 2006, 5: 2007, 7: 2008}
|
118
|
+
labels: null,
|
119
|
+
|
120
|
+
// Used internally for spacing.
|
121
|
+
//
|
122
|
+
// By default, labels are centered over the point they represent.
|
123
|
+
center_labels_over_point: null,
|
124
|
+
|
125
|
+
// Used internally for horizontal graph types.
|
126
|
+
has_left_labels: null,
|
127
|
+
|
128
|
+
// A label for the bottom of the graph
|
129
|
+
x_axis_label: null,
|
130
|
+
|
131
|
+
// A label for the left side of the graph
|
132
|
+
y_axis_label: null,
|
133
|
+
|
134
|
+
// x_axis_increment: null,
|
135
|
+
|
136
|
+
// Manually set increment of the horizontal marking lines
|
137
|
+
y_axis_increment: null,
|
138
|
+
|
139
|
+
// Get or set the list of colors that will be used to draw the bars or lines.
|
140
|
+
colors: null,
|
141
|
+
|
142
|
+
// The large title of the graph displayed at the top
|
143
|
+
title: null,
|
144
|
+
|
145
|
+
// Font used for titles, labels, etc.
|
146
|
+
font: null,
|
147
|
+
|
148
|
+
font_color: null,
|
149
|
+
|
150
|
+
// Prevent drawing of line markers
|
151
|
+
hide_line_markers: null,
|
152
|
+
|
153
|
+
// Prevent drawing of the legend
|
154
|
+
hide_legend: null,
|
155
|
+
|
156
|
+
// Prevent drawing of the title
|
157
|
+
hide_title: null,
|
158
|
+
|
159
|
+
// Prevent drawing of line numbers
|
160
|
+
hide_line_numbers: null,
|
161
|
+
|
162
|
+
// Message shown when there is no data. Fits up to 20 characters. Defaults
|
163
|
+
// to "No Data."
|
164
|
+
no_data_message: null,
|
165
|
+
|
166
|
+
// The font size of the large title at the top of the graph
|
167
|
+
title_font_size: null,
|
168
|
+
|
169
|
+
// Optionally set the size of the font. Based on an 800x600px graph.
|
170
|
+
// Default is 20.
|
171
|
+
//
|
172
|
+
// Will be scaled down if graph is smaller than 800px wide.
|
173
|
+
legend_font_size: null,
|
174
|
+
|
175
|
+
// The font size of the labels around the graph
|
176
|
+
marker_font_size: null,
|
177
|
+
|
178
|
+
// The color of the auxiliary lines
|
179
|
+
marker_color: null,
|
180
|
+
|
181
|
+
// The number of horizontal lines shown for reference
|
182
|
+
marker_count: null,
|
183
|
+
|
184
|
+
// You can manually set a minimum value instead of having the values
|
185
|
+
// guessed for you.
|
186
|
+
//
|
187
|
+
// Set it after you have given all your data to the graph object.
|
188
|
+
minimum_value: null,
|
189
|
+
|
190
|
+
// You can manually set a maximum value, such as a percentage-based graph
|
191
|
+
// that always goes to 100.
|
192
|
+
//
|
193
|
+
// If you use this, you must set it after you have given all your data to
|
194
|
+
// the graph object.
|
195
|
+
maximum_value: null,
|
196
|
+
|
197
|
+
// Set to false if you don't want the data to be sorted with largest avg
|
198
|
+
// values at the back.
|
199
|
+
sort: null,
|
200
|
+
|
201
|
+
// Experimental
|
202
|
+
additional_line_values: null,
|
203
|
+
|
204
|
+
// Experimental
|
205
|
+
stacked: null,
|
206
|
+
|
207
|
+
// Optionally set the size of the colored box by each item in the legend.
|
208
|
+
// Default is 20.0
|
209
|
+
//
|
210
|
+
// Will be scaled down if graph is smaller than 800px wide.
|
211
|
+
legend_box_size: null,
|
212
|
+
|
213
|
+
// Set to true to enable tooltip displays
|
214
|
+
tooltips: false,
|
215
|
+
|
216
|
+
// If one numerical argument is given, the graph is drawn at 4/3 ratio
|
217
|
+
// according to the given width (800 results in 800x600, 400 gives 400x300,
|
218
|
+
// etc.).
|
219
|
+
//
|
220
|
+
// Or, send a geometry string for other ratios ('800x400', '400x225').
|
221
|
+
initialize: function(renderer, target_width) {
|
222
|
+
this._d = new Bluff.Renderer(renderer);
|
223
|
+
target_width = target_width || this.klass.DEFAULT_TARGET_WIDTH;
|
224
|
+
|
225
|
+
var geo;
|
226
|
+
|
227
|
+
if (typeof target_width !== 'number') {
|
228
|
+
geo = target_width.split('x');
|
229
|
+
this._columns = parseFloat(geo[0]);
|
230
|
+
this._rows = parseFloat(geo[1]);
|
231
|
+
} else {
|
232
|
+
this._columns = parseFloat(target_width);
|
233
|
+
this._rows = this._columns * 0.75;
|
234
|
+
}
|
235
|
+
|
236
|
+
this.initialize_ivars();
|
237
|
+
|
238
|
+
this._reset_themes();
|
239
|
+
this.theme_keynote();
|
240
|
+
|
241
|
+
this._listeners = {};
|
242
|
+
},
|
243
|
+
|
244
|
+
// Set instance variables for this object.
|
245
|
+
//
|
246
|
+
// Subclasses can override this, call super, then set values separately.
|
247
|
+
//
|
248
|
+
// This makes it possible to set defaults in a subclass but still allow
|
249
|
+
// developers to change this values in their program.
|
250
|
+
initialize_ivars: function() {
|
251
|
+
// Internal for calculations
|
252
|
+
this._raw_columns = 800;
|
253
|
+
this._raw_rows = 800 * (this._rows/this._columns);
|
254
|
+
this._column_count = 0;
|
255
|
+
this.marker_count = null;
|
256
|
+
this.maximum_value = this.minimum_value = null;
|
257
|
+
this._has_data = false;
|
258
|
+
this._data = [];
|
259
|
+
this.labels = {};
|
260
|
+
this._labels_seen = {};
|
261
|
+
this.sort = true;
|
262
|
+
this.title = null;
|
263
|
+
|
264
|
+
this._scale = this._columns / this._raw_columns;
|
265
|
+
|
266
|
+
this.marker_font_size = 21.0;
|
267
|
+
this.legend_font_size = 20.0;
|
268
|
+
this.title_font_size = 36.0;
|
269
|
+
|
270
|
+
this.top_margin = this.bottom_margin =
|
271
|
+
this.left_margin = this.right_margin = this.klass.DEFAULT_MARGIN;
|
272
|
+
|
273
|
+
this.legend_margin = this.klass.LEGEND_MARGIN;
|
274
|
+
this.title_margin = this.klass.TITLE_MARGIN;
|
275
|
+
|
276
|
+
this.legend_box_size = 20.0;
|
277
|
+
|
278
|
+
this.no_data_message = "No Data";
|
279
|
+
|
280
|
+
this.hide_line_markers = this.hide_legend = this.hide_title = this.hide_line_numbers = false;
|
281
|
+
this.center_labels_over_point = true;
|
282
|
+
this.has_left_labels = false;
|
283
|
+
|
284
|
+
this.additional_line_values = [];
|
285
|
+
this._additional_line_colors = [];
|
286
|
+
this._theme_options = {};
|
287
|
+
|
288
|
+
this.x_axis_label = this.y_axis_label = null;
|
289
|
+
this.y_axis_increment = null;
|
290
|
+
this.stacked = null;
|
291
|
+
this._norm_data = null;
|
292
|
+
},
|
293
|
+
|
294
|
+
// Sets the top, bottom, left and right margins to +margin+.
|
295
|
+
set_margins: function(margin) {
|
296
|
+
this.top_margin = this.left_margin = this.right_margin = this.bottom_margin = margin;
|
297
|
+
},
|
298
|
+
|
299
|
+
// Sets the font for graph text to the font at +font_path+.
|
300
|
+
set_font: function(font_path) {
|
301
|
+
this.font = font_path;
|
302
|
+
this._d.font = this.font;
|
303
|
+
},
|
304
|
+
|
305
|
+
// Add a color to the list of available colors for lines.
|
306
|
+
//
|
307
|
+
// Example:
|
308
|
+
// add_color('#c0e9d3')
|
309
|
+
add_color: function(colorname) {
|
310
|
+
this.colors.push(colorname);
|
311
|
+
},
|
312
|
+
|
313
|
+
// Replace the entire color list with a new array of colors. Also
|
314
|
+
// aliased as the colors= setter method.
|
315
|
+
//
|
316
|
+
// If you specify fewer colors than the number of datasets you intend
|
317
|
+
// to draw, 'increment_color' will cycle through the array, reusing
|
318
|
+
// colors as needed.
|
319
|
+
//
|
320
|
+
// Note that (as with the 'set_theme' method), you should set up the color
|
321
|
+
// list before you send your data (via the 'data' method). Calls to the
|
322
|
+
// 'data' method made prior to this call will use whatever color scheme
|
323
|
+
// was in place at the time data was called.
|
324
|
+
//
|
325
|
+
// Example:
|
326
|
+
// replace_colors ['#cc99cc', '#d9e043', '#34d8a2']
|
327
|
+
replace_colors: function(color_list) {
|
328
|
+
this.colors = color_list || [];
|
329
|
+
this._color_index = 0;
|
330
|
+
},
|
331
|
+
|
332
|
+
// You can set a theme manually. Assign a hash to this method before you
|
333
|
+
// send your data.
|
334
|
+
//
|
335
|
+
// graph.set_theme({
|
336
|
+
// colors: ['orange', 'purple', 'green', 'white', 'red'],
|
337
|
+
// marker_color: 'blue',
|
338
|
+
// background_colors: ['black', 'grey']
|
339
|
+
// })
|
340
|
+
//
|
341
|
+
// background_image: 'squirrel.png' is also possible.
|
342
|
+
//
|
343
|
+
// (Or hopefully something better looking than that.)
|
344
|
+
//
|
345
|
+
set_theme: function(options) {
|
346
|
+
this._reset_themes();
|
347
|
+
|
348
|
+
this._theme_options = {
|
349
|
+
colors: ['black', 'white'],
|
350
|
+
additional_line_colors: [],
|
351
|
+
marker_color: 'white',
|
352
|
+
font_color: 'black',
|
353
|
+
background_colors: null,
|
354
|
+
background_image: null
|
355
|
+
};
|
356
|
+
for (var key in options) this._theme_options[key] = options[key];
|
357
|
+
|
358
|
+
this.colors = this._theme_options.colors;
|
359
|
+
this.marker_color = this._theme_options.marker_color;
|
360
|
+
this.font_color = this._theme_options.font_color || this.marker_color;
|
361
|
+
this._additional_line_colors = this._theme_options.additional_line_colors;
|
362
|
+
|
363
|
+
this._render_background();
|
364
|
+
},
|
365
|
+
|
366
|
+
// Set just the background colors
|
367
|
+
set_background: function(options) {
|
368
|
+
if (options.colors)
|
369
|
+
this._theme_options.background_colors = options.colors;
|
370
|
+
if (options.image)
|
371
|
+
this._theme_options.background_image = options.image;
|
372
|
+
this._render_background();
|
373
|
+
},
|
374
|
+
|
375
|
+
// A color scheme similar to the popular presentation software.
|
376
|
+
theme_keynote: function() {
|
377
|
+
// Colors
|
378
|
+
this._blue = '#6886B4';
|
379
|
+
this._yellow = '#FDD84E';
|
380
|
+
this._green = '#72AE6E';
|
381
|
+
this._red = '#D1695E';
|
382
|
+
this._purple = '#8A6EAF';
|
383
|
+
this._orange = '#EFAA43';
|
384
|
+
this._white = 'white';
|
385
|
+
this.colors = [this._yellow, this._blue, this._green, this._red, this._purple, this._orange, this._white];
|
386
|
+
|
387
|
+
this.set_theme({
|
388
|
+
colors: this.colors,
|
389
|
+
marker_color: 'white',
|
390
|
+
font_color: 'white',
|
391
|
+
background_colors: ['black', '#4a465a']
|
392
|
+
});
|
393
|
+
},
|
394
|
+
|
395
|
+
// A color scheme plucked from the colors on the popular usability blog.
|
396
|
+
theme_37signals: function() {
|
397
|
+
// Colors
|
398
|
+
this._green = '#339933';
|
399
|
+
this._purple = '#cc99cc';
|
400
|
+
this._blue = '#336699';
|
401
|
+
this._yellow = '#FFF804';
|
402
|
+
this._red = '#ff0000';
|
403
|
+
this._orange = '#cf5910';
|
404
|
+
this._black = 'black';
|
405
|
+
this.colors = [this._yellow, this._blue, this._green, this._red, this._purple, this._orange, this._black];
|
406
|
+
|
407
|
+
this.set_theme({
|
408
|
+
colors: this.colors,
|
409
|
+
marker_color: 'black',
|
410
|
+
font_color: 'black',
|
411
|
+
background_colors: ['#d1edf5', 'white']
|
412
|
+
});
|
413
|
+
},
|
414
|
+
|
415
|
+
// A color scheme from the colors used on the 2005 Rails keynote
|
416
|
+
// presentation at RubyConf.
|
417
|
+
theme_rails_keynote: function() {
|
418
|
+
// Colors
|
419
|
+
this._green = '#00ff00';
|
420
|
+
this._grey = '#333333';
|
421
|
+
this._orange = '#ff5d00';
|
422
|
+
this._red = '#f61100';
|
423
|
+
this._white = 'white';
|
424
|
+
this._light_grey = '#999999';
|
425
|
+
this._black = 'black';
|
426
|
+
this.colors = [this._green, this._grey, this._orange, this._red, this._white, this._light_grey, this._black];
|
427
|
+
|
428
|
+
this.set_theme({
|
429
|
+
colors: this.colors,
|
430
|
+
marker_color: 'white',
|
431
|
+
font_color: 'white',
|
432
|
+
background_colors: ['#0083a3', '#0083a3']
|
433
|
+
});
|
434
|
+
},
|
435
|
+
|
436
|
+
// A color scheme similar to that used on the popular podcast site.
|
437
|
+
theme_odeo: function() {
|
438
|
+
// Colors
|
439
|
+
this._grey = '#202020';
|
440
|
+
this._white = 'white';
|
441
|
+
this._dark_pink = '#a21764';
|
442
|
+
this._green = '#8ab438';
|
443
|
+
this._light_grey = '#999999';
|
444
|
+
this._dark_blue = '#3a5b87';
|
445
|
+
this._black = 'black';
|
446
|
+
this.colors = [this._grey, this._white, this._dark_blue, this._dark_pink, this._green, this._light_grey, this._black];
|
447
|
+
|
448
|
+
this.set_theme({
|
449
|
+
colors: this.colors,
|
450
|
+
marker_color: 'white',
|
451
|
+
font_color: 'white',
|
452
|
+
background_colors: ['#ff47a4', '#ff1f81']
|
453
|
+
});
|
454
|
+
},
|
455
|
+
|
456
|
+
// A pastel theme
|
457
|
+
theme_pastel: function() {
|
458
|
+
// Colors
|
459
|
+
this.colors = [
|
460
|
+
'#a9dada', // blue
|
461
|
+
'#aedaa9', // green
|
462
|
+
'#daaea9', // peach
|
463
|
+
'#dadaa9', // yellow
|
464
|
+
'#a9a9da', // dk purple
|
465
|
+
'#daaeda', // purple
|
466
|
+
'#dadada' // grey
|
467
|
+
];
|
468
|
+
|
469
|
+
this.set_theme({
|
470
|
+
colors: this.colors,
|
471
|
+
marker_color: '#aea9a9', // Grey
|
472
|
+
font_color: 'black',
|
473
|
+
background_colors: 'white'
|
474
|
+
});
|
475
|
+
},
|
476
|
+
|
477
|
+
// A greyscale theme
|
478
|
+
theme_greyscale: function() {
|
479
|
+
// Colors
|
480
|
+
this.colors = [
|
481
|
+
'#282828', //
|
482
|
+
'#383838', //
|
483
|
+
'#686868', //
|
484
|
+
'#989898', //
|
485
|
+
'#c8c8c8', //
|
486
|
+
'#e8e8e8' //
|
487
|
+
];
|
488
|
+
|
489
|
+
this.set_theme({
|
490
|
+
colors: this.colors,
|
491
|
+
marker_color: '#aea9a9', // Grey
|
492
|
+
font_color: 'black',
|
493
|
+
background_colors: 'white'
|
494
|
+
});
|
495
|
+
},
|
496
|
+
|
497
|
+
// Parameters are an array where the first element is the name of the dataset
|
498
|
+
// and the value is an array of values to plot.
|
499
|
+
//
|
500
|
+
// Can be called multiple times with different datasets for a multi-valued
|
501
|
+
// graph.
|
502
|
+
//
|
503
|
+
// If the color argument is nil, the next color from the default theme will
|
504
|
+
// be used.
|
505
|
+
//
|
506
|
+
// NOTE: If you want to use a preset theme, you must set it before calling
|
507
|
+
// data().
|
508
|
+
//
|
509
|
+
// Example:
|
510
|
+
// data("Bart S.", [95, 45, 78, 89, 88, 76], '#ffcc00')
|
511
|
+
data: function(name, data_points, color) {
|
512
|
+
data_points = (data_points === undefined) ? [] : data_points;
|
513
|
+
color = color || null;
|
514
|
+
|
515
|
+
data_points = Bluff.array(data_points); // make sure it's an array
|
516
|
+
this._data.push([name, data_points, (color || this._increment_color())]);
|
517
|
+
// Set column count if this is larger than previous counts
|
518
|
+
this._column_count = (data_points.length > this._column_count) ? data_points.length : this._column_count;
|
519
|
+
|
520
|
+
// Pre-normalize
|
521
|
+
Bluff.each(data_points, function(data_point, index) {
|
522
|
+
if (data_point === undefined) return;
|
523
|
+
|
524
|
+
// Setup max/min so spread starts at the low end of the data points
|
525
|
+
if (this.maximum_value === null && this.minimum_value === null)
|
526
|
+
this.maximum_value = this.minimum_value = data_point;
|
527
|
+
|
528
|
+
// TODO Doesn't work with stacked bar graphs
|
529
|
+
// Original: @maximum_value = _larger_than_max?(data_point, index) ? max(data_point, index) : @maximum_value
|
530
|
+
this.maximum_value = this._larger_than_max(data_point) ? data_point : this.maximum_value;
|
531
|
+
if (this.maximum_value >= 0) this._has_data = true;
|
532
|
+
|
533
|
+
this.minimum_value = this._less_than_min(data_point) ? data_point : this.minimum_value;
|
534
|
+
if (this.minimum_value < 0) this._has_data = true;
|
535
|
+
}, this);
|
536
|
+
},
|
537
|
+
|
538
|
+
// Overridden by subclasses to do the actual plotting of the graph.
|
539
|
+
//
|
540
|
+
// Subclasses should start by calling super() for this method.
|
541
|
+
draw: function() {
|
542
|
+
if (this.stacked) this._make_stacked();
|
543
|
+
this._setup_drawing();
|
544
|
+
|
545
|
+
this._debug(function() {
|
546
|
+
// Outer margin
|
547
|
+
this._d.rectangle(this.left_margin, this.top_margin,
|
548
|
+
this._raw_columns - this.right_margin, this._raw_rows - this.bottom_margin);
|
549
|
+
// Graph area box
|
550
|
+
this._d.rectangle(this._graph_left, this._graph_top, this._graph_right, this._graph_bottom);
|
551
|
+
});
|
552
|
+
},
|
553
|
+
|
554
|
+
clear: function() {
|
555
|
+
this._render_background();
|
556
|
+
},
|
557
|
+
|
558
|
+
on: function(eventType, callback, context) {
|
559
|
+
var list = this._listeners[eventType] = this._listeners[eventType] || [];
|
560
|
+
list.push([callback, context]);
|
561
|
+
},
|
562
|
+
|
563
|
+
trigger: function(eventType, data) {
|
564
|
+
var list = this._listeners[eventType];
|
565
|
+
if (!list) return;
|
566
|
+
Bluff.each(list, function(listener) {
|
567
|
+
listener[0].call(listener[1], data);
|
568
|
+
});
|
569
|
+
},
|
570
|
+
|
571
|
+
// Calculates size of drawable area and draws the decorations.
|
572
|
+
//
|
573
|
+
// * line markers
|
574
|
+
// * legend
|
575
|
+
// * title
|
576
|
+
_setup_drawing: function() {
|
577
|
+
// Maybe should be done in one of the following functions for more granularity.
|
578
|
+
if (!this._has_data) return this._draw_no_data();
|
579
|
+
|
580
|
+
this._normalize();
|
581
|
+
this._setup_graph_measurements();
|
582
|
+
if (this.sort) this._sort_norm_data();
|
583
|
+
|
584
|
+
this._draw_legend();
|
585
|
+
this._draw_line_markers();
|
586
|
+
this._draw_axis_labels();
|
587
|
+
this._draw_title();
|
588
|
+
},
|
589
|
+
|
590
|
+
// Make copy of data with values scaled between 0-100
|
591
|
+
_normalize: function(force) {
|
592
|
+
if (this._norm_data === null || force === true) {
|
593
|
+
this._norm_data = [];
|
594
|
+
if (!this._has_data) return;
|
595
|
+
|
596
|
+
this._calculate_spread();
|
597
|
+
|
598
|
+
Bluff.each(this._data, function(data_row) {
|
599
|
+
var norm_data_points = [];
|
600
|
+
Bluff.each(data_row[this.klass.DATA_VALUES_INDEX], function(data_point) {
|
601
|
+
if (data_point === null || data_point === undefined)
|
602
|
+
norm_data_points.push(null);
|
603
|
+
else
|
604
|
+
norm_data_points.push((data_point - this.minimum_value) / this._spread);
|
605
|
+
}, this);
|
606
|
+
this._norm_data.push([data_row[this.klass.DATA_LABEL_INDEX], norm_data_points, data_row[this.klass.DATA_COLOR_INDEX]]);
|
607
|
+
}, this);
|
608
|
+
}
|
609
|
+
},
|
610
|
+
|
611
|
+
_calculate_spread: function() {
|
612
|
+
this._spread = this.maximum_value - this.minimum_value;
|
613
|
+
this._spread = this._spread > 0 ? this._spread : 1;
|
614
|
+
|
615
|
+
var power = Math.round(Math.LOG10E*Math.log(this._spread));
|
616
|
+
this._significant_digits = Math.pow(10, 3 - power);
|
617
|
+
},
|
618
|
+
|
619
|
+
// Calculates size of drawable area, general font dimensions, etc.
|
620
|
+
_setup_graph_measurements: function() {
|
621
|
+
this._marker_caps_height = this.hide_line_markers ? 0 :
|
622
|
+
this._calculate_caps_height(this.marker_font_size);
|
623
|
+
this._title_caps_height = this.hide_title ? 0 :
|
624
|
+
this._calculate_caps_height(this.title_font_size);
|
625
|
+
this._legend_caps_height = this.hide_legend ? 0 :
|
626
|
+
this._calculate_caps_height(this.legend_font_size);
|
627
|
+
|
628
|
+
var longest_label,
|
629
|
+
longest_left_label_width,
|
630
|
+
line_number_width,
|
631
|
+
last_label,
|
632
|
+
extra_room_for_long_label,
|
633
|
+
x_axis_label_height,
|
634
|
+
key;
|
635
|
+
|
636
|
+
if (this.hide_line_markers) {
|
637
|
+
this._graph_left = this.left_margin;
|
638
|
+
this._graph_right_margin = this.right_margin;
|
639
|
+
this._graph_bottom_margin = this.bottom_margin;
|
640
|
+
} else {
|
641
|
+
longest_left_label_width = 0;
|
642
|
+
if (this.has_left_labels) {
|
643
|
+
longest_label = '';
|
644
|
+
for (key in this.labels) {
|
645
|
+
longest_label = longest_label.length > this.labels[key].length
|
646
|
+
? longest_label
|
647
|
+
: this.labels[key];
|
648
|
+
}
|
649
|
+
longest_left_label_width = this._calculate_width(this.marker_font_size, longest_label) * 1.25;
|
650
|
+
} else {
|
651
|
+
longest_left_label_width = this._calculate_width(this.marker_font_size, this._label(this.maximum_value));
|
652
|
+
}
|
653
|
+
|
654
|
+
// Shift graph if left line numbers are hidden
|
655
|
+
line_number_width = this.hide_line_numbers && !this.has_left_labels ?
|
656
|
+
0.0 :
|
657
|
+
longest_left_label_width + this.klass.LABEL_MARGIN * 2;
|
658
|
+
|
659
|
+
this._graph_left = this.left_margin +
|
660
|
+
line_number_width +
|
661
|
+
(this.y_axis_label === null ? 0.0 : this._marker_caps_height + this.klass.LABEL_MARGIN * 2);
|
662
|
+
|
663
|
+
// Make space for half the width of the rightmost column label.
|
664
|
+
// Might be greater than the number of columns if between-style bar markers are used.
|
665
|
+
last_label = -Infinity;
|
666
|
+
for (key in this.labels)
|
667
|
+
last_label = last_label > Number(key) ? last_label : Number(key);
|
668
|
+
last_label = Math.round(last_label);
|
669
|
+
extra_room_for_long_label = (last_label >= (this._column_count-1) && this.center_labels_over_point) ?
|
670
|
+
this._calculate_width(this.marker_font_size, this.labels[last_label]) / 2 :
|
671
|
+
0;
|
672
|
+
this._graph_right_margin = this.right_margin + extra_room_for_long_label;
|
673
|
+
|
674
|
+
this._graph_bottom_margin = this.bottom_margin +
|
675
|
+
this._marker_caps_height + this.klass.LABEL_MARGIN;
|
676
|
+
}
|
677
|
+
|
678
|
+
this._graph_right = this._raw_columns - this._graph_right_margin;
|
679
|
+
this._graph_width = this._raw_columns - this._graph_left - this._graph_right_margin;
|
680
|
+
|
681
|
+
// When hide_title, leave a title_margin space for aesthetics.
|
682
|
+
// Same with hide_legend
|
683
|
+
this._graph_top = this.top_margin +
|
684
|
+
(this.hide_title ? this.title_margin : this._title_caps_height + this.title_margin ) +
|
685
|
+
(this.hide_legend ? this.legend_margin : this._legend_caps_height + this.legend_margin);
|
686
|
+
|
687
|
+
x_axis_label_height = (this.x_axis_label === null) ? 0.0 :
|
688
|
+
this._marker_caps_height + this.klass.LABEL_MARGIN;
|
689
|
+
this._graph_bottom = this._raw_rows - this._graph_bottom_margin - x_axis_label_height;
|
690
|
+
this._graph_height = this._graph_bottom - this._graph_top;
|
691
|
+
},
|
692
|
+
|
693
|
+
// Draw the optional labels for the x axis and y axis.
|
694
|
+
_draw_axis_labels: function() {
|
695
|
+
if (this.x_axis_label) {
|
696
|
+
// X Axis
|
697
|
+
// Centered vertically and horizontally by setting the
|
698
|
+
// height to 1.0 and the width to the width of the graph.
|
699
|
+
var x_axis_label_y_coordinate = this._graph_bottom + this.klass.LABEL_MARGIN * 2 + this._marker_caps_height;
|
700
|
+
|
701
|
+
// TODO Center between graph area
|
702
|
+
this._d.fill = this.font_color;
|
703
|
+
if (this.font) this._d.font = this.font;
|
704
|
+
this._d.stroke = 'transparent';
|
705
|
+
this._d.pointsize = this._scale_fontsize(this.marker_font_size);
|
706
|
+
this._d.gravity = 'north';
|
707
|
+
this._d.annotate_scaled(
|
708
|
+
this._raw_columns, 1.0,
|
709
|
+
0.0, x_axis_label_y_coordinate,
|
710
|
+
this.x_axis_label, this._scale);
|
711
|
+
this._debug(function() {
|
712
|
+
this._d.line(0.0, x_axis_label_y_coordinate, this._raw_columns, x_axis_label_y_coordinate);
|
713
|
+
});
|
714
|
+
}
|
715
|
+
|
716
|
+
// TODO Y label (not generally possible in browsers)
|
717
|
+
},
|
718
|
+
|
719
|
+
// Draws horizontal background lines and labels
|
720
|
+
_draw_line_markers: function() {
|
721
|
+
if (this.hide_line_markers) return;
|
722
|
+
|
723
|
+
if (this.y_axis_increment === null) {
|
724
|
+
// Try to use a number of horizontal lines that will come out even.
|
725
|
+
//
|
726
|
+
// TODO Do the same for larger numbers...100, 75, 50, 25
|
727
|
+
if (this.marker_count === null) {
|
728
|
+
Bluff.each([3,4,5,6,7], function(lines) {
|
729
|
+
if (!this.marker_count && this._spread % lines === 0)
|
730
|
+
this.marker_count = lines;
|
731
|
+
}, this);
|
732
|
+
this.marker_count = this.marker_count || 4;
|
733
|
+
}
|
734
|
+
this._increment = (this._spread > 0) ? this._significant(this._spread / this.marker_count) : 1;
|
735
|
+
} else {
|
736
|
+
// TODO Make this work for negative values
|
737
|
+
this.maximum_value = Math.max(Math.ceil(this.maximum_value), this.y_axis_increment);
|
738
|
+
this.minimum_value = Math.floor(this.minimum_value);
|
739
|
+
this._calculate_spread();
|
740
|
+
this._normalize(true);
|
741
|
+
|
742
|
+
this.marker_count = Math.round(this._spread / this.y_axis_increment);
|
743
|
+
this._increment = this.y_axis_increment;
|
744
|
+
}
|
745
|
+
this._increment_scaled = this._graph_height / (this._spread / this._increment);
|
746
|
+
|
747
|
+
// Draw horizontal line markers and annotate with numbers
|
748
|
+
var index, n, y, marker_label;
|
749
|
+
for (index = 0, n = this.marker_count; index <= n; index++) {
|
750
|
+
y = this._graph_top + this._graph_height - index * this._increment_scaled;
|
751
|
+
|
752
|
+
this._d.stroke = this.marker_color;
|
753
|
+
this._d.stroke_width = 1;
|
754
|
+
this._d.line(this._graph_left, y, this._graph_right, y);
|
755
|
+
|
756
|
+
marker_label = index * this._increment + this.minimum_value;
|
757
|
+
|
758
|
+
if (!this.hide_line_numbers) {
|
759
|
+
this._d.fill = this.font_color;
|
760
|
+
if (this.font) this._d.font = this.font;
|
761
|
+
this._d.font_weight = 'normal';
|
762
|
+
this._d.stroke = 'transparent';
|
763
|
+
this._d.pointsize = this._scale_fontsize(this.marker_font_size);
|
764
|
+
this._d.gravity = 'east';
|
765
|
+
|
766
|
+
// Vertically center with 1.0 for the height
|
767
|
+
this._d.annotate_scaled(this._graph_left - this.klass.LABEL_MARGIN,
|
768
|
+
1.0, 0.0, y,
|
769
|
+
this._label(marker_label), this._scale);
|
770
|
+
}
|
771
|
+
}
|
772
|
+
},
|
773
|
+
|
774
|
+
_center: function(size) {
|
775
|
+
return (this._raw_columns - size) / 2;
|
776
|
+
},
|
777
|
+
|
778
|
+
// Draws a legend with the names of the datasets matched to the colors used
|
779
|
+
// to draw them.
|
780
|
+
_draw_legend: function() {
|
781
|
+
if (this.hide_legend) return;
|
782
|
+
|
783
|
+
this._legend_labels = Bluff.map(this._data, function(item) {
|
784
|
+
return item[this.klass.DATA_LABEL_INDEX];
|
785
|
+
}, this);
|
786
|
+
|
787
|
+
var legend_square_width = this.legend_box_size; // small square with color of this item
|
788
|
+
|
789
|
+
// May fix legend drawing problem at small sizes
|
790
|
+
if (this.font) this._d.font = this.font;
|
791
|
+
this._d.pointsize = this.legend_font_size;
|
792
|
+
|
793
|
+
var label_widths = [[]]; // Used to calculate line wrap
|
794
|
+
Bluff.each(this._legend_labels, function(label) {
|
795
|
+
var last = label_widths.length - 1;
|
796
|
+
var metrics = this._d.get_type_metrics(label);
|
797
|
+
var label_width = metrics.width + legend_square_width * 2.7;
|
798
|
+
label_widths[last].push(label_width);
|
799
|
+
|
800
|
+
if (Bluff.sum(label_widths[last]) > (this._raw_columns * 0.9))
|
801
|
+
label_widths.push([label_widths[last].pop()]);
|
802
|
+
}, this);
|
803
|
+
|
804
|
+
var current_x_offset = this._center(Bluff.sum(label_widths[0]));
|
805
|
+
var current_y_offset = this.hide_title ?
|
806
|
+
this.top_margin + this.title_margin :
|
807
|
+
this.top_margin + this.title_margin + this._title_caps_height;
|
808
|
+
|
809
|
+
this._debug(function() {
|
810
|
+
this._d.stroke_width = 1;
|
811
|
+
this._d.line(0, current_y_offset, this._raw_columns, current_y_offset);
|
812
|
+
});
|
813
|
+
|
814
|
+
Bluff.each(this._legend_labels, function(legend_label, index) {
|
815
|
+
|
816
|
+
// Draw label
|
817
|
+
this._d.fill = this.font_color;
|
818
|
+
if (this.font) this._d.font = this.font;
|
819
|
+
this._d.pointsize = this._scale_fontsize(this.legend_font_size);
|
820
|
+
this._d.stroke = 'transparent';
|
821
|
+
this._d.font_weight = 'normal';
|
822
|
+
this._d.gravity = 'west';
|
823
|
+
this._d.annotate_scaled(this._raw_columns, 1.0,
|
824
|
+
current_x_offset + (legend_square_width * 1.7), current_y_offset,
|
825
|
+
legend_label, this._scale);
|
826
|
+
|
827
|
+
// Now draw box with color of this dataset
|
828
|
+
this._d.stroke = 'transparent';
|
829
|
+
this._d.fill = this._data[index][this.klass.DATA_COLOR_INDEX];
|
830
|
+
this._d.rectangle(current_x_offset,
|
831
|
+
current_y_offset - legend_square_width / 2.0,
|
832
|
+
current_x_offset + legend_square_width,
|
833
|
+
current_y_offset + legend_square_width / 2.0);
|
834
|
+
|
835
|
+
this._d.pointsize = this.legend_font_size;
|
836
|
+
var metrics = this._d.get_type_metrics(legend_label);
|
837
|
+
var current_string_offset = metrics.width + (legend_square_width * 2.7),
|
838
|
+
line_height;
|
839
|
+
|
840
|
+
// Handle wrapping
|
841
|
+
label_widths[0].shift();
|
842
|
+
if (label_widths[0].length == 0) {
|
843
|
+
this._debug(function() {
|
844
|
+
this._d.line(0.0, current_y_offset, this._raw_columns, current_y_offset);
|
845
|
+
});
|
846
|
+
|
847
|
+
label_widths.shift();
|
848
|
+
if (label_widths.length > 0) current_x_offset = this._center(Bluff.sum(label_widths[0]));
|
849
|
+
line_height = Math.max(this._legend_caps_height, legend_square_width) + this.legend_margin;
|
850
|
+
if (label_widths.length > 0) {
|
851
|
+
// Wrap to next line and shrink available graph dimensions
|
852
|
+
current_y_offset += line_height;
|
853
|
+
this._graph_top += line_height;
|
854
|
+
this._graph_height = this._graph_bottom - this._graph_top;
|
855
|
+
}
|
856
|
+
} else {
|
857
|
+
current_x_offset += current_string_offset;
|
858
|
+
}
|
859
|
+
}, this);
|
860
|
+
this._color_index = 0;
|
861
|
+
},
|
862
|
+
|
863
|
+
// Draws a title on the graph.
|
864
|
+
_draw_title: function() {
|
865
|
+
if (this.hide_title || !this.title) return;
|
866
|
+
|
867
|
+
this._d.fill = this.font_color;
|
868
|
+
if (this.font) this._d.font = this.font;
|
869
|
+
this._d.pointsize = this._scale_fontsize(this.title_font_size);
|
870
|
+
this._d.font_weight = 'bold';
|
871
|
+
this._d.gravity = 'north';
|
872
|
+
this._d.annotate_scaled(this._raw_columns, 1.0,
|
873
|
+
0, this.top_margin,
|
874
|
+
this.title, this._scale);
|
875
|
+
},
|
876
|
+
|
877
|
+
// Draws column labels below graph, centered over x_offset
|
878
|
+
//--
|
879
|
+
// TODO Allow WestGravity as an option
|
880
|
+
_draw_label: function(x_offset, index) {
|
881
|
+
if (this.hide_line_markers) return;
|
882
|
+
|
883
|
+
var y_offset;
|
884
|
+
|
885
|
+
if (this.labels[index] && !this._labels_seen[index]) {
|
886
|
+
y_offset = this._graph_bottom + this.klass.LABEL_MARGIN;
|
887
|
+
|
888
|
+
this._d.fill = this.font_color;
|
889
|
+
if (this.font) this._d.font = this.font;
|
890
|
+
this._d.stroke = 'transparent';
|
891
|
+
this._d.font_weight = 'normal';
|
892
|
+
this._d.pointsize = this._scale_fontsize(this.marker_font_size);
|
893
|
+
this._d.gravity = 'north';
|
894
|
+
this._d.annotate_scaled(1.0, 1.0,
|
895
|
+
x_offset, y_offset,
|
896
|
+
this.labels[index], this._scale);
|
897
|
+
this._labels_seen[index] = true;
|
898
|
+
|
899
|
+
this._debug(function() {
|
900
|
+
this._d.stroke_width = 1;
|
901
|
+
this._d.line(0.0, y_offset, this._raw_columns, y_offset);
|
902
|
+
});
|
903
|
+
}
|
904
|
+
},
|
905
|
+
|
906
|
+
// Creates a mouse hover target rectangle for tooltip displays
|
907
|
+
_draw_tooltip: function(left, top, width, height, name, color, data, index) {
|
908
|
+
if (!this.tooltips) return;
|
909
|
+
var node = this._d.tooltip(left, top, width, height, name, color, data);
|
910
|
+
|
911
|
+
Bluff.Event.observe(node, 'click', function() {
|
912
|
+
var point = {
|
913
|
+
series: name,
|
914
|
+
label: this.labels[index],
|
915
|
+
value: data,
|
916
|
+
color: color
|
917
|
+
};
|
918
|
+
this.trigger('click:datapoint', point);
|
919
|
+
}, this);
|
920
|
+
},
|
921
|
+
|
922
|
+
// Shows an error message because you have no data.
|
923
|
+
_draw_no_data: function() {
|
924
|
+
this._d.fill = this.font_color;
|
925
|
+
if (this.font) this._d.font = this.font;
|
926
|
+
this._d.stroke = 'transparent';
|
927
|
+
this._d.font_weight = 'normal';
|
928
|
+
this._d.pointsize = this._scale_fontsize(80);
|
929
|
+
this._d.gravity = 'center';
|
930
|
+
this._d.annotate_scaled(this._raw_columns, this._raw_rows/2,
|
931
|
+
0, 10,
|
932
|
+
this.no_data_message, this._scale);
|
933
|
+
},
|
934
|
+
|
935
|
+
// Finds the best background to render based on the provided theme options.
|
936
|
+
_render_background: function() {
|
937
|
+
var colors = this._theme_options.background_colors;
|
938
|
+
switch (true) {
|
939
|
+
case colors instanceof Array:
|
940
|
+
this._render_gradiated_background.apply(this, colors);
|
941
|
+
break;
|
942
|
+
case typeof colors === 'string':
|
943
|
+
this._render_solid_background(colors);
|
944
|
+
break;
|
945
|
+
default:
|
946
|
+
this._render_image_background(this._theme_options.background_image);
|
947
|
+
break;
|
948
|
+
}
|
949
|
+
},
|
950
|
+
|
951
|
+
// Make a new image at the current size with a solid +color+.
|
952
|
+
_render_solid_background: function(color) {
|
953
|
+
this._d.render_solid_background(this._columns, this._rows, color);
|
954
|
+
},
|
955
|
+
|
956
|
+
// Use with a theme definition method to draw a gradiated background.
|
957
|
+
_render_gradiated_background: function(top_color, bottom_color) {
|
958
|
+
this._d.render_gradiated_background(this._columns, this._rows, top_color, bottom_color);
|
959
|
+
},
|
960
|
+
|
961
|
+
// Use with a theme to use an image (800x600 original) background.
|
962
|
+
_render_image_background: function(image_path) {
|
963
|
+
// TODO
|
964
|
+
},
|
965
|
+
|
966
|
+
// Resets everything to defaults (except data).
|
967
|
+
_reset_themes: function() {
|
968
|
+
this._color_index = 0;
|
969
|
+
this._labels_seen = {};
|
970
|
+
this._theme_options = {};
|
971
|
+
this._d.scale(this._scale, this._scale);
|
972
|
+
},
|
973
|
+
|
974
|
+
_scale_value: function(value) {
|
975
|
+
return this._scale * value;
|
976
|
+
},
|
977
|
+
|
978
|
+
// Return a comparable fontsize for the current graph.
|
979
|
+
_scale_fontsize: function(value) {
|
980
|
+
var new_fontsize = value * this._scale;
|
981
|
+
return new_fontsize;
|
982
|
+
},
|
983
|
+
|
984
|
+
_clip_value_if_greater_than: function(value, max_value) {
|
985
|
+
return (value > max_value) ? max_value : value;
|
986
|
+
},
|
987
|
+
|
988
|
+
// Overridden by subclasses such as stacked bar.
|
989
|
+
_larger_than_max: function(data_point, index) {
|
990
|
+
return data_point > this.maximum_value;
|
991
|
+
},
|
992
|
+
|
993
|
+
_less_than_min: function(data_point, index) {
|
994
|
+
return data_point < this.minimum_value;
|
995
|
+
},
|
996
|
+
|
997
|
+
// Overridden by subclasses that need it.
|
998
|
+
_max: function(data_point, index) {
|
999
|
+
return data_point;
|
1000
|
+
},
|
1001
|
+
|
1002
|
+
// Overridden by subclasses that need it.
|
1003
|
+
_min: function(data_point, index) {
|
1004
|
+
return data_point;
|
1005
|
+
},
|
1006
|
+
|
1007
|
+
_significant: function(inc) {
|
1008
|
+
if (inc == 0) return 1.0;
|
1009
|
+
var factor = 1.0;
|
1010
|
+
while (inc < 10) {
|
1011
|
+
inc *= 10;
|
1012
|
+
factor /= 10;
|
1013
|
+
}
|
1014
|
+
|
1015
|
+
while (inc > 100) {
|
1016
|
+
inc /= 10;
|
1017
|
+
factor *= 10;
|
1018
|
+
}
|
1019
|
+
|
1020
|
+
return Math.floor(inc) * factor;
|
1021
|
+
},
|
1022
|
+
|
1023
|
+
// Sort with largest overall summed value at front of array so it shows up
|
1024
|
+
// correctly in the drawn graph.
|
1025
|
+
_sort_norm_data: function() {
|
1026
|
+
var sums = this._sums, index = this.klass.DATA_VALUES_INDEX;
|
1027
|
+
|
1028
|
+
this._norm_data.sort(function(a,b) {
|
1029
|
+
return sums(b[index]) - sums(a[index]);
|
1030
|
+
});
|
1031
|
+
|
1032
|
+
this._data.sort(function(a,b) {
|
1033
|
+
return sums(b[index]) - sums(a[index]);
|
1034
|
+
});
|
1035
|
+
},
|
1036
|
+
|
1037
|
+
_sums: function(data_set) {
|
1038
|
+
var total_sum = 0;
|
1039
|
+
Bluff.each(data_set, function(num) { total_sum += (num || 0) });
|
1040
|
+
return total_sum;
|
1041
|
+
},
|
1042
|
+
|
1043
|
+
_make_stacked: function() {
|
1044
|
+
var stacked_values = [], i = this._column_count;
|
1045
|
+
while (i--) stacked_values[i] = 0;
|
1046
|
+
Bluff.each(this._data, function(value_set) {
|
1047
|
+
Bluff.each(value_set[this.klass.DATA_VALUES_INDEX], function(value, index) {
|
1048
|
+
stacked_values[index] += value;
|
1049
|
+
}, this);
|
1050
|
+
value_set[this.klass.DATA_VALUES_INDEX] = Bluff.array(stacked_values);
|
1051
|
+
}, this);
|
1052
|
+
},
|
1053
|
+
|
1054
|
+
// Takes a block and draws it if DEBUG is true.
|
1055
|
+
//
|
1056
|
+
// Example:
|
1057
|
+
// debug { @d.rectangle x1, y1, x2, y2 }
|
1058
|
+
_debug: function(block) {
|
1059
|
+
if (this.klass.DEBUG) {
|
1060
|
+
this._d.fill = 'transparent';
|
1061
|
+
this._d.stroke = 'turquoise';
|
1062
|
+
block.call(this);
|
1063
|
+
}
|
1064
|
+
},
|
1065
|
+
|
1066
|
+
// Returns the next color in your color list.
|
1067
|
+
_increment_color: function() {
|
1068
|
+
var offset = this._color_index;
|
1069
|
+
this._color_index = (this._color_index + 1) % this.colors.length;
|
1070
|
+
return this.colors[offset];
|
1071
|
+
},
|
1072
|
+
|
1073
|
+
// Return a formatted string representing a number value that should be
|
1074
|
+
// printed as a label.
|
1075
|
+
_label: function(value) {
|
1076
|
+
var sep = this.klass.THOUSAND_SEPARATOR,
|
1077
|
+
label = (this._spread % this.marker_count == 0 || this.y_axis_increment !== null)
|
1078
|
+
? String(Math.round(value))
|
1079
|
+
: String(Math.floor(value * this._significant_digits)/this._significant_digits);
|
1080
|
+
|
1081
|
+
var parts = label.split('.');
|
1082
|
+
parts[0] = parts[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1' + sep);
|
1083
|
+
return parts.join('.');
|
1084
|
+
},
|
1085
|
+
|
1086
|
+
// Returns the height of the capital letter 'X' for the current font and
|
1087
|
+
// size.
|
1088
|
+
//
|
1089
|
+
// Not scaled since it deals with dimensions that the regular scaling will
|
1090
|
+
// handle.
|
1091
|
+
_calculate_caps_height: function(font_size) {
|
1092
|
+
return this._d.caps_height(font_size);
|
1093
|
+
},
|
1094
|
+
|
1095
|
+
// Returns the width of a string at this pointsize.
|
1096
|
+
//
|
1097
|
+
// Not scaled since it deals with dimensions that the regular
|
1098
|
+
// scaling will handle.
|
1099
|
+
_calculate_width: function(font_size, text) {
|
1100
|
+
return this._d.text_width(font_size, text);
|
1101
|
+
}
|
1102
|
+
});
|
1103
|
+
|
1104
|
+
|
1105
|
+
Bluff.Area = new JS.Class(Bluff.Base, {
|
1106
|
+
|
1107
|
+
draw: function() {
|
1108
|
+
this.callSuper();
|
1109
|
+
|
1110
|
+
if (!this._has_data) return;
|
1111
|
+
|
1112
|
+
this._x_increment = this._graph_width / (this._column_count - 1);
|
1113
|
+
this._d.stroke = 'transparent';
|
1114
|
+
|
1115
|
+
Bluff.each(this._norm_data, function(data_row) {
|
1116
|
+
var poly_points = [],
|
1117
|
+
prev_x = 0.0,
|
1118
|
+
prev_y = 0.0;
|
1119
|
+
|
1120
|
+
Bluff.each(data_row[this.klass.DATA_VALUES_INDEX], function(data_point, index) {
|
1121
|
+
// Use incremented x and scaled y
|
1122
|
+
var new_x = this._graph_left + (this._x_increment * index);
|
1123
|
+
var new_y = this._graph_top + (this._graph_height - data_point * this._graph_height);
|
1124
|
+
|
1125
|
+
if (prev_x > 0 && prev_y > 0) {
|
1126
|
+
poly_points.push(new_x);
|
1127
|
+
poly_points.push(new_y);
|
1128
|
+
|
1129
|
+
// this._d.polyline(prev_x, prev_y, new_x, new_y);
|
1130
|
+
} else {
|
1131
|
+
poly_points.push(this._graph_left);
|
1132
|
+
poly_points.push(this._graph_bottom - 1);
|
1133
|
+
poly_points.push(new_x);
|
1134
|
+
poly_points.push(new_y);
|
1135
|
+
|
1136
|
+
// this._d.polyline(this._graph_left, this._graph_bottom, new_x, new_y);
|
1137
|
+
}
|
1138
|
+
|
1139
|
+
this._draw_label(new_x, index);
|
1140
|
+
|
1141
|
+
prev_x = new_x;
|
1142
|
+
prev_y = new_y;
|
1143
|
+
}, this);
|
1144
|
+
|
1145
|
+
// Add closing points, draw polygon
|
1146
|
+
poly_points.push(this._graph_right);
|
1147
|
+
poly_points.push(this._graph_bottom - 1);
|
1148
|
+
poly_points.push(this._graph_left);
|
1149
|
+
poly_points.push(this._graph_bottom - 1);
|
1150
|
+
|
1151
|
+
this._d.fill = data_row[this.klass.DATA_COLOR_INDEX];
|
1152
|
+
this._d.polyline(poly_points);
|
1153
|
+
|
1154
|
+
}, this);
|
1155
|
+
}
|
1156
|
+
});
|
1157
|
+
|
1158
|
+
|
1159
|
+
// This class perfoms the y coordinats conversion for the bar class.
|
1160
|
+
//
|
1161
|
+
// There are three cases:
|
1162
|
+
//
|
1163
|
+
// 1. Bars all go from zero in positive direction
|
1164
|
+
// 2. Bars all go from zero to negative direction
|
1165
|
+
// 3. Bars either go from zero to positive or from zero to negative
|
1166
|
+
//
|
1167
|
+
Bluff.BarConversion = new JS.Class({
|
1168
|
+
mode: null,
|
1169
|
+
zero: null,
|
1170
|
+
graph_top: null,
|
1171
|
+
graph_height: null,
|
1172
|
+
minimum_value: null,
|
1173
|
+
spread: null,
|
1174
|
+
|
1175
|
+
getLeftYRightYscaled: function(data_point, result) {
|
1176
|
+
var val;
|
1177
|
+
switch (this.mode) {
|
1178
|
+
case 1: // Case one
|
1179
|
+
// minimum value >= 0 ( only positiv values )
|
1180
|
+
result[0] = this.graph_top + this.graph_height*(1 - data_point) + 1;
|
1181
|
+
result[1] = this.graph_top + this.graph_height - 1;
|
1182
|
+
break;
|
1183
|
+
case 2: // Case two
|
1184
|
+
// only negativ values
|
1185
|
+
result[0] = this.graph_top + 1;
|
1186
|
+
result[1] = this.graph_top + this.graph_height*(1 - data_point) - 1;
|
1187
|
+
break;
|
1188
|
+
case 3: // Case three
|
1189
|
+
// positiv and negativ values
|
1190
|
+
val = data_point-this.minimum_value/this.spread;
|
1191
|
+
if ( data_point >= this.zero ) {
|
1192
|
+
result[0] = this.graph_top + this.graph_height*(1 - (val-this.zero)) + 1;
|
1193
|
+
result[1] = this.graph_top + this.graph_height*(1 - this.zero) - 1;
|
1194
|
+
} else {
|
1195
|
+
result[0] = this.graph_top + this.graph_height*(1 - (val-this.zero)) + 1;
|
1196
|
+
result[1] = this.graph_top + this.graph_height*(1 - this.zero) - 1;
|
1197
|
+
}
|
1198
|
+
break;
|
1199
|
+
default:
|
1200
|
+
result[0] = 0.0;
|
1201
|
+
result[1] = 0.0;
|
1202
|
+
}
|
1203
|
+
}
|
1204
|
+
|
1205
|
+
});
|
1206
|
+
|
1207
|
+
|
1208
|
+
Bluff.Bar = new JS.Class(Bluff.Base, {
|
1209
|
+
|
1210
|
+
// Spacing factor applied between bars
|
1211
|
+
bar_spacing: 0.9,
|
1212
|
+
|
1213
|
+
draw: function() {
|
1214
|
+
// Labels will be centered over the left of the bar if
|
1215
|
+
// there are more labels than columns. This is basically the same
|
1216
|
+
// as where it would be for a line graph.
|
1217
|
+
this.center_labels_over_point = (Bluff.keys(this.labels).length > this._column_count);
|
1218
|
+
|
1219
|
+
this.callSuper();
|
1220
|
+
if (!this._has_data) return;
|
1221
|
+
|
1222
|
+
this._draw_bars();
|
1223
|
+
},
|
1224
|
+
|
1225
|
+
_draw_bars: function() {
|
1226
|
+
this._bar_width = this._graph_width / (this._column_count * this._data.length);
|
1227
|
+
var padding = (this._bar_width * (1 - this.bar_spacing)) / 2;
|
1228
|
+
|
1229
|
+
this._d.stroke_opacity = 0.0;
|
1230
|
+
|
1231
|
+
// Setup the BarConversion Object
|
1232
|
+
var conversion = new Bluff.BarConversion();
|
1233
|
+
conversion.graph_height = this._graph_height;
|
1234
|
+
conversion.graph_top = this._graph_top;
|
1235
|
+
|
1236
|
+
// Set up the right mode [1,2,3] see BarConversion for further explanation
|
1237
|
+
if (this.minimum_value >= 0) {
|
1238
|
+
// all bars go from zero to positiv
|
1239
|
+
conversion.mode = 1;
|
1240
|
+
} else {
|
1241
|
+
// all bars go from 0 to negativ
|
1242
|
+
if (this.maximum_value <= 0) {
|
1243
|
+
conversion.mode = 2;
|
1244
|
+
} else {
|
1245
|
+
// bars either go from zero to negativ or to positiv
|
1246
|
+
conversion.mode = 3;
|
1247
|
+
conversion.spread = this._spread;
|
1248
|
+
conversion.minimum_value = this.minimum_value;
|
1249
|
+
conversion.zero = -this.minimum_value/this._spread;
|
1250
|
+
}
|
1251
|
+
}
|
1252
|
+
|
1253
|
+
// iterate over all normalised data
|
1254
|
+
Bluff.each(this._norm_data, function(data_row, row_index) {
|
1255
|
+
var raw_data = this._data[row_index][this.klass.DATA_VALUES_INDEX];
|
1256
|
+
|
1257
|
+
Bluff.each(data_row[this.klass.DATA_VALUES_INDEX], function(data_point, point_index) {
|
1258
|
+
// Use incremented x and scaled y
|
1259
|
+
// x
|
1260
|
+
var left_x = this._graph_left + (this._bar_width * (row_index + point_index + ((this._data.length - 1) * point_index))) + padding;
|
1261
|
+
var right_x = left_x + this._bar_width * this.bar_spacing;
|
1262
|
+
// y
|
1263
|
+
var conv = [];
|
1264
|
+
conversion.getLeftYRightYscaled(data_point, conv);
|
1265
|
+
|
1266
|
+
// create new bar
|
1267
|
+
this._d.fill = data_row[this.klass.DATA_COLOR_INDEX];
|
1268
|
+
this._d.rectangle(left_x, conv[0], right_x, conv[1]);
|
1269
|
+
|
1270
|
+
// create tooltip target
|
1271
|
+
this._draw_tooltip(left_x, conv[0],
|
1272
|
+
right_x - left_x, conv[1] - conv[0],
|
1273
|
+
data_row[this.klass.DATA_LABEL_INDEX],
|
1274
|
+
data_row[this.klass.DATA_COLOR_INDEX],
|
1275
|
+
raw_data[point_index], point_index);
|
1276
|
+
|
1277
|
+
// Calculate center based on bar_width and current row
|
1278
|
+
var label_center = this._graph_left +
|
1279
|
+
(this._data.length * this._bar_width * point_index) +
|
1280
|
+
(this._data.length * this._bar_width / 2.0);
|
1281
|
+
// Subtract half a bar width to center left if requested
|
1282
|
+
this._draw_label(label_center - (this.center_labels_over_point ? this._bar_width / 2.0 : 0.0), point_index);
|
1283
|
+
}, this);
|
1284
|
+
|
1285
|
+
}, this);
|
1286
|
+
|
1287
|
+
// Draw the last label if requested
|
1288
|
+
if (this.center_labels_over_point) this._draw_label(this._graph_right, this._column_count);
|
1289
|
+
}
|
1290
|
+
});
|
1291
|
+
|
1292
|
+
|
1293
|
+
// Here's how to make a Line graph:
|
1294
|
+
//
|
1295
|
+
// g = new Bluff.Line('canvasId');
|
1296
|
+
// g.title = "A Line Graph";
|
1297
|
+
// g.data('Fries', [20, 23, 19, 8]);
|
1298
|
+
// g.data('Hamburgers', [50, 19, 99, 29]);
|
1299
|
+
// g.draw();
|
1300
|
+
//
|
1301
|
+
// There are also other options described below, such as #baseline_value, #baseline_color, #hide_dots, and #hide_lines.
|
1302
|
+
|
1303
|
+
Bluff.Line = new JS.Class(Bluff.Base, {
|
1304
|
+
// Draw a dashed line at the given value
|
1305
|
+
baseline_value: null,
|
1306
|
+
|
1307
|
+
// Color of the baseline
|
1308
|
+
baseline_color: null,
|
1309
|
+
|
1310
|
+
// Dimensions of lines and dots; calculated based on dataset size if left unspecified
|
1311
|
+
line_width: null,
|
1312
|
+
dot_radius: null,
|
1313
|
+
|
1314
|
+
// Hide parts of the graph to fit more datapoints, or for a different appearance.
|
1315
|
+
hide_dots: null,
|
1316
|
+
hide_lines: null,
|
1317
|
+
|
1318
|
+
// Call with target pixel width of graph (800, 400, 300), and/or 'false' to omit lines (points only).
|
1319
|
+
//
|
1320
|
+
// g = new Bluff.Line('canvasId', 400) // 400px wide with lines
|
1321
|
+
//
|
1322
|
+
// g = new Bluff.Line('canvasId', 400, false) // 400px wide, no lines (for backwards compatibility)
|
1323
|
+
//
|
1324
|
+
// g = new Bluff.Line('canvasId', false) // Defaults to 800px wide, no lines (for backwards compatibility)
|
1325
|
+
//
|
1326
|
+
// The preferred way is to call hide_dots or hide_lines instead.
|
1327
|
+
initialize: function(renderer) {
|
1328
|
+
if (arguments.length > 3) throw 'Wrong number of arguments';
|
1329
|
+
if (arguments.length === 1 || (typeof arguments[1] !== 'number' && typeof arguments[1] !== 'string'))
|
1330
|
+
this.callSuper(renderer, null);
|
1331
|
+
else
|
1332
|
+
this.callSuper();
|
1333
|
+
|
1334
|
+
this.hide_dots = this.hide_lines = false;
|
1335
|
+
this.baseline_color = 'red';
|
1336
|
+
this.baseline_value = null;
|
1337
|
+
},
|
1338
|
+
|
1339
|
+
draw: function() {
|
1340
|
+
this.callSuper();
|
1341
|
+
|
1342
|
+
if (!this._has_data) return;
|
1343
|
+
|
1344
|
+
// Check to see if more than one datapoint was given. NaN can result otherwise.
|
1345
|
+
this.x_increment = (this._column_count > 1) ? (this._graph_width / (this._column_count - 1)) : this._graph_width;
|
1346
|
+
|
1347
|
+
var level;
|
1348
|
+
|
1349
|
+
if (this._norm_baseline !== undefined) {
|
1350
|
+
level = this._graph_top + (this._graph_height - this._norm_baseline * this._graph_height);
|
1351
|
+
this._d.push();
|
1352
|
+
this._d.stroke = this.baseline_color;
|
1353
|
+
this._d.fill_opacity = 0.0;
|
1354
|
+
// this._d.stroke_dasharray(10, 20);
|
1355
|
+
this._d.stroke_width = 3.0;
|
1356
|
+
this._d.line(this._graph_left, level, this._graph_left + this._graph_width, level);
|
1357
|
+
this._d.pop();
|
1358
|
+
}
|
1359
|
+
|
1360
|
+
Bluff.each(this._norm_data, function(data_row, row_index) {
|
1361
|
+
var prev_x = null, prev_y = null;
|
1362
|
+
var raw_data = this._data[row_index][this.klass.DATA_VALUES_INDEX];
|
1363
|
+
|
1364
|
+
this._one_point = this._contains_one_point_only(data_row);
|
1365
|
+
|
1366
|
+
Bluff.each(data_row[this.klass.DATA_VALUES_INDEX], function(data_point, index) {
|
1367
|
+
var new_x = this._graph_left + (this.x_increment * index);
|
1368
|
+
if (typeof data_point !== 'number') return;
|
1369
|
+
|
1370
|
+
this._draw_label(new_x, index);
|
1371
|
+
|
1372
|
+
var new_y = this._graph_top + (this._graph_height - data_point * this._graph_height);
|
1373
|
+
|
1374
|
+
// Reset each time to avoid thin-line errors
|
1375
|
+
this._d.stroke = data_row[this.klass.DATA_COLOR_INDEX];
|
1376
|
+
this._d.fill = data_row[this.klass.DATA_COLOR_INDEX];
|
1377
|
+
this._d.stroke_opacity = 1.0;
|
1378
|
+
this._d.stroke_width = this.line_width ||
|
1379
|
+
this._clip_value_if_greater_than(this._columns / (this._norm_data[0][this.klass.DATA_VALUES_INDEX].length * 6), 3.0);
|
1380
|
+
|
1381
|
+
var circle_radius = this.dot_radius ||
|
1382
|
+
this._clip_value_if_greater_than(this._columns / (this._norm_data[0][this.klass.DATA_VALUES_INDEX].length * 2), 7.0);
|
1383
|
+
|
1384
|
+
if (!this.hide_lines && prev_x !== null && prev_y !== null) {
|
1385
|
+
this._d.line(prev_x, prev_y, new_x, new_y);
|
1386
|
+
} else if (this._one_point) {
|
1387
|
+
// Show a circle if there's just one point
|
1388
|
+
this._d.circle(new_x, new_y, new_x - circle_radius, new_y);
|
1389
|
+
}
|
1390
|
+
|
1391
|
+
if (!this.hide_dots) this._d.circle(new_x, new_y, new_x - circle_radius, new_y);
|
1392
|
+
|
1393
|
+
this._draw_tooltip(new_x - circle_radius, new_y - circle_radius,
|
1394
|
+
2 * circle_radius, 2 *circle_radius,
|
1395
|
+
data_row[this.klass.DATA_LABEL_INDEX],
|
1396
|
+
data_row[this.klass.DATA_COLOR_INDEX],
|
1397
|
+
raw_data[index], index);
|
1398
|
+
|
1399
|
+
prev_x = new_x;
|
1400
|
+
prev_y = new_y;
|
1401
|
+
}, this);
|
1402
|
+
}, this);
|
1403
|
+
},
|
1404
|
+
|
1405
|
+
_normalize: function() {
|
1406
|
+
this.maximum_value = Math.max(this.maximum_value, this.baseline_value);
|
1407
|
+
this.callSuper();
|
1408
|
+
if (this.baseline_value !== null) this._norm_baseline = this.baseline_value / this.maximum_value;
|
1409
|
+
},
|
1410
|
+
|
1411
|
+
_contains_one_point_only: function(data_row) {
|
1412
|
+
// Spin through data to determine if there is just one value present.
|
1413
|
+
var count = 0;
|
1414
|
+
Bluff.each(data_row[this.klass.DATA_VALUES_INDEX], function(data_point) {
|
1415
|
+
if (data_point !== undefined) count += 1;
|
1416
|
+
});
|
1417
|
+
return count === 1;
|
1418
|
+
}
|
1419
|
+
});
|
1420
|
+
|
1421
|
+
|
1422
|
+
// Graph with dots and labels along a vertical access
|
1423
|
+
// see: 'Creating More Effective Graphs' by Robbins
|
1424
|
+
|
1425
|
+
Bluff.Dot = new JS.Class(Bluff.Base, {
|
1426
|
+
|
1427
|
+
draw: function() {
|
1428
|
+
this.has_left_labels = true;
|
1429
|
+
this.callSuper();
|
1430
|
+
|
1431
|
+
if (!this._has_data) return;
|
1432
|
+
|
1433
|
+
// Setup spacing.
|
1434
|
+
//
|
1435
|
+
var spacing_factor = 1.0;
|
1436
|
+
|
1437
|
+
this._items_width = this._graph_height / this._column_count;
|
1438
|
+
this._item_width = this._items_width * spacing_factor / this._norm_data.length;
|
1439
|
+
this._d.stroke_opacity = 0.0;
|
1440
|
+
var height = Bluff.array_new(this._column_count, 0),
|
1441
|
+
length = Bluff.array_new(this._column_count, this._graph_left),
|
1442
|
+
padding = (this._items_width * (1 - spacing_factor)) / 2;
|
1443
|
+
|
1444
|
+
Bluff.each(this._norm_data, function(data_row, row_index) {
|
1445
|
+
Bluff.each(data_row[this.klass.DATA_VALUES_INDEX], function(data_point, point_index) {
|
1446
|
+
|
1447
|
+
var x_pos = this._graph_left + (data_point * this._graph_width) - Math.round(this._item_width/6.0);
|
1448
|
+
var y_pos = this._graph_top + (this._items_width * point_index) + padding + Math.round(this._item_width/2.0);
|
1449
|
+
|
1450
|
+
if (row_index === 0) {
|
1451
|
+
this._d.stroke = this.marker_color;
|
1452
|
+
this._d.stroke_width = 1.0;
|
1453
|
+
this._d.opacity = 0.1;
|
1454
|
+
this._d.line(this._graph_left, y_pos, this._graph_left + this._graph_width, y_pos);
|
1455
|
+
}
|
1456
|
+
|
1457
|
+
this._d.fill = data_row[this.klass.DATA_COLOR_INDEX];
|
1458
|
+
this._d.stroke = 'transparent';
|
1459
|
+
this._d.circle(x_pos, y_pos, x_pos + Math.round(this._item_width/3.0), y_pos);
|
1460
|
+
|
1461
|
+
// Calculate center based on item_width and current row
|
1462
|
+
var label_center = this._graph_top + (this._items_width * point_index + this._items_width / 2) + padding;
|
1463
|
+
this._draw_label(label_center, point_index);
|
1464
|
+
}, this);
|
1465
|
+
|
1466
|
+
}, this);
|
1467
|
+
},
|
1468
|
+
|
1469
|
+
// Instead of base class version, draws vertical background lines and label
|
1470
|
+
_draw_line_markers: function() {
|
1471
|
+
|
1472
|
+
if (this.hide_line_markers) return;
|
1473
|
+
|
1474
|
+
this._d.stroke_antialias = false;
|
1475
|
+
|
1476
|
+
// Draw horizontal line markers and annotate with numbers
|
1477
|
+
this._d.stroke_width = 1;
|
1478
|
+
var number_of_lines = 5;
|
1479
|
+
|
1480
|
+
// TODO Round maximum marker value to a round number like 100, 0.1, 0.5, etc.
|
1481
|
+
var increment = this._significant(this.maximum_value / number_of_lines);
|
1482
|
+
for (var index = 0; index <= number_of_lines; index++) {
|
1483
|
+
|
1484
|
+
var line_diff = (this._graph_right - this._graph_left) / number_of_lines,
|
1485
|
+
x = this._graph_right - (line_diff * index) - 1,
|
1486
|
+
diff = index - number_of_lines,
|
1487
|
+
marker_label = Math.abs(diff) * increment;
|
1488
|
+
|
1489
|
+
this._d.stroke = this.marker_color;
|
1490
|
+
this._d.line(x, this._graph_bottom, x, this._graph_bottom + 0.5 * this.klass.LABEL_MARGIN);
|
1491
|
+
|
1492
|
+
if (!this.hide_line_numbers) {
|
1493
|
+
this._d.fill = this.font_color;
|
1494
|
+
if (this.font) this._d.font = this.font;
|
1495
|
+
this._d.stroke = 'transparent';
|
1496
|
+
this._d.pointsize = this._scale_fontsize(this.marker_font_size);
|
1497
|
+
this._d.gravity = 'center';
|
1498
|
+
// TODO Center text over line
|
1499
|
+
this._d.annotate_scaled(0, 0, // Width of box to draw text in
|
1500
|
+
x, this._graph_bottom + (this.klass.LABEL_MARGIN * 2.0), // Coordinates of text
|
1501
|
+
marker_label, this._scale);
|
1502
|
+
}
|
1503
|
+
this._d.stroke_antialias = true;
|
1504
|
+
}
|
1505
|
+
},
|
1506
|
+
|
1507
|
+
// Draw on the Y axis instead of the X
|
1508
|
+
_draw_label: function(y_offset, index) {
|
1509
|
+
if (this.labels[index] && !this._labels_seen[index]) {
|
1510
|
+
this._d.fill = this.font_color;
|
1511
|
+
if (this.font) this._d.font = this.font;
|
1512
|
+
this._d.stroke = 'transparent';
|
1513
|
+
this._d.font_weight = 'normal';
|
1514
|
+
this._d.pointsize = this._scale_fontsize(this.marker_font_size);
|
1515
|
+
this._d.gravity = 'east';
|
1516
|
+
this._d.annotate_scaled(1, 1,
|
1517
|
+
this._graph_left - this.klass.LABEL_MARGIN * 2.0, y_offset,
|
1518
|
+
this.labels[index], this._scale);
|
1519
|
+
this._labels_seen[index] = true;
|
1520
|
+
}
|
1521
|
+
}
|
1522
|
+
});
|
1523
|
+
|
1524
|
+
|
1525
|
+
// Experimental!!! See also the Spider graph.
|
1526
|
+
Bluff.Net = new JS.Class(Bluff.Base, {
|
1527
|
+
|
1528
|
+
// Hide parts of the graph to fit more datapoints, or for a different appearance.
|
1529
|
+
hide_dots: null,
|
1530
|
+
|
1531
|
+
//Dimensions of lines and dots; calculated based on dataset size if left unspecified
|
1532
|
+
line_width: null,
|
1533
|
+
dot_radius: null,
|
1534
|
+
|
1535
|
+
initialize: function() {
|
1536
|
+
this.callSuper();
|
1537
|
+
|
1538
|
+
this.hide_dots = false;
|
1539
|
+
this.hide_line_numbers = true;
|
1540
|
+
},
|
1541
|
+
|
1542
|
+
draw: function() {
|
1543
|
+
|
1544
|
+
this.callSuper();
|
1545
|
+
|
1546
|
+
if (!this._has_data) return;
|
1547
|
+
|
1548
|
+
this._radius = this._graph_height / 2.0;
|
1549
|
+
this._center_x = this._graph_left + (this._graph_width / 2.0);
|
1550
|
+
this._center_y = this._graph_top + (this._graph_height / 2.0) - 10; // Move graph up a bit
|
1551
|
+
|
1552
|
+
this._x_increment = this._graph_width / (this._column_count - 1);
|
1553
|
+
var circle_radius = this.dot_radius ||
|
1554
|
+
this._clip_value_if_greater_than(this._columns / (this._norm_data[0][this.klass.DATA_VALUES_INDEX].length * 2.5), 7.0);
|
1555
|
+
|
1556
|
+
this._d.stroke_opacity = 1.0;
|
1557
|
+
this._d.stroke_width = this.line_width ||
|
1558
|
+
this._clip_value_if_greater_than(this._columns / (this._norm_data[0][this.klass.DATA_VALUES_INDEX].length * 4), 3.0);
|
1559
|
+
|
1560
|
+
var level;
|
1561
|
+
|
1562
|
+
if (this._norm_baseline !== undefined) {
|
1563
|
+
level = this._graph_top + (this._graph_height - this._norm_baseline * this._graph_height);
|
1564
|
+
this._d.push();
|
1565
|
+
this._d.stroke_color = this.baseline_color;
|
1566
|
+
this._d.fill_opacity = 0.0;
|
1567
|
+
// this._d.stroke_dasharray(10, 20);
|
1568
|
+
this._d.stroke_width = 5;
|
1569
|
+
this._d.line(this._graph_left, level, this._graph_left + this._graph_width, level);
|
1570
|
+
this._d.pop();
|
1571
|
+
}
|
1572
|
+
|
1573
|
+
Bluff.each(this._norm_data, function(data_row) {
|
1574
|
+
var prev_x = null, prev_y = null;
|
1575
|
+
|
1576
|
+
Bluff.each(data_row[this.klass.DATA_VALUES_INDEX], function(data_point, index) {
|
1577
|
+
if (data_point === undefined) return;
|
1578
|
+
|
1579
|
+
var rad_pos = index * Math.PI * 2 / this._column_count,
|
1580
|
+
point_distance = data_point * this._radius,
|
1581
|
+
start_x = this._center_x + Math.sin(rad_pos) * point_distance,
|
1582
|
+
start_y = this._center_y - Math.cos(rad_pos) * point_distance,
|
1583
|
+
|
1584
|
+
next_index = (index + 1 < data_row[this.klass.DATA_VALUES_INDEX].length) ? index + 1 : 0,
|
1585
|
+
|
1586
|
+
next_rad_pos = next_index * Math.PI * 2 / this._column_count,
|
1587
|
+
next_point_distance = data_row[this.klass.DATA_VALUES_INDEX][next_index] * this._radius,
|
1588
|
+
end_x = this._center_x + Math.sin(next_rad_pos) * next_point_distance,
|
1589
|
+
end_y = this._center_y - Math.cos(next_rad_pos) * next_point_distance;
|
1590
|
+
|
1591
|
+
this._d.stroke = data_row[this.klass.DATA_COLOR_INDEX];
|
1592
|
+
this._d.fill = data_row[this.klass.DATA_COLOR_INDEX];
|
1593
|
+
this._d.line(start_x, start_y, end_x, end_y);
|
1594
|
+
|
1595
|
+
if (!this.hide_dots) this._d.circle(start_x, start_y, start_x - circle_radius, start_y);
|
1596
|
+
}, this);
|
1597
|
+
|
1598
|
+
}, this);
|
1599
|
+
},
|
1600
|
+
|
1601
|
+
// the lines connecting in the center, with the first line vertical
|
1602
|
+
_draw_line_markers: function() {
|
1603
|
+
if (this.hide_line_markers) return;
|
1604
|
+
|
1605
|
+
// have to do this here (AGAIN)... see draw() in this class
|
1606
|
+
// because this funtion is called before the @radius, @center_x and @center_y are set
|
1607
|
+
this._radius = this._graph_height / 2.0;
|
1608
|
+
this._center_x = this._graph_left + (this._graph_width / 2.0);
|
1609
|
+
this._center_y = this._graph_top + (this._graph_height / 2.0) - 10; // Move graph up a bit
|
1610
|
+
|
1611
|
+
var rad_pos, marker_label;
|
1612
|
+
|
1613
|
+
for (var index = 0, n = this._column_count; index < n; index++) {
|
1614
|
+
rad_pos = index * Math.PI * 2 / this._column_count;
|
1615
|
+
|
1616
|
+
// Draw horizontal line markers and annotate with numbers
|
1617
|
+
this._d.stroke = this.marker_color;
|
1618
|
+
this._d.stroke_width = 1;
|
1619
|
+
|
1620
|
+
this._d.line(this._center_x, this._center_y, this._center_x + Math.sin(rad_pos) * this._radius, this._center_y - Math.cos(rad_pos) * this._radius);
|
1621
|
+
|
1622
|
+
marker_label = this.labels[index] ? this.labels[index] : '000';
|
1623
|
+
|
1624
|
+
this._draw_label(this._center_x, this._center_y, rad_pos * 360 / (2 * Math.PI), this._radius, marker_label);
|
1625
|
+
}
|
1626
|
+
},
|
1627
|
+
|
1628
|
+
_draw_label: function(center_x, center_y, angle, radius, amount) {
|
1629
|
+
var r_offset = 1.1,
|
1630
|
+
x_offset = center_x, // + 15 // The label points need to be tweaked slightly
|
1631
|
+
y_offset = center_y, // + 0 // This one doesn't though
|
1632
|
+
rad_pos = angle * Math.PI / 180,
|
1633
|
+
x = x_offset + (radius * r_offset * Math.sin(rad_pos)),
|
1634
|
+
y = y_offset - (radius * r_offset * Math.cos(rad_pos));
|
1635
|
+
|
1636
|
+
// Draw label
|
1637
|
+
this._d.fill = this.marker_color;
|
1638
|
+
if (this.font) this._d.font = this.font;
|
1639
|
+
this._d.pointsize = this._scale_fontsize(20);
|
1640
|
+
this._d.stroke = 'transparent';
|
1641
|
+
this._d.font_weight = 'bold';
|
1642
|
+
this._d.gravity = 'center';
|
1643
|
+
this._d.annotate_scaled(0, 0, x, y, amount, this._scale);
|
1644
|
+
}
|
1645
|
+
});
|
1646
|
+
|
1647
|
+
|
1648
|
+
// Here's how to make a Pie graph:
|
1649
|
+
//
|
1650
|
+
// g = new Bluff.Pie('canvasId');
|
1651
|
+
// g.title = "Visual Pie Graph Test";
|
1652
|
+
// g.data('Fries', 20);
|
1653
|
+
// g.data('Hamburgers', 50);
|
1654
|
+
// g.draw();
|
1655
|
+
//
|
1656
|
+
// To control where the pie chart starts creating slices, use #zero_degree.
|
1657
|
+
|
1658
|
+
Bluff.Pie = new JS.Class(Bluff.Base, {
|
1659
|
+
extend: {
|
1660
|
+
TEXT_OFFSET_PERCENTAGE: 0.08
|
1661
|
+
},
|
1662
|
+
|
1663
|
+
// Can be used to make the pie start cutting slices at the top (-90.0)
|
1664
|
+
// or at another angle. Default is 0.0, which starts at 3 o'clock.
|
1665
|
+
zero_degreee: null,
|
1666
|
+
|
1667
|
+
// Do not show labels for slices that are less than this percent. Use 0 to always show all labels.
|
1668
|
+
hide_labels_less_than: null,
|
1669
|
+
|
1670
|
+
initialize_ivars: function() {
|
1671
|
+
this.callSuper();
|
1672
|
+
this.zero_degree = 0.0;
|
1673
|
+
this.hide_labels_less_than = 0.0;
|
1674
|
+
},
|
1675
|
+
|
1676
|
+
draw: function() {
|
1677
|
+
this.hide_line_markers = true;
|
1678
|
+
|
1679
|
+
this.callSuper();
|
1680
|
+
|
1681
|
+
if (!this._has_data) return;
|
1682
|
+
|
1683
|
+
var diameter = this._graph_height,
|
1684
|
+
radius = (Math.min(this._graph_width, this._graph_height) / 2.0) * 0.8,
|
1685
|
+
top_x = this._graph_left + (this._graph_width - diameter) / 2.0,
|
1686
|
+
center_x = this._graph_left + (this._graph_width / 2.0),
|
1687
|
+
center_y = this._graph_top + (this._graph_height / 2.0) - 10, // Move graph up a bit
|
1688
|
+
total_sum = this._sums_for_pie(),
|
1689
|
+
prev_degrees = this.zero_degree,
|
1690
|
+
index = this.klass.DATA_VALUES_INDEX;
|
1691
|
+
|
1692
|
+
// Use full data since we can easily calculate percentages
|
1693
|
+
if (this.sort) this._data.sort(function(a,b) { return a[index][0] - b[index][0]; });
|
1694
|
+
Bluff.each(this._data, function(data_row, i) {
|
1695
|
+
if (data_row[this.klass.DATA_VALUES_INDEX][0] > 0) {
|
1696
|
+
this._d.fill = data_row[this.klass.DATA_COLOR_INDEX];
|
1697
|
+
|
1698
|
+
var current_degrees = (data_row[this.klass.DATA_VALUES_INDEX][0] / total_sum) * 360;
|
1699
|
+
|
1700
|
+
// Gruff uses ellipse() here, but canvas doesn't seem to support it.
|
1701
|
+
// circle() is fine for our purposes here.
|
1702
|
+
this._d.circle(center_x, center_y,
|
1703
|
+
center_x + radius, center_y,
|
1704
|
+
prev_degrees, prev_degrees + current_degrees + 0.5); // <= +0.5 'fudge factor' gets rid of the ugly gaps
|
1705
|
+
|
1706
|
+
var half_angle = prev_degrees + ((prev_degrees + current_degrees) - prev_degrees) / 2,
|
1707
|
+
label_val = Math.round((data_row[this.klass.DATA_VALUES_INDEX][0] / total_sum) * 100.0),
|
1708
|
+
label_string;
|
1709
|
+
|
1710
|
+
if (label_val >= this.hide_labels_less_than) {
|
1711
|
+
label_string = this._label(data_row[this.klass.DATA_VALUES_INDEX][0]);
|
1712
|
+
this._draw_label(center_x, center_y, half_angle,
|
1713
|
+
radius + (radius * this.klass.TEXT_OFFSET_PERCENTAGE),
|
1714
|
+
label_string,
|
1715
|
+
data_row, i);
|
1716
|
+
}
|
1717
|
+
|
1718
|
+
prev_degrees += current_degrees;
|
1719
|
+
}
|
1720
|
+
}, this);
|
1721
|
+
|
1722
|
+
// TODO debug a circle where the text is drawn...
|
1723
|
+
},
|
1724
|
+
|
1725
|
+
// Labels are drawn around a slightly wider ellipse to give room for
|
1726
|
+
// labels on the left and right.
|
1727
|
+
_draw_label: function(center_x, center_y, angle, radius, amount, data_row, i) {
|
1728
|
+
// TODO Don't use so many hard-coded numbers
|
1729
|
+
var r_offset = 20.0, // The distance out from the center of the pie to get point
|
1730
|
+
x_offset = center_x, // + 15.0 # The label points need to be tweaked slightly
|
1731
|
+
y_offset = center_y, // This one doesn't though
|
1732
|
+
radius_offset = radius + r_offset,
|
1733
|
+
ellipse_factor = radius_offset * 0.15,
|
1734
|
+
x = x_offset + ((radius_offset + ellipse_factor) * Math.cos(angle * Math.PI/180)),
|
1735
|
+
y = y_offset + (radius_offset * Math.sin(angle * Math.PI/180));
|
1736
|
+
|
1737
|
+
// Draw label
|
1738
|
+
this._d.fill = this.font_color;
|
1739
|
+
if (this.font) this._d.font = this.font;
|
1740
|
+
this._d.pointsize = this._scale_fontsize(this.marker_font_size);
|
1741
|
+
this._d.font_weight = 'bold';
|
1742
|
+
this._d.gravity = 'center';
|
1743
|
+
this._d.annotate_scaled(0,0, x,y, amount, this._scale);
|
1744
|
+
|
1745
|
+
this._draw_tooltip(x - 20, y - 20, 40, 40,
|
1746
|
+
data_row[this.klass.DATA_LABEL_INDEX],
|
1747
|
+
data_row[this.klass.DATA_COLOR_INDEX],
|
1748
|
+
amount, i);
|
1749
|
+
},
|
1750
|
+
|
1751
|
+
_sums_for_pie: function() {
|
1752
|
+
var total_sum = 0;
|
1753
|
+
Bluff.each(this._data, function(data_row) {
|
1754
|
+
total_sum += data_row[this.klass.DATA_VALUES_INDEX][0];
|
1755
|
+
}, this);
|
1756
|
+
return total_sum;
|
1757
|
+
}
|
1758
|
+
});
|
1759
|
+
|
1760
|
+
|
1761
|
+
// Graph with individual horizontal bars instead of vertical bars.
|
1762
|
+
|
1763
|
+
Bluff.SideBar = new JS.Class(Bluff.Base, {
|
1764
|
+
|
1765
|
+
// Spacing factor applied between bars
|
1766
|
+
bar_spacing: 0.9,
|
1767
|
+
|
1768
|
+
draw: function() {
|
1769
|
+
this.has_left_labels = true;
|
1770
|
+
this.callSuper();
|
1771
|
+
|
1772
|
+
if (!this._has_data) return;
|
1773
|
+
this._draw_bars();
|
1774
|
+
},
|
1775
|
+
|
1776
|
+
_draw_bars: function() {
|
1777
|
+
this._bars_width = this._graph_height / this._column_count;
|
1778
|
+
this._bar_width = this._bars_width / this._norm_data.length;
|
1779
|
+
this._d.stroke_opacity = 0.0;
|
1780
|
+
var height = Bluff.array_new(this._column_count, 0),
|
1781
|
+
length = Bluff.array_new(this._column_count, this._graph_left),
|
1782
|
+
padding = (this._bar_width * (1 - this.bar_spacing)) / 2;
|
1783
|
+
|
1784
|
+
Bluff.each(this._norm_data, function(data_row, row_index) {
|
1785
|
+
var raw_data = this._data[row_index][this.klass.DATA_VALUES_INDEX];
|
1786
|
+
Bluff.each(data_row[this.klass.DATA_VALUES_INDEX], function(data_point, point_index) {
|
1787
|
+
|
1788
|
+
// Using the original calcs from the stacked bar chart
|
1789
|
+
// to get the difference between
|
1790
|
+
// part of the bart chart we wish to stack.
|
1791
|
+
var temp1 = this._graph_left + (this._graph_width - data_point * this._graph_width - height[point_index]),
|
1792
|
+
temp2 = this._graph_left + this._graph_width - height[point_index],
|
1793
|
+
difference = temp2 - temp1,
|
1794
|
+
|
1795
|
+
left_x = length[point_index] - 1,
|
1796
|
+
left_y = this._graph_top + (this._bars_width * point_index) + (this._bar_width * row_index) + padding,
|
1797
|
+
right_x = left_x + difference,
|
1798
|
+
right_y = left_y + this._bar_width * this.bar_spacing;
|
1799
|
+
|
1800
|
+
height[point_index] += (data_point * this._graph_width);
|
1801
|
+
|
1802
|
+
this._d.stroke = 'transparent';
|
1803
|
+
this._d.fill = data_row[this.klass.DATA_COLOR_INDEX];
|
1804
|
+
this._d.rectangle(left_x, left_y, right_x, right_y);
|
1805
|
+
|
1806
|
+
this._draw_tooltip(left_x, left_y,
|
1807
|
+
right_x - left_x, right_y - left_y,
|
1808
|
+
data_row[this.klass.DATA_LABEL_INDEX],
|
1809
|
+
data_row[this.klass.DATA_COLOR_INDEX],
|
1810
|
+
raw_data[point_index], point_index);
|
1811
|
+
|
1812
|
+
// Calculate center based on bar_width and current row
|
1813
|
+
var label_center = this._graph_top + (this._bars_width * point_index + this._bars_width / 2);
|
1814
|
+
this._draw_label(label_center, point_index);
|
1815
|
+
}, this)
|
1816
|
+
|
1817
|
+
}, this);
|
1818
|
+
},
|
1819
|
+
|
1820
|
+
// Instead of base class version, draws vertical background lines and label
|
1821
|
+
_draw_line_markers: function() {
|
1822
|
+
|
1823
|
+
if (this.hide_line_markers) return;
|
1824
|
+
|
1825
|
+
this._d.stroke_antialias = false;
|
1826
|
+
|
1827
|
+
// Draw horizontal line markers and annotate with numbers
|
1828
|
+
this._d.stroke_width = 1;
|
1829
|
+
var number_of_lines = 5;
|
1830
|
+
|
1831
|
+
// TODO Round maximum marker value to a round number like 100, 0.1, 0.5, etc.
|
1832
|
+
var increment = this._significant(this._spread / number_of_lines),
|
1833
|
+
line_diff, x, diff, marker_label;
|
1834
|
+
for (var index = 0; index <= number_of_lines; index++) {
|
1835
|
+
|
1836
|
+
line_diff = (this._graph_right - this._graph_left) / number_of_lines;
|
1837
|
+
x = this._graph_right - (line_diff * index) - 1;
|
1838
|
+
diff = index - number_of_lines;
|
1839
|
+
marker_label = Math.abs(diff) * increment + this.minimum_value;
|
1840
|
+
|
1841
|
+
this._d.stroke = this.marker_color;
|
1842
|
+
this._d.line(x, this._graph_bottom, x, this._graph_top);
|
1843
|
+
|
1844
|
+
if (!this.hide_line_numbers) {
|
1845
|
+
this._d.fill = this.font_color;
|
1846
|
+
if (this.font) this._d.font = this.font;
|
1847
|
+
this._d.stroke = 'transparent';
|
1848
|
+
this._d.pointsize = this._scale_fontsize(this.marker_font_size);
|
1849
|
+
this._d.gravity = 'center';
|
1850
|
+
// TODO Center text over line
|
1851
|
+
this._d.annotate_scaled(
|
1852
|
+
0, 0, // Width of box to draw text in
|
1853
|
+
x, this._graph_bottom + (this.klass.LABEL_MARGIN * 2.0), // Coordinates of text
|
1854
|
+
this._label(marker_label), this._scale);
|
1855
|
+
}
|
1856
|
+
}
|
1857
|
+
},
|
1858
|
+
|
1859
|
+
// Draw on the Y axis instead of the X
|
1860
|
+
_draw_label: function(y_offset, index) {
|
1861
|
+
if (this.labels[index] && !this._labels_seen[index]) {
|
1862
|
+
this._d.fill = this.font_color;
|
1863
|
+
if (this.font) this._d.font = this.font;
|
1864
|
+
this._d.stroke = 'transparent';
|
1865
|
+
this._d.font_weight = 'normal';
|
1866
|
+
this._d.pointsize = this._scale_fontsize(this.marker_font_size);
|
1867
|
+
this._d.gravity = 'east';
|
1868
|
+
this._d.annotate_scaled(1, 1,
|
1869
|
+
this._graph_left - this.klass.LABEL_MARGIN * 2.0, y_offset,
|
1870
|
+
this.labels[index], this._scale);
|
1871
|
+
this._labels_seen[index] = true;
|
1872
|
+
}
|
1873
|
+
}
|
1874
|
+
});
|
1875
|
+
|
1876
|
+
|
1877
|
+
// Experimental!!! See also the Net graph.
|
1878
|
+
//
|
1879
|
+
// Submitted by Kevin Clark http://glu.ttono.us/
|
1880
|
+
Bluff.Spider = new JS.Class(Bluff.Base, {
|
1881
|
+
|
1882
|
+
// Hide all text
|
1883
|
+
hide_text: null,
|
1884
|
+
hide_axes: null,
|
1885
|
+
transparent_background: null,
|
1886
|
+
|
1887
|
+
initialize: function(renderer, max_value, target_width) {
|
1888
|
+
this.callSuper(renderer, target_width);
|
1889
|
+
this._max_value = max_value;
|
1890
|
+
this.hide_legend = true;
|
1891
|
+
},
|
1892
|
+
|
1893
|
+
draw: function() {
|
1894
|
+
this.hide_line_markers = true;
|
1895
|
+
|
1896
|
+
this.callSuper();
|
1897
|
+
|
1898
|
+
if (!this._has_data) return;
|
1899
|
+
|
1900
|
+
// Setup basic positioning
|
1901
|
+
var diameter = this._graph_height,
|
1902
|
+
radius = this._graph_height / 2.0,
|
1903
|
+
top_x = this._graph_left + (this._graph_width - diameter) / 2.0,
|
1904
|
+
center_x = this._graph_left + (this._graph_width / 2.0),
|
1905
|
+
center_y = this._graph_top + (this._graph_height / 2.0) - 25; // Move graph up a bit
|
1906
|
+
|
1907
|
+
this._unit_length = radius / this._max_value;
|
1908
|
+
|
1909
|
+
var total_sum = this._sums_for_spider(),
|
1910
|
+
prev_degrees = 0.0,
|
1911
|
+
additive_angle = (2 * Math.PI) / this._data.length,
|
1912
|
+
|
1913
|
+
current_angle = 0.0;
|
1914
|
+
|
1915
|
+
// Draw axes
|
1916
|
+
if (!this.hide_axes) this._draw_axes(center_x, center_y, radius, additive_angle);
|
1917
|
+
|
1918
|
+
// Draw polygon
|
1919
|
+
this._draw_polygon(center_x, center_y, additive_angle);
|
1920
|
+
},
|
1921
|
+
|
1922
|
+
_normalize_points: function(value) {
|
1923
|
+
return value * this._unit_length;
|
1924
|
+
},
|
1925
|
+
|
1926
|
+
_draw_label: function(center_x, center_y, angle, radius, amount) {
|
1927
|
+
var r_offset = 50, // The distance out from the center of the pie to get point
|
1928
|
+
x_offset = center_x, // The label points need to be tweaked slightly
|
1929
|
+
y_offset = center_y + 0, // This one doesn't though
|
1930
|
+
x = x_offset + ((radius + r_offset) * Math.cos(angle)),
|
1931
|
+
y = y_offset + ((radius + r_offset) * Math.sin(angle));
|
1932
|
+
|
1933
|
+
// Draw label
|
1934
|
+
this._d.fill = this.marker_color;
|
1935
|
+
if (this.font) this._d.font = this.font;
|
1936
|
+
this._d.pointsize = this._scale_fontsize(this.legend_font_size);
|
1937
|
+
this._d.stroke = 'transparent';
|
1938
|
+
this._d.font_weight = 'bold';
|
1939
|
+
this._d.gravity = 'center';
|
1940
|
+
this._d.annotate_scaled(0, 0,
|
1941
|
+
x, y,
|
1942
|
+
amount, this._scale);
|
1943
|
+
},
|
1944
|
+
|
1945
|
+
_draw_axes: function(center_x, center_y, radius, additive_angle, line_color) {
|
1946
|
+
if (this.hide_axes) return;
|
1947
|
+
|
1948
|
+
var current_angle = 0.0;
|
1949
|
+
|
1950
|
+
Bluff.each(this._data, function(data_row) {
|
1951
|
+
this._d.stroke = line_color || data_row[this.klass.DATA_COLOR_INDEX];
|
1952
|
+
this._d.stroke_width = 5.0;
|
1953
|
+
|
1954
|
+
var x_offset = radius * Math.cos(current_angle);
|
1955
|
+
var y_offset = radius * Math.sin(current_angle);
|
1956
|
+
|
1957
|
+
this._d.line(center_x, center_y,
|
1958
|
+
center_x + x_offset,
|
1959
|
+
center_y + y_offset);
|
1960
|
+
|
1961
|
+
if (!this.hide_text) this._draw_label(center_x, center_y, current_angle, radius, data_row[this.klass.DATA_LABEL_INDEX]);
|
1962
|
+
|
1963
|
+
current_angle += additive_angle;
|
1964
|
+
}, this);
|
1965
|
+
},
|
1966
|
+
|
1967
|
+
_draw_polygon: function(center_x, center_y, additive_angle, color) {
|
1968
|
+
var points = [],
|
1969
|
+
current_angle = 0.0;
|
1970
|
+
Bluff.each(this._data, function(data_row) {
|
1971
|
+
points.push(center_x + this._normalize_points(data_row[this.klass.DATA_VALUES_INDEX][0]) * Math.cos(current_angle));
|
1972
|
+
points.push(center_y + this._normalize_points(data_row[this.klass.DATA_VALUES_INDEX][0]) * Math.sin(current_angle));
|
1973
|
+
current_angle += additive_angle;
|
1974
|
+
}, this);
|
1975
|
+
|
1976
|
+
this._d.stroke_width = 1.0;
|
1977
|
+
this._d.stroke = color || this.marker_color;
|
1978
|
+
this._d.fill = color || this.marker_color;
|
1979
|
+
this._d.fill_opacity = 0.4;
|
1980
|
+
this._d.polyline(points);
|
1981
|
+
},
|
1982
|
+
|
1983
|
+
_sums_for_spider: function() {
|
1984
|
+
var sum = 0.0;
|
1985
|
+
Bluff.each(this._data, function(data_row) {
|
1986
|
+
sum += data_row[this.klass.DATA_VALUES_INDEX][0];
|
1987
|
+
}, this);
|
1988
|
+
return sum;
|
1989
|
+
}
|
1990
|
+
});
|
1991
|
+
|
1992
|
+
|
1993
|
+
// Used by StackedBar and child classes.
|
1994
|
+
Bluff.Base.StackedMixin = new JS.Module({
|
1995
|
+
// Get sum of each stack
|
1996
|
+
_get_maximum_by_stack: function() {
|
1997
|
+
var max_hash = {};
|
1998
|
+
Bluff.each(this._data, function(data_set) {
|
1999
|
+
Bluff.each(data_set[this.klass.DATA_VALUES_INDEX], function(data_point, i) {
|
2000
|
+
if (!max_hash[i]) max_hash[i] = 0.0;
|
2001
|
+
max_hash[i] += data_point;
|
2002
|
+
}, this);
|
2003
|
+
}, this);
|
2004
|
+
|
2005
|
+
// this.maximum_value = 0;
|
2006
|
+
for (var key in max_hash) {
|
2007
|
+
if (max_hash[key] > this.maximum_value) this.maximum_value = max_hash[key];
|
2008
|
+
}
|
2009
|
+
this.minimum_value = 0;
|
2010
|
+
}
|
2011
|
+
});
|
2012
|
+
|
2013
|
+
|
2014
|
+
Bluff.StackedArea = new JS.Class(Bluff.Base, {
|
2015
|
+
include: Bluff.Base.StackedMixin,
|
2016
|
+
last_series_goes_on_bottom: null,
|
2017
|
+
|
2018
|
+
draw: function() {
|
2019
|
+
this._get_maximum_by_stack();
|
2020
|
+
this.callSuper();
|
2021
|
+
|
2022
|
+
if (!this._has_data) return;
|
2023
|
+
|
2024
|
+
this._x_increment = this._graph_width / (this._column_count - 1);
|
2025
|
+
this._d.stroke = 'transparent';
|
2026
|
+
|
2027
|
+
var height = Bluff.array_new(this._column_count, 0);
|
2028
|
+
|
2029
|
+
var data_points = null;
|
2030
|
+
var iterator = this.last_series_goes_on_bottom ? 'reverse_each' : 'each';
|
2031
|
+
Bluff[iterator](this._norm_data, function(data_row) {
|
2032
|
+
var prev_data_points = data_points;
|
2033
|
+
data_points = [];
|
2034
|
+
|
2035
|
+
Bluff.each(data_row[this.klass.DATA_VALUES_INDEX], function(data_point, index) {
|
2036
|
+
// Use incremented x and scaled y
|
2037
|
+
var new_x = this._graph_left + (this._x_increment * index);
|
2038
|
+
var new_y = this._graph_top + (this._graph_height - data_point * this._graph_height - height[index]);
|
2039
|
+
|
2040
|
+
height[index] += (data_point * this._graph_height);
|
2041
|
+
|
2042
|
+
data_points.push(new_x);
|
2043
|
+
data_points.push(new_y);
|
2044
|
+
|
2045
|
+
this._draw_label(new_x, index);
|
2046
|
+
}, this);
|
2047
|
+
|
2048
|
+
var poly_points, i, n;
|
2049
|
+
|
2050
|
+
if (prev_data_points) {
|
2051
|
+
poly_points = Bluff.array(data_points);
|
2052
|
+
for (i = prev_data_points.length/2 - 1; i >= 0; i--) {
|
2053
|
+
poly_points.push(prev_data_points[2*i]);
|
2054
|
+
poly_points.push(prev_data_points[2*i+1]);
|
2055
|
+
}
|
2056
|
+
poly_points.push(data_points[0]);
|
2057
|
+
poly_points.push(data_points[1]);
|
2058
|
+
} else {
|
2059
|
+
poly_points = Bluff.array(data_points);
|
2060
|
+
poly_points.push(this._graph_right);
|
2061
|
+
poly_points.push(this._graph_bottom - 1);
|
2062
|
+
poly_points.push(this._graph_left);
|
2063
|
+
poly_points.push(this._graph_bottom - 1);
|
2064
|
+
poly_points.push(data_points[0]);
|
2065
|
+
poly_points.push(data_points[1]);
|
2066
|
+
}
|
2067
|
+
this._d.fill = data_row[this.klass.DATA_COLOR_INDEX];
|
2068
|
+
this._d.polyline(poly_points);
|
2069
|
+
}, this);
|
2070
|
+
}
|
2071
|
+
});
|
2072
|
+
|
2073
|
+
|
2074
|
+
Bluff.StackedBar = new JS.Class(Bluff.Base, {
|
2075
|
+
include: Bluff.Base.StackedMixin,
|
2076
|
+
|
2077
|
+
// Spacing factor applied between bars
|
2078
|
+
bar_spacing: 0.9,
|
2079
|
+
|
2080
|
+
// Draws a bar graph, but multiple sets are stacked on top of each other.
|
2081
|
+
draw: function() {
|
2082
|
+
this._get_maximum_by_stack();
|
2083
|
+
this.callSuper();
|
2084
|
+
if (!this._has_data) return;
|
2085
|
+
|
2086
|
+
this._bar_width = this._graph_width / this._column_count;
|
2087
|
+
var padding = (this._bar_width * (1 - this.bar_spacing)) / 2;
|
2088
|
+
|
2089
|
+
this._d.stroke_opacity = 0.0;
|
2090
|
+
|
2091
|
+
var height = Bluff.array_new(this._column_count, 0);
|
2092
|
+
|
2093
|
+
Bluff.each(this._norm_data, function(data_row, row_index) {
|
2094
|
+
var raw_data = this._data[row_index][this.klass.DATA_VALUES_INDEX];
|
2095
|
+
|
2096
|
+
Bluff.each(data_row[this.klass.DATA_VALUES_INDEX], function(data_point, point_index) {
|
2097
|
+
// Calculate center based on bar_width and current row
|
2098
|
+
var label_center = this._graph_left + (this._bar_width * point_index) + (this._bar_width * this.bar_spacing / 2.0);
|
2099
|
+
this._draw_label(label_center, point_index);
|
2100
|
+
|
2101
|
+
if (data_point == 0) return;
|
2102
|
+
// Use incremented x and scaled y
|
2103
|
+
var left_x = this._graph_left + (this._bar_width * point_index) + padding;
|
2104
|
+
var left_y = this._graph_top + (this._graph_height -
|
2105
|
+
data_point * this._graph_height -
|
2106
|
+
height[point_index]) + 1;
|
2107
|
+
var right_x = left_x + this._bar_width * this.bar_spacing;
|
2108
|
+
var right_y = this._graph_top + this._graph_height - height[point_index] - 1;
|
2109
|
+
|
2110
|
+
// update the total height of the current stacked bar
|
2111
|
+
height[point_index] += (data_point * this._graph_height);
|
2112
|
+
|
2113
|
+
this._d.fill = data_row[this.klass.DATA_COLOR_INDEX];
|
2114
|
+
this._d.rectangle(left_x, left_y, right_x, right_y);
|
2115
|
+
|
2116
|
+
this._draw_tooltip(left_x, left_y,
|
2117
|
+
right_x - left_x, right_y - left_y,
|
2118
|
+
data_row[this.klass.DATA_LABEL_INDEX],
|
2119
|
+
data_row[this.klass.DATA_COLOR_INDEX],
|
2120
|
+
raw_data[point_index], point_index);
|
2121
|
+
}, this);
|
2122
|
+
}, this);
|
2123
|
+
}
|
2124
|
+
});
|
2125
|
+
|
2126
|
+
|
2127
|
+
// A special bar graph that shows a single dataset as a set of
|
2128
|
+
// stacked bars. The bottom bar shows the running total and
|
2129
|
+
// the top bar shows the new value being added to the array.
|
2130
|
+
|
2131
|
+
Bluff.AccumulatorBar = new JS.Class(Bluff.StackedBar, {
|
2132
|
+
|
2133
|
+
draw: function() {
|
2134
|
+
if (this._data.length !== 1) throw 'Incorrect number of datasets';
|
2135
|
+
|
2136
|
+
var accumulator_array = [],
|
2137
|
+
index = 0,
|
2138
|
+
increment_array = [];
|
2139
|
+
|
2140
|
+
Bluff.each(this._data[0][this.klass.DATA_VALUES_INDEX], function(value) {
|
2141
|
+
var max = -Infinity;
|
2142
|
+
Bluff.each(increment_array, function(x) { max = Math.max(max, x); });
|
2143
|
+
|
2144
|
+
increment_array.push((index > 0) ? (value + max) : value);
|
2145
|
+
accumulator_array.push(increment_array[index] - value);
|
2146
|
+
index += 1;
|
2147
|
+
}, this);
|
2148
|
+
|
2149
|
+
this.data("Accumulator", accumulator_array);
|
2150
|
+
|
2151
|
+
this.callSuper();
|
2152
|
+
}
|
2153
|
+
});
|
2154
|
+
|
2155
|
+
|
2156
|
+
// New gruff graph type added to enable sideways stacking bar charts
|
2157
|
+
// (basically looks like a x/y flip of a standard stacking bar chart)
|
2158
|
+
//
|
2159
|
+
// alun.eyre@googlemail.com
|
2160
|
+
|
2161
|
+
Bluff.SideStackedBar = new JS.Class(Bluff.SideBar, {
|
2162
|
+
include: Bluff.Base.StackedMixin,
|
2163
|
+
|
2164
|
+
// Spacing factor applied between bars
|
2165
|
+
bar_spacing: 0.9,
|
2166
|
+
|
2167
|
+
draw: function() {
|
2168
|
+
this.has_left_labels = true;
|
2169
|
+
this._get_maximum_by_stack();
|
2170
|
+
this.callSuper();
|
2171
|
+
},
|
2172
|
+
|
2173
|
+
_draw_bars: function() {
|
2174
|
+
this._bar_width = this._graph_height / this._column_count;
|
2175
|
+
var height = Bluff.array_new(this._column_count, 0),
|
2176
|
+
length = Bluff.array_new(this._column_count, this._graph_left),
|
2177
|
+
padding = (this._bar_width * (1 - this.bar_spacing)) / 2;
|
2178
|
+
|
2179
|
+
Bluff.each(this._norm_data, function(data_row, row_index) {
|
2180
|
+
var raw_data = this._data[row_index][this.klass.DATA_VALUES_INDEX];
|
2181
|
+
|
2182
|
+
Bluff.each(data_row[this.klass.DATA_VALUES_INDEX], function(data_point, point_index) {
|
2183
|
+
|
2184
|
+
// using the original calcs from the stacked bar chart to get the difference between
|
2185
|
+
// part of the bart chart we wish to stack.
|
2186
|
+
var temp1 = this._graph_left + (this._graph_width -
|
2187
|
+
data_point * this._graph_width -
|
2188
|
+
height[point_index]) + 1;
|
2189
|
+
var temp2 = this._graph_left + this._graph_width - height[point_index] - 1;
|
2190
|
+
var difference = temp2 - temp1;
|
2191
|
+
|
2192
|
+
this._d.fill = data_row[this.klass.DATA_COLOR_INDEX];
|
2193
|
+
|
2194
|
+
var left_x = length[point_index], //+ 1
|
2195
|
+
left_y = this._graph_top + (this._bar_width * point_index) + padding,
|
2196
|
+
right_x = left_x + difference,
|
2197
|
+
right_y = left_y + this._bar_width * this.bar_spacing;
|
2198
|
+
length[point_index] += difference;
|
2199
|
+
height[point_index] += (data_point * this._graph_width - 2);
|
2200
|
+
|
2201
|
+
this._d.rectangle(left_x, left_y, right_x, right_y);
|
2202
|
+
|
2203
|
+
this._draw_tooltip(left_x, left_y,
|
2204
|
+
right_x - left_x, right_y - left_y,
|
2205
|
+
data_row[this.klass.DATA_LABEL_INDEX],
|
2206
|
+
data_row[this.klass.DATA_COLOR_INDEX],
|
2207
|
+
raw_data[point_index], point_index);
|
2208
|
+
|
2209
|
+
// Calculate center based on bar_width and current row
|
2210
|
+
var label_center = this._graph_top + (this._bar_width * point_index) + (this._bar_width * this.bar_spacing / 2.0);
|
2211
|
+
this._draw_label(label_center, point_index);
|
2212
|
+
}, this);
|
2213
|
+
}, this);
|
2214
|
+
},
|
2215
|
+
|
2216
|
+
_larger_than_max: function(data_point, index) {
|
2217
|
+
index = index || 0;
|
2218
|
+
return this._max(data_point, index) > this.maximum_value;
|
2219
|
+
},
|
2220
|
+
|
2221
|
+
_max: function(data_point, index) {
|
2222
|
+
var sum = 0;
|
2223
|
+
Bluff.each(this._data, function(item) {
|
2224
|
+
sum += item[this.klass.DATA_VALUES_INDEX][index];
|
2225
|
+
}, this);
|
2226
|
+
return sum;
|
2227
|
+
}
|
2228
|
+
});
|
2229
|
+
|
2230
|
+
|
2231
|
+
Bluff.Mini.Legend = new JS.Module({
|
2232
|
+
|
2233
|
+
hide_mini_legend: false,
|
2234
|
+
|
2235
|
+
// The canvas needs to be bigger so we can put the legend beneath it.
|
2236
|
+
_expand_canvas_for_vertical_legend: function() {
|
2237
|
+
if (this.hide_mini_legend) return;
|
2238
|
+
|
2239
|
+
this._legend_labels = Bluff.map(this._data, function(item) {
|
2240
|
+
return item[this.klass.DATA_LABEL_INDEX];
|
2241
|
+
}, this);
|
2242
|
+
|
2243
|
+
var legend_height = this._scale_fontsize(
|
2244
|
+
this._data.length * this._calculate_line_height() +
|
2245
|
+
this.top_margin + this.bottom_margin);
|
2246
|
+
|
2247
|
+
this._original_rows = this._raw_rows;
|
2248
|
+
this._original_columns = this._raw_columns;
|
2249
|
+
|
2250
|
+
switch (this.legend_position) {
|
2251
|
+
case 'right':
|
2252
|
+
this._rows = Math.max(this._rows, legend_height);
|
2253
|
+
this._columns += this._calculate_legend_width() + this.left_margin;
|
2254
|
+
break;
|
2255
|
+
|
2256
|
+
default:
|
2257
|
+
this._rows += legend_height;
|
2258
|
+
break;
|
2259
|
+
}
|
2260
|
+
this._render_background();
|
2261
|
+
},
|
2262
|
+
|
2263
|
+
_calculate_line_height: function() {
|
2264
|
+
return this._calculate_caps_height(this.legend_font_size) * 1.7;
|
2265
|
+
},
|
2266
|
+
|
2267
|
+
_calculate_legend_width: function() {
|
2268
|
+
var width = 0;
|
2269
|
+
Bluff.each(this._legend_labels, function(label) {
|
2270
|
+
width = Math.max(this._calculate_width(this.legend_font_size, label), width);
|
2271
|
+
}, this);
|
2272
|
+
return this._scale_fontsize(width + 40*1.7);
|
2273
|
+
},
|
2274
|
+
|
2275
|
+
// Draw the legend beneath the existing graph.
|
2276
|
+
_draw_vertical_legend: function() {
|
2277
|
+
if (this.hide_mini_legend) return;
|
2278
|
+
|
2279
|
+
var legend_square_width = 40.0, // small square with color of this item
|
2280
|
+
legend_square_margin = 10.0,
|
2281
|
+
legend_left_margin = 100.0,
|
2282
|
+
legend_top_margin = 40.0;
|
2283
|
+
|
2284
|
+
// May fix legend drawing problem at small sizes
|
2285
|
+
if (this.font) this._d.font = this.font;
|
2286
|
+
this._d.pointsize = this.legend_font_size;
|
2287
|
+
|
2288
|
+
var current_x_offset, current_y_offset;
|
2289
|
+
|
2290
|
+
switch (this.legend_position) {
|
2291
|
+
case 'right':
|
2292
|
+
current_x_offset = this._original_columns + this.left_margin;
|
2293
|
+
current_y_offset = this.top_margin + legend_top_margin;
|
2294
|
+
break;
|
2295
|
+
|
2296
|
+
default:
|
2297
|
+
current_x_offset = legend_left_margin,
|
2298
|
+
current_y_offset = this._original_rows + legend_top_margin;
|
2299
|
+
break;
|
2300
|
+
}
|
2301
|
+
|
2302
|
+
this._debug(function() {
|
2303
|
+
this._d.line(0.0, current_y_offset, this._raw_columns, current_y_offset);
|
2304
|
+
});
|
2305
|
+
|
2306
|
+
Bluff.each(this._legend_labels, function(legend_label, index) {
|
2307
|
+
|
2308
|
+
// Draw label
|
2309
|
+
this._d.fill = this.font_color;
|
2310
|
+
if (this.font) this._d.font = this.font;
|
2311
|
+
this._d.pointsize = this._scale_fontsize(this.legend_font_size);
|
2312
|
+
this._d.stroke = 'transparent';
|
2313
|
+
this._d.font_weight = 'normal';
|
2314
|
+
this._d.gravity = 'west';
|
2315
|
+
this._d.annotate_scaled(this._raw_columns, 1.0,
|
2316
|
+
current_x_offset + (legend_square_width * 1.7), current_y_offset,
|
2317
|
+
this._truncate_legend_label(legend_label), this._scale);
|
2318
|
+
|
2319
|
+
// Now draw box with color of this dataset
|
2320
|
+
this._d.stroke = 'transparent';
|
2321
|
+
this._d.fill = this._data[index][this.klass.DATA_COLOR_INDEX];
|
2322
|
+
this._d.rectangle(current_x_offset,
|
2323
|
+
current_y_offset - legend_square_width / 2.0,
|
2324
|
+
current_x_offset + legend_square_width,
|
2325
|
+
current_y_offset + legend_square_width / 2.0);
|
2326
|
+
|
2327
|
+
current_y_offset += this._calculate_line_height();
|
2328
|
+
}, this);
|
2329
|
+
this._color_index = 0;
|
2330
|
+
},
|
2331
|
+
|
2332
|
+
// Shorten long labels so they will fit on the canvas.
|
2333
|
+
_truncate_legend_label: function(label) {
|
2334
|
+
var truncated_label = String(label);
|
2335
|
+
while (this._calculate_width(this._scale_fontsize(this.legend_font_size), truncated_label) > (this._columns - this.legend_left_margin - this.right_margin) && (truncated_label.length > 1))
|
2336
|
+
truncated_label = truncated_label.substr(0, truncated_label.length-1);
|
2337
|
+
return truncated_label + (truncated_label.length < String(label).length ? "..." : '');
|
2338
|
+
}
|
2339
|
+
});
|
2340
|
+
|
2341
|
+
|
2342
|
+
// Makes a small bar graph suitable for display at 200px or even smaller.
|
2343
|
+
//
|
2344
|
+
Bluff.Mini.Bar = new JS.Class(Bluff.Bar, {
|
2345
|
+
include: Bluff.Mini.Legend,
|
2346
|
+
|
2347
|
+
initialize_ivars: function() {
|
2348
|
+
this.callSuper();
|
2349
|
+
|
2350
|
+
this.hide_legend = true;
|
2351
|
+
this.hide_title = true;
|
2352
|
+
this.hide_line_numbers = true;
|
2353
|
+
|
2354
|
+
this.marker_font_size = 50.0;
|
2355
|
+
this.minimum_value = 0.0;
|
2356
|
+
this.maximum_value = 0.0;
|
2357
|
+
this.legend_font_size = 60.0;
|
2358
|
+
},
|
2359
|
+
|
2360
|
+
draw: function() {
|
2361
|
+
this._expand_canvas_for_vertical_legend();
|
2362
|
+
|
2363
|
+
this.callSuper();
|
2364
|
+
|
2365
|
+
this._draw_vertical_legend();
|
2366
|
+
}
|
2367
|
+
});
|
2368
|
+
|
2369
|
+
|
2370
|
+
// Makes a small pie graph suitable for display at 200px or even smaller.
|
2371
|
+
//
|
2372
|
+
Bluff.Mini.Pie = new JS.Class(Bluff.Pie, {
|
2373
|
+
include: Bluff.Mini.Legend,
|
2374
|
+
|
2375
|
+
initialize_ivars: function() {
|
2376
|
+
this.callSuper();
|
2377
|
+
|
2378
|
+
this.hide_legend = true;
|
2379
|
+
this.hide_title = true;
|
2380
|
+
this.hide_line_numbers = true;
|
2381
|
+
|
2382
|
+
this.marker_font_size = 60.0;
|
2383
|
+
this.legend_font_size = 60.0;
|
2384
|
+
},
|
2385
|
+
|
2386
|
+
draw: function() {
|
2387
|
+
this._expand_canvas_for_vertical_legend();
|
2388
|
+
|
2389
|
+
this.callSuper();
|
2390
|
+
|
2391
|
+
this._draw_vertical_legend();
|
2392
|
+
}
|
2393
|
+
});
|
2394
|
+
|
2395
|
+
|
2396
|
+
// Makes a small pie graph suitable for display at 200px or even smaller.
|
2397
|
+
//
|
2398
|
+
Bluff.Mini.SideBar = new JS.Class(Bluff.SideBar, {
|
2399
|
+
include: Bluff.Mini.Legend,
|
2400
|
+
|
2401
|
+
initialize_ivars: function() {
|
2402
|
+
this.callSuper();
|
2403
|
+
this.hide_legend = true;
|
2404
|
+
this.hide_title = true;
|
2405
|
+
this.hide_line_numbers = true;
|
2406
|
+
|
2407
|
+
this.marker_font_size = 50.0;
|
2408
|
+
this.legend_font_size = 50.0;
|
2409
|
+
},
|
2410
|
+
|
2411
|
+
draw: function() {
|
2412
|
+
this._expand_canvas_for_vertical_legend();
|
2413
|
+
|
2414
|
+
this.callSuper();
|
2415
|
+
|
2416
|
+
this._draw_vertical_legend();
|
2417
|
+
}
|
2418
|
+
});
|
2419
|
+
|
2420
|
+
|
2421
|
+
Bluff.Renderer = new JS.Class({
|
2422
|
+
extend: {
|
2423
|
+
WRAPPER_CLASS: 'bluff-wrapper',
|
2424
|
+
TEXT_CLASS: 'bluff-text',
|
2425
|
+
TARGET_CLASS: 'bluff-tooltip-target'
|
2426
|
+
},
|
2427
|
+
|
2428
|
+
font: 'Arial, Helvetica, Verdana, sans-serif',
|
2429
|
+
gravity: 'north',
|
2430
|
+
|
2431
|
+
initialize: function(canvasId) {
|
2432
|
+
this._canvas = document.getElementById(canvasId);
|
2433
|
+
this._ctx = this._canvas.getContext('2d');
|
2434
|
+
},
|
2435
|
+
|
2436
|
+
scale: function(sx, sy) {
|
2437
|
+
this._sx = sx;
|
2438
|
+
this._sy = sy || sx;
|
2439
|
+
},
|
2440
|
+
|
2441
|
+
caps_height: function(font_size) {
|
2442
|
+
var X = this._sized_text(font_size, 'X'),
|
2443
|
+
height = this._element_size(X).height;
|
2444
|
+
this._remove_node(X);
|
2445
|
+
return height;
|
2446
|
+
},
|
2447
|
+
|
2448
|
+
text_width: function(font_size, text) {
|
2449
|
+
var element = this._sized_text(font_size, text);
|
2450
|
+
var width = this._element_size(element).width;
|
2451
|
+
this._remove_node(element);
|
2452
|
+
return width;
|
2453
|
+
},
|
2454
|
+
|
2455
|
+
get_type_metrics: function(text) {
|
2456
|
+
var node = this._sized_text(this.pointsize, text);
|
2457
|
+
document.body.appendChild(node);
|
2458
|
+
var size = this._element_size(node);
|
2459
|
+
this._remove_node(node);
|
2460
|
+
return size;
|
2461
|
+
},
|
2462
|
+
|
2463
|
+
clear: function(width, height) {
|
2464
|
+
this._canvas.width = width;
|
2465
|
+
this._canvas.height = height;
|
2466
|
+
this._ctx.clearRect(0, 0, width, height);
|
2467
|
+
var wrapper = this._text_container(), children = wrapper.childNodes, i = children.length;
|
2468
|
+
wrapper.style.width = width + 'px';
|
2469
|
+
wrapper.style.height = height + 'px';
|
2470
|
+
while (i--) {
|
2471
|
+
if (children[i].tagName.toLowerCase() !== 'canvas') {
|
2472
|
+
Bluff.Event.stopObserving(children[i]);
|
2473
|
+
this._remove_node(children[i]);
|
2474
|
+
}
|
2475
|
+
}
|
2476
|
+
},
|
2477
|
+
|
2478
|
+
push: function() {
|
2479
|
+
this._ctx.save();
|
2480
|
+
},
|
2481
|
+
|
2482
|
+
pop: function() {
|
2483
|
+
this._ctx.restore();
|
2484
|
+
},
|
2485
|
+
|
2486
|
+
render_gradiated_background: function(width, height, top_color, bottom_color) {
|
2487
|
+
this.clear(width, height);
|
2488
|
+
var gradient = this._ctx.createLinearGradient(0,0, 0,height);
|
2489
|
+
gradient.addColorStop(0, top_color);
|
2490
|
+
gradient.addColorStop(1, bottom_color);
|
2491
|
+
this._ctx.fillStyle = gradient;
|
2492
|
+
this._ctx.fillRect(0, 0, width, height);
|
2493
|
+
},
|
2494
|
+
|
2495
|
+
render_solid_background: function(width, height, color) {
|
2496
|
+
this.clear(width, height);
|
2497
|
+
this._ctx.fillStyle = color;
|
2498
|
+
this._ctx.fillRect(0, 0, width, height);
|
2499
|
+
},
|
2500
|
+
|
2501
|
+
annotate_scaled: function(width, height, x, y, text, scale) {
|
2502
|
+
var scaled_width = (width * scale) >= 1 ? (width * scale) : 1;
|
2503
|
+
var scaled_height = (height * scale) >= 1 ? (height * scale) : 1;
|
2504
|
+
var text = this._sized_text(this.pointsize, text);
|
2505
|
+
text.style.color = this.fill;
|
2506
|
+
text.style.cursor = 'default';
|
2507
|
+
text.style.fontWeight = this.font_weight;
|
2508
|
+
text.style.textAlign = 'center';
|
2509
|
+
text.style.left = (this._sx * x + this._left_adjustment(text, scaled_width)) + 'px';
|
2510
|
+
text.style.top = (this._sy * y + this._top_adjustment(text, scaled_height)) + 'px';
|
2511
|
+
},
|
2512
|
+
|
2513
|
+
tooltip: function(left, top, width, height, name, color, data) {
|
2514
|
+
if (width < 0) left += width;
|
2515
|
+
if (height < 0) top += height;
|
2516
|
+
|
2517
|
+
var wrapper = this._canvas.parentNode,
|
2518
|
+
target = document.createElement('div');
|
2519
|
+
target.className = this.klass.TARGET_CLASS;
|
2520
|
+
target.style.cursor = 'default';
|
2521
|
+
target.style.position = 'absolute';
|
2522
|
+
target.style.left = (this._sx * left - 3) + 'px';
|
2523
|
+
target.style.top = (this._sy * top - 3) + 'px';
|
2524
|
+
target.style.width = (this._sx * Math.abs(width) + 5) + 'px';
|
2525
|
+
target.style.height = (this._sy * Math.abs(height) + 5) + 'px';
|
2526
|
+
target.style.fontSize = 0;
|
2527
|
+
target.style.overflow = 'hidden';
|
2528
|
+
|
2529
|
+
Bluff.Event.observe(target, 'mouseover', function(node) {
|
2530
|
+
Bluff.Tooltip.show(name, color, data);
|
2531
|
+
});
|
2532
|
+
Bluff.Event.observe(target, 'mouseout', function(node) {
|
2533
|
+
Bluff.Tooltip.hide();
|
2534
|
+
});
|
2535
|
+
|
2536
|
+
wrapper.appendChild(target);
|
2537
|
+
return target;
|
2538
|
+
},
|
2539
|
+
|
2540
|
+
circle: function(origin_x, origin_y, perim_x, perim_y, arc_start, arc_end) {
|
2541
|
+
var radius = Math.sqrt(Math.pow(perim_x - origin_x, 2) + Math.pow(perim_y - origin_y, 2));
|
2542
|
+
var alpha = 0, beta = 2 * Math.PI; // radians to full circle
|
2543
|
+
|
2544
|
+
this._ctx.fillStyle = this.fill;
|
2545
|
+
this._ctx.beginPath();
|
2546
|
+
|
2547
|
+
if (arc_start !== undefined && arc_end !== undefined &&
|
2548
|
+
Math.abs(Math.floor(arc_end - arc_start)) !== 360) {
|
2549
|
+
alpha = arc_start * Math.PI/180;
|
2550
|
+
beta = arc_end * Math.PI/180;
|
2551
|
+
|
2552
|
+
this._ctx.moveTo(this._sx * (origin_x + radius * Math.cos(beta)), this._sy * (origin_y + radius * Math.sin(beta)));
|
2553
|
+
this._ctx.lineTo(this._sx * origin_x, this._sy * origin_y);
|
2554
|
+
this._ctx.lineTo(this._sx * (origin_x + radius * Math.cos(alpha)), this._sy * (origin_y + radius * Math.sin(alpha)));
|
2555
|
+
}
|
2556
|
+
this._ctx.arc(this._sx * origin_x, this._sy * origin_y, this._sx * radius, alpha, beta, false); // draw it clockwise
|
2557
|
+
this._ctx.fill();
|
2558
|
+
},
|
2559
|
+
|
2560
|
+
line: function(sx, sy, ex, ey) {
|
2561
|
+
this._ctx.strokeStyle = this.stroke;
|
2562
|
+
this._ctx.lineWidth = this.stroke_width;
|
2563
|
+
this._ctx.beginPath();
|
2564
|
+
this._ctx.moveTo(this._sx * sx, this._sy * sy);
|
2565
|
+
this._ctx.lineTo(this._sx * ex, this._sy * ey);
|
2566
|
+
this._ctx.stroke();
|
2567
|
+
},
|
2568
|
+
|
2569
|
+
polyline: function(points) {
|
2570
|
+
this._ctx.fillStyle = this.fill;
|
2571
|
+
this._ctx.globalAlpha = this.fill_opacity || 1;
|
2572
|
+
try { this._ctx.strokeStyle = this.stroke; } catch (e) {}
|
2573
|
+
var x = points.shift(), y = points.shift();
|
2574
|
+
this._ctx.beginPath();
|
2575
|
+
this._ctx.moveTo(this._sx * x, this._sy * y);
|
2576
|
+
while (points.length > 0) {
|
2577
|
+
x = points.shift(); y = points.shift();
|
2578
|
+
this._ctx.lineTo(this._sx * x, this._sy * y);
|
2579
|
+
}
|
2580
|
+
this._ctx.fill();
|
2581
|
+
},
|
2582
|
+
|
2583
|
+
rectangle: function(ax, ay, bx, by) {
|
2584
|
+
var temp;
|
2585
|
+
if (ax > bx) { temp = ax; ax = bx; bx = temp; }
|
2586
|
+
if (ay > by) { temp = ay; ay = by; by = temp; }
|
2587
|
+
try {
|
2588
|
+
this._ctx.fillStyle = this.fill;
|
2589
|
+
this._ctx.fillRect(this._sx * ax, this._sy * ay, this._sx * (bx-ax), this._sy * (by-ay));
|
2590
|
+
} catch (e) {}
|
2591
|
+
try {
|
2592
|
+
this._ctx.strokeStyle = this.stroke;
|
2593
|
+
if (this.stroke !== 'transparent')
|
2594
|
+
this._ctx.strokeRect(this._sx * ax, this._sy * ay, this._sx * (bx-ax), this._sy * (by-ay));
|
2595
|
+
} catch (e) {}
|
2596
|
+
},
|
2597
|
+
|
2598
|
+
_left_adjustment: function(node, width) {
|
2599
|
+
var w = this._element_size(node).width;
|
2600
|
+
switch (this.gravity) {
|
2601
|
+
case 'west': return 0;
|
2602
|
+
case 'east': return width - w;
|
2603
|
+
case 'north': case 'south': case 'center':
|
2604
|
+
return (width - w) / 2;
|
2605
|
+
}
|
2606
|
+
},
|
2607
|
+
|
2608
|
+
_top_adjustment: function(node, height) {
|
2609
|
+
var h = this._element_size(node).height;
|
2610
|
+
switch (this.gravity) {
|
2611
|
+
case 'north': return 0;
|
2612
|
+
case 'south': return height - h;
|
2613
|
+
case 'west': case 'east': case 'center':
|
2614
|
+
return (height - h) / 2;
|
2615
|
+
}
|
2616
|
+
},
|
2617
|
+
|
2618
|
+
_text_container: function() {
|
2619
|
+
var wrapper = this._canvas.parentNode;
|
2620
|
+
if (wrapper.className === this.klass.WRAPPER_CLASS) return wrapper;
|
2621
|
+
wrapper = document.createElement('div');
|
2622
|
+
wrapper.className = this.klass.WRAPPER_CLASS;
|
2623
|
+
|
2624
|
+
wrapper.style.position = 'relative';
|
2625
|
+
wrapper.style.border = 'none';
|
2626
|
+
wrapper.style.padding = '0 0 0 0';
|
2627
|
+
|
2628
|
+
this._canvas.parentNode.insertBefore(wrapper, this._canvas);
|
2629
|
+
wrapper.appendChild(this._canvas);
|
2630
|
+
return wrapper;
|
2631
|
+
},
|
2632
|
+
|
2633
|
+
_sized_text: function(size, content) {
|
2634
|
+
var text = this._text_node(content);
|
2635
|
+
text.style.fontFamily = this.font;
|
2636
|
+
text.style.fontSize = (typeof size === 'number') ? size + 'px' : size;
|
2637
|
+
return text;
|
2638
|
+
},
|
2639
|
+
|
2640
|
+
_text_node: function(content) {
|
2641
|
+
var div = document.createElement('div');
|
2642
|
+
div.className = this.klass.TEXT_CLASS;
|
2643
|
+
div.style.position = 'absolute';
|
2644
|
+
div.appendChild(document.createTextNode(content));
|
2645
|
+
this._text_container().appendChild(div);
|
2646
|
+
return div;
|
2647
|
+
},
|
2648
|
+
|
2649
|
+
_remove_node: function(node) {
|
2650
|
+
node.parentNode.removeChild(node);
|
2651
|
+
if (node.className === this.klass.TARGET_CLASS)
|
2652
|
+
Bluff.Event.stopObserving(node);
|
2653
|
+
},
|
2654
|
+
|
2655
|
+
_element_size: function(element) {
|
2656
|
+
var display = element.style.display;
|
2657
|
+
return (display && display !== 'none')
|
2658
|
+
? {width: element.offsetWidth, height: element.offsetHeight}
|
2659
|
+
: {width: element.clientWidth, height: element.clientHeight};
|
2660
|
+
}
|
2661
|
+
});
|
2662
|
+
|
2663
|
+
|
2664
|
+
// DOM event module, adapted from Prototype
|
2665
|
+
// Copyright (c) 2005-2008 Sam Stephenson
|
2666
|
+
|
2667
|
+
Bluff.Event = {
|
2668
|
+
_cache: [],
|
2669
|
+
|
2670
|
+
_isIE: (window.attachEvent && navigator.userAgent.indexOf('Opera') === -1),
|
2671
|
+
|
2672
|
+
observe: function(element, eventName, callback, scope) {
|
2673
|
+
var handlers = Bluff.map(this._handlersFor(element, eventName),
|
2674
|
+
function(entry) { return entry._handler });
|
2675
|
+
if (Bluff.index(handlers, callback) !== -1) return;
|
2676
|
+
|
2677
|
+
var responder = function(event) {
|
2678
|
+
callback.call(scope || null, element, Bluff.Event._extend(event));
|
2679
|
+
};
|
2680
|
+
this._cache.push({_node: element, _name: eventName,
|
2681
|
+
_handler: callback, _responder: responder});
|
2682
|
+
|
2683
|
+
if (element.addEventListener)
|
2684
|
+
element.addEventListener(eventName, responder, false);
|
2685
|
+
else
|
2686
|
+
element.attachEvent('on' + eventName, responder);
|
2687
|
+
},
|
2688
|
+
|
2689
|
+
stopObserving: function(element) {
|
2690
|
+
var handlers = element ? this._handlersFor(element) : this._cache;
|
2691
|
+
Bluff.each(handlers, function(entry) {
|
2692
|
+
if (entry._node.removeEventListener)
|
2693
|
+
entry._node.removeEventListener(entry._name, entry._responder, false);
|
2694
|
+
else
|
2695
|
+
entry._node.detachEvent('on' + entry._name, entry._responder);
|
2696
|
+
});
|
2697
|
+
},
|
2698
|
+
|
2699
|
+
_handlersFor: function(element, eventName) {
|
2700
|
+
var results = [];
|
2701
|
+
Bluff.each(this._cache, function(entry) {
|
2702
|
+
if (element && entry._node !== element) return;
|
2703
|
+
if (eventName && entry._name !== eventName) return;
|
2704
|
+
results.push(entry);
|
2705
|
+
});
|
2706
|
+
return results;
|
2707
|
+
},
|
2708
|
+
|
2709
|
+
_extend: function(event) {
|
2710
|
+
if (!this._isIE) return event;
|
2711
|
+
if (!event) return false;
|
2712
|
+
if (event._extendedByBluff) return event;
|
2713
|
+
event._extendedByBluff = true;
|
2714
|
+
|
2715
|
+
var pointer = this._pointer(event);
|
2716
|
+
event.target = event.srcElement;
|
2717
|
+
event.pageX = pointer.x;
|
2718
|
+
event.pageY = pointer.y;
|
2719
|
+
|
2720
|
+
return event;
|
2721
|
+
},
|
2722
|
+
|
2723
|
+
_pointer: function(event) {
|
2724
|
+
var docElement = document.documentElement,
|
2725
|
+
body = document.body || { scrollLeft: 0, scrollTop: 0 };
|
2726
|
+
return {
|
2727
|
+
x: event.pageX || (event.clientX +
|
2728
|
+
(docElement.scrollLeft || body.scrollLeft) -
|
2729
|
+
(docElement.clientLeft || 0)),
|
2730
|
+
y: event.pageY || (event.clientY +
|
2731
|
+
(docElement.scrollTop || body.scrollTop) -
|
2732
|
+
(docElement.clientTop || 0))
|
2733
|
+
};
|
2734
|
+
}
|
2735
|
+
};
|
2736
|
+
|
2737
|
+
if (Bluff.Event._isIE)
|
2738
|
+
window.attachEvent('onunload', function() {
|
2739
|
+
Bluff.Event.stopObserving();
|
2740
|
+
Bluff.Event._cache = null;
|
2741
|
+
});
|
2742
|
+
|
2743
|
+
if (navigator.userAgent.indexOf('AppleWebKit/') > -1)
|
2744
|
+
window.addEventListener('unload', function() {}, false);
|
2745
|
+
|
2746
|
+
|
2747
|
+
Bluff.Tooltip = new JS.Singleton({
|
2748
|
+
LEFT_OFFSET: 20,
|
2749
|
+
TOP_OFFSET: -6,
|
2750
|
+
DATA_LENGTH: 8,
|
2751
|
+
|
2752
|
+
CLASS_NAME: 'bluff-tooltip',
|
2753
|
+
|
2754
|
+
setup: function() {
|
2755
|
+
this._tip = document.createElement('div');
|
2756
|
+
this._tip.className = this.CLASS_NAME;
|
2757
|
+
this._tip.style.position = 'absolute';
|
2758
|
+
this.hide();
|
2759
|
+
document.body.appendChild(this._tip);
|
2760
|
+
|
2761
|
+
Bluff.Event.observe(document.body, 'mousemove', function(body, event) {
|
2762
|
+
this._tip.style.left = (event.pageX + this.LEFT_OFFSET) + 'px';
|
2763
|
+
this._tip.style.top = (event.pageY + this.TOP_OFFSET) + 'px';
|
2764
|
+
}, this);
|
2765
|
+
},
|
2766
|
+
|
2767
|
+
show: function(name, color, data) {
|
2768
|
+
data = Number(String(data).substr(0, this.DATA_LENGTH));
|
2769
|
+
this._tip.innerHTML = '<span class="color" style="background: ' + color + ';"> </span> ' +
|
2770
|
+
'<span class="label">' + name + '</span> ' +
|
2771
|
+
'<span class="data">' + data + '</span>';
|
2772
|
+
this._tip.style.display = '';
|
2773
|
+
},
|
2774
|
+
|
2775
|
+
hide: function() {
|
2776
|
+
this._tip.style.display = 'none';
|
2777
|
+
}
|
2778
|
+
});
|
2779
|
+
|
2780
|
+
Bluff.Event.observe(window, 'load', Bluff.Tooltip.method('setup'));
|
2781
|
+
|
2782
|
+
|
2783
|
+
Bluff.TableReader = new JS.Class({
|
2784
|
+
|
2785
|
+
NUMBER_FORMAT: /\-?(0|[1-9]\d*)(\.\d+)?(e[\+\-]?\d+)?/i,
|
2786
|
+
|
2787
|
+
initialize: function(table, options) {
|
2788
|
+
this._options = options || {};
|
2789
|
+
this._orientation = this._options.orientation || 'auto';
|
2790
|
+
|
2791
|
+
this._table = (typeof table === 'string')
|
2792
|
+
? document.getElementById(table)
|
2793
|
+
: table;
|
2794
|
+
},
|
2795
|
+
|
2796
|
+
// Get array of data series from the table
|
2797
|
+
get_data: function() {
|
2798
|
+
if (!this._data) this._read();
|
2799
|
+
return this._data;
|
2800
|
+
},
|
2801
|
+
|
2802
|
+
// Get set of axis labels to use for the graph
|
2803
|
+
get_labels: function() {
|
2804
|
+
if (!this._labels) this._read();
|
2805
|
+
return this._labels;
|
2806
|
+
},
|
2807
|
+
|
2808
|
+
// Get the title from the table's caption
|
2809
|
+
get_title: function() {
|
2810
|
+
return this._title;
|
2811
|
+
},
|
2812
|
+
|
2813
|
+
// Return series number i
|
2814
|
+
get_series: function(i) {
|
2815
|
+
if (this._data[i]) return this._data[i];
|
2816
|
+
return this._data[i] = {points: []};
|
2817
|
+
},
|
2818
|
+
|
2819
|
+
// Gather data by reading from the table
|
2820
|
+
_read: function() {
|
2821
|
+
this._row = this._col = 0;
|
2822
|
+
this._row_offset = this._col_offset = 0;
|
2823
|
+
this._data = [];
|
2824
|
+
this._labels = {};
|
2825
|
+
this._row_headings = [];
|
2826
|
+
this._col_headings = [];
|
2827
|
+
this._skip_rows = [];
|
2828
|
+
this._skip_cols = [];
|
2829
|
+
|
2830
|
+
this._walk(this._table);
|
2831
|
+
this._cleanup();
|
2832
|
+
this._orient();
|
2833
|
+
|
2834
|
+
Bluff.each(this._col_headings, function(heading, i) {
|
2835
|
+
this.get_series(i - this._col_offset).name = heading;
|
2836
|
+
}, this);
|
2837
|
+
|
2838
|
+
Bluff.each(this._row_headings, function(heading, i) {
|
2839
|
+
this._labels[i - this._row_offset] = heading;
|
2840
|
+
}, this);
|
2841
|
+
},
|
2842
|
+
|
2843
|
+
// Walk the table's DOM tree
|
2844
|
+
_walk: function(node) {
|
2845
|
+
this._visit(node);
|
2846
|
+
var i, children = node.childNodes, n = children.length;
|
2847
|
+
for (i = 0; i < n; i++) this._walk(children[i]);
|
2848
|
+
},
|
2849
|
+
|
2850
|
+
// Read a single DOM node from the table
|
2851
|
+
_visit: function(node) {
|
2852
|
+
if (!node.tagName) return;
|
2853
|
+
var content = this._strip_tags(node.innerHTML), x, y;
|
2854
|
+
switch (node.tagName.toUpperCase()) {
|
2855
|
+
|
2856
|
+
case 'TR':
|
2857
|
+
if (!this._has_data) this._row_offset = this._row;
|
2858
|
+
this._row += 1;
|
2859
|
+
this._col = 0;
|
2860
|
+
break;
|
2861
|
+
|
2862
|
+
case 'TD':
|
2863
|
+
if (!this._has_data) this._col_offset = this._col;
|
2864
|
+
this._has_data = true;
|
2865
|
+
this._col += 1;
|
2866
|
+
content = content.match(this.NUMBER_FORMAT);
|
2867
|
+
if (content === null) {
|
2868
|
+
this.get_series(x).points[y] = null;
|
2869
|
+
} else {
|
2870
|
+
x = this._col - this._col_offset - 1;
|
2871
|
+
y = this._row - this._row_offset - 1;
|
2872
|
+
this.get_series(x).points[y] = parseFloat(content[0]);
|
2873
|
+
}
|
2874
|
+
break;
|
2875
|
+
|
2876
|
+
case 'TH':
|
2877
|
+
this._col += 1;
|
2878
|
+
if (this._ignore(node)) {
|
2879
|
+
this._skip_cols.push(this._col);
|
2880
|
+
this._skip_rows.push(this._row);
|
2881
|
+
}
|
2882
|
+
if (this._col === 1 && this._row === 1)
|
2883
|
+
this._row_headings[0] = this._col_headings[0] = content;
|
2884
|
+
else if (node.scope === "row" || this._col === 1)
|
2885
|
+
this._row_headings[this._row - 1] = content;
|
2886
|
+
else
|
2887
|
+
this._col_headings[this._col - 1] = content;
|
2888
|
+
break;
|
2889
|
+
|
2890
|
+
case 'CAPTION':
|
2891
|
+
this._title = content;
|
2892
|
+
break;
|
2893
|
+
}
|
2894
|
+
},
|
2895
|
+
|
2896
|
+
_ignore: function(node) {
|
2897
|
+
if (!this._options.except) return false;
|
2898
|
+
|
2899
|
+
var content = this._strip_tags(node.innerHTML),
|
2900
|
+
classes = (node.className || '').split(/\s+/),
|
2901
|
+
list = [].concat(this._options.except);
|
2902
|
+
|
2903
|
+
if (Bluff.index(list, content) >= 0) return true;
|
2904
|
+
var i = classes.length;
|
2905
|
+
while (i--) {
|
2906
|
+
if (Bluff.index(list, classes[i]) >= 0) return true;
|
2907
|
+
}
|
2908
|
+
return false;
|
2909
|
+
},
|
2910
|
+
|
2911
|
+
_cleanup: function() {
|
2912
|
+
var i = this._skip_cols.length, index;
|
2913
|
+
while (i--) {
|
2914
|
+
index = this._skip_cols[i];
|
2915
|
+
if (index <= this._col_offset) continue;
|
2916
|
+
this._col_headings.splice(index - 1, 1);
|
2917
|
+
if (index >= this._col_offset)
|
2918
|
+
this._data.splice(index - 1 - this._col_offset, 1);
|
2919
|
+
}
|
2920
|
+
|
2921
|
+
var i = this._skip_rows.length, index;
|
2922
|
+
while (i--) {
|
2923
|
+
index = this._skip_rows[i];
|
2924
|
+
if (index <= this._row_offset) continue;
|
2925
|
+
this._row_headings.splice(index - 1, 1);
|
2926
|
+
Bluff.each(this._data, function(series) {
|
2927
|
+
if (index >= this._row_offset)
|
2928
|
+
series.points.splice(index - 1 - this._row_offset, 1);
|
2929
|
+
}, this);
|
2930
|
+
}
|
2931
|
+
},
|
2932
|
+
|
2933
|
+
_orient: function() {
|
2934
|
+
switch (this._orientation) {
|
2935
|
+
case 'auto':
|
2936
|
+
if ((this._row_headings.length > 1 && this._col_headings.length === 1) ||
|
2937
|
+
this._row_headings.length < this._col_headings.length) {
|
2938
|
+
this._transpose();
|
2939
|
+
}
|
2940
|
+
break;
|
2941
|
+
|
2942
|
+
case 'rows':
|
2943
|
+
this._transpose();
|
2944
|
+
break;
|
2945
|
+
}
|
2946
|
+
},
|
2947
|
+
|
2948
|
+
// Transpose data in memory
|
2949
|
+
_transpose: function() {
|
2950
|
+
var data = this._data, tmp;
|
2951
|
+
this._data = [];
|
2952
|
+
|
2953
|
+
Bluff.each(data, function(row, i) {
|
2954
|
+
Bluff.each(row.points, function(point, p) {
|
2955
|
+
this.get_series(p).points[i] = point;
|
2956
|
+
}, this);
|
2957
|
+
}, this);
|
2958
|
+
|
2959
|
+
tmp = this._row_headings;
|
2960
|
+
this._row_headings = this._col_headings;
|
2961
|
+
this._col_headings = tmp;
|
2962
|
+
|
2963
|
+
tmp = this._row_offset;
|
2964
|
+
this._row_offset = this._col_offset;
|
2965
|
+
this._col_offset = tmp;
|
2966
|
+
},
|
2967
|
+
|
2968
|
+
// Remove HTML from a string
|
2969
|
+
_strip_tags: function(string) {
|
2970
|
+
return string.replace(/<\/?[^>]+>/gi, '');
|
2971
|
+
},
|
2972
|
+
|
2973
|
+
extend: {
|
2974
|
+
Mixin: new JS.Module({
|
2975
|
+
data_from_table: function(table, options) {
|
2976
|
+
var reader = new Bluff.TableReader(table, options),
|
2977
|
+
data_rows = reader.get_data();
|
2978
|
+
|
2979
|
+
Bluff.each(data_rows, function(row) {
|
2980
|
+
this.data(row.name, row.points);
|
2981
|
+
}, this);
|
2982
|
+
|
2983
|
+
this.labels = reader.get_labels();
|
2984
|
+
this.title = reader.get_title() || this.title;
|
2985
|
+
}
|
2986
|
+
})
|
2987
|
+
}
|
2988
|
+
});
|
2989
|
+
|
2990
|
+
Bluff.Base.include(Bluff.TableReader.Mixin);
|