echarts在Vue中的封装

封装echarts

封装echarts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
<template>
<div class="echarts"></div>
</template>

<script>
import echarts from 'echarts';
import _debounce from 'lodash/debounce';
// enumerating ECharts events for now
const ACTION_EVENTS = [
'legendselectchanged',
'legendselected',
'legendunselected',
'datazoom',
'datarangeselected',
'timelinechanged',
'timelineplaychanged',
'restore',
'dataviewchanged',
'magictypechanged',
'geoselectchanged',
'geoselected',
'geounselected',
'pieselectchanged',
'pieselected',
'pieunselected',
'mapselectchanged',
'mapselected',
'mapunselected',
'axisareaselected',
'brush',
'brushselected'
];
const MOUSE_EVENTS = ['click', 'dblclick', 'mouseover', 'mouseout', 'mousedown', 'mouseup', 'globalout'];
export default {
name: 'myChart',
data() {
return {
chart: null
};
},
props: {
options: Object,
theme: [String, Object],
initOptions: Object,
group: String,
autoResize: Boolean
},
computed: {
// Only recalculated when accessed from JavaScript.
// Won't update DOM on value change because getters
// don't depend on reactive values
width: {
cache: false,
get() {
return this.chart.getWidth();
}
},
height: {
cache: false,
get() {
return this.chart.getHeight();
}
},
isDisposed: {
cache: false,
get() {
return this.chart.isDisposed();
}
}
},
watch: {
// use assign statements to tigger "options" and "group" setters
options: {
handler(options) {
if (!this.chart && options) {
this._initEchart();
} else {
this.chart.setOption(this.options, true);
}
},
deep: true
},
group: {
handler(group) {
this.chart.group = group;
}
}
},
methods: {
// provide a explicit merge option method
mergeOptions(options) {
this._delegateMethod('setOption', options);
},
// just delegates ECharts methods to Vue component
// use explicit params to reduce transpiled size for now
resize(options) {
this._delegateMethod('resize', options);
},
dispatchAction(payload) {
this._delegateMethod('dispatchAction', payload);
},
convertToPixel(finder, value) {
return this._delegateMethod('convertToPixel', finder, value);
},
convertFromPixel(finder, value) {
return this._delegateMethod('convertFromPixel', finder, value);
},
containPixel(finder, value) {
return this._delegateMethod('containPixel', finder, value);
},
showLoading(type, options) {
this._delegateMethod('showLoading', type, options);
},
hideLoading() {
this._delegateMethod('hideLoading');
},
getDataURL(options) {
return this._delegateMethod('getDataURL', options);
},
getConnectedDataURL(options) {
return this._delegateMethod('getConnectedDataURL', options);
},
clear() {
this._delegateMethod('clear');
},
dispose() {
this._delegateMethod('dispose');
},
_delegateMethod(name, ...args) {
if (!this.chart) {
// Vue.util.warn(`Cannot call [${name}] before the chart is initialized. Set prop [options] first.`, this);
this.$Message.error(`Cannot call [${name}] before the chart is initialized. Set prop [options] first.`);
return;
}
return this.chart[name](...args);
},
_initEchart() {
if (this.chart) {
return;
}

let chart = echarts.init(this.$el, this.theme, this.initOptions);

if (this.group) {
chart.group = this.group;
}

chart.setOption(this.options, true);

// expose ECharts events as custom events
ACTION_EVENTS.forEach(event => {
chart.on(event, params => {
this.$emit(event, params);
});
});
MOUSE_EVENTS.forEach(event => {
chart.on(event, params => {
this.$emit(event, params);

// for backward compatibility, may remove in the future
this.$emit('chart' + event, params);
});
});

if (this.autoResize) {
this.__resizeHanlder = _debounce(
() => {
chart.resize();
},
100,
{ leading: true }
);
window.addEventListener('resize', this.__resizeHanlder);
}

this.chart = chart;
}
},
mounted() {
// auto init if `options` is already provided
if (this.options) {
this.$nextTick(() => {
this._initEchart();
});
}
},
activated() {
if (this.autoResize) {
this.chart && this.chart.resize();
}
},
beforeDestroy() {
if (!this.chart) {
return;
}
if (this.autoResize) {
window.removeEventListener('resize', this.__resizeHanlder);
}
this.dispose();
},
connect(group) {
if (typeof group !== 'string') {
group = group.map(chart => chart.chart);
}
echarts.connect(group);
},
disconnect(group) {
echarts.disConnect(group);
},
registerMap(...args) {
echarts.registerMap(...args);
},
registerTheme(...args) {
echarts.registerTheme(...args);
}
};
</script>

<style scoped>
.echarts {
width: 100%;
height: 100%;
}
</style>

传参:

  1. options 配置项
  2. autoResize 是否自动缩放

注:引入lodash的防抖

进一步封装——折线图

封装

line-chart.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<template>
<div class="line-chart-cnt">
<MyChart :options="option" :autoResize="true"></MyChart>
</div>
</template>

<script>
import MyChart from '@/components/my-chart';
export default {
name: 'lineChart',
data() {
return {
option: {
title: {
left: 'center',
text: ''
},
xAxis: {
axisLabel: {
interval: 0,
rotate: 40
},
type: 'category',
data: []
},
yAxis: {
type: 'value',
axisLabel: {
show: true,
interval: 'auto',
formatter: '{value}%'
}
},
series: [
{
data: [],
type: 'line'
}
]
}
};
},
props: {
title: String,
chartData: Array,
xAxisData: Array
},
components: {
MyChart
},
mounted() {
this.option.title.text = this.title;
this.option.xAxis.data = this.xAxisData;
this.option.series[0].data = this.chartData;
},
watch: {
xAxisData: {
handler: function(val) {
this.option.xAxis.data = val;
},
immediate: true
},
chartData: {
handler: function(val) {
this.option.series[0].data = val;
},
immediate: true
},
title: {
handler: function(val) {
this.option.title.text = val;
},
immediate: true
}
}
};
</script>

<style scoped lang="less">
.line-chart-cnt {
width: 100%;
height: 400px;
padding: 0 40px;
}
</style>

传参:

  1. title 图表的标题
  2. chartData 数据
  3. xAxisData x轴数据

使用

1
<LineChart :title="chartTitle" :chartData="chartData" :xAxisData="xAxisData"></LineChart>

进一步封装——饼图

封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<template>
<div class="line-chart-cnt">
<MyChart :options="option" :autoResize="true"></MyChart>
</div>
</template>

<script>
import MyChart from '@/components/my-chart';
export default {
name: 'lineChart',
data() {
return {
option: {
title: {
left: 'center',
text: ''
},
tooltip: {
trigger: 'item',
formatter: '{b}<br/>{d}%'
},
legend: {
orient: 'vertical',
left: 'left',
data: []
},
series: [
{
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: [],
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
label: {
normal: {
formatter: '{b}\n{d}%'
}
}
}
],
color: [
'#004DA1',
'#009EA7',
'#AACD03',
'#F9BE00',
'#EC6C00',
'#A40081',
'#0068B6',
'#00A73B',
'#FFD800',
'#F29600',
'#E5002D',
'#541B85'
]
}
};
},
props: {
title: String,
chartData: Array
},
mounted() {
this.option.title.text = this.title;
this.option.series[0].data = this.chartData;
this.chartData.forEach(item => {
this.option.legend.data.push(item.name);
});
},
watch: {
chartData: {
handler: function(val) {
this.option.series[0].data = val;
val.forEach(item => {
this.option.legend.data.push(item.name);
});
},
immediate: true
},
title: {
handler: function(val) {
this.option.title.text = val;
},
immediate: true
}
},
components: {
MyChart
}
};
</script>

<style scoped lang="less">
.line-chart-cnt {
width: 100%;
height: 400px;
padding: 0 40px;
}
</style>

传参:

  1. title 图表的标题
  2. chartData 数据

使用

1
<SimplePie :title="'各项目人力投向占比'" :chartData="pieChartData"></SimplePie>

进一步封装——柱状图

封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
<template>
<div class="line-chart-cnt">
<MyChart :options="option" :autoResize="true"></MyChart>
</div>
</template>

<script>
import MyChart from '@/components/my-chart';
export default {
name: 'BarChart',
data() {
return {
option: {
title: {
left: 'center',
text: ''
},
tooltip: {
trigger: 'axis',
axisPointer: {
// 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
xAxis: {
type: 'category',
data: []
},
yAxis: {
type: 'value',
axisLabel: {
show: true,
interval: 'auto'
}
},
series: [
{
data: [],
type: 'bar',
label: {
normal: {
show: true,
position: 'top'
}
},
itemStyle: {
normal: { color: 'rgb(0,77,161)' }
}
}
]
}
};
},
props: {
title: String, // 标题
chartData: Array, // 图标数据
xAxisData: Array, // x轴标签
isPercent: Boolean // true:y轴显示百分比,false:y轴显示数值
},
components: {
MyChart
},
mounted() {
this.option.title.text = this.title;
this.option.xAxis.data = this.xAxisData;
this.option.series[0].data = this.chartData;
},
watch: {
xAxisData: {
handler: function(val) {
this.option.xAxis.data = val;
},
immediate: true
},
chartData: {
handler: function(val) {
this.option.series[0].data = val;
},
immediate: true
},
title: {
handler: function(val) {
this.option.title.text = val;
},
immediate: true
},
isPercent: {
handler: function(val) {
if (val) {
this.option.yAxis.axisLabel.formatter = '{value}%';
}
},
immediate: true
}
}
};
</script>

<style scoped lang="less">
.line-chart-cnt {
width: 100%;
height: 400px;
padding: 0 40px;
}
</style>

传参:

  1. title 图表的标题
  2. chartData 数据
  3. xAxisData x轴数据
  4. isPercent true:y轴显示百分比,false:y轴显示数值

使用

1
<BarChart :title="'活跃度'" :chartData="activityChartData" :xAxisData="activityXAxisData" :isPercent="true"></BarChart>

带辅助横线的柱状图

辅助线只需设置series[index].markLine即可

封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
<template>
<div class="line-chart-cnt">
<MyChart :options="option" :autoResize="true"></MyChart>
</div>
</template>

<script>
import MyChart from '@/components/my-chart';
export default {
name: 'MarkLineBarChart',
data() {
return {
option: {
title: {
left: 'center',
text: ''
},
tooltip: {
trigger: 'axis',
axisPointer: {
// 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
xAxis: {
type: 'category',
data: []
},
yAxis: {
type: 'value',
axisLabel: {
show: true,
interval: 'auto'
}
},
series: [
{
data: [],
type: 'bar',
label: {
normal: {
show: true,
position: 'top'
}
},
markLine: {
data: [
{
name: '目标值',
yAxis: ''
}
]
},
itemStyle: {
normal: { color: 'rgba(0,77,161)' }
}
}
]
}
};
},
props: {
title: String, // 标题
chartData: Array, // 图标数据
xAxisData: Array, // x轴标签
isPercent: Boolean, // true:y轴显示百分比,false:y轴显示数值
percentAim: Number // 百分比的目标值
},
components: {
MyChart
},
mounted() {
this.option.title.text = this.title;
this.option.xAxis.data = this.xAxisData;
this.option.series[0].data = this.chartData;
},
watch: {
xAxisData: {
handler: function(val) {
this.option.xAxis.data = val;
},
immediate: true
},
chartData: {
handler: function(val) {
this.option.series[0].data = val;
},
immediate: true
},
title: {
handler: function(val) {
this.option.title.text = val;
},
immediate: true
},
isPercent: {
handler: function(val) {
if (val) {
this.option.yAxis.axisLabel.formatter = '{value}%';
}
},
immediate: true
},
percentAim: {
handler: function(val) {
this.option.series[0].markLine.data[0].yAxis = val;
},
immediate: true
}
}
};
</script>

<style scoped lang="less">
.line-chart-cnt {
width: 100%;
height: 400px;
padding: 0 40px;
}
</style>

传参:

  1. title: String 图表的标题
  2. chartData: Array 数据
  3. xAxisData: Array x轴数据
  4. isPercent: Boolean true:y轴显示百分比,false:y轴显示数值
  5. percentAim: Number 百分比的目标值

使用

1
<MarkLineBarChart :title="'打卡完成率'" :chartData="closeLoopChartData" :xAxisData="closeLoopXAxisData" :isPercent="true" :percentAim="90"></MarkLineBarChart>

覆盖的柱状图

覆盖只需设每个series[index].barGap: '-100%'即可

封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
<template>
<div class="line-chart-cnt">
<MyChart :options="option" :autoResize="true"></MyChart>
</div>
</template>

<script>
import MyChart from '@/components/my-chart';
import { toPercent } from '@/utils';
export default {
name: 'CoverBarChart',
data() {
return {
option: {
title: {
left: 'center',
text: ''
},
tooltip: {
trigger: 'axis',
axisPointer: {
// 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
legend: {
data: ['目标', '实际'],
left: 'right'
},
xAxis: {
type: 'category',
data: []
},
yAxis: {
type: 'value',
axisLabel: {
show: true,
interval: 'auto'
}
},
series: [
{
name: '目标',
data: [],
type: 'bar',
barGap: '-100%',
label: {
normal: {
show: true,
position: 'top',
offset: [55, 0]
}
},
itemStyle: {
normal: {
color: 'rgba(0,77,161)'
}
}
},
{
name: '实际',
data: [],
type: 'bar',
barGap: '-100%',
label: {
normal: {
show: true,
position: 'top',
formatter: ''
}
},
itemStyle: {
normal: {
color: 'rgba(249,190,0,0.7)'
// opacity: 0.7
}
}
}
]
}
};
},
props: {
title: String,
xAxisData: Array,
AimChartData: Array,
factChartData: Array
},
components: {
MyChart
},
mounted() {
this.option.title.text = this.title;
this.option.xAxis.data = this.xAxisData;
this.option.series[0].data = this.AimChartData;
this.option.series[1].data = this.factChartData;
},
watch: {
xAxisData: {
handler: function(val) {
this.option.xAxis.data = val;
},
immediate: true
},
AimChartData: {
handler: function(val) {
this.option.series[0].data = val;
},
immediate: true
},
factChartData: {
handler: function(val) {
this.option.series[1].data = val;
this.option.series[1].data.forEach((item, index) => {
this.option.series[1].label.normal.formatter = `${item},${toPercent(
item / this.option.series[0].data[index]
)}`;
});
},
immediate: true
},
title: {
handler: function(val) {
this.option.title.text = val;
},
immediate: true
}
}
};
</script>

<style scoped lang="less">
.line-chart-cnt {
width: 100%;
height: 400px;
padding: 0 40px;
}
</style>

传参:

  1. title: String 图表的标题
  2. xAxisData: Array x轴数据
  3. AimChartData: Array 两个数据
  4. factChartData: Array

使用

1
<CoverBarChart :title="'粉丝老师达成情况'" :AimChartData="AimChartData" :factChartData="factChartData" :xAxisData="fanXAxisData"></CoverBarChart>

参考:https://qjzd.net/topic/59afb984b9fded0f2581c259