All files / src/pages/flowsheet-page/pinch-analysis/hen-generation HENExchangerLines.tsx

86.36% Statements 38/44
65.78% Branches 25/38
100% Functions 2/2
100% Lines 37/37

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103                              109x 109x     109x     109x 253x 253x       253x 253x     253x 253x 253x 253x       253x 253x   253x 253x   253x 253x   253x 253x     253x 253x 253x 253x   253x       253x     253x       277x 277x       277x     277x     277x 277x     277x 277x   277x                             109x  
type ExchangerLinesProps = {
  streamYPositions: number[]; // For each stream, the pixel Y of its TOP edge. During drag, stream y is live-updated so lines follow it
  streamIndexBySdeId: Map<string, number>; // map SDE id -> index in renderStreams. What is stream index in render order?
  alignedHXByKey?: Map<string, Map<string, number[]>>; // Map<hxKey, Map<sdeId, number[]>> : X positions of HXs per participating row
  alignedHXRowsByKey?: Map<string, Map<string, number[]>>; // Map<hxKey, Map<sdeId, number[]>> : row indices of rows with HXs.
  streamBlockHeight: number;
};
 
export default function ExchangerLines({
  streamYPositions,
  streamIndexBySdeId,
  alignedHXByKey,
  alignedHXRowsByKey,
  streamBlockHeight,
}: ExchangerLinesProps) {
  const circlePad = 15; // so lines render from top/bot EDGE of circle (circle radius is 15).
  Iif (!alignedHXByKey || !alignedHXRowsByKey) return null;
 
  // push all <line> svg elemnts at the end for render.
  const lines: JSX.Element[] = [];
 
  // for each hx,
  alignedHXByKey.forEach((sdeId, hxKey) => {
    const rowsBySdeId = alignedHXRowsByKey.get(hxKey);
    Iif (!rowsBySdeId) return;
 
    // for each exchanger hxKey, grab all streams (sdeIds) where it appears.
    // need exactly two sides of the exchanger
    const sdeIds = Array.from(sdeId.keys());
    Iif (sdeIds.length !== 2) return;
 
    // look up stream's index to later get topY from streamYPositions
    const [sdeId1, sdeId2] = sdeIds;
    const idx1 = streamIndexBySdeId.get(sdeId1);
    const idx2 = streamIndexBySdeId.get(sdeId2);
    Iif (idx1 == null || idx2 == null) return;
 
    // determine which stream is visually on top/bottom (y-pos) right now
    // so we can draw lines from top to bottom consistently, even while dragging
    const baseY1 = streamYPositions[idx1];
    const baseY2 = streamYPositions[idx2];
 
    const topSdeId    = baseY1 <= baseY2 ? sdeId1 : sdeId2;
    const bottomSdeId = baseY1 <= baseY2 ? sdeId2 : sdeId1;
 
    const topIdx    = baseY1 <= baseY2 ? idx1 : idx2;
    const bottomIdx = baseY1 <= baseY2 ? idx2 : idx1;
 
    const baseYTop    = streamYPositions[topIdx];
    const baseYBottom = streamYPositions[bottomIdx];
 
    // get shared x pos, and participating row indices for each side
    const topXs = sdeId.get(topSdeId) ?? [];
    const botXs = sdeId.get(bottomSdeId) ?? [];
    const topRows = rowsBySdeId.get(topSdeId) ?? [];
    const botRows = rowsBySdeId.get(bottomSdeId) ?? [];
 
    Iif (!topXs.length || !botXs.length || !topRows.length || !botRows.length) return;
 
    // how many vertical lines to draw for this HX (some HX can connect multiple row pairs)
    // e.g. if top has 2 participating rows and bottom has 3, we draw 3 lines
    const multiplicity = Math.max(topXs.length, botXs.length);
 
    // for each line to be drawn (for the same HX),
    for (let k = 0; k < multiplicity; k++) {
      // use modulo fan-out to pair rows if counts differ
      // e.g. if top has 2 rows, but bottom has 3
      // k=0 -> top0 with bot0; k=1 -> top1 with bot1; k=2 -> top0 (wraps) with bot2
      const topIdxInList = k % topXs.length;
      const botIdxInList = k % botXs.length;
 
      // X should be identical for both sides (but be defensive)
      // grab from top if valid, else fall back to bottom
      const x = Number.isFinite(topXs[topIdxInList])
        ? topXs[topIdxInList]!
        : botXs[botIdxInList];
      Iif (x == null || !isFinite(x)) continue;
 
      // which ROW in each block are we connecting? (segments had multiple rows.)
      const topRowIndex = topRows[topIdxInList] ?? 0;
      const botRowIndex = botRows[botIdxInList] ?? 0;
 
      // the top and bottom Y for the exchanger lines.
      const yTop = baseYTop + topRowIndex * streamBlockHeight + circlePad; // nudge down
      const yBottom = baseYBottom + botRowIndex * streamBlockHeight - circlePad; // nudge up
 
      lines.push(
        <line
          key={`${hxKey}-link-${k}`} // hxKey pair, as well as how many times it occurs in visualisation.
          x1={x}
          y1={yTop}
          x2={x}
          y2={yBottom}
          stroke="hsl(var(--hen-diagram-foreground))"
          strokeWidth={1}
        />
      );
    }
  });
 
  // return all lines as a fragmeent.
  return <>{lines}</>;
}