diff --git a/angular.json b/angular.json index daa02deb9f77b04e2cb9ccf9839352466f7cdaf5..5482a7544c7eb2f297d410be6b225f995c3b1de9 100644 --- a/angular.json +++ b/angular.json @@ -32,7 +32,9 @@ "node_modules/primeng/resources/primeng.min.css", "node_modules/primeng/resources/themes/nova-light/theme.css" ], - "scripts": [], + "scripts": [ + "node_modules/chartjs-plugin-zoom/chartjs-plugin-zoom.min.js" + ], "showCircularDependencies": false }, "configurations": { diff --git a/package-lock.json b/package-lock.json index 72d12202a4b58cec9399e6f57b455428be2edaf3..23fc51a4600763f6611275c96aae80776545573a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3417,6 +3417,14 @@ "color-name": "^1.0.0" } }, + "chartjs-plugin-zoom": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/chartjs-plugin-zoom/-/chartjs-plugin-zoom-0.7.2.tgz", + "integrity": "sha512-5AtMCjlBlRsA/vxlvcBsAYKbkk0tVYE6+XX9M9LE6aSqbjqGR5NUQwYH0YvvvpmJjTZfB+HDhHaaGxJ/8aGaaw==", + "requires": { + "hammerjs": "^2.0.8" + } + }, "cheerio": { "version": "1.0.0-rc.2", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", diff --git a/package.json b/package.json index e39372066ebc30346e11099c74ee57aad18651c9..d3fda3c44d5c03a20718e9a468395292af30a4d4 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@types/pako": "^1.0.1", "@types/sprintf-js": "^1.1.2", "angular2-chartjs": "^0.5.1", + "chartjs-plugin-zoom": "^0.7.2", "cordova-android": "^8.0.0", "cordova-plugin-device": "^2.0.2", "core-js": "^2.6.5", 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 index bfbe7c551a39c3c855317577d5c39fd64040f610..28b4288fd2bbf48bcedac4e7a4ed9375a2f91cc1 100644 --- a/src/app/components/pab-profile-graph/pab-profile-graph.component.html +++ b/src/app/components/pab-profile-graph/pab-profile-graph.component.html @@ -1,13 +1,16 @@ <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)"> + <button mat-icon-button (click)="resetZoom()" [disabled]="! zoomWasChanged" [title]="uitextResetZoomTitle"> + <mat-icon color="primary">replay</mat-icon> + </button> + <button mat-icon-button (click)="exportAsImage(graphProfile)" [title]="uitextExportImageTitle"> <mat-icon color="primary">image</mat-icon> </button> - <button mat-icon-button *ngIf="! isFullscreen" (click)="setFullscreen(graphProfile)"> + <button mat-icon-button *ngIf="! isFullscreen" (click)="setFullscreen(graphProfile)" [title]="uitextEnterFSTitle"> <mat-icon color="primary" class="scaled12">fullscreen</mat-icon> </button> - <button mat-icon-button *ngIf="isFullscreen" (click)="exitFullscreen()"> + <button mat-icon-button *ngIf="isFullscreen" (click)="exitFullscreen()" [title]="uitextExitFSTitle"> <mat-icon color="primary" class="scaled12">fullscreen_exit</mat-icon> </button> </div> @@ -15,4 +18,4 @@ <chart type="scatter" [data]="graph_data" [options]="graph_options" #graphChart> </chart> </div> -</div> +</div> \ No newline at end of file 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 index 6382f8a709f582892c2411aa07c1701793d52dad..465866a6ce03ef7eaa710c1e4545563c44d2e9dd 100644 --- a/src/app/components/pab-profile-graph/pab-profile-graph.component.scss +++ b/src/app/components/pab-profile-graph/pab-profile-graph.component.scss @@ -19,5 +19,11 @@ transform: scale(1.2); } } + + &:disabled { + mat-icon { + color: #bfbfbf; + } + } } } 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 index 9c61cf60928e22db75be7fb878fe3b0c186e27dc..de364b5e3a6c04d9ec1772460f3d74580ced18cb 100644 --- a/src/app/components/pab-profile-graph/pab-profile-graph.component.ts +++ b/src/app/components/pab-profile-graph/pab-profile-graph.component.ts @@ -1,4 +1,6 @@ -import { Component } from "@angular/core"; +import { Component, ViewChild, ChangeDetectorRef } from "@angular/core"; + +import { ChartComponent } from "angular2-chartjs"; import { ApplicationSetupService } from "../../services/app-setup/app-setup.service"; import { I18nService } from "../../services/internationalisation/internationalisation.service"; @@ -14,6 +16,9 @@ import { PabResults } from "../../results/pab-results"; }) export class PabProfileGraphComponent extends ResultsComponent { + @ViewChild(ChartComponent) + private chartComponent; + private _results: PabResults; /** size of the longest variable value */ @@ -22,6 +27,8 @@ export class PabProfileGraphComponent extends ResultsComponent { /** inferred extended values list for each variating parameter */ private varValues = []; + private _zoomWasChanged = false; + /* * config du graphe */ @@ -50,7 +57,8 @@ export class PabProfileGraphComponent extends ResultsComponent { public constructor( private appSetupService: ApplicationSetupService, - private intlService: I18nService + private intlService: I18nService, + private cd: ChangeDetectorRef ) { super(); const nDigits = this.appSetupService.displayDigits; @@ -79,6 +87,24 @@ export class PabProfileGraphComponent extends ResultsComponent { } }] }; + // enable zoom and pan (using "chartjs-plugin-zoom" package) + const that = this; + this.graph_options["plugins"] = { + zoom: { + pan: { + enabled: false, // conflicts with drag zoom + mode: "xy", + }, + zoom: { + enabled: true, + drag: true, // conflicts with pan; set to false to enable mouse wheel zoom + mode: "xy", + // percentage of zoom on a wheel event + // speed: 0.1, + onZoomComplete: function(t: any) { return function() { t.zoomComplete(); }; }(that) + } + } + }; } public set results(r: PabResults) { @@ -109,6 +135,15 @@ export class PabProfileGraphComponent extends ResultsComponent { } } + private zoomComplete() { + this._zoomWasChanged = true; + this.cd.detectChanges(); + } + + public get zoomWasChanged(): boolean { + return this._zoomWasChanged; + } + public updateView() { this.generateScatterGraph(); } @@ -134,33 +169,6 @@ export class PabProfileGraphComponent extends ResultsComponent { 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) { @@ -170,6 +178,27 @@ export class PabProfileGraphComponent extends ResultsComponent { }); // defaults to image/png } + public resetZoom() { + this.chartComponent.chart.resetZoom(); + this._zoomWasChanged = false; + } + + public get uitextResetZoomTitle() { + return this.intlService.localizeText("INFO_GRAPH_BUTTON_TITLE_RESET_ZOOM"); + } + + public get uitextExportImageTitle() { + return this.intlService.localizeText("INFO_GRAPH_BUTTON_TITLE_EXPORT_IMAGE"); + } + + public get uitextEnterFSTitle() { + return this.intlService.localizeText("INFO_GRAPH_BUTTON_TITLE_ENTER_FS"); + } + + public get uitextExitFSTitle() { + return this.intlService.localizeText("INFO_GRAPH_BUTTON_TITLE_EXIT_FS"); + } + private getXSeries(): string[] { const data: string[] = []; const nDigits = this.appSetupService.displayDigits; diff --git a/src/app/components/results-graph/results-graph.component.html b/src/app/components/results-graph/results-graph.component.html index c30eebd7b950b4b1df94e68a27a8b31ed4c7ae61..1c15bdcc25844e4fcb2681d0ed6559a9757eb38d 100644 --- a/src/app/components/results-graph/results-graph.component.html +++ b/src/app/components/results-graph/results-graph.component.html @@ -1,13 +1,16 @@ <div class="graph-results-container" #graphResults fxLayout="row wrap" fxLayoutAlign="center center"> <div fxFlex="1 1 100%"> <div class="graph-results-buttons"> - <button mat-icon-button (click)="exportAsImage(graphResults)"> + <button mat-icon-button (click)="resetZoom()" [disabled]="! zoomWasChanged" [title]="uitextResetZoomTitle"> + <mat-icon color="primary">replay</mat-icon> + </button> + <button mat-icon-button (click)="exportAsImage(graphResults)" [title]="uitextExportImageTitle"> <mat-icon color="primary">image</mat-icon> </button> - <button mat-icon-button *ngIf="! isFullscreen" (click)="setFullscreen(graphResults)"> + <button mat-icon-button *ngIf="! isFullscreen" (click)="setFullscreen(graphResults)" [title]="uitextEnterFSTitle"> <mat-icon color="primary" class="scaled12">fullscreen</mat-icon> </button> - <button mat-icon-button *ngIf="isFullscreen" (click)="exitFullscreen()"> + <button mat-icon-button *ngIf="isFullscreen" (click)="exitFullscreen()" [title]="uitextExitFSTitle"> <mat-icon color="primary" class="scaled12">fullscreen_exit</mat-icon> </button> </div> diff --git a/src/app/components/results-graph/results-graph.component.scss b/src/app/components/results-graph/results-graph.component.scss index c50b430fde0e544b56c71b98f899ce694674414b..4eab50fee6ca30b52c952b3aed76d350a987e95f 100644 --- a/src/app/components/results-graph/results-graph.component.scss +++ b/src/app/components/results-graph/results-graph.component.scss @@ -19,6 +19,12 @@ transform: scale(1.2); } } + + &:disabled { + mat-icon { + color: #bfbfbf; + } + } } } diff --git a/src/app/components/results-graph/results-graph.component.ts b/src/app/components/results-graph/results-graph.component.ts index 7e775c89a5dd14cd0f761a21cb7ea25b6326f6d5..abda1f85a7f479a5189ca28114d2c81b37d21c02 100644 --- a/src/app/components/results-graph/results-graph.component.ts +++ b/src/app/components/results-graph/results-graph.component.ts @@ -1,4 +1,6 @@ -import { Component, ViewChild, AfterContentInit, OnInit } from "@angular/core"; +import { Component, ViewChild, AfterContentInit, ChangeDetectorRef } from "@angular/core"; + +import { ChartComponent } from "angular2-chartjs"; import { Observer } from "jalhyd"; @@ -17,11 +19,17 @@ import { ResultsComponent } from "../fixedvar-results/results.component"; ] }) export class ResultsGraphComponent extends ResultsComponent implements AfterContentInit, Observer { + + @ViewChild(ChartComponent) + private chartComponent; + private _results: PlottableData; /** used to briefly destroy/rebuild the chart component, to refresh axis labels (@see bug #137) */ public displayChart = true; + private _zoomWasChanged = false; + @ViewChild(GraphTypeSelectComponent) private _graphTypeComponent: GraphTypeSelectComponent; @@ -52,9 +60,28 @@ export class ResultsGraphComponent extends ResultsComponent implements AfterCont public constructor( private appSetup: ApplicationSetupService, - private intlService: I18nService + private intlService: I18nService, + private cd: ChangeDetectorRef ) { super(); + // enable zoom and pan (using "chartjs-plugin-zoom" package) + const that = this; + this.graph_options["plugins"] = { + zoom: { + pan: { + enabled: false, // conflicts with drag zoom + mode: "xy", + }, + zoom: { + enabled: true, + drag: true, // conflicts with pan; set to false to enable mouse wheel zoom + mode: "xy", + // percentage of zoom on a wheel event + // speed: 0.1, + onZoomComplete: function(t: any) { return function() { t.zoomComplete(); }; }(that) + } + } + }; } public set results(r: PlottableData) { @@ -116,6 +143,15 @@ export class ResultsGraphComponent extends ResultsComponent implements AfterCont return this.intlService.localizeText("INFO_PARAMFIELD_GRAPH_SELECT_Y_AXIS"); } + private zoomComplete() { + this._zoomWasChanged = true; + this.cd.detectChanges(); + } + + public get zoomWasChanged(): boolean { + return this._zoomWasChanged; + } + public updateView() { // (re)generate chart switch (this._graphTypeComponent.selectedValue) { @@ -316,6 +352,27 @@ export class ResultsGraphComponent extends ResultsComponent implements AfterCont }); // defaults to image/png } + public resetZoom() { + this.chartComponent.chart.resetZoom(); + this._zoomWasChanged = false; + } + + public get uitextResetZoomTitle() { + return this.intlService.localizeText("INFO_GRAPH_BUTTON_TITLE_RESET_ZOOM"); + } + + public get uitextExportImageTitle() { + return this.intlService.localizeText("INFO_GRAPH_BUTTON_TITLE_EXPORT_IMAGE"); + } + + public get uitextEnterFSTitle() { + return this.intlService.localizeText("INFO_GRAPH_BUTTON_TITLE_ENTER_FS"); + } + + public get uitextExitFSTitle() { + return this.intlService.localizeText("INFO_GRAPH_BUTTON_TITLE_EXIT_FS"); + } + // interface Observer update(sender: any, data: any) { diff --git a/src/app/formulaire/definition/concrete/form-pab.ts b/src/app/formulaire/definition/concrete/form-pab.ts index 898f542172bfdf2af0755e627ff7799dd5b2b7b0..aa5bac6865d805850182ac1912022910e9bc3568 100644 --- a/src/app/formulaire/definition/concrete/form-pab.ts +++ b/src/app/formulaire/definition/concrete/form-pab.ts @@ -23,10 +23,10 @@ export class FormulairePab extends FormulaireBase { return this.currentNub as Pab; } - public doCompute() { + /* public doCompute() { this.dumpPabStructure(this.pabNub); super.doCompute(); - } + } */ // debug method private dumpPabStructure(pab: Pab) { diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json index 5e07c524b0066afc7574eb4724a182194b4b031c..1fce8bfcdaf8e786cedf0587d7ed2a18410e0617 100644 --- a/src/locale/messages.en.json +++ b/src/locale/messages.en.json @@ -101,6 +101,10 @@ "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_1": "Partially submerged", "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_2": "Submerged", "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_3": "Zero flow", + "INFO_GRAPH_BUTTON_TITLE_RESET_ZOOM": "Restore default zoom", + "INFO_GRAPH_BUTTON_TITLE_EXPORT_IMAGE": "Save picture", + "INFO_GRAPH_BUTTON_TITLE_ENTER_FS": "Display fullscreen", + "INFO_GRAPH_BUTTON_TITLE_EXIT_FS": "Exis fullscreen mode", "INFO_DEVICE_ADDED": "1 device added", "INFO_DEVICE_ADDED_N_TIMES": "%s devices added", "INFO_DEVICE_COPIED": "Device #%s copied", diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json index 8b97db8956cb20751bdbfa803453555729462bce..1c4ace04cc51b24cf453797047f00db14128cc2e 100644 --- a/src/locale/messages.fr.json +++ b/src/locale/messages.fr.json @@ -101,6 +101,10 @@ "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_1": "Partiellement noyé", "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_2": "Noyé", "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_3": "Débit nul", + "INFO_GRAPH_BUTTON_TITLE_RESET_ZOOM": "Réinitialiser le zoom", + "INFO_GRAPH_BUTTON_TITLE_EXPORT_IMAGE": "Enregistrer l'image", + "INFO_GRAPH_BUTTON_TITLE_ENTER_FS": "Afficher en plein écran", + "INFO_GRAPH_BUTTON_TITLE_EXIT_FS": "Sortir du mode plein écran", "INFO_DEVICE_ADDED": "1 ouvrage ajouté", "INFO_DEVICE_ADDED_N_TIMES": "%s ouvrages ajoutés", "INFO_DEVICE_COPIED": "Ouvrage n°%s copié", diff --git a/src/main.ts b/src/main.ts index 9b8fff7b96bcd6f42218db26ef282901d5136450..018a6f867c32a3cbe4b9eee6682ce72c5b18614a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,6 +5,8 @@ import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; import { AppModule } from "./app/app.module"; import { environment } from "./environments/environment"; +import "chartjs-plugin-zoom"; + if (environment.production) { enableProdMode(); }