Commit 72139ecf by qlintonger xeno

fix: 添加额外图表类型呢

parent 2113a2c9
<script lang="ts" setup>
import {ChartCompType} from "./lib/Table2Chart/const/chartCompType.ts";
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, title: `示例图${q + 1}`, chartData: Math.random() > .6 ? [
{
type: ChartCompType.EChartsBar,
category: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
data: [120, 200, 150, 80, 70, 110, 130]
},
{
type: ChartCompType.EChartsBar,
category: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
data: [120, 200, 150, 80, 70, 110, 130]
}
] : [
{
type: ChartCompType.EChartsBar,
category: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
data: [120, 200, 150, 80, 70, 110, 130]
},
]
}));
import { layout } from "./layout.conf.ts";
</script>
<template>
<screen-chart :layout="layout"/>
<screen-chart :layout="layout" />
</template>
<style scoped>
</style>
<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 sampleForPieCharts = {
type: ChartCompType.EChartsPieCenterText,
radius: ["60%", "90%"],
title: "示例图表",
data: [
{ name: "类型一", color: "#ffaacc", value: 100 },
{ name: "类型二", color: "#abc001", value: 200 },
{ name: "类型三", color: "#cad13c", value: 50 },
],
};
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: [sampleForLineCharts, sampleForPieCharts],
},
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: [sampleForLineCharts, sampleForPieCharts],
},
6: {
title: "简单混排图",
chartData: [sampleForLineCharts, sampleForPieCharts],
},
};
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">
<global-echarts v-bind="$attrs" :height="ps.item.chartWidth" :width="ps.item.chartHeight" :options="ps.options"/>
<global-echarts
v-bind="$attrs"
:height="ps.item.chartWidth"
:width="ps.item.chartHeight"
:options="ps.options"
/>
</template>
<template v-else-if="ps.type === ChartCompType.EChartsPie">
<div class="flex flex-col items-center">
<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',
}"
>
{{ ps.item.title }}
</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.ts";
import { ECBasicOption } from "echarts/types/dist/shared";
import { ChartCompType } from "../const/chartCompType.ts";
import GlobalEcharts from "./global-echarts.vue";
import {GeneralChartOptions} from "../types/ScreenChart.typing.ts";
import {
GeneralChartOptions,
SimpleTableChart,
} from "../types/ScreenChart.typing.ts";
import { mapWidth, mapHeight } from "../funcs/mapGEOStyle.ts";
import { computed } from "vue";
interface Props {
options?: ECBasicOption;
width?: string | number;
height?: string | number;
type: ChartCompType;
item: GeneralChartOptions
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
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>
......@@ -4,14 +4,22 @@
<script setup lang="ts">
import * as echarts from "echarts";
import {ECBasicOption} from "echarts/types/dist/shared";
import {isNumber} from "lodash";
import {computed, getCurrentInstance, nextTick, onMounted, onUnmounted, ref, watch} from "vue";
import { ECBasicOption } from "echarts/types/dist/shared";
import { isNumber } from "lodash";
import {
computed,
getCurrentInstance,
nextTick,
onMounted,
onUnmounted,
ref,
watch,
} from "vue";
interface Props {
options?: ECBasicOption;
width?: string | number;
height?: string | number;
width?: string | number | Function;
height?: string | number | Function;
}
const ps = withDefaults(defineProps<Props>(), {
options: () => {
......@@ -21,30 +29,32 @@ const ps = withDefaults(defineProps<Props>(), {
height: "100%",
});
const getWidth = computed(() => {
if (isNumber(ps.width)) {
return ps.width + "px";
let targetWidth = typeof ps.width === "function" ? ps.width() : ps.width;
if (isNumber(targetWidth)) {
return targetWidth + "px";
} else {
return ps.width;
return targetWidth;
}
});
const getHeight = computed(() => {
if (isNumber(ps.height)) {
return ps.height + "px";
let targetHeight = typeof ps.height === "function" ? ps.height() : ps.height;
if (isNumber(targetHeight)) {
return targetHeight + "px";
} else {
return ps.height;
return targetHeight;
}
});
const chartRef = ref(null);
let chartInstance = null as unknown as echarts.ECharts;
const es = defineEmits(['node-inserted'])
const es = defineEmits(["node-inserted"]);
onMounted(() => {
const el = chartRef.value;
const inst = getCurrentInstance()
const inst = getCurrentInstance();
if (el) {
nextTick(() => {
chartInstance = echarts.init(el);
setOptions(ps.options);
es('node-inserted', {uid: inst!.uid, f: () => chartInstance})
es("node-inserted", { uid: inst!.uid, f: () => chartInstance });
chartInstance?.resize();
});
}
......@@ -66,7 +76,7 @@ const setOptions = (options: ECBasicOption) => {
};
defineExpose({
getChartInstance: () => chartInstance,
getDom: () => chartRef.value
getDom: () => chartRef.value,
});
</script>
<style scoped>
......
<script lang="ts" setup>
import {ScreenChartProps} from "./types/ScreenChart.typing.ts";
import {getAllGridSize, gridItemStyle, mapContainerStyle} from "./funcs/mapStyle.ts";
import {mapToEchartsOptions} from "./funcs/mapToEchartsOptions.ts";
import {computed, onMounted, onUnmounted} from "vue";
import { ScreenChartProps } from "./types/ScreenChart.typing.ts";
import {
getAllGridSize,
gridItemStyle,
mapContainerStyle,
} from "./funcs/mapStyle.ts";
import { mapToEchartsOptions } from "./funcs/mapToEchartsOptions.ts";
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.ts";
import {
containerRef,
isAllMounted,
registerGraph,
} from "./funcs/procedureFunc.ts";
import { onNew, onRemoved } from "./funcs/hooksFunc.ts";
import { mapHeight } from "./funcs/mapGEOStyle";
const props = withDefaults(defineProps<ScreenChartProps>(), {
gap: 16, containerPadding: 12, gridItemPadding: 12
})
gap: 16,
containerPadding: 12,
gridItemPadding: 12,
});
onUnmounted(function () {
onRemoved()
})
onRemoved();
});
const gridConfig = computed(() => {
return getAllGridSize(props.layout)
})
return getAllGridSize(props.layout);
});
onMounted(function () {
onNew()
})
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
: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 v-if="!item.renderTitle" class="w-full">{{ item.title }}</div>
<component :is="item.renderTitle(item, props.layout)" v-else/>
<div v-if="isAllMounted" class="mt-[12px] w-full flex items-stretch justify-between"
style="height: calc(100% - 12px - 12px)">
<ChartsWrapper :type="z.type" :item="z" @node-inserted="registerGraph" v-for="(z,x) in item.chartData" :key="x" :options="mapToEchartsOptions(z)"/>
<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)"
/>
</div>
</div>
</div>
......
export enum ChartCompType {
EChartsBar,
EChartsPie
EChartsPieCenterText,
EchartsPieBottomCenterText,
BottomCenterText,
SimpleTable,
}
export const singleContainerClass = "overflow-auto grid justify-between";
import {allGraphRegister, containerRef, isAllMounted, startNow} from "./procedureFunc.ts";
import {nextTick} from "vue";
import {
allGraphRegister,
containerRef,
isAllMounted,
startNow,
} from "./procedureFunc.ts";
import { nextTick } from "vue";
let ro: ResizeObserver | null = null
let ro: ResizeObserver | null = null;
export function onRemoved() {
if (ro) {
ro.disconnect()
ro = null
ro.disconnect();
ro = null;
}
allGraphRegister.value = []
allGraphRegister.value = [];
}
export function onNew() {
nextTick(function () {
}).then(function () {
isAllMounted.value = true
if (!ro) {
ro = new ResizeObserver(startNow)
ro.observe(containerRef.value, {box: 'border-box'})
startNow()
ro = new ResizeObserver(function () {
startNow();
});
ro.observe(containerRef.value, { box: "border-box" });
startNow();
}
})
}).then(function () {
isAllMounted.value = true;
});
}
import {BarChartOptions, GeneralChartOptions, PieCharOptions} from "../types/ScreenChart.typing.ts";
import {ChartCompType} from "../const/chartCompType.ts";
import {
BarChartOptions,
GeneralChartOptions,
PieCharOptions,
} from "../types/ScreenChart.typing.ts";
import { ChartCompType } from "../const/chartCompType.ts";
export function mapToEchartsOptions(item: GeneralChartOptions) {
if (item.type === ChartCompType.EChartsBar) {
let series: any[]
let series: any[];
// 简单类型数据
item = <BarChartOptions>item;
if (item.data.every(a=>typeof a === 'number')) {
series = [{data: item.data, type:'bar'}]
if (item.data.every((a) => typeof a === "number")) {
series = [{ data: item.data, type: "bar" }];
} else {
series = item.data.map((a) => {
return {
type: 'bar',
type: "bar",
name: a.name,
data: a.data
}
})
data: a.data,
};
});
}
return {
tooltip: {
trigger: 'axis',
trigger: "axis",
axisPointer: {
type: 'shadow'
}
type: "shadow",
},
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true,
top: '5%'
top: "5%",
},
xAxis: {
type: 'category',
type: "category",
data: item.category,
},
yAxis: {
type: 'value',
boundaryGap: [0, 0.01]
type: "value",
boundaryGap: [0, 0.01],
},
series
series,
};
}
}
if (item.type === ChartCompType.EChartsPie) {
if (
item.type === ChartCompType.EChartsPieCenterText ||
item.type === ChartCompType.EchartsPieBottomCenterText
) {
item = <PieCharOptions>item;
const total = item.data.reduce((a, b) => a + b.value, 0);
return {
legend: {
top: '5%',
left: 'center'
grid: {
left: 0,
right: 0,
bottom: 0,
containLabel: true,
top: 0,
},
radius: item.radius,
type: 'pie',
center: ['50%', '50%'],
series: [
{
radius: item.radius || undefined,
type: "pie",
center: ["50%", "50%"],
avoidLabelOverlap: false,
emphasis: {
scaleSize: 3,
......@@ -68,11 +80,14 @@ export function mapToEchartsOptions(item: GeneralChartOptions) {
value: item.value,
name: item.name,
itemStyle: {
color: item.color
}
}
color: item.color,
},
};
}),
},
],
toolbox: false,
legend: item.legend || undefined,
tooltip: {
trigger: "item",
formatter: function (param: any) {
......@@ -86,6 +101,6 @@ export function mapToEchartsOptions(item: GeneralChartOptions) {
position: "top",
appendTo: "body",
},
}
};
}
}
import {nextTick, ref} from "vue";
import {geoContainer} from "./mapGEOStyle.ts";
import { nextTick, ref } from "vue";
import { geoContainer } from "./mapGEOStyle.ts";
export const containerRef = ref()
export const isAllMounted = ref(false)
export const allGraphRegister = ref<Array<{ uid: string, f: Function }>>([])
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 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 = window.getComputedStyle(containerRef.value)
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'])
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'])
geoContainer.value.contentHeight =
parseFloat(allStyle["height"]) -
parseFloat(allStyle["padding-top"]) -
parseFloat(allStyle["padding-bottom"]);
// @ts-ignore
geoContainer.value.paddingAll = parseFloat(allStyle['paddingLeft'])
geoContainer.value.paddingAll = parseFloat(allStyle["paddingLeft"]);
nextTick(function () {
}).then(function () {
for (const item of allGraphRegister.value) {
const chart: any = item.f()
const chart: any = item.f();
chart.resize();
}
})
}
\ No newline at end of file
}).then(function () {});
};
import {VNode} from "vue";
import {ChartCompType} from "../const/chartCompType.ts";
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
category: string[];
data: number[] | { name: string; data: number[] }[];
type: ChartCompType;
chartWidth?: number | string;
chartHeight?: number | string;
};
export type PieCharOptions = {
type: ChartCompType,
title: string,
radius: Array<string>,
data: Array<{value: number, name: string, color: string}>,
centerText?: string | Function
chartWidth?: number | string
chartHeight?: number | string
}
export type PieChartOptionsBase = {
type: ChartCompType;
radius: Array<string>;
data: Array<{ value: number; name: string; color: string }>;
chartWidth?: number | string;
chartHeight?: number | string;
legend?: any;
};
export type PieCharOptions = PieChartOptionsBase & {
title: string;
};
export type GeneralChartOptions = BarChartOptions | PieCharOptions
export type PieCharOptionsForBottomCenterText = PieCharOptions & {
bottomText: string;
};
export type ChartOptionSet = Array<GeneralChartOptions>
export type BottonCenterText = {
title: string;
bottomTex: string;
type: ChartCompType;
};
export type SimpleTableChart = {
type: ChartCompType;
columns: Array<{ title: string; key: string }>;
data: Array<Record<string, any>>;
};
export type GeneralChartOptions =
| BarChartOptions
| PieCharOptions
| PieCharOptionsForBottomCenterText
| BottonCenterText
| SimpleTableChart;
export type ChartOptionSet = Array<GeneralChartOptions>;
export type SingleLayoutConf = {
x: number, y: number, w: number, h: number, title: string, renderTitle?: (...args: any[]) => VNode,
chartData: ChartOptionSet
}
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;
};
export type LayoutConfig = Array<SingleLayoutConf>
export type LayoutConfig = Array<SingleLayoutConf>;
export type ScreenChartProps = {
layout: LayoutConfig;
gap?: number;
containerPadding?: number;
gridItemPadding?: number;
}
\ No newline at end of file
};
@import "tailwindcss";
@import "tailwindcss/utilities";
html, body, #app {
html,
body,
#app {
display: block;
width: 100%;
height: 100%;
}
/* 滚动轴 */
::-webkit-scrollbar {
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
width: 3px;
height: 3px;
}
::-webkit-scrollbar-thumb {
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
background-color: var(--scrollbar-color, #a1a1a1);
background-clip: padding-box;
min-height: 28px;
}
::-webkit-scrollbar-thumb:hover {
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
background-color: var(--scrollbar-color-hover, #a1a1a1);
}
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