chartHelper.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. import defaultOption from "./chartOption"
  2. import { has, isArray, extend, extendDeep, getPercent, getTotal } from "@/core/utils/utils"
  3. const compatibleMultChartData = (data: any): any => {
  4. let arr: any = []
  5. if (has(data, "chartData")) {
  6. if (isArray(data.chartData)) {
  7. arr = extend(data.chartData)
  8. } else if (Object.keys(data.chartData)) {
  9. arr.push(extend(data.chartData))
  10. }
  11. }
  12. return arr
  13. }
  14. //获取兼容的chart对象
  15. const compatibleData = (data: any): any => {
  16. let obj = {}
  17. if (has(data, "chartData")) {
  18. if (isArray(data.chartData)) {
  19. obj = data.chartData[0]
  20. } else {
  21. obj = data.chartData
  22. }
  23. } else {
  24. obj = data
  25. }
  26. return obj
  27. }
  28. //获取兼容的SeriesData
  29. const compatibleSeries = (data: any): any => {
  30. let result = []
  31. if (has(data, "seriesData")) {
  32. result = data.seriesData
  33. } else if (has(data, "series")) {
  34. result = data.series
  35. } else {
  36. result = []
  37. }
  38. return result
  39. }
  40. /**
  41. * 获取某个配置项,并将其在原对象中删除 (保持eChart对象的干净)
  42. * @param key
  43. * @param options
  44. * @param defaultVal
  45. * @param objectType 0 值类型, 1 对象, 2 数组
  46. * @returns
  47. */
  48. const getOption = (options: any, key: string, defaultVal?: any) => {
  49. let type = 0
  50. if (typeof defaultVal == "object") {
  51. type = Array.isArray(defaultVal) ? 2 : 1
  52. }
  53. let val =
  54. type == 0 ? defaultVal : type == 1 ? Object.assign({}, defaultVal || {}) : Object.assign([], defaultVal || [])
  55. if (has(options, key)) {
  56. val = type == 0 ? options[key] : type == 1 ? extendDeep({}, val, options[key]) : extendDeep([], val, options[key])
  57. delete options[key]
  58. }
  59. return val
  60. }
  61. const lineBarSeriesOption = (chart: any, serieData: Array<any>, serieType: string, chartOptions: any) => {
  62. const _xAxisData = has(chart, "categories") ? chart.categories : []
  63. const legend: any = Object.assign({}, defaultOption.SIZE.legend, {
  64. type: getOption(chartOptions, "legendType", defaultOption.SIZE.legend.type),
  65. show: true,
  66. icon: getOption(chartOptions, "legendIcon", defaultOption.SIZE.legend.icon),
  67. data: [],
  68. })
  69. const tooltip = {
  70. show: getOption(chart, "showTooltip", true),
  71. trigger: "axis",
  72. axisPointer: {
  73. // 坐标轴指示器,坐标轴触发有效
  74. type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
  75. },
  76. }
  77. const totalArr: Array<any> = []
  78. if (has(serieData[0], "data")) {
  79. for (let i = 0; i < serieData[0].data.length; i++) {
  80. let temptotal = 0
  81. serieData.forEach((v: any) => {
  82. if (!v.data[i]) {
  83. v.data[i] = 0
  84. }
  85. temptotal += parseFloat(v.data[i])
  86. })
  87. totalArr.push(temptotal)
  88. }
  89. }
  90. const series = []
  91. let yAxisMax = 0
  92. let maxData = 0
  93. let showLabel = getOption(chartOptions, "showLabel", false)
  94. //const markPointStyles = getOption(chartOptions, "markPointStyles", [])
  95. //const markPointLabel = getOption(chartOptions, "markPointLabel", {})
  96. const markLineStyles = getOption(chartOptions, "markLineStyles", [
  97. {
  98. color: "#FF5147",
  99. },
  100. ])
  101. const markLineLabel = getOption(chartOptions, "markLineLabel", {
  102. color: "#FF5147",
  103. })
  104. const markAreaStyles = getOption(chartOptions, "markAreaStyles", {
  105. color: "rgba(255,81,71,0.2)",
  106. })
  107. const markAreaLabel = getOption(chartOptions, "markAreaLabel", {
  108. color: "#F2637B",
  109. offset: [31, 0],
  110. })
  111. const labelFormatter = getOption(chartOptions, "labelFormatter", undefined)
  112. const markPoint = getOption(chartOptions, "markPoint", {})
  113. const markLine = getOption(chartOptions, "markLine", {})
  114. const markArea = getOption(chartOptions, "markArea", {})
  115. const barGap = getOption(chartOptions, "barGap", defaultOption.SIZE.barGap)
  116. const symbol = getOption(chartOptions, "symbol", "circle")
  117. const smooth = getOption(chartOptions, "smooth", 0.5)
  118. const areaStyleColors = getOption(chartOptions, "areaStyle", [
  119. "rgba(56,164,90,.5)",
  120. "rgb(76,136,207,.3)",
  121. "rgb(255,185,126,.3)",
  122. ])
  123. const dataMarkLine = getOption(chart, "markLine", {})
  124. const dataMarkArea = getOption(chart, "markArea", {})
  125. serieData.forEach((v: any, i: number) => {
  126. legend.data.push({
  127. name: v.name,
  128. })
  129. if (typeof labelFormatter == "function") {
  130. showLabel = true
  131. }
  132. const type = has(v, "type") ? v.type : serieType
  133. let seriesOption: any = {
  134. type: type,
  135. //name: v.name,
  136. //data: v.data,
  137. stack: has(v, "stack") ? v.stack : null,
  138. itemStyle: getOption(v, "itemStyle", {}),
  139. markPoint,
  140. markLine,
  141. markArea,
  142. label: {
  143. show: showLabel,
  144. position: "",
  145. formatter: labelFormatter,
  146. fontSize: 10,
  147. },
  148. }
  149. seriesOption = Object.assign(
  150. seriesOption,
  151. type == "bar"
  152. ? {
  153. barWidth: defaultOption.SIZE.barWidth,
  154. barGap,
  155. //barMinWidth: defaultOption.SIZE.barMinWidth,
  156. barMaxWidth: defaultOption.SIZE.barMaxWidth,
  157. }
  158. : {
  159. symbol,
  160. symbolSize: defaultOption.SIZE.symbolSize,
  161. connectNulls: true,
  162. smooth,
  163. smoothMonotone: "x",
  164. areaStyle: {
  165. color: areaStyleColors[i],
  166. },
  167. },
  168. v
  169. )
  170. if (v.data && v.data.length) {
  171. v.data.forEach((vv: any) => {
  172. if (typeof vv == "object") {
  173. if (vv.value > maxData) {
  174. maxData = vv.value
  175. }
  176. } else {
  177. if (vv > maxData) {
  178. maxData = vv
  179. }
  180. }
  181. })
  182. yAxisMax = Math.floor(maxData * 1.2)
  183. }
  184. if (dataMarkLine) {
  185. seriesOption.markLine = dataMarkLine
  186. }
  187. if (has(chart, "warning")) {
  188. const markLine: any = {
  189. silent: true,
  190. label: markLineLabel,
  191. data: [
  192. {
  193. yAxis: chart.warning,
  194. },
  195. ],
  196. }
  197. if (markLineStyles && markLineStyles.length) {
  198. markLine.lineStyle = markLineStyles[i % markLineStyles.length]
  199. }
  200. if (chart.warning > maxData) {
  201. yAxisMax = Math.floor(chart.warning * 1.1)
  202. }
  203. seriesOption.markLine = markLine
  204. }
  205. if (dataMarkArea) {
  206. seriesOption.markArea = {
  207. data: dataMarkArea,
  208. label: markAreaLabel,
  209. itemStyle: markAreaStyles,
  210. }
  211. }
  212. const seriesItem = extendDeep(seriesOption, chartOptions.series || {})
  213. series.push(seriesItem)
  214. })
  215. if (getOption(chartOptions, "showTotal", false)) {
  216. series.push({
  217. name: "总计",
  218. type: serieType,
  219. stack: "total",
  220. markLine: {
  221. data: [
  222. { type: "max", name: "最大值" },
  223. { type: "min", name: "最小值" },
  224. ],
  225. label: { show: "true", position: "start" },
  226. lineStyle: {
  227. color: "rgba(255, 81, 71, 0.85)",
  228. },
  229. },
  230. itemStyle: {
  231. normal: {
  232. color: "rgba(128, 128, 128, 0)",
  233. },
  234. },
  235. data: totalArr,
  236. })
  237. }
  238. const axisLabel = getOption(chartOptions, "axisLabel", {})
  239. const axisLine = getOption(chartOptions, "axisLine", {})
  240. const splitLine = getOption(chartOptions, "splitLine", {})
  241. let xAxis: any = {
  242. show: true,
  243. type: "category",
  244. axisLabel,
  245. axisLine,
  246. splitLine,
  247. splitArea: getOption(chartOptions, "xSplitArea", {}),
  248. axisTick: {
  249. alignWithLabel: true,
  250. },
  251. data: _xAxisData,
  252. nameGap: defaultOption.SIZE.nameGap,
  253. boundaryGap: serieType != "line",
  254. }
  255. let yAxis: any = {
  256. show: true,
  257. name: getOption(chart, "yzTitle", ""),
  258. nameTextStyle: { align: "auto" },
  259. type: "value",
  260. max: yAxisMax ? yAxisMax : null,
  261. axisLabel: { ...axisLabel, formatter: getOption(chartOptions, "yAxisFormat", undefined) },
  262. axisLine,
  263. splitLine,
  264. splitArea: getOption(chartOptions, "ySplitArea", {}),
  265. axisTick: {
  266. alignWithLabel: true,
  267. },
  268. }
  269. //console.log("CHART_title", yAxis.name)
  270. const direction = getOption(chartOptions, "direction", false)
  271. if (direction) {
  272. //互换x,y轴
  273. if (direction == "y") {
  274. const tempX = xAxis
  275. const tempY = yAxis
  276. xAxis = Object.assign({}, tempY)
  277. yAxis = Object.assign({}, tempX)
  278. }
  279. }
  280. if (getOption(chartOptions, "inverseX", false)) {
  281. xAxis.inverse = true
  282. }
  283. if (getOption(chartOptions, "inverseY", false)) {
  284. yAxis.inverse = true
  285. }
  286. return { legend, tooltip, xAxis, yAxis, series }
  287. }
  288. const pieSeriesOption = (chart: any, serieData: Array<any>, serieType: string, chartOptions: any) => {
  289. const legend: any = {
  290. type: getOption(chartOptions, "legendType", defaultOption.SIZE.legend.type),
  291. show: true,
  292. icon: getOption(chartOptions, "legendIcon", defaultOption.SIZE.legend.icon),
  293. itemGap: defaultOption.SIZE.roseItemGap,
  294. data: [],
  295. }
  296. let showLabel = getOption(chartOptions, "showLabel", false)
  297. const showPercent = getOption(chartOptions, "showPercent", true)
  298. let labelFormatter = getOption(chartOptions, "labelFormatter", undefined)
  299. if (typeof labelFormatter == "function") {
  300. showLabel = true
  301. }
  302. if (!labelFormatter) {
  303. labelFormatter = "{b} ({c}) "
  304. }
  305. const title: any = {}
  306. const tooltip = {
  307. trigger: "item",
  308. formatter: labelFormatter,
  309. }
  310. const data: any = []
  311. const seriesOption: any = {
  312. type: "pie",
  313. center: getOption(chartOptions, "pieCenter", defaultOption.SIZE.pieCenter),
  314. radius: getOption(chartOptions, "pieRadius", defaultOption.SIZE.pieRadius),
  315. markPoint: getOption(chartOptions, "markPoint", {}),
  316. markLine: getOption(chartOptions, "markLine", {}),
  317. markArea: getOption(chartOptions, "markArea", {}),
  318. label: {
  319. show: showLabel,
  320. position: "outside",
  321. formatter: labelFormatter,
  322. },
  323. }
  324. const total = getTotal(serieData, has(serieData[0], "value") ? "value" : "data")
  325. const titleText = getOption(chartOptions, "titleText", getOption(chart, "yzTitle", "") ?? "")
  326. const titleUnit = getOption(chartOptions, "titleUnit", "")
  327. if (titleText) {
  328. if (titleUnit) {
  329. title.text = [`{title|${titleText}}`, `{total|${total}}`, `{unit|${titleUnit}}`].join("\n")
  330. } else {
  331. title.text = `{title|${titleText}}`
  332. }
  333. }
  334. const itemStyle = getOption(chartOptions, "pieItemStyle", undefined)
  335. serieData.forEach((v: any) => {
  336. const name = showPercent ? `${v.name}:${getPercent(total, v.value)}` : v.name
  337. legend.data.push({
  338. name,
  339. })
  340. const item: any = {
  341. name,
  342. value: v.value,
  343. }
  344. if (itemStyle) {
  345. item.itemStyle = itemStyle
  346. }
  347. data.push(item)
  348. })
  349. const series = [extendDeep(seriesOption, chartOptions.series || {}, { data })]
  350. return { legend, title, tooltip, series }
  351. }
  352. /**
  353. *
  354. * @param data 远程数据
  355. * @param options 为eChart的配置项
  356. 同时也可以单独配置
  357. serieType 图表类型
  358. showTotal 显示总数
  359. showLabel 显示数据标签
  360. labelFormatter 数据标签格式化
  361. markLineLabel 标记警告label (远程数据返回 "warning" 字段 生效)
  362. markLineStyles 标记警告线样式 (远程数据返回 "warning" 字段 生效)
  363. axisLabel 坐标轴label
  364. axisLine 坐标轴样式
  365. splitLine 分割线样式
  366. yAxisFormat y轴label格式化
  367. inverseX 反向坐标轴
  368. inverseY 反向坐标轴
  369. direction: 图表方向 'x' | 'y' (bar 类型生效)
  370. ...
  371. * @param serieType 图表类型
  372. * @returns eChart的配置项
  373. */
  374. const chart = (data: any, options: any, serieType?: string) => {
  375. if (data == null) {
  376. throw new Error("NO CHART DATA")
  377. }
  378. const _chart = compatibleData(data)
  379. const _series = compatibleSeries(_chart)
  380. const chartOptions = extend({}, options)
  381. serieType = getOption(chartOptions, "serieType", serieType)
  382. //const _xAxisData = has(_chart, "categories") ? _chart.categories : []
  383. //标题默认在图表下居中 ,使用远程数据 yzTitle 字段
  384. const title = {
  385. show: true,
  386. text:
  387. getOption(chartOptions, "titleText", "") || getOption(_chart, "title", "") || getOption(_chart, "yzTitle", ""),
  388. }
  389. const grid = Object.assign({}, defaultOption.SIZE.grid)
  390. let seriesOption = {}
  391. switch (serieType) {
  392. case "bar":
  393. case "line":
  394. seriesOption = lineBarSeriesOption(_chart, _series, serieType ?? "bar", chartOptions)
  395. break
  396. case "pie":
  397. seriesOption = pieSeriesOption(_chart, _series, serieType ?? "pie", chartOptions)
  398. break
  399. }
  400. const option = extendDeep({ title, grid }, seriesOption, chartOptions)
  401. return option
  402. //return Object.assign(true, {}, opt, options || {})
  403. }
  404. // const doubleTypes = (data: any, options: any, types = ["bar", "line"]) => {
  405. // if (data == null) {
  406. // throw new Error("no data")
  407. // }
  408. // const chartOptions = extendDeep({}, options)
  409. // const _charts = compatibleMultChartData(data)
  410. // const grid = Object.assign({}, defaultOption.SIZE.grid)
  411. // _charts.forEach((_chart: any, i: number) => {
  412. // const _series = compatibleSeries(_chart)
  413. // //标题默认在图表下居中 ,使用远程数据 title , yzTitle 字段
  414. // const title = {
  415. // show: true,
  416. // text: getOption(_chart, "title", "") || getOption(_chart, "yzTitle", ""),
  417. // }
  418. // })
  419. // }
  420. export default {
  421. bar(data: any, options: any) {
  422. return chart(data, options, "bar")
  423. },
  424. line(data: any, options: any) {
  425. return chart(data, options, "line")
  426. },
  427. pie(data: any, options: any) {
  428. return chart(data, options, "pie")
  429. },
  430. ring(data: any, options: any) {
  431. options = Object.assign({ pieRadius: defaultOption.SIZE.ringRadius }, options)
  432. return chart(data, options, "pie")
  433. },
  434. }