keigan 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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);
|