@liujitcn/kratos-admin-bi 0.0.0-stage → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -2
- package/dist/package/declarations/index.d.ts +1 -0
- package/dist/package/declarations/src/api/bi/admin/v1/bi_card.d.ts +18 -0
- package/dist/package/declarations/src/api/bi/admin/v1/bi_card.js +46 -0
- package/dist/package/declarations/src/api/bi/admin/v1/bi_dashboard.d.ts +17 -0
- package/dist/package/declarations/src/api/bi/admin/v1/bi_dashboard.js +30 -0
- package/dist/package/declarations/src/api/bi/admin/v1/bi_query.d.ts +11 -0
- package/dist/package/declarations/src/api/bi/admin/v1/bi_query.js +41 -0
- package/dist/package/declarations/src/api/bi/admin/v1/bi_query_record.d.ts +10 -0
- package/dist/package/declarations/src/api/bi/admin/v1/bi_query_record.js +14 -0
- package/dist/package/declarations/src/api/bi/admin/v1/bi_source.d.ts +26 -0
- package/dist/package/declarations/src/api/bi/admin/v1/bi_source.js +46 -0
- package/dist/package/declarations/src/bi-core.d.ts +17 -0
- package/dist/package/declarations/src/bi-core.js +22 -0
- package/dist/package/declarations/src/bi-dashboard-schema.d.ts +31 -0
- package/dist/package/declarations/src/bi-dashboard-schema.js +39 -0
- package/dist/package/declarations/src/bi-messages.d.ts +247 -0
- package/dist/package/declarations/src/bi-messages.js +247 -0
- package/dist/package/declarations/src/bi-response.d.ts +10 -0
- package/dist/package/declarations/src/bi-response.js +33 -0
- package/dist/package/declarations/src/bi-vue.d.ts +10 -0
- package/dist/package/declarations/src/bi-vue.js +10 -0
- package/dist/package/declarations/src/card-schema.d.ts +30 -0
- package/dist/package/declarations/src/card-schema.js +87 -0
- package/dist/package/declarations/src/components/BiCardChartEditor.vue.d.ts +17 -0
- package/dist/package/declarations/src/components/BiCardChartEditor.vue.js +527 -0
- package/dist/package/declarations/src/components/BiCardFormDialog.vue.d.ts +20 -0
- package/dist/package/declarations/src/components/BiCardFormDialog.vue.js +495 -0
- package/dist/package/declarations/src/components/BiCardLibrary.vue.d.ts +3 -0
- package/dist/package/declarations/src/components/BiCardLibrary.vue.js +807 -0
- package/dist/package/declarations/src/components/BiCardVisualization.vue.d.ts +10 -0
- package/dist/package/declarations/src/components/BiCardVisualization.vue.js +97 -0
- package/dist/package/declarations/src/components/BiChart.vue.d.ts +20 -0
- package/dist/package/declarations/src/components/BiChart.vue.js +112 -0
- package/dist/package/declarations/src/components/BiDashboard.vue.d.ts +9 -0
- package/dist/package/declarations/src/components/BiDashboard.vue.js +81 -0
- package/dist/package/declarations/src/components/BiDashboardMenuDialog.vue.d.ts +12 -0
- package/dist/package/declarations/src/components/BiDashboardMenuDialog.vue.js +252 -0
- package/dist/package/declarations/src/components/BiDataCard.vue.d.ts +36 -0
- package/dist/package/declarations/src/components/BiDataCard.vue.js +372 -0
- package/dist/package/declarations/src/components/BiDesigner.vue.d.ts +17 -0
- package/dist/package/declarations/src/components/BiDesigner.vue.js +666 -0
- package/dist/package/declarations/src/components/BiSourceModelDialog.vue.d.ts +17 -0
- package/dist/package/declarations/src/components/BiSourceModelDialog.vue.js +2118 -0
- package/dist/package/declarations/src/components/BiWorkbenchPreview.vue.d.ts +7 -0
- package/dist/package/declarations/src/components/BiWorkbenchPreview.vue.js +176 -0
- package/dist/package/declarations/src/components/source-model-layout.d.ts +3 -0
- package/dist/package/declarations/src/components/source-model-layout.js +66 -0
- package/dist/package/declarations/src/dataset/index.d.ts +7 -0
- package/dist/package/declarations/src/dataset/index.js +25 -0
- package/dist/package/declarations/src/dataset/labels.d.ts +2 -0
- package/dist/package/declarations/src/dataset/labels.js +21 -0
- package/dist/package/declarations/src/dataset/transform.d.ts +20 -0
- package/dist/package/declarations/src/dataset/transform.js +173 -0
- package/dist/package/declarations/src/engine/chart-types.d.ts +14 -0
- package/dist/package/declarations/src/engine/chart-types.js +37 -0
- package/dist/package/declarations/src/engine/echarts.d.ts +16 -0
- package/dist/package/declarations/src/engine/echarts.js +491 -0
- package/dist/package/declarations/src/engine/types.d.ts +36 -0
- package/dist/package/declarations/src/engine/types.js +20 -0
- package/dist/package/declarations/src/env.d.ts +17 -0
- package/dist/package/declarations/src/env.js +67 -0
- package/dist/package/declarations/src/index.d.ts +1 -0
- package/dist/package/declarations/src/index.js +1 -0
- package/dist/package/declarations/src/locales/en-US.json +254 -0
- package/dist/package/declarations/src/locales/generated.d.ts +1021 -0
- package/dist/package/declarations/src/locales/generated.js +13 -0
- package/dist/package/declarations/src/locales/ja-JP.json +254 -0
- package/dist/package/declarations/src/locales/zh-CN.json +254 -0
- package/dist/package/declarations/src/locales/zh-TW.json +254 -0
- package/dist/package/declarations/src/module.d.ts +2 -0
- package/dist/package/declarations/src/module.js +9 -0
- package/dist/package/declarations/src/rpc/bi/admin/v1/bi_card.d.ts +145 -0
- package/dist/package/declarations/src/rpc/bi/admin/v1/bi_card.js +6 -0
- package/dist/package/declarations/src/rpc/bi/admin/v1/bi_dashboard.d.ts +107 -0
- package/dist/package/declarations/src/rpc/bi/admin/v1/bi_dashboard.js +6 -0
- package/dist/package/declarations/src/rpc/bi/admin/v1/bi_query.d.ts +157 -0
- package/dist/package/declarations/src/rpc/bi/admin/v1/bi_query.js +6 -0
- package/dist/package/declarations/src/rpc/bi/admin/v1/bi_query_record.d.ts +83 -0
- package/dist/package/declarations/src/rpc/bi/admin/v1/bi_query_record.js +15 -0
- package/dist/package/declarations/src/rpc/bi/admin/v1/bi_source.d.ts +301 -0
- package/dist/package/declarations/src/rpc/bi/admin/v1/bi_source.js +6 -0
- package/dist/package/declarations/src/rpc/common/v1/types.d.ts +60 -0
- package/dist/package/declarations/src/rpc/common/v1/types.js +6 -0
- package/dist/package/declarations/src/schema/layout.d.ts +7 -0
- package/dist/package/declarations/src/schema/layout.js +52 -0
- package/dist/package/declarations/src/schema/types.d.ts +83 -0
- package/dist/package/declarations/src/schema/types.js +1 -0
- package/dist/package/declarations/src/theme/index.d.ts +29 -0
- package/dist/package/declarations/src/theme/index.js +83 -0
- package/dist/package/declarations/src/theme/palettes.d.ts +7 -0
- package/dist/package/declarations/src/theme/palettes.js +28 -0
- package/dist/package/declarations/src/theme/presets.d.ts +73 -0
- package/dist/package/declarations/src/theme/presets.js +235 -0
- package/dist/package/declarations/src/views/card/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/card/index.vue.js +18 -0
- package/dist/package/declarations/src/views/dashboard/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/dashboard/index.vue.js +107 -0
- package/dist/package/declarations/src/views/designer/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/designer/index.vue.js +332 -0
- package/dist/package/declarations/src/views/manage/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/manage/index.vue.js +325 -0
- package/dist/package/declarations/src/views/query/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/query/index.vue.js +876 -0
- package/dist/package/declarations/src/views/source/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/source/index.vue.js +343 -0
- package/dist/package/declarations/src/views/workbench/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/workbench/index.vue.js +362 -0
- package/dist/package/src/api/bi/admin/v1/bi_card.ts +67 -0
- package/dist/package/src/api/bi/admin/v1/bi_dashboard.ts +51 -0
- package/dist/package/src/api/bi/admin/v1/bi_query.ts +55 -0
- package/dist/package/src/api/bi/admin/v1/bi_query_record.ts +25 -0
- package/dist/package/src/api/bi/admin/v1/bi_source.ts +80 -0
- package/dist/package/src/bi-core.ts +43 -0
- package/dist/package/src/bi-dashboard-schema.ts +69 -0
- package/dist/package/src/bi-messages.ts +249 -0
- package/dist/package/src/bi-response.ts +39 -0
- package/dist/package/src/bi-vue.ts +13 -0
- package/dist/package/src/card-schema.ts +111 -0
- package/dist/package/src/components/BiCardChartEditor.vue +139 -0
- package/dist/package/src/components/BiCardFormDialog.vue +289 -0
- package/dist/package/src/components/BiCardLibrary.vue +310 -0
- package/dist/package/src/components/BiCardVisualization.vue +95 -0
- package/dist/package/src/components/BiChart.vue +134 -0
- package/dist/package/src/components/BiDashboard.vue +53 -0
- package/dist/package/src/components/BiDashboardMenuDialog.vue +177 -0
- package/dist/package/src/components/BiDataCard.vue +231 -0
- package/dist/package/src/components/BiDesigner.vue +287 -0
- package/dist/package/src/components/BiSourceModelDialog.vue +846 -0
- package/dist/package/src/components/BiWorkbenchPreview.vue +131 -0
- package/dist/package/src/components/source-model-layout.ts +62 -0
- package/dist/package/src/dataset/index.ts +28 -0
- package/dist/package/src/dataset/labels.ts +19 -0
- package/dist/package/src/dataset/transform.ts +189 -0
- package/dist/package/src/engine/chart-types.ts +44 -0
- package/dist/package/src/engine/echarts.ts +568 -0
- package/dist/package/src/engine/types.ts +60 -0
- package/dist/package/src/env.ts +82 -0
- package/dist/package/src/index.ts +1 -0
- package/dist/package/src/locales/en-US.json +254 -0
- package/dist/package/src/locales/generated.ts +16 -0
- package/dist/package/src/locales/ja-JP.json +254 -0
- package/dist/package/src/locales/zh-CN.json +254 -0
- package/dist/package/src/locales/zh-TW.json +254 -0
- package/dist/package/src/module.ts +12 -0
- package/dist/package/src/rpc/bi/admin/v1/bi_card.ts +169 -0
- package/dist/package/src/rpc/bi/admin/v1/bi_dashboard.ts +126 -0
- package/dist/package/src/rpc/bi/admin/v1/bi_query.ts +187 -0
- package/dist/package/src/rpc/bi/admin/v1/bi_query_record.ts +98 -0
- package/dist/package/src/rpc/bi/admin/v1/bi_source.ts +338 -0
- package/dist/package/src/rpc/common/v1/types.ts +77 -0
- package/dist/package/src/schema/layout.ts +54 -0
- package/dist/package/src/schema/types.ts +103 -0
- package/dist/package/src/styles.scss +1 -0
- package/dist/package/src/theme/index.ts +108 -0
- package/dist/package/src/theme/palettes.ts +34 -0
- package/dist/package/src/theme/presets.ts +278 -0
- package/dist/package/src/views/card/index.vue +5 -0
- package/dist/package/src/views/dashboard/index.vue +58 -0
- package/dist/package/src/views/designer/index.vue +164 -0
- package/dist/package/src/views/manage/index.vue +276 -0
- package/dist/package/src/views/query/index.vue +593 -0
- package/dist/package/src/views/source/index.vue +214 -0
- package/dist/package/src/views/workbench/index.vue +208 -0
- package/package.json +67 -3
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="host" class="bi-workbench-preview" :aria-busy="loading">
|
|
3
|
+
<el-skeleton v-if="loading" :rows="2" animated />
|
|
4
|
+
<div v-else-if="error" class="bi-workbench-preview__empty" role="alert">
|
|
5
|
+
<span>{{ error }}</span>
|
|
6
|
+
<el-button link type="primary" @click="load">{{ t("bi.card.retry") }}</el-button>
|
|
7
|
+
</div>
|
|
8
|
+
<template v-else>
|
|
9
|
+
<div v-for="item in layout.cards.slice(0, 2)" :key="item.id" class="bi-workbench-preview__card">
|
|
10
|
+
<h4 :title="item.name">{{ item.name }}</h4>
|
|
11
|
+
<div class="bi-workbench-preview__chart">
|
|
12
|
+
<BiCardVisualization
|
|
13
|
+
v-if="results.get(item.id)"
|
|
14
|
+
:card="dashboardSnapshotCard(item)"
|
|
15
|
+
:result="results.get(item.id)!"
|
|
16
|
+
compact
|
|
17
|
+
/>
|
|
18
|
+
<span v-else class="bi-workbench-preview__empty">{{ t("common.message.no_data") }}</span>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
<span v-if="!layout.cards.length" class="bi-workbench-preview__empty">{{ t("bi.dashboard.empty") }}</span>
|
|
22
|
+
</template>
|
|
23
|
+
</div>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<script setup lang="ts">
|
|
27
|
+
import { onBeforeUnmount, ref } from "vue";
|
|
28
|
+
import { useIntersectionObserver } from "@vueuse/core";
|
|
29
|
+
import { t } from "@liujitcn/kratos-admin-core";
|
|
30
|
+
import type { BiDashboard } from "../rpc/bi/admin/v1/bi_dashboard";
|
|
31
|
+
import type { BiQueryResult } from "../rpc/bi/admin/v1/bi_query";
|
|
32
|
+
import type { DashboardLayout } from "../bi-dashboard-schema";
|
|
33
|
+
import { dashboardResults, dashboardSnapshotCard, parseDashboardLayout } from "../bi-dashboard-schema";
|
|
34
|
+
import { defBiDashboardService } from "../api/bi/admin/v1/bi_dashboard";
|
|
35
|
+
import BiCardVisualization from "./BiCardVisualization.vue";
|
|
36
|
+
|
|
37
|
+
const props = defineProps<{ dashboard: BiDashboard }>();
|
|
38
|
+
const host = ref<HTMLElement>();
|
|
39
|
+
const layout = ref<DashboardLayout>({ schemaVersion: 3, name: "", cards: [] });
|
|
40
|
+
const results = ref(new Map<string, BiQueryResult | undefined>());
|
|
41
|
+
const loading = ref(true);
|
|
42
|
+
const error = ref("");
|
|
43
|
+
let active = true;
|
|
44
|
+
const { stop } = useIntersectionObserver(host, ([entry]) => {
|
|
45
|
+
if (entry?.isIntersecting) {
|
|
46
|
+
stop();
|
|
47
|
+
void load();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/** 入口可见时按发布版本取真实数据,缩略图复用卡片渲染器。 */
|
|
52
|
+
async function load() {
|
|
53
|
+
loading.value = true;
|
|
54
|
+
error.value = "";
|
|
55
|
+
try {
|
|
56
|
+
const data = await defBiDashboardService.GetBiDashboardData({
|
|
57
|
+
id: props.dashboard.id,
|
|
58
|
+
version: props.dashboard.published_version
|
|
59
|
+
});
|
|
60
|
+
if (!active) return;
|
|
61
|
+
layout.value = parseDashboardLayout(data.schema_json);
|
|
62
|
+
results.value = dashboardResults(data);
|
|
63
|
+
} catch (cause) {
|
|
64
|
+
if (active) error.value = cause instanceof Error ? cause.message : t("bi.query.error");
|
|
65
|
+
} finally {
|
|
66
|
+
if (active) loading.value = false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
onBeforeUnmount(() => {
|
|
71
|
+
active = false;
|
|
72
|
+
});
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<style scoped>
|
|
76
|
+
.bi-workbench-preview {
|
|
77
|
+
display: grid;
|
|
78
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
79
|
+
align-items: stretch;
|
|
80
|
+
gap: 8px;
|
|
81
|
+
box-sizing: border-box;
|
|
82
|
+
height: 144px;
|
|
83
|
+
padding: 16px;
|
|
84
|
+
background: var(--admin-page-card-bg-soft);
|
|
85
|
+
border-bottom: 1px solid var(--el-border-color-lighter);
|
|
86
|
+
pointer-events: none;
|
|
87
|
+
}
|
|
88
|
+
.bi-workbench-preview__card {
|
|
89
|
+
display: flex;
|
|
90
|
+
min-width: 0;
|
|
91
|
+
min-height: 0;
|
|
92
|
+
flex-direction: column;
|
|
93
|
+
gap: 6px;
|
|
94
|
+
padding: 10px;
|
|
95
|
+
overflow: hidden;
|
|
96
|
+
background: var(--admin-page-card-bg);
|
|
97
|
+
border: 1px solid var(--el-border-color-light);
|
|
98
|
+
border-radius: var(--admin-page-radius);
|
|
99
|
+
}
|
|
100
|
+
.bi-workbench-preview h4 {
|
|
101
|
+
margin: 0;
|
|
102
|
+
overflow: hidden;
|
|
103
|
+
color: var(--el-text-color-secondary);
|
|
104
|
+
font-size: 11px;
|
|
105
|
+
font-weight: 400;
|
|
106
|
+
line-height: 16px;
|
|
107
|
+
text-overflow: ellipsis;
|
|
108
|
+
white-space: nowrap;
|
|
109
|
+
}
|
|
110
|
+
.bi-workbench-preview__chart {
|
|
111
|
+
flex: 1;
|
|
112
|
+
min-height: 0;
|
|
113
|
+
}
|
|
114
|
+
.bi-workbench-preview__empty,
|
|
115
|
+
.bi-workbench-preview > .el-skeleton {
|
|
116
|
+
grid-column: 1 / -1;
|
|
117
|
+
}
|
|
118
|
+
.bi-workbench-preview__empty {
|
|
119
|
+
display: flex;
|
|
120
|
+
height: 100%;
|
|
121
|
+
flex-direction: column;
|
|
122
|
+
align-items: center;
|
|
123
|
+
justify-content: center;
|
|
124
|
+
gap: 6px;
|
|
125
|
+
overflow: auto;
|
|
126
|
+
color: var(--el-text-color-secondary);
|
|
127
|
+
font-size: 12px;
|
|
128
|
+
overflow-wrap: anywhere;
|
|
129
|
+
pointer-events: auto;
|
|
130
|
+
}
|
|
131
|
+
</style>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { BiSourceModel } from "../rpc/bi/admin/v1/bi_source";
|
|
2
|
+
|
|
3
|
+
/** 将事实表置于中央,关联子树分布两侧并为连线保留间距。 */
|
|
4
|
+
export function formatSourceModelLayout(model: BiSourceModel): void {
|
|
5
|
+
const tables = new Map(model.tables.map(table => [table.key, table]));
|
|
6
|
+
const children = new Map<string, string[]>();
|
|
7
|
+
const targets = new Set<string>();
|
|
8
|
+
for (const relation of model.relations) {
|
|
9
|
+
if (!tables.has(relation.left_table) || !tables.has(relation.right_table)) continue;
|
|
10
|
+
const branch = children.get(relation.left_table) ?? [];
|
|
11
|
+
branch.push(relation.right_table);
|
|
12
|
+
children.set(relation.left_table, branch);
|
|
13
|
+
targets.add(relation.right_table);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (tables.has(model.base_table)) {
|
|
17
|
+
const branches = children.get(model.base_table) ?? [];
|
|
18
|
+
for (const table of model.tables) {
|
|
19
|
+
if (table.key !== model.base_table && !targets.has(table.key)) branches.push(table.key);
|
|
20
|
+
}
|
|
21
|
+
children.set(model.base_table, branches);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const visited = new Set<string>();
|
|
25
|
+
let row = 0;
|
|
26
|
+
|
|
27
|
+
/** 为子树分配独立行,避免同层节点和不同分支相互遮挡。 */
|
|
28
|
+
function placeTable(key: string, depth: number, direction: number): number | undefined {
|
|
29
|
+
const table = tables.get(key);
|
|
30
|
+
if (!table || visited.has(key)) return;
|
|
31
|
+
visited.add(key);
|
|
32
|
+
const childRows = (children.get(key) ?? [])
|
|
33
|
+
.map(child => placeTable(child, depth + 1, direction))
|
|
34
|
+
.filter((value): value is number => value !== undefined);
|
|
35
|
+
const center = childRows.length ? (childRows[0] + childRows[childRows.length - 1]) / 2 : row++;
|
|
36
|
+
table.x = direction * depth * 520;
|
|
37
|
+
table.y = 40 + center * 280;
|
|
38
|
+
return center;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const base = tables.get(model.base_table);
|
|
42
|
+
if (base) {
|
|
43
|
+
visited.add(base.key);
|
|
44
|
+
const branches = children.get(base.key) ?? [];
|
|
45
|
+
const middle = Math.ceil(branches.length / 2);
|
|
46
|
+
for (const key of branches.slice(0, middle)) placeTable(key, 1, 1);
|
|
47
|
+
const rightRows = row;
|
|
48
|
+
const rightTables = model.tables.filter(table => visited.has(table.key) && table !== base);
|
|
49
|
+
row = 0;
|
|
50
|
+
for (const key of branches.slice(middle)) placeTable(key, 1, -1);
|
|
51
|
+
const leftRows = row;
|
|
52
|
+
row = Math.max(1, leftRows, rightRows);
|
|
53
|
+
for (const table of rightTables) table.y += (row - rightRows) * 140;
|
|
54
|
+
for (const table of model.tables.filter(table => visited.has(table.key) && table.x < 0)) table.y += (row - leftRows) * 140;
|
|
55
|
+
base.x = 0;
|
|
56
|
+
base.y = 40 + (row - 1) * 140;
|
|
57
|
+
}
|
|
58
|
+
for (const table of model.tables.filter(table => !targets.has(table.key))) placeTable(table.key, 0, 1);
|
|
59
|
+
for (const table of model.tables) placeTable(table.key, 0, 1);
|
|
60
|
+
const minX = Math.min(0, ...model.tables.map(table => table.x));
|
|
61
|
+
for (const table of model.tables) table.x += 40 - minX;
|
|
62
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { BiRow } from "../schema/types";
|
|
2
|
+
|
|
3
|
+
/** 从行集合推导列名(所有行键的并集,保持首次出现顺序) */
|
|
4
|
+
export function getColumns(rows: BiRow[] | undefined): string[] {
|
|
5
|
+
if (!rows?.length) return [];
|
|
6
|
+
const cols: string[] = [];
|
|
7
|
+
const seen = new Set<string>();
|
|
8
|
+
for (const row of rows) {
|
|
9
|
+
for (const key of Object.keys(row ?? {})) {
|
|
10
|
+
if (!seen.has(key)) {
|
|
11
|
+
seen.add(key);
|
|
12
|
+
cols.push(key);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return cols;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 数值化:非有限数归 0(图表轴不允许 NaN/undefined) */
|
|
20
|
+
export function toNumber(value: unknown): number {
|
|
21
|
+
const n = Number(value);
|
|
22
|
+
return Number.isFinite(n) ? n : 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 去重保序 */
|
|
26
|
+
export function unique<T>(list: T[]): T[] {
|
|
27
|
+
return [...new Set(list)];
|
|
28
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** 按日期分组值生成短标签,只改变显示文本,保留原始分类和提示数据。 */
|
|
2
|
+
export function createDimensionLabelFormatter(values: unknown[], field = ""): (value: unknown) => string {
|
|
3
|
+
const pattern = /^(\d{4})-(\d{2})(?:-(\d{2})(?:[ T](\d{2})(?::(\d{2}))?(?::(\d{2})(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?)?$/;
|
|
4
|
+
const dates = values.map(value => pattern.exec(String(value ?? ""))).filter(value => value !== null);
|
|
5
|
+
const month = /(?:^|_)(?:month|monthly|ym)(?:_|$)|月/i.test(field);
|
|
6
|
+
const hour =
|
|
7
|
+
/(?:^|_)(?:hour|hourly)(?:_|$)|小时/i.test(field) ||
|
|
8
|
+
(dates.length > 0 &&
|
|
9
|
+
dates.every(date => date[3] && date[0].slice(0, 10) === dates[0]![0].slice(0, 10)) &&
|
|
10
|
+
dates.some(date => Number(date[4]) || Number(date[5]) || Number(date[6])));
|
|
11
|
+
return value => {
|
|
12
|
+
const text = String(value ?? "");
|
|
13
|
+
const date = pattern.exec(text);
|
|
14
|
+
if (!date) return text;
|
|
15
|
+
if (month || !date[3]) return `${date[2]}月`;
|
|
16
|
+
if (hour && date[4]) return `${date[4]}时`;
|
|
17
|
+
return `${date[2]}/${date[3]}`;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 图表级数据变换管线:过滤 → 聚合 → 排序 → TopN。
|
|
3
|
+
* 渲染器在 compile 前调用 transformRows,设计器画布与渲染器共用同一管线。
|
|
4
|
+
* 聚合语义 = 主流 BI:按 dimension 分组,对 measure(或宽表 measures 全部列)施加 agg。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
BiAggFn,
|
|
9
|
+
BiChartFilter,
|
|
10
|
+
BiChartSchema,
|
|
11
|
+
BiChartSort,
|
|
12
|
+
BiRow,
|
|
13
|
+
} from "../schema/types";
|
|
14
|
+
import { NON_AGG_TYPES } from "../engine/chart-types";
|
|
15
|
+
import { toNumber } from "./index";
|
|
16
|
+
|
|
17
|
+
/** 单过滤器行判定;值解析:数字串转数,in 接数组 */
|
|
18
|
+
function matchFilter(row: BiRow, f: BiChartFilter): boolean {
|
|
19
|
+
const cell = row[f.field];
|
|
20
|
+
switch (f.op) {
|
|
21
|
+
case "eq":
|
|
22
|
+
return String(cell) === String(f.value);
|
|
23
|
+
case "ne":
|
|
24
|
+
return String(cell) !== String(f.value);
|
|
25
|
+
case "gt":
|
|
26
|
+
return toNumber(cell) > toNumber(f.value);
|
|
27
|
+
case "gte":
|
|
28
|
+
return toNumber(cell) >= toNumber(f.value);
|
|
29
|
+
case "lt":
|
|
30
|
+
return toNumber(cell) < toNumber(f.value);
|
|
31
|
+
case "lte":
|
|
32
|
+
return toNumber(cell) <= toNumber(f.value);
|
|
33
|
+
case "contains":
|
|
34
|
+
return String(cell ?? "").includes(String(f.value ?? ""));
|
|
35
|
+
case "in": {
|
|
36
|
+
const list = Array.isArray(f.value) ? f.value : [f.value];
|
|
37
|
+
return list.map(String).includes(String(cell));
|
|
38
|
+
}
|
|
39
|
+
default:
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function applyFilters(rows: BiRow[], filters: BiChartFilter[] | undefined): BiRow[] {
|
|
45
|
+
if (!filters?.length) return rows;
|
|
46
|
+
return rows.filter((row) => filters.every((f) => matchFilter(row, f)));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const AGG_FNS: Record<Exclude<BiAggFn, "none">, (nums: number[]) => number> = {
|
|
50
|
+
sum: (nums) => nums.reduce((a, b) => a + b, 0),
|
|
51
|
+
avg: (nums) => (nums.length ? nums.reduce((a, b) => a + b, 0) / nums.length : 0),
|
|
52
|
+
max: (nums) => (nums.length ? Math.max(...nums) : 0),
|
|
53
|
+
min: (nums) => (nums.length ? Math.min(...nums) : 0),
|
|
54
|
+
// count 不依赖数值:调用方传入占位 1 数组
|
|
55
|
+
count: (nums) => nums.length,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** 是否应执行聚合:给了 dimension + 显式 agg(非 none);
|
|
59
|
+
* 边列表/矩阵型(sankey/graph/heatmap)天然按行消费,不做维度聚合 */
|
|
60
|
+
function shouldAggregate(chart: BiChartSchema): boolean {
|
|
61
|
+
if (NON_AGG_TYPES.has(chart.type)) return false;
|
|
62
|
+
const agg = chart.aggregation ?? "sum";
|
|
63
|
+
const dim = chart.encoding?.dimension;
|
|
64
|
+
if (!dim || agg === "none") return false;
|
|
65
|
+
// 至少要有一个度量对象(count 允许无 measure)
|
|
66
|
+
return !!(chart.encoding?.measure || chart.encoding?.measures?.length || agg === "count");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** 分组键字段:维度 + (pivot 图的列维度也要保留,供 S2 交叉表用) */
|
|
70
|
+
function groupFieldsOf(chart: BiChartSchema): string[] {
|
|
71
|
+
const fields = [chart.encoding?.dimension ?? ""];
|
|
72
|
+
if (chart.type === "pivot" && chart.encoding?.series) fields.push(chart.encoding.series);
|
|
73
|
+
return fields.filter(Boolean);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** 分组聚合:输出每(维度组)一行的宽表(保留维度列 + 各度量列) */
|
|
77
|
+
export function aggregateRows(
|
|
78
|
+
rows: BiRow[],
|
|
79
|
+
chart: BiChartSchema
|
|
80
|
+
): BiRow[] {
|
|
81
|
+
const groupFields = groupFieldsOf(chart);
|
|
82
|
+
const agg: BiAggFn = chart.aggregation ?? "sum";
|
|
83
|
+
const measure = chart.encoding?.measure;
|
|
84
|
+
const measures = chart.encoding?.measures ?? (measure ? [measure] : []);
|
|
85
|
+
|
|
86
|
+
const keyOf = (row: BiRow) => groupFields.map((f) => String(row[f] ?? "")).join("\u0001");
|
|
87
|
+
const groups = new Map<string, BiRow[]>();
|
|
88
|
+
for (const row of rows) {
|
|
89
|
+
const key = keyOf(row);
|
|
90
|
+
const bucket = groups.get(key);
|
|
91
|
+
if (bucket) bucket.push(row);
|
|
92
|
+
else groups.set(key, [row]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const apply = (nums: number[]): number => {
|
|
96
|
+
switch (agg) {
|
|
97
|
+
case "count":
|
|
98
|
+
return nums.length;
|
|
99
|
+
case "none":
|
|
100
|
+
return 0;
|
|
101
|
+
case "avg":
|
|
102
|
+
return AGG_FNS.avg(nums);
|
|
103
|
+
case "max":
|
|
104
|
+
return AGG_FNS.max(nums);
|
|
105
|
+
case "min":
|
|
106
|
+
return AGG_FNS.min(nums);
|
|
107
|
+
default:
|
|
108
|
+
return AGG_FNS.sum(nums);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const out: BiRow[] = [];
|
|
113
|
+
for (const bucket of groups.values()) {
|
|
114
|
+
const row: BiRow = {};
|
|
115
|
+
for (const f of groupFields) row[f] = bucket[0]?.[f];
|
|
116
|
+
if (agg === "count" && measures.length === 0) {
|
|
117
|
+
// 纯计数:值列名用 measure 占位或固定 count
|
|
118
|
+
row[measure ?? "count"] = bucket.length;
|
|
119
|
+
}
|
|
120
|
+
for (const m of measures) {
|
|
121
|
+
row[m] = apply(bucket.map((r) => (agg === "count" ? 1 : toNumber(r[m]))));
|
|
122
|
+
}
|
|
123
|
+
// 非分组字段的长表 series 值在聚合后无意义(pivot 的列维度已保留),丢弃
|
|
124
|
+
out.push(row);
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function sortRows(rows: BiRow[], chart: BiChartSchema): BiRow[] {
|
|
130
|
+
const sort: BiChartSort | undefined = chart.sort;
|
|
131
|
+
if (!sort) return rows;
|
|
132
|
+
const dim = chart.encoding?.dimension ?? "";
|
|
133
|
+
const measure = chart.encoding?.measures?.[0] ?? chart.encoding?.measure ?? "";
|
|
134
|
+
const dir = sort.order === "desc" ? -1 : 1;
|
|
135
|
+
const sorted = [...rows].sort((a, b) => {
|
|
136
|
+
if (sort.by === "measure" && measure) {
|
|
137
|
+
return (toNumber(a[measure]) - toNumber(b[measure])) * dir;
|
|
138
|
+
}
|
|
139
|
+
return String(a[dim] ?? "").localeCompare(String(b[dim] ?? ""), "zh-CN") * dir;
|
|
140
|
+
});
|
|
141
|
+
return sort.topN && sort.topN > 0 ? sorted.slice(0, sort.topN) : sorted;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** 渲染管线:过滤 → 聚合 → 排序/TopN,返回供 compile 消费的行 */
|
|
145
|
+
export function transformRows(chart: BiChartSchema, inputRows: BiRow[]): BiRow[] {
|
|
146
|
+
let rows = applyFilters(inputRows, chart.filters);
|
|
147
|
+
if (shouldAggregate(chart)) {
|
|
148
|
+
rows = aggregateRows(rows, chart);
|
|
149
|
+
}
|
|
150
|
+
rows = sortRows(rows, chart);
|
|
151
|
+
return rows;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ============ 字段类型推断(设计器字段选择器提示用) ============
|
|
155
|
+
|
|
156
|
+
export type BiFieldType = "number" | "date" | "string";
|
|
157
|
+
|
|
158
|
+
export function inferFieldType(rows: BiRow[] | undefined, field: string): BiFieldType {
|
|
159
|
+
if (!rows?.length) return "string";
|
|
160
|
+
let sawValue = false;
|
|
161
|
+
let allNumber = true;
|
|
162
|
+
let allDate = true;
|
|
163
|
+
for (const row of rows.slice(0, 100)) {
|
|
164
|
+
const v = row?.[field];
|
|
165
|
+
if (v === null || v === undefined || v === "") continue;
|
|
166
|
+
sawValue = true;
|
|
167
|
+
if (allNumber && !Number.isFinite(Number(v))) allNumber = false;
|
|
168
|
+
if (allDate && !(typeof v === "string" && /^[\d-]{8,}$/.test(v) && !Number.isNaN(Date.parse(v)))) {
|
|
169
|
+
allDate = false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (!sawValue) return "string";
|
|
173
|
+
if (allNumber) return "number";
|
|
174
|
+
if (allDate) return "date";
|
|
175
|
+
return "string";
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ============ 数值格式化(轴标签/标签/提示共用) ============
|
|
179
|
+
|
|
180
|
+
export function formatNumber(
|
|
181
|
+
value: number,
|
|
182
|
+
format?: { decimals?: number; thousand?: boolean; prefix?: string; suffix?: string }
|
|
183
|
+
): string {
|
|
184
|
+
let n = Number.isFinite(value) ? value : 0;
|
|
185
|
+
if (format?.decimals !== undefined) n = Number(n.toFixed(format.decimals));
|
|
186
|
+
let str = String(n);
|
|
187
|
+
if (format?.thousand) str = str.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
188
|
+
return (format?.prefix ?? "") + str + (format?.suffix ?? "");
|
|
189
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 图型注册表:type → engine 映射。
|
|
3
|
+
* 内置 8 种 echarts 图型;引擎适配器包(bi-engine-s2 等)经
|
|
4
|
+
* registerChartType("pivot", "s2") 扩展设计器的图型选择与引擎解析。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { BiChartType } from "../schema/types";
|
|
8
|
+
|
|
9
|
+
const BUILTIN_CHART_TYPES: BiChartType[] = [
|
|
10
|
+
"bar",
|
|
11
|
+
"line",
|
|
12
|
+
"area",
|
|
13
|
+
"pie",
|
|
14
|
+
"donut",
|
|
15
|
+
"scatter",
|
|
16
|
+
"radar",
|
|
17
|
+
"gauge",
|
|
18
|
+
"funnel",
|
|
19
|
+
"treemap",
|
|
20
|
+
"sunburst",
|
|
21
|
+
"sankey",
|
|
22
|
+
"heatmap",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
/** 边列表 / 矩阵型图:数据是边或三元组,默认不做维度聚合 */
|
|
26
|
+
export const NON_AGG_TYPES = new Set(["sankey", "graph", "heatmap"]);
|
|
27
|
+
|
|
28
|
+
const typeToEngine = new Map<string, string>();
|
|
29
|
+
for (const t of BUILTIN_CHART_TYPES) typeToEngine.set(t, "echarts");
|
|
30
|
+
|
|
31
|
+
/** 注册新图型(设计器图型网格与 addChart 的引擎解析随之扩展);重复注册覆盖 */
|
|
32
|
+
export function registerChartType(type: string, engine: string): void {
|
|
33
|
+
typeToEngine.set(type, engine);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 已注册的全部图型(内置 + 扩展,按注册顺序) */
|
|
37
|
+
export function getChartTypes(): BiChartType[] {
|
|
38
|
+
return [...typeToEngine.keys()] as BiChartType[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 图型对应的引擎名(未注册图型回落 echarts) */
|
|
42
|
+
export function getEngineForType(type: string | undefined): string {
|
|
43
|
+
return typeToEngine.get(type ?? "") ?? "echarts";
|
|
44
|
+
}
|