Toggle navigation
Toggle navigation
This project
Loading...
Sign in
万朱浩
/
Venue-Ops
Go to a project
Toggle navigation
Projects
Groups
Snippets
Help
Toggle navigation pinning
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
马一丁
2025-11-25 15:42:00 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
2e0a526d2297b338a3cc459a14215c89f1739a16
2e0a526d
1 parent
a7ff9edd
Optimize the Color Replacement Scheme for Pie Charts
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
104 additions
and
15 deletions
ReportEngine/renderers/chart_to_svg.py
ReportEngine/renderers/html_renderer.py
ReportEngine/renderers/chart_to_svg.py
View file @
2e0a526
...
...
@@ -278,6 +278,25 @@ class ChartToSVGConverter:
# 其他格式(十六进制、颜色名等)直接返回
return
color
def
_ensure_visible_color
(
self
,
color
:
Any
,
fallback
:
str
,
min_alpha
:
float
=
0.6
)
->
Any
:
"""
确保颜色在渲染时可见:避免透明值并提升过低的不透明度
"""
base_color
=
fallback
if
color
in
(
None
,
""
,
"transparent"
)
else
color
parsed
=
self
.
_parse_color
(
base_color
)
fallback_parsed
=
self
.
_parse_color
(
fallback
)
if
isinstance
(
parsed
,
tuple
):
if
len
(
parsed
)
==
4
:
r
,
g
,
b
,
a
=
parsed
return
(
r
,
g
,
b
,
max
(
a
,
min_alpha
))
return
parsed
if
isinstance
(
parsed
,
str
)
and
parsed
.
lower
()
==
"transparent"
:
return
fallback_parsed
return
parsed
if
parsed
is
not
None
else
fallback_parsed
def
_get_colors
(
self
,
datasets
:
List
[
Dict
[
str
,
Any
]])
->
List
[
str
]:
"""
获取图表颜色
...
...
@@ -659,12 +678,17 @@ class ChartToSVGConverter:
fig
,
ax
=
self
.
_create_figure
(
width
,
height
,
dpi
,
title
)
# 获取颜色
colors
=
dataset
.
get
(
'backgroundColor'
,
self
.
DEFAULT_COLORS
[:
len
(
labels
)])
if
not
isinstance
(
colors
,
list
):
colors
=
self
.
DEFAULT_COLORS
[:
len
(
labels
)]
raw_colors
=
dataset
.
get
(
'backgroundColor'
,
self
.
DEFAULT_COLORS
[:
len
(
labels
)])
if
not
isinstance
(
raw_colors
,
list
):
raw_colors
=
self
.
DEFAULT_COLORS
[:
len
(
labels
)]
# 【修复】解析每个颜色,将CSS格式转换为matplotlib格式
colors
=
[
self
.
_parse_color
(
c
)
for
c
in
colors
]
colors
=
[
self
.
_ensure_visible_color
(
raw_colors
[
i
]
if
i
<
len
(
raw_colors
)
else
None
,
self
.
DEFAULT_COLORS
[
i
%
len
(
self
.
DEFAULT_COLORS
)]
)
for
i
in
range
(
len
(
labels
))
]
# 绘制饼图
wedges
,
texts
,
autotexts
=
ax
.
pie
(
...
...
@@ -713,12 +737,17 @@ class ChartToSVGConverter:
fig
,
ax
=
self
.
_create_figure
(
width
,
height
,
dpi
,
title
)
# 获取颜色
colors
=
dataset
.
get
(
'backgroundColor'
,
self
.
DEFAULT_COLORS
[:
len
(
labels
)])
if
not
isinstance
(
colors
,
list
):
colors
=
self
.
DEFAULT_COLORS
[:
len
(
labels
)]
raw_colors
=
dataset
.
get
(
'backgroundColor'
,
self
.
DEFAULT_COLORS
[:
len
(
labels
)])
if
not
isinstance
(
raw_colors
,
list
):
raw_colors
=
self
.
DEFAULT_COLORS
[:
len
(
labels
)]
# 【修复】解析每个颜色,将CSS格式转换为matplotlib格式
colors
=
[
self
.
_parse_color
(
c
)
for
c
in
colors
]
colors
=
[
self
.
_ensure_visible_color
(
raw_colors
[
i
]
if
i
<
len
(
raw_colors
)
else
None
,
self
.
DEFAULT_COLORS
[
i
%
len
(
self
.
DEFAULT_COLORS
)]
)
for
i
in
range
(
len
(
labels
))
]
# 绘制圆环图(通过设置wedgeprops实现中空效果)
wedges
,
texts
,
autotexts
=
ax
.
pie
(
...
...
@@ -889,9 +918,17 @@ class ChartToSVGConverter:
ax
.
set_title
(
title
,
fontsize
=
14
,
fontweight
=
'bold'
,
pad
=
20
)
# 获取颜色
colors
=
dataset
.
get
(
'backgroundColor'
,
self
.
DEFAULT_COLORS
[:
len
(
labels
)])
if
not
isinstance
(
colors
,
list
):
colors
=
self
.
DEFAULT_COLORS
[:
len
(
labels
)]
raw_colors
=
dataset
.
get
(
'backgroundColor'
,
self
.
DEFAULT_COLORS
[:
len
(
labels
)])
if
not
isinstance
(
raw_colors
,
list
):
raw_colors
=
self
.
DEFAULT_COLORS
[:
len
(
labels
)]
colors
=
[
self
.
_ensure_visible_color
(
raw_colors
[
i
]
if
i
<
len
(
raw_colors
)
else
None
,
self
.
DEFAULT_COLORS
[
i
%
len
(
self
.
DEFAULT_COLORS
)]
)
for
i
in
range
(
len
(
labels
))
]
# 计算角度
theta
=
np
.
linspace
(
0
,
2
*
np
.
pi
,
len
(
labels
),
endpoint
=
False
)
...
...
ReportEngine/renderers/html_renderer.py
View file @
2e0a526
...
...
@@ -2965,6 +2965,41 @@ function parseRgbString(color) {
return [parts[0], parts[1], parts[2]].map(v => Math.max(0, Math.min(255, v)));
}
function alphaFromColor(color) {
if (typeof color !== 'string') return null;
const raw = color.trim();
if (!raw) return null;
if (raw.toLowerCase() === 'transparent') return 0;
const extractAlpha = (source) => {
const match = source.match(/rgba?
\
s*
\
(([^)]+)
\
)/i);
if (!match) return null;
const parts = match[1].split(',').map(p => p.trim());
if (source.toLowerCase().startsWith('rgba') && parts.length >= 2) {
const alphaToken = parts[parts.length - 1];
const isPercent = /
%
$/.test(alphaToken);
const alphaVal = parseFloat(alphaToken.replace('
%
', ''));
if (!Number.isNaN(alphaVal)) {
const normalizedAlpha = isPercent ? alphaVal / 100 : alphaVal;
return Math.max(0, Math.min(1, normalizedAlpha));
}
}
if (parts.length >= 3) return 1;
return null;
};
const rawAlpha = extractAlpha(raw);
if (rawAlpha !== null) return rawAlpha;
const normalized = normalizeColorToken(raw);
if (typeof normalized === 'string' && normalized !== raw) {
const normalizedAlpha = extractAlpha(normalized);
if (normalizedAlpha !== null) return normalizedAlpha;
}
return null;
}
function rgbFromColor(color) {
const normalized = normalizeColorToken(color);
return hexToRgb(normalized) || parseRgbString(normalized);
...
...
@@ -3012,6 +3047,7 @@ function normalizeDatasetColors(payload, chartType) {
}
const type = chartType || 'bar';
const needsArrayColors = type === 'pie' || type === 'doughnut' || type === 'polarArea';
const MIN_PIE_ALPHA = 0.6;
const pickColor = (value, fallback) => {
if (Array.isArray(value) && value.length) return value[0];
return value || fallback;
...
...
@@ -3036,13 +3072,29 @@ function normalizeDatasetColors(payload, chartType) {
const dataLength = Array.isArray(dataset.data) ? dataset.data.length : 0;
const total = Math.max(labelCount, rawColors.length, dataLength, 1);
const normalizedColors = [];
let fixedTransparentCount = 0;
for (let i = 0; i < total; i++) {
const fallbackColor = DEFAULT_CHART_COLORS[(idx + i)
%
DEFAULT_CHART_COLORS.length];
const normalizedColor = liftDarkColor(rawColors[i] || fallbackColor);
const normalizedRaw = normalizeColorToken(rawColors[i]);
const alpha = alphaFromColor(normalizedRaw);
const isInvisible = typeof normalizedRaw === 'string' && normalizedRaw.toLowerCase() === 'transparent';
if (alpha === 0 || isInvisible) {
fixedTransparentCount += 1;
}
const baseColor = (!normalizedRaw || isInvisible) ? fallbackColor : normalizedRaw;
const targetAlpha = alpha === null ? 1 : alpha;
const normalizedColor = ensureAlpha(
liftDarkColor(baseColor),
Math.max(MIN_PIE_ALPHA, targetAlpha)
);
normalizedColors.push(normalizedColor);
}
dataset.backgroundColor = normalizedColors;
changes.push(`dataset${idx}: 标准化扇区颜色(${normalizedColors.length})`);
dataset.borderColor = normalizedColors.map(col => ensureAlpha(liftDarkColor(col), 1));
const changeLabel = fixedTransparentCount
? `dataset${idx}: 修正${fixedTransparentCount}个透明扇区`
: `dataset${idx}: 标准化扇区颜色(${normalizedColors.length})`;
changes.push(changeLabel);
return;
}
...
...
Please
register
or
login
to post a comment