HTML Node
Overview
HTML node is a custom rectangular area used to display HTML content. It allows you to embed arbitrary HTML elements within graph nodes, providing great flexibility for creating complex custom nodes.
Use Cases:
- Used to represent complex custom nodes such as tables, charts, or rich text
- Suitable for representing custom visual elements or interactive components
- Commonly used in custom charts, UI design, dashboards, and other scenarios
- When you need to embed interactive elements like forms and buttons in nodes
Framework Support
💡 Tips:
- React Projects: Recommended to use React Node for better component-based development experience
- Vue Projects: Vue Node is not currently supported, community contributions are welcome
- Native HTML: The HTML node introduced in this document is suitable for native HTML development
Online Demo
import { createGraph } from '/demo-runtime.ts';
createGraph(
{
data: {
nodes: [
{
id: 'node1',
style: {
x: 300,
y: 110,
size: [120, 40],
innerHTML: `
<div style="width: 100%; height: 100%; background: #7e3feb; display: flex; justify-content: center; align-items: center;">
<span style="color: #fff; font-size: 12px;">
HTML Node
</span>
</div>`,
},
},
],
},
node: { type: 'html' },
plugins: [{ type: 'grid-line', size: 30 }],
},
{ width: 600, height: 220 },
(gui, graph) => {
gui.add({ type: 'html' }, 'type').disable();
const options = {
size: 50,
innerHTML: `
<div style="width: 100%; height: 100%; background: #7863FF; display: flex; justify-content: center; align-items: center;">
<span style="color: #fff; font-size: 20px;">
'HTML Node'
</span>
</div>`,
};
const optionFolder = gui.addFolder('html.style');
optionFolder.add(options, 'size', 0, 100, 1);
optionFolder.add(options, 'innerHTML');
optionFolder.onChange(({ property, value }) => {
graph.updateNodeData([{ id: 'node1', style: { [property]: value } }]);
graph.render();
});
},
);
import { Graph } from '@antv/g6';
import GUI from 'lil-gui';
export function addPanel(renderPanel: (gui: GUI) => void) {
const gui = new GUI({ container: document.body });
gui.title('Control');
Object.assign(gui.domElement.style, { position: 'absolute', top: '0', right: '0', zIndex: '10' });
renderPanel(gui);
addEventListener('pagehide', () => gui.destroy(), { once: true });
}
export async function createGraph(
options: ConstructorParameters<typeof Graph>[0],
size: { width?: number; height?: number } = {},
renderPanel?: (gui: GUI, graph: Graph) => void,
) {
const container = document.createElement('div');
Object.assign(container.style, {
width: '100%',
maxWidth: `${size.width || 600}px`,
height: `${size.height || 400}px`,
});
document.getElementById('container')!.append(container);
const graph = new Graph({ ...size, ...options, width: container.clientWidth, container, autoResize: true });
addEventListener('pagehide', () => graph.destroy(), { once: true });
await graph.render();
if (renderPanel) addPanel((gui) => renderPanel(gui, graph));
return container;
}设置 node.type 为 html 以使用 HTML 节点。
Style Configuration
If the element has specific properties, we will list them below. For all common style properties, see BaseNode
| Property | Description | Type | Default | Required |
|---|---|---|---|---|
| dx | Horizontal offset. HTML container defaults to top-left corner as origin, use dx for horizontal offset | number | 0 | |
| dy | Vertical offset. HTML container defaults to top-left corner as origin, use dy for vertical offset | number | 0 | |
| innerHTML | HTML content, can be string or HTMLElement |
string | HTMLElement |
- | ✓ |
Examples
Basic HTML Node
import { Graph } from '@antv/g6';
const graph = new Graph({
container: 'container',
data: {
nodes: [
{ id: 'node-1', data: { location: 'East', status: 'error', ip: '192.168.1.2' } },
{ id: 'node-2', data: { location: 'West', status: 'overload', ip: '192.168.1.3' } },
{ id: 'node-3', data: { location: 'South', status: 'running', ip: '192.168.1.4' } },
],
},
node: {
type: 'html',
style: {
size: [240, 80],
dx: -120,
dy: -40,
innerHTML: (d) => {
const ICON_MAP = {
error: '❌',
overload: '⚡',
running: '✅',
};
const COLOR_MAP = {
error: '#f5222d',
overload: '#faad14',
running: '#52c41a',
};
const {
data: { location, status, ip },
} = d;
const color = COLOR_MAP[status];
return `
<div
style="
width:100%;
height: 100%;
background: ${color}bb;
border: 1px solid ${color};
color: #fff;
user-select: none;
display: flex;
padding: 10px;
border-radius: 8px;
"
>
<div style="display: flex;flex-direction: column;flex: 1;">
<div style="font-weight: bold; font-size: 14px;">
${location} Node
</div>
<div style="font-size: 12px; margin-top: 4px;">
status: ${status} ${ICON_MAP[status]}
</div>
</div>
<div>
<span style="border: 1px solid white; padding: 2px 6px; border-radius: 4px; font-size: 12px;">
${ip}
</span>
</div>
</div>`;
},
},
},
layout: {
type: 'grid',
},
behaviors: ['drag-element', 'zoom-canvas'],
});
graph.render();
import { Graph } from '@antv/g6';
import GUI from 'lil-gui';
export function addPanel(renderPanel: (gui: GUI) => void) {
const gui = new GUI({ container: document.body });
gui.title('Control');
Object.assign(gui.domElement.style, { position: 'absolute', top: '0', right: '0', zIndex: '10' });
renderPanel(gui);
addEventListener('pagehide', () => gui.destroy(), { once: true });
}
export async function createGraph(
options: ConstructorParameters<typeof Graph>[0],
size: { width?: number; height?: number } = {},
renderPanel?: (gui: GUI, graph: Graph) => void,
) {
const container = document.createElement('div');
Object.assign(container.style, {
width: '100%',
maxWidth: `${size.width || 600}px`,
height: `${size.height || 400}px`,
});
document.getElementById('container')!.append(container);
const graph = new Graph({ ...size, ...options, width: container.clientWidth, container, autoResize: true });
addEventListener('pagehide', () => graph.destroy(), { once: true });
await graph.render();
if (renderPanel) addPanel((gui) => renderPanel(gui, graph));
return container;
}HTML Node with Interactive Buttons
import { Graph } from '@antv/g6';
const graph = new Graph({
container: 'container',
data: {
nodes: [
{ id: 'server-1', data: { name: 'Web Server', cpu: 45, memory: 67, status: 'online' } },
{ id: 'server-2', data: { name: 'Database', cpu: 78, memory: 89, status: 'warning' } },
{ id: 'server-3', data: { name: 'Cache Server', cpu: 23, memory: 34, status: 'offline' } },
],
},
node: {
type: 'html',
style: {
size: [280, 210],
dx: -140,
dy: -105,
innerHTML: (d) => {
const { data } = d;
const statusColors = {
online: '#52c41a',
warning: '#faad14',
offline: '#f5222d',
};
return `
<div style="
width: 100%;
height: 100%;
background: #fff;
border: 2px solid ${statusColors[data.status]};
border-radius: 12px;
padding: 16px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<h3 style="margin: 0; font-size: 16px; color: #333;">${data.name}</h3>
<span style="
background: ${statusColors[data.status]};
color: white;
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
">${data.status.toUpperCase()}</span>
</div>
<div style="margin-bottom: 12px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
<span style="font-size: 12px; color: #666;">CPU</span>
<span style="font-size: 12px; color: #333;">${data.cpu}%</span>
</div>
<div style="background: #f0f0f0; height: 6px; border-radius: 3px; overflow: hidden;">
<div style="background: ${data.cpu > 70 ? '#f5222d' : '#52c41a'}; height: 100%; width: ${data.cpu}%; transition: width 0.3s;"></div>
</div>
</div>
<div style="margin-bottom: 12px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
<span style="font-size: 12px; color: #666;">Memory</span>
<span style="font-size: 12px; color: #333;">${data.memory}%</span>
</div>
<div style="background: #f0f0f0; height: 6px; border-radius: 3px; overflow: hidden;">
<div style="background: ${data.memory > 80 ? '#f5222d' : '#1890ff'}; height: 100%; width: ${data.memory}%; transition: width 0.3s;"></div>
</div>
</div>
<div style="display: flex; gap: 8px;">
<button
onclick="handleRestart('${d.id}')"
style="
flex: 1;
padding: 6px 12px;
background: #1890ff;
color: white;
border: none;
border-radius: 6px;
font-size: 12px;
cursor: pointer;
transition: background 0.2s;
"
onmouseover="this.style.background='#40a9ff'"
onmouseout="this.style.background='#1890ff'"
>Restart</button>
<button
onclick="handleMonitor('${d.id}')"
style="
flex: 1;
padding: 6px 12px;
background: #52c41a;
color: white;
border: none;
border-radius: 6px;
font-size: 12px;
cursor: pointer;
transition: background 0.2s;
"
onmouseover="this.style.background='#73d13d'"
onmouseout="this.style.background='#52c41a'"
>Monitor</button>
</div>
</div>`;
},
},
},
layout: {
type: 'grid',
cols: 2,
},
behaviors: ['drag-element', 'zoom-canvas'],
});
// Global functions to handle button clicks
window.handleRestart = (nodeId) => {
console.log(`Restarting server: ${nodeId}`);
alert(`Restarting server ${nodeId}...`);
};
window.handleMonitor = (nodeId) => {
console.log(`Opening monitoring panel: ${nodeId}`);
alert(`Opening monitoring panel for server ${nodeId}`);
};
graph.render();
import { Graph } from '@antv/g6';
import GUI from 'lil-gui';
export function addPanel(renderPanel: (gui: GUI) => void) {
const gui = new GUI({ container: document.body });
gui.title('Control');
Object.assign(gui.domElement.style, { position: 'absolute', top: '0', right: '0', zIndex: '10' });
renderPanel(gui);
addEventListener('pagehide', () => gui.destroy(), { once: true });
}
export async function createGraph(
options: ConstructorParameters<typeof Graph>[0],
size: { width?: number; height?: number } = {},
renderPanel?: (gui: GUI, graph: Graph) => void,
) {
const container = document.createElement('div');
Object.assign(container.style, {
width: '100%',
maxWidth: `${size.width || 600}px`,
height: `${size.height || 400}px`,
});
document.getElementById('container')!.append(container);
const graph = new Graph({ ...size, ...options, width: container.clientWidth, container, autoResize: true });
addEventListener('pagehide', () => graph.destroy(), { once: true });
await graph.render();
if (renderPanel) addPanel((gui) => renderPanel(gui, graph));
return container;
}Form Input HTML Node
import { Graph } from '@antv/g6';
const graph = new Graph({
container: 'container',
data: {
nodes: [
{ id: 'form-1', data: { title: 'User Information', type: 'user-form' } },
{ id: 'form-2', data: { title: 'Configuration Panel', type: 'config-form' } },
],
},
node: {
type: 'html',
style: {
size: [300, 400],
dx: -150,
dy: -200,
innerHTML: (d) => {
const { data } = d;
return `
<div style="
width: 100%;
height: 100%;
background: #fff;
border: 1px solid #d9d9d9;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
">
<h3 style="margin: 0 0 16px 0; color: #333; font-size: 16px;">${data.title}</h3>
<div style="margin-bottom: 12px;">
<label style="display: block; margin-bottom: 4px; font-size: 14px; color: #666;">Name</label>
<input
type="text"
placeholder="Enter name"
style="
width: 100%;
padding: 8px 12px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
"
/>
</div>
<div style="margin-bottom: 12px;">
<label style="display: block; margin-bottom: 4px; font-size: 14px; color: #666;">Email</label>
<input
type="email"
placeholder="Enter email"
style="
width: 100%;
padding: 8px 12px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
"
/>
</div>
<div style="margin-bottom: 16px;">
<label style="display: block; margin-bottom: 4px; font-size: 14px; color: #666;">Role</label>
<select style="
width: 100%;
padding: 8px 12px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
">
<option>Administrator</option>
<option>User</option>
<option>Guest</option>
</select>
</div>
<div style="display: flex; gap: 8px;">
<button
onclick="handleSave('${d.id}')"
style="
flex: 1;
padding: 8px 16px;
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
"
>Save</button>
<button
onclick="handleCancel('${d.id}')"
style="
flex: 1;
padding: 8px 16px;
background: #f5f5f5;
color: #333;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
"
>Cancel</button>
</div>
</div>`;
},
},
},
layout: {
type: 'grid',
cols: 2,
},
behaviors: ['drag-element', 'zoom-canvas'],
});
// Global functions to handle form operations
window.handleSave = (nodeId) => {
console.log(`Saving form: ${nodeId}`);
alert(`Form ${nodeId} saved`);
};
window.handleCancel = (nodeId) => {
console.log(`Canceling form: ${nodeId}`);
alert(`Form ${nodeId} operation canceled`);
};
graph.render();
import { Graph } from '@antv/g6';
import GUI from 'lil-gui';
export function addPanel(renderPanel: (gui: GUI) => void) {
const gui = new GUI({ container: document.body });
gui.title('Control');
Object.assign(gui.domElement.style, { position: 'absolute', top: '0', right: '0', zIndex: '10' });
renderPanel(gui);
addEventListener('pagehide', () => gui.destroy(), { once: true });
}
export async function createGraph(
options: ConstructorParameters<typeof Graph>[0],
size: { width?: number; height?: number } = {},
renderPanel?: (gui: GUI, graph: Graph) => void,
) {
const container = document.createElement('div');
Object.assign(container.style, {
width: '100%',
maxWidth: `${size.width || 600}px`,
height: `${size.height || 400}px`,
});
document.getElementById('container')!.append(container);
const graph = new Graph({ ...size, ...options, width: container.clientWidth, container, autoResize: true });
addEventListener('pagehide', () => graph.destroy(), { once: true });
await graph.render();
if (renderPanel) addPanel((gui) => renderPanel(gui, graph));
return container;
}Usage Notes
1. Performance Optimization
- HTML nodes have higher rendering costs compared to regular graphic nodes, recommend using when node count is small
- Complex HTML structures will affect performance, recommend keeping structure simple
- Avoid using too many animation effects in HTML
2. Event Handling
- Event handling in HTML nodes needs to be implemented through global functions or event delegation
- Recommend mounting event handling functions to the
windowobject to ensure accessibility in HTML strings - Be careful to prevent event bubbling from affecting graph interaction behavior
3. Style Isolation
- HTML node styles may be affected by global page styles
- Recommend using inline styles or ensuring sufficient style specificity
- Consider using CSS-in-JS or style namespaces to avoid style conflicts
4. Responsive Design
- HTML node dimensions are fixed and do not automatically adapt to content
- Need to dynamically calculate node dimensions based on content or use responsive layouts
- Consider display effects at different zoom levels
5. Framework Integration Recommendations
- React Projects: Recommended to use React Node, which allows direct use of React components as node content
- Vue Projects: Vue Node is not currently supported, community contributions are welcome if needed
- Native Projects: HTML nodes are the best choice, providing maximum flexibility