All files / src/pages/flowsheet-page/economics/results-panel/charts/comparison studyComparisonOverlayData.ts

84.78% Statements 39/46
63.63% Branches 14/22
100% Functions 4/4
86.66% Lines 39/45

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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140          103x 103x 103x 103x 103x                             5x       58x   58x     58x         58x         58x                   5x     144x 144x   144x           144x 144x 144x     5x 55x             10x       72x   72x           72x 72x 72x     10x       58x         58x 58x   174x         174x       58x   58x       5x 5x       5x             5x   29x      
import type { CSSProperties } from "react";
import type { ParsedChartDataset } from "../../model/types";
import { numberValue, stringField } from "../../model/valueParsing";
import { COMPARISON_CHART_COST_BREAKDOWN } from "../constants";
 
const COMPARISON_CHART_DEFAULT_HEIGHT = 320;
const COST_BREAKDOWN_CHART_PADDING_HEIGHT = 92;
const COST_BREAKDOWN_ROW_HEIGHT = 58;
const COST_BREAKDOWN_TICK_LINE_HEIGHT = 20;
const COST_BREAKDOWN_TICK_MAX_CHARS = 32;
 
export type ComparisonChartRow = {
  label: string | number;
  unit: string;
  sourceRows: Record<string, { unit?: string }>;
  [seriesKey: string]: unknown;
};
 
export function comparisonChartHeightStyle(
  dataset: ParsedChartDataset,
): CSSProperties | undefined {
  if (dataset.chart_key !== COMPARISON_CHART_COST_BREAKDOWN) {
    return undefined;
  }
  return { height: comparisonChartHeight(dataset) };
}
 
export function costBreakdownTickLines(value: unknown) {
  const rawLines = String(value ?? "")
    .split(/\n+/)
    .map((line) => line.trim())
    .filter(Boolean);
 
  return rawLines.flatMap((line, index) => {
    if (index !== 0) {
      return wrapCostBreakdownTickLine(line);
    }
 
    const separatorIndex = line.indexOf(": ");
    if (separatorIndex < 0) {
      return wrapCostBreakdownTickLine(line);
    }
 
    return [
      line.slice(0, separatorIndex + 1),
      ...wrapCostBreakdownTickLine(line.slice(separatorIndex + 2)),
    ];
  });
}
 
export function cashFlowOverlayRows(
  dataset: ParsedChartDataset,
): ComparisonChartRow[] {
  const rowsByLabel = new Map<number, ComparisonChartRow>();
  for (const series of dataset.series) {
    for (const point of series.points) {
      const label = numberValue(point.metadata.year);
      Iif (label == null) continue;
      const row =
        rowsByLabel.get(label) ??
        ({
          label,
          unit: point.unit || series.unit,
          sourceRows: {},
        } as ComparisonChartRow);
      row[series.key] = point.value;
      row.sourceRows[series.key] = { unit: point.unit || series.unit };
      rowsByLabel.set(label, row);
    }
  }
  return Array.from(rowsByLabel.values()).sort(
    (left, right) => Number(left.label) - Number(right.label),
  );
}
 
export function costBreakdownRows(
  dataset: ParsedChartDataset,
): ComparisonChartRow[] {
  const rowsByLabel = new Map<string, ComparisonChartRow>();
  for (const series of dataset.series) {
    for (const point of series.points) {
      const label =
        stringField(point.metadata.category_label) || point.label || point.key;
      const row =
        rowsByLabel.get(label) ??
        ({
          label,
          unit: point.unit || series.unit,
          sourceRows: {},
        } as ComparisonChartRow);
      row[series.key] = point.value;
      row.sourceRows[series.key] = { unit: point.unit || series.unit };
      rowsByLabel.set(label, row);
    }
  }
  return Array.from(rowsByLabel.values());
}
 
function wrapCostBreakdownTickLine(line: string) {
  const words = line.split(/\s+/).filter(Boolean);
  if (words.length === 0) {
    return [];
  }
 
  const lines: string[] = [];
  let current = "";
  for (const word of words) {
    const next = current ? `${current} ${word}` : word;
    if (current && next.length > COST_BREAKDOWN_TICK_MAX_CHARS) {
      lines.push(current);
      current = word;
    } else {
      current = next;
    }
  }
  if (current) {
    lines.push(current);
  }
  return lines;
}
 
function comparisonChartHeight(dataset: ParsedChartDataset) {
  const rows = costBreakdownRows(dataset);
  const rowHeight = Math.max(
    COST_BREAKDOWN_ROW_HEIGHT,
    maxCostBreakdownTickLineCount(rows) * COST_BREAKDOWN_TICK_LINE_HEIGHT + 16,
  );
  return Math.max(
    COMPARISON_CHART_DEFAULT_HEIGHT,
    rows.length * rowHeight + COST_BREAKDOWN_CHART_PADDING_HEIGHT,
  );
}
 
function maxCostBreakdownTickLineCount(rows: ComparisonChartRow[]) {
  return Math.max(
    1,
    ...rows.map((row) => costBreakdownTickLines(row.label).length),
  );
}