| … | … | @@ -0,0 +1,27 @@ |
| 1 | extends Node | |
| 2 | ||
| 3 | var group_dictionary = {} | |
| 4 | var group_count = 0 | |
| 5 | ||
| 6 | # Function to initialize the groups from a list of group names | |
| 7 | func initialize_groups(group_names: Array): | |
| 8 | group_dictionary.clear() | |
| 9 | group_count = 0 | |
| 10 | ||
| 11 | # Add each group name with an index (1-based) | |
| 12 | for group_name in group_names: | |
| 13 | group_count += 1 | |
| 14 | group_dictionary[group_name] = group_count | |
| 15 | ||
| 16 | # Function to get the index of a group name | |
| 17 | func get_group_index(group_name: String) -> int: | |
| 18 | if group_name in group_dictionary: | |
| 19 | return group_dictionary[group_name] | |
| 20 | return -1 # Return -1 if the group is not found | |
| 21 | ||
| 22 | # Function to get group name by index (if needed later) | |
| 23 | func get_group_name(group_index: int) -> String: | |
| 24 | for name in group_dictionary: | |
| 25 | if group_dictionary[name] == group_index: | |
| 26 | return name | |
| 27 | return "" |
| … | … | @@ -0,0 +1,6 @@ |
| 1 | [gd_scene load_steps=2 format=3 uid="uid://ookb725cjygc"] | |
| 2 | ||
| 3 | [ext_resource type="Script" path="res://Global.gd" id="1_d51fs"] | |
| 4 | ||
| 5 | [node name="Node" type="Node"] | |
| 6 | script = ExtResource("1_d51fs") |
| … | … | @@ -2,10 +2,13 @@ extends Node2D |
| 2 | 2 | |
| 3 | 3 | var nodes: Dictionary |
| 4 | 4 | var color: Color |
| 5 | var width = 5 | |
| 5 | var width = 5 | |
| 6 | 6 | var dragging = false |
| 7 | var group = "" | |
| 8 | ||
| 7 | 9 | func _ready(): |
| 8 | 10 | pass |
| 11 | ||
| 9 | 12 | func _process(delta): |
| 10 | 13 | if dragging: |
| 11 | 14 | queue_redraw() |
| … | … | @@ -17,63 +20,71 @@ func _input(event): |
| 17 | 20 | else: |
| 18 | 21 | dragging = false # Stop dragging |
| 19 | 22 | queue_redraw() # Optionally, queue a redraw when you stop dragging |
| 23 | func _draw(): | |
| 24 | var points = [] | |
| 25 | for node_id in nodes: | |
| 26 | var node = get_node_from_group(node_id) | |
| 27 | if node: | |
| 28 | 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 | |
| 20 | 31 | |
| 32 | # Compute the convex hull of the points | |
| 33 | var hull = andrew_monotone_chain(points) | |
| 34 | if hull.size() > 0 and hull[0] != hull[hull.size() - 1]: | |
| 35 | hull.append(hull[0]) # Close the hull | |
| 21 | 36 | |
| 22 | func _draw(): | |
| 23 | var points = [] | |
| 24 | for node_id in nodes: | |
| 25 | var node = get_node_from_group(node_id) | |
| 26 | if node: | |
| 27 | var center = node.position | |
| 28 | var radius = nodes[node_id] | |
| 29 | points += generate_circle_points(center, radius, 20) # 8 points per circle for approximation | |
| 30 | #for point in points: | |
| 31 | # draw_circle(point, 2, Color(1, 0, 0, 1)) # Draw each point as a small circle | |
| 32 | ||
| 33 | ||
| 34 | var hull = graham_scan(points) | |
| 35 | #for point in hull: | |
| 36 | # draw_circle(point, 2, Color(1, 0, 0, 1)) # Draw each point as a small circle | |
| 37 | if hull.size() > 0 and hull[0] != hull[hull.size() - 1]: | |
| 38 | hull.append(hull[0]) | |
| 39 | for i in range(hull.size() - 1): | |
| 40 | draw_line(hull[i], hull[i + 1], color, width) # Change '2' to your desired width | |
| 41 | ||
| 42 | var p0 | |
| 37 | ||
| 38 | var group_index = Global.get_group_index(group) # Get the group index from the global dictionary | |
| 43 | 39 | |
| 40 | # Iterate over the hull points and draw lines between them based on the group | |
| 41 | for i in range(hull.size() - 1): | |
| 42 | match group_index: | |
| 43 | 1: | |
| 44 | # Draw solid line for group 1 (e.g., 2021) | |
| 45 | draw_line(hull[i], hull[i + 1], color, width) | |
| 46 | 2: | |
| 47 | # Draw dashed line for group 2 (e.g., 2022) | |
| 48 | draw_dashed_line(hull[i], hull[i + 1], color, width, 10.0) | |
| 49 | _: | |
| 50 | # Default behavior (solid line for other groups) | |
| 51 | draw_line(hull[i], hull[i + 1], color, width) | |
| 52 | ||
| 53 | ||
| 54 | func update_position(): | |
| 55 | queue_redraw() # This will trigger a redraw with updated node positions and sizes | |
| 56 | ||
| 44 | 57 | func generate_circle_points(center, radius, point_count): |
| 45 | 58 | var points = [] |
| 46 | 59 | for i in range(point_count): |
| 47 | 60 | var angle = 2 * PI * i / point_count |
| 48 | 61 | points.append(center + Vector2(cos(angle), sin(angle)) * radius) |
| 49 | 62 | return points |
| 50 | ||
| 63 | ||
| 51 | 64 | func orientation(p, q, r): |
| 52 | 65 | var val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y) |
| 53 | 66 | return 0 if val == 0 else (1 if val > 0 else 2) |
| 54 | 67 | |
| 55 | ||
| 56 | 68 | func get_node_from_group(node_id) -> Node2D: |
| 57 | var nodes_group = get_tree().get_nodes_in_group("nodes") | |
| 58 | for node in nodes_group: | |
| 59 | if str(node.name) == str(node_id): | |
| 60 | return node | |
| 61 | return null | |
| 62 | ||
| 63 | ||
| 69 | var nodes_group = get_tree().get_nodes_in_group("nodes") | |
| 70 | for node in nodes_group: | |
| 71 | if str(node.name) == str(node_id): | |
| 72 | return node | |
| 73 | return null | |
| 74 | ||
| 64 | 75 | func find_lowest_point(points): |
| 65 | 76 | var lowest = points[0] |
| 66 | 77 | for point in points: |
| 67 | 78 | if point.y < lowest.y or (point.y == lowest.y and point.x < lowest.x): |
| 68 | 79 | lowest = point |
| 69 | 80 | return lowest |
| 70 | ||
| 81 | ||
| 71 | 82 | func sort_points_by_angle(points, p0): |
| 72 | 83 | points.sort_custom(func(a, b): |
| 73 | 84 | var angle_a = calculate_polar_angle(a, p0) |
| 74 | 85 | var angle_b = calculate_polar_angle(b, p0) |
| 75 | 86 | return angle_a < angle_b # For ascending order; use '>' for descending |
| 76 | ) | |
| 87 | ) | |
| 77 | 88 | return points |
| 78 | 89 | |
| 79 | 90 | func calculate_polar_angle(a, p0): |
| … | … | @@ -83,12 +94,11 @@ func calculate_polar_angle(a, p0): |
| 83 | 94 | return angle |
| 84 | 95 | |
| 85 | 96 | func compare_polar_angle(p0, a, b): |
| 86 | # Calculate orientation | |
| 87 | 97 | var o = orientation(p0, a, b) |
| 88 | 98 | if o == 0: # Collinear |
| 89 | 99 | return p0.distance_to(a) < p0.distance_to(b) |
| 90 | 100 | return o == 2 # Clockwise or counterclockwise |
| 91 | ||
| 101 | ||
| 92 | 102 | func graham_scan(points): |
| 93 | 103 | if points.size() < 3: |
| 94 | 104 | return [] |
| … | … | @@ -106,3 +116,39 @@ func graham_scan(points): |
| 106 | 116 | stack.append(sorted_points[i]) |
| 107 | 117 | |
| 108 | 118 | return stack |
| 119 | ||
| 120 | func andrew_monotone_chain(points): | |
| 121 | if points.size() < 3: | |
| 122 | return points | |
| 123 | ||
| 124 | # Sort points | |
| 125 | points.sort_custom(func(a, b): | |
| 126 | return a.x < b.x if a.x != b.x else a.y < b.y | |
| 127 | ) | |
| 128 | ||
| 129 | # BLower hull | |
| 130 | var lower = [] | |
| 131 | for p in points: | |
| 132 | while lower.size() >= 2 and cross(lower[lower.size() - 2], lower[lower.size() - 1], p) <= 0: | |
| 133 | lower.pop_back() | |
| 134 | lower.append(p) | |
| 135 | ||
| 136 | # Upper hull | |
| 137 | var upper = [] | |
| 138 | for i in range(points.size() - 1, -1, -1): | |
| 139 | var p = points[i] | |
| 140 | while upper.size() >= 2 and cross(upper[upper.size() - 2], upper[upper.size() - 1], p) <= 0: | |
| 141 | upper.pop_back() | |
| 142 | upper.append(p) | |
| 143 | ||
| 144 | # Concatenate the lower and upper hulls | |
| 145 | # 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 | |
| 147 | lower.pop_back() | |
| 148 | upper.pop_back() | |
| 149 | return lower + upper | |
| 150 | ||
| 151 | func cross(o, a, b): | |
| 152 | return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x) | |
| 153 | ||
| 154 |
| … | … | @@ -47,32 +47,38 @@ func _input(event): |
| 47 | 47 | |
| 48 | 48 | |
| 49 | 49 | func get_rect() -> Rect2: |
| 50 | var area = Rect2(-25, -25, 50, 50) | |
| 51 | area.position.x = position.x + area.position.x | |
| 52 | area.position.y = position.y + area.position.y | |
| 53 | return area | |
| 50 | var scale_factor = (scale.x + scale.y) / 2 | |
| 51 | var size = 50 * scale_factor # Assuming the original size is 50x50 | |
| 52 | var area = Rect2(-size/2, -size/2, size, size) | |
| 53 | area.position += position | |
| 54 | return area | |
| 54 | 55 | |
| 55 | 56 | func _process(delta): |
| 56 | 57 | if dragging: |
| 57 | 58 | var mouse_pos = get_viewport().get_mouse_position() |
| 58 | 59 | set_position(mouse_pos - drag_offset) |
| 59 | 60 | |
| 60 | ||
| 61 | var centrality_score = 0 | |
| 61 | 62 | func _draw(): |
| 62 | 63 | pass |
| 64 | ||
| 65 | func update_size(size_scale: float, new_centrality_score: float): | |
| 66 | scale = Vector2(size_scale, size_scale) | |
| 67 | centrality_score = new_centrality_score | |
| 68 | make_offset_circles() # Now this will use the updated scale | |
| 63 | 69 | |
| 64 | # This function should use the edges array, and construct as many circles around the node, with a small offset in the radius, that will be used | |
| 65 | # to connect the edges to the node, and prevent the edges from overlapping the node and other edges | |
| 66 | 70 | func make_offset_circles(): |
| 67 | offset_circles.clear() # Reset previous data | |
| 71 | offset_circles.clear() | |
| 68 | 72 | var circle_count = edges.size() |
| 69 | var base_radius = 25 # Starting radius for the smallest circle | |
| 70 | ||
| 73 | var scale_factor = (scale.x + scale.y) / 2 | |
| 74 | var base_radius = 25 * scale_factor | |
| 75 | var radius_increment = 10 * scale_factor | |
| 76 | ||
| 71 | 77 | for i in range(circle_count): |
| 72 | var radius = base_radius + i * 5 # Increase radius for each subsequent circle | |
| 73 | offset_circles.append(radius) # Store only the radius, as center is the node's position | |
| 74 | ||
| 75 | queue_redraw() | |
| 78 | var radius = base_radius + i * radius_increment | |
| 79 | offset_circles.append(radius) | |
| 80 | ||
| 81 | queue_redraw() | |
| 76 | 82 | |
| 77 | 83 | |
| 78 | 84 |
| … | … | @@ -0,0 +1,24 @@ |
| 1 | [gd_scene load_steps=3 format=3 uid="uid://2f8fvrck2f38"] | |
| 2 | ||
| 3 | [ext_resource type="Script" path="res://main.gd" id="1_1hwno"] | |
| 4 | [ext_resource type="Script" path="res://Camera2D.gd" id="2_iiqxp"] | |
| 5 | ||
| 6 | [node name="Node2D" type="Node2D"] | |
| 7 | script = ExtResource("1_1hwno") | |
| 8 | ||
| 9 | [node name="ColorRect" type="ColorRect" parent="."] | |
| 10 | offset_left = -8559.0 | |
| 11 | offset_top = -6223.0 | |
| 12 | offset_right = 9085.0 | |
| 13 | offset_bottom = 6156.0 | |
| 14 | ||
| 15 | [node name="CanvasLayer" type="CanvasLayer" parent="."] | |
| 16 | ||
| 17 | [node name="ColorRect" type="ColorRect" parent="CanvasLayer"] | |
| 18 | visible = false | |
| 19 | offset_right = 195.0 | |
| 20 | offset_bottom = 401.0 | |
| 21 | color = Color(0.0980392, 0.0980392, 0.0980392, 1) | |
| 22 | ||
| 23 | [node name="Camera2D" type="Camera2D" parent="."] | |
| 24 | script = ExtResource("2_iiqxp") |
| … | … | @@ -0,0 +1,24 @@ |
| 1 | [gd_scene load_steps=3 format=3 uid="uid://2f8fvrck2f38"] | |
| 2 | ||
| 3 | [ext_resource type="Script" path="res://main.gd" id="1_1hwno"] | |
| 4 | [ext_resource type="Script" path="res://Camera2D.gd" id="2_iiqxp"] | |
| 5 | ||
| 6 | [node name="Node2D" type="Node2D"] | |
| 7 | script = ExtResource("1_1hwno") | |
| 8 | ||
| 9 | [node name="ColorRect" type="ColorRect" parent="."] | |
| 10 | offset_left = -8559.0 | |
| 11 | offset_top = -6223.0 | |
| 12 | offset_right = 9085.0 | |
| 13 | offset_bottom = 6156.0 | |
| 14 | ||
| 15 | [node name="CanvasLayer" type="CanvasLayer" parent="."] | |
| 16 | ||
| 17 | [node name="ColorRect" type="ColorRect" parent="CanvasLayer"] | |
| 18 | visible = false | |
| 19 | offset_right = 195.0 | |
| 20 | offset_bottom = 401.0 | |
| 21 | color = Color(0.0980392, 0.0980392, 0.0980392, 1) | |
| 22 | ||
| 23 | [node name="Camera2D" type="Camera2D" parent="."] | |
| 24 | script = ExtResource("2_iiqxp") |
| … | … | @@ -5,104 +5,164 @@ var edges : Dictionary |
| 5 | 5 | var nodes : Dictionary |
| 6 | 6 | class NodeData: |
| 7 | 7 | var id = 0 # Node identifier |
| 8 | var edges = [] # Array to hold edge identifiers (based on line number or custom ID) | |
| 9 | var support = 0 # Support value | |
| 8 | var edges = {} # Array to hold edge identifiers (based on line number or custom ID) | |
| 10 | 9 | |
| 11 | func _to_string(): | |
| 12 | return "Edges: " + str(edges) + ", Support: " + str(support) | |
| 10 | ||
| 13 | 11 | |
| 14 | 12 | |
| 15 | 13 | |
| 16 | 14 | func create_nodes(nodes: Dictionary): |
| 17 | var scene = load("HyperNode.tscn") | |
| 15 | var scene = load("res://HyperNode.tscn") | |
| 18 | 16 | var row = 1 |
| 19 | 17 | var column = 1 |
| 20 | 18 | var spacing = Vector2(100, 100) |
| 21 | 19 | var per_row = 10 |
| 20 | ||
| 22 | 21 | for id in nodes.keys(): |
| 23 | 22 | var node_instance = scene.instantiate() |
| 24 | 23 | node_instance.node_id = id |
| 25 | 24 | node_instance.name = str(id) |
| 26 | 25 | node_instance.position = Vector2(column * spacing.x, row * spacing.y) |
| 27 | 26 | node_instance._set_node_id(str(id)) |
| 27 | ||
| 28 | # Set the edges property for the node | |
| 28 | 29 | node_instance.edges = nodes[id].edges |
| 29 | 30 | |
| 31 | # Add the node instance to the scene | |
| 30 | 32 | add_child(node_instance) |
| 31 | 33 | |
| 34 | # Increment column and row for positioning nodes | |
| 32 | 35 | column += 1 |
| 33 | 36 | if column >= per_row: |
| 34 | 37 | column = 0 |
| 35 | 38 | row += 1 |
| 36 | 39 | |
| 37 | func populate_edge_data(): | |
| 40 | ||
| 41 | func populate_edge_data(file_data): | |
| 38 | 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 | ||
| 46 | var group_names = [] | |
| 47 | for edge_id in edges: | |
| 48 | var group_name = edges[edge_id]["group"] | |
| 49 | if group_name not in group_names: | |
| 50 | group_names.append(group_name) | |
| 51 | Global.initialize_groups(group_names) | |
| 52 | # Iterate over nodes to build edge data based on node connections | |
| 39 | 53 | for node_id in nodes: |
| 40 | 54 | var node_edges = nodes[node_id].edges |
| 41 | var circle_ref = get_node(str(node_id)) # Assuming this gets a node instance with 'offset_circles' available | |
| 55 | var circle_ref = get_node(str(node_id)) # Assuming this gets a node instance with 'offset_circles' | |
| 42 | 56 | var offset_circles = circle_ref.offset_circles # List of radii for offset circles |
| 43 | 57 | for edge_index in range(node_edges.size()): |
| 44 | var edge_id = node_edges[edge_index] | |
| 58 | var edge_id = node_edges.keys()[edge_index] | |
| 45 | 59 | if not edges.has(edge_id): |
| 46 | edges[edge_id] = {"nodes": [], "radii": []} | |
| 47 | ||
| 60 | edges[edge_id] = {"nodes": [], "radii": [], "support": 0, "group": ""} | |
| 61 | ||
| 62 | ||
| 48 | 63 | if edge_index < offset_circles.size(): # Ensure there's a corresponding circle |
| 49 | 64 | var radius = offset_circles[edge_index] |
| 50 | edges[edge_id]["nodes"].append(node_id) | |
| 65 | print(offset_circles[edge_index]) | |
| 66 | ||
| 67 | # Append node and radius information for the edge | |
| 68 | ||
| 51 | 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 | ||
| 52 | 73 | else: |
| 53 | 74 | print("Error: Node %s has more edges (%s) than offset circles provided." % [node_id, node_edges.size()]) |
| 54 | ||
| 75 | break | |
| 76 | ||
| 77 | # Instantiate hyperedges and visualize them | |
| 55 | 78 | for edge_id in edges: |
| 56 | var hyperedge_instance = hyperedge_scene.instantiate() | |
| 57 | var edge_data = edges[edge_id] | |
| 58 | var node_radii_pairs = {} | |
| 59 | for i in range(edge_data["nodes"].size()): | |
| 60 | var nodeid = edge_data["nodes"][i] | |
| 61 | var radius = edge_data["radii"][i] | |
| 62 | node_radii_pairs[nodeid] = radius | |
| 63 | hyperedge_instance.nodes = node_radii_pairs | |
| 64 | var color = Color(randf(), randf(), randf(), 0.3) # Generate a random color | |
| 65 | hyperedge_instance.color = color | |
| 66 | hyperedge_instance.add_to_group("edges") | |
| 67 | hyperedge_instance.name = str(edge_id) | |
| 68 | add_child(hyperedge_instance) | |
| 79 | var hyperedge_instance = hyperedge_scene.instantiate() | |
| 80 | var edge_data = edges[edge_id] | |
| 81 | var node_radii_pairs = {} | |
| 82 | ||
| 83 | # Map each node to its radius | |
| 84 | for i in range(edge_data["nodes"].size()): | |
| 85 | var nodeid = edge_data["nodes"][i] | |
| 86 | var radius = edge_data["radii"][i] | |
| 87 | node_radii_pairs[nodeid] = radius | |
| 88 | ||
| 89 | # Assign the nodes and radii to the hyperedge instance | |
| 90 | hyperedge_instance.nodes = node_radii_pairs | |
| 91 | ||
| 92 | # Assign a color based on the group | |
| 93 | var color = Color(randf(), randf(), randf(), 0.3) # Generate a random color | |
| 94 | hyperedge_instance.color = color | |
| 95 | ||
| 96 | # Set the group name (2021 or 2022) to the hyperedge instance for future use | |
| 97 | hyperedge_instance.group = edge_data["group"] | |
| 98 | ||
| 99 | hyperedge_instance.add_to_group("edges") | |
| 100 | hyperedge_instance.name = str(edge_id) | |
| 101 | ||
| 102 | # Set the width of the hyperedge based on the support value | |
| 103 | hyperedge_instance.width = pow(edges[edge_id]["support"], 0.35) | |
| 104 | ||
| 105 | # Add the hyperedge instance to the scene tree | |
| 106 | add_child(hyperedge_instance) | |
| 107 | ||
| 69 | 108 | |
| 70 | 109 | |
| 71 | 110 | func _ready(): |
| 72 | nodes = read_file("patterns.txt") | |
| 111 | # Read the file and get both nodes and edges | |
| 112 | var file_data = read_file("patterns.txt") | |
| 113 | # Extract nodes and edges from the file data | |
| 114 | var nodes = file_data["nodes"] | |
| 115 | var edges = file_data["edges"] | |
| 73 | 116 | create_nodes(nodes) |
| 74 | populate_edge_data() | |
| 75 | apply_force_directed_layout(10,150.0,1000.0) | |
| 117 | populate_edge_data(file_data) | |
| 118 | apply_force_directed_layout(10, 150.0, 900.0) | |
| 119 | calculate_centrality_and_resize_nodes(file_data) | |
| 120 | #apply_hyperedge_constrained_layout() | |
| 76 | 121 | #apply_circular_layout(500) |
| 77 | 122 | pass # Replace with function body. |
| 78 | 123 | |
| 124 | ||
| 79 | 125 | func read_file(path): |
| 80 | 126 | var file = FileAccess.open(path, FileAccess.READ) |
| 81 | 127 | var nodes = {} # Dictionary to hold all nodes |
| 82 | var edge_id = 0 # Unique identifier for each edge, can use line number as well | |
| 128 | var edges = {} # Dictionary to hold all edges | |
| 129 | var edge_id = 0 # Unique identifier for each edge | |
| 83 | 130 | |
| 84 | 131 | while not file.eof_reached(): |
| 85 | 132 | var line = file.get_line().strip_edges() |
| 86 | 133 | if line.find("#SUP:") == -1: |
| 87 | continue # Skip this line | |
| 134 | continue # Skip this line if no support value | |
| 135 | ||
| 88 | 136 | var parts = line.split(" #SUP: ") |
| 89 | var node_ids = parts[0].split(" ") | |
| 90 | var support = int(parts[1]) | |
| 137 | var node_ids = parts[0].split(" ") # Nodes involved in this edge | |
| 138 | ||
| 139 | # Extract support and group | |
| 140 | var supp_group_info = parts[1].split(" #GROUP ") | |
| 141 | var support = int(supp_group_info[0]) | |
| 142 | var group = supp_group_info[1] | |
| 91 | 143 | |
| 92 | 144 | # Process each node ID in the line |
| 93 | 145 | for node_id in node_ids: |
| 94 | var id = node_id | |
| 95 | if not nodes.has(id): | |
| 96 | nodes[id] = NodeData.new() | |
| 97 | # Always append the edge_id to the node's edges array | |
| 98 | nodes[id].edges.append(edge_id) | |
| 99 | # Update the support value | |
| 100 | nodes[id].support = max(nodes[id].support, support) | |
| 146 | if not nodes.has(node_id): | |
| 147 | nodes[node_id] = NodeData.new() # Assuming NodeData is a class for node info | |
| 148 | ||
| 149 | # Add this edge to the node's edge list | |
| 150 | nodes[node_id].edges[edge_id] = support | |
| 151 | ||
| 152 | # Store edge information with group and support in edges dictionary | |
| 153 | edges[edge_id] = { | |
| 154 | "nodes": node_ids, | |
| 155 | "support": support, | |
| 156 | "radii" : [], | |
| 157 | "group": group | |
| 158 | } | |
| 101 | 159 | |
| 102 | 160 | edge_id += 1 # Increment edge ID for the next line |
| 103 | 161 | |
| 104 | 162 | file.close() |
| 105 | return nodes | |
| 163 | ||
| 164 | # Return both nodes and edges in a single dictionary | |
| 165 | return {"nodes": nodes, "edges": edges} | |
| 106 | 166 | |
| 107 | 167 | |
| 108 | 168 | |
| … | … | @@ -110,6 +170,51 @@ func read_file(path): |
| 110 | 170 | func _process(delta): |
| 111 | 171 | pass |
| 112 | 172 | |
| 173 | ||
| 174 | func apply_hyperedge_constrained_layout(): | |
| 175 | var node_positions = {} # Dictionary to store node positions temporarily | |
| 176 | var edge_bounds = {} # Dictionary to store edge bounds (min and max positions) | |
| 177 | ||
| 178 | # Initialize node positions | |
| 179 | for node in get_children(): | |
| 180 | if node.is_in_group("nodes"): # Assuming your nodes are added to a "nodes" group | |
| 181 | node_positions[node.node_id] = node.position | |
| 182 | ||
| 183 | # Calculate edge bounds based on nodes in the edge | |
| 184 | for edge_id in edges: | |
| 185 | var edge_nodes = edges[edge_id]["nodes"] | |
| 186 | var min_x = INF | |
| 187 | var min_y = INF | |
| 188 | var max_x = -INF | |
| 189 | var max_y = -INF | |
| 190 | ||
| 191 | for node_id in edge_nodes: | |
| 192 | var pos = node_positions[node_id] | |
| 193 | min_x = min(min_x, pos.x) | |
| 194 | min_y = min(min_y, pos.y) | |
| 195 | max_x = max(max_x, pos.x) | |
| 196 | max_y = max(max_y, pos.y) | |
| 197 | ||
| 198 | edge_bounds[edge_id] = Rect2(Vector2(min_x, min_y), Vector2(max_x - min_x, max_y - min_y)) | |
| 199 | ||
| 200 | # Reposition nodes to ensure they are outside of edges they don't belong to | |
| 201 | for node_id in node_positions.keys(): | |
| 202 | var node_pos = node_positions[node_id] | |
| 203 | for edge_id in edges: | |
| 204 | var edge_nodes = edges[edge_id]["nodes"] | |
| 205 | if node_id not in edge_nodes: | |
| 206 | var bounds = edge_bounds[edge_id] | |
| 207 | if bounds.has_point(node_pos): | |
| 208 | # Move the node outside the edge bounds | |
| 209 | var direction = (node_pos - bounds.position).normalized() | |
| 210 | node_positions[node_id] = bounds.position + bounds.size * direction | |
| 211 | ||
| 212 | # Update actual node positions | |
| 213 | for node in get_children(): | |
| 214 | if node.is_in_group("nodes"): | |
| 215 | node.position = node_positions[node.node_id] | |
| 216 | ||
| 217 | ||
| 113 | 218 | func apply_force_directed_layout(iterations: int, repulsion_force: float, spring_length: float): |
| 114 | 219 | var node_positions = {} # Dictionary to store node positions temporarily |
| 115 | 220 | var forces = {} # Dictionary to store forces applied on nodes |
| … | … | @@ -152,6 +257,9 @@ func apply_force_directed_layout(iterations: int, repulsion_force: float, spring |
| 152 | 257 | node.position = node_positions[node.node_id] |
| 153 | 258 | |
| 154 | 259 | |
| 260 | ||
| 261 | ||
| 262 | ||
| 155 | 263 | func apply_circular_layout(radius: float): |
| 156 | 264 | var center = get_viewport_rect().size / 2 |
| 157 | 265 | var nodes = [] # List to store nodes |
| … | … | @@ -166,3 +274,31 @@ func apply_circular_layout(radius: float): |
| 166 | 274 | var x = center.x + radius * cos(angle) |
| 167 | 275 | var y = center.y + radius * sin(angle) |
| 168 | 276 | node.position = Vector2(x, y) |
| 277 | ||
| 278 | ||
| 279 | func calculate_centrality_and_resize_nodes(file_data): | |
| 280 | var nodes = file_data["nodes"] | |
| 281 | var max_centrality = 0 | |
| 282 | var centrality_scores = {} | |
| 283 | ||
| 284 | # Calculate centrality (degree centrality in this case) | |
| 285 | print(nodes) | |
| 286 | for node_id in nodes: | |
| 287 | var degree = nodes[node_id].edges.size() | |
| 288 | centrality_scores[node_id] = degree | |
| 289 | max_centrality = max(max_centrality, degree) | |
| 290 | print(centrality_scores) | |
| 291 | # Resize nodes based on centrality | |
| 292 | for node_id in nodes: | |
| 293 | var node_instance = get_node(str(node_id)) | |
| 294 | if node_instance: | |
| 295 | var normalized_centrality = float(centrality_scores[node_id]) / max_centrality | |
| 296 | var size_scale = lerp(0.5, 2.0, normalized_centrality) | |
| 297 | node_instance.update_size(size_scale, normalized_centrality) | |
| 298 | ||
| 299 | # Now update all edge positions | |
| 300 | for edge in get_tree().get_nodes_in_group("edges"): | |
| 301 | edge.update_position() | |
| 302 | ||
| 303 | ||
| 304 |
| … | … | @@ -0,0 +1,15 @@ |
| 1 | Climate_Change Evidence-Based_Policy #SUP: 4035 #GROUP 2021 | |
| 2 | Energy_and_Extractives Climate_Change Climate_Change_Mitigation Short-lived_Climate_Pollutants_(SLCPs) Methane #SUP: 4059 #GROUP 2021 | |
| 3 | Climate_Change Environment_and_Natural_Resources Climate_Change_Mitigation #SUP: 3877 #GROUP 2021 | |
| 4 | Energy_and_Extractives Climate_Change Oil_and_Gas_Policy,_Strategy_and_Institutions #SUP: 3984 #GROUP 2021 | |
| 5 | Health_Emergency_Preparedness_and_Disaster_Response Energy_and_Extractives Oil_and_Gas_Policy,_Strategy_and_Institutions #SUP: 4478 #GROUP 2021 | |
| 6 | Energy_and_Extractives Environment_and_Natural_Resources Forests #SUP: 4586 #GROUP 2021 | |
| 7 | Health_Emergency_Preparedness_and_Disaster_Response Natural_Resource_Management_ #SUP: 4940 #GROUP 2021 | |
| 8 | Climate_Change Investment_Climate #SUP: 4109 #GROUP 2021 | |
| 9 | Health_Emergency_Preparedness_and_Disaster_Response Energy_and_Extractives Climate_Change #SUP: 4497 #GROUP 2021 | |
| 10 | Climate_Change Climate_Change_Mitigation Short-lived_Climate_Pollutants_(SLCPs) Methane #SUP: 2611 #GROUP 2022 | |
| 11 | Energy_and_Extractives Energy_Efficiency #SUP: 3163 #GROUP 2022 | |
| 12 | Evidence-Based_Policy #SUP: 5238 #GROUP 2022 | |
| 13 | Energy_and_Extractives Oil_and_Gas_Policy,_Strategy_and_Institutions #SUP: 14501 #GROUP 2022 | |
| 14 | Health_Emergency_Preparedness_and_Disaster_Response Climate_Change #SUP: 6731 #GROUP 2022 | |
| 15 | Health_Emergency_Preparedness_and_Disaster_Response Energy_and_Extractives #SUP: 7720 #GROUP 2022 |
| … | … | @@ -1,33 +1,6 @@ |
| 1 | Environment_and_Natural_Resources Pollution_Management #SUP: 1 | |
| 2 | Climate_Change Climate_Science #SUP: 1 | |
| 3 | Energy_and_Extractives Renewable_Energy Energy_Efficiency #SUP: 1 | |
| 4 | Energy_and_Extractives Climate_Change Renewable_Energy Energy_Efficiency #SUP: 1 | |
| 5 | Energy_and_Extractives Climate_Change Energy_Efficiency #SUP: 1 | |
| 6 | Energy_and_Extractives Energy_Efficiency #SUP: 1 | |
| 7 | Climate_Change Climate_Finance #SUP: 1 | |
| 8 | Energy_and_Extractives Climate_Change Climate_Change_Mitigation Short-lived_Climate_Pollutants_(SLCPs) Methane Oil_and_Gas_Policy,_Strategy_and_Institutions #SUP: 1 | |
| 9 | Energy_and_Extractives Climate_Change Climate_Change_Mitigation Oil_and_Gas_Policy,_Strategy_and_Institutions #SUP: 1 | |
| 10 | Energy_and_Extractives Renewable_Energy Oil_and_Gas_Policy,_Strategy_and_Institutions #SUP: 1 | |
| 11 | Energy_and_Extractives Climate_Change Renewable_Energy Oil_and_Gas_Policy,_Strategy_and_Institutions #SUP: 1 | |
| 12 | Energy_and_Extractives Climate_Change Oil_and_Gas_Policy,_Strategy_and_Institutions #SUP: 1 | |
| 13 | Energy_and_Extractives Oil_and_Gas_Policy,_Strategy_and_Institutions #SUP: 1 | |
| 14 | Environment_and_Natural_Resources Forests #SUP: 1 | |
| 15 | Climate_Change Environment_and_Natural_Resources Forests #SUP: 1 | |
| 16 | Energy_and_Extractives Climate_Change Renewable_Energy Climate_Change_Mitigation Short-lived_Climate_Pollutants_(SLCPs) Methane #SUP: 1 | |
| 17 | Climate_Change Climate_Change_Mitigation Short-lived_Climate_Pollutants_(SLCPs) Methane #SUP: 1 | |
| 18 | Energy_and_Extractives Climate_Change Climate_Change_Mitigation Short-lived_Climate_Pollutants_(SLCPs) Methane #SUP: 1 | |
| 19 | Energy_and_Extractives Climate_Change Renewable_Energy Climate_Change_Mitigation Short-lived_Climate_Pollutants_(SLCPs) #SUP: 1 | |
| 20 | Climate_Change Climate_Change_Mitigation Short-lived_Climate_Pollutants_(SLCPs) #SUP: 1 | |
| 21 | Energy_and_Extractives Climate_Change Climate_Change_Mitigation Short-lived_Climate_Pollutants_(SLCPs) #SUP: 1 | |
| 22 | Energy_and_Extractives Climate_Change Renewable_Energy Climate_Change_Mitigation #SUP: 1 | |
| 23 | Climate_Change Climate_Change_Mitigation #SUP: 1 | |
| 24 | Energy_and_Extractives Climate_Change Climate_Change_Mitigation #SUP: 1 | |
| 25 | Environment_and_Natural_Resources Natural_Resource_Management_ #SUP: 1 | |
| 26 | Climate_Change Natural_Resource_Management_ #SUP: 1 | |
| 27 | Energy_and_Extractives Renewable_Energy #SUP: 1 | |
| 28 | Energy_and_Extractives Climate_Change Renewable_Energy #SUP: 1 | |
| 29 | Environment_and_Natural_Resources Investment_Climate #SUP: 1 | |
| 30 | Climate_Change Environment_and_Natural_Resources #SUP: 1 | |
| 31 | Energy_and_Extractives Environment_and_Natural_Resources #SUP: 1 | |
| 32 | Energy_and_Extractives Health_Emergency_Preparedness_and_Disaster_Response #SUP: 1 | |
| 33 | Energy_and_Extractives Climate_Change #SUP: 1 | |
| 1 | Environment_and_Natural_Resources Pollution_Management #SUP: 2662 #GROUP 2022 | |
| 2 | Energy_and_Extractives Energy_Efficiency #SUP: 3163 #GROUP 2022 | |
| 3 | Health_Emergency_Preparedness_and_Disaster_Response Energy_and_Extractives #SUP: 7720 #GROUP 2022 | |
| 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 | |
| 6 | Energy_and_Extractives Health_Emergency_Preparedness_and_Disaster_Response #SUP: 5647 #GROUP 2023 |
| … | … | @@ -15,6 +15,18 @@ run/main_scene="res://main.tscn" |
| 15 | 15 | config/features=PackedStringArray("4.2", "Forward Plus") |
| 16 | 16 | config/icon="res://icon.svg" |
| 17 | 17 | |
| 18 | [autoload] | |
| 19 | ||
| 20 | Global="*res://Global.gd" | |
| 21 | ||
| 22 | [debug] | |
| 23 | ||
| 24 | settings/stdout/print_fps=true | |
| 25 | ||
| 26 | [display] | |
| 27 | ||
| 28 | window/vsync/vsync_mode=0 | |
| 29 | ||
| 18 | 30 | [dotnet] |
| 19 | 31 | |
| 20 | 32 | project/assembly_name="Hypergodot" |
| … | … | @@ -30,3 +42,11 @@ left_click={ |
| 30 | 42 | "events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null) |
| 31 | 43 | ] |
| 32 | 44 | } |
| 45 | ||
| 46 | [rendering] | |
| 47 | ||
| 48 | anti_aliasing/screen_space_roughness_limiter/amount=0.55 | |
| 49 | anti_aliasing/quality/msaa_2d=3 | |
| 50 | anti_aliasing/quality/msaa_3d=3 | |
| 51 | anti_aliasing/quality/screen_space_aa=1 | |
| 52 | anti_aliasing/quality/use_taa=true |