diff --git a/WebApp/Components/Features/Events/CareerMapping.razor b/WebApp/Components/Features/Events/CareerMapping.razor index 41cbd86..e0f2157 100644 --- a/WebApp/Components/Features/Events/CareerMapping.razor +++ b/WebApp/Components/Features/Events/CareerMapping.razor @@ -1,25 +1,25 @@ @page "/events/career-mapping" @attribute [Authorize] @using Microsoft.EntityFrameworkCore -@using WebApp.Components.Shared.Components @using Core.Utility -@using Core.Entities +@using VisNetwork.Blazor.Models +@using Edge = VisNetwork.Blazor.Models.Edge @inject AppDbContext Context + BackButtonUrl="/events"/> @if (_isLoading) { - + Loading career mapping data... } -else if (string.IsNullOrWhiteSpace(_mermaidDefinition)) +else if (_networkData == null || !_networkData.Nodes.Any()) { No Career Field Mappings Found @@ -33,18 +33,18 @@ else Event-Career Field Relationships - This diagram shows the connections between events and their related career fields. - Events are shown on the left, career fields on the right. Career fields are clusters of related careers. + This diagram shows the connections between events and their related career fields. + Events are shown in blue, career fields in green. Career fields are clusters of related careers. + Use mouse to zoom and pan the graph. -
- +
+
- @_mermaidDefinition } @code { - private string _mermaidDefinition = string.Empty; + private NetworkData? _networkData; private bool _isLoading = true; protected override async Task OnInitializedAsync() @@ -55,7 +55,7 @@ else private async Task LoadDataAsync() { _isLoading = true; - + try { var events = await Context.Events @@ -71,7 +71,7 @@ else if (eventsWithFields.Any()) { - _mermaidDefinition = GenerateMermaidDiagram(eventsWithFields); + _networkData = GenerateNetworkData(eventsWithFields); } } finally @@ -80,16 +80,16 @@ else } } - private string GenerateMermaidDiagram(List events) + private NetworkData GenerateNetworkData(List events) { - var builder = new System.Text.StringBuilder(); - builder.AppendLine("graph LR"); + var nodes = new List(); + var edges = new List(); - // Dictionary to track node IDs and labels (to avoid duplicates) - var eventNodeIds = new Dictionary(); - var fieldNodeIds = new Dictionary(); - var fieldCounter = 1; + // Dictionary to track node IDs (to avoid duplicates) + var eventNodeIds = new Dictionary(); + var fieldNodeIds = new Dictionary(); var eventCounter = 1; + var fieldCounter = 1; // Dictionary to track which events connect to which career fields var eventToFields = new Dictionary>(); @@ -100,8 +100,17 @@ else if (!eventNodeIds.ContainsKey(evt.Id)) { var eventNodeId = $"E{eventCounter++}"; - var eventLabel = EscapeMermaidLabel(evt.Name); - eventNodeIds[evt.Id] = (eventNodeId, eventLabel); + eventNodeIds[evt.Id] = eventNodeId; + + // Add event node (blue) + nodes.Add(new Node + { + Id = eventNodeId, + Label = evt.Name, + Color = new NodeColorType { Background = "#90C3F5", Border = "#7DB3F0" }, + Shape = "box", + Size = 25 + }); } // Get related career fields for this event's careers @@ -110,65 +119,70 @@ else { eventToFields[evt.Id] = new HashSet(relatedFields); - // Track career field nodes + // Track and add career field nodes foreach (var field in relatedFields) { if (!fieldNodeIds.ContainsKey(field.Id)) { var fieldNodeId = $"F{fieldCounter++}"; - var fieldLabel = EscapeMermaidLabel(field.Name); - fieldNodeIds[field.Id] = (fieldNodeId, fieldLabel); + fieldNodeIds[field.Id] = fieldNodeId; + + // Add career field node (green) + nodes.Add(new Node + { + Id = fieldNodeId, + Label = field.Name, + Color = new NodeColorType + { + Background = "#90F8B0", + Border = "#80E8A0", + Highlight = new NodeColorType.BorderBackgroundColor { Background = "#90F8B0", Border = "#80E8A0" } + }, + Shape = "box", + Size = 20 + }); } } } } - // Second pass: define all event nodes (blue) - foreach (var (id, (nodeId, label)) in eventNodeIds.OrderBy(e => e.Value.Label)) - { - builder.AppendLine($" {nodeId}[\"{label}\"]:::eventNode"); - } - - // Third pass: define all career field nodes (green) - foreach (var (id, (nodeId, label)) in fieldNodeIds.OrderBy(f => f.Value.Label)) - { - builder.AppendLine($" {nodeId}[\"{label}\"]:::fieldNode"); - } - - // Fourth pass: define all edges (events on left, fields on right) + // Second pass: create edges from events to career fields foreach (var evt in events.OrderBy(e => e.Name)) { if (eventToFields.TryGetValue(evt.Id, out var fields)) { - var currentEventNodeId = eventNodeIds[evt.Id].Id; + var eventNodeId = eventNodeIds[evt.Id]; foreach (var field in fields.OrderBy(f => f.Name)) { - var currentFieldNodeId = fieldNodeIds[field.Id].Id; - builder.AppendLine($" {currentEventNodeId} --> {currentFieldNodeId}"); + var fieldNodeId = fieldNodeIds[field.Id]; + edges.Add(new Edge + { + From = eventNodeId, + To = fieldNodeId + }); } } } - // Add styling for different colors - builder.AppendLine(""); - builder.AppendLine("classDef eventNode fill:#4A90E2,stroke:#2E5C8A,stroke-width:2px,color:#fff"); - builder.AppendLine("classDef fieldNode fill:#50C878,stroke:#2D8659,stroke-width:2px,color:#fff"); - - return builder.ToString(); + return new NetworkData + { + Nodes = nodes, + Edges = edges + }; } - private string EscapeMermaidLabel(string label) + private NetworkOptions GetNetworkOptions(Network network) { - if (string.IsNullOrEmpty(label)) - return string.Empty; - - // Escape quotes and other special characters for Mermaid labels - return label - .Replace("\"", """) - .Replace("\n", " ") - .Replace("\r", " ") - .Trim(); + return new NetworkOptions + { + AutoResize = true, + Physics = new PhysicsOptions + { + Enabled = true + }, + Height = "600px" + }; } -} +} \ No newline at end of file diff --git a/WebApp/Components/_Imports.razor b/WebApp/Components/_Imports.razor index 0ad4ad2..39e4dc2 100644 --- a/WebApp/Components/_Imports.razor +++ b/WebApp/Components/_Imports.razor @@ -26,4 +26,4 @@ @using MudBlazor @using Core.Entities @using Data -@using Blazorade.Mermaid.Components +@using VisNetwork.Blazor diff --git a/WebApp/Program.cs b/WebApp/Program.cs index 7fbd1bf..7820822 100644 --- a/WebApp/Program.cs +++ b/WebApp/Program.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore; using MudBlazor.Services; using Serilog; using System.Text.Json; +using VisNetwork.Blazor; using WebApp; using WebApp.Authentication; using WebApp.Components; @@ -118,6 +119,7 @@ builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); builder.Services.AddMudServices(); +builder.Services.AddVisNetwork(); // Configure Data Protection to persist keys across container restarts var keysPath = Path.Combine(builder.Environment.ContentRootPath, "DataProtectionKeys"); diff --git a/WebApp/WebApp.csproj b/WebApp/WebApp.csproj index 6a8184c..d7933df 100644 --- a/WebApp/WebApp.csproj +++ b/WebApp/WebApp.csproj @@ -14,8 +14,8 @@ - +