topologicpy 0.8.18__py3-none-any.whl → 0.8.20__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
@@ -4377,6 +4377,220 @@ class Graph:
4377
4377
  v = Topology.SetDictionary(v, d)
4378
4378
  return graph
4379
4379
 
4380
+ @staticmethod
4381
+ def Compare(graphA, graphB,
4382
+ weightAttributes: float = 1.0,
4383
+ weightGeometry: float = 1.0,
4384
+ weightMetrics: float = 1.0,
4385
+ weightStructure: float = 1.0,
4386
+ weightWL: float = 1.0,
4387
+ iterations: int = 3,
4388
+ mantissa: int = 6,
4389
+ silent: bool = False):
4390
+ """
4391
+ Compares two graphs and returns a similarity score based on attributres, geometry, metrics, structure, and
4392
+ the Weisfeiler-Lehman graph kernel. See https://en.wikipedia.org/wiki/Weisfeiler_Leman_graph_isomorphism_test
4393
+
4394
+ Parameters
4395
+ ----------
4396
+ graphA : topologic Graph
4397
+ The first input graph.
4398
+ graphB : topologic Graph
4399
+ The second input graph.
4400
+ weightAttributes : float , optional
4401
+ The desired weight for attribute similarity (dictionary key overlap at vertices). Default is 1.0.
4402
+ weightGeometry : float , optional
4403
+ The desired weight for geometric similarity (vertex positions). Default is 1.0.
4404
+ weightMetrics : float , optional
4405
+ The desired weight for metric similarity (graph-level and node-level). Default is 1.0.
4406
+ The compared metrics are: betweenness centrality, closeness centrality, clustering coefficient, degree, diameter, and pagerank.
4407
+ weightStructure : float , optional
4408
+ The desired weight for structural similarity (number of vertices and edges). Default is 1.0.
4409
+ weightWL : float , optional
4410
+ The desired weight for Weisfeiler-Lehman kernel similarity (iterative label propagation). Default is 1.0.
4411
+ iterations : int , optional
4412
+ The desired number of Weisfeiler-Lehman iterations. Default is 3.
4413
+ mantissa : int , optional
4414
+ The desired length of the mantissa. The default is 6.
4415
+ silent : bool , optional
4416
+ If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
4417
+
4418
+ Returns
4419
+ -------
4420
+ dict
4421
+ A dictionary of similarity scores between 0 (completely dissimilar) and 1 (identical), based on weighted components.
4422
+ The keys in the dictionary are:
4423
+ "attribute"
4424
+ "geometry"
4425
+ "metrics"
4426
+ "structure"
4427
+ "wl"
4428
+ "overall"
4429
+
4430
+ """
4431
+
4432
+ import hashlib
4433
+ from collections import Counter
4434
+ from topologicpy.Graph import Graph
4435
+ from topologicpy.Vertex import Vertex
4436
+ from topologicpy.Topology import Topology
4437
+ from topologicpy.Dictionary import Dictionary
4438
+
4439
+ def attribute_similarity(graphA, graphB, mantissa=6):
4440
+ v1 = Graph.Vertices(graphA)
4441
+ v2 = Graph.Vertices(graphB)
4442
+ if len(v1) != len(v2) or len(v1) == 0:
4443
+ return 0
4444
+
4445
+ match_score = 0
4446
+ for a, b in zip(v1, v2):
4447
+ dict_a = Topology.Dictionary(a)
4448
+ dict_b = Topology.Dictionary(b)
4449
+
4450
+ keys_a = set(Dictionary.Keys(dict_a)) if dict_a else set()
4451
+ keys_b = set(Dictionary.Keys(dict_b)) if dict_b else set()
4452
+
4453
+ if not keys_a and not keys_b:
4454
+ match_score += 1
4455
+ else:
4456
+ intersection = len(keys_a & keys_b)
4457
+ union = len(keys_a | keys_b)
4458
+ match_score += intersection / union if union > 0 else 0
4459
+
4460
+ return round(match_score / len(v1), mantissa)
4461
+
4462
+ def geometry_similarity(graphA, graphB, mantissa=6):
4463
+ v1 = Graph.Vertices(graphA)
4464
+ v2 = Graph.Vertices(graphB)
4465
+ if len(v1) != len(v2) or len(v1) == 0:
4466
+ return 0
4467
+
4468
+ total_dist = 0
4469
+ for a, b in zip(v1, v2): # assumes same order
4470
+ p1 = Vertex.Coordinates(a, mantissa=mantissa)
4471
+ p2 = Vertex.Coordinates(b, mantissa=mantissa)
4472
+ total_dist += sum((i - j) ** 2 for i, j in zip(p1, p2)) ** 0.5
4473
+ avg_dist = total_dist / len(v1)
4474
+ return round(1 / (1 + avg_dist), mantissa) # Inverse average distance
4475
+
4476
+ def metrics_similarity(graphA, graphB, mantissa=6):
4477
+ # Example using global metrics + mean of node metrics
4478
+ def safe_mean(lst):
4479
+ return sum(lst)/len(lst) if lst else 0
4480
+
4481
+ metrics1 = {
4482
+ "closeness": safe_mean(Graph.ClosenessCentrality(graphA)),
4483
+ "betweenness": safe_mean(Graph.BetweennessCentrality(graphA)),
4484
+ "degree": safe_mean(Graph.DegreeCentrality(graphA)),
4485
+ "pagerank": safe_mean(Graph.PageRank(graphA)),
4486
+ "clustering": Graph.GlobalClusteringCoefficient(graphA),
4487
+ "diameter": Graph.Diameter(graphA)
4488
+ }
4489
+
4490
+ metrics2 = {
4491
+ "closeness": safe_mean(Graph.ClosenessCentrality(graphB)),
4492
+ "betweenness": safe_mean(Graph.BetweennessCentrality(graphB)),
4493
+ "degree": safe_mean(Graph.DegreeCentrality(graphB)),
4494
+ "pagerank": safe_mean(Graph.PageRank(graphB)),
4495
+ "clustering": Graph.GlobalClusteringCoefficient(graphB),
4496
+ "diameter": Graph.Diameter(graphB)
4497
+ }
4498
+
4499
+ # Compute similarity as 1 - normalized absolute difference
4500
+ similarities = []
4501
+ for key in metrics1:
4502
+ v1, v2 = metrics1[key], metrics2[key]
4503
+ if v1 == 0 and v2 == 0:
4504
+ similarities.append(1)
4505
+ else:
4506
+ diff = abs(v1 - v2) / max(abs(v1), abs(v2), 1e-6)
4507
+ similarities.append(1 - diff)
4508
+
4509
+ return round(sum(similarities) / len(similarities), mantissa)
4510
+
4511
+ def structure_similarity(graphA, graphB, mantissa=6):
4512
+ v1 = Graph.Vertices(graphA)
4513
+ v2 = Graph.Vertices(graphB)
4514
+ e1 = Graph.Edges(graphA)
4515
+ e2 = Graph.Edges(graphB)
4516
+
4517
+ vertex_score = 1 - abs(len(v1) - len(v2)) / max(len(v1), len(v2), 1)
4518
+ edge_score = 1 - abs(len(e1) - len(e2)) / max(len(e1), len(e2), 1)
4519
+
4520
+ return round((vertex_score + edge_score) / 2, mantissa)
4521
+
4522
+ def weisfeiler_lehman_fingerprint(graph, iterations=3):
4523
+ vertices = Graph.Vertices(graph)
4524
+ labels = {}
4525
+
4526
+ for v in vertices:
4527
+ d = Topology.Dictionary(v)
4528
+ label = str(Dictionary.ValueAtKey(d, "label")) if d and Dictionary.ValueAtKey(d, "label") else "0"
4529
+ labels[v] = label
4530
+
4531
+ all_label_counts = Counter()
4532
+
4533
+ for _ in range(iterations):
4534
+ new_labels = {}
4535
+ for v in vertices:
4536
+ neighbors = Graph.AdjacentVertices(graph, v)
4537
+ neighbor_labels = sorted(labels.get(n, "0") for n in neighbors)
4538
+ long_label = labels[v] + "_" + "_".join(neighbor_labels)
4539
+ hashed_label = hashlib.md5(long_label.encode()).hexdigest()
4540
+ new_labels[v] = hashed_label
4541
+ all_label_counts[hashed_label] += 1
4542
+ labels = new_labels
4543
+
4544
+ return all_label_counts
4545
+
4546
+ def weisfeiler_lehman_similarity(graphA, graphB, iterations=3, mantissa=6):
4547
+ f1 = weisfeiler_lehman_fingerprint(graphA, iterations)
4548
+ f2 = weisfeiler_lehman_fingerprint(graphB, iterations)
4549
+
4550
+ common_labels = set(f1.keys()) & set(f2.keys())
4551
+ score = sum(min(f1[label], f2[label]) for label in common_labels)
4552
+ norm = max(sum(f1.values()), sum(f2.values()), 1)
4553
+
4554
+ return round(score / norm, mantissa)
4555
+
4556
+ if not Topology.IsInstance(graphA, "graph"):
4557
+ if not silent:
4558
+ print("Graph.Compare - Error: The graphA input parameter is not a valid topologic graph. Returning None.")
4559
+ return None
4560
+ if not Topology.IsInstance(graphB, "graph"):
4561
+ if not silent:
4562
+ print("Graph.Compare - Error: The graphB input parameter is not a valid topologic graph. Returning None.")
4563
+ return
4564
+
4565
+ total_weight = weightAttributes + weightGeometry + weightMetrics + weightStructure + weightWL
4566
+
4567
+ attribute_score = attribute_similarity(graphA, graphB, mantissa=mantissa) if weightAttributes else 0
4568
+ geometry_score = geometry_similarity(graphA, graphB, mantissa=mantissa) if weightGeometry else 0
4569
+ metrics_score = metrics_similarity(graphA, graphB, mantissa=mantissa) if weightMetrics else 0
4570
+ structure_score = structure_similarity(graphA, graphB, mantissa=mantissa) if weightStructure else 0
4571
+ wl_score = weisfeiler_lehman_similarity(graphA, graphB, iterations, mantissa=mantissa) if weightWL else 0
4572
+
4573
+ weighted_sum = (
4574
+ attribute_score * weightAttributes +
4575
+ geometry_score * weightGeometry +
4576
+ metrics_score * weightMetrics +
4577
+ structure_score * weightStructure +
4578
+ wl_score * weightWL
4579
+ )
4580
+
4581
+ if total_weight <= 0:
4582
+ overall_score = 0
4583
+ else:
4584
+ overall_score = weighted_sum / total_weight
4585
+
4586
+ return { "attribute": round(attribute_score, mantissa),
4587
+ "geometry": round(geometry_score, mantissa),
4588
+ "metrics": round(metrics_score, mantissa),
4589
+ "structure": round(structure_score, mantissa),
4590
+ "wl": round(wl_score, mantissa),
4591
+ "overall": round(overall_score, mantissa)
4592
+ }
4593
+
4380
4594
  @staticmethod
4381
4595
  def Complement(graph, tolerance=0.0001, silent=False):
4382
4596
  """
@@ -8850,7 +9064,7 @@ class Graph:
8850
9064
  return max_flow
8851
9065
 
8852
9066
  @staticmethod
8853
- def MeshData(graph, tolerance: float = 0.0001):
9067
+ def MeshData(graph, mantissa: int = 6, tolerance: float = 0.0001):
8854
9068
  """
8855
9069
  Returns the mesh data of the input graph.
8856
9070
 
@@ -8858,6 +9072,8 @@ class Graph:
8858
9072
  ----------
8859
9073
  graph : topologic_core.Graph
8860
9074
  The input graph.
9075
+ mantissa : int , optional
9076
+ The desired length of the mantissa. The default is 6.
8861
9077
  tolerance : float , optional
8862
9078
  The desired tolerance. The default is 0.0001.
8863
9079
 
@@ -8880,7 +9096,7 @@ class Graph:
8880
9096
  m_vertices = []
8881
9097
  v_dicts = []
8882
9098
  for g_vertex in g_vertices:
8883
- m_vertices.append(Vertex.Coordinates(g_vertex))
9099
+ m_vertices.append(Vertex.Coordinates(g_vertex, mantissa=mantissa))
8884
9100
  d = Dictionary.PythonDictionary(Topology.Dictionary(g_vertex))
8885
9101
  v_dicts.append(d)
8886
9102
  g_edges = Graph.Edges(graph)
@@ -9778,6 +9994,31 @@ class Graph:
9778
9994
  _ = graph.RemoveEdges([edge], tolerance) # Hook to Core
9779
9995
  return graph
9780
9996
 
9997
+ @staticmethod
9998
+ def RemoveIsolatedVertices(graph, tolerance=0.0001):
9999
+ """
10000
+ Removes all isolated vertices from the input graph.
10001
+
10002
+ Parameters
10003
+ ----------
10004
+ graph : topologic_core.Graph
10005
+ The input graph.
10006
+ tolerance : float , optional
10007
+ The desired tolerance. The default is 0.0001.
10008
+
10009
+ Returns
10010
+ -------
10011
+ topologic_core.Graph
10012
+ The input graph with all isolated vertices removed.
10013
+
10014
+ """
10015
+ from topologicpy.Topology import Topology
10016
+
10017
+ vertices = Graph.Vertices(graph)
10018
+ edges = Graph.Edges(graph)
10019
+ vertices = [v for v in vertices if Graph.VertexDegree(graph, v) > 0]
10020
+ return Graph.ByVerticesEdges(vertices, edges)
10021
+
9781
10022
  @staticmethod
9782
10023
  def RemoveVertex(graph, vertex, tolerance=0.0001):
9783
10024
  """
@@ -10299,6 +10540,125 @@ class Graph:
10299
10540
  v = Topology.SetDictionary(v, d)
10300
10541
  return return_graph
10301
10542
 
10543
+ @staticmethod
10544
+ def SubGraphMatches(subGraph, superGraph, strict=False, vertexMatcher=None, vertexKey: str = "id", mantissa: int = 6, tolerance: float = 0.0001):
10545
+ """
10546
+ Finds all subgraph matches from `subgraph` into `supergraph`.
10547
+ A match is valid if:
10548
+ - Each subgraph vertex maps to a unique supergraph vertex either by the vertexMatcher function or through matching the vertexKey values.
10549
+ - Each subgraph edge is represented either by an edge (if strict is set to True) or by an edge or a path (if strict is set to False) in the supergraph.
10550
+
10551
+ Parameters
10552
+ ----------
10553
+ subGraph : topologic_core.Graph
10554
+ The input subgraph.
10555
+ superGraph : topologic_core.Graph
10556
+ The input supergraph.
10557
+ strict : bool , optional
10558
+ If set to True, each subgraph edge must be represented by a single edge in the supergraph. Otherwise, an edge in the subgraph can be represented either with an edge or a path in the supergraph. The default is False.
10559
+ vertexMatcher : callable, optional
10560
+ If specified, this function is called to check if two vertices are matched. The format must be vertex_matcher(sub_vertex, super_vertex, mantissa, tolerance) -> bool.
10561
+ vertexKey : str , optional
10562
+ The dictionary key to use for vertex matching if the vertexMatcher input parameter is set to None. The default is "id".
10563
+ mantissa : int , optional
10564
+ The desired length of the mantissa. The default is 6.
10565
+ tolerance : float , optional
10566
+ The desired tolerance. The default is 0.0001.
10567
+
10568
+ Returns
10569
+ -------
10570
+ list
10571
+ A list of subgraphs matched to the supergraph. Each vertex in the matched subgraph has a dictionary that merges the keys and values from both the subgraph and the supergraph.
10572
+ """
10573
+
10574
+ from topologicpy.Vertex import Vertex
10575
+ from topologicpy.Edge import Edge
10576
+ from topologicpy.Dictionary import Dictionary
10577
+ from topologicpy.Topology import Topology
10578
+ import itertools
10579
+
10580
+ sub_vertices = Graph.Vertices(subGraph)
10581
+ super_vertices = Graph.Vertices(superGraph)
10582
+
10583
+ sub_ids = [Dictionary.ValueAtKey(Topology.Dictionary(v), vertexKey) for v in sub_vertices]
10584
+
10585
+ # Map vertex instance to index in sub_vertices
10586
+ sub_vertex_indices = {vid: i for i, vid in enumerate(sub_ids)}
10587
+
10588
+ # Default matcher by dictionary vertexKey
10589
+ if vertexMatcher is None:
10590
+ def vertexMatcher(v1, v2, mantissa=mantissa, tolerance=tolerance):
10591
+ d1 = Topology.Dictionary(v1)
10592
+ d2 = Topology.Dictionary(v2)
10593
+ id1 = Dictionary.ValueAtKey(d1, vertexKey) if d1 else None
10594
+ id2 = Dictionary.ValueAtKey(d2, vertexKey) if d2 else None
10595
+ return id1 == id2 and id1 is not None
10596
+
10597
+ # Step 1: Build candidate list for each subgraph vertex (by index)
10598
+ candidate_map = {}
10599
+ for i, sv in enumerate(sub_vertices):
10600
+ candidates = [v for v in super_vertices if vertexMatcher(sv, v, mantissa=mantissa, tolerance=tolerance)]
10601
+ if not candidates:
10602
+ return [] # No match for this vertex
10603
+ candidate_map[i] = candidates
10604
+
10605
+ # Step 2: Generate all injective mappings
10606
+ all_matches = []
10607
+ sub_indices = list(candidate_map.keys())
10608
+ candidate_lists = [candidate_map[i] for i in sub_indices]
10609
+
10610
+ for combo in itertools.product(*candidate_lists):
10611
+ if len(set(combo)) < len(combo):
10612
+ continue # Not injective
10613
+
10614
+ mapping = dict(zip(sub_indices, combo))
10615
+
10616
+ # Step 3: Check that each subgraph edge corresponds to a path in supergraph
10617
+ valid = True
10618
+ for edge in Graph.Edges(subGraph):
10619
+ sv1 = Edge.StartVertex(edge)
10620
+ sv2 = Edge.EndVertex(edge)
10621
+ d1 = Topology.Dictionary(sv1)
10622
+ d2 = Topology.Dictionary(sv2)
10623
+ id1 = Dictionary.ValueAtKey(d1, vertexKey) if d1 else None
10624
+ id2 = Dictionary.ValueAtKey(d2, vertexKey) if d2 else None
10625
+ if id1 == None or id2 == None:
10626
+ continue
10627
+ else:
10628
+ i1 = sub_vertex_indices[id1]
10629
+ i2 = sub_vertex_indices[id2]
10630
+ gv1 = mapping[i1]
10631
+ gv2 = mapping[i2]
10632
+
10633
+ path = Graph.ShortestPath(superGraph, gv1, gv2)
10634
+ if not path:
10635
+ valid = False
10636
+ break
10637
+ elif strict:
10638
+ if Topology.IsInstance(path, "Wire"):
10639
+ if len(Topology.Edges(path)) > 1:
10640
+ valid = False
10641
+ break
10642
+
10643
+ if valid:
10644
+ all_matches.append(mapping)
10645
+
10646
+ matched_subgraphs = []
10647
+ if len(all_matches) > 0:
10648
+ vertex_dictionaries = []
10649
+ d = Graph.MeshData(subGraph)
10650
+ subgraph_edges = d['edges']
10651
+ edge_dictionaries = d['edgeDictionaries']
10652
+ positions = []
10653
+ for i, mapping in enumerate(all_matches, 1):
10654
+ for svid, gv in mapping.items():
10655
+ positions.append(Vertex.Coordinates(gv))
10656
+ sd = Topology.Dictionary(sub_vertices[svid])
10657
+ gd = Topology.Dictionary(gv)
10658
+ vertex_dictionaries.append(Dictionary.ByMergedDictionaries(sd, gd))
10659
+ matched_subgraphs.append(Graph.ByMeshData(positions, subgraph_edges, vertexDictionaries=vertex_dictionaries, edgeDictionaries=edge_dictionaries))
10660
+ return matched_subgraphs
10661
+
10302
10662
  @staticmethod
10303
10663
  def _topological_distance(g, start, target):
10304
10664
  from collections import deque
topologicpy/Plotly.py CHANGED
@@ -282,6 +282,10 @@ class Plotly:
282
282
  vertexSize: float = 10,
283
283
  vertexSizeKey: str = None,
284
284
  vertexLabelKey: str = None,
285
+ vertexBorderColor: str = "black",
286
+ vertexBorderWidth: float = 1,
287
+ vertexBorderColorKey: str = None,
288
+ vertexBorderWidthKey: float = None,
285
289
  vertexGroupKey: str = None,
286
290
  vertexGroups: list = [],
287
291
  vertexMinGroup = None,
@@ -422,6 +426,10 @@ class Plotly:
422
426
  colorKey=vertexColorKey,
423
427
  size=vertexSize,
424
428
  sizeKey=vertexSizeKey,
429
+ borderColor=vertexBorderColor,
430
+ borderWidth=vertexBorderWidth,
431
+ borderColorKey=vertexBorderColorKey,
432
+ borderWidthKey=vertexBorderWidthKey,
425
433
  labelKey=vertexLabelKey,
426
434
  showVertexLabel=showVertexLabel,
427
435
  groupKey=vertexGroupKey,
@@ -433,24 +441,7 @@ class Plotly:
433
441
  legendRank=vertexLegendRank,
434
442
  showLegend=showVertexLegend,
435
443
  colorScale=colorScale)
436
-
437
- # v_data = Plotly.DataByTopology(e_cluster,
438
- # vertexColor=vertexColor,
439
- # vertexColorKey=vertexColorKey,
440
- # vertexSize=vertexSize,
441
- # vertexSizeKey=vertexSizeKey,
442
- # vertexLabelKey=vertexLabelKey,
443
- # showVertexLabel=showVertexLabel,
444
- # vertexGroupKey=vertexGroupKey,
445
- # vertexMinGroup=vertexMinGroup,
446
- # vertexMaxGroup=vertexMaxGroup,
447
- # vertexGroups=vertexGroups,
448
- # showVertexLegend=showVertexLegend,
449
- # vertexLegendLabel=vertexLegendLabel,
450
- # vertexLegendGroup=vertexLegendGroup,
451
- # vertexLegendRank=vertexLegendRank,
452
- # colorScale=colorScale)
453
- data += [v_data]
444
+ data += v_data
454
445
 
455
446
  if showEdges:
456
447
  e_dictionaries = []
@@ -486,17 +477,45 @@ class Plotly:
486
477
  return data
487
478
 
488
479
  @staticmethod
489
- def vertexData(vertices, dictionaries=[], color="black", colorKey=None, size=1.1, sizeKey=None, labelKey=None, showVertexLabel = False, groupKey=None, minGroup=None, maxGroup=None, groups=[], legendLabel="Topology Vertices", legendGroup=1, legendRank=1, showLegend=True, colorScale="Viridis"):
480
+ def vertexData(vertices,
481
+ dictionaries=[],
482
+ color="black",
483
+ colorKey=None,
484
+ size=1.1,
485
+ sizeKey=None,
486
+ borderColor="black",
487
+ borderWidth=0,
488
+ borderColorKey=None,
489
+ borderWidthKey=None,
490
+ labelKey=None,
491
+ showVertexLabel = False,
492
+ groupKey=None,
493
+ minGroup=None,
494
+ maxGroup=None,
495
+ groups=[],
496
+ legendLabel="Topology Vertices",
497
+ legendGroup=1,
498
+ legendRank=1,
499
+ showLegend=True,
500
+ colorScale="Viridis"):
490
501
  from topologicpy.Dictionary import Dictionary
491
502
  from topologicpy.Color import Color
492
503
  x = []
493
504
  y = []
494
505
  z = []
495
506
  n = len(str(len(vertices)))
496
- sizes = [size for i in range(len(vertices))]
497
- labels = ["Vertex_"+str(i+1).zfill(n) for i in range(len(vertices))]
498
- colors = [Color.AnyToHex(color) for i in range(len(vertices))]
499
- if colorKey or sizeKey or labelKey or groupKey:
507
+ sizes = []
508
+ labels = []
509
+ colors = []
510
+ borderColors = []
511
+ borderSizes = []
512
+ for i in range(len(vertices)):
513
+ sizes.append(size)
514
+ labels.append("Vertex_"+str(i+1).zfill(n))
515
+ colors.append(Color.AnyToHex(color))
516
+ borderColors.append(borderColor)
517
+ borderSizes.append(size+borderWidth*2)
518
+ if colorKey or sizeKey or borderColorKey or borderWidthKey or labelKey or groupKey:
500
519
  if groups:
501
520
  if len(groups) > 0:
502
521
  if type(groups[0]) == int or type(groups[0]) == float:
@@ -515,10 +534,8 @@ class Plotly:
515
534
  x.append(v[0])
516
535
  y.append(v[1])
517
536
  z.append(v[2])
518
- #colors.append(Color.AnyToHex(color))
519
- #labels.append("Vertex_"+str(m+1).zfill(n))
520
- #sizes.append(max(size, 1.1))
521
537
  if len(dictionaries) > 0:
538
+
522
539
  d = dictionaries[m]
523
540
  if d:
524
541
  if not colorKey == None:
@@ -533,6 +550,16 @@ class Plotly:
533
550
  sizes[m] = size
534
551
  if sizes[m] <= 0:
535
552
  sizes[m] = 1.1
553
+ if not borderColorKey == None:
554
+ temp_color = Dictionary.ValueAtKey(d, key=borderColorKey)
555
+ if not temp_color == None:
556
+ borderColors[m] = Color.AnyToHex(temp_color)
557
+ if not borderWidthKey == None:
558
+ temp_width = Dictionary.ValueAtKey(d, key=borderWidthKey)
559
+ if temp_width == None or temp_width <= 0:
560
+ borderSizes[m] = 0
561
+ else:
562
+ borderSizes[m] = sizes[m] + temp_width*2
536
563
  if not groupKey == None:
537
564
  c_value = Dictionary.ValueAtKey(d, key=groupKey)
538
565
  if not c_value == None:
@@ -565,14 +592,16 @@ class Plotly:
565
592
  mode = "markers+text"
566
593
  else:
567
594
  mode = "markers"
568
- vData= go.Scatter3d(x=x,
595
+ vData2 = go.Scatter3d(x=x,
569
596
  y=y,
570
597
  z=z,
571
598
  name=legendLabel,
572
599
  showlegend=showLegend,
573
600
  marker=dict(color=colors,
574
- size=sizes,
575
- opacity=1),
601
+ size=sizes,
602
+ symbol="circle",
603
+ opacity=1,
604
+ sizemode="diameter"),
576
605
  mode=mode,
577
606
  legendgroup=legendGroup,
578
607
  legendrank=legendRank,
@@ -580,7 +609,24 @@ class Plotly:
580
609
  hoverinfo='text',
581
610
  hovertext=labels
582
611
  )
583
- return vData
612
+ if borderWidth > 0 or borderWidthKey:
613
+ vData1 = go.Scatter3d(x=x,
614
+ y=y,
615
+ z=z,
616
+ name=legendLabel,
617
+ showlegend=showLegend,
618
+ marker=dict(color=borderColors,
619
+ size=borderSizes,
620
+ symbol="circle",
621
+ opacity=1,
622
+ sizemode="diameter"),
623
+ mode=mode
624
+ )
625
+
626
+ return_value = [vData1]+[vData2]
627
+ else:
628
+ return_value = [vData2]
629
+ return return_value
584
630
 
585
631
  @staticmethod
586
632
  def edgeData(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"):
@@ -716,11 +762,15 @@ class Plotly:
716
762
  @staticmethod
717
763
  def DataByTopology(topology,
718
764
  showVertices=True,
719
- vertexSize=1.1,
765
+ vertexSize=2.8,
720
766
  vertexSizeKey=None,
721
767
  vertexColor="black",
722
768
  vertexColorKey=None,
723
769
  vertexLabelKey=None,
770
+ vertexBorderColor: str = "black",
771
+ vertexBorderWidth: float = 0,
772
+ vertexBorderColorKey: str = None,
773
+ vertexBorderWidthKey: float = None,
724
774
  showVertexLabel=False,
725
775
  vertexGroupKey=None,
726
776
  vertexGroups=[],
@@ -771,7 +821,7 @@ class Plotly:
771
821
  showVertices : bool , optional
772
822
  If set to True the vertices will be drawn. Otherwise, they will not be drawn. The default is True.
773
823
  vertexSize : float , optional
774
- The desired size of the vertices. The default is 1.1.
824
+ The desired size of the output vertices. The default is 1.1.
775
825
  vertexSizeKey : str , optional
776
826
  The dictionary key under which to find the vertex size.The default is None.
777
827
  vertexColor : str , optional
@@ -784,6 +834,16 @@ class Plotly:
784
834
  The default is "black".
785
835
  vertexColorKey : str , optional
786
836
  The dictionary key under which to find the vertex color.The default is None.
837
+ vertexBorderWidth : float , optional
838
+ The desired width of the border of the output vertices. The default is 1.
839
+ vertexBorderColor : str , optional
840
+ The desired color of the border of the output vertices. This can be any plotly color string and may be specified as:
841
+ - A hex string (e.g. '#ff0000')
842
+ - An rgb/rgba string (e.g. 'rgb(255,0,0)')
843
+ - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
844
+ - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
845
+ - A named CSS color.
846
+ The default is "black".
787
847
  vertexLabelKey : str , optional
788
848
  The dictionary key to use to display the vertex label. The default is None.
789
849
  vertexGroupKey : str , optional
@@ -1066,11 +1126,31 @@ class Plotly:
1066
1126
  if showVertices:
1067
1127
  if len(vertices) == 0:
1068
1128
  for i, tp_v in enumerate(tp_vertices):
1069
- if vertexColorKey or vertexSizeKey or vertexLabelKey or vertexGroupKey:
1129
+ if vertexColorKey or vertexSizeKey or vertexBorderColorKey or vertexBorderWidthKey or vertexLabelKey or vertexGroupKey:
1070
1130
  d = Topology.Dictionary(tp_v)
1071
1131
  v_dictionaries.append(d)
1072
1132
  vertices.append([Vertex.X(tp_v, mantissa=mantissa), Vertex.Y(tp_v, mantissa=mantissa), Vertex.Z(tp_v, mantissa=mantissa)])
1073
- data.append(Plotly.vertexData(vertices, dictionaries=v_dictionaries, color=vertexColor, colorKey=vertexColorKey, size=vertexSize, sizeKey=vertexSizeKey, labelKey=vertexLabelKey, showVertexLabel=showVertexLabel, groupKey=vertexGroupKey, minGroup=vertexMinGroup, maxGroup=vertexMaxGroup, groups=vertexGroups, legendLabel=vertexLegendLabel, legendGroup=vertexLegendGroup, legendRank=vertexLegendRank, showLegend=showVertexLegend, colorScale=colorScale))
1133
+ data.extend(Plotly.vertexData(vertices,
1134
+ dictionaries=v_dictionaries,
1135
+ color=vertexColor,
1136
+ colorKey=vertexColorKey,
1137
+ size=vertexSize,
1138
+ sizeKey=vertexSizeKey,
1139
+ borderColor=vertexBorderColor,
1140
+ borderWidth=vertexBorderWidth,
1141
+ borderColorKey=vertexBorderColorKey,
1142
+ borderWidthKey=vertexBorderWidthKey,
1143
+ labelKey=vertexLabelKey,
1144
+ showVertexLabel=showVertexLabel,
1145
+ groupKey=vertexGroupKey,
1146
+ minGroup=vertexMinGroup,
1147
+ maxGroup=vertexMaxGroup,
1148
+ groups=vertexGroups,
1149
+ legendLabel=vertexLegendLabel,
1150
+ legendGroup=vertexLegendGroup,
1151
+ legendRank=vertexLegendRank,
1152
+ showLegend=showVertexLegend,
1153
+ colorScale=colorScale))
1074
1154
 
1075
1155
  if showEdges and Topology.Type(topology) > Topology.TypeID("Vertex"):
1076
1156
  if Topology.Type(topology) == Topology.TypeID("Edge"):
topologicpy/Topology.py CHANGED
@@ -8500,10 +8500,14 @@ class Topology():
8500
8500
  nameKey = "name",
8501
8501
  opacityKey = "opacity",
8502
8502
  showVertices=True,
8503
- vertexSize=None,
8503
+ vertexSize=2.8,
8504
8504
  vertexSizeKey = None,
8505
8505
  vertexColor="black",
8506
8506
  vertexColorKey = None,
8507
+ vertexBorderWidth=0,
8508
+ vertexBorderColor="black",
8509
+ vertexBorderWidthKey=None,
8510
+ vertexBorderColorKey=None,
8507
8511
  vertexLabelKey=None,
8508
8512
  showVertexLabel= False,
8509
8513
  vertexGroupKey=None,
@@ -8602,6 +8606,16 @@ class Topology():
8602
8606
  The default is "black".
8603
8607
  vertexColorKey : str , optional
8604
8608
  The key under which to find the color of the vertex. The default is None.
8609
+ vertexBorderWidth : float , optional
8610
+ The desired width of the borders of the output vertices. The default is 0.
8611
+ vertexBorderColor : str , optional
8612
+ The desired color of the borders of the output vertices. This can be any plotly color string and may be specified as:
8613
+ - A hex string (e.g. '#ff0000')
8614
+ - An rgb/rgba string (e.g. 'rgb(255,0,0)')
8615
+ - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
8616
+ - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
8617
+ - A named CSS color.
8618
+ The default is "black".
8605
8619
  vertexLabelKey : str , optional
8606
8620
  The dictionary key to use to display the vertex label. The default is None.
8607
8621
  showVertexLabels : bool , optional
@@ -8798,7 +8812,7 @@ class Topology():
8798
8812
  vll = name+" ("+vertexLegendLabel+")"
8799
8813
  ell = name+" ("+edgeLegendLabel+")"
8800
8814
 
8801
- data += Plotly.DataByGraph(topology,
8815
+ data.extend(Plotly.DataByGraph(topology,
8802
8816
  sagitta=sagitta,
8803
8817
  absolute=absolute,
8804
8818
  sides=sides,
@@ -8807,6 +8821,10 @@ class Topology():
8807
8821
  vertexColorKey=vertexColorKey,
8808
8822
  vertexSize=vSize,
8809
8823
  vertexSizeKey=vertexSizeKey,
8824
+ vertexBorderColor= vertexBorderColor,
8825
+ vertexBorderWidth=vertexBorderWidth,
8826
+ vertexBorderColorKey= vertexBorderColorKey,
8827
+ vertexBorderWidthKey=vertexBorderWidthKey,
8810
8828
  vertexLabelKey=vertexLabelKey,
8811
8829
  vertexGroupKey=vertexGroupKey,
8812
8830
  vertexGroups=vertexGroups,
@@ -8834,7 +8852,7 @@ class Topology():
8834
8852
  edgeLegendRank= (graph_counter+2),
8835
8853
  edgeLegendGroup=(graph_counter+2),
8836
8854
  colorScale=colorScale,
8837
- silent=silent)
8855
+ silent=silent))
8838
8856
  graph_counter += offset
8839
8857
  else:
8840
8858
  name = Dictionary.ValueAtKey(d, nameKey) or "Untitled"
@@ -8852,12 +8870,16 @@ class Topology():
8852
8870
  eColor = edgeColor
8853
8871
  if not d == None:
8854
8872
  faceOpacity = Dictionary.ValueAtKey(d, opacityKey) or faceOpacity
8855
- data += Plotly.DataByTopology(topology=topology,
8873
+ data.extend(Plotly.DataByTopology(topology=topology,
8856
8874
  showVertices=showVertices,
8857
8875
  vertexSize=vSize,
8858
8876
  vertexSizeKey=vertexSizeKey,
8859
8877
  vertexColor=vertexColor,
8860
8878
  vertexColorKey=vertexColorKey,
8879
+ vertexBorderWidth=vertexBorderWidth,
8880
+ vertexBorderColor=vertexBorderColor,
8881
+ vertexBorderColorKey= vertexBorderColorKey,
8882
+ vertexBorderWidthKey=vertexBorderWidthKey,
8861
8883
  vertexLabelKey=vertexLabelKey,
8862
8884
  showVertexLabel=showVertexLabel,
8863
8885
  vertexGroupKey=vertexGroupKey,
@@ -8901,7 +8923,7 @@ class Topology():
8901
8923
  intensities=intensities,
8902
8924
  colorScale=colorScale,
8903
8925
  mantissa=mantissa,
8904
- tolerance=tolerance)
8926
+ tolerance=tolerance))
8905
8927
  topology_counter += offset
8906
8928
  figure = Plotly.FigureByData(data=data, width=width, height=height,
8907
8929
  xAxis=xAxis, yAxis=yAxis, zAxis=zAxis, axisSize=axisSize,
topologicpy/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.8.18'
1
+ __version__ = '0.8.20'
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: topologicpy
3
- Version: 0.8.18
3
+ Version: 0.8.20
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
@@ -40,6 +40,7 @@ Requires-Dist: webcolors
40
40
  Requires-Dist: topologic_core>=7.0.1
41
41
  Provides-Extra: test
42
42
  Requires-Dist: pytest-xdist>=2.4.0; extra == "test"
43
+ Dynamic: license-file
43
44
 
44
45
  # topologicpy
45
46
 
@@ -11,26 +11,26 @@ topologicpy/Dictionary.py,sha256=7h-Gszgnt2OEOvOSADJ4pa-mTNlhQ9cuIiB5WHEW6aY,339
11
11
  topologicpy/Edge.py,sha256=yxkCVDYBflJNEYxnjMmlyvbkpg8TNy7y5bSH3yQ4jzs,71418
12
12
  topologicpy/EnergyModel.py,sha256=UoQ9Jm-hYsN383CbcLKw-y6BKitRHj0uyh84yQ-8ACg,53856
13
13
  topologicpy/Face.py,sha256=SlhB8L7BpDjd4a9YZE4UJ3zoGuF1oq9MSpuesEWro_Q,184847
14
- topologicpy/Graph.py,sha256=FBmiMObzztPwZFJ2T846Ivz0Y1kpzMF0sF-PDUMPk4o,498946
14
+ topologicpy/Graph.py,sha256=fsWgbYp1di1HtDipsXY9d2i42Fn_3dXsVKTac9vl7bU,515137
15
15
  topologicpy/Grid.py,sha256=2s9cSlWldivn1i9EUz4OOokJyANveqmRe_vR93CAndI,18245
16
16
  topologicpy/Helper.py,sha256=4H5KPiv_eiEs489UOOyGLe9RaeoZIfmMh3mk_YCHmXg,29100
17
17
  topologicpy/Honeybee.py,sha256=uDVtDbloydNoaBFcSNukKL_2PLyD6XKkCp1VHz1jtaU,21751
18
18
  topologicpy/Matrix.py,sha256=i22RLP5ebUAMuU7V1tZ__Z4lf1pg9fzq9nENsDZUV74,22779
19
19
  topologicpy/Neo4j.py,sha256=BKOF29fRgXmdpMGkrNzuYbyqgCJ6ElPPMYlfTxXiVbc,22392
20
- topologicpy/Plotly.py,sha256=RU_VioIRLGIYzwyKI9OQHOx9OxxGppdpagysgTbdxIE,115942
20
+ topologicpy/Plotly.py,sha256=gSe3tWLgF75-rPDjqw7riUIJpv0j_K7hedoxi3-OptE,119244
21
21
  topologicpy/Polyskel.py,sha256=ro5in--VT_uag55r5xymU5ufyAahsovIiJwyiqG_qH8,27082
22
22
  topologicpy/PyG.py,sha256=LU9LCCzjxGPUM31qbaJXZsTvniTtgugxJY7y612t4A4,109757
23
23
  topologicpy/Shell.py,sha256=--dJoSdz6BapxVEyG2DI0W5apO_xwLORj5qmR15yl2Y,87983
24
24
  topologicpy/Speckle.py,sha256=AlsGlSDuKRtX5jhVsPNSSjjbZis079HbUchDH_5RJmE,18187
25
25
  topologicpy/Sun.py,sha256=42tDWMYpwRG7Z2Qjtp94eRgBuqySq7k8TgNUZDK7QxQ,36837
26
- topologicpy/Topology.py,sha256=GFoSWeI5lwF1Uv2zQwZ6nZcs62YzdBAgI0HL1Lvq-NU,477015
26
+ topologicpy/Topology.py,sha256=QY195KZ5EostlGRnsPGSFglRIU0tunD2CiwsFPfpOqY,478447
27
27
  topologicpy/Vector.py,sha256=GkGt-aJ591IJ2IPffMAudvITLDPi2qZibZc4UAav6m8,42407
28
28
  topologicpy/Vertex.py,sha256=q99IrWwdNlvVfUXz6iP8icmP8NzP2nsiqtgnF9Jnpx8,80913
29
29
  topologicpy/Wire.py,sha256=IVPzBKsckuxC-rHvwxmvtVF7PpApz2lhPHomtQMo_kg,228499
30
30
  topologicpy/__init__.py,sha256=vlPCanUbxe5NifC4pHcnhSzkmmYcs_UrZrTlVMsxcFs,928
31
- topologicpy/version.py,sha256=vkzceT3rP0TlvJzcK0aNKq2TjD8ttqpbJun23OGEYQs,23
32
- topologicpy-0.8.18.dist-info/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
33
- topologicpy-0.8.18.dist-info/METADATA,sha256=Vl4wrRYKrz-K7io5ohz_sWZScxQf1MXQXR-I2x79ie0,10513
34
- topologicpy-0.8.18.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
35
- topologicpy-0.8.18.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
36
- topologicpy-0.8.18.dist-info/RECORD,,
31
+ topologicpy/version.py,sha256=uWqZIJblLUrle0kYZkrKO4aVSNXiyYenL0G_TSUJllE,23
32
+ topologicpy-0.8.20.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
33
+ topologicpy-0.8.20.dist-info/METADATA,sha256=0_3vo7ubfFiV0woZaJt8RCFHF9MZZ_Sc7VnqYZX4Mao,10535
34
+ topologicpy-0.8.20.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
35
+ topologicpy-0.8.20.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
36
+ topologicpy-0.8.20.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.1.0)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5