From 7c814454fe868d2c1603ab2b8a6435973261e750 Mon Sep 17 00:00:00 2001 From: ulleo Date: Fri, 21 Mar 2025 13:57:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=9B=BE=E8=A1=A8):=20=E6=B1=87=E6=80=BB?= =?UTF-8?q?=E8=A1=A8=E6=80=BB=E8=AE=A1=EF=BC=8C=E6=94=AF=E6=8C=81=E5=AF=B9?= =?UTF-8?q?=E6=95=B0=E5=80=BC=E7=B1=BB=E5=AD=97=E6=AE=B5=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E6=B1=87=E6=80=BB=20#12878?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/core-frontend/src/locales/en.ts | 1 + core/core-frontend/src/locales/tw.ts | 1 + core/core-frontend/src/locales/zh-CN.ts | 1 + .../src/models/chart/chart-attr.d.ts | 6 + .../src/models/chart/editor.d.ts | 1 + .../editor/editor-style/ChartStyle.vue | 18 ++ .../components/BasicStyleSelector.vue | 4 +- .../components/SummarySelector.vue | 186 +++++++++++++ .../js/panel/charts/table/common.ts | 1 + .../js/panel/charts/table/table-info.ts | 7 +- .../js/panel/charts/table/table-normal.ts | 5 +- .../js/panel/common/common_table.ts | 260 +++++++++++------- 12 files changed, 383 insertions(+), 108 deletions(-) create mode 100644 core/core-frontend/src/views/chart/components/editor/editor-style/components/SummarySelector.vue diff --git a/core/core-frontend/src/locales/en.ts b/core/core-frontend/src/locales/en.ts index 1212eee338..51703a7d77 100644 --- a/core/core-frontend/src/locales/en.ts +++ b/core/core-frontend/src/locales/en.ts @@ -1388,6 +1388,7 @@ export default { table_show_col_tooltip: 'Turn on column header tooltip', table_show_cell_tooltip: 'Turn on cell tooltip', table_show_header_tooltip: 'Turn on header tooltip', + table_summary: 'Total', table_show_summary: 'Show total', table_summary_label: 'Total label', table_header_show_horizon_border: 'Header horizontal border', diff --git a/core/core-frontend/src/locales/tw.ts b/core/core-frontend/src/locales/tw.ts index f1b10a1f06..8cee31e6e2 100644 --- a/core/core-frontend/src/locales/tw.ts +++ b/core/core-frontend/src/locales/tw.ts @@ -1352,6 +1352,7 @@ export default { table_show_col_tooltip: '開啟列頭提示', table_show_cell_tooltip: '開啟單元格提示', table_show_header_tooltip: '開啟表頭提示', + table_summary: '總計', table_show_summary: '顯示總計', table_summary_label: '總計標籤', table_header_show_horizon_border: '表頭橫邊框線', diff --git a/core/core-frontend/src/locales/zh-CN.ts b/core/core-frontend/src/locales/zh-CN.ts index e2f5981a07..7545231797 100644 --- a/core/core-frontend/src/locales/zh-CN.ts +++ b/core/core-frontend/src/locales/zh-CN.ts @@ -1357,6 +1357,7 @@ export default { table_show_col_tooltip: '开启列头提示', table_show_cell_tooltip: '开启单元格提示', table_show_header_tooltip: '开启表头提示', + table_summary: '总计', table_show_summary: '显示总计', table_summary_label: '总计标签', table_header_show_horizon_border: '表头横边框线', diff --git a/core/core-frontend/src/models/chart/chart-attr.d.ts b/core/core-frontend/src/models/chart/chart-attr.d.ts index 1aed43cc1f..f9b93c39f0 100644 --- a/core/core-frontend/src/models/chart/chart-attr.d.ts +++ b/core/core-frontend/src/models/chart/chart-attr.d.ts @@ -293,6 +293,12 @@ declare interface ChartBasicStyle { * 汇总表总计标签 */ summaryLabel: string + + seriesSummary?: Array<{ + show: boolean + field: string + summary: string + }> /** * 符号地图符号大小最小值 */ diff --git a/core/core-frontend/src/models/chart/editor.d.ts b/core/core-frontend/src/models/chart/editor.d.ts index 308aad1cb4..619e32a9dc 100644 --- a/core/core-frontend/src/models/chart/editor.d.ts +++ b/core/core-frontend/src/models/chart/editor.d.ts @@ -31,6 +31,7 @@ declare type EditorProperty = | 'flow-map-line-selector' | 'flow-map-point-selector' | 'bubble-animate' + | 'summary-selector' declare type EditorPropertyInner = { [key in EditorProperty]?: string[] } diff --git a/core/core-frontend/src/views/chart/components/editor/editor-style/ChartStyle.vue b/core/core-frontend/src/views/chart/components/editor/editor-style/ChartStyle.vue index 5a778335d2..b1c812a7b5 100644 --- a/core/core-frontend/src/views/chart/components/editor/editor-style/ChartStyle.vue +++ b/core/core-frontend/src/views/chart/components/editor/editor-style/ChartStyle.vue @@ -9,6 +9,7 @@ import YAxisSelector from '@/views/chart/components/editor/editor-style/componen import DualYAxisSelector from '@/views/chart/components/editor/editor-style/components/DualYAxisSelector.vue' import TitleSelector from '@/views/chart/components/editor/editor-style/components/TitleSelector.vue' import LegendSelector from '@/views/chart/components/editor/editor-style/components/LegendSelector.vue' +import SummarySelector from '@/views/chart/components/editor/editor-style/components/SummarySelector.vue' import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain' import { storeToRefs } from 'pinia' import CollapseSwitchItem from '@/components/collapse-switch-item/src/CollapseSwitchItem.vue' @@ -616,6 +617,23 @@ watch( @onChangeYAxisExtForm="onChangeYAxisExtForm" /> + + + + diff --git a/core/core-frontend/src/views/chart/components/editor/editor-style/components/BasicStyleSelector.vue b/core/core-frontend/src/views/chart/components/editor/editor-style/components/BasicStyleSelector.vue index c7a1d75b22..fb9cb851c9 100644 --- a/core/core-frontend/src/views/chart/components/editor/editor-style/components/BasicStyleSelector.vue +++ b/core/core-frontend/src/views/chart/components/editor/editor-style/components/BasicStyleSelector.vue @@ -860,7 +860,7 @@ onMounted(() => { - { :max-length="10" @blur="changeBasicStyle('summaryLabel')" /> - + --> +import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain' +import { useI18n } from '@/hooks/web/useI18n' +import { computed, onMounted, PropType, reactive, watch } from 'vue' +import { DEFAULT_BASIC_STYLE } from '@/views/chart/components/editor/util/chart' +import { cloneDeep, defaultsDeep, filter, find } from 'lodash-es' + +const dvMainStore = dvMainStoreWithOut() +const { t } = useI18n() +const props = defineProps({ + chart: { + type: Object as PropType, + required: true + }, + themes: { + type: String as PropType, + default: 'dark' + }, + propertyInner: { + type: Array + } +}) +const showProperty = prop => props.propertyInner?.includes(prop) + +const state = reactive({ + basicStyleForm: JSON.parse(JSON.stringify(DEFAULT_BASIC_STYLE)) as ChartBasicStyle, + currentAxis: undefined as string, + currentAxisSummary: undefined as { + show: boolean + field: string + summary: string + } +}) + +const emit = defineEmits(['onBasicStyleChange']) +const changeBasicStyle = (prop?: string, requestData = false) => { + emit('onBasicStyleChange', { data: state.basicStyleForm, requestData }, prop) +} + +watch( + [ + () => props.chart.customAttr.basicStyle.showSummary, + () => props.chart.xAxis, + () => props.chart.yAxis + ], + () => { + init() + }, + { + deep: true + } +) + +function getAxisList() { + return props.chart.type === 'table-info' + ? filter(props.chart.xAxis, axis => [2, 3, 4].includes(axis.deType)) + : props.chart.yAxis +} + +const computedAxis = computed(() => { + return getAxisList() +}) +const summaryTypes = [ + { key: 'sum', name: t('chart.sum') }, + { key: 'avg', name: t('chart.avg') }, + { key: 'max', name: t('chart.max') }, + { key: 'min', name: t('chart.min') } + // { key: 'stddev_pop', name: t('chart.stddev_pop') }, + // { key: 'var_pop', name: t('chart.var_pop') } +] + +function onSelectAxis(value) { + state.currentAxisSummary = find(state.basicStyleForm.seriesSummary, s => s.field === value) +} + +const init = () => { + const basicStyle = cloneDeep(props.chart.customAttr.basicStyle) + + state.basicStyleForm = defaultsDeep(basicStyle, cloneDeep(DEFAULT_BASIC_STYLE)) as ChartBasicStyle + + const axisList = getAxisList() + + const tempList = [] + for (let i = 0; i < axisList.length; i++) { + const axis = axisList[i] + let savedAxis = find(state.basicStyleForm.seriesSummary, s => s.field === axis.dataeaseName) + if (savedAxis) { + if (savedAxis.summary == undefined) { + savedAxis.summary = 'sum' + } + if (savedAxis.show == undefined) { + savedAxis.show = true + } + } else { + savedAxis = { + field: axis.dataeaseName, + summary: 'sum', + show: true + } + } + tempList.push(savedAxis) + } + + state.basicStyleForm.seriesSummary = tempList + + if (state.basicStyleForm.seriesSummary.length > 0 && state.basicStyleForm.showSummary) { + state.currentAxis = state.basicStyleForm.seriesSummary[0].field + onSelectAxis(state.currentAxis) + } else { + state.currentAxis = undefined + state.currentAxisSummary = undefined + } +} + +onMounted(() => { + init() +}) + + + + + diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/table/common.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/table/common.ts index 08b146e102..eb1ecf1304 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/table/common.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/table/common.ts @@ -6,6 +6,7 @@ export const TABLE_EDITOR_PROPERTY: EditorProperty[] = [ 'table-cell-selector', 'title-selector', 'tooltip-selector', + 'summary-selector', 'function-cfg', 'threshold', 'scroll-cfg', diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/table/table-info.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/table/table-info.ts index 3c909b8764..5f52abd437 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/table/table-info.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/table/table-info.ts @@ -58,9 +58,7 @@ export class TableInfo extends S2ChartView { 'alpha', 'tablePageMode', 'showHoverStyle', - 'autoWrap', - 'showSummary', - 'summaryLabel' + 'autoWrap' ], 'table-cell-selector': [ ...TABLE_EDITOR_PROPERTY_INNER['table-cell-selector'], @@ -68,7 +66,8 @@ export class TableInfo extends S2ChartView { 'tableColumnFreezeHead', 'tableRowFreezeHead', 'mergeCells' - ] + ], + 'summary-selector': ['showSummary', 'summaryLabel'] } axis: AxisType[] = ['xAxis', 'filter', 'drill'] axisConfig: AxisConfig = { diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/table/table-normal.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/table/table-normal.ts index a3aaaee0b7..5d692d8dbb 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/table/table-normal.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/table/table-normal.ts @@ -39,8 +39,6 @@ export class TableNormal extends S2ChartView { 'basic-style-selector': [ ...TABLE_EDITOR_PROPERTY_INNER['basic-style-selector'], 'tablePageMode', - 'showSummary', - 'summaryLabel', 'showHoverStyle' ], 'table-cell-selector': [ @@ -48,7 +46,8 @@ export class TableNormal extends S2ChartView { 'tableFreeze', 'tableColumnFreezeHead', 'tableRowFreezeHead' - ] + ], + 'summary-selector': ['showSummary', 'summaryLabel'] } axis: AxisType[] = ['xAxis', 'yAxis', 'drill', 'filter'] axisConfig: AxisConfig = { diff --git a/core/core-frontend/src/views/chart/components/js/panel/common/common_table.ts b/core/core-frontend/src/views/chart/components/js/panel/common/common_table.ts index 0ff971829e..aad4d06d1e 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/common/common_table.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/common/common_table.ts @@ -43,14 +43,30 @@ import { updateShapeAttr, ViewMeta } from '@antv/s2' -import { cloneDeep, filter, find, intersection, keys, merge, repeat } from 'lodash-es' -import { createVNode, render } from 'vue' +import { + cloneDeep, + filter, + find, + intersection, + keys, + map, + maxBy, + meanBy, + merge, + minBy, + repeat, + sumBy, + size, + sum +} from 'lodash-es' +import {createVNode, render} from 'vue' import TableTooltip from '@/views/chart/components/editor/common/TableTooltip.vue' import Exceljs from 'exceljs' -import { saveAs } from 'file-saver' -import { ElMessage } from 'element-plus-secondary' -import { useI18n } from '@/hooks/web/useI18n' -const { t: i18nt } = useI18n() +import {saveAs} from 'file-saver' +import {ElMessage} from 'element-plus-secondary' +import {useI18n} from '@/hooks/web/useI18n' + +const {t: i18nt} = useI18n() export function getCustomTheme(chart: Chart): S2Theme { const headerColor = hexColorToRGBA( @@ -199,7 +215,7 @@ export function getCustomTheme(chart: Chart): S2Theme { let customAttr: DeepPartial if (chart.customAttr) { customAttr = parseJson(chart.customAttr) - const { basicStyle, tableHeader, tableCell } = customAttr + const {basicStyle, tableHeader, tableCell} = customAttr // basic if (basicStyle) { const tableBorderColor = basicStyle.tableBorderColor @@ -251,7 +267,7 @@ export function getCustomTheme(chart: Chart): S2Theme { } const fontStyle = tableHeader.isItalic ? 'italic' : 'normal' const fontWeight = tableHeader.isBolder === false ? 'normal' : 'bold' - const { tableHeaderAlign, tableTitleFontSize } = tableHeader + const {tableHeaderAlign, tableTitleFontSize} = tableHeader const tmpTheme: S2Theme = { cornerCell: { cell: { @@ -366,7 +382,7 @@ export function getCustomTheme(chart: Chart): S2Theme { } const fontStyle = tableCell.isItalic ? 'italic' : 'normal' const fontWeight = tableCell.isBolder === false ? 'normal' : 'bold' - const { tableItemAlign, tableItemFontSize, enableTableCrossBG } = tableCell + const {tableItemAlign, tableItemFontSize, enableTableCrossBG} = tableCell const tmpTheme: S2Theme = { rowCell: { cell: { @@ -476,7 +492,7 @@ export function getStyle(chart: Chart, dataConfig: S2DataConfig): Style { let customAttr: DeepPartial if (chart.customAttr) { customAttr = parseJson(chart.customAttr) - const { basicStyle, tableHeader, tableCell } = customAttr + const {basicStyle, tableHeader, tableCell} = customAttr style.colCfg = { height: tableHeader.tableTitleHeight } @@ -497,7 +513,7 @@ export function getStyle(chart: Chart, dataConfig: S2DataConfig): Style { }, {}) || {} // 下钻字段使用入口字段的宽度 if (chart.drill) { - const { xAxis } = parseJson(chart) + const {xAxis} = parseJson(chart) const curDrillField = chart.drillFields[chart.drillFilters.length] const drillEnterFieldIndex = xAxis.findIndex( item => item.id === chart.drillFilters[0].fieldId @@ -589,7 +605,7 @@ export function getCurrentField(valueFieldList: Axis[], field: ChartViewField) { } export function getConditions(chart: Chart) { - const { threshold } = parseJson(chart.senior) + const {threshold} = parseJson(chart.senior) if (!threshold.enable) { return } @@ -601,7 +617,7 @@ export function getConditions(chart: Chart) { const dimFields = [...chart.xAxis, ...chart.xAxisExt].map(i => i.dataeaseName) if (conditions?.length > 0) { - const { tableCell, basicStyle, tableHeader } = parseJson(chart.customAttr) + const {tableCell, basicStyle, tableHeader} = parseJson(chart.customAttr) // 合并单元格时斑马纹失效 const enableTableCrossBG = chart.type === 'table-info' ? tableCell.enableTableCrossBG && !tableCell.mergeCells : tableCell.enableTableCrossBG const valueColor = isAlphaColor(tableCell.tableFontColor) @@ -610,8 +626,8 @@ export function getConditions(chart: Chart) { const valueBgColor = enableTableCrossBG ? null : isAlphaColor(tableCell.tableItemBgColor) - ? tableCell.tableItemBgColor - : hexColorToRGBA(tableCell.tableItemBgColor, basicStyle.alpha) + ? tableCell.tableItemBgColor + : hexColorToRGBA(tableCell.tableItemBgColor, basicStyle.alpha) const headerValueColor = tableHeader.tableHeaderFontColor const headerValueBgColor = isAlphaColor(tableHeader.tableHeaderBgColor) ? tableHeader.tableHeaderBgColor @@ -662,7 +678,7 @@ export function getConditions(chart: Chart) { if (isTransparent(fill)) { return null } - return { fill } + return {fill} } }) } @@ -876,9 +892,10 @@ export function handleTableEmptyStrategy(chart: Chart) { } return newData } + export class SortTooltip extends BaseTooltip { show(showOptions) { - const { iconName } = showOptions + const {iconName} = showOptions if (iconName) { this.showSortTooltip(showOptions) return @@ -887,9 +904,9 @@ export class SortTooltip extends BaseTooltip { } showSortTooltip(showOptions) { - const { position, options, meta, event } = showOptions - const { enterable } = getTooltipDefaultOptions(options) - const { autoAdjustBoundary, adjustPosition } = this.spreadsheet.options.tooltip || {} + const {position, options, meta, event} = showOptions + const {enterable} = getTooltipDefaultOptions(options) + const {autoAdjustBoundary, adjustPosition} = this.spreadsheet.options.tooltip || {} this.visible = true this.options = showOptions const container = this['getContainer']() @@ -903,14 +920,14 @@ export class SortTooltip extends BaseTooltip { this.spreadsheet.tooltip.container.appendChild(childElement) render(vNode, childElement) - const { x, y } = getAutoAdjustPosition({ + const {x, y} = getAutoAdjustPosition({ spreadsheet: this.spreadsheet, position, tooltipContainer: container, autoAdjustBoundary }) - this.position = adjustPosition?.({ position: { x, y }, event }) ?? { + this.position = adjustPosition?.({position: {x, y}, event}) ?? { x, y } @@ -930,6 +947,7 @@ export class SortTooltip extends BaseTooltip { }) } } + const SORT_DEFAULT = '' const SORT_UP = @@ -942,7 +960,7 @@ function svg2Base64(svg) { } export function configHeaderInteraction(chart: Chart, option: S2Options) { - const { tableHeaderFontColor, tableHeaderSort } = parseJson(chart.customAttr).tableHeader + const {tableHeaderFontColor, tableHeaderSort} = parseJson(chart.customAttr).tableHeader if (!tableHeaderSort) { return } @@ -994,7 +1012,7 @@ export function configHeaderInteraction(chart: Chart, option: S2Options) { return iconName === `customSortDefault${randomSuffix}` }, onClick: props => { - const { meta, event } = props + const {meta, event} = props meta.spreadsheet.showTooltip({ position: { x: event.clientX, @@ -1022,7 +1040,7 @@ export function configHeaderInteraction(chart: Chart, option: S2Options) { } export function configTooltip(chart: Chart, option: S2Options) { - const { tooltip } = parseJson(chart.customAttr) + const {tooltip} = parseJson(chart.customAttr) const textFontFamily = chart.fontFamily ? chart.fontFamily : FONT_FAMILY option.tooltip = { ...option.tooltip, @@ -1037,7 +1055,7 @@ export function configTooltip(chart: Chart, option: S2Options) { opacity: 0.95, position: 'absolute' }, - adjustPosition: ({ event }) => { + adjustPosition: ({event}) => { return getTooltipPosition(event) } } @@ -1052,7 +1070,7 @@ export function copyContent(s2Instance: SpreadSheet, event, fieldMeta) { let content = '' // 多选 if (selectState.stateName === InteractionStateName.SELECTED) { - const { cells } = selectState + const {cells} = selectState if (!cells?.length) { return } @@ -1147,13 +1165,13 @@ export function copyContent(s2Instance: SpreadSheet, event, fieldMeta) { function getTooltipPosition(event) { const s2Instance = event.s2Instance - const { x, y } = event - const result = { x: x + 15, y } + const {x, y} = event + const result = {x: x + 15, y} if (!s2Instance) { return result } - const { height, width } = s2Instance.getCanvasElement().getBoundingClientRect() - const { offsetHeight, offsetWidth } = s2Instance.tooltip.getContainer() + const {height, width} = s2Instance.getCanvasElement().getBoundingClientRect() + const {offsetHeight, offsetWidth} = s2Instance.tooltip.getContainer() if (offsetWidth > width) { result.x = 0 } @@ -1181,8 +1199,8 @@ function getTooltipPosition(event) { } export async function exportGridPivot(instance: PivotSheet, chart: ChartObj) { - const { layoutResult } = instance.facet - const { meta, fields } = instance.dataCfg + const {layoutResult} = instance.facet + const {meta, fields} = instance.dataCfg const rowLength = fields?.rows?.length || 0 const colLength = fields?.columns?.length || 0 const colNums = layoutResult.colLeafNodes.length + rowLength + 1 @@ -1202,27 +1220,27 @@ export async function exportGridPivot(instance: PivotSheet, chart: ChartObj) { fields.columns?.forEach((column, index) => { const cell = worksheet.getCell(index + 1, 1) cell.value = metaMap[column]?.name ?? column - cell.alignment = { vertical: 'middle', horizontal: 'center' } + cell.alignment = {vertical: 'middle', horizontal: 'center'} if (rowLength >= 2) { worksheet.mergeCells(index + 1, 1, index + 1, rowLength) } cell.border = { - right: { style: 'thick', color: { argb: '00000000' } } + right: {style: 'thick', color: {argb: '00000000'}} } }) fields?.rows?.forEach((row, index) => { const cell = worksheet.getCell(colLength + 1, index + 1) cell.value = metaMap[row]?.name ?? row - cell.alignment = { vertical: 'middle', horizontal: 'center' } + cell.alignment = {vertical: 'middle', horizontal: 'center'} cell.border = { - bottom: { style: 'thick', color: { argb: '00000000' } } + bottom: {style: 'thick', color: {argb: '00000000'}} } if (index === fields.rows.length - 1) { - cell.border.right = { style: 'thick', color: { argb: '00000000' } } + cell.border.right = {style: 'thick', color: {argb: '00000000'}} } }) // 行头 - const { rowLeafNodes, rowsHierarchy, rowNodes } = layoutResult + const {rowLeafNodes, rowsHierarchy, rowNodes} = layoutResult const maxColIndex = rowsHierarchy.maxLevel + 1 const notLeafNodeHeightMap: Record = {} rowLeafNodes.forEach(node => { @@ -1233,17 +1251,17 @@ export async function exportGridPivot(instance: PivotSheet, chart: ChartObj) { notLeafNodeHeightMap[curNode.id] = height + 1 curNode = curNode.parent } - const { rowIndex } = node + const {rowIndex} = node const writeRowIndex = rowIndex + 1 + colLength + 1 const writeColIndex = node.level + 1 const cell = worksheet.getCell(writeRowIndex, writeColIndex) cell.value = node.label - cell.alignment = { vertical: 'middle', horizontal: 'center' } + cell.alignment = {vertical: 'middle', horizontal: 'center'} if (writeColIndex < maxColIndex) { worksheet.mergeCells(writeRowIndex, writeColIndex, writeRowIndex, maxColIndex) } cell.border = { - right: { style: 'thick', color: { argb: '00000000' } } + right: {style: 'thick', color: {argb: '00000000'}} } }) @@ -1265,7 +1283,7 @@ export async function exportGridPivot(instance: PivotSheet, chart: ChartObj) { const value = node.label const cell = worksheet.getCell(writeRowIndex, node.level + 1) cell.value = value - cell.alignment = { vertical: 'middle', horizontal: 'center' } + cell.alignment = {vertical: 'middle', horizontal: 'center'} if (mergeColCount > 1 || height > 1) { worksheet.mergeCells( writeRowIndex, @@ -1277,7 +1295,7 @@ export async function exportGridPivot(instance: PivotSheet, chart: ChartObj) { }) // 列头 - const { colLeafNodes, colNodes, colsHierarchy } = layoutResult + const {colLeafNodes, colNodes, colsHierarchy} = layoutResult const maxColHeight = colsHierarchy.maxLevel + 1 const notLeafNodeWidthMap: Record = {} colLeafNodes.forEach(node => { @@ -1288,7 +1306,7 @@ export async function exportGridPivot(instance: PivotSheet, chart: ChartObj) { notLeafNodeWidthMap[curNode.id] = width + 1 curNode = curNode.parent } - const { colIndex } = node + const {colIndex} = node const writeRowIndex = node.level + 1 const writeColIndex = colIndex + 1 + rowLength const cell = worksheet.getCell(writeRowIndex, writeColIndex) @@ -1297,12 +1315,12 @@ export async function exportGridPivot(instance: PivotSheet, chart: ChartObj) { value = metaMap[value].name } cell.value = value - cell.alignment = { vertical: 'middle', horizontal: 'center' } + cell.alignment = {vertical: 'middle', horizontal: 'center'} if (writeRowIndex < maxColHeight) { worksheet.mergeCells(writeRowIndex, writeColIndex, maxColHeight, writeColIndex) } cell.border = { - bottom: { style: 'thick', color: { argb: '00000000' } } + bottom: {style: 'thick', color: {argb: '00000000'}} } }) const getNodeStartColIndex = (node: Node) => { @@ -1324,7 +1342,7 @@ export async function exportGridPivot(instance: PivotSheet, chart: ChartObj) { const writeColIndex = colIndex + rowLength const cell = worksheet.getCell(writeRowIndex, writeColIndex) cell.value = value - cell.alignment = { vertical: 'middle', horizontal: 'center' } + cell.alignment = {vertical: 'middle', horizontal: 'center'} if (mergeRowCount > 1 || width > 1) { worksheet.mergeCells( writeRowIndex, @@ -1338,12 +1356,12 @@ export async function exportGridPivot(instance: PivotSheet, chart: ChartObj) { for (let rowIndex = 0; rowIndex < rowLeafNodes.length; rowIndex++) { for (let colIndex = 0; colIndex < colLeafNodes.length; colIndex++) { const dataCellMeta = layoutResult.getCellMeta(rowIndex, colIndex) - const { fieldValue } = dataCellMeta + const {fieldValue} = dataCellMeta if (fieldValue === 0 || fieldValue) { const meta = metaMap[dataCellMeta.valueField] const cell = worksheet.getCell(rowIndex + maxColHeight + 1, rowLength + colIndex + 1) const value = meta?.formatter?.(fieldValue) || fieldValue.toString() - cell.alignment = { vertical: 'middle', horizontal: 'center' } + cell.alignment = {vertical: 'middle', horizontal: 'center'} cell.value = value } } @@ -1361,7 +1379,7 @@ export async function exportTreePivot(instance: PivotSheet, chart: ChartObj) { ElMessage.warning(i18nt('chart.pivot_export_invalid_col_exceed')) return } - const { meta, fields } = instance.dataCfg + const {meta, fields} = instance.dataCfg const colLength = fields?.columns?.length || 0 const workbook = new Exceljs.Workbook() const worksheet = workbook.addWorksheet(i18nt('chart.chart_data')) @@ -1376,33 +1394,33 @@ export async function exportTreePivot(instance: PivotSheet, chart: ChartObj) { fields.columns?.forEach((column, index) => { const cell = worksheet.getCell(index + 1, 1) cell.value = metaMap[column]?.name ?? column - cell.alignment = { vertical: 'middle', horizontal: 'center' } + cell.alignment = {vertical: 'middle', horizontal: 'center'} cell.border = { - right: { style: 'thick', color: { argb: '00000000' } } + right: {style: 'thick', color: {argb: '00000000'}} } }) const maxColHeight = layoutResult.colsHierarchy.maxLevel + 1 const rowName = fields?.rows?.map(row => metaMap[row]?.name ?? row).join('/') const cell = worksheet.getCell(colLength + 1, 1) cell.value = rowName - cell.alignment = { vertical: 'middle', horizontal: 'center' } + cell.alignment = {vertical: 'middle', horizontal: 'center'} cell.border = { - right: { style: 'thick', color: { argb: '00000000' } }, - bottom: { style: 'thick', color: { argb: '00000000' } } + right: {style: 'thick', color: {argb: '00000000'}}, + bottom: {style: 'thick', color: {argb: '00000000'}} } //行头 - const { rowLeafNodes } = layoutResult + const {rowLeafNodes} = layoutResult rowLeafNodes.forEach((node, index) => { const cell = worksheet.getCell(maxColHeight + index + 1, 1) cell.value = repeat(' ', node.level) + node.label - cell.alignment = { vertical: 'middle', horizontal: 'left' } + cell.alignment = {vertical: 'middle', horizontal: 'left'} cell.border = { - right: { style: 'thick', color: { argb: '00000000' } } + right: {style: 'thick', color: {argb: '00000000'}} } }) // 列头 const notLeafNodeWidthMap: Record = {} - const { colLeafNodes } = layoutResult + const {colLeafNodes} = layoutResult colLeafNodes.forEach(node => { let curNode = node.parent while (curNode) { @@ -1410,7 +1428,7 @@ export async function exportTreePivot(instance: PivotSheet, chart: ChartObj) { notLeafNodeWidthMap[curNode.id] = width + 1 curNode = curNode.parent } - const { colIndex } = node + const {colIndex} = node const writeRowIndex = node.level + 1 const writeColIndex = colIndex + 1 + 1 const cell = worksheet.getCell(writeRowIndex, writeColIndex) @@ -1419,12 +1437,12 @@ export async function exportTreePivot(instance: PivotSheet, chart: ChartObj) { value = metaMap[value].name } cell.value = value - cell.alignment = { vertical: 'middle', horizontal: 'center' } + cell.alignment = {vertical: 'middle', horizontal: 'center'} if (writeRowIndex < maxColHeight) { worksheet.mergeCells(writeRowIndex, writeColIndex, maxColHeight, writeColIndex) } cell.border = { - bottom: { style: 'thick', color: { argb: '00000000' } } + bottom: {style: 'thick', color: {argb: '00000000'}} } }) const colNodes = layoutResult.colNodes @@ -1446,7 +1464,7 @@ export async function exportTreePivot(instance: PivotSheet, chart: ChartObj) { const writeColIndex = colIndex + 1 const cell = worksheet.getCell(writeRowIndex, writeColIndex) cell.value = node.label - cell.alignment = { vertical: 'middle', horizontal: 'center' } + cell.alignment = {vertical: 'middle', horizontal: 'center'} if (mergeRowCount > 1 || width > 1) { worksheet.mergeCells( writeRowIndex, @@ -1460,12 +1478,12 @@ export async function exportTreePivot(instance: PivotSheet, chart: ChartObj) { for (let rowIndex = 0; rowIndex < rowLeafNodes.length; rowIndex++) { for (let colIndex = 0; colIndex < colLeafNodes.length; colIndex++) { const dataCellMeta = layoutResult.getCellMeta(rowIndex, colIndex) - const { fieldValue } = dataCellMeta + const {fieldValue} = dataCellMeta if (fieldValue === 0 || fieldValue) { const meta = metaMap[dataCellMeta.valueField] const cell = worksheet.getCell(rowIndex + maxColHeight + 1, colIndex + 1 + 1) const value = meta?.formatter?.(fieldValue) || fieldValue.toString() - cell.alignment = { vertical: 'middle', horizontal: 'center' } + cell.alignment = {vertical: 'middle', horizontal: 'center'} cell.value = value } } @@ -1476,8 +1494,9 @@ export async function exportTreePivot(instance: PivotSheet, chart: ChartObj) { }) saveAs(dataBlob, `${chart.title ?? '透视表'}.xlsx`) } + export async function exportPivotExcel(instance: PivotSheet, chart: ChartObj) { - const { fields } = instance.dataCfg + const {fields} = instance.dataCfg const rowLength = fields?.rows?.length || 0 const valueLength = fields?.values?.length || 0 if (!(rowLength && valueLength)) { @@ -1492,8 +1511,8 @@ export async function exportPivotExcel(instance: PivotSheet, chart: ChartObj) { } export function configMergeCells(chart: Chart, options: S2Options, dataConfig: S2DataConfig) { - const { mergeCells } = parseJson(chart.customAttr).tableCell - const { showIndex } = parseJson(chart.customAttr).tableHeader + const {mergeCells} = parseJson(chart.customAttr).tableCell + const {showIndex} = parseJson(chart.customAttr).tableHeader if (mergeCells) { options.frozenColCount = 0 options.frozenRowCount = 0 @@ -1601,7 +1620,7 @@ class CustomMergedCell extends MergedCell { const allPoints = getPolygonPoints(this.cells) // 处理条件样式,这里没有用透明度 // 因为合并的单元格是单独的图层,透明度降低的话会显示底下未合并的单元格,需要单独处理被覆盖的单元格 - const { backgroundColor: fill, backgroundColorOpacity: fillOpacity } = this.getBackgroundColor() + const {backgroundColor: fill, backgroundColorOpacity: fillOpacity} = this.getBackgroundColor() const cellTheme = this.theme.dataCell.cell this.backgroundShape = renderPolygon(this, { points: allPoints, @@ -1610,6 +1629,7 @@ class CustomMergedCell extends MergedCell { lineHeight: cellTheme.horizontalBorderWidth }) } + drawTextShape(): void { if (this.meta.deFieldType === 7) { drawImage.apply(this) @@ -1674,11 +1694,11 @@ const drawTextShape = (cell, isHeader) => { // 用户配置的最大行数 const maxLines = cell.meta.maxLines ?? 1 const { - options: { placeholder } + options: {placeholder} } = cell.spreadsheet const emptyPlaceholder = getEmptyPlaceholder(this, placeholder) // 单元格文本 - const { formattedValue } = cell.getFormattedFieldValue() + const {formattedValue} = cell.getFormattedFieldValue() // 获取文本样式 const textStyle = cell.getTextStyle() // 宽度能放几个字符,就放几个,放不下就换行 @@ -1758,7 +1778,7 @@ export const calculateHeaderHeight = (info, newChart, tableHeader, basicStyle, l if (tableHeader.showTableHeader === false) return const ev = layoutResult || newChart.facet.layoutResult const maxLines = basicStyle.maxLines ?? 1 - const textStyle = { ...newChart.theme.cornerCell.text } + const textStyle = {...newChart.theme.cornerCell.text} const sourceText = info.info.meta.value let maxHeight = getWrapTextHeight( getWrapText(sourceText, textStyle, info.info.resizedWidth, ev.spreadsheet), @@ -1779,10 +1799,10 @@ export const calculateHeaderHeight = (info, newChart, tableHeader, basicStyle, l maxLines ) return wrapTextHeight > maxHeightNode.height - ? { height: wrapTextHeight, colIndex: currentNode.colIndex } + ? {height: wrapTextHeight, colIndex: currentNode.colIndex} : maxHeightNode }, - { height: 0 } + {height: 0} ) // 使用最大高度 @@ -1873,30 +1893,71 @@ export const configSummaryRow = ( // 设置汇总行高度和表头一致 const heightByField = {} heightByField[newData.length] = tableHeader.tableTitleHeight - s2Options.style.rowCfg = { heightByField } + s2Options.style.rowCfg = {heightByField} // 计算汇总加入到数据里,冻结最后一行 s2Options.frozenTrailingRowCount = 1 - const yAxis = chart.yAxis + const xAxis = chart.xAxis - const summaryObj = newData.reduce( - (p, n) => { - if (chart.type === 'table-info') { - xAxis - .filter(axis => [2, 3, 4].includes(axis.deType)) - .forEach(axis => { - p[axis.dataeaseName] = - (parseFloat(n[axis.dataeaseName]) || 0) + (parseFloat(p[axis.dataeaseName]) || 0) - }) - } else { - yAxis.forEach(axis => { - p[axis.dataeaseName] = - (parseFloat(n[axis.dataeaseName]) || 0) + (parseFloat(p[axis.dataeaseName]) || 0) - }) + + const axis = chart.type === 'table-info' + ? filter(chart.xAxis, axis => [2, 3, 4].includes(axis.deType)) + : chart.yAxis + const summaryObj = {SUMMARY: true} + for (let i = 0; i < axis.length; i++) { + const a = axis[i].dataeaseName + let savedAxis = find(basicStyle.seriesSummary, s => s.field === a) + if (savedAxis) { + if (savedAxis.summary == undefined) { + savedAxis.summary = 'sum' } - return p - }, - { SUMMARY: true } - ) + if (savedAxis.show == undefined) { + savedAxis.show = true + } + } else { + savedAxis = { + field: a, + summary: 'sum', + show: true + } + } + if (!savedAxis.show) { + continue + } + switch (savedAxis.summary) { + case 'sum': + summaryObj[a] = sumBy(newData, d => (parseFloat(d[a]) || 0)) + break + case 'avg': + summaryObj[a] = meanBy(newData, d => (parseFloat(d[a]) || 0)) + break + case 'max': + summaryObj[a] = maxBy(filter(newData, d => parseFloat(d[a]) !== undefined), d => parseFloat(d[a]))[a] + break + case 'min': + summaryObj[a] = minBy(filter(newData, d => parseFloat(d[a]) !== undefined), d => parseFloat(d[a]))[a] + break + case 'var_pop'://方差 + if (newData.length < 2) { + continue + } else { + const mean = meanBy(newData, d => (parseFloat(d[a]) || 0)) // 计算均值 + const squaredDeviations = map(newData, d => ((parseFloat(d[a]) || 0) - mean) ** 2); // 计算偏差平方 + summaryObj[a] = sum(squaredDeviations) / (size(newData) - 1); // 样本方差(分母n-1) + } + break + case 'stddev_pop'://标准差 + if (newData.length < 2) { + continue + } else { + const mean = meanBy(newData, d => (parseFloat(d[a]) || 0)) // 计算均值 + const squaredDeviations = map(newData, d => ((parseFloat(d[a]) || 0) - mean) ** 2); // 计算偏差平方 + const sampleVariance = sum(squaredDeviations) / (size(newData) - 1); // 样本方差(分母n-1) + summaryObj[a] = Math.sqrt(sampleVariance); // 样本标准差 + } + break + } + } + newData.push(summaryObj) s2Options.dataCell = viewMeta => { // 配置文本自动换行参数 @@ -1948,9 +2009,10 @@ export class SummaryCell extends CustomDataCell { textStyle.textAlign = this.theme.dataCell.text.textAlign return textStyle } + getBackgroundColor() { - const { backgroundColor, backgroundColorOpacity } = this.theme.colCell.cell - return { backgroundColor, backgroundColorOpacity } + const {backgroundColor, backgroundColorOpacity} = this.theme.colCell.cell + return {backgroundColor, backgroundColorOpacity} } } @@ -2026,12 +2088,12 @@ export const getColumns = (fields, cols: Array) => { export function drawImage() { const img = new Image() - const { x, y, width, height, fieldValue } = this.meta + const {x, y, width, height, fieldValue} = this.meta img.src = fieldValue as string img.setAttribute('crossOrigin', 'anonymous') img.onload = () => { !this.cfg.children && (this.cfg.children = []) - const { width: imgWidth, height: imgHeight } = img + const {width: imgWidth, height: imgHeight} = img const ratio = Math.max(imgWidth / width, imgHeight / height) // 不铺满,部分留白 const imgShowWidth = (imgWidth / ratio) * 0.8