diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..ff3059c --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-env"] +} \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..4d123ce --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,21 @@ +{ + "env": { + "browser": true, + "es6": true, + "node": true + }, + "extends": "eslint:recommended", + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module" + }, + "rules": { + "indent": ["error", 4], + "quotes": ["error", "single"], + "semi": ["error", "always"] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5cd9315..08c7ccd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ .git node_modules/ dist/ -src/ npm-debug.log* yarn-debug.log* yarn-error.log* diff --git a/LICENCE b/LICENCE index 1474d32..91099dc 100644 --- a/LICENCE +++ b/LICENCE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 林鑫 +Copyright (c) 2019 林鑫 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 7570929..6b3453b 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,17 @@

-## Demo +## Document +- [中文文档](https://lin-xin.gitee.io/example/schart/) +- [English](https://lin-xin.gitee.io/example/schart/en/) -- [Demo](http://blog.gdfengshuo.com/example/sChart/demo.html) -- [Demo code](https://github.com/lin-xin/sChart.js/blob/master/example/chart.html) +## Demo -![demo](http://blog.gdfengshuo.com/example/sChart/static/img/demo.png) +![柱状图](https://lin-xin.gitee.io/example/schart/assets/img/1.6ad92a2c.png) +![折线图](https://lin-xin.gitee.io/example/schart/assets/img/2.c260f420.png) +![饼状图](https://lin-xin.gitee.io/example/schart/assets/img/3.bfe79000.png) +![环形图](https://lin-xin.gitee.io/example/schart/assets/img/4.5db72d39.png) -## Doc -- [中文文档](http://blog.gdfengshuo.com/example/sChart/index.html) -- [English](http://blog.gdfengshuo.com/example/sChart/en.html) ## Chart Types The following chart types are implemented: @@ -35,132 +36,9 @@ npm install schart.js ### using Javascript ```js -new sChart(canvasId, type, data, options) -``` - -### example - -```html - - - - - - +new Schart(canvasId, options) ``` -### Use rem or % in the mobile -```html - -
- -
- - - - -``` - -## Options - -### title -String.The title of chart. -Default is null. - -### titleColor -String.Title Color. -Default is '#000000'. - -### titlePosition -String.Title position. -Default is 'top'. - -### showValue -Display the value in the chart. -Default is true. - -### autoWidth -Adaptive width and height. -Default is false. - -### bgColor -String.The background color of chart. -Default is '#ffffff'. - -### topPadding -Number.Inside top margin of chart. -Default is 50. - -### bottomPadding -Number.Inside bottom margin of chart. -Default is 50. - -### leftPadding -Number.Inside left margin of chart. -Default is 50. - -### rightPadding -Number.Inside right margin of chart. -Default is 0. - -### yEqual -Number.The number of points on the Y axis. -Default is 5. - -### fillColor -String.The color of bar chart or line chart. -Default is '#1E9FFF'. - -### axisColor -String.The color of the coordinate axis. -Default is '#666666'. - -### contentColor -String.The color of the horizontal lines of the content. -Default is '#eeeeee'. - -### radius -Number.The radius of a pie chart or ring chart. -Default is 100. - -### innerRadius -Number.The inner radius of a ring chart. -Default is 700. - -### colorList -Array.The color list of a pie chart or ring chart. -Default is ['#1E9FFF', '#13CE66', '#F7BA2A', '#FF4949', '#72f6ff']; - -### legendColor -String.The color of the legend text. -Default is '#000000'. - -### legendTop -Number.Legend position of top. -Default is 40. - ## Relevant [vue-schart](https://github.com/lin-xin/vue-schart) : Vue.js wrapper for sChart.js diff --git a/example/chart.html b/example/chart.html deleted file mode 100644 index c4719a1..0000000 --- a/example/chart.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - sChart.js 演示 - - - - - - - -
-
- - -
- - - - - -
- - - - \ No newline at end of file diff --git a/example/index.html b/example/index.html new file mode 100644 index 0000000..412feee --- /dev/null +++ b/example/index.html @@ -0,0 +1,120 @@ + + + + + schart.js 案例演示 + + + + + + + +
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/lib/sChart.js b/lib/sChart.js index 7b1727c..bfa88d8 100644 --- a/lib/sChart.js +++ b/lib/sChart.js @@ -1,343 +1,544 @@ -/*! - * sChart JavaScript Library v2.0.1 - * http://blog.gdfengshuo.com/example/sChart/ | Released under the MIT license - * Date: 2018-04-16T18:59Z - */ -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - define(function () { - return factory(root); - }); - } else if (typeof exports === 'object') { - module.exports = factory; - } else { - root.sChart = factory(root); - } -})(this, function (root) { - 'use strict'; - /** - * 生成图表 - * @param {String} canvas 画布元素id - * @param {String} type 图表类型 - * @param {Array} data 生成图表的数据 - * @param {Object} options 图表参数 可选参数 - */ - function sChart(canvas, type, data, options) { - this.canvas = document.getElementById(canvas); - this.ctx = this.canvas.getContext('2d'); - this.dpi = window.devicePixelRatio || 1; - this.type = type; - this.data = data; // 存放图表数据 - this.dataLength = this.data.length; // 图表数据的长度 - this.showValue = true; // 是否在图表中显示数值 - this.autoWidth = false; // 宽高是否自适应 - this.width = this.canvas.width * this.dpi; // canvas 宽度 - this.height = this.canvas.height * this.dpi; // canvas 高度 - this.topPadding = 50 * this.dpi; - this.leftPadding = 50 * this.dpi; - this.rightPadding = 0 * this.dpi; - this.bottomPadding = 50 * this.dpi; - this.yEqual = 5; // y轴分成5等分 - this.yLength = 0; // y轴坐标点之间的真实长度 - this.xLength = 0; // x轴坐标点之间的真实长度 - this.yFictitious = 0; // y轴坐标点之间显示的间距 - this.yRatio = 0; // y轴坐标真实长度和坐标间距的比 - this.bgColor = '#ffffff'; // 默认背景颜色 - this.fillColor = '#1E9FFF'; // 默认填充颜色 - this.axisColor = '#666666'; // 坐标轴颜色 - this.contentColor = '#eeeeee'; // 内容横线颜色 - this.titleColor = '#000000'; // 图表标题颜色 - this.title = ''; // 图表标题 - this.titlePosition = 'top'; // 图表标题位置: top / bottom - this.radius = 100 * this.dpi; // 饼图半径和环形图外圆半径 - this.innerRadius = 70 * this.dpi; // 环形图内圆半径 - this.colorList = ['#1E9FFF', '#13CE66', '#F7BA2A', '#FF4949', '#72f6ff', '#199475', '#e08031', '#726dd1']; // 饼图颜色列表 - this.legendColor = '#000000'; // 图例文字颜色 - this.legendTop = 40 * this.dpi; // 图例距离顶部高度 - this.totalValue = this.getTotalValue(); // 获取饼图数据总和 - this.init(options); - } - sChart.prototype = { - init: function (options) { - if (this.dataLength === 0) { - return false; - } - if (options) { - var dpiList = ['topPadding', 'leftPadding', 'rightPadding', 'bottomPadding', 'radius', 'innerRadius', 'legendTop']; - for (var key in options) { - if (key === 'colorList' && Array.isArray(options[key])) { - this[key] = options[key].concat(this[key]) - } else if (dpiList.indexOf(key) > -1) { - this[key] = options[key] * this.dpi; - } else { - this[key] = options[key]; - } - } - } - - // 如果设置了自动宽高的话,则就宽高设为父元素的宽高 - if (options.autoWidth) { - this.width = this.canvas.width = this.canvas.parentNode.offsetWidth * this.dpi; - this.height = this.canvas.height = this.canvas.parentNode.offsetHeight * this.dpi; - this.canvas.setAttribute('style', 'width:' + this.canvas.parentNode.offsetWidth + 'px;height:' + this.canvas.parentNode.offsetHeight + 'px;') - } else { - this.canvas.setAttribute('style', 'width:' + this.canvas.width + 'px;height:' + this.canvas.height + 'px;'); - this.canvas.width *= this.dpi; - this.canvas.height *= this.dpi; - } - - if (this.type === 'bar' || this.type === 'line') { - this.yLength = Math.floor((this.height - this.topPadding - this.bottomPadding - 10) / this.yEqual); - this.xLength = Math.floor((this.width - this.leftPadding - this.rightPadding - 10) / this.dataLength); - this.yFictitious = this.getYFictitious(this.data); - this.yRatio = this.yLength / this.yFictitious; - this.drawBarUpdate(); - } else { - this.drawPieUpdate(); - } - }, - /** - * 绘制完整的柱状图或折线图 - */ - drawBarUpdate: function () { - this.ctx.fillStyle = this.bgColor; - this.ctx.fillRect(0, 0, this.width, this.height); - this.drawAxis(); - this.drawPoint(); - this.drawTitle(); - this.drawBarChart(); - }, - /** - * 绘制完整的饼状图或环形图 - */ - drawPieUpdate: function () { - this.ctx.fillStyle = this.bgColor; - this.ctx.fillRect(0, 0, this.width, this.height); - this.drawLegend(); - this.drawTitle(); - this.drawPieChart(); - }, - /** - * 把数据绘制出柱状或折线 - */ - drawBarChart: function () { - this.ctx.fillStyle = this.fillColor; - this.ctx.strokeStyle = this.fillColor; - for (var i = 0; i < this.dataLength; i++) { - this.data[i].left = this.leftPadding + this.xLength * (i + 0.25); - this.data[i].top = this.height - this.bottomPadding - this.data[i].value * this.yRatio; - this.data[i].right = this.leftPadding + this.xLength * (i + 0.75); - this.data[i].bottom = this.height - this.bottomPadding; - - // 绘制折线 - if (this.type === 'line') { - this.ctx.beginPath(); - this.ctx.arc(this.data[i].left + this.xLength / 4, this.data[i].top, 2, 0, 2 * Math.PI, true); - this.ctx.fill(); - if (i !== 0) { - this.ctx.moveTo(this.data[i].left + this.xLength / 4, this.data[i].top); - this.ctx.lineTo(this.data[i - 1].left + this.xLength / 4, this.data[i - 1].top); - } - this.ctx.stroke(); - } else if (this.type === 'bar') { - // 绘制柱状 - this.ctx.fillRect( - this.data[i].left, - this.data[i].top, - this.data[i].right - this.data[i].left, - this.data[i].bottom - this.data[i].top - ); - } - if (this.showValue) { - this.ctx.font = 12 * this.dpi + 'px Arial' - this.ctx.fillText( - this.data[i].value, - this.data[i].left + this.xLength / 4, - this.data[i].top - 5 - ); - } - } - }, - /** - * 把数据绘制出饼状或环形 - */ - drawPieChart: function () { - var x = this.width / 2, - y = this.height / 2, - x1 = 0, - y1 = 0; - for (var i = 0; i < this.dataLength; i++) { - this.ctx.beginPath(); - this.ctx.fillStyle = this.colorList[i]; - this.ctx.moveTo(x, y); - this.data[i].start = i === 0 ? -Math.PI / 2 : this.data[i - 1].end; - this.data[i].end = this.data[i].start + this.data[i].value / this.totalValue * 2 * Math.PI; - // 绘制扇形 - this.ctx.arc(x, y, this.radius, this.data[i].start, this.data[i].end); - this.ctx.closePath(); - this.ctx.fill(); - - this.data[i].middle = (this.data[i].start + this.data[i].end) / 2; - x1 = Math.ceil(Math.abs(this.radius * Math.cos(this.data[i].middle))); - y1 = Math.floor(Math.abs(this.radius * Math.sin(this.data[i].middle))); - - this.ctx.strokeStyle = this.colorList[i]; - // 绘制各个扇形边上的数据 - if(this.showValue){ - if (this.data[i].middle <= 0) { - this.ctx.textAlign = 'left'; - this.ctx.moveTo(x + x1, y - y1); - this.ctx.lineTo(x + x1 + 10, y - y1 - 10); - this.ctx.moveTo(x + x1 + 10, y - y1 - 10); - this.ctx.lineTo(x + x1 + this.radius / 2, y - y1 - 10); - this.ctx.stroke(); - this.ctx.fillText(this.data[i].value, x + x1 + 5 + this.radius / 2, y - y1 - 5); - } else if (this.data[i].middle > 0 && this.data[i].middle <= Math.PI / 2) { - this.ctx.textAlign = 'left'; - this.ctx.moveTo(x + x1, y + y1); - this.ctx.lineTo(x + x1 + 10, y + y1 + 10); - this.ctx.moveTo(x + x1 + 10, y + y1 + 10); - this.ctx.lineTo(x + x1 + this.radius / 2, y + y1 + 10); - this.ctx.stroke(); - this.ctx.fillText(this.data[i].value, x + x1 + 5 + this.radius / 2, y + y1 + 15); - } else if (this.data[i].middle > Math.PI / 2 && this.data[i].middle < Math.PI) { - this.ctx.textAlign = 'right'; - this.ctx.moveTo(x - x1, y + y1); - this.ctx.lineTo(x - x1 - 10, y + y1 + 10); - this.ctx.moveTo(x - x1 - 10, y + y1 + 10); - this.ctx.lineTo(x - x1 - this.radius / 2, y + y1 + 10); - this.ctx.stroke(); - this.ctx.fillText(this.data[i].value, x - x1 - 5 - this.radius / 2, y + y1 + 15); - } else { - this.ctx.textAlign = 'right'; - this.ctx.moveTo(x - x1, y - y1); - this.ctx.lineTo(x - x1 - 10, y - y1 - 10); - this.ctx.moveTo(x - x1 - 10, y - y1 - 10); - this.ctx.lineTo(x - x1 - this.radius / 2, y - y1 - 10); - this.ctx.stroke(); - this.ctx.fillText(this.data[i].value, x - x1 - 5 - this.radius / 2, y - y1 - 5); - } - } - } - // 如果类型是环形图,绘制一个内圆 - if (this.type === 'ring') { - this.ctx.beginPath(); - this.ctx.fillStyle = this.bgColor; - this.ctx.arc(x, y, this.innerRadius, 0, Math.PI * 2); - this.ctx.fill(); - } - }, - /** - * 绘制坐标轴 - */ - drawAxis: function () { - this.ctx.beginPath(); - this.ctx.strokeStyle = this.axisColor; - // y轴线, +0.5是为了解决canvas画1像素会显示成2像素的问题 - this.ctx.moveTo(this.leftPadding + 0.5, this.height - this.bottomPadding + 0.5); - this.ctx.lineTo(this.leftPadding + 0.5, this.topPadding + 0.5); - // x轴线 - this.ctx.moveTo(this.leftPadding + 0.5, this.height - this.bottomPadding + 0.5); - this.ctx.lineTo(this.width - this.rightPadding - 0.5, this.height - this.bottomPadding + 0.5); - this.ctx.stroke(); - }, - /** - * 绘制坐标轴上的点和值 - */ - drawPoint: function () { - // x轴坐标点 - this.ctx.beginPath(); - this.ctx.font = 12 * this.dpi + 'px Microsoft YaHei'; - this.ctx.textAlign = 'center'; - this.ctx.fillStyle = this.axisColor; - for (var i = 0; i < this.dataLength; i++) { - var name = this.data[i].name; - var xlen = this.xLength * (i + 1); - this.ctx.moveTo(this.leftPadding + xlen + 0.5, this.height - this.bottomPadding + 0.5); - this.ctx.lineTo(this.leftPadding + xlen + 0.5, this.height - this.bottomPadding + 5.5); - this.ctx.fillText(name, this.leftPadding + xlen - this.xLength / 2, this.height - this.bottomPadding + 15 * this.dpi); - } - this.ctx.stroke(); - - // y轴坐标点 - this.ctx.beginPath(); - this.ctx.font = 12 * this.dpi + 'px Microsoft YaHei'; - this.ctx.textAlign = 'right'; - this.ctx.fillStyle = this.axisColor; - this.ctx.moveTo(this.leftPadding + 0.5, this.height - this.bottomPadding + 0.5); - this.ctx.lineTo(this.leftPadding - 4.5, this.height - this.bottomPadding + 0.5); - this.ctx.fillText(0, this.leftPadding - 10, this.height - this.bottomPadding + 5); - for (var i = 0; i < this.yEqual; i++) { - var y = this.yFictitious * (i + 1); - var ylen = this.yLength * (i + 1); - this.ctx.beginPath(); - this.ctx.strokeStyle = this.axisColor; - this.ctx.moveTo(this.leftPadding + 0.5, this.height - this.bottomPadding - ylen + 0.5); - this.ctx.lineTo(this.leftPadding - 4.5, this.height - this.bottomPadding - ylen + 0.5); - this.ctx.stroke(); - this.ctx.fillText(y, this.leftPadding - 10, this.height - this.bottomPadding - ylen + 5); - this.ctx.beginPath(); - this.ctx.strokeStyle = this.contentColor; - this.ctx.moveTo(this.leftPadding + 0.5, this.height - this.bottomPadding - ylen + 0.5) - this.ctx.lineTo(this.width - this.rightPadding - 0.5, this.height - this.bottomPadding - ylen + 0.5); - this.ctx.stroke(); - } - }, - /** - * 绘制图表标题 - */ - drawTitle: function () { - if (this.title) { - this.ctx.beginPath(); - this.ctx.textAlign = 'center'; - this.ctx.fillStyle = this.titleColor; - this.ctx.font = 16 * this.dpi + 'px Microsoft YaHei'; - if (this.titlePosition === 'bottom' && this.bottomPadding >= 40) { - this.ctx.fillText(this.title, this.width / 2, this.height - 5) - } else { - this.ctx.fillText(this.title, this.width / 2, this.topPadding / 2 + 5) - } - } - }, - /** - * 绘制饼状图或环形图的图例 - */ - drawLegend: function () { - for (var i = 0; i < this.dataLength; i++) { - this.ctx.fillStyle = this.colorList[i]; - this.ctx.fillRect(10, this.legendTop + 15 * i * this.dpi, 20, 11); - this.ctx.fillStyle = this.legendColor; - this.ctx.font = 12 * this.dpi + 'px Microsoft YaHei'; - this.ctx.textAlign = 'left'; - this.ctx.fillText(this.data[i].name, 35, this.legendTop + 10 + 15 * i * this.dpi); - } - }, - /** - * y轴坐标点之间显示的间距 - * @param data 生成图表的数据 - * @return y轴坐标间距 - */ - getYFictitious: function (data) { - var arr = data.slice(0); - arr.sort(function (a, b) { - return -(a.value - b.value); - }); - var len = Math.ceil(arr[0].value / this.yEqual); - var pow = len.toString().length - 1; - pow = pow > 2 ? 2 : pow; - return Math.ceil(len / Math.pow(10, pow)) * Math.pow(10, pow); - }, - /** - * 获取饼状或环形图的数据总和 - * @return {Number} total - */ - getTotalValue: function () { - var total = 0; - for (var i = 0; i < this.dataLength; i++) { - total += this.data[i].value; - } - return total; - } - } - return sChart; -}); \ No newline at end of file +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.Schart = factory()); +}(this, (function () { 'use strict'; + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + + function _toConsumableArray(arr) { + return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); + } + + function _arrayWithoutHoles(arr) { + if (Array.isArray(arr)) { + for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; + + return arr2; + } + } + + function _iterableToArray(iter) { + if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); + } + + function _nonIterableSpread() { + throw new TypeError("Invalid attempt to spread non-iterable instance"); + } + + var DPI = window.devicePixelRatio || 1; + var S10 = 10 * DPI; + var S5 = S10 / 2; + var initCanvas = function initCanvas(id) { + var canvas = document.getElementById(id); + var width = canvas.parentNode.clientWidth; + var height = canvas.parentNode.clientHeight; + canvas.style.width = width + 'px'; + canvas.style.height = height + 'px'; + canvas.width = width * DPI; + canvas.height = height * DPI; + return canvas; + }; + var getYSpace = function getYSpace(datasets, yEqual) { + var arr = datasets.map(function (item) { + return item.data.reduce(function (prev, current) { + return prev > current ? prev : current; + }); + }); + var len = Math.ceil(Math.max.apply(Math, _toConsumableArray(arr)) / yEqual); + var pow = len.toString().length - 1; + pow = pow > 2 ? 2 : pow; + return Math.ceil(len / Math.pow(10, pow)) * Math.pow(10, pow); + }; + + /** + * 图表类 + * @param {String} canvas 画布元素id + * @param {Object} options 图表参数 可选参数 + */ + + var Schart = + /*#__PURE__*/ + function () { + function Schart(id, options) { + _classCallCheck(this, Schart); + + this.canvas = initCanvas(id); + this.ctx = this.canvas.getContext('2d'); + this.type = 'bar'; + this.showValue = true; // 是否在图表中显示数值 + + this.showGrid = true; // 是否显示网格 + + this.topPadding = 60 * DPI; // 图表上边距 + + this.leftPadding = 50 * DPI; // 图表左边距 + + this.rightPadding = 10 * DPI; // 图表右边距 + + this.bottomPadding = 50 * DPI; // 图表下边距 + + this.yEqual = 5; // y轴分成5等分 + + this.yLength = 0; // y轴坐标点之间的真实长度 + + this.xLength = 0; // x轴坐标点之间的真实长度 + + this.ySpace = 0; // y轴坐标点之间显示的间距 + + this.xRorate = 0; // x轴坐标点文本旋转角度 + + this.yRorate = 0; // y轴坐标点文本旋转角度 + + this.bgColor = '#fff'; // 默认背景颜色 + + this.axisColor = '#666'; // 坐标轴颜色 + + this.gridColor = '#eee'; // 网格颜色 + + this.title = { + // 标题 + text: '', + color: '#666', + position: 'top', + font: 'bold ' + 18 * DPI + 'px Arial', + top: S10, + bottom: S5 + }; + this.legend = { + // 图例 + display: true, + position: 'top', + color: '#666', + font: 14 * DPI + 'px Arial', + top: 45 * DPI, + bottom: 15 * DPI, + textWidth: 0 + }; + this.radius = 100 * DPI; // 饼图半径和环形图外圆半径 + + this.innerRadius = 60 * DPI; // 环形图内圆半径 + + this.colorList = [// 颜色列表 + '#4A90E2', '#F5A623', '#ff5858', '#5e64ff', '#2AC766', '#743ee2', '#b554ff', '#199475']; + this.init(options); + } + + _createClass(Schart, [{ + key: "init", + value: function init(options) { + options.title = Object.assign({}, this.title, options.title); + options.legend = Object.assign({}, this.legend, options.legend); + Object.assign(this, options); + + if (!options.labels || !options.labels.length) { + throw new Error('缺少主要参数labels'); + } + + if (!options.datasets || !options.datasets.length) { + throw new Error('缺少主要参数datasets'); + } + + this.drawBackground(); + + if (this.type === 'bar' || this.type === 'line') { + this.renderBarChart(); + } else { + this.renderPieChart(); + } + + this.drawLegend(); + } + }, { + key: "renderBarChart", + value: function renderBarChart() { + this.yLength = Math.floor((this.canvas.height - this.topPadding - this.bottomPadding - S10) / this.yEqual); + this.xLength = Math.floor((this.canvas.width - this.leftPadding - this.rightPadding - S10) / this.labels.length); + this.ySpace = getYSpace(this.datasets, this.yEqual); + this.drawXAxis(); + this.drawYAxis(); + this.drawBarContent(); + } // 绘制柱形图和折线图内容 + + }, { + key: "drawBarContent", + value: function drawBarContent() { + var ctx = this.ctx; + var length = this.datasets.length; + ctx.beginPath(); + + for (var i = 0; i < length; i++) { + ctx.font = this.legend.font; // 计算图例文本长度 + + this.legend.textWidth += Math.ceil(ctx.measureText(this.datasets[i].label).width); + ctx.fillStyle = ctx.strokeStyle = this.datasets[i].fillColor || this.colorList[i]; + var item = this.datasets[i].data; + + for (var j = 0; j < item.length; j++) { + if (j > this.labels.length - 1) { + // 兼容数据比labels多,多的部分不显示 + continue; + } + + var space = this.xLength / (length + 1); + var ratio = this.yLength / this.ySpace; + var left = this.leftPadding + this.xLength * j + space * (i + 1 / 2); + var right = left + space; + var bottom = this.canvas.height - this.bottomPadding; + var top = bottom - item[j] * ratio; + + if (this.type === 'bar') { + ctx.fillRect(left, top, right - left, bottom - top); + this.drawValue(item[j], left + space / 2, top - S5); + } else if (this.type === 'line') { + var x = this.leftPadding + this.xLength * (j + 1 / 2); // 折点小圆圈 + + ctx.beginPath(); + ctx.arc(x, top, 3 * DPI, 0, 2 * Math.PI, true); + ctx.fill(); + + if (j !== 0) { + ctx.beginPath(); + ctx.strokeStyle = this.datasets[i].fillColor || this.colorList[i]; + ctx.lineWidth = 2 * DPI; + ctx.moveTo(x - this.xLength, bottom - item[j - 1] * ratio); + ctx.lineTo(x, top); + ctx.stroke(); + ctx.lineWidth = 1 * DPI; + } + + this.drawValue(item[j], x, top - S10); + } + } + } + + ctx.stroke(); + } // 绘制饼状图和环形图 + + }, { + key: "renderPieChart", + value: function renderPieChart() { + var ctx = this.ctx; + var length = this.labels.length; + var item = this.datasets[0]; + var data = item.data; // 获取所有数据的总和 + + var total = data.reduce(function (prev, current) { + return prev + current; + }); // 用于计算每块扇形弧度的起始位和终止位 + + var circular = -Math.PI / 2; + var x = this.canvas.width / 2; + var y = this.canvas.height / 2; + + for (var i = 0; i < length; i++) { + ctx.font = this.legend.font; // 计算图例文本长度 + + this.legend.textWidth += Math.ceil(ctx.measureText(this.labels[i]).width); + ctx.beginPath(); + ctx.strokeStyle = ctx.fillStyle = item.colorList && item.colorList[i] || this.colorList[i]; + ctx.moveTo(x, y); + var start = circular; + circular += data[i] / total * 2 * Math.PI; + var end = circular; // 绘制扇形 + + ctx.arc(x, y, this.radius, start, end); + ctx.closePath(); + ctx.fill(); // 绘制数据 + + var middle = (start + end) / 2; + this.drawPieValue(data[i], middle); + } // 环形图在饼状图基础上再绘制一个内圆 + + + if (this.type === 'ring') { + ctx.beginPath(); + ctx.fillStyle = this.bgColor; + ctx.arc(x, y, this.innerRadius, 0, 2 * Math.PI); + ctx.closePath(); + ctx.fill(); + } + } // 绘制柱形图和折线图的数据值显示 + + }, { + key: "drawValue", + value: function drawValue(value, x, y) { + var ctx = this.ctx; + + if (!this.showValue) { + return; + } + + ctx.textBaseline = 'middle'; + ctx.font = 12 * DPI + 'px Arial'; + ctx.textAlign = 'center'; + ctx.fillText(value, x, y); + } // 绘制饼状图和环形图的数据值显示 + + }, { + key: "drawPieValue", + value: function drawPieValue(value, middle) { + var ctx = this.ctx; + + if (!this.showValue) { + return; + } + + var x = this.canvas.width / 2; + var y = this.canvas.height / 2; + var x1 = Math.ceil(Math.abs(this.radius * Math.cos(middle))); + var y1 = Math.floor(Math.abs(this.radius * Math.sin(middle))); + ctx.textBaseline = 'middle'; // 绘制各个扇形边上的数据 + + if (this.showValue) { + if (middle <= 0) { + ctx.textAlign = 'left'; + ctx.moveTo(x + x1, y - y1); + ctx.lineTo(x + x1 + S10, y - y1 - S10); + ctx.moveTo(x + x1 + S10, y - y1 - S10); + ctx.lineTo(x + x1 + 3 * S10, y - y1 - S10); + ctx.stroke(); + ctx.fillText(value, x + x1 + 3.5 * S10, y - y1 - S10); + } else if (middle > 0 && middle <= Math.PI / 2) { + ctx.textAlign = 'left'; + ctx.moveTo(x + x1, y + y1); + ctx.lineTo(x + x1 + S10, y + y1 + S10); + ctx.moveTo(x + x1 + S10, y + y1 + S10); + ctx.lineTo(x + x1 + 3 * S10, y + y1 + S10); + ctx.stroke(); + ctx.fillText(value, x + x1 + 3.5 * S10, y + y1 + S10); + } else if (middle > Math.PI / 2 && middle < Math.PI) { + ctx.textAlign = 'right'; + ctx.moveTo(x - x1, y + y1); + ctx.lineTo(x - x1 - S10, y + y1 + S10); + ctx.moveTo(x - x1 - S10, y + y1 + S10); + ctx.lineTo(x - x1 - 3 * S10, y + y1 + S10); + ctx.stroke(); + ctx.fillText(value, x - x1 - 3.5 * S10, y + y1 + S10); + } else { + ctx.textAlign = 'right'; + ctx.moveTo(x - x1, y - y1); + ctx.lineTo(x - x1 - S10, y - y1 - S10); + ctx.moveTo(x - x1 - S10, y - y1 - S10); + ctx.lineTo(x - x1 - 3 * S10, y - y1 - S10); + ctx.stroke(); + ctx.fillText(value, x - x1 - 3.5 * S10, y - y1 - S10); + } + } + } // 绘制图表背景 + + }, { + key: "drawBackground", + value: function drawBackground() { + this.ctx.fillStyle = this.bgColor; + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + this.drawTitle(); + } // 绘制图表标题 + + }, { + key: "drawTitle", + value: function drawTitle() { + var title = this.title; + + if (!title.text) { + return; + } + + var ctx = this.ctx; + ctx.beginPath(); + ctx.font = title.font; + ctx.textAlign = 'center'; + ctx.fillStyle = title.color; + + if (title.position === 'top') { + ctx.textBaseline = 'top'; + ctx.fillText(title.text, this.canvas.width / 2, title.top); + } else { + ctx.textBaseline = 'bottom'; + ctx.fillText(title.text, this.canvas.width / 2, this.canvas.height - title.bottom); + } + } // 绘制X轴 + + }, { + key: "drawXAxis", + value: function drawXAxis() { + var ctx = this.ctx; + var y = this.canvas.height - this.bottomPadding + 0.5; // x轴坐标点 + + ctx.beginPath(); + ctx.strokeStyle = this.axisColor; + ctx.moveTo(this.leftPadding, y); + ctx.lineTo(this.canvas.width - this.rightPadding, y); + ctx.stroke(); + this.drawXPoint(); + } // 绘制X轴坐标点 + + }, { + key: "drawXPoint", + value: function drawXPoint() { + var ctx = this.ctx; + ctx.beginPath(); + ctx.font = 12 * DPI + 'px Microsoft YaHei'; + ctx.textAlign = this.xRorate ? 'right' : 'center'; + ctx.textBaseline = 'top'; + ctx.fillStyle = this.axisColor; + + for (var i = 0; i < this.labels.length; i++) { + var text = this.labels[i]; + var x = this.leftPadding + this.xLength * (i + 1) + 0.5; + var y = this.canvas.height - this.bottomPadding; + + if (this.showGrid) { + // 绘制网格线 + ctx.strokeStyle = this.gridColor; + ctx.moveTo(x, y); + ctx.lineTo(x, this.topPadding + S10); + } else { + ctx.moveTo(x, y); + ctx.lineTo(x, y - S5); + } + + ctx.stroke(); + ctx.save(); // 允许文本旋转 + + ctx.translate(x - this.xLength / 2, y + S5); + ctx.rotate(-this.xRorate * Math.PI / 180); + ctx.fillText(text, 0, 0); + ctx.restore(); + } + } // 绘制Y轴 + + }, { + key: "drawYAxis", + value: function drawYAxis() { + var ctx = this.ctx; + ctx.beginPath(); + ctx.strokeStyle = this.axisColor; + ctx.moveTo(this.leftPadding - 0.5, this.canvas.height - this.bottomPadding + 0.5); + ctx.lineTo(this.leftPadding - 0.5, this.topPadding + 0.5); + ctx.stroke(); + this.drawYPoint(); + } // 绘制Y轴坐标点 + + }, { + key: "drawYPoint", + value: function drawYPoint() { + var ctx = this.ctx; + ctx.font = 12 * DPI + 'px Microsoft YaHei'; + ctx.textAlign = 'right'; + ctx.textBaseline = 'middle'; + ctx.beginPath(); + + for (var i = 0; i < this.yEqual; i++) { + var x = this.leftPadding; + var y = this.canvas.height - this.bottomPadding - this.yLength * (i + 1) + 0.5; + + if (this.showGrid) { + // 绘制网格线 + ctx.strokeStyle = this.gridColor; + ctx.moveTo(x, y); + ctx.lineTo(this.canvas.width - this.rightPadding - S10, y); + } else { + ctx.strokeStyle = this.axisColor; + ctx.moveTo(x - S5, y); + ctx.lineTo(x, y); + } + + ctx.stroke(); + ctx.save(); + ctx.fillStyle = this.axisColor; // 文本旋转 + + ctx.translate(x - S10, y); + ctx.rotate(-this.yRorate * Math.PI / 180); + ctx.fillText(this.ySpace * (i + 1), 0, 0); + ctx.restore(); + } + } // 绘制图例 + + }, { + key: "drawLegend", + value: function drawLegend() { + var legend = this.legend; // 是否显示图例 + + if (legend.display) { + var ctx = this.ctx; + var pie = this.type === 'pie' || this.type === 'ring'; + ctx.beginPath(); + ctx.font = legend.font; + ctx.textAlign = 'left'; + ctx.textBaseline = 'middle'; + var length = pie ? this.labels.length : this.datasets.length; + var x = (this.canvas.width - (this.legend.textWidth + (5 * length - 2) * S10)) / 2; + var textWidth = 0; + + for (var i = 0; i < length; i++) { + var item = pie ? this.datasets[0] : this.datasets[i]; + var text = (pie ? this.labels[i] : item.label) || ''; + ctx.fillStyle = item.colorList && item.colorList[i] || item.fillColor || this.colorList[i]; // 区分图例位置显示,分别为top,bottom,left + + if (legend.position === 'top') { + this.drawLegendIcon(x + 5 * S10 * i + textWidth, legend.top - S5, 2 * S10, S10); + ctx.fillStyle = legend.color; + ctx.fillText(text, x + (5 * i + 3) * S10 + textWidth, legend.top); + } else if (legend.position === 'bottom') { + this.drawLegendIcon(x + 5 * S10 * i + textWidth, this.canvas.height - legend.bottom - S5, 2 * S10, S10); + ctx.fillStyle = legend.color; + ctx.fillText(text, x + (5 * i + 3) * S10 + textWidth, this.canvas.height - legend.bottom); + } else { + ctx.fillRect(S10, legend.top + 2 * S10 * i, 2 * S10, S10); + ctx.fillStyle = legend.color; + ctx.fillText(text, 4 * S10, legend.top + 2 * S10 * i + 0.5 * S10); + } + + textWidth += Math.ceil(ctx.measureText(text).width); + } + } + } // 绘制图例每项里的小图标,折线图为线条,其他为矩形 + + }, { + key: "drawLegendIcon", + value: function drawLegendIcon(x, y, w, h) { + var ctx = this.ctx; + + if (this.type === 'line') { + ctx.beginPath(); + ctx.strokeStyle = ctx.fillStyle; + ctx.lineWidth = 2 * DPI; + ctx.moveTo(x, y + S5); + ctx.lineTo(x + 2 * S10, y + S5); + ctx.stroke(); + ctx.lineWidth = 1 * DPI; + ctx.arc(x + S10, y + S5, 3 * DPI, 0, 2 * Math.PI, true); + ctx.fill(); + } else { + ctx.fillRect(x, y, w, h); + } + } + }]); + + return Schart; + }(); + + return Schart; + +}))); diff --git a/lib/sChart.min.js b/lib/sChart.min.js index 077d63d..666fadc 100644 --- a/lib/sChart.min.js +++ b/lib/sChart.min.js @@ -1,6 +1 @@ -/*! - * sChart JavaScript Library v2.0.1 - * http://blog.gdfengshuo.com/example/sChart/ | Released under the MIT license - * Date: 2018-04-16T18:59Z - */ -!function(t,i){"function"==typeof define&&define.amd?define(function(){return i(t)}):"object"==typeof exports?module.exports=i:t.sChart=i(t)}(this,function(t){"use strict";function i(t,i,h,s){this.canvas=document.getElementById(t),this.ctx=this.canvas.getContext("2d"),this.dpi=window.devicePixelRatio||1,this.type=i,this.data=h,this.dataLength=this.data.length,this.showValue=!0,this.autoWidth=!1,this.width=this.canvas.width*this.dpi,this.height=this.canvas.height*this.dpi,this.topPadding=50*this.dpi,this.leftPadding=50*this.dpi,this.rightPadding=0*this.dpi,this.bottomPadding=50*this.dpi,this.yEqual=5,this.yLength=0,this.xLength=0,this.yFictitious=0,this.yRatio=0,this.bgColor="#ffffff",this.fillColor="#1E9FFF",this.axisColor="#666666",this.contentColor="#eeeeee",this.titleColor="#000000",this.title="",this.titlePosition="top",this.radius=100*this.dpi,this.innerRadius=70*this.dpi,this.colorList=["#1E9FFF","#13CE66","#F7BA2A","#FF4949","#72f6ff","#199475","#e08031","#726dd1"],this.legendColor="#000000",this.legendTop=40*this.dpi,this.totalValue=this.getTotalValue(),this.init(s)}return i.prototype={init:function(t){if(0===this.dataLength)return!1;if(t){var i=["topPadding","leftPadding","rightPadding","bottomPadding","radius","innerRadius","legendTop"];for(var h in t)"colorList"===h&&Array.isArray(t[h])?this[h]=t[h].concat(this[h]):i.indexOf(h)>-1?this[h]=t[h]*this.dpi:this[h]=t[h]}t.autoWidth?(this.width=this.canvas.width=this.canvas.parentNode.offsetWidth*this.dpi,this.height=this.canvas.height=this.canvas.parentNode.offsetHeight*this.dpi,this.canvas.setAttribute("style","width:"+this.canvas.parentNode.offsetWidth+"px;height:"+this.canvas.parentNode.offsetHeight+"px;")):(this.canvas.setAttribute("style","width:"+this.canvas.width+"px;height:"+this.canvas.height+"px;"),this.canvas.width*=this.dpi,this.canvas.height*=this.dpi),"bar"===this.type||"line"===this.type?(this.yLength=Math.floor((this.height-this.topPadding-this.bottomPadding-10)/this.yEqual),this.xLength=Math.floor((this.width-this.leftPadding-this.rightPadding-10)/this.dataLength),this.yFictitious=this.getYFictitious(this.data),this.yRatio=this.yLength/this.yFictitious,this.drawBarUpdate()):this.drawPieUpdate()},drawBarUpdate:function(){this.ctx.fillStyle=this.bgColor,this.ctx.fillRect(0,0,this.width,this.height),this.drawAxis(),this.drawPoint(),this.drawTitle(),this.drawBarChart()},drawPieUpdate:function(){this.ctx.fillStyle=this.bgColor,this.ctx.fillRect(0,0,this.width,this.height),this.drawLegend(),this.drawTitle(),this.drawPieChart()},drawBarChart:function(){this.ctx.fillStyle=this.fillColor,this.ctx.strokeStyle=this.fillColor;for(var t=0;t0&&this.data[a].middle<=Math.PI/2?(this.ctx.textAlign="left",this.ctx.moveTo(t+h,i+s),this.ctx.lineTo(t+h+10,i+s+10),this.ctx.moveTo(t+h+10,i+s+10),this.ctx.lineTo(t+h+this.radius/2,i+s+10),this.ctx.stroke(),this.ctx.fillText(this.data[a].value,t+h+5+this.radius/2,i+s+15)):this.data[a].middle>Math.PI/2&&this.data[a].middle=40?this.ctx.fillText(this.title,this.width/2,this.height-5):this.ctx.fillText(this.title,this.width/2,this.topPadding/2+5))},drawLegend:function(){for(var t=0;t2?2:s,Math.ceil(h/Math.pow(10,s))*Math.pow(10,s)},getTotalValue:function(){for(var t=0,i=0;ithis.labels.length-1)){var o=this.xLength/(i+1),h=this.yLength/this.ySpace,l=this.leftPadding+this.xLength*s+o*(e+.5),n=l+o,r=this.canvas.height-this.bottomPadding,d=r-a[s]*h;if("bar"===this.type)t.fillRect(l,d,n-l,r-d),this.drawValue(a[s],l+o/2,d-u);else if("line"===this.type){var c=this.leftPadding+this.xLength*(s+.5);t.beginPath(),t.arc(c,d,3*f,0,2*Math.PI,!0),t.fill(),0!==s&&(t.beginPath(),t.strokeStyle=this.datasets[e].fillColor||this.colorList[e],t.lineWidth=2*f,t.moveTo(c-this.xLength,r-a[s-1]*h),t.lineTo(c,d),t.stroke(),t.lineWidth=1*f),this.drawValue(a[s],c,d-g)}}}t.stroke()}},{key:"renderPieChart",value:function(){for(var t=this.ctx,i=this.labels.length,e=this.datasets[0],a=e.data,s=a.reduce(function(t,i){return t+i}),o=-Math.PI/2,h=this.canvas.width/2,l=this.canvas.height/2,n=0;nMath.PI/2&&i= 2.1.2 < 3" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", + "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "inquirer": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.0.tgz", + "integrity": "sha512-rSdC7zelHdRQFkWnhsMu2+2SO41mpv2oF2zy4tMhmiLWkcKbOAs87fWAJhVXttKVwhdZvymvnuM95EyEXg2/tQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^2.4.2", + "cli-cursor": "^3.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^4.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "jest-worker": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", + "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "dev": true, + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json5": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", + "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-releases": { + "version": "1.1.39", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.39.tgz", + "integrity": "sha512-8MRC/ErwNCHOlAFycy9OPca46fQYUjbJRDcZTHVWIGXIjYLM73k70vv3WkYutVnM4cCo4hE0MqBVVZjP6vjISA==", + "dev": true, + "requires": { + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz", + "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "regenerator-transform": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", + "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", + "dev": true, + "requires": { + "babel-runtime": "^6.18.0", + "babel-types": "^6.19.0", + "private": "^0.1.6" + } + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "regexpu-core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", + "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rollup-plugin-babel": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.3.3.tgz", + "integrity": "sha512-tKzWOCmIJD/6aKNz0H1GMM+lW1q9KyFubbWzGiOG540zxPPifnEAHTZwjo0g991Y+DyOZcLqBgqOdqazYE5fkw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "rollup-pluginutils": "^2.8.1" + } + }, + "rollup-plugin-eslint": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-eslint/-/rollup-plugin-eslint-7.0.0.tgz", + "integrity": "sha512-u35kXiY11ULeNQGTlRkYx7uGJ/hS/Dx3wj8f9YVC3oMLTGU9fOqQJsAKYtBFZU3gJ8Vt3gu8ppB1vnKl+7gatQ==", + "dev": true, + "requires": { + "eslint": "^6.0.0", + "rollup-pluginutils": "^2.7.1" + } + }, + "rollup-plugin-uglify": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-uglify/-/rollup-plugin-uglify-6.0.3.tgz", + "integrity": "sha512-PIv3CfhZJlOG8C85N0GX+uK09TPggmAS6Nk6fpp2ELzDAV5VUhNzOURDU2j7+MwuRr0zq9IZttUTADc/jH8Gkg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "jest-worker": "^24.0.0", + "serialize-javascript": "^1.9.0", + "uglify-js": "^3.4.9" + } + }, + "rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "requires": { + "estree-walker": "^0.6.1" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "rxjs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "serialize-javascript": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", + "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.1.0.tgz", + "integrity": "sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^5.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-fest": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.5.2.tgz", + "integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==", + "dev": true + }, + "uglify-js": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.5.tgz", + "integrity": "sha512-7L3W+Npia1OCr5Blp4/Vw83tK1mu5gnoIURtT1fUVfQ3Kf8WStWV6NJz0fdoBJZls0KlweruRTLVe6XLafmy5g==", + "dev": true, + "requires": { + "commander": "~2.20.3", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", + "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", + "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + } + } +} diff --git a/package.json b/package.json index b4fd8fa..8ef7014 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,26 @@ { "name": "schart.js", - "version": "2.0.1", - "description": "Small & aimple HTML5 charts", - "main": "lib/sChart.js", + "version": "3.0.0", + "description": "Small & simple HTML5 charts", + "main": "lib/schart.min.js", "directories": { "lib": "lib" }, "dependencies": {}, - "devDependencies": {}, + "devDependencies": { + "@babel/core": "^7.6.4", + "@babel/preset-env": "^7.6.3", + "babel-plugin-external-helpers": "^6.22.0", + "babel-preset-env": "^1.7.0", + "cross-env": "^6.0.3", + "eslint": "^6.6.0", + "rollup-plugin-babel": "^4.3.3", + "rollup-plugin-eslint": "^7.0.0", + "rollup-plugin-uglify": "^6.0.3" + }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "watch": "rollup -c -w", + "build": "cross-env NODE_ENV=development rollup -c && cross-env NODE_ENV=production rollup -c" }, "repository": { "type": "git", @@ -17,7 +28,7 @@ }, "keywords": [ "schart.js", - "charts", + "chart", "图表", "js插件" ], @@ -26,5 +37,5 @@ "bugs": { "url": "https://github.com/lin-xin/schart.js/issues" }, - "homepage": "http://test.omwteam.com/sChart/" -} + "homepage": "https://lin-xin.gitee.io/example/schart/" +} \ No newline at end of file diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..3f2d233 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,26 @@ +import babel from 'rollup-plugin-babel'; +import { eslint } from 'rollup-plugin-eslint'; +import { uglify } from 'rollup-plugin-uglify'; + +const pathName = process.env.NODE_ENV === 'production' ? 'schart.min' : 'schart'; + +export default { + input: './src/index.js', + output: { + file: `./lib/${pathName}.js`, + format: 'umd', + name: 'Schart' + }, + plugins: [ + eslint({ + throwOnError: true, + throwOnWarning: true, + include: ['src/**'], + exclude: ['node_modules/**'] + }), + babel({ + exclude: 'node_modules/**' + }), + (process.env.NODE_ENV === 'production' && uglify()) + ] +}; \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..036ea70 --- /dev/null +++ b/src/index.js @@ -0,0 +1,425 @@ +import { + DPI, + S10, + S5, + getYSpace, + initCanvas +} from './utils.js'; +/** + * 图表类 + * @param {String} canvas 画布元素id + * @param {Object} options 图表参数 可选参数 + */ +class Schart { + constructor(id, options) { + this.canvas = initCanvas(id); + this.ctx = this.canvas.getContext('2d'); + this.type = 'bar'; + this.showValue = true; // 是否在图表中显示数值 + this.showGrid = true; // 是否显示网格 + this.topPadding = 60 * DPI; // 图表上边距 + this.leftPadding = 50 * DPI; // 图表左边距 + this.rightPadding = 10 * DPI; // 图表右边距 + this.bottomPadding = 50 * DPI; // 图表下边距 + this.yEqual = 5; // y轴分成5等分 + this.yLength = 0; // y轴坐标点之间的真实长度 + this.xLength = 0; // x轴坐标点之间的真实长度 + this.ySpace = 0; // y轴坐标点之间显示的间距 + this.xRorate = 0; // x轴坐标点文本旋转角度 + this.yRorate = 0; // y轴坐标点文本旋转角度 + this.bgColor = '#fff'; // 默认背景颜色 + this.axisColor = '#666'; // 坐标轴颜色 + this.gridColor = '#eee'; // 网格颜色 + this.title = { // 标题 + text: '', + color: '#666', + position: 'top', + font: 'bold ' + 18 * DPI + 'px Arial', + top: S10, + bottom: S5 + }; + this.legend = { // 图例 + display: true, + position: 'top', + color: '#666', + font: 14 * DPI + 'px Arial', + top: 45 * DPI, + bottom: 15 * DPI, + textWidth: 0 + }; + this.radius = 100 * DPI; // 饼图半径和环形图外圆半径 + this.innerRadius = 60 * DPI; // 环形图内圆半径 + this.colorList = [ // 颜色列表 + '#4A90E2', + '#F5A623', + '#ff5858', + '#5e64ff', + '#2AC766', + '#743ee2', + '#b554ff', + '#199475' + ]; + this.init(options); + } + + init(options) { + options.title = Object.assign({}, this.title, options.title); + options.legend = Object.assign({}, this.legend, options.legend); + Object.assign(this, options); + + if (!options.labels || !options.labels.length) { + throw new Error('缺少主要参数labels'); + } + if (!options.datasets || !options.datasets.length) { + throw new Error('缺少主要参数datasets'); + } + + this.drawBackground(); + if (this.type === 'bar' || this.type === 'line') { + this.renderBarChart(); + } else { + this.renderPieChart(); + } + this.drawLegend(); + } + + renderBarChart() { + this.yLength = Math.floor((this.canvas.height - this.topPadding - this.bottomPadding - S10) / this.yEqual); + this.xLength = Math.floor((this.canvas.width - this.leftPadding - this.rightPadding - S10) / this.labels.length); + this.ySpace = getYSpace(this.datasets, this.yEqual); + this.drawXAxis(); + this.drawYAxis(); + this.drawBarContent(); + } + + // 绘制柱形图和折线图内容 + drawBarContent() { + let ctx = this.ctx; + let length = this.datasets.length; + ctx.beginPath(); + for (let i = 0; i < length; i++) { + ctx.font = this.legend.font; + // 计算图例文本长度 + this.legend.textWidth += Math.ceil(ctx.measureText(this.datasets[i].label).width); + ctx.fillStyle = ctx.strokeStyle = this.datasets[i].fillColor || this.colorList[i]; + const item = this.datasets[i].data; + + for (let j = 0; j < item.length; j++) { + if (j > this.labels.length - 1) { + // 兼容数据比labels多,多的部分不显示 + continue; + } + let space = this.xLength / (length + 1); + let ratio = this.yLength / this.ySpace; + let left = this.leftPadding + this.xLength * j + space * (i + 1 / 2); + let right = left + space; + let bottom = this.canvas.height - this.bottomPadding; + let top = bottom - item[j] * ratio; + + if (this.type === 'bar') { + ctx.fillRect( + left, + top, + right - left, + bottom - top + ); + this.drawValue(item[j], left + space / 2, top - S5); + } else if (this.type === 'line') { + let x = this.leftPadding + this.xLength * (j + 1 / 2); + // 折点小圆圈 + ctx.beginPath(); + ctx.arc(x, top, 3 * DPI, 0, 2 * Math.PI, true); + ctx.fill(); + if (j !== 0) { + ctx.beginPath(); + ctx.strokeStyle = this.datasets[i].fillColor || this.colorList[i]; + ctx.lineWidth = 2 * DPI; + ctx.moveTo(x - this.xLength, bottom - item[j - 1] * ratio); + ctx.lineTo(x, top); + ctx.stroke(); + ctx.lineWidth = 1 * DPI; + } + this.drawValue(item[j], x, top - S10); + } + } + } + ctx.stroke(); + } + + // 绘制饼状图和环形图 + renderPieChart() { + let ctx = this.ctx; + let length = this.labels.length; + let item = this.datasets[0]; + let data = item.data; + // 获取所有数据的总和 + let total = data.reduce((prev, current) => { + return prev + current; + }); + // 用于计算每块扇形弧度的起始位和终止位 + let circular = -Math.PI / 2; + let x = this.canvas.width / 2; + let y = this.canvas.height / 2; + for (let i = 0; i < length; i++) { + ctx.font = this.legend.font; + // 计算图例文本长度 + this.legend.textWidth += Math.ceil(ctx.measureText(this.labels[i]).width); + ctx.beginPath(); + + ctx.strokeStyle = ctx.fillStyle = (item.colorList && item.colorList[i]) || this.colorList[i]; + ctx.moveTo(x, y); + + let start = circular; + circular += data[i] / total * 2 * Math.PI; + let end = circular; + // 绘制扇形 + ctx.arc(x, y, this.radius, start, end); + ctx.closePath(); + ctx.fill(); + // 绘制数据 + let middle = (start + end) / 2; + this.drawPieValue(data[i], middle); + } + // 环形图在饼状图基础上再绘制一个内圆 + if (this.type === 'ring') { + ctx.beginPath(); + ctx.fillStyle = this.bgColor; + ctx.arc(x, y, this.innerRadius, 0, 2 * Math.PI); + ctx.closePath(); + ctx.fill(); + } + } + + // 绘制柱形图和折线图的数据值显示 + drawValue(value, x, y) { + let ctx = this.ctx; + if (!this.showValue) { + return ; + } + ctx.textBaseline = 'middle'; + ctx.font = 12 * DPI + 'px Arial'; + ctx.textAlign = 'center'; + ctx.fillText(value, x, y); + } + + // 绘制饼状图和环形图的数据值显示 + drawPieValue(value, middle) { + let ctx = this.ctx; + if (!this.showValue) { + return ; + } + let x = this.canvas.width / 2; + let y = this.canvas.height / 2; + let x1 = Math.ceil(Math.abs(this.radius * Math.cos(middle))); + let y1 = Math.floor(Math.abs(this.radius * Math.sin(middle))); + ctx.textBaseline = 'middle'; + // 绘制各个扇形边上的数据 + if (this.showValue) { + if (middle <= 0) { + ctx.textAlign = 'left'; + ctx.moveTo(x + x1, y - y1); + ctx.lineTo(x + x1 + S10, y - y1 - S10); + ctx.moveTo(x + x1 + S10, y - y1 - S10); + ctx.lineTo(x + x1 + 3 * S10, y - y1 - S10); + ctx.stroke(); + ctx.fillText(value, x + x1 + 3.5 * S10, y - y1 - S10); + } else if (middle > 0 && middle <= Math.PI / 2) { + ctx.textAlign = 'left'; + ctx.moveTo(x + x1, y + y1); + ctx.lineTo(x + x1 + S10, y + y1 + S10); + ctx.moveTo(x + x1 + S10, y + y1 + S10); + ctx.lineTo(x + x1 + 3 * S10, y + y1 + S10); + ctx.stroke(); + ctx.fillText(value, x + x1 + 3.5 * S10, y + y1 + S10); + } else if (middle > Math.PI / 2 && middle < Math.PI) { + ctx.textAlign = 'right'; + ctx.moveTo(x - x1, y + y1); + ctx.lineTo(x - x1 - S10, y + y1 + S10); + ctx.moveTo(x - x1 - S10, y + y1 + S10); + ctx.lineTo(x - x1 - 3 * S10, y + y1 + S10); + ctx.stroke(); + ctx.fillText(value, x - x1 - 3.5 * S10, y + y1 + S10); + } else { + ctx.textAlign = 'right'; + ctx.moveTo(x - x1, y - y1); + ctx.lineTo(x - x1 - S10, y - y1 - S10); + ctx.moveTo(x - x1 - S10, y - y1 - S10); + ctx.lineTo(x - x1 - 3 * S10, y - y1 - S10); + ctx.stroke(); + ctx.fillText(value, x - x1 - 3.5 * S10, y - y1 - S10); + } + } + } + + // 绘制图表背景 + drawBackground() { + this.ctx.fillStyle = this.bgColor; + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + this.drawTitle(); + } + + // 绘制图表标题 + drawTitle() { + let title = this.title; + if (!title.text) { + return; + } + let ctx = this.ctx; + ctx.beginPath(); + ctx.font = title.font; + ctx.textAlign = 'center'; + ctx.fillStyle = title.color; + if (title.position === 'top') { + ctx.textBaseline = 'top'; + ctx.fillText(title.text, this.canvas.width / 2, title.top); + } else { + ctx.textBaseline = 'bottom'; + ctx.fillText(title.text, this.canvas.width / 2, this.canvas.height - title.bottom); + } + } + + // 绘制X轴 + drawXAxis() { + let ctx = this.ctx; + let y = this.canvas.height - this.bottomPadding + 0.5; + // x轴坐标点 + ctx.beginPath(); + ctx.strokeStyle = this.axisColor; + ctx.moveTo(this.leftPadding, y); + ctx.lineTo(this.canvas.width - this.rightPadding, y); + ctx.stroke(); + this.drawXPoint(); + } + + // 绘制X轴坐标点 + drawXPoint() { + let ctx = this.ctx; + ctx.beginPath(); + ctx.font = 12 * DPI + 'px Microsoft YaHei'; + ctx.textAlign = this.xRorate ? 'right' : 'center'; + ctx.textBaseline = 'top'; + ctx.fillStyle = this.axisColor; + for (let i = 0; i < this.labels.length; i++) { + let text = this.labels[i]; + let x = this.leftPadding + this.xLength * (i + 1) + 0.5; + let y = this.canvas.height - this.bottomPadding; + if (this.showGrid) { + // 绘制网格线 + ctx.strokeStyle = this.gridColor; + ctx.moveTo(x, y); + ctx.lineTo(x, this.topPadding + S10); + } else { + ctx.moveTo(x, y); + ctx.lineTo(x, y - S5); + } + ctx.stroke(); + ctx.save(); + // 允许文本旋转 + ctx.translate(x - this.xLength / 2, y + S5); + ctx.rotate(-this.xRorate * Math.PI / 180); + ctx.fillText(text, 0, 0); + ctx.restore(); + } + } + + // 绘制Y轴 + drawYAxis() { + let ctx = this.ctx; + ctx.beginPath(); + ctx.strokeStyle = this.axisColor; + ctx.moveTo(this.leftPadding - 0.5, this.canvas.height - this.bottomPadding + 0.5); + ctx.lineTo(this.leftPadding - 0.5, this.topPadding + 0.5); + ctx.stroke(); + this.drawYPoint(); + } + + // 绘制Y轴坐标点 + drawYPoint() { + let ctx = this.ctx; + ctx.font = 12 * DPI + 'px Microsoft YaHei'; + ctx.textAlign = 'right'; + ctx.textBaseline = 'middle'; + ctx.beginPath(); + for (let i = 0; i < this.yEqual; i++) { + let x = this.leftPadding; + let y = this.canvas.height - this.bottomPadding - this.yLength * (i + 1) + 0.5; + if (this.showGrid) { + // 绘制网格线 + ctx.strokeStyle = this.gridColor; + ctx.moveTo(x, y); + ctx.lineTo(this.canvas.width - this.rightPadding - S10, y); + } else { + ctx.strokeStyle = this.axisColor; + ctx.moveTo(x - S5, y); + ctx.lineTo(x, y); + } + ctx.stroke(); + ctx.save(); + ctx.fillStyle = this.axisColor; + // 文本旋转 + ctx.translate(x - S10, y); + ctx.rotate(-this.yRorate * Math.PI / 180); + ctx.fillText(this.ySpace * (i + 1), 0, 0); + ctx.restore(); + } + } + + // 绘制图例 + drawLegend() { + let legend = this.legend; + // 是否显示图例 + if (legend.display) { + let ctx = this.ctx; + let pie = this.type === 'pie' || this.type === 'ring'; + ctx.beginPath(); + ctx.font = legend.font; + ctx.textAlign = 'left'; + ctx.textBaseline = 'middle'; + + let length = pie ? this.labels.length : this.datasets.length; + let x = (this.canvas.width - (this.legend.textWidth + (5 * length - 2) * S10)) / 2; + let textWidth = 0; + + for (let i = 0; i < length; i++) { + const item = pie ? this.datasets[0] : this.datasets[i]; + const text = (pie ? this.labels[i] : item.label) || ''; + ctx.fillStyle = (item.colorList && item.colorList[i]) || item.fillColor || this.colorList[i]; + // 区分图例位置显示,分别为top,bottom,left + if (legend.position === 'top') { + this.drawLegendIcon(x + (5 * S10 * i) + textWidth, legend.top - S5, 2 * S10, S10); + ctx.fillStyle = legend.color; + ctx.fillText(text, x + (5 * i + 3) * S10 + textWidth, legend.top); + } else if (legend.position === 'bottom') { + this.drawLegendIcon(x + (5 * S10 * i) + textWidth, this.canvas.height - legend.bottom - S5, 2 * S10, S10); + ctx.fillStyle = legend.color; + ctx.fillText(text, x + (5 * i + 3) * S10 + textWidth, this.canvas.height - legend.bottom); + } else { + ctx.fillRect(S10, legend.top + 2 * S10 * i, 2 * S10, S10); + ctx.fillStyle = legend.color; + ctx.fillText(text, 4 * S10, legend.top + 2 * S10 * i + 0.5 * S10); + } + textWidth += Math.ceil(ctx.measureText(text).width); + } + } + } + + // 绘制图例每项里的小图标,折线图为线条,其他为矩形 + drawLegendIcon(x, y, w, h) { + let ctx = this.ctx; + if (this.type === 'line') { + ctx.beginPath(); + ctx.strokeStyle = ctx.fillStyle; + ctx.lineWidth = 2 * DPI; + ctx.moveTo(x, y + S5); + ctx.lineTo(x + 2 * S10, y + S5); + ctx.stroke(); + ctx.lineWidth = 1 * DPI; + ctx.arc(x + S10, y + S5, 3 * DPI, 0, 2 * Math.PI, true); + ctx.fill(); + } else { + ctx.fillRect(x, y, w, h); + } + } +} + +export default Schart; \ No newline at end of file diff --git a/src/sChart.js b/src/sChart.js deleted file mode 100644 index 98255b5..0000000 --- a/src/sChart.js +++ /dev/null @@ -1,338 +0,0 @@ -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - define(function () { - return factory(root); - }); - } else if (typeof exports === 'object') { - module.exports = factory; - } else { - root.sChart = factory(root); - } -})(this, function (root) { - 'use strict'; - /** - * 生成图表 - * @param {String} canvas 画布元素id - * @param {String} type 图表类型 - * @param {Array} data 生成图表的数据 - * @param {Object} options 图表参数 可选参数 - */ - function sChart(canvas, type, data, options) { - this.canvas = document.getElementById(canvas); - this.ctx = this.canvas.getContext('2d'); - this.dpi = window.devicePixelRatio || 1; - this.type = type; - this.data = data; // 存放图表数据 - this.dataLength = this.data.length; // 图表数据的长度 - this.showValue = true; // 是否在图表中显示数值 - this.autoWidth = false; // 宽高是否自适应 - this.width = this.canvas.width * this.dpi; // canvas 宽度 - this.height = this.canvas.height * this.dpi; // canvas 高度 - this.topPadding = 50 * this.dpi; - this.leftPadding = 50 * this.dpi; - this.rightPadding = 0 * this.dpi; - this.bottomPadding = 50 * this.dpi; - this.yEqual = 5; // y轴分成5等分 - this.yLength = 0; // y轴坐标点之间的真实长度 - this.xLength = 0; // x轴坐标点之间的真实长度 - this.yFictitious = 0; // y轴坐标点之间显示的间距 - this.yRatio = 0; // y轴坐标真实长度和坐标间距的比 - this.bgColor = '#ffffff'; // 默认背景颜色 - this.fillColor = '#1E9FFF'; // 默认填充颜色 - this.axisColor = '#666666'; // 坐标轴颜色 - this.contentColor = '#eeeeee'; // 内容横线颜色 - this.titleColor = '#000000'; // 图表标题颜色 - this.title = ''; // 图表标题 - this.titlePosition = 'top'; // 图表标题位置: top / bottom - this.radius = 100 * this.dpi; // 饼图半径和环形图外圆半径 - this.innerRadius = 70 * this.dpi; // 环形图内圆半径 - this.colorList = ['#1E9FFF', '#13CE66', '#F7BA2A', '#FF4949', '#72f6ff', '#199475', '#e08031', '#726dd1']; // 饼图颜色列表 - this.legendColor = '#000000'; // 图例文字颜色 - this.legendTop = 40 * this.dpi; // 图例距离顶部高度 - this.totalValue = this.getTotalValue(); // 获取饼图数据总和 - this.init(options); - } - sChart.prototype = { - init: function (options) { - if (this.dataLength === 0) { - return false; - } - if (options) { - var dpiList = ['topPadding', 'leftPadding', 'rightPadding', 'bottomPadding', 'radius', 'innerRadius', 'legendTop']; - for (var key in options) { - if (key === 'colorList' && Array.isArray(options[key])) { - this[key] = options[key].concat(this[key]) - } else if (dpiList.indexOf(key) > -1) { - this[key] = options[key] * this.dpi; - } else { - this[key] = options[key]; - } - } - } - - // 如果设置了自动宽高的话,则就宽高设为父元素的宽高 - if (options.autoWidth) { - this.width = this.canvas.width = this.canvas.parentNode.offsetWidth * this.dpi; - this.height = this.canvas.height = this.canvas.parentNode.offsetHeight * this.dpi; - this.canvas.setAttribute('style', 'width:' + this.canvas.parentNode.offsetWidth + 'px;height:' + this.canvas.parentNode.offsetHeight + 'px;') - } else { - this.canvas.setAttribute('style', 'width:' + this.canvas.width + 'px;height:' + this.canvas.height + 'px;'); - this.canvas.width *= this.dpi; - this.canvas.height *= this.dpi; - } - - if (this.type === 'bar' || this.type === 'line') { - this.yLength = Math.floor((this.height - this.topPadding - this.bottomPadding - 10) / this.yEqual); - this.xLength = Math.floor((this.width - this.leftPadding - this.rightPadding - 10) / this.dataLength); - this.yFictitious = this.getYFictitious(this.data); - this.yRatio = this.yLength / this.yFictitious; - this.drawBarUpdate(); - } else { - this.drawPieUpdate(); - } - }, - /** - * 绘制完整的柱状图或折线图 - */ - drawBarUpdate: function () { - this.ctx.fillStyle = this.bgColor; - this.ctx.fillRect(0, 0, this.width, this.height); - this.drawAxis(); - this.drawPoint(); - this.drawTitle(); - this.drawBarChart(); - }, - /** - * 绘制完整的饼状图或环形图 - */ - drawPieUpdate: function () { - this.ctx.fillStyle = this.bgColor; - this.ctx.fillRect(0, 0, this.width, this.height); - this.drawLegend(); - this.drawTitle(); - this.drawPieChart(); - }, - /** - * 把数据绘制出柱状或折线 - */ - drawBarChart: function () { - this.ctx.fillStyle = this.fillColor; - this.ctx.strokeStyle = this.fillColor; - for (var i = 0; i < this.dataLength; i++) { - this.data[i].left = this.leftPadding + this.xLength * (i + 0.25); - this.data[i].top = this.height - this.bottomPadding - this.data[i].value * this.yRatio; - this.data[i].right = this.leftPadding + this.xLength * (i + 0.75); - this.data[i].bottom = this.height - this.bottomPadding; - - // 绘制折线 - if (this.type === 'line') { - this.ctx.beginPath(); - this.ctx.arc(this.data[i].left + this.xLength / 4, this.data[i].top, 2, 0, 2 * Math.PI, true); - this.ctx.fill(); - if (i !== 0) { - this.ctx.moveTo(this.data[i].left + this.xLength / 4, this.data[i].top); - this.ctx.lineTo(this.data[i - 1].left + this.xLength / 4, this.data[i - 1].top); - } - this.ctx.stroke(); - } else if (this.type === 'bar') { - // 绘制柱状 - this.ctx.fillRect( - this.data[i].left, - this.data[i].top, - this.data[i].right - this.data[i].left, - this.data[i].bottom - this.data[i].top - ); - } - if (this.showValue) { - this.ctx.font = 12 * this.dpi + 'px Arial' - this.ctx.fillText( - this.data[i].value, - this.data[i].left + this.xLength / 4, - this.data[i].top - 5 - ); - } - } - }, - /** - * 把数据绘制出饼状或环形 - */ - drawPieChart: function () { - var x = this.width / 2, - y = this.height / 2, - x1 = 0, - y1 = 0; - for (var i = 0; i < this.dataLength; i++) { - this.ctx.beginPath(); - this.ctx.fillStyle = this.colorList[i]; - this.ctx.moveTo(x, y); - this.data[i].start = i === 0 ? -Math.PI / 2 : this.data[i - 1].end; - this.data[i].end = this.data[i].start + this.data[i].value / this.totalValue * 2 * Math.PI; - // 绘制扇形 - this.ctx.arc(x, y, this.radius, this.data[i].start, this.data[i].end); - this.ctx.closePath(); - this.ctx.fill(); - - this.data[i].middle = (this.data[i].start + this.data[i].end) / 2; - x1 = Math.ceil(Math.abs(this.radius * Math.cos(this.data[i].middle))); - y1 = Math.floor(Math.abs(this.radius * Math.sin(this.data[i].middle))); - - this.ctx.strokeStyle = this.colorList[i]; - // 绘制各个扇形边上的数据 - if(this.showValue){ - if (this.data[i].middle <= 0) { - this.ctx.textAlign = 'left'; - this.ctx.moveTo(x + x1, y - y1); - this.ctx.lineTo(x + x1 + 10, y - y1 - 10); - this.ctx.moveTo(x + x1 + 10, y - y1 - 10); - this.ctx.lineTo(x + x1 + this.radius / 2, y - y1 - 10); - this.ctx.stroke(); - this.ctx.fillText(this.data[i].value, x + x1 + 5 + this.radius / 2, y - y1 - 5); - } else if (this.data[i].middle > 0 && this.data[i].middle <= Math.PI / 2) { - this.ctx.textAlign = 'left'; - this.ctx.moveTo(x + x1, y + y1); - this.ctx.lineTo(x + x1 + 10, y + y1 + 10); - this.ctx.moveTo(x + x1 + 10, y + y1 + 10); - this.ctx.lineTo(x + x1 + this.radius / 2, y + y1 + 10); - this.ctx.stroke(); - this.ctx.fillText(this.data[i].value, x + x1 + 5 + this.radius / 2, y + y1 + 15); - } else if (this.data[i].middle > Math.PI / 2 && this.data[i].middle < Math.PI) { - this.ctx.textAlign = 'right'; - this.ctx.moveTo(x - x1, y + y1); - this.ctx.lineTo(x - x1 - 10, y + y1 + 10); - this.ctx.moveTo(x - x1 - 10, y + y1 + 10); - this.ctx.lineTo(x - x1 - this.radius / 2, y + y1 + 10); - this.ctx.stroke(); - this.ctx.fillText(this.data[i].value, x - x1 - 5 - this.radius / 2, y + y1 + 15); - } else { - this.ctx.textAlign = 'right'; - this.ctx.moveTo(x - x1, y - y1); - this.ctx.lineTo(x - x1 - 10, y - y1 - 10); - this.ctx.moveTo(x - x1 - 10, y - y1 - 10); - this.ctx.lineTo(x - x1 - this.radius / 2, y - y1 - 10); - this.ctx.stroke(); - this.ctx.fillText(this.data[i].value, x - x1 - 5 - this.radius / 2, y - y1 - 5); - } - } - } - // 如果类型是环形图,绘制一个内圆 - if (this.type === 'ring') { - this.ctx.beginPath(); - this.ctx.fillStyle = this.bgColor; - this.ctx.arc(x, y, this.innerRadius, 0, Math.PI * 2); - this.ctx.fill(); - } - }, - /** - * 绘制坐标轴 - */ - drawAxis: function () { - this.ctx.beginPath(); - this.ctx.strokeStyle = this.axisColor; - // y轴线, +0.5是为了解决canvas画1像素会显示成2像素的问题 - this.ctx.moveTo(this.leftPadding + 0.5, this.height - this.bottomPadding + 0.5); - this.ctx.lineTo(this.leftPadding + 0.5, this.topPadding + 0.5); - // x轴线 - this.ctx.moveTo(this.leftPadding + 0.5, this.height - this.bottomPadding + 0.5); - this.ctx.lineTo(this.width - this.rightPadding - 0.5, this.height - this.bottomPadding + 0.5); - this.ctx.stroke(); - }, - /** - * 绘制坐标轴上的点和值 - */ - drawPoint: function () { - // x轴坐标点 - this.ctx.beginPath(); - this.ctx.font = 12 * this.dpi + 'px Microsoft YaHei'; - this.ctx.textAlign = 'center'; - this.ctx.fillStyle = this.axisColor; - for (var i = 0; i < this.dataLength; i++) { - var name = this.data[i].name; - var xlen = this.xLength * (i + 1); - this.ctx.moveTo(this.leftPadding + xlen + 0.5, this.height - this.bottomPadding + 0.5); - this.ctx.lineTo(this.leftPadding + xlen + 0.5, this.height - this.bottomPadding + 5.5); - this.ctx.fillText(name, this.leftPadding + xlen - this.xLength / 2, this.height - this.bottomPadding + 15 * this.dpi); - } - this.ctx.stroke(); - - // y轴坐标点 - this.ctx.beginPath(); - this.ctx.font = 12 * this.dpi + 'px Microsoft YaHei'; - this.ctx.textAlign = 'right'; - this.ctx.fillStyle = this.axisColor; - this.ctx.moveTo(this.leftPadding + 0.5, this.height - this.bottomPadding + 0.5); - this.ctx.lineTo(this.leftPadding - 4.5, this.height - this.bottomPadding + 0.5); - this.ctx.fillText(0, this.leftPadding - 10, this.height - this.bottomPadding + 5); - for (var i = 0; i < this.yEqual; i++) { - var y = this.yFictitious * (i + 1); - var ylen = this.yLength * (i + 1); - this.ctx.beginPath(); - this.ctx.strokeStyle = this.axisColor; - this.ctx.moveTo(this.leftPadding + 0.5, this.height - this.bottomPadding - ylen + 0.5); - this.ctx.lineTo(this.leftPadding - 4.5, this.height - this.bottomPadding - ylen + 0.5); - this.ctx.stroke(); - this.ctx.fillText(y, this.leftPadding - 10, this.height - this.bottomPadding - ylen + 5); - this.ctx.beginPath(); - this.ctx.strokeStyle = this.contentColor; - this.ctx.moveTo(this.leftPadding + 0.5, this.height - this.bottomPadding - ylen + 0.5) - this.ctx.lineTo(this.width - this.rightPadding - 0.5, this.height - this.bottomPadding - ylen + 0.5); - this.ctx.stroke(); - } - }, - /** - * 绘制图表标题 - */ - drawTitle: function () { - if (this.title) { - this.ctx.beginPath(); - this.ctx.textAlign = 'center'; - this.ctx.fillStyle = this.titleColor; - this.ctx.font = 16 * this.dpi + 'px Microsoft YaHei'; - if (this.titlePosition === 'bottom' && this.bottomPadding >= 40) { - this.ctx.fillText(this.title, this.width / 2, this.height - 5) - } else { - this.ctx.fillText(this.title, this.width / 2, this.topPadding / 2 + 5) - } - } - }, - /** - * 绘制饼状图或环形图的图例 - */ - drawLegend: function () { - for (var i = 0; i < this.dataLength; i++) { - this.ctx.fillStyle = this.colorList[i]; - this.ctx.fillRect(10, this.legendTop + 15 * i * this.dpi, 20, 11); - this.ctx.fillStyle = this.legendColor; - this.ctx.font = 12 * this.dpi + 'px Microsoft YaHei'; - this.ctx.textAlign = 'left'; - this.ctx.fillText(this.data[i].name, 35, this.legendTop + 10 + 15 * i * this.dpi); - } - }, - /** - * y轴坐标点之间显示的间距 - * @param data 生成图表的数据 - * @return y轴坐标间距 - */ - getYFictitious: function (data) { - var arr = data.slice(0); - arr.sort(function (a, b) { - return -(a.value - b.value); - }); - var len = Math.ceil(arr[0].value / this.yEqual); - var pow = len.toString().length - 1; - pow = pow > 2 ? 2 : pow; - return Math.ceil(len / Math.pow(10, pow)) * Math.pow(10, pow); - }, - /** - * 获取饼状或环形图的数据总和 - * @return {Number} total - */ - getTotalValue: function () { - var total = 0; - for (var i = 0; i < this.dataLength; i++) { - total += this.data[i].value; - } - return total; - } - } - return sChart; -}); \ No newline at end of file diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..4af17b5 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,26 @@ +export const DPI = window.devicePixelRatio || 1; +export const S10 = 10 * DPI; +export const S5 = S10 / 2; + +export const initCanvas = id => { + let canvas = document.getElementById(id); + let width = canvas.parentNode.clientWidth; + let height = canvas.parentNode.clientHeight; + canvas.style.width = width + 'px'; + canvas.style.height = height + 'px'; + canvas.width = width * DPI; + canvas.height = height * DPI; + return canvas; +}; + +export const getYSpace = (datasets, yEqual) => { + let arr = datasets.map(item => { + return item.data.reduce((prev, current) => { + return prev > current ? prev : current; + }); + }); + let len = Math.ceil(Math.max(...arr) / yEqual); + let pow = len.toString().length - 1; + pow = pow > 2 ? 2 : pow; + return Math.ceil(len / Math.pow(10, pow)) * Math.pow(10, pow); +}; \ No newline at end of file diff --git a/src/version.js b/src/version.js deleted file mode 100644 index dd83c1e..0000000 --- a/src/version.js +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * sChart JavaScript Library v2.0.1 - * http://blog.gdfengshuo.com/example/sChart/ | Released under the MIT license - * Date: 2018-04-16T18:59Z - */ \ No newline at end of file