Git Product home page Git Product logo

graphview's Introduction

GraphView

Get it from pub package pub points popularity likes |

Flutter GraphView is used to display data in graph structures. It can display Tree layout, Directed and Layered graph. Useful for Family Tree, Hierarchy View.

alt Example alt Example alt Example

Overview

The library is designed to support different graph layouts and currently works excellent with small graphs.

You can have a look at the flutter web implementation here: http://graphview.surge.sh/

Layouts

Tree

Uses Walker's algorithm with Buchheim's runtime improvements (BuchheimWalkerAlgorithm class). Supports different orientations. All you have to do is using the BuchheimWalkerConfiguration.orientation with either ORIENTATION_LEFT_RIGHT, ORIENTATION_RIGHT_LEFT, ORIENTATION_TOP_BOTTOM and ORIENTATION_BOTTOM_TOP (default). Furthermore parameters like sibling-, level-, subtree separation can be set.

Useful for: Family Tree, Hierarchy View, Flutter Widget Tree,

Directed graph

Directed graph drawing by simulating attraction/repulsion forces. For this the algorithm by Fruchterman and Reingold (FruchtermanReingoldAlgorithm class) was implemented.

Useful for: Social network, Mind Map, Cluster, Graphs, Intercity Road Network,

Layered graph

Algorithm from Sugiyama et al. for drawing multilayer graphs, taking advantage of the hierarchical structure of the graph (SugiyamaAlgorithm class). You can also set the parameters for node and level separation using the SugiyamaConfiguration. Supports different orientations. All you have to do is using the SugiyamaConfiguration.orientation with either ORIENTATION_LEFT_RIGHT, ORIENTATION_RIGHT_LEFT, ORIENTATION_TOP_BOTTOM and ORIENTATION_BOTTOM_TOP (default).

Useful for: Hierarchical Graph which it can have weird edges/multiple paths

Usage

Currently GraphView must be used together with a Zoom Engine like InteractiveViewer. To change the zoom values just use the different attributes described in the InteractiveViewer class.

To create a graph, we need to instantiate the Graph class. Then we need to pass the layout and also optional the edge renderer.

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => MaterialApp(
        home: TreeViewPage(),
      );
}

class TreeViewPage extends StatefulWidget {
  @override
  _TreeViewPageState createState() => _TreeViewPageState();
}

class _TreeViewPageState extends State<TreeViewPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Column(
      mainAxisSize: MainAxisSize.max,
      children: [
        Wrap(
          children: [
            Container(
              width: 100,
              child: TextFormField(
                initialValue: builder.siblingSeparation.toString(),
                decoration: InputDecoration(labelText: "Sibling Separation"),
                onChanged: (text) {
                  builder.siblingSeparation = int.tryParse(text) ?? 100;
                  this.setState(() {});
                },
              ),
            ),
            Container(
              width: 100,
              child: TextFormField(
                initialValue: builder.levelSeparation.toString(),
                decoration: InputDecoration(labelText: "Level Separation"),
                onChanged: (text) {
                  builder.levelSeparation = int.tryParse(text) ?? 100;
                  this.setState(() {});
                },
              ),
            ),
            Container(
              width: 100,
              child: TextFormField(
                initialValue: builder.subtreeSeparation.toString(),
                decoration: InputDecoration(labelText: "Subtree separation"),
                onChanged: (text) {
                  builder.subtreeSeparation = int.tryParse(text) ?? 100;
                  this.setState(() {});
                },
              ),
            ),
            Container(
              width: 100,
              child: TextFormField(
                initialValue: builder.orientation.toString(),
                decoration: InputDecoration(labelText: "Orientation"),
                onChanged: (text) {
                  builder.orientation = int.tryParse(text) ?? 100;
                  this.setState(() {});
                },
              ),
            ),
            ElevatedButton(
              onPressed: () {
                final node12 = Node.Id(r.nextInt(100));
                var edge = graph.getNodeAtPosition(r.nextInt(graph.nodeCount()));
                print(edge);
                graph.addEdge(edge, node12);
                setState(() {});
              },
              child: Text("Add"),
            )
          ],
        ),
        Expanded(
          child: InteractiveViewer(
              constrained: false,
              boundaryMargin: EdgeInsets.all(100),
              minScale: 0.01,
              maxScale: 5.6,
              child: GraphView(
                graph: graph,
                algorithm: BuchheimWalkerAlgorithm(builder, TreeEdgeRenderer(builder)),
                paint: Paint()
                  ..color = Colors.green
                  ..strokeWidth = 1
                  ..style = PaintingStyle.stroke,
                builder: (Node node) {
                  // I can decide what widget should be shown here based on the id
                  var a = node.key.value as int;
                  return rectangleWidget(a);
                },
              )),
        ),
      ],
    ));
  }

  Random r = Random();

  Widget rectangleWidget(int a) {
    return InkWell(
      onTap: () {
        print('clicked');
      },
      child: Container(
          padding: EdgeInsets.all(16),
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(4),
            boxShadow: [
              BoxShadow(color: Colors.blue[100], spreadRadius: 1),
            ],
          ),
          child: Text('Node ${a}')),
    );
  }

  final Graph graph = Graph()..isTree = true;
  BuchheimWalkerConfiguration builder = BuchheimWalkerConfiguration();

  @override
  void initState() {
    final node1 = Node.Id(1);
    final node2 = Node.Id(2);
    final node3 = Node.Id(3);
    final node4 = Node.Id(4);
    final node5 = Node.Id(5);
    final node6 = Node.Id(6);
    final node8 = Node.Id(7);
    final node7 = Node.Id(8);
    final node9 = Node.Id(9);
    final node10 = Node.Id(10);  
    final node11 = Node.Id(11);
    final node12 = Node.Id(12);

    graph.addEdge(node1, node2);
    graph.addEdge(node1, node3, paint: Paint()..color = Colors.red);
    graph.addEdge(node1, node4, paint: Paint()..color = Colors.blue);
    graph.addEdge(node2, node5);
    graph.addEdge(node2, node6);
    graph.addEdge(node6, node7, paint: Paint()..color = Colors.red);
    graph.addEdge(node6, node8, paint: Paint()..color = Colors.red);
    graph.addEdge(node4, node9);
    graph.addEdge(node4, node10, paint: Paint()..color = Colors.black);
    graph.addEdge(node4, node11, paint: Paint()..color = Colors.red);
    graph.addEdge(node11, node12);

    builder
      ..siblingSeparation = (100)
      ..levelSeparation = (150)
      ..subtreeSeparation = (150)
      ..orientation = (BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM);
  }
}

Using builder mechanism to build Nodes

You can use any widget inside the node:

Node node = Node.Id(fromNodeId) ;

builder: (Node node) {
                  // I can decide what widget should be shown here based on the id
                  var a = node.key.value as int;
                  if(a ==2)
                    return rectangleWidget(a);
                  else 
                    return circleWidget(a);
                },

Using Paint to color and line thickness

You can specify the edge color and thickness by using a custom paint

getGraphView() {
        return GraphView(
                graph: graph,
                algorithm: SugiyamaAlgorithm(builder),
                paint: Paint()..color = Colors.green..strokeWidth = 1..style = PaintingStyle.stroke,
              );
}

Color Edges individually

Add an additional parameter paint. Applicable for ArrowEdgeRenderer for now.

var a = Node();
var b = Node();
 graph.addEdge(a, b, paint: Paint()..color = Colors.red);

Add focused Node

You can focus on a specific node. This will allow scrolling to that node in the future, but for now , using it we can drag a node with realtime updates in force directed graph

 onPanUpdate: (details) {
        var x = details.globalPosition.dx;
        var y = details.globalPosition.dy;
        setState(() {
          builder.setFocusedNode(graph.getNodeAtPosition(i));
          graph.getNodeAtPosition(i).position = Offset(x,y);
        });
      },

Add drag nodes feature with animation

The code is there but not enabled yet due to dart null safety migration being more important

Extract info from any json to Graph Object

Now its a bit easy to use Ids to extract info from any json to Graph Object

For example, if the json is like this:

var json = {
   "nodes": [
     {"id": 1, "label": 'circle'},
     {"id": 2, "label": 'ellipse'},
     {"id": 3, "label": 'database'},
     {"id": 4, "label": 'box'},
     {"id": 5, "label": 'diamond'},
     {"id": 6, "label": 'dot'},
     {"id": 7, "label": 'square'},
     {"id": 8, "label": 'triangle'},
   ],
   "edges": [
     {"from": 1, "to": 2},
     {"from": 2, "to": 3},
     {"from": 2, "to": 4},
     {"from": 2, "to": 5},
     {"from": 5, "to": 6},
     {"from": 5, "to": 7},
     {"from": 6, "to": 8}
   ]
 };

Step 1, add the edges by using ids

  edges.forEach((element) {
      var fromNodeId = element['from'];
      var toNodeId = element['to'];
      graph.addEdge(Node.Id(fromNodeId), Node.Id(toNodeId));
    });

Step 2: Then using builder and find the nodeValues from the json using id and then set the value of that.

 builder: (Node node) {
                  // I can decide what widget should be shown here based on the id
                  var a = node.key.value as int;
                  var nodes = json['nodes'];
                  var nodeValue = nodes.firstWhere((element) => element['id'] == a);
                  return rectangleWidget(nodeValue['label'] as String);
                },

Using any widget inside the Node (Deprecated)

You can use any widget inside the node:

Node node = Node(getNodeText);

getNodeText() {
    return Container(
        padding: EdgeInsets.all(16),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(4),
          boxShadow: [
            BoxShadow(color: Colors.blue[100], spreadRadius: 1),
          ],
        ),
        child: Text("Node ${n++}"));
  }

Examples

Rooted Tree

alt Example

Rooted Tree (Bottom to Top)

alt Example

Rooted Tree (Left to Right)

alt Example

Rooted Tree (Right to Left)

alt Example

Directed Graph

alt Example alt Example

Layered Graph

alt Example

Inspirations

This library is basically a dart representation of the excellent Android Library GraphView by Team-Blox

I would like to thank them for open sourcing their code for which reason I was able to port their code to dart and use for flutter.

Future Works

  • Add nodeOnTap
  • Add Layered Graph
  • [] Use a builder pattern to draw items on demand.
  • [] Animations
  • [] Dynamic Node Position update for directed graph

License

MIT License

Copyright (c) 2020 Nabil Mosharraf

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

graphview's People

Contributors

emmby avatar frezyx avatar jouby avatar krishna-devolo avatar nabil6391 avatar tarektolba1 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphview's Issues

How to stop the graph from reorganizing

Hello Nabil,

I am using GraphView in one of my projects and found it awesome. But I have a question not exactly an issue because I might be missing something. The question is - Each time the graph is refreshed it rearranges itself which becomes quite awkward when the graph is large as visually finding the nodes becomes quite daunting using FruchtermanReingold algorithm. I tried setting the x and y values of the nodes but it is apparently having no effect.

Regards,
Pankaj

Not able to connect from 2 node to 1 node

I tried to build a graph for family tree, requirement is from father and mother i need to conenct to child(2 node to 1) using BuchheimWalkerConfiguration algorith, but I'm not able to connect like that.

Color edges

Is there a way to customize the color or line width/style of the edges? Can I for example create an edge that is a dashed red line?

Update paint without updating graphView

Hello!

Is there a way to update the color of the pain without refreshing the whole GraphView?
Right now when I need to highlight some flow - I remove all nodes and all edges and add a new one with proper pain.

But it would be nice to add some possibility to refresh color (maybe the node itself as well) without full recreation.
It probably might be achievable with the builder method.

How to add a directed edge in Tree graph?

I would like to add a directed edge in a tree graph using Buchheim Walker algorithm.

This would be just a standard tree but with directed edges. Is it possible? If so, can a example be provided?

I tried to change the line:
algorithm: BuchheimWalkerAlgorithm(builder, TreeEdgeRenderer(builder)),
for:
algorithm: FruchtermanReingoldAlgorithm(),

and it kinda worked, except that it lost the rectangular edges.

Null safety

Hi !

I like this package and was wondering if there's plans for supporting null safety. I can also start a PR if you'd like.

Thanks

RenderCustomLayoutBox does not meet its constraints on empty initState

copy-paste example:

import 'dart:html';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:graph_page/service/service.dart';
import 'package:graphview/GraphView.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => MaterialApp(
        home: TreeViewPage(),
      );
}

class TreeViewPage extends StatefulWidget {
  @override
  _TreeViewPageState createState() => _TreeViewPageState();
}

class _TreeViewPageState extends State<TreeViewPage> {
  @override
  Widget build(BuildContext context) {
    var appBar = AppBar();
    return Scaffold(
      body: Row(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        Expanded(
          child: Container(
            color: Colors.blue[100],
            child: Text("sdfsf"),
          ),
        ),
        Expanded(
          flex: 4,
          child: Container(
            color: Colors.green[100],
            child: InteractiveViewer(
                  constrained: false,
                  boundaryMargin: EdgeInsets.all(100),
                  minScale: 0.01,
                  maxScale: 5.6,
                  child: GraphView(
                    graph: graph,
                    algorithm: BuchheimWalkerAlgorithm(
                        builder, TreeEdgeRenderer(builder)),
                    paint: Paint()
                      ..color = Colors.green
                      ..strokeWidth = 1
                      ..style = PaintingStyle.stroke,
                    builder: (Node node) {
                      // I can decide what widget should be shown here based on the id
                      var a = node.key!.value as int;
                      return rectangleWidget(a);
                    },
                  )),
          ),
        ),
      ],

    ));
  }

  Random r = Random();

  Widget rectangleWidget(int a) {
    return InkWell(
      onTap: () {
        print('clicked');
      },
      child: Container(
          padding: EdgeInsets.all(16),
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(4),

            boxShadow: [
              BoxShadow(
                color: Colors.red,
                blurRadius: 15.0, // soften the shadow
                spreadRadius: 5.0, //extend the shadow
              )
            ],

            // boxShadow: [
            //   BoxShadow(color: Colors.blue[100]!, spreadRadius: 1),
            // ],
          ),
          child: Text('Node ${a}')),
    );
  }

  final Graph graph = Graph()..isTree = true;
  BuchheimWalkerConfiguration builder = BuchheimWalkerConfiguration();

  @override
  void initState() {
    // final node1 = Node.Id(1);
    // final node2 = Node.Id(2);
    // graph.addEdge(node1, node2);

    /*
    final node1 = Node.Id(1);
    final node2 = Node.Id(2);
    final node3 = Node.Id(3);
    final node4 = Node.Id(4);
    final node5 = Node.Id(5);
    final node6 = Node.Id(6);
    final node8 = Node.Id(7);
    final node7 = Node.Id(8);
    final node9 = Node.Id(9);
    final node10 = Node(rectangleWidget(10));  //using deprecated mechanism of directly placing the widget here
    final node11 = Node(rectangleWidget(11));
    final node12 = Node(rectangleWidget(12));

    graph.addEdge(node1, node2);
    graph.addEdge(node1, node3, paint: Paint()..color = Colors.red);
    graph.addEdge(node1, node4, paint: Paint()..color = Colors.blue);
    graph.addEdge(node2, node5);
    graph.addEdge(node2, node6);
    graph.addEdge(node6, node7, paint: Paint()..color = Colors.red);
    graph.addEdge(node6, node8, paint: Paint()..color = Colors.red);
    graph.addEdge(node4, node9);
    graph.addEdge(node4, node10, paint: Paint()..color = Colors.black);
    graph.addEdge(node4, node11, paint: Paint()..color = Colors.red);
    graph.addEdge(node11, node12);
  */
    builder
      ..siblingSeparation = (100)
      ..levelSeparation = (150)
      ..subtreeSeparation = (150)
      ..orientation = (BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM);
  }
}

error:

The following assertion was thrown during performLayout():
RenderCustomLayoutBox does not meet its constraints.

Constraints: BoxConstraints(w=947.2, h=937.6)
Size: Size(77.0, 246.0)

If you are not writing your own RenderBox subclass, then this is not your fault. Contact support: https://github.com/flutter/flutter/issues/new?template=2_bug.md

The relevant error-causing widget was
_GraphView
C:\…\lib\GraphView.dart:57

Feature request: Create graph based on data entries and provide builder argument

Hi, thanks for building and maintaining such a great package.

I wonder if it would be possible to allow generating a graph based on purely data classes and not widgets.

Here's what I mean. Now nodes need to be created with widgets as their arguments:

final node = Node(
  MyWidget(
    data: data,
  ),
  key: Key(data.id),
);
graph.addNode(node);

What I would love to see is to be able to just provide some data or id:

final node = NodeData(
  id: data.id
);
graph.addNode(node);

And then when creating a GraphView I would be able to provide builder:

GraphView(
  graph: graph,
  algorithm: g.BuchheimWalkerAlgorithm(
    g.BuchheimWalkerConfiguration(),
    g.TreeEdgeRenderer(builder),
  builder: (data){
    // I can decide what widget should be shown here
    return MyWidget(data: data);
  }
),

This would simplify populating graph data and would abstract Flutter/material dependencies

Index out of range

I'm not sure if this is exactly related, but this is what happens. I am moving from 0.6.1 - 0.6.5, the orientation works fantastically, thank you. But there seems to be something going when changing out edges and nodes.

I am driving this from a database. Data comes in, map to a list of objects that define the nodes and arrows.

With 0.6.1 this works fine, as I am not sure what nodes I will be drawing each time, they all go away, along with the edges:

graph.edges.clear();
graph.nodes.clear();

I have also done this:

List<gv.Edge> edges = [];
graph.edges.forEach((element) {edges.add(element);});
graph.removeEdges(edges);

List<gv.Node> nodes = [];
graph.nodes.forEach((element) {nodes.add(element);});
graph.removeNodes(nodes);

Both works great for 0.6.1

But, 0.6.5 I get the attached error
Screenshot (277)

Add a feature to see the all nodes

Allow the user to see the all nodes to allow him to have a perpective of everything. I am having this issue where I can't zoom out more.

Screenshot 2020-09-11 at 22 49 58

Use BuchheimWalkerConfiguration to show tree merge node has problem

I use BuchheimWalkerConfiguration like this, there are 2 branch and one merge node, it's OK

`graph.addEdge(node1, node2, paint: Paint()..color = Colors.red);
graph.addEdge(node2, node6);
graph.addEdge(node6, node5);

graph.addEdge(node2, node3);
graph.addEdge(node3, node4);
graph.addEdge(node4, node5);`
image

but if i change the order like this, it will show wrong connect line

`graph.addEdge(node1, node2, paint: Paint()..color = Colors.red);
graph.addEdge(node2, node3);
graph.addEdge(node3, node4);
graph.addEdge(node4, node5);

graph.addEdge(node2, node6);
graph.addEdge(node6, node5);`
image

Displaying big json take too long time

Not critical, but when I am trying to display big graph (40KB json). But displaying take too long time. For example graph visjs.github.io/vis-network doing it much faster.
data.txt

copy-paste example:

import 'dart:html';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:graph_page/service/service.dart'; // only it need be added to get it work
import 'package:graphview/GraphView.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => MaterialApp(
        home: LayeredGraphViewPage(),
      );
}

class LayeredGraphViewPage extends StatefulWidget {
  @override
  _LayeredGraphViewPageState createState() => _LayeredGraphViewPageState();
}

class _LayeredGraphViewPageState extends State<LayeredGraphViewPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
      height: MediaQuery.of(context).size.height,
      color: Colors.amber[100],
      margin: const EdgeInsets.all(10.0),
      child: Row(
        children: [
           Container(
             width: 150,
            color: Colors.blue[100],
            child: Column(children: [
              ElevatedButton(onPressed: () { 

                    var data = TenderData.getGraphData(); // need to add this to get it work
                    // print(data['edges']);
                    data['edges']!.forEach((element) {
                        var fromNodeId = element['from'];
                        var toNodeId = element['to'];
                        graph.addEdge(Node.Id(fromNodeId), Node.Id(toNodeId));
                      });
                    setState(() {});

               }, child: Text("get data"),),
              Text("some text"),

            ],)
           
          ), 
          Expanded(child: Container(
            color: Colors.yellow[100],
             child: InteractiveViewer(
               constrained: false,
               boundaryMargin: EdgeInsets.all(100),
               minScale: 0.01,
               maxScale: 5.6,
               child: GraphView(
                 graph: graph,
                algorithm: SugiyamaAlgorithm(builder),
                 paint: Paint()
                   ..color = Colors.green
                   ..strokeWidth = 1
                   ..style = PaintingStyle.stroke,
                 builder: (Node node) {
                   // I can decide what widget should be shown here based on the id
                   var a = node.key!.value as int;
                   return rectangleWidget(node);
                 },
               )),
          
          ))
        ],
      ),
    ));
  }

  Random r = Random();

  Widget rectangleWidget(Node a) {
    return InkWell(
      onTap: () {
        print('clicked');
      },
      child: Container(
          padding: EdgeInsets.all(16),
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(4),


          border: Border.all(
          color: Colors.black,
          width: 5,
        ),


          ),
          child: Text('Node ${a}')),
    );
  }

 final Graph graph = Graph();

 SugiyamaConfiguration builder = SugiyamaConfiguration();

  @override
  void initState() {
    final node1 = Node.Id(1);
    final node2 = Node.Id(2);
    graph.addEdge(node1, node2);

    
    builder
      ..nodeSeparation = (15)
      ..levelSeparation = (15)
      ..orientation = SugiyamaConfiguration.ORIENTATION_LEFT_RIGHT;    

  }
}

Add edge without shifting other nodes?

I'm implementing a drag and drop interface and I'm noticing that nodes tend to shift positions as edges are created. Is there a way to fix the position of the existing node when using it to create a new edge?

Great library btw!

screen-20201024-140652

TreeEdgeRenderer Doesn't Pick Up Customized Edge Paint

For the edges leaving a given node, TreeEdgeRenderer uses a customized paint from the first input edge of the node, if specified, and otherwise uses the default edge paint. So, for example, if you customize all the edges individually to use an indigo color, but leave the global default as is (black), the tree will have black edges from the root node to the next level, but indigo edges elsewhere.

The fix is trivial. When painting the path from a node to each child, just use the paint specified for that edge, if any, otherwise the global default edge paint.

Does this not work with mobile?

I am running copy and paste of code at usage section and I am running into following error

Click to expand error
════════ Exception caught by rendering library ═════════════════════════════════════════════════════
The following assertion was thrown during performLayout():
RenderCustomLayoutBox does not meet its constraints.

Constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
Size: Size(865.0, 646.0)

If you are not writing your own RenderBox subclass, then this is not your fault. Contact support: https://github.com/flutter/flutter/issues/new?template=BUG.md

The relevant error-causing widget was: 
  GraphView file:///Users/jihochoi/Documents/outsource/spinor%20media/projects/kinshealthcare_flutter/lib/components/graph_view/family_graph_view.dart:82:24
When the exception was thrown, this was the stack: 
#0      RenderBox.debugAssertDoesMeetConstraints.<anonymous closure> (package:flutter/src/rendering/box.dart:2069:9)
#1      RenderBox.debugAssertDoesMeetConstraints (package:flutter/src/rendering/box.dart:2128:6)
#2      RenderBox.size=.<anonymous closure> (package:flutter/src/rendering/box.dart:1846:7)
#3      RenderBox.size= (package:flutter/src/rendering/box.dart:1848:6)
#4      RenderCustomLayoutBox.performLayout (package:graphview/GraphView.dart:131:5)
...
The following RenderObject was being processed when the exception was fired: RenderCustomLayoutBox#76b18 relayoutBoundary=up10 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...  parentData: <none> (can use size)
...  constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...  size: Size(865.0, 646.0)
...  graph: Instance of 'Graph'
...  algorithm: Instance of 'BuchheimWalkerAlgorithm'
...  paint: Paint(PaintingStyle.stroke 1.0 StrokeJoin.miter up to 0.0; Color(0xff4caf50))
RenderObject: RenderCustomLayoutBox#76b18 relayoutBoundary=up10 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
  parentData: <none> (can use size)
  constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
  size: Size(865.0, 646.0)
  graph: Instance of 'Graph'
  algorithm: Instance of 'BuchheimWalkerAlgorithm'
  paint: Paint(PaintingStyle.stroke 1.0 StrokeJoin.miter up to 0.0; Color(0xff4caf50))
...  child 1: RenderSemanticsAnnotations#8e34a relayoutBoundary=up11 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...    parentData: offset=Offset(0.0, 0.0) (can use size)
...    constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...    size: Size(78.0, 49.0)
...    child: RenderMouseRegion#7c4d3 relayoutBoundary=up12 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...      parentData: <none> (can use size)
...      constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...      size: Size(78.0, 49.0)
...      listeners: enter, exit
...      cursor: SystemMouseCursor(click)
...      child: RenderSemanticsGestureHandler#1272b relayoutBoundary=up13 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...        parentData: <none> (can use size)
...        constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...        size: Size(78.0, 49.0)
...        gestures: tap
...        child: RenderPointerListener#cf12a relayoutBoundary=up14 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...          parentData: <none> (can use size)
...          constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...          size: Size(78.0, 49.0)
...          behavior: opaque
...          listeners: down
...  child 2: RenderSemanticsAnnotations#4744a relayoutBoundary=up11 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...    parentData: offset=Offset(0.0, 0.0) (can use size)
...    constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...    size: Size(80.0, 49.0)
...    child: RenderMouseRegion#924bf relayoutBoundary=up12 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...      parentData: <none> (can use size)
...      constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...      size: Size(80.0, 49.0)
...      listeners: enter, exit
...      cursor: SystemMouseCursor(click)
...      child: RenderSemanticsGestureHandler#f6791 relayoutBoundary=up13 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...        parentData: <none> (can use size)
...        constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...        size: Size(80.0, 49.0)
...        gestures: tap
...        child: RenderPointerListener#81a84 relayoutBoundary=up14 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...          parentData: <none> (can use size)
...          constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...          size: Size(80.0, 49.0)
...          behavior: opaque
...          listeners: down
...  child 3: RenderSemanticsAnnotations#67690 relayoutBoundary=up11 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...    parentData: offset=Offset(0.0, 0.0) (can use size)
...    constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...    size: Size(80.0, 49.0)
...    child: RenderMouseRegion#7f2dd relayoutBoundary=up12 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...      parentData: <none> (can use size)
...      constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...      size: Size(80.0, 49.0)
...      listeners: enter, exit
...      cursor: SystemMouseCursor(click)
...      child: RenderSemanticsGestureHandler#6f7ed relayoutBoundary=up13 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...        parentData: <none> (can use size)
...        constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...        size: Size(80.0, 49.0)
...        gestures: tap
...        child: RenderPointerListener#0abab relayoutBoundary=up14 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...          parentData: <none> (can use size)
...          constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...          size: Size(80.0, 49.0)
...          behavior: opaque
...          listeners: down
...  child 4: RenderSemanticsAnnotations#dfefa relayoutBoundary=up11 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...    parentData: offset=Offset(0.0, 0.0) (can use size)
...    constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...    size: Size(81.0, 49.0)
...    child: RenderMouseRegion#8f253 relayoutBoundary=up12 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...      parentData: <none> (can use size)
...      constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...      size: Size(81.0, 49.0)
...      listeners: enter, exit
...      cursor: SystemMouseCursor(click)
...      child: RenderSemanticsGestureHandler#2d73d relayoutBoundary=up13 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...        parentData: <none> (can use size)
...        constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...        size: Size(81.0, 49.0)
...        gestures: tap
...        child: RenderPointerListener#2a252 relayoutBoundary=up14 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...          parentData: <none> (can use size)
...          constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...          size: Size(81.0, 49.0)
...          behavior: opaque
...          listeners: down
...  child 5: RenderSemanticsAnnotations#074c1 relayoutBoundary=up11 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...    parentData: offset=Offset(0.0, 0.0) (can use size)
...    constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...    size: Size(80.0, 49.0)
...    child: RenderMouseRegion#5d144 relayoutBoundary=up12 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...      parentData: <none> (can use size)
...      constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...      size: Size(80.0, 49.0)
...      listeners: enter, exit
...      cursor: SystemMouseCursor(click)
...      child: RenderSemanticsGestureHandler#d329f relayoutBoundary=up13 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...        parentData: <none> (can use size)
...        constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...        size: Size(80.0, 49.0)
...        gestures: tap
...        child: RenderPointerListener#ee855 relayoutBoundary=up14 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...          parentData: <none> (can use size)
...          constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...          size: Size(80.0, 49.0)
...          behavior: opaque
...          listeners: down
...  child 6: RenderSemanticsAnnotations#ec174 relayoutBoundary=up11 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...    parentData: offset=Offset(0.0, 0.0) (can use size)
...    constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...    size: Size(81.0, 49.0)
...    child: RenderMouseRegion#10b2e relayoutBoundary=up12 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...      parentData: <none> (can use size)
...      constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...      size: Size(81.0, 49.0)
...      listeners: enter, exit
...      cursor: SystemMouseCursor(click)
...      child: RenderSemanticsGestureHandler#c42e5 relayoutBoundary=up13 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...        parentData: <none> (can use size)
...        constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...        size: Size(81.0, 49.0)
...        gestures: tap
...        child: RenderPointerListener#a2b48 relayoutBoundary=up14 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...          parentData: <none> (can use size)
...          constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...          size: Size(81.0, 49.0)
...          behavior: opaque
...          listeners: down
...  child 7: RenderSemanticsAnnotations#60ba8 relayoutBoundary=up11 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...    parentData: offset=Offset(0.0, 0.0) (can use size)
...    constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...    size: Size(81.0, 49.0)
...    child: RenderMouseRegion#ff7a2 relayoutBoundary=up12 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...      parentData: <none> (can use size)
...      constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...      size: Size(81.0, 49.0)
...      listeners: enter, exit
...      cursor: SystemMouseCursor(click)
...      child: RenderSemanticsGestureHandler#4bd87 relayoutBoundary=up13 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...        parentData: <none> (can use size)
...        constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...        size: Size(81.0, 49.0)
...        gestures: tap
...        child: RenderPointerListener#d3f16 relayoutBoundary=up14 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...          parentData: <none> (can use size)
...          constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...          size: Size(81.0, 49.0)
...          behavior: opaque
...          listeners: down
...  child 8: RenderSemanticsAnnotations#1114b relayoutBoundary=up11 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...    parentData: offset=Offset(0.0, 0.0) (can use size)
...    constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...    size: Size(80.0, 49.0)
...    child: RenderMouseRegion#dbb21 relayoutBoundary=up12 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...      parentData: <none> (can use size)
...      constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...      size: Size(80.0, 49.0)
...      listeners: enter, exit
...      cursor: SystemMouseCursor(click)
...      child: RenderSemanticsGestureHandler#0f2c6 relayoutBoundary=up13 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...        parentData: <none> (can use size)
...        constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...        size: Size(80.0, 49.0)
...        gestures: tap
...        child: RenderPointerListener#2341c relayoutBoundary=up14 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...          parentData: <none> (can use size)
...          constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...          size: Size(80.0, 49.0)
...          behavior: opaque
...          listeners: down
...  child 9: RenderSemanticsAnnotations#4f4f2 relayoutBoundary=up11 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...    parentData: offset=Offset(0.0, 0.0) (can use size)
...    constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...    size: Size(81.0, 49.0)
...    child: RenderMouseRegion#3abcb relayoutBoundary=up12 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...      parentData: <none> (can use size)
...      constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...      size: Size(81.0, 49.0)
...      listeners: enter, exit
...      cursor: SystemMouseCursor(click)
...      child: RenderSemanticsGestureHandler#5b7a5 relayoutBoundary=up13 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...        parentData: <none> (can use size)
...        constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...        size: Size(81.0, 49.0)
...        gestures: tap
...        child: RenderPointerListener#4e610 relayoutBoundary=up14 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...          parentData: <none> (can use size)
...          constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...          size: Size(81.0, 49.0)
...          behavior: opaque
...          listeners: down
...  child 10: RenderSemanticsAnnotations#599e0 relayoutBoundary=up11 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...    parentData: offset=Offset(0.0, 0.0) (can use size)
...    constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...    size: Size(87.0, 49.0)
...    child: RenderMouseRegion#a6f82 relayoutBoundary=up12 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...      parentData: <none> (can use size)
...      constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...      size: Size(87.0, 49.0)
...      listeners: enter, exit
...      cursor: SystemMouseCursor(click)
...      child: RenderSemanticsGestureHandler#9ef20 relayoutBoundary=up13 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...        parentData: <none> (can use size)
...        constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...        size: Size(87.0, 49.0)
...        gestures: tap
...        child: RenderPointerListener#9ef0a relayoutBoundary=up14 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...          parentData: <none> (can use size)
...          constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...          size: Size(87.0, 49.0)
...          behavior: opaque
...          listeners: down
...  child 11: RenderSemanticsAnnotations#d5ab0 relayoutBoundary=up11 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...    parentData: offset=Offset(0.0, 0.0) (can use size)
...    constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...    size: Size(85.0, 49.0)
...    child: RenderMouseRegion#441e9 relayoutBoundary=up12 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...      parentData: <none> (can use size)
...      constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...      size: Size(85.0, 49.0)
...      listeners: enter, exit
...      cursor: SystemMouseCursor(click)
...      child: RenderSemanticsGestureHandler#0c16e relayoutBoundary=up13 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...        parentData: <none> (can use size)
...        constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...        size: Size(85.0, 49.0)
...        gestures: tap
...        child: RenderPointerListener#e7c7b relayoutBoundary=up14 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...          parentData: <none> (can use size)
...          constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...          size: Size(85.0, 49.0)
...          behavior: opaque
...          listeners: down
...  child 12: RenderSemanticsAnnotations#aa3ca relayoutBoundary=up11 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...    parentData: offset=Offset(0.0, 0.0) (can use size)
...    constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...    size: Size(87.0, 49.0)
...    child: RenderMouseRegion#4f9c8 relayoutBoundary=up12 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...      parentData: <none> (can use size)
...      constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...      size: Size(87.0, 49.0)
...      listeners: enter, exit
...      cursor: SystemMouseCursor(click)
...      child: RenderSemanticsGestureHandler#6bf26 relayoutBoundary=up13 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...        parentData: <none> (can use size)
...        constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...        size: Size(87.0, 49.0)
...        gestures: tap
...        child: RenderPointerListener#b7e2d relayoutBoundary=up14 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...          parentData: <none> (can use size)
...          constraints: BoxConstraints(0.0<=w<=390.0, 0.0<=h<=Infinity)
...          size: Size(87.0, 49.0)
...          behavior: opaque
...          listeners: down
════════════════════════════════════════════════════════════════════════════════════════════════════

════════ Exception caught by rendering library ═════════════════════════════════════════════════════
RenderTransform does not meet its constraints.
The relevant error-causing widget was: 
  InteractiveViewer file:///Users/jihochoi/Documents/outsource/spinor%20media/projects/kinshealthcare_flutter/lib/components/graph_view/family_graph_view.dart:77:13
════════════════════════════════════════════════════════════════════════════════════════════════════

my flutter doctor is below and trying to run it on ios simulator

[✓] Flutter (Channel stable, 1.22.4, on Mac OS X 10.15.7 19H114 darwin-x64, locale en-KR)
 
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[✓] Xcode - develop for iOS and macOS (Xcode 12.3)
[!] Android Studio (version 4.1)
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
[✓] IntelliJ IDEA Ultimate Edition (version 2020.2.1)
[!] VS Code (version 1.49.0)
    ✗ Flutter extension not installed; install from
      https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter
[✓] Connected device (1 available)

1.0.0.-nullsafety - Keys aren't being recorded

Take:

Node getNode(ContentGraphDto content) {
return Node(
InkWell(
child: Container(
height: 150,
width: 250,
child: Text(content.name),
),
),
key: ObjectKey(content),
);
}

when inspecting the result of this function, you'll note that the key is ValueKey with a number in it and not the objectkey that was passed.

Handle disconnected nodes

Is there a way to handle for nodes that aren't connected to the main branch? I guess I'm asking whether it is possible to show two disconnected networks in the same interactive viewer. Currently if not all nodes are connected they are drawn on top of each other.

mouse_tracker.dart error and complete freeze

I am using the graph view inside one of my views and struggle getting it to work without freezing.
I get a massive load of errors while the mouse is over the graph:

════════ Exception caught by scheduler library ═════════════════════════════════
'package:flutter/src/rendering/mouse_tracker.dart': Failed assertion: line 201 pos 12: '!_debugDuringDeviceUpdate': is not true.
════════════════════════════════════════════════════════════════════════════════
[ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: 'package:flutter/src/rendering/mouse_tracker.dart': Failed assertion: line 201 pos 12: '!_debugDuringDeviceUpdate': is not true.
#0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:46:39)
#1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:36:5)
#2      MouseTracker._deviceUpdatePhase
package:flutter/…/rendering/mouse_tracker.dart:201
#3      MouseTracker.updateWithEvent.<anonymous closure>
package:flutter/…/rendering/mouse_tracker.dart:309
#4      MouseTracker._monitorMouseConnection
package:flutter/…/rendering/mouse_tracker.dart:190

My code for the view:

@override
  Widget build(BuildContext context) {
    return ViewModelBuilder<TechTreeViewModel>.reactive(
      builder: (context, model, snapshot) => ScaffoldBase(
        body: 

// I just tried to see if this is caused by unbound sizes... its not
SizedBox(
          height: 400,
          width: 400,
          child: InteractiveViewer(
            constrained: false,
            scaleEnabled: false,
            maxScale: 1,
            minScale: 1,
            boundaryMargin: EdgeInsets.all(100),
            child: GraphView(
              algorithm: BuchheimWalkerAlgorithm(
                model.builder,
                TreeEdgeRenderer(model.builder),
              ),
              graph: model.graph,
              paint: Paint()
                ..color = Colors.green
                ..strokeWidth = 1
                ..style = PaintingStyle.stroke,
              builder: (Node node) {
                if (node.key.value is TechTreeConditionNode) {
                  return conditionNode(node.key.value);
                } else if (node.key.value is TechTreeTechnologyNode) {
                  return rootNode(node.key.value);
                }

                return Container(
                  width: 100,
                  height: 100,
                );
              },
            ),
          ),
        ),
      ),
    );
  }

  

  Widget rootNode(TechTreeTechnologyNode nodeModel) {
    return InkWell(
      child: Container(
        height: 100,
        width: 200,
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            Text(nodeModel.technology.name),
          ],
        ),
      ),
    );
  }

And inside my ViewModel:

  Graph graph;
  BuchheimWalkerConfiguration builder;

  TechTreeViewModel( ) {
    graph = Graph()..isTree = true;
    builder = new BuchheimWalkerConfiguration();
  }

I tried to find out, what is causing this issue, but haven't found anything. If you need more info, please let me know

Size Issue with SugiyamaAlgorithm

There seems to be a positioning / size issue with the SugiyamaAlgorithm.

The setup looks like:

final _graph = Graph()..isTree = true

GraphView(
                graph: _graph,
                algorithm: SugiyamaAlgorithm(SugiyamaConfiguration()
    ..nodeSeparation = 40
    ..levelSeparation = 50),
                builder: (node) {
                //_buildEdge returns a random value between 100 and 300 
                  return _buildEdge(payload!);
                },
              ),

Check out the screenshot.

Bildschirmfoto 2021-08-04 um 16 03 11

Do you need any additional information from my side?

Graphs Larger Than Container Size Throw Exception

When you try to display a graph whose nominal size is larger than the available display size, an exception gets thrown due to an assertion failure ("A renderflex overflowed by x pixels ..."), and Flutter shows a border with yellow/black diagonal stripes on the offending side. Enlarging the window gets rid of it; shrinking the window gets it back. I haven't found an easy workaround yet.

Adjustment for Phone

Hi,

Currently I'm trying to graph a Top to Bottom diagram. I'm having trouble to make the nodes visible on a phone, they are somewhere else placed on the screen. The picture is the example of TreeViewPage without changes

Phone: Galaxy S10
Android: 10

Phone

On the contrary on a Android Tablet works just fine with Android 9

Tablet

What I need to change/configure to fix the Phone view?

Regards,
William

Paint is not always complete

I noticed with some graphs when you zoom you lose part of the painting:

This is with graphview: ^0.6.5
Flutter (Channel beta, 1.24.0-10.2.pre, on Microsoft Windows [Version 10.0.19041.685], locale en-US)

Screenshot (231)

Screenshot (230)

Upgrade to 1.26.0-17.8.pre

Since upgrading to beta 1.26.0-17.8.pre this last weekend I've noticed, not always, and some what randomly the following error in the terminal when calling GraphView with the SugiyamaAlgorithm. It doesn't seem to effect the running app:

======== Exception caught by rendering library =====================================================
The following IndexError was thrown during performLayout():
RangeError (index): Index out of range: index should be less than 1: 1

The relevant error-causing widget was:
GraphView$ file:///C:/source/AndroidStudioProjects/orpheus/lib/screens/parking_lot/forced_direction_view.dart:65:14
When the exception was thrown, this was the stack:
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/internal/js_dev_runtime/private/ddc_runtime/errors.dart 236:49 throw
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/_internal/js_dev_runtime/private/js_array.dart 581:7 _get]
packages/graphview/layered/SugiyamaAlgorithm.dart 564:49 verticalAlignment
packages/graphview/layered/SugiyamaAlgorithm.dart 375:9 assignX
packages/graphview/layered/SugiyamaAlgorithm.dart 329:5 coordinateAssignment
...
The following RenderObject was being processed when the exception was fired: RenderCustomLayoutBox#68954 relayoutBoundary=up2 NEEDS-LAYOUT NEEDS-PAINT
... parentData: (can use size)
... constraints: BoxConstraints(unconstrained)
... size: Size(1125.0, 232.5)
... graph: Instance of 'Graph'
... algorithm: Instance of 'SugiyamaAlgorithm'
... paint: Instance of 'CkPaint'
RenderObject: RenderCustomLayoutBox#68954 relayoutBoundary=up2 NEEDS-LAYOUT NEEDS-PAINT
parentData: (can use size)
constraints: BoxConstraints(unconstrained)
size: Size(1125.0, 232.5)
graph: Instance of 'Graph'
algorithm: Instance of 'SugiyamaAlgorithm'
paint: Instance of 'CkPaint'
... child 1: RenderSemanticsGestureHandler#6614d relayoutBoundary=up3
... parentData: offset=Offset(10.0, 62.5) (can use size)
... constraints: BoxConstraints(unconstrained)
... size: Size(30.0, 30.0)
... gestures: tap
... child: RenderPointerListener#dd099 relayoutBoundary=up4
... parentData: (can use size)
... constraints: BoxConstraints(unconstrained)
... size: Size(30.0, 30.0)
... behavior: deferToChild
... listeners: down
... child: RenderConstrainedBox#3bbfd relayoutBoundary=up5
... parentData: (can use size)
... constraints: BoxConstraints(unconstrained)
... size: Size(30.0, 30.0)
... additionalConstraints: BoxConstraints(w=30.0, h=30.0)
... child: RenderDecoratedBox#1958c
... parentData: (can use size)
... constraints: BoxConstraints(w=30.0, h=30.0)
... size: Size(30.0, 30.0)
... decoration: BoxDecoration
... color: Color(0xffffffff)
... image: DecorationImage(AssetImage(bundle: null, name: "assets/process.png"), BoxFit.fill, Alignment.center, scale: 1)
... border: Border.all(BorderSide(Color(0xff000000), 1.0, BorderStyle.solid))
... borderRadius: BorderRadius.circular(5.0)
... boxShadow: BoxShadow(Color(0xff000000), Offset(4.0, 4.0), 4.0, 0.0)
... configuration: ImageConfiguration(bundle: PlatformAssetBundle#589c7(), devicePixelRatio: 1.0, locale: en_US, textDirection: TextDirection.ltr, platform: windows)
... child 2: RenderSemanticsGestureHandler#a7315 relayoutBoundary=up3
... parentData: offset=Offset(115.0, 62.5) (can use size)
... constraints: BoxConstraints(unconstrained)
... size: Size(30.0, 30.0)
... gestures: tap
... child: RenderPointerListener#f1907 relayoutBoundary=up4
... parentData: (can use size)
... constraints: BoxConstraints(unconstrained)
... size: Size(30.0, 30.0)
... behavior: deferToChild
... listeners: down
... child: RenderConstrainedBox#3bd74 relayoutBoundary=up5
... parentData: (can use size)
... constraints: BoxConstraints(unconstrained)
... size: Size(30.0, 30.0)
... additionalConstraints: BoxConstraints(w=30.0, h=30.0)
... child: RenderDecoratedBox#a9207
... parentData: (can use size)
... constraints: BoxConstraints(w=30.0, h=30.0)
... size: Size(30.0, 30.0)
... decoration: BoxDecoration
... color: Color(0xffffffff)
... image: DecorationImage(AssetImage(bundle: null, name: "assets/process.png"), BoxFit.fill, Alignment.center, scale: 1)
... border: Border.all(BorderSide(Color(0xff000000), 1.0, BorderStyle.solid))
... borderRadius: BorderRadius.circular(5.0)
... boxShadow: BoxShadow(Color(0xff000000), Offset(4.0, 4.0), 4.0, 0.0)
... configuration: ImageConfiguration(bundle: PlatformAssetBundle#589c7(), devicePixelRatio: 1.0, locale: en_US, textDirection: TextDirection.ltr, platform: windows)
... child 3: RenderSemanticsGestureHandler#7319b relayoutBoundary=up3
... parentData: offset=Offset(220.0, 62.5) (can use size)
... constraints: BoxConstraints(unconstrained)
... size: Size(30.0, 30.0)
... gestures: tap
... child: RenderPointerListener#42246 relayoutBoundary=up4
... parentData: (can use size)
... constraints: BoxConstraints(unconstrained)
... size: Size(30.0, 30.0)
... behavior: deferToChild
... listeners: down
... child: RenderConstrainedBox#5b69f relayoutBoundary=up5
... parentData: (can use size)
... constraints: BoxConstraints(unconstrained)
... size: Size(30.0, 30.0)
... additionalConstraints: BoxConstraints(w=30.0, h=30.0)
... child: RenderDecoratedBox#c1f06
... parentData: (can use size)
... constraints: BoxConstraints(w=30.0, h=30.0)
... size: Size(30.0, 30.0)
... decoration: BoxDecoration
... color: MaterialColor(primary value: Color(0xffffeb3b))
... image: DecorationImage(AssetImage(bundle: null, name: "assets/process.png"), BoxFit.fill, Alignment.center, scale: 1)
... border: Border.all(BorderSide(Color(0xff000000), 1.0, BorderStyle.solid))
... borderRadius: BorderRadius.circular(5.0)
... boxShadow: BoxShadow(MaterialColor(primary value: Color(0xffffeb3b)), Offset(10.0, 10.0), 10.0, 0.0)
... configuration: ImageConfiguration(bundle: PlatformAssetBundle#589c7(), devicePixelRatio: 1.0, locale: en_US, textDirection: TextDirection.ltr, platform: windows)

Improve performance of `SugiyamaAlgorithm`

Thanks again for taking the time to hop on a call with me and discuss potential performance fixes.
I reduced the iterations in nodeOrdering to 3 and then I did some research to see what else takes the most time.

image

As you can see crossingb and assignX takes most of the time.
I checked the graphview android lib to see if they made any performance adjustments, but there are none.

I tried to optimize both methods but I don't have enough knowledge about the algorithm so I need some help here.
@nabil6391 It would be a help if you could explain what the implementation exactly does and how a fix could look like so I can help.

how can i solve this EXCEPTION

══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following assertion was thrown while applying parent data.:
Incorrect use of ParentDataWidget.
The ParentDataWidget Expanded(flex: 1) wants to apply ParentData of type FlexParentData to a
RenderObject, which has been set up to accept ParentData of incompatible type ParentData.
Usually, this means that the Expanded widget has the wrong ancestor RenderObjectWidget. Typically,
Expanded widgets are placed directly inside Flex widgets.
The offending Expanded is currently placed inside a ConstrainedBox widget.
The ownership chain for the RenderObject that received the incompatible parent data was:
Listener-[GlobalKey#d4799] ← InteractiveViewer ← Expanded ← Consumer ←
_InheritedProviderScope ← ChangeNotifierProvider ←
ProviderWidget ← ConstrainedBox ← Container ← MediaQuery ← ⋯

                When the exception was thrown, this was the stack:
                #0      RenderObjectElement._updateParentData.<anonymous closure> (package:flutter/src/widgets/framework.dart:5626:11)
                #1      RenderObjectElement._updateParentData (package:flutter/src/widgets/framework.dart:5642:6)
                #2      RenderObjectElement.attachRenderObject (package:flutter/src/widgets/framework.dart:5664:7)
                #3      RenderObjectElement.mount (package:flutter/src/widgets/framework.dart:5357:5)
                #4      SingleChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:5973:11)
                ...     Normal element mounting (21 frames)
                #25     SingleChildWidgetElementMixin.mount (package:nested/nested.dart:222:11)
                #26     Element.inflateWidget (package:flutter/src/widgets/framework.dart:3541:14)
                #27     Element.updateChild (package:flutter/src/widgets/framework.dart:3306:18)
                #28     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4520:16)
                #29     _InheritedProviderScopeElement.performRebuild (package:provider/src/inherited_provider.dart:426:11)
                #30     Element.rebuild (package:flutter/src/widgets/framework.dart:4189:5)
                #31     ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4474:5)
                #32     ComponentElement.mount (package:flutter/src/widgets/framework.dart:4469:5)
                ...     Normal element mounting (7 frames)
                #39     SingleChildWidgetElementMixin.mount (package:nested/nested.dart:222:11)
                ...     Normal element mounting (59 frames)
                #98     Element.inflateWidget (package:flutter/src/widgets/framework.dart:3541:14)
                #99     MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:6094:32)
                ...     Normal element mounting (7 frames)
                #106    Element.inflateWidget (package:flutter/src/widgets/framework.dart:3541:14)
                #107    MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:6094:32)
                ...     Normal element mounting (19 frames)
                #126    Element.inflateWidget (package:flutter/src/widgets/framework.dart:3541:14)
                #127    MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:6094:32)
                ...     Normal element mounting (91 frames)
                #218    Element.inflateWidget (package:flutter/src/widgets/framework.dart:3541:14)
                #219    MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:6094:32)
                ...     Normal element mounting (244 frames)
                #463    Element.inflateWidget (package:flutter/src/widgets/framework.dart:3541:14)
                #464    MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:6094:32)
                ...     Normal element mounting (320 frames)
                #784    Element.inflateWidget (package:flutter/src/widgets/framework.dart:3541:14)
                #785    Element.updateChild (package:flutter/src/widgets/framework.dart:3306:18)
                #786    _LayoutBuilderElement._layout.<anonymous closure> (package:flutter/src/widgets/layout_builder.dart:136:18)
                #787    BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2647:19)
                #788    _LayoutBuilderElement._layout (package:flutter/src/widgets/layout_builder.dart:118:12)
                #789    RenderObject.invokeLayoutCallback.<anonymous closure> (package:flutter/src/rendering/object.dart:1894:59)
                #790    PipelineOwner._enableMutationsToDirtySubtrees (package:flutter/src/rendering/object.dart:915:15)
                #791    RenderObject.invokeLayoutCallback (package:flutter/src/rendering/object.dart:1894:14)
                #792    RenderConstrainedLayoutBuilder.rebuildIfNecessary (package:flutter/src/widgets/layout_builder.dart:225:7)
                #793    _RenderLayoutBuilder.performLayout (package:flutter/src/widgets/layout_builder.dart:360:5)
                #794    RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
                #795    _RenderLayoutBuilder.performLayout (package:flutter/src/widgets/layout_builder.dart:362:14)
                #796    RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
                #797    RenderView.performLayout (package:flutter/src/rendering/view.dart:153:14)
                #798    RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:1641:7)
                #799    PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:884:18)
                #800    RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:453:19)
                #801    WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:874:13)
                #802    RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:319:5)
                #803    SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1144:15)
                #804    SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1082:9)
                #805    SchedulerBinding.scheduleWarmUpFrame.<anonymous closure> (package:flutter/src/scheduler/binding.dart:865:7)
                (elided 11 frames from class _RawReceivePortImpl, class _Timer, dart:async, and dart:async-patch)
                ════════════════════════════════════════════════════════════════════════════════════════════════════

Is it possible to call removeNode on a node with successors?

Hi when I try to call graph.removeNode() on a node with successors I get the following error:

The following JSNoSuchMethodError was thrown during performLayout():
NoSuchMethodError: invalid member on null: 'depth'

Should graph.removeNode() be able to handle nodes with successors? Or am I supposed to call removeNode() on all the deepest nodes first and work my way up from there?

I am using the TreeEdgeRenderer inside my GraphView based on the pub.dev example: https://pub.dev/packages/graphview#usage

Here is my code in case this is a bug instead of improper usage of removeNode() on my part.

This is my initState method:

  @override
  void initState() {
    super.initState();
    myGraph = Graph();

    final organisation = Organisation();
    final firstNode = Node(
      OrganisationCard(
        organisation,
        addNodeToOrg: (Node node, Organisation org) {
          myGraph.addEdge(
            myGraph.getNodeUsingKey(ObjectKey(org)),
            node,
          );
          setState(() {});
        },
        hideNodes: (List<Node> nodes) {
          myGraph.removeNodes(nodes);
          setState(() {});
        },
        expanded: true,
      ),
      key: ObjectKey(organisation),
    );

    myGraph.addNode(firstNode);

    builder = BuchheimWalkerConfiguration()
      ..siblingSeparation = (50)
      ..levelSeparation = (50)
      ..subtreeSeparation = (50)
      ..orientation = BuchheimWalkerConfiguration.DEFAULT_ORIENTATION;
  }

This is my Build method:

  @override
  Widget build(BuildContext context) {
    return Screen(
      title: Text('Organisations'),
      body: Center(
        child: SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          child: SingleChildScrollView(
            child: Padding(
              padding: const EdgeInsets.only(
                right: 16.0,
                bottom: 16.0,
              ),
              child: GraphView(
                algorithm: BuchheimWalkerAlgorithm(
                  builder,
                  TreeEdgeRenderer(builder),
                ),
                graph: myGraph,
                paint: Paint()
                  ..color = Theme.of(context).primaryIconTheme.color
                  ..strokeWidth = 1
                  ..style = PaintingStyle.fill,
              ),
            ),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.search_rounded),
        onPressed: () {},
      ),
    );
  }

And this is my OrganisationCard widget that's used inside the Nodes:

class OrganisationCard extends StatefulWidget {
  final Organisation organisation;

  final void Function(Node node, Organisation org) addNodeToOrg;
  final void Function(List<Node> nodes) hideNodes;

  final bool expanded;

  OrganisationCard(
    this.organisation, {
    @required this.addNodeToOrg,
    @required this.hideNodes,
    this.expanded = false,
  })  : assert(addNodeToOrg != null),
        assert(hideNodes != null);

  @override
  State<StatefulWidget> createState() {
    return OrganisationCardState();
  }
}

enum popupOptions { expand, add }

class OrganisationCardState extends State<OrganisationCard> {
  List<Organisation> subOrganisations;

  final subOrganisationNodes = <Node>[];

  bool expanded;

  bool hasBeenExpanded = false;

  Node organisationToNode(Organisation organisation) => Node(
        OrganisationCard(
          organisation,
          addNodeToOrg: widget.addNodeToOrg,
          hideNodes: widget.hideNodes,
        ),
        key: ObjectKey(organisation),
      );

  void expand() {
    if (!hasBeenExpanded) {
      subOrganisationNodes.addAll(
        subOrganisations.map<Node>(organisationToNode).toList(),
      );
      hasBeenExpanded = true;
    }

    subOrganisationNodes.forEach(
      (node) => widget.addNodeToOrg(node, widget.organisation),
    );
  }

  @override
  void initState() {
    super.initState();
    expanded = widget.expanded;

    //TODO: This class should either use the subOrganisations list from
    // widget.organisation or generate subOrganisations based on data from
    // widget.organisation once the `Organisation` class has been implemented
    subOrganisations = [
      Organisation(),
      Organisation(),
      Organisation(),
    ];

    if (expanded) {
      WidgetsBinding.instance.addPostFrameCallback((_) => expand());
    }
  }

  @override
  Widget build(BuildContext context) {
    const double maxTextWidth = 150;
    const double maxTextHeight = 60;

    return RoundedContainer(
      child: Material(
        color: Colors.transparent,
        child: InkWell(
          customBorder: RoundedRectangleBorder(
            borderRadius: BorderRadius.all(
              Radius.circular(10),
            ),
          ),
          child: Padding(
            padding: EdgeInsets.only(left: 5),
            child: Row(
              children: [
                ConstrainedBox(
                  constraints: BoxConstraints(
                    maxWidth: maxTextWidth,
                    maxHeight: maxTextHeight,
                  ),
                  child: Container(
                    width: maxTextWidth,
                    height: maxTextHeight,
                    child: Center(
                      child: Text(
                        'Mobiléa',
                        style: Theme.of(context).textTheme.subtitle2,
                        softWrap: true,
                      ),
                    ),
                  ),
                ),
                PopupMenuButton(
                  icon: Icon(
                    Icons.more_vert_rounded,
                    size: 18,
                  ),
                  onSelected: (option) {
                    when(option, {
                      popupOptions.expand: () {
                        if (expanded) {
                          widget.hideNodes(subOrganisationNodes);
                        } else {
                          expand();
                        }
                        setState(() => expanded = !expanded);
                      },
                      popupOptions.add: () {
                        //TODO: Show dialog that gets the user input thats required
                        // to construct an `Organisation` instance once
                        // `Organisation` has been implemented.
                        //TODO: Call some bloc to post newOrganisation to backend.
                        final newOrganisation = Organisation();
                        final node = organisationToNode(newOrganisation);

                        subOrganisations.add(newOrganisation);
                        subOrganisationNodes.add(node);

                        if (expanded) {
                          widget.addNodeToOrg(
                            node,
                            widget.organisation,
                          );
                        }
                      },
                    });
                  },
                  itemBuilder: (context) => <PopupMenuEntry<popupOptions>>[
                    PopupMenuItem<popupOptions>(
                      value: popupOptions.expand,
                      child: Text(expanded ? 'Shrink' : 'Expand'),
                    ),
                    PopupMenuItem<popupOptions>(
                      value: popupOptions.add,
                      child: Text('Add'),
                    ),
                  ],
                ),
              ],
            ),
          ),
          onTap: () => Navigator.of(context).pushNamed('/organisation'),
        ),
      ),
    );
  }
}

Graph so big nothing is visible unless zooming out, centering around `Layout.setFocusedNode` could be a fix

Made a video of how the graph looks: https://youtu.be/-oLNpC1voJs

You can see that the top left corner of the graph view (where the graph view seems to always start) is empty, often with every nodes and edge off-screen.

I had to zoom out alot to be able to start seeing some graph data. If we could "focus" on the focusedNode we'd guarantee that this wouldn't happen.

Any other suggestions on making this work so it doesn't look to the user as though there is no graph?

Add option to center `InteractiveViewer` at a node

We are really enjoying your package and it works great!

It would be nice to have to option to center the InteractiveViewer to a specific node.
TransformationController already provides the option to move the viewer around but we still need to know the position of a node to be able to generate the correct Matrix4.

Do you have an idea how this can be done?

Possible error in NodeCluster.offset() method in NodeCluster class.

It looks to me as if there is an error in setting the offset of the rect property in the offset() method in the NodeCluster class.

The code currently reads

    nodes.forEach((node) {
      node.position = (node.position + Offset(xDiff, yDiff));
    });

    rect.translate(xDiff, yDiff);
  }

This, I suggest, is incorrect as the translate() method of the Dart Rect object returns a new Rect object, not modifying the original Rect.

Thus, I believe the code should read


    nodes.forEach((node) {
      node.position = (node.position + Offset(xDiff, yDiff));
    });

    rect = rect.translate(xDiff, yDiff);
    
  }

The jank

Hey buddy,when my data list more than 100,i find a jank frame. BuchheimWalkerAlgorithm run() takes 700+ ms, how can i solve this problem, thx

jank

Builder with ID

Node* getNodeUsingId(int* id) expects an int id vs (new) Node* Node.Id(dynamic id) expects dynamic. Making it dynamic would let us use even a string as an id for the node. Can you please fix this discrepancy? Also, when will this version be available in null safety.

Issue with SugiyamaAlgorithm when introducing circular dependency between nodes

Not sure if this is related to the circular dependency, but I've only seen this issue occur in such scenarios. I had two nodes FTA and EFTA, with FTA pointing to EFTA. When I added a new node % Auto Accept Bookings, the edge was not rendered correctly (as can be seen in the image below).

No exceptions or errors were thrown

image

import issues

First of thank you very much for putting effort and sharing with us. I may not be the best tester but I found some issues with importing stuff over. I have got the dependency of graphview: ^0.6.7

so there are some screenshots may help you to see what I mean. all of them giving error except one.

btw this code straight from your example section on pub.dev
1
2
3

create family tree view using BuchheimWalkerAlgorithm?

Hi Brother!,
I'm creating a family tree using BuchheimWalkerAlgorithm, it is possible to get this type of output with separate nodes. Like father mother on the different node but nearest position. Connect from 2 nodes to 1 node.
Screenshot_1630301227

Help me to find out.
Thank You!

Length of edges

Hello,
how I can resize the length of edges? Graphs don't enter into the web page

Do not accept Strings as keys for nodes and edges

My data is not int-based. Because some of real data have leading zero. The graphview crush on next data:

    var json = {
      "nodes": [
        {"id": "01", "label": 'circle'},
        {"id": "02", "label": 'ellipse'},
        {"id": "03", "label": 'database'},
        {"id": "04", "label": 'box'},
        {"id": "05", "label": 'diamond'},
        {"id": "06", "label": 'dot'},
        {"id": "07", "label": 'square'},
        {"id": "08", "label": 'triangle'},
      ],
      "edges": [
        {"from": "01", "to": "02"},
        {"from": "02", "to": "03"},
        {"from": "02", "to": "04"},
        {"from": "02", "to": "05"},
        {"from": "05", "to": "06"},
        {"from": "05", "to": "07"},
        {"from": "06", "to": "08"}
      ]
    };

error:

    The following TypeErrorImpl was thrown building GraphView$(dirty, state: _GraphViewState#a4f6c):
Expected a value of type 'int', but got one of type 'String'

I tried change to:

var a = node.key!.value.toString();

But got error:

Node{position: Offset(0.0, 0.0), key: [<1>], _size: Size(77.0, 48.0)} is not connected to primary ancestor
Node{position: Offset(0.0, 0.0), key: [<2>], _size: Size(77.0, 48.0)} is not connected to primary ancestor

Nodes and edges not rendering correctly when generated dynamically.

Hello, thanks for this great package.
Just a little query though. The package works great when the nodes and edges are created statically, but the edges are created one over another when the nodes and edges are created inside a for loop. Maybe I am missing something, can you please demonstrate how to create the nodes and edges dynamically and update the documentation. Will be of so much help. Thanks.

How to center tree on the canvas ?

I am using this plugin to display Family tree. And it does not center when displayed.

I need to center horizontally like this on small tree and big family tree when first displayed :

How to do it ?

Uneven rendering using the Sugiyama Configuration

I was trying to make a graph using the sugiyama configuration, but in many cases, the result was not as I expected it to be. I was expecting a layer-wise graph with even layering as shown below.

image

But, instead, i get a layout as
image
It isnt that prominent for larger graphs, but it is still there. Maybe its the way the graph is rendered, or its dependent on the algorithm, i'm not sure.
Was hoping if you know any workaround or fix, would be great!
Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.