let svg = null;
let g = null;
let tree = null;
let root = null;
let currentLayout = 'vertical'; let zoom = null;
const config = {
nodeRadius: 6,
verticalSpacing: 60,
horizontalSpacing: 120,
transitionDuration: 750,
maxLabelLength: 20
};
export function initTreeViz(containerId) {
const container = document.getElementById(containerId);
if (!container) {
console.error(`Container ${containerId} not found`);
return;
}
container.innerHTML = '';
const width = container.clientWidth;
const height = container.clientHeight;
svg = d3.select(container)
.append('svg')
.attr('id', 'tree-viz-svg')
.attr('width', width)
.attr('height', height);
zoom = d3.zoom()
.scaleExtent([0.1, 3])
.on('zoom', (event) => {
g.attr('transform', event.transform);
});
svg.call(zoom);
g = svg.append('g')
.attr('class', 'tree-group')
.attr('transform', `translate(${width / 2}, 50)`);
console.log('✅ Tree visualization initialized');
}
export function renderTree(cst) {
if (!svg || !g) {
console.error('Tree visualization not initialized');
return;
}
console.log('🔧 TODO: Render D3 tree from CST:', cst);
renderPlaceholder();
}
function convertCstToD3Format(node) {
return {
name: node.type,
value: node.value,
range: node.range,
children: node.children ? node.children.map(convertCstToD3Format) : undefined
};
}
function createTreeLayout() {
const container = document.getElementById('tree-viz-container');
const width = container.clientWidth;
const height = container.clientHeight;
if (currentLayout === 'vertical') {
return d3.tree()
.size([width - 100, height - 100])
.separation((a, b) => a.parent === b.parent ? 1 : 2);
} else {
return d3.tree()
.size([height - 100, width - 100])
.separation((a, b) => a.parent === b.parent ? 1 : 2);
}
}
function renderNodes(nodes) {
}
function renderLinks(links) {
}
function renderPlaceholder() {
g.selectAll('*').remove();
g.append('text')
.attr('text-anchor', 'middle')
.attr('fill', 'var(--placeholder-color)')
.attr('font-size', '18px')
.attr('y', 100)
.text('🌳 Tree visualization coming soon!');
g.append('text')
.attr('text-anchor', 'middle')
.attr('fill', 'var(--placeholder-color)')
.attr('font-size', '14px')
.attr('y', 130)
.text('This will show an interactive D3.js tree of the parsed code');
}
export function toggleLayout() {
currentLayout = currentLayout === 'vertical' ? 'horizontal' : 'vertical';
const icon = document.getElementById('layout-icon');
if (icon) {
icon.textContent = currentLayout === 'vertical' ? '↔️' : '↕️';
}
if (root) {
renderTree(root);
}
console.log(`🔄 Switched to ${currentLayout} layout`);
}
export function fitToScreen() {
if (!svg || !g) return;
const bounds = g.node().getBBox();
const parent = svg.node().parentElement;
const fullWidth = parent.clientWidth;
const fullHeight = parent.clientHeight;
const width = bounds.width;
const height = bounds.height;
const midX = bounds.x + width / 2;
const midY = bounds.y + height / 2;
if (width === 0 || height === 0) return;
const scale = 0.9 / Math.max(width / fullWidth, height / fullHeight);
const translate = [
fullWidth / 2 - scale * midX,
fullHeight / 2 - scale * midY
];
svg.transition()
.duration(750)
.call(zoom.transform, d3.zoomIdentity
.translate(translate[0], translate[1])
.scale(scale));
console.log('📐 Fitted tree to screen');
}
export function resetZoom() {
if (!svg || !zoom) return;
const container = document.getElementById('tree-viz-container');
const width = container.clientWidth;
const height = container.clientHeight;
svg.transition()
.duration(750)
.call(zoom.transform, d3.zoomIdentity
.translate(width / 2, 50)
.scale(1));
console.log('🔄 Reset zoom');
}
function handleNodeClick(nodeData) {
console.log('Node clicked:', nodeData);
}
function showNodeDetails(nodeData) {
const detailsPanel = document.getElementById('tree-node-details');
const detailsContent = document.getElementById('node-details-content');
if (!detailsPanel || !detailsContent) return;
detailsContent.innerHTML = `
<p><strong>Type:</strong> ${nodeData.name}</p>
${nodeData.value ? `<p><strong>Value:</strong> ${nodeData.value}</p>` : ''}
${nodeData.range ? `<p><strong>Range:</strong> [${nodeData.range[0]}..${nodeData.range[1]}]</p>` : ''}
<p><strong>Depth:</strong> ${nodeData.depth}</p>
<p><strong>Children:</strong> ${nodeData.children ? nodeData.children.length : 0}</p>
`;
detailsPanel.classList.remove('hidden');
}
export function clearTree() {
if (!g) return;
g.selectAll('*').remove();
root = null;
renderPlaceholder();
}
export function getTreeStats(cst) {
let nodeCount = 0;
let maxDepth = 0;
function traverse(node, depth) {
nodeCount++;
maxDepth = Math.max(maxDepth, depth);
if (node.children) {
node.children.forEach(child => traverse(child, depth + 1));
}
}
traverse(cst, 0);
return {
nodeCount,
maxDepth
};
}
export function exportAsSvg() {
console.log('🔧 TODO: Export tree as SVG');
}
export function exportAsPng() {
console.log('🔧 TODO: Export tree as PNG');
}
export default {
initTreeViz,
renderTree,
toggleLayout,
fitToScreen,
resetZoom,
clearTree,
getTreeStats,
exportAsSvg,
exportAsPng
};