Skip to content

Commit

Permalink
docs: complete theme config soucecode explanation
Browse files Browse the repository at this point in the history
  • Loading branch information
dundun003 committed Jan 16, 2025
1 parent cae081f commit 8e050d7
Showing 1 changed file with 164 additions and 46 deletions.
210 changes: 164 additions & 46 deletions docs/assets/contributing/zh/sourcesode/11-1 Theme explanation.md
Original file line number Diff line number Diff line change
@@ -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<ITheme>, ...sources: Maybe<ITheme>[]): Maybe<ITheme> {
Expand All @@ -34,9 +71,10 @@ export function mergeTheme(target: Maybe<ITheme>, ...sources: Maybe<ITheme>[]):
```

- 是合并主题的基础,一层简单的封装,简单地说是对象的属性覆盖

- 表现结果是后出现的 `sources` 会覆盖前出现的 `theme`

**合并示例**
**示例**

```typescript
const baseTheme = { color: 'blue', fontSize: 12 };
Expand All @@ -47,7 +85,45 @@ const finalTheme = mergeTheme({}, baseTheme, optionTheme, specTheme);
// 结果:{ color: 'red', fontSize: 14 }
```

#### processThemeByChartType 函数
#### transformThemeToMerge 函数

```typescript
function transformThemeToMerge(theme?: Maybe<ITheme>): Maybe<ITheme> {
if (!theme) {
return theme;
}
// 将色板转化为标准形式
const colorScheme = transformColorSchemeToMerge(theme.colorScheme);

return Object.assign({}, theme, {
colorScheme,
token: theme.token ?? {},
series: Object.assign({}, theme.series)
} as Partial<ITheme>);
}

/** 将色板转化为标准形式 */
export function transformColorSchemeToMerge(colorScheme?: Maybe<IThemeColorScheme>): Maybe<IThemeColorScheme> {
if (colorScheme) {
colorScheme = Object.keys(colorScheme).reduce<IThemeColorScheme>((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) => {
Expand All @@ -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({
Expand All @@ -86,29 +165,33 @@ 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. 递归处理嵌套对象

**预处理流程**

```typescript
this._currentTheme = preprocessTheme(processThemeByChartType(chartType, finalTheme));
```

## 主题的预处理与解析
## **主题的预处理与解析**

```typescript
export function preprocessTheme(
Expand All @@ -119,9 +202,10 @@ export function preprocessTheme(
);
```

这里涉及了 VChart 主题配置的三个重要概念
这里涉及了 VChart 主题配置的重要概念

- `colorScheme`: 颜色方案

- `tokenMap`: 标记映射

```typescript
Expand Down Expand Up @@ -176,7 +260,7 @@ Object.keys(obj).forEach(key => {

接下来分析具体的对于颜色语义和 token 语义的处理与解析

#### getActualColor 颜色语义化
#### **getActualColor 颜色语义化**

```typescript
/** 查询语义化颜色 */
Expand Down Expand Up @@ -251,40 +335,68 @@ export function getColorSchemeBySeries(

值得一提的是,此外函数还提供了两种高级颜色处理能力,根据 `colorKey``l``a` 的属性来动态处理颜色特性:

1. **通过 HSL 色彩空间转换实现颜色亮度的动态调整**
1\. **通过 HSL 色彩空间转换实现颜色亮度的动态调整**

- **算法原理**
&#x20; **算法原理**

> - 色彩空间转换:RGB → HSL → RGB
&#x20; 色彩空间转换:RGB → HSL → RGB

- **HSL 亮度调整核心代码**
&#x20; **HSL 亮度调整核心代码** &#x20;

```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. **直接设置颜色的透明度**
&#x20;**透明度调整核心代码**

- **透明度调整核心代码**
```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<T>(tokenMap: TokenMap, tokenKey: ITokenKey<T>): T | undefined {
Expand All @@ -296,3 +408,9 @@ export function queryToken<T>(tokenMap: TokenMap, tokenKey: ITokenKey<T>): T | u
```

这个函数用于根据 tokenMap 和 tokenKey 查询对应的 token 值,如果 tokenMap 中存在对应的 token,就返回对应的值,否则返回默认值。

---

# 本文档由以下人员提供

吨吨(https://github.com/Shabi-x)

0 comments on commit 8e050d7

Please sign in to comment.