Showing preview only (219K chars total). Download the full file or copy to clipboard to get everything.
Repository: Ovilia/ECharts-Theme-Builder
Branch: master
Commit: 10a67cc57507
Files: 51
Total size: 203.4 KB
Directory structure:
gitextract_jnmntyz5/
├── .asf.yaml
├── .editorconfig
├── .gitattributes
├── .github/
│ └── workflows/
│ └── deploy.yml
├── .gitignore
├── .jshintrc
├── .vscode/
│ └── settings.json
├── .yo-rc.json
├── LICENSE
├── README.md
├── app.html
├── config/
│ └── env.asf.js
├── index.html
├── package.json
├── public/
│ └── themes/
│ ├── chalk.json
│ ├── dark.json
│ ├── default.json
│ ├── essos.json
│ ├── halloween.json
│ ├── infographic.json
│ ├── macarons.json
│ ├── purple-passion.json
│ ├── roma.json
│ ├── shine.json
│ ├── v5.json
│ ├── vintage.json
│ ├── walden.json
│ ├── westeros.json
│ └── wonderland.json
├── scripts/
│ └── release.js
├── src/
│ ├── App.vue
│ ├── components/
│ │ ├── ChartPreviewPanel.vue
│ │ ├── ColorList.vue
│ │ ├── ColorPicker.vue
│ │ └── ThemePanel.vue
│ ├── composables/
│ │ └── useLocalization.ts
│ ├── i18n.ts
│ ├── locales/
│ │ ├── en.json
│ │ └── zh.json
│ ├── main.ts
│ ├── stores/
│ │ └── theme.ts
│ ├── style.css
│ ├── types/
│ │ └── theme.ts
│ ├── utils/
│ │ ├── chartConfigs.ts
│ │ ├── download.ts
│ │ └── themeGenerator.ts
│ └── vite-env.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .asf.yaml
================================================
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
github:
description: Design your own theme for Apache ECharts.
homepage: https://echarts.apache.org/theme-builder.html
labels:
- echarts
- apache
- theme
ghp_branch: gh-pages
notifications:
commits: commits@echarts.apache.org
pullrequests: commits@echarts.apache.org
issues: commits@echarts.apache.org
================================================
FILE: .editorconfig
================================================
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 2
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[package.json]
indent_style = space
indent_size = 2
[bower.json]
indent_style = space
indent_size = 2
================================================
FILE: .gitattributes
================================================
* text=auto
================================================
FILE: .github/workflows/deploy.yml
================================================
name: Build and Deploy
on:
push:
branches:
- master
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
registry-url: https://registry.npmjs.org/
- name: Install and Build 🔧
run: |
npm i
npm i @jamesives/github-pages-deploy-action -D
npm run build
- name: Deploy 🚀
uses: ./node_modules/@jamesives/github-pages-deploy-action
with:
branch: gh-pages # The branch the action should deploy to.
folder: dist
target-folder: docs
single-commit: true
clean: true
================================================
FILE: .gitignore
================================================
node_modules
dist
.tmp
.sass-cache
app/bower_components
test/bower_components
app/vendors/echarts.js
.DS_Store
app
================================================
FILE: .jshintrc
================================================
{
"node": true,
"browser": true,
"esnext": true,
"bitwise": true,
"camelcase": true,
"curly": true,
"eqeqeq": true,
"immed": true,
"indent": 4,
"latedef": true,
"newcap": true,
"noarg": true,
"quotmark": "single",
"undef": true,
"unused": true,
"strict": true,
"trailing": true,
"smarttabs": true,
"jquery": true
}
================================================
FILE: .vscode/settings.json
================================================
{
"i18n-ally.localesPaths": [
"src/locales"
],
"typescript.tsdk": "./node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
================================================
FILE: .yo-rc.json
================================================
{
"generator-mocha": {
"ui": "bdd",
"rjs": false
}
}
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
# Apache ECharts Theme Builder
Design your own theme for Apache ECharts.
[Online Theme Builder](https://echarts.apache.org/theme-builder.html)
[](https://github.com/apache/echarts-theme-builder/actions/workflows/deploy.yml)
## Build
```bash
npm i
npm run build
```
## Release to echarts-www
Update the [echarts-www](https://github.com/apache/echarts-www) and [echarts-website](https://github.com/apache/echarts-website) project path in `config/env.asf.js` before run
```bash
npm run release
```
It will generate the html to the `_generated` folder of [echarts-www](https://github.com/apache/echarts-www) and other resources to the [echarts-website](https://github.com/apache/echarts-website) folder.
================================================
FILE: app.html
================================================
<script type="module" src="/src/main.ts"></script>
<div id="theme-builder-app"></div>
================================================
FILE: config/env.asf.js
================================================
import path from 'path';
import { fileURLToPath } from 'url';
// Get __dirname equivalent in ESM
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
releaseDestDir: path.resolve(__dirname, '../../echarts-website'),
ecWWWGeneratedDir: path.resolve(__dirname, '../../echarts-www/_generated')
};
================================================
FILE: index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="https://echarts.apache.org/en/images/favicon.png">
<title>Apache ECharts Theme Builder</title>
<style>
/* only for non-release environment */
html, body {
height: 100%;
margin: 0;
}
</style>
<!-- include echarts CDN lib if NOT IN dev environment -->
%VITE_EXTERNAL_ECHARTS_SCRIPT%
</head>
<body>
<div id="theme-builder-app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
================================================
FILE: package.json
================================================
{
"name": "echarts-theme-builder",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build && vite build --mode app",
"serve:dist": "http-server dist",
"serve:app": "http-server app",
"release": "vue-tsc -b && vite build --mode app && node scripts/release.js"
},
"dependencies": {
"colorthief": "^3.3.1",
"vant": "^4.9.24",
"vue": "^3.5.31",
"vue-i18n": "^9.14.5",
"vue3-colorpicker": "^2.3.0"
},
"devDependencies": {
"@types/fs-extra": "^11.0.4",
"@types/node": "^25.5.0",
"@vitejs/plugin-vue": "^6.0.5",
"@vue/tsconfig": "^0.9.1",
"copy-dir": "^1.3.0",
"echarts": "^6.0.0",
"fs-extra": "^11.3.4",
"typescript": "~5.8.3",
"vite": "^7.3.2",
"vite-plugin-static-copy": "^3.4.0",
"vue-tsc": "^3.2.6"
}
}
================================================
FILE: public/themes/chalk.json
================================================
{
"version": 1,
"themeName": "chalk",
"theme": {
"seriesCnt": "3",
"backgroundColor": "rgba(41,52,65,1)",
"titleColor": "#ffffff",
"subtitleColor": "#dddddd",
"textColorShow": false,
"textColor": "#333",
"markTextColor": "#293441",
"color": [
"#fc97af",
"#87f7cf",
"#f7f494",
"#72ccff",
"#f7c5a0",
"#d4a4eb",
"#d2f5a6",
"#76f2f2"
],
"borderColor": "#ccc",
"borderWidth": 0,
"visualMapColor": [
"#fc97af",
"#87f7cf"
],
"legendTextColor": "#999999",
"kColor": "#fc97af",
"kColor0": "transparent",
"kBorderColor": "#fc97af",
"kBorderColor0": "#87f7cf",
"kBorderWidth": "2",
"lineWidth": "3",
"symbolSize": "0",
"symbol": "circle",
"symbolBorderWidth": "4",
"lineSmooth": true,
"graphLineWidth": "1",
"graphLineColor": "#ffffff",
"mapLabelColor": "#893448",
"mapLabelColorE": "rgb(137,52,72)",
"mapBorderColor": "#999999",
"mapBorderColorE": "#eb8146",
"mapBorderWidth": 0.5,
"mapBorderWidthE": 1,
"mapAreaColor": "#f3f3f3",
"mapAreaColorE": "rgba(255,178,72,1)",
"axes": [
{
"type": "all",
"name": "通用坐标轴",
"axisLineShow": true,
"axisLineColor": "#666666",
"axisTickShow": false,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#aaaaaa",
"splitLineShow": false,
"splitLineColor": [
"#e6e6e6"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
},
{
"type": "category",
"name": "类目坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": false,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "value",
"name": "数值坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "log",
"name": "对数坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "time",
"name": "时间坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
],
"axisSeperateSetting": false,
"toolboxColor": "#999999",
"toolboxEmphasisColor": "#666666",
"tooltipAxisColor": "#cccccc",
"tooltipAxisWidth": 1,
"timelineLineColor": "#87f7cf",
"timelineLineWidth": 1,
"timelineItemColor": "#87f7cf",
"timelineItemColorE": "#f7f494",
"timelineCheckColor": "#fc97af",
"timelineCheckBorderColor": "rgba(252,151,175,0.3)",
"timelineItemBorderWidth": 1,
"timelineControlColor": "#87f7cf",
"timelineControlBorderColor": "#87f7cf",
"timelineControlBorderWidth": 0.5,
"timelineLabelColor": "#87f7cf",
"datazoomBackgroundColor": "rgba(255,255,255,0)",
"datazoomDataColor": "rgba(114,204,255,1)",
"datazoomFillColor": "rgba(114,204,255,0.2)",
"datazoomHandleColor": "#72ccff",
"datazoomHandleWidth": "100",
"datazoomLabelColor": "#333333"
}
}
================================================
FILE: public/themes/dark.json
================================================
{
"version": 1,
"themeName": "dark",
"theme": {
"seriesCnt": "4",
"backgroundColor": "rgba(51,51,51,1)",
"titleColor": "#eeeeee",
"subtitleColor": "#aaa",
"textColorShow": false,
"textColor": "#333",
"markTextColor": "#eee",
"color": [
"#dd6b66",
"#759aa0",
"#e69d87",
"#8dc1a9",
"#ea7e53",
"#eedd78",
"#73a373",
"#73b9bc",
"#7289ab",
"#91ca8c",
"#f49f42"
],
"borderColor": "#ccc",
"borderWidth": 0,
"visualMapColor": [
"#bf444c",
"#d88273",
"#f6efa6"
],
"legendTextColor": "#eeeeee",
"kColor": "#fd1050",
"kColor0": "#0cf49b",
"kBorderColor": "#fd1050",
"kBorderColor0": "#0cf49b",
"kBorderWidth": 1,
"lineWidth": 2,
"symbolSize": 4,
"symbol": "circle",
"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": [
{
"type": "all",
"name": "通用坐标轴",
"axisLineShow": true,
"axisLineColor": "#eeeeee",
"axisTickShow": true,
"axisTickColor": "#eeeeee",
"axisLabelShow": true,
"axisLabelColor": "#eeeeee",
"splitLineShow": true,
"splitLineColor": [
"#aaaaaa"
],
"splitAreaShow": false,
"splitAreaColor": [
"#eeeeee"
]
},
{
"type": "category",
"name": "类目坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": false,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "value",
"name": "数值坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "log",
"name": "对数坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "time",
"name": "时间坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
],
"axisSeperateSetting": false,
"toolboxColor": "#999",
"toolboxEmphasisColor": "#666",
"tooltipAxisColor": "#eeeeee",
"tooltipAxisWidth": "1",
"timelineLineColor": "#eeeeee",
"timelineLineWidth": 1,
"timelineItemColor": "#dd6b66",
"timelineItemColorE": "#a9334c",
"timelineCheckColor": "#e43c59",
"timelineCheckBorderColor": "rgba(194,53,49, 0.5)",
"timelineItemBorderWidth": 1,
"timelineControlColor": "#eeeeee",
"timelineControlBorderColor": "#eeeeee",
"timelineControlBorderWidth": 0.5,
"timelineLabelColor": "#eeeeee",
"datazoomBackgroundColor": "rgba(47,69,84,0)",
"datazoomDataColor": "rgba(255,255,255,0.3)",
"datazoomFillColor": "rgba(167,183,204,0.4)",
"datazoomHandleColor": "#a7b7cc",
"datazoomHandleWidth": "100",
"datazoomLabelColor": "#eeeeee"
}
}
================================================
FILE: public/themes/default.json
================================================
{
"version": 1,
"themeName": "default",
"theme": {
"seriesCnt": 3,
"backgroundColor": "rgba(0,0,0,0)",
"titleColor": "#333333",
"subtitleColor": "#aaa",
"textColorShow": false,
"textColor": "#333",
"markTextColor": "#eee",
"color": [
"#c23531",
"#2f4554",
"#61a0a8",
"#d48265",
"#91c7ae",
"#749f83",
"#ca8622",
"#bda29a",
"#6e7074",
"#546570",
"#c4ccd3"
],
"borderColor": "#ccc",
"borderWidth": 0,
"visualMapColor": [
"#bf444c",
"#d88273",
"#f6efa6"
],
"legendTextColor": "#333333",
"kColor": "#c23531",
"kColor0": "#314656",
"kBorderColor": "#c23531",
"kBorderColor0": "#314656",
"kBorderWidth": 1,
"lineWidth": 2,
"symbolSize": 4,
"symbol": "emptyCircle",
"symbolBorderWidth": 1,
"lineSmooth": false,
"graphLineWidth": 1,
"graphLineColor": "#aaa",
"mapLabelColor": "#000000",
"mapLabelColorE": "rgb(100,0,0)",
"mapBorderColor": "#444444",
"mapBorderColorE": "#444",
"mapBorderWidth": 0.5,
"mapBorderWidthE": 1,
"mapAreaColor": "#eeeeee",
"mapAreaColorE": "rgba(255,215,0,0.8)",
"axes": [
{
"type": "all",
"name": "通用坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "category",
"name": "类目坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": false,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "value",
"name": "数值坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "log",
"name": "对数坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "time",
"name": "时间坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
],
"axisSeperateSetting": true,
"toolboxColor": "#999999",
"toolboxEmphasisColor": "#666666",
"tooltipAxisColor": "#cccccc",
"tooltipAxisWidth": 1,
"timelineLineColor": "#293c55",
"timelineLineWidth": 1,
"timelineItemColor": "#293c55",
"timelineItemColorE": "#a9334c",
"timelineCheckColor": "#e43c59",
"timelineCheckBorderColor": "rgba(194,53,49, 0.5)",
"timelineItemBorderWidth": 1,
"timelineControlColor": "#293c55",
"timelineControlBorderColor": "#293c55",
"timelineControlBorderWidth": 0.5,
"timelineLabelColor": "#293c55",
"datazoomBackgroundColor": "rgba(47,69,84,0)",
"datazoomDataColor": "rgba(47,69,84,0.3)",
"datazoomFillColor": "rgba(167,183,204,0.4)",
"datazoomHandleColor": "#a7b7cc",
"datazoomHandleWidth": "100",
"datazoomLabelColor": "#333333"
}
}
================================================
FILE: public/themes/essos.json
================================================
{
"version": 1,
"themeName": "essos",
"theme": {
"seriesCnt": "4",
"backgroundColor": "rgba(242,234,191,0.15)",
"titleColor": "#893448",
"subtitleColor": "#d95850",
"textColorShow": false,
"textColor": "#333",
"markTextColor": "#ffffff",
"color": [
"#893448",
"#d95850",
"#eb8146",
"#ffb248",
"#f2d643",
"#ebdba4"
],
"borderColor": "#ccc",
"borderWidth": 0,
"visualMapColor": [
"#893448",
"#d95850",
"#eb8146",
"#ffb248",
"#f2d643",
"rgb(247,238,173)"
],
"legendTextColor": "#999999",
"kColor": "#eb8146",
"kColor0": "transparent",
"kBorderColor": "#d95850",
"kBorderColor0": "#58c470",
"kBorderWidth": "2",
"lineWidth": "2",
"symbolSize": "6",
"symbol": "emptyCircle",
"symbolBorderWidth": "2",
"lineSmooth": true,
"graphLineWidth": 1,
"graphLineColor": "#aaa",
"mapLabelColor": "#893448",
"mapLabelColorE": "#893448",
"mapBorderColor": "#999999",
"mapBorderColorE": "#eb8146",
"mapBorderWidth": 0.5,
"mapBorderWidthE": 1,
"mapAreaColor": "#f3f3f3",
"mapAreaColorE": "#ffb248",
"axes": [
{
"type": "all",
"name": "通用坐标轴",
"axisLineShow": true,
"axisLineColor": "#aaaaaa",
"axisTickShow": false,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#999999",
"splitLineShow": true,
"splitLineColor": [
"#e6e6e6"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
},
{
"type": "category",
"name": "类目坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": false,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "value",
"name": "数值坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "log",
"name": "对数坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "time",
"name": "时间坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
],
"axisSeperateSetting": false,
"toolboxColor": "#999",
"toolboxEmphasisColor": "#666",
"tooltipAxisColor": "#ccc",
"tooltipAxisWidth": 1,
"timelineLineColor": "#893448",
"timelineLineWidth": 1,
"timelineItemColor": "#893448",
"timelineItemColorE": "#ffb248",
"timelineCheckColor": "#eb8146",
"timelineCheckBorderColor": "rgba(255,178,72,0.41)",
"timelineItemBorderWidth": 1,
"timelineControlColor": "#893448",
"timelineControlBorderColor": "#893448",
"timelineControlBorderWidth": 0.5,
"timelineLabelColor": "#893448",
"datazoomBackgroundColor": "rgba(255,255,255,0)",
"datazoomDataColor": "rgba(255,178,72,0.5)",
"datazoomFillColor": "rgba(255,178,72,0.15)",
"datazoomHandleColor": "#ffb248",
"datazoomHandleWidth": "100",
"datazoomLabelColor": "#333"
}
}
================================================
FILE: public/themes/halloween.json
================================================
{
"version": 1,
"themeName": "halloween",
"theme": {
"seriesCnt": "4",
"backgroundColor": "rgba(64,64,64,0.5)",
"titleColor": "#ffaf51",
"subtitleColor": "#eeeeee",
"textColorShow": false,
"textColor": "#333",
"markTextColor": "#333333",
"color": [
"#ff715e",
"#ffaf51",
"#ffee51",
"#8c6ac4",
"#715c87"
],
"borderColor": "#ccc",
"borderWidth": "0",
"visualMapColor": [
"#ff715e",
"#ffee51",
"#797fba"
],
"legendTextColor": "#999999",
"kColor": "#ffee51",
"kColor0": "rgba(255,255,255,0)",
"kBorderColor": "#ff715e",
"kBorderColor0": "#797fba",
"kBorderWidth": "1",
"lineWidth": "3",
"symbolSize": "8",
"symbol": "emptyCircle",
"symbolBorderWidth": "2",
"lineSmooth": false,
"graphLineWidth": "1",
"graphLineColor": "#888888",
"mapLabelColor": "#ffffff",
"mapLabelColorE": "#ffee51",
"mapBorderColor": "#999999",
"mapBorderColorE": "#ffaf51",
"mapBorderWidth": 0.5,
"mapBorderWidthE": 1,
"mapAreaColor": "#555555",
"mapAreaColorE": "rgba(255,175,81,0.5)",
"axes": [
{
"type": "all",
"name": "通用坐标轴",
"axisLineShow": true,
"axisLineColor": "#666666",
"axisTickShow": false,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#999999",
"splitLineShow": true,
"splitLineColor": [
"#555555"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
},
{
"type": "category",
"name": "类目坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": false,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "value",
"name": "数值坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "log",
"name": "对数坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "time",
"name": "时间坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
],
"axisSeperateSetting": false,
"toolboxColor": "#999999",
"toolboxEmphasisColor": "#666666",
"tooltipAxisColor": "#cccccc",
"tooltipAxisWidth": 1,
"timelineLineColor": "#ffaf51",
"timelineLineWidth": 1,
"timelineItemColor": "#ffaf51",
"timelineItemColorE": "#ffaf51",
"timelineCheckColor": "#ff715e",
"timelineCheckBorderColor": "rgba(255,113,94,0.4)",
"timelineItemBorderWidth": 1,
"timelineControlColor": "#ffaf51",
"timelineControlBorderColor": "#ffaf51",
"timelineControlBorderWidth": 0.5,
"timelineLabelColor": "#ff715e",
"datazoomBackgroundColor": "rgba(255,255,255,0)",
"datazoomDataColor": "rgba(222,222,222,1)",
"datazoomFillColor": "rgba(255,113,94,0.2)",
"datazoomHandleColor": "#cccccc",
"datazoomHandleWidth": "100",
"datazoomLabelColor": "#999999"
}
}
================================================
FILE: public/themes/infographic.json
================================================
{
"version": 1,
"themeName": "infographic",
"theme": {
"seriesCnt": "4",
"backgroundColor": "rgba(0,0,0,0)",
"titleColor": "#27727b",
"subtitleColor": "#aaa",
"textColorShow": false,
"textColor": "#333",
"markTextColor": "#eee",
"color": [
"#c1232b",
"#27727b",
"#fcce10",
"#e87c25",
"#b5c334",
"#fe8463",
"#9bca63",
"#fad860",
"#f3a43b",
"#60c0dd",
"#d7504b",
"#c6e579",
"#f4e001",
"#f0805a",
"#26c0c0"
],
"borderColor": "#ccc",
"borderWidth": 0,
"visualMapColor": [
"#c1232b",
"#fcce10"
],
"legendTextColor": "#333333",
"kColor": "#c1232b",
"kColor0": "#b5c334",
"kBorderColor": "#c1232b",
"kBorderColor0": "#b5c334",
"kBorderWidth": 1,
"lineWidth": "3",
"symbolSize": "5",
"symbol": "emptyCircle",
"symbolBorderWidth": 1,
"lineSmooth": false,
"graphLineWidth": 1,
"graphLineColor": "#aaa",
"mapLabelColor": "#c1232b",
"mapLabelColorE": "rgb(100,0,0)",
"mapBorderColor": "#eeeeee",
"mapBorderColorE": "#444",
"mapBorderWidth": 0.5,
"mapBorderWidthE": 1,
"mapAreaColor": "#dddddd",
"mapAreaColorE": "#fe994e",
"axes": [
{
"type": "all",
"name": "通用坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "category",
"name": "类目坐标轴",
"axisLineShow": true,
"axisLineColor": "#27727b",
"axisTickShow": true,
"axisTickColor": "#27727b",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": false,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "value",
"name": "数值坐标轴",
"axisLineShow": false,
"axisLineColor": "#333",
"axisTickShow": false,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "log",
"name": "对数坐标轴",
"axisLineShow": true,
"axisLineColor": "#27727b",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "time",
"name": "时间坐标轴",
"axisLineShow": true,
"axisLineColor": "#27727b",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
],
"axisSeperateSetting": true,
"toolboxColor": "#c1232b",
"toolboxEmphasisColor": "#e87c25",
"tooltipAxisColor": "#27727b",
"tooltipAxisWidth": 1,
"timelineLineColor": "#293c55",
"timelineLineWidth": 1,
"timelineItemColor": "#27727b",
"timelineItemColorE": "#72d4e0",
"timelineCheckColor": "#c1232b",
"timelineCheckBorderColor": "rgba(194,53,49, 0.5)",
"timelineItemBorderWidth": 1,
"timelineControlColor": "#27727b",
"timelineControlBorderColor": "#27727b",
"timelineControlBorderWidth": 0.5,
"timelineLabelColor": "#293c55",
"datazoomBackgroundColor": "rgba(0,0,0,0)",
"datazoomDataColor": "rgba(181,195,52,0.3)",
"datazoomFillColor": "rgba(181,195,52,0.2)",
"datazoomHandleColor": "#27727b",
"datazoomHandleWidth": "100",
"datazoomLabelColor": "#999999"
}
}
================================================
FILE: public/themes/macarons.json
================================================
{
"version": 1,
"themeName": "macarons",
"theme": {
"seriesCnt": "4",
"backgroundColor": "rgba(0,0,0,0)",
"titleColor": "#008acd",
"subtitleColor": "#aaa",
"textColorShow": false,
"textColor": "#333",
"markTextColor": "#eee",
"color": [
"#2ec7c9",
"#b6a2de",
"#5ab1ef",
"#ffb980",
"#d87a80",
"#8d98b3",
"#e5cf0d",
"#97b552",
"#95706d",
"#dc69aa",
"#07a2a4",
"#9a7fd1",
"#588dd5",
"#f5994e",
"#c05050",
"#59678c",
"#c9ab00",
"#7eb00a",
"#6f5553",
"#c14089"
],
"borderColor": "#ccc",
"borderWidth": 0,
"visualMapColor": [
"#5ab1ef",
"#e0ffff"
],
"legendTextColor": "#333333",
"kColor": "#d87a80",
"kColor0": "#2ec7c9",
"kBorderColor": "#d87a80",
"kBorderColor0": "#2ec7c9",
"kBorderWidth": 1,
"lineWidth": 2,
"symbolSize": 3,
"symbol": "emptyCircle",
"symbolBorderWidth": 1,
"lineSmooth": true,
"graphLineWidth": 1,
"graphLineColor": "#aaa",
"mapLabelColor": "#d87a80",
"mapLabelColorE": "rgb(100,0,0)",
"mapBorderColor": "#eeeeee",
"mapBorderColorE": "#444",
"mapBorderWidth": 0.5,
"mapBorderWidthE": 1,
"mapAreaColor": "#dddddd",
"mapAreaColorE": "rgba(254,153,78,1)",
"axes": [
{
"type": "all",
"name": "通用坐标轴",
"axisLineShow": true,
"axisLineColor": "#eeeeee",
"axisTickShow": true,
"axisTickColor": "#eeeeee",
"axisLabelShow": true,
"axisLabelColor": "#eeeeee",
"splitLineShow": true,
"splitLineColor": [
"#aaaaaa"
],
"splitAreaShow": false,
"splitAreaColor": [
"#eeeeee"
]
},
{
"type": "category",
"name": "类目坐标轴",
"axisLineShow": true,
"axisLineColor": "#008acd",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": false,
"splitLineColor": [
"#eee"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "value",
"name": "数值坐标轴",
"axisLineShow": true,
"axisLineColor": "#008acd",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#eee"
],
"splitAreaShow": true,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "log",
"name": "对数坐标轴",
"axisLineShow": true,
"axisLineColor": "#008acd",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#eee"
],
"splitAreaShow": true,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "time",
"name": "时间坐标轴",
"axisLineShow": true,
"axisLineColor": "#008acd",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#eee"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
],
"axisSeperateSetting": true,
"toolboxColor": "#2ec7c9",
"toolboxEmphasisColor": "#18a4a6",
"tooltipAxisColor": "#008acd",
"tooltipAxisWidth": "1",
"timelineLineColor": "#008acd",
"timelineLineWidth": 1,
"timelineItemColor": "#008acd",
"timelineItemColorE": "#a9334c",
"timelineCheckColor": "#2ec7c9",
"timelineCheckBorderColor": "rgba(46,199,201,0.4)",
"timelineItemBorderWidth": 1,
"timelineControlColor": "#008acd",
"timelineControlBorderColor": "#008acd",
"timelineControlBorderWidth": 0.5,
"timelineLabelColor": "#008acd",
"datazoomBackgroundColor": "rgba(47,69,84,0)",
"datazoomDataColor": "#efefff",
"datazoomFillColor": "rgba(182,162,222,0.2)",
"datazoomHandleColor": "#008acd",
"datazoomHandleWidth": "100",
"datazoomLabelColor": "#333333"
}
}
================================================
FILE: public/themes/purple-passion.json
================================================
{
"version": 1,
"themeName": "purple-passion",
"theme": {
"seriesCnt": "3",
"backgroundColor": "rgba(91,92,110,1)",
"titleColor": "#ffffff",
"subtitleColor": "#cccccc",
"textColorShow": false,
"textColor": "#333",
"markTextColor": "#eee",
"color": [
"#9b8bba",
"#e098c7",
"#8fd3e8",
"#71669e",
"#cc70af",
"#7cb4cc"
],
"borderColor": "#ccc",
"borderWidth": 0,
"visualMapColor": [
"#8a7ca8",
"#e098c7",
"#cceffa"
],
"legendTextColor": "#cccccc",
"kColor": "#e098c7",
"kColor0": "transparent",
"kBorderColor": "#e098c7",
"kBorderColor0": "#8fd3e8",
"kBorderWidth": "2",
"lineWidth": "3",
"symbolSize": "7",
"symbol": "circle",
"symbolBorderWidth": "2",
"lineSmooth": true,
"graphLineWidth": 1,
"graphLineColor": "#aaa",
"mapLabelColor": "#000",
"mapLabelColorE": "#ffffff",
"mapBorderColor": "#444",
"mapBorderColorE": "#444",
"mapBorderWidth": 0.5,
"mapBorderWidthE": 1,
"mapAreaColor": "#eee",
"mapAreaColorE": "#e098c7",
"axes": [
{
"type": "all",
"name": "通用坐标轴",
"axisLineShow": true,
"axisLineColor": "#cccccc",
"axisTickShow": false,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#cccccc",
"splitLineShow": false,
"splitLineColor": [
"#eeeeee",
"#333333"
],
"splitAreaShow": true,
"splitAreaColor": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
},
{
"type": "category",
"name": "类目坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": false,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "value",
"name": "数值坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "log",
"name": "对数坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "time",
"name": "时间坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
],
"axisSeperateSetting": false,
"toolboxColor": "#999",
"toolboxEmphasisColor": "#666",
"tooltipAxisColor": "#ccc",
"tooltipAxisWidth": 1,
"timelineLineColor": "#8fd3e8",
"timelineLineWidth": 1,
"timelineItemColor": "#8fd3e8",
"timelineItemColorE": "#8fd3e8",
"timelineCheckColor": "#8fd3e8",
"timelineCheckBorderColor": "rgba(138,124,168,0.37)",
"timelineItemBorderWidth": 1,
"timelineControlColor": "#8fd3e8",
"timelineControlBorderColor": "#8fd3e8",
"timelineControlBorderWidth": 0.5,
"timelineLabelColor": "#8fd3e8",
"datazoomBackgroundColor": "rgba(0,0,0,0)",
"datazoomDataColor": "rgba(255,255,255,0.3)",
"datazoomFillColor": "rgba(167,183,204,0.4)",
"datazoomHandleColor": "#a7b7cc",
"datazoomHandleWidth": "100",
"datazoomLabelColor": "#333"
}
}
================================================
FILE: public/themes/roma.json
================================================
{
"version": 1,
"themeName": "roma",
"theme": {
"seriesCnt": "4",
"backgroundColor": "rgba(0,0,0,0)",
"titleColor": "#333333",
"subtitleColor": "#aaa",
"textColorShow": false,
"textColor": "#333",
"markTextColor": "#eee",
"color": [
"#e01f54",
"#001852",
"#f5e8c8",
"#b8d2c7",
"#c6b38e",
"#a4d8c2",
"#f3d999",
"#d3758f",
"#dcc392",
"#2e4783",
"#82b6e9",
"#ff6347",
"#a092f1",
"#0a915d",
"#eaf889",
"#6699FF",
"#ff6666",
"#3cb371",
"#d5b158",
"#38b6b6"
],
"borderColor": "#ccc",
"borderWidth": 0,
"visualMapColor": [
"#e01f54",
"#e7dbc3"
],
"legendTextColor": "#333333",
"kColor": "#e01f54",
"kColor0": "#001852",
"kBorderColor": "#f5e8c8",
"kBorderColor0": "#b8d2c7",
"kBorderWidth": 1,
"lineWidth": 2,
"symbolSize": 4,
"symbol": "emptyCircle",
"symbolBorderWidth": 1,
"lineSmooth": false,
"graphLineWidth": 1,
"graphLineColor": "#aaa",
"mapLabelColor": "#000000",
"mapLabelColorE": "rgb(100,0,0)",
"mapBorderColor": "#444444",
"mapBorderColorE": "#444",
"mapBorderWidth": 0.5,
"mapBorderWidthE": 1,
"mapAreaColor": "#eeeeee",
"mapAreaColorE": "rgba(255,215,0,0.8)",
"axes": [
{
"type": "all",
"name": "通用坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "category",
"name": "类目坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": false,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "value",
"name": "数值坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "log",
"name": "对数坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "time",
"name": "时间坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
],
"axisSeperateSetting": true,
"toolboxColor": "#999999",
"toolboxEmphasisColor": "#666666",
"tooltipAxisColor": "#cccccc",
"tooltipAxisWidth": 1,
"timelineLineColor": "#293c55",
"timelineLineWidth": 1,
"timelineItemColor": "#293c55",
"timelineItemColorE": "#a9334c",
"timelineCheckColor": "#e43c59",
"timelineCheckBorderColor": "rgba(194,53,49,0.5)",
"timelineItemBorderWidth": 1,
"timelineControlColor": "#293c55",
"timelineControlBorderColor": "#293c55",
"timelineControlBorderWidth": 0.5,
"timelineLabelColor": "#293c55",
"datazoomBackgroundColor": "rgba(47,69,84,0)",
"datazoomDataColor": "rgba(47,69,84,0.3)",
"datazoomFillColor": "rgba(167,183,204,0.4)",
"datazoomHandleColor": "#a7b7cc",
"datazoomHandleWidth": "100",
"datazoomLabelColor": "#333333"
}
}
================================================
FILE: public/themes/shine.json
================================================
{
"version": 1,
"themeName": "shine",
"theme": {
"seriesCnt": 4,
"backgroundColor": "rgba(0,0,0,0)",
"titleColor": "#333333",
"subtitleColor": "#aaa",
"textColorShow": false,
"textColor": "#333",
"markTextColor": "#eee",
"color": [
"#c12e34","#e6b600","#0098d9","#2b821d",
"#005eaa","#339ca8","#cda819","#32a487"
],
"borderColor": "#ccc",
"borderWidth": 0,
"visualMapColor": [
"#1790cf",
"#a2d4e6"
],
"legendTextColor": "#333333",
"kColor": "#c12e34",
"kColor0": "#2b821d",
"kBorderColor": "#c12e34",
"kBorderColor0": "#2b821d",
"kBorderWidth": 1,
"lineWidth": 2,
"symbolSize": 4,
"symbol": "emptyCircle",
"symbolBorderWidth": 1,
"lineSmooth": false,
"graphLineWidth": 1,
"graphLineColor": "#aaa",
"mapLabelColor": "#c12e34",
"mapLabelColorE": "#c12e34",
"mapBorderColor": "#eee",
"mapBorderColorE": "#ddd",
"mapBorderWidth": 0.5,
"mapBorderWidthE": 1,
"mapAreaColor": "#ddd",
"mapAreaColorE": "#e6b600",
"axes": [
{
"type": "all",
"name": "通用坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "category",
"name": "类目坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": false,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "value",
"name": "数值坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "log",
"name": "对数坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "time",
"name": "时间坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
],
"axisSeperateSetting": true,
"toolboxColor": "#06467c",
"toolboxEmphasisColor": "#4187c2",
"tooltipAxisColor": "#cccccc",
"tooltipAxisWidth": 1,
"timelineLineColor": "#005eaa",
"timelineLineWidth": 1,
"timelineItemColor": "#005eaa",
"timelineItemColorE": "#005eaa",
"timelineCheckColor": "#005eaa",
"timelineCheckBorderColor": "rgba(49,107,194,0.5)",
"timelineItemBorderWidth": 1,
"timelineControlColor": "#005eaa",
"timelineControlBorderColor": "#005eaa",
"timelineControlBorderWidth": 0.5,
"timelineLabelColor": "#005eaa",
"datazoomBackgroundColor": "rgba(47,69,84,0)",
"datazoomDataColor": "rgba(47,69,84,0.3)",
"datazoomFillColor": "rgba(167,183,204,0.4)",
"datazoomHandleColor": "#a7b7cc",
"datazoomHandleWidth": "100",
"datazoomLabelColor": "#333333"
}
}
================================================
FILE: public/themes/v5.json
================================================
{
"version": 1,
"themeName": "v5",
"theme": {
"seriesCnt": 3,
"backgroundColor": "rgba(0, 0, 0, 0)",
"titleColor": "#464646",
"subtitleColor": "#6E7079",
"textColorShow": false,
"textColor": "#333",
"markTextColor": "#eee",
"color": [
"#5470c6",
"#91cc75",
"#fac858",
"#ee6666",
"#73c0de",
"#3ba272",
"#fc8452",
"#9a60b4",
"#ea7ccc"
],
"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": [
{
"type": "all",
"name": "General Axis",
"axisLineShow": true,
"axisLineColor": "#6E7079",
"axisTickShow": true,
"axisTickColor": "#6E7079",
"axisLabelShow": true,
"axisLabelColor": "#6E7079",
"splitLineShow": true,
"splitLineColor": [
"#E0E6F1"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.2)",
"rgba(210,219,238,0.2)"
]
},
{
"type": "category",
"name": "Category Axis",
"axisLineShow": true,
"axisLineColor": "#6E7079",
"axisTickShow": true,
"axisTickColor": "#6E7079",
"axisLabelShow": true,
"axisLabelColor": "#6E7079",
"splitLineShow": false,
"splitLineColor": [
"#E0E6F1"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.2)",
"rgba(210,219,238,0.2)"
]
},
{
"type": "value",
"name": "Value Axis",
"axisLineShow": false,
"axisLineColor": "#6E7079",
"axisTickShow": false,
"axisTickColor": "#6E7079",
"axisLabelShow": true,
"axisLabelColor": "#6E7079",
"splitLineShow": true,
"splitLineColor": [
"#E0E6F1"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.2)",
"rgba(210,219,238,0.2)"
]
},
{
"type": "log",
"name": "Log Axis",
"axisLineShow": false,
"axisLineColor": "#6E7079",
"axisTickShow": false,
"axisTickColor": "#6E7079",
"axisLabelShow": true,
"axisLabelColor": "#6E7079",
"splitLineShow": true,
"splitLineColor": [
"#E0E6F1"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.2)",
"rgba(210,219,238,0.2)"
]
},
{
"type": "time",
"name": "Time Axis",
"axisLineShow": true,
"axisLineColor": "#6E7079",
"axisTickShow": true,
"axisTickColor": "#6E7079",
"axisLabelShow": true,
"axisLabelColor": "#6E7079",
"splitLineShow": false,
"splitLineColor": [
"#E0E6F1"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.2)",
"rgba(210,219,238,0.2)"
]
}
],
"axisSeperateSetting": true,
"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": "10%",
"gridTop": 60,
"gridBottom": 70,
"gridRight": "10%",
"legendLeft": "center",
"legendRight": "",
"legendTop": 0,
"legendBottom": ""
}
}
================================================
FILE: public/themes/vintage.json
================================================
{
"version": 1,
"themeName": "vintage",
"theme": {
"seriesCnt": 4,
"backgroundColor": "rgba(254,248,239,1)",
"titleColor": "#333333",
"subtitleColor": "#aaa",
"textColorShow": false,
"textColor": "#333",
"markTextColor": "#eee",
"color": [
"#d87c7c",
"#919e8b",
"#d7ab82",
"#6e7074",
"#61a0a8",
"#efa18d",
"#787464",
"#cc7e63",
"#724e58",
"#4b565b"
],
"borderColor": "#ccc",
"borderWidth": 0,
"visualMapColor": [
"#bf444c",
"#d88273",
"#f6efa6"
],
"legendTextColor": "#333333",
"kColor": "#c23531",
"kColor0": "#314656",
"kBorderColor": "#c23531",
"kBorderColor0": "#314656",
"kBorderWidth": 1,
"lineWidth": 2,
"symbolSize": 4,
"symbol": "emptyCircle",
"symbolBorderWidth": 1,
"lineSmooth": false,
"graphLineWidth": 1,
"graphLineColor": "#aaa",
"mapLabelColor": "#000000",
"mapLabelColorE": "rgb(100,0,0)",
"mapBorderColor": "#444444",
"mapBorderColorE": "#444444",
"mapBorderWidth": 0.5,
"mapBorderWidthE": 1,
"mapAreaColor": "#eeeeee",
"mapAreaColorE": "rgba(255,215,0,0.8)",
"axes": [
{
"type": "all",
"name": "通用坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "category",
"name": "类目坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": false,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "value",
"name": "数值坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "log",
"name": "对数坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "time",
"name": "时间坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
],
"axisSeperateSetting": true,
"toolboxColor": "#999999",
"toolboxEmphasisColor": "#666666",
"tooltipAxisColor": "#cccccc",
"tooltipAxisWidth": 1,
"timelineLineColor": "#293c55",
"timelineLineWidth": 1,
"timelineItemColor": "#293c55",
"timelineItemColorE": "#a9334c",
"timelineCheckColor": "#e43c59",
"timelineCheckBorderColor": "rgba(194,53,49,0.5)",
"timelineItemBorderWidth": 1,
"timelineControlColor": "#293c55",
"timelineControlBorderColor": "#293c55",
"timelineControlBorderWidth": 0.5,
"timelineLabelColor": "#293c55",
"datazoomBackgroundColor": "rgba(47,69,84,0)",
"datazoomDataColor": "rgba(47,69,84,0.3)",
"datazoomFillColor": "rgba(167,183,204,0.4)",
"datazoomHandleColor": "#a7b7cc",
"datazoomHandleWidth": "100",
"datazoomLabelColor": "#333333"
}
}
================================================
FILE: public/themes/walden.json
================================================
{
"version": 1,
"themeName": "walden",
"theme": {
"seriesCnt": "3",
"backgroundColor": "rgba(252,252,252,0)",
"titleColor": "#666666",
"subtitleColor": "#999999",
"textColorShow": false,
"textColor": "#333",
"markTextColor": "#ffffff",
"color": [
"#3fb1e3",
"#6be6c1",
"#626c91",
"#a0a7e6",
"#c4ebad",
"#96dee8"
],
"borderColor": "#ccc",
"borderWidth": 0,
"visualMapColor": [
"#2a99c9",
"#afe8ff"
],
"legendTextColor": "#999999",
"kColor": "#e6a0d2",
"kColor0": "transparent",
"kBorderColor": "#e6a0d2",
"kBorderColor0": "#3fb1e3",
"kBorderWidth": "2",
"lineWidth": "3",
"symbolSize": "8",
"symbol": "emptyCircle",
"symbolBorderWidth": "2",
"lineSmooth": false,
"graphLineWidth": "1",
"graphLineColor": "#cccccc",
"mapLabelColor": "#ffffff",
"mapLabelColorE": "#3fb1e3",
"mapBorderColor": "#aaaaaa",
"mapBorderColorE": "#3fb1e3",
"mapBorderWidth": 0.5,
"mapBorderWidthE": 1,
"mapAreaColor": "#eeeeee",
"mapAreaColorE": "rgba(63,177,227,0.25)",
"axes": [
{
"type": "all",
"name": "通用坐标轴",
"axisLineShow": true,
"axisLineColor": "#cccccc",
"axisTickShow": false,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#999999",
"splitLineShow": true,
"splitLineColor": [
"#eeeeee"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
},
{
"type": "category",
"name": "类目坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": false,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "value",
"name": "数值坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "log",
"name": "对数坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "time",
"name": "时间坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
],
"axisSeperateSetting": false,
"toolboxColor": "#999999",
"toolboxEmphasisColor": "#666666",
"tooltipAxisColor": "#cccccc",
"tooltipAxisWidth": 1,
"timelineLineColor": "#626c91",
"timelineLineWidth": 1,
"timelineItemColor": "#626c91",
"timelineItemColorE": "#626c91",
"timelineCheckColor": "#3fb1e3",
"timelineCheckBorderColor": "rgba(63,177,227,0.15)",
"timelineItemBorderWidth": 1,
"timelineControlColor": "#626c91",
"timelineControlBorderColor": "#626c91",
"timelineControlBorderWidth": 0.5,
"timelineLabelColor": "#626c91",
"datazoomBackgroundColor": "rgba(255,255,255,0)",
"datazoomDataColor": "rgba(222,222,222,1)",
"datazoomFillColor": "rgba(114,230,212,0.25)",
"datazoomHandleColor": "#cccccc",
"datazoomHandleWidth": "100",
"datazoomLabelColor": "#999999"
}
}
================================================
FILE: public/themes/westeros.json
================================================
{
"version": 1,
"themeName": "westeros",
"theme": {
"seriesCnt": "4",
"backgroundColor": "rgba(0,0,0,0)",
"titleColor": "#516b91",
"subtitleColor": "#93b7e3",
"textColorShow": false,
"textColor": "#333",
"markTextColor": "#eee",
"color": [
"#516b91",
"#59c4e6",
"#edafda",
"#93b7e3",
"#a5e7f0",
"#cbb0e3"
],
"borderColor": "#ccc",
"borderWidth": 0,
"visualMapColor": [
"#516b91",
"#59c4e6",
"#a5e7f0"
],
"legendTextColor": "#999999",
"kColor": "#edafda",
"kColor0": "transparent",
"kBorderColor": "#d680bc",
"kBorderColor0": "#8fd3e8",
"kBorderWidth": "2",
"lineWidth": "2",
"symbolSize": "6",
"symbol": "emptyCircle",
"symbolBorderWidth": "2",
"lineSmooth": true,
"graphLineWidth": 1,
"graphLineColor": "#aaa",
"mapLabelColor": "#000",
"mapLabelColorE": "#516b91",
"mapBorderColor": "#516b91",
"mapBorderColorE": "#516b91",
"mapBorderWidth": 0.5,
"mapBorderWidthE": 1,
"mapAreaColor": "#f3f3f3",
"mapAreaColorE": "#a5e7f0",
"axes": [
{
"type": "all",
"name": "通用坐标轴",
"axisLineShow": true,
"axisLineColor": "#cccccc",
"axisTickShow": false,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#999999",
"splitLineShow": true,
"splitLineColor": [
"#eeeeee"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
},
{
"type": "category",
"name": "类目坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": false,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "value",
"name": "数值坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "log",
"name": "对数坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "time",
"name": "时间坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
],
"axisSeperateSetting": false,
"toolboxColor": "#999",
"toolboxEmphasisColor": "#666",
"tooltipAxisColor": "#ccc",
"tooltipAxisWidth": 1,
"timelineLineColor": "#8fd3e8",
"timelineLineWidth": 1,
"timelineItemColor": "#8fd3e8",
"timelineItemColorE": "#8fd3e8",
"timelineCheckColor": "#8fd3e8",
"timelineCheckBorderColor": "rgba(138,124,168,0.37)",
"timelineItemBorderWidth": 1,
"timelineControlColor": "#8fd3e8",
"timelineControlBorderColor": "#8fd3e8",
"timelineControlBorderWidth": 0.5,
"timelineLabelColor": "#8fd3e8",
"datazoomBackgroundColor": "rgba(0,0,0,0)",
"datazoomDataColor": "rgba(255,255,255,0.3)",
"datazoomFillColor": "rgba(167,183,204,0.4)",
"datazoomHandleColor": "#a7b7cc",
"datazoomHandleWidth": "100",
"datazoomLabelColor": "#333"
}
}
================================================
FILE: public/themes/wonderland.json
================================================
{
"version": 1,
"themeName": "wonderland",
"theme": {
"seriesCnt": "3",
"backgroundColor": "rgba(255,255,255,0)",
"titleColor": "#666666",
"subtitleColor": "#999999",
"textColorShow": false,
"textColor": "#333",
"markTextColor": "#ffffff",
"color": [
"#4ea397",
"#22c3aa",
"#7bd9a5",
"#d0648a",
"#f58db2",
"#f2b3c9"
],
"borderColor": "#ccc",
"borderWidth": 0,
"visualMapColor": [
"#d0648a",
"#22c3aa",
"#adfff1"
],
"legendTextColor": "#999999",
"kColor": "#d0648a",
"kColor0": "transparent",
"kBorderColor": "#d0648a",
"kBorderColor0": "#22c3aa",
"kBorderWidth": "1",
"lineWidth": "3",
"symbolSize": "8",
"symbol": "emptyCircle",
"symbolBorderWidth": "2",
"lineSmooth": false,
"graphLineWidth": "1",
"graphLineColor": "#cccccc",
"mapLabelColor": "#28544e",
"mapLabelColorE": "#349e8e",
"mapBorderColor": "#999999",
"mapBorderColorE": "#22c3aa",
"mapBorderWidth": 0.5,
"mapBorderWidthE": 1,
"mapAreaColor": "#eeeeee",
"mapAreaColorE": "rgba(34,195,170,0.25)",
"axes": [
{
"type": "all",
"name": "通用坐标轴",
"axisLineShow": true,
"axisLineColor": "#cccccc",
"axisTickShow": false,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#999999",
"splitLineShow": true,
"splitLineColor": [
"#eeeeee"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
},
{
"type": "category",
"name": "类目坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": false,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "value",
"name": "数值坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "log",
"name": "对数坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "time",
"name": "时间坐标轴",
"axisLineShow": true,
"axisLineColor": "#333",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#ccc"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
],
"axisSeperateSetting": false,
"toolboxColor": "#999999",
"toolboxEmphasisColor": "#666666",
"tooltipAxisColor": "#cccccc",
"tooltipAxisWidth": 1,
"timelineLineColor": "#4ea397",
"timelineLineWidth": 1,
"timelineItemColor": "#4ea397",
"timelineItemColorE": "#4ea397",
"timelineCheckColor": "#4ea397",
"timelineCheckBorderColor": "rgba(60,235,210,0.3)",
"timelineItemBorderWidth": 1,
"timelineControlColor": "#4ea397",
"timelineControlBorderColor": "#4ea397",
"timelineControlBorderWidth": 0.5,
"timelineLabelColor": "#4ea397",
"datazoomBackgroundColor": "rgba(255,255,255,0)",
"datazoomDataColor": "rgba(222,222,222,1)",
"datazoomFillColor": "rgba(114,230,212,0.25)",
"datazoomHandleColor": "#cccccc",
"datazoomHandleWidth": "100",
"datazoomLabelColor": "#999999"
}
}
================================================
FILE: scripts/release.js
================================================
import fs from 'fs-extra';
import path from 'path';
import { fileURLToPath } from 'url';
import config from '../config/env.asf.js';
// Get __dirname equivalent in ESM
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const appDir = path.resolve(__dirname, '../app');
const releaseDestDir = config.releaseDestDir;
const ecWWWGeneratedDir = config.ecWWWGeneratedDir;
const languages = ['en', 'zh'];
console.log('Releasing theme builder...');
// Make sure directories exist
languages.forEach(lang => {
fs.ensureDirSync(path.join(ecWWWGeneratedDir, lang, 'theme-builder'));
fs.ensureDirSync(path.join(releaseDestDir, lang, 'theme-builder'));
});
// Clean up existing JS and CSS files in the releaseDestDir
console.log('Cleaning up old CSS and JS files...');
languages.forEach(lang => {
const themeBuilderDir = path.join(releaseDestDir, lang, 'theme-builder');
if (fs.existsSync(themeBuilderDir)) {
const files = fs.readdirSync(themeBuilderDir);
files.forEach(file => {
if (file.endsWith('.js') || file.endsWith('.css')) {
const filePath = path.join(themeBuilderDir, file);
console.log(`Removing: ${filePath}`);
fs.removeSync(filePath);
}
});
}
});
// Move app.html files to echarts-www body.html
console.log('Moving app.html files to echarts-www body.html...');
languages.forEach(lang => {
fs.copySync(
path.join(appDir, 'app.html'),
path.join(ecWWWGeneratedDir, lang, 'theme-builder', 'body.html')
);
});
// Move theme-builder files to website
console.log('Moving theme-builder files to echarts-website...');
languages.forEach(lang => {
fs.copySync(
path.join(appDir, 'theme-builder'),
path.join(releaseDestDir, lang, 'theme-builder'),
{ overwrite: true }
);
});
console.log('Release completed successfully!');
================================================
FILE: src/App.vue
================================================
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue'
// Simple fixed sidebar layout without responsive design
import ChartPreviewPanel from './components/ChartPreviewPanel.vue'
import ThemePanel from './components/ThemePanel.vue'
import { useLocalization } from './composables/useLocalization'
import { RadioGroup as VanRadioGroup, Radio as VanRadio, Row as VanRow, Col as VanCol } from 'vant'
// Set up language control
const { switchLanguage, currentLanguage, availableLanguages } = useLocalization()
const currentLang = ref(currentLanguage)
// Only show language selector in dev/preview mode
// Use import.meta.env.DEV to only show in development mode
const showLanguageSelector = import.meta.env.VITE_SHOW_LANGUAGE_SELECTOR
const onLanguageChange = (lang: string) => {
switchLanguage(lang)
}
// Get reference to chart preview panel
const chartPreviewRef = useTemplateRef('chartPreviewRef')
</script>
<template>
<div class="theme-builder-body">
<!-- Language Selector - only shown in dev/preview mode -->
<div v-if="showLanguageSelector" class="language-selector">
<VanRadioGroup v-model="currentLang" direction="horizontal" @change="onLanguageChange">
<VanRadio v-for="locale in availableLanguages" :name="locale.code" :key="locale.code">
{{ locale.name }}
</VanRadio>
</VanRadioGroup>
</div>
<div class="container-fluid" id="content">
<VanRow class="row-container" :gutter="0">
<VanCol span="6" class="theme-config">
<ThemePanel :chart-preview-ref="chartPreviewRef" />
</VanCol>
<VanCol span="18" class="chart-container">
<ChartPreviewPanel ref="chartPreviewRef" />
</VanCol>
</VanRow>
</div>
</div>
</template>
<style scoped>
.theme-builder-body {
width: 100%;
height: 100%;
position: relative;
--van-button-default-height: auto;
--van-button-normal-padding: 8px 10px;
}
.language-selector {
position: absolute;
top: 10px;
right: 20px;
z-index: 1000;
background-color: rgba(255, 255, 255, 0.9);
padding: 15px;
}
.container-fluid {
height: 100%;
padding: 0;
width: 100%;
}
.row-container {
height: 100%;
display: flex !important;
flex-direction: row !important;
}
.theme-config {
height: 100%;
overflow-y: auto;
background-color: #ffffff;
border-right: 1px solid #ddd;
border-bottom: 1px solid #ddd;
padding: 0;
box-sizing: border-box;
flex: 0 0 25%; /* Fixed 25% width */
}
.chart-container {
height: 100%;
overflow: hidden;
background-color: #ffffff;
padding: 20px;
box-sizing: border-box;
flex: 1; /* Take remaining space */
}
.placeholder {
padding: 20px;
text-align: center;
color: #6c757d;
border: 2px dashed #dee2e6;
border-radius: 4px;
font-size: 16px;
}
</style>
================================================
FILE: src/components/ChartPreviewPanel.vue
================================================
<template>
<div class="chart-preview">
<div class="preview-header">
<h3>{{ $t('preview.chartPreview') }}</h3>
</div>
<div class="charts-grid">
<div
v-for="(config, index) in displayedCharts"
:key="config.type + index"
class="chart-item"
>
<div
:ref="el => setChartDomRef(el, index)"
class="chart-container"
></div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, watch, computed, markRaw, nextTick } from 'vue'
import * as echarts from 'echarts'
import { getChartConfigs } from '../utils/chartConfigs'
import { useThemeStore } from '../stores/theme'
import type { ECharts } from 'echarts'
import { useI18n } from 'vue-i18n'
// Debounce function to limit how often a function can be called
function debounce<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => void {
let timer: ReturnType<typeof setTimeout> | null = null
return function(this: any, ...args: Parameters<T>) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, delay)
}
}
// Initialize i18n
const { locale } = useI18n()
const themeStore = useThemeStore()
const chartInstances: ECharts[] = []
const chartDoms: (HTMLElement | null)[] = []
// Dynamically generate charts based on seriesCnt
const displayedCharts = computed(() => getChartConfigs(themeStore.theme.seriesCnt))
// Set chart DOM ref
function setChartDomRef(el: any, index: number) {
chartDoms[index] = el as HTMLElement
}
// Original update charts implementation
function _updateChartsImpl() {
// Dispose existing charts
chartInstances.forEach(chart => chart.dispose())
chartInstances.length = 0
// re-register theme with the same name
const currentTheme = themeStore.getEChartsTheme()
const themeId = 'customized'
echarts.registerTheme(themeId, currentTheme)
const chartLocale = locale.value === 'zh' ? 'ZH' : 'EN'
// Recreate charts with new theme
nextTick(() => {
displayedCharts.value.forEach((config, index) => {
const container = chartDoms[index]
const chart = markRaw(echarts.init(container, themeId, {
locale: chartLocale
}))
chart.setOption(config.option)
chartInstances[index] = chart
})
})
}
const updateCharts = debounce(_updateChartsImpl, 100)
// Expose updateCharts method for external calling
defineExpose({
updateCharts
})
// Watch for theme or locale changes and automatically update charts
watch(() => [themeStore.theme, locale.value], updateCharts, { deep: true })
// Resize charts when window resizes
function handleResize() {
chartInstances.forEach(chart => chart.resize())
}
const debouncedHandleResize = debounce(handleResize, 100)
onMounted(() => {
updateCharts()
window.addEventListener('resize', debouncedHandleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', debouncedHandleResize)
chartInstances.forEach(chart => chart.dispose())
chartDoms.length = chartInstances.length = 0
})
</script>
<style scoped>
.chart-preview {
height: 100%;
display: flex;
flex-direction: column;
}
.preview-header {
display: flex;
justify-content: flex-start;
align-items: center;
margin-bottom: 20px;
}
.preview-header h3 {
margin: 0;
color: #333;
font-size: 24px;
}
.charts-grid {
flex: 1;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 12px;
overflow-y: auto;
padding-right: 8px;
}
.chart-item {
background: #fff;
border: 1px solid #e9ecef;
border-radius: 8px;
}
.chart-container {
width: 100%;
height: 320px;
border-radius: 4px;
}
/* Responsive adjustments */
@media (max-width: 1400px) {
.charts-grid {
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
}
}
@media (max-width: 1200px) {
.charts-grid {
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}
}
@media (max-width: 768px) {
.charts-grid {
grid-template-columns: 1fr;
}
}
</style>
================================================
FILE: src/components/ColorList.vue
================================================
<template>
<van-field :label="label">
<template #input>
<van-checkbox v-if="canDisable" :model-value="enabled" @update:model-value="$emit('update:enabled', $event)" style="margin-right: 8px;" />
<div class="color-list-wrapper" v-show="!canDisable || enabled">
<div class="color-items">
<div
v-for="(color, index) in modelValue"
:key="index"
class="color-item"
>
<ColorPicker
:pureColor="color"
@pureColorChange="(newColor: string) => updateColor(index, newColor)"
format="hex"
:pickerType="'fk'"
:shape="'square'"
:size="24"
/>
<van-field
:model-value="color"
@update:model-value="(value: string) => updateColorText(index, value)"
placeholder="#000000"
class="color-text"
/>
<van-button
v-if="modelValue.length > 1"
type="danger"
size="mini"
@click="removeColor(index)"
icon="cross"
plain
/>
</div>
</div>
<div class="color-actions">
<van-button
type="primary"
size="small"
@click="addColor"
icon="plus"
>
{{ $t('common.add') }}
</van-button>
<van-button
v-if="modelValue.length > 1"
size="small"
@click="removeLastColor"
>
{{ $t('common.reduce') }}
</van-button>
</div>
</div>
</template>
</van-field>
</template>
<script setup lang="ts">
import { ColorPicker } from 'vue3-colorpicker'
import 'vue3-colorpicker/style.css'
interface Props {
modelValue: string[]
label: string
canDisable?: boolean
enabled?: boolean
}
interface Emits {
(e: 'update:modelValue', value: string[]): void
(e: 'update:enabled', value: boolean): void
}
const props = withDefaults(defineProps<Props>(), {
canDisable: false,
enabled: true
})
const emit = defineEmits<Emits>()
const updateColor = (index: number, color: string) => {
const newColors = [...props.modelValue]
newColors[index] = color
emit('update:modelValue', newColors)
}
const updateColorText = (index: number, value: string) => {
// Validate color format
const colorRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$|^rgba?\([^)]+\)$|^[a-zA-Z]+$/
if (colorRegex.test(value) || value === '') {
const newColors = [...props.modelValue]
newColors[index] = value
emit('update:modelValue', newColors)
}
}
const addColor = () => {
const newColors = [...props.modelValue, '#333333']
emit('update:modelValue', newColors)
}
const removeColor = (index: number) => {
if (props.modelValue.length > 1) {
const newColors = props.modelValue.filter((_, i) => i !== index)
emit('update:modelValue', newColors)
}
}
const removeLastColor = () => {
if (props.modelValue.length > 1) {
const newColors = [...props.modelValue]
newColors.pop()
emit('update:modelValue', newColors)
}
}
</script>
<style scoped>
.color-list-wrapper {
width: 100%;
}
.color-items {
margin-bottom: 12px;
}
.color-item {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.color-text {
flex: 1;
min-width: 0;
}
.color-actions {
display: flex;
gap: 8px;
}
:deep(.van-field__control) {
display: flex;
align-items: flex-start;
}
:deep(.van-button--mini) {
padding: 4px;
min-width: 32px;
}
:deep(.vc-color-wrap) {
border-radius: 4px;
border: 1px solid #dcdee0;
flex-shrink: 0;
}
</style>
================================================
FILE: src/components/ColorPicker.vue
================================================
<template>
<van-field :label="label">
<template #input>
<van-checkbox v-if="canDisable" :model-value="enabled" @update:model-value="$emit('update:enabled', $event)" style="margin-right: 8px;" />
<div class="color-picker-wrapper" v-show="!canDisable || enabled">
<ColorPicker
:pureColor="modelValue"
@pureColorChange="handleColorChange"
format="hex"
:pickerType="'fk'"
:shape="'square'"
:size="32"
/>
<van-field
:model-value="modelValue"
@update:model-value="handleTextChange"
placeholder="#000000"
class="color-text"
/>
</div>
</template>
</van-field>
</template>
<script setup lang="ts">
import { ColorPicker } from 'vue3-colorpicker'
import 'vue3-colorpicker/style.css'
interface Props {
modelValue: string
label: string
canDisable?: boolean
enabled?: boolean
}
interface Emits {
(e: 'update:modelValue', value: string): void
(e: 'update:enabled', value: boolean): void
}
withDefaults(defineProps<Props>(), {
canDisable: false,
enabled: true
})
const emit = defineEmits<Emits>()
const handleColorChange = (color: string) => {
emit('update:modelValue', color)
}
const handleTextChange = (value: string) => {
// Validate color format
const colorRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$|^rgba?\([^)]+\)$|^[a-zA-Z]+$/
if (colorRegex.test(value) || value === '') {
emit('update:modelValue', value)
}
}
</script>
<style scoped>
.color-picker-wrapper {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
}
.color-text {
flex: 1;
}
:deep(.van-field__control) {
display: flex;
align-items: center;
}
:deep(.vc-color-wrap) {
border-radius: 4px;
border: 1px solid #dcdee0;
}
</style>
================================================
FILE: src/components/ThemePanel.vue
================================================
<template>
<div class="theme-panel">
<!-- Functions Section -->
<van-collapse v-model="activeNames">
<van-collapse-item :title="$t('panel.functions')" name="functions">
<div class="panel-content">
<!-- Action Buttons -->
<div class="action-buttons">
<van-button type="primary" @click="downloadTheme">
<van-icon name="down" />
{{ $t('panel.download') }}
</van-button>
<van-button @click="importConfig">
<van-icon name="upgrade" />
{{ $t('panel.import') }}
</van-button>
<van-button @click="exportConfig">
<van-icon name="share" />
{{ $t('panel.export') }}
</van-button>
<!-- showThemeCode 按钮已删除 -->
</div>
<div class="action-buttons">
<van-button @click="refreshCharts">
<van-icon name="replay" />
{{ $t('common.refresh') }}
</van-button>
<van-button @click="resetTheme">
<van-icon name="revoke" />
{{ $t('common.reset') }}
</van-button>
</div>
<div class="action-buttons">
<van-button @click="showHelp">
<van-icon name="info-o" />
{{ $t('common.help') }}
</van-button>
<van-button @click="openSourceCode">
<van-icon name="link-o" />
{{ $t('panel.sourceCode') }}
</van-button>
</div>
<!-- Theme Name and Series Count -->
<van-field
v-model.trim="themeName"
:label="$t('panel.themeName')"
:placeholder="$t('panel.themePlaceholder')"
/>
<van-field
v-model="theme.seriesCnt"
type="number"
inputmode="numeric"
:min="1"
:max="12"
:label="$t('panel.seriesCount')"
:placeholder="$t('panel.seriesPlaceholder')"
/>
<!-- Predefined Themes -->
<div class="predefined-themes">
<h4>{{ $t('panel.preDefinedThemes') }}</h4>
<div class="theme-grid">
<div
v-for="(themeItem, index) in preDefinedThemes"
:key="themeItem.name"
class="theme-item"
:class="themeStore.activePreDefinedThemeIndex.value === index ? 'active' : ''"
:style="{ backgroundColor: themeItem.background }"
:title="themeItem.name"
@click="selectPreDefinedTheme(index)"
>
<div
v-for="color in themeItem.theme"
:key="color"
class="color-dot"
:style="{ backgroundColor: color }"
/>
</div>
<div
class="theme-item new-theme"
:title="$t('panel.extractFromImageTitle')"
@click="uploadImageInputRef?.click()"
>
<van-icon name="plus" />
<span>{{ $t('panel.extractFromImage') }}</span>
</div>
</div>
</div>
</div>
</van-collapse-item>
<!-- Basic Configuration -->
<van-collapse-item :title="$t('panel.basicConfig')" name="basic">
<div class="panel-content">
<ColorPicker
v-model="theme.backgroundColor"
:label="$t('colors.background')"
/>
<ColorPicker
v-model="theme.titleColor"
:label="$t('colors.title')"
/>
<ColorPicker
v-model="theme.subtitleColor"
:label="$t('colors.subtitle')"
/>
<ColorList
v-model="theme.color"
:label="$t('colors.theme')"
/>
<ColorPicker
v-model="theme.markTextColor"
:label="$t('colors.markText')"
/>
<van-field
v-model.number="theme.borderWidth"
type="number"
:label="$t('colors.borderWidth')"
/>
<ColorPicker
v-model="theme.borderColor"
:label="$t('colors.border')"
/>
</div>
</van-collapse-item>
<!-- Visual Map -->
<van-collapse-item :title="$t('panel.visualMap')" name="visualMap">
<div class="panel-content">
<ColorList
v-model="theme.visualMapColor"
:label="$t('colors.visualMapColor')"
/>
</div>
</van-collapse-item>
<!-- Grid Layout -->
<van-collapse-item :title="$t('panel.grid')" name="grid">
<div class="panel-content">
<van-field
v-model="gridLeft"
:label="$t('position.left')"
@blur="validateGridValue('left')"
/>
<van-field
v-model="gridRight"
:label="$t('position.right')"
@blur="validateGridValue('right')"
/>
<van-field
v-model="gridTop"
:label="$t('position.top')"
@blur="validateGridValue('top')"
/>
<van-field
v-model="gridBottom"
:label="$t('position.bottom')"
@blur="validateGridValue('bottom')"
/>
</div>
</van-collapse-item>
<!-- Coordinate Axis -->
<van-collapse-item :title="$t('panel.axis')" name="axis">
<div class="panel-content">
<van-field :label="$t('panel.axis')">
<template #input>
<van-checkbox v-model="theme.axisSeperateSetting" @change="onAxisSettingChange">
{{ $t('panel.separateAxisSetting') }}
</van-checkbox>
</template>
</van-field>
<div
v-for="(axis, index) in theme.axis"
:key="index"
class="axis-group"
>
<h4 v-if="axis.type !== 'all'">{{ $t(`axis.${axis.type}Axis`) }}</h4>
<ColorPicker
v-model="axis.axisLineColor"
:label="$t('colors.axisLine')"
:can-disable="true"
v-model:enabled="axis.axisLineShow"
/>
<ColorPicker
v-model="axis.axisTickColor"
:label="$t('colors.axisTick')"
:can-disable="true"
v-model:enabled="axis.axisTickShow"
/>
<ColorList
v-model="axis.splitLineColor"
:label="$t('colors.splitLine')"
:can-disable="true"
v-model:enabled="axis.splitLineShow"
/>
<ColorList
v-model="axis.splitAreaColor"
:label="$t('colors.splitArea')"
:can-disable="true"
v-model:enabled="axis.splitAreaShow"
/>
<ColorPicker
v-model="axis.axisLabelColor"
:label="$t('colors.axisLabel')"
:can-disable="true"
v-model:enabled="axis.axisLabelShow"
/>
</div>
</div>
</van-collapse-item>
<!-- Legend -->
<van-collapse-item :title="$t('panel.legend')" name="legend">
<div class="panel-content">
<ColorPicker
v-model="theme.legendTextColor"
:label="$t('colors.legendText')"
/>
<h4>{{ $t('panel.legendPosition') }}</h4>
<van-field
v-model="legendLeft"
:label="$t('position.left')"
@blur="validateLegendValue('left')"
/>
<van-field
v-model="legendRight"
:label="$t('position.right')"
@blur="validateLegendValue('right')"
/>
<van-field
v-model="legendTop"
:label="$t('position.top')"
@blur="validateLegendValue('top')"
/>
<van-field
v-model="legendBottom"
:label="$t('position.bottom')"
@blur="validateLegendValue('bottom')"
/>
</div>
</van-collapse-item>
<!-- Toolbox -->
<van-collapse-item :title="$t('panel.toolbox')" name="toolbox">
<div class="panel-content">
<ColorPicker
v-model="theme.toolboxColor"
:label="$t('colors.toolbox')"
/>
<ColorPicker
v-model="theme.toolboxEmphasisColor"
:label="$t('colors.toolboxEmphasis')"
/>
</div>
</van-collapse-item>
<!-- Tooltip -->
<van-collapse-item :title="$t('panel.tooltip')" name="tooltip">
<div class="panel-content">
<ColorPicker
v-model="theme.tooltipAxisColor"
:label="$t('tooltip.axisPointer')"
/>
<van-field
v-model.number="theme.tooltipAxisWidth"
type="number"
:label="$t('tooltip.axisPointerWidth')"
/>
</div>
</van-collapse-item>
<!-- Timeline -->
<van-collapse-item :title="$t('panel.timeline')" name="timeline">
<div class="panel-content">
<ColorPicker
v-model="theme.timelineItemColor"
:label="$t('timeline.item')"
/>
<ColorPicker
v-model="theme.timelineItemColorE"
:label="$t('timeline.itemHover')"
/>
<ColorPicker
v-model="theme.timelineCheckColor"
:label="$t('timeline.itemSelected')"
/>
<ColorPicker
v-model="theme.timelineCheckBorderColor"
:label="$t('timeline.itemSelectedBorder')"
/>
<van-field
v-model.number="theme.timelineItemBorderWidth"
type="number"
:label="$t('timeline.itemBorderWidth')"
/>
<ColorPicker
v-model="theme.timelineLineColor"
:label="$t('timeline.mainAxis')"
/>
<van-field
v-model.number="theme.timelineLineWidth"
type="number"
:label="$t('timeline.mainAxisWidth')"
/>
<ColorPicker
v-model="theme.timelineControlColor"
:label="$t('timeline.controlFill')"
/>
<ColorPicker
v-model="theme.timelineControlBorderColor"
:label="$t('timeline.controlBorder')"
/>
<van-field
v-model.number="theme.timelineControlBorderWidth"
type="number"
:label="$t('timeline.controlBorderWidth')"
/>
<ColorPicker
v-model="theme.timelineLabelColor"
:label="$t('timeline.label')"
/>
</div>
</van-collapse-item>
<!-- Line Chart -->
<van-collapse-item :title="$t('panel.lineChart')" name="line">
<div class="panel-content">
<van-field :label="$t('lineChart.smooth')">
<template #input>
<van-checkbox v-model="theme.lineSmooth">
{{ $t('lineChart.smooth') }}
</van-checkbox>
</template>
</van-field>
<van-field
v-model.number="theme.lineWidth"
type="number"
:label="$t('lineChart.lineWidth')"
/>
<van-field
v-model.number="theme.symbolBorderWidth"
type="number"
:label="$t('lineChart.symbolBorder')"
/>
<van-field
v-model.number="theme.symbolSize"
type="number"
:label="$t('lineChart.symbolSize')"
/>
<van-field :label="$t('lineChart.symbolShape')">
<template #input>
<van-radio-group v-model="theme.symbol" direction="horizontal">
<van-radio name="circle">{{ $t('lineChart.circle') }}</van-radio>
<van-radio name="emptyCircle">{{ $t('lineChart.emptyCircle') }}</van-radio>
<van-radio name="rect">{{ $t('lineChart.rect') }}</van-radio>
<van-radio name="emptyRect">{{ $t('lineChart.emptyRect') }}</van-radio>
<van-radio name="roundRect">{{ $t('lineChart.roundRect') }}</van-radio>
<van-radio name="emptyRoundRect">{{ $t('lineChart.emptyRoundRect') }}</van-radio>
<van-radio name="triangle">{{ $t('lineChart.triangle') }}</van-radio>
<van-radio name="emptyTriangle">{{ $t('lineChart.emptyTriangle') }}</van-radio>
<van-radio name="diamond">{{ $t('lineChart.diamond') }}</van-radio>
<van-radio name="emptyDiamond">{{ $t('lineChart.emptyDiamond') }}</van-radio>
<van-radio name="pin">{{ $t('lineChart.pin') }}</van-radio>
<van-radio name="emptyPin">{{ $t('lineChart.emptyPin') }}</van-radio>
<van-radio name="arrow">{{ $t('lineChart.arrow') }}</van-radio>
<van-radio name="emptyArrow">{{ $t('lineChart.emptyArrow') }}</van-radio>
</van-radio-group>
</template>
</van-field>
</div>
</van-collapse-item>
<!-- Graph -->
<van-collapse-item :title="$t('panel.graph')" name="graph">
<div class="panel-content">
<van-field
v-model.number="theme.graphLineWidth"
type="number"
:label="$t('graph.lineWidth')"
/>
<ColorPicker
v-model="theme.graphLineColor"
:label="$t('graph.lineColor')"
/>
</div>
</van-collapse-item>
<!-- Map -->
<van-collapse-item :title="$t('panel.map')" name="map">
<div class="panel-content">
<ColorPicker
v-model="theme.mapAreaColor"
:label="$t('map.areaColor')"
/>
<ColorPicker
v-model="theme.mapBorderColor"
:label="$t('map.borderColor')"
/>
<van-field
v-model.number="theme.mapBorderWidth"
type="number"
:label="$t('map.borderWidth')"
/>
<ColorPicker
v-model="theme.mapLabelColor"
:label="$t('map.labelColor')"
/>
<ColorPicker
v-model="theme.mapAreaColorE"
:label="$t('map.areaHoverColor')"
/>
<ColorPicker
v-model="theme.mapBorderColorE"
:label="$t('map.borderHoverColor')"
/>
<van-field
v-model.number="theme.mapBorderWidthE"
type="number"
:label="$t('map.borderHoverWidth')"
/>
<ColorPicker
v-model="theme.mapLabelColorE"
:label="$t('map.labelHoverColor')"
/>
</div>
</van-collapse-item>
<!-- K Line Chart -->
<van-collapse-item :title="$t('panel.kline')" name="kline">
<div class="panel-content">
<ColorPicker
v-model="theme.kColor"
:label="$t('kline.upColor')"
/>
<ColorPicker
v-model="theme.kColor0"
:label="$t('kline.downColor')"
/>
<ColorPicker
v-model="theme.kBorderColor"
:label="$t('kline.upBorderColor')"
/>
<ColorPicker
v-model="theme.kBorderColor0"
:label="$t('kline.downBorderColor')"
/>
<van-field
v-model.number="theme.kBorderWidth"
type="number"
:label="$t('kline.borderWidth')"
/>
</div>
</van-collapse-item>
</van-collapse>
<!-- Hidden file input for import -->
<input
ref="importFileInput"
type="file"
accept=".json"
style="display: none"
@change="handleFileImport"
/>
<input
ref="uploadImageInput"
type="file"
accept="image/jpeg, image/png, image/webp, image/avif, image/apng, image/svg+xml"
style="display: none"
@change="handleImageUpload"
/>
</div>
</template>
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue'
import { useThemeStore } from '../stores/theme'
import { PRE_DEFINED_THEMES } from '../stores/theme'
import ColorPicker from './ColorPicker.vue'
import ColorList from './ColorList.vue'
import type ChartPreviewPanel from './ChartPreviewPanel.vue'
import { downloadJsonFile, downloadJsFile } from '../utils/download'
import { showToast, showDialog, showLoadingToast } from 'vant'
import { useI18n } from 'vue-i18n'
import * as echarts from 'echarts'
import { getPalette } from 'colorthief'
// Initialize i18n and localization
const { t } = useI18n()
// Props
interface Props {
chartPreviewRef?: InstanceType<typeof ChartPreviewPanel> | null
}
const props = defineProps<Props>()
// Component state
const activeNames = ref(['functions'])
const importFileInputRef = useTemplateRef('importFileInput')
const uploadImageInputRef = useTemplateRef('uploadImageInput')
// Theme store
const themeStore = useThemeStore()
const { theme, themeName } = themeStore
// Predefined themes
const preDefinedThemes = PRE_DEFINED_THEMES
// Grid layout reactive properties
const gridLeft = ref(String(theme.gridLeft))
const gridRight = ref(String(theme.gridRight))
const gridTop = ref(String(theme.gridTop))
const gridBottom = ref(String(theme.gridBottom))
// Legend position reactive properties
const legendLeft = ref(String(theme.legendLeft))
const legendRight = ref(String(theme.legendRight))
const legendTop = ref(String(theme.legendTop))
const legendBottom = ref(String(theme.legendBottom))
// Legend value validation function
const validateLegendValue = (position: 'left' | 'right' | 'top' | 'bottom') => {
let valueRef;
switch (position) {
case 'left':
valueRef = legendLeft;
break;
case 'right':
valueRef = legendRight;
break;
case 'top':
valueRef = legendTop;
break;
case 'bottom':
valueRef = legendBottom;
break;
}
const inputValue = valueRef.value.trim();
// Check if the value is a valid position value (number, percentage, or special value)
const isValid =
// Empty string - will be converted to auto in setOption
inputValue === '' ||
// Valid number
/^[0-9]+$/.test(inputValue) ||
// Valid percentage (e.g. 10%, 10.5%)
/^[0-9]+(\.[0-9]+)?%$/.test(inputValue) ||
// Special values for legend position
inputValue === 'auto' ||
inputValue === 'center' ||
inputValue === 'left' ||
inputValue === 'right' ||
inputValue === 'top' ||
inputValue === 'bottom';
if (isValid) {
// For numeric values, convert to number if it's a pure number
const finalValue = inputValue === '' ?
position === 'bottom' ? 10 :
position === 'left' ? 'center' : 'auto' :
(/^[0-9]+$/.test(inputValue)) ? parseInt(inputValue, 10) : inputValue;
// Update the corresponding theme property
switch (position) {
case 'left':
theme.legendLeft = finalValue;
break;
case 'right':
theme.legendRight = finalValue;
break;
case 'top':
theme.legendTop = finalValue;
break;
case 'bottom':
theme.legendBottom = finalValue;
break;
}
} else {
// If invalid, reset to default values
let defaultValue;
switch (position) {
case 'left':
defaultValue = 'center';
break;
case 'bottom':
defaultValue = 10;
break;
default:
defaultValue = 'auto';
}
valueRef.value = String(defaultValue);
// Set the theme property to default value
switch (position) {
case 'left':
theme.legendLeft = 'center';
break;
case 'right':
theme.legendRight = 'auto';
break;
case 'top':
theme.legendTop = 'auto';
break;
case 'bottom':
theme.legendBottom = 10;
break;
}
}
}
// Grid value validation function
const validateGridValue = (position: 'left' | 'right' | 'top' | 'bottom') => {
let valueRef;
switch (position) {
case 'left':
valueRef = gridLeft;
break;
case 'right':
valueRef = gridRight;
break;
case 'top':
valueRef = gridTop;
break;
case 'bottom':
valueRef = gridBottom;
break;
}
const inputValue = valueRef.value.trim();
// Check if the value is a number or a percentage string
const isValid =
// Empty string - will be converted to undefined in setOption
inputValue === '' ||
// Valid number
/^[0-9]+$/.test(inputValue) ||
// Valid percentage (e.g. 10%, 10.5%)
/^[0-9]+(\.[0-9]+)?%$/.test(inputValue);
if (isValid) {
// Update the theme property with the validated value
// For numeric values, convert to number if it's a pure number
// Empty strings will use default values defined in themeGenerator.ts
const finalValue = inputValue === '' ?
(position === 'left' || position === 'right' ? '10%' : 60) :
(/^[0-9]+$/.test(inputValue)) ? parseInt(inputValue, 10) : inputValue;
// Update the corresponding theme property
switch (position) {
case 'left':
theme.gridLeft = finalValue;
break;
case 'right':
theme.gridRight = finalValue;
break;
case 'top':
theme.gridTop = finalValue;
break;
case 'bottom':
theme.gridBottom = finalValue;
break;
}
} else {
// If invalid, reset to default values
const defaultValue = position === 'left' || position === 'right' ? '10%' : 60;
valueRef.value = String(defaultValue);
// Set the theme property to default value
switch (position) {
case 'left':
theme.gridLeft = '10%';
break;
case 'right':
theme.gridRight = '10%';
break;
case 'top':
theme.gridTop = 60;
break;
case 'bottom':
theme.gridBottom = 60;
break;
}
}
}
// Methods
const downloadTheme = async () => {
try {
const themeConfig = themeStore.getEChartsTheme()
const jsContent = themeStore.getThemeJsFile()
const filename = themeName.value && echarts.format.encodeHTML(themeName.value) || 'customized'
// Show format selection dialog using action sheet style
try {
await showDialog({
title: t('modals.formatSelection'),
message: t('modals.formatSelectionMsg'),
showCancelButton: true,
confirmButtonText: t('modals.jsFormat'),
cancelButtonText: t('modals.jsonFormat'),
closeOnClickOverlay: true
})
// User chose JavaScript
downloadJsFile(jsContent, filename)
showUsageInstructions('js', filename)
} catch (e) {
if (e !== 'cancel') {
throw e
}
// User chose JSON (clicked cancel button)
downloadJsonFile(themeConfig, filename)
showUsageInstructions('json', filename)
}
} catch (error) {
console.error('Download failed:', error)
showToast({
message: t('modals.downloadFailed'),
type: 'fail'
})
}
}
const showUsageInstructions = (format: 'js' | 'json', filename: string) => {
if (format === 'js') {
showDialog({
title: t('modals.jsUsageTitle'),
message: `<div style="text-align: left; padding: 5px 0;">
<ol style="margin: 0; line-height: 1; list-style: inside decimal;">
<li>${t('modals.jsUsageStep1').replace('__filename__', `<code style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-family: Monaco, monospace;">${filename}.js</code>`)}</li>
<li>${t('modals.jsUsageStep2')}<br/><code style="background: #f0f0f0; padding: 4px 8px; border-radius: 3px; font-family: Monaco, monospace; display: inline-block; margin-top: 6px;"><script src="${filename}.js"></script></code></li>
<li>${t('modals.jsUsageStep3')}<br/><code style="background: #f0f0f0; padding: 4px 8px; border-radius: 3px; font-family: Monaco, monospace; display: inline-block; margin-top: 6px;">echarts.init(dom, '${filename}')</code></li>
</ol>
<p style="margin: 0; color: #666; font-size: 14px; line-height: 1; background: #f8f9fa; padding: 10px; border-radius: 4px; border-left: 3px solid var(--van-primary-color);">${t('modals.jsUsageTip')}</p>
</div>`,
allowHtml: true,
confirmButtonText: t('common.ok'),
closeOnClickOverlay: true
})
} else {
showDialog({
title: t('modals.jsonUsageTitle'),
message: `<div style="text-align: left; padding: 5px 0;">
<ol style="margin: 0; line-height: 1; list-style: inside decimal;">
<li>${t('modals.jsonUsageStep1').replace('__filename__', `<code style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-family: Monaco, monospace;">${filename}.json</code>`)}</li>
<li>${t('modals.jsonUsageStep2')}<br/><code style="background: #f0f0f0; padding: 4px 8px; border-radius: 3px; font-family: Monaco, monospace; display: inline-block; margin-top: 6px;">const obj = JSON.parse(data)</code></li>
<li>${t('modals.jsonUsageStep3')}<br/><code style="background: #f0f0f0; padding: 4px 8px; border-radius: 3px; font-family: Monaco, monospace; display: inline-block; margin-top: 6px;">echarts.registerTheme('${filename}', obj)</code></li>
<li>${t('modals.jsonUsageStep4')}<br/><code style="background: #f0f0f0; padding: 4px 8px; border-radius: 3px; font-family: Monaco, monospace; display: inline-block; margin-top: 6px;">echarts.init(dom, '${filename}')</code></li>
</ol>
<p style="margin: 0; color: #666; font-size: 14px; line-height: 1; background: #f8f9fa; padding: 10px; border-radius: 4px; border-left: 3px solid var(--van-primary-color);">${t('modals.jsonUsageTip')}</p>
</div>`,
allowHtml: true,
confirmButtonText: t('common.ok'),
closeOnClickOverlay: true
})
}
}
const importConfig = () => {
importFileInputRef.value?.click()
}
const exportConfig = async () => {
try {
const configData = themeStore.getThemeConfigForDownload()
const filename = `${themeName.value && echarts.format.encodeHTML(themeName.value) || 'customized'}.project`
downloadJsonFile(configData, filename)
showToast({
message: t('modals.exportSuccess'),
type: 'success'
})
} catch (error) {
console.error('Export failed:', error)
showToast({
message: t('modals.exportFailed'),
type: 'fail'
})
}
}
const refreshCharts = () => {
if (props.chartPreviewRef?.updateCharts) {
props.chartPreviewRef.updateCharts()
showToast({
message: t('modals.chartsRefreshed'),
type: 'success'
})
}
}
const resetTheme = async () => {
try {
await showDialog({
title: t('common.reset'),
message: t('modals.resetConfirm'),
})
themeStore.resetTheme()
showToast({
message: t('modals.themeReset'),
type: 'success'
})
} catch {
// User cancelled
}
}
const showHelp = () => {
showDialog({
title: t('modals.helpTitle'),
message: `<div class="modal-body">
<h4>${t('modals.helpContent.whatIs')}</h4>
<p>${t('modals.helpContent.whatIsDesc1')}</p>
<p>${t('modals.helpContent.whatIsDesc2')}</p>
<p>${t('modals.helpContent.whatIsDesc3')}</p>
<h4>${t('modals.helpContent.importExport')}</h4>
<p>${t('modals.helpContent.importExportDesc')}</p>
</div>`,
allowHtml: true,
confirmButtonText: t('common.gotIt')
})
}
const openSourceCode = () => {
window.open('https://github.com/apache/echarts-theme-builder', '_blank', 'noopener,noreferrer')
}
const selectPreDefinedTheme = async (index: number) => {
if (themeStore.activePreDefinedThemeIndex.value === index) {
// Already selected
return
}
try {
await themeStore.loadPreDefinedTheme(index)
// Manually trigger chart update
if (props.chartPreviewRef?.updateCharts) {
props.chartPreviewRef.updateCharts()
}
} catch (error) {
console.error('Error selecting predefined theme:', error)
}
}
const onAxisSettingChange = () => {
themeStore.updateAxisSetting()
}
const handleFileImport = async (event: Event) => {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
if (!file) return
// Check file extension
const extension = file.name.slice(file.name.lastIndexOf('.'))
if (extension !== '.json') {
showToast({
message: t('modals.selectJsonFile'),
type: 'fail'
})
target.value = ''
return
}
try {
const reader = new FileReader()
reader.onload = async (e) => {
try {
const result = e.target?.result as string
const data = JSON.parse(result)
// Validate imported data
if (!data.themeName && !data.version && !data.theme) {
showToast({
message: t('modals.useExportedFile'),
type: 'fail'
})
return
}
// Check version compatibility
if (data.version && data.version < 1) {
try {
await showDialog({
title: t('modals.importThemeTitle'),
message: t('modals.oldVersionPrompt'),
})
} catch {
return // User cancelled
}
}
themeStore.importTheme(result)
// Update charts if reference is available
if (props.chartPreviewRef?.updateCharts) {
props.chartPreviewRef.updateCharts()
}
showToast({
message: t('modals.importSuccess'),
type: 'success'
})
} catch (error) {
console.error('Import error:', error)
showToast({
message: t('modals.invalidFormat'),
type: 'fail'
})
}
}
reader.onerror = () => {
showToast({
message: t('modals.fileReadFailed'),
type: 'fail'
})
}
reader.readAsText(file)
} catch (error) {
console.error('File import failed:', error)
showToast({
message: t('modals.fileReadFailed'),
type: 'fail'
})
}
// Clear input
target.value = ''
}
const handleImageUpload = async (event: Event) => {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
if (!file) return
const loader = showLoadingToast({
message: t('modals.extractingColors'),
duration: 0,
forbidClick: true,
loadingType: 'spinner',
wordBreak: 'break-word',
className: 'ec-loading-toast'
})
let imgURL: string | undefined;
try {
imgURL = URL.createObjectURL(file)
const img = new Image()
img.src = imgURL
await new Promise((resolve, reject) => {
img.onload = resolve
img.onerror = (e) => {
console.error('Failed to load image')
reject(e)
}
})
const palette = await getPalette(img, { colorCount: 10, quality: 1, worker: true })
if (!palette) {
throw new Error('No colors extracted from this image')
}
const colors = palette.map(
(rgb) => `rgb(${rgb.array().join(',')})`
)
loader.close()
themeName.value = 'image-based'
theme.color = colors
// PENDING
theme.visualMapColor = colors.slice(0, 2)
props.chartPreviewRef?.updateCharts()
themeStore.activePreDefinedThemeIndex.value = null
} catch (e) {
console.error('Failed to extract image color:', e)
showToast({
message: t('modals.extractColorFailed'),
type: 'fail'
})
} finally {
imgURL && URL.revokeObjectURL(imgURL)
target.value = ''
}
}
</script>
<style scoped>
.theme-panel {
height: 100%;
overflow-y: auto;
}
.panel-content {
padding: 16px;
}
.action-buttons {
display: flex;
gap: 8px;
margin-bottom: 8px;
flex-wrap: wrap;
}
.action-buttons .van-button {
flex: 1;
min-width: 80px;
font-size: 13px;
}
.predefined-themes {
margin-top: 16px;
}
.predefined-themes h4 {
margin: 0 0 12px 0;
font-size: 14px;
font-weight: 500;
color: #323233;
}
.theme-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.theme-item {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
width: auto;
box-sizing: border-box;
height: 32px;
margin-bottom: 5px;
overflow: hidden;
border: 1px solid #eee;
padding: 5px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
gap: 2px;
}
.theme-item:hover,
.theme-item.active {
border-color: var(--van-primary-color);
box-shadow: 0 0 4px var(--van-primary-color);
}
.new-theme {
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
height: auto;
font-size: 85%;
line-height: 1;
white-space: nowrap;
cursor: pointer;
}
.color-dot {
width: 20px;
height: 20px;
margin: 0 2px 4px 2px;
border-radius: 2px;
border: 1px solid rgba(0, 0, 0, 0.1);
flex-shrink: 0;
}
.axis-group {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #ebedf0;
}
.axis-group h4 {
margin: 0 0 12px 0;
font-size: 14px;
font-weight: 500;
color: rgb(41, 60, 85);
}
@media (max-width: 1200px) {
.new-theme span {
display: none;
}
}
/* Custom Vant styles */
:deep(.van-collapse) {
border: none;
background: #fff;
}
:deep(.van-collapse-item) {
border-top: 1px solid #ccc;
background: #fff;
}
:deep(.van-collapse-item:first-child) {
border-top: none;
}
:deep(.van-collapse-item__title) {
padding: 10px 16px;
background-color: #fff;
font-weight: 500;
border: none;
transition: background-color 0.3s;
}
:deep(.van-collapse-item__title:hover) {
background-color: #f7f8fa;
}
:deep(.van-collapse-item__content) {
padding: 0;
border-top: none;
background: #fff;
}
:deep(.van-collapse-item__wrapper) {
border: none;
overflow: hidden;
transition: height 0.3s ease;
}
:deep(.van-field) {
padding: 8px 16px;
border-bottom: 1px solid #f7f8fa;
}
:deep(.van-field:last-child) {
border-bottom: none;
}
:deep(.van-field__label) {
width: 80px;
font-size: 13px;
color: #323233;
text-align: right;
padding-right: 8px;
}
:deep(.van-field__control) {
font-size: 13px;
}
:deep(.van-radio-group) {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
padding: 8px 0;
}
:deep(.van-radio) {
margin: 0;
font-size: 12px;
}
:deep(.van-checkbox) {
font-size: 12px;
min-width: 24px;
}
:deep(.van-button--small) {
height: 28px;
font-size: 12px;
}
/* Color picker adjustments */
:deep(.vc-color-wrap) {
width: 32px !important;
height: 32px !important;
border-radius: 4px;
border: 1px solid #dcdee0;
}
:deep(.vc-color-picker) {
background: white;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
/* Dialog width adjustments */
:global(.van-dialog) {
width: 500px;
max-width: 90vw;
}
:global(.van-dialog__content) {
max-height: 70vh !important;
overflow-y: auto !important;
}
:global(.van-dialog__message) {
text-align: left !important;
line-height: 1 !important;
}
/* Code dialog specific styles */
:global(.van-dialog__message pre) {
white-space: pre-wrap !important;
word-wrap: break-word !important;
font-size: 12px !important;
line-height: 1 !important;
}
/* Help dialog styles */
:global(.modal-body) {
text-align: left;
padding: 5px 0;
white-space: normal;
}
:global(.modal-body h4) {
margin: 30px 0 10px;
color: #333;
font-size: 16px;
font-weight: 500;
}
:global(.modal-body h4:first-child) {
margin-top: 0;
}
:global(.modal-body p) {
margin: 15px 0;
font-size: 14px;
line-height: 1.5;
color: #555;
}
:global(.modal-body code) {
background: #f0f0f0;
padding: 2px 4px;
border-radius: 3px;
font-family: Monaco, monospace;
font-size: 90%;
}
:global(.modal-body a) {
color: var(--van-primary-color);
text-decoration: none;
}
:global(.modal-body a:hover) {
text-decoration: underline;
}
:global(.ec-loading-toast) {
width: 200px;
}
</style>
================================================
FILE: src/composables/useLocalization.ts
================================================
import { useI18n } from 'vue-i18n'
import { setLocale, getCurrentLocale, availableLocales } from '../i18n'
/**
* Localization composable
* Provides common i18n functionality
*/
export function useLocalization() {
const { t, locale } = useI18n()
/**
* Switch language
* @param newLocale New language code
*/
const switchLanguage = (newLocale: string) => {
setLocale(newLocale)
}
/**
* Get current language
*/
const currentLanguage = getCurrentLocale()
/**
* Check if current language matches specified language
* @param lang Language code
*/
const isLanguage = (lang: string) => {
return getCurrentLocale() === lang
}
/**
* Get formatted date text
* @param date Date object
*/
const formatDate = (date: Date) => {
const isZh = isLanguage('zh')
return date.toLocaleDateString(isZh ? 'zh-CN' : 'en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
}
/**
* Get formatted number text
* @param num Number
*/
const formatNumber = (num: number) => {
const isZh = isLanguage('zh')
return num.toLocaleString(isZh ? 'zh-CN' : 'en-US')
}
return {
t,
locale,
switchLanguage,
currentLanguage,
availableLanguages: availableLocales,
isLanguage,
formatDate,
formatNumber
}
}
================================================
FILE: src/i18n.ts
================================================
import { createI18n } from 'vue-i18n'
import en from './locales/en.json'
import zh from './locales/zh.json'
// Available languages list
export const availableLocales = Object.freeze([
{ code: 'en', name: 'English' },
{ code: 'zh', name: '简体中文' }
])
// Get saved language setting from localStorage, default to Chinese
const getStoredLanguage = (): string => {
const stored = import.meta.env.DEV
? localStorage.getItem('echarts-theme-builder-locale')
: ((window as any).EC_WWW_LANG === 'zh' ? 'zh' : 'en')
if (stored && availableLocales.some(l => l.code === stored)) {
return stored
}
// Auto-select based on browser language
const browserLang: string = document.documentElement.lang || navigator.language || (navigator as any).browserLanguage
return browserLang && browserLang.toLowerCase().startsWith('zh') ? 'zh' : 'en'
}
const i18n = createI18n({
legacy: false, // Use Composition API mode
locale: getStoredLanguage(),
fallbackLocale: 'en',
messages: {
en,
zh
}
})
// Save language setting to localStorage
export const setLocale = (locale: string) => {
if (availableLocales.some(l => l.code === locale)) {
i18n.global.locale.value = locale as 'en' | 'zh'
if (import.meta.env.DEV) {
localStorage.setItem('echarts-theme-builder-locale', locale)
}
}
}
// Get current language
export const getCurrentLocale = () => {
return i18n.global.locale.value
}
export default i18n
================================================
FILE: src/locales/en.json
================================================
{
"common": {
"add": "Add",
"reduce": "Remove",
"refresh": "Refresh",
"reset": "Reset",
"help": "Help",
"cancel": "Cancel",
"confirm": "Confirm",
"ok": "OK",
"copyCode": "Copy Code",
"close": "Close",
"gotIt": "Got it"
},
"panel": {
"functions": "Functions",
"basicConfig": "Basic Configuration",
"visualMap": "Visual Map",
"grid": "Grid (Cartesian)",
"axis": "Axes",
"legend": "Legend",
"legendPosition": "Legend Position",
"toolbox": "Toolbox",
"tooltip": "Tooltip",
"timeline": "Timeline",
"lineChart": "Line Chart",
"graph": "Graph",
"map": "Map",
"kline": "Candlestick Chart",
"download": "Download",
"import": "Import",
"export": "Export",
"themeName": "Name",
"themePlaceholder": "Please enter theme name",
"sourceCode": "Source Code",
"seriesCount": "Series",
"seriesPlaceholder": "Please enter series count",
"preDefinedThemes": "Predefined Themes",
"separateAxisSetting": "Configure different axis types separately",
"extractFromImage": "From Image",
"extractFromImageTitle": "Extract theme colors from an image"
},
"colors": {
"background": "Background",
"title": "Title",
"subtitle": "Subtitle",
"theme": "Theme",
"markText": "Mark Text",
"border": "Border",
"borderWidth": "Border Width",
"axisLine": "Axis Line",
"axisTick": "Axis Tick",
"splitLine": "Split Line",
"splitArea": "Split Area",
"axisLabel": "Axis Label",
"legendText": "Legend Text",
"toolbox": "Toolbox",
"toolboxEmphasis": "Toolbox Hover",
"visualMapColor": "Visual Map"
},
"position": {
"left": "Left",
"right": "Right",
"top": "Top",
"bottom": "Bottom"
},
"legend": {
},
"grid": {
},
"tooltip": {
"axisPointer": "Axis Pointer",
"axisPointerWidth": "Width"
},
"axis": {
"categoryAxis": "Category Axis",
"valueAxis": "Value Axis",
"timeAxis": "Time Axis",
"logAxis": "Log Axis",
"all": "All Axes"
},
"preview": {
"chartPreview": "Chart Preview"
},
"language": {
"title": "Language",
"en": "English",
"zh": "中文"
},
"timeline": {
"item": "Marker",
"itemHover": "Marker Hover",
"itemSelected": "Marker Selected",
"itemSelectedBorder": "Selected Marker Border",
"itemBorderWidth": "Marker Border Width",
"mainAxis": "Main Axis",
"mainAxisWidth": "Main Axis Width",
"controlFill": "Control Fill",
"controlBorder": "Control Border",
"controlBorderWidth": "Control Border Width",
"label": "Label"
},
"lineChart": {
"smooth": "Smooth Curve",
"lineWidth": "Line Width",
"symbolBorder": "Symbol Border",
"symbolSize": "Symbol Size",
"symbolShape": "Symbol Shape",
"circle": "Circle",
"emptyCircle": "Empty Circle",
"rect": "Rectangle",
"emptyRect": "Empty Rectangle",
"roundRect": "Rounded Rectangle",
"emptyRoundRect": "Empty Rounded Rectangle",
"triangle": "Triangle",
"emptyTriangle": "Empty Triangle",
"diamond": "Diamond",
"emptyDiamond": "Empty Diamond",
"pin": "Pin",
"emptyPin": "Empty Pin",
"arrow": "Arrow",
"emptyArrow": "Empty Arrow"
},
"graph": {
"lineWidth": "Line Width",
"lineColor": "Line Color"
},
"map": {
"areaColor": "Area Color",
"borderColor": "Border Color",
"borderWidth": "Border Width",
"labelColor": "Label Color",
"areaHoverColor": "Area Hover Color",
"borderHoverColor": "Border Hover Color",
"borderHoverWidth": "Border Hover Width",
"labelHoverColor": "Label Hover Color"
},
"kline": {
"upColor": "Up Color",
"downColor": "Down Color",
"upBorderColor": "Up Border Color",
"downBorderColor": "Down Border Color",
"borderWidth": "Border Width"
},
"modals": {
"themeCodeTitle": "Theme Code",
"importThemeTitle": "Import Theme",
"helpTitle": "Help",
"helpContent": {
"whatIs": "What is the Theme Builder?",
"whatIsDesc1": "A \"theme\" is a style abstraction for ECharts charts, used to unify the style of multiple charts. Using the online theme builder, you can quickly and intuitively generate theme configuration files and use customized theme styles in ECharts.",
"whatIsDesc2": "Based on this theme, you can still use <code>setOption</code> to override or set theme styles.",
"whatIsDesc3": "ECharts officially provides themes such as <code>default</code>, <code>infographic</code>, <code>shine</code>, <code>roma</code>, <code>macarons</code>, <code>vintage</code>, etc., which can be <a href=\"https://echarts.apache.org/download-theme.html\" target=\"_blank\">downloaded</a> for use.",
"importExport": "Import and Export",
"importExportDesc": "For convenient secondary modifications, our theme builder supports importing and exporting configuration items. The exported JSON file is only used for importing in this tool and cannot be directly registered as a theme in ECharts."
},
"formatSelection": "Select Format",
"formatSelectionMsg": "Please select a format for the theme file:",
"jsFormat": "JavaScript File",
"jsonFormat": "JSON File",
"jsUsageTitle": "JavaScript Theme File Usage",
"jsonUsageTitle": "JSON Theme File Usage",
"jsUsageStep1": "Save the downloaded __filename__ file to your project",
"jsonUsageStep1": "Save the downloaded __filename__ file to your project",
"jsUsageStep2": "Include this file in your HTML:",
"jsonUsageStep2": "Read and parse the JSON file:",
"jsUsageStep3": "Use the theme when creating a chart:",
"jsonUsageStep3": "Register the theme:",
"jsonUsageStep4": "Use the theme when creating a chart:",
"jsUsageTip": "💡 The second parameter is the theme name registered in the JS file.",
"jsonUsageTip": "💡 The second parameter is the theme name used during registration.",
"downloadFailed": "Download failed, please try again",
"exportSuccess": "Configuration exported successfully!",
"exportFailed": "Export failed, please try again",
"chartsRefreshed": "Charts refreshed",
"resetConfirm": "Are you sure you want to reset to the default theme? This action cannot be undone.",
"themeReset": "Theme has been reset",
"selectCodeFormat": "Select code format to view:",
"codeGenFailed": "Code generation failed",
"codeCopied": "Code copied to clipboard",
"copyFailed": "Copy failed, please copy manually",
"selectJsonFile": "Please select a JSON format configuration file!",
"useExportedFile": "Please use a JSON configuration file exported from this site!",
"oldVersionPrompt": "The imported theme is from an older version, some properties may not be correctly set. Continue importing?",
"importSuccess": "Theme imported successfully!",
"invalidFormat": "Configuration file format error, please use a JSON file exported from this site!",
"fileReadFailed": "File read failed, please try again",
"extractingColors": "Please wait when extracting colors from image...",
"extractColorFailed": "Failed to extract colors from image"
}
}
================================================
FILE: src/locales/zh.json
================================================
{
"common": {
"add": "增加",
"reduce": "减少",
"refresh": "刷新",
"reset": "复原",
"help": "帮助",
"cancel": "取消",
"confirm": "确定",
"ok": "好的",
"copyCode": "复制代码",
"close": "关闭",
"gotIt": "知道了"
},
"panel": {
"functions": "功能",
"basicConfig": "基本配置",
"visualMap": "视觉映射",
"grid": "Grid 布局",
"axis": "坐标轴",
"legend": "图例",
"legendPosition": "图例位置",
"toolbox": "工具箱",
"tooltip": "提示框",
"timeline": "时间轴",
"lineChart": "折线图",
"graph": "关系图",
"map": "地图",
"kline": "K线图",
"download": "下载主题",
"import": "导入配置",
"export": "导出配置",
"themeName": "主题名称",
"themePlaceholder": "请输入主题名称",
"sourceCode": "项目源码",
"seriesCount": "系列数量",
"seriesPlaceholder": "请输入系列数量",
"preDefinedThemes": "默认方案",
"separateAxisSetting": "为不同类型坐标轴分别设置",
"extractFromImage": "从图片提取",
"extractFromImageTitle": "上传一张图片并从中提取颜色"
},
"colors": {
"background": "背景",
"title": "标题",
"subtitle": "副标题",
"theme": "主题",
"markText": "标签文字",
"border": "描边",
"borderWidth": "描边粗细",
"axisLine": "轴线",
"axisTick": "刻度",
"splitLine": "网格",
"splitArea": "填充",
"axisLabel": "文字",
"legendText": "文字",
"toolbox": "图标",
"toolboxEmphasis": "悬停",
"visualMapColor": "视觉映射"
},
"position": {
"left": "左",
"right": "右",
"top": "上",
"bottom": "下"
},
"legend": {
},
"grid": {
},
"tooltip": {
"axisPointer": "指示线",
"axisPointerWidth": "宽度"
},
"axis": {
"categoryAxis": "类目轴",
"valueAxis": "数值轴",
"timeAxis": "时间轴",
"logAxis": "对数轴",
"all": "所有坐标轴"
},
"preview": {
"chartPreview": "图表预览"
},
"language": {
"title": "语言",
"en": "English",
"zh": "中文"
},
"timeline": {
"item": "标记",
"itemHover": "标记悬停",
"itemSelected": "标记选中",
"itemSelectedBorder": "标记选中描边",
"itemBorderWidth": "标记描边",
"mainAxis": "主轴",
"mainAxisWidth": "主轴宽度",
"controlFill": "控件填充",
"controlBorder": "控件描边",
"controlBorderWidth": "控件描边宽度",
"label": "文字"
},
"lineChart": {
"smooth": "平滑曲线",
"lineWidth": "线条宽度",
"symbolBorder": "图形描边",
"symbolSize": "图形大小",
"symbolShape": "图形形状",
"circle": "圆形",
"emptyCircle": "空心圆形",
"rect": "方形",
"emptyRect": "空心方形",
"roundRect": "圆角矩形",
"emptyRoundRect": "空心圆角矩形",
"triangle": "三角形",
"emptyTriangle": "空心三角形",
"diamond": "菱形",
"emptyDiamond": "空心菱形",
"pin": "水滴",
"emptyPin": "空心水滴",
"arrow": "箭头",
"emptyArrow": "空心箭头"
},
"graph": {
"lineWidth": "线条宽度",
"lineColor": "线条颜色"
},
"map": {
"areaColor": "区域颜色",
"borderColor": "边界颜色",
"borderWidth": "边界宽度",
"labelColor": "标签颜色",
"areaHoverColor": "悬停区域颜色",
"borderHoverColor": "悬停边界颜色",
"borderHoverWidth": "悬停边界宽度",
"labelHoverColor": "悬停标签颜色"
},
"kline": {
"upColor": "阳线颜色",
"downColor": "阴线颜色",
"upBorderColor": "阳线边框",
"downBorderColor": "阴线边框",
"borderWidth": "边框宽度"
},
"modals": {
"themeCodeTitle": "主题代码",
"importThemeTitle": "导入主题",
"helpTitle": "帮助",
"helpContent": {
"whatIs": "主题在线构建工具是什么?",
"whatIsDesc1": "\"主题\"是 ECharts 图表的风格抽象,用于统一多个图表的风格样式。使用主题在线构建工具,可以快速直观地生成主题配置文件,并在 ECharts 中使用自定义的主题样式。",
"whatIsDesc2": "在此主题的基础上,你仍然可以使用 <code>setOption</code> 覆盖或设置主题样式。",
"whatIsDesc3": "ECharts 官方提供 <code>default</code>、<code>infographic</code>、<code>shine</code>、<code>roma</code>、<code>macarons</code>、<code>vintage</code> 等主题,可供<a href=\"https://echarts.apache.org/download-theme.html\" target=\"_blank\">下载</a>使用。",
"importExport": "导入、导出",
"importExportDesc": "为了便于二次修改,我们的主题构建工具支持导入、导出配置项,导出的 JSON 文件仅用于在本工具中导入使用,而不能直接作为主题在 ECharts 中注册。"
},
"formatSelection": "选择下载格式",
"formatSelectionMsg": "请选择要下载的主题文件格式:",
"jsFormat": "JavaScript 文件",
"jsonFormat": "JSON 文件",
"jsUsageTitle": "JavaScript 主题文件使用方法",
"jsonUsageTitle": "JSON 主题文件使用方法",
"jsUsageStep1": "将下载的 __filename__ 文件保存到项目中",
"jsonUsageStep1": "将下载的 __filename__ 文件保存到项目中",
"jsUsageStep2": "在 HTML 中引入此文件:",
"jsonUsageStep2": "读取 JSON 文件并解析:",
"jsUsageStep3": "创建图表时使用主题:",
"jsonUsageStep3": "注册主题:",
"jsonUsageStep4": "创建图表时使用主题:",
"jsUsageTip": "💡 第二个参数是在 JS 文件中注册的主题名称。",
"jsonUsageTip": "💡 第二个参数是注册时使用的主题名称。",
"downloadFailed": "下载失败,请重试",
"exportSuccess": "配置导出成功!",
"exportFailed": "导出失败,请重试",
"chartsRefreshed": "图表已刷新",
"resetConfirm": "确定要重置为默认主题吗?此操作不可撤销。",
"themeReset": "主题已重置",
"selectCodeFormat": "选择要查看的代码格式:",
"codeGenFailed": "代码生成失败",
"codeCopied": "代码已复制到剪贴板",
"copyFailed": "复制失败,请手动复制",
"selectJsonFile": "请选择 JSON 格式的配置文件!",
"useExportedFile": "请使用从本网站导出的 JSON 配置文件!",
"oldVersionPrompt": "导入的主题版本较低,某些属性可能无法正确设置。是否继续导入?",
"importSuccess": "主题导入成功!",
"invalidFormat": "配置文件格式错误,请使用从本网站导出的 JSON 文件!",
"fileReadFailed": "文件读取失败,请重试",
"extractingColors": "正在从图片中提取颜色...",
"extractColorFailed": "从图片提取颜色失败"
}
}
================================================
FILE: src/main.ts
================================================
import 'vant/lib/index.css'
import './style.css'
import { createApp } from 'vue'
import {
Col,
Row,
Collapse,
CollapseItem,
Field,
Button,
Icon,
Checkbox,
RadioGroup,
Radio
} from 'vant'
import App from './App.vue'
import i18n from './i18n'
const app = createApp(App)
app.use(i18n)
app.use(Col)
app.use(Row)
app.use(Collapse)
app.use(CollapseItem)
app.use(Field)
app.use(Button)
app.use(Icon)
app.use(Checkbox)
app.use(RadioGroup)
app.use(Radio)
app.mount('#theme-builder-app')
================================================
FILE: src/stores/theme.ts
================================================
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
}
================================================
FILE: src/style.css
================================================
#theme-builder-app {
height: 100%;
overflow: auto;
}
:root, :host {
--van-primary-color: #ff6d8f;
/* inherit font from parent page */
--van-base-font: inherit;
}
================================================
FILE: src/types/theme.ts
================================================
// ECharts theme type definitions
export interface ThemeAxis {
type: string
name: string
axisLineShow: boolean
axisLineColor: string
axisTickShow: boolean
axisTickColor: string
axisLabelShow: boolean
axisLabelColor: string
splitLineShow: boolean
splitLineColor: string[]
splitAreaShow: boolean
splitAreaColor: string[]
}
export interface ThemeData {
// Basic configuration
seriesCnt: number
backgroundColor: string
titleColor: string
subtitleColor: string
textColorShow: boolean
textColor: string
markTextColor: string
// Main color palette
color: string[]
// Border
borderColor: string
borderWidth: number
// Visual mapping
visualMapColor: string[]
// Legend
legendTextColor: string
// Candlestick chart
kColor: string
kColor0: string
kBorderColor: string
kBorderColor0: string
kBorderWidth: number
// Line chart
lineWidth: number
symbolSize: number
symbol: string
symbolBorderWidth: number
lineSmooth: boolean
// Graph/Network chart
graphLineWidth: number
graphLineColor: string
// Map
mapLabelColor: string
mapLabelColorE: string
mapBorderColor: string
mapBorderColorE: string
mapBorderWidth: number
mapBorderWidthE: number
mapAreaColor: string
mapAreaColorE: string
// Coordinate axis
axes: ThemeAxis[]
axisSeperateSetting: boolean
axis: ThemeAxis[] | null
// Toolbox
toolboxColor: string
toolboxEmphasisColor: string
// Tooltip
tooltipAxisColor: string
tooltipAxisWidth: number
// Timeline
timelineLineColor: string
timelineLineWidth: number
timelineItemColor: string
timelineItemColorE: string
timelineCheckColor: string
timelineCheckBorderColor: string
timelineItemBorderWidth: number
timelineControlColor: string
timelineControlBorderColor: string
timelineControlBorderWidth: number
timelineLabelColor: string
// Grid layout
gridLeft: number | string
gridRight: number | string
gridTop: number | string
gridBottom: number | string
// Legend position
legendLeft: number | string
legendRight: number | string
legendTop: number | string
legendBottom: number | string
}
export interface PreDefinedTheme {
name: string
background: string
theme: string[]
}
export interface ChartDisplay {
background: string
title: string
}
================================================
FILE: src/utils/chartConfigs.ts
================================================
import type { EChartsOption } from 'echarts'
// Chart configuration interface
interface ChartConfig {
title: string
subtitle?: string
type: string
option: EChartsOption
}
// Generate random data helper functions
function getRandomValue(max: number = 1000, min: number = 100): number {
return Math.floor(Math.random() * (max - min) + min)
}
function getRandomArray(length: number, max: number = 1000, min: number = 100): number[] {
return Array.from({ length }, () => getRandomValue(max, min))
}
// Chart configurations
export function getChartConfigs(seriesCnt: number = 4): ChartConfig[] {
const axisCat = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
const legendData = Array.from({ length: seriesCnt }, (_, i) => `Series ${i + 1}`)
const commonTooltip = {
trigger: 'axis' as const
}
const commonToolbox = {
feature: {
restore: { show: true },
saveAsImage: { show: true },
dataView: { show: true },
dataZoom: { show: true }
}
}
const configs: ChartConfig[] = [
// Line Chart
{
title: 'Line Chart',
subtitle: 'Basic line chart example',
type: 'line',
option: {
title: {
text: 'Line Chart',
subtext: 'Basic line chart example'
},
legend: {
data: legendData
},
tooltip: commonTooltip,
toolbox: commonToolbox,
xAxis: {
type: 'category',
data: axisCat
},
yAxis: {
type: 'value'
},
series: legendData.map(name => ({
name,
type: 'line' as const,
data: getRandomArray(axisCat.length, 800, 200),
markPoint: {
data: [{ name: 'Min', type: 'min' }]
}
}))
}
},
// Stacked Area Chart
{
title: 'Stacked Area Chart',
subtitle: 'Stacked area chart example',
type: 'area',
option: {
title: {
text: 'Stacked Area Chart',
subtext: 'Stacked area chart example'
},
legend: {
data: legendData
},
tooltip: commonTooltip,
toolbox: commonToolbox,
xAxis: {
type: 'category',
data: axisCat,
boundaryGap: false
},
yAxis: {
type: 'value'
},
series: legendData.map(name => ({
name,
type: 'line' as const,
data: getRandomArray(axisCat.length, 800, 200),
areaStyle: {},
stack: 'total'
}))
}
},
// Bar Chart
{
title: 'Bar Chart',
subtitle: 'Basic bar chart example',
type: 'bar',
option: {
title: {
text: 'Bar Chart',
subtext: 'Basic bar chart example'
},
legend: {
data: legendData
},
tooltip: commonTooltip,
toolbox: commonToolbox,
xAxis: {
type: 'category',
data: axisCat
},
yAxis: {
type: 'value'
},
series: legendData.map(name => ({
name,
type: 'bar' as const,
data: getRandomArray(axisCat.length, 800, 200),
markPoint: {
data: [{ name: 'Min', type: 'min' }]
}
}))
}
},
// Stacked Bar Chart
{
title: 'Stacked Bar Chart',
subtitle: 'Stacked bar chart example',
type: 'stackedBar',
option: {
title: {
text: 'Stacked Bar Chart',
subtext: 'Stacked bar chart example'
},
legend: {
data: legendData
},
tooltip: commonTooltip,
toolbox: commonToolbox,
xAxis: {
type: 'category',
data: axisCat
},
yAxis: {
type: 'value'
},
series: legendData.map(name => ({
name,
type: 'bar' as const,
data: getRandomArray(axisCat.length, 800, 200),
stack: 'total'
}))
}
},
// Scatter Chart
{
title: 'Scatter Chart',
subtitle: 'Basic scatter chart example',
type: 'scatter',
option: {
title: {
text: 'Scatter Chart',
subtext: 'Basic scatter chart example'
},
legend: {
data: legendData
},
tooltip: {
trigger: 'item' as const
},
toolbox: commonToolbox,
xAxis: {
type: 'value'
},
yAxis: {
type: 'value'
},
series: legendData.map(name => ({
name,
type: 'scatter' as const,
data: Array.from({ length: 32 }, () => [
getRandomValue(600, 100),
getRandomValue(600, 100)
])
}))
}
},
// Pie Chart
{
title: 'Pie Chart',
subtitle: 'Basic pie chart example',
type: 'pie',
option: {
title: {
text: 'Pie Chart',
subtext: 'Basic pie chart example'
},
legend: {
data: legendData
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
toolbox: commonToolbox,
series: [{
name: 'Data',
type: 'pie' as const,
radius: '50%',
data: legendData.map(name => ({
name,
value: getRandomValue(800, 200)
})),
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}]
}
},
// Radar Chart
{
title: 'Radar Chart',
subtitle: 'Basic radar chart example',
type: 'radar',
option: {
title: {
text: 'Radar Chart',
subtext: 'Basic radar chart example'
},
legend: {
data: legendData
},
tooltip: commonTooltip,
toolbox: commonToolbox,
radar: {
indicator: axisCat.map(name => ({ name, max: 1000 })),
center: ['50%', '60%']
},
series: legendData.map(name => ({
name,
type: 'radar' as const,
data: [{
value: getRandomArray(axisCat.length, 800, 200),
name
}]
}))
}
},
// Candlestick Chart (K-Line)
{
title: 'Candlestick Chart',
subtitle: 'K-line chart with data zoom',
type: 'candlestick',
option: {
title: {
text: 'Candlestick Chart',
subtext: 'K-line chart with data zoom'
},
tooltip: {
trigger: 'axis' as const
},
toolbox: {
show: true,
feature: {
dataZoom: { show: true },
dataView: { show: true },
restore: { show: true }
}
},
dataZoom: {
show: true,
realtime: true,
start: 50,
end: 100
},
xAxis: {
type: 'category',
data: Array.from({ length: 20 }, (_, i) => `Day ${i + 1}`)
},
yAxis: {
type: 'value',
scale: true
},
series: [{
name: 'Stock Price',
type: 'candlestick' as const,
data: Array.from({ length: 20 }, () => {
const open = getRandomValue(2400, 2200)
const close = getRandomValue(2400, 2200)
const low = Math.min(open, close) - getRandomValue(50, 0)
const high = Math.max(open, close) + getRandomValue(50, 0)
return [open, close, low, high]
})
}]
}
},
// Heatmap
{
title: 'Heatmap',
subtitle: 'Basic heatmap example',
type: 'heatmap',
option: {
title: {
text: 'Heatmap',
subtext: 'Basic heatmap example'
},
tooltip: {
trigger: 'item' as const
},
toolbox: commonToolbox,
xAxis: {
type: 'category',
data: ['12a', '1a', '2a', '3a', '4a', '5a', '6a', '7a', '8a', '9a', '10a', '11a']
},
yAxis: {
type: 'category',
data: ['Sat', 'Fri', 'Thu', 'Wed', 'Tue', 'Mon', 'Sun']
},
visualMap: {
min: 1,
max: 10,
calculable: true
},
series: [{
name: 'Heat',
type: 'heatmap' as const,
data: Array.from({ length: 84 }, (_, i) => {
const x = i % 12
const y = Math.floor(i / 12)
return [x, y, getRandomValue(10, 1)]
}),
label: {
show: true
}
}]
}
},
// Treemap
{
title: 'Treemap',
subtitle: 'Basic treemap example',
type: 'treemap',
option: {
title: {
text: 'Treemap',
subtext: 'Basic treemap example'
},
tooltip: {
trigger: 'item' as const,
formatter: '{b}: {c}'
},
toolbox: commonToolbox,
series: [{
type: 'treemap' as const,
data: [
{
name: 'Category A',
value: getRandomValue(1000, 500),
children: [
{ name: 'A1', value: getRandomValue(300, 100) },
{ name: 'A2', value: getRandomValue(300, 100) },
{ name: 'A3', value: getRandomValue(300, 100) }
]
},
{
name: 'Category B',
value: getRandomValue(1000, 500),
children: [
{ name: 'B1', value: getRandomValue(300, 100) },
{ name: 'B2', value: getRandomValue(300, 100) }
]
},
{
name: 'Category C',
value: getRandomValue(1000, 500),
children: [
{ name: 'C1', value: getRandomValue(300, 100) },
{ name: 'C2', value: getRandomValue(300, 100) },
{ name: 'C3', value: getRandomValue(300, 100) },
{ name: 'C4', value: getRandomValue(300, 100) }
]
}
]
}]
}
},
// Graph/Network Chart
{
title: 'Graph Chart',
subtitle: 'Network graph example',
type: 'graph',
option: {
title: {
text: 'Graph Chart',
subtext: 'Network graph example'
},
tooltip: {
trigger: 'item' as const
},
toolbox: commonToolbox,
legend: {
data: ['Category 0', 'Category 1', 'Category 2', 'Category 3']
},
series: [{
type: 'graph' as const,
layout: 'force',
roam: true,
label: {
show: true,
color: 'auto'
},
force: {
repulsion: 400,
edgeLength: 150
},
categories: [
{ name: 'Category 0' },
{ name: 'Category 1' },
{ name: 'Category 2' },
{ name: 'Category 3' }
],
data: [
{ id: '0', name: 'Node 1', symbolSize: 30, value: 30, category: 0 },
{ id: '1', name: 'Node 2', symbolSize: 25, value: 25, category: 1 },
{ id: '2', name: 'Node 3', symbolSize: 35, value: 35, category: 0 },
{ id: '3', name: 'Node 4', symbolSize: 20, value: 20, category: 2 },
{ id: '4', name: 'Node 5', symbolSize: 40, value: 40, category: 1 },
{ id: '5', name: 'Node 6', symbolSize: 28, value: 28, category: 3 },
{ id: '6', name: 'Node 7', symbolSize: 32, value: 32, category: 2 },
{ id: '7', name: 'Node 8', symbolSize: 22, value: 22, category: 0 },
{ id: '8', name: 'Node 9', symbolSize: 38, value: 38, category: 3 },
{ id: '9', name: 'Node 10', symbolSize: 26, value: 26, category: 1 }
],
links: [
{ source: '0', target: '1' },
{ source: '0', target: '2' },
{ source: '1', target: '3' },
{ source: '2', target: '4' },
{ source: '3', target: '5' },
{ source: '4', target: '6' },
{ source: '5', target: '7' },
{ source: '6', target: '8' },
{ source: '7', target: '9' },
{ source: '8', target: '0' },
{ source: '9', target: '2' },
{ source: '1', target: '4' },
{ source: '3', target: '7' },
{ source: '5', target: '9' }
],
lineStyle: {
curveness: 0.3
}
}]
}
},
// Timeline Chart
{
title: 'Timeline Chart',
subtitle: 'Timeline with multiple series',
type: 'timeline',
option: {
baseOption: {
timeline: {
axisType: 'category',
autoPlay: false,
data: ['2020', '2021', '2022', '2023'],
label: {
formatter: (value: string | number) => value.toString()
}
},
title: {
text: 'Timeline Chart',
subtext: 'Timeline with multiple series'
},
tooltip: {
trigger: 'axis' as const
},
legend: {
data: ['Primary', 'Secondary', 'Tertiary']
},
xAxis: {
type: 'category',
data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
},
yAxis: {
type: 'value',
name: 'Value'
},
series: [
{ name: 'Primary', type: 'bar' as const },
{ name: 'Secondary', type: 'bar' as const },
{ name: 'Tertiary', type: 'bar' as const }
]
},
options: [
{
title: { text: 'Timeline Chart - 2020' },
series: [
{ data: getRandomArray(6, 800, 400) },
{ data: getRandomArray(6, 600, 300) },
{ data: getRandomArray(6, 400, 200) }
]
},
{
title: { text: 'Timeline Chart - 2021' },
series: [
{ data: getRandomArray(6, 900, 500) },
{ data: getRandomArray(6, 700, 400) },
{ data: getRandomArray(6, 500, 300) }
]
},
{
title: { text: 'Timeline Chart - 2022' },
series: [
{ data: getRandomArray(6, 1000, 600) },
{ data: getRandomArray(6, 800, 500) },
{ data: getRandomArray(6, 600, 400) }
]
},
{
title: { text: 'Timeline Chart - 2023' },
series: [
{ data: getRandomArray(6, 1100, 700) },
{ data: getRandomArray(6, 900, 600) },
{ data: getRandomArray(6, 700, 500) }
]
}
]
}
}
]
return configs
}
================================================
FILE: src/utils/download.ts
================================================
/**
* Download utilities for theme builder
*/
/**
* Check if the browser is Safari
*/
function isSafari(): boolean {
return navigator.userAgent.indexOf('Safari') > 0 &&
navigator.userAgent.indexOf('Chrome') < 0
}
/**
* Save a file with the given content and filename
* @param data - File content
* @param filename - Name of the file
* @param type - MIME type of the file
*/
function saveFile(data: string, filename: string, type: string = 'text/plain'): void {
if (isSafari()) {
// Safari doesn't support Blob downloads well, use data URL
window.open('data:text/plain;charset=utf-8,' + encodeURIComponent(data))
} else {
try {
const file = new Blob([data], { type })
const url = URL.createObjectURL(file)
const a = document.createElement('a')
a.href = url
a.download = filename
document.body.appendChild(a)
a.click()
docum
gitextract_jnmntyz5/ ├── .asf.yaml ├── .editorconfig ├── .gitattributes ├── .github/ │ └── workflows/ │ └── deploy.yml ├── .gitignore ├── .jshintrc ├── .vscode/ │ └── settings.json ├── .yo-rc.json ├── LICENSE ├── README.md ├── app.html ├── config/ │ └── env.asf.js ├── index.html ├── package.json ├── public/ │ └── themes/ │ ├── chalk.json │ ├── dark.json │ ├── default.json │ ├── essos.json │ ├── halloween.json │ ├── infographic.json │ ├── macarons.json │ ├── purple-passion.json │ ├── roma.json │ ├── shine.json │ ├── v5.json │ ├── vintage.json │ ├── walden.json │ ├── westeros.json │ └── wonderland.json ├── scripts/ │ └── release.js ├── src/ │ ├── App.vue │ ├── components/ │ │ ├── ChartPreviewPanel.vue │ │ ├── ColorList.vue │ │ ├── ColorPicker.vue │ │ └── ThemePanel.vue │ ├── composables/ │ │ └── useLocalization.ts │ ├── i18n.ts │ ├── locales/ │ │ ├── en.json │ │ └── zh.json │ ├── main.ts │ ├── stores/ │ │ └── theme.ts │ ├── style.css │ ├── types/ │ │ └── theme.ts │ ├── utils/ │ │ ├── chartConfigs.ts │ │ ├── download.ts │ │ └── themeGenerator.ts │ └── vite-env.d.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts
SYMBOL INDEX (21 symbols across 8 files)
FILE: src/composables/useLocalization.ts
function useLocalization (line 8) | function useLocalization() {
FILE: src/stores/theme.ts
constant PRE_DEFINED_THEMES (line 6) | const PRE_DEFINED_THEMES: PreDefinedTheme[] = markRaw([
FILE: src/types/theme.ts
type ThemeAxis (line 2) | interface ThemeAxis {
type ThemeData (line 17) | interface ThemeData {
type PreDefinedTheme (line 107) | interface PreDefinedTheme {
type ChartDisplay (line 113) | interface ChartDisplay {
FILE: src/utils/chartConfigs.ts
type ChartConfig (line 4) | interface ChartConfig {
function getRandomValue (line 12) | function getRandomValue(max: number = 1000, min: number = 100): number {
function getRandomArray (line 16) | function getRandomArray(length: number, max: number = 1000, min: number ...
function getChartConfigs (line 21) | function getChartConfigs(seriesCnt: number = 4): ChartConfig[] {
FILE: src/utils/download.ts
function isSafari (line 8) | function isSafari(): boolean {
function saveFile (line 19) | function saveFile(data: string, filename: string, type: string = 'text/p...
function downloadJsonFile (line 48) | function downloadJsonFile(data: any, filename: string): void {
function downloadJsFile (line 58) | function downloadJsFile(content: string, filename: string): void {
function copyToClipboard (line 67) | async function copyToClipboard(text: string): Promise<boolean> {
FILE: src/utils/themeGenerator.ts
function generateEChartsTheme (line 8) | function generateEChartsTheme(themeData: ThemeData) {
function generateThemeJsFile (line 263) | function generateThemeJsFile(themeData: ThemeData, themeName: string): s...
function generateThemeConfigForDownload (line 303) | function generateThemeConfigForDownload(themeData: ThemeData, themeName:...
FILE: src/vite-env.d.ts
type ImportMetaEnv (line 3) | interface ImportMetaEnv {
type ImportMeta (line 9) | interface ImportMeta {
FILE: vite.config.ts
method generateBundle (line 33) | generateBundle(_, bundle) {
Condensed preview — 51 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (229K chars).
[
{
"path": ".asf.yaml",
"chars": 1115,
"preview": "# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements. See the NOTICE f"
},
{
"path": ".editorconfig",
"chars": 519,
"preview": "# EditorConfig helps developers define and maintain consistent\n# coding styles between different editors and IDEs\n# edit"
},
{
"path": ".gitattributes",
"chars": 11,
"preview": "* text=auto"
},
{
"path": ".github/workflows/deploy.yml",
"chars": 774,
"preview": "name: Build and Deploy\n\non:\n push:\n branches:\n - master\n\njobs:\n build-and-deploy:\n runs-on: ubuntu-latest\n "
},
{
"path": ".gitignore",
"chars": 115,
"preview": "node_modules\ndist\n.tmp\n.sass-cache\napp/bower_components\ntest/bower_components\napp/vendors/echarts.js\n.DS_Store\napp\n"
},
{
"path": ".jshintrc",
"chars": 391,
"preview": "{\n \"node\": true,\n \"browser\": true,\n \"esnext\": true,\n \"bitwise\": true,\n \"camelcase\": true,\n \"curly\": tr"
},
{
"path": ".vscode/settings.json",
"chars": 161,
"preview": "{\n \"i18n-ally.localesPaths\": [\n \"src/locales\"\n ],\n \"typescript.tsdk\": \"./node_modules/typescript/lib\",\n \"typescri"
},
{
"path": ".yo-rc.json",
"chars": 64,
"preview": "{\n \"generator-mocha\": {\n \"ui\": \"bdd\",\n \"rjs\": false\n }\n}"
},
{
"path": "LICENSE",
"chars": 10996,
"preview": "\n Apache License\n Version 2.0, January 2004\n ht"
},
{
"path": "README.md",
"chars": 817,
"preview": "# Apache ECharts Theme Builder\n\nDesign your own theme for Apache ECharts.\n\n[Online Theme Builder](https://echarts.apache"
},
{
"path": "app.html",
"chars": 86,
"preview": "<script type=\"module\" src=\"/src/main.ts\"></script>\n<div id=\"theme-builder-app\"></div>\n"
},
{
"path": "config/env.asf.js",
"chars": 359,
"preview": "import path from 'path';\nimport { fileURLToPath } from 'url';\n\n// Get __dirname equivalent in ESM\nconst __filename = fil"
},
{
"path": "index.html",
"chars": 650,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "package.json",
"chars": 868,
"preview": "{\n \"name\": \"echarts-theme-builder\",\n \"private\": true,\n \"version\": \"0.0.0\",\n \"type\": \"module\",\n \"scripts\": {\n \"de"
},
{
"path": "public/themes/chalk.json",
"chars": 5541,
"preview": "{\n \"version\": 1,\n \"themeName\": \"chalk\",\n \"theme\": {\n \"seriesCnt\": \"3\",\n \"backgroundColor\": \"rgba("
},
{
"path": "public/themes/dark.json",
"chars": 5531,
"preview": "{\n \"version\": 1,\n \"themeName\": \"dark\",\n \"theme\": {\n \"seriesCnt\": \"4\",\n \"backgroundColor\": \"rgba(5"
},
{
"path": "public/themes/default.json",
"chars": 5590,
"preview": "{\n \"version\": 1,\n \"themeName\": \"default\",\n \"theme\": {\n \"seriesCnt\": 3,\n \"backgroundColor\": \"rgba("
},
{
"path": "public/themes/essos.json",
"chars": 5572,
"preview": "{\n \"version\": 1,\n \"themeName\": \"essos\",\n \"theme\": {\n \"seriesCnt\": \"4\",\n \"backgroundColor\": \"rgba("
},
{
"path": "public/themes/halloween.json",
"chars": 5509,
"preview": "{\n \"version\": 1,\n \"themeName\": \"halloween\",\n \"theme\": {\n \"seriesCnt\": \"4\",\n \"backgroundColor\": \"r"
},
{
"path": "public/themes/infographic.json",
"chars": 5669,
"preview": "{\n \"version\": 1,\n \"themeName\": \"infographic\",\n \"theme\": {\n \"seriesCnt\": \"4\",\n \"backgroundColor\": "
},
{
"path": "public/themes/macarons.json",
"chars": 5729,
"preview": "{\n \"version\": 1,\n \"themeName\": \"macarons\",\n \"theme\": {\n \"seriesCnt\": \"4\",\n \"backgroundColor\": \"rg"
},
{
"path": "public/themes/purple-passion.json",
"chars": 5505,
"preview": "{\n \"version\": 1,\n \"themeName\": \"purple-passion\",\n \"theme\": {\n \"seriesCnt\": \"3\",\n \"backgroundColor"
},
{
"path": "public/themes/roma.json",
"chars": 5772,
"preview": "{\n \"version\": 1,\n \"themeName\": \"roma\",\n \"theme\": {\n \"seriesCnt\": \"4\",\n \"backgroundColor\": \"rgba(0"
},
{
"path": "public/themes/shine.json",
"chars": 5391,
"preview": "{\n \"version\": 1,\n \"themeName\": \"shine\",\n \"theme\": {\n \"seriesCnt\": 4,\n \"backgroundColor\": \"rgba(0,"
},
{
"path": "public/themes/v5.json",
"chars": 5529,
"preview": "{\n \"version\": 1,\n \"themeName\": \"v5\",\n \"theme\": {\n \"seriesCnt\": 3,\n \"backgroundColor\": \"rgba(0, 0,"
},
{
"path": "public/themes/vintage.json",
"chars": 5575,
"preview": "{\n \"version\": 1,\n \"themeName\": \"vintage\",\n \"theme\": {\n \"seriesCnt\": 4,\n \"backgroundColor\": \"rgba("
},
{
"path": "public/themes/walden.json",
"chars": 5501,
"preview": "{\n \"version\": 1,\n \"themeName\": \"walden\",\n \"theme\": {\n \"seriesCnt\": \"3\",\n \"backgroundColor\": \"rgba"
},
{
"path": "public/themes/westeros.json",
"chars": 5478,
"preview": "{\n \"version\": 1,\n \"themeName\": \"westeros\",\n \"theme\": {\n \"seriesCnt\": \"4\",\n \"backgroundColor\": \"rg"
},
{
"path": "public/themes/wonderland.json",
"chars": 5527,
"preview": "{\n \"version\": 1,\n \"themeName\": \"wonderland\",\n \"theme\": {\n \"seriesCnt\": \"3\",\n \"backgroundColor\": \""
},
{
"path": "scripts/release.js",
"chars": 1847,
"preview": "import fs from 'fs-extra';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport config from '../config/e"
},
{
"path": "src/App.vue",
"chars": 2806,
"preview": "<script setup lang=\"ts\">\nimport { ref, useTemplateRef } from 'vue'\n// Simple fixed sidebar layout without responsive des"
},
{
"path": "src/components/ChartPreviewPanel.vue",
"chars": 4096,
"preview": "<template>\n <div class=\"chart-preview\">\n <div class=\"preview-header\">\n <h3>{{ $t('preview.chartPreview') }}</h3"
},
{
"path": "src/components/ColorList.vue",
"chars": 3705,
"preview": "<template>\n <van-field :label=\"label\">\n <template #input>\n <van-checkbox v-if=\"canDisable\" :model-value=\"enable"
},
{
"path": "src/components/ColorPicker.vue",
"chars": 1807,
"preview": "<template>\n <van-field :label=\"label\">\n <template #input>\n <van-checkbox v-if=\"canDisable\" :model-value=\"enable"
},
{
"path": "src/components/ThemePanel.vue",
"chars": 36142,
"preview": "<template>\n <div class=\"theme-panel\">\n <!-- Functions Section -->\n <van-collapse v-model=\"activeNames\">\n <va"
},
{
"path": "src/composables/useLocalization.ts",
"chars": 1329,
"preview": "import { useI18n } from 'vue-i18n'\nimport { setLocale, getCurrentLocale, availableLocales } from '../i18n'\n\n/**\n * Local"
},
{
"path": "src/i18n.ts",
"chars": 1445,
"preview": "import { createI18n } from 'vue-i18n'\nimport en from './locales/en.json'\nimport zh from './locales/zh.json'\n\n// Availabl"
},
{
"path": "src/locales/en.json",
"chars": 7171,
"preview": "{\n \"common\": {\n \"add\": \"Add\",\n \"reduce\": \"Remove\",\n \"refresh\": \"Refresh\",\n \"reset\": \"Reset\",\n \"help\": \"H"
},
{
"path": "src/locales/zh.json",
"chars": 5080,
"preview": "{\n \"common\": {\n \"add\": \"增加\",\n \"reduce\": \"减少\",\n \"refresh\": \"刷新\",\n \"reset\": \"复原\",\n \"help\": \"帮助\",\n \"canc"
},
{
"path": "src/main.ts",
"chars": 499,
"preview": "import 'vant/lib/index.css'\nimport './style.css'\nimport { createApp } from 'vue'\nimport {\n Col,\n Row,\n Collapse,\n Co"
},
{
"path": "src/stores/theme.ts",
"chars": 9654,
"preview": "import { ref, reactive, markRaw } from 'vue'\nimport type { ThemeData, PreDefinedTheme } from '../types/theme'\nimport { g"
},
{
"path": "src/style.css",
"chars": 173,
"preview": "#theme-builder-app {\n height: 100%;\n overflow: auto;\n}\n\n:root, :host {\n --van-primary-color: #ff6d8f;\n /* inherit fo"
},
{
"path": "src/types/theme.ts",
"chars": 2323,
"preview": "// ECharts theme type definitions\nexport interface ThemeAxis {\n type: string\n name: string\n axisLineShow: boolean\n a"
},
{
"path": "src/utils/chartConfigs.ts",
"chars": 14946,
"preview": "import type { EChartsOption } from 'echarts'\n\n// Chart configuration interface\ninterface ChartConfig {\n title: string\n "
},
{
"path": "src/utils/download.ts",
"chars": 2619,
"preview": "/**\n * Download utilities for theme builder\n */\n\n/**\n * Check if the browser is Safari\n */\nfunction isSafari(): boolean "
},
{
"path": "src/utils/themeGenerator.ts",
"chars": 8373,
"preview": "import type { ThemeData } from '../types/theme'\n\n/**\n * Generate ECharts theme configuration based on theme data\n * @par"
},
{
"path": "src/vite-env.d.ts",
"chars": 263,
"preview": "/// <reference types=\"vite/client\" />\n\ninterface ImportMetaEnv {\n readonly VITE_SHOW_LANGUAGE_SELECTOR?: boolean;\n rea"
},
{
"path": "tsconfig.app.json",
"chars": 424,
"preview": "{\n \"extends\": \"@vue/tsconfig/tsconfig.dom.json\",\n \"compilerOptions\": {\n \"tsBuildInfoFile\": \"./node_modules/.tmp/tsc"
},
{
"path": "tsconfig.json",
"chars": 119,
"preview": "{\n \"files\": [],\n \"references\": [\n { \"path\": \"./tsconfig.app.json\" },\n { \"path\": \"./tsconfig.node.json\" }\n ]\n}\n"
},
{
"path": "tsconfig.node.json",
"chars": 630,
"preview": "{\n \"compilerOptions\": {\n \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n \"target\": \"ES2023\","
},
{
"path": "vite.config.ts",
"chars": 2494,
"preview": "import path from 'node:path'\nimport { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\nimport { viteStati"
}
]
About this extraction
This page contains the full source code of the Ovilia/ECharts-Theme-Builder GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 51 files (203.4 KB), approximately 54.8k tokens, and a symbol index with 21 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.