mirror of
https://github.com/dataease/dataease.git
synced 2026-05-16 22:41:06 +08:00
feat(图表): 汇总表支持表头分组 #16302
This commit is contained in:
@@ -347,7 +347,7 @@ public class ChartDataServer implements ChartDataApi {
|
||||
xAxis.addAll(viewInfo.getDrillFields());
|
||||
TableHeader tableHeader = null;
|
||||
Integer totalDepth = 0;
|
||||
if (viewInfo.getType().equalsIgnoreCase("table-normal") || viewInfo.getType().equalsIgnoreCase("table-info")) {
|
||||
if (StringUtils.equalsAnyIgnoreCase(viewInfo.getType(), "table-normal", "table-info")) {
|
||||
for (ChartViewFieldDTO xAxi : xAxis) {
|
||||
if (xAxi.getDeType().equals(DeTypeConstants.DE_INT) || xAxi.getDeType().equals(DeTypeConstants.DE_FLOAT)) {
|
||||
CellStyle formatterCellStyle = createCellStyle(wb, xAxi.getFormatterCfg(), null);
|
||||
@@ -362,7 +362,11 @@ public class ChartDataServer implements ChartDataApi {
|
||||
if (tableHeaderMap.get("headerGroup") != null && Boolean.parseBoolean(tableHeaderMap.get("headerGroup").toString())) {
|
||||
var tmpHeader = JsonUtil.parseObject((String) JsonUtil.toJSONString(customAttr.get("tableHeader")), TableHeader.class);
|
||||
// 校验字段数量和顺序
|
||||
if (validateHeaderGroup(tmpHeader, viewInfo.getXAxis())) {
|
||||
var allAxis = new ArrayList<>(viewInfo.getXAxis().stream().filter(x -> !x.isHide()).toList());
|
||||
if (StringUtils.equalsIgnoreCase(viewInfo.getType(), "table-normal")) {
|
||||
allAxis.addAll(viewInfo.getYAxis().stream().filter(x -> !x.isHide()).toList());
|
||||
}
|
||||
if (validateHeaderGroup(tmpHeader, allAxis)) {
|
||||
tableHeader = tmpHeader;
|
||||
for (TableHeader.ColumnInfo column : tableHeader.getHeaderGroupConfig().getColumns()) {
|
||||
totalDepth = Math.max(totalDepth, getDepth(column, 1));
|
||||
|
||||
@@ -52,22 +52,27 @@ const emits = defineEmits(['onConfigChange', 'onCancelConfig'])
|
||||
const onCancelConfig = () => {
|
||||
emits('onCancelConfig')
|
||||
}
|
||||
|
||||
const allAxis = computed(() => {
|
||||
const axis = [...props.chart.xAxis]
|
||||
if (props.chart.type === 'table-normal') {
|
||||
axis.push(...props.chart.yAxis)
|
||||
}
|
||||
return axis
|
||||
})
|
||||
const onConfigChange = () => {
|
||||
const allAxis = props.chart.xAxis
|
||||
const showAxis = allAxis.value
|
||||
?.map(axis => axis.hide !== true && axis.dataeaseName)
|
||||
.filter(i => i)
|
||||
const { fields, meta } = s2.dataCfg
|
||||
const groupMeta = meta.filter(item => !allAxis.includes(item.field))
|
||||
const groupMeta = meta.filter(item => !showAxis.includes(item.field))
|
||||
emits('onConfigChange', { columns: fields.columns, meta: groupMeta })
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
const chart = cloneDeep(props.chart)
|
||||
const xAxis = chart.xAxis
|
||||
const { headerGroupConfig } = chart.customAttr.tableHeader
|
||||
const showColumns = []
|
||||
xAxis?.forEach(axis => {
|
||||
allAxis.value?.forEach(axis => {
|
||||
axis.hide !== true && showColumns.push({ key: axis.dataeaseName })
|
||||
})
|
||||
if (!showColumns.length) {
|
||||
@@ -109,7 +114,7 @@ const renderTable = (chart: ChartObj) => {
|
||||
const { headerGroupConfig } = chart.customAttr.tableHeader
|
||||
const meta = [...headerGroupConfig.meta]
|
||||
const columns = headerGroupConfig.columns
|
||||
const axisMap = chart.xAxis.reduce((pre, cur) => {
|
||||
const axisMap = allAxis.value.reduce((pre, cur) => {
|
||||
pre[cur.dataeaseName] = cur
|
||||
return pre
|
||||
}, {})
|
||||
@@ -141,7 +146,7 @@ const renderTable = (chart: ChartObj) => {
|
||||
})
|
||||
})
|
||||
} else {
|
||||
chart.xAxis?.forEach(axis => {
|
||||
allAxis.value?.forEach(axis => {
|
||||
if (axis.hide !== true) {
|
||||
meta.push({
|
||||
field: axis.dataeaseName,
|
||||
|
||||
@@ -96,18 +96,21 @@ const groupConfigValid = computed(() => {
|
||||
if (noGroup) {
|
||||
return false
|
||||
}
|
||||
const xAxis = props.chart.xAxis
|
||||
const allAxis = [...props.chart?.xAxis]
|
||||
if (props.chart.type === 'table-normal') {
|
||||
allAxis.push(...props.chart?.yAxis)
|
||||
}
|
||||
const showColumns = []
|
||||
xAxis?.forEach(axis => {
|
||||
allAxis?.forEach(axis => {
|
||||
axis.hide !== true && showColumns.push({ key: axis.dataeaseName })
|
||||
})
|
||||
if (!showColumns.length) {
|
||||
return false
|
||||
}
|
||||
const allAxis = showColumns.map(item => item.key)
|
||||
const showColumnFields = showColumns.map(item => item.key)
|
||||
const leafNodes = getLeafNodes(columns as Array<ColumnNode>)
|
||||
const leafKeys = leafNodes.map(item => item.key)
|
||||
return isEqual(allAxis, leafKeys)
|
||||
return isEqual(showColumnFields, leafKeys)
|
||||
})
|
||||
const init = () => {
|
||||
const tableHeader = props.chart?.customAttr?.tableHeader
|
||||
|
||||
@@ -27,7 +27,10 @@ import {
|
||||
getColumns,
|
||||
drawImage,
|
||||
getSummaryRow,
|
||||
SummaryCell
|
||||
SummaryCell,
|
||||
summaryRowStyle,
|
||||
calcTreeWidth,
|
||||
getStartPosition
|
||||
} from '@/views/chart/components/js/panel/common/common_table'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -229,7 +232,7 @@ export class TableInfo extends S2ChartView<TableSheet> {
|
||||
// 开始渲染
|
||||
const newChart = new TableSheet(containerDom, s2DataConfig, s2Options)
|
||||
// 总计紧贴在单元格后面
|
||||
this.summaryRowStyle(newChart, newData, tableCell, tableHeader, basicStyle.showSummary)
|
||||
summaryRowStyle(newChart, newData, tableCell, tableHeader, basicStyle.showSummary)
|
||||
// 开启自动换行
|
||||
if (basicStyle.autoWrap && !tableCell.mergeCells) {
|
||||
// 调整表头宽度时,计算表头高度
|
||||
@@ -512,54 +515,7 @@ export class TableInfo extends S2ChartView<TableSheet> {
|
||||
}
|
||||
}
|
||||
|
||||
protected summaryRowStyle(newChart: TableSheet, newData, tableCell, tableHeader, showSummary) {
|
||||
if (!showSummary || !newData.length) return
|
||||
const columns = newChart.dataCfg.fields.columns
|
||||
const showHeader = tableHeader.showTableHeader === true
|
||||
// 不显示表头时,减少一个表头的高度
|
||||
const headerAndSummaryHeight = showHeader ? getMaxTreeDepth(columns) + 1 : 1
|
||||
newChart.on(S2Event.LAYOUT_BEFORE_RENDER, () => {
|
||||
const totalHeight =
|
||||
tableHeader.tableTitleHeight * headerAndSummaryHeight +
|
||||
tableCell.tableItemHeight * (newData.length - 1)
|
||||
if (totalHeight < newChart.container.cfg.height) {
|
||||
newChart.options.height =
|
||||
totalHeight < newChart.container.cfg.height - 8 ? totalHeight + 8 : totalHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super('table-info', [])
|
||||
}
|
||||
}
|
||||
|
||||
function calcTreeWidth(node) {
|
||||
if (!node.children?.length) {
|
||||
return node.width
|
||||
}
|
||||
return node.children.reduce((pre, cur) => {
|
||||
return pre + calcTreeWidth(cur)
|
||||
}, 0)
|
||||
}
|
||||
|
||||
function getStartPosition(node) {
|
||||
if (!node.children?.length) {
|
||||
return node.x
|
||||
}
|
||||
return getStartPosition(node.children[0])
|
||||
}
|
||||
|
||||
function getMaxTreeDepth(nodes) {
|
||||
if (!nodes?.length) {
|
||||
return 0
|
||||
}
|
||||
return Math.max(
|
||||
...nodes.map(node => {
|
||||
if (!node.children?.length) {
|
||||
return 1
|
||||
}
|
||||
return getMaxTreeDepth(node.children) + 1
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,12 @@ import {
|
||||
CustomDataCell,
|
||||
getSummaryRow,
|
||||
SortTooltip,
|
||||
SummaryCell
|
||||
SummaryCell,
|
||||
getLeafNodes,
|
||||
getColumns,
|
||||
summaryRowStyle,
|
||||
calcTreeWidth,
|
||||
getStartPosition
|
||||
} from '@/views/chart/components/js/panel/common/common_table'
|
||||
import { S2ChartView, S2DrawOptions } from '@/views/chart/components/js/panel/types/impl/s2'
|
||||
import { parseJson } from '@/views/chart/components/js/util'
|
||||
@@ -20,7 +25,7 @@ import {
|
||||
TableSheet,
|
||||
ViewMeta
|
||||
} from '@antv/s2'
|
||||
import { isNumber } from 'lodash-es'
|
||||
import { isNumber, isEqual } from 'lodash-es'
|
||||
import { TABLE_EDITOR_PROPERTY, TABLE_EDITOR_PROPERTY_INNER } from './common'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -34,7 +39,8 @@ export class TableNormal extends S2ChartView<TableSheet> {
|
||||
'table-header-selector': [
|
||||
...TABLE_EDITOR_PROPERTY_INNER['table-header-selector'],
|
||||
'tableHeaderSort',
|
||||
'showTableHeader'
|
||||
'showTableHeader',
|
||||
'headerGroup'
|
||||
],
|
||||
'basic-style-selector': [
|
||||
...TABLE_EDITOR_PROPERTY_INNER['basic-style-selector'],
|
||||
@@ -76,6 +82,7 @@ export class TableNormal extends S2ChartView<TableSheet> {
|
||||
|
||||
const columns = []
|
||||
const meta = []
|
||||
const drillFieldMap = {}
|
||||
if (chart.drill) {
|
||||
// 下钻过滤字段
|
||||
const filterFields = chart.drillFilters.map(i => i.fieldId)
|
||||
@@ -84,13 +91,14 @@ export class TableNormal extends S2ChartView<TableSheet> {
|
||||
const drillFieldIndex = chart.xAxis.findIndex(ele => ele.id === drillFieldId)
|
||||
// 当前下钻字段
|
||||
const curDrillFieldId = chart.drillFields[filterFields.length].id
|
||||
const curDrillField = fields.filter(ele => ele.id === curDrillFieldId)
|
||||
const curDrillField = fields.find(ele => ele.id === curDrillFieldId)
|
||||
filterFields.push(curDrillFieldId)
|
||||
// 移除下钻字段,把当前下钻字段插入到下钻入口位置
|
||||
fields = fields.filter(ele => {
|
||||
return !filterFields.includes(ele.id)
|
||||
})
|
||||
fields.splice(drillFieldIndex, 0, ...curDrillField)
|
||||
drillFieldMap[curDrillField.dataeaseName] = chart.drillFields[0].dataeaseName
|
||||
fields.splice(drillFieldIndex, 0, curDrillField)
|
||||
}
|
||||
const axisMap = [...chart.xAxis, ...chart.yAxis].reduce((pre, cur) => {
|
||||
pre[cur.dataeaseName] = cur
|
||||
@@ -124,7 +132,27 @@ export class TableNormal extends S2ChartView<TableSheet> {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const { basicStyle, tableCell, tableHeader, tooltip } = parseJson(chart.customAttr)
|
||||
// 表头分组
|
||||
const { headerGroup, showTableHeader } = tableHeader
|
||||
if (headerGroup && showTableHeader !== false) {
|
||||
const { headerGroupConfig } = tableHeader
|
||||
if (headerGroupConfig?.columns?.length) {
|
||||
const allKeys = columns.map(c => drillFieldMap[c] || c)
|
||||
const leafNodes = getLeafNodes(headerGroupConfig.columns as ColumnNode[])
|
||||
const leafKeys = leafNodes.map(c => c.key)
|
||||
if (isEqual(leafKeys, allKeys)) {
|
||||
if (Object.keys(drillFieldMap).length) {
|
||||
const originField = Object.values(drillFieldMap)[0]
|
||||
const drillField = Object.keys(drillFieldMap)[0]
|
||||
const [drillCol] = getColumns([originField], headerGroupConfig.columns as ColumnNode[])
|
||||
drillCol.key = drillField
|
||||
}
|
||||
columns.splice(0, columns.length, ...headerGroupConfig.columns)
|
||||
meta.push(...headerGroupConfig.meta)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 空值处理
|
||||
const newData = this.configEmptyDataStrategy(chart)
|
||||
// data config
|
||||
@@ -136,7 +164,6 @@ export class TableNormal extends S2ChartView<TableSheet> {
|
||||
data: newData
|
||||
}
|
||||
|
||||
const { basicStyle, tableCell, tableHeader, tooltip } = parseJson(chart.customAttr)
|
||||
// options
|
||||
const s2Options: S2Options = {
|
||||
width: containerDom.getBoundingClientRect().width,
|
||||
@@ -186,7 +213,7 @@ export class TableNormal extends S2ChartView<TableSheet> {
|
||||
// 开始渲染
|
||||
const newChart = new TableSheet(containerDom, s2DataConfig, s2Options)
|
||||
// 总计紧贴在单元格后面
|
||||
this.summaryRowStyle(newChart, newData, tableCell, tableHeader, basicStyle.showSummary)
|
||||
summaryRowStyle(newChart, newData, tableCell, tableHeader, basicStyle.showSummary)
|
||||
// 自适应铺满
|
||||
if (basicStyle.tableColumnMode === 'adapt') {
|
||||
newChart.on(S2Event.LAYOUT_RESIZE_COL_WIDTH, () => {
|
||||
@@ -207,6 +234,13 @@ export class TableNormal extends S2ChartView<TableSheet> {
|
||||
n.x = p
|
||||
return p + n.width
|
||||
}, 0)
|
||||
// 处理分组的单元格,宽度为所有叶子节点之和
|
||||
ev.colNodes.forEach(n => {
|
||||
if (n.colIndex === -1) {
|
||||
n.width = calcTreeWidth(n)
|
||||
n.x = getStartPosition(n)
|
||||
}
|
||||
})
|
||||
ev.colsHierarchy.width = totalWidth
|
||||
newChart.store.set('lastLayoutResult', undefined)
|
||||
return
|
||||
@@ -227,6 +261,13 @@ export class TableNormal extends S2ChartView<TableSheet> {
|
||||
n.x = p
|
||||
return p + n.width
|
||||
}, 0)
|
||||
// 处理分组的单元格,宽度为所有叶子节点之和
|
||||
ev.colNodes.forEach(n => {
|
||||
if (n.colIndex === -1) {
|
||||
n.width = calcTreeWidth(n)
|
||||
n.x = getStartPosition(n)
|
||||
}
|
||||
})
|
||||
if (totalWidth > containerWidth) {
|
||||
// 从最后一列减掉
|
||||
ev.colLeafNodes[ev.colLeafNodes.length - 1].width -= totalWidth - containerWidth
|
||||
@@ -332,22 +373,6 @@ export class TableNormal extends S2ChartView<TableSheet> {
|
||||
}
|
||||
}
|
||||
|
||||
protected summaryRowStyle(newChart, newData, tableCell, tableHeader, showSummary) {
|
||||
if (!showSummary || !newData.length) return
|
||||
newChart.on(S2Event.LAYOUT_BEFORE_RENDER, () => {
|
||||
const showHeader = tableHeader.showTableHeader === true
|
||||
// 不显示表头时,减少一个表头的高度
|
||||
const headerAndSummaryHeight = showHeader ? 2 : 1
|
||||
const totalHeight =
|
||||
tableHeader.tableTitleHeight * headerAndSummaryHeight +
|
||||
tableCell.tableItemHeight * (newData.length - 1)
|
||||
if (totalHeight < newChart.container.cfg.height) {
|
||||
newChart.options.height =
|
||||
totalHeight < newChart.container.cfg.height - 8 ? totalHeight + 8 : totalHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super('table-normal', [])
|
||||
}
|
||||
|
||||
@@ -2393,3 +2393,51 @@ export function drawImage() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function calcTreeWidth(node) {
|
||||
if (!node.children?.length) {
|
||||
return node.width
|
||||
}
|
||||
return node.children.reduce((pre, cur) => {
|
||||
return pre + calcTreeWidth(cur)
|
||||
}, 0)
|
||||
}
|
||||
|
||||
export function getStartPosition(node) {
|
||||
if (!node.children?.length) {
|
||||
return node.x
|
||||
}
|
||||
return getStartPosition(node.children[0])
|
||||
}
|
||||
|
||||
function getMaxTreeDepth(nodes) {
|
||||
if (!nodes?.length) {
|
||||
return 0
|
||||
}
|
||||
return Math.max(
|
||||
...nodes.map(node => {
|
||||
if (!node.children?.length) {
|
||||
return 1
|
||||
}
|
||||
return getMaxTreeDepth(node.children) + 1
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export function summaryRowStyle(newChart, newData, tableCell, tableHeader, showSummary) {
|
||||
if (!showSummary || !newData.length) return
|
||||
const columns = newChart.dataCfg.fields.columns
|
||||
const showHeader = tableHeader.showTableHeader === true
|
||||
// 不显示表头时,减少一个表头的高度
|
||||
const headerAndSummaryHeight = showHeader ? getMaxTreeDepth(columns) + 1 : 1
|
||||
newChart.on(S2Event.LAYOUT_BEFORE_RENDER, () => {
|
||||
const totalHeight =
|
||||
tableHeader.tableTitleHeight * headerAndSummaryHeight +
|
||||
tableCell.tableItemHeight * (newData.length - 1)
|
||||
if (totalHeight < newChart.container.cfg.height) {
|
||||
newChart.options.height =
|
||||
totalHeight < newChart.container.cfg.height - 8 ? totalHeight + 8 : totalHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user