前言
每个开发者在制作图表时都需要从头到尾书写一遍完整的option配置,十分冗余。在同一个项目中,各类图表设计十分相似,甚至是相同,没必要一直做重复工作,可能有一些开发者忘记考虑echarts更新数据的特性,以及窗口缩放时的适应问题。这样导致数据更新了echarts视图却没有更新,窗口缩放引起echarts图形变形问题,这就需要我们程序猿发动偷懒的特技啦!QAQ
我希望这个echarts组件能设计成什么样
- 业务数据和样式配置数据分离,我只需要传入业务数据就行了
- 可以自定义一套需要的图表样式进行集中管理,或者可以直接引用echart官网下载的主题
- 不会因为缩放出现变形问题,而是能很好地自适应
- 有时候某个图表的样式可能有点不一样,希望能保留自己配置样式的灵活性
- 无论传入什么数据指定图表类型就能正确地更新视图
- 如果我传入的数据为空,能展示一个空状态
一、对使用echarts需要提前知道的知识
option = {
color: ['#3398DB'], // 图表色系
tooltip: {
trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
grid: { // canvas容器内边距
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true // 是否允许某些情况超出面板,默认false:允许
},
xAxis: [ // 横坐标轴
{
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
axisTick: {
alignWithLabel: true
}
}
],
yAxis: [ //纵坐标轴
{
type: 'value',
name: 'y轴名称'
}
],
series: [ //不同类型的数据结合
{
name: '直接访问',
type: 'bar', //此数据对象对应的图表类型,可以为line, pie等
barWidth: '60%',
data: [10, 52, 200, 334, 390, 330, 220]
}
]
};
1.
观察echart不同类型的图表,如上图,我们会发现xAxis、yAxis、series都是数组,那么我们就可以使他们都变成两条数据即 两个相同或不同的横坐标轴、两个相同或不同的纵坐标轴、两个相同或不同的图表类型 进行任意搭配
2.
我们把他们的样式和数据拆分来看时(例如:饼状图、柱状图、折线图),你会发现用同一个’option’ 我们仅需要改变series中type属性即可它在不同形状之前切换(当然啦,饼状图并没有横纵坐标轴~)
3.图表初始化
(1)let myChart = echarts.init(dom, theme, opts)
(dom: HTMLDivElement|HTMLCanvasElement, theme?: Object|string, opts?: {
devicePixelRatio?: number,
renderer?: string,
width?: number|string,
height?: number|string
}) => ECharts
dom:实例容器,一般是一个具有高宽的div元素。
theme:应用的主题。可以是一个主题的配置对象,也可以是使用已经通过 echarts.registerTheme 注册的主题名称。
opts:附加参数。附加选项见 图表初始化
(2)myChart.setOption(option, merge, updates);
option:图表数据 Object
merge:是否和上次设置的option进行合并? boolean, 默认false即合并
updates:设置完option后是否不立即更新图表? boolean,默认false即立即更新
(3)echart4 配置属性:dataset数据集
ECharts 4 开始支持了 数据集(dataset)组件用于单独的数据集声明,从而数据可以单独管理,被多个组件复用,并且可以自由指定数据到视觉的映射。这在不少场景下能带来使用上的方便。
关于 dataset 的详情,请参见教程。
二、开始封装
1.封装思路及使用
(1) 除了option中最基础的框架,我们把样式的设置都尽量放在我们自定义的主题文件中 ‘mysetTheme.js’
(2) 数据组建的格式为:
其中 group存储类型名称(图例名),name存储xAxis.data数据(x轴名称),value存储 x轴对应的数据。
(3) 比较特别的情况
我们需要两个y轴时就需要在每条数据中指定右侧y轴对应哪些数据(默认不指定的情况下为左侧y轴)即添加‘yAxisIndex: 1’。此时数据格式应如下:
如果有两个不同的group都指定了‘yAxisIndex: 1’ 那么右侧的y轴会对应两个不同的图表。
(4) 为了便于以后个性化扩展,我们可以定义不同的option放在EchartsOption对象中,我们在使用时就可以向下方这样去获取不同的option
/**
* 饼图
* @param title : 标题<br>
* @param subtext :副标题<br>
* @param data : json 数据
* @param radiusArr : array [内环百分比,外环百分比],不指定为普通饼图
*/
var option = MyEcharts.EchartsOption.pie("饼状图", "单位(人)", data,["48%","68%"]);
/**
* 折线图
* @param title : 标题<br>
* @param subtext :副标题<br>
* @param data : json 数据
*/
var option = MyEcharts.EchartsOption.Line("折线图", "单位(人)", data);
/**
* 参数说明同折线图
*/
var option = MyEcharts.EchartsOption.bar("柱状图", "单位(人)", data);
/**
* 参数说明同折线图
*/
var option = MyEcharts.EchartsOption.acrossBar("横向柱状图", "种类数(份)", data);
/**
* 双y轴/混搭
* @param title : string 标题
* @param subtext : string 副标题
* @param data : json 数据
* @param type : Array 或 string,数组时需要和图例个数相对应,字符串时为统一类型 **注意:要想双y轴,type不管为哪种类型时都需要在数据中指定AxisIndex的值**
*/
var option = MyEcharts.EchartsOption.doubleYMixUp("混搭or双y轴", "种类数(份)", data, ['line', 'bar', 'line']);
var option = MyEcharts.EchartsOption.Radar("雷达图", "副标题名称", data);
var option = MyEcharts.EchartsOption.Funnel("漏斗图", "副标题名称", data);
(5) 获取到option后就可以像正常使用echart一样,指定id进行初始化了
/**
* @param option : option
* @param echartId : 具备高宽的div元素id
*/
MyEcharts.initChart(option, "main");
2.灵活配置
当我们不想改变EchartsOption中option的配置又想添加自己的样式时,我们可以使用以下不同的方式
(1) 使用initChart.setOption()指定新的配置
var data = [{
name: '男生', value: 10 },{
name: '女生', value: 20 }];
var option = MyEcharts.EchartsOption.pie("男女人数统计", "单位(人)", data);
var initChart = MyEcharts.initChart(option, "main");
//重新定义样式
var options = {
color:["#FDB157","#3498DB"],
legend : {
orient: 'vertical', //垂直:vertical; 水平 horizontal
right:'right',
},
series:[{
radius : '55%', //圆的大小
center : ['50%', '60%'],//位置居中
}]
};
initChart.setOption(options);
(2) 直接对option进行赋值(不推荐)
var data = [{
name: '男生', value: 10 },{
name: '女生', value: 20 }];
var option = MyEcharts.EchartsOption.pie("男女人数统计", "单位(人)", data);
//重新定义样式
option.color = ["#FDB157","#3498DB"];
option.legend = {
orient: 'vertical', //垂直:vertical; 水平 horizontal
right:'right',
}
option.series[0] = {
radius : '55%', //圆的大小
center : ['50%', '60%'],//位置居中
}
var initChart = MyEcharts.initChart(option, "main");
(3) 对于非数据的配置,如:色系,title的颜色或排列位置的公用样式推荐都在mysetTheme.js中配置,如下
/**
* echart自定义主题
* Author: abner
* Date: 2020-12-25
* 注意:主题的优先级比setOption低
*/
let myTheme = {
// 全图默认背景
// backgroundColor: 'rgba(0,0,0,0)',
// 全图文字
"textStyle": {
color: '#fff',
fontFamily: 'Microsoft YaHei UI',
fontSize: 12,
},
// 默认色板
"color": ['#838CFF', '#2AE3FF', '#FA7332', '#D797FF', '#fa2c7b', '#ff38e0', '#ffa235', '#04c5f3', '#0066fe', '#8932a5', '#c90444', '#cb9bff', '#434348', '#90ed7d'],
//标题
"title": {
x: 'center', //位置默认居中
textStyle: {
fontFamily: 'Microsoft YaHei UI',
fontSize: 14,
color: '#fff' // 主标题文字颜色
},
subtextStyle: {
color: '#aaa' // 副标题文字颜色
}
},
// @图例
"legend": {
type: 'plain', //plain:普通图例 'scroll':可滚动翻页的图例。当图例数量较多时可以使用。
orient: 'horizontal', //垂直 vertical | 水平 horizontal
left: 'left', //位置默认左
// bottom: '0',
textStyle: {
color: '#fff' // 图例文字颜色
}
},
// @工具栏 此配置无法起效,必须在option中配置
// "toolbox": {
// show: true, // 默认显示
// feature: {
// dataZoom: {
// yAxisIndex: 'none'
// },
// dataView: { readOnly: false },
// magicType: { type: ['line', 'bar'] },
// restore: {},
// saveAsImage: {}
// }
// },
// @纵轴
"categoryAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#008acd"
}
},
"axisTick": {
// 刻度线
"show": true,
"lineStyle": {
"color": "#fff"
}
},
"axisLabel": {
// 刻度值
"show": true,
"textStyle": {
"color": "#fff"
}
},
"splitLine": {
// 坐标轴在 grid 区域中的分隔线。默认数值轴(横向)显示,类目轴(纵向)不显示。
"show": false,
"lineStyle": {
"color": [
"#eee"
]
}
},
"splitArea": {
// 坐标轴在 grid 区域中的分隔区域,默认不显示。
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
}
},
// @横轴
"valueAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#008acd"
}
},
"axisTick": {
// 刻度线
"show": true,
"lineStyle": {
"color": "#fff"
}
},
"axisLabel": {
// 刻度值
"show": true,
"textStyle": {
"color": "#fff"
}
},
"splitLine": {
// 坐标轴在 grid 区域中的分隔线。默认数值轴(横向)显示,类目轴(纵向)不显示。
"show": true,
"lineStyle": {
// 分隔线的样式设置。
"color": [
"#ccc"
]
}
},
"splitArea": {
// 坐标轴在 grid 区域中的分隔区域,默认不显示。
"show": false,
"areaStyle": {
// 分隔区域的样式设置。
"color": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
}
},
// @数据滑竿
"dataZoom": {
"show": true,
"backgroundColor": "rgba(47,69,84,0)",
"dataBackgroundColor": "rgba(239,239,255,1)",
"fillerColor": "rgba(182,162,222,0.2)",
"handleColor": "#008acd",
"handleSize": "100%",
"textStyle": {
"color": "#333333"
}
},
"markPoint": {
"label": {
"color": "#eeeeee"
},
"emphasis": {
"label": {
"color": "#eeeeee"
}
}
}
}
(4) 使用自定义loading
echarts不同版本的loading显示效果不太一样,echarts4的loading只有一种如下 详见文档
默认的loading default的正确打开方式应该是这样的:(这是官网api都没有的哦!)
var initChart = MyEcharts.initChart(option, "mixCharts");
/**
* @param {string} “default” showLoading默认值
* @param {Object} {
text: 'loading', // loading显示的文字
textColor: '#000', // 文字颜色
fontSize: '12px', // 文字大小
maskColor: 'rgba(255, 255, 255, 0.8)',
showSpinner: true, // 是否显示gif动画,默认true
color: '#c23531', // gif填充的颜色
spinnerRadius: 10,
lineWidth: 5,
zlevel: 0
}
*/
initChart.showLoading();
//initChart.showLoading("default",{text:'暂无数据!',showSpinner: true}); // 或者指定配置
loading隐藏
initChart.hideLoading();
//initChart.hideLoading("default"); // 或者指定名称
敲黑板!重点来啦!如果以上的配置还不能满足需求呢?比如我想把转圈圈的动画换成自定义的图片
自定义loading详见echarts-util.js 文件loadingNodata方法
自定义的loading nodatas的正确打开方式应该是这样的:
initChart.showLoading("nodatas",{
text:'暂无数据!',showSpinner: true});
loading隐藏
initChart.hideLoading("nodatas");
ps:我实在弄不下去了,有时间再进行补充吧!
3.代码实例
echartsUtil.html代码如下(示例):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>echarts二次封装</title>
<script src="./js/echarts.min.js"></script>
<script src="./js/mysetTheme.js"></script>
<script src="./js/echarts-util.js"></script>
<style>
body,
html {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
background: url("./image/bi_board_background.jpg") center center no-repeat;
background-size: 100% 100%;
background-color: rgb(31, 37, 49);
}
.container {
display: grid;
grid-gap: 10px;
grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));
grid-auto-rows: 450px;
}
.container>div {
border: 1px solid #0a6ebd;
position: relative;
}
</style>
</head>
<body>
<div class="container">
<div id="main">饼状图</div>
<div id="pieC">环形饼图</div>
<div id="mainLine">折线图</div>
<div id="mainBar">柱状图</div>
<div id="barGroup">柱状分组</div>
<div id="mixCharts">混搭</div>
<div id="acrossBar">横向柱状图</div>
<div id="Radar">雷达图</div>
<div id="Funnel">漏斗图</div>
</div>
<script>
/**
* 饼图
**/
var data = [{
name: '男生', value: 10 },
{
name: '女生', value: 20 }];
var option = MyEcharts.EchartsOption.pie("男女人数统计", "单位(人)", data);
var initChart = MyEcharts.initChart(option, "main");
//要替换默认样式就取消下面注释
// var options = {
// color: ["#FDB157", "#3498DB"],
// legend: {
// orient: 'vertical', //垂直:vertical; 水平 horizontal
// right: 'right',
// },
// series: [{
// radius: '55%', //圆的大小
// center: ['50%', '60%'],//位置居中
// }]
// };
// initChart.setOption(options);
/**
* 环形饼图 滑翔
**/
var pieCData = [
{
value: 335, name: '直接访问' },
{
value: 310, name: '邮件营销' },
{
value: 234, name: '联盟广告' },
{
value: 135, name: '视频广告' },
{
value: 1548, name: '搜索引擎' }
]
var option = MyEcharts.EchartsOption.pie("环形饼图", "种类数(份)", pieCData, ['40%', '60%']);
var initChart = MyEcharts.initChart(option, "pieC");
/**
* 折线图
**/
var nameLine = [];
for (var i = 1; i <= 11; i++) {
nameLine.push('11月' + i + '日');
}
var valLine = [0, -120, 1245, 0, -190, 1376, 1511, 1689, 1256, 1495, 1192];
var dataLine = []
for (var i = 0; i < nameLine.length; i++) {
dataLine.push({
name: nameLine[i], value: valLine[i], group: '出生人数1' })
// dataLine.push({ name: nameLine[i], value: valLine.reverse()[i], group: '出生人数2' })
}
var option = MyEcharts.EchartsOption.Line("折线图", "单位(人)", dataLine);
//区域填充样式(渐变)
/*option.series[0].areaStyle = {
color: {
type: 'linear', // 线性渐变:linear,径向渐变:radial
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0.2, color: '#838CFF' // 0% 处的颜色
}, {
offset: 0.6, color: 'transparent' // 100% 处的颜色
}],
global: false // 缺省为 false
}
// 纹理填充
// color: {
// image: imageDom, // 支持为 HTMLImageElement, HTMLCanvasElement,不支持路径字符串
// repeat: 'repeat' // 是否平铺,可以是 'repeat-x', 'repeat-y', 'no-repeat'
// }
}*/
var initChartLine = MyEcharts.initChart(option, "mainLine");
/**
* 柱状图
**/
var option = MyEcharts.EchartsOption.bar("柱状图", "单位(人)", dataLine);
//柱子线性渐变
option.series[0].itemStyle = {
color: new echarts.graphic.LinearGradient(
0, 0, 0, 1, //x0, y0, x2, y2, 范围从 0 - 1,相当于在图形包围盒中的百分比。x0 x2从左到右横向的渐变方向,y0 y2从上到下纵向的渐变方向
[
{
offset: 0, color: '#F97332' },
{
offset: 0.5, color: '#F97332' },
{
offset: 1, color: 'transparent' }
]
)
}
var initChart = MyEcharts.initChart(option, "mainBar");
/**
* 柱状分組
**/
var nameBar = [];
for (var j = 1; j <= 6; j++) {
nameBar.push('11月' + j + '日');
}
var value = [55, 900, 1245, 1530, 1376, 1376];
var dataBar = []
for (var i = 0; i < nameBar.length; i++) {
dataBar.push({
name: nameBar[i], value: value[i], group: '人生1' })
}
var dataBar2 = [];
for (var i = 0; i < nameBar.length; i++) {
dataBar2.push({
name: nameBar[i], value: value.reverse()[i], group: '人生2' })
}
var option = MyEcharts.EchartsOption.bar("柱状分组", "种类数(份)", [...dataBar, ...dataBar2]);
// console.log([...dataBar, ...dataBar2]);
var initChart = MyEcharts.initChart(option, "barGroup");
/**
* 横向柱状图
**/
var dataacrossBar = [];
var valBar = [55, 900, 1245, 1530, 0, 1376, 1511, 1689, 1856, 1495, 1292];
for (var i = 0; i < nameBar.length; i++) {
dataacrossBar.push({
name: nameBar[i], value: valBar[i], group: '很想' })
}
var option = MyEcharts.EchartsOption.acrossBar("横向柱状图", "种类数(份)", dataacrossBar);
/**
* 未分组的情况下(group)使每个柱子独立的颜色
**/
option.series[0].itemStyle = {
normal: {
color: function (params) {
let color = myTheme.color;
return color[params.dataIndex]
},
}
}
var initChart = MyEcharts.initChart(option, "acrossBar");
/**
* 双y轴,和柱状折线混搭
**/
var nameBar = [];
for (var j = 1; j <= 6; j++) {
nameBar.push('11月' + j + '日');
}
var value = [55, 90, 124, 153, 137, 105];
var dataBar = []
for (var i = 0; i < nameBar.length; i++) {
dataBar.push({
name: nameBar[i], value: value[i], group: '人生1' })
}
var value2 = [12.0, 23.2, 25.6, 62.2, 32.6, 43.3];
var dataBar2 = [];
for (var i = 0; i < nameBar.length; i++) {
dataBar2.push({
name: nameBar[i], value: value2.reverse()[i], group: '人生2', yAxisIndex: 1 })
}
var dataBar3 = [];
for (var i = 0; i < nameBar.length; i++) {
dataBar2.push({
name: nameBar[i], value: value2[i], group: '人生3', yAxisIndex: 1 })
}
// console.log( [...dataBar, ...dataBar2, ...dataBar3]);
// var option = MyEcharts.EchartsOption.doubleYMixUp("混搭/双y轴", "种类数(份)", [...dataBar, ...dataBar2, ...dataBar3], 'bar');
var option = MyEcharts.EchartsOption.doubleYMixUp("混搭/双y轴", "种类数(份)", [...dataBar, ...dataBar2, ...dataBar3], ['line', 'bar', 'line']);
//重置右侧y轴样式
/*问题1:重置yAxis的样式后,点击图例无法控制y轴显隐*/
option.yAxis[1] = {
type: 'value',
name: '温度',
interval: 5,
splitLine: {
show: false, 去除网格线
},
axisLabel: {
formatter: '{value} °C'
}
}
option.yAxis[0].name = '%';
option.series[0].label = {
show: true,
position: 'top',
color: '#FFFFFF',
formatter: '{c}%'
};
var initChart = MyEcharts.initChart(option, "mixCharts");
var options = {
yAxis: [{
name: '%',
}, {
type: 'value',
name: '温度',
interval: 5,
splitLine: {
show: false, // 去除网格线
},
axisLabel: {
formatter: '{value}'
}
}],
series: [
{
label: {
show: true,
position: 'top',
color: '#FFFFFF',
formatter: '{c}%'
}
}
]
}
initChart.setOption(options);
//添加自定义loading
// initChart.showLoading("nodatas",{text:'暂无数据!',showSpinner: true});
// initChart.hideLoading("nodatas");
/**
* 雷达图Radar
**/
var option = MyEcharts.EchartsOption.Radar("雷达图", "", [...dataBar, ...dataBar2, ...dataBar3]);
var initChart = MyEcharts.initChart(option, "Radar");
/**
* 漏斗图Funnel
**/
var option = MyEcharts.EchartsOption.Funnel("漏斗图Funnel", "", [...dataBar, ...dataBar2, ...dataBar3]);
var initChart = MyEcharts.initChart(option, "Funnel");
</script>
</body>
</html>
echarts-util.js代码如下(示例):
/**
* 封装echarts 工具
* Author: abner
* Date: 2021-1-5 更新
*/
let MyEcharts = {
echartSet: [], // 表格集合
//整理数据没有分组类型的,适合饼图
EchartsDataFormate: {
NoGroupFormate: function (data) {
//category 的数据存储
let categorys = [];
//data 的数据存储
let datas = [];
//遍历
for (let i = 0; i < data.length; i++) {
categorys.push(data[i].name || "");
//定义一个中间变量
let temp_data = {
value: data[i].value || 0, name: data[i].name || "" };
datas.push(temp_data);
}
return {
categorys: categorys, data: datas };
},
//整理数据有分组类型的,适合折线图、柱形图(分组,堆积)
//数据格式:{group:XXX,name:XXX,value:XXX} 注意:group不填写没有图例,也无法(分组,堆积)
/**
* @param data : json数据<br>
* @param type Array | string: 图表类型<br>
* let data1 = [ <br>
* { group:'类型1' , name: '1月', value: 10 }, <br>
* { group:'类型2' , name: '1月', value: 15 }, <br>
* { group:'类型1' , name: '2月', value: 25 }, <br>
* { group:'类型2' , name: '2月', value: 12 }, <br>
* { group:'类型1' , name: '3月', value: 22 }, <br>
* { group:'类型2' , name: '3月', value: 12 }, <br>
* ];
*
*/
GroupFormate: function (data, type) {
//用于存储类型名称(图例名)
let groups = [];
//用于存储xAxis.data数据(x轴名称)
let names = [];
//存储返回series数据 (一个或者多个)
let series = [];
let yAxisIndex = 0;
for (let i = 0; i < data.length; i++) {
//判断data[i].group是否存在数租groups中
if (!groups.contains(data[i].group)) {
//不存在则跳进 存放
groups.push(data[i].group);
}
//判断name数据是否存在 数组names中
if (!names.contains(data[i].name)) {
//不存在则跳进 存放
names.push(data[i].name);
}
}
//遍历分类
for (let i = 0; i < groups.length; i++) {
//定义一个series中间变量
let temp_series = {
};
//定义data.value数据存储
let temp_data = [];
//定义图形类型
let temp_type = type.constructor === Array ? type[i] : type;
//遍历所有数据
for (let j = 0; j < data.length; j++) {
//遍历data.name数据
for (let k = 0; k < names.length; k++) {
//判断所有分类中的所有数据含name数据分开
if (groups[i] == data[j].group && names[k] == data[j].name) {
temp_data.push(data[j].value);
//判断y轴对应的数据,如果存在yAxisIndex则使用此数据,否则使用默认值 默认:0
yAxisIndex = data[j].yAxisIndex ? data[j].yAxisIndex : 0;
}
}
}
temp_series = {
name: groups[i],
type: temp_type === 'acrossBar' ? 'bar' : temp_type, //横向柱状图:acrossBar
data: temp_data,
yAxisIndex: yAxisIndex
};
//每个柱子或拐点对应的文字位置和颜色。echarts3是在label.normal里配置,echart4中兼容
temp_series.label = {
position: 'top',
show: true,
textStyle: {
color: '#fff' //auto则根据系列色同步
},
formatter: function (params) {
//当值为0时不显示在柱子或拐点顶部
let val = params.value;
return val === 0 ? '' : val;
}
};
// 柱状图参数配置
if (temp_type === 'bar') {
temp_series.barMaxWidth = 24; //柱宽
}
// 折线图参数配置
if (temp_type === 'line') {
temp_series.lineStyle = {
width: 4 }; //线租
temp_series.smooth = true; //拐点平滑
}
// 横向柱状图参数配置
if (temp_type === 'acrossBar') {
temp_series.barMaxWidth = 24; //柱宽
temp_series.label = {
position: 'right',
show: true,
textStyle: {
color: 'auto' //auto则根据系列色同步
}
};
}
series.push(temp_series);
}
// console.log(groups)
// console.log(names)
// console.log(series)
return {
groups: groups, category: names, series: series };
},
/**
* 雷达图数据格式化
*/
RadarFormate: function (data, type) {
//用于存储类型名称
let groups = [];
//用于存储data.name数据
let names = [];
//存储最大值数组
let indicators = [];
//定义data.value数据存储
let temp_data = [];
for (let i = 0; i < data.length; i++) {
//判断data[i].group是否存在数租groups中
if (!groups.contains(data[i].group)) {
//不存在则跳进 存放
groups.push(data[i].group);
}
//判断name数据是否存在 数组names中
if (!names.contains(data[i].name)) {
//不存在则跳进 存放
names.push(data[i].name);
}
}
for (let i = 0; i < names.length; i++) {
//中
let temp_maxValue = [];
for (let j = 0; j < data.length; j++) {
if (names[i] == data[j].name) {
temp_maxValue.push(data[j].value);
}
}
indicators.push({
name: names[i], max: Number(temp_maxValue.max() * 2 / 1.5).toFixed(2) })
}
//遍历分类
for (let i = 0; i < groups.length; i++) {
//定义一个series中间变量
let temp_series = {
};
//定义datavalue数组
let dataValues = [];
//遍历所有数据
for (let j = 0; j < data.length; j++) {
if (groups[i] == data[j].group) {
dataValues.push(data[j].value);
}
}
temp_data.push({
value: dataValues, name: groups[i] });
}
let series = {
type: type, data: temp_data };
return {
indicators: indicators, groups: groups, category: names, series: series };
},
/**
* 漏斗图数据格式化
*/
FunnelFormate: function (data, type) {
//用于存储类型名称
let groups = [];
//用于存储data.name数据
let names = [];
//定义一个存放series的数组
let series = [];
for (let i = 0; i < data.length; i++) {
//判断data[i].group是否存在数租groups中
if (!groups.contains(data[i].group)) {
//不存在则跳进 存放
groups.push(data[i].group);
}
//判断name数据是否存在 数组names中
if (!names.contains(data[i].name)) {
//不存在则跳进 存放
names.push(data[i].name);
}
}
let width = parseInt(100 / groups.length);
//遍历分类
for (let i = 0; i < groups.length; i++) {
//定义data.value数据存储
let temp_data = [];
let k = 0;
//遍历所有数据
for (let j = 0; j < data.length; j++) {
//判断所有分类中的所有数据含name数据分开
if (groups[i] == data[j].group) {
k++;
temp_data.push({
value: k, name: data[j].name + ":" + data[j].value });
}
}
let left = width * i;
series.push({
name: groups[i],
type: type,
sort: 'ascending',
grap: 2,
left: left + "%",
width: width - 5 + "%",
label: {
normal: {
show: true,
position: 'inside'
},
emphasis: {
textStyle: {
fontSize: 20
}
}
},
data: temp_data
});
}
return {
groups: groups, category: names, series: series };
},
/**
* 仪表盘图数据格式化
*/
GaugeFormate: function (data, type) {
let temp_datas = [{
value: data.value, name: data.name }];
let names = data.name;
//判断最大值和最小值几位数
maxNum = Number(parseInt(data.value)).toString().length;
minNum = Number(parseInt(data.value)).toString().length;
if (minNum <= 2) {
min = 0;
} else {
//最小值
min = Math.pow(10, (maxNum - 1));
}
//最大值
max = Math.pow(10, maxNum);
let series = [];
series.push({
name: data.group,
type: type,
min: min,
max: max,
radius: '70%',
startAngle: 180,
endAngle: -0,
axisLine: {
// 坐标轴线
lineStyle: {
// 属性lineStyle控制线条样式
color: [[0.09, 'lime'], [0.82, '#1e90ff'], [1, '#ff4500']],
width: 3,
shadowColor: '#fff', //默认透明
shadowBlur: 10
}
},
axisLabel: {
// 坐标轴小标记
textStyle: {
// 属性lineStyle控制线条样式
fontWeight: 'bolder',
color: '#444',
shadowColor: '#fff', //默认透明
shadowBlur: 10
}
},
axisTick: {
// 坐标轴小标记
length: 15, // 属性length控制线长
lineStyle: {
// 属性lineStyle控制线条样式
color: 'auto',
shadowColor: '#fff', //默认透明
shadowBlur: 10
}
},
splitLine: {
// 分隔线
length: 25, // 属性length控制线长
lineStyle: {
// 属性lineStyle(详见lineStyle)控制线条样式
width: 3,
color: 'auto',
shadowColor: '#fff', //默认透明
shadowBlur: 10
}
},
pointer: {
// 分隔线
shadowColor: '#fff', //默认透明
shadowBlur: 5
},
title: {
offsetCenter: ['-10%', '30%'],
textStyle: {
// 其余属性默认使用全局文本样式,详见TEXTSTYLE
fontWeight: 'bolder',
fontSize: 14,
fontStyle: 'italic',
color: '#',
shadowColor: '#fff', //默认透明
shadowBlur: 10
}
},
detail: {
backgroundColor: 'rgba(30,144,255,0.8)',
borderWidth: 1,
borderColor: '#fff',
shadowColor: '#fff', //默认透明
shadowBlur: 5,
fontSize: 14,
offsetCenter: ['20%', '30%'], // x, y,单位px
textStyle: {
// 其余属性默认使用全局文本样式,详见TEXTSTYLE
fontWeight: 'bolder',
color: '#fff'
}
},
data: temp_datas
});
return {
category: names, series: series };
}
},
//生成图形option
EchartsOption: {
/**
* 饼图
* @param title : 标题<br>
* @param subtext :副标题<br>
* @param data : json 数据
* @param radiusArr : array [内环百分比,外环百分比],不指定为普通饼图
*
*/
pie: function (title, subtext, data, radiusArr) {
//数据格式
let datas = MyEcharts.EchartsDataFormate.NoGroupFormate(data);
let option = {
//标题
title: {
text: title || "", //标题
subtext: subtext || "", //副标题
},
//提示
tooltip: {
show: true,
trigger: 'item',
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
//组建
legend: {
data: datas.categorys
},
series: [
{
name: title || "",
type: 'pie', //类型
radius: radiusArr || '48%', //圆的大小
center: ['50%', '50%'],//位置居中
data: datas.data,
emphasis: {
//高亮的扇区和标签样式。
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
},
labelLine: {
//引导线
show: false,
},
label: {
textStyle: {
color: 'auto' //改变标示文字的颜色
}
}
}
]
};
return option;
},
/**
* 柱形图
* @param title : 标题<br>
* @param subtext :副标题<br>
* @param data : json 数据
*/
bar: function (title, subtext, data) {
let datas = MyEcharts.EchartsDataFormate.GroupFormate(data, 'bar');
let option = {
//标题
title: {
text: title || "", //标题
subtext: subtext || "", //副标题
},
//提示
tooltip: {
show: true,
trigger: 'axis', //散点图,饼图等无类目轴的图表中使用:'item';
axisPointer: {
//鼠标指向背景
type: 'shadow', //'line' 直线指示器 ,'shadow' 阴影指示器 ,'none' 无指示器 ,'cross' 十字准星指示器。其实是种简写,表示启用两个正交的轴的 axisPointer。
label: {
show: true
}
},
// formatter: function (params) {
// return MyEcharts.resetTooltip(params);
// }
},
// 工具条
toolbox: {
show: false, // 默认显示
feature: {
dataZoom: {
yAxisIndex: 'none'
},
dataView: {
readOnly: false },
magicType: {
type: ['line', 'bar'] },
restore: {
},
saveAsImage: {
}
}
},
// 数据滑竿
dataZoom: [
{
show: false,
realtime: true,
},
{
type: 'slider',
realtime: true,
}
],
//组建
legend: {
data: datas.groups.map((text) => {
return {
name: text, icon: 'rect' } //icon图形样式:'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow'
})
},
//水平坐标
xAxis: [
{
name: '', //x轴单位
nameTextStyle: {
//x轴上方单位的颜色
color: 'auto'
},
type: 'category',
data: datas.category
}
],
//垂直坐标
yAxis: [
{
name: '', //y轴单位
nameTextStyle: {
//y轴上方单位的颜色
color: '#151515'
},
type: 'value'
}
],
//series数据
series: datas.series
};
return option;
},
/**
* 横向柱形图
* @param title : 标题<br>
* @param subtext :副标题<br>
* @param data : json 数据
*/
acrossBar: function (title, subtext, data) {
let datas = MyEcharts.EchartsDataFormate.GroupFormate(data, 'acrossBar');
let option = {
//标题
title: {
text: title || "", //标题
subtext: subtext || "", //副标题
},
//提示
tooltip: {
show: true,
trigger: 'axis', //散点图,饼图等无类目轴的图表中使用:'item';
axisPointer: {
//鼠标指向背景
type: 'none', //'line' 直线指示器 ,'shadow' 阴影指示器 ,'none' 无指示器 ,'cross' 十字准星指示器。其实是种简写,表示启用两个正交的轴的 axisPointer。
label: {
show: true
}
},
formatter: function (params) {
return MyEcharts.resetTooltip(params);
}
},
//组建
legend: {
data: datas.groups.map((text) => {
return {
name: text, icon: 'rect' } //icon图形样式:'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow'
})
},
//水平坐标
xAxis: [
{
name: '', //x轴单位
nameTextStyle: {
//x轴上方单位的颜色
color: '#151515'
},
type: 'value'
}
],
//垂直坐标
yAxis: [
{
name: '', //y轴单位
nameTextStyle: {
//y轴上方单位的颜色
color: '#151515'
},
type: 'category',
data: datas.category
}
],
//series数据
series: datas.series
};
return option;
},
/**
* 双y轴/混搭
* @param title : string 标题
* @param subtext : string 副标题
* @param data : json 数据
* @param type : Array 或 string,数组时需要和图例个数相对应,字符串时为统一类型 **注意:type不管为哪种类型时都需要在数据中指定yAxisIndex的值**
*/
doubleYMixUp: function (title, subtext, data, type) {
let datas = MyEcharts.EchartsDataFormate.GroupFormate(data, type);
let option = {
//标题
title: {
text: title || "", //标题
subtext: subtext || "", //副标题
},
//提示
tooltip: {
show: true,
trigger: 'axis', //散点图,饼图等无类目轴的图表中使用:'item';
axisPointer: {
//鼠标指向背景
type: 'shadow', //'line' 直线指示器 ,'shadow' 阴影指示器 ,'none' 无指示器 ,'cross' 十字准星指示器。其实是种简写,表示启用两个正交的轴的 axisPointer。
label: {
show: true
}
}
},
//组建
legend: {
data: datas.groups.map((text) => {
return {
name: text, icon: 'rect' } //icon图形样式:'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow'
})
},
//水平坐标
xAxis: [
{
name: '', //x轴单位
type: 'category',
data: datas.category,
}
],
//垂直坐标
yAxis: [
{
name: '', //y轴单位
type: 'value',
splitLine: {
show: false, 去除网格线
}
},
{
name: '', //右y轴单位
type: 'value',
splitLine: {
show: false, 去除网格线
}
}
],
//series数据
series: datas.series
};
return option;
},
/**
* 折线图
* @param title : 标题<br>
* @param subtext :副标题<br>
* @param data : json 数据
*/
Line: function (title, subtext, data) {
let datas = MyEcharts.EchartsDataFormate.GroupFormate(data, 'line');
let option = {
//标题
title: {
text: title || "", //标题
subtext: subtext || "", //副标题
},
//提示
tooltip: {
show: true,
trigger: 'axis',
// formatter: function (params) {
// debugger
// return MyEcharts.resetTooltip(params);
// }
},
//组建
legend: {
data: datas.groups.map((text) => {
return {
name: text, icon: 'rect' } //icon图形样式:'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow'
})
},
//水平坐标
xAxis: [
{
type: 'category',
boundaryGap: false,
splitLine: {
show: false, 去除网格线
},
data: datas.category,
}
],
//垂直坐标
yAxis: [
{
type: 'value'
}
],
//series数据
series: datas.series
};
return option;
},
/**
* 雷达图
* @param title : 标题<br>
* @param subtext :副标题<br>
* @param data : json 数据
*/
Radar: function (title, subtext, data) {
let datas = MyEcharts.EchartsDataFormate.RadarFormate(data, 'radar');
let option = {
//标题
title: {
text: title || "", //标题
subtext: subtext || "", //副标题
},
//提示
tooltip: {
show: true,
},
//组建
legend: {
data: datas.groups
},
radar: {
name: {
textStyle: {
color: '#fff',
backgroundColor: '#999',
borderRadius: 3,
padding: [3, 5]
}
},
indicator: datas.indicators
},
series: datas.series
};
return option;
},
/**
* 漏斗图
* @param title : 标题<br>
* @param subtext :副标题<br>
* @param data : json 数据
*/
Funnel: function (title, subtext, data) {
let datas = MyEcharts.EchartsDataFormate.FunnelFormate(data, 'funnel');
let option = {
//标题
title: {
text: title || "", //标题
subtext: subtext || "", //副标题
},
//提示
tooltip: {
show: true,
trigger: 'item',
formatter: "{a} <br/>{b} ({c}%)"
},
//组建
legend: {
data: datas.groups,
},
series: datas.series
};
return option;
},
/**
* 仪表图
* @param title : 标题<br>
* @param subtext :副标题<br>
* @param data : json 数据
*/
Gauge: function (title, subtext, data) {
let datas = MyEcharts.EchartsDataFormate.GaugeFormate(data, 'gauge');
let option = {
//标题
title: {
text: title || "", //标题
subtext: subtext || "", //副标题
},
//提示
tooltip: {
show: true,
formatter: "{a} <br/>{b}:{c}"
},
series: datas.series
};
return option;
}
},
/**
* 重置Tooltip样式
* @param params : object tooltip函数返回值
*/
resetTooltip: function (params) {
if (params.length === 1) {
//只有一组数据时
let text = params[0].seriesName + '<br/>';
let colorStops = params[0].color;
if (typeof colorStops === 'object') {
//色彩为渐变时
colorStops = params[0].color.colorStops[0].color; //***待优化,此处color不一定有颜色,第一个可能为透明色
}
text += '<span style="display:inline-block;margin-right:5px;border-radius:50%;width:10px;height:10px;left:5px;background-color:' + colorStops + '"></span>' + params[0].axisValue + ' : ' + params[0].value + '<br/>';
return text;
} else {
//一组 多条数据时
let text = params[0].axisValue + '<br/>';
params.forEach((item) => {
let colorStops = item.color;
if (typeof colorStops === 'object') {
//色彩为渐变时
colorStops = item.color.colorStops[0].color; //***待优化,此处color不一定有颜色,第一个可能为透明色
}
text += '<span style="display:inline-block;margin-right:5px;border-radius:50%;width:10px;height:10px;left:5px;background-color:' + colorStops + '"></span>' + item.seriesName + ' : ' + item.value + '<br/>';
})
return text;
}
},
/**
* @param option : option
* @param echartId : 图表的id 需要加引号
*/
initChart: function (option, echartId) {
let container = eval("document.getElementById('" + echartId + "')");
let myChart = echarts.init(container, myTheme);
console.log(myChart.getOption());
myChart.setOption(option, true); // (1图表数据: Object, 2是否不跟之前设置的option进行合并?默认为false,即合并: boolean, 3在设置完option后是否不立即更新图表?默认为false,即立即更新。: boolean)
this.echartSet.push(myChart); // 存储已初始化的图表实例
this.resize(); // 添加图表大小改变事件
return myChart;
},
//图表大小改变事件监听
resize: function () {
try {
window.onresize = function () {
isDebounce(function () {
MyEcharts.echartSet.forEach(item => {
item.resize();
})
}, 300);
}.bind(this);
} catch (error) {
}
}
};
/**
* 数组是否存在某数据
* @param obj
* @returns {Boolean}
*/
Array.prototype.contains = function (obj) {
let i = this.length;
while (i--) {
if (this[i] === obj) {
return true;
}
}
return false;
};
/**
* 数组中最大值 最小值
* @param array
* @returns
*/
Array.prototype.max = function () {
return Math.max.apply({
}, this);
};
Array.prototype.min = function () {
return Math.min.apply({
}, this);
};
/**
* 判断是否为整数
* @param obj
* @returns {Boolean}
*/
function isInteger(obj) {
return obj % 1 === 0;
}
/**
* 判断值是否包含指定字符
* @param str
* @returns {Boolean}
*/
function myRegExp(str) {
let patt = new RegExp(/[%|$]/)
return patt.test(str);
}
/**防抖函数
* 当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。
* 适用于延迟时间后触发一次事件。
*使用方式:isDebounce(函数名,延迟时间)();
*/
let isTimer; // 维护一个 timer
function isDebounce(fn, delay) {
try {
let _this = this; // 取debounce执行作用域的this
let args = arguments;
if (isTimer) {
clearTimeout(isTimer);
}
isTimer = setTimeout(function () {
fn.apply(_this, args); // 用apply指向调用debounce的对象,相当于_this.fn(args);
}, delay);
} catch (error) {
console.log(error);
}
};
/**自定义loading
* 当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。
* 适用于延迟时间后触发一次事件。
*使用方式:isDebounce(函数名,延迟时间)();
*/
function loadingNodata(api, opts) {
let PI = Math.PI;
opts = opts || {
};
opts = new echarts.util.defaults(opts, {
text: 'loading', // loading显示的文字
textColor: '#000', // 文字颜色
fontSize: '12px', // 文字大小
maskColor: 'rgba(255, 255, 255, 0.8)',
showSpinner: true, // 是否显示gif动画
color: '#c23531', // gif填充的颜色
spinnerRadius: 10,
lineWidth: 5,
zlevel: 0
})
var group = new echarts.graphic.Group();
var mask = new echarts.graphic.Rect({
style: {
fill: opts.maskColor
},
zlevel: opts.zlevel,
z: 10000
});
group.add(mask);
var font = opts.fontSize + ' sans-serif';
var labelRect = new echarts.graphic.Rect({
style: {
fill: 'none',
text: opts.text,
font: font,
textPosition: 'right',
textDistance: 10,
textFill: opts.textColor
},
zlevel: opts.zlevel,
z: 10001
});
group.add(labelRect);
if (opts.showSpinner) {
var arc = new echarts.graphic.Arc({
shape: {
startAngle: -PI / 2,
endAngle: -PI / 2 + 0.1,
r: opts.spinnerRadius
},
style: {
stroke: opts.color,
lineCap: 'round',
lineWidth: opts.lineWidth
},
zlevel: opts.zlevel,
z: 10001
});
arc.animateShape(true)
.when(1000, {
endAngle: PI * 3 / 2
})
.start('circularInOut');
arc.animateShape(true)
.when(1000, {
startAngle: PI * 3 / 2
})
.delay(300)
.start('circularInOut');
group.add(arc);
}
//添加计算canvas文字宽度方法
api.getTextWith = function (
text = '',
fontStyle = '14px/1.5715 "Source Sans Pro, Helvetica Neue, Helvetica, Arial, sans-serif"', // 设置字体大小和字体
) {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
context.font = fontStyle;
const elem = context.measureText(text);
return elem.width;
}
group.resize = function () {
var textWidth = api.getTextWith(opts.text, font);
var r = opts.showSpinner ? opts.spinnerRadius : 0;
console.log("@r" + r);
console.log("@showSpinner" + opts.showSpinner);
// cx = (containerWidth - arcDiameter - textDistance - textWidth) / 2
// textDistance needs to be calculated when both animation and text exist
var cx = (api.getWidth() - r * 2 - (opts.showSpinner && textWidth ? 10 : 0) - textWidth) / 2
// only show the text
// - (opts.showSpinner ? 0 : textWidth / 2);
var cy = api.getHeight() / 2;
opts.showSpinner && arc.setShape({
cx: cx,
cy: cy
});
labelRect.setShape({
x: cx - r,
y: cy - r,
width: r * 2,
height: r * 2
});
mask.setShape({
x: 0,
y: 0,
width: api.getWidth(),
height: api.getHeight()
});
};
group.resize();
return group;
};
//向全局echarts对象注册新的loading: nodatas
echarts.registerLoading("nodatas", loadingNodata);
4.效果展示
总结
可能有些地方还不算完善,大家有问题留言,我会及时更新!
都看到这里了,还不给个三连QAQ!