# AstrolBoard > AstrolBoard: interactive Blazor board from AstrolUI with boxes you drag and resize, relations with vertices, zoom, mini-map, search, context menus, undo/redo and optional guides; smooth even with hundreds of elements. Source page: https://doc.astrolui.com/board-component **AstrolBoard** renders a **Board**: a list of boxes (**Box**, with content of type **TBox**) and of relations between them (**Relation**). You decide how each box looks with **BoxRender**; the board changes position and size on the same object and raises **OnChangeBoard** so you can save it. ## 1. Minimal example ## 2. Data model - **Board**: **Id** (string), **Scale** (float, zoom; 1 = 100%), **Boxes** (List>) and **Relations** (List>). - **Box**: **Id** (string, unique), **BOX_NAME** (text used by the search), **X**, **Y**, **Width**, **Height** (float, in board units without zoom), **Color** (color in the mini-map), **Content** (TBox, your data) and **ShowGuides** (bool?, guides for this box only). - **Relation**: **BoxIdA** and **BoxIdB** (the boxes it joins; if they are the same a loop is drawn at its bottom-right corner), **Vertices** (List, intermediate points), **AnchorA** / **AnchorB** (BoxAnchor?, fixed point of each box where the line starts and ends), **Arrow** (RelationArrow?), **Label** (string) and **Content** (TRelation). - **Vertex**: **Id**, **X**, **Y** and **Order** (the line goes through the vertices in that order). - **Position**: **X**, **Y** (received by the menu events, in board pixels; divide by **Scale** to get model units). - To add or remove boxes or relations, change the lists of the same **Board** object: the board detects it when it renders again and adds it to the history. If you assign another **Board** object, the board starts again (selection and history). ## 3. Mouse and keyboard - **Drag a box**: moves it (and all the selected ones, with the vertices of the relations between them). - **Drag a corner or side** of the box: resizes it, respecting **MinWidth** / **MinHeight**. - **Drag a vertex**: bends the relation. **Drag the background**: scrolls the board. - **Drag the end of a line** (with **EditAnchors**): fixes it to a point of the box border; double-click releases it. - **Ctrl + click**: adds a box to or removes it from the selection. **Ctrl + A**: selects everything. **Esc**: clears the selection and closes menus. - **Arrows**: move the selection 5 px (**Shift + arrows**: 20 px). **Delete**: raises **OnKeyDownDelete** or, when it is not used, deletes the selection and its relations. - **Ctrl + C / V / D**: copy, paste and duplicate. **Drag a port** (AllowConnect): connect. **Shift + drag the background**: select with a rectangle. - **Ctrl + Z**: undo. **Ctrl + Y** or **Ctrl + Shift + Z**: redo. - **Ctrl + F**: search boxes by **BOX_NAME** (ignoring case and accents). **Ctrl + wheel**: zoom (20% to 300%). - **Right click**: board, box, relation or vertex menu (when enabled). - Shortcuts only act with the focus inside the board; right click and browser shortcuts keep working in the rest of the page, and they do not act while typing in a field inside a box. ## 4. Context menus - Enable each menu with **ShowBoardContextMenu**, **ShowBoxContextMenu**, **ShowRelationContextMenu** or **ShowVertexContextMenu** and write its content in **BoardContextMenu**, **BoxContextMenu**, **RelationContextMenu** or **VertexContextMenu**. - When it opens, **OnOpen…ContextMenu** gives you the id of the box, of the relation (**BoxIdA**, **BoxIdB**) or of the vertex and the click position in board pixels (divide by **Scale** to insert a box there). Store them and use them in the options. - Inside the board the browser menu never opens: only the board, box, line or vertex menu appears. - The menu closes when an option is chosen, with **Esc** or when clicking outside, and is placed so that it fits in the visible part of the board. - **MenuOpion**: ready-made option with **Icon**, **IconColor** and **IconBackground** (optional; without them the icon is neutral), **Text**, **KeyboardShortcut**, **HasSeparator**, **Status** (false = disabled) and **OnClick**. Use **NeedAuthorize="false"** if you do not filter by roles (**Roles**). You can also use your own HTML with the **p-board-menu-option** class. ```razor ... ``` ```csharp private Position position = new(); private string? boxId; // The position is in board pixels: divide by Scale to get model units private void AddBox() => board.Boxes.Add(new Box { X = position.X / board.Scale, Y = position.Y / board.Scale, BOX_NAME = "New" }); private void Delete() => board.Boxes.RemoveAll(b => b.Id == boxId); ``` ## 5. Line anchors - **RelationAnchor** (board parameter): **Center** (default, lines go from center to center) or **Border** (they start and end at the border of the boxes, in the direction of the other end or of the nearest vertex). - **Relation.AnchorA** / **AnchorB** (**BoxAnchor**, optional): a fixed point of each box, relative to it (**X** and **Y** from 0 to 1). Shortcuts: **BoxAnchor.Top**, **Right**, **Bottom**, **Left**, **Center**. When it is **null**, **RelationAnchor** is used. - **LineStyle** (board parameter): **Straight** (default, straight segments) or **Curved** (smooth curves that leave perpendicular to the side of the box and go through the vertices). - **EditAnchors** (board parameter): when hovering a line or a box the circles at its ends appear, drawn over the box (they take priority over the resize handles: if they match, move the end first); the user drags the end of a line to a point of the border (it snaps to corners, quarters and middles of the sides) and the point is stored in **AnchorA** / **AnchorB**; double-click on the end releases it. It goes into the history (Ctrl+Z) and raises **OnChangeBoard**. - A relation of a box with itself (**BoxIdA** = **BoxIdB**) is drawn as a loop at its bottom-right corner. ```razor ... ``` ```csharp // From the middle of the right side of A to the top quarter of the left side of B board.Relations.Add(new Relation { BoxIdA = a.Id, BoxIdB = b.Id, AnchorA = BoxAnchor.Right, // (1, 0.5) AnchorB = new BoxAnchor(0, 0.25F), // x and y from 0 to 1, relative to the box }); ``` ## 6. Editing tools - **Arrows**: **RelationArrow** (board parameter: **None**, **End** A → B, **Start** A ← B, **Both**) and **Relation.Arrow** for a single relation. An end with an arrow goes to the border of the box even in center mode. - **Labels**: **Relation.Label** (text) or **RelationRender** (**RenderFragment**, any content), centered at half the length of the line or curve. Right-click on the label = relation menu. - **Connect by dragging**: **AllowConnect**. Hovering a box shows four ports outside it; drag one to another box (it is highlighted) and drop. **OnConnect** (**BoardConnectEventArgs**) receives the relation before it is added to complete it (**Content**, **Label**, **Arrow**…) or cancel it (**Cancel = true**). - **Rectangle selection**: **BackgroundDrag** = **Pan** (default, dragging the background scrolls and with Shift selects) or **Select** (the other way round). Ctrl adds to the selection. - **Snapping**: **SnapToGrid** (grid size, 0 = off) when moving and resizing, and **SnapToBoxes**: when moving, the box aligns with the edges and centers of the others (within 6 px) and the guide is drawn. - **Clipboard** (**AllowClipboard**, default **true**): Ctrl+C, Ctrl+V (moved 20 px each time) and Ctrl+D copy the selected boxes with the relations between them; the copies stay selected and raise **OnBoxesAdded**. The content is copied with **CloneBox** / **CloneRelation** or, when not given, through JSON (if that is not possible, the reference is shared). - **Delete**: if you use **OnKeyDownDelete**, your code decides (as before); otherwise the selection and its relations are removed and **OnBoxesRemoved** is raised. - Methods for your menus: **CopySelection()**, **PasteAsync()**, **DuplicateSelectionAsync()**, **DeleteSelectionAsync()**. Everything goes into the history (Ctrl+Z) and raises **OnChangeBoard**. ```razor @context.Label ... ``` ```csharp // The relation is not on the board yet: complete it or cancel it private void Connect(BoardConnectEventArgs e) { e.Relation.Label = "next"; e.Cancel = board.Relations.Any(r => r.BoxIdA == e.Relation.BoxIdA && r.BoxIdB == e.Relation.BoxIdB); } board.Relations.Add(new Relation { BoxIdA = a.Id, BoxIdB = b.Id, Label = "Yes", Arrow = RelationArrow.Both }); ``` ## 7. Automatic arrangement - **ArrangeAsync(BoardLayoutOptions?)** (with **@ref**) orders the board by layers following the direction of the relations (from **BoxIdA** to **BoxIdB**): it breaks cycles, reduces line crossings and aligns each box with its neighbors. Groups that are not connected are placed side by side and boxes without relations at the end. - Relations that skip layers get vertices to go around the boxes; the others are straight (their vertices are removed). With **UseVertices = false** all of them are removed. - **It never adds, removes or changes relations** (boxes, content, anchors) nor the size of the boxes: it only moves boxes and vertices. It goes into the history (Ctrl+Z) and raises **OnChangeBoard**. - **BoardLayoutOptions**: **Direction** (**TopToBottom** or **LeftToRight**), **LayerSpacing** (80), **BoxSpacing** (40), **GroupSpacing** (80), **UseVertices** (true), **Left**/**Top** (20, never less than MinLeft/MinTop), **MaxWidth** (1600, width of each row of groups; limited to MaxRight) and **Animate** (true). - **BoardLayout.Arrange(board, options)** does the same without the component (e.g. when you create the board or on the server). ```razor ... ``` ```csharp private AstrolBoard? boardRef; private Task Arrange() => boardRef!.ArrangeAsync(new BoardLayoutOptions { Direction = BoardLayoutDirection.LeftToRight, LayerSpacing = 60, }); // Without the component (e.g. before showing the board): BoardLayout.Arrange(board); ``` ## 8. Guides while dragging (ShowGuides) With **ShowGuides="true"**, while dragging or resizing the selected box, dashed guide lines appear from its corners (outwards) and from the middle of its sides (fainter), up to the edge of the board, to align it with other boxes. They are off by default. Each box can force them with **Box.ShowGuides** (**true** / **false**; **null** follows the board). The look is changed with CSS variables: ```razor ... ``` ```csharp // Per box: null = follow the board, true / false = force it for this box board.Boxes.Add(new Box { BOX_NAME = "Title", ShowGuides = false }); ``` ```css /* Optional: line style, separation and opacity of the side lines */ :root { --border-line: 1px dashed var(--color-red-transparent); --pbse-offset: 2px; --pbse-side-opacity: 0.45; } ``` ## 9. Performance - Dragging, resizing, moving vertices, scrolling the board and the mini-map happen in the browser, one paint per frame: **no calls to .NET while you drag** (a single one on release). In Blazor Server nothing travels over the network during the drag. - When a change is committed, only the boxes and relations that changed are rendered again. - Nothing is initialized per box: with 500 boxes the board mounts in half the time it used to and each drag step goes from ~360 ms to ~2 ms. - The history only stores geometry (it does not copy your **TBox** content). - If your **BoxRender** is expensive, keep it simple or use your own components: it renders again when the board's parent renders again. ### Supported parameters - Board **(Board)**, the board to render **(required)**. - BoxRender **(RenderFragment>)**, content of each box; **context** is the box. - BackgroundType **(BackgroundType)**, background: **None**, **Grids** (default), **Points** or **Quadrants**. - ShowGuides **(bool)**, guides while dragging (default **false**). - ShowSearch, ShowZoom **(bool)**, search and zoom with mini-map (default **true**); ZoomStatus **(bool)**, mini-map open; EnableZoom and AllowChangeScale **(bool)**, allow zooming with the wheel and with the buttons. - MinWidth, MinHeight **(float)**, minimum box size (default **100**); MinLeft, MinTop **(float)** (default **10**), MaxRight, MaxBottom **(float)**, area where boxes can move. - RelationAnchor **(RelationAnchor)**, lines start at the center (**Center**, default) or at the border (**Border**) of the boxes. LineStyle **(RelationLineStyle)**, straight (**Straight**, default) or curved (**Curved**) lines. EditAnchors **(bool)**, the user fixes the ends by dragging them. - RelationArrow **(RelationArrow)**, arrows; RelationRender **(RenderFragment)**, labels. AllowConnect **(bool)** and OnConnect **(BoardConnectEventArgs)**, connect by dragging. BackgroundDrag **(BoardBackgroundDrag)**, scroll or select. SnapToGrid **(float)**, SnapToBoxes **(bool)**, snapping. AllowClipboard **(bool)**, CloneBox, CloneRelation **(Func)**, OnBoxesAdded, OnBoxesRemoved. - DiscontinueLine **(bool)**, dashed relation lines. BoxDelay **(bool)**, smooth transition when moving with the keyboard or undoing. - LongMemory **(int)**, history states (default **10**; **0** turns it off). - ShowBoardContextMenu, ShowBoxContextMenu, ShowRelationContextMenu, ShowVertexContextMenu **(bool)** and BoardContextMenu, BoxContextMenu, RelationContextMenu, VertexContextMenu **(RenderFragment)**, right-click menus (use **MenuOpion** or the **p-board-menu-option** class on the options). - LoadComponent **(RenderFragment)**, loading indicator. TextPlaceholderSearch, AriaLabel **(string)**, CssClass, CssStyle **(string)**. - OnChangeBoard **(EventCallback)**, after every user change (move, resize, anchor a line, arrows, undo). - OnOpenBoardContextMenu **(Position)**, OnOpenBoxContextMenu **((string, Position))**, OnOpenRelationContextMenu **((string, string, Position))**, OnOpenVertexContextMenu **((string, Position))** and OnChange…ContextMenuStatus **(bool)**. - OnDobleClickBox **(Box)**, OnKeyDownDelete, OnChangeScale **(float)**, OnChangeZoomStatus **(bool)**. ### Public methods With **@ref** (**private AstrolBoard? boardRef;**): - **ArrangeAsync(options)**: orders every box by its relations (see «Automatic arrangement»). - **CopySelection()**, **PasteAsync()**, **DuplicateSelectionAsync()**, **DeleteSelectionAsync()**: clipboard and deleting the selection. - **UndoAsync()** / **RedoAsync()**: undo and redo. - **GetHistoryInfo()**: **CanUndo**, **CanRedo**, **CurrentIndex**, **TotalStates**. **ClearHistory()**: clears the history. - **_idBoxesSelected**: ids of the selected boxes. **RemoveItemSelected(ids)**: removes them from the selection. - **GetSVGWidth()**: content width. **EndMoveBoxPosition()**: recalculates the size and raises **OnChangeBoard**.