Merge pull request #9757 from ulleo/dev-v2

feat(图表): 新增桑基图
This commit is contained in:
ulleo 2024-05-21 17:44:31 +08:00 committed by GitHub
commit 006a8e34a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 337 additions and 1 deletions

View File

@ -115,7 +115,9 @@ public class ChartDataManage {
if (StringUtils.equalsIgnoreCase(view.getType(), "table-pivot")
|| StringUtils.containsIgnoreCase(view.getType(), "group")
|| ("antv".equalsIgnoreCase(view.getRender()) && "line".equalsIgnoreCase(view.getType()))
|| StringUtils.equalsIgnoreCase(view.getType(), "flow-map")) {
|| StringUtils.equalsIgnoreCase(view.getType(), "flow-map")
|| StringUtils.equalsIgnoreCase(view.getType(), "sankey")
) {
xAxis.addAll(xAxisExt);
}
List<ChartViewFieldDTO> yAxis = new ArrayList<>(view.getYAxis());

View File

@ -0,0 +1,5 @@
<svg width="80" height="56" viewBox="0 0 80 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 32.7862C35.0501 32.7862 43.9248 11.0972 67.002 10.375C67.0341 17.043 66.8736 16.9467 67.002 23.2296C45.7703 23.2296 43.0421 45.6408 13 45.6408C13.0642 40.0801 13.0963 40.0159 13 32.7862Z" fill="#00D6B9"/>
<path d="M13 27.4899C34.6891 27.1288 37.1926 46.0576 67.002 47.9994C67.0341 41.0987 66.8736 51.4819 67.002 44.9744C38.7974 42.3826 36.4624 24.4648 13 24.4648C13.0642 30.2502 13.0963 20.0275 13 27.4899Z" fill="#3370FF"/>
<path d="M13 20.9829C35.0501 20.9829 43.9247 40.2086 67.002 40.9629C67.8204 33.2036 66.8736 34.327 67.002 27.9799C45.7703 27.9799 43.0421 8 13 8C13.0642 13.6249 13.0963 13.7212 13 20.9829Z" fill="#3370FF"/>
</svg>

After

Width:  |  Height:  |  Size: 748 B

View File

@ -692,6 +692,7 @@ export default {
chart_pie_rose: '玫瑰图',
chart_pie_donut_rose: '玫瑰环形图',
chart_funnel: '漏斗图',
chart_sankey: '桑基图',
chart_radar: '雷达图',
chart_gauge: '仪表盘',
chart_map: '地图',
@ -766,6 +767,8 @@ export default {
chart_data: '数据',
chart_style: '样式',
drag_block_type_axis: '类别轴',
drag_block_type_axis_start: '源',
drag_block_type_axis_end: '目的',
drag_block_value_axis: '值轴',
drag_block_value_start: '开始值',
drag_block_value_end: '结束值',

View File

@ -1336,6 +1336,13 @@ export const CHART_TYPE_CONFIGS = [
value: 'funnel',
title: t('chart.chart_funnel'),
icon: 'funnel'
},
{
render: 'antv',
category: 'relation',
value: 'sankey',
title: t('chart.chart_sankey'),
icon: 'sankey'
}
]
},

View File

@ -0,0 +1,39 @@
export const SANKEY_EDITOR_PROPERTY: EditorProperty[] = [
'background-overall-component',
'basic-style-selector',
'label-selector',
'tooltip-selector',
'title-selector',
'function-cfg',
'jump-set',
'linkage'
]
export const SANKEY_EDITOR_PROPERTY_INNER: EditorPropertyInner = {
'background-overall-component': ['all'],
'basic-style-selector': ['colors', 'alpha', 'gradient'],
'label-selector': ['fontSize', 'color', 'labelFormatter'],
'tooltip-selector': ['fontSize', 'color', 'tooltipFormatter'],
'title-selector': [
'title',
'fontSize',
'color',
'hPosition',
'isItalic',
'isBolder',
'remarkShow',
'fontFamily',
'letterSpace',
'fontShadow'
],
'function-cfg': ['slider', 'emptyDataStrategy']
}
export const SANKEY_AXIS_TYPE: AxisType[] = [
'xAxis',
'xAxisExt',
'yAxis',
'filter',
'extLabel',
'extTooltip'
]

View File

@ -0,0 +1,280 @@
import {
G2PlotChartView,
G2PlotDrawOptions
} from '@/views/chart/components/js/panel/types/impl/g2plot'
import { Sankey, SankeyOptions } from '@antv/g2plot/esm/plots/sankey'
import { getPadding, setGradientColor } from '@/views/chart/components/js/panel/common/common_antv'
import { cloneDeep, get } from 'lodash-es'
import { flow, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util'
import { valueFormatter } from '@/views/chart/components/js/formatter'
import { Datum } from '@antv/g2plot/esm/types/common'
import { useI18n } from '@/hooks/web/useI18n'
import {
SANKEY_AXIS_TYPE,
SANKEY_EDITOR_PROPERTY,
SANKEY_EDITOR_PROPERTY_INNER
} from '@/views/chart/components/js/panel/charts/others/sankey-common'
const { t } = useI18n()
const DEFAULT_DATA = []
/**
* 区间条形图
*/
export class RangeBar extends G2PlotChartView<SankeyOptions, Sankey> {
axisConfig = {
...this['axisConfig'],
xAxis: {
name: `${t('chart.drag_block_type_axis_start')} / ${t('chart.dimension')}`,
limit: 1,
type: 'd'
},
xAxisExt: {
name: `${t('chart.drag_block_type_axis_end')} / ${t('chart.dimension')}`,
limit: 1,
type: 'd'
},
yAxis: {
name: `${t('chart.chart_data')} / ${t('chart.quota')}`,
limit: 1,
type: 'q'
}
}
properties = SANKEY_EDITOR_PROPERTY
propertyInner = {
...SANKEY_EDITOR_PROPERTY_INNER,
'label-selector': ['color', 'fontSize'],
'tooltip-selector': ['fontSize', 'color', 'backgroundColor', 'tooltipFormatter']
}
axis: AxisType[] = [...SANKEY_AXIS_TYPE]
protected baseOptions: SankeyOptions = {
data: [],
sourceField: 'source',
targetField: 'target',
weightField: 'value',
rawFields: ['dimensionList', 'quotaList'],
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<Sankey>): Sankey {
const { chart, container, action } = drawOptions
if (!chart.data?.data?.length) {
return
}
// data
const data: Array<any> = cloneDeep(chart.data.data)
data.forEach(d => {
if (d.dimensionList) {
if (d.dimensionList[0]) {
d.source = d.dimensionList[0].value
}
if (d.dimensionList[1]) {
d.target = d.dimensionList[1].value
}
}
})
// options
const initOptions: SankeyOptions = {
...this.baseOptions,
appendPadding: getPadding(chart),
data,
nodeSort: (a, b) => {
// 这里是前端自己排序
if (chart.yAxis && chart.yAxis[0]) {
if (chart.yAxis[0].sort === 'asc') {
return a.value - b.value
} else if (chart.yAxis[0].sort === 'desc') {
return b.value - a.value
}
}
if (chart.xAxis && chart.xAxis[0] && a.sourceLinks.length > 0) {
if (chart.xAxis[0].sort === 'custom_sort' && chart.xAxis[0].customSort) {
return (
chart.xAxis[0].customSort.indexOf(a.name) - chart.xAxis[0].customSort.indexOf(b.name)
)
} else if (chart.xAxis[0].sort === 'asc') {
return a.name.localeCompare(b.name)
} else if (chart.xAxis[0].sort === 'desc') {
return b.name.localeCompare(a.name)
}
}
if (chart.xAxisExt && chart.xAxisExt[0] && a.targetLinks.length > 0) {
if (chart.xAxisExt[0].sort === 'custom_sort' && chart.xAxisExt[0].customSort) {
return (
chart.xAxisExt[0].customSort.indexOf(a.name) -
chart.xAxisExt[0].customSort.indexOf(b.name)
)
} else if (chart.xAxisExt[0].sort === 'asc') {
return a.name.localeCompare(b.name)
} else if (chart.xAxisExt[0].sort === 'desc') {
return b.name.localeCompare(a.name)
}
}
return b.value - a.value
}
}
const options = this.setupOptions(chart, initOptions)
// 开始渲染
const newChart = new Sankey(container, options)
newChart.on('edge:click', action)
return newChart
}
protected configTooltip(chart: Chart, options: SankeyOptions): SankeyOptions {
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 = {
showTitle: false,
showMarkers: false,
shared: false,
// 内置node 不显示 tooltipedge 显示 tooltip
showContent: items => {
return !get(items, [0, 'data', 'isNode'])
},
formatter: (datum: Datum) => {
const { source, target, value } = datum
return {
name: source + ' -> ' + target,
value: valueFormatter(value, t.tooltipFormatter)
}
}
}
} else {
tooltip = false
}
}
}
return { ...options, tooltip }
}
protected configBasicStyle(chart: Chart, options: SankeyOptions): SankeyOptions {
const basicStyle = parseJson(chart.customAttr).basicStyle
let color = basicStyle.colors
color = color.map(ele => {
const tmp = hexColorToRGBA(ele, basicStyle.alpha)
if (basicStyle.gradient) {
return setGradientColor(tmp, true)
} else {
return tmp
}
})
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: SankeyOptions): SankeyOptions {
const labelAttr = parseJson(chart.customAttr).label
if (labelAttr.show) {
const label = {
//...tmpOptions.label,
formatter: ({ name }) => name,
callback: (x: number[]) => {
const isLast = x[1] === 1 // 最后一列靠边的节点
return {
style: {
fill: labelAttr.color,
fontSize: labelAttr.fontSize,
textAlign: isLast ? 'end' : 'start'
},
offsetX: isLast ? -8 : 8
}
},
layout: [{ type: 'hide-overlap' }, { type: 'limit-in-canvas' }]
}
return {
...options,
label
}
} else {
return {
...options,
label: false
}
}
}
protected setupOptions(chart: Chart, options: SankeyOptions): SankeyOptions {
return flow(
this.configTheme,
this.configBasicStyle,
this.configLabel,
this.configTooltip,
this.configLegend,
this.configSlider,
this.configAnalyseHorizontal,
this.configEmptyDataStrategy
)(chart, options)
}
constructor(name = 'sankey') {
super(name, DEFAULT_DATA)
}
}