Commit 626306b3 authored by Andy-bubu's avatar Andy-bubu

feat: refine dashboard chart palettes

parent a1f45177
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
## 品牌与图表 Token ## 品牌与图表 Token
- 本应用的 HeroUI Primary 入口为 `--accent: #DA241A`,并提供固定 `--primary`/`--color-primary-*` 品牌色阶别名;`--focus` 同值,品牌 Hover 为 `#C91F17`。不得在页面或组件中另设 primary 色或让默认蓝紫主题覆盖。 - 本应用的 HeroUI Primary 入口为 `--accent: #DA241A`,并提供固定 `--primary`/`--color-primary-*` 品牌色阶别名;`--focus` 同值,品牌 Hover 为 `#C91F17`。不得在页面或组件中另设 primary 色或让默认蓝紫主题覆盖。
- 图表必须通过 `XinyuanEChart` 与 `chartPalette.js` 使用 `--xinyuan-chart-*`。色板顺序固定为品牌红深浅、暖棕、灰紫、灰蓝,禁止绿色、荧光色和彩虹式相邻配色;单系列使用品牌红。 - 普通柱状、折线、排名和组合图继续通过 `XinyuanEChart` 使用 `--xinyuan-chart-*`,不承载分类含义的单一数量序列使用品牌红;驾驶舱“案例类型排行”“案例来源分布”“关键字频次”均按稳定分类 key 使用普通红色/暖棕同类色板。饼图/环形图独立使用 `--xinyuan-pie-chart-*`(别名 `--rc-pie-chart-*`)。共享映射保证筛选、排序、分页、Tab 与主题切换不改变分类颜色;页面不得以私有色值覆盖共享色板。
- 图表 Token 在浅色/深色主题中保持相同具体颜色,ECharts 入参必须是可绘制的具体颜色,不能把嵌套 CSS `var(...)` 原样传入 Canvas。 - 图表 Token 在浅色/深色主题中保持相同具体颜色,ECharts 入参必须是可绘制的具体颜色,不能把嵌套 CSS `var(...)` 原样传入 Canvas。
## 样式所有权 ## 样式所有权
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
- [x] 应用切换:同步审计项目管理 Modal 几何、动态分组/计数、其他/全部回退、两列 Card 与 identity well(代码结构) - [x] 应用切换:同步审计项目管理 Modal 几何、动态分组/计数、其他/全部回退、两列 Card 与 identity well(代码结构)
- [x] 驾驶舱:Pro KPI、统一 `XinyuanEChart`/ECharts;环形扇区间隙/10px 扇区圆角/外部引导线、柱图 8px 末端圆角与 24px 分析模块圆角(代码结构) - [x] 驾驶舱:Pro KPI、统一 `XinyuanEChart`/ECharts;环形扇区间隙/10px 扇区圆角/外部引导线、柱图 8px 末端圆角与 24px 分析模块圆角(代码结构)
- [x] 品牌与图表专项:HeroUI `--accent`/`--focus` 固定为 `#DA241A`,品牌 Hover 为 `#C91F17`;图表 16 色切换为低饱和品牌红、暖棕、灰紫、灰蓝体系,移除绿色/荧光色与彩虹式相邻配色;KPI 图标同步使用同一协调色系 - [x] 品牌与图表专项:HeroUI `--accent`/`--focus` 固定为 `#DA241A`,品牌 Hover 为 `#C91F17`;图表 16 色切换为低饱和品牌红、暖棕、灰紫、灰蓝体系,移除绿色/荧光色与彩虹式相邻配色;KPI 图标同步使用同一协调色系
- [x] 分类图表专项:饼图/环形图使用独立 `--xinyuan-pie-chart-*` / `--rc-pie-chart-*`;驾驶舱案例类型排行、案例来源分布和关键字频次恢复普通 `--rc-chart-*` 红色/暖棕同类色板,按稳定分类 key 分色,业务数据保持不变
- [x] 案例管理:Tabs、筛选工具栏、DataGrid、Pagination、ActionBar、EmptyState(代码结构) - [x] 案例管理:Tabs、筛选工具栏、DataGrid、Pagination、ActionBar、EmptyState(代码结构)
- [x] 弹窗:新建、查看/编辑、批量修改、导入、删除确认(代码结构) - [x] 弹窗:新建、查看/编辑、批量修改、导入、删除确认(代码结构)
- [x] 清除 Catalyst/Headless/旧图表调用/原生可见控件与页面私有视觉 CSS - [x] 清除 Catalyst/Headless/旧图表调用/原生可见控件与页面私有视觉 CSS
...@@ -60,4 +61,6 @@ ...@@ -60,4 +61,6 @@
本次品牌与图表专项额外执行:`npm run build`、`git diff --check`、颜色 token 扫描及 Playwright computed token/canvas/viewport 检查。结果:构建通过、差异检查通过、图表与 KPI 未使用绿色/荧光色;1440x900/1280x720 viewport 溢出为 0,移动端宽表保留既有内部横向滚动。 本次品牌与图表专项额外执行:`npm run build`、`git diff --check`、颜色 token 扫描及 Playwright computed token/canvas/viewport 检查。结果:构建通过、差异检查通过、图表与 KPI 未使用绿色/荧光色;1440x900/1280x720 viewport 溢出为 0,移动端宽表保留既有内部横向滚动。
本次分类图表专项已完成代码扫描、`npm run build`、`git diff --check` 和依赖树检查;目标项目唯一 `pie` series(驾驶舱“分类结构”环形图)不再保留页面级颜色覆盖,案例类型排行、案例来源分布和关键字频次使用普通红色/暖棕共享稳定分类映射。真实浏览器复核结果见审计文档。
应用切换与数据表目标页打开态已在历史真实会话中验收;本次主题同步后的受鉴权复核仍需有效验证码,不得使用伪造 token、网络数据或测试数字替代。 应用切换与数据表目标页打开态已在历史真实会话中验收;本次主题同步后的受鉴权复核仍需有效验证码,不得使用伪造 token、网络数据或测试数字替代。
...@@ -19,8 +19,8 @@ ...@@ -19,8 +19,8 @@
- 页容量选择使用现有 HeroUI Autocomplete 的受控 value 合同;切换容量只重置列表页码并复用既有查询回调,不改变筛选、排序或服务请求协议。 - 页容量选择使用现有 HeroUI Autocomplete 的受控 value 合同;切换容量只重置列表页码并复用既有查询回调,不改变筛选、排序或服务请求协议。
- 详情和内容型表单使用鑫元 4xl(1088px)Modal:HeroUI `Modal.Container size="lg"` 保留官方交互与响应式结构,`Modal.Dialog` 通过共享布局类限制为 `bg-background p-7 sm:max-w-[68rem]`,使详情背景与页面画布一致;查看态不添加灰色嵌套 Surface,编辑态直接放置 `secondary` 控件。 - 详情和内容型表单使用鑫元 4xl(1088px)Modal:HeroUI `Modal.Container size="lg"` 保留官方交互与响应式结构,`Modal.Dialog` 通过共享布局类限制为 `bg-background p-7 sm:max-w-[68rem]`,使详情背景与页面画布一致;查看态不添加灰色嵌套 Surface,编辑态直接放置 `secondary` 控件。
- 指标必须使用 HeroUI Pro KPI compound API;所有业务图表统一使用共享 `XinyuanEChart` ECharts 渲染器,图表主题、Tooltip、坐标轴和颜色由鑫元 Token 统一注入。 - 指标必须使用 HeroUI Pro KPI compound API;所有业务图表统一使用共享 `XinyuanEChart` ECharts 渲染器,图表主题、Tooltip、坐标轴和颜色由鑫元 Token 统一注入。
- 图表业务序列只使用 `--rc-chart-*` Token,不使用 UI 状态色或页面硬编码颜色。色板按“品牌红深浅 -> 暖棕 -> 灰紫 -> 灰蓝”协调排序,禁止绿色、荧光色和彩虹式相邻对撞;单系列统一为 `#DA241A`,同一指标状态使用同色相深浅变化。当前 16 色顺序为 `#DA241A`、`#B83A32`、`#C95A52`、`#D47A70`、`#A65A45`、`#B87958`、`#C99772`、`#8A6F63`、`#9A8174`、`#6F6674`、`#807783`、`#938B96`、`#4F6380`、`#61758F`、`#74879A`、`#8798A8`。 - 普通业务序列继续使用 `--rc-chart-*` Token,不使用 UI 状态色或页面硬编码颜色。色板按“品牌红深浅 -> 暖棕 -> 灰紫 -> 灰蓝”协调排序,禁止绿色、荧光色和彩虹式相邻对撞;不承载分类含义的单一数量序列统一为 `#DA241A`,同一指标状态使用同色相深浅变化。当前 16 色顺序为 `#DA241A`、`#B83A32`、`#C95A52`、`#D47A70`、`#A65A45`、`#B87958`、`#C99772`、`#8A6F63`、`#9A8174`、`#6F6674`、`#807783`、`#938B96`、`#4F6380`、`#61758F`、`#74879A`、`#8798A8`。驾驶舱“案例类型排行”“案例来源分布”“关键字频次”恢复使用该普通同类色板,按稳定分类 key 呈现红色到暖棕的协调层次。
- 环形图通过共享 `--xinyuan-chart-sector-gap`、`--xinyuan-chart-sector-radius` 呈现扇区间隙与 10px 扇区圆角;柱图通过共享图表几何统一使用 8px 末端圆角。分析卡片沿用 `.rc-dashboard-card` 的 24px 外圆角。 - 环形图通过共享 `--xinyuan-chart-sector-gap`、`--xinyuan-chart-sector-radius` 呈现扇区间隙与 10px 扇区圆角;柱图通过共享图表几何统一使用 8px 末端圆角。分析卡片沿用 `.rc-dashboard-card` 的 24px 外圆角。饼图/环形图颜色独立使用 `--xinyuan-pie-chart-*`,前六色固定为 `#DA241A`、`#3F5F86`、`#8A668F`、`#C28A3D`、`#8A6253`、`#667080`,由共享适配器按稳定分类 key 分配并避免重复;不使用绿色、青绿色、荧光色或彩虹色,且不影响普通图表色板。
## 跨应用能力 ## 跨应用能力
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
### 图表配色与品牌专项 ### 图表配色与品牌专项
- HeroUI `--accent` 与 `--focus` 已固定为 `#DA241A`,品牌 Hover 为 `#C91F17`;Button、Tabs、Chip、选中态和焦点态不再使用 HeroUI 默认蓝色。 - HeroUI `--accent` 与 `--focus` 已固定为 `#DA241A`,品牌 Hover 为 `#C91F17`;Button、Tabs、Chip、选中态和焦点态不再使用 HeroUI 默认蓝色。
- 饼图/环形图已切换至独立 `--xinyuan-pie-chart-*` / `--rc-pie-chart-*` 专用色板;共享 `XinyuanEChart` 按稳定分类 key 解析颜色并覆盖旧页面级扇区颜色,普通柱状/折线/排名图仍使用原 `--xinyuan-chart-*`。
- `--xinyuan-chart-1..16` 已替换为低饱和、低冲突的品牌红/暖棕/灰紫/灰蓝顺序,完整色板不含绿色或荧光色;同一图表的序列由共享分配器按协调顺序分配。 - `--xinyuan-chart-1..16` 已替换为低饱和、低冲突的品牌红/暖棕/灰紫/灰蓝顺序,完整色板不含绿色或荧光色;同一图表的序列由共享分配器按协调顺序分配。
- 单序列柱图、横向排名图首项、风险重点和主要趋势使用 `--rc-chart-primary`(`#DA241A`);同一指标状态使用品牌红深浅,比较序列使用灰紫/灰蓝,不使用 UI `success` 或绿色 Token。 - 单序列柱图、横向排名图首项、风险重点和主要趋势使用 `--rc-chart-primary`(`#DA241A`);同一指标状态使用品牌红深浅,比较序列使用灰紫/灰蓝,不使用 UI `success` 或绿色 Token。
- KPI 图标表面已改为同一低饱和品牌/中性体系;布局、数据、接口、统计口径、交互回调和业务状态未改变。 - KPI 图标表面已改为同一低饱和品牌/中性体系;布局、数据、接口、统计口径、交互回调和业务状态未改变。
...@@ -77,3 +78,12 @@ View 配置审计确认本次没有实现权限功能:活动代码不读取 `v ...@@ -77,3 +78,12 @@ View 配置审计确认本次没有实现权限功能:活动代码不读取 `v
- 1440x900 下驾驶舱渲染 4 个 ECharts Canvas;抽样 Canvas 像素包含 `#DA241A`、`#B83A32`、`#C95A52`、`#D47A70`、`#A65A45`,未发现绿色系像素。 - 1440x900 下驾驶舱渲染 4 个 ECharts Canvas;抽样 Canvas 像素包含 `#DA241A`、`#B83A32`、`#C95A52`、`#D47A70`、`#A65A45`,未发现绿色系像素。
- 环形图和横向排行图 Tooltip 可正常打开,Tooltip 使用共享背景、边框和阴影 Token;390x844 下 `body.scrollWidth === innerWidth`,无视口横向溢出。 - 环形图和横向排行图 Tooltip 可正常打开,Tooltip 使用共享背景、边框和阴影 Token;390x844 下 `body.scrollWidth === innerWidth`,无视口横向溢出。
- 轻色与临时 `data-theme="dark"` 运行时检查均保持 `--accent`、`--primary`、`--focus` 和 `--xinyuan-chart-1` 为 `#DA241A`;登录页主按钮计算背景为 `rgb(218, 36, 26)`、前景为白色。 - 轻色与临时 `data-theme="dark"` 运行时检查均保持 `--accent`、`--primary`、`--focus` 和 `--xinyuan-chart-1` 为 `#DA241A`;登录页主按钮计算背景为 `rgb(218, 36, 26)`、前景为白色。
## 2026-09-08 饼图 / 环形图专项复核
- 全局扫描确认活动源码只有驾驶舱 `/resource/stat` 的“分类结构”环形图使用 `series.type = 'pie'`;其页面级 `itemStyle.color` 和统一灰色引导线颜色已移除,颜色由共享 `XinyuanEChart` 注入。
- 真实账号登录后在 1440x900 验证 6 个真实分类,6 个扇区颜色互不重复;扇区间隙、圆角、圆环粗细、外部两行标签、中心真实总数和现有 Card 布局均未变化,`body.scrollWidth === innerWidth`。
- Hover Tooltip 显示真实分类与数量,marker 与对应品牌红扇区均为 `rgb(218, 36, 26)`;引导线跟随各扇区颜色。
- “案例来源分布”曾对每个分类数据项固定使用 `--rc-chart-primary`,造成所有柱子完全同色;现已与“案例类型排行”“关键字频次”共同使用 `createResolvedXinyuanChartColorMap`,按各模块完整分类 key 集合使用原红色/暖棕同类色板,不改变统计值、坐标轴、柱宽、圆角或 Tooltip。
- 当前真实接口只返回 6 个分类,未伪造 2/3/4/6+ 分类数据;本轮未能在有效会话内覆盖深色图表打开态。专用 Token 在主题作用域外保持固定 Hex,代码路径和构建已验证,但以上场景仍列为真实数据/会话验收限制。
- 配套分类图表恢复同类色后,以三个独立 8 分类 key 集合验证“案例类型排行”“案例来源分布”“关键字频次”的共享映射,结果均为 8/8 唯一的红色/暖棕色位。`npm run build` 与 `git diff --check` 通过。新的真实浏览器会话连续返回“验证码错误”,因此本次三张柱图的登录后截图仍待有效会话复核。
import { getXinyuanEChartsTheme } from '@/design-system/chartPalette'; import {
createResolvedXinyuanPieChartColorMap,
getXinyuanEChartsTheme,
} from '@/design-system/chartPalette';
import ReactECharts from 'echarts-for-react'; import ReactECharts from 'echarts-for-react';
import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useCallback, useEffect, useMemo, useRef } from 'react';
...@@ -58,6 +61,50 @@ export default function XinyuanEChart({ ...@@ -58,6 +61,50 @@ export default function XinyuanEChart({
const themedOption = useMemo(() => { const themedOption = useMemo(() => {
const theme = getXinyuanEChartsTheme(); const theme = getXinyuanEChartsTheme();
const applyPieTheme = (series) => {
if (series?.type !== 'pie') return series;
const colorMap = createResolvedXinyuanPieChartColorMap(
Array.isArray(series.data) ? series.data.map((item) => item?.name) : [],
);
const data = Array.isArray(series.data)
? series.data.map((item, index) => {
if (!item || typeof item !== 'object' || Array.isArray(item)) return item;
const key = String(item.name ?? '').trim();
const color = colorMap.get(key) || theme.piePalette[index % theme.piePalette.length];
return {
...item,
itemStyle: {
...item.itemStyle,
// Ignore legacy page-level colors for pie/donut slices.
color,
},
labelLine: {
...item.labelLine,
lineStyle: {
...item.labelLine?.lineStyle,
color,
},
},
};
})
: series.data;
// A page-level series itemStyle.color is legacy input and must not
// override the dedicated pie palette. Preserve all other series styles.
const seriesItemStyle = { ...series.itemStyle };
delete seriesItemStyle.color;
return {
...series,
color: theme.piePalette,
itemStyle: seriesItemStyle,
data,
};
};
const themedSeries = Array.isArray(option?.series)
? option.series.map(applyPieTheme)
: applyPieTheme(option?.series);
return { return {
animationDuration: 320, animationDuration: 320,
...@@ -75,6 +122,7 @@ export default function XinyuanEChart({ ...@@ -75,6 +122,7 @@ export default function XinyuanEChart({
...option?.textStyle, ...option?.textStyle,
}, },
...option, ...option,
series: themedSeries,
legend: option?.legend ? { legend: option?.legend ? {
icon: 'circle', icon: 'circle',
itemHeight: 8, itemHeight: 8,
......
...@@ -17,6 +17,19 @@ export const XINYUAN_CHART_PALETTE = Object.freeze([ ...@@ -17,6 +17,19 @@ export const XINYUAN_CHART_PALETTE = Object.freeze([
'var(--rc-chart-16)', 'var(--rc-chart-16)',
]); ]);
// Pie and donut slices use a dedicated palette so bar/line/ranking colors stay
// unchanged. Values resolve from the shared Xinyuan CSS tokens at render time.
export const XINYUAN_PIE_CHART_PALETTE = Object.freeze([
'var(--rc-pie-chart-1)', 'var(--rc-pie-chart-2)',
'var(--rc-pie-chart-3)', 'var(--rc-pie-chart-4)',
'var(--rc-pie-chart-5)', 'var(--rc-pie-chart-6)',
'var(--rc-pie-chart-7)', 'var(--rc-pie-chart-8)',
'var(--rc-pie-chart-9)', 'var(--rc-pie-chart-10)',
'var(--rc-pie-chart-11)', 'var(--rc-pie-chart-12)',
'var(--rc-pie-chart-13)', 'var(--rc-pie-chart-14)',
'var(--rc-pie-chart-15)', 'var(--rc-pie-chart-16)',
]);
export const XINYUAN_CHART_SEMANTIC = Object.freeze({ export const XINYUAN_CHART_SEMANTIC = Object.freeze({
primary: 'var(--rc-chart-primary)', primary: 'var(--rc-chart-primary)',
risk: 'var(--rc-chart-risk)', risk: 'var(--rc-chart-risk)',
...@@ -88,6 +101,31 @@ export function createResolvedXinyuanChartColorMap(keys = [], options = {}) { ...@@ -88,6 +101,31 @@ export function createResolvedXinyuanChartColorMap(keys = [], options = {}) {
); );
} }
/** Resolve pie/donut colors from stable category keys with collision handling. */
export function createResolvedXinyuanPieChartColorMap(keys = []) {
const stableKeys = [...new Set(
(Array.isArray(keys) ? keys : []).map(normalizeChartKey).filter(Boolean),
)].sort();
const colorMap = new Map();
const usedIndexes = new Set();
stableKeys.forEach((key) => {
const preferredIndex = hashChartKey(key) % XINYUAN_PIE_CHART_PALETTE.length;
let paletteIndex = preferredIndex;
for (let offset = 0; offset < XINYUAN_PIE_CHART_PALETTE.length; offset += 1) {
const candidate = (preferredIndex + offset) % XINYUAN_PIE_CHART_PALETTE.length;
if (!usedIndexes.has(candidate)) {
paletteIndex = candidate;
usedIndexes.add(candidate);
break;
}
}
colorMap.set(key, resolveXinyuanChartColor(getXinyuanPieChartColor(paletteIndex)));
});
return colorMap;
}
export function getXinyuanEChartsTheme() { export function getXinyuanEChartsTheme() {
const documentFont = typeof document === 'undefined' const documentFont = typeof document === 'undefined'
? 'Inter, sans-serif' ? 'Inter, sans-serif'
...@@ -102,6 +140,9 @@ export function getXinyuanEChartsTheme() { ...@@ -102,6 +140,9 @@ export function getXinyuanEChartsTheme() {
palette: Array.from({ length: 16 }, (_, index) => ( palette: Array.from({ length: 16 }, (_, index) => (
resolveXinyuanChartColor(readRootToken(`--xinyuan-chart-${index + 1}`, '#667085')) resolveXinyuanChartColor(readRootToken(`--xinyuan-chart-${index + 1}`, '#667085'))
)), )),
piePalette: Array.from({ length: 16 }, (_, index) => (
resolveXinyuanChartColor(readRootToken(`--rc-pie-chart-${index + 1}`, '#667080'))
)),
tooltipBackground: resolveXinyuanChartColor(readRootToken('--xinyuan-chart-tooltip-background', '#ffffff')), tooltipBackground: resolveXinyuanChartColor(readRootToken('--xinyuan-chart-tooltip-background', '#ffffff')),
tooltipBorder: resolveXinyuanChartColor(readRootToken('--xinyuan-chart-tooltip-border', '#dedede')), tooltipBorder: resolveXinyuanChartColor(readRootToken('--xinyuan-chart-tooltip-border', '#dedede')),
tooltipShadow: readRootToken( tooltipShadow: readRootToken(
...@@ -131,6 +172,12 @@ export function getXinyuanChartColor(key, fallbackIndex = 0) { ...@@ -131,6 +172,12 @@ export function getXinyuanChartColor(key, fallbackIndex = 0) {
return XINYUAN_CHART_PALETTE[hashChartKey(stableKey) % XINYUAN_CHART_PALETTE.length]; return XINYUAN_CHART_PALETTE[hashChartKey(stableKey) % XINYUAN_CHART_PALETTE.length];
} }
export function getXinyuanPieChartColor(index = 0) {
return XINYUAN_PIE_CHART_PALETTE[
Math.max(0, Number(index) || 0) % XINYUAN_PIE_CHART_PALETTE.length
];
}
export function createXinyuanChartColorMap(keys = [], options = {}) { export function createXinyuanChartColorMap(keys = [], options = {}) {
const sourceKeys = Array.isArray(keys) ? keys : []; const sourceKeys = Array.isArray(keys) ? keys : [];
const orderedKeys = [...new Set(sourceKeys.map(normalizeChartKey).filter(Boolean))]; const orderedKeys = [...new Set(sourceKeys.map(normalizeChartKey).filter(Boolean))];
......
...@@ -117,6 +117,23 @@ ...@@ -117,6 +117,23 @@
--xinyuan-chart-14: #61758F; --xinyuan-chart-14: #61758F;
--xinyuan-chart-15: #74879A; --xinyuan-chart-15: #74879A;
--xinyuan-chart-16: #8798A8; --xinyuan-chart-16: #8798A8;
/* Pie / donut only. Keep this palette independent from bar/line colors. */
--xinyuan-pie-chart-1: #DA241A;
--xinyuan-pie-chart-2: #3F5F86;
--xinyuan-pie-chart-3: #8A668F;
--xinyuan-pie-chart-4: #C28A3D;
--xinyuan-pie-chart-5: #8A6253;
--xinyuan-pie-chart-6: #667080;
--xinyuan-pie-chart-7: #74879A;
--xinyuan-pie-chart-8: #9A8174;
--xinyuan-pie-chart-9: #6F6674;
--xinyuan-pie-chart-10: #B87958;
--xinyuan-pie-chart-11: #61758F;
--xinyuan-pie-chart-12: #807783;
--xinyuan-pie-chart-13: #B0A4A0;
--xinyuan-pie-chart-14: #AFA5B1;
--xinyuan-pie-chart-15: #897A73;
--xinyuan-pie-chart-16: #556B88;
--xinyuan-chart-sector-gap: var(--surface); --xinyuan-chart-sector-gap: var(--surface);
--xinyuan-chart-sector-radius: 10px; --xinyuan-chart-sector-radius: 10px;
--xinyuan-chart-grid: #e8e8e8; --xinyuan-chart-grid: #e8e8e8;
......
...@@ -164,6 +164,22 @@ ...@@ -164,6 +164,22 @@
--rc-chart-14: var(--xinyuan-chart-14); --rc-chart-14: var(--xinyuan-chart-14);
--rc-chart-15: var(--xinyuan-chart-15); --rc-chart-15: var(--xinyuan-chart-15);
--rc-chart-16: var(--xinyuan-chart-16); --rc-chart-16: var(--xinyuan-chart-16);
--rc-pie-chart-1: var(--xinyuan-pie-chart-1);
--rc-pie-chart-2: var(--xinyuan-pie-chart-2);
--rc-pie-chart-3: var(--xinyuan-pie-chart-3);
--rc-pie-chart-4: var(--xinyuan-pie-chart-4);
--rc-pie-chart-5: var(--xinyuan-pie-chart-5);
--rc-pie-chart-6: var(--xinyuan-pie-chart-6);
--rc-pie-chart-7: var(--xinyuan-pie-chart-7);
--rc-pie-chart-8: var(--xinyuan-pie-chart-8);
--rc-pie-chart-9: var(--xinyuan-pie-chart-9);
--rc-pie-chart-10: var(--xinyuan-pie-chart-10);
--rc-pie-chart-11: var(--xinyuan-pie-chart-11);
--rc-pie-chart-12: var(--xinyuan-pie-chart-12);
--rc-pie-chart-13: var(--xinyuan-pie-chart-13);
--rc-pie-chart-14: var(--xinyuan-pie-chart-14);
--rc-pie-chart-15: var(--xinyuan-pie-chart-15);
--rc-pie-chart-16: var(--xinyuan-pie-chart-16);
--rc-chart-primary: var(--rc-chart-1); --rc-chart-primary: var(--rc-chart-1);
--rc-chart-risk: var(--rc-chart-1); --rc-chart-risk: var(--rc-chart-1);
--rc-chart-warning: var(--rc-chart-3); --rc-chart-warning: var(--rc-chart-3);
......
...@@ -85,10 +85,6 @@ function MetricCard({ icon: Icon, label, value, tone }) { ...@@ -85,10 +85,6 @@ function MetricCard({ icon: Icon, label, value, tone }) {
function DonutChart({ groups }) { function DonutChart({ groups }) {
const data = normalizeChartData(groups); const data = normalizeChartData(groups);
const total = data.reduce((sum, item) => sum + item.value, 0); const total = data.reduce((sum, item) => sum + item.value, 0);
const colorMap = createResolvedXinyuanChartColorMap(
data.map((item) => item.name),
{ order: data.map((item) => item.name) },
);
const geometry = getXinyuanChartGeometry(); const geometry = getXinyuanChartGeometry();
const option = useMemo(() => { const option = useMemo(() => {
const theme = getXinyuanEChartsTheme(); const theme = getXinyuanEChartsTheme();
...@@ -109,14 +105,7 @@ function DonutChart({ groups }) { ...@@ -109,14 +105,7 @@ function DonutChart({ groups }) {
series: [{ series: [{
avoidLabelOverlap: true, avoidLabelOverlap: true,
center: ['50%', '42%'], center: ['50%', '42%'],
data: data.map((item) => { data,
const color = colorMap.get(String(item.name ?? '').trim());
return {
...item,
itemStyle: { color },
labelLine: { lineStyle: { color, width: 1.5 } },
};
}),
label: { label: {
alignTo: 'labelLine', alignTo: 'labelLine',
distanceToLabelLine: 6, distanceToLabelLine: 6,
...@@ -132,7 +121,7 @@ function DonutChart({ groups }) { ...@@ -132,7 +121,7 @@ function DonutChart({ groups }) {
labelLine: { labelLine: {
length: 20, length: 20,
length2: 22, length2: 22,
lineStyle: { color: theme.grid, width: 1.5 }, lineStyle: { width: 1.5 },
show: true, show: true,
}, },
labelLayout: (params) => { labelLayout: (params) => {
...@@ -161,7 +150,7 @@ function DonutChart({ groups }) { ...@@ -161,7 +150,7 @@ function DonutChart({ groups }) {
}], }],
tooltip: { trigger: 'item', valueFormatter: (value) => `${formatCount(value)} 条` }, tooltip: { trigger: 'item', valueFormatter: (value) => `${formatCount(value)} 条` },
}; };
}, [colorMap, data, geometry.sectorGap, geometry.sectorRadius, total]); }, [data, geometry.sectorGap, geometry.sectorRadius, total]);
return <XinyuanEChart ariaLabel="分类结构分布图" option={option} />; return <XinyuanEChart ariaLabel="分类结构分布图" option={option} />;
} }
...@@ -171,7 +160,6 @@ function HorizontalBarChart({ groups }) { ...@@ -171,7 +160,6 @@ function HorizontalBarChart({ groups }) {
const geometry = getXinyuanChartGeometry(); const geometry = getXinyuanChartGeometry();
const colorMap = createResolvedXinyuanChartColorMap( const colorMap = createResolvedXinyuanChartColorMap(
data.map((item) => item.name), data.map((item) => item.name),
{ order: data.map((item) => item.name) },
); );
const option = useMemo(() => ({ const option = useMemo(() => ({
grid: { containLabel: true, left: 16, right: 24, top: 12, bottom: 12 }, grid: { containLabel: true, left: 16, right: 24, top: 12, bottom: 12 },
...@@ -207,6 +195,9 @@ function HorizontalBarChart({ groups }) { ...@@ -207,6 +195,9 @@ function HorizontalBarChart({ groups }) {
function SourceComposedChart({ groups }) { function SourceComposedChart({ groups }) {
const data = normalizeChartData(groups); const data = normalizeChartData(groups);
const geometry = getXinyuanChartGeometry(); const geometry = getXinyuanChartGeometry();
const colorMap = createResolvedXinyuanChartColorMap(
data.map((item) => item.name),
);
const option = useMemo(() => ({ const option = useMemo(() => ({
grid: { containLabel: true, left: 8, right: 16, top: 24, bottom: 12 }, grid: { containLabel: true, left: 8, right: 16, top: 24, bottom: 12 },
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, valueFormatter: (value) => `${formatCount(value)} 条` }, tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, valueFormatter: (value) => `${formatCount(value)} 条` },
...@@ -216,13 +207,16 @@ function SourceComposedChart({ groups }) { ...@@ -216,13 +207,16 @@ function SourceComposedChart({ groups }) {
barMaxWidth: 28, barMaxWidth: 28,
data: data.map((item) => ({ data: data.map((item) => ({
...item, ...item,
itemStyle: { color: resolveXinyuanChartColor(XINYUAN_CHART_SEMANTIC.primary) }, itemStyle: {
color: colorMap.get(String(item.name ?? '').trim())
|| resolveXinyuanChartColor(XINYUAN_CHART_SEMANTIC.primary),
},
})), })),
itemStyle: { borderRadius: [geometry.barRadius, geometry.barRadius, 0, 0] }, itemStyle: { borderRadius: [geometry.barRadius, geometry.barRadius, 0, 0] },
name: '数量', name: '数量',
type: 'bar', type: 'bar',
}], }],
}), [data, geometry.barRadius]); }), [colorMap, data, geometry.barRadius]);
return <XinyuanEChart ariaLabel="案例来源分布图" option={option} />; return <XinyuanEChart ariaLabel="案例来源分布图" option={option} />;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment