All files / src/pages/flowsheet-page/flowsheet/Canvas streamEdgeStyle.ts

95.74% Statements 45/47
83.87% Branches 26/31
100% Functions 5/5
95.45% Lines 42/44

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 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165              76x 76x 76x 76x 76x                 76x                           2147x                   2008x 11532x   2008x       2008x 7656x   2008x   2008x       7x   3x       546x         4016x 4016x   926x 926x       556x       880x 2640x   880x               556x               81x   324x     81x       2008x   556x         556x     556x   1197x           556x   556x   556x     556x                           2008x 2008x         2008x   2008x            
import {
  ObjectTypeEnum,
  PropertyInfoRead,
  PropertySetRead,
  SimulationObjectRetrieveRead,
} from "@/api/apiStore.gen";
 
const MIN_TEMPERATURE_C = 0;
const MAX_TEMPERATURE_C = 200;
const DEFAULT_STREAM_COLOR = "#000000";
export const TRACKED_STREAM_COLOR = "#26967f";
const VAPOR_FRACTION_DOTTED_THRESHOLD = 0.1;
 
type TemperatureColorStop = {
  position: number;
  h: number;
  s: number;
  l: number;
};
 
const TEMPERATURE_COLOR_STOPS: TemperatureColorStop[] = [
  { position: 0, h: 204.07, s: 100, l: 33.73 },
  { position: 0.1, h: 110, s: 46, l: 56 },
  { position: 0.5, h: 51, s: 82, l: 60 },
  { position: 1, h: 0, s: 74, l: 53 },
];
 
export type StreamPropertyStyle = {
  stroke: string;
  strokeDasharray?: string;
  strokeLinecap?: "butt" | "round" | "square";
};
 
export function shouldStyleStreamEdges(objectType: ObjectTypeEnum | undefined) {
  return (
    objectType === ObjectTypeEnum.Stream ||
    objectType === ObjectTypeEnum.HumidAirStream
  );
}
 
function propertyNumericValue(
  propertySet: PropertySetRead | undefined,
  key: string,
) {
  const property = propertySet?.ContainedProperties?.find(
    (prop) => prop.key === key,
  );
  return numericValue(property);
}
 
function temperatureInCelsius(propertySet: PropertySetRead | undefined) {
  const property = propertySet?.ContainedProperties?.find(
    (prop) => prop.key === "temperature",
  );
  const value = numericValue(property);
 
  if (value === undefined) return undefined;
 
  switch (property?.unit) {
    case "K":
      return value - 273.15;
    case "degF":
      return ((value - 32) * 5) / 9;
    case "degR":
      return (value * 5) / 9 - 273.15;
    default:
      return value;
  }
}
 
function numericValue(property: PropertyInfoRead | undefined) {
  const rawValue = property?.values?.[0]?.value;
  if (rawValue === null || rawValue === undefined) return undefined;
 
  const value = typeof rawValue === "number" ? rawValue : Number(rawValue);
  return Number.isFinite(value) ? value : undefined;
}
 
function clamp(value: number, min: number, max: number) {
  return Math.min(Math.max(value, min), max);
}
 
function formatHsla({ h, s, l }: Omit<TemperatureColorStop, "position">) {
  const format = (value: number) =>
    Number.isInteger(value) ? value.toString() : value.toFixed(2);
 
  return `hsla(${format(h)}, ${format(s)}%, ${format(l)}%, 1)`;
}
 
function interpolateColorStop(
  start: TemperatureColorStop,
  end: TemperatureColorStop,
  ratio: number,
) {
  return formatHsla({
    h: start.h + (end.h - start.h) * ratio,
    s: start.s + (end.s - start.s) * ratio,
    l: start.l + (end.l - start.l) * ratio,
  });
}
 
export function streamTemperatureGradient() {
  const stops = TEMPERATURE_COLOR_STOPS.map(
    (stop) =>
      `${formatHsla(stop)} ${Math.round(stop.position * 100).toString()}%`,
  ).join(", ");
 
  return `linear-gradient(90deg, ${stops})`;
}
 
export function streamTemperatureColor(temperatureC: number | undefined) {
  if (temperatureC === undefined) return DEFAULT_STREAM_COLOR;
 
  const clampedTemperature = clamp(
    temperatureC,
    MIN_TEMPERATURE_C,
    MAX_TEMPERATURE_C,
  );
  const ratio =
    (clampedTemperature - MIN_TEMPERATURE_C) /
    (MAX_TEMPERATURE_C - MIN_TEMPERATURE_C);
  const startStopIndex = TEMPERATURE_COLOR_STOPS.findIndex(
    (stop, index) =>
      index < TEMPERATURE_COLOR_STOPS.length - 1 &&
      ratio >= stop.position &&
      ratio <= TEMPERATURE_COLOR_STOPS[index + 1].position,
  );
 
  const startStop =
    TEMPERATURE_COLOR_STOPS[startStopIndex] ?? TEMPERATURE_COLOR_STOPS[0];
  const endStop =
    TEMPERATURE_COLOR_STOPS[startStopIndex + 1] ??
    TEMPERATURE_COLOR_STOPS[TEMPERATURE_COLOR_STOPS.length - 1];
  const segmentRatio =
    (ratio - startStop.position) / (endStop.position - startStop.position);
 
  return interpolateColorStop(startStop, endStop, segmentRatio);
}
 
export function streamEdgeStyle(
  streamData: SimulationObjectRetrieveRead | undefined,
  options: { tracked?: boolean } = {},
): StreamPropertyStyle {
  if (options.tracked) {
    return {
      stroke: TRACKED_STREAM_COLOR,
      strokeLinecap: "round",
    };
  }
 
  const temperature = temperatureInCelsius(streamData?.properties);
  const vaporAmount = propertyNumericValue(
    streamData?.properties,
    "vapor_frac",
  );
  const hasVisibleVapor =
    vaporAmount !== undefined && vaporAmount > VAPOR_FRACTION_DOTTED_THRESHOLD;
 
  return {
    stroke: streamTemperatureColor(temperature),
    strokeDasharray: hasVisibleVapor ? "1,6" : undefined,
    strokeLinecap: hasVisibleVapor ? "round" : "butt",
  };
}