| … | … | @@ -24,10 +24,14 @@ func _draw(): |
| 24 | 24 | var points = [] |
| 25 | 25 | for node_id in nodes: |
| 26 | 26 | var node = get_node_from_group(node_id) |
| 27 | ||
| 27 | 28 | if node: |
| 28 | 29 | var center = node.position |
| 29 | var radius = nodes[node_id] * node.scale.x # Adjust radius based on node scale | |
| 30 | points += generate_circle_points(center, radius, 100) # Generate circle points | |
| 30 | var radius = nodes[node_id] | |
| 31 | points += generate_circle_points(center, radius,50) # Generate circle points | |
| 32 | # Draw each point as a small circle for visibility | |
| 33 | for point in points: | |
| 34 | draw_circle(point, 2, Color(1, 0, 0)) # Red color, radius 2 | |
| 31 | 35 | |
| 32 | 36 | # Compute the convex hull of the points |
| 33 | 37 | var hull = andrew_monotone_chain(points) |
| … | … | @@ -79,60 +83,20 @@ func find_lowest_point(points): |
| 79 | 83 | lowest = point |
| 80 | 84 | return lowest |
| 81 | 85 | |
| 82 | func sort_points_by_angle(points, p0): | |
| 83 | points.sort_custom(func(a, b): | |
| 84 | var angle_a = calculate_polar_angle(a, p0) | |
| 85 | var angle_b = calculate_polar_angle(b, p0) | |
| 86 | return angle_a < angle_b # For ascending order; use '>' for descending | |
| 87 | ) | |
| 88 | return points | |
| 89 | ||
| 90 | func calculate_polar_angle(a, p0): | |
| 91 | var dy = a.y - p0.y | |
| 92 | var dx = a.x - p0.x | |
| 93 | var angle = atan2(dy, dx) | |
| 94 | return angle | |
| 95 | ||
| 96 | func compare_polar_angle(p0, a, b): | |
| 97 | var o = orientation(p0, a, b) | |
| 98 | if o == 0: # Collinear | |
| 99 | return p0.distance_to(a) < p0.distance_to(b) | |
| 100 | return o == 2 # Clockwise or counterclockwise | |
| 101 | ||
| 102 | func graham_scan(points): | |
| 103 | if points.size() < 3: | |
| 104 | return [] | |
| 105 | ||
| 106 | var p0 = find_lowest_point(points) | |
| 107 | var sorted_points = sort_points_by_angle(points.duplicate(), p0) | |
| 108 | var stack = [] | |
| 109 | stack.append(p0) | |
| 110 | stack.append(sorted_points[1]) | |
| 111 | stack.append(sorted_points[2]) | |
| 112 | ||
| 113 | for i in range(3, sorted_points.size()): | |
| 114 | while stack.size() >= 2 and orientation(stack[stack.size() - 2], stack[stack.size() - 1], sorted_points[i]) != 2: | |
| 115 | stack.pop_back() | |
| 116 | stack.append(sorted_points[i]) | |
| 117 | ||
| 118 | return stack | |
| 119 | 86 | |
| 120 | 87 | func andrew_monotone_chain(points): |
| 121 | 88 | if points.size() < 3: |
| 122 | 89 | return points |
| 123 | ||
| 124 | 90 | # Sort points |
| 125 | 91 | points.sort_custom(func(a, b): |
| 126 | 92 | return a.x < b.x if a.x != b.x else a.y < b.y |
| 127 | 93 | ) |
| 128 | ||
| 129 | # BLower hull | |
| 94 | # Lower hull | |
| 130 | 95 | var lower = [] |
| 131 | 96 | for p in points: |
| 132 | 97 | while lower.size() >= 2 and cross(lower[lower.size() - 2], lower[lower.size() - 1], p) <= 0: |
| 133 | 98 | lower.pop_back() |
| 134 | 99 | lower.append(p) |
| 135 | ||
| 136 | 100 | # Upper hull |
| 137 | 101 | var upper = [] |
| 138 | 102 | for i in range(points.size() - 1, -1, -1): |
| … | … | @@ -140,10 +104,9 @@ func andrew_monotone_chain(points): |
| 140 | 104 | while upper.size() >= 2 and cross(upper[upper.size() - 2], upper[upper.size() - 1], p) <= 0: |
| 141 | 105 | upper.pop_back() |
| 142 | 106 | upper.append(p) |
| 143 | ||
| 144 | # Concatenate the lower and upper hulls | |
| 107 | # Concatenate the hulls | |
| 145 | 108 | # The first and last points in lower will be the same as the last two points in upper, |
| 146 | # so we omit them from the result | |
| 109 | # so we pop them from the result | |
| 147 | 110 | lower.pop_back() |
| 148 | 111 | upper.pop_back() |
| 149 | 112 | return lower + upper |
| … | … | @@ -69,15 +69,26 @@ func update_size(size_scale: float, new_centrality_score: float): |
| 69 | 69 | |
| 70 | 70 | func make_offset_circles(): |
| 71 | 71 | offset_circles.clear() |
| 72 | var circle_count = edges.size() | |
| 73 | 72 | var scale_factor = (scale.x + scale.y) / 2 |
| 74 | 73 | var base_radius = 25 * scale_factor |
| 75 | var radius_increment = 10 * scale_factor | |
| 76 | ||
| 77 | for i in range(circle_count): | |
| 78 | var radius = base_radius + i * radius_increment | |
| 79 | offset_circles.append(radius) | |
| 80 | ||
| 74 | var cumulative_radius = base_radius | |
| 75 | var edge_widths = [] | |
| 76 | for edge_id in edges.keys(): | |
| 77 | var edge_support = edges[edge_id] | |
| 78 | var edge_width = pow(edge_support, 0.35) # Function to calculate edge width based on support | |
| 79 | edge_widths.append(edge_width) | |
| 80 | ||
| 81 | for i in range(edge_widths.size()): | |
| 82 | var edge_width = edge_widths[i] | |
| 83 | # Increase the radius by half of the previous edge's width and half of the current edge's width | |
| 84 | # This ensures that the edges do not overlap | |
| 85 | if i == 0: | |
| 86 | cumulative_radius += edge_width / 2 | |
| 87 | else: | |
| 88 | cumulative_radius += (edge_widths[i - 1] / 2) + (edge_width / 2) | |
| 89 | ||
| 90 | offset_circles.append(cumulative_radius) | |
| 91 | # cumulative_radius += edge_width / 2 # Prepare for the next edge (not needed here) | |
| 81 | 92 | queue_redraw() |
| 82 | 93 | |
| 83 | 94 |
| … | … | @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/Times New Roman.ttf-5309258ee4eb8e8a0e31d33c7 |
| 15 | 15 | Rendering=null |
| 16 | 16 | antialiasing=1 |
| 17 | 17 | generate_mipmaps=false |
| 18 | disable_embedded_bitmaps=true | |
| 18 | 19 | multichannel_signed_distance_field=false |
| 19 | 20 | msdf_pixel_range=8 |
| 20 | 21 | msdf_size=48 |
| … | … | @@ -40,52 +40,78 @@ func create_nodes(nodes: Dictionary): |
| 40 | 40 | |
| 41 | 41 | func populate_edge_data(file_data): |
| 42 | 42 | var hyperedge_scene = load("res://HyperEdge.tscn") |
| 43 | var nodes = file_data["nodes"] # Retrieve nodes from the file data | |
| 44 | var edges = file_data["edges"] # Retrieve edges from the file data | |
| 45 | ||
| 43 | var nodes = file_data["nodes"] # Retrieve nodes from the file data | |
| 44 | var edges = file_data["edges"] # Retrieve edges from the file data | |
| 45 | ||
| 46 | 46 | var group_names = [] |
| 47 | 47 | for edge_id in edges: |
| 48 | 48 | var group_name = edges[edge_id]["group"] |
| 49 | 49 | if group_name not in group_names: |
| 50 | 50 | group_names.append(group_name) |
| 51 | 51 | Global.initialize_groups(group_names) |
| 52 | # Iterate over nodes to build edge data based on node connections | |
| 52 | ||
| 53 | # Step 1: Build a per-node mapping of edge IDs to radii | |
| 54 | var node_edge_radius_map = {} # Dictionary mapping node_id to {edge_id: radius} | |
| 53 | 55 | for node_id in nodes: |
| 54 | 56 | var node_edges = nodes[node_id].edges |
| 55 | var circle_ref = get_node(str(node_id)) # Assuming this gets a node instance with 'offset_circles' | |
| 56 | var offset_circles = circle_ref.offset_circles # List of radii for offset circles | |
| 57 | for edge_index in range(node_edges.size()): | |
| 58 | var edge_id = node_edges.keys()[edge_index] | |
| 59 | if not edges.has(edge_id): | |
| 60 | edges[edge_id] = {"nodes": [], "radii": [], "support": 0, "group": ""} | |
| 61 | ||
| 62 | ||
| 63 | if edge_index < offset_circles.size(): # Ensure there's a corresponding circle | |
| 64 | var radius = offset_circles[edge_index] | |
| 65 | print(offset_circles[edge_index]) | |
| 66 | ||
| 67 | # Append node and radius information for the edge | |
| 68 | ||
| 69 | edges[edge_id]["radii"].append(radius) | |
| 70 | edges[edge_id]["support"] = nodes[node_id].edges[edge_id] # Support value | |
| 71 | edges[edge_id]["group"] = edges[edge_id]["group"] # Group is already stored in the edges dictionary | |
| 72 | ||
| 57 | var circle_ref = get_node(str(node_id)) # Get node instance with 'offset_circles' | |
| 58 | var offset_circles = circle_ref.offset_circles # List of radii for offset circles | |
| 59 | ||
| 60 | var edge_ids = node_edges.keys() | |
| 61 | var edge_radius_map = {} | |
| 62 | ||
| 63 | # Ensure there are enough radii for each edge | |
| 64 | if offset_circles.size() < edge_ids.size(): | |
| 65 | print("Error: Node %s has more edges (%s) than offset circles provided." % [node_id, edge_ids.size()]) | |
| 66 | continue # Skip this node if radii are insufficient | |
| 67 | ||
| 68 | # Map each edge_id to its corresponding radius for this node | |
| 69 | for i in range(edge_ids.size()): | |
| 70 | var edge_id = edge_ids[i] | |
| 71 | var radius = offset_circles[i] | |
| 72 | edge_radius_map[edge_id] = radius | |
| 73 | ||
| 74 | node_edge_radius_map[node_id] = edge_radius_map | |
| 75 | ||
| 76 | # Step 2: Build edges data using node_edge_radius_map | |
| 77 | for edge_id in edges: | |
| 78 | var edge_nodes = edges[edge_id]["nodes"] # Nodes connected by this edge | |
| 79 | var edge_radii = [] | |
| 80 | var edge_node_ids = [] | |
| 81 | ||
| 82 | for node_id in edge_nodes: | |
| 83 | if node_edge_radius_map.has(node_id): | |
| 84 | var edge_radius_map = node_edge_radius_map[node_id] | |
| 85 | if edge_radius_map.has(edge_id): | |
| 86 | var radius = edge_radius_map[edge_id] | |
| 87 | edge_node_ids.append(node_id) | |
| 88 | edge_radii.append(radius) | |
| 89 | else: | |
| 90 | print("Error: Edge radius not found for node %s and edge %s" % [node_id, edge_id]) | |
| 73 | 91 | else: |
| 74 | print("Error: Node %s has more edges (%s) than offset circles provided." % [node_id, node_edges.size()]) | |
| 75 | break | |
| 92 | print("Error: Node %s not found in node_edge_radius_map" % node_id) | |
| 76 | 93 | |
| 77 | # Instantiate hyperedges and visualize them | |
| 94 | # Update edges[edge_id] with aligned nodes and radii | |
| 95 | edges[edge_id]["nodes"] = edge_node_ids | |
| 96 | edges[edge_id]["radii"] = edge_radii | |
| 97 | ||
| 98 | # Step 3: Instantiate hyperedges and visualize them | |
| 78 | 99 | for edge_id in edges: |
| 79 | 100 | var hyperedge_instance = hyperedge_scene.instantiate() |
| 80 | 101 | var edge_data = edges[edge_id] |
| 81 | 102 | var node_radii_pairs = {} |
| 82 | 103 | |
| 104 | # Ensure nodes and radii arrays are aligned | |
| 105 | if edge_data["nodes"].size() != edge_data["radii"].size(): | |
| 106 | print("Error: Mismatch between nodes and radii sizes for edge", edge_id) | |
| 107 | continue # Skip this edge if there's a mismatch | |
| 108 | ||
| 83 | 109 | # Map each node to its radius |
| 84 | 110 | for i in range(edge_data["nodes"].size()): |
| 85 | 111 | var nodeid = edge_data["nodes"][i] |
| 86 | 112 | var radius = edge_data["radii"][i] |
| 87 | 113 | node_radii_pairs[nodeid] = radius |
| 88 | ||
| 114 | ||
| 89 | 115 | # Assign the nodes and radii to the hyperedge instance |
| 90 | 116 | hyperedge_instance.nodes = node_radii_pairs |
| 91 | 117 | |
| … | … | @@ -93,7 +119,7 @@ func populate_edge_data(file_data): |
| 93 | 119 | var color = Color(randf(), randf(), randf(), 0.3) # Generate a random color |
| 94 | 120 | hyperedge_instance.color = color |
| 95 | 121 | |
| 96 | # Set the group name (2021 or 2022) to the hyperedge instance for future use | |
| 122 | # Set the group name to the hyperedge instance for future use | |
| 97 | 123 | hyperedge_instance.group = edge_data["group"] |
| 98 | 124 | |
| 99 | 125 | hyperedge_instance.add_to_group("edges") |
| … | … | @@ -101,10 +127,11 @@ func populate_edge_data(file_data): |
| 101 | 127 | |
| 102 | 128 | # Set the width of the hyperedge based on the support value |
| 103 | 129 | hyperedge_instance.width = pow(edges[edge_id]["support"], 0.35) |
| 104 | ||
| 130 | ||
| 105 | 131 | # Add the hyperedge instance to the scene tree |
| 106 | 132 | add_child(hyperedge_instance) |
| 107 | 133 | |
| 134 | ||
| 108 | 135 | |
| 109 | 136 | |
| 110 | 137 | func _ready(): |
| … | … | @@ -282,12 +309,12 @@ func calculate_centrality_and_resize_nodes(file_data): |
| 282 | 309 | var centrality_scores = {} |
| 283 | 310 | |
| 284 | 311 | # Calculate centrality (degree centrality in this case) |
| 285 | print(nodes) | |
| 312 | ||
| 286 | 313 | for node_id in nodes: |
| 287 | 314 | var degree = nodes[node_id].edges.size() |
| 288 | 315 | centrality_scores[node_id] = degree |
| 289 | 316 | max_centrality = max(max_centrality, degree) |
| 290 | print(centrality_scores) | |
| 317 | ||
| 291 | 318 | # Resize nodes based on centrality |
| 292 | 319 | for node_id in nodes: |
| 293 | 320 | var node_instance = get_node(str(node_id)) |
| … | … | @@ -0,0 +1,4 @@ |
| 1 | Energy_and_Extractives Oil_and_Gas_Policy,_Strategy_and_Institutions #SUP: 457 #GROUP 2022 | |
| 2 | Energy_and_Extractives Environment_and_Natural_Resources Renewable_Energy #SUP: 211 #GROUP 2022 | |
| 3 | Energy_and_Extractives Climate_Change Renewable_Energy #SUP: 213 #GROUP 2022 | |
| 4 | Health_Emergency_Preparedness_and_Disaster_Response Energy_and_Extractives #SUP: 657 #GROUP 2022 |
| … | … | @@ -0,0 +1,4 @@ |
| 1 | Energy_and_Extractives Oil_and_Gas_Policy,_Strategy_and_Institutions #SUP: 457 | |
| 2 | Energy_and_Extractives Environment_and_Natural_Resources Renewable_Energy #SUP: 211 | |
| 3 | Energy_and_Extractives Climate_Change Renewable_Energy #SUP: 213 | |
| 4 | Health_Emergency_Preparedness_and_Disaster_Response Energy_and_Extractives #SUP: 657 |
| … | … | @@ -1,6 +1,6 @@ |
| 1 | 1 | Environment_and_Natural_Resources Pollution_Management #SUP: 2662 #GROUP 2022 |
| 2 | 2 | Energy_and_Extractives Energy_Efficiency #SUP: 3163 #GROUP 2022 |
| 3 | Health_Emergency_Preparedness_and_Disaster_Response Energy_and_Extractives #SUP: 7720 #GROUP 2022 | |
| 3 | Energy_and_Extractives Health_Emergency_Preparedness_and_Disaster_Response #SUP: 7720 #GROUP 2022 | |
| 4 | 4 | Environment_and_Natural_Resources Pollution_Management Air_Pollution #SUP: 3312 #GROUP 2023 |
| 5 | Energy_and_Extractives Climate_Change Environment_and_Natural_Resources Renewable_Energy #SUP: 3301 #GROUP 2023 | |
| 5 | Environment_and_Natural_Resources Energy_and_Extractives Climate_Change Renewable_Energy #SUP: 3301 #GROUP 2023 | |
| 6 | 6 | Energy_and_Extractives Health_Emergency_Preparedness_and_Disaster_Response #SUP: 5647 #GROUP 2023 |
| … | … | @@ -12,17 +12,13 @@ config_version=5 |
| 12 | 12 | |
| 13 | 13 | config/name="Hypergodot" |
| 14 | 14 | run/main_scene="res://main.tscn" |
| 15 | config/features=PackedStringArray("4.2", "Forward Plus") | |
| 15 | config/features=PackedStringArray("4.3", "Forward Plus") | |
| 16 | 16 | config/icon="res://icon.svg" |
| 17 | 17 | |
| 18 | 18 | [autoload] |
| 19 | 19 | |
| 20 | 20 | Global="*res://Global.gd" |
| 21 | 21 | |
| 22 | [debug] | |
| 23 | ||
| 24 | settings/stdout/print_fps=true | |
| 25 | ||
| 26 | 22 | [display] |
| 27 | 23 | |
| 28 | 24 | window/vsync/vsync_mode=0 |
| … | … | @@ -33,7 +29,7 @@ project/assembly_name="Hypergodot" |
| 33 | 29 | |
| 34 | 30 | [editor_plugins] |
| 35 | 31 | |
| 36 | enabled=PackedStringArray("res://addons/kanban_tasks/plugin.cfg") | |
| 32 | enabled=PackedStringArray() | |
| 37 | 33 | |
| 38 | 34 | [input] |
| 39 | 35 |