Chart.Radar.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. (function(){
  2. "use strict";
  3. var root = this,
  4. Chart = root.Chart,
  5. helpers = Chart.helpers;
  6. Chart.Type.extend({
  7. name: "Radar",
  8. defaults:{
  9. //Boolean - Whether to show lines for each scale point
  10. scaleShowLine : true,
  11. //Boolean - Whether we show the angle lines out of the radar
  12. angleShowLineOut : true,
  13. //Boolean - Whether to show labels on the scale
  14. scaleShowLabels : false,
  15. // Boolean - Whether the scale should begin at zero
  16. scaleBeginAtZero : true,
  17. //String - Colour of the angle line
  18. angleLineColor : "rgba(0,0,0,.1)",
  19. //Number - Pixel width of the angle line
  20. angleLineWidth : 1,
  21. //String - Point label font declaration
  22. pointLabelFontFamily : "'Arial'",
  23. //String - Point label font weight
  24. pointLabelFontStyle : "normal",
  25. //Number - Point label font size in pixels
  26. pointLabelFontSize : 10,
  27. //String - Point label font colour
  28. pointLabelFontColor : "#666",
  29. //Boolean - Whether to show a dot for each point
  30. pointDot : true,
  31. //Number - Radius of each point dot in pixels
  32. pointDotRadius : 3,
  33. //Number - Pixel width of point dot stroke
  34. pointDotStrokeWidth : 1,
  35. //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
  36. pointHitDetectionRadius : 20,
  37. //Boolean - Whether to show a stroke for datasets
  38. datasetStroke : true,
  39. //Number - Pixel width of dataset stroke
  40. datasetStrokeWidth : 2,
  41. //Boolean - Whether to fill the dataset with a colour
  42. datasetFill : true,
  43. //String - A legend template
  44. 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>"
  45. },
  46. initialize: function(data){
  47. this.PointClass = Chart.Point.extend({
  48. strokeWidth : this.options.pointDotStrokeWidth,
  49. radius : this.options.pointDotRadius,
  50. display: this.options.pointDot,
  51. hitDetectionRadius : this.options.pointHitDetectionRadius,
  52. ctx : this.chart.ctx
  53. });
  54. this.datasets = [];
  55. this.buildScale(data);
  56. //Set up tooltip events on the chart
  57. if (this.options.showTooltips){
  58. helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
  59. var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
  60. this.eachPoints(function(point){
  61. point.restore(['fillColor', 'strokeColor']);
  62. });
  63. helpers.each(activePointsCollection, function(activePoint){
  64. activePoint.fillColor = activePoint.highlightFill;
  65. activePoint.strokeColor = activePoint.highlightStroke;
  66. });
  67. this.showTooltip(activePointsCollection);
  68. });
  69. }
  70. //Iterate through each of the datasets, and build this into a property of the chart
  71. helpers.each(data.datasets,function(dataset){
  72. var datasetObject = {
  73. label: dataset.label || null,
  74. fillColor : dataset.fillColor,
  75. strokeColor : dataset.strokeColor,
  76. pointColor : dataset.pointColor,
  77. pointStrokeColor : dataset.pointStrokeColor,
  78. points : []
  79. };
  80. this.datasets.push(datasetObject);
  81. helpers.each(dataset.data,function(dataPoint,index){
  82. //Add a new point for each piece of data, passing any required data to draw.
  83. var pointPosition;
  84. if (!this.scale.animation){
  85. pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint));
  86. }
  87. datasetObject.points.push(new this.PointClass({
  88. value : dataPoint,
  89. label : data.labels[index],
  90. datasetLabel: dataset.label,
  91. x: (this.options.animation) ? this.scale.xCenter : pointPosition.x,
  92. y: (this.options.animation) ? this.scale.yCenter : pointPosition.y,
  93. strokeColor : dataset.pointStrokeColor,
  94. fillColor : dataset.pointColor,
  95. highlightFill : dataset.pointHighlightFill || dataset.pointColor,
  96. highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
  97. }));
  98. },this);
  99. },this);
  100. this.render();
  101. },
  102. eachPoints : function(callback){
  103. helpers.each(this.datasets,function(dataset){
  104. helpers.each(dataset.points,callback,this);
  105. },this);
  106. },
  107. getPointsAtEvent : function(evt){
  108. var mousePosition = helpers.getRelativePosition(evt),
  109. fromCenter = helpers.getAngleFromPoint({
  110. x: this.scale.xCenter,
  111. y: this.scale.yCenter
  112. }, mousePosition);
  113. var anglePerIndex = (Math.PI * 2) /this.scale.valuesCount,
  114. pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex),
  115. activePointsCollection = [];
  116. // If we're at the top, make the pointIndex 0 to get the first of the array.
  117. if (pointIndex >= this.scale.valuesCount || pointIndex < 0){
  118. pointIndex = 0;
  119. }
  120. if (fromCenter.distance <= this.scale.drawingArea){
  121. helpers.each(this.datasets, function(dataset){
  122. activePointsCollection.push(dataset.points[pointIndex]);
  123. });
  124. }
  125. return activePointsCollection;
  126. },
  127. buildScale : function(data){
  128. this.scale = new Chart.RadialScale({
  129. display: this.options.showScale,
  130. fontStyle: this.options.scaleFontStyle,
  131. fontSize: this.options.scaleFontSize,
  132. fontFamily: this.options.scaleFontFamily,
  133. fontColor: this.options.scaleFontColor,
  134. showLabels: this.options.scaleShowLabels,
  135. showLabelBackdrop: this.options.scaleShowLabelBackdrop,
  136. backdropColor: this.options.scaleBackdropColor,
  137. backdropPaddingY : this.options.scaleBackdropPaddingY,
  138. backdropPaddingX: this.options.scaleBackdropPaddingX,
  139. lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
  140. lineColor: this.options.scaleLineColor,
  141. angleLineColor : this.options.angleLineColor,
  142. angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0,
  143. // Point labels at the edge of each line
  144. pointLabelFontColor : this.options.pointLabelFontColor,
  145. pointLabelFontSize : this.options.pointLabelFontSize,
  146. pointLabelFontFamily : this.options.pointLabelFontFamily,
  147. pointLabelFontStyle : this.options.pointLabelFontStyle,
  148. height : this.chart.height,
  149. width: this.chart.width,
  150. xCenter: this.chart.width/2,
  151. yCenter: this.chart.height/2,
  152. ctx : this.chart.ctx,
  153. templateString: this.options.scaleLabel,
  154. labels: data.labels,
  155. valuesCount: data.datasets[0].data.length
  156. });
  157. this.scale.setScaleSize();
  158. this.updateScaleRange(data.datasets);
  159. this.scale.buildYLabels();
  160. },
  161. updateScaleRange: function(datasets){
  162. var valuesArray = (function(){
  163. var totalDataArray = [];
  164. helpers.each(datasets,function(dataset){
  165. if (dataset.data){
  166. totalDataArray = totalDataArray.concat(dataset.data);
  167. }
  168. else {
  169. helpers.each(dataset.points, function(point){
  170. totalDataArray.push(point.value);
  171. });
  172. }
  173. });
  174. return totalDataArray;
  175. })();
  176. var scaleSizes = (this.options.scaleOverride) ?
  177. {
  178. steps: this.options.scaleSteps,
  179. stepValue: this.options.scaleStepWidth,
  180. min: this.options.scaleStartValue,
  181. max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
  182. } :
  183. helpers.calculateScaleRange(
  184. valuesArray,
  185. helpers.min([this.chart.width, this.chart.height])/2,
  186. this.options.scaleFontSize,
  187. this.options.scaleBeginAtZero,
  188. this.options.scaleIntegersOnly
  189. );
  190. helpers.extend(
  191. this.scale,
  192. scaleSizes
  193. );
  194. },
  195. addData : function(valuesArray,label){
  196. //Map the values array for each of the datasets
  197. this.scale.valuesCount++;
  198. helpers.each(valuesArray,function(value,datasetIndex){
  199. var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value));
  200. this.datasets[datasetIndex].points.push(new this.PointClass({
  201. value : value,
  202. label : label,
  203. x: pointPosition.x,
  204. y: pointPosition.y,
  205. strokeColor : this.datasets[datasetIndex].pointStrokeColor,
  206. fillColor : this.datasets[datasetIndex].pointColor
  207. }));
  208. },this);
  209. this.scale.labels.push(label);
  210. this.reflow();
  211. this.update();
  212. },
  213. removeData : function(){
  214. this.scale.valuesCount--;
  215. this.scale.labels.shift();
  216. helpers.each(this.datasets,function(dataset){
  217. dataset.points.shift();
  218. },this);
  219. this.reflow();
  220. this.update();
  221. },
  222. update : function(){
  223. this.eachPoints(function(point){
  224. point.save();
  225. });
  226. this.reflow();
  227. this.render();
  228. },
  229. reflow: function(){
  230. helpers.extend(this.scale, {
  231. width : this.chart.width,
  232. height: this.chart.height,
  233. size : helpers.min([this.chart.width, this.chart.height]),
  234. xCenter: this.chart.width/2,
  235. yCenter: this.chart.height/2
  236. });
  237. this.updateScaleRange(this.datasets);
  238. this.scale.setScaleSize();
  239. this.scale.buildYLabels();
  240. },
  241. draw : function(ease){
  242. var easeDecimal = ease || 1,
  243. ctx = this.chart.ctx;
  244. this.clear();
  245. this.scale.draw();
  246. helpers.each(this.datasets,function(dataset){
  247. //Transition each point first so that the line and point drawing isn't out of sync
  248. helpers.each(dataset.points,function(point,index){
  249. if (point.hasValue()){
  250. point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal);
  251. }
  252. },this);
  253. //Draw the line between all the points
  254. ctx.lineWidth = this.options.datasetStrokeWidth;
  255. ctx.strokeStyle = dataset.strokeColor;
  256. ctx.beginPath();
  257. helpers.each(dataset.points,function(point,index){
  258. if (index === 0){
  259. ctx.moveTo(point.x,point.y);
  260. }
  261. else{
  262. ctx.lineTo(point.x,point.y);
  263. }
  264. },this);
  265. ctx.closePath();
  266. ctx.stroke();
  267. ctx.fillStyle = dataset.fillColor;
  268. ctx.fill();
  269. //Now draw the points over the line
  270. //A little inefficient double looping, but better than the line
  271. //lagging behind the point positions
  272. helpers.each(dataset.points,function(point){
  273. if (point.hasValue()){
  274. point.draw();
  275. }
  276. });
  277. },this);
  278. }
  279. });
  280. }).call(this);