Coverage for src / graphable / views / cytoscape.py: 100%

39 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-16 21:32 +0000

1from dataclasses import dataclass 

2from json import dumps 

3from logging import getLogger 

4from pathlib import Path 

5from typing import Any, Callable 

6 

7from ..graph import Graph 

8from ..graphable import Graphable 

9from ..registry import register_view 

10 

11logger = getLogger(__name__) 

12 

13 

14@dataclass 

15class CytoscapeStylingConfig: 

16 """ 

17 Configuration for Cytoscape.js JSON serialization. 

18 

19 Attributes: 

20 node_data_fnc: Optional function to add extra data to each node's 'data' object. 

21 edge_data_fnc: Optional function to add extra data to each edge's 'data' object. 

22 reference_fnc: Function to generate the string identifier for each node. 

23 indent: JSON indentation level. 

24 """ 

25 

26 node_data_fnc: Callable[[Graphable[Any]], dict[str, Any]] | None = None 

27 edge_data_fnc: Callable[[Graphable[Any], Graphable[Any]], dict[str, Any]] | None = ( 

28 None 

29 ) 

30 reference_fnc: Callable[[Graphable[Any]], str] = lambda n: str(n.reference) 

31 indent: int | str | None = 2 

32 

33 

34def create_topology_cytoscape( 

35 graph: Graph, config: CytoscapeStylingConfig | None = None 

36) -> str: 

37 """ 

38 Generate a Cytoscape.js compatible JSON representation of the graph. 

39 

40 Args: 

41 graph (Graph): The graph to convert. 

42 config (CytoscapeStylingConfig | None): Serialization configuration. 

43 

44 Returns: 

45 str: A JSON string in Cytoscape.js elements format. 

46 """ 

47 logger.debug("Creating Cytoscape JSON representation.") 

48 config = config or CytoscapeStylingConfig() 

49 

50 elements = [] 

51 

52 for node in graph.topological_order(): 

53 # Node 

54 node_id = config.reference_fnc(node) 

55 node_data = { 

56 "id": node_id, 

57 "label": str(node.reference), 

58 "tags": list(node.tags), 

59 } 

60 if config.node_data_fnc: 

61 node_data.update(config.node_data_fnc(node)) 

62 

63 elements.append({"data": node_data}) 

64 

65 # Edges 

66 for dependent, attrs in graph.internal_dependents(node): 

67 dep_id = config.reference_fnc(dependent) 

68 edge_id = f"{node_id}_{dep_id}" 

69 edge_data = { 

70 "id": edge_id, 

71 "source": node_id, 

72 "target": dep_id, 

73 } 

74 if config.edge_data_fnc: 

75 edge_data.update(config.edge_data_fnc(node, dependent)) 

76 

77 # Add existing attributes 

78 edge_data.update(attrs) 

79 

80 elements.append({"data": edge_data}) 

81 

82 return dumps(elements, indent=config.indent) 

83 

84 

85@register_view(".cy.json", creator_fnc=create_topology_cytoscape) 

86def export_topology_cytoscape( 

87 graph: Graph, 

88 output: Path, 

89 config: CytoscapeStylingConfig | None = None, 

90) -> None: 

91 """ 

92 Export the graph to a Cytoscape JSON file. 

93 

94 Args: 

95 graph (Graph): The graph to export. 

96 output (Path): The output file path. 

97 config (CytoscapeStylingConfig | None): Serialization configuration. 

98 """ 

99 logger.info(f"Exporting Cytoscape JSON to: {output}") 

100 with open(output, "w+") as f: 

101 f.write(create_topology_cytoscape(graph, config))