Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
SkywalkerDarren committed Aug 10, 2023
1 parent f90d11a commit 5600bcb
Showing 1 changed file with 139 additions and 35 deletions.
174 changes: 139 additions & 35 deletions lib/widget/force_directed_graph_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class ForceDirectedGraphWidget<T> extends StatefulWidget {
const ForceDirectedGraphWidget({
super.key,
required this.controller,
this.cachePaintOffset = 0,
this.cachePaintOffset = 50,
required this.nodesBuilder,
required this.edgesBuilder,
this.onDraggingStart,
Expand Down Expand Up @@ -71,6 +71,7 @@ class _ForceDirectedGraphState<T> extends State<ForceDirectedGraphWidget<T>>
ForceDirectedGraphController<T> get _controller => widget.controller;
late Ticker _ticker;
double _scale = 1.0;
Rect? paintBound;

@override
void initState() {
Expand Down Expand Up @@ -112,9 +113,58 @@ class _ForceDirectedGraphState<T> extends State<ForceDirectedGraphWidget<T>>
super.didUpdateWidget(oldWidget);
}

bool isLineIntersectsRect(Offset p1, Offset p2, Rect rect) {
// Rect four corners
Offset topLeft = rect.topLeft;
Offset topRight = rect.topRight;
Offset bottomLeft = rect.bottomLeft;
Offset bottomRight = rect.bottomRight;

// Check if the line segment intersects any of the sides of the Rect
return isLineIntersect(p1, p2, topLeft, topRight) ||
isLineIntersect(p1, p2, topLeft, bottomLeft) ||
isLineIntersect(p1, p2, topRight, bottomRight) ||
isLineIntersect(p1, p2, bottomLeft, bottomRight);
}

bool isLineIntersect(Offset p1, Offset p2, Offset q1, Offset q2) {
double cross1 = crossProduct(p1, p2, q1);
double cross2 = crossProduct(p1, p2, q2);
double cross3 = crossProduct(q1, q2, p1);
double cross4 = crossProduct(q1, q2, p2);

// If the two cross products have different signs,
// then the line segments are on opposite sides of the rectangle,
// so they must intersect.
return (cross1 * cross2 < 0) && (cross3 * cross4 < 0);
}

double crossProduct(Offset a, Offset b, Offset c) {
// Calculate the cross product of vectors AB and AC
double y1 = b.dy - a.dy;
double x1 = b.dx - a.dx;
double y2 = c.dy - a.dy;
double x2 = c.dx - a.dx;

return (x1 * y2) - (x2 * y1);
}

bool inRect(Offset offset, Rect rect) {
return offset.dx >= rect.left &&
offset.dx <= rect.right &&
offset.dy >= rect.top &&
offset.dy <= rect.bottom;
}

@override
Widget build(BuildContext context) {
final nodes = _controller.graph.nodes.map((e) {
final nodes = _controller.graph.nodes.where((element) {
if (paintBound == null) {
return true;
}
final offset = Offset(element.position.x, element.position.y);
return inRect(offset, paintBound!);
}).map((e) {
final child = widget.nodesBuilder(context, e.data);
if (child is NodeWidget) {
assert(child.node == e);
Expand All @@ -126,7 +176,16 @@ class _ForceDirectedGraphState<T> extends State<ForceDirectedGraphWidget<T>>
);
});

final edges = _controller.graph.edges.map((e) {
final edges = _controller.graph.edges.where((element) {
if (paintBound == null) {
return true;
}
final a = Offset(element.a.position.x, element.a.position.y);
final b = Offset(element.b.position.x, element.b.position.y);
return inRect(a, paintBound!) ||
inRect(b, paintBound!) ||
isLineIntersectsRect(a, b, paintBound!);
}).map((e) {
final child =
widget.edgesBuilder(context, e.a.data, e.b.data, e.distance);
if (child is EdgeWidget) {
Expand All @@ -152,24 +211,30 @@ class _ForceDirectedGraphState<T> extends State<ForceDirectedGraphWidget<T>>
.clamp(_controller.minScale, _controller.maxScale);
_controller.scale = scale;
},
child: RepaintBoundary(
child: ClipRect(
child: ForceDirectedGraphBody(
controller: _controller,
cachePaintOffset: widget.cachePaintOffset,
graph: _controller.graph,
scale: _controller.scale,
nodes: nodes,
edges: edges,
onDraggingStart: (data) {
widget.onDraggingStart?.call(data);
},
onDraggingEnd: (data) {
widget.onDraggingEnd?.call(data);
},
onDraggingUpdate: (data) {
widget.onDraggingUpdate?.call(data);
},
child: NotificationListener<PaintBoundChangeNotification>(
onNotification: (notification) {
paintBound = notification.paintBound;
return true;
},
child: RepaintBoundary(
child: ClipRect(
child: ForceDirectedGraphBody(
controller: _controller,
cachePaintOffset: widget.cachePaintOffset,
graph: _controller.graph,
scale: _controller.scale,
nodes: nodes,
edges: edges,
onDraggingStart: (data) {
widget.onDraggingStart?.call(data);
},
onDraggingEnd: (data) {
widget.onDraggingEnd?.call(data);
},
onDraggingUpdate: (data) {
widget.onDraggingUpdate?.call(data);
},
),
),
),
),
Expand Down Expand Up @@ -202,22 +267,26 @@ class ForceDirectedGraphBody extends MultiChildRenderObjectWidget {
@override
ForceDirectedGraphRenderObject createRenderObject(BuildContext context) {
return ForceDirectedGraphRenderObject(
graph: graph,
scale: scale,
cachePaintOffset: cachePaintOffset,
controller: controller,
onDraggingUpdate: onDraggingUpdate,
onDraggingStart: onDraggingStart,
onDraggingEnd: onDraggingEnd);
graph: graph,
scale: scale,
cachePaintOffset: cachePaintOffset,
controller: controller,
onDraggingUpdate: onDraggingUpdate,
onDraggingStart: onDraggingStart,
onDraggingEnd: onDraggingEnd,
onPaintBoundChange: (Rect rect) {
PaintBoundChangeNotification(rect).dispatch(context);
},
);
}

@override
void updateRenderObject(
BuildContext context, ForceDirectedGraphRenderObject renderObject) {
renderObject
..graph = graph
.._cachePaintOffset = cachePaintOffset
.._scale = scale;
..cachePaintOffset = cachePaintOffset
..scale = scale;
}
}

Expand All @@ -234,6 +303,7 @@ class ForceDirectedGraphRenderObject extends RenderBox
required this.onDraggingUpdate,
required this.onDraggingStart,
required this.onDraggingEnd,
required this.onPaintBoundChange,
}) : _graph = graph,
_cachePaintOffset = cachePaintOffset,
_scale = scale;
Expand All @@ -243,6 +313,7 @@ class ForceDirectedGraphRenderObject extends RenderBox
final void Function(dynamic data) onDraggingStart;
final void Function(dynamic data) onDraggingUpdate;
final void Function(dynamic data) onDraggingEnd;
final void Function(Rect bound) onPaintBoundChange;

ForceDirectedGraph _graph;

Expand All @@ -251,17 +322,39 @@ class ForceDirectedGraphRenderObject extends RenderBox
markNeedsPaint();
}

Rect _canPaintBound = Rect.zero;

set canPaintBound(Rect value) {
if (value == _canPaintBound) {
return;
}
_canPaintBound = value;
onPaintBoundChange(value);
}

Rect get canPaintBound => _canPaintBound;

double _scale;

set scale(double value) {
_scale = value;
canPaintBound = Rect.fromLTRB(
(-size.width / 2 - cachePaintOffset) / _scale,
(-size.height / 2 - cachePaintOffset) / _scale,
(size.width / 2 + cachePaintOffset) / _scale,
(size.height / 2 + cachePaintOffset) / _scale);
markNeedsPaint();
}

double _cachePaintOffset;

set cachePaintOffset(double value) {
_cachePaintOffset = value;
canPaintBound = Rect.fromLTRB(
(-size.width / 2 - cachePaintOffset) / _scale,
(-size.height / 2 - cachePaintOffset) / _scale,
(size.width / 2 + cachePaintOffset) / _scale,
(size.height / 2 + cachePaintOffset) / _scale);
markNeedsPaint();
}

Expand All @@ -286,6 +379,16 @@ class ForceDirectedGraphRenderObject extends RenderBox
}
}

@override
void performResize() {
super.performResize();
canPaintBound = Rect.fromLTRB(
(-size.width / 2 - cachePaintOffset) / _scale,
(-size.height / 2 - cachePaintOffset) / _scale,
(size.width / 2 + cachePaintOffset) / _scale,
(size.height / 2 + cachePaintOffset) / _scale);
}

@override
bool get sizedByParent => true;

Expand Down Expand Up @@ -314,11 +417,6 @@ class ForceDirectedGraphRenderObject extends RenderBox
context.canvas.save();
context.canvas.translate(center.dx, center.dy);
context.canvas.scale(_scale, _scale);
final canPaintBound = Rect.fromLTRB(
(-size.width / 2 - cachePaintOffset) / _scale,
(-size.height / 2 - cachePaintOffset) / _scale,
(size.width / 2 + cachePaintOffset) / _scale,
(size.height / 2 + cachePaintOffset) / _scale);

RenderBox? child = firstChild;
while (child != null) {
Expand Down Expand Up @@ -490,6 +588,12 @@ class ForceDirectedGraphRenderObject extends RenderBox
}
}

class PaintBoundChangeNotification extends Notification {
final Rect paintBound;

PaintBoundChangeNotification(this.paintBound);
}

class ForceDirectedGraphParentData extends ContainerBoxParentData<RenderBox> {
Node? node;
Edge? edge;
Expand Down

0 comments on commit 5600bcb

Please sign in to comment.