modules/post/assessment_datums.mjs

export { AssessmentDatums };

import { JSPath } from "../shared/path.mjs";
import { ProtocolDatum } from "./datums.mjs";
import { AssessmentType } from "../shared/assessment_types.mjs";

/** Class representing datums for an assessment type */
class AssessmentDatums {
    /**
     *
     * @param {ProtocolDatum[]} datums Array of ProtocolDatum instances
     * @param {string} assessment Assessment type, e.g. AssessmentType.NECK_AXIAL
     */
    constructor(datums, assessment) {
        this.datums = datums;
        this.assessment = assessment;
    }

    /* Class methods */

    /**
     * Reads assessment datums from a JSON file
     * from the 'datums' directory
     * @param {string} regulation Regulation, e.g. Regulation.CNCAP
     * @param {string} crash_test Crash test type, e.g. CrashTest.ODB
     * @param {string} version Protocol version, e.g. "7.1.2"
     * @param {string} assessment_type Assessment type, e.g. AssessmentType.NECK_AXIAL
     * @param {string} filename Filename to read from, e.g. "default"
     * @returns {?AssessmentDatums}
     * @example
     * let assessment_datums = AssessmentDatums.ReadFromFile(Regulation.CNCAP,
     *                                                       CrashTest.ODB,
     *                                                       "7.1.2",
     *                                                       AssessmentType.NECK_SHEAR_EXCEEDENCE,
     *                                                       JSPath.WorkflowsDirectory(JSPath.POST),
     *                                                       "default");
     */
    static ReadFromFile(regulation, crash_test, version, assessment_type, filename) {
        /* Get datums directory relative to the workflows directory */

        let datums_dir = JSPath.GetDatumsDirectory();

        /* Datums file should be in a sub-directory of the datums directory
         *
         * datums/[regulation]/[crash_test]/[version]/[assessment_type]
         */

        datums_dir = `${datums_dir}/${regulation}/${crash_test}/${version}/${assessment_type}`;

        /* File to read */

        let datums_filename = `${datums_dir}/${filename}.json`;

        /* Not an error if it doesn't exist - silently ingore */

        if (!File.Exists(datums_filename)) {
            return null;
        }

        /* Try and parse the JSON file to create the datums. Expected format is:
         *
         *  {
         *      "name": "NECK_TENSION",
         *      "datums": [
         *          {
         *              "name": "GOOD",
         *              "value": 1.7,
         *              "line_colour": "GREEN",
         *              "colour_above": "ORANGE",
         *              "colour_below": "GREEN"
         *          },
         *          {
         *              "name": "MARGINAL",
         *              "value": 2.62,
         *              "line_colour": "ORANGE",
         *              "colour_above": "RED"
         *          }
         *      ]
         *  }
         *
         */

        try {
            let f = new File(datums_filename, File.READ);
            let text = f.ReadAll();
            f.Close();

            let json = JSON.parse(text);

            let name_prefix = `${regulation}_${crash_test}_${json.name}`;

            let datums = [];

            for (let datum of json.datums) {
                /* Colours will either be a string, e.g. "GREEN", "YELLOW", etc.
                 * or an array of RGB numbers. Convert them to a Colour class number.  */

                let line_colour = AssessmentDatums.Colour(datum.line_colour);
                let colour_above = AssessmentDatums.Colour(datum.colour_above);
                let colour_below = AssessmentDatums.Colour(datum.colour_below);
                let colour_left = AssessmentDatums.Colour(datum.colour_left);
                let colour_right = AssessmentDatums.Colour(datum.colour_right);

                let pd = new ProtocolDatum(
                    datum.value,
                    `${name_prefix}_${datum.name}`,
                    line_colour,
                    colour_above,
                    colour_below,
                    colour_left,
                    colour_right
                );

                datums.push(pd);
            }

            return new AssessmentDatums(datums, assessment_type);
        } catch (e) {
            ErrorMessage(e);
            return null;
        }
    }

    /**
     * Converts the string or array of RGB numbers to a Colour
     * @param {string | number[]} colour
     * @returns {?number}
     */
    static Colour(colour) {
        if (!colour) return null;

        /* RGB array */
        if (colour instanceof Array) {
            return Colour.RGB(colour[0], colour[1], colour[2]);

            /* Colour strings */
        } else {
            /* Use RGB colours for GREEN, YELLOW, ORANGE, BROWN and RED
             * For any other string get it from the Colour class constant */
            if (colour == "GREEN") {
                return Colour.RGB(38, 155, 41);
            } else if (colour == "YELLOW") {
                return Colour.RGB(255, 204, 0);
            } else if (colour == "ORANGE") {
                return Colour.RGB(255, 151, 0);
            } else if (colour == "BROWN") {
                return Colour.RGB(117, 63, 42);
            } else if (colour == "RED") {
                return Colour.RGB(224, 7, 0);

                /* Colour class constant */
            } else {
                return Colour[colour];
            }
        }
    }

    /* Instance property getter and setters */

    /**
     * Array of ProtocolDatum instances
     * @type {ProtocolDatum[]}
     */
    get datums() {
        return this._datums;
    }
    set datums(new_datums) {
        if (!(new_datums instanceof Array)) {
            throw new Error("datums must be an Array");
        }

        for (let new_datum of new_datums) {
            if (!(new_datum instanceof ProtocolDatum)) {
                throw new Error("datums must be an array of ProtocolDatum instances");
            }
        }

        this._datums = new_datums;
    }

    /**
     * Assessment type
     * @type {string}
     */
    get assessment() {
        return this._assessment;
    }
    set assessment(new_assessment) {
        if (AssessmentType.GetAll().indexOf(new_assessment) == -1) {
            throw new Error(`Invalid assessment: ${new_assessment}`);
        }

        this._assessment = new_assessment;
    }

    /* Instance methods */

    /**
     * Return the minimum X value for the assessment datums
     * @returns {number}
     */
    MinX() {
        let min_x = Number.MAX_VALUE;

        for (let datum of this.datums) {
            min_x = Math.min(min_x, datum.MinX());
        }

        return min_x;
    }

    /**
     * Return the maximum X value for the assessment datums
     * @returns {number}
     */
    MaxX() {
        let max_x = -Number.MAX_VALUE;

        for (let datum of this.datums) {
            max_x = Math.max(max_x, datum.MaxX());
        }

        return max_x;
    }

    /**
     * Return the minimum Y value for the assessment datums
     * @returns {number}
     */
    MinY() {
        let min_y = Number.MAX_VALUE;

        for (let datum of this.datums) {
            min_y = Math.min(min_y, datum.MinY());
        }

        return min_y;
    }

    /**
     * Return the maximum Y value for the assessment datums
     * @returns {number}
     */
    MaxY() {
        let max_y = -Number.MAX_VALUE;

        for (let datum of this.datums) {
            max_y = Math.max(max_y, datum.MaxY());
        }

        return max_y;
    }

    /**
     * Extends the last point on the datums to the specified X value
     * @param {number} x X value to extend the datum to
     */
    ExtendLastYValueToX(x) {
        if (!this.datums) return;

        for (let datum of this.datums) {
            datum.ExtendLastYValueToX(x);
        }
    }

    /**
     * Plot all the datums on a graph
     * @param {number} [graph] Index of graph to plot on. If not specified, it's plotted on all graphs
     */
    Plot(graph) {
        if (!this.datums) return;

        for (let datum of this.datums) datum.Plot(graph);
    }
}