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.
@@ -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