UNPKG

@dcrackel/meyersquaredui

Version:

This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.

211 lines (174 loc) 6.96 kB
// TableauLayoutEngine.js export const TableauLayoutTuning = { baseGapPx: 0, padTopBiasPx: 0, padByDepthPx: [0, 32, 96, 224, 480, 992, 2016], gapByDepthPx: [20, 84, 212, 468, 980, 2004, 0], padByDepthPxCompact: [0, 26, 76, 176, 376, 365, 0], gapByDepthPxCompact: [10, 60, 160, 360, 760, 1000, 0], finalPadBiasPx: 0, thirdPlacePadBiasPx: 120, splitFinalPadTopByBracket: { "Table of 64": -45, "Table of 128": 730, "Table of 32": 0, }, compact: { enabledFor: ["Table of 64", "Table of 128", "Table of 256"], interFencerGapPx: 1, }, splitFinalPadBiasPx: 0, }; // layout/TableauLayoutEngine.js export function createDomAnchorLayoutProvider({ rootEl, getOutAnchorEl, getInAnchorEl }) { function rootRect() { return rootEl.getBoundingClientRect(); } function snap(v) { return Math.round(v) + 0.5; } function pointRelativeToRoot(el) { const rr = rootRect(); const r = el.getBoundingClientRect(); return { x: snap((r.left - rr.left) + r.width / 2), y: snap((r.top - rr.top) + r.height / 2), }; } function getCanvasSize() { const rr = rootRect(); // IMPORTANT: pad the canvas so strokes at the edges don't get clipped const pad = 8; // try 8–16 return { w: Math.ceil(rr.width) + pad, h: Math.ceil(rr.height) + pad, }; } function getOutPoint(nodeId) { const el = getOutAnchorEl(nodeId); if (!el) return null; return pointRelativeToRoot(el); } function getInPoint(nodeId) { const el = getInAnchorEl(nodeId); if (!el) return null; return pointRelativeToRoot(el); } return { getOutPoint, getInPoint, getCanvasSize }; } export function buildBracketPaths({ bouts, getOutPoint, getInPoint }) { const paths = []; // group children by their parent (DENextBoutId) const childrenByParent = new Map(); for (const bout of bouts) { const fromId = bout?.DEBoutId; const toId = bout?.DENextBoutId; if (!fromId || !toId) continue; if (!childrenByParent.has(toId)) childrenByParent.set(toId, []); childrenByParent.get(toId).push(fromId); } // tuning knobs const joinFromParent = 120; // how far "inside" the round the trunk sits const minJoinFromChild = 16; // keep trunk away from child anchor const joinGap = 6; // keep trunk away from parent anchor const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v)); let missing = 0; for (const [parentId, childIds] of childrenByParent.entries()) { const parent = getInPoint(parentId); if (!parent) { missing++; continue; } // we only really care about the first 2 (standard bracket) const c1 = getOutPoint(childIds[0]); const c2 = childIds.length > 1 ? getOutPoint(childIds[1]) : null; // fallback: 1-child connection (bye / incomplete) if (!c1 || !c2) { if (!c1) { missing++; continue; } // Determine if child is left or right of parent const sign = (c1.x > parent.x) ? +1 : -1; // place trunk between child and parent let trunkX; if (sign === -1) { // parent to the right trunkX = clamp( parent.x - joinFromParent, c1.x + minJoinFromChild, parent.x - joinGap ); } else { // parent to the left trunkX = clamp( parent.x + joinFromParent, parent.x + joinGap, c1.x - minJoinFromChild ); } paths.push({ key: `${childIds[0]}->${parentId}`, d: `M ${c1.x} ${c1.y} L ${trunkX} ${c1.y} L ${trunkX} ${parent.y} L ${parent.x} ${parent.y}` }); continue; } // Two-child merged connector const top = c1.y <= c2.y ? c1 : c2; const bot = c1.y <= c2.y ? c2 : c1; // SPECIAL CASE: children are on opposite sides of parent (split-final situation) // i.e., one child left of parent and one child right of parent const oppositeSides = (c1.x - parent.x) * (c2.x - parent.x) < 0; if (oppositeSides) { // draw two separate elbows into the parent (no shared trunk) const elbowFor = (childPoint, childKey) => { const sign = (childPoint.x > parent.x) ? +1 : -1; const midX = parent.x + sign * joinGap; // approach parent from correct side return { key: `${childKey}->final(${parentId})`, d: `M ${childPoint.x} ${childPoint.y} L ${midX} ${childPoint.y} L ${midX} ${parent.y} L ${parent.x} ${parent.y}` }; }; paths.push(elbowFor(c1, childIds[0])); paths.push(elbowFor(c2, childIds[1])); continue; } // NORMAL CASE: both children are on same side of parent (LTR or RTL half) const avgChildX = (c1.x + c2.x) / 2; const sign = (avgChildX > parent.x) ? +1 : -1; const minChildX = Math.min(c1.x, c2.x); const maxChildX = Math.max(c1.x, c2.x); let trunkX; if (sign === -1) { // children left, parent right trunkX = clamp( parent.x - joinFromParent, maxChildX + minJoinFromChild, parent.x - joinGap ); } else { // children right, parent left trunkX = clamp( parent.x + joinFromParent, parent.x + joinGap, minChildX - minJoinFromChild ); } // 1) horizontal from each child to trunk paths.push({ key: `${childIds[0]}->join(${parentId})`, d: `M ${c1.x} ${c1.y} L ${trunkX} ${c1.y}` }); paths.push({ key: `${childIds[1]}->join(${parentId})`, d: `M ${c2.x} ${c2.y} L ${trunkX} ${c2.y}` }); // 2) vertical trunk paths.push({ key: `trunk(${parentId})`, d: `M ${trunkX} ${top.y} L ${trunkX} ${bot.y}` }); // 3) run from trunk midpoint to parent const midY = (top.y + bot.y) / 2; paths.push({ key: `join(${parentId})->parent`, d: `M ${trunkX} ${midY} L ${parent.x} ${midY}` }); } console.log({ parents: childrenByParent.size, paths: paths.length, missing }); return paths; }