Skip to main content

Building Visualizations

Create custom visualizations and dynamic renderers for your entity data in SixDegree.

Overview

Visualizations in SixDegree are React components that render entity data in custom ways. While the default entity ontology provides a graph view, visualizations allow you to create specialized displays like:

  • Custom charts and graphs
  • Timeline visualizations
  • Network diagrams
  • Custom tables and lists
  • Interactive dashboards
  • Domain-specific renderers

What is a Visualization?

A visualization is a React component that receives entity data and renders it in a custom format. Visualizations can:

  • Display entity attributes in custom layouts
  • Visualize relationships between entities
  • Create interactive controls
  • Integrate with external charting libraries
  • Respond to user interactions

Architecture

┌─────────────────┐
│ SixDegree │
│ Dashboard │
└────────┬────────┘

│ Entity Data

┌─────────────────┐
│ Visualization │
│ Component │
└────────┬────────┘

│ Rendered Output

┌─────────────────┐
│ User │
│ Interface │
└─────────────────┘

Creating a Visualization

Basic Structure

Visualizations are React components that implement a standard interface:

import React from 'react';
import { Widget, WidgetProps } from '@sixdegree/widget-sdk';

interface MyVisualizationProps extends WidgetProps {
// Custom props
}

export const MyVisualization: Widget<MyVisualizationProps> = ({ entities, config }) => {
return (
<div className="my-visualization">
<h3>{config.title}</h3>
<div className="visualization-content">
{entities.map(entity => (
<div key={entity.id}>
{entity.name}
</div>
))}
</div>
</div>
);
};

// Visualization metadata
MyVisualization.metadata = {
id: 'my-visualization',
name: 'My Visualization',
description: 'A custom visualization for displaying entity data',
version: '1.0.0',
supportedEntityTypes: ['Repository', 'User'],
configSchema: {
type: 'object',
properties: {
title: {
type: 'string',
default: 'My Visualization'
}
}
}
};

Visualization Props

Visualizations receive the following props:

interface WidgetProps {
// Entities to render
entities: Entity[];

// Visualization configuration
config: Record<string, any>;

// Environment context
environment: {
id: string;
name: string;
};

// Callbacks
onEntityClick?: (entity: Entity) => void;
onConfigChange?: (config: Record<string, any>) => void;
}

SDK Installation

Installation

npm install @sixdegree/widget-sdk

Or with TypeScript SDK:

npm install @sixdegree/typescript-sixdegree

Core Types

import {
Widget,
WidgetProps,
Entity,
Relationship,
WidgetMetadata,
ConfigSchema
} from '@sixdegree/widget-sdk';

Example Visualizations

Timeline

Display entities on a timeline:

import React from 'react';
import { Widget, WidgetProps } from '@sixdegree/widget-sdk';
import { Timeline } from 'react-vis-timeline';

export const TimelineVisualization: Widget = ({ entities, config }) => {
const items = entities.map(entity => ({
id: entity.id,
content: entity.name,
start: entity.attributes.created_at,
group: entity.type
}));

const groups = [...new Set(entities.map(e => e.type))].map(type => ({
id: type,
content: type
}));

return (
<div className="timeline-visualization">
<Timeline
items={items}
groups={groups}
options={config.options}
/>
</div>
);
};

TimelineVisualization.metadata = {
id: 'timeline',
name: 'Timeline',
description: 'Display entities on a timeline',
version: '1.0.0',
supportedEntityTypes: ['*'],
configSchema: {
type: 'object',
properties: {
dateField: {
type: 'string',
default: 'created_at',
description: 'Entity attribute to use for date'
}
}
}
};

Chart

Create custom charts:

import React from 'react';
import { Widget } from '@sixdegree/widget-sdk';
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts';

export const ChartVisualization: Widget = ({ entities, config }) => {
// Group entities by type and count them
const data = Object.entries(
entities.reduce((acc, entity) => {
acc[entity.type] = (acc[entity.type] || 0) + 1;
return acc;
}, {} as Record<string, number>)
).map(([type, count]) => ({
name: type,
count
}));

return (
<div className="chart-visualization">
<h3>{config.title || 'Entity Distribution'}</h3>
<BarChart width={600} height={300} data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Bar dataKey="count" fill={config.barColor || '#0891b2'} />
</BarChart>
</div>
);
};

ChartVisualization.metadata = {
id: 'chart',
name: 'Chart',
description: 'Display entity data as charts',
version: '1.0.0',
supportedEntityTypes: ['*'],
configSchema: {
type: 'object',
properties: {
title: {
type: 'string',
default: 'Entity Distribution'
},
barColor: {
type: 'string',
default: '#0891b2'
}
}
}
};

Table

Custom table with sorting and filtering:

import React, { useState } from 'react';
import { Widget } from '@sixdegree/widget-sdk';

export const TableVisualization: Widget = ({ entities, config, onEntityClick }) => {
const [sortField, setSortField] = useState(config.defaultSortField || 'name');
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc');

const columns = config.columns || [
{ key: 'name', label: 'Name' },
{ key: 'type', label: 'Type' },
{ key: 'created_at', label: 'Created' }
];

const sortedEntities = [...entities].sort((a, b) => {
const aVal = a.attributes[sortField] || a[sortField];
const bVal = b.attributes[sortField] || b[sortField];
const comparison = String(aVal).localeCompare(String(bVal));
return sortOrder === 'asc' ? comparison : -comparison;
});

return (
<div className="table-visualization">
<table>
<thead>
<tr>
{columns.map(col => (
<th
key={col.key}
onClick={() => {
if (sortField === col.key) {
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
} else {
setSortField(col.key);
setSortOrder('asc');
}
}}
style={{ cursor: 'pointer' }}
>
{col.label}
{sortField === col.key && (sortOrder === 'asc' ? ' ↑' : ' ↓')}
</th>
))}
</tr>
</thead>
<tbody>
{sortedEntities.map(entity => (
<tr
key={entity.id}
onClick={() => onEntityClick?.(entity)}
style={{ cursor: 'pointer' }}
>
{columns.map(col => (
<td key={col.key}>
{entity.attributes[col.key] || entity[col.key]}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
};

TableVisualization.metadata = {
id: 'table',
name: 'Table',
description: 'Display entities in a sortable table',
version: '1.0.0',
supportedEntityTypes: ['*'],
configSchema: {
type: 'object',
properties: {
columns: {
type: 'array',
items: {
type: 'object',
properties: {
key: { type: 'string' },
label: { type: 'string' }
}
}
},
defaultSortField: {
type: 'string',
default: 'name'
}
}
}
};

Configuration

Configuration Schema

Define configurable options using JSON Schema:

const configSchema = {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Visualization title',
default: 'My Visualization'
},
showLegend: {
type: 'boolean',
description: 'Show legend',
default: true
},
maxItems: {
type: 'number',
description: 'Maximum items to display',
default: 10,
minimum: 1,
maximum: 100
},
colors: {
type: 'array',
description: 'Color palette',
items: {
type: 'string',
pattern: '^#[0-9A-Fa-f]{6}$'
},
default: ['#0891b2', '#06b6d4', '#22d3ee']
}
}
};

Runtime Configuration

Access configuration in your visualization:

export const MyVisualization: Widget = ({ config }) => {
const {
title = 'Default Title',
showLegend = true,
maxItems = 10
} = config;

return (
<div>
<h3>{title}</h3>
{/* Use config values */}
</div>
);
};

Advanced Features

Entity Filtering

Filter entities based on criteria:

export const FilteredVisualization: Widget = ({ entities, config }) => {
const filteredEntities = entities.filter(entity => {
// Filter by type
if (config.entityTypes?.length > 0) {
if (!config.entityTypes.includes(entity.type)) {
return false;
}
}

// Filter by attribute
if (config.filterAttribute && config.filterValue) {
const value = entity.attributes[config.filterAttribute];
if (value !== config.filterValue) {
return false;
}
}

return true;
});

return <div>{/* Render filtered entities */}</div>;
};

Relationship Visualization

Display entity relationships:

import React from 'react';
import { Widget } from '@sixdegree/widget-sdk';
import { ForceGraph2D } from 'react-force-graph';

export const RelationshipVisualization: Widget = ({ entities, config }) => {
// Build graph data from entities and relationships
const graphData = {
nodes: entities.map(e => ({
id: e.id,
name: e.name,
type: e.type
})),
links: entities.flatMap(e =>
e.relationships.map(r => ({
source: e.id,
target: r.target_id,
type: r.type
}))
)
};

return (
<ForceGraph2D
graphData={graphData}
nodeLabel="name"
nodeColor={node => config.colorMap[node.type] || '#0891b2'}
linkLabel="type"
/>
);
};

Interactive Controls

Add user controls:

export const InteractiveVisualization: Widget = ({ entities, config, onConfigChange }) => {
const [viewMode, setViewMode] = useState(config.viewMode || 'grid');

const handleViewModeChange = (mode: string) => {
setViewMode(mode);
onConfigChange?.({ ...config, viewMode: mode });
};

return (
<div>
<div className="controls">
<button onClick={() => handleViewModeChange('grid')}>Grid</button>
<button onClick={() => handleViewModeChange('list')}>List</button>
</div>
<div className={`view-${viewMode}`}>
{/* Render based on viewMode */}
</div>
</div>
);
};

Deployment

Package Your Visualization

Create a package.json:

{
"name": "@myorg/sixdegree-visualization-timeline",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"peerDependencies": {
"react": "^18.0.0",
"@sixdegree/widget-sdk": "^1.0.0"
},
"scripts": {
"build": "tsc",
"prepublish": "npm run build"
}
}

Build and Publish

# Build your visualization
npm run build

# Publish to npm
npm publish

# Or publish to private registry
npm publish --registry https://registry.myorg.com

Install in SixDegree

Via Dashboard:

  1. Go to SettingsVisualizations
  2. Click Add Visualization
  3. Enter package name or upload bundle
  4. Configure settings
  5. Enable for your environment

Via CLI:

degree visualization install @myorg/sixdegree-visualization-timeline

Via Terraform:

resource "sixdegree_visualization" "timeline" {
environment_id = sixdegree_environment.prod.id
name = "Timeline"
package = "@myorg/sixdegree-visualization-timeline"
version = "1.0.0"

config = {
dateField = "created_at"
}
}

Best Practices

Performance

  • Virtualize large lists: Use libraries like react-window for long lists
  • Memoize expensive calculations: Use React.useMemo
  • Debounce interactions: Avoid excessive updates
  • Lazy load data: Load only what's visible
import { useMemo } from 'react';
import { FixedSizeList } from 'react-window';

export const OptimizedVisualization: Widget = ({ entities }) => {
const processedData = useMemo(() => {
// Expensive calculation
return entities.map(e => processData(e));
}, [entities]);

return (
<FixedSizeList
height={600}
itemCount={entities.length}
itemSize={50}
width="100%"
>
{({ index, style }) => (
<div style={style}>{processedData[index]}</div>
)}
</FixedSizeList>
);
};

Accessibility

  • Add ARIA labels
  • Support keyboard navigation
  • Provide text alternatives
  • Use semantic HTML
<div role="region" aria-label="Entity timeline">
<button
aria-label="Next page"
onClick={handleNext}
tabIndex={0}
>
Next
</button>
</div>

Error Handling

Handle errors gracefully:

export const RobustVisualization: Widget = ({ entities, config }) => {
try {
if (!entities || entities.length === 0) {
return <EmptyState message="No entities to display" />;
}

// Render visualization
return <div>{/* Visualization content */}</div>;
} catch (error) {
console.error('Visualization error:', error);
return <ErrorState error={error} />;
}
};

Styling

Use CSS modules or styled-components:

import styles from './MyVisualization.module.css';

export const StyledVisualization: Widget = ({ entities }) => {
return (
<div className={styles.container}>
<h3 className={styles.title}>My Visualization</h3>
<div className={styles.content}>
{/* Visualization content */}
</div>
</div>
);
};

Testing

Unit Tests

import { render, screen } from '@testing-library/react';
import { MyVisualization } from './MyVisualization';

describe('MyVisualization', () => {
it('renders entities', () => {
const entities = [
{ id: '1', name: 'Test Entity', type: 'Repository' }
];

render(<MyVisualization entities={entities} config={{}} />);

expect(screen.getByText('Test Entity')).toBeInTheDocument();
});

it('applies configuration', () => {
const config = { title: 'Custom Title' };

render(<MyVisualization entities={[]} config={config} />);

expect(screen.getByText('Custom Title')).toBeInTheDocument();
});
});

Integration Tests

Test with real entity data:

import { SixDegreeClient } from '@sixdegree/typescript-sixdegree';

describe('MyVisualization Integration', () => {
it('renders with real data', async () => {
const client = new SixDegreeClient({ apiKey: process.env.API_KEY });
const entities = await client.entities.list({
environment_id: 'env-123',
type: 'Repository'
});

render(<MyVisualization entities={entities} config={{}} />);

expect(screen.getByRole('region')).toBeInTheDocument();
});
});

Example Repository

Browse example visualizations:

git clone https://github.com/sixdegree-ai/visualization-examples
cd visualization-examples
npm install
npm run dev

Examples included:

  • Timeline
  • Heatmap
  • Network diagram
  • Gantt chart
  • Kanban board
  • Custom table
  • And more

Troubleshooting

Visualization Not Loading

Check:

  • Package is installed correctly
  • Visualization is enabled in environment settings
  • No JavaScript errors in console
  • Dependencies are installed

Performance Issues

  • Profile with React DevTools
  • Check entity count (limit to fewer than 1000)
  • Enable virtualization for large lists
  • Optimize re-renders with React.memo

Styling Issues

  • Check CSS conflicts with dashboard styles
  • Use CSS modules or scoped styles
  • Test in different themes (light/dark)

Next Steps

Resources