├── pages └── index │ ├── index.json │ ├── index.wxml │ ├── index.wxss │ └── index.js ├── image ├── pie.png ├── line.png ├── barchart.png └── barline.png ├── app.js ├── app.wxss ├── app.json ├── utils ├── util.js ├── chartUtils.js └── chart.js ├── LICENSE └── README.md /pages/index/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /image/pie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioneday/wxchart/HEAD/image/pie.png -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | //app.js 2 | App({ 3 | onLaunch: function () { 4 | 5 | } 6 | }) -------------------------------------------------------------------------------- /image/line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioneday/wxchart/HEAD/image/line.png -------------------------------------------------------------------------------- /image/barchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioneday/wxchart/HEAD/image/barchart.png -------------------------------------------------------------------------------- /image/barline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioneday/wxchart/HEAD/image/barline.png -------------------------------------------------------------------------------- /pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app.wxss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | .container { 3 | height: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: space-between; 8 | padding: 200rpx 0; 9 | box-sizing: border-box; 10 | } 11 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages":[ 3 | "pages/index/index" 4 | ], 5 | "window":{ 6 | "backgroundTextStyle":"light", 7 | "navigationBarBackgroundColor": "#fff", 8 | "navigationBarTitleText": "WeChat", 9 | "navigationBarTextStyle":"black" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | text-align: center; 3 | background-color: #ffffff; 4 | } 5 | .myCanvas{ 6 | background-color: #f4f4f4; 7 | } 8 | .downloadChart { 9 | font-size: 16px; 10 | background: #699fed; 11 | border-radius: 6px; 12 | color: white; 13 | width: 70%; 14 | margin-top: 32px; 15 | } 16 | -------------------------------------------------------------------------------- /utils/util.js: -------------------------------------------------------------------------------- 1 | function formatTime(date) { 2 | var year = date.getFullYear() 3 | var month = date.getMonth() + 1 4 | var day = date.getDate() 5 | 6 | var hour = date.getHours() 7 | var minute = date.getMinutes() 8 | var second = date.getSeconds() 9 | 10 | 11 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':') 12 | } 13 | 14 | function formatNumber(n) { 15 | n = n.toString() 16 | return n[1] ? n : '0' + n 17 | } 18 | 19 | module.exports = { 20 | formatTime: formatTime 21 | } 22 | -------------------------------------------------------------------------------- /pages/index/index.js: -------------------------------------------------------------------------------- 1 | // test.js 2 | var chart = require("../../utils/chart.js"); 3 | Page({ 4 | 5 | data: { 6 | }, 7 | onLoad: function (options) { 8 | chart.draw(this, 'canvas1', { 9 | title: { 10 | text: "2017城市人均收入(万)", 11 | color: "#333333" 12 | }, 13 | xAxis: { 14 | data: ['北京', '上海', '杭州', '深圳', '广州', '成都', '南京', '西安'] 15 | }, 16 | series: [ 17 | // { 18 | // name: "第一季度", 19 | // category: "bar", 20 | // data: [37, 63, 60, 78, 92, 63, 57, 48] 21 | // }, 22 | // { 23 | // name: "第二季度", 24 | // category: "line", 25 | // data: [20, 35, 38, 59, 48, 27, 43, 35] 26 | // }, 27 | { 28 | name: ['北京', '上海', '杭州', '深圳', '广州', '成都'], 29 | category: "pie", 30 | data: [40, 38, 39, 28, 27, 33] 31 | } 32 | ] 33 | }); 34 | }, 35 | onSaveClick: function () { 36 | chart.saveCanvans(function () { 37 | 38 | }); 39 | } 40 | }) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Dee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wxchart 2 | 微信小程序,图表组件(柱状图、折线图、饼图、雷达图...)。持续更新中... 3 | 4 | 5 | 6 | ![barchart](https://github.com/ioneday/wxchart/blob/master/image/barchart.png)![linechart](https://github.com/ioneday/wxchart/blob/master/image/line.png)![linechart](https://github.com/ioneday/wxchart/blob/master/image/barline.png)![linechart](https://github.com/ioneday/wxchart/blob/master/image/pie.png) 7 | 8 | 9 | 10 | 11 | 12 | ### Useage 13 | 14 | wxml: 15 | 16 | ```javascript 17 | //自适应屏幕宽度 18 | 19 | ``` 20 | 21 | js: 22 | 23 | ```javascript 24 | var chart = require("../../utils/chart.js"); 25 | 26 | chart.draw(this, 'canvasId', { 27 | hideYaxis: false, 28 | color: ['#394655', '#74DAE5', '#ED7672', '#F3AA59', '#FEE746'], 29 | title: { 30 | text: "2017城市人均收入(万)", 31 | color: "#333333", 32 | size: 16 33 | }, 34 | xAxis: { 35 | color: "#666A73", 36 | size: 10, 37 | data: ['北京', '上海', '杭州', '深圳', '广州', '成都', '南京', '西安'] 38 | }, 39 | series: [ 40 | { 41 | name: "第一季度", 42 | category: "bar", //切换柱状图或折线图 43 | data: [37, 63, 60, 78, 92, 63, 57, 48] 44 | }, 45 | { 46 | name: "第二季度", 47 | category: "line", 48 | data: [20, 35, 38, 59, 48, 27, 43, 35] 49 | }, 50 | { 51 | name: ['北京', '上海', '杭州', '深圳', '广州', '成都'], 52 | category: "pie",//饼图不能与以上类型(bar、line)同时绘制 53 | data: [40, 38, 39, 28, 27, 33] 54 | } 55 | ] 56 | }); 57 | ``` 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /utils/chartUtils.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mesureText: mesureText, 3 | calculateY: calculateY, 4 | drawDashLine: drawDashLine, 5 | drawRoundBar: drawRoundBar 6 | } 7 | /** 8 | * 测量文字宽度, 9 | * Canvas宽度太大,微信提供的setTextAlign(center) 10 | * 方法并不能准确居中显示 11 | */ 12 | function mesureText(text, textSize) { 13 | var ratio = textSize / 20; 14 | var text = text.split(''); 15 | var width = 0; 16 | text.forEach(function (item) { 17 | if (/[a-zA-Z]/.test(item)) { 18 | width += (14 * ratio); 19 | } else if (/[0-9]/.test(item)) { 20 | width += (11 * ratio); 21 | } else if (/\./.test(item)) { 22 | width += (5.4 * ratio); 23 | } else if (/-/.test(item)) { 24 | width += (6.5 * ratio); 25 | } else if (/[\u4e00-\u9fa5]/.test(item)) { 26 | width += (20 * ratio); 27 | } 28 | }); 29 | return width; 30 | } 31 | /** 32 | * 计算Y轴显示刻度 33 | */ 34 | function calculateY(dMin, dMax, iMaxAxisNum) { 35 | if (iMaxAxisNum < 1 || dMax < dMin) 36 | return; 37 | 38 | var dDelta = dMax - dMin; 39 | if (dDelta < 1.0) { 40 | dMax += (1.0 - dDelta) / 2.0; 41 | dMin -= (1.0 - dDelta) / 2.0; 42 | } 43 | dDelta = dMax - dMin; 44 | 45 | var iExp = parseInt(Math.log(dDelta) / Math.log(10.0)) - 2; 46 | var dMultiplier = Math.pow(10, iExp); 47 | var dSolutions = [1, 2, 2.5, 5, 10, 20, 25, 50, 100, 200, 250, 500]; 48 | var i; 49 | for (i = 0; i < dSolutions.length; i++) { 50 | var dMultiCal = dMultiplier * dSolutions[i]; 51 | if ((parseInt(dDelta / dMultiCal) + 1) <= iMaxAxisNum) { 52 | break; 53 | } 54 | } 55 | 56 | var dInterval = dMultiplier * dSolutions[i]; 57 | 58 | var dStartPoint = (parseInt(dMin / dInterval) - 1) * dInterval; 59 | var yIndex = []; 60 | var iAxisIndex; 61 | for (iAxisIndex = 1; true; iAxisIndex++) { 62 | var y = dStartPoint + dInterval * iAxisIndex; 63 | console.log(y); 64 | yIndex.push(y); 65 | if (y > dMax) 66 | break; 67 | } 68 | return yIndex; 69 | } 70 | /** 71 | * 绘制虚线 72 | */ 73 | function drawDashLine(ctx, x1, y1, x2, y2, dashLen) { 74 | dashLen = dashLen === undefined ? 4 : dashLen; 75 | var beveling = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); 76 | var num = Math.floor(beveling / dashLen); 77 | 78 | ctx.beginPath(); 79 | for (var i = 0; i < num; i++) { 80 | var x = x1 + (x2 - x1) / num * i; 81 | var y = y1 + (y2 - y1) / num * i; 82 | if (i % 2 == 0) { 83 | ctx.moveTo(x, y); 84 | } else { 85 | ctx.lineTo(x, y); 86 | } 87 | 88 | } 89 | ctx.stroke(); 90 | ctx.closePath(); 91 | } 92 | /** 93 | * 绘制圆角矩形 94 | */ 95 | function drawRoundBar(ctx, x, y, width, height, radius) { 96 | ctx.beginPath(); 97 | ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 3 / 2); 98 | ctx.lineTo(width - radius + x, y); 99 | ctx.arc(width - radius + x, radius + y, radius, Math.PI * 3 / 2, Math.PI * 2); 100 | ctx.lineTo(width + x, height + y - radius); 101 | ctx.arc(width - radius + x, height - radius + y, radius, 0, Math.PI * 1 / 2); 102 | ctx.lineTo(radius + x, height + y); 103 | ctx.arc(radius + x, height - radius + y, radius, Math.PI * 1 / 2, Math.PI); 104 | ctx.closePath(); 105 | ctx.fill(); 106 | } 107 | 108 | function requestAnimation(callback) { 109 | var that = this; 110 | // 保证如果重复执行callback的话,callback的执行起始时间相隔16ms 111 | var currTime = new Date().getTime(); 112 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 113 | var id = setTimeout(function () { 114 | callback(currTime + timeToCall); 115 | }, timeToCall); 116 | lastTime = currTime + timeToCall; 117 | return id; 118 | } 119 | 120 | function easeOut(t, b, c, d) { 121 | return c * ((t = t / d - 1) * t * t + 1) + b; 122 | } -------------------------------------------------------------------------------- /utils/chart.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | draw: init, 4 | saveCanvans: saveCanvans 5 | } 6 | 7 | var util = require("chartUtils.js"); 8 | var canvasId = ''; 9 | var pageObj = null; 10 | var chartOpt = { 11 | chartPieCount: 0, 12 | hideXYaxis: false, 13 | axisMark: [], 14 | barLength: 0, 15 | barNum: 0, 16 | // bgColor: "transparent", 17 | lineColor: "#c2c2c2", 18 | bgColor: "#ffffff", 19 | chartWidth: 0, 20 | chartHeight: 0, 21 | legendWidth: 0, 22 | legendHeight: 0, 23 | chartSpace: 10, 24 | textSpace: 5, 25 | top: 0, 26 | left: 0, 27 | right: 0, 28 | bottom: 0, 29 | axisLeft: 0, 30 | axisBottom: 0, 31 | axisTop: 0 32 | } 33 | var dataSet = { 34 | hideYaxis: false, 35 | title: { 36 | color: "#394655", 37 | size: 16, 38 | text: "" 39 | }, 40 | legend: { 41 | color: "", 42 | size: 12 43 | }, 44 | color: ['#74DAE5', '#394655', '#FEE746', '#B9A39B', '#C18734', '#9EC3AD', '#6D9BA3', '#7E9C82', '#DAEE59', '#CFDCED'], 45 | xAxis: { 46 | color: "#666A73", 47 | size: 10, 48 | data: [] 49 | }, 50 | series: [ 51 | { 52 | name: "", 53 | category: "bar", 54 | data: [] 55 | }, 56 | { 57 | name: "", 58 | category: "line", 59 | data: [] 60 | } 61 | ] 62 | } 63 | 64 | function init(page, canvas, data) { 65 | canvasId = canvas; 66 | pageObj = page; 67 | checkData(data); 68 | 69 | var ctx = initCanvas(page, canvasId); 70 | drawChart(ctx); 71 | } 72 | /** 73 | * 初始化Canvas 74 | */ 75 | function initCanvas(page, canvasId) { 76 | var ctx = wx.createCanvasContext(canvasId); 77 | var Sys = wx.getSystemInfoSync(); 78 | 79 | chartOpt.chartWidth = Sys.windowWidth; 80 | chartOpt.chartHeight = Sys.windowWidth * 0.8;//Canvas组件的宽高比 81 | 82 | chartOpt.legendWidth = dataSet.legend.size * 1.3; 83 | chartOpt.legendHeight = dataSet.legend.size * 0.8; 84 | 85 | chartOpt.top = chartOpt.left = chartOpt.chartSpace; 86 | chartOpt.right = chartOpt.chartWidth - chartOpt.chartSpace; 87 | chartOpt.bottom = chartOpt.chartHeight - chartOpt.chartSpace; 88 | 89 | //3个数字的文字长度 90 | var textWidth = util.mesureText('100', dataSet.xAxis.size); 91 | var legendHeight = dataSet.series.length > 1 ? (chartOpt.legendHeight + chartOpt.chartSpace * 2) : 0; 92 | 93 | chartOpt.axisLeft = chartOpt.left + (dataSet.hideYaxis ? 0 : textWidth + chartOpt.textSpace); 94 | chartOpt.axisBottom = chartOpt.bottom - dataSet.xAxis.size - chartOpt.textSpace - legendHeight; 95 | chartOpt.axisTop = chartOpt.top + dataSet.title.size + chartOpt.textSpace + dataSet.xAxis.size * 2; 96 | 97 | 98 | 99 | //更新页面Canvas的宽度、高度 100 | page.setData({ 101 | chartWidth: chartOpt.chartWidth, 102 | chartHeight: chartOpt.chartHeight 103 | }); 104 | 105 | return ctx; 106 | } 107 | /** 108 | * 检查并更新图表数据 109 | */ 110 | function checkData(data) { 111 | 112 | if (data.title != undefined) { 113 | if (data.title.color != undefined && data.title.color != '') { 114 | dataSet.title.color = data.title.color; 115 | } 116 | dataSet.title.text = data.title.text; 117 | 118 | } 119 | if (data.color != undefined && data.color != [] && data.color.length > 0) { 120 | dataSet.color = data.color; 121 | } 122 | dataSet.xAxis.data = data.xAxis.data; 123 | 124 | 125 | dataSet.series = data.series; 126 | 127 | var value = new Array(); 128 | for (var i = 0; i < dataSet.series.length; i++) { 129 | var item = dataSet.series[i]; 130 | var itemLenght = item.data.length; 131 | if (itemLenght > chartOpt.barLength) { 132 | chartOpt.barLength = itemLenght; 133 | } 134 | for (var k = 0; k < itemLenght; k++) { 135 | value.push(item.data[k]); 136 | } 137 | if (item.category == 'bar') { 138 | chartOpt.barNum += 1; 139 | 140 | } 141 | if (item.category == 'pie') { 142 | chartOpt.hideXYaxis = true; 143 | for (var k = 0; k < itemLenght; k++) { 144 | chartOpt.chartPieCount += item.data[k]; 145 | } 146 | } 147 | 148 | } 149 | 150 | var minNum = Math.min.apply(null, value); 151 | var maxNum = Math.max.apply(null, value); 152 | //计算Y轴刻度尺数据 153 | chartOpt.axisMark = util.calculateY(minNum, maxNum, 5); 154 | } 155 | /** 156 | * 绘制图表 157 | */ 158 | function drawChart(ctx) { 159 | drawBackground(ctx); 160 | drawTitle(ctx); 161 | drawLegend(ctx); 162 | if (!chartOpt.hideXYaxis) { 163 | drawXaxis(ctx); 164 | drawYaxis(ctx); 165 | } 166 | 167 | // drawBarChart(ctx); 168 | drawCharts(ctx); 169 | ctx.draw(); 170 | } 171 | /** 172 | * 绘制图表背景 173 | */ 174 | function drawBackground(ctx) { 175 | if (chartOpt.bgColor != "" && chartOpt.bgColor != "transparent") { 176 | ctx.setFillStyle(chartOpt.bgColor); 177 | ctx.fillRect(0, 0, chartOpt.chartWidth, chartOpt.chartHeight); 178 | } 179 | } 180 | /** 181 | * 绘制标题 182 | */ 183 | function drawTitle(ctx) { 184 | var title = dataSet.title; 185 | if (title.text != '') { 186 | 187 | var textWidth = util.mesureText(title.text, title.size); 188 | ctx.setFillStyle(title.color); 189 | ctx.setFontSize(title.size) 190 | ctx.setTextAlign('left'); 191 | ctx.fillText(title.text, (chartOpt.chartWidth - textWidth) / 2, chartOpt.top + title.size); 192 | } 193 | 194 | } 195 | /** 196 | * 绘制X轴刻度尺 197 | */ 198 | function drawXaxis(ctx) { 199 | //绘制X轴横线 200 | ctx.setLineWidth(0.5); 201 | ctx.setLineCap('round'); 202 | ctx.moveTo(chartOpt.axisLeft, chartOpt.axisBottom) 203 | ctx.lineTo(chartOpt.right, chartOpt.axisBottom) 204 | ctx.stroke(); 205 | 206 | 207 | var width = (chartOpt.right - chartOpt.axisLeft) / chartOpt.barLength; 208 | var data = dataSet.xAxis.data; 209 | //绘制X轴显示文字 210 | for (var i = 0; i < data.length; i++) { 211 | var textX = (width * (i + 1)) - (width / 2) + chartOpt.axisLeft; 212 | ctx.setFillStyle(dataSet.xAxis.color); 213 | ctx.setFontSize(dataSet.xAxis.size); 214 | ctx.setTextAlign('center'); 215 | ctx.fillText(data[i], textX, chartOpt.axisBottom + dataSet.xAxis.size + chartOpt.textSpace); 216 | } 217 | } 218 | /** 219 | * 绘制Y轴刻度尺 220 | */ 221 | function drawYaxis(ctx) { 222 | 223 | //绘制Y轴横线 224 | ctx.setLineWidth(0.5); 225 | ctx.setLineCap('round'); 226 | 227 | var height = (chartOpt.axisBottom - chartOpt.axisTop) / (chartOpt.axisMark.length - 1); 228 | //绘制Y轴显示数字 229 | for (var i = 0; i < chartOpt.axisMark.length; i++) { 230 | var y = chartOpt.axisBottom - height * i; 231 | if (i > 0) { 232 | ctx.setStrokeStyle(chartOpt.lineColor); 233 | util.drawDashLine(ctx, chartOpt.axisLeft, y, chartOpt.right, y); 234 | } 235 | 236 | if (!dataSet.hideYaxis) { 237 | ctx.setFillStyle(dataSet.xAxis.color); 238 | ctx.setFontSize(dataSet.xAxis.size) 239 | ctx.setTextAlign('right'); 240 | ctx.fillText(chartOpt.axisMark[i], chartOpt.axisLeft - chartOpt.textSpace, y + chartOpt.textSpace); 241 | } 242 | } 243 | } 244 | 245 | /** 246 | * 绘制图例 247 | */ 248 | function drawLegend(ctx) { 249 | var series = dataSet.series; 250 | 251 | for (var i = 0; i < series.length; i++) { 252 | var names = series[i].name; 253 | var isPie = series[i].category == 'pie'; 254 | var textWidth = util.mesureText(isPie?names[0]:names, dataSet.xAxis.size); 255 | var legendWidth = chartOpt.legendWidth + textWidth + chartOpt.chartSpace * 2; 256 | var startX = (chartOpt.chartWidth / 2) - (legendWidth * (isPie ?names.length:series.length)) / 2; 257 | 258 | if (series[i].category == 'pie') { 259 | for (var k = 0; k < names.length; k++) { 260 | var x = startX + legendWidth * k; 261 | var y = chartOpt.bottom - chartOpt.legendHeight; 262 | 263 | ctx.setFillStyle(dataSet.xAxis.color); 264 | ctx.setFontSize(dataSet.legend.size) 265 | ctx.setTextAlign('left'); 266 | ctx.fillText(names[k], x + chartOpt.textSpace + chartOpt.legendWidth, chartOpt.bottom); 267 | 268 | var color = getColor(k); 269 | ctx.setFillStyle(color); 270 | ctx.fillRect(x, y + 1, chartOpt.legendWidth, chartOpt.legendHeight); 271 | } 272 | } else { 273 | 274 | var x = startX + legendWidth * i + chartOpt.legendWidth * i; 275 | var y = chartOpt.bottom - chartOpt.legendHeight; 276 | 277 | ctx.setFillStyle(dataSet.xAxis.color); 278 | ctx.setFontSize(dataSet.legend.size) 279 | ctx.setTextAlign('left'); 280 | ctx.fillText(series[i].name, x + chartOpt.chartSpace + chartOpt.legendWidth, chartOpt.bottom); 281 | 282 | var color = getColor(i); 283 | ctx.setFillStyle(color); 284 | ctx.setLineWidth(2); 285 | ctx.setStrokeStyle(color); 286 | if (series[i].category == 'bar') { 287 | ctx.fillRect(x, y + 1, chartOpt.legendWidth, chartOpt.legendHeight); 288 | } else if (series[i].category == 'line') { 289 | var lx = x + chartOpt.legendWidth / 2; 290 | var ly = y + chartOpt.legendHeight / 2 + 1; 291 | ctx.beginPath(); 292 | ctx.moveTo(x, ly); 293 | ctx.lineTo(x + chartOpt.legendWidth, ly); 294 | ctx.stroke(); 295 | ctx.closePath(); 296 | drawPoint(ctx, lx, ly, chartOpt.legendHeight / 2, color); 297 | drawPoint(ctx, lx, ly, chartOpt.legendHeight / 4, chartOpt.bgColor); 298 | } 299 | } 300 | } 301 | } 302 | /** 303 | * 绘制数据标签 304 | */ 305 | function drawToolTips(ctx, text, x, y, color) { 306 | ctx.setFillStyle(color); 307 | ctx.setFontSize(dataSet.xAxis.size) 308 | ctx.setTextAlign('center'); 309 | ctx.fillText(text, x, y); 310 | } 311 | /** 312 | * 画图 313 | */ 314 | function drawCharts(ctx) { 315 | var series = dataSet.series; 316 | for (var i = 0; i < series.length; i++) { 317 | var category = series[i].category; 318 | var barWidth = (chartOpt.right - chartOpt.axisLeft) / chartOpt.barLength; 319 | var barHeight = chartOpt.axisBottom - chartOpt.axisTop; 320 | var maxMark = chartOpt.axisMark[chartOpt.axisMark.length - 1]; 321 | 322 | if (category == "bar") { 323 | barWidth = barWidth - chartOpt.chartSpace; 324 | drawBarChart(ctx, i, series, barWidth, barHeight, maxMark); 325 | } else if (category == "line") { 326 | drawLineChart(ctx, i, series, barWidth, barHeight, maxMark); 327 | } else if (category == 'pie') { 328 | drawPieChart(ctx, i, series); 329 | } 330 | } 331 | } 332 | /** 333 | * 绘制柱状图 334 | */ 335 | function drawBarChart(ctx, i, series, barWidth, barHeight, maxMark) { 336 | var item = series[i]; 337 | var itemWidth = barWidth / chartOpt.barNum; 338 | 339 | for (var k = 0; k < item.data.length; k++) { 340 | var itemHeight = barHeight * (item.data[k] / maxMark); 341 | var x = (barWidth * k + chartOpt.axisLeft + k * chartOpt.chartSpace + (chartOpt.chartSpace / 2)) + (i * itemWidth); 342 | var y = chartOpt.axisBottom - itemHeight; 343 | var color = getColor(series.length <= 1 ? k : i); 344 | ctx.setFillStyle(color); 345 | ctx.fillRect(x, y, itemWidth, itemHeight); 346 | 347 | drawToolTips(ctx, item.data[k], x + itemWidth / 2, y - chartOpt.textSpace, color); 348 | } 349 | } 350 | /** 351 | * 绘制折线图 352 | */ 353 | function drawLineChart(ctx, i, series, barWidth, barHeight, maxMark) { 354 | var item = series[i]; 355 | var color = getColor(i); 356 | ctx.setLineWidth(2); 357 | ctx.setStrokeStyle(color); 358 | ctx.beginPath(); 359 | for (var k = 0; k < item.data.length; k++) { 360 | var point = getLinePoint(k, item, barWidth, barHeight, maxMark); 361 | if (k == 0) { 362 | ctx.moveTo(point.x, point.y); 363 | } else { 364 | ctx.lineTo(point.x, point.y); 365 | } 366 | } 367 | ctx.stroke(); 368 | ctx.closePath(); 369 | for (var k = 0; k < item.data.length; k++) { 370 | var point = getLinePoint(k, item, barWidth, barHeight, maxMark); 371 | drawPoint(ctx, point.x, point.y, 3, color); 372 | drawPoint(ctx, point.x, point.y, 1, chartOpt.bgColor); 373 | drawToolTips(ctx, item.data[k], point.x, point.y - chartOpt.chartSpace, color); 374 | } 375 | } 376 | function getLinePoint(k, item, barWidth, barHeight, maxMark) { 377 | var x = barWidth * k + chartOpt.axisLeft + barWidth / 2; 378 | var y = chartOpt.axisBottom - (barHeight * (item.data[k] / maxMark)); 379 | return { x: x, y: y } 380 | } 381 | function drawPoint(ctx, x, y, radius, color) { 382 | ctx.setFillStyle(color); 383 | ctx.beginPath(); 384 | ctx.arc(x, y, radius, 0, 2 * Math.PI); 385 | ctx.fill(); 386 | ctx.closePath(); 387 | } 388 | /** 389 | * 绘制饼图 390 | */ 391 | function drawPieChart(ctx, i, series) { 392 | var item = series[i]; 393 | 394 | var x = (chartOpt.right - chartOpt.left) / 2 + chartOpt.left; 395 | var radius = (chartOpt.axisBottom - chartOpt.axisTop) / 3; 396 | var y = (chartOpt.axisBottom - chartOpt.axisTop) / 2 + chartOpt.axisTop 397 | 398 | var lastAngel = 0; 399 | for (var k = 0; k < item.data.length; k++) { 400 | var color = getColor(k); 401 | var curAngel = 2 / chartOpt.chartPieCount * item.data[k]; 402 | var precent = 100 / chartOpt.chartPieCount * item.data[k]; 403 | 404 | drawPieToolTips(ctx, item.data[k] + "(" + Math.round(precent) + "%)", color, x, y, radius, lastAngel, curAngel); 405 | 406 | ctx.setFillStyle(color); 407 | ctx.beginPath(); 408 | ctx.moveTo(x, y); 409 | ctx.arc(x, y, radius, (lastAngel - 0.5) * Math.PI, (lastAngel + curAngel - 0.5) * Math.PI); 410 | ctx.fill(); 411 | ctx.closePath(); 412 | lastAngel += curAngel; 413 | 414 | } 415 | } 416 | /** 417 | * 绘制饼图数据标签 418 | */ 419 | function drawPieToolTips(ctx, value, color, x, y, radius, lastAngel, curAngel) { 420 | var textWidth = util.mesureText(value, dataSet.xAxis.size); 421 | var cosc = Math.cos((lastAngel - 0.5 + curAngel / 2) * Math.PI); 422 | var sinc = Math.sin((lastAngel - 0.5 + curAngel / 2) * Math.PI); 423 | var x1 = (radius) * cosc + x; 424 | var y1 = (radius) * sinc + y; 425 | 426 | var x2 = (radius + 20) * cosc + x; 427 | var y2 = (radius + 20) * sinc + y; 428 | 429 | ctx.setFillStyle(color); 430 | ctx.setTextAlign(x2 < x1 ? 'right' : 'left'); 431 | ctx.setFontSize(dataSet.xAxis.size); 432 | ctx.setStrokeStyle(color); 433 | ctx.setLineWidth(1); 434 | ctx.beginPath(); 435 | ctx.moveTo(x1, y1); 436 | if (x1 >= x && y1 < y) { 437 | ctx.quadraticCurveTo(x2, y2, x2 + 15, y2) 438 | ctx.fillText(value, x2 + 15 + chartOpt.textSpace, y2 + dataSet.xAxis.size / 2); 439 | } else if (x1 >= x && y1 >= y) { 440 | ctx.quadraticCurveTo(x2, y2, x2 + 15, y2) 441 | ctx.fillText(value, x2 + 15 + chartOpt.textSpace, y2 + dataSet.xAxis.size / 2); 442 | } else if (x1 < x && y1 >= y) { 443 | ctx.quadraticCurveTo(x2, y2, x2 - 15, y2) 444 | ctx.fillText(value, x2 - 15 - chartOpt.textSpace, y2 + dataSet.xAxis.size / 2); 445 | } else if (x1 < x && y1 < y) { 446 | ctx.quadraticCurveTo(x2, y2, x2 - 15, y2) 447 | ctx.fillText(value, x2 - 15 - chartOpt.textSpace, y2 + dataSet.xAxis.size / 2); 448 | } 449 | ctx.stroke(); 450 | ctx.closePath(); 451 | } 452 | /** 453 | * 获取柱状图颜色值,循环渲染 454 | */ 455 | function getColor(index) { 456 | var cLength = dataSet.color.length; 457 | if (index >= cLength) { 458 | return dataSet.color[index % cLength]; 459 | } else { 460 | return dataSet.color[index]; 461 | } 462 | } 463 | /** 464 | * 保存图表为图片 465 | */ 466 | function saveCanvans(func) { 467 | wx.canvasToTempFilePath({ 468 | canvasId: canvasId, 469 | success: function (res) { 470 | console.log(res.tempFilePath) 471 | // wx.previewImage({ 472 | // urls: [res.tempFilePath], 473 | // }) 474 | wx.saveImageToPhotosAlbum({ 475 | filePath: res.tempFilePath, 476 | success(ress) { 477 | console.log(ress) 478 | } 479 | }) 480 | } 481 | }) 482 | } --------------------------------------------------------------------------------