modules/post/datums.mjs

import { THisHelper } from "./this.mjs";

export { ProtocolDatum };

/** Class representing a datum for a protocol */
class ProtocolDatum {
    /**
     *
     * @param {number | number[]} value Datum value or array of X, Y pairs
     * @param {string} name Datum name
     * @param {?number} line_colour Line colour
     * @param {?number} colour_above Colour to fill above datum
     * @param {?number} colour_below Colour to fill below datum
     * @param {?number} colour_left Colour to fill to the left of the datum
     * @param {?number} colour_right Colour to fill to the right of the datum
     * @example
     * // Constant value datum
     * let pd = new ProtocolDatum(10.0, "My Datum", Colour.GREEN, Colour.RED, Colour.GREEN);
     *
     * // Variable value datum
     * let pd = new ProtocolDatum([0.0, 10.0, 0.5, 20.0, 1.0, 30.0],
     *                            "My Datum",
     *                            Colour.GREEN,
     *                            Colour.RED,
     *                            Colour.GREEN);
     */
    constructor(value, name, line_colour, colour_above, colour_below, colour_left, colour_right) {
        this.value = value;
        this.name = name;
        this.line_colour = line_colour;
        this.colour_above = colour_above;
        this.colour_below = colour_below;
        this.colour_left = colour_left;
        this.colour_right = colour_right;
    }

    /* Instance property getter and setters */

    /**
     * Datum value or array of X, Y pairs
     * @type {number | number[]}
     */
    get value() {
        return this._value;
    }
    set value(new_value) {
        if (typeof new_value != "number" && !(new_value instanceof Array)) {
            throw new Error("value must be a number or an array of numbers");
        }

        this._value = new_value;
    }

    /**
     * Datum name
     * @type {string}
     */
    get name() {
        return this._name;
    }
    set name(new_name) {
        if (typeof new_name != "string") {
            throw new Error("name must be a string");
        }

        this._name = new_name;
    }

    /**
     * Line colour
     * @type {?number}
     */
    get line_colour() {
        return this._line_colour;
    }
    set line_colour(new_line_colour) {
        if (typeof new_line_colour != "number") {
            throw new Error("line_colour must be a number");
        }

        this._line_colour = new_line_colour;
    }

    /**
     * Colour to fill above datum
     * @type {?number}
     */
    get colour_above() {
        return this._colour_above;
    }
    set colour_above(new_colour_above) {
        if (new_colour_above && typeof new_colour_above != "number") {
            throw new Error("colour_above must be a number");
        }

        this._colour_above = new_colour_above;
    }

    /**
     * Colour to fill below datum
     * @type {?number}
     */
    get colour_below() {
        return this._colour_below;
    }
    set colour_below(new_colour_below) {
        if (new_colour_below && typeof new_colour_below != "number") {
            throw new Error("colour_below must be a number");
        }

        this._colour_below = new_colour_below;
    }

    /**
     * Colour to fill to left of datum
     * @type {?number}
     */
    get colour_left() {
        return this._colour_left;
    }
    set colour_left(new_colour_left) {
        if (new_colour_left && typeof new_colour_left != "number") {
            throw new Error("colour_left must be a number");
        }

        this._colour_left = new_colour_left;
    }

    /**
     * Colour to fill to right of datum
     * @type {?number}
     */
    get colour_right() {
        return this._colour_right;
    }
    set colour_right(new_colour_right) {
        if (new_colour_right && typeof new_colour_right != "number") {
            throw new Error("colour_right must be a number");
        }

        this._colour_right = new_colour_right;
    }

    /* Instance methods */

    /**
     * Returns the minimum X value of the datum
     * If it's a constant value datum, returns Number.MAX_VALUE
     * @returns {number}
     * @example
     * let min_x = pd.MinX();
     */
    MinX() {
        let min_x = Number.MAX_VALUE;

        if (this.value instanceof Array) {
            for (let i = 0; i < this.value.length; i += 2) {
                if (this.value[i] < min_x) min_x = this.value[i];
            }
        }

        return min_x;
    }

    /**
     * Returns the maximum X value of the datum
     * If it's a constant value datum, returns -Numer.MAX_VALUE
     * @returns {number}
     * @example
     * let max_x = pd.MaxX();
     */
    MaxX() {
        let max_x = -Number.MAX_VALUE;

        if (this.value instanceof Array) {
            for (let i = 0; i < this.value.length; i += 2) {
                if (this.value[i] > max_x) max_x = this.value[i];
            }
        }

        return max_x;
    }

    /**
     * Returns the minimum Y value of the datum
     * @returns {number}
     * @example
     * let min_y = pd.MinY();
     */
    MinY() {
        let min_y = Number.MAX_VALUE;

        if (this.value instanceof Array) {
            for (let i = 1; i < this.value.length; i += 2) {
                if (this.value[i] < min_y) min_y = this.value[i];
            }
        } else {
            min_y = this.value;
        }

        return min_y;
    }

    /**
     * Returns the maximum Y value of the datum
     * @returns {number}
     * @example
     * let max_y = pd.MaxY();
     */
    MaxY() {
        let max_y = -Number.MAX_VALUE;

        if (this.value instanceof Array) {
            for (let i = 1; i < this.value.length; i += 2) {
                if (this.value[i] > max_y) max_y = this.value[i];
            }
        } else {
            max_y = this.value;
        }

        return max_y;
    }

    /**
     * Extends the last point on the datum to the specified X value
     * @param {number} x X value to extend the datum to
     */
    ExtendLastYValueToX(x) {
        if (this.value instanceof Array) {
            if (x <= this.value[this.value.length - 2]) return;

            this.value.push(x);
            this.value.push(this.value[this.value.length - 2]);
        }
    }

    /**
     * Plot a datum
     * @param {number} [graph] Index of graph to plot on. If not specified, it's plotted on all graphs
     * @example
     * pd.Plot();
     */
    Plot(graph) {
        /* Delete a datum if it already exists, storing the graphs it is
         * currently on so we can add it back later */

        let on_graphs = [];

        if (Datum.Exists(this.name)) {
            let d = Datum.GetFromAcronym(this.name);

            for (let i = 1; i < THisHelper.MAX_GRAPHS; i++) {
                if (d.IsOnGraph(i)) {
                    on_graphs.push(i);
                }
            }

            Datum.Delete(this.name);
        }

        /* Create a new datum */

        /** @type {Datum} */
        let datum;

        if (this.value instanceof Array) {
            datum = new Datum(this.name, Datum.POINTS, this.value);
        } else {
            datum = new Datum(this.name, Datum.CONSTANT_Y, this.value);
        }

        if (this.line_colour) datum.line_colour = this.line_colour;
        if (this.colour_below) datum.fill_colour_below = this.colour_below;
        if (this.colour_above) datum.fill_colour_above = this.colour_above;

        if (this.colour_left) {
            datum.fill_type = Datum.FILL_RIGHT_LEFT;
            datum.fill_colour_left = this.colour_left;
        }

        if (this.colour_right) {
            datum.fill_type = Datum.FILL_RIGHT_LEFT;
            datum.fill_colour_right = this.colour_right;
        }

        /* If the graph is specified, plot the datum on that graph, otherwise plot it on all graphs */

        if (graph) {
            datum.RemoveFromGraph();
            datum.AddToGraph(graph);
        } else {
            datum.AddToGraph();
        }

        /* Add it to the graphs it was originally on */

        for (let id of on_graphs) {
            datum.AddToGraph(id);
        }

        /* Update the graphs in T/HIS */

        Plot();
    }
}