Skip to main content

Graph

The Graph component has been designed to visualize network graphs with a relatively low number of nodes and links, but with a good level of detail. Nodes can be different shapes, have multiple icons, labels, and more. Links can have arrows, labels, bands and flying particles representing active traffic. There are several built-in graph layouts such as: force layout, dagre, circular, concentric and parallel. During the development of the Graph component, a lot of attention has been paid to the animations and data transitions to make the component act as a complex UI element.

Data

Graph expects the data to be an object with nodes and links arrays:

type GraphData = {
nodes: NodeDatum[];
links: LinkDatum[];
}

The NodeDatum object type in generic and doesn't have any mandatory fields, however we recommend adding unique ids to each node to support better graph animations. Your links will need to have the source and target properties referencing specific nodes either by their index in the nodes array or by their unique id; or they can be references to the actual node objects.

type NodeDatum = {
id?: string;
x?: number;
y?: number;
}

type LinkDatum = {
id?: string;
source: number | string | NodeDatum;
target: number | string | NodeDatum;
}

Basic Configuration

Graph is a component that works inside Single Container. By default, Graph will render your network using force layout and will display nodes as circles.

component.tsx
import { VisSingleContainer, VisGraph } from '@unovis/react'

function Component(props) {
const data: NodeData = props.data

return (
<VisSingleContainer data={data}>
<VisGraph/>
</VisSingleContainer>
)
}
Loading...

Nodes

Shape and Size

Shape and size of the nodes can be set with the nodeShape and nodeSize config properties. They can accept either a constant value or an accessor function.

The following node shapes are supported: Shape.Circle ("circle"), Shape.Square ("square"), Shape.Hexagon ("hexagon"), and Shape.Triangle ("triangle").

nodeSize sets the diameter of the node in pixels at zoom level 1. Keep in mind that Graph will automatically choose zoom level at initialization to fit to the size of the container.

component.tsx
function Component(props) {
const data: NodeData = props.data
const nodeShape = (n: NodeDatum, i: number) => ['square', 'circle', 'hexagon', 'triangle'][i % 4]

return (
<VisGraph nodeShape={nodeShape} nodeSize={50}/>
)
}
Loading...

Custom SVG Shapes

Alternatively, you can provide nodeShape property with custom SVGs to get the exact shape you want. You can either provide it directly as a string in your StringAccessor or for better control over the element, put the shape(s) definition in the container's svgDefs property and reference it with use tag. For more information on providing custom svgDefs, visit our doc about it.

Here is an example using the svgDefs approach and where every node has the following type:

type NodeDatum = {
id: string;
shape: 'blob' | 'cloud' | 'diamond' | 'house'
}
component.tsx
import { VisSingleContainer, VisGraph } from '@unovis/react'

function Component(props) {
const data: NodeData = props.data
const svgDefs = `
<path id="blob" d="M 19.415 1.0564 C 20.585 2.47 20.225 5.89 21.665 8.2612 C 23.06 10.678 26.345 12.046 28.325 14.554 C 30.305 17.1076 31.025 20.8012 28.865 21.9412 C 26.75 23.1268 21.755 21.7588 18.605 23.3092 C 15.455 24.8596 14.105 29.3284 12.485 29.8756 C 10.865 30.3772 8.93 27.0028 6.41 25.042 C 3.89 23.1268 0.83 22.6708 0.38 20.9836 C -0.07 19.2964 2.135 16.378 2.54 13.642 C 2.945 10.9516 1.55 8.4892 2.135 7.03 C 2.72 5.5708 5.285 5.1148 7.355 4.294 C 9.47 3.4276 11.135 2.1964 13.34 1.2388 C 15.545 0.3268 18.245 -0.3116 19.415 1.0564 Z"/>
<path id="cloud" d="M 14 -1 A 9 9 90 0 0 5 8 A 9 9 90 0 0 5.1055 9.3125 A 6 6 90 0 0 1 15 A 6 6 90 0 0 7 21 L 22 21 A 7 7 90 0 0 29 14 A 7 7 90 0 0 22.9414 7.0703 A 9 9 90 0 0 14 -1 z"/>
<polygon id="diamond" points="0,15 15,0 30,15 15,30"/>
<polygon id="house" points="4,18 4,30 13,30 13,24 17,24 17,30 26,30 26,18 30,18 15,0 0,18"/>
`
const nodeLabel = (n: NodeDatum) => n.shape
const nodeShape = (n: NodeDatum) => `<use href = "#${n.shape}" stroke - width = "3" / >`

return (
<VisSingleContainer
svgDefs={svgDefs}
data={data}
>
<VisGraph nodeLabel={nodeLabel} nodeShape={nodeShape}/>
</VisSingleContainer>
)
}
Loading...

Note that depending on your SVG, it may require some additional tweaking to get the appearance you want. For example, custom nodes would require stroke-width, or fill attributes if not already defined. It may also be necessary to tweak your shape size to fit into the bounds of your configured nodeSize, like this example:

<VisGraph
nodeSize={50}
nodeShape="<image width='50' height='50' href='https://upload.wikimedia.org/wikipedia/commons/7/7e/Sphere_-_monochrome_simple.svg'/>"
/>
Loading...

Fill and Stroke

Use the nodeFill and nodeStroke properties to set node's fill and stroke color. A custom stroke width can be provided to nodeStrokeWidth.

component.tsx
function Component(props) {
const data: NodeData = props.data
const nodeFill = (n: NodeDatum, i: number) => getColorByIndex(i)
const nodeStroke = (n: NodeDatum, i: number) => getColorByIndex(i + 1)
const nodeStrokeWidth = (d: NodeDatum, i: number) => 1 + i * 3

return (
<VisGraph
nodeFill={nodeFill}
nodeStroke={nodeStroke}
nodeStrokeWidth={nodeStrokeWidth}
/>
)
}
Loading...

Icons and Labels

In the center

Nodes can have icons in the center, set by providing the nodeIcon accessor function, which returns either a character (if you're using an icon font) or an internal href (if you're defining your icons as custom SVG defs). By default, the icon will fit to the size of the node. You can set custom icon size in pixels with the nodeIconSize property.

When using an icon font or when your SVG icon fill is set to currentColor, the component will automatically choose the icon color (dark or light) based on the node's fill color. You can change the dark and light colors with the --vis-graph-node-icon-fill-color-bright and --vis-graph-node-icon-fill-color-dark CSS variables.

The icon font can be set via the --vis-graph-icon-font-family CSS variable.

note

Icon fonts have different baseline values and sometime you might need to specify a custom dominant-baseline to make the icons be vertically centered. You can achieve that by changing the --vis-graph-node-dominant-baseline CSS variable value (usually between middle and central). The default value is middle

component.tsx
function Component(props) {
const data: NodeData = props.data
const nodeIcon = (n: NodeDatum, i: number) => getNodeIconByIndex(i)
const nodeIconSize = (n: NodeDatum, i: number) => 10 + i
const nodeFill = (n: NodeDatum, i: number) => getColorByIndex(i)

return (
<VisGraph
nodeIcon={nodeIcon}
nodeIconSize={nodeIconSize}
nodeFill={nodeFill}
/>
)
}
Loading...

At the bottom

Node label and sub-label can be set by providing the nodeLabel and nodeSubLabel accessor functions.

tip

By default, long labels will be trimmed and displayed in full only on hover. You can control that behaviour via the nodeLabelTrim and nodeSubLabelTrim config properties.

You can also configure how trimming works by specifying custom nodeLabelTrimMode, nodeLabelTrimLength, nodeSubLabelTrimMode, and nodeSubLabelTrimLength values.

<VisGraph nodeLabel={nodeLabel} nodeSubLabel={nodeSubLabel}/>
Loading...

The font size of the labels can be controlled via CSS variables. Here is a full list of the variables available to control the appearance of the node labels, and their default values:

--vis-graph-node-label-font-size: 9pt;
--vis-graph-node-label-background: #ffffff;
--vis-graph-node-label-text-color: #0F1E57;
--vis-graph-node-sublabel-text-color: #989aa3;
--vis-graph-node-sublabel-font-size: 8pt;
--vis-graph-node-label-font-family: var(--vis-font-family);

/* Dark Theme */
--vis-dark-graph-node-label-background: var(--vis-color-grey);
--vis-dark-graph-node-label-text-color: #ffffff;
--vis-dark-graph-node-sublabel-text-color: #989aa3;

On the side

The third option to add labels to the nodes is nodeSideLabels. Side labels are small circular labels in the top right corner of the node. One node can have multiple side labels at once. You can configure them by providing an accessor function to nodeSideLabels that returns an array of GraphCircleLabel objects:

type GraphCircleLabel = {
text: string;
textColor?: string; // Optional text color. By default, the text color will depend on the brightness of the circle color.
color?: string | null; // Optional color
cursor?: string | null; // Optional cursor on hover
fontSize?: string | null; // Optional font size as a CSS string
radius?: number; // Circle radius in pixels
}
<VisGraph nodeSideLabels={nodeSideLabels}/>
Loading...

You can configure the default appearance of the side labels with the following CSS variables:

--vis-graph-node-side-label-background-fill-color: #a0a9af;
--vis-graph-node-side-label-background-stroke-color: #ffffff;
--vis-graph-node-side-label-fill-color-bright: #ffffff;
--vis-graph-node-side-label-fill-color-dark: #494b56;

// Dark Theme
--vis-dark-graph-node-side-label-background-fill-color: #989aa3;
--vis-dark-graph-node-side-label-background-stroke-color: var(--vis-color-grey);
--vis-dark-graph-node-side-label-fill-color-bright: #f1f4f7;
--vis-dark-graph-node-side-label-fill-color-dark: var(--vis-color-grey);

Gauge Outline

Nodes can also have a gauge outline on top of stroke, if you provide an accessor function to nodeGaugeValue returning a number in the range of [0, 1]. The width of the gauge is linked to the nodeStrokeWidth value.

You can set the color of the gauge with nodeGaugeFill. The default gauge color is configurable via the --vis-graph-node-stroke-segment-color CSS variable.

component.tsx
function Component(props) {
const data: NodeData = props.data
const nodeGaugeFill = (n: NodeDatum, i: number) => gradient(i * 10)
const nodeGaugeValue = (n: NodeDatum, i: number) => i * 10
const nodeLabel = (n: NodeDatum, i: number) => `${i * 10}%`

return (
<VisGraph
nodeGaugeFill={nodeGaugeFill}
nodeGaugeValue={nodeGaugeValue}
nodeLabel={nodeLabel}
/>
)
}
Loading...

Disabled State

Nodes can have a disabled (greyed out) state configured by the nodeDisabled property.

component.tsx
function Component(props) {
const data: NodeData = props.data
const nodeDisabled = (n: NodeDatum, i: number) => n.value < 50
const nodeLabel = (n: NodeDatum) => n.value.toFixed(0)

return (
<VisGraph nodeDisabled={nodeDisabled} nodeLabel={nodeLabel}/>
)
}
Loading...

The disabled state appearance can be redefined with these CSS variables:

--vis-graph-node-greyout-color: #ebeff7;
--vis-graph-node-icon-greyout-color: #c6cad1;
--vis-graph-node-side-label-background-greyout-color: #f1f4f7;

// Dark Theme
--vis-dark-graph-node-greyout-color: #494b56;
--vis-dark-graph-node-icon-greyout-color: var(--vis-color-grey);
--vis-dark-graph-node-side-label-background-greyout-color: #f1f4f7;

Custom Rendering 1.5

The Graph component offers extensive customization options for node rendering, allowing you to define how nodes are displayed at various stages of their lifecycle — such as on entering, updating, zooming, and exiting. You can inject custom rendering functions using the following configuration properties:

  • nodeEnterCustomRenderFunction: Customize the node rendering when a node enters the DOM.
  • nodeUpdateCustomRenderFunction: Define the rendering when a node updates its position or properties.
  • nodePartialUpdateCustomRenderFunction: Partially update nodes on specific interactions like mouseover, background click, and brushing.
  • nodeExitCustomRenderFunction: Customize how nodes are rendered when they exit the DOM.
  • nodeOnZoomCustomRenderFunction: Adjust node rendering dynamically during zooming or panning.

These functions provide access to each node’s data (datum), the node’s DOM element selection (g), the component configuration (config), and the current zoom level (zoomLevel). This gives you full control to modify elements such as SVG shapes, colors, labels, icons, and more.

import { select, Selection } from 'd3-selection'
import { GraphNode, GraphConfigInterface } from '@unovis/ts'

export const nodeEnterCustomRenderFunction = (
datum: GraphNode,
g: Selection<SVGGElement, GraphNode, null, unknown>,
config: GraphConfigInterface
) => {
// Initial rendering logic for the node
g.append('circle')

// Add custom icons, labels, or any additional elements
g.append('text')
.attr('dy', -10)
.attr('text-anchor', 'middle')
}

export const nodeUpdateCustomRenderFunction = (
datum: GraphNode,
g: Selection<SVGGElement, GraphNode, null, unknown>,
config: GraphConfigInterface
) => {
// Update the node's size, color
g.select('circle')
.attr('r', config.nodeSize ?? 20)
.style('fill', config.nodeFill ?? 'steelblue')

// Update labels or any custom elements based on node data
g.select('text')
.text(datum.id)
}

Using these functions, you can create highly customized and dynamic node appearances, adapting the visual representation based on data or user interaction. These functions are invoked for each node in the graph, providing flexibility for various use cases, from static iconography to interactive, animated elements.

Color, Width and Type

Set link color and width with the linkStroke and linkWidth properties. The default link color can be set with the --vis-graph-link-stroke-color CSS variable (and --vis-dark-graph-node-greyout-color for the dark theme). Use the --vis-graph-link-stroke-opacity variable to set link opacity.

If you want a link to be dashed, you can achieve that by providing an accessor function to linkStyle returning either GraphLinkStyle.Dashed ("dashed") or GraphLinkStyle.Solid ("solid"). The dashes length and spacing can be changed with the --vis-graph-link-dashed-stroke-dasharray CSS variable.

<VisGraph
linkStroke={linkStroke}
linkStyle={linkStyle}
linkWidth={linkWidth}
/>
Loading...

Curvature

You can control link curvature by setting the linkCurvature property to a number in the range of [0, 2]. The default value is 0 which means straight lines.

<VisGraph linkCurvature={1}/>
Loading...

Arrows

Providing an accessor function to linkArrow will turn on arrows display on links. The function should return GraphLinkArrowStyle.Single ("single" or simply true) or GraphLinkArrowStyle.Double ("double") or null.

<VisGraph linkArrow={linkArrow}/>
Loading...

Labels Updated in 1.5

Links can have textual or custom SVG labels. When the label is short (two characters or less) or an SVG href, it'll be rendered with a circular background similarly to node's side labels. Longer labels will have a rectangular background. To enable link labels you'll need to provide a function for linkLabel returning a GraphLinkLabel object to display.

To use custom SVG as labels (available in Unovis 1.5), you'll first need to define it in your container SVG defs and provide the href to your custom SVG definition using the text property of your label. In this case the fontSize property will control the size of your custom SVG label.

<VisGraph linkLabel={linkLabel}/>
Loading...

The default appearance of the labels is controlled with the following CSS variables:

--vis-graph-link-label-font-size: 9pt;

--vis-graph-link-label-background: #e6e9f3;
--vis-graph-link-label-text-color: #18181B;

// Dark Theme
--vis-dark-graph-link-label-background: #3f3f45;
--vis-dark-graph-link-label-text-color: var(--vis-graph-link-label-text-color-bright);

Band

Link can also have a semi-transparent band, which you can turn on by providing a constant value or an accessor function to linkBandWidth. The opacity of the band can be changed with the --vis-graph-link-band-opacity CSS property.

<VisGraph linkBandWidth={linkBandWidth}/>
Loading...

Flying Particles

Set linkFlow to true (or provide an accessor function) to turn on flying particles along the links. Configure the size of the particles with linkFlowParticleSize and linkFlowAnimDuration properties.

<VisGraph
linkFlow={linkFlow}
linkFlowParticleSize={3}
linkFlowAnimDuration={10000}
/>
Loading...

If there are multiple links between two nodes, all of them will be displayed. You can control the spacing between the neighboring links with the linkNeighborSpacing property.

<VisGraph linkNeighborSpacing={3}/>
Loading...

Layout

Force

By default, the layout of the graph will be calculated using d3-force. If you want to change the default force simulation settings, provide a GraphForceLayoutSettings configuration object to the forceLayoutSettings config property.

type GraphForceLayoutSettings = {
/** Preferred Link Distance. Default: `60` */
linkDistance?: number;
/** Link Strength [0:1]. Default: `0.45` */
linkStrength?: number;
/** Charge Force (<0 repulsion, >0 attraction). Default: `-500` */
charge?: number;
/** X-centring force. Default: `0.15` */
forceXStrength?: number;
/** Y-centring force. Default: `0.25` */
forceYStrength?: number;
}

Parallel

If your nodes can be separated into multiple groups, and you want them to be organized by columns or rows, set layoutType to GraphLayoutType.Parallel ("parallel") or GraphLayoutType.ParallelHorizontal ("parallel horizontal") respectively.

You can define the node's group (i.e. column or row) by providing a layoutNodeGroup accessor function.

component.tsx
function Component(props) {
const data: NodeData = props.data
const layoutNodeGroup = (d: NodeDatum, i: number) => (i + 1) % 4

return (
<VisGraph layoutType="parallel" layoutNodeGroup={layoutNodeGroup}/>
)
}
Loading...

You can control the group spacing with the layoutParallelGroupSpacing property. By default, group spacing will be calculated automatically based on the size of the container.

Every group can also have sub-groups, which can be defined by providing the layoutParallelNodeSubGroup accessor function. When there are too many nodes inside one sub-group you can make them wrap and go onto the next column (or row in case of GraphLayoutType.ParallelHorizontal) by specifying the layoutParallelNodesPerColumn value.

<VisGraph
layoutType="parallel"
layoutNodeGroup={layoutNodeGroup}
layoutParallelNodeSubGroup={layoutParallelNodeSubGroup}
layoutParallelNodesPerColumn={2}
/>
Loading...

The sub-groups will be placed on one column (or row in case of GraphLayoutType.ParallelHorizontal) by default. You can make them wrap and form more columns and rows by providing a layoutParallelSubGroupsPerRow value.

<VisGraph
layoutType="parallel"
layoutNodeGroup={layoutNodeGroup}
layoutParallelNodeSubGroup={layoutParallelNodeSubGroup}
layoutParallelNodesPerColumn={2}
layoutParallelSubGroupsPerRow={2}
/>
Loading...

Here's a diagram showing the anatomy of the parallel layouts and their configuration properties: Parallel Layout

Circular and Concentric

There are two circular layout types available: GraphLayoutType.Circular ("circular") and GraphLayoutType.Concentric ("concentric"). The first one just places all the nodes along one circle, the second one allows you to define multiple concentric circles with the layoutNodeGroup accessor function.

<VisGraph layoutType="circular" layoutNodeGroup={layoutNodeGroup}/>
Loading...
<VisGraph layoutType="concentric" layoutNodeGroup={layoutNodeGroup}/>
Loading...

Dagre

Graph also supports Dagre layout, which works especially well for trees and directed acyclic graphs. Dagre can be enabled by setting layoutType to GraphLayoutType.Dagre ("dagre"). You can configure the layout by providing a Dagre configuration object to dagreLayoutSettings.

<VisGraph
layoutType="dagre"
dagreLayoutSettings={dagreLayoutSettings}
/>
Loading...

ELK

Starting from Unovis version 1.1.0 Graph supports The Eclipse Layout Kernel which has several layout algorithms available. You can enable ELK by setting layoutType to GraphLayoutType.Elk (or "elk") and providing the layout configuration via the layoutElkSettings property.

The default configuration is provided for the ELK Layered algorithm which works with hierarchical nodes structure. Since Graph's data model doesn't support hierarchy, we allow specifying it externally via the layoutElkNodeGroups property as an array of grouping function. For example, if your nodes have group and subGroup properties, you can set layoutElkNodeGroups to [d => d.group, d => d.subGroup] and Graph will automatically generate hierarchical data structure for ELK.

Additionally, you can specify individual layout configuration for each group (or layer in the ELK's terms) by providing a function to layoutElkSettings that receives group name as its only argument, and returns an ELK layout configuration object:

layoutElkSettings: layer => {
switch (layer) {
case 'root': return { ... }
case 'group1': return { ... }
...
}
}

The ELK Layered algorithm works best when combined with panels, allowing you to highlight the hierarchical nature of your graph data. Check out the ELK example in the gallery to learn more.

<VisGraph
layoutType="elk"
layoutElkNodeGroups={layoutElkNodeGroups}
nodeLabel={nodeLabel}
panels={panels}
/>
Loading...

Precalculated

If you want to specify node locations, set layoutType to GraphLayoutType.Precalculated(or "precalculated"). Then pass in node positions (x and y) as part of graph data.

Note: if you selected GraphLayoutType.Precalculated but fail to pass in x and y, all your nodes will render at the default positions.

Non-connected nodes aside

If you want non-connected graph nodes to be placed below the layout, set layoutNonConnectedAside to true.

Post-Layout Customization 1.5

The Graph component includes a onLayoutCalculated callback, which provides an opportunity to adjust the positions or properties of nodes and links after the layout has been calculated. This can be useful if you need to make final tweaks or apply additional logic once the layout is determined.

This callback function is triggered with the calculated node and link arrays, allowing you to inspect and modify their properties directly. For example, you can use this callback to enforce specific positioning constraints or adjust node/link styles.

Here’s a basic example that demonstrates how to use the onLayoutCalculated callback to adjust node positions:

const onLayoutCalculated = (nodes: GraphNode<N, L>[], links: GraphLink<N, L>[]) => {
// Modify nodes based on custom criteria
nodes.forEach(node => {
if (node.group === 'special') {
// Set specific positions or styles for nodes in the 'special' group
node.x += 50;
node.y -= 30;
}
});
};

Fitting the graph into container

Graph automatically fits the layout to the container size on every config or data update. However, when the user has moved or zoomed the graph there's some level of tolerance after which automatic fitting will be disabled. The tolerance value can be tweaked with the layoutAutofitTolerance property (default value is 8.0):

  • 0 — Stop fitting after any pan or zoom;
  • Number.POSITIVE_INFINITY — Always fit;

If you need to re-enable autofit after the graph has been panned or zoomed, you can do so by calling the public resetAutofitState() method of the component instance.

To disable the autofit behavior completely set layoutAutofit to false.

Panels

When you use one of the parallel layouts, it can be useful to draw panels around groups and sub-groups. You can define your set of panels by providing an array of GraphPanelConfig to the panels configuration property.

type GraphPanelConfig = {
/** Panel nodes references by unique ids */
nodes: (string|number)[];
/** Panel label */
label?: string;
/** Position of the label */
labelPosition?: Position.Top | Position.Bottom;
/** Color of the panel's border */
borderColor?: string;
/** Border width of the panel in pixels */
borderWidth?: number;
/** Inner padding */
padding?: number;
/** Dashed outline showing that the panel is selected */
dashedOutline?: boolean;
/** Side icon symbol */
sideIconSymbol?: string;
/** Size of the icon as a CSS string. e.g.: `12pt` or `12px` */
sideIconFontSize?: string;
/** Color of the icon */
sideIconSymbolColor?: string;
/** Shape of the icon's background */
sideIconShape?: GraphNodeShape;
/** Size of the icon's background shape */
sideIconShapeSize?: number;
/** Stroke color of the icon's background shape */
sideIconShapeStroke?: string;
/** Cursor, when hovering over the icon */
sideIconCursor?: string;
}

The only thing you need to do to define a panel is to provide an array of node ids — the panel will be drawn around them. Here is an example of a graph, that has the following panels configuration:

const panels = [{
nodes: [...],
label: 'Group 1',
color: '#E3116C',
padding: 1,
dashedOutline: true,
sideIconSymbol: '⚾',
sideIconShape: 'circle',
sideIconShapeStroke: '#777',
sideIconCursor: 'pointer',
sideIconFontSize: '24pt',
sideIconShapeSize: 58,
}]
<VisGraph
layoutType="parallel"
layoutNodeGroup={layoutNodeGroup}
layoutParallelNodeSubGroup={layoutParallelNodeSubGroup}
layoutParallelNodesPerColumn={5}
panels={panels}
/>
Loading...

The default panel appearance can be controlled with these CSS variables:

  --vis-graph-panel-border-color: #E6E9F3;
--vis-graph-panel-border-opacity: 0.9;
--vis-graph-panel-fill-color: #ffffff;

--vis-graph-panel-label-color: #6c778c;
--vis-graph-panel-label-background: #ffffff;
--vis-graph-panel-label-font-family: var(--vis-font-family);
--vis-graph-panel-label-font-size: 10pt;
--vis-graph-panel-label-font-weight: 300;

--vis-graph-panel-dashed-outline-color: #b7b7b7;

--vis-graph-panel-side-icon-symbol-color: #9ea7b8;
--vis-graph-panel-side-icon-shape-fill-color: #ffffff;

/* Dark Theme */
--vis-dark-graph-panel-border-color: var(--vis-color-grey);
--vis-dark-graph-panel-fill-color: #292b34;
--vis-dark-graph-panel-label-color: #E6E9F3;
--vis-dark-graph-panel-label-background: var(--vis-color-grey);
--vis-dark-graph-panel-side-icon-symbol-color: #ffffff;
--vis-dark-graph-panel-side-icon-shape-fill-color: #6c778c;
--vis-dark-graph-panel-border-color: #a0a6ad;

The side icon font family can be set with the --vis-graph-icon-font-family CSS variable.

Node and link selection can be set via the config of the graph, which has two dedicated properties for that: selectedNodeId and selectedLinkId. When a node or link is selected, the graph will highlight it as well as the nodes it is connected to.

If you want to be able to select a node or a link by clicking on them, you can achieve that by setting up an event that will update selectedNodeId or selectedLinkId configuration property of your Graph component.

const events = {
[Graph.selectors.node]: {
click: (d: GraphNode) => {
// Set the selected node id here, e.g.: config.selectedNodeId = d.id
// and trigger the component update if required by your UI framework
}
},
}

Pan / Zoom / Drag

By default, you can pan / zoom the graph and drag its nodes around. That behavior can be disabled by setting the disableZoom and disableDrag properties to false respectively.

The maximum and minimum zoom levels can be set with the zoomScaleExtent property (default is [0.35, 1.25]).

You can also provide a callback function to onZoom if you want to have some custom reaction when the graph is being panned or zoomed.

Graph implements a set of public methods to allow you controlling the view externally:

public zoomIn (increment: number)
public zoomOut (increment: number)
public setZoom (zoomLevel: number)
public fitView ()

If you use React or Angular, you can access the component instance for calling these methods by using useRef or ViewChild respectively.

Callbacks

The Graph component supports a comprehensive set of interaction callbacks, giving you control over node dragging, zooming/panning. These callbacks allow you to add custom behavior and responses during user interactions, enhancing the interactivity and responsiveness of the graph.

Node Dragging Callbacks

You can define custom actions for node dragging with the following callbacks:

  • onNodeDragStart: Triggered when a node drag starts.
  • onNodeDrag: Called continuously as a node is being dragged.
  • onNodeDragEnd: Invoked when a node drag operation ends.

Each of these callbacks receives the node data and the drag event, allowing for actions such as updating other elements based on the dragged node’s position or applying visual effects during the drag.

onNodeDragStart: (n: GraphNode<N, L>, event: D3DragEvent<SVGGElement, GraphNode<N, L>, unknown>) => void | undefined;
onNodeDrag: (n: GraphNode<N, L>, event: D3DragEvent<SVGGElement, GraphNode<N, L>, unknown>) => void | undefined;
onNodeDragEnd: (n: GraphNode<N, L>, event: D3DragEvent<SVGGElement, GraphNode<N, L>, unknown>) => void | undefined;

Zoom and Pan Callbacks

For handling zoom and pan interactions, you can use these callbacks:

  • onZoomStart: Fires when a zoom or pan operation begins.
  • onZoom: Triggered continuously during zooming or panning, providing the current zoom scale and transform details.
  • onZoomEnd: Called when zooming or panning ends.

These callbacks allow you to dynamically adjust graph elements, update UI components, or log zoom and pan activities.

onZoom: (zoomScale: number, zoomScaleExtent: [number, number], event: D3ZoomEvent<SVGGElement, unknown> | undefined, transform: ZoomTransform) => void;
onZoomStart: (zoomScale: number, zoomScaleExtent: [number, number], event: D3ZoomEvent<SVGGElement, unknown> | undefined, transform: ZoomTransform) => void;
onZoomEnd: (zoomScale: number, zoomScaleExtent: [number, number], event: D3ZoomEvent<SVGGElement, unknown> | undefined, transform: ZoomTransform) => void;

Multiple Node Drag

To drag multiple nodes at once, the node selection brush can be activated with the shift key. This behavior can be disabled by setting the disableBrush property to true.

While the shift key is pressed, multiple nodes can be brushed by dragging the mouse over them. When the mouse releases, the selected nodes will be grouped inside a selection rectangle, which can be dragged around as a single entity.

Releasing the shift key will unselect all nodes and hide the selection rectangle.

The brush selection rectangle opacity can be changed with the --vis-graph-brush-selection-opacity CSS variable. The appearance of the "brushed" nodes can be customized with the following CSS variables:

--vis-graph-brushed-node-stroke-color;
--vis-graph-brushed-node-label-text-color;
--vis-graph-brushed-node-icon-fill-color;

Callbacks

Additionally, there are two callback functions in Graph's configuration that can be used to see which nodes are being brushed and dragged:

  • onNodeSelectionBrush: Fires when nodes are selected using the brushing tool. It provides an array of selected nodes and the brush event, making it ideal for grouping or highlighting nodes.
  • onNodeSelectionDrag: Called during a multi-node drag operation, enabling you to manage grouped node movements or apply custom interactions.
onNodeSelectionBrush: (selectedNodes: GraphNode<N, L>[], event: D3BrushEvent<SVGGElement> | undefined) => void;
onNodeSelectionDrag: (selectedNodes: GraphNode<N, L>[], event: D3DragEvent<SVGGElement, GraphNode<N, L>, unknown>) => void;

Post-Render Customization 1.5

The Graph component provides an onRenderComplete callback function that allows you to add custom elements to the graph’s canvas after the rendering is fully complete. This function is especially useful for layering additional elements or annotations on top of the existing nodes and links.

This callback receives several parameters, including the canvas selection (g),arrays of nodes and links, and configuration details. With this access, you can append, update, or transform elements as needed to enhance the visual output.

onRenderComplete (
g: Selection<SVGGElement, unknown, null, undefined>,
nodes: GraphNode<N, L>[],
links: GraphLink<N, L>[],
config: GraphConfigInterface<N, L>,
duration: number,
zoomLevel: number,
width: number,
height: number
) => void;

Events

The following selectors are available for events:

import { Graph } from '@unovis/ts'

const events = {
[Graph.selectors.root]: { ... },
[Graph.selectors.background]: { ... },
[Graph.selectors.node]: { ... },
[Graph.selectors.nodeShape]: { ... },
[Graph.selectors.nodeGauge]: { ... },
[Graph.selectors.nodeSideLabel]: { ... },
[Graph.selectors.nodeLabel]: { ... },
[Graph.selectors.link]: { ... },
[Graph.selectors.linkLine]: { ... },
[Graph.selectors.panel]: { ... },
[Graph.selectors.panelRect]: { ... },
[Graph.selectors.panelSelection]: { ... },
[Graph.selectors.panelLabel]: { ... },
[Graph.selectors.panelLabelText]: { ... },
[Graph.selectors.panelSideIcon]: { ... },
[Graph.selectors.panelSideIconShape]: { ... },
[Graph.selectors.panelSideIconSymbol]: { ... },
}

Component Props

NameTypeDescription
* required property