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:
- Go to Settings → Visualizations
- Click Add Visualization
- Enter package name or upload bundle
- Configure settings
- 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
- Building Molecules — Create molecules with visualizations
- Building Standalone MCP Servers — Add custom AI tools
- TypeScript SDK — API client library