kececilayout 0.2.1__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.
- kececilayout/__init__.py +37 -0
- kececilayout/_version.py +9 -0
- kececilayout/kececi_layout.py +1021 -0
- kececilayout-0.2.1.dist-info/METADATA +723 -0
- kececilayout-0.2.1.dist-info/RECORD +8 -0
- kececilayout-0.2.1.dist-info/WHEEL +5 -0
- kececilayout-0.2.1.dist-info/licenses/LICENSE +21 -0
- kececilayout-0.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1021 @@
|
|
|
1
|
+
# kececilayout/kececi_layout.py
|
|
2
|
+
import itertools # Graphillion için eklendi
|
|
3
|
+
import numpy as np # rustworkx
|
|
4
|
+
import math
|
|
5
|
+
import networkx as nx
|
|
6
|
+
import rustworkx as rx
|
|
7
|
+
import igraph as ig
|
|
8
|
+
import networkit as nk
|
|
9
|
+
import graphillion as gg
|
|
10
|
+
import random
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Gerekli olabilecek kütüphane importları (type hinting veya isinstance için)
|
|
14
|
+
try:
|
|
15
|
+
import networkx as nx
|
|
16
|
+
except ImportError:
|
|
17
|
+
nx = None # Yoksa None ata
|
|
18
|
+
try:
|
|
19
|
+
import rustworkx as rx
|
|
20
|
+
except ImportError:
|
|
21
|
+
rx = None
|
|
22
|
+
try:
|
|
23
|
+
import igraph as ig
|
|
24
|
+
except ImportError:
|
|
25
|
+
ig = None
|
|
26
|
+
try:
|
|
27
|
+
import networkit as nk
|
|
28
|
+
except ImportError:
|
|
29
|
+
nk = None
|
|
30
|
+
try:
|
|
31
|
+
import graphillion as gg # Graphillion importu eklendi
|
|
32
|
+
except ImportError:
|
|
33
|
+
gg = None
|
|
34
|
+
|
|
35
|
+
def find_max_node_id(edges):
|
|
36
|
+
"""Verilen kenar listesindeki en büyük düğüm ID'sini bulur."""
|
|
37
|
+
if not edges:
|
|
38
|
+
return 0
|
|
39
|
+
try:
|
|
40
|
+
# Tüm düğüm ID'lerini tek bir kümede topla ve en büyüğünü bul
|
|
41
|
+
all_nodes = set(itertools.chain.from_iterable(edges))
|
|
42
|
+
return max(all_nodes) if all_nodes else 0
|
|
43
|
+
except TypeError: # Eğer kenarlar (node, node) formatında değilse
|
|
44
|
+
print("Uyarı: Kenar formatı beklenenden farklı, max node ID 0 varsayıldı.")
|
|
45
|
+
return 0
|
|
46
|
+
|
|
47
|
+
def kececi_layout_v4(graph, primary_spacing=1.0, secondary_spacing=1.0,
|
|
48
|
+
primary_direction='top-down', secondary_start='right'):
|
|
49
|
+
"""
|
|
50
|
+
Keçeci Layout v4 - Graf düğümlerine sıralı-zigzag yerleşimi sağlar.
|
|
51
|
+
NetworkX, Rustworkx, igraph, Networkit ve Graphillion grafikleriyle çalışır.
|
|
52
|
+
|
|
53
|
+
Parametreler:
|
|
54
|
+
-------------
|
|
55
|
+
graph : Kütüphaneye özel graf nesnesi
|
|
56
|
+
NetworkX, Rustworkx, igraph, Networkit veya Graphillion nesnesi.
|
|
57
|
+
Graphillion için bir GraphSet nesnesi beklenir.
|
|
58
|
+
... (diğer parametreler) ...
|
|
59
|
+
|
|
60
|
+
Dönüş:
|
|
61
|
+
------
|
|
62
|
+
dict[node_identifier, tuple[float, float]]
|
|
63
|
+
Her düğümün koordinatını içeren sözlük. Anahtarlar kütüphaneye
|
|
64
|
+
göre değişir (NX: node obj/id, RW/NK/igraph: int index, GG: 1-based int index).
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
nodes = None
|
|
68
|
+
|
|
69
|
+
# Kütüphane Tespiti ve Düğüm Listesi Çıkarımı
|
|
70
|
+
# isinstance kullanmak hasattr'dan daha güvenilirdir.
|
|
71
|
+
# Önce daha spesifik tipleri kontrol etmek iyi olabilir.
|
|
72
|
+
|
|
73
|
+
if gg and isinstance(graph, gg.GraphSet): # Graphillion kontrolü EKLENDİ
|
|
74
|
+
edges = graph.universe()
|
|
75
|
+
max_node_id = find_max_node_id(edges)
|
|
76
|
+
if max_node_id > 0:
|
|
77
|
+
nodes = list(range(1, max_node_id + 1)) # 1-tabanlı indeksleme
|
|
78
|
+
else:
|
|
79
|
+
nodes = [] # Boş evren
|
|
80
|
+
print(f"DEBUG: Graphillion tespit edildi. Düğümler (1..{max_node_id}): {nodes[:10]}...") # Debug mesajı
|
|
81
|
+
|
|
82
|
+
elif ig and isinstance(graph, ig.Graph): # igraph
|
|
83
|
+
nodes = sorted([v.index for v in graph.vs]) # 0-tabanlı indeksleme
|
|
84
|
+
print(f"DEBUG: igraph tespit edildi. Düğümler (0..{len(nodes)-1}): {nodes[:10]}...")
|
|
85
|
+
|
|
86
|
+
elif nk and isinstance(graph, nk.graph.Graph): # Networkit
|
|
87
|
+
try:
|
|
88
|
+
# iterNodes genellikle 0..N-1 verir ama garanti değil
|
|
89
|
+
nodes = sorted(list(graph.iterNodes()))
|
|
90
|
+
except Exception:
|
|
91
|
+
nodes = list(range(graph.numberOfNodes()))
|
|
92
|
+
print(f"DEBUG: Networkit tespit edildi. Düğümler: {nodes[:10]}...")
|
|
93
|
+
|
|
94
|
+
elif rx and isinstance(graph, (rx.PyGraph, rx.PyDiGraph)): # Rustworkx (hem yönlü hem yönsüz)
|
|
95
|
+
nodes = sorted(graph.node_indices()) # 0-tabanlı indeksleme
|
|
96
|
+
print(f"DEBUG: Rustworkx tespit edildi. Düğümler (0..{len(nodes)-1}): {nodes[:10]}...")
|
|
97
|
+
|
|
98
|
+
elif nx and isinstance(graph, (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)): # NetworkX
|
|
99
|
+
try:
|
|
100
|
+
# Düğümler sıralanabilirse sırala (genelde int/str)
|
|
101
|
+
nodes = sorted(list(graph.nodes()))
|
|
102
|
+
except TypeError:
|
|
103
|
+
# Sıralanamayan düğüm tipleri varsa (örn. tuple, nesne) sırasız al
|
|
104
|
+
nodes = list(graph.nodes())
|
|
105
|
+
print(f"DEBUG: NetworkX tespit edildi. Düğümler: {nodes[:10]}...")
|
|
106
|
+
|
|
107
|
+
else:
|
|
108
|
+
# Desteklenmeyen tip veya kütüphane kurulu değilse
|
|
109
|
+
supported_types = []
|
|
110
|
+
if nx: supported_types.append("NetworkX")
|
|
111
|
+
if rx: supported_types.append("Rustworkx")
|
|
112
|
+
if ig: supported_types.append("igraph")
|
|
113
|
+
if nk: supported_types.append("Networkit")
|
|
114
|
+
if gg: supported_types.append("Graphillion.GraphSet")
|
|
115
|
+
raise TypeError(f"Unsupported graph type: {type(graph)}. Desteklenen türler: {', '.join(supported_types)}")
|
|
116
|
+
|
|
117
|
+
# ----- Buradan sonrası tüm kütüphaneler için ortak -----
|
|
118
|
+
|
|
119
|
+
num_nodes = len(nodes)
|
|
120
|
+
if num_nodes == 0:
|
|
121
|
+
return {} # Boş graf için boş sözlük döndür
|
|
122
|
+
|
|
123
|
+
pos = {} # Sonuç sözlüğü
|
|
124
|
+
is_vertical = primary_direction in ['top-down', 'bottom-up']
|
|
125
|
+
is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
|
|
126
|
+
|
|
127
|
+
# Parametre kontrolleri
|
|
128
|
+
if not (is_vertical or is_horizontal):
|
|
129
|
+
raise ValueError(f"Invalid primary_direction: {primary_direction}")
|
|
130
|
+
if is_vertical and secondary_start not in ['right', 'left']:
|
|
131
|
+
raise ValueError(f"Invalid secondary_start ('{secondary_start}') for vertical primary_direction ('{primary_direction}'). Use 'right' or 'left'.")
|
|
132
|
+
if is_horizontal and secondary_start not in ['up', 'down']:
|
|
133
|
+
raise ValueError(f"Invalid secondary_start ('{secondary_start}') for horizontal primary_direction ('{primary_direction}'). Use 'up' or 'down'.")
|
|
134
|
+
|
|
135
|
+
# Ana döngü - Düğümleri sıralı indekslerine göre yerleştirir,
|
|
136
|
+
# sözlüğe ise gerçek düğüm ID/indeks/nesnesini anahtar olarak kullanır.
|
|
137
|
+
for i, node_id in enumerate(nodes):
|
|
138
|
+
# i: Düğümün sıralı listedeki 0-tabanlı indeksi (0, 1, 2, ...) - Yerleşim için kullanılır
|
|
139
|
+
# node_id: Gerçek düğüm kimliği/indeksi - Sonuç sözlüğünün anahtarı
|
|
140
|
+
|
|
141
|
+
# 1. Ana eksen koordinatını hesapla
|
|
142
|
+
if primary_direction == 'top-down':
|
|
143
|
+
primary_coord = i * -primary_spacing; secondary_axis = 'x'
|
|
144
|
+
elif primary_direction == 'bottom-up':
|
|
145
|
+
primary_coord = i * primary_spacing; secondary_axis = 'x'
|
|
146
|
+
elif primary_direction == 'left-to-right':
|
|
147
|
+
primary_coord = i * primary_spacing; secondary_axis = 'y'
|
|
148
|
+
else: # right-to-left
|
|
149
|
+
primary_coord = i * -primary_spacing; secondary_axis = 'y'
|
|
150
|
+
|
|
151
|
+
# 2. Yan eksen ofsetini hesapla (zigzag)
|
|
152
|
+
if i == 0: secondary_offset_multiplier = 0.0
|
|
153
|
+
else:
|
|
154
|
+
start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
|
|
155
|
+
magnitude = math.ceil(i / 2.0)
|
|
156
|
+
side = 1 if i % 2 != 0 else -1
|
|
157
|
+
secondary_offset_multiplier = start_mult * magnitude * side
|
|
158
|
+
secondary_coord = secondary_offset_multiplier * secondary_spacing
|
|
159
|
+
|
|
160
|
+
# 3. (x, y) koordinatlarını ata
|
|
161
|
+
if secondary_axis == 'x': x, y = secondary_coord, primary_coord
|
|
162
|
+
else: x, y = primary_coord, secondary_coord
|
|
163
|
+
|
|
164
|
+
# Sonuç sözlüğüne ekle
|
|
165
|
+
pos[node_id] = (x, y)
|
|
166
|
+
|
|
167
|
+
return pos
|
|
168
|
+
|
|
169
|
+
def kececi_layout(graph, primary_spacing=1.0, secondary_spacing=1.0,
|
|
170
|
+
primary_direction='top-down', secondary_start='right'):
|
|
171
|
+
"""
|
|
172
|
+
Keçeci Layout v4 - Graf düğümlerine sıralı-zigzag yerleşimi sağlar.
|
|
173
|
+
NetworkX, Rustworkx, igraph, Networkit ve Graphillion grafikleriyle çalışır.
|
|
174
|
+
|
|
175
|
+
Parametreler:
|
|
176
|
+
-------------
|
|
177
|
+
graph : Kütüphaneye özel graf nesnesi
|
|
178
|
+
NetworkX, Rustworkx, igraph, Networkit veya Graphillion nesnesi.
|
|
179
|
+
Graphillion için bir GraphSet nesnesi beklenir.
|
|
180
|
+
... (diğer parametreler) ...
|
|
181
|
+
|
|
182
|
+
Dönüş:
|
|
183
|
+
------
|
|
184
|
+
dict[node_identifier, tuple[float, float]]
|
|
185
|
+
Her düğümün koordinatını içeren sözlük. Anahtarlar kütüphaneye
|
|
186
|
+
göre değişir (NX: node obj/id, RW/NK/igraph: int index, GG: 1-based int index).
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
nodes = None
|
|
190
|
+
|
|
191
|
+
# Kütüphane Tespiti ve Düğüm Listesi Çıkarımı
|
|
192
|
+
# isinstance kullanmak hasattr'dan daha güvenilirdir.
|
|
193
|
+
# Önce daha spesifik tipleri kontrol etmek iyi olabilir.
|
|
194
|
+
|
|
195
|
+
if gg and isinstance(graph, gg.GraphSet): # Graphillion kontrolü EKLENDİ
|
|
196
|
+
edges = graph.universe()
|
|
197
|
+
max_node_id = find_max_node_id(edges)
|
|
198
|
+
if max_node_id > 0:
|
|
199
|
+
nodes = list(range(1, max_node_id + 1)) # 1-tabanlı indeksleme
|
|
200
|
+
else:
|
|
201
|
+
nodes = [] # Boş evren
|
|
202
|
+
print(f"DEBUG: Graphillion tespit edildi. Düğümler (1..{max_node_id}): {nodes[:10]}...") # Debug mesajı
|
|
203
|
+
|
|
204
|
+
elif ig and isinstance(graph, ig.Graph): # igraph
|
|
205
|
+
nodes = sorted([v.index for v in graph.vs]) # 0-tabanlı indeksleme
|
|
206
|
+
print(f"DEBUG: igraph tespit edildi. Düğümler (0..{len(nodes)-1}): {nodes[:10]}...")
|
|
207
|
+
|
|
208
|
+
elif nk and isinstance(graph, nk.graph.Graph): # Networkit
|
|
209
|
+
try:
|
|
210
|
+
# iterNodes genellikle 0..N-1 verir ama garanti değil
|
|
211
|
+
nodes = sorted(list(graph.iterNodes()))
|
|
212
|
+
except Exception:
|
|
213
|
+
nodes = list(range(graph.numberOfNodes()))
|
|
214
|
+
print(f"DEBUG: Networkit tespit edildi. Düğümler: {nodes[:10]}...")
|
|
215
|
+
|
|
216
|
+
elif rx and isinstance(graph, (rx.PyGraph, rx.PyDiGraph)): # Rustworkx (hem yönlü hem yönsüz)
|
|
217
|
+
nodes = sorted(graph.node_indices()) # 0-tabanlı indeksleme
|
|
218
|
+
print(f"DEBUG: Rustworkx tespit edildi. Düğümler (0..{len(nodes)-1}): {nodes[:10]}...")
|
|
219
|
+
|
|
220
|
+
elif nx and isinstance(graph, (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)): # NetworkX
|
|
221
|
+
try:
|
|
222
|
+
# Düğümler sıralanabilirse sırala (genelde int/str)
|
|
223
|
+
nodes = sorted(list(graph.nodes()))
|
|
224
|
+
except TypeError:
|
|
225
|
+
# Sıralanamayan düğüm tipleri varsa (örn. tuple, nesne) sırasız al
|
|
226
|
+
nodes = list(graph.nodes())
|
|
227
|
+
print(f"DEBUG: NetworkX tespit edildi. Düğümler: {nodes[:10]}...")
|
|
228
|
+
|
|
229
|
+
else:
|
|
230
|
+
# Desteklenmeyen tip veya kütüphane kurulu değilse
|
|
231
|
+
supported_types = []
|
|
232
|
+
if nx: supported_types.append("NetworkX")
|
|
233
|
+
if rx: supported_types.append("Rustworkx")
|
|
234
|
+
if ig: supported_types.append("igraph")
|
|
235
|
+
if nk: supported_types.append("Networkit")
|
|
236
|
+
if gg: supported_types.append("Graphillion.GraphSet")
|
|
237
|
+
raise TypeError(f"Unsupported graph type: {type(graph)}. Desteklenen türler: {', '.join(supported_types)}")
|
|
238
|
+
|
|
239
|
+
# ----- Buradan sonrası tüm kütüphaneler için ortak -----
|
|
240
|
+
|
|
241
|
+
num_nodes = len(nodes)
|
|
242
|
+
if num_nodes == 0:
|
|
243
|
+
return {} # Boş graf için boş sözlük döndür
|
|
244
|
+
|
|
245
|
+
pos = {} # Sonuç sözlüğü
|
|
246
|
+
is_vertical = primary_direction in ['top-down', 'bottom-up']
|
|
247
|
+
is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
|
|
248
|
+
|
|
249
|
+
# Parametre kontrolleri
|
|
250
|
+
if not (is_vertical or is_horizontal):
|
|
251
|
+
raise ValueError(f"Invalid primary_direction: {primary_direction}")
|
|
252
|
+
if is_vertical and secondary_start not in ['right', 'left']:
|
|
253
|
+
raise ValueError(f"Invalid secondary_start ('{secondary_start}') for vertical primary_direction ('{primary_direction}'). Use 'right' or 'left'.")
|
|
254
|
+
if is_horizontal and secondary_start not in ['up', 'down']:
|
|
255
|
+
raise ValueError(f"Invalid secondary_start ('{secondary_start}') for horizontal primary_direction ('{primary_direction}'). Use 'up' or 'down'.")
|
|
256
|
+
|
|
257
|
+
# Ana döngü - Düğümleri sıralı indekslerine göre yerleştirir,
|
|
258
|
+
# sözlüğe ise gerçek düğüm ID/indeks/nesnesini anahtar olarak kullanır.
|
|
259
|
+
for i, node_id in enumerate(nodes):
|
|
260
|
+
# i: Düğümün sıralı listedeki 0-tabanlı indeksi (0, 1, 2, ...) - Yerleşim için kullanılır
|
|
261
|
+
# node_id: Gerçek düğüm kimliği/indeksi - Sonuç sözlüğünün anahtarı
|
|
262
|
+
|
|
263
|
+
# 1. Ana eksen koordinatını hesapla
|
|
264
|
+
if primary_direction == 'top-down':
|
|
265
|
+
primary_coord = i * -primary_spacing; secondary_axis = 'x'
|
|
266
|
+
elif primary_direction == 'bottom-up':
|
|
267
|
+
primary_coord = i * primary_spacing; secondary_axis = 'x'
|
|
268
|
+
elif primary_direction == 'left-to-right':
|
|
269
|
+
primary_coord = i * primary_spacing; secondary_axis = 'y'
|
|
270
|
+
else: # right-to-left
|
|
271
|
+
primary_coord = i * -primary_spacing; secondary_axis = 'y'
|
|
272
|
+
|
|
273
|
+
# 2. Yan eksen ofsetini hesapla (zigzag)
|
|
274
|
+
if i == 0: secondary_offset_multiplier = 0.0
|
|
275
|
+
else:
|
|
276
|
+
start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
|
|
277
|
+
magnitude = math.ceil(i / 2.0)
|
|
278
|
+
side = 1 if i % 2 != 0 else -1
|
|
279
|
+
secondary_offset_multiplier = start_mult * magnitude * side
|
|
280
|
+
secondary_coord = secondary_offset_multiplier * secondary_spacing
|
|
281
|
+
|
|
282
|
+
# 3. (x, y) koordinatlarını ata
|
|
283
|
+
if secondary_axis == 'x': x, y = secondary_coord, primary_coord
|
|
284
|
+
else: x, y = primary_coord, secondary_coord
|
|
285
|
+
|
|
286
|
+
# Sonuç sözlüğüne ekle
|
|
287
|
+
pos[node_id] = (x, y)
|
|
288
|
+
|
|
289
|
+
return pos
|
|
290
|
+
|
|
291
|
+
def kececi_layout_v4_nx(graph, primary_spacing=1.0, secondary_spacing=1.0,
|
|
292
|
+
primary_direction='top-down', secondary_start='right'):
|
|
293
|
+
"""
|
|
294
|
+
Genişletilmiş Keçeci Düzeni: Ana eksen boyunca ilerler, ikincil eksende artan şekilde sapar.
|
|
295
|
+
Düğümler ikincil eksende daha geniş bir alana yayılır.
|
|
296
|
+
"""
|
|
297
|
+
pos = {}
|
|
298
|
+
# NetworkX 2.x ve 3.x uyumluluğu için listeye çevirme
|
|
299
|
+
nodes = sorted(list(graph.nodes()))
|
|
300
|
+
num_nodes = len(nodes)
|
|
301
|
+
if num_nodes == 0: return {}
|
|
302
|
+
|
|
303
|
+
is_vertical = primary_direction in ['top-down', 'bottom-up']
|
|
304
|
+
is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
|
|
305
|
+
if not (is_vertical or is_horizontal): raise ValueError(f"Invalid primary_direction: {primary_direction}")
|
|
306
|
+
if is_vertical and secondary_start not in ['right', 'left']: raise ValueError(f"Invalid secondary_start for vertical: {secondary_start}")
|
|
307
|
+
if is_horizontal and secondary_start not in ['up', 'down']: raise ValueError(f"Invalid secondary_start for horizontal: {secondary_start}")
|
|
308
|
+
|
|
309
|
+
for i, node_id in enumerate(nodes):
|
|
310
|
+
# 1. Ana Eksen Koordinatını Hesapla
|
|
311
|
+
if primary_direction == 'top-down': primary_coord, secondary_axis = i * -primary_spacing, 'x'
|
|
312
|
+
elif primary_direction == 'bottom-up': primary_coord, secondary_axis = i * primary_spacing, 'x'
|
|
313
|
+
elif primary_direction == 'left-to-right': primary_coord, secondary_axis = i * primary_spacing, 'y'
|
|
314
|
+
else: # right-to-left
|
|
315
|
+
primary_coord, secondary_axis = i * -primary_spacing, 'y'
|
|
316
|
+
|
|
317
|
+
# 2. İkincil Eksen Koordinatını Hesapla (Genişletilmiş Sapma)
|
|
318
|
+
if i == 0:
|
|
319
|
+
secondary_offset_multiplier = 0.0
|
|
320
|
+
else:
|
|
321
|
+
# Sapma yönünü belirle (sağ/yukarı +1, sol/aşağı -1)
|
|
322
|
+
start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
|
|
323
|
+
# Sapma büyüklüğünü belirle (i arttıkça artar: 1, 1, 2, 2, 3, 3, ...)
|
|
324
|
+
magnitude = math.ceil(i / 2.0)
|
|
325
|
+
# Sapma tarafını belirle (tek i için pozitif, çift i için negatif)
|
|
326
|
+
side = 1 if i % 2 != 0 else -1
|
|
327
|
+
secondary_offset_multiplier = start_mult * magnitude * side
|
|
328
|
+
|
|
329
|
+
secondary_coord = secondary_offset_multiplier * secondary_spacing
|
|
330
|
+
|
|
331
|
+
# 3. (x, y) Koordinatlarını Ata
|
|
332
|
+
x, y = (secondary_coord, primary_coord) if secondary_axis == 'x' else (primary_coord, secondary_coord)
|
|
333
|
+
pos[node_id] = (x, y)
|
|
334
|
+
|
|
335
|
+
return pos
|
|
336
|
+
|
|
337
|
+
def kececi_layout_v4_networkx(graph, primary_spacing=1.0, secondary_spacing=1.0,
|
|
338
|
+
primary_direction='top-down', secondary_start='right'):
|
|
339
|
+
"""
|
|
340
|
+
Genişletilmiş Keçeci Düzeni: Ana eksen boyunca ilerler, ikincil eksende artan şekilde sapar.
|
|
341
|
+
Düğümler ikincil eksende daha geniş bir alana yayılır.
|
|
342
|
+
"""
|
|
343
|
+
pos = {}
|
|
344
|
+
# NetworkX 2.x ve 3.x uyumluluğu için listeye çevirme
|
|
345
|
+
nodes = sorted(list(graph.nodes()))
|
|
346
|
+
num_nodes = len(nodes)
|
|
347
|
+
if num_nodes == 0: return {}
|
|
348
|
+
|
|
349
|
+
is_vertical = primary_direction in ['top-down', 'bottom-up']
|
|
350
|
+
is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
|
|
351
|
+
if not (is_vertical or is_horizontal): raise ValueError(f"Invalid primary_direction: {primary_direction}")
|
|
352
|
+
if is_vertical and secondary_start not in ['right', 'left']: raise ValueError(f"Invalid secondary_start for vertical: {secondary_start}")
|
|
353
|
+
if is_horizontal and secondary_start not in ['up', 'down']: raise ValueError(f"Invalid secondary_start for horizontal: {secondary_start}")
|
|
354
|
+
|
|
355
|
+
for i, node_id in enumerate(nodes):
|
|
356
|
+
# 1. Ana Eksen Koordinatını Hesapla
|
|
357
|
+
if primary_direction == 'top-down': primary_coord, secondary_axis = i * -primary_spacing, 'x'
|
|
358
|
+
elif primary_direction == 'bottom-up': primary_coord, secondary_axis = i * primary_spacing, 'x'
|
|
359
|
+
elif primary_direction == 'left-to-right': primary_coord, secondary_axis = i * primary_spacing, 'y'
|
|
360
|
+
else: # right-to-left
|
|
361
|
+
primary_coord, secondary_axis = i * -primary_spacing, 'y'
|
|
362
|
+
|
|
363
|
+
# 2. İkincil Eksen Koordinatını Hesapla (Genişletilmiş Sapma)
|
|
364
|
+
if i == 0:
|
|
365
|
+
secondary_offset_multiplier = 0.0
|
|
366
|
+
else:
|
|
367
|
+
# Sapma yönünü belirle (sağ/yukarı +1, sol/aşağı -1)
|
|
368
|
+
start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
|
|
369
|
+
# Sapma büyüklüğünü belirle (i arttıkça artar: 1, 1, 2, 2, 3, 3, ...)
|
|
370
|
+
magnitude = math.ceil(i / 2.0)
|
|
371
|
+
# Sapma tarafını belirle (tek i için pozitif, çift i için negatif)
|
|
372
|
+
side = 1 if i % 2 != 0 else -1
|
|
373
|
+
secondary_offset_multiplier = start_mult * magnitude * side
|
|
374
|
+
|
|
375
|
+
secondary_coord = secondary_offset_multiplier * secondary_spacing
|
|
376
|
+
|
|
377
|
+
# 3. (x, y) Koordinatlarını Ata
|
|
378
|
+
x, y = (secondary_coord, primary_coord) if secondary_axis == 'x' else (primary_coord, secondary_coord)
|
|
379
|
+
pos[node_id] = (x, y)
|
|
380
|
+
|
|
381
|
+
return pos
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def kececi_layout_v4_ig(graph: ig.Graph, primary_spacing=1.0, secondary_spacing=1.0,
|
|
385
|
+
primary_direction='top-down', secondary_start='right'):
|
|
386
|
+
"""igraph.Graph nesnesi için Keçeci layout.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
graph: igraph.Graph nesnesi.
|
|
390
|
+
primary_spacing: Ana eksendeki düğümler arasındaki boşluk.
|
|
391
|
+
secondary_spacing: İkincil eksendeki ofset boşluğu.
|
|
392
|
+
primary_direction: Ana eksenin yönü ('top-down', 'bottom-up', 'left-to-right', 'right-to-left').
|
|
393
|
+
secondary_start: İkincil eksendeki ilk ofsetin yönü ('right', 'left', 'up', 'down').
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
Vertex ID'lerine göre sıralanmış koordinatların listesi (ör: [[x0,y0], [x1,y1], ...]).
|
|
397
|
+
"""
|
|
398
|
+
num_nodes = graph.vcount()
|
|
399
|
+
if num_nodes == 0:
|
|
400
|
+
return []
|
|
401
|
+
|
|
402
|
+
# Koordinat listesi oluştur (vertex ID'leri 0'dan N-1'e sıralı olacak şekilde)
|
|
403
|
+
pos_list = [[0.0, 0.0]] * num_nodes
|
|
404
|
+
# Vertex ID'leri zaten 0'dan N-1'e olduğu için doğrudan range kullanabiliriz
|
|
405
|
+
nodes = range(num_nodes) # Vertex ID'leri
|
|
406
|
+
|
|
407
|
+
is_vertical = primary_direction in ['top-down', 'bottom-up']
|
|
408
|
+
is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
|
|
409
|
+
|
|
410
|
+
if not (is_vertical or is_horizontal):
|
|
411
|
+
raise ValueError(f"Invalid primary_direction: {primary_direction}")
|
|
412
|
+
if is_vertical and secondary_start not in ['right', 'left']:
|
|
413
|
+
raise ValueError(f"Invalid secondary_start for vertical: {secondary_start}")
|
|
414
|
+
if is_horizontal and secondary_start not in ['up', 'down']:
|
|
415
|
+
raise ValueError(f"Invalid secondary_start for horizontal: {secondary_start}")
|
|
416
|
+
|
|
417
|
+
for i in nodes: # i burada vertex index'i (0, 1, 2...)
|
|
418
|
+
if primary_direction == 'top-down':
|
|
419
|
+
primary_coord, secondary_axis = i * -primary_spacing, 'x'
|
|
420
|
+
elif primary_direction == 'bottom-up':
|
|
421
|
+
primary_coord, secondary_axis = i * primary_spacing, 'x'
|
|
422
|
+
elif primary_direction == 'left-to-right':
|
|
423
|
+
primary_coord, secondary_axis = i * primary_spacing, 'y'
|
|
424
|
+
else: # right-to-left
|
|
425
|
+
primary_coord, secondary_axis = i * -primary_spacing, 'y'
|
|
426
|
+
|
|
427
|
+
if i == 0:
|
|
428
|
+
secondary_offset_multiplier = 0.0
|
|
429
|
+
else:
|
|
430
|
+
start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
|
|
431
|
+
magnitude = math.ceil(i / 2.0)
|
|
432
|
+
side = 1 if i % 2 != 0 else -1
|
|
433
|
+
secondary_offset_multiplier = start_mult * magnitude * side
|
|
434
|
+
|
|
435
|
+
secondary_coord = secondary_offset_multiplier * secondary_spacing
|
|
436
|
+
|
|
437
|
+
x, y = (secondary_coord, primary_coord) if secondary_axis == 'x' else (primary_coord, secondary_coord)
|
|
438
|
+
pos_list[i] = [x, y] # Listeye doğru index'e [x, y] olarak ekle
|
|
439
|
+
|
|
440
|
+
# igraph Layout nesnesi gibi davranması için basit bir nesne döndürelim
|
|
441
|
+
# veya doğrudan koordinat listesini kullanalım. Çizim fonksiyonu listeyi kabul eder.
|
|
442
|
+
# return ig.Layout(pos_list) # İsterseniz Layout nesnesi de döndürebilirsiniz
|
|
443
|
+
return pos_list # Doğrudan liste döndürmek en yaygın ve esnek yoldur
|
|
444
|
+
|
|
445
|
+
def kececi_layout_v4_igraph(graph: ig.Graph, primary_spacing=1.0, secondary_spacing=1.0,
|
|
446
|
+
primary_direction='top-down', secondary_start='right'):
|
|
447
|
+
"""igraph.Graph nesnesi için Keçeci layout.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
graph: igraph.Graph nesnesi.
|
|
451
|
+
primary_spacing: Ana eksendeki düğümler arasındaki boşluk.
|
|
452
|
+
secondary_spacing: İkincil eksendeki ofset boşluğu.
|
|
453
|
+
primary_direction: Ana eksenin yönü ('top-down', 'bottom-up', 'left-to-right', 'right-to-left').
|
|
454
|
+
secondary_start: İkincil eksendeki ilk ofsetin yönü ('right', 'left', 'up', 'down').
|
|
455
|
+
|
|
456
|
+
Returns:
|
|
457
|
+
Vertex ID'lerine göre sıralanmış koordinatların listesi (ör: [[x0,y0], [x1,y1], ...]).
|
|
458
|
+
"""
|
|
459
|
+
num_nodes = graph.vcount()
|
|
460
|
+
if num_nodes == 0:
|
|
461
|
+
return []
|
|
462
|
+
|
|
463
|
+
# Koordinat listesi oluştur (vertex ID'leri 0'dan N-1'e sıralı olacak şekilde)
|
|
464
|
+
pos_list = [[0.0, 0.0]] * num_nodes
|
|
465
|
+
# Vertex ID'leri zaten 0'dan N-1'e olduğu için doğrudan range kullanabiliriz
|
|
466
|
+
nodes = range(num_nodes) # Vertex ID'leri
|
|
467
|
+
|
|
468
|
+
is_vertical = primary_direction in ['top-down', 'bottom-up']
|
|
469
|
+
is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
|
|
470
|
+
|
|
471
|
+
if not (is_vertical or is_horizontal):
|
|
472
|
+
raise ValueError(f"Invalid primary_direction: {primary_direction}")
|
|
473
|
+
if is_vertical and secondary_start not in ['right', 'left']:
|
|
474
|
+
raise ValueError(f"Invalid secondary_start for vertical: {secondary_start}")
|
|
475
|
+
if is_horizontal and secondary_start not in ['up', 'down']:
|
|
476
|
+
raise ValueError(f"Invalid secondary_start for horizontal: {secondary_start}")
|
|
477
|
+
|
|
478
|
+
for i in nodes: # i burada vertex index'i (0, 1, 2...)
|
|
479
|
+
if primary_direction == 'top-down':
|
|
480
|
+
primary_coord, secondary_axis = i * -primary_spacing, 'x'
|
|
481
|
+
elif primary_direction == 'bottom-up':
|
|
482
|
+
primary_coord, secondary_axis = i * primary_spacing, 'x'
|
|
483
|
+
elif primary_direction == 'left-to-right':
|
|
484
|
+
primary_coord, secondary_axis = i * primary_spacing, 'y'
|
|
485
|
+
else: # right-to-left
|
|
486
|
+
primary_coord, secondary_axis = i * -primary_spacing, 'y'
|
|
487
|
+
|
|
488
|
+
if i == 0:
|
|
489
|
+
secondary_offset_multiplier = 0.0
|
|
490
|
+
else:
|
|
491
|
+
start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
|
|
492
|
+
magnitude = math.ceil(i / 2.0)
|
|
493
|
+
side = 1 if i % 2 != 0 else -1
|
|
494
|
+
secondary_offset_multiplier = start_mult * magnitude * side
|
|
495
|
+
|
|
496
|
+
secondary_coord = secondary_offset_multiplier * secondary_spacing
|
|
497
|
+
|
|
498
|
+
x, y = (secondary_coord, primary_coord) if secondary_axis == 'x' else (primary_coord, secondary_coord)
|
|
499
|
+
pos_list[i] = [x, y] # Listeye doğru index'e [x, y] olarak ekle
|
|
500
|
+
|
|
501
|
+
# igraph Layout nesnesi gibi davranması için basit bir nesne döndürelim
|
|
502
|
+
# veya doğrudan koordinat listesini kullanalım. Çizim fonksiyonu listeyi kabul eder.
|
|
503
|
+
# return ig.Layout(pos_list) # İsterseniz Layout nesnesi de döndürebilirsiniz
|
|
504
|
+
return pos_list # Doğrudan liste döndürmek en yaygın ve esnek yoldur
|
|
505
|
+
|
|
506
|
+
def kececi_layout_v4_nk(graph: nk.graph.Graph,
|
|
507
|
+
primary_spacing=1.0,
|
|
508
|
+
secondary_spacing=1.0,
|
|
509
|
+
primary_direction='top-down',
|
|
510
|
+
secondary_start='right'):
|
|
511
|
+
"""
|
|
512
|
+
Keçeci Layout v4 - Networkit graf düğümlerine sıralı-zigzag yerleşimi sağlar.
|
|
513
|
+
|
|
514
|
+
Parametreler:
|
|
515
|
+
-------------
|
|
516
|
+
graph : networkit.graph.Graph
|
|
517
|
+
Kenar ve düğüm bilgisi içeren Networkit graf nesnesi.
|
|
518
|
+
primary_spacing : float
|
|
519
|
+
Ana yön mesafesi.
|
|
520
|
+
secondary_spacing : float
|
|
521
|
+
Yan yön mesafesi.
|
|
522
|
+
primary_direction : str
|
|
523
|
+
'top-down', 'bottom-up', 'left-to-right', 'right-to-left'.
|
|
524
|
+
secondary_start : str
|
|
525
|
+
Başlangıç yönü ('right', 'left', 'up', 'down').
|
|
526
|
+
|
|
527
|
+
Dönüş:
|
|
528
|
+
------
|
|
529
|
+
dict[int, tuple[float, float]]
|
|
530
|
+
Her düğüm ID'sinin (Networkit'te genelde integer olur)
|
|
531
|
+
koordinatını içeren sözlük.
|
|
532
|
+
"""
|
|
533
|
+
|
|
534
|
+
# Networkit'te düğüm ID'leri genellikle 0'dan N-1'e sıralıdır,
|
|
535
|
+
# ancak garantiye almak için sıralı bir liste alalım.
|
|
536
|
+
# iterNodes() düğüm ID'lerini döndürür.
|
|
537
|
+
try:
|
|
538
|
+
# Networkit'te node ID'lerinin contiguous (0..n-1) olup olmadığını kontrol edebiliriz
|
|
539
|
+
# ama her zaman böyle olmayabilir. iterNodes en genel yöntem.
|
|
540
|
+
nodes = sorted(list(graph.iterNodes()))
|
|
541
|
+
except Exception as e:
|
|
542
|
+
print(f"Networkit düğüm listesi alınırken hata: {e}")
|
|
543
|
+
# Alternatif olarak, eğer ID'lerin 0'dan başladığı varsayılıyorsa:
|
|
544
|
+
# nodes = list(range(graph.numberOfNodes()))
|
|
545
|
+
# Ancak iterNodes daha güvenlidir.
|
|
546
|
+
return {} # Hata durumunda boş dön
|
|
547
|
+
|
|
548
|
+
num_nodes = len(nodes) # Veya graph.numberOfNodes()
|
|
549
|
+
if num_nodes == 0:
|
|
550
|
+
return {} # Boş graf için boş sözlük döndür
|
|
551
|
+
|
|
552
|
+
pos = {} # Sonuç sözlüğü
|
|
553
|
+
is_vertical = primary_direction in ['top-down', 'bottom-up']
|
|
554
|
+
is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
|
|
555
|
+
|
|
556
|
+
# Parametre kontrolleri
|
|
557
|
+
if not (is_vertical or is_horizontal):
|
|
558
|
+
raise ValueError(f"Invalid primary_direction: {primary_direction}")
|
|
559
|
+
if is_vertical and secondary_start not in ['right', 'left']:
|
|
560
|
+
raise ValueError(f"Invalid secondary_start ('{secondary_start}') for vertical primary_direction ('{primary_direction}'). Use 'right' or 'left'.")
|
|
561
|
+
if is_horizontal and secondary_start not in ['up', 'down']:
|
|
562
|
+
raise ValueError(f"Invalid secondary_start ('{secondary_start}') for horizontal primary_direction ('{primary_direction}'). Use 'up' or 'down'.")
|
|
563
|
+
|
|
564
|
+
# Ana döngü
|
|
565
|
+
for i, node_id in enumerate(nodes):
|
|
566
|
+
# i: Düğümün sıralı listedeki indeksi (0, 1, 2, ...) - Yerleşim için kullanılır
|
|
567
|
+
# node_id: Gerçek Networkit düğüm ID'si - Sonuç sözlüğünün anahtarı
|
|
568
|
+
|
|
569
|
+
# 1. Ana eksen koordinatını hesapla
|
|
570
|
+
if primary_direction == 'top-down':
|
|
571
|
+
primary_coord = i * -primary_spacing
|
|
572
|
+
secondary_axis = 'x'
|
|
573
|
+
elif primary_direction == 'bottom-up':
|
|
574
|
+
primary_coord = i * primary_spacing
|
|
575
|
+
secondary_axis = 'x'
|
|
576
|
+
elif primary_direction == 'left-to-right':
|
|
577
|
+
primary_coord = i * primary_spacing
|
|
578
|
+
secondary_axis = 'y'
|
|
579
|
+
else: # primary_direction == 'right-to-left'
|
|
580
|
+
primary_coord = i * -primary_spacing
|
|
581
|
+
secondary_axis = 'y'
|
|
582
|
+
|
|
583
|
+
# 2. Yan eksen ofsetini hesapla (zigzag)
|
|
584
|
+
if i == 0:
|
|
585
|
+
secondary_offset_multiplier = 0.0
|
|
586
|
+
else:
|
|
587
|
+
start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
|
|
588
|
+
magnitude = math.ceil(i / 2.0)
|
|
589
|
+
side = 1 if i % 2 != 0 else -1
|
|
590
|
+
secondary_offset_multiplier = start_mult * magnitude * side
|
|
591
|
+
|
|
592
|
+
secondary_coord = secondary_offset_multiplier * secondary_spacing
|
|
593
|
+
|
|
594
|
+
# 3. (x, y) koordinatlarını ata
|
|
595
|
+
if secondary_axis == 'x':
|
|
596
|
+
x, y = secondary_coord, primary_coord
|
|
597
|
+
else: # secondary_axis == 'y'
|
|
598
|
+
x, y = primary_coord, secondary_coord
|
|
599
|
+
|
|
600
|
+
# Sonuç sözlüğüne ekle: anahtar=düğüm ID, değer=(x, y) tuple'ı
|
|
601
|
+
pos[node_id] = (x, y)
|
|
602
|
+
|
|
603
|
+
return pos
|
|
604
|
+
|
|
605
|
+
def kececi_layout_v4_networkit(graph: nk.graph.Graph,
|
|
606
|
+
primary_spacing=1.0,
|
|
607
|
+
secondary_spacing=1.0,
|
|
608
|
+
primary_direction='top-down',
|
|
609
|
+
secondary_start='right'):
|
|
610
|
+
"""
|
|
611
|
+
Keçeci Layout v4 - Networkit graf düğümlerine sıralı-zigzag yerleşimi sağlar.
|
|
612
|
+
|
|
613
|
+
Parametreler:
|
|
614
|
+
-------------
|
|
615
|
+
graph : networkit.graph.Graph
|
|
616
|
+
Kenar ve düğüm bilgisi içeren Networkit graf nesnesi.
|
|
617
|
+
primary_spacing : float
|
|
618
|
+
Ana yön mesafesi.
|
|
619
|
+
secondary_spacing : float
|
|
620
|
+
Yan yön mesafesi.
|
|
621
|
+
primary_direction : str
|
|
622
|
+
'top-down', 'bottom-up', 'left-to-right', 'right-to-left'.
|
|
623
|
+
secondary_start : str
|
|
624
|
+
Başlangıç yönü ('right', 'left', 'up', 'down').
|
|
625
|
+
|
|
626
|
+
Dönüş:
|
|
627
|
+
------
|
|
628
|
+
dict[int, tuple[float, float]]
|
|
629
|
+
Her düğüm ID'sinin (Networkit'te genelde integer olur)
|
|
630
|
+
koordinatını içeren sözlük.
|
|
631
|
+
"""
|
|
632
|
+
|
|
633
|
+
# Networkit'te düğüm ID'leri genellikle 0'dan N-1'e sıralıdır,
|
|
634
|
+
# ancak garantiye almak için sıralı bir liste alalım.
|
|
635
|
+
# iterNodes() düğüm ID'lerini döndürür.
|
|
636
|
+
try:
|
|
637
|
+
# Networkit'te node ID'lerinin contiguous (0..n-1) olup olmadığını kontrol edebiliriz
|
|
638
|
+
# ama her zaman böyle olmayabilir. iterNodes en genel yöntem.
|
|
639
|
+
nodes = sorted(list(graph.iterNodes()))
|
|
640
|
+
except Exception as e:
|
|
641
|
+
print(f"Networkit düğüm listesi alınırken hata: {e}")
|
|
642
|
+
# Alternatif olarak, eğer ID'lerin 0'dan başladığı varsayılıyorsa:
|
|
643
|
+
# nodes = list(range(graph.numberOfNodes()))
|
|
644
|
+
# Ancak iterNodes daha güvenlidir.
|
|
645
|
+
return {} # Hata durumunda boş dön
|
|
646
|
+
|
|
647
|
+
num_nodes = len(nodes) # Veya graph.numberOfNodes()
|
|
648
|
+
if num_nodes == 0:
|
|
649
|
+
return {} # Boş graf için boş sözlük döndür
|
|
650
|
+
|
|
651
|
+
pos = {} # Sonuç sözlüğü
|
|
652
|
+
is_vertical = primary_direction in ['top-down', 'bottom-up']
|
|
653
|
+
is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
|
|
654
|
+
|
|
655
|
+
# Parametre kontrolleri
|
|
656
|
+
if not (is_vertical or is_horizontal):
|
|
657
|
+
raise ValueError(f"Invalid primary_direction: {primary_direction}")
|
|
658
|
+
if is_vertical and secondary_start not in ['right', 'left']:
|
|
659
|
+
raise ValueError(f"Invalid secondary_start ('{secondary_start}') for vertical primary_direction ('{primary_direction}'). Use 'right' or 'left'.")
|
|
660
|
+
if is_horizontal and secondary_start not in ['up', 'down']:
|
|
661
|
+
raise ValueError(f"Invalid secondary_start ('{secondary_start}') for horizontal primary_direction ('{primary_direction}'). Use 'up' or 'down'.")
|
|
662
|
+
|
|
663
|
+
# Ana döngü
|
|
664
|
+
for i, node_id in enumerate(nodes):
|
|
665
|
+
# i: Düğümün sıralı listedeki indeksi (0, 1, 2, ...) - Yerleşim için kullanılır
|
|
666
|
+
# node_id: Gerçek Networkit düğüm ID'si - Sonuç sözlüğünün anahtarı
|
|
667
|
+
|
|
668
|
+
# 1. Ana eksen koordinatını hesapla
|
|
669
|
+
if primary_direction == 'top-down':
|
|
670
|
+
primary_coord = i * -primary_spacing
|
|
671
|
+
secondary_axis = 'x'
|
|
672
|
+
elif primary_direction == 'bottom-up':
|
|
673
|
+
primary_coord = i * primary_spacing
|
|
674
|
+
secondary_axis = 'x'
|
|
675
|
+
elif primary_direction == 'left-to-right':
|
|
676
|
+
primary_coord = i * primary_spacing
|
|
677
|
+
secondary_axis = 'y'
|
|
678
|
+
else: # primary_direction == 'right-to-left'
|
|
679
|
+
primary_coord = i * -primary_spacing
|
|
680
|
+
secondary_axis = 'y'
|
|
681
|
+
|
|
682
|
+
# 2. Yan eksen ofsetini hesapla (zigzag)
|
|
683
|
+
if i == 0:
|
|
684
|
+
secondary_offset_multiplier = 0.0
|
|
685
|
+
else:
|
|
686
|
+
start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
|
|
687
|
+
magnitude = math.ceil(i / 2.0)
|
|
688
|
+
side = 1 if i % 2 != 0 else -1
|
|
689
|
+
secondary_offset_multiplier = start_mult * magnitude * side
|
|
690
|
+
|
|
691
|
+
secondary_coord = secondary_offset_multiplier * secondary_spacing
|
|
692
|
+
|
|
693
|
+
# 3. (x, y) koordinatlarını ata
|
|
694
|
+
if secondary_axis == 'x':
|
|
695
|
+
x, y = secondary_coord, primary_coord
|
|
696
|
+
else: # secondary_axis == 'y'
|
|
697
|
+
x, y = primary_coord, secondary_coord
|
|
698
|
+
|
|
699
|
+
# Sonuç sözlüğüne ekle: anahtar=düğüm ID, değer=(x, y) tuple'ı
|
|
700
|
+
pos[node_id] = (x, y)
|
|
701
|
+
|
|
702
|
+
return pos
|
|
703
|
+
|
|
704
|
+
def kececi_layout_v4_gg(graph_set: gg.GraphSet,
|
|
705
|
+
primary_spacing=1.0,
|
|
706
|
+
secondary_spacing=1.0,
|
|
707
|
+
primary_direction='top-down',
|
|
708
|
+
secondary_start='right'):
|
|
709
|
+
"""
|
|
710
|
+
Keçeci Layout v4 - Graphillion evren grafının düğümlerine
|
|
711
|
+
sıralı-zigzag yerleşimi sağlar.
|
|
712
|
+
"""
|
|
713
|
+
|
|
714
|
+
# DÜZELTME: Evrenden kenar listesini al
|
|
715
|
+
edges_in_universe = graph_set.universe()
|
|
716
|
+
|
|
717
|
+
# DÜZELTME: Düğüm sayısını kenarlardan türet
|
|
718
|
+
num_vertices = find_max_node_id(edges_in_universe)
|
|
719
|
+
|
|
720
|
+
if num_vertices == 0:
|
|
721
|
+
return {}
|
|
722
|
+
|
|
723
|
+
# Graphillion genellikle 1-tabanlı düğüm indekslemesi kullanır.
|
|
724
|
+
# Düğüm ID listesini oluştur: 1, 2, ..., num_vertices
|
|
725
|
+
nodes = list(range(1, num_vertices + 1)) # En yüksek ID'ye kadar tüm nodları varsay
|
|
726
|
+
|
|
727
|
+
pos = {} # Sonuç sözlüğü
|
|
728
|
+
is_vertical = primary_direction in ['top-down', 'bottom-up']
|
|
729
|
+
is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
|
|
730
|
+
|
|
731
|
+
# Parametre kontrolleri (değişiklik yok)
|
|
732
|
+
if not (is_vertical or is_horizontal):
|
|
733
|
+
raise ValueError(f"Invalid primary_direction: {primary_direction}")
|
|
734
|
+
if is_vertical and secondary_start not in ['right', 'left']:
|
|
735
|
+
raise ValueError(f"Invalid secondary_start ('{secondary_start}') for vertical primary_direction ('{primary_direction}'). Use 'right' or 'left'.")
|
|
736
|
+
if is_horizontal and secondary_start not in ['up', 'down']:
|
|
737
|
+
raise ValueError(f"Invalid secondary_start ('{secondary_start}') for horizontal primary_direction ('{primary_direction}'). Use 'up' or 'down'.")
|
|
738
|
+
|
|
739
|
+
# Ana döngü (değişiklik yok)
|
|
740
|
+
for i, node_id in enumerate(nodes):
|
|
741
|
+
# ... (Koordinat hesaplama kısmı aynı kalır) ...
|
|
742
|
+
if primary_direction == 'top-down':
|
|
743
|
+
primary_coord = i * -primary_spacing; secondary_axis = 'x'
|
|
744
|
+
elif primary_direction == 'bottom-up':
|
|
745
|
+
primary_coord = i * primary_spacing; secondary_axis = 'x'
|
|
746
|
+
elif primary_direction == 'left-to-right':
|
|
747
|
+
primary_coord = i * primary_spacing; secondary_axis = 'y'
|
|
748
|
+
else: # right-to-left
|
|
749
|
+
primary_coord = i * -primary_spacing; secondary_axis = 'y'
|
|
750
|
+
|
|
751
|
+
if i == 0: secondary_offset_multiplier = 0.0
|
|
752
|
+
else:
|
|
753
|
+
start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
|
|
754
|
+
magnitude = math.ceil(i / 2.0)
|
|
755
|
+
side = 1 if i % 2 != 0 else -1
|
|
756
|
+
secondary_offset_multiplier = start_mult * magnitude * side
|
|
757
|
+
secondary_coord = secondary_offset_multiplier * secondary_spacing
|
|
758
|
+
|
|
759
|
+
if secondary_axis == 'x': x, y = secondary_coord, primary_coord
|
|
760
|
+
else: x, y = primary_coord, secondary_coord
|
|
761
|
+
pos[node_id] = (x, y)
|
|
762
|
+
|
|
763
|
+
return pos
|
|
764
|
+
|
|
765
|
+
def kececi_layout_v4_graphillion(graph_set: gg.GraphSet,
|
|
766
|
+
primary_spacing=1.0,
|
|
767
|
+
secondary_spacing=1.0,
|
|
768
|
+
primary_direction='top-down',
|
|
769
|
+
secondary_start='right'):
|
|
770
|
+
"""
|
|
771
|
+
Keçeci Layout v4 - Graphillion evren grafının düğümlerine
|
|
772
|
+
sıralı-zigzag yerleşimi sağlar.
|
|
773
|
+
"""
|
|
774
|
+
|
|
775
|
+
# DÜZELTME: Evrenden kenar listesini al
|
|
776
|
+
edges_in_universe = graph_set.universe()
|
|
777
|
+
|
|
778
|
+
# DÜZELTME: Düğüm sayısını kenarlardan türet
|
|
779
|
+
num_vertices = find_max_node_id(edges_in_universe)
|
|
780
|
+
|
|
781
|
+
if num_vertices == 0:
|
|
782
|
+
return {}
|
|
783
|
+
|
|
784
|
+
# Graphillion genellikle 1-tabanlı düğüm indekslemesi kullanır.
|
|
785
|
+
# Düğüm ID listesini oluştur: 1, 2, ..., num_vertices
|
|
786
|
+
nodes = list(range(1, num_vertices + 1)) # En yüksek ID'ye kadar tüm nodları varsay
|
|
787
|
+
|
|
788
|
+
pos = {} # Sonuç sözlüğü
|
|
789
|
+
is_vertical = primary_direction in ['top-down', 'bottom-up']
|
|
790
|
+
is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
|
|
791
|
+
|
|
792
|
+
# Parametre kontrolleri (değişiklik yok)
|
|
793
|
+
if not (is_vertical or is_horizontal):
|
|
794
|
+
raise ValueError(f"Invalid primary_direction: {primary_direction}")
|
|
795
|
+
if is_vertical and secondary_start not in ['right', 'left']:
|
|
796
|
+
raise ValueError(f"Invalid secondary_start ('{secondary_start}') for vertical primary_direction ('{primary_direction}'). Use 'right' or 'left'.")
|
|
797
|
+
if is_horizontal and secondary_start not in ['up', 'down']:
|
|
798
|
+
raise ValueError(f"Invalid secondary_start ('{secondary_start}') for horizontal primary_direction ('{primary_direction}'). Use 'up' or 'down'.")
|
|
799
|
+
|
|
800
|
+
# Ana döngü (değişiklik yok)
|
|
801
|
+
for i, node_id in enumerate(nodes):
|
|
802
|
+
# ... (Koordinat hesaplama kısmı aynı kalır) ...
|
|
803
|
+
if primary_direction == 'top-down':
|
|
804
|
+
primary_coord = i * -primary_spacing; secondary_axis = 'x'
|
|
805
|
+
elif primary_direction == 'bottom-up':
|
|
806
|
+
primary_coord = i * primary_spacing; secondary_axis = 'x'
|
|
807
|
+
elif primary_direction == 'left-to-right':
|
|
808
|
+
primary_coord = i * primary_spacing; secondary_axis = 'y'
|
|
809
|
+
else: # right-to-left
|
|
810
|
+
primary_coord = i * -primary_spacing; secondary_axis = 'y'
|
|
811
|
+
|
|
812
|
+
if i == 0: secondary_offset_multiplier = 0.0
|
|
813
|
+
else:
|
|
814
|
+
start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
|
|
815
|
+
magnitude = math.ceil(i / 2.0)
|
|
816
|
+
side = 1 if i % 2 != 0 else -1
|
|
817
|
+
secondary_offset_multiplier = start_mult * magnitude * side
|
|
818
|
+
secondary_coord = secondary_offset_multiplier * secondary_spacing
|
|
819
|
+
|
|
820
|
+
if secondary_axis == 'x': x, y = secondary_coord, primary_coord
|
|
821
|
+
else: x, y = primary_coord, secondary_coord
|
|
822
|
+
pos[node_id] = (x, y)
|
|
823
|
+
|
|
824
|
+
return pos
|
|
825
|
+
|
|
826
|
+
def kececi_layout_v4_rx(graph: rx.PyGraph, primary_spacing=1.0, secondary_spacing=1.0,
|
|
827
|
+
primary_direction='top-down', secondary_start='right'):
|
|
828
|
+
pos = {}
|
|
829
|
+
nodes = sorted(graph.node_indices())
|
|
830
|
+
num_nodes = len(nodes)
|
|
831
|
+
if num_nodes == 0: return {}
|
|
832
|
+
|
|
833
|
+
is_vertical = primary_direction in ['top-down', 'bottom-up']
|
|
834
|
+
is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
|
|
835
|
+
if not (is_vertical or is_horizontal): raise ValueError(f"Invalid primary_direction: {primary_direction}")
|
|
836
|
+
if is_vertical and secondary_start not in ['right', 'left']: raise ValueError(f"Invalid secondary_start for vertical: {secondary_start}")
|
|
837
|
+
if is_horizontal and secondary_start not in ['up', 'down']: raise ValueError(f"Invalid secondary_start for horizontal: {secondary_start}")
|
|
838
|
+
|
|
839
|
+
for i, node_index in enumerate(nodes):
|
|
840
|
+
if primary_direction == 'top-down': primary_coord, secondary_axis = i * -primary_spacing, 'x'
|
|
841
|
+
elif primary_direction == 'bottom-up': primary_coord, secondary_axis = i * primary_spacing, 'x'
|
|
842
|
+
elif primary_direction == 'left-to-right': primary_coord, secondary_axis = i * primary_spacing, 'y'
|
|
843
|
+
else: primary_coord, secondary_axis = i * -primary_spacing, 'y'
|
|
844
|
+
|
|
845
|
+
if i == 0: secondary_offset_multiplier = 0.0
|
|
846
|
+
else:
|
|
847
|
+
start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
|
|
848
|
+
magnitude = math.ceil(i / 2.0)
|
|
849
|
+
side = 1 if i % 2 != 0 else -1
|
|
850
|
+
secondary_offset_multiplier = start_mult * magnitude * side
|
|
851
|
+
secondary_coord = secondary_offset_multiplier * secondary_spacing
|
|
852
|
+
|
|
853
|
+
x, y = (secondary_coord, primary_coord) if secondary_axis == 'x' else (primary_coord, secondary_coord)
|
|
854
|
+
pos[node_index] = np.array([x, y])
|
|
855
|
+
return pos
|
|
856
|
+
|
|
857
|
+
def kececi_layout_v4_rustworkx(graph: rx.PyGraph, primary_spacing=1.0, secondary_spacing=1.0,
|
|
858
|
+
primary_direction='top-down', secondary_start='right'):
|
|
859
|
+
pos = {}
|
|
860
|
+
nodes = sorted(graph.node_indices())
|
|
861
|
+
num_nodes = len(nodes)
|
|
862
|
+
if num_nodes == 0: return {}
|
|
863
|
+
|
|
864
|
+
is_vertical = primary_direction in ['top-down', 'bottom-up']
|
|
865
|
+
is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
|
|
866
|
+
if not (is_vertical or is_horizontal): raise ValueError(f"Invalid primary_direction: {primary_direction}")
|
|
867
|
+
if is_vertical and secondary_start not in ['right', 'left']: raise ValueError(f"Invalid secondary_start for vertical: {secondary_start}")
|
|
868
|
+
if is_horizontal and secondary_start not in ['up', 'down']: raise ValueError(f"Invalid secondary_start for horizontal: {secondary_start}")
|
|
869
|
+
|
|
870
|
+
for i, node_index in enumerate(nodes):
|
|
871
|
+
if primary_direction == 'top-down': primary_coord, secondary_axis = i * -primary_spacing, 'x'
|
|
872
|
+
elif primary_direction == 'bottom-up': primary_coord, secondary_axis = i * primary_spacing, 'x'
|
|
873
|
+
elif primary_direction == 'left-to-right': primary_coord, secondary_axis = i * primary_spacing, 'y'
|
|
874
|
+
else: primary_coord, secondary_axis = i * -primary_spacing, 'y'
|
|
875
|
+
|
|
876
|
+
if i == 0: secondary_offset_multiplier = 0.0
|
|
877
|
+
else:
|
|
878
|
+
start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
|
|
879
|
+
magnitude = math.ceil(i / 2.0)
|
|
880
|
+
side = 1 if i % 2 != 0 else -1
|
|
881
|
+
secondary_offset_multiplier = start_mult * magnitude * side
|
|
882
|
+
secondary_coord = secondary_offset_multiplier * secondary_spacing
|
|
883
|
+
|
|
884
|
+
x, y = (secondary_coord, primary_coord) if secondary_axis == 'x' else (primary_coord, secondary_coord)
|
|
885
|
+
pos[node_index] = np.array([x, y])
|
|
886
|
+
return pos
|
|
887
|
+
|
|
888
|
+
# =============================================================================
|
|
889
|
+
# Rastgele Graf Oluşturma Fonksiyonu (Rustworkx ile - Düzeltilmiş subgraph)
|
|
890
|
+
# =============================================================================
|
|
891
|
+
def generate_random_rx_graph(min_nodes=5, max_nodes=15, edge_prob_min=0.15, edge_prob_max=0.4):
|
|
892
|
+
if min_nodes < 2: min_nodes = 2
|
|
893
|
+
if max_nodes < min_nodes: max_nodes = min_nodes
|
|
894
|
+
while True:
|
|
895
|
+
num_nodes_target = random.randint(min_nodes, max_nodes)
|
|
896
|
+
edge_probability = random.uniform(edge_prob_min, edge_prob_max)
|
|
897
|
+
G_candidate = rx.PyGraph()
|
|
898
|
+
node_indices = G_candidate.add_nodes_from([None] * num_nodes_target)
|
|
899
|
+
for i in range(num_nodes_target):
|
|
900
|
+
for j in range(i + 1, num_nodes_target):
|
|
901
|
+
if random.random() < edge_probability:
|
|
902
|
+
G_candidate.add_edge(node_indices[i], node_indices[j], None)
|
|
903
|
+
|
|
904
|
+
if G_candidate.num_nodes() == 0: continue
|
|
905
|
+
if num_nodes_target > 1 and G_candidate.num_edges() == 0: continue
|
|
906
|
+
|
|
907
|
+
if not rx.is_connected(G_candidate):
|
|
908
|
+
components = rx.connected_components(G_candidate)
|
|
909
|
+
if not components: continue
|
|
910
|
+
largest_cc_nodes_indices = max(components, key=len, default=set())
|
|
911
|
+
if len(largest_cc_nodes_indices) < 2 and num_nodes_target >=2 : continue
|
|
912
|
+
if not largest_cc_nodes_indices: continue
|
|
913
|
+
# Set'i listeye çevirerek subgraph oluştur
|
|
914
|
+
G = G_candidate.subgraph(list(largest_cc_nodes_indices))
|
|
915
|
+
if G.num_nodes() == 0: continue
|
|
916
|
+
else:
|
|
917
|
+
G = G_candidate
|
|
918
|
+
|
|
919
|
+
if G.num_nodes() >= 2: break
|
|
920
|
+
print(f"Oluşturulan Rustworkx Graf: {G.num_nodes()} Düğüm, {G.num_edges()} Kenar (Başlangıç p={edge_probability:.3f})")
|
|
921
|
+
return G
|
|
922
|
+
|
|
923
|
+
|
|
924
|
+
def kececi_layout_v4_pure(nodes, primary_spacing=1.0, secondary_spacing=1.0,
|
|
925
|
+
primary_direction='top-down', secondary_start='right'):
|
|
926
|
+
"""
|
|
927
|
+
Keçeci layout mantığını kullanarak düğüm pozisyonlarını hesaplar.
|
|
928
|
+
Sadece standart Python ve math modülünü kullanır.
|
|
929
|
+
"""
|
|
930
|
+
pos = {}
|
|
931
|
+
# Tutarlı sıra garantisi için düğümleri sırala
|
|
932
|
+
# Girdi zaten liste/tuple olsa bile kopyasını oluşturup sırala
|
|
933
|
+
# ... (Bir önceki cevaptaki fonksiyonun TAMAMI buraya yapıştırılacak) ...
|
|
934
|
+
try:
|
|
935
|
+
sorted_nodes = sorted(list(nodes))
|
|
936
|
+
except TypeError:
|
|
937
|
+
print("Uyarı: Düğümler sıralanamadı...")
|
|
938
|
+
sorted_nodes = list(nodes)
|
|
939
|
+
|
|
940
|
+
num_nodes = len(sorted_nodes)
|
|
941
|
+
if num_nodes == 0: return {}
|
|
942
|
+
is_vertical = primary_direction in ['top-down', 'bottom-up']
|
|
943
|
+
is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
|
|
944
|
+
if not (is_vertical or is_horizontal): raise ValueError(f"Invalid primary_direction: {primary_direction}")
|
|
945
|
+
if is_vertical and secondary_start not in ['right', 'left']: raise ValueError(f"Dikey yön için geçersiz secondary_start: {secondary_start}")
|
|
946
|
+
if is_horizontal and secondary_start not in ['up', 'down']: raise ValueError(f"Yatay yön için geçersiz secondary_start: {secondary_start}")
|
|
947
|
+
|
|
948
|
+
for i, node_id in enumerate(sorted_nodes):
|
|
949
|
+
primary_coord = 0.0
|
|
950
|
+
secondary_axis = ''
|
|
951
|
+
if primary_direction == 'top-down': primary_coord, secondary_axis = i * -primary_spacing, 'x'
|
|
952
|
+
elif primary_direction == 'bottom-up': primary_coord, secondary_axis = i * primary_spacing, 'x'
|
|
953
|
+
elif primary_direction == 'left-to-right': primary_coord, secondary_axis = i * primary_spacing, 'y'
|
|
954
|
+
else: primary_coord, secondary_axis = i * -primary_spacing, 'y'
|
|
955
|
+
|
|
956
|
+
secondary_offset_multiplier = 0.0
|
|
957
|
+
if i > 0:
|
|
958
|
+
start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
|
|
959
|
+
magnitude = math.ceil(i / 2.0)
|
|
960
|
+
side = 1 if i % 2 != 0 else -1
|
|
961
|
+
secondary_offset_multiplier = start_mult * magnitude * side
|
|
962
|
+
secondary_coord = secondary_offset_multiplier * secondary_spacing
|
|
963
|
+
|
|
964
|
+
x, y = (secondary_coord, primary_coord) if secondary_axis == 'x' else (primary_coord, secondary_coord)
|
|
965
|
+
pos[node_id] = (x, y)
|
|
966
|
+
return pos
|
|
967
|
+
|
|
968
|
+
# =============================================================================
|
|
969
|
+
# Rastgele Graf Oluşturma Fonksiyonu (NetworkX) - Değişiklik yok
|
|
970
|
+
# =============================================================================
|
|
971
|
+
def generate_random_graph(min_nodes=0, max_nodes=200, edge_prob_min=0.15, edge_prob_max=0.4):
|
|
972
|
+
|
|
973
|
+
if min_nodes < 2: min_nodes = 2
|
|
974
|
+
if max_nodes < min_nodes: max_nodes = min_nodes
|
|
975
|
+
while True:
|
|
976
|
+
num_nodes_target = random.randint(min_nodes, max_nodes)
|
|
977
|
+
edge_probability = random.uniform(edge_prob_min, edge_prob_max)
|
|
978
|
+
G_candidate = nx.gnp_random_graph(num_nodes_target, edge_probability, seed=None)
|
|
979
|
+
if G_candidate.number_of_nodes() == 0: continue
|
|
980
|
+
# Düzeltme: 0 kenarlı ama >1 düğümlü grafı da tekrar dene
|
|
981
|
+
if num_nodes_target > 1 and G_candidate.number_of_edges() == 0 : continue
|
|
982
|
+
|
|
983
|
+
if not nx.is_connected(G_candidate):
|
|
984
|
+
# Düzeltme: default=set() kullanmak yerine önce kontrol et
|
|
985
|
+
connected_components = list(nx.connected_components(G_candidate))
|
|
986
|
+
if not connected_components: continue # Bileşen yoksa tekrar dene
|
|
987
|
+
largest_cc_nodes = max(connected_components, key=len)
|
|
988
|
+
if len(largest_cc_nodes) < 2 and num_nodes_target >=2 : continue
|
|
989
|
+
if not largest_cc_nodes: continue # Bu aslında gereksiz ama garanti olsun
|
|
990
|
+
G = G_candidate.subgraph(largest_cc_nodes).copy()
|
|
991
|
+
if G.number_of_nodes() == 0: continue
|
|
992
|
+
else: G = G_candidate
|
|
993
|
+
if G.number_of_nodes() >= 2: break
|
|
994
|
+
G = nx.convert_node_labels_to_integers(G, first_label=0)
|
|
995
|
+
print(f"Oluşturulan Graf: {G.number_of_nodes()} Düğüm, {G.number_of_edges()} Kenar (Başlangıç p={edge_probability:.3f})")
|
|
996
|
+
return G
|
|
997
|
+
|
|
998
|
+
def generate_random_graph_ig(min_nodes=0, max_nodes=200, edge_prob_min=0.15, edge_prob_max=0.4):
|
|
999
|
+
"""igraph kullanarak rastgele bağlı bir graf oluşturur."""
|
|
1000
|
+
|
|
1001
|
+
if min_nodes < 2: min_nodes = 2
|
|
1002
|
+
if max_nodes < min_nodes: max_nodes = min_nodes
|
|
1003
|
+
while True:
|
|
1004
|
+
num_nodes_target = random.randint(min_nodes, max_nodes)
|
|
1005
|
+
edge_probability = random.uniform(edge_prob_min, edge_prob_max)
|
|
1006
|
+
g_candidate = ig.Graph.Erdos_Renyi(n=num_nodes_target, p=edge_probability, directed=False)
|
|
1007
|
+
if g_candidate.vcount() == 0: continue
|
|
1008
|
+
if num_nodes_target > 1 and g_candidate.ecount() == 0 : continue
|
|
1009
|
+
if not g_candidate.is_connected(mode='weak'):
|
|
1010
|
+
components = g_candidate.components(mode='weak')
|
|
1011
|
+
if not components or len(components) == 0: continue
|
|
1012
|
+
largest_cc_subgraph = components.giant()
|
|
1013
|
+
if largest_cc_subgraph.vcount() < 2 and num_nodes_target >=2 : continue
|
|
1014
|
+
g = largest_cc_subgraph
|
|
1015
|
+
if g.vcount() == 0: continue
|
|
1016
|
+
else: g = g_candidate
|
|
1017
|
+
if g.vcount() >= 2: break
|
|
1018
|
+
print(f"Oluşturulan igraph Graf: {g.vcount()} Düğüm, {g.ecount()} Kenar (Başlangıç p={edge_probability:.3f})")
|
|
1019
|
+
g.vs["label"] = [str(i) for i in range(g.vcount())]
|
|
1020
|
+
g.vs["degree"] = g.degree()
|
|
1021
|
+
return g
|