Simplify theming - Remove dependency on listening to mutations on pf-theme-provider. In an embedded world the UI might not have theme provider so there would be nothing to listen to. - Make charts more reactive to to when CSS themes change by reading them out every frame. This can cause a reflow but we trigger reflows in plenty of other places in the UI so it's hardly an issue. - Don't rely on custom themes, just set the theme colors using setOption().
diff --git a/ui/src/components/widgets/charts/bar_chart.ts b/ui/src/components/widgets/charts/bar_chart.ts index a41a20f..97bfcad 100644 --- a/ui/src/components/widgets/charts/bar_chart.ts +++ b/ui/src/components/widgets/charts/bar_chart.ts
@@ -15,11 +15,7 @@ import m from 'mithril'; import type {EChartsCoreOption} from 'echarts/core'; import {AggregationType, extractBrushRange, formatNumber} from './chart_utils'; -import { - EChartView, - EChartEventHandler, - getPerfettoThemeColors, -} from './echart_view'; +import {EChartView, EChartEventHandler} from './echart_view'; import { buildAxisOption, buildGridOption, @@ -154,13 +150,6 @@ orientation = 'vertical', } = attrs; const fmtMeasure = formatMeasure ?? formatNumber; - - // Only get theme for custom color overrides - const theme = - barColor === undefined || barHoverColor === undefined - ? getPerfettoThemeColors() - : undefined; - const horizontal = orientation === 'horizontal'; const labels = data.items.map((item) => String(item.label)); @@ -213,9 +202,7 @@ emphasis: barHoverColor !== undefined ? {itemStyle: {color: barHoverColor}} - : theme !== undefined - ? {itemStyle: {color: theme.accentColor}} - : undefined, + : undefined, }, ], }; @@ -230,7 +217,7 @@ option.toolbox = {show: false}; } - return option as EChartsCoreOption; + return option; } function buildBarEventHandlers(
diff --git a/ui/src/components/widgets/charts/chart_option_builder.ts b/ui/src/components/widgets/charts/chart_option_builder.ts index 176d67d..b8ae7fd 100644 --- a/ui/src/components/widgets/charts/chart_option_builder.ts +++ b/ui/src/components/widgets/charts/chart_option_builder.ts
@@ -13,7 +13,6 @@ // limitations under the License. import type {EChartsCoreOption} from 'echarts/core'; -import {getChartThemeColors} from './chart_theme'; /** * Configuration for an axis in a chart. @@ -48,35 +47,31 @@ /** * Build an axis option from config. - * Explicitly includes theme colors because ECharts doesn't deep merge - * option objects with theme objects - setting axisLabel overrides the theme's - * axisLabel entirely, so we must include colors here. + * Theme colors are applied by EChartView via the registered ECharts theme. */ export function buildAxisOption( config: AxisConfig, isXAxis: boolean, ): Record<string, unknown> { - const theme = getChartThemeColors(); const axis: Record<string, unknown> = { type: config.type, name: config.name, nameLocation: isXAxis ? ('middle' as const) : ('end' as const), nameGap: config.nameGap ?? (isXAxis ? 25 : 10), - nameTextStyle: {fontSize: 11, color: theme.textColor}, + nameTextStyle: {fontSize: 11}, axisLabel: { fontSize: 10, - color: theme.textColor, ...(config.formatter !== undefined && {formatter: config.formatter}), ...(config.labelOverflow !== undefined && { overflow: config.labelOverflow, }), ...(config.labelWidth !== undefined && {width: config.labelWidth}), }, - axisTick: {lineStyle: {color: theme.borderColor}}, - axisLine: {lineStyle: {color: theme.borderColor}}, + axisTick: {lineStyle: {}}, + axisLine: {lineStyle: {}}, splitLine: { show: config.showSplitLine ?? false, - lineStyle: {color: theme.borderColor}, + lineStyle: {}, }, }; @@ -100,7 +95,7 @@ } /** - * Build a themed grid option. + * Build a grid option. */ export function buildGridOption(opts?: { top?: number; @@ -120,10 +115,9 @@ /** * Build a brush configuration. - * Uses accent color from theme (ECharts doesn't theme brush colors). + * Theme colors are applied by EChartView via the registered ECharts theme. */ export function buildBrushOption(config: BrushConfig): Record<string, unknown> { - const theme = getChartThemeColors(); return { ...(config.xAxisIndex !== undefined && {xAxisIndex: config.xAxisIndex}), ...(config.yAxisIndex !== undefined && {yAxisIndex: config.yAxisIndex}), @@ -132,7 +126,6 @@ brushStyle: { borderWidth: 1, color: 'rgba(0, 0, 0, 0.1)', - borderColor: theme.accentColor, }, throttleType: 'debounce' as const, throttleDelay: 100, @@ -141,13 +134,11 @@ /** * Build a legend option. - * Explicitly includes theme colors because ECharts doesn't deep merge - * option objects with theme objects. + * Theme colors are applied by EChartView via the registered ECharts theme. */ export function buildLegendOption( position: 'top' | 'right' = 'top', ): Record<string, unknown> { - const theme = getChartThemeColors(); if (position === 'right') { return { show: true, @@ -161,7 +152,6 @@ width: 120, overflow: 'truncate', ellipsis: '\u2026', - color: theme.textColor, }, tooltip: {show: true}, pageButtonPosition: 'end', @@ -170,14 +160,14 @@ return { show: true, top: 0, - textStyle: {fontSize: 10, color: theme.textColor}, + textStyle: {fontSize: 10}, }; } /** * Build a complete base chart option with grid, axes, and optional * tooltip/brush/legend. Charts add their own `series` on top. - * Theme colors are applied by ECharts theme system. + * Theme colors are applied by EChartView via the registered ECharts theme. */ export function buildChartOption(config: { readonly grid?: Parameters<typeof buildGridOption>[0];
diff --git a/ui/src/components/widgets/charts/chart_theme.ts b/ui/src/components/widgets/charts/chart_theme.ts index c76612b..065ca21 100644 --- a/ui/src/components/widgets/charts/chart_theme.ts +++ b/ui/src/components/widgets/charts/chart_theme.ts
@@ -13,25 +13,7 @@ // limitations under the License. /** - * Chart theme utilities for reading Perfetto theme colors. - * - * DESIGN NOTES: - * - * 1. Colors are read from CSS variables defined in theme_provider.scss, NOT - * hardcoded. This ensures charts automatically adapt when theme colors - * are updated in SCSS. - * - * 2. We read from the `.pf-theme-provider` element (not document.documentElement) - * because that's where theme classes (.pf-theme-provider--light/--dark) are - * applied and where CSS variables are scoped. - * - * 3. ECharts doesn't automatically pick up CSS variable changes, so chart - * components must call getChartThemeColors() when building options and - * rebuild when the theme changes (see EChartView.onThemeChange). - * - * 4. Chart options that set sub-objects (like axisLabel: {fontSize: 10}) - * override theme values entirely - ECharts doesn't deep merge. Therefore, - * chart_option_builder.ts explicitly includes theme colors in axis options. + * Chart theme utilities for reading Perfetto theme colors from CSS variables. */ /** @@ -46,23 +28,10 @@ } /** - * Returns true if dark theme is currently active. + * Returns the current theme colors by reading CSS variables from the given element. */ -export function isDarkTheme(): boolean { - const themeProvider = document.querySelector('.pf-theme-provider'); - return themeProvider?.classList.contains('pf-theme-provider--dark') ?? false; -} - -/** - * Returns the current theme colors by reading CSS variables from the - * theme provider element. Colors are defined in theme_provider.scss. - */ -export function getChartThemeColors(): ChartThemeColors { - const themeProvider = document.querySelector('.pf-theme-provider'); - if (themeProvider === null) { - throw new Error('Theme provider element not found'); - } - const style = getComputedStyle(themeProvider); +export function getChartThemeColors(element: Element): ChartThemeColors { + const style = getComputedStyle(element); const chartColors: string[] = []; for (let i = 1; i <= 8; i++) {
diff --git a/ui/src/components/widgets/charts/echart_view.ts b/ui/src/components/widgets/charts/echart_view.ts index dda7e17..09f3ce9 100644 --- a/ui/src/components/widgets/charts/echart_view.ts +++ b/ui/src/components/widgets/charts/echart_view.ts
@@ -15,17 +15,11 @@ /** * ECharts integration for Perfetto UI. * - * THEME HANDLING: - * - * ECharts themes are registered at initialization time by reading CSS variables - * from the theme provider (see chart_theme.ts). When the user switches themes: - * - * 1. A MutationObserver detects the class change on .pf-theme-provider - * 2. onThemeChange() re-registers ECharts themes with fresh CSS variable values - * 3. The chart is disposed and re-initialized with the new theme - * 4. m.redraw() triggers parent components to rebuild options with new colors - * - * This approach ensures charts respond to theme changes without page reload. + * Theme colors are read from CSS variables and used to register an ECharts + * theme at init time. When the theme changes (e.g., light/dark mode toggle), + * the chart is disposed and re-initialized with the new theme. This approach + * avoids mutating options (which would drop formatter functions) and ensures + * charts automatically respond to theme changes. */ import m from 'mithril'; @@ -50,15 +44,9 @@ import {classNames} from '../../../base/classnames'; import {SimpleResizeObserver} from '../../../base/resize_observer'; import {Spinner} from '../../../widgets/spinner'; -import { - isDarkTheme, - getChartThemeColors, - type ChartThemeColors, -} from './chart_theme'; +import {getChartThemeColors, type ChartThemeColors} from './chart_theme'; -// Re-export for backward compatibility -export {getChartThemeColors as getPerfettoThemeColors}; -export type {ChartThemeColors as ThemeColors}; +const PERFETTO_THEME_NAME = 'perfetto'; let echartsInitialized = false; @@ -79,162 +67,6 @@ ToolboxComponent, CanvasRenderer, ]); - registerPerfettoThemes(); -} - -/** - * Returns the ECharts theme name based on current theme. - */ -function getCurrentThemeName(): 'perfetto-light' | 'perfetto-dark' { - return isDarkTheme() ? 'perfetto-dark' : 'perfetto-light'; -} - -/** - * Builds an ECharts theme object by reading CSS variables. - */ -function buildEChartsTheme(): Record<string, unknown> { - const theme = getChartThemeColors(); - - return { - color: theme.chartColors, - backgroundColor: 'transparent', - textStyle: { - color: theme.textColor, - fontFamily: 'inherit', - }, - title: { - textStyle: { - color: theme.textColor, - }, - }, - legend: { - textStyle: { - color: theme.textColor, - }, - }, - tooltip: { - backgroundColor: theme.backgroundColor, - borderColor: theme.borderColor, - textStyle: { - color: theme.textColor, - }, - }, - axisPointer: { - lineStyle: { - color: theme.borderColor, - }, - crossStyle: { - color: theme.borderColor, - }, - }, - xAxis: { - axisLine: { - lineStyle: { - color: theme.borderColor, - }, - }, - axisTick: { - lineStyle: { - color: theme.borderColor, - }, - }, - axisLabel: { - color: theme.textColor, - }, - splitLine: { - lineStyle: { - color: theme.borderColor, - }, - }, - nameTextStyle: { - color: theme.textColor, - }, - }, - yAxis: { - axisLine: { - lineStyle: { - color: theme.borderColor, - }, - }, - axisTick: { - lineStyle: { - color: theme.borderColor, - }, - }, - axisLabel: { - color: theme.textColor, - }, - splitLine: { - lineStyle: { - color: theme.borderColor, - }, - }, - nameTextStyle: { - color: theme.textColor, - }, - }, - }; -} - -/** - * Registers both light and dark Perfetto themes with ECharts. - * Called once during ECharts initialization. - */ -function registerPerfettoThemes(): void { - // Register themes with current CSS variable values. - // Note: Theme registration happens once at init time. For dynamic theme - // switching, we re-initialize the chart instance with the new theme name. - const theme = buildEChartsTheme(); - echarts.registerTheme('perfetto-light', theme); - echarts.registerTheme('perfetto-dark', theme); -} - -// Global set to track all mounted EChartView instances -const mountedCharts = new Set<EChartView>(); - -// Single MutationObserver for all charts -let themeObserver: MutationObserver | undefined; - -/** - * Starts observing theme provider class changes to detect theme switches. - * Only creates the observer when the first chart mounts. - */ -function startThemeObserver(): void { - if (themeObserver !== undefined) return; - - const themeProvider = document.querySelector('.pf-theme-provider'); - if (themeProvider === null) return; - - themeObserver = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if ( - mutation.type === 'attributes' && - mutation.attributeName === 'class' - ) { - const newTheme = getCurrentThemeName(); - // Notify all mounted charts - for (const chart of mountedCharts) { - chart.onThemeChange(newTheme); - } - break; - } - } - }); - - themeObserver.observe(themeProvider, { - attributes: true, - attributeFilter: ['class'], - }); -} - -/** - * Stops the theme observer when no charts are mounted. - */ -function stopThemeObserver(): void { - if (themeObserver !== undefined && mountedCharts.size === 0) { - themeObserver.disconnect(); - themeObserver = undefined; - } } /** @@ -311,17 +143,64 @@ const DEFAULT_HEIGHT = 200; +/** + * Build an ECharts theme object from CSS variable colors. + * This sets default styling for axes, text, legends, tooltips, etc. + */ +function buildEChartsTheme(colors: ChartThemeColors): Record<string, unknown> { + const {textColor, borderColor, backgroundColor, chartColors} = colors; + return { + color: chartColors, + backgroundColor: 'transparent', + textStyle: { + color: textColor, + fontFamily: 'inherit', + }, + title: {textStyle: {color: textColor}}, + legend: {textStyle: {color: textColor}}, + tooltip: { + backgroundColor, + borderColor, + textStyle: {color: textColor}, + }, + axisPointer: { + lineStyle: {color: borderColor}, + crossStyle: {color: borderColor}, + }, + categoryAxis: { + axisLabel: {color: textColor}, + nameTextStyle: {color: textColor}, + axisLine: {lineStyle: {color: borderColor}}, + axisTick: {lineStyle: {color: borderColor}}, + splitLine: {lineStyle: {color: borderColor}}, + }, + valueAxis: { + axisLabel: {color: textColor}, + nameTextStyle: {color: textColor}, + axisLine: {lineStyle: {color: borderColor}}, + axisTick: {lineStyle: {color: borderColor}}, + splitLine: {lineStyle: {color: borderColor}}, + }, + logAxis: { + axisLabel: {color: textColor}, + nameTextStyle: {color: textColor}, + axisLine: {lineStyle: {color: borderColor}}, + axisTick: {lineStyle: {color: borderColor}}, + splitLine: {lineStyle: {color: borderColor}}, + }, + }; +} + export class EChartView implements m.ClassComponent<EChartViewAttrs> { private chart?: EChartsType; private container?: HTMLElement; private resizeObs?: Disposable; private prevHandlers: ReadonlyArray<EChartEventHandler> = []; private prevOptionJson?: string; - private currentTheme: 'perfetto-light' | 'perfetto-dark' = 'perfetto-light'; + private prevThemeJson?: string; oncreate({dom, attrs}: m.CVnodeDOM<EChartViewAttrs>) { ensureEChartsSetup(); - this.currentTheme = getCurrentThemeName(); const container = dom.querySelector( '.pf-echart-view__canvas', @@ -332,7 +211,7 @@ // Only init ECharts when we have an option to render (the canvas // is display:none during loading, so init would get 0×0 dimensions). if (attrs.option !== undefined) { - this.initChart(attrs); + this.initChart(attrs, dom); } // Defer resize to the next frame so that a layout change caused by @@ -340,21 +219,28 @@ this.resizeObs = new SimpleResizeObserver(dom, () => { requestAnimationFrame(() => this.chart?.resize()); }); - - // Register for theme changes - mountedCharts.add(this); - startThemeObserver(); } - onupdate({attrs}: m.CVnodeDOM<EChartViewAttrs>) { + onupdate({attrs, dom}: m.CVnodeDOM<EChartViewAttrs>) { if (attrs.option === undefined) return; - // Lazy init: first option arrived after a loading state. + // Read theme colors from DOM + const themeColors = getChartThemeColors(dom); + const themeJson = JSON.stringify(themeColors); + + // If theme changed, we need to re-init the chart with the new theme + if (this.chart !== undefined && themeJson !== this.prevThemeJson) { + this.chart.dispose(); + this.chart = undefined; + } + + // Lazy init: first option arrived after a loading state, or theme changed. if (this.chart === undefined) { - this.initChart(attrs); + this.initChart(attrs, dom, themeColors); return; } + // Update option (just stringify option, not themed version since theme is in ECharts) const optionJson = JSON.stringify(attrs.option); if (optionJson !== this.prevOptionJson) { this.prevOptionJson = optionJson; @@ -367,11 +253,24 @@ this.syncHandlers(attrs.eventHandlers ?? []); } - private initChart(attrs: EChartViewAttrs): void { + private initChart( + attrs: EChartViewAttrs, + dom: Element, + themeColors?: ChartThemeColors, + ): void { if (this.container === undefined || attrs.option === undefined) return; - this.chart = echarts.init(this.container, this.currentTheme); + + // Get theme colors if not provided + const colors = themeColors ?? getChartThemeColors(dom); + const themeJson = JSON.stringify(colors); + + // Register theme with ECharts (re-registering is safe, it just overwrites) + echarts.registerTheme(PERFETTO_THEME_NAME, buildEChartsTheme(colors)); + + this.chart = echarts.init(this.container, PERFETTO_THEME_NAME); this.chart.setOption(attrs.option); this.prevOptionJson = JSON.stringify(attrs.option); + this.prevThemeJson = themeJson; this.syncHandlers(attrs.eventHandlers ?? []); this.activateBrush(attrs.activeBrushType); } @@ -389,9 +288,6 @@ } onremove() { - mountedCharts.delete(this); - stopThemeObserver(); - if (this.resizeObs) { this.resizeObs[Symbol.dispose](); this.resizeObs = undefined; @@ -403,31 +299,6 @@ } } - /** - * Called when the document theme changes. - * Re-registers ECharts themes with new CSS values and reinitializes charts. - */ - onThemeChange(newTheme: 'perfetto-light' | 'perfetto-dark'): void { - if (this.currentTheme === newTheme) return; - this.currentTheme = newTheme; - - // Re-register themes with updated CSS variable values - const theme = buildEChartsTheme(); - echarts.registerTheme('perfetto-light', theme); - echarts.registerTheme('perfetto-dark', theme); - - // Re-initialize chart with new theme - if (this.chart !== undefined && this.container !== undefined) { - const currentOption = this.chart.getOption(); - this.chart.dispose(); - this.chart = echarts.init(this.container, newTheme); - this.chart.setOption(currentOption, {notMerge: true}); - this.syncHandlers(this.prevHandlers); - this.chart.resize(); - m.redraw(); - } - } - view({attrs}: m.Vnode<EChartViewAttrs>) { const height = attrs.height ?? DEFAULT_HEIGHT; const isLoading = attrs.option === undefined && !attrs.empty;
diff --git a/ui/src/components/widgets/charts/histogram.ts b/ui/src/components/widgets/charts/histogram.ts index 9b98f01..ddbda62 100644 --- a/ui/src/components/widgets/charts/histogram.ts +++ b/ui/src/components/widgets/charts/histogram.ts
@@ -21,11 +21,7 @@ HistogramConfig, computeHistogram, } from './histogram_loader'; -import { - EChartView, - EChartEventHandler, - getPerfettoThemeColors, -} from './echart_view'; +import {EChartView, EChartEventHandler} from './echart_view'; import {buildChartOption} from './chart_option_builder'; // Re-export data types for convenience @@ -138,12 +134,6 @@ logScale = false, } = attrs; const fmtY = formatYValue ?? formatNumber; - - // Only get theme for custom color overrides - const theme = - barColor === undefined || barHoverColor === undefined - ? getPerfettoThemeColors() - : undefined; const categories = data.buckets.map((b) => formatXValue(b.start)); const option = buildChartOption({ @@ -197,9 +187,7 @@ emphasis: barHoverColor !== undefined ? {itemStyle: {color: barHoverColor}} - : theme !== undefined - ? {itemStyle: {color: theme.accentColor}} - : undefined, + : undefined, }, ];
diff --git a/ui/src/components/widgets/charts/pie_chart.ts b/ui/src/components/widgets/charts/pie_chart.ts index 1f3bcd8..ca123bb 100644 --- a/ui/src/components/widgets/charts/pie_chart.ts +++ b/ui/src/components/widgets/charts/pie_chart.ts
@@ -15,12 +15,7 @@ import m from 'mithril'; import type {EChartsCoreOption} from 'echarts/core'; import {formatNumber} from './chart_utils'; -import { - EChartView, - EChartEventHandler, - EChartClickParams, - getPerfettoThemeColors, -} from './echart_view'; +import {EChartView, EChartEventHandler, EChartClickParams} from './echart_view'; import {buildLegendOption} from './chart_option_builder'; /** @@ -123,9 +118,6 @@ innerRadiusRatio = 0, } = attrs; - // Only get theme for border color (not themed by ECharts) - const theme = getPerfettoThemeColors(); - const pieData = slices.map((s) => ({ name: s.label, value: s.value, @@ -166,10 +158,6 @@ emphasis: { scaleSize: 5, }, - itemStyle: { - borderColor: theme.backgroundColor, - borderWidth: 2, - }, }, ], };
diff --git a/ui/src/components/widgets/charts/scatter_chart.ts b/ui/src/components/widgets/charts/scatter_chart.ts index d1ad227..c47de82 100644 --- a/ui/src/components/widgets/charts/scatter_chart.ts +++ b/ui/src/components/widgets/charts/scatter_chart.ts
@@ -15,11 +15,7 @@ import m from 'mithril'; import type {EChartsCoreOption} from 'echarts/core'; import {extractBrushRange, formatNumber} from './chart_utils'; -import { - EChartView, - EChartEventHandler, - getPerfettoThemeColors, -} from './echart_view'; +import {EChartView, EChartEventHandler} from './echart_view'; import {buildChartOption, buildLegendOption} from './chart_option_builder'; /** @@ -182,9 +178,6 @@ } = attrs; const fmtX = formatXValue ?? formatNumber; const fmtY = formatYValue ?? formatNumber; - - // Only get theme for emphasis border color (not themed by ECharts) - const theme = getPerfettoThemeColors(); const displayLegend = showLegend ?? data.series.length > 1; // Compute size range for normalization if any points have sizes @@ -238,7 +231,7 @@ : symbolSize, itemStyle: s.color !== undefined ? {color: s.color} : undefined, emphasis: { - itemStyle: {borderWidth: 2, borderColor: theme.backgroundColor}, + itemStyle: {borderWidth: 2}, }, }; });
diff --git a/ui/src/components/widgets/charts/treemap_chart.ts b/ui/src/components/widgets/charts/treemap_chart.ts index fdfd68b..5274bfb 100644 --- a/ui/src/components/widgets/charts/treemap_chart.ts +++ b/ui/src/components/widgets/charts/treemap_chart.ts
@@ -15,12 +15,7 @@ import m from 'mithril'; import type {EChartsCoreOption} from 'echarts/core'; import {formatNumber} from './chart_utils'; -import { - EChartView, - EChartEventHandler, - EChartClickParams, - getPerfettoThemeColors, -} from './echart_view'; +import {EChartView, EChartEventHandler, EChartClickParams} from './echart_view'; /** * A node in the treemap hierarchy. @@ -126,12 +121,6 @@ enableDrillDown = false, } = attrs; - const theme = getPerfettoThemeColors(); - - // Build category-to-color mapping - const categoryColors = new Map<string, string>(); - assignColors(data.nodes, categoryColors, theme.chartColors); - const total = data.nodes.reduce((sum, n) => sum + computeTotal(n), 0); return { @@ -148,7 +137,7 @@ series: [ { type: 'treemap', - data: convertNodes(data.nodes, categoryColors), + data: convertNodes(data.nodes), roam: enableDrillDown ? 'move' : false, nodeClick: enableDrillDown ? 'zoomToNode' : false, visibleMin, @@ -156,33 +145,22 @@ show: showLabels, formatter: '{b}', fontSize: 11, - color: theme.textColor, }, itemStyle: { - borderColor: theme.backgroundColor, borderWidth: 2, gapWidth: 2, }, - breadcrumb: enableDrillDown - ? { - show: true, - itemStyle: { - textStyle: {color: theme.textColor}, - }, - } - : {show: false}, + breadcrumb: enableDrillDown ? {show: true} : {show: false}, levels: [ { // Level 0: parent groups itemStyle: { - borderColor: theme.borderColor, borderWidth: 3, gapWidth: 3, }, upperLabel: { show: true, height: 20, - color: theme.textColor, fontSize: 12, fontWeight: 'bold' as const, }, @@ -191,7 +169,6 @@ // Level 1: children colorSaturation: [0.35, 0.65], itemStyle: { - borderColor: theme.backgroundColor, borderWidth: 1, gapWidth: 1, }, @@ -200,7 +177,6 @@ // Level 2+: deeper children (if any) colorSaturation: [0.25, 0.55], itemStyle: { - borderColor: theme.backgroundColor, borderWidth: 1, gapWidth: 1, }, @@ -212,37 +188,12 @@ } /** - * Recursively assign colors to categories found in nodes. - */ -function assignColors( - nodes: readonly TreemapNode[], - categoryColors: Map<string, string>, - chartColors: readonly string[], -): void { - for (const node of nodes) { - const category = node.category ?? node.name; - if (!categoryColors.has(category)) { - categoryColors.set( - category, - chartColors[categoryColors.size % chartColors.length], - ); - } - if (node.children !== undefined) { - assignColors(node.children, categoryColors, chartColors); - } - } -} - -/** * Convert TreemapNode tree to ECharts data format. */ function convertNodes( nodes: readonly TreemapNode[], - categoryColors: Map<string, string>, ): Array<Record<string, unknown>> { return nodes.map((node) => { - const category = node.category ?? node.name; - const color = categoryColors.get(category); const children = node.children; const hasChildren = children !== undefined && children.length > 0; // For nodes with children, use computed value from children if value is 0 @@ -251,10 +202,9 @@ const result: Record<string, unknown> = { name: node.name, value, - itemStyle: {color}, }; if (hasChildren) { - result.children = convertNodes(children, categoryColors); + result.children = convertNodes(children); } return result; });