topologicpy 0.7.73__py3-none-any.whl → 0.7.74__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.
topologicpy/Graph.py CHANGED
@@ -1373,6 +1373,7 @@ class Graph:
1373
1373
  print("Graph.BetweenessCentrality - Error: The input graph is not a valid graph. Returning None.")
1374
1374
  return None
1375
1375
  graphVertices = Graph.Vertices(graph)
1376
+
1376
1377
  if not isinstance(vertices, list):
1377
1378
  vertices = graphVertices
1378
1379
  else:
@@ -1394,7 +1395,15 @@ class Graph:
1394
1395
  if len(destinations) < 1:
1395
1396
  print("Graph.BetweenessCentrality - Error: The input list of destinations does not contain valid vertices. Returning None.")
1396
1397
  return None
1397
-
1398
+ graphEdges = Graph.Edges(graph)
1399
+ if len(graphEdges) == 0:
1400
+ print("Graph.BetweenessCentrality - Warning: The input graph is a null graph.")
1401
+ scores = [0 for t in vertices]
1402
+ for i, v in enumerate(vertices):
1403
+ d = Topology.Dictionary(v)
1404
+ d = Dictionary.SetValueAtKey(d, key, scores[i])
1405
+ v = Topology.SetDictionary(v, d)
1406
+ return scores
1398
1407
  paths = []
1399
1408
  try:
1400
1409
  for so in tqdm(sources, desc="Computing Shortest Paths", leave=False):
@@ -2002,8 +2011,6 @@ class Graph:
2002
2011
  for i, vertices, in enumerate(vertices_ds):
2003
2012
  edges = edges_ds[i]
2004
2013
  g = Graph.ByVerticesEdges(vertices, edges)
2005
- temp_v = Graph.Vertices(g)
2006
- temp_e = Graph.Edges(g)
2007
2014
  if Topology.IsInstance(g, "Graph"):
2008
2015
  if len(graphFeaturesKeys) == 0:
2009
2016
  values = [graph_ids[i], graph_labels[i], graph_features[i]]
@@ -5622,19 +5629,35 @@ class Graph:
5622
5629
  return False
5623
5630
 
5624
5631
  @staticmethod
5625
- def Flatten(graph, layout="spring", k=0.8, seed=None, iterations=50, rootVertex=None, radius=0.5, tolerance=0.0001):
5626
- """
5627
- Flattens the input graph.
5632
+ def Reshape(graph,
5633
+ shape="spring_2d",
5634
+ k=0.8, seed=None,
5635
+ iterations=50,
5636
+ rootVertex=None,
5637
+ size=1,
5638
+ sides=16,
5639
+ key="",
5640
+ tolerance=0.0001,
5641
+ silent=False):
5642
+ """
5643
+ Reshapes the input graph according to the desired input shape parameter.
5628
5644
 
5629
5645
  Parameters
5630
5646
  ----------
5631
5647
  graph : topologic_core.Graph
5632
5648
  The input graph.
5633
- layout : str , optional
5634
- The desired mode for flattening. If set to 'spring', the algorithm uses a simplified version of the Fruchterman-Reingold force-directed algorithm to flatten and distribute the vertices.
5635
- If set to 'radial', the nodes will be distributed along concentric circles.
5636
- If set to 'tree', the nodes will be distributed using the Reingold-Tillford layout.
5637
- If set to 'circle', the nodes will be distributed on the cirumference of a circle. The default is 'spring'.
5649
+ shape : str , optional
5650
+ The desired shape of the graph.
5651
+ If set to 'spring_2d' or 'spring_3d', the algorithm uses a simplified version of the Fruchterman-Reingold force-directed algorithm to distribute the vertices.
5652
+ If set to 'radial_2d', the nodes will be distributed along concentric circles in the XY plane.
5653
+ If set to 'tree_2d' or 'tree_3d', the nodes will be distributed using the Reingold-Tillford layout.
5654
+ If set to 'circle_2d', the nodes will be distributed on the cirumference of a segemented circles in the XY plane, based on the size and sides input parameter (radius=size/2).
5655
+ If set to 'line_2d', the nodes will be distributed on a line in the XY plane based on the size input parameter (length=size).
5656
+ If set to 'spehere_3d', the nodes will be distributed on the surface of a sphere based on the size input parameter raidus=size/2).
5657
+ If set to 'grid_2d', the nodes will be distributed on a grid in the XY plane with size based on the size input parameter (length=width=size).
5658
+ If set to 'grid_3d', the nodes will be distributed on a 3D cubic grid/matrix based on the size input parameter(width=length=height=size).
5659
+ If set to 'cluster_2d', or 'cluster_3d, the nodes will be clustered according to the 'key' input parameter. The overall radius of the cluster is determined by the size input parameter (radius = size/2)
5660
+ The default is 'spring_2d'.
5638
5661
  k : float, optional
5639
5662
  The desired spring constant to use for the attractive and repulsive forces. The default is 0.8.
5640
5663
  seed : int , optional
@@ -5643,20 +5666,38 @@ class Graph:
5643
5666
  The desired maximum number of iterations to solve the forces in the 'spring' mode. The default is 50.
5644
5667
  rootVertex : topologic_core.Vertex , optional
5645
5668
  The desired vertex to use as the root of the tree and radial layouts.
5646
- radius : float, optional
5647
- The desired radius for the circle layout option. The default is 0.5.
5669
+ sides : int , optional
5670
+ The desired number of sides of the circle layout option. The default is 16
5671
+ length : float, optional
5672
+ The desired horizontal length for the line layout option. The default is 1.0.
5673
+ key : string, optional
5674
+ The key under which to find the clustering value for the 'cluster_2d' and 'cluster_3d' options. The default is "".
5648
5675
  tolerance : float , optional
5649
5676
  The desired tolerance. The default is 0.0001.
5677
+ silent : bool , optional
5678
+ If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
5650
5679
 
5651
5680
  Returns
5652
5681
  -------
5653
5682
  topologic_core.Graph
5654
- The flattened graph.
5683
+ The reshaped graph.
5655
5684
 
5656
5685
  """
5657
5686
  from topologicpy.Vertex import Vertex
5687
+ from topologicpy.Edge import Edge
5688
+ from topologicpy.Wire import Wire
5689
+ from topologicpy.Face import Face
5690
+ from topologicpy.Graph import Graph
5691
+ from topologicpy.Grid import Grid
5692
+ from topologicpy.Helper import Helper
5693
+ from topologicpy.Vector import Vector
5658
5694
  from topologicpy.Topology import Topology
5695
+ from topologicpy.Dictionary import Dictionary
5659
5696
  import numpy as np
5697
+ import math
5698
+ from collections import defaultdict
5699
+ import random
5700
+
5660
5701
 
5661
5702
  def buchheim(tree):
5662
5703
  dt = firstwalk(_DrawTree(tree))
@@ -5770,7 +5811,7 @@ class Graph:
5770
5811
  Returns:
5771
5812
  A numpy array representing the adjacency matrix.
5772
5813
  """
5773
- from topologicpy.Helper import Helper
5814
+
5774
5815
  # Get the number of nodes from the edge list.
5775
5816
  flat_list = Helper.Flatten(edge_list)
5776
5817
  flat_list = [x for x in flat_list if not x == None]
@@ -5810,15 +5851,226 @@ class Graph:
5810
5851
  new_roots.extend(children)
5811
5852
  old_roots = new_roots
5812
5853
  return root, num_nodes
5854
+
5855
+ def generate_cubic_matrix(size, min_points):
5856
+ # Calculate the minimum points per axis to reach or exceed min_points in total
5857
+ points_per_axis = int(np.ceil(min_points ** (1/3)))
5858
+
5859
+ # Calculate the spacing based on the size and points per axis
5860
+ spacing = size / (points_per_axis - 1) if points_per_axis > 1 else 0
5861
+
5862
+ # Generate linearly spaced points from -size/2 to size/2 along each axis
5863
+ x = np.linspace(-size / 2, size / 2, points_per_axis)
5864
+ y = np.linspace(-size / 2, size / 2, points_per_axis)
5865
+ z = np.linspace(-size / 2, size / 2, points_per_axis)
5866
+
5867
+ # Create a meshgrid and stack them to get XYZ coordinates for each point
5868
+ X, Y, Z = np.meshgrid(x, y, z)
5869
+ points = np.vstack([X.ravel(), Y.ravel(), Z.ravel()]).T
5870
+ return points
5871
+
5872
+ def vertex_max_degree(graph, vertices):
5873
+ degrees = [Graph.VertexDegree(graph, vertex) for vertex in vertices]
5874
+ i = degrees.index(max(degrees))
5875
+ return vertices[i], i
5876
+
5877
+ def circle_layout_2d(graph, radius=0.5, sides=16):
5878
+ vertices = Graph.Vertices(graph)
5879
+ edges = Graph.Edges(graph)
5880
+ edge_dict = {}
5813
5881
 
5882
+ for i, edge in enumerate(edges):
5883
+ sv = Edge.StartVertex(edge)
5884
+ ev = Edge.EndVertex(edge)
5885
+ si = Vertex.Index(sv, vertices)
5886
+ ei = Vertex.Index(ev, vertices)
5887
+ edge_dict[str(si)+"_"+str(ei)] = i
5888
+ edge_dict[str(ei)+"_"+str(si)] = i
5889
+ n = len(vertices)
5890
+ c = Wire.Circle(radius=radius, sides=sides)
5891
+ c_vertices = []
5892
+ for i in range(n):
5893
+ u = i*(1/n)
5894
+ c_vertices.append(Wire.VertexByParameter(c, u))
5814
5895
 
5815
- def circle_layout(graph, radius=0.5):
5816
- from topologicpy.Vertex import Vertex
5817
- from topologicpy.Vector import Vector
5818
- from topologicpy.Wire import Wire
5819
- from topologicpy.Graph import Graph
5820
- from topologicpy.Edge import Edge
5896
+ for i, c_v in enumerate(c_vertices):
5897
+ d = Topology.Dictionary(vertices[i])
5898
+ c_v = Topology.SetDictionary(c_v, d)
5899
+ adj_dict = Graph.AdjacencyDictionary(graph)
5900
+ keys = adj_dict.keys()
5901
+
5902
+ c_edges = []
5903
+ used = [[0] * n for _ in range(n)]
5904
+ for key in keys:
5905
+ x = int(key)
5906
+ adj_vertices = [int(v) for v in adj_dict[key]]
5907
+ for y in adj_vertices:
5908
+ if used[x][y] == 0:
5909
+ v1 = Vector.ByCoordinates(Vertex.X(c_vertices[x]), Vertex.Y(c_vertices[x]), Vertex.Z(c_vertices[x]))
5910
+ v2 = Vector.ByCoordinates(Vertex.X(c_vertices[y]), Vertex.Y(c_vertices[y]), Vertex.Z(c_vertices[y]))
5911
+ ang1 = Vector.CompassAngle(v1, [0,1,0])
5912
+ ang2 = Vector.CompassAngle(v2, [0,1,0])
5913
+ if ang2-ang1 < 180:
5914
+ e = Edge.ByVertices(c_vertices[x], c_vertices[y])
5915
+ else:
5916
+ e = Edge.ByVertices(c_vertices[y], c_vertices[x])
5917
+ orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
5918
+ if orig_edge_index:
5919
+ d = Topology.Dictionary(edges[orig_edge_index])
5920
+ e = Topology.SetDictionary(e, d)
5921
+ c_edges.append(e)
5922
+ used[x][y] = 1
5923
+ used[y][x] = 1
5924
+ new_g = Graph.ByVerticesEdges(c_vertices, c_edges)
5925
+ return new_g
5926
+
5927
+ def cluster_layout_2d(graph, key, radius=0.5):
5928
+
5929
+ d = Graph.MeshData(graph)
5930
+ edges = d['edges']
5931
+ v_dicts = d['vertexDictionaries']
5932
+ e_dicts = d['edgeDictionaries']
5933
+ vertices = Graph.Vertices(graph)
5934
+ # Step 1: Group objects by key value while remembering their original indices
5935
+ grouped_objects = defaultdict(list)
5936
+ object_indices = [] # Stores original indices of objects in the order they were grouped
5937
+
5938
+ for idx, obj in enumerate(vertices):
5939
+ d = Topology.Dictionary(obj)
5940
+ value = Dictionary.ValueAtKey(d, key)
5941
+ grouped_objects[value].append((obj, idx))
5942
+ object_indices.append((value, idx))
5943
+
5944
+ # Step 2: Compute cluster centers on the circumference of a circle
5945
+ cluster_centers = {}
5946
+ num_clusters = len(grouped_objects)
5947
+
5948
+ # Function to generate cluster center on the circle's circumference
5949
+ def generate_cluster_center(index, total_clusters, circle_radius):
5950
+ # Distribute cluster centers evenly along the circumference of a circle
5951
+ angle = (2 * math.pi * index) / total_clusters # Equal angle separation
5952
+ x = circle_radius * math.cos(angle)
5953
+ y = circle_radius * math.sin(angle)
5954
+ return (x, y)
5955
+
5956
+ # Step 3: Compute vertices for each cluster
5957
+ object_positions = [None] * len(vertices) # Placeholder list for ordered vertices
5958
+ cluster_index = 0
5959
+
5960
+ for value, objs in grouped_objects.items():
5961
+ intra_cluster_radius = radius*(len(objs)/len(vertices) + 0.1)
5962
+ # Determine the center of the current cluster
5963
+ if value not in cluster_centers:
5964
+ cluster_center = generate_cluster_center(cluster_index, num_clusters, radius)
5965
+ cluster_centers[value] = cluster_center
5966
+ cluster_index += 1
5967
+ else:
5968
+ cluster_center = cluster_centers[value]
5969
+
5970
+ # Step 4: Place objects randomly around the cluster center
5971
+ for obj, original_index in objs:
5972
+ # Randomly place the object within the intra-cluster circle
5973
+ r = intra_cluster_radius * math.sqrt(random.random()) # Random distance with sqrt for uniform distribution
5974
+ angle = random.uniform(0, 2 * math.pi) # Random angle
5975
+
5976
+ # Polar coordinates to Cartesian for local positioning
5977
+ x = cluster_center[0] + r * math.cos(angle)
5978
+ y = cluster_center[1] + r * math.sin(angle)
5979
+
5980
+ # Save the coordinates in the correct order
5981
+ object_positions[original_index] = [x, y]
5982
+
5983
+ positions = [[p[0], p[1], 0] for p in object_positions]
5984
+ new_g = Graph.ByMeshData(positions, edges, v_dicts, e_dicts, tolerance=0.001)
5985
+ return new_g
5821
5986
 
5987
+ def cluster_layout_3d(graph, key, radius=0.5):
5988
+ d = Graph.MeshData(graph)
5989
+ edges = d['edges']
5990
+ v_dicts = d['vertexDictionaries']
5991
+ e_dicts = d['edgeDictionaries']
5992
+ vertices = Graph.Vertices(graph)
5993
+
5994
+ # Step 1: Group objects by key value while remembering their original indices
5995
+ grouped_objects = defaultdict(list)
5996
+ object_indices = [] # Stores original indices of objects in the order they were grouped
5997
+
5998
+ for idx, obj in enumerate(vertices):
5999
+ d = Topology.Dictionary(obj)
6000
+ value = Dictionary.ValueAtKey(d, key)
6001
+ grouped_objects[value].append((obj, idx))
6002
+ object_indices.append((value, idx))
6003
+
6004
+ # Step 2: Compute cluster centers on the surface of a sphere
6005
+ cluster_centers = {}
6006
+ num_clusters = len(grouped_objects)
6007
+
6008
+ # Function to generate cluster center on the surface of a sphere
6009
+ def generate_cluster_center(index, total_clusters, sphere_radius):
6010
+ # Use a spiral algorithm to distribute cluster centers evenly on a sphere's surface
6011
+ phi = math.acos(1 - 2 * (index + 0.5) / total_clusters) # Inclination angle
6012
+ theta = math.pi * (1 + 5**0.5) * index # Azimuthal angle (Golden angle)
6013
+
6014
+ x = sphere_radius * math.sin(phi) * math.cos(theta)
6015
+ y = sphere_radius * math.sin(phi) * math.sin(theta)
6016
+ z = sphere_radius * math.cos(phi)
6017
+ return (x, y, z)
6018
+
6019
+ # Step 3: Compute vertices for each cluster
6020
+ object_positions = [None] * len(vertices) # Placeholder list for ordered vertices
6021
+ cluster_index = 0
6022
+
6023
+ for value, objs in grouped_objects.items():
6024
+ # Determine the center of the current cluster
6025
+ if value not in cluster_centers:
6026
+ cluster_center = generate_cluster_center(cluster_index, num_clusters, radius)
6027
+ cluster_centers[value] = cluster_center
6028
+ cluster_index += 1
6029
+ else:
6030
+ cluster_center = cluster_centers[value]
6031
+
6032
+ intra_cluster_radius = radius*(len(objs)/len(vertices) + 0.1)
6033
+
6034
+ # Step 4: Place objects randomly within the cluster's spherical volume
6035
+ for obj, original_index in objs:
6036
+ # Randomly place the object within the intra-cluster sphere
6037
+ u = random.random()
6038
+ v = random.random()
6039
+ r = intra_cluster_radius * (u ** (1/3)) # Random distance with cube root for uniform distribution
6040
+
6041
+ theta = 2 * math.pi * v # Random azimuthal angle
6042
+ phi = math.acos(2 * u - 1) # Random polar angle
6043
+
6044
+ # Spherical to Cartesian for local positioning
6045
+ x = cluster_center[0] + r * math.sin(phi) * math.cos(theta)
6046
+ y = cluster_center[1] + r * math.sin(phi) * math.sin(theta)
6047
+ z = cluster_center[2] + r * math.cos(phi)
6048
+
6049
+ # Save the coordinates in the correct order
6050
+ object_positions[original_index] = [x, y, z]
6051
+
6052
+ positions = [[p[0], p[1], p[2]] for p in object_positions]
6053
+ new_g = Graph.ByMeshData(positions, edges, v_dicts, e_dicts, tolerance=0.001)
6054
+ return new_g
6055
+
6056
+ def sphere_layout_3d(graph, radius=0.5):
6057
+ def points_on_sphere(n, r):
6058
+ points = []
6059
+ phi = math.pi * (3. - math.sqrt(5.)) # Golden angle in radians
6060
+
6061
+ for i in range(n):
6062
+ y = 1 - (i / float(n - 1)) * 2 # y goes from 1 to -1
6063
+ radius = math.sqrt(1 - y * y) # radius at y
6064
+
6065
+ theta = phi * i # Golden angle increment
6066
+
6067
+ x = math.cos(theta) * radius * r
6068
+ z = math.sin(theta) * radius * r
6069
+ y *= r
6070
+
6071
+ points.append([x, y, z])
6072
+ return points
6073
+
5822
6074
  vertices = Graph.Vertices(graph)
5823
6075
  edges = Graph.Edges(graph)
5824
6076
  edge_dict = {}
@@ -5830,16 +6082,15 @@ class Graph:
5830
6082
  ei = Vertex.Index(ev, vertices)
5831
6083
  edge_dict[str(si)+"_"+str(ei)] = i
5832
6084
  edge_dict[str(ei)+"_"+str(si)] = i
5833
-
5834
6085
  n = len(vertices)
5835
- c = Wire.Circle(radius=radius, sides=n)
5836
- c_vertices = Topology.Vertices(c)
5837
-
6086
+ c_points = points_on_sphere(n, r=radius)
6087
+ c_vertices = [Vertex.ByCoordinates(coord) for coord in c_points]
5838
6088
  for i, c_v in enumerate(c_vertices):
5839
6089
  d = Topology.Dictionary(vertices[i])
5840
6090
  c_v = Topology.SetDictionary(c_v, d)
5841
6091
  adj_dict = Graph.AdjacencyDictionary(graph)
5842
6092
  keys = adj_dict.keys()
6093
+
5843
6094
  c_edges = []
5844
6095
  used = [[0] * n for _ in range(n)]
5845
6096
  for key in keys:
@@ -5855,6 +6106,94 @@ class Graph:
5855
6106
  e = Edge.ByVertices(c_vertices[x], c_vertices[y])
5856
6107
  else:
5857
6108
  e = Edge.ByVertices(c_vertices[y], c_vertices[x])
6109
+ orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
6110
+ if orig_edge_index:
6111
+ d = Topology.Dictionary(edges[orig_edge_index])
6112
+ e = Topology.SetDictionary(e, d)
6113
+ c_edges.append(e)
6114
+ used[x][y] = 1
6115
+ used[y][x] = 1
6116
+ new_g = Graph.ByVerticesEdges(c_vertices, c_edges)
6117
+ return new_g
6118
+
6119
+ def grid_layout_2d(graph, size=1):
6120
+ vertices = Graph.Vertices(graph)
6121
+ n = len(vertices)
6122
+ u = int(math.sqrt(n))
6123
+ if u*u < n:
6124
+ u += 1
6125
+ u_range = [t/(u-1) for t in range(u)]
6126
+ edges = Graph.Edges(graph)
6127
+ edge_dict = {}
6128
+
6129
+ for i, edge in enumerate(edges):
6130
+ sv = Edge.StartVertex(edge)
6131
+ ev = Edge.EndVertex(edge)
6132
+ si = Vertex.Index(sv, vertices)
6133
+ ei = Vertex.Index(ev, vertices)
6134
+ edge_dict[str(si)+"_"+str(ei)] = i
6135
+ edge_dict[str(ei)+"_"+str(si)] = i
6136
+ f = Face.Rectangle(width=size, length=size)
6137
+ c = Grid.VerticesByParameters(face=f, uRange=u_range, vRange=u_range)
6138
+ c_vertices = Topology.Vertices(c)[:len(vertices)]
6139
+
6140
+ for i, c_v in enumerate(c_vertices):
6141
+ d = Topology.Dictionary(vertices[i])
6142
+ c_v = Topology.SetDictionary(c_v, d)
6143
+ adj_dict = Graph.AdjacencyDictionary(graph)
6144
+ keys = adj_dict.keys()
6145
+
6146
+ c_edges = []
6147
+ used = [[0] * n for _ in range(n)]
6148
+ for key in keys:
6149
+ x = int(key)
6150
+ adj_vertices = [int(v) for v in adj_dict[key]]
6151
+ for y in adj_vertices:
6152
+ if used[x][y] == 0:
6153
+ e = Edge.ByVertices(c_vertices[x], c_vertices[y])
6154
+ orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
6155
+ if orig_edge_index:
6156
+ d = Topology.Dictionary(edges[orig_edge_index])
6157
+ e = Topology.SetDictionary(e, d)
6158
+ c_edges.append(e)
6159
+ used[x][y] = 1
6160
+ used[y][x] = 1
6161
+ new_g = Graph.ByVerticesEdges(c_vertices, c_edges)
6162
+ return new_g
6163
+
6164
+ def line_layout_2d(graph, length=1):
6165
+ vertices = Graph.Vertices(graph)
6166
+ edges = Graph.Edges(graph)
6167
+ edge_dict = {}
6168
+
6169
+ for i, edge in enumerate(edges):
6170
+ sv = Edge.StartVertex(edge)
6171
+ ev = Edge.EndVertex(edge)
6172
+ si = Vertex.Index(sv, vertices)
6173
+ ei = Vertex.Index(ev, vertices)
6174
+ edge_dict[str(si)+"_"+str(ei)] = i
6175
+ edge_dict[str(ei)+"_"+str(si)] = i
6176
+
6177
+ n = len(vertices)
6178
+ c = Wire.Line(length=length, sides=n-1)
6179
+ c_vertices = Topology.Vertices(c)
6180
+
6181
+ for i, c_v in enumerate(c_vertices):
6182
+ d = Topology.Dictionary(vertices[i])
6183
+ c_v = Topology.SetDictionary(c_v, d)
6184
+ adj_dict = Graph.AdjacencyDictionary(graph)
6185
+ keys = adj_dict.keys()
6186
+ c_edges = []
6187
+ used = [[0] * n for _ in range(n)]
6188
+ for key in keys:
6189
+ x = int(key)
6190
+ adj_vertices = [int(v) for v in adj_dict[key]]
6191
+ for y in adj_vertices:
6192
+ if used[x][y] == 0:
6193
+ if Vertex.X(c_vertices[x]) < Vertex.X(c_vertices[y]):
6194
+ e = Edge.ByVertices(c_vertices[x], c_vertices[y])
6195
+ else:
6196
+ e = Edge.ByVertices(c_vertices[y], c_vertices[x])
5858
6197
 
5859
6198
  orig_edge_index = edge_dict[str(x)+"_"+str(y)]
5860
6199
  d = Topology.Dictionary(edges[orig_edge_index])
@@ -5865,10 +6204,51 @@ class Graph:
5865
6204
  new_g = Graph.ByVerticesEdges(c_vertices, c_edges)
5866
6205
  return new_g
5867
6206
 
5868
- def spring_layout(edge_list, iterations=500, k=None, seed=None):
5869
- # Compute the layout of a graph using the Fruchterman-Reingold algorithm
5870
- # with a force-directed layout approach.
6207
+ def grid_layout_3d(graph, size=1):
6208
+ vertices = Graph.Vertices(graph)
6209
+ n = len(vertices)
6210
+ edges = Graph.Edges(graph)
6211
+ edge_dict = {}
5871
6212
 
6213
+ for i, edge in enumerate(edges):
6214
+ sv = Edge.StartVertex(edge)
6215
+ ev = Edge.EndVertex(edge)
6216
+ si = Vertex.Index(sv, vertices)
6217
+ ei = Vertex.Index(ev, vertices)
6218
+ edge_dict[str(si)+"_"+str(ei)] = i
6219
+ edge_dict[str(ei)+"_"+str(si)] = i
6220
+ c_coords = generate_cubic_matrix(size, n)
6221
+ c_vertices = [Vertex.ByCoordinates(list(coord)) for coord in c_coords[:n]]
6222
+
6223
+ for i, c_v in enumerate(c_vertices):
6224
+ d = Topology.Dictionary(vertices[i])
6225
+ c_v = Topology.SetDictionary(c_v, d)
6226
+ adj_dict = Graph.AdjacencyDictionary(graph)
6227
+ keys = adj_dict.keys()
6228
+
6229
+ c_edges = []
6230
+ used = [[0] * n for _ in range(n)]
6231
+ for key in keys:
6232
+ x = int(key)
6233
+ adj_vertices = [int(v) for v in adj_dict[key]]
6234
+ for y in adj_vertices:
6235
+ if used[x][y] == 0:
6236
+ e = Edge.ByVertices(c_vertices[x], c_vertices[y])
6237
+ orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
6238
+ if orig_edge_index:
6239
+ d = Topology.Dictionary(edges[orig_edge_index])
6240
+ e = Topology.SetDictionary(e, d)
6241
+ c_edges.append(e)
6242
+ used[x][y] = 1
6243
+ used[y][x] = 1
6244
+ new_g = Graph.ByVerticesEdges(c_vertices, c_edges)
6245
+ return new_g
6246
+
6247
+ def spring_layout_2d(edge_list, iterations=500, k=None, seed=None):
6248
+ # Compute the layout of a graph using the Fruchterman-Reingold algorithm
6249
+ # with a force-directed
6250
+
6251
+ iterations = max(1, iterations)
5872
6252
  adj_matrix = edge_list_to_adjacency_matrix(edge_list)
5873
6253
  # Set the random seed
5874
6254
  if seed is not None:
@@ -5916,7 +6296,60 @@ class Graph:
5916
6296
 
5917
6297
  return pos
5918
6298
 
5919
- def tree_layout(edge_list, root_index=0):
6299
+ def spring_layout_3d(edge_list, iterations=500, k=None, seed=None):
6300
+ # Compute the layout of a graph using the Fruchterman-Reingold algorithm
6301
+ # with a force-directed layout approach.
6302
+
6303
+ iterations = max(1,iterations)
6304
+
6305
+ adj_matrix = edge_list_to_adjacency_matrix(edge_list)
6306
+ # Set the random seed
6307
+ if seed is not None:
6308
+ np.random.seed(seed)
6309
+
6310
+ # Set the optimal distance between nodes
6311
+ if k is None or k <= 0:
6312
+ k = np.cbrt(1.0 / adj_matrix.shape[0]) # Adjusted for 3D
6313
+
6314
+ # Initialize the positions of the nodes randomly in 3D
6315
+ pos = np.random.rand(adj_matrix.shape[0], 3)
6316
+
6317
+ # Compute the initial temperature
6318
+ t = 0.1 * np.max(pos)
6319
+
6320
+ # Compute the cooling factor
6321
+ cooling_factor = t / iterations
6322
+
6323
+ # Iterate over the specified number of iterations
6324
+ for i in range(iterations):
6325
+ # Compute the distance between each pair of nodes
6326
+ delta = pos[:, np.newaxis, :] - pos[np.newaxis, :, :]
6327
+ distance = np.linalg.norm(delta, axis=-1)
6328
+
6329
+ # Avoid division by zero
6330
+ distance = np.where(distance == 0, 0.1, distance)
6331
+
6332
+ # Compute the repulsive force between each pair of nodes
6333
+ repulsive_force = k ** 2 / distance ** 2
6334
+
6335
+ # Compute the attractive force between each pair of adjacent nodes
6336
+ attractive_force = adj_matrix * distance / k
6337
+
6338
+ # Compute the total force acting on each node
6339
+ force = np.sum((repulsive_force - attractive_force)[:, :, np.newaxis] * delta, axis=1)
6340
+
6341
+ # Compute the displacement of each node
6342
+ displacement = t * force / np.linalg.norm(force, axis=1)[:, np.newaxis]
6343
+
6344
+ # Update the positions of the nodes
6345
+ pos += displacement
6346
+
6347
+ # Cool the temperature
6348
+ t -= cooling_factor
6349
+
6350
+ return pos
6351
+
6352
+ def tree_layout_2d(edge_list, root_index=0):
5920
6353
 
5921
6354
  root, num_nodes = tree_from_edge_list(edge_list, root_index)
5922
6355
  dt = buchheim(root)
@@ -5942,8 +6375,46 @@ class Graph:
5942
6375
  pos[:, 1] = np.max(pos[:, 1]) - pos[:, 1]
5943
6376
 
5944
6377
  return pos
6378
+
6379
+ def tree_layout_3d(edge_list, root_index=0, base_radius=1.0, radius_factor=1.5):
6380
+ root, num_nodes = tree_from_edge_list(edge_list, root_index)
6381
+ dt = buchheim(root)
6382
+ pos = np.zeros((num_nodes, 3)) # Initialize 3D positions
6383
+
6384
+ pos[int(dt.tree.node), 0] = dt.x
6385
+ pos[int(dt.tree.node), 1] = dt.y
6386
+ pos[int(dt.tree.node), 2] = 0 # Root at z = 0
6387
+
6388
+ old_roots = [dt]
6389
+ new_roots = []
6390
+ depth = 1 # Start at depth level 1 for children
6391
+
6392
+ while len(old_roots) > 0:
6393
+ new_roots = []
6394
+ for temp_root in old_roots:
6395
+ children = temp_root.children
6396
+ num_children = len(children)
6397
+ if num_children > 0:
6398
+ # Increase the radius dynamically based on the number of children
6399
+ dynamic_radius = base_radius + (num_children - 1) * radius_factor * depth
6400
+
6401
+ angle_step = 2 * np.pi / num_children # Angle between each child
6402
+ for i, child in enumerate(children):
6403
+ angle = i * angle_step
6404
+ pos[int(child.tree.node), 0] = pos[int(temp_root.tree.node), 0] + dynamic_radius * np.cos(angle) # X position
6405
+ pos[int(child.tree.node), 1] = pos[int(temp_root.tree.node), 1] + dynamic_radius * np.sin(angle) # Y position
6406
+ pos[int(child.tree.node), 2] = -dynamic_radius*depth # Z-coordinate based on depth
6407
+
6408
+ new_roots.extend(children)
6409
+
6410
+ old_roots = new_roots
6411
+ depth += 1 # Increment depth for the next level
6412
+
6413
+ pos[:, 1] = np.max(pos[:, 1]) - pos[:, 1] # Flip y-coordinates if necessary
5945
6414
 
5946
- def radial_layout(edge_list, root_index=0):
6415
+ return pos
6416
+
6417
+ def radial_layout_2d(edge_list, root_index=0):
5947
6418
  root, num_nodes = tree_from_edge_list(edge_list, root_index)
5948
6419
  dt = buchheim(root)
5949
6420
  pos = np.zeros((num_nodes, 2))
@@ -5984,53 +6455,119 @@ class Graph:
5984
6455
 
5985
6456
  return new_pos
5986
6457
 
5987
- def graph_layout(edge_list, layout='tree', root_index=0, k=None, seed=None, iterations=500):
6458
+ def spherical_layout_3d(edge_list, root_index=0):
6459
+ root, num_nodes = tree_from_edge_list(edge_list, root_index)
6460
+ dt = buchheim(root)
5988
6461
 
5989
- if layout == 'tree':
5990
- return tree_layout(edge_list, root_index=root_index)
5991
- elif layout == 'spring':
5992
- return spring_layout(edge_list, k=k, seed=seed, iterations=iterations)
5993
- elif layout == 'radial':
5994
- return radial_layout(edge_list, root_index=root_index)
5995
- else:
5996
- raise NotImplementedError(f"{layout} is not implemented yet. Please choose from ['radial', 'spring', 'tree']")
6462
+ # Initialize positions with 3 columns for x, y, z coordinates
6463
+ pos = np.zeros((num_nodes, 3))
5997
6464
 
5998
- def vertex_max_degree(graph, vertices):
5999
- degrees = [Graph.VertexDegree(graph, vertex) for vertex in vertices]
6000
- i = degrees.index(max(degrees))
6001
- return vertices[i], i
6465
+ # Set initial coordinates and depth for the root node
6466
+ pos[int(dt.tree.node), 0] = dt.x
6467
+ pos[int(dt.tree.node), 1] = dt.y
6468
+ depth = np.zeros(num_nodes) # To track the depth of each node
6469
+
6470
+ old_roots = [(dt, 0)] # Store nodes with their depth levels
6471
+ new_roots = []
6472
+
6473
+ while len(old_roots) > 0:
6474
+ new_roots = []
6475
+ for temp_root, current_depth in old_roots:
6476
+ children = temp_root.children
6477
+ for child in children:
6478
+ node_index = int(child.tree.node)
6479
+ pos[node_index, 0] = child.x
6480
+ pos[node_index, 1] = child.y
6481
+ depth[node_index] = current_depth + 1 # Increase depth for children
6482
+ new_roots.extend([(child, current_depth + 1) for child in children])
6483
+
6484
+ old_roots = new_roots
6485
+
6486
+ # Normalize x and y coordinates to a [0, 1] range
6487
+ pos[:, 0] = pos[:, 0] - np.min(pos[:, 0])
6488
+ pos[:, 1] = pos[:, 1] - np.min(pos[:, 1])
6489
+
6490
+ pos[:, 0] = pos[:, 0] / np.max(pos[:, 0])
6491
+ pos[:, 0] = pos[:, 0] - pos[:, 0][root_index]
6492
+
6493
+ range_ = np.max(pos[:, 0]) - np.min(pos[:, 0])
6494
+ pos[:, 0] = pos[:, 0] / range_
6495
+
6496
+ pos[:, 0] = pos[:, 0] * np.pi * 1.98 # Longitude (azimuthal angle)
6497
+ pos[:, 1] = pos[:, 1] / np.max(pos[:, 1]) # Latitude (polar angle)
6498
+
6499
+ # Convert the 2D coordinates to 3D spherical coordinates
6500
+ new_pos = np.zeros((num_nodes, 3)) # 3D position array
6501
+ base_radius = 1 # Base radius for the first sphere
6502
+ radius_increment = 1.3 # Increase in radius per depth level
6503
+
6504
+ # pos[:, 0] is the azimuth angle (longitude) in radians
6505
+ # pos[:, 1] is the polar angle (latitude) in radians (adjusted to go from 0 to pi)
6506
+ polar_angle = pos[:, 1] * np.pi # Scaling to go from 0 to pi
6507
+ azimuth_angle = pos[:, 0]
6508
+
6509
+ # Calculate the 3D Cartesian coordinates based on depth
6510
+ for i in range(num_nodes):
6511
+ r = base_radius + depth[i] * radius_increment # Radius grows with depth
6512
+ new_pos[i, 0] = r * np.sin(polar_angle[i]) * np.cos(azimuth_angle[i]) # X = r * sin(θ) * cos(φ)
6513
+ new_pos[i, 1] = r * np.sin(polar_angle[i]) * np.sin(azimuth_angle[i]) # Y = r * sin(θ) * sin(φ)
6514
+ new_pos[i, 2] = r * np.cos(polar_angle[i]) # Z = r * cos(θ)
6515
+
6516
+ return new_pos
6002
6517
 
6003
6518
  if not Topology.IsInstance(graph, "Graph"):
6004
- print("Graph.Flatten - Error: The input graph is not a valid topologic graph. Returning None.")
6519
+ if not silent:
6520
+ print("Graph.Flatten - Error: The input graph is not a valid topologic graph. Returning None.")
6005
6521
  return None
6006
6522
 
6007
- if 'circ' in layout.lower():
6008
- new_graph = circle_layout(graph, radius=radius)
6009
- return new_graph
6010
- d = Graph.MeshData(graph)
6011
- vertices = d['vertices']
6012
- edges = d['edges']
6013
- v_dicts = d['vertexDictionaries']
6014
- e_dicts = d['edgeDictionaries']
6015
- vertices = Graph.Vertices(graph)
6016
- if rootVertex == None:
6017
- rootVertex, root_index = vertex_max_degree(graph, vertices)
6018
- else:
6019
- root_index = Vertex.Index(rootVertex, vertices, tolerance=tolerance)
6020
- if root_index == None:
6021
- root_index = 0
6022
- if 'rad' in layout.lower():
6023
- positions = radial_layout(edges, root_index=root_index)
6024
- elif 'spring' in layout.lower():
6025
- positions = spring_layout(edges, k=k, seed=seed, iterations=iterations)
6026
- elif 'tree' in layout.lower():
6027
- positions = tree_layout(edges, root_index=root_index)
6523
+ if 'circ' in shape.lower():
6524
+ return circle_layout_2d(graph, radius=size/2, sides=sides)
6525
+ elif 'lin' in shape.lower():
6526
+ return line_layout_2d(graph, length=size)
6527
+ elif 'grid' in shape.lower() and '2d' in shape.lower():
6528
+ return grid_layout_2d(graph, size=size)
6529
+ elif 'sphere' == shape.lower() and '3d' in shape.lower():
6530
+ return sphere_layout_3d(graph, radius=size/2)
6531
+ elif 'grid' in shape.lower() and '3d' in shape.lower():
6532
+ return grid_layout_3d(graph, size=size)
6533
+ elif 'cluster' in shape.lower() and '2d' in shape.lower():
6534
+ return cluster_layout_2d(graph, radius=size/2, key=key)
6535
+ elif 'cluster' in shape.lower() and '3d' in shape.lower():
6536
+ return cluster_layout_3d(graph, radius=size/2, key=key)
6028
6537
  else:
6029
- raise NotImplementedError(f"{layout} is not implemented yet. Please choose from ['radial', 'spring', 'tree']")
6030
- positions = positions.tolist()
6031
- positions = [[p[0], p[1], 0] for p in positions]
6032
- flat_graph = Graph.ByMeshData(positions, edges, v_dicts, e_dicts, tolerance=tolerance)
6033
- return flat_graph
6538
+ d = Graph.MeshData(graph)
6539
+ edges = d['edges']
6540
+ v_dicts = d['vertexDictionaries']
6541
+ e_dicts = d['edgeDictionaries']
6542
+ vertices = Graph.Vertices(graph)
6543
+ if rootVertex == None:
6544
+ rootVertex, root_index = vertex_max_degree(graph, vertices)
6545
+ else:
6546
+ root_index = Vertex.Index(rootVertex, vertices, tolerance=tolerance)
6547
+ if root_index == None:
6548
+ root_index = 0
6549
+ if 'rad' in shape.lower() and '2d' in shape.lower():
6550
+ positions = radial_layout_2d(edges, root_index=root_index)
6551
+ elif 'spherical' in shape.lower() and '3d' in shape.lower():
6552
+ positions = spherical_layout_3d(edges, root_index=root_index)
6553
+ elif 'spring' in shape.lower() and "3d" in shape.lower():
6554
+ positions = spring_layout_3d(edges, k=k, seed=seed, iterations=iterations)
6555
+ elif 'spring' in shape.lower() and '2d' in shape.lower():
6556
+ positions = spring_layout_2d(edges, k=k, seed=seed, iterations=iterations)
6557
+ elif 'tree' in shape.lower() and '2d' in shape.lower():
6558
+ positions = tree_layout_2d(edges, root_index=root_index)
6559
+ elif 'tree' in shape.lower() and '3d' in shape.lower():
6560
+ positions = tree_layout_3d(edges, root_index=root_index, base_radius=1.0, radius_factor=1.5)
6561
+ else:
6562
+ if not silent:
6563
+ print(f"{shape} is not implemented yet. Please choose from ['circle 2D', 'grid 2D', 'line 2D', 'radial 2D', 'spring 2D', 'tree 2D', 'grid 3D', 'sphere 3D', 'tree 3D']. Returning None.")
6564
+ return None
6565
+ positions = positions.tolist()
6566
+ if len(positions[0]) == 3:
6567
+ positions = [[p[0], p[1], p[2]] for p in positions]
6568
+ else:
6569
+ positions = [[p[0], p[1], 0] for p in positions]
6570
+ return Graph.ByMeshData(positions, edges, v_dicts, e_dicts, tolerance=tolerance)
6034
6571
 
6035
6572
  @staticmethod
6036
6573
  def GlobalClusteringCoefficient(graph):
topologicpy/Plotly.py CHANGED
@@ -415,23 +415,24 @@ class Plotly:
415
415
  e_cluster = Cluster.ByTopologies(vertices)
416
416
  geo = Topology.Geometry(e_cluster, mantissa=mantissa)
417
417
  vertices = geo['vertices']
418
- v_data = Plotly.DataByTopology(e_cluster,
419
- vertexColor=vertexColor,
420
- vertexColorKey=vertexColorKey,
421
- vertexSize=vertexSize,
422
- vertexSizeKey=vertexSizeKey,
423
- vertexLabelKey=vertexLabelKey,
424
- showVertexLabel=showVertexLabel,
425
- vertexGroupKey=vertexGroupKey,
426
- vertexMinGroup=vertexMinGroup,
427
- vertexMaxGroup=vertexMaxGroup,
428
- vertexGroups=vertexGroups,
429
- vertexLegendLabel=vertexLegendLabel,
430
- vertexLegendGroup=vertexLegendGroup,
431
- vertexLegendRank=vertexLegendRank,
432
- colorScale=colorScale)
433
-
434
- data += v_data
418
+ if len(vertices) > 0:
419
+ v_data = Plotly.DataByTopology(e_cluster,
420
+ vertexColor=vertexColor,
421
+ vertexColorKey=vertexColorKey,
422
+ vertexSize=vertexSize,
423
+ vertexSizeKey=vertexSizeKey,
424
+ vertexLabelKey=vertexLabelKey,
425
+ showVertexLabel=showVertexLabel,
426
+ vertexGroupKey=vertexGroupKey,
427
+ vertexMinGroup=vertexMinGroup,
428
+ vertexMaxGroup=vertexMaxGroup,
429
+ vertexGroups=vertexGroups,
430
+ vertexLegendLabel=vertexLegendLabel,
431
+ vertexLegendGroup=vertexLegendGroup,
432
+ vertexLegendRank=vertexLegendRank,
433
+ colorScale=colorScale)
434
+
435
+ data += v_data
435
436
 
436
437
  if showEdges:
437
438
  e_dictionaries = []
@@ -458,12 +459,12 @@ class Plotly:
458
459
  else:
459
460
  new_edges = edges
460
461
  e_dictionaries.append(d)
461
-
462
- e_cluster = Cluster.ByTopologies(new_edges)
463
- geo = Topology.Geometry(e_cluster, mantissa=mantissa)
464
- vertices = geo['vertices']
465
- edges = geo['edges']
466
- data.extend(Plotly.edgeData(vertices, edges, dictionaries=e_dictionaries, color=edgeColor, colorKey=edgeColorKey, width=edgeWidth, widthKey=edgeWidthKey, labelKey=edgeLabelKey, showEdgeLabel=showEdgeLabel, groupKey=edgeGroupKey, minGroup=edgeMinGroup, maxGroup=edgeMaxGroup, groups=edgeGroups, legendLabel=edgeLegendLabel, legendGroup=edgeLegendGroup, legendRank=edgeLegendRank, showLegend=showEdgeLegend, colorScale=colorScale))
462
+ if len(new_edges) > 0:
463
+ e_cluster = Cluster.ByTopologies(new_edges)
464
+ geo = Topology.Geometry(e_cluster, mantissa=mantissa)
465
+ vertices = geo['vertices']
466
+ edges = geo['edges']
467
+ data.extend(Plotly.edgeData(vertices, edges, dictionaries=e_dictionaries, color=edgeColor, colorKey=edgeColorKey, width=edgeWidth, widthKey=edgeWidthKey, labelKey=edgeLabelKey, showEdgeLabel=showEdgeLabel, groupKey=edgeGroupKey, minGroup=edgeMinGroup, maxGroup=edgeMaxGroup, groups=edgeGroups, legendLabel=edgeLegendLabel, legendGroup=edgeLegendGroup, legendRank=edgeLegendRank, showLegend=showEdgeLegend, colorScale=colorScale))
467
468
  return data
468
469
 
469
470
  @staticmethod
@@ -496,7 +497,7 @@ class Plotly:
496
497
  x.append(v[0])
497
498
  y.append(v[1])
498
499
  z.append(v[2])
499
- label = ""
500
+ label = " "
500
501
  group = None
501
502
  if len(dictionaries) > 0:
502
503
  d = dictionaries[m]
@@ -506,15 +507,13 @@ class Plotly:
506
507
  color = Color.AnyToHex(d_color)
507
508
  groupList.append(color)
508
509
  if not labelKey == None:
509
- label = str(Dictionary.ValueAtKey(d, key=labelKey)) or ""
510
+ label = str(Dictionary.ValueAtKey(d, key=labelKey)) or " "
510
511
  if not sizeKey == None:
511
512
  size = Dictionary.ValueAtKey(d, key=sizeKey) or size
512
513
  if not groupKey == None:
513
- group = Dictionary.ValueAtKey(d, key=groupKey) or ""
514
+ group = Dictionary.ValueAtKey(d, key=groupKey) or " "
514
515
  try:
515
- if group == "":
516
- #color = 'white'
517
- #groupList.append(Color.AnyToHex(color))
516
+ if group == " ":
518
517
  pass
519
518
  elif type(group) == int or type(group) == float:
520
519
  if group < minGroup:
@@ -853,121 +852,6 @@ class Plotly:
853
852
  def closest_index(input_value, values):
854
853
  return int(min(range(len(values)), key=lambda i: abs(values[i] - input_value)))
855
854
 
856
- def edgeData_old(vertices, edges, dictionaries=None, color="black", colorKey=None, width=1, widthKey=None, labelKey=None, showEdgeLabel = False, groupKey=None, minGroup=None, maxGroup=None, groups=[], legendLabel="Topology Edges", legendGroup=2, legendRank=2, showLegend=True, colorScale="Viridis"):
857
- traces = []
858
- x = []
859
- y = []
860
- z = []
861
- labels = []
862
- groupList = []
863
- label = ""
864
- group = ""
865
-
866
- if showEdgeLabel == True:
867
- mode = "lines+text"
868
- else:
869
- mode = "lines"
870
-
871
- if showEdgeLabel == True:
872
- mode = "lines+text"
873
- else:
874
- mode = "lines"
875
- if groups:
876
- if len(groups) > 0:
877
- if type(groups[0]) == int or type(groups[0]) == float:
878
- if not minGroup:
879
- minGroup = min(groups)
880
- if not maxGroup:
881
- maxGroup = max(groups)
882
- else:
883
- minGroup = 0
884
- maxGroup = len(groups) - 1
885
- else:
886
- minGroup = 0
887
- maxGroup = 1
888
-
889
- if colorKey or widthKey or labelKey or groupKey:
890
- keys = [x for x in [colorKey, widthKey, labelKey, groupKey] if not x == None]
891
- temp_dict = Helper.ClusterByKeys(edges, dictionaries, keys, silent=False)
892
- dict_clusters = temp_dict["dictionaries"]
893
- elements_clusters = temp_dict['elements']
894
- for j, elements_cluster in enumerate(elements_clusters):
895
- d = dict_clusters[j][0] # All dicitonaries have same values in dictionaries, so take first one.
896
- if d:
897
- if not colorKey == None:
898
- d_color = Dictionary.ValueAtKey(d, key=colorKey) or color
899
- color = Color.AnyToHex(d_color)
900
- if not labelKey == None:
901
- label = str(Dictionary.ValueAtKey(d, key=labelKey)) or ""
902
- if not widthKey == None:
903
- width = Dictionary.ValueAtKey(d, key=edgeWidthKey) or width
904
- if not groupKey == None:
905
- group = Dictionary.ValueAtKey(d, key=groupKey)
906
- if not group == None:
907
- if type(group) == int or type(group) == float:
908
- if group < minGroup:
909
- group = minGroup
910
- if group > maxGroup:
911
- group = maxGroup
912
- d_color = Color.ByValueInRange(group, minValue=minGroup, maxValue=maxGroup, colorScale=colorScale)
913
- else:
914
- d_color = Color.ByValueInRange(groups.index(group), minValue=minGroup, maxValue=maxGroup, colorScale=colorScale)
915
- color = d_color
916
- x = []
917
- y = []
918
- z = []
919
- for e in elements_cluster:
920
- sv = vertices[e[0]]
921
- ev = vertices[e[1]]
922
- x+=[sv[0], ev[0], None] # x-coordinates of edge ends
923
- y+=[sv[1], ev[1], None] # y-coordinates of edge ends
924
- z+=[sv[2], ev[2], None] # z-coordinates of edge ends
925
- if showEdgeLabel == True:
926
- mode = "lines+text"
927
- else:
928
- mode = "lines"
929
- trace = go.Scatter3d(x=x,
930
- y=y,
931
- z=z,
932
- name=label,
933
- showlegend=showLegend,
934
- marker_size=0,
935
- mode=mode,
936
- line=dict(color=color, width=width),
937
- legendgroup=legendGroup,
938
- legendrank=legendRank,
939
- text=label,
940
- hoverinfo='text')
941
- traces.append(trace)
942
- else:
943
- x = []
944
- y = []
945
- z = []
946
- for e in edges:
947
- sv = vertices[e[0]]
948
- ev = vertices[e[1]]
949
- x+=[sv[0], ev[0], None] # x-coordinates of edge ends
950
- y+=[sv[1], ev[1], None] # y-coordinates of edge ends
951
- z+=[sv[2], ev[2], None] # z-coordinates of edge ends
952
- if showEdgeLabel == True:
953
- mode = "lines+text"
954
- else:
955
- mode = "lines"
956
- trace = go.Scatter3d(x=x,
957
- y=y,
958
- z=z,
959
- name=label,
960
- showlegend=showLegend,
961
- marker_size=0,
962
- mode=mode,
963
- line=dict(color=color, width=width),
964
- legendgroup=legendGroup,
965
- legendrank=legendRank,
966
- text=label,
967
- hoverinfo='text')
968
- traces.append(trace)
969
-
970
- return traces
971
855
 
972
856
  def faceData(vertices, faces, dictionaries=None, color="#FAFAFA", colorKey=None,
973
857
  opacity=0.5, labelKey=None, groupKey=None,
@@ -1150,7 +1034,8 @@ class Plotly:
1150
1034
  geo = Topology.Geometry(e_cluster, mantissa=mantissa)
1151
1035
  vertices = geo['vertices']
1152
1036
  edges = geo['edges']
1153
- data.extend(Plotly.edgeData(vertices, edges, dictionaries=e_dictionaries, color=edgeColor, colorKey=edgeColorKey, width=edgeWidth, widthKey=edgeWidthKey, labelKey=edgeLabelKey, showEdgeLabel=showEdgeLabel, groupKey=edgeGroupKey, minGroup=edgeMinGroup, maxGroup=edgeMaxGroup, groups=edgeGroups, legendLabel=edgeLegendLabel, legendGroup=edgeLegendGroup, legendRank=edgeLegendRank, showLegend=showEdgeLegend, colorScale=colorScale))
1037
+ if len(edges) > 0:
1038
+ data.extend(Plotly.edgeData(vertices, edges, dictionaries=e_dictionaries, color=edgeColor, colorKey=edgeColorKey, width=edgeWidth, widthKey=edgeWidthKey, labelKey=edgeLabelKey, showEdgeLabel=showEdgeLabel, groupKey=edgeGroupKey, minGroup=edgeMinGroup, maxGroup=edgeMaxGroup, groups=edgeGroups, legendLabel=edgeLegendLabel, legendGroup=edgeLegendGroup, legendRank=edgeLegendRank, showLegend=showEdgeLegend, colorScale=colorScale))
1154
1039
 
1155
1040
  if showFaces and Topology.Type(topology) >= Topology.TypeID("Face"):
1156
1041
  if not faceColorKey == None:
@@ -1183,7 +1068,8 @@ class Plotly:
1183
1068
  geo = Topology.Geometry(f_cluster, mantissa=mantissa)
1184
1069
  vertices = geo['vertices']
1185
1070
  faces = geo['faces']
1186
- data.append(faceData(vertices, faces, dictionaries=f_dictionaries, color=faceColor, colorKey=faceColorKey, opacity=faceOpacity, labelKey=faceLabelKey, groupKey=faceGroupKey, minGroup=faceMinGroup, maxGroup=faceMaxGroup, groups=faceGroups, legendLabel=faceLegendLabel, legendGroup=faceLegendGroup, legendRank=faceLegendRank, showLegend=showFaceLegend, intensities=intensityList, colorScale=colorScale))
1071
+ if len(faces) > 0:
1072
+ data.append(faceData(vertices, faces, dictionaries=f_dictionaries, color=faceColor, colorKey=faceColorKey, opacity=faceOpacity, labelKey=faceLabelKey, groupKey=faceGroupKey, minGroup=faceMinGroup, maxGroup=faceMaxGroup, groups=faceGroups, legendLabel=faceLegendLabel, legendGroup=faceLegendGroup, legendRank=faceLegendRank, showLegend=showFaceLegend, intensities=intensityList, colorScale=colorScale))
1187
1073
  return data
1188
1074
 
1189
1075
  @staticmethod
topologicpy/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.7.73'
1
+ __version__ = '0.7.74'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: topologicpy
3
- Version: 0.7.73
3
+ Version: 0.7.74
4
4
  Summary: An AI-Powered Spatial Modelling and Analysis Software Library for Architecture, Engineering, and Construction.
5
5
  Author-email: Wassim Jabi <wassim.jabi@gmail.com>
6
6
  License: AGPL v3 License
@@ -11,13 +11,13 @@ topologicpy/Dictionary.py,sha256=0AsGoz48pGTye_F4KcJopNjD9STeQ50LHc6PPvERFaA,319
11
11
  topologicpy/Edge.py,sha256=9u9SdUxuenLUIK26xwFvPoYV34p0dCfXmHHBxdgvAdM,67164
12
12
  topologicpy/EnergyModel.py,sha256=AqTtmXE35SxvRXhG3vYAQd7GQDW-6HtjYPHua6ME4Eg,53762
13
13
  topologicpy/Face.py,sha256=q7x6auTju6IS3mdOhhXZdU3rqKSuJCE-5EOfxofDDMI,124348
14
- topologicpy/Graph.py,sha256=ULniT6Notyt9D-2AGws1B87FiVCV0qQp1bxgD0qpLQY,390517
14
+ topologicpy/Graph.py,sha256=BuSg8ilbZd2Qqoenmtw5FrkSe1fDLFS6jshljvq4dOs,416822
15
15
  topologicpy/Grid.py,sha256=9N6PE84qCm40TRi2WtlVZSBwXXr47zHpscEpZHg_JW4,18205
16
16
  topologicpy/Helper.py,sha256=Sv35czP_j0oLDeJcN8usswUm4U3auiK1LQ_Z_HBvxxg,21716
17
17
  topologicpy/Honeybee.py,sha256=HfTaEV1R8K1xOVQQy9sBOhBTF_ap8A2RxZOYhirp_Mw,21835
18
18
  topologicpy/Matrix.py,sha256=umgR7An919-wGInXJ1wpqnoQ2jCPdyMe2rcWTZ16upk,8079
19
19
  topologicpy/Neo4j.py,sha256=t52hgE9cVsqkGc7m7fjRsLnyfRHakVHwdvF4ms7ow78,22342
20
- topologicpy/Plotly.py,sha256=-Ewc-MPR7wI0Ml_NHoBOMLAJQjFTZRAPbSvVcv9q_Wg,118227
20
+ topologicpy/Plotly.py,sha256=VNUHRUNu4NUmf3HScQRml3TcwHe_ZoqXjqwttGSuU88,112642
21
21
  topologicpy/Polyskel.py,sha256=EFsuh2EwQJGPLiFUjvtXmAwdX-A4r_DxP5hF7Qd3PaU,19829
22
22
  topologicpy/PyG.py,sha256=LU9LCCzjxGPUM31qbaJXZsTvniTtgugxJY7y612t4A4,109757
23
23
  topologicpy/Shell.py,sha256=8OJjlWk9eCZ3uGOTht6ZVrcMczCafw-YWoDGueaz7eg,87673
@@ -28,9 +28,9 @@ topologicpy/Vector.py,sha256=A1g83zDHep58iVPY8WQ8iHNrSOfGWFEzvVeDuMnjDNY,33078
28
28
  topologicpy/Vertex.py,sha256=ZS6xK89JKokBKc0W8frdRhhuzR8c-dI1TTLt7pTf1iA,71032
29
29
  topologicpy/Wire.py,sha256=eVet2OToVsXi9AkDYo35LpfMPqJ6aKGD6QkiU4-Jvs8,182271
30
30
  topologicpy/__init__.py,sha256=vlPCanUbxe5NifC4pHcnhSzkmmYcs_UrZrTlVMsxcFs,928
31
- topologicpy/version.py,sha256=_EZM97Nx9ZHcqJT-3j_L8-CxcswiiAnQpZx_aTh--Yc,23
32
- topologicpy-0.7.73.dist-info/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
33
- topologicpy-0.7.73.dist-info/METADATA,sha256=DXCQK7AXT5405KD8O3PBcTDNL0gEjLfwM802I_H8QH4,10493
34
- topologicpy-0.7.73.dist-info/WHEEL,sha256=a7TGlA-5DaHMRrarXjVbQagU3Man_dCnGIWMJr5kRWo,91
35
- topologicpy-0.7.73.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
36
- topologicpy-0.7.73.dist-info/RECORD,,
31
+ topologicpy/version.py,sha256=_XwNUtWN5b4vRt6q08wKnk-7iLHDZXyRKXWBhgnWyP4,23
32
+ topologicpy-0.7.74.dist-info/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
33
+ topologicpy-0.7.74.dist-info/METADATA,sha256=QQ-v5awljewQVDPhAMmdZdVtUcKzJh4LRiYGoCePtmk,10493
34
+ topologicpy-0.7.74.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
35
+ topologicpy-0.7.74.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
36
+ topologicpy-0.7.74.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.4.0)
2
+ Generator: setuptools (75.5.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5