Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,34 @@ sequence.Destroy(db);
var mermaid = sequence.Build();
```

## Class API status (Mermaid v11)

- Backward compatible API: existing `ClassDiagram.Create(...)` and current output are preserved.
- New additive API: `ClassDiagram.CreateAdvanced(...)`.
- Advanced additions: `namespace`, `note`, `note for`, `classDef`, `cssClass`.
- Advanced rendering fixes: visibility/classifier output for members, method argument separators, consistent class ID rendering for `link` and `callback`.

### Quick class example

```csharp
using FluentMermaid.ClassDiagram;
using FluentMermaid.ClassDiagram.Enums;
using FluentMermaid.ClassDiagram.Nodes;
using FluentMermaid.Enums;

var diagram = ClassDiagram.CreateAdvanced(Orientation.LeftToRight);
var order = diagram.AddClassInNamespace("Domain", new TypeName("Order", null), "entity", "domainClass");
order.AddProperty("id", new TypeName("Guid", null), Visibility.Public);
order.AddFunction("Validate", new TypeName("bool", null), Visibility.Public, new FunctionArgument("source", new TypeName("string", null)));

diagram.AddNote("Generated by FluentMermaid");
diagram.AddNoteFor(order, "Aggregate root");
diagram.AddClassDef("domainClass", "fill:#eef,stroke:#88a;");
diagram.AddCssClass("Order", "domainClass");

var mermaid = diagram.Render();
```

# Roadmap
- [x] [Flowchart](https://mermaid.js.org/syntax/flowchart.html)
- [x] [Sequence diagram](https://mermaid.js.org/syntax/sequenceDiagram.html)
Expand Down
5 changes: 4 additions & 1 deletion src/FluentMermaid/ClassDiagram/ClassDiagram.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using FluentMermaid.ClassDiagram.Interfaces;
using FluentMermaid.ClassDiagram.Interfaces;
using FluentMermaid.Enums;

namespace FluentMermaid.ClassDiagram;
Expand All @@ -7,4 +7,7 @@ public static class ClassDiagram
{
public static IClassDiagram Create(Orientation orientation)
=> new ClassDiagramRoot(orientation);

public static IClassDiagramAdvanced CreateAdvanced(Orientation orientation)
=> new ClassDiagramRootAdvanced(orientation);
}
146 changes: 146 additions & 0 deletions src/FluentMermaid/ClassDiagram/ClassDiagramRootAdvanced.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using System.Text;
using FluentMermaid.ClassDiagram.Enums;
using FluentMermaid.ClassDiagram.Interfaces;
using FluentMermaid.ClassDiagram.Interfaces.ClassMembers;
using FluentMermaid.ClassDiagram.Nodes;
using FluentMermaid.Enums;
using FluentMermaid.Extensions;

namespace FluentMermaid.ClassDiagram;

internal class ClassDiagramRootAdvanced : IClassDiagramAdvanced
{
private readonly List<ClassNodeAdvanced> _classes = new();
private readonly List<IRelation> _relations = new();
private readonly List<IClassDiagramStatement> _statements = new();
private readonly Dictionary<string, List<ClassNodeAdvanced>> _namespaces = new(StringComparer.Ordinal);
private readonly List<string> _namespaceOrder = new();

public ClassDiagramRootAdvanced(Orientation orientation)
{
Orientation = orientation;
}

public Orientation Orientation { get; }

public IClass AddClass(ITypeName typeName, string? annotation, string? cssClass)
{
_ = typeName ?? throw new ArgumentNullException(nameof(typeName));

var @class = new ClassNodeAdvanced(typeName, annotation, cssClass);
_classes.Add(@class);
return @class;
}

public IClass AddClassInNamespace(string @namespace, ITypeName typeName, string? annotation, string? cssClass)
{
if (string.IsNullOrWhiteSpace(@namespace))
{
throw new ArgumentException("Namespace should not be null or empty", nameof(@namespace));
}

var @class = (ClassNodeAdvanced)AddClass(typeName, annotation, cssClass);

if (!_namespaces.TryGetValue(@namespace, out List<ClassNodeAdvanced>? classes))
{
classes = new List<ClassNodeAdvanced>();
_namespaces[@namespace] = classes;
_namespaceOrder.Add(@namespace);
}

classes.Add(@class);
return @class;
}

public IRelation Relation(
IClass from,
IClass to,
Relationship? relationshipFrom,
Cardinality? cardinalityFrom,
Relationship? relationshipTo,
Cardinality? cardinalityTo,
RelationLink relationLink,
string? label)
{
var relation = new RelationNode(
from,
to,
relationshipFrom,
cardinalityFrom,
relationLink,
cardinalityTo,
relationshipTo,
label);
_relations.Add(relation);
return relation;
}

public IClassDiagramAdvanced AddNote(string text)
{
_statements.Add(new NoteNode(text));
return this;
}

public IClassDiagramAdvanced AddNoteFor(IClass @class, string text)
{
if (@class is not ClassNodeAdvanced classNode)
{
throw new ArgumentException("Note target should be created by advanced class diagram API", nameof(@class));
}

_statements.Add(new NoteNode(text, classNode));
return this;
}

public IClassDiagramAdvanced AddClassDef(string className, string styles)
{
_statements.Add(new ClassDefNode(className, styles));
return this;
}

public IClassDiagramAdvanced AddCssClass(string classIds, string className)
{
_statements.Add(new CssClassNode(classIds, className));
return this;
}

public string Render()
{
StringBuilder builder = new();
builder.AppendLine("classDiagram");
builder.Append("direction ").AppendLine(Orientation.Render());

_relations.ForEach(r => r.RenderTo(builder));

HashSet<ClassNodeAdvanced> namespaceClasses = new();
foreach (string namespaceName in _namespaceOrder)
{
List<ClassNodeAdvanced> classes = _namespaces[namespaceName];
new NamespaceNode(namespaceName, classes).RenderTo(builder);
foreach (ClassNodeAdvanced @class in classes)
{
namespaceClasses.Add(@class);
}
}

foreach (ClassNodeAdvanced @class in _classes)
{
if (!namespaceClasses.Contains(@class))
{
@class.RenderDeclarationTo(builder, null);
}
}

foreach (IClassDiagramStatement statement in _statements)
{
statement.RenderTo(builder);
}

foreach (ClassNodeAdvanced @class in _classes)
{
@class.RenderMetadataTo(builder);
}

return builder.ToString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using FluentMermaid.ClassDiagram.Enums;

namespace FluentMermaid.ClassDiagram.Extensions;

internal static class VisibilityAdvancedExtensions
{
public static char RenderPrefix(this Visibility visibility)
=> visibility switch
{
Visibility.Public => '+',
Visibility.Private => '-',
Visibility.Protected => '#',
Visibility.Internal => '~',
_ => ' '
};

public static char RenderSuffix(this Visibility visibility)
=> visibility switch
{
Visibility.Abstract => '*',
Visibility.Static => '$',
_ => ' '
};
}
16 changes: 16 additions & 0 deletions src/FluentMermaid/ClassDiagram/Interfaces/IClassDiagramAdvanced.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using FluentMermaid.ClassDiagram.Interfaces.ClassMembers;

namespace FluentMermaid.ClassDiagram.Interfaces;

public interface IClassDiagramAdvanced : IClassDiagram
{
IClass AddClassInNamespace(string @namespace, ITypeName typeName, string? annotation, string? cssClass);

IClassDiagramAdvanced AddNote(string text);

IClassDiagramAdvanced AddNoteFor(IClass @class, string text);

IClassDiagramAdvanced AddClassDef(string className, string styles);

IClassDiagramAdvanced AddCssClass(string classIds, string className);
}
47 changes: 47 additions & 0 deletions src/FluentMermaid/ClassDiagram/Nodes/CallbackAdvanced.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Text;
using FluentMermaid.ClassDiagram.Interfaces;

namespace FluentMermaid.ClassDiagram.Nodes;

internal class CallbackAdvanced : ICallback
{
private readonly ClassNodeAdvanced _class;

public CallbackAdvanced(ClassNodeAdvanced @class, string function, string? tooltip)
{
if (string.IsNullOrWhiteSpace(function))
{
throw new ArgumentException("Function name should not be null or empty", nameof(function));
}

_class = @class ?? throw new ArgumentNullException(nameof(@class));
Function = function;
Tooltip = tooltip;
}

public IClass Class => _class;

public string Function { get; }

public string? Tooltip { get; }

public void RenderTo(StringBuilder builder)
{
builder
.Append("callback ")
.Append(_class.ClassNameId)
.Append(" \"")
.Append(Function)
.Append('"');

if (!string.IsNullOrWhiteSpace(Tooltip))
{
builder
.Append(" \"")
.Append(Tooltip)
.Append('"');
}

builder.AppendLine();
}
}
35 changes: 35 additions & 0 deletions src/FluentMermaid/ClassDiagram/Nodes/ClassDefNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Text;

namespace FluentMermaid.ClassDiagram.Nodes;

internal class ClassDefNode : IClassDiagramStatement
{
public ClassDefNode(string className, string styles)
{
if (string.IsNullOrWhiteSpace(className))
{
throw new ArgumentException("ClassDef class name should not be null or empty", nameof(className));
}

if (string.IsNullOrWhiteSpace(styles))
{
throw new ArgumentException("ClassDef styles should not be null or empty", nameof(styles));
}

ClassName = className;
Styles = styles;
}

public string ClassName { get; }

public string Styles { get; }

public void RenderTo(StringBuilder builder)
{
builder
.Append("classDef ")
.Append(ClassName)
.Append(' ')
.AppendLine(Styles);
}
}
Loading
Loading