import {
|
G2PlotChartView,
|
G2PlotDrawOptions
|
} from '@/views/chart/components/js/panel/types/impl/g2plot'
|
import type { ScatterOptions, Scatter as G2Scatter } from '@antv/g2plot/esm/plots/scatter'
|
import { flow, parseJson, setUpSingleDimensionSeriesColor } from '../../../util'
|
import { valueFormatter } from '@/views/chart/components/js/formatter'
|
import { useI18n } from '@/hooks/web/useI18n'
|
import { defaults, isEmpty, map } from 'lodash-es'
|
import { cloneDeep, defaultTo } from 'lodash-es'
|
import {
|
configAxisLabelLengthLimit,
|
configPlotTooltipEvent,
|
configYaxisTitleLengthLimit,
|
getTooltipContainer,
|
TOOLTIP_TPL,
|
getPadding
|
} from '../../common/common_antv'
|
import { DEFAULT_LEGEND_STYLE } from '@/views/chart/components/editor/util/chart'
|
|
const { t } = useI18n()
|
/**
|
* 象限图
|
*/
|
export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
|
properties: EditorProperty[] = [
|
'background-overall-component',
|
'border-style',
|
'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',
|
'seriesColor'
|
],
|
'label-selector': ['fontSize', 'color'],
|
'tooltip-selector': ['fontSize', 'color', 'backgroundColor', 'seriesTooltipFormatter', 'show'],
|
'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',
|
'yAxisExt',
|
'extBubble',
|
'filter',
|
'drill',
|
'extLabel',
|
'extTooltip'
|
]
|
axisConfig: AxisConfig = {
|
extBubble: {
|
name: `${t('chart.bubble_size')} / ${t('chart.quota')}`,
|
type: 'q',
|
limit: 1,
|
allowEmpty: true
|
},
|
xAxis: {
|
name: `${t('chart.form_type')} / ${t('chart.dimension')}`,
|
type: 'd',
|
limit: 1
|
},
|
yAxis: {
|
name: `${t('chart.x_axis')} / ${t('chart.quota')}`,
|
type: 'q',
|
limit: 1
|
},
|
yAxisExt: {
|
name: `${t('chart.y_axis')} / ${t('chart.quota')}`,
|
type: 'q',
|
limit: 1
|
}
|
}
|
|
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.yAxisExt[0]?.id, name: chart.yAxisExt[0]?.['originName'] }
|
return { colorFieldObj, sizeFieldObj, xFieldObj, yFieldObj }
|
}
|
public getUniqueObjects<T>(arr: T[]): T[] {
|
return [...new Set(arr.map(JSON.stringify))].map(JSON.parse) as T[]
|
}
|
|
async drawChart(drawOptions: G2PlotDrawOptions<G2Scatter>): Promise<G2Scatter> {
|
const { chart, container, action, quadrantDefaultBaseline } = drawOptions
|
if (!chart.data?.data) {
|
return
|
}
|
// data
|
const sourceData: Array<any> = cloneDeep(chart.data.data)
|
const data1 = defaultTo(sourceData[0]?.data, [])
|
const data2 = defaultTo(sourceData[1]?.data, [])
|
const data3 = defaultTo(sourceData[2]?.data, [])
|
const xData = data1.map(item => {
|
return {
|
...item,
|
id: item.quotaList[0]?.id,
|
field: item.field,
|
value: item.value
|
}
|
})
|
const yData = data2.map(item => {
|
return {
|
...item,
|
id: item.quotaList[0]?.id,
|
field: item.field,
|
value: item.value
|
}
|
})
|
const eData = data3.map(item => {
|
return {
|
...item,
|
id: item.quotaList[0]?.id,
|
field: item.field,
|
value: item.value
|
}
|
})
|
// x轴基准线 默认值
|
const xValues = xData.map(item => item.value)
|
const xBaseline = ((Math.max(...xValues) + Math.min(...xValues)) / 2).toFixed()
|
// y轴基准线 默认值
|
const yValues = yData.map(item => item.value)
|
const yBaseline = ((Math.max(...yValues) + Math.min(...yValues)) / 2).toFixed()
|
const defaultBaselineQuadrant = {
|
...chart.customAttr['quadrant']
|
}
|
// 新建图表
|
if (defaultBaselineQuadrant.xBaseline === undefined) {
|
// 默认基准线值
|
defaultBaselineQuadrant.xBaseline = xBaseline
|
defaultBaselineQuadrant.yBaseline = yBaseline
|
}
|
const getQuotaList = d => {
|
const eQuotaList = eData.find(item => item.field === d.field)?.quotaList
|
const yQuotaList = yData.find(item => item.field === d.field)?.quotaList
|
if (JSON.stringify(eQuotaList) === JSON.stringify(yQuotaList)) {
|
return yQuotaList
|
}
|
return [...(eQuotaList || []), ...(yQuotaList || [])]
|
}
|
const data = map(defaultTo(xData, []), d => {
|
return {
|
...d,
|
yAxis: d.value,
|
quotaList: getQuotaList(d),
|
yAxisExt: yData.find(item => item.field === d.field)?.value,
|
extBubble: eData.find(item => item.field === d.field)?.value
|
}
|
})
|
const baseOptions: ScatterOptions = {
|
colorField: 'field',
|
meta: {
|
field: {
|
type: 'cat'
|
}
|
},
|
quadrant: {
|
...defaultBaselineQuadrant
|
},
|
data: data,
|
xField: 'yAxis',
|
yField: 'yAxisExt',
|
appendPadding: getPadding(chart),
|
pointStyle: {
|
fillOpacity: 0.8,
|
stroke: '#bbb'
|
}
|
}
|
chart.container = container
|
const options = this.setupOptions(chart, baseOptions)
|
const { Scatter: G2Scatter } = await import('@antv/g2plot/esm/plots/scatter')
|
const newChart = new G2Scatter(container, options)
|
newChart.on('point:click', action)
|
if (options.label) {
|
newChart.on('label:click', action)
|
}
|
newChart.on('click', () => quadrantDefaultBaseline(defaultBaselineQuadrant))
|
newChart.on('afterrender', () => quadrantDefaultBaseline(defaultBaselineQuadrant))
|
const yAxis = parseJson(chart.customStyle).yAxis
|
if (yAxis?.name) {
|
configYaxisTitleLengthLimit(chart, newChart)
|
configAxisLabelLengthLimit(chart, newChart, 'axis-title')
|
}
|
configPlotTooltipEvent(chart, newChart)
|
return newChart
|
}
|
|
protected configBasicStyle(chart: Chart, options: ScatterOptions): ScatterOptions {
|
const customAttr = parseJson(chart.customAttr)
|
const basicStyle = customAttr.basicStyle
|
if (chart.extBubble?.length) {
|
return {
|
...options,
|
size: [4, 30],
|
sizeField: 'extBubble',
|
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) {
|
const layout = []
|
if (!l.fullDisplay) {
|
layout.push({ type: 'hide-overlap' })
|
layout.push({ type: 'limit-in-shape' })
|
}
|
label = {
|
offset: 0,
|
style: {
|
fill: l.color,
|
fontSize: l.fontSize
|
},
|
content: datum => {
|
return datum['name']
|
},
|
layout
|
}
|
} 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]
|
const yAxisExtTitle = chart.yAxisExt[0]
|
if (!tooltipAttr.show || (!xAxisTitle && !yAxisTitle && !yAxisExtTitle)) {
|
return {
|
...options,
|
tooltip: false
|
}
|
}
|
const formatterMap = tooltipAttr.seriesTooltipFormatter
|
?.filter(i => i.show)
|
.reduce((pre, next) => {
|
pre[next['seriesId']] = next
|
return pre
|
}, {}) as Record<string, SeriesFormatter>
|
const optionsData = cloneDeep(options.data)
|
const tooltip: ScatterOptions['tooltip'] = {
|
showTitle: true,
|
title: (_title, datum) => {
|
return datum?.['name']
|
},
|
customItems(originalItems) {
|
if (!tooltipAttr.seriesTooltipFormatter?.length) {
|
return originalItems
|
}
|
const result = []
|
originalItems.forEach(item => {
|
Object.keys(formatterMap).forEach(key => {
|
if (key.endsWith(item.name)) {
|
const formatter = formatterMap[key]
|
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 })
|
}
|
}
|
})
|
})
|
const dynamicTooltipValue = optionsData.find(
|
d => d.field === originalItems[0]['title']
|
)?.dynamicTooltipValue
|
if (dynamicTooltipValue.length > 0) {
|
dynamicTooltipValue.forEach(dy => {
|
const q = tooltipAttr.seriesTooltipFormatter.filter(i => i.id === dy.fieldId)
|
if (q && q.length > 0) {
|
const value = valueFormatter(parseFloat(dy.value as string), q[0].formatterCfg)
|
const name = isEmpty(q[0].chartShowName) ? q[0].name : q[0].chartShowName
|
result.push({ color: 'grey', name, value })
|
}
|
})
|
}
|
return result
|
},
|
container: getTooltipContainer(`tooltip-${chart.id}`),
|
itemTpl: TOOLTIP_TPL,
|
enterable: true
|
}
|
return {
|
...options,
|
tooltip
|
}
|
}
|
|
setupDefaultOptions(chart: ChartObj): ChartObj {
|
chart.customStyle.yAxis.splitLine = {
|
...chart.customStyle.yAxis.splitLine,
|
show: false
|
}
|
chart.customStyle.yAxisExt.splitLine = {
|
...chart.customStyle.yAxisExt.splitLine,
|
show: false
|
}
|
chart.customStyle.yAxis.axisLine = {
|
...chart.customStyle.yAxis.axisLine,
|
show: true
|
}
|
chart.customStyle.yAxisExt.axisLine = {
|
...chart.customStyle.yAxisExt.axisLine,
|
show: true
|
}
|
return chart
|
}
|
|
protected configColor(chart: Chart, options: ScatterOptions): ScatterOptions {
|
const { xAxis, yAxis, yAxisExt } = chart
|
if (!(xAxis?.length && yAxis?.length && yAxisExt?.length)) {
|
return options
|
}
|
return this.configSingleDimensionColor(chart, options)
|
}
|
|
public setupSeriesColor(chart: ChartObj, data?: any[]): ChartBasicStyle['seriesColor'] {
|
const { xAxis, yAxis, yAxisExt } = chart
|
if (!(xAxis?.length && yAxis?.length && yAxisExt?.length)) {
|
return []
|
}
|
const tmp = data?.[0]?.data
|
return setUpSingleDimensionSeriesColor(chart, tmp)
|
}
|
|
protected configLegend(chart: Chart, options: ScatterOptions): ScatterOptions {
|
const optionTmp = super.configLegend(chart, options)
|
if (!optionTmp.legend) {
|
return optionTmp
|
}
|
const customStyle = parseJson(chart.customStyle)
|
let size
|
if (customStyle && customStyle.legend) {
|
size = defaults(JSON.parse(JSON.stringify(customStyle.legend)), DEFAULT_LEGEND_STYLE).size
|
} else {
|
size = DEFAULT_LEGEND_STYLE.size
|
}
|
optionTmp.legend.marker.style = style => {
|
return {
|
r: size,
|
fill: style.fill
|
}
|
}
|
return optionTmp
|
}
|
|
protected setupOptions(chart: Chart, options: ScatterOptions) {
|
return flow(
|
this.configTheme,
|
this.configColor,
|
this.configLabel,
|
this.configTooltip,
|
this.configLegend,
|
this.configXAxis,
|
this.configYAxis,
|
this.configSlider,
|
this.configBasicStyle
|
)(chart, options, {}, this)
|
}
|
|
constructor() {
|
super('quadrant', [])
|
}
|
}
|