Canvas Extension Content (#canvas)
#canvas places custom content in the graph canvas coordinate system. It is suitable for things that belong to the “graph world”, such as group boxes, lanes, grids, rulers, business regions, and coordinate annotations. These contents move and scale with the canvas, just like nodes and lines.
If the content should be fixed to the viewport, such as a toolbar, button, menu, or property panel, use #view, not #canvas.
1. Platform Syntax
| Platform | Syntax | Description |
|---|---|---|
| Vue 3 / Vue 2 | <template #canvas>...</template> |
Recommended syntax. |
| Vue 3 / Vue 2 | Default slot | The current implementation also places default slot content on the lower canvas layer. |
| React | <RGSlotOnCanvas>...</RGSlotOnCanvas> |
Recommended syntax. |
| React | Normal children | The current implementation places children not recognized as other slots on the lower canvas layer. |
| Svelte | <div slot="canvas">...</div> |
Recommended syntax. |
| Svelte | Default slot | The current implementation also places default slot content on the lower canvas layer. |
2. Render Position and Layering
In the current implementation, #canvas is rendered into:
<div class="rg-map-canvas rg-canvas-behind">
<div class="rg-canvas-slot rg-canvas-slot-behind">
<!-- #canvas / default child content -->
</div>
</div>
It is below nodes and lines. Simplified layers:
| Layer | Content | Follows canvas zoom/pan | Description |
|---|---|---|---|
| Background layer | #background |
No | Fixed viewport background. |
| Lower canvas | #canvas |
Yes | Below nodes/lines. |
| Main graph layer | Nodes, lines, node/line slots | Yes | Main graph content. |
| Canvas foreground | #canvas-above |
Yes | Above nodes/lines. |
| Viewport layer | #view |
No | Fixed UI. |
#canvas and the main graph layer use the same canvas transform:
transform: translate(canvasOffset.x, canvasOffset.y) scale(canvasZoom / 100);
transform-origin: 0 0;
Therefore, left/top, SVG x/y, and Canvas drawing coordinates inside #canvas should use canvas coordinates, not browser client coordinates or RelationGraph viewport coordinates.
3. Coordinate Conversion
Common APIs:
| API | Use |
|---|---|
graphInstance.getCanvasXyByClientXy({ x, y }) |
Convert mouse client coordinates to canvas coordinates. |
graphInstance.getCanvasXyByViewXy({ x, y }) |
Convert RelationGraph viewport coordinates to canvas coordinates. |
graphInstance.getViewXyByCanvasXy({ x, y }) |
Convert canvas coordinates to viewport coordinates. |
graphInstance.getViewXyByEvent(event) |
Get viewport coordinates directly from a mouse/touch event. |
Example: create a canvas annotation at the current mouse position after clicking a button:
function createMarker(event: MouseEvent) {
const point = graphInstance.getCanvasXyByClientXy({
x: event.clientX,
y: event.clientY
});
markers.value.push({
id: `marker-${Date.now()}`,
x: point.x,
y: point.y,
text: 'New Marker'
});
}
Render with canvas coordinates directly:
<template #canvas>
<div
v-for="marker in markers"
:key="marker.id"
class="canvas-marker"
:style="{
left: marker.x + 'px',
top: marker.y + 'px'
}"
>
{{ marker.text }}
</div>
</template>
4. Event Handling
The canvas layer wrapper .rg-map-canvas uses pointer-events: none by default. This avoids blocking canvas drag, selection, node events, and line events. If an interactive element inside #canvas needs to receive clicks, explicitly enable pointer events.
Recommended built-in class:
<button class="rg-events-all">Action</button>
Or inline style:
<button style="pointer-events: auto;">Action</button>
Vue example:
<template #canvas>
<button
class="marker-button rg-events-all"
:style="{ left: marker.x + 'px', top: marker.y + 'px' }"
@mousedown.stop
@click.stop="openMarker(marker)"
>
{{ marker.text }}
</button>
</template>
Notes:
- Enable events only on the elements that need interaction. Do not set a large overlay to
pointer-events: autounless you intentionally want to block canvas dragging, node dragging, and line clicks. - If an interactive element is below nodes and gets covered,
#canvas-aboveis more suitable. - If an interactive element should be fixed on screen,
#viewis more suitable.
5. Group Box Example
Group boxes usually belong to the canvas coordinate system and should be placed in #canvas. They move and scale with the graph and appear below nodes/lines.
<script setup lang="ts">
const groups = [
{
id: 'group-a',
title: 'Engineering Team',
x: -260,
y: -140,
width: 420,
height: 260,
color: 'rgba(59, 130, 246, 0.08)',
borderColor: '#3b82f6'
},
{
id: 'group-b',
title: 'Business Team',
x: 220,
y: -120,
width: 380,
height: 240,
color: 'rgba(16, 185, 129, 0.08)',
borderColor: '#10b981'
}
];
</script>
<template>
<RelationGraph :options="graphOptions" :initial-data="graphData">
<template #canvas>
<div
v-for="group in groups"
:key="group.id"
class="group-box"
:style="{
left: group.x + 'px',
top: group.y + 'px',
width: group.width + 'px',
height: group.height + 'px',
background: group.color,
borderColor: group.borderColor
}"
>
<div class="group-title">{{ group.title }}</div>
</div>
</template>
</RelationGraph>
</template>
.group-box {
position: absolute;
box-sizing: border-box;
border: 1px dashed;
border-radius: 8px;
pointer-events: none;
}
.group-title {
position: absolute;
left: 10px;
top: 8px;
font-size: 12px;
color: #374151;
}
6. SVG Helper Layer Example
If you need to draw coordinate lines, region outlines, or helper paths, SVG is often more suitable than many DOM elements.
<template #canvas>
<svg
class="canvas-svg-layer"
:style="{
left: '-1000px',
top: '-1000px',
width: '2000px',
height: '2000px'
}"
viewBox="-1000 -1000 2000 2000"
>
<line x1="-1000" y1="0" x2="1000" y2="0" class="axis-line" />
<line x1="0" y1="-1000" x2="0" y2="1000" class="axis-line" />
<rect
v-for="area in areas"
:key="area.id"
:x="area.x"
:y="area.y"
:width="area.width"
:height="area.height"
class="area-rect"
/>
</svg>
</template>
.canvas-svg-layer {
position: absolute;
overflow: visible;
pointer-events: none;
}
.axis-line {
stroke: rgba(100, 116, 139, 0.25);
stroke-width: 1;
}
.area-rect {
fill: rgba(245, 158, 11, 0.08);
stroke: rgba(245, 158, 11, 0.8);
stroke-dasharray: 6 4;
}
7. Difference from #canvas-above
| Comparison | #canvas |
#canvas-above |
|---|---|---|
| Layer | Below nodes/lines | Above nodes/lines |
| Coordinate system | Canvas coordinates | Canvas coordinates |
| Follows zoom/pan | Yes | Yes |
| Typical use | Group boxes, lanes, grids, lower annotations | Selection, drag hints, temporary markers above nodes |
| Can cover nodes | Does not cover nodes, but can be covered by nodes | May cover nodes; control events and transparent areas carefully |
Selection guidance:
- Put elements with background semantics into
#canvas. - Put hints that must appear above nodes/lines into
#canvas-above. - Put fixed screen UI into
#view.
8. Relationship with Fake Line Targets and Connection Targets
If you place connectable custom targets in #canvas, you can use them together with RGConnectTarget or fake line target APIs. Fake line targets support multiple target types. See Fake Line Data Model for details.
Common scenarios:
| Scenario | Recommended approach |
|---|---|
| Drag to a canvas point to create a line | Use a CanvasPoint target type or convert mouse events to canvas coordinates yourself. |
| Drag to a custom HTML element | Use HTMLElementId and ensure the element has a stable id. |
| Drag to a small point inside a node | Use NodePoint or place a connection controller inside the node slot. |
| Business ports on the canvas | Place them in #canvas or #canvas-above, and explicitly enable pointer-events. |
9. Performance Recommendations
#canvas content follows canvas zoom. Large amounts of DOM, shadows, filters, or complex images can affect pan and zoom performance.
Recommendations:
- Merge many static background elements into one SVG or Canvas layer when possible.
- Reduce DOM count for frequently changing helper content.
- Keep large overlay layers at
pointer-events: noneby default. - Avoid recalculating complex layout whenever nodes move; cache group box and grid positions.
- If the content is only a visual background and does not need canvas coordinates, consider
#background.
10. FAQ
Why cannot I click a button inside #canvas?
Because .rg-map-canvas uses pointer-events: none by default. Add class="rg-events-all" or style="pointer-events:auto" to the button, and stop event propagation when necessary.
Why does my group box not align with nodes?
The left/top of the group box must use canvas coordinates. Do not use event.clientX/clientY directly; first convert them with getCanvasXyByClientXy or getCanvasXyByViewXy.
Why is my group box covered by nodes?
#canvas is below nodes/lines by design. If it needs to appear above nodes, use #canvas-above. If only the title is covered, put the title separately in #canvas-above or #view.
Why does custom text scale up/down after zooming?
This is expected for #canvas. The whole canvas layer is scaled. If text should keep its screen size, use #view, or apply inverse scaling based on the current canvasZoom inside the canvas layer. The latter adds maintenance complexity.