From 7b7c17079293120615818c0fd5da069cb9faaf06 Mon Sep 17 00:00:00 2001 From: Vishesh Handa Date: Fri, 7 Aug 2020 00:50:37 +0200 Subject: [PATCH] Graph Rendering Experiment: Try layouting via a RenderBox Instead of using a Custom Painter and then re-implementing all the hit testing and everything, instead lets try to learn about Flutter's lower level RenderObjects and let each node just be a simple Widget and provide a GraphView class, which will take a list of nodes (widgets), and edges. This is a small PoC, which looks quite promising as instead of drawing a circle as a node, we will be able to render the node as how a Note is rendered in Markdown. --- lib/main_graph.dart | 120 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 4 deletions(-) diff --git a/lib/main_graph.dart b/lib/main_graph.dart index 10a88f02..43fc0524 100644 --- a/lib/main_graph.dart +++ b/lib/main_graph.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:touchable/touchable.dart'; @@ -267,10 +268,16 @@ class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - body: Container( - height: 700, - width: 500, - child: MyExampleWidget(), + body: Center( + //child: MyExampleWidget(), + child: GraphView( + children: [ + Container(width: 50, height: 50, color: Colors.orange), + Container(width: 50, height: 50, color: Colors.blue), + Container(width: 50, height: 50, color: Colors.red), + Container(width: 50, height: 50, color: Colors.yellow), + ], + ), ), ); } @@ -383,3 +390,108 @@ bool updateGraphPositions(Graph g) { g.notify(); return allBelowThreshold; } + +class GraphRenderObjectParentData extends ContainerBoxParentData { + double x = -1; + double y = -1; + + GraphRenderObjectParentData(); +} + +const maxX = 400; +const maxY = 650; + +class GraphRenderBox extends RenderBox + with + ContainerRenderObjectMixin, + RenderBoxContainerDefaultsMixin { + @override + void setupParentData(RenderBox child) { + if (child.parentData is! GraphRenderObjectParentData) { + child.parentData = GraphRenderObjectParentData(); + } + } + + @override + double computeDistanceToActualBaseline(TextBaseline baseline) => + defaultComputeDistanceToFirstActualBaseline(baseline); + + @override + void performLayout() { + print('performLayout'); + size = const Size(400, 650); + + var random = Random(DateTime.now().millisecondsSinceEpoch); + + // Assign each of the children their value + var child = firstChild; + while (child != null) { + child.layout(constraints, parentUsesSize: true); + // child.size gives size + + final childParentData = child.parentData as GraphRenderObjectParentData; + + if (childParentData.x == -1 && childParentData.y == -1) { + var x = random.nextInt(maxX).toDouble(); + var y = random.nextInt(maxY).toDouble(); + childParentData.x = x; + childParentData.y = y; + } + + childParentData.offset = Offset(childParentData.x, childParentData.y); + print(childParentData.offset); + + child = childParentData.nextSibling; + + // FIXME: Do not let them intersect + // Do not let them go over the max size + // Computer a proper size + } + print('performLayout done'); + } + + @override + bool hitTestChildren(BoxHitTestResult result, {Offset position}) { + return defaultHitTestChildren(result, position: position); + } + + @override + void paint(PaintingContext context, Offset offset) { + // + // Paint Edges + // + + var child = firstChild; + final childParentData = child.parentData as GraphRenderObjectParentData; + + var secondChild = childParentData.nextSibling; + var secondChildParentData = + secondChild.parentData as GraphRenderObjectParentData; + + var paint = Paint(); + paint.color = Colors.grey; + paint.strokeWidth = 3.0; + + context.canvas.drawLine( + childParentData.offset.translate(child.size.width / 2, child.size.height), + secondChildParentData.offset + .translate(child.size.width / 2, child.size.height), + paint, + ); + + defaultPaint(context, offset); + } +} + +class GraphView extends MultiChildRenderObjectWidget { + GraphView({ + Key key, + List children = const [], + }) : super(key: key, children: children); + + @override + GraphRenderBox createRenderObject(BuildContext context) { + return GraphRenderBox(); + } +}