This is an automated email from the ASF dual-hosted git repository. ovilia pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/echarts-theme-builder.git
commit 92e9056adae89832eab034726175fb7695e83684 Author: Ovilia <[email protected]> AuthorDate: Fri Aug 29 15:14:57 2025 +0800 feat: update themes --- src/stores/theme.ts | 88 +++++++++++- src/utils/themeGenerator.ts | 318 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 403 insertions(+), 3 deletions(-) diff --git a/src/stores/theme.ts b/src/stores/theme.ts index 92ef40d..f1b8069 100644 --- a/src/stores/theme.ts +++ b/src/stores/theme.ts @@ -1,5 +1,6 @@ import { ref, reactive } from 'vue' import type { ThemeData, PreDefinedTheme } from '../types/theme' +import { generateEChartsTheme, generateThemeJsFile, generateThemeConfigForDownload } from '../utils/themeGenerator' // 预定义主题 export const PRE_DEFINED_THEMES: PreDefinedTheme[] = [ @@ -208,9 +209,75 @@ export const useThemeStore = () => { themeName.value = 'customized' } - const loadPreDefinedTheme = (index: number) => { + const loadPreDefinedTheme = async (index: number) => { + console.log('🏪 loadPreDefinedTheme called with index:', index) const preTheme = PRE_DEFINED_THEMES[index] - if (preTheme) { + if (!preTheme) { + console.error('❌ No theme found at index:', index) + return + } + + console.log('🏪 Loading theme:', preTheme.name) + + try { + // Load the complete theme configuration from JSON file + const response = await fetch(`/themes/${preTheme.name}.json`) + if (!response.ok) { + throw new Error(`Failed to load theme: ${preTheme.name}`) + } + + const themeData = await response.json() + console.log('🏪 Loaded theme data:', themeData) + + if (themeData.theme) { + // Convert string numbers to actual numbers + const loadedTheme = { ...themeData.theme } + if (typeof loadedTheme.seriesCnt === 'string') { + loadedTheme.seriesCnt = parseInt(loadedTheme.seriesCnt, 10) + } + if (typeof loadedTheme.borderWidth === 'string') { + loadedTheme.borderWidth = parseFloat(loadedTheme.borderWidth) + } + if (typeof loadedTheme.lineWidth === 'string') { + loadedTheme.lineWidth = parseFloat(loadedTheme.lineWidth) + } + if (typeof loadedTheme.symbolSize === 'string') { + loadedTheme.symbolSize = parseFloat(loadedTheme.symbolSize) + } + if (typeof loadedTheme.symbolBorderWidth === 'string') { + loadedTheme.symbolBorderWidth = parseFloat(loadedTheme.symbolBorderWidth) + } + + console.log('🏪 Before applying theme:', { ...theme }) + + // Apply the complete theme configuration property by property to ensure reactivity + // This ensures Vue's reactive system properly detects changes + Object.keys(loadedTheme).forEach(key => { + if (key in theme) { + (theme as any)[key] = loadedTheme[key] + } + }) + + themeName.value = themeData.themeName || preTheme.name + + // Ensure color is an array + if (typeof theme.color === 'string') { + theme.color = [theme.color] + } + + // Update axis settings based on axisSeperateSetting + updateAxisSetting() + + console.log('🏪 After applying theme:', theme) + console.log('🏪 Theme name set to:', themeName.value) + + // Force trigger reactive update by modifying a dummy property + ;(theme as any).__forceUpdate = Date.now() + } + } catch (error) { + console.error('❌ Error loading predefined theme:', error) + // Fallback to basic theme loading + console.log('🏪 Using fallback theme loading') theme.backgroundColor = preTheme.background theme.color = [...preTheme.theme] themeName.value = preTheme.name @@ -248,6 +315,18 @@ export const useThemeStore = () => { return cleanedData } + const getEChartsTheme = (isToExport: boolean = false) => { + return generateEChartsTheme(theme, isToExport) + } + + const getThemeJsFile = () => { + return generateThemeJsFile(theme, themeName.value) + } + + const getThemeConfigForDownload = () => { + return generateThemeConfigForDownload(theme, themeName.value, 1) + } + return { theme, themeName, @@ -257,6 +336,9 @@ export const useThemeStore = () => { loadPreDefinedTheme, updateAxisSetting, importTheme, - exportTheme + exportTheme, + getEChartsTheme, + getThemeJsFile, + getThemeConfigForDownload } } diff --git a/src/utils/themeGenerator.ts b/src/utils/themeGenerator.ts new file mode 100644 index 0000000..28e3973 --- /dev/null +++ b/src/utils/themeGenerator.ts @@ -0,0 +1,318 @@ +import type { ThemeData } from '../types/theme' + +/** + * Generate ECharts theme configuration based on theme data + * @param themeData - Theme configuration data + * @param isToExport - Whether this is for export (includes backgroundColor) + * @returns ECharts theme configuration object + */ +export function generateEChartsTheme(themeData: ThemeData, isToExport: boolean = false) { + console.log('🎨 generateEChartsTheme called with:', themeData) + console.log('🎨 Theme backgroundColor:', themeData.backgroundColor) + console.log('🎨 Theme colors:', themeData.color) + console.log('🎨 Theme titleColor:', themeData.titleColor) + + // Halloween pumpkin symbol path + const pumpkin = 'path://M237.062,81.761L237.062,81.761c-12.144-14.24-25.701-20.1-40.68-19.072 c-10.843,0.747-20.938,5.154-30.257,13.127c-9.51-5.843-19.8-9.227-30.859-10.366c0.521-3.197,1.46-6.306,2.85-9.363 c3.458-7.038,8.907-12.741,16.331-17.296c-5.609-3.384-11.227-6.799-16.854-10.279c-16.257,8.104-25.06,20.601-26.463,38.417 c-7.599,1.705-14.685,4.486-21.247,8.437c-9.164-7.677-18.996-11.917-29.496-12.632c-14.819-0.998-28.467,4.787-40.938,18.827 C6.445,96.182,0,114.867,0,136.242c-0.007 [...] + + // Generate series style configuration + const seriesStyle = { + itemStyle: { + borderWidth: themeData.symbolBorderWidth + }, + lineStyle: { + width: themeData.lineWidth + }, + symbolSize: themeData.symbolSize, + symbol: themeData.symbol, + smooth: themeData.lineSmooth + } + + // Generate item style configuration + const itemStyle = { + borderWidth: themeData.borderWidth, + borderColor: themeData.borderColor + } + + const border = { + itemStyle: itemStyle + } as any + + // Special case for Halloween theme + if (themeData.symbol === 'halloween') { + seriesStyle.symbol = pumpkin + border.symbol = pumpkin + } + + // Generate map configuration + const mapConfig = { + itemStyle: { + areaColor: themeData.mapAreaColor, + borderColor: themeData.mapBorderColor, + borderWidth: themeData.mapBorderWidth + }, + label: { + color: themeData.mapLabelColor + }, + emphasis: { + itemStyle: { + areaColor: themeData.mapAreaColorE, + borderColor: themeData.mapBorderColorE, + borderWidth: themeData.mapBorderWidthE + }, + label: { + color: themeData.mapLabelColorE + } + } + } + + // Generate axis configuration helper function + const getAxisConfig = (axisType: number) => { + let axisIndex = 0 + if (themeData.axisSeperateSetting && axisType > 0 && axisType < themeData.axes.length) { + axisIndex = axisType + } + + const axisData = themeData.axes[axisIndex] + return { + axisLine: { + show: axisData.axisLineShow, + lineStyle: { + color: axisData.axisLineColor + } + }, + axisTick: { + show: axisData.axisTickShow, + lineStyle: { + color: axisData.axisTickColor + } + }, + axisLabel: { + show: axisData.axisLabelShow, + color: axisData.axisLabelColor + }, + splitLine: { + show: axisData.splitLineShow, + lineStyle: { + color: axisData.splitLineColor + } + }, + splitArea: { + show: axisData.splitAreaShow, + areaStyle: { + color: axisData.splitAreaColor + } + } + } + } + + // Generate graph style configuration + const graphStyle = { + ...seriesStyle, + color: themeData.color, + lineStyle: { + width: themeData.graphLineWidth, + color: themeData.graphLineColor + }, + label: { + color: themeData.markTextColor + }, + itemStyle: { + ...itemStyle, + borderWidth: themeData.borderWidth, + borderColor: themeData.borderColor + } + } + + // Generate candlestick configuration + const candlestickConfig = { + itemStyle: { + color: themeData.kColor, + color0: themeData.kColor0, + borderColor: themeData.kBorderColor, + borderColor0: themeData.kBorderColor0, + borderWidth: themeData.kBorderWidth + } + } + + // Generate bar configuration + const barConfig = { + itemStyle: { + barBorderWidth: themeData.borderWidth, + barBorderColor: themeData.borderColor + } + } + + // Generate mark point configuration + const markPointConfig = { + label: { + color: themeData.markTextColor + }, + emphasis: { + label: { + color: themeData.markTextColor + } + } + } + + // Main theme configuration object + const themeConfig = { + color: themeData.color, + backgroundColor: isToExport ? themeData.backgroundColor : 'transparent', + textStyle: themeData.textColorShow ? { + color: themeData.textColor + } : {}, + title: { + textStyle: { + color: themeData.titleColor + }, + subtextStyle: { + color: themeData.subtitleColor + } + }, + line: seriesStyle, + radar: seriesStyle, + bar: barConfig, + pie: border, + scatter: border, + boxplot: border, + parallel: border, + sankey: border, + funnel: border, + gauge: border, + candlestick: candlestickConfig, + graph: graphStyle, + map: mapConfig, + geo: mapConfig, + categoryAxis: getAxisConfig(1), + valueAxis: getAxisConfig(2), + logAxis: getAxisConfig(3), + timeAxis: getAxisConfig(4), + toolbox: { + iconStyle: { + borderColor: themeData.toolboxColor + }, + emphasis: { + iconStyle: { + borderColor: themeData.toolboxEmphasisColor + } + } + }, + legend: { + textStyle: { + color: themeData.legendTextColor + } + }, + tooltip: { + axisPointer: { + lineStyle: { + color: themeData.tooltipAxisColor, + width: themeData.tooltipAxisWidth + }, + crossStyle: { + color: themeData.tooltipAxisColor, + width: themeData.tooltipAxisWidth + } + } + }, + timeline: { + lineStyle: { + color: themeData.timelineLineColor, + width: themeData.timelineLineWidth + }, + itemStyle: { + color: themeData.timelineItemColor, + borderWidth: themeData.timelineItemBorderWidth + }, + controlStyle: { + color: themeData.timelineControlColor, + borderColor: themeData.timelineControlBorderColor, + borderWidth: themeData.timelineControlBorderWidth + }, + checkpointStyle: { + color: themeData.timelineCheckColor, + borderColor: themeData.timelineCheckBorderColor + }, + label: { + color: themeData.timelineLabelColor + }, + emphasis: { + itemStyle: { + color: themeData.timelineItemColorE + }, + controlStyle: { + color: themeData.timelineControlColor, + borderColor: themeData.timelineControlBorderColor, + borderWidth: themeData.timelineControlBorderWidth + }, + label: { + color: themeData.timelineLabelColor + } + } + }, + visualMap: { + color: themeData.visualMapColor + }, + markPoint: markPointConfig + } + + return themeConfig +} + +/** + * Generate JavaScript file content for theme export + * @param themeData - Theme configuration data + * @param themeName - Name of the theme + * @returns JavaScript file content as string + */ +export function generateThemeJsFile(themeData: ThemeData, themeName: string): string { + const themeConfig = generateEChartsTheme(themeData, true) + + // Format theme with 4 spaces indentation + let themeJson = JSON.stringify(themeConfig, null, ' ') + // Indent each line with 4 spaces + themeJson = themeJson.split('\n').join('\n ') + + return `(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['exports', 'echarts'], factory); + } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') { + // CommonJS + factory(exports, require('echarts')); + } else { + // Browser globals + factory({}, root.echarts); + } +}(this, function (exports, echarts) { + var log = function (msg) { + if (typeof console !== 'undefined') { + console && console.error && console.error(msg); + } + }; + if (!echarts) { + log('ECharts is not Loaded'); + return; + } + echarts.registerTheme('${themeName}', ${themeJson}); +}));` +} + +/** + * Generate theme configuration for download + * @param themeData - Theme configuration data + * @param themeName - Name of the theme + * @param version - Version number + * @returns Configuration object for download + */ +export function generateThemeConfigForDownload(themeData: ThemeData, themeName: string, version: number = 1) { + const cleanedTheme = { ...themeData } + // Remove duplicate axis option as it's included in axes + delete (cleanedTheme as any).axis + + return { + version, + themeName, + theme: cleanedTheme + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
