From ea91ed6d5d457e1b803f053b4f74db70624c0a47 Mon Sep 17 00:00:00 2001 From: shabix <2936670268@qq.com> Date: Thu, 2 Jan 2025 18:51:32 +0800 Subject: [PATCH 1/9] docs: init docs of theme sourceCode #3535 --- .../contributing/zh/sourcesode/11-1 theme concept.md | 0 .../zh/sourcesode/11-2 theme explanation..md | 10 ++++++++++ 2 files changed, 10 insertions(+) create mode 100644 docs/assets/contributing/zh/sourcesode/11-1 theme concept.md create mode 100644 docs/assets/contributing/zh/sourcesode/11-2 theme explanation..md diff --git a/docs/assets/contributing/zh/sourcesode/11-1 theme concept.md b/docs/assets/contributing/zh/sourcesode/11-1 theme concept.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/assets/contributing/zh/sourcesode/11-2 theme explanation..md b/docs/assets/contributing/zh/sourcesode/11-2 theme explanation..md new file mode 100644 index 0000000000..f53aaf578f --- /dev/null +++ b/docs/assets/contributing/zh/sourcesode/11-2 theme explanation..md @@ -0,0 +1,10 @@ +# 主题配置的解析逻辑 + +在这一部分,我们将详细介绍主题配置的解析逻辑。 + +## 代码示例 + +# 主题的更新逻辑 + +## 主要解读setCurrentThemeSync()方法 + From da850b3a81db3fe99575f22409f0e1730b574369 Mon Sep 17 00:00:00 2001 From: dundun003 Date: Mon, 13 Jan 2025 11:20:47 +0800 Subject: [PATCH 2/9] docs(theme): analyze theme configuration and merging rules --- .../zh/sourcesode/11-2 theme explanation..md | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/docs/assets/contributing/zh/sourcesode/11-2 theme explanation..md b/docs/assets/contributing/zh/sourcesode/11-2 theme explanation..md index f53aaf578f..8ae3a9f52d 100644 --- a/docs/assets/contributing/zh/sourcesode/11-2 theme explanation..md +++ b/docs/assets/contributing/zh/sourcesode/11-2 theme explanation..md @@ -1,10 +1,42 @@ -# 主题配置的解析逻辑 +# 主题的配置解析逻辑 -在这一部分,我们将详细介绍主题配置的解析逻辑。 +VChart 提供了两种方式配置图表主题: -## 代码示例 +- 通过图表 spec 配置 +- 通过 ThemeManager 注册主题 -# 主题的更新逻辑 +## 主题配置的获取与优先级比较 (core/vchart.ts) -## 主要解读setCurrentThemeSync()方法 +这两种配置都可以通过配置一套 `ITheme` 类型的主题对象,但是这两种配置的优先级是什么呢? +> **注意**:严谨地说是三种,还有可以在 option 中配置,但是教程文档没有提到。源码中都告诉我们是: +> `currentTheme` < `optionTheme` < `specTheme` + +在 `src/core/vchart.ts` 中有如下属性,获取到了用户配置的主题内容: + +- `_spec.theme`:用户在图表 spec 对象配置中指定的主题 +- `_currentThemeName`:通过 `VChart.ThemeManager.registerTheme` 注册的当前全局主题名称 + +### 简析主题合并的逻辑 (util/theme/merge-theme.ts) + +#### mergeTheme 函数 + +```typescript +export function mergeTheme(target: Maybe, ...sources: Maybe[]): Maybe { + return mergeSpec(transformThemeToMerge(target), ...sources.map(transformThemeToMerge)); +} +``` + +#### mergeTheme 函数特点 + +- 是合并主题的基础,一层简单的封装 +- 表现结果是后出现的 `sources` 会覆盖前出现的 `theme` +- 核心思路:简单地说是对象的属性覆盖 + +#### processThemeByChartType 函数 + +- 实现了特定类型表格的配置处理 +- 提供更细粒度的主题定制能力 +- 允许不同图表类型有独特的样式配置 + +经过层层关于优先级比较,表格类型的合并(`processThemeByChartType`),主题的合并处理逻辑,最终得到挂载在 VChart 对象里的 `currentTheme` 属性。 From def0713a0cb374d685364497fbd9a41b0bd739cb Mon Sep 17 00:00:00 2001 From: dundun003 Date: Mon, 13 Jan 2025 11:32:19 +0800 Subject: [PATCH 3/9] chore: config docs path to render site --- docs/assets/contributing/menu.json | 7 ++ ...lanation..md => 11-2 Theme explanation.md} | 84 +++++++++---------- 2 files changed, 49 insertions(+), 42 deletions(-) rename docs/assets/contributing/zh/sourcesode/{11-2 theme explanation..md => 11-2 Theme explanation.md} (97%) diff --git a/docs/assets/contributing/menu.json b/docs/assets/contributing/menu.json index c5d5bb185d..cd430f189a 100644 --- a/docs/assets/contributing/menu.json +++ b/docs/assets/contributing/menu.json @@ -35,6 +35,13 @@ "zh": "5.如何贡献代码", "en": "5-How to Contribute Code" } + }, + { + "path": "sourcesode/11-2-Theme explanation", + "title": { + "zh": "11-2-主题说明", + "en": "11-2-Theme explanation" + } } ] } \ No newline at end of file diff --git a/docs/assets/contributing/zh/sourcesode/11-2 theme explanation..md b/docs/assets/contributing/zh/sourcesode/11-2 Theme explanation.md similarity index 97% rename from docs/assets/contributing/zh/sourcesode/11-2 theme explanation..md rename to docs/assets/contributing/zh/sourcesode/11-2 Theme explanation.md index 8ae3a9f52d..ae56bbbada 100644 --- a/docs/assets/contributing/zh/sourcesode/11-2 theme explanation..md +++ b/docs/assets/contributing/zh/sourcesode/11-2 Theme explanation.md @@ -1,42 +1,42 @@ -# 主题的配置解析逻辑 - -VChart 提供了两种方式配置图表主题: - -- 通过图表 spec 配置 -- 通过 ThemeManager 注册主题 - -## 主题配置的获取与优先级比较 (core/vchart.ts) - -这两种配置都可以通过配置一套 `ITheme` 类型的主题对象,但是这两种配置的优先级是什么呢? - -> **注意**:严谨地说是三种,还有可以在 option 中配置,但是教程文档没有提到。源码中都告诉我们是: -> `currentTheme` < `optionTheme` < `specTheme` - -在 `src/core/vchart.ts` 中有如下属性,获取到了用户配置的主题内容: - -- `_spec.theme`:用户在图表 spec 对象配置中指定的主题 -- `_currentThemeName`:通过 `VChart.ThemeManager.registerTheme` 注册的当前全局主题名称 - -### 简析主题合并的逻辑 (util/theme/merge-theme.ts) - -#### mergeTheme 函数 - -```typescript -export function mergeTheme(target: Maybe, ...sources: Maybe[]): Maybe { - return mergeSpec(transformThemeToMerge(target), ...sources.map(transformThemeToMerge)); -} -``` - -#### mergeTheme 函数特点 - -- 是合并主题的基础,一层简单的封装 -- 表现结果是后出现的 `sources` 会覆盖前出现的 `theme` -- 核心思路:简单地说是对象的属性覆盖 - -#### processThemeByChartType 函数 - -- 实现了特定类型表格的配置处理 -- 提供更细粒度的主题定制能力 -- 允许不同图表类型有独特的样式配置 - -经过层层关于优先级比较,表格类型的合并(`processThemeByChartType`),主题的合并处理逻辑,最终得到挂载在 VChart 对象里的 `currentTheme` 属性。 +# 主题的配置解析逻辑 + +VChart 提供了两种方式配置图表主题: + +- 通过图表 spec 配置 +- 通过 ThemeManager 注册主题 + +## 主题配置的获取与优先级比较 (core/vchart.ts) + +这两种配置都可以通过配置一套 `ITheme` 类型的主题对象,但是这两种配置的优先级是什么呢? + +> **注意**:严谨地说是三种,还有可以在 option 中配置,但是教程文档没有提到。源码中都告诉我们是: +> `currentTheme` < `optionTheme` < `specTheme` + +在 `src/core/vchart.ts` 中有如下属性,获取到了用户配置的主题内容: + +- `_spec.theme`:用户在图表 spec 对象配置中指定的主题 +- `_currentThemeName`:通过 `VChart.ThemeManager.registerTheme` 注册的当前全局主题名称 + +### 简析主题合并的逻辑 (util/theme/merge-theme.ts) + +#### mergeTheme 函数 + +```typescript +export function mergeTheme(target: Maybe, ...sources: Maybe[]): Maybe { + return mergeSpec(transformThemeToMerge(target), ...sources.map(transformThemeToMerge)); +} +``` + +#### mergeTheme 函数特点 + +- 是合并主题的基础,一层简单的封装 +- 表现结果是后出现的 `sources` 会覆盖前出现的 `theme` +- 核心思路:简单地说是对象的属性覆盖 + +#### processThemeByChartType 函数 + +- 实现了特定类型表格的配置处理 +- 提供更细粒度的主题定制能力 +- 允许不同图表类型有独特的样式配置 + +经过层层关于优先级比较,表格类型的合并(`processThemeByChartType`),主题的合并处理逻辑,最终得到挂载在 VChart 对象里的 `currentTheme` 属性。 From 4cad1ed3426640ab898361962b672be06bcaaa1b Mon Sep 17 00:00:00 2001 From: dundun003 Date: Mon, 13 Jan 2025 11:33:58 +0800 Subject: [PATCH 4/9] fix: rename docs file --- docs/assets/contributing/menu.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/assets/contributing/menu.json b/docs/assets/contributing/menu.json index cd430f189a..b557d39a38 100644 --- a/docs/assets/contributing/menu.json +++ b/docs/assets/contributing/menu.json @@ -37,10 +37,10 @@ } }, { - "path": "sourcesode/11-2-Theme explanation", + "path": "sourcesode/11-2 Theme explanation", "title": { - "zh": "11-2-主题说明", - "en": "11-2-Theme explanation" + "zh": "11-2.主题说明", + "en": "11-2.Theme explanation" } } ] From c51b96389fdb4908fc384752e926dba1ff3cc087 Mon Sep 17 00:00:00 2001 From: dundun003 Date: Tue, 14 Jan 2025 13:49:47 +0800 Subject: [PATCH 5/9] docs: analysis of theme preprocessing and parsing --- docs/assets/contributing/menu.json | 9 +- .../zh/sourcesode/11-1 Theme update.md | 3 + .../zh/sourcesode/11-1 theme concept.md | 0 .../zh/sourcesode/11-2 Theme explanation.md | 271 +++++++++++++++++- 4 files changed, 273 insertions(+), 10 deletions(-) create mode 100644 docs/assets/contributing/zh/sourcesode/11-1 Theme update.md delete mode 100644 docs/assets/contributing/zh/sourcesode/11-1 theme concept.md diff --git a/docs/assets/contributing/menu.json b/docs/assets/contributing/menu.json index b557d39a38..7f5439cd24 100644 --- a/docs/assets/contributing/menu.json +++ b/docs/assets/contributing/menu.json @@ -36,10 +36,17 @@ "en": "5-How to Contribute Code" } }, + { + "path": "sourcesode/11-1 Theme update", + "title": { + "zh": "11-1.主题的更新逻辑", + "en": "11-1.Theme update" + } + }, { "path": "sourcesode/11-2 Theme explanation", "title": { - "zh": "11-2.主题说明", + "zh": "11-2.主题的解析", "en": "11-2.Theme explanation" } } diff --git a/docs/assets/contributing/zh/sourcesode/11-1 Theme update.md b/docs/assets/contributing/zh/sourcesode/11-1 Theme update.md new file mode 100644 index 0000000000..aa7bd61edc --- /dev/null +++ b/docs/assets/contributing/zh/sourcesode/11-1 Theme update.md @@ -0,0 +1,3 @@ +# 主题更新 + +咕咕咕咕 diff --git a/docs/assets/contributing/zh/sourcesode/11-1 theme concept.md b/docs/assets/contributing/zh/sourcesode/11-1 theme concept.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/assets/contributing/zh/sourcesode/11-2 Theme explanation.md b/docs/assets/contributing/zh/sourcesode/11-2 Theme explanation.md index ae56bbbada..caf70c6427 100644 --- a/docs/assets/contributing/zh/sourcesode/11-2 Theme explanation.md +++ b/docs/assets/contributing/zh/sourcesode/11-2 Theme explanation.md @@ -9,7 +9,13 @@ VChart 提供了两种方式配置图表主题: 这两种配置都可以通过配置一套 `ITheme` 类型的主题对象,但是这两种配置的优先级是什么呢? -> **注意**:严谨地说是三种,还有可以在 option 中配置,但是教程文档没有提到。源码中都告诉我们是: +> **注意**:严谨地说是三种主题来源: +> +> - `currentTheme`:通过 `ThemeManager` 注册的全局默认主题 +> - `optionTheme`:在 VChart 构造函数的 options 中传入的主题 +> - `specTheme`:在图表规格(spec)中指定的主题 +> +> 它们的优先级从低到高依次是: > `currentTheme` < `optionTheme` < `specTheme` 在 `src/core/vchart.ts` 中有如下属性,获取到了用户配置的主题内容: @@ -27,16 +33,263 @@ export function mergeTheme(target: Maybe, ...sources: Maybe[]): } ``` -#### mergeTheme 函数特点 - -- 是合并主题的基础,一层简单的封装 +- 是合并主题的基础,一层简单的封装,简单地说是对象的属性覆盖 - 表现结果是后出现的 `sources` 会覆盖前出现的 `theme` -- 核心思路:简单地说是对象的属性覆盖 + +**合并示例** + +```typescript +const baseTheme = { color: 'blue', fontSize: 12 }; +const optionTheme = { color: 'red' }; +const specTheme = { fontSize: 14 }; + +const finalTheme = mergeTheme({}, baseTheme, optionTheme, specTheme); +// 结果:{ color: 'red', fontSize: 14 } +``` #### processThemeByChartType 函数 -- 实现了特定类型表格的配置处理 -- 提供更细粒度的主题定制能力 -- 允许不同图表类型有独特的样式配置 +```typescript +const processThemeByChartType = (type: string, theme: ITheme) => { + if (theme.chart?.[type]) { + theme = mergeTheme({}, theme, theme.chart[type]); + } + return theme; +}; +``` + +processThemeByChartType 是 VChart 主题系统中实现图表类型个性化的关键函数。它通过条件合并和 mergeTheme,实现了在保持全局主题一致性的同时,为不同图表类型提供定制化样式的能力。 + +### 字符串主题与对象主题的解析处理 + +用户配置主题时可以简单便捷的传入字符串主题,例如: + +```typescript +const chart = new VChart({ + theme: 'light' +}); +``` + +也可以传入详细配置的对象主题,例如: + +```typescript +const chart = new VChart({ + theme: { + color: { primary: 'red' }, + fontSize: 14, + chart: { + bar: { + color: 'blue' + } + } + } +}); +``` + +在源码里针对两者的处理的核心,在\_updateCurrentTheme 里判断类型,并通过 getThemeObject()做转化,统一处理成对象主题来解析的,这是个简单的逻辑,却为 VChart 的配置提供了灵活性和便捷性。 + +最终,经过层层关于优先级比较,表格类型的合并(`processThemeByChartType`),主题的合并处理逻辑,最终得到挂载在 VChart 对象里的 `currentTheme` 属性。 + +## 主题配置的预处理 + +当主题配置,合并后,会进入预处理阶段。主题预处理是 VChart 主题系统的关键步骤,主要完成以下工作: + +1. 语义化颜色转换 + - 将 `{ color: 'brand.primary' }` 转换为具体颜色值 +2. Token 替换 + - 将 `{ fontSize: 'size.m' }` 转换为具体字号 +3. 递归处理嵌套对象 + +将抽象的主题描述转换为具体的样式配置,为开发者提供直观的配置能力 + +**预处理流程**: + +```typescript +this._currentTheme = preprocessTheme(processThemeByChartType(chartType, finalTheme)); +``` + +## 主题的预处理与解析 + +```typescript +export function preprocessTheme( + obj: any, //主题对象 + colorScheme?: IThemeColorScheme, // 颜色方案 + tokenMap?: TokenMap, // 标记映射 + seriesSpec?: ISeriesSpec // 系列规格 +); +``` + +这里涉及了 VChart 主题配置的三个重要概念: + +- `colorScheme`: 颜色方案 +- `tokenMap`: 标记映射 + +```typescript +VChart.ThemeManager.registerTheme('dataVizTheme', { + colorScheme: { + brand: { primary: '#3A8DFF' }, + data: { + positive: '#48BB78', + negative: '#F56565' + } + }, + tokenMap: { + typography: { + fontSize: { + small: 12, + medium: 14, + large: 16 + } + } + } +}); +``` + +开发者可以在注册时利用`registerTheme`方法仿照如上案例注册一套基于这 2 个概念的复杂主题配置,在实际使用中,开发者可以通过 { color: 'data.positive' } 或 { fontSize: { token: 'typography.fontSize.medium' } } 的方式引用这些定义。这里谈谈 VChart 是如何解析这个复杂对象的。 + +先逐层分析,这个处理函数 processTheme 的关键算法是递归遍历对象: + +```typescript +Object.keys(obj).forEach(key => { + const value = obj[key]; + if (IGNORE_KEYS.includes(key)) { + newObj[key] = value; + } + // 处理颜色语义化转换、Token 语义化转换 + else if (isPlainObject(value)) { + if (isColorKey(value)) { + newObj[key] = getActualColor(value, colorScheme, seriesSpec); + } else if (isTokenKey(value)) { + newObj[key] = queryToken(tokenMap, value); + } + // 这里使用了递归处理嵌套对象,使得能够处理任意深度的嵌套对象 + else { + newObj[key] = preprocessTheme(value, colorScheme, tokenMap, seriesSpec); + } + } + // 非对象类型直接赋值 + else { + newObj[key] = value; + } +}); +``` + +接下来分析具体的对于颜色语义和 token 语义的处理与解析 + +#### getActualColor 颜色语义化 + +```typescript +/** 查询语义化颜色 */ +export const getActualColor = (value: any, colorScheme?: IThemeColorScheme, seriesSpec?: ISeriesSpec) => { + if (colorScheme && isColorKey(value)) { + const color = queryColorFromColorScheme(colorScheme, value, seriesSpec); + if (color) { + return color; + } + } + return value; +}; + +export function queryColorFromColorScheme( + colorScheme: IThemeColorScheme, + colorKey: IColorKey, + seriesSpec?: ISeriesSpec +): ColorSchemeItem | undefined { + const scheme = getColorSchemeBySeries(colorScheme, seriesSpec); + if (!scheme) { + return undefined; + } + let color; + const { palette } = scheme as IColorSchemeStruct; + if (isObject(palette)) { + color = getUpgradedTokenValue(palette, colorKey.key) ?? colorKey.default; + } + if (!color) { + return undefined; + } + if ((isNil(colorKey.a) && isNil(colorKey.l)) || !isString(color)) { + return color; + } + let c = new Color(color); + if (isValid(colorKey.l)) { + const { r, g, b } = c.color; + const { h, s } = rgbToHsl(r, g, b); + const rgb = hslToRgb(h, s, colorKey.l); + const newColor = new Color(`rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`); + newColor.setOpacity(c.color.opacity); + c = newColor; + } + if (isValid(colorKey.a)) { + c.setOpacity(colorKey.a); + } + return c.toRGBA(); +} +``` + +queryColorFromColorScheme 是 VChart 主题系统中颜色处理的核心函数,它接收颜色方案(colorScheme)、颜色键(colorKey)和可选的系列规格(seriesSpec),通过一系列复杂的颜色查找和转换算法,实现了语义化颜色的精确定位和动态增强。 + +函数的核心逻辑是:首先根据系列规格获取特定的颜色方案,然后从调色板中查找对应的颜色。 + +```typescript +export function getColorSchemeBySeries( + colorScheme?: IThemeColorScheme, + seriesSpec?: ISeriesSpec +): ColorScheme | undefined { + const { type: seriesType } = seriesSpec ?? {}; + let scheme: ColorScheme | undefined; + if (!seriesSpec || isNil(seriesType)) { + scheme = colorScheme?.default; + } else { + const direction = getDirectionFromSeriesSpec(seriesSpec); + scheme = colorScheme?.[`${seriesType}_${direction}`] ?? colorScheme?.[seriesType] ?? colorScheme?.default; + } + return scheme; +} +``` + +这个算法优先匹配具体 `seriesType_direction` 的颜色方案,然后再匹配通用 `seriesType` 的颜色方案,最后再匹配默认颜色方案。 + +值得一提的是,此外函数还提供了两种高级颜色处理能力,根据 `colorKey` 中 `l` 或 `a` 的属性来动态处理颜色特性: + +1. **通过 HSL 色彩空间转换实现颜色亮度的动态调整** + + - **算法原理** + + > - 色彩空间转换:RGB → HSL → RGB + + - **HSL 亮度调整核心代码** + + ```javascript + if (isValid(colorKey.l)) { + const { r, g, b } = c.color; + const { h, s } = rgbToHsl(r, g, b); + + // 关键步骤:保持色相和饱和度,仅调整亮度 + const rgb = hslToRgb(h, s, colorKey.l); + + const newColor = new Color(`rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`); + newColor.setOpacity(c.color.opacity); + c = newColor; + } + ``` + +2. **直接设置颜色的透明度** + - **透明度调整核心代码** + ```javascript + if (isValid(colorKey.a)) { + c.setOpacity(colorKey.a); + } + ``` + +#### queryToken Token 语义化 + +```typescript +export function queryToken(tokenMap: TokenMap, tokenKey: ITokenKey): T | undefined { + if (tokenMap && tokenKey.key in tokenMap) { + return tokenMap[tokenKey.key]; + } + return tokenKey.default; +} +``` -经过层层关于优先级比较,表格类型的合并(`processThemeByChartType`),主题的合并处理逻辑,最终得到挂载在 VChart 对象里的 `currentTheme` 属性。 +这个函数用于根据 tokenMap 和 tokenKey 查询对应的 token 值,如果 tokenMap 中存在对应的 token,就返回对应的值,否则返回默认值。 From dd68b6c4577bace2e5113ca03246fbcba6c265e7 Mon Sep 17 00:00:00 2001 From: dundun003 Date: Tue, 14 Jan 2025 14:03:44 +0800 Subject: [PATCH 6/9] docs: fix error of theme preprocessing and parsing --- .../zh/sourcesode/11-2 Theme explanation.md | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/assets/contributing/zh/sourcesode/11-2 Theme explanation.md b/docs/assets/contributing/zh/sourcesode/11-2 Theme explanation.md index caf70c6427..c8de11e3c8 100644 --- a/docs/assets/contributing/zh/sourcesode/11-2 Theme explanation.md +++ b/docs/assets/contributing/zh/sourcesode/11-2 Theme explanation.md @@ -7,7 +7,7 @@ VChart 提供了两种方式配置图表主题: ## 主题配置的获取与优先级比较 (core/vchart.ts) -这两种配置都可以通过配置一套 `ITheme` 类型的主题对象,但是这两种配置的优先级是什么呢? +这两种配置都可以通过配置一套 `ITheme` 类型的主题对象,但是这两种配置的优先级是什么呢?这在\_updateCurrentTheme 方法里处理了优先级问题: > **注意**:严谨地说是三种主题来源: > @@ -95,9 +95,9 @@ const chart = new VChart({ 当主题配置,合并后,会进入预处理阶段。主题预处理是 VChart 主题系统的关键步骤,主要完成以下工作: 1. 语义化颜色转换 - - 将 `{ color: 'brand.primary' }` 转换为具体颜色值 + - 将形如 `{ color: 'brand.primary' }` 的颜色语义转换为具体颜色值 2. Token 替换 - - 将 `{ fontSize: 'size.m' }` 转换为具体字号 + - 将形如 `{ fontSize: 'size.m' }` 的 token 语义转换为具体字号 3. 递归处理嵌套对象 将抽象的主题描述转换为具体的样式配置,为开发者提供直观的配置能力 @@ -273,13 +273,16 @@ export function getColorSchemeBySeries( } ``` +todo: rgbToHsl hslToRgb 的分析 + 2. **直接设置颜色的透明度** - - **透明度调整核心代码** - ```javascript - if (isValid(colorKey.a)) { - c.setOpacity(colorKey.a); - } - ``` + +- **透明度调整核心代码** + ```javascript + if (isValid(colorKey.a)) { + c.setOpacity(colorKey.a); + } + ``` #### queryToken Token 语义化 From ae807229d9bae313c181602a444ecad311a1491e Mon Sep 17 00:00:00 2001 From: dundun003 Date: Wed, 15 Jan 2025 18:03:39 +0800 Subject: [PATCH 7/9] chore: revert useless changes to seperate PR --- docs/assets/contributing/menu.json | 13 +++---------- .../contributing/zh/sourcesode/11-1 Theme update.md | 3 --- 2 files changed, 3 insertions(+), 13 deletions(-) delete mode 100644 docs/assets/contributing/zh/sourcesode/11-1 Theme update.md diff --git a/docs/assets/contributing/menu.json b/docs/assets/contributing/menu.json index 7f5439cd24..e77863cd39 100644 --- a/docs/assets/contributing/menu.json +++ b/docs/assets/contributing/menu.json @@ -37,17 +37,10 @@ } }, { - "path": "sourcesode/11-1 Theme update", + "path": "sourcesode/11-1 Theme explanation", "title": { - "zh": "11-1.主题的更新逻辑", - "en": "11-1.Theme update" - } - }, - { - "path": "sourcesode/11-2 Theme explanation", - "title": { - "zh": "11-2.主题的解析", - "en": "11-2.Theme explanation" + "zh": "11-1.主题的解析", + "en": "11-1.Theme explanation" } } ] diff --git a/docs/assets/contributing/zh/sourcesode/11-1 Theme update.md b/docs/assets/contributing/zh/sourcesode/11-1 Theme update.md deleted file mode 100644 index aa7bd61edc..0000000000 --- a/docs/assets/contributing/zh/sourcesode/11-1 Theme update.md +++ /dev/null @@ -1,3 +0,0 @@ -# 主题更新 - -咕咕咕咕 From cae081f3e8b76012432b57bad0c8838846c3f507 Mon Sep 17 00:00:00 2001 From: dundun003 Date: Wed, 15 Jan 2025 18:03:50 +0800 Subject: [PATCH 8/9] chore: revert useless changes to seperate PR --- .../{11-2 Theme explanation.md => 11-1 Theme explanation.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/assets/contributing/zh/sourcesode/{11-2 Theme explanation.md => 11-1 Theme explanation.md} (100%) diff --git a/docs/assets/contributing/zh/sourcesode/11-2 Theme explanation.md b/docs/assets/contributing/zh/sourcesode/11-1 Theme explanation.md similarity index 100% rename from docs/assets/contributing/zh/sourcesode/11-2 Theme explanation.md rename to docs/assets/contributing/zh/sourcesode/11-1 Theme explanation.md From 8e050d7e89315aa4ec629a1f16f876d0d56fe795 Mon Sep 17 00:00:00 2001 From: dundun003 Date: Thu, 16 Jan 2025 17:24:14 +0800 Subject: [PATCH 9/9] docs: complete theme config soucecode explanation --- .../zh/sourcesode/11-1 Theme explanation.md | 210 ++++++++++++++---- 1 file changed, 164 insertions(+), 46 deletions(-) diff --git a/docs/assets/contributing/zh/sourcesode/11-1 Theme explanation.md b/docs/assets/contributing/zh/sourcesode/11-1 Theme explanation.md index c8de11e3c8..352e1bfe5a 100644 --- a/docs/assets/contributing/zh/sourcesode/11-1 Theme explanation.md +++ b/docs/assets/contributing/zh/sourcesode/11-1 Theme explanation.md @@ -1,31 +1,68 @@ -# 主题的配置解析逻辑 +# VChart 主题相关概念 + +VChart 的主题模块是一个强大且灵活的图表样式配置系统。它允许用户通过统一和可复用的方式定制图表的视觉外观。用户可以轻松地为整个图表或特定图表类型定义全面的样式配置,包括颜色、字体、布局、组件样式等。通过预定义主题,用户可以快速实现一致的设计风格,无需为每个图表重复配置样式,从而大大简化了图表开发过程,并确保图表在不同场景下保持视觉一致性和专业性。简单来说,VChart 的主题就像是图表的"设计模板",用户只需选择或自定义主题,就能快速创建美观、专业的数据可视化图表。 + +主题概念相关文档:[VisActor/VChart tutorial documents](https://www.visactor.io/vchart/guide/tutorial_docs/Theme/Theme_Concept_and_Design_Rules) + +## 主题相关源码位置与内容 + +- package/vchart/scr/util/theme:主题相关的工具类文件夹,包含对主题合并,解析,预处理(色板,token 语义化)以及字符串主题转对象等实用的工具。 + +- package/vchart/scr/core/vchart.ts:定义了核心类 VChart,包括图表生命周期内的一系列钩子例如 主题初始化,注册,更新,切换,销毁。VChart 是具体的图表实例,负责应用和渲染,与主题的配置和更新有密不可分的联系。 + +- package/vchart/src/theme:该文件夹包含了主题相关的特殊概念:色板(color-theme)、tokenMap、主题管理类(theme-manager)等数据结构。 + +## 核心类及之间的联系 + +- VChart:负责图表的具体渲染、实例化和生命周期管理 + +- ThemeManager:负责主题的全局注册、管理和切换 + +`ThemeManager`作为 VChart 的一个静态类暴露出来,用户可以使用诸如 + +`VChart.ThemeManager.registerTheme('myTheme', { ... });`或`VChart.ThemeManager.setCurrentTheme('myTheme');`来管理主题 + +```typescript +export class VChart implements IVChart { + static readonly ThemeManager = ThemeManager; +} +``` + +但是本质上,`ThemeManager `仍然是一个独立的类,只是通过这种方式提供了更便捷的访问方式,这种静态属性暴露的设计模式做到了主题管理和图表渲染的解耦。 + +# **主题的配置解析逻辑** VChart 提供了两种方式配置图表主题: -- 通过图表 spec 配置 -- 通过 ThemeManager 注册主题 +- 通过图表 `spec `配置 -## 主题配置的获取与优先级比较 (core/vchart.ts) +- 通过 `ThemeManager `注册主题 -这两种配置都可以通过配置一套 `ITheme` 类型的主题对象,但是这两种配置的优先级是什么呢?这在\_updateCurrentTheme 方法里处理了优先级问题: +## **主题配置的获取与优先级比较 (core/vchart.ts)** + +这两种配置都可以通过配置一套 `ITheme` 类型的主题对象,但是这两种配置的优先级是什么呢?这在 updateCurrentTheme 方法里处理了优先级问题: + + **注意**:严谨地说是三种主题来源: -> **注意**:严谨地说是三种主题来源: -> > - `currentTheme`:通过 `ThemeManager` 注册的全局默认主题 +> > - `optionTheme`:在 VChart 构造函数的 options 中传入的主题 +> > - `specTheme`:在图表规格(spec)中指定的主题 > > 它们的优先级从低到高依次是: -> `currentTheme` < `optionTheme` < `specTheme` +> +> - `currentTheme` < `optionTheme` < `specTheme` 在 `src/core/vchart.ts` 中有如下属性,获取到了用户配置的主题内容: - `_spec.theme`:用户在图表 spec 对象配置中指定的主题 + - `_currentThemeName`:通过 `VChart.ThemeManager.registerTheme` 注册的当前全局主题名称 -### 简析主题合并的逻辑 (util/theme/merge-theme.ts) +### **简析主题合并的逻辑 (util/theme/merge-theme.ts)** -#### mergeTheme 函数 +#### **mergeTheme 函数** ```typescript export function mergeTheme(target: Maybe, ...sources: Maybe[]): Maybe { @@ -34,9 +71,10 @@ export function mergeTheme(target: Maybe, ...sources: Maybe[]): ``` - 是合并主题的基础,一层简单的封装,简单地说是对象的属性覆盖 + - 表现结果是后出现的 `sources` 会覆盖前出现的 `theme` -**合并示例** +**示例** ```typescript const baseTheme = { color: 'blue', fontSize: 12 }; @@ -47,7 +85,45 @@ const finalTheme = mergeTheme({}, baseTheme, optionTheme, specTheme); // 结果:{ color: 'red', fontSize: 14 } ``` -#### processThemeByChartType 函数 +#### transformThemeToMerge 函数 + +```typescript +function transformThemeToMerge(theme?: Maybe): Maybe { + if (!theme) { + return theme; + } + // 将色板转化为标准形式 + const colorScheme = transformColorSchemeToMerge(theme.colorScheme); + + return Object.assign({}, theme, { + colorScheme, + token: theme.token ?? {}, + series: Object.assign({}, theme.series) + } as Partial); +} + +/** 将色板转化为标准形式 */ +export function transformColorSchemeToMerge(colorScheme?: Maybe): Maybe { + if (colorScheme) { + colorScheme = Object.keys(colorScheme).reduce((scheme, key) => { + const value = colorScheme[key]; + scheme[key] = transformColorSchemeToStandardStruct(value); + return scheme; + }, {} as IThemeColorScheme); + } + return colorScheme; +} +``` + +`transformThemeToMerge`总的作用是完成了对主题对象进行标准化和规范化处理,他解决了 + +- 颜色总是数组形式 + +- 始终存在 `token `和 `series `属性 + +确保无论用户传入的主题配置如何,都能转换成一个结构完整、一致且可预测的主题对象,为后续的主题合并和应用提供一个标准化的数据结构。 + +#### **processThemeByChartType 函数** ```typescript const processThemeByChartType = (type: string, theme: ITheme) => { @@ -58,19 +134,22 @@ const processThemeByChartType = (type: string, theme: ITheme) => { }; ``` -processThemeByChartType 是 VChart 主题系统中实现图表类型个性化的关键函数。它通过条件合并和 mergeTheme,实现了在保持全局主题一致性的同时,为不同图表类型提供定制化样式的能力。 +`processThemeByChartType `是 VChart 主题系统中实现图表类型个性化的关键函数。它通过条件合并和 `mergeTheme`,实现了在保持全局主题一致性的同时,为不同图表类型提供定制化样式的能力。 -### 字符串主题与对象主题的解析处理 +### **字符串主题与对象主题的解析处理** -用户配置主题时可以简单便捷的传入字符串主题,例如: +用户配置主题时可以简单便捷的传入字符串主题(通常是从第三方主题包中导出的主题),例如: ```typescript -const chart = new VChart({ - theme: 'light' -}); +import vScreenVolcanoBlue from '@visactor/vchart-theme/public/vScreenVolcanoBlue.json'; +import VChart from '@visactor/vchart'; + +VChart.ThemeManager.registerTheme('vScreenVolcanoBlue', vScreenVolcanoBlue); + +VChart.ThemeManager.setCurrentTheme('vScreenVolcanoBlue'); ``` -也可以传入详细配置的对象主题,例如: +也可以传入详细配置的自定义主题,例如: ```typescript const chart = new VChart({ @@ -86,21 +165,25 @@ const chart = new VChart({ }); ``` -在源码里针对两者的处理的核心,在\_updateCurrentTheme 里判断类型,并通过 getThemeObject()做转化,统一处理成对象主题来解析的,这是个简单的逻辑,却为 VChart 的配置提供了灵活性和便捷性。 +在源码里针对两者的处理的核心,在\\\_updateCurrentTheme 里判断类型,并通过 `getThemeObject()`做转化,统一处理成对象主题来解析的,这是个简单的逻辑,却为 VChart 的配置提供了灵活性和便捷性。 最终,经过层层关于优先级比较,表格类型的合并(`processThemeByChartType`),主题的合并处理逻辑,最终得到挂载在 VChart 对象里的 `currentTheme` 属性。 -## 主题配置的预处理 +## **主题配置的预处理** -当主题配置,合并后,会进入预处理阶段。主题预处理是 VChart 主题系统的关键步骤,主要完成以下工作: +当主题配置,合并后,会进入预处理阶段。主题预处理是 VChart 主题系统的关键步骤,将抽象的主题描述转换为具体的样式配置,为开发者提供直观的配置能力。 + +主要完成以下工作: 1. 语义化颜色转换 + - 将形如 `{ color: 'brand.primary' }` 的颜色语义转换为具体颜色值 + 2. Token 替换 + - 将形如 `{ fontSize: 'size.m' }` 的 token 语义转换为具体字号 -3. 递归处理嵌套对象 -将抽象的主题描述转换为具体的样式配置,为开发者提供直观的配置能力 +3. 递归处理嵌套对象 **预处理流程**: @@ -108,7 +191,7 @@ const chart = new VChart({ this._currentTheme = preprocessTheme(processThemeByChartType(chartType, finalTheme)); ``` -## 主题的预处理与解析 +## **主题的预处理与解析** ```typescript export function preprocessTheme( @@ -119,9 +202,10 @@ export function preprocessTheme( ); ``` -这里涉及了 VChart 主题配置的三个重要概念: +这里涉及了 VChart 主题配置的重要概念: - `colorScheme`: 颜色方案 + - `tokenMap`: 标记映射 ```typescript @@ -176,7 +260,7 @@ Object.keys(obj).forEach(key => { 接下来分析具体的对于颜色语义和 token 语义的处理与解析 -#### getActualColor 颜色语义化 +#### **getActualColor 颜色语义化** ```typescript /** 查询语义化颜色 */ @@ -251,40 +335,68 @@ export function getColorSchemeBySeries( 值得一提的是,此外函数还提供了两种高级颜色处理能力,根据 `colorKey` 中 `l` 或 `a` 的属性来动态处理颜色特性: -1. **通过 HSL 色彩空间转换实现颜色亮度的动态调整** +1\. **通过 HSL 色彩空间转换实现颜色亮度的动态调整** - - **算法原理** + **算法原理** - > - 色彩空间转换:RGB → HSL → RGB + 色彩空间转换:RGB → HSL → RGB - - **HSL 亮度调整核心代码** + **HSL 亮度调整核心代码** - ```javascript +```typescript if (isValid(colorKey.l)) { const { r, g, b } = c.color; const { h, s } = rgbToHsl(r, g, b); - - // 关键步骤:保持色相和饱和度,仅调整亮度 const rgb = hslToRgb(h, s, colorKey.l); - - const newColor = new Color(`rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`); + const newColor = new Color(rgb(${rgb.r}, ${rgb.g}, ${rgb.b})); newColor.setOpacity(c.color.opacity); c = newColor; } - ``` +``` + +简单来说,就是在保持颜色原有色调(H)和饱和度(S)的情况下,仅调整颜色的明暗程度(L)。有关 hsl 和 rgb 格式的转换算法不是主题解析的重点,就简单提一下: + +> RGB 转 HSL 算法: +> +> 1. 将 RGB 值归一化到 \[0,1] +> +> 2. 找出 R、G、B 中的最大值和最小值 +> +> 3. 计算亮度 L = (max + min) / 2 +> +> 4. 计算饱和度 S +> +> - 如果 max == min,S = 0 +> +> - 否则 S = (max - min) / (1 - |2L - 1|) +> +> 5. 计算色相 H +> +> - 根据哪个颜色分量最大,用不同公式计算 +> +> - 范围 0-360 度 +> +> HSL 转 RGB 算法: +> +> 1. 将 H 分成 6 个区间 +> +> 2. 根据 S 和 L 计算中间变量 +> +> 3. 通过不同公式计算 R、G、B 值 +> +> 4. 将结果映射到 \[0,255] -todo: rgbToHsl hslToRgb 的分析 +2\. **设置颜色的透明度** -2. **直接设置颜色的透明度** + **透明度调整核心代码** -- **透明度调整核心代码** - ```javascript - if (isValid(colorKey.a)) { - c.setOpacity(colorKey.a); - } - ``` +```javascript +if (isValid(colorKey.a)) { + c.setOpacity(colorKey.a); +} +``` -#### queryToken Token 语义化 +#### **queryToken Token 语义化** ```typescript export function queryToken(tokenMap: TokenMap, tokenKey: ITokenKey): T | undefined { @@ -296,3 +408,9 @@ export function queryToken(tokenMap: TokenMap, tokenKey: ITokenKey): T | u ``` 这个函数用于根据 tokenMap 和 tokenKey 查询对应的 token 值,如果 tokenMap 中存在对应的 token,就返回对应的值,否则返回默认值。 + +--- + +# 本文档由以下人员提供 + +吨吨(https://github.com/Shabi-x)