Skip to content

Commit 6192519

Browse files
Recursive TreeNode component
1 parent 8c90044 commit 6192519

6 files changed

Lines changed: 146 additions & 164 deletions

File tree

Lines changed: 4 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,10 @@
11
<div class="border border-secondary rounded overflow-hidden">
22
<div class="card-header text-bg-secondary p-3">
3-
<h3 class="h5 fw-bold">{{ getTreeTypeDisplay(tree().type) }}</h3>
3+
<h3 class="h5 fw-bold">{{ treeType() }}</h3>
44
</div>
55
<div class="p-4">
6-
<table class="parse-tree-table" laParseTreeHighlight>
7-
<tbody>
8-
@for (row of tableRows(); track $index) {
9-
<tr>
10-
@for (cell of row; track $index) {
11-
<td
12-
[attr.colspan]="cell.colspan"
13-
class="p-2 align-top text-center border"
14-
[class]="cell.type + '-cell'"
15-
>
16-
@if (cell.rule) {
17-
<span class="badge rounded pill text-bg-secondary">
18-
{{ cell.rule }}
19-
</span>
20-
}
21-
22-
@switch (cell.type) {
23-
@case ('leaf') {
24-
<div class="d-flex flex-column align-items-center gap-2">
25-
<div class="h5 mb-0">
26-
<strong>{{ cell.lem }}</strong>
27-
</div>
28-
<div class="text-secondary">
29-
<small>{{ cell.tok }}</small>
30-
<br />
31-
<small>{{ cell.pos }}</small>
32-
<br />
33-
<small>{{ cell.ner }}</small>
34-
</div>
35-
<div
36-
class="category"
37-
[innerHTML]="
38-
cell.content | subscriptAngleBrackets
39-
"
40-
></div>
41-
</div>
42-
}
43-
44-
@case ('var') {
45-
<div class="d-flex flex-column gap-1">
46-
<strong class="h5 fw-bold mb-0">
47-
{{ cell.content }}
48-
</strong>
49-
<br />
50-
<span>{{ cell.typeInfo }}</span>
51-
</div>
52-
}
53-
54-
@case ('node') {
55-
<p
56-
class="mb-0"
57-
[innerHTML]="cell.content | subscriptAngleBrackets"
58-
></p>
59-
}
60-
}
61-
</td>
62-
}
63-
</tr>
64-
}
65-
</tbody>
66-
</table>
6+
<div class="d-flex border border-secondary">
7+
<la-tree-node [node]="rootNode()" />
8+
</div>
679
</div>
6810
</div>
Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +0,0 @@
1-
td {
2-
transition: background-color 0.2s ease;
3-
4-
&.highlight {
5-
background-color: var(--bs-yellow);
6-
}
7-
}
8-
9-
.leaf-cell,
10-
.var-cell {
11-
background-color: var(--bs-light);
12-
min-width: 5rem;
13-
}
Lines changed: 41 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,6 @@
11
import { Component, computed, input } from '@angular/core';
22
import { BinaryNode, LeafNode, ParseTree, ParseTreeNode, ParseTreeType, UnaryNode, VariableNode } from '../types';
3-
import { SubscriptAngleBracketsPipe } from './subscript-angle-brackets.pipe';
4-
import { ParseTreeHighlightDirective } from './parse-tree-highlight.directive';
5-
6-
interface BaseCell {
7-
content: string;
8-
rule?: string;
9-
colspan: number;
10-
}
11-
12-
interface NodeCell extends BaseCell {
13-
type: "node";
14-
}
15-
16-
interface LeafCell extends BaseCell {
17-
type: "leaf";
18-
tok: string;
19-
lem: string;
20-
pos: string;
21-
ner: string;
22-
}
23-
24-
interface VarCell extends BaseCell {
25-
type: "var";
26-
typeInfo: string;
27-
}
28-
29-
type TableCell = NodeCell | LeafCell | VarCell;
3+
import { TreeNodeComponent, TreeNodeDisplay } from './tree-node.component';
304

315
const TreeTypeDisplay: Record<ParseTreeType, string> = {
326
[ParseTreeType.CCG_DERIVATION]: 'CCG Derivation',
@@ -38,23 +12,22 @@ const TreeTypeDisplay: Record<ParseTreeType, string> = {
3812

3913
@Component({
4014
selector: 'la-parse-tree-table',
41-
imports: [SubscriptAngleBracketsPipe, ParseTreeHighlightDirective],
15+
imports: [TreeNodeComponent],
4216
templateUrl: './parse-tree-table.component.html',
4317
styleUrl: './parse-tree-table.component.scss'
4418
})
4519
export class ParseTreeTableComponent {
4620
public readonly tree = input.required<ParseTree>();
4721

48-
public readonly tableRows = computed<TableCell[][]>(() => {
49-
const root = this.tree().root;
50-
return this.createTableRows(root);
22+
public readonly rootNode = computed<TreeNodeDisplay>(() => {
23+
return this.buildDisplayTree(this.tree().root);
5124
});
5225

53-
public getTreeTypeDisplay(type: ParseTreeType): string {
54-
return TreeTypeDisplay[type] || "Unknown Type";
55-
}
26+
public treeType = computed(() => {
27+
return TreeTypeDisplay[this.tree().type] || "Unknown Type";
28+
});
5629

57-
private createTableRows(node: ParseTreeNode): TableCell[][] {
30+
private buildDisplayTree(node: ParseTreeNode): TreeNodeDisplay {
5831
switch (node.type) {
5932
case 'leaf':
6033
return this.buildLeafNode(node);
@@ -67,73 +40,52 @@ export class ParseTreeTableComponent {
6740
}
6841
}
6942

70-
private getRowWidth(row: TableCell[]): number {
71-
return row.reduce((sum, cell) => sum + cell.colspan, 0);
72-
}
73-
74-
private buildLeafNode(node: LeafNode): TableCell[][] {
75-
return [[{
76-
...node,
43+
private buildLeafNode(node: LeafNode): TreeNodeDisplay {
44+
return {
45+
type: 'leaf',
7746
content: node.cat,
78-
colspan: 1,
79-
}]];
47+
children: [],
48+
leaf: {
49+
tok: node.tok,
50+
lem: node.lem,
51+
pos: node.pos,
52+
ner: node.ner
53+
}
54+
};
8055
}
8156

82-
private buildVariableNode(node: VariableNode): TableCell[][] {
83-
return [[{
84-
type: "var",
85-
colspan: 1,
57+
private buildVariableNode(node: VariableNode): TreeNodeDisplay {
58+
return {
59+
type: 'var',
8660
content: node.name,
87-
typeInfo: node.typeInfo
88-
}]];
61+
children: [],
62+
var: {
63+
typeInfo: node.typeInfo
64+
}
65+
};
8966
}
9067

91-
private buildBinaryNode(node: BinaryNode): TableCell[][] {
92-
const leftRows = this.createTableRows(node.left);
93-
const rightRows = this.createTableRows(node.right);
94-
95-
// Pad shorter side with empty rows
96-
const maxRows = Math.max(leftRows.length, rightRows.length);
97-
98-
while (leftRows.length < maxRows) {
99-
const previousRow = leftRows[leftRows.length - 1];
100-
const leftWidth = previousRow ? this.getRowWidth(previousRow) : 1;
101-
leftRows.push([{ content: '', colspan: leftWidth, type: "node" }]);
102-
}
103-
while (rightRows.length < maxRows) {
104-
const previousRow = rightRows[rightRows.length - 1];
105-
const rightWidth = previousRow ? this.getRowWidth(previousRow) : 1;
106-
rightRows.push([{ content: '', colspan: rightWidth, type: "node" }]);
107-
}
108-
109-
// Combine rows horizontally
110-
const combinedRows: TableCell[][] = [];
111-
for (let i = 0; i < maxRows; i++) {
112-
combinedRows.push([...leftRows[i], ...rightRows[i]]);
113-
}
68+
private buildBinaryNode(node: BinaryNode): TreeNodeDisplay {
69+
const left = this.buildDisplayTree(node.left);
70+
const right = this.buildDisplayTree(node.right);
11471

115-
// Add parent node row
116-
const binaryTotalWidth = this.getRowWidth(combinedRows[0]);
117-
combinedRows.push([{
72+
return {
73+
type: 'node',
11874
content: node.cat,
11975
rule: node.rule,
120-
colspan: binaryTotalWidth,
121-
type: "node"
122-
}]);
123-
124-
return combinedRows;
76+
children: [left, right]
77+
};
12578
}
12679

127-
private buildUnaryNode(node: UnaryNode): TableCell[][] {
128-
const childRows = this.createTableRows(node.child);
129-
const unaryTotalWidth = this.getRowWidth(childRows[0]);
130-
childRows.push([{
80+
private buildUnaryNode(node: UnaryNode): TreeNodeDisplay {
81+
const child = this.buildDisplayTree(node.child);
82+
83+
return {
84+
type: 'node',
13185
content: node.cat,
13286
rule: node.rule,
133-
colspan: unaryTotalWidth,
134-
type: "node"
135-
}]);
136-
return childRows;
87+
children: [child]
88+
};
13789
}
13890

13991
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
@let hasChildren = node().children.length > 0;
2+
<div
3+
class="tree-node d-flex flex-column"
4+
[class.terminal-node]="!hasChildren"
5+
>
6+
@if (hasChildren) {
7+
<div class="d-flex">
8+
@for (childRow of node().children; track $index) {
9+
<la-tree-node [node]="childRow" />
10+
}
11+
</div>
12+
}
13+
14+
<div class="node-content d-flex flex-column align-items-center p-2 gap-1">
15+
@if (node().rule) {
16+
<span class="badge rounded-pill text-bg-secondary mt-2">
17+
{{ node().rule }}
18+
</span>
19+
} @switch (node().type) { @case ('leaf') {
20+
<div class="d-flex flex-column align-items-center gap-2">
21+
<div class="h5 mb-0">
22+
<strong>{{ node().leaf?.lem }}</strong>
23+
</div>
24+
<div class="text-secondary">
25+
<small>{{ node().leaf?.tok }}</small>
26+
<br />
27+
<small>{{ node().leaf?.pos }}</small>
28+
<br />
29+
<small>{{ node().leaf?.ner }}</small>
30+
</div>
31+
<div [innerHTML]="node().content | subscriptAngleBrackets"></div>
32+
</div>
33+
} @case ('var') {
34+
<div class="d-flex flex-column gap-1">
35+
<strong class="h5 fw-bold mb-0">
36+
{{ node().content }}
37+
</strong>
38+
<br />
39+
<span>{{ node().var?.typeInfo }}</span>
40+
</div>
41+
} @case ('node') {
42+
<p
43+
class="mb-0"
44+
[innerHTML]="node().content | subscriptAngleBrackets"
45+
></p>
46+
} }
47+
</div>
48+
</div>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
.tree-node {
2+
transition: background-color 0.2s ease;
3+
min-height: 10em;
4+
text-align: center;
5+
6+
&:hover:not(:has(.tree-node:hover)) {
7+
background-color: var(--bs-yellow);
8+
9+
.tree-node {
10+
background-color: var(--bs-yellow);
11+
}
12+
}
13+
}
14+
15+
:not(.terminal-node) > .node-content {
16+
border-top: 1px solid var(--bs-gray-500);
17+
}
18+
19+
.terminal-node {
20+
background-color: var(--bs-light);
21+
min-width: 5rem;
22+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Component, input } from '@angular/core';
2+
import { SubscriptAngleBracketsPipe } from './subscript-angle-brackets.pipe';
3+
4+
export interface TreeNodeDisplay {
5+
type: 'node' | 'leaf' | 'var';
6+
content: string;
7+
rule?: string;
8+
children: TreeNodeDisplay[];
9+
// For leaf nodes
10+
leaf?: {
11+
tok: string;
12+
lem: string;
13+
pos: string;
14+
ner: string;
15+
};
16+
// For variable nodes
17+
var?: {
18+
typeInfo: string;
19+
};
20+
}
21+
22+
@Component({
23+
selector: 'la-tree-node',
24+
standalone: true,
25+
imports: [SubscriptAngleBracketsPipe],
26+
templateUrl: './tree-node.component.html',
27+
styleUrl: './tree-node.component.scss'
28+
})
29+
export class TreeNodeComponent {
30+
public readonly node = input.required<TreeNodeDisplay>();
31+
}

0 commit comments

Comments
 (0)