boletia_ui 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +42 -0
- data/lib/boletia_ui/version.rb +3 -0
- data/lib/boletia_ui.rb +8 -0
- data/vendor/assets/boletia_ui/fonts/ss-standard.css +53 -0
- data/vendor/assets/boletia_ui/fonts/ss-standard.eot +0 -0
- data/vendor/assets/boletia_ui/fonts/ss-standard.js +84 -0
- data/vendor/assets/boletia_ui/fonts/ss-standard.svg +404 -0
- data/vendor/assets/boletia_ui/fonts/ss-standard.ttf +0 -0
- data/vendor/assets/boletia_ui/fonts/ss-standard.woff +0 -0
- data/vendor/assets/boletia_ui/images/cancel.png +0 -0
- data/vendor/assets/boletia_ui/images/check.png +0 -0
- data/vendor/assets/boletia_ui/images/default-search.png +0 -0
- data/vendor/assets/boletia_ui/images/download.png +0 -0
- data/vendor/assets/boletia_ui/images/edit.png +0 -0
- data/vendor/assets/boletia_ui/images/icon-logo140.png +0 -0
- data/vendor/assets/boletia_ui/images/logo/header-logo-black.png +0 -0
- data/vendor/assets/boletia_ui/images/logo/header-logo-white.png +0 -0
- data/vendor/assets/boletia_ui/images/logo/icon-logo140.png +0 -0
- data/vendor/assets/boletia_ui/images/resend.png +0 -0
- data/vendor/assets/boletia_ui/images/trash.png +0 -0
- data/vendor/assets/boletia_ui/images/trophy.png +0 -0
- data/vendor/assets/boletia_ui/images/white-resend.png +0 -0
- data/vendor/assets/boletia_ui/images/white-search.png +0 -0
- data/vendor/assets/boletia_ui/javascripts/boletia-ui/boletia-ui-components.js +117 -0
- data/vendor/assets/boletia_ui/javascripts/boletia-ui/classie.js +80 -0
- data/vendor/assets/boletia_ui/javascripts/boletia-ui/select_fx.js +335 -0
- data/vendor/assets/boletia_ui/javascripts/charts/Chart.Legend.js +56 -0
- data/vendor/assets/boletia_ui/javascripts/charts/chart.min.js +11 -0
- data/vendor/assets/boletia_ui/javascripts/charts/refundCharts.js +167 -0
- data/vendor/assets/boletia_ui/javascripts/charts/src/Chart.Bar.js +302 -0
- data/vendor/assets/boletia_ui/javascripts/charts/src/Chart.Core.js +2002 -0
- data/vendor/assets/boletia_ui/javascripts/charts/src/Chart.Doughnut.js +184 -0
- data/vendor/assets/boletia_ui/javascripts/charts/src/Chart.Line.js +374 -0
- data/vendor/assets/boletia_ui/javascripts/charts/src/Chart.PolarArea.js +250 -0
- data/vendor/assets/boletia_ui/javascripts/charts/src/Chart.Radar.js +343 -0
- data/vendor/assets/boletia_ui/javascripts/counter/counter.js +12 -0
- data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-bg_flat_0_eeeeee_40x100.png +0 -0
- data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-bg_flat_55_ffffff_40x100.png +0 -0
- data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-bg_highlight-soft_100_f6f6f6_1x100.png +0 -0
- data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-bg_highlight-soft_25_0073ea_1x100.png +0 -0
- data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-bg_highlight-soft_50_dddddd_1x100.png +0 -0
- data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-icons_0073ea_256x240.png +0 -0
- data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-icons_454545_256x240.png +0 -0
- data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-icons_666666_256x240.png +0 -0
- data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-icons_ff0084_256x240.png +0 -0
- data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-icons_ffffff_256x240.png +0 -0
- data/vendor/assets/boletia_ui/javascripts/jquery-ui/jquery-ui.min.js +8 -0
- data/vendor/assets/boletia_ui/javascripts/scrolling-header/scrolling-header.js +41 -0
- data/vendor/assets/boletia_ui/javascripts/styleguide/styleguide.js +87 -0
- data/vendor/assets/boletia_ui/javascripts/tooltips/jquery.tooltipster.min.js +1 -0
- data/vendor/assets/boletia_ui/javascripts/vendor/magnific-popup/core.js +966 -0
- data/vendor/assets/boletia_ui/javascripts/vendor/magnific-popup/fastclick.js +93 -0
- data/vendor/assets/boletia_ui/javascripts/vendor/magnific-popup/image.js +245 -0
- data/vendor/assets/boletia_ui/javascripts/vendor/magnific-popup/inline.js +66 -0
- data/vendor/assets/boletia_ui/stylesheets/boletia-admin.css.scss +41 -0
- data/vendor/assets/boletia_ui/stylesheets/fonts/ss-standard.css +53 -0
- data/vendor/assets/boletia_ui/stylesheets/fonts/ss-standard.eot +0 -0
- data/vendor/assets/boletia_ui/stylesheets/fonts/ss-standard.js +84 -0
- data/vendor/assets/boletia_ui/stylesheets/fonts/ss-standard.svg +404 -0
- data/vendor/assets/boletia_ui/stylesheets/fonts/ss-standard.ttf +0 -0
- data/vendor/assets/boletia_ui/stylesheets/fonts/ss-standard.woff +0 -0
- data/vendor/assets/boletia_ui/stylesheets/jquery-ui/jquery-ui.min.css +7 -0
- data/vendor/assets/boletia_ui/stylesheets/jquery-ui/jquery-ui.structure.min.css +5 -0
- data/vendor/assets/boletia_ui/stylesheets/jquery-ui/jquery-ui.theme.css +410 -0
- data/vendor/assets/boletia_ui/stylesheets/layouts/_event_details.scss +100 -0
- data/vendor/assets/boletia_ui/stylesheets/layouts/_refund-wizard.scss +290 -0
- data/vendor/assets/boletia_ui/stylesheets/layouts/_sales.scss +17 -0
- data/vendor/assets/boletia_ui/stylesheets/layouts/_style_guide.scss +216 -0
- data/vendor/assets/boletia_ui/stylesheets/menu/_footer.scss +117 -0
- data/vendor/assets/boletia_ui/stylesheets/menu/_header.scss +314 -0
- data/vendor/assets/boletia_ui/stylesheets/menu/_new_front_footer.scss +117 -0
- data/vendor/assets/boletia_ui/stylesheets/menu/_new_front_header.scss +314 -0
- data/vendor/assets/boletia_ui/stylesheets/partials/_alerts.scss +183 -0
- data/vendor/assets/boletia_ui/stylesheets/partials/_base.scss +376 -0
- data/vendor/assets/boletia_ui/stylesheets/partials/_boletia-cards.scss +401 -0
- data/vendor/assets/boletia_ui/stylesheets/partials/_buttons.scss +200 -0
- data/vendor/assets/boletia_ui/stylesheets/partials/_dashboard.scss +192 -0
- data/vendor/assets/boletia_ui/stylesheets/partials/_forms.scss +395 -0
- data/vendor/assets/boletia_ui/stylesheets/partials/_grid.scss +85 -0
- data/vendor/assets/boletia_ui/stylesheets/partials/_tables.scss +139 -0
- data/vendor/assets/boletia_ui/stylesheets/partials/_wizard.scss +125 -0
- data/vendor/assets/boletia_ui/stylesheets/vendor/_select.scss +109 -0
- data/vendor/assets/boletia_ui/stylesheets/vendor/magnific-popup/_magnific.scss +642 -0
- data/vendor/assets/boletia_ui/stylesheets/vendor/magnific-popup/_settings.scss +45 -0
- data/vendor/assets/boletia_ui/stylesheets/vendor/tooltipster.css +274 -0
- metadata +174 -0
|
@@ -0,0 +1,2002 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Chart.js
|
|
3
|
+
* http://chartjs.org/
|
|
4
|
+
* Version: {{ version }}
|
|
5
|
+
*
|
|
6
|
+
* Copyright 2015 Nick Downie
|
|
7
|
+
* Released under the MIT license
|
|
8
|
+
* https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
(function(){
|
|
13
|
+
|
|
14
|
+
"use strict";
|
|
15
|
+
|
|
16
|
+
//Declare root variable - window in the browser, global on the server
|
|
17
|
+
var root = this,
|
|
18
|
+
previous = root.Chart;
|
|
19
|
+
|
|
20
|
+
//Occupy the global variable of Chart, and create a simple base class
|
|
21
|
+
var Chart = function(context){
|
|
22
|
+
var chart = this;
|
|
23
|
+
this.canvas = context.canvas;
|
|
24
|
+
|
|
25
|
+
this.ctx = context;
|
|
26
|
+
|
|
27
|
+
//Variables global to the chart
|
|
28
|
+
var width = this.width = context.canvas.width;
|
|
29
|
+
var height = this.height = context.canvas.height;
|
|
30
|
+
this.aspectRatio = this.width / this.height;
|
|
31
|
+
//High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
|
|
32
|
+
helpers.retinaScale(this);
|
|
33
|
+
|
|
34
|
+
return this;
|
|
35
|
+
};
|
|
36
|
+
//Globally expose the defaults to allow for user updating/changing
|
|
37
|
+
Chart.defaults = {
|
|
38
|
+
global: {
|
|
39
|
+
// Boolean - Whether to animate the chart
|
|
40
|
+
animation: true,
|
|
41
|
+
|
|
42
|
+
// Number - Number of animation steps
|
|
43
|
+
animationSteps: 60,
|
|
44
|
+
|
|
45
|
+
// String - Animation easing effect
|
|
46
|
+
animationEasing: "easeOutQuart",
|
|
47
|
+
|
|
48
|
+
// Boolean - If we should show the scale at all
|
|
49
|
+
showScale: true,
|
|
50
|
+
|
|
51
|
+
// Boolean - If we want to override with a hard coded scale
|
|
52
|
+
scaleOverride: false,
|
|
53
|
+
|
|
54
|
+
// ** Required if scaleOverride is true **
|
|
55
|
+
// Number - The number of steps in a hard coded scale
|
|
56
|
+
scaleSteps: null,
|
|
57
|
+
// Number - The value jump in the hard coded scale
|
|
58
|
+
scaleStepWidth: null,
|
|
59
|
+
// Number - The scale starting value
|
|
60
|
+
scaleStartValue: null,
|
|
61
|
+
|
|
62
|
+
// String - Colour of the scale line
|
|
63
|
+
scaleLineColor: "rgba(0,0,0,.1)",
|
|
64
|
+
|
|
65
|
+
// Number - Pixel width of the scale line
|
|
66
|
+
scaleLineWidth: 1,
|
|
67
|
+
|
|
68
|
+
// Boolean - Whether to show labels on the scale
|
|
69
|
+
scaleShowLabels: true,
|
|
70
|
+
|
|
71
|
+
// Interpolated JS string - can access value
|
|
72
|
+
scaleLabel: "<%=value%>",
|
|
73
|
+
|
|
74
|
+
// Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there
|
|
75
|
+
scaleIntegersOnly: true,
|
|
76
|
+
|
|
77
|
+
// Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
|
|
78
|
+
scaleBeginAtZero: false,
|
|
79
|
+
|
|
80
|
+
// String - Scale label font declaration for the scale label
|
|
81
|
+
scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
|
|
82
|
+
|
|
83
|
+
// Number - Scale label font size in pixels
|
|
84
|
+
scaleFontSize: 12,
|
|
85
|
+
|
|
86
|
+
// String - Scale label font weight style
|
|
87
|
+
scaleFontStyle: "normal",
|
|
88
|
+
|
|
89
|
+
// String - Scale label font colour
|
|
90
|
+
scaleFontColor: "#666",
|
|
91
|
+
|
|
92
|
+
// Boolean - whether or not the chart should be responsive and resize when the browser does.
|
|
93
|
+
responsive: false,
|
|
94
|
+
|
|
95
|
+
// Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
|
|
96
|
+
maintainAspectRatio: true,
|
|
97
|
+
|
|
98
|
+
// Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
|
|
99
|
+
showTooltips: true,
|
|
100
|
+
|
|
101
|
+
// Boolean - Determines whether to draw built-in tooltip or call custom tooltip function
|
|
102
|
+
customTooltips: false,
|
|
103
|
+
|
|
104
|
+
// Array - Array of string names to attach tooltip events
|
|
105
|
+
tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],
|
|
106
|
+
|
|
107
|
+
// String - Tooltip background colour
|
|
108
|
+
tooltipFillColor: "rgba(0,0,0,0.8)",
|
|
109
|
+
|
|
110
|
+
// String - Tooltip label font declaration for the scale label
|
|
111
|
+
tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
|
|
112
|
+
|
|
113
|
+
// Number - Tooltip label font size in pixels
|
|
114
|
+
tooltipFontSize: 14,
|
|
115
|
+
|
|
116
|
+
// String - Tooltip font weight style
|
|
117
|
+
tooltipFontStyle: "normal",
|
|
118
|
+
|
|
119
|
+
// String - Tooltip label font colour
|
|
120
|
+
tooltipFontColor: "#fff",
|
|
121
|
+
|
|
122
|
+
// String - Tooltip title font declaration for the scale label
|
|
123
|
+
tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
|
|
124
|
+
|
|
125
|
+
// Number - Tooltip title font size in pixels
|
|
126
|
+
tooltipTitleFontSize: 14,
|
|
127
|
+
|
|
128
|
+
// String - Tooltip title font weight style
|
|
129
|
+
tooltipTitleFontStyle: "bold",
|
|
130
|
+
|
|
131
|
+
// String - Tooltip title font colour
|
|
132
|
+
tooltipTitleFontColor: "#fff",
|
|
133
|
+
|
|
134
|
+
// Number - pixel width of padding around tooltip text
|
|
135
|
+
tooltipYPadding: 6,
|
|
136
|
+
|
|
137
|
+
// Number - pixel width of padding around tooltip text
|
|
138
|
+
tooltipXPadding: 6,
|
|
139
|
+
|
|
140
|
+
// Number - Size of the caret on the tooltip
|
|
141
|
+
tooltipCaretSize: 8,
|
|
142
|
+
|
|
143
|
+
// Number - Pixel radius of the tooltip border
|
|
144
|
+
tooltipCornerRadius: 6,
|
|
145
|
+
|
|
146
|
+
// Number - Pixel offset from point x to tooltip edge
|
|
147
|
+
tooltipXOffset: 10,
|
|
148
|
+
|
|
149
|
+
// String - Template string for single tooltips
|
|
150
|
+
tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>",
|
|
151
|
+
|
|
152
|
+
// String - Template string for single tooltips
|
|
153
|
+
multiTooltipTemplate: "<%= value %>",
|
|
154
|
+
|
|
155
|
+
// String - Colour behind the legend colour block
|
|
156
|
+
multiTooltipKeyBackground: '#fff',
|
|
157
|
+
|
|
158
|
+
// Function - Will fire on animation progression.
|
|
159
|
+
onAnimationProgress: function(){},
|
|
160
|
+
|
|
161
|
+
// Function - Will fire on animation completion.
|
|
162
|
+
onAnimationComplete: function(){}
|
|
163
|
+
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
//Create a dictionary of chart types, to allow for extension of existing types
|
|
168
|
+
Chart.types = {};
|
|
169
|
+
|
|
170
|
+
//Global Chart helpers object for utility methods and classes
|
|
171
|
+
var helpers = Chart.helpers = {};
|
|
172
|
+
|
|
173
|
+
//-- Basic js utility methods
|
|
174
|
+
var each = helpers.each = function(loopable,callback,self){
|
|
175
|
+
var additionalArgs = Array.prototype.slice.call(arguments, 3);
|
|
176
|
+
// Check to see if null or undefined firstly.
|
|
177
|
+
if (loopable){
|
|
178
|
+
if (loopable.length === +loopable.length){
|
|
179
|
+
var i;
|
|
180
|
+
for (i=0; i<loopable.length; i++){
|
|
181
|
+
callback.apply(self,[loopable[i], i].concat(additionalArgs));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else{
|
|
185
|
+
for (var item in loopable){
|
|
186
|
+
callback.apply(self,[loopable[item],item].concat(additionalArgs));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
clone = helpers.clone = function(obj){
|
|
192
|
+
var objClone = {};
|
|
193
|
+
each(obj,function(value,key){
|
|
194
|
+
if (obj.hasOwnProperty(key)) objClone[key] = value;
|
|
195
|
+
});
|
|
196
|
+
return objClone;
|
|
197
|
+
},
|
|
198
|
+
extend = helpers.extend = function(base){
|
|
199
|
+
each(Array.prototype.slice.call(arguments,1), function(extensionObject) {
|
|
200
|
+
each(extensionObject,function(value,key){
|
|
201
|
+
if (extensionObject.hasOwnProperty(key)) base[key] = value;
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
return base;
|
|
205
|
+
},
|
|
206
|
+
merge = helpers.merge = function(base,master){
|
|
207
|
+
//Merge properties in left object over to a shallow clone of object right.
|
|
208
|
+
var args = Array.prototype.slice.call(arguments,0);
|
|
209
|
+
args.unshift({});
|
|
210
|
+
return extend.apply(null, args);
|
|
211
|
+
},
|
|
212
|
+
indexOf = helpers.indexOf = function(arrayToSearch, item){
|
|
213
|
+
if (Array.prototype.indexOf) {
|
|
214
|
+
return arrayToSearch.indexOf(item);
|
|
215
|
+
}
|
|
216
|
+
else{
|
|
217
|
+
for (var i = 0; i < arrayToSearch.length; i++) {
|
|
218
|
+
if (arrayToSearch[i] === item) return i;
|
|
219
|
+
}
|
|
220
|
+
return -1;
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
where = helpers.where = function(collection, filterCallback){
|
|
224
|
+
var filtered = [];
|
|
225
|
+
|
|
226
|
+
helpers.each(collection, function(item){
|
|
227
|
+
if (filterCallback(item)){
|
|
228
|
+
filtered.push(item);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
return filtered;
|
|
233
|
+
},
|
|
234
|
+
findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex){
|
|
235
|
+
// Default to start of the array
|
|
236
|
+
if (!startIndex){
|
|
237
|
+
startIndex = -1;
|
|
238
|
+
}
|
|
239
|
+
for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
|
|
240
|
+
var currentItem = arrayToSearch[i];
|
|
241
|
+
if (filterCallback(currentItem)){
|
|
242
|
+
return currentItem;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex){
|
|
247
|
+
// Default to end of the array
|
|
248
|
+
if (!startIndex){
|
|
249
|
+
startIndex = arrayToSearch.length;
|
|
250
|
+
}
|
|
251
|
+
for (var i = startIndex - 1; i >= 0; i--) {
|
|
252
|
+
var currentItem = arrayToSearch[i];
|
|
253
|
+
if (filterCallback(currentItem)){
|
|
254
|
+
return currentItem;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
inherits = helpers.inherits = function(extensions){
|
|
259
|
+
//Basic javascript inheritance based on the model created in Backbone.js
|
|
260
|
+
var parent = this;
|
|
261
|
+
var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function(){ return parent.apply(this, arguments); };
|
|
262
|
+
|
|
263
|
+
var Surrogate = function(){ this.constructor = ChartElement;};
|
|
264
|
+
Surrogate.prototype = parent.prototype;
|
|
265
|
+
ChartElement.prototype = new Surrogate();
|
|
266
|
+
|
|
267
|
+
ChartElement.extend = inherits;
|
|
268
|
+
|
|
269
|
+
if (extensions) extend(ChartElement.prototype, extensions);
|
|
270
|
+
|
|
271
|
+
ChartElement.__super__ = parent.prototype;
|
|
272
|
+
|
|
273
|
+
return ChartElement;
|
|
274
|
+
},
|
|
275
|
+
noop = helpers.noop = function(){},
|
|
276
|
+
uid = helpers.uid = (function(){
|
|
277
|
+
var id=0;
|
|
278
|
+
return function(){
|
|
279
|
+
return "chart-" + id++;
|
|
280
|
+
};
|
|
281
|
+
})(),
|
|
282
|
+
warn = helpers.warn = function(str){
|
|
283
|
+
//Method for warning of errors
|
|
284
|
+
if (window.console && typeof window.console.warn == "function") console.warn(str);
|
|
285
|
+
},
|
|
286
|
+
amd = helpers.amd = (typeof define == 'function' && define.amd),
|
|
287
|
+
//-- Math methods
|
|
288
|
+
isNumber = helpers.isNumber = function(n){
|
|
289
|
+
return !isNaN(parseFloat(n)) && isFinite(n);
|
|
290
|
+
},
|
|
291
|
+
max = helpers.max = function(array){
|
|
292
|
+
return Math.max.apply( Math, array );
|
|
293
|
+
},
|
|
294
|
+
min = helpers.min = function(array){
|
|
295
|
+
return Math.min.apply( Math, array );
|
|
296
|
+
},
|
|
297
|
+
cap = helpers.cap = function(valueToCap,maxValue,minValue){
|
|
298
|
+
if(isNumber(maxValue)) {
|
|
299
|
+
if( valueToCap > maxValue ) {
|
|
300
|
+
return maxValue;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
else if(isNumber(minValue)){
|
|
304
|
+
if ( valueToCap < minValue ){
|
|
305
|
+
return minValue;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return valueToCap;
|
|
309
|
+
},
|
|
310
|
+
getDecimalPlaces = helpers.getDecimalPlaces = function(num){
|
|
311
|
+
if (num%1!==0 && isNumber(num)){
|
|
312
|
+
return num.toString().split(".")[1].length;
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
return 0;
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
toRadians = helpers.radians = function(degrees){
|
|
319
|
+
return degrees * (Math.PI/180);
|
|
320
|
+
},
|
|
321
|
+
// Gets the angle from vertical upright to the point about a centre.
|
|
322
|
+
getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint){
|
|
323
|
+
var distanceFromXCenter = anglePoint.x - centrePoint.x,
|
|
324
|
+
distanceFromYCenter = anglePoint.y - centrePoint.y,
|
|
325
|
+
radialDistanceFromCenter = Math.sqrt( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter);
|
|
329
|
+
|
|
330
|
+
//If the segment is in the top left quadrant, we need to add another rotation to the angle
|
|
331
|
+
if (distanceFromXCenter < 0 && distanceFromYCenter < 0){
|
|
332
|
+
angle += Math.PI*2;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
angle: angle,
|
|
337
|
+
distance: radialDistanceFromCenter
|
|
338
|
+
};
|
|
339
|
+
},
|
|
340
|
+
aliasPixel = helpers.aliasPixel = function(pixelWidth){
|
|
341
|
+
return (pixelWidth % 2 === 0) ? 0 : 0.5;
|
|
342
|
+
},
|
|
343
|
+
splineCurve = helpers.splineCurve = function(FirstPoint,MiddlePoint,AfterPoint,t){
|
|
344
|
+
//Props to Rob Spencer at scaled innovation for his post on splining between points
|
|
345
|
+
//http://scaledinnovation.com/analytics/splines/aboutSplines.html
|
|
346
|
+
var d01=Math.sqrt(Math.pow(MiddlePoint.x-FirstPoint.x,2)+Math.pow(MiddlePoint.y-FirstPoint.y,2)),
|
|
347
|
+
d12=Math.sqrt(Math.pow(AfterPoint.x-MiddlePoint.x,2)+Math.pow(AfterPoint.y-MiddlePoint.y,2)),
|
|
348
|
+
fa=t*d01/(d01+d12),// scaling factor for triangle Ta
|
|
349
|
+
fb=t*d12/(d01+d12);
|
|
350
|
+
return {
|
|
351
|
+
inner : {
|
|
352
|
+
x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x),
|
|
353
|
+
y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y)
|
|
354
|
+
},
|
|
355
|
+
outer : {
|
|
356
|
+
x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x),
|
|
357
|
+
y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y)
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
},
|
|
361
|
+
calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val){
|
|
362
|
+
return Math.floor(Math.log(val) / Math.LN10);
|
|
363
|
+
},
|
|
364
|
+
calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly){
|
|
365
|
+
|
|
366
|
+
//Set a minimum step of two - a point at the top of the graph, and a point at the base
|
|
367
|
+
var minSteps = 2,
|
|
368
|
+
maxSteps = Math.floor(drawingSize/(textSize * 1.5)),
|
|
369
|
+
skipFitting = (minSteps >= maxSteps);
|
|
370
|
+
|
|
371
|
+
var maxValue = max(valuesArray),
|
|
372
|
+
minValue = min(valuesArray);
|
|
373
|
+
|
|
374
|
+
// We need some degree of seperation here to calculate the scales if all the values are the same
|
|
375
|
+
// Adding/minusing 0.5 will give us a range of 1.
|
|
376
|
+
if (maxValue === minValue){
|
|
377
|
+
maxValue += 0.5;
|
|
378
|
+
// So we don't end up with a graph with a negative start value if we've said always start from zero
|
|
379
|
+
if (minValue >= 0.5 && !startFromZero){
|
|
380
|
+
minValue -= 0.5;
|
|
381
|
+
}
|
|
382
|
+
else{
|
|
383
|
+
// Make up a whole number above the values
|
|
384
|
+
maxValue += 0.5;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
var valueRange = Math.abs(maxValue - minValue),
|
|
389
|
+
rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
|
|
390
|
+
graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
|
|
391
|
+
graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
|
|
392
|
+
graphRange = graphMax - graphMin,
|
|
393
|
+
stepValue = Math.pow(10, rangeOrderOfMagnitude),
|
|
394
|
+
numberOfSteps = Math.round(graphRange / stepValue);
|
|
395
|
+
|
|
396
|
+
//If we have more space on the graph we'll use it to give more definition to the data
|
|
397
|
+
while((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {
|
|
398
|
+
if(numberOfSteps > maxSteps){
|
|
399
|
+
stepValue *=2;
|
|
400
|
+
numberOfSteps = Math.round(graphRange/stepValue);
|
|
401
|
+
// Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
|
|
402
|
+
if (numberOfSteps % 1 !== 0){
|
|
403
|
+
skipFitting = true;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
//We can fit in double the amount of scale points on the scale
|
|
407
|
+
else{
|
|
408
|
+
//If user has declared ints only, and the step value isn't a decimal
|
|
409
|
+
if (integersOnly && rangeOrderOfMagnitude >= 0){
|
|
410
|
+
//If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
|
|
411
|
+
if(stepValue/2 % 1 === 0){
|
|
412
|
+
stepValue /=2;
|
|
413
|
+
numberOfSteps = Math.round(graphRange/stepValue);
|
|
414
|
+
}
|
|
415
|
+
//If it would make it a float break out of the loop
|
|
416
|
+
else{
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
//If the scale doesn't have to be an int, make the scale more granular anyway.
|
|
421
|
+
else{
|
|
422
|
+
stepValue /=2;
|
|
423
|
+
numberOfSteps = Math.round(graphRange/stepValue);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (skipFitting){
|
|
430
|
+
numberOfSteps = minSteps;
|
|
431
|
+
stepValue = graphRange / numberOfSteps;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
steps : numberOfSteps,
|
|
436
|
+
stepValue : stepValue,
|
|
437
|
+
min : graphMin,
|
|
438
|
+
max : graphMin + (numberOfSteps * stepValue)
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
},
|
|
442
|
+
/* jshint ignore:start */
|
|
443
|
+
// Blows up jshint errors based on the new Function constructor
|
|
444
|
+
//Templating methods
|
|
445
|
+
//Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
|
|
446
|
+
template = helpers.template = function(templateString, valuesObject){
|
|
447
|
+
|
|
448
|
+
// If templateString is function rather than string-template - call the function for valuesObject
|
|
449
|
+
|
|
450
|
+
if(templateString instanceof Function){
|
|
451
|
+
return templateString(valuesObject);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
var cache = {};
|
|
455
|
+
function tmpl(str, data){
|
|
456
|
+
// Figure out if we're getting a template, or if we need to
|
|
457
|
+
// load the template - and be sure to cache the result.
|
|
458
|
+
var fn = !/\W/.test(str) ?
|
|
459
|
+
cache[str] = cache[str] :
|
|
460
|
+
|
|
461
|
+
// Generate a reusable function that will serve as a template
|
|
462
|
+
// generator (and which will be cached).
|
|
463
|
+
new Function("obj",
|
|
464
|
+
"var p=[],print=function(){p.push.apply(p,arguments);};" +
|
|
465
|
+
|
|
466
|
+
// Introduce the data as local variables using with(){}
|
|
467
|
+
"with(obj){p.push('" +
|
|
468
|
+
|
|
469
|
+
// Convert the template into pure JavaScript
|
|
470
|
+
str
|
|
471
|
+
.replace(/[\r\t\n]/g, " ")
|
|
472
|
+
.split("<%").join("\t")
|
|
473
|
+
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
|
|
474
|
+
.replace(/\t=(.*?)%>/g, "',$1,'")
|
|
475
|
+
.split("\t").join("');")
|
|
476
|
+
.split("%>").join("p.push('")
|
|
477
|
+
.split("\r").join("\\'") +
|
|
478
|
+
"');}return p.join('');"
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
// Provide some basic currying to the user
|
|
482
|
+
return data ? fn( data ) : fn;
|
|
483
|
+
}
|
|
484
|
+
return tmpl(templateString,valuesObject);
|
|
485
|
+
},
|
|
486
|
+
/* jshint ignore:end */
|
|
487
|
+
generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){
|
|
488
|
+
var labelsArray = new Array(numberOfSteps);
|
|
489
|
+
if (labelTemplateString){
|
|
490
|
+
each(labelsArray,function(val,index){
|
|
491
|
+
labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))});
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
return labelsArray;
|
|
495
|
+
},
|
|
496
|
+
//--Animation methods
|
|
497
|
+
//Easing functions adapted from Robert Penner's easing equations
|
|
498
|
+
//http://www.robertpenner.com/easing/
|
|
499
|
+
easingEffects = helpers.easingEffects = {
|
|
500
|
+
linear: function (t) {
|
|
501
|
+
return t;
|
|
502
|
+
},
|
|
503
|
+
easeInQuad: function (t) {
|
|
504
|
+
return t * t;
|
|
505
|
+
},
|
|
506
|
+
easeOutQuad: function (t) {
|
|
507
|
+
return -1 * t * (t - 2);
|
|
508
|
+
},
|
|
509
|
+
easeInOutQuad: function (t) {
|
|
510
|
+
if ((t /= 1 / 2) < 1) return 1 / 2 * t * t;
|
|
511
|
+
return -1 / 2 * ((--t) * (t - 2) - 1);
|
|
512
|
+
},
|
|
513
|
+
easeInCubic: function (t) {
|
|
514
|
+
return t * t * t;
|
|
515
|
+
},
|
|
516
|
+
easeOutCubic: function (t) {
|
|
517
|
+
return 1 * ((t = t / 1 - 1) * t * t + 1);
|
|
518
|
+
},
|
|
519
|
+
easeInOutCubic: function (t) {
|
|
520
|
+
if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t;
|
|
521
|
+
return 1 / 2 * ((t -= 2) * t * t + 2);
|
|
522
|
+
},
|
|
523
|
+
easeInQuart: function (t) {
|
|
524
|
+
return t * t * t * t;
|
|
525
|
+
},
|
|
526
|
+
easeOutQuart: function (t) {
|
|
527
|
+
return -1 * ((t = t / 1 - 1) * t * t * t - 1);
|
|
528
|
+
},
|
|
529
|
+
easeInOutQuart: function (t) {
|
|
530
|
+
if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t;
|
|
531
|
+
return -1 / 2 * ((t -= 2) * t * t * t - 2);
|
|
532
|
+
},
|
|
533
|
+
easeInQuint: function (t) {
|
|
534
|
+
return 1 * (t /= 1) * t * t * t * t;
|
|
535
|
+
},
|
|
536
|
+
easeOutQuint: function (t) {
|
|
537
|
+
return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
|
|
538
|
+
},
|
|
539
|
+
easeInOutQuint: function (t) {
|
|
540
|
+
if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t;
|
|
541
|
+
return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
|
|
542
|
+
},
|
|
543
|
+
easeInSine: function (t) {
|
|
544
|
+
return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
|
|
545
|
+
},
|
|
546
|
+
easeOutSine: function (t) {
|
|
547
|
+
return 1 * Math.sin(t / 1 * (Math.PI / 2));
|
|
548
|
+
},
|
|
549
|
+
easeInOutSine: function (t) {
|
|
550
|
+
return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
|
|
551
|
+
},
|
|
552
|
+
easeInExpo: function (t) {
|
|
553
|
+
return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
|
|
554
|
+
},
|
|
555
|
+
easeOutExpo: function (t) {
|
|
556
|
+
return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
|
|
557
|
+
},
|
|
558
|
+
easeInOutExpo: function (t) {
|
|
559
|
+
if (t === 0) return 0;
|
|
560
|
+
if (t === 1) return 1;
|
|
561
|
+
if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1));
|
|
562
|
+
return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
|
|
563
|
+
},
|
|
564
|
+
easeInCirc: function (t) {
|
|
565
|
+
if (t >= 1) return t;
|
|
566
|
+
return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
|
|
567
|
+
},
|
|
568
|
+
easeOutCirc: function (t) {
|
|
569
|
+
return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
|
|
570
|
+
},
|
|
571
|
+
easeInOutCirc: function (t) {
|
|
572
|
+
if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
|
|
573
|
+
return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
|
|
574
|
+
},
|
|
575
|
+
easeInElastic: function (t) {
|
|
576
|
+
var s = 1.70158;
|
|
577
|
+
var p = 0;
|
|
578
|
+
var a = 1;
|
|
579
|
+
if (t === 0) return 0;
|
|
580
|
+
if ((t /= 1) == 1) return 1;
|
|
581
|
+
if (!p) p = 1 * 0.3;
|
|
582
|
+
if (a < Math.abs(1)) {
|
|
583
|
+
a = 1;
|
|
584
|
+
s = p / 4;
|
|
585
|
+
} else s = p / (2 * Math.PI) * Math.asin(1 / a);
|
|
586
|
+
return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
|
|
587
|
+
},
|
|
588
|
+
easeOutElastic: function (t) {
|
|
589
|
+
var s = 1.70158;
|
|
590
|
+
var p = 0;
|
|
591
|
+
var a = 1;
|
|
592
|
+
if (t === 0) return 0;
|
|
593
|
+
if ((t /= 1) == 1) return 1;
|
|
594
|
+
if (!p) p = 1 * 0.3;
|
|
595
|
+
if (a < Math.abs(1)) {
|
|
596
|
+
a = 1;
|
|
597
|
+
s = p / 4;
|
|
598
|
+
} else s = p / (2 * Math.PI) * Math.asin(1 / a);
|
|
599
|
+
return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
|
|
600
|
+
},
|
|
601
|
+
easeInOutElastic: function (t) {
|
|
602
|
+
var s = 1.70158;
|
|
603
|
+
var p = 0;
|
|
604
|
+
var a = 1;
|
|
605
|
+
if (t === 0) return 0;
|
|
606
|
+
if ((t /= 1 / 2) == 2) return 1;
|
|
607
|
+
if (!p) p = 1 * (0.3 * 1.5);
|
|
608
|
+
if (a < Math.abs(1)) {
|
|
609
|
+
a = 1;
|
|
610
|
+
s = p / 4;
|
|
611
|
+
} else s = p / (2 * Math.PI) * Math.asin(1 / a);
|
|
612
|
+
if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
|
|
613
|
+
return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
|
|
614
|
+
},
|
|
615
|
+
easeInBack: function (t) {
|
|
616
|
+
var s = 1.70158;
|
|
617
|
+
return 1 * (t /= 1) * t * ((s + 1) * t - s);
|
|
618
|
+
},
|
|
619
|
+
easeOutBack: function (t) {
|
|
620
|
+
var s = 1.70158;
|
|
621
|
+
return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
|
|
622
|
+
},
|
|
623
|
+
easeInOutBack: function (t) {
|
|
624
|
+
var s = 1.70158;
|
|
625
|
+
if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
|
|
626
|
+
return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
|
|
627
|
+
},
|
|
628
|
+
easeInBounce: function (t) {
|
|
629
|
+
return 1 - easingEffects.easeOutBounce(1 - t);
|
|
630
|
+
},
|
|
631
|
+
easeOutBounce: function (t) {
|
|
632
|
+
if ((t /= 1) < (1 / 2.75)) {
|
|
633
|
+
return 1 * (7.5625 * t * t);
|
|
634
|
+
} else if (t < (2 / 2.75)) {
|
|
635
|
+
return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
|
|
636
|
+
} else if (t < (2.5 / 2.75)) {
|
|
637
|
+
return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
|
|
638
|
+
} else {
|
|
639
|
+
return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
|
|
640
|
+
}
|
|
641
|
+
},
|
|
642
|
+
easeInOutBounce: function (t) {
|
|
643
|
+
if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5;
|
|
644
|
+
return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
//Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
|
|
648
|
+
requestAnimFrame = helpers.requestAnimFrame = (function(){
|
|
649
|
+
return window.requestAnimationFrame ||
|
|
650
|
+
window.webkitRequestAnimationFrame ||
|
|
651
|
+
window.mozRequestAnimationFrame ||
|
|
652
|
+
window.oRequestAnimationFrame ||
|
|
653
|
+
window.msRequestAnimationFrame ||
|
|
654
|
+
function(callback) {
|
|
655
|
+
return window.setTimeout(callback, 1000 / 60);
|
|
656
|
+
};
|
|
657
|
+
})(),
|
|
658
|
+
cancelAnimFrame = helpers.cancelAnimFrame = (function(){
|
|
659
|
+
return window.cancelAnimationFrame ||
|
|
660
|
+
window.webkitCancelAnimationFrame ||
|
|
661
|
+
window.mozCancelAnimationFrame ||
|
|
662
|
+
window.oCancelAnimationFrame ||
|
|
663
|
+
window.msCancelAnimationFrame ||
|
|
664
|
+
function(callback) {
|
|
665
|
+
return window.clearTimeout(callback, 1000 / 60);
|
|
666
|
+
};
|
|
667
|
+
})(),
|
|
668
|
+
animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){
|
|
669
|
+
|
|
670
|
+
var currentStep = 0,
|
|
671
|
+
easingFunction = easingEffects[easingString] || easingEffects.linear;
|
|
672
|
+
|
|
673
|
+
var animationFrame = function(){
|
|
674
|
+
currentStep++;
|
|
675
|
+
var stepDecimal = currentStep/totalSteps;
|
|
676
|
+
var easeDecimal = easingFunction(stepDecimal);
|
|
677
|
+
|
|
678
|
+
callback.call(chartInstance,easeDecimal,stepDecimal, currentStep);
|
|
679
|
+
onProgress.call(chartInstance,easeDecimal,stepDecimal);
|
|
680
|
+
if (currentStep < totalSteps){
|
|
681
|
+
chartInstance.animationFrame = requestAnimFrame(animationFrame);
|
|
682
|
+
} else{
|
|
683
|
+
onComplete.apply(chartInstance);
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
requestAnimFrame(animationFrame);
|
|
687
|
+
},
|
|
688
|
+
//-- DOM methods
|
|
689
|
+
getRelativePosition = helpers.getRelativePosition = function(evt){
|
|
690
|
+
var mouseX, mouseY;
|
|
691
|
+
var e = evt.originalEvent || evt,
|
|
692
|
+
canvas = evt.currentTarget || evt.srcElement,
|
|
693
|
+
boundingRect = canvas.getBoundingClientRect();
|
|
694
|
+
|
|
695
|
+
if (e.touches){
|
|
696
|
+
mouseX = e.touches[0].clientX - boundingRect.left;
|
|
697
|
+
mouseY = e.touches[0].clientY - boundingRect.top;
|
|
698
|
+
|
|
699
|
+
}
|
|
700
|
+
else{
|
|
701
|
+
mouseX = e.clientX - boundingRect.left;
|
|
702
|
+
mouseY = e.clientY - boundingRect.top;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return {
|
|
706
|
+
x : mouseX,
|
|
707
|
+
y : mouseY
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
},
|
|
711
|
+
addEvent = helpers.addEvent = function(node,eventType,method){
|
|
712
|
+
if (node.addEventListener){
|
|
713
|
+
node.addEventListener(eventType,method);
|
|
714
|
+
} else if (node.attachEvent){
|
|
715
|
+
node.attachEvent("on"+eventType, method);
|
|
716
|
+
} else {
|
|
717
|
+
node["on"+eventType] = method;
|
|
718
|
+
}
|
|
719
|
+
},
|
|
720
|
+
removeEvent = helpers.removeEvent = function(node, eventType, handler){
|
|
721
|
+
if (node.removeEventListener){
|
|
722
|
+
node.removeEventListener(eventType, handler, false);
|
|
723
|
+
} else if (node.detachEvent){
|
|
724
|
+
node.detachEvent("on"+eventType,handler);
|
|
725
|
+
} else{
|
|
726
|
+
node["on" + eventType] = noop;
|
|
727
|
+
}
|
|
728
|
+
},
|
|
729
|
+
bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){
|
|
730
|
+
// Create the events object if it's not already present
|
|
731
|
+
if (!chartInstance.events) chartInstance.events = {};
|
|
732
|
+
|
|
733
|
+
each(arrayOfEvents,function(eventName){
|
|
734
|
+
chartInstance.events[eventName] = function(){
|
|
735
|
+
handler.apply(chartInstance, arguments);
|
|
736
|
+
};
|
|
737
|
+
addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]);
|
|
738
|
+
});
|
|
739
|
+
},
|
|
740
|
+
unbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) {
|
|
741
|
+
each(arrayOfEvents, function(handler,eventName){
|
|
742
|
+
removeEvent(chartInstance.chart.canvas, eventName, handler);
|
|
743
|
+
});
|
|
744
|
+
},
|
|
745
|
+
getMaximumWidth = helpers.getMaximumWidth = function(domNode){
|
|
746
|
+
var container = domNode.parentNode;
|
|
747
|
+
// TODO = check cross browser stuff with this.
|
|
748
|
+
return container.clientWidth;
|
|
749
|
+
},
|
|
750
|
+
getMaximumHeight = helpers.getMaximumHeight = function(domNode){
|
|
751
|
+
var container = domNode.parentNode;
|
|
752
|
+
// TODO = check cross browser stuff with this.
|
|
753
|
+
return container.clientHeight;
|
|
754
|
+
},
|
|
755
|
+
getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support
|
|
756
|
+
retinaScale = helpers.retinaScale = function(chart){
|
|
757
|
+
var ctx = chart.ctx,
|
|
758
|
+
width = chart.canvas.width,
|
|
759
|
+
height = chart.canvas.height;
|
|
760
|
+
|
|
761
|
+
if (window.devicePixelRatio) {
|
|
762
|
+
ctx.canvas.style.width = width + "px";
|
|
763
|
+
ctx.canvas.style.height = height + "px";
|
|
764
|
+
ctx.canvas.height = height * window.devicePixelRatio;
|
|
765
|
+
ctx.canvas.width = width * window.devicePixelRatio;
|
|
766
|
+
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
|
767
|
+
}
|
|
768
|
+
},
|
|
769
|
+
//-- Canvas methods
|
|
770
|
+
clear = helpers.clear = function(chart){
|
|
771
|
+
chart.ctx.clearRect(0,0,chart.width,chart.height);
|
|
772
|
+
},
|
|
773
|
+
fontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){
|
|
774
|
+
return fontStyle + " " + pixelSize+"px " + fontFamily;
|
|
775
|
+
},
|
|
776
|
+
longestText = helpers.longestText = function(ctx,font,arrayOfStrings){
|
|
777
|
+
ctx.font = font;
|
|
778
|
+
var longest = 0;
|
|
779
|
+
each(arrayOfStrings,function(string){
|
|
780
|
+
var textWidth = ctx.measureText(string).width;
|
|
781
|
+
longest = (textWidth > longest) ? textWidth : longest;
|
|
782
|
+
});
|
|
783
|
+
return longest;
|
|
784
|
+
},
|
|
785
|
+
drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){
|
|
786
|
+
ctx.beginPath();
|
|
787
|
+
ctx.moveTo(x + radius, y);
|
|
788
|
+
ctx.lineTo(x + width - radius, y);
|
|
789
|
+
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
|
790
|
+
ctx.lineTo(x + width, y + height - radius);
|
|
791
|
+
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
|
792
|
+
ctx.lineTo(x + radius, y + height);
|
|
793
|
+
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
|
794
|
+
ctx.lineTo(x, y + radius);
|
|
795
|
+
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
796
|
+
ctx.closePath();
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
//Store a reference to each instance - allowing us to globally resize chart instances on window resize.
|
|
801
|
+
//Destroy method on the chart will remove the instance of the chart from this reference.
|
|
802
|
+
Chart.instances = {};
|
|
803
|
+
|
|
804
|
+
Chart.Type = function(data,options,chart){
|
|
805
|
+
this.options = options;
|
|
806
|
+
this.chart = chart;
|
|
807
|
+
this.id = uid();
|
|
808
|
+
//Add the chart instance to the global namespace
|
|
809
|
+
Chart.instances[this.id] = this;
|
|
810
|
+
|
|
811
|
+
// Initialize is always called when a chart type is created
|
|
812
|
+
// By default it is a no op, but it should be extended
|
|
813
|
+
if (options.responsive){
|
|
814
|
+
this.resize();
|
|
815
|
+
}
|
|
816
|
+
this.initialize.call(this,data);
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
//Core methods that'll be a part of every chart type
|
|
820
|
+
extend(Chart.Type.prototype,{
|
|
821
|
+
initialize : function(){return this;},
|
|
822
|
+
clear : function(){
|
|
823
|
+
clear(this.chart);
|
|
824
|
+
return this;
|
|
825
|
+
},
|
|
826
|
+
stop : function(){
|
|
827
|
+
// Stops any current animation loop occuring
|
|
828
|
+
helpers.cancelAnimFrame(this.animationFrame);
|
|
829
|
+
return this;
|
|
830
|
+
},
|
|
831
|
+
resize : function(callback){
|
|
832
|
+
this.stop();
|
|
833
|
+
var canvas = this.chart.canvas,
|
|
834
|
+
newWidth = getMaximumWidth(this.chart.canvas),
|
|
835
|
+
newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);
|
|
836
|
+
|
|
837
|
+
canvas.width = this.chart.width = newWidth;
|
|
838
|
+
canvas.height = this.chart.height = newHeight;
|
|
839
|
+
|
|
840
|
+
retinaScale(this.chart);
|
|
841
|
+
|
|
842
|
+
if (typeof callback === "function"){
|
|
843
|
+
callback.apply(this, Array.prototype.slice.call(arguments, 1));
|
|
844
|
+
}
|
|
845
|
+
return this;
|
|
846
|
+
},
|
|
847
|
+
reflow : noop,
|
|
848
|
+
render : function(reflow){
|
|
849
|
+
if (reflow){
|
|
850
|
+
this.reflow();
|
|
851
|
+
}
|
|
852
|
+
if (this.options.animation && !reflow){
|
|
853
|
+
helpers.animationLoop(
|
|
854
|
+
this.draw,
|
|
855
|
+
this.options.animationSteps,
|
|
856
|
+
this.options.animationEasing,
|
|
857
|
+
this.options.onAnimationProgress,
|
|
858
|
+
this.options.onAnimationComplete,
|
|
859
|
+
this
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
else{
|
|
863
|
+
this.draw();
|
|
864
|
+
this.options.onAnimationComplete.call(this);
|
|
865
|
+
}
|
|
866
|
+
return this;
|
|
867
|
+
},
|
|
868
|
+
generateLegend : function(){
|
|
869
|
+
return template(this.options.legendTemplate,this);
|
|
870
|
+
},
|
|
871
|
+
destroy : function(){
|
|
872
|
+
this.clear();
|
|
873
|
+
unbindEvents(this, this.events);
|
|
874
|
+
var canvas = this.chart.canvas;
|
|
875
|
+
|
|
876
|
+
// Reset canvas height/width attributes starts a fresh with the canvas context
|
|
877
|
+
canvas.width = this.chart.width;
|
|
878
|
+
canvas.height = this.chart.height;
|
|
879
|
+
|
|
880
|
+
// < IE9 doesn't support removeProperty
|
|
881
|
+
if (canvas.style.removeProperty) {
|
|
882
|
+
canvas.style.removeProperty('width');
|
|
883
|
+
canvas.style.removeProperty('height');
|
|
884
|
+
} else {
|
|
885
|
+
canvas.style.removeAttribute('width');
|
|
886
|
+
canvas.style.removeAttribute('height');
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
delete Chart.instances[this.id];
|
|
890
|
+
},
|
|
891
|
+
showTooltip : function(ChartElements, forceRedraw){
|
|
892
|
+
// Only redraw the chart if we've actually changed what we're hovering on.
|
|
893
|
+
if (typeof this.activeElements === 'undefined') this.activeElements = [];
|
|
894
|
+
|
|
895
|
+
var isChanged = (function(Elements){
|
|
896
|
+
var changed = false;
|
|
897
|
+
|
|
898
|
+
if (Elements.length !== this.activeElements.length){
|
|
899
|
+
changed = true;
|
|
900
|
+
return changed;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
each(Elements, function(element, index){
|
|
904
|
+
if (element !== this.activeElements[index]){
|
|
905
|
+
changed = true;
|
|
906
|
+
}
|
|
907
|
+
}, this);
|
|
908
|
+
return changed;
|
|
909
|
+
}).call(this, ChartElements);
|
|
910
|
+
|
|
911
|
+
if (!isChanged && !forceRedraw){
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
else{
|
|
915
|
+
this.activeElements = ChartElements;
|
|
916
|
+
}
|
|
917
|
+
this.draw();
|
|
918
|
+
if(this.options.customTooltips){
|
|
919
|
+
this.options.customTooltips(false);
|
|
920
|
+
}
|
|
921
|
+
if (ChartElements.length > 0){
|
|
922
|
+
// If we have multiple datasets, show a MultiTooltip for all of the data points at that index
|
|
923
|
+
if (this.datasets && this.datasets.length > 1) {
|
|
924
|
+
var dataArray,
|
|
925
|
+
dataIndex;
|
|
926
|
+
|
|
927
|
+
for (var i = this.datasets.length - 1; i >= 0; i--) {
|
|
928
|
+
dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments;
|
|
929
|
+
dataIndex = indexOf(dataArray, ChartElements[0]);
|
|
930
|
+
if (dataIndex !== -1){
|
|
931
|
+
break;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
var tooltipLabels = [],
|
|
935
|
+
tooltipColors = [],
|
|
936
|
+
medianPosition = (function(index) {
|
|
937
|
+
|
|
938
|
+
// Get all the points at that particular index
|
|
939
|
+
var Elements = [],
|
|
940
|
+
dataCollection,
|
|
941
|
+
xPositions = [],
|
|
942
|
+
yPositions = [],
|
|
943
|
+
xMax,
|
|
944
|
+
yMax,
|
|
945
|
+
xMin,
|
|
946
|
+
yMin;
|
|
947
|
+
helpers.each(this.datasets, function(dataset){
|
|
948
|
+
dataCollection = dataset.points || dataset.bars || dataset.segments;
|
|
949
|
+
if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){
|
|
950
|
+
Elements.push(dataCollection[dataIndex]);
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
helpers.each(Elements, function(element) {
|
|
955
|
+
xPositions.push(element.x);
|
|
956
|
+
yPositions.push(element.y);
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
//Include any colour information about the element
|
|
960
|
+
tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element));
|
|
961
|
+
tooltipColors.push({
|
|
962
|
+
fill: element._saved.fillColor || element.fillColor,
|
|
963
|
+
stroke: element._saved.strokeColor || element.strokeColor
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
}, this);
|
|
967
|
+
|
|
968
|
+
yMin = min(yPositions);
|
|
969
|
+
yMax = max(yPositions);
|
|
970
|
+
|
|
971
|
+
xMin = min(xPositions);
|
|
972
|
+
xMax = max(xPositions);
|
|
973
|
+
|
|
974
|
+
return {
|
|
975
|
+
x: (xMin > this.chart.width/2) ? xMin : xMax,
|
|
976
|
+
y: (yMin + yMax)/2
|
|
977
|
+
};
|
|
978
|
+
}).call(this, dataIndex);
|
|
979
|
+
|
|
980
|
+
new Chart.MultiTooltip({
|
|
981
|
+
x: medianPosition.x,
|
|
982
|
+
y: medianPosition.y,
|
|
983
|
+
xPadding: this.options.tooltipXPadding,
|
|
984
|
+
yPadding: this.options.tooltipYPadding,
|
|
985
|
+
xOffset: this.options.tooltipXOffset,
|
|
986
|
+
fillColor: this.options.tooltipFillColor,
|
|
987
|
+
textColor: this.options.tooltipFontColor,
|
|
988
|
+
fontFamily: this.options.tooltipFontFamily,
|
|
989
|
+
fontStyle: this.options.tooltipFontStyle,
|
|
990
|
+
fontSize: this.options.tooltipFontSize,
|
|
991
|
+
titleTextColor: this.options.tooltipTitleFontColor,
|
|
992
|
+
titleFontFamily: this.options.tooltipTitleFontFamily,
|
|
993
|
+
titleFontStyle: this.options.tooltipTitleFontStyle,
|
|
994
|
+
titleFontSize: this.options.tooltipTitleFontSize,
|
|
995
|
+
cornerRadius: this.options.tooltipCornerRadius,
|
|
996
|
+
labels: tooltipLabels,
|
|
997
|
+
legendColors: tooltipColors,
|
|
998
|
+
legendColorBackground : this.options.multiTooltipKeyBackground,
|
|
999
|
+
title: ChartElements[0].label,
|
|
1000
|
+
chart: this.chart,
|
|
1001
|
+
ctx: this.chart.ctx,
|
|
1002
|
+
custom: this.options.customTooltips
|
|
1003
|
+
}).draw();
|
|
1004
|
+
|
|
1005
|
+
} else {
|
|
1006
|
+
each(ChartElements, function(Element) {
|
|
1007
|
+
var tooltipPosition = Element.tooltipPosition();
|
|
1008
|
+
new Chart.Tooltip({
|
|
1009
|
+
x: Math.round(tooltipPosition.x),
|
|
1010
|
+
y: Math.round(tooltipPosition.y),
|
|
1011
|
+
xPadding: this.options.tooltipXPadding,
|
|
1012
|
+
yPadding: this.options.tooltipYPadding,
|
|
1013
|
+
fillColor: this.options.tooltipFillColor,
|
|
1014
|
+
textColor: this.options.tooltipFontColor,
|
|
1015
|
+
fontFamily: this.options.tooltipFontFamily,
|
|
1016
|
+
fontStyle: this.options.tooltipFontStyle,
|
|
1017
|
+
fontSize: this.options.tooltipFontSize,
|
|
1018
|
+
caretHeight: this.options.tooltipCaretSize,
|
|
1019
|
+
cornerRadius: this.options.tooltipCornerRadius,
|
|
1020
|
+
text: template(this.options.tooltipTemplate, Element),
|
|
1021
|
+
chart: this.chart,
|
|
1022
|
+
custom: this.options.customTooltips
|
|
1023
|
+
}).draw();
|
|
1024
|
+
}, this);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return this;
|
|
1028
|
+
},
|
|
1029
|
+
toBase64Image : function(){
|
|
1030
|
+
return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
Chart.Type.extend = function(extensions){
|
|
1035
|
+
|
|
1036
|
+
var parent = this;
|
|
1037
|
+
|
|
1038
|
+
var ChartType = function(){
|
|
1039
|
+
return parent.apply(this,arguments);
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
//Copy the prototype object of the this class
|
|
1043
|
+
ChartType.prototype = clone(parent.prototype);
|
|
1044
|
+
//Now overwrite some of the properties in the base class with the new extensions
|
|
1045
|
+
extend(ChartType.prototype, extensions);
|
|
1046
|
+
|
|
1047
|
+
ChartType.extend = Chart.Type.extend;
|
|
1048
|
+
|
|
1049
|
+
if (extensions.name || parent.prototype.name){
|
|
1050
|
+
|
|
1051
|
+
var chartName = extensions.name || parent.prototype.name;
|
|
1052
|
+
//Assign any potential default values of the new chart type
|
|
1053
|
+
|
|
1054
|
+
//If none are defined, we'll use a clone of the chart type this is being extended from.
|
|
1055
|
+
//I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart
|
|
1056
|
+
//doesn't define some defaults of their own.
|
|
1057
|
+
|
|
1058
|
+
var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};
|
|
1059
|
+
|
|
1060
|
+
Chart.defaults[chartName] = extend(baseDefaults,extensions.defaults);
|
|
1061
|
+
|
|
1062
|
+
Chart.types[chartName] = ChartType;
|
|
1063
|
+
|
|
1064
|
+
//Register this new chart type in the Chart prototype
|
|
1065
|
+
Chart.prototype[chartName] = function(data,options){
|
|
1066
|
+
var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {});
|
|
1067
|
+
return new ChartType(data,config,this);
|
|
1068
|
+
};
|
|
1069
|
+
} else{
|
|
1070
|
+
warn("Name not provided for this chart, so it hasn't been registered");
|
|
1071
|
+
}
|
|
1072
|
+
return parent;
|
|
1073
|
+
};
|
|
1074
|
+
|
|
1075
|
+
Chart.Element = function(configuration){
|
|
1076
|
+
extend(this,configuration);
|
|
1077
|
+
this.initialize.apply(this,arguments);
|
|
1078
|
+
this.save();
|
|
1079
|
+
};
|
|
1080
|
+
extend(Chart.Element.prototype,{
|
|
1081
|
+
initialize : function(){},
|
|
1082
|
+
restore : function(props){
|
|
1083
|
+
if (!props){
|
|
1084
|
+
extend(this,this._saved);
|
|
1085
|
+
} else {
|
|
1086
|
+
each(props,function(key){
|
|
1087
|
+
this[key] = this._saved[key];
|
|
1088
|
+
},this);
|
|
1089
|
+
}
|
|
1090
|
+
return this;
|
|
1091
|
+
},
|
|
1092
|
+
save : function(){
|
|
1093
|
+
this._saved = clone(this);
|
|
1094
|
+
delete this._saved._saved;
|
|
1095
|
+
return this;
|
|
1096
|
+
},
|
|
1097
|
+
update : function(newProps){
|
|
1098
|
+
each(newProps,function(value,key){
|
|
1099
|
+
this._saved[key] = this[key];
|
|
1100
|
+
this[key] = value;
|
|
1101
|
+
},this);
|
|
1102
|
+
return this;
|
|
1103
|
+
},
|
|
1104
|
+
transition : function(props,ease){
|
|
1105
|
+
each(props,function(value,key){
|
|
1106
|
+
this[key] = ((value - this._saved[key]) * ease) + this._saved[key];
|
|
1107
|
+
},this);
|
|
1108
|
+
return this;
|
|
1109
|
+
},
|
|
1110
|
+
tooltipPosition : function(){
|
|
1111
|
+
return {
|
|
1112
|
+
x : this.x,
|
|
1113
|
+
y : this.y
|
|
1114
|
+
};
|
|
1115
|
+
},
|
|
1116
|
+
hasValue: function(){
|
|
1117
|
+
return isNumber(this.value);
|
|
1118
|
+
}
|
|
1119
|
+
});
|
|
1120
|
+
|
|
1121
|
+
Chart.Element.extend = inherits;
|
|
1122
|
+
|
|
1123
|
+
|
|
1124
|
+
Chart.Point = Chart.Element.extend({
|
|
1125
|
+
display: true,
|
|
1126
|
+
inRange: function(chartX,chartY){
|
|
1127
|
+
var hitDetectionRange = this.hitDetectionRadius + this.radius;
|
|
1128
|
+
return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2));
|
|
1129
|
+
},
|
|
1130
|
+
draw : function(){
|
|
1131
|
+
if (this.display){
|
|
1132
|
+
var ctx = this.ctx;
|
|
1133
|
+
ctx.beginPath();
|
|
1134
|
+
|
|
1135
|
+
ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
|
|
1136
|
+
ctx.closePath();
|
|
1137
|
+
|
|
1138
|
+
ctx.strokeStyle = this.strokeColor;
|
|
1139
|
+
ctx.lineWidth = this.strokeWidth;
|
|
1140
|
+
|
|
1141
|
+
ctx.fillStyle = this.fillColor;
|
|
1142
|
+
|
|
1143
|
+
ctx.fill();
|
|
1144
|
+
ctx.stroke();
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
|
|
1148
|
+
//Quick debug for bezier curve splining
|
|
1149
|
+
//Highlights control points and the line between them.
|
|
1150
|
+
//Handy for dev - stripped in the min version.
|
|
1151
|
+
|
|
1152
|
+
// ctx.save();
|
|
1153
|
+
// ctx.fillStyle = "black";
|
|
1154
|
+
// ctx.strokeStyle = "black"
|
|
1155
|
+
// ctx.beginPath();
|
|
1156
|
+
// ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2);
|
|
1157
|
+
// ctx.fill();
|
|
1158
|
+
|
|
1159
|
+
// ctx.beginPath();
|
|
1160
|
+
// ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2);
|
|
1161
|
+
// ctx.fill();
|
|
1162
|
+
|
|
1163
|
+
// ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);
|
|
1164
|
+
// ctx.lineTo(this.x, this.y);
|
|
1165
|
+
// ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);
|
|
1166
|
+
// ctx.stroke();
|
|
1167
|
+
|
|
1168
|
+
// ctx.restore();
|
|
1169
|
+
|
|
1170
|
+
|
|
1171
|
+
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
Chart.Arc = Chart.Element.extend({
|
|
1176
|
+
inRange : function(chartX,chartY){
|
|
1177
|
+
|
|
1178
|
+
var pointRelativePosition = helpers.getAngleFromPoint(this, {
|
|
1179
|
+
x: chartX,
|
|
1180
|
+
y: chartY
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
//Check if within the range of the open/close angle
|
|
1184
|
+
var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle),
|
|
1185
|
+
withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius);
|
|
1186
|
+
|
|
1187
|
+
return (betweenAngles && withinRadius);
|
|
1188
|
+
//Ensure within the outside of the arc centre, but inside arc outer
|
|
1189
|
+
},
|
|
1190
|
+
tooltipPosition : function(){
|
|
1191
|
+
var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2),
|
|
1192
|
+
rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius;
|
|
1193
|
+
return {
|
|
1194
|
+
x : this.x + (Math.cos(centreAngle) * rangeFromCentre),
|
|
1195
|
+
y : this.y + (Math.sin(centreAngle) * rangeFromCentre)
|
|
1196
|
+
};
|
|
1197
|
+
},
|
|
1198
|
+
draw : function(animationPercent){
|
|
1199
|
+
|
|
1200
|
+
var easingDecimal = animationPercent || 1;
|
|
1201
|
+
|
|
1202
|
+
var ctx = this.ctx;
|
|
1203
|
+
|
|
1204
|
+
ctx.beginPath();
|
|
1205
|
+
|
|
1206
|
+
ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle);
|
|
1207
|
+
|
|
1208
|
+
ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true);
|
|
1209
|
+
|
|
1210
|
+
ctx.closePath();
|
|
1211
|
+
ctx.strokeStyle = this.strokeColor;
|
|
1212
|
+
ctx.lineWidth = this.strokeWidth;
|
|
1213
|
+
|
|
1214
|
+
ctx.fillStyle = this.fillColor;
|
|
1215
|
+
|
|
1216
|
+
ctx.fill();
|
|
1217
|
+
ctx.lineJoin = 'bevel';
|
|
1218
|
+
|
|
1219
|
+
if (this.showStroke){
|
|
1220
|
+
ctx.stroke();
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
Chart.Rectangle = Chart.Element.extend({
|
|
1226
|
+
draw : function(){
|
|
1227
|
+
var ctx = this.ctx,
|
|
1228
|
+
halfWidth = this.width/2,
|
|
1229
|
+
leftX = this.x - halfWidth,
|
|
1230
|
+
rightX = this.x + halfWidth,
|
|
1231
|
+
top = this.base - (this.base - this.y),
|
|
1232
|
+
halfStroke = this.strokeWidth / 2;
|
|
1233
|
+
|
|
1234
|
+
// Canvas doesn't allow us to stroke inside the width so we can
|
|
1235
|
+
// adjust the sizes to fit if we're setting a stroke on the line
|
|
1236
|
+
if (this.showStroke){
|
|
1237
|
+
leftX += halfStroke;
|
|
1238
|
+
rightX -= halfStroke;
|
|
1239
|
+
top += halfStroke;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
ctx.beginPath();
|
|
1243
|
+
|
|
1244
|
+
ctx.fillStyle = this.fillColor;
|
|
1245
|
+
ctx.strokeStyle = this.strokeColor;
|
|
1246
|
+
ctx.lineWidth = this.strokeWidth;
|
|
1247
|
+
|
|
1248
|
+
// It'd be nice to keep this class totally generic to any rectangle
|
|
1249
|
+
// and simply specify which border to miss out.
|
|
1250
|
+
ctx.moveTo(leftX, this.base);
|
|
1251
|
+
ctx.lineTo(leftX, top);
|
|
1252
|
+
ctx.lineTo(rightX, top);
|
|
1253
|
+
ctx.lineTo(rightX, this.base);
|
|
1254
|
+
ctx.fill();
|
|
1255
|
+
if (this.showStroke){
|
|
1256
|
+
ctx.stroke();
|
|
1257
|
+
}
|
|
1258
|
+
},
|
|
1259
|
+
height : function(){
|
|
1260
|
+
return this.base - this.y;
|
|
1261
|
+
},
|
|
1262
|
+
inRange : function(chartX,chartY){
|
|
1263
|
+
return (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base);
|
|
1264
|
+
}
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
Chart.Tooltip = Chart.Element.extend({
|
|
1268
|
+
draw : function(){
|
|
1269
|
+
|
|
1270
|
+
var ctx = this.chart.ctx;
|
|
1271
|
+
|
|
1272
|
+
ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
|
|
1273
|
+
|
|
1274
|
+
this.xAlign = "center";
|
|
1275
|
+
this.yAlign = "above";
|
|
1276
|
+
|
|
1277
|
+
//Distance between the actual element.y position and the start of the tooltip caret
|
|
1278
|
+
var caretPadding = this.caretPadding = 2;
|
|
1279
|
+
|
|
1280
|
+
var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding,
|
|
1281
|
+
tooltipRectHeight = this.fontSize + 2*this.yPadding,
|
|
1282
|
+
tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding;
|
|
1283
|
+
|
|
1284
|
+
if (this.x + tooltipWidth/2 >this.chart.width){
|
|
1285
|
+
this.xAlign = "left";
|
|
1286
|
+
} else if (this.x - tooltipWidth/2 < 0){
|
|
1287
|
+
this.xAlign = "right";
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
if (this.y - tooltipHeight < 0){
|
|
1291
|
+
this.yAlign = "below";
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
|
|
1295
|
+
var tooltipX = this.x - tooltipWidth/2,
|
|
1296
|
+
tooltipY = this.y - tooltipHeight;
|
|
1297
|
+
|
|
1298
|
+
ctx.fillStyle = this.fillColor;
|
|
1299
|
+
|
|
1300
|
+
// Custom Tooltips
|
|
1301
|
+
if(this.custom){
|
|
1302
|
+
this.custom(this);
|
|
1303
|
+
}
|
|
1304
|
+
else{
|
|
1305
|
+
switch(this.yAlign)
|
|
1306
|
+
{
|
|
1307
|
+
case "above":
|
|
1308
|
+
//Draw a caret above the x/y
|
|
1309
|
+
ctx.beginPath();
|
|
1310
|
+
ctx.moveTo(this.x,this.y - caretPadding);
|
|
1311
|
+
ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight));
|
|
1312
|
+
ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight));
|
|
1313
|
+
ctx.closePath();
|
|
1314
|
+
ctx.fill();
|
|
1315
|
+
break;
|
|
1316
|
+
case "below":
|
|
1317
|
+
tooltipY = this.y + caretPadding + this.caretHeight;
|
|
1318
|
+
//Draw a caret below the x/y
|
|
1319
|
+
ctx.beginPath();
|
|
1320
|
+
ctx.moveTo(this.x, this.y + caretPadding);
|
|
1321
|
+
ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight);
|
|
1322
|
+
ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight);
|
|
1323
|
+
ctx.closePath();
|
|
1324
|
+
ctx.fill();
|
|
1325
|
+
break;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
switch(this.xAlign)
|
|
1329
|
+
{
|
|
1330
|
+
case "left":
|
|
1331
|
+
tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight);
|
|
1332
|
+
break;
|
|
1333
|
+
case "right":
|
|
1334
|
+
tooltipX = this.x - (this.cornerRadius + this.caretHeight);
|
|
1335
|
+
break;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius);
|
|
1339
|
+
|
|
1340
|
+
ctx.fill();
|
|
1341
|
+
|
|
1342
|
+
ctx.fillStyle = this.textColor;
|
|
1343
|
+
ctx.textAlign = "center";
|
|
1344
|
+
ctx.textBaseline = "middle";
|
|
1345
|
+
ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
});
|
|
1349
|
+
|
|
1350
|
+
Chart.MultiTooltip = Chart.Element.extend({
|
|
1351
|
+
initialize : function(){
|
|
1352
|
+
this.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
|
|
1353
|
+
|
|
1354
|
+
this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily);
|
|
1355
|
+
|
|
1356
|
+
this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5;
|
|
1357
|
+
|
|
1358
|
+
this.ctx.font = this.titleFont;
|
|
1359
|
+
|
|
1360
|
+
var titleWidth = this.ctx.measureText(this.title).width,
|
|
1361
|
+
//Label has a legend square as well so account for this.
|
|
1362
|
+
labelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3,
|
|
1363
|
+
longestTextWidth = max([labelWidth,titleWidth]);
|
|
1364
|
+
|
|
1365
|
+
this.width = longestTextWidth + (this.xPadding*2);
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
var halfHeight = this.height/2;
|
|
1369
|
+
|
|
1370
|
+
//Check to ensure the height will fit on the canvas
|
|
1371
|
+
if (this.y - halfHeight < 0 ){
|
|
1372
|
+
this.y = halfHeight;
|
|
1373
|
+
} else if (this.y + halfHeight > this.chart.height){
|
|
1374
|
+
this.y = this.chart.height - halfHeight;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
//Decide whether to align left or right based on position on canvas
|
|
1378
|
+
if (this.x > this.chart.width/2){
|
|
1379
|
+
this.x -= this.xOffset + this.width;
|
|
1380
|
+
} else {
|
|
1381
|
+
this.x += this.xOffset;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
|
|
1385
|
+
},
|
|
1386
|
+
getLineHeight : function(index){
|
|
1387
|
+
var baseLineHeight = this.y - (this.height/2) + this.yPadding,
|
|
1388
|
+
afterTitleIndex = index-1;
|
|
1389
|
+
|
|
1390
|
+
//If the index is zero, we're getting the title
|
|
1391
|
+
if (index === 0){
|
|
1392
|
+
return baseLineHeight + this.titleFontSize/2;
|
|
1393
|
+
} else{
|
|
1394
|
+
return baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
},
|
|
1398
|
+
draw : function(){
|
|
1399
|
+
// Custom Tooltips
|
|
1400
|
+
if(this.custom){
|
|
1401
|
+
this.custom(this);
|
|
1402
|
+
}
|
|
1403
|
+
else{
|
|
1404
|
+
drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius);
|
|
1405
|
+
var ctx = this.ctx;
|
|
1406
|
+
ctx.fillStyle = this.fillColor;
|
|
1407
|
+
ctx.fill();
|
|
1408
|
+
ctx.closePath();
|
|
1409
|
+
|
|
1410
|
+
ctx.textAlign = "left";
|
|
1411
|
+
ctx.textBaseline = "middle";
|
|
1412
|
+
ctx.fillStyle = this.titleTextColor;
|
|
1413
|
+
ctx.font = this.titleFont;
|
|
1414
|
+
|
|
1415
|
+
ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0));
|
|
1416
|
+
|
|
1417
|
+
ctx.font = this.font;
|
|
1418
|
+
helpers.each(this.labels,function(label,index){
|
|
1419
|
+
ctx.fillStyle = this.textColor;
|
|
1420
|
+
ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));
|
|
1421
|
+
|
|
1422
|
+
//A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
|
|
1423
|
+
//ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
|
|
1424
|
+
//Instead we'll make a white filled block to put the legendColour palette over.
|
|
1425
|
+
|
|
1426
|
+
ctx.fillStyle = this.legendColorBackground;
|
|
1427
|
+
ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
|
|
1428
|
+
|
|
1429
|
+
ctx.fillStyle = this.legendColors[index].fill;
|
|
1430
|
+
ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
|
|
1431
|
+
|
|
1432
|
+
|
|
1433
|
+
},this);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
|
|
1438
|
+
Chart.Scale = Chart.Element.extend({
|
|
1439
|
+
initialize : function(){
|
|
1440
|
+
this.fit();
|
|
1441
|
+
},
|
|
1442
|
+
buildYLabels : function(){
|
|
1443
|
+
this.yLabels = [];
|
|
1444
|
+
|
|
1445
|
+
var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
|
|
1446
|
+
|
|
1447
|
+
for (var i=0; i<=this.steps; i++){
|
|
1448
|
+
this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
|
|
1449
|
+
}
|
|
1450
|
+
this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0;
|
|
1451
|
+
},
|
|
1452
|
+
addXLabel : function(label){
|
|
1453
|
+
this.xLabels.push(label);
|
|
1454
|
+
this.valuesCount++;
|
|
1455
|
+
this.fit();
|
|
1456
|
+
},
|
|
1457
|
+
removeXLabel : function(){
|
|
1458
|
+
this.xLabels.shift();
|
|
1459
|
+
this.valuesCount--;
|
|
1460
|
+
this.fit();
|
|
1461
|
+
},
|
|
1462
|
+
// Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use
|
|
1463
|
+
fit: function(){
|
|
1464
|
+
// First we need the width of the yLabels, assuming the xLabels aren't rotated
|
|
1465
|
+
|
|
1466
|
+
// To do that we need the base line at the top and base of the chart, assuming there is no x label rotation
|
|
1467
|
+
this.startPoint = (this.display) ? this.fontSize : 0;
|
|
1468
|
+
this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels
|
|
1469
|
+
|
|
1470
|
+
// Apply padding settings to the start and end point.
|
|
1471
|
+
this.startPoint += this.padding;
|
|
1472
|
+
this.endPoint -= this.padding;
|
|
1473
|
+
|
|
1474
|
+
// Cache the starting height, so can determine if we need to recalculate the scale yAxis
|
|
1475
|
+
var cachedHeight = this.endPoint - this.startPoint,
|
|
1476
|
+
cachedYLabelWidth;
|
|
1477
|
+
|
|
1478
|
+
// Build the current yLabels so we have an idea of what size they'll be to start
|
|
1479
|
+
/*
|
|
1480
|
+
* This sets what is returned from calculateScaleRange as static properties of this class:
|
|
1481
|
+
*
|
|
1482
|
+
this.steps;
|
|
1483
|
+
this.stepValue;
|
|
1484
|
+
this.min;
|
|
1485
|
+
this.max;
|
|
1486
|
+
*
|
|
1487
|
+
*/
|
|
1488
|
+
this.calculateYRange(cachedHeight);
|
|
1489
|
+
|
|
1490
|
+
// With these properties set we can now build the array of yLabels
|
|
1491
|
+
// and also the width of the largest yLabel
|
|
1492
|
+
this.buildYLabels();
|
|
1493
|
+
|
|
1494
|
+
this.calculateXLabelRotation();
|
|
1495
|
+
|
|
1496
|
+
while((cachedHeight > this.endPoint - this.startPoint)){
|
|
1497
|
+
cachedHeight = this.endPoint - this.startPoint;
|
|
1498
|
+
cachedYLabelWidth = this.yLabelWidth;
|
|
1499
|
+
|
|
1500
|
+
this.calculateYRange(cachedHeight);
|
|
1501
|
+
this.buildYLabels();
|
|
1502
|
+
|
|
1503
|
+
// Only go through the xLabel loop again if the yLabel width has changed
|
|
1504
|
+
if (cachedYLabelWidth < this.yLabelWidth){
|
|
1505
|
+
this.calculateXLabelRotation();
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
},
|
|
1510
|
+
calculateXLabelRotation : function(){
|
|
1511
|
+
//Get the width of each grid by calculating the difference
|
|
1512
|
+
//between x offsets between 0 and 1.
|
|
1513
|
+
|
|
1514
|
+
this.ctx.font = this.font;
|
|
1515
|
+
|
|
1516
|
+
var firstWidth = this.ctx.measureText(this.xLabels[0]).width,
|
|
1517
|
+
lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,
|
|
1518
|
+
firstRotated,
|
|
1519
|
+
lastRotated;
|
|
1520
|
+
|
|
1521
|
+
|
|
1522
|
+
this.xScalePaddingRight = lastWidth/2 + 3;
|
|
1523
|
+
this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10;
|
|
1524
|
+
|
|
1525
|
+
this.xLabelRotation = 0;
|
|
1526
|
+
if (this.display){
|
|
1527
|
+
var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels),
|
|
1528
|
+
cosRotation,
|
|
1529
|
+
firstRotatedWidth;
|
|
1530
|
+
this.xLabelWidth = originalLabelWidth;
|
|
1531
|
+
//Allow 3 pixels x2 padding either side for label readability
|
|
1532
|
+
var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;
|
|
1533
|
+
|
|
1534
|
+
//Max label rotate should be 90 - also act as a loop counter
|
|
1535
|
+
while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){
|
|
1536
|
+
cosRotation = Math.cos(toRadians(this.xLabelRotation));
|
|
1537
|
+
|
|
1538
|
+
firstRotated = cosRotation * firstWidth;
|
|
1539
|
+
lastRotated = cosRotation * lastWidth;
|
|
1540
|
+
|
|
1541
|
+
// We're right aligning the text now.
|
|
1542
|
+
if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){
|
|
1543
|
+
this.xScalePaddingLeft = firstRotated + this.fontSize / 2;
|
|
1544
|
+
}
|
|
1545
|
+
this.xScalePaddingRight = this.fontSize/2;
|
|
1546
|
+
|
|
1547
|
+
|
|
1548
|
+
this.xLabelRotation++;
|
|
1549
|
+
this.xLabelWidth = cosRotation * originalLabelWidth;
|
|
1550
|
+
|
|
1551
|
+
}
|
|
1552
|
+
if (this.xLabelRotation > 0){
|
|
1553
|
+
this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
else{
|
|
1557
|
+
this.xLabelWidth = 0;
|
|
1558
|
+
this.xScalePaddingRight = this.padding;
|
|
1559
|
+
this.xScalePaddingLeft = this.padding;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
},
|
|
1563
|
+
// Needs to be overidden in each Chart type
|
|
1564
|
+
// Otherwise we need to pass all the data into the scale class
|
|
1565
|
+
calculateYRange: noop,
|
|
1566
|
+
drawingArea: function(){
|
|
1567
|
+
return this.startPoint - this.endPoint;
|
|
1568
|
+
},
|
|
1569
|
+
calculateY : function(value){
|
|
1570
|
+
var scalingFactor = this.drawingArea() / (this.min - this.max);
|
|
1571
|
+
return this.endPoint - (scalingFactor * (value - this.min));
|
|
1572
|
+
},
|
|
1573
|
+
calculateX : function(index){
|
|
1574
|
+
var isRotated = (this.xLabelRotation > 0),
|
|
1575
|
+
// innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
|
|
1576
|
+
innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
|
|
1577
|
+
valueWidth = innerWidth/Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1),
|
|
1578
|
+
valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
|
|
1579
|
+
|
|
1580
|
+
if (this.offsetGridLines){
|
|
1581
|
+
valueOffset += (valueWidth/2);
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
return Math.round(valueOffset);
|
|
1585
|
+
},
|
|
1586
|
+
update : function(newProps){
|
|
1587
|
+
helpers.extend(this, newProps);
|
|
1588
|
+
this.fit();
|
|
1589
|
+
},
|
|
1590
|
+
draw : function(){
|
|
1591
|
+
var ctx = this.ctx,
|
|
1592
|
+
yLabelGap = (this.endPoint - this.startPoint) / this.steps,
|
|
1593
|
+
xStart = Math.round(this.xScalePaddingLeft);
|
|
1594
|
+
if (this.display){
|
|
1595
|
+
ctx.fillStyle = this.textColor;
|
|
1596
|
+
ctx.font = this.font;
|
|
1597
|
+
each(this.yLabels,function(labelString,index){
|
|
1598
|
+
var yLabelCenter = this.endPoint - (yLabelGap * index),
|
|
1599
|
+
linePositionY = Math.round(yLabelCenter),
|
|
1600
|
+
drawHorizontalLine = this.showHorizontalLines;
|
|
1601
|
+
|
|
1602
|
+
ctx.textAlign = "right";
|
|
1603
|
+
ctx.textBaseline = "middle";
|
|
1604
|
+
if (this.showLabels){
|
|
1605
|
+
ctx.fillText(labelString,xStart - 10,yLabelCenter);
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
// This is X axis, so draw it
|
|
1609
|
+
if (index === 0 && !drawHorizontalLine){
|
|
1610
|
+
drawHorizontalLine = true;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
if (drawHorizontalLine){
|
|
1614
|
+
ctx.beginPath();
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
if (index > 0){
|
|
1618
|
+
// This is a grid line in the centre, so drop that
|
|
1619
|
+
ctx.lineWidth = this.gridLineWidth;
|
|
1620
|
+
ctx.strokeStyle = this.gridLineColor;
|
|
1621
|
+
} else {
|
|
1622
|
+
// This is the first line on the scale
|
|
1623
|
+
ctx.lineWidth = this.lineWidth;
|
|
1624
|
+
ctx.strokeStyle = this.lineColor;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
linePositionY += helpers.aliasPixel(ctx.lineWidth);
|
|
1628
|
+
|
|
1629
|
+
if(drawHorizontalLine){
|
|
1630
|
+
ctx.moveTo(xStart, linePositionY);
|
|
1631
|
+
ctx.lineTo(this.width, linePositionY);
|
|
1632
|
+
ctx.stroke();
|
|
1633
|
+
ctx.closePath();
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
ctx.lineWidth = this.lineWidth;
|
|
1637
|
+
ctx.strokeStyle = this.lineColor;
|
|
1638
|
+
ctx.beginPath();
|
|
1639
|
+
ctx.moveTo(xStart - 5, linePositionY);
|
|
1640
|
+
ctx.lineTo(xStart, linePositionY);
|
|
1641
|
+
ctx.stroke();
|
|
1642
|
+
ctx.closePath();
|
|
1643
|
+
|
|
1644
|
+
},this);
|
|
1645
|
+
|
|
1646
|
+
each(this.xLabels,function(label,index){
|
|
1647
|
+
var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
|
|
1648
|
+
// Check to see if line/bar here and decide where to place the line
|
|
1649
|
+
linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
|
|
1650
|
+
isRotated = (this.xLabelRotation > 0),
|
|
1651
|
+
drawVerticalLine = this.showVerticalLines;
|
|
1652
|
+
|
|
1653
|
+
// This is Y axis, so draw it
|
|
1654
|
+
if (index === 0 && !drawVerticalLine){
|
|
1655
|
+
drawVerticalLine = true;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
if (drawVerticalLine){
|
|
1659
|
+
ctx.beginPath();
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
if (index > 0){
|
|
1663
|
+
// This is a grid line in the centre, so drop that
|
|
1664
|
+
ctx.lineWidth = this.gridLineWidth;
|
|
1665
|
+
ctx.strokeStyle = this.gridLineColor;
|
|
1666
|
+
} else {
|
|
1667
|
+
// This is the first line on the scale
|
|
1668
|
+
ctx.lineWidth = this.lineWidth;
|
|
1669
|
+
ctx.strokeStyle = this.lineColor;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
if (drawVerticalLine){
|
|
1673
|
+
ctx.moveTo(linePos,this.endPoint);
|
|
1674
|
+
ctx.lineTo(linePos,this.startPoint - 3);
|
|
1675
|
+
ctx.stroke();
|
|
1676
|
+
ctx.closePath();
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
|
|
1680
|
+
ctx.lineWidth = this.lineWidth;
|
|
1681
|
+
ctx.strokeStyle = this.lineColor;
|
|
1682
|
+
|
|
1683
|
+
|
|
1684
|
+
// Small lines at the bottom of the base grid line
|
|
1685
|
+
ctx.beginPath();
|
|
1686
|
+
ctx.moveTo(linePos,this.endPoint);
|
|
1687
|
+
ctx.lineTo(linePos,this.endPoint + 5);
|
|
1688
|
+
ctx.stroke();
|
|
1689
|
+
ctx.closePath();
|
|
1690
|
+
|
|
1691
|
+
ctx.save();
|
|
1692
|
+
ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8);
|
|
1693
|
+
ctx.rotate(toRadians(this.xLabelRotation)*-1);
|
|
1694
|
+
ctx.font = this.font;
|
|
1695
|
+
ctx.textAlign = (isRotated) ? "right" : "center";
|
|
1696
|
+
ctx.textBaseline = (isRotated) ? "middle" : "top";
|
|
1697
|
+
ctx.fillText(label, 0, 0);
|
|
1698
|
+
ctx.restore();
|
|
1699
|
+
},this);
|
|
1700
|
+
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
Chart.RadialScale = Chart.Element.extend({
|
|
1707
|
+
initialize: function(){
|
|
1708
|
+
this.size = min([this.height, this.width]);
|
|
1709
|
+
this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
|
|
1710
|
+
},
|
|
1711
|
+
calculateCenterOffset: function(value){
|
|
1712
|
+
// Take into account half font size + the yPadding of the top value
|
|
1713
|
+
var scalingFactor = this.drawingArea / (this.max - this.min);
|
|
1714
|
+
|
|
1715
|
+
return (value - this.min) * scalingFactor;
|
|
1716
|
+
},
|
|
1717
|
+
update : function(){
|
|
1718
|
+
if (!this.lineArc){
|
|
1719
|
+
this.setScaleSize();
|
|
1720
|
+
} else {
|
|
1721
|
+
this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
|
|
1722
|
+
}
|
|
1723
|
+
this.buildYLabels();
|
|
1724
|
+
},
|
|
1725
|
+
buildYLabels: function(){
|
|
1726
|
+
this.yLabels = [];
|
|
1727
|
+
|
|
1728
|
+
var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
|
|
1729
|
+
|
|
1730
|
+
for (var i=0; i<=this.steps; i++){
|
|
1731
|
+
this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
|
|
1732
|
+
}
|
|
1733
|
+
},
|
|
1734
|
+
getCircumference : function(){
|
|
1735
|
+
return ((Math.PI*2) / this.valuesCount);
|
|
1736
|
+
},
|
|
1737
|
+
setScaleSize: function(){
|
|
1738
|
+
/*
|
|
1739
|
+
* Right, this is really confusing and there is a lot of maths going on here
|
|
1740
|
+
* The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
|
|
1741
|
+
*
|
|
1742
|
+
* Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
|
|
1743
|
+
*
|
|
1744
|
+
* Solution:
|
|
1745
|
+
*
|
|
1746
|
+
* We assume the radius of the polygon is half the size of the canvas at first
|
|
1747
|
+
* at each index we check if the text overlaps.
|
|
1748
|
+
*
|
|
1749
|
+
* Where it does, we store that angle and that index.
|
|
1750
|
+
*
|
|
1751
|
+
* After finding the largest index and angle we calculate how much we need to remove
|
|
1752
|
+
* from the shape radius to move the point inwards by that x.
|
|
1753
|
+
*
|
|
1754
|
+
* We average the left and right distances to get the maximum shape radius that can fit in the box
|
|
1755
|
+
* along with labels.
|
|
1756
|
+
*
|
|
1757
|
+
* Once we have that, we can find the centre point for the chart, by taking the x text protrusion
|
|
1758
|
+
* on each side, removing that from the size, halving it and adding the left x protrusion width.
|
|
1759
|
+
*
|
|
1760
|
+
* This will mean we have a shape fitted to the canvas, as large as it can be with the labels
|
|
1761
|
+
* and position it in the most space efficient manner
|
|
1762
|
+
*
|
|
1763
|
+
* https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
|
|
1764
|
+
*/
|
|
1765
|
+
|
|
1766
|
+
|
|
1767
|
+
// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
|
|
1768
|
+
// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
|
|
1769
|
+
var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]),
|
|
1770
|
+
pointPosition,
|
|
1771
|
+
i,
|
|
1772
|
+
textWidth,
|
|
1773
|
+
halfTextWidth,
|
|
1774
|
+
furthestRight = this.width,
|
|
1775
|
+
furthestRightIndex,
|
|
1776
|
+
furthestRightAngle,
|
|
1777
|
+
furthestLeft = 0,
|
|
1778
|
+
furthestLeftIndex,
|
|
1779
|
+
furthestLeftAngle,
|
|
1780
|
+
xProtrusionLeft,
|
|
1781
|
+
xProtrusionRight,
|
|
1782
|
+
radiusReductionRight,
|
|
1783
|
+
radiusReductionLeft,
|
|
1784
|
+
maxWidthRadius;
|
|
1785
|
+
this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
|
|
1786
|
+
for (i=0;i<this.valuesCount;i++){
|
|
1787
|
+
// 5px to space the text slightly out - similar to what we do in the draw function.
|
|
1788
|
+
pointPosition = this.getPointPosition(i, largestPossibleRadius);
|
|
1789
|
+
textWidth = this.ctx.measureText(template(this.templateString, { value: this.labels[i] })).width + 5;
|
|
1790
|
+
if (i === 0 || i === this.valuesCount/2){
|
|
1791
|
+
// If we're at index zero, or exactly the middle, we're at exactly the top/bottom
|
|
1792
|
+
// of the radar chart, so text will be aligned centrally, so we'll half it and compare
|
|
1793
|
+
// w/left and right text sizes
|
|
1794
|
+
halfTextWidth = textWidth/2;
|
|
1795
|
+
if (pointPosition.x + halfTextWidth > furthestRight) {
|
|
1796
|
+
furthestRight = pointPosition.x + halfTextWidth;
|
|
1797
|
+
furthestRightIndex = i;
|
|
1798
|
+
}
|
|
1799
|
+
if (pointPosition.x - halfTextWidth < furthestLeft) {
|
|
1800
|
+
furthestLeft = pointPosition.x - halfTextWidth;
|
|
1801
|
+
furthestLeftIndex = i;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
else if (i < this.valuesCount/2) {
|
|
1805
|
+
// Less than half the values means we'll left align the text
|
|
1806
|
+
if (pointPosition.x + textWidth > furthestRight) {
|
|
1807
|
+
furthestRight = pointPosition.x + textWidth;
|
|
1808
|
+
furthestRightIndex = i;
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
else if (i > this.valuesCount/2){
|
|
1812
|
+
// More than half the values means we'll right align the text
|
|
1813
|
+
if (pointPosition.x - textWidth < furthestLeft) {
|
|
1814
|
+
furthestLeft = pointPosition.x - textWidth;
|
|
1815
|
+
furthestLeftIndex = i;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
xProtrusionLeft = furthestLeft;
|
|
1821
|
+
|
|
1822
|
+
xProtrusionRight = Math.ceil(furthestRight - this.width);
|
|
1823
|
+
|
|
1824
|
+
furthestRightAngle = this.getIndexAngle(furthestRightIndex);
|
|
1825
|
+
|
|
1826
|
+
furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
|
|
1827
|
+
|
|
1828
|
+
radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2);
|
|
1829
|
+
|
|
1830
|
+
radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2);
|
|
1831
|
+
|
|
1832
|
+
// Ensure we actually need to reduce the size of the chart
|
|
1833
|
+
radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
|
|
1834
|
+
radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
|
|
1835
|
+
|
|
1836
|
+
this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2;
|
|
1837
|
+
|
|
1838
|
+
//this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
|
|
1839
|
+
this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
|
|
1840
|
+
|
|
1841
|
+
},
|
|
1842
|
+
setCenterPoint: function(leftMovement, rightMovement){
|
|
1843
|
+
|
|
1844
|
+
var maxRight = this.width - rightMovement - this.drawingArea,
|
|
1845
|
+
maxLeft = leftMovement + this.drawingArea;
|
|
1846
|
+
|
|
1847
|
+
this.xCenter = (maxLeft + maxRight)/2;
|
|
1848
|
+
// Always vertically in the centre as the text height doesn't change
|
|
1849
|
+
this.yCenter = (this.height/2);
|
|
1850
|
+
},
|
|
1851
|
+
|
|
1852
|
+
getIndexAngle : function(index){
|
|
1853
|
+
var angleMultiplier = (Math.PI * 2) / this.valuesCount;
|
|
1854
|
+
// Start from the top instead of right, so remove a quarter of the circle
|
|
1855
|
+
|
|
1856
|
+
return index * angleMultiplier - (Math.PI/2);
|
|
1857
|
+
},
|
|
1858
|
+
getPointPosition : function(index, distanceFromCenter){
|
|
1859
|
+
var thisAngle = this.getIndexAngle(index);
|
|
1860
|
+
return {
|
|
1861
|
+
x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
|
|
1862
|
+
y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
|
|
1863
|
+
};
|
|
1864
|
+
},
|
|
1865
|
+
draw: function(){
|
|
1866
|
+
if (this.display){
|
|
1867
|
+
var ctx = this.ctx;
|
|
1868
|
+
each(this.yLabels, function(label, index){
|
|
1869
|
+
// Don't draw a centre value
|
|
1870
|
+
if (index > 0){
|
|
1871
|
+
var yCenterOffset = index * (this.drawingArea/this.steps),
|
|
1872
|
+
yHeight = this.yCenter - yCenterOffset,
|
|
1873
|
+
pointPosition;
|
|
1874
|
+
|
|
1875
|
+
// Draw circular lines around the scale
|
|
1876
|
+
if (this.lineWidth > 0){
|
|
1877
|
+
ctx.strokeStyle = this.lineColor;
|
|
1878
|
+
ctx.lineWidth = this.lineWidth;
|
|
1879
|
+
|
|
1880
|
+
if(this.lineArc){
|
|
1881
|
+
ctx.beginPath();
|
|
1882
|
+
ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2);
|
|
1883
|
+
ctx.closePath();
|
|
1884
|
+
ctx.stroke();
|
|
1885
|
+
} else{
|
|
1886
|
+
ctx.beginPath();
|
|
1887
|
+
for (var i=0;i<this.valuesCount;i++)
|
|
1888
|
+
{
|
|
1889
|
+
pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue)));
|
|
1890
|
+
if (i === 0){
|
|
1891
|
+
ctx.moveTo(pointPosition.x, pointPosition.y);
|
|
1892
|
+
} else {
|
|
1893
|
+
ctx.lineTo(pointPosition.x, pointPosition.y);
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
ctx.closePath();
|
|
1897
|
+
ctx.stroke();
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
if(this.showLabels){
|
|
1901
|
+
ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
|
|
1902
|
+
if (this.showLabelBackdrop){
|
|
1903
|
+
var labelWidth = ctx.measureText(label).width;
|
|
1904
|
+
ctx.fillStyle = this.backdropColor;
|
|
1905
|
+
ctx.fillRect(
|
|
1906
|
+
this.xCenter - labelWidth/2 - this.backdropPaddingX,
|
|
1907
|
+
yHeight - this.fontSize/2 - this.backdropPaddingY,
|
|
1908
|
+
labelWidth + this.backdropPaddingX*2,
|
|
1909
|
+
this.fontSize + this.backdropPaddingY*2
|
|
1910
|
+
);
|
|
1911
|
+
}
|
|
1912
|
+
ctx.textAlign = 'center';
|
|
1913
|
+
ctx.textBaseline = "middle";
|
|
1914
|
+
ctx.fillStyle = this.fontColor;
|
|
1915
|
+
ctx.fillText(label, this.xCenter, yHeight);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
}, this);
|
|
1919
|
+
|
|
1920
|
+
if (!this.lineArc){
|
|
1921
|
+
ctx.lineWidth = this.angleLineWidth;
|
|
1922
|
+
ctx.strokeStyle = this.angleLineColor;
|
|
1923
|
+
for (var i = this.valuesCount - 1; i >= 0; i--) {
|
|
1924
|
+
if (this.angleLineWidth > 0){
|
|
1925
|
+
var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max));
|
|
1926
|
+
ctx.beginPath();
|
|
1927
|
+
ctx.moveTo(this.xCenter, this.yCenter);
|
|
1928
|
+
ctx.lineTo(outerPosition.x, outerPosition.y);
|
|
1929
|
+
ctx.stroke();
|
|
1930
|
+
ctx.closePath();
|
|
1931
|
+
}
|
|
1932
|
+
// Extra 3px out for some label spacing
|
|
1933
|
+
var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
|
|
1934
|
+
ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
|
|
1935
|
+
ctx.fillStyle = this.pointLabelFontColor;
|
|
1936
|
+
|
|
1937
|
+
var labelsCount = this.labels.length,
|
|
1938
|
+
halfLabelsCount = this.labels.length/2,
|
|
1939
|
+
quarterLabelsCount = halfLabelsCount/2,
|
|
1940
|
+
upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
|
|
1941
|
+
exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
|
|
1942
|
+
if (i === 0){
|
|
1943
|
+
ctx.textAlign = 'center';
|
|
1944
|
+
} else if(i === halfLabelsCount){
|
|
1945
|
+
ctx.textAlign = 'center';
|
|
1946
|
+
} else if (i < halfLabelsCount){
|
|
1947
|
+
ctx.textAlign = 'left';
|
|
1948
|
+
} else {
|
|
1949
|
+
ctx.textAlign = 'right';
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
// Set the correct text baseline based on outer positioning
|
|
1953
|
+
if (exactQuarter){
|
|
1954
|
+
ctx.textBaseline = 'middle';
|
|
1955
|
+
} else if (upperHalf){
|
|
1956
|
+
ctx.textBaseline = 'bottom';
|
|
1957
|
+
} else {
|
|
1958
|
+
ctx.textBaseline = 'top';
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
});
|
|
1967
|
+
|
|
1968
|
+
// Attach global event to resize each chart instance when the browser resizes
|
|
1969
|
+
helpers.addEvent(window, "resize", (function(){
|
|
1970
|
+
// Basic debounce of resize function so it doesn't hurt performance when resizing browser.
|
|
1971
|
+
var timeout;
|
|
1972
|
+
return function(){
|
|
1973
|
+
clearTimeout(timeout);
|
|
1974
|
+
timeout = setTimeout(function(){
|
|
1975
|
+
each(Chart.instances,function(instance){
|
|
1976
|
+
// If the responsive flag is set in the chart instance config
|
|
1977
|
+
// Cascade the resize event down to the chart.
|
|
1978
|
+
if (instance.options.responsive){
|
|
1979
|
+
instance.resize(instance.render, true);
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
}, 50);
|
|
1983
|
+
};
|
|
1984
|
+
})());
|
|
1985
|
+
|
|
1986
|
+
|
|
1987
|
+
if (amd) {
|
|
1988
|
+
define(function(){
|
|
1989
|
+
return Chart;
|
|
1990
|
+
});
|
|
1991
|
+
} else if (typeof module === 'object' && module.exports) {
|
|
1992
|
+
module.exports = Chart;
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
root.Chart = Chart;
|
|
1996
|
+
|
|
1997
|
+
Chart.noConflict = function(){
|
|
1998
|
+
root.Chart = previous;
|
|
1999
|
+
return Chart;
|
|
2000
|
+
};
|
|
2001
|
+
|
|
2002
|
+
}).call(this);
|