You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
514 lines
13 KiB
514 lines
13 KiB
<template> |
|
<view class="lime-echart" :style="customStyle" v-if="canvasId" ref="limeEchart" :aria-label="ariaLabel"> |
|
<!-- #ifndef APP-NVUE --> |
|
<canvas |
|
class="lime-echart__canvas" |
|
v-if="use2dCanvas" |
|
type="2d" |
|
:id="canvasId" |
|
:style="canvasStyle" |
|
:disable-scroll="isDisableScroll" |
|
@touchstart="touchStart" |
|
@touchmove="touchMove" |
|
@touchend="touchEnd" |
|
/> |
|
<canvas |
|
class="lime-echart__canvas" |
|
v-else |
|
:width="nodeWidth" |
|
:height="nodeHeight" |
|
:style="canvasStyle" |
|
:canvas-id="canvasId" |
|
:id="canvasId" |
|
:disable-scroll="isDisableScroll" |
|
@touchstart="touchStart" |
|
@touchmove="touchMove" |
|
@touchend="touchEnd" |
|
/> |
|
<view class="lime-echart__mask" |
|
v-if="isPC" |
|
@mousedown="touchStart" |
|
@mousemove="touchMove" |
|
@mouseup="touchEnd" |
|
@touchstart="touchStart" |
|
@touchmove="touchMove" |
|
@touchend="touchEnd"> |
|
</view> |
|
<canvas v-if="isOffscreenCanvas" :style="offscreenStyle" :canvas-id="offscreenCanvasId"></canvas> |
|
<!-- #endif --> |
|
<!-- #ifdef APP-NVUE --> |
|
<web-view |
|
class="lime-echart__canvas" |
|
:id="canvasId" |
|
:style="canvasStyle" |
|
:webview-styles="webviewStyles" |
|
ref="webview" |
|
src="/uni_modules/lime-echart/static/uvue.html?v=1" |
|
@pagefinish="finished = true" |
|
@onPostMessage="onMessage" |
|
></web-view> |
|
<!-- #endif --> |
|
</view> |
|
</template> |
|
|
|
<script> |
|
// #ifndef APP-NVUE |
|
import {Canvas, setCanvasCreator, dispatch} from './canvas'; |
|
import {wrapTouch, convertTouchesToArray, devicePixelRatio ,sleep, canIUseCanvas2d, getRect} from './utils'; |
|
// #endif |
|
// #ifdef APP-NVUE |
|
import { base64ToPath, sleep } from './utils'; |
|
import {Echarts} from './nvue' |
|
// #endif |
|
const charts = {} |
|
const echartsObj = {} |
|
|
|
|
|
/** |
|
* LimeChart 图表 |
|
* @description 全端兼容的eCharts |
|
* @tutorial https://ext.dcloud.net.cn/plugin?id=4899 |
|
|
|
* @property {String} customStyle 自定义样式 |
|
* @property {String} type 指定 canvas 类型 |
|
* @value 2d 使用canvas 2d,部分小程序支持 |
|
* @value '' 使用原生canvas,会有层级问题 |
|
* @value bottom right 不缩放图片,只显示图片的右下边区域 |
|
* @property {Boolean} isDisableScroll |
|
* @property {number} beforeDelay = [30] 延迟初始化 (毫秒) |
|
* @property {Boolean} enableHover PC端使用鼠标悬浮 |
|
|
|
* @event {Function} finished 加载完成触发 |
|
*/ |
|
export default { |
|
name: 'lime-echart', |
|
props: { |
|
// #ifdef MP-WEIXIN || MP-TOUTIAO |
|
type: { |
|
type: String, |
|
default: '2d' |
|
}, |
|
// #endif |
|
// #ifdef APP-NVUE |
|
webviewStyles: Object, |
|
// hybrid: Boolean, |
|
// #endif |
|
customStyle: String, |
|
isDisableScroll: Boolean, |
|
isClickable: { |
|
type: Boolean, |
|
default: true |
|
}, |
|
enableHover: Boolean, |
|
beforeDelay: { |
|
type: Number, |
|
default: 30 |
|
} |
|
}, |
|
data() { |
|
return { |
|
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY |
|
use2dCanvas: true, |
|
// #endif |
|
// #ifndef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY |
|
use2dCanvas: false, |
|
// #endif |
|
ariaLabel: '图表', |
|
width: null, |
|
height: null, |
|
nodeWidth: null, |
|
nodeHeight: null, |
|
// canvasNode: null, |
|
config: {}, |
|
inited: false, |
|
finished: false, |
|
file: '', |
|
platform: '', |
|
isPC: false, |
|
isDown: false, |
|
isOffscreenCanvas: false, |
|
offscreenWidth: 0, |
|
offscreenHeight: 0 |
|
}; |
|
}, |
|
computed: { |
|
canvasId() { |
|
return `lime-echart${this._ && this._.uid || this._uid}` |
|
}, |
|
offscreenCanvasId() { |
|
return `${this.canvasId}_offscreen` |
|
}, |
|
offscreenStyle() { |
|
return `width:${this.offscreenWidth}px;height: ${this.offscreenHeight}px; position: fixed; left: 99999px; background: red` |
|
}, |
|
canvasStyle() { |
|
return this.width && this.height ? ('width:' + this.width + 'px;height:' + this.height + 'px') : '' |
|
} |
|
}, |
|
// #ifndef VUE3 |
|
beforeDestroy() { |
|
this.clear() |
|
this.dispose() |
|
// #ifdef H5 |
|
if(this.isPC) { |
|
document.removeEventListener('mousewheel', this.mousewheel) |
|
} |
|
// #endif |
|
}, |
|
// #endif |
|
// #ifdef VUE3 |
|
beforeUnmount() { |
|
this.clear() |
|
this.dispose() |
|
// #ifdef H5 |
|
if(this.isPC) { |
|
document.removeEventListener('mousewheel', this.mousewheel) |
|
} |
|
// #endif |
|
}, |
|
// #endif |
|
created() { |
|
// #ifdef H5 |
|
if(!('ontouchstart' in window)) { |
|
this.isPC = true |
|
document.addEventListener('mousewheel', this.mousewheel) |
|
} |
|
// #endif |
|
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY |
|
const { platform } = uni.getSystemInfoSync(); |
|
this.isPC = /windows/i.test(platform) |
|
// #endif |
|
this.use2dCanvas = this.type === '2d' && canIUseCanvas2d() |
|
}, |
|
mounted() { |
|
this.$nextTick(() => { |
|
this.$emit('finished') |
|
}) |
|
}, |
|
methods: { |
|
// #ifdef APP-NVUE |
|
onMessage(e) { |
|
const detail = e?.detail?.data[0] || null; |
|
const data = detail?.data |
|
const key = detail?.event |
|
const options = data?.options |
|
const event = data?.event |
|
const file = detail?.file |
|
if (key == 'log' && data) { |
|
console.log(data) |
|
} |
|
if(event) { |
|
this.chart.dispatchAction(event.replace(/"/g,''), options) |
|
} |
|
if(file) { |
|
thie.file = file |
|
} |
|
}, |
|
// #endif |
|
setChart(callback) { |
|
if(!this.chart) { |
|
console.warn(`组件还未初始化,请先使用 init`) |
|
return |
|
} |
|
if(typeof callback === 'function' && this.chart) { |
|
callback(this.chart); |
|
} |
|
// #ifdef APP-NVUE |
|
if(typeof callback === 'function') { |
|
this.$refs.webview.evalJs(`setChart(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.chart.options)})`); |
|
} |
|
// #endif |
|
}, |
|
setOption() { |
|
if (!this.chart || !this.chart.setOption) { |
|
console.warn(`组件还未初始化,请先使用 init`) |
|
return |
|
} |
|
this.chart.setOption(...arguments); |
|
}, |
|
showLoading() { |
|
if(this.chart) { |
|
this.chart.showLoading(...arguments) |
|
} |
|
}, |
|
hideLoading() { |
|
if(this.chart) { |
|
this.chart.hideLoading() |
|
} |
|
}, |
|
clear() { |
|
if(this.chart) { |
|
this.chart.clear() |
|
} |
|
}, |
|
dispose() { |
|
if(this.chart) { |
|
this.chart.dispose() |
|
} |
|
}, |
|
resize(size) { |
|
if(size && size.width && size.height) { |
|
this.height = size.height |
|
this.width = size.width |
|
if(this.chart) {this.chart.resize(size)} |
|
} else { |
|
this.$nextTick(() => { |
|
uni.createSelectorQuery() |
|
.in(this) |
|
.select(`.lime-echart`) |
|
.boundingClientRect() |
|
.exec(res => { |
|
if (res) { |
|
let { width, height } = res[0]; |
|
this.width = width = width || 300; |
|
this.height = height = height || 300; |
|
this.chart.resize({width, height}) |
|
} |
|
}); |
|
}) |
|
|
|
} |
|
|
|
}, |
|
canvasToTempFilePath(args = {}) { |
|
// #ifndef APP-NVUE |
|
const { use2dCanvas, canvasId } = this; |
|
return new Promise((resolve, reject) => { |
|
const copyArgs = Object.assign({ |
|
canvasId, |
|
success: resolve, |
|
fail: reject |
|
}, args); |
|
if (use2dCanvas) { |
|
delete copyArgs.canvasId; |
|
copyArgs.canvas = this.canvasNode; |
|
} |
|
uni.canvasToTempFilePath(copyArgs, this); |
|
}); |
|
// #endif |
|
// #ifdef APP-NVUE |
|
this.file = '' |
|
this.$refs.webview.evalJs(`canvasToTempFilePath()`); |
|
return new Promise((resolve, reject) => { |
|
this.$watch('file', async (file) => { |
|
if(file) { |
|
const tempFilePath = await base64ToPath(file) |
|
resolve(args.success({tempFilePath})) |
|
} else { |
|
reject(args.fail({error: ``})) |
|
} |
|
}) |
|
}) |
|
// #endif |
|
}, |
|
async init(echarts, ...args) { |
|
// #ifndef APP-NVUE |
|
if(args && args.length == 0 && !echarts) { |
|
console.error('缺少参数:init(echarts, theme?:string, opts?: object, callback?: function)') |
|
return |
|
} |
|
// #endif |
|
let theme=null,opts={},callback; |
|
|
|
Array.from(arguments).forEach(item => { |
|
if(typeof item === 'function') { |
|
callback = item |
|
} |
|
if(['string'].includes(typeof item)) { |
|
theme = item |
|
} |
|
if(typeof item === 'object') { |
|
opts = item |
|
} |
|
}) |
|
|
|
if(this.beforeDelay) { |
|
await sleep(this.beforeDelay) |
|
} |
|
let config = await this.getContext(); |
|
// #ifndef APP-NVUE |
|
setCanvasCreator(echarts, config) |
|
try { |
|
this.chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts)) |
|
if(typeof callback === 'function') { |
|
callback(this.chart) |
|
} else { |
|
return this.chart |
|
} |
|
} catch(e) { |
|
console.error(e.messges) |
|
return null |
|
} |
|
// #endif |
|
// #ifdef APP-NVUE |
|
this.chart = new Echarts(this.$refs.webview) |
|
this.$refs.webview.evalJs(`init(null, null, ${JSON.stringify(opts)}, ${theme})`) |
|
if(callback) { |
|
callback(this.chart) |
|
} else { |
|
return this.chart |
|
} |
|
// #endif |
|
}, |
|
getContext() { |
|
// #ifdef APP-NVUE |
|
if(this.finished) { |
|
return Promise.resolve(this.finished) |
|
} |
|
return new Promise(resolve => { |
|
this.$watch('finished', (val) => { |
|
if(val) { |
|
resolve(this.finished) |
|
} |
|
}) |
|
}) |
|
// #endif |
|
// #ifndef APP-NVUE |
|
return getRect(`#${this.canvasId}`, {context: this, type: this.use2dCanvas ? 'fields': 'boundingClientRect'}).then(res => { |
|
if(res) { |
|
let dpr = devicePixelRatio |
|
let {width, height, node} = res |
|
let canvas; |
|
this.width = width = width || 300; |
|
this.height = height = height || 300; |
|
if(node) { |
|
const ctx = node.getContext('2d'); |
|
canvas = new Canvas(ctx, this, true, node); |
|
this.canvasNode = node |
|
} else { |
|
// #ifdef MP-TOUTIAO |
|
dpr = !this.isPC ? devicePixelRatio : 1// 1.25 |
|
// #endif |
|
// #ifndef MP-ALIPAY || MP-TOUTIAO |
|
dpr = this.isPC ? devicePixelRatio : 1 |
|
// #endif |
|
// #ifdef MP-ALIPAY || MP-LARK |
|
dpr = devicePixelRatio |
|
// #endif |
|
// #ifdef WEB |
|
dpr = 1 |
|
// #endif |
|
this.rect = res |
|
this.nodeWidth = width * dpr; |
|
this.nodeHeight = height * dpr; |
|
const ctx = uni.createCanvasContext(this.canvasId, this); |
|
canvas = new Canvas(ctx, this, false); |
|
} |
|
return { canvas, width, height, devicePixelRatio: dpr, node }; |
|
} else { |
|
return {} |
|
} |
|
}) |
|
// #endif |
|
}, |
|
// #ifndef APP-NVUE |
|
getRelative(e, touches) { |
|
let { clientX, clientY } = e |
|
if(!(clientX && clientY) && touches && touches[0]) { |
|
clientX = touches[0].clientX |
|
clientY = touches[0].clientY |
|
} |
|
return {x: clientX - this.rect.left, y: clientY - this.rect.top, wheelDelta: e.wheelDelta || 0} |
|
}, |
|
getTouch(e, touches) { |
|
const {x} = touches && touches[0] || {} |
|
return x ? touches[0] : this.getRelative(e, touches); |
|
}, |
|
touchStart(e) { |
|
this.isDown = true |
|
const next = () => { |
|
const touches = convertTouchesToArray(e.touches) |
|
if(this.chart) { |
|
const touch = this.getTouch(e, touches) |
|
this.startX = touch.x |
|
this.startY = touch.y |
|
this.startT = new Date() |
|
const handler = this.chart.getZr().handler; |
|
dispatch.call(handler, 'mousedown', touch) |
|
dispatch.call(handler, 'mousemove', touch) |
|
handler.processGesture(wrapTouch(e), 'start'); |
|
clearTimeout(this.endTimer); |
|
} |
|
|
|
} |
|
if(this.isPC) { |
|
getRect(`#${this.canvasId}`, {context: this}).then(res => { |
|
this.rect = res |
|
next() |
|
}) |
|
return |
|
} |
|
next() |
|
}, |
|
touchMove(e) { |
|
if(this.isPC && this.enableHover && !this.isDown) {this.isDown = true} |
|
const touches = convertTouchesToArray(e.touches) |
|
if (this.chart && this.isDown) { |
|
const handler = this.chart.getZr().handler; |
|
dispatch.call(handler, 'mousemove', this.getTouch(e, touches)) |
|
handler.processGesture(wrapTouch(e), 'change'); |
|
} |
|
|
|
}, |
|
touchEnd(e) { |
|
this.isDown = false |
|
if (this.chart) { |
|
const touches = convertTouchesToArray(e.changedTouches) |
|
const {x} = touches && touches[0] || {} |
|
const touch = (x ? touches[0] : this.getRelative(e, touches)) || {}; |
|
const handler = this.chart.getZr().handler; |
|
const isClick = Math.abs(touch.x - this.startX) < 10 && new Date() - this.startT < 200; |
|
dispatch.call(handler, 'mouseup', touch) |
|
handler.processGesture(wrapTouch(e), 'end'); |
|
if(isClick) { |
|
dispatch.call(handler, 'click', touch) |
|
} else { |
|
this.endTimer = setTimeout(() => { |
|
dispatch.call(handler, 'mousemove', {x: 999999999,y: 999999999}); |
|
dispatch.call(handler, 'mouseup', {x: 999999999,y: 999999999}); |
|
},50) |
|
} |
|
} |
|
}, |
|
// #endif |
|
// #ifdef H5 |
|
mousewheel(e){ |
|
if(this.chart) { |
|
dispatch.call(this.chart.getZr().handler, 'mousewheel', this.getTouch(e)) |
|
} |
|
} |
|
// #endif |
|
} |
|
}; |
|
</script> |
|
<style> |
|
.lime-echart { |
|
position: relative; |
|
/* #ifndef APP-NVUE */ |
|
width: 100%; |
|
height: 100%; |
|
/* #endif */ |
|
/* #ifdef APP-NVUE */ |
|
flex: 1; |
|
/* #endif */ |
|
} |
|
.lime-echart__canvas { |
|
/* #ifndef APP-NVUE */ |
|
width: 100%; |
|
height: 100%; |
|
/* #endif */ |
|
/* #ifdef APP-NVUE */ |
|
flex: 1; |
|
/* #endif */ |
|
} |
|
/* #ifndef APP-NVUE */ |
|
.lime-echart__mask { |
|
position: absolute; |
|
width: 100%; |
|
height: 100%; |
|
left: 0; |
|
top: 0; |
|
z-index: 1; |
|
} |
|
/* #endif */ |
|
</style>
|
|
|