44//! with stable [`CellId`] and [`RelationshipId`] keys. [`to_graph_data`] serializes a
55//! [`Sheet`] and its [`Labels`] into a [`GraphData`] value ready for JSON encoding.
66
7- use std:: collections:: HashMap ;
8-
97use indexmap:: IndexMap ;
108use property_model:: { CellId , Error , RelationshipId , Sheet } ;
119use serde:: Serialize ;
@@ -28,16 +26,13 @@ pub struct CellMeta {
2826pub struct Labels {
2927 /// Cells in insertion order (preserves sidebar ordering).
3028 pub cells : IndexMap < CellId , CellMeta > ,
31- /// Relationship labels (reserved for future tooltip display).
32- pub relationships : HashMap < RelationshipId , String > ,
3329}
3430
3531impl Labels {
3632 /// Creates an empty label set.
3733 pub fn new ( ) -> Self {
3834 Self {
3935 cells : IndexMap :: new ( ) ,
40- relationships : HashMap :: new ( ) ,
4136 }
4237 }
4338
@@ -69,13 +64,6 @@ impl Labels {
6964 } ,
7065 ) ;
7166 }
72-
73- /// Registers a label for a relationship.
74- ///
75- /// - Precondition: `id` is a live relationship in the sheet this `Labels` will be used with.
76- pub fn add_relationship ( & mut self , id : RelationshipId , label : & str ) {
77- self . relationships . insert ( id, label. to_owned ( ) ) ;
78- }
7967}
8068
8169impl Default for Labels {
@@ -106,7 +94,10 @@ pub struct NodeData {
10694 pub value : String ,
10795}
10896
109- /// A single edge in the D3 graph (undirected; connects a cell to a relationship).
97+ /// A single edge in the D3 graph.
98+ ///
99+ /// When [`GraphData::arrows`] is `false` the edge is undirected; when `true`
100+ /// it is directed from `source` to `target`.
110101#[ derive( Serialize , Clone , PartialEq ) ]
111102pub struct LinkData {
112103 pub source : String ,
@@ -130,6 +121,9 @@ pub struct GraphData {
130121 pub changed : Vec < String > ,
131122 /// Always empty; reserved for future `when`/`otherwise` conditional relationships.
132123 pub groups : Vec < GroupData > ,
124+ /// `true` when at least one relationship has a cached plan and links are directed where
125+ /// plans exist; `false` when no plan has been computed.
126+ pub arrows : bool ,
133127}
134128
135129fn cell_node_id ( id : CellId ) -> String {
@@ -142,11 +136,17 @@ fn rel_node_id(id: RelationshipId) -> String {
142136
143137/// Serializes `sheet` and `labels` into a [`GraphData`] snapshot for D3.
144138///
139+ /// When a plan is cached (`sheet.selected_method` returns `Some`) the links are
140+ /// directed (inputs → relationship → outputs) and [`GraphData::arrows`] is `true`.
141+ /// Otherwise all cells adjacent to the relationship are emitted as undirected
142+ /// source→relationship edges and `arrows` is `false`.
143+ ///
145144/// - Complexity: O(c + r + e) where c is the number of cells, r the number of
146145/// relationships, and e the number of cell–relationship adjacency pairs.
147146pub fn to_graph_data ( sheet : & Sheet , labels : & Labels ) -> GraphData {
148147 let mut nodes = Vec :: new ( ) ;
149148 let mut links = Vec :: new ( ) ;
149+ let mut arrows = false ;
150150
151151 for id in sheet. cells ( ) {
152152 let ( label, value) = labels
@@ -169,7 +169,26 @@ pub fn to_graph_data(sheet: &Sheet, labels: &Labels) -> GraphData {
169169 label : String :: new ( ) ,
170170 value : String :: new ( ) ,
171171 } ) ;
172- if let Some ( adj) = sheet. relationship_adj ( id) {
172+
173+ if let Some ( method_idx) = sheet. selected_method ( id) {
174+ arrows = true ;
175+ if let Some ( inputs) = sheet. method_inputs ( id, method_idx) {
176+ for & cell_id in inputs {
177+ links. push ( LinkData {
178+ source : cell_node_id ( cell_id) ,
179+ target : rel_node_id ( id) ,
180+ } ) ;
181+ }
182+ }
183+ if let Some ( outputs) = sheet. method_outputs ( id, method_idx) {
184+ for & cell_id in outputs {
185+ links. push ( LinkData {
186+ source : rel_node_id ( id) ,
187+ target : cell_node_id ( cell_id) ,
188+ } ) ;
189+ }
190+ }
191+ } else if let Some ( adj) = sheet. relationship_adj ( id) {
173192 for & cell_id in adj {
174193 links. push ( LinkData {
175194 source : cell_node_id ( cell_id) ,
@@ -186,6 +205,7 @@ pub fn to_graph_data(sheet: &Sheet, labels: &Labels) -> GraphData {
186205 links,
187206 changed,
188207 groups : vec ! [ ] ,
208+ arrows,
189209 }
190210}
191211
@@ -205,12 +225,11 @@ mod tests {
205225 let c = sheet. add_cell ( 0.0_f64 ) ;
206226 labels. add_cell :: < f64 > ( c, "c" ) ;
207227
208- let rel = sheet
228+ sheet
209229 . add_relationship ( vec ! [ Method :: from_fn_2_1( [ a, b] , c, |x: & f64 , y: & f64 | {
210230 Ok ( x * y)
211231 } ) ] )
212232 . unwrap ( ) ;
213- labels. add_relationship ( rel, "×" ) ;
214233
215234 ( sheet, labels)
216235 }
@@ -296,6 +315,79 @@ mod tests {
296315 assert ! ( data. groups. is_empty( ) ) ;
297316 }
298317
318+ // Separate helper that adds the output cell first so propagation succeeds.
319+ fn demo_sheet_with_plan ( ) -> ( Sheet , Labels ) {
320+ let mut sheet = Sheet :: new ( ) ;
321+ let mut labels = Labels :: new ( ) ;
322+
323+ // c added first → lowest strength (output by default).
324+ let c = sheet. add_cell ( 0.0_f64 ) ;
325+ labels. add_cell :: < f64 > ( c, "c" ) ;
326+ let a = sheet. add_cell ( 2.0_f64 ) ;
327+ labels. add_cell :: < f64 > ( a, "a" ) ;
328+ let b = sheet. add_cell ( 3.0_f64 ) ;
329+ labels. add_cell :: < f64 > ( b, "b" ) ;
330+
331+ sheet
332+ . add_relationship ( vec ! [ Method :: from_fn_2_1( [ a, b] , c, |x: & f64 , y: & f64 | {
333+ Ok ( x * y)
334+ } ) ] )
335+ . unwrap ( ) ;
336+
337+ ( sheet, labels)
338+ }
339+
340+ #[ test]
341+ fn to_graph_data_arrows_false_before_propagate ( ) {
342+ let ( sheet, labels) = demo_sheet_with_plan ( ) ;
343+ let data = to_graph_data ( & sheet, & labels) ;
344+ assert ! ( !data. arrows) ;
345+ }
346+
347+ #[ test]
348+ fn to_graph_data_arrows_true_after_propagate ( ) {
349+ let ( mut sheet, labels) = demo_sheet_with_plan ( ) ;
350+ sheet. propagate ( ) . unwrap ( ) ;
351+ let data = to_graph_data ( & sheet, & labels) ;
352+ assert ! ( data. arrows) ;
353+ }
354+
355+ #[ test]
356+ fn to_graph_data_directed_input_links_target_relationship ( ) {
357+ // Method [a, b] → c; after propagate, a and b are inputs → 2 edges into rel.
358+ let ( mut sheet, labels) = demo_sheet_with_plan ( ) ;
359+ sheet. propagate ( ) . unwrap ( ) ;
360+ let data = to_graph_data ( & sheet, & labels) ;
361+
362+ let rel_id = data
363+ . nodes
364+ . iter ( )
365+ . find ( |n| n. kind == NodeKind :: Relationship )
366+ . map ( |n| n. id . clone ( ) )
367+ . unwrap ( ) ;
368+
369+ let to_rel: Vec < _ > = data. links . iter ( ) . filter ( |l| l. target == rel_id) . collect ( ) ;
370+ assert_eq ! ( to_rel. len( ) , 2 ) ;
371+ }
372+
373+ #[ test]
374+ fn to_graph_data_directed_output_links_source_relationship ( ) {
375+ // Method [a, b] → c; after propagate, c is the output → 1 edge out of rel.
376+ let ( mut sheet, labels) = demo_sheet_with_plan ( ) ;
377+ sheet. propagate ( ) . unwrap ( ) ;
378+ let data = to_graph_data ( & sheet, & labels) ;
379+
380+ let rel_id = data
381+ . nodes
382+ . iter ( )
383+ . find ( |n| n. kind == NodeKind :: Relationship )
384+ . map ( |n| n. id . clone ( ) )
385+ . unwrap ( ) ;
386+
387+ let from_rel: Vec < _ > = data. links . iter ( ) . filter ( |l| l. source == rel_id) . collect ( ) ;
388+ assert_eq ! ( from_rel. len( ) , 1 ) ;
389+ }
390+
299391 #[ test]
300392 fn display_closure_returns_value_string ( ) {
301393 let ( sheet, labels) = demo_sheet ( ) ;
0 commit comments