From 7c7920baa01f7cf7a55cc68c7b429df2c807f9d1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr>
Date: Thu, 29 Sep 2022 15:11:02 +0200
Subject: [PATCH] feat: parameterised sections: add check button to apply 1:1
 aspect ratio to graph

refs #497
---
 .../section-canvas.component.html             |   9 +-
 .../section-canvas.component.ts               | 250 ++++++++++++------
 src/locale/messages.en.json                   |   1 +
 src/locale/messages.fr.json                   |   1 +
 4 files changed, 185 insertions(+), 76 deletions(-)

diff --git a/src/app/components/section-canvas/section-canvas.component.html b/src/app/components/section-canvas/section-canvas.component.html
index da242b966..fc687f76c 100644
--- a/src/app/components/section-canvas/section-canvas.component.html
+++ b/src/app/components/section-canvas/section-canvas.component.html
@@ -1,2 +1,7 @@
-<canvas #canvas [attr.width]="width" [attr.height]="height">
-</canvas>
+<div fxLayout="column" fxLayoutAlign="center center">
+    <canvas #canvas [attr.width]="width" [attr.height]="height">
+    </canvas>
+    <mat-checkbox [ngModel]="useRealAspectRatio" (ngModelChange)="setUseRealAspectRatio($event)">
+        {{ uitextUseRealRatio }}
+    </mat-checkbox>
+</div>
\ No newline at end of file
diff --git a/src/app/components/section-canvas/section-canvas.component.ts b/src/app/components/section-canvas/section-canvas.component.ts
index 41364eac3..be18032fa 100644
--- a/src/app/components/section-canvas/section-canvas.component.ts
+++ b/src/app/components/section-canvas/section-canvas.component.ts
@@ -1,4 +1,5 @@
 import { Component, ViewChild, Input, OnChanges, AfterViewInit, ElementRef } from "@angular/core";
+import { I18nService } from "app/services/internationalisation.service";
 
 import {
     acSection, cSnTrapez, ParamsSectionTrapez, cSnRectang, ParamsSectionRectang, cSnCirc,
@@ -40,19 +41,44 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
 
     private _result: Result;
 
+    /**
+     * paramètre de taille du canvas en entrée (pixels)
+     */
     private _size: number;
 
+    /**
+     * taille horizontale résultante, suivant le flag de respect de l'échelle (pixels)
+     */
+    private _sizeX: number;
+
+    /**
+     * taille verticale résultante, suivant le flag de respect de l'échelle (pixels)
+     */
+    private _sizeY: number;
+
+    /**
+     * taille horizontale de la section (m)
+     */
+    private _sectionWidth: number;
+
+    /**
+     * taille verticale de la section (m)
+     */
+    private _sectionHeight: number;
+
     // tirants
     private _levels: Object[] = [];
 
+    constructor(private intlService: I18nService) {
+        super();
+    }
+
     public get width(): number {
-        // return this._calcCanvas.nativeElement.width;
-        return this._size;
+        return this._sizeX;
     }
 
     public get height(): number {
-        // return this._calcCanvas.nativeElement.height;
-        return this._size;
+        return this._sizeY;
     }
 
     private _context2d: CanvasRenderingContext2D;
@@ -63,6 +89,7 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
     @Input()
     public set section(s: acSection) {
         this._section = s;
+        this.computeSectionWidth();
     }
 
     @Input()
@@ -75,6 +102,9 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
         this._size = s;
     }
 
+    // respect du rapport abscisses/ordonnées
+    public useRealAspectRatio: boolean = false;
+
     // redessine le canvas chaque fois qu'une entrée change
     public ngOnChanges() {
         setTimeout(() => {
@@ -92,7 +122,30 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
         this.clear();
     }
 
-    public addLevel(val: number, label: string, rgb: {}) {
+    /**
+     * calcul des tailles horizontale et verticale du canvas (pixels)
+     */
+    private updateCanvasSize() {
+        if (this.useRealAspectRatio) {
+            if (this._sectionWidth > this._sectionHeight) {
+                this._sizeX = this._size;
+                this._sizeY = this._size * this._sectionHeight / this._sectionWidth;
+            }
+            else {
+                this._sizeX = this._size * this._sectionHeight / this._sectionWidth;
+                this._sizeY = this._size;
+            }
+        } else {
+            this._sizeX = this._sizeY = this._size;
+        }
+    }
+
+    public setUseRealAspectRatio(b: boolean) {
+        this.useRealAspectRatio = b;
+        this.draw();
+    }
+
+    private addLevel(val: number, label: string, rgb: {}) {
         this._levels.push({ val, label, rgb });
     }
 
@@ -111,6 +164,26 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
         return false;
     }
 
+    /**
+     * calcul des tailles horizontale et verticale de la section (m)
+     */
+    private computeSectionWidth() {
+        if (this._section instanceof cSnTrapez) {
+            this.computeSectionWidthTrapez();
+        }
+        else if (this._section instanceof cSnRectang) {
+            this.computeSectionWidthRect();
+        }
+        else if (this._section instanceof cSnCirc) {
+            this.computeSectionWidthCirc();
+        }
+        else if (this._section instanceof cSnPuiss) {
+            this.computeSectionWidthPara();
+        }
+        else
+            throw new Error("SectionCanvasComponent.computeSectionWidth() : type de section non pris en charge");
+    }
+
     public draw() {
         // console.log(">> redrawing at size", this._size);
         if (this._context2d && this._section) {
@@ -136,10 +209,13 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
             const valY = this._result.sourceNub.getParameter("Y").singleValue;
             this.addLevel(valY, "Y = " + this.formattedValue(valY), SectionCanvasComponent.labelColors["Y"]);
 
-            this.sortLevels();
-            this.drawFrame();
-            const maxWidth = this.drawSection();
-            this.drawLevels(maxWidth);
+            this.computeSectionHeight();
+            this.updateCanvasSize();
+            setTimeout(() => { // à cause du changement de taille du canvas dans updateCanvasSize()
+                this.drawFrame();
+                this.drawSection();
+                this.drawLevels();
+            }, 10);
         }
     }
 
@@ -157,24 +233,35 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
         this.drawSectionLine(xmax, yb, xmax, maxHeight);
     }
 
-    private drawSectionTrapez(): number {
+    private computeSectionHeight() {
+        this.sortLevels();
+
+        // hauteur totale de la section
+        this._sectionHeight = this.getMaxLevel() * 1.1;
+    }
+
+    private computeSectionWidthTrapez() {
         const sect: cSnTrapez = <cSnTrapez>this._section;
         const prms: ParamsSectionTrapez = <ParamsSectionTrapez>sect.prms;
 
-        // cote de berge
-        const yb: number = prms.YB.v;
-
         // largeur de la partie pentue
         const lp: number = prms.Fruit.v * prms.YB.v;
 
         // largeur totale de la section
-        const maxWidth: number = lp * 2 + prms.LargeurFond.v;
+        this._sectionWidth = lp * 2 + prms.LargeurFond.v;
+    }
 
-        // hauteur totale de la section
-        let maxHeight: number = this.getMaxLevel();
-        maxHeight *= 1.1;
+    private drawSectionTrapez() {
+        const sect: cSnTrapez = <cSnTrapez>this._section;
+        const prms: ParamsSectionTrapez = <ParamsSectionTrapez>sect.prms;
 
-        this.computeScale(maxWidth, maxHeight);
+        // cote de berge
+        const yb: number = prms.YB.v;
+
+        // largeur de la partie pentue
+        const lp: number = prms.Fruit.v * prms.YB.v;
+
+        this.computeScale();
 
         // dessin de la section
 
@@ -182,13 +269,19 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
         this.setLineWidth(5);
         this.drawSectionLine(0, yb, lp, 0);
         this.drawSectionLine(lp, 0, lp + prms.LargeurFond.v, 0);
-        this.drawSectionLine(lp + prms.LargeurFond.v, 0, maxWidth, yb);
+        this.drawSectionLine(lp + prms.LargeurFond.v, 0, this._sectionWidth, yb);
 
         // pointillés du haut
 
-        this.drawTopDashLines(0, maxWidth, yb, maxHeight);
+        this.drawTopDashLines(0, this._sectionWidth, yb, this._sectionHeight);
+    }
+
+    private computeSectionWidthRect() {
+        const sect: cSnRectang = <cSnRectang>this._section;
+        const prms: ParamsSectionRectang = <ParamsSectionRectang>sect.prms;
 
-        return maxWidth;
+        // largeur totale de la section
+        this._sectionWidth = prms.LargeurBerge.v;
     }
 
     private drawSectionRect() {
@@ -198,14 +291,7 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
         // cote de berge
         const yb: number = prms.YB.v;
 
-        // largeur totale de la section
-        const maxWidth: number = prms.LargeurBerge.v;
-
-        // hauteur totale de la section
-        let maxHeight: number = this.getMaxLevel();
-        maxHeight *= 1.1;
-
-        this.computeScale(maxWidth, maxHeight);
+        this.computeScale();
 
         // dessin de la section
 
@@ -213,13 +299,32 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
         this.setLineWidth(5);
         this.drawSectionLine(0, yb, 0, 0);
         this.drawSectionLine(0, 0, prms.LargeurBerge.v, 0);
-        this.drawSectionLine(prms.LargeurBerge.v, 0, maxWidth, yb);
+        this.drawSectionLine(prms.LargeurBerge.v, 0, this._sectionWidth, yb);
 
         // pointillés du haut
 
-        this.drawTopDashLines(0, maxWidth, yb, maxHeight);
+        this.drawTopDashLines(0, this._sectionWidth, yb, this._sectionHeight);
+    }
+
+    private computeSectionWidthCirc() {
+        const sect: cSnCirc = <cSnCirc>this._section;
+        const prms: ParamsSectionCirc = <ParamsSectionCirc>sect.prms;
+
+        // cote de berge
+        const yb: number = prms.YB.v;
+
+        // diamètre, rayon
+        const D: number = prms.D.v;
+        const r: number = D / 2;
+
+        // largeur au miroir
+        const B: Result = sect.CalcSection("B", yb);
+        if (!B.ok) {
+            throw B;
+        }
 
-        return maxWidth;
+        // largeur totale de la section
+        this._sectionWidth = yb < r ? B.vCalc : D;
     }
 
     private drawSectionCirc() {
@@ -240,21 +345,14 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
                 throw B;
             }
 
-            // largeur totale de la section
-            const maxWidth: number = yb < r ? B.vCalc : D;
-
-            // hauteur totale de la section
-            let maxHeight: number = this.getMaxLevel();
-            maxHeight *= 1.1;
-
-            this.computeScale(maxWidth, maxHeight);
+            this.computeScale();
 
             // dessin de la section
 
             this.setStrokeColor(0, 0, 0);
             this.setLineWidth(5);
 
-            const wx: number = maxWidth / 2;
+            const wx: number = this._sectionWidth / 2;
             const alpha: Result = sect.CalcSection("Alpha", yb);
             if (!alpha.ok) {
                 throw alpha;
@@ -267,15 +365,24 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
             // pointillés du haut
 
             const w: number = yb > r ? (D - B.vCalc) / 2 : 0;
-            this.drawTopDashLines(w, maxWidth - w, yb, maxHeight);
-
-            return maxWidth;
+            this.drawTopDashLines(w, this._sectionWidth - w, yb, this._sectionHeight);
         } catch (e) {
             const res: Result = e as Result;
             this.drawText("error : " + res.log.toString(), 0, 0);
         }
     }
 
+    private computeSectionWidthPara() {
+        const sect: cSnPuiss = <cSnPuiss>this._section;
+        const prms: ParamsSectionPuiss = <ParamsSectionPuiss>sect.prms;
+
+        // largeur au miroir
+        const B: number = prms.LargeurBerge.v;
+
+        // largeur totale de la section
+        this._sectionWidth = B;
+    }
+
     private drawSectionPara() {
         const sect: cSnPuiss = <cSnPuiss>this._section;
         const prms: ParamsSectionPuiss = <ParamsSectionPuiss>sect.prms;
@@ -286,14 +393,7 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
         // largeur au miroir
         const B: number = prms.LargeurBerge.v;
 
-        // largeur totale de la section
-        const maxWidth: number = B;
-
-        // hauteur totale de la section
-        let maxHeight: number = this.getMaxLevel();
-        maxHeight *= 1.1;
-
-        this.computeScale(maxWidth, maxHeight);
+        this.computeScale();
 
         // contour de la section
 
@@ -312,9 +412,7 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
         this._context2d.stroke();
 
         // pointillés du haut
-        this.drawTopDashLines(0, maxWidth, yb, maxHeight);
-
-        return maxWidth;
+        this.drawTopDashLines(0, this._sectionWidth, yb, this._sectionHeight);
     }
 
     private drawSectionEllipse(x: number, y: number, rX: number, rY: number, rot: number, start: number, end: number) {
@@ -333,25 +431,26 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
      * dessin de la section
      * @returns largeur de la section (en m)
      */
-    private drawSection(): number {
+    private drawSection() {
         if (this._section instanceof cSnTrapez) {
-            return this.drawSectionTrapez();
+            this.drawSectionTrapez();
         }
-        if (this._section instanceof cSnRectang) {
-            return this.drawSectionRect();
+        else if (this._section instanceof cSnRectang) {
+            this.drawSectionRect();
         }
-        if (this._section instanceof cSnCirc) {
-            return this.drawSectionCirc();
+        else if (this._section instanceof cSnCirc) {
+            this.drawSectionCirc();
         }
-        if (this._section instanceof cSnPuiss) {
-            return this.drawSectionPara();
+        else if (this._section instanceof cSnPuiss) {
+            this.drawSectionPara();
         }
-        throw new Error("SectionCanvasComponent.drawSection() : type de section non pris en charge");
+        else
+            throw new Error("SectionCanvasComponent.drawSection() : type de section non pris en charge");
     }
 
-    private computeScale(maxWidth: number, maxHeight: number) {
-        this._scaleX = (this._size - 2 * this._textMargin) / maxWidth;
-        this._scaleY = (this._size - this._bottomMargin) / maxHeight;
+    private computeScale() {
+        this._scaleX = (this._sizeX - 2 * this._textMargin) / this._sectionWidth;
+        this._scaleY = (this._sizeY - this._bottomMargin) / this._sectionHeight;
     }
 
     /**
@@ -365,7 +464,7 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
      * convertit une ordonnée en m en pixels
      */
     private Ym2pix(y: number) {
-        return this._size - this._bottomMargin - y * this._scaleY;
+        return this._sizeY - this._bottomMargin - y * this._scaleY;
     }
 
     private sortLevels() {
@@ -380,7 +479,7 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
         });
     }
 
-    private drawLevels(maxWidth: number) {
+    private drawLevels() {
         let left = true;
 
         this.resetLineDash();
@@ -390,13 +489,13 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
             const y = l["val"];
             const col = l["rgb"];
             this.setStrokeColor(col["r"], col["g"], col["b"]);
-            this.drawSectionLine(0, y, maxWidth, y);
+            this.drawSectionLine(0, y, this._sectionWidth, y);
 
             this.setFillColor(col["r"], col["g"], col["b"]);
             if (left) {
                 this.drawText(l["label"], -0.1, y, "right");
             } else {
-                this.drawText(l["label"], maxWidth + 0.1, y, "left");
+                this.drawText(l["label"], this._sectionWidth + 0.1, y, "left");
             }
             left = !left;
         }
@@ -404,15 +503,14 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
 
     // contour du canvas
     private drawFrame() {
-        this.clear();
         this.resetLineDash();
         this.setStrokeColor(128, 128, 128);
-        this.drawRect(0, 0, this.width, this.height);
+        this.drawRect(0, 0, this._sizeX, this._sizeY);
     }
 
     public clear() {
         if (this._context2d) {
-            this._context2d.clearRect(0, 0, this.width, this.height);
+            this._context2d.clearRect(0, 0, this._sizeX, this._sizeY);
         }
     }
 
@@ -475,4 +573,8 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements
         this._context2d.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle);
         this._context2d.stroke();
     }
+
+    public get uitextUseRealRatio(): string {
+        return this.intlService.localizeText("INFO_SECTIONPARAMETREE_REAL_RATIO");
+    }
 }
diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json
index 56a2f105b..80c1a982c 100755
--- a/src/locale/messages.en.json
+++ b/src/locale/messages.en.json
@@ -584,6 +584,7 @@
     "INFO_SECTIONPARAMETREE_DESCRIPTION": "open-channel canal rectangular circular trapezoidal depth head normal critical conjugate corresponding subcritical supercritical Froude",
     "INFO_SECTIONPARAMETREE_TITRE_COURT": "Param. section",
     "INFO_SECTIONPARAMETREE_TITRE": "Parametric section",
+    "INFO_SECTIONPARAMETREE_REAL_RATIO": "Apply 1:1 scale",
     "INFO_SELECT_MULTIPLE_AND_OTHER": "other",
     "INFO_SELECT_MULTIPLE_AND_OTHERS": "others",
     "INFO_SETUP_ENABLE_HOTKEYS": "Enable keyboard shortcuts",
diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json
index 225b0751e..77a92907f 100755
--- a/src/locale/messages.fr.json
+++ b/src/locale/messages.fr.json
@@ -585,6 +585,7 @@
     "INFO_SECTIONPARAMETREE_DESCRIPTION": "hydraulique à surface libre canal chenal bief rectangulaire circulaire puissance trapézoïdale périmètre charge mouillée rugosité hauteur charge critique normal conjuguée correspondante fluvial torrentiel Froude",
     "INFO_SECTIONPARAMETREE_TITRE_COURT": "Sec. param.",
     "INFO_SECTIONPARAMETREE_TITRE": "Section paramétrée",
+    "INFO_SECTIONPARAMETREE_REAL_RATIO": "Respecter l'échelle",
     "INFO_SELECT_MULTIPLE_AND_OTHER": "autre",
     "INFO_SELECT_MULTIPLE_AND_OTHERS": "autres",
     "INFO_SETUP_ENABLE_HOTKEYS": "Activer les raccourcis clavier",
-- 
GitLab