blob: be26e9277a0ffd28f0285a13284e738e98f368b0 [file]
import { ref, reactive, markRaw } from 'vue'
import type { ThemeData, PreDefinedTheme } from '../types/theme'
import { generateEChartsTheme, generateThemeJsFile, generateThemeConfigForDownload } from '../utils/themeGenerator'
// Predefined themes configuration
export const PRE_DEFINED_THEMES: PreDefinedTheme[] = markRaw([
{
name: 'v5',
background: 'rgba(0, 0, 0, 0)',
theme: [
'#5470c6',
'#91cc75',
'#fac858',
'#ee6666',
'#73c0de',
'#3ba272',
'#fc8452',
'#9a60b4',
'#ea7ccc'
]
},
{
name: 'vintage',
background: '#fef8ef',
theme: [
'#d87c7c', '#919e8b', '#d7ab82', '#6e7074', '#61a0a8',
'#efa18d', '#787464', '#cc7e63', '#724e58', '#4b565b'
]
},
{
name: 'dark',
background: '#333',
theme: [
'#dd6b66', '#759aa0', '#e69d87', '#8dc1a9', '#ea7e53',
'#eedd78', '#73a373', '#73b9bc', '#7289ab', '#91ca8c',
'#f49f42'
]
},
{
name: 'westeros',
background: 'transparent',
theme: [
'#516b91', '#59c4e6', '#edafda', '#93b7e3', '#a5e7f0',
'#cbb0e3'
]
},
{
name: 'essos',
background: 'rgba(242,234,191,0.15)',
theme: [
'#893448', '#d95850', '#eb8146', '#ffb248', '#f2d643',
'#ebdba4'
]
},
{
name: 'wonderland',
background: 'transparent',
theme: [
'#4ea397', '#22c3aa', '#7bd9a5', '#d0648a', '#f58db2',
'#f2b3c9'
]
},
{
name: 'walden',
background: 'rgba(252,252,252,0)',
theme: [
'#3fb1e3', '#6be6c1', '#626c91', '#a0a7e6', '#c4ebad',
'#96dee8'
]
},
{
name: 'chalk',
background: '#293441',
theme: [
'#fc97af', '#87f7cf', '#f7f494', '#72ccff', '#f7c5a0',
'#d4a4eb', '#d2f5a6', '#76f2f2'
]
},
{
name: 'infographic',
background: 'transparent',
theme: [
'#C1232B', '#27727B', '#FCCE10', '#E87C25', '#B5C334',
'#FE8463', '#9BCA63', '#FAD860', '#F3A43B', '#60C0DD',
'#D7504B', '#C6E579', '#F4E001', '#F0805A', '#26C0C0'
]
},
{
name: 'macarons',
background: 'transparent',
theme: [
'#2ec7c9', '#b6a2de', '#5ab1ef', '#ffb980', '#d87a80',
'#8d98b3', '#e5cf0d', '#97b552', '#95706d', '#dc69aa',
'#07a2a4', '#9a7fd1', '#588dd5', '#f5994e', '#c05050',
'#59678c', '#c9ab00', '#7eb00a', '#6f5553', '#c14089'
]
},
{
name: 'roma',
background: 'transparent',
theme: [
'#E01F54', '#001852', '#f5e8c8', '#b8d2c7', '#c6b38e',
'#a4d8c2', '#f3d999', '#d3758f', '#dcc392', '#2e4783',
'#82b6e9', '#ff6347', '#a092f1', '#0a915d', '#eaf889',
'#6699FF', '#ff6666', '#3cb371', '#d5b158', '#38b6b6'
]
},
{
name: 'shine',
background: 'transparent',
theme: [
'#c12e34', '#e6b600', '#0098d9', '#2b821d', '#005eaa',
'#339ca8', '#cda819', '#32a487'
]
},
{
name: 'purple-passion',
background: 'rgba(91,92,110,1)',
theme: [
'#8a7ca8', '#e098c7', '#8fd3e8', '#71669e', '#cc70af',
'#7cb4cc'
]
}
])
// Default theme axes configuration
const createDefaultAxes = () => {
const types = ['all', 'category', 'value', 'log', 'time']
const names = ['General', 'Category', 'Value', 'Log', 'Time']
return types.map((type, i) => ({
type,
name: names[i] + ' Axis',
axisLineShow: type !== 'value' && type !== 'log',
axisLineColor: '#54555a',
axisTickShow: type !== 'value' && type !== 'log',
axisTickColor: '#54555a',
axisLabelShow: true,
axisLabelColor: '#54555a',
splitLineShow: type !== 'category' && type !== 'time',
splitLineColor: ['#dbdee4'],
splitAreaShow: false,
splitAreaColor: ['rgba(234,237,245,0.5)', 'rgba(255,255,255,0)']
}))
}
export const createDefaultTheme = (): ThemeData => {
const axes = createDefaultAxes()
return {
seriesCnt: 3,
backgroundColor: 'rgba(0, 0, 0, 0)',
titleColor: '#464646',
subtitleColor: '#6E7079',
textColorShow: false,
textColor: '#333',
markTextColor: '#eee',
color: [
'#5070dd',
'#b6d634',
'#505372',
'#ff994d',
'#0ca8df',
'#ffd10a',
'#fb628b',
'#785db0',
'#3fbe95'
],
borderColor: '#ccc',
borderWidth: 0,
visualMapColor: ['#bf444c', '#d88273', '#f6efa6'],
legendTextColor: '#333',
kColor: '#eb5454',
kColor0: '#47b262',
kBorderColor: '#eb5454',
kBorderColor0: '#47b262',
kBorderWidth: 1,
lineWidth: 2,
symbolSize: 4,
symbol: 'emptyCircle',
symbolBorderWidth: 1,
lineSmooth: false,
graphLineWidth: 1,
graphLineColor: '#aaa',
mapLabelColor: '#000',
mapLabelColorE: 'rgb(100,0,0)',
mapBorderColor: '#444',
mapBorderColorE: '#444',
mapBorderWidth: 0.5,
mapBorderWidthE: 1,
mapAreaColor: '#eee',
mapAreaColorE: 'rgba(255,215,0,0.8)',
axes,
axisSeperateSetting: true,
axis: [axes[0]!],
toolboxColor: '#999',
toolboxEmphasisColor: '#666',
tooltipAxisColor: '#ccc',
tooltipAxisWidth: 1,
timelineLineColor: '#DAE1F5',
timelineLineWidth: 2,
timelineItemColor: '#A4B1D7',
timelineItemColorE: '#FFF',
timelineCheckColor: '#316bf3',
timelineCheckBorderColor: '#fff',
timelineItemBorderWidth: 1,
timelineControlColor: '#A4B1D7',
timelineControlBorderColor: '#A4B1D7',
timelineControlBorderWidth: 1,
timelineLabelColor: '#A4B1D7',
gridLeft: '15%',
gridRight: '10%',
gridTop: 65,
gridBottom: 80,
legendLeft: 'center',
legendRight: 'auto',
legendTop: 'auto',
legendBottom: 15
}
}
// Global state management - Singleton pattern
let themeStoreInstance: ReturnType<typeof createThemeStore> | null = null
const createThemeStore = () => {
const theme = reactive<ThemeData>(createDefaultTheme())
const themeName = ref('customized')
const chartDisplay = reactive({
background: '#fff',
title: '#000'
})
const resetTheme = () => {
activePreDefinedThemeIndex.value = null
Object.assign(theme, createDefaultTheme())
themeName.value = 'customized'
}
const activePreDefinedThemeIndex = ref<number | null>(null)
let definedThemeLoadAbortController: AbortController | null = null
const loadPreDefinedTheme = async (index: number) => {
if (activePreDefinedThemeIndex.value === index) {
return
}
activePreDefinedThemeIndex.value = index
definedThemeLoadAbortController?.abort()
definedThemeLoadAbortController = new AbortController()
const preTheme = PRE_DEFINED_THEMES[index]
if (!preTheme) {
console.error('No theme found at index:', index)
return
}
try {
// Load the complete theme configuration from JSON file
const response = await fetch(`${import.meta.env.VITE_APP_ASSETS_DIR}themes/${preTheme.name}.json`, {
signal: definedThemeLoadAbortController.signal
})
if (!response.ok) {
throw new Error(`Failed to load theme: ${preTheme.name}`)
}
const themeData = await response.json()
if (themeData.theme) {
// Convert string numbers to actual numbers
const loadedTheme = { ...themeData.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()
}
} catch (error) {
console.error('Error loading predefined theme:', error)
// Fallback to basic theme loading
theme.backgroundColor = preTheme.background
theme.color = [...preTheme.theme]
themeName.value = preTheme.name
}
}
const updateAxisSetting = () => {
if (theme.axisSeperateSetting) {
theme.axis = theme.axes
} else {
theme.axis = [theme.axes[0]!]
}
}
const importTheme = (jsonString: string) => {
try {
const data = JSON.parse(jsonString)
if (data.theme) {
Object.assign(theme, data.theme)
if (data.themeName) {
themeName.value = data.themeName
}
updateAxisSetting()
}
} catch (error) {
console.error('Failed to import theme:', error)
throw error
}
}
const exportTheme = () => {
const exportData = { ...theme }
// Remove duplicate axis options since they are already included in theme.axes
const { axis, ...cleanedData } = exportData
return cleanedData
}
const getEChartsTheme = () => {
// Convert reactive object to plain object to ensure proper data passing
const plainTheme = JSON.parse(JSON.stringify(theme))
return generateEChartsTheme(plainTheme)
}
const getThemeJsFile = () => {
return generateThemeJsFile(theme, themeName.value)
}
const getThemeConfigForDownload = () => {
return generateThemeConfigForDownload(theme, themeName.value, 1)
}
return {
theme,
themeName,
chartDisplay,
resetTheme,
activePreDefinedThemeIndex,
loadPreDefinedTheme,
updateAxisSetting,
importTheme,
exportTheme,
getEChartsTheme,
getThemeJsFile,
getThemeConfigForDownload
}
}
export const useThemeStore = () => {
if (!themeStoreInstance) {
themeStoreInstance = createThemeStore()
}
return themeStoreInstance
}