l-echart.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  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 < 2) {
  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. if(typeof callback === 'function') {
  352. setCanvasCreator(echarts, config)
  353. this.chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts))
  354. callback(this.chart)
  355. } else {
  356. console.error('callback 非 function')
  357. }
  358. // #endif
  359. // #ifdef APP-NVUE
  360. if(callback) {
  361. this.chart = {
  362. setOption: (options) => {
  363. this.roptions = options
  364. }
  365. }
  366. callback(this.chart)
  367. this.$refs.webview.evalJs(`init(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.roptions)}, ${JSON.stringify(opts)}, ${theme})`)
  368. } else {
  369. console.error('callback 非 function')
  370. }
  371. // #endif
  372. },
  373. getContext() {
  374. // #ifdef APP-NVUE
  375. if(this.finished) {
  376. return Promise.resolve(this.finished)
  377. }
  378. return new Promise(resolve => {
  379. this.$watch('finished', (val) => {
  380. if(val) {
  381. resolve(this.finished)
  382. }
  383. })
  384. })
  385. // #endif
  386. // #ifndef APP-NVUE
  387. const { use2dCanvas} = this;
  388. let dpr = devicePixelRatio
  389. if (use2dCanvas) {
  390. return new Promise(resolve => {
  391. uni.createSelectorQuery()
  392. .in(this)
  393. .select(`#${this.canvasId}`)
  394. .fields({
  395. node: true,
  396. size: true
  397. })
  398. .exec(res => {
  399. let { node, width, height } = res[0];
  400. this.width = width = width || 300;
  401. this.height = height = height || 300;
  402. const ctx = node.getContext('2d');
  403. const canvas = new Canvas(ctx, this, true, node);
  404. this.canvasNode = node
  405. resolve({ canvas, width, height, devicePixelRatio: dpr, node });
  406. });
  407. });
  408. }
  409. return new Promise(resolve => {
  410. uni.createSelectorQuery()
  411. .in(this)
  412. .select(`#${this.canvasId}`)
  413. .boundingClientRect()
  414. .exec(res => {
  415. if (res) {
  416. let { width, height } = res[0];
  417. this.width = width = width || 300;
  418. this.height = height = height || 300;
  419. // #ifdef MP-TOUTIAO
  420. dpr = !this.isPC ? devicePixelRatio : 1// 1.25
  421. // #endif
  422. // #ifndef MP-ALIPAY || MP-TOUTIAO
  423. dpr = this.isPC ? devicePixelRatio : 1
  424. // #endif
  425. // #ifdef MP-ALIPAY || MP-LARK
  426. dpr = devicePixelRatio
  427. // #endif
  428. this.rect = res[0]
  429. this.nodeWidth = width * dpr;
  430. this.nodeHeight = height * dpr;
  431. const ctx = uni.createCanvasContext(this.canvasId, this);
  432. const canvas = new Canvas(ctx, this, false);
  433. resolve({ canvas, width, height, devicePixelRatio: dpr });
  434. }
  435. });
  436. });
  437. // #endif
  438. },
  439. // #ifndef APP-NVUE
  440. getRelative(e) {
  441. return {x: e.pageX - this.rect.left, y: e.pageY - this.rect.top, wheelDelta: e.wheelDelta}
  442. },
  443. getTouch(e) {
  444. return e.touches && e.touches[0] && e.touches[0].x ? e.touches[0] : this.getRelative(e);
  445. },
  446. touchStart(e) {
  447. this.isDown = true
  448. if (this.chart && ((e.touches.length > 0 || e.touches['0']) && e.type != 'mousemove' || e.type == 'mousedown')) {
  449. const touch = this.getTouch(e)
  450. this.startX = touch.x
  451. this.startY = touch.y
  452. this.startT = new Date()
  453. const handler = this.chart.getZr().handler;
  454. dispatch.call(handler, 'mousedown', touch)
  455. dispatch.call(handler, 'mousemove', touch)
  456. handler.processGesture(wrapTouch(e), 'start');
  457. clearTimeout(this.endTimer);
  458. }
  459. },
  460. touchMove(e) {
  461. if(this.isPc && this.enableHover && !this.isDown) {this.isDown = true}
  462. if (this.chart && ((e.touches.length > 0 || e.touches['0']) && e.type != 'mousemove' || e.type == 'mousemove' && this.isDown)) {
  463. const handler = this.chart.getZr().handler;
  464. dispatch.call(handler, 'mousemove', this.getTouch(e))
  465. handler.processGesture(wrapTouch(e), 'change');
  466. }
  467. },
  468. touchEnd(e) {
  469. this.isDown = false
  470. if (this.chart) {
  471. const {x} = e.changedTouches && e.changedTouches[0] || {}
  472. const touch = (x ? e.changedTouches[0] : this.getRelative(e)) || {};
  473. const handler = this.chart.getZr().handler;
  474. const isClick = Math.abs(touch.x - this.startX) < 10 && new Date() - this.startT < 200;
  475. dispatch.call(handler, 'mouseup', touch)
  476. handler.processGesture(wrapTouch(e), 'end');
  477. if(isClick) {
  478. dispatch.call(handler, 'click', touch)
  479. } else {
  480. this.endTimer = setTimeout(() => {
  481. dispatch.call(handler, 'mousemove', {x: 999999999,y: 999999999});
  482. dispatch.call(handler, 'mouseup', {x: 999999999,y: 999999999});
  483. },50)
  484. }
  485. }
  486. }
  487. // #endif
  488. }
  489. };
  490. </script>
  491. <style scoped>
  492. .lime-echart {
  493. position: relative;
  494. /* #ifndef APP-NVUE */
  495. width: 100%;
  496. height: 100%;
  497. /* #endif */
  498. /* #ifdef APP-NVUE */
  499. flex: 1;
  500. /* #endif */
  501. }
  502. .lime-echart__canvas {
  503. /* #ifndef APP-NVUE */
  504. width: 100%;
  505. height: 100%;
  506. /* #endif */
  507. /* #ifdef APP-NVUE */
  508. flex: 1;
  509. /* #endif */
  510. }
  511. </style>