-
mathias.chouet authoredmathias.chouet authored
modules-diagram.component.ts 7.80 KiB
import {
Component,
ViewChild,
AfterContentInit,
OnInit,
AfterViewChecked,
AfterViewInit
} from "@angular/core";
import { Router } from "@angular/router";
import {
Session,
ParamValueMode,
CalculatorType,
SectionType,
LoiDebit,
Nub,
MacrorugoCompound,
Pab,
Solveur
} from "jalhyd";
import { I18nService } from "../../services/internationalisation.service";
import { FormulaireService } from "../../services/formulaire.service";
import * as mermaid from "mermaid";
import * as SvgPanZoom from "svg-pan-zoom";
import { MatomoTracker } from "ngx-matomo";
import { fv } from "../../util";
@Component({
selector: "modules-diagram",
templateUrl: "./modules-diagram.component.html",
styleUrls: ["./modules-diagram.component.scss"]
})
export class ModulesDiagramComponent implements AfterContentInit, AfterViewChecked, AfterViewInit, OnInit {
private svgPanZoom: SvgPanZoom.Instance = null;
private needsToInitSvgPanZoom = false;
/** handle on SVG container */
private nativeElement: any;
@ViewChild("diagram", { static: true })
public diagram: any;
public error: boolean;
public showDebug = false;
constructor(
private intlService: I18nService,
private router: Router,
private formulaireService: FormulaireService,
private matomoTracker: MatomoTracker
) {
this.error = false;
this.matomoTracker.trackPageView("diagram");
}
public get uitextTitle(): string {
return this.intlService.localizeText("INFO_DIAGRAM_TITLE");
}
public get uitextDrawingError(): string {
return this.intlService.localizeText("INFO_DIAGRAM_DRAWING_ERROR");
}
public get uitextCalculatedParam(): string {
return this.intlService.localizeText("INFO_DIAGRAM_CALCULATED_PARAM");
}
public ngAfterViewChecked() {
if (this.needsToInitSvgPanZoom) {
this.initSvgPanZoom();
}
}
public initSvgPanZoom() {
if (this.svgPanZoom) {
this.svgPanZoom.destroy();
}
this.svgPanZoom = SvgPanZoom("#graphDiv", {
minZoom: 1,
maxZoom: 10,
zoomScaleSensitivity: 0.8,
contain: true
});
this.needsToInitSvgPanZoom = false;
}
public ngOnInit() {
// if app is started on this page but session is empty, redirect to home page
if (! this.hasModules) {
this.router.navigate([ "/list" ]);
}
}
public ngAfterViewInit(): void {
// add click listener on every calculator node in the graph, that
// corresponds to an open module
this.nativeElement.querySelectorAll("g.node").forEach(item => {
if (item.id && this.formIsOpen(item.id)) {
item.style.cursor = "pointer";
item.addEventListener("click", () => {
this.openCalc(item.id);
});
}
});
}
public ngAfterContentInit(): void {
this.error = false;
mermaid.initialize({
// theme: "forest"
flowchart: {
curve: "basis"
}
});
this.nativeElement = this.diagram.nativeElement;
if (this.hasModules) {
// generate graph description
const graphDefinition = this.graphDefinition();
// draw
try {
mermaid.render("graphDiv", graphDefinition, (svgCode, bindFunctions) => {
this.nativeElement.innerHTML = svgCode;
});
} catch (e) {
console.error(e);
this.error = true;
}
}
}
public resetZoom() {
if (this.svgPanZoom) {
this.svgPanZoom.resetZoom();
// this.svgPanZoom.contain();
// this.svgPanZoom.fit();
}
}
public get hasModules(): boolean {
return Session.getInstance().getNumberOfNubs() > 0;
}
/**
* Builds a Mermaid graph text definition
*/
private graphDefinition() {
const def: string[] = [ "graph TB" ];
const forms = this.formulaireService.formulaires;
for (const f of forms) {
// register Nub in diagram
const nub = f.currentNub;
const children = nub.getChildren();
if (children.length > 0) {
// subgraph for Nubs having children
def.push("subgraph \"" + f.calculatorName + "\"");
def.push(f.uid + "(\"" + f.calculatorName + "\")");
if (nub instanceof MacrorugoCompound || nub instanceof Pab) {
// PAB or MRC : gather all children in one node
def.push(f.uid + "_children" + "[\"" + this.describe(children[0]) + " (x" + children.length + ")\"]");
def.push(f.uid + " --- " + f.uid + "_children");
} else {
// other Nub with children: display all children
for (const c of children) {
def.push(c.uid + "[\"" + this.describe(c) + "\"]");
def.push(f.uid + " --- " + c.uid);
}
}
def.push("end");
} else {
// simple Nub (no children)
def.push(f.uid + "(\"" + f.calculatorName + "\")");
}
// find all linked parameters
for (const p of nub.parameterIterator) {
if (p.valueMode === ParamValueMode.LINK && p.isReferenceDefined()) {
const target = p.referencedValue.nub;
let symb = p.symbol;
const rv = p.referencedValue;
if (rv.isCalculated()) {
symb += "*";
} else {
if (rv.getParamValues().valueMode === ParamValueMode.SINGLE) {
symb += "=" + fv(rv.getValue());
}
}
def.push(target.uid + "-->|" + symb + "|" + nub.uid);
}
}
// add Solveur links
if (nub instanceof Solveur) {
const ntc = nub.nubToCalculate;
const sp = nub.searchedParameter;
const reads = this.intlService.localizeText("INFO_DIAGRAM_SOLVEUR_READS");
const finds = this.intlService.localizeText("INFO_DIAGRAM_SOLVEUR_FINDS");
if (ntc !== undefined) {
def.push(ntc.uid + "-->|" + reads + ":" + ntc.calculatedParam.symbol + "|" + nub.uid);
}
if (sp !== undefined) {
def.push(nub.uid + "-->|" + finds + ":" + sp.symbol + "|" + sp.parentNub.uid);
}
}
}
return def.join("\n");
}
public get graphDef(): string {
return this.graphDefinition();
}
public openCalc(uid: string) {
this.router.navigate(["/calculator", uid]);
}
/**
* Returns a very short "description" of the given Nub,
* based on the most specific of its properties
*/
private describe(n: Nub) {
let type = CalculatorType[n.calcType];
const nt = n.properties.getPropValue("nodeType");
if (nt) {
type = SectionType[nt];
} else {
const ld = n.properties.getPropValue("loiDebit");
if (ld !== undefined) {
type = LoiDebit[ld];
}
}
return type;
}
/**
* Returns true if uid is the id of the main Nub of any
* of the open modules
*/
private formIsOpen(uid: string) {
for (const f of this.formulaireService.formulaires) {
if (f.currentNub.uid === uid) {
return true;
}
}
return false;
}
}