Generate a self-contained HTML page with a D3.js force-directed graph.
(query: &str, nodes: &[KgNode], edges: &[KgEdge])
| 617 | |
| 618 | /// Generate a self-contained HTML page with a D3.js force-directed graph. |
| 619 | fn emit_html(query: &str, nodes: &[KgNode], edges: &[KgEdge]) -> String { |
| 620 | // Build JSON data for embedding |
| 621 | let graph_nodes: Vec<serde_json::Value> = nodes |
| 622 | .iter() |
| 623 | .map(|n| { |
| 624 | let content_matches = n |
| 625 | .metadata |
| 626 | .as_ref() |
| 627 | .and_then(|m| m.get("content_matches")) |
| 628 | .and_then(|v| v.as_u64()) |
| 629 | .unwrap_or(0); |
| 630 | let entity_kind = n |
| 631 | .metadata |
| 632 | .as_ref() |
| 633 | .and_then(|m| m.get("kind")) |
| 634 | .and_then(|v| v.as_str()) |
| 635 | .unwrap_or(""); |
| 636 | let line = n |
| 637 | .metadata |
| 638 | .as_ref() |
| 639 | .and_then(|m| m.get("line")) |
| 640 | .and_then(|v| v.as_u64()) |
| 641 | .unwrap_or(0); |
| 642 | let end_line = n |
| 643 | .metadata |
| 644 | .as_ref() |
| 645 | .and_then(|m| m.get("end_line")) |
| 646 | .and_then(|v| v.as_u64()) |
| 647 | .unwrap_or(0); |
| 648 | serde_json::json!({ |
| 649 | "id": n.id, |
| 650 | "kind": n.kind, |
| 651 | "label": n.label, |
| 652 | "summary": n.summary.as_deref().unwrap_or(""), |
| 653 | "content_matches": content_matches, |
| 654 | "entity_kind": entity_kind, |
| 655 | "line": line, |
| 656 | "end_line": end_line, |
| 657 | }) |
| 658 | }) |
| 659 | .collect(); |
| 660 | |
| 661 | let graph_edges: Vec<serde_json::Value> = edges |
| 662 | .iter() |
| 663 | .map(|e| { |
| 664 | serde_json::json!({ |
| 665 | "source": e.from_id, |
| 666 | "target": e.to_id, |
| 667 | "kind": e.kind, |
| 668 | }) |
| 669 | }) |
| 670 | .collect(); |
| 671 | |
| 672 | let graph_data = serde_json::json!({ |
| 673 | "nodes": graph_nodes, |
| 674 | "links": graph_edges, |
| 675 | }); |
| 676 |