Commit 0f71878b by qlintonger xeno

feat: 添加其他图以及数字动画

parent 72139ecf
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.1.3", "@tailwindcss/vite": "^4.1.3",
"echarts": "^5.6.0", "echarts": "^5.6.0",
"gsap": "^3.13.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"v-scale-screen": "^2.3.0", "v-scale-screen": "^2.3.0",
"vue": "^3.4.31", "vue": "^3.4.31",
...@@ -1794,6 +1795,12 @@ ...@@ -1794,6 +1795,12 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/gsap": {
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.13.0.tgz",
"integrity": "sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw==",
"license": "Standard 'no charge' license: https://gsap.com/standard-license."
},
"node_modules/he": { "node_modules/he": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.1.3", "@tailwindcss/vite": "^4.1.3",
"echarts": "^5.6.0", "echarts": "^5.6.0",
"gsap": "^3.13.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"v-scale-screen": "^2.3.0", "v-scale-screen": "^2.3.0",
"vue": "^3.4.31", "vue": "^3.4.31",
......
...@@ -7,17 +7,124 @@ const sampleForLineCharts = { ...@@ -7,17 +7,124 @@ const sampleForLineCharts = {
data: [120, 200, 150, 80, 70, 110, 130], data: [120, 200, 150, 80, 70, 110, 130],
}; };
const sampleForPieCharts = { const sampleForRadarCharts = {
type: ChartCompType.EChartsPieCenterText, type: ChartCompType.SimpleRadar,
radius: ["60%", "90%"], indicators: [
title: "示例图表", { name: 'Sales', max: 6500 },
{ name: 'Administration', max: 16000 },
{ name: 'Information Technology', max: 30000 },
{ name: 'Customer Support', max: 38000 },
{ name: 'Development', max: 52000 },
{ name: 'Marketing', max: 25000 }
],
data: [
{
value: [4200, 3000, 20000, 35000, 50000, 18000],
name: 'Allocated Budget'
},
{
value: [5000, 14000, 28000, 26000, 42000, 21000],
name: 'Actual Spending'
}
]
}
const sampleForVerLineCharts = {
type: ChartCompType.SimpleLine,
category: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
data: [ data: [
{ name: "类型一", color: "#ffaacc", value: 100 }, { name: "测试1", data: [120, 200, 150, 80, 70, 110, 130] },
{ name: "类型二", color: "#abc001", value: 200 }, { name: "测试2", data: [100, 100, 110, 10, 10, 100, 100] },
{ name: "类型三", color: "#cad13c", value: 50 },
], ],
}; };
const sampleKLine = {
type: ChartCompType.DIY,
options: {
xAxis: {
data: ['2017-10-24', '2017-10-25', '2017-10-26', '2017-10-27']
},
yAxis: {},
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
},
appendTo: 'body'
},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true,
top: "5%",
},
series: [
{
type: 'candlestick',
data: [
[20, 34, 10, 38],
[40, 35, 30, 50],
[31, 38, 33, 44],
[38, 15, 5, 42]
]
}
]
}
}
const sampleScatter = {
type: ChartCompType.DIY,
options: {
xAxis: {},
yAxis: {},
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
},
appendTo: 'body'
},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true,
top: "5%",
},
series: [
{
symbolSize: 20,
data: [
[10.0, 8.04],
[8.07, 6.95],
[13.0, 7.58],
[9.05, 8.81],
[11.0, 8.33],
[14.0, 7.66],
[13.4, 6.81],
[10.0, 6.33],
[14.0, 8.96],
[12.5, 6.82],
[9.15, 7.2],
[11.5, 7.2],
[3.03, 4.23],
[12.2, 7.83],
[2.02, 4.47],
[1.05, 3.33],
[4.05, 4.96],
[6.03, 7.24],
[12.0, 6.26],
[12.0, 8.84],
[7.08, 5.82],
[5.02, 5.68]
],
type: 'scatter'
}
]
}
}
const bottomCenterTextCharts = { const bottomCenterTextCharts = {
title: "奇特上下排位图表", title: "奇特上下排位图表",
columns: 4, columns: 4,
...@@ -77,8 +184,8 @@ const indexMapper: Record<string, any> = { ...@@ -77,8 +184,8 @@ const indexMapper: Record<string, any> = {
chartData: [sampleForLineCharts, sampleForLineCharts], chartData: [sampleForLineCharts, sampleForLineCharts],
}, },
0: { 0: {
title: "简单混排图", title: "简单折线图",
chartData: [sampleForLineCharts, sampleForPieCharts], chartData: [sampleForVerLineCharts, sampleForVerLineCharts],
}, },
5: { 5: {
title: "啊哈哈表格呢", title: "啊哈哈表格呢",
...@@ -106,12 +213,12 @@ const indexMapper: Record<string, any> = { ...@@ -106,12 +213,12 @@ const indexMapper: Record<string, any> = {
], ],
}, },
1: { 1: {
title: "简单混排图", title: "随意设置图",
chartData: [sampleForLineCharts, sampleForPieCharts], chartData: [sampleKLine, sampleScatter],
}, },
6: { 6: {
title: "简单混排图", title: "简单混排图",
chartData: [sampleForLineCharts, sampleForPieCharts], chartData: [sampleForLineCharts, sampleForRadarCharts],
}, },
}; };
......
<template> <template>
<template v-if="ps.type === ChartCompType.EChartsBar"> <template
v-if="
ps.type === ChartCompType.EChartsBar ||
ps.type === ChartCompType.SimpleLine ||
ps.type === ChartCompType.SimpleRadar ||
ps.type === ChartCompType.DIY
"
>
<global-echarts <global-echarts
v-bind="$attrs" v-bind="$attrs"
:height="ps.item.chartWidth" :height="ps.item.chartWidth"
...@@ -61,10 +68,20 @@ ...@@ -61,10 +68,20 @@
:style="{ :style="{
fontSize: mapWidth(32) + 'px', fontSize: mapWidth(32) + 'px',
}" }"
v-if="!isNumber(ps.item.title)"
> >
{{ ps.item.title }} {{ ps.item.title }}
</div> </div>
<div <div
:style="{
fontSize: mapWidth(32) + 'px',
}"
v-else
v-number-animation="ps.item.title"
>
0
</div>
<div
:style="{ :style="{
marginTop: mapHeight(16) + 'px', marginTop: mapHeight(16) + 'px',
fontSize: mapWidth(14) + 'px', fontSize: mapWidth(14) + 'px',
...@@ -89,15 +106,14 @@ ...@@ -89,15 +106,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ECBasicOption } from "echarts/types/dist/shared"; import {ECBasicOption} from "echarts/types/dist/shared";
import { ChartCompType } from "../const/chartCompType.ts"; import {ChartCompType} from "../const/chartCompType";
import GlobalEcharts from "./global-echarts.vue"; import GlobalEcharts from "./global-echarts.vue";
import { import {GeneralChartOptions, SimpleTableChart,} from "../types/ScreenChart.typing";
GeneralChartOptions, import {mapHeight, mapWidth} from "../funcs/mapGEOStyle";
SimpleTableChart, import {computed} from "vue";
} from "../types/ScreenChart.typing.ts"; import {isNumber} from "lodash";
import { mapWidth, mapHeight } from "../funcs/mapGEOStyle.ts"; import {vNumberAnimation} from "../directive/number-animate.ts";
import { computed } from "vue";
interface Props { interface Props {
options?: ECBasicOption; options?: ECBasicOption;
......
...@@ -51,6 +51,9 @@ onMounted(() => { ...@@ -51,6 +51,9 @@ onMounted(() => {
const el = chartRef.value; const el = chartRef.value;
const inst = getCurrentInstance(); const inst = getCurrentInstance();
if (el) { if (el) {
if (ps.options!.series?.find((item: any) => item.type === "radar")) {
console.log('the ps -radar', ps.options)
}
nextTick(() => { nextTick(() => {
chartInstance = echarts.init(el); chartInstance = echarts.init(el);
setOptions(ps.options); setOptions(ps.options);
......
<script lang="ts" setup> <script lang="ts" setup>
import { ScreenChartProps } from "./types/ScreenChart.typing.ts"; import { ScreenChartProps } from "./types/ScreenChart.typing";
import { import {
getAllGridSize, getAllGridSize,
gridItemStyle, gridItemStyle,
mapContainerStyle, mapContainerStyle,
} from "./funcs/mapStyle.ts"; } from "./funcs/mapStyle";
import { mapToEchartsOptions } from "./funcs/mapToEchartsOptions.ts"; import { mapToEchartsOptions } from "./funcs/mapToEchartsOptions";
import { computed, onMounted, onUnmounted } from "vue"; import { computed, onMounted, onUnmounted } from "vue";
import ChartsWrapper from "./Components/charts-wrapper.vue"; import ChartsWrapper from "./Components/charts-wrapper.vue";
import { import {
...@@ -13,7 +13,7 @@ import { ...@@ -13,7 +13,7 @@ import {
isAllMounted, isAllMounted,
registerGraph, registerGraph,
} from "./funcs/procedureFunc.ts"; } from "./funcs/procedureFunc.ts";
import { onNew, onRemoved } from "./funcs/hooksFunc.ts"; import { onNew, onRemoved } from "./funcs/hooksFunc";
import { mapHeight } from "./funcs/mapGEOStyle"; import { mapHeight } from "./funcs/mapGEOStyle";
const props = withDefaults(defineProps<ScreenChartProps>(), { const props = withDefaults(defineProps<ScreenChartProps>(), {
......
...@@ -4,4 +4,7 @@ export enum ChartCompType { ...@@ -4,4 +4,7 @@ export enum ChartCompType {
EchartsPieBottomCenterText, EchartsPieBottomCenterText,
BottomCenterText, BottomCenterText,
SimpleTable, SimpleTable,
SimpleLine,
SimpleRadar,
DIY
} }
export const singleContainerClass = "overflow-auto grid justify-between"; export const singleContainerClass = "overflow-auto grid justify-between";
export const baseEchartOptions = (item: any) => ({
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
},
},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true,
top: "5%",
},
xAxis: {
type: "category",
data: item.category,
},
yAxis: {
type: "value",
boundaryGap: [0, 0.01],
},
});
import {Directive, DirectiveBinding, ref} from 'vue';
import gsap from 'gsap';
interface NumberAnimationOptions {
value?: number;
decimals?: number;
ease?: string;
formatter?: (value: number) => string;
}
type NumberAnimationBinding = number | NumberAnimationOptions;
export const vNumberAnimation: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding<NumberAnimationBinding>) {
const target = ref(0);
const duration = parseFloat(binding.arg as string) || 1;
const options = typeof binding.value === 'number'
? { value: binding.value }
: binding.value || {};
el.textContent = target.value.toString();
// @ts-ignore
el._animation = gsap.to(target, {
value: options.value || parseFloat(el.dataset.value || '0'),
duration,
ease: options.ease || 'power1.out',
onUpdate: () => {
el.textContent = options.formatter
? options.formatter(target.value)
: target.value.toFixed(options.decimals || 0);
}
});
},
updated(el: HTMLElement, binding: DirectiveBinding<NumberAnimationBinding>) {
// @ts-ignore
if (el._animation) {
// @ts-ignore
el._animation.kill();
}
// @ts-ignore
const target = ref(parseFloat(el.textContent));
const duration = parseFloat(binding.arg as string) || 1;
const options = typeof binding.value === 'number'
? { value: binding.value }
: binding.value || {};
// @ts-ignore
el._animation = gsap.to(target, {
value: options.value || parseFloat(el.dataset.value || '0'),
duration,
ease: options.ease || 'power1.out',
onUpdate: () => {
el.textContent = options.formatter
? options.formatter(target.value)
: target.value.toFixed(options.decimals || 0);
}
});
},
unmounted(el: HTMLElement) {
// @ts-ignore
if (el._animation) {
// @ts-ignore
el._animation.kill();
}
}
};
\ No newline at end of file
import { import {
BarChartOptions, BarChartOptions,
DIYChart,
GeneralChartOptions, GeneralChartOptions,
LineChartOptions,
PieCharOptions, PieCharOptions,
SimpleRadarChart
} from "../types/ScreenChart.typing.ts"; } from "../types/ScreenChart.typing.ts";
import { ChartCompType } from "../const/chartCompType.ts"; import {ChartCompType} from "../const/chartCompType.ts";
import {baseEchartOptions} from "../const/staticConsts.ts";
export function mapToEchartsOptions(item: GeneralChartOptions) { export function mapToEchartsOptions(item: GeneralChartOptions) {
if (item.type === ChartCompType.EChartsBar) { if (item.type === ChartCompType.DIY) {
let series: any[]; item = <DIYChart>item;
// 简单类型数据 return item.options;
item = <BarChartOptions>item; }
if (item.data.every((a) => typeof a === "number")) { if (item.type === ChartCompType.SimpleLine) {
series = [{ data: item.data, type: "bar" }]; let series: any[];
} else { item = <LineChartOptions>item;
series = item.data.map((a) => { if (item.data.every((a) => typeof a === "number")) {
series = [{data: item.data, type: "line"}];
} else {
series = item.data.map((a) => {
return {
type: "line",
name: a.name,
data: a.data,
};
});
}
return { return {
type: "bar", ...baseEchartOptions(item),
name: a.name, series,
data: a.data,
}; };
});
} }
return { if (item.type === ChartCompType.EChartsBar) {
tooltip: { let series: any[];
trigger: "axis", // 简单类型数据
axisPointer: { item = <BarChartOptions>item;
type: "shadow", if (item.data.every((a) => typeof a === "number")) {
}, series = [{data: item.data, type: "bar"}];
}, } else {
grid: { series = item.data.map((a) => {
left: "3%", return {
right: "4%", type: "bar",
bottom: "3%", name: a.name,
containLabel: true, data: a.data,
top: "5%", };
}, });
xAxis: { }
type: "category", return {
data: item.category, ...baseEchartOptions(item),
}, series,
yAxis: { };
type: "value", }
boundaryGap: [0, 0.01], if (
}, item.type === ChartCompType.EChartsPieCenterText ||
series, item.type === ChartCompType.EchartsPieBottomCenterText
}; ) {
} item = <PieCharOptions>item;
if ( const total = item.data.reduce((a, b) => a + b.value, 0);
item.type === ChartCompType.EChartsPieCenterText || return {
item.type === ChartCompType.EchartsPieBottomCenterText grid: {
) { left: 0,
item = <PieCharOptions>item; right: 0,
const total = item.data.reduce((a, b) => a + b.value, 0); bottom: 0,
return { containLabel: true,
grid: { top: 0,
left: 0, },
right: 0, series: [
bottom: 0, {
containLabel: true, radius: item.radius || undefined,
top: 0, type: "pie",
}, center: ["50%", "50%"],
series: [ avoidLabelOverlap: false,
{ emphasis: {
radius: item.radius || undefined, scaleSize: 3,
type: "pie", },
center: ["50%", "50%"], label: {
avoidLabelOverlap: false, show: false,
emphasis: { },
scaleSize: 3, labelLine: {
}, show: false,
label: { },
show: false, data: item.data.map(function (item) {
}, return {
labelLine: { value: item.value,
show: false, name: item.name,
}, itemStyle: {
data: item.data.map(function (item) { color: item.color,
return { },
value: item.value, };
name: item.name, }),
itemStyle: { },
color: item.color, ],
}, toolbox: false,
}; legend: item.legend || undefined,
}), tooltip: {
}, trigger: "item",
], formatter: function (param: any) {
toolbox: false, return `${param.name}${
legend: item.legend || undefined, total ? ((param.value * 100) / total).toFixed(2) : 100
tooltip: { }% ${param.value}`;
trigger: "item", },
formatter: function (param: any) { showDelay: 0,
return `${param.name}${ hideDelay: 0,
total ? ((param.value * 100) / total).toFixed(2) : 100 extraCssText: "pointer-events: none;",
}% ${param.value}`; position: "top",
}, appendTo: "body",
showDelay: 0, },
hideDelay: 0, };
extraCssText: "pointer-events: none;", }
position: "top", if (item.type === ChartCompType.SimpleRadar) {
appendTo: "body", item = <SimpleRadarChart>item;
}, return {
}; ...baseEchartOptions(item),
} xAxis: null,
yAxis: null,
tooltip: {
trigger: 'item',
appendTo: 'body'
},
radar: {
indicator: item.indicators || [],
},
legend: item.legend || undefined,
series: [
{
type: 'radar',
data: item.data,
areaStyle: {
color: 'rgb(188, 217, 251)' // 设置填充颜色
},
symbol: 'none', // 不显示圆点
symbolSize: 0 // 圆点大小设置为0
}
]
}
}
} }
...@@ -7,8 +7,11 @@ export type BarChartOptions = { ...@@ -7,8 +7,11 @@ export type BarChartOptions = {
type: ChartCompType; type: ChartCompType;
chartWidth?: number | string; chartWidth?: number | string;
chartHeight?: number | string; chartHeight?: number | string;
options?: any
}; };
export type LineChartOptions = BarChartOptions;
export type PieChartOptionsBase = { export type PieChartOptionsBase = {
type: ChartCompType; type: ChartCompType;
radius: Array<string>; radius: Array<string>;
...@@ -16,6 +19,7 @@ export type PieChartOptionsBase = { ...@@ -16,6 +19,7 @@ export type PieChartOptionsBase = {
chartWidth?: number | string; chartWidth?: number | string;
chartHeight?: number | string; chartHeight?: number | string;
legend?: any; legend?: any;
options?: any
}; };
export type PieCharOptions = PieChartOptionsBase & { export type PieCharOptions = PieChartOptionsBase & {
...@@ -26,7 +30,7 @@ export type PieCharOptionsForBottomCenterText = PieCharOptions & { ...@@ -26,7 +30,7 @@ export type PieCharOptionsForBottomCenterText = PieCharOptions & {
bottomText: string; bottomText: string;
}; };
export type BottonCenterText = { export type ButtonCenterText = {
title: string; title: string;
bottomTex: string; bottomTex: string;
type: ChartCompType; type: ChartCompType;
...@@ -36,14 +40,34 @@ export type SimpleTableChart = { ...@@ -36,14 +40,34 @@ export type SimpleTableChart = {
type: ChartCompType; type: ChartCompType;
columns: Array<{ title: string; key: string }>; columns: Array<{ title: string; key: string }>;
data: Array<Record<string, any>>; data: Array<Record<string, any>>;
options?: any
};
export type SimpleRadarChart = {
type: ChartCompType;
data: Array<{
name: string;
value: number[];
}>;
legend?: any;
indicators: Array<{ name: string; max: number }>;
options?: any
}; };
export type DIYChart = {
type: ChartCompType.DIY,
options: any,
chartWidth?: number | string;
chartHeight?: number | string;
}
export type GeneralChartOptions = export type GeneralChartOptions =
| BarChartOptions | BarChartOptions
| PieCharOptions | PieCharOptions
| PieCharOptionsForBottomCenterText | PieCharOptionsForBottomCenterText
| BottonCenterText | ButtonCenterText
| SimpleTableChart; | SimpleTableChart
| LineChartOptions | SimpleRadarChart | DIYChart;
export type ChartOptionSet = Array<GeneralChartOptions>; export type ChartOptionSet = Array<GeneralChartOptions>;
......
import {createApp} from 'vue' import { createApp } from "vue";
import './style.css' import "./style.css";
import App from './App.vue' import App from "./App.vue";
import {injectScreenChart} from "./lib/Table2Chart/plugin.inject.ts"; import { injectScreenChart } from "./lib/Table2Chart/plugin.inject.ts";
createApp(App).use(injectScreenChart).mount('#app') createApp(App).use(injectScreenChart).mount("#app");
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment