| |
| |
| |
| |
|
|
| import { getLangName, escapeHtml } from './utils.js'; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export function buildTree(nodes, edges, startWord, maxDepth) { |
| if (!nodes || !edges || nodes.length === 0) return null; |
|
|
| |
| const nodeMap = new Map(nodes.map(n => [n.id, n])); |
|
|
| |
| const childToParents = new Map(); |
| for (const edge of edges) { |
| if (!childToParents.has(edge.source)) { |
| childToParents.set(edge.source, []); |
| } |
| childToParents.get(edge.source).push(edge.target); |
| } |
|
|
| |
| const startNodeId = `${startWord.toLowerCase()}|en`; |
| let startNode = nodeMap.get(startNodeId); |
|
|
| |
| if (!startNode) { |
| startNode = nodes.find(n => |
| n.lexeme && n.lexeme.toLowerCase() === startWord.toLowerCase() |
| ); |
| } |
|
|
| if (!startNode) return null; |
|
|
| |
| function buildSubtree(nodeId, depth, visited) { |
| if (depth > maxDepth || visited.has(nodeId)) { |
| return null; |
| } |
|
|
| const node = nodeMap.get(nodeId); |
| if (!node) return null; |
|
|
| visited.add(nodeId); |
|
|
| const children = []; |
| const parentIds = childToParents.get(nodeId) || []; |
|
|
| for (const parentId of parentIds) { |
| const childTree = buildSubtree(parentId, depth + 1, new Set(visited)); |
| if (childTree) { |
| children.push(childTree); |
| } |
| } |
|
|
| return { |
| id: nodeId, |
| lexeme: node.lexeme, |
| lang: node.lang, |
| langName: node.lang_name || getLangName(node.lang), |
| sense: node.sense, |
| family: node.family, |
| branch: node.branch, |
| children, |
| }; |
| } |
|
|
| return buildSubtree(startNode.id, 0, new Set()); |
| } |
|
|
| |
| |
| |
| |
| |
| export function renderTreeHTML(tree) { |
| if (!tree) return '<div class="tree-empty">No tree data available</div>'; |
|
|
| const lines = []; |
|
|
| function renderNode(node, prefix, isLast, isRoot) { |
| |
| const connector = isRoot ? '' : (isLast ? 'βββ ' : 'βββ '); |
| const langDisplay = node.langName || node.lang; |
|
|
| |
| const nodeId = `tree-node-${node.id.replace(/[^a-zA-Z0-9]/g, '-')}`; |
| const senseAttr = node.sense ? ` data-sense="${escapeHtml(node.sense)}"` : ''; |
| const familyAttr = node.family ? ` data-family="${escapeHtml(node.family)}"` : ''; |
| const branchAttr = node.branch ? ` data-branch="${escapeHtml(node.branch)}"` : ''; |
|
|
| const nodeHtml = `<span class="tree-node" id="${nodeId}" data-lexeme="${escapeHtml(node.lexeme)}" data-lang="${escapeHtml(node.lang)}" data-lang-name="${escapeHtml(langDisplay)}"${senseAttr}${familyAttr}${branchAttr}><span class="tree-word">${escapeHtml(node.lexeme)}</span> <span class="tree-lang">(${escapeHtml(langDisplay)})</span></span>`; |
|
|
| lines.push(`<div class="tree-line">${escapeHtml(prefix)}${connector}${nodeHtml}</div>`); |
|
|
| |
| const newPrefix = isRoot ? '' : (prefix + (isLast ? ' ' : 'β ')); |
| node.children.forEach((child, i) => { |
| const childIsLast = i === node.children.length - 1; |
| renderNode(child, newPrefix, childIsLast, false); |
| }); |
| } |
|
|
| renderNode(tree, '', true, true); |
|
|
| return `<div class="tree-content">${lines.join('')}</div>`; |
| } |
|
|
|
|
|
|