kececilayout 0.4.9__py3-none-any.whl → 0.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- docs/conf.py +97 -0
- kececilayout/__init__.py +29 -3
- kececilayout/_version.py +3 -5
- kececilayout/kececi_layout.py +587 -6
- {kececilayout-0.4.9.dist-info → kececilayout-0.5.1.dist-info}/METADATA +758 -25
- kececilayout-0.5.1.dist-info/RECORD +10 -0
- {kececilayout-0.4.9.dist-info → kececilayout-0.5.1.dist-info}/WHEEL +1 -1
- kececilayout-0.5.1.dist-info/licenses/LICENSE +661 -0
- {kececilayout-0.4.9.dist-info → kececilayout-0.5.1.dist-info}/top_level.txt +2 -0
- tests/test_sample.py +261 -0
- kececilayout-0.4.9.dist-info/RECORD +0 -8
- kececilayout-0.4.9.dist-info/licenses/LICENSE +0 -21
kececilayout/kececi_layout.py
CHANGED
|
@@ -5,8 +5,25 @@ kececilayout.py
|
|
|
5
5
|
|
|
6
6
|
This module provides sequential-zigzag ("Keçeci Layout") and advanced visualization styles for various Python graph libraries.
|
|
7
7
|
Bu modül, çeşitli Python graf kütüphaneleri için sıralı-zigzag ("Keçeci Layout") ve gelişmiş görselleştirme stilleri sağlar.
|
|
8
|
+
|
|
9
|
+
**Key Features:**
|
|
10
|
+
* **Linear Focus:** Ideal for visualizing paths, chains, or ordered processes.
|
|
11
|
+
* **Deterministic:** Produces identical results for the same input.
|
|
12
|
+
* **Overlap Reduction:** Prevents node collisions by spreading them across axes.
|
|
13
|
+
* **Parametric:** Fully customizable with parameters like `primary_spacing`, `secondary_spacing`, `primary_direction`, and `secondary_start`.
|
|
14
|
+
|
|
15
|
+
**v0.2.7**: Curved, transparent, 3D, and `expanding=True` styles supported.
|
|
16
|
+
|
|
17
|
+
**v0.5.0:**
|
|
18
|
+
|
|
19
|
+
layouts = ['2d', 'cylindrical', 'cubic', 'spherical', 'elliptical', 'toric']
|
|
20
|
+
|
|
21
|
+
styles = ['standard', 'default', 'curved', 'helix', '3d', 'weighted', 'colored']
|
|
22
|
+
|
|
23
|
+
**v0.5.1:** edge (kececi_layout_edge)
|
|
8
24
|
"""
|
|
9
25
|
|
|
26
|
+
from collections import defaultdict
|
|
10
27
|
import graphillion as gg
|
|
11
28
|
import igraph as ig
|
|
12
29
|
import itertools # Graphillion için eklendi
|
|
@@ -16,9 +33,11 @@ from mpl_toolkits.mplot3d import Axes3D
|
|
|
16
33
|
import networkit as nk
|
|
17
34
|
import networkx as nx
|
|
18
35
|
import numpy as np # rustworkx
|
|
36
|
+
from numba import jit
|
|
19
37
|
import platform # graph_tool için
|
|
20
38
|
import random
|
|
21
39
|
import rustworkx as rx
|
|
40
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
22
41
|
import warnings
|
|
23
42
|
|
|
24
43
|
|
|
@@ -58,6 +77,79 @@ if platform.system() == "Linux":
|
|
|
58
77
|
else:
|
|
59
78
|
gt = None
|
|
60
79
|
|
|
80
|
+
"""
|
|
81
|
+
@jit(nopython=True)
|
|
82
|
+
def calculate_coordinates(nodes, primary_spacing, secondary_spacing, primary_direction, secondary_start, expanding):
|
|
83
|
+
#Numba ile hızlandırılmış koordinat hesaplama.
|
|
84
|
+
pos = {}
|
|
85
|
+
for i, node_id in enumerate(nodes):
|
|
86
|
+
# Koordinat hesaplama mantığı...
|
|
87
|
+
pos[node_id] = (x, y)
|
|
88
|
+
return pos
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
@jit(nopython=True)
|
|
92
|
+
def calculate_coordinates(
|
|
93
|
+
nodes: list,
|
|
94
|
+
primary_spacing: float,
|
|
95
|
+
secondary_spacing: float,
|
|
96
|
+
primary_direction: str,
|
|
97
|
+
secondary_start: str,
|
|
98
|
+
expanding: bool
|
|
99
|
+
) -> dict:
|
|
100
|
+
"""
|
|
101
|
+
Numba ile hızlandırılmış koordinat hesaplama fonksiyonu.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
nodes: Düğümlerin listesi.
|
|
105
|
+
primary_spacing: Birincil eksendeki düğümler arası mesafe.
|
|
106
|
+
secondary_spacing: İkincil eksendeki zigzag ofseti.
|
|
107
|
+
primary_direction: Birincil yön ('left-to-right', 'right-to-left', 'top_down', 'bottom_up').
|
|
108
|
+
secondary_start: Zigzag'ın başlangıç yönü ('up', 'down', 'left', 'right').
|
|
109
|
+
expanding: Zigzag ofsetinin büyümesi gerekip gerekmediği (True/False).
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
dict: Düğümlerin koordinatlarını içeren sözlük. Örneğin: {0: (x, y), 1: (x, y), ...}.
|
|
113
|
+
"""
|
|
114
|
+
pos = {}
|
|
115
|
+
n = len(nodes)
|
|
116
|
+
|
|
117
|
+
for i in range(n):
|
|
118
|
+
node_id = nodes[i]
|
|
119
|
+
primary_coord = 0.0
|
|
120
|
+
secondary_axis = ''
|
|
121
|
+
|
|
122
|
+
# Birincil eksen koordinatını hesapla
|
|
123
|
+
if primary_direction == 'left-to-right':
|
|
124
|
+
primary_coord = i * primary_spacing
|
|
125
|
+
secondary_axis = 'y'
|
|
126
|
+
elif primary_direction == 'right-to-left':
|
|
127
|
+
primary_coord = -i * primary_spacing
|
|
128
|
+
secondary_axis = 'y'
|
|
129
|
+
elif primary_direction == 'top_down':
|
|
130
|
+
primary_coord = -i * primary_spacing
|
|
131
|
+
secondary_axis = 'x'
|
|
132
|
+
elif primary_direction == 'bottom_up':
|
|
133
|
+
primary_coord = i * primary_spacing
|
|
134
|
+
secondary_axis = 'x'
|
|
135
|
+
|
|
136
|
+
# İkincil eksen ofsetini hesapla
|
|
137
|
+
secondary_offset = 0.0
|
|
138
|
+
if i > 0:
|
|
139
|
+
start_multiplier = 1.0 if secondary_start in ['right', 'up'] else -1.0
|
|
140
|
+
magnitude = math.ceil(i / 2.0) if expanding else 1.0
|
|
141
|
+
side = 1 if i % 2 != 0 else -1
|
|
142
|
+
secondary_offset = start_multiplier * magnitude * side * secondary_spacing
|
|
143
|
+
|
|
144
|
+
# Koordinatları ata
|
|
145
|
+
if secondary_axis == 'x':
|
|
146
|
+
x, y = (secondary_offset, primary_coord)
|
|
147
|
+
else:
|
|
148
|
+
x, y = (primary_coord, secondary_offset)
|
|
149
|
+
|
|
150
|
+
pos[node_id] = (x, y)
|
|
151
|
+
|
|
152
|
+
return pos
|
|
61
153
|
|
|
62
154
|
def find_max_node_id(edges):
|
|
63
155
|
"""
|
|
@@ -170,12 +262,104 @@ def kececi_layout(graph, primary_spacing=1.0, secondary_spacing=1.0,
|
|
|
170
262
|
pos[node_id] = (x, y)
|
|
171
263
|
return pos
|
|
172
264
|
|
|
265
|
+
def kececi_layout_edge(graph: Any,
|
|
266
|
+
primary_spacing: float = 1.0,
|
|
267
|
+
secondary_spacing: float = 1.0,
|
|
268
|
+
primary_direction: str = 'top_down',
|
|
269
|
+
secondary_start: str = 'right',
|
|
270
|
+
expanding: bool = True,
|
|
271
|
+
edge: bool = True) -> Dict[Any, Tuple[float, float]]:
|
|
272
|
+
"""Deterministik O(n) layout — edge farkındalıklı mod ile."""
|
|
273
|
+
nodes, edges = _extract_graph_data(graph)
|
|
274
|
+
_validate_directions(primary_direction, secondary_start)
|
|
275
|
+
|
|
276
|
+
if edge and edges:
|
|
277
|
+
degree = defaultdict(int)
|
|
278
|
+
for u, v in edges:
|
|
279
|
+
degree[u] += 1
|
|
280
|
+
degree[v] += 1
|
|
281
|
+
nodes = sorted(nodes, key=lambda n: (-degree.get(n, 0), str(n)))
|
|
282
|
+
|
|
283
|
+
return _compute_positions(
|
|
284
|
+
nodes, primary_spacing, secondary_spacing,
|
|
285
|
+
primary_direction, secondary_start, expanding
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
def _validate_directions(pd: str, ss: str) -> None:
|
|
289
|
+
VERTICAL = {'top_down', 'bottom_up'}
|
|
290
|
+
HORIZONTAL = {'left-to-right', 'right-to-left'}
|
|
291
|
+
|
|
292
|
+
if pd in VERTICAL and ss not in {'left', 'right'}:
|
|
293
|
+
raise ValueError(
|
|
294
|
+
f"Invalid secondary_start '{ss}' for vertical direction '{pd}'\n"
|
|
295
|
+
f"✓ Use: 'left' or 'right' (e.g., secondary_start='right')"
|
|
296
|
+
)
|
|
297
|
+
if pd in HORIZONTAL and ss not in {'up', 'down'}:
|
|
298
|
+
raise ValueError(
|
|
299
|
+
f"Invalid secondary_start '{ss}' for horizontal direction '{pd}'\n"
|
|
300
|
+
f"✓ Use: 'up' or 'down' (e.g., secondary_start='up')"
|
|
301
|
+
)
|
|
302
|
+
if pd not in VERTICAL and pd not in HORIZONTAL:
|
|
303
|
+
raise ValueError(f"Invalid primary_direction: '{pd}'")
|
|
304
|
+
|
|
305
|
+
def _extract_graph_data(graph: Any) -> Tuple[List[Any], List[Tuple[Any, Any]]]:
|
|
306
|
+
# Rustworkx
|
|
307
|
+
try:
|
|
308
|
+
import rustworkx as rx
|
|
309
|
+
if isinstance(graph, (rx.PyGraph, rx.PyDiGraph)):
|
|
310
|
+
nodes = sorted(int(u) for u in graph.node_indices())
|
|
311
|
+
edges = [(int(u), int(v)) for u, v in graph.edge_list()]
|
|
312
|
+
return nodes, edges
|
|
313
|
+
except (ImportError, AttributeError, NameError):
|
|
314
|
+
pass
|
|
315
|
+
|
|
316
|
+
# NetworkX (fallback)
|
|
317
|
+
try:
|
|
318
|
+
import networkx as nx
|
|
319
|
+
if isinstance(graph, (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)):
|
|
320
|
+
try:
|
|
321
|
+
nodes = sorted(graph.nodes())
|
|
322
|
+
except TypeError:
|
|
323
|
+
nodes = list(graph.nodes())
|
|
324
|
+
edges = [(u, v) for u, v in graph.edges()]
|
|
325
|
+
return nodes, edges
|
|
326
|
+
except (ImportError, AttributeError, NameError):
|
|
327
|
+
pass
|
|
328
|
+
|
|
329
|
+
raise TypeError(
|
|
330
|
+
f"Unsupported graph type: {type(graph).__name__}\n"
|
|
331
|
+
"Supported: NetworkX, Rustworkx"
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
def _compute_positions(nodes: List[Any],
|
|
335
|
+
ps: float, ss: float,
|
|
336
|
+
pd: str, sc: str, exp: bool) -> Dict[Any, Tuple[float, float]]:
|
|
337
|
+
pos = {}
|
|
338
|
+
for i, node in enumerate(nodes):
|
|
339
|
+
if pd == 'top_down':
|
|
340
|
+
pc, sa = i * -ps, 'x'
|
|
341
|
+
elif pd == 'bottom_up':
|
|
342
|
+
pc, sa = i * ps, 'x'
|
|
343
|
+
elif pd == 'left-to-right':
|
|
344
|
+
pc, sa = i * ps, 'y'
|
|
345
|
+
else: # right-to-left
|
|
346
|
+
pc, sa = i * -ps, 'y'
|
|
347
|
+
|
|
348
|
+
so = 0.0
|
|
349
|
+
if i > 0:
|
|
350
|
+
sm = 1.0 if sc in {'right', 'up'} else -1.0
|
|
351
|
+
mag = math.ceil(i / 2.0) if exp else 1.0
|
|
352
|
+
side = 1 if i % 2 else -1
|
|
353
|
+
so = sm * mag * side * ss
|
|
354
|
+
|
|
355
|
+
pos[node] = (so, pc) if sa == 'x' else (pc, so)
|
|
356
|
+
return pos
|
|
357
|
+
|
|
173
358
|
# =============================================================================
|
|
174
359
|
# 1. TEMEL LAYOUT HESAPLAMA FONKSİYONU (2D)
|
|
175
360
|
# Bu fonksiyon sadece koordinatları hesaplar, çizim yapmaz.
|
|
176
361
|
# 1. LAYOUT CALCULATION FUNCTION (UNIFIED AND IMPROVED)
|
|
177
362
|
# =============================================================================
|
|
178
|
-
|
|
179
363
|
def kececi_layout_v4(graph, primary_spacing=1.0, secondary_spacing=1.0,
|
|
180
364
|
primary_direction='top_down', secondary_start='right',
|
|
181
365
|
expanding=True):
|
|
@@ -268,7 +452,7 @@ def kececi_layout_nx(graph, primary_spacing=1.0, secondary_spacing=1.0,
|
|
|
268
452
|
primary_direction (str): 'top_down', 'bottom_up', 'left-to-right', 'right-to-left'.
|
|
269
453
|
secondary_start (str): Initial direction for the zigzag offset.
|
|
270
454
|
expanding (bool): If True (default), the zigzag offset grows.
|
|
271
|
-
If False, the offset is constant (parallel lines).
|
|
455
|
+
If False, the offset is constant (parallel lines).
|
|
272
456
|
|
|
273
457
|
Returns:
|
|
274
458
|
dict: A dictionary of positions keyed by node ID.
|
|
@@ -327,7 +511,7 @@ def kececi_layout_networkx(graph, primary_spacing=1.0, secondary_spacing=1.0,
|
|
|
327
511
|
primary_direction (str): 'top_down', 'bottom_up', 'left-to-right', 'right-to-left'.
|
|
328
512
|
secondary_start (str): Initial direction for the zigzag offset.
|
|
329
513
|
expanding (bool): If True (default), the zigzag offset grows.
|
|
330
|
-
If False, the offset is constant (parallel lines).
|
|
514
|
+
If False, the offset is constant (parallel lines).
|
|
331
515
|
|
|
332
516
|
Returns:
|
|
333
517
|
dict: A dictionary of positions keyed by node ID.
|
|
@@ -1420,6 +1604,228 @@ def generate_distinct_colors(n):
|
|
|
1420
1604
|
colors.append(adjusted_rgb)
|
|
1421
1605
|
return colors
|
|
1422
1606
|
|
|
1607
|
+
# 2D Layout
|
|
1608
|
+
def kececi_layout_2d(
|
|
1609
|
+
nx_graph: nx.Graph,
|
|
1610
|
+
primary_spacing: float = 1.0,
|
|
1611
|
+
secondary_spacing: float = 1.0,
|
|
1612
|
+
primary_direction: str = 'left-to-right',
|
|
1613
|
+
secondary_start: str = 'up',
|
|
1614
|
+
expanding: bool = True
|
|
1615
|
+
) -> Dict[int, Tuple[float, float]]:
|
|
1616
|
+
pos = {}
|
|
1617
|
+
nodes = sorted(list(nx_graph.nodes()))
|
|
1618
|
+
|
|
1619
|
+
for i, node_id in enumerate(nodes):
|
|
1620
|
+
if primary_direction == 'left-to-right':
|
|
1621
|
+
primary_coord, secondary_axis = i * primary_spacing, 'y'
|
|
1622
|
+
elif primary_direction == 'right-to-left':
|
|
1623
|
+
primary_coord, secondary_axis = i * -primary_spacing, 'y'
|
|
1624
|
+
elif primary_direction == 'top_down':
|
|
1625
|
+
primary_coord, secondary_axis = i * -primary_spacing, 'x'
|
|
1626
|
+
else: # 'bottom_up'
|
|
1627
|
+
primary_coord, secondary_axis = i * primary_spacing, 'x'
|
|
1628
|
+
|
|
1629
|
+
secondary_offset = 0.0
|
|
1630
|
+
if i > 0:
|
|
1631
|
+
start_multiplier = 1.0 if secondary_start in ['right', 'up'] else -1.0
|
|
1632
|
+
magnitude = math.ceil(i / 2.0) if expanding else 1.0
|
|
1633
|
+
side = 1 if i % 2 != 0 else -1
|
|
1634
|
+
secondary_offset = start_multiplier * magnitude * side * secondary_spacing
|
|
1635
|
+
|
|
1636
|
+
x, y = (secondary_offset, primary_coord) if secondary_axis == 'x' else (primary_coord, secondary_offset)
|
|
1637
|
+
pos[node_id] = (x, y)
|
|
1638
|
+
|
|
1639
|
+
return pos
|
|
1640
|
+
|
|
1641
|
+
# Silindirik Layout
|
|
1642
|
+
def kececi_layout_cylindrical(
|
|
1643
|
+
nx_graph: nx.Graph,
|
|
1644
|
+
radius: float = 5.0,
|
|
1645
|
+
height: float = 10.0
|
|
1646
|
+
) -> Dict[int, Tuple[float, float, float]]:
|
|
1647
|
+
pos_3d = {}
|
|
1648
|
+
nodes = sorted(list(nx_graph.nodes()))
|
|
1649
|
+
num_nodes = len(nodes)
|
|
1650
|
+
|
|
1651
|
+
for i, node_id in enumerate(nodes):
|
|
1652
|
+
theta = 2 * np.pi * i / num_nodes
|
|
1653
|
+
x = radius * np.cos(theta)
|
|
1654
|
+
y = radius * np.sin(theta)
|
|
1655
|
+
z = height * i / num_nodes
|
|
1656
|
+
pos_3d[node_id] = (x, y, z)
|
|
1657
|
+
|
|
1658
|
+
return pos_3d
|
|
1659
|
+
|
|
1660
|
+
# Kübik Layout
|
|
1661
|
+
def kececi_layout_cubic(
|
|
1662
|
+
nx_graph: nx.Graph,
|
|
1663
|
+
size: float = 5.0
|
|
1664
|
+
) -> Dict[int, Tuple[float, float, float]]:
|
|
1665
|
+
pos_3d = {}
|
|
1666
|
+
nodes = sorted(list(nx_graph.nodes()))
|
|
1667
|
+
num_nodes = len(nodes)
|
|
1668
|
+
cube_size = int(np.cbrt(num_nodes)) + 1
|
|
1669
|
+
|
|
1670
|
+
for i, node_id in enumerate(nodes):
|
|
1671
|
+
x = size * (i % cube_size)
|
|
1672
|
+
y = size * ((i // cube_size) % cube_size)
|
|
1673
|
+
z = size * ((i // (cube_size ** 2)) % cube_size)
|
|
1674
|
+
pos_3d[node_id] = (x, y, z)
|
|
1675
|
+
|
|
1676
|
+
return pos_3d
|
|
1677
|
+
|
|
1678
|
+
# Küresel Layout
|
|
1679
|
+
def kececi_layout_spherical(
|
|
1680
|
+
nx_graph: nx.Graph,
|
|
1681
|
+
radius: float = 5.0
|
|
1682
|
+
) -> Dict[int, Tuple[float, float, float]]:
|
|
1683
|
+
pos_3d = {}
|
|
1684
|
+
nodes = sorted(list(nx_graph.nodes()))
|
|
1685
|
+
num_nodes = len(nodes)
|
|
1686
|
+
|
|
1687
|
+
for i, node_id in enumerate(nodes):
|
|
1688
|
+
theta = 2 * np.pi * i / num_nodes
|
|
1689
|
+
phi = np.arccos(1 - 2 * (i + 0.5) / num_nodes)
|
|
1690
|
+
x = radius * np.sin(phi) * np.cos(theta)
|
|
1691
|
+
y = radius * np.sin(phi) * np.sin(theta)
|
|
1692
|
+
z = radius * np.cos(phi)
|
|
1693
|
+
pos_3d[node_id] = (x, y, z)
|
|
1694
|
+
|
|
1695
|
+
return pos_3d
|
|
1696
|
+
|
|
1697
|
+
# Eliptik Layout
|
|
1698
|
+
def kececi_layout_elliptical(
|
|
1699
|
+
nx_graph: nx.Graph,
|
|
1700
|
+
a: float = 5.0,
|
|
1701
|
+
b: float = 3.0
|
|
1702
|
+
) -> Dict[int, Tuple[float, float]]:
|
|
1703
|
+
pos = {}
|
|
1704
|
+
nodes = sorted(list(nx_graph.nodes()))
|
|
1705
|
+
num_nodes = len(nodes)
|
|
1706
|
+
|
|
1707
|
+
for i, node_id in enumerate(nodes):
|
|
1708
|
+
theta = 2 * np.pi * i / num_nodes
|
|
1709
|
+
x = a * np.cos(theta)
|
|
1710
|
+
y = b * np.sin(theta)
|
|
1711
|
+
pos[node_id] = (x, y)
|
|
1712
|
+
|
|
1713
|
+
return pos
|
|
1714
|
+
|
|
1715
|
+
# Torik (Halkasal) Layout
|
|
1716
|
+
def kececi_layout_toric(
|
|
1717
|
+
nx_graph: nx.Graph,
|
|
1718
|
+
major_radius: float = 5.0,
|
|
1719
|
+
minor_radius: float = 2.0
|
|
1720
|
+
) -> Dict[int, Tuple[float, float, float]]:
|
|
1721
|
+
pos_3d = {}
|
|
1722
|
+
nodes = sorted(list(nx_graph.nodes()))
|
|
1723
|
+
num_nodes = len(nodes)
|
|
1724
|
+
|
|
1725
|
+
for i, node_id in enumerate(nodes):
|
|
1726
|
+
theta = 2 * np.pi * i / num_nodes
|
|
1727
|
+
phi = 2 * np.pi * i / num_nodes
|
|
1728
|
+
x = (major_radius + minor_radius * np.cos(phi)) * np.cos(theta)
|
|
1729
|
+
y = (major_radius + minor_radius * np.cos(phi)) * np.sin(theta)
|
|
1730
|
+
z = minor_radius * np.sin(phi)
|
|
1731
|
+
pos_3d[node_id] = (x, y, z)
|
|
1732
|
+
|
|
1733
|
+
return pos_3d
|
|
1734
|
+
|
|
1735
|
+
# Ağırlıklı Çizim (draw_kececi_weighted)
|
|
1736
|
+
def draw_kececi_weighted(
|
|
1737
|
+
nx_graph: nx.Graph,
|
|
1738
|
+
pos: Dict[int, Tuple[float, ...]],
|
|
1739
|
+
ax: Optional[plt.Axes] = None,
|
|
1740
|
+
node_size: int = 300,
|
|
1741
|
+
edge_width_scale: float = 2.0,
|
|
1742
|
+
**kwargs
|
|
1743
|
+
) -> plt.Axes:
|
|
1744
|
+
if ax is None:
|
|
1745
|
+
fig = plt.figure(figsize=(10, 8))
|
|
1746
|
+
ax = fig.add_subplot(111)
|
|
1747
|
+
|
|
1748
|
+
weights = nx.get_edge_attributes(nx_graph, 'weight')
|
|
1749
|
+
if not weights:
|
|
1750
|
+
weights = {edge: 1.0 for edge in nx_graph.edges()}
|
|
1751
|
+
|
|
1752
|
+
nx.draw_networkx_nodes(nx_graph, pos, ax=ax, node_size=node_size, **kwargs)
|
|
1753
|
+
|
|
1754
|
+
is_3d = len(pos[next(iter(pos))]) == 3
|
|
1755
|
+
if is_3d:
|
|
1756
|
+
for node, coord in pos.items():
|
|
1757
|
+
ax.text(coord[0], coord[1], coord[2], f' {node}', size=10, zorder=1, color='black')
|
|
1758
|
+
else:
|
|
1759
|
+
nx.draw_networkx_labels(nx_graph, pos, ax=ax)
|
|
1760
|
+
|
|
1761
|
+
for (u, v), weight in weights.items():
|
|
1762
|
+
width = weight * edge_width_scale
|
|
1763
|
+
if is_3d:
|
|
1764
|
+
ax.plot(
|
|
1765
|
+
[pos[u][0], pos[v][0]],
|
|
1766
|
+
[pos[u][1], pos[v][1]],
|
|
1767
|
+
[pos[u][2], pos[v][2]],
|
|
1768
|
+
linewidth=width,
|
|
1769
|
+
color='gray',
|
|
1770
|
+
alpha=0.7
|
|
1771
|
+
)
|
|
1772
|
+
else:
|
|
1773
|
+
ax.plot(
|
|
1774
|
+
[pos[u][0], pos[v][0]],
|
|
1775
|
+
[pos[u][1], pos[v][1]],
|
|
1776
|
+
linewidth=width,
|
|
1777
|
+
color='gray',
|
|
1778
|
+
alpha=0.7
|
|
1779
|
+
)
|
|
1780
|
+
|
|
1781
|
+
ax.set_title("Keçeci Layout: Weighted Edges")
|
|
1782
|
+
return ax
|
|
1783
|
+
|
|
1784
|
+
# Renkli Çizim (draw_kececi_colored)
|
|
1785
|
+
def draw_kececi_colored(
|
|
1786
|
+
nx_graph: nx.Graph,
|
|
1787
|
+
pos: Dict[int, Tuple[float, ...]],
|
|
1788
|
+
ax: Optional[plt.Axes] = None,
|
|
1789
|
+
node_size: int = 300,
|
|
1790
|
+
**kwargs
|
|
1791
|
+
) -> plt.Axes:
|
|
1792
|
+
if ax is None:
|
|
1793
|
+
fig = plt.figure(figsize=(10, 8))
|
|
1794
|
+
ax = fig.add_subplot(111)
|
|
1795
|
+
|
|
1796
|
+
degrees = dict(nx_graph.degree())
|
|
1797
|
+
max_degree = max(degrees.values()) if degrees else 1
|
|
1798
|
+
node_colors = [plt.cm.viridis(deg / max_degree) for deg in degrees.values()]
|
|
1799
|
+
|
|
1800
|
+
nx.draw_networkx_nodes(
|
|
1801
|
+
nx_graph, pos, ax=ax,
|
|
1802
|
+
node_color=node_colors,
|
|
1803
|
+
node_size=node_size,
|
|
1804
|
+
**kwargs
|
|
1805
|
+
)
|
|
1806
|
+
|
|
1807
|
+
is_3d = len(pos[next(iter(pos))]) == 3
|
|
1808
|
+
if is_3d:
|
|
1809
|
+
for node, coord in pos.items():
|
|
1810
|
+
ax.text(coord[0], coord[1], coord[2], f' {node}', size=10, zorder=1, color='black')
|
|
1811
|
+
else:
|
|
1812
|
+
nx.draw_networkx_labels(nx_graph, pos, ax=ax)
|
|
1813
|
+
|
|
1814
|
+
if is_3d:
|
|
1815
|
+
for u, v in nx_graph.edges():
|
|
1816
|
+
ax.plot(
|
|
1817
|
+
[pos[u][0], pos[v][0]],
|
|
1818
|
+
[pos[u][1], pos[v][1]],
|
|
1819
|
+
[pos[u][2], pos[v][2]],
|
|
1820
|
+
color='gray',
|
|
1821
|
+
alpha=0.5
|
|
1822
|
+
)
|
|
1823
|
+
else:
|
|
1824
|
+
nx.draw_networkx_edges(nx_graph, pos, ax=ax, alpha=0.5)
|
|
1825
|
+
|
|
1826
|
+
ax.set_title("Keçeci Layout: Colored Nodes")
|
|
1827
|
+
return ax
|
|
1828
|
+
|
|
1423
1829
|
# =============================================================================
|
|
1424
1830
|
# 3. INTERNAL DRAWING STYLE IMPLEMENTATIONS
|
|
1425
1831
|
# =============================================================================
|
|
@@ -1523,9 +1929,181 @@ def _draw_internal(nx_graph, ax, style, **kwargs):
|
|
|
1523
1929
|
# =============================================================================
|
|
1524
1930
|
# 4. MAIN USER-FACING DRAWING FUNCTION
|
|
1525
1931
|
# =============================================================================
|
|
1932
|
+
def draw_kececi(
|
|
1933
|
+
graph,
|
|
1934
|
+
pos: Optional[Dict[int, Tuple[float, ...]]] = None,
|
|
1935
|
+
layout: Optional[str] = None,
|
|
1936
|
+
style: str = 'default',
|
|
1937
|
+
ax: Optional[plt.Axes] = None,
|
|
1938
|
+
with_labels: bool = True,
|
|
1939
|
+
node_color: str = 'lightblue',
|
|
1940
|
+
node_size: int = 500,
|
|
1941
|
+
font_weight: str = 'bold',
|
|
1942
|
+
**kwargs
|
|
1943
|
+
) -> plt.Axes:
|
|
1944
|
+
"""
|
|
1945
|
+
Keçeci Layout ile graf çizimi.
|
|
1526
1946
|
|
|
1527
|
-
|
|
1947
|
+
Args:
|
|
1948
|
+
graph: Graf objesi (NetworkX, igraph, vb.).
|
|
1949
|
+
pos: Önceden hesaplanmış koordinatlar (opsiyonel).
|
|
1950
|
+
layout: '2d', 'cylindrical', 'cubic', 'spherical', 'elliptical', 'toric' (opsiyonel).
|
|
1951
|
+
style: 'default', 'weighted', 'colored'.
|
|
1952
|
+
ax: Matplotlib ekseni.
|
|
1953
|
+
with_labels: Düğüm etiketlerini göster.
|
|
1954
|
+
node_color: Düğüm rengi.
|
|
1955
|
+
node_size: Düğüm boyutu.
|
|
1956
|
+
font_weight: Yazı kalınlığı.
|
|
1957
|
+
**kwargs: Ek parametreler.
|
|
1958
|
+
|
|
1959
|
+
Returns:
|
|
1960
|
+
Matplotlib ekseni.
|
|
1528
1961
|
"""
|
|
1962
|
+
nx_graph = to_networkx(graph)
|
|
1963
|
+
|
|
1964
|
+
# Eğer pos verilmemişse, layout'a göre hesapla
|
|
1965
|
+
if pos is None:
|
|
1966
|
+
if layout is None:
|
|
1967
|
+
layout = '2d' # Varsayılan layout
|
|
1968
|
+
|
|
1969
|
+
if layout == '2d':
|
|
1970
|
+
pos = kececi_layout_2d(nx_graph, **kwargs)
|
|
1971
|
+
elif layout == 'cylindrical':
|
|
1972
|
+
pos = kececi_layout_cylindrical(nx_graph, **kwargs)
|
|
1973
|
+
elif layout == 'cubic':
|
|
1974
|
+
pos = kececi_layout_cubic(nx_graph, **kwargs)
|
|
1975
|
+
elif layout == 'spherical':
|
|
1976
|
+
pos = kececi_layout_spherical(nx_graph, **kwargs)
|
|
1977
|
+
elif layout == 'elliptical':
|
|
1978
|
+
pos = kececi_layout_elliptical(nx_graph, **kwargs)
|
|
1979
|
+
elif layout == 'toric':
|
|
1980
|
+
pos = kececi_layout_toric(nx_graph, **kwargs)
|
|
1981
|
+
else:
|
|
1982
|
+
raise ValueError(f"Geçersiz layout: {layout}")
|
|
1983
|
+
|
|
1984
|
+
# 3D için eksen ayarlaması
|
|
1985
|
+
is_3d = len(pos[next(iter(pos))]) == 3
|
|
1986
|
+
if ax is None:
|
|
1987
|
+
fig = plt.figure(figsize=(10, 8))
|
|
1988
|
+
if is_3d:
|
|
1989
|
+
ax = fig.add_subplot(111, projection='3d')
|
|
1990
|
+
else:
|
|
1991
|
+
ax = fig.add_subplot(111)
|
|
1992
|
+
|
|
1993
|
+
# Stile göre çizim yap
|
|
1994
|
+
if style == 'weighted':
|
|
1995
|
+
draw_kececi_weighted(nx_graph, pos, ax, **kwargs)
|
|
1996
|
+
elif style == 'colored':
|
|
1997
|
+
draw_kececi_colored(nx_graph, pos, ax, **kwargs)
|
|
1998
|
+
else: # 'default'
|
|
1999
|
+
nx.draw_networkx_nodes(nx_graph, pos, ax=ax, node_color=node_color, node_size=node_size)
|
|
2000
|
+
|
|
2001
|
+
# Düğüm etiketlerini çiz
|
|
2002
|
+
if with_labels:
|
|
2003
|
+
if is_3d:
|
|
2004
|
+
for node, coord in pos.items():
|
|
2005
|
+
ax.text(coord[0], coord[1], coord[2], f' {node}', size=10, zorder=1, color='black', fontweight=font_weight)
|
|
2006
|
+
else:
|
|
2007
|
+
nx.draw_networkx_labels(nx_graph, pos, ax=ax, font_weight=font_weight)
|
|
2008
|
+
|
|
2009
|
+
# Kenarları çiz
|
|
2010
|
+
if is_3d:
|
|
2011
|
+
for u, v in nx_graph.edges():
|
|
2012
|
+
ax.plot(
|
|
2013
|
+
[pos[u][0], pos[v][0]],
|
|
2014
|
+
[pos[u][1], pos[v][1]],
|
|
2015
|
+
[pos[u][2], pos[v][2]],
|
|
2016
|
+
color='gray',
|
|
2017
|
+
alpha=0.5
|
|
2018
|
+
)
|
|
2019
|
+
else:
|
|
2020
|
+
nx.draw_networkx_edges(nx_graph, pos, ax=ax, alpha=0.5)
|
|
2021
|
+
|
|
2022
|
+
ax.set_title(f"Keçeci Layout: {layout.capitalize() if layout else 'Custom'} ({style})")
|
|
2023
|
+
return ax
|
|
2024
|
+
"""
|
|
2025
|
+
def draw_kececi(
|
|
2026
|
+
graph,
|
|
2027
|
+
layout: str = '2d',
|
|
2028
|
+
style: str = 'default',
|
|
2029
|
+
ax: Optional[plt.Axes] = None,
|
|
2030
|
+
**kwargs
|
|
2031
|
+
) -> plt.Axes:
|
|
2032
|
+
|
|
2033
|
+
Keçeci Layout ile graf çizimi.
|
|
2034
|
+
|
|
2035
|
+
Args:
|
|
2036
|
+
graph: Graf objesi (NetworkX, igraph, vb.).
|
|
2037
|
+
layout: '2d', 'cylindrical', 'cubic', 'spherical', 'elliptical', 'toric'.
|
|
2038
|
+
style: 'default', 'weighted', 'colored'.
|
|
2039
|
+
ax: Matplotlib ekseni.
|
|
2040
|
+
**kwargs: Ek parametreler.
|
|
2041
|
+
|
|
2042
|
+
Returns:
|
|
2043
|
+
Matplotlib ekseni.
|
|
2044
|
+
|
|
2045
|
+
nx_graph = to_networkx(graph)
|
|
2046
|
+
|
|
2047
|
+
# Layout'a göre koordinatları hesapla
|
|
2048
|
+
if layout == '2d':
|
|
2049
|
+
pos = kececi_layout_2d(nx_graph, **kwargs)
|
|
2050
|
+
elif layout == 'cylindrical':
|
|
2051
|
+
pos = kececi_layout_cylindrical(nx_graph, **kwargs)
|
|
2052
|
+
elif layout == 'cubic':
|
|
2053
|
+
pos = kececi_layout_cubic(nx_graph, **kwargs)
|
|
2054
|
+
elif layout == 'spherical':
|
|
2055
|
+
pos = kececi_layout_spherical(nx_graph, **kwargs)
|
|
2056
|
+
elif layout == 'elliptical':
|
|
2057
|
+
pos = kececi_layout_elliptical(nx_graph, **kwargs)
|
|
2058
|
+
elif layout == 'toric':
|
|
2059
|
+
pos = kececi_layout_toric(nx_graph, **kwargs)
|
|
2060
|
+
else:
|
|
2061
|
+
raise ValueError(f"Invalid layout: {layout}")
|
|
2062
|
+
|
|
2063
|
+
# 3D için eksen ayarlaması
|
|
2064
|
+
is_3d = len(pos[next(iter(pos))]) == 3
|
|
2065
|
+
if ax is None:
|
|
2066
|
+
fig = plt.figure(figsize=(10, 8))
|
|
2067
|
+
if is_3d:
|
|
2068
|
+
ax = fig.add_subplot(111, projection='3d')
|
|
2069
|
+
else:
|
|
2070
|
+
ax = fig.add_subplot(111)
|
|
2071
|
+
|
|
2072
|
+
# Stile göre çizim yap
|
|
2073
|
+
if style == 'weighted':
|
|
2074
|
+
draw_kececi_weighted(nx_graph, pos, ax, **kwargs)
|
|
2075
|
+
elif style == 'colored':
|
|
2076
|
+
draw_kececi_colored(nx_graph, pos, ax, **kwargs)
|
|
2077
|
+
else: # 'default'
|
|
2078
|
+
nx.draw_networkx_nodes(nx_graph, pos, ax=ax, **kwargs)
|
|
2079
|
+
|
|
2080
|
+
# Düğüm etiketlerini çiz
|
|
2081
|
+
if is_3d:
|
|
2082
|
+
for node, coord in pos.items():
|
|
2083
|
+
ax.text(coord[0], coord[1], coord[2], f' {node}', size=10, zorder=1, color='black')
|
|
2084
|
+
else:
|
|
2085
|
+
nx.draw_networkx_labels(nx_graph, pos, ax=ax)
|
|
2086
|
+
|
|
2087
|
+
# Kenarları çiz
|
|
2088
|
+
if is_3d:
|
|
2089
|
+
for u, v in nx_graph.edges():
|
|
2090
|
+
ax.plot(
|
|
2091
|
+
[pos[u][0], pos[v][0]],
|
|
2092
|
+
[pos[u][1], pos[v][1]],
|
|
2093
|
+
[pos[u][2], pos[v][2]],
|
|
2094
|
+
color='gray',
|
|
2095
|
+
alpha=0.5
|
|
2096
|
+
)
|
|
2097
|
+
else:
|
|
2098
|
+
nx.draw_networkx_edges(nx_graph, pos, ax=ax, alpha=0.5)
|
|
2099
|
+
|
|
2100
|
+
ax.set_title(f"Keçeci Layout: {layout.capitalize()} ({style})")
|
|
2101
|
+
return ax
|
|
2102
|
+
"""
|
|
2103
|
+
|
|
2104
|
+
"""
|
|
2105
|
+
def draw_kececi(graph, style='curved', ax=None, **kwargs):
|
|
2106
|
+
|
|
1529
2107
|
Draws a graph using the Keçeci Layout with a specified style.
|
|
1530
2108
|
|
|
1531
2109
|
This function automatically handles graphs from different libraries
|
|
@@ -1541,7 +2119,7 @@ def draw_kececi(graph, style='curved', ax=None, **kwargs):
|
|
|
1541
2119
|
|
|
1542
2120
|
Returns:
|
|
1543
2121
|
matplotlib.axis.Axis: The axis object where the graph was drawn.
|
|
1544
|
-
|
|
2122
|
+
|
|
1545
2123
|
nx_graph = to_networkx(graph)
|
|
1546
2124
|
is_3d = (style.lower() == '3d')
|
|
1547
2125
|
|
|
@@ -1559,7 +2137,7 @@ def draw_kececi(graph, style='curved', ax=None, **kwargs):
|
|
|
1559
2137
|
|
|
1560
2138
|
_draw_internal(nx_graph, ax, style.lower(), **kwargs)
|
|
1561
2139
|
return ax
|
|
1562
|
-
|
|
2140
|
+
"""
|
|
1563
2141
|
|
|
1564
2142
|
# =============================================================================
|
|
1565
2143
|
# MODULE TEST CODE
|
|
@@ -1606,3 +2184,6 @@ if __name__ == '__main__':
|
|
|
1606
2184
|
draw_kececi(G_test, style='3d', ax=fig_styles.add_subplot(2, 2, (3, 4), projection='3d'))
|
|
1607
2185
|
plt.tight_layout(rect=[0, 0, 1, 0.96])
|
|
1608
2186
|
plt.show()
|
|
2187
|
+
|
|
2188
|
+
|
|
2189
|
+
|