linting fix
This commit is contained in:
@@ -8,6 +8,7 @@ Version: 2.2
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Tuple, Dict, List, Any
|
from typing import Optional, Tuple, Dict, List, Any
|
||||||
import pipmaster as pm
|
import pipmaster as pm
|
||||||
|
|
||||||
# Install all required dependencies
|
# Install all required dependencies
|
||||||
REQUIRED_PACKAGES = [
|
REQUIRED_PACKAGES = [
|
||||||
"PyQt5",
|
"PyQt5",
|
||||||
@@ -18,10 +19,9 @@ REQUIRED_PACKAGES = [
|
|||||||
"networkx",
|
"networkx",
|
||||||
"matplotlib",
|
"matplotlib",
|
||||||
"python-louvain",
|
"python-louvain",
|
||||||
"ascii_colors"
|
"ascii_colors",
|
||||||
]
|
]
|
||||||
|
|
||||||
from ascii_colors import ASCIIColors, trace_exception
|
|
||||||
|
|
||||||
def setup_dependencies():
|
def setup_dependencies():
|
||||||
"""
|
"""
|
||||||
@@ -32,6 +32,7 @@ def setup_dependencies():
|
|||||||
print(f"Installing {package}...")
|
print(f"Installing {package}...")
|
||||||
pm.install(package)
|
pm.install(package)
|
||||||
|
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
setup_dependencies()
|
setup_dependencies()
|
||||||
|
|
||||||
@@ -40,18 +41,32 @@ import numpy as np
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import community
|
import community
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QApplication, QMainWindow, QWidget, QVBoxLayout,
|
QApplication,
|
||||||
QHBoxLayout, QPushButton, QFileDialog, QLabel,
|
QMainWindow,
|
||||||
QMessageBox, QSpinBox, QComboBox, QCheckBox,
|
QWidget,
|
||||||
QTableWidget, QTableWidgetItem, QSplitter, QDockWidget,
|
QVBoxLayout,
|
||||||
QTextEdit
|
QHBoxLayout,
|
||||||
|
QPushButton,
|
||||||
|
QFileDialog,
|
||||||
|
QLabel,
|
||||||
|
QMessageBox,
|
||||||
|
QSpinBox,
|
||||||
|
QComboBox,
|
||||||
|
QCheckBox,
|
||||||
|
QTableWidget,
|
||||||
|
QTableWidgetItem,
|
||||||
|
QSplitter,
|
||||||
|
QDockWidget,
|
||||||
|
QTextEdit,
|
||||||
)
|
)
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
import pyqtgraph.opengl as gl
|
import pyqtgraph.opengl as gl
|
||||||
|
from ascii_colors import trace_exception
|
||||||
|
|
||||||
|
|
||||||
class Point:
|
class Point:
|
||||||
"""Simple point class to handle coordinates"""
|
"""Simple point class to handle coordinates"""
|
||||||
|
|
||||||
def __init__(self, x: float, y: float):
|
def __init__(self, x: float, y: float):
|
||||||
self.x = x
|
self.x = x
|
||||||
self.y = y
|
self.y = y
|
||||||
@@ -59,6 +74,7 @@ class Point:
|
|||||||
|
|
||||||
class NodeState:
|
class NodeState:
|
||||||
"""Data class for node visual state"""
|
"""Data class for node visual state"""
|
||||||
|
|
||||||
NORMAL_SCALE = 1.0
|
NORMAL_SCALE = 1.0
|
||||||
HOVER_SCALE = 1.2
|
HOVER_SCALE = 1.2
|
||||||
SELECTED_SCALE = 1.3
|
SELECTED_SCALE = 1.3
|
||||||
@@ -76,8 +92,15 @@ class NodeState:
|
|||||||
|
|
||||||
class Node3D:
|
class Node3D:
|
||||||
"""Class representing a 3D node in the graph"""
|
"""Class representing a 3D node in the graph"""
|
||||||
def __init__(self, position: np.ndarray, color: Tuple[float, float, float, float],
|
|
||||||
label: str, node_type: str, size: float):
|
def __init__(
|
||||||
|
self,
|
||||||
|
position: np.ndarray,
|
||||||
|
color: Tuple[float, float, float, float],
|
||||||
|
label: str,
|
||||||
|
node_type: str,
|
||||||
|
size: float,
|
||||||
|
):
|
||||||
self.position = position
|
self.position = position
|
||||||
self.base_color = color
|
self.base_color = color
|
||||||
self.color = color
|
self.color = color
|
||||||
@@ -119,12 +142,13 @@ class Node3D:
|
|||||||
"""Update node visual appearance"""
|
"""Update node visual appearance"""
|
||||||
if self.mesh_item:
|
if self.mesh_item:
|
||||||
self.mesh_item.setData(
|
self.mesh_item.setData(
|
||||||
color=np.array([self.color]),
|
color=np.array([self.color]), size=np.array([self.size * scale * 5])
|
||||||
size=np.array([self.size * scale * 5])
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class NodeDetailsWidget(QWidget):
|
class NodeDetailsWidget(QWidget):
|
||||||
"""Widget to display node details"""
|
"""Widget to display node details"""
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
@@ -161,13 +185,14 @@ class NodeDetailsWidget(QWidget):
|
|||||||
for idx, (neighbor, edge_data) in enumerate(connections.items()):
|
for idx, (neighbor, edge_data) in enumerate(connections.items()):
|
||||||
self.connections.setItem(idx, 0, QTableWidgetItem(str(neighbor)))
|
self.connections.setItem(idx, 0, QTableWidgetItem(str(neighbor)))
|
||||||
self.connections.setItem(
|
self.connections.setItem(
|
||||||
idx, 1,
|
idx, 1, QTableWidgetItem(edge_data.get("relationship", "unknown"))
|
||||||
QTableWidgetItem(edge_data.get('relationship', 'unknown'))
|
|
||||||
)
|
)
|
||||||
self.connections.setItem(idx, 2, QTableWidgetItem("outgoing"))
|
self.connections.setItem(idx, 2, QTableWidgetItem("outgoing"))
|
||||||
|
|
||||||
|
|
||||||
class GraphMLViewer3D(QMainWindow):
|
class GraphMLViewer3D(QMainWindow):
|
||||||
"""Main window class for 3D GraphML visualization"""
|
"""Main window class for 3D GraphML visualization"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
@@ -186,7 +211,6 @@ class GraphMLViewer3D(QMainWindow):
|
|||||||
self.elevation = 30 # Initial camera elevation
|
self.elevation = 30 # Initial camera elevation
|
||||||
self.azimuth = 45 # Initial camera azimuth
|
self.azimuth = 45 # Initial camera azimuth
|
||||||
|
|
||||||
|
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
|
|
||||||
def init_ui(self):
|
def init_ui(self):
|
||||||
@@ -233,9 +257,7 @@ class GraphMLViewer3D(QMainWindow):
|
|||||||
|
|
||||||
# Set initial camera position
|
# Set initial camera position
|
||||||
self.view.setCameraPosition(
|
self.view.setCameraPosition(
|
||||||
distance=self.distance,
|
distance=self.distance, elevation=self.elevation, azimuth=self.azimuth
|
||||||
elevation=self.elevation,
|
|
||||||
azimuth=self.azimuth
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Connect all mouse events
|
# Connect all mouse events
|
||||||
@@ -268,7 +290,6 @@ class GraphMLViewer3D(QMainWindow):
|
|||||||
|
|
||||||
return sizes
|
return sizes
|
||||||
|
|
||||||
|
|
||||||
def create_toolbar(self, layout: QVBoxLayout):
|
def create_toolbar(self, layout: QVBoxLayout):
|
||||||
"""Create the toolbar with controls"""
|
"""Create the toolbar with controls"""
|
||||||
toolbar = QHBoxLayout()
|
toolbar = QHBoxLayout()
|
||||||
@@ -310,8 +331,6 @@ class GraphMLViewer3D(QMainWindow):
|
|||||||
reset_btn.clicked.connect(self.reset_view) # Use the new reset_view method
|
reset_btn.clicked.connect(self.reset_view) # Use the new reset_view method
|
||||||
toolbar.addWidget(reset_btn)
|
toolbar.addWidget(reset_btn)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def load_graphml(self) -> None:
|
def load_graphml(self) -> None:
|
||||||
"""Load and visualize a GraphML file"""
|
"""Load and visualize a GraphML file"""
|
||||||
try:
|
try:
|
||||||
@@ -338,11 +357,7 @@ class GraphMLViewer3D(QMainWindow):
|
|||||||
|
|
||||||
if layout_type == "spring":
|
if layout_type == "spring":
|
||||||
pos = nx.spring_layout(
|
pos = nx.spring_layout(
|
||||||
self.graph,
|
self.graph, dim=3, k=2.0, iterations=100, weight=None
|
||||||
dim=3,
|
|
||||||
k=2.0,
|
|
||||||
iterations=100,
|
|
||||||
weight=None
|
|
||||||
)
|
)
|
||||||
elif layout_type == "circular":
|
elif layout_type == "circular":
|
||||||
pos_2d = nx.circular_layout(self.graph)
|
pos_2d = nx.circular_layout(self.graph)
|
||||||
@@ -365,13 +380,12 @@ class GraphMLViewer3D(QMainWindow):
|
|||||||
|
|
||||||
def get_node_color(self, node_id: str) -> Tuple[float, float, float, float]:
|
def get_node_color(self, node_id: str) -> Tuple[float, float, float, float]:
|
||||||
"""Get RGBA color based on community"""
|
"""Get RGBA color based on community"""
|
||||||
if hasattr(self, 'communities') and node_id in self.communities:
|
if hasattr(self, "communities") and node_id in self.communities:
|
||||||
comm_id = self.communities[node_id]
|
comm_id = self.communities[node_id]
|
||||||
color = self.community_colors[comm_id]
|
color = self.community_colors[comm_id]
|
||||||
return tuple(color)
|
return tuple(color)
|
||||||
return (0.5, 0.5, 0.5, 0.8)
|
return (0.5, 0.5, 0.5, 0.8)
|
||||||
|
|
||||||
|
|
||||||
def create_node(self, node_id: str, position: np.ndarray, node_type: str) -> Node3D:
|
def create_node(self, node_id: str, position: np.ndarray, node_type: str) -> Node3D:
|
||||||
"""Create a 3D node with interaction capabilities"""
|
"""Create a 3D node with interaction capabilities"""
|
||||||
color = self.get_node_color(node_id)
|
color = self.get_node_color(node_id)
|
||||||
@@ -386,11 +400,11 @@ class GraphMLViewer3D(QMainWindow):
|
|||||||
pos=np.array([position]),
|
pos=np.array([position]),
|
||||||
size=np.array([size * 8]),
|
size=np.array([size * 8]),
|
||||||
color=np.array([color]),
|
color=np.array([color]),
|
||||||
pxMode=False
|
pxMode=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Enable picking and set node ID
|
# Enable picking and set node ID
|
||||||
node.mesh_item.setGLOptions('translucent')
|
node.mesh_item.setGLOptions("translucent")
|
||||||
node.mesh_item.node_id = node_id
|
node.mesh_item.node_id = node_id
|
||||||
|
|
||||||
if self.show_labels.isChecked():
|
if self.show_labels.isChecked():
|
||||||
@@ -402,9 +416,6 @@ class GraphMLViewer3D(QMainWindow):
|
|||||||
|
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def mapToView(self, pos) -> Point:
|
def mapToView(self, pos) -> Point:
|
||||||
"""Convert screen coordinates to world coordinates"""
|
"""Convert screen coordinates to world coordinates"""
|
||||||
# Get the viewport size
|
# Get the viewport size
|
||||||
@@ -451,9 +462,7 @@ class GraphMLViewer3D(QMainWindow):
|
|||||||
self.elevation = np.clip(self.elevation, -89, 89)
|
self.elevation = np.clip(self.elevation, -89, 89)
|
||||||
|
|
||||||
self.view.setCameraPosition(
|
self.view.setCameraPosition(
|
||||||
distance=self.distance,
|
distance=self.distance, elevation=self.elevation, azimuth=self.azimuth
|
||||||
elevation=self.elevation,
|
|
||||||
azimuth=self.azimuth
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Handle hover events when no buttons are pressed
|
# Handle hover events when no buttons are pressed
|
||||||
@@ -462,14 +471,14 @@ class GraphMLViewer3D(QMainWindow):
|
|||||||
mouse_pos = self.mapToView(pos)
|
mouse_pos = self.mapToView(pos)
|
||||||
|
|
||||||
# Check for hover
|
# Check for hover
|
||||||
min_dist = float('inf')
|
min_dist = float("inf")
|
||||||
hovered_node = None
|
hovered_node = None
|
||||||
|
|
||||||
for node_id, node in self.nodes.items():
|
for node_id, node in self.nodes.items():
|
||||||
# Calculate distance to mouse in world coordinates
|
# Calculate distance to mouse in world coordinates
|
||||||
dx = mouse_pos.x - node.position[0]
|
dx = mouse_pos.x - node.position[0]
|
||||||
dy = mouse_pos.y - node.position[1]
|
dy = mouse_pos.y - node.position[1]
|
||||||
dist = np.sqrt(dx*dx + dy*dy)
|
dist = np.sqrt(dx * dx + dy * dy)
|
||||||
|
|
||||||
if dist < min_dist and dist < 0.5: # Adjust threshold as needed
|
if dist < min_dist and dist < 0.5: # Adjust threshold as needed
|
||||||
min_dist = dist
|
min_dist = dist
|
||||||
@@ -496,13 +505,13 @@ class GraphMLViewer3D(QMainWindow):
|
|||||||
mouse_pos = self.mapToView(pos)
|
mouse_pos = self.mapToView(pos)
|
||||||
|
|
||||||
# Find closest node
|
# Find closest node
|
||||||
min_dist = float('inf')
|
min_dist = float("inf")
|
||||||
clicked_node = None
|
clicked_node = None
|
||||||
|
|
||||||
for node_id, node in self.nodes.items():
|
for node_id, node in self.nodes.items():
|
||||||
dx = mouse_pos.x - node.position[0]
|
dx = mouse_pos.x - node.position[0]
|
||||||
dy = mouse_pos.y - node.position[1]
|
dy = mouse_pos.y - node.position[1]
|
||||||
dist = np.sqrt(dx*dx + dy*dy)
|
dist = np.sqrt(dx * dx + dy * dy)
|
||||||
|
|
||||||
if dist < min_dist and dist < 0.5: # Adjust threshold as needed
|
if dist < min_dist and dist < 0.5: # Adjust threshold as needed
|
||||||
min_dist = dist
|
min_dist = dist
|
||||||
@@ -518,15 +527,14 @@ class GraphMLViewer3D(QMainWindow):
|
|||||||
|
|
||||||
if self.graph:
|
if self.graph:
|
||||||
self.details.update_node_info(
|
self.details.update_node_info(
|
||||||
self.graph.nodes[clicked_node],
|
self.graph.nodes[clicked_node], self.graph[clicked_node]
|
||||||
self.graph[clicked_node]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_mouse_release(self, event):
|
def on_mouse_release(self, event):
|
||||||
"""Handle mouse release events"""
|
"""Handle mouse release events"""
|
||||||
self.mouse_buttons_pressed.discard(event.button())
|
self.mouse_buttons_pressed.discard(event.button())
|
||||||
self.mouse_pos_last = None
|
self.mouse_pos_last = None
|
||||||
|
|
||||||
|
|
||||||
def on_mouse_wheel(self, event):
|
def on_mouse_wheel(self, event):
|
||||||
"""Handle mouse wheel for zooming"""
|
"""Handle mouse wheel for zooming"""
|
||||||
delta = event.angleDelta().y()
|
delta = event.angleDelta().y()
|
||||||
@@ -539,9 +547,7 @@ class GraphMLViewer3D(QMainWindow):
|
|||||||
self.distance = np.clip(self.distance, 1.0, 100.0)
|
self.distance = np.clip(self.distance, 1.0, 100.0)
|
||||||
|
|
||||||
self.view.setCameraPosition(
|
self.view.setCameraPosition(
|
||||||
distance=self.distance,
|
distance=self.distance, elevation=self.elevation, azimuth=self.azimuth
|
||||||
elevation=self.elevation,
|
|
||||||
azimuth=self.azimuth
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def reset_view(self):
|
def reset_view(self):
|
||||||
@@ -552,22 +558,22 @@ class GraphMLViewer3D(QMainWindow):
|
|||||||
self.center = np.array([0, 0, 0])
|
self.center = np.array([0, 0, 0])
|
||||||
|
|
||||||
self.view.setCameraPosition(
|
self.view.setCameraPosition(
|
||||||
distance=self.distance,
|
distance=self.distance, elevation=self.elevation, azimuth=self.azimuth
|
||||||
elevation=self.elevation,
|
|
||||||
azimuth=self.azimuth
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def create_edge(
|
||||||
def create_edge(self, start_pos: np.ndarray, end_pos: np.ndarray,
|
self,
|
||||||
color: Tuple[float, float, float, float] = (0.3, 0.3, 0.3, 0.2)
|
start_pos: np.ndarray,
|
||||||
) -> gl.GLLinePlotItem:
|
end_pos: np.ndarray,
|
||||||
|
color: Tuple[float, float, float, float] = (0.3, 0.3, 0.3, 0.2),
|
||||||
|
) -> gl.GLLinePlotItem:
|
||||||
"""Create a 3D edge between nodes"""
|
"""Create a 3D edge between nodes"""
|
||||||
return gl.GLLinePlotItem(
|
return gl.GLLinePlotItem(
|
||||||
pos=np.array([start_pos, end_pos]),
|
pos=np.array([start_pos, end_pos]),
|
||||||
color=color,
|
color=color,
|
||||||
width=1,
|
width=1,
|
||||||
antialias=True,
|
antialias=True,
|
||||||
mode='lines'
|
mode="lines",
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle_node_hover(self, event: Any, node_id: str) -> None:
|
def handle_node_hover(self, event: Any, node_id: str) -> None:
|
||||||
@@ -595,8 +601,7 @@ class GraphMLViewer3D(QMainWindow):
|
|||||||
|
|
||||||
if self.graph:
|
if self.graph:
|
||||||
self.details.update_node_info(
|
self.details.update_node_info(
|
||||||
self.graph.nodes[node_id],
|
self.graph.nodes[node_id], self.graph[node_id]
|
||||||
self.graph[node_id]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def refresh_layout(self) -> None:
|
def refresh_layout(self) -> None:
|
||||||
@@ -620,7 +625,7 @@ class GraphMLViewer3D(QMainWindow):
|
|||||||
positions = self.calculate_layout()
|
positions = self.calculate_layout()
|
||||||
|
|
||||||
for node_id in self.graph.nodes():
|
for node_id in self.graph.nodes():
|
||||||
node_type = self.graph.nodes[node_id].get('type', 'default')
|
node_type = self.graph.nodes[node_id].get("type", "default")
|
||||||
node = self.create_node(node_id, positions[node_id], node_type)
|
node = self.create_node(node_id, positions[node_id], node_type)
|
||||||
|
|
||||||
self.view.addItem(node.mesh_item)
|
self.view.addItem(node.mesh_item)
|
||||||
@@ -636,7 +641,7 @@ class GraphMLViewer3D(QMainWindow):
|
|||||||
|
|
||||||
if self.show_labels.isChecked():
|
if self.show_labels.isChecked():
|
||||||
mid_point = (positions[source] + positions[target]) / 2
|
mid_point = (positions[source] + positions[target]) / 2
|
||||||
relationship = self.graph.edges[source, target].get('relationship', '')
|
relationship = self.graph.edges[source, target].get("relationship", "")
|
||||||
if relationship:
|
if relationship:
|
||||||
label = gl.GLTextItem(
|
label = gl.GLTextItem(
|
||||||
pos=mid_point,
|
pos=mid_point,
|
||||||
@@ -646,6 +651,7 @@ class GraphMLViewer3D(QMainWindow):
|
|||||||
self.view.addItem(label)
|
self.view.addItem(label)
|
||||||
self.edge_labels.append(label)
|
self.edge_labels.append(label)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Application entry point"""
|
"""Application entry point"""
|
||||||
import sys
|
import sys
|
||||||
@@ -655,5 +661,6 @@ def main():
|
|||||||
viewer.show()
|
viewer.show()
|
||||||
sys.exit(app.exec_())
|
sys.exit(app.exec_())
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
Reference in New Issue
Block a user