flashgrid-ext 2.1.0 → 2.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 +4 -4
- data/lib/flashgrid/ext/version.rb +1 -1
- data/vendor/assets/javascripts/chart.js +3032 -1503
- data/vendor/assets/stylesheets/editor.css.scss +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 498b94d1dd886a00fc90c94ddcd565049ddcff67
|
4
|
+
data.tar.gz: 636e5899ea50dcd923dce8e09e2f8bfdacc1d1d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc8415e599bdbff5d6c070eaf2aa82e9838e995f3d40bee6036bcc37fc34c3b9a533c3e98bc38042361dba6dfc2d26b1c3ed901f4916288c084dbedb54088856
|
7
|
+
data.tar.gz: 3131953fc868b849d0e7feb16c581ccda3cf8b0f655e4ef1ab0799468cffbff6cc264ec075bee8cf2b395053a1f057f05d588676dc2875e64e4abdf7372be7f7
|
@@ -1,1751 +1,3280 @@
|
|
1
|
-
|
2
|
-
var chart = this;
|
1
|
+
(function(){
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
"use strict";
|
4
|
+
|
5
|
+
//Declare root variable - window in the browser, global on the server
|
6
|
+
var root = this,
|
7
|
+
previous = root.Chart;
|
8
|
+
|
9
|
+
//Occupy the global variable of Chart, and create a simple base class
|
10
|
+
var Chart = function(context){
|
11
|
+
var chart = this;
|
12
|
+
this.canvas = context.canvas;
|
13
|
+
|
14
|
+
this.ctx = context;
|
15
|
+
|
16
|
+
//Variables global to the chart
|
17
|
+
var width = this.width = context.canvas.width;
|
18
|
+
var height = this.height = context.canvas.height;
|
19
|
+
this.aspectRatio = this.width / this.height;
|
20
|
+
//High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
|
21
|
+
helpers.retinaScale(this);
|
22
|
+
|
23
|
+
return this;
|
24
|
+
};
|
25
|
+
//Globally expose the defaults to allow for user updating/changing
|
26
|
+
Chart.defaults = {
|
27
|
+
global: {
|
28
|
+
// Boolean - Whether to animate the chart
|
29
|
+
animation: true,
|
30
|
+
|
31
|
+
// Number - Number of animation steps
|
32
|
+
animationSteps: 60,
|
33
|
+
|
34
|
+
// String - Animation easing effect
|
35
|
+
animationEasing: "easeOutQuart",
|
36
|
+
|
37
|
+
// Boolean - If we should show the scale at all
|
38
|
+
showScale: true,
|
39
|
+
|
40
|
+
// Boolean - If we want to override with a hard coded scale
|
41
|
+
scaleOverride: false,
|
42
|
+
|
43
|
+
// ** Required if scaleOverride is true **
|
44
|
+
// Number - The number of steps in a hard coded scale
|
45
|
+
scaleSteps: null,
|
46
|
+
// Number - The value jump in the hard coded scale
|
47
|
+
scaleStepWidth: null,
|
48
|
+
// Number - The scale starting value
|
49
|
+
scaleStartValue: null,
|
50
|
+
|
51
|
+
// String - Colour of the scale line
|
52
|
+
scaleLineColor: "rgba(0,0,0,.1)",
|
53
|
+
|
54
|
+
// Number - Pixel width of the scale line
|
55
|
+
scaleLineWidth: 1,
|
56
|
+
|
57
|
+
// Boolean - Whether to show labels on the scale
|
58
|
+
scaleShowLabels: true,
|
59
|
+
|
60
|
+
// Interpolated JS string - can access value
|
61
|
+
scaleLabel: "<%=value%>",
|
62
|
+
|
63
|
+
// Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there
|
64
|
+
scaleIntegersOnly: true,
|
65
|
+
|
66
|
+
// Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
|
67
|
+
scaleBeginAtZero: false,
|
68
|
+
|
69
|
+
// String - Scale label font declaration for the scale label
|
70
|
+
scaleFontFamily: "'Gotham', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
|
71
|
+
|
72
|
+
// Number - Scale label font size in pixels
|
73
|
+
scaleFontSize: 13,
|
74
|
+
|
75
|
+
// String - Scale label font weight style
|
76
|
+
scaleFontStyle: "normal",
|
77
|
+
|
78
|
+
// String - Scale label font colour
|
79
|
+
scaleFontColor: "rgba(158,171,179,1)",
|
80
|
+
|
81
|
+
// Boolean - whether or not the chart should be responsive and resize when the browser does.
|
82
|
+
responsive: true,
|
83
|
+
|
84
|
+
// Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
|
85
|
+
showTooltips: true,
|
86
|
+
|
87
|
+
// Array - Array of string names to attach tooltip events
|
88
|
+
tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],
|
89
|
+
|
90
|
+
// String - Tooltip background colour
|
91
|
+
tooltipFillColor: "rgba(71,74,84,1)",
|
92
|
+
|
93
|
+
// String - Tooltip label font declaration for the scale label
|
94
|
+
tooltipFontFamily: "'Gotham', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
|
95
|
+
|
96
|
+
// Number - Tooltip label font size in pixels
|
97
|
+
tooltipFontSize: 11,
|
98
|
+
|
99
|
+
// String - Tooltip font weight style
|
100
|
+
tooltipFontStyle: "bold",
|
101
|
+
|
102
|
+
// String - Tooltip label font colour
|
103
|
+
tooltipFontColor: "rgba(255,255,255,1)",
|
104
|
+
|
105
|
+
// String - Tooltip title font declaration for the scale label
|
106
|
+
tooltipTitleFontFamily: "'Gotham', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
|
107
|
+
|
108
|
+
// Number - Tooltip title font size in pixels
|
109
|
+
tooltipTitleFontSize: 11,
|
110
|
+
|
111
|
+
// String - Tooltip title font weight style
|
112
|
+
tooltipTitleFontStyle: "bold",
|
113
|
+
|
114
|
+
// String - Tooltip title font colour
|
115
|
+
tooltipTitleFontColor: "rgba(255,255,255,1)",
|
116
|
+
|
117
|
+
// Number - pixel width of padding around tooltip text
|
118
|
+
tooltipYPadding: 8,
|
119
|
+
|
120
|
+
// Number - pixel width of padding around tooltip text
|
121
|
+
tooltipXPadding: 10,
|
122
|
+
|
123
|
+
// Number - Size of the caret on the tooltip
|
124
|
+
tooltipCaretSize: 5,
|
125
|
+
|
126
|
+
// Number - Pixel radius of the tooltip border
|
127
|
+
tooltipCornerRadius: 3,
|
128
|
+
|
129
|
+
// Number - Pixel offset from point x to tooltip edge
|
130
|
+
tooltipXOffset: 10,
|
131
|
+
|
132
|
+
// String - Template string for single tooltips
|
133
|
+
tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>",
|
134
|
+
|
135
|
+
// String - Template string for single tooltips
|
136
|
+
multiTooltipTemplate: "<%= value %>",
|
137
|
+
|
138
|
+
// String - Colour behind the legend colour block
|
139
|
+
multiTooltipKeyBackground: 'rgba(255,255,255,1)',
|
140
|
+
|
141
|
+
// Function - Will fire on animation progression.
|
142
|
+
onAnimationProgress: function(){},
|
143
|
+
|
144
|
+
// Function - Will fire on animation completion.
|
145
|
+
onAnimationComplete: function(){}
|
146
|
+
|
147
|
+
}
|
148
|
+
};
|
149
|
+
|
150
|
+
//Create a dictionary of chart types, to allow for extension of existing types
|
151
|
+
Chart.types = {};
|
152
|
+
|
153
|
+
//Global Chart helpers object for utility methods and classes
|
154
|
+
var helpers = Chart.helpers = {};
|
155
|
+
|
156
|
+
//-- Basic js utility methods
|
157
|
+
var each = helpers.each = function(loopable,callback,self){
|
158
|
+
var additionalArgs = Array.prototype.slice.call(arguments, 3);
|
159
|
+
// Check to see if null or undefined firstly.
|
160
|
+
if (loopable){
|
161
|
+
if (loopable.length === +loopable.length){
|
162
|
+
var i;
|
163
|
+
for (i=0; i<loopable.length; i++){
|
164
|
+
callback.apply(self,[loopable[i], i].concat(additionalArgs));
|
165
|
+
}
|
166
|
+
}
|
167
|
+
else{
|
168
|
+
for (var item in loopable){
|
169
|
+
callback.apply(self,[loopable[item],item].concat(additionalArgs));
|
170
|
+
}
|
171
|
+
}
|
172
|
+
}
|
173
|
+
},
|
174
|
+
clone = helpers.clone = function(obj){
|
175
|
+
var objClone = {};
|
176
|
+
each(obj,function(value,key){
|
177
|
+
if (obj.hasOwnProperty(key)) objClone[key] = value;
|
178
|
+
});
|
179
|
+
return objClone;
|
180
|
+
},
|
181
|
+
extend = helpers.extend = function(base){
|
182
|
+
each(Array.prototype.slice.call(arguments,1), function(extensionObject) {
|
183
|
+
each(extensionObject,function(value,key){
|
184
|
+
if (extensionObject.hasOwnProperty(key)) base[key] = value;
|
185
|
+
});
|
186
|
+
});
|
187
|
+
return base;
|
188
|
+
},
|
189
|
+
merge = helpers.merge = function(base,master){
|
190
|
+
//Merge properties in left object over to a shallow clone of object right.
|
191
|
+
var args = Array.prototype.slice.call(arguments,0);
|
192
|
+
args.unshift({});
|
193
|
+
return extend.apply(null, args);
|
194
|
+
},
|
195
|
+
indexOf = helpers.indexOf = function(arrayToSearch, item){
|
196
|
+
if (Array.prototype.indexOf) {
|
197
|
+
return arrayToSearch.indexOf(item);
|
198
|
+
}
|
199
|
+
else{
|
200
|
+
for (var i = 0; i < arrayToSearch.length; i++) {
|
201
|
+
if (arrayToSearch[i] === item) return i;
|
202
|
+
}
|
203
|
+
return -1;
|
204
|
+
}
|
205
|
+
},
|
206
|
+
inherits = helpers.inherits = function(extensions){
|
207
|
+
//Basic javascript inheritance based on the model created in Backbone.js
|
208
|
+
var parent = this;
|
209
|
+
var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function(){ return parent.apply(this, arguments); };
|
210
|
+
|
211
|
+
var Surrogate = function(){ this.constructor = ChartElement;};
|
212
|
+
Surrogate.prototype = parent.prototype;
|
213
|
+
ChartElement.prototype = new Surrogate();
|
214
|
+
|
215
|
+
ChartElement.extend = inherits;
|
216
|
+
|
217
|
+
if (extensions) extend(ChartElement.prototype, extensions);
|
218
|
+
|
219
|
+
ChartElement.__super__ = parent.prototype;
|
220
|
+
|
221
|
+
return ChartElement;
|
222
|
+
},
|
223
|
+
noop = helpers.noop = function(){},
|
224
|
+
uid = helpers.uid = (function(){
|
225
|
+
var id=0;
|
226
|
+
return function(){
|
227
|
+
return "chart-" + id++;
|
228
|
+
};
|
229
|
+
})(),
|
230
|
+
warn = helpers.warn = function(str){
|
231
|
+
//Method for warning of errors
|
232
|
+
if (window.console && typeof window.console.warn == "function") console.warn(str);
|
233
|
+
},
|
234
|
+
amd = helpers.amd = (typeof root.define == 'function' && root.define.amd),
|
235
|
+
//-- Math methods
|
236
|
+
isNumber = helpers.isNumber = function(n){
|
237
|
+
return !isNaN(parseFloat(n)) && isFinite(n);
|
238
|
+
},
|
239
|
+
max = helpers.max = function(array){
|
240
|
+
return Math.max.apply( Math, array );
|
241
|
+
},
|
242
|
+
min = helpers.min = function(array){
|
243
|
+
return Math.min.apply( Math, array );
|
244
|
+
},
|
245
|
+
cap = helpers.cap = function(valueToCap,maxValue,minValue){
|
246
|
+
if(isNumber(maxValue)) {
|
247
|
+
if( valueToCap > maxValue ) {
|
248
|
+
return maxValue;
|
249
|
+
}
|
250
|
+
}
|
251
|
+
else if(isNumber(minValue)){
|
252
|
+
if ( valueToCap < minValue ){
|
253
|
+
return minValue;
|
254
|
+
}
|
255
|
+
}
|
256
|
+
return valueToCap;
|
257
|
+
},
|
258
|
+
getDecimalPlaces = helpers.getDecimalPlaces = function(num){
|
259
|
+
if (num%1!==0 && isNumber(num)){
|
260
|
+
return num.toString().split(".")[1].length;
|
261
|
+
}
|
262
|
+
else {
|
263
|
+
return 0;
|
264
|
+
}
|
265
|
+
},
|
266
|
+
toRadians = helpers.radians = function(degrees){
|
267
|
+
return degrees * (Math.PI/180);
|
268
|
+
},
|
269
|
+
// Gets the angle from vertical upright to the point about a centre.
|
270
|
+
getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint){
|
271
|
+
var distanceFromXCenter = anglePoint.x - centrePoint.x,
|
272
|
+
distanceFromYCenter = anglePoint.y - centrePoint.y,
|
273
|
+
radialDistanceFromCenter = Math.sqrt( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
|
274
|
+
|
275
|
+
|
276
|
+
var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter);
|
277
|
+
|
278
|
+
//If the segment is in the top left quadrant, we need to add another rotation to the angle
|
279
|
+
if (distanceFromXCenter < 0 && distanceFromYCenter < 0){
|
280
|
+
angle += Math.PI*2;
|
281
|
+
}
|
282
|
+
|
283
|
+
return {
|
284
|
+
angle: angle,
|
285
|
+
distance: radialDistanceFromCenter
|
286
|
+
};
|
7
287
|
},
|
8
|
-
|
9
|
-
return
|
288
|
+
aliasPixel = helpers.aliasPixel = function(pixelWidth){
|
289
|
+
return (pixelWidth % 2 === 0) ? 0 : 0.5;
|
10
290
|
},
|
11
|
-
|
12
|
-
|
291
|
+
splineCurve = helpers.splineCurve = function(FirstPoint,MiddlePoint,AfterPoint,t){
|
292
|
+
//Props to Rob Spencer at scaled innovation for his post on splining between points
|
293
|
+
//http://scaledinnovation.com/analytics/splines/aboutSplines.html
|
294
|
+
var d01=Math.sqrt(Math.pow(MiddlePoint.x-FirstPoint.x,2)+Math.pow(MiddlePoint.y-FirstPoint.y,2)),
|
295
|
+
d12=Math.sqrt(Math.pow(AfterPoint.x-MiddlePoint.x,2)+Math.pow(AfterPoint.y-MiddlePoint.y,2)),
|
296
|
+
fa=t*d01/(d01+d12),// scaling factor for triangle Ta
|
297
|
+
fb=t*d12/(d01+d12);
|
298
|
+
return {
|
299
|
+
inner : {
|
300
|
+
x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x),
|
301
|
+
y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y)
|
302
|
+
},
|
303
|
+
outer : {
|
304
|
+
x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x),
|
305
|
+
y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y)
|
306
|
+
}
|
307
|
+
};
|
308
|
+
},
|
309
|
+
calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val){
|
310
|
+
return Math.floor(Math.log(val) / Math.LN10);
|
13
311
|
},
|
14
|
-
|
15
|
-
|
16
|
-
|
312
|
+
calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly){
|
313
|
+
|
314
|
+
//Set a minimum step of two - a point at the top of the graph, and a point at the base
|
315
|
+
var minSteps = 2,
|
316
|
+
maxSteps = Math.floor(drawingSize/(textSize * 1.5)),
|
317
|
+
skipFitting = (minSteps >= maxSteps);
|
318
|
+
|
319
|
+
var maxValue = max(valuesArray),
|
320
|
+
minValue = min(valuesArray);
|
321
|
+
|
322
|
+
// We need some degree of seperation here to calculate the scales if all the values are the same
|
323
|
+
// Adding/minusing 0.5 will give us a range of 1.
|
324
|
+
if (maxValue === minValue){
|
325
|
+
maxValue += 0.5;
|
326
|
+
// So we don't end up with a graph with a negative start value if we've said always start from zero
|
327
|
+
if (minValue >= 0.5 && !startFromZero){
|
328
|
+
minValue -= 0.5;
|
329
|
+
}
|
330
|
+
else{
|
331
|
+
// Make up a whole number above the values
|
332
|
+
maxValue += 0.5;
|
333
|
+
}
|
334
|
+
}
|
335
|
+
|
336
|
+
var valueRange = Math.abs(maxValue - minValue),
|
337
|
+
rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
|
338
|
+
graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
|
339
|
+
graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
|
340
|
+
graphRange = graphMax - graphMin,
|
341
|
+
stepValue = Math.pow(10, rangeOrderOfMagnitude),
|
342
|
+
numberOfSteps = Math.round(graphRange / stepValue);
|
343
|
+
|
344
|
+
//If we have more space on the graph we'll use it to give more definition to the data
|
345
|
+
while((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {
|
346
|
+
if(numberOfSteps > maxSteps){
|
347
|
+
stepValue *=2;
|
348
|
+
numberOfSteps = Math.round(graphRange/stepValue);
|
349
|
+
// Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
|
350
|
+
if (numberOfSteps % 1 !== 0){
|
351
|
+
skipFitting = true;
|
352
|
+
}
|
353
|
+
}
|
354
|
+
//We can fit in double the amount of scale points on the scale
|
355
|
+
else{
|
356
|
+
//If user has declared ints only, and the step value isn't a decimal
|
357
|
+
if (integersOnly && rangeOrderOfMagnitude >= 0){
|
358
|
+
//If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
|
359
|
+
if(stepValue/2 % 1 === 0){
|
360
|
+
stepValue /=2;
|
361
|
+
numberOfSteps = Math.round(graphRange/stepValue);
|
362
|
+
}
|
363
|
+
//If it would make it a float break out of the loop
|
364
|
+
else{
|
365
|
+
break;
|
366
|
+
}
|
367
|
+
}
|
368
|
+
//If the scale doesn't have to be an int, make the scale more granular anyway.
|
369
|
+
else{
|
370
|
+
stepValue /=2;
|
371
|
+
numberOfSteps = Math.round(graphRange/stepValue);
|
372
|
+
}
|
373
|
+
|
374
|
+
}
|
375
|
+
}
|
376
|
+
|
377
|
+
if (skipFitting){
|
378
|
+
numberOfSteps = minSteps;
|
379
|
+
stepValue = graphRange / numberOfSteps;
|
380
|
+
}
|
381
|
+
|
382
|
+
return {
|
383
|
+
steps : numberOfSteps,
|
384
|
+
stepValue : stepValue,
|
385
|
+
min : graphMin,
|
386
|
+
max : graphMin + (numberOfSteps * stepValue)
|
387
|
+
};
|
388
|
+
|
389
|
+
},
|
390
|
+
/* jshint ignore:start */
|
391
|
+
// Blows up jshint errors based on the new Function constructor
|
392
|
+
//Templating methods
|
393
|
+
//Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
|
394
|
+
template = helpers.template = function(templateString, valuesObject){
|
395
|
+
var cache = {};
|
396
|
+
function tmpl(str, data){
|
397
|
+
// Figure out if we're getting a template, or if we need to
|
398
|
+
// load the template - and be sure to cache the result.
|
399
|
+
var fn = !/\W/.test(str) ?
|
400
|
+
cache[str] = cache[str] :
|
401
|
+
|
402
|
+
// Generate a reusable function that will serve as a template
|
403
|
+
// generator (and which will be cached).
|
404
|
+
new Function("obj",
|
405
|
+
"var p=[],print=function(){p.push.apply(p,arguments);};" +
|
406
|
+
|
407
|
+
// Introduce the data as local variables using with(){}
|
408
|
+
"with(obj){p.push('" +
|
409
|
+
|
410
|
+
// Convert the template into pure JavaScript
|
411
|
+
str
|
412
|
+
.replace(/[\r\t\n]/g, " ")
|
413
|
+
.split("<%").join("\t")
|
414
|
+
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
|
415
|
+
.replace(/\t=(.*?)%>/g, "',$1,'")
|
416
|
+
.split("\t").join("');")
|
417
|
+
.split("%>").join("p.push('")
|
418
|
+
.split("\r").join("\\'") +
|
419
|
+
"');}return p.join('');"
|
420
|
+
);
|
421
|
+
|
422
|
+
// Provide some basic currying to the user
|
423
|
+
return data ? fn( data ) : fn;
|
424
|
+
}
|
425
|
+
return tmpl(templateString,valuesObject);
|
426
|
+
},
|
427
|
+
/* jshint ignore:end */
|
428
|
+
generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){
|
429
|
+
var labelsArray = new Array(numberOfSteps);
|
430
|
+
if (labelTemplateString){
|
431
|
+
each(labelsArray,function(val,index){
|
432
|
+
labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))});
|
433
|
+
});
|
434
|
+
}
|
435
|
+
return labelsArray;
|
436
|
+
},
|
437
|
+
//--Animation methods
|
438
|
+
//Easing functions adapted from Robert Penner's easing equations
|
439
|
+
//http://www.robertpenner.com/easing/
|
440
|
+
easingEffects = helpers.easingEffects = {
|
441
|
+
linear: function (t) {
|
442
|
+
return t;
|
443
|
+
},
|
444
|
+
easeInQuad: function (t) {
|
445
|
+
return t * t;
|
446
|
+
},
|
447
|
+
easeOutQuad: function (t) {
|
448
|
+
return -1 * t * (t - 2);
|
449
|
+
},
|
450
|
+
easeInOutQuad: function (t) {
|
451
|
+
if ((t /= 1 / 2) < 1) return 1 / 2 * t * t;
|
452
|
+
return -1 / 2 * ((--t) * (t - 2) - 1);
|
453
|
+
},
|
454
|
+
easeInCubic: function (t) {
|
455
|
+
return t * t * t;
|
456
|
+
},
|
457
|
+
easeOutCubic: function (t) {
|
458
|
+
return 1 * ((t = t / 1 - 1) * t * t + 1);
|
459
|
+
},
|
460
|
+
easeInOutCubic: function (t) {
|
461
|
+
if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t;
|
462
|
+
return 1 / 2 * ((t -= 2) * t * t + 2);
|
463
|
+
},
|
464
|
+
easeInQuart: function (t) {
|
465
|
+
return t * t * t * t;
|
466
|
+
},
|
467
|
+
easeOutQuart: function (t) {
|
468
|
+
return -1 * ((t = t / 1 - 1) * t * t * t - 1);
|
469
|
+
},
|
470
|
+
easeInOutQuart: function (t) {
|
471
|
+
if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t;
|
472
|
+
return -1 / 2 * ((t -= 2) * t * t * t - 2);
|
473
|
+
},
|
474
|
+
easeInQuint: function (t) {
|
475
|
+
return 1 * (t /= 1) * t * t * t * t;
|
476
|
+
},
|
477
|
+
easeOutQuint: function (t) {
|
478
|
+
return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
|
479
|
+
},
|
480
|
+
easeInOutQuint: function (t) {
|
481
|
+
if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t;
|
482
|
+
return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
|
483
|
+
},
|
484
|
+
easeInSine: function (t) {
|
485
|
+
return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
|
486
|
+
},
|
487
|
+
easeOutSine: function (t) {
|
488
|
+
return 1 * Math.sin(t / 1 * (Math.PI / 2));
|
489
|
+
},
|
490
|
+
easeInOutSine: function (t) {
|
491
|
+
return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
|
492
|
+
},
|
493
|
+
easeInExpo: function (t) {
|
494
|
+
return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
|
495
|
+
},
|
496
|
+
easeOutExpo: function (t) {
|
497
|
+
return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
|
498
|
+
},
|
499
|
+
easeInOutExpo: function (t) {
|
500
|
+
if (t === 0) return 0;
|
501
|
+
if (t === 1) return 1;
|
502
|
+
if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1));
|
503
|
+
return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
|
504
|
+
},
|
505
|
+
easeInCirc: function (t) {
|
506
|
+
if (t >= 1) return t;
|
507
|
+
return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
|
508
|
+
},
|
509
|
+
easeOutCirc: function (t) {
|
510
|
+
return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
|
511
|
+
},
|
512
|
+
easeInOutCirc: function (t) {
|
513
|
+
if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
|
514
|
+
return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
|
515
|
+
},
|
516
|
+
easeInElastic: function (t) {
|
517
|
+
var s = 1.70158;
|
518
|
+
var p = 0;
|
519
|
+
var a = 1;
|
520
|
+
if (t === 0) return 0;
|
521
|
+
if ((t /= 1) == 1) return 1;
|
522
|
+
if (!p) p = 1 * 0.3;
|
523
|
+
if (a < Math.abs(1)) {
|
524
|
+
a = 1;
|
525
|
+
s = p / 4;
|
526
|
+
} else s = p / (2 * Math.PI) * Math.asin(1 / a);
|
527
|
+
return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
|
528
|
+
},
|
529
|
+
easeOutElastic: function (t) {
|
530
|
+
var s = 1.70158;
|
531
|
+
var p = 0;
|
532
|
+
var a = 1;
|
533
|
+
if (t === 0) return 0;
|
534
|
+
if ((t /= 1) == 1) return 1;
|
535
|
+
if (!p) p = 1 * 0.3;
|
536
|
+
if (a < Math.abs(1)) {
|
537
|
+
a = 1;
|
538
|
+
s = p / 4;
|
539
|
+
} else s = p / (2 * Math.PI) * Math.asin(1 / a);
|
540
|
+
return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
|
541
|
+
},
|
542
|
+
easeInOutElastic: function (t) {
|
543
|
+
var s = 1.70158;
|
544
|
+
var p = 0;
|
545
|
+
var a = 1;
|
546
|
+
if (t === 0) return 0;
|
547
|
+
if ((t /= 1 / 2) == 2) return 1;
|
548
|
+
if (!p) p = 1 * (0.3 * 1.5);
|
549
|
+
if (a < Math.abs(1)) {
|
550
|
+
a = 1;
|
551
|
+
s = p / 4;
|
552
|
+
} else s = p / (2 * Math.PI) * Math.asin(1 / a);
|
553
|
+
if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
|
554
|
+
return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
|
555
|
+
},
|
556
|
+
easeInBack: function (t) {
|
557
|
+
var s = 1.70158;
|
558
|
+
return 1 * (t /= 1) * t * ((s + 1) * t - s);
|
559
|
+
},
|
560
|
+
easeOutBack: function (t) {
|
561
|
+
var s = 1.70158;
|
562
|
+
return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
|
563
|
+
},
|
564
|
+
easeInOutBack: function (t) {
|
565
|
+
var s = 1.70158;
|
566
|
+
if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
|
567
|
+
return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
|
568
|
+
},
|
569
|
+
easeInBounce: function (t) {
|
570
|
+
return 1 - easingEffects.easeOutBounce(1 - t);
|
571
|
+
},
|
572
|
+
easeOutBounce: function (t) {
|
573
|
+
if ((t /= 1) < (1 / 2.75)) {
|
574
|
+
return 1 * (7.5625 * t * t);
|
575
|
+
} else if (t < (2 / 2.75)) {
|
576
|
+
return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
|
577
|
+
} else if (t < (2.5 / 2.75)) {
|
578
|
+
return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
|
579
|
+
} else {
|
580
|
+
return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
|
581
|
+
}
|
582
|
+
},
|
583
|
+
easeInOutBounce: function (t) {
|
584
|
+
if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5;
|
585
|
+
return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
|
586
|
+
}
|
17
587
|
},
|
18
|
-
|
19
|
-
|
588
|
+
//Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
|
589
|
+
requestAnimFrame = helpers.requestAnimFrame = (function(){
|
590
|
+
return window.requestAnimationFrame ||
|
591
|
+
window.webkitRequestAnimationFrame ||
|
592
|
+
window.mozRequestAnimationFrame ||
|
593
|
+
window.oRequestAnimationFrame ||
|
594
|
+
window.msRequestAnimationFrame ||
|
595
|
+
function(callback) {
|
596
|
+
return window.setTimeout(callback, 1000 / 60);
|
597
|
+
};
|
598
|
+
})(),
|
599
|
+
cancelAnimFrame = helpers.cancelAnimFrame = (function(){
|
600
|
+
return window.cancelAnimationFrame ||
|
601
|
+
window.webkitCancelAnimationFrame ||
|
602
|
+
window.mozCancelAnimationFrame ||
|
603
|
+
window.oCancelAnimationFrame ||
|
604
|
+
window.msCancelAnimationFrame ||
|
605
|
+
function(callback) {
|
606
|
+
return window.clearTimeout(callback, 1000 / 60);
|
607
|
+
};
|
608
|
+
})(),
|
609
|
+
animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){
|
610
|
+
|
611
|
+
var currentStep = 0,
|
612
|
+
easingFunction = easingEffects[easingString] || easingEffects.linear;
|
613
|
+
|
614
|
+
var animationFrame = function(){
|
615
|
+
currentStep++;
|
616
|
+
var stepDecimal = currentStep/totalSteps;
|
617
|
+
var easeDecimal = easingFunction(stepDecimal);
|
618
|
+
|
619
|
+
callback.call(chartInstance,easeDecimal,stepDecimal, currentStep);
|
620
|
+
onProgress.call(chartInstance,easeDecimal,stepDecimal);
|
621
|
+
if (currentStep < totalSteps){
|
622
|
+
chartInstance.animationFrame = requestAnimFrame(animationFrame);
|
623
|
+
} else{
|
624
|
+
onComplete.apply(chartInstance);
|
625
|
+
}
|
626
|
+
};
|
627
|
+
requestAnimFrame(animationFrame);
|
20
628
|
},
|
21
|
-
|
22
|
-
|
629
|
+
//-- DOM methods
|
630
|
+
getRelativePosition = helpers.getRelativePosition = function(evt){
|
631
|
+
var mouseX, mouseY;
|
632
|
+
var e = evt.originalEvent || evt,
|
633
|
+
canvas = evt.currentTarget || evt.srcElement,
|
634
|
+
boundingRect = canvas.getBoundingClientRect();
|
635
|
+
|
636
|
+
if (e.touches){
|
637
|
+
mouseX = e.touches[0].clientX - boundingRect.left;
|
638
|
+
mouseY = e.touches[0].clientY - boundingRect.top;
|
639
|
+
|
640
|
+
}
|
641
|
+
else{
|
642
|
+
mouseX = e.clientX - boundingRect.left;
|
643
|
+
mouseY = e.clientY - boundingRect.top;
|
644
|
+
}
|
645
|
+
|
646
|
+
return {
|
647
|
+
x : mouseX,
|
648
|
+
y : mouseY
|
649
|
+
};
|
650
|
+
|
23
651
|
},
|
24
|
-
|
25
|
-
if (
|
26
|
-
|
652
|
+
addEvent = helpers.addEvent = function(node,eventType,method){
|
653
|
+
if (node.addEventListener){
|
654
|
+
node.addEventListener(eventType,method);
|
655
|
+
} else if (node.attachEvent){
|
656
|
+
node.attachEvent("on"+eventType, method);
|
657
|
+
} else {
|
658
|
+
node["on"+eventType] = method;
|
659
|
+
}
|
27
660
|
},
|
28
|
-
|
29
|
-
|
661
|
+
removeEvent = helpers.removeEvent = function(node, eventType, handler){
|
662
|
+
if (node.removeEventListener){
|
663
|
+
node.removeEventListener(eventType, handler, false);
|
664
|
+
} else if (node.detachEvent){
|
665
|
+
node.detachEvent("on"+eventType,handler);
|
666
|
+
} else{
|
667
|
+
node["on" + eventType] = noop;
|
668
|
+
}
|
30
669
|
},
|
31
|
-
|
32
|
-
|
670
|
+
bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){
|
671
|
+
// Create the events object if it's not already present
|
672
|
+
if (!chartInstance.events) chartInstance.events = {};
|
673
|
+
|
674
|
+
each(arrayOfEvents,function(eventName){
|
675
|
+
chartInstance.events[eventName] = function(){
|
676
|
+
handler.apply(chartInstance, arguments);
|
677
|
+
};
|
678
|
+
addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]);
|
679
|
+
});
|
33
680
|
},
|
34
|
-
|
35
|
-
|
36
|
-
|
681
|
+
unbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) {
|
682
|
+
each(arrayOfEvents, function(handler,eventName){
|
683
|
+
removeEvent(chartInstance.chart.canvas, eventName, handler);
|
684
|
+
});
|
37
685
|
},
|
38
|
-
|
39
|
-
|
686
|
+
getMaximumSize = helpers.getMaximumSize = function(domNode){
|
687
|
+
var container = domNode.parentNode;
|
688
|
+
// TODO = check cross browser stuff with this.
|
689
|
+
return container.clientWidth;
|
40
690
|
},
|
41
|
-
|
42
|
-
|
691
|
+
retinaScale = helpers.retinaScale = function(chart){
|
692
|
+
var ctx = chart.ctx,
|
693
|
+
width = chart.canvas.width,
|
694
|
+
height = chart.canvas.height;
|
695
|
+
//console.log(width + " x " + height);
|
696
|
+
if (window.devicePixelRatio) {
|
697
|
+
ctx.canvas.style.width = width + "px";
|
698
|
+
ctx.canvas.style.height = height + "px";
|
699
|
+
ctx.canvas.height = height * window.devicePixelRatio;
|
700
|
+
ctx.canvas.width = width * window.devicePixelRatio;
|
701
|
+
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
702
|
+
}
|
43
703
|
},
|
44
|
-
|
45
|
-
|
46
|
-
|
704
|
+
//-- Canvas methods
|
705
|
+
clear = helpers.clear = function(chart){
|
706
|
+
chart.ctx.clearRect(0,0,chart.width,chart.height);
|
47
707
|
},
|
48
|
-
|
49
|
-
return
|
708
|
+
fontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){
|
709
|
+
return fontStyle + " " + pixelSize+"px " + fontFamily;
|
50
710
|
},
|
51
|
-
|
52
|
-
|
711
|
+
longestText = helpers.longestText = function(ctx,font,arrayOfStrings){
|
712
|
+
ctx.font = font;
|
713
|
+
var longest = 0;
|
714
|
+
each(arrayOfStrings,function(string){
|
715
|
+
var textWidth = ctx.measureText(string).width;
|
716
|
+
longest = (textWidth > longest) ? textWidth : longest;
|
717
|
+
});
|
718
|
+
return longest;
|
53
719
|
},
|
54
|
-
|
55
|
-
|
720
|
+
drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){
|
721
|
+
ctx.beginPath();
|
722
|
+
ctx.moveTo(x + radius, y);
|
723
|
+
ctx.lineTo(x + width - radius, y);
|
724
|
+
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
725
|
+
ctx.lineTo(x + width, y + height - radius);
|
726
|
+
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
727
|
+
ctx.lineTo(x + radius, y + height);
|
728
|
+
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
729
|
+
ctx.lineTo(x, y + radius);
|
730
|
+
ctx.quadraticCurveTo(x, y, x + radius, y);
|
731
|
+
ctx.closePath();
|
732
|
+
};
|
733
|
+
|
734
|
+
|
735
|
+
//Store a reference to each instance - allowing us to globally resize chart instances on window resize.
|
736
|
+
//Destroy method on the chart will remove the instance of the chart from this reference.
|
737
|
+
Chart.instances = {};
|
738
|
+
|
739
|
+
Chart.Type = function(data,options,chart){
|
740
|
+
this.options = options;
|
741
|
+
this.chart = chart;
|
742
|
+
this.id = uid();
|
743
|
+
//Add the chart instance to the global namespace
|
744
|
+
Chart.instances[this.id] = this;
|
745
|
+
|
746
|
+
// Initialize is always called when a chart type is created
|
747
|
+
// By default it is a no op, but it should be extended
|
748
|
+
if (options.responsive){
|
749
|
+
this.resize();
|
750
|
+
}
|
751
|
+
this.initialize.call(this,data);
|
752
|
+
};
|
753
|
+
|
754
|
+
//Core methods that'll be a part of every chart type
|
755
|
+
extend(Chart.Type.prototype,{
|
756
|
+
initialize : function(){return this;},
|
757
|
+
clear : function(){
|
758
|
+
clear(this.chart);
|
759
|
+
return this;
|
760
|
+
},
|
761
|
+
stop : function(){
|
762
|
+
// Stops any current animation loop occuring
|
763
|
+
helpers.cancelAnimFrame.call(root, this.animationFrame);
|
764
|
+
return this;
|
765
|
+
},
|
766
|
+
resize : function(callback){
|
767
|
+
this.stop();
|
768
|
+
var canvas = this.chart.canvas,
|
769
|
+
newWidth = getMaximumSize(this.chart.canvas),
|
770
|
+
newHeight = newWidth / this.chart.aspectRatio;
|
771
|
+
|
772
|
+
canvas.width = this.chart.width = newWidth;
|
773
|
+
canvas.height = this.chart.height = newHeight;
|
774
|
+
|
775
|
+
retinaScale(this.chart);
|
776
|
+
|
777
|
+
if (typeof callback === "function"){
|
778
|
+
callback.apply(this, Array.prototype.slice.call(arguments, 1));
|
779
|
+
}
|
780
|
+
return this;
|
781
|
+
},
|
782
|
+
reflow : noop,
|
783
|
+
render : function(reflow){
|
784
|
+
if (reflow){
|
785
|
+
this.reflow();
|
786
|
+
}
|
787
|
+
if (this.options.animation && !reflow){
|
788
|
+
helpers.animationLoop(
|
789
|
+
this.draw,
|
790
|
+
this.options.animationSteps,
|
791
|
+
this.options.animationEasing,
|
792
|
+
this.options.onAnimationProgress,
|
793
|
+
this.options.onAnimationComplete,
|
794
|
+
this
|
795
|
+
);
|
796
|
+
}
|
797
|
+
else{
|
798
|
+
this.draw();
|
799
|
+
this.options.onAnimationComplete.call(this);
|
800
|
+
}
|
801
|
+
return this;
|
802
|
+
},
|
803
|
+
generateLegend : function(){
|
804
|
+
return template(this.options.legendTemplate,this);
|
805
|
+
},
|
806
|
+
destroy : function(){
|
807
|
+
this.clear();
|
808
|
+
unbindEvents(this, this.events);
|
809
|
+
delete Chart.instances[this.id];
|
810
|
+
},
|
811
|
+
showTooltip : function(ChartElements, forceRedraw){
|
812
|
+
// Only redraw the chart if we've actually changed what we're hovering on.
|
813
|
+
if (typeof this.activeElements === 'undefined') this.activeElements = [];
|
814
|
+
|
815
|
+
var isChanged = (function(Elements){
|
816
|
+
var changed = false;
|
817
|
+
|
818
|
+
if (Elements.length !== this.activeElements.length){
|
819
|
+
changed = true;
|
820
|
+
return changed;
|
821
|
+
}
|
822
|
+
|
823
|
+
each(Elements, function(element, index){
|
824
|
+
if (element !== this.activeElements[index]){
|
825
|
+
changed = true;
|
826
|
+
}
|
827
|
+
}, this);
|
828
|
+
return changed;
|
829
|
+
}).call(this, ChartElements);
|
830
|
+
|
831
|
+
if (!isChanged && !forceRedraw){
|
832
|
+
return;
|
833
|
+
}
|
834
|
+
else{
|
835
|
+
this.activeElements = ChartElements;
|
836
|
+
}
|
837
|
+
this.draw();
|
838
|
+
if (ChartElements.length > 0){
|
839
|
+
// If we have multiple datasets, show a MultiTooltip for all of the data points at that index
|
840
|
+
if (this.datasets && this.datasets.length > 1) {
|
841
|
+
var dataArray,
|
842
|
+
dataIndex;
|
843
|
+
|
844
|
+
for (var i = this.datasets.length - 1; i >= 0; i--) {
|
845
|
+
dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments;
|
846
|
+
dataIndex = indexOf(dataArray, ChartElements[0]);
|
847
|
+
if (dataIndex !== -1){
|
848
|
+
break;
|
849
|
+
}
|
850
|
+
}
|
851
|
+
var tooltipLabels = [],
|
852
|
+
tooltipColors = [],
|
853
|
+
medianPosition = (function(index) {
|
854
|
+
|
855
|
+
// Get all the points at that particular index
|
856
|
+
var Elements = [],
|
857
|
+
dataCollection,
|
858
|
+
xPositions = [],
|
859
|
+
yPositions = [],
|
860
|
+
xMax,
|
861
|
+
yMax,
|
862
|
+
xMin,
|
863
|
+
yMin;
|
864
|
+
helpers.each(this.datasets, function(dataset){
|
865
|
+
dataCollection = dataset.points || dataset.bars || dataset.segments;
|
866
|
+
Elements.push(dataCollection[dataIndex]);
|
867
|
+
});
|
868
|
+
|
869
|
+
helpers.each(Elements, function(element) {
|
870
|
+
xPositions.push(element.x);
|
871
|
+
yPositions.push(element.y);
|
872
|
+
|
873
|
+
|
874
|
+
//Include any colour information about the element
|
875
|
+
tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element));
|
876
|
+
tooltipColors.push({
|
877
|
+
fill: element._saved.fillColor || element.fillColor,
|
878
|
+
stroke: element._saved.strokeColor || element.strokeColor
|
879
|
+
});
|
880
|
+
|
881
|
+
}, this);
|
882
|
+
|
883
|
+
yMin = min(yPositions);
|
884
|
+
yMax = max(yPositions);
|
885
|
+
|
886
|
+
xMin = min(xPositions);
|
887
|
+
xMax = max(xPositions);
|
888
|
+
|
889
|
+
return {
|
890
|
+
x: (xMin > this.chart.width/2) ? xMin : xMax,
|
891
|
+
y: (yMin + yMax)/2
|
892
|
+
};
|
893
|
+
}).call(this, dataIndex);
|
894
|
+
|
895
|
+
new Chart.MultiTooltip({
|
896
|
+
x: medianPosition.x,
|
897
|
+
y: medianPosition.y,
|
898
|
+
xPadding: this.options.tooltipXPadding,
|
899
|
+
yPadding: this.options.tooltipYPadding,
|
900
|
+
xOffset: this.options.tooltipXOffset,
|
901
|
+
fillColor: this.options.tooltipFillColor,
|
902
|
+
textColor: this.options.tooltipFontColor,
|
903
|
+
fontFamily: this.options.tooltipFontFamily,
|
904
|
+
fontStyle: this.options.tooltipFontStyle,
|
905
|
+
fontSize: this.options.tooltipFontSize,
|
906
|
+
titleTextColor: this.options.tooltipTitleFontColor,
|
907
|
+
titleFontFamily: this.options.tooltipTitleFontFamily,
|
908
|
+
titleFontStyle: this.options.tooltipTitleFontStyle,
|
909
|
+
titleFontSize: this.options.tooltipTitleFontSize,
|
910
|
+
cornerRadius: this.options.tooltipCornerRadius,
|
911
|
+
labels: tooltipLabels,
|
912
|
+
legendColors: tooltipColors,
|
913
|
+
legendColorBackground : this.options.multiTooltipKeyBackground,
|
914
|
+
title: ChartElements[0].label,
|
915
|
+
chart: this.chart,
|
916
|
+
ctx: this.chart.ctx
|
917
|
+
}).draw();
|
918
|
+
|
919
|
+
} else {
|
920
|
+
each(ChartElements, function(Element) {
|
921
|
+
var tooltipPosition = Element.tooltipPosition();
|
922
|
+
new Chart.Tooltip({
|
923
|
+
x: Math.round(tooltipPosition.x),
|
924
|
+
y: Math.round(tooltipPosition.y),
|
925
|
+
xPadding: this.options.tooltipXPadding,
|
926
|
+
yPadding: this.options.tooltipYPadding,
|
927
|
+
fillColor: this.options.tooltipFillColor,
|
928
|
+
textColor: this.options.tooltipFontColor,
|
929
|
+
fontFamily: this.options.tooltipFontFamily,
|
930
|
+
fontStyle: this.options.tooltipFontStyle,
|
931
|
+
fontSize: this.options.tooltipFontSize,
|
932
|
+
caretHeight: this.options.tooltipCaretSize,
|
933
|
+
cornerRadius: this.options.tooltipCornerRadius,
|
934
|
+
text: template(this.options.tooltipTemplate, Element),
|
935
|
+
chart: this.chart
|
936
|
+
}).draw();
|
937
|
+
}, this);
|
938
|
+
}
|
939
|
+
}
|
940
|
+
return this;
|
941
|
+
},
|
942
|
+
toBase64Image : function(){
|
943
|
+
return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
|
944
|
+
}
|
945
|
+
});
|
946
|
+
|
947
|
+
Chart.Type.extend = function(extensions){
|
948
|
+
|
949
|
+
var parent = this;
|
950
|
+
|
951
|
+
var ChartType = function(){
|
952
|
+
return parent.apply(this,arguments);
|
953
|
+
};
|
954
|
+
|
955
|
+
//Copy the prototype object of the this class
|
956
|
+
ChartType.prototype = clone(parent.prototype);
|
957
|
+
//Now overwrite some of the properties in the base class with the new extensions
|
958
|
+
extend(ChartType.prototype, extensions);
|
959
|
+
|
960
|
+
ChartType.extend = Chart.Type.extend;
|
961
|
+
|
962
|
+
if (extensions.name || parent.prototype.name){
|
963
|
+
|
964
|
+
var chartName = extensions.name || parent.prototype.name;
|
965
|
+
//Assign any potential default values of the new chart type
|
966
|
+
|
967
|
+
//If none are defined, we'll use a clone of the chart type this is being extended from.
|
968
|
+
//I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart
|
969
|
+
//doesn't define some defaults of their own.
|
970
|
+
|
971
|
+
var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};
|
972
|
+
|
973
|
+
Chart.defaults[chartName] = extend(baseDefaults,extensions.defaults);
|
974
|
+
|
975
|
+
Chart.types[chartName] = ChartType;
|
976
|
+
|
977
|
+
//Register this new chart type in the Chart prototype
|
978
|
+
Chart.prototype[chartName] = function(data,options){
|
979
|
+
var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {});
|
980
|
+
return new ChartType(data,config,this);
|
981
|
+
};
|
982
|
+
} else{
|
983
|
+
warn("Name not provided for this chart, so it hasn't been registered");
|
984
|
+
}
|
985
|
+
return parent;
|
986
|
+
};
|
987
|
+
|
988
|
+
Chart.Element = function(configuration){
|
989
|
+
extend(this,configuration);
|
990
|
+
this.initialize.apply(this,arguments);
|
991
|
+
this.save();
|
992
|
+
};
|
993
|
+
extend(Chart.Element.prototype,{
|
994
|
+
initialize : function(){},
|
995
|
+
restore : function(props){
|
996
|
+
if (!props){
|
997
|
+
extend(this,this._saved);
|
998
|
+
} else {
|
999
|
+
each(props,function(key){
|
1000
|
+
this[key] = this._saved[key];
|
1001
|
+
},this);
|
1002
|
+
}
|
1003
|
+
return this;
|
1004
|
+
},
|
1005
|
+
save : function(){
|
1006
|
+
this._saved = clone(this);
|
1007
|
+
delete this._saved._saved;
|
1008
|
+
return this;
|
1009
|
+
},
|
1010
|
+
update : function(newProps){
|
1011
|
+
each(newProps,function(value,key){
|
1012
|
+
this._saved[key] = this[key];
|
1013
|
+
this[key] = value;
|
1014
|
+
},this);
|
1015
|
+
return this;
|
1016
|
+
},
|
1017
|
+
transition : function(props,ease){
|
1018
|
+
each(props,function(value,key){
|
1019
|
+
this[key] = ((value - this._saved[key]) * ease) + this._saved[key];
|
1020
|
+
},this);
|
1021
|
+
return this;
|
1022
|
+
},
|
1023
|
+
tooltipPosition : function(){
|
1024
|
+
return {
|
1025
|
+
x : this.x,
|
1026
|
+
y : this.y
|
1027
|
+
};
|
1028
|
+
}
|
1029
|
+
});
|
1030
|
+
|
1031
|
+
Chart.Element.extend = inherits;
|
1032
|
+
|
1033
|
+
|
1034
|
+
Chart.Point = Chart.Element.extend({
|
1035
|
+
inRange : function(chartX,chartY){
|
1036
|
+
var hitDetectionRange = this.hitDetectionRadius + this.radius;
|
1037
|
+
return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2));
|
1038
|
+
},
|
1039
|
+
draw : function(){
|
1040
|
+
var ctx = this.ctx;
|
1041
|
+
ctx.beginPath();
|
1042
|
+
|
1043
|
+
ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
|
1044
|
+
ctx.closePath();
|
1045
|
+
|
1046
|
+
ctx.strokeStyle = this.strokeColor;
|
1047
|
+
ctx.lineWidth = this.strokeWidth;
|
1048
|
+
|
1049
|
+
ctx.fillStyle = this.fillColor;
|
1050
|
+
|
1051
|
+
ctx.fill();
|
1052
|
+
ctx.stroke();
|
1053
|
+
|
1054
|
+
|
1055
|
+
|
1056
|
+
//Quick debug for bezier curve splining
|
1057
|
+
//Highlights control points and the line between them.
|
1058
|
+
//Handy for dev - stripped in the min version.
|
1059
|
+
|
1060
|
+
// ctx.save();
|
1061
|
+
// ctx.fillStyle = "black";
|
1062
|
+
// ctx.strokeStyle = "black"
|
1063
|
+
// ctx.beginPath();
|
1064
|
+
// ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2);
|
1065
|
+
// ctx.fill();
|
1066
|
+
|
1067
|
+
// ctx.beginPath();
|
1068
|
+
// ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2);
|
1069
|
+
// ctx.fill();
|
1070
|
+
|
1071
|
+
// ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);
|
1072
|
+
// ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);
|
1073
|
+
// ctx.stroke();
|
1074
|
+
|
1075
|
+
// ctx.restore();
|
1076
|
+
|
1077
|
+
|
1078
|
+
|
1079
|
+
}
|
1080
|
+
});
|
1081
|
+
|
1082
|
+
Chart.Arc = Chart.Element.extend({
|
1083
|
+
inRange : function(chartX,chartY){
|
1084
|
+
|
1085
|
+
var pointRelativePosition = helpers.getAngleFromPoint(this, {
|
1086
|
+
x: chartX,
|
1087
|
+
y: chartY
|
1088
|
+
});
|
1089
|
+
|
1090
|
+
//Check if within the range of the open/close angle
|
1091
|
+
var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle),
|
1092
|
+
withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius);
|
1093
|
+
|
1094
|
+
return (betweenAngles && withinRadius);
|
1095
|
+
//Ensure within the outside of the arc centre, but inside arc outer
|
1096
|
+
},
|
1097
|
+
tooltipPosition : function(){
|
1098
|
+
var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2),
|
1099
|
+
rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius;
|
1100
|
+
return {
|
1101
|
+
x : this.x + (Math.cos(centreAngle) * rangeFromCentre),
|
1102
|
+
y : this.y + (Math.sin(centreAngle) * rangeFromCentre)
|
1103
|
+
};
|
1104
|
+
},
|
1105
|
+
draw : function(animationPercent){
|
1106
|
+
|
1107
|
+
var easingDecimal = animationPercent || 1;
|
1108
|
+
|
1109
|
+
var ctx = this.ctx;
|
1110
|
+
|
1111
|
+
ctx.beginPath();
|
1112
|
+
|
1113
|
+
ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle);
|
1114
|
+
|
1115
|
+
ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true);
|
1116
|
+
|
1117
|
+
ctx.closePath();
|
1118
|
+
ctx.strokeStyle = this.strokeColor;
|
1119
|
+
ctx.lineWidth = this.strokeWidth;
|
1120
|
+
|
1121
|
+
ctx.fillStyle = this.fillColor;
|
1122
|
+
|
1123
|
+
ctx.fill();
|
1124
|
+
ctx.lineJoin = 'bevel';
|
1125
|
+
|
1126
|
+
if (this.showStroke){
|
1127
|
+
ctx.stroke();
|
1128
|
+
}
|
1129
|
+
}
|
1130
|
+
});
|
1131
|
+
|
1132
|
+
Chart.Rectangle = Chart.Element.extend({
|
1133
|
+
draw : function(){
|
1134
|
+
var ctx = this.ctx,
|
1135
|
+
halfWidth = this.width/2,
|
1136
|
+
leftX = this.x - halfWidth,
|
1137
|
+
rightX = this.x + halfWidth,
|
1138
|
+
top = this.base - (this.base - this.y),
|
1139
|
+
halfStroke = this.strokeWidth / 2;
|
1140
|
+
|
1141
|
+
// Canvas doesn't allow us to stroke inside the width so we can
|
1142
|
+
// adjust the sizes to fit if we're setting a stroke on the line
|
1143
|
+
if (this.showStroke){
|
1144
|
+
leftX += halfStroke;
|
1145
|
+
rightX -= halfStroke;
|
1146
|
+
top += halfStroke;
|
1147
|
+
}
|
1148
|
+
|
1149
|
+
ctx.beginPath();
|
1150
|
+
|
1151
|
+
ctx.fillStyle = this.fillColor;
|
1152
|
+
ctx.strokeStyle = this.strokeColor;
|
1153
|
+
ctx.lineWidth = this.strokeWidth;
|
1154
|
+
|
1155
|
+
// It'd be nice to keep this class totally generic to any rectangle
|
1156
|
+
// and simply specify which border to miss out.
|
1157
|
+
ctx.moveTo(leftX, this.base);
|
1158
|
+
ctx.lineTo(leftX, top);
|
1159
|
+
ctx.lineTo(rightX, top);
|
1160
|
+
ctx.lineTo(rightX, this.base);
|
1161
|
+
ctx.fill();
|
1162
|
+
if (this.showStroke){
|
1163
|
+
ctx.stroke();
|
1164
|
+
}
|
1165
|
+
},
|
1166
|
+
height : function(){
|
1167
|
+
return this.base - this.y;
|
1168
|
+
},
|
1169
|
+
inRange : function(chartX,chartY){
|
1170
|
+
return (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base);
|
1171
|
+
}
|
1172
|
+
});
|
1173
|
+
|
1174
|
+
Chart.Tooltip = Chart.Element.extend({
|
1175
|
+
draw : function(){
|
1176
|
+
|
1177
|
+
var ctx = this.chart.ctx;
|
1178
|
+
|
1179
|
+
ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
|
1180
|
+
|
1181
|
+
this.xAlign = "center";
|
1182
|
+
this.yAlign = "above";
|
1183
|
+
|
1184
|
+
//Distance between the actual element.y position and the start of the tooltip caret
|
1185
|
+
var caretPadding = 2;
|
1186
|
+
|
1187
|
+
var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding,
|
1188
|
+
tooltipRectHeight = this.fontSize + 2*this.yPadding,
|
1189
|
+
tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding;
|
1190
|
+
|
1191
|
+
if (this.x + tooltipWidth/2 >this.chart.width){
|
1192
|
+
this.xAlign = "left";
|
1193
|
+
} else if (this.x - tooltipWidth/2 < 0){
|
1194
|
+
this.xAlign = "right";
|
1195
|
+
}
|
1196
|
+
|
1197
|
+
if (this.y - tooltipHeight < 0){
|
1198
|
+
this.yAlign = "below";
|
1199
|
+
}
|
1200
|
+
|
1201
|
+
|
1202
|
+
var tooltipX = this.x - tooltipWidth/2,
|
1203
|
+
tooltipY = this.y - tooltipHeight;
|
1204
|
+
|
1205
|
+
ctx.fillStyle = this.fillColor;
|
1206
|
+
|
1207
|
+
switch(this.yAlign)
|
1208
|
+
{
|
1209
|
+
case "above":
|
1210
|
+
//Draw a caret above the x/y
|
1211
|
+
ctx.beginPath();
|
1212
|
+
ctx.moveTo(this.x,this.y - caretPadding);
|
1213
|
+
ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight));
|
1214
|
+
ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight));
|
1215
|
+
ctx.closePath();
|
1216
|
+
ctx.fill();
|
1217
|
+
break;
|
1218
|
+
case "below":
|
1219
|
+
tooltipY = this.y + caretPadding + this.caretHeight;
|
1220
|
+
//Draw a caret below the x/y
|
1221
|
+
ctx.beginPath();
|
1222
|
+
ctx.moveTo(this.x, this.y + caretPadding);
|
1223
|
+
ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight);
|
1224
|
+
ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight);
|
1225
|
+
ctx.closePath();
|
1226
|
+
ctx.fill();
|
1227
|
+
break;
|
1228
|
+
}
|
1229
|
+
|
1230
|
+
switch(this.xAlign)
|
1231
|
+
{
|
1232
|
+
case "left":
|
1233
|
+
tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight);
|
1234
|
+
break;
|
1235
|
+
case "right":
|
1236
|
+
tooltipX = this.x - (this.cornerRadius + this.caretHeight);
|
1237
|
+
break;
|
1238
|
+
}
|
1239
|
+
|
1240
|
+
drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius);
|
1241
|
+
|
1242
|
+
ctx.fill();
|
1243
|
+
|
1244
|
+
ctx.fillStyle = this.textColor;
|
1245
|
+
ctx.textAlign = "center";
|
1246
|
+
ctx.textBaseline = "middle";
|
1247
|
+
ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2);
|
1248
|
+
}
|
1249
|
+
});
|
1250
|
+
|
1251
|
+
Chart.MultiTooltip = Chart.Element.extend({
|
1252
|
+
initialize : function(){
|
1253
|
+
this.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
|
1254
|
+
|
1255
|
+
this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily);
|
1256
|
+
|
1257
|
+
this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5;
|
1258
|
+
|
1259
|
+
this.ctx.font = this.titleFont;
|
1260
|
+
|
1261
|
+
var titleWidth = this.ctx.measureText(this.title).width,
|
1262
|
+
//Label has a legend square as well so account for this.
|
1263
|
+
labelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3,
|
1264
|
+
longestTextWidth = max([labelWidth,titleWidth]);
|
1265
|
+
|
1266
|
+
this.width = longestTextWidth + (this.xPadding*2);
|
1267
|
+
|
1268
|
+
|
1269
|
+
var halfHeight = this.height/2;
|
1270
|
+
|
1271
|
+
//Check to ensure the height will fit on the canvas
|
1272
|
+
//The three is to buffer form the very
|
1273
|
+
if (this.y - halfHeight < 0 ){
|
1274
|
+
this.y = halfHeight;
|
1275
|
+
} else if (this.y + halfHeight > this.chart.height){
|
1276
|
+
this.y = this.chart.height - halfHeight;
|
1277
|
+
}
|
1278
|
+
|
1279
|
+
//Decide whether to align left or right based on position on canvas
|
1280
|
+
if (this.x > this.chart.width/2){
|
1281
|
+
this.x -= this.xOffset + this.width;
|
1282
|
+
} else {
|
1283
|
+
this.x += this.xOffset;
|
1284
|
+
}
|
1285
|
+
|
1286
|
+
|
1287
|
+
},
|
1288
|
+
getLineHeight : function(index){
|
1289
|
+
var baseLineHeight = this.y - (this.height/2) + this.yPadding,
|
1290
|
+
afterTitleIndex = index-1;
|
1291
|
+
|
1292
|
+
//If the index is zero, we're getting the title
|
1293
|
+
if (index === 0){
|
1294
|
+
return baseLineHeight + this.titleFontSize/2;
|
1295
|
+
} else{
|
1296
|
+
return baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5;
|
1297
|
+
}
|
1298
|
+
|
1299
|
+
},
|
1300
|
+
draw : function(){
|
1301
|
+
drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius);
|
1302
|
+
var ctx = this.ctx;
|
1303
|
+
ctx.fillStyle = this.fillColor;
|
1304
|
+
ctx.fill();
|
1305
|
+
ctx.closePath();
|
1306
|
+
|
1307
|
+
ctx.textAlign = "left";
|
1308
|
+
ctx.textBaseline = "middle";
|
1309
|
+
ctx.fillStyle = this.titleTextColor;
|
1310
|
+
ctx.font = this.titleFont;
|
1311
|
+
|
1312
|
+
ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0));
|
1313
|
+
|
1314
|
+
ctx.font = this.font;
|
1315
|
+
helpers.each(this.labels,function(label,index){
|
1316
|
+
ctx.fillStyle = this.textColor;
|
1317
|
+
ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));
|
1318
|
+
|
1319
|
+
//A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
|
1320
|
+
//ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
|
1321
|
+
//Instead we'll make a white filled block to put the legendColour palette over.
|
1322
|
+
|
1323
|
+
ctx.fillStyle = this.legendColorBackground;
|
1324
|
+
ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
|
1325
|
+
|
1326
|
+
ctx.fillStyle = this.legendColors[index].fill;
|
1327
|
+
ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
|
1328
|
+
|
1329
|
+
|
1330
|
+
},this);
|
1331
|
+
}
|
1332
|
+
});
|
1333
|
+
|
1334
|
+
Chart.Scale = Chart.Element.extend({
|
1335
|
+
initialize : function(){
|
1336
|
+
this.fit();
|
56
1337
|
},
|
57
|
-
|
58
|
-
|
1338
|
+
buildYLabels : function(){
|
1339
|
+
this.yLabels = [];
|
1340
|
+
|
1341
|
+
var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
|
1342
|
+
|
1343
|
+
for (var i=0; i<=this.steps; i++){
|
1344
|
+
this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
|
1345
|
+
}
|
1346
|
+
this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0;
|
59
1347
|
},
|
60
|
-
|
61
|
-
|
1348
|
+
addXLabel : function(label){
|
1349
|
+
this.xLabels.push(label);
|
1350
|
+
this.valuesCount++;
|
1351
|
+
this.fit();
|
62
1352
|
},
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
return 1/2 * (-Math.pow(2, -10 * --t) + 2);
|
68
|
-
},
|
69
|
-
easeInCirc: function (t) {
|
70
|
-
if (t>=1) return t;
|
71
|
-
return -1 * (Math.sqrt(1 - (t/=1)*t) - 1);
|
72
|
-
},
|
73
|
-
easeOutCirc: function (t) {
|
74
|
-
return 1 * Math.sqrt(1 - (t=t/1-1)*t);
|
75
|
-
},
|
76
|
-
easeInOutCirc: function (t) {
|
77
|
-
if ((t/=1/2) < 1) return -1/2 * (Math.sqrt(1 - t*t) - 1);
|
78
|
-
return 1/2 * (Math.sqrt(1 - (t-=2)*t) + 1);
|
79
|
-
},
|
80
|
-
easeInElastic: function (t) {
|
81
|
-
var s=1.70158;var p=0;var a=1;
|
82
|
-
if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3;
|
83
|
-
if (a < Math.abs(1)) { a=1; var s=p/4; }
|
84
|
-
else var s = p/(2*Math.PI) * Math.asin (1/a);
|
85
|
-
return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p ));
|
86
|
-
},
|
87
|
-
easeOutElastic: function (t) {
|
88
|
-
var s=1.70158;var p=0;var a=1;
|
89
|
-
if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3;
|
90
|
-
if (a < Math.abs(1)) { a=1; var s=p/4; }
|
91
|
-
else var s = p/(2*Math.PI) * Math.asin (1/a);
|
92
|
-
return a*Math.pow(2,-10*t) * Math.sin( (t*1-s)*(2*Math.PI)/p ) + 1;
|
93
|
-
},
|
94
|
-
easeInOutElastic: function (t) {
|
95
|
-
var s=1.70158;var p=0;var a=1;
|
96
|
-
if (t==0) return 0; if ((t/=1/2)==2) return 1; if (!p) p=1*(.3*1.5);
|
97
|
-
if (a < Math.abs(1)) { a=1; var s=p/4; }
|
98
|
-
else var s = p/(2*Math.PI) * Math.asin (1/a);
|
99
|
-
if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p ));
|
100
|
-
return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )*.5 + 1;
|
101
|
-
},
|
102
|
-
easeInBack: function (t) {
|
103
|
-
var s = 1.70158;
|
104
|
-
return 1*(t/=1)*t*((s+1)*t - s);
|
105
|
-
},
|
106
|
-
easeOutBack: function (t) {
|
107
|
-
var s = 1.70158;
|
108
|
-
return 1*((t=t/1-1)*t*((s+1)*t + s) + 1);
|
109
|
-
},
|
110
|
-
easeInOutBack: function (t) {
|
111
|
-
var s = 1.70158;
|
112
|
-
if ((t/=1/2) < 1) return 1/2*(t*t*(((s*=(1.525))+1)*t - s));
|
113
|
-
return 1/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2);
|
114
|
-
},
|
115
|
-
easeInBounce: function (t) {
|
116
|
-
return 1 - animationOptions.easeOutBounce (1-t);
|
117
|
-
},
|
118
|
-
easeOutBounce: function (t) {
|
119
|
-
if ((t/=1) < (1/2.75)) {
|
120
|
-
return 1*(7.5625*t*t);
|
121
|
-
} else if (t < (2/2.75)) {
|
122
|
-
return 1*(7.5625*(t-=(1.5/2.75))*t + .75);
|
123
|
-
} else if (t < (2.5/2.75)) {
|
124
|
-
return 1*(7.5625*(t-=(2.25/2.75))*t + .9375);
|
125
|
-
} else {
|
126
|
-
return 1*(7.5625*(t-=(2.625/2.75))*t + .984375);
|
127
|
-
}
|
1353
|
+
removeXLabel : function(){
|
1354
|
+
this.xLabels.shift();
|
1355
|
+
this.valuesCount--;
|
1356
|
+
this.fit();
|
128
1357
|
},
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
1358
|
+
// Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use
|
1359
|
+
fit: function(){
|
1360
|
+
// First we need the width of the yLabels, assuming the xLabels aren't rotated
|
1361
|
+
|
1362
|
+
// To do that we need the base line at the top and base of the chart, assuming there is no x label rotation
|
1363
|
+
this.startPoint = (this.display) ? this.fontSize : 0;
|
1364
|
+
this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels
|
1365
|
+
|
1366
|
+
// Apply padding settings to the start and end point.
|
1367
|
+
this.startPoint += this.padding;
|
1368
|
+
this.endPoint -= this.padding;
|
1369
|
+
|
1370
|
+
// Cache the starting height, so can determine if we need to recalculate the scale yAxis
|
1371
|
+
var cachedHeight = this.endPoint - this.startPoint,
|
1372
|
+
cachedYLabelWidth;
|
1373
|
+
|
1374
|
+
// Build the current yLabels so we have an idea of what size they'll be to start
|
1375
|
+
/*
|
1376
|
+
* This sets what is returned from calculateScaleRange as static properties of this class:
|
1377
|
+
*
|
1378
|
+
this.steps;
|
1379
|
+
this.stepValue;
|
1380
|
+
this.min;
|
1381
|
+
this.max;
|
1382
|
+
*
|
1383
|
+
*/
|
1384
|
+
this.calculateYRange(cachedHeight);
|
1385
|
+
|
1386
|
+
// With these properties set we can now build the array of yLabels
|
1387
|
+
// and also the width of the largest yLabel
|
1388
|
+
this.buildYLabels();
|
1389
|
+
|
1390
|
+
this.calculateXLabelRotation();
|
1391
|
+
|
1392
|
+
while((cachedHeight > this.endPoint - this.startPoint)){
|
1393
|
+
cachedHeight = this.endPoint - this.startPoint;
|
1394
|
+
cachedYLabelWidth = this.yLabelWidth;
|
1395
|
+
|
1396
|
+
this.calculateYRange(cachedHeight);
|
1397
|
+
this.buildYLabels();
|
1398
|
+
|
1399
|
+
// Only go through the xLabel loop again if the yLabel width has changed
|
1400
|
+
if (cachedYLabelWidth < this.yLabelWidth){
|
1401
|
+
this.calculateXLabelRotation();
|
164
1402
|
}
|
165
1403
|
}
|
1404
|
+
|
166
1405
|
},
|
167
|
-
|
1406
|
+
calculateXLabelRotation : function(){
|
1407
|
+
//Get the width of each grid by calculating the difference
|
1408
|
+
//between x offsets between 0 and 1.
|
168
1409
|
|
169
|
-
|
170
|
-
chart.tooltips.push(new Tooltip(
|
171
|
-
ctx,
|
172
|
-
areaObj,
|
173
|
-
data,
|
174
|
-
type
|
175
|
-
));
|
176
|
-
}
|
1410
|
+
this.ctx.font = this.font;
|
177
1411
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
this.savedState = null;
|
183
|
-
this.highlightState = null;
|
184
|
-
this.x = null;
|
185
|
-
this.y = null;
|
186
|
-
|
187
|
-
this.inRange = function(x,y) {
|
188
|
-
if(this.areaObj.type) {
|
189
|
-
switch(this.areaObj.type) {
|
190
|
-
case 'rect':
|
191
|
-
return (x >= this.areaObj.x && x <= this.areaObj.x+this.areaObj.width) &&
|
192
|
-
(y >= this.areaObj.y && y <= this.areaObj.y+this.areaObj.height);
|
193
|
-
break;
|
194
|
-
case 'circle':
|
195
|
-
return ((Math.pow(x-this.areaObj.x, 2)+Math.pow(y-this.areaObj.y, 2)) < Math.pow(this.areaObj.r,2));
|
196
|
-
break;
|
197
|
-
case 'shape':
|
198
|
-
var poly = this.areaObj.points;
|
199
|
-
for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
|
200
|
-
((poly[i].y <= y && y < poly[j].y) || (poly[j].y <= y && y < poly[i].y))
|
201
|
-
&& (x < (poly[j].x - poly[i].x) * (y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x)
|
202
|
-
&& (c = !c);
|
203
|
-
return c;
|
204
|
-
break;
|
205
|
-
}
|
206
|
-
}
|
207
|
-
}
|
1412
|
+
var firstWidth = this.ctx.measureText(this.xLabels[0]).width,
|
1413
|
+
lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,
|
1414
|
+
firstRotated,
|
1415
|
+
lastRotated;
|
208
1416
|
|
209
|
-
this.render = function(x,y) {
|
210
|
-
if(this.savedState == null) {
|
211
|
-
this.ctx.putImageData(chart.savedState,0,0);
|
212
|
-
this.savedState = this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height);
|
213
|
-
}
|
214
|
-
this.ctx.putImageData(this.savedState,0,0);
|
215
|
-
if(options.tooltips.showHighlight) {
|
216
|
-
if(this.highlightState == null) {
|
217
|
-
this.ctx.strokeStyle = options.tooltips.highlight.stroke.color;
|
218
|
-
this.ctx.lineWidth = options.tooltips.highlight.stroke.width;
|
219
|
-
this.ctx.fillStyle = options.tooltips.highlight.fill;
|
220
|
-
switch(this.areaObj.type) {
|
221
|
-
case 'rect':
|
222
|
-
this.ctx.strokeRect(this.areaObj.x, this.areaObj.y, this.areaObj.width, this.areaObj.height);
|
223
|
-
this.ctx.fillStyle = options.tooltips.highlight.fill;
|
224
|
-
this.ctx.fillRect(this.areaObj.x, this.areaObj.y, this.areaObj.width, this.areaObj.height);
|
225
|
-
break;
|
226
|
-
case 'circle':
|
227
|
-
this.ctx.beginPath();
|
228
|
-
this.ctx.arc(this.areaObj.x, this.areaObj.y, this.areaObj.r, 0, 2*Math.PI, false);
|
229
|
-
this.ctx.stroke();
|
230
|
-
this.ctx.fill();
|
231
|
-
break;
|
232
|
-
case 'shape':
|
233
|
-
this.ctx.beginPath();
|
234
|
-
this.ctx.moveTo(this.areaObj.points[0].x, this.areaObj.points[0].y);
|
235
|
-
for(var p in this.areaObj.points) {
|
236
|
-
this.ctx.lineTo(this.areaObj.points[p].x, this.areaObj.points[p].y);
|
237
|
-
}
|
238
|
-
this.ctx.stroke();
|
239
|
-
this.ctx.fill();
|
240
|
-
break;
|
241
|
-
}
|
242
|
-
this.highlightState = this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height);
|
243
|
-
} else {
|
244
|
-
this.ctx.putImageData(this.highlightState,0,0);
|
245
|
-
}
|
246
|
-
}
|
247
|
-
//if(this.x != x || this.y != y) {
|
248
|
-
var posX = x+options.tooltips.offset.left,
|
249
|
-
posY = y+options.tooltips.offset.top,
|
250
|
-
tpl = tmpl(options.tooltips.labelTemplate, this.data),
|
251
|
-
rectWidth = options.tooltips.padding.left+this.ctx.measureText(tpl).width+options.tooltips.padding.right;
|
252
|
-
if(posX + rectWidth > this.ctx.canvas.width) {
|
253
|
-
posX -= posX-rectWidth < 0 ? posX : rectWidth;
|
254
|
-
}
|
255
|
-
if(posY + 24 > this.ctx.canvas.height) {
|
256
|
-
posY -= 24;
|
257
|
-
}
|
258
|
-
this.ctx.fillStyle = options.tooltips.background;
|
259
|
-
this.ctx.fillRect(posX, posY, rectWidth, 24);
|
260
|
-
if(options.tooltips.border.width > 0) {
|
261
|
-
this.ctx.fillStyle = options.tooltips.order.color;
|
262
|
-
this.ctx.lineWidth = options.tooltips.border.width;
|
263
|
-
this.ctx.strokeRect(posX, posY, rectWidth, 24);
|
264
|
-
}
|
265
|
-
this.ctx.font = options.tooltips.fontStyle+ " "+options.tooltips.fontSize+" " + options.tooltips.fontFamily;
|
266
|
-
this.ctx.fillStyle = options.tooltips.fontColor;
|
267
|
-
this.ctx.textAlign = 'center';
|
268
|
-
this.ctx.textBaseline = 'middle';
|
269
|
-
this.ctx.fillText(tpl, posX+rectWidth/2, posY+12);
|
270
|
-
this.x = x;
|
271
|
-
this.y = y;
|
272
|
-
//}
|
273
|
-
}
|
274
|
-
}
|
275
1417
|
|
276
|
-
|
277
|
-
|
278
|
-
height = context.canvas.height;
|
1418
|
+
this.xScalePaddingRight = lastWidth/2 + 3;
|
1419
|
+
this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10;
|
279
1420
|
|
280
|
-
|
1421
|
+
this.xLabelRotation = 0;
|
1422
|
+
if (this.display){
|
1423
|
+
var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels),
|
1424
|
+
cosRotation,
|
1425
|
+
firstRotatedWidth;
|
1426
|
+
this.xLabelWidth = originalLabelWidth;
|
1427
|
+
//Allow 3 pixels x2 padding either side for label readability
|
1428
|
+
var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;
|
281
1429
|
|
282
|
-
|
283
|
-
|
284
|
-
|
1430
|
+
//Max label rotate should be 90 - also act as a loop counter
|
1431
|
+
while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){
|
1432
|
+
cosRotation = Math.cos(toRadians(this.xLabelRotation));
|
1433
|
+
|
1434
|
+
firstRotated = cosRotation * firstWidth;
|
1435
|
+
lastRotated = cosRotation * lastWidth;
|
1436
|
+
|
1437
|
+
// We're right aligning the text now.
|
1438
|
+
if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){
|
1439
|
+
this.xScalePaddingLeft = firstRotated + this.fontSize / 2;
|
1440
|
+
}
|
1441
|
+
this.xScalePaddingRight = this.fontSize/2;
|
285
1442
|
|
286
|
-
while(e) {
|
287
|
-
xPosition += (e.offsetLeft - e.scrollLeft + e.clientLeft);
|
288
|
-
yPosition += (e.offsetTop - e.scrollTop + e.clientTop);
|
289
|
-
e = e.offsetParent;
|
290
|
-
}
|
291
|
-
return { x: xPosition, y: yPosition };
|
292
|
-
}
|
293
1443
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
mx = (e.clientX)-position.x,
|
301
|
-
my = (e.clientY)-position.y;
|
302
|
-
if(chart.tooltips[i].inRange(mx,my)) {
|
303
|
-
chart.tooltips[i].render(mx,my);
|
304
|
-
rendered++;
|
1444
|
+
this.xLabelRotation++;
|
1445
|
+
this.xLabelWidth = cosRotation * originalLabelWidth;
|
1446
|
+
|
1447
|
+
}
|
1448
|
+
if (this.xLabelRotation > 0){
|
1449
|
+
this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3;
|
305
1450
|
}
|
306
1451
|
}
|
307
|
-
|
308
|
-
|
1452
|
+
else{
|
1453
|
+
this.xLabelWidth = 0;
|
1454
|
+
this.xScalePaddingRight = this.padding;
|
1455
|
+
this.xScalePaddingLeft = this.padding;
|
309
1456
|
}
|
310
|
-
}
|
311
|
-
}
|
312
1457
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
}
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
1458
|
+
},
|
1459
|
+
// Needs to be overidden in each Chart type
|
1460
|
+
// Otherwise we need to pass all the data into the scale class
|
1461
|
+
calculateYRange: noop,
|
1462
|
+
drawingArea: function(){
|
1463
|
+
return this.startPoint - this.endPoint;
|
1464
|
+
},
|
1465
|
+
calculateY : function(value){
|
1466
|
+
var scalingFactor = this.drawingArea() / (this.min - this.max);
|
1467
|
+
return this.endPoint - (scalingFactor * (value - this.min));
|
1468
|
+
},
|
1469
|
+
calculateX : function(index){
|
1470
|
+
var isRotated = (this.xLabelRotation > 0),
|
1471
|
+
// innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
|
1472
|
+
innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
|
1473
|
+
valueWidth = innerWidth/(this.valuesCount - ((this.offsetGridLines) ? 0 : 1)),
|
1474
|
+
valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
|
1475
|
+
|
1476
|
+
if (this.offsetGridLines){
|
1477
|
+
valueOffset += (valueWidth/2);
|
1478
|
+
}
|
334
1479
|
|
1480
|
+
return Math.round(valueOffset);
|
1481
|
+
},
|
1482
|
+
update : function(newProps){
|
1483
|
+
helpers.extend(this, newProps);
|
1484
|
+
this.fit();
|
1485
|
+
},
|
1486
|
+
draw : function(){
|
1487
|
+
var ctx = this.ctx,
|
1488
|
+
yLabelGap = (this.endPoint - this.startPoint) / this.steps,
|
1489
|
+
xStart = Math.round(this.xScalePaddingLeft);
|
1490
|
+
if (this.display){
|
1491
|
+
ctx.fillStyle = this.textColor;
|
1492
|
+
ctx.font = this.font;
|
1493
|
+
each(this.yLabels,function(labelString,index){
|
1494
|
+
var yLabelCenter = this.endPoint - (yLabelGap * index),
|
1495
|
+
linePositionY = Math.round(yLabelCenter);
|
335
1496
|
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
1497
|
+
ctx.textAlign = "right";
|
1498
|
+
ctx.textBaseline = "middle";
|
1499
|
+
if (this.showLabels){
|
1500
|
+
ctx.fillText(labelString,xStart - 10,yLabelCenter);
|
1501
|
+
}
|
1502
|
+
ctx.beginPath();
|
1503
|
+
if (index > 0){
|
1504
|
+
// This is a grid line in the centre, so drop that
|
1505
|
+
ctx.lineWidth = this.gridLineWidth;
|
1506
|
+
ctx.strokeStyle = this.gridLineColor;
|
1507
|
+
} else {
|
1508
|
+
// This is the first line on the scale
|
1509
|
+
ctx.lineWidth = this.lineWidth;
|
1510
|
+
ctx.strokeStyle = this.lineColor;
|
1511
|
+
}
|
344
1512
|
|
345
|
-
|
1513
|
+
linePositionY += helpers.aliasPixel(ctx.lineWidth);
|
346
1514
|
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
scaleStepWidth : null,
|
352
|
-
scaleStartValue : null,
|
353
|
-
scaleShowLine : true,
|
354
|
-
scaleLineColor : "rgba(0,0,0,0.1)",
|
355
|
-
scaleLineWidth : 1,
|
356
|
-
scaleShowLabels : true,
|
357
|
-
scaleLabel : "<%=value%>",
|
358
|
-
scaleFontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
|
359
|
-
scaleFontSize : 12,
|
360
|
-
scaleFontStyle : "normal",
|
361
|
-
scaleFontColor : "rgba(71,74,84,1)",
|
362
|
-
scaleShowLabelBackdrop : true,
|
363
|
-
scaleBackdropColor : "rgba(255,255,255,0.75)",
|
364
|
-
scaleBackdropPaddingY : 2,
|
365
|
-
scaleBackdropPaddingX : 2,
|
366
|
-
segmentShowStroke : true,
|
367
|
-
segmentStrokeColor : "#fff",
|
368
|
-
segmentStrokeWidth : 1,
|
369
|
-
animation : true,
|
370
|
-
animationSteps : 100,
|
371
|
-
animationEasing : "easeOutBounce",
|
372
|
-
animateRotate : true,
|
373
|
-
animateScale : false,
|
374
|
-
onAnimationComplete : null,
|
375
|
-
showTooltips : true
|
376
|
-
};
|
1515
|
+
ctx.moveTo(xStart, linePositionY);
|
1516
|
+
ctx.lineTo(this.width, linePositionY);
|
1517
|
+
ctx.stroke();
|
1518
|
+
ctx.closePath();
|
377
1519
|
|
378
|
-
|
1520
|
+
ctx.lineWidth = this.lineWidth;
|
1521
|
+
ctx.strokeStyle = this.lineColor;
|
1522
|
+
ctx.beginPath();
|
1523
|
+
ctx.moveTo(xStart - 5, linePositionY);
|
1524
|
+
ctx.lineTo(xStart, linePositionY);
|
1525
|
+
ctx.stroke();
|
1526
|
+
ctx.closePath();
|
379
1527
|
|
380
|
-
|
381
|
-
};
|
1528
|
+
},this);
|
382
1529
|
|
383
|
-
|
1530
|
+
each(this.xLabels,function(label,index){
|
1531
|
+
var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
|
1532
|
+
// Check to see if line/bar here and decide where to place the line
|
1533
|
+
linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
|
1534
|
+
isRotated = (this.xLabelRotation > 0);
|
384
1535
|
|
385
|
-
|
386
|
-
scaleOverlay : false,
|
387
|
-
scaleOverride : false,
|
388
|
-
scaleSteps : null,
|
389
|
-
scaleStepWidth : null,
|
390
|
-
scaleStartValue : null,
|
391
|
-
scaleShowLine : true,
|
392
|
-
scaleLineColor : "rgba(0,0,0,0.1)",
|
393
|
-
scaleLineWidth : 1,
|
394
|
-
scaleShowLabels : false,
|
395
|
-
scaleLabel : "<%=value%>",
|
396
|
-
scaleFontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
|
397
|
-
scaleFontSize : 12,
|
398
|
-
scaleFontStyle : "normal",
|
399
|
-
scaleFontColor : "rgba(71,74,84,1)",
|
400
|
-
scaleShowLabelBackdrop : true,
|
401
|
-
scaleBackdropColor : "rgba(255,255,255,0.75)",
|
402
|
-
scaleBackdropPaddingY : 2,
|
403
|
-
scaleBackdropPaddingX : 2,
|
404
|
-
angleShowLineOut : true,
|
405
|
-
angleLineColor : "rgba(0,0,0,0.1)",
|
406
|
-
angleLineWidth : 1,
|
407
|
-
pointLabelFontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
|
408
|
-
pointLabelFontStyle : "normal",
|
409
|
-
pointLabelFontSize : 12,
|
410
|
-
pointLabelFontColor : "rgba(71,74,84,1)",
|
411
|
-
pointDot : true,
|
412
|
-
pointDotRadius : 3,
|
413
|
-
pointDotStrokeWidth : 1,
|
414
|
-
datasetStroke : true,
|
415
|
-
datasetStrokeWidth : 1,
|
416
|
-
datasetFill : true,
|
417
|
-
animation : true,
|
418
|
-
animationSteps : 60,
|
419
|
-
animationEasing : "easeOutQuart",
|
420
|
-
onAnimationComplete : null,
|
421
|
-
showTooltips : true
|
422
|
-
};
|
1536
|
+
ctx.beginPath();
|
423
1537
|
|
424
|
-
|
1538
|
+
if (index > 0){
|
1539
|
+
// This is a grid line in the centre, so drop that
|
1540
|
+
ctx.lineWidth = this.gridLineWidth;
|
1541
|
+
ctx.strokeStyle = this.gridLineColor;
|
1542
|
+
} else {
|
1543
|
+
// This is the first line on the scale
|
1544
|
+
ctx.lineWidth = this.lineWidth;
|
1545
|
+
ctx.strokeStyle = this.lineColor;
|
1546
|
+
}
|
1547
|
+
ctx.moveTo(linePos,this.endPoint);
|
1548
|
+
ctx.lineTo(linePos,this.startPoint - 3);
|
1549
|
+
ctx.stroke();
|
1550
|
+
ctx.closePath();
|
425
1551
|
|
426
|
-
return new Radar(data,config,context);
|
427
|
-
};
|
428
1552
|
|
429
|
-
|
430
|
-
|
431
|
-
segmentShowStroke : true,
|
432
|
-
segmentStrokeColor : "#fff",
|
433
|
-
segmentStrokeWidth : 1,
|
434
|
-
animation : true,
|
435
|
-
animationSteps : 100,
|
436
|
-
animationEasing : "easeOutBounce",
|
437
|
-
animateRotate : true,
|
438
|
-
animateScale : false,
|
439
|
-
onAnimationComplete : null,
|
440
|
-
labelFontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
|
441
|
-
labelFontStyle : "normal",
|
442
|
-
labelFontSize : 12,
|
443
|
-
labelFontColor : "rgba(71,74,84,1)",
|
444
|
-
labelAlign : 'right',
|
445
|
-
showTooltips : true
|
446
|
-
};
|
1553
|
+
ctx.lineWidth = this.lineWidth;
|
1554
|
+
ctx.strokeStyle = this.lineColor;
|
447
1555
|
|
448
|
-
var config = (options)? mergeChartConfig(chart.Pie.defaults,options) : chart.Pie.defaults;
|
449
1556
|
|
450
|
-
|
451
|
-
|
1557
|
+
// Small lines at the bottom of the base grid line
|
1558
|
+
ctx.beginPath();
|
1559
|
+
ctx.moveTo(linePos,this.endPoint);
|
1560
|
+
ctx.lineTo(linePos,this.endPoint + 5);
|
1561
|
+
ctx.stroke();
|
1562
|
+
ctx.closePath();
|
452
1563
|
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
animationEasing : "easeOutBounce",
|
463
|
-
animateRotate : true,
|
464
|
-
animateScale : false,
|
465
|
-
onAnimationComplete : null,
|
466
|
-
showTooltips : true
|
467
|
-
};
|
1564
|
+
ctx.save();
|
1565
|
+
ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8);
|
1566
|
+
ctx.rotate(toRadians(this.xLabelRotation)*-1);
|
1567
|
+
ctx.font = this.font;
|
1568
|
+
ctx.textAlign = (isRotated) ? "right" : "center";
|
1569
|
+
ctx.textBaseline = (isRotated) ? "middle" : "top";
|
1570
|
+
ctx.fillText(label, 0, 0);
|
1571
|
+
ctx.restore();
|
1572
|
+
},this);
|
468
1573
|
|
469
|
-
|
1574
|
+
}
|
1575
|
+
}
|
470
1576
|
|
471
|
-
|
1577
|
+
});
|
472
1578
|
|
473
|
-
|
1579
|
+
Chart.RadialScale = Chart.Element.extend({
|
1580
|
+
initialize: function(){
|
1581
|
+
this.size = min([this.height, this.width]);
|
1582
|
+
this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
|
1583
|
+
},
|
1584
|
+
calculateCenterOffset: function(value){
|
1585
|
+
// Take into account half font size + the yPadding of the top value
|
1586
|
+
var scalingFactor = this.drawingArea / (this.max - this.min);
|
474
1587
|
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
scaleFontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
|
488
|
-
scaleFontSize : 12,
|
489
|
-
scaleFontStyle : "normal",
|
490
|
-
scaleFontColor : "rgba(71,74,84,1)",
|
491
|
-
scaleShowGridLines : true,
|
492
|
-
scaleGridLineColor : "rgba(0,0,0,0.05)",
|
493
|
-
scaleGridLineWidth : 1,
|
494
|
-
bezierCurve : true,
|
495
|
-
pointDot : true,
|
496
|
-
pointDotRadius : 4,
|
497
|
-
pointDotStrokeWidth : 1,
|
498
|
-
datasetStroke : true,
|
499
|
-
datasetStrokeWidth : 1,
|
500
|
-
datasetFill : true,
|
501
|
-
animation : true,
|
502
|
-
animationSteps : 60,
|
503
|
-
animationEasing : "easeOutQuart",
|
504
|
-
onAnimationComplete : null,
|
505
|
-
showTooltips : true
|
506
|
-
};
|
507
|
-
var config = (options) ? mergeChartConfig(chart.Line.defaults,options) : chart.Line.defaults;
|
1588
|
+
return (value - this.min) * scalingFactor;
|
1589
|
+
},
|
1590
|
+
update : function(){
|
1591
|
+
if (!this.lineArc){
|
1592
|
+
this.setScaleSize();
|
1593
|
+
} else {
|
1594
|
+
this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
|
1595
|
+
}
|
1596
|
+
this.buildYLabels();
|
1597
|
+
},
|
1598
|
+
buildYLabels: function(){
|
1599
|
+
this.yLabels = [];
|
508
1600
|
|
509
|
-
|
510
|
-
}
|
1601
|
+
var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
|
511
1602
|
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
1603
|
+
for (var i=0; i<=this.steps; i++){
|
1604
|
+
this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
|
1605
|
+
}
|
1606
|
+
},
|
1607
|
+
getCircumference : function(){
|
1608
|
+
return ((Math.PI*2) / this.valuesCount);
|
1609
|
+
},
|
1610
|
+
setScaleSize: function(){
|
1611
|
+
/*
|
1612
|
+
* Right, this is really confusing and there is a lot of maths going on here
|
1613
|
+
* The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
|
1614
|
+
*
|
1615
|
+
* Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
|
1616
|
+
*
|
1617
|
+
* Solution:
|
1618
|
+
*
|
1619
|
+
* We assume the radius of the polygon is half the size of the canvas at first
|
1620
|
+
* at each index we check if the text overlaps.
|
1621
|
+
*
|
1622
|
+
* Where it does, we store that angle and that index.
|
1623
|
+
*
|
1624
|
+
* After finding the largest index and angle we calculate how much we need to remove
|
1625
|
+
* from the shape radius to move the point inwards by that x.
|
1626
|
+
*
|
1627
|
+
* We average the left and right distances to get the maximum shape radius that can fit in the box
|
1628
|
+
* along with labels.
|
1629
|
+
*
|
1630
|
+
* Once we have that, we can find the centre point for the chart, by taking the x text protrusion
|
1631
|
+
* on each side, removing that from the size, halving it and adding the left x protrusion width.
|
1632
|
+
*
|
1633
|
+
* This will mean we have a shape fitted to the canvas, as large as it can be with the labels
|
1634
|
+
* and position it in the most space efficient manner
|
1635
|
+
*
|
1636
|
+
* https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
|
1637
|
+
*/
|
1638
|
+
|
1639
|
+
|
1640
|
+
// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
|
1641
|
+
// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
|
1642
|
+
var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]),
|
1643
|
+
pointPosition,
|
1644
|
+
i,
|
1645
|
+
textWidth,
|
1646
|
+
halfTextWidth,
|
1647
|
+
furthestRight = this.width,
|
1648
|
+
furthestRightIndex,
|
1649
|
+
furthestRightAngle,
|
1650
|
+
furthestLeft = 0,
|
1651
|
+
furthestLeftIndex,
|
1652
|
+
furthestLeftAngle,
|
1653
|
+
xProtrusionLeft,
|
1654
|
+
xProtrusionRight,
|
1655
|
+
radiusReductionRight,
|
1656
|
+
radiusReductionLeft,
|
1657
|
+
maxWidthRadius;
|
1658
|
+
this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
|
1659
|
+
for (i=0;i<this.valuesCount;i++){
|
1660
|
+
// 5px to space the text slightly out - similar to what we do in the draw function.
|
1661
|
+
pointPosition = this.getPointPosition(i, largestPossibleRadius);
|
1662
|
+
textWidth = this.ctx.measureText(template(this.templateString, { value: this.labels[i] })).width + 5;
|
1663
|
+
if (i === 0 || i === this.valuesCount/2){
|
1664
|
+
// If we're at index zero, or exactly the middle, we're at exactly the top/bottom
|
1665
|
+
// of the radar chart, so text will be aligned centrally, so we'll half it and compare
|
1666
|
+
// w/left and right text sizes
|
1667
|
+
halfTextWidth = textWidth/2;
|
1668
|
+
if (pointPosition.x + halfTextWidth > furthestRight) {
|
1669
|
+
furthestRight = pointPosition.x + halfTextWidth;
|
1670
|
+
furthestRightIndex = i;
|
1671
|
+
}
|
1672
|
+
if (pointPosition.x - halfTextWidth < furthestLeft) {
|
1673
|
+
furthestLeft = pointPosition.x - halfTextWidth;
|
1674
|
+
furthestLeftIndex = i;
|
1675
|
+
}
|
1676
|
+
}
|
1677
|
+
else if (i < this.valuesCount/2) {
|
1678
|
+
// Less than half the values means we'll left align the text
|
1679
|
+
if (pointPosition.x + textWidth > furthestRight) {
|
1680
|
+
furthestRight = pointPosition.x + textWidth;
|
1681
|
+
furthestRightIndex = i;
|
1682
|
+
}
|
1683
|
+
}
|
1684
|
+
else if (i > this.valuesCount/2){
|
1685
|
+
// More than half the values means we'll right align the text
|
1686
|
+
if (pointPosition.x - textWidth < furthestLeft) {
|
1687
|
+
furthestLeft = pointPosition.x - textWidth;
|
1688
|
+
furthestLeftIndex = i;
|
1689
|
+
}
|
1690
|
+
}
|
1691
|
+
}
|
541
1692
|
|
542
|
-
|
543
|
-
}
|
1693
|
+
xProtrusionLeft = furthestLeft;
|
544
1694
|
|
545
|
-
|
546
|
-
c.clearRect(0, 0, width, height);
|
547
|
-
};
|
1695
|
+
xProtrusionRight = Math.ceil(furthestRight - this.width);
|
548
1696
|
|
549
|
-
|
550
|
-
var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString;
|
1697
|
+
furthestRightAngle = this.getIndexAngle(furthestRightIndex);
|
551
1698
|
|
1699
|
+
furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
|
552
1700
|
|
553
|
-
|
1701
|
+
radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2);
|
554
1702
|
|
555
|
-
|
1703
|
+
radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2);
|
556
1704
|
|
557
|
-
|
1705
|
+
// Ensure we actually need to reduce the size of the chart
|
1706
|
+
radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
|
1707
|
+
radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
|
558
1708
|
|
559
|
-
|
560
|
-
if (!config.scaleOverride){
|
1709
|
+
this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2;
|
561
1710
|
|
562
|
-
|
563
|
-
|
564
|
-
else {
|
565
|
-
calculatedScale = {
|
566
|
-
steps : config.scaleSteps,
|
567
|
-
stepValue : config.scaleStepWidth,
|
568
|
-
graphMin : config.scaleStartValue,
|
569
|
-
labels : []
|
570
|
-
}
|
571
|
-
populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
|
572
|
-
}
|
1711
|
+
//this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
|
1712
|
+
this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
|
573
1713
|
|
574
|
-
|
1714
|
+
},
|
1715
|
+
setCenterPoint: function(leftMovement, rightMovement){
|
575
1716
|
|
576
|
-
|
577
|
-
|
1717
|
+
var maxRight = this.width - rightMovement - this.drawingArea,
|
1718
|
+
maxLeft = leftMovement + this.drawingArea;
|
578
1719
|
|
579
|
-
|
580
|
-
|
581
|
-
|
1720
|
+
this.xCenter = (maxLeft + maxRight)/2;
|
1721
|
+
// Always vertically in the centre as the text height doesn't change
|
1722
|
+
this.yCenter = (this.height/2);
|
1723
|
+
},
|
582
1724
|
|
583
|
-
|
1725
|
+
getIndexAngle : function(index){
|
1726
|
+
var angleMultiplier = (Math.PI * 2) / this.valuesCount;
|
1727
|
+
// Start from the top instead of right, so remove a quarter of the circle
|
584
1728
|
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
1729
|
+
return index * angleMultiplier - (Math.PI/2);
|
1730
|
+
},
|
1731
|
+
getPointPosition : function(index, distanceFromCenter){
|
1732
|
+
var thisAngle = this.getIndexAngle(index);
|
1733
|
+
return {
|
1734
|
+
x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
|
1735
|
+
y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
|
1736
|
+
};
|
1737
|
+
},
|
1738
|
+
draw: function(){
|
1739
|
+
if (this.display){
|
1740
|
+
var ctx = this.ctx;
|
1741
|
+
each(this.yLabels, function(label, index){
|
1742
|
+
// Don't draw a centre value
|
1743
|
+
if (index > 0){
|
1744
|
+
var yCenterOffset = index * (this.drawingArea/this.steps),
|
1745
|
+
yHeight = this.yCenter - yCenterOffset,
|
1746
|
+
pointPosition;
|
1747
|
+
|
1748
|
+
// Draw circular lines around the scale
|
1749
|
+
if (this.lineWidth > 0){
|
1750
|
+
ctx.strokeStyle = this.lineColor;
|
1751
|
+
ctx.lineWidth = this.lineWidth;
|
1752
|
+
|
1753
|
+
if(this.lineArc){
|
1754
|
+
ctx.beginPath();
|
1755
|
+
ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2);
|
1756
|
+
ctx.closePath();
|
1757
|
+
ctx.stroke();
|
1758
|
+
} else{
|
1759
|
+
ctx.beginPath();
|
1760
|
+
for (var i=0;i<this.valuesCount;i++)
|
1761
|
+
{
|
1762
|
+
pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue)));
|
1763
|
+
if (i === 0){
|
1764
|
+
ctx.moveTo(pointPosition.x, pointPosition.y);
|
1765
|
+
} else {
|
1766
|
+
ctx.lineTo(pointPosition.x, pointPosition.y);
|
1767
|
+
}
|
1768
|
+
}
|
1769
|
+
ctx.closePath();
|
1770
|
+
ctx.stroke();
|
1771
|
+
}
|
1772
|
+
}
|
1773
|
+
if(this.showLabels){
|
1774
|
+
ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
|
1775
|
+
if (this.showLabelBackdrop){
|
1776
|
+
var labelWidth = ctx.measureText(label).width;
|
1777
|
+
ctx.fillStyle = this.backdropColor;
|
1778
|
+
ctx.fillRect(
|
1779
|
+
this.xCenter - labelWidth/2 - this.backdropPaddingX,
|
1780
|
+
yHeight - this.fontSize/2 - this.backdropPaddingY,
|
1781
|
+
labelWidth + this.backdropPaddingX*2,
|
1782
|
+
this.fontSize + this.backdropPaddingY*2
|
1783
|
+
);
|
1784
|
+
}
|
1785
|
+
ctx.textAlign = 'center';
|
1786
|
+
ctx.textBaseline = "middle";
|
1787
|
+
ctx.fillStyle = this.fontColor;
|
1788
|
+
ctx.fillText(label, this.xCenter, yHeight);
|
1789
|
+
}
|
1790
|
+
}
|
1791
|
+
}, this);
|
1792
|
+
|
1793
|
+
if (!this.lineArc){
|
1794
|
+
ctx.lineWidth = this.angleLineWidth;
|
1795
|
+
ctx.strokeStyle = this.angleLineColor;
|
1796
|
+
for (var i = this.valuesCount - 1; i >= 0; i--) {
|
1797
|
+
if (this.angleLineWidth > 0){
|
1798
|
+
var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max));
|
1799
|
+
ctx.beginPath();
|
1800
|
+
ctx.moveTo(this.xCenter, this.yCenter);
|
1801
|
+
ctx.lineTo(outerPosition.x, outerPosition.y);
|
1802
|
+
ctx.stroke();
|
1803
|
+
ctx.closePath();
|
1804
|
+
}
|
1805
|
+
// Extra 3px out for some label spacing
|
1806
|
+
var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
|
1807
|
+
ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
|
1808
|
+
ctx.fillStyle = this.pointLabelFontColor;
|
1809
|
+
|
1810
|
+
var labelsCount = this.labels.length,
|
1811
|
+
halfLabelsCount = this.labels.length/2,
|
1812
|
+
quarterLabelsCount = halfLabelsCount/2,
|
1813
|
+
upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
|
1814
|
+
exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
|
1815
|
+
if (i === 0){
|
1816
|
+
ctx.textAlign = 'center';
|
1817
|
+
} else if(i === halfLabelsCount){
|
1818
|
+
ctx.textAlign = 'center';
|
1819
|
+
} else if (i < halfLabelsCount){
|
1820
|
+
ctx.textAlign = 'left';
|
1821
|
+
} else {
|
1822
|
+
ctx.textAlign = 'right';
|
1823
|
+
}
|
591
1824
|
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
ctx.beginPath();
|
601
|
-
ctx.arc(width/2, height/2, scaleHop * (i + 1), 0, (Math.PI * 2), true);
|
602
|
-
ctx.strokeStyle = config.scaleLineColor;
|
603
|
-
ctx.lineWidth = config.scaleLineWidth;
|
604
|
-
ctx.stroke();
|
605
|
-
}
|
1825
|
+
// Set the correct text baseline based on outer positioning
|
1826
|
+
if (exactQuarter){
|
1827
|
+
ctx.textBaseline = 'middle';
|
1828
|
+
} else if (upperHalf){
|
1829
|
+
ctx.textBaseline = 'bottom';
|
1830
|
+
} else {
|
1831
|
+
ctx.textBaseline = 'top';
|
1832
|
+
}
|
606
1833
|
|
607
|
-
|
608
|
-
ctx.textAlign = "center";
|
609
|
-
ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily;
|
610
|
-
var label = calculatedScale.labels[i];
|
611
|
-
//If the backdrop object is within the font object
|
612
|
-
if (config.scaleShowLabelBackdrop){
|
613
|
-
var textWidth = ctx.measureText(label).width;
|
614
|
-
ctx.fillStyle = config.scaleBackdropColor;
|
615
|
-
ctx.beginPath();
|
616
|
-
ctx.rect(
|
617
|
-
Math.round(width/2 - textWidth/2 - config.scaleBackdropPaddingX), //X
|
618
|
-
Math.round(height/2 - (scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y
|
619
|
-
Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width
|
620
|
-
Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height
|
621
|
-
);
|
622
|
-
ctx.fill();
|
1834
|
+
ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
|
623
1835
|
}
|
624
|
-
ctx.textBaseline = "middle";
|
625
|
-
ctx.fillStyle = config.scaleFontColor;
|
626
|
-
ctx.fillText(label,width/2,height/2 - (scaleHop * (i + 1)));
|
627
1836
|
}
|
628
1837
|
}
|
629
1838
|
}
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
1839
|
+
});
|
1840
|
+
|
1841
|
+
// Attach global event to resize each chart instance when the browser resizes
|
1842
|
+
helpers.addEvent(window, "resize", (function(){
|
1843
|
+
// Basic debounce of resize function so it doesn't hurt performance when resizing browser.
|
1844
|
+
var timeout;
|
1845
|
+
return function(){
|
1846
|
+
clearTimeout(timeout);
|
1847
|
+
timeout = setTimeout(function(){
|
1848
|
+
each(Chart.instances,function(instance){
|
1849
|
+
// If the responsive flag is set in the chart instance config
|
1850
|
+
// Cascade the resize event down to the chart.
|
1851
|
+
if (instance.options.responsive){
|
1852
|
+
instance.resize(instance.render, true);
|
1853
|
+
}
|
1854
|
+
});
|
1855
|
+
}, 50);
|
1856
|
+
};
|
1857
|
+
})());
|
643
1858
|
|
644
|
-
for (var i=0; i<data.length; i++){
|
645
1859
|
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
ctx.fill();
|
1860
|
+
if (amd) {
|
1861
|
+
define(function(){
|
1862
|
+
return Chart;
|
1863
|
+
});
|
1864
|
+
}
|
652
1865
|
|
653
|
-
|
654
|
-
var points = [{x:width/2,y:height/2}],
|
655
|
-
pAmount = 50,
|
656
|
-
radius = calculateOffset(data[i].value,calculatedScale,scaleHop);
|
657
|
-
points.push({x:width/2+radius*Math.cos(startAngle),y:height/2+radius*Math.sin(startAngle)});
|
658
|
-
for(var p = 0; p <= pAmount; p++) {
|
659
|
-
points.push({x:width/2+radius*Math.cos(startAngle+p/pAmount*rotateAnimation*angleStep),y:height/2+radius*Math.sin(startAngle+p/pAmount*rotateAnimation*angleStep)});
|
660
|
-
}
|
661
|
-
registerTooltip(ctx,{type:'shape',points:points},{label:data[i].label,value:data[i].value},'PolarArea');
|
662
|
-
}
|
1866
|
+
root.Chart = Chart;
|
663
1867
|
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
}
|
669
|
-
startAngle += rotateAnimation*angleStep;
|
670
|
-
}
|
671
|
-
}
|
672
|
-
function getValueBounds() {
|
673
|
-
var upperValue = Number.MIN_VALUE;
|
674
|
-
var lowerValue = Number.MAX_VALUE;
|
675
|
-
for (var i=0; i<data.length; i++){
|
676
|
-
if (data[i].value > upperValue) {upperValue = data[i].value;}
|
677
|
-
if (data[i].value < lowerValue) {lowerValue = data[i].value;}
|
678
|
-
};
|
1868
|
+
Chart.noConflict = function(){
|
1869
|
+
root.Chart = previous;
|
1870
|
+
return Chart;
|
1871
|
+
};
|
679
1872
|
|
680
|
-
|
681
|
-
|
1873
|
+
}).call(this);
|
1874
|
+
(function(){
|
1875
|
+
"use strict";
|
682
1876
|
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
maxSteps : maxSteps,
|
687
|
-
minSteps : minSteps
|
688
|
-
};
|
1877
|
+
var root = this,
|
1878
|
+
Chart = root.Chart,
|
1879
|
+
helpers = Chart.helpers;
|
689
1880
|
|
690
1881
|
|
691
|
-
|
692
|
-
|
1882
|
+
var defaultConfig = {
|
1883
|
+
//Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
|
1884
|
+
scaleBeginAtZero : true,
|
693
1885
|
|
694
|
-
|
695
|
-
|
1886
|
+
//Boolean - Whether grid lines are shown across the chart
|
1887
|
+
scaleShowGridLines : true,
|
696
1888
|
|
697
|
-
//
|
698
|
-
|
1889
|
+
//String - Colour of the grid lines
|
1890
|
+
scaleGridLineColor : "rgba(0,0,0,.05)",
|
699
1891
|
|
700
|
-
|
1892
|
+
//Number - Width of the grid lines
|
1893
|
+
scaleGridLineWidth : 1,
|
701
1894
|
|
702
|
-
|
1895
|
+
//Boolean - If there is a stroke on each bar
|
1896
|
+
barShowStroke : true,
|
703
1897
|
|
704
|
-
|
1898
|
+
//Number - Pixel width of the bar stroke
|
1899
|
+
barStrokeWidth : 1,
|
705
1900
|
|
706
|
-
//
|
707
|
-
|
1901
|
+
//Number - Spacing between each of the X value sets
|
1902
|
+
barValueSpacing : 5,
|
708
1903
|
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
stepValue : config.scaleStepWidth,
|
715
|
-
graphMin : config.scaleStartValue,
|
716
|
-
labels : []
|
717
|
-
}
|
718
|
-
populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
|
719
|
-
}
|
1904
|
+
//Number - Spacing between data sets within X values
|
1905
|
+
barDatasetSpacing : 1,
|
1906
|
+
|
1907
|
+
//String - A legend template
|
1908
|
+
legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].fillColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
|
720
1909
|
|
721
|
-
|
1910
|
+
};
|
722
1911
|
|
723
|
-
animationLoop(config,drawScale,drawAllDataPoints,ctx);
|
724
1912
|
|
725
|
-
|
726
|
-
|
727
|
-
|
1913
|
+
Chart.Type.extend({
|
1914
|
+
name: "Bar",
|
1915
|
+
defaults : defaultConfig,
|
1916
|
+
initialize: function(data){
|
728
1917
|
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
1918
|
+
//Expose options as a scope variable here so we can access it in the ScaleClass
|
1919
|
+
var options = this.options;
|
1920
|
+
|
1921
|
+
this.ScaleClass = Chart.Scale.extend({
|
1922
|
+
offsetGridLines : true,
|
1923
|
+
calculateBarX : function(datasetCount, datasetIndex, barIndex){
|
1924
|
+
//Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar
|
1925
|
+
var xWidth = this.calculateBaseWidth(),
|
1926
|
+
xAbsolute = this.calculateX(barIndex) - (xWidth/2),
|
1927
|
+
barWidth = this.calculateBarWidth(datasetCount);
|
1928
|
+
|
1929
|
+
return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth/2;
|
1930
|
+
},
|
1931
|
+
calculateBaseWidth : function(){
|
1932
|
+
return (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing);
|
1933
|
+
},
|
1934
|
+
calculateBarWidth : function(datasetCount){
|
1935
|
+
//The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
|
1936
|
+
var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing);
|
1937
|
+
|
1938
|
+
return (baseWidth / datasetCount);
|
743
1939
|
}
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
1940
|
+
});
|
1941
|
+
|
1942
|
+
this.datasets = [];
|
1943
|
+
|
1944
|
+
//Set up tooltip events on the chart
|
1945
|
+
if (this.options.showTooltips){
|
1946
|
+
helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
|
1947
|
+
var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : [];
|
1948
|
+
|
1949
|
+
this.eachBars(function(bar){
|
1950
|
+
bar.restore(['fillColor', 'strokeColor']);
|
1951
|
+
});
|
1952
|
+
helpers.each(activeBars, function(activeBar){
|
1953
|
+
activeBar.fillColor = activeBar.highlightFill;
|
1954
|
+
activeBar.strokeColor = activeBar.highlightStroke;
|
1955
|
+
});
|
1956
|
+
this.showTooltip(activeBars);
|
1957
|
+
});
|
1958
|
+
}
|
1959
|
+
|
1960
|
+
//Declare the extension of the default point, to cater for the options passed in to the constructor
|
1961
|
+
this.BarClass = Chart.Rectangle.extend({
|
1962
|
+
strokeWidth : this.options.barStrokeWidth,
|
1963
|
+
showStroke : this.options.barShowStroke,
|
1964
|
+
ctx : this.chart.ctx
|
1965
|
+
});
|
1966
|
+
|
1967
|
+
//Iterate through each of the datasets, and build this into a property of the chart
|
1968
|
+
helpers.each(data.datasets,function(dataset,datasetIndex){
|
1969
|
+
|
1970
|
+
var datasetObject = {
|
1971
|
+
label : dataset.label || null,
|
1972
|
+
fillColor : dataset.fillColor,
|
1973
|
+
strokeColor : dataset.strokeColor,
|
1974
|
+
bars : []
|
1975
|
+
};
|
1976
|
+
|
1977
|
+
this.datasets.push(datasetObject);
|
1978
|
+
|
1979
|
+
helpers.each(dataset.data,function(dataPoint,index){
|
1980
|
+
if (helpers.isNumber(dataPoint)){
|
1981
|
+
//Add a new point for each piece of data, passing any required data to draw.
|
1982
|
+
datasetObject.bars.push(new this.BarClass({
|
1983
|
+
value : dataPoint,
|
1984
|
+
label : data.labels[index],
|
1985
|
+
strokeColor : dataset.strokeColor,
|
1986
|
+
fillColor : dataset.fillColor,
|
1987
|
+
highlightFill : dataset.highlightFill || dataset.fillColor,
|
1988
|
+
highlightStroke : dataset.highlightStroke || dataset.strokeColor
|
1989
|
+
}));
|
754
1990
|
}
|
755
|
-
}
|
756
|
-
ctx.closePath();
|
1991
|
+
},this);
|
757
1992
|
|
1993
|
+
},this);
|
758
1994
|
|
759
|
-
|
760
|
-
ctx.strokeStyle = data.datasets[i].strokeColor;
|
761
|
-
ctx.lineWidth = config.datasetStrokeWidth;
|
762
|
-
ctx.fill();
|
763
|
-
ctx.stroke();
|
1995
|
+
this.buildScale(data.labels);
|
764
1996
|
|
1997
|
+
this.BarClass.prototype.base = this.scale.endPoint;
|
765
1998
|
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
1999
|
+
this.eachBars(function(bar, index, datasetIndex){
|
2000
|
+
helpers.extend(bar, {
|
2001
|
+
width : this.scale.calculateBarWidth(this.datasets.length),
|
2002
|
+
x: this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
|
2003
|
+
y: this.scale.endPoint
|
2004
|
+
});
|
2005
|
+
bar.save();
|
2006
|
+
}, this);
|
2007
|
+
|
2008
|
+
this.render();
|
2009
|
+
},
|
2010
|
+
update : function(){
|
2011
|
+
this.scale.update();
|
2012
|
+
// Reset any highlight colours before updating.
|
2013
|
+
helpers.each(this.activeElements, function(activeElement){
|
2014
|
+
activeElement.restore(['fillColor', 'strokeColor']);
|
2015
|
+
});
|
2016
|
+
|
2017
|
+
this.eachBars(function(bar){
|
2018
|
+
bar.save();
|
2019
|
+
});
|
2020
|
+
this.render();
|
2021
|
+
},
|
2022
|
+
eachBars : function(callback){
|
2023
|
+
helpers.each(this.datasets,function(dataset, datasetIndex){
|
2024
|
+
helpers.each(dataset.bars, callback, this, datasetIndex);
|
2025
|
+
},this);
|
2026
|
+
},
|
2027
|
+
getBarsAtEvent : function(e){
|
2028
|
+
var barsArray = [],
|
2029
|
+
eventPosition = helpers.getRelativePosition(e),
|
2030
|
+
datasetIterator = function(dataset){
|
2031
|
+
barsArray.push(dataset.bars[barIndex]);
|
2032
|
+
},
|
2033
|
+
barIndex;
|
777
2034
|
|
2035
|
+
for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) {
|
2036
|
+
for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) {
|
2037
|
+
if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){
|
2038
|
+
helpers.each(this.datasets, datasetIterator);
|
2039
|
+
return barsArray;
|
2040
|
+
}
|
778
2041
|
}
|
779
|
-
ctx.rotate(rotationDegree);
|
780
2042
|
}
|
781
|
-
ctx.restore();
|
782
2043
|
|
2044
|
+
return barsArray;
|
2045
|
+
},
|
2046
|
+
buildScale : function(labels){
|
2047
|
+
var self = this;
|
2048
|
+
|
2049
|
+
var dataTotal = function(){
|
2050
|
+
var values = [];
|
2051
|
+
self.eachBars(function(bar){
|
2052
|
+
values.push(bar.value);
|
2053
|
+
});
|
2054
|
+
return values;
|
2055
|
+
};
|
2056
|
+
|
2057
|
+
var scaleOptions = {
|
2058
|
+
templateString : this.options.scaleLabel,
|
2059
|
+
height : this.chart.height,
|
2060
|
+
width : this.chart.width,
|
2061
|
+
ctx : this.chart.ctx,
|
2062
|
+
textColor : this.options.scaleFontColor,
|
2063
|
+
fontSize : this.options.scaleFontSize,
|
2064
|
+
fontStyle : this.options.scaleFontStyle,
|
2065
|
+
fontFamily : this.options.scaleFontFamily,
|
2066
|
+
valuesCount : labels.length,
|
2067
|
+
beginAtZero : this.options.scaleBeginAtZero,
|
2068
|
+
integersOnly : this.options.scaleIntegersOnly,
|
2069
|
+
calculateYRange: function(currentHeight){
|
2070
|
+
var updatedRanges = helpers.calculateScaleRange(
|
2071
|
+
dataTotal(),
|
2072
|
+
currentHeight,
|
2073
|
+
this.fontSize,
|
2074
|
+
this.beginAtZero,
|
2075
|
+
this.integersOnly
|
2076
|
+
);
|
2077
|
+
helpers.extend(this, updatedRanges);
|
2078
|
+
},
|
2079
|
+
xLabels : labels,
|
2080
|
+
font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
|
2081
|
+
lineWidth : this.options.scaleLineWidth,
|
2082
|
+
lineColor : this.options.scaleLineColor,
|
2083
|
+
gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
|
2084
|
+
gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
|
2085
|
+
padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0,
|
2086
|
+
showLabels : this.options.scaleShowLabels,
|
2087
|
+
display : this.options.showScale
|
2088
|
+
};
|
2089
|
+
|
2090
|
+
if (this.options.scaleOverride){
|
2091
|
+
helpers.extend(scaleOptions, {
|
2092
|
+
calculateYRange: helpers.noop,
|
2093
|
+
steps: this.options.scaleSteps,
|
2094
|
+
stepValue: this.options.scaleStepWidth,
|
2095
|
+
min: this.options.scaleStartValue,
|
2096
|
+
max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
|
2097
|
+
});
|
2098
|
+
}
|
2099
|
+
|
2100
|
+
this.scale = new this.ScaleClass(scaleOptions);
|
2101
|
+
},
|
2102
|
+
addData : function(valuesArray,label){
|
2103
|
+
//Map the values array for each of the datasets
|
2104
|
+
helpers.each(valuesArray,function(value,datasetIndex){
|
2105
|
+
if (helpers.isNumber(value)){
|
2106
|
+
//Add a new point for each piece of data, passing any required data to draw.
|
2107
|
+
this.datasets[datasetIndex].bars.push(new this.BarClass({
|
2108
|
+
value : value,
|
2109
|
+
label : label,
|
2110
|
+
x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1),
|
2111
|
+
y: this.scale.endPoint,
|
2112
|
+
width : this.scale.calculateBarWidth(this.datasets.length),
|
2113
|
+
base : this.scale.endPoint,
|
2114
|
+
strokeColor : this.datasets[datasetIndex].strokeColor,
|
2115
|
+
fillColor : this.datasets[datasetIndex].fillColor
|
2116
|
+
}));
|
2117
|
+
}
|
2118
|
+
},this);
|
783
2119
|
|
2120
|
+
this.scale.addXLabel(label);
|
2121
|
+
//Then re-render the chart.
|
2122
|
+
this.update();
|
2123
|
+
},
|
2124
|
+
removeData : function(){
|
2125
|
+
this.scale.removeXLabel();
|
2126
|
+
//Then re-render the chart.
|
2127
|
+
helpers.each(this.datasets,function(dataset){
|
2128
|
+
dataset.bars.shift();
|
2129
|
+
},this);
|
2130
|
+
this.update();
|
2131
|
+
},
|
2132
|
+
reflow : function(){
|
2133
|
+
helpers.extend(this.BarClass.prototype,{
|
2134
|
+
y: this.scale.endPoint,
|
2135
|
+
base : this.scale.endPoint
|
2136
|
+
});
|
2137
|
+
var newScaleProps = helpers.extend({
|
2138
|
+
height : this.chart.height,
|
2139
|
+
width : this.chart.width
|
2140
|
+
});
|
2141
|
+
this.scale.update(newScaleProps);
|
2142
|
+
},
|
2143
|
+
draw : function(ease){
|
2144
|
+
var easingDecimal = ease || 1;
|
2145
|
+
this.clear();
|
2146
|
+
|
2147
|
+
var ctx = this.chart.ctx;
|
2148
|
+
|
2149
|
+
this.scale.draw(easingDecimal);
|
2150
|
+
|
2151
|
+
//Draw all the bars for each dataset
|
2152
|
+
helpers.each(this.datasets,function(dataset,datasetIndex){
|
2153
|
+
helpers.each(dataset.bars,function(bar,index){
|
2154
|
+
bar.base = this.scale.endPoint;
|
2155
|
+
//Transition then draw
|
2156
|
+
bar.transition({
|
2157
|
+
x : this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
|
2158
|
+
y : this.scale.calculateY(bar.value),
|
2159
|
+
width : this.scale.calculateBarWidth(this.datasets.length)
|
2160
|
+
}, easingDecimal).draw();
|
2161
|
+
},this);
|
2162
|
+
|
2163
|
+
},this);
|
784
2164
|
}
|
785
|
-
|
786
|
-
var rotationDegree = (2*Math.PI)/data.datasets[0].data.length;
|
787
|
-
ctx.save();
|
788
|
-
ctx.translate(width / 2, height / 2);
|
2165
|
+
});
|
789
2166
|
|
790
|
-
if (config.angleShowLineOut){
|
791
|
-
ctx.strokeStyle = config.angleLineColor;
|
792
|
-
ctx.lineWidth = config.angleLineWidth;
|
793
|
-
for (var h=0; h<data.datasets[0].data.length; h++){
|
794
2167
|
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
ctx.lineTo(0,-maxSize);
|
799
|
-
ctx.stroke();
|
800
|
-
}
|
801
|
-
}
|
2168
|
+
}).call(this);
|
2169
|
+
(function(){
|
2170
|
+
"use strict";
|
802
2171
|
|
803
|
-
|
804
|
-
|
2172
|
+
var root = this,
|
2173
|
+
Chart = root.Chart,
|
2174
|
+
//Cache a local reference to Chart.helpers
|
2175
|
+
helpers = Chart.helpers;
|
805
2176
|
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
ctx.moveTo(0,-scaleHop * (i+1));
|
810
|
-
for (var j=0; j<data.datasets[0].data.length; j++){
|
811
|
-
ctx.rotate(rotationDegree);
|
812
|
-
ctx.lineTo(0,-scaleHop * (i+1));
|
813
|
-
}
|
814
|
-
ctx.closePath();
|
815
|
-
ctx.stroke();
|
2177
|
+
var defaultConfig = {
|
2178
|
+
//Boolean - Whether we should show a stroke on each segment
|
2179
|
+
segmentShowStroke : true,
|
816
2180
|
|
817
|
-
|
2181
|
+
//String - The colour of each segment stroke
|
2182
|
+
segmentStrokeColor : "rgba(255,255,255,1)",
|
818
2183
|
|
819
|
-
|
820
|
-
|
821
|
-
ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
|
822
|
-
ctx.textBaseline = "middle";
|
2184
|
+
//Number - The width of each segment stroke
|
2185
|
+
segmentStrokeWidth : 2,
|
823
2186
|
|
824
|
-
|
825
|
-
|
826
|
-
ctx.fillStyle = config.scaleBackdropColor;
|
827
|
-
ctx.beginPath();
|
828
|
-
ctx.rect(
|
829
|
-
Math.round(- textWidth/2 - config.scaleBackdropPaddingX), //X
|
830
|
-
Math.round((-scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y
|
831
|
-
Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width
|
832
|
-
Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height
|
833
|
-
);
|
834
|
-
ctx.fill();
|
835
|
-
}
|
836
|
-
ctx.fillStyle = config.scaleFontColor;
|
837
|
-
ctx.fillText(calculatedScale.labels[i],0,-scaleHop*(i+1));
|
838
|
-
}
|
2187
|
+
//The percentage of the chart that we cut out of the middle.
|
2188
|
+
percentageInnerCutout : 50,
|
839
2189
|
|
840
|
-
|
841
|
-
|
842
|
-
ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily;
|
843
|
-
ctx.fillStyle = config.pointLabelFontColor;
|
844
|
-
var opposite = Math.sin(rotationDegree*k) * (maxSize + config.pointLabelFontSize);
|
845
|
-
var adjacent = Math.cos(rotationDegree*k) * (maxSize + config.pointLabelFontSize);
|
846
|
-
|
847
|
-
if(rotationDegree*k == Math.PI || rotationDegree*k == 0){
|
848
|
-
ctx.textAlign = "center";
|
849
|
-
}
|
850
|
-
else if(rotationDegree*k > Math.PI){
|
851
|
-
ctx.textAlign = "right";
|
852
|
-
}
|
853
|
-
else{
|
854
|
-
ctx.textAlign = "left";
|
855
|
-
}
|
2190
|
+
//Number - Amount of animation steps
|
2191
|
+
animationSteps : 100,
|
856
2192
|
|
857
|
-
|
2193
|
+
//String - Animation easing effect
|
2194
|
+
animationEasing : "easeOutBounce",
|
858
2195
|
|
859
|
-
|
2196
|
+
//Boolean - Whether we animate the rotation of the Doughnut
|
2197
|
+
animateRotate : true,
|
860
2198
|
|
861
|
-
|
862
|
-
|
863
|
-
};
|
864
|
-
function calculateDrawingSizes(){
|
865
|
-
maxSize = (Min([width,height])/2);
|
2199
|
+
//Boolean - Whether we animate scaling the Doughnut from the centre
|
2200
|
+
animateScale : false,
|
866
2201
|
|
867
|
-
|
2202
|
+
//String - A legend template
|
2203
|
+
legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"
|
868
2204
|
|
869
|
-
|
870
|
-
for (var i=0; i<data.labels.length; i++){
|
871
|
-
ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily;
|
872
|
-
var textMeasurement = ctx.measureText(data.labels[i]).width;
|
873
|
-
if(textMeasurement>labelLength) labelLength = textMeasurement;
|
874
|
-
}
|
2205
|
+
};
|
875
2206
|
|
876
|
-
//Figure out whats the largest - the height of the text or the width of what's there, and minus it from the maximum usable size.
|
877
|
-
maxSize -= Max([labelLength,((config.pointLabelFontSize/2)*1.5)]);
|
878
2207
|
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
2208
|
+
Chart.Type.extend({
|
2209
|
+
//Passing in a name registers this chart in the Chart namespace
|
2210
|
+
name: "Doughnut",
|
2211
|
+
//Providing a defaults will also register the deafults in the chart namespace
|
2212
|
+
defaults : defaultConfig,
|
2213
|
+
//Initialize is fired when the chart is initialized - Data is passed in as a parameter
|
2214
|
+
//Config is automatically merged by the core of Chart.js, and is available at this.options
|
2215
|
+
initialize: function(data){
|
2216
|
+
|
2217
|
+
//Declare segments as a static property to prevent inheriting across the Chart type prototype
|
2218
|
+
this.segments = [];
|
2219
|
+
this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2;
|
2220
|
+
|
2221
|
+
this.SegmentArc = Chart.Arc.extend({
|
2222
|
+
ctx : this.chart.ctx,
|
2223
|
+
x : this.chart.width/2,
|
2224
|
+
y : this.chart.height/2
|
2225
|
+
});
|
2226
|
+
|
2227
|
+
//Set up tooltip events on the chart
|
2228
|
+
if (this.options.showTooltips){
|
2229
|
+
helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
|
2230
|
+
var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
|
2231
|
+
|
2232
|
+
helpers.each(this.segments,function(segment){
|
2233
|
+
segment.restore(["fillColor"]);
|
2234
|
+
});
|
2235
|
+
helpers.each(activeSegments,function(activeSegment){
|
2236
|
+
activeSegment.fillColor = activeSegment.highlightColor;
|
2237
|
+
});
|
2238
|
+
this.showTooltip(activeSegments);
|
2239
|
+
});
|
894
2240
|
}
|
2241
|
+
this.calculateTotal(data);
|
895
2242
|
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
return {
|
900
|
-
maxValue : upperValue,
|
901
|
-
minValue : lowerValue,
|
902
|
-
maxSteps : maxSteps,
|
903
|
-
minSteps : minSteps
|
904
|
-
};
|
2243
|
+
helpers.each(data,function(datapoint, index){
|
2244
|
+
this.addData(datapoint, index, true);
|
2245
|
+
},this);
|
905
2246
|
|
2247
|
+
this.render();
|
2248
|
+
},
|
2249
|
+
getSegmentsAtEvent : function(e){
|
2250
|
+
var segmentsArray = [];
|
906
2251
|
|
907
|
-
|
908
|
-
}
|
2252
|
+
var location = helpers.getRelativePosition(e);
|
909
2253
|
|
910
|
-
|
911
|
-
|
2254
|
+
helpers.each(this.segments,function(segment){
|
2255
|
+
if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
|
2256
|
+
},this);
|
2257
|
+
return segmentsArray;
|
2258
|
+
},
|
2259
|
+
addData : function(segment, atIndex, silent){
|
2260
|
+
var index = atIndex || this.segments.length;
|
2261
|
+
this.segments.splice(index, 0, new this.SegmentArc({
|
2262
|
+
value : segment.value,
|
2263
|
+
outerRadius : (this.options.animateScale) ? 0 : this.outerRadius,
|
2264
|
+
innerRadius : (this.options.animateScale) ? 0 : (this.outerRadius/100) * this.options.percentageInnerCutout,
|
2265
|
+
fillColor : segment.color,
|
2266
|
+
highlightColor : segment.highlight || segment.color,
|
2267
|
+
showStroke : this.options.segmentShowStroke,
|
2268
|
+
strokeWidth : this.options.segmentStrokeWidth,
|
2269
|
+
strokeColor : this.options.segmentStrokeColor,
|
2270
|
+
startAngle : Math.PI * 1.5,
|
2271
|
+
circumference : (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value),
|
2272
|
+
label : segment.label
|
2273
|
+
}));
|
2274
|
+
if (!silent){
|
2275
|
+
this.reflow();
|
2276
|
+
this.update();
|
2277
|
+
}
|
2278
|
+
},
|
2279
|
+
calculateCircumference : function(value){
|
2280
|
+
return (Math.PI*2)*(value / this.total);
|
2281
|
+
},
|
2282
|
+
calculateTotal : function(data){
|
2283
|
+
this.total = 0;
|
2284
|
+
helpers.each(data,function(segment){
|
2285
|
+
this.total += segment.value;
|
2286
|
+
},this);
|
2287
|
+
},
|
2288
|
+
update : function(){
|
2289
|
+
this.calculateTotal(this.segments);
|
2290
|
+
|
2291
|
+
// Reset any highlight colours before updating.
|
2292
|
+
helpers.each(this.activeElements, function(activeElement){
|
2293
|
+
activeElement.restore(['fillColor']);
|
2294
|
+
});
|
2295
|
+
|
2296
|
+
helpers.each(this.segments,function(segment){
|
2297
|
+
segment.save();
|
2298
|
+
});
|
2299
|
+
this.render();
|
2300
|
+
},
|
912
2301
|
|
913
|
-
|
914
|
-
|
2302
|
+
removeData: function(atIndex){
|
2303
|
+
var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
|
2304
|
+
this.segments.splice(indexToDelete, 1);
|
2305
|
+
this.reflow();
|
2306
|
+
this.update();
|
2307
|
+
},
|
915
2308
|
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
2309
|
+
reflow : function(){
|
2310
|
+
helpers.extend(this.SegmentArc.prototype,{
|
2311
|
+
x : this.chart.width/2,
|
2312
|
+
y : this.chart.height/2
|
2313
|
+
});
|
2314
|
+
this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2;
|
2315
|
+
helpers.each(this.segments, function(segment){
|
2316
|
+
segment.update({
|
2317
|
+
outerRadius : this.outerRadius,
|
2318
|
+
innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
|
2319
|
+
});
|
2320
|
+
}, this);
|
2321
|
+
},
|
2322
|
+
draw : function(easeDecimal){
|
2323
|
+
var animDecimal = (easeDecimal) ? easeDecimal : 1;
|
2324
|
+
this.clear();
|
2325
|
+
helpers.each(this.segments,function(segment,index){
|
2326
|
+
segment.transition({
|
2327
|
+
circumference : this.calculateCircumference(segment.value),
|
2328
|
+
outerRadius : this.outerRadius,
|
2329
|
+
innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
|
2330
|
+
},animDecimal);
|
2331
|
+
|
2332
|
+
segment.endAngle = segment.startAngle + segment.circumference;
|
2333
|
+
|
2334
|
+
segment.draw();
|
2335
|
+
if (index === 0){
|
2336
|
+
segment.startAngle = Math.PI * 1.5;
|
931
2337
|
}
|
932
|
-
if
|
933
|
-
|
2338
|
+
//Check to see if it's the last segment, if not get the next and update the start angle
|
2339
|
+
if (index < this.segments.length-1){
|
2340
|
+
this.segments[index+1].startAngle = segment.endAngle;
|
934
2341
|
}
|
935
|
-
}
|
2342
|
+
},this);
|
936
2343
|
|
937
|
-
|
938
|
-
|
939
|
-
ctx.beginPath();
|
940
|
-
ctx.arc(width/2,height/2,scaleAnimation * pieRadius,cumulativeAngle,cumulativeAngle + segmentAngle);
|
941
|
-
ctx.lineTo(width/2,height/2);
|
942
|
-
ctx.closePath();
|
943
|
-
ctx.fillStyle = data[i].color;
|
944
|
-
ctx.fill();
|
2344
|
+
}
|
2345
|
+
});
|
945
2346
|
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
2347
|
+
Chart.types.Doughnut.extend({
|
2348
|
+
name : "Pie",
|
2349
|
+
defaults : helpers.merge(defaultConfig,{percentageInnerCutout : 0})
|
2350
|
+
});
|
2351
|
+
|
2352
|
+
}).call(this);
|
2353
|
+
(function(){
|
2354
|
+
"use strict";
|
2355
|
+
|
2356
|
+
var root = this,
|
2357
|
+
Chart = root.Chart,
|
2358
|
+
helpers = Chart.helpers;
|
2359
|
+
|
2360
|
+
var defaultConfig = {
|
2361
|
+
|
2362
|
+
///Boolean - Whether grid lines are shown across the chart
|
2363
|
+
scaleShowGridLines : true,
|
2364
|
+
|
2365
|
+
//String - Colour of the grid lines
|
2366
|
+
scaleGridLineColor : "rgba(0,0,0,.05)",
|
958
2367
|
|
959
|
-
|
960
|
-
|
961
|
-
case 'left': return 'right'; break;
|
962
|
-
case 'right': return 'left'; break;
|
963
|
-
case 'center': return align; break;
|
964
|
-
}
|
965
|
-
}
|
2368
|
+
//Number - Width of the grid lines
|
2369
|
+
scaleGridLineWidth : 1,
|
966
2370
|
|
967
|
-
|
2371
|
+
//Boolean - Whether the line is curved between points
|
2372
|
+
bezierCurve : true,
|
968
2373
|
|
969
|
-
|
970
|
-
|
971
|
-
}
|
972
|
-
ctx.font = config.labelFontStyle+ " " +fontSize+" " + config.labelFontFamily;
|
973
|
-
ctx.fillStyle = getFadeColor(animationDecimal, data[i].labelColor || 'black', data[i].color);
|
974
|
-
ctx.textBaseline = 'middle';
|
975
|
-
// rotate text, so it perfectly fits in segments
|
976
|
-
var textRotation = -(cumulativeAngle + segmentAngle)+segmentAngle/2,
|
977
|
-
tX = width/2+scaleAnimation*pieRadius*Math.cos(textRotation),
|
978
|
-
tY = height/2-scaleAnimation*pieRadius*Math.sin(textRotation);
|
979
|
-
ctx.textAlign = data[i].labelAlign || config.labelAlign;
|
980
|
-
textX = getPieLabelX(ctx.textAlign, scaleAnimation*pieRadius);
|
981
|
-
if(textRotation < -Math.PI/2) {
|
982
|
-
textRotation -= Math.PI;
|
983
|
-
ctx.textAlign = reversePieLabelAlign(ctx.textAlign);
|
984
|
-
textX = -textX;
|
985
|
-
}
|
986
|
-
ctx.translate(tX, tY);
|
987
|
-
ctx.rotate(-textRotation);
|
988
|
-
ctx.fillText(data[i].label, textX, 0);
|
989
|
-
ctx.rotate(textRotation);
|
990
|
-
ctx.translate(-tX, -tY);
|
991
|
-
}
|
2374
|
+
//Number - Tension of the bezier curve between points
|
2375
|
+
bezierCurveTension : 0.4,
|
992
2376
|
|
993
|
-
|
994
|
-
|
995
|
-
pAmount = 50;
|
996
|
-
points.push({x:width/2+pieRadius*Math.cos(cumulativeAngle),y:height/2+pieRadius*Math.sin(cumulativeAngle)});
|
997
|
-
for(var p = 0; p <= pAmount; p++) {
|
998
|
-
points.push({x:width/2+pieRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+pieRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)});
|
999
|
-
}
|
1000
|
-
registerTooltip(ctx,{type:'shape',points:points},{label:data[i].label,value:data[i].value},'Pie');
|
1001
|
-
}
|
2377
|
+
//Boolean - Whether to show a dot for each point
|
2378
|
+
pointDot : true,
|
1002
2379
|
|
1003
|
-
|
1004
|
-
|
1005
|
-
ctx.strokeStyle = config.segmentStrokeColor;
|
1006
|
-
ctx.stroke();
|
1007
|
-
}
|
1008
|
-
cumulativeAngle += segmentAngle;
|
1009
|
-
}
|
1010
|
-
}
|
1011
|
-
}
|
2380
|
+
//Number - Radius of each point dot in pixels
|
2381
|
+
pointDotRadius : 4,
|
1012
2382
|
|
1013
|
-
|
1014
|
-
|
2383
|
+
//Number - Pixel width of point dot stroke
|
2384
|
+
pointDotStrokeWidth : 1,
|
1015
2385
|
|
1016
|
-
//
|
1017
|
-
|
2386
|
+
//Number - amount extra to add to the radius to cater for hit detection outside the drawn point
|
2387
|
+
pointHitDetectionRadius : 20,
|
1018
2388
|
|
1019
|
-
|
2389
|
+
//Boolean - Whether to show a stroke for datasets
|
2390
|
+
datasetStroke : true,
|
1020
2391
|
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
2392
|
+
//Number - Pixel width of dataset stroke
|
2393
|
+
datasetStrokeWidth : 1,
|
2394
|
+
|
2395
|
+
//Boolean - Whether to fill the dataset with a colour
|
2396
|
+
datasetFill : true,
|
1024
2397
|
|
2398
|
+
//String - A legend template
|
2399
|
+
legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
|
1025
2400
|
|
1026
|
-
|
2401
|
+
};
|
1027
2402
|
|
1028
2403
|
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
2404
|
+
Chart.Type.extend({
|
2405
|
+
name: "Line",
|
2406
|
+
defaults : defaultConfig,
|
2407
|
+
initialize: function(data){
|
2408
|
+
//Declare the extension of the default point, to cater for the options passed in to the constructor
|
2409
|
+
this.PointClass = Chart.Point.extend({
|
2410
|
+
strokeWidth : this.options.pointDotStrokeWidth,
|
2411
|
+
radius : this.options.pointDotRadius,
|
2412
|
+
hitDetectionRadius : this.options.pointHitDetectionRadius,
|
2413
|
+
ctx : this.chart.ctx,
|
2414
|
+
inRange : function(mouseX){
|
2415
|
+
return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2));
|
1039
2416
|
}
|
2417
|
+
});
|
2418
|
+
|
2419
|
+
this.datasets = [];
|
2420
|
+
|
2421
|
+
//Set up tooltip events on the chart
|
2422
|
+
if (this.options.showTooltips){
|
2423
|
+
helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
|
2424
|
+
var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
|
2425
|
+
this.eachPoints(function(point){
|
2426
|
+
point.restore(['fillColor', 'strokeColor']);
|
2427
|
+
});
|
2428
|
+
helpers.each(activePoints, function(activePoint){
|
2429
|
+
activePoint.fillColor = activePoint.highlightFill;
|
2430
|
+
activePoint.strokeColor = activePoint.highlightStroke;
|
2431
|
+
});
|
2432
|
+
this.showTooltip(activePoints);
|
2433
|
+
});
|
1040
2434
|
}
|
1041
|
-
for (var i=0; i<data.length; i++){
|
1042
|
-
var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2));
|
1043
|
-
ctx.beginPath();
|
1044
|
-
ctx.arc(width/2,height/2,scaleAnimation * doughnutRadius,cumulativeAngle,cumulativeAngle + segmentAngle,false);
|
1045
|
-
ctx.arc(width/2,height/2,scaleAnimation * cutoutRadius,cumulativeAngle + segmentAngle,cumulativeAngle,true);
|
1046
|
-
ctx.closePath();
|
1047
|
-
ctx.fillStyle = data[i].color;
|
1048
|
-
ctx.fill();
|
1049
2435
|
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
2436
|
+
//Iterate through each of the datasets, and build this into a property of the chart
|
2437
|
+
helpers.each(data.datasets,function(dataset){
|
2438
|
+
|
2439
|
+
var datasetObject = {
|
2440
|
+
label : dataset.label || null,
|
2441
|
+
fillColor : dataset.fillColor,
|
2442
|
+
strokeColor : dataset.strokeColor,
|
2443
|
+
pointColor : dataset.pointColor,
|
2444
|
+
pointStrokeColor : dataset.pointStrokeColor,
|
2445
|
+
points : []
|
2446
|
+
};
|
2447
|
+
|
2448
|
+
this.datasets.push(datasetObject);
|
2449
|
+
|
2450
|
+
|
2451
|
+
helpers.each(dataset.data,function(dataPoint,index){
|
2452
|
+
//Best way to do this? or in draw sequence...?
|
2453
|
+
if (helpers.isNumber(dataPoint)){
|
2454
|
+
//Add a new point for each piece of data, passing any required data to draw.
|
2455
|
+
datasetObject.points.push(new this.PointClass({
|
2456
|
+
value : dataPoint,
|
2457
|
+
label : data.labels[index],
|
2458
|
+
// x: this.scale.calculateX(index),
|
2459
|
+
// y: this.scale.endPoint,
|
2460
|
+
strokeColor : dataset.pointStrokeColor,
|
2461
|
+
fillColor : dataset.pointColor,
|
2462
|
+
highlightFill : dataset.pointHighlightFill || dataset.pointColor,
|
2463
|
+
highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
|
2464
|
+
}));
|
1060
2465
|
}
|
1061
|
-
|
1062
|
-
}
|
2466
|
+
},this);
|
1063
2467
|
|
1064
|
-
|
1065
|
-
ctx.lineWidth = config.segmentStrokeWidth;
|
1066
|
-
ctx.strokeStyle = config.segmentStrokeColor;
|
1067
|
-
ctx.stroke();
|
1068
|
-
}
|
1069
|
-
cumulativeAngle += segmentAngle;
|
1070
|
-
}
|
1071
|
-
}
|
2468
|
+
this.buildScale(data.labels);
|
1072
2469
|
|
1073
2470
|
|
2471
|
+
this.eachPoints(function(point, index){
|
2472
|
+
helpers.extend(point, {
|
2473
|
+
x: this.scale.calculateX(index),
|
2474
|
+
y: this.scale.endPoint
|
2475
|
+
});
|
2476
|
+
point.save();
|
2477
|
+
}, this);
|
1074
2478
|
|
1075
|
-
|
2479
|
+
},this);
|
1076
2480
|
|
1077
|
-
var Line = function(data,config,ctx){
|
1078
|
-
var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY, rotateLabels = 0;
|
1079
2481
|
|
1080
|
-
|
2482
|
+
this.render();
|
2483
|
+
},
|
2484
|
+
update : function(){
|
2485
|
+
this.scale.update();
|
2486
|
+
// Reset any highlight colours before updating.
|
2487
|
+
helpers.each(this.activeElements, function(activeElement){
|
2488
|
+
activeElement.restore(['fillColor', 'strokeColor']);
|
2489
|
+
});
|
2490
|
+
this.eachPoints(function(point){
|
2491
|
+
point.save();
|
2492
|
+
});
|
2493
|
+
this.render();
|
2494
|
+
},
|
2495
|
+
eachPoints : function(callback){
|
2496
|
+
helpers.each(this.datasets,function(dataset){
|
2497
|
+
helpers.each(dataset.points,callback,this);
|
2498
|
+
},this);
|
2499
|
+
},
|
2500
|
+
getPointsAtEvent : function(e){
|
2501
|
+
var pointsArray = [],
|
2502
|
+
eventPosition = helpers.getRelativePosition(e);
|
2503
|
+
helpers.each(this.datasets,function(dataset){
|
2504
|
+
helpers.each(dataset.points,function(point){
|
2505
|
+
if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point);
|
2506
|
+
});
|
2507
|
+
},this);
|
2508
|
+
return pointsArray;
|
2509
|
+
},
|
2510
|
+
buildScale : function(labels){
|
2511
|
+
var self = this;
|
1081
2512
|
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
2513
|
+
var dataTotal = function(){
|
2514
|
+
var values = [];
|
2515
|
+
self.eachPoints(function(point){
|
2516
|
+
values.push(point.value);
|
2517
|
+
});
|
1086
2518
|
|
1087
|
-
|
1088
|
-
|
1089
|
-
else {
|
1090
|
-
calculatedScale = {
|
1091
|
-
steps : config.scaleSteps,
|
1092
|
-
stepValue : config.scaleStepWidth,
|
1093
|
-
graphMin : config.scaleStartValue,
|
1094
|
-
labels : []
|
1095
|
-
}
|
1096
|
-
populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
|
1097
|
-
}
|
2519
|
+
return values;
|
2520
|
+
};
|
1098
2521
|
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
2522
|
+
var scaleOptions = {
|
2523
|
+
templateString : this.options.scaleLabel,
|
2524
|
+
height : this.chart.height,
|
2525
|
+
width : this.chart.width,
|
2526
|
+
ctx : this.chart.ctx,
|
2527
|
+
textColor : this.options.scaleFontColor,
|
2528
|
+
fontSize : this.options.scaleFontSize,
|
2529
|
+
fontStyle : this.options.scaleFontStyle,
|
2530
|
+
fontFamily : this.options.scaleFontFamily,
|
2531
|
+
valuesCount : labels.length,
|
2532
|
+
beginAtZero : this.options.scaleBeginAtZero,
|
2533
|
+
integersOnly : this.options.scaleIntegersOnly,
|
2534
|
+
calculateYRange : function(currentHeight){
|
2535
|
+
var updatedRanges = helpers.calculateScaleRange(
|
2536
|
+
dataTotal(),
|
2537
|
+
currentHeight,
|
2538
|
+
this.fontSize,
|
2539
|
+
this.beginAtZero,
|
2540
|
+
this.integersOnly
|
2541
|
+
);
|
2542
|
+
helpers.extend(this, updatedRanges);
|
2543
|
+
},
|
2544
|
+
xLabels : labels,
|
2545
|
+
font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
|
2546
|
+
lineWidth : this.options.scaleLineWidth,
|
2547
|
+
lineColor : this.options.scaleLineColor,
|
2548
|
+
gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
|
2549
|
+
gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
|
2550
|
+
padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth,
|
2551
|
+
showLabels : this.options.scaleShowLabels,
|
2552
|
+
display : this.options.showScale
|
2553
|
+
};
|
1102
2554
|
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
2555
|
+
if (this.options.scaleOverride){
|
2556
|
+
helpers.extend(scaleOptions, {
|
2557
|
+
calculateYRange: helpers.noop,
|
2558
|
+
steps: this.options.scaleSteps,
|
2559
|
+
stepValue: this.options.scaleStepWidth,
|
2560
|
+
min: this.options.scaleStartValue,
|
2561
|
+
max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
|
2562
|
+
});
|
2563
|
+
}
|
1109
2564
|
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
ctx.lineTo(yAxisPosX + (valueHop*(data.datasets[i].data.length-1)),xAxisPosY);
|
1128
|
-
ctx.lineTo(yAxisPosX,xAxisPosY);
|
1129
|
-
ctx.closePath();
|
1130
|
-
ctx.fillStyle = data.datasets[i].fillColor;
|
1131
|
-
ctx.fill();
|
1132
|
-
}
|
1133
|
-
else{
|
1134
|
-
ctx.closePath();
|
1135
|
-
}
|
1136
|
-
if(config.pointDot){
|
1137
|
-
ctx.fillStyle = data.datasets[i].pointColor;
|
1138
|
-
ctx.strokeStyle = data.datasets[i].pointStrokeColor;
|
1139
|
-
ctx.lineWidth = config.pointDotStrokeWidth;
|
1140
|
-
for (var k=0; k<data.datasets[i].data.length; k++){
|
1141
|
-
ctx.beginPath();
|
1142
|
-
ctx.arc(yAxisPosX + (valueHop *k),xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[k],calculatedScale,scaleHop)),config.pointDotRadius,0,Math.PI*2,true);
|
1143
|
-
ctx.fill();
|
1144
|
-
ctx.stroke();
|
2565
|
+
|
2566
|
+
this.scale = new Chart.Scale(scaleOptions);
|
2567
|
+
},
|
2568
|
+
addData : function(valuesArray,label){
|
2569
|
+
//Map the values array for each of the datasets
|
2570
|
+
|
2571
|
+
helpers.each(valuesArray,function(value,datasetIndex){
|
2572
|
+
if (helpers.isNumber(value)){
|
2573
|
+
//Add a new point for each piece of data, passing any required data to draw.
|
2574
|
+
this.datasets[datasetIndex].points.push(new this.PointClass({
|
2575
|
+
value : value,
|
2576
|
+
label : label,
|
2577
|
+
x: this.scale.calculateX(this.scale.valuesCount+1),
|
2578
|
+
y: this.scale.endPoint,
|
2579
|
+
strokeColor : this.datasets[datasetIndex].pointStrokeColor,
|
2580
|
+
fillColor : this.datasets[datasetIndex].pointColor
|
2581
|
+
}));
|
1145
2582
|
}
|
1146
|
-
|
1147
|
-
}
|
2583
|
+
},this);
|
1148
2584
|
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
2585
|
+
this.scale.addXLabel(label);
|
2586
|
+
//Then re-render the chart.
|
2587
|
+
this.update();
|
2588
|
+
},
|
2589
|
+
removeData : function(){
|
2590
|
+
this.scale.removeXLabel();
|
2591
|
+
//Then re-render the chart.
|
2592
|
+
helpers.each(this.datasets,function(dataset){
|
2593
|
+
dataset.points.shift();
|
2594
|
+
},this);
|
2595
|
+
this.update();
|
2596
|
+
},
|
2597
|
+
reflow : function(){
|
2598
|
+
var newScaleProps = helpers.extend({
|
2599
|
+
height : this.chart.height,
|
2600
|
+
width : this.chart.width
|
2601
|
+
});
|
2602
|
+
this.scale.update(newScaleProps);
|
2603
|
+
},
|
2604
|
+
draw : function(ease){
|
2605
|
+
var easingDecimal = ease || 1;
|
2606
|
+
this.clear();
|
1164
2607
|
|
2608
|
+
var ctx = this.chart.ctx;
|
1165
2609
|
|
1166
|
-
|
1167
|
-
ctx.save();
|
1168
|
-
ctx.textAlign = "right";
|
1169
|
-
}
|
1170
|
-
else{
|
1171
|
-
ctx.textAlign = "center";
|
1172
|
-
}
|
1173
|
-
ctx.fillStyle = config.scaleFontColor;
|
1174
|
-
for (var i=0; i<data.labels.length; i++){
|
1175
|
-
ctx.save();
|
1176
|
-
if (rotateLabels > 0){
|
1177
|
-
ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize);
|
1178
|
-
ctx.rotate(-(rotateLabels * (Math.PI/180)));
|
1179
|
-
ctx.fillText(data.labels[i], 0,0);
|
1180
|
-
ctx.restore();
|
1181
|
-
}
|
2610
|
+
this.scale.draw(easingDecimal);
|
1182
2611
|
|
1183
|
-
else{
|
1184
|
-
ctx.fillText(data.labels[i], yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize+3);
|
1185
|
-
}
|
1186
2612
|
|
1187
|
-
|
1188
|
-
ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY+3);
|
2613
|
+
helpers.each(this.datasets,function(dataset){
|
1189
2614
|
|
1190
|
-
//
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
2615
|
+
//Transition each point first so that the line and point drawing isn't out of sync
|
2616
|
+
//We can use this extra loop to calculate the control points of this dataset also in this loop
|
2617
|
+
|
2618
|
+
helpers.each(dataset.points,function(point,index){
|
2619
|
+
point.transition({
|
2620
|
+
y : this.scale.calculateY(point.value),
|
2621
|
+
x : this.scale.calculateX(index)
|
2622
|
+
}, easingDecimal);
|
2623
|
+
|
2624
|
+
},this);
|
2625
|
+
|
2626
|
+
|
2627
|
+
// Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point
|
2628
|
+
// This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed
|
2629
|
+
if (this.options.bezierCurve){
|
2630
|
+
helpers.each(dataset.points,function(point,index){
|
2631
|
+
//If we're at the start or end, we don't have a previous/next point
|
2632
|
+
//By setting the tension to 0 here, the curve will transition to straight at the end
|
2633
|
+
if (index === 0){
|
2634
|
+
point.controlPoints = helpers.splineCurve(point,point,dataset.points[index+1],0);
|
2635
|
+
}
|
2636
|
+
else if (index >= dataset.points.length-1){
|
2637
|
+
point.controlPoints = helpers.splineCurve(dataset.points[index-1],point,point,0);
|
2638
|
+
}
|
2639
|
+
else{
|
2640
|
+
point.controlPoints = helpers.splineCurve(dataset.points[index-1],point,dataset.points[index+1],this.options.bezierCurveTension);
|
2641
|
+
}
|
2642
|
+
},this);
|
1198
2643
|
}
|
1199
|
-
ctx.stroke();
|
1200
|
-
}
|
1201
2644
|
|
1202
|
-
//Y axis
|
1203
|
-
ctx.lineWidth = config.scaleLineWidth;
|
1204
|
-
ctx.strokeStyle = config.scaleLineColor;
|
1205
|
-
ctx.beginPath();
|
1206
|
-
ctx.moveTo(yAxisPosX,xAxisPosY+5);
|
1207
|
-
ctx.lineTo(yAxisPosX,5);
|
1208
|
-
ctx.stroke();
|
1209
2645
|
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
2646
|
+
//Draw the line between all the points
|
2647
|
+
ctx.lineWidth = this.options.datasetStrokeWidth;
|
2648
|
+
ctx.strokeStyle = dataset.strokeColor;
|
1213
2649
|
ctx.beginPath();
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
2650
|
+
helpers.each(dataset.points,function(point,index){
|
2651
|
+
if (index>0){
|
2652
|
+
if(this.options.bezierCurve){
|
2653
|
+
ctx.bezierCurveTo(
|
2654
|
+
dataset.points[index-1].controlPoints.outer.x,
|
2655
|
+
dataset.points[index-1].controlPoints.outer.y,
|
2656
|
+
point.controlPoints.inner.x,
|
2657
|
+
point.controlPoints.inner.y,
|
2658
|
+
point.x,
|
2659
|
+
point.y
|
2660
|
+
);
|
2661
|
+
}
|
2662
|
+
else{
|
2663
|
+
ctx.lineTo(point.x,point.y);
|
2664
|
+
}
|
1223
2665
|
|
2666
|
+
}
|
2667
|
+
else{
|
2668
|
+
ctx.moveTo(point.x,point.y);
|
2669
|
+
}
|
2670
|
+
},this);
|
1224
2671
|
ctx.stroke();
|
1225
2672
|
|
1226
|
-
if (config.scaleShowLabels){
|
1227
|
-
ctx.fillText(calculatedScale.labels[j],yAxisPosX-8,xAxisPosY - ((j+1) * scaleHop));
|
1228
|
-
}
|
1229
|
-
}
|
1230
|
-
|
1231
2673
|
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
var measuredText = ctx.measureText(calculatedScale.labels[i]).width;
|
1240
|
-
longestText = (measuredText > longestText)? measuredText : longestText;
|
2674
|
+
if (this.options.datasetFill){
|
2675
|
+
//Round off the line by going to the base of the chart, back to the start, then fill.
|
2676
|
+
ctx.lineTo(dataset.points[dataset.points.length-1].x, this.scale.endPoint);
|
2677
|
+
ctx.lineTo(this.scale.calculateX(0), this.scale.endPoint);
|
2678
|
+
ctx.fillStyle = dataset.fillColor;
|
2679
|
+
ctx.closePath();
|
2680
|
+
ctx.fill();
|
1241
2681
|
}
|
1242
|
-
//Add a little extra padding from the y axis
|
1243
|
-
longestText +=10;
|
1244
|
-
}
|
1245
|
-
xAxisLength = width - longestText - widestXLabel;
|
1246
|
-
valueHop = Math.floor(xAxisLength/(data.labels.length-1));
|
1247
2682
|
|
1248
|
-
|
1249
|
-
|
2683
|
+
//Now draw the points over the line
|
2684
|
+
//A little inefficient double looping, but better than the line
|
2685
|
+
//lagging behind the point positions
|
2686
|
+
helpers.each(dataset.points,function(point){
|
2687
|
+
point.draw();
|
2688
|
+
});
|
2689
|
+
|
2690
|
+
},this);
|
1250
2691
|
}
|
1251
|
-
|
1252
|
-
maxSize = height;
|
1253
|
-
|
1254
|
-
//Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees.
|
1255
|
-
ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
|
1256
|
-
widestXLabel = 1;
|
1257
|
-
for (var i=0; i<data.labels.length; i++){
|
1258
|
-
var textLength = ctx.measureText(data.labels[i]).width;
|
1259
|
-
//If the text length is longer - make that equal to longest text!
|
1260
|
-
widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel;
|
1261
|
-
}
|
1262
|
-
if (width/data.labels.length < widestXLabel){
|
1263
|
-
rotateLabels = 45;
|
1264
|
-
if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){
|
1265
|
-
rotateLabels = 90;
|
1266
|
-
maxSize -= widestXLabel;
|
1267
|
-
}
|
1268
|
-
else{
|
1269
|
-
maxSize -= Math.sin(rotateLabels) * widestXLabel;
|
1270
|
-
}
|
1271
|
-
}
|
1272
|
-
else{
|
1273
|
-
maxSize -= config.scaleFontSize;
|
1274
|
-
}
|
2692
|
+
});
|
1275
2693
|
|
1276
|
-
//Add a little padding between the x line and the text
|
1277
|
-
maxSize -= 5;
|
1278
2694
|
|
2695
|
+
}).call(this);
|
2696
|
+
(function(){
|
2697
|
+
"use strict";
|
1279
2698
|
|
1280
|
-
|
2699
|
+
var root = this,
|
2700
|
+
Chart = root.Chart,
|
2701
|
+
//Cache a local reference to Chart.helpers
|
2702
|
+
helpers = Chart.helpers;
|
1281
2703
|
|
1282
|
-
|
1283
|
-
|
2704
|
+
var defaultConfig = {
|
2705
|
+
//Boolean - Show a backdrop to the scale label
|
2706
|
+
scaleShowLabelBackdrop : true,
|
1284
2707
|
|
1285
|
-
|
2708
|
+
//String - The colour of the label backdrop
|
2709
|
+
scaleBackdropColor : "rgba(255,255,255,0.75)",
|
1286
2710
|
|
1287
|
-
|
2711
|
+
// Boolean - Whether the scale should begin at zero
|
2712
|
+
scaleBeginAtZero : true,
|
1288
2713
|
|
1289
|
-
|
1290
|
-
|
1291
|
-
var upperValue = Number.MIN_VALUE;
|
1292
|
-
var lowerValue = Number.MAX_VALUE;
|
1293
|
-
for (var i=0; i<data.datasets.length; i++){
|
1294
|
-
for (var j=0; j<data.datasets[i].data.length; j++){
|
1295
|
-
if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] };
|
1296
|
-
if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] };
|
1297
|
-
}
|
1298
|
-
};
|
2714
|
+
//Number - The backdrop padding above & below the label in pixels
|
2715
|
+
scaleBackdropPaddingY : 2,
|
1299
2716
|
|
1300
|
-
|
1301
|
-
|
2717
|
+
//Number - The backdrop padding to the side of the label in pixels
|
2718
|
+
scaleBackdropPaddingX : 2,
|
1302
2719
|
|
1303
|
-
|
1304
|
-
|
1305
|
-
minValue : lowerValue,
|
1306
|
-
maxSteps : maxSteps,
|
1307
|
-
minSteps : minSteps
|
1308
|
-
};
|
2720
|
+
//Boolean - Show line for each value in the scale
|
2721
|
+
scaleShowLine : true,
|
1309
2722
|
|
2723
|
+
//Boolean - Stroke a line around each segment in the chart
|
2724
|
+
segmentShowStroke : true,
|
1310
2725
|
|
1311
|
-
|
2726
|
+
//String - The colour of the stroke on each segement.
|
2727
|
+
segmentStrokeColor : "rgba(255,255,255,1)",
|
1312
2728
|
|
2729
|
+
//Number - The width of the stroke value in pixels
|
2730
|
+
segmentStrokeWidth : 2,
|
1313
2731
|
|
1314
|
-
|
2732
|
+
//Number - Amount of animation steps
|
2733
|
+
animationSteps : 100,
|
1315
2734
|
|
1316
|
-
|
1317
|
-
|
2735
|
+
//String - Animation easing effect.
|
2736
|
+
animationEasing : "easeOutBounce",
|
1318
2737
|
|
1319
|
-
|
2738
|
+
//Boolean - Whether to animate the rotation of the chart
|
2739
|
+
animateRotate : true,
|
1320
2740
|
|
1321
|
-
|
1322
|
-
|
1323
|
-
labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : "";
|
1324
|
-
if (!config.scaleOverride){
|
2741
|
+
//Boolean - Whether to animate scaling the chart from the centre
|
2742
|
+
animateScale : false,
|
1325
2743
|
|
1326
|
-
|
1327
|
-
}
|
1328
|
-
|
1329
|
-
calculatedScale = {
|
1330
|
-
steps : config.scaleSteps,
|
1331
|
-
stepValue : config.scaleStepWidth,
|
1332
|
-
graphMin : config.scaleStartValue,
|
1333
|
-
labels : []
|
1334
|
-
}
|
1335
|
-
populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
|
1336
|
-
}
|
2744
|
+
//String - A legend template
|
2745
|
+
legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"
|
2746
|
+
};
|
1337
2747
|
|
1338
|
-
scaleHop = Math.floor(scaleHeight/calculatedScale.steps);
|
1339
|
-
calculateXAxisSize();
|
1340
|
-
animationLoop(config,drawScale,drawBars,ctx);
|
1341
|
-
|
1342
|
-
function drawBars(animPc){
|
1343
|
-
ctx.lineWidth = config.barStrokeWidth;
|
1344
|
-
for (var i=0; i<data.datasets.length; i++){
|
1345
|
-
for (var j=0; j<data.datasets[i].data.length; j++){
|
1346
|
-
var barOffset = yAxisPosX + config.barValueSpacing + valueHop*j + barWidth*i + config.barDatasetSpacing*i + config.barStrokeWidth*i;
|
1347
|
-
ctx.fillStyle = cycleColor(data.datasets[i].fillColor, j);
|
1348
|
-
ctx.strokeStyle = cycleColor(data.datasets[i].strokeColor, j);
|
1349
|
-
ctx.beginPath();
|
1350
|
-
ctx.moveTo(barOffset, xAxisPosY);
|
1351
|
-
ctx.lineTo(barOffset, xAxisPosY - animPc*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2));
|
1352
|
-
ctx.lineTo(barOffset + barWidth, xAxisPosY - animPc*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2));
|
1353
|
-
ctx.lineTo(barOffset + barWidth, xAxisPosY);
|
1354
|
-
if(config.barShowStroke){
|
1355
|
-
ctx.stroke();
|
1356
|
-
}
|
1357
|
-
ctx.closePath();
|
1358
|
-
ctx.fill();
|
1359
2748
|
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
1367
|
-
|
1368
|
-
|
2749
|
+
Chart.Type.extend({
|
2750
|
+
//Passing in a name registers this chart in the Chart namespace
|
2751
|
+
name: "PolarArea",
|
2752
|
+
//Providing a defaults will also register the deafults in the chart namespace
|
2753
|
+
defaults : defaultConfig,
|
2754
|
+
//Initialize is fired when the chart is initialized - Data is passed in as a parameter
|
2755
|
+
//Config is automatically merged by the core of Chart.js, and is available at this.options
|
2756
|
+
initialize: function(data){
|
2757
|
+
this.segments = [];
|
2758
|
+
//Declare segment class as a chart instance specific class, so it can share props for this instance
|
2759
|
+
this.SegmentArc = Chart.Arc.extend({
|
2760
|
+
showStroke : this.options.segmentShowStroke,
|
2761
|
+
strokeWidth : this.options.segmentStrokeWidth,
|
2762
|
+
strokeColor : this.options.segmentStrokeColor,
|
2763
|
+
ctx : this.chart.ctx,
|
2764
|
+
innerRadius : 0,
|
2765
|
+
x : this.chart.width/2,
|
2766
|
+
y : this.chart.height/2
|
2767
|
+
});
|
2768
|
+
this.scale = new Chart.RadialScale({
|
2769
|
+
display: this.options.showScale,
|
2770
|
+
fontStyle: this.options.scaleFontStyle,
|
2771
|
+
fontSize: this.options.scaleFontSize,
|
2772
|
+
fontFamily: this.options.scaleFontFamily,
|
2773
|
+
fontColor: this.options.scaleFontColor,
|
2774
|
+
showLabels: this.options.scaleShowLabels,
|
2775
|
+
showLabelBackdrop: this.options.scaleShowLabelBackdrop,
|
2776
|
+
backdropColor: this.options.scaleBackdropColor,
|
2777
|
+
backdropPaddingY : this.options.scaleBackdropPaddingY,
|
2778
|
+
backdropPaddingX: this.options.scaleBackdropPaddingX,
|
2779
|
+
lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
|
2780
|
+
lineColor: this.options.scaleLineColor,
|
2781
|
+
lineArc: true,
|
2782
|
+
width: this.chart.width,
|
2783
|
+
height: this.chart.height,
|
2784
|
+
xCenter: this.chart.width/2,
|
2785
|
+
yCenter: this.chart.height/2,
|
2786
|
+
ctx : this.chart.ctx,
|
2787
|
+
templateString: this.options.scaleLabel,
|
2788
|
+
valuesCount: data.length
|
2789
|
+
});
|
2790
|
+
|
2791
|
+
this.updateScaleRange(data);
|
2792
|
+
|
2793
|
+
this.scale.update();
|
2794
|
+
|
2795
|
+
helpers.each(data,function(segment,index){
|
2796
|
+
this.addData(segment,index,true);
|
2797
|
+
},this);
|
2798
|
+
|
2799
|
+
//Set up tooltip events on the chart
|
2800
|
+
if (this.options.showTooltips){
|
2801
|
+
helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
|
2802
|
+
var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
|
2803
|
+
helpers.each(this.segments,function(segment){
|
2804
|
+
segment.restore(["fillColor"]);
|
2805
|
+
});
|
2806
|
+
helpers.each(activeSegments,function(activeSegment){
|
2807
|
+
activeSegment.fillColor = activeSegment.highlightColor;
|
2808
|
+
});
|
2809
|
+
this.showTooltip(activeSegments);
|
2810
|
+
});
|
1369
2811
|
}
|
1370
2812
|
|
1371
|
-
|
1372
|
-
|
1373
|
-
|
1374
|
-
|
1375
|
-
ctx.strokeStyle = config.scaleLineColor;
|
1376
|
-
ctx.beginPath();
|
1377
|
-
ctx.moveTo(width-widestXLabel/2+5,xAxisPosY);
|
1378
|
-
ctx.lineTo(width-(widestXLabel/2)-xAxisLength-5,xAxisPosY);
|
1379
|
-
ctx.stroke();
|
2813
|
+
this.render();
|
2814
|
+
},
|
2815
|
+
getSegmentsAtEvent : function(e){
|
2816
|
+
var segmentsArray = [];
|
1380
2817
|
|
2818
|
+
var location = helpers.getRelativePosition(e);
|
1381
2819
|
|
1382
|
-
|
1383
|
-
|
1384
|
-
|
1385
|
-
|
1386
|
-
|
1387
|
-
|
2820
|
+
helpers.each(this.segments,function(segment){
|
2821
|
+
if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
|
2822
|
+
},this);
|
2823
|
+
return segmentsArray;
|
2824
|
+
},
|
2825
|
+
addData : function(segment, atIndex, silent){
|
2826
|
+
var index = atIndex || this.segments.length;
|
2827
|
+
|
2828
|
+
this.segments.splice(index, 0, new this.SegmentArc({
|
2829
|
+
fillColor: segment.color,
|
2830
|
+
highlightColor: segment.highlight || segment.color,
|
2831
|
+
label: segment.label,
|
2832
|
+
value: segment.value,
|
2833
|
+
outerRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value),
|
2834
|
+
circumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(),
|
2835
|
+
startAngle: Math.PI * 1.5
|
2836
|
+
}));
|
2837
|
+
if (!silent){
|
2838
|
+
this.reflow();
|
2839
|
+
this.update();
|
1388
2840
|
}
|
1389
|
-
|
1390
|
-
|
1391
|
-
|
1392
|
-
|
1393
|
-
|
1394
|
-
|
1395
|
-
|
1396
|
-
|
1397
|
-
|
1398
|
-
|
1399
|
-
|
1400
|
-
|
2841
|
+
},
|
2842
|
+
removeData: function(atIndex){
|
2843
|
+
var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
|
2844
|
+
this.segments.splice(indexToDelete, 1);
|
2845
|
+
this.reflow();
|
2846
|
+
this.update();
|
2847
|
+
},
|
2848
|
+
calculateTotal: function(data){
|
2849
|
+
this.total = 0;
|
2850
|
+
helpers.each(data,function(segment){
|
2851
|
+
this.total += segment.value;
|
2852
|
+
},this);
|
2853
|
+
this.scale.valuesCount = this.segments.length;
|
2854
|
+
},
|
2855
|
+
updateScaleRange: function(datapoints){
|
2856
|
+
var valuesArray = [];
|
2857
|
+
helpers.each(datapoints,function(segment){
|
2858
|
+
valuesArray.push(segment.value);
|
2859
|
+
});
|
2860
|
+
|
2861
|
+
var scaleSizes = (this.options.scaleOverride) ?
|
2862
|
+
{
|
2863
|
+
steps: this.options.scaleSteps,
|
2864
|
+
stepValue: this.options.scaleStepWidth,
|
2865
|
+
min: this.options.scaleStartValue,
|
2866
|
+
max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
|
2867
|
+
} :
|
2868
|
+
helpers.calculateScaleRange(
|
2869
|
+
valuesArray,
|
2870
|
+
helpers.min([this.chart.width, this.chart.height])/2,
|
2871
|
+
this.options.scaleFontSize,
|
2872
|
+
this.options.scaleBeginAtZero,
|
2873
|
+
this.options.scaleIntegersOnly
|
2874
|
+
);
|
2875
|
+
|
2876
|
+
helpers.extend(
|
2877
|
+
this.scale,
|
2878
|
+
scaleSizes,
|
2879
|
+
{
|
2880
|
+
size: helpers.min([this.chart.width, this.chart.height]),
|
2881
|
+
xCenter: this.chart.width/2,
|
2882
|
+
yCenter: this.chart.height/2
|
1401
2883
|
}
|
2884
|
+
);
|
1402
2885
|
|
1403
|
-
|
1404
|
-
|
1405
|
-
|
1406
|
-
//Check i isnt 0, so we dont go over the Y axis twice.
|
1407
|
-
ctx.lineWidth = config.scaleGridLineWidth;
|
1408
|
-
ctx.strokeStyle = config.scaleGridLineColor;
|
1409
|
-
ctx.lineTo(yAxisPosX + (i+1) * valueHop, 5);
|
1410
|
-
ctx.stroke();
|
1411
|
-
}
|
2886
|
+
},
|
2887
|
+
update : function(){
|
2888
|
+
this.calculateTotal(this.segments);
|
1412
2889
|
|
1413
|
-
|
1414
|
-
|
1415
|
-
|
1416
|
-
|
1417
|
-
|
1418
|
-
|
1419
|
-
|
2890
|
+
helpers.each(this.segments,function(segment){
|
2891
|
+
segment.save();
|
2892
|
+
});
|
2893
|
+
this.render();
|
2894
|
+
},
|
2895
|
+
reflow : function(){
|
2896
|
+
helpers.extend(this.SegmentArc.prototype,{
|
2897
|
+
x : this.chart.width/2,
|
2898
|
+
y : this.chart.height/2
|
2899
|
+
});
|
2900
|
+
this.updateScaleRange(this.segments);
|
2901
|
+
this.scale.update();
|
2902
|
+
|
2903
|
+
helpers.extend(this.scale,{
|
2904
|
+
xCenter: this.chart.width/2,
|
2905
|
+
yCenter: this.chart.height/2
|
2906
|
+
});
|
2907
|
+
|
2908
|
+
helpers.each(this.segments, function(segment){
|
2909
|
+
segment.update({
|
2910
|
+
outerRadius : this.scale.calculateCenterOffset(segment.value)
|
2911
|
+
});
|
2912
|
+
}, this);
|
1420
2913
|
|
1421
|
-
|
1422
|
-
|
1423
|
-
|
1424
|
-
|
1425
|
-
|
1426
|
-
|
1427
|
-
|
1428
|
-
|
1429
|
-
|
1430
|
-
}
|
1431
|
-
|
1432
|
-
|
2914
|
+
},
|
2915
|
+
draw : function(ease){
|
2916
|
+
var easingDecimal = ease || 1;
|
2917
|
+
//Clear & draw the canvas
|
2918
|
+
this.clear();
|
2919
|
+
helpers.each(this.segments,function(segment, index){
|
2920
|
+
segment.transition({
|
2921
|
+
circumference : this.scale.getCircumference(),
|
2922
|
+
outerRadius : this.scale.calculateCenterOffset(segment.value)
|
2923
|
+
},easingDecimal);
|
2924
|
+
|
2925
|
+
segment.endAngle = segment.startAngle + segment.circumference;
|
2926
|
+
|
2927
|
+
// If we've removed the first segment we need to set the first one to
|
2928
|
+
// start at the top.
|
2929
|
+
if (index === 0){
|
2930
|
+
segment.startAngle = Math.PI * 1.5;
|
1433
2931
|
}
|
1434
2932
|
|
1435
|
-
|
1436
|
-
if (
|
1437
|
-
|
2933
|
+
//Check to see if it's the last segment, if not get the next and update the start angle
|
2934
|
+
if (index < this.segments.length - 1){
|
2935
|
+
this.segments[index+1].startAngle = segment.endAngle;
|
1438
2936
|
}
|
1439
|
-
|
2937
|
+
segment.draw();
|
2938
|
+
}, this);
|
2939
|
+
this.scale.draw();
|
2940
|
+
}
|
2941
|
+
});
|
1440
2942
|
|
2943
|
+
}).call(this);
|
2944
|
+
(function(){
|
2945
|
+
"use strict";
|
1441
2946
|
|
1442
|
-
|
1443
|
-
|
1444
|
-
|
1445
|
-
//if we are showing the labels
|
1446
|
-
if (config.scaleShowLabels){
|
1447
|
-
ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
|
1448
|
-
for (var i=0; i<calculatedScale.labels.length; i++){
|
1449
|
-
var measuredText = ctx.measureText(calculatedScale.labels[i]).width;
|
1450
|
-
longestText = (measuredText > longestText)? measuredText : longestText;
|
1451
|
-
}
|
1452
|
-
//Add a little extra padding from the y axis
|
1453
|
-
longestText +=10;
|
1454
|
-
}
|
1455
|
-
xAxisLength = width - longestText - widestXLabel;
|
1456
|
-
valueHop = Math.floor(xAxisLength/(data.labels.length));
|
2947
|
+
var root = this,
|
2948
|
+
Chart = root.Chart,
|
2949
|
+
helpers = Chart.helpers;
|
1457
2950
|
|
1458
|
-
barWidth = (valueHop - config.scaleGridLineWidth*2 - (config.barValueSpacing*2) - (config.barDatasetSpacing*data.datasets.length-1) - ((config.barStrokeWidth/2)*data.datasets.length-1))/data.datasets.length;
|
1459
2951
|
|
1460
|
-
yAxisPosX = width-widestXLabel/2-xAxisLength;
|
1461
|
-
xAxisPosY = scaleHeight + config.scaleFontSize/2;
|
1462
|
-
}
|
1463
|
-
function calculateDrawingSizes(){
|
1464
|
-
maxSize = height;
|
1465
|
-
|
1466
|
-
//Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees.
|
1467
|
-
ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
|
1468
|
-
widestXLabel = 1;
|
1469
|
-
for (var i=0; i<data.labels.length; i++){
|
1470
|
-
var textLength = ctx.measureText(data.labels[i]).width;
|
1471
|
-
//If the text length is longer - make that equal to longest text!
|
1472
|
-
widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel;
|
1473
|
-
}
|
1474
|
-
if (width/data.labels.length < widestXLabel){
|
1475
|
-
rotateLabels = 45;
|
1476
|
-
if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){
|
1477
|
-
rotateLabels = 90;
|
1478
|
-
maxSize -= widestXLabel;
|
1479
|
-
}
|
1480
|
-
else{
|
1481
|
-
maxSize -= Math.sin(rotateLabels) * widestXLabel;
|
1482
|
-
}
|
1483
|
-
}
|
1484
|
-
else{
|
1485
|
-
maxSize -= config.scaleFontSize;
|
1486
|
-
}
|
1487
2952
|
|
1488
|
-
|
1489
|
-
|
2953
|
+
Chart.Type.extend({
|
2954
|
+
name: "Radar",
|
2955
|
+
defaults:{
|
2956
|
+
//Boolean - Whether to show lines for each scale point
|
2957
|
+
scaleShowLine : true,
|
1490
2958
|
|
2959
|
+
//Boolean - Whether we show the angle lines out of the radar
|
2960
|
+
angleShowLineOut : true,
|
1491
2961
|
|
1492
|
-
|
2962
|
+
//Boolean - Whether to show labels on the scale
|
2963
|
+
scaleShowLabels : false,
|
1493
2964
|
|
1494
|
-
|
1495
|
-
|
2965
|
+
// Boolean - Whether the scale should begin at zero
|
2966
|
+
scaleBeginAtZero : true,
|
1496
2967
|
|
1497
|
-
|
2968
|
+
//String - Colour of the angle line
|
2969
|
+
angleLineColor : "rgba(0,0,0,.1)",
|
1498
2970
|
|
1499
|
-
//
|
2971
|
+
//Number - Pixel width of the angle line
|
2972
|
+
angleLineWidth : 1,
|
1500
2973
|
|
1501
|
-
|
1502
|
-
|
1503
|
-
var upperValue = Number.MIN_VALUE;
|
1504
|
-
var lowerValue = Number.MAX_VALUE;
|
1505
|
-
for (var i=0; i<data.datasets.length; i++){
|
1506
|
-
for (var j=0; j<data.datasets[i].data.length; j++){
|
1507
|
-
if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] };
|
1508
|
-
if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] };
|
1509
|
-
}
|
1510
|
-
};
|
2974
|
+
//String - Point label font declaration
|
2975
|
+
pointLabelFontFamily : "'Gotham', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
|
1511
2976
|
|
1512
|
-
|
1513
|
-
|
2977
|
+
//String - Point label font weight
|
2978
|
+
pointLabelFontStyle : "normal",
|
1514
2979
|
|
1515
|
-
|
1516
|
-
|
1517
|
-
minValue : lowerValue,
|
1518
|
-
maxSteps : maxSteps,
|
1519
|
-
minSteps : minSteps
|
1520
|
-
};
|
2980
|
+
//Number - Point label font size in pixels
|
2981
|
+
pointLabelFontSize : 11,
|
1521
2982
|
|
2983
|
+
//String - Point label font colour
|
2984
|
+
pointLabelFontColor : "rgba(158,171,179,1)",
|
1522
2985
|
|
1523
|
-
|
1524
|
-
|
2986
|
+
//Boolean - Whether to show a dot for each point
|
2987
|
+
pointDot : true,
|
1525
2988
|
|
1526
|
-
|
1527
|
-
|
1528
|
-
|
1529
|
-
|
1530
|
-
|
1531
|
-
|
2989
|
+
//Number - Radius of each point dot in pixels
|
2990
|
+
pointDotRadius : 3,
|
2991
|
+
|
2992
|
+
//Number - Pixel width of point dot stroke
|
2993
|
+
pointDotStrokeWidth : 1,
|
2994
|
+
|
2995
|
+
//Number - amount extra to add to the radius to cater for hit detection outside the drawn point
|
2996
|
+
pointHitDetectionRadius : 20,
|
1532
2997
|
|
1533
|
-
|
1534
|
-
|
1535
|
-
easingFunction = animationOptions[config.animationEasing],
|
1536
|
-
percentAnimComplete =(config.animation)? 0 : 1;
|
2998
|
+
//Boolean - Whether to show a stroke for datasets
|
2999
|
+
datasetStroke : true,
|
1537
3000
|
|
3001
|
+
//Number - Pixel width of dataset stroke
|
3002
|
+
datasetStrokeWidth : 1,
|
1538
3003
|
|
3004
|
+
//Boolean - Whether to fill the dataset with a colour
|
3005
|
+
datasetFill : true,
|
1539
3006
|
|
1540
|
-
|
3007
|
+
//String - A legend template
|
3008
|
+
legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
|
1541
3009
|
|
1542
|
-
|
3010
|
+
},
|
1543
3011
|
|
1544
|
-
function
|
1545
|
-
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
}
|
1551
|
-
|
1552
|
-
|
3012
|
+
initialize: function(data){
|
3013
|
+
this.PointClass = Chart.Point.extend({
|
3014
|
+
strokeWidth : this.options.pointDotStrokeWidth,
|
3015
|
+
radius : this.options.pointDotRadius,
|
3016
|
+
hitDetectionRadius : this.options.pointHitDetectionRadius,
|
3017
|
+
ctx : this.chart.ctx
|
3018
|
+
});
|
3019
|
+
|
3020
|
+
this.datasets = [];
|
3021
|
+
|
3022
|
+
this.buildScale(data);
|
3023
|
+
|
3024
|
+
//Set up tooltip events on the chart
|
3025
|
+
if (this.options.showTooltips){
|
3026
|
+
helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
|
3027
|
+
var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
|
3028
|
+
|
3029
|
+
this.eachPoints(function(point){
|
3030
|
+
point.restore(['fillColor', 'strokeColor']);
|
3031
|
+
});
|
3032
|
+
helpers.each(activePointsCollection, function(activePoint){
|
3033
|
+
activePoint.fillColor = activePoint.highlightFill;
|
3034
|
+
activePoint.strokeColor = activePoint.highlightStroke;
|
3035
|
+
});
|
3036
|
+
|
3037
|
+
this.showTooltip(activePointsCollection);
|
3038
|
+
});
|
1553
3039
|
}
|
1554
|
-
}
|
1555
|
-
function animLoop(){
|
1556
|
-
//We need to check if the animation is incomplete (less than 1), or complete (1).
|
1557
|
-
percentAnimComplete += animFrameAmount;
|
1558
|
-
animateFrame();
|
1559
|
-
//Stop the loop continuing forever
|
1560
|
-
if (percentAnimComplete <= 1){
|
1561
|
-
requestAnimFrame(animLoop);
|
1562
|
-
}
|
1563
|
-
else{
|
1564
|
-
if (typeof config.onAnimationComplete == "function") config.onAnimationComplete();
|
1565
|
-
}
|
1566
3040
|
|
1567
|
-
|
3041
|
+
//Iterate through each of the datasets, and build this into a property of the chart
|
3042
|
+
helpers.each(data.datasets,function(dataset){
|
3043
|
+
|
3044
|
+
var datasetObject = {
|
3045
|
+
label: dataset.label || null,
|
3046
|
+
fillColor : dataset.fillColor,
|
3047
|
+
strokeColor : dataset.strokeColor,
|
3048
|
+
pointColor : dataset.pointColor,
|
3049
|
+
pointStrokeColor : dataset.pointStrokeColor,
|
3050
|
+
points : []
|
3051
|
+
};
|
3052
|
+
|
3053
|
+
this.datasets.push(datasetObject);
|
3054
|
+
|
3055
|
+
helpers.each(dataset.data,function(dataPoint,index){
|
3056
|
+
//Best way to do this? or in draw sequence...?
|
3057
|
+
if (helpers.isNumber(dataPoint)){
|
3058
|
+
//Add a new point for each piece of data, passing any required data to draw.
|
3059
|
+
var pointPosition;
|
3060
|
+
if (!this.scale.animation){
|
3061
|
+
pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint));
|
3062
|
+
}
|
3063
|
+
datasetObject.points.push(new this.PointClass({
|
3064
|
+
value : dataPoint,
|
3065
|
+
label : data.labels[index],
|
3066
|
+
x: (this.options.animation) ? this.scale.xCenter : pointPosition.x,
|
3067
|
+
y: (this.options.animation) ? this.scale.yCenter : pointPosition.y,
|
3068
|
+
strokeColor : dataset.pointStrokeColor,
|
3069
|
+
fillColor : dataset.pointColor,
|
3070
|
+
highlightFill : dataset.pointHighlightFill || dataset.pointColor,
|
3071
|
+
highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
|
3072
|
+
}));
|
3073
|
+
}
|
3074
|
+
},this);
|
1568
3075
|
|
1569
|
-
|
3076
|
+
},this);
|
1570
3077
|
|
1571
|
-
|
3078
|
+
this.render();
|
3079
|
+
},
|
3080
|
+
eachPoints : function(callback){
|
3081
|
+
helpers.each(this.datasets,function(dataset){
|
3082
|
+
helpers.each(dataset.points,callback,this);
|
3083
|
+
},this);
|
3084
|
+
},
|
1572
3085
|
|
3086
|
+
getPointsAtEvent : function(evt){
|
3087
|
+
var mousePosition = helpers.getRelativePosition(evt),
|
3088
|
+
fromCenter = helpers.getAngleFromPoint({
|
3089
|
+
x: this.scale.xCenter,
|
3090
|
+
y: this.scale.yCenter
|
3091
|
+
}, mousePosition);
|
1573
3092
|
|
1574
|
-
|
1575
|
-
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
1579
|
-
|
1580
|
-
|
1581
|
-
function(callback) {
|
1582
|
-
window.setTimeout(callback, 1000 / 60);
|
1583
|
-
};
|
1584
|
-
})();
|
1585
|
-
|
1586
|
-
function calculateScale(drawingHeight,maxSteps,minSteps,maxValue,minValue,labelTemplateString){
|
1587
|
-
var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum;
|
1588
|
-
valueRange = maxValue - minValue;
|
1589
|
-
rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange);
|
1590
|
-
graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);
|
1591
|
-
graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);
|
1592
|
-
graphRange = graphMax - graphMin;
|
1593
|
-
stepValue = Math.pow(10, rangeOrderOfMagnitude);
|
1594
|
-
numberOfSteps = Math.round(graphRange / stepValue);
|
1595
|
-
|
1596
|
-
//Compare number of steps to the max and min for that size graph, and add in half steps if need be.
|
1597
|
-
while(numberOfSteps < minSteps || numberOfSteps > maxSteps) {
|
1598
|
-
if (numberOfSteps < minSteps){
|
1599
|
-
stepValue /= 2;
|
1600
|
-
numberOfSteps = Math.round(graphRange/stepValue);
|
3093
|
+
var anglePerIndex = (Math.PI * 2) /this.scale.valuesCount,
|
3094
|
+
pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex),
|
3095
|
+
activePointsCollection = [];
|
3096
|
+
|
3097
|
+
// If we're at the top, make the pointIndex 0 to get the first of the array.
|
3098
|
+
if (pointIndex >= this.scale.valuesCount || pointIndex < 0){
|
3099
|
+
pointIndex = 0;
|
1601
3100
|
}
|
1602
|
-
|
1603
|
-
|
1604
|
-
|
3101
|
+
|
3102
|
+
if (fromCenter.distance <= this.scale.drawingArea){
|
3103
|
+
helpers.each(this.datasets, function(dataset){
|
3104
|
+
activePointsCollection.push(dataset.points[pointIndex]);
|
3105
|
+
});
|
1605
3106
|
}
|
1606
|
-
}
|
1607
3107
|
|
1608
|
-
|
1609
|
-
|
3108
|
+
return activePointsCollection;
|
3109
|
+
},
|
1610
3110
|
|
1611
|
-
|
1612
|
-
|
1613
|
-
|
1614
|
-
|
1615
|
-
|
1616
|
-
|
3111
|
+
buildScale : function(data){
|
3112
|
+
this.scale = new Chart.RadialScale({
|
3113
|
+
display: this.options.showScale,
|
3114
|
+
fontStyle: this.options.scaleFontStyle,
|
3115
|
+
fontSize: this.options.scaleFontSize,
|
3116
|
+
fontFamily: this.options.scaleFontFamily,
|
3117
|
+
fontColor: this.options.scaleFontColor,
|
3118
|
+
showLabels: this.options.scaleShowLabels,
|
3119
|
+
showLabelBackdrop: this.options.scaleShowLabelBackdrop,
|
3120
|
+
backdropColor: this.options.scaleBackdropColor,
|
3121
|
+
backdropPaddingY : this.options.scaleBackdropPaddingY,
|
3122
|
+
backdropPaddingX: this.options.scaleBackdropPaddingX,
|
3123
|
+
lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
|
3124
|
+
lineColor: this.options.scaleLineColor,
|
3125
|
+
angleLineColor : this.options.angleLineColor,
|
3126
|
+
angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0,
|
3127
|
+
// Point labels at the edge of each line
|
3128
|
+
pointLabelFontColor : this.options.pointLabelFontColor,
|
3129
|
+
pointLabelFontSize : this.options.pointLabelFontSize,
|
3130
|
+
pointLabelFontFamily : this.options.pointLabelFontFamily,
|
3131
|
+
pointLabelFontStyle : this.options.pointLabelFontStyle,
|
3132
|
+
height : this.chart.height,
|
3133
|
+
width: this.chart.width,
|
3134
|
+
xCenter: this.chart.width/2,
|
3135
|
+
yCenter: this.chart.height/2,
|
3136
|
+
ctx : this.chart.ctx,
|
3137
|
+
templateString: this.options.scaleLabel,
|
3138
|
+
labels: data.labels,
|
3139
|
+
valuesCount: data.datasets[0].data.length
|
3140
|
+
});
|
3141
|
+
|
3142
|
+
this.scale.setScaleSize();
|
3143
|
+
this.updateScaleRange(data.datasets);
|
3144
|
+
this.scale.buildYLabels();
|
3145
|
+
},
|
3146
|
+
updateScaleRange: function(datasets){
|
3147
|
+
var valuesArray = (function(){
|
3148
|
+
var totalDataArray = [];
|
3149
|
+
helpers.each(datasets,function(dataset){
|
3150
|
+
if (dataset.data){
|
3151
|
+
totalDataArray = totalDataArray.concat(dataset.data);
|
3152
|
+
}
|
3153
|
+
else {
|
3154
|
+
helpers.each(dataset.points, function(point){
|
3155
|
+
totalDataArray.push(point.value);
|
3156
|
+
});
|
3157
|
+
}
|
3158
|
+
});
|
3159
|
+
return totalDataArray;
|
3160
|
+
})();
|
3161
|
+
|
3162
|
+
|
3163
|
+
var scaleSizes = (this.options.scaleOverride) ?
|
3164
|
+
{
|
3165
|
+
steps: this.options.scaleSteps,
|
3166
|
+
stepValue: this.options.scaleStepWidth,
|
3167
|
+
min: this.options.scaleStartValue,
|
3168
|
+
max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
|
3169
|
+
} :
|
3170
|
+
helpers.calculateScaleRange(
|
3171
|
+
valuesArray,
|
3172
|
+
helpers.min([this.chart.width, this.chart.height])/2,
|
3173
|
+
this.options.scaleFontSize,
|
3174
|
+
this.options.scaleBeginAtZero,
|
3175
|
+
this.options.scaleIntegersOnly
|
3176
|
+
);
|
3177
|
+
|
3178
|
+
helpers.extend(
|
3179
|
+
this.scale,
|
3180
|
+
scaleSizes
|
3181
|
+
);
|
1617
3182
|
|
1618
|
-
|
1619
|
-
|
1620
|
-
|
1621
|
-
|
3183
|
+
},
|
3184
|
+
addData : function(valuesArray,label){
|
3185
|
+
//Map the values array for each of the datasets
|
3186
|
+
this.scale.valuesCount++;
|
3187
|
+
helpers.each(valuesArray,function(value,datasetIndex){
|
3188
|
+
if (helpers.isNumber(value)){
|
3189
|
+
var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value));
|
3190
|
+
this.datasets[datasetIndex].points.push(new this.PointClass({
|
3191
|
+
value : value,
|
3192
|
+
label : label,
|
3193
|
+
x: pointPosition.x,
|
3194
|
+
y: pointPosition.y,
|
3195
|
+
strokeColor : this.datasets[datasetIndex].pointStrokeColor,
|
3196
|
+
fillColor : this.datasets[datasetIndex].pointColor
|
3197
|
+
}));
|
3198
|
+
}
|
3199
|
+
},this);
|
1622
3200
|
|
1623
|
-
|
1624
|
-
function populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue) {
|
1625
|
-
if (labelTemplateString) {
|
1626
|
-
//Fix floating point errors by setting to fixed the on the same decimal as the stepValue.
|
1627
|
-
for (var i = 1; i < numberOfSteps + 1; i++) {
|
1628
|
-
labels.push(tmpl(labelTemplateString, {value: (graphMin + (stepValue * i)).toFixed(getDecimalPlaces(stepValue))}));
|
1629
|
-
}
|
1630
|
-
}
|
1631
|
-
}
|
1632
|
-
// Cycle a given array of colours (for multi coloured bars in bargraphs)
|
1633
|
-
function cycleColor(colors, i) {
|
1634
|
-
return (colors && colors.constructor.name == "Array") ? colors[i % colors.length] : colors;
|
1635
|
-
}
|
1636
|
-
//Max value from array
|
1637
|
-
function Max( array ){
|
1638
|
-
return Math.max.apply( Math, array );
|
1639
|
-
};
|
1640
|
-
//Min value from array
|
1641
|
-
function Min( array ){
|
1642
|
-
return Math.min.apply( Math, array );
|
1643
|
-
};
|
1644
|
-
//Default if undefined
|
1645
|
-
function Default(userDeclared,valueIfFalse){
|
1646
|
-
if(!userDeclared){
|
1647
|
-
return valueIfFalse;
|
1648
|
-
} else {
|
1649
|
-
return userDeclared;
|
1650
|
-
}
|
1651
|
-
};
|
1652
|
-
//Is a number function
|
1653
|
-
function isNumber(n) {
|
1654
|
-
return !isNaN(parseFloat(n)) && isFinite(n);
|
1655
|
-
}
|
1656
|
-
//Apply cap a value at a high or low number
|
1657
|
-
function CapValue(valueToCap, maxValue, minValue){
|
1658
|
-
if(isNumber(maxValue)) {
|
1659
|
-
if( valueToCap > maxValue ) {
|
1660
|
-
return maxValue;
|
1661
|
-
}
|
1662
|
-
}
|
1663
|
-
if(isNumber(minValue)){
|
1664
|
-
if ( valueToCap < minValue ){
|
1665
|
-
return minValue;
|
1666
|
-
}
|
1667
|
-
}
|
1668
|
-
return valueToCap;
|
1669
|
-
}
|
1670
|
-
function getDecimalPlaces (num){
|
1671
|
-
var numberOfDecimalPlaces;
|
1672
|
-
if (num%1!=0){
|
1673
|
-
return num.toString().split(".")[1].length
|
1674
|
-
}
|
1675
|
-
else{
|
1676
|
-
return 0;
|
1677
|
-
}
|
3201
|
+
this.scale.labels.push(label);
|
1678
3202
|
|
1679
|
-
|
3203
|
+
this.reflow();
|
3204
|
+
|
3205
|
+
this.update();
|
3206
|
+
},
|
3207
|
+
removeData : function(){
|
3208
|
+
this.scale.valuesCount--;
|
3209
|
+
this.scale.labels.shift();
|
3210
|
+
helpers.each(this.datasets,function(dataset){
|
3211
|
+
dataset.points.shift();
|
3212
|
+
},this);
|
3213
|
+
this.reflow();
|
3214
|
+
this.update();
|
3215
|
+
},
|
3216
|
+
update : function(){
|
3217
|
+
this.eachPoints(function(point){
|
3218
|
+
point.save();
|
3219
|
+
});
|
3220
|
+
this.render();
|
3221
|
+
},
|
3222
|
+
reflow: function(){
|
3223
|
+
helpers.extend(this.scale, {
|
3224
|
+
width : this.chart.width,
|
3225
|
+
height: this.chart.height,
|
3226
|
+
size : helpers.min([this.chart.width, this.chart.height]),
|
3227
|
+
xCenter: this.chart.width/2,
|
3228
|
+
yCenter: this.chart.height/2
|
3229
|
+
});
|
3230
|
+
this.updateScaleRange(this.datasets);
|
3231
|
+
this.scale.setScaleSize();
|
3232
|
+
this.scale.buildYLabels();
|
3233
|
+
},
|
3234
|
+
draw : function(ease){
|
3235
|
+
var easeDecimal = ease || 1,
|
3236
|
+
ctx = this.chart.ctx;
|
3237
|
+
this.clear();
|
3238
|
+
this.scale.draw();
|
3239
|
+
|
3240
|
+
helpers.each(this.datasets,function(dataset){
|
3241
|
+
|
3242
|
+
//Transition each point first so that the line and point drawing isn't out of sync
|
3243
|
+
helpers.each(dataset.points,function(point,index){
|
3244
|
+
point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal);
|
3245
|
+
},this);
|
3246
|
+
|
3247
|
+
|
3248
|
+
|
3249
|
+
//Draw the line between all the points
|
3250
|
+
ctx.lineWidth = this.options.datasetStrokeWidth;
|
3251
|
+
ctx.strokeStyle = dataset.strokeColor;
|
3252
|
+
ctx.beginPath();
|
3253
|
+
helpers.each(dataset.points,function(point,index){
|
3254
|
+
if (index === 0){
|
3255
|
+
ctx.moveTo(point.x,point.y);
|
3256
|
+
}
|
3257
|
+
else{
|
3258
|
+
ctx.lineTo(point.x,point.y);
|
3259
|
+
}
|
3260
|
+
},this);
|
3261
|
+
ctx.closePath();
|
3262
|
+
ctx.stroke();
|
3263
|
+
|
3264
|
+
ctx.fillStyle = dataset.fillColor;
|
3265
|
+
ctx.fill();
|
3266
|
+
|
3267
|
+
//Now draw the points over the line
|
3268
|
+
//A little inefficient double looping, but better than the line
|
3269
|
+
//lagging behind the point positions
|
3270
|
+
helpers.each(dataset.points,function(point){
|
3271
|
+
point.draw();
|
3272
|
+
});
|
3273
|
+
|
3274
|
+
},this);
|
1680
3275
|
|
1681
|
-
function mergeChartConfig(defaults,userDefined){
|
1682
|
-
var returnObj = {};
|
1683
|
-
for (var attrname in defaults) { returnObj[attrname] = defaults[attrname]; }
|
1684
|
-
for (var attrname in userDefined) {
|
1685
|
-
if(typeof(userDefined[attrname]) === "object" && defaults[attrname]) {
|
1686
|
-
returnObj[attrname] = mergeChartConfig(defaults[attrname], userDefined[attrname]);
|
1687
|
-
} else {
|
1688
|
-
returnObj[attrname] = userDefined[attrname];
|
1689
|
-
}
|
1690
3276
|
}
|
1691
|
-
return returnObj;
|
1692
|
-
}
|
1693
3277
|
|
1694
|
-
|
1695
|
-
var cache = {};
|
1696
|
-
|
1697
|
-
function tmpl(str, data){
|
1698
|
-
// Figure out if we're getting a template, or if we need to
|
1699
|
-
// load the template - and be sure to cache the result.
|
1700
|
-
var fn = !/\W/.test(str) ?
|
1701
|
-
cache[str] = cache[str] ||
|
1702
|
-
tmpl(document.getElementById(str).innerHTML) :
|
1703
|
-
|
1704
|
-
// Generate a reusable function that will serve as a template
|
1705
|
-
// generator (and which will be cached).
|
1706
|
-
new Function("obj",
|
1707
|
-
"var p=[],print=function(){p.push.apply(p,arguments);};" +
|
1708
|
-
|
1709
|
-
// Introduce the data as local variables using with(){}
|
1710
|
-
"with(obj){p.push('" +
|
1711
|
-
|
1712
|
-
// Convert the template into pure JavaScript
|
1713
|
-
str
|
1714
|
-
.replace(/[\r\t\n]/g, " ")
|
1715
|
-
.split("<%").join("\t")
|
1716
|
-
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
|
1717
|
-
.replace(/\t=(.*?)%>/g, "',$1,'")
|
1718
|
-
.split("\t").join("');")
|
1719
|
-
.split("%>").join("p.push('")
|
1720
|
-
.split("\r").join("\\'")
|
1721
|
-
+ "');}return p.join('');");
|
1722
|
-
|
1723
|
-
// Provide some basic currying to the user
|
1724
|
-
return data ? fn( data ) : fn;
|
1725
|
-
};
|
3278
|
+
});
|
1726
3279
|
|
1727
|
-
|
1728
|
-
var pseudoEl = document.createElement('div'),
|
1729
|
-
rgbPrim,
|
1730
|
-
rgbSec;
|
1731
|
-
pseudoEl.style.color = primColor;
|
1732
|
-
document.body.appendChild(pseudoEl);
|
1733
|
-
rgbPrim = window.getComputedStyle(pseudoEl).color;
|
1734
|
-
pseudoEl.style.color = secColor;
|
1735
|
-
rgbSec = window.getComputedStyle(pseudoEl).color;
|
1736
|
-
var regex = /rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/,
|
1737
|
-
valuesP = regex.exec(rgbPrim),
|
1738
|
-
valuesS = regex.exec(rgbSec),
|
1739
|
-
rP = Math.round(parseFloat(valuesP[1])),
|
1740
|
-
gP = Math.round(parseFloat(valuesP[2])),
|
1741
|
-
bP = Math.round(parseFloat(valuesP[3])),
|
1742
|
-
rS = Math.round(parseFloat(valuesS[1])),
|
1743
|
-
gS = Math.round(parseFloat(valuesS[2])),
|
1744
|
-
bS = Math.round(parseFloat(valuesS[3])),
|
1745
|
-
rCur = parseInt((rP-rS)*percent+rS),
|
1746
|
-
gCur = parseInt((gP-gS)*percent+gS),
|
1747
|
-
bCur = parseInt((bP-bS)*percent+bS);
|
1748
|
-
pseudoEl.parentNode.removeChild(pseudoEl);
|
1749
|
-
return "rgb("+rCur+','+gCur+','+bCur+')';
|
1750
|
-
}
|
1751
|
-
}
|
3280
|
+
}).call(this);
|