diff --git a/README.md b/README.md index 228485c..51a2ffa 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ rich interactive react charting components using [chart.js](http://www.chartjs.o * Polar area chart * Pie chart * Doughnut chart +* Doughnut chart with text inside [view chart examples](http://jhudson8.github.io/react-chartjs/index.html) diff --git a/dist/react-chartjs.js b/dist/react-chartjs.js index 17f2fd9..69561d1 100644 --- a/dist/react-chartjs.js +++ b/dist/react-chartjs.js @@ -57,10 +57,11 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = { Bar: __webpack_require__(1), Doughnut: __webpack_require__(6), - Line: __webpack_require__(7), - Pie: __webpack_require__(8), - PolarArea: __webpack_require__(9), - Radar: __webpack_require__(10), + DoughnutTextInside: __webpack_require__(7), + Line: __webpack_require__(8), + Pie: __webpack_require__(9), + PolarArea: __webpack_require__(10), + Radar: __webpack_require__(11), createClass: __webpack_require__(2).createClass }; @@ -137,6 +138,43 @@ return /******/ (function(modules) { // webpackBootstrap var Chart = __webpack_require__(5); var el = ReactDOM.findDOMNode(this); var ctx = el.getContext("2d"); + Chart.types.Doughnut.extend({ + name: "DoughnutTextInside", + showTooltip: function() { + this.chart.ctx.save(); + Chart.types.Doughnut.prototype.showTooltip.apply(this, arguments); + this.chart.ctx.restore(); + }, + draw: function() { + Chart.types.Doughnut.prototype.draw.apply(this,chart, arguments); + var width = this.chart.width, + height = this.chart.height; + + var fontSize = (height / 114).toFixed(2); + this.chart.ctx.fillStyle = '#000'; + this.chart.ctx.font = fontSize + "em Verdana"; + this.chart.ctx.textBaseline = "middle"; + + var total = 0; + var filled = 0; + var clone = this.segments.slice(); + clone.map(function(val) { + total = total + val.value; + }) + clone.pop(); + clone.map(function(val) { + filled = filled + val.value; + }) + + var text = Math.round((100 / total) * filled) + "%", + textX = Math.round((width - this.chart.ctx.measureText(text).width) / 2), + textY = height / 2; + + this.chart.ctx.fillText(text, textX, textY); + } + }); + + var chart = new Chart(ctx)[chartType](nextProps.data, nextProps.options || {}); this.state.chart = chart; }; @@ -184,8 +222,12 @@ return /******/ (function(modules) { // webpackBootstrap }); } }); + + while(nextProps.data.length < chart.segments.length) { + chart.removeData(); + } } else { - while (chart.scale.xLabels.length > nextProps.data.labels.length) { + while (chart.scale && chart.scale.xLabels.length > nextProps.data.labels.length) { chart.removeData(); } nextProps.data.datasets.forEach(function(set, setIndex) { @@ -242,7 +284,7 @@ return /******/ (function(modules) { // webpackBootstrap var vars = __webpack_require__(2); - module.exports = vars.createClass('Line', ['getPointsAtEvent']); + module.exports = vars.createClass('DoughnutTextInside', ['getSegmentsAtEvent']); /***/ }, @@ -251,7 +293,7 @@ return /******/ (function(modules) { // webpackBootstrap var vars = __webpack_require__(2); - module.exports = vars.createClass('Pie', ['getSegmentsAtEvent']); + module.exports = vars.createClass('Line', ['getPointsAtEvent']); /***/ }, @@ -260,7 +302,7 @@ return /******/ (function(modules) { // webpackBootstrap var vars = __webpack_require__(2); - module.exports = vars.createClass('PolarArea', ['getSegmentsAtEvent']); + module.exports = vars.createClass('Pie', ['getSegmentsAtEvent']); /***/ }, @@ -269,6 +311,15 @@ return /******/ (function(modules) { // webpackBootstrap var vars = __webpack_require__(2); + module.exports = vars.createClass('PolarArea', ['getSegmentsAtEvent']); + + +/***/ }, +/* 11 */ +/***/ function(module, exports, __webpack_require__) { + + var vars = __webpack_require__(2); + module.exports = vars.createClass('Radar', ['getPointsAtEvent']); diff --git a/dist/react-chartjs.min.js b/dist/react-chartjs.min.js index 3051eff..d3a77fc 100644 --- a/dist/react-chartjs.min.js +++ b/dist/react-chartjs.min.js @@ -1 +1 @@ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("react"),require("react-dom"),require("Chartjs")):"function"==typeof define&&define.amd?define(["react","react-dom","Chartjs"],e):"object"==typeof exports?exports["react-chartjs"]=e(require("react"),require("react-dom"),require("Chartjs")):t["react-chartjs"]=e(t.React,t.ReactDOM,t.Chart)}(this,function(t,e,a){return function(t){function e(r){if(a[r])return a[r].exports;var n=a[r]={exports:{},id:r,loaded:!1};return t[r].call(n.exports,n,n.exports,e),n.loaded=!0,n.exports}var a={};return e.m=t,e.c=a,e.p="",e(0)}([function(t,e,a){t.exports={Bar:a(1),Doughnut:a(6),Line:a(7),Pie:a(8),PolarArea:a(9),Radar:a(10),createClass:a(2).createClass}},function(t,e,a){var r=a(2);t.exports=r.createClass("Bar",["getBarsAtEvent"])},function(t,e,a){var r=a(3),n=a(4);t.exports={createClass:function(t,e,i){function c(t){u[t]=function(){return this.state.chart[t].apply(this.state.chart,arguments)}}var u={displayName:t+"Chart",getInitialState:function(){return{}},render:function(){var t={ref:"canvass"};for(var e in this.props)this.props.hasOwnProperty(e)&&"data"!==e&&"options"!==e&&(t[e]=this.props[e]);return r.createElement("canvas",t)}},f=["clear","stop","resize","toBase64Image","generateLegend","update","addData","removeData"];u.componentDidMount=function(){this.initializeChart(this.props)},u.componentWillUnmount=function(){var t=this.state.chart;t.destroy()},u.componentWillReceiveProps=function(t){var e=this.state.chart;t.redraw?(e.destroy(),this.initializeChart(t)):(i=i||s[e.name],o(t,e,i),e.scale&&(e.scale.xLabels=t.data.labels,e.scale.calculateXLabelRotation()),e.update())},u.initializeChart=function(e){var r=a(5),s=n.findDOMNode(this),o=s.getContext("2d"),i=new r(o)[t](e.data,e.options||{});this.state.chart=i},u.getChart=function(){return this.state.chart},u.getCanvass=function(){return this.refs.canvass},u.getCanvas=u.getCanvass;var l;for(l=0;lt.data.labels.length;)e.removeData();t.data.datasets.forEach(function(r,n){r.data.forEach(function(r,s){"undefined"==typeof e.datasets[n][a][s]?i(t,e,n,s):e.datasets[n][a][s].value=r})})}},i=function(t,e,a,r){var n=[];t.data.datasets.forEach(function(t){n.push(t.data[r])}),e.addData(n,t.data.labels[a])}},function(e,a){e.exports=t},function(t,a){t.exports=e},function(t,e){t.exports=a},function(t,e,a){var r=a(2);t.exports=r.createClass("Doughnut",["getSegmentsAtEvent"])},function(t,e,a){var r=a(2);t.exports=r.createClass("Line",["getPointsAtEvent"])},function(t,e,a){var r=a(2);t.exports=r.createClass("Pie",["getSegmentsAtEvent"])},function(t,e,a){var r=a(2);t.exports=r.createClass("PolarArea",["getSegmentsAtEvent"])},function(t,e,a){var r=a(2);t.exports=r.createClass("Radar",["getPointsAtEvent"])}])}); \ No newline at end of file +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("react"),require("react-dom"),require("Chartjs")):"function"==typeof define&&define.amd?define(["react","react-dom","Chartjs"],e):"object"==typeof exports?exports["react-chartjs"]=e(require("react"),require("react-dom"),require("Chartjs")):t["react-chartjs"]=e(t.React,t.ReactDOM,t.Chart)}(this,function(t,e,a){return function(t){function e(r){if(a[r])return a[r].exports;var n=a[r]={exports:{},id:r,loaded:!1};return t[r].call(n.exports,n,n.exports,e),n.loaded=!0,n.exports}var a={};return e.m=t,e.c=a,e.p="",e(0)}([function(t,e,a){t.exports={Bar:a(1),Doughnut:a(6),Line:a(7),Pie:a(8),PolarArea:a(9),Radar:a(10),createClass:a(2).createClass}},function(t,e,a){var r=a(2);t.exports=r.createClass("Bar",["getBarsAtEvent"])},function(t,e,a){var r=a(3),n=a(4);t.exports={createClass:function(t,e,i){function c(t){u[t]=function(){return this.state.chart[t].apply(this.state.chart,arguments)}}var u={displayName:t+"Chart",getInitialState:function(){return{}},render:function(){var t={ref:"canvass"};for(var e in this.props)this.props.hasOwnProperty(e)&&"data"!==e&&"options"!==e&&(t[e]=this.props[e]);return r.createElement("canvas",t)}},f=["clear","stop","resize","toBase64Image","generateLegend","update","addData","removeData"];u.componentDidMount=function(){this.initializeChart(this.props)},u.componentWillUnmount=function(){var t=this.state.chart;t.destroy()},u.componentWillReceiveProps=function(t){var e=this.state.chart;t.redraw?(e.destroy(),this.initializeChart(t)):(i=i||s[e.name],o(t,e,i),e.scale&&(e.scale.xLabels=t.data.labels,e.scale.calculateXLabelRotation()),e.update())},u.initializeChart=function(e){var r=a(5),s=n.findDOMNode(this),o=s.getContext("2d"),i=new r(o)[t](e.data,e.options||{});this.state.chart=i},u.getChart=function(){return this.state.chart},u.getCanvass=function(){return this.refs.canvass},u.getCanvas=u.getCanvass;var l;for(l=0;lt.data.labels.length;)e.removeData();t.data.datasets.forEach(function(r,n){r.data.forEach(function(r,s){"undefined"==typeof e.datasets[n][a][s]?i(t,e,n,s):e.datasets[n][a][s].value=r})})}},i=function(t,e,a,r){var n=[];t.data.datasets.forEach(function(t){n.push(t.data[r])}),e.addData(n,t.data.labels[a])}},function(e,a){e.exports=t},function(t,a){t.exports=e},function(t,e){t.exports=a},function(t,e,a){var r=a(2);t.exports=r.createClass("Doughnut",["getSegmentsAtEvent"])},function(t,e,a){var r=a(2);t.exports=r.createClass("Line",["getPointsAtEvent"])},function(t,e,a){var r=a(2);t.exports=r.createClass("Pie",["getSegmentsAtEvent"])},function(t,e,a){var r=a(2);t.exports=r.createClass("PolarArea",["getSegmentsAtEvent"])},function(t,e,a){var r=a(2);t.exports=r.createClass("Radar",["getPointsAtEvent"])}])}); \ No newline at end of file diff --git a/index.js b/index.js index beaedd0..ad4c48b 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ module.exports = { Bar: require('./lib/bar'), Doughnut: require('./lib/doughnut'), + DoughnutTextInside: require('./lib/doughnutTextInside'), Line: require('./lib/line'), Pie: require('./lib/pie'), PolarArea: require('./lib/polar-area'), diff --git a/lib/core.js b/lib/core.js index 5334e68..6c68f0e 100644 --- a/lib/core.js +++ b/lib/core.js @@ -1,129 +1,171 @@ -var React = require('react'); -var ReactDOM = require('react-dom'); +var React = require('react') +var ReactDOM = require('react-dom') module.exports = { - createClass: function(chartType, methodNames, dataKey) { + createClass: function (chartType, methodNames, dataKey) { var classData = { displayName: chartType + 'Chart', - getInitialState: function() { return {}; }, - render: function() { + getInitialState: function () { + return {} + }, + render: function () { var _props = { ref: 'canvass' - }; + } for (var name in this.props) { if (this.props.hasOwnProperty(name)) { if (name !== 'data' && name !== 'options') { - _props[name] = this.props[name]; + _props[name] = this.props[name] } } } - return React.createElement('canvas', _props); + return React.createElement('canvas', _props) } - }; + } + + var extras = ['clear', 'stop', 'resize', 'toBase64Image', 'generateLegend', 'update', 'addData', 'removeData'] - var extras = ['clear', 'stop', 'resize', 'toBase64Image', 'generateLegend', 'update', 'addData', 'removeData']; - function extra(type) { - classData[type] = function() { - return this.state.chart[type].apply(this.state.chart, arguments); - }; + function extra (type) { + classData[type] = function () { + return this.state.chart[type].apply(this.state.chart, arguments) + } } - classData.componentDidMount = function() { - this.initializeChart(this.props); - }; + classData.componentDidMount = function () { + this.initializeChart(this.props) + } - classData.componentWillUnmount = function() { - var chart = this.state.chart; - chart.destroy(); - }; + classData.componentWillUnmount = function () { + var chart = this.state.chart + chart.destroy() + } - classData.componentWillReceiveProps = function(nextProps) { - var chart = this.state.chart; + classData.componentWillReceiveProps = function (nextProps) { + var chart = this.state.chart if (nextProps.redraw) { - chart.destroy(); - this.initializeChart(nextProps); + chart.destroy() + this.initializeChart(nextProps) } else { - dataKey = dataKey || dataKeys[chart.name]; - updatePoints(nextProps, chart, dataKey); + dataKey = dataKey || dataKeys[chart.name] + updatePoints(nextProps, chart, dataKey) if (chart.scale) { - chart.scale.xLabels = nextProps.data.labels; - chart.scale.calculateXLabelRotation(); + chart.scale.xLabels = nextProps.data.labels + chart.scale.calculateXLabelRotation() } - chart.update(); + chart.update() } - }; + } + + classData.initializeChart = function (nextProps) { + var Chart = require('chart.js') + var el = ReactDOM.findDOMNode(this) + var ctx = el.getContext('2d') + Chart.types.Doughnut.extend({ + name: 'DoughnutTextInside', + showTooltip: function () { + this.chart.ctx.save() + Chart.types.Doughnut.prototype.showTooltip.apply(this, arguments) + this.chart.ctx.restore() + }, + defaults: { + labelScale: 100 + }, + draw: function () { + Chart.types.Doughnut.prototype.draw.apply(this, arguments) + var width = this.chart.width, + height = this.chart.height + + var fontSize = (height / this.options.labelScale).toFixed(2) + this.chart.ctx.fillStyle = '#000' + this.chart.ctx.font = fontSize + "em 'Open Sans'" + this.chart.ctx.textBaseline = 'middle' - classData.initializeChart = function(nextProps) { - var Chart = require('chart.js'); - var el = ReactDOM.findDOMNode(this); - var ctx = el.getContext("2d"); - var chart = new Chart(ctx)[chartType](nextProps.data, nextProps.options || {}); - this.state.chart = chart; - }; + var total = this.total + var clone = this.segments.slice() + clone.pop() + var filled = clone.reduce(function (a, val) { + return a + val.value + }, 0) + + var text = Math.round(total > 0 ? (100 / total) * filled : 0) + '%' + var textX = Math.round((width - this.chart.ctx.measureText(text).width) / 2), + textY = height / 2 + + this.chart.ctx.fillText(text, textX, textY) + } + }) + + var chart = new Chart(ctx)[chartType](nextProps.data, nextProps.options || {}) + this.state.chart = chart + } // return the chartjs instance - classData.getChart = function() { - return this.state.chart; - }; + classData.getChart = function () { + return this.state.chart + } // return the canvass element that contains the chart - classData.getCanvass = function() { - return this.refs.canvass; - }; + classData.getCanvass = function () { + return this.refs.canvass + } - classData.getCanvas = classData.getCanvass; + classData.getCanvas = classData.getCanvass - var i; - for (i=0; i nextProps.data.labels.length) { - chart.removeData(); + if (chart.scale) { + while (chart.scale.xLabels.length > nextProps.data.labels.length) { + chart.removeData() + } + } + if (nextProps.data && nextProps.data.datasets) { + nextProps.data.datasets.forEach(function (set, setIndex) { + set.data.forEach(function (val, pointIndex) { + if (typeof (chart.datasets[setIndex][dataKey][pointIndex]) == 'undefined') { + addData(nextProps, chart, setIndex, pointIndex) + } else { + chart.datasets[setIndex][dataKey][pointIndex].value = val + } + }) + }) } - nextProps.data.datasets.forEach(function(set, setIndex) { - set.data.forEach(function(val, pointIndex) { - if (typeof(chart.datasets[setIndex][dataKey][pointIndex]) == "undefined") { - addData(nextProps, chart, setIndex, pointIndex); - } else { - chart.datasets[setIndex][dataKey][pointIndex].value = val; - } - }); - }); } -}; - -var addData = function(nextProps, chart, setIndex, pointIndex) { - var values = []; - nextProps.data.datasets.forEach(function(set) { - values.push(set.data[pointIndex]); - }); - chart.addData(values, nextProps.data.labels[setIndex]); -}; +} + +var addData = function (nextProps, chart, setIndex, pointIndex) { + var values = [] + nextProps.data.datasets.forEach(function (set) { + values.push(set.data[pointIndex]) + }) + chart.addData(values, nextProps.data.labels[setIndex]) +} diff --git a/lib/doughnutTextInside.js b/lib/doughnutTextInside.js new file mode 100644 index 0000000..1ea3c0a --- /dev/null +++ b/lib/doughnutTextInside.js @@ -0,0 +1,3 @@ +var vars = require('./core'); + +module.exports = vars.createClass('DoughnutTextInside', ['getSegmentsAtEvent']);