This is an automated email from the ASF dual-hosted git repository. ovilia pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/echarts-wordcloud-generator.git
commit b24c4e583cddc401a101a43390bbb7726292ff08 Author: Ovilia <zwl.s...@gmail.com> AuthorDate: Wed Dec 22 11:05:35 2021 +0800 feat: colors --- package-lock.json | 61 ++++++++++- package.json | 2 + src/App.vue | 66 ++++++------ src/components/WChart.vue | 42 +++++++- src/components/WConfig.vue | 264 +++++++++++++++++++++++++++++++++------------ 5 files changed, 328 insertions(+), 107 deletions(-) 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 wconfig = ref<any>(null); 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 @@ defineExpose({ }); type Config = { - hue: number[]; + bgColor: string; + themeColors: string[]; saturation: number[]; lightness: number[]; alpha: number[]; @@ -33,8 +35,32 @@ type Config = { }; 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 @@ function run(data?: [], config?: Config) { 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 @@ function changeMask() { 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 @@ function getConfig() { 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 @@ function getConfig() { margin-right: 5px; } -.color-picker-btn { +.color-picker-btn, +.color-palette { display: inline-block; width: 30px; height: 30px; @@ -309,6 +409,32 @@ function getConfig() { 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; } --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@echarts.apache.org For additional commands, e-mail: commits-h...@echarts.apache.org