From d94fe8806b0e1deaddc62ccaec76dab4e5e3be88 Mon Sep 17 00:00:00 2001
From: Jamie V <jamie.j.vigliotta@nasa.gov>
Date: Tue, 24 Oct 2023 16:07:43 -0700
Subject: [PATCH] [Plots] Gracefully handle Float32Array breaking values
 (#7138)

* WIP

* guaranteeing float32breaking values for swgs when option is set

* cleaning up and clarity

* more clarity

* removing randomization of float breaking number, as it is not necessary

* logging the values that could not be plotted for awareness

* remving auto-added imports

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
---
 example/generator/GeneratorProvider.js       |  6 +-
 example/generator/generatorWorker.js         | 78 ++++++++++++++++++--
 example/generator/plugin.js                  |  8 ++
 src/plugins/plot/configuration/PlotSeries.js | 24 ++++--
 4 files changed, 99 insertions(+), 17 deletions(-)

diff --git a/example/generator/GeneratorProvider.js b/example/generator/GeneratorProvider.js
index b74915ef12..2aeee687a2 100644
--- a/example/generator/GeneratorProvider.js
+++ b/example/generator/GeneratorProvider.js
@@ -29,7 +29,8 @@ define(['./WorkerInterface'], function (WorkerInterface) {
     randomness: 0,
     phase: 0,
     loadDelay: 0,
-    infinityValues: false
+    infinityValues: false,
+    exceedFloat32: false
   };
 
   function GeneratorProvider(openmct, StalenessProvider) {
@@ -53,7 +54,8 @@ define(['./WorkerInterface'], function (WorkerInterface) {
       'randomness',
       'phase',
       'loadDelay',
-      'infinityValues'
+      'infinityValues',
+      'exceedFloat32'
     ];
 
     request = request || {};
diff --git a/example/generator/generatorWorker.js b/example/generator/generatorWorker.js
index 6a83fe2ba8..024f54032d 100644
--- a/example/generator/generatorWorker.js
+++ b/example/generator/generatorWorker.js
@@ -85,7 +85,8 @@
                 data.offset,
                 data.phase,
                 data.randomness,
-                data.infinityValues
+                data.infinityValues,
+                data.exceedFloat32
               ),
               wavelengths: wavelengths(),
               intensities: intensities(),
@@ -96,7 +97,8 @@
                 data.offset,
                 data.phase,
                 data.randomness,
-                data.infinityValues
+                data.infinityValues,
+                data.exceedFloat32
               )
             }
           });
@@ -136,6 +138,7 @@
     var randomness = request.randomness;
     var loadDelay = Math.max(request.loadDelay, 0);
     var infinityValues = request.infinityValues;
+    var exceedFloat32 = request.exceedFloat32;
 
     var step = 1000 / dataRateInHz;
     var nextStep = start - (start % step) + step;
@@ -146,10 +149,28 @@
       data.push({
         utc: nextStep,
         yesterday: nextStep - 60 * 60 * 24 * 1000,
-        sin: sin(nextStep, period, amplitude, offset, phase, randomness, infinityValues),
+        sin: sin(
+          nextStep,
+          period,
+          amplitude,
+          offset,
+          phase,
+          randomness,
+          infinityValues,
+          exceedFloat32
+        ),
         wavelengths: wavelengths(),
         intensities: intensities(),
-        cos: cos(nextStep, period, amplitude, offset, phase, randomness, infinityValues)
+        cos: cos(
+          nextStep,
+          period,
+          amplitude,
+          offset,
+          phase,
+          randomness,
+          infinityValues,
+          exceedFloat32
+        )
       });
     }
 
@@ -176,9 +197,26 @@
     });
   }
 
-  function cos(timestamp, period, amplitude, offset, phase, randomness, infinityValues) {
-    if (infinityValues && Math.random() > 0.5) {
+  function cos(
+    timestamp,
+    period,
+    amplitude,
+    offset,
+    phase,
+    randomness,
+    infinityValues,
+    exceedFloat32
+  ) {
+    if (infinityValues && exceedFloat32) {
+      if (Math.random() > 0.5) {
+        return Number.POSITIVE_INFINITY;
+      } else if (Math.random() < 0.01) {
+        return getRandomFloat32OverflowValue();
+      }
+    } else if (infinityValues && Math.random() > 0.5) {
       return Number.POSITIVE_INFINITY;
+    } else if (exceedFloat32 && Math.random() < 0.01) {
+      return getRandomFloat32OverflowValue();
     }
 
     return (
@@ -188,9 +226,26 @@
     );
   }
 
-  function sin(timestamp, period, amplitude, offset, phase, randomness, infinityValues) {
-    if (infinityValues && Math.random() > 0.5) {
+  function sin(
+    timestamp,
+    period,
+    amplitude,
+    offset,
+    phase,
+    randomness,
+    infinityValues,
+    exceedFloat32
+  ) {
+    if (infinityValues && exceedFloat32) {
+      if (Math.random() > 0.5) {
+        return Number.POSITIVE_INFINITY;
+      } else if (Math.random() < 0.01) {
+        return getRandomFloat32OverflowValue();
+      }
+    } else if (infinityValues && Math.random() > 0.5) {
       return Number.POSITIVE_INFINITY;
+    } else if (exceedFloat32 && Math.random() < 0.01) {
+      return getRandomFloat32OverflowValue();
     }
 
     return (
@@ -200,6 +255,13 @@
     );
   }
 
+  // Values exceeding float32 range (Positive: 3.4+38, Negative: -3.4+38)
+  function getRandomFloat32OverflowValue() {
+    const sign = Math.random() > 0.5 ? 1 : -1;
+
+    return sign * 3.4e39;
+  }
+
   function wavelengths() {
     let values = [];
     while (values.length < 5) {
diff --git a/example/generator/plugin.js b/example/generator/plugin.js
index d419196156..6f60cd83d4 100644
--- a/example/generator/plugin.js
+++ b/example/generator/plugin.js
@@ -122,6 +122,13 @@ export default function (openmct) {
         key: 'infinityValues',
         property: ['telemetry', 'infinityValues']
       },
+      {
+        name: 'Exceed Float32 Limits',
+        control: 'toggleSwitch',
+        cssClass: 'l-input',
+        key: 'exceedFloat32',
+        property: ['telemetry', 'exceedFloat32']
+      },
       {
         name: 'Provide Staleness Updates',
         control: 'toggleSwitch',
@@ -140,6 +147,7 @@ export default function (openmct) {
         randomness: 0,
         loadDelay: 0,
         infinityValues: false,
+        exceedFloat32: false,
         staleness: false
       };
     }
diff --git a/src/plugins/plot/configuration/PlotSeries.js b/src/plugins/plot/configuration/PlotSeries.js
index 7d9edd6b7c..00a2e41bf0 100644
--- a/src/plugins/plot/configuration/PlotSeries.js
+++ b/src/plugins/plot/configuration/PlotSeries.js
@@ -19,8 +19,6 @@
  * this source code distribution or the Licensing information page available
  * at runtime from the About dialog for additional information.
  *****************************************************************************/
-import _ from 'lodash';
-
 import configStore from '../configuration/ConfigStore';
 import { MARKER_SHAPES } from '../draw/MarkerShapes';
 import { symlog } from '../mathUtils';
@@ -64,6 +62,10 @@ import Model from './Model';
  *
  * @extends {Model<PlotSeriesModelType, PlotSeriesModelOptions>}
  */
+
+const FLOAT32_MAX = 3.4e38;
+const FLOAT32_MIN = -3.4e38;
+
 export default class PlotSeries extends Model {
   logMode = false;
 
@@ -371,7 +373,7 @@ export default class PlotSeries extends Model {
     let stats = this.get('stats');
     let changed = false;
     if (!stats) {
-      if ([Infinity, -Infinity].includes(value)) {
+      if ([Infinity, -Infinity].includes(value) || !this.isValidFloat32(value)) {
         return;
       }
 
@@ -383,13 +385,13 @@ export default class PlotSeries extends Model {
       };
       changed = true;
     } else {
-      if (stats.maxValue < value && value !== Infinity) {
+      if (stats.maxValue < value && value !== Infinity && this.isValidFloat32(value)) {
         stats.maxValue = value;
         stats.maxPoint = point;
         changed = true;
       }
 
-      if (stats.minValue > value && value !== -Infinity) {
+      if (stats.minValue > value && value !== -Infinity && this.isValidFloat32(value)) {
         stats.minValue = value;
         stats.minPoint = point;
         changed = true;
@@ -425,7 +427,7 @@ export default class PlotSeries extends Model {
     const lastYVal = this.getYVal(data[insertIndex - 1]);
 
     if (this.isValueInvalid(currentYVal) && this.isValueInvalid(lastYVal)) {
-      console.warn('[Plot] Invalid Y Values detected');
+      console.warn(`[Plot] Invalid Y Values detected: ${currentYVal} ${lastYVal}`);
 
       return;
     }
@@ -453,7 +455,15 @@ export default class PlotSeries extends Model {
    * @private
    */
   isValueInvalid(val) {
-    return Number.isNaN(val) || this.unPlottableValues.includes(val);
+    return Number.isNaN(val) || this.unPlottableValues.includes(val) || !this.isValidFloat32(val);
+  }
+
+  /**
+   *
+   * @private
+   */
+  isValidFloat32(val) {
+    return val < FLOAT32_MAX && val > FLOAT32_MIN;
   }
 
   /**