@liuhange/dsh-data-visualization 2.0.1 → 2.0.3
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 +7 -0
- package/package.json +72 -39
- package/lib/index.js +0 -573
- package/lib/invariant.js +0 -5
- package/lib/types/chartRenderers/barChartRenderer.d.ts +0 -8
- package/lib/types/chartRenderers/comparisonChartRenderer.d.ts +0 -8
- package/lib/types/chartRenderers/flowchartRenderer.d.ts +0 -8
- package/lib/types/chartRenderers/pieChartRenderer.d.ts +0 -8
- package/lib/types/chartRenderers/radarChartRenderer.d.ts +0 -8
- package/lib/types/defaultVisualizationConfig.d.ts +0 -3
- package/lib/types/htmlAssembler.d.ts +0 -12
- package/lib/types/index.d.ts +0 -5
- package/lib/types/interactionScript.d.ts +0 -5
- package/lib/types/invariant.d.ts +0 -3
- package/lib/types/reportDataLoader.d.ts +0 -5
- package/lib/types/templateLoader.d.ts +0 -5
- package/lib/types/types.d.ts +0 -30
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,39 +1,72 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@liuhange/dsh-data-visualization",
|
|
3
|
+
"description": "Data visualization plugin: generate self-contained HTML reports with interactive charts (bar/comparison/pie/radar/flowchart) Also known as @deepseek-ai/dsh-data-visualization.",
|
|
4
|
+
"version": "2.0.3",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/liuhange789/data-asset-inspector.git",
|
|
11
|
+
"directory": "packages/data-asset/data-visualization"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"lib/index.js",
|
|
30
|
+
"lib/invariant.js",
|
|
31
|
+
"lib/types/**/*.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"scripts": {
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"build": "tsc"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@deepseek-ai/cordis": "^4.0.0",
|
|
41
|
+
"@deepseek-ai/dsh-tools": "0.0.1-rc.1",
|
|
42
|
+
"@liuhange/dsh-data-asset-shared": "^2.0.0"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"deepseek-harness",
|
|
46
|
+
"dsh-plugin",
|
|
47
|
+
"data-asset",
|
|
48
|
+
"data-asset-inspector",
|
|
49
|
+
"shujintong",
|
|
50
|
+
"数据资产",
|
|
51
|
+
"data-visualization",
|
|
52
|
+
"html-report",
|
|
53
|
+
"interactive-charts",
|
|
54
|
+
"bar-chart",
|
|
55
|
+
"pie-chart",
|
|
56
|
+
"radar-chart",
|
|
57
|
+
"flowchart",
|
|
58
|
+
"数据可视化",
|
|
59
|
+
"交互式图表",
|
|
60
|
+
"HTML报表",
|
|
61
|
+
"柱状图",
|
|
62
|
+
"饼图",
|
|
63
|
+
"雷达图",
|
|
64
|
+
"流程图"
|
|
65
|
+
],
|
|
66
|
+
"homepage": "https://github.com/liuhange789/data-asset-inspector#readme",
|
|
67
|
+
"dsh": {
|
|
68
|
+
"bundle": {
|
|
69
|
+
"patch": true
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
package/lib/index.js
DELETED
|
@@ -1,573 +0,0 @@
|
|
|
1
|
-
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
2
|
-
import * as fs from "fs";
|
|
3
|
-
import * as path from "path";
|
|
4
|
-
import { AuditLogger, BusinessRulesLoader, PathValidator } from "@liuhange/dsh-data-asset-shared";
|
|
5
|
-
//#region lib/types/defaultVisualizationConfig.js
|
|
6
|
-
const defaultVisualizationConfig = {
|
|
7
|
-
charts: {
|
|
8
|
-
bar: {
|
|
9
|
-
enabled: true,
|
|
10
|
-
title: "脱敏统计"
|
|
11
|
-
},
|
|
12
|
-
comparison: {
|
|
13
|
-
enabled: true,
|
|
14
|
-
title: "清洗前后对比"
|
|
15
|
-
},
|
|
16
|
-
pie: {
|
|
17
|
-
enabled: true,
|
|
18
|
-
title: "数据资产分布"
|
|
19
|
-
},
|
|
20
|
-
radar: {
|
|
21
|
-
enabled: true,
|
|
22
|
-
title: "质量评分"
|
|
23
|
-
},
|
|
24
|
-
flowchart: {
|
|
25
|
-
enabled: true,
|
|
26
|
-
title: "血缘追踪"
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
layout: {
|
|
30
|
-
columns: 2,
|
|
31
|
-
responsive: true
|
|
32
|
-
},
|
|
33
|
-
colorScheme: {
|
|
34
|
-
primary: "#4A90D9",
|
|
35
|
-
secondary: "#F5A623",
|
|
36
|
-
success: "#7ED321",
|
|
37
|
-
warning: "#F8E71C",
|
|
38
|
-
danger: "#D0021B",
|
|
39
|
-
background: "#F5F5F5",
|
|
40
|
-
text: "#333333"
|
|
41
|
-
},
|
|
42
|
-
interactions: {
|
|
43
|
-
expandCollapse: true,
|
|
44
|
-
chartSwitch: true,
|
|
45
|
-
tooltip: true
|
|
46
|
-
},
|
|
47
|
-
templateVersion: "1.0"
|
|
48
|
-
};
|
|
49
|
-
//#endregion
|
|
50
|
-
//#region lib/types/reportDataLoader.js
|
|
51
|
-
var ReportDataLoader = class {
|
|
52
|
-
async load(filePath) {
|
|
53
|
-
if (!fs.existsSync(filePath)) return {
|
|
54
|
-
data: null,
|
|
55
|
-
error: "文件不存在"
|
|
56
|
-
};
|
|
57
|
-
try {
|
|
58
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
59
|
-
return { data: JSON.parse(content) };
|
|
60
|
-
} catch {
|
|
61
|
-
return {
|
|
62
|
-
data: null,
|
|
63
|
-
error: "数据格式错误"
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
//#endregion
|
|
69
|
-
//#region lib/types/chartRenderers/barChartRenderer.js
|
|
70
|
-
var BarChartRenderer = class {
|
|
71
|
-
render(data, config) {
|
|
72
|
-
const title = config.charts.bar?.title ?? "脱敏统计";
|
|
73
|
-
const primary = config.colorScheme.primary;
|
|
74
|
-
if (data === null || data === void 0) return {
|
|
75
|
-
type: "bar",
|
|
76
|
-
title,
|
|
77
|
-
svgContent: this.placeholder(title, "数据不可用"),
|
|
78
|
-
dataAvailable: false
|
|
79
|
-
};
|
|
80
|
-
const counts = this.extractCounts(data);
|
|
81
|
-
if (counts.length === 0) return {
|
|
82
|
-
type: "bar",
|
|
83
|
-
title,
|
|
84
|
-
svgContent: this.placeholder(title, "无数据"),
|
|
85
|
-
dataAvailable: false
|
|
86
|
-
};
|
|
87
|
-
const maxCount = Math.max(...counts.map((c) => c.value));
|
|
88
|
-
const barWidth = 60;
|
|
89
|
-
const chartHeight = 200;
|
|
90
|
-
const startX = 40;
|
|
91
|
-
let svg = `<svg width="${startX + counts.length * 80}" height="260" xmlns="http://www.w3.org/2000/svg">`;
|
|
92
|
-
for (let i = 0; i < counts.length; i++) {
|
|
93
|
-
const item = counts[i];
|
|
94
|
-
const barHeight = maxCount > 0 ? item.value / maxCount * chartHeight : 0;
|
|
95
|
-
const x = startX + i * 80;
|
|
96
|
-
const y = 220 - barHeight;
|
|
97
|
-
svg += `<rect x="${x}" y="${y}" width="${barWidth}" height="${barHeight}" fill="${primary}" />`;
|
|
98
|
-
svg += `<text x="${x + barWidth / 2}" y="240" text-anchor="middle" fill="${config.colorScheme.text}" font-size="12">${item.label}</text>`;
|
|
99
|
-
svg += `<text x="${x + barWidth / 2}" y="${y - 5}" text-anchor="middle" fill="${config.colorScheme.text}" font-size="12">${item.value}</text>`;
|
|
100
|
-
}
|
|
101
|
-
svg += "</svg>";
|
|
102
|
-
return {
|
|
103
|
-
type: "bar",
|
|
104
|
-
title,
|
|
105
|
-
svgContent: svg,
|
|
106
|
-
dataAvailable: true
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
extractCounts(data) {
|
|
110
|
-
if (typeof data === "object" && data !== null) {
|
|
111
|
-
const obj = data;
|
|
112
|
-
if ("fieldTypeCounts" in obj) {
|
|
113
|
-
const counts = obj.fieldTypeCounts;
|
|
114
|
-
return Object.entries(counts).map(([label, value]) => ({
|
|
115
|
-
label,
|
|
116
|
-
value
|
|
117
|
-
}));
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return [];
|
|
121
|
-
}
|
|
122
|
-
placeholder(title, message) {
|
|
123
|
-
return `<svg width="300" height="100" xmlns="http://www.w3.org/2000/svg"><text x="150" y="50" text-anchor="middle" fill="#999" font-size="14">${title}: ${message}</text></svg>`;
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
//#endregion
|
|
127
|
-
//#region lib/types/chartRenderers/comparisonChartRenderer.js
|
|
128
|
-
var ComparisonChartRenderer = class {
|
|
129
|
-
render(data, config) {
|
|
130
|
-
const title = config.charts.comparison?.title ?? "清洗前后对比";
|
|
131
|
-
const primary = config.colorScheme.primary;
|
|
132
|
-
const secondary = config.colorScheme.secondary;
|
|
133
|
-
if (data === null || data === void 0) return {
|
|
134
|
-
type: "comparison",
|
|
135
|
-
title,
|
|
136
|
-
svgContent: this.placeholder(title, "数据不可用"),
|
|
137
|
-
dataAvailable: false
|
|
138
|
-
};
|
|
139
|
-
const { before, after } = this.extractData(data);
|
|
140
|
-
if (before === 0 && after === 0) return {
|
|
141
|
-
type: "comparison",
|
|
142
|
-
title,
|
|
143
|
-
svgContent: this.placeholder(title, "无数据"),
|
|
144
|
-
dataAvailable: false
|
|
145
|
-
};
|
|
146
|
-
const maxValue = Math.max(before, after, 1);
|
|
147
|
-
const chartHeight = 200;
|
|
148
|
-
const barWidth = 80;
|
|
149
|
-
const startX = 40;
|
|
150
|
-
const beforeHeight = before / maxValue * chartHeight;
|
|
151
|
-
const afterHeight = after / maxValue * chartHeight;
|
|
152
|
-
let svg = `<svg width="280" height="260" xmlns="http://www.w3.org/2000/svg">`;
|
|
153
|
-
svg += `<rect x="${startX}" y="${220 - beforeHeight}" width="${barWidth}" height="${beforeHeight}" fill="${primary}" />`;
|
|
154
|
-
svg += `<text x="80" y="240" text-anchor="middle" fill="${config.colorScheme.text}" font-size="12">清洗前</text>`;
|
|
155
|
-
svg += `<text x="80" y="${220 - beforeHeight - 5}" text-anchor="middle" fill="${config.colorScheme.text}" font-size="12">${before}</text>`;
|
|
156
|
-
svg += `<rect x="160" y="${220 - afterHeight}" width="${barWidth}" height="${afterHeight}" fill="${secondary}" />`;
|
|
157
|
-
svg += `<text x="200" y="240" text-anchor="middle" fill="${config.colorScheme.text}" font-size="12">清洗后</text>`;
|
|
158
|
-
svg += `<text x="200" y="${220 - afterHeight - 5}" text-anchor="middle" fill="${config.colorScheme.text}" font-size="12">${after}</text>`;
|
|
159
|
-
svg += "</svg>";
|
|
160
|
-
return {
|
|
161
|
-
type: "comparison",
|
|
162
|
-
title,
|
|
163
|
-
svgContent: svg,
|
|
164
|
-
dataAvailable: true
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
extractData(data) {
|
|
168
|
-
if (typeof data === "object" && data !== null) {
|
|
169
|
-
const obj = data;
|
|
170
|
-
return {
|
|
171
|
-
before: "originalLineCount" in obj ? Number(obj.originalLineCount) : 0,
|
|
172
|
-
after: "cleanedLineCount" in obj ? Number(obj.cleanedLineCount) : 0
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
return {
|
|
176
|
-
before: 0,
|
|
177
|
-
after: 0
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
placeholder(title, message) {
|
|
181
|
-
return `<svg width="300" height="100" xmlns="http://www.w3.org/2000/svg"><text x="150" y="50" text-anchor="middle" fill="#999" font-size="14">${title}: ${message}</text></svg>`;
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
//#endregion
|
|
185
|
-
//#region lib/types/chartRenderers/pieChartRenderer.js
|
|
186
|
-
var PieChartRenderer = class {
|
|
187
|
-
render(data, config) {
|
|
188
|
-
const title = config.charts.pie?.title ?? "数据资产分布";
|
|
189
|
-
const colors = [
|
|
190
|
-
config.colorScheme.primary,
|
|
191
|
-
config.colorScheme.secondary,
|
|
192
|
-
config.colorScheme.success,
|
|
193
|
-
config.colorScheme.warning,
|
|
194
|
-
config.colorScheme.danger
|
|
195
|
-
];
|
|
196
|
-
if (data === null || data === void 0) return {
|
|
197
|
-
type: "pie",
|
|
198
|
-
title,
|
|
199
|
-
svgContent: this.placeholder(title, "数据不可用"),
|
|
200
|
-
dataAvailable: false
|
|
201
|
-
};
|
|
202
|
-
const items = this.extractData(data);
|
|
203
|
-
if (items.length === 0) return {
|
|
204
|
-
type: "pie",
|
|
205
|
-
title,
|
|
206
|
-
svgContent: this.placeholder(title, "无数据"),
|
|
207
|
-
dataAvailable: false
|
|
208
|
-
};
|
|
209
|
-
const total = items.reduce((sum, item) => sum + item.value, 0);
|
|
210
|
-
if (total === 0) return {
|
|
211
|
-
type: "pie",
|
|
212
|
-
title,
|
|
213
|
-
svgContent: this.placeholder(title, "无数据"),
|
|
214
|
-
dataAvailable: false
|
|
215
|
-
};
|
|
216
|
-
const cx = 150;
|
|
217
|
-
const cy = 100;
|
|
218
|
-
const r = 80;
|
|
219
|
-
let currentAngle = -Math.PI / 2;
|
|
220
|
-
let svg = `<svg width="300" height="200" xmlns="http://www.w3.org/2000/svg">`;
|
|
221
|
-
for (let i = 0; i < items.length; i++) {
|
|
222
|
-
const angle = items[i].value / total * 2 * Math.PI;
|
|
223
|
-
const x1 = cx + r * Math.cos(currentAngle);
|
|
224
|
-
const y1 = cy + r * Math.sin(currentAngle);
|
|
225
|
-
const x2 = cx + r * Math.cos(currentAngle + angle);
|
|
226
|
-
const y2 = cy + r * Math.sin(currentAngle + angle);
|
|
227
|
-
const largeArc = angle > Math.PI ? 1 : 0;
|
|
228
|
-
const color = colors[i % colors.length];
|
|
229
|
-
svg += `<path d="M${cx},${cy} L${x1},${y1} A${r},${r} 0 ${largeArc},1 ${x2},${y2} Z" fill="${color}" />`;
|
|
230
|
-
currentAngle += angle;
|
|
231
|
-
}
|
|
232
|
-
svg += "</svg>";
|
|
233
|
-
return {
|
|
234
|
-
type: "pie",
|
|
235
|
-
title,
|
|
236
|
-
svgContent: svg,
|
|
237
|
-
dataAvailable: true
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
extractData(data) {
|
|
241
|
-
if (typeof data === "object" && data !== null) {
|
|
242
|
-
const obj = data;
|
|
243
|
-
if ("assetItems" in obj && Array.isArray(obj.assetItems)) return obj.assetItems.map((item) => ({
|
|
244
|
-
label: String(item.fileName ?? "unknown"),
|
|
245
|
-
value: Number(item.size ?? 0)
|
|
246
|
-
}));
|
|
247
|
-
}
|
|
248
|
-
return [];
|
|
249
|
-
}
|
|
250
|
-
placeholder(title, message) {
|
|
251
|
-
return `<svg width="300" height="100" xmlns="http://www.w3.org/2000/svg"><text x="150" y="50" text-anchor="middle" fill="#999" font-size="14">${title}: ${message}</text></svg>`;
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
//#endregion
|
|
255
|
-
//#region lib/types/chartRenderers/radarChartRenderer.js
|
|
256
|
-
var RadarChartRenderer = class {
|
|
257
|
-
render(data, config) {
|
|
258
|
-
const title = config.charts.radar?.title ?? "质量评分";
|
|
259
|
-
const primary = config.colorScheme.primary;
|
|
260
|
-
if (data === null || data === void 0) return {
|
|
261
|
-
type: "radar",
|
|
262
|
-
title,
|
|
263
|
-
svgContent: this.placeholder(title, "数据不可用"),
|
|
264
|
-
dataAvailable: false
|
|
265
|
-
};
|
|
266
|
-
const dimensions = this.extractData(data);
|
|
267
|
-
if (dimensions.length === 0) return {
|
|
268
|
-
type: "radar",
|
|
269
|
-
title,
|
|
270
|
-
svgContent: this.placeholder(title, "无数据"),
|
|
271
|
-
dataAvailable: false
|
|
272
|
-
};
|
|
273
|
-
const cx = 150;
|
|
274
|
-
const cy = 100;
|
|
275
|
-
const maxR = 80;
|
|
276
|
-
const n = dimensions.length;
|
|
277
|
-
let svg = `<svg width="300" height="200" xmlns="http://www.w3.org/2000/svg">`;
|
|
278
|
-
for (let i = 0; i < n; i++) {
|
|
279
|
-
const angle = i / n * 2 * Math.PI - Math.PI / 2;
|
|
280
|
-
const x = cx + maxR * Math.cos(angle);
|
|
281
|
-
const y = cy + maxR * Math.sin(angle);
|
|
282
|
-
svg += `<line x1="${cx}" y1="${cy}" x2="${x}" y2="${y}" stroke="#ddd" stroke-width="1" />`;
|
|
283
|
-
const labelX = cx + 95 * Math.cos(angle);
|
|
284
|
-
const labelY = cy + 95 * Math.sin(angle);
|
|
285
|
-
svg += `<text x="${labelX}" y="${labelY}" text-anchor="middle" fill="${config.colorScheme.text}" font-size="10">${dimensions[i].label}</text>`;
|
|
286
|
-
}
|
|
287
|
-
let pathData = "";
|
|
288
|
-
for (let i = 0; i < n; i++) {
|
|
289
|
-
const angle = i / n * 2 * Math.PI - Math.PI / 2;
|
|
290
|
-
const r = dimensions[i].value / 100 * maxR;
|
|
291
|
-
const x = cx + r * Math.cos(angle);
|
|
292
|
-
const y = cy + r * Math.sin(angle);
|
|
293
|
-
pathData += (i === 0 ? "M" : "L") + `${x},${y} `;
|
|
294
|
-
}
|
|
295
|
-
pathData += "Z";
|
|
296
|
-
svg += `<path d="${pathData}" fill="${primary}" fill-opacity="0.3" stroke="${primary}" stroke-width="2" />`;
|
|
297
|
-
svg += "</svg>";
|
|
298
|
-
return {
|
|
299
|
-
type: "radar",
|
|
300
|
-
title,
|
|
301
|
-
svgContent: svg,
|
|
302
|
-
dataAvailable: true
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
extractData(data) {
|
|
306
|
-
if (typeof data === "object" && data !== null) {
|
|
307
|
-
const obj = data;
|
|
308
|
-
if ("dimensions" in obj && typeof obj.dimensions === "object" && obj.dimensions !== null) {
|
|
309
|
-
const dims = obj.dimensions;
|
|
310
|
-
return Object.entries(dims).map(([key, val]) => ({
|
|
311
|
-
label: key,
|
|
312
|
-
value: Number(val.score ?? 0)
|
|
313
|
-
}));
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
return [];
|
|
317
|
-
}
|
|
318
|
-
placeholder(title, message) {
|
|
319
|
-
return `<svg width="300" height="100" xmlns="http://www.w3.org/2000/svg"><text x="150" y="50" text-anchor="middle" fill="#999" font-size="14">${title}: ${message}</text></svg>`;
|
|
320
|
-
}
|
|
321
|
-
};
|
|
322
|
-
//#endregion
|
|
323
|
-
//#region lib/types/chartRenderers/flowchartRenderer.js
|
|
324
|
-
var FlowchartRenderer = class {
|
|
325
|
-
render(data, config) {
|
|
326
|
-
const title = config.charts.flowchart?.title ?? "血缘追踪";
|
|
327
|
-
const primary = config.colorScheme.primary;
|
|
328
|
-
if (data === null || data === void 0) return {
|
|
329
|
-
type: "flowchart",
|
|
330
|
-
title,
|
|
331
|
-
svgContent: this.placeholder(title, "数据不可用"),
|
|
332
|
-
dataAvailable: false
|
|
333
|
-
};
|
|
334
|
-
const steps = this.extractData(data);
|
|
335
|
-
if (steps.length === 0) return {
|
|
336
|
-
type: "flowchart",
|
|
337
|
-
title,
|
|
338
|
-
svgContent: this.placeholder(title, "无数据"),
|
|
339
|
-
dataAvailable: false
|
|
340
|
-
};
|
|
341
|
-
const nodeWidth = 120;
|
|
342
|
-
const nodeHeight = 40;
|
|
343
|
-
const gap = 60;
|
|
344
|
-
const startX = 20;
|
|
345
|
-
const startY = 80;
|
|
346
|
-
let svg = `<svg width="${startX + steps.length * 180}" height="160" xmlns="http://www.w3.org/2000/svg">`;
|
|
347
|
-
for (let i = 0; i < steps.length; i++) {
|
|
348
|
-
const x = startX + i * 180;
|
|
349
|
-
svg += `<rect x="${x}" y="${startY}" width="${nodeWidth}" height="${nodeHeight}" rx="5" fill="${primary}" fill-opacity="0.2" stroke="${primary}" stroke-width="2" />`;
|
|
350
|
-
svg += `<text x="${x + nodeWidth / 2}" y="105" text-anchor="middle" fill="${config.colorScheme.text}" font-size="12">${steps[i]}</text>`;
|
|
351
|
-
if (i < steps.length - 1) {
|
|
352
|
-
const arrowX = x + nodeWidth;
|
|
353
|
-
svg += `<line x1="${arrowX}" y1="100" x2="${arrowX + gap}" y2="100" stroke="${primary}" stroke-width="2" />`;
|
|
354
|
-
svg += `<polygon points="${arrowX + gap},100 ${arrowX + gap - 5},95 ${arrowX + gap - 5},105" fill="${primary}" />`;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
svg += "</svg>";
|
|
358
|
-
return {
|
|
359
|
-
type: "flowchart",
|
|
360
|
-
title,
|
|
361
|
-
svgContent: svg,
|
|
362
|
-
dataAvailable: true
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
extractData(data) {
|
|
366
|
-
if (typeof data === "object" && data !== null) {
|
|
367
|
-
const obj = data;
|
|
368
|
-
if ("steps" in obj && Array.isArray(obj.steps)) return obj.steps.map((step) => String(step.stepName ?? ""));
|
|
369
|
-
}
|
|
370
|
-
return [];
|
|
371
|
-
}
|
|
372
|
-
placeholder(title, message) {
|
|
373
|
-
return `<svg width="300" height="100" xmlns="http://www.w3.org/2000/svg"><text x="150" y="50" text-anchor="middle" fill="#999" font-size="14">${title}: ${message}</text></svg>`;
|
|
374
|
-
}
|
|
375
|
-
};
|
|
376
|
-
//#endregion
|
|
377
|
-
//#region lib/types/interactionScript.js
|
|
378
|
-
var InteractionScript = class {
|
|
379
|
-
generate(interactions) {
|
|
380
|
-
const parts = [];
|
|
381
|
-
if (interactions.expandCollapse) parts.push(`
|
|
382
|
-
function toggleSection(id) {
|
|
383
|
-
const el = document.getElementById(id);
|
|
384
|
-
if (el) { el.style.display = el.style.display === 'none' ? 'block' : 'none'; }
|
|
385
|
-
}`);
|
|
386
|
-
if (interactions.chartSwitch) parts.push(`
|
|
387
|
-
function switchChart(selectId, chartContainerId) {
|
|
388
|
-
const select = document.getElementById(selectId);
|
|
389
|
-
const container = document.getElementById(chartContainerId);
|
|
390
|
-
if (select && container) {
|
|
391
|
-
const charts = container.querySelectorAll('.chart-item');
|
|
392
|
-
charts.forEach(c => c.style.display = 'none');
|
|
393
|
-
const selected = container.querySelector('#' + select.value);
|
|
394
|
-
if (selected) { selected.style.display = 'block'; }
|
|
395
|
-
}
|
|
396
|
-
}`);
|
|
397
|
-
if (interactions.tooltip) parts.push(`
|
|
398
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
399
|
-
const elements = document.querySelectorAll('[data-tooltip]');
|
|
400
|
-
elements.forEach(el => {
|
|
401
|
-
el.addEventListener('mouseenter', function(e) {
|
|
402
|
-
const tooltip = document.createElement('div');
|
|
403
|
-
tooltip.textContent = e.target.getAttribute('data-tooltip');
|
|
404
|
-
tooltip.style.cssText = 'position:absolute;background:#333;color:#fff;padding:5px;border-radius:3px;font-size:12px;z-index:1000;';
|
|
405
|
-
tooltip.id = 'tooltip';
|
|
406
|
-
document.body.appendChild(tooltip);
|
|
407
|
-
});
|
|
408
|
-
el.addEventListener('mouseleave', function() {
|
|
409
|
-
const tooltip = document.getElementById('tooltip');
|
|
410
|
-
if (tooltip) { tooltip.remove(); }
|
|
411
|
-
});
|
|
412
|
-
});
|
|
413
|
-
});`);
|
|
414
|
-
return parts.join("\n");
|
|
415
|
-
}
|
|
416
|
-
};
|
|
417
|
-
//#endregion
|
|
418
|
-
//#region lib/types/htmlAssembler.js
|
|
419
|
-
var HtmlAssembler = class {
|
|
420
|
-
interactionScript = new InteractionScript();
|
|
421
|
-
assemble(input) {
|
|
422
|
-
const { charts, config, reportData } = input;
|
|
423
|
-
const colors = config.colorScheme;
|
|
424
|
-
const columns = config.layout.columns;
|
|
425
|
-
const chartSections = charts.map((chart, _i) => `
|
|
426
|
-
<div class="chart-item" id="chart-${chart.type}" style="display:block;">
|
|
427
|
-
<h3>${chart.title}</h3>
|
|
428
|
-
${chart.svgContent}
|
|
429
|
-
${chart.errorMessage ? `<p style="color:${colors.danger};">${chart.errorMessage}</p>` : ""}
|
|
430
|
-
</div>`).join("\n");
|
|
431
|
-
const script = this.interactionScript.generate(config.interactions);
|
|
432
|
-
const dataJson = JSON.stringify(reportData);
|
|
433
|
-
return `<!DOCTYPE html>
|
|
434
|
-
<html lang="zh-CN">
|
|
435
|
-
<head>
|
|
436
|
-
<meta charset="UTF-8">
|
|
437
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
438
|
-
<title>数据资产可视化报表</title>
|
|
439
|
-
<style>
|
|
440
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
441
|
-
body { font-family: 'Microsoft YaHei', sans-serif; background: ${colors.background}; color: ${colors.text}; padding: 20px; }
|
|
442
|
-
h1 { text-align: center; margin-bottom: 20px; color: ${colors.primary}; }
|
|
443
|
-
.chart-grid { display: grid; grid-template-columns: repeat(${columns}, 1fr); gap: 20px; margin-bottom: 20px; }
|
|
444
|
-
.chart-item { background: white; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
|
445
|
-
.chart-item h3 { margin-bottom: 10px; color: ${colors.primary}; }
|
|
446
|
-
.footer { text-align: center; margin-top: 20px; color: #999; font-size: 12px; }
|
|
447
|
-
</style>
|
|
448
|
-
</head>
|
|
449
|
-
<body>
|
|
450
|
-
<h1>数据资产可视化报表</h1>
|
|
451
|
-
<div class="chart-grid">
|
|
452
|
-
${chartSections}
|
|
453
|
-
</div>
|
|
454
|
-
<div class="footer">生成时间: ${(/* @__PURE__ */ new Date()).toISOString()} | 模板版本: ${config.templateVersion}</div>
|
|
455
|
-
<script>
|
|
456
|
-
const reportData = ${dataJson};
|
|
457
|
-
${script}
|
|
458
|
-
<\/script>
|
|
459
|
-
</body>
|
|
460
|
-
</html>`;
|
|
461
|
-
}
|
|
462
|
-
};
|
|
463
|
-
//#endregion
|
|
464
|
-
//#region lib/types/templateLoader.js
|
|
465
|
-
var TemplateLoader = class {
|
|
466
|
-
load(config) {
|
|
467
|
-
if (config) return config;
|
|
468
|
-
return defaultVisualizationConfig;
|
|
469
|
-
}
|
|
470
|
-
};
|
|
471
|
-
//#endregion
|
|
472
|
-
//#region lib/types/index.js
|
|
473
|
-
const name = "data-visualization";
|
|
474
|
-
const inject = ["tools"];
|
|
475
|
-
function apply(ctx) {
|
|
476
|
-
const loader = new BusinessRulesLoader();
|
|
477
|
-
const pathValidator = new PathValidator();
|
|
478
|
-
const auditLogger = new AuditLogger();
|
|
479
|
-
const dataLoader = new ReportDataLoader();
|
|
480
|
-
const barRenderer = new BarChartRenderer();
|
|
481
|
-
const comparisonRenderer = new ComparisonChartRenderer();
|
|
482
|
-
const pieRenderer = new PieChartRenderer();
|
|
483
|
-
const radarRenderer = new RadarChartRenderer();
|
|
484
|
-
const flowchartRenderer = new FlowchartRenderer();
|
|
485
|
-
const htmlAssembler = new HtmlAssembler();
|
|
486
|
-
const templateLoader = new TemplateLoader();
|
|
487
|
-
ctx.tools.register(defineTool({
|
|
488
|
-
name: "generate_visualization",
|
|
489
|
-
description: "生成可视化HTML报表,含脱敏统计/清洗对比/资产分布/质量评分/血缘追踪图表",
|
|
490
|
-
parameters: {
|
|
491
|
-
reportPaths: {
|
|
492
|
-
type: "string",
|
|
493
|
-
required: true,
|
|
494
|
-
description: "各报告JSON文件路径(JSON格式: {masking, cleaning, inventory, quality, lineage})"
|
|
495
|
-
},
|
|
496
|
-
outputPath: {
|
|
497
|
-
type: "string",
|
|
498
|
-
required: true,
|
|
499
|
-
description: "HTML报表输出路径"
|
|
500
|
-
}
|
|
501
|
-
},
|
|
502
|
-
output: {
|
|
503
|
-
schema: { type: "string" },
|
|
504
|
-
render: (_args, value) => [{
|
|
505
|
-
type: "text",
|
|
506
|
-
text: value
|
|
507
|
-
}]
|
|
508
|
-
},
|
|
509
|
-
async execute(args) {
|
|
510
|
-
const { config, status: configStatus } = loader.load();
|
|
511
|
-
const workingDir = process.cwd();
|
|
512
|
-
const params = args;
|
|
513
|
-
const outputPath = params.outputPath;
|
|
514
|
-
try {
|
|
515
|
-
pathValidator.validate(outputPath, workingDir);
|
|
516
|
-
} catch {
|
|
517
|
-
return `错误:路径不合法 - ${outputPath}`;
|
|
518
|
-
}
|
|
519
|
-
const vizConfig = templateLoader.load(config.visualization ?? defaultVisualizationConfig);
|
|
520
|
-
let reportPathsObj;
|
|
521
|
-
try {
|
|
522
|
-
reportPathsObj = JSON.parse(params.reportPaths);
|
|
523
|
-
} catch {
|
|
524
|
-
reportPathsObj = {};
|
|
525
|
-
}
|
|
526
|
-
const reportPaths = reportPathsObj;
|
|
527
|
-
const maskingData = reportPaths.masking ? await dataLoader.load(reportPaths.masking) : { data: null };
|
|
528
|
-
const cleaningData = reportPaths.cleaning ? await dataLoader.load(reportPaths.cleaning) : { data: null };
|
|
529
|
-
const inventoryData = reportPaths.inventory ? await dataLoader.load(reportPaths.inventory) : { data: null };
|
|
530
|
-
const qualityData = reportPaths.quality ? await dataLoader.load(reportPaths.quality) : { data: null };
|
|
531
|
-
const lineageData = reportPaths.lineage ? await dataLoader.load(reportPaths.lineage) : { data: null };
|
|
532
|
-
const charts = [];
|
|
533
|
-
if (vizConfig.charts.bar?.enabled) charts.push(barRenderer.render(maskingData.data, vizConfig));
|
|
534
|
-
if (vizConfig.charts.comparison?.enabled) charts.push(comparisonRenderer.render(cleaningData.data, vizConfig));
|
|
535
|
-
if (vizConfig.charts.pie?.enabled) charts.push(pieRenderer.render(inventoryData.data, vizConfig));
|
|
536
|
-
if (vizConfig.charts.radar?.enabled) charts.push(radarRenderer.render(qualityData.data, vizConfig));
|
|
537
|
-
if (vizConfig.charts.flowchart?.enabled) charts.push(flowchartRenderer.render(lineageData.data, vizConfig));
|
|
538
|
-
const html = htmlAssembler.assemble({
|
|
539
|
-
charts,
|
|
540
|
-
config: vizConfig,
|
|
541
|
-
reportData: {
|
|
542
|
-
masking: maskingData.data,
|
|
543
|
-
cleaning: cleaningData.data,
|
|
544
|
-
inventory: inventoryData.data,
|
|
545
|
-
quality: qualityData.data,
|
|
546
|
-
lineage: lineageData.data
|
|
547
|
-
}
|
|
548
|
-
});
|
|
549
|
-
const htmlPath = path.resolve(outputPath);
|
|
550
|
-
const dir = path.dirname(htmlPath);
|
|
551
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
552
|
-
fs.writeFileSync(htmlPath, html, "utf-8");
|
|
553
|
-
auditLogger.log({
|
|
554
|
-
pluginName: "data-visualization",
|
|
555
|
-
operation: "generate_visualization",
|
|
556
|
-
inputPath: JSON.stringify(reportPaths),
|
|
557
|
-
outputPath: htmlPath,
|
|
558
|
-
result: "SUCCESS"
|
|
559
|
-
});
|
|
560
|
-
const result = {
|
|
561
|
-
htmlPath,
|
|
562
|
-
chartCount: charts.length,
|
|
563
|
-
chartsRendered: charts.filter((c) => c.dataAvailable).map((c) => c.type),
|
|
564
|
-
status: "SUCCESS",
|
|
565
|
-
configStatus
|
|
566
|
-
};
|
|
567
|
-
return JSON.stringify(result, null, 2);
|
|
568
|
-
}
|
|
569
|
-
}));
|
|
570
|
-
console.log("[data-visualization] 可视化报表插件已加载");
|
|
571
|
-
}
|
|
572
|
-
//#endregion
|
|
573
|
-
export { apply, inject, name };
|
package/lib/invariant.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { ChartRenderResult } from '../types.js';
|
|
2
|
-
import type { VisualizationConfig } from '@liuhange/dsh-data-asset-shared';
|
|
3
|
-
export declare class BarChartRenderer {
|
|
4
|
-
render(data: unknown, config: VisualizationConfig): ChartRenderResult;
|
|
5
|
-
private extractCounts;
|
|
6
|
-
private placeholder;
|
|
7
|
-
}
|
|
8
|
-
//# sourceMappingURL=barChartRenderer.d.ts.map
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { ChartRenderResult } from '../types.js';
|
|
2
|
-
import type { VisualizationConfig } from '@liuhange/dsh-data-asset-shared';
|
|
3
|
-
export declare class ComparisonChartRenderer {
|
|
4
|
-
render(data: unknown, config: VisualizationConfig): ChartRenderResult;
|
|
5
|
-
private extractData;
|
|
6
|
-
private placeholder;
|
|
7
|
-
}
|
|
8
|
-
//# sourceMappingURL=comparisonChartRenderer.d.ts.map
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { ChartRenderResult } from '../types.js';
|
|
2
|
-
import type { VisualizationConfig } from '@liuhange/dsh-data-asset-shared';
|
|
3
|
-
export declare class FlowchartRenderer {
|
|
4
|
-
render(data: unknown, config: VisualizationConfig): ChartRenderResult;
|
|
5
|
-
private extractData;
|
|
6
|
-
private placeholder;
|
|
7
|
-
}
|
|
8
|
-
//# sourceMappingURL=flowchartRenderer.d.ts.map
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { ChartRenderResult } from '../types.js';
|
|
2
|
-
import type { VisualizationConfig } from '@liuhange/dsh-data-asset-shared';
|
|
3
|
-
export declare class PieChartRenderer {
|
|
4
|
-
render(data: unknown, config: VisualizationConfig): ChartRenderResult;
|
|
5
|
-
private extractData;
|
|
6
|
-
private placeholder;
|
|
7
|
-
}
|
|
8
|
-
//# sourceMappingURL=pieChartRenderer.d.ts.map
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { ChartRenderResult } from '../types.js';
|
|
2
|
-
import type { VisualizationConfig } from '@liuhange/dsh-data-asset-shared';
|
|
3
|
-
export declare class RadarChartRenderer {
|
|
4
|
-
render(data: unknown, config: VisualizationConfig): ChartRenderResult;
|
|
5
|
-
private extractData;
|
|
6
|
-
private placeholder;
|
|
7
|
-
}
|
|
8
|
-
//# sourceMappingURL=radarChartRenderer.d.ts.map
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { VisualizationConfig } from '@liuhange/dsh-data-asset-shared';
|
|
2
|
-
import type { ChartRenderResult } from './types.js';
|
|
3
|
-
export interface HtmlAssembleInput {
|
|
4
|
-
charts: ChartRenderResult[];
|
|
5
|
-
config: VisualizationConfig;
|
|
6
|
-
reportData: Record<string, unknown>;
|
|
7
|
-
}
|
|
8
|
-
export declare class HtmlAssembler {
|
|
9
|
-
private interactionScript;
|
|
10
|
-
assemble(input: HtmlAssembleInput): string;
|
|
11
|
-
}
|
|
12
|
-
//# sourceMappingURL=htmlAssembler.d.ts.map
|
package/lib/types/index.d.ts
DELETED
package/lib/types/invariant.d.ts
DELETED
package/lib/types/types.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
export type ChartType = 'bar' | 'comparison' | 'pie' | 'radar' | 'flowchart';
|
|
2
|
-
export interface GenerateVisualizationParams {
|
|
3
|
-
reportPaths: {
|
|
4
|
-
masking?: string;
|
|
5
|
-
cleaning?: string;
|
|
6
|
-
inventory?: string;
|
|
7
|
-
quality?: string;
|
|
8
|
-
lineage?: string;
|
|
9
|
-
};
|
|
10
|
-
outputPath: string;
|
|
11
|
-
}
|
|
12
|
-
export interface ChartRenderResult {
|
|
13
|
-
type: ChartType;
|
|
14
|
-
title: string;
|
|
15
|
-
svgContent: string;
|
|
16
|
-
dataAvailable: boolean;
|
|
17
|
-
errorMessage?: string;
|
|
18
|
-
}
|
|
19
|
-
export interface GenerateVisualizationResult {
|
|
20
|
-
htmlPath: string;
|
|
21
|
-
chartCount: number;
|
|
22
|
-
chartsRendered: string[];
|
|
23
|
-
status: 'SUCCESS' | 'FAILED';
|
|
24
|
-
configStatus: string;
|
|
25
|
-
}
|
|
26
|
-
export interface ReportDataLoadResult {
|
|
27
|
-
data: unknown;
|
|
28
|
-
error?: string;
|
|
29
|
-
}
|
|
30
|
-
//# sourceMappingURL=types.d.ts.map
|