Skip to content

Commit 12648c4

Browse files
Antamansiddraedful
andauthored
refactor: separate React dependencies from core library (#74)
BREAKING CHANGE: React components moved to separate module See [Migration Guide v0.x → v1.x](https://github.com/gravity-ui/graph/blob/main/docs/migration-guides/v0-to-v1.md) for detailed instructions. --------- Co-authored-by: draedful <[email protected]>
1 parent 2ba183c commit 12648c4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+478
-257
lines changed
Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ Layers are fundamental to the Canvas rendering pipeline in @gravity-ui/graph. Th
3434
- Potentially culling elements outside the current viewport.
3535
- Managing redraws or logic updates based on relevant state changes.
3636

37+
## Layer Lifecycle and DOM Operations
38+
39+
- **Layer Initialization Sequence:**
40+
1. **Creation (`constructor` → `init`)**: Layer is created, but not yet attached to DOM
41+
2. **Attachment (`attachLayer` → `afterInit`)**: Layer is attached to DOM
42+
3. **Updates (rendering cycle)**: Layer responds to changes
43+
4. **Detachment (`detachLayer` → `unmount`)**: Layer is removed from DOM
44+
45+
- **DOM Operation Timing:** Any operations that require the layer to be attached to the DOM (such as DOM manipulations, style injections, measurements, or accessing parent elements) must be performed in the `afterInit` method, not in `init` or the constructor.
46+
47+
- **Why This Matters:** During the creation phase, the root element may be undefined or not yet attached to the document. The `afterInit` method is guaranteed to be called after the layer is properly attached to the DOM, ensuring that DOM operations work correctly.
48+
3749
## HTML Rendering in Layers
3850

3951
The Layer class can create both Canvas and HTML elements for rendering:
@@ -110,6 +122,10 @@ this.html.style.transform = `matrix(${camera.scale}, 0, 0, ${camera.scale}, ${ca
110122
```
111123
- Register the new layer in the main graph's processing pipeline in the correct order (often controlled by `zIndex` in `props.canvas` or `props.html`).
112124

125+
- **Creating New Layers:**
126+
- If adding new types of visual elements or behaviors, create a new layer class.
127+
- New layers should ideally extend a base `Layer` class or a suitable existing layer, inheriting common functionality.
128+
- Register the new layer in the main graph's processing pipeline in the correct order.
113129
- **Modifying Existing Layers:**
114130
- When changing how elements are rendered or behaviors are managed, modify the relevant methods (e.g., `render`, event handlers) of the corresponding layer.
115131
- Ensure efficiency; avoid unnecessary computations or drawing invisible elements.
@@ -125,7 +141,7 @@ this.html.style.transform = `matrix(${camera.scale}, 0, 0, ${camera.scale}, ${ca
125141
- Use `afterInit` for setup that requires the canvas and context to be ready.
126142
- Inside `afterInit`, reliably get the canvas using `this.getCanvas()`.
127143
- Initialize or update the layer's full context using `this.setContext({ ...this.context, /* your additions */ });`. The base `Layer` already initializes `graph`, `camera`, `colors`, `constants`, `layer`. If you create a canvas/HTML element, you need to add `graphCanvas`/`html` and `ctx` to the context after creation (e.g., in `afterInit`).
128-
- Attach event listeners (e.g., `mousemove`, `mouseleave`) to the graph's root element (`this.props.graph.getGraphHTML()`) or specific layer elements (`this.getCanvas()`, `this.getHTML()`) within `afterInit`.
144+
- Attach event listeners (e.g., `mousemove`, `mouseleave`) to the layer's root element (`this.root`) or specific layer elements (`this.getCanvas()`, `this.getHTML()`) within `afterInit`.
129145
- Always clean up listeners and subscriptions in the `unmount` method.
130146
- **Camera Interaction & Coordinates:**
131147
- Subscribe to graph's `'camera-change'` event (`this.props.graph.on(...)`) to get updates. The event detail (`event.detail`) provides the `TCameraState` (containing `width`, `height`, `scale`, `x`, `y`).
@@ -202,4 +218,4 @@ protected afterInit() {
202218
}
203219
```
204220

205-
When the layer is unmounted, all handlers are automatically removed.
221+
When the layer is unmounted, all handlers are automatically removed.

README-ru.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ npm install @gravity-ui/graph
6262
[Подробная документация по React компонентам](docs/react/usage.md)
6363

6464
```typescript
65-
import { GraphCanvas, GraphState, GraphBlock, useGraph } from "@gravity-ui/graph";
65+
import { Graph } from "@gravity-ui/graph";
66+
import { GraphCanvas, GraphState, GraphBlock, useGraph } from "@gravity-ui/graph/react";
6667
import React from "react";
6768

6869
const config = {};
@@ -226,4 +227,3 @@ graph.zoomTo("center", { padding: 100 });
226227
|------|-----------|--------------|
227228
| Настройки графа | Параметры конфигурации | [Подробнее](docs/system/graph-settings.md) |
228229
| API | Методы для управления графом | [Подробнее](docs/system/public_api.md) |
229-

README.md

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Modern web applications often require complex visualization and interactivity, b
2323
The library uses a smart rendering system that automatically manages the transition between Canvas and React components:
2424

2525
1. At low zoom levels, everything is rendered on Canvas for performance
26-
2. When zooming in to detailed view, the `BlocksList` component:
26+
2. When zooming in to detailed view, the `GraphCanvas` component:
2727
- Tracks camera viewport and scale changes
2828
- Calculates which blocks are visible in the current viewport (with padding for smooth scrolling)
2929
- Renders React components only for visible blocks
@@ -62,7 +62,8 @@ npm install @gravity-ui/graph
6262
[Detailed React Components Documentation](docs/react/usage.md)
6363

6464
```typescript
65-
import { GraphCanvas, GraphState, GraphBlock, useGraph } from "@gravity-ui/graph";
65+
import { EAnchorType, Graph } from "@gravity-ui/graph";
66+
import { GraphCanvas, GraphState, GraphBlock, useGraph } from "@gravity-ui/graph/react";
6667
import React from "react";
6768

6869
const config = {};
@@ -82,7 +83,14 @@ export function GraphEditor() {
8283
height: 126,
8384
selected: true,
8485
name: "Block #1",
85-
anchors: [],
86+
anchors: [
87+
{
88+
id: "out1",
89+
blockId: "action_1",
90+
type: EAnchorType.OUT,
91+
index: 0
92+
}
93+
],
8694
},
8795
{
8896
id: "action_2",
@@ -93,13 +101,22 @@ export function GraphEditor() {
93101
height: 126,
94102
selected: false,
95103
name: "Block #2",
96-
anchors: [],
104+
anchors: [
105+
{
106+
id: "in1",
107+
blockId: "action_2",
108+
type: EAnchorType.IN,
109+
index: 0
110+
}
111+
],
97112
}
98113
],
99114
connections: [
100115
{
101116
sourceBlockId: "action_1",
117+
sourceAnchorId: "out1",
102118
targetBlockId: "action_2",
119+
targetAnchorId: "in1",
103120
}
104121
]
105122
});
@@ -159,7 +176,15 @@ graph.setEntities({
159176
y: 100,
160177
width: 120,
161178
height: 120,
162-
name: "Block #1"
179+
name: "Block #1",
180+
anchors: [
181+
{
182+
id: "out1",
183+
blockId: "block1",
184+
type: EAnchorType.OUT,
185+
index: 0
186+
}
187+
]
163188
},
164189
{
165190
is: "block-action",
@@ -168,13 +193,23 @@ graph.setEntities({
168193
y: 300,
169194
width: 120,
170195
height: 120,
171-
name: "Block #2"
196+
name: "Block #2",
197+
anchors: [
198+
{
199+
id: "in1",
200+
blockId: "block2",
201+
type: EAnchorType.IN,
202+
index: 0
203+
}
204+
]
172205
}
173206
],
174207
connections: [
175208
{
176209
sourceBlockId: "block1",
177-
targetBlockId: "block2"
210+
sourceAnchorId: "out1",
211+
targetBlockId: "block2",
212+
targetAnchorId: "in1"
178213
}
179214
]
180215
});
@@ -208,6 +243,7 @@ graph.zoomTo("center", { padding: 100 });
208243
2. Components
209244
- [Canvas Graph Component](docs/components/canvas-graph-component.md)
210245
- [Block Component](docs/components/block-component.md)
246+
- [Anchors](docs/components/anchors.md)
211247

212248
3. Rendering
213249
- [Rendering Mechanism](docs/rendering/rendering-mechanism.md)

docs/components/anchors.md

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# Anchors Documentation
2+
3+
Anchors are connection points on blocks that allow creating connections between blocks. This document describes the anchor structure, behavior, and how to properly configure them.
4+
5+
> **Note:** Anchors are optional in the graph system. You can create graphs without anchors by setting the `useBlocksAnchors` setting to `false`. In this case, connections will be made directly between blocks without specific anchor points.
6+
7+
## Anchor Structure
8+
9+
Anchors are defined using the `TAnchor` interface:
10+
11+
```typescript
12+
export type TAnchor = {
13+
id: string; // Unique identifier for the anchor
14+
blockId: TBlockId; // ID of the block this anchor belongs to
15+
type: EAnchorType; // Type of anchor (IN or OUT)
16+
index?: number; // Optional index for ordering anchors of the same type
17+
};
18+
```
19+
20+
### Key Properties
21+
22+
1. **id**: A unique identifier for the anchor within the block. This is used to reference the anchor in connections and for selection.
23+
24+
2. **blockId**: The ID of the block this anchor belongs to. This is critical for the anchor to be properly associated with its parent block and for the selection system to work correctly.
25+
26+
3. **type**: The type of anchor, defined by the `EAnchorType` enum:
27+
```typescript
28+
export enum EAnchorType {
29+
IN = "IN", // Input anchor (can receive connections)
30+
OUT = "OUT", // Output anchor (can initiate connections)
31+
}
32+
```
33+
34+
4. **index**: An optional numeric value that determines the order of anchors of the same type. This affects the visual positioning of anchors on the block.
35+
36+
## Configuring Anchors and Connections
37+
38+
When defining blocks in your graph configuration, you can configure anchors and connections between them:
39+
40+
```typescript
41+
const graphConfig = {
42+
blocks: [
43+
{
44+
id: "block1",
45+
is: "Block",
46+
x: 100,
47+
y: 100,
48+
width: 150,
49+
height: 80,
50+
name: "Source Block",
51+
selected: false,
52+
anchors: [
53+
{
54+
id: "out1",
55+
blockId: "block1", // Must match the parent block's ID
56+
type: EAnchorType.OUT,
57+
index: 0
58+
}
59+
]
60+
},
61+
{
62+
id: "block2",
63+
is: "Block",
64+
x: 400,
65+
y: 200,
66+
width: 150,
67+
height: 80,
68+
name: "Target Block",
69+
selected: false,
70+
anchors: [
71+
{
72+
id: "in1",
73+
blockId: "block2", // Must match the parent block's ID
74+
type: EAnchorType.IN,
75+
index: 0
76+
}
77+
]
78+
}
79+
],
80+
connections: [
81+
{
82+
id: "conn1",
83+
sourceBlockId: "block1",
84+
sourceAnchorId: "out1", // Reference to the source anchor
85+
targetBlockId: "block2",
86+
targetAnchorId: "in1", // Reference to the target anchor
87+
selected: false
88+
}
89+
]
90+
};
91+
```
92+
93+
## Dynamic Anchor Creation
94+
95+
When creating blocks dynamically, ensure you set the correct `blockId` for each anchor:
96+
97+
```typescript
98+
// Create a new block with a unique ID
99+
const newBlockId = `block-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
100+
101+
const blockId = graph.api.addBlock({
102+
id: newBlockId, // Explicitly set the block ID
103+
is: "Block",
104+
x: 100,
105+
y: 100,
106+
width: 150,
107+
height: 80,
108+
name: "New Block",
109+
selected: false,
110+
anchors: [
111+
{
112+
id: "in",
113+
blockId: newBlockId, // Use the same ID as the block
114+
type: EAnchorType.IN,
115+
index: 0
116+
},
117+
{
118+
id: "out",
119+
blockId: newBlockId, // Use the same ID as the block
120+
type: EAnchorType.OUT,
121+
index: 0
122+
}
123+
]
124+
});
125+
```
126+
127+
## Anchor Selection
128+
129+
Anchors can be selected programmatically or through user interaction. The selection state is managed by the `AnchorState` class:
130+
131+
```typescript
132+
// Get the anchor state
133+
const anchorState = block.getAnchorState("anchorId");
134+
135+
// Select the anchor
136+
anchorState.setSelection(true);
137+
138+
// Deselect the anchor
139+
anchorState.setSelection(false);
140+
```
141+
142+
## Anchor Positioning
143+
144+
The position of anchors is determined by the block's geometry and the anchor's type and index. The `Block` class provides methods to get the position of an anchor:
145+
146+
```typescript
147+
// Get the position of an anchor
148+
const position = block.getAnchorPosition(anchorId);
149+
```
150+
151+
## Anchor Positioning and Block Height
152+
153+
The vertical position of anchors is determined by the block's constants, specifically `HEAD_HEIGHT` and `BODY_PADDING`. These constants are defined in the graph configuration and affect where anchors are placed on blocks.
154+
155+
```typescript
156+
// From Block.ts - getAnchorPosition method
157+
const offset = this.context.constants.block.HEAD_HEIGHT + this.context.constants.block.BODY_PADDING;
158+
return {
159+
x: anchor.type === EAnchorType.OUT ? this.state.width : 0,
160+
y: offset + index * this.context.constants.system.GRID_SIZE * 2,
161+
};
162+
```
163+
164+
### Important Considerations for Block Height
165+
166+
1. **Default Constants**: By default, `HEAD_HEIGHT` is 64px and `BODY_PADDING` is 24px, resulting in an 88px vertical offset for the first anchor.
167+
168+
2. **Small Blocks Warning**: If your blocks have a small height (e.g., less than 90px), the anchors may appear below the actual block. To fix this:
169+
- Set `HEAD_HEIGHT` to 0 or a smaller value
170+
- Reduce `BODY_PADDING` as needed
171+
172+
3. **Custom Configuration Example**:
173+
```javascript
174+
const graph = new Graph({
175+
constants: {
176+
block: {
177+
HEAD_HEIGHT: 0, // Remove header height for small blocks
178+
BODY_PADDING: 8 // Reduce padding for better anchor positioning
179+
}
180+
},
181+
// other configuration...
182+
}, container);
183+
```
184+
185+
4. **Anchor Index Impact**: Each additional anchor is positioned with an offset of `GRID_SIZE * 2` (default 32px). Consider this when determining how many anchors can fit on a block of a given height.

0 commit comments

Comments
 (0)