Skip to content

Commit e7f9130

Browse files
committed
feat: add camera bounds
1 parent b8230b9 commit e7f9130

File tree

29 files changed

+859
-3036
lines changed

29 files changed

+859
-3036
lines changed

.cursor/worktrees.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"setup-worktree": [
3+
"npm install"
4+
]
5+
}

docs/system/camera-constraints.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Camera Constraints
2+
3+
This document describes the camera constraints feature that prevents users from losing track of the graph by limiting pan and zoom operations.
4+
5+
## Overview
6+
7+
Camera constraints ensure that users cannot zoom out too far or pan away from the graph content, maintaining visual context at all times. This feature is controlled by the `constrainCameraToGraph` setting and uses the `usableRect` calculated from the graph's hit test system.
8+
9+
## Configuration
10+
11+
### Settings
12+
13+
To enable camera constraints, set the `constrainCameraToGraph` flag in the graph settings:
14+
15+
```typescript
16+
const graph = new Graph({
17+
settings: {
18+
constrainCameraToGraph: true, // Default: false
19+
},
20+
});
21+
22+
// Or enable dynamically:
23+
graph.api.setSetting("constrainCameraToGraph", true);
24+
```
25+
26+
### Constants
27+
28+
The padding around the graph content can be configured via the `CAMERA_BOUNDS_PADDING` constant:
29+
30+
```typescript
31+
const graph = new Graph(
32+
{
33+
// ... config
34+
},
35+
rootElement,
36+
colors,
37+
{
38+
camera: {
39+
CAMERA_BOUNDS_PADDING: 200, // Default: 200 (in camera space)
40+
},
41+
}
42+
);
43+
```
44+
45+
## Behavior
46+
47+
### Non-empty Graph
48+
49+
When the graph contains blocks or connections:
50+
51+
- **Minimum Scale**: Automatically calculated to ensure the entire graph fits in the viewport with padding
52+
- **Maximum Scale**: Uses the configured `scaleMax` value (default: 1)
53+
- **Position Bounds**: Camera center is constrained to the graph area plus padding
54+
55+
### Empty Graph
56+
57+
When the graph is empty (no blocks or connections):
58+
59+
- **Minimum Scale**: Uses the configured `scaleMin` value (default: 0.01)
60+
- **Maximum Scale**: Limited to 0.5
61+
- **Position Bounds**: Camera can pan within 50% of the viewport from center
62+
63+
## Implementation Details
64+
65+
### Core Components
66+
67+
1. **TCameraBounds** type: Defines the bounds structure
68+
```typescript
69+
type TCameraBounds = {
70+
minX: number;
71+
maxX: number;
72+
minY: number;
73+
maxY: number;
74+
minScale: number;
75+
maxScale: number;
76+
};
77+
```
78+
79+
2. **updateCameraBounds()**: Calculates bounds based on usableRect
80+
3. **applyCameraBounds()**: Applies constraints to camera state changes
81+
4. **Automatic updates**: Bounds are recalculated when:
82+
- usableRect changes (blocks added/removed/moved)
83+
- Viewport size changes (resize)
84+
- Viewport insets change
85+
- constrainCameraToGraph setting is enabled
86+
87+
### Edge Cases Handled
88+
89+
- **Viewport larger than graph**: Camera is centered when viewport is larger than the constrained area
90+
- **Dynamic enable/disable**: Constraints can be toggled at runtime
91+
- **Viewport insets**: Constraints account for visible viewport area with insets
92+
- **Empty to non-empty transition**: Bounds are recalculated when first block is added
93+
94+
## API
95+
96+
### CameraService Methods
97+
98+
```typescript
99+
// Update bounds based on usable rect
100+
cameraService.updateCameraBounds(usableRect: TRect): void
101+
102+
// Apply constraints to a state change (internal use)
103+
cameraService.applyCameraBounds(state: Partial<TCameraState>): Partial<TCameraState>
104+
```
105+
106+
### Types
107+
108+
```typescript
109+
import type { TCameraBounds, TCameraState } from "@gravity-ui/graph";
110+
```
111+
112+
## Example Usage
113+
114+
See the [Camera Constraints story](/packages/stories/src/examples/cameraConstraints/cameraConstraints.stories.tsx) for a complete interactive example demonstrating:
115+
116+
- Enabling/disabling constraints
117+
- Behavior with and without blocks
118+
- Adding and removing blocks
119+
- Real-time bounds visualization
120+
121+
## Notes
122+
123+
- Constraints are applied at the camera service level, affecting all camera operations (zoom, pan, drag)
124+
- The feature respects viewport insets for proper constraint calculation
125+
- Constraints do not prevent programmatic camera changes via API, but the resulting position will be constrained
126+

0 commit comments

Comments
 (0)