l-echart.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  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-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 {wrapTouch, devicePixelRatio ,sleep, canIUseCanvas2d, getRect} 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. ariaLabel: '图表',
  105. width: null,
  106. height: null,
  107. nodeWidth: null,
  108. nodeHeight: null,
  109. // canvasNode: null,
  110. config: {},
  111. inited: false,
  112. finished: false,
  113. file: '',
  114. platform: '',
  115. isPc: false,
  116. isDown: false,
  117. isOffscreenCanvas: false,
  118. offscreenWidth: 0,
  119. offscreenHeight: 0
  120. };
  121. },
  122. computed: {
  123. canvasId() {
  124. return `lime-echart${this._ && this._.uid || this._uid}`
  125. },
  126. offscreenCanvasId() {
  127. return `${this.canvasId}_offscreen`
  128. },
  129. offscreenStyle() {
  130. return `width:${this.offscreenWidth}px;height: ${this.offscreenHeight}px; position: fixed; left: 99999px; background: red`
  131. },
  132. canvasStyle() {
  133. return this.width && this.height ? ('width:' + this.width + 'px;height:' + this.height + 'px') : ''
  134. }
  135. },
  136. // #ifndef VUE3
  137. beforeDestroy() {
  138. this.clear()
  139. this.dispose()
  140. // #ifdef H5
  141. if(this.isPc) {
  142. document.removeEventListener('mousewheel', this.mousewheel)
  143. }
  144. // #endif
  145. },
  146. // #endif
  147. // #ifdef VUE3
  148. unmounted() {
  149. this.clear()
  150. this.dispose()
  151. // #ifdef H5
  152. if(this.isPc) {
  153. document.removeEventListener('mousewheel', this.mousewheel)
  154. }
  155. // #endif
  156. },
  157. // #endif
  158. created() {
  159. // #ifdef H5
  160. if(!('ontouchstart' in window)) {
  161. this.isPc = true
  162. document.addEventListener('mousewheel', this.mousewheel)
  163. }
  164. // #endif
  165. // #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
  166. const { platform } = uni.getSystemInfoSync();
  167. this.isPC = /windows/i.test(platform)
  168. // #endif
  169. this.use2dCanvas = this.type === '2d' && canIUseCanvas2d()
  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.chart.options)})`);
  205. }
  206. // #endif
  207. },
  208. setOption() {
  209. if (!this.chart || !this.chart.setOption) {
  210. console.warn(`组件还未初始化,请先使用 init`)
  211. return
  212. }
  213. this.chart.setOption(...arguments);
  214. },
  215. showLoading() {
  216. if(this.chart) {
  217. this.chart.showLoading(...arguments)
  218. }
  219. },
  220. hideLoading() {
  221. if(this.chart) {
  222. this.chart.hideLoading()
  223. }
  224. },
  225. clear() {
  226. if(this.chart) {
  227. this.chart.clear()
  228. }
  229. },
  230. dispose() {
  231. if(this.chart) {
  232. this.chart.dispose()
  233. }
  234. },
  235. resize(size) {
  236. if(size && size.width && size.height) {
  237. this.height = size.height
  238. this.width = size.width
  239. if(this.chart) {this.chart.resize(size)}
  240. } else {
  241. this.$nextTick(() => {
  242. uni.createSelectorQuery()
  243. .in(this)
  244. .select(`.lime-echart`)
  245. .boundingClientRect()
  246. .exec(res => {
  247. if (res) {
  248. let { width, height } = res[0];
  249. this.width = width = width || 300;
  250. this.height = height = height || 300;
  251. this.chart.resize({width, height})
  252. }
  253. });
  254. })
  255. }
  256. },
  257. canvasToTempFilePath(args = {}) {
  258. // #ifndef APP-NVUE
  259. const { use2dCanvas, canvasId } = this;
  260. return new Promise((resolve, reject) => {
  261. const copyArgs = Object.assign({
  262. canvasId,
  263. success: resolve,
  264. fail: reject
  265. }, args);
  266. if (use2dCanvas) {
  267. delete copyArgs.canvasId;
  268. copyArgs.canvas = this.canvasNode;
  269. }
  270. uni.canvasToTempFilePath(copyArgs, this);
  271. });
  272. // #endif
  273. // #ifdef APP-NVUE
  274. this.file = ''
  275. this.$refs.webview.evalJs(`canvasToTempFilePath()`);
  276. return new Promise((resolve, reject) => {
  277. this.$watch('file', async (file) => {
  278. if(file) {
  279. const tempFilePath = await base64ToPath(file)
  280. resolve(args.success({tempFilePath}))
  281. } else {
  282. reject(args.fail({error: ``}))
  283. }
  284. })
  285. })
  286. // #endif
  287. },
  288. async init(echarts, ...args) {
  289. // #ifndef APP-NVUE
  290. if(arguments && arguments.length < 1) {
  291. console.error('缺少参数:init(echarts, theme?:string, opts?: object, callback?: function)')
  292. return
  293. }
  294. // #endif
  295. let theme=null,opts={},callback;
  296. Array.from(arguments).forEach(item => {
  297. if(typeof item === 'function') {
  298. callback = item
  299. }
  300. if(['string'].includes(typeof item)) {
  301. theme = item
  302. }
  303. if(typeof item === 'object') {
  304. opts = item
  305. }
  306. })
  307. if(this.beforeDelay) {
  308. await sleep(this.beforeDelay)
  309. }
  310. let config = await this.getContext();
  311. // #ifndef APP-NVUE
  312. setCanvasCreator(echarts, config)
  313. this.chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts))
  314. if(typeof callback === 'function') {
  315. callback(this.chart)
  316. } else {
  317. return this.chart
  318. }
  319. // #endif
  320. // #ifdef APP-NVUE
  321. this.chart = new Echarts(this.$refs.webview)
  322. this.$refs.webview.evalJs(`init(null, null, ${JSON.stringify(opts)}, ${theme})`)
  323. if(callback) {
  324. callback(this.chart)
  325. } else {
  326. return this.chart
  327. }
  328. // #endif
  329. },
  330. getContext() {
  331. // #ifdef APP-NVUE
  332. if(this.finished) {
  333. return Promise.resolve(this.finished)
  334. }
  335. return new Promise(resolve => {
  336. this.$watch('finished', (val) => {
  337. if(val) {
  338. resolve(this.finished)
  339. }
  340. })
  341. })
  342. // #endif
  343. // #ifndef APP-NVUE
  344. return getRect(`#${this.canvasId}`, {context: this, type: this.use2dCanvas ? 'fields': 'boundingClientRect'}).then(res => {
  345. if(res) {
  346. let dpr = devicePixelRatio
  347. let {width, height, node} = res
  348. let canvas;
  349. this.width = width = width || 300;
  350. this.height = height = height || 300;
  351. if(node) {
  352. const ctx = node.getContext('2d');
  353. canvas = new Canvas(ctx, this, true, node);
  354. this.canvasNode = node
  355. } else {
  356. // #ifdef MP-TOUTIAO
  357. dpr = !this.isPC ? devicePixelRatio : 1// 1.25
  358. // #endif
  359. // #ifndef MP-ALIPAY || MP-TOUTIAO
  360. dpr = this.isPC ? devicePixelRatio : 1
  361. // #endif
  362. // #ifdef MP-ALIPAY || MP-LARK
  363. dpr = devicePixelRatio
  364. // #endif
  365. this.rect = res
  366. this.nodeWidth = width * dpr;
  367. this.nodeHeight = height * dpr;
  368. const ctx = uni.createCanvasContext(this.canvasId, this);
  369. canvas = new Canvas(ctx, this, false);
  370. }
  371. return { canvas, width, height, devicePixelRatio: dpr, node };
  372. } else {
  373. return {}
  374. }
  375. })
  376. // #endif
  377. },
  378. // #ifndef APP-NVUE
  379. getRelative(e) {
  380. return {x: e.pageX - this.rect.left, y: e.pageY - this.rect.top, wheelDelta: e.wheelDelta}
  381. },
  382. getTouch(e) {
  383. return e.touches && e.touches[0] && e.touches[0].x ? e.touches[0] : this.getRelative(e);
  384. },
  385. touchStart(e) {
  386. this.isDown = true
  387. if (this.chart && ((e.touches.length > 0 || e.touches['0']) && e.type != 'mousemove' || e.type == 'mousedown')) {
  388. const touch = this.getTouch(e)
  389. this.startX = touch.x
  390. this.startY = touch.y
  391. this.startT = new Date()
  392. const handler = this.chart.getZr().handler;
  393. dispatch.call(handler, 'mousedown', touch)
  394. dispatch.call(handler, 'mousemove', touch)
  395. handler.processGesture(wrapTouch(e), 'start');
  396. clearTimeout(this.endTimer);
  397. }
  398. },
  399. touchMove(e) {
  400. if(this.isPc && this.enableHover && !this.isDown) {this.isDown = true}
  401. if (this.chart && ((e.touches.length > 0 || e.touches['0']) && e.type != 'mousemove' || e.type == 'mousemove' && this.isDown)) {
  402. const handler = this.chart.getZr().handler;
  403. dispatch.call(handler, 'mousemove', this.getTouch(e))
  404. handler.processGesture(wrapTouch(e), 'change');
  405. }
  406. },
  407. touchEnd(e) {
  408. this.isDown = false
  409. if (this.chart) {
  410. const {x} = e.changedTouches && e.changedTouches[0] || {}
  411. const touch = (x ? e.changedTouches[0] : this.getRelative(e)) || {};
  412. const handler = this.chart.getZr().handler;
  413. const isClick = Math.abs(touch.x - this.startX) < 10 && new Date() - this.startT < 200;
  414. dispatch.call(handler, 'mouseup', touch)
  415. handler.processGesture(wrapTouch(e), 'end');
  416. if(isClick) {
  417. dispatch.call(handler, 'click', touch)
  418. } else {
  419. this.endTimer = setTimeout(() => {
  420. dispatch.call(handler, 'mousemove', {x: 999999999,y: 999999999});
  421. dispatch.call(handler, 'mouseup', {x: 999999999,y: 999999999});
  422. },50)
  423. }
  424. }
  425. },
  426. // #endif
  427. // #ifdef H5
  428. mousewheel(e){
  429. if(this.chart) {
  430. dispatch.call(this.chart.getZr().handler, 'mousewheel', this.getTouch(e))
  431. }
  432. }
  433. // #endif
  434. }
  435. };
  436. </script>
  437. <style scoped>
  438. .lime-echart {
  439. position: relative;
  440. /* #ifndef APP-NVUE */
  441. width: 100%;
  442. height: 100%;
  443. /* #endif */
  444. /* #ifdef APP-NVUE */
  445. flex: 1;
  446. /* #endif */
  447. }
  448. .lime-echart__canvas {
  449. /* #ifndef APP-NVUE */
  450. width: 100%;
  451. height: 100%;
  452. /* #endif */
  453. /* #ifdef APP-NVUE */
  454. flex: 1;
  455. /* #endif */
  456. }
  457. </style>