sayou-visualizer 0.0.10__py3-none-any.whl → 0.0.11__py3-none-any.whl
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.
- sayou/visualizer/pipeline.py +13 -3
- sayou/visualizer/renderer/showcase_kg_renderer.py +193 -205
- {sayou_visualizer-0.0.10.dist-info → sayou_visualizer-0.0.11.dist-info}/METADATA +1 -1
- {sayou_visualizer-0.0.10.dist-info → sayou_visualizer-0.0.11.dist-info}/RECORD +5 -5
- {sayou_visualizer-0.0.10.dist-info → sayou_visualizer-0.0.11.dist-info}/WHEEL +0 -0
sayou/visualizer/pipeline.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from sayou.core.base_component import BaseComponent
|
|
2
2
|
|
|
3
|
-
from .renderer.
|
|
3
|
+
from .renderer.analytic_kg_renderer import AnalyticKGRenderer
|
|
4
|
+
from .renderer.showcase_kg_renderer import ShowcaseKGRenderer
|
|
4
5
|
from .renderer.pyvis_renderer import PyVisRenderer
|
|
5
6
|
from .tracer.graph_tracer import GraphTracer
|
|
6
7
|
from .tracer.rich_tracer import RichConsoleTracer
|
|
@@ -81,11 +82,20 @@ class VisualizerPipeline(BaseComponent):
|
|
|
81
82
|
self._renderer = PyVisRenderer()
|
|
82
83
|
self._renderer.render(self._graph_tracer.graph, output_path, **kwargs)
|
|
83
84
|
|
|
84
|
-
def
|
|
85
|
+
def render_analytic_kg(self, json_path: str, output_path: str = "kg_view.html"):
|
|
85
86
|
"""
|
|
86
87
|
Visualizes the OUTPUT JSON (Knowledge Graph).
|
|
87
88
|
"""
|
|
88
|
-
self._kg_renderer =
|
|
89
|
+
self._kg_renderer = AnalyticKGRenderer()
|
|
90
|
+
self._kg_renderer.render(json_path, output_path)
|
|
91
|
+
|
|
92
|
+
def render_showcase_kg(
|
|
93
|
+
self, json_path: str, output_path: str = "showcase_kg_view.html"
|
|
94
|
+
):
|
|
95
|
+
"""
|
|
96
|
+
Visualizes the OUTPUT JSON (Knowledge Graph).
|
|
97
|
+
"""
|
|
98
|
+
self._kg_renderer = ShowcaseKGRenderer()
|
|
89
99
|
self._kg_renderer.render(json_path, output_path)
|
|
90
100
|
|
|
91
101
|
def save_live_log(self, output_path="live_status.html"):
|
|
@@ -1,298 +1,287 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
|
-
from collections import defaultdict
|
|
4
|
-
|
|
5
3
|
from sayou.core.base_component import BaseComponent
|
|
6
4
|
|
|
7
5
|
|
|
8
6
|
class ShowcaseKGRenderer(BaseComponent):
|
|
9
7
|
"""
|
|
10
|
-
Renders
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
rather than granular node analysis.
|
|
8
|
+
Renders the Final Stable 3D Knowledge Graph.
|
|
9
|
+
- [Architecture] Removed unstable Post-Processing shaders to fix physics crash.
|
|
10
|
+
- [Visuals] Uses native 'Emissive' materials for Neon aesthetics.
|
|
11
|
+
- [Topology] Distinguishes 'Imports' (Gold/Bright) vs 'Hierarchy' (Dark/Blue).
|
|
15
12
|
"""
|
|
16
13
|
|
|
17
14
|
component_name = "ShowcaseKGRenderer"
|
|
18
15
|
|
|
19
|
-
def render(self, json_path: str, output_path: str = "
|
|
16
|
+
def render(self, json_path: str, output_path: str = "sayou_showcase_3d.html"):
|
|
20
17
|
if not os.path.exists(json_path):
|
|
21
|
-
self._log(f"Output file not found: {json_path}", level="error")
|
|
22
18
|
return
|
|
23
19
|
|
|
24
20
|
with open(json_path, "r", encoding="utf-8") as f:
|
|
25
21
|
raw_data = json.load(f)
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# ---------------------------------------------------------
|
|
30
|
-
nodes_by_source = defaultdict(list)
|
|
31
|
-
final_nodes = []
|
|
32
|
-
final_links = []
|
|
33
|
-
|
|
23
|
+
nodes = []
|
|
24
|
+
links = []
|
|
34
25
|
existing_ids = set()
|
|
35
26
|
|
|
36
27
|
for node in raw_data.get("nodes", []):
|
|
37
28
|
node_id = node.get("node_id")
|
|
38
29
|
existing_ids.add(node_id)
|
|
39
30
|
attrs = node.get("attributes", {})
|
|
40
|
-
|
|
31
|
+
n_cls = node.get("node_class", "unknown").lower()
|
|
32
|
+
|
|
33
|
+
group = "Chunk"
|
|
34
|
+
color = "#4a69bd"
|
|
35
|
+
val = 3
|
|
41
36
|
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
if "file" in n_cls or "package" in n_cls:
|
|
38
|
+
group = "Document"
|
|
39
|
+
color = "#00d2d3" # Cyan
|
|
40
|
+
val = 20
|
|
41
|
+
elif "class" in n_cls:
|
|
42
|
+
group = "Header"
|
|
43
|
+
color = "#ff6b81" # Pink
|
|
44
|
+
val = 12
|
|
45
|
+
elif "method" in n_cls or "function" in n_cls:
|
|
46
|
+
group = "Code"
|
|
47
|
+
color = "#feca57" # Gold
|
|
48
|
+
val = 6
|
|
49
|
+
elif "library" in n_cls:
|
|
50
|
+
group = "Library"
|
|
51
|
+
color = "#2ed573" # Green
|
|
52
|
+
val = 10
|
|
44
53
|
|
|
45
|
-
|
|
54
|
+
label = attrs.get("label") or node.get("friendly_name") or node_id
|
|
55
|
+
if group == "Document":
|
|
56
|
+
label = os.path.basename(attrs.get("sayou:filePath", label))
|
|
46
57
|
|
|
47
58
|
clean_attrs = {}
|
|
48
59
|
for k, v in attrs.items():
|
|
49
|
-
if isinstance(v, str) and len(v) >
|
|
60
|
+
if isinstance(v, str) and len(v) > 200:
|
|
50
61
|
clean_attrs[k] = v[:200] + "..."
|
|
51
|
-
|
|
62
|
+
elif not k.startswith("sayou:"):
|
|
52
63
|
clean_attrs[k] = v
|
|
53
64
|
|
|
54
|
-
|
|
55
|
-
if len(display_label) > 30:
|
|
56
|
-
display_label = display_label[:30] + "..."
|
|
57
|
-
|
|
58
|
-
group = "Chunk"
|
|
59
|
-
color = "#1e90ff"
|
|
60
|
-
val = 5
|
|
61
|
-
|
|
62
|
-
if sem_type in ["h1", "h2", "h3", "title"]:
|
|
63
|
-
group = "Header"
|
|
64
|
-
color = "#ffa502"
|
|
65
|
-
val = 12
|
|
66
|
-
elif "list" in sem_type:
|
|
67
|
-
group = "List"
|
|
68
|
-
color = "#2ed573"
|
|
69
|
-
val = 4
|
|
70
|
-
elif "table" in sem_type:
|
|
71
|
-
group = "Table"
|
|
72
|
-
color = "#a55eea"
|
|
73
|
-
val = 10
|
|
74
|
-
elif "code" in sem_type:
|
|
75
|
-
group = "Code"
|
|
76
|
-
color = "#ff4757"
|
|
77
|
-
val = 8
|
|
78
|
-
|
|
79
|
-
final_nodes.append(
|
|
65
|
+
nodes.append(
|
|
80
66
|
{
|
|
81
67
|
"id": node_id,
|
|
82
|
-
"label":
|
|
68
|
+
"label": label,
|
|
83
69
|
"group": group,
|
|
84
|
-
"sem_type": sem_type,
|
|
85
70
|
"color": color,
|
|
86
71
|
"val": val,
|
|
87
72
|
"attributes": clean_attrs,
|
|
88
|
-
"source": source,
|
|
89
|
-
}
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
for edge in raw_data.get("links", []) + raw_data.get("edges", []):
|
|
93
|
-
final_links.append(
|
|
94
|
-
{
|
|
95
|
-
"source": edge.get("source"),
|
|
96
|
-
"target": edge.get("target"),
|
|
97
|
-
"relation": edge.get("relation", "relates_to"),
|
|
98
73
|
}
|
|
99
74
|
)
|
|
100
75
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
virtual_doc_id = f"VIRTUAL_DOC:{source_name}"
|
|
76
|
+
# [2] Edge data processing
|
|
77
|
+
for edge in raw_data.get("edges", []):
|
|
78
|
+
src = edge.get("source")
|
|
79
|
+
tgt = edge.get("target")
|
|
106
80
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
"label": source_name,
|
|
111
|
-
"group": "Document",
|
|
112
|
-
"color": "#ff4757",
|
|
113
|
-
"val": 30,
|
|
114
|
-
"attributes": {
|
|
115
|
-
"type": "Virtual Parent",
|
|
116
|
-
"child_count": len(child_ids),
|
|
117
|
-
},
|
|
118
|
-
"is_virtual": True,
|
|
119
|
-
}
|
|
120
|
-
)
|
|
81
|
+
if src in existing_ids and tgt in existing_ids:
|
|
82
|
+
e_type = edge.get("type", "relates")
|
|
83
|
+
is_import = "import" in e_type or "calls" in e_type
|
|
121
84
|
|
|
122
|
-
|
|
123
|
-
final_links.append(
|
|
85
|
+
links.append(
|
|
124
86
|
{
|
|
125
|
-
"source":
|
|
126
|
-
"target":
|
|
127
|
-
"
|
|
87
|
+
"source": src,
|
|
88
|
+
"target": tgt,
|
|
89
|
+
"type": e_type,
|
|
90
|
+
"is_import": is_import,
|
|
128
91
|
}
|
|
129
92
|
)
|
|
130
93
|
|
|
131
|
-
graph_data = {"nodes":
|
|
132
|
-
self.
|
|
94
|
+
graph_data = {"nodes": nodes, "links": links}
|
|
95
|
+
self._generate_html(graph_data, output_path)
|
|
96
|
+
self._log(f"✅ Final Visual Showcase generated at: {output_path}")
|
|
97
|
+
|
|
98
|
+
def _generate_html(self, graph_data, output_path):
|
|
99
|
+
json_str = json.dumps(graph_data)
|
|
133
100
|
|
|
134
|
-
# ---------------------------------------------------------
|
|
135
|
-
# [JS Logic] Renderer
|
|
136
|
-
# ---------------------------------------------------------
|
|
137
101
|
html_content = f"""
|
|
138
102
|
<!DOCTYPE html>
|
|
139
103
|
<html lang="en">
|
|
140
104
|
<head>
|
|
141
105
|
<meta charset="UTF-8">
|
|
142
106
|
<title>Sayou Dataverse</title>
|
|
107
|
+
<script src="https://unpkg.com/three@0.160.0/build/three.min.js"></script>
|
|
108
|
+
<script src="https://unpkg.com/3d-force-graph@1.73.1/dist/3d-force-graph.min.js"></script>
|
|
143
109
|
<style>
|
|
144
|
-
body {{ margin: 0; background-color: #
|
|
110
|
+
body {{ margin: 0; background-color: #020202; overflow: hidden; font-family: sans-serif; }}
|
|
145
111
|
#graph {{ width: 100%; height: 100vh; }}
|
|
146
|
-
|
|
147
112
|
#info {{
|
|
148
|
-
position: absolute; top:
|
|
149
|
-
background: rgba(
|
|
150
|
-
border: 1px solid
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
font-family: 'Consolas', 'Monaco', monospace;
|
|
155
|
-
backdrop-filter: blur(5px);
|
|
156
|
-
display: none; pointer-events: none;
|
|
157
|
-
border-radius: 0 10px 10px 0;
|
|
158
|
-
}}
|
|
159
|
-
.tag {{
|
|
160
|
-
display: inline-block; padding: 2px 8px; border-radius: 2px;
|
|
161
|
-
font-size: 9px; font-weight: 800; color: #000; margin-bottom: 12px;
|
|
162
|
-
text-transform: uppercase; letter-spacing: 1px;
|
|
113
|
+
position: absolute; top: 30px; right: 30px; width: 300px;
|
|
114
|
+
background: rgba(15, 20, 30, 0.9);
|
|
115
|
+
border: 1px solid #444; border-left: 4px solid #00d2d3;
|
|
116
|
+
box-shadow: 0 0 20px rgba(0,0,0,0.8);
|
|
117
|
+
color: #fff; padding: 20px; font-size:13px;
|
|
118
|
+
backdrop-filter: blur(5px); display: none; border-radius: 4px; pointer-events: none;
|
|
163
119
|
}}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}}
|
|
169
|
-
.row {{ font-size: 11px; margin-bottom: 4px; color: #a4b0be; }}
|
|
170
|
-
.key {{ color: #00d2d3; margin-right: 5px; }}
|
|
120
|
+
.tag {{ display: inline-block; padding: 3px 6px; border-radius: 4px; color: #000; font-weight:bold; font-size:10px; margin-bottom:10px; }}
|
|
121
|
+
h2 {{ margin:0 0 10px 0; font-size:16px; color:#eee; }}
|
|
122
|
+
.row {{ margin-bottom:4px; color:#aaa; border-bottom:1px solid #333; padding-bottom:2px; }}
|
|
123
|
+
.key {{ color:#00d2d3; margin-right:5px; }}
|
|
171
124
|
</style>
|
|
172
|
-
<script src="https://unpkg.com/three@0.160.0/build/three.min.js"></script>
|
|
173
|
-
<script src="https://unpkg.com/3d-force-graph@1.73.1/dist/3d-force-graph.min.js"></script>
|
|
174
125
|
</head>
|
|
175
126
|
<body>
|
|
176
127
|
<div id="graph"></div>
|
|
177
128
|
<div id="info"></div>
|
|
178
129
|
|
|
179
130
|
<script>
|
|
180
|
-
const gData = {
|
|
131
|
+
const gData = {json_str};
|
|
181
132
|
const infoDiv = document.getElementById('info');
|
|
133
|
+
|
|
134
|
+
// [State]
|
|
135
|
+
const highlightNodes = new Set();
|
|
136
|
+
const highlightLinks = new Set();
|
|
137
|
+
let hoverNode = null;
|
|
138
|
+
let isFlying = false;
|
|
182
139
|
|
|
140
|
+
// [Init] Big Bang
|
|
141
|
+
gData.nodes.forEach(node => {{
|
|
142
|
+
node.x = Math.random() * 2000 - 1000;
|
|
143
|
+
node.y = Math.random() * 2000 - 1000;
|
|
144
|
+
node.z = Math.random() * 2000 - 1000;
|
|
145
|
+
}});
|
|
146
|
+
|
|
147
|
+
// [Graph Init]
|
|
183
148
|
const Graph = ForceGraph3D()(document.getElementById('graph'))
|
|
184
149
|
.graphData(gData)
|
|
185
|
-
.backgroundColor('#
|
|
150
|
+
.backgroundColor('#050505')
|
|
186
151
|
.showNavInfo(false)
|
|
187
|
-
|
|
188
|
-
|
|
152
|
+
.nodeLabel(null)
|
|
153
|
+
.cooldownTicks(50);
|
|
154
|
+
|
|
155
|
+
// [Physics]
|
|
156
|
+
Graph.d3Force('charge').strength(-100);
|
|
157
|
+
Graph.d3Force('link').distance(link => link.is_import ? 10 : 100);
|
|
158
|
+
|
|
159
|
+
// [Visuals]
|
|
160
|
+
Graph
|
|
189
161
|
.nodeThreeObject(node => {{
|
|
190
|
-
let
|
|
191
|
-
|
|
192
|
-
|
|
162
|
+
let isDimmed = false;
|
|
163
|
+
let isTarget = false;
|
|
164
|
+
if (hoverNode) {{
|
|
165
|
+
if (hoverNode === node || highlightNodes.has(node)) isTarget = true;
|
|
166
|
+
else isDimmed = true;
|
|
167
|
+
}}
|
|
168
|
+
const baseColor = node.color;
|
|
169
|
+
const opacity = isDimmed ? 0.1 : 0.9;
|
|
170
|
+
const emissiveInt = isTarget ? 1.5 : (isDimmed ? 0 : 0.6);
|
|
171
|
+
|
|
172
|
+
const material = new THREE.MeshPhongMaterial({{
|
|
173
|
+
color: baseColor,
|
|
174
|
+
emissive: baseColor,
|
|
175
|
+
emissiveIntensity: emissiveInt,
|
|
176
|
+
transparent: true,
|
|
177
|
+
opacity: opacity,
|
|
178
|
+
shininess: 90
|
|
179
|
+
}});
|
|
180
|
+
|
|
193
181
|
if (node.group === "Document") {{
|
|
194
|
-
const
|
|
195
|
-
geometry = new THREE.BoxGeometry(
|
|
196
|
-
|
|
182
|
+
const s = node.val;
|
|
183
|
+
const geometry = new THREE.BoxGeometry(s, s, s);
|
|
197
184
|
const edges = new THREE.EdgesGeometry(geometry);
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
opacity: 0.4
|
|
202
|
-
}});
|
|
203
|
-
const wireframe = new THREE.LineSegments(edges, material);
|
|
204
|
-
|
|
205
|
-
const coreGeo = new THREE.BoxGeometry(size*0.2, size*0.2, size*0.2);
|
|
206
|
-
const coreMat = new THREE.MeshBasicMaterial({{ color: node.color, wireframe: true }});
|
|
207
|
-
wireframe.add(new THREE.Mesh(coreGeo, coreMat));
|
|
208
|
-
|
|
185
|
+
const lineMat = new THREE.LineBasicMaterial({{ color: baseColor, transparent:true, opacity: isDimmed ? 0.05 : 0.4 }});
|
|
186
|
+
const wireframe = new THREE.LineSegments(edges, lineMat);
|
|
187
|
+
wireframe.add(new THREE.Mesh(new THREE.BoxGeometry(s*0.4, s*0.4, s*0.4), material));
|
|
209
188
|
return wireframe;
|
|
210
189
|
}}
|
|
190
|
+
else if (node.group === "Header") return new THREE.Mesh(new THREE.OctahedronGeometry(node.val * 0.7), material);
|
|
191
|
+
else return new THREE.Mesh(new THREE.IcosahedronGeometry(node.val * 0.6, 2), material);
|
|
192
|
+
}})
|
|
193
|
+
.linkWidth(link => {{
|
|
194
|
+
if (highlightLinks.has(link)) return 3;
|
|
195
|
+
if (hoverNode && !highlightLinks.has(link)) return 0;
|
|
196
|
+
return link.is_import ? 1.5 : 0.5;
|
|
197
|
+
}})
|
|
198
|
+
.linkColor(link => {{
|
|
199
|
+
if (highlightLinks.has(link)) return link.is_import ? '#feca57' : '#00d2d3';
|
|
200
|
+
return link.is_import ? 'rgba(254, 202, 87, 0.4)' : 'rgba(44, 62, 80, 0.3)';
|
|
201
|
+
}})
|
|
202
|
+
.linkDirectionalParticles(link => highlightLinks.has(link) ? 3 : 0)
|
|
203
|
+
.linkDirectionalParticleWidth(3)
|
|
204
|
+
.onNodeHover(node => {{
|
|
205
|
+
if ((!node && !hoverNode) || (node && hoverNode === node)) return;
|
|
211
206
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
emissiveIntensity: 0.5,
|
|
219
|
-
shininess: 100,
|
|
220
|
-
flatShading: true
|
|
221
|
-
}});
|
|
222
|
-
}}
|
|
223
|
-
|
|
224
|
-
// 3. Chunk / Text: [데이터 오브]
|
|
225
|
-
else {{
|
|
226
|
-
// 단순 구 대신 Icosahedron(정이십면체)을 써서 디지털 느낌
|
|
227
|
-
geometry = new THREE.IcosahedronGeometry(3, 1);
|
|
228
|
-
material = new THREE.MeshLambertMaterial({{
|
|
229
|
-
color: node.color,
|
|
230
|
-
transparent: true,
|
|
231
|
-
opacity: 0.8
|
|
207
|
+
highlightNodes.clear(); highlightLinks.clear();
|
|
208
|
+
if (node) {{
|
|
209
|
+
highlightNodes.add(node);
|
|
210
|
+
gData.links.forEach(link => {{
|
|
211
|
+
if (link.source.id === node.id) {{ highlightNodes.add(link.target); highlightLinks.add(link); }}
|
|
212
|
+
else if (link.target.id === node.id) {{ highlightNodes.add(link.source); highlightLinks.add(link); }}
|
|
232
213
|
}});
|
|
233
214
|
}}
|
|
215
|
+
hoverNode = node || null;
|
|
216
|
+
requestAnimationFrame(() => {{
|
|
217
|
+
Graph
|
|
218
|
+
.nodeThreeObject(Graph.nodeThreeObject())
|
|
219
|
+
.linkWidth(Graph.linkWidth())
|
|
220
|
+
.linkColor(Graph.linkColor())
|
|
221
|
+
.linkDirectionalParticles(Graph.linkDirectionalParticles());
|
|
222
|
+
}});
|
|
234
223
|
|
|
235
|
-
return new THREE.Mesh(geometry, material);
|
|
236
|
-
}})
|
|
237
|
-
|
|
238
|
-
// [Link Design]
|
|
239
|
-
.linkWidth(link => link.relation === "CONTAINS" ? 0 : 0.5)
|
|
240
|
-
.linkColor(() => '#2f3542')
|
|
241
|
-
.linkDirectionalParticles(link => link.relation === "CONTAINS" ? 1 : 3)
|
|
242
|
-
.linkDirectionalParticleWidth(1.2)
|
|
243
|
-
.linkDirectionalParticleSpeed(0.006)
|
|
244
|
-
.linkDirectionalParticleColor(link => link.relation === "CONTAINS" ? '#57606f' : '#00f2ff')
|
|
245
|
-
|
|
246
|
-
// [Interaction]
|
|
247
|
-
.onNodeHover(node => {{
|
|
248
224
|
document.body.style.cursor = node ? 'crosshair' : null;
|
|
249
225
|
if (node) {{
|
|
250
226
|
infoDiv.style.display = 'block';
|
|
251
|
-
|
|
252
|
-
let tagColor = node.color;
|
|
253
|
-
if(node.group === 'Document') tagColor = '#ff4757';
|
|
254
|
-
|
|
255
|
-
let html = `<span class='tag' style='background:${{tagColor}}'>${{node.group}}</span>`;
|
|
256
|
-
if(node.sem_type) html += `<span class='tag' style='background:#2f3542; color:#fff; margin-left:5px'>${{node.sem_type}}</span>`;
|
|
257
|
-
|
|
258
|
-
html += `<h3>${{node.label}}</h3>`;
|
|
259
|
-
|
|
227
|
+
let html = `<span class='tag' style='background:${{node.color}}'>${{node.group}}</span><h2>${{node.label}}</h2>`;
|
|
260
228
|
if (node.attributes) {{
|
|
261
229
|
for (const [k, v] of Object.entries(node.attributes)) {{
|
|
262
|
-
if(k
|
|
263
|
-
let key = k.includes(':') ? k.split(':').pop() : k;
|
|
264
|
-
html += `<div class='row'><span class='key'>${{key}}:</span> ${{v}}</div>`;
|
|
230
|
+
if(k!=='type') html += `<div class='row'><span class='key'>${{k.split(':').pop()}}:</span> ${{v}}</div>`;
|
|
265
231
|
}}
|
|
266
232
|
}}
|
|
267
233
|
infoDiv.innerHTML = html;
|
|
268
|
-
}} else
|
|
269
|
-
infoDiv.style.display = 'none';
|
|
270
|
-
}}
|
|
234
|
+
}} else infoDiv.style.display = 'none';
|
|
271
235
|
}})
|
|
272
236
|
.onNodeClick(node => {{
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
237
|
+
if (!node) return;
|
|
238
|
+
isFlying = true;
|
|
239
|
+
const dist = 150;
|
|
240
|
+
const distRatio = 1 + dist/Math.hypot(node.x, node.y, node.z);
|
|
241
|
+
Graph.cameraPosition(
|
|
242
|
+
{{ x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }},
|
|
243
|
+
node,
|
|
244
|
+
2000
|
|
245
|
+
);
|
|
246
|
+
setTimeout(() => {{ isFlying = false; }}, 2000);
|
|
279
247
|
}});
|
|
280
248
|
|
|
281
|
-
// [
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
249
|
+
// [Env]
|
|
250
|
+
const scene = Graph.scene();
|
|
251
|
+
const starsGeo = new THREE.BufferGeometry();
|
|
252
|
+
const pos = new Float32Array(2000 * 3);
|
|
253
|
+
for(let i=0; i<2000*3; i++) pos[i] = (Math.random()-0.5) * 4000;
|
|
254
|
+
starsGeo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
|
|
255
|
+
scene.add(new THREE.Points(starsGeo, new THREE.PointsMaterial({{size:2, color:0xffffff, opacity:0.5, transparent:true}})));
|
|
256
|
+
scene.add(new THREE.AmbientLight(0x222222));
|
|
257
|
+
const light = new THREE.DirectionalLight(0xffffff, 1);
|
|
258
|
+
light.position.set(100, 100, 100);
|
|
259
|
+
scene.add(light);
|
|
292
260
|
|
|
293
|
-
// [
|
|
294
|
-
|
|
295
|
-
|
|
261
|
+
// [Manual Orbit Engine]
|
|
262
|
+
setInterval(() => {{
|
|
263
|
+
if (hoverNode || isFlying) return;
|
|
264
|
+
const cam = Graph.camera();
|
|
265
|
+
const controls = Graph.controls();
|
|
266
|
+
if (!controls) return;
|
|
267
|
+
|
|
268
|
+
const target = controls.target;
|
|
269
|
+
const relX = cam.position.x - target.x;
|
|
270
|
+
const relZ = cam.position.z - target.z;
|
|
271
|
+
const r = Math.sqrt(relX*relX + relZ*relZ);
|
|
272
|
+
let theta = Math.atan2(relZ, relX);
|
|
273
|
+
|
|
274
|
+
theta += 0.0015;
|
|
275
|
+
|
|
276
|
+
const newX = target.x + r * Math.cos(theta);
|
|
277
|
+
const newZ = target.z + r * Math.sin(theta);
|
|
278
|
+
|
|
279
|
+
Graph.cameraPosition(
|
|
280
|
+
{{ x: newX, y: cam.position.y, z: newZ }},
|
|
281
|
+
target,
|
|
282
|
+
0
|
|
283
|
+
);
|
|
284
|
+
}}, 15);
|
|
296
285
|
|
|
297
286
|
</script>
|
|
298
287
|
</body>
|
|
@@ -300,4 +289,3 @@ class ShowcaseKGRenderer(BaseComponent):
|
|
|
300
289
|
"""
|
|
301
290
|
with open(output_path, "w", encoding="utf-8") as f:
|
|
302
291
|
f.write(html_content)
|
|
303
|
-
self._log(f"✅ Semantic 3D KG Showcase saved to: {output_path}")
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
sayou/visualizer/__init__.py,sha256=DRv-5qzP6nal7qXVf7Zl67CQx3Kf3mTZhlLKl-vs_G0,82
|
|
2
|
-
sayou/visualizer/pipeline.py,sha256=
|
|
2
|
+
sayou/visualizer/pipeline.py,sha256=LduZlz5jGqTfzRRuv3W-IFfj1VxjjTkXAwXL6zs-yQE,4116
|
|
3
3
|
sayou/visualizer/core/exceptions.py,sha256=Mk5UtIfim7i9688c4qAKP7kB1GpLPM29t94HbQM9fhw,99
|
|
4
4
|
sayou/visualizer/core/schemas.py,sha256=qn44BINevFZF_ALBhh20DS4GyMo5HV3UzqY4UTh_p3A,381
|
|
5
5
|
sayou/visualizer/interfaces/base_renderer.py,sha256=orllTXlqM4-wDemOWbcZX8zF708KOdWFgoqZh8MeAzE,760
|
|
6
6
|
sayou/visualizer/renderer/analytic_kg_renderer.py,sha256=I5ccv7JUCGvPE79c2d1O-XuYwrK3fxk9qj4Xzyx_WLg,16404
|
|
7
7
|
sayou/visualizer/renderer/pyvis_renderer.py,sha256=2HKv_qAKKNHMKLwC7xoIn3EZ5oFLpgds7LPgK4EeXfA,2408
|
|
8
|
-
sayou/visualizer/renderer/showcase_kg_renderer.py,sha256=
|
|
8
|
+
sayou/visualizer/renderer/showcase_kg_renderer.py,sha256=_K9GjAPuKzA-BKbJYDK0Ku0U8tThusuqgwAwhdtaprU,11643
|
|
9
9
|
sayou/visualizer/tracer/graph_tracer.py,sha256=j0dqd0_67ZnQCNjn5siKHXXsXasWim9olYqUQA2jKxk,3638
|
|
10
10
|
sayou/visualizer/tracer/rich_tracer.py,sha256=ik7J1P7AMTN47lkjMLE7iTlRuksUpwSKluvWdpY6kdQ,2406
|
|
11
11
|
sayou/visualizer/tracer/websocket_tracer.py,sha256=OZLg4jTfuxp6IwDacmAACKZ_0FirZFhyLysqh9QyrJA,1626
|
|
12
|
-
sayou_visualizer-0.0.
|
|
13
|
-
sayou_visualizer-0.0.
|
|
14
|
-
sayou_visualizer-0.0.
|
|
12
|
+
sayou_visualizer-0.0.11.dist-info/METADATA,sha256=0wlBaqJxYAqGoTx4B7vRusfBxQIwSKJFnrt36ad74bw,16681
|
|
13
|
+
sayou_visualizer-0.0.11.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
14
|
+
sayou_visualizer-0.0.11.dist-info/RECORD,,
|
|
File without changes
|