l-echart_old.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. <template>
  2. <view class="lime-echart" :style="[customStyle]" v-if="canvasId" ref="limeEchart" :aria-label="ariaLabel">
  3. <!-- #ifndef APP-NVUE -->
  4. <canvas
  5. class="lime-echart__canvas"
  6. v-if="use2dCanvas"
  7. type="2d"
  8. :id="canvasId"
  9. :style="canvasStyle"
  10. :disable-scroll="isDisableScroll"
  11. @touchstart="touchStart"
  12. @touchmove="touchMove"
  13. @touchend="touchEnd"
  14. />
  15. <canvas
  16. class="lime-echart__canvas"
  17. v-else
  18. :width="nodeWidth"
  19. :height="nodeHeight"
  20. :style="canvasStyle"
  21. :canvas-id="canvasId"
  22. :id="canvasId"
  23. :disable-scroll="isDisableScroll"
  24. @touchstart="touchStart"
  25. @touchmove="touchMove"
  26. @touchend="touchEnd"
  27. />
  28. <view class="lime-echart__mask"
  29. v-if="isPC"
  30. @mousedown="touchStart"
  31. @mousemove="touchMove"
  32. @mouseup="touchEnd"
  33. @touchstart="touchStart"
  34. @touchmove="touchMove"
  35. @touchend="touchEnd">
  36. </view>
  37. <canvas v-if="isOffscreenCanvas" :style="offscreenStyle" :canvas-id="offscreenCanvasId"></canvas>
  38. <!-- #endif -->
  39. <!-- #ifdef APP-NVUE -->
  40. <web-view
  41. class="lime-echart__canvas"
  42. :id="canvasId"
  43. :style="canvasStyle"
  44. :webview-styles="webviewStyles"
  45. ref="webview"
  46. src="/uni_modules/lime-echart/static/uvue.html?v=1"
  47. @pagefinish="finished = true"
  48. @onPostMessage="onMessage"
  49. ></web-view>
  50. <!-- #endif -->
  51. </view>
  52. </template>
  53. <script>
  54. // @ts-nocheck
  55. // #ifndef APP-NVUE
  56. import {Canvas, setCanvasCreator, dispatch} from './canvas';
  57. import {wrapTouch, convertTouchesToArray, devicePixelRatio ,sleep, canIUseCanvas2d, getRect, getDeviceInfo} from './utils';
  58. // #endif
  59. // #ifdef APP-NVUE
  60. import { base64ToPath, sleep } from './utils';
  61. import {Echarts} from './nvue'
  62. // #endif
  63. /**
  64. * LimeChart 图表
  65. * @description 全端兼容的eCharts
  66. * @tutorial https://ext.dcloud.net.cn/plugin?id=4899
  67. * @property {String} customStyle 自定义样式
  68. * @property {String} type 指定 canvas 类型
  69. * @value 2d 使用canvas 2d,部分小程序支持
  70. * @value '' 使用原生canvas,会有层级问题
  71. * @value bottom right 不缩放图片,只显示图片的右下边区域
  72. * @property {Boolean} isDisableScroll
  73. * @property {number} beforeDelay = [30] 延迟初始化 (毫秒)
  74. * @property {Boolean} enableHover PC端使用鼠标悬浮
  75. * @event {Function} finished 加载完成触发
  76. */
  77. export default {
  78. name: 'lime-echart',
  79. props: {
  80. // #ifdef MP-WEIXIN || MP-TOUTIAO
  81. type: {
  82. type: String,
  83. default: '2d'
  84. },
  85. // #endif
  86. // #ifdef APP-NVUE
  87. webviewStyles: Object,
  88. // hybrid: Boolean,
  89. // #endif
  90. customStyle: String,
  91. isDisableScroll: Boolean,
  92. isClickable: {
  93. type: Boolean,
  94. default: true
  95. },
  96. enableHover: Boolean,
  97. beforeDelay: {
  98. type: Number,
  99. default: 30
  100. },
  101. landscape: Boolean
  102. },
  103. data() {
  104. return {
  105. // #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
  106. use2dCanvas: true,
  107. // #endif
  108. // #ifndef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
  109. use2dCanvas: false,
  110. // #endif
  111. ariaLabel: '图表',
  112. width: null,
  113. height: null,
  114. nodeWidth: null,
  115. nodeHeight: null,
  116. // canvasNode: null,
  117. config: {},
  118. inited: false,
  119. finished: false,
  120. file: '',
  121. platform: '',
  122. isPC: false,
  123. isDown: false,
  124. isOffscreenCanvas: false,
  125. offscreenWidth: 0,
  126. offscreenHeight: 0,
  127. };
  128. },
  129. computed: {
  130. rootStyle() {
  131. if(this.landscape) {
  132. return `transform: translate(-50%,-50%) rotate(90deg); top:50%; left:50%;`
  133. }
  134. },
  135. canvasId() {
  136. return `lime-echart${this._ && this._.uid || this._uid}`
  137. },
  138. offscreenCanvasId() {
  139. return `${this.canvasId}_offscreen`
  140. },
  141. offscreenStyle() {
  142. return `width:${this.offscreenWidth}px;height: ${this.offscreenHeight}px; position: fixed; left: 99999px; background: red`
  143. },
  144. canvasStyle() {
  145. return this.rootStyle + (this.width && this.height ? ('width:' + this.width + 'px;height:' + this.height + 'px') : '')
  146. }
  147. },
  148. // #ifndef VUE3
  149. beforeDestroy() {
  150. this.clear()
  151. this.dispose()
  152. // #ifdef H5
  153. if(this.isPC) {
  154. document.removeEventListener('mousewheel', this.mousewheel)
  155. }
  156. // #endif
  157. },
  158. // #endif
  159. // #ifdef VUE3
  160. beforeUnmount() {
  161. this.clear()
  162. this.dispose()
  163. // #ifdef H5
  164. if(this.isPC) {
  165. document.removeEventListener('mousewheel', this.mousewheel)
  166. }
  167. // #endif
  168. },
  169. // #endif
  170. created() {
  171. // #ifdef H5
  172. if(!('ontouchstart' in window)) {
  173. this.isPC = true
  174. document.addEventListener('mousewheel', this.mousewheel)
  175. }
  176. // #endif
  177. // #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
  178. const { platform } = getDeviceInfo();
  179. this.isPC = /windows/i.test(platform)
  180. // #endif
  181. this.use2dCanvas = this.type === '2d' && canIUseCanvas2d()
  182. },
  183. mounted() {
  184. this.$nextTick(() => {
  185. this.$emit('finished')
  186. })
  187. },
  188. methods: {
  189. // #ifdef APP-NVUE
  190. onMessage(e) {
  191. const detail = e?.detail?.data[0] || null;
  192. const data = detail?.data
  193. const key = detail?.event
  194. const options = data?.options
  195. const event = data?.event
  196. const file = detail?.file
  197. if (key == 'log' && data) {
  198. console.log(data)
  199. }
  200. if(event) {
  201. this.chart.dispatchAction(event.replace(/"/g,''), options)
  202. }
  203. if(file) {
  204. thie.file = file
  205. }
  206. },
  207. // #endif
  208. setChart(callback) {
  209. if(!this.chart) {
  210. console.warn(`组件还未初始化,请先使用 init`)
  211. return
  212. }
  213. if(typeof callback === 'function' && this.chart) {
  214. callback(this.chart);
  215. }
  216. // #ifdef APP-NVUE
  217. if(typeof callback === 'function') {
  218. this.$refs.webview.evalJs(`setChart(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.chart.options)})`);
  219. }
  220. // #endif
  221. },
  222. setOption() {
  223. if (!this.chart || !this.chart.setOption) {
  224. console.warn(`组件还未初始化,请先使用 init`)
  225. return
  226. }
  227. this.chart.setOption(...arguments);
  228. },
  229. showLoading() {
  230. if(this.chart) {
  231. this.chart.showLoading(...arguments)
  232. }
  233. },
  234. hideLoading() {
  235. if(this.chart) {
  236. this.chart.hideLoading()
  237. }
  238. },
  239. clear() {
  240. if(this.chart && !this.chart.isDisposed()) {
  241. this.chart.clear()
  242. }
  243. },
  244. dispose() {
  245. if(this.chart && !this.chart.isDisposed()) {
  246. this.chart.dispose()
  247. }
  248. },
  249. resize(size) {
  250. if(size && size.width && size.height) {
  251. this.height = size.height
  252. this.width = size.width
  253. if(this.chart) {this.chart.resize(size)}
  254. } else {
  255. this.$nextTick(() => {
  256. getRect('.lime-echart', this).then(res =>{
  257. if (res) {
  258. let { width, height } = res;
  259. this.width = width = width || 300;
  260. this.height = height = height || 300;
  261. this.chart.resize({width, height})
  262. }
  263. })
  264. })
  265. }
  266. },
  267. canvasToTempFilePath(args = {}) {
  268. // #ifndef APP-NVUE
  269. const { use2dCanvas, canvasId } = this;
  270. return new Promise((resolve, reject) => {
  271. const copyArgs = Object.assign({
  272. canvasId,
  273. success: resolve,
  274. fail: reject
  275. }, args);
  276. if (use2dCanvas) {
  277. delete copyArgs.canvasId;
  278. copyArgs.canvas = this.canvasNode;
  279. }
  280. uni.canvasToTempFilePath(copyArgs, this);
  281. });
  282. // #endif
  283. // #ifdef APP-NVUE
  284. this.file = ''
  285. this.$refs.webview.evalJs(`canvasToTempFilePath()`);
  286. return new Promise((resolve, reject) => {
  287. this.$watch('file', async (file) => {
  288. if(file) {
  289. const tempFilePath = await base64ToPath(file)
  290. resolve(args.success({tempFilePath}))
  291. } else {
  292. reject(args.fail({error: ``}))
  293. }
  294. })
  295. })
  296. // #endif
  297. },
  298. async init(echarts, ...args) {
  299. // #ifndef APP-NVUE
  300. if(args && args.length == 0 && !echarts) {
  301. console.error('缺少参数:init(echarts, theme?:string, opts?: object, callback?: function)')
  302. return
  303. }
  304. // #endif
  305. let theme=null,opts={},callback;
  306. // Array.from(arguments)
  307. args.forEach(item => {
  308. if(typeof item === 'function') {
  309. callback = item
  310. }
  311. if(['string'].includes(typeof item)) {
  312. theme = item
  313. }
  314. if(typeof item === 'object') {
  315. opts = item
  316. }
  317. })
  318. if(this.beforeDelay) {
  319. await sleep(this.beforeDelay)
  320. }
  321. let config = await this.getContext();
  322. // #ifndef APP-NVUE
  323. setCanvasCreator(echarts, config)
  324. try {
  325. this.chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts || {}))
  326. callback?.(this.chart)
  327. return this.chart
  328. } catch(e) {
  329. console.error("【lime-echarts】:", e)
  330. return null
  331. }
  332. // #endif
  333. // #ifdef APP-NVUE
  334. this.chart = new Echarts(this.$refs.webview)
  335. this.$refs.webview.evalJs(`init(null, null, ${JSON.stringify(opts)}, ${theme})`)
  336. callback?.(this.chart)
  337. return this.chart
  338. // #endif
  339. },
  340. getContext() {
  341. // #ifdef APP-NVUE
  342. if(this.finished) {
  343. return Promise.resolve(this.finished)
  344. }
  345. return new Promise(resolve => {
  346. this.$watch('finished', (val) => {
  347. if(val) {
  348. resolve(this.finished)
  349. }
  350. })
  351. })
  352. // #endif
  353. // #ifndef APP-NVUE
  354. return getRect(`#${this.canvasId}`, this, this.use2dCanvas).then(res => {
  355. if(res) {
  356. let dpr = devicePixelRatio
  357. let {width, height, node} = res
  358. let canvas;
  359. this.width = width = width || 300;
  360. this.height = height = height || 300;
  361. if(node) {
  362. const ctx = node.getContext('2d');
  363. canvas = new Canvas(ctx, this, true, node);
  364. this.canvasNode = node
  365. } else {
  366. // #ifdef MP-TOUTIAO
  367. dpr = !this.isPC ? devicePixelRatio : 1// 1.25
  368. // #endif
  369. // #ifndef MP-ALIPAY || MP-TOUTIAO
  370. dpr = this.isPC ? devicePixelRatio : 1
  371. // #endif
  372. // #ifdef MP-ALIPAY || MP-LARK
  373. dpr = devicePixelRatio
  374. // #endif
  375. // #ifdef WEB
  376. dpr = 1
  377. // #endif
  378. this.rect = res
  379. this.nodeWidth = width * dpr;
  380. this.nodeHeight = height * dpr;
  381. const ctx = uni.createCanvasContext(this.canvasId, this);
  382. canvas = new Canvas(ctx, this, false);
  383. }
  384. return { canvas, width, height, devicePixelRatio: dpr, node };
  385. } else {
  386. return {}
  387. }
  388. })
  389. // #endif
  390. },
  391. // #ifndef APP-NVUE
  392. getRelative(e, touches) {
  393. let { clientX, clientY } = e
  394. if(!(clientX && clientY) && touches && touches[0]) {
  395. clientX = touches[0].clientX
  396. clientY = touches[0].clientY
  397. }
  398. return {x: clientX - this.rect.left, y: clientY - this.rect.top, wheelDelta: e.wheelDelta || 0}
  399. },
  400. getTouch(e, touches) {
  401. const {x} = touches && touches[0] || {}
  402. const touch = x ? touches[0] : this.getRelative(e, touches);
  403. if(this.landscape) {
  404. [touch.x, touch.y] = [touch.y, this.height - touch.x]
  405. }
  406. return touch;
  407. },
  408. touchStart(e) {
  409. this.isDown = true
  410. const next = () => {
  411. const touches = convertTouchesToArray(e.touches)
  412. if(this.chart) {
  413. const touch = this.getTouch(e, touches)
  414. this.startX = touch.x
  415. this.startY = touch.y
  416. this.startT = new Date()
  417. const handler = this.chart.getZr().handler;
  418. dispatch.call(handler, 'mousedown', touch)
  419. dispatch.call(handler, 'mousemove', touch)
  420. handler.processGesture(wrapTouch(e), 'start');
  421. clearTimeout(this.endTimer);
  422. }
  423. }
  424. if(this.isPC) {
  425. getRect(`#${this.canvasId}`, {context: this}).then(res => {
  426. this.rect = res
  427. next()
  428. })
  429. return
  430. }
  431. next()
  432. },
  433. touchMove(e) {
  434. if(this.isPC && this.enableHover && !this.isDown) {this.isDown = true}
  435. const touches = convertTouchesToArray(e.touches)
  436. if (this.chart && this.isDown) {
  437. const handler = this.chart.getZr().handler;
  438. dispatch.call(handler, 'mousemove', this.getTouch(e, touches))
  439. handler.processGesture(wrapTouch(e), 'change');
  440. }
  441. },
  442. touchEnd(e) {
  443. this.isDown = false
  444. if (this.chart) {
  445. const touches = convertTouchesToArray(e.changedTouches)
  446. const {x} = touches && touches[0] || {}
  447. const touch = (x ? touches[0] : this.getRelative(e, touches)) || {};
  448. if(this.landscape) {
  449. [touch.x, touch.y] = [touch.y, this.height - touch.x]
  450. }
  451. const handler = this.chart.getZr().handler;
  452. const isClick = Math.abs(touch.x - this.startX) < 10 && new Date() - this.startT < 200;
  453. dispatch.call(handler, 'mouseup', touch)
  454. handler.processGesture(wrapTouch(e), 'end');
  455. if(isClick) {
  456. dispatch.call(handler, 'click', touch)
  457. } else {
  458. this.endTimer = setTimeout(() => {
  459. dispatch.call(handler, 'mousemove', {x: 999999999,y: 999999999});
  460. dispatch.call(handler, 'mouseup', {x: 999999999,y: 999999999});
  461. },50)
  462. }
  463. }
  464. },
  465. // #endif
  466. // #ifdef H5
  467. mousewheel(e){
  468. if(this.chart) {
  469. dispatch.call(this.chart.getZr().handler, 'mousewheel', this.getTouch(e))
  470. }
  471. }
  472. // #endif
  473. }
  474. };
  475. </script>
  476. <style>
  477. .lime-echart {
  478. position: relative;
  479. /* #ifndef APP-NVUE */
  480. width: 100%;
  481. height: 100%;
  482. /* #endif */
  483. /* #ifdef APP-NVUE */
  484. flex: 1;
  485. /* #endif */
  486. }
  487. .lime-echart__canvas {
  488. /* #ifndef APP-NVUE */
  489. width: 100%;
  490. height: 100%;
  491. /* #endif */
  492. /* #ifdef APP-NVUE */
  493. flex: 1;
  494. /* #endif */
  495. }
  496. /* #ifndef APP-NVUE */
  497. .lime-echart__mask {
  498. position: absolute;
  499. width: 100%;
  500. height: 100%;
  501. left: 0;
  502. top: 0;
  503. z-index: 1;
  504. }
  505. /* #endif */
  506. </style>