l-echart.vue 14 KB

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