kececilayout 0.2.4__py3-none-any.whl → 0.2.6__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 CHANGED
@@ -1,37 +1,59 @@
1
1
  # __init__.py
2
- # Bu dosya paketin başlangıç noktası olarak çalışır.
3
- # Alt modülleri yükler, sürüm bilgileri tanımlar ve geriye dönük uyumluluk için uyarılar sağlar.
2
+
3
+ """
4
+ kececilayout - A Python package for sequential-zigzag graph layouts
5
+ and advanced visualizations compatible with multiple graph libraries.
6
+ """
4
7
 
5
8
  from __future__ import annotations
6
- import importlib
9
+ import inspect
7
10
  import warnings
8
- import os
9
- # if os.getenv("DEVELOPMENT") == "true":
10
- # importlib.reload(kececi_layout) # F821 undefined name 'kececi_layout'
11
-
12
- # Göreli modül içe aktarmaları
13
- # F401 hatasını önlemek için sadece kullanacağınız şeyleri dışa aktarın
14
- # Aksi halde linter'lar "imported but unused" uyarısı verir
15
- try:
16
- from .kececi_layout import * # gerekirse burada belirli fonksiyonları seçmeli yapmak daha güvenlidir
17
- from . import kececi_layout # Modülün kendisine doğrudan erişim isteniyorsa
18
- except ImportError as e:
19
- warnings.warn(f"Gerekli modül yüklenemedi: {e}", ImportWarning)
20
-
21
- # Eski bir fonksiyonun yer tutucusu - gelecekte kaldırılacak
22
- def eski_fonksiyon():
11
+
12
+ # Paket sürüm numarası
13
+ __version__ = "0.2.6"
14
+
15
+ # =============================================================================
16
+ # OTOMATİK İÇE AKTARMA VE __all__ OLUŞTURMA
17
+ # Bu bölüm, yeni fonksiyon eklediğinizde elle güncelleme yapma
18
+ # ihtiyacını ortadan kaldırır.
19
+ # =============================================================================
20
+
21
+ # Ana modülümüzü içe aktarıyoruz
22
+ from . import kececi_layout
23
+
24
+ # __all__ listesini dinamik olarak dolduracağız
25
+ __all__ = []
26
+
27
+ # kececi_layout modülünün içindeki tüm üyelere (fonksiyonlar, sınıflar vb.) bak
28
+ for name, member in inspect.getmembers(kececi_layout):
29
+ # Eğer üye bir fonksiyonsa VE adı '_' ile başlamıyorsa (yani public ise)
30
+ if inspect.isfunction(member) and not name.startswith('_'):
31
+ # Onu paketin ana seviyesine taşı (örn: kl.draw_kececi)
32
+ globals()[name] = member
33
+ # Ve dışa aktarılacaklar listesine ekle
34
+ __all__.append(name)
35
+
36
+ # Temizlik: Döngüde kullanılan geçici değişkenleri sil
37
+ del inspect, name, member
38
+
39
+ # =============================================================================
40
+ # GERİYE DÖNÜK UYUMLULUK VE UYARILAR
41
+ # =============================================================================
42
+
43
+ def old_function_placeholder():
23
44
  """
24
- Kaldırılması planlanan eski bir fonksiyondur.
25
- Lütfen alternatif fonksiyonları kullanın.
45
+ This is an old function scheduled for removal.
46
+ Please use alternative functions.
26
47
  """
27
48
  warnings.warn(
28
- "eski_fonksiyon() artık kullanılmamaktadır ve gelecekte kaldırılacaktır. "
29
- "Lütfen yeni alternatif fonksiyonları kullanın. "
30
- "Keçeci Layout; Python 3.7-3.14 sürümlerinde sorunsuz çalışmalıdır.",
49
+ (
50
+ "old_function_placeholder() is deprecated and will be removed in a future version. "
51
+ "Please use the new alternative functions. "
52
+ "Keçeci Layout should work smoothly on Python 3.7-3.14."
53
+ ),
31
54
  category=DeprecationWarning,
32
55
  stacklevel=2
33
56
  )
34
57
 
35
-
36
- # Paket sürüm numarası
37
- __version__ = "0.2.2"
58
+ # Eğer bu eski fonksiyonu da dışa aktarmak istiyorsanız, __all__ listesine ekleyin
59
+ # __all__.append('old_function_placeholder')
kececilayout/_version.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # _version.py
2
2
 
3
- __version__ = "0.2.2"
3
+ __version__ = "0.2.6"
4
4
  __license__ = "MIT"
5
5
  __description__ = "A deterministic node placement algorithm used in graph visualization. In this layout, nodes are arranged sequentially along a defined primary axis. Each subsequent node is then alternately offset along a secondary, perpendicular axis, typically moving to one side of the primary axis and then the other. Often, the magnitude of this secondary offset increases as nodes progress along the primary axis, creating a characteristic zig-zag or serpentine pattern."
6
6
  __author__ = "Mehmet Keçeci"
@@ -1,21 +1,37 @@
1
- # kececilayout/kececi_layout.py
1
+ # -*- coding: utf-8 -*-
2
+ # ruff: noqa: N806, N815
3
+ """
4
+ kececilayout.py
2
5
 
6
+ Bu modül, çeşitli Python graf kütüphaneleri için sıralı-zigzag ("Keçeci Layout")
7
+ ve gelişmiş görselleştirme stilleri sağlar.
8
+ """
9
+
10
+ import graphillion as gg
11
+ import igraph as ig
3
12
  import itertools # Graphillion için eklendi
4
- import numpy as np # rustworkx
5
13
  import math
6
- import networkx as nx
7
- import rustworkx as rx
8
- import igraph as ig
14
+ import matplotlib.pyplot as plt
15
+ from mpl_toolkits.mplot3d import Axes3D
9
16
  import networkit as nk
10
- import graphillion as gg
17
+ import networkx as nx
18
+ import numpy as np # rustworkx
11
19
  import random
20
+ import rustworkx as rx
21
+ import warnings
12
22
 
13
23
 
14
- # Gerekli olabilecek kütüphane importları (type hinting veya isinstance için)
24
+ # Ana bağımlılıklar (çizim için gerekli)
15
25
  try:
16
26
  import networkx as nx
17
- except ImportError:
18
- nx = None # Yoksa None ata
27
+ #from mpl_toolkits.mplot3d import Axes3D
28
+ except ImportError as e:
29
+ raise ImportError(
30
+ "Bu modülün çalışması için 'networkx' ve 'matplotlib' gereklidir. "
31
+ "Lütfen `pip install networkx matplotlib` ile kurun."
32
+ ) from e
33
+
34
+ # Opsiyonel graf kütüphaneleri
19
35
  try:
20
36
  import rustworkx as rx
21
37
  except ImportError:
@@ -29,10 +45,11 @@ try:
29
45
  except ImportError:
30
46
  nk = None
31
47
  try:
32
- import graphillion as gg # Graphillion importu eklendi
48
+ import graphillion as gg
33
49
  except ImportError:
34
50
  gg = None
35
51
 
52
+
36
53
  def find_max_node_id(edges):
37
54
  """Verilen kenar listesindeki en büyük düğüm ID'sini bulur."""
38
55
  if not edges:
@@ -45,246 +62,160 @@ def find_max_node_id(edges):
45
62
  print("Uyarı: Kenar formatı beklenenden farklı, max node ID 0 varsayıldı.")
46
63
  return 0
47
64
 
48
- def kececi_layout_v4(graph, primary_spacing=1.0, secondary_spacing=1.0,
49
- primary_direction='top-down', secondary_start='right'):
50
- """
51
- Keçeci Layout v4 - Graf düğümlerine sıralı-zigzag yerleşimi sağlar.
52
- NetworkX, Rustworkx, igraph, Networkit ve Graphillion grafikleriyle çalışır.
53
-
54
- Parametreler:
55
- -------------
56
- graph : Kütüphaneye özel graf nesnesi
57
- NetworkX, Rustworkx, igraph, Networkit veya Graphillion nesnesi.
58
- Graphillion için bir GraphSet nesnesi beklenir.
59
- ... (diğer parametreler) ...
60
65
 
61
- Dönüş:
62
- ------
63
- dict[node_identifier, tuple[float, float]]
64
- Her düğümün koordinatını içeren sözlük. Anahtarlar kütüphaneye
65
- göre değişir (NX: node obj/id, RW/NK/igraph: int index, GG: 1-based int index).
66
+ def kececi_layout(graph, primary_spacing=1.0, secondary_spacing=1.0,
67
+ primary_direction='top_down', secondary_start='right',
68
+ expanding=True):
66
69
  """
70
+ Calculates 2D sequential-zigzag coordinates for the nodes of a graph.
67
71
 
68
- nodes = None
69
-
70
- # Kütüphane Tespiti ve Düğüm Listesi Çıkarımı
71
- # isinstance kullanmak hasattr'dan daha güvenilirdir.
72
- # Önce daha spesifik tipleri kontrol etmek iyi olabilir.
73
-
74
- if gg and isinstance(graph, gg.GraphSet): # Graphillion kontrolü EKLENDİ
75
- edges = graph.universe()
76
- max_node_id = find_max_node_id(edges)
77
- if max_node_id > 0:
78
- nodes = list(range(1, max_node_id + 1)) # 1-tabanlı indeksleme
79
- else:
80
- nodes = [] # Boş evren
81
- print(f"DEBUG: Graphillion tespit edildi. Düğümler (1..{max_node_id}): {nodes[:10]}...") # Debug mesajı
72
+ This function is compatible with graphs from NetworkX, Rustworkx, igraph,
73
+ Networkit, and Graphillion.
82
74
 
83
- elif ig and isinstance(graph, ig.Graph): # igraph
84
- nodes = sorted([v.index for v in graph.vs]) # 0-tabanlı indeksleme
85
- print(f"DEBUG: igraph tespit edildi. Düğümler (0..{len(nodes)-1}): {nodes[:10]}...")
86
-
87
- elif nk and isinstance(graph, nk.graph.Graph): # Networkit
88
- try:
89
- # iterNodes genellikle 0..N-1 verir ama garanti değil
90
- nodes = sorted(list(graph.iterNodes()))
91
- except Exception:
92
- nodes = list(range(graph.numberOfNodes()))
93
- print(f"DEBUG: Networkit tespit edildi. Düğümler: {nodes[:10]}...")
94
-
95
- elif rx and isinstance(graph, (rx.PyGraph, rx.PyDiGraph)): # Rustworkx (hem yönlü hem yönsüz)
96
- nodes = sorted(graph.node_indices()) # 0-tabanlı indeksleme
97
- print(f"DEBUG: Rustworkx tespit edildi. Düğümler (0..{len(nodes)-1}): {nodes[:10]}...")
98
-
99
- elif nx and isinstance(graph, (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)): # NetworkX
100
- try:
101
- # Düğümler sıralanabilirse sırala (genelde int/str)
102
- nodes = sorted(list(graph.nodes()))
103
- except TypeError:
104
- # Sıralanamayan düğüm tipleri varsa (örn. tuple, nesne) sırasız al
105
- nodes = list(graph.nodes())
106
- print(f"DEBUG: NetworkX tespit edildi. Düğümler: {nodes[:10]}...")
107
-
108
- else:
109
- # Desteklenmeyen tip veya kütüphane kurulu değilse
110
- supported_types = []
111
- if nx: supported_types.append("NetworkX")
112
- if rx: supported_types.append("Rustworkx")
113
- if ig: supported_types.append("igraph")
114
- if nk: supported_types.append("Networkit")
115
- if gg: supported_types.append("Graphillion.GraphSet")
116
- raise TypeError(f"Unsupported graph type: {type(graph)}. Desteklenen türler: {', '.join(supported_types)}")
117
-
118
- # ----- Buradan sonrası tüm kütüphaneler için ortak -----
75
+ Args:
76
+ graph: A graph object from a supported library.
77
+ primary_spacing (float): The distance between nodes along the primary axis.
78
+ secondary_spacing (float): The base unit for the zigzag offset.
79
+ primary_direction (str): 'top_down', 'bottom_up', 'left-to-right', 'right-to-left'.
80
+ secondary_start (str): Initial direction for the zigzag ('up', 'down', 'left', 'right').
81
+ expanding (bool): If True (default), the zigzag offset grows (the 'v4' style).
82
+ If False, the offset is constant (parallel lines).
119
83
 
120
- num_nodes = len(nodes)
121
- if num_nodes == 0:
122
- return {} # Boş graf için boş sözlük döndür
84
+ Returns:
85
+ dict: A dictionary of positions formatted as {node_id: (x, y)}.
86
+ """
87
+ # Bu blok, farklı kütüphanelerden düğüm listelerini doğru şekilde alır.
88
+ nx_graph = to_networkx(graph) # Emin olmak için en başta dönüştür
89
+ try:
90
+ nodes = sorted(list(nx_graph.nodes()))
91
+ except TypeError:
92
+ nodes = list(nx_graph.nodes())
123
93
 
124
- pos = {} # Sonuç sözlüğü
94
+ pos = {}
95
+
96
+ # --- DOĞRULANMIŞ KONTROL BLOĞU ---
125
97
  is_vertical = primary_direction in ['top-down', 'bottom-up']
126
98
  is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
127
99
 
128
- # Parametre kontrolleri
129
100
  if not (is_vertical or is_horizontal):
130
- raise ValueError(f"Invalid primary_direction: {primary_direction}")
101
+ raise ValueError(f"Invalid primary_direction: '{primary_direction}'")
131
102
  if is_vertical and secondary_start not in ['right', 'left']:
132
- raise ValueError(f"Invalid secondary_start ('{secondary_start}') for vertical primary_direction ('{primary_direction}'). Use 'right' or 'left'.")
103
+ raise ValueError(f"Invalid secondary_start for vertical direction: '{secondary_start}'")
133
104
  if is_horizontal and secondary_start not in ['up', 'down']:
134
- raise ValueError(f"Invalid secondary_start ('{secondary_start}') for horizontal primary_direction ('{primary_direction}'). Use 'up' or 'down'.")
105
+ raise ValueError(f"Invalid secondary_start for horizontal direction: '{secondary_start}'")
106
+ # --- BİTİŞ ---
135
107
 
136
- # Ana döngü - Düğümleri sıralı indekslerine göre yerleştirir,
137
- # sözlüğe ise gerçek düğüm ID/indeks/nesnesini anahtar olarak kullanır.
138
108
  for i, node_id in enumerate(nodes):
139
- # i: Düğümün sıralı listedeki 0-tabanlı indeksi (0, 1, 2, ...) - Yerleşim için kullanılır
140
- # node_id: Gerçek düğüm kimliği/indeksi - Sonuç sözlüğünün anahtarı
141
-
142
- # 1. Ana eksen koordinatını hesapla
109
+ primary_coord, secondary_axis = 0.0, ''
143
110
  if primary_direction == 'top-down':
144
- primary_coord = i * -primary_spacing; secondary_axis = 'x'
111
+ primary_coord, secondary_axis = i * -primary_spacing, 'x'
145
112
  elif primary_direction == 'bottom-up':
146
- primary_coord = i * primary_spacing; secondary_axis = 'x'
113
+ primary_coord, secondary_axis = i * primary_spacing, 'x'
147
114
  elif primary_direction == 'left-to-right':
148
- primary_coord = i * primary_spacing; secondary_axis = 'y'
149
- else: # right-to-left
150
- primary_coord = i * -primary_spacing; secondary_axis = 'y'
151
-
152
- # 2. Yan eksen ofsetini hesapla (zigzag)
153
- if i == 0: secondary_offset_multiplier = 0.0
115
+ primary_coord, secondary_axis = i * primary_spacing, 'y'
154
116
  else:
155
- start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
156
- magnitude = math.ceil(i / 2.0)
157
- side = 1 if i % 2 != 0 else -1
158
- secondary_offset_multiplier = start_mult * magnitude * side
159
- secondary_coord = secondary_offset_multiplier * secondary_spacing
117
+ primary_coord, secondary_axis = i * -primary_spacing, 'y'
160
118
 
161
- # 3. (x, y) koordinatlarını ata
162
- if secondary_axis == 'x': x, y = secondary_coord, primary_coord
163
- else: x, y = primary_coord, secondary_coord
119
+ secondary_offset = 0.0
120
+ if i > 0:
121
+ start_multiplier = 1.0 if secondary_start in ['right', 'up'] else -1.0
122
+ magnitude = math.ceil(i / 2.0) if expanding else 1.0
123
+ side = 1 if i % 2 != 0 else -1
124
+ secondary_offset = start_multiplier * magnitude * side * secondary_spacing
164
125
 
165
- # Sonuç sözlüğüne ekle
126
+ x, y = ((secondary_offset, primary_coord) if secondary_axis == 'x' else
127
+ (primary_coord, secondary_offset))
166
128
  pos[node_id] = (x, y)
167
-
168
129
  return pos
169
-
170
- def kececi_layout(graph, primary_spacing=1.0, secondary_spacing=1.0,
171
- primary_direction='top-down', secondary_start='right'):
172
- """
173
- Keçeci Layout v4 - Graf düğümlerine sıralı-zigzag yerleşimi sağlar.
174
- NetworkX, Rustworkx, igraph, Networkit ve Graphillion grafikleriyle çalışır.
175
130
 
176
- Parametreler:
177
- -------------
178
- graph : Kütüphaneye özel graf nesnesi
179
- NetworkX, Rustworkx, igraph, Networkit veya Graphillion nesnesi.
180
- Graphillion için bir GraphSet nesnesi beklenir.
181
- ... (diğer parametreler) ...
131
+ # =============================================================================
132
+ # 1. TEMEL LAYOUT HESAPLAMA FONKSİYONU (2D)
133
+ # Bu fonksiyon sadece koordinatları hesaplar, çizim yapmaz.
134
+ # 1. LAYOUT CALCULATION FUNCTION (UNIFIED AND IMPROVED)
135
+ # =============================================================================
182
136
 
183
- Dönüş:
184
- ------
185
- dict[node_identifier, tuple[float, float]]
186
- Her düğümün koordinatını içeren sözlük. Anahtarlar kütüphaneye
187
- göre değişir (NX: node obj/id, RW/NK/igraph: int index, GG: 1-based int index).
137
+ def kececi_layout_v4(graph, primary_spacing=1.0, secondary_spacing=1.0,
138
+ primary_direction='top_down', secondary_start='right',
139
+ expanding=True): # v4 davranışını kontrol etmek için parametre eklendi
188
140
  """
141
+ Calculates 2D sequential-zigzag coordinates for the nodes of a graph.
189
142
 
190
- nodes = None
143
+ This function is compatible with graphs from NetworkX, Rustworkx, igraph,
144
+ Networkit, and Graphillion.
191
145
 
192
- # Kütüphane Tespiti ve Düğüm Listesi Çıkarımı
193
- # isinstance kullanmak hasattr'dan daha güvenilirdir.
194
- # Önce daha spesifik tipleri kontrol etmek iyi olabilir.
146
+ Args:
147
+ graph: A graph object from a supported library.
148
+ primary_spacing (float): The distance between nodes along the primary axis.
149
+ secondary_spacing (float): The base unit for the zigzag offset.
150
+ primary_direction (str): 'top_down', 'bottom_up', 'left_to_right', 'right_to_left'.
151
+ secondary_start (str): Initial direction for the zigzag ('up', 'down', 'left', 'right').
152
+ expanding (bool): If True (default), the zigzag offset grows, creating the
153
+ triangle-like 'v4' style. If False, the offset is constant,
154
+ creating parallel lines.
195
155
 
196
- if gg and isinstance(graph, gg.GraphSet): # Graphillion kontrolü EKLENDİ
156
+ Returns:
157
+ dict: A dictionary of positions formatted as {node_id: (x, y)}.
158
+ """
159
+ # ==========================================================
160
+ # Sizin orijinal, çoklu kütüphane uyumluluk bloğunuz burada korunuyor.
161
+ # Bu, kodun sağlamlığını garanti eder.
162
+ # ==========================================================
163
+ nodes = None
164
+ if gg and isinstance(graph, gg.GraphSet):
197
165
  edges = graph.universe()
198
- max_node_id = find_max_node_id(edges)
199
- if max_node_id > 0:
200
- nodes = list(range(1, max_node_id + 1)) # 1-tabanlı indeksleme
201
- else:
202
- nodes = [] # Boş evren
203
- print(f"DEBUG: Graphillion tespit edildi. Düğümler (1..{max_node_id}): {nodes[:10]}...") # Debug mesajı
204
-
205
- elif ig and isinstance(graph, ig.Graph): # igraph
206
- nodes = sorted([v.index for v in graph.vs]) # 0-tabanlı indeksleme
207
- print(f"DEBUG: igraph tespit edildi. Düğümler (0..{len(nodes)-1}): {nodes[:10]}...")
208
-
209
- elif nk and isinstance(graph, nk.graph.Graph): # Networkit
210
- try:
211
- # iterNodes genellikle 0..N-1 verir ama garanti değil
212
- nodes = sorted(list(graph.iterNodes()))
213
- except Exception:
214
- nodes = list(range(graph.numberOfNodes()))
215
- print(f"DEBUG: Networkit tespit edildi. Düğümler: {nodes[:10]}...")
216
-
217
- elif rx and isinstance(graph, (rx.PyGraph, rx.PyDiGraph)): # Rustworkx (hem yönlü hem yönsüz)
218
- nodes = sorted(graph.node_indices()) # 0-tabanlı indeksleme
219
- print(f"DEBUG: Rustworkx tespit edildi. Düğümler (0..{len(nodes)-1}): {nodes[:10]}...")
220
-
221
- elif nx and isinstance(graph, (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)): # NetworkX
166
+ max_node_id = max(set(itertools.chain.from_iterable(edges))) if edges else 0
167
+ nodes = list(range(1, max_node_id + 1)) if max_node_id > 0 else []
168
+ elif ig and isinstance(graph, ig.Graph):
169
+ nodes = sorted([v.index for v in graph.vs])
170
+ elif nk and isinstance(graph, nk.graph.Graph):
171
+ nodes = sorted(list(graph.iterNodes()))
172
+ elif rx and isinstance(graph, (rx.PyGraph, rx.PyDiGraph)):
173
+ nodes = sorted(graph.node_indices())
174
+ elif isinstance(graph, nx.Graph):
222
175
  try:
223
- # Düğümler sıralanabilirse sırala (genelde int/str)
224
176
  nodes = sorted(list(graph.nodes()))
225
177
  except TypeError:
226
- # Sıralanamayan düğüm tipleri varsa (örn. tuple, nesne) sırasız al
227
- nodes = list(graph.nodes())
228
- print(f"DEBUG: NetworkX tespit edildi. Düğümler: {nodes[:10]}...")
229
-
178
+ nodes = list(graph.nodes())
230
179
  else:
231
- # Desteklenmeyen tip veya kütüphane kurulu değilse
232
- supported_types = []
233
- if nx: supported_types.append("NetworkX")
234
- if rx: supported_types.append("Rustworkx")
235
- if ig: supported_types.append("igraph")
236
- if nk: supported_types.append("Networkit")
237
- if gg: supported_types.append("Graphillion.GraphSet")
238
- raise TypeError(f"Unsupported graph type: {type(graph)}. Desteklenen türler: {', '.join(supported_types)}")
239
-
240
- # ----- Buradan sonrası tüm kütüphaneler için ortak -----
180
+ supported = ["NetworkX", "Rustworkx", "igraph", "Networkit", "Graphillion"]
181
+ raise TypeError(f"Unsupported graph type: {type(graph)}. Supported: {', '.join(supported)}")
182
+ # ==========================================================
241
183
 
242
- num_nodes = len(nodes)
243
- if num_nodes == 0:
244
- return {} # Boş graf için boş sözlük döndür
245
-
246
- pos = {} # Sonuç sözlüğü
247
- is_vertical = primary_direction in ['top-down', 'bottom-up']
184
+ pos = {}
185
+ is_vertical = primary_direction in ['top_down', 'bottom_up']
248
186
  is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
249
187
 
250
- # Parametre kontrolleri
251
188
  if not (is_vertical or is_horizontal):
252
189
  raise ValueError(f"Invalid primary_direction: {primary_direction}")
253
190
  if is_vertical and secondary_start not in ['right', 'left']:
254
- raise ValueError(f"Invalid secondary_start ('{secondary_start}') for vertical primary_direction ('{primary_direction}'). Use 'right' or 'left'.")
191
+ raise ValueError(f"Invalid secondary_start for vertical direction: {secondary_start}")
255
192
  if is_horizontal and secondary_start not in ['up', 'down']:
256
- raise ValueError(f"Invalid secondary_start ('{secondary_start}') for horizontal primary_direction ('{primary_direction}'). Use 'up' or 'down'.")
193
+ raise ValueError(f"Invalid secondary_start for horizontal direction: {secondary_start}")
257
194
 
258
- # Ana döngü - Düğümleri sıralı indekslerine göre yerleştirir,
259
- # sözlüğe ise gerçek düğüm ID/indeks/nesnesini anahtar olarak kullanır.
260
195
  for i, node_id in enumerate(nodes):
261
- # i: Düğümün sıralı listedeki 0-tabanlı indeksi (0, 1, 2, ...) - Yerleşim için kullanılır
262
- # node_id: Gerçek düğüm kimliği/indeksi - Sonuç sözlüğünün anahtarı
263
-
264
- # 1. Ana eksen koordinatını hesapla
196
+ primary_coord, secondary_axis = 0.0, ''
265
197
  if primary_direction == 'top-down':
266
- primary_coord = i * -primary_spacing; secondary_axis = 'x'
267
- elif primary_direction == 'bottom-up':
268
- primary_coord = i * primary_spacing; secondary_axis = 'x'
198
+ primary_coord, secondary_axis = i * -primary_spacing, 'x'
199
+ elif primary_direction == 'bottom_up':
200
+ primary_coord, secondary_axis = i * primary_spacing, 'x'
269
201
  elif primary_direction == 'left-to-right':
270
- primary_coord = i * primary_spacing; secondary_axis = 'y'
271
- else: # right-to-left
272
- primary_coord = i * -primary_spacing; secondary_axis = 'y'
202
+ primary_coord, secondary_axis = i * primary_spacing, 'y'
203
+ else: # 'right_to_left'
204
+ primary_coord, secondary_axis = i * -primary_spacing, 'y'
273
205
 
274
- # 2. Yan eksen ofsetini hesapla (zigzag)
275
- if i == 0: secondary_offset_multiplier = 0.0
276
- else:
277
- start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
278
- magnitude = math.ceil(i / 2.0)
206
+ secondary_offset = 0.0
207
+ if i > 0:
208
+ start_multiplier = 1.0 if secondary_start in ['right', 'up'] else -1.0
209
+
210
+ # --- YENİ ESNEK MANTIK BURADA ---
211
+ # `expanding` True ise 'v4' stili gibi genişler, değilse sabit kalır.
212
+ magnitude = math.ceil(i / 2.0) if expanding else 1.0
213
+
279
214
  side = 1 if i % 2 != 0 else -1
280
- secondary_offset_multiplier = start_mult * magnitude * side
281
- secondary_coord = secondary_offset_multiplier * secondary_spacing
215
+ secondary_offset = start_multiplier * magnitude * side * secondary_spacing
282
216
 
283
- # 3. (x, y) koordinatlarını ata
284
- if secondary_axis == 'x': x, y = secondary_coord, primary_coord
285
- else: x, y = primary_coord, secondary_coord
286
-
287
- # Sonuç sözlüğüne ekle
217
+ x, y = ((secondary_offset, primary_coord) if secondary_axis == 'x' else
218
+ (primary_coord, secondary_offset))
288
219
  pos[node_id] = (x, y)
289
220
 
290
221
  return pos
@@ -299,19 +230,26 @@ def kececi_layout_v4_nx(graph, primary_spacing=1.0, secondary_spacing=1.0,
299
230
  # NetworkX 2.x ve 3.x uyumluluğu için listeye çevirme
300
231
  nodes = sorted(list(graph.nodes()))
301
232
  num_nodes = len(nodes)
302
- if num_nodes == 0: return {}
233
+ if num_nodes == 0:
234
+ return {}
303
235
 
304
236
  is_vertical = primary_direction in ['top-down', 'bottom-up']
305
237
  is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
306
- if not (is_vertical or is_horizontal): raise ValueError(f"Invalid primary_direction: {primary_direction}")
307
- if is_vertical and secondary_start not in ['right', 'left']: raise ValueError(f"Invalid secondary_start for vertical: {secondary_start}")
308
- if is_horizontal and secondary_start not in ['up', 'down']: raise ValueError(f"Invalid secondary_start for horizontal: {secondary_start}")
238
+ if not (is_vertical or is_horizontal):
239
+ raise ValueError(f"Invalid primary_direction: {primary_direction}")
240
+ if is_vertical and secondary_start not in ['right', 'left']:
241
+ raise ValueError(f"Invalid secondary_start for vertical: {secondary_start}")
242
+ if is_horizontal and secondary_start not in ['up', 'down']:
243
+ raise ValueError(f"Invalid secondary_start for horizontal: {secondary_start}")
309
244
 
310
245
  for i, node_id in enumerate(nodes):
311
246
  # 1. Ana Eksen Koordinatını Hesapla
312
- if primary_direction == 'top-down': primary_coord, secondary_axis = i * -primary_spacing, 'x'
313
- elif primary_direction == 'bottom-up': primary_coord, secondary_axis = i * primary_spacing, 'x'
314
- elif primary_direction == 'left-to-right': primary_coord, secondary_axis = i * primary_spacing, 'y'
247
+ if primary_direction == 'top-down':
248
+ primary_coord, secondary_axis = i * -primary_spacing, 'x'
249
+ elif primary_direction == 'bottom-up':
250
+ primary_coord, secondary_axis = i * primary_spacing, 'x'
251
+ elif primary_direction == 'left-to-right':
252
+ primary_coord, secondary_axis = i * primary_spacing, 'y'
315
253
  else: # right-to-left
316
254
  primary_coord, secondary_axis = i * -primary_spacing, 'y'
317
255
 
@@ -345,19 +283,26 @@ def kececi_layout_v4_networkx(graph, primary_spacing=1.0, secondary_spacing=1.0,
345
283
  # NetworkX 2.x ve 3.x uyumluluğu için listeye çevirme
346
284
  nodes = sorted(list(graph.nodes()))
347
285
  num_nodes = len(nodes)
348
- if num_nodes == 0: return {}
286
+ if num_nodes == 0:
287
+ return {}
349
288
 
350
289
  is_vertical = primary_direction in ['top-down', 'bottom-up']
351
290
  is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
352
- if not (is_vertical or is_horizontal): raise ValueError(f"Invalid primary_direction: {primary_direction}")
353
- if is_vertical and secondary_start not in ['right', 'left']: raise ValueError(f"Invalid secondary_start for vertical: {secondary_start}")
354
- if is_horizontal and secondary_start not in ['up', 'down']: raise ValueError(f"Invalid secondary_start for horizontal: {secondary_start}")
291
+ if not (is_vertical or is_horizontal):
292
+ raise ValueError(f"Invalid primary_direction: {primary_direction}")
293
+ if is_vertical and secondary_start not in ['right', 'left']:
294
+ raise ValueError(f"Invalid secondary_start for vertical: {secondary_start}")
295
+ if is_horizontal and secondary_start not in ['up', 'down']:
296
+ raise ValueError(f"Invalid secondary_start for horizontal: {secondary_start}")
355
297
 
356
298
  for i, node_id in enumerate(nodes):
357
299
  # 1. Ana Eksen Koordinatını Hesapla
358
- if primary_direction == 'top-down': primary_coord, secondary_axis = i * -primary_spacing, 'x'
359
- elif primary_direction == 'bottom-up': primary_coord, secondary_axis = i * primary_spacing, 'x'
360
- elif primary_direction == 'left-to-right': primary_coord, secondary_axis = i * primary_spacing, 'y'
300
+ if primary_direction == 'top-down':
301
+ primary_coord, secondary_axis = i * -primary_spacing, 'x'
302
+ elif primary_direction == 'bottom-up':
303
+ primary_coord, secondary_axis = i * primary_spacing, 'x'
304
+ elif primary_direction == 'left-to-right':
305
+ primary_coord, secondary_axis = i * primary_spacing, 'y'
361
306
  else: # right-to-left
362
307
  primary_coord, secondary_axis = i * -primary_spacing, 'y'
363
308
 
@@ -741,15 +686,20 @@ def kececi_layout_v4_gg(graph_set: gg.GraphSet,
741
686
  for i, node_id in enumerate(nodes):
742
687
  # ... (Koordinat hesaplama kısmı aynı kalır) ...
743
688
  if primary_direction == 'top-down':
744
- primary_coord = i * -primary_spacing; secondary_axis = 'x'
689
+ primary_coord = i * -primary_spacing;
690
+ secondary_axis = 'x'
745
691
  elif primary_direction == 'bottom-up':
746
- primary_coord = i * primary_spacing; secondary_axis = 'x'
692
+ primary_coord = i * primary_spacing;
693
+ secondary_axis = 'x'
747
694
  elif primary_direction == 'left-to-right':
748
- primary_coord = i * primary_spacing; secondary_axis = 'y'
695
+ primary_coord = i * primary_spacing;
696
+ secondary_axis = 'y'
749
697
  else: # right-to-left
750
- primary_coord = i * -primary_spacing; secondary_axis = 'y'
698
+ primary_coord = i * -primary_spacing;
699
+ secondary_axis = 'y'
751
700
 
752
- if i == 0: secondary_offset_multiplier = 0.0
701
+ if i == 0:
702
+ secondary_offset_multiplier = 0.0
753
703
  else:
754
704
  start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
755
705
  magnitude = math.ceil(i / 2.0)
@@ -757,8 +707,10 @@ def kececi_layout_v4_gg(graph_set: gg.GraphSet,
757
707
  secondary_offset_multiplier = start_mult * magnitude * side
758
708
  secondary_coord = secondary_offset_multiplier * secondary_spacing
759
709
 
760
- if secondary_axis == 'x': x, y = secondary_coord, primary_coord
761
- else: x, y = primary_coord, secondary_coord
710
+ if secondary_axis == 'x':
711
+ x, y = secondary_coord, primary_coord
712
+ else:
713
+ x, y = primary_coord, secondary_coord
762
714
  pos[node_id] = (x, y)
763
715
 
764
716
  return pos
@@ -802,15 +754,20 @@ def kececi_layout_v4_graphillion(graph_set: gg.GraphSet,
802
754
  for i, node_id in enumerate(nodes):
803
755
  # ... (Koordinat hesaplama kısmı aynı kalır) ...
804
756
  if primary_direction == 'top-down':
805
- primary_coord = i * -primary_spacing; secondary_axis = 'x'
757
+ primary_coord = i * -primary_spacing;
758
+ secondary_axis = 'x'
806
759
  elif primary_direction == 'bottom-up':
807
- primary_coord = i * primary_spacing; secondary_axis = 'x'
760
+ primary_coord = i * primary_spacing;
761
+ secondary_axis = 'x'
808
762
  elif primary_direction == 'left-to-right':
809
- primary_coord = i * primary_spacing; secondary_axis = 'y'
763
+ primary_coord = i * primary_spacing;
764
+ secondary_axis = 'y'
810
765
  else: # right-to-left
811
- primary_coord = i * -primary_spacing; secondary_axis = 'y'
766
+ primary_coord = i * -primary_spacing;
767
+ secondary_axis = 'y'
812
768
 
813
- if i == 0: secondary_offset_multiplier = 0.0
769
+ if i == 0:
770
+ secondary_offset_multiplier = 0.0
814
771
  else:
815
772
  start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
816
773
  magnitude = math.ceil(i / 2.0)
@@ -818,32 +775,44 @@ def kececi_layout_v4_graphillion(graph_set: gg.GraphSet,
818
775
  secondary_offset_multiplier = start_mult * magnitude * side
819
776
  secondary_coord = secondary_offset_multiplier * secondary_spacing
820
777
 
821
- if secondary_axis == 'x': x, y = secondary_coord, primary_coord
822
- else: x, y = primary_coord, secondary_coord
778
+ if secondary_axis == 'x':
779
+ x, y = secondary_coord, primary_coord
780
+ else:
781
+ x, y = primary_coord, secondary_coord
823
782
  pos[node_id] = (x, y)
824
783
 
825
784
  return pos
826
785
 
827
- def kececi_layout_v4_rx(graph: rx.PyGraph, primary_spacing=1.0, secondary_spacing=1.0,
786
+ def kececi_layout_v4_rx(graph:
787
+ rx.PyGraph, primary_spacing=1.0, secondary_spacing=1.0,
828
788
  primary_direction='top-down', secondary_start='right'):
829
789
  pos = {}
830
790
  nodes = sorted(graph.node_indices())
831
791
  num_nodes = len(nodes)
832
- if num_nodes == 0: return {}
792
+ if num_nodes == 0:
793
+ return {}
833
794
 
834
795
  is_vertical = primary_direction in ['top-down', 'bottom-up']
835
796
  is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
836
- if not (is_vertical or is_horizontal): raise ValueError(f"Invalid primary_direction: {primary_direction}")
837
- if is_vertical and secondary_start not in ['right', 'left']: raise ValueError(f"Invalid secondary_start for vertical: {secondary_start}")
838
- if is_horizontal and secondary_start not in ['up', 'down']: raise ValueError(f"Invalid secondary_start for horizontal: {secondary_start}")
797
+ if not (is_vertical or is_horizontal):
798
+ raise ValueError(f"Invalid primary_direction: {primary_direction}")
799
+ if is_vertical and secondary_start not in ['right', 'left']:
800
+ raise ValueError(f"Invalid secondary_start for vertical: {secondary_start}")
801
+ if is_horizontal and secondary_start not in ['up', 'down']:
802
+ raise ValueError(f"Invalid secondary_start for horizontal: {secondary_start}")
839
803
 
840
804
  for i, node_index in enumerate(nodes):
841
- if primary_direction == 'top-down': primary_coord, secondary_axis = i * -primary_spacing, 'x'
842
- elif primary_direction == 'bottom-up': primary_coord, secondary_axis = i * primary_spacing, 'x'
843
- elif primary_direction == 'left-to-right': primary_coord, secondary_axis = i * primary_spacing, 'y'
844
- else: primary_coord, secondary_axis = i * -primary_spacing, 'y'
805
+ if primary_direction == 'top-down':
806
+ primary_coord, secondary_axis = i * -primary_spacing, 'x'
807
+ elif primary_direction == 'bottom-up':
808
+ primary_coord, secondary_axis = i * primary_spacing, 'x'
809
+ elif primary_direction == 'left-to-right':
810
+ primary_coord, secondary_axis = i * primary_spacing, 'y'
811
+ else:
812
+ primary_coord, secondary_axis = i * -primary_spacing, 'y'
845
813
 
846
- if i == 0: secondary_offset_multiplier = 0.0
814
+ if i == 0:
815
+ secondary_offset_multiplier = 0.0
847
816
  else:
848
817
  start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
849
818
  magnitude = math.ceil(i / 2.0)
@@ -855,26 +824,36 @@ def kececi_layout_v4_rx(graph: rx.PyGraph, primary_spacing=1.0, secondary_spacin
855
824
  pos[node_index] = np.array([x, y])
856
825
  return pos
857
826
 
858
- def kececi_layout_v4_rustworkx(graph: rx.PyGraph, primary_spacing=1.0, secondary_spacing=1.0,
827
+ def kececi_layout_v4_rustworkx(graph:
828
+ rx.PyGraph, primary_spacing=1.0, secondary_spacing=1.0,
859
829
  primary_direction='top-down', secondary_start='right'):
860
830
  pos = {}
861
831
  nodes = sorted(graph.node_indices())
862
832
  num_nodes = len(nodes)
863
- if num_nodes == 0: return {}
833
+ if num_nodes == 0:
834
+ return {}
864
835
 
865
836
  is_vertical = primary_direction in ['top-down', 'bottom-up']
866
837
  is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
867
- if not (is_vertical or is_horizontal): raise ValueError(f"Invalid primary_direction: {primary_direction}")
868
- if is_vertical and secondary_start not in ['right', 'left']: raise ValueError(f"Invalid secondary_start for vertical: {secondary_start}")
869
- if is_horizontal and secondary_start not in ['up', 'down']: raise ValueError(f"Invalid secondary_start for horizontal: {secondary_start}")
838
+ if not (is_vertical or is_horizontal):
839
+ raise ValueError(f"Invalid primary_direction: {primary_direction}")
840
+ if is_vertical and secondary_start not in ['right', 'left']:
841
+ raise ValueError(f"Invalid secondary_start for vertical: {secondary_start}")
842
+ if is_horizontal and secondary_start not in ['up', 'down']:
843
+ raise ValueError(f"Invalid secondary_start for horizontal: {secondary_start}")
870
844
 
871
845
  for i, node_index in enumerate(nodes):
872
- if primary_direction == 'top-down': primary_coord, secondary_axis = i * -primary_spacing, 'x'
873
- elif primary_direction == 'bottom-up': primary_coord, secondary_axis = i * primary_spacing, 'x'
874
- elif primary_direction == 'left-to-right': primary_coord, secondary_axis = i * primary_spacing, 'y'
875
- else: primary_coord, secondary_axis = i * -primary_spacing, 'y'
846
+ if primary_direction == 'top-down':
847
+ primary_coord, secondary_axis = i * -primary_spacing, 'x'
848
+ elif primary_direction == 'bottom-up':
849
+ primary_coord, secondary_axis = i * primary_spacing, 'x'
850
+ elif primary_direction == 'left-to-right':
851
+ primary_coord, secondary_axis = i * primary_spacing, 'y'
852
+ else:
853
+ primary_coord, secondary_axis = i * -primary_spacing, 'y'
876
854
 
877
- if i == 0: secondary_offset_multiplier = 0.0
855
+ if i == 0:
856
+ secondary_offset_multiplier = 0.0
878
857
  else:
879
858
  start_mult = 1.0 if secondary_start in ['right', 'up'] else -1.0
880
859
  magnitude = math.ceil(i / 2.0)
@@ -890,8 +869,10 @@ def kececi_layout_v4_rustworkx(graph: rx.PyGraph, primary_spacing=1.0, secondary
890
869
  # Rastgele Graf Oluşturma Fonksiyonu (Rustworkx ile - Düzeltilmiş subgraph)
891
870
  # =============================================================================
892
871
  def generate_random_rx_graph(min_nodes=5, max_nodes=15, edge_prob_min=0.15, edge_prob_max=0.4):
893
- if min_nodes < 2: min_nodes = 2
894
- if max_nodes < min_nodes: max_nodes = min_nodes
872
+ if min_nodes < 2:
873
+ min_nodes = 2
874
+ if max_nodes < min_nodes:
875
+ max_nodes = min_nodes
895
876
  while True:
896
877
  num_nodes_target = random.randint(min_nodes, max_nodes)
897
878
  edge_probability = random.uniform(edge_prob_min, edge_prob_max)
@@ -902,22 +883,29 @@ def generate_random_rx_graph(min_nodes=5, max_nodes=15, edge_prob_min=0.15, edge
902
883
  if random.random() < edge_probability:
903
884
  G_candidate.add_edge(node_indices[i], node_indices[j], None)
904
885
 
905
- if G_candidate.num_nodes() == 0: continue
906
- if num_nodes_target > 1 and G_candidate.num_edges() == 0: continue
886
+ if G_candidate.num_nodes() == 0:
887
+ continue
888
+ if num_nodes_target > 1 and G_candidate.num_edges() == 0:
889
+ continue
907
890
 
908
891
  if not rx.is_connected(G_candidate):
909
892
  components = rx.connected_components(G_candidate)
910
- if not components: continue
893
+ if not components:
894
+ continue
911
895
  largest_cc_nodes_indices = max(components, key=len, default=set())
912
- if len(largest_cc_nodes_indices) < 2 and num_nodes_target >=2 : continue
913
- if not largest_cc_nodes_indices: continue
896
+ if len(largest_cc_nodes_indices) < 2 and num_nodes_target >=2 :
897
+ continue
898
+ if not largest_cc_nodes_indices:
899
+ continue
914
900
  # Set'i listeye çevirerek subgraph oluştur
915
901
  G = G_candidate.subgraph(list(largest_cc_nodes_indices))
916
- if G.num_nodes() == 0: continue
902
+ if G.num_nodes() == 0:
903
+ continue
917
904
  else:
918
905
  G = G_candidate
919
906
 
920
- if G.num_nodes() >= 2: break
907
+ if G.num_nodes() >= 2:
908
+ break
921
909
  print(f"Oluşturulan Rustworkx Graf: {G.num_nodes()} Düğüm, {G.num_edges()} Kenar (Başlangıç p={edge_probability:.3f})")
922
910
  return G
923
911
 
@@ -939,20 +927,28 @@ def kececi_layout_v4_pure(nodes, primary_spacing=1.0, secondary_spacing=1.0,
939
927
  sorted_nodes = list(nodes)
940
928
 
941
929
  num_nodes = len(sorted_nodes)
942
- if num_nodes == 0: return {}
930
+ if num_nodes == 0:
931
+ return {}
943
932
  is_vertical = primary_direction in ['top-down', 'bottom-up']
944
933
  is_horizontal = primary_direction in ['left-to-right', 'right-to-left']
945
- if not (is_vertical or is_horizontal): raise ValueError(f"Invalid primary_direction: {primary_direction}")
946
- if is_vertical and secondary_start not in ['right', 'left']: raise ValueError(f"Dikey yön için geçersiz secondary_start: {secondary_start}")
947
- if is_horizontal and secondary_start not in ['up', 'down']: raise ValueError(f"Yatay yön için geçersiz secondary_start: {secondary_start}")
934
+ if not (is_vertical or is_horizontal):
935
+ raise ValueError(f"Invalid primary_direction: {primary_direction}")
936
+ if is_vertical and secondary_start not in ['right', 'left']:
937
+ raise ValueError(f"Dikey yön için geçersiz secondary_start: {secondary_start}")
938
+ if is_horizontal and secondary_start not in ['up', 'down']:
939
+ raise ValueError(f"Yatay yön için geçersiz secondary_start: {secondary_start}")
948
940
 
949
941
  for i, node_id in enumerate(sorted_nodes):
950
942
  primary_coord = 0.0
951
943
  secondary_axis = ''
952
- if primary_direction == 'top-down': primary_coord, secondary_axis = i * -primary_spacing, 'x'
953
- elif primary_direction == 'bottom-up': primary_coord, secondary_axis = i * primary_spacing, 'x'
954
- elif primary_direction == 'left-to-right': primary_coord, secondary_axis = i * primary_spacing, 'y'
955
- else: primary_coord, secondary_axis = i * -primary_spacing, 'y'
944
+ if primary_direction == 'top-down':
945
+ primary_coord, secondary_axis = i * -primary_spacing, 'x'
946
+ elif primary_direction == 'bottom-up':
947
+ primary_coord, secondary_axis = i * primary_spacing, 'x'
948
+ elif primary_direction == 'left-to-right':
949
+ primary_coord, secondary_axis = i * primary_spacing, 'y'
950
+ else:
951
+ primary_coord, secondary_axis = i * -primary_spacing, 'y'
956
952
 
957
953
  secondary_offset_multiplier = 0.0
958
954
  if i > 0:
@@ -971,27 +967,37 @@ def kececi_layout_v4_pure(nodes, primary_spacing=1.0, secondary_spacing=1.0,
971
967
  # =============================================================================
972
968
  def generate_random_graph(min_nodes=0, max_nodes=200, edge_prob_min=0.15, edge_prob_max=0.4):
973
969
 
974
- if min_nodes < 2: min_nodes = 2
975
- if max_nodes < min_nodes: max_nodes = min_nodes
970
+ if min_nodes < 2:
971
+ min_nodes = 2
972
+ if max_nodes < min_nodes:
973
+ max_nodes = min_nodes
976
974
  while True:
977
975
  num_nodes_target = random.randint(min_nodes, max_nodes)
978
976
  edge_probability = random.uniform(edge_prob_min, edge_prob_max)
979
977
  G_candidate = nx.gnp_random_graph(num_nodes_target, edge_probability, seed=None)
980
- if G_candidate.number_of_nodes() == 0: continue
978
+ if G_candidate.number_of_nodes() == 0:
979
+ continue
981
980
  # Düzeltme: 0 kenarlı ama >1 düğümlü grafı da tekrar dene
982
- if num_nodes_target > 1 and G_candidate.number_of_edges() == 0 : continue
981
+ if num_nodes_target > 1 and G_candidate.number_of_edges() == 0 :
982
+ continue
983
983
 
984
984
  if not nx.is_connected(G_candidate):
985
985
  # Düzeltme: default=set() kullanmak yerine önce kontrol et
986
986
  connected_components = list(nx.connected_components(G_candidate))
987
- if not connected_components: continue # Bileşen yoksa tekrar dene
987
+ if not connected_components:
988
+ continue # Bileşen yoksa tekrar dene
988
989
  largest_cc_nodes = max(connected_components, key=len)
989
- if len(largest_cc_nodes) < 2 and num_nodes_target >=2 : continue
990
- if not largest_cc_nodes: continue # Bu aslında gereksiz ama garanti olsun
990
+ if len(largest_cc_nodes) < 2 and num_nodes_target >=2 :
991
+ continue
992
+ if not largest_cc_nodes:
993
+ continue # Bu aslında gereksiz ama garanti olsun
991
994
  G = G_candidate.subgraph(largest_cc_nodes).copy()
992
- if G.number_of_nodes() == 0: continue
993
- else: G = G_candidate
994
- if G.number_of_nodes() >= 2: break
995
+ if G.number_of_nodes() == 0:
996
+ continue
997
+ else:
998
+ G = G_candidate
999
+ if G.number_of_nodes() >= 2:
1000
+ break
995
1001
  G = nx.convert_node_labels_to_integers(G, first_label=0)
996
1002
  print(f"Oluşturulan Graf: {G.number_of_nodes()} Düğüm, {G.number_of_edges()} Kenar (Başlangıç p={edge_probability:.3f})")
997
1003
  return G
@@ -999,24 +1005,235 @@ def generate_random_graph(min_nodes=0, max_nodes=200, edge_prob_min=0.15, edge_p
999
1005
  def generate_random_graph_ig(min_nodes=0, max_nodes=200, edge_prob_min=0.15, edge_prob_max=0.4):
1000
1006
  """igraph kullanarak rastgele bağlı bir graf oluşturur."""
1001
1007
 
1002
- if min_nodes < 2: min_nodes = 2
1003
- if max_nodes < min_nodes: max_nodes = min_nodes
1008
+ if min_nodes < 2:
1009
+ min_nodes = 2
1010
+ if max_nodes < min_nodes:
1011
+ max_nodes = min_nodes
1004
1012
  while True:
1005
1013
  num_nodes_target = random.randint(min_nodes, max_nodes)
1006
1014
  edge_probability = random.uniform(edge_prob_min, edge_prob_max)
1007
1015
  g_candidate = ig.Graph.Erdos_Renyi(n=num_nodes_target, p=edge_probability, directed=False)
1008
- if g_candidate.vcount() == 0: continue
1009
- if num_nodes_target > 1 and g_candidate.ecount() == 0 : continue
1016
+ if g_candidate.vcount() == 0:
1017
+ continue
1018
+ if num_nodes_target > 1 and g_candidate.ecount() == 0 :
1019
+ continue
1010
1020
  if not g_candidate.is_connected(mode='weak'):
1011
1021
  components = g_candidate.components(mode='weak')
1012
- if not components or len(components) == 0: continue
1022
+ if not components or len(components) == 0:
1023
+ continue
1013
1024
  largest_cc_subgraph = components.giant()
1014
- if largest_cc_subgraph.vcount() < 2 and num_nodes_target >=2 : continue
1025
+ if largest_cc_subgraph.vcount() < 2 and num_nodes_target >=2 :
1026
+ continue
1015
1027
  g = largest_cc_subgraph
1016
- if g.vcount() == 0: continue
1017
- else: g = g_candidate
1018
- if g.vcount() >= 2: break
1028
+ if g.vcount() == 0:
1029
+ continue
1030
+ else:
1031
+ g = g_candidate
1032
+ if g.vcount() >= 2:
1033
+ break
1019
1034
  print(f"Oluşturulan igraph Graf: {g.vcount()} Düğüm, {g.ecount()} Kenar (Başlangıç p={edge_probability:.3f})")
1020
1035
  g.vs["label"] = [str(i) for i in range(g.vcount())]
1021
1036
  g.vs["degree"] = g.degree()
1022
1037
  return g
1038
+
1039
+ # =============================================================================
1040
+ # 1. GRAPH PROCESSING AND CONVERSION HELPERS
1041
+ # =============================================================================
1042
+
1043
+ def _get_nodes_from_graph(graph):
1044
+ """Extracts a sorted list of nodes from various graph library objects."""
1045
+ nodes = None
1046
+ if gg and isinstance(graph, gg.GraphSet):
1047
+ edges = graph.universe()
1048
+ max_node_id = max(set(itertools.chain.from_iterable(edges))) if edges else 0
1049
+ nodes = list(range(1, max_node_id + 1)) if max_node_id > 0 else []
1050
+ elif ig and isinstance(graph, ig.Graph):
1051
+ nodes = sorted([v.index for v in graph.vs])
1052
+ elif nk and isinstance(graph, nk.graph.Graph):
1053
+ nodes = sorted(list(graph.iterNodes()))
1054
+ elif rx and isinstance(graph, (rx.PyGraph, rx.PyDiGraph)):
1055
+ nodes = sorted(graph.node_indices())
1056
+ elif isinstance(graph, nx.Graph):
1057
+ try:
1058
+ nodes = sorted(list(graph.nodes()))
1059
+ except TypeError: # For non-sortable node types
1060
+ nodes = list(graph.nodes())
1061
+ else:
1062
+ supported = ["NetworkX"]
1063
+ if rx:
1064
+ supported.append("Rustworkx")
1065
+ if ig:
1066
+ supported.append("igraph")
1067
+ if nk:
1068
+ supported.append("Networkit")
1069
+ if gg:
1070
+ supported.append("Graphillion")
1071
+ raise TypeError(
1072
+ f"Unsupported graph type: {type(graph)}. Supported types: {', '.join(supported)}"
1073
+ )
1074
+ return nodes
1075
+
1076
+
1077
+ def to_networkx(graph):
1078
+ """Converts any supported graph type to a NetworkX graph."""
1079
+ if isinstance(graph, nx.Graph):
1080
+ return graph.copy()
1081
+ nx_graph = nx.Graph()
1082
+ if rx and isinstance(graph, (rx.PyGraph, rx.PyDiGraph)):
1083
+ nx_graph.add_nodes_from(graph.node_indices())
1084
+ nx_graph.add_edges_from(graph.edge_list())
1085
+ elif ig and isinstance(graph, ig.Graph):
1086
+ nx_graph.add_nodes_from(v.index for v in graph.vs)
1087
+ nx_graph.add_edges_from(graph.get_edgelist())
1088
+ elif nk and isinstance(graph, nk.graph.Graph):
1089
+ nx_graph.add_nodes_from(graph.iterNodes())
1090
+ nx_graph.add_edges_from(graph.iterEdges())
1091
+ elif gg and isinstance(graph, gg.GraphSet):
1092
+ edges = graph.universe()
1093
+ max_node_id = max(set(itertools.chain.from_iterable(edges))) if edges else 0
1094
+ if max_node_id > 0:
1095
+ nx_graph.add_nodes_from(range(1, max_node_id + 1))
1096
+ nx_graph.add_edges_from(edges)
1097
+ else:
1098
+ # This block is rarely reached as _get_nodes_from_graph would fail first
1099
+ raise TypeError(f"Unsupported graph type {type(graph)} could not be converted to NetworkX.")
1100
+ #raise TypeError(f"Desteklenmeyen graf tipi {type(graph)} NetworkX'e dönüştürülemedi.")
1101
+ return nx_graph
1102
+
1103
+
1104
+ def _kececi_layout_3d_helix(nx_graph):
1105
+ """Internal function: Arranges nodes in a helix along the Z-axis."""
1106
+ pos_3d = {}
1107
+ nodes = sorted(list(nx_graph.nodes()))
1108
+ for i, node_id in enumerate(nodes):
1109
+ angle, radius, z_step = i * (np.pi / 2.5), 1.0, i * 0.8
1110
+ pos_3d[node_id] = (np.cos(angle) * radius, np.sin(angle) * radius, z_step)
1111
+ return pos_3d
1112
+
1113
+
1114
+ # =============================================================================
1115
+ # 3. INTERNAL DRAWING STYLE IMPLEMENTATIONS
1116
+ # =============================================================================
1117
+
1118
+ def _draw_internal(nx_graph, ax, style, **kwargs):
1119
+ """Internal router that handles the different drawing styles."""
1120
+ layout_params = {
1121
+ k: v for k, v in kwargs.items()
1122
+ if k in ['primary_spacing', 'secondary_spacing', 'primary_direction',
1123
+ 'secondary_start', 'expanding']
1124
+ }
1125
+ draw_params = {k: v for k, v in kwargs.items() if k not in layout_params}
1126
+
1127
+ if style == 'curved':
1128
+ pos = kececi_layout(nx_graph, **layout_params)
1129
+ final_params = {'ax': ax, 'with_labels': True, 'node_color': '#1f78b4',
1130
+ 'node_size': 700, 'font_color': 'white',
1131
+ 'connectionstyle': 'arc3,rad=0.2', 'arrows': True}
1132
+ final_params.update(draw_params)
1133
+ with warnings.catch_warnings():
1134
+ warnings.simplefilter("ignore", UserWarning)
1135
+ nx.draw(nx_graph, pos, **final_params)
1136
+ ax.set_title("Keçeci Layout: Curved Edges")
1137
+
1138
+ elif style == 'transparent':
1139
+ pos = kececi_layout(nx_graph, **layout_params)
1140
+ nx.draw_networkx_nodes(nx_graph, pos, ax=ax, node_color='#2ca02c', node_size=700, **draw_params)
1141
+ nx.draw_networkx_labels(nx_graph, pos, ax=ax, font_color='white')
1142
+ edge_lengths = {e: np.linalg.norm(np.array(pos[e[0]]) - np.array(pos[e[1]])) for e in nx_graph.edges()}
1143
+ max_len = max(edge_lengths.values()) if edge_lengths else 1.0
1144
+ for edge, length in edge_lengths.items():
1145
+ alpha = 0.15 + 0.85 * (1 - length / max_len)
1146
+ nx.draw_networkx_edges(nx_graph, pos, edgelist=[edge], ax=ax, width=1.5, edge_color='black', alpha=alpha)
1147
+ ax.set_title("Keçeci Layout: Transparent Edges")
1148
+
1149
+ elif style == '3d':
1150
+ pos_3d = _kececi_layout_3d_helix(nx_graph)
1151
+ node_color = draw_params.get('node_color', '#d62728')
1152
+ edge_color = draw_params.get('edge_color', 'gray')
1153
+ for node, (x, y, z) in pos_3d.items():
1154
+ ax.scatter([x], [y], [z], s=200, c=[node_color], depthshade=True)
1155
+ ax.text(x, y, z, f' {node}', size=10, zorder=1, color='k')
1156
+ for u, v in nx_graph.edges():
1157
+ coords = np.array([pos_3d[u], pos_3d[v]])
1158
+ ax.plot(coords[:, 0], coords[:, 1], coords[:, 2], color=edge_color, alpha=0.8)
1159
+ ax.set_title("Keçeci Layout: 3D Helix")
1160
+ ax.set_axis_off()
1161
+ ax.view_init(elev=20, azim=-60)
1162
+
1163
+
1164
+ # =============================================================================
1165
+ # 4. MAIN USER-FACING DRAWING FUNCTION
1166
+ # =============================================================================
1167
+
1168
+ def draw_kececi(graph, style='curved', ax=None, **kwargs):
1169
+ """
1170
+ Draws a graph using the Keçeci Layout with a specified style.
1171
+
1172
+ This function automatically handles graphs from different libraries
1173
+ (NetworkX, Rustworkx, igraph, etc.).
1174
+
1175
+ Args:
1176
+ graph: The graph object to be drawn.
1177
+ style (str): The drawing style. Options: 'curved', 'transparent', '3d'.
1178
+ ax (matplotlib.axis.Axis, optional): The axis to draw on. If not
1179
+ provided, a new figure and axis are created.
1180
+ **kwargs: Additional keyword arguments passed to both `kececi_layout`
1181
+ and the drawing functions (e.g., expanding=True, node_size=500).
1182
+
1183
+ Returns:
1184
+ matplotlib.axis.Axis: The axis object where the graph was drawn.
1185
+ """
1186
+ nx_graph = to_networkx(graph)
1187
+ is_3d = (style.lower() == '3d')
1188
+
1189
+ if ax is None:
1190
+ fig = plt.figure(figsize=(10, 8))
1191
+ projection = '3d' if is_3d else None
1192
+ ax = fig.add_subplot(111, projection=projection)
1193
+
1194
+ if is_3d and getattr(ax, 'name', '') != '3d':
1195
+ raise ValueError("The '3d' style requires an axis with 'projection=\"3d\"'.")
1196
+
1197
+ draw_styles = ['curved', 'transparent', '3d']
1198
+ if style.lower() not in draw_styles:
1199
+ raise ValueError(f"Invalid style: '{style}'. Options are: {draw_styles}")
1200
+
1201
+ _draw_internal(nx_graph, ax, style.lower(), **kwargs)
1202
+ return ax
1203
+
1204
+
1205
+ # =============================================================================
1206
+ # MODULE TEST CODE
1207
+ # =============================================================================
1208
+
1209
+ if __name__ == '__main__':
1210
+ print("Testing kececilayout.py module...")
1211
+ G_test = nx.gnp_random_graph(12, 0.3, seed=42)
1212
+
1213
+ # Compare expanding=False (parallel) vs. expanding=True ('v4' style)
1214
+ fig_v4 = plt.figure(figsize=(16, 7))
1215
+ fig_v4.suptitle("Effect of the `expanding` Parameter", fontsize=20)
1216
+ ax_v4_1 = fig_v4.add_subplot(1, 2, 1)
1217
+ draw_kececi(G_test, ax=ax_v4_1, style='curved',
1218
+ primary_direction='left_to_right', secondary_start='up',
1219
+ expanding=False)
1220
+ ax_v4_1.set_title("Parallel Style (expanding=False)", fontsize=16)
1221
+
1222
+ ax_v4_2 = fig_v4.add_subplot(1, 2, 2)
1223
+ draw_kececi(G_test, ax=ax_v4_2, style='curved',
1224
+ primary_direction='left_to_right', secondary_start='up',
1225
+ expanding=True)
1226
+ ax_v4_2.set_title("Expanding 'v4' Style (expanding=True)", fontsize=16)
1227
+ plt.show()
1228
+
1229
+ # Test all advanced drawing styles
1230
+ fig_styles = plt.figure(figsize=(18, 12))
1231
+ fig_styles.suptitle("Advanced Drawing Styles Test", fontsize=20)
1232
+ draw_kececi(G_test, style='curved', ax=fig_styles.add_subplot(2, 2, 1),
1233
+ primary_direction='left_to_right', secondary_start='up', expanding=True)
1234
+ draw_kececi(G_test, style='transparent', ax=fig_styles.add_subplot(2, 2, 2),
1235
+ primary_direction='top_down', secondary_start='left', expanding=True, node_color='purple')
1236
+ draw_kececi(G_test, style='3d', ax=fig_styles.add_subplot(2, 2, (3, 4), projection='3d'))
1237
+ plt.tight_layout(rect=[0, 0, 1, 0.96])
1238
+ plt.show()
1239
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kececilayout
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: A deterministic node placement algorithm used in graph visualization. In this layout, nodes are arranged sequentially along a defined primary axis. Each subsequent node is then alternately offset along a secondary, perpendicular axis, typically moving to one side of the primary axis and then the other. Often, the magnitude of this secondary offset increases as nodes progress along the primary axis, creating a characteristic zig-zag or serpentine pattern.
5
5
  Home-page: https://github.com/WhiteSymmetry/kececilayout
6
6
  Author: Mehmet Keçeci
@@ -64,6 +64,8 @@ Dynamic: summary
64
64
  [![PyPI version](https://badge.fury.io/py/kececilayout.svg)](https://badge.fury.io/py/kececilayout)
65
65
  [![PyPI Downloads](https://static.pepy.tech/badge/kececilayout)](https://pepy.tech/projects/kececilayout)
66
66
  [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md)
67
+ [![CI/CD](https://github.com/WhiteSymmetry/kececilayout/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/WhiteSymmetry/kececilayout/actions/workflows/ci-cd.yml)
68
+ [![Linted with Ruff](https://img.shields.io/badge/Linted%20with-Ruff-green?logo=python&logoColor=white)](https://github.com/astral-sh/ruff)
67
69
 
68
70
  ---
69
71
 
@@ -762,6 +764,10 @@ If this library was useful to you in your research, please cite us. Following th
762
764
 
763
765
  ```
764
766
 
767
+ Keçeci, M. (2025). The Keçeci Layout: A Deterministic Visualisation Framework for the Structural Analysis of Ordered Systems in Chemistry and Environmental Science. Open Science Articles (OSAs), Zenodo. https://doi.org/10.5281/zenodo.16696713
768
+
769
+ Keçeci, M. (2025). The Keçeci Layout: A Deterministic, Order-Preserving Visualization Algorithm for Structured Systems. Open Science Articles (OSAs), Zenodo. https://doi.org/10.5281/zenodo.16526798
770
+
765
771
  Keçeci, M. (2025). Keçeci Deterministic Zigzag Layout. WorkflowHub. https://doi.org/10.48546/workflowhub.document.31.1
766
772
 
767
773
  Keçeci, M. (2025). Keçeci Zigzag Layout Algorithm. Authorea. https://doi.org/10.22541/au.175087581.16524538/v1
@@ -780,7 +786,7 @@ Keçeci, M. (2025, July 3). The Keçeci Layout: A Structural Approach for Interd
780
786
 
781
787
  Keçeci, M. (2025). Beyond Topology: Deterministic and Order-Preserving Graph Visualization with the Keçeci Layout. WorkflowHub. https://doi.org/10.48546/workflowhub.document.34.4
782
788
 
783
- Keçeci, M. (2025). A Graph-Theoretic Perspective on the Keçeci Layout: Structuring Cross-Disciplinary Inquiry. Preprints. https://doi.org/10.20944/preprints202507.0589
789
+ Keçeci, M. (2025). A Graph-Theoretic Perspective on the Keçeci Layout: Structuring Cross-Disciplinary Inquiry. Preprints. https://doi.org/10.20944/preprints202507.0589.v1
784
790
 
785
791
  Keçeci, M. (2025). Keçeci Layout. Open Science Articles (OSAs), Zenodo. https://doi.org/10.5281/zenodo.15314328
786
792
 
@@ -793,9 +799,16 @@ Keçeci, M. (2025, May 1). Kececilayout. Open Science Articles (OSAs), Zenodo. h
793
799
  ### Chicago
794
800
 
795
801
  ```
802
+
803
+ Keçeci, Mehmet. The Keçeci Layout: A Deterministic Visualisation Framework for the Structural Analysis of Ordered Systems in Chemistry and Environmental Science. Open Science Articles (OSAs), Zenodo, 2025. https://doi.org/10.5281/zenodo.16696713
804
+
805
+ Keçeci, Mehmet. The Keçeci Layout: A Deterministic, Order-Preserving Visualization Algorithm for Structured Systems. Open Science Articles (OSAs), Zenodo, 2025. https://doi.org/10.5281/zenodo.16526798
806
+
796
807
  Keçeci, Mehmet. kececilayout [Data set]. WorkflowHub, 2025. https://doi.org/10.48546/workflowhub.datafile.17.1
797
808
 
798
- Keçeci, Mehmet. "Kececilayout". Zenodo, 01 May 2025. https://doi.org/10.5281/zenodo.15313946.
809
+ Keçeci, Mehmet. "Kececilayout". Open Science Articles (OSAs), Zenodo, 2025. https://doi.org/10.5281/zenodo.15313946.
799
810
 
800
- Keçeci, Mehmet. "Keçeci Layout", 01 May 2025. https://doi.org/10.5281/zenodo.15314329.
811
+ Keçeci, Mehmet. "Keçeci Layout". Open Science Articles (OSAs), Zenodo, 2025. https://doi.org/10.5281/zenodo.15314329.
801
812
  ```
813
+
814
+
@@ -0,0 +1,8 @@
1
+ kececilayout/__init__.py,sha256=hEJG4kuebGgqJwadlVvB_3cVM18bdws0qwmUEphnlXo,2137
2
+ kececilayout/_version.py,sha256=161LHS7Qzd90Krd87UH7JrPx1CGK0Oq1hWGwkNbJZyc,812
3
+ kececilayout/kececi_layout.py,sha256=31VqXPSVUzKyptMfzuIaAJi1NtinuXCWw-bGv-z0TRg,56397
4
+ kececilayout-0.2.6.dist-info/licenses/LICENSE,sha256=NJZsJEbQuKzxn1mWPWCbRx8jRUqGS22thl8wwuRQJ9c,1071
5
+ kececilayout-0.2.6.dist-info/METADATA,sha256=R20kQoVU1K4WV2tT4HAWOj7gOeBL8jp6d5UAow15mBY,34377
6
+ kececilayout-0.2.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ kececilayout-0.2.6.dist-info/top_level.txt,sha256=OBzN_wm4q-iwSkeACF4E8ET_LFLJKBTldSH3D1jG2hA,13
8
+ kececilayout-0.2.6.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- kececilayout/__init__.py,sha256=AlH_il7DPamlzF7KcYWg0YmBB_uPq8nXxQH7f79YZYM,1463
2
- kececilayout/_version.py,sha256=50lt3GrP2yq9W8tzAp0g9rviB8C_JanrD7tayR3IN6A,812
3
- kececilayout/kececi_layout.py,sha256=oICgD-yRnzWsS6I8n_DLQMXSyoG07PngIiigJkOY9Ko,49638
4
- kececilayout-0.2.4.dist-info/licenses/LICENSE,sha256=NJZsJEbQuKzxn1mWPWCbRx8jRUqGS22thl8wwuRQJ9c,1071
5
- kececilayout-0.2.4.dist-info/METADATA,sha256=EeLPD6ixa1ZPwoxkD3n1DvHduDB5Zr0XrKCt3zs14fs,33106
6
- kececilayout-0.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
- kececilayout-0.2.4.dist-info/top_level.txt,sha256=OBzN_wm4q-iwSkeACF4E8ET_LFLJKBTldSH3D1jG2hA,13
8
- kececilayout-0.2.4.dist-info/RECORD,,