@inject IEntityDialogService entityDialogService @inject NavigationManager NavigationManager
@foreach (var edge in Graph.Edges) { var source = Graph.Nodes.FirstOrDefault(n => n.Id == edge.SourceId); var target = Graph.Nodes.FirstOrDefault(n => n.Id == edge.TargetId); if (source != null && target != null) { var color = EdgeColor(edge.EdgeType); var strokeWidth = edge.EdgeType == TechTreeEdgeType.Produces ? "2.5" : "1.5"; var strokeDash = edge.EdgeType == TechTreeEdgeType.RequiresProduction || edge.EdgeType == TechTreeEdgeType.RequiresResearch ? "6,3" : ""; strokeDash = edge.EdgeType == TechTreeEdgeType.Morph ? "2,2" : strokeDash; } } @foreach (var edgeType in Graph.Edges.Select(e => e.EdgeType).Distinct()) { } @foreach (var node in Graph.Nodes) { var color = NodeColor(node.Descriptive); var isHighlighted = HighlightEntityId == node.Id; @TruncateText(node.Name, 20) @node.Name (@node.EntityType) }
@code { [Parameter] public TechTreeGraphModel Graph { get; set; } = new(); [Parameter] public string? HighlightEntityId { get; set; } [Parameter] public EventCallback OnEntitySelected { get; set; } private ElementReference containerRef; private const float nodeWidth = 140; private const float nodeHeight = 36; private const float horizontalSpacing = 60; private const float verticalSpacing = 30; private const float topPadding = 40; private const float leftPadding = 40; private float svgWidth = 800; private float svgHeight = 600; protected override void OnParametersSet() { ComputeLayout(); } void ComputeLayout() { if (Graph.Nodes.Count == 0) return; var layers = Graph.Nodes.GroupBy(n => n.Layer) .OrderBy(g => g.Key) .ToList(); // Simple layered layout: left to right (layers are columns) for (var layerIdx = 0; layerIdx < layers.Count; layerIdx++) { var layerNodes = layers.ElementAt(layerIdx).OrderBy(n => n.Name).ToList(); var layerHeight = layerNodes.Count * (nodeHeight + verticalSpacing) - verticalSpacing; for (var nodeIdx = 0; nodeIdx < layerNodes.Count; nodeIdx++) { var node = layerNodes[nodeIdx]; node.X = leftPadding + layerIdx * (nodeWidth + horizontalSpacing); node.Y = topPadding + nodeIdx * (nodeHeight + verticalSpacing); } } svgWidth = layers.Count * (nodeWidth + horizontalSpacing) + leftPadding + 100; svgHeight = layers.Max(g => g.Count()) * (nodeHeight + verticalSpacing) + topPadding + 80; if (svgWidth < 800) svgWidth = 800; if (svgHeight < 400) svgHeight = 400; } void OnNodeClick(string entityId) { entityDialogService.AddDialog(entityId); OnEntitySelected.InvokeAsync(entityId); } string NodeColor(string descriptive) { return descriptive.ToLowerInvariant() switch { "frontliner" => "#ff6b6b", "skirmisher" => "#ffa502", "support" => "#2ed573", "generalist" => "#1e90ff", "worker" => "#ffd43b", "stronghold" => "#ff4757", "upgrade" => "#a55eea", "technology" => "#a55eea", _ => "#8fc5ff" }; } string EdgeColor(string edgeType) { return edgeType switch { "Produces" => "#2ed573", "RequiresProduction" => "#ffa502", "RequiresResearch" => "#a55eea", "Morph" => "#ff6b6b", "Upgrades" => "#1e90ff", _ => "#666" }; } string TruncateText(string text, int maxLength) { return text.Length <= maxLength ? text : text.Substring(0, maxLength - 3) + "..."; } }