import 'package:arrow_path/arrow_path.dart'; import 'package:graphs/src/graph.dart'; import 'package:flutter/material.dart'; import 'dart:math'; class CurvePainter extends CustomPainter { CurvePainter({ Key? key, required this.gr, }); Graphs gr; final double rad = 7; final double width = 4; final Color col = Colors.black; final double aboveHeight = 5; double circleRad = 100; final TextStyle textStyle = const TextStyle( color: Colors.black, fontSize: 21, ); void drawLine(Canvas canvas, Offset p1, Offset p2) { Paint p = Paint(); p.color = col; p.strokeWidth = width - 2; canvas.drawLine(p1, p2, p); } void drawDot(Canvas canvas, Offset p1) { var p = Paint(); p.color = col; p.strokeWidth = width; canvas.drawCircle(p1, rad, p); } void drawSelfConnect(Canvas canvas, Offset p1) { var p = Paint(); p.color = col; p.strokeWidth = width - 2; p.style = PaintingStyle.stroke; canvas.drawCircle(Offset(p1.dx + rad + 20, p1.dy), rad + 20, p); } TextSpan _getTextSpan(String s) => TextSpan(text: s, style: textStyle); TextPainter _getTextPainter(String s) => TextPainter( text: _getTextSpan(s), textDirection: TextDirection.ltr, textAlign: TextAlign.center); void drawDotNames(Canvas canvas, Offset place, String s) { var textPainter = _getTextPainter(s); textPainter.layout(); textPainter.paint( canvas, Offset((place.dx - textPainter.width), (place.dy - textPainter.height) - aboveHeight)); } void drawAboveText(Canvas canvas, Offset size, String s) { var textPainter = _getTextPainter(s); textPainter.layout(); textPainter.paint( canvas, Offset((size.dx - textPainter.width), (size.dy - textPainter.height) - aboveHeight)); } Graphs getGraph() { List d = []; d.add(Dot.fromTwoLists("1", [2, 3], [1, 1])); d.add(Dot.fromTwoLists("2", [1, 3], [1, 1])); d.add(Dot.fromTwoLists("3", [1, 2], [1, 1])); d.add(Dot.fromTwoLists("Name1", [], [])); d.add(Dot.fromTwoLists("Name2", [], [])); return Graphs.fromList("1", d, false, false); } int getHighConnections() { int higest = -1; gr.getDots().forEach((element) { if (element.getL().length > higest) { higest = element.num; } }); return higest; } Map getDotPos(int dotsAm, Size size, int exclude) { dotsAm--; Map off = {}; var width = size.width / 2; var height = size.height / 2; int add = 1; for (int i = 0; i < dotsAm; i++) { double x = cos(2 * pi * i / dotsAm) * circleRad + width; double y = sin(2 * pi * i / dotsAm) * circleRad + height; if (i == exclude - 1) add++; off[i + add] = Offset(x, y); } //print(off); return off; } void drawLowArrow(Canvas canvas, Size size, Offset from, Offset to, [bool doubleSided = false]) { Path path; // The arrows usually looks better with rounded caps. Paint paint = Paint() ..color = Colors.black ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round ..strokeJoin = StrokeJoin.round ..strokeWidth = width - 1; var length = sqrt((to.dx - from.dx) * (to.dx - from.dx) + (to.dy - from.dy) * (to.dy - from.dy)); /// Draw a single arrow. path = Path(); path.moveTo(from.dx, from.dy); path.relativeCubicTo(0, 0, -(from.dx + to.dx) / length - 40, -(from.dy + to.dy) / length - 40, to.dx - from.dx, to.dy - from.dy); path = ArrowPath.make(path: path, isDoubleSided: doubleSided, tipLength: 13); canvas.drawPath(path, paint); } void drawHighArrow(Canvas canvas, Size size, Offset from, Offset to, [bool doubleSided = false]) { Path path; // The arrows usually looks better with rounded caps. Paint paint = Paint() ..color = Colors.black ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round ..strokeJoin = StrokeJoin.round ..strokeWidth = width - 1; var length = sqrt((to.dx - from.dx) * (to.dx - from.dx) + (to.dy - from.dy) * (to.dy - from.dy)); /// Draw a single arrow. path = Path(); path.moveTo(from.dx, from.dy); path.relativeCubicTo(0, 0, (from.dx + to.dx) / length + 60, (from.dy + to.dy) / length + 0, to.dx - from.dx, to.dy - from.dy); path = ArrowPath.make( path: path, isDoubleSided: doubleSided, tipLength: 13, isAdjusted: false); canvas.drawPath(path, paint); } void drawConnections( Canvas canvas, Size size, List dots, Map off) { for (var i in dots) { var list = i.getL(); var beg = off[i.num]; for (var d in list.keys) { if (d == i.num) { drawSelfConnect(canvas, off[d]!); } else { if (gr.getDoubleSidedBool()) { if (d > i.num) { drawLowArrow(canvas, size, beg!, off[d]!, false); if (gr.getUseLengthBool()) { drawDotNames( canvas, Offset((off[d]!.dx + beg.dx) / 2 - 18, (off[d]!.dy + beg.dy) / 2 - 18), i.getL()[d].toString()); } } else { drawHighArrow(canvas, size, beg!, off[d]!, false); if (gr.getUseLengthBool()) { drawDotNames( canvas, Offset((off[d]!.dx + beg.dx) / 2 + 30, (off[d]!.dy + beg.dy) / 2 + 30), i.getL()[d].toString()); } } } else { drawLine(canvas, beg!, off[d]!); if (gr.getUseLengthBool()) { drawDotNames( canvas, Offset((off[d]!.dx + beg.dx) / 2, (off[d]!.dy + beg.dy) / 2), i.getL()[d].toString()); } } } } } } @override void paint(Canvas canvas, Size size) { if (size.width > size.height) { circleRad = size.height / 3; } else { circleRad = size.width / 3; } //var paint = Paint(); //drawLine(canvas, Offset(0, size.height / 2), //Offset(size.width, size.height / 2)); //gr = getGraph(); int higest = getHighConnections(); if (higest > -1) { var off = getDotPos(gr.getDotAmount(), size, higest); off[higest] = Offset(size.width / 2, size.height / 2); for (int i in off.keys) { drawDot(canvas, off[i]!); drawDotNames( canvas, off[i]!, "${gr.getDots()[i - 1].getName()}:[${i}]"); } //var g = gr.getNoRepeatDots(); //print(g); var g = gr.getDots(); drawConnections(canvas, size, g, off); //pringArr(canvas, size); //drawArrow(canvas, Offset(size.width / 2, size.height / 2), // Offset(size.width / 2 + 50, size.height / 2 + 200)); } } @override bool shouldRepaint(CustomPainter oldDelegate) { return true; } /*void _drawTextAt(String txt, Offset position, Canvas canvas) { final textPainter = getTextPainter(txt); textPainter.layout(minWidth: 0, maxWidth: 0); Offset drawPosition = Offset(position.dx, position.dy - (textPainter.height / 2)); textPainter.paint(canvas, drawPosition); }*/ } void pringArr(Canvas canvas, Size size) { TextSpan textSpan; TextPainter textPainter; Path path; // The arrows usually looks better with rounded caps. Paint paint = Paint() ..color = Colors.black ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round ..strokeJoin = StrokeJoin.round ..strokeWidth = 3.0; /// Draw a single arrow. path = Path(); path.moveTo(size.width * 0.25, size.height * 0.10); path.relativeCubicTo(0, 0, size.width * 0.25, 50, size.width * 0.5, 0); path = ArrowPath.make(path: path); canvas.drawPath(path, paint..color = Colors.blue); textSpan = const TextSpan( text: 'Single arrow', style: TextStyle(color: Colors.blue), ); textPainter = TextPainter( text: textSpan, textAlign: TextAlign.center, textDirection: TextDirection.ltr, ); textPainter.layout(minWidth: size.width); textPainter.paint(canvas, Offset(0, size.height * 0.06)); /* /// Draw a double sided arrow. path = Path(); path.moveTo(size.width * 0.25, size.height * 0.2); path.relativeCubicTo(0, 0, size.width * 0.25, 50, size.width * 0.5, 0); path = ArrowPath.make(path: path, isDoubleSided: true); canvas.drawPath(path, paint..color = Colors.cyan); textSpan = const TextSpan( text: 'Double sided arrow', style: TextStyle(color: Colors.cyan), ); textPainter = TextPainter( text: textSpan, textAlign: TextAlign.center, textDirection: TextDirection.ltr, ); textPainter.layout(minWidth: size.width); textPainter.paint(canvas, Offset(0, size.height * 0.16)); /// Use complex path. path = Path(); path.moveTo(size.width * 0.25, size.height * 0.3); path.relativeCubicTo(0, 0, size.width * 0.25, 0, size.width * 0.5, 50); path.relativeCubicTo(0, 0, -size.width * 0.25, 50, -size.width * 0.5, 50); //path.relativeCubicTo(0, 0, size.width * 0.125, 10, size.width * 0.25, -10); path = ArrowPath.make(path: path, isDoubleSided: true); canvas.drawPath(path, paint..color = Colors.blue); textSpan = const TextSpan( text: 'Complex path', style: TextStyle(color: Colors.blue), ); textPainter = TextPainter( text: textSpan, textAlign: TextAlign.center, textDirection: TextDirection.ltr, ); textPainter.layout(minWidth: size.width); textPainter.paint(canvas, Offset(0, size.height * 0.28)); /// Draw several arrows on the same path. path = Path(); path.moveTo(size.width * 0.25, size.height * 0.53); path.relativeCubicTo(0, 0, size.width * 0.25, 50, size.width * 0.5, 50); path = ArrowPath.make(path: path); path.relativeCubicTo(0, 0, -size.width * 0.25, 0, -size.width * 0.5, 50); path = ArrowPath.make(path: path); Path subPath = Path(); subPath.moveTo(size.width * 0.375, size.height * 0.53 + 100); subPath.relativeCubicTo(0, 0, size.width * 0.125, 10, size.width * 0.25, -10); subPath = ArrowPath.make(path: subPath, isDoubleSided: true); path.addPath(subPath, Offset.zero); canvas.drawPath(path, paint..color = Colors.cyan); textSpan = const TextSpan( text: 'Several arrows on the same path', style: TextStyle(color: Colors.cyan), ); textPainter = TextPainter( text: textSpan, textAlign: TextAlign.center, textDirection: TextDirection.ltr, ); textPainter.layout(minWidth: size.width); textPainter.paint(canvas, Offset(0, size.height * 0.49)); /// Adjusted path = Path(); path.moveTo(size.width * 0.1, size.height * 0.8); path.relativeCubicTo(0, 0, size.width * 0.3, 50, size.width * 0.25, 75); path = ArrowPath.make(path: path, isAdjusted: true); canvas.drawPath(path, paint..color = Colors.blue); textSpan = const TextSpan( text: 'Adjusted', style: TextStyle(color: Colors.blue), ); textPainter = TextPainter( text: textSpan, textAlign: TextAlign.left, textDirection: TextDirection.ltr, ); textPainter.layout(); textPainter.paint(canvas, Offset(size.width * 0.2, size.height * 0.77)); /// Non adjusted. path = Path(); path.moveTo(size.width * 0.6, size.height * 0.8); path.relativeCubicTo(0, 0, size.width * 0.3, 50, size.width * 0.25, 75); path = ArrowPath.make(path: path, isAdjusted: false); canvas.drawPath(path, paint..color = Colors.blue); textSpan = const TextSpan( text: 'Non adjusted', style: TextStyle(color: Colors.blue), ); textPainter = TextPainter( text: textSpan, textAlign: TextAlign.left, textDirection: TextDirection.ltr, ); textPainter.layout(); textPainter.paint(canvas, Offset(size.width * 0.65, size.height * 0.77));*/ }