hd-echarts
Version:
huidian ECharts component for Vue.js.
474 lines (453 loc) • 13.3 kB
JavaScript
import echarts from 'echarts/lib/echarts'
import debounce from 'lodash/debounce'
import { addListener, removeListener } from 'resize-detector'
import { legendIcons } from './constant'
// enumerating ECharts events for now
const EVENTS = [
'legendselectchanged',
'legendselected',
'legendunselected',
'legendscroll',
'datazoom',
'datarangeselected',
'timelinechanged',
'timelineplaychanged',
'restore',
'dataviewchanged',
'magictypechanged',
'geoselectchanged',
'geoselected',
'geounselected',
'pieselectchanged',
'pieselected',
'pieunselected',
'mapselectchanged',
'mapselected',
'mapunselected',
'axisareaselected',
'focusnodeadjacency',
'unfocusnodeadjacency',
'brush',
'brushselected',
'rendered',
'finished',
'click',
'dblclick',
'mouseover',
'mouseout',
'mousemove',
'mousedown',
'mouseup',
'globalout',
'contextmenu'
]
const INIT_TRIGGERS = ['theme', 'initOptions', 'autoresize', 'series']
const REWATCH_TRIGGERS = ['manualUpdate', 'watchShallow']
export default {
props: {
title: Object,
legend: Object,
tooltip: Object,
radar: [Array, Object],
xAxis: [Array, Object],
yAxis: [Array, Object],
series: [Array, Object],
options: {
type: Object,
default () {
return {}
}
},
theme: {
type: [String, Object],
default: function () {
return 'default'
}
},
initOptions: {
type: Object,
default () {
return {}
}
},
height: String,
group: String,
autoresize: {
type: Boolean,
default: true
},
watchShallow: Boolean,
manualUpdate: Boolean
},
data () {
return {
lastArea: 0,
style: {
height: '400px',
width: '100%'
},
_options: null,
yAxisIsObject: true
}
},
watch: {
group (group) {
this.chart.group = group
}
},
methods: {
initInitOptions () {
this.initOptions.width && (this.style.width = this.initOptions.width)
this.initOptions.height && (this.style.height = this.initOptions.height)
this.height && (this.style.height = this.height)
},
mergeNormalOptions (options) {
this.title && (options.title = this.title)
this.legend && (options.legend = this.legend)
this.tooltip && (options.tooltip = this.tooltip)
this.radar && (options.radar = this.radar)
this.xAxis && (options.xAxis = this.xAxis)
this.yAxis && (options.yAxis = this.yAxis)
this.series && (options.series = this.series)
return this.deepClone(options)
},
/**
* 自动格式化yAxisLabel
* 值超过10000时自动格式化成1.0万,带一位小数
*/
formatterYAxisAxisLabel (options) {
if (!options.yAxis) return options
let yAxis = options.yAxis
const setFormatter = function (yAxis) {
if (!yAxis) return
if (!(yAxis && yAxis.axisLabel && yAxis.axisLabel.formatter)) {
!yAxis.axisLabel && (yAxis.axisLabel = {})
yAxis.axisLabel.formatter = function (v) {
if (Number.isInteger(v)) {
let symbol = '',
value,
k = 10000,
sizes = ['', '万', '亿', '万亿', '亿亿', 'Infinite'],
i
if (v < 0) {
symbol = '-'
value = - v
} else {
value = v
}
if (value < k) {
return v
} else {
i = Math.floor(Math.log(value) / Math.log(k))
return symbol + ((value / Math.pow(k, i))).toFixed(1) + sizes[i]
}
} else {
return v
}
}
}
}
this.yAxisIsObject = Object.prototype.toString.call(options.yAxis) === '[object Object]'
if (this.yAxisIsObject) {
setFormatter(yAxis)
} else {
yAxis.forEach(yAxi => {
setFormatter(yAxi)
})
}
return options
},
/**
* 极坐标type会是line或bar
*/
isBarOrLine () {
let series= this._options.series
if(Object.prototype.toString.call(series) === '[object Array]') {
return series.some(elem => {
return elem.type === 'line' || elem.type === 'bar'
})
} else {
return series.type === 'line' || series.type === 'bar'
}
},
/**
* 获取xAxis.data的长度
* xAxis是object or array
*/
getXAxisDataLength () {
let options = this._options
if (options.xAxis !== undefined) {
if (Object.prototype.toString.call(options.xAxis) === '[object Object]') {
return options.xAxis.data !== undefined ? options.xAxis.data.length : null
} else {
return Math.max(
(options.xAxis[0].data !== undefined ? options.xAxis[0].data.length : 0),
(options.xAxis[1] !== undefined && options.xAxis[1].data !== undefined ? options.xAxis[1].data.length : 0)
)
}
}
return null
},
// provide a explicit merge option method
mergeOptions (options, notMerge, lazyUpdate) {
if (this.manualUpdate) {
this.manualOptions = options
}
if (!this.chart) {
this.init()
} else {
this.delegateMethod('setOption', options, notMerge, lazyUpdate)
}
},
// just delegates ECharts methods to Vue component
// use explicit params to reduce transpiled size for now
appendData (params) {
this.delegateMethod('appendData', params)
},
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) {
this.init()
}
return this.chart[name](...args)
},
delegateGet (methodName) {
if (!this.chart) {
this.init()
}
return this.chart[methodName]()
},
getArea () {
return this.$el.offsetWidth * this.$el.offsetHeight
},
// 实现legend.icon和legend.data.icon自定义配置
modifyLegend (options) {
const legend = options.legend
if (!legend || !echarts.customOptions || !echarts.customOptions.legend) return options
const customIcon = echarts.customOptions.legend.icon
if (customIcon !== undefined) {
const icon = legend.icon
if (icon !== undefined
&& !legendIcons.includes(icon)
&& customIcon[icon]) {
legend.icon = customIcon[icon]
}
if (legend.data) {
legend.data.forEach(item => {
if (Object.prototype.toString.call(item) === '[object Object]') {
const icon = item.icon
if (icon !== undefined
&& !legendIcons.includes(icon)
&& customIcon[icon]) {
item.icon = customIcon[icon]
}
}
})
}
}
return options
},
modifyOptions (opts) {
let options = this.formatterYAxisAxisLabel(opts)
options = this.modifyLegend(options)
return options
},
//使用递归的方式实现数组、对象的深拷贝
deepClone (obj) {
var objClone = Array.isArray(obj) ? [] : {};
if (obj && typeof obj === "object") {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === "object") {
objClone[key] = this.deepClone(obj[key]);
} else {
objClone[key] = obj[key];
}
}
}
}
return objClone;
},
init () {
if (this.chart) {
return
}
let chart = echarts.init(this.$el, this.theme, this.initOptions)
if (this.group) {
chart.group = this.group
}
this._options = this.mergeNormalOptions(this.manualOptions || this.options)
this.setType && this.setType()
this._options = this.modifyOptions(this._options)
chart.setOption(this._options || {}, true)
// expose ECharts events as custom events
EVENTS.forEach(event => {
chart.on(event, params => {
this.$emit(event, params)
})
})
if (this.autoresize) {
this.lastArea = this.getArea()
this.__resizeHandler = debounce(() => {
if (this.lastArea === 0) {
// emulate initial render for initially hidden charts
this.mergeOptions({}, true)
this.resize()
this.mergeOptions(this.options || this.manualOptions || {}, true)
} else {
this.resize()
}
this.lastArea = this.getArea()
}, 100, { leading: true })
addListener(this.$el, this.__resizeHandler)
}
Object.defineProperties(this, {
// Only recalculated when accessed from JavaScript.
// Won't update DOM on value change because getters
// don't depend on reactive values
width: {
configurable: true,
get: () => {
return this.delegateGet('getWidth')
}
},
height: {
configurable: true,
get: () => {
return this.delegateGet('getHeight')
}
},
isDisposed: {
configurable: true,
get: () => {
return !!this.delegateGet('isDisposed')
}
},
computedOptions: {
configurable: true,
get: () => {
return this.delegateGet('getOption')
}
}
})
this.chart = chart
},
initOptionsWatcher () {
if (this.__unwatchOptions) {
this.__unwatchOptions()
this.__unwatchOptions = null
}
if (!this.manualUpdate) {
this.__unwatchOptions = this.$watch('options', (val, oldVal) => {
if (!this.chart && val) {
this.init()
} else {
// mutating `options` will lead to merging
// replacing it with new reference will lead to not merging
// eg.
// `this.options = Object.assign({}, this.options, { ... })`
// will trigger `this.chart.setOption(val, true)
// `this.options.title.text = 'Trends'`
// will trigger `this.chart.setOption(val, false)`
this.chart.setOption(this.modifyOptions(val), val !== oldVal)
}
}, { deep: !this.watchShallow })
}
},
destroy () {
if (this.autoresize) {
removeListener(this.$el, this.__resizeHandler)
}
this.dispose()
this.chart = null
},
refresh () {
if (this.chart) {
this.destroy()
this.init()
}
}
},
created () {
this.initOptionsWatcher()
if (this.initOptions) {
this.initInitOptions()
}
INIT_TRIGGERS.forEach(prop => {
this.$watch(prop, () => {
this.refresh()
}, { deep: true })
})
REWATCH_TRIGGERS.forEach(prop => {
this.$watch(prop, () => {
this.initOptionsWatcher()
this.refresh()
})
})
},
mounted () {
// auto init if `options` is already provided
if (this.options) {
this.init()
}
},
activated () {
if (this.autoresize) {
this.chart && this.chart.resize()
}
},
beforeDestroy () {
if (!this.chart) {
return
}
this.destroy()
},
connect (group) {
if (typeof group !== 'string') {
group = group.map(chart => chart.chart)
}
echarts.connect(group)
},
disconnect (group) {
echarts.disConnect(group)
},
registerMap (mapName, geoJSON, specialAreas) {
echarts.registerMap(mapName, geoJSON, specialAreas)
},
registerTheme (name, theme) {
echarts.registerTheme(name, theme)
},
graphic: echarts.graphic
}