Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,22 @@
<i :class="darkMode ? 'ph-sun-bold' : 'ph-moon-bold'"></i>
{{ darkMode ? 'light' : 'dark' }}
</button>
<div class="sep"></div>
<button class="btn" @click="stressTest">
<i class="ph-lightning-bold"></i> stress test
</button>
<div class="spacer"></div>
<button class="btn" @click="settingsModal.open = true">
<i class="ph-sliders-horizontal-bold"></i> settings
</button>
</div>

<!-- FPS OVERLAY -->
<div class="fps-overlay" v-if="settingsModal.showFps">
<div class="fps-line"><b>{{ fpsDisplay.fps }}</b> fps</div>
<div class="fps-line">{{ fpsDisplay.nodes }} nodes · {{ fpsDisplay.edges }} edges</div>
</div>

<!-- CANVAS CONTAINER (label overlay attaches here) -->
<div class="canvas-container" :class="{ light: !darkMode }">
<canvas id="graph-canvas"></canvas>
Expand Down Expand Up @@ -181,6 +191,16 @@
</button>
</div>
</div>

<div class="setting-row">
<label>Show FPS</label>
<div class="toggle-group">
<button class="toggle-btn" :class="{ active: settingsModal.showFps }"
@click="onToggleSetting('showFps')">
{{ settingsModal.showFps ? 'On' : 'Off' }}
</button>
</div>
</div>
</div>

<div class="settings-section">
Expand Down
99 changes: 96 additions & 3 deletions demo/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ interface SettingsModalState {
nodeSize: number;
edgeOpacity: number;
showLabels: boolean;
showFps: boolean;
gravitationalConstant: number;
springLength: number;
springConstant: number;
Expand All @@ -52,6 +53,7 @@ const DEFAULT_SETTINGS: Omit<SettingsModalState, 'open'> = {
nodeSize: 8,
edgeOpacity: 0.8,
showLabels: true,
showFps: false,
gravitationalConstant: -0.25,
springLength: 0.2,
springConstant: 0.06,
Expand Down Expand Up @@ -114,6 +116,10 @@ createApp({
const legendItems = ref<LegendItem[]>([]);
const tagColorCache: Record<string, string> = {};

// FPS display
const fpsDisplay = reactive({ fps: 0, nodes: 0, edges: 0 });
let fpsInterval: number | null = null;

// Computed
const hasSelection = computed(() => selectedNode.value !== null);
const paletteNames = PALETTE_NAMES;
Expand Down Expand Up @@ -279,6 +285,19 @@ createApp({
if (key === 'showLabels') {
settingsModal.showLabels = !settingsModal.showLabels;
g?.setLabelsVisible(settingsModal.showLabels);
} else if (key === 'showFps') {
settingsModal.showFps = !settingsModal.showFps;
if (settingsModal.showFps) {
fpsInterval = window.setInterval(() => {
if (!g) return;
fpsDisplay.fps = g.getFps();
fpsDisplay.nodes = g.nodeCount;
fpsDisplay.edges = g.edgeCount;
}, 250);
} else if (fpsInterval !== null) {
clearInterval(fpsInterval);
fpsInterval = null;
}
}
}

Expand All @@ -288,12 +307,86 @@ createApp({
g.setNodeSize(DEFAULT_SETTINGS.nodeSize);
g.setEdgeOpacity(DEFAULT_SETTINGS.edgeOpacity);
g.setLabelsVisible(DEFAULT_SETTINGS.showLabels);
if (fpsInterval !== null) {
clearInterval(fpsInterval);
fpsInterval = null;
}
if (layoutRunning.value) {
g.stopLayout();
g.startLayout(getPhysicsOpts());
}
}

// ── Stress Test ──

function stressTest(): void {
if (!g) return;

// Stop current layout
if (layoutRunning.value) g.stopLayout();

// Clear existing graph
const graph = g.getGraph();
for (const id of [...graph.activeNodeIds()]) {
g.unput(id);
}

// Use smaller nodes for stress test
g.setNodeSize(4);
settingsModal.nodeSize = 4;

// Generate random graph: 500 nodes, ~1500 edges
const N = 500;
const tags = ['alpha', 'beta', 'gamma', 'delta', 'epsilon'];
const edgeTags = ['connects', 'links', 'references', 'depends'];
const nodeIds: number[] = [];

for (let i = 0; i < N; i++) {
const tag = tags[Math.floor(Math.random() * tags.length)];
const id = g.put(tag, { name: `${tag}-${i}` });
nodeIds.push(id);
}

// Each node gets ~3 random edges on average
const numEdges = Math.floor(N * 3);
for (let i = 0; i < numEdges; i++) {
const src = nodeIds[Math.floor(Math.random() * N)];
let tgt = nodeIds[Math.floor(Math.random() * N)];
if (src === tgt) tgt = nodeIds[(nodeIds.indexOf(src) + 1) % N];
const tag = edgeTags[Math.floor(Math.random() * edgeTags.length)];
g.link(src, tag, tgt);
}

// Scatter positions randomly before layout
g.resetPositions();

// Light pre-stabilization (don't block for too long)
for (let i = 0; i < 50; i++) {
g.stepLayout(3);
}
g.fitView(0.15);

// Start layout and let it settle live
g.startLayout(getPhysicsOpts());
layoutRunning.value = true;
setTimeout(() => g!.fitView(0.15), 500);
setTimeout(() => g!.fitView(0.15), 2000);

updateCounts();
refreshLegend();

// Auto-enable FPS display
if (!settingsModal.showFps) {
settingsModal.showFps = true;
fpsInterval = window.setInterval(() => {
if (!g) return;
fpsDisplay.fps = g.getFps();
fpsDisplay.nodes = g.nodeCount;
fpsDisplay.edges = g.edgeCount;
}, 250);
}
}

// ── Init ──

onMounted(async () => {
Expand Down Expand Up @@ -390,7 +483,7 @@ createApp({

return {
darkMode, layoutRunning, animatedEnabled, activePalette,
nodeCount, edgeCount,
nodeCount, edgeCount, fpsDisplay,
selectedNode, selectedNodeColor, hoveredNode, hoveredNodeColor,
hasSelection,
tooltipVisible, tooltipStyle, tooltipTag, tooltipName, tooltipProps, tooltipColor,
Expand All @@ -399,7 +492,7 @@ createApp({
toggleLayout, fitView, resetGraph, toggleAnimated, toggleDarkMode,
switchPalette, getPalettePreview,
showEditModal, saveEdit, deleteSelected, confirmDelete,
onSettingChange, onToggleSetting, resetSettings,
onSettingChange, onToggleSetting, resetSettings, stressTest,
};
},
}).mount('#app');
Expand Down Expand Up @@ -511,4 +604,4 @@ function populateGraph(g: GraphGPU): void {
g.link(mystic, 'basedOn', mysticB);
g.link(wach1, 'sibling', wach2);
g.link(cruise, 'married', kidman);
}
}
29 changes: 29 additions & 0 deletions demo/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,35 @@ body {
}
}

// ============================================================
// FPS Overlay
// ============================================================

.fps-overlay {
position: fixed;
top: calc(#{$toolbar-h} + 10px);
left: 50%;
transform: translateX(-50%);
z-index: 15;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: $radius;
padding: 6px 14px;
box-shadow: var(--shadow);
font-family: $font-mono;
font-size: 11px;
color: var(--text-dim);
display: flex;
gap: 12px;
pointer-events: none;

b {
color: var(--accent);
font-weight: 600;
font-size: 13px;
}
}

// ============================================================
// Fallback
// ============================================================
Expand Down
Loading