From d4a842c618d04ff6f8bed4c6e845bdc651dc6d5d Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Wed, 10 Jul 2019 15:28:34 +0200
Subject: [PATCH] Fix #234 : profil en long de la passe

---
 src/app/app.module.ts                         |   2 +
 .../pab-profile-graph.component.html          |  18 ++
 .../pab-profile-graph.component.scss          |  23 ++
 .../pab-profile-graph.component.ts            | 280 ++++++++++++++++++
 .../pab-results/pab-results.component.html    |   7 +-
 .../pab-results/pab-results.component.scss    |   4 +
 .../pab-results/pab-results.component.ts      |  31 +-
 src/app/results/plottable-pab-results.ts      |   2 +-
 8 files changed, 357 insertions(+), 10 deletions(-)
 create mode 100644 src/app/components/pab-profile-graph/pab-profile-graph.component.html
 create mode 100644 src/app/components/pab-profile-graph/pab-profile-graph.component.scss
 create mode 100644 src/app/components/pab-profile-graph/pab-profile-graph.component.ts
 create mode 100644 src/app/components/pab-results/pab-results.component.scss

diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index ae38d7ea0..c9ec24010 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -79,6 +79,7 @@ import { VarResultsComponent } from "./components/fixedvar-results/var-results.c
 import { LogEntryComponent } from "./components/log-entry/log-entry.component";
 import { ParamLinkComponent } from "./components/param-link/param-link.component";
 import { SelectModelFieldLineComponent } from "./components/select-model-field-line/select-model-field-line.component";
+import { PabProfileGraphComponent } from "./components/pab-profile-graph/pab-profile-graph.component";
 import { PabTableComponent } from "./components/pab-table/pab-table.component";
 import { PabVariableResultsSelectorComponent } from "./components/pab-results/pab-variable-results-selector.component";
 
@@ -183,6 +184,7 @@ const appRoutes: Routes = [
     LogComponent,
     LogEntryComponent,
     NgParamInputComponent,
+    PabProfileGraphComponent,
     PabResultsComponent,
     PabResultsTableComponent,
     PabTableComponent,
diff --git a/src/app/components/pab-profile-graph/pab-profile-graph.component.html b/src/app/components/pab-profile-graph/pab-profile-graph.component.html
new file mode 100644
index 000000000..bfbe7c551
--- /dev/null
+++ b/src/app/components/pab-profile-graph/pab-profile-graph.component.html
@@ -0,0 +1,18 @@
+<div class="graph-results-container" #graphProfile fxLayout="row wrap" fxLayoutAlign="center center">
+    <div fxFlex="1 1 100%">
+        <div class="graph-profile-buttons">
+            <button mat-icon-button (click)="exportAsImage(graphProfile)">
+                <mat-icon color="primary">image</mat-icon>
+            </button>
+            <button mat-icon-button *ngIf="! isFullscreen" (click)="setFullscreen(graphProfile)">
+                <mat-icon color="primary" class="scaled12">fullscreen</mat-icon>
+            </button>
+            <button mat-icon-button *ngIf="isFullscreen" (click)="exitFullscreen()">
+                <mat-icon color="primary" class="scaled12">fullscreen_exit</mat-icon>
+            </button>
+        </div>
+
+        <chart type="scatter" [data]="graph_data" [options]="graph_options" #graphChart>
+        </chart>
+    </div>
+</div>
diff --git a/src/app/components/pab-profile-graph/pab-profile-graph.component.scss b/src/app/components/pab-profile-graph/pab-profile-graph.component.scss
new file mode 100644
index 000000000..6382f8a70
--- /dev/null
+++ b/src/app/components/pab-profile-graph/pab-profile-graph.component.scss
@@ -0,0 +1,23 @@
+.graph-results-container{
+    display: block;
+    background-color: white;
+}
+
+.graph-profile-buttons {
+    padding-right: 10px;
+    padding-top: 4px;
+    margin-bottom: -30px;
+    text-align: right;
+    background-color: white;
+
+    button {
+        margin-left: 3px;
+        width: auto;
+
+        mat-icon {
+            &.scaled12 {
+                transform: scale(1.2);
+            }
+        }
+    }
+}
diff --git a/src/app/components/pab-profile-graph/pab-profile-graph.component.ts b/src/app/components/pab-profile-graph/pab-profile-graph.component.ts
new file mode 100644
index 000000000..921eea440
--- /dev/null
+++ b/src/app/components/pab-profile-graph/pab-profile-graph.component.ts
@@ -0,0 +1,280 @@
+import { Component } from "@angular/core";
+
+import { ApplicationSetupService } from "../../services/app-setup/app-setup.service";
+import { I18nService } from "../../services/internationalisation/internationalisation.service";
+import { ResultsComponent } from "../fixedvar-results/results.component";
+import { PabResults } from "../../results/pab-results";
+
+@Component({
+    selector: "pab-profile-graph",
+    templateUrl: "./pab-profile-graph.component.html",
+    styleUrls: [
+        "./pab-profile-graph.component.scss"
+    ]
+})
+export class PabProfileGraphComponent extends ResultsComponent {
+
+    private _results: PabResults;
+
+    /*
+     * config du graphe
+     */
+    public graph_data: { datasets: any[] };
+    public graph_options = {
+        responsive: true,
+        maintainAspectRatio: true,
+        animation: {
+            duration: 0
+        },
+        legend: {
+            display: false
+        },
+        title: {
+            display: true,
+            text: "Profil en long de la passe"
+        },
+        elements: {
+            line: {
+                tension: 0
+            }
+        }
+    };
+
+    public constructor(
+        private appSetup: ApplicationSetupService,
+        private intlService: I18nService
+    ) {
+        super();
+    }
+
+    public set results(r: PabResults) {
+        this._results = r;
+    }
+
+    public updateView() {
+        console.log("PPG => updateView() !");
+        this.generateScatterGraph();
+    }
+
+    /**
+     * génère les données d'un graphe de type "scatter"
+     */
+    private generateScatterGraph() {
+        const nDigits = this.appSetup.displayDigits;
+        const ySeries = this.getYSeries();
+        this.graph_data = {
+            datasets: []
+        };
+
+        this.graph_options["scales"] = {
+            xAxes: [{
+                type: "linear",
+                position: "bottom",
+                ticks: {
+                    precision: nDigits
+                },
+                scaleLabel: {
+                    display: true,
+                    labelString: "la super abscisse"
+                    // labelString: this.axisLabelWithoutSymbol(this.chartX)
+                }
+            }],
+            yAxes: [{
+                type: "linear",
+                position: "left",
+                ticks: {
+                    precision: nDigits
+                },
+                scaleLabel: {
+                    display: true,
+                    labelString: "la super ordonnée"
+                    /// labelString: this.axisLabelWithoutSymbol(this.chartY)
+                }
+            }]
+        };
+
+        // build Y data series
+        for (const ys of ySeries) {
+            // push series config
+            this.graph_data.datasets.push({
+                label: ys.label,
+                data: ys.data,
+                borderColor: ys.color, // couleur de la ligne
+                backgroundColor: "rgba(0,0,0,0)",  // couleur de remplissage sous la courbe : transparent
+                showLine: "true"
+            });
+        }
+
+        /* const that = this;
+        this.graph_options["tooltips"] = {
+            displayColors: false,
+            callbacks: {
+                title: (tooltipItems, data) => {
+                    return this.chartY + " = " + Number(tooltipItems[0].yLabel).toFixed(nDigits);
+                },
+                label: (tooltipItem, data) => {
+                    const lines: string[] = [];
+                    const nbLines = that._results.getVariatingParametersSymbols().length;
+                    for (const v of that._results.getVariatingParametersSymbols()) {
+                        const series = that._results.getValuesSeries(v);
+                        const line = v + " = " + series[tooltipItem.index].toFixed(nDigits);
+                        if (v === this.chartX) {
+                            if (nbLines > 1) {
+                                lines.unshift("");
+                            }
+                            lines.unshift(line);
+                        } else {
+                            lines.push(line);
+                        }
+                    }
+                    return lines;
+                }
+            }
+        }; */
+    }
+
+    public exportAsImage(element: HTMLDivElement) {
+        const canvas: HTMLCanvasElement = element.querySelector("canvas");
+        canvas.toBlob((blob) => {
+            saveAs(blob, "chart.png");
+        }); // defaults to image/png
+    }
+
+    private getXSeries(): string[] {
+        const data: string[] = [];
+        const nDigits = this.appSetup.displayDigits;
+        // X is always wall abscissa
+        for (const cr of this._results.cloisonsResults) {
+            const x = cr.resultElement.getExtraResult("x"); // any resultElement will do
+            data.push(x.toFixed(nDigits));
+        }
+        const xdw = this._results.cloisonAvalResults.resultElement.getExtraResult("x");
+        data.push(xdw.toFixed(nDigits));
+        return data;
+    }
+
+    private getYSeries(): { data: { x: string, y: string }[], label: string, color: string }[] {
+        const ret: { data: { x: string, y: string }[], label: string, color: string }[] = [];
+        const xs = this.getXSeries(); // abscissae
+        const pabLength = Number(xs[xs.length - 1]) - Number(xs[0]);
+        const pabLength5Pct = (pabLength * 5) / 100;
+
+        // 1. fond du machin
+        const dataF: { x: string, y: string }[] = [];
+        const nDigits = this.appSetup.displayDigits;
+        // extend upstrem
+        dataF.push({
+            x: (Number(xs[0]) - pabLength5Pct).toFixed(nDigits),
+            y: this._results.cloisonsResults[0].resultElement.getExtraResult("ZRAM").toFixed(nDigits)
+        });
+        // regular walls
+        for (let i = 0; i < this._results.cloisonsResults.length; i++) {
+            const cr = this._results.cloisonsResults[i];
+            const ZRAM = cr.resultElement.getExtraResult("ZRAM"); // any ResultElement will do
+            dataF.push({
+                x: xs[i],
+                y: ZRAM.toFixed(nDigits)
+            });
+        }
+        // downwall
+        const ZRAMdw = this._results.cloisonAvalResults.resultElement.getExtraResult("ZRAM");
+        dataF.push({
+            x: xs[ xs.length - 1 ],
+            y: ZRAMdw.toFixed(nDigits)
+        });
+        // extend downstream
+        dataF.push({
+            x: (Number(xs[xs.length - 1]) + pabLength5Pct).toFixed(nDigits),
+            y: ZRAMdw.toFixed(nDigits)
+        });
+        // add series
+        ret.push({
+            data: dataF,
+            label: "fond du machin",
+            color: "#000000"
+        });
+
+        // 2. séries
+        const nbSeries = this._results.cloisonsResults[0].resultElements.length;
+        const palette = this.distinctColors;
+
+        for (let n = 0; n < nbSeries; n++) {
+            // --------- build nth series ---------
+            const dataN: { x: string, y: string }[] = [];
+            let i = 0; // abscissa index
+
+            // extend upstream
+            dataN.push({
+                x: (Number(xs[0]) - pabLength5Pct).toFixed(nDigits),
+                y: this._results.cloisonsResults[0].resultElements[n].vCalc.toFixed(nDigits)
+            });
+
+            // walls
+            for (const x of xs) {
+                let Z1: number;
+                let nextZ1: number;
+                if (i < xs.length - 2) {
+                    // regular walls
+                    Z1 = this._results.cloisonsResults[i].resultElements[n].vCalc;
+                    nextZ1 = this._results.cloisonsResults[i + 1].resultElements[n].vCalc;
+                } else if (i === xs.length - 2) {
+                    // last regular wall
+                    Z1 = this._results.cloisonsResults[i].resultElements[n].vCalc;
+                    nextZ1 = this._results.cloisonAvalResults.resultElements[n].vCalc;
+                } else {
+                    // downwall
+                    Z1 = this._results.cloisonAvalResults.resultElements[n].vCalc;
+                    nextZ1 = this._results.Z2[n];
+                }
+
+                // 2 points for each abscissa
+                dataN.push({
+                    x: x,
+                    y: Z1.toFixed(nDigits)
+                });
+                dataN.push({
+                    x: x,
+                    y: nextZ1.toFixed(nDigits)
+                });
+
+                i++;
+            }
+
+            // extend downstream
+            dataN.push({
+                x: (Number(xs[xs.length - 1]) + pabLength5Pct).toFixed(nDigits),
+                y: this._results.Z2[n].toFixed(nDigits)
+            });
+
+            ret.push({
+                data: dataN,
+                label: "série " + n,
+                color: palette[ n % palette.length ]
+            });
+        }
+
+        return ret;
+    }
+
+    /**
+     * 14 distinct colors @see https://sashat.me/2017/01/11/list-of-20-simple-distinct-colors
+     */
+    private get distinctColors(): string[] {
+        return [
+            "#4363d8", // blue
+            "#f58231", // orange
+            "#3cb44b", // green
+            "#e6194B", // red
+            "#911eb4", // purple
+            "#ffe119", // yellow
+            "#f032e6", // magenta
+            "#9A6324", // brown
+            "#000075", // navy
+            "#808000", // olive
+            "#42d4f4", // cyan
+            "#a9a9a9", // grey
+            "#bfef45", // lime
+            "#469990", // teal
+        ];
+    }
+}
diff --git a/src/app/components/pab-results/pab-results.component.html b/src/app/components/pab-results/pab-results.component.html
index b80345d1e..fbb8e9c23 100644
--- a/src/app/components/pab-results/pab-results.component.html
+++ b/src/app/components/pab-results/pab-results.component.html
@@ -12,6 +12,11 @@
         <pab-results-table *ngIf="hasDisplayableResults" [results]="pabResults"></pab-results-table>
     </div>
 
-    <results-graph *ngIf="hasDisplayableResults"></results-graph>
+    <div id="pab-graphs-container" class="container" fxLayout="row wrap" fxLayoutAlign="space-around start">
+        <pab-profile-graph *ngIf="hasDisplayableResults" fxFlex.gt-xs="1 0 400px" fxFlex.lt-sm="1 0 300px">
+        </pab-profile-graph>
+        <results-graph *ngIf="hasDisplayableResults" fxFlex.gt-xs="1 0 400px" fxFlex.lt-sm="1 0 300px">
+        </results-graph>
+    </div>
 
 </div>
diff --git a/src/app/components/pab-results/pab-results.component.scss b/src/app/components/pab-results/pab-results.component.scss
new file mode 100644
index 000000000..6f5eb10d5
--- /dev/null
+++ b/src/app/components/pab-results/pab-results.component.scss
@@ -0,0 +1,4 @@
+results-graph {
+    margin-left: 1em;
+    margin-right: 1em;
+}
diff --git a/src/app/components/pab-results/pab-results.component.ts b/src/app/components/pab-results/pab-results.component.ts
index 10f7aa941..2d7f0e2d2 100644
--- a/src/app/components/pab-results/pab-results.component.ts
+++ b/src/app/components/pab-results/pab-results.component.ts
@@ -1,6 +1,6 @@
 import { Component, ViewChild, DoCheck } from "@angular/core";
 
-import { Result, cLog, MessageSeverity, Message, MessageCode } from "jalhyd";
+import { Result, cLog, Message, MessageCode } from "jalhyd";
 
 import { LogComponent } from "../../components/log/log.component";
 import { CalculatorResults } from "../../results/calculator-results";
@@ -13,12 +13,13 @@ import { PlottableData } from "../../results/plottable-data";
 import { PlottablePabResults } from "../../results/plottable-pab-results";
 import { ResultsGraphComponent } from "../results-graph/results-graph.component";
 import { I18nService } from "../../services/internationalisation/internationalisation.service";
+import { PabProfileGraphComponent } from "../pab-profile-graph/pab-profile-graph.component";
 
 @Component({
     selector: "pab-results",
     templateUrl: "./pab-results.component.html",
     styleUrls: [
-        "../fixedvar-results/fixedvar-results.component.scss"
+        "./pab-results.component.scss"
     ]
 })
 export class PabResultsComponent implements DoCheck {
@@ -26,7 +27,7 @@ export class PabResultsComponent implements DoCheck {
     /** résultats non mis en forme */
     private _pabResults: PabResults;
 
-    /** résultats mis en forme pour le graphique */
+    /** résultats mis en forme pour le graphique de données (classique) */
     private _plottableResults: PlottablePabResults;
 
     /** true si les résultats doiventt être remis à jour */
@@ -47,6 +48,9 @@ export class PabResultsComponent implements DoCheck {
     @ViewChild(ResultsGraphComponent)
     private resultsGraphComponent: ResultsGraphComponent;
 
+    @ViewChild(PabProfileGraphComponent)
+    private profileGraphComponent: PabProfileGraphComponent;
+
     constructor(
         private appSetupService: ApplicationSetupService,
         private i18nService: I18nService,
@@ -83,6 +87,9 @@ export class PabResultsComponent implements DoCheck {
         if (this.resultsGraphComponent) {
             this.resultsGraphComponent.results = undefined;
         }
+        if (this.profileGraphComponent) {
+            this.profileGraphComponent.results = undefined;
+        }
         // set _doUpdate flag so that results are rebuilt on the next Angular display cycle
         this._doUpdate = false;
         if (this._pabResults !== undefined) {
@@ -198,7 +205,8 @@ export class PabResultsComponent implements DoCheck {
      */
     private updateResults() {
         let pabUpdated: boolean;
-        let graphUpdated: boolean;
+        let resultsGraphUpdated: boolean;
+        let profileGraphUpdated: boolean;
         let selectorUpdated: boolean;
 
         // results or not, there might be a log
@@ -218,18 +226,24 @@ export class PabResultsComponent implements DoCheck {
             if (selectorUpdated) {
                 this.pabVariableResultsSelectorComponent.results = this._pabResults;
             }
-            graphUpdated = this.resultsGraphComponent !== undefined;
-            if (graphUpdated) {
+            resultsGraphUpdated = this.resultsGraphComponent !== undefined;
+            if (resultsGraphUpdated) {
                 this.resultsGraphComponent.results = this.plottableResults;
                 this.resultsGraphComponent.updateView();
             }
+            profileGraphUpdated = this.profileGraphComponent !== undefined;
+            if (profileGraphUpdated) {
+                this.profileGraphComponent.results = this._pabResults;
+                this.profileGraphComponent.updateView();
+            }
         } else {
             pabUpdated = true;
-            graphUpdated = true;
+            resultsGraphUpdated = true;
+            profileGraphUpdated = true;
             selectorUpdated = true;
         }
 
-        return pabUpdated && logUpdated && graphUpdated && selectorUpdated;
+        return pabUpdated && logUpdated && resultsGraphUpdated && profileGraphUpdated && selectorUpdated;
     }
 
     public get pabResults() {
@@ -273,4 +287,5 @@ export class PabResultsComponent implements DoCheck {
         this._plottableResults.setPabResults(this.pabResults);
         return this._plottableResults;
     }
+
 }
diff --git a/src/app/results/plottable-pab-results.ts b/src/app/results/plottable-pab-results.ts
index ae7b294ef..901069fba 100644
--- a/src/app/results/plottable-pab-results.ts
+++ b/src/app/results/plottable-pab-results.ts
@@ -17,7 +17,7 @@ export class PlottablePabResults implements PlottableData {
         }
         // axes par défaut
         this.chartX = this.chartX || "CLOISON";
-        this.chartY = this.chartY || "Z";
+        this.chartY = this.chartY || "YMOY";
     }
 
     /** reaffect pabResults, for ex. when objet was contructed with empty pabResults */
-- 
GitLab