Merge pull request #9590 from ulleo/dev-v2

feat(图表): 新增区间条形图
This commit is contained in:
ulleo 2024-05-10 16:11:17 +08:00 committed by GitHub
commit d2a5045784
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 1230 additions and 127 deletions

View File

@ -5,11 +5,11 @@ import java.io.Serializable;
/**
* <p>
*
* 组件图表表
* </p>
*
* @author fit2cloud
* @since 2023-08-20
* @since 2024-05-07
*/
@TableName("core_chart_view")
public class CoreChartView implements Serializable {
@ -191,10 +191,21 @@ public class CoreChartView implements Serializable {
*/
private Boolean jumpActive;
/**
* 复制来源
*/
private Long copyFrom;
/**
* 复制ID
*/
private Long copyId;
/**
* 区间条形图开启时间纬度开启聚合
*/
private Boolean aggregate;
public Long getId() {
return id;
}
@ -491,6 +502,14 @@ public class CoreChartView implements Serializable {
this.copyId = copyId;
}
public Boolean getAggregate() {
return aggregate;
}
public void setAggregate(Boolean aggregate) {
this.aggregate = aggregate;
}
@Override
public String toString() {
return "CoreChartView{" +
@ -531,6 +550,7 @@ public class CoreChartView implements Serializable {
", jumpActive = " + jumpActive +
", copyFrom = " + copyFrom +
", copyId = " + copyId +
", aggregate = " + aggregate +
"}";
}
}

View File

@ -6,11 +6,11 @@ import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper 接口
* 组件图表表 Mapper 接口
* </p>
*
* @author fit2cloud
* @since 2023-08-20
* @since 2024-05-07
*/
@Mapper
public interface CoreChartViewMapper extends BaseMapper<CoreChartView> {

View File

@ -34,6 +34,7 @@ import io.dataease.utils.JsonUtil;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
@ -127,6 +128,35 @@ public class ChartDataManage {
List<ChartViewFieldDTO> yAxisExt = new ArrayList<>(view.getYAxisExt());
yAxis.addAll(yAxisExt);
}
boolean skipBarRange = false;
boolean barRangeDate = false;
if (StringUtils.equalsIgnoreCase(view.getType(), "bar-range")) { //针对区间条形图进行处理
yAxis.clear();
if (CollectionUtils.isNotEmpty(view.getYAxis()) && CollectionUtils.isNotEmpty(view.getYAxisExt())) {
ChartViewFieldDTO axis1 = view.getYAxis().get(0);
ChartViewFieldDTO axis2 = view.getYAxisExt().get(0);
if (StringUtils.equalsIgnoreCase(axis1.getGroupType(), "q") && StringUtils.equalsIgnoreCase(axis2.getGroupType(), "q")) {
yAxis.add(axis1);
yAxis.add(axis2);
} else if (StringUtils.equalsIgnoreCase(axis1.getGroupType(), "d") && axis1.getDeType() == 1 && StringUtils.equalsIgnoreCase(axis2.getGroupType(), "d") && axis2.getDeType() == 1) {
barRangeDate = true;
if (BooleanUtils.isTrue(view.getAggregate())) {
axis1.setSummary("min");
axis2.setSummary("max");
yAxis.add(axis1);
yAxis.add(axis2);
} else {
xAxis.add(axis1);
xAxis.add(axis2);
}
} else {
skipBarRange = true;
}
}
}
List<ChartViewFieldDTO> extStack = new ArrayList<>(view.getExtStack());
List<ChartViewFieldDTO> extBubble = new ArrayList<>(view.getExtBubble());
if (ObjectUtils.isNotEmpty(view.getExtLabel()) && enableExtData(view.getType())) {
@ -753,6 +783,8 @@ public class ChartDataManage {
mapChart = ChartDataBuild.transLabelChartData(xAxis, yAxis, view, data, isDrill);
} else if (StringUtils.containsIgnoreCase(view.getType(), "quadrant")) {
mapChart = ChartDataBuild.transQuadrantDataAntV(xAxis, yAxis, view, data, extBubble, isDrill);
} else if (StringUtils.equalsIgnoreCase(view.getType(), "bar-range")) {
mapChart = ChartDataBuild.transTimeBarDataAntV(skipBarRange, barRangeDate, xAxisBase, xAxis, yAxis, view, data, isDrill);
} else {
mapChart = ChartDataBuild.transChartDataAntV(xAxis, yAxis, view, data, isDrill);
}

View File

@ -1,13 +1,17 @@
package io.dataease.chart.utils;
import io.dataease.api.chart.dto.*;
import io.dataease.i18n.Lang;
import io.dataease.i18n.Translator;
import io.dataease.utils.IDUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
@ -1323,4 +1327,268 @@ public class ChartDataBuild {
return map;
}
public static Map<String, Object> transTimeBarDataAntV(boolean skipBarRange, boolean isDate, List<ChartViewFieldDTO> xAxisBase, List<ChartViewFieldDTO> xAxis, List<ChartViewFieldDTO> yAxis, ChartViewDTO view, List<String[]> data, boolean isDrill) {
Map<String, Object> map = new HashMap<>();
if (skipBarRange) {
map.put("data", new ArrayList<>());
return map;
}
List<Date> dates = new ArrayList<>();
List<BigDecimal> numbers = new ArrayList<>();
ChartViewFieldDTO dateAxis1 = null;
SimpleDateFormat sdf = null;
if (isDate) {
if (BooleanUtils.isTrue(view.getAggregate())) {
dateAxis1 = yAxis.get(0);
} else {
dateAxis1 = xAxis.get(xAxisBase.size());
}
sdf = new SimpleDateFormat(getDateFormat(dateAxis1.getDateStyle(), dateAxis1.getDatePattern()));
}
List<Object> dataList = new ArrayList<>();
for (int i1 = 0; i1 < data.size(); i1++) {
String[] row = data.get(i1);
StringBuilder xField = new StringBuilder();
if (isDrill) {
xField.append(row[xAxis.size() - 1]);
} else {
for (int i = 0; i < xAxisBase.size(); i++) {
if (i == xAxisBase.size() - 1) {
xField.append(row[i]);
} else {
xField.append(row[i]).append("\n");
}
}
}
Map<String, Object> obj = new HashMap<>();
obj.put("field", xField.toString());
obj.put("category", xField.toString());
List<ChartDimensionDTO> dimensionList = new ArrayList<>();
for (int i = 0; i < xAxisBase.size(); i++) {
ChartDimensionDTO chartDimensionDTO = new ChartDimensionDTO();
chartDimensionDTO.setId(xAxis.get(i).getId());
chartDimensionDTO.setValue(row[i]);
dimensionList.add(chartDimensionDTO);
}
if (isDrill) {
int index = xAxis.size() - 1;
ChartDimensionDTO chartDimensionDTO = new ChartDimensionDTO();
chartDimensionDTO.setId(xAxis.get(index).getId());
chartDimensionDTO.setValue(row[index]);
dimensionList.add(chartDimensionDTO);
}
obj.put("dimensionList", dimensionList);
List<Object> values = new ArrayList<>();
if (row[xAxisBase.size()] == null || row[xAxisBase.size() + 1] == null) {
continue;
}
if (isDate) {
int index;
if (BooleanUtils.isTrue(view.getAggregate())) {
index = xAxis.size();
} else {
index = xAxisBase.size();
}
values.add(row[index]);
values.add(row[index + 1]);
obj.put("values", values);
Date date1 = null, date2 = null;
try {
date1 = sdf.parse(row[index]);
if (date1 != null) {
dates.add(date1);
}
} catch (Exception ignore) {
}
try {
date2 = sdf.parse(row[index + 1]);
if (date2 != null) {
dates.add(date2);
}
} catch (Exception ignore) {
}
//间隔时间
obj.put("gap", getTimeGap(date1, date2, dateAxis1.getDateStyle()));
} else {
values.add(new BigDecimal(row[xAxis.size()]));
values.add(new BigDecimal(row[xAxis.size() + 1]));
obj.put("values", values);
numbers.add(new BigDecimal(row[xAxis.size()]));
numbers.add(new BigDecimal(row[xAxis.size() + 1]));
//间隔差
obj.put("gap", new BigDecimal(row[xAxis.size() + 1]).subtract(new BigDecimal(row[xAxis.size()])));
}
dataList.add(obj);
}
if (isDate) {
Date minDate = dates.stream().min(Date::compareTo).orElse(null);
if (minDate != null) {
map.put("minTime", sdf.format(minDate));
}
Date maxDate = dates.stream().max(Date::compareTo).orElse(null);
if (maxDate != null) {
map.put("maxTime", sdf.format(maxDate));
}
} else {
map.put("min", numbers.stream().min(BigDecimal::compareTo).orElse(null));
map.put("max", numbers.stream().max(BigDecimal::compareTo).orElse(null));
}
map.put("isDate", isDate);
map.put("data", dataList);
return map;
}
private static String getDateFormat(String dateStyle, String datePattern) {
String split;
if (StringUtils.equalsIgnoreCase(datePattern, "date_split")) {
split = "/";
} else {
split = "-";
}
switch (dateStyle) {
case "y":
return "yyyy";
case "y_M":
return "yyyy" + split + "MM";
case "y_M_d":
return "yyyy" + split + "MM" + split + "dd";
case "H_m_s":
return "HH:mm:ss";
case "y_M_d_H":
return "yyyy" + split + "MM" + split + "dd" + " HH";
case "y_M_d_H_m":
return "yyyy" + split + "MM" + split + "dd" + " HH:mm";
case "y_M_d_H_m_s":
return "yyyy" + split + "MM" + split + "dd" + " HH:mm:ss";
default:
return "yyyy-MM-dd HH:mm:ss";
}
}
private static String getTimeGap(Date from, Date to, String dateStyle) {
if (from == null || to == null) {
return "";
}
Calendar fromCalender = Calendar.getInstance();
fromCalender.setTime(from);
Calendar toCalender = Calendar.getInstance();
toCalender.setTime(to);
long yearGap = 0;
long monthGap = 0;
long dayGap = (toCalender.getTimeInMillis() - fromCalender.getTimeInMillis()) / (1000 * 3600 * 24);
long hourGap = ((toCalender.getTimeInMillis() - fromCalender.getTimeInMillis()) / (1000 * 3600)) % 24;
long minuteGap = ((toCalender.getTimeInMillis() - fromCalender.getTimeInMillis()) / (1000 * 60)) % 60;
long secondGap = ((toCalender.getTimeInMillis() - fromCalender.getTimeInMillis()) / 1000) % 60;
String language = "zh-CN"; //国际化
Lang lang = Lang.getLangWithoutDefault(language);
boolean isEnUs = Lang.en_US.equals(lang);
String splitter = isEnUs ? " " : "";
String yearGapStr = "";
String monthGapStr = "";
String dayGapStr = "";
if (dayGap != 0) {
dayGapStr = dayGap + splitter + Translator.get("i18n_day") + (isEnUs && dayGap != 1 ? "s" : "");
}
String hourGapStr = "";
if (hourGap != 0) {
hourGapStr = hourGap + splitter + Translator.get("i18n_hour") + (isEnUs && hourGap != 1 ? "s" : "");
}
String minuteGapStr = "";
if (minuteGap != 0) {
minuteGapStr = minuteGap + splitter + Translator.get("i18n_minute") + (isEnUs && minuteGap != 1 ? "s" : "");
}
String secondGapStr = "";
if (secondGap != 0) {
secondGapStr = secondGap + splitter + Translator.get("i18n_second") + (isEnUs && secondGap != 1 ? "s" : "");
}
List<String> list = new ArrayList<>();
switch (dateStyle) {
case "y":
yearGap = toCalender.get(Calendar.YEAR) - fromCalender.get(Calendar.YEAR);
yearGapStr = yearGap == 0 ? "" : (yearGap + splitter + Translator.get("i18n_year") + (isEnUs && yearGap != 1 ? "s" : ""));
return yearGapStr;
case "y_M":
yearGap = ((toCalender.get(Calendar.YEAR) - fromCalender.get(Calendar.YEAR)) * 12L + (toCalender.get(Calendar.MONTH) - fromCalender.get(Calendar.MONTH))) / 12;
monthGap = ((toCalender.get(Calendar.YEAR) - fromCalender.get(Calendar.YEAR)) * 12L + (toCalender.get(Calendar.MONTH) - fromCalender.get(Calendar.MONTH))) % 12;
yearGapStr = yearGap == 0 ? "" : (yearGap + splitter + Translator.get("i18n_year") + (isEnUs && yearGap != 1 ? "s" : ""));
monthGapStr = monthGap == 0 ? "" : (monthGap + splitter + Translator.get("i18n_month") + (isEnUs && monthGap != 1 ? "s" : ""));
if (!yearGapStr.isEmpty()) {
list.add(yearGapStr);
}
if (!monthGapStr.isEmpty()) {
list.add(monthGapStr);
}
return StringUtils.join(list, splitter);
case "y_M_d":
return dayGapStr;
case "y_M_d_H":
if (!dayGapStr.isEmpty()) {
list.add(dayGapStr);
}
if (!hourGapStr.isEmpty()) {
list.add(hourGapStr);
}
return StringUtils.join(list, splitter);
case "y_M_d_H_m":
if (!dayGapStr.isEmpty()) {
list.add(dayGapStr);
}
if (!hourGapStr.isEmpty()) {
list.add(hourGapStr);
}
if (!minuteGapStr.isEmpty()) {
list.add(minuteGapStr);
}
return StringUtils.join(list, splitter);
case "H_m_s":
case "y_M_d_H_m_s":
if (!dayGapStr.isEmpty()) {
list.add(dayGapStr);
}
if (!hourGapStr.isEmpty()) {
list.add(hourGapStr);
}
if (!minuteGapStr.isEmpty()) {
list.add(minuteGapStr);
}
if (!secondGapStr.isEmpty()) {
list.add(secondGapStr);
}
return StringUtils.join(list, splitter);
default:
return "";
}
}
}

View File

@ -51,3 +51,10 @@ i18n_error_login_type=error login type
i18n_schema_is_empty=Schema is empty!
i18n_table_name_repeat=Has duplicate name:
i18n_sql_not_empty=SQL cannot be empty!
i18n_year=Year
i18n_month=Month
i18n_day=Day
i18n_hour=Hour
i18n_minute=Minute
i18n_second=Second

View File

@ -64,3 +64,10 @@ i18n_sql_not_empty=sql \u4E0D\u80FD\u4E3A\u7A7A
i18n_menu.parameter=\u7CFB\u7EDF\u53C2\u6570
i18n_user_old_pwd_error=\u539F\u59CB\u5BC6\u7801\u9519\u8BEF
i18n_menu.toolbox-log=\u64CD\u4F5C\u65E5\u5FD7
i18n_year=\u5E74
i18n_month=\u6708
i18n_day=\u5929
i18n_hour=\u5C0F\u65F6
i18n_minute=\u5206\u949F
i18n_second=\u79D2

View File

@ -31,7 +31,7 @@ i18n_union_field_can_not_empty=\u95DC\u806F\u5B57\u6BB5\u4E0D\u80FD\u70BA\u7A7A
i18n_table_duplicate=\u76F8\u540C\u7BC0\u9EDE\u9700\u91CD\u65B0\u62D6\u5165\u624D\u80FD\u7E7C\u7E8C\u65B0\u5EFA\u6578\u64DA\u96C6
i18n_no_column_permission=\u6C92\u6709\u5217\u6B0A\u9650
i18n_fetch_error=SQL\u57F7\u884C\u5931\u6557\uFF0C\u8ACB\u6AA2\u67E5\u8868\u3001\u5B57\u6BB5\u3001\u95DC\u806F\u95DC\u7CFB\u7B49\u4FE1\u606F\u662F\u5426\u6B63\u78BA\u4E26\u91CD\u65B0\u7DE8\u8F2F\u3002
i18n_no_datasource_permission=\u65e0\u6570\u636e\u6e90\u8bbf\u95ee\u6743\u9650
i18n_no_datasource_permission=\u65E0\u6570\u636E\u6E90\u8BBF\u95EE\u6743\u9650
i18n_field_circular_ref=\u5B57\u6BB5\u5B58\u5728\u5FAA\u74B0\u5F15\u7528
@ -51,4 +51,11 @@ i18n_login_name_pwd_err=\u7528\u6236\u540D\u6216\u5BC6\u78BC\u932F\u8AA4
i18n_error_login_type=\u767B\u9304\u985E\u578B\u932F\u8AA4
i18n_schema_is_empty=schema\u70BA\u7A7A\uFF01
i18n_table_name_repeat=\u540D\u7A31\u91CD\u8907:
i18n_sql_not_empty=sql\u4e0d\u80fd\u70ba\u7a7a
i18n_sql_not_empty=sql\u4E0D\u80FD\u70BA\u7A7A
i18n_year=\u5E74
i18n_month=\u6708
i18n_day=\u5929
i18n_hour=\u5C0F\u6642
i18n_minute=\u5206\u9418
i18n_second=\u79D2

View File

@ -0,0 +1,7 @@
<svg width="80" height="56" viewBox="0 0 80 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 2.00391V53.0016V54.0016H4V2.00391H7Z" fill="#434343"/>
<path d="M17.5 50C17.2239 50 17 49.7761 17 49.5V42.5C17 42.2239 17.2239 42 17.5 42H52.5C52.7761 42 53 42.2239 53 42.5V49.5C53 49.7761 52.7761 50 52.5 50H17.5Z" fill="#00D6B9"/>
<path d="M48.5 38C48.2239 38 48 37.7761 48 37.5V30.5C48 30.2239 48.2239 30 48.5 30H71.5C71.7761 30 72 30.2239 72 30.5V37.5C72 37.7761 71.7761 38 71.5 38H48.5Z" fill="#3370FF"/>
<path d="M28.5 26C28.2239 26 28 25.7761 28 25.5V18.5C28 18.2239 28.2239 18 28.5 18H47.5C47.7761 18 48 18.2239 48 18.5V25.5C48 25.7761 47.7761 26 47.5 26H28.5Z" fill="#00D6B9"/>
<path d="M9.5 14C9.22386 14 9 13.7761 9 13.5V6.5C9 6.22386 9.22386 6 9.5 6L48.5 6C48.7761 6 49 6.22386 49 6.5V13.5C49 13.7761 48.7761 14 48.5 14L9.5 14Z" fill="#3370FF"/>
</svg>

After

Width:  |  Height:  |  Size: 912 B

View File

@ -0,0 +1,7 @@
<svg width="80" height="56" viewBox="0 0 80 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 2.00391V53.0016V54.0016H4V2.00391H7Z" fill="#DEE0E3"/>
<path d="M17.5 50C17.2239 50 17 49.7761 17 49.5V42.5C17 42.2239 17.2239 42 17.5 42H52.5C52.7761 42 53 42.2239 53 42.5V49.5C53 49.7761 52.7761 50 52.5 50H17.5Z" fill="#00D6B9"/>
<path d="M48.5 38C48.2239 38 48 37.7761 48 37.5V30.5C48 30.2239 48.2239 30 48.5 30H71.5C71.7761 30 72 30.2239 72 30.5V37.5C72 37.7761 71.7761 38 71.5 38H48.5Z" fill="#3370FF"/>
<path d="M28.5 26C28.2239 26 28 25.7761 28 25.5V18.5C28 18.2239 28.2239 18 28.5 18H47.5C47.7761 18 48 18.2239 48 18.5V25.5C48 25.7761 47.7761 26 47.5 26H28.5Z" fill="#00D6B9"/>
<path d="M9.5 14C9.22386 14 9 13.7761 9 13.5V6.5C9 6.22386 9.22386 6 9.5 6L48.5 6C48.7761 6 49 6.22386 49 6.5V13.5C49 13.7761 48.7761 14 48.5 14L9.5 14Z" fill="#3370FF"/>
</svg>

After

Width:  |  Height:  |  Size: 912 B

View File

@ -503,6 +503,8 @@ export default {
data_preview: '数据预览',
dimension: '维度',
quota: '指标',
time_dimension_or_quota: '时间维度或指标',
aggregate_time: '聚合时间纬度',
title: '标题',
show: '显示',
chart_type: '图表类型',
@ -678,6 +680,7 @@ export default {
chart_bar_horizontal: '横向柱状图',
chart_bar_stack_horizontal: '横向堆叠柱状图',
chart_percentage_bar_stack_horizontal: '横向百分比柱状图',
chart_bar_range: '区间条形图',
chart_line: '基础折线图',
chart_area_stack: '堆叠折线图',
chart_pie: '饼图',
@ -760,6 +763,8 @@ export default {
chart_style: '样式',
drag_block_type_axis: '类别轴',
drag_block_value_axis: '值轴',
drag_block_value_start: '开始值',
drag_block_value_end: '结束值',
drag_block_value_axis_left: '左值轴',
drag_block_value_axis_right: '右值轴',
drag_block_table_data_column: '数据列',
@ -939,6 +944,7 @@ export default {
value_formatter_unit: '数量单位',
value_formatter_decimal_count: '小数位数',
value_formatter_suffix: '单位后缀',
show_gap: '显示间隔值',
indicator_suffix_placeholder: '请输入1-10个字符',
indicator_suffix: '后缀',
indicator_value: '指标值',
@ -1101,6 +1107,8 @@ export default {
error_not_number: '不支持拖拽非数值类型指标',
error_q_2_d: '不支持拖拽指标至维度',
error_d_2_q: '不支持拖拽维度至指标',
error_d_not_time_2_q: '不支持拖拽非时间类型的维度',
error_bar_range_axis_type_not_equal: '开始值与结束值需要设置相同类型',
only_input_number: '请输入正确数值',
value_min_max_invalid: '最小值必须小于最大值',
add_assist_line: '添加辅助线',

View File

@ -659,6 +659,8 @@ declare interface ChartLabelAttr {
* 多系列标签设置
*/
seriesLabelFormatter: SeriesFormatter[]
showGap?: boolean
}
/**
* 提示设置
@ -690,6 +692,8 @@ declare interface ChartTooltipAttr {
* 多系列提示设置
*/
seriesTooltipFormatter: SeriesFormatter[]
showGap?: boolean
}
/**

View File

@ -40,6 +40,7 @@ declare interface Chart {
resultCount: number
linkageActive: boolean
jumpActive: boolean
aggregate?: boolean
}
declare type CustomAttr = DeepPartial<ChartAttr> | JSONString<DeepPartial<ChartAttr>>
declare type CustomStyle = DeepPartial<ChartStyle> | JSONString<DeepPartial<ChartStyle>>

View File

@ -110,6 +110,7 @@ const sort = param => {
item.value.index = props.index
item.value.sort = param.type
item.value.customSort = []
delete item.value.axisType
emit('onDimensionItemChange', item.value)
}
}
@ -122,6 +123,7 @@ const beforeSort = type => {
const dateStyle = param => {
item.value.dateStyle = param.type
item.value.axisType = props.type
emit('onDimensionItemChange', item.value)
}
@ -133,6 +135,7 @@ const beforeDateStyle = type => {
const datePattern = param => {
item.value.datePattern = param.type
item.value.axisType = props.type
emit('onDimensionItemChange', item.value)
}

View File

@ -204,8 +204,23 @@ const showSeriesLabelFormatter = computed(() => {
})
const showDivider = computed(() => {
const DIVIDER_PROPS = ['labelFormatter', 'showDimension', 'showQuota', 'showProportion']
return includesAny(props.propertyInner, ...DIVIDER_PROPS)
return includesAny(props.propertyInner, ...DIVIDER_PROPS) && !isBarRangeTime.value
})
const isBarRangeTime = computed<boolean>(() => {
if (props.chart.type === 'bar-range') {
const tempYAxis = props.chart.yAxis[0]
const tempYAxisExt = props.chart.yAxisExt[0]
if (
(tempYAxis && tempYAxis.groupType === 'd') ||
(tempYAxisExt && tempYAxisExt.groupType === 'd')
) {
return true
}
}
return false
})
onMounted(() => {
init()
})
@ -329,7 +344,7 @@ onMounted(() => {
:class="{ 'divider-dark': themes === 'dark' }"
v-if="showDivider"
/>
<template v-if="showProperty('labelFormatter')">
<template v-if="showProperty('labelFormatter') && !isBarRangeTime">
<el-form-item
:label="$t('chart.value_formatter_type')"
class="form-item"
@ -803,6 +818,15 @@ onMounted(() => {
</div>
</template>
</div>
<el-form-item class="form-item" :class="'form-item-' + themes" v-show="showProperty('showGap')">
<el-checkbox
:effect="themes"
@change="changeLabelAttr('showGap')"
v-model="state.labelForm.showGap"
>
{{ t('chart.show_gap') }}
</el-checkbox>
</el-form-item>
</el-form>
</template>

View File

@ -151,6 +151,21 @@ const aggregationList = computed(() => {
}
return AGGREGATION_TYPE
})
const isBarRangeTime = computed<boolean>(() => {
if (props.chart.type === 'bar-range') {
const tempYAxis = props.chart.yAxis[0]
const tempYAxisExt = props.chart.yAxisExt[0]
if (
(tempYAxis && tempYAxis.groupType === 'd') ||
(tempYAxisExt && tempYAxisExt.groupType === 'd')
) {
return true
}
}
return false
})
watch(
[() => props.chart.customAttr.tooltip, () => props.chart.customAttr.tooltip.show],
() => {
@ -425,7 +440,7 @@ onMounted(() => {
</el-tooltip>
</el-form-item>
</el-space>
<template v-if="showProperty('tooltipFormatter')">
<template v-if="showProperty('tooltipFormatter') && !isBarRangeTime">
<el-form-item
:label="t('chart.value_formatter_type')"
class="form-item"
@ -707,6 +722,15 @@ onMounted(() => {
</div>
</template>
</div>
<el-form-item class="form-item" :class="'form-item-' + themes" v-show="showProperty('showGap')">
<el-checkbox
:effect="themes"
@change="changeTooltipAttr('showGap')"
v-model="state.tooltipForm.showGap"
>
{{ t('chart.show_gap') }}
</el-checkbox>
</el-form-item>
</el-form>
</template>

View File

@ -52,6 +52,20 @@ const fontSizeList = computed(() => {
return arr
})
const isBarRangeTime = computed<boolean>(() => {
if (props.chart.type === 'bar-range') {
const tempYAxis = props.chart.yAxis[0]
const tempYAxisExt = props.chart.yAxisExt[0]
if (
(tempYAxis && tempYAxis.groupType === 'd') ||
(tempYAxisExt && tempYAxisExt.groupType === 'd')
) {
return true
}
}
return false
})
const changeAxisStyle = prop => {
if (
state.axisForm.axisValue.splitCount &&
@ -374,7 +388,7 @@ onMounted(() => {
/>
</el-form-item>
<template v-if="showProperty('axisLabelFormatter')">
<template v-if="showProperty('axisLabelFormatter') && !isBarRangeTime">
<el-form-item
class="form-item"
:class="'form-item-' + themes"

View File

@ -300,9 +300,22 @@ const startToMove = (e, item) => {
)
}
const dimensionItemChange = () => {
const dimensionItemChange = item => {
recordSnapshotInfo('calcData')
// do dimensionItemChange
if (view.value.type === 'bar-range') {
if (item.axisType === 'quota') {
view.value.yAxisExt?.forEach(y => {
y.dateStyle = item.dateStyle
y.datePattern = item.datePattern
})
} else if (item.axisType === 'quotaExt') {
view.value.yAxis?.forEach(y => {
y.dateStyle = item.dateStyle
y.datePattern = item.datePattern
})
}
}
}
const dimensionItemRemove = item => {
recordSnapshotInfo('calcData')
@ -312,6 +325,10 @@ const dimensionItemRemove = item => {
view.value.xAxisExt.splice(item.index, 1)
} else if (item.removeType === 'dimensionStack') {
view.value.extStack.splice(item.index, 1)
} else if (item.removeType === 'quota') {
view.value.yAxis.splice(item.index, 1)
} else if (item.removeType === 'quotaExt') {
view.value.yAxisExt.splice(item.index, 1)
}
}
@ -320,6 +337,11 @@ const quotaItemChange = (axis: Axis, axisType: AxisType) => {
// do quotaItemChange
emitter.emit('updateAxis', { axisType, axis: [axis], editType: 'update' })
}
const aggregateChange = () => {
recordSnapshotInfo('calcData')
}
const quotaItemRemove = item => {
recordSnapshotInfo('calcData')
let axisType: AxisType = item.removeType
@ -442,6 +464,20 @@ const dragRemoveAggField = (list, e) => {
}
}
const showAggregate = computed<boolean>(() => {
if (view.value.type === 'bar-range') {
const tempYAxis = view.value.yAxis[0]
const tempYAxisExt = view.value.yAxisExt[0]
if (
(tempYAxis && tempYAxis.groupType === 'd') ||
(tempYAxisExt && tempYAxisExt.groupType === 'd')
) {
return true
}
}
return false
})
const addAxis = (e, axis: AxisType) => {
recordSnapshotInfo('calcData')
const axisSpec = chartViewInstance.value.axisConfig[axis]
@ -450,9 +486,30 @@ const addAxis = (e, axis: AxisType) => {
}
const { type, limit, duplicate } = axisSpec
let typeValid, dup
if (type) {
if (view.value.type === 'bar-range' && (axis === 'yAxis' || axis === 'yAxisExt')) {
//
const list = view.value[axis]
if (list && list.length > 0) {
let valid = true
for (let i = 0; i < list.length; i++) {
if (!(list[i].groupType === 'q' || (list[i].groupType === 'd' && list[i].deType === 1))) {
list.splice(i, 1)
valid = false
}
}
if (!valid) {
ElMessage({
message: t('chart.error_d_not_time_2_q'),
type: 'warning'
})
}
typeValid = valid
}
} else if (type) {
typeValid = dragCheckType(view.value[axis], type)
}
//
if (typeValid && type === 'q' && view.value.type === 'indicator') {
const list = view.value[axis]
@ -528,16 +585,40 @@ const addAxis = (e, axis: AxisType) => {
}
}
if (view.value.type === 'indicator' || view.value.type === 'chart-mix') {
if (view.value?.yAxis?.length > 1) {
const axis = view.value.yAxis.splice(1)
emitter.emit('removeAxis', { axisType: 'yAxis', axis, editType: 'remove' })
if (typeValid && view.value.type === 'bar-range') {
//
let tempType = null
let tempDeType = null
if (axis === 'yAxis' && view.value.yAxisExt[0]) {
tempType = view.value.yAxisExt[0].groupType
tempDeType = view.value.yAxisExt[0].deType
} else if (axis === 'yAxisExt' && view.value.yAxis[0]) {
tempType = view.value.yAxis[0].groupType
tempDeType = view.value.yAxis[0].deType
}
}
if (view.value.type === 'chart-mix') {
if (view.value?.yAxisExt?.length > 1) {
const axis = view.value.yAxisExt.splice(1)
emitter.emit('removeAxis', { axisType: 'yAxisExt', axis, editType: 'remove' })
if (tempType !== null) {
const list = view.value[axis]
if (list && list.length > 0) {
let valid = true
for (let i = 0; i < list.length; i++) {
if (
!(
list[i].groupType === tempType &&
(tempType === 'q' || (tempType === 'd' && list[i].deType === tempDeType))
)
) {
list.splice(i, 1)
valid = false
}
}
if (!valid) {
ElMessage({
message: t('chart.error_bar_range_axis_type_not_equal'),
type: 'warning'
})
}
typeValid = valid
}
}
}
}
@ -1683,114 +1764,272 @@ const drop = (ev: MouseEvent, type = 'xAxis') => {
</div>
</el-row>
<!--yAxis-->
<el-row class="padding-lr drag-data" v-if="showAxis('yAxis')">
<div class="form-draggable-title">
<span>
{{ chartViewInstance.axisConfig.yAxis.name }}
</span>
<el-tooltip :effect="toolTip" placement="top" :content="t('common.delete')">
<el-icon
class="remove-icon"
:class="{ 'remove-icon--dark': themes === 'dark' }"
size="14px"
@click="removeItems('yAxis')"
<template v-if="view.type !== 'bar-range'">
<!--yAxis-->
<el-row class="padding-lr drag-data" v-if="showAxis('yAxis')">
<div class="form-draggable-title">
<span>
{{ chartViewInstance.axisConfig.yAxis.name }}
</span>
<el-tooltip
:effect="toolTip"
placement="top"
:content="t('common.delete')"
>
<Icon class-name="inner-class" name="icon_delete-trash_outlined" />
</el-icon>
</el-tooltip>
</div>
<div
@drop="$event => drop($event, 'yAxis')"
@dragenter="dragEnter"
@dragover="$event => dragOver($event)"
>
<draggable
:list="view.yAxis"
:move="onMove"
item-key="id"
group="drag"
animation="300"
class="drag-block-style"
:class="{ dark: themes === 'dark' }"
@add="addYaxis"
@change="e => onAxisChange(e, 'yAxis')"
<el-icon
class="remove-icon"
:class="{ 'remove-icon--dark': themes === 'dark' }"
size="14px"
@click="removeItems('yAxis')"
>
<Icon class-name="inner-class" name="icon_delete-trash_outlined" />
</el-icon>
</el-tooltip>
</div>
<div
@drop="$event => drop($event, 'yAxis')"
@dragenter="dragEnter"
@dragover="$event => dragOver($event)"
>
<template #item="{ element, index }">
<quota-item
:dimension-data="state.dimension"
:quota-data="state.quota"
:chart="view"
:item="element"
:index="index"
type="quota"
:themes="props.themes"
@onQuotaItemChange="item => quotaItemChange(item, 'yAxis')"
@onQuotaItemRemove="quotaItemRemove"
@onNameEdit="showRename"
@editItemFilter="showQuotaEditFilter"
@editItemCompare="showQuotaEditCompare"
@valueFormatter="valueFormatter"
/>
</template>
</draggable>
<drag-placeholder :drag-list="view.yAxis" />
</div>
</el-row>
<!--yAxisExt-->
<el-row class="padding-lr drag-data" v-if="showAxis('yAxisExt')">
<div class="form-draggable-title">
<span>
{{ chartViewInstance.axisConfig.yAxisExt.name }}
</span>
<el-tooltip :effect="toolTip" placement="top" :content="t('common.delete')">
<el-icon
class="remove-icon"
:class="{ 'remove-icon--dark': themes === 'dark' }"
size="14px"
@click="removeItems('yAxisExt')"
<draggable
:list="view.yAxis"
:move="onMove"
item-key="id"
group="drag"
animation="300"
class="drag-block-style"
:class="{ dark: themes === 'dark' }"
@add="addYaxis"
@change="e => onAxisChange(e, 'yAxis')"
>
<Icon class-name="inner-class" name="icon_delete-trash_outlined" />
</el-icon>
</el-tooltip>
</div>
<div
@drop="$event => drop($event, 'yAxisExt')"
@dragenter="dragEnter"
@dragover="$event => dragOver($event)"
>
<draggable
:list="view.yAxisExt"
:move="onMove"
item-key="id"
group="drag"
animation="300"
class="drag-block-style"
:class="{ dark: themes === 'dark' }"
@add="addYaxisExt"
@change="e => onAxisChange(e, 'yAxisExt')"
<template #item="{ element, index }">
<quota-item
:dimension-data="state.dimension"
:quota-data="state.quota"
:chart="view"
:item="element"
:index="index"
type="quota"
:themes="props.themes"
@onQuotaItemChange="item => quotaItemChange(item, 'yAxis')"
@onQuotaItemRemove="quotaItemRemove"
@onNameEdit="showRename"
@editItemFilter="showQuotaEditFilter"
@editItemCompare="showQuotaEditCompare"
@valueFormatter="valueFormatter"
/>
</template>
</draggable>
<drag-placeholder :drag-list="view.yAxis" />
</div>
</el-row>
<!--yAxisExt-->
<el-row class="padding-lr drag-data" v-if="showAxis('yAxisExt')">
<div class="form-draggable-title">
<span>
{{ chartViewInstance.axisConfig.yAxisExt.name }}
</span>
<el-tooltip
:effect="toolTip"
placement="top"
:content="t('common.delete')"
>
<el-icon
class="remove-icon"
:class="{ 'remove-icon--dark': themes === 'dark' }"
size="14px"
@click="removeItems('yAxisExt')"
>
<Icon class-name="inner-class" name="icon_delete-trash_outlined" />
</el-icon>
</el-tooltip>
</div>
<div
@drop="$event => drop($event, 'yAxisExt')"
@dragenter="dragEnter"
@dragover="$event => dragOver($event)"
>
<template #item="{ element, index }">
<quota-item
:dimension-data="state.dimension"
:quota-data="state.quota"
:chart="view"
:item="element"
:index="index"
type="quotaExt"
:themes="props.themes"
@onQuotaItemChange="item => quotaItemChange(item, 'yAxisExt')"
@onQuotaItemRemove="quotaItemRemove"
@onNameEdit="showRename"
@editItemFilter="showQuotaEditFilter"
@editItemCompare="showQuotaEditCompare"
@valueFormatter="valueFormatter"
/>
</template>
</draggable>
<drag-placeholder :drag-list="view.yAxisExt" />
</div>
</el-row>
<draggable
:list="view.yAxisExt"
:move="onMove"
item-key="id"
group="drag"
animation="300"
class="drag-block-style"
:class="{ dark: themes === 'dark' }"
@add="addYaxisExt"
@change="e => onAxisChange(e, 'yAxisExt')"
>
<template #item="{ element, index }">
<quota-item
:dimension-data="state.dimension"
:quota-data="state.quota"
:chart="view"
:item="element"
:index="index"
type="quotaExt"
:themes="props.themes"
@onQuotaItemChange="item => quotaItemChange(item, 'yAxisExt')"
@onQuotaItemRemove="quotaItemRemove"
@onNameEdit="showRename"
@editItemFilter="showQuotaEditFilter"
@editItemCompare="showQuotaEditCompare"
@valueFormatter="valueFormatter"
/>
</template>
</draggable>
<drag-placeholder :drag-list="view.yAxisExt" />
</div>
</el-row>
</template>
<template v-else-if="view.type === 'bar-range'">
<!--yAxis-->
<el-row class="padding-lr drag-data" v-if="showAxis('yAxis')">
<div class="form-draggable-title">
<span>
{{ chartViewInstance.axisConfig.yAxis.name }}
</span>
<el-tooltip
:effect="toolTip"
placement="top"
:content="t('common.delete')"
>
<el-icon
class="remove-icon"
:class="{ 'remove-icon--dark': themes === 'dark' }"
size="14px"
@click="removeItems('yAxis')"
>
<Icon class-name="inner-class" name="icon_delete-trash_outlined" />
</el-icon>
</el-tooltip>
</div>
<div
@drop="$event => drop($event, 'yAxis')"
@dragenter="dragEnter"
@dragover="$event => dragOver($event)"
>
<draggable
:list="view.yAxis"
:move="onMove"
item-key="id"
group="drag"
animation="300"
class="drag-block-style"
:class="{ dark: themes === 'dark' }"
@add="addYaxis"
@change="e => onAxisChange(e, 'yAxis')"
>
<template #item="{ element, index }">
<dimension-item
v-if="element.groupType === 'd'"
:dimension-data="state.dimension"
:quota-data="state.quota"
:chart="view"
:item="element"
:index="index"
:themes="props.themes"
type="quota"
@onDimensionItemChange="dimensionItemChange"
@onDimensionItemRemove="dimensionItemRemove"
@onNameEdit="showRename"
@onCustomSort="onExtCustomSort"
/>
<quota-item
v-else-if="element.groupType === 'q'"
:dimension-data="state.dimension"
:quota-data="state.quota"
:chart="view"
:item="element"
:index="index"
type="quota"
:themes="props.themes"
@onQuotaItemChange="item => quotaItemChange(item, 'yAxis')"
@onQuotaItemRemove="quotaItemRemove"
@onNameEdit="showRename"
@editItemFilter="showQuotaEditFilter"
@editItemCompare="showQuotaEditCompare"
@valueFormatter="valueFormatter"
/>
</template>
</draggable>
<drag-placeholder :drag-list="view.yAxis" />
</div>
</el-row>
<!--yAxisExt-->
<el-row class="padding-lr drag-data" v-if="showAxis('yAxisExt')">
<div class="form-draggable-title">
<span>
{{ chartViewInstance.axisConfig.yAxisExt.name }}
</span>
<el-tooltip
:effect="toolTip"
placement="top"
:content="t('common.delete')"
>
<el-icon
class="remove-icon"
:class="{ 'remove-icon--dark': themes === 'dark' }"
size="14px"
@click="removeItems('yAxisExt')"
>
<Icon class-name="inner-class" name="icon_delete-trash_outlined" />
</el-icon>
</el-tooltip>
</div>
<div
@drop="$event => drop($event, 'yAxisExt')"
@dragenter="dragEnter"
@dragover="$event => dragOver($event)"
>
<draggable
:list="view.yAxisExt"
:move="onMove"
item-key="id"
group="drag"
animation="300"
class="drag-block-style"
:class="{ dark: themes === 'dark' }"
@add="addYaxisExt"
@change="e => onAxisChange(e, 'yAxisExt')"
>
<template #item="{ element, index }">
<dimension-item
v-if="element.groupType === 'd'"
:dimension-data="state.dimension"
:quota-data="state.quota"
:chart="view"
:item="element"
:index="index"
:themes="props.themes"
type="quotaExt"
@onDimensionItemChange="dimensionItemChange"
@onDimensionItemRemove="dimensionItemRemove"
@onNameEdit="showRename"
@onCustomSort="onExtCustomSort"
/>
<quota-item
v-else-if="element.groupType === 'q'"
:dimension-data="state.dimension"
:quota-data="state.quota"
:chart="view"
:item="element"
:index="index"
type="quotaExt"
:themes="props.themes"
@onQuotaItemChange="item => quotaItemChange(item, 'yAxisExt')"
@onQuotaItemRemove="quotaItemRemove"
@onNameEdit="showRename"
@editItemFilter="showQuotaEditFilter"
@editItemCompare="showQuotaEditCompare"
@valueFormatter="valueFormatter"
/>
</template>
</draggable>
<drag-placeholder :drag-list="view.yAxisExt" />
</div>
</el-row>
</template>
<!-- extBubble -->
<el-row class="padding-lr drag-data" v-if="showAxis('extBubble')">
<div class="form-draggable-title">
@ -1955,6 +2194,22 @@ const drop = (ev: MouseEvent, type = 'xAxis') => {
<drag-placeholder :drag-list="view.customFilter" />
</div>
</el-row>
<el-row class="refresh-area" v-if="showAggregate">
<el-form-item
class="form-item no-margin-bottom"
:class="'form-item-' + themes"
>
<el-checkbox
:effect="themes"
size="small"
v-model="view.aggregate"
@change="aggregateChange"
>
{{ t('chart.aggregate_time') }}
</el-checkbox>
</el-form-item>
</el-row>
</el-scrollbar>
<el-footer :class="{ 'refresh-active-footer': view.refreshViewEnable }">
<el-row class="refresh-area">

View File

@ -1198,6 +1198,13 @@ export const CHART_TYPE_CONFIGS = [
value: 'percentage-bar-stack-horizontal',
title: t('chart.chart_percentage_bar_stack_horizontal'),
icon: 'percentage-bar-stack-horizontal'
},
{
render: 'antv',
category: 'compare',
value: 'bar-range',
title: t('chart.chart_bar_range'),
icon: 'bar-range'
}
]
},

View File

@ -12,6 +12,19 @@ export const BAR_EDITOR_PROPERTY: EditorProperty[] = [
'jump-set',
'linkage'
]
export const BAR_RANGE_EDITOR_PROPERTY: EditorProperty[] = [
'background-overall-component',
'basic-style-selector',
'label-selector',
'tooltip-selector',
'x-axis-selector',
'y-axis-selector',
'title-selector',
'legend-selector',
'function-cfg',
'jump-set',
'linkage'
]
export const BAR_EDITOR_PROPERTY_INNER: EditorPropertyInner = {
'background-overall-component': ['all'],

View File

@ -0,0 +1,383 @@
import {
G2PlotChartView,
G2PlotDrawOptions
} from '@/views/chart/components/js/panel/types/impl/g2plot'
import { Bar, BarOptions } from '@antv/g2plot/esm/plots/bar'
import { getPadding, setGradientColor } from '@/views/chart/components/js/panel/common/common_antv'
import { cloneDeep, find } from 'lodash-es'
import { flow, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util'
import { valueFormatter } from '@/views/chart/components/js/formatter'
import {
BAR_AXIS_TYPE,
BAR_RANGE_EDITOR_PROPERTY,
BAR_EDITOR_PROPERTY_INNER
} from '@/views/chart/components/js/panel/charts/bar/common'
import { Datum } from '@antv/g2plot/esm/types/common'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
const DEFAULT_DATA = []
/**
* 区间条形图
*/
export class RangeBar extends G2PlotChartView<BarOptions, Bar> {
axisConfig = {
...this['axisConfig'],
yAxis: {
name: `${t('chart.drag_block_value_start')} / ${t('chart.time_dimension_or_quota')}`,
limit: 1,
type: 'q'
},
yAxisExt: {
name: `${t('chart.drag_block_value_end')} / ${t('chart.time_dimension_or_quota')}`,
limit: 1,
type: 'q'
}
}
properties = BAR_RANGE_EDITOR_PROPERTY
propertyInner = {
...BAR_EDITOR_PROPERTY_INNER,
'label-selector': ['hPosition', 'color', 'fontSize', 'labelFormatter', 'showGap'],
'tooltip-selector': ['fontSize', 'color', 'backgroundColor', 'tooltipFormatter', 'showGap'],
'x-axis-selector': [...BAR_EDITOR_PROPERTY_INNER['x-axis-selector'], 'axisLabelFormatter']
}
axis: AxisType[] = [...BAR_AXIS_TYPE, 'yAxisExt']
protected baseOptions: BarOptions = {
data: [],
xField: 'values',
yField: 'field',
colorFiled: 'category',
isGroup: true,
interactions: [
{
type: 'legend-active',
cfg: {
start: [{ trigger: 'legend-item:mouseenter', action: ['element-active:reset'] }],
end: [{ trigger: 'legend-item:mouseleave', action: ['element-active:reset'] }]
}
},
{
type: 'legend-filter',
cfg: {
start: [
{
trigger: 'legend-item:click',
action: [
'list-unchecked:toggle',
'data-filter:filter',
'element-active:reset',
'element-highlight:reset'
]
}
]
}
},
{
type: 'tooltip',
cfg: {
start: [{ trigger: 'interval:mousemove', action: 'tooltip:show' }],
end: [{ trigger: 'interval:mouseleave', action: 'tooltip:hide' }]
}
},
{
type: 'active-region',
cfg: {
start: [{ trigger: 'interval:mousemove', action: 'active-region:show' }],
end: [{ trigger: 'interval:mouseleave', action: 'active-region:hide' }]
}
}
]
}
drawChart(drawOptions: G2PlotDrawOptions<Bar>): Bar {
const { chart, container, action } = drawOptions
if (!chart.data?.data?.length) {
return
}
// data
const data: Array<any> = cloneDeep(chart.data.data)
data.forEach(d => {
d.tempId = (Math.random() * 10000000).toString()
})
const ifAggregate = !!chart.aggregate
const isDate = !!chart.data.isDate
const minTime = chart.data.minTime
const maxTime = chart.data.maxTime
const minNumber = chart.data.min
const maxNumber = chart.data.max
// options
const initOptions: BarOptions = {
...this.baseOptions,
appendPadding: getPadding(chart),
data,
seriesField: isDate ? (ifAggregate ? 'category' : undefined) : 'category',
isGroup: isDate ? !ifAggregate : false,
isStack: isDate ? !ifAggregate : false,
meta: isDate
? {
values: {
type: 'time',
min: minTime,
max: maxTime,
mask: 'YYYY-MM-DD HH:mm:ss'
},
tempId: {
key: true
}
}
: {
values: {
min: minNumber,
max: maxNumber,
mask: 'YYYY-MM-DD HH:mm:ss'
},
tempId: {
key: true
}
}
}
const options = this.setupOptions(chart, initOptions)
// 开始渲染
const newChart = new Bar(container, options)
newChart.on('interval:click', action)
return newChart
}
protected configXAxis(chart: Chart, options: BarOptions): BarOptions {
const tmpOptions = super.configXAxis(chart, options)
if (!tmpOptions.xAxis) {
return tmpOptions
}
const xAxis = parseJson(chart.customStyle).xAxis
const axisValue = xAxis.axisValue
const isDate = !!chart.data.isDate
if (tmpOptions.xAxis.label) {
tmpOptions.xAxis.label.formatter = value => {
if (isDate) {
return value
}
return valueFormatter(value, xAxis.axisLabelFormatter)
}
}
if (tmpOptions.xAxis.position === 'top') {
tmpOptions.xAxis.position = 'left'
}
if (tmpOptions.xAxis.position === 'bottom') {
tmpOptions.xAxis.position = 'right'
}
if (!axisValue?.auto) {
const axis = {
xAxis: {
...tmpOptions.xAxis,
min: axisValue.min,
max: axisValue.max,
minLimit: axisValue.min,
maxLimit: axisValue.max,
tickCount: axisValue.splitCount
}
}
return { ...tmpOptions, ...axis }
}
return tmpOptions
}
protected configTooltip(chart: Chart, options: BarOptions): BarOptions {
const isDate = !!chart.data.isDate
let tooltip
let customAttr: DeepPartial<ChartAttr>
if (chart.customAttr) {
customAttr = parseJson(chart.customAttr)
// tooltip
if (customAttr.tooltip) {
const t = JSON.parse(JSON.stringify(customAttr.tooltip))
if (t.show) {
tooltip = {
formatter: function (param: Datum) {
let res
if (isDate) {
res = param.values[0] + ' ~ ' + param.values[1]
if (t.showGap) {
res = res + ' (' + param.gap + ')'
}
} else {
res =
valueFormatter(param.values[0], t.tooltipFormatter) +
' ~ ' +
valueFormatter(param.values[1], t.tooltipFormatter)
if (t.showGap) {
res = res + ' (' + valueFormatter(param.gap, t.tooltipFormatter) + ')'
}
}
return { value: res, values: param.values, name: param.field }
}
}
} else {
tooltip = false
}
}
}
return { ...options, tooltip }
}
protected configBasicStyle(chart: Chart, options: BarOptions): BarOptions {
const isDate = !!chart.data.isDate
const ifAggregate = !!chart.aggregate
const basicStyle = parseJson(chart.customAttr).basicStyle
if (isDate && !ifAggregate) {
const customColors = []
const groups = []
for (let i = 0; i < chart.data.data.length; i++) {
const name = chart.data.data[i].field
if (groups.indexOf(name) < 0) {
groups.push(name)
}
}
for (let i = 0; i < groups.length; i++) {
const s = groups[i]
customColors.push({
name: s,
color: basicStyle.colors[i % basicStyle.colors.length],
isCustom: false
})
}
const color = obj => {
const colorObj = find(customColors, o => {
return o.name === obj.field
})
if (colorObj === undefined) {
return undefined
}
const color = hexColorToRGBA(colorObj.color, basicStyle.alpha)
if (basicStyle.gradient) {
return setGradientColor(color, true)
} else {
return color
}
}
options = {
...options,
color
}
} else {
if (basicStyle.gradient) {
let color = basicStyle.colors
color = color.map(ele => {
const tmp = hexColorToRGBA(ele, basicStyle.alpha)
return setGradientColor(tmp, true)
})
options = {
...options,
color
}
}
}
return options
}
setupDefaultOptions(chart: ChartObj): ChartObj {
const { customAttr, senior } = chart
const { label } = customAttr
if (!['left', 'middle', 'right'].includes(label.position)) {
label.position = 'middle'
}
senior.functionCfg.emptyDataStrategy = 'ignoreData'
return chart
}
protected configLabel(chart: Chart, options: BarOptions): BarOptions {
const isDate = !!chart.data.isDate
const ifAggregate = !!chart.aggregate
const tmpOptions = super.configLabel(chart, options)
if (!tmpOptions.label) {
return {
...tmpOptions,
label: false
}
}
const labelAttr = parseJson(chart.customAttr).label
if (isDate && !ifAggregate) {
if (!tmpOptions.label.layout) {
tmpOptions.label.layout = []
}
tmpOptions.label.layout.push({ type: 'interval-hide-overlap' })
tmpOptions.label.layout.push({ type: 'limit-in-plot', cfg: { action: 'hide' } })
}
const label = {
fields: [],
...tmpOptions.label,
formatter: (param: Datum) => {
let res
if (isDate) {
if (labelAttr.showGap) {
res = param.gap
} else {
res = param.values[0] + ' ~ ' + param.values[1]
}
} else {
if (labelAttr.showGap) {
res = valueFormatter(param.gap, labelAttr.labelFormatter)
} else {
res =
valueFormatter(param.values[0], labelAttr.labelFormatter) +
' ~ ' +
valueFormatter(param.values[1], labelAttr.labelFormatter)
}
}
return res
}
}
return {
...tmpOptions,
label
}
}
protected configYAxis(chart: Chart, options: BarOptions): BarOptions {
const tmpOptions = super.configYAxis(chart, options)
if (!tmpOptions.yAxis) {
return tmpOptions
}
if (tmpOptions.yAxis.position === 'left') {
tmpOptions.yAxis.position = 'bottom'
}
if (tmpOptions.yAxis.position === 'right') {
tmpOptions.yAxis.position = 'top'
}
return tmpOptions
}
protected setupOptions(chart: Chart, options: BarOptions): BarOptions {
return flow(
this.configTheme,
this.configBasicStyle,
this.configLabel,
this.configTooltip,
this.configLegend,
this.configXAxis,
this.configYAxis,
this.configSlider,
this.configAnalyseHorizontal,
this.configEmptyDataStrategy
)(chart, options)
}
constructor(name = 'bar-range') {
super(name, DEFAULT_DATA)
}
}

View File

@ -44,10 +44,12 @@ export class ColumnLineMix extends G2PlotChartView<DualAxesOptions, DualAxes> {
...this['axisConfig'],
yAxis: {
name: `${t('chart.drag_block_value_axis_left')} / ${t('chart.quota')}`,
limit: 1,
type: 'q'
},
yAxisExt: {
name: `${t('chart.drag_block_value_axis_right')} / ${t('chart.quota')}`,
limit: 1,
type: 'q'
}
}

View File

@ -55,6 +55,7 @@ export class IndicatorChartView extends AbstractChartView {
axisConfig: AxisConfig = {
yAxis: {
name: `${t('chart.quota')}`,
limit: 1,
type: 'q'
}
}

View File

@ -226,8 +226,12 @@ const trackClick = trackAction => {
if (state.pointParam.data.dimensionList.length > 1) {
checkName = state.pointParam.data.dimensionList[0].id
}
const quotaList = state.pointParam.data.quotaList
quotaList[0]['value'] = state.pointParam.data.value
let quotaList = state.pointParam.data.quotaList
if (curView.type === 'bar-range') {
quotaList = state.pointParam.data.dimensionList
} else {
quotaList[0]['value'] = state.pointParam.data.value
}
const linkageParam = {
option: 'linkage',
name: checkName,

View File

@ -202,4 +202,9 @@ public class ChartViewBaseDTO implements Serializable {
*/
private Boolean jumpActive;
/**
* 区间条形图开启时间纬度开启聚合
*/
private Boolean aggregate;
}