l-echart.vue 12 KB

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