feat: Add thousand separators to default chart value display

#1095
This commit is contained in:
ulleo
2026-05-11 17:04:30 +08:00
committed by ulleo
parent e72c0bedda
commit e8463fb75e
11 changed files with 141 additions and 33 deletions
@@ -3,6 +3,7 @@ import type { ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts'
import type { G2Spec } from '@antv/g2'
import {
checkIsPercent,
formatNumber,
getAxesWithFilter,
processMultiQuotaData,
} from '@/views/chat/component/charts/utils.ts'
@@ -104,6 +105,9 @@ export class Bar extends BaseG2Chart {
labelAutoRotate: false,
labelAutoWrap: true,
labelAutoEllipsis: true,
labelFormatter: (value: any) => {
return String(formatNumber(value))
},
},
},
scale: {
@@ -123,10 +127,13 @@ export class Bar extends BaseG2Chart {
if (series.length > 0) {
return {
name: data[series[0].value],
value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
}
} else {
return { name: y[0].name, value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}` }
return {
name: y[0].name,
value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
}
}
},
labels: this.showLabel
@@ -137,7 +144,7 @@ export class Bar extends BaseG2Chart {
if (value === undefined || value === null) {
return ''
}
return `${value}${_data.isPercent ? '%' : ''}`
return `${formatNumber(value)}${_data.isPercent ? '%' : ''}`
},
position: (data: any) => {
if (data[y[0].value] < 0) {
@@ -3,6 +3,7 @@ import type { ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts'
import type { G2Spec } from '@antv/g2'
import {
checkIsPercent,
formatNumber,
getAxesWithFilter,
processMultiQuotaData,
} from '@/views/chat/component/charts/utils.ts'
@@ -95,6 +96,9 @@ export class Column extends BaseG2Chart {
},
y: {
title: false, // y[0].name,
labelFormatter: (value: any) => {
return String(formatNumber(value))
},
},
},
scale: {
@@ -114,10 +118,13 @@ export class Column extends BaseG2Chart {
if (series.length > 0) {
return {
name: data[series[0].value],
value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
}
} else {
return { name: y[0].name, value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}` }
return {
name: y[0].name,
value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
}
}
},
labels: this.showLabel
@@ -128,7 +135,7 @@ export class Column extends BaseG2Chart {
if (value === undefined || value === null) {
return ''
}
return `${value}${_data.isPercent ? '%' : ''}`
return `${formatNumber(value)}${_data.isPercent ? '%' : ''}`
},
position: (data: any) => {
if (data[y[0].value] < 0) {
@@ -3,6 +3,7 @@ import type { ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts'
import type { G2Spec } from '@antv/g2'
import {
checkIsPercent,
formatNumber,
getAxesWithFilter,
processMultiQuotaData,
} from '@/views/chat/component/charts/utils.ts'
@@ -69,6 +70,9 @@ export class Line extends BaseG2Chart {
},
y: {
title: false, // y[0].name,
labelFormatter: (value: any) => {
return String(formatNumber(value))
},
},
},
scale: {
@@ -94,7 +98,7 @@ export class Line extends BaseG2Chart {
if (value === undefined || value === null) {
return ''
}
return `${value}${_data.isPercent ? '%' : ''}`
return `${formatNumber(value)}${_data.isPercent ? '%' : ''}`
},
style: {
dx: -10,
@@ -112,10 +116,13 @@ export class Line extends BaseG2Chart {
if (series.length > 0) {
return {
name: data[series[0].value],
value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
}
} else {
return { name: y[0].name, value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}` }
return {
name: y[0].name,
value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
}
}
},
},
@@ -1,7 +1,7 @@
import { BaseG2Chart } from '@/views/chat/component/BaseG2Chart.ts'
import type { ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts'
import type { G2Spec } from '@antv/g2'
import { checkIsPercent, getAxesWithFilter } from '@/views/chat/component/charts/utils.ts'
import { checkIsPercent, formatNumber, getAxesWithFilter } from '@/views/chat/component/charts/utils.ts'
export class Pie extends BaseG2Chart {
constructor(id: string) {
@@ -48,7 +48,7 @@ export class Pie extends BaseG2Chart {
{
position: 'spider',
text: (data: any) => {
return `${data[series[0].value]}: ${data[y[0].value]}${_data.isPercent ? '%' : ''}`
return `${data[series[0].value]}: ${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`
},
},
]
@@ -59,7 +59,7 @@ export class Pie extends BaseG2Chart {
(data: any) => {
return {
name: y[0].name,
value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
}
},
],
@@ -11,6 +11,7 @@ import {
} from '@antv/s2'
import { debounce, filter } from 'lodash-es'
import { i18n } from '@/i18n'
import { formatNumber } from '@/views/chat/component/charts/utils.ts'
import '@antv/s2/dist/s2.min.css'
const { t } = i18n.global
@@ -120,6 +121,10 @@ export class Table extends BaseChart {
return {
field: a.value,
name: a.name,
formatter: (value: any) => {
const formatted = formatNumber(value)
return String(formatted)
},
}
}) ?? [],
data: this.data,
@@ -196,7 +201,8 @@ export class Table extends BaseChart {
container.style.fontSize = '14px'
container.style.whiteSpace = 'pre-wrap'
const text = document.createTextNode(meta.fieldValue)
const formattedValue = formatNumber(meta.fieldValue)
const text = document.createTextNode(String(formattedValue))
container.appendChild(text)
return container
@@ -207,7 +213,7 @@ export class Table extends BaseChart {
interaction: {
copy: {
enable: true,
withFormat: true,
withFormat: false,
withHeader: true,
},
brushSelection: {
@@ -1,6 +1,39 @@
import type { ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts'
import { endsWith, filter, replace } from 'lodash-es'
/**
* 为数值添加千分符,保持原有小数位数不变
* 纯字符串处理,避免精度丢失
* 支持:正负整数、小数、字符串格式的数值
*/
export function formatNumber(value: any): string | number {
if (value === null || value === undefined || value === '') {
return value
}
let str: string
if (typeof value === 'string') {
str = value.trim()
} else if (typeof value === 'number') {
str = String(value)
} else {
return value
}
const match = str.match(/^([+-])?(\d+)(\.(\d+))?$/)
if (!match) {
return value
}
const sign = match[1] || ''
const intPart = match[2]
const decPart = match[3] || ''
const formattedInt = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
return sign + formattedInt + decPart
}
interface CheckedData {
isPercent: boolean
data: Array<ChartData>
+10 -5
View File
@@ -1,4 +1,4 @@
const { checkIsPercent, getAxesWithFilter, processMultiQuotaData } = require('./utils')
const { checkIsPercent, formatNumber, getAxesWithFilter, processMultiQuotaData } = require('./utils')
function getBarOptions(baseOptions, axis, data) {
@@ -78,7 +78,12 @@ function getBarOptions(baseOptions, axis, data) {
labelAutoWrap: true,
labelAutoEllipsis: true,
},
y: { title: false },
y: {
title: false,
labelFormatter: (value) => {
return String(formatNumber(value))
},
},
},
scale: {
x: {
@@ -96,10 +101,10 @@ function getBarOptions(baseOptions, axis, data) {
if (series.length > 0) {
return {
name: data[series[0].value],
value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
}
} else {
return { name: y[0].name, value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}` }
return { name: y[0].name, value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}` }
}
},
labels: [
@@ -109,7 +114,7 @@ function getBarOptions(baseOptions, axis, data) {
if (value === undefined || value === null) {
return ''
}
return `${value}${_data.isPercent ? '%' : ''}`
return `${formatNumber(value)}${_data.isPercent ? '%' : ''}`
},
position: (data) => {
if (data[y[0].value] < 0) {
+10 -5
View File
@@ -1,4 +1,4 @@
const { checkIsPercent, getAxesWithFilter, processMultiQuotaData } = require('./utils')
const { checkIsPercent, formatNumber, getAxesWithFilter, processMultiQuotaData } = require('./utils')
function getColumnOptions(baseOptions, axis, data) {
@@ -77,7 +77,12 @@ function getColumnOptions(baseOptions, axis, data) {
labelAutoWrap: true,
labelAutoEllipsis: true,
},
y: { title: false },
y: {
title: false,
labelFormatter: (value) => {
return String(formatNumber(value))
},
},
},
scale: {
x: {
@@ -95,10 +100,10 @@ function getColumnOptions(baseOptions, axis, data) {
if (series.length > 0) {
return {
name: data[series[0].value],
value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
}
} else {
return { name: y[0].name, value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}` }
return { name: y[0].name, value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}` }
}
},
labels: [
@@ -108,7 +113,7 @@ function getColumnOptions(baseOptions, axis, data) {
if (value === undefined || value === null) {
return ''
}
return `${value}${_data.isPercent ? '%' : ''}`
return `${formatNumber(value)}${_data.isPercent ? '%' : ''}`
},
position: (data) => {
if (data[y[0].value] < 0) {
+10 -5
View File
@@ -1,4 +1,4 @@
const { checkIsPercent, getAxesWithFilter, processMultiQuotaData } = require('./utils')
const { checkIsPercent, formatNumber, getAxesWithFilter, processMultiQuotaData } = require('./utils')
function getLineOptions(baseOptions, axis, data) {
@@ -51,7 +51,12 @@ function getLineOptions(baseOptions, axis, data) {
labelAutoWrap: true,
labelAutoEllipsis: true,
},
y: { title: y[0].name },
y: {
title: y[0].name,
labelFormatter: (value) => {
return String(formatNumber(value))
},
},
},
scale: {
x: {
@@ -75,7 +80,7 @@ function getLineOptions(baseOptions, axis, data) {
if (value === undefined || value === null) {
return ''
}
return `${value}${_data.isPercent ? '%' : ''}`
return `${formatNumber(value)}${_data.isPercent ? '%' : ''}`
},
style: {
dx: -10,
@@ -92,10 +97,10 @@ function getLineOptions(baseOptions, axis, data) {
if (series.length > 0) {
return {
name: data[series[0].value],
value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
}
} else {
return { name: y[0].name, value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}` }
return { name: y[0].name, value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}` }
}
},
},
+3 -3
View File
@@ -1,4 +1,4 @@
const { checkIsPercent, getAxesWithFilter } = require('./utils')
const { checkIsPercent, formatNumber, getAxesWithFilter } = require('./utils')
function getPieOptions(baseOptions, axis, data) {
@@ -35,7 +35,7 @@ function getPieOptions(baseOptions, axis, data) {
{
position: 'spider',
text: (data) =>
`${data[series[0].value]}: ${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
`${data[series[0].value]}: ${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
},
],
tooltip: {
@@ -44,7 +44,7 @@ function getPieOptions(baseOptions, axis, data) {
(data) => {
return {
name: y[0].name,
value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
}
},
],
+34 -1
View File
@@ -1,5 +1,38 @@
const { endsWith, filter, replace } = require('lodash')
/**
* 为数值添加千分符,保持原有小数位数不变
* 纯字符串处理,避免精度丢失
* 支持:正负整数、小数、字符串格式的数值
*/
function formatNumber(value) {
if (value === null || value === undefined || value === '') {
return value
}
let str
if (typeof value === 'string') {
str = value.trim()
} else if (typeof value === 'number') {
str = String(value)
} else {
return value
}
const match = str.match(/^([+-])?(\d+)(\.(\d+))?$/)
if (!match) {
return value
}
const sign = match[1] || ''
const intPart = match[2]
const decPart = match[3] || ''
const formattedInt = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
return sign + formattedInt + decPart
}
function getAxesWithFilter(axes) {
const groups = {
x: [],
@@ -115,4 +148,4 @@ function checkIsPercent(valueAxes, data) {
return result
}
module.exports = { checkIsPercent, getAxesWithFilter, processMultiQuotaData }
module.exports = { checkIsPercent, formatNumber, getAxesWithFilter, processMultiQuotaData }