feat: colors
diff --git a/package-lock.json b/package-lock.json
index af8bb1e..2766bfc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -308,6 +308,30 @@
"defer-to-connect": "^1.0.1"
}
},
+ "@types/color": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmmirror.com/@types/color/download/@types/color-3.0.2.tgz",
+ "integrity": "sha1-N3kEPngvViqpFXtfxr0H4U/Y5/M=",
+ "dev": true,
+ "requires": {
+ "@types/color-convert": "*"
+ }
+ },
+ "@types/color-convert": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/@types/color-convert/download/@types/color-convert-2.0.0.tgz",
+ "integrity": "sha1-j17muehj3L7lcD9aUX/7E9PqTiI=",
+ "dev": true,
+ "requires": {
+ "@types/color-name": "*"
+ }
+ },
+ "@types/color-name": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/@types/color-name/download/@types/color-name-1.1.1.tgz",
+ "integrity": "sha1-HBJhu+qhCoBVu8XYq4S3sq/IRqA=",
+ "dev": true
+ },
"@types/glob": {
"version": "7.1.4",
"resolved": "https://registry.nlark.com/@types/glob/download/@types/glob-7.1.4.tgz",
@@ -1000,11 +1024,19 @@
"integrity": "sha1-5jYpwAFmZXkgYNu+t5xCI50sUoc=",
"dev": true
},
+ "color": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/color/download/color-4.1.0.tgz",
+ "integrity": "sha512-o2rkkxyLGgYoeUy1OodXpbPAQNmlNBrirQ8ODO8QutzDiDMNdezSOZLNnusQ6pUpCQJUsaJIo9DZJKqa2HgH7A==",
+ "requires": {
+ "color-convert": "^2.0.1",
+ "color-string": "^1.9.0"
+ }
+ },
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npm.taobao.org/color-convert/download/color-convert-2.0.1.tgz",
"integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=",
- "dev": true,
"requires": {
"color-name": "~1.1.4"
}
@@ -1012,8 +1044,16 @@
"color-name": {
"version": "1.1.4",
"resolved": "http://registry.npm.taobao.org/color-name/download/color-name-1.1.4.tgz",
- "integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=",
- "dev": true
+ "integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI="
+ },
+ "color-string": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmmirror.com/color-string/download/color-string-1.9.0.tgz",
+ "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==",
+ "requires": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
},
"colors": {
"version": "1.4.0",
@@ -3925,6 +3965,21 @@
"integrity": "sha1-NmpGhNF1ucqyCB42gf2jdHtsUdc=",
"dev": true
},
+ "simple-swizzle": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmmirror.com/simple-swizzle/download/simple-swizzle-0.2.2.tgz",
+ "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=",
+ "requires": {
+ "is-arrayish": "^0.3.1"
+ },
+ "dependencies": {
+ "is-arrayish": {
+ "version": "0.3.2",
+ "resolved": "http://registry.npm.taobao.org/is-arrayish/download/is-arrayish-0.3.2.tgz",
+ "integrity": "sha1-RXSirlb3qyBolvtDHq7tBm/fjwM="
+ }
+ }
+ },
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npm.taobao.org/slash/download/slash-3.0.0.tgz?cache=0&sync_timestamp=1618384508676&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fslash%2Fdownload%2Fslash-3.0.0.tgz",
diff --git a/package.json b/package.json
index 1ba51f0..c79c5aa 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
},
"devDependencies": {
"@babel/polyfill": "^7.12.1",
+ "@types/color": "^3.0.2",
"@vitejs/plugin-vue": "^1.2.2",
"@vue/compiler-sfc": "^3.0.11",
"chalk": "^3.0.0",
@@ -23,6 +24,7 @@
"yargs": "^6.6.0"
},
"dependencies": {
+ "color": "^4.1.0",
"echarts": "^5.2.2",
"echarts-wordcloud": "file:../echarts-wordcloud",
"element-plus": "^1.0.2-beta.44",
diff --git a/src/App.vue b/src/App.vue
index bd3ffef..c5d1655 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,26 +1,26 @@
<template>
-<el-container>
+ <el-container>
<el-aside>
- <h3>
- {{$t('title')}}
- </h3>
- <el-tabs type="card" v-model="activeName">
- <el-tab-pane label="样式" name="config">
- <WConfig ref="wconfig" @change="onChange"></WConfig>
- </el-tab-pane>
- <el-tab-pane label="数据" name="data">
- <WData ref="wdata" @change="onChange"></WData>
- </el-tab-pane>
- <el-tab-pane label="导出" name="export">导出</el-tab-pane>
- </el-tabs>
+ <h3>
+ {{ $t('title') }}
+ </h3>
+ <el-tabs type="card" v-model="activeName">
+ <el-tab-pane label="样式" name="config">
+ <WConfig ref="wconfig" @change="onChange"></WConfig>
+ </el-tab-pane>
+ <el-tab-pane label="数据" name="data">
+ <WData ref="wdata" @change="onChange"></WData>
+ </el-tab-pane>
+ <el-tab-pane label="导出" name="export">导出</el-tab-pane>
+ </el-tabs>
</el-aside>
<el-main>
- <WChart ref="wchart"></WChart>
+ <WChart ref="wchart"></WChart>
</el-main>
-</el-container>
+ </el-container>
</template>
-<script lang='ts' setup>
+<script lang="ts" setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import WChart from './components/WChart.vue';
@@ -32,41 +32,43 @@
const wdata = ref<any>(null);
const wchart = ref<any>(null);
-const {t} = useI18n({ useScope: 'global' });
+const { t } = useI18n({ useScope: 'global' });
const activeName = 'config';
function onChange() {
- wchart.value?.run(wdata.value?.data, wconfig.value?.getConfig());
+ wchart.value?.run(wdata.value?.data, wconfig.value?.getConfig());
}
setTimeout(() => {
- wdata.value?.setData(defaultData);
+ wdata.value?.setData(defaultData);
});
</script>
<style>
h4 {
- margin: 10px 0;
+ margin: 10px 0;
}
</style>
<style scoped lang="scss">
#echarts-spa-app {
- font-family: Avenir, Helvetica, Arial, sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- text-align: center;
- color: #2c3e50;
+ font-family: Avenir, Helvetica, Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ text-align: center;
+ color: #2c3e50;
- position: absolute;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
}
.el-aside {
- padding: 0 15px;
- --el-aside-width: 400px;
+ padding: 0 15px;
+ --el-aside-width: 400px;
+ height: calc(100vh - 50px);
+ overflow: auto;
}
</style>
diff --git a/src/components/WChart.vue b/src/components/WChart.vue
index f351378..037d5ab 100644
--- a/src/components/WChart.vue
+++ b/src/components/WChart.vue
@@ -8,6 +8,7 @@
import { onMounted, shallowRef, ref } from 'vue';
import * as echarts from 'echarts';
import 'echarts-wordcloud';
+import Color from 'color';
// const props = defineProps({
// foo: String
@@ -20,7 +21,8 @@
});
type Config = {
- hue: number[];
+ bgColor: string;
+ themeColors: string[];
saturation: number[];
lightness: number[];
alpha: number[];
@@ -33,8 +35,32 @@
};
function run(data?: [], config?: Config) {
- config && console.log(config.width, config.height);
+ const hues = config
+ ? config.themeColors.map(
+ color =>
+ Color(color)
+ .hsl()
+ .object().h
+ )
+ : [];
+
+ function getHue() {
+ const index = Math.floor(Math.random() * hues.length);
+ return hues[index];
+ }
+
+ function getRandom(minMax: number[] | undefined) {
+ if (!minMax) {
+ return 0;
+ }
+ const max = minMax[1] == null ? 1 : minMax[1];
+ const min = minMax[0] == null ? 0 : minMax[0];
+ const range = max - min || 1;
+ return Math.random() * range + min;
+ }
+
chart.value!.setOption({
+ backgroundColor: config!.bgColor,
series: [
{
type: 'wordCloud',
@@ -46,7 +72,17 @@
width: config?.width + '%',
height: config?.height + '%',
layoutAnimation: true,
- keepAspect: true
+ keepAspect: true,
+ textStyle: {
+ color: (param: any) => {
+ const value = param.value;
+ const h = getHue();
+ const s = getRandom(config?.saturation);
+ const l = getRandom(config?.lightness);
+ const color = Color(`hsl(${h}, ${s}%, ${l}%)`);
+ return color.toString();
+ }
+ }
}
],
textStyle: {
diff --git a/src/components/WConfig.vue b/src/components/WConfig.vue
index 1085151..37a11ff 100644
--- a/src/components/WConfig.vue
+++ b/src/components/WConfig.vue
@@ -1,69 +1,88 @@
<template>
<el-collapse>
<el-collapse-item title="颜色" name="color">
- <!-- <h5>基础色</h5>
- <div>
- <el-color-picker
- v-for="(color, index) in themeColors"
- v-bind:key="index"
- v-model="themeColors[index]"
- size="small"
- >
- </el-color-picker>
- <div class="color-picker-btn">
- <i class="el-icon-minus"></i>
- </div>
- <div class="color-picker-btn">
- <i class="el-icon-plus"></i>
- </div>
- </div> -->
+ <h5>配色方案</h5>
+ <div>
+ <div
+ class="color-palette"
+ v-for="palette in colorPalettes"
+ :style="{ background: palette.bgColor }"
+ @click="useColorPalette(palette)"
+ >
+ <div
+ class="color"
+ v-for="color in palette.themeColors"
+ :style="{ background: color }"
+ ></div>
+ </div>
+ </div>
+
+ <h5>背景色</h5>
+ <el-color-picker v-model="bgColor" size="small" @change="change">
+ </el-color-picker>
<h5>色相范围</h5>
<el-row>
- <el-col :span="22" :offset="1">
- <el-slider
- v-model="hue"
- range
- show-tooltip
- :max="1"
- :step="0.05"
- input-size="medium"
+ <el-col :span="14">
+ <el-color-picker
+ v-for="(color, index) in themeColors"
+ v-bind:key="index"
+ v-model="themeColors[index]"
+ size="small"
+ @change="changeColor"
>
- </el-slider>
+ </el-color-picker>
+ <div class="color-picker-btn" @click="removeThemeColor()">
+ <i class="el-icon-minus"></i>
+ </div>
+ <div class="color-picker-btn" @click="addThemeColor()">
+ <i class="el-icon-plus"></i>
+ </div>
+ </el-col>
+ <el-col :span="8" :offset="2">
+ <el-checkbox>关联数据大小</el-checkbox>
</el-col>
</el-row>
<h5>饱和度范围</h5>
<el-row>
- <el-col :span="22" :offset="1">
+ <el-col :span="13" :offset="1">
<el-slider
v-model="saturation"
range
show-tooltip
- :max="1"
- :step="0.05"
+ :max="100"
+ :step="1"
input-size="medium"
+ @change="change"
>
</el-slider>
</el-col>
+ <el-col :span="8" :offset="2">
+ <el-checkbox>关联数据大小</el-checkbox>
+ </el-col>
</el-row>
<h5>亮度范围</h5>
<el-row>
- <el-col :span="22" :offset="1">
+ <el-col :span="13" :offset="1">
<el-slider
v-model="lightness"
range
show-tooltip
- :max="1"
- :step="0.05"
+ :max="100"
+ :step="1"
input-size="medium"
+ @change="change"
>
</el-slider>
</el-col>
+ <el-col :span="8" :offset="2">
+ <el-checkbox>关联数据大小</el-checkbox>
+ </el-col>
</el-row>
- <h5>透明度范围</h5>
+ <!-- <h5>透明度范围</h5>
<el-row>
<el-col :span="22" :offset="1">
<el-slider
@@ -76,7 +95,7 @@
>
</el-slider>
</el-col>
- </el-row>
+ </el-row> -->
</el-collapse-item>
<el-collapse-item title="文字" name="font">
@@ -108,7 +127,7 @@
v-model="fontSize"
range
show-tooltip
- :max="100"
+ :max="200"
:step="1"
input-size="medium"
@change="change"
@@ -191,68 +210,97 @@
</template>
<script setup lang="ts">
-import { ref } from "vue";
-// const themeColors = ref(['#720FEB', '#EB1AA9', '#B6DA02']);
-const hue = ref([0, 255]);
-const saturation = ref([0.5, 0.8]);
-const lightness = ref([0.5, 0.8]);
+import { ref } from 'vue';
+import Color from 'color';
+
+const colorPalettes = [
+ {
+ bgColor: '#f8eddc',
+ themeColors: ['#f19d70', '#b7b7a4', '#6b705c']
+ },
+ {
+ bgColor: '#eef6fa',
+ themeColors: ['#5470c6', '#91cc75', '#fac858', '#ee6666']
+ },
+ {
+ bgColor: '#dbf3ff',
+ themeColors: ['#4aa6d5', '#d3b16a', '#fb8500']
+ },
+ // {
+ // bgColor: '#eae2b7',
+ // themeColors: ['#003049', '#d62828', '#f77f00', '#fcbf49']
+ // },
+ {
+ bgColor: '#fbffd1',
+ themeColors: ['#132a13', '#90a955', '#ecf39e']
+ }
+];
+
+const bgColor = ref(colorPalettes[0].bgColor);
+const themeColors = ref(colorPalettes[0].themeColors);
+const saturation = ref([50, 80]);
+const lightness = ref([50, 80]);
const alpha = ref([0.5, 0.8]);
-const selectedFontFamily = ref("Arial");
+const selectedFontFamily = ref('Arial');
const fontSize = ref([4, 100]);
const rotate = ref([-90, 90]);
const width = ref(90);
const height = ref(90);
-const selectedMask = ref("circle");
+const selectedMask = ref('circle');
const fontFamilies = [
- "Arial",
- "Lato",
- "Times New Roman",
- "Courier New",
- "Georgia",
- "Helvetica",
- "Lucida Sans",
- "Tahoma",
- "Verdana"
+ 'Arial',
+ 'Lato',
+ 'Times New Roman',
+ 'Courier New',
+ 'Georgia',
+ 'Helvetica',
+ 'Lucida Sans',
+ 'Tahoma',
+ 'Verdana'
];
const masks = [
{
- name: "椭圆",
- value: "circle"
+ name: '椭圆',
+ value: 'circle'
},
{
- name: "方形",
- value: "square"
+ name: '方形',
+ value: 'square'
},
{
- name: "菱形",
- value: "diamond"
+ name: '菱形',
+ value: 'diamond'
},
{
- name: "三角形",
- value: "triangle"
+ name: '三角形',
+ value: 'triangle'
},
{
- name: "五边形",
- value: "pentagon"
+ name: '五边形',
+ value: 'pentagon'
},
{
- name: "五角星",
- value: "star"
+ name: '五角星',
+ value: 'star'
},
{
- name: "爱心",
- value: "cardioid"
+ name: '爱心',
+ value: 'cardioid'
// }, {
// name: '自定义图片',
// value: 'image'
}
];
-const emit = defineEmits(["change"]);
+const emit = defineEmits(['change']);
defineExpose({ getConfig });
+setTimeout(() => {
+ changeColor();
+}, 0);
+
function changeFontFamily() {
change();
}
@@ -261,13 +309,64 @@
change();
}
+function changeColor() {
+ let minS = 100;
+ let maxS = 0;
+ let minL = 100;
+ let maxL = 0;
+ themeColors.value.forEach(color => {
+ const c = Color(color);
+ const s = Math.round(c.saturationv());
+ const l = Math.round(c.lightness());
+ if (s < minS) {
+ minS = s;
+ }
+ if (s > maxS) {
+ maxS = s;
+ }
+ if (l < minL) {
+ minL = l;
+ }
+ if (l > maxL) {
+ maxL = l;
+ }
+ });
+ saturation.value = [minS, maxS];
+ lightness.value = [minL, maxL];
+ change();
+}
+
function change() {
- emit("change");
+ emit('change');
+}
+
+function useColorPalette(palette: { bgColor: string; themeColors: string[] }) {
+ themeColors.value = palette.themeColors;
+ bgColor.value = palette.bgColor;
+ changeColor();
+}
+
+function removeThemeColor() {
+ if (themeColors.value.length === 1) {
+ return;
+ }
+ themeColors.value.splice(themeColors.value.length - 1, 1);
+ emit('change');
+}
+
+function addThemeColor() {
+ themeColors.value.push(
+ themeColors.value.length === 0
+ ? '#555'
+ : themeColors.value[themeColors.value.length - 1]
+ );
+ emit('change');
}
function getConfig() {
return {
- hue: hue.value,
+ bgColor: bgColor.value,
+ themeColors: themeColors.value,
saturation: saturation.value,
lightness: lightness.value,
alpha: alpha.value,
@@ -276,7 +375,7 @@
rotate: rotate.value,
width: width.value,
height: height.value,
- shape: selectedMask.value === "image" ? null : selectedMask.value
+ shape: selectedMask.value === 'image' ? null : selectedMask.value
};
}
</script>
@@ -296,7 +395,8 @@
margin-right: 5px;
}
-.color-picker-btn {
+.color-picker-btn,
+.color-palette {
display: inline-block;
width: 30px;
height: 30px;
@@ -309,6 +409,32 @@
color: #409eff;
}
+.color-palette {
+ display: inline-block;
+ margin-bottom: 5px;
+ width: auto;
+ height: 33px;
+ padding: 5px;
+ cursor: pointer;
+
+ .color {
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ margin-right: 5px;
+ border-radius: 3px;
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+}
+
+.el-checkbox {
+ --el-checkbox-font-color: #888;
+ margin-top: 7px;
+}
+
.text-pad {
padding: 8px 0;
}