Skip to content

Commit

Permalink
Updated so that nodes can also be removed.
Browse files Browse the repository at this point in the history
  • Loading branch information
KristofferStrube committed Apr 7, 2024
1 parent 4f0370f commit cd5fc6a
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@
transitions = transitions.DistinctBy(t => t.from + "-" + t.to).ToList();

await GraphEditor.LoadGraph(pages, transitions);
GraphEditor.FitToShapes(delta: 1, 200);
GraphEditor.SVGEditor.FitViewportToAllShapes(delta: 1, 200);
GraphEditor.MoveEdgesToBack();

double prevUnixTimeSeconds = DateTimeOffset.Now.ToUnixTimeMilliseconds() / 1000.0;
while (running)
{
await GraphEditor.ForceDirectedLayout();
double unixTimeSeconds = DateTimeOffset.Now.ToUnixTimeMilliseconds() / 1000.0;
GraphEditor.FitToShapes(delta: Math.Min((unixTimeSeconds - prevUnixTimeSeconds) * 4, 1));
GraphEditor.SVGEditor.FitViewportToAllShapes(delta: Math.Min((unixTimeSeconds - prevUnixTimeSeconds) * 4, 1));
prevUnixTimeSeconds = unixTimeSeconds;
await Task.Delay(1);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
p.Radians = p.BaseOffset + (unixTimeSeconds * 2) % (Math.PI * 2);
});
await GraphEditor.ForceDirectedLayout();
GraphEditor.FitToShapes(delta: Math.Min((unixTimeSeconds - prevUnixTimeSeconds) * 4, 1));
GraphEditor.SVGEditor.FitViewportToAllShapes(delta: Math.Min((unixTimeSeconds - prevUnixTimeSeconds) * 4, 1));
prevUnixTimeSeconds = unixTimeSeconds;
await Task.Delay(1);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
double unixTimeSeconds = DateTimeOffset.Now.ToUnixTimeMilliseconds() / 1000.0;
if (unixTimeSeconds - startUnixTimeSeconds < 7)
{
GraphEditor.FitToShapes(delta: Math.Min((unixTimeSeconds - prevUnixTimeSeconds) * 2, 1), padding: 100);
GraphEditor.SVGEditor.FitViewportToAllShapes(delta: Math.Min((unixTimeSeconds - prevUnixTimeSeconds) * 2, 1), padding: 100);
prevUnixTimeSeconds = unixTimeSeconds;
}
await Task.Delay(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@

<button class="btn btn-success" @onclick="AddNode">Add Node</button>

@if (pages.Count > 3)
{
<button class="btn btn-success" @onclick="RemoveNode">Remove Node</button>
}

<div style="height:70vh;">
<GraphEditor @ref=GraphEditor
TNode="Page"
Expand All @@ -32,7 +37,6 @@
private bool running = true;

private double edgeLengthSinAngle = 0;
int nodeCount = 8;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
Expand All @@ -42,8 +46,8 @@
await Task.Delay(50);
}

pages = Enumerable.Range(0, nodeCount).Select(i => new Page(i.ToString()) { BaseOffset = i * Math.PI * 2 / nodeCount }).ToList();
transitions = Enumerable.Range(0, nodeCount).Select(i => new Transition(pages[i % nodeCount], pages[(i + 1) % nodeCount], 1)).ToList();
pages = Enumerable.Range(0, 8).Select(i => new Page(i.ToString()) { BaseOffset = i * Math.PI * 2 / 8 }).ToList();
transitions = Enumerable.Range(0, 8).Select(i => new Transition(pages[i % 8], pages[(i + 1) % 8], 1)).ToList();

await GraphEditor.LoadGraph(pages, transitions);

Expand All @@ -57,8 +61,9 @@
p.Radians = p.BaseOffset + unixTimeSeconds % (Math.PI * 2);
});
await GraphEditor.ForceDirectedLayout();
GraphEditor.FitToShapes(delta: Math.Min((unixTimeSeconds - prevUnixTimeSeconds) * 2, 1), 50);
GraphEditor.SVGEditor.FitViewportToAllShapes(delta: Math.Min((unixTimeSeconds - prevUnixTimeSeconds) * 2, 1), 50);
prevUnixTimeSeconds = unixTimeSeconds;
StateHasChanged();
await Task.Delay(1);
}
}
Expand All @@ -74,19 +79,32 @@

public async Task AddNode()
{
pages.Add(new Page(nodeCount++.ToString()) { BaseOffset = DateTimeOffset.Now.ToUnixTimeMilliseconds() / 1000.0 });
transitions.Add(new Transition(pages[Random.Shared.Next(nodeCount - 2)], pages[^1], 1, 200));
if (Random.Shared.NextDouble() < 0.07)
{
transitions.Add(new Transition(pages[Random.Shared.Next(nodeCount - 2)], pages[^1], 1, 200));
}
if (Random.Shared.NextDouble() < 0.07)
pages.Add(new Page(Guid.NewGuid().ToString()) { BaseOffset = DateTimeOffset.Now.ToUnixTimeMilliseconds() / 1000.0 });
double chance = 1;
for (int i = 0; i < 3; i++)
{
transitions.Add(new Transition(pages[Random.Shared.Next(nodeCount - 2)], pages[^1], 1, 200));
if (chance > 0.93)
{
var fromNode = pages[Random.Shared.Next(Math.Max(0, pages.Count - 2))];
var toNode = pages[^1];
if (fromNode != toNode)
{
transitions.Add(new Transition(fromNode, toNode, 1, 200));
}
}
chance = Random.Shared.NextDouble();
}
await GraphEditor.UpdateGraph(pages, transitions);
}

public async Task RemoveNode()
{
Page pageToRemove = pages[Random.Shared.Next(pages.Count - 1)];
pages.Remove(pageToRemove);
transitions = transitions.Where(t => t.from != pageToRemove && t.to != pageToRemove).ToList();
await GraphEditor.UpdateGraph(pages, transitions);
}

double EdgeLengthMapper(Transition edge) => edge.length * (1.5 + Math.Sin(edgeLengthSinAngle) / 2);

public record Page(string id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ else
double unixTimeSeconds = DateTimeOffset.Now.ToUnixTimeMilliseconds() / 1000.0;
if (unixTimeSeconds - startUnixTimeSeconds < 3)
{
GraphEditor.FitToShapes(delta: Math.Min((unixTimeSeconds - prevUnixTimeSeconds) * 2, 1), padding: 50);
GraphEditor.SVGEditor.FitViewportToAllShapes(delta: Math.Min((unixTimeSeconds - prevUnixTimeSeconds) * 2, 1), padding: 50);
prevUnixTimeSeconds = unixTimeSeconds;
}
await Task.Delay(1);
Expand Down
1 change: 0 additions & 1 deletion src/KristofferStrube.Blazor.GraphEditor/Edge.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using AngleSharp.Dom;
using KristofferStrube.Blazor.SVGEditor;
using Microsoft.AspNetCore.Components.Web;
using System.Xml.Linq;

namespace KristofferStrube.Blazor.GraphEditor;

Expand Down
85 changes: 45 additions & 40 deletions src/KristofferStrube.Blazor.GraphEditor/GraphEditor.razor.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
using AngleSharp.Dom;
using KristofferStrube.Blazor.SVGEditor;
using Microsoft.AspNetCore.Components;
using System.Globalization;
using System.Xml.Linq;
using static System.Formats.Asn1.AsnWriter;

namespace KristofferStrube.Blazor.GraphEditor;

Expand Down Expand Up @@ -48,6 +44,14 @@ private string EdgeId(TEdge e)

public bool IsReadyToLoad => SVGEditor.BBox is not null;

protected Dictionary<string, TNode> Nodes { get; set; } = [];

protected Dictionary<string, TEdge> Edges { get; set; } = [];

public SVGEditor.SVGEditor SVGEditor { get; set; } = default!;

protected string Input { get; set; } = "";

protected override void OnInitialized()
{
callbackContext = new()
Expand Down Expand Up @@ -104,27 +108,60 @@ public async Task UpdateGraph(List<TNode> nodes, List<TEdge> edges)
{
Dictionary<TNode, Node<TNode, TEdge>> newNodeElementDictionary = [];

HashSet<string> newSetOfNodes = new(nodes.Count);
HashSet<string> newSetOfEdges = new(edges.Count);

// Add new nodes
foreach (TNode node in nodes)
{
if (!Nodes.ContainsKey(NodeIdMapper(node)))
string nodeKey = NodeIdMapper(node);
if (!Nodes.ContainsKey(nodeKey))
{
Node<TNode, TEdge> element = Node<TNode, TEdge>.CreateNew(SVGEditor, this, node);
newNodeElementDictionary.Add(node, element);
Nodes.Add(NodeIdMapper(node), node);
Nodes.Add(nodeKey, node);
}
newSetOfNodes.Add(nodeKey);
}

// Add new edges
foreach (TEdge edge in edges)
{
if (!Edges.ContainsKey(EdgeId(edge)))
string edgeKey = EdgeId(edge);
if (!Edges.ContainsKey(edgeKey))
{
TNode from = EdgeFromMapper(edge);
TNode to = EdgeToMapper(edge);
Node<TNode, TEdge> fromElement = newNodeElementDictionary.TryGetValue(from, out var eFrom) ? eFrom : nodeElements.First(n => n.Data.Equals(from));
Node<TNode, TEdge> toElement = newNodeElementDictionary.TryGetValue(to, out var eTo) ? eTo : nodeElements.First(n => n.Data.Equals(to));
Edge<TNode, TEdge>.AddNew(SVGEditor, this, edge, fromElement, toElement);
Edges.Add(EdgeId(edge), edge);
Edges.Add(edgeKey, edge);
}
newSetOfEdges.Add(edgeKey);
}

// Remove old edges
foreach (string edgeKey in Edges.Keys)
{
if (!newSetOfEdges.Contains(edgeKey))
{
Edge<TNode, TEdge> edgeToRemove = (Edge<TNode, TEdge>)SVGEditor.Elements.First(e => e is Edge<TNode, TEdge> edge && EdgeId(edge.Data) == edgeKey);
SVGEditor.RemoveElement(edgeToRemove);
Edges.Remove(edgeKey);
}
}

// Remove old nodes
foreach (string nodeKey in Nodes.Keys)
{
if (!newSetOfNodes.Contains(nodeKey))
{
Node<TNode, TEdge> nodeToRemove = (Node<TNode, TEdge>)SVGEditor.Elements.First(e => e is Node<TNode, TEdge> node && NodeIdMapper(node.Data) == nodeKey);
SVGEditor.RemoveElement(nodeToRemove);
Nodes.Remove(nodeKey);
}
}

foreach (var newNodeElement in newNodeElementDictionary.Values)
{
if (newNodeElement.Edges.Count is 0)
Expand Down Expand Up @@ -244,30 +281,6 @@ public Task ForceDirectedLayout()
return Task.CompletedTask;
}

public void FitToShapes(double delta = 1, double padding = 20)
{
if (SVGEditor.BBox is null || SVGEditor.SelectedShapes.Count > 0) return;
double lowerX = double.MaxValue, lowerY = double.MaxValue;
double upperX = double.MinValue, upperY = double.MinValue;
foreach (Shape shape in SVGEditor.Elements.Cast<Shape>())
{
foreach ((double x, double y) in shape.SelectionPoints)
{
double strokeWidth = double.TryParse(shape.StrokeWidth, CultureInfo.InvariantCulture, out double result) ? result : 0;
lowerX = Math.Min(x - strokeWidth, lowerX);
upperX = Math.Max(x + strokeWidth, upperX);
lowerY = Math.Min(y - strokeWidth, lowerY);
upperY = Math.Max(y + strokeWidth, upperY);
}
}
double elementsWidth = upperX - lowerX;
double elementsHeight = upperY - lowerY;
var newScale = Math.Min((SVGEditor.BBox.Width - (padding * 2)) / elementsWidth, (SVGEditor.BBox.Height - (padding * 2)) / elementsHeight);
(double x, double y) newTranslate = ((SVGEditor.BBox.Width / 2) - ((lowerX + (elementsWidth / 2)) * newScale), (SVGEditor.BBox.Height / 2) - ((lowerY + (elementsHeight / 2)) * newScale));
SVGEditor.Scale = (SVGEditor.Scale * (1 - delta) + newScale * delta);
SVGEditor.Translate = (SVGEditor.Translate.x * (1 - delta) + newTranslate.x * delta, SVGEditor.Translate.y * (1 - delta) + newTranslate.y * delta);
}

public void MoveEdgesToBack()
{
var prevSelectedShapes = SVGEditor.SelectedShapes.ToList();
Expand All @@ -280,14 +293,6 @@ public void MoveEdgesToBack()
SVGEditor.SelectedShapes = prevSelectedShapes;
}

protected Dictionary<string, TNode> Nodes { get; set; } = [];

protected Dictionary<string, TEdge> Edges { get; set; } = [];

protected SVGEditor.SVGEditor SVGEditor { get; set; } = default!;

protected string Input { get; set; } = "";

protected List<SupportedElement> SupportedElements { get; set; } =
[
new(typeof(Node<TNode, TEdge>), element => element.TagName is "CIRCLE" && element.GetAttribute("data-elementtype") == "node"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="KristofferStrube.Blazor.SVGEditor" Version="0.2.1" />
<PackageReference Include="KristofferStrube.Blazor.SVGEditor" Version="0.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="7.0.8" />
</ItemGroup>

Expand Down

0 comments on commit cd5fc6a

Please sign in to comment.