545 lines
18 KiB
JavaScript
545 lines
18 KiB
JavaScript
(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;
|
||
|
||
})));
|