l-echart.vue 12 KB

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