chartHelper.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  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. )
  169. if (v.data && v.data.length) {
  170. v.data.forEach((vv: any) => {
  171. if (vv > maxData) {
  172. maxData = vv
  173. }
  174. })
  175. yAxisMax = Math.floor(maxData * 1.2)
  176. }
  177. if (dataMarkLine) {
  178. seriesOption.markLine = dataMarkLine
  179. }
  180. if (has(chart, "warning")) {
  181. const markLine: any = {
  182. silent: true,
  183. label: markLineLabel,
  184. data: [
  185. {
  186. yAxis: chart.warning,
  187. },
  188. ],
  189. }
  190. if (markLineStyles && markLineStyles.length) {
  191. markLine.lineStyle = markLineStyles[i % markLineStyles.length]
  192. }
  193. if (chart.warning > maxData) {
  194. yAxisMax = Math.floor(chart.warning * 1.1)
  195. }
  196. seriesOption.markLine = markLine
  197. }
  198. if (dataMarkArea) {
  199. seriesOption.markArea = {
  200. data: dataMarkArea,
  201. label: markAreaLabel,
  202. itemStyle: markAreaStyles,
  203. }
  204. }
  205. const seriesItem = extendDeep(seriesOption, chartOptions.series || {})
  206. series.push(seriesItem)
  207. })
  208. if (getOption(chartOptions, "showTotal", false)) {
  209. series.push({
  210. name: "总计",
  211. type: serieType,
  212. stack: "total",
  213. markLine: {
  214. data: [
  215. { type: "max", name: "最大值" },
  216. { type: "min", name: "最小值" },
  217. ],
  218. label: { show: "true", position: "start" },
  219. lineStyle: {
  220. color: "rgba(255, 81, 71, 0.85)",
  221. },
  222. },
  223. itemStyle: {
  224. normal: {
  225. color: "rgba(128, 128, 128, 0)",
  226. },
  227. },
  228. data: totalArr,
  229. })
  230. }
  231. const axisLabel = getOption(chartOptions, "axisLabel", {})
  232. const axisLine = getOption(chartOptions, "axisLine", {})
  233. const splitLine = getOption(chartOptions, "splitLine", {})
  234. let xAxis: any = {
  235. show: true,
  236. type: "category",
  237. axisLabel,
  238. axisLine,
  239. splitLine,
  240. splitArea: getOption(chartOptions, "xSplitArea", {}),
  241. axisTick: {
  242. alignWithLabel: true,
  243. },
  244. data: _xAxisData,
  245. nameGap: defaultOption.SIZE.nameGap,
  246. boundaryGap: serieType != "line",
  247. }
  248. let yAxis: any = {
  249. show: true,
  250. name: getOption(chart, "yzTitle", ""),
  251. nameTextStyle: { align: "auto" },
  252. type: "value",
  253. max: yAxisMax ? yAxisMax : null,
  254. axisLabel: { ...axisLabel, formatter: getOption(chartOptions, "yAxisFormat", undefined) },
  255. axisLine,
  256. splitLine,
  257. splitArea: getOption(chartOptions, "ySplitArea", {}),
  258. axisTick: {
  259. alignWithLabel: true,
  260. },
  261. }
  262. //console.log("CHART_title", yAxis.name)
  263. const direction = getOption(chartOptions, "direction", false)
  264. if (direction) {
  265. //互换x,y轴
  266. if (direction == "y") {
  267. const tempX = xAxis
  268. const tempY = yAxis
  269. xAxis = Object.assign({}, tempY)
  270. yAxis = Object.assign({}, tempX)
  271. }
  272. }
  273. if (getOption(chartOptions, "inverseX", false)) {
  274. xAxis.inverse = true
  275. }
  276. if (getOption(chartOptions, "inverseY", false)) {
  277. yAxis.inverse = true
  278. }
  279. return { legend, tooltip, xAxis, yAxis, series }
  280. }
  281. const pieSeriesOption = (chart: any, serieData: Array<any>, serieType: string, chartOptions: any) => {
  282. const legend: any = {
  283. type: getOption(chartOptions, "legendType", defaultOption.SIZE.legend.type),
  284. show: true,
  285. icon: getOption(chartOptions, "legendIcon", defaultOption.SIZE.legend.icon),
  286. itemGap: defaultOption.SIZE.roseItemGap,
  287. data: [],
  288. }
  289. let showLabel = getOption(chartOptions, "showLabel", false)
  290. const showPercent = getOption(chartOptions, "showPercent", true)
  291. let labelFormatter = getOption(chartOptions, "labelFormatter", undefined)
  292. if (typeof labelFormatter == "function") {
  293. showLabel = true
  294. }
  295. if (!labelFormatter) {
  296. labelFormatter = "{b} ({c}) "
  297. }
  298. const title: any = {}
  299. const tooltip = {
  300. trigger: "item",
  301. formatter: labelFormatter,
  302. }
  303. const data: any = []
  304. const seriesOption: any = {
  305. type: "pie",
  306. center: getOption(chartOptions, "pieCenter", defaultOption.SIZE.pieCenter),
  307. radius: getOption(chartOptions, "pieRadius", defaultOption.SIZE.pieRadius),
  308. markPoint: getOption(chartOptions, "markPoint", {}),
  309. markLine: getOption(chartOptions, "markLine", {}),
  310. markArea: getOption(chartOptions, "markArea", {}),
  311. label: {
  312. show: showLabel,
  313. position: "outside",
  314. formatter: labelFormatter,
  315. },
  316. }
  317. const total = getTotal(serieData, has(serieData[0], "value") ? "value" : "data")
  318. const titleText = getOption(chartOptions, "titleText", getOption(chart, "yzTitle", "") ?? "")
  319. const titleUnit = getOption(chartOptions, "titleUnit", "")
  320. if (titleText) {
  321. if (titleUnit) {
  322. title.text = [`{title|${titleText}}`, `{total|${total}}`, `{unit|${titleUnit}}`].join("\n")
  323. } else {
  324. title.text = `{title|${titleText}}`
  325. }
  326. }
  327. const itemStyle = getOption(chartOptions, "pieItemStyle", undefined)
  328. serieData.forEach((v: any) => {
  329. const name = showPercent ? `${v.name}:${getPercent(total, v.value)}` : v.name
  330. legend.data.push({
  331. name,
  332. })
  333. const item: any = {
  334. name,
  335. value: v.value,
  336. }
  337. if (itemStyle) {
  338. item.itemStyle = itemStyle
  339. }
  340. data.push(item)
  341. })
  342. const series = [extendDeep(seriesOption, chartOptions.series || {}, { data })]
  343. return { legend, title, tooltip, series }
  344. }
  345. /**
  346. *
  347. * @param data 远程数据
  348. * @param options 为eChart的配置项
  349. 同时也可以单独配置
  350. serieType 图表类型
  351. showTotal 显示总数
  352. showLabel 显示数据标签
  353. labelFormatter 数据标签格式化
  354. markLineLabel 标记警告label (远程数据返回 "warning" 字段 生效)
  355. markLineStyles 标记警告线样式 (远程数据返回 "warning" 字段 生效)
  356. axisLabel 坐标轴label
  357. axisLine 坐标轴样式
  358. splitLine 分割线样式
  359. yAxisFormat y轴label格式化
  360. inverseX 反向坐标轴
  361. inverseY 反向坐标轴
  362. direction: 图表方向 'x' | 'y' (bar 类型生效)
  363. ...
  364. * @param serieType 图表类型
  365. * @returns eChart的配置项
  366. */
  367. const chart = (data: any, options: any, serieType?: string) => {
  368. if (data == null) {
  369. throw new Error("NO CHART DATA")
  370. }
  371. const _chart = compatibleData(data)
  372. const _series = compatibleSeries(_chart)
  373. const chartOptions = extend({}, options)
  374. serieType = getOption(chartOptions, "serieType", serieType)
  375. //const _xAxisData = has(_chart, "categories") ? _chart.categories : []
  376. //标题默认在图表下居中 ,使用远程数据 yzTitle 字段
  377. const title = {
  378. show: true,
  379. text:
  380. getOption(chartOptions, "titleText", "") || getOption(_chart, "title", "") || getOption(_chart, "yzTitle", ""),
  381. }
  382. const grid = Object.assign({}, defaultOption.SIZE.grid)
  383. let seriesOption = {}
  384. switch (serieType) {
  385. case "bar":
  386. case "line":
  387. seriesOption = lineBarSeriesOption(_chart, _series, serieType ?? "bar", chartOptions)
  388. break
  389. case "pie":
  390. seriesOption = pieSeriesOption(_chart, _series, serieType ?? "pie", chartOptions)
  391. break
  392. }
  393. const option = extendDeep({ title, grid }, seriesOption, chartOptions)
  394. return option
  395. //return Object.assign(true, {}, opt, options || {})
  396. }
  397. // const doubleTypes = (data: any, options: any, types = ["bar", "line"]) => {
  398. // if (data == null) {
  399. // throw new Error("no data")
  400. // }
  401. // const chartOptions = extendDeep({}, options)
  402. // const _charts = compatibleMultChartData(data)
  403. // const grid = Object.assign({}, defaultOption.SIZE.grid)
  404. // _charts.forEach((_chart: any, i: number) => {
  405. // const _series = compatibleSeries(_chart)
  406. // //标题默认在图表下居中 ,使用远程数据 title , yzTitle 字段
  407. // const title = {
  408. // show: true,
  409. // text: getOption(_chart, "title", "") || getOption(_chart, "yzTitle", ""),
  410. // }
  411. // })
  412. // }
  413. export default {
  414. bar(data: any, options: any) {
  415. return chart(data, options, "bar")
  416. },
  417. line(data: any, options: any) {
  418. return chart(data, options, "line")
  419. },
  420. pie(data: any, options: any) {
  421. return chart(data, options, "pie")
  422. },
  423. ring(data: any, options: any) {
  424. options = Object.assign({ pieRadius: defaultOption.SIZE.ringRadius }, options)
  425. return chart(data, options, "pie")
  426. },
  427. }