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