<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy"
content="default-src 'none';
script-src 'unsafe-inline';
style-src 'unsafe-inline';
img-src data: blob:;">
<title>{{TITLE}}</title>
<style>
{{CSS}}
/* Community-specific styles */
.community-header {
margin-bottom: var(--daemon-space-lg);
}
.community-header__title {
display: flex;
align-items: center;
gap: var(--daemon-space-md);
margin-bottom: var(--daemon-space-sm);
}
.community-header__count {
color: var(--daemon-text-muted);
font-size: 0.875rem;
}
</style>
</head>
<body>
<!-- Real-time update notification badge -->
<div id="update-indicator" class="daemon-update-badge" role="status" aria-live="polite">
<span class="daemon-update-badge__dot"></span>
<span>New data available</span>
<button id="refresh-btn" class="daemon-btn daemon-btn--small">Refresh</button>
</div>
<div id="app">
<div class="daemon-container" style="padding: var(--daemon-space-lg);">
<!-- Header Section -->
<header class="community-header">
<div class="community-header__title">
<h1>Community Cluster Map</h1>
<span class="community-header__count">
{{COMMUNITY_COUNT}} communities
</span>
</div>
</header>
<!-- Breadcrumb Navigation -->
<nav class="treemap-breadcrumb" id="breadcrumb">
{{BREADCRUMB}}
</nav>
<!-- Treemap Container -->
<div class="treemap-container" id="treemap"></div>
<!-- Tooltip (hidden by default) -->
<div class="treemap-tooltip" id="tooltip">
<div class="treemap-tooltip__title" id="tooltip-title"></div>
<div class="treemap-tooltip__summary" id="tooltip-summary"></div>
<div class="treemap-tooltip__meta">
<span id="tooltip-count"></span>
<span id="tooltip-level"></span>
</div>
</div>
</div>
</div>
<script>
{{SCRIPT}}
// Community Cluster Map - D3 Treemap
(function() {
// Parse treemap data from template
var treemapData = {{TREEMAP_DATA}};
var currentPath = {{CURRENT_PATH}};
// Container dimensions
var container = document.getElementById('treemap');
var width = container.clientWidth;
var height = container.clientHeight;
// Tooltip element
var tooltip = document.getElementById('tooltip');
var tooltipTitle = document.getElementById('tooltip-title');
var tooltipSummary = document.getElementById('tooltip-summary');
var tooltipCount = document.getElementById('tooltip-count');
var tooltipLevel = document.getElementById('tooltip-level');
/**
* Build D3 treemap from hierarchical data
*/
function buildTreemap(data) {
// Clear existing cells
container.innerHTML = '';
// Create hierarchy
var root = d3.hierarchy(data)
.sum(function(d) { return d.member_count || 0; })
.sort(function(a, b) { return b.value - a.value; });
// Create treemap layout
var treemap = d3.treemap()
.size([width, height])
.padding(2)
.round(true);
treemap(root);
// Render cells (skip root node)
var leaves = root.descendants().slice(1);
leaves.forEach(function(node) {
var cell = document.createElement('div');
cell.className = 'treemap-cell treemap-cell--level-' + Math.min(node.data.level || 0, 5);
cell.style.left = node.x0 + 'px';
cell.style.top = node.y0 + 'px';
cell.style.width = (node.x1 - node.x0) + 'px';
cell.style.height = (node.y1 - node.y0) + 'px';
// Only show label if cell is large enough
var cellWidth = node.x1 - node.x0;
var cellHeight = node.y1 - node.y0;
if (cellWidth > 60 && cellHeight > 40) {
var label = document.createElement('span');
label.className = 'treemap-cell__label';
label.textContent = node.data.name || 'Community ' + node.data.id;
cell.appendChild(label);
}
// Show member count if cell is large enough
if (cellWidth > 40 && cellHeight > 30) {
var count = document.createElement('span');
count.className = 'treemap-cell__count';
count.textContent = node.value;
cell.appendChild(count);
}
// Store data on element
cell.dataset.id = node.data.id || '';
cell.dataset.name = node.data.name || '';
cell.dataset.summary = node.data.summary || '';
cell.dataset.memberCount = node.value;
cell.dataset.level = node.data.level || 0;
cell.dataset.hasChildren = (node.children && node.children.length > 0) ? 'true' : 'false';
cell.dataset.action = 'drill-down';
// Apply leaf class for cells without children (no drill-down available)
if (cell.dataset.hasChildren === 'false') {
cell.classList.add('treemap-cell--leaf');
}
// Event handlers
cell.addEventListener('mouseenter', showTooltip);
cell.addEventListener('mousemove', moveTooltip);
cell.addEventListener('mouseleave', hideTooltip);
cell.addEventListener('click', handleCellClick);
container.appendChild(cell);
});
}
/**
* Show tooltip on hover
*/
function showTooltip(e) {
var cell = e.currentTarget;
tooltipTitle.textContent = cell.dataset.name;
tooltipSummary.textContent = cell.dataset.summary || 'No description available';
tooltipCount.textContent = cell.dataset.memberCount + ' members';
tooltipLevel.textContent = 'Level ' + cell.dataset.level;
tooltip.classList.add('treemap-tooltip--visible');
}
/**
* Move tooltip with cursor
*/
function moveTooltip(e) {
var x = e.clientX + 15;
var y = e.clientY + 15;
// Keep tooltip in viewport
var tooltipRect = tooltip.getBoundingClientRect();
if (x + tooltipRect.width > window.innerWidth) {
x = e.clientX - tooltipRect.width - 15;
}
if (y + tooltipRect.height > window.innerHeight) {
y = e.clientY - tooltipRect.height - 15;
}
tooltip.style.left = x + 'px';
tooltip.style.top = y + 'px';
}
/**
* Hide tooltip
*/
function hideTooltip() {
tooltip.classList.remove('treemap-tooltip--visible');
}
/**
* Handle cell click for drill-down
*/
function handleCellClick(e) {
var cell = e.currentTarget;
var communityId = cell.dataset.id;
var hasChildren = cell.dataset.hasChildren === 'true';
if (hasChildren && communityId && window.SecureMessenger) {
// Emit drill-down event via SecureMessenger
SecureMessenger.send('tool_request', {
tool: 'list_communities',
args: { parent_community_id: parseInt(communityId, 10) }
});
}
}
/**
* Handle breadcrumb navigation
*/
function initBreadcrumbs() {
var breadcrumb = document.getElementById('breadcrumb');
var items = breadcrumb.querySelectorAll('.treemap-breadcrumb__item');
items.forEach(function(item) {
if (!item.classList.contains('treemap-breadcrumb__item--current')) {
item.addEventListener('click', function() {
var id = this.dataset.id;
if (window.SecureMessenger) {
if (id === 'root') {
// Navigate to root
SecureMessenger.send('tool_request', {
tool: 'list_communities',
args: {}
});
} else {
// Navigate to specific parent
SecureMessenger.send('tool_request', {
tool: 'list_communities',
args: { parent_community_id: parseInt(id, 10) }
});
}
}
});
}
});
}
/**
* Handle window resize
*/
function handleResize() {
width = container.clientWidth;
height = container.clientHeight;
buildTreemap(treemapData);
}
// Initialize
document.addEventListener('DOMContentLoaded', function() {
buildTreemap(treemapData);
initBreadcrumbs();
// Debounced resize handler
var resizeTimeout;
window.addEventListener('resize', function() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(handleResize, 250);
});
});
})();
// Real-time update notification receiver
(function() {
var updateTimeout = null;
var DEBOUNCE_MS = 300;
var indicator = document.getElementById('update-indicator');
var refreshBtn = document.getElementById('refresh-btn');
if (window.SecureMessenger) {
SecureMessenger.on('data_updated', function(data) {
clearTimeout(updateTimeout);
updateTimeout = setTimeout(function() {
if (indicator) {
indicator.classList.add('daemon-update-badge--visible');
indicator.setAttribute('data-last-update', data.last_update || '');
}
}, DEBOUNCE_MS);
});
}
if (refreshBtn) {
refreshBtn.addEventListener('click', function() {
if (indicator) {
indicator.classList.remove('daemon-update-badge--visible');
}
if (window.SecureMessenger) {
SecureMessenger.send('tool_request', { tool: 'refresh_ui' });
}
});
}
})();
</script>
</body>
</html>