Skip to content
Snippets Groups Projects
var-results.ts 15.6 KiB
Newer Older
import { CalculatedParamResults } from "./param-calc-results";
import { ServiceFactory } from "../services/service-factory";
mathias.chouet's avatar
mathias.chouet committed
import { PlottableData } from "./plottable-data";
import { ChartType } from "./chart-type";
import { longestVarParam } from "../util/util";
import { FormulaireDefinition } from "../formulaire/definition/form-definition";
mathias.chouet's avatar
mathias.chouet committed
import { sprintf } from "sprintf-js";

import { ResultElement, ParamFamily, capitalize, Nub, VariatedDetails, ParamDefinition, ParamDomain, ParamDomainValue } from "jalhyd";
mathias.chouet's avatar
mathias.chouet committed
export class VarResults extends CalculatedParamResults implements PlottableData {
     * paramètres variés
    private _variatedParams: VariatedDetails[] = [];
     * titre des colonnes des résultats variés
    private _variableParamHeaders: string[];
Mathias Chouet's avatar
Mathias Chouet committed
     * type de graphique
    protected _graphType: ChartType = ChartType.Scatter;
    /** pointer to form that instantiated this object */
    protected _form: FormulaireDefinition;

     * variated parameter or result displayed as chart's X-axis
    public chartX: string;
    /**
     * variated parameter or result displayed as chart's Y-axis
     */
    public chartY: string;
    /** size of the longest variated parameter */
    public size: number;

    /** index of the longest variated parameter */
    public longest: number;

Mathias Chouet's avatar
Mathias Chouet committed
     * tableau des ordonnées du graphique des résultats variés
     */
    private _yValues: number[] = [];

    constructor(form?: FormulaireDefinition) {
        this._form = form;
        this.reset();
    }

    public reset() {
        super.reset();
        this._variableParamHeaders = [];
        this._resultHeaders = [];
        this.resultKeys = [];
    public get variatedParameters(): VariatedDetails[] {
        return this._variatedParams;
    public set variatedParameters(p: VariatedDetails[]) {
        this._variatedParams = p;
    public get variableParamHeaders() {
        return this._variableParamHeaders;
    }

    public get yValues() {
        return this._yValues;
    }

    public get resultElements(): ResultElement[] {
mathias.chouet's avatar
mathias.chouet committed
        return this.result.resultElements;
    public get resultHeaders() {
        return this._resultHeaders;
    public get chartType(): ChartType {
mathias.chouet's avatar
mathias.chouet committed
        return this._graphType;
    }

    public set chartType(gt: ChartType) {
mathias.chouet's avatar
mathias.chouet committed
        this._graphType = gt;
        this.resetDefaultAxisIfNeeded();
    }

    public hasPlottableResults(): boolean {
        if (this.result === undefined) {
            return false;
        return ! this.result.hasOnlyErrors;
    }

    public getChartAxisLabel(symbol: string): string {
        if (this.result) {
            // 1. calculated param ?
            if (this.calculatedParameter && this.calculatedParameter.symbol === symbol) {
                return this.calculatedParameterHeader;
            }
            // 2. variated param ?
            for (let i = 0; i < this.variatedParameters.length; i++) {
                if (this._variatedParams[i].param.symbol === symbol) {
                    return this.variableParamHeaders[i];
                }
            // 3. Result element / child result
            return this.expandLabelFromSymbol(new ParamDefinition(undefined, symbol, ParamDomainValue.ANY));
     * Returns the translated name of the given symbol (usually a result or child result) with
     * its unit, but without the symbol itself
     */
    public expandLabelFromSymbol(p: ParamDefinition): string {
        // calculator type for translation
        const sn = this.result.sourceNub;
        let ct = sn.calcType;
        if (sn.parent) {
            ct = sn.parent.calcType;
        }
        const match = /^([0-9]+)_(.+)$/.exec(p.symbol);
        let symbol = p.symbol;
            // only parent translation file is loaded; look for children translations in it // ct = sn.getChildren()[pos].calcType;
            const cn = capitalize(ServiceFactory.i18nService.childName(sn.getChildren()[0]));
            ret += sprintf(ServiceFactory.i18nService.localizeText("INFO_STUFF_N"), cn) + (pos + 1) + " : ";
        ret += ServiceFactory.formulaireService.expandVariableNameAndUnit(ct, symbol);
    /**
     * Returns the series of values for the required variated parameter / result element
     * @param symbol parameter / result symbol (ex: "Q", "0_Q"...)
mathias.chouet's avatar
mathias.chouet committed
    public getValuesSeries(symbol: string): number[] {
        let found = false;
        const series: number[] = [];
mathias.chouet's avatar
mathias.chouet committed
        // detect children results
        const isChildResult = /^([0-9]+)_(.+)$/.exec(symbol);

        for (let i = 0; i < this.variatedParameters.length; i++) {
mathias.chouet's avatar
mathias.chouet committed
            const vp = this._variatedParams[i];
            let isTheGoodChild = false;
            // are we looking for a child variated param ?
            if (isChildResult !== null) {
                const children = this.result.sourceNub.getChildren();
                const parameterNub = vp.param.parentNub;
mathias.chouet's avatar
mathias.chouet committed
                if (children.includes(parameterNub)) { // current var param is a child param !
                    const pos = parameterNub.findPositionInParent();
                    isTheGoodChild = (pos === +isChildResult[1] && vp.param.symbol === isChildResult[2]);
mathias.chouet's avatar
mathias.chouet committed
                }
            }
            // in any case
            if (isTheGoodChild || vp.param.symbol === symbol) {
mathias.chouet's avatar
mathias.chouet committed
                found = true;
                const iter = vp.param.getExtendedValuesIterator(this.size);
mathias.chouet's avatar
mathias.chouet committed
        if (! found) {
            for (const r of this.result.resultElements) { // re:ResultElement
                for (const k in r.values) {
                    if (k === symbol) {
                        found = true;
                        series.push(r.getValue(k));
                    }
mathias.chouet's avatar
mathias.chouet committed
        if (! found) {
mathias.chouet's avatar
mathias.chouet committed
            if (isChildResult !== null) {
mathias.chouet's avatar
mathias.chouet committed
                found = true;
                const sn = this.result.sourceNub;
mathias.chouet's avatar
mathias.chouet committed
                const pos = +isChildResult[1];
                symbol = isChildResult[2];
mathias.chouet's avatar
mathias.chouet committed
                const child = sn.getChildren()[pos];
                for (const r of child.result.resultElements) {
                    series.push(r.getValue(symbol));
                }

        return series;
    }

    /**
     * Returns a list of plottable parameters / result elements, that can be defined
mathias.chouet's avatar
mathias.chouet committed
     * as X chart axis
mathias.chouet's avatar
mathias.chouet committed
    public getAvailableXAxis(): string[] {
mathias.chouet's avatar
mathias.chouet committed
        let res: string[] = [];
        res = res.concat(this.getVariatingParametersSymbols());
        for (const erk of this.resultKeys) {
            if (erk.indexOf("ENUM_") === -1) { // ENUM variables are not plottable
                res.push(erk);
            }
        }
        // children results
        if (this.result) {
            const sn = this.result.sourceNub;
            for (const c of sn.getChildren()) {
                if (c.result) {
                    // using latest ResultElement; results count / types are supposed to be the same on every iteration
                    for (const k of c.result.resultElement.keys) {
                        if (k.indexOf("ENUM_") === -1) { // ENUM variables are not plottable
                            res.push(c.findPositionInParent() + "_" + k);
                        }
mathias.chouet's avatar
mathias.chouet committed
                    }
mathias.chouet's avatar
mathias.chouet committed
    /**
     * Same as X axis, plus results families if chart type is Scatter
mathias.chouet's avatar
mathias.chouet committed
     * (for multi-series comparison)
     */
    public getAvailableYAxis(): string[] {
        const res: string[] = this.getAvailableXAxis();
        if (this._graphType === ChartType.Scatter) {
mathias.chouet's avatar
mathias.chouet committed
            // add families having more than 1 variable as plottable ordinates
            const families = this.extractFamilies();
            for (const f in families) {
                if (families[f].length > 1) {
                    res.push(f);
                }
            }
        }
        return res;
    }

    /**
     * Browses all parameters and results to produce a map of families => list of
     * symbols in this family
     */
mathias.chouet's avatar
mathias.chouet committed
    public extractFamilies(): { [key: string]: string[] } {
mathias.chouet's avatar
mathias.chouet committed
        const families: { [key: string]: string[] } = {};
mathias.chouet's avatar
mathias.chouet committed
        // variating parameters
mathias.chouet's avatar
mathias.chouet committed
        for (const v of this._variatedParams) {
            // exclude pseudo-family "ANY"
            const fam = v.param.family;
            if (fam !== undefined && fam !== ParamFamily.ANY) {
                const f = ParamFamily[fam];
mathias.chouet's avatar
mathias.chouet committed
                if (! (f in families)) {
                    families[f] = [];
                }
                families[f].push(v.param.symbol);
mathias.chouet's avatar
mathias.chouet committed
            }
        }
mathias.chouet's avatar
mathias.chouet committed
        // results
            const fam = this.result.sourceNub.getFamily(erk);
            // exclude pseudo-family "ANY"
            if (fam !== undefined && fam !== ParamFamily.ANY) {
                const f = ParamFamily[fam];
mathias.chouet's avatar
mathias.chouet committed
                if (! (f in families)) {
                    families[f] = [];
                }
                families[f].push(erk);
            }
        }
mathias.chouet's avatar
mathias.chouet committed
        // children results
        if (this.result) {
            const sn = this.result.sourceNub;
            for (const c of sn.getChildren()) {
                if (c.result) {
                    for (const k of c.result.resultElement.keys) {
                        const fam = this.result.sourceNub.getFamily(k);
                        // exclude pseudo-family "ANY"
                        if (fam !== undefined && fam !== ParamFamily.ANY) {
                            const f = ParamFamily[fam];
                            if (! (f in families)) {
                                families[f] = [];
                            }
                            const pos = c.findPositionInParent();
                            families[f].push(pos + "_" + k);
mathias.chouet's avatar
mathias.chouet committed
        return families;
    }

    /**
     * Returns the list of variating parameters
     * (used by tooltip functions)
     */
    public getVariatingParametersSymbols(): string[] {
        if (this.result && this.result.sourceNub) {
            return this._variatedParams.map(vp => this.getVariatingParameterSymbol(vp.param, this.result.sourceNub));
        } else {
            return [];
        }
    public getVariatingParameterSymbol(vp: ParamDefinition, sourceNub: Nub): string {
        // detect if variated param is a children param
        const parameterNub = vp.parentNub;
        const children = sourceNub.getChildren();
        let symb = vp.symbol;
        if (children.includes(parameterNub)) {
            symb = parameterNub.findPositionInParent() + "_" + symb;
        }
        return symb;
mathias.chouet's avatar
mathias.chouet committed
    public update() {
        // refresh param headers and build source nub headers
        const parentVariatedParameters = this._variatedParams.filter(v => v.param.nubCalcType === this.result.sourceNub.calcType);
        this._variableParamHeaders = parentVariatedParameters.map((v) => {
            let h = this.expandLabelFromSymbol(v.param);
            h += this.getHelpLink(v.param.symbol);
mathias.chouet's avatar
mathias.chouet committed
        });
        const lvp = longestVarParam(this._variatedParams);
        this.size = lvp.size;
        this.longest = lvp.index;
        // result keys (extra or not) - some lines might miss some results, in case of an error;
        // use those keys to ensure all columns are filled
        if (this.resultKeys.length === 0) {
            if (this.result.symbol !== undefined) {
                this.resultKeys.push(this.result.symbol);
            }
mathias.chouet's avatar
mathias.chouet committed
            for (const re of this.result.resultElements) { // re:ResultElement
                for (const erk in re.values) {
                    if (!this.resultKeys.includes(erk)) {
                        this.resultKeys.push(erk);
        // set axis selectors values the first time
        let defaultY = this.chartY;
        if (this.resultKeys.length > 0) {
            defaultY = this.resultKeys[0];
        if (this.chartX === undefined || ! this.getAvailableXAxis().includes(this.chartX)) {
            this.chartX = this.getVariatingParameterSymbol(this.variatedParameters[this.longest].param, this.result.sourceNub);
        // calculator type for translation
        const sn = this.result.sourceNub;
        let ct = sn.calcType;
        if (sn.parent) {
            ct = sn.parent.calcType;
        // entêtes des résultats
        this._resultHeaders = [];
        for (const k of this.resultKeys) {
            let unit;
            // is k the calculated parameter ? If so, extract its unit
mathias.chouet's avatar
mathias.chouet committed
            try {
                const p = sn.getParameter(k);
                if (p) {
                    unit = p.unit;
                }
            } catch (e) { /* silent fail */ }
            let rh = ServiceFactory.formulaireService.expandVariableNameAndUnit(ct, k, unit);
            rh += this.getHelpLink(k);
            this._resultHeaders.push(rh);
mathias.chouet's avatar
mathias.chouet committed
        }
        // entêtes des résultats des enfants
        for (const c of sn.getChildren()) {
mathias.chouet's avatar
mathias.chouet committed
            if (c.result) {
                const cn = capitalize(ServiceFactory.i18nService.childName(c));
mathias.chouet's avatar
mathias.chouet committed
                // using latest ResultElement; results count / types are supposed to be the same on every iteration
                for (const k of c.result.resultElement.keys) {
                    let rh = sprintf(ServiceFactory.i18nService.localizeText("INFO_STUFF_N"), cn)
                        + (c.findPositionInParent() + 1) + " : "
                        + ServiceFactory.formulaireService.expandVariableNameAndUnit(ct, k);
                    rh += this.getHelpLink(k);
                    this._resultHeaders.push(rh);
mathias.chouet's avatar
mathias.chouet committed
                }
                // build header for children variated parameters
                for (const v of c.findVariatedParams()) {
                    let h = sprintf(ServiceFactory.i18nService.localizeText("INFO_STUFF_N"), cn)
                        + (c.findPositionInParent() + 1) + " : "
                        + ServiceFactory.formulaireService.expandVariableNameAndUnit(ct, v.param.symbol);
                    h += this.getHelpLink(v.param.symbol);
                    this.variableParamHeaders.push(h);
                }
                // this.variableParamHeaders.push()
mathias.chouet's avatar
mathias.chouet committed
        this.resetDefaultAxisIfNeeded();
    }
mathias.chouet's avatar
mathias.chouet committed
    /**
     * When variable parameter or chart type changes, ensure the X / Y current values are still available
mathias.chouet's avatar
mathias.chouet committed
     */
    public resetDefaultAxisIfNeeded() {
        if (this.variatedParameters.length > 0 && ! this.getAvailableXAxis().includes(this.chartX)) {
            this.chartX = this.variatedParameters[0].param.symbol;
        if (this.variatedParameters.length > 0 && ! this.getAvailableYAxis().includes(this.chartY)) {
            this.chartY = this.variatedParameters[0].param.symbol;