ui-echarts.vue 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. <template>
  2. <view class="ui-echarts" :class="[customClass]" :style="wrapStyle">
  3. <!-- #ifndef APP-NVUE -->
  4. <!-- #ifdef MP-WEIXIN || MP-TOUTIAO -->
  5. <canvas class="ui-echarts_canvas" :style="wrapStyle" :canvas-id="canvasId" :id="canvasId" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd" type="2d" />
  6. <!-- #endif -->
  7. <!-- #ifndef MP-WEIXIN || MP-TOUTIAO -->
  8. <canvas class="ui-echarts_canvas" :style="wrapStyle" :canvas-id="canvasId" :id="canvasId" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd" />
  9. <!-- #endif -->
  10. <!-- #endif -->
  11. <!-- #ifdef APP-NVUE -->
  12. <web-view ref="webview" class="ui-echarts_webview"
  13. src="/uni_modules/ui-echarts/static/index.html"
  14. :style="wrapStyle"
  15. :id="canvasId"
  16. @onPostMessage="onPostMessage">
  17. </web-view>
  18. <!-- #endif -->
  19. </view>
  20. </template>
  21. <script>
  22. import * as echarts from '../../static/echarts.min.js';
  23. import * as ecStat from '../../static/ecStat.min.js';
  24. import WxCanvas from '../../static/wxcanvas.js';
  25. let chartList = {};
  26. function wrapTouch(event) {
  27. for (let i = 0; i < event.touches.length; ++i) {
  28. const touch = event.touches[i];
  29. touch.offsetX = touch.x;
  30. touch.offsetY = touch.y;
  31. }
  32. return event;
  33. }
  34. /**
  35. * Echart 图表工具
  36. * @description 通过折叠面板收纳内容区域(搭配ui-collapse使用)
  37. * @tutorial https://echarts.apache.org/zh/api.html#echarts
  38. * @property {String} option 图表配置
  39. * @property {String | Number} width 宽度,默认100%
  40. * @property {String | Number} height 高度,默认300px
  41. * @property {Boolean} exportBase64=[base64] 输出类型, 默认false
  42. * @example
  43. */
  44. export default {
  45. name: 'ui-echarts',
  46. mixins: [uni.$ui.mixin],
  47. // #ifdef MP-WEIXIN
  48. // 将自定义节点设置成虚拟的,更加接近Vue组件的表现,能更好的使用flex属性
  49. options: {
  50. virtualHost: true
  51. },
  52. // #endif
  53. props: {
  54. option: {
  55. type: Object,
  56. default: () => {
  57. return {}
  58. }
  59. },
  60. width: {
  61. type: [String, Number],
  62. default: '100%',
  63. },
  64. height: {
  65. type: [String, Number],
  66. default: '600rpx'
  67. },
  68. // 输出类型, base64输出base64, H5无效。
  69. exportBase64: {
  70. type: Boolean,
  71. default: false
  72. }
  73. },
  74. data() {
  75. return {
  76. canvasId: `canvas_${uni.$ui.guid(10)}`,
  77. ctx: null,
  78. // 网页初始化就绪。用于NVUE
  79. webviewReady: false,
  80. // 图表初始化就绪。用于NVUE
  81. webviewChart: false,
  82. // 图表导出图片临时文件
  83. webviewCallback: null
  84. }
  85. },
  86. computed: {
  87. wrapStyle () {
  88. const { width, height } = this;
  89. const { addUnit, addStyle } = uni.$ui;
  90. return addStyle({width: addUnit(width), height: addUnit(height)}, 'string')
  91. }
  92. },
  93. watch: {
  94. option(newValue, oldValue) {
  95. if(newValue.series){
  96. this.initChart(newValue)
  97. }
  98. },
  99. // #ifdef APP-NVUE
  100. webviewReady(state) {
  101. this.initChart(this.option);
  102. }
  103. // #endif
  104. },
  105. methods: {
  106. getCanvasAttr2d() {
  107. return new Promise((resolve, reject) => {
  108. const query = uni.createSelectorQuery().in(this)
  109. query
  110. .select('#' + this.canvasId)
  111. .fields({
  112. node: true,
  113. size: true
  114. })
  115. .exec(res => {
  116. const canvasNode = res[0].node
  117. this.canvasNode = canvasNode
  118. const canvasDpr = uni.getSystemInfoSync().pixelRatio
  119. const canvasWidth = res[0].width
  120. const canvasHeight = res[0].height
  121. this.ctx = canvasNode.getContext('2d')
  122. const canvas = new WxCanvas(this.ctx, this.canvasId, true, canvasNode)
  123. echarts.setCanvasCreator(() => {
  124. return canvas
  125. })
  126. resolve({
  127. canvas,
  128. canvasWidth,
  129. canvasHeight,
  130. canvasDpr
  131. })
  132. })
  133. });
  134. },
  135. getCanvasAttr() {
  136. return new Promise((resolve, reject) => {
  137. this.ctx = uni.createCanvasContext(this.canvasId, this);
  138. var canvas = new WxCanvas(this.ctx, this.canvasId, false);
  139. echarts.setCanvasCreator(() => {
  140. return canvas;
  141. });
  142. const canvasDpr = 1
  143. var query = uni.createSelectorQuery()
  144. // #ifndef MP-ALIPAY
  145. .in(this)
  146. // #endif
  147. query.select('#' + this.canvasId).boundingClientRect(res => {
  148. const canvasWidth = res.width
  149. const canvasHeight = res.height
  150. resolve({
  151. canvas,
  152. canvasWidth,
  153. canvasHeight,
  154. canvasDpr
  155. })
  156. }).exec();
  157. });
  158. },
  159. // #ifdef H5
  160. //H5绘制图表
  161. initChart(option) {
  162. this.ctx = uni.createCanvasContext(this.canvasId, this);
  163. chartList[this.canvasId] = echarts.init(document.getElementById(this.canvasId));
  164. chartList[this.canvasId].setOption(option?option:this.option);
  165. },
  166. //H5生成图片
  167. toImageFile(opt) {
  168. const base64 = chartList[this.canvasId].getDataURL()
  169. opt.success && opt.success({tempFilePath: base64, base64 })
  170. },
  171. // #endif
  172. // #ifndef H5
  173. //绘制图表
  174. async initChart(option) {
  175. // #ifdef APP-NVUE
  176. this.chartInitOption(option);
  177. return;
  178. // #endif
  179. // #ifdef MP-WEIXIN || MP-TOUTIAO
  180. const canvasAttr = await this.getCanvasAttr2d();
  181. // #endif
  182. // #ifndef MP-WEIXIN || MP-TOUTIAO
  183. const canvasAttr = await this.getCanvasAttr();
  184. // #endif
  185. const {
  186. canvas,
  187. canvasWidth,
  188. canvasHeight,
  189. canvasDpr
  190. } = canvasAttr
  191. chartList[this.canvasId] = echarts.init(canvas, null, {
  192. width: canvasWidth,
  193. height: canvasHeight,
  194. devicePixelRatio: canvasDpr // new
  195. });
  196. canvas.setChart(chartList[this.canvasId]);
  197. chartList[this.canvasId].setOption(option?option:this.option);
  198. },
  199. //生成图片
  200. toImageFile(opt) {
  201. const self = this;
  202. // #ifdef APP-NVUE
  203. this.chartToImage(opt);
  204. return;
  205. // #endif
  206. // #ifdef MP-WEIXIN || MP-TOUTIAO
  207. var query = uni.createSelectorQuery()
  208. // #ifndef MP-ALIPAY
  209. .in(this)
  210. // #endif
  211. query.select('#' + this.canvasId).fields({ node: true, size: true }).exec(res => {
  212. const canvasNode = res[0].node
  213. opt.canvas = canvasNode
  214. self.toImageFileHandle(opt)
  215. // uni.canvasToTempFilePath(opt, this)
  216. })
  217. // #endif
  218. // #ifndef MP-WEIXIN || MP-TOUTIAO
  219. if (!opt.canvasId) {
  220. opt.canvasId = this.canvasId;
  221. }
  222. this.ctx.draw(true, () => {
  223. self.toImageFileHandle(opt)
  224. // uni.canvasToTempFilePath(opt, this);
  225. });
  226. // #endif
  227. },
  228. toImageFileHandle (opt) {
  229. if (!this.exportBase64) {
  230. uni.canvasToTempFilePath(opt, this);
  231. } else {
  232. const { success, fail, complete, ...rest } = opt;
  233. uni.canvasToTempFilePath({
  234. ...rest,
  235. success: async (res) => {
  236. const tempFilePath = res.tempFilePath;
  237. const base64Str = await uni.$ui.base64.pathToBase64(tempFilePath);
  238. success && success({ errMsg: res.errMsg, tempFilePath, base64: base64Str })
  239. },
  240. fail: (res) => {
  241. fail && fail(res)
  242. },
  243. complete: (res) => {
  244. complete && complete(res)
  245. }
  246. })
  247. }
  248. },
  249. // #endif
  250. touchStart(e) {
  251. if (chartList[this.canvasId] && e.touches.length > 0) {
  252. var touch = e.touches[0];
  253. var handler = chartList[this.canvasId].getZr().handler;
  254. handler.dispatch('mousedown', {
  255. zrX: touch.x,
  256. zrY: touch.y
  257. });
  258. handler.dispatch('mousemove', {
  259. zrX: touch.x,
  260. zrY: touch.y
  261. });
  262. handler.processGesture(wrapTouch(e), 'start');
  263. }
  264. },
  265. touchMove(e) {
  266. if (chartList[this.canvasId] && e.touches.length > 0) {
  267. var touch = e.touches[0];
  268. var handler = chartList[this.canvasId].getZr().handler;
  269. handler.dispatch('mousemove', {
  270. zrX: touch.x,
  271. zrY: touch.y
  272. });
  273. handler.processGesture(wrapTouch(e), 'change');
  274. }
  275. },
  276. touchEnd(e) {
  277. if (chartList[this.canvasId]) {
  278. const touch = e.changedTouches ? e.changedTouches[0] : {};
  279. var handler = chartList[this.canvasId].getZr().handler;
  280. handler.dispatch('mouseup', {
  281. zrX: touch.x,
  282. zrY: touch.y
  283. });
  284. handler.dispatch('click', {
  285. zrX: touch.x,
  286. zrY: touch.y
  287. });
  288. handler.processGesture(wrapTouch(e), 'end');
  289. }
  290. },
  291. // #ifdef APP-NVUE
  292. chartInitOption (option) {
  293. this.webviewReady && this.$refs.webview.evalJs(`setOption(${JSON.stringify(option)})`);
  294. },
  295. chartToImage (opt) {
  296. this.webviewCallback = opt;
  297. this.webviewReady && this.webviewChart && this.$refs.webview.evalJs(`toImage()`);
  298. },
  299. chartClear () {
  300. this.webviewReady && this.webviewChart && this.$refs.webview.evalJs(`clearChart()`);
  301. },
  302. async onPostMessage (evt) {
  303. /**
  304. * 1: 准备就绪(webview)
  305. * 2: 初始化echarts
  306. * 3: 设置echarts option
  307. * 4: 导出图片
  308. * 5: 销毁echarts实例
  309. * */
  310. const res = evt?.detail?.data[0] || { state: 0 };
  311. if (res.state === 1) {
  312. this.webviewReady = true;
  313. } else if (res.state === 2) {
  314. this.webviewChart = true;
  315. } else if (res.state === 3) {
  316. // option 更新成功
  317. } else if (res.state === 4) {
  318. const opt = { ...this.webviewCallback };
  319. const filePath = await uni.$ui.base64.base64ToPath(res.base64);
  320. opt.success && opt.success({ errMsg: 'ok', tempFilePath: filePath, base64: res.base64 });
  321. }
  322. },
  323. // #endif
  324. clearChart () {
  325. // #ifdef APP-NVUE
  326. this.chartClear();
  327. return;
  328. // #endif
  329. if (chartList[this.canvasId]) {
  330. chartList[this.canvasId].clear()
  331. }
  332. }
  333. },
  334. beforeDestroy() {
  335. this.clearChart();
  336. },
  337. mounted() {
  338. // Disable prograssive because drawImage doesn't support DOM as parameter
  339. // See https://developers.weixin.qq.com/miniprogram/dev/api/canvas/CanvasContext.drawImage.html
  340. echarts.registerPreprocessor(option => {
  341. if (option && option.series) {
  342. if (option.series.length > 0) {
  343. option.series.forEach(series => {
  344. series.progressive = 0;
  345. });
  346. } else if (typeof option.series === 'object') {
  347. option.series.progressive = 0;
  348. }
  349. }
  350. });
  351. }
  352. }
  353. </script>
  354. <style lang="scss" scoped>
  355. .ui-echarts {
  356. flex: 1;
  357. &_canvas {
  358. width: 100%;
  359. height: 100%;
  360. }
  361. }
  362. </style>