From ebb5eaa671745526beeccfca717b0f26782c5086 Mon Sep 17 00:00:00 2001 From: Migle Markeviciute Date: Fri, 2 Feb 2024 16:25:33 +0000 Subject: [PATCH 1/2] SVG renderer work in progress --- OmopTransformer/CDS/Parser/BirthDetails.cs | 2 - .../Documentation/Charting/SvgRenderer.cs | 99 +++++++++++-------- 2 files changed, 56 insertions(+), 45 deletions(-) diff --git a/OmopTransformer/CDS/Parser/BirthDetails.cs b/OmopTransformer/CDS/Parser/BirthDetails.cs index 870f9bb..28050fd 100644 --- a/OmopTransformer/CDS/Parser/BirthDetails.cs +++ b/OmopTransformer/CDS/Parser/BirthDetails.cs @@ -6,7 +6,6 @@ private BirthDetails() { } - public string? BirthOrder { get; set; } public string? DeliveryMethod { get; set; } public string? GestationLengthAssessment { get; set; } @@ -75,7 +74,6 @@ private BirthDetails() birthDetails.PersonGenderCurrent = text.SubstringOrNull(index, 1); index += 1; birthDetails.OverseasVisitorStatusClassificationAtCDSActivityDate = text.SubstringOrNull(index, 1); - index += 1; return birthDetails; } diff --git a/OmopTransformer/Documentation/Charting/SvgRenderer.cs b/OmopTransformer/Documentation/Charting/SvgRenderer.cs index 7d92bda..75d5fad 100644 --- a/OmopTransformer/Documentation/Charting/SvgRenderer.cs +++ b/OmopTransformer/Documentation/Charting/SvgRenderer.cs @@ -1,4 +1,5 @@ -using System.Xml.Linq; +using System.Collections.Generic; +using System.Xml.Linq; namespace OmopTransformer.Documentation.Charting; @@ -19,35 +20,49 @@ public SvgRenderer(IReadOnlyCollection sourceBoxes, IReadOnlyCollection FilteredSourceBoxes => FilerBoxes(_sourceBoxes, relationship => relationship.Source); - private IReadOnlyCollection FilteredTargetBoxes => FilerBoxes(_targetBoxes, relationship => relationship.Target); + private IReadOnlyCollection FilteredSourceBoxes => FilterBoxes(_sourceBoxes, relationship => relationship.Source); + private IReadOnlyCollection FilteredTargetBoxes => FilterBoxes(_targetBoxes, relationship => relationship.Target); - private IReadOnlyCollection FilerBoxes(IEnumerable boxes, Func filter) => + private IReadOnlyCollection FilterBoxes(IEnumerable boxes, Func filter) => boxes .Where( - box => + box => _relationships .Any(relationship => filter(relationship) == box.Name)) .ToList(); + static Dictionary> GroupSourcesByTarget(List sources, List relationships) + { + // Group relationships by target + var groupedSources = relationships + .GroupBy(r => r.Target) + .ToDictionary( + group => group.Key, + group => group.Join(sources, r => r.Source, s => s.Name, (r, s) => s).ToList() + ); + + return groupedSources; + } + public string Render() { - var height = GetBoxY(Math.Max(FilteredSourceBoxes.Count, FilteredTargetBoxes.Count)) + BoxHeight; + var width = GetBoxX(Math.Max(FilteredSourceBoxes.Count, FilteredTargetBoxes.Count)) + BoxWidth; XElement svg = + new XElement( + SvgNamespace + "svg", + new XAttribute("xmlns", SvgNamespace), + new XAttribute("width", width), + new XAttribute("height", "1500"), new XElement( - SvgNamespace + "svg", - new XAttribute("xmlns", SvgNamespace), - new XAttribute("width", "1500"), - new XAttribute("height", height), - new XElement( - SvgNamespace + "rect", - new XAttribute("width", "100%"), - new XAttribute("height", "100%"), - new XAttribute("fill", "white")), - DrawBoxes(FilteredSourceBoxes, 10, "#fab10c", "black"), - DrawBoxes(FilteredTargetBoxes, 400, "#3c4459", "white"), - DrawLines()); + SvgNamespace + "rect", + new XAttribute("width", "100%"), + new XAttribute("height", "100%"), + new XAttribute("fill", "#005EB8")), + DrawBoxes(FilteredSourceBoxes, 10, "#005EB8", "white"), + DrawBoxes(FilteredTargetBoxes, 400, "white", "#005EB8"), + DrawLines() + ); return svg.ToString(); } @@ -65,7 +80,7 @@ private static string GetLineColour(int index) }; } - private IEnumerable DrawLines() => + private IEnumerable DrawLines() => _relationships .Select(DrawLine); @@ -78,19 +93,17 @@ private static int GetBoxIndex(IEnumerable boxes, string name) => private XElement DrawLine(Relationship relationship, int index) { int sourceIndex = GetBoxIndex(FilteredSourceBoxes, relationship.Source); - int targetIndex = GetBoxIndex(FilteredTargetBoxes, relationship.Target); - - int box1Y = GetBoxY(sourceIndex); - int box2X = 400; + int box1X = GetBoxX(sourceIndex); + int box2Y = 70; - int arrowStartX = BoxWidth + 10; - int arrowStartY = box1Y + BoxHeight / 2; + int arrowStartX = box1X + BoxWidth / 2; + int arrowStartY = BoxHeight + 10; - int arrowEndX = box2X; - int arrowEndY = GetBoxY(targetIndex) + BoxHeight / 2; + int arrowEndX = box1X + BoxWidth / 2; + int arrowEndY = box2Y + 330; - return + return new XElement( SvgNamespace + "g", new XElement( @@ -99,27 +112,27 @@ private XElement DrawLine(Relationship relationship, int index) new XAttribute("y1", arrowStartY), new XAttribute("x2", arrowEndX), new XAttribute("y2", arrowEndY), - new XAttribute("stroke", GetLineColour(index)), - new XAttribute("stroke-width", "2")), - new XElement( - SvgNamespace + "text", - new XAttribute("x", BoxWidth + 410), - new XAttribute("y", arrowEndY + 5), - new XAttribute("fill", "black"), - new XAttribute("font-family", "Helvetica"), - new XAttribute("text-anchor", "start"), - relationship.Label)); + new XAttribute("stroke", "white"), + new XAttribute("stroke-width", "2"))); + //new XElement( + // SvgNamespace + "text", + // new XAttribute("x", arrowEndX - 120), + // new XAttribute("y", BoxHeight + 420), + // new XAttribute("fill", "white"), + // new XAttribute("font-family", "Helvetica"), + // new XAttribute("text-anchor", "start"), + // relationship.Label)); } - private static int GetBoxY(int index) => index * 100 + 10; + private static int GetBoxX(int index) => index * 270 + 5; private static IEnumerable DrawBoxes(IEnumerable boxes, int x, string colour, string textColour) => boxes .Select( (box, index) => CreateBoxElement( - x: x, - y: GetBoxY(index), + x: GetBoxX(index), + y: x, width: BoxWidth, height: BoxHeight, label: box.Name, @@ -137,8 +150,8 @@ private static XElement CreateBoxElement(int x, int y, int width, int height, st new XAttribute("width", width), new XAttribute("height", height), new XAttribute("fill", fillColor), - new XAttribute("stroke", "black"), - new XAttribute("rx", 10) + new XAttribute("stroke", "white"), + new XAttribute("stroke-width", "2") ), new XElement(SvgNamespace + "text", new XAttribute("x", x + 10), From 20561971834962422a4a4d58ab90e9fd0e850b63 Mon Sep 17 00:00:00 2001 From: Migle Markeviciute Date: Mon, 5 Feb 2024 14:57:40 +0000 Subject: [PATCH 2/2] Update SVG renderer --- Documentation/CdsConditionOccurrence.svg | 131 ++++++---- Documentation/CdsLocation.svg | 46 ++-- Documentation/CdsPerson.svg | 244 +++++++++++------- Documentation/CdsStructuredLocation.svg | 234 +++++++++-------- Documentation/CosdLocation.svg | 219 +++++++++------- Documentation/CosdPerson.svg | 196 ++++++++------ Documentation/RtdsLocation.svg | 50 ++-- Documentation/RtdsPerson.svg | 152 ++++++----- Documentation/SactLocation.svg | 78 +++--- Documentation/SactPerson.svg | 174 ++++++++----- .../Documentation/Charting/SvgRenderer.cs | 212 ++++++++------- .../Documentation/DocumentationRenderer.cs | 12 +- 12 files changed, 1008 insertions(+), 740 deletions(-) diff --git a/Documentation/CdsConditionOccurrence.svg b/Documentation/CdsConditionOccurrence.svg index a9aef1b..8938df1 100644 --- a/Documentation/CdsConditionOccurrence.svg +++ b/Documentation/CdsConditionOccurrence.svg @@ -1,59 +1,76 @@ - - - - - DiagnosisCode - - - - DiagnosisId - - - - NHSNumber - - - - CDSActivityDate - - - - nhs_number - - - - cds_diagnosis_id - - - - condition_concept_id - - - - condition_start_date - - - - condition_source_value - - - - Copy value. - - - - Resolve ICD10 codes to OMOP concepts. If code cannot be mapped, map using the parent code. - - - - Converts text to dates. - - - - Copy value. - - - - Copy value. + + + + + NHSNumber + + + + + + Copy value. + + + + nhs_number + + + + DiagnosisCode + + + + + + Resolve ICD10 codes to OMOP + concepts. If code cannot be + mapped, map using the parent + code. + + + + condition_concept_id + + + + CDSActivityDate + + + + + + Converts text to dates. + + + + condition_start_date + + + + DiagnosisId + + + + + + Copy value. + + + + cds_diagnosis_id + + + + DiagnosisCode + + + + + + Copy value. + + + + condition_source_value \ No newline at end of file diff --git a/Documentation/CdsLocation.svg b/Documentation/CdsLocation.svg index e96c594..09e5a19 100644 --- a/Documentation/CdsLocation.svg +++ b/Documentation/CdsLocation.svg @@ -1,23 +1,33 @@ - - - - - Postcode - - - - zip - - - - location_source_value - + + - - Uppercase the postcode then insert the space in the correct location, if needed. + + Postcode - - Copy value. + + + + Uppercase the postcode then insert + the space in the correct + location, if needed. + + + + zip + + + + Postcode + + + + + + Copy value. + + + + location_source_value \ No newline at end of file diff --git a/Documentation/CdsPerson.svg b/Documentation/CdsPerson.svg index 196f69f..e30ec16 100644 --- a/Documentation/CdsPerson.svg +++ b/Documentation/CdsPerson.svg @@ -1,99 +1,149 @@ - - - - - NHSNumber - - - - DateOfBirth - - - - EthnicCategory - - - - PersonCurrentGenderCode - - - - gender_concept_id - - - - year_of_birth - - - - month_of_birth - - - - day_of_birth - - - - birth_datetime - - - - race_concept_id - - - - person_source_value - - - - gender_source_value - - - - race_source_value - - - - race_source_concept_id - - - - Copy value. - - - - Lookup gender concept. - - - - Selects the year from a date or null of the date is null. - - - - Selects the month of the year or null if the date is null. - - - - Selects the day of the month or null if the date is null. - - - - Converts text to dates. - - - - Lookup race concept. - - - - Copy value. - - - - Copy value. - - - - Lookup race source concept. + + + + + NHSNumber + + + + + + Copy value. + + + + person_source_value + + + + PersonCurrentGenderCode + + + + + + Lookup gender concept. + + + + gender_concept_id + + + + DateOfBirth + + + + + + Selects the year from a + date or null of the + date is null. + + + + year_of_birth + + + + DateOfBirth + + + + + + Selects the month of the + year or null if the + date is null. + + + + month_of_birth + + + + DateOfBirth + + + + + + Selects the day of the + month or null if the + date is null. + + + + day_of_birth + + + + DateOfBirth + + + + + + Converts text to dates. + + + + birth_datetime + + + + EthnicCategory + + + + + + Lookup race concept. + + + + race_concept_id + + + + PersonCurrentGenderCode + + + + + + Copy value. + + + + gender_source_value + + + + EthnicCategory + + + + + + Copy value. + + + + race_source_value + + + + EthnicCategory + + + + + + Lookup race source concept. + + + + race_source_concept_id \ No newline at end of file diff --git a/Documentation/CdsStructuredLocation.svg b/Documentation/CdsStructuredLocation.svg index 7c547ae..2d8cc90 100644 --- a/Documentation/CdsStructuredLocation.svg +++ b/Documentation/CdsStructuredLocation.svg @@ -1,111 +1,129 @@ - - - - - PatientAddressStructured1 - - - - PatientAddressStructured2 - - - - PatientAddressStructured3 - - - - PatientAddressStructured4 - - - - PatientAddressStructured5 - - - - Postcode - - - - NhsNumber - - - - address_1 - - - - address_2 - - - - city - - - - zip - - - - county - - - - location_source_value - - - - nhs_number - - - - Separates text with newlines. Trim whitespace. - - - - Separates text with newlines. Trim whitespace. - - - - Copy value. - - - - Copy value. - - - - Copy value. - - - - Uppercase the postcode then insert the space in the correct location, if needed. - - - - Separates text with newlines. Trim whitespace. - - - - Separates text with newlines. Trim whitespace. - - - - Separates text with newlines. Trim whitespace. - - - - Separates text with newlines. Trim whitespace. - - - - Separates text with newlines. Trim whitespace. - - - - Separates text with newlines. Trim whitespace. - + + - - Copy value. + + PatientAddressStructured1 + + + + PatientAddressStructured2 + + + + + + Separates text with newlines. Trim + whitespace. + + + + address_1 + + + + PatientAddressStructured3 + + + + + + Copy value. + + + + address_2 + + + + PatientAddressStructured4 + + + + + + Copy value. + + + + city + + + + PatientAddressStructured5 + + + + + + Copy value. + + + + county + + + + Postcode + + + + + + Uppercase the postcode then insert + the space in the correct + location, if needed. + + + + zip + + + + PatientAddressStructured1 + + + + PatientAddressStructured2 + + + + PatientAddressStructured3 + + + + PatientAddressStructured4 + + + + PatientAddressStructured5 + + + + Postcode + + + + + + Separates text with newlines. Trim + whitespace. + + + + location_source_value + + + + NhsNumber + + + + + + Copy value. + + + + nhs_number \ No newline at end of file diff --git a/Documentation/CosdLocation.svg b/Documentation/CosdLocation.svg index d133a2f..af43f21 100644 --- a/Documentation/CosdLocation.svg +++ b/Documentation/CosdLocation.svg @@ -1,99 +1,124 @@ - - - - - StreetAddressLine1 - - - - StreetAddressLine2 - - - - StreetAddressLine3 - - - - StreetAddressLine4 - - - - Postcode - - - - NhsNumber - - - - address_1 - - - - address_2 - - - - city - - - - zip - - - - county - - - - location_source_value - - - - nhs_number - - - - Convert text to uppercase. Trim whitespace. - - - - Convert text to uppercase. Trim whitespace. - - - - Convert text to uppercase. Trim whitespace. - - - - Convert text to uppercase. Trim whitespace. - - - - Uppercase the postcode then insert the space in the correct location, if needed. - - - - Separates text with newlines. Trim whitespace. - - - - Separates text with newlines. Trim whitespace. - - - - Separates text with newlines. Trim whitespace. - - - - Separates text with newlines. Trim whitespace. - - - - Separates text with newlines. Trim whitespace. - - - - Copy value. + + + + + StreetAddressLine1 + + + + + + Convert text to uppercase. Trim + whitespace. + + + + address_1 + + + + StreetAddressLine2 + + + + + + Convert text to uppercase. Trim + whitespace. + + + + address_2 + + + + StreetAddressLine3 + + + + + + Convert text to uppercase. Trim + whitespace. + + + + city + + + + StreetAddressLine4 + + + + + + Convert text to uppercase. Trim + whitespace. + + + + county + + + + Postcode + + + + + + Uppercase the postcode then insert + the space in the correct + location, if needed. + + + + zip + + + + StreetAddressLine1 + + + + StreetAddressLine2 + + + + StreetAddressLine3 + + + + StreetAddressLine4 + + + + Postcode + + + + + + Separates text with newlines. Trim + whitespace. + + + + location_source_value + + + + NhsNumber + + + + + + Copy value. + + + + nhs_number \ No newline at end of file diff --git a/Documentation/CosdPerson.svg b/Documentation/CosdPerson.svg index d89aa7e..5877c3c 100644 --- a/Documentation/CosdPerson.svg +++ b/Documentation/CosdPerson.svg @@ -1,79 +1,121 @@ - - - - - NhsNumber - - - - DateOfBirth - - - - EthnicCategory - - - - year_of_birth - - - - month_of_birth - - - - day_of_birth - - - - birth_datetime - - - - race_concept_id - - - - person_source_value - - - - race_source_value - - - - race_source_concept_id - - - - Copy value. - - - - Selects the year from a date or null of the date is null. - - - - Selects the month of the year or null if the date is null. - - - - Selects the day of the month or null if the date is null. - - - - Converts text to dates. - - - - Lookup race concept. - - - - Copy value. - - - - Lookup race source concept. + + + + + NhsNumber + + + + + + Copy value. + + + + person_source_value + + + + DateOfBirth + + + + + + Selects the year from a + date or null of the + date is null. + + + + year_of_birth + + + + DateOfBirth + + + + + + Selects the month of the + year or null if the + date is null. + + + + month_of_birth + + + + DateOfBirth + + + + + + Selects the day of the + month or null if the + date is null. + + + + day_of_birth + + + + DateOfBirth + + + + + + Converts text to dates. + + + + birth_datetime + + + + EthnicCategory + + + + + + Lookup race concept. + + + + race_concept_id + + + + EthnicCategory + + + + + + Copy value. + + + + race_source_value + + + + EthnicCategory + + + + + + Lookup race source concept. + + + + race_source_concept_id \ No newline at end of file diff --git a/Documentation/RtdsLocation.svg b/Documentation/RtdsLocation.svg index 735385c..d481cef 100644 --- a/Documentation/RtdsLocation.svg +++ b/Documentation/RtdsLocation.svg @@ -1,27 +1,33 @@ - - - - - NhsNumber - - - - Postcode - - - - zip - - - - nhs_number - + + - - Uppercase the postcode then insert the space in the correct location, if needed. + + Postcode - - Copy value. + + + + Uppercase the postcode then insert + the space in the correct + location, if needed. + + + + zip + + + + NhsNumber + + + + + + Copy value. + + + + nhs_number \ No newline at end of file diff --git a/Documentation/RtdsPerson.svg b/Documentation/RtdsPerson.svg index 1adaf0c..e46be35 100644 --- a/Documentation/RtdsPerson.svg +++ b/Documentation/RtdsPerson.svg @@ -1,63 +1,93 @@ - - - - - PatientId - - - - DateOfBirth - - - - Sex - - - - gender_concept_id - - - - year_of_birth - - - - month_of_birth - - - - day_of_birth - - - - birth_datetime - - - - person_source_value - - - - Copy value. - - - - Selects the year from a date or null of the date is null. - - - - Selects the month of the year or null if the date is null. - - - - Selects the day of the month or null if the date is null. - - - - Converts text to dates. - - - - Lookup gender concept. + + + + + PatientId + + + + + + Copy value. + + + + person_source_value + + + + DateOfBirth + + + + + + Selects the year from a + date or null of the + date is null. + + + + year_of_birth + + + + DateOfBirth + + + + + + Selects the month of the + year or null if the + date is null. + + + + month_of_birth + + + + DateOfBirth + + + + + + Selects the day of the + month or null if the + date is null. + + + + day_of_birth + + + + DateOfBirth + + + + + + Converts text to dates. + + + + birth_datetime + + + + Sex + + + + + + Lookup gender concept. + + + + gender_concept_id \ No newline at end of file diff --git a/Documentation/SactLocation.svg b/Documentation/SactLocation.svg index c5e7bab..88cb65c 100644 --- a/Documentation/SactLocation.svg +++ b/Documentation/SactLocation.svg @@ -1,35 +1,47 @@ - - - - - NHS_Number - - - - Patient_Postcode - - - - zip - - - - location_source_value - - - - nhs_number - - - - Uppercase the postcode then insert the space in the correct location, if needed. - - - - Copy value. - - - - Copy value. + + + + + Patient_Postcode + + + + + + Uppercase the postcode then insert + the space in the correct + location, if needed. + + + + zip + + + + Patient_Postcode + + + + + + Copy value. + + + + location_source_value + + + + NHS_Number + + + + + + Copy value. + + + + nhs_number \ No newline at end of file diff --git a/Documentation/SactPerson.svg b/Documentation/SactPerson.svg index 0d5483b..25eb534 100644 --- a/Documentation/SactPerson.svg +++ b/Documentation/SactPerson.svg @@ -1,71 +1,107 @@ - - - - - NHS_Number - - - - Date_Of_Birth - - - - Person_Stated_Gender_Code - - - - year_of_birth - - - - month_of_birth - - - - day_of_birth - - - - birth_datetime - - - - person_source_value - - - - gender_source_value - - - - gender_source_concept_id - - - - Copy value. - - - - Selects the year from a date or null of the date is null. - - - - Selects the month of the year or null if the date is null. - - - - Selects the day of the month or null if the date is null. - - - - Converts text to dates. - - - - Lookup gender concept. - - - - Copy value. + + + + + NHS_Number + + + + + + Copy value. + + + + person_source_value + + + + Date_Of_Birth + + + + + + Selects the year from a + date or null of the + date is null. + + + + year_of_birth + + + + Date_Of_Birth + + + + + + Selects the month of the + year or null if the + date is null. + + + + month_of_birth + + + + Date_Of_Birth + + + + + + Selects the day of the + month or null if the + date is null. + + + + day_of_birth + + + + Date_Of_Birth + + + + + + Converts text to dates. + + + + birth_datetime + + + + Person_Stated_Gender_Code + + + + + + Lookup gender concept. + + + + gender_source_concept_id + + + + Person_Stated_Gender_Code + + + + + + Copy value. + + + + gender_source_value \ No newline at end of file diff --git a/OmopTransformer/Documentation/Charting/SvgRenderer.cs b/OmopTransformer/Documentation/Charting/SvgRenderer.cs index 75d5fad..bdf4c30 100644 --- a/OmopTransformer/Documentation/Charting/SvgRenderer.cs +++ b/OmopTransformer/Documentation/Charting/SvgRenderer.cs @@ -1,107 +1,107 @@ -using System.Collections.Generic; +using System.Drawing; +using System.Reflection.Emit; using System.Xml.Linq; +using static System.Net.Mime.MediaTypeNames; namespace OmopTransformer.Documentation.Charting; public class SvgRenderer { - private readonly IReadOnlyCollection _sourceBoxes; - private readonly IReadOnlyCollection _targetBoxes; private readonly IReadOnlyCollection _relationships; private static readonly XNamespace SvgNamespace = "http://www.w3.org/2000/svg"; private const int BoxWidth = 250; private const int BoxHeight = 50; - public SvgRenderer(IReadOnlyCollection sourceBoxes, IReadOnlyCollection targetBoxes, IReadOnlyCollection relationships) + public SvgRenderer(IReadOnlyCollection relationships) { - _sourceBoxes = sourceBoxes; - _targetBoxes = targetBoxes; _relationships = relationships; } - private IReadOnlyCollection FilteredSourceBoxes => FilterBoxes(_sourceBoxes, relationship => relationship.Source); - private IReadOnlyCollection FilteredTargetBoxes => FilterBoxes(_targetBoxes, relationship => relationship.Target); - - private IReadOnlyCollection FilterBoxes(IEnumerable boxes, Func filter) => - boxes - .Where( - box => - _relationships - .Any(relationship => filter(relationship) == box.Name)) - .ToList(); - - static Dictionary> GroupSourcesByTarget(List sources, List relationships) - { - // Group relationships by target - var groupedSources = relationships - .GroupBy(r => r.Target) - .ToDictionary( - group => group.Key, - group => group.Join(sources, r => r.Source, s => s.Name, (r, s) => s).ToList() - ); - - return groupedSources; - } - public string Render() { - var width = GetBoxX(Math.Max(FilteredSourceBoxes.Count, FilteredTargetBoxes.Count)) + BoxWidth; + var sourceBoxesByTargetBoxes = _relationships.GroupBy(r => r.Target); + var width = GetBoxX(sourceBoxesByTargetBoxes.Count()) + BoxWidth / 2; XElement svg = new XElement( SvgNamespace + "svg", new XAttribute("xmlns", SvgNamespace), new XAttribute("width", width), - new XAttribute("height", "1500"), + new XAttribute("height", "800"), new XElement( SvgNamespace + "rect", new XAttribute("width", "100%"), new XAttribute("height", "100%"), - new XAttribute("fill", "#005EB8")), - DrawBoxes(FilteredSourceBoxes, 10, "#005EB8", "white"), - DrawBoxes(FilteredTargetBoxes, 400, "white", "#005EB8"), - DrawLines() + new XAttribute("fill", "#3E5EB2")), + DrawRelationships (sourceBoxesByTargetBoxes) ); return svg.ToString(); } - private static string GetLineColour(int index) + private static IEnumerable DrawRelationships (IEnumerable> relationships) { - int range = index % 3; + var boxes = new List(); + + int index = 0; - return range switch + foreach (var relationshipGroup in relationships) { - 0 => "#000000", - 1 => "#b0a9af", - 2 => "#3c445a", - _ => throw new InvalidOperationException() - }; - } + boxes.AddRange(DrawRelationshipSource(index, relationshipGroup)); + boxes.Add(DrawLine(relationshipGroup.First(), index)); + boxes.Add(CreateBoxElement( + x: GetBoxX(index), + y: 620, + width: BoxWidth, + height: 80, + label: relationshipGroup.Key, + fillColor: "white", + textColour: "#3E5EB2", + roundCorners: true) + ); - private IEnumerable DrawLines() => - _relationships - .Select(DrawLine); + index++; + } - private static int GetBoxIndex(IEnumerable boxes, string name) => - boxes - .Select((source, index) => new { source, index }) - .Single(box => box.source.Name == name) - .index; + return boxes; + } - private XElement DrawLine(Relationship relationship, int index) + private static IEnumerable DrawRelationshipSource(int index, IEnumerable relationships) { - int sourceIndex = GetBoxIndex(FilteredSourceBoxes, relationship.Source); + int sourceIndex = 0; - int box1X = GetBoxX(sourceIndex); - int box2Y = 70; + foreach (var relationship in relationships) + { + var height = 400; + var count = relationships.Count(); + var margin = 20; + + yield return + CreateBoxElement( + x: GetBoxX(index), + y: GetSourceTopY(height, count, margin, sourceIndex) + 20, + width: BoxWidth, + height: GetSourceBoxHeight(height, count, margin), + label: relationship.Source, + fillColor: "#3E5EB2", + textColour: "white", + roundCorners: false + ); + + sourceIndex++; + } + } - int arrowStartX = box1X + BoxWidth / 2; - int arrowStartY = BoxHeight + 10; + private static XElement DrawLine(Relationship relationship, int index) + { + int box1X = GetBoxX(index); - int arrowEndX = box1X + BoxWidth / 2; - int arrowEndY = box2Y + 330; + int arrowStartX = box1X + BoxWidth / 2 + 20; + int arrowStartY = 420; + + int arrowEndX = box1X + BoxWidth / 2 + 20; + int arrowEndY = arrowStartY + 195; return new XElement( @@ -113,51 +113,83 @@ private XElement DrawLine(Relationship relationship, int index) new XAttribute("x2", arrowEndX), new XAttribute("y2", arrowEndY), new XAttribute("stroke", "white"), - new XAttribute("stroke-width", "2"))); - //new XElement( - // SvgNamespace + "text", - // new XAttribute("x", arrowEndX - 120), - // new XAttribute("y", BoxHeight + 420), - // new XAttribute("fill", "white"), - // new XAttribute("font-family", "Helvetica"), - // new XAttribute("text-anchor", "start"), - // relationship.Label)); + new XAttribute("stroke-width", "2")), + new XElement( + SvgNamespace + "line", + new XAttribute("x1", arrowStartX-10), + new XAttribute("y1", arrowEndY-10), + new XAttribute("x2", arrowEndX), + new XAttribute("y2", arrowEndY), + new XAttribute("stroke", "white"), + new XAttribute("stroke-width", "2")), + new XElement( + SvgNamespace + "line", + new XAttribute("x1", arrowStartX+10), + new XAttribute("y1", arrowEndY-10), + new XAttribute("x2", arrowEndX), + new XAttribute("y2", arrowEndY), + new XAttribute("stroke", "white"), + new XAttribute("stroke-width", "2")), + WrapLabelText(arrowEndX - 130, BoxHeight + 660, 18, relationship.Label) + ); } private static int GetBoxX(int index) => index * 270 + 5; - private static IEnumerable DrawBoxes(IEnumerable boxes, int x, string colour, string textColour) => - boxes - .Select( - (box, index) => - CreateBoxElement( - x: GetBoxX(index), - y: x, - width: BoxWidth, - height: BoxHeight, - label: box.Name, - fillColor: colour, - textColour: textColour, - hyperlink: box.Hyperlink)); - - private static XElement CreateBoxElement(int x, int y, int width, int height, string fillColor, string label, string textColour, string? hyperlink) => + private static int GetSourceBoxHeight(int height, int count, int margin) + { + return (height - (margin * (count - 1))) / count; + } + + private static int GetSourceTopY(int height, int count, int margin, int index) + { + return (GetSourceBoxHeight(height, count, margin) + margin) * index; + } + + private static XElement CreateBoxElement(int x, int y, int width, int height, string fillColor, string label, string textColour, bool roundCorners) => new( - SvgNamespace + "a", - hyperlink == null ? null : new XAttribute("href", hyperlink), + SvgNamespace + "g", new XElement(SvgNamespace + "rect", - new XAttribute("x", x), + new XAttribute("x", x + 20), new XAttribute("y", y), new XAttribute("width", width), new XAttribute("height", height), new XAttribute("fill", fillColor), new XAttribute("stroke", "white"), - new XAttribute("stroke-width", "2") + new XAttribute("stroke-width", "2"), + roundCorners ? new XAttribute("rx", "10") : null ), new XElement(SvgNamespace + "text", - new XAttribute("x", x + 10), + new XAttribute("x", x + 30), new XAttribute("y", y + height / 2 + 5), new XAttribute("fill", textColour), new XAttribute("font-family", "Helvetica"), + new XAttribute("font-size", "1.2em"), new XAttribute("text-anchor", "start"), label)); + + private static IEnumerable WrapLabelText(int x, int y, int lineHeight, string label) + { + + string[] words = label.Split(' '); + + // Calculate the number of segments + int numSegments = (int)Math.Ceiling((double)words.Length / 5); + + // Initialize the result array + string[] result = new string[numSegments]; + + // Add each line as a separate text element + for (int i = 0; i < numSegments; i++) + { + result[i] = string.Join(" ", words.Skip(i * 5).Take(5)); + yield return new XElement(SvgNamespace + "text", + new XAttribute("x", x + 5), + new XAttribute("y", y + (i + 1) * lineHeight), + new XAttribute("fill", "white"), + new XAttribute("font-size", "1em"), + new XAttribute("font-family", "Helvetica"), + result[i]); + } + } } \ No newline at end of file diff --git a/OmopTransformer/Documentation/DocumentationRenderer.cs b/OmopTransformer/Documentation/DocumentationRenderer.cs index 8bbe73e..f459923 100644 --- a/OmopTransformer/Documentation/DocumentationRenderer.cs +++ b/OmopTransformer/Documentation/DocumentationRenderer.cs @@ -108,19 +108,9 @@ private IEnumerable RenderDiagrams(IEnumerable omopTargets) => private Document RenderDiagram(Type type) { - var targetMembers = - type - .BaseType! - .GetProperties() - .Where(property => !IgnoreProperty(property)) - .Select(property => new Box(property.Name, $"{OmopDescription(type)}_{property.Name}.md")) - .ToList(); - - var sourceMembers = GetOmopSourceType(type).GetProperties().Select(property => new Box(property.Name, null)).ToList(); - var relationships = GetRelationships(type); - var svgRenderer = new SvgRenderer(sourceMembers, targetMembers, relationships); + var svgRenderer = new SvgRenderer(relationships); return new Document($"{type.Name}.svg", svgRenderer.Render()); }