Parcourir la source

Merge branch 'master' into feature/eta1.9.6_chart_series

# Conflicts:
#	models/chart_info.go
#	services/chart_info_show.go
xyxie il y a 7 mois
Parent
commit
59561637c0

+ 154 - 8
models/chart_info.go

@@ -1103,10 +1103,118 @@ type PreviewChartInfoReq struct {
 }
 
 type SeasonExtraItem struct {
-	ChartLegend []SeasonChartLegend `description:"自定义的图例名称"`
-	XStartDate  string              `description:"横坐标显示的起始日"`
-	XEndDate    string              `description:"横坐标显示的截止日"`
-	JumpYear    int                 `description:"横坐标日期是否跨年,1跨年,0不跨年"`
+	ChartLegend                 []SeasonChartLegend         `description:"自定义的图例名称"`
+	XStartDate                  string                      `description:"横坐标显示的起始日"`
+	XEndDate                    string                      `description:"横坐标显示的截止日"`
+	JumpYear                    int                         `description:"横坐标日期是否跨年,1跨年,0不跨年"`
+	RightAxis                   SeasonRightAxis             `description:"自定义右轴指标"`
+	MaxMinLimits                MaxMinLimits                `description:"自定义同期上下限"`
+	SamePeriodAverage           SamePeriodAverage           `description:"自定义同期均线"`
+	SamePeriodStandardDeviation SamePeriodStandardDeviation `description:"自定义同期标准差"`
+}
+
+// 自定义右轴指标
+type SeasonRightAxis struct {
+	IndicatorType int     `description:"右轴指标类型 1:左轴指标同比,2:指标库,3:预测指标 "`
+	Style         string  `description:"生成样式"`
+	Shape         string  `description:"形状"`
+	ChartColor    string  `description:"图表颜色"`
+	Size          int     `description:"大小"`
+	Legend        string  `description:"图例名称"`
+	NumFormat     int     `description:"数值格式 1:百分比 2:小数"`
+	IsConnected   int     `description:"是否连接 0不连接 1连接"`
+	LineColor     string  `description:"线条颜色"`
+	LineWidth     float64 `description:"线条宽度"`
+	LineStyle     string  `description:"线条样式"`
+	IsShow        bool    `description:"是否显示"`
+	IsAdd         bool    `description:"是否添加"`
+}
+
+// 自定义同期上下限
+type MaxMinLimits struct {
+	Color  string `description:"颜色"`
+	Year   int    `description:"上下限取值范围"`
+	Legend string `description:"图例名称"`
+	IsShow bool   `description:"是否显示"`
+	IsAdd  bool   `description:"是否添加"`
+}
+
+// 自定义同期均线
+type SamePeriodAverage struct {
+	Color     string  `description:"颜色"`
+	Year      int     `description:"均线取值范围"`
+	Legend    string  `description:"图例名称"`
+	LineType  string  `description:"线型"`
+	LineWidth float64 `description:"线宽"`
+	IsShow    bool    `description:"是否显示"`
+	IsAdd     bool    `description:"是否添加"`
+}
+
+// 自定义同期均线
+type SamePeriodAverageResp struct {
+	Color     string                   `description:"颜色"`
+	Year      int                      `description:"均线取值范围"`
+	Legend    string                   `description:"图例名称"`
+	LineType  string                   `description:"线型"`
+	LineWidth float64                  `description:"线宽"`
+	IsShow    bool                     `description:"是否显示"`
+	List      []*SamePeriodAverageData `description:"自定义均线列表"`
+	IsAdd     bool                     `description:"是否添加"`
+}
+
+type SamePeriodAverageData struct {
+	DataTime      string  `description:"数据日期"`
+	DataTimestamp int64   `description:"数据日期时间戳"`
+	Value         float64 `description:"均值"`
+}
+
+// 自定义同期标准差
+type SamePeriodStandardDeviation struct {
+	Color    string  `description:"颜色"`
+	Year     int     `description:"标准差取值范围"`
+	Legend   string  `description:"图例名称"`
+	Multiple float64 `description:"标准差倍数"`
+	IsShow   bool    `description:"是否显示"`
+	IsAdd    bool    `description:"是否添加"`
+}
+
+type SamePeriodStandardDeviationResp struct {
+	Color    string              `description:"颜色"`
+	Year     int                 `description:"标准差取值范围"`
+	Legend   string              `description:"图例名称"`
+	Multiple float64             `description:"标准差倍数"`
+	IsShow   bool                `description:"是否显示"`
+	List     []*MaxMinLimitsData `description:"自定义标准差列表"`
+	IsAdd    bool                `description:"是否添加"`
+}
+
+type SeasonChartResp struct {
+	MaxMinLimits                MaxMinLimitsResp                `description:"自定义上下限"`
+	SamePeriodAverage           SamePeriodAverageResp           `description:"自定义同期均线"`
+	SamePeriodStandardDeviation SamePeriodStandardDeviationResp `description:"自定义同期标准差线"`
+	RightAxis                   SeasonRightAxisResp             `description:"自定义右轴指标"`
+}
+
+// 自定义右轴指标
+type SeasonRightAxisResp struct {
+	SeasonRightAxis
+	EdbInfoList []*ChartEdbInfoMapping
+}
+
+type MaxMinLimitsResp struct {
+	List   []*MaxMinLimitsData `description:"自定义上下限列表"`
+	Color  string              `description:"颜色"`
+	Year   int                 `description:"上下限取值范围"`
+	Legend string              `description:"图例名称"`
+	IsShow bool                `description:"是否显示"`
+	IsAdd  bool                `description:"是否添加"`
+}
+
+type MaxMinLimitsData struct {
+	DataTime      string  `description:"数据日期"`
+	DataTimestamp int64   `description:"数据日期时间戳"`
+	MaxValue      float64 `description:"最大值"`
+	MinValue      float64 `description:"最小值"`
 }
 
 type SeasonChartLegend struct {
@@ -2109,11 +2217,49 @@ type ChartSectionDateConfItem struct {
 	DateType       int    `description:"日期类型:0 指标日期,1系统日期"`
 	DateConfName   string `description:"引用日期名称"` // 引用日期名称不能重复
 	DateConfNameEn string `description:"引用日期英文名称"`
-	DateChange     []*ChartSectionDateChange
+	DateChange     []*EdbDateConfDateChange
+}
+
+type MarkersLine struct {
+	Axis             int             `json:"axis" description:"1左轴 2右轴 3横轴"`
+	AxisName         string          `json:"axisName" description:"轴的名称,例如'左轴'"`
+	MarkerType       string          `json:"markerType" description:"标识线或标识区"`
+	MarkLineType     int             `json:"markLineType" description:"1:固定 2:指标计算"`
+	Value            string          `json:"value" description:"连线指向的数值,例如'4000'"`
+	FromValue        string          `json:"fromValue" description:"连线的起始点,可以为空"`
+	ToValue          string          `json:"toValue" description:"连线的结束点,可以为空"`
+	LineWidth        float64         `json:"lineWidth" description:"连线的宽度"`
+	DashStyle        string          `json:"dashStyle" description:"连线的虚线样式,例如'ShortDashDot'"`
+	Color            string          `json:"color" description:"连线的颜色"`
+	Text             string          `json:"text" description:"连线旁边显示的文本"`
+	TextPosition     string          `json:"textPosition" description:"文本的显示位置,例如'bottom'"`
+	TextColor        string          `json:"textColor" description:"文本颜色"`
+	TextFontSize     int             `json:"textFontSize" description:"文本的字号大小"`
+	IsShow           bool            `json:"isShow" description:"是否显示连线及文本"`
+	EdbType          int             `json:"edbType" description:"指标类型 0图中第一个指标 1其他指标 前端回显用"`
+	EdbInfoId        int             `json:"edbInfoId" description:"指标id"`
+	Calculation      int             `json:"calculation" description:"计算方式 1区间均值 2区间均值加N倍标准差 3区间个数分位 4区间数值分位"`
+	CalculationValue int             `json:"calculationValue" description:"计算方式对应的值 2就是几倍标准差 3就是分位值 4就是数值值·"`
+	TimeIntervalType int             `json:"timeInterval" description:"时间区间 0跟随图表 1自定义"`
+	StartDate        MarkersLineTime `json:"startTime" description:"开始时间"`
+	EndDate          MarkersLineTime `json:"endTime" description:"结束时间"`
+}
+
+type MarkersLineTime struct {
+	TimeType int               `json:"timeType" description:"时间类型 1固定 2动态 3至今(仅结束时间)"`
+	Date     string            `json:"date" description:"日期"`
+	Conf     EdbDateChangeConf `json:"conf" description:"动态时间配置"`
+}
+
+// EdbDateExtraConf
+// @Description: 导入指标日期前移和日期变换
+type EdbDateChangeConf struct {
+	MoveForward int `description:"前移的期数"`
+	BaseDate    int `description:"基准日期 0系统日期 1指标最新日期"`
+	DateChange  []*EdbDateConfDateChange
 }
 
-// 截面组合图引用日期配置
-type ChartSectionDateChange struct {
+type EdbDateConfDateChange struct {
 	Year         int
 	Month        int
 	Day          int
@@ -2174,7 +2320,7 @@ type ChartSectionCombineDataResp struct {
 // 系列里的指标日期配置
 type ChartSectionSeriesDateConfItem struct {
 	MoveForward int `description:"前移的期数"`
-	DateChange  []*ChartSectionDateChange
+	DateChange  []*EdbDateConfDateChange
 }
 
 // PreviewSectionCombineChartReq 预览截面组合图的请求

+ 10 - 10
models/chart_theme/request/theme.go

@@ -31,7 +31,7 @@ type SetDefaultThemeReq struct {
 
 type LineOptions struct {
 	DashStyle string  `json:"dashStyle"`
-	LineWidth int     `json:"lineWidth"`
+	LineWidth float64 `json:"lineWidth"`
 	LineType  string  `json:"lineType"`
 	Radius    float64 `json:"radius"`
 }
@@ -63,13 +63,13 @@ type NewLineOptions struct {
 }
 
 type LineStyleOptions struct {
-	DashStyle string `json:"dashStyle"`
-	Color     string `json:"color"`
-	LineWidth int    `json:"lineWidth"`
-	LineType  string `json:"lineType"`
-	Radius    int    `json:"radius"`
-	DataMark  string `json:"dataMark"`
-	MarkType  string `json:"markType"`
-	MarkSize  int    `json:"markSize"`
-	MarkColor string `json:"markColor"`
+	DashStyle string  `json:"dashStyle"`
+	Color     string  `json:"color"`
+	LineWidth float64 `json:"lineWidth"`
+	LineType  string  `json:"lineType"`
+	Radius    int     `json:"radius"`
+	DataMark  string  `json:"dataMark"`
+	MarkType  string  `json:"markType"`
+	MarkSize  int     `json:"markSize"`
+	MarkColor string  `json:"markColor"`
 }

+ 2 - 2
services/chart_extra_config.go

@@ -323,7 +323,7 @@ func GetChartSectionCombineData(chartInfo *models.ChartInfo, mappingList []*mode
 }
 
 // GetChartSectionSeriesDateByDateChange 获取日期变换后的日期edbInfoId 1指标日期,2 系统日期
-func GetChartSectionSeriesDateByDateChange(edbInfoId int, dataList []*models.EdbDataList, dateChange []*models.ChartSectionDateChange, moveForward int) (newDate string, err error) {
+func GetChartSectionSeriesDateByDateChange(edbInfoId int, dataList []*models.EdbDataList, dateChange []*models.EdbDateConfDateChange, moveForward int) (newDate string, err error) {
 	if edbInfoId > 0 { //指标日期
 		newDate = GetEdbDateByMoveForward(moveForward, dataList)
 	} else {
@@ -359,7 +359,7 @@ func GetEdbDateByMoveForwardByDateList(moveForward int, dateList []string) (date
 }
 
 // HandleChartSectionSeriesDateChange 处理日期变换
-func HandleChartSectionSeriesDateChange(date string, dateChange []*models.ChartSectionDateChange) (newDate string, err error) {
+func HandleChartSectionSeriesDateChange(date string, dateChange []*models.EdbDateConfDateChange) (newDate string, err error) {
 	newDate = date
 	if newDate != "" {
 		if len(dateChange) > 0 {

+ 547 - 1
services/chart_info_show.go

@@ -273,6 +273,19 @@ func GetChartEdbData(chartInfoId, chartType int, calendar, startDate, endDate st
 
 	// 特殊图形数据处理
 	switch chartType {
+	case 2: // 季节性图
+		if seasonExtraConfig != "" {
+			// 季节性图计算不管图上数据时间,拿所有数据
+			_, tempEdbList, e := getEdbDataMapList(chartInfoId, chartType, calendar, "1990-01-01", "", mappingList, seasonExtraConfig)
+			if e != nil {
+				err = e
+				return
+			}
+			dataResp, err = SeasonChartData(tempEdbList, seasonExtraConfig)
+		} else {
+			// 兼容无配置的老图
+			dataResp = new(models.SeasonChartResp)
+		}
 	case 6: //时序组合图
 		//判断是否堆积
 		timeConf := extraConfig.(models.ChartTimeCombineExtraConf)
@@ -423,6 +436,9 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 		var diffSeconds int64
 		if chartType == 2 { //季节性图
 			startDateReal = startDate
+			if len(mappingList) > 1 {
+				item.IsAxis = v.IsAxis
+			}
 		} else {
 			if v.EdbInfoType == 0 && v.LeadUnit != "" && v.LeadValue > 0 { //领先指标
 				var startTimeRealTemp time.Time
@@ -520,7 +536,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 			}
 		}
 
-		if chartType == 2 {
+		if chartType == 2 && item.IsAxis == 1 {
 			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
 			if tmpErr != nil {
 				//item.DataList = dataList
@@ -558,6 +574,29 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 				item.DataList = quarterDataList
 			}
 
+		} else if chartType == 2 && item.IsAxis == 0 {
+			// 右轴数据处理,只要最新一年
+			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
+			if tmpErr != nil {
+				//item.DataList = dataList
+				item.IsNullData = true
+				edbList = append(edbList, item)
+				continue
+				err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
+				return
+			}
+			newDataList := make([]*models.EdbDataList, 0)
+			for _, v := range dataList {
+				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
+				if e != nil {
+					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
+					return
+				}
+				if dataTime.Year() == latestDate.Year() {
+					newDataList = append(newDataList, v)
+				}
+			}
+			item.DataList = newDataList
 		} else if chartType == 7 || chartType == utils.CHART_TYPE_RADAR { //柱方图
 			//item.DataList = dataList
 		} else {
@@ -1775,3 +1814,510 @@ func RadarChartData(mappingList []*models.ChartEdbInfoMapping, edbDataListMap ma
 	}
 	return
 }
+
+// SeasonChartData 季节性图的数据处理
+func SeasonChartData(dataList []*models.ChartEdbInfoMapping, seasonExtraConfig string) (dataResp models.SeasonChartResp, err error) {
+	var seasonConfig models.SeasonExtraItem
+	err = json.Unmarshal([]byte(seasonExtraConfig), &seasonConfig)
+	if err != nil {
+		err = errors.New("季节性图配置异常, Err:" + err.Error())
+		return
+	}
+
+	for _, mappingItem := range dataList {
+		if mappingItem.IsAxis == 0 {
+			continue
+		}
+		quarterDataList, ok := mappingItem.DataList.(models.QuarterDataList)
+		if !ok {
+			continue
+		}
+		// 上下限区间
+		if seasonConfig.MaxMinLimits.Year > 0 {
+			yearRange := time.Now().Year() - seasonConfig.MaxMinLimits.Year
+			startYear := time.Now().AddDate(-yearRange, 0, 0).Year()
+			dataResp.MaxMinLimits.List = make([]*models.MaxMinLimitsData, 0)
+			dataTimeMap := make(map[time.Time]time.Time)
+			dataTimeList := make([]string, 0)
+			maxValueMap := make(map[time.Time]float64)
+			minValueMap := make(map[time.Time]float64)
+
+			maxMinDataList := make([]*models.MaxMinLimitsData, 0)
+			var startDateStr string
+			var endDateStr string
+			var newDate time.Time
+
+			startDateStr = fmt.Sprintf("%d-%s", time.Now().Year(), seasonConfig.XStartDate)
+			endDateStr = fmt.Sprintf("%d-%s", time.Now().Year(), seasonConfig.XEndDate)
+			startDate, e := time.Parse(utils.FormatDate, startDateStr)
+			if e != nil {
+				err = e
+				return
+			}
+			endDate, e := time.Parse(utils.FormatDate, endDateStr)
+			if e != nil {
+				err = e
+				return
+			}
+			// 日度 周度插值
+			for _, v := range quarterDataList {
+				if mappingItem.Frequency == "日度" || mappingItem.Frequency == "周度" {
+					handleDataMap := make(map[string]float64)
+					dataTimeList, _, err = HandleDataByLinearRegressionToList(v.DataList, handleDataMap)
+					if err != nil {
+						err = errors.New("插值处理数据异常, Err:" + err.Error())
+						return
+					}
+					// 不包含当年
+					if v.ChartLegend == strconv.Itoa(time.Now().Year()) {
+						continue
+					}
+					for _, date := range dataTimeList {
+						dateTime, e := time.Parse(utils.FormatDate, date)
+						if e != nil {
+							err = errors.New("时间格式化异常, Err:" + e.Error())
+							return
+						}
+						if dateTime.Year() < startYear {
+							continue
+						}
+						// 不包含2月29号
+						if dateTime.Month() == 2 && dateTime.Day() == 29 {
+							continue
+						}
+						newDate = dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+						if seasonConfig.JumpYear == 1 {
+							if startDate.After(endDate) {
+								// 如果跨年且不到一年
+								// 全年截取一部分
+								if newDate.Before(startDate.AddDate(0, 0, 1)) && newDate.After(endDate) {
+									continue
+								}
+								if newDate.After(startDate.AddDate(0, 0, -1)) {
+									// 减一年
+									newDate = dateTime.AddDate(time.Now().Year()-dateTime.Year()-1, 0, 0)
+								}
+								// 处理上下限列表
+								if value, ok := maxValueMap[newDate]; ok {
+									if value < handleDataMap[date] {
+										maxValueMap[newDate] = handleDataMap[date]
+									}
+								} else {
+									maxValueMap[newDate] = handleDataMap[date]
+								}
+
+								if value, ok := minValueMap[newDate]; ok {
+									if value > handleDataMap[date] {
+										minValueMap[newDate] = handleDataMap[date]
+									}
+								} else {
+									minValueMap[newDate] = handleDataMap[date]
+								}
+
+								dataTimeMap[newDate] = newDate
+							} else {
+								// 如果跨年且大于等于一年
+								// double后截取
+								if newDate.After(startDate) {
+									// 处理上下限列表
+									if value, ok := maxValueMap[newDate]; ok {
+										if value < handleDataMap[date] {
+											maxValueMap[newDate] = handleDataMap[date]
+										}
+									} else {
+										maxValueMap[newDate] = handleDataMap[date]
+									}
+
+									if value, ok := minValueMap[newDate]; ok {
+										if value > handleDataMap[date] {
+											minValueMap[newDate] = handleDataMap[date]
+										}
+									} else {
+										minValueMap[newDate] = handleDataMap[date]
+									}
+
+									dataTimeMap[newDate] = newDate
+								}
+
+								newDate = dateTime.AddDate(time.Now().Year()-dateTime.Year()+1, 0, 0)
+								newEndDate := endDate.AddDate(1, 0, 0)
+								if newDate.Before(newEndDate) {
+									// 处理上下限列表
+									if value, ok := maxValueMap[newDate]; ok {
+										if value < handleDataMap[date] {
+											maxValueMap[newDate] = handleDataMap[date]
+										}
+									} else {
+										maxValueMap[newDate] = handleDataMap[date]
+									}
+
+									if value, ok := minValueMap[newDate]; ok {
+										if value > handleDataMap[date] {
+											minValueMap[newDate] = handleDataMap[date]
+										}
+									} else {
+										minValueMap[newDate] = handleDataMap[date]
+									}
+
+									dataTimeMap[newDate] = newDate
+								}
+
+							}
+						} else {
+							// 如果不跨年 正常获取
+							// 获取当前日期所在区间
+							// 处理上下限列表
+							if value, ok := maxValueMap[newDate]; ok {
+								if value < handleDataMap[date] {
+									maxValueMap[newDate] = handleDataMap[date]
+								}
+							} else {
+								maxValueMap[newDate] = handleDataMap[date]
+							}
+
+							if value, ok := minValueMap[newDate]; ok {
+								if value > handleDataMap[date] {
+									minValueMap[newDate] = handleDataMap[date]
+								}
+							} else {
+								minValueMap[newDate] = handleDataMap[date]
+							}
+
+							dataTimeMap[newDate] = newDate
+						}
+					}
+				} else {
+					// 旬度、月度、季度、半年度 不插值,需要先把日期列表和数据map取出来
+					for _, vv := range v.DataList {
+						dateTime, e := time.Parse(utils.FormatDate, vv.DataTime)
+						if e != nil {
+							err = errors.New("时间格式化异常, Err:" + e.Error())
+							return
+						}
+
+						// 不包含当年
+						if v.ChartLegend == strconv.Itoa(time.Now().Year()) {
+							continue
+						}
+
+						if dateTime.Year() < startYear {
+							continue
+						}
+						// 不包含2月29号
+						if dateTime.Month() == 2 && dateTime.Day() == 29 {
+							continue
+						}
+						newDate = dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+						if seasonConfig.JumpYear == 1 {
+							if startDate.After(endDate) {
+								// 如果跨年且不到一年
+								// 全年截取一部分
+								if newDate.Before(startDate.AddDate(0, 0, 1)) && newDate.After(endDate) {
+									continue
+								}
+								if newDate.After(startDate.AddDate(0, 0, -1)) {
+									// 减一年
+									newDate = dateTime.AddDate(time.Now().Year()-dateTime.Year()-1, 0, 0)
+								}
+								// 处理上下限列表
+								if value, ok := maxValueMap[newDate]; ok {
+									if value < vv.Value {
+										maxValueMap[newDate] = vv.Value
+									}
+								} else {
+									maxValueMap[newDate] = vv.Value
+								}
+
+								if value, ok := minValueMap[newDate]; ok {
+									if value > vv.Value {
+										minValueMap[newDate] = vv.Value
+									}
+								} else {
+									minValueMap[newDate] = vv.Value
+								}
+
+								dataTimeMap[newDate] = newDate
+							} else {
+								// 如果跨年且大于等于一年
+								// double后截取
+								if newDate.After(startDate) {
+									// 处理上下限列表
+									if value, ok := maxValueMap[newDate]; ok {
+										if value < vv.Value {
+											maxValueMap[newDate] = vv.Value
+										}
+									} else {
+										maxValueMap[newDate] = vv.Value
+									}
+
+									if value, ok := minValueMap[newDate]; ok {
+										if value > vv.Value {
+											minValueMap[newDate] = vv.Value
+										}
+									} else {
+										minValueMap[newDate] = vv.Value
+									}
+
+									dataTimeMap[newDate] = newDate
+								}
+
+								newDate = dateTime.AddDate(time.Now().Year()-dateTime.Year()+1, 0, 0)
+								newEndDate := endDate.AddDate(1, 0, 0)
+								if newDate.Before(newEndDate) {
+									// 处理上下限列表
+									if value, ok := maxValueMap[newDate]; ok {
+										if value < vv.Value {
+											maxValueMap[newDate] = vv.Value
+										}
+									} else {
+										maxValueMap[newDate] = vv.Value
+									}
+
+									if value, ok := minValueMap[newDate]; ok {
+										if value > vv.Value {
+											minValueMap[newDate] = vv.Value
+										}
+									} else {
+										minValueMap[newDate] = vv.Value
+									}
+
+									dataTimeMap[newDate] = newDate
+								}
+
+							}
+						} else {
+							// 如果不跨年 正常获取
+							// 获取当前日期所在区间
+							// 处理上下限列表
+							if value, ok := maxValueMap[newDate]; ok {
+								if value < vv.Value {
+									maxValueMap[newDate] = vv.Value
+								}
+							} else {
+								maxValueMap[newDate] = vv.Value
+							}
+
+							if value, ok := minValueMap[newDate]; ok {
+								if value > vv.Value {
+									minValueMap[newDate] = vv.Value
+								}
+							} else {
+								minValueMap[newDate] = vv.Value
+							}
+
+							dataTimeMap[newDate] = newDate
+						}
+
+					}
+				}
+			}
+
+			for _, v := range dataTimeMap {
+				maxMinItem := &models.MaxMinLimitsData{}
+				if maxValue, ok := maxValueMap[v]; ok {
+					maxMinItem.MaxValue = maxValue
+				} else {
+					maxMinItem.MaxValue = minValueMap[v]
+				}
+
+				if minValue, ok := minValueMap[v]; ok {
+					maxMinItem.MinValue = minValue
+				} else {
+					maxMinItem.MinValue = maxValueMap[v]
+				}
+
+				maxMinItem.DataTime = v.Format(utils.FormatDate)
+				maxMinItem.DataTimestamp = v.UnixNano() / 1e6
+
+				maxMinDataList = append(maxMinDataList, maxMinItem)
+
+			}
+
+			// 排序
+			sort.Slice(maxMinDataList, func(i, j int) bool {
+				return maxMinDataList[i].DataTime < maxMinDataList[j].DataTime
+			})
+
+			dataResp.MaxMinLimits.List = maxMinDataList
+			dataResp.MaxMinLimits.Color = seasonConfig.MaxMinLimits.Color
+			dataResp.MaxMinLimits.Legend = seasonConfig.MaxMinLimits.Legend
+			dataResp.MaxMinLimits.IsShow = seasonConfig.MaxMinLimits.IsShow
+			dataResp.MaxMinLimits.IsAdd = seasonConfig.MaxMinLimits.IsAdd
+			dataResp.MaxMinLimits.Year = seasonConfig.MaxMinLimits.Year
+		}
+
+		// 自定义同期均线
+		if seasonConfig.SamePeriodAverage.Year > 0 {
+			handleDataMap := make(map[string]float64)
+			dataTimeMap := make(map[time.Time]time.Time)
+			dataTimeList := make([]string, 0)
+			valueMap := make(map[time.Time]float64)
+			averageDataList := make([]*models.SamePeriodAverageData, 0)
+			for i := len(quarterDataList) - 1; i > len(quarterDataList)-seasonConfig.SamePeriodAverage.Year && i > 0; i-- {
+				// 插值成日度
+				dataTimeList, _, err = HandleDataByLinearRegressionToList(quarterDataList[i].DataList, handleDataMap)
+				if err != nil {
+					err = errors.New("插值处理数据异常, Err:" + err.Error())
+					return
+				}
+				for _, date := range dataTimeList {
+					dateTime, e := time.Parse(utils.FormatDate, date)
+					if e != nil {
+						err = errors.New("时间格式化异常, Err:" + e.Error())
+						return
+					}
+					yearRange := time.Now().Year() - seasonConfig.SamePeriodAverage.Year
+					startYear := time.Now().AddDate(-yearRange, 0, 0).Year()
+					if dateTime.Year() < startYear {
+						continue
+					}
+					// 不包含2月29号
+					if dateTime.Month() == 2 && dateTime.Day() == 29 {
+						continue
+					}
+					newDate := dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+					// 处理均值
+					if value, ok := valueMap[newDate]; ok {
+						valueMap[newDate] = (handleDataMap[date] + value) / 2
+					} else {
+						valueMap[newDate] = handleDataMap[date]
+					}
+
+					dataTimeMap[newDate] = newDate
+				}
+			}
+
+			for _, v := range dataTimeMap {
+				averageItem := &models.SamePeriodAverageData{}
+				if value, ok := valueMap[v]; ok {
+					averageItem.Value = value
+				}
+				averageItem.DataTime = v.Format(utils.FormatDate)
+				averageItem.DataTimestamp = v.UnixNano() / 1e6
+
+				averageDataList = append(averageDataList, averageItem)
+
+			}
+
+			// 排序
+			sort.Slice(averageDataList, func(i, j int) bool {
+				return averageDataList[i].DataTime < averageDataList[j].DataTime
+			})
+
+			dataResp.SamePeriodAverage.List = averageDataList
+			dataResp.SamePeriodAverage.Year = seasonConfig.SamePeriodAverage.Year
+			dataResp.SamePeriodAverage.Color = seasonConfig.SamePeriodAverage.Color
+			dataResp.SamePeriodAverage.Legend = seasonConfig.SamePeriodAverage.Legend
+			dataResp.SamePeriodAverage.IsShow = seasonConfig.SamePeriodAverage.IsShow
+			dataResp.SamePeriodAverage.LineType = seasonConfig.SamePeriodAverage.LineType
+			dataResp.SamePeriodAverage.LineWidth = seasonConfig.SamePeriodAverage.LineWidth
+			dataResp.SamePeriodAverage.IsAdd = seasonConfig.SamePeriodAverage.IsAdd
+
+		}
+
+		// 自定义同期标准差
+		if seasonConfig.SamePeriodStandardDeviation.Year > 1 && seasonConfig.SamePeriodStandardDeviation.Multiple > 0 {
+			yearRange := time.Now().Year() - seasonConfig.SamePeriodAverage.Year
+			startYear := time.Now().AddDate(-yearRange, 0, 0).Year()
+
+			// 先算均值,再算标准差
+			handleDataMap := make(map[string]float64)
+			dataTimeMap := make(map[time.Time]time.Time)
+			dataTimeValueMap := make(map[time.Time][]float64)
+			dataTimeList := make([]string, 0)
+			valueMap := make(map[time.Time]float64)
+
+			samePeriodStandardDeviationList := make([]*models.MaxMinLimitsData, 0)
+			for i := len(quarterDataList) - 1; i > len(quarterDataList)-seasonConfig.SamePeriodAverage.Year-1 && i > 0; i-- {
+				// 插值成日度
+				dataTimeList, _, err = HandleDataByLinearRegressionToListV2(quarterDataList[i].DataList, handleDataMap)
+				if err != nil {
+					err = errors.New("插值处理数据异常, Err:" + err.Error())
+					return
+				}
+				for _, date := range dataTimeList {
+					dateTime, e := time.Parse(utils.FormatDate, date)
+					if e != nil {
+						err = errors.New("时间格式化异常, Err:" + e.Error())
+						return
+					}
+					if dateTime.Year() < startYear {
+						continue
+					}
+					// 不包含2月29号
+					if dateTime.Month() == 2 && dateTime.Day() == 29 {
+						continue
+					}
+					newDate := dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+					// 处理均值
+					if value, ok := valueMap[newDate]; ok {
+						valueMap[newDate] = (handleDataMap[date] + value) / 2
+					} else {
+						valueMap[newDate] = handleDataMap[date]
+					}
+
+					dataTimeMap[newDate] = newDate
+					valueList := dataTimeValueMap[newDate]
+					valueList = append(valueList, handleDataMap[date])
+					dataTimeValueMap[newDate] = valueList
+				}
+			}
+
+			for _, v := range dataTimeMap {
+				valueList := dataTimeValueMap[v]
+				stdev := utils.CalculateStandardDeviation(valueList)
+				stdev, _ = decimal.NewFromFloat(stdev).Round(4).Float64()
+
+				maxMinItem := &models.MaxMinLimitsData{}
+
+				if value, ok := valueMap[v]; ok {
+					maxMinItem.MaxValue = value + stdev*seasonConfig.SamePeriodStandardDeviation.Multiple
+					maxMinItem.MinValue = value - stdev*seasonConfig.SamePeriodStandardDeviation.Multiple
+				}
+
+				maxMinItem.DataTime = v.Format(utils.FormatDate)
+				maxMinItem.DataTimestamp = v.UnixNano() / 1e6
+
+				samePeriodStandardDeviationList = append(samePeriodStandardDeviationList, maxMinItem)
+
+			}
+
+			// 排序
+			sort.Slice(samePeriodStandardDeviationList, func(i, j int) bool {
+				return samePeriodStandardDeviationList[i].DataTime < samePeriodStandardDeviationList[j].DataTime
+			})
+
+			dataResp.SamePeriodStandardDeviation.List = samePeriodStandardDeviationList
+			dataResp.SamePeriodStandardDeviation.Color = seasonConfig.SamePeriodStandardDeviation.Color
+			dataResp.SamePeriodStandardDeviation.Legend = seasonConfig.SamePeriodStandardDeviation.Legend
+			dataResp.SamePeriodStandardDeviation.IsShow = seasonConfig.SamePeriodStandardDeviation.IsShow
+			dataResp.SamePeriodStandardDeviation.Multiple = seasonConfig.SamePeriodStandardDeviation.Multiple
+			dataResp.SamePeriodStandardDeviation.Year = seasonConfig.SamePeriodStandardDeviation.Year
+			dataResp.SamePeriodStandardDeviation.IsAdd = seasonConfig.SamePeriodStandardDeviation.IsAdd
+		}
+
+		// 自定义右轴
+		if seasonConfig.RightAxis.IndicatorType != 0 {
+			if seasonConfig.RightAxis.IndicatorType == 1 {
+				startTime, _ := time.Parse(utils.FormatDate, mappingItem.StartDate)
+				for i := len(quarterDataList) - 1; i > len(quarterDataList)-2 && i > 0; i-- {
+					var rightMappingItem models.ChartEdbInfoMapping
+					rightMappingItem = *mappingItem
+					// 计算同比值
+					tbzDataList, minValue, maxValue, e := GetEdbDataTbzForSeason(mappingItem.Frequency, quarterDataList[i].DataList, startTime)
+					if e != nil {
+						err = errors.New("计算同比值失败, Err:" + e.Error())
+						return
+					}
+					rightMappingItem.DataList = tbzDataList
+					rightMappingItem.MaxData = maxValue
+					rightMappingItem.MinData = minValue
+					dataResp.RightAxis.EdbInfoList = append(dataResp.RightAxis.EdbInfoList, &rightMappingItem)
+				}
+			}
+			dataResp.RightAxis.SeasonRightAxis = seasonConfig.RightAxis
+		}
+	}
+
+	return
+}

+ 122 - 0
services/edb_data.go

@@ -5,6 +5,7 @@ import (
 	"eta/eta_forum_admin/models/mgodb"
 	"eta/eta_forum_admin/utils"
 	"fmt"
+	"github.com/shopspring/decimal"
 	"time"
 )
 
@@ -53,3 +54,124 @@ func GetEdbDataList(endInfoId, edbType int, startDate, endDate string) (list []*
 
 	return
 }
+
+// TbzDiv 同比值计算
+func TbzDiv(a, b float64) float64 {
+	var valFloat float64
+	if b != 0 {
+		af := decimal.NewFromFloat(a)
+		bf := decimal.NewFromFloat(b)
+		val, _ := af.Div(bf).Float64()
+		val = val - 1
+		valFloat, _ = decimal.NewFromFloat(val).RoundCeil(4).Float64()
+	} else {
+		valFloat = 0
+	}
+	return valFloat
+}
+
+// GetEdbDataTbzForSeason 获取指标的同比值数据
+func GetEdbDataTbzForSeason(frequency string, tmpDataList []*models.EdbDataList, startDateTime time.Time) (dataList []*models.EdbDataList, minValue, maxValue float64, err error) {
+	dataList = make([]*models.EdbDataList, 0)
+
+	// 数据处理
+	var dateArr []string
+	dataMap := make(map[string]*models.EdbDataList)
+	for _, v := range tmpDataList {
+		dateArr = append(dateArr, v.DataTime)
+		dataMap[v.DataTime] = v
+	}
+	for _, av := range dateArr {
+		currentItem, ok := dataMap[av]
+		// 如果找不到当前日期的数据,那么终止当前循环,进入下一循环
+		if !ok {
+			continue
+		}
+		tmpItem := *currentItem
+		var isOk bool //是否计算出来结果
+
+		//当前日期
+		currentDate, tmpErr := time.ParseInLocation(utils.FormatDate, av, time.Local)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		// 如果存在开始日期,同时,当前日期早于开始日期,那么终止当前循环,进入下一循环
+		if !startDateTime.IsZero() && currentDate.Before(startDateTime) {
+			continue
+		}
+		//上一年的日期
+		preDate := currentDate.AddDate(-1, 0, 0)
+		preDateStr := preDate.Format(utils.FormatDate)
+		if findItem, ok := dataMap[preDateStr]; ok { //上一年同期找到
+			tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+			isOk = true
+		} else {
+			if frequency == "月度" { //向上和向下,各找一个月
+				for i := 0; i <= 35; i++ {
+					nextDateDay := preDate.AddDate(0, 0, i)
+					nextDateDayStr := nextDateDay.Format(utils.FormatDate)
+					if findItem, ok := dataMap[nextDateDayStr]; ok { //上一年同期->下一个月找到
+						tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+						isOk = true
+						break
+					} else {
+						preDateDay := preDate.AddDate(0, 0, -i)
+						preDateDayStr := preDateDay.Format(utils.FormatDate)
+						if findItem, ok := dataMap[preDateDayStr]; ok { //上一年同期->上一个月找到
+							tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+							isOk = true
+							break
+						}
+					}
+				}
+			} else if frequency == "季度" || frequency == "年度" {
+				if findItem, ok := dataMap[preDateStr]; ok { //上一年同期->下一个月找到
+					tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+					isOk = true
+					break
+				}
+			} else {
+				nextDateDay := preDate.AddDate(0, 0, 1)
+				nextDateDayStr := nextDateDay.Format(utils.FormatDate)
+
+				preDateDay := preDate.AddDate(0, 0, -1)
+				preDateDayStr := preDateDay.Format(utils.FormatDate)
+
+				for i := 0; i < 35; i++ {
+					if i >= 1 {
+						nextDateDay = nextDateDay.AddDate(0, 0, i)
+						nextDateDayStr = nextDateDay.Format(utils.FormatDate)
+					}
+					if findItem, ok := dataMap[nextDateDayStr]; ok { //上一年同期->下一个月找到
+						tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+						isOk = true
+						break
+					} else {
+						if i >= 1 {
+							preDateDay = preDate.AddDate(0, 0, -i)
+							preDateDayStr = nextDateDay.Format(utils.FormatDate)
+						}
+						if findItem, ok := dataMap[preDateDayStr]; ok { //上一年同期->上一个月找到
+							tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+							isOk = true
+							break
+						}
+					}
+				}
+			}
+		}
+
+		if isOk {
+			if tmpItem.Value > maxValue {
+				maxValue = tmpItem.Value
+			}
+			if tmpItem.Value < minValue {
+				minValue = tmpItem.Value
+			}
+			dataList = append(dataList, &tmpItem)
+		}
+	}
+
+	return
+}

+ 143 - 0
services/edb_info.go

@@ -6,6 +6,9 @@ import (
 	"eta/eta_forum_admin/models"
 	"eta/eta_forum_admin/utils"
 	"fmt"
+	"github.com/shopspring/decimal"
+	"math"
+	"sort"
 	"strings"
 	"time"
 )
@@ -483,3 +486,143 @@ func getEdbRuleTitle(edbInfo, parentEdbInfo *models.EdbInfo, childList []models.
 
 	return
 }
+
+
+// HandleDataByLinearRegressionToList 插值法补充数据(线性方程式)
+func HandleDataByLinearRegressionToList (edbInfoDataList []*models.EdbDataList, handleDataMap map[string]float64) (dataTimeList []string,valueList []float64, err error) {
+	if len(edbInfoDataList) < 2 {
+		return
+	}
+
+	var startEdbInfoData *models.EdbDataList
+	for _, v := range edbInfoDataList {
+		handleDataMap[v.DataTime] = v.Value
+		dataTimeList = append(dataTimeList, v.DataTime)
+		// 第一个数据就给过滤了,给后面的试用
+		if startEdbInfoData == nil {
+			startEdbInfoData = v
+			//startEdbInfoData.DataTime = startEdbInfoData.DataTime[:5]+ "01-01"
+			continue
+		}
+
+		// 获取两条数据之间相差的天数
+		startDataTime, _ := time.ParseInLocation(utils.FormatDate, startEdbInfoData.DataTime, time.Local)
+		currDataTime, _ := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local)
+		betweenHour := int(currDataTime.Sub(startDataTime).Hours())
+		betweenDay := betweenHour / 24
+
+		// 如果相差一天,那么过滤
+		if betweenDay <= 1 {
+			startEdbInfoData = v
+			continue
+		}
+
+		// 生成线性方程式
+		var a, b float64
+		{
+			coordinateData := make([]utils.Coordinate, 0)
+			tmpCoordinate1 := utils.Coordinate{
+				X: 1,
+				Y: startEdbInfoData.Value,
+			}
+			coordinateData = append(coordinateData, tmpCoordinate1)
+			tmpCoordinate2 := utils.Coordinate{
+				X: float64(betweenDay) + 1,
+				Y: v.Value,
+			}
+			coordinateData = append(coordinateData, tmpCoordinate2)
+
+			a, b = utils.GetLinearResult(coordinateData)
+			if math.IsNaN(a) || math.IsNaN(b) {
+				err = errors.New("线性方程公式生成失败")
+				return
+			}
+		}
+
+		// 生成对应的值
+		{
+			for i := 1; i < betweenDay; i++ {
+				tmpDataTime := startDataTime.AddDate(0, 0, i)
+				aDecimal := decimal.NewFromFloat(a)
+				xDecimal := decimal.NewFromInt(int64(i) + 1)
+				bDecimal := decimal.NewFromFloat(b)
+
+				val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).Round(4).Float64()
+				handleDataMap[tmpDataTime.Format(utils.FormatDate)] = val
+				dataTimeList = append(dataTimeList, tmpDataTime.Format(utils.FormatDate))
+				valueList = append(valueList, val)
+			}
+		}
+
+		startEdbInfoData = v
+	}
+
+	return
+}
+
+// HandleDataByLinearRegressionToList 保证生成365个数据点的线性插值法
+func HandleDataByLinearRegressionToListV2(edbInfoDataList []*models.EdbDataList, handleDataMap map[string]float64) (dataTimeList []string, valueList []float64, err error) {
+	if len(edbInfoDataList) < 2 {
+		return
+	}
+
+	// 确保至少有两天数据来生成线性方程
+	if len(edbInfoDataList) < 2 {
+		err = errors.New("至少需要两天的数据来执行线性插值")
+		return
+	}
+
+	// 对数据按日期排序,确保顺序正确
+	sort.Slice(edbInfoDataList, func(i, j int) bool {
+		t1, _ := time.ParseInLocation(utils.FormatDate, edbInfoDataList[i].DataTime, time.Local)
+		t2, _ := time.ParseInLocation(utils.FormatDate, edbInfoDataList[j].DataTime, time.Local)
+		return t1.Before(t2)
+	})
+
+	startEdbInfoData := edbInfoDataList[0]
+	endEdbInfoData := edbInfoDataList[len(edbInfoDataList)-1]
+
+	// 计算起始和结束日期间实际的天数
+	startDate, _ := time.ParseInLocation(utils.FormatDate, startEdbInfoData.DataTime, time.Local)
+	endDate, _ := time.ParseInLocation(utils.FormatDate, endEdbInfoData.DataTime, time.Local)
+	actualDays := endDate.Sub(startDate).Hours() / 24
+
+	// 生成365个数据点,首先处理已有数据
+	for _, v := range edbInfoDataList {
+		handleDataMap[v.DataTime] = v.Value
+		dataTimeList = append(dataTimeList, v.DataTime)
+		valueList = append(valueList, v.Value)
+	}
+
+	// 如果已有数据跨越天数不足365天,则对缺失的日期进行线性插值
+	if actualDays < 365 {
+		// 使用已有数据点生成线性方程(这里简化处理,实际可能需更细致处理边界情况)
+		var a, b float64
+		coordinateData := []utils.Coordinate{
+			{X: 1, Y: startEdbInfoData.Value},
+			{X: float64(len(edbInfoDataList)), Y: endEdbInfoData.Value},
+		}
+		a, b = utils.GetLinearResult(coordinateData)
+		if math.IsNaN(a) || math.IsNaN(b) {
+			err = errors.New("线性方程公式生成失败")
+			return
+		}
+
+		// 对剩余日期进行插值
+		for i := 1; i < 365; i++ {
+			day := startDate.AddDate(0, 0, i)
+			if _, exists := handleDataMap[day.Format(utils.FormatDate)]; !exists {
+				aDecimal := decimal.NewFromFloat(a)
+				xDecimal := decimal.NewFromInt(int64(i) + 1)
+				bDecimal := decimal.NewFromFloat(b)
+
+				val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).Round(4).Float64()
+				handleDataMap[day.Format(utils.FormatDate)] = val
+				dataTimeList = append(dataTimeList, day.Format(utils.FormatDate))
+				valueList = append(valueList, val)
+			}
+		}
+	}
+
+	return
+}

+ 1 - 0
utils/constants.go

@@ -298,6 +298,7 @@ const (
 // 图表样式类型
 const (
 	CHART_TYPE_CURVE           = 1  //曲线图
+	CHART_TYPE_SEASON          = 2  //季节性图
 	CHART_TYPE_BAR             = 7  //柱形图
 	CHART_TYPE_SECTION_SCATTER = 10 //截面散点图样式
 	CHART_TYPE_RADAR           = 11 //雷达图