You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
176 lines
5.4 KiB
176 lines
5.4 KiB
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>SQL Data Lineage Visualization</title>
|
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
<style>
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
margin: 20px;
|
|
}
|
|
<style>
|
|
.node rect {
|
|
stroke: #333;
|
|
fill: #f8f8f8;
|
|
stroke-width: 1.5px;
|
|
rx: 5;
|
|
ry: 5;
|
|
}
|
|
.node text {
|
|
font-size: 12px;
|
|
fill: #333;
|
|
}
|
|
.link {
|
|
stroke: #666;
|
|
stroke-opacity: 0.8;
|
|
stroke-width: 2px;
|
|
}
|
|
.arrow {
|
|
fill: #666;
|
|
}
|
|
.title {
|
|
color: #333;
|
|
font-size: 20px;
|
|
margin-bottom: 20px;
|
|
}
|
|
</style>
|
|
.node text {
|
|
font-size: 12px;
|
|
}
|
|
.link {
|
|
stroke: #999;
|
|
stroke-opacity: 0.6;
|
|
stroke-width: 2px;
|
|
}
|
|
.arrow {
|
|
fill: #999;
|
|
stroke-opacity: 0.6;
|
|
}
|
|
.title {
|
|
font-size: 18px;
|
|
font-weight: bold;
|
|
margin-bottom: 20px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="title">SQL Data Lineage Visualization</div>
|
|
<div id="lineage"></div>
|
|
|
|
<script>
|
|
const data = {
|
|
nodes: [
|
|
{ id: "t_sys_loginperson", name: "t_sys_loginperson", columns: ["person_id", "person_name", "mz", "xb"] },
|
|
{ id: "t_dm_mz", name: "t_dm_mz", columns: ["mz_id", "mz_name"] },
|
|
{ id: "t_dm_xb", name: "t_dm_xb", columns: ["xb_id", "xb_name"] },
|
|
{ id: "result", name: "Result", columns: ["person_id", "person_name", "mz", "mz_name", "xb", "xb_name"] }
|
|
],
|
|
links: [
|
|
{ source: "t_sys_loginperson", target: "result", columns: ["person_id", "person_name", "mz", "xb"] },
|
|
{ source: "t_dm_mz", target: "result", columns: ["mz_name"], join: "t_sys_loginperson.mz = t_dm_mz.mz_id" },
|
|
{ source: "t_dm_xb", target: "result", columns: ["xb_name"], join: "t_sys_loginperson.xb = t_dm_xb.xb_id" }
|
|
]
|
|
};
|
|
|
|
const width = 800;
|
|
const height = 500;
|
|
|
|
const svg = d3.select("#lineage")
|
|
.append("svg")
|
|
.attr("width", width)
|
|
.attr("height", height);
|
|
|
|
const simulation = d3.forceSimulation(data.nodes)
|
|
.force("link", d3.forceLink(data.links).id(d => d.id).distance(150))
|
|
.force("charge", d3.forceManyBody().strength(-500))
|
|
.force("center", d3.forceCenter(width / 2, height / 2));
|
|
|
|
const link = svg.append("g")
|
|
.selectAll("line")
|
|
.data(data.links)
|
|
.enter().append("line")
|
|
.attr("class", "link")
|
|
.attr("marker-end", "url(#arrow)");
|
|
|
|
const node = svg.append("g")
|
|
.selectAll("g")
|
|
.data(data.nodes)
|
|
.enter().append("g")
|
|
.call(d3.drag()
|
|
.on("start", dragstarted)
|
|
.on("drag", dragged)
|
|
.on("end", dragended));
|
|
|
|
node.append("rect")
|
|
.attr("width", d => Math.max(120, d.name.length * 8))
|
|
.attr("height", d => 40 + d.columns.length * 20)
|
|
.attr("rx", 5)
|
|
.attr("ry", 5);
|
|
|
|
node.append("text")
|
|
.attr("x", 10)
|
|
.attr("y", 20)
|
|
.attr("font-weight", "bold")
|
|
.text(d => d.name);
|
|
|
|
data.nodes.forEach(d => {
|
|
const columnGroup = node.append("g")
|
|
.attr("transform", `translate(10, 30)`);
|
|
|
|
d.columns.forEach((col, i) => {
|
|
columnGroup.append("text")
|
|
.attr("x", 0)
|
|
.attr("y", 20 + i * 20)
|
|
.text(col);
|
|
});
|
|
});
|
|
|
|
svg.append("defs").append("marker")
|
|
.attr("id", "arrow")
|
|
.attr("viewBox", "0 -5 10 10")
|
|
.attr("refX", 25)
|
|
.attr("refY", 0)
|
|
.attr("markerWidth", 6)
|
|
.attr("markerHeight", 6)
|
|
.attr("orient", "auto")
|
|
.append("path")
|
|
.attr("class", "arrow")
|
|
.attr("d", "M0,-5L10,0L0,5");
|
|
|
|
simulation.on("tick", () => {
|
|
link
|
|
.attr("x1", d => d.source.x)
|
|
.attr("y1", d => d.source.y)
|
|
.attr("x2", d => d.target.x)
|
|
.attr("y2", d => d.target.y);
|
|
|
|
node.attr("transform", d => `translate(${d.x - Math.max(120, d.name.length * 8) / 2},${d.y - (40 + d.columns.length * 20) / 2})`);
|
|
});
|
|
|
|
function dragstarted(event, d) {
|
|
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
d.fx = d.x;
|
|
d.fy = d.y;
|
|
}
|
|
|
|
function dragged(event, d) {
|
|
d.fx = event.x;
|
|
d.fy = event.y;
|
|
}
|
|
|
|
function dragended(event, d) {
|
|
if (!event.active) simulation.alphaTarget(0);
|
|
d.fx = null;
|
|
d.fy = null;
|
|
}
|
|
|
|
node.on("mouseover", function() {
|
|
d3.select(this).select("rect")
|
|
.style("fill", "#e6f7ff");
|
|
}).on("mouseout", function() {
|
|
d3.select(this).select("rect")
|
|
.style("fill", "#f8f8f8");
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |