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 1/5] 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 From 8b5cfc6bd0066c1c0c22fbdb5ddc1ede92abdc4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr> Date: Wed, 5 Oct 2022 11:52:34 +0200 Subject: [PATCH 2/5] fix: parametric section graph: keep canvas the same size, fix text at the wrong place with narrow embankment refs #497 --- .../section-canvas.component.ts | 98 ++++++++++++------- 1 file changed, 61 insertions(+), 37 deletions(-) diff --git a/src/app/components/section-canvas/section-canvas.component.ts b/src/app/components/section-canvas/section-canvas.component.ts index be18032fa..965f6b7c7 100644 --- a/src/app/components/section-canvas/section-canvas.component.ts +++ b/src/app/components/section-canvas/section-canvas.component.ts @@ -27,11 +27,19 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements "Y": { r: 50, g: 50, b: 50 } }; + /* + * l'origine des coordonnées en m est par ex pour la section rectangulaire + * le coin en bas (radier) à gauche de la section + */ + /** marges gauche/droite pour le texte (pixels) */ - private _textMargin = 90; + private readonly _textMargin = 90; /** marge basse (pixels) */ - private _bottomMargin = 5; + private readonly _bottomMargin = 5; + + /** largeur des repères des niveaux sur le bord de la section (pixels) */ + private readonly _levelMark = 5; /** facteurs d'échelle (coordonnées en m <-> coordonnées en pixels) */ private _scaleX: number; @@ -47,14 +55,14 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements private _size: number; /** - * taille horizontale résultante, suivant le flag de respect de l'échelle (pixels) + * taille horizontale de rendu de la section (sans le texte) à l'intérieur du canvas suivant le flag de respect de l'échelle (pixels) */ - private _sizeX: number; + private _renderSectionX: number; /** - * taille verticale résultante, suivant le flag de respect de l'échelle (pixels) + * taille verticale de rendu de la section à l'intérieur du canvas suivant le flag de respect de l'échelle (pixels) */ - private _sizeY: number; + private _renderSectionY: number; /** * taille horizontale de la section (m) @@ -62,7 +70,7 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements private _sectionWidth: number; /** - * taille verticale de la section (m) + * taille verticale de la section en comptant les tirants (m) */ private _sectionHeight: number; @@ -74,11 +82,11 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements } public get width(): number { - return this._sizeX; + return this._size; } public get height(): number { - return this._sizeY; + return this._size; } private _context2d: CanvasRenderingContext2D; @@ -114,7 +122,6 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements public ngAfterViewInit() { // wait for the view to init before using the element this._context2d = this._calcCanvas.nativeElement.getContext("2d"); - this.draw(); } public reset() { @@ -123,20 +130,21 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements } /** - * calcul des tailles horizontale et verticale du canvas (pixels) + * calcul des tailles horizontale et verticale de la zone de rendu de la section à l'intérieur du canvas (pixels) */ - private updateCanvasSize() { + private computeRenderSize() { if (this.useRealAspectRatio) { if (this._sectionWidth > this._sectionHeight) { - this._sizeX = this._size; - this._sizeY = this._size * this._sectionHeight / this._sectionWidth; + this._renderSectionX = this._size - 2 * (this._textMargin + this._levelMark) + this._renderSectionY = (this._size - this._bottomMargin) * this._sectionHeight / this._sectionWidth; } else { - this._sizeX = this._size * this._sectionHeight / this._sectionWidth; - this._sizeY = this._size; + this._renderSectionX = this._size * this._sectionWidth / this._sectionHeight; + this._renderSectionY = this._size - this._bottomMargin;; } } else { - this._sizeX = this._sizeY = this._size; + this._renderSectionX = this._size - 2 * (this._textMargin + this._levelMark) + this._renderSectionY = this._size - this._bottomMargin; } } @@ -194,7 +202,7 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements if (re !== undefined) { for (const k in re.values) { if (k !== re.vCalcSymbol) { - const lbl = k.toUpperCase(); + //const lbl = k.toUpperCase(); const er = re.getValue(k); // this._resultElement.addExtraResult(lbl, er); @@ -210,7 +218,8 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements this.addLevel(valY, "Y = " + this.formattedValue(valY), SectionCanvasComponent.labelColors["Y"]); this.computeSectionHeight(); - this.updateCanvasSize(); + this.computeRenderSize(); + this.computeScale(); setTimeout(() => { // à cause du changement de taille du canvas dans updateCanvasSize() this.drawFrame(); this.drawSection(); @@ -261,8 +270,6 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements // largeur de la partie pentue const lp: number = prms.Fruit.v * prms.YB.v; - this.computeScale(); - // dessin de la section this.setStrokeColor(0, 0, 0); @@ -291,15 +298,14 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements // cote de berge const yb: number = prms.YB.v; - this.computeScale(); // dessin de la section this.setStrokeColor(0, 0, 0); this.setLineWidth(5); this.drawSectionLine(0, yb, 0, 0); - this.drawSectionLine(0, 0, prms.LargeurBerge.v, 0); - this.drawSectionLine(prms.LargeurBerge.v, 0, this._sectionWidth, yb); + this.drawSectionLine(0, 0, this._sectionWidth, 0); + this.drawSectionLine(this._sectionWidth, 0, this._sectionWidth, yb); // pointillés du haut @@ -345,8 +351,6 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements throw B; } - this.computeScale(); - // dessin de la section this.setStrokeColor(0, 0, 0); @@ -393,8 +397,6 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements // largeur au miroir const B: number = prms.LargeurBerge.v; - this.computeScale(); - // contour de la section this.setStrokeColor(0, 0, 0); @@ -419,8 +421,17 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements this.drawEllipse(this.Xm2pix(x), this.Ym2pix(y), rX * this._scaleX, rY * this._scaleY, rot, start, end); } + /** + * dessine un texte + * @param s texte à dessiner + * @param x position horizontale (m) + * @param y position verticale (m) + * @param align alignement "left" ou "right" cf. https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textAlign + */ private drawText(s: string, x: number, y: number, align?: string) { - this.fillText(s, this.Xm2pix(x), this.Ym2pix(y), align); + // décalage du texte par rapport au bord gauche/droit de la section + const shiftX = align === undefined ? 0 : (align === "left" ? this._levelMark : -this._levelMark); + this.fillText(s, this.Xm2pix(x) + shiftX, this.Ym2pix(y), align); } private drawSectionLine(x1: number, y1: number, x2: number, y2: number) { @@ -449,22 +460,28 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements } private computeScale() { - this._scaleX = (this._sizeX - 2 * this._textMargin) / this._sectionWidth; - this._scaleY = (this._sizeY - this._bottomMargin) / this._sectionHeight; + this._scaleX = this._renderSectionX / this._sectionWidth; + this._scaleY = this._renderSectionY / this._sectionHeight; } /** * convertit une abscisse en m en pixels */ private Xm2pix(x: number) { - return this._textMargin + x * this._scaleX; + // origine X de la section dans le canvas + const origX = (this._size - this._renderSectionX) / 2; + + return origX + x * this._scaleX; } /** * convertit une ordonnée en m en pixels */ private Ym2pix(y: number) { - return this._sizeY - this._bottomMargin - y * this._scaleY; + // origine Y de la section dans le canvas + const origY = (this._size - this._renderSectionY) / 2; + + return this._size - this._bottomMargin - origY - y * this._scaleY; } private sortLevels() { @@ -493,9 +510,9 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements this.setFillColor(col["r"], col["g"], col["b"]); if (left) { - this.drawText(l["label"], -0.1, y, "right"); + this.drawText(l["label"], 0, y, "right"); } else { - this.drawText(l["label"], this._sectionWidth + 0.1, y, "left"); + this.drawText(l["label"], this._sectionWidth, y, "left"); } left = !left; } @@ -505,12 +522,12 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements private drawFrame() { this.resetLineDash(); this.setStrokeColor(128, 128, 128); - this.drawRect(0, 0, this._sizeX, this._sizeY); + this.drawRect(0, 0, this._size, this._size); } public clear() { if (this._context2d) { - this._context2d.clearRect(0, 0, this._sizeX, this._sizeY); + this._context2d.clearRect(0, 0, this._size, this._size); } } @@ -528,6 +545,13 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements this._context2d.font = f; } + /** + * dessine un texte sur le canvas + * @param s texte à dessiner + * @param x position horizontale (pixels) + * @param y position verticale (pixels) + * @param align alignement "left" ou "right" cf. https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textAlign + */ public fillText(s: string, x: number, y: number, align?: any) { if (align) { this._context2d.textAlign = align; -- GitLab From 698e52e0aa80aa5b238db8de3dd98b3409b0f87d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr> Date: Thu, 6 Oct 2022 09:29:31 +0200 Subject: [PATCH 3/5] fix: parametric sections graph: circular section wrongly displayed in 1:1 mode refs #497 --- .../section-canvas.component.ts | 261 ++++++++++-------- 1 file changed, 149 insertions(+), 112 deletions(-) diff --git a/src/app/components/section-canvas/section-canvas.component.ts b/src/app/components/section-canvas/section-canvas.component.ts index 965f6b7c7..0c7a71635 100644 --- a/src/app/components/section-canvas/section-canvas.component.ts +++ b/src/app/components/section-canvas/section-canvas.component.ts @@ -28,16 +28,24 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements }; /* - * l'origine des coordonnées en m est par ex pour la section rectangulaire - * le coin en bas (radier) à gauche de la section + * notes : + * - l'origine des coordonnées en m est par ex pour la section rectangulaire le coin en bas (radier) à gauche de la section + * - canal : partie physique, sans les niveaux d'eau + * - section : canal + niveaux d'eau + * - rendu : section + texte + pointillés en haut + * - W : dimension horizontale, H : dimension verticale + * _ _m : valeur en mètres, _pix : valeur en pixels */ - /** marges gauche/droite pour le texte (pixels) */ - private readonly _textMargin = 90; + /** largeur gauche/droite du texte (pixels) */ + private readonly _textWidth = 110; /** marge basse (pixels) */ private readonly _bottomMargin = 5; + /** marge haute pour les pointillés (pixels) */ + private readonly _topDashMargin = 30; + /** largeur des repères des niveaux sur le bord de la section (pixels) */ private readonly _levelMark = 5; @@ -45,6 +53,10 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements private _scaleX: number; private _scaleY: number; + /** origine (pixels) */ + private _origX: number; + private _origY: number; + private _section: acSection; private _result: Result; @@ -55,24 +67,24 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements private _size: number; /** - * taille horizontale de rendu de la section (sans le texte) à l'intérieur du canvas suivant le flag de respect de l'échelle (pixels) + * taille horizontale de la section (m) */ - private _renderSectionX: number; + private _Wsect_m: number; /** - * taille verticale de rendu de la section à l'intérieur du canvas suivant le flag de respect de l'échelle (pixels) + * taille verticale de la section (m) */ - private _renderSectionY: number; + private _Hsect_m: number; /** - * taille horizontale de la section (m) + * taille horizontale de la section (pixels) */ - private _sectionWidth: number; + private _Wsect_pix: number; /** - * taille verticale de la section en comptant les tirants (m) + * taille verticale de la section (pixels) */ - private _sectionHeight: number; + private _Hsect_pix: number; // tirants private _levels: Object[] = []; @@ -97,7 +109,6 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements @Input() public set section(s: acSection) { this._section = s; - this.computeSectionWidth(); } @Input() @@ -111,10 +122,13 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements } // respect du rapport abscisses/ordonnées - public useRealAspectRatio: boolean = false; + private _useRealAspectRatio: boolean = false; // redessine le canvas chaque fois qu'une entrée change public ngOnChanges() { + this.computeSectionWidth(); + this.computeSectionHeight(); + this.computeTransform(); setTimeout(() => { this.draw(); }, 10); @@ -124,39 +138,121 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements this._context2d = this._calcCanvas.nativeElement.getContext("2d"); } - public reset() { - this._levels = []; - this.clear(); - } - /** - * calcul des tailles horizontale et verticale de la zone de rendu de la section à l'intérieur du canvas (pixels) + * calcul des tailles horizontale et verticale de la zone de rendu (pixels) + * @returns true si le rendu touche le haut et le bas du canvas */ - private computeRenderSize() { - if (this.useRealAspectRatio) { - if (this._sectionWidth > this._sectionHeight) { - this._renderSectionX = this._size - 2 * (this._textMargin + this._levelMark) - this._renderSectionY = (this._size - this._bottomMargin) * this._sectionHeight / this._sectionWidth; + private computeRenderSize(): boolean { + let res: boolean; + if (this._useRealAspectRatio) { + // largeur en pixels pour la représentation de la section + const a = this.width - 2 * (this._textWidth + this._levelMark); + + // calcul des rapports largeur/hauteur pour le canvas et la section + const rlim = a / this.height; + const rsect = this._Wsect_m / this._Hsect_m; + + if (rsect <= rlim) { + this._Hsect_pix = this.height - this._bottomMargin - this._topDashMargin; + this._Wsect_pix = this._Hsect_pix * rsect; + res = true; } else { - this._renderSectionX = this._size * this._sectionWidth / this._sectionHeight; - this._renderSectionY = this._size - this._bottomMargin;; + this._Wsect_pix = a; + this._Hsect_pix = this._Wsect_pix / rsect; + res = false; } + } else { - this._renderSectionX = this._size - 2 * (this._textMargin + this._levelMark) - this._renderSectionY = this._size - this._bottomMargin; + this._Wsect_pix = this.width - 2 * (this._textWidth + this._levelMark) + this._Hsect_pix = this.height - this._bottomMargin - this._topDashMargin; + res = true; } + + return res; + } + + /** + * calcul du coef pour passer de m -> pixels + * et de l'origine du rendu + */ + private computeTransform() { + const b = this.computeRenderSize(); + + // échelle m -> pix + this._scaleX = this._Wsect_pix / this._Wsect_m; + this._scaleY = this._Hsect_pix / this._Hsect_m; + // origine + this._origX = (this._size - this._Wsect_pix) / 2; + this._origY = b ? this._bottomMargin : (this._size - this._Hsect_pix) / 2; + } + + /** + * convertit une abscisse en m en pixels + */ + private Xm2pix(x: number) { + return this._origX + x * this._scaleX; + } + + /** + * convertit une ordonnée en m en pixels + */ + private Ym2pix(y: number) { + return this._size - this._origY - y * this._scaleY; + } + + public get useRealAspectRatio(): boolean { + return this._useRealAspectRatio; } public setUseRealAspectRatio(b: boolean) { - this.useRealAspectRatio = b; + this._useRealAspectRatio = b; + this.computeTransform(); this.draw(); } + private setLevels() { + this._levels = []; + + // traduction des symboles des variables calculées + const re = this._result.resultElement; + if (re !== undefined) { + for (const k in re.values) { + if (k !== re.vCalcSymbol) { + //const lbl = k.toUpperCase(); + const er = re.getValue(k); + // this._resultElement.addExtraResult(lbl, er); + + if (this.isSectionLevel(k)) { + this.addLevel(er, k + " = " + this.formattedValue(er), SectionCanvasComponent.labelColors[k]); + } + } + } + } + + // ajout du tirant d'eau saisi + const valY = this._result.sourceNub.getParameter("Y").singleValue; + this.addLevel(valY, "Y = " + this.formattedValue(valY), SectionCanvasComponent.labelColors["Y"]); + + this.sortLevels(); + } + private addLevel(val: number, label: string, rgb: {}) { this._levels.push({ val, label, rgb }); } + private sortLevels() { + this._levels.sort((a, b) => { + if (a["val"] < b["val"]) { + return -1; + } + if (a["val"] > b["val"]) { + return 1; + } + return 0; + }); + } + // max des niveaux à représenter private getMaxLevel(): number { // max de la cote de berge et des niveaux (qui sont triés) @@ -193,33 +289,8 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements } public draw() { - // console.log(">> redrawing at size", this._size); if (this._context2d && this._section) { - this.reset(); - - // traduction des symboles des variables calculées - const re = this._result.resultElement; - if (re !== undefined) { - for (const k in re.values) { - if (k !== re.vCalcSymbol) { - //const lbl = k.toUpperCase(); - const er = re.getValue(k); - // this._resultElement.addExtraResult(lbl, er); - - if (this.isSectionLevel(k)) { - this.addLevel(er, k + " = " + this.formattedValue(er), SectionCanvasComponent.labelColors[k]); - } - } - } - } - - // ajout du tirant d'eau saisi - const valY = this._result.sourceNub.getParameter("Y").singleValue; - this.addLevel(valY, "Y = " + this.formattedValue(valY), SectionCanvasComponent.labelColors["Y"]); - - this.computeSectionHeight(); - this.computeRenderSize(); - this.computeScale(); + this.clear(); setTimeout(() => { // à cause du changement de taille du canvas dans updateCanvasSize() this.drawFrame(); this.drawSection(); @@ -234,19 +305,22 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements * yb : cote de berge * maxHeight : valeur maxi des cotes */ - private drawTopDashLines(xmin: number, xmax: number, yb: number, maxHeight: number) { + private drawTopDashLines(xmin: number, xmax: number, yb: number) { this.setLineWidth(2); this.setLineDash([5]); this.setStrokeColor(128, 128, 128); - this.drawSectionLine(xmin, yb, xmin, maxHeight); - this.drawSectionLine(xmax, yb, xmax, maxHeight); + const x1 = this.Xm2pix(xmin); + const ybp = this.Ym2pix(yb); + this.drawLine(x1, ybp, x1, 0); + const x2 = this.Xm2pix(xmax); + this.drawLine(x2, ybp, x2, 0); } private computeSectionHeight() { - this.sortLevels(); + this.setLevels(); // hauteur totale de la section - this._sectionHeight = this.getMaxLevel() * 1.1; + this._Hsect_m = this.getMaxLevel(); } private computeSectionWidthTrapez() { @@ -257,7 +331,7 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements const lp: number = prms.Fruit.v * prms.YB.v; // largeur totale de la section - this._sectionWidth = lp * 2 + prms.LargeurFond.v; + this._Wsect_m = lp * 2 + prms.LargeurFond.v; } private drawSectionTrapez() { @@ -276,11 +350,11 @@ 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, this._sectionWidth, yb); + this.drawSectionLine(lp + prms.LargeurFond.v, 0, this._Wsect_m, yb); // pointillés du haut - this.drawTopDashLines(0, this._sectionWidth, yb, this._sectionHeight); + this.drawTopDashLines(0, this._Wsect_m, yb); } private computeSectionWidthRect() { @@ -288,7 +362,7 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements const prms: ParamsSectionRectang = <ParamsSectionRectang>sect.prms; // largeur totale de la section - this._sectionWidth = prms.LargeurBerge.v; + this._Wsect_m = prms.LargeurBerge.v; } private drawSectionRect() { @@ -304,12 +378,12 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements this.setStrokeColor(0, 0, 0); this.setLineWidth(5); this.drawSectionLine(0, yb, 0, 0); - this.drawSectionLine(0, 0, this._sectionWidth, 0); - this.drawSectionLine(this._sectionWidth, 0, this._sectionWidth, yb); + this.drawSectionLine(0, 0, this._Wsect_m, 0); + this.drawSectionLine(this._Wsect_m, 0, this._Wsect_m, yb); // pointillés du haut - this.drawTopDashLines(0, this._sectionWidth, yb, this._sectionHeight); + this.drawTopDashLines(0, this._Wsect_m, yb); } private computeSectionWidthCirc() { @@ -330,7 +404,7 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements } // largeur totale de la section - this._sectionWidth = yb < r ? B.vCalc : D; + this._Wsect_m = yb < r ? B.vCalc : D; } private drawSectionCirc() { @@ -356,7 +430,7 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements this.setStrokeColor(0, 0, 0); this.setLineWidth(5); - const wx: number = this._sectionWidth / 2; + const wx: number = this._Wsect_m / 2; const alpha: Result = sect.CalcSection("Alpha", yb); if (!alpha.ok) { throw alpha; @@ -369,7 +443,7 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements // pointillés du haut const w: number = yb > r ? (D - B.vCalc) / 2 : 0; - this.drawTopDashLines(w, this._sectionWidth - w, yb, this._sectionHeight); + this.drawTopDashLines(w, this._Wsect_m - w, yb); } catch (e) { const res: Result = e as Result; this.drawText("error : " + res.log.toString(), 0, 0); @@ -384,7 +458,7 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements const B: number = prms.LargeurBerge.v; // largeur totale de la section - this._sectionWidth = B; + this._Wsect_m = B; } private drawSectionPara() { @@ -414,7 +488,7 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements this._context2d.stroke(); // pointillés du haut - this.drawTopDashLines(0, this._sectionWidth, yb, this._sectionHeight); + this.drawTopDashLines(0, this._Wsect_m, yb); } private drawSectionEllipse(x: number, y: number, rX: number, rY: number, rot: number, start: number, end: number) { @@ -459,43 +533,6 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements throw new Error("SectionCanvasComponent.drawSection() : type de section non pris en charge"); } - private computeScale() { - this._scaleX = this._renderSectionX / this._sectionWidth; - this._scaleY = this._renderSectionY / this._sectionHeight; - } - - /** - * convertit une abscisse en m en pixels - */ - private Xm2pix(x: number) { - // origine X de la section dans le canvas - const origX = (this._size - this._renderSectionX) / 2; - - return origX + x * this._scaleX; - } - - /** - * convertit une ordonnée en m en pixels - */ - private Ym2pix(y: number) { - // origine Y de la section dans le canvas - const origY = (this._size - this._renderSectionY) / 2; - - return this._size - this._bottomMargin - origY - y * this._scaleY; - } - - private sortLevels() { - this._levels.sort((a, b) => { - if (a["val"] < b["val"]) { - return -1; - } - if (a["val"] > b["val"]) { - return 1; - } - return 0; - }); - } - private drawLevels() { let left = true; @@ -506,13 +543,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, this._sectionWidth, y); + this.drawSectionLine(0, y, this._Wsect_m, y); this.setFillColor(col["r"], col["g"], col["b"]); if (left) { this.drawText(l["label"], 0, y, "right"); } else { - this.drawText(l["label"], this._sectionWidth, y, "left"); + this.drawText(l["label"], this._Wsect_m, y, "left"); } left = !left; } -- GitLab From 02a019d0f0f0b633499a997e8e49dff799fc468f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr> Date: Thu, 6 Oct 2022 12:05:27 +0200 Subject: [PATCH 4/5] fix: parametric sections graph: overlapping levels text refs #497 --- .../section-canvas.component.ts | 67 ++++++++++++++++--- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/src/app/components/section-canvas/section-canvas.component.ts b/src/app/components/section-canvas/section-canvas.component.ts index 0c7a71635..cc5f23320 100644 --- a/src/app/components/section-canvas/section-canvas.component.ts +++ b/src/app/components/section-canvas/section-canvas.component.ts @@ -241,6 +241,9 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements this._levels.push({ val, label, rgb }); } + /** + * trie les tirants par niveau d'eau croissant + */ private sortLevels() { this._levels.sort((a, b) => { if (a["val"] < b["val"]) { @@ -533,26 +536,68 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements throw new Error("SectionCanvasComponent.drawSection() : type de section non pris en charge"); } - private drawLevels() { - let left = true; + /** + * dessin des niveaux en gérant le chevauchement + * @param levels liste des niveaux à tracer + * @param textHeight hauteur minimal entre le texte des niveaux (m) + * @param left true pour tracer le texte à gauche du niveau, false à droite + */ + private drawLevelsWithoutOverlap(levels: any, textHeight: number, left: boolean) { + for (let i = levels.length - 1; i >= 0; i--) { + const l = levels[i]; + // chevauchement avec le précédent ? + if (i < levels.length - 1) { + let yprec = levels[i + 1].y; + + const ycurr = l.y; + if (yprec - ycurr < textHeight) { + l.y = yprec - textHeight; + } + } + + // tracé du tirant courant + const col = l["rgb"]; + this.setStrokeColor(col["r"], col["g"], col["b"]); + this.drawSectionLine(0, l.val, this._Wsect_m, l.val); + this.setFillColor(col["r"], col["g"], col["b"]); + + if (left) { + this.drawText(l["label"], 0, l.y, "right"); + } else { + this.drawText(l["label"], this._Wsect_m, l.y, "left"); + } + } + } + + private drawLevels() { this.resetLineDash(); this.setLineWidth(1); - this.setFont("12px sans- serif"); + this.setFont("12px sans-serif"); + + // hauteur des caractères + const tm: TextMetrics = this._context2d.measureText("Ag"); + const charHeightPix = tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent; + const charHeightMeter = charHeightPix / this._scaleY; + + // sépare les niveaux de gauche/droite + const leftLevels = []; + const rightLevels = []; + let left = true; for (const l of this._levels) { const y = l["val"]; - const col = l["rgb"]; - this.setStrokeColor(col["r"], col["g"], col["b"]); - this.drawSectionLine(0, y, this._Wsect_m, y); - - this.setFillColor(col["r"], col["g"], col["b"]); + Object.assign(l, { "y": y }); // y = ordonnée de tracé if (left) { - this.drawText(l["label"], 0, y, "right"); + leftLevels.push(l); } else { - this.drawText(l["label"], this._Wsect_m, y, "left"); + rightLevels.push(l); } left = !left; } + + // dessin des textes + this.drawLevelsWithoutOverlap(leftLevels, charHeightMeter, true); + this.drawLevelsWithoutOverlap(rightLevels, charHeightMeter, false); } // contour du canvas @@ -578,7 +623,7 @@ export class SectionCanvasComponent extends ResultsComponentDirective implements this._context2d.fillStyle = col; } - public setFont(f: string) { + private setFont(f: string) { this._context2d.font = f; } -- GitLab From 16435bba52b5ba2c9c1424373e4fe9df5dd6a14e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr> Date: Thu, 6 Oct 2022 14:55:54 +0200 Subject: [PATCH 5/5] update jalhyd_branch to devel refs #497 --- jalhyd_branch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jalhyd_branch b/jalhyd_branch index 626e97d71..d64531f13 100644 --- a/jalhyd_branch +++ b/jalhyd_branch @@ -1 +1 @@ -devel \ No newline at end of file +devel -- GitLab