From 9d33fee00869cf38d112d8087de55502e3a18978 Mon Sep 17 00:00:00 2001 From: "tarlison.brito" Date: Wed, 22 Feb 2023 15:46:14 -0300 Subject: [PATCH 1/5] feat: Added linetype to Node and removed the triangle at the end of the line --- lib/Graph.dart | 4 ++- lib/GraphView.dart | 1 + lib/layered/SugiyamaAlgorithm.dart | 4 +-- lib/layered/SugiyamaEdgeRenderer.dart | 43 +++++++++++++++++++++++++-- lib/layered/SugiyamaNodeData.dart | 5 +++- 5 files changed, 51 insertions(+), 6 deletions(-) diff --git a/lib/Graph.dart b/lib/Graph.dart index ac35f69..0659815 100644 --- a/lib/Graph.dart +++ b/lib/Graph.dart @@ -163,6 +163,8 @@ class Node { Offset position = Offset(0, 0); + String lineType = 'default'; + double get height => size.height; double get width => size.width; @@ -189,7 +191,7 @@ class Node { @override String toString() { - return 'Node{position: $position, key: $key, _size: $size}'; + return 'Node{position: $position, key: $key, _size: $size, lineType: $lineType}'; } } diff --git a/lib/GraphView.dart b/lib/GraphView.dart index c56b3c8..0e7e015 100644 --- a/lib/GraphView.dart +++ b/lib/GraphView.dart @@ -7,6 +7,7 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'dart:ui'; import 'package:collection/collection.dart' show IterableExtension; part 'Graph.dart'; diff --git a/lib/layered/SugiyamaAlgorithm.dart b/lib/layered/SugiyamaAlgorithm.dart index e03525d..0607f35 100644 --- a/lib/layered/SugiyamaAlgorithm.dart +++ b/lib/layered/SugiyamaAlgorithm.dart @@ -85,7 +85,7 @@ class SugiyamaAlgorithm extends Algorithm { void initSugiyamaData() { graph.nodes.forEach((node) { node.position = Offset(0, 0); - nodeData[node] = SugiyamaNodeData(); + nodeData[node] = SugiyamaNodeData(node.lineType); }); graph.edges.forEach((edge) { @@ -149,7 +149,7 @@ class SugiyamaAlgorithm extends Algorithm { while (iterator.moveNext()) { final edge = iterator.current; final dummy = Node.Id(dummyId.hashCode); - final dummyNodeData = SugiyamaNodeData(); + final dummyNodeData = SugiyamaNodeData(node.lineType); dummyNodeData.isDummy = true; dummyNodeData.layer = indexNextLayer; nextLayer.add(dummy); diff --git a/lib/layered/SugiyamaEdgeRenderer.dart b/lib/layered/SugiyamaEdgeRenderer.dart index a7ab267..f109e87 100644 --- a/lib/layered/SugiyamaEdgeRenderer.dart +++ b/lib/layered/SugiyamaEdgeRenderer.dart @@ -1,5 +1,6 @@ part of graphview; + class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { Map nodeData; Map edgeData; @@ -15,7 +16,7 @@ class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { var trianglePaint = Paint() ..color = paint.color ..style = PaintingStyle.fill; - + graph.edges.forEach((edge) { final source = edge.source; @@ -52,6 +53,9 @@ class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { bendPoints[size - 4], bendPoints[size - 3], bendPoints[size - 2], bendPoints[size - 1], destination); } + // final triangleCentroid = drawTriangle( + // canvas, edgeTrianglePaint ?? trianglePaint, clippedLine[0], clippedLine[1], clippedLine[2], clippedLine[3]); + path.reset(); path.moveTo(bendPoints[0], bendPoints[1]); @@ -91,6 +95,7 @@ class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { final stopY = y1 + destination.height / 2; path.lineTo(stopX, stopY); } + // path.lineTo(triangleCentroid[0], triangleCentroid[1]); canvas.drawPath(path, currentPaint); } else { final startX = x + source.width / 2; @@ -110,9 +115,19 @@ class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { } else { canvas.drawLine( Offset(clippedLine[0], clippedLine[1]), Offset(stopX, stopY), currentPaint); + switch (nodeData[destination]?.lineType.toUpperCase()) { + case 'DASHEDONE': + _drawDashedLine(canvas, Offset(clippedLine[0], clippedLine[1]), Offset(stopX, stopY), currentPaint, 0.6); + break; + case 'DASHEDTWO': + _drawDashedLine(canvas, Offset(clippedLine[0], clippedLine[1]), Offset(stopX, stopY), currentPaint, 0.3); + break; + default: + canvas.drawLine(Offset(clippedLine[0], clippedLine[1]), Offset(stopX, stopY), currentPaint); + break; } } - }); + }}); } void _drawSharpBendPointsEdge(List bendPoints) { @@ -152,4 +167,28 @@ class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { } } } + + void _drawDashedLine(Canvas canvas, Offset source, Offset destination, Paint paint, double lineLength) { + var numLines = 14; + + // Calculate the distance between the source and destination points + var dx = destination.dx - source.dx; + var dy = destination.dy - source.dy; + + // Calculate the step size for each line + var stepX = dx / (numLines - 1); + var stepY = dy / (numLines - 1); + + // Draw the lines between the two points + Iterable.generate(numLines).map((i) { + var startX = source.dx + (i * stepX); + var startY = source.dy + (i * stepY); + var endX = startX + (stepX * lineLength); + var endY = startY + (stepY * lineLength); + return [Offset(startX, startY), Offset(endX, endY)]; + }).forEach((points) => canvas.drawLine(points[0], points[1], paint)); + + } + + } diff --git a/lib/layered/SugiyamaNodeData.dart b/lib/layered/SugiyamaNodeData.dart index 0d68c81..c3bf1f9 100644 --- a/lib/layered/SugiyamaNodeData.dart +++ b/lib/layered/SugiyamaNodeData.dart @@ -8,11 +8,14 @@ class SugiyamaNodeData { int position = -1; List predecessorNodes = []; List successorNodes = []; + String lineType; + + SugiyamaNodeData(this.lineType); bool get isReversed => reversed.isNotEmpty; @override String toString() { - return 'SugiyamaNodeData{reversed: $reversed, isDummy: $isDummy, median: $median, layer: $layer, position: $position'; + return 'SugiyamaNodeData{reversed: $reversed, isDummy: $isDummy, median: $median, layer: $layer, position: $position, lineType: $lineType}'; } } From ddd0a63f42869ea92218a54e4019c9df0748f7bd Mon Sep 17 00:00:00 2001 From: "tarlison.brito" Date: Thu, 23 Feb 2023 18:14:20 -0300 Subject: [PATCH 2/5] fix: indentation --- lib/layered/SugiyamaEdgeRenderer.dart | 75 +++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/lib/layered/SugiyamaEdgeRenderer.dart b/lib/layered/SugiyamaEdgeRenderer.dart index f109e87..c0fcd25 100644 --- a/lib/layered/SugiyamaEdgeRenderer.dart +++ b/lib/layered/SugiyamaEdgeRenderer.dart @@ -1,6 +1,5 @@ part of graphview; - class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { Map nodeData; Map edgeData; @@ -17,6 +16,7 @@ class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { ..color = paint.color ..style = PaintingStyle.fill; + graph.edges.forEach((edge) { final source = edge.source; @@ -53,8 +53,8 @@ class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { bendPoints[size - 4], bendPoints[size - 3], bendPoints[size - 2], bendPoints[size - 1], destination); } - // final triangleCentroid = drawTriangle( - // canvas, edgeTrianglePaint ?? trianglePaint, clippedLine[0], clippedLine[1], clippedLine[2], clippedLine[3]); + final triangleCentroid = drawTriangle( + canvas, edgeTrianglePaint ?? trianglePaint, clippedLine[0], clippedLine[1], clippedLine[2], clippedLine[3]); path.reset(); path.moveTo(bendPoints[0], bendPoints[1]); @@ -96,6 +96,7 @@ class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { path.lineTo(stopX, stopY); } // path.lineTo(triangleCentroid[0], triangleCentroid[1]); + path.lineTo(triangleCentroid[0], triangleCentroid[1]); canvas.drawPath(path, currentPaint); } else { final startX = x + source.width / 2; @@ -115,12 +116,21 @@ class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { } else { canvas.drawLine( Offset(clippedLine[0], clippedLine[1]), Offset(stopX, stopY), currentPaint); + // final triangleCentroid = drawTriangle( + // canvas, edgeTrianglePaint ?? trianglePaint, clippedLine[0], clippedLine[1], clippedLine[2], clippedLine[3]); + + // canvas.drawLine( + // Offset(clippedLine[0], clippedLine[1]), Offset(triangleCentroid[0], triangleCentroid[1]), currentPaint); + switch (nodeData[destination]?.lineType.toUpperCase()) { - case 'DASHEDONE': + case 'DASHEDLINE': _drawDashedLine(canvas, Offset(clippedLine[0], clippedLine[1]), Offset(stopX, stopY), currentPaint, 0.6); break; - case 'DASHEDTWO': - _drawDashedLine(canvas, Offset(clippedLine[0], clippedLine[1]), Offset(stopX, stopY), currentPaint, 0.3); + case 'DOTTEDLINE': + _drawDashedLine(canvas, Offset(clippedLine[0], clippedLine[1]), Offset(stopX, stopY), currentPaint, 0.0); + break; + case 'SINELINE': + _drawSineLine(canvas, Offset(clippedLine[0], clippedLine[1]), Offset(stopX, stopY), currentPaint); break; default: canvas.drawLine(Offset(clippedLine[0], clippedLine[1]), Offset(stopX, stopY), currentPaint); @@ -171,13 +181,18 @@ class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { void _drawDashedLine(Canvas canvas, Offset source, Offset destination, Paint paint, double lineLength) { var numLines = 14; + // lineLength == 0.0 to use dotted lines + if (lineLength == 0.0) { + numLines = 40; + } + // Calculate the distance between the source and destination points var dx = destination.dx - source.dx; var dy = destination.dy - source.dy; - + // Calculate the step size for each line - var stepX = dx / (numLines - 1); - var stepY = dy / (numLines - 1); + var stepX = dx / numLines; + var stepY = dy / numLines; // Draw the lines between the two points Iterable.generate(numLines).map((i) { @@ -187,8 +202,48 @@ class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { var endY = startY + (stepY * lineLength); return [Offset(startX, startY), Offset(endX, endY)]; }).forEach((points) => canvas.drawLine(points[0], points[1], paint)); - } + void _drawSineLine(Canvas canvas, Offset source, Offset destination, Paint paint) { + paint..strokeWidth = 1.5; + + final dx = destination.dx - source.dx; + final dy = destination.dy - source.dy; + final distance = sqrt(dx * dx + dy * dy); + final lineLength = 6; + var phaseOffset = 2; + + // Verify dx and dy to avoid NaN to Offset() + if (dx != 0 || dy != 0) { + var distanceTraveled = 0.0; + var phase = 0.0; + final path = Path()..moveTo(source.dx, source.dy); + + while (distanceTraveled < distance) { + final segmentLength = min(lineLength, distance - distanceTraveled); + final segmentFraction = segmentLength / distance; + final segmentDestination = Offset( + source.dx + dx * segmentFraction, + source.dy + dy * segmentFraction, + ); + + final y = sin(phase + phaseOffset) * segmentLength; + + num x; + if ((dx > 0 && dy < 0) || (dx < 0 && dy > 0)) { + x = sin(phase + phaseOffset) * segmentLength; + } else { // dx < 0 && dy < 0 + x = -sin(phase + phaseOffset) * segmentLength; + } + + path.lineTo(segmentDestination.dx + x, segmentDestination.dy + y); + distanceTraveled += segmentLength; + source = segmentDestination; + phase += pi * segmentLength / lineLength; + } + + canvas.drawPath(path, paint); + } + } } From 4c86c64cc85770a4c065a4c26141246a259cf8e9 Mon Sep 17 00:00:00 2001 From: Tarlison Brito Date: Thu, 14 Mar 2024 16:57:35 -0300 Subject: [PATCH 3/5] feat: join the line type with triangle to edge --- lib/layered/SugiyamaEdgeRenderer.dart | 46 +++++++++++++++------------ 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/lib/layered/SugiyamaEdgeRenderer.dart b/lib/layered/SugiyamaEdgeRenderer.dart index c0fcd25..a05cf7e 100644 --- a/lib/layered/SugiyamaEdgeRenderer.dart +++ b/lib/layered/SugiyamaEdgeRenderer.dart @@ -53,9 +53,6 @@ class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { bendPoints[size - 4], bendPoints[size - 3], bendPoints[size - 2], bendPoints[size - 1], destination); } - final triangleCentroid = drawTriangle( - canvas, edgeTrianglePaint ?? trianglePaint, clippedLine[0], clippedLine[1], clippedLine[2], clippedLine[3]); - path.reset(); path.moveTo(bendPoints[0], bendPoints[1]); @@ -95,8 +92,6 @@ class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { final stopY = y1 + destination.height / 2; path.lineTo(stopX, stopY); } - // path.lineTo(triangleCentroid[0], triangleCentroid[1]); - path.lineTo(triangleCentroid[0], triangleCentroid[1]); canvas.drawPath(path, currentPaint); } else { final startX = x + source.width / 2; @@ -106,37 +101,47 @@ class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { clippedLine = clipLine(startX, startY, stopX, stopY, destination); + var destinationPoint = Offset(stopX, stopY); + if (addTriangleToEdge) { final triangleCentroid = drawTriangle( canvas, edgeTrianglePaint ?? trianglePaint, clippedLine[0], clippedLine[1], clippedLine[2], clippedLine[3]); - canvas.drawLine( - Offset(clippedLine[0], clippedLine[1]), Offset(triangleCentroid[0], triangleCentroid[1]), currentPaint); - } else { - canvas.drawLine( - Offset(clippedLine[0], clippedLine[1]), Offset(stopX, stopY), currentPaint); - // final triangleCentroid = drawTriangle( - // canvas, edgeTrianglePaint ?? trianglePaint, clippedLine[0], clippedLine[1], clippedLine[2], clippedLine[3]); - - // canvas.drawLine( - // Offset(clippedLine[0], clippedLine[1]), Offset(triangleCentroid[0], triangleCentroid[1]), currentPaint); + destinationPoint = Offset(triangleCentroid[0], triangleCentroid[1]); + } + // Draw the line switch (nodeData[destination]?.lineType.toUpperCase()) { case 'DASHEDLINE': - _drawDashedLine(canvas, Offset(clippedLine[0], clippedLine[1]), Offset(stopX, stopY), currentPaint, 0.6); + _drawDashedLine( + canvas, + Offset(clippedLine[0], clippedLine[1]), destinationPoint, + currentPaint, 0.6 + ); break; case 'DOTTEDLINE': - _drawDashedLine(canvas, Offset(clippedLine[0], clippedLine[1]), Offset(stopX, stopY), currentPaint, 0.0); + // dotted line uses the same method as dashed line, but with a lineLength of 0.0 + _drawDashedLine( + canvas, + Offset(clippedLine[0], clippedLine[1]), destinationPoint, + currentPaint, 0.0 + ); break; case 'SINELINE': - _drawSineLine(canvas, Offset(clippedLine[0], clippedLine[1]), Offset(stopX, stopY), currentPaint); + _drawSineLine( + canvas, + Offset(clippedLine[0], clippedLine[1]), destinationPoint, + currentPaint + ); break; default: - canvas.drawLine(Offset(clippedLine[0], clippedLine[1]), Offset(stopX, stopY), currentPaint); + canvas.drawLine( + Offset(clippedLine[0], clippedLine[1]), destinationPoint, + currentPaint + ); break; } - } }}); } @@ -242,7 +247,6 @@ class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { source = segmentDestination; phase += pi * segmentLength / lineLength; } - canvas.drawPath(path, paint); } } From eea4e1d50b09ff0fc3e5abecc85404bb47de7cbe Mon Sep 17 00:00:00 2001 From: Tarlison Brito Date: Fri, 15 Mar 2024 13:07:50 -0300 Subject: [PATCH 4/5] fix: draw of dotted lines now have a fixed size --- lib/layered/SugiyamaEdgeRenderer.dart | 53 +++++++++++++++++---------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/lib/layered/SugiyamaEdgeRenderer.dart b/lib/layered/SugiyamaEdgeRenderer.dart index a05cf7e..a22b725 100644 --- a/lib/layered/SugiyamaEdgeRenderer.dart +++ b/lib/layered/SugiyamaEdgeRenderer.dart @@ -183,31 +183,46 @@ class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { } } - void _drawDashedLine(Canvas canvas, Offset source, Offset destination, Paint paint, double lineLength) { - var numLines = 14; - // lineLength == 0.0 to use dotted lines - if (lineLength == 0.0) { - numLines = 40; - } +void _drawDashedLine(Canvas canvas, Offset source, Offset destination, Paint paint, double lineLength) { + // Calculate the distance between the source and destination points + var dx = destination.dx - source.dx; + var dy = destination.dy - source.dy; + + // Calculate the Euclidean distance + var distance = sqrt(dx * dx + dy * dy); + + var numLines = lineLength == 0.0 ? (distance / 5).ceil() : 14; + + // Calculate the step size for each line + var stepX = dx / numLines; + var stepY = dy / numLines; - // Calculate the distance between the source and destination points - var dx = destination.dx - source.dx; - var dy = destination.dy - source.dy; + // Set a fixed radius for the circles + var circleRadius = 1.0; - // Calculate the step size for each line - var stepX = dx / numLines; - var stepY = dy / numLines; + // Set a fixed stroke width for the circles + var circleStrokeWidth = 1.0; + var circlePaint = Paint() + ..color = paint.color + ..strokeWidth = circleStrokeWidth + ..style = PaintingStyle.fill; // Change to fill style - // Draw the lines between the two points - Iterable.generate(numLines).map((i) { - var startX = source.dx + (i * stepX); - var startY = source.dy + (i * stepY); + // Draw the lines or dots between the two points + Iterable.generate(numLines).forEach((i) { + var startX = source.dx + (i * stepX); + var startY = source.dy + (i * stepY); + if (lineLength == 0.0) { + // Draw a dot with a fixed radius and stroke width + canvas.drawCircle(Offset(startX, startY), circleRadius, circlePaint); + } else { + // Draw a dash var endX = startX + (stepX * lineLength); var endY = startY + (stepY * lineLength); - return [Offset(startX, startY), Offset(endX, endY)]; - }).forEach((points) => canvas.drawLine(points[0], points[1], paint)); - } + canvas.drawLine(Offset(startX, startY), Offset(endX, endY), paint); + } + }); +} void _drawSineLine(Canvas canvas, Offset source, Offset destination, Paint paint) { paint..strokeWidth = 1.5; From c70f5f3363cf6828424b8fb513b25253ddf6e948 Mon Sep 17 00:00:00 2001 From: Tarlison Brito Date: Fri, 12 Apr 2024 10:07:03 -0300 Subject: [PATCH 5/5] add enum for lineType --- lib/Graph.dart | 9 ++++++++- lib/layered/SugiyamaEdgeRenderer.dart | 8 ++++---- lib/layered/SugiyamaNodeData.dart | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/Graph.dart b/lib/Graph.dart index 0659815..9f3d605 100644 --- a/lib/Graph.dart +++ b/lib/Graph.dart @@ -144,6 +144,13 @@ class Graph { } +enum LineType { + Default, + DottedLine, + DashedLine, + SineLine, +} + class Node { ValueKey? key; @@ -163,7 +170,7 @@ class Node { Offset position = Offset(0, 0); - String lineType = 'default'; + LineType lineType = LineType.Default; double get height => size.height; diff --git a/lib/layered/SugiyamaEdgeRenderer.dart b/lib/layered/SugiyamaEdgeRenderer.dart index a22b725..9d1c148 100644 --- a/lib/layered/SugiyamaEdgeRenderer.dart +++ b/lib/layered/SugiyamaEdgeRenderer.dart @@ -112,15 +112,15 @@ class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { } // Draw the line - switch (nodeData[destination]?.lineType.toUpperCase()) { - case 'DASHEDLINE': + switch (nodeData[destination]?.lineType) { + case LineType.DashedLine: _drawDashedLine( canvas, Offset(clippedLine[0], clippedLine[1]), destinationPoint, currentPaint, 0.6 ); break; - case 'DOTTEDLINE': + case LineType.DottedLine: // dotted line uses the same method as dashed line, but with a lineLength of 0.0 _drawDashedLine( canvas, @@ -128,7 +128,7 @@ class SugiyamaEdgeRenderer extends ArrowEdgeRenderer { currentPaint, 0.0 ); break; - case 'SINELINE': + case LineType.SineLine: _drawSineLine( canvas, Offset(clippedLine[0], clippedLine[1]), destinationPoint, diff --git a/lib/layered/SugiyamaNodeData.dart b/lib/layered/SugiyamaNodeData.dart index c3bf1f9..3b37c06 100644 --- a/lib/layered/SugiyamaNodeData.dart +++ b/lib/layered/SugiyamaNodeData.dart @@ -8,7 +8,7 @@ class SugiyamaNodeData { int position = -1; List predecessorNodes = []; List successorNodes = []; - String lineType; + LineType lineType; SugiyamaNodeData(this.lineType);