sayou-visualizer 0.0.10__py3-none-any.whl → 0.0.12__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/core/styles.py +223 -0
- sayou/visualizer/pipeline.py +13 -3
- sayou/visualizer/renderer/analytic_kg_renderer.py +17 -185
- sayou/visualizer/renderer/showcase_kg_renderer.py +200 -215
- {sayou_visualizer-0.0.10.dist-info → sayou_visualizer-0.0.12.dist-info}/METADATA +1 -1
- {sayou_visualizer-0.0.10.dist-info → sayou_visualizer-0.0.12.dist-info}/RECORD +7 -6
- {sayou_visualizer-0.0.10.dist-info → sayou_visualizer-0.0.12.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
from sayou.core.ontology import SayouClass
|
|
2
|
+
|
|
3
|
+
HIDDEN_ATTR_PREFIXES = ["sayou:", "meta:", "schema:"]
|
|
4
|
+
|
|
5
|
+
# =========================================================
|
|
6
|
+
# 1. 3D Showcase Styles (Original Design Restored)
|
|
7
|
+
# =========================================================
|
|
8
|
+
SHOWCASE_STYLE_MAP = {
|
|
9
|
+
"default": {"group": "Chunk", "color": "#4a69bd", "val": 3},
|
|
10
|
+
# [Original Design Colors]
|
|
11
|
+
SayouClass.FILE: {"group": "Document", "color": "#00d2d3", "val": 20}, # Cyan
|
|
12
|
+
SayouClass.CLASS: {"group": "Header", "color": "#ff6b81", "val": 12}, # Pink
|
|
13
|
+
SayouClass.METHOD: {"group": "Code", "color": "#feca57", "val": 6}, # Gold
|
|
14
|
+
SayouClass.FUNCTION: {"group": "Code", "color": "#feca57", "val": 6}, # Gold
|
|
15
|
+
SayouClass.LIBRARY: {"group": "Library", "color": "#2ed573", "val": 10}, # Green
|
|
16
|
+
# [New] YouTube Domain (Compatible Theme)
|
|
17
|
+
SayouClass.VIDEO: {"group": "Video", "color": "#e84118", "val": 40}, # Deep Red
|
|
18
|
+
SayouClass.VIDEO_SEGMENT: {
|
|
19
|
+
"group": "Segment",
|
|
20
|
+
"color": "#dcdde1",
|
|
21
|
+
"val": 4,
|
|
22
|
+
}, # Gray
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# 동적 크기 계산 규칙
|
|
26
|
+
DYNAMIC_SIZING_RULES = {
|
|
27
|
+
SayouClass.VIDEO_SEGMENT: {
|
|
28
|
+
"attr_start": "sayou:startTime",
|
|
29
|
+
"attr_end": "sayou:endTime",
|
|
30
|
+
"base_val": 4,
|
|
31
|
+
"scale_factor": 0.5,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# =========================================================
|
|
36
|
+
# 2. 2D Analyst Styles (Original Design + Interactions Restored)
|
|
37
|
+
# =========================================================
|
|
38
|
+
ANALYST_TYPE_MAPPING = {
|
|
39
|
+
SayouClass.FILE: "file",
|
|
40
|
+
SayouClass.CLASS: "class",
|
|
41
|
+
SayouClass.METHOD: "method",
|
|
42
|
+
SayouClass.FUNCTION: "function",
|
|
43
|
+
SayouClass.LIBRARY: "library",
|
|
44
|
+
SayouClass.CODE_BLOCK: "code_block",
|
|
45
|
+
SayouClass.VIDEO: "video",
|
|
46
|
+
SayouClass.VIDEO_SEGMENT: "segment",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
ANALYST_STYLE_SHEET = [
|
|
50
|
+
# [Global]
|
|
51
|
+
{
|
|
52
|
+
"selector": "node",
|
|
53
|
+
"style": {
|
|
54
|
+
"label": "data(label)",
|
|
55
|
+
"color": "#ecf0f1",
|
|
56
|
+
"font-size": "10px",
|
|
57
|
+
"text-valign": "center",
|
|
58
|
+
"text-halign": "center",
|
|
59
|
+
"text-wrap": "wrap",
|
|
60
|
+
"text-max-width": "100px",
|
|
61
|
+
"background-color": "#95a5a6",
|
|
62
|
+
"border-width": 1,
|
|
63
|
+
"border-color": "#7f8c8d",
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
# [Original Node Shapes/Colors]
|
|
67
|
+
{
|
|
68
|
+
"selector": "node[type='file']",
|
|
69
|
+
"style": {
|
|
70
|
+
"shape": "rectangle",
|
|
71
|
+
"background-color": "#2c3e50",
|
|
72
|
+
"width": 60,
|
|
73
|
+
"height": 60,
|
|
74
|
+
"font-size": "12px",
|
|
75
|
+
"font-weight": "bold",
|
|
76
|
+
"border-width": 2,
|
|
77
|
+
"border-color": "#00d2d3",
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"selector": "node[type='class']",
|
|
82
|
+
"style": {
|
|
83
|
+
"shape": "diamond",
|
|
84
|
+
"background-color": "#8e44ad",
|
|
85
|
+
"width": 40,
|
|
86
|
+
"height": 40,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"selector": "node[type='method'], node[type='function']",
|
|
91
|
+
"style": {
|
|
92
|
+
"shape": "ellipse",
|
|
93
|
+
"background-color": "#e67e22",
|
|
94
|
+
"width": 25,
|
|
95
|
+
"height": 25,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"selector": "node[type='code_block']",
|
|
100
|
+
"style": {
|
|
101
|
+
"shape": "round-rectangle",
|
|
102
|
+
"background-color": "#7f8c8d",
|
|
103
|
+
"width": 15,
|
|
104
|
+
"height": 15,
|
|
105
|
+
"label": "",
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"selector": "node[type='library']",
|
|
110
|
+
"style": {
|
|
111
|
+
"shape": "hexagon",
|
|
112
|
+
"background-color": "#16a085",
|
|
113
|
+
"width": 50,
|
|
114
|
+
"height": 50,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
# [New YouTube Nodes]
|
|
118
|
+
{
|
|
119
|
+
"selector": "node[type='video']",
|
|
120
|
+
"style": {
|
|
121
|
+
"shape": "rectangle",
|
|
122
|
+
"background-color": "#c0392b",
|
|
123
|
+
"width": 80,
|
|
124
|
+
"height": 80,
|
|
125
|
+
"border-width": 4,
|
|
126
|
+
"border-color": "#e74c3c",
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"selector": "node[type='segment']",
|
|
131
|
+
"style": {
|
|
132
|
+
"shape": "round-rectangle",
|
|
133
|
+
"background-color": "#bdc3c7",
|
|
134
|
+
"width": 40,
|
|
135
|
+
"height": 20,
|
|
136
|
+
"color": "#2c3e50",
|
|
137
|
+
"font-size": "8px",
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
# [Original Edges]
|
|
141
|
+
{
|
|
142
|
+
"selector": "edge",
|
|
143
|
+
"style": {
|
|
144
|
+
"width": 1,
|
|
145
|
+
"curve-style": "bezier",
|
|
146
|
+
"opacity": 0.6,
|
|
147
|
+
"arrow-scale": 1,
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"selector": "edge[edgeType='sayou:contains']",
|
|
152
|
+
"style": {
|
|
153
|
+
"line-color": "#7f8c8d",
|
|
154
|
+
"target-arrow-shape": "circle",
|
|
155
|
+
"line-style": "dashed",
|
|
156
|
+
"width": 1.5,
|
|
157
|
+
"opacity": 0.7,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"selector": "edge[edgeType='sayou:imports']",
|
|
162
|
+
"style": {
|
|
163
|
+
"line-color": "#00d2d3",
|
|
164
|
+
"target-arrow-shape": "triangle",
|
|
165
|
+
"line-style": "dashed",
|
|
166
|
+
"width": 2,
|
|
167
|
+
"opacity": 0.9,
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"selector": "edge[edgeType='sayou:inherits']",
|
|
172
|
+
"style": {
|
|
173
|
+
"line-color": "#ff6b6b",
|
|
174
|
+
"target-arrow-shape": "triangle",
|
|
175
|
+
"width": 3,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"selector": "edge[edgeType='sayou:next']",
|
|
180
|
+
"style": {
|
|
181
|
+
"line-color": "#f39c12",
|
|
182
|
+
"target-arrow-shape": "triangle",
|
|
183
|
+
"width": 2,
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
# =========================================================
|
|
187
|
+
# [CRITICAL FIX] Restored Interaction Styles
|
|
188
|
+
# =========================================================
|
|
189
|
+
{
|
|
190
|
+
"selector": ".highlighted",
|
|
191
|
+
"style": {
|
|
192
|
+
"background-color": "#f1c40f",
|
|
193
|
+
"line-color": "#f1c40f",
|
|
194
|
+
"target-arrow-color": "#f1c40f",
|
|
195
|
+
"opacity": 1,
|
|
196
|
+
"z-index": 999,
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"selector": ".faded",
|
|
201
|
+
"style": {"opacity": 0.05, "label": ""},
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"selector": ".found",
|
|
205
|
+
"style": {
|
|
206
|
+
"border-width": 4,
|
|
207
|
+
"border-color": "#e056fd",
|
|
208
|
+
"background-color": "#e056fd",
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"selector": "node.no-label",
|
|
213
|
+
"style": {
|
|
214
|
+
"text-opacity": 0,
|
|
215
|
+
"text-background-opacity": 0,
|
|
216
|
+
"text-border-opacity": 0,
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"selector": "edge.hidden-edge",
|
|
221
|
+
"style": {"display": "none"},
|
|
222
|
+
},
|
|
223
|
+
]
|
sayou/visualizer/pipeline.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from sayou.core.base_component import BaseComponent
|
|
2
2
|
|
|
3
|
-
from .renderer.
|
|
3
|
+
from .renderer.analytic_kg_renderer import AnalyticKGRenderer
|
|
4
4
|
from .renderer.pyvis_renderer import PyVisRenderer
|
|
5
|
+
from .renderer.showcase_kg_renderer import ShowcaseKGRenderer
|
|
5
6
|
from .tracer.graph_tracer import GraphTracer
|
|
6
7
|
from .tracer.rich_tracer import RichConsoleTracer
|
|
7
8
|
from .tracer.websocket_tracer import WebSocketTracer
|
|
@@ -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"):
|
|
@@ -3,6 +3,8 @@ import os
|
|
|
3
3
|
|
|
4
4
|
from sayou.core.base_component import BaseComponent
|
|
5
5
|
|
|
6
|
+
from ..core.styles import ANALYST_STYLE_SHEET, ANALYST_TYPE_MAPPING
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
class AnalyticKGRenderer(BaseComponent):
|
|
8
10
|
"""
|
|
@@ -15,158 +17,7 @@ class AnalyticKGRenderer(BaseComponent):
|
|
|
15
17
|
|
|
16
18
|
component_name = "AnalyticKGRenderer"
|
|
17
19
|
|
|
18
|
-
STYLE_SHEET =
|
|
19
|
-
# [Global Nodes]
|
|
20
|
-
{
|
|
21
|
-
"selector": "node",
|
|
22
|
-
"style": {
|
|
23
|
-
"label": "data(label)",
|
|
24
|
-
"color": "#ecf0f1",
|
|
25
|
-
"font-size": "10px",
|
|
26
|
-
"text-valign": "center",
|
|
27
|
-
"text-halign": "center",
|
|
28
|
-
"text-wrap": "wrap",
|
|
29
|
-
"text-max-width": "100px",
|
|
30
|
-
"background-color": "#95a5a6",
|
|
31
|
-
"border-width": 1,
|
|
32
|
-
"border-color": "#7f8c8d",
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
# [File Node]
|
|
36
|
-
{
|
|
37
|
-
"selector": "node[type='file']",
|
|
38
|
-
"style": {
|
|
39
|
-
"shape": "rectangle",
|
|
40
|
-
"background-color": "#2c3e50",
|
|
41
|
-
"width": 60,
|
|
42
|
-
"height": 60,
|
|
43
|
-
"font-size": "12px",
|
|
44
|
-
"font-weight": "bold",
|
|
45
|
-
"border-width": 2,
|
|
46
|
-
"border-color": "#00d2d3",
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
# [Class Node]
|
|
50
|
-
{
|
|
51
|
-
"selector": "node[type='class']",
|
|
52
|
-
"style": {
|
|
53
|
-
"shape": "diamond",
|
|
54
|
-
"background-color": "#8e44ad",
|
|
55
|
-
"width": 40,
|
|
56
|
-
"height": 40,
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
# [Method/Function]
|
|
60
|
-
{
|
|
61
|
-
"selector": "node[type='method'], node[type='function']",
|
|
62
|
-
"style": {
|
|
63
|
-
"shape": "ellipse",
|
|
64
|
-
"background-color": "#e67e22",
|
|
65
|
-
"width": 25,
|
|
66
|
-
"height": 25,
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
# [Code Chunk]
|
|
70
|
-
{
|
|
71
|
-
"selector": "node[type='code_block']",
|
|
72
|
-
"style": {
|
|
73
|
-
"shape": "round-rectangle",
|
|
74
|
-
"background-color": "#7f8c8d",
|
|
75
|
-
"width": 15,
|
|
76
|
-
"height": 15,
|
|
77
|
-
"label": "",
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
# [Package/Library]
|
|
81
|
-
{
|
|
82
|
-
"selector": "node[type='library'], node[type='package']",
|
|
83
|
-
"style": {
|
|
84
|
-
"shape": "hexagon",
|
|
85
|
-
"background-color": "#16a085",
|
|
86
|
-
"width": 50,
|
|
87
|
-
"height": 50,
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
# [Edges]
|
|
91
|
-
{
|
|
92
|
-
"selector": "edge",
|
|
93
|
-
"style": {
|
|
94
|
-
"width": 1,
|
|
95
|
-
"curve-style": "bezier",
|
|
96
|
-
"opacity": 0.6,
|
|
97
|
-
"arrow-scale": 1,
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
# 1. Structure Line (contains) -> Gray Dashed Line (Skeleton)
|
|
101
|
-
{
|
|
102
|
-
"selector": "edge[edgeType='sayou:contains']",
|
|
103
|
-
"style": {
|
|
104
|
-
"line-color": "#7f8c8d",
|
|
105
|
-
"target-arrow-color": "#7f8c8d",
|
|
106
|
-
"target-arrow-shape": "circle",
|
|
107
|
-
"width": 1.5,
|
|
108
|
-
"line-style": "dashed",
|
|
109
|
-
"opacity": 0.7,
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
# 2. Logic Line (imports) -> Cyan Dashed Line (Flow)
|
|
113
|
-
{
|
|
114
|
-
"selector": "edge[edgeType='sayou:imports']",
|
|
115
|
-
"style": {
|
|
116
|
-
"line-color": "#00d2d3",
|
|
117
|
-
"target-arrow-color": "#00d2d3",
|
|
118
|
-
"target-arrow-shape": "triangle",
|
|
119
|
-
"line-style": "dashed",
|
|
120
|
-
"width": 2,
|
|
121
|
-
"opacity": 0.9,
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
# 3. Inheritance Line (inherits) -> Red Solid Line
|
|
125
|
-
{
|
|
126
|
-
"selector": "edge[edgeType='sayou:inherits']",
|
|
127
|
-
"style": {
|
|
128
|
-
"line-color": "#ff6b6b",
|
|
129
|
-
"target-arrow-color": "#ff6b6b",
|
|
130
|
-
"target-arrow-shape": "triangle",
|
|
131
|
-
"width": 3,
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
# [Interaction]
|
|
135
|
-
{
|
|
136
|
-
"selector": ".highlighted",
|
|
137
|
-
"style": {
|
|
138
|
-
"background-color": "#f1c40f",
|
|
139
|
-
"line-color": "#f1c40f",
|
|
140
|
-
"target-arrow-color": "#f1c40f",
|
|
141
|
-
"opacity": 1,
|
|
142
|
-
"z-index": 999,
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
"selector": ".faded",
|
|
147
|
-
"style": {"opacity": 0.05, "label": ""},
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
"selector": ".found",
|
|
151
|
-
"style": {
|
|
152
|
-
"border-width": 4,
|
|
153
|
-
"border-color": "#e056fd",
|
|
154
|
-
"background-color": "#e056fd",
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
"selector": "node.no-label",
|
|
159
|
-
"style": {
|
|
160
|
-
"text-opacity": 0,
|
|
161
|
-
"text-background-opacity": 0,
|
|
162
|
-
"text-border-opacity": 0,
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
"selector": "edge.hidden-edge",
|
|
167
|
-
"style": {"display": "none"},
|
|
168
|
-
},
|
|
169
|
-
]
|
|
20
|
+
STYLE_SHEET = ANALYST_STYLE_SHEET
|
|
170
21
|
|
|
171
22
|
def render(self, json_path: str, output_path: str = "sayou_analyst_view.html"):
|
|
172
23
|
if not os.path.exists(json_path):
|
|
@@ -177,57 +28,38 @@ class AnalyticKGRenderer(BaseComponent):
|
|
|
177
28
|
|
|
178
29
|
elements = []
|
|
179
30
|
|
|
180
|
-
# 1. Nodes
|
|
31
|
+
# 1. Nodes Processing
|
|
181
32
|
for node in raw_data.get("nodes", []):
|
|
182
33
|
node_id = node.get("node_id")
|
|
183
34
|
attrs = node.get("attributes", {})
|
|
184
|
-
n_cls = node.get("node_class", "unknown")
|
|
185
|
-
|
|
186
|
-
#
|
|
187
|
-
cy_type = "unknown"
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
elif "method" in n_cls:
|
|
193
|
-
cy_type = "method"
|
|
194
|
-
elif "function" in n_cls:
|
|
195
|
-
cy_type = "function"
|
|
196
|
-
elif "library" in n_cls:
|
|
197
|
-
cy_type = "library"
|
|
198
|
-
elif "code" in n_cls:
|
|
199
|
-
cy_type = "code_block"
|
|
200
|
-
|
|
201
|
-
# Labeling
|
|
202
|
-
label = attrs.get("label") or node.get("friendly_name") or node_id
|
|
203
|
-
if cy_type == "file":
|
|
204
|
-
label = os.path.basename(attrs.get("sayou:filePath", label))
|
|
205
|
-
elif cy_type == "class":
|
|
206
|
-
label = attrs.get("meta:class_name", label)
|
|
207
|
-
elif cy_type in ["method", "function"]:
|
|
208
|
-
label = attrs.get("function_name", label)
|
|
209
|
-
|
|
210
|
-
# Code Text
|
|
211
|
-
code_text = attrs.get("schema:text", "")
|
|
35
|
+
n_cls = node.get("node_class", "unknown")
|
|
36
|
+
|
|
37
|
+
# [Pure Config Lookup]
|
|
38
|
+
cy_type = ANALYST_TYPE_MAPPING.get(n_cls, "unknown")
|
|
39
|
+
|
|
40
|
+
# [No Logic] Builder가 준 라벨을 그대로 사용
|
|
41
|
+
label = node.get("friendly_name") or attrs.get("label") or node_id
|
|
42
|
+
|
|
212
43
|
cy_data = {
|
|
213
44
|
"id": node_id,
|
|
214
45
|
"label": label,
|
|
215
46
|
"type": cy_type,
|
|
216
|
-
"code":
|
|
47
|
+
"code": attrs.get("schema:text", ""),
|
|
217
48
|
"meta": attrs,
|
|
218
49
|
}
|
|
219
50
|
elements.append({"group": "nodes", "data": cy_data})
|
|
220
51
|
|
|
221
|
-
# 2. Edges
|
|
52
|
+
# 2. Edges Processing
|
|
222
53
|
for edge in raw_data.get("edges", []):
|
|
54
|
+
e_type = edge.get("type", "relates")
|
|
223
55
|
elements.append(
|
|
224
56
|
{
|
|
225
57
|
"group": "edges",
|
|
226
58
|
"data": {
|
|
227
59
|
"source": edge.get("source"),
|
|
228
60
|
"target": edge.get("target"),
|
|
229
|
-
"edgeType":
|
|
230
|
-
"label":
|
|
61
|
+
"edgeType": e_type,
|
|
62
|
+
"label": e_type.split(":")[-1],
|
|
231
63
|
},
|
|
232
64
|
}
|
|
233
65
|
)
|
|
@@ -1,298 +1,284 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
|
-
from collections import defaultdict
|
|
4
3
|
|
|
5
4
|
from sayou.core.base_component import BaseComponent
|
|
6
5
|
|
|
6
|
+
from ..core.styles import HIDDEN_ATTR_PREFIXES, SHOWCASE_STYLE_MAP
|
|
7
|
+
|
|
7
8
|
|
|
8
9
|
class ShowcaseKGRenderer(BaseComponent):
|
|
9
10
|
"""
|
|
10
|
-
Renders
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
rather than granular node analysis.
|
|
11
|
+
Renders the Final Stable 3D Knowledge Graph.
|
|
12
|
+
- [Architecture] Removed unstable Post-Processing shaders to fix physics crash.
|
|
13
|
+
- [Visuals] Uses native 'Emissive' materials for Neon aesthetics.
|
|
14
|
+
- [Topology] Distinguishes 'Imports' (Gold/Bright) vs 'Hierarchy' (Dark/Blue).
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
component_name = "ShowcaseKGRenderer"
|
|
18
18
|
|
|
19
|
-
def render(self, json_path: str, output_path: str = "
|
|
19
|
+
def render(self, json_path: str, output_path: str = "sayou_showcase_3d.html"):
|
|
20
20
|
if not os.path.exists(json_path):
|
|
21
|
-
self._log(f"
|
|
21
|
+
self._log(f"❌ File not found: {json_path}", level="error")
|
|
22
22
|
return
|
|
23
23
|
|
|
24
24
|
with open(json_path, "r", encoding="utf-8") as f:
|
|
25
25
|
raw_data = json.load(f)
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# ---------------------------------------------------------
|
|
30
|
-
nodes_by_source = defaultdict(list)
|
|
31
|
-
final_nodes = []
|
|
32
|
-
final_links = []
|
|
33
|
-
|
|
27
|
+
nodes = []
|
|
28
|
+
links = []
|
|
34
29
|
existing_ids = set()
|
|
35
30
|
|
|
31
|
+
# 1. Nodes Processing
|
|
36
32
|
for node in raw_data.get("nodes", []):
|
|
37
33
|
node_id = node.get("node_id")
|
|
34
|
+
if not node_id:
|
|
35
|
+
continue
|
|
38
36
|
existing_ids.add(node_id)
|
|
37
|
+
|
|
39
38
|
attrs = node.get("attributes", {})
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
source = meta.get("source") or attrs.get("sayou:source") or "Unknown Source"
|
|
43
|
-
nodes_by_source[source].append(node_id)
|
|
44
|
-
|
|
45
|
-
sem_type = attrs.get("sayou:semanticType", "text")
|
|
46
|
-
|
|
47
|
-
clean_attrs = {}
|
|
48
|
-
for k, v in attrs.items():
|
|
49
|
-
if isinstance(v, str) and len(v) > 300:
|
|
50
|
-
clean_attrs[k] = v[:200] + "..."
|
|
51
|
-
else:
|
|
52
|
-
clean_attrs[k] = v
|
|
53
|
-
|
|
54
|
-
display_label = attrs.get("schema:text", node_id)
|
|
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(
|
|
80
|
-
{
|
|
81
|
-
"id": node_id,
|
|
82
|
-
"label": display_label,
|
|
83
|
-
"group": group,
|
|
84
|
-
"sem_type": sem_type,
|
|
85
|
-
"color": color,
|
|
86
|
-
"val": val,
|
|
87
|
-
"attributes": clean_attrs,
|
|
88
|
-
"source": source,
|
|
89
|
-
}
|
|
90
|
-
)
|
|
39
|
+
n_cls = node.get("node_class", "unknown")
|
|
91
40
|
|
|
92
|
-
|
|
93
|
-
final_links.append(
|
|
94
|
-
{
|
|
95
|
-
"source": edge.get("source"),
|
|
96
|
-
"target": edge.get("target"),
|
|
97
|
-
"relation": edge.get("relation", "relates_to"),
|
|
98
|
-
}
|
|
99
|
-
)
|
|
41
|
+
style = SHOWCASE_STYLE_MAP.get(n_cls, SHOWCASE_STYLE_MAP["default"])
|
|
100
42
|
|
|
101
|
-
|
|
102
|
-
if source_name in existing_ids:
|
|
103
|
-
continue
|
|
43
|
+
val = attrs.get("val", style["val"])
|
|
104
44
|
|
|
105
|
-
|
|
45
|
+
label = node.get("friendly_name") or attrs.get("label") or node_id
|
|
106
46
|
|
|
107
|
-
|
|
47
|
+
clean_attrs = self._clean_attributes(attrs)
|
|
48
|
+
|
|
49
|
+
nodes.append(
|
|
108
50
|
{
|
|
109
|
-
"id":
|
|
110
|
-
"label":
|
|
111
|
-
"group": "
|
|
112
|
-
"color": "
|
|
113
|
-
"val":
|
|
114
|
-
"attributes":
|
|
115
|
-
"type": "Virtual Parent",
|
|
116
|
-
"child_count": len(child_ids),
|
|
117
|
-
},
|
|
118
|
-
"is_virtual": True,
|
|
51
|
+
"id": node_id,
|
|
52
|
+
"label": label,
|
|
53
|
+
"group": style["group"],
|
|
54
|
+
"color": style["color"],
|
|
55
|
+
"val": val,
|
|
56
|
+
"attributes": clean_attrs,
|
|
119
57
|
}
|
|
120
58
|
)
|
|
121
59
|
|
|
122
|
-
|
|
123
|
-
|
|
60
|
+
# 2. Edges Processing
|
|
61
|
+
for edge in raw_data.get("edges", []):
|
|
62
|
+
src = edge.get("source")
|
|
63
|
+
tgt = edge.get("target")
|
|
64
|
+
if src in existing_ids and tgt in existing_ids:
|
|
65
|
+
e_type = edge.get("type", "relates")
|
|
66
|
+
is_import = any(
|
|
67
|
+
k in e_type for k in ["import", "calls", "next", "contains"]
|
|
68
|
+
)
|
|
69
|
+
links.append(
|
|
124
70
|
{
|
|
125
|
-
"source":
|
|
126
|
-
"target":
|
|
127
|
-
"
|
|
71
|
+
"source": src,
|
|
72
|
+
"target": tgt,
|
|
73
|
+
"type": e_type,
|
|
74
|
+
"is_import": is_import,
|
|
128
75
|
}
|
|
129
76
|
)
|
|
130
77
|
|
|
131
|
-
graph_data = {"nodes":
|
|
132
|
-
self.
|
|
78
|
+
graph_data = {"nodes": nodes, "links": links}
|
|
79
|
+
self._generate_html(graph_data, output_path)
|
|
80
|
+
self._log(f"✅ Final Visual Showcase generated at: {output_path}")
|
|
81
|
+
|
|
82
|
+
def _clean_attributes(self, attrs):
|
|
83
|
+
"""설정 파일 기반 속성 정제"""
|
|
84
|
+
clean = {}
|
|
85
|
+
for k, v in attrs.items():
|
|
86
|
+
if any(k.startswith(prefix) for prefix in HIDDEN_ATTR_PREFIXES):
|
|
87
|
+
continue
|
|
88
|
+
s = str(v)
|
|
89
|
+
if len(s) > 200:
|
|
90
|
+
clean[k] = s[:200] + "..."
|
|
91
|
+
else:
|
|
92
|
+
clean[k] = v
|
|
93
|
+
return clean
|
|
94
|
+
|
|
95
|
+
def _generate_html(self, graph_data, output_path):
|
|
96
|
+
json_str = json.dumps(graph_data)
|
|
133
97
|
|
|
134
|
-
# ---------------------------------------------------------
|
|
135
|
-
# [JS Logic] Renderer
|
|
136
|
-
# ---------------------------------------------------------
|
|
137
98
|
html_content = f"""
|
|
138
99
|
<!DOCTYPE html>
|
|
139
100
|
<html lang="en">
|
|
140
101
|
<head>
|
|
141
102
|
<meta charset="UTF-8">
|
|
142
103
|
<title>Sayou Dataverse</title>
|
|
104
|
+
<script src="https://unpkg.com/three@0.160.0/build/three.min.js"></script>
|
|
105
|
+
<script src="https://unpkg.com/3d-force-graph@1.73.1/dist/3d-force-graph.min.js"></script>
|
|
143
106
|
<style>
|
|
144
|
-
body {{ margin: 0; background-color: #
|
|
107
|
+
body {{ margin: 0; background-color: #020202; overflow: hidden; font-family: sans-serif; }}
|
|
145
108
|
#graph {{ width: 100%; height: 100vh; }}
|
|
146
|
-
|
|
147
109
|
#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;
|
|
110
|
+
position: absolute; top: 30px; right: 30px; width: 300px;
|
|
111
|
+
background: rgba(15, 20, 30, 0.9);
|
|
112
|
+
border: 1px solid #444; border-left: 4px solid #00d2d3;
|
|
113
|
+
box-shadow: 0 0 20px rgba(0,0,0,0.8);
|
|
114
|
+
color: #fff; padding: 20px; font-size:13px;
|
|
115
|
+
backdrop-filter: blur(5px); display: none; border-radius: 4px; pointer-events: none;
|
|
163
116
|
}}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}}
|
|
169
|
-
.row {{ font-size: 11px; margin-bottom: 4px; color: #a4b0be; }}
|
|
170
|
-
.key {{ color: #00d2d3; margin-right: 5px; }}
|
|
117
|
+
.tag {{ display: inline-block; padding: 3px 6px; border-radius: 4px; color: #000; font-weight:bold; font-size:10px; margin-bottom:10px; }}
|
|
118
|
+
h2 {{ margin:0 0 10px 0; font-size:16px; color:#eee; }}
|
|
119
|
+
.row {{ margin-bottom:4px; color:#aaa; border-bottom:1px solid #333; padding-bottom:2px; }}
|
|
120
|
+
.key {{ color:#00d2d3; margin-right:5px; }}
|
|
171
121
|
</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
122
|
</head>
|
|
175
123
|
<body>
|
|
176
124
|
<div id="graph"></div>
|
|
177
125
|
<div id="info"></div>
|
|
178
126
|
|
|
179
127
|
<script>
|
|
180
|
-
const gData = {
|
|
128
|
+
const gData = {json_str};
|
|
181
129
|
const infoDiv = document.getElementById('info');
|
|
130
|
+
|
|
131
|
+
// [State]
|
|
132
|
+
const highlightNodes = new Set();
|
|
133
|
+
const highlightLinks = new Set();
|
|
134
|
+
let hoverNode = null;
|
|
135
|
+
let isFlying = false;
|
|
136
|
+
|
|
137
|
+
// [Init] Big Bang
|
|
138
|
+
gData.nodes.forEach(node => {{
|
|
139
|
+
node.x = Math.random() * 2000 - 1000;
|
|
140
|
+
node.y = Math.random() * 2000 - 1000;
|
|
141
|
+
node.z = Math.random() * 2000 - 1000;
|
|
142
|
+
}});
|
|
182
143
|
|
|
144
|
+
// [Graph Init]
|
|
183
145
|
const Graph = ForceGraph3D()(document.getElementById('graph'))
|
|
184
146
|
.graphData(gData)
|
|
185
|
-
.backgroundColor('#
|
|
147
|
+
.backgroundColor('#050505')
|
|
186
148
|
.showNavInfo(false)
|
|
187
|
-
|
|
188
|
-
|
|
149
|
+
.nodeLabel(null)
|
|
150
|
+
.cooldownTicks(50);
|
|
151
|
+
|
|
152
|
+
// [Physics]
|
|
153
|
+
Graph.d3Force('charge').strength(-100);
|
|
154
|
+
Graph.d3Force('link').distance(link => link.is_import ? 10 : 100);
|
|
155
|
+
|
|
156
|
+
// [Visuals]
|
|
157
|
+
Graph
|
|
189
158
|
.nodeThreeObject(node => {{
|
|
190
|
-
let
|
|
191
|
-
|
|
192
|
-
|
|
159
|
+
let isDimmed = false;
|
|
160
|
+
let isTarget = false;
|
|
161
|
+
if (hoverNode) {{
|
|
162
|
+
if (hoverNode === node || highlightNodes.has(node)) isTarget = true;
|
|
163
|
+
else isDimmed = true;
|
|
164
|
+
}}
|
|
165
|
+
const baseColor = node.color;
|
|
166
|
+
const opacity = isDimmed ? 0.1 : 0.9;
|
|
167
|
+
const emissiveInt = isTarget ? 1.5 : (isDimmed ? 0 : 0.6);
|
|
168
|
+
|
|
169
|
+
const material = new THREE.MeshPhongMaterial({{
|
|
170
|
+
color: baseColor,
|
|
171
|
+
emissive: baseColor,
|
|
172
|
+
emissiveIntensity: emissiveInt,
|
|
173
|
+
transparent: true,
|
|
174
|
+
opacity: opacity,
|
|
175
|
+
shininess: 90
|
|
176
|
+
}});
|
|
177
|
+
|
|
193
178
|
if (node.group === "Document") {{
|
|
194
|
-
const
|
|
195
|
-
geometry = new THREE.BoxGeometry(
|
|
196
|
-
|
|
179
|
+
const s = node.val;
|
|
180
|
+
const geometry = new THREE.BoxGeometry(s, s, s);
|
|
197
181
|
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
|
-
|
|
182
|
+
const lineMat = new THREE.LineBasicMaterial({{ color: baseColor, transparent:true, opacity: isDimmed ? 0.05 : 0.4 }});
|
|
183
|
+
const wireframe = new THREE.LineSegments(edges, lineMat);
|
|
184
|
+
wireframe.add(new THREE.Mesh(new THREE.BoxGeometry(s*0.4, s*0.4, s*0.4), material));
|
|
209
185
|
return wireframe;
|
|
210
186
|
}}
|
|
187
|
+
else if (node.group === "Header") return new THREE.Mesh(new THREE.OctahedronGeometry(node.val * 0.7), material);
|
|
188
|
+
else return new THREE.Mesh(new THREE.IcosahedronGeometry(node.val * 0.6, 2), material);
|
|
189
|
+
}})
|
|
190
|
+
.linkWidth(link => {{
|
|
191
|
+
if (highlightLinks.has(link)) return 3;
|
|
192
|
+
if (hoverNode && !highlightLinks.has(link)) return 0;
|
|
193
|
+
return link.is_import ? 1.5 : 0.5;
|
|
194
|
+
}})
|
|
195
|
+
.linkColor(link => {{
|
|
196
|
+
if (highlightLinks.has(link)) return link.is_import ? '#feca57' : '#00d2d3';
|
|
197
|
+
return link.is_import ? 'rgba(254, 202, 87, 0.4)' : 'rgba(44, 62, 80, 0.3)';
|
|
198
|
+
}})
|
|
199
|
+
.linkDirectionalParticles(link => highlightLinks.has(link) ? 3 : 0)
|
|
200
|
+
.linkDirectionalParticleWidth(3)
|
|
201
|
+
.onNodeHover(node => {{
|
|
202
|
+
if ((!node && !hoverNode) || (node && hoverNode === node)) return;
|
|
211
203
|
|
|
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
|
|
204
|
+
highlightNodes.clear(); highlightLinks.clear();
|
|
205
|
+
if (node) {{
|
|
206
|
+
highlightNodes.add(node);
|
|
207
|
+
gData.links.forEach(link => {{
|
|
208
|
+
if (link.source.id === node.id) {{ highlightNodes.add(link.target); highlightLinks.add(link); }}
|
|
209
|
+
else if (link.target.id === node.id) {{ highlightNodes.add(link.source); highlightLinks.add(link); }}
|
|
232
210
|
}});
|
|
233
211
|
}}
|
|
212
|
+
hoverNode = node || null;
|
|
213
|
+
requestAnimationFrame(() => {{
|
|
214
|
+
Graph
|
|
215
|
+
.nodeThreeObject(Graph.nodeThreeObject())
|
|
216
|
+
.linkWidth(Graph.linkWidth())
|
|
217
|
+
.linkColor(Graph.linkColor())
|
|
218
|
+
.linkDirectionalParticles(Graph.linkDirectionalParticles());
|
|
219
|
+
}});
|
|
234
220
|
|
|
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
221
|
document.body.style.cursor = node ? 'crosshair' : null;
|
|
249
222
|
if (node) {{
|
|
250
223
|
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
|
-
|
|
224
|
+
let html = `<span class='tag' style='background:${{node.color}}'>${{node.group}}</span><h2>${{node.label}}</h2>`;
|
|
260
225
|
if (node.attributes) {{
|
|
261
226
|
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>`;
|
|
227
|
+
if(k!=='type') html += `<div class='row'><span class='key'>${{k.split(':').pop()}}:</span> ${{v}}</div>`;
|
|
265
228
|
}}
|
|
266
229
|
}}
|
|
267
230
|
infoDiv.innerHTML = html;
|
|
268
|
-
}} else
|
|
269
|
-
infoDiv.style.display = 'none';
|
|
270
|
-
}}
|
|
231
|
+
}} else infoDiv.style.display = 'none';
|
|
271
232
|
}})
|
|
272
233
|
.onNodeClick(node => {{
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
234
|
+
if (!node) return;
|
|
235
|
+
isFlying = true;
|
|
236
|
+
const dist = 150;
|
|
237
|
+
const distRatio = 1 + dist/Math.hypot(node.x, node.y, node.z);
|
|
238
|
+
Graph.cameraPosition(
|
|
239
|
+
{{ x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }},
|
|
240
|
+
node,
|
|
241
|
+
2000
|
|
242
|
+
);
|
|
243
|
+
setTimeout(() => {{ isFlying = false; }}, 2000);
|
|
279
244
|
}});
|
|
280
245
|
|
|
281
|
-
// [
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
246
|
+
// [Env]
|
|
247
|
+
const scene = Graph.scene();
|
|
248
|
+
const starsGeo = new THREE.BufferGeometry();
|
|
249
|
+
const pos = new Float32Array(2000 * 3);
|
|
250
|
+
for(let i=0; i<2000*3; i++) pos[i] = (Math.random()-0.5) * 4000;
|
|
251
|
+
starsGeo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
|
|
252
|
+
scene.add(new THREE.Points(starsGeo, new THREE.PointsMaterial({{size:2, color:0xffffff, opacity:0.5, transparent:true}})));
|
|
253
|
+
scene.add(new THREE.AmbientLight(0x222222));
|
|
254
|
+
const light = new THREE.DirectionalLight(0xffffff, 1);
|
|
255
|
+
light.position.set(100, 100, 100);
|
|
256
|
+
scene.add(light);
|
|
292
257
|
|
|
293
|
-
// [
|
|
294
|
-
|
|
295
|
-
|
|
258
|
+
// [Manual Orbit Engine]
|
|
259
|
+
setInterval(() => {{
|
|
260
|
+
if (hoverNode || isFlying) return;
|
|
261
|
+
const cam = Graph.camera();
|
|
262
|
+
const controls = Graph.controls();
|
|
263
|
+
if (!controls) return;
|
|
264
|
+
|
|
265
|
+
const target = controls.target;
|
|
266
|
+
const relX = cam.position.x - target.x;
|
|
267
|
+
const relZ = cam.position.z - target.z;
|
|
268
|
+
const r = Math.sqrt(relX*relX + relZ*relZ);
|
|
269
|
+
let theta = Math.atan2(relZ, relX);
|
|
270
|
+
|
|
271
|
+
theta += 0.0015;
|
|
272
|
+
|
|
273
|
+
const newX = target.x + r * Math.cos(theta);
|
|
274
|
+
const newZ = target.z + r * Math.sin(theta);
|
|
275
|
+
|
|
276
|
+
Graph.cameraPosition(
|
|
277
|
+
{{ x: newX, y: cam.position.y, z: newZ }},
|
|
278
|
+
target,
|
|
279
|
+
0
|
|
280
|
+
);
|
|
281
|
+
}}, 15);
|
|
296
282
|
|
|
297
283
|
</script>
|
|
298
284
|
</body>
|
|
@@ -300,4 +286,3 @@ class ShowcaseKGRenderer(BaseComponent):
|
|
|
300
286
|
"""
|
|
301
287
|
with open(output_path, "w", encoding="utf-8") as f:
|
|
302
288
|
f.write(html_content)
|
|
303
|
-
self._log(f"✅ Semantic 3D KG Showcase saved to: {output_path}")
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
sayou/visualizer/__init__.py,sha256=DRv-5qzP6nal7qXVf7Zl67CQx3Kf3mTZhlLKl-vs_G0,82
|
|
2
|
-
sayou/visualizer/pipeline.py,sha256=
|
|
2
|
+
sayou/visualizer/pipeline.py,sha256=NWeIwQqp6ctP-xnhYEPdRwcLQ0RkYSmGRX-my_898GE,4116
|
|
3
3
|
sayou/visualizer/core/exceptions.py,sha256=Mk5UtIfim7i9688c4qAKP7kB1GpLPM29t94HbQM9fhw,99
|
|
4
4
|
sayou/visualizer/core/schemas.py,sha256=qn44BINevFZF_ALBhh20DS4GyMo5HV3UzqY4UTh_p3A,381
|
|
5
|
+
sayou/visualizer/core/styles.py,sha256=ZyMqzu9Xk-TY1xpkHeGZb0qkElG7MyHyYEpqFWXig2U,6285
|
|
5
6
|
sayou/visualizer/interfaces/base_renderer.py,sha256=orllTXlqM4-wDemOWbcZX8zF708KOdWFgoqZh8MeAzE,760
|
|
6
|
-
sayou/visualizer/renderer/analytic_kg_renderer.py,sha256=
|
|
7
|
+
sayou/visualizer/renderer/analytic_kg_renderer.py,sha256=5lNhllAe4WLf3-vc_Gg1DEpRbKWGCFBEvAwZGpncRWU,11246
|
|
7
8
|
sayou/visualizer/renderer/pyvis_renderer.py,sha256=2HKv_qAKKNHMKLwC7xoIn3EZ5oFLpgds7LPgK4EeXfA,2408
|
|
8
|
-
sayou/visualizer/renderer/showcase_kg_renderer.py,sha256=
|
|
9
|
+
sayou/visualizer/renderer/showcase_kg_renderer.py,sha256=P1OoKOsmAJFdP1weWWOD6EIINY_prfB135j5PFQ9a4E,11489
|
|
9
10
|
sayou/visualizer/tracer/graph_tracer.py,sha256=j0dqd0_67ZnQCNjn5siKHXXsXasWim9olYqUQA2jKxk,3638
|
|
10
11
|
sayou/visualizer/tracer/rich_tracer.py,sha256=ik7J1P7AMTN47lkjMLE7iTlRuksUpwSKluvWdpY6kdQ,2406
|
|
11
12
|
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.
|
|
13
|
+
sayou_visualizer-0.0.12.dist-info/METADATA,sha256=l6O1MyvZrMebDZ5OGY9wbEJSVj2B7gUCiz_0wOVzTXA,16681
|
|
14
|
+
sayou_visualizer-0.0.12.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
15
|
+
sayou_visualizer-0.0.12.dist-info/RECORD,,
|
|
File without changes
|