Merge pull request #1423 from dataease/pr@dev@feat_compare

feat(视图): 同比环比
This commit is contained in:
XiaJunjie2020 2021-12-09 18:33:16 +08:00 committed by GitHub
commit a78248e2fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 222 additions and 8 deletions

View File

@ -0,0 +1,21 @@
package io.dataease.dto.chart;
import lombok.Data;
import java.util.List;
/**
* @Author gin
* @Date 2021/12/9 2:48 下午
*/
@Data
public class ChartFieldCompareCustomDTO {
private String field;
private String calcType;
private String timeType;
private String currentTime;
private String compareTime;
private List<String> currentTimeRange;
private List<String> compareTimeRange;
}

View File

@ -0,0 +1,15 @@
package io.dataease.dto.chart;
import lombok.Data;
/**
* @Author gin
* @Date 2021/12/9 2:48 下午
*/
@Data
public class ChartFieldCompareDTO {
private String type;
private String resultData;
private String field;
private ChartFieldCompareCustomDTO custom;
}

View File

@ -46,4 +46,6 @@ public class ChartViewFieldDTO implements Serializable {
private Integer extField;
private String chartType;
private ChartFieldCompareDTO compareCalc;
}

View File

@ -0,0 +1,13 @@
package io.dataease.service.chart;
/**
* @Author gin
* @Date 2021/12/9 3:58 下午
*/
public class ChartConstants {
public static final String YEAR_MOM = "year_mom";
public static final String MONTH_MOM = "month_mom";
public static final String YEAR_YOY = "year_yoy";
public static final String DAY_MOM = "day_mom";
public static final String MONTH_YOY = "month_yoy";
}

View File

@ -36,6 +36,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
@ -433,6 +434,87 @@ public class ChartViewService {
}
}
// 同比/环比计算通过对比类型和数据设置计算出对应指标的结果然后替换结果data数组中的对应元素
// 如果因维度变化如时间字段缺失时间字段的展示格式变化导致无法计算结果的则结果data数组中的对应元素全置为null
// 根据不同图表类型获得需要替换的指标index array
for (int i = 0; i < yAxis.size(); i++) {
ChartViewFieldDTO chartViewFieldDTO = yAxis.get(i);
ChartFieldCompareDTO compareCalc = chartViewFieldDTO.getCompareCalc();
if (ObjectUtils.isEmpty(compareCalc)) {
continue;
}
if (StringUtils.isNotEmpty(compareCalc.getType())
&& !StringUtils.equalsIgnoreCase(compareCalc.getType(), "none")) {
String compareFieldId = compareCalc.getField();// 选中字段
String resultData = compareCalc.getResultData();// 数据设置
// 获取选中字段以及下标
List<ChartViewFieldDTO> checkedField = new ArrayList<>(xAxis);
if (StringUtils.containsIgnoreCase(view.getType(), "stack")) {
checkedField.addAll(extStack);
}
int timeIndex = 0;// 时间字段下标
ChartViewFieldDTO timeField = null;
for (int j = 0; j < checkedField.size(); j++) {
if (StringUtils.equalsIgnoreCase(checkedField.get(j).getId(), compareFieldId)) {
timeIndex = j;
timeField = checkedField.get(j);
}
}
// 计算指标对应的下标
int dataIndex = 0;// 数据字段下标
if (StringUtils.containsIgnoreCase(view.getType(), "stack")) {
dataIndex = xAxis.size() + extStack.size() + i;
} else {
dataIndex = xAxis.size() + i;
}
// 无选中字段或者选中字段已经不在维度list中或者选中字段日期格式不符合对比类型的直接将对应数据置为null
if (ObjectUtils.isEmpty(timeField) || !checkCalcType(timeField.getDateStyle(), compareCalc.getType())) {
// set null
for (String[] item : data) {
item[dataIndex] = null;
}
} else {
// 计算 同比/环比
// 1处理当期数据2根据type计算上一期数据3根据resultData计算结果
Map<String, String> currentMap = new LinkedHashMap<>();
for (String[] item : data) {
currentMap.put(item[timeIndex], item[dataIndex]);
}
Iterator<Map.Entry<String, String>> iterator = currentMap.entrySet().iterator();
int index = 0;
while (iterator.hasNext()) {
Map.Entry<String, String> next = iterator.next();
String cTime = next.getKey();
String cValue = next.getValue();
String lastTime = calcLastTime(cTime, compareCalc.getType(), timeField.getDateStyle());
String lastValue = currentMap.get(lastTime);
if (StringUtils.isEmpty(cValue) || StringUtils.isEmpty(lastValue)) {
data.get(index)[dataIndex] = null;
} else {
if (StringUtils.equalsIgnoreCase(resultData, "sub")) {
data.get(index)[dataIndex] = new BigDecimal(cValue).subtract(new BigDecimal(lastValue)).toString();
} else if (StringUtils.equalsIgnoreCase(resultData, "percent")) {
if (StringUtils.isEmpty(lastValue)) {
data.get(index)[dataIndex] = null;
} else {
data.get(index)[dataIndex] = new BigDecimal(cValue)
.divide(new BigDecimal(lastValue), 2, RoundingMode.HALF_UP)
.subtract(new BigDecimal(1))
.setScale(2, RoundingMode.HALF_UP)
.toString();
}
}
}
index++;
}
}
}
}
// 构建结果
Map<String, Object> map = new TreeMap<>();
// 图表组件可再扩展
Map<String, Object> mapChart = new HashMap<>();
@ -491,6 +573,68 @@ public class ChartViewService {
return dto;
}
private boolean checkCalcType(String dateStyle, String calcType) {
switch (dateStyle) {
case "y":
return StringUtils.equalsIgnoreCase(calcType, "year_mom");
case "y_M":
return StringUtils.equalsIgnoreCase(calcType, "month_mom")
|| StringUtils.equalsIgnoreCase(calcType, "year_yoy");
case "y_M_d":
return StringUtils.equalsIgnoreCase(calcType, "day_mom")
|| StringUtils.equalsIgnoreCase(calcType, "month_yoy")
|| StringUtils.equalsIgnoreCase(calcType, "year_yoy");
}
return false;
}
private String calcLastTime(String cTime, String type, String dateStyle) throws Exception {
String lastTime = null;
Calendar calendar = Calendar.getInstance();
if (StringUtils.equalsIgnoreCase(type, ChartConstants.YEAR_MOM)) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy");
Date date = simpleDateFormat.parse(cTime);
calendar.setTime(date);
calendar.add(Calendar.YEAR, -1);
lastTime = simpleDateFormat.format(calendar.getTime());
} else if (StringUtils.equalsIgnoreCase(type, ChartConstants.MONTH_MOM)) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM");
Date date = simpleDateFormat.parse(cTime);
calendar.setTime(date);
calendar.add(Calendar.MONTH, -1);
lastTime = simpleDateFormat.format(calendar.getTime());
} else if (StringUtils.equalsIgnoreCase(type, ChartConstants.YEAR_YOY)) {
SimpleDateFormat simpleDateFormat = null;
if (StringUtils.equalsIgnoreCase(dateStyle, "y_M")) {
simpleDateFormat = new SimpleDateFormat("yyyy-MM");
} else if (StringUtils.equalsIgnoreCase(dateStyle, "y_M_d")) {
simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
}
Date date = simpleDateFormat.parse(cTime);
calendar.setTime(date);
calendar.add(Calendar.YEAR, -1);
lastTime = simpleDateFormat.format(calendar.getTime());
} else if (StringUtils.equalsIgnoreCase(type, ChartConstants.DAY_MOM)) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date date = simpleDateFormat.parse(cTime);
calendar.setTime(date);
calendar.add(Calendar.DAY_OF_MONTH, -1);
lastTime = simpleDateFormat.format(calendar.getTime());
} else if (StringUtils.equalsIgnoreCase(type, ChartConstants.MONTH_YOY)) {
SimpleDateFormat simpleDateFormat = null;
if (StringUtils.equalsIgnoreCase(dateStyle, "y_M")) {
simpleDateFormat = new SimpleDateFormat("yyyy-MM");
} else if (StringUtils.equalsIgnoreCase(dateStyle, "y_M_d")) {
simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
}
Date date = simpleDateFormat.parse(cTime);
calendar.setTime(date);
calendar.add(Calendar.MONTH, -1);
lastTime = simpleDateFormat.format(calendar.getTime());
}
return lastTime;
}
private boolean checkDrillExist(List<ChartViewFieldDTO> xAxis, List<ChartViewFieldDTO> extStack, ChartViewFieldDTO dto, ChartViewWithBLOBs view) {
if (CollectionUtils.isNotEmpty(xAxis)) {
for (ChartViewFieldDTO x : xAxis) {

View File

@ -11,6 +11,9 @@
</span>
<span class="item-span-style" :title="item.name">{{ item.name }}</span>
<span v-if="item.summary" class="summary-span">{{ $t('chart.'+item.summary) }}</span>
<span v-if="item.deType === 1" class="summary-span">
{{ $t('chart.' + item.dateStyle) }}
</span>
</el-tag>
<el-dropdown v-else trigger="click" size="mini" @command="clickItem">
<span class="el-dropdown-link">
@ -25,6 +28,9 @@
</span>
<span class="item-span-style" :title="item.name">{{ item.name }}</span>
<span v-if="item.summary" class="summary-span">{{ $t('chart.'+item.summary) }}</span>
<span v-if="item.deType === 1" class="summary-span">
{{ $t('chart.' + item.dateStyle) }}
</span>
<i class="el-icon-arrow-down el-icon--right" style="position: absolute;top: 6px;right: 10px;" />
</el-tag>
<el-dropdown-menu slot="dropdown">
@ -239,7 +245,7 @@ export default {
.item-span-style{
display: inline-block;
width: 80px;
width: 70px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
@ -254,6 +260,6 @@ export default {
margin-left: 4px;
color: #878d9f;
position: absolute;
right: 30px;
right: 25px;
}
</style>

View File

@ -10,6 +10,9 @@
<svg-icon v-if="item.sort === 'desc'" icon-class="sort-desc" class-name="field-icon-sort" />
</span>
<span class="item-span-style" :title="item.name">{{ item.name }}</span>
<span v-if="item.deType === 1" class="summary-span">
{{ $t('chart.' + item.dateStyle) }}
</span>
</el-tag>
<el-dropdown v-else trigger="click" size="mini" @command="clickItem">
<span class="el-dropdown-link">
@ -23,6 +26,9 @@
<svg-icon v-if="item.sort === 'desc'" icon-class="sort-desc" class-name="field-icon-sort" />
</span>
<span class="item-span-style" :title="item.name">{{ item.name }}</span>
<span v-if="item.deType === 1" class="summary-span">
{{ $t('chart.' + item.dateStyle) }}
</span>
<i class="el-icon-arrow-down el-icon--right" style="position: absolute;top: 6px;right: 10px;" />
</el-tag>
<el-dropdown-menu slot="dropdown">
@ -32,7 +38,7 @@
<span>
<i class="el-icon-sort" />
<span>{{ $t('chart.sort') }}</span>
<span class="summary-span">({{ $t('chart.'+item.sort) }})</span>
<span class="summary-span-item">({{ $t('chart.'+item.sort) }})</span>
</span>
<i class="el-icon-arrow-right el-icon--right" />
</span>
@ -53,7 +59,7 @@
<span>
<i class="el-icon-c-scale-to-original" />
<span>{{ $t('chart.dateStyle') }}</span>
<span class="summary-span">({{ $t('chart.'+item.dateStyle) }})</span>
<span class="summary-span-item">({{ $t('chart.'+item.dateStyle) }})</span>
</span>
<i class="el-icon-arrow-right el-icon--right" />
</span>
@ -73,7 +79,7 @@
<span>
<i class="el-icon-timer" />
<span>{{ $t('chart.datePattern') }}</span>
<span class="summary-span">({{ $t('chart.'+item.datePattern) }})</span>
<span class="summary-span-item">({{ $t('chart.'+item.datePattern) }})</span>
</span>
<i class="el-icon-arrow-right el-icon--right" />
</span>
@ -215,7 +221,9 @@ export default {
.summary-span{
margin-left: 4px;
color: #878d9f;;
color: #878d9f;
position: absolute;
right: 25px;
}
.inner-dropdown-menu{
@ -227,9 +235,14 @@ export default {
.item-span-style{
display: inline-block;
width: 80px;
width: 70px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.summary-span-item{
margin-left: 4px;
color: #878d9f;
}
</style>

View File

@ -195,7 +195,7 @@ export default {
const t2 = extStack.filter(ele => {
return ele.deType === 1
})
if ((t1.length > 0 || t2.length > 0) && this.chart.type !== 'text' && this.chart.type !== 'gauge' && this.chart.type !== 'liquid') {
if ((t1.length + t2.length === 1) && this.chart.type !== 'text' && this.chart.type !== 'gauge' && this.chart.type !== 'liquid') {
this.disableEditCompare = false
} else {
this.disableEditCompare = true