Commit 499b90b8 by qlintonger xeno

feat: 重设所有基本内容

parent 3b4a940c
<script lang="ts" setup>
import { layout } from "./layout.conf.ts";
</script>
<template>
<screen-chart :layout="layout" />
</template>
<style scoped></style>
import { ChartCompType } from "./lib/Table2Chart/const/chartCompType.ts";
import { mapHeight, mapWidth } from "./lib/Table2Chart/funcs/mapGEOStyle.ts";
const sampleForLineCharts = {
type: ChartCompType.EChartsBar,
category: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
data: [120, 200, 150, 80, 70, 110, 130],
};
const sampleForRadarCharts = {
type: ChartCompType.SimpleRadar,
indicators: [
{ name: 'Sales', max: 6500, min: 0 },
{ name: 'Administration', max: 16000, min: 0 },
{ name: 'Information Technology', max: 30000, min: 0 },
{ name: 'Customer Support', max: 38000, min: 0 },
{ name: 'Development', max: 52000, min: 0 },
{ name: 'Marketing', max: 25000, min: 0 }
],
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: [
{ name: "测试1", data: [120, 200, 150, 80, 70, 110, 130] },
{ name: "测试2", data: [100, 100, 110, 10, 10, 100, 100] },
],
};
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]
]
}
]
},
onClickGraph(item: any, index: number) {
alert('click-kline')
console.log('item-here', {item, index})
}
}
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'
}
]
},
onClickGraph(item: any, index: number) {
alert('click-scatter!')
console.log('item-here', {item, index})
}
}
const bottomCenterTextCharts = {
title: "奇特上下排位图表",
columns: 4,
chartData: Array(12)
.fill(2)
.map(() => ({
type: ChartCompType.EchartsPieBottomCenterText,
radius: ["60%", "90%"],
chartWidth: () => mapWidth(120),
chartHeight: () => mapHeight(120),
title: "示例图表",
data: [
{ name: "类型一", color: "#ffaacc", value: 100 },
{ name: "类型二", color: "#abc001", value: 200 },
{ name: "类型三", color: "#cad13c", value: 50 },
],
bottomText: "奇特数据呢!",
})),
};
const indexMapper: Record<string, any> = {
7: bottomCenterTextCharts,
3: {
title: "简单数字内容显示",
columns: 2,
chartData: Array(2)
.fill(1)
.map(() => ({
title: 15,
bottomText: "奇特数据",
type: ChartCompType.BottomCenterText,
})),
},
2: {
title: "混编数据",
columns: 2,
chartData: [
{
type: ChartCompType.BottomCenterText,
title: 100,
bottomText: "奇特数据",
},
{
type: ChartCompType.EChartsPieCenterText,
radius: ["60%", "90%"],
title: "示例图表",
data: [
{ name: "类型一", color: "#ffaacc", value: 100 },
{ name: "类型二", color: "#abc001", value: 200 },
{ name: "类型三", color: "#cad13c", value: 50 },
],
},
],
},
4: {
title: "简单混排图",
chartData: [sampleForLineCharts, sampleForLineCharts],
},
0: {
title: "简单折线图",
chartData: [sampleForVerLineCharts, sampleForVerLineCharts],
},
5: {
title: "啊哈哈表格呢",
columns: 1,
chartData: [
{
type: ChartCompType.SimpleTable,
columns: [
{ key: "a", title: "种类一" },
{ key: "b", title: "种类二" },
{ key: "c", title: "中类似" },
{ key: "d", title: "奇特呢" },
],
data: Array(20)
.fill(1)
.map(() => {
return {
a: 12,
b: 123,
c: 434,
d: 3123,
};
}),
},
],
},
1: {
title: "随意设置图",
chartData: [sampleKLine, sampleScatter],
onClickTitle(item: any) {
alert('click-random')
console.log('item-here', {item})
}
},
6: {
title: "简单混排图",
chartData: [sampleForLineCharts, sampleForRadarCharts],
},
};
const layout = [
{ x: 0, y: 0, w: 4, h: 1 },
{ x: 4, y: 0, w: 8, h: 1 },
{ x: 0, y: 2, w: 2, h: 1 },
{ x: 2, y: 2, w: 2, h: 1 },
{ x: 4, y: 2, w: 3, h: 1 },
{ x: 7, y: 1, w: 5, h: 4 },
{ x: 0, y: 1, w: 7, h: 1 },
{ x: 0, y: 3, w: 7, h: 2 },
].map((a, q) => ({
...a,
...indexMapper[q],
}));
export { layout };
<template>
<template
v-if="
ps.type === ChartCompType.EChartsBar ||
ps.type === ChartCompType.SimpleLine ||
ps.type === ChartCompType.SimpleRadar ||
ps.type === ChartCompType.DIY
"
>
<global-echarts
v-bind="$attrs"
:height="ps.item.chartWidth"
:width="ps.item.chartHeight"
:options="ps.options"
/>
</template>
<template v-else-if="ps.type === ChartCompType.EChartsPieCenterText">
<div class="relative w-full h-full">
<global-echarts
v-bind="$attrs"
:width="ps.item.chartWidth"
:height="ps.item.chartHeight"
:options="ps.options"
/>
<div
class="absolute z-10 top-[50%] left-[50%]"
style="transform: translate(-50%, -50%)"
:style="{
fontSize: mapWidth(14) + 'px',
}"
>
{{ ps.item.title }}
</div>
</div>
</template>
<template v-else-if="ps.type === ChartCompType.EchartsPieBottomCenterText">
<div class="flex flex-col items-center justify-center">
<div class="relative">
<global-echarts
v-bind="$attrs"
:width="ps.item.chartWidth"
:height="ps.item.chartHeight"
:options="ps.options"
/>
<div
class="absolute z-10 top-[50%] left-[50%]"
style="transform: translate(-50%, -50%)"
:style="{
fontSize: mapWidth(14) + 'px',
}"
>
{{ ps.item.title }}
</div>
</div>
<div
:style="{
marginTop: mapHeight(16) + 'px',
fontSize: mapWidth(14) + 'px',
}"
>
{{ ps.item.bottomText }}
</div>
</div>
</template>
<template v-else-if="ps.type === ChartCompType.BottomCenterText">
<div class="flex flex-col items-center justify-center">
<div
:style="{
fontSize: mapWidth(32) + 'px',
}"
v-if="!isNumber(ps.item.title)"
>
{{ ps.item.title }}
</div>
<div
:style="{
fontSize: mapWidth(32) + 'px',
}"
v-else
v-number-animation="ps.item.title"
>
0
</div>
<div
:style="{
marginTop: mapHeight(16) + 'px',
fontSize: mapWidth(14) + 'px',
}"
>
{{ ps.item.bottomText }}
</div>
</div>
</template>
<template v-else-if="ps.type === ChartCompType.SimpleTable">
<div class="w-full h-full overflow-auto">
<table class="w-full">
<thead>
<th v-for="(q, w) in tableInside.tableHead" :key="w">{{ q }}</th>
</thead>
<tr v-for="(q, w) in tableInside.rowsDataEach" :key="w">
<td v-for="(z, c) in q" :key="c">{{ z }}</td>
</tr>
</table>
</div>
</template>
</template>
<script setup lang="ts">
import {ECBasicOption} from "echarts/types/dist/shared";
import {ChartCompType} from "../const/chartCompType";
import GlobalEcharts from "./global-echarts.vue";
import {GeneralChartOptions, SimpleTableChart,} from "../types/ScreenChart.typing";
import {mapHeight, mapWidth} from "../funcs/mapGEOStyle";
import {computed} from "vue";
import {isNumber} from "lodash";
import {vNumberAnimation} from "../directive/number-animate.ts";
interface Props {
options?: ECBasicOption;
width?: string | number;
height?: string | number;
type: ChartCompType;
item: GeneralChartOptions;
}
const tableFont = computed(function () {
return mapWidth(12) + "px";
});
const paddingForTD = computed(() => {
return `${mapHeight(10)}px ${mapWidth(12)}px`;
});
const ps = withDefaults(defineProps<Props>(), {
options: () => {
return {};
},
width: "100%",
height: "100%",
type: ChartCompType.EChartsBar,
});
const tableInside = computed(() => {
const { data, columns } = ps.item as SimpleTableChart;
const tableHead = columns.map((vc: any) => vc.title) as string[];
const rowsDataEach = data.map((a: any) => {
return columns.map((v) => a[v.key]);
});
return {
tableHead,
rowsDataEach,
};
});
</script>
<style scoped>
table,
td,
tr,
th {
border-collapse: collapse;
}
td,
th {
border: 1px solid #c2c2c2;
font-size: v-bind(tableFont);
text-align: center;
padding: v-bind(paddingForTD);
}
th {
font-weight: bold;
}
</style>
<script lang="ts" setup>
import { ScreenChartProps } from "./types/ScreenChart.typing";
import {
getAllGridSize,
gridItemStyle,
mapContainerStyle,
} from "./funcs/mapStyle";
import { mapToEchartsOptions } from "./funcs/mapToEchartsOptions";
import { computed, onMounted, onUnmounted } from "vue";
import ChartsWrapper from "./Components/charts-wrapper.vue";
import {
containerRef,
isAllMounted,
registerGraph,
} from "./funcs/procedureFunc.ts";
import { onNew, onRemoved } from "./funcs/hooksFunc";
import { mapHeight } from "./funcs/mapGEOStyle";
import {ScreenChartProps} from "./types/ScreenChart.typing";
import {onMounted, onUnmounted} from "vue";
import {onNew, onRemoved} from "./funcs/hooksFunc";
const props = withDefaults(defineProps<ScreenChartProps>(), {
gap: 16,
......@@ -24,53 +11,13 @@ const props = withDefaults(defineProps<ScreenChartProps>(), {
onUnmounted(function () {
onRemoved();
});
const gridConfig = computed(() => {
return getAllGridSize(props.layout);
});
onMounted(function () {
console.log("passing props here", props);
onNew();
});
import { singleContainerClass } from "./const/staticConsts.ts";
</script>
<template>
<div
:style="mapContainerStyle(props)"
class="w-full h-full pr"
ref="containerRef"
>
<div
v-for="(item, w) in props.layout"
:key="w"
:style="gridItemStyle(item, props, gridConfig)"
class="flex flex-col"
>
<div @click="item.onClickTitle ? item.onClickTitle(item) : () => {}" v-if="!item.renderTitle" class="w-full">{{ item.title }}</div>
<component :is="item.renderTitle(item, props.layout)" v-else />
<div
v-if="isAllMounted"
class="w-full"
:class="item.wrapperClass || singleContainerClass"
:style="{
height: `calc(100% - ${mapHeight(24)}px)`,
marginTop: `${mapHeight(12)}px`,
gridTemplateColumns: `repeat(${item.columns || item.chartData.length}, 1fr)`,
rowGap: mapHeight(20) + 'px',
...<Object>(typeof item.wrapperStyle === 'function' ? item.wrapperStyle() : typeof item.wrapperStyle === 'object' ? item.wrapperStyle : {}),
}"
>
<ChartsWrapper
:type="z.type"
:item="z"
@node-inserted="registerGraph"
v-for="(z, x) in item.chartData"
:key="x"
:options="mapToEchartsOptions(z)"
@click="z.onClickGraph && z.onClickGraph(z, x)"
/>
</div>
</div>
</div>
</template>
export const singleContainerClass = "overflow-auto grid justify-between";
export const baseEchartOptions = (item: any) => ({
tooltip: {
trigger: "axis",
......
import {
allGraphRegister,
containerRef,
isAllMounted,
startNow,
} from "./procedureFunc.ts";
import { nextTick } from "vue";
import {containerRef, startNow,} from "./procedureFunc.ts";
import {nextTick} from "vue";
let ro: ResizeObserver | null = null;
......@@ -13,7 +8,6 @@ export function onRemoved() {
ro.disconnect();
ro = null;
}
allGraphRegister.value = [];
}
export function onNew() {
......@@ -26,6 +20,6 @@ export function onNew() {
startNow();
}
}).then(function () {
isAllMounted.value = true;
});
}
import {ScreenChartProps, SingleLayoutConf} from "../types/ScreenChart.typing.ts";
import {geoContainer, mapHeight, mapWidth} from "./mapGEOStyle.ts";
export const gridItemStyle = function (item: SingleLayoutConf, props: ScreenChartProps, geoConf: { rows: number, cols: number}) {
return {
position: 'absolute',
background: '#f1f1f1',
boxSizing: 'border-box',
// @ts-ignore
padding: `${mapHeight(props.gridItemPadding)}px ${mapWidth(props.gridItemPadding)}px ${mapHeight(props.gridItemPadding)}px ${mapWidth(props.gridItemPadding)}px`,
fontSize: `${mapWidth(14)}px`,
...wholeGridGeo(item, geoConf, props.gap!),
transition: 'left 0.3s ease, top 0.3 ease, width 0.3 ease, height 0.3 ease',
}
}
export const wholeGridGeo = function (item: SingleLayoutConf, geoConf: { rows: number, cols: number}, gap: number) {
const {rows, cols} = geoConf
const cellWidth = (geoContainer.value.contentWidth - (cols - 1) * gap) / cols
const cellHeight = (geoContainer.value.contentHeight - (rows - 1) * gap) / rows
const left = item.x * (cellWidth + gap) + geoContainer.value.paddingAll;
const top = item.y * (cellHeight + gap) + geoContainer.value.paddingAll;
const width = item.w * cellWidth + (item.w - 1) * gap;
const height = item.h * cellHeight + (item.h - 1) * gap;
return {
left: `${left}px`,
top: `${top}px`,
width: `${width}px`,
height: `${height}px`,
};
}
export function getAllGridSize(items: Array<SingleLayoutConf>) {
let maxCol = 0;
let maxRow = 0;
for (const item of items) {
// 计算当前元素的右边界和下边界
const rightEdge = item.x + item.w;
const bottomEdge = item.y + item.h;
// 更新最大列数和行数
if (rightEdge > maxCol) {
maxCol = rightEdge;
}
if (bottomEdge > maxRow) {
maxRow = bottomEdge;
}
}
return { rows: maxRow, cols: maxCol };
}
export const mapContainerStyle = function (props: ScreenChartProps) {
return {
display: 'grid',
boxSizing: 'border-box',
// @ts-ignore
padding: `${mapHeight(props.containerPadding)}px ${mapWidth(props.containerPadding)}px ${mapHeight(props.containerPadding)}px ${mapWidth(props.containerPadding)}px`,
}
}
\ No newline at end of file
import {
BarChartOptions,
DIYChart,
GeneralChartOptions,
LineChartOptions,
PieCharOptions,
SimpleRadarChart
} from "../types/ScreenChart.typing.ts";
import {ChartCompType} from "../const/chartCompType.ts";
import {baseEchartOptions} from "../const/staticConsts.ts";
export function mapToEchartsOptions(item: GeneralChartOptions) {
if (item.type === ChartCompType.DIY) {
item = <DIYChart>item;
return item.options;
}
if (item.type === ChartCompType.SimpleLine) {
let series: any[];
item = <LineChartOptions>item;
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 {
...baseEchartOptions(item),
series,
};
}
if (item.type === ChartCompType.EChartsBar) {
let series: any[];
// 简单类型数据
item = <BarChartOptions>item;
if (item.data.every((a) => typeof a === "number")) {
series = [{data: item.data, type: "bar"}];
} else {
series = item.data.map((a) => {
return {
type: "bar",
name: a.name,
data: a.data,
};
});
}
return {
...baseEchartOptions(item),
series,
};
}
if (
item.type === ChartCompType.EChartsPieCenterText ||
item.type === ChartCompType.EchartsPieBottomCenterText
) {
item = <PieCharOptions>item;
const total = item.data.reduce((a, b) => a + b.value, 0);
return {
grid: {
left: 0,
right: 0,
bottom: 0,
containLabel: true,
top: 0,
},
series: [
{
radius: item.radius || undefined,
type: "pie",
center: ["50%", "50%"],
avoidLabelOverlap: false,
emphasis: {
scaleSize: 3,
},
label: {
show: false,
},
labelLine: {
show: false,
},
data: item.data.map(function (item) {
return {
value: item.value,
name: item.name,
itemStyle: {
color: item.color,
},
};
}),
},
],
toolbox: false,
legend: item.legend || undefined,
tooltip: {
trigger: "item",
formatter: function (param: any) {
return `${param.name}${
total ? ((param.value * 100) / total).toFixed(2) : 100
}% ${param.value}`;
},
showDelay: 0,
hideDelay: 0,
extraCssText: "pointer-events: none;",
position: "top",
appendTo: "body",
},
};
}
if (item.type === ChartCompType.SimpleRadar) {
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
}
]
}
}
}
import { nextTick, ref } from "vue";
import { geoContainer } from "./mapGEOStyle.ts";
import {ref} from "vue";
export const containerRef = ref();
export const isAllMounted = ref(false);
export const allGraphRegister = ref<Array<{ uid: string; f: Function }>>([]);
export function registerGraph(item: { uid: string; f: Function }) {
allGraphRegister.value.push(item);
}
export const startNow = function calcGEO() {
const rect = containerRef.value.getBoundingClientRect();
geoContainer.value.width = rect.width;
geoContainer.value.height = rect.height;
const allStyle: any = window.getComputedStyle(containerRef.value);
// @ts-ignore
geoContainer.value.contentWidth =
parseFloat(allStyle["width"]) -
parseFloat(allStyle["padding-left"]) -
parseFloat(allStyle["padding-right"]);
// @ts-ignore
geoContainer.value.contentHeight =
parseFloat(allStyle["height"]) -
parseFloat(allStyle["padding-top"]) -
parseFloat(allStyle["padding-bottom"]);
// @ts-ignore
geoContainer.value.paddingAll = parseFloat(allStyle["paddingLeft"]);
nextTick(function () {
for (const item of allGraphRegister.value) {
const chart: any = item.f();
chart.resize();
}
}).then(function () {});
};
import { VNode } from "vue";
import { ChartCompType } from "../const/chartCompType.ts";
export type BarChartOptions = {
category: string[];
data: number[] | { name: string; data: number[] }[];
type: ChartCompType;
chartWidth?: number | string;
chartHeight?: number | string;
options?: any
onClickGraph?: Function;
};
export type LineChartOptions = BarChartOptions;
export type PieChartOptionsBase = {
type: ChartCompType;
radius: Array<string>;
data: Array<{ value: number; name: string; color: string }>;
chartWidth?: number | string;
chartHeight?: number | string;
legend?: any;
options?: any
onClickGraph?: Function;
};
export type PieCharOptions = PieChartOptionsBase & {
title: string;
};
export type PieCharOptionsForBottomCenterText = PieCharOptions & {
bottomText: string;
};
export type ButtonCenterText = {
title: string;
bottomTex: string;
type: ChartCompType;
onClickGraph?: Function;
};
export type SimpleTableChart = {
type: ChartCompType;
columns: Array<{ title: string; key: string }>;
data: Array<Record<string, any>>;
options?: any
onClickGraph?: Function;
};
export type SimpleRadarChart = {
type: ChartCompType;
data: Array<{
name: string;
value: number[];
}>;
legend?: any;
indicators: Array<{ name: string; max: number }>;
options?: any
onClickGraph?: Function;
};
export type DIYChart = {
type: ChartCompType.DIY,
options: any,
chartWidth?: number | string;
chartHeight?: number | string;
onClickGraph?: Function;
}
export type GeneralChartOptions =
| BarChartOptions
| PieCharOptions
| PieCharOptionsForBottomCenterText
| ButtonCenterText
| SimpleTableChart
| LineChartOptions | SimpleRadarChart | DIYChart;
export type ChartOptionSet = Array<GeneralChartOptions>;
export type SingleLayoutConf = {
x: number;
y: number;
w: number;
h: number;
title: string;
renderTitle?: (...args: any[]) => VNode;
chartData: ChartOptionSet;
wrapperClass?: string | Array<string> | Record<string, any>;
wrapperStyle?: Record<string, any> | Function;
columns?: number;
onClickTitle?: Function;
};
export type LayoutConfig = Array<SingleLayoutConf>;
export type ScreenChartProps = {
layout: LayoutConfig;
gap?: number;
containerPadding?: number;
gridItemPadding?: number;
};
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