forked from github/dataease
feat(仪表板): 支持象限图
This commit is contained in:
parent
85a5a929b3
commit
ededcbfe38
@ -527,6 +527,20 @@ public class ChartDataManage {
|
||||
String limit = ((pageInfo.getGoPage() != null && pageInfo.getPageSize() != null) ? " LIMIT " + pageInfo.getPageSize() + " OFFSET " + (pageInfo.getGoPage() - 1) * pageInfo.getPageSize() : "");
|
||||
querySql = originSql + limit;
|
||||
totalPageSql = "SELECT COUNT(*) FROM (" + originSql + ") COUNT_TEMP";
|
||||
} else if (StringUtils.containsIgnoreCase(view.getType(), "quadrant")) {
|
||||
Dimension2SQLObj.dimension2sqlObj(sqlMeta, xAxis, transFields(allFields));
|
||||
yAxis.addAll(extBubble);
|
||||
Quota2SQLObj.quota2sqlObj(sqlMeta, yAxis, transFields(allFields));
|
||||
querySql = SQLProvider.createQuerySQL(sqlMeta, true, needOrder, view);
|
||||
if (containDetailField(view) && ObjectUtils.isNotEmpty(viewFields)) {
|
||||
detailFieldList.addAll(xAxis);
|
||||
detailFieldList.addAll(viewFields);
|
||||
|
||||
Dimension2SQLObj.dimension2sqlObj(sqlMeta, detailFieldList, transFields(allFields));
|
||||
String originSql = SQLProvider.createQuerySQL(sqlMeta, false, needOrder, view);
|
||||
String limit = ((pageInfo.getGoPage() != null && pageInfo.getPageSize() != null) ? " LIMIT " + pageInfo.getPageSize() + " OFFSET " + (pageInfo.getGoPage() - 1) * pageInfo.getPageSize() : "");
|
||||
detailFieldSql = originSql + limit;
|
||||
}
|
||||
} else {
|
||||
Dimension2SQLObj.dimension2sqlObj(sqlMeta, xAxis, transFields(allFields));
|
||||
Quota2SQLObj.quota2sqlObj(sqlMeta, yAxis, transFields(allFields));
|
||||
@ -733,6 +747,8 @@ public class ChartDataManage {
|
||||
mapChart = ChartDataBuild.transMixChartDataAntV(xAxis, yAxis, view, data, isDrill);
|
||||
} else if (StringUtils.containsIgnoreCase(view.getType(), "label")) {
|
||||
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 {
|
||||
mapChart = ChartDataBuild.transChartDataAntV(xAxis, yAxis, view, data, isDrill);
|
||||
}
|
||||
|
@ -1263,4 +1263,64 @@ public class ChartDataBuild {
|
||||
axisChartDataDTO.setDynamicLabelValue(dynamicLabelValue);
|
||||
axisChartDataDTO.setDynamicTooltipValue(dynamicTooltipValue);
|
||||
}
|
||||
|
||||
//AntV quadrant
|
||||
public static Map<String, Object> transQuadrantDataAntV(List<ChartViewFieldDTO> xAxis, List<ChartViewFieldDTO> yAxis, ChartViewDTO view, List<String[]> data, List<ChartViewFieldDTO> extBubble, boolean isDrill) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
|
||||
List<AxisChartDataAntVDTO> dataList = new ArrayList<>();
|
||||
for (int i1 = 0; i1 < data.size(); i1++) {
|
||||
String[] row = data.get(i1);
|
||||
|
||||
StringBuilder a = new StringBuilder();
|
||||
if (isDrill) {
|
||||
a.append(row[xAxis.size() - 1]);
|
||||
} else {
|
||||
for (int i = 0; i < xAxis.size(); i++) {
|
||||
if (i == xAxis.size() - 1) {
|
||||
a.append(row[i]);
|
||||
} else {
|
||||
a.append(row[i]).append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < xAxis.size() + yAxis.size(); i++) {
|
||||
AxisChartDataAntVDTO axisChartDataDTO = new AxisChartDataAntVDTO();
|
||||
axisChartDataDTO.setField(a.toString());
|
||||
axisChartDataDTO.setName(a.toString());
|
||||
|
||||
List<ChartDimensionDTO> dimensionList = new ArrayList<>();
|
||||
List<ChartQuotaDTO> quotaList = new ArrayList<>();
|
||||
|
||||
|
||||
for (int j = 0; j < xAxis.size(); j++) {
|
||||
ChartDimensionDTO chartDimensionDTO = new ChartDimensionDTO();
|
||||
chartDimensionDTO.setId(xAxis.get(j).getId());
|
||||
chartDimensionDTO.setValue(row[j]);
|
||||
dimensionList.add(chartDimensionDTO);
|
||||
}
|
||||
axisChartDataDTO.setDimensionList(dimensionList);
|
||||
|
||||
int j = i - xAxis.size();
|
||||
if (j > -1) {
|
||||
ChartQuotaDTO chartQuotaDTO = new ChartQuotaDTO();
|
||||
chartQuotaDTO.setId(yAxis.get(j).getId());
|
||||
quotaList.add(chartQuotaDTO);
|
||||
axisChartDataDTO.setQuotaList(quotaList);
|
||||
try {
|
||||
axisChartDataDTO.setValue(StringUtils.isEmpty(row[i]) ? null : new BigDecimal(row[i]));
|
||||
axisChartDataDTO.setField(yAxis.get(j).getOriginName());
|
||||
axisChartDataDTO.setName(yAxis.get(j).getName());
|
||||
} catch (Exception e) {
|
||||
axisChartDataDTO.setValue(new BigDecimal(0));
|
||||
}
|
||||
axisChartDataDTO.setCategory(StringUtils.defaultIfBlank(yAxis.get(j).getChartShowName(), yAxis.get(j).getName()));
|
||||
}
|
||||
dataList.add(axisChartDataDTO);
|
||||
}
|
||||
}
|
||||
map.put("data", dataList);
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,26 +3,20 @@ import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
|
||||
|
||||
import { storeToRefs } from 'pinia'
|
||||
import {onMounted, reactive} from "vue";
|
||||
import { onMounted, reactive } from 'vue'
|
||||
|
||||
|
||||
|
||||
const state = reactive({
|
||||
|
||||
})
|
||||
const state = reactive({})
|
||||
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const snapshotStore = snapshotStoreWithOut()
|
||||
|
||||
const { curComponent } = storeToRefs(dvMainStore)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-collapse-item :effect="themes" title="位置" name="position" v-if="!dashboardActive">
|
||||
<component-position :themes="themes" />
|
||||
</el-collapse-item>
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -1093,7 +1093,10 @@ export default {
|
||||
value_min_max_invalid: '最小值必须小于最大值',
|
||||
add_assist_line: '添加辅助线',
|
||||
add_threshold: '添加阈值',
|
||||
add_condition: '添加条件'
|
||||
add_condition: '添加条件',
|
||||
chart_quadrant: '象限图',
|
||||
quadrant: '象限',
|
||||
font_size: '字号'
|
||||
},
|
||||
dataset: {
|
||||
scope_edit: '仅编辑时生效',
|
||||
|
@ -34,6 +34,10 @@ declare interface ChartAttr {
|
||||
* 地图设置
|
||||
*/
|
||||
map: MapCfg
|
||||
/**
|
||||
* 象限设置
|
||||
*/
|
||||
quadrant: QuadrantAttr
|
||||
}
|
||||
/**
|
||||
* 基础样式设置
|
||||
@ -644,3 +648,57 @@ declare interface MapCfg {
|
||||
*/
|
||||
id: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 象限属性
|
||||
*/
|
||||
declare interface QuadrantAttr {
|
||||
lineStyle: QuadrantLineStyle
|
||||
regionStyle: QuadrantCommonStyle[]
|
||||
labels: QuadrantLabelConf[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 象限图label配置
|
||||
*/
|
||||
declare interface QuadrantLabelConf {
|
||||
content: string
|
||||
style: QuadrantCommonStyle
|
||||
}
|
||||
|
||||
/**
|
||||
* 象限公共样式
|
||||
*/
|
||||
declare interface QuadrantCommonStyle {
|
||||
/**
|
||||
* 颜色
|
||||
*/
|
||||
fill: string
|
||||
/**
|
||||
* 透明度
|
||||
*/
|
||||
fillOpacity: number
|
||||
|
||||
/**
|
||||
* 字体大小
|
||||
*/
|
||||
fontSize?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 象限分割线样式
|
||||
*/
|
||||
declare interface QuadrantLineStyle {
|
||||
/**
|
||||
* 颜色
|
||||
*/
|
||||
stroke: string
|
||||
/**
|
||||
* 宽度
|
||||
*/
|
||||
lineWidth: number
|
||||
/**
|
||||
* 透明度
|
||||
*/
|
||||
opacity: number
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ declare type EditorProperty =
|
||||
| 'linkage'
|
||||
| 'indicator-value-selector'
|
||||
| 'indicator-name-selector'
|
||||
| 'quadrant-selector'
|
||||
declare type EditorPropertyInner = {
|
||||
[key in EditorProperty]?: string[]
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import TableTotalSelector from '@/views/chart/components/editor/editor-style/com
|
||||
import MiscStyleSelector from '@/views/chart/components/editor/editor-style/components/MiscStyleSelector.vue'
|
||||
import IndicatorValueSelector from '@/views/chart/components/editor/editor-style/components/IndicatorValueSelector.vue'
|
||||
import IndicatorNameSelector from '@/views/chart/components/editor/editor-style/components/IndicatorNameSelector.vue'
|
||||
import QuadrantSelector from '@/views/chart/components/editor/editor-style/components/QuadrantSelector.vue'
|
||||
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const { dvInfo } = storeToRefs(dvMainStore)
|
||||
@ -87,7 +88,8 @@ const emit = defineEmits([
|
||||
'onChangeMiscStyleForm',
|
||||
'onExtTooltipChange',
|
||||
'onIndicatorChange',
|
||||
'onIndicatorNameChange'
|
||||
'onIndicatorNameChange',
|
||||
'onChangeQuadrantForm'
|
||||
])
|
||||
|
||||
const indicatorValueRef = ref()
|
||||
@ -162,6 +164,9 @@ const onChangeMiscStyleForm = (val, prop) => {
|
||||
const onExtTooltipChange = val => {
|
||||
emit('onExtTooltipChange', val)
|
||||
}
|
||||
const onChangeQuadrantForm = val => {
|
||||
emit('onChangeQuadrantForm', val)
|
||||
}
|
||||
watch(
|
||||
() => props.chart.id,
|
||||
() => {
|
||||
@ -386,6 +391,20 @@ watch(
|
||||
@onTableTotalChange="onTableTotalChange"
|
||||
/>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item
|
||||
:effect="themes"
|
||||
name="quadrant"
|
||||
:title="t('chart.quadrant')"
|
||||
v-if="showProperties('quadrant-selector')"
|
||||
>
|
||||
<quadrant-selector
|
||||
class="attr-selector"
|
||||
:property-inner="propertyInnerAll['quadrant-selector']"
|
||||
:themes="themes"
|
||||
:chart="chart"
|
||||
@onChangeQuadrantForm="onChangeQuadrantForm"
|
||||
/>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
||||
<el-collapse v-model="state.styleActiveNames" class="style-collapse">
|
||||
|
@ -0,0 +1,283 @@
|
||||
<script lang="tsx" setup>
|
||||
import { computed, onMounted, PropType, reactive, watch } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { COLOR_PANEL, DEFAULT_QUADRANT_STYLE } from '@/views/chart/components/editor/util/chart'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
chart: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
themes: {
|
||||
type: String as PropType<EditorTheme>,
|
||||
default: 'dark'
|
||||
},
|
||||
propertyInner: {
|
||||
type: Array<string>
|
||||
}
|
||||
})
|
||||
|
||||
const predefineColors = COLOR_PANEL
|
||||
|
||||
const state = reactive({
|
||||
quadrantForm: JSON.parse(JSON.stringify(DEFAULT_QUADRANT_STYLE))
|
||||
})
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
})
|
||||
const emit = defineEmits(['onChangeQuadrantForm'])
|
||||
|
||||
watch(
|
||||
() => props.chart.customAttr.quadrant,
|
||||
() => {
|
||||
init()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const fontSizeList = computed(() => {
|
||||
const arr = []
|
||||
for (let i = 10; i <= 40; i = i + 2) {
|
||||
arr.push({
|
||||
name: i + '',
|
||||
value: i
|
||||
})
|
||||
}
|
||||
return arr
|
||||
})
|
||||
|
||||
const fillOpacityList = computed(() => {
|
||||
const arr = []
|
||||
for (let i = 0; i <= 1; i = i + 0.1) {
|
||||
let c = i.toFixed(1)
|
||||
arr.push({
|
||||
name: c + '',
|
||||
value: c
|
||||
})
|
||||
}
|
||||
return arr
|
||||
})
|
||||
|
||||
const changeStyle = () => {
|
||||
emit('onChangeQuadrantForm', state.quadrantForm)
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
const chart = JSON.parse(JSON.stringify(props.chart))
|
||||
if (chart.customAttr) {
|
||||
let customAttr = null
|
||||
if (Object.prototype.toString.call(chart.customAttr) === '[object Object]') {
|
||||
customAttr = JSON.parse(JSON.stringify(chart.customAttr))
|
||||
} else {
|
||||
customAttr = JSON.parse(chart.customAttr)
|
||||
}
|
||||
if (customAttr.quadrant) {
|
||||
state.quadrantForm = customAttr.quadrant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const showProperty = prop => props.propertyInner?.includes(prop)
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form ref="quadrantForm" :model="state.quadrantForm" size="small" label-position="top">
|
||||
<template v-if="showProperty('lineStyle')">
|
||||
<label class="custom-form-item-label" :class="'custom-form-item-label--' + themes"
|
||||
>{{ t('chart.quadrant') }}{{ t('chart.split_line') }}</label
|
||||
>
|
||||
<div style="display: flex">
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-right: 4px">
|
||||
<el-color-picker
|
||||
v-model="state.quadrantForm.lineStyle.stroke"
|
||||
class="color-picker-style"
|
||||
:predefine="predefineColors"
|
||||
@change="changeStyle()"
|
||||
:effect="themes"
|
||||
is-custom
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-left: 4px">
|
||||
<el-tooltip :content="t('chart.alpha')" :effect="toolTip" placement="top">
|
||||
<el-select
|
||||
style="width: 53px"
|
||||
:effect="props.themes"
|
||||
v-model="state.quadrantForm.lineStyle.opacity"
|
||||
:placeholder="t('chart.alpha')"
|
||||
@change="changeStyle()"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in fillOpacityList"
|
||||
:key="option.value"
|
||||
:label="option.name"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-left: 4px">
|
||||
<el-tooltip :content="t('chart.funnel_width')" :effect="toolTip" placement="top">
|
||||
<el-input-number
|
||||
style="width: 108px"
|
||||
:effect="props.themes"
|
||||
v-model="state.quadrantForm.lineStyle.lineWidth"
|
||||
:min="1"
|
||||
:max="10"
|
||||
size="small"
|
||||
controls-position="right"
|
||||
@change="changeStyle()"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
<div
|
||||
v-for="(l, index) in state.quadrantForm.labels"
|
||||
:key="index"
|
||||
style="flex-direction: row; justify-content: space-between"
|
||||
>
|
||||
<el-divider
|
||||
class="m-divider"
|
||||
v-if="index < state.quadrantForm.labels.length"
|
||||
:class="'m-divider--' + themes"
|
||||
/>
|
||||
<label class="custom-form-item-label" :class="'custom-form-item-label--' + themes"
|
||||
>{{ t('chart.quadrant') }}{{ index + 1 }}</label
|
||||
>
|
||||
<template v-if="showProperty('regionStyle')">
|
||||
<div style="display: flex">
|
||||
<label class="custom-form-item-label" :class="'custom-form-item-label--' + themes">{{
|
||||
t('chart.backgroundColor')
|
||||
}}</label>
|
||||
</div>
|
||||
<div style="display: flex">
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-right: 4px">
|
||||
<el-color-picker
|
||||
v-model="state.quadrantForm.regionStyle[index].fill"
|
||||
class="color-picker-style"
|
||||
:predefine="predefineColors"
|
||||
@change="changeStyle()"
|
||||
:effect="themes"
|
||||
is-custom
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-left: 4px">
|
||||
<el-tooltip :content="t('chart.alpha')" :effect="toolTip" placement="top">
|
||||
<el-select
|
||||
style="width: 53px"
|
||||
:effect="props.themes"
|
||||
v-model="state.quadrantForm.regionStyle[index].fillOpacity"
|
||||
:placeholder="t('chart.alpha')"
|
||||
@change="changeStyle()"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in fillOpacityList"
|
||||
:key="option.value"
|
||||
:label="option.name"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="showProperty('label')">
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes" :label="t('chart.text')">
|
||||
<el-input
|
||||
:effect="props.themes"
|
||||
v-model="l.content"
|
||||
size="small"
|
||||
maxlength="50"
|
||||
@blur="changeStyle()"
|
||||
/>
|
||||
</el-form-item>
|
||||
<label class="custom-form-item-label" :class="'custom-form-item-label--' + themes">
|
||||
{{ t('chart.text') }}{{ t('chart.chart_style') }}</label
|
||||
>
|
||||
<div style="display: flex">
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-right: 4px">
|
||||
<el-color-picker
|
||||
v-model="l.style.fill"
|
||||
class="color-picker-style"
|
||||
:predefine="predefineColors"
|
||||
@change="changeStyle()"
|
||||
:effect="themes"
|
||||
is-custom
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-left: 4px">
|
||||
<el-tooltip :content="t('chart.alpha')" :effect="toolTip" placement="top">
|
||||
<el-select
|
||||
style="width: 53px"
|
||||
:effect="props.themes"
|
||||
v-model="l.style.fillOpacity"
|
||||
:placeholder="t('chart.alpha')"
|
||||
@change="changeStyle()"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in fillOpacityList"
|
||||
:key="option.value"
|
||||
:label="option.name"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-left: 4px">
|
||||
<el-tooltip :content="t('chart.font_size')" :effect="toolTip" placement="top">
|
||||
<el-select
|
||||
style="width: 108px"
|
||||
:effect="props.themes"
|
||||
v-model="l.style.fontSize"
|
||||
:placeholder="t('chart.axis_name_fontsize')"
|
||||
@change="changeStyle()"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in fontSizeList"
|
||||
:key="option.value"
|
||||
:label="option.name"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.custom-form-item-label {
|
||||
margin-bottom: 4px;
|
||||
line-height: 20px;
|
||||
color: #646a73;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
padding: 2px 12px 0 0;
|
||||
|
||||
&.custom-form-item-label--dark {
|
||||
color: #a6a6a6;
|
||||
}
|
||||
}
|
||||
|
||||
.form-item-checkbox {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
.m-divider {
|
||||
border-color: rgba(31, 35, 41, 0.15);
|
||||
margin: 0 0 16px;
|
||||
|
||||
&.m-divider--dark {
|
||||
border-color: rgba(235, 235, 235, 0.15);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -803,6 +803,10 @@ const onTableColumnWidthChange = val => {
|
||||
const onExtTooltipChange = val => {
|
||||
view.value.extTooltip = val
|
||||
}
|
||||
const onChangeQuadrantForm = val => {
|
||||
view.value.customAttr.quadrant = val
|
||||
renderChart(view.value)
|
||||
}
|
||||
|
||||
const showRename = val => {
|
||||
recordSnapshotInfo('render')
|
||||
@ -1799,6 +1803,7 @@ const onRefreshChange = val => {
|
||||
@onTableTotalChange="onTableTotalChange"
|
||||
@onChangeMiscStyleForm="onChangeMiscStyleForm"
|
||||
@onExtTooltipChange="onExtTooltipChange"
|
||||
@onChangeQuadrantForm="onChangeQuadrantForm"
|
||||
/>
|
||||
</el-scrollbar>
|
||||
</el-container>
|
||||
|
@ -665,6 +665,66 @@ export const DEFAULT_SCROLL: ScrollCfg = {
|
||||
step: 50
|
||||
}
|
||||
|
||||
export const DEFAULT_QUADRANT_STYLE: QuadrantAttr = {
|
||||
lineStyle: {
|
||||
stroke: '#aaa',
|
||||
lineWidth: 1,
|
||||
opacity: 0.5
|
||||
},
|
||||
regionStyle: [
|
||||
{
|
||||
fill: '#fdfcfc',
|
||||
fillOpacity: 0.5
|
||||
},
|
||||
{
|
||||
fill: '#fafdfa',
|
||||
fillOpacity: 0.5
|
||||
},
|
||||
{
|
||||
fill: '#fdfcfc',
|
||||
fillOpacity: 0.5
|
||||
},
|
||||
{
|
||||
fill: '#fafdfa',
|
||||
fillOpacity: 0.5
|
||||
}
|
||||
],
|
||||
labels: [
|
||||
{
|
||||
content: '',
|
||||
style: {
|
||||
fill: '#000000',
|
||||
fillOpacity: 0.5,
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
{
|
||||
content: '',
|
||||
style: {
|
||||
fill: '#000000',
|
||||
fillOpacity: 0.5,
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
{
|
||||
content: '',
|
||||
style: {
|
||||
fill: '#000000',
|
||||
fillOpacity: 0.5,
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
{
|
||||
content: '',
|
||||
style: {
|
||||
fill: '#000000',
|
||||
fillOpacity: 0.5,
|
||||
fontSize: 14
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const COLOR_PANEL = [
|
||||
'#FF4500',
|
||||
'#FF8C00',
|
||||
@ -1227,6 +1287,13 @@ export const CHART_TYPE_CONFIGS = [
|
||||
value: 'funnel',
|
||||
title: t('chart.chart_funnel'),
|
||||
icon: 'funnel'
|
||||
},
|
||||
{
|
||||
render: 'antv',
|
||||
category: 'distribute',
|
||||
value: 'quadrant',
|
||||
title: t('chart.chart_quadrant'),
|
||||
icon: 'scatter'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -0,0 +1,373 @@
|
||||
import {
|
||||
G2PlotChartView,
|
||||
G2PlotDrawOptions
|
||||
} from '@/views/chart/components/js/panel/types/impl/g2plot'
|
||||
import { ScatterOptions, Scatter as G2Scatter } from '@antv/g2plot/esm/plots/scatter'
|
||||
import { flow, parseJson } from '../../../util'
|
||||
import { valueFormatter } from '../../../formatter'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { isEmpty } from 'lodash-es'
|
||||
import { Datum } from '@antv/g2plot/esm/types/common'
|
||||
import { DEFAULT_QUADRANT_STYLE } from '@/views/chart/components/editor/util/chart'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
/**
|
||||
* 象限图
|
||||
*/
|
||||
export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
|
||||
properties: EditorProperty[] = [
|
||||
'background-overall-component',
|
||||
'basic-style-selector',
|
||||
'x-axis-selector',
|
||||
'y-axis-selector',
|
||||
'title-selector',
|
||||
'label-selector',
|
||||
'tooltip-selector',
|
||||
'legend-selector',
|
||||
'jump-set',
|
||||
'linkage',
|
||||
'quadrant-selector'
|
||||
]
|
||||
propertyInner: EditorPropertyInner = {
|
||||
'basic-style-selector': ['colors', 'alpha', 'scatterSymbol', 'scatterSymbolSize'],
|
||||
'label-selector': ['fontSize', 'color', 'labelFormatter'],
|
||||
'tooltip-selector': ['fontSize', 'color', 'backgroundColor', 'seriesTooltipFormatter'],
|
||||
'x-axis-selector': [
|
||||
'position',
|
||||
'name',
|
||||
'color',
|
||||
'fontSize',
|
||||
'axisLine',
|
||||
'axisValue',
|
||||
'splitLine',
|
||||
'axisForm',
|
||||
'axisLabel',
|
||||
'axisLabelFormatter'
|
||||
],
|
||||
'y-axis-selector': [
|
||||
'position',
|
||||
'name',
|
||||
'color',
|
||||
'fontSize',
|
||||
'axisValue',
|
||||
'axisLine',
|
||||
'splitLine',
|
||||
'axisForm',
|
||||
'axisLabel',
|
||||
'axisLabelFormatter'
|
||||
],
|
||||
'title-selector': [
|
||||
'title',
|
||||
'fontSize',
|
||||
'color',
|
||||
'hPosition',
|
||||
'isItalic',
|
||||
'isBolder',
|
||||
'remarkShow',
|
||||
'fontFamily',
|
||||
'letterSpace',
|
||||
'fontShadow'
|
||||
],
|
||||
'legend-selector': ['icon', 'orient', 'color', 'fontSize', 'hPosition', 'vPosition'],
|
||||
'quadrant-selector': ['regionStyle', 'label', 'lineStyle']
|
||||
}
|
||||
axis: AxisType[] = ['xAxis', 'yAxis', 'extBubble', 'filter', 'drill', 'extLabel', 'extTooltip']
|
||||
axisConfig: AxisConfig = {
|
||||
...this['axisConfig'],
|
||||
extBubble: {
|
||||
name: `${t('chart.bubble_size')} / ${t('chart.quota')}`,
|
||||
type: 'q',
|
||||
limit: 1
|
||||
},
|
||||
xAxis: {
|
||||
name: `${t('chart.drag_block_table_data_column')} / ${t('chart.dimension')}`,
|
||||
type: 'd',
|
||||
limit: 1
|
||||
},
|
||||
yAxis: {
|
||||
name: `${t('chart.drag_block_table_data_column')} / ${t('chart.quota')}`,
|
||||
type: 'q',
|
||||
limit: 2
|
||||
}
|
||||
}
|
||||
|
||||
public getFieldObject(chart: Chart) {
|
||||
const colorFieldObj = { id: chart.xAxis[0]?.id, name: chart.xAxis[0]?.['originName'] }
|
||||
const sizeFieldObj = { id: chart.extBubble[0]?.id, name: chart.extBubble[0]?.['originName'] }
|
||||
const xFieldObj = { id: chart.yAxis[0]?.id, name: chart.yAxis[0]?.['originName'] }
|
||||
const yFieldObj = { id: chart.yAxis[1]?.id, name: chart.yAxis[1]?.['originName'] }
|
||||
return { colorFieldObj, sizeFieldObj, xFieldObj, yFieldObj }
|
||||
}
|
||||
public getUniqueObjects<T>(arr: T[]): T[] {
|
||||
return [...new Set(arr.map(JSON.stringify))].map(JSON.parse) as T[]
|
||||
}
|
||||
|
||||
public drawChart(drawOptions: G2PlotDrawOptions<G2Scatter>) {
|
||||
const { chart, container, action } = drawOptions
|
||||
if (!chart.data?.data) {
|
||||
return
|
||||
}
|
||||
const { colorFieldObj, sizeFieldObj, xFieldObj, yFieldObj } = this.getFieldObject(chart)
|
||||
if (!xFieldObj.id || !yFieldObj.id) {
|
||||
return
|
||||
}
|
||||
const data: any[] = []
|
||||
// 根据指标字段对数据列表进行分组
|
||||
const groupedData = chart.data?.data
|
||||
?.filter(item => item['category'] != null)
|
||||
.reduce((result, item) => {
|
||||
;(result[item['field']] = result[item['field']] || []).push(item)
|
||||
return result
|
||||
}, {})
|
||||
// 维度字段数据分组
|
||||
chart.data?.data
|
||||
?.filter(item => item['category'] === null)
|
||||
.forEach(item => {
|
||||
;(groupedData[colorFieldObj.name] = groupedData[colorFieldObj.name] || []).push(
|
||||
item['field']
|
||||
)
|
||||
})
|
||||
// 去掉groupedData每个key中集合的对象重复项
|
||||
Object.keys(groupedData).forEach(key => {
|
||||
groupedData[key] = Array.from(this.getUniqueObjects(groupedData[key]))
|
||||
})
|
||||
// 一个指标字段的数据长度,视为数据长度,也就是有多少数据
|
||||
const dataLength = chart.data?.data.length / chart.data?.fields.length
|
||||
for (let index = 0; index < dataLength; index++) {
|
||||
const tmpData = {
|
||||
[xFieldObj.name]: groupedData[xFieldObj.name][index].value
|
||||
}
|
||||
if (groupedData[yFieldObj.name]) {
|
||||
tmpData[yFieldObj.name] = groupedData[yFieldObj.name][index].value
|
||||
}
|
||||
if (
|
||||
groupedData[sizeFieldObj.name] &&
|
||||
sizeFieldObj.name !== yFieldObj.name &&
|
||||
sizeFieldObj.name !== xFieldObj.name
|
||||
) {
|
||||
tmpData[sizeFieldObj.name] = groupedData[sizeFieldObj.name]?.[index].value
|
||||
}
|
||||
if (groupedData[colorFieldObj.name]) {
|
||||
tmpData[colorFieldObj.name] = groupedData[colorFieldObj.name][index]
|
||||
}
|
||||
data.push(tmpData)
|
||||
}
|
||||
chart.customAttr['quadrant'].xBaseline = (
|
||||
data.reduce((valueSoFar, currentItem) => {
|
||||
return valueSoFar + currentItem[xFieldObj.name]
|
||||
}, 0) / data.length
|
||||
).toFixed()
|
||||
chart.customAttr['quadrant'].yBaseline = (
|
||||
data.reduce((valueSoFar, currentItem) => {
|
||||
return valueSoFar + currentItem[yFieldObj.name]
|
||||
}, 0) / data.length
|
||||
).toFixed()
|
||||
const colorField = colorFieldObj.name ? { colorField: colorFieldObj.name } : {}
|
||||
const quadrant = chart.customAttr['quadrant'] ? { quadrant: chart.customAttr['quadrant'] } : {}
|
||||
const baseOptions: ScatterOptions = {
|
||||
...colorField,
|
||||
...quadrant,
|
||||
data: data,
|
||||
xField: xFieldObj.name,
|
||||
yField: yFieldObj.name,
|
||||
appendPadding: 30,
|
||||
pointStyle: {
|
||||
fillOpacity: 0.8,
|
||||
stroke: '#bbb'
|
||||
}
|
||||
}
|
||||
|
||||
const options = this.setupOptions(chart, baseOptions)
|
||||
const newChart = new G2Scatter(container, options)
|
||||
newChart.on('point:click', action)
|
||||
return newChart
|
||||
}
|
||||
|
||||
protected configBasicStyle(chart: Chart, options: ScatterOptions): ScatterOptions {
|
||||
const customAttr = parseJson(chart.customAttr)
|
||||
const basicStyle = customAttr.basicStyle
|
||||
const extBubbleObj = { id: chart.extBubble[0]?.id, name: chart.extBubble[0]?.['originName'] }
|
||||
if (chart.extBubble?.length) {
|
||||
return {
|
||||
...options,
|
||||
size: [5, 30],
|
||||
sizeField: extBubbleObj.name,
|
||||
shape: basicStyle.scatterSymbol
|
||||
}
|
||||
}
|
||||
return {
|
||||
...options,
|
||||
size: basicStyle.scatterSymbolSize,
|
||||
shape: basicStyle.scatterSymbol
|
||||
}
|
||||
}
|
||||
|
||||
protected configXAxis(chart: Chart, options: ScatterOptions): ScatterOptions {
|
||||
const tmpOptions = super.configXAxis(chart, options)
|
||||
if (!tmpOptions.xAxis) {
|
||||
return tmpOptions
|
||||
}
|
||||
const xAxis = parseJson(chart.customStyle).xAxis
|
||||
if (tmpOptions.xAxis.label) {
|
||||
tmpOptions.xAxis.label.formatter = value => {
|
||||
return valueFormatter(value, xAxis.axisLabelFormatter)
|
||||
}
|
||||
}
|
||||
const axisValue = xAxis.axisValue
|
||||
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 configYAxis(chart: Chart, options: ScatterOptions): ScatterOptions {
|
||||
const tmpOptions = super.configYAxis(chart, options)
|
||||
if (!tmpOptions.yAxis) {
|
||||
return tmpOptions
|
||||
}
|
||||
const yAxis = parseJson(chart.customStyle).yAxis
|
||||
if (tmpOptions.yAxis.label) {
|
||||
tmpOptions.yAxis.label.formatter = value => {
|
||||
return valueFormatter(value, yAxis.axisLabelFormatter)
|
||||
}
|
||||
}
|
||||
const axisValue = yAxis.axisValue
|
||||
if (!axisValue?.auto) {
|
||||
const axis = {
|
||||
yAxis: {
|
||||
...tmpOptions.yAxis,
|
||||
min: axisValue.min,
|
||||
max: axisValue.max,
|
||||
minLimit: axisValue.min,
|
||||
maxLimit: axisValue.max,
|
||||
tickCount: axisValue.splitCount
|
||||
}
|
||||
}
|
||||
return { ...tmpOptions, ...axis }
|
||||
}
|
||||
return tmpOptions
|
||||
}
|
||||
|
||||
protected configLabel(chart: Chart, options: ScatterOptions): ScatterOptions {
|
||||
let label
|
||||
let customAttr: DeepPartial<ChartAttr>
|
||||
if (chart.customAttr) {
|
||||
customAttr = parseJson(chart.customAttr)
|
||||
// label
|
||||
if (customAttr.label) {
|
||||
const l = customAttr.label
|
||||
if (l.show) {
|
||||
label = {
|
||||
position: l.position,
|
||||
offsetY: 5,
|
||||
style: {
|
||||
fill: l.color,
|
||||
fontSize: l.fontSize
|
||||
},
|
||||
formatter: function (param: Datum, item) {
|
||||
const text = String(param[chart.xAxis[0]?.['originName']])
|
||||
const radius = item.size
|
||||
const textWidth = text.length * 10
|
||||
return textWidth > 2 * radius ? '' : param[chart.xAxis[0]?.['originName']]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
label = false
|
||||
}
|
||||
}
|
||||
}
|
||||
return { ...options, label }
|
||||
}
|
||||
|
||||
protected configTooltip(chart: Chart, options: ScatterOptions): ScatterOptions {
|
||||
const customAttr: DeepPartial<ChartAttr> = parseJson(chart.customAttr)
|
||||
const tooltipAttr = customAttr.tooltip
|
||||
const xAxisTitle = chart.xAxis[0]
|
||||
const yAxisTitle = chart.yAxis[0]
|
||||
if (!tooltipAttr.show || (!xAxisTitle && !yAxisTitle)) {
|
||||
return {
|
||||
...options,
|
||||
tooltip: false
|
||||
}
|
||||
}
|
||||
xAxisTitle['show'] = true
|
||||
yAxisTitle['show'] = true
|
||||
tooltipAttr.seriesTooltipFormatter?.push(xAxisTitle)
|
||||
tooltipAttr.seriesTooltipFormatter?.push(yAxisTitle)
|
||||
const formatterMap = tooltipAttr.seriesTooltipFormatter
|
||||
?.filter(i => i.show)
|
||||
.reduce((pre, next) => {
|
||||
pre[next['originName']] = next
|
||||
return pre
|
||||
}, {}) as Record<string, SeriesFormatter>
|
||||
const tooltip: ScatterOptions['tooltip'] = {
|
||||
showTitle: false,
|
||||
customItems(originalItems) {
|
||||
if (!tooltipAttr.seriesTooltipFormatter?.length) {
|
||||
return originalItems
|
||||
}
|
||||
const result = []
|
||||
originalItems?.forEach(item => {
|
||||
const formatter = formatterMap[item.name]
|
||||
if (formatter) {
|
||||
const value =
|
||||
formatter.groupType === 'q'
|
||||
? valueFormatter(parseFloat(item.value as string), formatter.formatterCfg)
|
||||
: item.value
|
||||
const name = isEmpty(formatter.chartShowName) ? formatter.name : formatter.chartShowName
|
||||
result.push({ color: item.color, name, value })
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
}
|
||||
return {
|
||||
...options,
|
||||
tooltip
|
||||
}
|
||||
}
|
||||
|
||||
setupDefaultOptions(chart: ChartObj): ChartObj {
|
||||
chart.customStyle.yAxis.splitLine = {
|
||||
...chart.customStyle.yAxis.splitLine,
|
||||
show: false
|
||||
}
|
||||
chart.customStyle.yAxis.axisLine = {
|
||||
...chart.customStyle.yAxis.axisLine,
|
||||
show: true
|
||||
}
|
||||
chart.customAttr.quadrant = {
|
||||
...DEFAULT_QUADRANT_STYLE
|
||||
}
|
||||
return chart
|
||||
}
|
||||
|
||||
protected setupOptions(chart: Chart, options: ScatterOptions) {
|
||||
return flow(
|
||||
this.configTheme,
|
||||
this.configLabel,
|
||||
this.configTooltip,
|
||||
this.configLegend,
|
||||
this.configXAxis,
|
||||
this.configYAxis,
|
||||
this.configAnalyse,
|
||||
this.configSlider,
|
||||
this.configBasicStyle
|
||||
)(chart, options)
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super('quadrant', [])
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user