feat(仪表板): 支持象限图

This commit is contained in:
jianneng-fit2cloud 2024-04-09 18:42:12 +08:00
parent 85a5a929b3
commit ededcbfe38
11 changed files with 889 additions and 10 deletions

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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: '仅编辑时生效',

View File

@ -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
}

View File

@ -22,6 +22,7 @@ declare type EditorProperty =
| 'linkage'
| 'indicator-value-selector'
| 'indicator-name-selector'
| 'quadrant-selector'
declare type EditorPropertyInner = {
[key in EditorProperty]?: string[]
}

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -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'
}
]
},

View File

@ -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', [])
}
}