Enhance CareerMapping component to reflect career fields instead of careers
Updated the CareerMapping component to improve clarity by changing terminology from "careers" to "career fields" throughout the UI. Enhanced the diagram generation logic to filter events based on related career fields, ensuring accurate representation of relationships. Added styling for event and field nodes in the Mermaid diagram for better visual distinction.
This commit is contained in:
@@ -0,0 +1,48 @@
|
|||||||
|
namespace Core.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a career field cluster that groups related careers together.
|
||||||
|
/// </summary>
|
||||||
|
public class CareerField
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Unique identifier for the career field (1-25)
|
||||||
|
/// </summary>
|
||||||
|
public int Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display name of the career field
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exact career names that belong to this field
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<string> DirectCareerMatches { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Keywords for pattern matching (case-insensitive substring matching)
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<string> PatternKeywords { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new CareerField instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Unique identifier</param>
|
||||||
|
/// <param name="name">Display name</param>
|
||||||
|
/// <param name="directCareerMatches">Exact career name matches</param>
|
||||||
|
/// <param name="patternKeywords">Keywords for pattern matching</param>
|
||||||
|
public CareerField(int id, string name, IReadOnlyList<string> directCareerMatches, IReadOnlyList<string> patternKeywords)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Name = name;
|
||||||
|
DirectCareerMatches = directCareerMatches ?? Array.Empty<string>();
|
||||||
|
PatternKeywords = patternKeywords ?? Array.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
using Core.Entities;
|
||||||
|
|
||||||
|
namespace Core.Utility;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Static class that defines all 25 career field clusters and provides methods to match careers to fields.
|
||||||
|
/// </summary>
|
||||||
|
public static class CareerFieldDefinitions
|
||||||
|
{
|
||||||
|
private static readonly Lazy<IReadOnlyList<CareerField>> _allFields = new(() => CreateAllCareerFields());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all 25 career field definitions
|
||||||
|
/// </summary>
|
||||||
|
public static IReadOnlyList<CareerField> GetAllCareerFields() => _allFields.Value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the career fields that are related to the given careers.
|
||||||
|
/// Uses both direct name matching and pattern-based keyword matching.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="careers">The careers to match against</param>
|
||||||
|
/// <returns>Distinct list of related CareerFields</returns>
|
||||||
|
public static IReadOnlyList<CareerField> GetRelatedCareerFields(IEnumerable<Career> careers)
|
||||||
|
{
|
||||||
|
if (careers == null)
|
||||||
|
return Array.Empty<CareerField>();
|
||||||
|
|
||||||
|
var careerNames = careers
|
||||||
|
.Where(c => !string.IsNullOrWhiteSpace(c.Name))
|
||||||
|
.Select(c => c.Name.Trim())
|
||||||
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (!careerNames.Any())
|
||||||
|
return Array.Empty<CareerField>();
|
||||||
|
|
||||||
|
var matchingFields = new HashSet<CareerField>();
|
||||||
|
var allFields = GetAllCareerFields();
|
||||||
|
|
||||||
|
foreach (var field in allFields)
|
||||||
|
{
|
||||||
|
bool matches = false;
|
||||||
|
|
||||||
|
// Check direct matches
|
||||||
|
foreach (var directMatch in field.DirectCareerMatches)
|
||||||
|
{
|
||||||
|
if (careerNames.Contains(directMatch))
|
||||||
|
{
|
||||||
|
matches = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check pattern matches if no direct match found
|
||||||
|
if (!matches)
|
||||||
|
{
|
||||||
|
foreach (var keyword in field.PatternKeywords)
|
||||||
|
{
|
||||||
|
if (careerNames.Any(name => name.Contains(keyword, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
matches = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matches)
|
||||||
|
{
|
||||||
|
matchingFields.Add(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchingFields.OrderBy(f => f.Id).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<CareerField> CreateAllCareerFields()
|
||||||
|
{
|
||||||
|
return new List<CareerField>
|
||||||
|
{
|
||||||
|
// 1. Aerospace & Automotive Engineering
|
||||||
|
new CareerField(
|
||||||
|
1,
|
||||||
|
"Aerospace & Automotive Engineering",
|
||||||
|
new[] { "Aeronautical engineer", "Aircraft systems engineer", "Automobile designer", "Automotive designer", "Automotive modeler", "Race car engineer" },
|
||||||
|
new[] { "aeronautical", "aircraft", "automobile", "automotive", "race car" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 2. Mechanical & Robotics Engineering
|
||||||
|
new CareerField(
|
||||||
|
2,
|
||||||
|
"Mechanical & Robotics Engineering",
|
||||||
|
new[] { "Machine designer", "Mechanical drafter", "Mechanical engineer", "Robotics engineer" },
|
||||||
|
new[] { "mechanical", "robotics", "machine" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 3. Electrical & Electronics Engineering
|
||||||
|
new CareerField(
|
||||||
|
3,
|
||||||
|
"Electrical & Electronics Engineering",
|
||||||
|
new[] { "Electrical engineer", "Electrical technician", "Electrician", "Electromechanical engineer", "Electronic analyst", "Electronic designer" },
|
||||||
|
new[] { "electrical", "electronic", "electrician" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 4. Civil & Structural Engineering
|
||||||
|
new CareerField(
|
||||||
|
4,
|
||||||
|
"Civil & Structural Engineering",
|
||||||
|
new[] { "Civil engineer", "Construction analyst", "Construction manager", "General contractor", "Structural engineer", "Structural iron and steel work technician" },
|
||||||
|
new[] { "civil", "construction", "structural", "contractor" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 5. Environmental & Energy Engineering
|
||||||
|
new CareerField(
|
||||||
|
5,
|
||||||
|
"Environmental & Energy Engineering",
|
||||||
|
new[] { "Chemical engineer", "Energy efficiency technician", "Environmental engineer", "Solar engineer", "Solar panel installer", "Solar sales consultant" },
|
||||||
|
new[] { "chemical", "energy", "environmental", "solar" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 6. General Engineering & Quality
|
||||||
|
new CareerField(
|
||||||
|
6,
|
||||||
|
"General Engineering & Quality",
|
||||||
|
new[] { "Engineer", "Engineering manager", "Engineering technician", "Quality assurance engineer", "Quality engineer", "Standards engineer" },
|
||||||
|
new[] { "engineer", "quality", "standards" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 7. Architecture & Urban Planning
|
||||||
|
new CareerField(
|
||||||
|
7,
|
||||||
|
"Architecture & Urban Planning",
|
||||||
|
new[] { "Architect", "Community planner", "Interior designer", "Urban and regional planner" },
|
||||||
|
new[] { "architect", "planner", "interior design", "urban" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 8. Software Development
|
||||||
|
new CareerField(
|
||||||
|
8,
|
||||||
|
"Software Development",
|
||||||
|
new[] { "Computer programmer", "Computer software engineer", "Programming & software development", "Software designer", "Software engineer" },
|
||||||
|
new[] { "programming", "programmer", "software", "developer" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 9. IT & Networking
|
||||||
|
new CareerField(
|
||||||
|
9,
|
||||||
|
"IT & Networking",
|
||||||
|
new[] { "Computer engineer", "Computer network specialist", "Computer technician", "Information support & services", "Network systems", "Technical support specialist", "Telecommunications manager" },
|
||||||
|
new[] { "network", "computer", "technical support", "telecommunications", "IT" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 10. Cybersecurity & Digital Forensics
|
||||||
|
new CareerField(
|
||||||
|
10,
|
||||||
|
"Cybersecurity & Digital Forensics",
|
||||||
|
new[] { "Cryptographer", "Cyber Crime Investigator", "Cyber defense incident responder", "Cyber forensics expert", "Cyber legal advisor", "Cyber operator", "Cybersecurity engineer", "Vulnerability assessor" },
|
||||||
|
new[] { "cyber", "security", "forensics", "cryptography", "vulnerability" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 11. Data Science & Analytics
|
||||||
|
new CareerField(
|
||||||
|
11,
|
||||||
|
"Data Science & Analytics",
|
||||||
|
new[] { "Actuary", "Data analyst", "Data scientist", "Economist", "Mathematician", "Operations research analyst" },
|
||||||
|
new[] { "data", "analyst", "actuary", "economist", "mathematician", "research" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 12. CAD, CNC & Manufacturing
|
||||||
|
new CareerField(
|
||||||
|
12,
|
||||||
|
"CAD, CNC & Manufacturing",
|
||||||
|
new[] { "CAD professional", "CNC programmer", "Manufacturing", "Production planner" },
|
||||||
|
new[] { "CAD", "CNC", "manufacturing", "production" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 13. Industrial & Product Design
|
||||||
|
new CareerField(
|
||||||
|
13,
|
||||||
|
"Industrial & Product Design",
|
||||||
|
new[] { "Appraiser", "Commercial and industrial design", "Designer", "Industrial designer", "Product designer" },
|
||||||
|
new[] { "designer", "design", "industrial", "product", "appraiser" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 14. Visual Arts & Animation
|
||||||
|
new CareerField(
|
||||||
|
14,
|
||||||
|
"Visual Arts & Animation",
|
||||||
|
new[] { "Animator", "Artist", "Computer animator", "Graphic artist", "Illustrator", "Multimedia designer" },
|
||||||
|
new[] { "animator", "artist", "graphic", "illustrator", "multimedia" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 15. Game Design & Interactive Media
|
||||||
|
new CareerField(
|
||||||
|
15,
|
||||||
|
"Game Design & Interactive Media",
|
||||||
|
new[] { "Game designer", "Game Play Tester", "Professional Gamer" },
|
||||||
|
new[] { "game", "gamer", "gaming" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 16. Audio & Music Production
|
||||||
|
new CareerField(
|
||||||
|
16,
|
||||||
|
"Audio & Music Production",
|
||||||
|
new[] { "Audio designer or engineer", "Audio Engineer", "Audio operator or technician", "Broadcast technician", "Music composer" },
|
||||||
|
new[] { "audio", "music", "broadcast", "sound" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 17. Video & Film Production
|
||||||
|
new CareerField(
|
||||||
|
17,
|
||||||
|
"Video & Film Production",
|
||||||
|
new[] { "Audiovisual technician", "Director", "Entertainment/television broadcaster", "Videographer" },
|
||||||
|
new[] { "video", "film", "director", "television", "broadcast", "videographer" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 18. Web & Digital Communications
|
||||||
|
new CareerField(
|
||||||
|
18,
|
||||||
|
"Web & Digital Communications",
|
||||||
|
new[] { "Instructional technologist", "Web & digital communications", "Webmaster", "Website designer" },
|
||||||
|
new[] { "web", "website", "digital", "communications", "webmaster" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 19. Writing & Publishing
|
||||||
|
new CareerField(
|
||||||
|
19,
|
||||||
|
"Writing & Publishing",
|
||||||
|
new[] { "Ad copy writer", "Editor", "Publisher", "Screenplay writer", "Speech writer", "Technical writer", "Writer" },
|
||||||
|
new[] { "writing", "writer", "editor", "publisher", "copy" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 20. Journalism & Public Relations
|
||||||
|
new CareerField(
|
||||||
|
20,
|
||||||
|
"Journalism & Public Relations",
|
||||||
|
new[] { "Internal communications manager", "Motivational speaker", "Photojournalist", "Reporter" },
|
||||||
|
new[] { "journalism", "reporter", "photojournalist", "communications", "speaker" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 21. Forensics & Criminal Investigation
|
||||||
|
new CareerField(
|
||||||
|
21,
|
||||||
|
"Forensics & Criminal Investigation",
|
||||||
|
new[] { "Crime scene investigator", "Detective", "Forensic accountant", "Forensic anthropologist", "Forensic engineering scientist", "Forensic pathologist" },
|
||||||
|
new[] { "forensic", "detective", "investigator", "crime" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 22. Healthcare & Medical Technology
|
||||||
|
new CareerField(
|
||||||
|
22,
|
||||||
|
"Healthcare & Medical Technology",
|
||||||
|
new[] { "Dietitian", "Doctor", "Epidemiologist", "Medical technologist", "Nurse", "Pharmacist", "Prosthetics practitioner" },
|
||||||
|
new[] { "medical", "health", "doctor", "nurse", "pharmacist", "dietitian", "epidemiology" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 23. Science & Research
|
||||||
|
new CareerField(
|
||||||
|
23,
|
||||||
|
"Science & Research",
|
||||||
|
new[] { "Botanist", "Food scientist", "Meteorologist", "Molecular biologist", "Physics instructor", "Plant geneticist", "Research and development scientist", "Research assistant", "Researcher" },
|
||||||
|
new[] { "scientist", "research", "biology", "physics", "botanist", "meteorologist", "geneticist" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 24. Education & Training
|
||||||
|
new CareerField(
|
||||||
|
24,
|
||||||
|
"Education & Training",
|
||||||
|
new[] { "Educator", "Teacher/trainer", "Technology education instructor" },
|
||||||
|
new[] { "educator", "teacher", "trainer", "education", "instructor" }
|
||||||
|
),
|
||||||
|
|
||||||
|
// 25. Business, Legal & Government
|
||||||
|
new CareerField(
|
||||||
|
25,
|
||||||
|
"Business, Legal & Government",
|
||||||
|
new[] { "Creative consultant", "Entrepreneur", "Government Official", "Lawyer", "Legal Aide", "Lobbyist", "Management executive", "Market researcher", "Marketing strategist", "Parliamentarian", "Politician", "Project manager", "Public affairs specialist", "Public policy specialist", "Recording Clerk", "Small business owner", "Volunteer manager" },
|
||||||
|
new[] { "business", "legal", "lawyer", "government", "politician", "manager", "marketing", "consultant", "entrepreneur" }
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
@using WebApp.Components.Shared.Components
|
@using WebApp.Components.Shared.Components
|
||||||
|
@using Core.Utility
|
||||||
|
@using Core.Entities
|
||||||
@inject AppDbContext Context
|
@inject AppDbContext Context
|
||||||
|
|
||||||
<PageHeader
|
<PageHeader
|
||||||
@@ -20,23 +22,24 @@
|
|||||||
else if (string.IsNullOrWhiteSpace(_mermaidDefinition))
|
else if (string.IsNullOrWhiteSpace(_mermaidDefinition))
|
||||||
{
|
{
|
||||||
<MudPaper Elevation="2" Class="pa-6">
|
<MudPaper Elevation="2" Class="pa-6">
|
||||||
<MudText Typo="Typo.h6" Class="mb-4">No Career Mappings Found</MudText>
|
<MudText Typo="Typo.h6" Class="mb-4">No Career Field Mappings Found</MudText>
|
||||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||||
No events have related careers assigned. Edit events to add related careers.
|
No events have related careers assigned that match any career fields. Edit events to add related careers.
|
||||||
</MudText>
|
</MudText>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<MudPaper Elevation="2" Class="pa-6">
|
<MudPaper Elevation="2" Class="pa-6">
|
||||||
<MudText Typo="Typo.h6" Class="mb-4">Event-Career Relationships</MudText>
|
<MudText Typo="Typo.h6" Class="mb-4">Event-Career Field Relationships</MudText>
|
||||||
<MudText Typo="Typo.body2" Class="mb-4 mud-text-secondary">
|
<MudText Typo="Typo.body2" Class="mb-4 mud-text-secondary">
|
||||||
This diagram shows the connections between events and their related careers.
|
This diagram shows the connections between events and their related career fields.
|
||||||
Events are shown on the left, careers on the right.
|
Events are shown on the left, career fields on the right. Career fields are clusters of related careers.
|
||||||
</MudText>
|
</MudText>
|
||||||
<div class="mermaid-diagram-container" style="overflow-x: auto; min-height: 400px;">
|
<div class="mermaid-diagram-container" style="overflow-x: auto; min-height: 400px;">
|
||||||
<MermaidDiagram Definition="@_mermaidDefinition" />
|
<MermaidDiagram Definition="@_mermaidDefinition" />
|
||||||
</div>
|
</div>
|
||||||
|
<MudText Style="white-space:pre-wrap;">@_mermaidDefinition</MudText>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,9 +64,14 @@ else
|
|||||||
.OrderBy(e => e.Name)
|
.OrderBy(e => e.Name)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
if (events.Any())
|
// Filter to only events that have career fields (after matching)
|
||||||
|
var eventsWithFields = events
|
||||||
|
.Where(e => CareerFieldDefinitions.GetRelatedCareerFields(e.RelatedCareers).Any())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (eventsWithFields.Any())
|
||||||
{
|
{
|
||||||
_mermaidDefinition = GenerateMermaidDiagram(events);
|
_mermaidDefinition = GenerateMermaidDiagram(eventsWithFields);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -79,12 +87,15 @@ else
|
|||||||
|
|
||||||
// Dictionary to track node IDs and labels (to avoid duplicates)
|
// Dictionary to track node IDs and labels (to avoid duplicates)
|
||||||
var eventNodeIds = new Dictionary<int, (string Id, string Label)>();
|
var eventNodeIds = new Dictionary<int, (string Id, string Label)>();
|
||||||
var careerNodeIds = new Dictionary<int, (string Id, string Label)>();
|
var fieldNodeIds = new Dictionary<int, (string Id, string Label)>();
|
||||||
var careerCounter = 1;
|
var fieldCounter = 1;
|
||||||
var eventCounter = 1;
|
var eventCounter = 1;
|
||||||
|
|
||||||
// First pass: collect all unique nodes
|
// Dictionary to track which events connect to which career fields
|
||||||
foreach (var evt in events)
|
var eventToFields = new Dictionary<int, HashSet<CareerField>>();
|
||||||
|
|
||||||
|
// First pass: collect all unique nodes and determine relationships
|
||||||
|
foreach (var evt in events.OrderBy(e => e.Name))
|
||||||
{
|
{
|
||||||
if (!eventNodeIds.ContainsKey(evt.Id))
|
if (!eventNodeIds.ContainsKey(evt.Id))
|
||||||
{
|
{
|
||||||
@@ -93,40 +104,57 @@ else
|
|||||||
eventNodeIds[evt.Id] = (eventNodeId, eventLabel);
|
eventNodeIds[evt.Id] = (eventNodeId, eventLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var career in evt.RelatedCareers)
|
// Get related career fields for this event's careers
|
||||||
|
var relatedFields = CareerFieldDefinitions.GetRelatedCareerFields(evt.RelatedCareers);
|
||||||
|
if (relatedFields.Any())
|
||||||
{
|
{
|
||||||
if (!careerNodeIds.ContainsKey(career.Id))
|
eventToFields[evt.Id] = new HashSet<CareerField>(relatedFields);
|
||||||
|
|
||||||
|
// Track career field nodes
|
||||||
|
foreach (var field in relatedFields)
|
||||||
{
|
{
|
||||||
var careerNodeId = $"C{careerCounter++}";
|
if (!fieldNodeIds.ContainsKey(field.Id))
|
||||||
var careerLabel = EscapeMermaidLabel(career.Name);
|
{
|
||||||
careerNodeIds[career.Id] = (careerNodeId, careerLabel);
|
var fieldNodeId = $"F{fieldCounter++}";
|
||||||
|
var fieldLabel = EscapeMermaidLabel(field.Name);
|
||||||
|
fieldNodeIds[field.Id] = (fieldNodeId, fieldLabel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second pass: define all nodes
|
// Second pass: define all event nodes (blue)
|
||||||
foreach (var (id, (nodeId, label)) in eventNodeIds.OrderBy(e => e.Value.Label))
|
foreach (var (id, (nodeId, label)) in eventNodeIds.OrderBy(e => e.Value.Label))
|
||||||
{
|
{
|
||||||
builder.AppendLine($" {nodeId}[\"{label}\"]");
|
builder.AppendLine($" {nodeId}[\"{label}\"]:::eventNode");
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var (id, (nodeId, label)) in careerNodeIds.OrderBy(c => c.Value.Label))
|
// Third pass: define all career field nodes (green)
|
||||||
|
foreach (var (id, (nodeId, label)) in fieldNodeIds.OrderBy(f => f.Value.Label))
|
||||||
{
|
{
|
||||||
builder.AppendLine($" {nodeId}[\"{label}\"]");
|
builder.AppendLine($" {nodeId}[\"{label}\"]:::fieldNode");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Third pass: define all edges
|
// Fourth pass: define all edges (events on left, fields on right)
|
||||||
foreach (var evt in events.OrderBy(e => e.Name))
|
foreach (var evt in events.OrderBy(e => e.Name))
|
||||||
{
|
{
|
||||||
var currentEventNodeId = eventNodeIds[evt.Id].Id;
|
if (eventToFields.TryGetValue(evt.Id, out var fields))
|
||||||
|
|
||||||
foreach (var career in evt.RelatedCareers.OrderBy(c => c.Name))
|
|
||||||
{
|
{
|
||||||
var currentCareerNodeId = careerNodeIds[career.Id].Id;
|
var currentEventNodeId = eventNodeIds[evt.Id].Id;
|
||||||
builder.AppendLine($" {currentEventNodeId} --> {currentCareerNodeId}");
|
|
||||||
|
foreach (var field in fields.OrderBy(f => f.Name))
|
||||||
|
{
|
||||||
|
var currentFieldNodeId = fieldNodeIds[field.Id].Id;
|
||||||
|
builder.AppendLine($" {currentEventNodeId} --> {currentFieldNodeId}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user