<DrawService />
<DrawService /> connects an embedded <Visualizer /> to a running draw server over Connect-RPC so any client/api calls made elsewhere render in this instance live. Skip it if you only render snapshots — snapshots are entirely client-side and don’t need the server.
The plugin is headless: it provides the connection context and starts the stream; it doesn’t render anything itself.
The Connect RPC client is required for the <DrawService />.
Install
Section titled “Install”| Prop | Type | Description |
|---|---|---|
config | { backendIP: string; port?: string } | Host and port of the draw server. port defaults to 3030. |
The config prop is reactive — change it and the plugin reconnects to the new endpoint.
Connection behavior
Section titled “Connection behavior”<DrawService /> opens one connection: a Connect-RPC stream for entity changes.
The stream reconnects on its own with exponential backoff, and resubscribing replays the full scene, so a dropped connection recovers without anything to act on.
Connection logs
Section titled “Connection logs”The plugin reports connection lifecycle through the useLogs hook. Mount <Logs /> alongside it to see the messages in a panel; without it they’re dropped.
| Message | Level | When |
|---|---|---|
Connected to draw server at <url> | info | The stream delivers its first message. |
Disconnected from draw server, reconnecting... | warn | An established connection drops. |
Could not reach draw server at <url>, retrying... | warn | The first attempt fails, once per outage rather than per retry. |
Draw server error: <message> | error | An attempt fails with a stream error. |
Because the stream retries on a backoff, a server that stays down logs the “could not reach” warning once, not once per attempt. Errors also go to the browser console with their stack.
Running a draw server
Section titled “Running a draw server”<DrawService /> is a client — it needs a draw server to talk to. You have three options:
Use the local app
Section titled “Use the local app”The fastest path: run the visualizer locally (make up or pnpm dev). The local app already hosts a draw server on port 3030, and client/api calls to the same host land in it.
Host the Go DrawService yourself
Section titled “Host the Go DrawService yourself”Viam Visualization exports a ready-made DrawService from github.com/viamrobotics/visualization/draw — the same Connect-RPC handler the local app uses. Wire it up to your own HTTP server:
Your client/api producer code then points at this server, and <DrawService config={{ backendIP }} /> in the browser subscribes to its stream.
Implement your own server
Section titled “Implement your own server”If you need a custom backend (different language, different storage, extra side effects), the proto definitions are published under github.com/viamrobotics/visualization/draw/v1 (Go) and ship as .proto files in the source tree under protos/draw/v1/. Implement the DrawService RPCs in whichever language you like; <DrawService /> just needs a Connect-RPC server at the configured host:port.
Start with these, which between them cover everything client/api calls:
| RPC | Why it’s needed |
|---|---|
AddEntity | Single-entity draws (DrawGeometry, DrawPoints, DrawLine, and friends). |
AddEntities | Batch draws. DrawFrameSystem, DrawWorldState, DrawGeometriesInFrame, and DrawFrames send one batch rather than one request per entity, so a backend without this fails on the most common calls. |
UpdateEntity | Partial updates, and the delivery channel for chunked point-cloud payloads. |
RemoveEntity, RemoveAll, RemoveAllTransforms, RemoveAllDrawings | Removal. |
StreamEntityChanges | The subscription <DrawService /> consumes. Replay the current scene as ADDED events on connect so a reconnecting client resyncs. |
GetEntityChunk | Only if you support chunked entities. |
SetScene, StreamSceneChanges | Only if you drive the camera from the server. |
CreateRelationship, DeleteRelationship | Only if you use entity relationships. |
The cleared_scope field on StreamEntityChangesResponse is optional. It lets a bulk removal travel as one event instead of one per entity, but a backend that emits an individual REMOVED per entity works against the client unchanged.
See the client/api reference for the producer-side calls regardless of which server you use.