diff --git a/packages/vchart/__tests__/unit/chart/bar.test.ts b/packages/vchart/__tests__/unit/chart/bar.test.ts index 71bfd468a1..725377ad20 100644 --- a/packages/vchart/__tests__/unit/chart/bar.test.ts +++ b/packages/vchart/__tests__/unit/chart/bar.test.ts @@ -4,8 +4,6 @@ import { GlobalScale } from '../../../src/scale/global-scale'; import { EventDispatcher } from '../../../src/event/event-dispatcher'; import type { BarSeries, IChartSpec } from '../../../src'; // eslint-disable-next-line no-duplicate-imports -import { ThemeManager } from '../../../src'; -// eslint-disable-next-line no-duplicate-imports import { BarChart } from '../../../src'; import { DataSet } from '@visactor/vdataset'; import { createCanvas, removeDom } from '../../util/dom'; @@ -117,8 +115,7 @@ describe('Bar chart test', () => { } } as any; }, - globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any), - getTheme: () => ThemeManager.getCurrentTheme() + globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any) } as any ); chart.created(); diff --git a/packages/vchart/__tests__/unit/chart/histogram.test.ts b/packages/vchart/__tests__/unit/chart/histogram.test.ts index 2ba6e61d28..69bbba2a87 100644 --- a/packages/vchart/__tests__/unit/chart/histogram.test.ts +++ b/packages/vchart/__tests__/unit/chart/histogram.test.ts @@ -1,8 +1,6 @@ import { EventDispatcher } from '../../../src/event/event-dispatcher'; import type { BarSeries } from '../../../src'; // eslint-disable-next-line no-duplicate-imports -import { ThemeManager } from '../../../src'; -// eslint-disable-next-line no-duplicate-imports import { HistogramChart } from '../../../src'; import { DataSet, DataView, csvParser, dataViewParser } from '@visactor/vdataset'; import { createCanvas, removeDom } from '../../util/dom'; @@ -63,8 +61,7 @@ describe('histogram chart test', () => { container: null, mode: 'desktop-browser', getCompiler: getTestCompiler, - globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any), - getTheme: () => ThemeManager.getCurrentTheme() + globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any) } as any); chart.created(); chart.init(); diff --git a/packages/vchart/__tests__/unit/chart/line.test.ts b/packages/vchart/__tests__/unit/chart/line.test.ts index 19f1bedcd0..a1ff69212e 100644 --- a/packages/vchart/__tests__/unit/chart/line.test.ts +++ b/packages/vchart/__tests__/unit/chart/line.test.ts @@ -1,15 +1,14 @@ import type { ILineChartSpec } from '../../../src/chart/line/interface'; import { GlobalScale } from '../../../src/scale/global-scale'; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-nocheck import type { LineSeries } from '../../../src/series/line/line'; import { DataSet, DataView, csvParser } from '@visactor/vdataset'; import { LineChart } from '../../../src/chart/line/line'; import { EventDispatcher } from '../../../src/event/event-dispatcher'; import { getTestCompiler } from '../../util/factory/compiler'; -import { ThemeManager } from '../../../src'; import { initChartDataSet } from '../../util/context'; +import VChart from '../../../src'; +const VChartClass = VChart; // 确保引用 vchart 以确保注册所需的图表 const dataSet = new DataSet(); initChartDataSet(dataSet); dataSet.registerParser('csv', csvParser); @@ -48,7 +47,6 @@ describe('line chart test', () => { mode: 'desktop-browser', getCompiler: getTestCompiler, globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any), - getTheme: () => ThemeManager.getCurrentTheme(), onError: () => {} } as any); chart.created(); @@ -120,8 +118,7 @@ describe('line chart test', () => { container: null, mode: 'mobile-browser', getCompiler: getTestCompiler, - globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any), - getTheme: () => ThemeManager.getCurrentTheme() + globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any) } as any ); chart.created(); diff --git a/packages/vchart/__tests__/unit/chart/linearProgress.test.ts b/packages/vchart/__tests__/unit/chart/linearProgress.test.ts index c2a2a0a6c5..563d9fd1ad 100644 --- a/packages/vchart/__tests__/unit/chart/linearProgress.test.ts +++ b/packages/vchart/__tests__/unit/chart/linearProgress.test.ts @@ -1,9 +1,7 @@ -import { DataSet, csvParser, dataViewParser } from '@visactor/vdataset'; +import { DataSet, csvParser } from '@visactor/vdataset'; import { EventDispatcher } from '../../../src/event/event-dispatcher'; import type { LinearProgressSeries } from '../../../src'; // eslint-disable-next-line no-duplicate-imports -import { ThemeManager } from '../../../src'; -// eslint-disable-next-line no-duplicate-imports import { LinearProgressChart } from '../../../src'; import { getTestCompiler } from '../../util/factory/compiler'; import { GlobalScale } from '../../../src/scale/global-scale'; @@ -52,7 +50,6 @@ describe('linearProgress chart test', () => { mode: 'desktop-browser', getCompiler: getTestCompiler, globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any), - getTheme: () => ThemeManager.getCurrentTheme(), animation: false } as any); chart.created(); diff --git a/packages/vchart/__tests__/unit/chart/rangeColumn.test.ts b/packages/vchart/__tests__/unit/chart/rangeColumn.test.ts index 896ee77d56..7f7e5289c5 100644 --- a/packages/vchart/__tests__/unit/chart/rangeColumn.test.ts +++ b/packages/vchart/__tests__/unit/chart/rangeColumn.test.ts @@ -1,5 +1,5 @@ -import { DataSet, DataView, csvParser, dataViewParser } from '@visactor/vdataset'; -import { RangeColumnChart, ThemeManager } from '../../../src'; +import { DataSet, DataView, csvParser } from '@visactor/vdataset'; +import { RangeColumnChart } from '../../../src'; import type { RangeColumnSeries } from '../../../src/series/range-column/range-column'; import { EventDispatcher } from '../../../src/event/event-dispatcher'; import { getTestCompiler } from '../../util/factory/compiler'; @@ -63,7 +63,6 @@ describe('rangeColumn chart test', () => { mode: 'desktop-browser', getCompiler: getTestCompiler, globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any), - getTheme: () => ThemeManager.getCurrentTheme(), animation: false } as any); chart.created(); diff --git a/packages/vchart/__tests__/unit/chart/treemap.test.ts b/packages/vchart/__tests__/unit/chart/treemap.test.ts index 9704ae4f38..2606154e0b 100644 --- a/packages/vchart/__tests__/unit/chart/treemap.test.ts +++ b/packages/vchart/__tests__/unit/chart/treemap.test.ts @@ -2,8 +2,8 @@ import { GlobalScale } from '../../../src/scale/global-scale'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-nocheck import { EventDispatcher } from '../../../src/event/event-dispatcher'; -import { ThemeManager, default as VChart } from '../../../src'; -import { DataSet, csvParser, dataViewParser } from '@visactor/vdataset'; +import { default as VChart } from '../../../src'; +import { DataSet, csvParser } from '@visactor/vdataset'; import { createCanvas, removeDom } from '../../util/dom'; import { getTestCompiler } from '../../util/factory/compiler'; import { disk } from '../../data/disk'; @@ -66,7 +66,6 @@ describe('treemap chart test', () => { mode: 'desktop-browser', getCompiler: getTestCompiler, globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any), - getTheme: () => ThemeManager.getCurrentTheme(), onError: () => {} } as any); chart.created(); diff --git a/packages/vchart/__tests__/unit/chart/word-cloud.test.ts b/packages/vchart/__tests__/unit/chart/word-cloud.test.ts index 2af11d5790..a259308a73 100644 --- a/packages/vchart/__tests__/unit/chart/word-cloud.test.ts +++ b/packages/vchart/__tests__/unit/chart/word-cloud.test.ts @@ -4,8 +4,8 @@ import { GlobalScale } from '../../../src/scale/global-scale'; import { EventDispatcher } from '../../../src/event/event-dispatcher'; import type { IWordCloudChartSpec } from '../../../src'; // eslint-disable-next-line no-duplicate-imports -import { ThemeManager, default as VChart } from '../../../src'; -import { DataSet, csvParser, dataViewParser } from '@visactor/vdataset'; +import { default as VChart } from '../../../src'; +import { DataSet, csvParser } from '@visactor/vdataset'; import { WordCloudChart } from '../../../src/chart/word-cloud/word-cloud'; import type { WordCloudSeries } from '../../../src/series/word-cloud/word-cloud'; import { dataWordCloud } from '../../data/data-wordcloud'; @@ -73,8 +73,7 @@ describe('wordCloud chart test', () => { container: null, mode: 'desktop-browser', getCompiler: getTestCompiler, - globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any), - getTheme: () => ThemeManager.getCurrentTheme() + globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any) } as any); chart.created(); chart.init(); diff --git a/packages/vchart/__tests__/unit/component/cartesian/axis/linear-axis.test.ts b/packages/vchart/__tests__/unit/component/cartesian/axis/linear-axis.test.ts index e4ab243da1..6971b70800 100644 --- a/packages/vchart/__tests__/unit/component/cartesian/axis/linear-axis.test.ts +++ b/packages/vchart/__tests__/unit/component/cartesian/axis/linear-axis.test.ts @@ -2,14 +2,13 @@ import { GlobalScale } from '../../../../../src/scale/global-scale'; import type { IEventDispatcher } from '../../../../../src/event/interface'; /* eslint-disable no-lone-blocks */ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import { DataSet, csvParser, dataViewParser } from '@visactor/vdataset'; +import { DataSet, csvParser } from '@visactor/vdataset'; import { dimensionStatistics } from '../../../../../src/data/transforms/dimension-statistics'; import type { CartesianLinearAxis } from '../../../../../src/index'; // eslint-disable-next-line no-duplicate-imports import { CartesianAxis } from '../../../../../src/index'; import type { IComponent, IComponentOption } from '../../../../../src/component/interface'; import { EventDispatcher } from '../../../../../src/event/event-dispatcher'; -import { lightTheme } from '../../../../../src/theme/builtin/light'; import { getTestCompiler } from '../../../../util/factory/compiler'; import { initChartDataSet } from '../../../../util/context'; @@ -90,7 +89,6 @@ const ctx: IComponentOption = { getChartViewRect: () => { return { width: 500, height: 500 } as any; }, - getTheme: () => lightTheme, globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any), getComponentByUserId: function (user_id: string | number): IComponent | undefined { throw new Error('Function not implemented.'); diff --git a/packages/vchart/__tests__/unit/data/fields.test.ts b/packages/vchart/__tests__/unit/data/fields.test.ts index bc5e68dd9f..4504fcc908 100644 --- a/packages/vchart/__tests__/unit/data/fields.test.ts +++ b/packages/vchart/__tests__/unit/data/fields.test.ts @@ -1,10 +1,9 @@ import { LineChart } from '../../../src/chart/line/line'; -import { DataSet, dataViewParser } from '@visactor/vdataset'; +import { DataSet } from '@visactor/vdataset'; import { EventDispatcher } from '../../../src/event/event-dispatcher'; import * as bt from '../../../src/vchart-all'; import { getTestCompiler } from '../../util/factory/compiler'; import { GlobalScale } from '../../../src/scale/global-scale'; -import { ThemeManager } from '../../../src'; import { initChartDataSet } from '../../util/context'; bt; @@ -68,8 +67,7 @@ describe('data fields test', () => { container: null, mode: 'desktop-browser', getCompiler: getTestCompiler, - globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any), - getTheme: () => ThemeManager.getCurrentTheme() + globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any) } as any ); chart.created(); @@ -143,8 +141,7 @@ describe('data fields test', () => { container: null, mode: 'desktop-browser', getCompiler: getTestCompiler, - globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any), - getTheme: () => ThemeManager.getCurrentTheme() + globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any) } as any ); chart.created(); diff --git a/packages/vchart/__tests__/unit/morph/morph.test.ts b/packages/vchart/__tests__/unit/morph/morph.test.ts index fcd379269a..9bec7a4802 100644 --- a/packages/vchart/__tests__/unit/morph/morph.test.ts +++ b/packages/vchart/__tests__/unit/morph/morph.test.ts @@ -1,5 +1,5 @@ -import { BarChart, CommonChart, ThemeManager } from '../../../src'; -import { DataSet, dataViewParser, DataView, csvParser } from '@visactor/vdataset'; +import { BarChart, CommonChart } from '../../../src'; +import { DataSet, DataView, csvParser } from '@visactor/vdataset'; import { EventDispatcher } from '../../../src/event/event-dispatcher'; import { createCanvas, removeDom } from '../../util/dom'; import { getTestCompiler } from '../../util/factory/compiler'; @@ -126,7 +126,6 @@ describe('Bar chart test', () => { getCompiler: getTestCompiler, globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any), animation: true, - getTheme: () => ThemeManager.getCurrentTheme(), onError: () => {} } as any); scatterChart.created(); @@ -147,7 +146,6 @@ describe('Bar chart test', () => { getCompiler: getTestCompiler, globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any), animation: true, - getTheme: () => ThemeManager.getCurrentTheme(), onError: () => {} } as any); barChart.created(); @@ -184,7 +182,6 @@ describe('Bar chart test', () => { getCompiler: getTestCompiler, globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any), animation: true, - getTheme: () => ThemeManager.getCurrentTheme(), onError: () => {} } as any ); diff --git a/packages/vchart/__tests__/unit/scale/global-scale.test.ts b/packages/vchart/__tests__/unit/scale/global-scale.test.ts index b911370fd6..387d70f9a5 100644 --- a/packages/vchart/__tests__/unit/scale/global-scale.test.ts +++ b/packages/vchart/__tests__/unit/scale/global-scale.test.ts @@ -4,15 +4,14 @@ import { CommonChart } from '../../../src/chart/common/common'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-nocheck import { EventDispatcher } from '../../../src/event/event-dispatcher'; -import type { IChartSpec, ScatterSeries } from '../../../src'; -// eslint-disable-next-line no-duplicate-imports -import { ThemeManager } from '../../../src'; +import { VChart, type IChartSpec, type ScatterSeries } from '../../../src'; // eslint-disable-next-line no-duplicate-imports import { DataSet, dataViewParser, DataView } from '@visactor/vdataset'; import { createCanvas, removeDom } from '../../util/dom'; import type { IAttrs, VisualScaleType } from '../../../src/mark/interface'; import { dimensionStatistics } from '../../../src/data/transforms/dimension-statistics'; +const VChartClass = VChart; // 确保引用 vchart 以确保注册所需的图表 // 保证引入执行 Build-in let dataSet: DataSet; const data = [ @@ -315,8 +314,7 @@ describe('global-scale test', () => { }; } } as any; - }, - getTheme: () => ThemeManager.getCurrentTheme() + } } as any ); chart.created(); @@ -470,8 +468,7 @@ describe('global-scale test', () => { }; } } as any; - }, - getTheme: () => ThemeManager.getCurrentTheme() + } } as any ); chart.created(); diff --git a/packages/vchart/__tests__/unit/theme/line.test.ts b/packages/vchart/__tests__/unit/theme/line.test.ts index e3298799df..745675f806 100644 --- a/packages/vchart/__tests__/unit/theme/line.test.ts +++ b/packages/vchart/__tests__/unit/theme/line.test.ts @@ -1,4 +1,4 @@ -import { DataSet, DataView, csvParser, dataViewParser } from '@visactor/vdataset'; +import { DataSet, DataView, csvParser } from '@visactor/vdataset'; import { VChart } from '../../../src/vchart-all'; import type { ITheme } from '../../../src/theme'; // eslint-disable-next-line no-duplicate-imports @@ -137,8 +137,7 @@ describe('theme switch test', () => { await vchart.renderAsync(); // await vchart.setCurrentTheme('light'); // sepc - expect(vchart.getCurrentTheme().background).toBe('red'); - expect(vchart.getCurrentThemeName()).toBe('newTheme'); + expect(vchart.getCompiler().getVGrammarView().background()).toBe('red'); }); it('set theme in spec and theme is an object', async () => { @@ -179,7 +178,6 @@ describe('theme switch test', () => { await vchart.renderAsync(); // spec - expect(vchart.getCurrentTheme().background).toBe('red'); expect(vchart.getCompiler().getVGrammarView().background()).toBe('red'); expect(vchart.getCurrentThemeName()).toBe('light'); }); diff --git a/packages/vchart/__tests__/util/context.ts b/packages/vchart/__tests__/util/context.ts index 4f92b7a2bf..aba5d6eb4d 100644 --- a/packages/vchart/__tests__/util/context.ts +++ b/packages/vchart/__tests__/util/context.ts @@ -51,7 +51,6 @@ export function modelOption(opt: Partial = {}, chart?: TestChart): export function seriesOption(opt: Partial = {}, chart?: TestChart): ISeriesOption { const option = modelOption(opt) as ISeriesOption; option.globalScale = new GlobalScale([], chart as any); - option.getTheme = () => ThemeManager.getCurrentTheme(); option.region = (chart?.getAllRegions?.()?.[0] ?? new TestRegion({})) as IRegion; option.onError = msg => { console.log(msg); @@ -65,7 +64,6 @@ export function seriesOption(opt: Partial = {}, chart?: TestChart) export function componentOption(opt: Partial = {}, chart: TestChart): IComponentOption { const option = modelOption(opt) as IComponentOption; - option.getTheme = () => ThemeManager.getCurrentTheme(); // 区域 option.getRegionsInIndex = chart.getRegionsInIndex.bind(chart); option.getRegionsInIds = chart.getRegionsInIds.bind(chart); diff --git a/packages/vchart/src/chart/base-chart.ts b/packages/vchart/src/chart/base-chart.ts index 99cc65fec2..3fb1e1426d 100644 --- a/packages/vchart/src/chart/base-chart.ts +++ b/packages/vchart/src/chart/base-chart.ts @@ -58,7 +58,6 @@ import { import { Stack } from './stack'; import { BaseModel } from '../model/base-model'; import { BaseMark } from '../mark/base/base-mark'; -import type { ITheme } from '../theme/interface'; import { DEFAULT_CHART_WIDTH, DEFAULT_CHART_HEIGHT } from '../constant/base'; import type { IParserOptions } from '@visactor/vdataset/es/parser'; import type { IBoundsLike } from '@visactor/vutils'; @@ -83,6 +82,8 @@ import type { IRectMark } from '../mark/rect'; import { calculateChartSize, mergeUpdateResult } from './util'; import { isDiscrete } from '@visactor/vscale'; import { updateDataViewInData } from '../data/initialize'; +import { getThemeFromOption } from '../theme/util'; +import { defaultChartLevelTheme } from '../theme'; export class BaseChart extends CompilableBase implements IChart { readonly type: string = 'chart'; @@ -104,9 +105,6 @@ export class BaseChart extends CompilableBase implements IChart { return this._option; } - // 主题 - protected _theme: ITheme; - protected _regions: IRegion[] = []; // 系列 protected _series: ISeries[] = []; @@ -193,8 +191,7 @@ export class BaseChart extends CompilableBase implements IChart { constructor(spec: any, option: IChartOption) { super(option); - this._theme = option.getTheme(); - this._paddingSpec = normalizeLayoutPaddingSpec(spec.padding ?? this._theme?.padding); + this._paddingSpec = normalizeLayoutPaddingSpec(spec.padding ?? getThemeFromOption('padding', this._option)); this._event = new Event(option.eventDispatcher, option.mode); this._dataSet = option.dataSet; @@ -359,7 +356,6 @@ export class BaseChart extends CompilableBase implements IChart { region, specIndex: index, specKey: 'series', - getTheme: () => this._theme, globalScale: this._globalScale, getSeriesData: this._chartData.getSeriesData.bind(this._chartData), sourceDataList: this._chartData.dataList @@ -390,7 +386,6 @@ export class BaseChart extends CompilableBase implements IChart { getRegionsInIndex: this.getRegionsInIndex, getRegionsInIds: this.getRegionsInIds, getRegionsInUserIdOrIndex: this.getRegionsInUserIdOrIndex, - getTheme: () => this._theme, getAllSeries: this.getAllSeries, getSeriesInIndex: this.getSeriesInIndex, getSeriesInIds: this.getSeriesInIds, @@ -490,13 +485,13 @@ export class BaseChart extends CompilableBase implements IChart { if (this._spec.zField || (this._spec.series && this._spec.series.some((s: any) => s.zField))) { use3dLayout = true; } - const layout = new (Factory.getLayoutInKey(this._spec.layout?.type ?? (use3dLayout ? 'layout3d' : 'base')))( - this._spec.layout, - { + const constructor = Factory.getLayoutInKey(this._spec.layout?.type ?? (use3dLayout ? 'layout3d' : 'base')); + if (constructor) { + const layout = new constructor(this._spec.layout, { onError: this._option?.onError - } - ); - this._layoutFunc = layout.layoutItems.bind(layout); + }); + this._layoutFunc = layout.layoutItems.bind(layout); + } } } @@ -742,6 +737,7 @@ export class BaseChart extends CompilableBase implements IChart { private _transformSpecScale() { const scales: IChartSpec['scales'] = this._spec.scales ?? []; let colorScaleSpec: IVisualSpecScale = scales.find(s => s.id === 'color'); + const colorScheme = this.getColorScheme(); if (!colorScaleSpec) { colorScaleSpec = { type: 'ordinal', @@ -755,11 +751,11 @@ export class BaseChart extends CompilableBase implements IChart { // range array if (isArray(colorSpec)) { - colorScaleSpec.range = colorSpec.map(color => getActualColor(color, this._theme?.colorScheme)); + colorScaleSpec.range = colorSpec.map(color => getActualColor(color, colorScheme)); } else { const tempSpec = colorSpec as IVisualSpecScale; if (tempSpec.range) { - tempSpec.range = tempSpec.range.map(color => getActualColor(color, this._theme?.colorScheme)); + tempSpec.range = tempSpec.range.map(color => getActualColor(color, colorScheme)); } Object.prototype.hasOwnProperty.call(tempSpec, 'type') && (colorScaleSpec.type = tempSpec.type); Object.prototype.hasOwnProperty.call(tempSpec, 'domain') && (colorScaleSpec.domain = tempSpec.domain); @@ -772,7 +768,7 @@ export class BaseChart extends CompilableBase implements IChart { // 如果没有range设置 // length === 0 就认为是没有配置,用户配置 color: [] 依然认为是无效配置,启用主题色板 if (!colorScaleSpec.range?.length) { - colorScaleSpec.range = getDataScheme(this._theme?.colorScheme); + colorScaleSpec.range = getDataScheme(colorScheme); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore colorScaleSpec.rangeTheme = true; @@ -807,10 +803,11 @@ export class BaseChart extends CompilableBase implements IChart { updateGlobalScaleTheme() { const colorSpec = this._globalScale.getScaleSpec('color'); + const colorScheme = this.getColorScheme(); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore if (colorSpec.rangeTheme) { - colorSpec.range = getDataScheme(this._theme?.colorScheme); + colorSpec.range = getDataScheme(colorScheme); this._globalScale.getScale('color').range(colorSpec.range); } } @@ -877,7 +874,7 @@ export class BaseChart extends CompilableBase implements IChart { updateChartConfig(result: IUpdateSpecResult, oldSpec: IChartSpec) { // padding; - this._paddingSpec = normalizeLayoutPaddingSpec(this._spec.padding ?? this._theme?.padding); + this._paddingSpec = normalizeLayoutPaddingSpec(this._spec.padding ?? getThemeFromOption('padding', this._option)); // re compute padding & layout this._updateLayoutRect(this._viewBox); @@ -973,7 +970,7 @@ export class BaseChart extends CompilableBase implements IChart { seriesStyle: spec.seriesStyle, animation: spec.animation, - animationThreshold: spec.animationThreshold ?? this._theme.animationThreshold, + animationThreshold: spec.animationThreshold ?? getThemeFromOption('animationThreshold', this._option), animationAppear: spec.animationAppear, animationDisappear: spec.animationDisappear, animationEnter: spec.animationEnter, @@ -1020,15 +1017,8 @@ export class BaseChart extends CompilableBase implements IChart { this._event.emit(ChartEvent.layoutRectUpdate, {}); } - /** 获取当前全局主题 */ - getCurrentTheme() { - return this._theme; - } - /** 设置当前全局主题 */ - setCurrentTheme(theme: ITheme, reInit: boolean = true) { - this._theme = theme; - + setCurrentTheme(reInit: boolean = true) { // 需要重新布局 this.setLayoutTag(true); @@ -1038,25 +1028,25 @@ export class BaseChart extends CompilableBase implements IChart { this.updateGlobalScaleTheme(); this.setRegionTheme(reInit); - this.setComponentTheme(theme, reInit); - this.setSeriesTheme(theme, reInit); + this.setComponentTheme(reInit); + this.setSeriesTheme(reInit); } protected setRegionTheme(reInit: boolean = true) { - this._regions.forEach(r => { - reInit ? r.reInit() : r.setTheme(); + this._regions.forEach(async r => { + await r.setCurrentTheme(reInit); }); } - protected setComponentTheme(theme: ITheme, reInit: boolean = true) { - this._components.forEach(c => { - reInit ? c.setCurrentTheme(theme.component[c.type], true) : c.setTheme(theme.component[c.type]); + protected setComponentTheme(reInit: boolean = true) { + this._components.forEach(async c => { + await c.setCurrentTheme(reInit); }); } - protected setSeriesTheme(theme: ITheme, reInit: boolean = true) { + protected setSeriesTheme(reInit: boolean = true) { this._series.forEach(async s => { - reInit ? await s.setCurrentTheme(theme.series[s.type], true) : s.setTheme(theme.series[s.type]); + await s.setCurrentTheme(reInit); }); } @@ -1410,4 +1400,8 @@ export class BaseChart extends CompilableBase implements IChart { } } } + + getColorScheme() { + return (this._option.getThemeConfig?.().chartLevelTheme ?? defaultChartLevelTheme).colorScheme; + } } diff --git a/packages/vchart/src/chart/interface/chart.ts b/packages/vchart/src/chart/interface/chart.ts index b58ca898f9..d1c7189d8f 100644 --- a/packages/vchart/src/chart/interface/chart.ts +++ b/packages/vchart/src/chart/interface/chart.ts @@ -28,6 +28,7 @@ import type { IDataValues } from '../../typings'; import type { DataView } from '@visactor/vdataset'; +import type { IThemeColorScheme } from '../../theme/color-scheme/interface'; export type DimensionIndexOption = { filter?: (cmp: IComponent) => boolean; @@ -156,9 +157,8 @@ export interface IChart extends ICompilable { // 获取实际渲染的 canvas getCanvas: () => HTMLCanvasElement | undefined; - getCurrentTheme: () => ITheme; - - setCurrentTheme: (theme: ITheme, reInit?: boolean) => void; + setCurrentTheme: (reInit?: boolean) => void; + getColorScheme: () => IThemeColorScheme | undefined; getSeriesData: (id: StringOrNumber | undefined, index: number | undefined) => DataView | undefined; // setDimensionIndex diff --git a/packages/vchart/src/chart/sequence/sequence.ts b/packages/vchart/src/chart/sequence/sequence.ts index 3995329e1d..3934ffdf3f 100644 --- a/packages/vchart/src/chart/sequence/sequence.ts +++ b/packages/vchart/src/chart/sequence/sequence.ts @@ -321,7 +321,6 @@ export class SequenceChart extends BaseChart { region, specIndex: index, specKey: 'series', - getTheme: () => this._theme, globalScale: this._globalScale } as ISeriesOption); diff --git a/packages/vchart/src/component/axis/util.ts b/packages/vchart/src/component/axis/util.ts index 4ebbfe82e6..120e05c7f3 100644 --- a/packages/vchart/src/component/axis/util.ts +++ b/packages/vchart/src/component/axis/util.ts @@ -1,10 +1,11 @@ -import type { ITheme } from '../../theme/interface'; import { get } from '@visactor/vutils'; import { mergeSpec } from '../../util'; import type { IOrientType, IPolarOrientType } from '../../typings'; import type { AxisType, ICommonAxisSpec, ILinearAxisSpec } from './interface'; import { transformComponentStyle } from '../../util/style'; import { isXAxis } from './cartesian/util'; +import type { IModelOption } from '../../model/interface'; +import { getComponentThemeFromOption } from '../util'; export const DEFAULT_TITLE_STYLE = { left: { @@ -64,17 +65,29 @@ export function isValidPolarAxis(spec: any) { return orient === 'angle' || orient === 'radius'; } -export const getCartesianAxisTheme = (orient: IOrientType, type: AxisType, theme: ITheme) => { - const { axisBand, axisLinear, axisX, axisY, axis } = theme.component ?? {}; +export const getCartesianAxisTheme = (orient: IOrientType, type: AxisType, option: Partial) => { const axisTypeTheme = - (type === 'band' ? axisBand : (['linear', 'log', 'symlog'] as AxisType[]).includes(type) ? axisLinear : {}) ?? {}; - const axisTheme = isXAxis(orient) ? axisX : axisY; - return mergeSpec({}, axis, axisTypeTheme, axisTheme); + (type === 'band' + ? getComponentThemeFromOption('axisBand', option) + : (['linear', 'log', 'symlog'] as AxisType[]).includes(type) + ? getComponentThemeFromOption('axisLinear', option) + : {}) ?? {}; + const axisTheme = isXAxis(orient) + ? getComponentThemeFromOption('axisX', option) + : getComponentThemeFromOption('axisY', option); + return mergeSpec({}, getComponentThemeFromOption('axis', option), axisTypeTheme, axisTheme); }; -export const getPolarAxisTheme = (orient: IPolarOrientType, type: AxisType, theme: ITheme) => { - const { axisBand, axisLinear, axisAngle, axisRadius, axis } = theme.component ?? {}; - const axisTypeTheme = (type === 'band' ? axisBand : type === 'linear' ? axisLinear : {}) ?? {}; - const axisTheme = orient === 'angle' ? axisAngle : axisRadius; - return mergeSpec({}, axis, axisTypeTheme, axisTheme); +export const getPolarAxisTheme = (orient: IPolarOrientType, type: AxisType, option: Partial) => { + const axisTypeTheme = + (type === 'band' + ? getComponentThemeFromOption('axisBand', option) + : type === 'linear' + ? getComponentThemeFromOption('axisLinear', option) + : {}) ?? {}; + const axisTheme = + orient === 'angle' + ? getComponentThemeFromOption('axisAngle', option) + : getComponentThemeFromOption('axisRadius', option); + return mergeSpec({}, getComponentThemeFromOption('axis', option), axisTypeTheme, axisTheme); }; diff --git a/packages/vchart/src/component/base/base-component.ts b/packages/vchart/src/component/base/base-component.ts index cddd487e89..45c744ea64 100644 --- a/packages/vchart/src/component/base/base-component.ts +++ b/packages/vchart/src/component/base/base-component.ts @@ -12,7 +12,7 @@ import { Event_Source_Type } from '../../constant'; import type { IAnimate } from '../../animation/interface'; import { AnimateManager } from '../../animation/animate-manager'; import type { Datum } from '../../typings'; -import { normalizeLayoutPaddingSpec } from '../../util'; +import { normalizeLayoutPaddingSpec, preprocessSpecOrTheme } from '../../util'; import type { IComponentSpec } from './interface'; export abstract class BaseComponent @@ -66,39 +66,12 @@ export abstract class BaseComponent }; } - async setCurrentTheme(theme: any, noRender?: boolean) { - const modifyConfig = () => { - // 重新初始化 - this.reInit(theme); - - return { change: true, reMake: false }; - }; - - if (noRender) { - modifyConfig(); - } else { - await this._option.globalInstance.updateCustomConfigAndRerender(modifyConfig); - } - } - - protected _initTheme(theme?: any) { - const globalTheme = this._option.getTheme(); - - if (theme) { - super._initTheme(theme); - } else { - super._initTheme( - getComponentThemeFromGlobalTheme( - this.type as ComponentTypeEnum, - globalTheme, - this._originalSpec, - this._option.getChart() - ) - ); - } - - // 将 theme merge 到 spec 中 - this._mergeThemeToSpec(); + protected _getTheme() { + return preprocessSpecOrTheme( + 'theme', + getComponentThemeFromGlobalTheme(this.type as ComponentTypeEnum, this._option, this._originalSpec), + this.getColorScheme() + ); } protected _mergeThemeToSpec() { diff --git a/packages/vchart/src/component/base/util.ts b/packages/vchart/src/component/base/util.ts index 67a0f6578c..bf3d19d8fd 100644 --- a/packages/vchart/src/component/base/util.ts +++ b/packages/vchart/src/component/base/util.ts @@ -1,44 +1,44 @@ -import type { IChart } from '../../chart/interface'; -import type { ITheme } from '../../theme'; +import type { IModelOption } from '../../model/interface'; +import { getThemeFromOption } from '../../theme/util'; import { getOrient } from '../axis/cartesian/util'; import { getCartesianAxisTheme, getPolarAxisTheme } from '../axis/util'; import { getCartesianCrosshairTheme, getPolarCrosshairTheme } from '../crosshair/util'; import { ComponentTypeEnum } from '../interface'; import { getLayout } from '../legend/util'; +import { getComponentThemeFromOption } from '../util'; export function getComponentThemeFromGlobalTheme( type: ComponentTypeEnum, - theme: ITheme, - componentSpec: any, - chart: IChart + option: Partial, + componentSpec: any ) { + const chart = option.getChart?.(); switch (type) { case ComponentTypeEnum.cartesianBandAxis: - return getCartesianAxisTheme(getOrient(componentSpec), 'band', theme); + return getCartesianAxisTheme(getOrient(componentSpec), 'band', option); case ComponentTypeEnum.cartesianLinearAxis: - return getCartesianAxisTheme(getOrient(componentSpec), 'linear', theme); + return getCartesianAxisTheme(getOrient(componentSpec), 'linear', option); case ComponentTypeEnum.cartesianLogAxis: - return getCartesianAxisTheme(getOrient(componentSpec), 'log', theme); + return getCartesianAxisTheme(getOrient(componentSpec), 'log', option); case ComponentTypeEnum.cartesianSymlogAxis: - return getCartesianAxisTheme(getOrient(componentSpec), 'symlog', theme); + return getCartesianAxisTheme(getOrient(componentSpec), 'symlog', option); case ComponentTypeEnum.cartesianAxis: case ComponentTypeEnum.cartesianTimeAxis: - return getCartesianAxisTheme(getOrient(componentSpec), undefined, theme); + return getCartesianAxisTheme(getOrient(componentSpec), undefined, option); case ComponentTypeEnum.polarBandAxis: - return getPolarAxisTheme(componentSpec.orient, 'band', theme); + return getPolarAxisTheme(componentSpec.orient, 'band', option); case ComponentTypeEnum.polarLinearAxis: - return getPolarAxisTheme(componentSpec.orient, 'linear', theme); + return getPolarAxisTheme(componentSpec.orient, 'linear', option); case ComponentTypeEnum.polarAxis: - return getPolarAxisTheme(componentSpec.orient, undefined, theme); + return getPolarAxisTheme(componentSpec.orient, undefined, option); case ComponentTypeEnum.cartesianCrosshair: - return getCartesianCrosshairTheme(theme, chart); + return getCartesianCrosshairTheme(option, chart); case ComponentTypeEnum.polarCrosshair: - return getPolarCrosshairTheme(theme, chart); + return getPolarCrosshairTheme(option, chart); case ComponentTypeEnum.colorLegend: - return theme.component?.colorLegend[getLayout(componentSpec)]; case ComponentTypeEnum.sizeLegend: - return theme.component?.sizeLegend[getLayout(componentSpec)]; + return getThemeFromOption(`component.${type}.${getLayout(componentSpec)}`, option); default: - return theme.component?.[type]; + return getComponentThemeFromOption(type, option); } } diff --git a/packages/vchart/src/component/crosshair/util.ts b/packages/vchart/src/component/crosshair/util.ts index 3bb25c092a..81f07e2459 100644 --- a/packages/vchart/src/component/crosshair/util.ts +++ b/packages/vchart/src/component/crosshair/util.ts @@ -2,12 +2,14 @@ import type { Tag } from '@visactor/vrender-components'; import type { IBoundsLike } from '@visactor/vutils'; import type { Datum, IOrientType } from '../../typings'; import type { IChart } from '../../chart/interface'; -import type { ITheme } from '../../theme'; import type { ICrosshairTheme } from './interface'; import { isValid, mergeSpec } from '../../util'; import { isXAxis, isYAxis } from '../axis/cartesian/util'; import { isDiscrete } from '@visactor/vscale'; import type { IAxis } from '../axis'; +import type { IModelOption } from '../../model/interface'; +import { getComponentThemeFromOption } from '../util'; +import { ComponentTypeEnum } from '../interface'; export function limitTagInBounds(shape: Tag, bounds: IBoundsLike) { const { x1: regionMinX, y1: regionMinY, x2: regionMaxX, y2: regionMaxY } = bounds; @@ -65,9 +67,10 @@ export function getDatumByValue(data: Datum[], value: number, startField: string return null; } -export const getCartesianCrosshairTheme = (theme: ITheme, chart: IChart): ICrosshairTheme => { +export const getCartesianCrosshairTheme = (option: Partial, chart: IChart): ICrosshairTheme => { const axes = chart.getAllComponents().filter(component => component.type.includes('Axis')) as IAxis[]; - const { bandField, linearField, xField, yField } = theme.component.crosshair ?? {}; + const { bandField, linearField, xField, yField } = + getComponentThemeFromOption(ComponentTypeEnum.crosshair, option) ?? {}; const xAxis = axes.find(axis => isXAxis(axis.getOrient() as IOrientType)); let newXField; @@ -91,9 +94,10 @@ export const getCartesianCrosshairTheme = (theme: ITheme, chart: IChart): ICross }; }; -export const getPolarCrosshairTheme = (theme: ITheme, chart: IChart): ICrosshairTheme => { +export const getPolarCrosshairTheme = (option: Partial, chart: IChart): ICrosshairTheme => { const axes = chart.getAllComponents().filter(component => component.type.includes('Axis')) as IAxis[]; - const { bandField, linearField, categoryField, valueField } = theme.component.crosshair ?? {}; + const { bandField, linearField, categoryField, valueField } = + getComponentThemeFromOption(ComponentTypeEnum.crosshair, option) ?? {}; const angleAxis = axes.find(axis => axis.getOrient() === 'angle'); let newAngleField; diff --git a/packages/vchart/src/component/tooltip/handler/base.ts b/packages/vchart/src/component/tooltip/handler/base.ts index 92c6a918d8..48c94b57b1 100644 --- a/packages/vchart/src/component/tooltip/handler/base.ts +++ b/packages/vchart/src/component/tooltip/handler/base.ts @@ -45,6 +45,8 @@ import type { IContainerSize, TooltipAttributes } from '@visactor/vrender-compon import { getTooltipAttributes } from './utils/attribute'; import type { DimensionEventParams } from '../../../event/events/dimension/interface'; import type { IChartOption } from '../../../chart/interface'; +import type { IChartLevelTheme } from '../../../core/interface'; +import { defaultChartLevelTheme } from '../../../theme'; type ChangeTooltipFunc = ( visible: boolean, @@ -669,8 +671,12 @@ export abstract class BaseTooltipHandler implements ITooltipHandler { // 计算 tooltip 内容区域的宽高,并缓存结果 protected _getTooltipBoxSize(actualTooltip: IToolTipActual, changePositionOnly: boolean): IContainerSize | undefined { if (!changePositionOnly || isNil(this._attributes)) { - const globalTheme = this._chartOption.getTheme?.(); - this._attributes = getTooltipAttributes(actualTooltip, this._component.getSpec(), globalTheme); + const { chartLevelTheme = defaultChartLevelTheme } = this._chartOption.getThemeConfig?.() ?? {}; + this._attributes = getTooltipAttributes( + actualTooltip, + this._component.getSpec(), + chartLevelTheme as IChartLevelTheme + ); } return { width: this._attributes?.panel?.width, diff --git a/packages/vchart/src/component/tooltip/handler/utils/attribute.ts b/packages/vchart/src/component/tooltip/handler/utils/attribute.ts index cf96cd95fb..59e626ae5d 100644 --- a/packages/vchart/src/component/tooltip/handler/utils/attribute.ts +++ b/packages/vchart/src/component/tooltip/handler/utils/attribute.ts @@ -1,5 +1,4 @@ import type { - IContainerSize, TooltipAttributes, TooltipPanelAttrs, TooltipRowAttrs, @@ -12,10 +11,10 @@ import type { ITooltipTextStyle } from '../interface'; import { isValid, normalizePadding } from '@visactor/vutils'; import { mergeSpec, normalizeLayoutPaddingSpec } from '../../../../util'; import type { ITooltipSpec } from '../../interface/spec'; -import type { ITheme } from '../../../../theme'; import type { ITooltipTextTheme, ITooltipTheme } from '../../interface/theme'; import { THEME_CONSTANTS } from '../../../../theme/builtin/common/constants'; import { measureTooltipText } from './common'; +import type { IChartLevelTheme } from '../../../../core/interface'; const DEFAULT_TEXT_ATTRIBUTES: Partial = { fontFamily: THEME_CONSTANTS.defaultFontFamily, @@ -25,7 +24,7 @@ const DEFAULT_TEXT_ATTRIBUTES: Partial = { export function getTextAttributes( style: ITooltipTextTheme = {}, - globalTheme?: ITheme, + globalTheme?: IChartLevelTheme, defaultAttributes?: Partial ): ITooltipTextStyle { const attrs: ITooltipTextStyle = { @@ -75,7 +74,7 @@ export const getPanelAttributes = (style: ITooltipTheme['panel']): TooltipPanelA export const getTooltipAttributes = ( actualTooltip: IToolTipActual, spec: ITooltipSpec, - globalTheme: ITheme + globalTheme: IChartLevelTheme ): TooltipAttributes => { const { style = {}, enterable, transitionDuration } = spec; const { panel = {}, titleLabel, shape, keyLabel, valueLabel, spaceRow: commonSpaceRow } = style; diff --git a/packages/vchart/src/component/util.ts b/packages/vchart/src/component/util.ts new file mode 100644 index 0000000000..4a4caa7d1f --- /dev/null +++ b/packages/vchart/src/component/util.ts @@ -0,0 +1,11 @@ +import type { IModelOption } from '../model/interface'; +import { getThemeFromOption } from '../theme/util'; +import type { ComponentTypeEnum } from './interface'; +import type { IComponentTheme } from './interface/theme'; + +export function getComponentThemeFromOption( + type: keyof IComponentTheme | ComponentTypeEnum, + option: Partial +) { + return getThemeFromOption(`component.${type}`, option); +} diff --git a/packages/vchart/src/core/interface.ts b/packages/vchart/src/core/interface.ts index 2133a3186e..b5a6c3d158 100644 --- a/packages/vchart/src/core/interface.ts +++ b/packages/vchart/src/core/interface.ts @@ -1,4 +1,4 @@ -import type { DataSet, DataView } from '@visactor/vdataset'; +import type { DataSet } from '@visactor/vdataset'; import type { IParserOptions } from '@visactor/vdataset/es/parser'; import type { @@ -23,7 +23,8 @@ import type { IComponent } from '../component/interface'; import type { LayoutCallBack } from '../layout/interface'; import type { Compiler } from '../compile/compiler'; import type { IChart } from '../chart/interface'; -import type { Stage } from '@visactor/vrender-core'; +import type { IGradientColor, Stage } from '@visactor/vrender-core'; +import type { IThemeColorScheme } from '../theme/color-scheme/interface'; export type DataLinkSeries = { /** @@ -195,7 +196,7 @@ export interface IVChart { ) => void; /** - * 获取当前主题,会返回完整的主题配置 + * 获取当前主题,会返回完整的主题配置(只能获取用户通过`setCurrentTheme`方法设置过的主题,默认值为`ThemeManager`统一设置的主题) * */ getCurrentTheme: () => ITheme; @@ -409,3 +410,13 @@ export interface IGlobalConfig { // TODO // autoRelease?: boolean; } + +/** 图表层级的主题 */ +export interface IChartLevelTheme { + /** 图表背景色 */ + background?: string | IGradientColor; + /** 图表字体配置 */ + fontFamily?: string; + /** 全局色板 */ + colorScheme?: IThemeColorScheme; +} diff --git a/packages/vchart/src/core/vchart.ts b/packages/vchart/src/core/vchart.ts index 62be040235..3d970b196b 100644 --- a/packages/vchart/src/core/vchart.ts +++ b/packages/vchart/src/core/vchart.ts @@ -22,7 +22,7 @@ import type { IParserOptions } from '@visactor/vdataset/es/parser'; import type { IFields, Transform } from '@visactor/vdataset'; // eslint-disable-next-line no-duplicate-imports import { DataSet, dataViewParser, DataView } from '@visactor/vdataset'; -import type { Stage } from '@visactor/vrender-core'; +import { globalTheme, type Stage } from '@visactor/vrender-core'; import { isString, isValid, @@ -36,7 +36,6 @@ import { specTransform, convertPoint, preprocessSpecOrTheme, - mergeTheme, getThemeObject, mergeSpecWithFilter } from '../util'; @@ -85,7 +84,7 @@ import { LoggerLevel, isEqual } from '@visactor/vutils'; -import type { DataLinkAxis, DataLinkSeries, IGlobalConfig, IVChart } from './interface'; +import type { DataLinkAxis, DataLinkSeries, IChartLevelTheme, IGlobalConfig, IVChart } from './interface'; import { InstanceManager } from './instance-manager'; import type { IAxis } from '../component/axis'; import { setPoptipTheme } from '@visactor/vrender-components'; @@ -96,6 +95,8 @@ import { GroupMark } from '../mark'; import { registerVGrammarAnimation } from '../animation/config'; import { View, registerFilterTransform, registerMapTransform } from '@visactor/vgrammar-core'; import { VCHART_UTILS } from './util'; +import type { IThemeColorScheme } from '../theme/color-scheme/interface'; +import { mergeThemeAndGet } from '../theme/util'; export class VChart implements IVChart { readonly id = createID(); @@ -247,7 +248,8 @@ export class VChart implements IVChart { private _observer: ResizeObserver = null; private _currentThemeName: string; - private _currentTheme: ITheme; + //private _currentTheme: ITheme; + private _currentChartLevelTheme: IChartLevelTheme = {}; private _onError?: (...args: any[]) => void; @@ -304,7 +306,7 @@ export class VChart implements IVChart { this._compiler.initView(); // 设置全局字体 this.getStage()?.setTheme({ - text: { fontFamily: this._currentTheme.fontFamily } + text: { fontFamily: this._currentChartLevelTheme.fontFamily } }); this._initDataSet(this._option.dataSet); this._autoSize = isTrueBrowser(mode) ? spec.autoFit ?? this._option.autoFit ?? true : false; @@ -345,7 +347,12 @@ export class VChart implements IVChart { performanceHook: this._option.performanceHook, viewBox: this._viewBox, animation: this._option.animation, - getTheme: () => this._currentTheme, + getThemeConfig: () => ({ + globalTheme: this._currentThemeName, + optionTheme: this._option.theme, + specTheme: this._spec?.theme, + chartLevelTheme: this._currentChartLevelTheme + }), layout: this._option.layout, onError: this._onError }); @@ -772,7 +779,7 @@ export class VChart implements IVChart { this._spec = spec; if (!isEqual(lastSpec.theme, spec.theme)) { this._updateCurrentTheme(); - this._chart?.setCurrentTheme(this._currentTheme, false); + this._chart?.setCurrentTheme(); } const reSize = this._updateChartConfiguration(lastSpec); this._compiler?.getVGrammarView()?.updateLayoutTag(); @@ -1062,39 +1069,28 @@ export class VChart implements IVChart { * @param nextThemeName 通过 setCurrentTheme 方法新设的主题 */ private _updateCurrentTheme(nextThemeName?: string) { - let optionTheme: Maybe = this._option.theme; - let specTheme: Maybe = this._spec?.theme; - - let finalTheme: ITheme; - if (ThemeManager.themeExist(nextThemeName)) { - // 优先级最低 - const newTheme = ThemeManager.getTheme(nextThemeName); - // 优先级适中 - optionTheme = !optionTheme || isString(optionTheme) ? {} : optionTheme; - // 优先级最高 - specTheme = !specTheme || isString(specTheme) ? {} : specTheme; - // 合并 - finalTheme = mergeTheme({}, newTheme, optionTheme, specTheme); + const optionTheme: Maybe = this._option.theme; + const specTheme: Maybe = this._spec?.theme; + + if (nextThemeName) { this._currentThemeName = nextThemeName; - } else { - if (isString(specTheme) && ThemeManager.themeExist(specTheme)) { - // 以 specTheme 为最底开始合并 - finalTheme = mergeTheme({}, ThemeManager.getTheme(specTheme)); - this._currentThemeName = specTheme; - } else if (isString(optionTheme) && ThemeManager.themeExist(optionTheme)) { - // 以 optionTheme 为最底开始合并 - finalTheme = mergeTheme({}, ThemeManager.getTheme(optionTheme), getThemeObject(specTheme)); - this._currentThemeName = optionTheme; - } else { - // 以 baseTheme 为最底开始合并 - const baseTheme = getThemeObject(this._currentThemeName); - finalTheme = mergeTheme({}, baseTheme, getThemeObject(optionTheme), getThemeObject(specTheme)); - } } - this._currentTheme = preprocessSpecOrTheme('theme', finalTheme, finalTheme.colorScheme); + const colorScheme = mergeThemeAndGet('colorScheme', this._currentThemeName, optionTheme, specTheme); + this._currentChartLevelTheme = { + colorScheme, + background: mergeThemeAndGet('background', this._currentThemeName, optionTheme, specTheme, colorScheme), + fontFamily: mergeThemeAndGet('fontFamily', this._currentThemeName, optionTheme, specTheme, colorScheme) + }; + // 设置 poptip 的主题 - setPoptipTheme(preprocessSpecOrTheme('mark-theme', mergeSpec({}, this._currentTheme.component?.poptip))); + setPoptipTheme( + preprocessSpecOrTheme( + 'mark-theme', + mergeThemeAndGet('component.poptip', this._currentThemeName, optionTheme, specTheme, colorScheme), + colorScheme + ) + ); // 设置背景色 this._compiler?.setBackground(this._getBackground()); } @@ -1116,14 +1112,14 @@ export class VChart implements IVChart { private _getBackground() { const specBackground = typeof this._spec.background === 'string' ? this._spec.background : null; // spec > spec.theme > initOptions.theme - return specBackground || (this._currentTheme.background as string) || this._option.background; + return specBackground || (this._currentChartLevelTheme.background as string) || this._option.background; } /** - * 获取当前主题,会返回完整的主题配置 + * 获取当前主题,会返回完整的主题配置(只能获取用户通过`setCurrentTheme`方法设置过的主题,默认值为`ThemeManager`统一设置的主题) * */ getCurrentTheme() { - return this._currentTheme; + return getThemeObject(this._currentThemeName); } /** @@ -1146,7 +1142,7 @@ export class VChart implements IVChart { await this.updateCustomConfigAndRerender(() => { this._updateCurrentTheme(name); - this._chart?.setCurrentTheme(this._currentTheme, true); + this._chart?.setCurrentTheme(true); return { change: true, reMake: false }; }); @@ -1166,7 +1162,7 @@ export class VChart implements IVChart { this.updateCustomConfigAndRerenderSync(() => { this._updateCurrentTheme(name); - this._chart?.setCurrentTheme(this._currentTheme, true); + this._chart?.setCurrentTheme(true); return { change: true, reMake: false }; }); diff --git a/packages/vchart/src/mark/base/base-mark.ts b/packages/vchart/src/mark/base/base-mark.ts index c87c385f56..2aafea6c51 100644 --- a/packages/vchart/src/mark/base/base-mark.ts +++ b/packages/vchart/src/mark/base/base-mark.ts @@ -411,7 +411,7 @@ export class BaseMark extends CompilableMark implements I const themeColor = computeActualDataScheme( getDataScheme( - this.model.getOption()?.getTheme()?.colorScheme, + this.model.getColorScheme(), this.model.modelType === 'series' ? (this.model.type as SeriesTypeEnum) : undefined ), (this.model as ISeries).getDefaultColorDomain() @@ -469,7 +469,7 @@ export class BaseMark extends CompilableMark implements I if (!('stroke' in computeStyle)) { const themeColor = computeActualDataScheme( getDataScheme( - this.model.getOption()?.getTheme()?.colorScheme, + this.model.getColorScheme(), this.model.modelType === 'series' ? (this.model.type as SeriesTypeEnum) : undefined ), (this.model as ISeries).getDefaultColorDomain() diff --git a/packages/vchart/src/model/base-model.ts b/packages/vchart/src/model/base-model.ts index 9cf7c58481..0283d87543 100644 --- a/packages/vchart/src/model/base-model.ts +++ b/packages/vchart/src/model/base-model.ts @@ -25,6 +25,8 @@ import { array, isArray, isEqual, isNil } from '@visactor/vutils'; import { Factory } from '../core/factory'; import type { SeriesTypeEnum } from '../series/interface'; import { MarkSet } from '../mark/mark-set'; +import { getThemeFromOption } from '../theme/util'; +import { defaultChartLevelTheme } from '../theme'; export abstract class BaseModel extends LayoutItem implements IModel { readonly type: string = 'null'; @@ -221,23 +223,28 @@ export abstract class BaseModel extends LayoutItem impl } protected _initTheme(theme?: any) { - this._theme = theme; - + if (theme) { + this._theme = theme; + } else { + this._theme = this._getTheme(); + } this._mergeMarkTheme(); + this._mergeThemeToSpec(); } - setTheme(theme?: any) { - this._theme = theme; + protected _getTheme(): any { + return undefined; } /** 将全局的 mark theme 合并进 model theme */ protected _mergeMarkTheme() { - const globalTheme = this._option.getTheme?.(); - if (isNil(globalTheme) || isNil(this._theme)) { + const config = this._option.getThemeConfig?.(); + if (isNil(config) || isNil(this._theme)) { return; } - const { mark: markThemeByType, markByName: markThemeByName } = globalTheme; + const markThemeByType = getThemeFromOption('mark', this._option); + const markThemeByName = getThemeFromOption('markByName', this._option); this.getMarkInfoList().forEach(({ type, name }) => { this._theme[name] = mergeSpec( {}, @@ -297,7 +304,7 @@ export abstract class BaseModel extends LayoutItem impl const newObj = preprocessSpecOrTheme( 'spec', obj, - this._option.getTheme?.()?.colorScheme, + this.getColorScheme(), this.modelType === 'series' ? (this.type as SeriesTypeEnum) : undefined ); @@ -307,8 +314,19 @@ export abstract class BaseModel extends LayoutItem impl return newObj; } - setCurrentTheme(theme: any, noRender?: boolean) { - // do nothing + async setCurrentTheme(noRender?: boolean) { + const modifyConfig = () => { + // 重新初始化 + this.reInit(this._getTheme()); + + return { change: true, reMake: false }; + }; + + if (noRender) { + modifyConfig(); + } else { + await this._option.globalInstance.updateCustomConfigAndRerender(modifyConfig); + } } updateLayoutAttribute() { @@ -411,4 +429,12 @@ export abstract class BaseModel extends LayoutItem impl protected _getDataIdKey(): string | ((datum: Datum) => string) | undefined { return undefined; } + + getColorScheme() { + return (this._option.getThemeConfig?.().chartLevelTheme ?? defaultChartLevelTheme).colorScheme; + } + + protected _getChartLevelTheme() { + return this._option.getThemeConfig?.().chartLevelTheme ?? defaultChartLevelTheme; + } } diff --git a/packages/vchart/src/model/interface.ts b/packages/vchart/src/model/interface.ts index 0189da9c95..66cbb6c2bc 100644 --- a/packages/vchart/src/model/interface.ts +++ b/packages/vchart/src/model/interface.ts @@ -23,6 +23,8 @@ import type { ICompilable, ICompilableInitOption } from '../compile/interface'; import type { ICompilableData } from '../compile/data'; import type { IGlobalScale } from '../scale/interface'; import type { IChart } from '../chart/interface'; +import type { IChartLevelTheme } from '../core/interface'; +import type { IThemeColorScheme } from '../theme/color-scheme/interface'; export type ILayoutNumber = number | IPercent | ((layoutRect: ILayoutRect) => number) | IPercentOffset; @@ -275,8 +277,8 @@ export interface IModel extends ICompilable, ILayoutItem { getSpecIndex: () => number; // theme - setTheme: (theme?: any) => void; - setCurrentTheme: (theme: any, noRender?: boolean) => void; + setCurrentTheme: (noRender?: boolean) => void; + getColorScheme: () => IThemeColorScheme | undefined; setMarkStyle: ( mark?: IMarkRaw, @@ -304,7 +306,12 @@ export interface IModelOption extends ICompilableInitOption { specIndex?: number; specKey?: string; - getTheme?: () => ITheme; + getThemeConfig?: () => { + globalTheme?: string; + optionTheme?: string | ITheme; + specTheme?: string | ITheme; + chartLevelTheme: IChartLevelTheme; + }; getChartLayoutRect: () => IRect; getChartViewRect: () => ILayoutRect; diff --git a/packages/vchart/src/series/base/base-series.ts b/packages/vchart/src/series/base/base-series.ts index ee48293d71..1e49f2561f 100644 --- a/packages/vchart/src/series/base/base-series.ts +++ b/packages/vchart/src/series/base/base-series.ts @@ -40,7 +40,8 @@ import type { ISeriesStackData, ISeriesTooltipHelper, SeriesMarkMap, - ISeriesMarkInfo + ISeriesMarkInfo, + SeriesTypeEnum } from '../interface'; import { dataToDataView, dataViewFromDataView, updateDataViewInData } from '../../data/initialize'; import { @@ -53,7 +54,8 @@ import { isArray, mergeFields, getFieldAlias, - couldBeValidNumber + couldBeValidNumber, + preprocessSpecOrTheme } from '../../util'; import type { IModelEvaluateOption, IModelRenderOption } from '../../model/interface'; import { Group } from './group'; @@ -73,10 +75,11 @@ import { getDataScheme } from '../../theme/color-scheme/util'; import { SeriesData } from './series-data'; import { addDataKey, initKeyMap } from '../../data/transforms/data-key'; import type { IGroupMark } from '../../mark/group'; -import { array, isEmpty, isEqual } from '@visactor/vutils'; +import { array, isEqual } from '@visactor/vutils'; import type { ISeriesMarkAttributeContext } from '../../compile/mark'; import { ColorOrdinalScale } from '../../scale/color-ordinal-scale'; import { baseSeriesMark } from './constant'; +import { getThemeFromOption } from '../../theme/util'; export abstract class BaseSeries extends BaseModel implements ISeries { readonly type: string = 'series'; @@ -1037,12 +1040,16 @@ export abstract class BaseSeries extends BaseModel imp // get default color scale // 重复代码太多了,整合一下 - protected getDefaultColorScale() { + protected _getDefaultColorScale() { const colorDomain = this.getDefaultColorDomain(); - const colorRange = getDataScheme(this._option.getTheme().colorScheme, this.type as any); + const colorRange = this._getDataScheme(); return new ColorOrdinalScale().domain(colorDomain).range?.(colorRange); } + protected _getDataScheme() { + return getDataScheme(this.getColorScheme(), this.type as any); + } + /** 获取默认 color scale 的 domain */ getDefaultColorDomain(): any[] { return this._seriesField ? this._viewDataStatistics?.latestData[this._seriesField]?.values : []; @@ -1051,7 +1058,7 @@ export abstract class BaseSeries extends BaseModel imp // 通用的默认颜色映射 用户设置优先级比这个高,会在setStyle中处理 getColorAttribute() { return { - scale: this._option.globalScale.getScale('color') ?? this.getDefaultColorScale(), + scale: this._option.globalScale.getScale('color') ?? this._getDefaultColorScale(), field: this._seriesField ?? DEFAULT_DATA_SERIES_FIELD }; } @@ -1074,30 +1081,13 @@ export abstract class BaseSeries extends BaseModel imp // do nothing } - async setCurrentTheme(theme: any, noRender?: boolean) { - const modifyConfig = () => { - // 重新初始化 - this.reInit(theme); - - return { change: true, reMake: false }; - }; - - if (noRender) { - modifyConfig(); - } else { - await this._option.globalInstance.updateCustomConfigAndRerender(modifyConfig); - } - } - - protected _initTheme(theme?: any) { - const globalTheme = this._option.getTheme(); - - if (theme) { - super._initTheme(theme); - } else { - super._initTheme(globalTheme.series[this.type] ?? {}); - } - this._mergeThemeToSpec(); + protected _getTheme() { + return preprocessSpecOrTheme( + 'theme', + getThemeFromOption(`series.${this.type}`, this._option), + this.getColorScheme(), + this.type as SeriesTypeEnum + ); } protected _createMark(markInfo: ISeriesMarkInfo, option: ISeriesMarkInitOption = {}) { diff --git a/packages/vchart/src/series/dot/dot.ts b/packages/vchart/src/series/dot/dot.ts index d8a732b641..07724d4bf5 100644 --- a/packages/vchart/src/series/dot/dot.ts +++ b/packages/vchart/src/series/dot/dot.ts @@ -17,7 +17,6 @@ import { DotSeriesTooltipHelper } from './tooltip-helper'; import type { IRectMark } from '../../mark/rect'; import type { FunctionType, IFillMarkSpec, VisualType } from '../../typings/visual'; import type { IDotSeriesSpec, IDotSeriesTheme } from './interface'; -import { getDataScheme } from '../../theme/color-scheme/util'; import { copyDataView } from '../../data/transforms/copy-data-view'; import { objFlat } from '../../data/transforms/obj-flat'; import { DEFAULT_GRID_BACKGROUND } from './config'; @@ -426,7 +425,7 @@ export class DotSeries extends Cartes // 通用的默认颜色映射 用户设置优先级比这个高,会在setStyle中处理 getColorAttribute() { return { - scale: this._option.globalScale.getScale('color') ?? this.getDefaultColorScale(), + scale: this._option.globalScale.getScale('color') ?? this._getDefaultColorScale(), field: this._seriesGroupField ?? this._seriesField ?? DEFAULT_DATA_SERIES_FIELD }; } @@ -443,7 +442,7 @@ export class DotSeries extends Cartes : this._seriesField ? this._viewDataStatistics?.latestData[this._seriesField].values : []; - const colorRange = getDataScheme(this._option.getTheme().colorScheme, this.type); + const colorRange = this._getDataScheme(); return new ColorOrdinalScale().domain(colorDomain).range(colorRange); } diff --git a/packages/vchart/src/series/heatmap/heatmap.ts b/packages/vchart/src/series/heatmap/heatmap.ts index 58cccfdd2a..4f0081daf5 100644 --- a/packages/vchart/src/series/heatmap/heatmap.ts +++ b/packages/vchart/src/series/heatmap/heatmap.ts @@ -139,7 +139,7 @@ export class HeatmapSeries ex getColorAttribute() { return { // TODO: 为热力图实现默认线性颜色 scale - scale: this._option.globalScale.getScale('color') ?? this.getDefaultColorScale(), + scale: this._option.globalScale.getScale('color') ?? this._getDefaultColorScale(), field: this.getFieldValue[0] }; } diff --git a/packages/vchart/src/series/interface/theme.ts b/packages/vchart/src/series/interface/theme.ts index 6cf16f8704..743b3cf36f 100644 --- a/packages/vchart/src/series/interface/theme.ts +++ b/packages/vchart/src/series/interface/theme.ts @@ -48,6 +48,8 @@ import { rangeColumnSeriesMark } from '../range-column/constant'; import { circlePackingSeriesMark } from '../circle-packing/constant'; import { heatmapSeriesMark } from '../heatmap/constant'; import { correlationSeriesMark } from '../correlation/constant'; +import { rangeAreaSeriesMark } from '../range-area/constant'; +import type { IRangeAreaSeriesTheme } from '../range-area/interface'; export interface ISeriesTheme { [SeriesTypeEnum.bar]?: IBarSeriesTheme; @@ -79,6 +81,7 @@ export interface ISeriesTheme { [SeriesTypeEnum.circlePacking]?: ICirclePackingSeriesTheme; [SeriesTypeEnum.heatmap]?: IHeatmapSeriesTheme; [SeriesTypeEnum.correlation]?: ICorrelationSeriesTheme; + [SeriesTypeEnum.rangeArea]?: IRangeAreaSeriesTheme; } export const seriesMarkInfoMap: Record = { @@ -110,5 +113,6 @@ export const seriesMarkInfoMap: Record = { [SeriesTypeEnum.rangeColumn]: rangeColumnSeriesMark, [SeriesTypeEnum.circlePacking]: circlePackingSeriesMark, [SeriesTypeEnum.heatmap]: heatmapSeriesMark, - [SeriesTypeEnum.correlation]: correlationSeriesMark + [SeriesTypeEnum.correlation]: correlationSeriesMark, + [SeriesTypeEnum.rangeArea]: rangeAreaSeriesMark }; diff --git a/packages/vchart/src/series/link/link.ts b/packages/vchart/src/series/link/link.ts index c6bb5886de..49739a0e81 100644 --- a/packages/vchart/src/series/link/link.ts +++ b/packages/vchart/src/series/link/link.ts @@ -327,7 +327,7 @@ export class LinkSeries extends Car // 通用的默认颜色映射 用户设置优先级比这个高,会在setStyle中处理 getColorAttribute() { return { - scale: this._option.globalScale.getScale('color') ?? this.getDefaultColorScale(), + scale: this._option.globalScale.getScale('color') ?? this._getDefaultColorScale(), field: this._dotTypeField ?? this._seriesField ?? DEFAULT_DATA_SERIES_FIELD }; } diff --git a/packages/vchart/src/series/map/map.ts b/packages/vchart/src/series/map/map.ts index c683bf6d75..47e3287934 100644 --- a/packages/vchart/src/series/map/map.ts +++ b/packages/vchart/src/series/map/map.ts @@ -136,7 +136,7 @@ export class MapSeries extends GeoSer { fill: (datum: any) => { if (isValid(datum[this._seriesField ?? DEFAULT_DATA_SERIES_FIELD])) { - return (this._option.globalScale.getScale('color') ?? this.getDefaultColorScale()).scale( + return (this._option.globalScale.getScale('color') ?? this._getDefaultColorScale()).scale( datum[this._seriesField ?? DEFAULT_DATA_SERIES_FIELD] ); } diff --git a/packages/vchart/src/series/polar/progress-like/progress-like.ts b/packages/vchart/src/series/polar/progress-like/progress-like.ts index 61bef3aae4..fa32dba44c 100644 --- a/packages/vchart/src/series/polar/progress-like/progress-like.ts +++ b/packages/vchart/src/series/polar/progress-like/progress-like.ts @@ -231,12 +231,7 @@ export abstract class ProgressLikeSeries exte const subTickData = this._getAngleAxisSubTickData(axis); const { x, y } = this.angleAxisHelper.center(); const radius = this._computeLayoutRadius(); - const markStyle = preprocessSpecOrTheme( - 'mark-spec', - style, - this._option.getTheme()?.colorScheme, - this.type as any - ); + const markStyle = preprocessSpecOrTheme('mark-spec', style, this.getColorScheme(), this.type as any); return subTickData.map(({ value }) => { const pos = this.angleAxisHelper.dataToPosition([value]) + degreeToRadian(offsetAngle); const angleUnit = degreeToRadian(angle) / 2; diff --git a/packages/vchart/src/series/range-area/interface.ts b/packages/vchart/src/series/range-area/interface.ts index 4ed238d53e..03d8637fd1 100644 --- a/packages/vchart/src/series/range-area/interface.ts +++ b/packages/vchart/src/series/range-area/interface.ts @@ -1,4 +1,4 @@ -import type { IAreaSeriesSpec } from '../area/interface'; +import type { IAreaSeriesSpec, IAreaSeriesTheme } from '../area/interface'; export interface IRangeAreaSeriesSpec extends Omit { /** * 系列类型 @@ -13,3 +13,5 @@ export interface IRangeAreaSeriesSpec extends Omit { /* 区间最大值字段 */ maxField?: string; } + +export type IRangeAreaSeriesTheme = IAreaSeriesTheme; diff --git a/packages/vchart/src/series/sankey/sankey.ts b/packages/vchart/src/series/sankey/sankey.ts index e606b0aa7c..2c92ca3f9d 100644 --- a/packages/vchart/src/series/sankey/sankey.ts +++ b/packages/vchart/src/series/sankey/sankey.ts @@ -20,7 +20,6 @@ import { DEFAULT_DATA_INDEX, LayoutZIndex, AttributeLevel, Event_Bubble_Level, C import { SeriesData } from '../base/series-data'; import { addVChartProperty } from '../../data/transforms/add-property'; import { addDataKey, initKeyMap } from '../../data/transforms/data-key'; -import { getDataScheme } from '../../theme/color-scheme/util'; import { SankeySeriesTooltipHelper } from './tooltip-helper'; import type { IBounds } from '@visactor/vutils'; import { Bounds } from '@visactor/vutils'; @@ -1242,16 +1241,14 @@ export class SankeySeries exten ? this._option.globalScale.getScale('color').domain() : this.getNodeList(); - let colorRange = - this._option.globalScale.getScale('color')?.range() ?? - getDataScheme(this._option.getTheme().colorScheme, this.type as any); + let colorRange = this._option.globalScale.getScale('color')?.range() ?? this._getDataScheme(); if ( this._option.globalScale.getScale('color')?.domain().length === 0 || isNil(this._option.globalScale.getScale('color').domain()[0]) ) { if (colorDomain.length > 10) { - colorRange = (getDataScheme(this._option.getTheme().colorScheme, this.type as any)[1] as any)?.scheme; + colorRange = (this._getDataScheme()[1] as any)?.scheme; } } diff --git a/packages/vchart/src/series/word-cloud/base.ts b/packages/vchart/src/series/word-cloud/base.ts index bffa03b33b..01c3979380 100644 --- a/packages/vchart/src/series/word-cloud/base.ts +++ b/packages/vchart/src/series/word-cloud/base.ts @@ -35,7 +35,6 @@ import { WORD_CLOUD_TEXT, WORD_CLOUD_WEIGHT } from '../../constant/word-cloud'; -import { getDataScheme } from '../../theme/color-scheme/util'; import type { ICompilableMark } from '../../compile/mark'; import { BaseSeries } from '../base/base-series'; import { ColorOrdinalScale } from '../../scale/color-ordinal-scale'; @@ -173,7 +172,7 @@ export class BaseWordCloudSeries datum[field]) : []; - const colorRange = - colorList ?? - this._option.globalScale.getScale('color')?.range() ?? - getDataScheme(this._option.getTheme().colorScheme, this.type as any); + const colorRange = colorList ?? this._option.globalScale.getScale('color')?.range() ?? this._getDataScheme(); return new ColorOrdinalScale().domain(colorDomain).range?.(colorRange); } @@ -343,7 +339,8 @@ export class BaseWordCloudSeries = new Map([ - [lightTheme.name, lightTheme], - [darkTheme.name, darkTheme] -]); - -/** 默认主题 */ +/** 声明内置主题 */ +export const builtinThemes: Record = { + [lightTheme.name]: lightTheme, + [darkTheme.name]: darkTheme +}; +/** 声明默认主题 */ export const defaultThemeName = lightTheme.name; -/** merge 过默认主题的最终主题字典 */ -export const themes: Map = new Map([[defaultThemeName, builtinThemeMap.get(defaultThemeName)]]); +/** 全局主题 map (包含用户新注册的主题) */ +export const themes: Map = new Map(Object.keys(builtinThemes).map(key => [key, builtinThemes[key]])); +/** 主题 map 中的元素是否 merge 过默认主题 (非默认主题的其他内置主题没有 merge 过默认主题) */ +export const hasThemeMerged: Map = new Map( + Object.keys(builtinThemes).map(key => [key, key === defaultThemeName]) +); /** 使新主题基于默认主题扩展,保证基础值 */ -export const getMergedTheme = (theme: Partial): ITheme => - mergeTheme({}, builtinThemeMap.get(defaultThemeName), theme); +export const getMergedTheme = (theme: Partial): ITheme => mergeTheme({}, themes.get(defaultThemeName), theme); -// 注册其他内置主题 -builtinThemeMap.forEach((theme, name) => { - if (name !== defaultThemeName) { - themes.set(name, getMergedTheme(theme)); - } -}); +export const defaultChartLevelTheme: IChartLevelTheme = { + background: getActualColor(builtinThemes[defaultThemeName].background, builtinThemes[defaultThemeName].colorScheme), + fontFamily: builtinThemes[defaultThemeName].fontFamily, + colorScheme: builtinThemes[defaultThemeName].colorScheme +}; diff --git a/packages/vchart/src/theme/color-scheme/util.ts b/packages/vchart/src/theme/color-scheme/util.ts index f419f35129..e76e0e126c 100644 --- a/packages/vchart/src/theme/color-scheme/util.ts +++ b/packages/vchart/src/theme/color-scheme/util.ts @@ -148,7 +148,7 @@ export function isColorKey(obj: any): obj is IColorKey { } export function isProgressiveDataColorScheme(obj: any): obj is ProgressiveDataScheme { - if (!isArray(obj)) { + if (!isArray(obj) || obj.length === 0) { return false; } return obj.every(item => { diff --git a/packages/vchart/src/theme/theme-manager.ts b/packages/vchart/src/theme/theme-manager.ts index 8cfe102471..3d05ba6c79 100644 --- a/packages/vchart/src/theme/theme-manager.ts +++ b/packages/vchart/src/theme/theme-manager.ts @@ -1,4 +1,4 @@ -import { defaultThemeName, getMergedTheme, themes } from './builtin'; +import { defaultThemeName, getMergedTheme, hasThemeMerged, themes } from './builtin'; import type { ITheme } from './interface'; import { InstanceManager } from '../core/instance-manager'; import type { IVChart } from '../core/interface'; @@ -22,6 +22,7 @@ export class ThemeManager { } // 所有主题基于默认主题扩展,保证基础值 ThemeManager.themes.set(name, getMergedTheme(theme)); + hasThemeMerged.set(name, true); } /** @@ -30,6 +31,10 @@ export class ThemeManager { * @returns */ static getTheme(name: string) { + if (hasThemeMerged.has(name) && !hasThemeMerged.get(name)) { + // 重新 merge 默认主题 + ThemeManager.registerTheme(name, ThemeManager.themes.get(name)); + } return ThemeManager.themes.get(name) || ThemeManager.getDefaultTheme(); } @@ -39,7 +44,7 @@ export class ThemeManager { * @returns 是否移除成功 */ static removeTheme(name: string): boolean { - return ThemeManager.themes.delete(name); + return ThemeManager.themes.delete(name) && hasThemeMerged.delete(name); } /** diff --git a/packages/vchart/src/theme/util.ts b/packages/vchart/src/theme/util.ts new file mode 100644 index 0000000000..99a095db2d --- /dev/null +++ b/packages/vchart/src/theme/util.ts @@ -0,0 +1,90 @@ +import { isString, get, isObject, isNil } from '@visactor/vutils'; +import type { ITheme } from './interface'; +import { ThemeManager } from './theme-manager'; +import { mergeSpec, transformColorSchemeToMerge, transformSeriesThemeToMerge } from '../util'; +import { getActualColor } from './color-scheme/util'; +import type { IThemeColorScheme } from './color-scheme/interface'; +import type { IModelOption } from '../model/interface'; +import { defaultChartLevelTheme, defaultThemeName } from './builtin'; + +/** + * 性能优化过的主题合并,只合并需要取用的主题部分 + * @param path 需要取用的路径 + * @param currentThemeName (低优先级)当前全局主题 name + * @param optionTheme (中优先级)option 中设置的主题 + * @param specTheme (高优先级)spec 中设置的主题 + * @returns + */ +export const mergeThemeAndGet = ( + path: string, + currentThemeName?: string, + optionTheme?: string | ITheme, + specTheme?: string | ITheme, + colorScheme?: IThemeColorScheme +) => { + if (isString(specTheme) && ThemeManager.themeExist(specTheme)) { + // 以 specTheme 为最底开始合并 + return getMergedValue(getThemeValue(path, specTheme, colorScheme)); + } else if (isString(optionTheme) && ThemeManager.themeExist(optionTheme)) { + // 以 optionTheme 为最底开始合并 + return getMergedValue(getThemeValue(path, optionTheme, colorScheme), getThemeValue(path, specTheme, colorScheme)); + } + // 以 baseTheme 为最底开始合并 + return getMergedValue( + getThemeValue(path, currentThemeName, colorScheme), + getThemeValue(path, optionTheme, colorScheme), + getThemeValue(path, specTheme, colorScheme) + ); +}; + +/** 从 theme 取特定 path 的值,但可能会改变形式为 merge 作准备 */ +const getThemeValue = (path: string, theme?: string | ITheme, colorScheme?: IThemeColorScheme) => { + let themeObject: ITheme; + if (isString(theme) && ThemeManager.themeExist(theme)) { + themeObject = ThemeManager.getTheme(theme); + } else if (isObject(theme)) { + themeObject = theme; + } + if (isNil(themeObject)) { + return undefined; + } + + const paths = path.split('.'); + + const colorSchemePath: keyof ITheme = 'colorScheme'; + if (path === colorSchemePath) { + return transformColorSchemeToMerge(themeObject.colorScheme); + } + const backgroundPath: keyof ITheme = 'background'; + if (path === backgroundPath) { + return getActualColor(themeObject.background, colorScheme); + } + const seriesPath: keyof ITheme = 'series'; + if (paths.length === 2 && paths[0] === seriesPath) { + const { markByName, mark } = themeObject; + return transformSeriesThemeToMerge(get(themeObject, paths), paths[1], mark, markByName); + } + + return get(themeObject, paths); +}; + +const getMergedValue = (...sources: any[]) => { + const tmpKey = '__TMP_KEY__'; + const obj = mergeSpec( + {}, + ...sources.map(source => ({ + [tmpKey]: source + })) + ); + return obj[tmpKey]; +}; + +export const getThemeFromOption = (path: string, option: Partial) => { + const { + globalTheme = defaultThemeName, + optionTheme, + specTheme, + chartLevelTheme = defaultChartLevelTheme + } = option.getThemeConfig?.() ?? {}; + return mergeThemeAndGet(path, globalTheme, optionTheme, specTheme, chartLevelTheme?.colorScheme); +}; diff --git a/packages/vchart/src/util/spec/merge-theme.ts b/packages/vchart/src/util/spec/merge-theme.ts index a54709d9e8..63781a78e3 100644 --- a/packages/vchart/src/util/spec/merge-theme.ts +++ b/packages/vchart/src/util/spec/merge-theme.ts @@ -1,5 +1,5 @@ import { type Maybe, array } from '@visactor/vutils'; -import type { ITheme } from '../../theme'; +import type { IGlobalMarkThemeByName, IGlobalMarkThemeByType, ITheme } from '../../theme'; import { transformColorSchemeToStandardStruct } from '../../theme/color-scheme/util'; import type { IThemeColorScheme } from '../../theme/color-scheme/interface'; import type { ISeriesTheme } from '../../series/interface'; @@ -17,14 +17,7 @@ function transformThemeToMerge(theme?: Maybe): Maybe { } // 将色板转化为标准形式 - let { colorScheme } = theme; - if (colorScheme) { - colorScheme = Object.keys(colorScheme).reduce((scheme, key) => { - const value = colorScheme[key]; - scheme[key] = transformColorSchemeToStandardStruct(value); - return scheme; - }, {} as IThemeColorScheme); - } + const colorScheme = transformColorSchemeToMerge(theme.colorScheme); // 将全局 mark 主题 merge 进系列主题 let { series } = theme; @@ -32,14 +25,7 @@ function transformThemeToMerge(theme?: Maybe): Maybe { if (markByType || markByName) { series = Object.keys(seriesMarkInfoMap).reduce((newSeriesTheme, key) => { const value = series?.[key] ?? {}; - const newValue = {}; - Object.values(seriesMarkInfoMap[key]).forEach(({ type, name }) => { - newValue[name] = mergeSpec({}, markByType?.[array(type)[0]] ?? {}, markByName?.[name] ?? {}, value[name]); - }); - newSeriesTheme[key] = { - ...value, - ...newValue - }; + newSeriesTheme[key] = transformSeriesThemeToMerge(value, key, markByType, markByName); return newSeriesTheme; }, {} as ISeriesTheme); } @@ -50,3 +36,32 @@ function transformThemeToMerge(theme?: Maybe): Maybe { series }; } + +/** 将色板转化为标准形式 */ +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; +} + +/** 将全局 mark 主题 merge 进系列主题 */ +export function transformSeriesThemeToMerge( + seriesTheme: any, + seriesType: string, + markByType: IGlobalMarkThemeByType, + markByName: IGlobalMarkThemeByName +): any { + const newTheme: any = {}; + Object.values(seriesMarkInfoMap[seriesType]).forEach(({ type, name }) => { + newTheme[name] = mergeSpec({}, markByType?.[array(type)[0]], markByName?.[name], seriesTheme?.[name]); + }); + return { + ...seriesTheme, + ...newTheme + }; +} diff --git a/packages/vchart/src/util/spec/preprocess.ts b/packages/vchart/src/util/spec/preprocess.ts index 7c025c1af2..b56c5e2571 100644 --- a/packages/vchart/src/util/spec/preprocess.ts +++ b/packages/vchart/src/util/spec/preprocess.ts @@ -1,4 +1,4 @@ -import { isArray, isFunction, isObject, isString, isValid, isValidNumber } from '@visactor/vutils'; +import { isArray, isFunction, isNil, isObject, isString, isValid, isValidNumber } from '@visactor/vutils'; import { seriesMarkNameSet, type SeriesTypeEnum } from '../../series/interface'; import type { IThemeColorScheme } from '../../theme/color-scheme/interface'; import { isDataView, isHTMLElement } from './common'; @@ -19,6 +19,9 @@ export function preprocessSpecOrTheme( colorScheme?: IThemeColorScheme, seriesType?: SeriesTypeEnum ): any { + if (isNil(obj)) { + return obj; + } if (isArray(obj)) { return obj.map(element => { if (isObject(element) && !isFunction(element)) {