Skip to content

Commit

Permalink
adding node hit-testing and improving simulation values
Browse files Browse the repository at this point in the history
  • Loading branch information
rlch committed Jul 1, 2021
1 parent b948d1c commit 99968b1
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 27 deletions.
79 changes: 57 additions & 22 deletions example/lib/screens/canvas.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:math';

import 'package:example/widgets/node_hit_test.dart';
import 'package:example/widgets/simulation_canvas.dart';
import 'package:example/widgets/simulation_canvas_object.dart';
import 'package:flutter/material.dart';
Expand All @@ -24,12 +25,20 @@ class _CanvasScreenState extends State<CanvasScreen>
void didChangeDependencies() {
super.didChangeDependencies();

final nodes = List.generate(100, (index) => f.Node());
final size = MediaQuery.of(context).size;

final nodes = List.generate(
100,
(index) => f.Node(
x: size.width / 2,
y: size.height / 2,
),
);
final r = Random();
edges = [
for (final n in nodes)
if (r.nextDouble() < 0.6) ...[
for (int i = 0; i < (r.nextDouble() * 5).toInt(); i++)
if (r.nextDouble() < 0.8) ...[
for (int i = 0; i < (r.nextDouble() * 3).toInt(); i++)
f.Edge(
source: n,
target: nodes[(nodes.length * r.nextDouble()).toInt()],
Expand All @@ -38,14 +47,18 @@ class _CanvasScreenState extends State<CanvasScreen>
];
simulation = f.ForceSimulation()
..nodes = nodes
..setForce('collide', f.Collide(radius: 5))
// ..setForce('collide', f.Collide(radius: 10))
// ..setForce('radial', f.Radial(radius: 400))
..setForce('manyBody', f.ManyBody())
// ..setForce(
// 'center', f.Center(size.width / 2, size.height / 2, strength: 0.5))
..setForce(
'edges',
f.Edges(edges: edges, distance: 15),
)
..alpha = 1
..tick(10);
..setForce('x', f.XPositioning(x: size.width / 2))
..setForce('y', f.YPositioning(y: size.height / 2))
..alpha = 1;

_ticker = this.createTicker((_) {
i++;
Expand All @@ -67,25 +80,47 @@ class _CanvasScreenState extends State<CanvasScreen>

@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;

return Scaffold(
body: Center(
child: SimulationCanvas(
children: [
for (final node in simulation.nodes)
if (!node.isNaN)
SimulationCanvasObject(
node: node,
edges: [...edges.where((e) => e.source == node)],
child: Container(
width: 10,
height: 10,
decoration: BoxDecoration(
color: Colors.black,
shape: BoxShape.circle,
body: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: ConstrainedBox(
constraints: BoxConstraints.tight(size),
child: SimulationCanvas(
children: [
for (final node in simulation.nodes)
if (!node.isNaN)
SimulationCanvasObject(
constraints: BoxConstraints.tight(Size(10, 10)),
node: node,
edges: [...edges.where((e) => e.source == node)],
child: NodeHitTester(
node,
onDragUpdate: (update) {
node
..fx = update.globalPosition.dx
..fy = update.globalPosition.dy;
simulation..alpha = 1;
},
onDragEnd: (_) {
node
..fx = null
..fy = null;
},
child: Container(
width: 10,
height: 10,
decoration: BoxDecoration(
color: Colors.black,
shape: BoxShape.circle,
),
),
),
),
),
],
],
),
),
),
);
Expand Down
107 changes: 107 additions & 0 deletions example/lib/widgets/node_hit_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import 'package:d3_force_flutter/d3_force_flutter.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

class NodeHitTester extends SingleChildRenderObjectWidget {
const NodeHitTester(
this.node, {
required Widget child,
required this.onDragUpdate,
required this.onDragEnd,
Key? key,
}) : super(key: key, child: child);

final Node node;

final GestureDragUpdateCallback onDragUpdate;
final GestureDragEndCallback onDragEnd;

@override
RenderObject createRenderObject(BuildContext context) {
return RenderNodeHitTester(
node,
onDragUpdate: onDragUpdate,
onDragEnd: onDragEnd,
);
}

@override
void updateRenderObject(
BuildContext context, covariant RenderNodeHitTester renderObject) {
renderObject
..node = node
..onDragUpdate = onDragUpdate
..onDragEnd = onDragEnd;
}
}

class RenderNodeHitTester extends RenderProxyBox {
RenderNodeHitTester(
this._node, {
required GestureDragUpdateCallback onDragUpdate,
required GestureDragEndCallback onDragEnd,
}) : _onDragUpdate = onDragUpdate,
_onDragEnd = onDragEnd;

late final PanGestureRecognizer _panGestureRecognizer;

Node _node;
Node get node => node;
set node(Node node) {
if (node == _node) return;
_node = node;
}

GestureDragUpdateCallback _onDragUpdate;
GestureDragUpdateCallback get onDragUpdate => _onDragUpdate;
set onDragUpdate(GestureDragUpdateCallback onDragUpdate) {
if (_onDragUpdate == onDragUpdate) return;
_panGestureRecognizer.onUpdate = _onDragUpdate = onDragUpdate;
}

GestureDragEndCallback _onDragEnd;
GestureDragEndCallback get onDragEnd => _onDragEnd;
set onDragEnd(GestureDragEndCallback onDragEnd) {
if (_onDragEnd == onDragEnd) return;
_panGestureRecognizer.onEnd = _onDragEnd = onDragEnd;
}

@override
void attach(covariant PipelineOwner owner) {
super.attach(owner);

_panGestureRecognizer = PanGestureRecognizer(debugOwner: this)
..onUpdate = _onDragUpdate
..onEnd = _onDragEnd;
}

@override
void detach() {
_panGestureRecognizer.dispose();
super.detach();
}

@override
bool hitTestSelf(Offset position) {
return size.contains(position);
}

@override
void handleEvent(PointerEvent event, covariant HitTestEntry entry) {
assert(debugHandleEvent(event, entry));

if (event is PointerDownEvent) {
_panGestureRecognizer.addPointer(event);
}
}

@override
void paint(PaintingContext context, Offset offset) {
final canvas = context.canvas;
canvas.save();
context.paintChild(child!, offset);
canvas.restore();
}
}
21 changes: 16 additions & 5 deletions example/lib/widgets/simulation_canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ class SimulationCanvas extends MultiChildRenderObjectWidget {
}

class SimulationCanvasParentData extends ContainerBoxParentData<RenderBox> {
SimulationCanvasParentData({required this.edges});
SimulationCanvasParentData({
required this.edges,
required this.constraints,
});

List<Edge> edges;
BoxConstraints constraints;
}

class RenderSimulationCanvas extends RenderBox
Expand All @@ -30,15 +34,17 @@ class RenderSimulationCanvas extends RenderBox
@override
void setupParentData(covariant RenderObject child) {
if (child.parentData is! SimulationCanvasParentData) {
child.parentData = SimulationCanvasParentData(edges: []);
child.parentData = SimulationCanvasParentData(
edges: [],
constraints: BoxConstraints.tight(Size(0, 0)),
);
}
}

@override
void paint(PaintingContext context, Offset offset) {
final canvas = context.canvas;
canvas.save();
offset += Offset(size.width / 2, size.height / 2);

RenderBox? child = firstChild;

Expand Down Expand Up @@ -83,11 +89,11 @@ class RenderSimulationCanvas extends RenderBox

if (!dry) {
child.layout(
constraints,
childParentData.constraints,
parentUsesSize: true,
);
} else {
child.getDryLayout(constraints);
child.getDryLayout(childParentData.constraints);
}

child = childParentData.nextSibling;
Expand Down Expand Up @@ -146,4 +152,9 @@ class RenderSimulationCanvas extends RenderBox
double? computeDistanceToActualBaseline(TextBaseline baseline) {
return defaultComputeDistanceToFirstActualBaseline(baseline);
}

@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
return defaultHitTestChildren(result, position: position);
}
}
6 changes: 6 additions & 0 deletions example/lib/widgets/simulation_canvas_object.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ class SimulationCanvasObject
required Widget child,
required this.node,
required this.edges,
required this.constraints,
Key? key,
}) : super(child: child, key: key);

final Node node;
final List<Edge> edges;
final BoxConstraints constraints;

@override
void applyParentData(RenderObject renderObject) {
Expand All @@ -26,6 +28,10 @@ class SimulationCanvasObject
parentData.edges = edges;
}

if (parentData.constraints != constraints) {
parentData.constraints = constraints;
}

final targetObject = renderObject.parent;
if (targetObject is RenderObject) {
targetObject.markNeedsLayout();
Expand Down
11 changes: 11 additions & 0 deletions workspace.code-workspace
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
h{
"folders": [
{
"path": "."
},
{
"path": "example"
}
],
"settings": {}
}

0 comments on commit 99968b1

Please sign in to comment.