chart.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. package cross_variety
  2. import (
  3. "errors"
  4. "eta/eta_chart_lib/models"
  5. cross_varietyModel "eta/eta_chart_lib/models/data_manage/cross_variety"
  6. "eta/eta_chart_lib/models/data_manage/cross_variety/request"
  7. "eta/eta_chart_lib/services/data"
  8. "eta/eta_chart_lib/utils"
  9. "fmt"
  10. "github.com/shopspring/decimal"
  11. "time"
  12. )
  13. // ChartInfoResp 截面散点图数据
  14. type ChartInfoResp struct {
  15. XName string `description:"x轴名称"`
  16. XNameEn string `description:"x轴名称(英文)"`
  17. XUnitName string `description:"x轴单位名称"`
  18. XUnitNameEn string `description:"x轴单位名称(英文)"`
  19. YName string `description:"y轴名称"`
  20. YNameEn string `description:"y轴名称(英文)"`
  21. YUnitName string `description:"y轴单位名称"`
  22. YUnitNameEn string `description:"y轴单位名称(英文)"`
  23. XMinValue string `description:"X轴的最小值"`
  24. XMaxValue string `description:"X轴的最大值"`
  25. YMinValue string `description:"Y轴的最小值"`
  26. YMaxValue string `description:"Y轴的最大值"`
  27. DataList []SectionScatterSeriesItemResp `description:"数据列"`
  28. }
  29. // SectionScatterSeriesItemResp 系列的返回
  30. type SectionScatterSeriesItemResp struct {
  31. Name string `description:"系列名"`
  32. NameEn string `description:"系列名(英文)"`
  33. Color string `description:"颜色"`
  34. CoordinatePointData []CoordinatePoint `description:"趋势线的前后坐标点"`
  35. }
  36. // CoordinatePoint 坐标点
  37. type CoordinatePoint struct {
  38. X float64
  39. Y float64
  40. XEdbInfoId int
  41. YEdbInfoId int
  42. XDate string
  43. YDate string
  44. DateType int `description:"日期类型:1-最新日期;2-N天前;3-固定日期"`
  45. DaysAgo int `description:"N天前的N值"`
  46. }
  47. // GetChartData
  48. // @Description: 获取跨品种分析图表数据
  49. // @author: Roc
  50. // @datetime 2023-11-24 09:42:59
  51. // @param chartInfoId int
  52. // @param config request.ChartConfigReq
  53. // @return edbList []*data_manage.ChartEdbInfoMapping
  54. // @return dataResp ChartInfoResp
  55. // @return err error
  56. // @return errMsg string
  57. // @return isSendEmail bool
  58. func GetChartData(chartInfoId int, config request.ChartConfigReq) (edbList []*models.ChartEdbInfoMapping, dataResp ChartInfoResp, err error, errMsg string, isSendEmail bool) {
  59. moveUnitDays, ok := utils.FrequencyDaysMap[config.CalculateUnit]
  60. if !ok {
  61. errMsg = "错误的分析周期"
  62. err = errors.New(errMsg)
  63. isSendEmail = false
  64. return
  65. }
  66. isSendEmail = true
  67. // 品种map
  68. varietyMap := make(map[int]*cross_varietyModel.ChartVariety)
  69. {
  70. varietyList, tmpErr := cross_varietyModel.GetVarietyListByIdList(config.VarietyList)
  71. if tmpErr != nil {
  72. err = tmpErr
  73. return
  74. }
  75. for _, v := range varietyList {
  76. varietyMap[v.ChartVarietyId] = v
  77. }
  78. }
  79. // 标签m
  80. var xTagInfo, yTagInfo *cross_varietyModel.ChartTag
  81. {
  82. tagList, tmpErr := cross_varietyModel.GetTagListByIdList([]int{config.TagX, config.TagY})
  83. if tmpErr != nil {
  84. err = tmpErr
  85. return
  86. }
  87. for _, v := range tagList {
  88. if v.ChartTagId == config.TagX {
  89. xTagInfo = v
  90. } else if v.ChartTagId == config.TagY {
  91. yTagInfo = v
  92. }
  93. }
  94. }
  95. if xTagInfo == nil {
  96. errMsg = "找不到对应的X轴标签"
  97. err = errors.New(errMsg)
  98. return
  99. }
  100. if yTagInfo == nil {
  101. errMsg = "找不到对应的Y轴标签"
  102. err = errors.New(errMsg)
  103. return
  104. }
  105. xVarietyEdbMap, yVarietyEdbMap, edbInfoIdList, err := GetXYEdbIdList(config.TagX, config.TagY, config.VarietyList)
  106. if err != nil {
  107. return
  108. }
  109. if len(edbInfoIdList) <= 0 {
  110. errMsg = "品种未配置指标"
  111. err = errors.New(errMsg)
  112. isSendEmail = false
  113. return
  114. }
  115. mappingList, err := models.GetChartEdbMappingListByEdbInfoIdList(edbInfoIdList)
  116. if err != nil {
  117. errMsg = "获取指标信息失败"
  118. err = errors.New("获取指标信息失败,ERR:" + err.Error())
  119. return
  120. }
  121. // 指标对应的所有数据
  122. chartType := 1 //1:普通图,2:季节性图
  123. calendar := "公历"
  124. edbDataListMap, edbList, err := data.GetEdbDataMapList(chartInfoId, chartType, calendar, "", "", mappingList, "")
  125. if err != nil {
  126. return
  127. }
  128. currDay := time.Now()
  129. currDay = time.Date(currDay.Year(), currDay.Month(), currDay.Day(), 0, 0, 0, 0, time.Local)
  130. dataMap := make(map[string]float64)
  131. dateMap := make(map[string]string)
  132. dateTypeMap := make(map[int]int) // 日期配置key对应的日期类型
  133. daysAgoMap := make(map[int]int) // 日期配置key对应的N天前的N值
  134. for dateIndex, dateConfig := range config.DateConfigList {
  135. dateTypeMap[dateIndex] = dateConfig.DateType
  136. daysAgoMap[dateIndex] = dateConfig.Num
  137. for _, edbInfoMapping := range mappingList {
  138. // 数据会是正序的
  139. dataList, ok := edbDataListMap[edbInfoMapping.EdbInfoId]
  140. if !ok {
  141. continue
  142. }
  143. lenData := len(dataList)
  144. if lenData <= 0 {
  145. continue
  146. }
  147. // 数据的开始索引
  148. k := lenData - 1
  149. // 数据的最晚日期
  150. dataEndDateStr := dataList[k].DataTime
  151. dataEndDate, tmpErr := time.ParseInLocation(utils.FormatDate, dataEndDateStr, time.Local)
  152. if tmpErr != nil {
  153. err = tmpErr
  154. return
  155. }
  156. // 数据开始日期
  157. endDateStr := ``
  158. var endDate time.Time
  159. var currVal float64
  160. switch dateConfig.DateType {
  161. case 1: // 1:最新日期;
  162. endDateStr = dataEndDateStr
  163. endDate = dataEndDate
  164. currVal = dataList[k].Value
  165. case 2: // 2:N天前(原为指标最新日期的N天前, 现为系统日期的N天)
  166. tmpEndDate := time.Now().AddDate(0, 0, -dateConfig.Num)
  167. tmpEndDateStr := tmpEndDate.Format(utils.FormatDate)
  168. for i := k; i >= 0; i-- {
  169. tmpDateStr := dataList[i].DataTime
  170. // 如果正好是这一天,那么就直接break了
  171. if tmpEndDateStr == tmpDateStr {
  172. k = i
  173. endDateStr = tmpDateStr
  174. endDate = tmpEndDate
  175. currVal = dataList[i].Value
  176. break
  177. }
  178. tmpDate, tmpErr := time.ParseInLocation(utils.FormatDate, tmpDateStr, time.Local)
  179. if tmpErr != nil {
  180. err = tmpErr
  181. return
  182. }
  183. // 如果这期的日期晚于选择的日期,那么继续遍历
  184. if tmpDate.After(tmpEndDate) {
  185. continue
  186. }
  187. k = i
  188. endDateStr = tmpDateStr
  189. endDate = tmpDate
  190. currVal = dataList[i].Value
  191. break
  192. }
  193. case 3: // 固定日期
  194. if dateConfig.FixDate == "" {
  195. errMsg = "固定日期不可为空"
  196. err = fmt.Errorf("固定日期为空")
  197. return
  198. }
  199. strFixDate := dateConfig.FixDate
  200. fixDate, e := time.ParseInLocation(utils.FormatDate, strFixDate, time.Local)
  201. if e != nil {
  202. errMsg = "固定日期格式有误"
  203. err = fmt.Errorf("固定日期有误, FixDate: %s", dateConfig.FixDate)
  204. return
  205. }
  206. for i := k; i >= 0; i-- {
  207. strThisDate := dataList[i].DataTime
  208. // 如果正好是这一天,那么就直接break了
  209. if strFixDate == strThisDate {
  210. k = i
  211. endDateStr = strThisDate
  212. endDate = fixDate
  213. currVal = dataList[i].Value
  214. break
  215. }
  216. // 若固定日期无值, 则取固定日期之前, 能找到的第一个值(同上N天前的逻辑)
  217. thisDate, e := time.ParseInLocation(utils.FormatDate, strThisDate, time.Local)
  218. if e != nil {
  219. err = fmt.Errorf("数据日期格式有误: %s", e.Error())
  220. return
  221. }
  222. if thisDate.After(fixDate) {
  223. continue
  224. }
  225. k = i
  226. endDateStr = strThisDate
  227. endDate = thisDate
  228. currVal = dataList[i].Value
  229. break
  230. }
  231. }
  232. // 没有找到日期,那么就不处理
  233. if endDateStr == `` || endDate.IsZero() {
  234. continue
  235. }
  236. // 最早的日期
  237. earliestDate := endDate.AddDate(0, 0, -config.CalculateValue*moveUnitDays)
  238. earliestDateStr := earliestDate.Format(utils.FormatDate)
  239. var percentVal float64 // 百分位计算值
  240. // 百分位数据区间算法
  241. if config.PercentType == utils.PercentCalculateTypeRange {
  242. var minVal, maxVal float64
  243. var isNotFirst bool // 是否是第一条数据
  244. for i := k; i >= 0; i-- {
  245. tmpData := dataList[i]
  246. if !isNotFirst {
  247. maxVal = tmpData.Value
  248. minVal = tmpData.Value
  249. isNotFirst = true
  250. continue
  251. }
  252. tmpDateStr := dataList[i].DataTime
  253. // 如果正好是这一天,那么就直接break了
  254. if earliestDateStr == tmpDateStr {
  255. break
  256. }
  257. tmpDate, tmpErr := time.ParseInLocation(utils.FormatDate, tmpDateStr, time.Local)
  258. if tmpErr != nil {
  259. err = tmpErr
  260. return
  261. }
  262. // 如果这期的日期早于选择的日期,那么继续停止遍历
  263. if tmpDate.Before(earliestDate) {
  264. continue
  265. }
  266. if tmpData.Value > maxVal {
  267. maxVal = tmpData.Value
  268. }
  269. if tmpData.Value < minVal {
  270. minVal = tmpData.Value
  271. }
  272. }
  273. // 最大值等于最小值,说明计算结果无效
  274. if maxVal == minVal {
  275. continue
  276. }
  277. // 数据区间百分位=(现值-Min)/(Max-Min)
  278. tmpV := (currVal - minVal) / (maxVal - minVal) * 100
  279. percentVal, _ = decimal.NewFromFloat(tmpV).Round(4).Float64()
  280. }
  281. // 百分位数据个数算法
  282. // 数据区间第一个和最后一个数据点的时间和数据分别为(T1,S1)(T2,S2); N=T1到T2指标数据个数, n=小于等于S2的数据个数
  283. // 个数百分位=(n-1)/(N-1)
  284. if config.PercentType == utils.PercentCalculateTypeNum {
  285. // T1为earliestDate, T2为endDate, S2为currVal
  286. var tinyN, bigN int
  287. lastVal := decimal.NewFromFloat(currVal)
  288. for i := k; i >= 0; i-- {
  289. date, e := time.ParseInLocation(utils.FormatDate, dataList[i].DataTime, time.Local)
  290. if e != nil {
  291. err = fmt.Errorf("数据日期格式有误: %s", e.Error())
  292. return
  293. }
  294. if !date.Before(earliestDate) && !date.After(endDate) {
  295. bigN += 1
  296. }
  297. dateVal := decimal.NewFromFloat(dataList[i].Value)
  298. if dateVal.LessThanOrEqual(lastVal) {
  299. tinyN += 1
  300. }
  301. }
  302. // N=1时说明计算无效
  303. if bigN == 1 {
  304. continue
  305. }
  306. numerator := decimal.NewFromInt(int64(tinyN - 1))
  307. denominator := decimal.NewFromInt(int64(bigN - 1))
  308. percentVal, _ = numerator.Div(denominator).Round(4).Float64()
  309. }
  310. // key的生成(日期配置下标+指标id)
  311. key := fmt.Sprint(dateIndex, "_", edbInfoMapping.EdbInfoId)
  312. dataMap[key] = percentVal
  313. dateMap[key] = endDateStr
  314. }
  315. }
  316. // 返回数据处理
  317. dataList := make([]SectionScatterSeriesItemResp, 0)
  318. var xMinVal, xMaxVal, yMinVal, yMaxVal float64
  319. var isNotFirst bool
  320. for _, varietyId := range config.VarietyList {
  321. xEdbInfoId, ok1 := xVarietyEdbMap[varietyId]
  322. if !ok1 {
  323. continue
  324. }
  325. yEdbInfoId, ok2 := yVarietyEdbMap[varietyId]
  326. if !ok2 {
  327. continue
  328. }
  329. variety, ok := varietyMap[varietyId]
  330. if !ok {
  331. continue
  332. }
  333. coordinatePointList := make([]CoordinatePoint, 0)
  334. for dateIndex, _ := range config.DateConfigList {
  335. key1 := fmt.Sprint(dateIndex, "_", xEdbInfoId)
  336. xVal, ok1 := dataMap[key1]
  337. if !ok1 {
  338. continue
  339. }
  340. key2 := fmt.Sprint(dateIndex, "_", yEdbInfoId)
  341. yVal, ok2 := dataMap[key2]
  342. if !ok2 {
  343. continue
  344. }
  345. if !isNotFirst {
  346. xMinVal = xVal
  347. xMaxVal = xVal
  348. yMinVal = yVal
  349. yMaxVal = yVal
  350. isNotFirst = true
  351. } else {
  352. if xVal < xMinVal {
  353. xMinVal = xVal
  354. }
  355. if xVal > xMaxVal {
  356. xMaxVal = xVal
  357. }
  358. if yVal < yMinVal {
  359. yMinVal = yVal
  360. }
  361. if yVal > yMaxVal {
  362. yMaxVal = yVal
  363. }
  364. }
  365. coordinatePointList = append(coordinatePointList, CoordinatePoint{
  366. X: xVal,
  367. Y: yVal,
  368. XEdbInfoId: xEdbInfoId,
  369. YEdbInfoId: yEdbInfoId,
  370. XDate: dateMap[key1],
  371. YDate: dateMap[key2],
  372. DateType: dateTypeMap[dateIndex], // 日期类型
  373. DaysAgo: daysAgoMap[dateIndex], // N天前的N值
  374. })
  375. }
  376. dataList = append(dataList, SectionScatterSeriesItemResp{
  377. Name: variety.ChartVarietyName,
  378. NameEn: variety.ChartVarietyNameEn,
  379. Color: "",
  380. CoordinatePointData: coordinatePointList,
  381. })
  382. }
  383. // 处理颜色
  384. colorMap := utils.GetColorMap()
  385. for k, _ := range dataList {
  386. if c, ok1 := colorMap[k]; ok1 {
  387. dataList[k].Color = c
  388. }
  389. }
  390. dataResp = ChartInfoResp{
  391. XName: xTagInfo.ChartTagName + "百分位",
  392. XNameEn: xTagInfo.ChartTagNameEn,
  393. XUnitName: "%",
  394. XUnitNameEn: "%",
  395. YName: yTagInfo.ChartTagName + "百分位",
  396. YNameEn: yTagInfo.ChartTagNameEn,
  397. YUnitName: "%",
  398. YUnitNameEn: "%",
  399. XMinValue: fmt.Sprint(xMinVal),
  400. XMaxValue: fmt.Sprint(xMaxVal),
  401. YMinValue: fmt.Sprint(yMinVal),
  402. YMaxValue: fmt.Sprint(yMaxVal),
  403. DataList: dataList,
  404. }
  405. // 去除返回指标中的数据信息,避免没必要的数据传输
  406. for k, _ := range edbList {
  407. edbList[k].DataList = nil
  408. }
  409. return
  410. }
  411. // GetXYEdbIdList
  412. // @Description: 根据标签id和品种获取指标列表信息
  413. // @author: Roc
  414. // @datetime 2023-11-27 14:31:23
  415. // @param tagX int
  416. // @param tagY int
  417. // @param varietyList []int
  418. // @return xVarietyEdbMap map[int]int
  419. // @return yVarietyEdbMap map[int]int
  420. // @return edbInfoIdList []int
  421. // @return errMsg string
  422. // @return err error
  423. func GetXYEdbIdList(tagX, tagY int, varietyList []int) (xVarietyEdbMap, yVarietyEdbMap map[int]int, edbInfoIdList []int, err error) {
  424. edbInfoIdList = make([]int, 0)
  425. xVarietyEdbMap = make(map[int]int)
  426. yVarietyEdbMap = make(map[int]int)
  427. xList, err := cross_varietyModel.GetChartTagVarietyListByTagAndVariety(tagX, varietyList)
  428. if err != nil {
  429. err = errors.New("获取X轴的品种指标配置信息失败,Err:" + err.Error())
  430. return
  431. }
  432. yList, err := cross_varietyModel.GetChartTagVarietyListByTagAndVariety(tagY, varietyList)
  433. if err != nil {
  434. err = errors.New("获取Y轴的品种指标配置信息失败,Err:" + err.Error())
  435. return
  436. }
  437. baseVarietyIdMap := make(map[int]int)
  438. for _, v := range xList {
  439. baseVarietyIdMap[v.ChartVarietyId] = v.ChartVarietyId
  440. }
  441. // 两个标签里面的品种并集
  442. needVarietyIdMap := make(map[int]int)
  443. for _, v := range yList {
  444. if val, ok := baseVarietyIdMap[v.ChartVarietyId]; ok {
  445. // 如果在 map2 中存在相同的键,则将键和值添加到结果中
  446. needVarietyIdMap[v.ChartVarietyId] = val
  447. }
  448. }
  449. for _, v := range xList {
  450. if _, ok := needVarietyIdMap[v.ChartVarietyId]; ok {
  451. xVarietyEdbMap[v.ChartVarietyId] = v.EdbInfoId
  452. edbInfoIdList = append(edbInfoIdList, v.EdbInfoId)
  453. }
  454. }
  455. for _, v := range yList {
  456. if _, ok := needVarietyIdMap[v.ChartVarietyId]; ok {
  457. yVarietyEdbMap[v.ChartVarietyId] = v.EdbInfoId
  458. edbInfoIdList = append(edbInfoIdList, v.EdbInfoId)
  459. }
  460. }
  461. return
  462. }