浏览代码

Merge remote-tracking branch 'origin/master' into dm

# Conflicts:
#	models/chart.go
Roc 1 月之前
父节点
当前提交
df3c78f3cd

+ 66 - 0
controllers/chart.go

@@ -2,6 +2,7 @@ package controllers
 
 import (
 	"encoding/json"
+	"errors"
 	"eta/eta_chart_lib/facade"
 	"eta/eta_chart_lib/models"
 	"eta/eta_chart_lib/models/data_manage"
@@ -9,6 +10,7 @@ import (
 	"eta/eta_chart_lib/models/data_manage/excel"
 	requestDTO "eta/eta_chart_lib/models/request"
 	"eta/eta_chart_lib/services/data"
+	"eta/eta_chart_lib/services/data/area_graph"
 	"eta/eta_chart_lib/services/data/cross_variety"
 	"eta/eta_chart_lib/services/data/range_analysis"
 	dwmini "eta/eta_chart_lib/services/dw_mini"
@@ -548,6 +550,16 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *models.ChartInfo, key string) (
 		return
 	}
 
+	// 面积图 面积堆积 数据处理
+	if chartType == utils.CHART_TYPE_AREA {
+		err, errMsg = fillAreaGraphData(extraConfigStr, edbList)
+		if err != nil {
+			msg = "获取失败"
+			errMsg = "获取面积图数据失败,Err:" + err.Error()
+			return
+		}
+	}
+
 	for _, v := range edbList {
 		// 指标别名
 		if barConfig.EdbInfoIdList != nil && len(barConfig.EdbInfoIdList) > 0 {
@@ -578,3 +590,57 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *models.ChartInfo, key string) (
 	isOk = true
 	return
 }
+
+func fillAreaGraphData(extraConfigStr string, edbDataList []*models.ChartEdbInfoMapping) (err error, errMsg string) {
+
+	var tmpConfig data_manage.AreaExtraConf
+	if extraConfigStr != `` {
+		err = json.Unmarshal([]byte(extraConfigStr), &tmpConfig)
+		if err != nil {
+			errMsg = "面积图配置异常"
+			err = errors.New(errMsg)
+			return
+		}
+		if tmpConfig.StandardEdbInfoId <= 0 {
+			utils.FileLog.Info("面积图未开启面积堆积")
+			return
+		}
+	}
+	if tmpConfig.IsHeap == 1 {
+		standardIndexMap := make(map[string]*models.EdbDataList)
+		var startDate, endDate string
+		for _, v := range edbDataList {
+			// 判断是否为基准指标
+			if v.EdbInfoId == tmpConfig.StandardEdbInfoId {
+				if dataList, ok := v.DataList.([]*models.EdbDataList); ok {
+					startDate = dataList[0].DataTime
+					endDate = dataList[len(dataList)-1].DataTime
+					for _, dataObject := range dataList {
+						standardIndexMap[dataObject.DataTime] = dataObject
+					}
+				}
+				break
+			}
+		}
+		strategy, err := area_graph.CreateStrategy(tmpConfig.NullDealWay)
+		if err != nil {
+			return err, "创建空值处理器失败"
+		}
+		err = strategy.Deal(tmpConfig, edbDataList, standardIndexMap, startDate, endDate)
+		if err != nil {
+			return err, err.Error()
+		}
+
+		// 时间戳处理
+		for _, mapping := range edbDataList {
+			if dataList, ok := mapping.DataList.([]*models.EdbDataList); ok {
+				for _, dataInfo := range dataList {
+					toFormatTime := utils.StringToFormatTime(dataInfo.DataTime, utils.FormatDate)
+					dataInfo.DataTimestamp = toFormatTime.UnixMilli()
+				}
+			}
+		}
+	}
+
+	return nil, ""
+}

+ 3 - 3
models/chart.go

@@ -48,7 +48,7 @@ type ChartInfo struct {
 	Source            int       `description:"1:ETA图库;2:商品价格曲线"`
 	Unit              string    `description:"中文单位名称"`
 	UnitEn            string    `description:"英文单位名称"`
-	ExtraConfig       string    `description:"图表额外配置,json数据" json:"-"`
+	ExtraConfig       string    `description:"图表额外配置,json数据"`
 	ChartSource       string    `description:"图表来源str"`
 	ChartSourceEn     string    `description:"图表来源(英文)"`
 	SeasonExtraConfig string    `description:"季节性图表中的配置,json数据"`
@@ -99,6 +99,7 @@ type ChartEdbInfoMapping struct {
 	ChartStyle        string  `description:"图表类型"`
 	ChartColor        string  `description:"颜色"`
 	ChartWidth        float64 `description:"线条大小"`
+	ChartScale        float64 `description:"参考刻度线"`
 	DataList          interface{}
 
 	EdbInfoCategoryType int     `description:"0:普通指标,1:预测指标"`
@@ -126,8 +127,7 @@ type ChartEdbInfoMapping struct {
 }
 
 func GetChartEdbMappingList(chartInfoId int) (list []*ChartEdbInfoMapping, err error) {
-	//o := orm.NewOrmUsingDB("data")
-	aField := `a.chart_edb_mapping_id,a.chart_info_id,a.edb_info_id,a.create_time,a.modify_time,a.unique_code,a.max_data,a.min_data,a.is_order,a.is_axis,a.edb_info_type,a.lead_value,a.lead_unit,a.chart_style,a.chart_color,a.predict_chart_color,a.chart_width,a.source as mapping_source,a.edb_alias_name`
+	aField := `a.chart_edb_mapping_id,a.chart_info_id,a.edb_info_id,a.create_time,a.modify_time,a.unique_code,a.max_data,a.min_data,a.is_order,a.is_axis,a.edb_info_type,a.lead_value,a.lead_unit,a.chart_style,a.chart_color,a.predict_chart_color,a.chart_width,a.source as mapping_source,a.edb_alias_name,a.chart_scale`
 	sql := ` SELECT ` + aField + `,b.source_name,b.source,b.edb_code,b.edb_name,b.edb_name_en,b.frequency,b.unit,b.unit_en,b.start_date,b.end_date,b.modify_time,b.edb_type,b.latest_date,b.latest_value,b.unique_code,b.edb_info_type AS edb_info_category_type,b.sub_source,
 a.is_convert, a.convert_type, a.convert_value, a.convert_unit, a.convert_en_unit 
              FROM chart_edb_mapping AS a

+ 8 - 0
models/data_manage/chart_info.go

@@ -47,6 +47,14 @@ type ChartEdbInfoMapping struct {
 	IsNullData          bool `json:"-" description:"是否空数据"`
 }
 
+// AreaExtraConf 面积图配置
+type AreaExtraConf struct {
+	IsHeap            int `description:"是否堆积 1-堆积 2-不堆积"`
+	HeapWay           int `description:"堆积方式 1-普通 2-百分比"`
+	StandardEdbInfoId int `description:"基准指标id"`
+	NullDealWay       int `description:"空值处理方式,1-插值填充 2-前值填充 3-后值填充 4-等于0 5-删除日期"`
+}
+
 func GetChartEdbMappingList(chartInfoId int) (list []*ChartEdbInfoMapping, err error) {
 	//o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT a.*,b.source_name,b.source,b.edb_code,b.edb_name,b.edb_name_en,b.frequency,b.unit,b.unit_en,b.start_date,b.end_date,b.modify_time,b.latest_date,b.latest_value,b.unique_code,b.edb_info_type AS edb_info_category_type

+ 593 - 0
services/data/area_graph/processor_business_logic.go

@@ -0,0 +1,593 @@
+package area_graph
+
+import (
+	"errors"
+	"eta/eta_chart_lib/models"
+	"eta/eta_chart_lib/models/data_manage"
+	"eta/eta_chart_lib/utils"
+	"github.com/shopspring/decimal"
+	"math"
+	"sort"
+	"time"
+)
+
+type InterpolateStrategy struct{}
+
+// Deal 空值填充:插值法填充
+func (i *InterpolateStrategy) Deal(tmpConfig data_manage.AreaExtraConf, edbDataList []*models.ChartEdbInfoMapping, standardIndexMap map[string]*models.EdbDataList, startDate string, endDate string) (err error) {
+	for _, v := range edbDataList {
+		if v.EdbInfoId != tmpConfig.StandardEdbInfoId {
+			if dataList, ok := v.DataList.([]*models.EdbDataList); ok {
+
+				// 存放补充数据
+				var replenishDataList []*models.EdbDataList
+
+				// 处理从 startDate 到第一个数据的日期补充
+				if len(dataList) > 0 {
+					firstData := dataList[0]
+					// 将 startDate 到第一个数据日期之间的自然日填充补充数据,值为 0
+					startDataTime, _ := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+					firstDataTime, _ := time.ParseInLocation(utils.FormatDate, firstData.DataTime, time.Local)
+
+					// 计算两个日期之间的天数差
+					if !startDataTime.Equal(firstDataTime) {
+						for startDataTime.Before(firstDataTime) {
+							// 补充数据
+							nextDay := startDataTime.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := models.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: startDataTime.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 startDataTime 到下一个日期
+							startDataTime = startDataTime.AddDate(0, 0, 1)
+						}
+					}
+				}
+
+				// 插值法补充数据
+				var startEdbInfoData *models.EdbDataList
+				for index := 0; index < len(dataList); index++ {
+					// 获取当前数据和下一个数据
+					currentIndexData := dataList[index]
+					//afterIndexData := dataList[index+1]
+
+					if startEdbInfoData == nil {
+						startEdbInfoData = currentIndexData
+						continue
+					}
+
+					// 获取两条数据之间相差的天数
+					startDataTime, _ := time.ParseInLocation(utils.FormatDate, startEdbInfoData.DataTime, time.Local)
+					currDataTime, _ := time.ParseInLocation(utils.FormatDate, currentIndexData.DataTime, time.Local)
+					betweenHour := int(currDataTime.Sub(startDataTime).Hours())
+					betweenDay := betweenHour / 24
+
+					// 如果相差一天,那么过滤
+					if betweenDay <= 1 {
+						startEdbInfoData = currentIndexData
+						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: currentIndexData.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()
+						nextDay := tmpDataTime.Format(utils.FormatDate)
+
+						replenishIndexData := models.EdbDataList{
+							EdbDataId:     currentIndexData.EdbDataId,
+							DataTime:      nextDay,
+							DataTimestamp: tmpDataTime.UnixMilli(),
+							Value:         val,
+						}
+
+						// 将补充数据加入补充数据列表
+						replenishDataList = append(replenishDataList, &replenishIndexData)
+					}
+					startEdbInfoData = currentIndexData
+				}
+
+				// 处理从最后一个数据到 endDate 的日期补充
+				if len(dataList) > 0 {
+					lastData := dataList[len(dataList)-1]
+					// 将最后一个数据日期到 endDate 之间的自然日填充补充数据,值为 0
+					lastDataTime, _ := time.ParseInLocation(utils.FormatDate, lastData.DataTime, time.Local)
+					endDataTime, _ := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+
+					// 如果 lastDataTime 不等于 endDate,进行补充
+					if !lastDataTime.Equal(endDataTime) {
+						// 补充数据直到 endDate
+						for lastDataTime.Before(endDataTime) {
+							// 补充数据
+							addDate := lastDataTime.AddDate(0, 0, 1)
+							nextDay := addDate.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := models.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: addDate.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 lastDataTime 到下一个日期
+							lastDataTime = addDate
+						}
+					}
+				}
+
+				dataList = append(dataList, replenishDataList...)
+
+				// 根据基准指标筛选出符合数据
+				var resultDataList []*models.EdbDataList
+				for _, dataObject := range dataList {
+					if _, ok := standardIndexMap[dataObject.DataTime]; ok {
+						// 存在才保留
+						resultDataList = append(resultDataList, dataObject)
+					}
+				}
+
+				// 排序
+				sort.Slice(resultDataList, func(i, j int) bool {
+					return resultDataList[i].DataTimestamp < resultDataList[j].DataTimestamp
+				})
+
+				v.DataList = resultDataList
+			}
+		}
+	}
+	return nil
+}
+
+type FillWithPreviousStrategy struct{}
+
+// Deal 空值填充:前值填充
+func (f *FillWithPreviousStrategy) Deal(tmpConfig data_manage.AreaExtraConf, edbDataList []*models.ChartEdbInfoMapping, standardIndexMap map[string]*models.EdbDataList, startDate string, endDate string) (err error) {
+	// 按自然日补充,再根据基准指标取对应数据
+	for _, v := range edbDataList {
+		if v.EdbInfoId != tmpConfig.StandardEdbInfoId {
+			if dataList, ok := v.DataList.([]*models.EdbDataList); ok {
+				// 存放补充数据
+				var replenishDataList []*models.EdbDataList
+
+				// 处理从 startDate 到第一个数据的日期补充
+				if len(dataList) > 0 {
+					firstData := dataList[0]
+					// 将 startDate 到第一个数据日期之间的自然日填充补充数据,值为 0
+					startDataTime, _ := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+					firstDataTime, _ := time.ParseInLocation(utils.FormatDate, firstData.DataTime, time.Local)
+
+					// 计算两个日期之间的天数差
+					if !startDataTime.Equal(firstDataTime) {
+						for startDataTime.Before(firstDataTime) {
+							// 补充数据
+							nextDay := startDataTime.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := models.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: startDataTime.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 startDataTime 到下一个日期
+							startDataTime = startDataTime.AddDate(0, 0, 1)
+						}
+					}
+				}
+
+				// 处理指标中空值数据
+				for index := 0; index < len(dataList)-1; index++ {
+					// 获取当前数据和下一个数据
+					beforeIndexData := dataList[index]
+					afterIndexData := dataList[index+1]
+
+					for utils.IsMoreThanOneDay(beforeIndexData.DataTime, afterIndexData.DataTime) {
+						// 创建补充数据
+						nextDay := utils.GetNextDay(beforeIndexData.DataTime)
+
+						toTime := utils.StringToTime(nextDay)
+						replenishIndexData := models.EdbDataList{
+							EdbInfoId:     v.EdbInfoId,
+							DataTime:      nextDay,
+							DataTimestamp: toTime.UnixMilli(),
+							Value:         beforeIndexData.Value,
+						}
+
+						// 将补充数据加入补充数据列表
+						replenishDataList = append(replenishDataList, &replenishIndexData)
+
+						// 更新 beforeIndexData 为新创建的补充数据
+						beforeIndexData = &replenishIndexData
+					}
+				}
+
+				// 处理从最后一个数据到 endDate 的日期补充
+				if len(dataList) > 0 {
+					lastData := dataList[len(dataList)-1]
+					// 将最后一个数据日期到 endDate 之间的自然日填充补充数据,值为 0
+					lastDataTime, _ := time.ParseInLocation(utils.FormatDate, lastData.DataTime, time.Local)
+					endDataTime, _ := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+
+					// 如果 lastDataTime 不等于 endDate,进行补充
+					if !lastDataTime.Equal(endDataTime) {
+						// 补充数据直到 endDate
+						for lastDataTime.Before(endDataTime) {
+							// 补充数据
+							addDate := lastDataTime.AddDate(0, 0, 1)
+							nextDay := addDate.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := models.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: addDate.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 lastDataTime 到下一个日期
+							lastDataTime = addDate
+						}
+					}
+				}
+
+				dataList = append(dataList, replenishDataList...)
+
+				// 根据基准指标筛选出符合数据
+				var resultDataList []*models.EdbDataList
+				for _, dataObject := range dataList {
+					_, ok = standardIndexMap[dataObject.DataTime]
+					if ok {
+						// 存在才保留
+						resultDataList = append(resultDataList, dataObject)
+					}
+				}
+
+				// 排序
+				sort.Slice(resultDataList, func(i, j int) bool {
+					return resultDataList[i].DataTimestamp < resultDataList[j].DataTimestamp
+				})
+
+				v.DataList = resultDataList
+			}
+		}
+	}
+	return nil
+}
+
+type FillWithNextStrategy struct{}
+
+// Deal 空值填充:后值填充
+func (f *FillWithNextStrategy) Deal(tmpConfig data_manage.AreaExtraConf, edbDataList []*models.ChartEdbInfoMapping, standardIndexMap map[string]*models.EdbDataList, startDate string, endDate string) (err error) {
+	// 按自然日补充,再根据基准指标取对应数据
+	for _, v := range edbDataList {
+		if v.EdbInfoId != tmpConfig.StandardEdbInfoId {
+			if dataList, ok := v.DataList.([]*models.EdbDataList); ok {
+
+				// 存放补充数据
+				var replenishDataList []*models.EdbDataList
+
+				// 处理从 startDate 到第一个数据的日期补充
+				if len(dataList) > 0 {
+					firstData := dataList[0]
+					// 将 startDate 到第一个数据日期之间的自然日填充补充数据,值为 0
+					startDataTime, _ := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+					firstDataTime, _ := time.ParseInLocation(utils.FormatDate, firstData.DataTime, time.Local)
+
+					// 计算两个日期之间的天数差
+					if !startDataTime.Equal(firstDataTime) {
+						for !startDataTime.After(firstDataTime) {
+							// 补充数据
+							nextDay := startDataTime.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := models.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: startDataTime.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 startDataTime 到下一个日期
+							startDataTime = startDataTime.AddDate(0, 0, 1)
+						}
+					}
+				}
+
+				// 处理从最后一个数据到 endDate 的日期补充
+				if len(dataList) > 0 {
+					lastData := dataList[len(dataList)-1]
+					// 将最后一个数据日期到 endDate 之间的自然日填充补充数据,值为 0
+					lastDataTime, _ := time.ParseInLocation(utils.FormatDate, lastData.DataTime, time.Local)
+					endDataTime, _ := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+
+					// 如果 lastDataTime 不等于 endDate,进行补充
+					if !lastDataTime.Equal(endDataTime) {
+						// 补充数据直到 endDate
+						for lastDataTime.Before(endDataTime) {
+							// 补充数据
+							addDate := lastDataTime.AddDate(0, 0, 1)
+							nextDay := addDate.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := models.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: addDate.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 lastDataTime 到下一个日期
+							lastDataTime = addDate
+						}
+					}
+				}
+
+				// 将切片数据倒序
+				reverseSlice(dataList)
+
+				// 处理指标中空值数据
+				for index := 0; index < len(dataList)-1; index++ {
+					// 获取当前数据和下一个数据
+					beforeIndexData := dataList[index]
+					afterIndexData := dataList[index+1]
+
+					for utils.IsMoreThanOneDay(afterIndexData.DataTime, beforeIndexData.DataTime) {
+						// 创建补充数据
+						nextDay := utils.GetNextDay(afterIndexData.DataTime)
+
+						toTime := utils.StringToTime(nextDay)
+						replenishIndexData := models.EdbDataList{
+							EdbInfoId:     v.EdbInfoId,
+							DataTime:      nextDay,
+							DataTimestamp: toTime.UnixMilli(),
+							Value:         beforeIndexData.Value,
+						}
+
+						// 将补充数据加入补充数据列表
+						replenishDataList = append(replenishDataList, &replenishIndexData)
+
+						// 更新 beforeIndexData 为新创建的补充数据
+						afterIndexData = &replenishIndexData
+					}
+				}
+
+				dataList = append(dataList, replenishDataList...)
+
+				// 根据基准指标筛选出符合数据
+				var resultDataList []*models.EdbDataList
+				for _, dataObject := range dataList {
+					_, ok = standardIndexMap[dataObject.DataTime]
+					if ok {
+						// 存在才保留
+						resultDataList = append(resultDataList, dataObject)
+					}
+				}
+
+				// 排序
+				sort.Slice(resultDataList, func(i, j int) bool {
+					return resultDataList[i].DataTimestamp < resultDataList[j].DataTimestamp
+				})
+
+				v.DataList = resultDataList
+			}
+		}
+	}
+	return nil
+}
+
+type SetToZeroStrategy struct{}
+
+// Deal 空值填充:设为0
+func (s *SetToZeroStrategy) Deal(tmpConfig data_manage.AreaExtraConf, edbDataList []*models.ChartEdbInfoMapping, standardIndexMap map[string]*models.EdbDataList, startDate string, endDate string) (err error) {
+	// 按自然日补充,再根据基准指标取对应数据
+	for _, v := range edbDataList {
+		if v.EdbInfoId != tmpConfig.StandardEdbInfoId {
+			if dataList, ok := v.DataList.([]*models.EdbDataList); ok {
+				// 存放补充数据
+				var replenishDataList []*models.EdbDataList
+
+				// 处理从 startDate 到第一个数据的日期补充
+				if len(dataList) > 0 {
+					firstData := dataList[0]
+					// 将 startDate 到第一个数据日期之间的自然日填充补充数据,值为 0
+					startDataTime, _ := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+					firstDataTime, _ := time.ParseInLocation(utils.FormatDate, firstData.DataTime, time.Local)
+
+					// 计算两个日期之间的天数差
+					if !startDataTime.Equal(firstDataTime) {
+						for startDataTime.Before(firstDataTime) {
+							// 补充数据
+							nextDay := startDataTime.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := models.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: startDataTime.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 startDataTime 到下一个日期
+							startDataTime = startDataTime.AddDate(0, 0, 1)
+						}
+					}
+				}
+
+				// 处理指标中空值数据
+				for index := 0; index < len(dataList)-1; index++ {
+					// 获取当前数据和下一个数据
+					beforeIndexData := dataList[index]
+					afterIndexData := dataList[index+1]
+
+					for utils.IsMoreThanOneDay(beforeIndexData.DataTime, afterIndexData.DataTime) {
+						// 创建补充数据
+						nextDay := utils.GetNextDay(beforeIndexData.DataTime)
+
+						toTime := utils.StringToTime(nextDay)
+						replenishIndexData := models.EdbDataList{
+							EdbInfoId:     v.EdbInfoId,
+							DataTime:      nextDay,
+							DataTimestamp: toTime.UnixMilli(),
+							Value:         0,
+						}
+
+						// 将补充数据加入补充数据列表
+						replenishDataList = append(replenishDataList, &replenishIndexData)
+
+						// 更新 beforeIndexData 为新创建的补充数据
+						beforeIndexData = &replenishIndexData
+					}
+				}
+
+				// 处理从最后一个数据到 endDate 的日期补充
+				if len(dataList) > 0 {
+					lastData := dataList[len(dataList)-1]
+					// 将最后一个数据日期到 endDate 之间的自然日填充补充数据,值为 0
+					lastDataTime, _ := time.ParseInLocation(utils.FormatDate, lastData.DataTime, time.Local)
+					endDataTime, _ := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+
+					// 如果 lastDataTime 不等于 endDate,进行补充
+					if !lastDataTime.Equal(endDataTime) {
+						// 补充数据直到 endDate
+						for lastDataTime.Before(endDataTime) {
+							// 补充数据
+							addDate := lastDataTime.AddDate(0, 0, 1)
+							nextDay := addDate.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := models.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: addDate.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 lastDataTime 到下一个日期
+							lastDataTime = addDate
+						}
+					}
+				}
+
+				dataList = append(dataList, replenishDataList...)
+
+				// 根据基准指标筛选出符合数据
+				var resultDataList []*models.EdbDataList
+				for _, dataObject := range dataList {
+					_, ok = standardIndexMap[dataObject.DataTime]
+					if ok {
+						// 存在才保留
+						resultDataList = append(resultDataList, dataObject)
+					}
+				}
+
+				// 排序
+				sort.Slice(resultDataList, func(i, j int) bool {
+					return resultDataList[i].DataTimestamp < resultDataList[j].DataTimestamp
+				})
+
+				v.DataList = resultDataList
+			}
+		}
+	}
+	return nil
+}
+
+type DeleteDateStrategy struct{}
+
+// Deal 删除日期
+func (d *DeleteDateStrategy) Deal(tmpConfig data_manage.AreaExtraConf, edbDataList []*models.ChartEdbInfoMapping, standardIndexMap map[string]*models.EdbDataList, startDate string, endDate string) error {
+	// 取所有指标的时间交集
+	// 创建一个 map 来保存每个时间点的出现次数
+	timeMap := make(map[string]int)
+	for _, v := range edbDataList {
+		if dataList, ok := v.DataList.([]*models.EdbDataList); ok {
+			// 遍历所有的 dataList,为每个 DataTime 增加一个计数
+			for _, dataObject := range dataList {
+				timeMap[dataObject.DataTime]++
+			}
+		}
+	}
+
+	for _, v := range edbDataList {
+		if dataList, ok := v.DataList.([]*models.EdbDataList); ok {
+			// 遍历所有的 dataList,保留所有时间点在所有指标中都存在的数据
+			var resultDataList []*models.EdbDataList
+			for _, dataObject := range dataList {
+				if timeMap[dataObject.DataTime] == len(edbDataList) {
+					// 如果该时间点在所有指标中都存在,加入到结果列表
+					resultDataList = append(resultDataList, dataObject)
+				}
+			}
+
+			// 将符合条件的数据重新赋值回 v.DataList
+			v.DataList = resultDataList
+		}
+	}
+	return nil
+}
+
+// 将列表颠倒
+func reverseSlice(dataList []*models.EdbDataList) {
+	// 使用双指针法,前后两个指针向中间逼近
+	for i, j := 0, len(dataList)-1; i < j; i, j = i+1, j-1 {
+		// 交换位置
+		dataList[i], dataList[j] = dataList[j], dataList[i]
+	}
+}

+ 28 - 0
services/data/area_graph/processor_factory.go

@@ -0,0 +1,28 @@
+package area_graph
+
+import (
+	"eta/eta_chart_lib/models"
+	"eta/eta_chart_lib/models/data_manage"
+	"fmt"
+)
+
+type NullDealStrategy interface {
+	Deal(tmpConfig data_manage.AreaExtraConf, edbDataList []*models.ChartEdbInfoMapping, standardIndexMap map[string]*models.EdbDataList, startDate string, endDate string) (err error)
+}
+
+func CreateStrategy(dealWay int) (NullDealStrategy, error) {
+	switch dealWay {
+	case 1:
+		return &InterpolateStrategy{}, nil
+	case 2:
+		return &FillWithPreviousStrategy{}, nil
+	case 3:
+		return &FillWithNextStrategy{}, nil
+	case 4:
+		return &SetToZeroStrategy{}, nil
+	case 5:
+		return &DeleteDateStrategy{}, nil
+	default:
+		return nil, fmt.Errorf("未知的空值处理类型: %d", dealWay)
+	}
+}

+ 20 - 4
services/data/chart_info.go

@@ -399,6 +399,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 			item.ChartStyle = ""
 			item.ChartColor = ""
 			item.ChartWidth = 0
+			item.ChartScale = 0
 			item.MaxData = v.MaxValue
 			item.MinData = v.MinValue
 		} else {
@@ -412,6 +413,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 			item.ChartStyle = v.ChartStyle
 			item.ChartColor = v.ChartColor
 			item.ChartWidth = v.ChartWidth
+			item.ChartScale = v.ChartScale
 			item.IsOrder = v.IsOrder
 			item.MaxData = v.MaxData
 			item.MinData = v.MinData
@@ -612,6 +614,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 				return
 			}
 
+			nowYear := time.Now().Year()
 			newDataList := make([]*models.EdbDataList, 0)
 			for _, v := range dataList {
 				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
@@ -619,6 +622,10 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
 					return
 				}
+				dataTimeT, _ := time.Parse(utils.FormatDate, v.DataTime)
+				year := dataTimeT.Year()
+				newItemDate := dataTimeT.AddDate(nowYear-year, 0, 0)
+				v.DataTimestamp = newItemDate.UnixNano() / 1e6
 				if dataTime.Equal(rightAxisDate) || dataTime.After(rightAxisDate) {
 					newDataList = append(newDataList, v)
 				}
@@ -1002,6 +1009,10 @@ func GetSeasonEdbInfoDataListByXDateNong(result *models.EdbDataResult, latestDat
 				Value:         item.Value,
 			}
 			dataTimeT, _ := time.Parse(utils.FormatDate, item.DataTime)
+			year := dataTimeT.Year()
+			newItemDate := dataTimeT.AddDate(nowYear-year, 0, 0)
+			timestamp := newItemDate.UnixNano() / 1e6
+			tmpVal.DataTimestamp = timestamp
 			if (startTmpT.Before(dataTimeT) && endTmpT.After(dataTimeT)) || startTmpT == dataTimeT || endTmpT == dataTimeT {
 				tmpV := &tmpVal
 				if findVal, ok := quarterMap[name]; !ok {
@@ -2051,7 +2062,7 @@ func SeasonChartData(dataList []*models.ChartEdbInfoMapping, seasonExtraConfig s
 						continue
 					}
 					// 农历时dataTimeList的年份为最新年份,图例时间的年份才是真实年份
-					chartLegend,_ := strconv.Atoi(v.ChartLegend)
+					chartLegend, _ := strconv.Atoi(v.ChartLegend)
 					if chartLegend < startYear {
 						continue
 					}
@@ -2187,7 +2198,7 @@ func SeasonChartData(dataList []*models.ChartEdbInfoMapping, seasonExtraConfig s
 							continue
 						}
 						// 农历时dataTimeList的年份为最新年份,图例时间的年份才是真实年份
-						chartLegend,_ := strconv.Atoi(v.ChartLegend)
+						chartLegend, _ := strconv.Atoi(v.ChartLegend)
 						if chartLegend < startYear {
 							continue
 						}
@@ -2360,7 +2371,7 @@ func SeasonChartData(dataList []*models.ChartEdbInfoMapping, seasonExtraConfig s
 						continue
 					}
 					// 农历时dataTimeList的年份为最新年份,图例时间的年份才是真实年份
-					chartLegend,_ := strconv.Atoi(quarterDataList[i].ChartLegend)
+					chartLegend, _ := strconv.Atoi(quarterDataList[i].ChartLegend)
 					if chartLegend < startYear {
 						continue
 					}
@@ -2448,7 +2459,7 @@ func SeasonChartData(dataList []*models.ChartEdbInfoMapping, seasonExtraConfig s
 						continue
 					}
 					// 农历时dataTimeList的年份为最新年份,图例时间的年份才是真实年份
-					chartLegend,_ := strconv.Atoi(quarterDataList[i].ChartLegend)
+					chartLegend, _ := strconv.Atoi(quarterDataList[i].ChartLegend)
 					if chartLegend < startYear {
 						continue
 					}
@@ -2796,6 +2807,7 @@ func getEdbDataMapListForSeason(chartInfoId, chartType int, calendar, startDate,
 				return
 			}
 
+			nowYear := time.Now().Year()
 			newList := make([]*models.EdbDataList, 0)
 			for _, v := range newDataList {
 				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
@@ -2803,6 +2815,10 @@ func getEdbDataMapListForSeason(chartInfoId, chartType int, calendar, startDate,
 					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
 					return
 				}
+				dataTimeT, _ := time.Parse(utils.FormatDate, v.DataTime)
+				year := dataTimeT.Year()
+				newItemDate := dataTimeT.AddDate(nowYear-year, 0, 0)
+				v.DataTimestamp = newItemDate.UnixNano() / 1e6
 				if dataTime.Equal(rightAxisDate) || dataTime.After(rightAxisDate) {
 					newList = append(newList, v)
 				}

+ 1 - 0
utils/constants.go

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

+ 135 - 0
utils/date_util.go

@@ -0,0 +1,135 @@
+package utils
+
+import (
+	"time"
+)
+
+// 定义时间格式常量
+const (
+	YearMonthDay     = "2006-01-02"                     // yyyy-MM-dd
+	YearMonthDayTime = "2006-01-02 15:04:05"            // yyyy-MM-dd HH:mm:ss
+	MonthDay         = "01-02"                          // MM-dd
+	DayMonthYear     = "02-01-2006"                     // dd-MM-yyyy
+	YearMonth        = "2006-01"                        // yyyy-MM
+	FullDate         = "Monday, 02-Jan-06 15:04:05 PST" // 完整日期:例如:Monday, 02-Jan-06 15:04:05 PST
+)
+
+// GetPreYearTime 获取当前时间 前n年的时间 返回yyyy-MM-dd 格式的时间
+func GetPreYearTime(n int) string {
+	// 获取当前时间
+	now := time.Now()
+	// 计算前n年的时间
+	preYearTime := now.AddDate(-n, 0, 0)
+	// 格式化时间
+	return preYearTime.Format("2006-01-02")
+}
+
+// IsMoreThanOneDay 判断两个yyyy-MM-dd类型的时间,相差是否大于1天
+func IsMoreThanOneDay(startDate, endDate string) bool {
+	startTime, _ := time.Parse("2006-01-02", startDate)
+	endTime, _ := time.Parse("2006-01-02", endDate)
+	diff := endTime.Sub(startTime)
+	days := diff.Hours() / 24
+	return days > 1
+}
+
+// GetNextDay 获取 yyyy-MM-dd类型的时间的下一天
+func GetNextDay(date string) string {
+	t, _ := time.Parse("2006-01-02", date)
+	nextDay := t.AddDate(0, 0, 1)
+	return nextDay.Format("2006-01-02")
+}
+
+// GetNextDayN 获取 yyyy-MM-dd 类型的时间的下n天
+func GetNextDayN(date string, n int) string {
+	t, _ := time.Parse("2006-01-02", date)
+	nextDay := t.AddDate(0, 0, n)
+	return nextDay.Format("2006-01-02")
+}
+
+var daysOfMonth = [...]int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
+
+// AddDate 解决 Go time 包 AddDate() 添加年份/月份溢出到下一个月的问题。
+// 例如:
+//
+//	2024-02-29 AddDate(1, 0, 0) 期望结果: 2025-02-28
+//	2024-08-31 AddDate(0, 1, 1) 期望结果: 2024-09-30
+func AddDate(t time.Time, years, months int) time.Time {
+	month := t.Month()
+
+	// 规范年份和月份
+	years, months = norm(years, months, 12)
+
+	// 计算目标月份
+	targetMonth := int(month) + months
+	if targetMonth <= 0 {
+		// 处理负值月份
+		targetMonth += 12 * ((-targetMonth)/12 + 1)
+	}
+	// 取余计算目标月份
+	targetMonth = (targetMonth-1)%12 + 1
+
+	// 计算目标年份
+	targetYear := t.Year() + years + (int(month)+months-1)/12
+
+	// 计算目标月份最大天数
+	maxDayOfTargetMonth := daysOfMonth[targetMonth-1]
+	if isLeap(targetYear) && targetMonth == 2 {
+		maxDayOfTargetMonth++ // 闰年2月多一天
+	}
+
+	// 计算目标日期
+	targetDay := t.Day()
+	if targetDay > maxDayOfTargetMonth {
+		// 如果目标日期超出该月的天数,设置为该月的最后一天
+		targetDay = maxDayOfTargetMonth
+	}
+
+	// 返回新的日期
+	return time.Date(targetYear, time.Month(targetMonth), targetDay, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
+}
+
+// norm 规范化年份和月份
+func norm(hi, lo, base int) (nhi, nlo int) {
+	if lo < 0 {
+		n := (-lo-1)/base + 1
+		hi -= n
+		lo += n * base
+	}
+	if lo >= base {
+		n := lo / base
+		hi += n
+		lo -= n * base
+	}
+	return hi, lo
+}
+
+// isLeap 判断是否为闰年
+func isLeap(year int) bool {
+	return year%4 == 0 && (year%100 != 0 || year%400 == 0)
+}
+
+// StringToTime string 类型时间 转换为 time.Time 类型
+func StringToTime(date string) time.Time {
+	t, _ := time.ParseInLocation("2006-01-02", date, time.Local)
+	return t
+}
+
+// StringToFormatTime string 类型时间 转换为指定格式的 time.Time类型
+func StringToFormatTime(data string, format string) time.Time {
+	t, _ := time.ParseInLocation(format, data, time.Local)
+	return t
+}
+
+// TimeToString time.Time 类型时间 转换为 string 类型
+func TimeToString(t time.Time, format string) string {
+	formattedTime := t.Format(format)
+	return formattedTime
+}
+
+// CompareDate 判断传入的两个字符串时间的前后顺序
+func CompareDate(data1, data2 string) bool {
+	t1, _ := time.Parse("2006-01-02", data1)
+	t2, _ := time.Parse("2006-01-02", data2)
+	return !t1.After(t2)
+}