Customization
Diagrammatic-UI provides powerful customization options to create tailored graph visualizations. This guide covers custom renderers, styling, and advanced customization patterns.
Custom Node Renderers
For complete control over node appearance, you can create custom node renderers:
import React from 'react';
import { Graph, GraphNodeProps } from 'diagrammatic-ui';
// Custom node renderer component
const CustomNodeRenderer: React.FC<GraphNodeProps> = (props) => {
const {
node,
position,
isHighlighted,
isPathHighlighted,
onNodeClick,
zoomScale = 1
} = props;
// Scale node size based on zoom level
const baseSize = 60;
const size = baseSize / zoomScale;
// Determine color based on node state
let fillColor = '#e6f7ff';
if (isHighlighted) fillColor = '#1890ff';
if (isPathHighlighted) fillColor = '#91d5ff';
return (
<g
transform={`translate(${position.x}, ${position.y})`}
onClick={() => onNodeClick?.(node)}
style={{ cursor: 'pointer' }}
>
{/* Node shape */}
<rect
x={-size/2}
y={-size/2}
width={size}
height={size}
rx={8}
fill={fillColor}
stroke="#1890ff"
strokeWidth={2}
/>
{/* Node label */}
<text
textAnchor="middle"
dominantBaseline="middle"
fill="#000000"
fontSize={14 / zoomScale}
fontWeight={isHighlighted ? 'bold' : 'normal'}
>
{node.label || node.id}
</text>
{/* Custom badge if node has a specific type */}
{node.type === 'important' && (
<circle
cx={size/2 - 5}
cy={-size/2 + 5}
r={8 / zoomScale}
fill="#f5222d"
/>
)}
</g>
);
};
// Use the custom renderer
const CustomNodesGraph = () => {
return (
<Graph
data={graphData}
nodeStyleConfig={{
type: 'custom',
renderer: CustomNodeRenderer
}}
/>
);
};
Custom Edge Rendering
Customize edge appearance with SVG paths:
import React from 'react';
import { Graph } from 'diagrammatic-ui';
const CustomEdgesGraph = () => {
// Define custom edge renderer
const renderEdge = ({ edge, sourcePosition, targetPosition, isHighlighted }) => {
// Calculate control points for a curved edge
const dx = targetPosition.x - sourcePosition.x;
const dy = targetPosition.y - sourcePosition.y;
const controlX = sourcePosition.x + dx / 2;
const controlY = sourcePosition.y + dy / 2 - 50;
// Create SVG path
const path = `M ${sourcePosition.x} ${sourcePosition.y} Q ${controlX} ${controlY}, ${targetPosition.x} ${targetPosition.y}`;
return (
<g>
<path
d={path}
stroke={isHighlighted ? '#1890ff' : '#bfbfbf'}
strokeWidth={isHighlighted ? 2 : 1}
fill="none"
strokeDasharray={edge.type === 'dashed' ? '5,5' : ''}
/>
{edge.label && (
<text
x={controlX}
y={controlY - 10}
textAnchor="middle"
fill="#000000"
fontSize={12}
fontWeight={isHighlighted ? 'bold' : 'normal'}
>
{edge.label}
</text>
)}
</g>
);
};
return (
<Graph
data={graphData}
edgeRenderer={renderEdge}
/>
);
};
Controlled Graph State
For more control over the graph state, you can create a controlled component:
import React, { useState, useCallback } from 'react';
import { Graph, GraphData, Node, Edge, Position } from 'diagrammatic-ui';
const ControlledGraph = () => {
// Maintain graph data in state
const [graphData, setGraphData] = useState<GraphData>({
nodes: [
{ id: '1', label: 'Node 1' },
{ id: '2', label: 'Node 2' }
],
edges: [
{ id: 'e1', source: '1', target: '2' }
]
});
// Track node positions
const [nodePositions, setNodePositions] = useState<Record<string, Position>>({});
// Track selected nodes
const [selectedNodes, setSelectedNodes] = useState<string[]>([]);
// Handle node position changes (dragging)
const handleNodePositionChange = useCallback((nodeId: string, position: Position) => {
setNodePositions(prev => ({
...prev,
[nodeId]: position
}));
}, []);
// Handle node selection
const handleSelectionChange = useCallback((event) => {
setSelectedNodes(event.selectedNodes.map((node: Node) => node.id));
}, []);
// Add a new node
const addNode = useCallback(() => {
const newNodeId = `node-${graphData.nodes.length + 1}`;
setGraphData(prev => ({
...prev,
nodes: [
...prev.nodes,
{ id: newNodeId, label: `Node ${prev.nodes.length + 1}` }
]
}));
}, [graphData.nodes.length]);
// Connect selected nodes
const connectNodes = useCallback(() => {
if (selectedNodes.length === 2) {
const [source, target] = selectedNodes;
const newEdgeId = `edge-${graphData.edges.length + 1}`;
setGraphData(prev => ({
...prev,
edges: [
...prev.edges,
{ id: newEdgeId, source, target }
]
}));
}
}, [selectedNodes, graphData.edges.length]);
return (
<div>
<div className="controls">
<button onClick={addNode}>Add Node</button>
<button
onClick={connectNodes}
disabled={selectedNodes.length !== 2}
>
Connect Selected Nodes
</button>
</div>
<Graph
data={graphData}
interactionOptions={{
selectionEnabled: true,
multiSelectionEnabled: true,
draggingEnabled: true
}}
onSelectionChange={handleSelectionChange}
onNodePositionChange={handleNodePositionChange}
/>
</div>
);
};
Integration with External Libraries
Integrate with D3.js for advanced visualizations:
import React, { useRef, useEffect } from 'react';
import { Graph, GraphData, Node } from 'diagrammatic-ui';
import * as d3 from 'd3';
const D3EnhancedGraph = () => {
const svgRef = useRef(null);
// Custom node renderer that includes a D3 pie chart
const renderNode = (props) => {
const { node, position, isHighlighted } = props;
// Create a unique ID for this node's chart
const chartId = `pie-chart-${node.id}`;
return (
<g transform={`translate(${position.x}, ${position.y})`}>
<rect
x={-50}
y={-50}
width={100}
height={100}
rx={8}
fill={isHighlighted ? '#e6f7ff' : '#ffffff'}
stroke="#1890ff"
strokeWidth={2}
/>
<text
y={-30}
textAnchor="middle"
fill="#000000"
fontSize={14}
>
{node.label}
</text>
{/* Container for D3 to render into */}
<g id={chartId} transform="translate(0, 10)" />
</g>
);
};
// Use D3 to create pie charts inside the nodes
useEffect(() => {
if (!svgRef.current) return;
// For each node with data
graphData.nodes.forEach(node => {
if (!node.data?.chartData) return;
const chartContainer = d3.select(`#pie-chart-${node.id}`);
if (chartContainer.empty()) return;
// Clear previous chart
chartContainer.selectAll('*').remove();
// Create pie chart
const width = 80;
const height = 80;
const radius = Math.min(width, height) / 2;
const pie = d3.pie().value(d => d.value);
const arc = d3.arc().innerRadius(0).outerRadius(radius);
const arcs = chartContainer.selectAll('.arc')
.data(pie(node.data.chartData))
.enter()
.append('g')
.attr('class', 'arc');
arcs.append('path')
.attr('d', arc)
.attr('fill', d => d.data.color);
});
}, [graphData, svgRef.current]);
return (
<div ref={svgRef}>
<Graph
data={graphData}
nodeStyleConfig={{
type: 'custom',
renderer: renderNode
}}
/>
</div>
);
};
Performance Optimization
For large graphs, consider these performance optimizations:
import React, { useMemo } from 'react';
import { Graph, GraphData } from 'diagrammatic-ui';
const LargeGraphOptimized = ({ rawData }) => {
// Filter and process data to reduce complexity
const optimizedData = useMemo(() => {
// Only show important nodes if there are too many
const shouldFilter = rawData.nodes.length > 500;
if (shouldFilter) {
// Filter to only important nodes
const importantNodes = rawData.nodes.filter(
node => node.type === 'important' || node.data?.weight > 10
);
const importantNodeIds = new Set(importantNodes.map(n => n.id));
// Keep only edges between important nodes
const relevantEdges = rawData.edges.filter(
edge => importantNodeIds.has(edge.source) && importantNodeIds.has(edge.target)
);
return {
nodes: importantNodes,
edges: relevantEdges
};
}
return rawData;
}, [rawData]);
return (
<Graph
data={optimizedData}
// Use a simpler layout for large graphs
autoLayout={optimizedData.nodes.length > 200 ? 'grid' : 'force'}
// Disable expensive features for large graphs
interactionOptions={{
draggingEnabled: optimizedData.nodes.length < 300,
zoomEnabled: true,
panningEnabled: true,
// Disable multi-selection for better performance
multiSelectionEnabled: false
}}
// Use simplified node rendering for large graphs
nodeStyleConfig={{
type: optimizedData.nodes.length > 200 ? 'compact' : 'default'
}}
/>
);
};