I have working half organization chart, I am struggling with making sure connectors are nicely connecting and moving with nodes when they are expanded or the node tree is growing/grown size. I need help with the calculations for proper lines connectors. My idea is to utilize the calc method and but the formula i am not sure about. Image attached with broken lines is the one without the calc method changes. // Update CSS custom property for line calculations container.style.setProperty('--first-child-offset', `${firstChildCenter}px`); container.style.setProperty('--last-child-offset', `${lastChildCenter}px`); I did this changes on the DOM inspect mode first container(app-chart-node) --first-child-offset: calc(18% - 7px); --last-child-offset: calc(83% - 10px); second(inner children from a child). --first-child-offset: calc(30% - 7px); --last-child-offset: calc(73% - 7px); HTML code <div class="org-node-container"> <div class="org-node" [class.expanded]="orgChartService.isNodeExpanded(node.id)" > <div class="node-content"> <div class="node-info"> <div class="node-name">{{ node.name }}</div> <div class="node-title" *ngIf="node.title">{{ node.title }}</div> </div> </div> <button *ngIf="node.children?.length" class="toggle-btn" (click)="toggleNode($event)" [attr.aria-expanded]="orgChartService.isNodeExpanded(node.id)" [attr.aria-label]=" orgChartService.isNodeExpanded(node.id) ? 'Collapse' : 'Expand' " > <span class="toggle-icon"></span> </button> </div> <div *ngIf="node.children?.length && orgChartService.isNodeExpanded(node.id)" class="children-container" [class.single-child]="node.children && node.children.length === 1" [attr.data-children]="node.children && node.children.length" #childrenContainer > <claude-node *ngFor=" let child of node.children; let first = first; let last = last " [node]="child" [level]="level + 1" [class.first-child]="first" [class.last-child]="last" > </claude-node> </div> </div> TS private updateChildrenLines() { const container = this.elementRef.nativeElement.querySelector('.children-container'); if (!container || !this.node.children) return; // Handle single child case if (this.node.children.length === 1) { container.classList.add('single-child'); return; } container.classList.remove('single-child'); // Update the container's width to ensure proper line rendering requestAnimationFrame(() => { const children = container.querySelectorAll('claude-node'); if (children.length >= 2) { const firstChild = children[0] as HTMLElement; const lastChild = children[children.length - 1] as HTMLElement; // Calculate the actual width needed based on the children's positions const containerWidth = container.offsetWidth; const firstChildCenter = firstChild.offsetLeft + (firstChild.offsetWidth / 2); const lastChildCenter = lastChild.offsetLeft + (lastChild.offsetWidth / 2); // Update CSS custom property for line calculations container.style.setProperty('--first-child-offset', `${firstChildCenter}px`); container.style.setProperty('--last-child-offset', `${lastChildCenter}px`); this.cdr.detectChanges(); } }); } CSS .org-node-container { --node-min-width: 200px; --node-padding: 16px; --node-margin: 24px; --line-color: #ccc; --line-thickness: 2px; --transition-duration: 0.3s; --horizontal-line-width: 0px; display: flex; flex-direction: column; align-items: center; position: relative; margin: 0 var(--node-margin); } /* Remove vertical line above parent */ .org-node-container:first-child::before { display: none; } .org-node { position: relative; min-width: var(--node-min-width); margin: var(--node-margin) 10px 0 0; transition: all var(--transition-duration) ease; } .node-content { background: white; border: 1px solid #ddd; border-radius: 8px; padding: var(--node-padding); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: all var(--transition-duration) ease; cursor: pointer; position: relative; z-index: 1; } .node-content:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } .children-container { --first-child-offset: 0px; --last-child-offset: 0px; display: flex; justify-content: center; flex-wrap: wrap; position: relative; padding-top: var(--node-margin); transition: all var(--transition-duration) ease; /* gap: 20px; */ column-gap: 20px; } /* Vertical line from parent to horizontal line */ .children-container::before { content: ''; position: absolute; top: 0; left: 50%; transform: translateX(-50%); width: var(--line-thickness); height: var(--node-margin); background: var(--line-color); } /* Horizontal line between children */ /* .children-container:not(.single-child)::after { content: ''; position: absolute; top: var(--node-margin); left: 0; right: 0; height: var(--line-thickness); background: var(--line-color); Calculate width based on first and last child position left: calc(var(--first-child-offset) - var(--node-margin)); width: calc(var(--last-child-offset) - var(--first-child-offset) + (2 * var(--node-margin))); margin: 0 auto; } */ .children-container:not(.single-child)::after { content: ''; position: absolute; top: var(--node-margin); left: calc(var(--first-child-offset) - var(--node-margin)); width: calc(var(--last-child-offset) - var(--first-child-offset) + (2 * var(--node-margin))); height: var(--line-thickness); background: var(--line-color); } .children-container claude-node { position: relative; flex: 1; min-width: calc(var(--node-min-width) + 2 * var(--node-margin)); display: flex; flex-direction: column; align-items: center; } /* Vertical lines from horizontal line to children */ .children-container claude-node::before { content: ''; position: absolute; top: 0px; left: 50%; transform: translateX(-50%); width: var(--line-thickness); height: var(--node-margin); background: var(--line-color); } /* Node info styles */ .node-info { display: flex; flex-direction: column; align-items: center; gap: 4px; } .node-name { font-weight: 600; color: #333; font-size: 1rem; } .node-title { font-size: 0.875rem; color: #666; } /* Toggle button styles */ .toggle-btn { position: absolute; top: 55px; left: 53%; transform: translateX(-50%); width: 24px; height: 24px; border-radius: 50%; background: white; border: 1px solid #ddd; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); cursor: pointer; z-index: 2; transition: all var(--transition-duration) ease; } .toggle-btn:hover { background: #f5f5f5; } .toggle-btn .toggle-icon { position: absolute; top: 50%; left: 50%; width: 8px; height: 8px; border: solid #666; border-width: 0 2px 2px 0; transform: translate(-50%, -50%) rotate(45deg); transition: transform var(--transition-duration) ease; } .org-node.expanded .toggle-icon { transform: translate(-50%, -25%) rotate(-135deg); } I do not want to use chart libraries, so I tried couple of CSS properties but I can't seem to find the right connections and missing the formulas to calculate and manage the lines dynamically. I will appreciate any help and thank you. For Demonstraion Continue reading...