fix: percent labels, background opacity, border radius
- Percent label format: calculate actual % from total instead of {d}
- Value labels: use toLocaleString for thousand separators
- Background opacity: convert hex+opacity to rgba
- Border radius: apply to ECharts container div
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
49ed6a9bcb
commit
b3098eefbd
|
|
@ -46,7 +46,19 @@ function applyCommonStyle(option: Record<string, any>, style: StyleConfig): void
|
|||
}
|
||||
|
||||
option.color = style.colors;
|
||||
option.backgroundColor = style.background.color !== '' ? style.background.color : 'transparent';
|
||||
// Convert background color + opacity to rgba
|
||||
const bgColor = style.background.color || 'transparent';
|
||||
const bgOpacity = style.background.opacity ?? 1;
|
||||
if (bgColor !== 'transparent' && bgOpacity < 1) {
|
||||
// Parse hex to rgba
|
||||
const hex = bgColor.replace('#', '');
|
||||
const r = parseInt(hex.substring(0, 2), 16) || 0;
|
||||
const g = parseInt(hex.substring(2, 4), 16) || 0;
|
||||
const b = parseInt(hex.substring(4, 6), 16) || 0;
|
||||
option.backgroundColor = `rgba(${r},${g},${b},${bgOpacity})`;
|
||||
} else {
|
||||
option.backgroundColor = bgColor;
|
||||
}
|
||||
|
||||
if (style.animation.enabled) {
|
||||
option.animation = true;
|
||||
|
|
@ -121,27 +133,36 @@ function buildAxisConfig(style: StyleConfig, isHorizontal: boolean) {
|
|||
return { xAxis, yAxis };
|
||||
}
|
||||
|
||||
function buildLabelConfig(style: StyleConfig) {
|
||||
function buildLabelConfig(style: StyleConfig, data?: number[]) {
|
||||
if (!style.dataLabel.visible) return { show: false };
|
||||
let formatter: string | undefined;
|
||||
switch (style.dataLabel.format) {
|
||||
case 'percent':
|
||||
formatter = '{b}: {d}%';
|
||||
break;
|
||||
case 'custom':
|
||||
formatter = style.dataLabel.template;
|
||||
break;
|
||||
case 'value':
|
||||
default:
|
||||
formatter = '{c}';
|
||||
break;
|
||||
}
|
||||
|
||||
const position = style.dataLabel.position ?? 'top';
|
||||
const fontSize = style.dataLabel.fontSize ?? 12;
|
||||
const color = style.dataLabel.color ?? '#333';
|
||||
|
||||
if (style.dataLabel.format === 'percent' && data) {
|
||||
const total = data.reduce((a, b) => a + (typeof b === 'number' ? b : 0), 0);
|
||||
return {
|
||||
show: true,
|
||||
fontSize: style.dataLabel.fontSize,
|
||||
color: style.dataLabel.color,
|
||||
position: style.dataLabel.position,
|
||||
formatter,
|
||||
show: true, fontSize, color, position,
|
||||
formatter: (params: any) => {
|
||||
const val = typeof params.value === 'object' ? params.value.value ?? params.value : params.value;
|
||||
const pct = total > 0 ? ((val / total) * 100).toFixed(1) : '0';
|
||||
return pct + '%';
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (style.dataLabel.format === 'custom' && style.dataLabel.template) {
|
||||
return { show: true, fontSize, color, position, formatter: style.dataLabel.template };
|
||||
}
|
||||
|
||||
// Default: value
|
||||
return {
|
||||
show: true, fontSize, color, position,
|
||||
formatter: (params: any) => {
|
||||
const val = typeof params.value === 'object' ? params.value.value ?? params.value : params.value;
|
||||
return typeof val === 'number' ? val.toLocaleString() : String(val);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -168,11 +189,6 @@ export function buildBarOption(
|
|||
const categories = [...new Set(data.map((row) => String(row[xField])))];
|
||||
const { xAxis, yAxis } = buildAxisConfig(style, isHorizontal);
|
||||
|
||||
// If a label binding exists, force show data labels
|
||||
const label = labelBinding
|
||||
? { show: true, position: 'top' as const, fontSize: 12, formatter: '{c}' }
|
||||
: buildLabelConfig(style);
|
||||
|
||||
const colorBinding = getBinding(bindings, 'color');
|
||||
const splitField = seriesBinding?.columnName ?? colorBinding?.columnName;
|
||||
const hasSplit = !!splitField;
|
||||
|
|
@ -180,10 +196,13 @@ export function buildBarOption(
|
|||
if (hasSplit) {
|
||||
// Multi-series: split by series or color field
|
||||
const splitNames = [...new Set(data.map((row) => String(row[splitField])))];
|
||||
const allNums = data.map((r) => Number(r[yField]) || 0);
|
||||
const label = labelBinding
|
||||
? { show: true, position: 'top' as const, fontSize: 12, formatter: (p: any) => (typeof p.value === 'number' ? p.value.toLocaleString() : String(p.value)) }
|
||||
: buildLabelConfig(style, allNums);
|
||||
|
||||
const seriesList = splitNames.map((name, idx) => {
|
||||
const seriesData = categories.map((cat) => {
|
||||
// Sum all matching rows (there may be multiple)
|
||||
const matchingRows = data.filter(
|
||||
(r) => String(r[xField]) === cat && String(r[splitField]) === name,
|
||||
);
|
||||
|
|
@ -217,6 +236,9 @@ export function buildBarOption(
|
|||
const matchingRows = data.filter((r) => String(r[xField]) === cat);
|
||||
return matchingRows.reduce((sum, r) => sum + (Number(r[yField]) || 0), 0);
|
||||
});
|
||||
const label = labelBinding
|
||||
? { show: true, position: 'top' as const, fontSize: 12, formatter: (p: any) => { const v = typeof p.value === 'object' ? p.value.value : p.value; return typeof v === 'number' ? v.toLocaleString() : String(v); } }
|
||||
: buildLabelConfig(style, seriesData);
|
||||
|
||||
if (isHorizontal) {
|
||||
option.yAxis = { ...xAxis, type: 'category', data: categories };
|
||||
|
|
|
|||
|
|
@ -34,8 +34,10 @@ export const ChartRenderer: React.FC<ChartRendererProps> = ({ chart, data }) =>
|
|||
case 'data-table':
|
||||
return <DataTable chart={chart} data={data} />;
|
||||
default:
|
||||
const borderRadius = chart.style?.background?.borderRadius ?? 0;
|
||||
const overflow = borderRadius > 0 ? 'hidden' as const : undefined;
|
||||
return echartsOption
|
||||
? <EChartsBase option={echartsOption} style={{ width: '100%', height: '100%', minHeight: 250 }} />
|
||||
? <EChartsBase option={echartsOption} style={{ width: '100%', height: '100%', minHeight: 250, borderRadius, overflow }} />
|
||||
: <div style={{ padding: 16, color: '#999', textAlign: 'center' }}>请绑定数据字段</div>;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue