Skip to content

Commit

Permalink
Merge pull request #103 from neuroglia-io/fix-dagre-edges-2
Browse files Browse the repository at this point in the history
Used dagre for positioning edges labels in Blazor.Dagre
  • Loading branch information
JBBianchi authored Aug 12, 2024
2 parents 3f412d0 + 425db2e commit 7fd6316
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 40 deletions.
4 changes: 2 additions & 2 deletions src/Neuroglia.Blazor.Dagre/DagreGraph.razor
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@using Microsoft.AspNetCore.Components.Web
@inject IDagreService DagreService
@inject IGraphLayoutService GraphLayoutService
@inject IJSRuntime JSRuntime
@implements IDisposable

Expand Down Expand Up @@ -223,7 +223,7 @@
{
if (this.graph != null)
{
await this.DagreService.ComputePositionsAsync(this.graph, this.Options);
await this.GraphLayoutService.ComputeLayoutAsync(this.graph, this.Options);
this.TriggerRender(this, new());
await this.ZoomToFit();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,25 @@ namespace Neuroglia.Blazor.Dagre;
/// Creates a new instance of DagreService
/// </remarks>
/// <param name="jSRuntime"></param>
public class DagreService(IJSRuntime jSRuntime)
: IDagreService
public class GraphLayoutService(IJSRuntime jSRuntime)
: IGraphLayoutService
{
/// <summary>
/// The JS Runtime instance
/// </summary>
readonly IJSRuntime _jsRuntime = jSRuntime;

/// <summary>
/// Computes the nodes and edges position of the provided <see cref="IGraphViewModel"/>
/// </summary>
/// <param name="graphViewModel"></param>
/// <param name="options"></param>
/// <returns>The updated <see cref="IGraphViewModel"/></returns>
public virtual async Task<IGraphViewModel> ComputePositionsAsync(IGraphViewModel graphViewModel, IDagreGraphOptions? options = null)
/// <inheritdoc/>
public virtual async Task<IGraphViewModel> ComputeLayoutAsync(IGraphViewModel graphViewModel, IDagreGraphOptions? options = null)
{
ProfilingTimer? profilingTimer = null;
if (graphViewModel.EnableProfiling)
{
profilingTimer = new("ComputePositionsAsync");
profilingTimer.Start();
}
// build the dagre/graphlib graph
var graph = await this.GraphAsync(options);
// create a dagre/graphlib graph instance
var graph = await this.CreateGraphAsync(options);
var nodes = graphViewModel.AllNodes.Values.Concat(graphViewModel.AllClusters.Values);
foreach (var node in nodes)
{
Expand All @@ -64,14 +59,15 @@ public virtual async Task<IGraphViewModel> ComputePositionsAsync(IGraphViewModel
foreach (var node in nodes)
{
var graphNode = await graph.NodeAsync(node.Id);
node.SetBounds(graphNode.X, graphNode.Y, graphNode.Width, graphNode.Height);
node.SetBounds(graphNode.Width, graphNode.Height, graphNode.X, graphNode.Y);
}
foreach (var edge in graphViewModel.Edges.Values)
{
GraphLibEdge graphEdge;
if (options?.Multigraph == true) graphEdge = await graph.EdgeAsync(edge.SourceId, edge.TargetId, edge.Id);
else graphEdge = await graph.EdgeAsync(edge.SourceId, edge.TargetId);
if (graphEdge?.Points != null) edge.Points = [.. graphEdge.Points];
//if (graphEdge?.Points != null) edge.Points = [.. graphEdge.Points];
edge.SetBounds(graphEdge.Points ?? [], edge.Width, edge.Height, graphEdge.X ?? edge.X, graphEdge.Y ?? edge.Y);
}
graphViewModel.DagreGraph = graph;
if (profilingTimer != null)
Expand All @@ -90,7 +86,7 @@ public virtual async Task<IGraphViewModel> ComputePositionsAsync(IGraphViewModel
/// </summary>
/// <param name="options"></param>
/// <returns></returns>
public virtual async Task<IGraphLib> GraphAsync(IDagreGraphOptions? options = null)
async Task<IGraphLib> CreateGraphAsync(IDagreGraphOptions? options = null)
{
var graphLibOptions = new GraphLibOptions(options);
graphLibOptions.Multigraph ??= true;
Expand All @@ -108,7 +104,7 @@ public virtual async Task<IGraphLib> GraphAsync(IDagreGraphOptions? options = nu
/// </summary>
/// <param name="graph"></param>
/// <returns></returns>
public virtual async Task<IGraphLib?> LayoutAsync(IGraphLib graph) => await this._jsRuntime.InvokeAsync<IJSObjectReference>("neuroglia.blazor.dagre.layout", await graph.InstanceAsync()) as IGraphLib;
async Task<IGraphLib?> LayoutAsync(IGraphLib graph) => await this._jsRuntime.InvokeAsync<IJSObjectReference>("neuroglia.blazor.dagre.layout", await graph.InstanceAsync()) as IGraphLib;

/// <inheritdoc/>
public virtual async Task<string> SerializeAsync(IGraphLib graph) => await this._jsRuntime.InvokeAsync<string>("neuroglia.blazor.dagre.write", await graph.InstanceAsync());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,17 @@

namespace Neuroglia.Blazor.Dagre;

public interface IDagreService
/// <summary>
/// Represents the service used to compute the graph layout
/// </summary>
public interface IGraphLayoutService
: IGraphLibJsonConverter
{
Task<IGraphViewModel> ComputePositionsAsync(IGraphViewModel graphViewModel, IDagreGraphOptions? options = null);
Task<IGraphLib> GraphAsync(IDagreGraphOptions? options = null);
Task<IGraphLib?> LayoutAsync(IGraphLib graph);
/// <summary>
/// Computes the nodes and egdes positions
/// </summary>
/// <param name="graphViewModel">The graph to compute the layout of</param>
/// <param name="options">The Drage options</param>
/// <returns>The computed graph</returns>
Task<IGraphViewModel> ComputeLayoutAsync(IGraphViewModel graphViewModel, IDagreGraphOptions? options = null);
}
2 changes: 1 addition & 1 deletion src/Neuroglia.Blazor.Dagre/Models/ClusterViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,6 @@ protected virtual void OnChildChanged(object? sender, EventArgs e)
var y = (minY + maxY) / 2;
var width = maxX - minX + Constants.ClusterPaddingX;
var height = maxY - minY + Constants.ClusterPaddingY;
this.SetBounds(x, y, width, height);
this.SetBounds(width, height, x, y);
}
}
54 changes: 51 additions & 3 deletions src/Neuroglia.Blazor.Dagre/Models/EdgeViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,30 @@ public virtual IEnumerable<IPositionable> Points
}
}

double _x = 0;
/// <inheritdoc/>
public virtual double X
{
get => this._x;
set
{
this._x = value;
this.OnChange();
}
}

double _y = 0;
/// <inheritdoc/>
public virtual double Y
{
get => this._y;
set
{
this._y = value;
this.OnChange();
}
}

double _width = Constants.EdgeLabelWidth;
/// <inheritdoc/>
public virtual double Width
Expand Down Expand Up @@ -84,14 +108,38 @@ public virtual double Height
[Newtonsoft.Json.JsonIgnore]
public virtual BoundingBox Bounds => this._bounds;

/// <inheritdoc/>
public virtual void SetBounds(IEnumerable<IPositionable> points, double width = 0, double height = 0, double x = 0, double y = 0)
{
this._points = points;
this._x = x;
this._y = y;
this._width = width;
this._height = height;
this.UpdateBounds();
}

/// <summary>
/// Updates the bounding box
/// </summary>
private void UpdateBounds()
{
var minX = this.Points.Min(p => p.X);
var maxX = this.Points.Max(p => p.X);
this._bounds = new BoundingBox(this.Width, this.Height, minX, maxX);
var minX = 0d;
var maxX = 0d;
var minY = 0d;
var maxY = 0d;
if (this.Points.Any())
{
minX = this.Points.Min(p => p.X);
maxX = this.Points.Max(p => p.X);
minY = this.Points.Min(p => p.Y);
maxY = this.Points.Max(p => p.Y);
}
var width = maxX - minX;
var height = maxY - minY;
var x = minX + width;
var y = minY + height;
this._bounds = new BoundingBox(Math.Max(width, 1), Math.Max(height, 1), minX, minY);
this.OnChange();
}
}
10 changes: 10 additions & 0 deletions src/Neuroglia.Blazor.Dagre/Models/GraphLibEdge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,14 @@ public class GraphLibEdge
[Newtonsoft.Json.JsonProperty(NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public virtual Position[]? Points { get; set; }
IPositionable[]? IGraphLibEdge.Points { get => this.Points; set => this.Points = value?.OfType<Position>()?.ToArray(); }

/// <inheritdoc />
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
[Newtonsoft.Json.JsonProperty(NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public virtual double? X { get; set; }

/// <inheritdoc />
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
[Newtonsoft.Json.JsonProperty(NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public virtual double? Y { get; set; }
}
12 changes: 11 additions & 1 deletion src/Neuroglia.Blazor.Dagre/Models/Interfaces/IEdgeViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Neuroglia.Blazor.Dagre.Models;
/// Defines the fundamentals of a graph link view model
/// </summary>
public interface IEdgeViewModel
: IGraphElement, ISizeable
: IGraphElement, ISizeable, IPositionable
{
/// <summary>
/// Gets/sets the id of the node at the beginning of the edge
Expand Down Expand Up @@ -63,4 +63,14 @@ public interface IEdgeViewModel
/// Gets/sets the id of the marker used at the target end of the <see cref="IEdgeViewModel"/>
/// </summary>
string? EndMarkerId { get; set; }

/// <summary>
/// Sets the <see cref="IEdgeViewModel"/>'s measurments
/// </summary>
/// <param name="points">The edge's points</param>
/// <param name="width">The width</param>
/// <param name="height">The height</param>
/// <param name="x">The label center horizontal position</param>
/// <param name="y">The label center vertical position</param>
void SetBounds(IEnumerable<IPositionable> points, double width, double height, double x, double y);
}
10 changes: 10 additions & 0 deletions src/Neuroglia.Blazor.Dagre/Models/Interfaces/IGraphLibEdge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,14 @@ public interface IGraphLibEdge
/// </summary>
IPositionable[]? Points { get; set; }

/// <summary>
/// Gets/sets the edge label center's horizontal position
/// </summary>
double? X { get; set; }

/// <summary>
/// Gets/sets the edge label center's vertical position
/// </summary>
double? Y { get; set; }

}
10 changes: 5 additions & 5 deletions src/Neuroglia.Blazor.Dagre/Models/Interfaces/INodeViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ public interface INodeViewModel
/// <summary>
/// Sets the <see cref="INodeViewModel"/>'s <see cref="BoundingBox"/> using measurments
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="width"></param>
/// <param name="height"></param>
void SetBounds(double x, double y, double width, double height);
/// <param name="width">The width</param>
/// <param name="height">The height</param>
/// <param name="x">The horizontal position</param>
/// <param name="y">The vertical position</param>
void SetBounds(double width, double height, double x, double y);

/// <summary>
/// Moves the node
Expand Down
2 changes: 1 addition & 1 deletion src/Neuroglia.Blazor.Dagre/Models/NodeViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ public NodeViewModel(
}

/// <inheritdoc/>
public virtual void SetBounds(double x = 0, double y = 0, double width = 0, double height = 0)
public virtual void SetBounds(double width = 0, double height = 0, double x = 0, double y = 0)
{
bool changed = false;
if (this._x != x)
Expand Down
12 changes: 4 additions & 8 deletions src/Neuroglia.Blazor.Dagre/Templates/EdgeTemplate.razor
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,10 @@

protected override void OnParametersSet()
{
if (this.Edge.Points.Any()) {
var middlePointIndex = (int)Math.Ceiling((double)this.Edge.Points.Count() / 2d) - 1;
var middlePoint = this.Edge.Points.ElementAt(middlePointIndex);
this.LabelCenter = new Position(
middlePoint.X - (this.Edge.Bounds.Width / 2),
middlePoint.Y - (this.Edge.Bounds.Height / 2)
);
}
this.LabelCenter = new Position(
this.Edge.X - (this.Edge.Width / 2),
this.Edge.Y - (this.Edge.Height / 2)
);
}

protected virtual string GetPath(IEdgeViewModel edge)
Expand Down

0 comments on commit 7fd6316

Please sign in to comment.