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 +48 -26
- kececilayout/_version.py +1 -1
- kececilayout/kececi_layout.py +504 -287
- {kececilayout-0.2.4.dist-info → kececilayout-0.2.6.dist-info}/METADATA +17 -4
- kececilayout-0.2.6.dist-info/RECORD +8 -0
- kececilayout-0.2.4.dist-info/RECORD +0 -8
- {kececilayout-0.2.4.dist-info → kececilayout-0.2.6.dist-info}/WHEEL +0 -0
- {kececilayout-0.2.4.dist-info → kececilayout-0.2.6.dist-info}/licenses/LICENSE +0 -0
- {kececilayout-0.2.4.dist-info → kececilayout-0.2.6.dist-info}/top_level.txt +0 -0
kececilayout/__init__.py
CHANGED
|
@@ -1,37 +1,59 @@
|
|
|
1
1
|
# __init__.py
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
9
|
+
import inspect
|
|
7
10
|
import warnings
|
|
8
|
-
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
#
|
|
22
|
-
|
|
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
|
-
|
|
25
|
-
|
|
45
|
+
This is an old function scheduled for removal.
|
|
46
|
+
Please use alternative functions.
|
|
26
47
|
"""
|
|
27
48
|
warnings.warn(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
#
|
|
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.
|
|
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"
|
kececilayout/kececi_layout.py
CHANGED
|
@@ -1,21 +1,37 @@
|
|
|
1
|
-
#
|
|
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
|
|
7
|
-
|
|
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
|
|
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
|
-
#
|
|
24
|
+
# Ana bağımlılıklar (çizim için gerekli)
|
|
15
25
|
try:
|
|
16
26
|
import networkx as nx
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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 = {}
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
111
|
+
primary_coord, secondary_axis = i * -primary_spacing, 'x'
|
|
145
112
|
elif primary_direction == 'bottom-up':
|
|
146
|
-
primary_coord = i * primary_spacing
|
|
113
|
+
primary_coord, secondary_axis = i * primary_spacing, 'x'
|
|
147
114
|
elif primary_direction == 'left-to-right':
|
|
148
|
-
primary_coord = i * primary_spacing
|
|
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
|
-
|
|
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
|
-
|
|
162
|
-
if
|
|
163
|
-
|
|
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
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
143
|
+
This function is compatible with graphs from NetworkX, Rustworkx, igraph,
|
|
144
|
+
Networkit, and Graphillion.
|
|
191
145
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
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 =
|
|
199
|
-
if max_node_id > 0
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
243
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
267
|
-
elif primary_direction == '
|
|
268
|
-
primary_coord = i * primary_spacing
|
|
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
|
|
271
|
-
else:
|
|
272
|
-
primary_coord = i * -primary_spacing
|
|
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
|
-
|
|
275
|
-
if i
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
281
|
-
secondary_coord = secondary_offset_multiplier * secondary_spacing
|
|
215
|
+
secondary_offset = start_multiplier * magnitude * side * secondary_spacing
|
|
282
216
|
|
|
283
|
-
|
|
284
|
-
|
|
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:
|
|
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):
|
|
307
|
-
|
|
308
|
-
if
|
|
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':
|
|
313
|
-
|
|
314
|
-
elif primary_direction == '
|
|
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:
|
|
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):
|
|
353
|
-
|
|
354
|
-
if
|
|
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':
|
|
359
|
-
|
|
360
|
-
elif primary_direction == '
|
|
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;
|
|
689
|
+
primary_coord = i * -primary_spacing;
|
|
690
|
+
secondary_axis = 'x'
|
|
745
691
|
elif primary_direction == 'bottom-up':
|
|
746
|
-
primary_coord = i * primary_spacing;
|
|
692
|
+
primary_coord = i * primary_spacing;
|
|
693
|
+
secondary_axis = 'x'
|
|
747
694
|
elif primary_direction == 'left-to-right':
|
|
748
|
-
primary_coord = i * primary_spacing;
|
|
695
|
+
primary_coord = i * primary_spacing;
|
|
696
|
+
secondary_axis = 'y'
|
|
749
697
|
else: # right-to-left
|
|
750
|
-
primary_coord = i * -primary_spacing;
|
|
698
|
+
primary_coord = i * -primary_spacing;
|
|
699
|
+
secondary_axis = 'y'
|
|
751
700
|
|
|
752
|
-
if i == 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':
|
|
761
|
-
|
|
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;
|
|
757
|
+
primary_coord = i * -primary_spacing;
|
|
758
|
+
secondary_axis = 'x'
|
|
806
759
|
elif primary_direction == 'bottom-up':
|
|
807
|
-
primary_coord = i * primary_spacing;
|
|
760
|
+
primary_coord = i * primary_spacing;
|
|
761
|
+
secondary_axis = 'x'
|
|
808
762
|
elif primary_direction == 'left-to-right':
|
|
809
|
-
primary_coord = i * primary_spacing;
|
|
763
|
+
primary_coord = i * primary_spacing;
|
|
764
|
+
secondary_axis = 'y'
|
|
810
765
|
else: # right-to-left
|
|
811
|
-
primary_coord = i * -primary_spacing;
|
|
766
|
+
primary_coord = i * -primary_spacing;
|
|
767
|
+
secondary_axis = 'y'
|
|
812
768
|
|
|
813
|
-
if i == 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':
|
|
822
|
-
|
|
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:
|
|
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:
|
|
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):
|
|
837
|
-
|
|
838
|
-
if
|
|
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':
|
|
842
|
-
|
|
843
|
-
elif primary_direction == '
|
|
844
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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):
|
|
868
|
-
|
|
869
|
-
if
|
|
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':
|
|
873
|
-
|
|
874
|
-
elif primary_direction == '
|
|
875
|
-
|
|
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:
|
|
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:
|
|
894
|
-
|
|
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:
|
|
906
|
-
|
|
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:
|
|
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 :
|
|
913
|
-
|
|
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:
|
|
902
|
+
if G.num_nodes() == 0:
|
|
903
|
+
continue
|
|
917
904
|
else:
|
|
918
905
|
G = G_candidate
|
|
919
906
|
|
|
920
|
-
if G.num_nodes() >= 2:
|
|
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:
|
|
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):
|
|
946
|
-
|
|
947
|
-
if
|
|
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':
|
|
953
|
-
|
|
954
|
-
elif primary_direction == '
|
|
955
|
-
|
|
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:
|
|
975
|
-
|
|
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:
|
|
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 :
|
|
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:
|
|
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 :
|
|
990
|
-
|
|
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:
|
|
993
|
-
|
|
994
|
-
|
|
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:
|
|
1003
|
-
|
|
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:
|
|
1009
|
-
|
|
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:
|
|
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 :
|
|
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:
|
|
1017
|
-
|
|
1018
|
-
|
|
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.
|
|
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
|
[](https://badge.fury.io/py/kececilayout)
|
|
65
65
|
[](https://pepy.tech/projects/kececilayout)
|
|
66
66
|
[](CODE_OF_CONDUCT.md)
|
|
67
|
+
[](https://github.com/WhiteSymmetry/kececilayout/actions/workflows/ci-cd.yml)
|
|
68
|
+
[](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,
|
|
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",
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|