@aarpaardev/stix-visualizer
v1.3.2
Published
STIX Visualizer is a React-based, enhanced version of the OASIS CTI STIX Visualization project. It offers interactive canvas-based rendering of STIX 2.0 bundles with support for custom nodes, links, labels, and complete styling and behavior control.
Maintainers
Readme
📦 STIX Visualizer
STIX Visualizer is a React-based, enhanced version of the OASIS CTI STIX Visualization project. It offers interactive canvas-based rendering of STIX 2.0 bundles with support for custom nodes, links, labels, and complete styling and behavior control.
🚀 Features
- Visualizes STIX 2.x bundles
- Canvas-based performance with directional links and animations
- Fully customizable via props
- Optional legends with configurable positions
- Zoom/hover/click event hooks
- Storybook integration for isolated component previews
🚧 Try it out / Live Demo
You can interact with the live Storybook demo and explore props such as node interaction and neighbor customization on hover:
Live preview:
Open the demo on Storybook
📦 Installation
npm install @aarpaardev/stix-visualizer
# or
yarn add @aarpaardev/stix-visualizer🔰 Usage
🧩 React (via npm/yarn)
import React, { useEffect, useState } from 'react';
import { Stix2Visualizer } from '@aarpaardev/stix-visualizer';
export default function App() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('https://raw.githubusercontent.com/aarpaardev/stix2-visualizer/main/src/examples/MandiantAPT1Report.json')
.then(res => res.json())
.then(setData);
}, []);
if (!data) return <div>Loading...</div>;
return <Stix2Visualizer data={data} />;
}🌐 HTML (via CDN/UMD)
<!DOCTYPE html>
<html>
<head>
<title>STIX Visualizer Test</title>
<!-- React & ReactDOM -->
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<!-- UMD Bundle of Stix Visualizer -->
<script src="https://unpkg.com/@aarpaardev/stix-visualizer@latest/dist/index.min.js"></script>
</head>
<body>
<div id="root"></div>
<script>
// Load a STIX2 JSON file
fetch('https://raw.githubusercontent.com/aarpaardev/stix2-visualizer/main/src/examples/MandiantAPT1Report.json')
.then(res => res.json())
.then(json => {
props = { data: json };
const Visualizer = window.AaarPaarDevStixVisualizer.Stix2Visualizer;
ReactDOM.render(React.createElement(Visualizer, props), document.getElementById('root'));
});
</script>
</body>
</html>🧩 Props
Stix2VisualizerProps
| Prop | Type | Required | Description |
|--------------------|------------------------|----------|-------------|
| data | StixBundle \| object | ✅ | A valid STIX 2.x Bundle JSON object that defines the entities and relationships to be visualized. Refer to this sample STIX bundle for reference. |
| height | number | ❌ | Height of canvas (default is undefined)|
| width | number | ❌ | With of canvas (default is undefined)|
| nodeOptions | INodeOptions | ❌ | Configuration for visual node appearance (e.g., color, radius, interactivity). See table below. |
| nodeOptions | INodeOptions | ❌ | Configuration for visual node appearance (e.g., color, radius, interactivity). See table below. |
| linkOptions | ILinkOptions | ❌ | Configuration for link appearance and behavior. See table below. |
| noiseOptions | INoiseOptions | ❌ | Configuration for noise options (e.g., "object_refs" relations with "Report" object). See table below. |
| legendOptions | ILegendOptions | ❌ | Options for showing and positioning the legend. See table below. |
| directionOptions | ILinkDirectionOptions| ❌ | Defines directional indicators on links like arrows and particles. See table below. |
| linkLabelOptions | ILabelOptions | ❌ | Options for displaying text labels on links. See table below. |
| nodeLabelOptions | ILabelOptions | ❌ | Options for displaying text labels on nodes. See table below. |
nodeOptions Props
| Property | Type | Default | Description |
| -------------------- | -------------------------------- | ------------- | ---------------------------------------------------------------------------- |
| size | number | 12 | Node size in pixels. |
| disableZoomOnClick | boolean | false | Disables zoom behavior on node click. |
| onHover | (node, ctx, neighbors) => void | (See below) | Callback for when a node is hovered. Highlights neighboring nodes. |
| onClick | (node, ref?) => void | undefined | Callback for when a node is clicked. You can access the graph ref if needed. |
Default onHover
/**
* @param {NodeObject} node Node Object
* @param {CanvasRenderingContext2D} ctx node canvas context
* @param {Set<NodeObject>} neighbors node canvas context
*/
(node: NodeObject, ctx: CanvasRenderingContext2D, neighbors: Set<NodeObject>) => {
Array.from(neighbors.values()).forEach((neighbor: NodeObject) => {
/**
*
* @param {CanvasRenderingContext2D} neighCtx node canvas context
* @param {number} x starting x-axis position of node
* @param {number} y starting y-axis position of node
*/
neighbor.drawHighlight = (
neighCtx: CanvasRenderingContext2D,
x: number,
y: number
): void => {
neighCtx.beginPath();
neighCtx.arc(x, y, 10, 0, Math.PI * 2); // full circle
neighCtx.fillStyle = 'rgba(182, 181, 181, 0.5)';
neighCtx.fill();
neighCtx.stroke();
};
});
node.links?.forEach((link: LinkObject) => {
link.particleWidth = 4;
});
if (node.img && node.x && node.y) {
ctx.drawImage(node.img, node.x - 20 / 2, node.y - 20 / 2, 20, 20);
}
}linkOptions Props
| Prop | Type | Default | Description |
| -------------------- | ----------------------------------------------------------- | --------------------------------- | ----------------------------------------------------------------- |
| width | number | (link: LinkObject) => number | 1 | Width of the link line or a function to calculate it dynamically. |
| curvature | number | 0.25 | Controls how curved the link lines are. |
| distance | number | 60 | Distance between connected nodes. |
| color | string | 'rgba(126,126,126, 0.6)' | Stroke color of the link. |
| disableZoomOnClick | boolean | false | Prevents zoom on link click if set to true. |
| onHover | (link: LinkObject, ctx: CanvasRenderingContext2D) => void | (See below) | Callback invoked when hovering over a link. |
| onClick | (link: LinkObject, ref?: ReactForceRef) => void | undefined | Callback invoked when clicking a link. |
Default onHover
/**
* @param {LinkObject} link link Object
* @param {CanvasRenderingContext2D} ctx node canvas context
*/
(link: LinkObject, ctx: CanvasRenderingContext2D) => {
ctx.strokeStyle = 'rgba(36, 35, 35, 0.6)';
ctx.stroke();
/**
*
* @param {CanvasRenderingContext2D} neighCtx node canvas context
* @param {number} x starting x-axis position of node
* @param {number} y starting y-axis position of node
*/
const drawHighlightFunc = (
neighCtx: CanvasRenderingContext2D,
x: number,
y: number
): void => {
neighCtx.beginPath();
neighCtx.arc(x, y, 10, 0, Math.PI * 2); // full circle
neighCtx.fillStyle = 'rgba(182, 181, 181, 0.5)';
neighCtx.fill();
neighCtx.stroke();
};
if (link.source) {
(link.source as NodeObject).drawHighlight = drawHighlightFunc;
}
if (link.target) {
(link.target as NodeObject).drawHighlight = drawHighlightFunc;
}
}noiseOptions Props
| Prop | Type | Default | Description |
|-----------------|----------------------------|---------------|---------------------------------------------------------------------------------------------|
| ignoreReportObjectRefs | boolean | true | Whether to show the "object_refs" relations to "Report" object or not. |
legendOptions Props
| Prop | Type | Default | Description |
|-----------------|----------------------------|---------------|---------------------------------------------------------------------------------------------|
| display | boolean | true | Whether to show the legend. |
| position | 'top-right' \| 'top-left' \| 'bottom-right' \| 'bottom-left' | 'top-right' | Position of the legend in the container. |
| containerStyle| React.CSSProperties | undefined | Optional custom style object for the legend container.
| displayignoreReportObjectRefsCheckBox| boolean | true | Optional Checkbox to reduce noise by ignoring certain relations (see noiseOption prop). |
nodeLabelOptions Props
| Prop | Type | Default | Description |
|--------------------|-----------------|--------------------------------|-------------------------------------------------------------------|
| font | string | undefined | Font family to use for labels (e.g., 'Arial', 'Roboto'). |
| fontSize | number | 4 | Size of the label font. |
| backgroundColor | string | undefined | Optional background color behind the label. |
| color | string | 'rgba(39, 37, 37, 0.9)' | Color of the label text. |
| display | boolean | true | Whether to display the label. |
| onZoomOutDisplay | boolean | false | Whether to continue showing the label when zoomed out. |
linkLabelOptions Props
| Prop | Type | Default | Description |
|--------------------|-----------------|--------------------------------|-------------------------------------------------------------------|
| font | string | undefined | Font family to use for labels (e.g., 'Arial', 'Roboto'). |
| fontSize | number | 4 | Size of the label font. |
| backgroundColor | string | undefined | Optional background color behind the label. |
| color | string | 'rgba(39, 37, 37, 0.9)' | Color of the label text. |
| display | boolean | true | Whether to display the label. |
| onZoomOutDisplay | boolean | false | Whether to continue showing the label when zoomed out. |
directionOptions Props
| Prop | Type | Default | Description |
|--------------------------------------------|----------------------------------------------|--------------------------------|-----------------------------------------------------------------------------|
| directionSize | number | (link: LinkObject) => number | 4 | Size of the arrow indicating direction. |
| arrowRelativePositions | number | (link: LinkObject) => number | 0.98 | Position of the arrow relative to the link length. |
| directionalParticles | number | (link: LinkObject) => number | 10 | Number of directional particles to display on a link. |
| directionalParticleSize | number | (link: LinkObject) => number | 1 | Size of directional particles. |
| directionalParticleSpeed | number | (link: LinkObject) => number | 0.005 | Speed of directional particles. |
| directionalParticlesAndArrowColor | string | (link: LinkObject) => string | 'rgba(0, 0, 0, 0, 0)' | Color of both arrows and directional particles. |
| onHoverParticlesSize | number | 4 | Size of directional particles on hover. |
| onHoverArrowSize | number | undefined | Optional custom arrow size on hover. |
| displayDirections | boolean | true | Whether to display link directions using arrows. |
| displayParticles | boolean | true | Whether to display directional particles along the links (Greater than 0 will cause the canvas to be continuously redrawn to simulate particle motion). |
📚 Storybook
The project includes a Storybook setup for developing, testing, and showcasing components in isolation.
🔧 Run Storybook locally
To start the Storybook server:
npm run storybookThis will launch Storybook at:
http://localhost:6006You can visually explore all customizable props, states, and interactions of the Stix2Visualizer component from there.
🧱 Interfaces & Types
ILabelOptions
interface ILabelOptions {
font?: string;
fontSize?: number;
backgroundColor?: string;
color?: string;
display?: boolean;
onZoomOutDisplay?: boolean;
}Used to style and control label rendering for nodes and links.
INodeOptions
interface INodeOptions {
size?: number;
disableZoomOnClick?: boolean;
onHover?: (
node: NodeObject,
ctx: CanvasRenderingContext2D,
highlightedNeighbors: Set<NodeObject>
) => void;
onClick?: (node: NodeObject, ref?: ReactForceRef) => void;
}Controls how nodes behave, appear, and respond to interaction.
ILinkOptions
interface ILinkOptions {
width?: ((link: LinkObject) => number) | number;
curvature?: number;
distance?: number;
color?: string;
disableZoomOnClick?: boolean;
onHover?: (link: LinkObject, ctx: CanvasRenderingContext2D) => void;
onClick?: (link: LinkObject, ref?: ReactForceRef) => void;
}Defines link styling, interaction behavior, and rendering logic.
ILinkDirectionOptions
interface ILinkDirectionOptions {
directionSize?: ((link: LinkObject) => number) | number;
arrowRelativePositions?: ((link: LinkObject) => number) | number;
directionalParticles?: ((link: LinkObject) => number) | number;
directionalParticleSpeed?: ((link: LinkObject) => number) | number;
directionalParticleSize?: ((link: LinkObject) => number) | number;
directionalParticlesAndArrowColor?: ((link: LinkObject) => string) | string;
onHoverParticlesSize?: number;
onHoverArrowSize?: number;
displayDirections?: boolean;
displayParticles?: boolean;
}Configures directional arrows and animated particles on links.
⚠️ Performance Note
IfdirectionalParticlesis greater than0, the canvas will be continuously redrawn to simulate particle motion.
This may impact performance on large graphs. Use this option with caution.
ILegendOptions
type LegendPosition =
| 'top-left'
| 'top-right'
| 'bottom-left'
| 'bottom-right'
| 'top-center'
| 'bottom-center';
interface ILegendOptions {
display?: boolean;
position?: LegendPosition;
containerStyle?: React.CSSProperties;
}NodeObject
type NodeObject<NodeType = object> = NodeType & {
id: string | number;
img?: HTMLImageElement;
size?: number;
name?: string;
val?: number;
x?: number;
y?: number;
z?: number;
vx?: number;
vy?: number;
vz?: number;
fx?: number;
fy?: number;
fz?: number;
draw?: (ctx: CanvasRenderingContext2D, x: number, y: number) => void;
drawHighlight?: (ctx: CanvasRenderingContext2D, x: number, y: number) => void;
neighbors?: Array<NodeObject>;
links?: Array<LinkObject>;
[others: string]: unknown;
}; Represents a node in the visualizer. This is a generic structure and can be extended with additional fields as needed.
LinkObject
type LinkObject<NodeType = object, LinkType = object> = LinkType & {
source?: string | number | NodeObject<NodeType>;
target?: string | number | NodeObject<NodeType>;
drawHighlight?: (ctx: CanvasRenderingContext2D) => void;
particleWidth?: number;
color?: string;
[others: string]: unknown;
};Defines a connection between two nodes. Can be enriched with custom properties.
🛠 Development
Follow these steps to set up the project locally for development:
🔧 Prerequisites
- Node.js (version 20.10.0 or above recommended)
- npm or yarn
📥 Clone the repository and run
git clone https://github.com/your-org/stix-visualizer.git
cd stix-visualizer
npm run storybook🤝 Contributing
Contributions are welcome!
Fork the repository
Create your feature branch:
git checkout -b feature/amazing-featureCommit your changes:
git commit -m 'Add amazing feature'Push to the branch:
git push origin feature/amazing-featureOpen Pull Request
