l-echart.vue 12 KB

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