forked from github/dataease
feat(图表): 新增桑基图
This commit is contained in:
parent
04fa61be25
commit
cbb48209f0
@ -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());
|
||||
|
5
core/core-frontend/src/assets/svg/sankey.svg
Normal file
5
core/core-frontend/src/assets/svg/sankey.svg
Normal 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 |
@ -690,6 +690,7 @@ export default {
|
||||
chart_pie_rose: '玫瑰图',
|
||||
chart_pie_donut_rose: '玫瑰环形图',
|
||||
chart_funnel: '漏斗图',
|
||||
chart_sankey: '桑基图',
|
||||
chart_radar: '雷达图',
|
||||
chart_gauge: '仪表盘',
|
||||
chart_map: '地图',
|
||||
@ -764,6 +765,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: '结束值',
|
||||
|
@ -1335,6 +1335,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'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -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'
|
||||
]
|
@ -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 不显示 tooltip,edge 显示 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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user