l-echart.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. <template>
  2. <!-- #ifndef APP-NVUE || WEB -->
  3. <view class="lime-echart"
  4. :style="[lStyle]"
  5. v-if="canvasId"
  6. ref="chartContainer"
  7. :aria-label="'图表'">
  8. <canvas class="lime-echart__canvas"
  9. type="2d"
  10. :style="[styles]"
  11. :id="canvasId"
  12. :disable-scroll="isDisableScroll"
  13. :canvas-id="canvasId"
  14. @touchstart="handleTouchStart"
  15. @touchmove="handleTouchMove"
  16. @touchend="handleTouchEnd">
  17. </canvas>
  18. </view>
  19. <!-- #endif -->
  20. <!-- #ifdef WEB -->
  21. <div class="lime-echart" ref="chartContainer" :style="[styles, lStyle]"></div>
  22. <!-- #endif -->
  23. <!-- #ifdef APP-NVUE -->
  24. <view class="lime-echart" :style="[lStyle]">
  25. <web-view class="lime-echart__canvas"
  26. :webview-styles="webviewStyles"
  27. :style="[styles]"
  28. ref="chartContainer"
  29. src="/uni_modules/lime-echart/static/app/uvue.html?v=1"
  30. @pagefinish="isInitialized = true"
  31. @onPostMessage="handleWebviewMessage"></web-view>
  32. </view>
  33. <!-- #endif -->
  34. </template>
  35. <script lang="ts">
  36. // @ts-nocheck
  37. import { defineComponent, getCurrentInstance, ref, onMounted, nextTick, onBeforeUnmount, watch, computed } from './vue'
  38. import echartProps from './props'
  39. // #ifndef APP-NVUE || WEB
  40. import { Canvas, setCanvasCreator, dispatch } from './canvas';
  41. import { wrapTouch, convertTouchesToArray, devicePixelRatio ,sleep, canIUseCanvas2d, getRect, getDeviceInfo } from './utils';
  42. // #endif
  43. // #ifdef APP-NVUE
  44. import { base64ToPath, sleep } from './utils';
  45. import { Echarts } from './nvue'
  46. // #endif
  47. // #ifdef WEB
  48. import * as echartsLibrary from '@/uni_modules/lime-echart/static/web/echarts.esm.min.js';
  49. // #endif
  50. // #ifdef APP-VUE
  51. import '@/uni_modules/lime-echart/static/app/echarts.min.js';
  52. const echartsLibrary = globalThis.echarts
  53. // #endif
  54. export default defineComponent({
  55. props: echartProps,
  56. emits: ['finished'],
  57. setup(props, { emit, expose }) {
  58. // #ifndef APP-NVUE || WEB || APP-VUE
  59. let echartsLibrary = null
  60. // #endif
  61. const instance = getCurrentInstance()!;
  62. const canvasId = `lime-echart-${instance.uid}`
  63. const isInitialized = ref(false)
  64. const chartContainer = ref(null)
  65. type ChartOptions = Record<string, any>
  66. type EChartsInstance = typeof echartsLibrary
  67. type EChartsResolveCallback = (value: EChartsInstance) => void
  68. const initializationQueue = [] as EChartsResolveCallback[]
  69. const callbackQueue = [] as EChartsResolveCallback[]
  70. let chartInstance: null | EChartsInstance = null
  71. const styles = computed(()=> {
  72. if(props.landscape) {
  73. return {
  74. transform: 'translate(-50%,-50%) rotate(90deg)',
  75. top: '50%',
  76. left: '50%',
  77. }
  78. }
  79. return {}
  80. })
  81. const checkInitialization = (): boolean => {
  82. if(chartInstance) return false
  83. console.warn(`组件还未初始化,请先使用 init`)
  84. return true
  85. }
  86. const setOption = (options: ChartOptions) => {
  87. if (checkInitialization()) return
  88. chartInstance!.setOption(options);
  89. }
  90. const hideLoading = () => {
  91. if (checkInitialization()) return
  92. chartInstance!.showLoading();
  93. }
  94. const showLoading = () => {
  95. if (checkInitialization()) return
  96. chartInstance!.hideLoading();
  97. }
  98. const clear = () => {
  99. if (checkInitialization()) return
  100. chartInstance!.clear();
  101. }
  102. const dispose = () => {
  103. if (checkInitialization()) return
  104. chartInstance!.dispose();
  105. }
  106. const processInitializationQueue = () => {
  107. while (initializationQueue.length > 0) {
  108. if (chartInstance != null) {
  109. const resolve = initializationQueue.pop() as EChartsResolveCallback
  110. resolve(chartInstance!)
  111. }
  112. }
  113. if (chartInstance != null) {
  114. while (callbackQueue.length > 0) {
  115. const callback = callbackQueue.pop() as EChartsResolveCallback
  116. callback(chartInstance!)
  117. }
  118. }
  119. }
  120. const resize = (dimensions?: { width?: number; height?: number }) => {
  121. if (checkInitialization()) return
  122. // #ifdef APP-NVUE || WEB
  123. chartInstance!.resize(dimensions);
  124. // #endif
  125. // #ifndef APP-NVUE || WEB
  126. getRect(`#${canvasId}`, instance.proxy).then(res => {
  127. chartInstance!.resize({width: res.width, height: res.height});
  128. })
  129. // #endif
  130. }
  131. // #ifdef APP-NVUE
  132. let chartFile = ref(null);
  133. const handleWebviewMessage = (e) => {
  134. const detail = e?.detail?.data[0] || null;
  135. const data = detail?.data
  136. const key = detail?.event
  137. const options = data?.options
  138. const event = data?.event
  139. const file = detail?.file
  140. if (key == 'log' && data) {
  141. console.log(data)
  142. }
  143. if(event) {
  144. chartInstance.dispatchAction(event.replace(/"/g,''), options)
  145. }
  146. if(file) {
  147. chartFile.value = file
  148. }
  149. }
  150. const canvasToTempFilePath = (options: ChartOptions) => {
  151. if (checkInitialization()) return
  152. chartContainer.value.evalJs(`canvasToTempFilePath()`);
  153. watch(chartFile, async (file) =>{
  154. if(!file) return
  155. const tempFilePath = await base64ToPath(file)
  156. options.success({tempFilePath})
  157. })
  158. }
  159. const getContext = () => {
  160. if(isInitialized.value) {
  161. return Promise.resolve(isInitialized.value)
  162. }
  163. return new Promise(resolve => {
  164. watch(isInitialized, (val) =>{
  165. if(!val) return
  166. resolve(val)
  167. })
  168. })
  169. }
  170. const init = async (echarts, ...args) => {
  171. let theme: string | null = null
  172. let config:Record<string, any> = {}
  173. let callback: Function | null = null;
  174. args.forEach(item => {
  175. if (typeof item === 'function') {
  176. callback = item
  177. } else if (typeof item === 'string') {
  178. theme = item
  179. } else if (typeof item === 'object') {
  180. config = item
  181. }
  182. })
  183. if(props.beforeDelay) {
  184. await sleep(props.beforeDelay)
  185. }
  186. await getContext();
  187. chartInstance = new Echarts(chartContainer.value)
  188. chartContainer.value.evalJs(`init(null, null, ${JSON.stringify(config)}, ${theme})`)
  189. if (callback && typeof callback === 'function') {
  190. callbackQueue.push(callback)
  191. }
  192. return new Promise<EChartsInstance>((resolve) => {
  193. nextTick(()=>{
  194. initializationQueue.push(resolve)
  195. processInitializationQueue()
  196. })
  197. })
  198. }
  199. // #endif
  200. // #ifndef APP-NVUE || WEB
  201. let canvasNode;
  202. const canvasToTempFilePath = (options: ChartOptions) => {
  203. if (checkInitialization()) return
  204. if(canvasNode) {
  205. options.success?.({
  206. tempFilePath: canvasNode.toDataURL()
  207. })
  208. } else {
  209. uni.canvasToTempFilePath({
  210. ...options,
  211. canvasId
  212. }, instance.proxy);
  213. }
  214. }
  215. const getContext = () => {
  216. return getRect(`#${canvasId}`, instance.proxy).then(res => {
  217. let dpr = devicePixelRatio
  218. let {width, height, node} = res
  219. let canvas: Canvas | null = null;
  220. if(!(width || height)) {
  221. return Promise.reject('no rect')
  222. }
  223. if(node && node.getContext) {
  224. const ctx = node.getContext('2d');
  225. canvas = new Canvas(ctx, instance.proxy, true, node);
  226. canvasNode = node
  227. } else {
  228. dpr = 1
  229. const ctx = uni.createCanvasContext(canvasId, instance.proxy);
  230. canvas = new Canvas(ctx, instance.proxy, false);
  231. }
  232. return { canvas, width, height, devicePixelRatio: dpr, node }
  233. })
  234. }
  235. const getTouch = (e) => {
  236. const touches = e.touches[0]
  237. const touch = props.landscape
  238. ? {
  239. x: touches.y,
  240. y: touches.x
  241. }
  242. : {
  243. x: touches.x,
  244. y: touches.y
  245. }
  246. return touch
  247. }
  248. const handleTouchStart = (e) => {
  249. if (chartInstance == null) return
  250. const handler = chartInstance.getZr().handler;
  251. const touch = getTouch(e)
  252. dispatch.call(handler, 'mousedown', touch)
  253. dispatch.call(handler, 'click', touch)
  254. }
  255. const handleTouchMove = (e) => {
  256. if (chartInstance == null) return
  257. const handler = chartInstance.getZr().handler;
  258. const touch = getTouch(e)
  259. dispatch.call(handler, 'mousemove', touch)
  260. }
  261. const handleTouchEnd = (e) => {
  262. if (chartInstance == null || !props.autoHideTooltip) return
  263. const handler = chartInstance.getZr().handler;
  264. const touch = {
  265. x: 999999999,
  266. y: 999999999
  267. }
  268. dispatch.call(handler, 'mousemove', touch)
  269. dispatch.call(handler, 'touchend', touch)
  270. }
  271. const init = async (echartsLib: EChartsInstance = echartsLibrary, ...args: any[]): Promise<EChartsInstance> => {
  272. const library = echartsLib || echartsLibrary
  273. if (!library) {
  274. console.error('ECharts library is required');
  275. return Promise.reject('ECharts library is required');
  276. }
  277. let theme: string | null = null
  278. let config:Record<string, any> = {}
  279. let callback: Function | null = null;
  280. args.forEach(item => {
  281. if (typeof item === 'function') {
  282. callback = item
  283. } else if (typeof item === 'string') {
  284. theme = item
  285. } else if (typeof item === 'object') {
  286. config = item
  287. }
  288. })
  289. if(props.beforeDelay) {
  290. await sleep(props.beforeDelay)
  291. }
  292. let options = await getContext();
  293. setCanvasCreator(library, options)
  294. chartInstance = library.init(options.canvas, theme, Object.assign({}, options, config))
  295. if (callback && typeof callback === 'function') {
  296. callbackQueue.push(callback)
  297. }
  298. return new Promise<EChartsInstance>((resolve) => {
  299. initializationQueue.push(resolve)
  300. processInitializationQueue()
  301. })
  302. }
  303. // #endif
  304. // #ifdef WEB
  305. const canvasToTempFilePath = (options: ChartOptions) => {
  306. if (checkInitialization()) return
  307. options.success?.({
  308. tempFilePath: chartInstance._api.getDataURL()
  309. })
  310. }
  311. const init = async (echarts: EChartsInstance = echartsLibrary, ...args: any[]): Promise<EChartsInstance> => {
  312. const library = echarts || echartsLibrary
  313. if (!library) {
  314. console.error('ECharts library is required');
  315. return Promise.reject('ECharts library is required');
  316. }
  317. let theme: string | null = null
  318. let config = {}
  319. let callback: Function | null = null;
  320. args.forEach(item => {
  321. if (typeof item === 'function') {
  322. callback = item
  323. } else if (typeof item === 'string') {
  324. theme = item
  325. } else if (typeof item === 'object') {
  326. config = item
  327. }
  328. })
  329. // Configure ECharts environment
  330. library.env.domSupported = true
  331. library.env.hasGlobalWindow = true
  332. library.env.node = false
  333. library.env.pointerEventsSupported = false
  334. library.env.svgSupported = true
  335. library.env.touchEventsSupported = true
  336. library.env.transform3dSupported = true
  337. library.env.transformSupported = true
  338. library.env.worker = false
  339. library.env.wxa = false
  340. chartInstance = library.init(chartContainer.value, theme, config)
  341. if (callback != null && typeof callback === 'function') {
  342. callbackQueue.push(callback)
  343. }
  344. return new Promise<EChartsInstance>((resolve) => {
  345. initializationQueue.push(resolve)
  346. processInitializationQueue()
  347. })
  348. }
  349. // #endif
  350. onMounted(() => {
  351. nextTick(() => {
  352. // #ifndef APP-NVUE
  353. isInitialized.value = true
  354. // #endif
  355. emit('finished')
  356. processInitializationQueue()
  357. })
  358. })
  359. onBeforeUnmount(()=> {
  360. clear()
  361. dispose()
  362. })
  363. // #ifdef VUE3
  364. expose({
  365. init,
  366. setOption,
  367. hideLoading,
  368. showLoading,
  369. clear,
  370. dispose,
  371. resize,
  372. canvasToTempFilePath
  373. })
  374. // #endif
  375. return {
  376. canvasId,
  377. chartContainer,
  378. styles,
  379. // #ifndef WEB || APP-NVUE
  380. handleTouchStart,
  381. handleTouchMove,
  382. handleTouchEnd,
  383. // #endif
  384. // #ifdef APP-NVUE
  385. handleWebviewMessage,
  386. isInitialized,
  387. // #endif
  388. // #ifdef VUE2
  389. init,
  390. setOption,
  391. hideLoading,
  392. showLoading,
  393. clear,
  394. dispose,
  395. resize,
  396. canvasToTempFilePath,
  397. // #endif
  398. }
  399. }
  400. })
  401. </script>
  402. <style>
  403. .lime-echart {
  404. position: relative;
  405. /* #ifndef APP-NVUE */
  406. width: 100%;
  407. height: 100%;
  408. /* #endif */
  409. /* #ifdef APP-NVUE */
  410. flex: 1;
  411. /* #endif */
  412. }
  413. .lime-echart__canvas {
  414. /* #ifndef APP-NVUE */
  415. width: 100%;
  416. height: 100%;
  417. /* #endif */
  418. /* #ifdef APP-NVUE */
  419. flex: 1;
  420. /* #endif */
  421. }
  422. /* #ifndef APP-NVUE */
  423. .lime-echart__mask {
  424. position: absolute;
  425. width: 100%;
  426. height: 100%;
  427. left: 0;
  428. top: 0;
  429. z-index: 1;
  430. }
  431. /* #endif */
  432. </style>