l-echart.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  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',this.mouseMoveFunc)
  140. }
  141. // #endif
  142. },
  143. created() {
  144. // #ifdef H5
  145. if(!('ontouchstart' in window)) {
  146. this.isPc = true
  147. document.addEventListener('mousewheel', this.mouseMoveFunc)
  148. }
  149. // #endif
  150. // #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
  151. const { SDKVersion, version, platform, environment } = uni.getSystemInfoSync();
  152. // #endif
  153. // #ifdef MP-WEIXIN
  154. this.isPC = /windows/i.test(platform)
  155. 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;
  156. // #endif
  157. // #ifdef MP-TOUTIAO
  158. this.isPC = /devtools/i.test(platform)
  159. this.use2dCanvas = this.type === '2d' && compareVersion(SDKVersion, '1.78.0') >= 0;
  160. // #endif
  161. // #ifdef MP-ALIPAY
  162. this.use2dCanvas = this.type === '2d' && compareVersion(my.SDKVersion, '2.7.0') >= 0;
  163. // #endif
  164. },
  165. mounted() {
  166. this.$nextTick(() => {
  167. this.$emit('finished')
  168. })
  169. },
  170. methods: {
  171. mouseMoveFunc(e){
  172. if(this.chart) {
  173. const touch = this.getTouch(e)
  174. const handler = this.chart.getZr().handler;
  175. dispatch.call(handler, 'mousewheel', touch)
  176. }
  177. },
  178. // #ifdef APP-NVUE
  179. onMessage(e) {
  180. const res = e?.detail?.data[0] || null;
  181. if (res?.event) {
  182. if(res.event === 'inited') {
  183. this.inited = true
  184. }
  185. this.$emit(res.event, JSON.parse(res.data));
  186. } else if(res?.file){
  187. this.file = res.data
  188. } else if(!res[0] && JSON.stringify(res[0]) != '{}'){
  189. console.error(res);
  190. } else {
  191. console.log(...res)
  192. }
  193. },
  194. // #endif
  195. setChart(callback) {
  196. if(!this.chart) {
  197. console.warn(`组件还未初始化,请先使用 init`)
  198. return
  199. }
  200. if(typeof callback === 'function' && this.chart) {
  201. callback(this.chart);
  202. }
  203. // #ifdef APP-NVUE
  204. if(typeof callback === 'function') {
  205. this.$refs.webview.evalJs(`setChart(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.roptions)})`);
  206. }
  207. // #endif
  208. },
  209. setOption() {
  210. if (!this.chart || !this.chart.setOption) {
  211. console.warn(`组件还未初始化,请先使用 init`)
  212. return
  213. }
  214. // #ifndef APP-NVUE
  215. this.chart.setOption(...arguments);
  216. // #endif
  217. // #ifdef APP-NVUE
  218. this.$refs.webview.evalJs(`setOption(${JSON.stringify(arguments)})`);
  219. // #endif
  220. },
  221. showLoading() {
  222. if(this.chart) {
  223. // #ifndef APP-NVUE
  224. this.chart.showLoading(...arguments)
  225. // #endif
  226. // #ifdef APP-NVUE
  227. this.$refs.webview.evalJs(`showLoading(${JSON.stringify(arguments)})`);
  228. // #endif
  229. }
  230. },
  231. hideLoading() {
  232. if(this.chart) {
  233. // #ifndef APP-NVUE
  234. this.chart.hideLoading()
  235. // #endif
  236. // #ifdef APP-NVUE
  237. this.$refs.webview.evalJs(`hideLoading()`);
  238. // #endif
  239. }
  240. },
  241. clear() {
  242. if(this.chart) {
  243. // #ifndef APP-NVUE
  244. this.chart.clear()
  245. // #endif
  246. // #ifdef APP-NVUE
  247. this.$refs.webview.evalJs(`clear()`);
  248. // #endif
  249. }
  250. },
  251. dispose() {
  252. if(this.chart) {
  253. // #ifndef APP-NVUE
  254. this.chart.dispose()
  255. // #endif
  256. // #ifdef APP-NVUE
  257. this.$refs.webview.evalJs(`dispose()`);
  258. // #endif
  259. }
  260. },
  261. resize(size) {
  262. if(size && size.width && size.height) {
  263. this.height = size.height
  264. this.width = size.width
  265. if(this.chart) {this.chart.resize(size)}
  266. // #ifdef APP-NVUE
  267. this.$refs.webview.evalJs(`resize(${size})`);
  268. // #endif
  269. } else {
  270. this.$nextTick(() => {
  271. // #ifndef APP-NVUE
  272. uni.createSelectorQuery()
  273. .in(this)
  274. .select(`.lime-echart`)
  275. .boundingClientRect()
  276. .exec(res => {
  277. if (res) {
  278. let { width, height } = res[0];
  279. this.width = width = width || 300;
  280. this.height = height = height || 300;
  281. this.chart.resize({width, height})
  282. }
  283. });
  284. // #endif
  285. // #ifdef APP-NVUE
  286. this.$refs.webview.evalJs(`resize()`);
  287. // #endif
  288. })
  289. }
  290. },
  291. canvasToTempFilePath(args = {}) {
  292. // #ifndef APP-NVUE
  293. const { use2dCanvas, canvasId, canvasNode } = this;
  294. return new Promise((resolve, reject) => {
  295. const copyArgs = Object.assign({
  296. canvasId,
  297. success: resolve,
  298. fail: reject
  299. }, args);
  300. if (use2dCanvas) {
  301. delete copyArgs.canvasId;
  302. copyArgs.canvas = canvasNode;
  303. }
  304. uni.canvasToTempFilePath(copyArgs, this);
  305. });
  306. // #endif
  307. // #ifdef APP-NVUE
  308. this.file = ''
  309. this.$refs.webview.evalJs(`canvasToTempFilePath()`);
  310. return new Promise((resolve, reject) => {
  311. this.$watch('file', async (file) => {
  312. if(file) {
  313. const tempFilePath = await base64ToPath(file)
  314. resolve(args.success({tempFilePath}))
  315. } else {
  316. reject(args.fail({error: ``}))
  317. }
  318. })
  319. })
  320. // #endif
  321. },
  322. async init(echarts, ...args) {
  323. // #ifdef APP-NVUE
  324. if(arguments && !arguments.length) {
  325. console.error('缺少参数:init(theme?:string, opts?: object, callback: function)')
  326. return
  327. }
  328. // #endif
  329. // #ifndef APP-NVUE
  330. if(arguments && arguments.length < 1) {
  331. console.error('缺少参数:init(echarts, theme?:string, opts?: object, callback: function)')
  332. return
  333. }
  334. // #endif
  335. let theme=null,opts={},callback;
  336. Array.from(arguments).forEach(item => {
  337. if(typeof item === 'function') {
  338. callback = item
  339. }
  340. if(['string'].includes(typeof item)) {
  341. theme = item
  342. }
  343. if(typeof item === 'object') {
  344. opts = item
  345. }
  346. })
  347. if(this.beforeDelay) {
  348. await sleep(this.beforeDelay)
  349. }
  350. let config = await this.getContext();
  351. // #ifndef APP-NVUE
  352. setCanvasCreator(echarts, config)
  353. this.chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts))
  354. if(typeof callback === 'function') {
  355. callback(this.chart)
  356. } else {
  357. return this.chart
  358. // console.info('callback 非 function')
  359. }
  360. // #endif
  361. // #ifdef APP-NVUE
  362. if(callback) {
  363. this.chart = {
  364. setOption: (options) => {
  365. this.roptions = options
  366. }
  367. }
  368. callback(this.chart)
  369. this.$refs.webview.evalJs(`init(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.roptions)}, ${JSON.stringify(opts)}, ${theme})`)
  370. } else {
  371. console.error('callback 非 function')
  372. }
  373. // #endif
  374. },
  375. getContext() {
  376. // #ifdef APP-NVUE
  377. if(this.finished) {
  378. return Promise.resolve(this.finished)
  379. }
  380. return new Promise(resolve => {
  381. this.$watch('finished', (val) => {
  382. if(val) {
  383. resolve(this.finished)
  384. }
  385. })
  386. })
  387. // #endif
  388. // #ifndef APP-NVUE
  389. const { use2dCanvas } = this;
  390. let dpr = devicePixelRatio
  391. if (use2dCanvas) {
  392. return new Promise(resolve => {
  393. uni.createSelectorQuery()
  394. .in(this)
  395. .select(`#${this.canvasId}`)
  396. .fields({
  397. node: true,
  398. size: true
  399. })
  400. .exec(res => {
  401. let { node, width, height } = res[0];
  402. this.width = width = width || 300;
  403. this.height = height = height || 300;
  404. const ctx = node.getContext('2d');
  405. const canvas = new Canvas(ctx, this, true, node);
  406. this.canvasNode = node
  407. resolve({ canvas, width, height, devicePixelRatio: dpr, node });
  408. });
  409. });
  410. }
  411. return new Promise(resolve => {
  412. uni.createSelectorQuery()
  413. .in(this)
  414. .select(`#${this.canvasId}`)
  415. .boundingClientRect()
  416. .exec(res => {
  417. if (res) {
  418. let { width, height } = res[0];
  419. this.width = width = width || 300;
  420. this.height = height = height || 300;
  421. // #ifdef MP-TOUTIAO
  422. dpr = !this.isPC ? devicePixelRatio : 1// 1.25
  423. // #endif
  424. // #ifndef MP-ALIPAY || MP-TOUTIAO
  425. dpr = this.isPC ? devicePixelRatio : 1
  426. // #endif
  427. // #ifdef MP-ALIPAY || MP-LARK
  428. dpr = devicePixelRatio
  429. // #endif
  430. this.rect = res[0]
  431. this.nodeWidth = width * dpr;
  432. this.nodeHeight = height * dpr;
  433. const ctx = uni.createCanvasContext(this.canvasId, this);
  434. const canvas = new Canvas(ctx, this, false);
  435. resolve({ canvas, width, height, devicePixelRatio: dpr });
  436. }
  437. });
  438. });
  439. // #endif
  440. },
  441. // #ifndef APP-NVUE
  442. getRelative(e) {
  443. return {x: e.pageX - this.rect.left, y: e.pageY - this.rect.top, wheelDelta: e.wheelDelta}
  444. },
  445. getTouch(e) {
  446. return e.touches && e.touches[0] && e.touches[0].x ? e.touches[0] : this.getRelative(e);
  447. },
  448. touchStart(e) {
  449. this.isDown = true
  450. if (this.chart && ((e.touches.length > 0 || e.touches['0']) && e.type != 'mousemove' || e.type == 'mousedown')) {
  451. const touch = this.getTouch(e)
  452. this.startX = touch.x
  453. this.startY = touch.y
  454. this.startT = new Date()
  455. const handler = this.chart.getZr().handler;
  456. dispatch.call(handler, 'mousedown', touch)
  457. dispatch.call(handler, 'mousemove', touch)
  458. handler.processGesture(wrapTouch(e), 'start');
  459. clearTimeout(this.endTimer);
  460. }
  461. },
  462. touchMove(e) {
  463. if(this.isPc && this.enableHover && !this.isDown) {this.isDown = true}
  464. if (this.chart && ((e.touches.length > 0 || e.touches['0']) && e.type != 'mousemove' || e.type == 'mousemove' && this.isDown)) {
  465. const handler = this.chart.getZr().handler;
  466. dispatch.call(handler, 'mousemove', this.getTouch(e))
  467. handler.processGesture(wrapTouch(e), 'change');
  468. }
  469. },
  470. touchEnd(e) {
  471. this.isDown = false
  472. if (this.chart) {
  473. const {x} = e.changedTouches && e.changedTouches[0] || {}
  474. const touch = (x ? e.changedTouches[0] : this.getRelative(e)) || {};
  475. const handler = this.chart.getZr().handler;
  476. const isClick = Math.abs(touch.x - this.startX) < 10 && new Date() - this.startT < 200;
  477. dispatch.call(handler, 'mouseup', touch)
  478. handler.processGesture(wrapTouch(e), 'end');
  479. if(isClick) {
  480. dispatch.call(handler, 'click', touch)
  481. } else {
  482. this.endTimer = setTimeout(() => {
  483. dispatch.call(handler, 'mousemove', {x: 999999999,y: 999999999});
  484. dispatch.call(handler, 'mouseup', {x: 999999999,y: 999999999});
  485. },50)
  486. }
  487. }
  488. }
  489. // #endif
  490. }
  491. };
  492. </script>
  493. <style scoped>
  494. .lime-echart {
  495. position: relative;
  496. /* #ifndef APP-NVUE */
  497. width: 100%;
  498. height: 100%;
  499. /* #endif */
  500. /* #ifdef APP-NVUE */
  501. flex: 1;
  502. /* #endif */
  503. }
  504. .lime-echart__canvas {
  505. /* #ifndef APP-NVUE */
  506. width: 100%;
  507. height: 100%;
  508. /* #endif */
  509. /* #ifdef APP-NVUE */
  510. flex: 1;
  511. /* #endif */
  512. }
  513. </style>