diff --git a/angular.json b/angular.json
index e87d2b0a1a6f02297dd35553c7d5cd37e50f7094..5482a7544c7eb2f297d410be6b225f995c3b1de9 100644
--- a/angular.json
+++ b/angular.json
@@ -28,9 +28,13 @@
               "src/styles.scss",
               "src/theme.scss",
               "node_modules/material-design-icons/iconfont/material-icons.css",
-              "node_modules/roboto-fontface/css/roboto/roboto-fontface.css"
+              "node_modules/roboto-fontface/css/roboto/roboto-fontface.css",
+              "node_modules/primeng/resources/primeng.min.css",
+              "node_modules/primeng/resources/themes/nova-light/theme.css"
+            ],
+            "scripts": [
+              "node_modules/chartjs-plugin-zoom/chartjs-plugin-zoom.min.js"
             ],
-            "scripts": [],
             "showCircularDependencies": false
           },
           "configurations": {
diff --git a/e2e/calculator.po.ts b/e2e/calculator.po.ts
index a3e578709bc86a9505ccb1158804f135d02faf5b..e94c27d19f31b79edbda1c4f62b61aaca39a9d98 100644
--- a/e2e/calculator.po.ts
+++ b/e2e/calculator.po.ts
@@ -91,9 +91,15 @@ export class CalculatorPage {
       await element(by.css("fixedvar-results fixed-results > .fixed-results-container")).isPresent()
       ||
       await element(by.css("fixedvar-results results-graph > graph-results-container")).isPresent()
+      ||
+      await element(by.css("pab-results pab-results-table")).isPresent()
     );
   }
 
+  async hasLog() {
+    return (await element.all(by.css("log-entry")).count()) > 0;
+  }
+
   async clickSaveCalcButton() {
     return await element(by.css("#save-calc")).click();
   }
diff --git a/e2e/clone-all-calc.e2e-spec.ts b/e2e/clone-all-calc.e2e-spec.ts
index a6e46288c8198291e0bd055a3e0b62f8d25da2ad..1cf0a8d470e2ec2e4a1de4c20190408c165ebbe5 100644
--- a/e2e/clone-all-calc.e2e-spec.ts
+++ b/e2e/clone-all-calc.e2e-spec.ts
@@ -48,7 +48,9 @@ describe("ngHyd − clone all calculators with all possible <select> values", ()
 
         // read all <input> values and compare them to stored ones
         const cloneValues = await calcPage.storeAllInputValues();
-        expect(cloneValues).toEqual(sourceValues);
+        for (const k in cloneValues) {
+          expect(cloneValues[k]).toBeCloseTo(sourceValues[k]);
+        }
       });
     });
   }
diff --git a/e2e/compute-reset-chained-links.e2e-spec.ts b/e2e/compute-reset-chained-links.e2e-spec.ts
index ac42745193b90d02a7cbf23e952b16403e02c597..0ec8e5dfc0b25143672755dcc2248306e3b20b11 100644
--- a/e2e/compute-reset-chained-links.e2e-spec.ts
+++ b/e2e/compute-reset-chained-links.e2e-spec.ts
@@ -32,7 +32,7 @@ describe("ngHyd − compute then reset chained results", () => {
     await browser.sleep(200);
     await sidenav.clickLoadSessionButton();
     await browser.sleep(200);
-    await sidenav.loadSessionFile("./session-cascade-params.json");
+    await sidenav.loadSessionFile("./session/session-cascade-params.json");
     await browser.sleep(500);
     expect(await navbar.getAllCalculatorTabs().count()).toBe(3);
 
@@ -78,7 +78,7 @@ describe("ngHyd − compute then reset chained results", () => {
     await browser.sleep(200);
     await sidenav.clickLoadSessionButton();
     await browser.sleep(200);
-    await sidenav.loadSessionFile("./session-cascade-results.json");
+    await sidenav.loadSessionFile("./session/session-cascade-results.json");
     await browser.sleep(500);
     expect(await navbar.getAllCalculatorTabs().count()).toBe(3);
 
diff --git a/e2e/load-linked-params.e2e-spec.ts b/e2e/load-linked-params.e2e-spec.ts
index 99f13e11f2f5b7ada8709420d001fd91fcc24b83..a26bb90c8f644c37d553185c6567a343798e088f 100644
--- a/e2e/load-linked-params.e2e-spec.ts
+++ b/e2e/load-linked-params.e2e-spec.ts
@@ -35,7 +35,7 @@ describe("ngHyd − load session with multiple linked parameters − ", () => {
     await sidenav.clickLoadSessionButton();
     await browser.sleep(200);
 
-    await sidenav.loadSessionFile("./session-liens-spaghetti.json");
+    await sidenav.loadSessionFile("./session/session-liens-spaghetti.json");
     await browser.sleep(500);
 
     expect(await navbar.getAllCalculatorTabs().count()).toBe(5);
diff --git a/e2e/load-save-session.e2e-spec.ts b/e2e/load-save-session.e2e-spec.ts
index d18d7732b85ceac4c52827e813df8d9f7a0dd631..4badbd4568284fea07dfffde76e7b70e91ce6968 100644
--- a/e2e/load-save-session.e2e-spec.ts
+++ b/e2e/load-save-session.e2e-spec.ts
@@ -32,7 +32,7 @@ describe("ngHyd − save and load sessions", () => {
     await sidenav.clickLoadSessionButton();
     await browser.sleep(200);
 
-    await sidenav.loadSessionFile("./session-6-calc.test.json");
+    await sidenav.loadSessionFile("./session/session-6-calc.test.json");
     await browser.sleep(200);
 
     expect(await navbar.getAllCalculatorTabs().count()).toBe(6);
@@ -47,7 +47,7 @@ describe("ngHyd − save and load sessions", () => {
     await sidenav.clickLoadSessionButton();
     await browser.sleep(200);
 
-    await sidenav.loadSessionFile("./session-optional-params.test.json");
+    await sidenav.loadSessionFile("./session/session-optional-params.test.json");
     await browser.sleep(200);
 
     expect(await navbar.getAllCalculatorTabs().count()).toBe(1);
diff --git a/e2e/pab.e2e-spec.ts b/e2e/pab.e2e-spec.ts
index 8d06db122cdd4a7acc2a0cfeecf3b063d81190a8..6d6ad1a5b21a6955f4e0d4a0157a998835e8a91d 100644
--- a/e2e/pab.e2e-spec.ts
+++ b/e2e/pab.e2e-spec.ts
@@ -25,49 +25,13 @@ describe("ngHyd − Passe à Bassins", () => {
 
   describe("create PAB - ", async () => {
 
-    it("when PAB is created after Cloisons & ParallelStructure", async() => {
-      // create Cloisons
-      await startPage.navigateTo();
-      await listPage.clickMenuEntryForCalcType(10);
-      // create ParallelStructure
-      await navbar.clickNewCalculatorButton();
-      await listPage.clickMenuEntryForCalcType(8);
-      // create PAB
-      await navbar.clickNewCalculatorButton();
-      await listPage.clickMenuEntryForCalcType(15);
-      // check number of basins
-      const innerFieldsets = element.all(by.css(".fieldset-inner"));
-      expect(await innerFieldsets.count()).toBe(1);
-      // check <select> value
-      const smc = calcPage.getSelectById("select_modele_cloisons");
-      const v = await calcPage.getSelectValueText(smc);
-      expect(v).toEqual("Cloisons");
-      // check downstream wall
-      const smca = calcPage.getSelectById("select_modele_cloison_aval");
-      expect(await calcPage.getSelectValueText(smca)).toEqual("Ouvrages");
-    });
-
-    it("when PAB is created before Cloisons & ParallelStructure", async() => {
+    it("when PAB is created from scratch", async() => {
       // create PAB
       await startPage.navigateTo();
       await listPage.clickMenuEntryForCalcType(15);
-      // create Cloisons
-      await navbar.clickNewCalculatorButton();
-      await listPage.clickMenuEntryForCalcType(10);
-      // create ParallelStructure
-      await navbar.clickNewCalculatorButton();
-      await listPage.clickMenuEntryForCalcType(8);
-      // check number of basins
-      await navbar.clickCalculatorTab(0);
-      const innerFieldsets = element.all(by.css(".fieldset-inner"));
+      // check that pab-table is present
+      const innerFieldsets = element.all(by.css(".pab-data-table"));
       expect(await innerFieldsets.count()).toBe(1);
-      // check <select> value
-      const smc = calcPage.getSelectById("select_modele_cloisons");
-      const v = await calcPage.getSelectValueText(smc);
-      expect(v).toEqual("Cloisons");
-      // check downstream wall
-      const smca = calcPage.getSelectById("select_modele_cloison_aval");
-      expect(await calcPage.getSelectValueText(smca)).toEqual("Ouvrages");
     });
 
   });
@@ -93,12 +57,14 @@ describe("ngHyd − Passe à Bassins", () => {
       const DH = calcPage.getInputById("DH");
       await DH.clear();
       await DH.sendKeys("0.72");
+
       // create PAB from it, changing modal parameters
       const genButton = calcPage.getGeneratePabButton();
       await genButton.click();
       const debit = calcPage.getInputById("generatePabDebit");
       expect(await debit.getAttribute("value")).toBe("1.5");
       await debit.clear();
+      await browser.sleep(300);
       // send "1.6" in 3 movements, because "." triggers an error and Angular can't cope with the subsequent keys
       await debit.sendKeys("1");
       await browser.sleep(300);
@@ -114,22 +80,22 @@ describe("ngHyd − Passe à Bassins", () => {
       expect(await nbBassins.getAttribute("value")).toBe("6");
       await nbBassins.clear();
       await nbBassins.sendKeys("5");
+
       // click "Generate"
       await element(by.css("dialog-generate-pab button#do-generate")).click();
       await browser.sleep(1000);
+
       // check parameters values
       const P_Q = calcPage.getInputById("Q");
       expect(await P_Q.getAttribute("value")).toBe("1.6");
       const P_Z2 = calcPage.getInputById("Z2");
       expect(await P_Z2.getAttribute("value")).toBe("111.4");
+
       // check number of basins
-      const innerFieldsets = element.all(by.css(".fieldset-inner"));
+      const innerFieldsets = element.all(by.css("td.basin_number"));
       expect(await innerFieldsets.count()).toBe(5);
-      // check <select> values
-      innerFieldsets.each(async (inf) => {
-        const smc = inf.element(by.css("mat-select#select_modele_cloisons"));
-        expect(await calcPage.getSelectValueText(smc)).toEqual("Cloisons 1");
-      });
+
+      // @TODO check more stuff
     });
 
   });
@@ -143,7 +109,7 @@ describe("ngHyd − Passe à Bassins", () => {
       await browser.sleep(200);
       await sidenav.clickLoadSessionButton();
       await browser.sleep(200);
-      await sidenav.loadSessionFile("./session-pab.json");
+      await sidenav.loadSessionFile("./session/session-pab.json");
       await browser.sleep(500);
       // check existence of the loaded modules
       expect(await navbar.getAllCalculatorTabs().count()).toBe(6);
@@ -173,86 +139,32 @@ describe("ngHyd − Passe à Bassins", () => {
       expect(await calcPage.getSelectValueText(smca)).toEqual("Ouvrages 1");
     });
 
-    it("when PAB is loaded before Cloisons & ParallelStructure", async() => {
-      await startPage.navigateTo();
-      // load
-      await navbar.clickMenuButton();
-      await browser.sleep(200);
-      await sidenav.clickLoadSessionButton();
-      await browser.sleep(200);
-      await sidenav.loadSessionFile("./session-pab-mauvais-ordre.json");
-      await browser.sleep(500);
-      // check existence of the loaded modules
-      expect(await navbar.getAllCalculatorTabs().count()).toBe(6);
-      // check parameters values
-      await navbar.clickCalculatorTab(0);
-      await browser.sleep(200);
-      const P_Q = calcPage.getInputById("Q");
-      expect(await P_Q.getAttribute("value")).toBe("1.533");
-      const P_Z2 = calcPage.getInputById("Z2");
-      expect(await P_Z2.getAttribute("value")).toBe("99.44");
-      // check number of basins
-      const innerFieldsets = element.all(by.css(".fieldset-inner"));
-      expect(await innerFieldsets.count()).toBe(5);
-      // check <select> values
-      const expectedCloisons = [ "Cloisons 1", "Cloisons 2", "Cloisons", "Cloisons 2", "Cloisons 1" ];
-      const expectedQA = [ 2, 1, 3, 5, 4 ];
-      let i = 0;
-      for (let i = 0; i < innerFieldsets.length; i++) {
-        const inf = innerFieldsets[i];
-        const smc = inf.element(by.css("mat-select#select_modele_cloisons"));
-        expect(await calcPage.getSelectValueText(smc)).toEqual(expectedCloisons[i]);
-        const QA = inf.element(by.css("input[id$=QA]"));
-        expect(Number(await QA.getAttribute("value"))).toBe(expectedQA[i]);
-        i++;
-      }
-      // check downstream wall
-      const smca = calcPage.getSelectById("select_modele_cloison_aval");
-      expect(await calcPage.getSelectValueText(smca)).toEqual("Ouvrages 1");
-    });
+  });
 
-    it("when PAB is loaded without Cloisons & ParallelStructure", async() => {
+  describe("load regulated variated PAB with calc errors - ", async () => {
+
+    it("should display logs", async() => {
       await startPage.navigateTo();
       // load
       await navbar.clickMenuButton();
       await browser.sleep(200);
       await sidenav.clickLoadSessionButton();
       await browser.sleep(200);
-      await sidenav.loadSessionFile("./session-pab-modeles-vides.json");
+      await sidenav.loadSessionFile("./session/session-pab-regulee-variee.json");
       await browser.sleep(500);
-
-      // check existence of the loaded modules
+      // check existence of the loaded module
       expect(await navbar.getAllCalculatorTabs().count()).toBe(1);
-      // check number of basins
       await navbar.clickCalculatorTab(0);
-      await browser.sleep(200);
-      const innerFieldsets = element.all(by.css(".fieldset-inner"));
-      expect(await innerFieldsets.count()).toBe(3);
-      // check empty <select>
-      innerFieldsets.each(async (inf) => {
-        const smc = inf.element(by.css("mat-select#select_modele_cloisons"));
-        const smcEmpty = await calcPage.isSelectEmpty(smc);
-        console.log("SMC EMPTY ?", smcEmpty);
-        expect(smcEmpty).toBe(true);
-      });
-      // check empty <select> for downstream wall
-      const smca = calcPage.getSelectById("select_modele_cloison_aval");
-      expect(await calcPage.isSelectEmpty(smca)).toBe(true);
 
-      // create a Cloisons
-      await navbar.clickNewCalculatorButton();
-      await listPage.clickMenuEntryForCalcType(10);
-      // create a ParallelStructure
-      await navbar.clickNewCalculatorButton();
-      await listPage.clickMenuEntryForCalcType(8);
-      // check that they are selected
-      await navbar.clickCalculatorTab(0);
-      innerFieldsets.each(async (inf) => {
-        const smc = inf.element(by.css("mat-select#select_modele_cloisons"));
-        expect(await calcPage.getSelectValueText(smc)).toEqual("Cloisons");
-      });
-      const smca2 = calcPage.getSelectById("select_modele_cloison_aval");
-      expect(await calcPage.getSelectValueText(smca2)).toEqual("Ouvrages");
+      // calculate
+      const calcButton = calcPage.getCalculateButton();
+      await calcButton.click();
+      // check that result is not empty
+      const hasResults = await calcPage.hasResults();
+      expect(hasResults).toBe(true);
+
+      // check presence of logs
+      expect(await calcPage.hasLog()).toBe(true);
     });
 
   });
diff --git a/e2e/session-pab-mauvais-ordre.json b/e2e/session-pab-mauvais-ordre.json
deleted file mode 100644
index c60b52be8d02bf622573c496d7418831293068ec..0000000000000000000000000000000000000000
--- a/e2e/session-pab-mauvais-ordre.json
+++ /dev/null
@@ -1,458 +0,0 @@
-{
-    "header": {
-        "source": "jalhyd",
-        "format_version": "1.0",
-        "created": "2019-05-10T13:09:33.792Z"
-    },
-    "session": [
-        {
-            "uid": "NXd0dH",
-            "props": {
-                "calcType": 15,
-                "nodeType": 0,
-                "modeleCloisonAval": "eWtnaj"
-            },
-            "meta": {
-                "title": "PAB"
-            },
-            "children": [
-                {
-                    "uid": "bWswdD",
-                    "props": {
-                        "calcType": 16,
-                        "nodeType": 0,
-                        "modeleCloisons": "bTkxem"
-                    },
-                    "parameters": [
-                        {
-                            "symbol": "QA",
-                            "mode": "SINGLE",
-                            "value": 2
-                        }
-                    ]
-                },
-                {
-                    "uid": "am1oNz",
-                    "props": {
-                        "calcType": 16,
-                        "nodeType": 0,
-                        "modeleCloisons": "bnNhen"
-                    },
-                    "parameters": [
-                        {
-                            "symbol": "QA",
-                            "mode": "SINGLE",
-                            "value": 1
-                        }
-                    ]
-                },
-                {
-                    "uid": "cWJrZm",
-                    "props": {
-                        "calcType": 16,
-                        "nodeType": 0,
-                        "modeleCloisons": "amp6N2"
-                    },
-                    "parameters": [
-                        {
-                            "symbol": "QA",
-                            "mode": "SINGLE",
-                            "value": 3
-                        }
-                    ]
-                },
-                {
-                    "uid": "Y25nNn",
-                    "props": {
-                        "calcType": 16,
-                        "nodeType": 0,
-                        "modeleCloisons": "bnNhen"
-                    },
-                    "parameters": [
-                        {
-                            "symbol": "QA",
-                            "mode": "SINGLE",
-                            "value": 5
-                        }
-                    ]
-                },
-                {
-                    "uid": "M2gxaX",
-                    "props": {
-                        "calcType": 16,
-                        "nodeType": 0,
-                        "modeleCloisons": "bTkxem"
-                    },
-                    "parameters": [
-                        {
-                            "symbol": "QA",
-                            "mode": "SINGLE",
-                            "value": 4
-                        }
-                    ]
-                }
-            ],
-            "parameters": [
-                {
-                    "symbol": "Pr",
-                    "mode": "SINGLE",
-                    "value": 0.0001
-                },
-                {
-                    "symbol": "Q",
-                    "mode": "SINGLE",
-                    "value": "1.533"
-                },
-                {
-                    "symbol": "Z1",
-                    "mode": "CALCUL"
-                },
-                {
-                    "symbol": "Z2",
-                    "mode": "SINGLE",
-                    "value": 99.44
-                }
-            ]
-        },
-        {
-            "uid": "amp6N2",
-            "props": {
-                "calcType": 10,
-                "nodeType": 0
-            },
-            "meta": {
-                "title": "Cloisons"
-            },
-            "children": [
-                {
-                    "uid": "YzNjam",
-                    "props": {
-                        "calcType": 7,
-                        "nodeType": 5,
-                        "structureType": 2,
-                        "loiDebit": 12
-                    },
-                    "children": [],
-                    "parameters": [
-                        {
-                            "symbol": "S",
-                            "mode": "SINGLE",
-                            "value": 0.1
-                        },
-                        {
-                            "symbol": "Cd",
-                            "mode": "SINGLE",
-                            "value": 0.7
-                        }
-                    ]
-                }
-            ],
-            "parameters": [
-                {
-                    "symbol": "Pr",
-                    "mode": "SINGLE",
-                    "value": 0.0001
-                },
-                {
-                    "symbol": "Q",
-                    "mode": "CALCUL"
-                },
-                {
-                    "symbol": "Z1",
-                    "mode": "SINGLE",
-                    "value": 102
-                },
-                {
-                    "symbol": "LB",
-                    "mode": "SINGLE",
-                    "value": 10
-                },
-                {
-                    "symbol": "BB",
-                    "mode": "SINGLE",
-                    "value": 1
-                },
-                {
-                    "symbol": "PB",
-                    "mode": "SINGLE",
-                    "value": 1
-                },
-                {
-                    "symbol": "DH",
-                    "mode": "SINGLE",
-                    "value": 0.5
-                }
-            ]
-        },
-        {
-            "uid": "bTkxem",
-            "props": {
-                "calcType": 10,
-                "nodeType": 0
-            },
-            "meta": {
-                "title": "Cloisons 2"
-            },
-            "children": [
-                {
-                    "uid": "Y3JjaW",
-                    "props": {
-                        "calcType": 7,
-                        "nodeType": 5,
-                        "structureType": 2,
-                        "loiDebit": 12
-                    },
-                    "children": [],
-                    "parameters": [
-                        {
-                            "symbol": "S",
-                            "mode": "SINGLE",
-                            "value": 0.1
-                        },
-                        {
-                            "symbol": "Cd",
-                            "mode": "SINGLE",
-                            "value": 0.7
-                        }
-                    ]
-                }
-            ],
-            "parameters": [
-                {
-                    "symbol": "Pr",
-                    "mode": "SINGLE",
-                    "value": 0.0001
-                },
-                {
-                    "symbol": "Q",
-                    "mode": "CALCUL"
-                },
-                {
-                    "symbol": "Z1",
-                    "mode": "SINGLE",
-                    "value": 102
-                },
-                {
-                    "symbol": "LB",
-                    "mode": "SINGLE",
-                    "value": 10
-                },
-                {
-                    "symbol": "BB",
-                    "mode": "SINGLE",
-                    "value": 1
-                },
-                {
-                    "symbol": "PB",
-                    "mode": "SINGLE",
-                    "value": 1
-                },
-                {
-                    "symbol": "DH",
-                    "mode": "SINGLE",
-                    "value": 0.5
-                }
-            ]
-        },
-        {
-            "uid": "bnNhen",
-            "props": {
-                "calcType": 10,
-                "nodeType": 0
-            },
-            "meta": {
-                "title": "Cloisons 1"
-            },
-            "children": [
-                {
-                    "uid": "amJzem",
-                    "props": {
-                        "calcType": 7,
-                        "nodeType": 5,
-                        "structureType": 2,
-                        "loiDebit": 12
-                    },
-                    "children": [],
-                    "parameters": [
-                        {
-                            "symbol": "S",
-                            "mode": "SINGLE",
-                            "value": 0.1
-                        },
-                        {
-                            "symbol": "Cd",
-                            "mode": "SINGLE",
-                            "value": 0.7
-                        }
-                    ]
-                }
-            ],
-            "parameters": [
-                {
-                    "symbol": "Pr",
-                    "mode": "SINGLE",
-                    "value": 0.0001
-                },
-                {
-                    "symbol": "Q",
-                    "mode": "CALCUL"
-                },
-                {
-                    "symbol": "Z1",
-                    "mode": "SINGLE",
-                    "value": 102
-                },
-                {
-                    "symbol": "LB",
-                    "mode": "SINGLE",
-                    "value": 10
-                },
-                {
-                    "symbol": "BB",
-                    "mode": "SINGLE",
-                    "value": 1
-                },
-                {
-                    "symbol": "PB",
-                    "mode": "SINGLE",
-                    "value": 1
-                },
-                {
-                    "symbol": "DH",
-                    "mode": "SINGLE",
-                    "value": 0.5
-                }
-            ]
-        },
-        {
-            "uid": "M2Q0NX",
-            "props": {
-                "calcType": 8,
-                "nodeType": 0
-            },
-            "meta": {
-                "title": "Ouvrages"
-            },
-            "children": [
-                {
-                    "uid": "MmJxen",
-                    "props": {
-                        "calcType": 7,
-                        "nodeType": 5,
-                        "structureType": 1,
-                        "loiDebit": 1
-                    },
-                    "children": [],
-                    "parameters": [
-                        {
-                            "symbol": "ZDV",
-                            "mode": "SINGLE",
-                            "value": 100
-                        },
-                        {
-                            "symbol": "W",
-                            "mode": "SINGLE",
-                            "value": 0.5
-                        },
-                        {
-                            "symbol": "L",
-                            "mode": "SINGLE",
-                            "value": 2
-                        },
-                        {
-                            "symbol": "Cd",
-                            "mode": "SINGLE",
-                            "value": 0.4
-                        }
-                    ]
-                }
-            ],
-            "parameters": [
-                {
-                    "symbol": "Pr",
-                    "mode": "SINGLE",
-                    "value": 0.0001
-                },
-                {
-                    "symbol": "Q",
-                    "mode": "CALCUL"
-                },
-                {
-                    "symbol": "Z1",
-                    "mode": "SINGLE",
-                    "value": 102
-                },
-                {
-                    "symbol": "Z2",
-                    "mode": "SINGLE",
-                    "value": 101.5
-                }
-            ]
-        },
-        {
-            "uid": "eWtnaj",
-            "props": {
-                "calcType": 8,
-                "nodeType": 0
-            },
-            "meta": {
-                "title": "Ouvrages 1"
-            },
-            "children": [
-                {
-                    "uid": "ZmhpeG",
-                    "props": {
-                        "calcType": 7,
-                        "nodeType": 5,
-                        "structureType": 1,
-                        "loiDebit": 1
-                    },
-                    "children": [],
-                    "parameters": [
-                        {
-                            "symbol": "ZDV",
-                            "mode": "SINGLE",
-                            "value": 100
-                        },
-                        {
-                            "symbol": "W",
-                            "mode": "SINGLE",
-                            "value": 0.5
-                        },
-                        {
-                            "symbol": "L",
-                            "mode": "SINGLE",
-                            "value": 2
-                        },
-                        {
-                            "symbol": "Cd",
-                            "mode": "SINGLE",
-                            "value": 0.4
-                        }
-                    ]
-                }
-            ],
-            "parameters": [
-                {
-                    "symbol": "Pr",
-                    "mode": "SINGLE",
-                    "value": 0.0001
-                },
-                {
-                    "symbol": "Q",
-                    "mode": "CALCUL"
-                },
-                {
-                    "symbol": "Z1",
-                    "mode": "SINGLE",
-                    "value": 102
-                },
-                {
-                    "symbol": "Z2",
-                    "mode": "SINGLE",
-                    "value": 101.5
-                }
-            ]
-        }
-    ]
-}
\ No newline at end of file
diff --git a/e2e/session-pab-modeles-vides.json b/e2e/session-pab-modeles-vides.json
deleted file mode 100644
index b67b85a2eaca8a0b7807eb2159558f2e0399b047..0000000000000000000000000000000000000000
--- a/e2e/session-pab-modeles-vides.json
+++ /dev/null
@@ -1,84 +0,0 @@
-{
-    "header": {
-        "source": "jalhyd",
-        "format_version": "1.0",
-        "created": "2019-05-09T09:57:38.480Z"
-    },
-    "session": [
-        {
-            "uid": "b2xqMj",
-            "props": {
-                "calcType": 15,
-                "nodeType": 0
-            },
-            "meta": {
-                "title": "PAB"
-            },
-            "children": [
-                {
-                    "uid": "czlkYn",
-                    "props": {
-                        "calcType": 16,
-                        "nodeType": 0
-                    },
-                    "parameters": [
-                        {
-                            "symbol": "QA",
-                            "mode": "SINGLE",
-                            "value": 0
-                        }
-                    ]
-                },
-                {
-                    "uid": "eTlyYT",
-                    "props": {
-                        "calcType": 16,
-                        "nodeType": 0
-                    },
-                    "parameters": [
-                        {
-                            "symbol": "QA",
-                            "mode": "SINGLE",
-                            "value": 0
-                        }
-                    ]
-                },
-                {
-                    "uid": "d3Q3an",
-                    "props": {
-                        "calcType": 16,
-                        "nodeType": 0
-                    },
-                    "parameters": [
-                        {
-                            "symbol": "QA",
-                            "mode": "SINGLE",
-                            "value": 0
-                        }
-                    ]
-                }
-            ],
-            "parameters": [
-                {
-                    "symbol": "Pr",
-                    "mode": "SINGLE",
-                    "value": 0.0001
-                },
-                {
-                    "symbol": "Q",
-                    "mode": "SINGLE",
-                    "value": 1.5
-                },
-                {
-                    "symbol": "Z1",
-                    "mode": "CALCUL"
-                },
-                {
-                    "symbol": "Z2",
-                    "mode": "SINGLE",
-                    "value": 99
-                }
-            ]
-        }
-    ]
-}
diff --git a/e2e/session-pab.json b/e2e/session-pab.json
index 2d00f2eecf74e85ef506b780ebd21131e5a7792e..11cc43baaa316f350fdf40075d0b0a96e8e555bd 100644
--- a/e2e/session-pab.json
+++ b/e2e/session-pab.json
@@ -349,8 +349,7 @@
             "uid": "NXd0dH",
             "props": {
                 "calcType": 15,
-                "nodeType": 0,
-                "modeleCloisonAval": "eWtnaj"
+                "nodeType": 0
             },
             "meta": {
                 "title": "PAB"
@@ -360,8 +359,7 @@
                     "uid": "bWswdD",
                     "props": {
                         "calcType": 16,
-                        "nodeType": 0,
-                        "modeleCloisons": "bTkxem"
+                        "nodeType": 0
                     },
                     "parameters": [
                         {
@@ -375,8 +373,7 @@
                     "uid": "am1oNz",
                     "props": {
                         "calcType": 16,
-                        "nodeType": 0,
-                        "modeleCloisons": "bnNhen"
+                        "nodeType": 0
                     },
                     "parameters": [
                         {
@@ -390,8 +387,7 @@
                     "uid": "cWJrZm",
                     "props": {
                         "calcType": 16,
-                        "nodeType": 0,
-                        "modeleCloisons": "amp6N2"
+                        "nodeType": 0
                     },
                     "parameters": [
                         {
@@ -405,8 +401,7 @@
                     "uid": "Y25nNn",
                     "props": {
                         "calcType": 16,
-                        "nodeType": 0,
-                        "modeleCloisons": "bnNhen"
+                        "nodeType": 0
                     },
                     "parameters": [
                         {
@@ -420,8 +415,7 @@
                     "uid": "M2gxaX",
                     "props": {
                         "calcType": 16,
-                        "nodeType": 0,
-                        "modeleCloisons": "bTkxem"
+                        "nodeType": 0
                     },
                     "parameters": [
                         {
diff --git a/e2e/session-6-calc.test.json b/e2e/sessions/session-6-calc.test.json
similarity index 100%
rename from e2e/session-6-calc.test.json
rename to e2e/sessions/session-6-calc.test.json
diff --git a/e2e/session-cascade-params.json b/e2e/sessions/session-cascade-params.json
similarity index 100%
rename from e2e/session-cascade-params.json
rename to e2e/sessions/session-cascade-params.json
diff --git a/e2e/session-cascade-results.json b/e2e/sessions/session-cascade-results.json
similarity index 100%
rename from e2e/session-cascade-results.json
rename to e2e/sessions/session-cascade-results.json
diff --git a/e2e/session-liens-spaghetti.json b/e2e/sessions/session-liens-spaghetti.json
similarity index 100%
rename from e2e/session-liens-spaghetti.json
rename to e2e/sessions/session-liens-spaghetti.json
diff --git a/e2e/session-optional-params.test.json b/e2e/sessions/session-optional-params.test.json
similarity index 100%
rename from e2e/session-optional-params.test.json
rename to e2e/sessions/session-optional-params.test.json
diff --git a/e2e/sessions/session-pab-regulee-variee.json b/e2e/sessions/session-pab-regulee-variee.json
new file mode 100644
index 0000000000000000000000000000000000000000..3dcc919c8bab1d28352a13ee09360c1ba1419356
--- /dev/null
+++ b/e2e/sessions/session-pab-regulee-variee.json
@@ -0,0 +1 @@
+{"header":{"source":"jalhyd","format_version":"1.0","created":"2019-07-08T08:00:03.373Z"},"session":[{"uid":"dXhia3","props":{"calcType":15},"meta":{"title":"PAB régulée varié"},"children":[{"uid":"NnB5bm","props":{"calcType":10,"nodeType":0},"children":[{"uid":"cGsweT","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":76.67},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":76.53999999999999},{"symbol":"ZRAM","mode":"SINGLE","value":76.65499999999999},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"amMxc2","props":{"calcType":10,"nodeType":0},"children":[{"uid":"MjRhNT","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":76.44},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":76.30999999999999},{"symbol":"ZRAM","mode":"SINGLE","value":76.42499999999998},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"anZwOT","props":{"calcType":10,"nodeType":0},"children":[{"uid":"MDNnaj","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":76.21000000000001},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":76.08},{"symbol":"ZRAM","mode":"SINGLE","value":76.195},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"cDRwMD","props":{"calcType":10,"nodeType":0},"children":[{"uid":"aHQ2NT","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":75.98},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":75.85},{"symbol":"ZRAM","mode":"SINGLE","value":75.96499999999999},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"NnppdW","props":{"calcType":10,"nodeType":0},"children":[{"uid":"anYzZj","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":75.75},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":75.61999999999999},{"symbol":"ZRAM","mode":"SINGLE","value":75.73499999999999},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"bGhzNn","props":{"calcType":10,"nodeType":0},"children":[{"uid":"MGM5bm","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":75.52},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":75.38999999999999},{"symbol":"ZRAM","mode":"SINGLE","value":75.50499999999998},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"eXY0aT","props":{"calcType":10,"nodeType":0},"children":[{"uid":"NXp6cz","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":75.29},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":75.16},{"symbol":"ZRAM","mode":"SINGLE","value":75.27499999999999},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"bnJuZ2","props":{"calcType":10,"nodeType":0},"children":[{"uid":"bDR2YX","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":75.06},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":74.92999999999999},{"symbol":"ZRAM","mode":"SINGLE","value":75.04499999999999},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"bmEzOG","props":{"calcType":10,"nodeType":0},"children":[{"uid":"Y2ZobT","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":74.83},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":74.69999999999999},{"symbol":"ZRAM","mode":"SINGLE","value":74.81499999999998},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"ODg5Yj","props":{"calcType":10,"nodeType":0},"children":[{"uid":"eGpueG","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":74.6},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":74.47},{"symbol":"ZRAM","mode":"SINGLE","value":74.58499999999998},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"Mzl6aD","props":{"calcType":10,"nodeType":0},"children":[{"uid":"dno5Mj","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":74.37},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":74.24},{"symbol":"ZRAM","mode":"SINGLE","value":74.35499999999999},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"NnEyY2","props":{"calcType":10,"nodeType":0},"children":[{"uid":"OGlrMG","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":74.14},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":74.00999999999999},{"symbol":"ZRAM","mode":"SINGLE","value":74.12499999999999},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"eGVvbz","props":{"calcType":10,"nodeType":0},"children":[{"uid":"d292cH","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":73.91},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":73.77999999999999},{"symbol":"ZRAM","mode":"SINGLE","value":73.89499999999998},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"Nzltdm","props":{"calcType":10,"nodeType":0},"children":[{"uid":"cm9zOT","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":73.68},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":73.55},{"symbol":"ZRAM","mode":"SINGLE","value":73.66499999999999},{"symbol":"QA","mode":"SINGLE","value":0}]}],"parameters":[{"symbol":"Q","mode":"SINGLE","value":0.773},{"symbol":"Z1","mode":"CALCUL"},{"symbol":"Z2","mode":"MINMAX","min":74.5,"max":75,"step":0.1}],"downWall":{"uid":"Y21pOG","props":{"calcType":16},"children":[{"uid":"c216YW","props":{"calcType":7,"loiDebit":15},"children":[],"parameters":[{"symbol":"L","mode":"SINGLE","value":0.6},{"symbol":"Cd","mode":"SINGLE","value":0.4},{"symbol":"minZDV","mode":"SINGLE","value":73.8},{"symbol":"maxZDV","mode":"SINGLE","value":74.3},{"symbol":"DH","mode":"SINGLE","value":0.217}]}],"parameters":[]}}]}
\ No newline at end of file
diff --git a/jalhyd_branch b/jalhyd_branch
index 94f89b3b03cef0d6963c4d0d2b1be518876e8eb7..19c587308945308f3787a17aee4282c5c6de79df 100644
--- a/jalhyd_branch
+++ b/jalhyd_branch
@@ -1 +1 @@
-62-fonctionnalite-plusieurs-parametres-qui-varient-nghyd-126
\ No newline at end of file
+33-ajout-du-calcul-d-une-passe-a-bassin
diff --git a/package-lock.json b/package-lock.json
index 265e3c560a30d324c6d0baf680706804d7c7a706..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",
@@ -10859,6 +10867,11 @@
         "meow": "^3.1.0"
       }
     },
+    "primeng": {
+      "version": "8.0.0-rc.1",
+      "resolved": "https://registry.npmjs.org/primeng/-/primeng-8.0.0-rc.1.tgz",
+      "integrity": "sha512-xgynBphs4wtCbyyAVQD1ipQCVcSOlfZ697wVVIlZ76V9E1e6eYeNGXN/fLud3NngNyoFiJkP+VJL7kfcfWFnPQ=="
+    },
     "printj": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz",
diff --git a/package.json b/package.json
index bad7e6e723f7aecf7b0cef64641e728ad9467056..d3fda3c44d5c03a20718e9a468395292af30a4d4 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
     "e2e": "ng e2e",
     "electron": "electron .",
     "electron-builder": "electron-builder",
-    "start": "npm run preprocess && ng serve --host 0.0.0.0",
+    "start": "npm run preprocess && ng serve --host 0.0.0.0 --poll 5000",
     "prod": "npm run preprocess && ng serve --host 0.0.0.0 --prod",
     "build": "npm run preprocess && ng build --prod --build-optimizer=false",
     "build-cordova": "npm run preprocess && ng build --prod --build-optimizer=false && npm run add-cordova-js",
@@ -22,7 +22,7 @@
     "release-windows": "rd /s /q release & ng build --prod --build-optimizer=false && npm run electron-builder",
     "release-android": "npm run build-cordova && cordova platform add android; rm dist/assets/docs-fr/sitemap.xml.gz && cordova build android && mv platforms/android/app/build/outputs/apk/debug/app-debug.apk release/cassiopee.apk",
     "e2equick": "ng e2e --dev-server-target= --baseUrl=http://localhost:4200",
-    "jalhyd": "rm node_modules/jalhyd && cd ../jalhyd && npm run build && cd ../nghyd && npm install ../jalhyd",
+    "jalhyd": "rm -f node_modules/jalhyd && cd ../jalhyd && npm run build && cd ../nghyd && npm install ../jalhyd",
     "mathjax": "mkdir -p docs-fr/javascripts/mathjax && rsync -az --delete `cat rsync-mathjax-files` docs-fr/javascripts/mathjax",
     "mkdocs": "npm run mathjax && find docs-fr/javascripts/ -name '*.md' -type f -delete && python3 -m mkdocs build",
     "preprocess": "node preprocessors.js && npm run mkdocs",
@@ -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",
@@ -60,6 +61,7 @@
     "ngx-md": "^7.0.0",
     "ngx-webstorage-service": "^4.0.1",
     "pako": "^1.0.10",
+    "primeng": "^8.0.0-rc.1",
     "roboto-fontface": "^0.10.0",
     "rxjs": "^6.3.3",
     "rxjs-compat": "^6.3.3",
@@ -103,4 +105,4 @@
       "android"
     ]
   }
-}
\ No newline at end of file
+}
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 59d238cf1e0804dcf833355354bd55cc00271940..a4f44c3d2bdf8ccb859e03224da8940aaafc3aaa 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -577,8 +577,7 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
   /**
    * détection de la fermeture de la page/navigateur et demande de confirmation
    */
-  @HostListener("window:beforeunload", ["$event"])
-  confirmExit($event) {
+  @HostListener("window:beforeunload", [ "$event" ]) confirmExit($event) {
     if (
       this.appSetupService.warnBeforeTabClose
       && environment.production // otherwise prevents dev server to reload app after recompiling
@@ -587,4 +586,28 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
       $event.returnValue = "Your data will be lost !";
     }
   }
+
+  /**
+   * Disable value modification on mouse wheel or up/down arrows, in input type="number"
+   */
+  @HostListener("mousewheel", [ "$event" ]) onMouseWheelChrome(event: any) {
+    this.disableScroll(event);
+  }
+  @HostListener("DOMMouseScroll", [ "$event" ]) onMouseWheelFirefox(event: any) {
+    this.disableScroll(event);
+  }
+  @HostListener("onmousewheel", [ "$event" ]) onMouseWheelIE(event: any) {
+    this.disableScroll(event);
+  }
+  disableScroll(event: any) {
+    if (event.srcElement.type === "number") {
+        event.preventDefault();
+        // @TODO how to send event to parent so that scrolling the page works ?
+    }
+  }
+  @HostListener("keydown", [ "$event" ]) onKeydown(event: any) {
+    if (event.which === 38 || event.which === 40) { // up / down arrow
+      event.preventDefault();
+    }
+  }
 }
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 69328bd5fe0b0e395432d09c6de258b8009581d0..c9ec24010f9eb43f0a83385406b6743c543b3043 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -18,6 +18,7 @@ import {
   MatListModule,
   MatCardModule,
   MatProgressBarModule,
+  MatRadioModule,
   MatTableModule,
   MatSnackBarModule,
   MatBadgeModule,
@@ -26,6 +27,8 @@ import {
 } from "@angular/material";
 import { MaterialFileInputModule } from "ngx-material-file-input";
 
+import {TableModule} from "primeng/components/table/table";
+
 import { FlexLayoutModule } from "@angular/flex-layout";
 import {
   CustomBreakPointsProvider,
@@ -76,9 +79,13 @@ 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";
 
 import { DialogConfirmEmptySessionComponent } from "./components/dialog-confirm-empty-session/dialog-confirm-empty-session.component";
 import { DialogConfirmCloseCalcComponent } from "./components/dialog-confirm-close-calc/dialog-confirm-close-calc.component";
+import { DialogEditPabComponent } from "./components/dialog-edit-pab/dialog-edit-pab.component";
 import { DialogEditParamComputedComponent } from "./components/dialog-edit-param-computed/dialog-edit-param-computed.component";
 import { DialogEditParamValuesComponent } from "./components/dialog-edit-param-values/dialog-edit-param-values.component";
 import { DialogGeneratePABComponent } from "./components/dialog-generate-pab/dialog-generate-pab.component";
@@ -124,6 +131,7 @@ const appRoutes: Routes = [
     MatListModule,
     MatMenuModule,
     MatProgressBarModule,
+    MatRadioModule,
     MatSelectModule,
     MatSidenavModule,
     MatSnackBarModule,
@@ -139,7 +147,8 @@ const appRoutes: Routes = [
         enableTracing: false // debugging purposes only
       }
     ),
-    StorageServiceModule
+    StorageServiceModule,
+    TableModule
   ],
   declarations: [ // composants, pipes et directives
     AppComponent,
@@ -151,6 +160,7 @@ const appRoutes: Routes = [
     CalculatorResultsComponent,
     DialogConfirmCloseCalcComponent,
     DialogConfirmEmptySessionComponent,
+    DialogEditPabComponent,
     DialogEditParamComputedComponent,
     DialogEditParamValuesComponent,
     DialogGeneratePABComponent,
@@ -174,8 +184,11 @@ const appRoutes: Routes = [
     LogComponent,
     LogEntryComponent,
     NgParamInputComponent,
+    PabProfileGraphComponent,
     PabResultsComponent,
     PabResultsTableComponent,
+    PabTableComponent,
+    PabVariableResultsSelectorComponent,
     ParamComputedComponent,
     ParamFieldLineComponent,
     ParamLinkComponent,
@@ -191,6 +204,7 @@ const appRoutes: Routes = [
   entryComponents: [
     DialogConfirmCloseCalcComponent,
     DialogConfirmEmptySessionComponent,
+    DialogEditPabComponent,
     DialogEditParamComputedComponent,
     DialogEditParamValuesComponent,
     DialogGeneratePABComponent,
diff --git a/src/app/calculators/cloisons/cloisons.config.json b/src/app/calculators/cloisons/cloisons.config.json
index 6ce6b17ba99df0ae7eeb462779fc6f7909d6cafc..ea679624bfed8ac817ef405a9543b1d328dca705 100644
--- a/src/app/calculators/cloisons/cloisons.config.json
+++ b/src/app/calculators/cloisons/cloisons.config.json
@@ -14,6 +14,7 @@
             {
                 "type": "input",
                 "id": "Z1",
+                "option": "var",
                 "unit": "m"
             },
             {
@@ -82,8 +83,8 @@
                         "enum": "LoiDebit.WeirSubmergedLarinier"
                     },
                     {
-                        "id": "select_loidebit1_kivi",
-                        "enum": "LoiDebit.KIVI"
+                        "id": "select_loidebit1_villemonte",
+                        "enum": "LoiDebit.WeirVillemonte"
                     }
                 ],
                 "dep_exist": [
@@ -143,7 +144,7 @@
             },
             {
                 "type": "input",
-                "id": "ZDV",
+                "id": "h1",
                 "unit": "m",
                 "nodeType": "StructureRectangle",
                 "dep_exist": [
@@ -191,31 +192,7 @@
                     }
                 ]
             },
-            {
-                "type": "input",
-                "id": "alpha",
-                "unit": "",
-                "nodeType": "StructureRectangle",
-                "dep_exist": [
-                    {
-                        "refid": "select_loidebit1",
-                        "refvalue": "select_loidebit1_kivi"
-                    }
-                ]
-            },
-            {
-                "type": "input",
-                "id": "beta",
-                "unit": "",
-                "nodeType": "StructureRectangle",
-                "dep_exist": [
-                    {
-                        "refid": "select_loidebit1",
-                        "refvalue": "select_loidebit1_kivi"
-                    }
-                ]
-            },
-            {
+             {
                 "type": "input",
                 "id": "alpha2",
                 "unit": "",
diff --git a/src/app/calculators/cloisons/cloisons.en.json b/src/app/calculators/cloisons/cloisons.en.json
index c60b60eae765bc22efe6c5c2314d766c58fa0ad9..d95c9ebadbb1b04e6cc70eb2dae499428f3e6673 100644
--- a/src/app/calculators/cloisons/cloisons.en.json
+++ b/src/app/calculators/cloisons/cloisons.en.json
@@ -1,20 +1,13 @@
 {
-    "Q": "Débit total",
-    "ZR": "Cote du lit amont",
-    "LB": "Longueur du bassin",
-    "BB": "Largeur du bassin",
-    "PB": "Profondeur moyenne du bassin",
-    "DH": "Chute",
-    "S": "Surface de l'orifice",
-    "ZRAM": "Bottom elevation",
-    "alpha2": "Demi-angle au sommet (°)",
-    "BT": "Demi-ouverture du triangle (m)",
-    "ZT": "Cote haute du triangle (m)",
+    "Q": "Total discharge",
+    "ZR": "Upstream bed elevation",
+    "PB": "Pool mean depth",
+    "h1": "Head",
     "select_ouvrage_seuil_triang": "Triangular weir",
     "select_ouvrage_seuil_triangtrunc": "Truncated triangular weir",
     "select_ouvrage_orifice": "Orifice",
     "select_ouvrage_seuil_rect": "Rectangular weir",
-    "select_loidebit1_kivi": "Kindsvater-Carter and Villemonte",
+    "select_loidebit1_villemonte": "Villemonte 1947",
     "select_loidebit1_fente": "Submerged slot (Larinier 1992)",
     "select_loidebit2_orifice": "Submerged orifice",
     "select_loidebit3_seuiltriang": "Free flow triangular weir",
diff --git a/src/app/calculators/cloisons/cloisons.fr.json b/src/app/calculators/cloisons/cloisons.fr.json
index fc6e0d91e9a17611cdd7137de74a552bec4e4ebf..7082d1cc1d82def88f048f55836b137fc91d3e3e 100644
--- a/src/app/calculators/cloisons/cloisons.fr.json
+++ b/src/app/calculators/cloisons/cloisons.fr.json
@@ -1,20 +1,10 @@
 {
     "Q": "Débit total",
-    "ZR": "Cote du lit amont",
-    "LB": "Longueur du bassin",
-    "BB": "Largeur du bassin",
-    "PB": "Profondeur moyenne du bassin",
-    "DH": "Chute",
-    "S": "Surface de l'orifice",
-    "ZRAM": "Cote de radier amont de la cloison",
-    "alpha2": "Demi-angle au sommet (°)",
-    "BT": "Demi-ouverture du triangle (m)",
-    "ZT": "Cote haute du triangle (m)",
     "select_ouvrage_seuil_triang": "Seuil triangulaire",
     "select_ouvrage_seuil_triangtrunc": "Seuil triangulaire tronqué",
     "select_ouvrage_orifice": "Orifice",
     "select_ouvrage_seuil_rect": "Seuil rectangulaire",
-    "select_loidebit1_kivi": "Kindsvater-Carter et Villemonte",
+    "select_loidebit1_villemonte": "Villemonte 1947",
     "select_loidebit1_fente": "Fente noyée (Larinier 1992)",
     "select_loidebit2_orifice": "Orifice noyé",
     "select_loidebit3_seuiltriang": "Déversoir triangulaire dénoyé",
diff --git a/src/app/calculators/pab/pab.config.json b/src/app/calculators/pab/pab.config.json
index 443720b8d7f8eea4d5431f752b17ebe6afefaced..d477bc7af1a31bac68cacae5b905ff559617395e 100644
--- a/src/app/calculators/pab/pab.config.json
+++ b/src/app/calculators/pab/pab.config.json
@@ -8,7 +8,6 @@
             {
                 "type": "input",
                 "id": "Q",
-                "symbol": "Q",
                 "unit": "m³/s"
             },
             {
@@ -19,51 +18,13 @@
             {
                 "type": "input",
                 "id": "Z2",
-                "unit": "m"
-            }
-        ]
-    },
-    {
-        "id": "fs_bassin",
-        "type": "fieldset_template",
-        "calcType": "PabCloisons",
-        "option": "fix",
-        "fields": [
-            {
-                "id": "select_modele_cloisons",
-                "type": "select_cloisons",
-                "select": []
-            },
-            {
-                "type": "input",
-                "id": "QA",
-                "unit": "m³/s"
-            }
-        ]
-    },
-    {
-        "id": "bassin_container",
-        "type": "template_container",
-        "templates": [
-            "fs_bassin"
-        ]
-    },
-    {
-        "id": "fs_cloison_aval",
-        "type": "fieldset",
-        "calcType": "Pab",
-        "fields": [
-            {
-                "id": "select_modele_cloison_aval",
-                "type": "select_cloison_aval",
-                "select": []
+                "unit": "m",
+                "option": "var"
             }
         ]
     },
     {
-        "type": "options",
-        "modeleCloisonsSelectId": "select_modele_cloisons",
-        "modeleCloisonAvalSelectId": "select_modele_cloison_aval",
-        "idCal": "Q"
+        "id": "tableau_entree_pab",
+        "type": "pab_table"
     }
-]
\ No newline at end of file
+]
diff --git a/src/app/calculators/pab/pab.en.json b/src/app/calculators/pab/pab.en.json
index cf3cbb4ee03232538f17997af04dc4562a4aad4a..56e4550abe0114b66b06e0cbae4022825fc6912b 100644
--- a/src/app/calculators/pab/pab.en.json
+++ b/src/app/calculators/pab/pab.en.json
@@ -3,11 +3,20 @@
     "Q": "Flow",
     "Z1": "Upstream elevation",
     "Z2": "Downstream elevation",
+    "LB": "Basin length",
+    "BB": "Basin width",
+    "PB": "Basin mean depth",
+    "W": "Gate opening",
+    "ZRAM": "Bottom elevation",
+    "ZRMB": "Mid-basin elevation",
+    "h1": "Head",
     "fs_bassin": "Basin",
     "fs_cloison_aval": "Downstream wall",
     "bassin_container": "Basins",
     "select_modele_cloisons": "Cross walls model",
     "select_modele_cloison_aval": "Downstream wall model",
+    "UNIT_LB": "m",
+    "UNIT_BB": "m",
     "UNIT_DH": "m",
 	"UNIT_PV": "W/m³",
     "UNIT_Q": "m³/s",
@@ -15,5 +24,6 @@
 	"UNIT_YMOY": "m",
     "UNIT_Z": "m",
 	"UNIT_ZRAM": "m",
-	"UNIT_ZRB": "m"
+	"UNIT_ZRB": "m",
+	"UNIT_ZRMB": "m"
 }
\ No newline at end of file
diff --git a/src/app/calculators/pab/pab.fr.json b/src/app/calculators/pab/pab.fr.json
index a372cb4e238b08103ebced6412687ba4ae64c45d..dd171f91acad2d5d1abf44c6b183d3e52901d8d7 100644
--- a/src/app/calculators/pab/pab.fr.json
+++ b/src/app/calculators/pab/pab.fr.json
@@ -1,13 +1,12 @@
 {
     "fs_param_hydro": "Paramètres hydrauliques",
-    "Q": "Débit",
-    "Z1": "Cote amont",
-    "Z2": "Cote aval",
     "fs_bassin": "Bassin",
     "fs_cloison_aval": "Cloison aval",
     "bassin_container": "Bassins",
     "select_modele_cloisons": "Modèle de cloisons",
     "select_modele_cloison_aval": "Modèle de la cloison aval",
+    "UNIT_LB": "m",
+    "UNIT_BB": "m",
     "UNIT_DH": "m",
 	"UNIT_PV": "W/m³",
     "UNIT_Q": "m³/s",
@@ -15,5 +14,5 @@
 	"UNIT_YMOY": "m",
     "UNIT_Z": "m",
 	"UNIT_ZRAM": "m",
-	"UNIT_ZRB": "m"
+	"UNIT_ZRMB": "m"
 }
\ No newline at end of file
diff --git a/src/app/calculators/parallel-structures/parallel-structures.config.json b/src/app/calculators/parallel-structures/parallel-structures.config.json
index 966819227a3382cab9a093c7c111d58718ec8dba..2e61bc489dfa3ba373e85b617ad15065b82898f9 100644
--- a/src/app/calculators/parallel-structures/parallel-structures.config.json
+++ b/src/app/calculators/parallel-structures/parallel-structures.config.json
@@ -74,6 +74,10 @@
                         "id": "select_loidebit1_cunge80",
                         "enum": "LoiDebit.Cunge80"
                     },
+                    {
+                        "id": "select_loidebit1_villemonte",
+                        "enum": "LoiDebit.WeirVillemonte"
+                    },
                     {
                         "id": "select_loidebit1_kivi",
                         "enum": "LoiDebit.KIVI"
diff --git a/src/app/calculators/parallel-structures/parallel-structures.en.json b/src/app/calculators/parallel-structures/parallel-structures.en.json
index ad6d5252f752b4b9e3e6150b8a5daba99e63ed46..52135d4610ddac92895e2258560413415516261c 100644
--- a/src/app/calculators/parallel-structures/parallel-structures.en.json
+++ b/src/app/calculators/parallel-structures/parallel-structures.en.json
@@ -14,6 +14,7 @@
     "select_loidebit1_cem88v": "Weir/Undershot gate Cemagref 88",
     "select_loidebit1_fente": "Submerged slot (Larinier 1992)",
     "select_loidebit1_kivi": "Kindsvater-Carter and Villemonte",
+    "select_loidebit1_villemonte": "Villemonte 1957",
     "select_loidebit2_vannedenoye": "Free flow gate",
     "select_loidebit2_vannenoye": "Submerged gate",
     "select_loidebit2_cunge80": "Cunge 80",
diff --git a/src/app/calculators/parallel-structures/parallel-structures.fr.json b/src/app/calculators/parallel-structures/parallel-structures.fr.json
index 45e1910810eaf075737ac53a821291e7d2a29a4b..3b9d38763ea8196a0c472ce34554cf7808b68d3e 100644
--- a/src/app/calculators/parallel-structures/parallel-structures.fr.json
+++ b/src/app/calculators/parallel-structures/parallel-structures.fr.json
@@ -7,13 +7,13 @@
     "select_ouvrage_seuil_triangtrunc": "Seuil triangulaire tronqué",
     "select_ouvrage_seuil_trap": "Seuil trapézoïdal",
     "select_ouvrage_vanne_trap": "Vanne trapézoïdale",
-    "W": "Ouverture de vanne",
     "select_loidebit1_seuildenoye": "Seuil dénoyé",
     "select_loidebit1_cunge80": "Cunge 80",
     "select_loidebit1_cem88d": "Déversoir/Orifice Cemagref 88",
     "select_loidebit1_cem88v": "Déversoir/Vanne de fond Cemagref 88",
     "select_loidebit1_fente": "Fente noyée (Larinier 1992)",
     "select_loidebit1_kivi": "Kindsvater-Carter et Villemonte",
+    "select_loidebit1_villemonte": "Villemonte 1957",
     "select_loidebit2_vannedenoye": "Vanne dénoyé",
     "select_loidebit2_vannenoye": "Vanne noyé",
     "select_loidebit2_cunge80": "Cunge 80",
diff --git a/src/app/components/calculator-list/calculator-list.component.ts b/src/app/components/calculator-list/calculator-list.component.ts
index f05e933f0188edb8091ab220aeb57a3826a355bd..c4acb05d66f1251e2ebc8a26f1fa66f927bd9a14 100644
--- a/src/app/components/calculator-list/calculator-list.component.ts
+++ b/src/app/components/calculator-list/calculator-list.component.ts
@@ -79,7 +79,7 @@ export class CalculatorListComponent implements OnInit {
                     if ( // those sub-Nub types cannot be built outside a parent
                         t !== CalculatorType.Structure
                         && t !== CalculatorType.Section
-                        && t !== CalculatorType.PabCloisons
+                        && t !== CalculatorType.CloisonAval
                     ) {
                         unusedTheme.calculators.push({
                             type: t,
diff --git a/src/app/components/dialog-edit-pab/dialog-edit-pab.component.html b/src/app/components/dialog-edit-pab/dialog-edit-pab.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..8674eb361f97173fe01467f1e8a70671864751e7
--- /dev/null
+++ b/src/app/components/dialog-edit-pab/dialog-edit-pab.component.html
@@ -0,0 +1,74 @@
+<h1 mat-dialog-title [innerHTML]="uitextEditPabTitle"></h1>
+
+<form>
+
+  <div mat-dialog-content>
+
+    <!-- récap selection -->
+    <div id="selection-abstract">
+      {{ selectionAbstract }}
+    </div>
+
+    <!-- champ à modifier -->
+    <mat-form-field class="select-form-field">
+      <mat-select [placeholder]="uitextVariable" [(value)]="variable">
+          <mat-option *ngFor="let v of availableVariables" [value]="v.value">
+              {{ v.label }}
+          </mat-option>
+      </mat-select>
+    </mat-form-field>
+
+    <!-- ngDefaultControl : see https://github.com/angular/components/issues/8267 -->
+    <mat-radio-group ngDefaultControl [(ngModel)]="varAction" name="varAction">
+
+      <div class="radio-button-and-input-wrapper rbaiw-set-value">
+        <mat-radio-button value="set-value">
+          {{ uitextSetValue }}
+        </mat-radio-button>
+        <mat-form-field class="input-form-field">
+          <input matInput [(ngModel)]="valueToSet" name="valueToSet" #valueToSetRef="ngModel"
+            required pattern="^-?([0-9]*\.)?([0-9]+[Ee]-?)?[0-9]+$">
+        </mat-form-field>
+        <mat-error *ngIf="varAction === 'set-value' && valueToSetRef.invalid">
+            {{ uitextMustBeANumber }}
+        </mat-error>
+      </div>
+
+      <div class="radio-button-and-input-wrapper rbaiw-delta">
+        <mat-radio-button value="delta">
+          {{ uitextDelta }}
+        </mat-radio-button>
+        <mat-form-field class="input-form-field">
+          <input matInput [(ngModel)]="delta" name="delta" #deltaRef="ngModel"
+            required pattern="^-?([0-9]*\.)?([0-9]+[Ee]-?)?[0-9]+$">
+        </mat-form-field>
+        <mat-error *ngIf="varAction === 'delta' && deltaRef.invalid">
+            {{ uitextMustBeANumber }}
+        </mat-error>
+      </div>
+
+      <div class="radio-button-and-input-wrapper">
+        <mat-radio-button value="interpolate" [disabled]="! interpolationEnabled">
+          {{ uitextInterpolate }}
+          <span class="interpolation-bounds">
+            {{ interpolationBounds }}
+          </span>
+        </mat-radio-button>
+      </div>
+
+    </mat-radio-group>
+
+  </div>
+
+  <div mat-dialog-actions [attr.align]="'end'">
+    <button mat-raised-button color="primary" [mat-dialog-close]="false" cdkFocusInitial>
+      {{ uitextCancel }}
+    </button>
+    <button mat-raised-button type="submit" color="warn" (click)="applyValues()"
+      [disabled]="buttonDisabled(valueToSetRef, deltaRef)">
+
+      {{ uitextValidate }}
+    </button>
+  </div>
+
+</form>
diff --git a/src/app/components/dialog-edit-pab/dialog-edit-pab.component.scss b/src/app/components/dialog-edit-pab/dialog-edit-pab.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..d8f214cbebe7f7aae7dffb37cece5d77e1bcfe54
--- /dev/null
+++ b/src/app/components/dialog-edit-pab/dialog-edit-pab.component.scss
@@ -0,0 +1,54 @@
+.mat-dialog-content {
+    margin-bottom: 2em;
+    overflow: inherit; // wtf
+}
+
+#selection-abstract {
+    font-size: .9em;
+    margin-bottom: 2em;
+}
+
+mat-form-field.select-form-field {
+    width: 100%;
+    margin-bottom: 10px;
+}
+
+.radio-button-and-input-wrapper {
+    margin-bottom: .5em;
+
+    mat-radio-button {
+        width: 60%;
+
+        &[value="interpolate"] {
+            width: 100%;
+
+            ::ng-deep label.mat-radio-label {
+                width: 100%;
+
+                > .mat-radio-label-content {
+                    width: 100%;
+
+                    .interpolation-bounds {
+                        float: right;
+                        color: #707070;
+                    }
+                }
+            }
+        }
+    }
+
+    mat-form-field.input-form-field {
+        width: 35%;
+        margin-left: 5%;
+    }
+
+    mat-error {
+        font-size: .8em;
+        margin-top: -12px;
+        margin-bottom: 15px;
+    }
+
+    &.rbaiw-set-value, &.rbaiw-delta {
+        margin-top: -1em;
+    }
+}
diff --git a/src/app/components/dialog-edit-pab/dialog-edit-pab.component.ts b/src/app/components/dialog-edit-pab/dialog-edit-pab.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ba3986a127aafa0f69422d61c870e66ea81bc9db
--- /dev/null
+++ b/src/app/components/dialog-edit-pab/dialog-edit-pab.component.ts
@@ -0,0 +1,194 @@
+import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material";
+import { Inject, Component } from "@angular/core";
+
+import { I18nService } from "../../services/internationalisation/internationalisation.service";
+
+import { sprintf } from "sprintf-js";
+
+@Component({
+    selector: "dialog-edit-pab",
+    templateUrl: "dialog-edit-pab.component.html",
+    styleUrls: [ "dialog-edit-pab.component.scss" ]
+})
+export class DialogEditPabComponent {
+
+  /** variables eligible to modification */
+  public availableVariables: any[];
+
+  /** symbols of the variables eligible to modification */
+  public availableVariableSymbols: string[];
+
+  /** action to apply to the variable */
+  public varAction: string;
+
+  /** value to set to all occurrences of the variable */
+  public valueToSet: number;
+
+  /** delta to apply (add) to all occurrences of the variable */
+  public delta: number;
+
+  /** true if selected devices are contained in one unique column */
+  public vertical: boolean;
+
+  /** variable to modify on every selected object */
+  private _variable: string;
+
+  /** number of selected walls and devices */
+  private selectedItemsAbstract: { walls: number, wallsDevices: number, devices: number };
+
+  constructor(
+    public dialogRef: MatDialogRef<DialogEditPabComponent>,
+    private intlService: I18nService,
+    @Inject(MAT_DIALOG_DATA) public data: any
+  ) {
+    this.selectedItemsAbstract = this.data.selectedItemsAbstract;
+    this.vertical = this.data.vertical;
+    this.availableVariables = this.data.availableVariables;
+    // console.log("AV VAR", this.availableVariables);
+    this.availableVariableSymbols = this.availableVariables.map(av => av.value);
+    this.variable = this.availableVariableSymbols[0];
+    // default action
+    this.varAction = "set-value";
+    // example values for validation / avoiding using a placeholder
+    this.valueToSet = this.availableVariables[0].first;
+    this.delta = 0;
+  }
+
+  public get variable(): string {
+    return this._variable;
+  }
+
+  // find available variable details from key
+  private findVariableDetails(variable: string): any {
+    let details: any;
+    for (const av of this.availableVariables) {
+      if (av.value === variable) {
+        details = av;
+      }
+    }
+    return details;
+  }
+
+  // preloads valueToSet depending on variable chosen
+  public set variable(v: string) {
+    this._variable = v;
+    const av = this.findVariableDetails(v);
+    this.valueToSet = av.first;
+  }
+
+  /** closes dialog and tells parent to apply modifications */
+  public applyValues() {
+    this.dialogRef.close({
+      action: this.varAction,
+      variable: this.variable,
+      value: Number(this.valueToSet),
+      delta: Number(this.delta),
+      variableDetails: this.findVariableDetails(this.variable)
+    });
+  }
+
+  public buttonDisabled(value: any, delta: any) {
+    return (
+      (this.varAction === "set-value" && ! value.valid)
+      || (this.varAction === "delta" && ! delta.valid)
+    );
+  }
+
+  /** returns a short text summing up what is selected */
+  public get selectionAbstract() {
+    let abstract = "";
+    if (this.selectedItemsAbstract.walls > 0) {
+      // devices outside of complete walls ?
+      if (this.selectedItemsAbstract.devices > 0) {
+        abstract = sprintf(
+          this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_N_WALLS_P_DEVICES"),
+          this.selectedItemsAbstract.walls,
+          this.selectedItemsAbstract.devices,
+          this.selectedItemsAbstract.wallsDevices + this.selectedItemsAbstract.devices
+        );
+      } else {
+        // only complete walls
+        abstract = sprintf(
+          this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_N_WALLS"),
+          this.selectedItemsAbstract.walls,
+          this.selectedItemsAbstract.wallsDevices
+        );
+      }
+    } else {
+      // no complete wall, devices only
+      abstract = sprintf(
+        this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_N_DEVICES"),
+        this.selectedItemsAbstract.devices
+      );
+    }
+    return abstract;
+  }
+
+  public variableLabel(v: string) {
+    const av = this.findVariableDetails(v);
+    return av.label;
+  }
+
+  public get interpolationEnabled() {
+    return (
+      this.vertical
+      && (this.selectedItemsAbstract.devices + this.selectedItemsAbstract.wallsDevices) > 1
+    );
+  }
+
+  public get interpolationBounds() {
+    let bounds = "";
+    if (this.interpolationEnabled) {
+      const varDetails = this.findVariableDetails(this.variable);
+      if (varDetails) {
+        bounds = sprintf(
+          this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_INTERPOLATION_BOUNDS"),
+          varDetails.first,
+          varDetails.last
+        );
+      }
+    }
+    return bounds;
+  }
+
+  public get uitextEditPabTitle() {
+    return this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_TITLE");
+  }
+
+  public get uitextVariable() {
+    return this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_OPTION_VARIABLE");
+  }
+
+  public get uitextSetValue() {
+    return this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_OPTION_SET_VALUE");
+  }
+
+  public get uitextDelta() {
+    return this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_OPTION_DELTA");
+  }
+
+  public get uitextInterpolate() {
+    return this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_OPTION_INTERPOLATE");
+  }
+
+  public get uitextSetValueInput() {
+    return this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_SET_VALUE_INPUT");
+  }
+
+  public get uitextDeltaInput() {
+    return this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_DELTA_INPUT");
+  }
+
+  public get uitextMustBeANumber(): string {
+      return this.intlService.localizeText("ERROR_PARAM_MUST_BE_A_NUMBER");
+  }
+
+  public get uitextValidate() {
+    return this.intlService.localizeText("INFO_OPTION_VALIDATE");
+  }
+
+  public get uitextCancel() {
+    return this.intlService.localizeText("INFO_OPTION_CANCEL");
+  }
+
+}
diff --git a/src/app/components/dialog-generate-pab/dialog-generate-pab.component.html b/src/app/components/dialog-generate-pab/dialog-generate-pab.component.html
index 14202deecd17f1de7c5a8bc248dcdc84c93ddfc9..c1161f20c451d272622d87db4d7f3b5d795b9a02 100644
--- a/src/app/components/dialog-generate-pab/dialog-generate-pab.component.html
+++ b/src/app/components/dialog-generate-pab/dialog-generate-pab.component.html
@@ -25,12 +25,12 @@
     </mat-error>
 
     <mat-form-field>
-      <input matInput required [placeholder]="uitextNBBassins" pattern="^[1-9][0-9]*$"
+      <input matInput required [placeholder]="uitextNBBassins" pattern="^([2-9]|[1-9][0-9]+)$"
       [(ngModel)]="nbBassins" name="nbBassins" #inputNbBassins="ngModel" id="generatePabNbBassins">
     </mat-form-field>
     <mat-error *ngIf="inputNbBassins.invalid && (inputNbBassins.dirty || inputNbBassins.touched)">
         <div *ngIf="inputNbBassins.errors.required || inputNbBassins.errors.pattern">
-            {{ uitextMustBePositive }}
+            {{ uitextMustBeAtLeastTwo }}
         </div>
     </mat-error>
 
diff --git a/src/app/components/dialog-generate-pab/dialog-generate-pab.component.ts b/src/app/components/dialog-generate-pab/dialog-generate-pab.component.ts
index a0f55835b40c16228e8704fc973fda828f809267..bb81e269d9b3f8186ab10d25827336913ec0edb3 100644
--- a/src/app/components/dialog-generate-pab/dialog-generate-pab.component.ts
+++ b/src/app/components/dialog-generate-pab/dialog-generate-pab.component.ts
@@ -1,8 +1,10 @@
 import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material";
 import { Inject, Component } from "@angular/core";
 
+import { ApplicationSetupService } from "../../services/app-setup/app-setup.service";
 import { I18nService } from "../../services/internationalisation/internationalisation.service";
-import { FormBuilder } from "@angular/forms";
+
+import { sprintf } from "sprintf-js";
 
 @Component({
     selector: "dialog-generate-pab",
@@ -22,12 +24,13 @@ export class DialogGeneratePABComponent {
   constructor(
     public dialogRef: MatDialogRef<DialogGeneratePABComponent>,
     private intlService: I18nService,
-    private fb: FormBuilder,
+    private appSetupService: ApplicationSetupService,
     @Inject(MAT_DIALOG_DATA) public data: any
   ) {
-    this.coteAmont = data.coteAmont;
-    this.debit = data.debit;
-    this.chute = data.chute;
+    const nDigits = this.appSetupService.displayDigits;
+    this.coteAmont = Number(data.coteAmont.toFixed(nDigits));
+    this.debit = Number(data.debit.toFixed(nDigits));
+    this.chute = Number(data.chute.toFixed(nDigits));
   }
 
   public generatePAB() {
@@ -67,6 +70,10 @@ export class DialogGeneratePABComponent {
     return this.intlService.localizeText("ERROR_PARAM_MUST_BE_POSITIVE");
   }
 
+  public get uitextMustBeAtLeastTwo() {
+    return sprintf(this.intlService.localizeText("ERROR_PARAM_MUST_BE_AT_LEAST"), 2);
+  }
+
   public get uitextGeneratePAB() {
       return this.intlService.localizeText("INFO_CALCULATOR_RESULTS_GENERATE_PAB");
   }
diff --git a/src/app/components/dialog-save-session/dialog-save-session.component.ts b/src/app/components/dialog-save-session/dialog-save-session.component.ts
index eac698d910b693c0bda270014b765531b4506702..425c5f5ed09046dde490187c875df3adb51d85f3 100644
--- a/src/app/components/dialog-save-session/dialog-save-session.component.ts
+++ b/src/app/components/dialog-save-session/dialog-save-session.component.ts
@@ -47,7 +47,6 @@ export class DialogSaveSessionComponent {
      *  - PabCloisons depend on their models
      */
     public checkLinkedParamsAndModelsDependencies() {
-      console.log("CLPMD !", this.calculators);
       this.dependenciesProblems = [];
       // for all checked Nubs
       this.calculators.forEach((c) => {
diff --git a/src/app/components/field-set/field-set.component.html b/src/app/components/field-set/field-set.component.html
index 2471c8dcb4c729e487c16c8e1b9ed2c3f6de27d7..708b04ad6a322a05841b1f4e1e2199e88900fe41 100644
--- a/src/app/components/field-set/field-set.component.html
+++ b/src/app/components/field-set/field-set.component.html
@@ -35,8 +35,5 @@
 
         <select-field-line *ngIf="isSelectField(p)" [_select]=p>
         </select-field-line>
-
-        <select-model-field-line *ngIf="isSelectModelField(p)" [_select]=p>
-        </select-model-field-line>
     </ng-template>
 </mat-card-content>
diff --git a/src/app/components/field-set/field-set.component.ts b/src/app/components/field-set/field-set.component.ts
index ee5e3de853b7a884b6f6b0f8f2b18963205f5f0b..d26f1e0048abe33f0f47a8eee5966ca15624b771 100644
--- a/src/app/components/field-set/field-set.component.ts
+++ b/src/app/components/field-set/field-set.component.ts
@@ -207,19 +207,6 @@ export class FieldSetComponent implements DoCheck {
         );
     }
 
-    /**
-     * détermine si un Field est du type SelectFieldModel
-     */
-    private isSelectModelField(f: Field): boolean {
-        return (
-            f instanceof SelectFieldModel
-            && f.parentForm instanceof FormulairePab
-            && (
-                f.id === (f.parentForm as FormulairePab).modeleCloisonsSelectId
-                || f.id === (f.parentForm as FormulairePab).modeleCloisonAvalSelectId
-            )
-        );
-    }
     /*
      * gestion des événements clic sur les radios :
      * réception d'un message du composant enfant (param-field)
@@ -336,9 +323,9 @@ export class FieldSetComponent implements DoCheck {
         }
         let msg: string;
         if (this.childrenToAdd === 1) {
-            msg = this.i18nService.localizeText("INFO_FSC_FS_ADDED");
+            msg = this.i18nService.localizeText("INFO_DEVICE_ADDED");
         } else {
-            msg = sprintf(this.i18nService.localizeText("INFO_FSC_FS_ADDED_N_TIMES"), this.childrenToAdd);
+            msg = sprintf(this.i18nService.localizeText("INFO_DEVICE_ADDED_N_TIMES"), this.childrenToAdd);
         }
         this.notifService.notify(msg);
         this.childrenToAdd = 1; // reinit to avoid confusion
@@ -357,9 +344,9 @@ export class FieldSetComponent implements DoCheck {
         const pos = (this._fieldSet.parent as FieldsetContainer).getFieldsetPosition(this._fieldSet) + 1;
         let msg: string;
         if (this.childrenToAdd === 1) {
-            msg = sprintf(this.i18nService.localizeText("INFO_FSC_FS_COPIED"), pos);
+            msg = sprintf(this.i18nService.localizeText("INFO_DEVICE_COPIED"), pos);
         } else {
-            msg = sprintf(this.i18nService.localizeText("INFO_FSC_FS_COPIED_N_TIMES"), pos, this.childrenToAdd);
+            msg = sprintf(this.i18nService.localizeText("INFO_DEVICE_COPIED_N_TIMES"), pos, this.childrenToAdd);
         }
         this.notifService.notify(msg);
         this.childrenToAdd = 1; // reinit to avoid confusion
@@ -372,7 +359,7 @@ export class FieldSetComponent implements DoCheck {
         const pos = (this._fieldSet.parent as FieldsetContainer).getFieldsetPosition(this._fieldSet) + 1;
         this.removeFieldset.emit(this._fieldSet);
         this.notifService.notify(
-            sprintf(this.i18nService.localizeText("INFO_FSC_FS_REMOVED"), pos)
+            sprintf(this.i18nService.localizeText("INFO_DEVICE_REMOVED"), pos)
         );
     }
 
@@ -383,7 +370,7 @@ export class FieldSetComponent implements DoCheck {
         const pos = (this._fieldSet.parent as FieldsetContainer).getFieldsetPosition(this._fieldSet) + 1;
         this.moveFieldsetUp.emit(this._fieldSet);
         this.notifService.notify(
-            sprintf(this.i18nService.localizeText("INFO_FSC_FS_MOVED"), pos)
+            sprintf(this.i18nService.localizeText("INFO_DEVICE_MOVED"), pos)
         );
     }
 
@@ -394,7 +381,7 @@ export class FieldSetComponent implements DoCheck {
         const pos = (this._fieldSet.parent as FieldsetContainer).getFieldsetPosition(this._fieldSet) + 1;
         this.moveFieldsetDown.emit(this._fieldSet);
         this.notifService.notify(
-            sprintf(this.i18nService.localizeText("INFO_FSC_FS_MOVED"), pos)
+            sprintf(this.i18nService.localizeText("INFO_DEVICE_MOVED"), pos)
         );
     }
 }
diff --git a/src/app/components/fixedvar-results/var-results.component.ts b/src/app/components/fixedvar-results/var-results.component.ts
index 7a97e80c6028145b7443e1cb61c1b9ce9df197f2..424e434239f081897fa449828c155dfe2fe6698e 100644
--- a/src/app/components/fixedvar-results/var-results.component.ts
+++ b/src/app/components/fixedvar-results/var-results.component.ts
@@ -72,7 +72,7 @@ export class VarResultsComponent extends ResultsComponent {
             }
             this._headers = this._headers.concat(this._varResults.extraResultHeaders);
 
-            // C. pre-extract variable parameters valueslet longest = 0;
+            // C. pre-extract variable parameters values
             const varValues = [];
             // find longest list
             this.size = 0;
diff --git a/src/app/components/generic-calculator/calculator.component.html b/src/app/components/generic-calculator/calculator.component.html
index 4b518f5ab96f9dc279ed229bb9f4cd42c4c44842..f94d21d49c1b4d86244131ce3de674a7dcf9940f 100644
--- a/src/app/components/generic-calculator/calculator.component.html
+++ b/src/app/components/generic-calculator/calculator.component.html
@@ -28,19 +28,30 @@
             <!-- nom du module de calcul -->
             <calc-name id="calculator-name" [title]="uitextCalculatorName"></calc-name>
 
-            <div id="calc-cards-container" class="container" fxLayout="row wrap" fxLayoutAlign="space-around start">
+            <div id="calc-cards-container" class="container"
+              [fxLayout]="isPAB ? 'column' : 'row wrap'"
+              [fxLayoutAlign]="isPAB ? 'space-around stretch' : 'space-around start'">
+
                 <!-- chapitres -->
-                <mat-card id="calc-card-field-sets" fxFlex.gt-xs="1 0 400px" fxFlex.lt-sm="1 0 300px">
+                <mat-card id="calc-card-field-sets"
+                  [class.pab-field-sets]="isPAB"
+                  [fxFlex.gt-xs]="isPAB ? '1 0 auto' : '1 0 400px'"
+                  [fxFlex.lt-sm]="isPAB ? '1 0 auto' : '1 0 300px'">
+
                     <ng-template ngFor let-fe [ngForOf]="formElements">
                         <field-set *ngIf="isFieldset(fe)" [style.display]="getFieldsetStyleDisplay(fe.id)" [fieldSet]=fe
-                            (radio)=onRadioClick($event) (validChange)=OnFieldsetValid() (inputChange)=onInputChange($event)
+                            (radio)=onRadioClick($event) (validChange)=onElementValid() (inputChange)=onInputChange($event)
                             (tabPressed)="onTabPressed($event)">
                         </field-set>
 
                         <fieldset-container *ngIf="isFieldsetContainer(fe)" [_container]=fe (radio)=onRadioClick($event)
-                            (validChange)=onFieldsetContainerValid() (inputChange)=onInputChange($event)
+                            (validChange)=onElementValid() (inputChange)=onInputChange($event)
                             (tabPressed)="onTabPressed($event)">
                         </fieldset-container>
+
+                        <pab-table *ngIf="isPabTable(fe)" [pabTable]=fe (radio)=onRadioClick($event)
+                            (validChange)=onElementValid() (inputChange)=onInputChange($event)>
+                        </pab-table>
                     </ng-template>
 
                     <mat-card-actions>
@@ -52,14 +63,19 @@
                 </mat-card>
 
                 <!-- résultats -->
-                <mat-card id="calc-card-results" fxFlex.gt-xs="1 0 400px" fxFlex.lt-sm="1 0 300px">
+                <mat-card id="calc-card-results"
+                  [class.pab-results]="isPAB"
+                  [fxFlex.gt-xs]="isPAB ? '1 0 auto' : '1 0 400px'"
+                  [fxFlex.lt-sm]="isPAB ? '1 0 auto' : '1 0 300px'">
+
                     <div id="fake-results-anchor"></div>
                     <mat-card-header>
                         <mat-card-title>
                             <h1 [innerHTML]="uitextResultsTitle"></h1>
                         </mat-card-title>
                         <div fxFlex></div>
-                        <button mat-raised-button color="accent" id="generate-pab" *ngIf="isPABCloisons" (click)="generatePAB()">
+                        <button mat-raised-button color="accent" id="generate-pab" *ngIf="isPABCloisons" (click)="generatePAB()"
+                            [disabled]="! generatePABEnabled" [title]="uitextGeneratePabTitle">
                             {{ uitextGeneratePAB }}
                         </button>
                     </mat-card-header>
diff --git a/src/app/components/generic-calculator/calculator.component.scss b/src/app/components/generic-calculator/calculator.component.scss
index c9cbd66b283bf1f3543a3101b9c9dcf91a80ce05..e1e07ee877c8930fded6cb3a3ce2e3f29f5d4330 100644
--- a/src/app/components/generic-calculator/calculator.component.scss
+++ b/src/app/components/generic-calculator/calculator.component.scss
@@ -38,6 +38,10 @@ mat-card {
         margin-left: 1em;
         margin-right: 1em;
 
+        &.pab-field-sets {
+            margin-right: -1em;
+        }
+
         mat-card-actions {
             text-align: center;
 
@@ -51,8 +55,12 @@ mat-card {
         margin-left: 1em;
         margin-right: 1em;
 
+        &.pab-results {
+            margin-right: -1em;
+        }
+
         mat-card-header {
-            margin-bottom: 1em;
+            // margin-bottom: 1em;
 
             #generate-pab {
                 height: min-content;
diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index 67b856e1f70cc84e07f725a449ba3d7a440b6ed5..48e622a72859780b76fe5cff84109301cdbcb6b2 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -2,7 +2,7 @@ import { Component, OnInit, DoCheck, OnDestroy, ViewChild, ViewChildren,
          QueryList, AfterViewChecked, ElementRef } from "@angular/core";
 import { ActivatedRoute, Router } from "@angular/router";
 
-import { Observer, Session, Cloisons, Pab, PabCloisons, ParamValueMode, CalculatorType } from "jalhyd";
+import { Observer, Session, Cloisons, Pab, ParamValueMode, CalculatorType } from "jalhyd";
 
 import { FormulaireService } from "../../services/formulaire/formulaire.service";
 import { I18nService } from "../../services/internationalisation/internationalisation.service";
@@ -16,11 +16,12 @@ import { CalculatorNameComponent } from "./calc-name.component";
 import { FormulaireElement } from "../../formulaire/formulaire-element";
 import { FieldsetContainer } from "../../formulaire/fieldset-container";
 import { FieldsetContainerComponent } from "../fieldset-container/fieldset-container.component";
+import { PabTableComponent } from "../pab-table/pab-table.component";
 import { ServiceFactory } from "../../services/service-factory";
 import { MatDialog } from "@angular/material";
 import { DialogConfirmCloseCalcComponent } from "../dialog-confirm-close-calc/dialog-confirm-close-calc.component";
 import { DialogGeneratePABComponent } from "../dialog-generate-pab/dialog-generate-pab.component";
-import { SelectFieldModel } from "../../formulaire/select-field-model";
+import { PabTable } from "../../formulaire/pab-table";
 
 @Component({
     selector: "hydrocalc",
@@ -40,6 +41,12 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
     @ViewChildren(FieldsetContainerComponent)
     private _fieldsetContainerComponents: QueryList<FieldsetContainerComponent>;
 
+    /**
+     * PabTableComponent if any
+     */
+    @ViewChild(PabTableComponent)
+    private _pabTableComponent: PabTableComponent;
+
     /**
      * composant d'affichage des résultats
      */
@@ -131,6 +138,13 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
         return fe instanceof FieldsetContainer;
     }
 
+    /**
+     * détermine si un FormulaireElement est du type PabTable
+     */
+    public isPabTable(fe: any): boolean {
+        return fe instanceof PabTable;
+    }
+
     public get hasForm() {
         return this._formulaire !== undefined;
     }
@@ -328,6 +342,7 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
                 // valeur initiale
                 , this._fieldsetComponents.length > 0);
         }
+
         if (this._fieldsetContainerComponents !== undefined) {
             this._isUIValid = this._isUIValid && this._fieldsetContainerComponents.reduce(
                 // callback
@@ -346,6 +361,10 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
                 // valeur initiale
                 , true);
         }
+
+        if (this._pabTableComponent !== undefined) {
+            this._isUIValid = this._isUIValid && this._pabTableComponent.isValid;
+        }
     }
 
     public getFieldsetStyleDisplay(id: string) {
@@ -354,16 +373,9 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
     }
 
     /**
-     * réception d'un événement de validité d'un FieldSetComponent
-     */
-    public OnFieldsetValid() {
-        this.updateUIValidity();
-    }
-
-    /**
-     * réception d'un événement de validité d'un FieldsetContainerComponent
+     * réception d'un événement de validité d'un FormElement
      */
-    public onFieldsetContainerValid() {
+    public onElementValid() {
         this.updateUIValidity();
     }
 
@@ -425,6 +437,16 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
         return false;
     }
 
+    // for "one wide column" layout
+    public get isPAB() {
+        return (
+            this._formulaire
+            && this._formulaire.currentNub
+            && this._formulaire.currentNub.calcType === CalculatorType.Pab
+        );
+    }
+
+    // for "generate PAB" button
     public get isPABCloisons() {
         return (
             this._formulaire
@@ -433,6 +455,22 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
         );
     }
 
+    // PAB generation is enabled only if no parameter is varying
+    public get generatePABEnabled(): boolean {
+        let ret = true;
+        for (const p of this._formulaire.currentNub.parameterIterator) {
+            ret = ret && (! p.hasMultipleValues);
+        }
+        return ret;
+    }
+
+    public get uitextGeneratePabTitle() {
+        if (! this.generatePABEnabled) {
+            return this.intlService.localizeText("INFO_PAB_PARAMETRES_FIXES");
+        }
+        return "";
+    }
+
     /**
      * Génère une passe à bassins à partir du modèle de cloisons en cours
      */
@@ -443,9 +481,9 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
           DialogGeneratePABComponent,
           {
             data: {
-                chute: cloisons.prms.DH.singleValue,
-                debit: cloisons.prms.Q.singleValue,
-                coteAmont: cloisons.prms.Z1.singleValue
+                chute: cloisons.V(cloisons.prms.DH),
+                debit: cloisons.V(cloisons.prms.Q),
+                coteAmont: cloisons.V(cloisons.prms.Z1)
             },
             disableClose: true
           }
@@ -461,21 +499,8 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
                 params.Z1.singleValue = result.coteAmont;
                 params.Z2.singleValue = result.coteAval;
                 // création des bassins
-                for (let i = 0; i < result.nbBassins; i++) {
-                    const pabCloisons = new PabCloisons(this._formulaire.currentNub as Cloisons);
-                    pab.addChild(pabCloisons); // @TODO should be replace afterwards
-                    for (const e of f.allFormElements) {
-                        if (e instanceof FieldsetContainer) {
-                            const newFieldset = e.addFromTemplate(0, undefined, pabCloisons);
-                            // set selected value by ID; nub should be set by "select value changed" event listener
-                            const modeleSelect = (newFieldset.getFormulaireNodeById("select_modele_cloisons") as SelectFieldModel);
-                            modeleSelect.updateEntries();
-                            // ID of the Cloisons nub used by pabCloisons as a model
-                            modeleSelect.setValueFromId(this._formulaire.currentNub.uid);
-                            break;
-                        }
-                    }
-                }
+                pab.deleteChild(0);
+                pab.addCloisonsFromModel(this._formulaire.currentNub as Cloisons, result.nbBassins);
                 // go to new PAB
                 this.router.navigate(["/calculator", f.uid]);
               });
diff --git a/src/app/components/generic-input/generic-input.component.ts b/src/app/components/generic-input/generic-input.component.ts
index 4b23185d6d9738661fc67c44ce48c99f6e9742f5..19232853b2f118725cbeebfc2d25bbb18fadbe41 100644
--- a/src/app/components/generic-input/generic-input.component.ts
+++ b/src/app/components/generic-input/generic-input.component.ts
@@ -1,7 +1,7 @@
 import { Input, Output, EventEmitter, ChangeDetectorRef, OnChanges, ViewChild } from "@angular/core";
 import { NgModel } from "@angular/forms";
 import { BaseComponent } from "../base/base.component";
-import { isNumeric, Structure, PabCloisons } from "jalhyd";
+import { isNumeric, Structure, Pab } from "jalhyd";
 import { FormulaireDefinition } from "../../formulaire/definition/form-definition";
 import { NgParameter } from "../../formulaire/ngparam";
 import { I18nService } from "../../services/internationalisation/internationalisation.service";
@@ -46,7 +46,7 @@ export abstract class GenericInputComponent extends BaseComponent implements OnC
                 // if inside a nested Structure, prefix with Structure position
                 // to disambiguate
                 const nub = param.paramDefinition.parentNub;
-                if (nub && (nub instanceof Structure || nub instanceof PabCloisons)) {
+                if (nub && (nub instanceof Structure || nub.parent instanceof Pab)) {
                     id = nub.findPositionInParent() + "_" + id;
                 }
             }
diff --git a/src/app/components/log/log.component.ts b/src/app/components/log/log.component.ts
index 6ab7883505321926fb9209f9a00bfe8c9d5938f0..c84de036410d6effe1a9c82f088b12bc6e77579b 100644
--- a/src/app/components/log/log.component.ts
+++ b/src/app/components/log/log.component.ts
@@ -1,4 +1,4 @@
-import { Component } from "@angular/core";
+import { Component, Input } from "@angular/core";
 
 import { cLog, Message } from "jalhyd";
 
@@ -17,12 +17,20 @@ export class LogComponent {
      */
     private _log: cLog;
 
+    // title to display above the log
+    @Input()
+    public logTitle: string;
+
     constructor(
         private intlService: I18nService,
     ) { }
 
     private get uitextTitreJournal() {
-        return this.intlService.localizeText("INFO_TITREJOURNAL");
+        if (this.logTitle) {
+            return this.logTitle;
+        } else {
+            return this.intlService.localizeText("INFO_TITREJOURNAL");
+        }
     }
 
     public get hasEntries(): boolean {
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 0000000000000000000000000000000000000000..28b4288fd2bbf48bcedac4e7a4ed9375a2f91cc1
--- /dev/null
+++ b/src/app/components/pab-profile-graph/pab-profile-graph.component.html
@@ -0,0 +1,21 @@
+<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)="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)" [title]="uitextEnterFSTitle">
+                <mat-icon color="primary" class="scaled12">fullscreen</mat-icon>
+            </button>
+            <button mat-icon-button *ngIf="isFullscreen" (click)="exitFullscreen()" [title]="uitextExitFSTitle">
+                <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>
\ 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
new file mode 100644
index 0000000000000000000000000000000000000000..465866a6ce03ef7eaa710c1e4545563c44d2e9dd
--- /dev/null
+++ b/src/app/components/pab-profile-graph/pab-profile-graph.component.scss
@@ -0,0 +1,29 @@
+.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);
+            }
+        }
+
+        &: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
new file mode 100644
index 0000000000000000000000000000000000000000..a6689110f4804d3782793e66a5707f2783fb7585
--- /dev/null
+++ b/src/app/components/pab-profile-graph/pab-profile-graph.component.ts
@@ -0,0 +1,369 @@
+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";
+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 {
+
+    @ViewChild(ChartComponent)
+    private chartComponent;
+
+    private _results: PabResults;
+
+    /** size of the longest variable value */
+    private size = 0;
+
+    /** inferred extended values list for each variating parameter */
+    private varValues = [];
+
+    private _zoomWasChanged = false;
+
+    /*
+     * config du graphe
+     */
+    public graph_data: { datasets: any[] };
+    public graph_options = {
+        responsive: true,
+        maintainAspectRatio: true,
+        animation: {
+            duration: 0
+        },
+        legend: {
+            display: true,
+            position: "bottom",
+            reverse: false
+        },
+        title: {
+            display: true,
+            text: this.intlService.localizeText("INFO_PAB_TITRE_PROFIL")
+        },
+        elements: {
+            line: {
+                tension: 0
+            }
+        }
+    };
+
+    public constructor(
+        private appSetupService: ApplicationSetupService,
+        private intlService: I18nService,
+        private cd: ChangeDetectorRef
+    ) {
+        super();
+        const nDigits = this.appSetupService.displayDigits;
+        // do not move following block out of constructor or scale labels won't be rendered
+        this.graph_options["scales"] = {
+            xAxes: [{
+                type: "linear",
+                position: "bottom",
+                ticks: {
+                    precision: nDigits
+                },
+                scaleLabel: {
+                    display: true,
+                    labelString: this.intlService.localizeText("INFO_LIB_DISTANCE_AMONT")
+                }
+            }],
+            yAxes: [{
+                type: "linear",
+                position: "left",
+                ticks: {
+                    precision: nDigits
+                },
+                scaleLabel: {
+                    display: true,
+                    labelString: this.intlService.localizeText("INFO_LIB_COTE")
+                }
+            }]
+        };
+        // 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: { // conflicts with pan; set to false to enable mouse wheel zoom,
+                        borderColor: "rgba(225,225,225,0.3)",
+                        borderWidth: 1,
+                        backgroundColor: "rgba(0,0,0,0.25)"
+                    },
+                    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) {
+        this._results = r;
+
+        // pre-extract variable parameters values
+        if (this._results) {
+            this.varValues = [];
+            const nDigits = this.appSetupService.displayDigits;
+            // find longest list
+            this.size = 0;
+            for (let i = 0; i < this._results.variatedParameters.length; i++) {
+                const vs = this._results.variatedParameters[i].valuesIterator.count();
+                if (vs > this.size) {
+                    this.size = vs;
+                }
+            }
+            // get extended values lists for each variable parameter
+            for (const v of this._results.variatedParameters) {
+                const vv = [];
+                const iter = v.getExtendedValuesIterator(this.size);
+                while (iter.hasNext) {
+                    const nv = iter.next();
+                    vv.push(nv.value.toFixed(nDigits));
+                }
+                this.varValues.push(vv);
+            }
+        }
+    }
+
+    private zoomComplete() {
+        this._zoomWasChanged = true;
+        this.cd.detectChanges();
+    }
+
+    public get zoomWasChanged(): boolean {
+        return this._zoomWasChanged;
+    }
+
+    public updateView() {
+        this.generateScatterGraph();
+    }
+
+    /**
+     * génère les données d'un graphe de type "scatter"
+     */
+    private generateScatterGraph() {
+        const ySeries = this.getYSeries();
+
+        this.graph_data = {
+            datasets: []
+        };
+
+        // 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"
+            });
+        }
+    }
+
+    public exportAsImage(element: HTMLDivElement) {
+        const canvas: HTMLCanvasElement = element.querySelector("canvas");
+        canvas.toBlob((blob) => {
+            saveAs(blob, "chart.png");
+        }); // 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;
+        // 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.appSetupService.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: this.intlService.localizeText("INFO_LIB_RADIER"),
+            color: "#808080"
+        });
+
+        // 2. séries
+        const nbSeries = this._results.cloisonsResults[0].resultElements.length;
+        const palette = this.distinctColors;
+
+        seriesLoop:
+        for (let n = 0; n < nbSeries; n++) {
+            // --------- build nth series ---------
+            const dataN: { x: string, y: string }[] = [];
+            let i = 0; // abscissa index
+
+            // if this iteration has errors, do not try to draw it
+            if (this._results.iterationHasError(n)) {
+                continue seriesLoop;
+            }
+
+            // 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: (
+                    this._results.variatedParameters.length > 0 ?
+                    this.getLegendForSeries(n) :
+                    this.intlService.localizeText("INFO_LIB_LIGNE_D_EAU")
+                ),
+                color: palette[ n % palette.length ]
+            });
+        }
+
+        return ret;
+    }
+
+    /**
+     * Returns a label showing the boundary conditions values for
+     * the given iteration
+     * @param n index of the variating parameter(s) iteration
+     */
+    private getLegendForSeries(n: number): string {
+        let i = 0;
+        return this.varValues.map((vv) => {
+            const vp = this._results.variatedParameters[i];
+            i++;
+            let value = "0";
+            value = vv[n];
+            return `${vp.symbol} = ${value}`;
+        }).join(", ");
+    }
+
+    /**
+     * 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-table.component.html b/src/app/components/pab-results/pab-results-table.component.html
index 4a86d0a987436e5a49b4ae273a74ba8de11c90fa..ce1029faef169b64fc08b8bb7215bf0ea99ec43a 100644
--- a/src/app/components/pab-results/pab-results-table.component.html
+++ b/src/app/components/pab-results/pab-results-table.component.html
@@ -1,5 +1,5 @@
 <!-- @TODO copied from var-results.component.html > merge ?-->
-<div class="pab-results-table-container" #pabResultsTable *ngIf="hasResults" fxLayout="row wrap" fxLayoutAlign="center center">
+<div class="pab-results-table-container" #pabResultsTable fxLayout="row wrap" fxLayoutAlign="center center">
     <div fxFlex="1 1 100%">
         <div class="pab-results-table-buttons">
             <button mat-icon-button (click)="exportAsSpreadsheet()">
diff --git a/src/app/components/pab-results/pab-results-table.component.scss b/src/app/components/pab-results/pab-results-table.component.scss
index 7e227e04eb8446b28b1a55995311e77330982856..ea1d83f80958f4d7b394b8706813e254fe895167 100644
--- a/src/app/components/pab-results/pab-results-table.component.scss
+++ b/src/app/components/pab-results/pab-results-table.component.scss
@@ -3,7 +3,6 @@
 }
 
 .pab-results-table-container {
-    margin-top: 2em;
     background-color: white;
 }
 
diff --git a/src/app/components/pab-results/pab-results-table.component.ts b/src/app/components/pab-results/pab-results-table.component.ts
index af0ebe4df7a8dc6e4f06aa33fbc616650b69172d..3080acf1f7415f4c7734b20dc262fc7765ea1b55 100644
--- a/src/app/components/pab-results/pab-results-table.component.ts
+++ b/src/app/components/pab-results/pab-results-table.component.ts
@@ -1,8 +1,10 @@
 import { Component, ViewChild, ElementRef } from "@angular/core";
 
-import { PabResults } from "../../results/pab-results";
+import { CloisonAval } from "jalhyd";
 
 import * as XLSX from "xlsx";
+
+import { PabResults } from "../../results/pab-results";
 import { ApplicationSetupService } from "../../services/app-setup/app-setup.service";
 import { I18nService } from "../../services/internationalisation/internationalisation.service";
 import { ResultsComponent } from "../fixedvar-results/results.component";
@@ -42,89 +44,79 @@ export class PabResultsTableComponent extends ResultsComponent {
             this._pabResults
             && this._pabResults.cloisonsResults
             && this._pabResults.cloisonsResults.length > 0
-            && ! this._pabResults.hasError()
+            && ! this._pabResults.hasOnlyErrors()
         ) {
             const pr = this._pabResults;
             const nDigits = this.appSetupService.displayDigits;
+            // when a parameter is variating, index of the variating parameter
+            // values to build the data from
+            const vi = pr.variableIndex;
 
             // refresh headers here if language changed
             this._headers = pr.headers;
 
             // line 1
-            if (pr.cloisonsResults[0].vCalc) { // parfois le calcul des cloisons échoue
+            if (pr.cloisonsResults[0].resultElements[vi].vCalc) { // parfois le calcul des cloisons échoue
                 this._dataSet.push([
-                    "",
-                    pr.cloisonsResults[0] ? pr.cloisonsResults[0].vCalc.toFixed(nDigits) : "",
+                    this.intlService.localizeText("INFO_LIB_AMONT"),
+                    pr.cloisonsResults[0].resultElements[vi] ? pr.cloisonsResults[0].resultElements[vi].vCalc.toFixed(nDigits) : "",
                     "", "", "", "", "", "", ""
                 ]);
             }
 
             // lines 2 - n-1
-            for (let i = 0; i < pr.cloisonsResults.length - 1; i++) {
+            for (let i = 0; i < pr.cloisonsResults.length; i++) {
                 if (
-                    pr.cloisonsResults[i].vCalc
-                    && pr.cloisonsResults[i + 1].vCalc
+                    pr.cloisonsResults[i].resultElements[vi].vCalc
                 ) {
-                    const ZRAM = pr.cloisonsResults[i].getExtraResult("ZRAM");
-                    const PV = pr.cloisonsResults[i].getExtraResult("PV");
-                    const YMOY = pr.cloisonsResults[i].getExtraResult("YMOY");
-                    const ZRB = pr.cloisonsResults[i].getExtraResult("ZRB");
+                    const r2n = pr.cloisonsResults[i].resultElements[vi].extraResults;
+                    let Z1: number;
+                    if (i < pr.cloisonsResults.length - 1) {
+                        Z1 = pr.cloisonsResults[i + 1].resultElements[vi].vCalc;
+                    } else {
+                        Z1 = pr.cloisonAvalResults.resultElements[vi].vCalc;
+                    }
                     this._dataSet.push([
                         i + 1, // n° cloison
-                        pr.cloisonsResults[i + 1].vCalc.toFixed(nDigits), // Z
-                        (ZRAM !== undefined) ? ZRAM.toFixed(nDigits) : "", // ZRAM
-                        (pr.cloisonsResults[i].vCalc - pr.cloisonsResults[i + 1].vCalc).toFixed(nDigits), // DH
-                        pr.cloisonsQ[i].toFixed(nDigits), // Q
-                        (PV !== undefined) ? PV.toFixed(nDigits) : "", // PV
-                        (YMOY !== undefined) ? YMOY.toFixed(nDigits) : "", // YMOY
-                        (ZRB !== undefined) ? ZRB.toFixed(nDigits) : "", // ZRB
-                        pr.bassinsQA[i] // QA
+                        Z1.toFixed(nDigits), // Z
+                        r2n.ZRAM.toFixed(nDigits),
+                        r2n.DH.toFixed(nDigits),
+                        r2n.Q.toFixed(nDigits),
+                        r2n.PV.toFixed(nDigits),
+                        r2n.YMOY.toFixed(nDigits),
+                        r2n.ZRMB.toFixed(nDigits),
+                        r2n.QA // QA
                     ]);
                 }
             }
 
-            // line n
-            const l = pr.cloisonsResults.length;
-            if (
-                pr.cloisonsResults[l - 1].vCalc
-                && pr.cloisonAvalResults.vCalc
-            ) {
-                const nZRAM = pr.cloisonsResults[l - 1].getExtraResult("ZRAM");
-                const nPV = pr.cloisonsResults[l - 1].getExtraResult("PV");
-                const nY = pr.cloisonsResults[l - 1].getExtraResult("YMOY");
-                const nZRB = pr.cloisonsResults[l - 1].getExtraResult("ZRB");
-                this._dataSet.push([
-                    l, // n° cloison
-                    pr.cloisonAvalResults.vCalc.toFixed(nDigits), // Z
-                    (nZRAM !== undefined) ? nZRAM.toFixed(nDigits) : "", // ZRAM
-                    (pr.cloisonsResults[l - 1].vCalc - pr.cloisonAvalResults.vCalc).toFixed(nDigits), // DH
-                    pr.cloisonsQ[l - 1].toFixed(nDigits), // Q
-                    (nPV !== undefined) ? nPV.toFixed(nDigits) : "", // PV
-                    (nY !== undefined) ? nY.toFixed(nDigits) : "", // YMOY
-                    (nZRB !== undefined) ? nZRB.toFixed(nDigits) : "", // ZRB
-                    pr.bassinsQA[l - 1] // QA
-                ]);
-            }
-
             // downstream line
-            if (pr.cloisonAvalResults.vCalc) {
-                const caZRAM = pr.cloisonAvalResults.getExtraResult("ZRAM");
+            if (pr.cloisonAvalResults.resultElements[vi].vCalc) {
+                const rln = pr.cloisonAvalResults.resultElements[vi].extraResults;
                 this._dataSet.push([
-                    this.intlService.localizeText("INFO_LIB_AVAL"), // n° cloison
-                    pr.Z2.toFixed(nDigits), // Z
-                    (caZRAM !== undefined) ? caZRAM.toFixed(nDigits) : "", // ZRAM
-                    (pr.cloisonAvalResults.vCalc - pr.Z2).toFixed(nDigits), // DH
-                    pr.cloisonAvalQ.toFixed(nDigits), // Q
+                    this.intlService.localizeText("INFO_LIB_AVAL"),
+                    pr.Z2[vi].toFixed(nDigits),
+                    rln.ZRAM.toFixed(nDigits),
+                    rln.DH.toFixed(nDigits),
+                    rln.Q.toFixed(nDigits),
                     "", "", "", ""
                 ]);
+                // extra lift gate ?
+                const cloisonAval = (pr.cloisonAvalResults.sourceNub as CloisonAval);
+                if (cloisonAval && cloisonAval.hasVanneLevante()) {
+                    const vanneZDV = cloisonAval.result.resultElements[vi].getExtraResult("ZDV");
+                    if (vanneZDV) {
+                        this._dataSet.push([
+                            this.intlService.localizeText("INFO_LIB_COTE_VANNE_LEVANTE"),
+                            vanneZDV.toFixed(nDigits),
+                            "", "", "", "", "", "", ""
+                        ]);
+                    }
+                }
             }
         }
     }
 
-    public get hasResults(): boolean {
-        return this._pabResults && this._pabResults.hasResults;
-    }
-
     public get headers() {
         return this._headers;
     }
diff --git a/src/app/components/pab-results/pab-results.component.html b/src/app/components/pab-results/pab-results.component.html
index 9fcc9927cd0a5b351b994c0585577205d89f36ce..fbb8e9c23fc4bee8ffaae0d19b1aaa82cf7ad5fb 100644
--- a/src/app/components/pab-results/pab-results.component.html
+++ b/src/app/components/pab-results/pab-results.component.html
@@ -1,11 +1,22 @@
 <div class="container">
-    <!-- journal -->
-    <log></log>
 
-    <results-graph *ngIf="hasResults"></results-graph>
+    <log #generalLog [logTitle]="uitextGeneralLogTitle">log général</log>
+
+    <pab-variable-results-selector [results]="pabResults" (indexChange)="variableIndexChanged()">
+    </pab-variable-results-selector>
+
+    <log #iterationLog></log>
 
     <div>
         <!-- tableau de résultats -->
-        <pab-results-table [results]="pabResults"></pab-results-table>
+        <pab-results-table *ngIf="hasDisplayableResults" [results]="pabResults"></pab-results-table>
     </div>
+
+    <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 0000000000000000000000000000000000000000..6f5eb10d525664ef64b09c7c5eda9a8a074218b1
--- /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 d17318d768e48b1b73eb202b9c32f6c61891eeaf..ff73e62cbe9c14ca7c584acf7be3e4a19566cb11 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 } from "jalhyd";
+import { Result, cLog, Message, MessageCode, MessageSeverity } from "jalhyd";
 
 import { LogComponent } from "../../components/log/log.component";
 import { CalculatorResults } from "../../results/calculator-results";
@@ -8,15 +8,18 @@ import { NgParameter } from "../../formulaire/ngparam";
 import { ApplicationSetupService } from "../../services/app-setup/app-setup.service";
 import { PabResultsTableComponent } from "./pab-results-table.component";
 import { PabResults } from "../../results/pab-results";
-import { ResultsGraphComponent } from "../results-graph/results-graph.component";
+import { PabVariableResultsSelectorComponent } from "./pab-variable-results-selector.component";
 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 {
@@ -24,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 */
@@ -33,16 +36,24 @@ export class PabResultsComponent implements DoCheck {
     @ViewChild(PabResultsTableComponent)
     private pabResultsTableComponent: PabResultsTableComponent;
 
-    /** composant journal */
-    @ViewChild(LogComponent)
-    private logComponent: LogComponent;
+    @ViewChild(PabVariableResultsSelectorComponent)
+    private pabVariableResultsSelectorComponent: PabVariableResultsSelectorComponent;
+
+    @ViewChild("generalLog")
+    private generalLogComponent: LogComponent;
+
+    @ViewChild("iterationLog")
+    private iterationLogComponent: LogComponent;
 
-    /** graphique dans le cas d'un paramètre à varier */
     @ViewChild(ResultsGraphComponent)
     private resultsGraphComponent: ResultsGraphComponent;
 
+    @ViewChild(PabProfileGraphComponent)
+    private profileGraphComponent: PabProfileGraphComponent;
+
     constructor(
         private appSetupService: ApplicationSetupService,
+        private i18nService: I18nService,
     ) {
         this._plottableResults = new PlottablePabResults();
     }
@@ -55,14 +66,30 @@ export class PabResultsComponent implements DoCheck {
         this.updateView();
     }
 
+    /**
+     * update results table and chart when the variable index changed (event sent by
+     * PabVariableResultsSelectorComponent); variable index is already set in
+     * pabResults at this time
+     */
+    public variableIndexChanged() {
+        this.updateView();
+    }
+
     public updateView() {
-        this.logComponent.log = undefined;
+        this.iterationLogComponent.log = undefined;
+        this.generalLogComponent.log = undefined;
         if (this.pabResultsTableComponent) {
             this.pabResultsTableComponent.results = undefined;
         }
+        if (this.pabVariableResultsSelectorComponent) {
+            this.pabVariableResultsSelectorComponent.results = undefined;
+        }
         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) {
@@ -76,28 +103,164 @@ export class PabResultsComponent implements DoCheck {
         }
     }
 
-    private mergeLog(result: Result, log: cLog) {
-        if (result && result.hasLog) {
+    private mergeGlobalLog(result: Result, log: cLog) {
+        if (result) {
             if (result.hasGlobalLog) {
                 log.addLog(result.globalLog);
-            } else {
-                log.addLog(result.log);
+            }
+            // if no parameter is varying, 1st element log is considered "global"
+            if (this.pabResults.variatedParameters.length === 0) {
+                if (result.hasResultElements && result.resultElement.hasLog) {
+                    log.addLog(result.log);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the number of errors, warnings, infos among children logs
+     */
+    private logStats(): any {
+        const ret = {
+            info: 0,
+            warning: 0,
+            error: 0
+        };
+        if (this._pabResults.result && this._pabResults.result.hasLog) {
+            for (const re of this._pabResults.result.resultElements) {
+                if (re.hasLog) {
+                    for (const m of re.log.messages) {
+                        const s = m.getSeverity();
+                        switch (s) {
+                            case MessageSeverity.INFO:
+                                ret.info ++;
+                                break;
+                            case MessageSeverity.WARNING:
+                                ret.warning ++;
+                                break;
+                            case MessageSeverity.ERROR:
+                                ret.error ++;
+                                break;
+                        }
+                    }
+                }
+            }
+        }
+        for (const cr of this._pabResults.cloisonsResults) {
+            if (cr && cr.hasLog) {
+                for (const re of cr.resultElements) {
+                    if (re.hasLog) {
+                        for (const m of re.log.messages) {
+                            const s = m.getSeverity();
+                            switch (s) {
+                                case MessageSeverity.INFO:
+                                    ret.info ++;
+                                    break;
+                                case MessageSeverity.WARNING:
+                                    ret.warning ++;
+                                    break;
+                                case MessageSeverity.ERROR:
+                                    ret.error ++;
+                                    break;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        if (this._pabResults.cloisonAvalResults && this._pabResults.cloisonAvalResults.hasLog) {
+            for (const re of this._pabResults.cloisonAvalResults.resultElements) {
+                if (re.hasLog) {
+                    for (const m of re.log.messages) {
+                        const s = m.getSeverity();
+                        switch (s) {
+                            case MessageSeverity.INFO:
+                                ret.info ++;
+                                break;
+                            case MessageSeverity.WARNING:
+                                ret.warning ++;
+                                break;
+                            case MessageSeverity.ERROR:
+                                ret.error ++;
+                                break;
+                        }
+                    }
+                }
             }
         }
+        return ret;
+    }
+
+    /*
+     * Retourne les logs à afficher dans le composant de log global, au dessus
+     * du sélecteur d'itération : messages globaux et / ou résumé des messages
+     * spécifiques à chaque ResultElement
+     */
+    private get globalLog(): cLog {
+        const l = new cLog();
+        if (this._pabResults && this.pabResults.variatedParameters.length > 0) {
+            this.mergeGlobalLog(this._pabResults.result, l);
+            // un problème avec la PAB en général / les cloisons, à une étape quelconque ?
+            if (
+                (this.pabResults.hasLog)
+                && l.messages.length === 0 // existing global messages make generic message below useless
+            ) {
+                const logStats = this.logStats();
+                const m = new Message(MessageCode.WARNING_PROBLEMS_ENCOUNTERED);
+                m.extraVar.info = "" + logStats.info; // to avoid displaying fixed number of digits
+                m.extraVar.warning = "" + logStats.warning;
+                m.extraVar.error = "" + logStats.error;
+                l.add(m);
+                // l.add(new Message(MessageCode.WARNING_PROBLEMS_ENCOUNTERED));
+            }
+        } // sinon pas de log global (aucun paramètre ne varie)
+        return l;
     }
 
-    private get mergedGlobalLogs(): cLog {
-        const res = new cLog();
+    /**
+     * Retourne les logs à afficher dans le composant de log global, au dessus
+     * du sélecteur d'itération : messages globaux et / ou résumé des messages
+     * spécifiques à chaque ResultElement
+     */
+    private get iterationLog(): cLog {
+        const l = new cLog();
         if (this._pabResults) {
-            this.mergeLog(this._pabResults.result, res);
-            // log des cloisons
-            for (const c of this._pabResults.cloisonsResults) {
-                this.mergeLog(c, res);
+            if (this.pabResults.variatedParameters.length > 0) {
+                // A. si un paramètre varie
+                const vi = this._pabResults.variableIndex;
+                // log de la PAB pour l'itération en cours
+                if (
+                    this._pabResults.result
+                    && this._pabResults.result.hasResultElements
+                    && this._pabResults.result.resultElements[vi]
+                    && this._pabResults.result.resultElements[vi].hasLog
+                ) {
+                    l.addLog(this._pabResults.result.resultElements[vi].log);
+                }
+                // logs des enfants pour l'itération en cours
+                for (const cr of this._pabResults.cloisonsResults) {
+                    if (cr && cr.hasResultElements && cr.resultElements[vi].hasLog) {
+                        l.addLog(cr.resultElements[vi].log);
+                    }
+                }
+                if (this._pabResults.cloisonAvalResults && this._pabResults.cloisonAvalResults.resultElements[vi].hasLog) {
+                    l.addLog(this._pabResults.cloisonAvalResults.resultElements[vi].log);
+                }
+            } else {
+                // B. si aucun paramètre ne varie
+                this.mergeGlobalLog(this._pabResults.result, l); // faut bien mettre le log global quelque part
+                // logs des enfants
+                for (const cr of this._pabResults.cloisonsResults) {
+                    if (cr && cr.hasResultElements && cr.resultElement.hasLog) {
+                        l.addLog(cr.resultElement.log);
+                    }
+                }
+                if (this._pabResults.cloisonAvalResults && this._pabResults.cloisonAvalResults.resultElement.hasLog) {
+                    l.addLog(this._pabResults.cloisonAvalResults.resultElement.log);
+                }
             }
-            // log de la cloison aval
-            this.mergeLog(this._pabResults.cloisonAvalResults, res);
         }
-        return res;
+        return l;
     }
 
     /**
@@ -106,28 +269,45 @@ 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
+        const logUpdated = (this.iterationLogComponent !== undefined || this.generalLogComponent !== undefined); // gne ?
+        if (logUpdated) {
+            // order of logs is important !
+            this.iterationLogComponent.log = this.iterationLog;
+            this.generalLogComponent.log = this.globalLog;
+        }
+
         if (this.hasResults) {
             pabUpdated = this.pabResultsTableComponent !== undefined;
             if (pabUpdated) {
                 this.pabResultsTableComponent.results = this._pabResults;
             }
-            graphUpdated = this.resultsGraphComponent !== undefined;
-            if (graphUpdated) {
+            selectorUpdated = this.pabVariableResultsSelectorComponent !== undefined;
+            if (selectorUpdated) {
+                this.pabVariableResultsSelectorComponent.results = this._pabResults;
+            }
+            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;
-        }
-
-        const logUpdated = this.logComponent !== undefined;
-        if (logUpdated) {
-            this.logComponent.log = this.mergedGlobalLogs;
+            resultsGraphUpdated = true;
+            profileGraphUpdated = true;
+            selectorUpdated = true;
         }
 
-        return pabUpdated && logUpdated && graphUpdated;
+        return pabUpdated && logUpdated && resultsGraphUpdated && profileGraphUpdated && selectorUpdated;
     }
 
     public get pabResults() {
@@ -147,9 +327,29 @@ export class PabResultsComponent implements DoCheck {
         return this._pabResults && this._pabResults.hasResults;
     }
 
+    public get hasDisplayableResults(): boolean {
+        let ret = this._pabResults && this._pabResults.hasResults;
+        if (
+            this._pabResults
+            && this._pabResults.variatedParameters
+            && this._pabResults.variatedParameters.length > 0
+        ) {
+            ret = ret
+            && this._pabResults.variableIndex !== undefined
+            && this._pabResults.result.resultElements[this._pabResults.variableIndex] !== undefined
+            && this._pabResults.result.resultElements[this._pabResults.variableIndex].ok;
+        }
+        return ret;
+    }
+
+    public get uitextGeneralLogTitle(): string {
+        return this.i18nService.localizeText("INFO_TITREJOURNAL_GLOBAL");
+    }
+
     /** builds a set of PlottableData from PabResults, to feed the graph */
     protected get plottableResults(): PlottableData {
         this._plottableResults.setPabResults(this.pabResults);
         return this._plottableResults;
     }
+
 }
diff --git a/src/app/components/pab-results/pab-variable-results-selector.component.html b/src/app/components/pab-results/pab-variable-results-selector.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..a6cc6df2405b9cec9f568f12a04806d39e4ca02d
--- /dev/null
+++ b/src/app/components/pab-results/pab-variable-results-selector.component.html
@@ -0,0 +1,11 @@
+<div class="pab-variable-results-selector" *ngIf="hasVariableResults" fxLayout="row wrap" fxLayoutAlign="center center">
+    <div fxFlex="1 1 100%">
+        <mat-form-field>
+            <mat-select id="pab-variating-element" [placeholder]="label" [(value)]="selectedValue">
+                <mat-option *ngFor="let e of entries" [value]="e">
+                    {{ entryLabel(e) }}
+                </mat-option>
+            </mat-select>
+        </mat-form-field>
+    </div>
+</div>
diff --git a/src/app/components/pab-results/pab-variable-results-selector.component.scss b/src/app/components/pab-results/pab-variable-results-selector.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..d3c5d0c6d7819fcde1dabef15aa146abd2fe58b7
--- /dev/null
+++ b/src/app/components/pab-results/pab-variable-results-selector.component.scss
@@ -0,0 +1,10 @@
+:host {
+    margin-top: 2em;
+}
+
+mat-form-field {
+    width: 100%;
+    max-width: 400px;
+    margin-top: 1em;
+    margin-bottom: -1em;
+}
diff --git a/src/app/components/pab-results/pab-variable-results-selector.component.ts b/src/app/components/pab-results/pab-variable-results-selector.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4986bfbf8502342932af0831c8104985d575f3a9
--- /dev/null
+++ b/src/app/components/pab-results/pab-variable-results-selector.component.ts
@@ -0,0 +1,104 @@
+import { Component, Output, EventEmitter } from "@angular/core";
+
+import { PabResults } from "../../results/pab-results";
+import { I18nService } from "../../services/internationalisation/internationalisation.service";
+import { ApplicationSetupService } from "../../services/app-setup/app-setup.service";
+
+@Component({
+    selector: "pab-variable-results-selector",
+    templateUrl: "./pab-variable-results-selector.component.html",
+    styleUrls: [
+        "./pab-variable-results-selector.component.scss"
+    ]
+})
+export class PabVariableResultsSelectorComponent {
+
+    /** résultats non mis en forme */
+    private _pabResults: PabResults;
+
+    private _selectedValue: number;
+
+    /** size of the longest variable value */
+    private size = 0;
+
+    /** inferred extended values list for each variating parameter */
+    private varValues = [];
+
+    @Output()
+    protected indexChange = new EventEmitter();
+
+    constructor(
+        protected intlService: I18nService,
+        protected appSetupService: ApplicationSetupService
+    ) {
+        this._selectedValue = 0;
+    }
+
+    public set results(r: PabResults) {
+        this._pabResults = r;
+
+        if (this._pabResults) {
+            // pre-extract variable parameters values
+            this.varValues = [];
+            const nDigits = this.appSetupService.displayDigits;
+            // find longest list
+            this.size = 0;
+            for (let i = 0; i < this._pabResults.variatedParameters.length; i++) {
+                const vs = this._pabResults.variatedParameters[i].valuesIterator.count();
+                if (vs > this.size) {
+                    this.size = vs;
+                }
+            }
+            // get extended values lists for each variable parameter
+            for (const v of this._pabResults.variatedParameters) {
+                const vv = [];
+                const iter = v.getExtendedValuesIterator(this.size);
+                while (iter.hasNext) {
+                    const nv = iter.next();
+                    vv.push(nv.value.toFixed(nDigits));
+                }
+                this.varValues.push(vv);
+            }
+        }
+    }
+
+    public get hasVariableResults(): boolean {
+        return (
+            this._pabResults
+            && this._pabResults.hasResults
+            && this._pabResults.variatedParameters.length > 0
+        );
+    }
+
+    public get entries(): number[] {
+        const ret: number[] = [];
+        for (let i = 0; i < this.size; i++) {
+            ret.push(i);
+        }
+        return ret;
+    }
+
+    protected entryLabel(index: number): string {
+        let i = 0;
+        return this.varValues.map((vv) => {
+            const vp = this._pabResults.variatedParameters[i];
+            i++;
+            let value = "0";
+            value = vv[index];
+            return `${vp.symbol} = ${value}`;
+        }).join(", ");
+    }
+
+    public get selectedValue(): number {
+        return this._selectedValue;
+    }
+
+    public set selectedValue(v: number) {
+        this._pabResults.variableIndex = v;
+        this.indexChange.emit();
+    }
+
+    public get label() {
+        return this.intlService.localizeText("INFO_PARAMFIELD_BOUNDARY_CONDITIONS");
+    }
+}
diff --git a/src/app/components/pab-table/pab-table.component.html b/src/app/components/pab-table/pab-table.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..93864ec7b69efc4a816a047dcdc6d7d11800df14
--- /dev/null
+++ b/src/app/components/pab-table/pab-table.component.html
@@ -0,0 +1,98 @@
+<mat-card-header class="bg-accent-light">
+    <mat-card-title>
+        {{ title }}
+    </mat-card-title>
+</mat-card-header>
+
+<mat-card-content>
+
+    <div id="pab-table-toolbar">
+        <button type="button" id="edit-pab-table" mat-raised-button color="accent"
+          [disabled]="! enableEditPabButton" (click)="showEditPab()">
+            {{ uitextEditPabTable }}
+        </button>
+
+        <div class="hyd-window-btns">
+            <span class="related-entity-title">
+                {{ relatedEntityTitle }}
+            </span>
+            <mat-select id="add-many-children" [(value)]="childrenToAdd">
+                <mat-option *ngFor="let i of addManyOptionsList" [value]="i">
+                    {{ i }}
+                </mat-option>
+            </mat-select>
+            <button type="button" mat-icon-button color="primary" [disabled]="! enableAddButton" (click)="onAddClick()">
+                <mat-icon>add_box</mat-icon>
+            </button>
+            <button type="button" mat-icon-button color="primary" [disabled]="! enableCopyButton" (click)="onCopyClick()">
+                <mat-icon>content_copy</mat-icon>
+            </button>
+            |
+            <button type="button" mat-icon-button color="primary" [disabled]="! enableRemoveButton" (click)="onRemoveClick()">
+                <mat-icon>delete</mat-icon>
+            </button>
+            <button type="button" mat-icon-button color="primary" [disabled]="! enableUpButton" (click)="onMoveUpClick()">
+                <mat-icon *ngIf="! selectionIsOneDevice">arrow_upward</mat-icon>
+                <mat-icon *ngIf="selectionIsOneDevice">arrow_back</mat-icon>
+            </button>
+            <button type="button" mat-icon-button color="primary" [disabled]="! enableDownButton" (click)="onMoveDownClick()">
+                <mat-icon *ngIf="! selectionIsOneDevice">arrow_downward</mat-icon>
+                <mat-icon *ngIf="selectionIsOneDevice">arrow_forward</mat-icon>
+            </button>
+        </div>
+    </div>
+
+    <p-table [columns]="cols" [value]="rows" class="pab-data-table">
+
+        <ng-template pTemplate="header" let-columns>
+            <tr>
+                <th *ngFor="let h of headers"
+                  (click)="toggleSelection(h, $event)"
+                  (mousedown)="preventCtrlClickBorder($event)"
+                  [attr.rowspan]="h.rowspan ? h.rowspan : null" [attr.colspan]="h.colspan ? h.colspan : null"
+                  [class.selectable-cell]="isSelectable(h)" [class.selected-cell]="isSelected(h)">
+
+                    {{ h.title }}
+                </th>
+            </tr>
+            <tr>
+                <th *ngFor="let col of columns"
+                  (click)="toggleSelection(col, $event)"
+                  (mousedown)="preventCtrlClickBorder($event)"
+                  [class.selectable-cell]="isSelectable(col)" [class.selected-cell]="isSelected(col)">
+
+                    {{ col.title }}
+                </th>
+            </tr>
+        </ng-template>
+
+        <ng-template pTemplate="body" let-row>
+            <tr [class.selected-row]="isSelected(row)">
+                <td *ngFor="let cell of row.cells"
+                  (click)="toggleSelection(cell, $event)"
+                  (mousedown)="preventCtrlClickBorder($event)"
+                  [ngClass]="cell.class"
+                  [class.editable-cell]="hasModel(cell)" [class.readonly-cell]="! hasModel(cell)"
+                  [class.selectable-cell]="isSelectable(cell)" [class.selected-cell]="isSelected(cell) && ! isSelected(row)"
+                  [class.invalid-cell]="isInvalid(cell)" [class.select]="isSelect(cell)"
+                  [attr.rowspan]="rowSpan(cell)" [attr.colspan]="colSpan(cell)"
+                  [title]="cellTitle(cell)">
+
+                    <input matInput *ngIf="isNumberInput(cell)" step="0.00000000000001" type="number"
+                      [(ngModel)]="cell.model.singleValue" (input)="inputValueChanged($event, cell)">
+
+                    <mat-select #selectWidget *ngIf="isSelect(cell)" [value]="cell.modelValue"
+                      (selectionChange)="loiDebitSelected($event, cell)">
+
+                        <mat-option *ngFor="let opt of cell.options" [value]="opt.value">
+                            {{ opt.label }}
+                        </mat-option>
+                    </mat-select>
+
+                    <span *ngIf="! hasModel(cell)">{{ cellValue(cell) }}</span>
+                </td>
+            </tr>
+        </ng-template>
+    </p-table>
+
+</mat-card-content>
diff --git a/src/app/components/pab-table/pab-table.component.scss b/src/app/components/pab-table/pab-table.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..3f27ab369593e68ad608eac5d927aa51e326d40b
--- /dev/null
+++ b/src/app/components/pab-table/pab-table.component.scss
@@ -0,0 +1,150 @@
+:host {
+    display: block;
+    // reduce margins to avoid inner field-sets being too narrow on 360px display
+    margin-left: -8px;
+    margin-right: -8px;
+}
+
+mat-card-header {
+    margin-left: -8px;
+    margin-right: -8px;
+    padding-left: 16px;
+    padding-top: 8px;
+    color: white;
+
+    // Pourquoi n'est-ce pas hérité de calculator.component.scss ?
+    // À cause de la surcharge de mat-card-header ci-dessus ?
+    mat-card-title {
+        font-size: 16px !important;
+        margin-bottom: 8px;
+    }
+}
+
+mat-card-content {
+    margin-top: 1em;
+}
+
+// cells colors
+
+@import "../../../theme.scss";
+
+.editable-cell-bg {
+    @extend .bg-accent-extralight;
+}
+.selected-row-bg {
+    // @extend .bg-warn-extralight;
+    @extend .bg-accent-extralight;
+}
+.selected-cell-bg {
+    // @extend .bg-warn-extralight;
+    @extend .bg-accent-extralight;
+}
+.selected-editable-cell-bg {
+    @extend .bg-accent-verylight;
+}
+.invalid-cell-bg {
+    @extend .bg-error;
+}
+
+#pab-table-toolbar {
+    #edit-pab-table {
+        float: left;
+    }
+    .related-entity-title {
+        vertical-align: middle;
+        font-weight: bold;
+    }
+    .hyd-window-btns {
+        text-align: right;
+
+        #add-many-children {
+            width: 3em;
+            vertical-align: middle;
+        }
+
+        button.mat-icon-button {
+            width: 32px;
+        }
+    }
+}
+
+.pab-data-table {
+    ::ng-deep .ui-table.ui-widget {
+        table {
+            thead.ui-table-thead {
+                > tr {
+                    > th {
+                        font-size: .8em;
+                        padding: 6px 8px;
+                        &.selectable-cell {
+                            cursor: pointer;
+                        }
+                        &.selected-cell {
+                            @extend .selected-cell-bg;
+                        }
+                    }
+                }
+            }
+            tbody.ui-table-tbody {
+                > tr {
+                    &.selected-row {
+                        @extend .selected-row-bg;
+                        > td {
+                            &.editable-cell {
+                                @extend .selected-editable-cell-bg;
+                            }
+                        }
+                    }
+                    > td {
+                        &:nth-child(1) {
+                            width: 50px;
+                        }
+                        font-size: .8em;
+                        &.readonly-cell {
+                            cursor: default;
+                            padding: 4px 8px;
+                        }
+                        &.editable-cell {
+                            @extend .editable-cell-bg;
+                            padding: 0;
+                            > input.mat-input-element {
+                                padding: 4px 8px;
+                            }
+                            &:focus-within {
+                                @extend .bg-warn-extralight;
+                            }
+                        }
+                        &.selectable-cell {
+                            cursor: pointer;
+                        }
+                        &.selected-cell {
+                            @extend .selected-cell-bg;
+                            &.editable-cell {
+                                @extend .selected-editable-cell-bg;
+                            }
+                        }
+                        &.invalid-cell {
+                            @extend .invalid-cell-bg;
+                        }
+                        &.basin_number {
+                            text-align: center;
+                            font-weight: bold;
+                        }
+                        > mat-select.mat-select {
+                            padding: 0 8px;
+                        }
+
+                        > input[type="number"] {
+                            -moz-appearance: textfield;
+                        }
+                        input[type=number]::-webkit-outer-spin-button, 
+                        input[type=number]::-webkit-inner-spin-button {
+                            -webkit-appearance: none;
+                            margin: 0;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/app/components/pab-table/pab-table.component.ts b/src/app/components/pab-table/pab-table.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cf4561da10fcce41d95e7ab5b6cfad5a9c1e0a81
--- /dev/null
+++ b/src/app/components/pab-table/pab-table.component.ts
@@ -0,0 +1,1201 @@
+import { Component, Input, Output, EventEmitter, OnInit, AfterViewInit } from "@angular/core";
+
+import { MatDialog } from "@angular/material";
+
+import {
+    Pab,
+    Session,
+    Props,
+    CalculatorType,
+    Cloisons,
+    Nub,
+    Structure,
+    ParallelStructure,
+    ParamDefinition
+ } from "jalhyd";
+
+ import { sprintf } from "sprintf-js";
+
+import { I18nService } from "../../services/internationalisation/internationalisation.service";
+import { FormulaireService } from "../../services/formulaire/formulaire.service";
+import { ApplicationSetupService } from "../../services/app-setup/app-setup.service";
+import { NotificationsService } from "../../services/notifications/notifications.service";
+import { PabTable } from "../../formulaire/pab-table";
+import { DialogEditPabComponent } from "../dialog-edit-pab/dialog-edit-pab.component";
+
+/**
+ * The big editable data grid for calculator type "Pab" (component)
+ */
+@Component({
+    selector: "pab-table",
+    templateUrl: "./pab-table.component.html",
+    styleUrls: [
+        "./pab-table.component.scss"
+    ]
+})
+export class PabTableComponent implements AfterViewInit, OnInit {
+
+    @Input()
+    private pabTable: PabTable;
+
+    /** flag de validité des FieldSet enfants */
+    private _isValid = false;
+
+    /** événément de changement de validité */
+    @Output()
+    private validChange = new EventEmitter();
+
+    /** événément de changement de valeur d'un input */
+    @Output()
+    private inputChange = new EventEmitter();
+
+    /** underlying Pab, binded to the rows */
+    private model: Pab;
+
+    /** general headers above the columns */
+    public headers: any[];
+
+    /** columns headers description */
+    public cols: any[];
+
+    /** data binded to the table */
+    public rows: any[];
+
+    /** number of children to add when clicking "add" or "clone" button */
+    public childrenToAdd = 1;
+
+    /** items currently selected */
+    private selectedItems: Nub[];
+
+    /** used for shift+click implementation */
+    private latestClickedCell: any;
+
+    public constructor(
+        private i18nService: I18nService,
+        private formService: FormulaireService,
+        private editPabDialog: MatDialog,
+        private appSetupService: ApplicationSetupService,
+        private notifService: NotificationsService
+    ) {
+        this.selectedItems = [];
+    }
+
+    public get title(): string {
+        return this.i18nService.localizeText("INFO_PAB_TABLE");
+    }
+
+    /** Global Pab validity */
+    public get isValid() {
+        return this._isValid;
+    }
+
+    /** returns true if the cell has an underlying model (ie. is editable) */
+    public hasModel(cell: any): boolean {
+        return (cell !== undefined && cell.model !== undefined);
+    }
+
+    /** returns true if the cell is an editable number */
+    public isNumberInput(cell: any): boolean {
+        return this.hasModel(cell) && ! this.isSelect(cell);
+    }
+
+    /** returns true if the cell is a select box @TODO rename */
+    public isSelect(cell: any): boolean {
+        return this.hasModel(cell) && (cell.options !== undefined);
+    }
+
+    /** value to display in a cell, if it is not editable */
+    public cellValue(cell: any) {
+        if (cell === undefined) {
+            return "";
+        } else {
+            if (this.hasModel(cell)) {
+                return cell.model;
+            } else {
+                return cell.value;
+            }
+        }
+    }
+
+    /** "title" tooltip to display in a cell */
+    public cellTitle(cell: any) {
+        if (cell !== undefined && cell.title !== undefined) {
+            return cell.title;
+        } else {
+            return "";
+        }
+    }
+
+    public rowSpan(cell: any) {
+        if (cell !== undefined && cell.rowspan) {
+            return cell.rowspan;
+        }
+        return undefined;
+    }
+
+    public colSpan(cell: any) {
+        if (cell !== undefined && cell.colspan) {
+            return cell.colspan;
+        }
+        return undefined;
+    }
+
+    /**
+     * Checks that input value is a valid number, according to input[type="number"] algorithm,
+     * and stores it in cell.uiValidity, so that the <td> element can access it and get angry
+     * if input is invalid
+     */
+    public inputValueChanged($event, cell) {
+        // console.log("input value changed", $event.target.validity.valid, $event.target.validity);
+        if ($event && $event.target && $event.target.validity) {
+            cell.uiValidity = $event.target.validity.valid;
+        }
+        this.updateValidity();
+        // send input change event (used to reset form results)
+        this.inputChange.emit();
+    }
+
+    /**
+     * Returns true if current cell is bound to a model that says its input value is
+     * no valid, or if characters typed in the input field are not a valid number
+     * (read from cell.uiValidity, see inputValueChanged() above)
+     */
+    public isInvalid(cell: any): boolean {
+        let valid = true;
+        if (this.hasModel(cell) && cell.model instanceof ParamDefinition) {
+            valid = valid && cell.model.isValid;
+        }
+        if (cell.uiValidity !== undefined) {
+            valid = valid && cell.uiValidity;
+        }
+        return ! valid;
+    }
+
+    /** returns true if the cell / row has a selectable item */
+    public isSelectable(cellOrRow: any): boolean {
+        return (
+            cellOrRow !== undefined
+            && cellOrRow.selectable
+        );
+    }
+
+    /** returns true if the cell / row has a selectableColumn item */
+    public isSelectableByColumn(cellOrRow: any): boolean {
+        return (
+            cellOrRow !== undefined
+            && cellOrRow.selectableColumn
+        );
+    }
+
+    /**
+     *  - checks if the cell / row has a selectable item, that is currently
+     *      selected
+     *  - if cell / row has a selectableColumn attribute, also checks if
+     *      this column is selected
+     *
+     *  returns true if at least one criterion is met
+     */
+    public isSelected(cellOrRow: any): boolean {
+        let cellSelected = false;
+        let columnSelected = false;
+        // cell
+        if (this.isSelectable(cellOrRow)) {
+            cellSelected = true;
+            if (Array.isArray(cellOrRow.selectable)) {
+                for (const elt of cellOrRow.selectable) {
+                    cellSelected = cellSelected && this.selectedItems.includes(elt);
+                }
+            } else {
+                cellSelected = this.selectedItems.includes(cellOrRow.selectable);
+            }
+        }
+        // column
+        if (this.isSelectableByColumn(cellOrRow)) {
+            columnSelected = this.isDeviceColumnSelected(cellOrRow.selectableColumn);
+        }
+        // done
+        return (cellSelected || columnSelected);
+    }
+
+    /**
+     * returns true if every wall (including downwall) has its nth device
+     * selected (or has no nth device)
+     */
+    public isDeviceColumnSelected(n: number): boolean {
+        let ok = true;
+        for (const c of this.model.children) {
+            const nthChild = c.getChildren()[n];
+            if (nthChild) {
+                ok = ok && this.selectedItems.includes(nthChild);
+            }
+        }
+        const nthChildDW = this.model.downWall.getChildren()[n];
+        if (nthChildDW) {
+            ok = ok && this.selectedItems.includes(nthChildDW);
+        }
+        return ok;
+    }
+
+    /**
+     * selects or unselects the clicked cell, depending on its current state
+     * and the modifier key held if any
+     */
+    public toggleSelection(cell: any, $event: any) {
+        if (
+            this.isSelectable(cell)
+            && ! this.hasModel(cell) // editable cells listen to the click event for edition only
+        ) {
+            if ($event.shiftKey && cell !== this.latestClickedCell) { // shift + click
+                // interpolate from this.latestClickedCell to this one
+                if (! Array.isArray(cell.selectable)) { // multiselectable cells are not managed
+                    const wallsUIDs = this.getSortedWallsUIDs();
+                    let posOld: number;
+                    let posNew: number;
+                    // find positions depending on types, and only if types are equal
+                    if (cell.selectable instanceof ParallelStructure) {
+                        if (this.latestClickedCell.selectable instanceof ParallelStructure) {
+                            // interpolate walls
+                            posOld = wallsUIDs.indexOf(this.latestClickedCell.selectable.uid);
+                            posNew = wallsUIDs.indexOf(cell.selectable.uid);
+                            if (posOld !== posNew) {
+                                if (posOld > posNew) {
+                                    // invert order
+                                    [ posOld, posNew ] = [ posNew, posOld ];
+                                }
+                                // go
+                                for (let i = posOld; i <= posNew; i++) {
+                                    if (i < this.model.children.length) {
+                                        // push regular wall
+                                        this.selectedItems.push(this.model.children[i]);
+                                    } else {
+                                        // push downwall
+                                        this.selectedItems.push(this.model.downWall);
+                                    }
+                                    this.latestClickedCell = cell;
+                                }
+                            }
+                        }
+                    } else if (cell.selectable instanceof Structure) {
+                        if (this.latestClickedCell.selectable instanceof Structure) {
+                            // accept interpolation only if both devices are on the same column
+                            const columnOld = this.latestClickedCell.selectable.findPositionInParent();
+                            const columnNew = cell.selectable.findPositionInParent();
+                            if (columnOld === columnNew) {
+                                // interpolate devices
+                                posOld = wallsUIDs.indexOf(this.latestClickedCell.selectable.parent.uid);
+                                posNew = wallsUIDs.indexOf(cell.selectable.parent.uid);
+                                if (posOld !== posNew) {
+                                    if (posOld > posNew) {
+                                        // invert order
+                                        [ posOld, posNew ] = [ posNew, posOld ];
+                                    }
+                                    // go
+                                    for (let i = posOld; i <= posNew; i++) {
+                                        if (i < this.model.children.length) {
+                                            // push regular wall
+                                            this.selectedItems.push(this.model.children[i].structures[columnOld]);
+                                        } else {
+                                            // push downwall
+                                            this.selectedItems.push(this.model.downWall.structures[columnOld]);
+                                        }
+                                    }
+                                    this.latestClickedCell = cell;
+                                }
+                            }
+                        }
+                    }
+                    // clean selected items list (deduplicate, sort)
+                    this.selectedItems = this.selectedItems.filter(
+                        (item, index) => this.selectedItems.indexOf(item) === index // deduplicate
+                    );
+                    this.sortSelectedItems();
+                }
+
+            } else if (
+                $event.ctrlKey // ctrl + click
+                || ($event.shiftKey && cell === this.latestClickedCell) // shift on same cell => equiv. of ctrl
+            ) {
+                if (this.isSelected(cell)) {
+                    // unselect this cell / these cells
+                    if (Array.isArray(cell.selectable)) {
+                        this.selectedItems = this.selectedItems.filter(e => ! cell.selectable.includes(e));
+                    } else {
+                        this.selectedItems = this.selectedItems.filter(e => e !== cell.selectable);
+                    }
+                } else {
+                    // add this cell / these cells to selection
+                    if (Array.isArray(cell.selectable)) {
+                        this.selectedItems = this.selectedItems.concat(cell.selectable);
+                        this.selectedItems = this.selectedItems.filter(
+                            (item, index) => this.selectedItems.indexOf(item) === index // deduplicate
+                        );
+                    } else {
+                        this.selectedItems.push(cell.selectable);
+                    }
+                    this.sortSelectedItems();
+                }
+                this.latestClickedCell = cell;
+
+            } else { // just a click
+                if (this.isSelected(cell)) {
+                    // select nothing
+                    this.selectedItems = [];
+                } else {
+                    // select this cell / thses cells only
+                    if (Array.isArray(cell.selectable)) {
+                        this.selectedItems = cell.selectable.slice(); // array copy
+                    } else {
+                        this.selectedItems = [ cell.selectable ];
+                    }
+                }
+                this.latestClickedCell = cell;
+            }
+
+            // clean list
+            this.selectedItems = this.selectedItems.filter(e => e !== undefined);
+
+            this.clearSelection();
+            $event.preventDefault();
+            $event.stopPropagation();
+            return false;
+        }
+    }
+
+    // quick getter for 1st selected item
+    public get selectedItem() {
+        if (this.selectedItems.length === 0) {
+            throw new Error("get selectedItem() : no item selected");
+        }
+        return this.selectedItems[0];
+    }
+
+    // prevents Firefox to display weird cell border when ctrl+clicking
+    public preventCtrlClickBorder($event) {
+        if ($event.ctrlKey) {
+            $event.preventDefault();
+        }
+    }
+
+    public get addManyOptionsList() {
+        return Array(20).fill(0).map((value, index) => index + 1);
+    }
+
+    // at this time @Input data is supposed to be already populated
+    public ngOnInit() {
+        this.model = this.pabTable.pab;
+        this.refresh();
+    }
+
+    /** Unselects all selected text (side-effect of shift+clicking) */
+    private clearSelection() {
+        if (window.getSelection) {
+            const sel = window.getSelection();
+            sel.removeAllRanges();
+        }
+    }
+
+    // extract PAB walls order
+    private getSortedWallsUIDs(): string[] {
+        const wallsUIDs: string[] = [];
+        for (const c of this.pabTable.pab.children) {
+            wallsUIDs.push(c.uid);
+        }
+        wallsUIDs.push(this.pabTable.pab.downWall.uid);
+        return wallsUIDs;
+    }
+
+    /**
+     * Ensures that this.selectedItems elements are ordered according to
+     * the walls order in the PAB (important for interpolation)
+     */
+    private sortSelectedItems() {
+        const wallsUIDs = this.getSortedWallsUIDs();
+        // are items walls or devices ?
+        if (this.onlyWallsAreSelected(false)) {
+            // 1. walls : order by uid, according to model
+            this.selectedItems.sort((a, b) => {
+                const posA = wallsUIDs.indexOf(a.uid);
+                const posB = wallsUIDs.indexOf(b.uid);
+                return posA - posB;
+            });
+        } else {
+            // 2. devices : order by parent (wall) uid, according to model
+            this.selectedItems.sort((a, b) => {
+                const posA = wallsUIDs.indexOf(a.parent.uid);
+                const posB = wallsUIDs.indexOf(b.parent.uid);
+                return posA - posB;
+            });
+        }
+        return this.selectedItems;
+    }
+
+    /**
+     * Builds the editable data grid from the Pab model
+     */
+    private refresh() {
+        const maxNbDevices = this.findMaxNumberOfDevices();
+
+        // 0. build spanned headers over real columns
+        this.headers = [];
+        // 1 header for basin
+        let bs: any[] = this.model.children;
+        bs = bs.concat(this.model.downWall);
+        this.headers.push({
+            title: this.i18nService.localizeText("INFO_PAB_BASSIN"),
+            colspan: 6,
+            selectable: bs
+        });
+        // 1 header for each device of the wall having the most devices (including downwall)
+        for (let i = 0; i < maxNbDevices; i++) {
+            this.headers.push({
+                title: sprintf(this.i18nService.localizeText("INFO_PAB_CLOISON_OUVRAGE_N"), (i + 1)),
+                colspan: 3,
+                selectable: this.model.children.map(c => c.getChildren()[i]).concat(this.model.downWall.getChildren()[i]),
+                selectableColumn: i
+            });
+        }
+
+        // A. build columns set
+        this.cols = [];
+        // 6 cols for basin
+        this.cols.push({
+            title: this.i18nService.localizeText("INFO_PAB_NUM_BASSIN"),
+            selectable: bs
+        });
+        this.cols.push({
+            title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "LB"),
+            selectable: bs
+        });
+        this.cols.push({
+            title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "BB"),
+            selectable: bs
+        });
+        this.cols.push({
+            title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "QA"),
+            selectable: bs
+        });
+        this.cols.push({
+            title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "ZRMB"),
+            selectable: bs
+        });
+        this.cols.push({
+            title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "ZRAM"),
+            selectable: bs
+        });
+        // no col for wall type (defined by rowspan-2 header above)
+        // 3 cols for each device of the wall having the most devices (including downwall)
+        for (let i = 0; i < maxNbDevices; i++) {
+            this.cols.push({
+                title: this.i18nService.localizeText("INFO_PAB_HEADER_TYPE"),
+                selectable: this.model.children.map(c => c.getChildren()[i]).concat(this.model.downWall.getChildren()[i]),
+                selectableColumn: i
+            });
+            this.cols.push({
+                title: this.i18nService.localizeText("INFO_PAB_HEADER_PARAMETERS"),
+                selectable: this.model.children.map(c => c.getChildren()[i]).concat(this.model.downWall.getChildren()[i]),
+                selectableColumn: i
+            });
+            this.cols.push({
+                title: this.i18nService.localizeText("INFO_PAB_HEADER_VALUES"),
+                selectable: this.model.children.map(c => c.getChildren()[i]).concat(this.model.downWall.getChildren()[i]),
+                selectableColumn: i
+            });
+        }
+
+        // B. Build rows set
+        this.rows = [];
+        // B.1 many rows for each wall
+        let childIndex = 0;
+        for (const cloison of this.model.children) {
+            // admissible LoiDebit
+            const loisCloisons = cloison.getLoisAdmissiblesArray().map(l => { // @TODO move up ? (same for all cloisons)
+                return {
+                    label: this.i18nService.localizeLoiDebit(l),
+                    value: l
+                };
+            });
+            // as much rows as the greatest number of parameters among its devices
+            const maxNbParams = this.findMaxNumberOfDeviceParameters(cloison);
+            for (let i = 0; i < maxNbParams; i++) {
+                // build device params row
+                const deviceParamRow = { selectable: cloison, cells: [] };
+                // basin number
+                if (i === 0) {
+                    deviceParamRow.cells.push({
+                        value: childIndex + 1,
+                        rowspan: maxNbParams + 1,
+                        class: "basin_number",
+                        selectable: cloison
+                    });
+                }
+                // 5 empty cells
+                if (i === 0) {
+                    deviceParamRow.cells.push({
+                        colspan: 5,
+                        rowspan: maxNbParams,
+                        selectable: cloison
+                    });
+                }
+                // device param cells : 3 cells for each device
+                for (const ouvrage of cloison.structures) {
+                    const nvParam = ouvrage.getNthVisibleParam(i);
+                    const nvParamTitle = nvParam ? this.formService.expandVariableNameAndUnit(CalculatorType.Pab, nvParam.symbol) : "";
+                    // cell 1 : device type
+                    if (i === 0) { // 1st row
+                        deviceParamRow.cells.push({
+                            model: ouvrage,
+                            modelValue: ouvrage.properties.getPropValue("loiDebit"),
+                            options: loisCloisons,
+                            selectable: ouvrage
+                        });
+                    }
+                    // fill space
+                    if (i === 1) {
+                        deviceParamRow.cells.push({
+                            rowspan: (maxNbParams - 1),
+                            selectable: ouvrage
+                        });
+                    }
+                    // cell 2 : param name
+                    if (nvParam) {
+                        deviceParamRow.cells.push({
+                            value: nvParam.symbol,
+                            title: nvParamTitle,
+                            selectable: ouvrage
+                        });
+                    } else {
+                        deviceParamRow.cells.push({
+                            selectable: ouvrage
+                        });
+                    }
+                    // cell 3 : param value
+                    if (nvParam) {
+                        deviceParamRow.cells.push({
+                            model: nvParam,
+                            title: nvParamTitle,
+                            selectable: ouvrage
+                        });
+                    } else {
+                        deviceParamRow.cells.push({
+                            selectable: ouvrage
+                        });
+                    }
+                }
+                // fill horizontal space
+                const devDiff =  (maxNbDevices - cloison.structures.length);
+                if (i === 0) {
+                    for (let j = 0; j < devDiff; j++) {
+                        deviceParamRow.cells.push({
+                            colspan: 3,
+                            rowspan: maxNbParams,
+                            selectable: cloison,
+                            selectableColumn: cloison.structures.length + j
+                        });
+                    }
+                }
+                // done !
+                this.rows.push(deviceParamRow);
+            }
+            // 1 row for the basin after the wall
+            const basinRow: { selectable: any, cells: any[] } = {
+                selectable: cloison,
+                cells: [
+                    // no cell for basin number (defined by rowspan-n cell above)
+                    {
+                        model: cloison.prms.LB,
+                        title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "LB")
+                    },
+                    {
+                        model: cloison.prms.BB,
+                        title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "BB")
+                    },
+                    {
+                        model: cloison.prms.QA,
+                        title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "QA")
+                    },
+                    {
+                        model: cloison.prms.ZRMB,
+                        title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "ZRMB")
+                    },
+                    {
+                        model: cloison.prms.ZRAM,
+                        title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "ZRAM")
+                    }
+                ]
+            };
+            // fill horizontal space
+            for (let i = 0; i < maxNbDevices; i++) {
+                basinRow.cells.push({
+                    colspan: 3
+                });
+            }
+            // done !
+            this.rows.push(basinRow);
+            childIndex ++;
+        }
+
+        // B.2 many rows for downwall
+        // admissible LoiDebit
+        const loisAval = this.model.downWall.getLoisAdmissiblesArray().map(l => {
+            return {
+                label: this.i18nService.localizeLoiDebit(l),
+                value: l
+            };
+        });
+        // as much rows as the greatest number of parameters among its devices
+        const maxNbParamsDW = this.findMaxNumberOfDeviceParameters(this.model.downWall);
+        for (let i = 0; i < maxNbParamsDW; i++) {
+            // build device params row
+            const deviceParamRowDW = { selectable: this.model.downWall, cells: [] };
+            // basin number
+            if (i === 0) {
+                deviceParamRowDW.cells.push({
+                    value: "Aval",
+                    rowspan: maxNbParamsDW,
+                    class: "basin_number",
+                    selectable: this.model.downWall
+                });
+            }
+            // 5 empty cells
+            if (i === 0) {
+                deviceParamRowDW.cells.push({
+                    colspan: 5,
+                    rowspan: maxNbParamsDW,
+                    selectable: this.model.downWall
+                });
+            }
+            // downwall device param cells : 3 cells for each device
+            for (const ouvrage of this.model.downWall.structures) {
+                const nvParam = ouvrage.getNthVisibleParam(i);
+                const nvParamTitle = nvParam ? this.formService.expandVariableNameAndUnit(CalculatorType.Pab, nvParam.symbol) : "";
+                // cell 1 : device type
+                if (i === 0) { // 1st row
+                    deviceParamRowDW.cells.push({
+                        model: ouvrage,
+                        modelValue: ouvrage.properties.getPropValue("loiDebit"),
+                        options: loisAval
+                    });
+                }
+                // fill space
+                if (i === 1) {
+                    deviceParamRowDW.cells.push({
+                        rowspan: (maxNbParamsDW - 1),
+                        selectable: ouvrage
+                    });
+                }
+                // cell 2 : param name
+                if (nvParam) {
+                    deviceParamRowDW.cells.push({
+                        value: nvParam.symbol,
+                        title: nvParamTitle,
+                        selectable: ouvrage
+                    });
+                } else {
+                    deviceParamRowDW.cells.push({
+                        selectable: ouvrage
+                    });
+                }
+                // cell 3 : param value
+                if (nvParam) {
+                    deviceParamRowDW.cells.push({
+                        model: nvParam,
+                        title: nvParamTitle,
+                        selectable: ouvrage
+                    });
+                } else {
+                    deviceParamRowDW.cells.push({
+                        selectable: ouvrage
+                    });
+                }
+            }
+            // fill horizontal space
+            const devDiff =  (maxNbDevices - this.model.downWall.structures.length);
+            if (i === 0) {
+                for (let j = 0; j < devDiff; j++) {
+                    deviceParamRowDW.cells.push({
+                        colspan: 3,
+                        rowspan: maxNbParamsDW,
+                        selectable: this.model.downWall,
+                        selectableColumn: this.model.downWall.structures.length + j
+                    });
+                }
+            }
+            // done !
+            this.rows.push(deviceParamRowDW);
+        }
+
+        this.updateValidity();
+    }
+
+    private findMaxNumberOfDevices(): number {
+        let maxNbDevices = 1;
+        for (const w of this.model.children) {
+            maxNbDevices = Math.max(maxNbDevices, w.getChildren().length);
+        }
+        maxNbDevices = Math.max(maxNbDevices, this.model.downWall.getChildren().length);
+        return maxNbDevices;
+    }
+
+    private findMaxNumberOfDeviceParameters(struct: ParallelStructure): number {
+        let maxNbParams = 1;
+        for (const d of struct.getChildren()) {
+            let nbParams = 0;
+            for (const p of d.parameterIterator) {
+                if (p.visible) {
+                    // console.log("(counting)", p.symbol);
+                    nbParams ++;
+                }
+            }
+            // console.log(">>> child params: ", nbParams);
+            maxNbParams = Math.max(maxNbParams, nbParams);
+        }
+        return maxNbParams;
+    }
+
+    /** returns true if exactly one device is selected, and nothing else */
+    public get selectionIsOneDevice() {
+        return (
+            this.selectedItems.length === 1
+            && this.selectedItem instanceof Structure
+        );
+    }
+
+    /**
+     * Returns true if there is at least one selected item,
+     * and all selected items are devices
+     */
+    private onlyDevicesAreSelected() {
+        let ok = false;
+        if (this.selectedItems.length > 0) {
+            ok = true;
+            for (const s of this.selectedItems) {
+                ok = ok && (s instanceof Structure);
+            }
+        }
+        return ok;
+    }
+
+    /**
+     * Returns true if there is at least one selected item,
+     * and all selected items are walls
+     */
+    private onlyWallsAreSelected(excludeDownwall: boolean = true) {
+        let ok = false;
+        if (this.selectedItems.length > 0) {
+            ok = true;
+            for (const s of this.selectedItems) {
+                if (excludeDownwall) {
+                    ok = ok && (s instanceof Cloisons);
+                } else {
+                    ok = ok && (s instanceof ParallelStructure);
+                }
+            }
+        }
+        return ok;
+    }
+
+    public get relatedEntityTitle() {
+        let title = "";
+        if (this.onlyDevicesAreSelected()) {
+            title = "Ouvrages";
+        } else if (this.onlyWallsAreSelected()) {
+            title = "Cloisons";
+        }
+        if (title !== "") {
+            title += " :";
+        }
+        return title;
+    }
+
+    public get enableAddButton() {
+        return (
+            this.selectedItems.length === 1
+            && this.selectedItem.parent // exclude downwall
+        );
+    }
+
+    public get enableCopyButton() {
+        return (
+            this.selectedItems.length === 1
+            && this.selectedItem.parent // exclude downwall
+        );
+    }
+
+    public get enableUpButton() {
+        return (
+            this.selectedItems.length === 1
+            && this.selectedItem.parent
+            && this.selectedItem.findPositionInParent() !== 0
+        );
+    }
+
+    public get enableDownButton() {
+        return (
+            this.selectedItems.length === 1
+            && this.selectedItem.parent
+            && this.selectedItem.findPositionInParent() < (this.selectedItem.parent.getChildren().length - 1)
+        );
+    }
+
+    public get enableRemoveButton() {
+        return (
+            this.selectedItems.length === 1
+            && this.selectedItem.parent
+            && this.selectedItem.parent.getChildren().length > 1
+        );
+    }
+
+    /**
+     * returns true if at least one object is selected
+     */
+    public get enableEditPabButton() {
+        return (
+            this.selectedItems.length > 0
+            && (
+                this.onlyDevicesAreSelected()
+                || this.onlyWallsAreSelected(false)
+            )
+        );
+    }
+
+    public onAddClick() {
+        // add default item
+        for (let i = 0; i < this.childrenToAdd; i++) {
+            if (this.selectedItem instanceof Structure) {
+                // add new default device for wall parent
+                const newDevice = Session.getInstance().createNub(
+                    new Props({
+                        calcType: CalculatorType.Structure,
+                        loiDebit: (this.selectedItem.parent as ParallelStructure).getDefaultLoiDebit()
+                    })
+                );
+                this.selectedItem.parent.addChild(newDevice, this.selectedItem.findPositionInParent());
+
+            } else {
+                // add new default wall for PAB parent
+                const newWall = Session.getInstance().createNub(
+                    new Props({
+                        calcType: CalculatorType.Cloisons
+                    })
+                );
+                // add new default device for new wall
+                const newDevice = Session.getInstance().createNub(
+                    new Props({
+                        calcType: CalculatorType.Structure,
+                        loiDebit: (newWall as ParallelStructure).getDefaultLoiDebit()
+                    })
+                );
+                newWall.addChild(newDevice);
+                this.model.addChild(newWall, this.selectedItem.findPositionInParent());
+            }
+        }
+        this.refresh();
+
+        // notify
+        const pos = this.selectedItem.findPositionInParent() + 1;
+        let msg: string;
+        if (this.childrenToAdd === 1) {
+            if (this.selectedItem instanceof Structure) {
+                msg = sprintf(this.i18nService.localizeText("INFO_DEVICE_ADDED"), pos);
+            } else {
+                msg = sprintf(this.i18nService.localizeText("INFO_WALL_ADDED"), pos);
+            }
+        } else {
+            if (this.selectedItem instanceof Structure) {
+                msg = sprintf(this.i18nService.localizeText("INFO_DEVICE_ADDED_N_TIMES"), this.childrenToAdd);
+            } else {
+                msg = sprintf(this.i18nService.localizeText("INFO_WALL_ADDED_N_TIMES"), this.childrenToAdd);
+            }
+        }
+        this.notifService.notify(msg);
+
+        this.childrenToAdd = 1; // reinit to avoid confusion
+    }
+
+    public onCopyClick() {
+        // cloned selected item
+        for (let i = 0; i < this.childrenToAdd; i++) {
+            const newChild = Session.getInstance().createNub(
+                this.selectedItem.properties.clone(),
+                this.selectedItem.parent
+            );
+            // copy parameter values
+            for (const p of this.selectedItem.prms) {
+                newChild.getParameter(p.symbol).singleValue = p.singleValue;
+            }
+            // copy children
+            if (this.selectedItem instanceof ParallelStructure) {
+                for (const c of this.selectedItem.getChildren()) {
+                    const newGrandChild = Session.getInstance().createNub(
+                        c.properties.clone(),
+                        newChild
+                    );
+                    // copy children parameters values
+                    for (const p of c.prms) {
+                        newGrandChild.getParameter(p.symbol).singleValue = p.singleValue;
+                    }
+                    // add to parent
+                    newChild.addChild(
+                        newGrandChild,
+                        c.findPositionInParent()
+                    );
+                }
+            }
+            // add to parent
+            this.selectedItem.parent.addChild(
+                newChild,
+                this.selectedItem.findPositionInParent()
+            );
+        }
+        this.refresh();
+
+        // notify
+        const pos = this.selectedItem.findPositionInParent() + 1;
+        let msg: string;
+        if (this.childrenToAdd === 1) {
+            if (this.selectedItem instanceof Structure) {
+                msg = sprintf(this.i18nService.localizeText("INFO_DEVICE_COPIED"), pos);
+            } else {
+                msg = sprintf(this.i18nService.localizeText("INFO_WALL_COPIED"), pos);
+            }
+        } else {
+            if (this.selectedItem instanceof Structure) {
+                msg = sprintf(this.i18nService.localizeText("INFO_DEVICE_COPIED_N_TIMES"), pos, this.childrenToAdd);
+            } else {
+                msg = sprintf(this.i18nService.localizeText("INFO_WALL_COPIED_N_TIMES"), pos, this.childrenToAdd);
+            }
+        }
+        this.notifService.notify(msg);
+
+        this.childrenToAdd = 1; // reinit to avoid confusion
+    }
+
+    public onMoveUpClick() {
+        const pos = this.selectedItem.findPositionInParent() + 1;
+        this.selectedItem.parent.moveChildUp(this.selectedItem);
+        if (this.selectedItem instanceof Structure) {
+            this.notifService.notify(sprintf(this.i18nService.localizeText("INFO_DEVICE_MOVED"), pos));
+        } else {
+            this.notifService.notify(sprintf(this.i18nService.localizeText("INFO_WALL_MOVED"), pos));
+        }
+        this.refresh();
+    }
+
+    public onMoveDownClick() {
+        const pos = this.selectedItem.findPositionInParent() + 1;
+        this.selectedItem.parent.moveChildDown(this.selectedItem);
+        if (this.selectedItem instanceof Structure) {
+            this.notifService.notify(sprintf(this.i18nService.localizeText("INFO_DEVICE_MOVED"), pos));
+        } else {
+            this.notifService.notify(sprintf(this.i18nService.localizeText("INFO_WALL_MOVED"), pos));
+        }
+        this.refresh();
+    }
+
+    public onRemoveClick() {
+        const pos = this.selectedItem.findPositionInParent() + 1;
+        this.selectedItem.parent.deleteChild(this.selectedItem.findPositionInParent());
+        if (this.selectedItem instanceof Structure) {
+            this.notifService.notify(sprintf(this.i18nService.localizeText("INFO_DEVICE_REMOVED"), pos));
+        } else {
+            this.notifService.notify(sprintf(this.i18nService.localizeText("INFO_WALL_REMOVED"), pos));
+        }
+        this.selectedItems = [];
+        this.refresh();
+    }
+
+    /** Replace device Nub when LoiDebit is changed */
+    public loiDebitSelected($event: any, cell: any) {
+        const device = cell.model as Nub;
+        // create new child device
+        const newDevice = Session.getInstance().createNub(
+            new Props({
+                calcType: CalculatorType.Structure,
+                loiDebit: $event.value
+            })
+        );
+        // replace the current one
+        device.parent.replaceChildInplace(device, newDevice);
+        this.refresh();
+    }
+
+    // show modal dialog for values edition
+    public showEditPab() {
+        if (this.selectedItems.length > 0) {
+
+            // list variables eligible to modification
+            const availableVariables: { label: string, value: string, occurrences: number, first: number, last: number }[] = [];
+            for (const c of this.selectedItems) {
+                for (const p of c.parameterIterator) { // deep one
+                    if (
+                        p.visible &&
+                        ! availableVariables.map(av => av.value).includes(p.symbol)
+                    ) {
+                        availableVariables.push({
+                            label: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, p.symbol),
+                            value: p.symbol,
+                            occurrences: 0,
+                            first: undefined,
+                            last: undefined
+                        });
+                    }
+                }
+            }
+            // find their min/max values (2nd pass)
+            for (const av of availableVariables) {
+                for (const c of this.selectedItems) {
+                    for (const p of c.parameterIterator) {
+                        if (p.visible && p.symbol === av.value) {
+                            av.occurrences ++;
+                            if (av.first === undefined) {
+                                av.first = p.singleValue;
+                            }
+                            av.last = p.singleValue;
+                        }
+                    }
+                }
+            }
+
+            // sum up selected items
+            const walls: ParallelStructure[] = [];
+            const wallsDevices: Structure[] = [];
+            const devices: Structure[] = [];
+            let vertical = true; // @TODO vertical AND consecutive !
+            let firstDevicePosition: number;
+            // 1st pass
+            for (const s of this.selectedItems) {
+                if (s instanceof ParallelStructure) {
+                    walls.push(s);
+                    for (const c of s.structures) {
+                        if (firstDevicePosition === undefined) {
+                            firstDevicePosition = c.findPositionInParent();
+                        } else {
+                            vertical = (vertical && (c.findPositionInParent() === firstDevicePosition));
+                        }
+                        wallsDevices.push(c);
+                    }
+                }
+            }
+            // 2nd pass
+            for (const c of this.selectedItems) {
+                if (c instanceof Structure) {
+                    if (! wallsDevices.includes(c)) {
+                        if (firstDevicePosition === undefined) {
+                            firstDevicePosition = c.findPositionInParent();
+                        } else {
+                            vertical = (vertical && (c.findPositionInParent() === firstDevicePosition));
+                        }
+                        devices.push(c);
+                    }
+                }
+            }
+
+            // open dialog
+            const dialogRef = this.editPabDialog.open(
+                DialogEditPabComponent,
+                {
+                    data: {
+                        availableVariables: availableVariables,
+                        selectedItemsAbstract: {
+                            walls: walls.length,
+                            wallsDevices: wallsDevices.length,
+                            devices: devices.length
+                        },
+                        vertical: vertical // used to enable interpolation
+                    },
+                    disableClose: true
+                }
+            );
+
+            // apply modifications
+            dialogRef.afterClosed().subscribe(result => {
+                if (result) {
+                    /* console.log("Apply values in parent !!", result.action, result.variable, result.value,
+                      result.delta, result.variableDetails); */
+                    switch (result.action) {
+                        case "set-value":
+                            for (const s of this.selectedItems) {
+                                for (const p of s.parameterIterator) { // deep
+                                    if (p.symbol === result.variable) {
+                                        p.singleValue = result.value;
+                                    }
+                                }
+                            }
+                            break;
+
+                        case "delta":
+                            for (const s of this.selectedItems) {
+                                for (const p of s.parameterIterator) { // deep
+                                    if (p.symbol === result.variable) {
+                                        p.singleValue += result.delta;
+                                    }
+                                }
+                            }
+                            break;
+
+                        case "interpolate":
+                            if (result.variableDetails.occurrences > 1) {
+                                const nDigits = this.appSetupService.displayDigits;
+                                // build values list
+                                const interpolatedValues: number[] = [];
+                                const step = (
+                                    (result.variableDetails.last - result.variableDetails.first)
+                                    / (result.variableDetails.occurrences - 1)
+                                );
+                                interpolatedValues.push(result.variableDetails.first);
+                                let currentValue: number = result.variableDetails.first;
+                                for (let i = 0; i < result.variableDetails.occurrences - 2; i++) {
+                                    currentValue += step;
+                                    interpolatedValues.push(Number(currentValue.toFixed(nDigits)));
+                                }
+                                interpolatedValues.push(result.variableDetails.last);
+                                // apply
+                                let idx = 0;
+                                for (const s of this.selectedItems) {
+                                    for (const p of s.parameterIterator) { // deep
+                                        if (p.symbol === result.variable) {
+                                            p.singleValue = interpolatedValues[idx];
+                                            idx ++;
+                                        }
+                                    }
+                                }
+                            } else {
+                                throw new Error(
+                                    `showEditPab() : cannot interpolate, too few occurrences (${result.variableDetails.occurrences})`
+                                );
+                            }
+                            break;
+                    }
+                }
+            });
+        }
+    }
+
+    public ngAfterViewInit() {
+        this.updateValidity();
+    }
+
+    /**
+     * Computes the global Pab validity : validity of every cell of every row
+     */
+    private updateValidity() {
+        this._isValid = true;
+        for (const r of this.rows) {
+            for (const c of r.cells) {
+                this._isValid = this._isValid && ! this.isInvalid(c);
+            }
+        }
+        this.validChange.emit();
+    }
+
+    /**
+     * Renvoie l'événement au composant du dessus
+     */
+    public onTabPressed(event) {
+        console.log("tab pressed dans le tablo !");
+    }
+
+    public get uitextEditPabTable() {
+        return "Modifier les valeurs";
+    }
+}
diff --git a/src/app/components/param-values/param-values.component.ts b/src/app/components/param-values/param-values.component.ts
index 41cefb196e912a830554167f5c8207b8478c8ce2..3e1e933d51f2b66edd2222424c4b9a5a8adade7c 100644
--- a/src/app/components/param-values/param-values.component.ts
+++ b/src/app/components/param-values/param-values.component.ts
@@ -57,7 +57,7 @@ export class ParamValuesComponent implements AfterViewInit, Observer {
         this.editValuesDialog.open(
             DialogEditParamValuesComponent,
             {
-                disableClose: true,
+                disableClose: false,
                 data: {
                     param: this.param
                 },
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..0edf3e57c99ed46506e76d4c8f9e9b97db46f811 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,32 @@ 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: { // conflicts with pan; set to false to enable mouse wheel zoom,
+                        borderColor: "rgba(225,225,225,0.3)",
+                        borderWidth: 1,
+                        backgroundColor: "rgba(0,0,0,0.25)"
+                    },
+                    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 +147,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 +356,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 18b63e068b240f0ba53f36ab2bdfee004887847b..aa5bac6865d805850182ac1912022910e9bc3568 100644
--- a/src/app/formulaire/definition/concrete/form-pab.ts
+++ b/src/app/formulaire/definition/concrete/form-pab.ts
@@ -1,11 +1,5 @@
-import { Nub, Props, Session, PabCloisons, Pab } from "jalhyd";
+import { Pab } from "jalhyd";
 
-import { FieldsetContainer } from "../../fieldset-container";
-import { FieldSet } from "../../fieldset";
-import { SelectField } from "../../select-field";
-import { NgParameter } from "../../ngparam";
-import { FieldsetTemplate } from "../../fieldset-template";
-import { FormulaireNode } from "../../formulaire-node";
 import { FormulaireBase } from "./form-base";
 import { FormComputePab } from "../form-compute-pab";
 import { FormResultPab } from "../form-result-pab";
@@ -16,12 +10,6 @@ import { FormResultPab } from "../form-result-pab";
  */
 export class FormulairePab extends FormulaireBase {
 
-    /** id du select configurant le modèle de cloisons */
-    private _modeleCloisonsSelectId: string;
-
-    /** id du select configurant le modèle de la cloison aval */
-    private _modeleCloisonAvalSelectId: string;
-
     constructor() {
         super();
         this._formResult = new FormResultPab(this);
@@ -31,254 +19,26 @@ export class FormulairePab extends FormulaireBase {
         this._formCompute = new FormComputePab(this, (this._formResult as FormResultPab));
     }
 
-    public get modeleCloisonsSelectId(): string {
-        return this._modeleCloisonsSelectId;
-    }
-
-    public get modeleCloisonAvalSelectId(): string {
-        return this._modeleCloisonAvalSelectId;
-    }
-
-    /**
-     * Creates a virgin PabCloisons when a new fieldset is added through the GUI,
-     * to ensure consistency; this object is not related to any Cloisons
-     */
-    private createDummyPabCloisons(templ: FieldsetTemplate): Nub {
-        const params = {};
-        params["calcType"] = templ.calcTypeFromConfig;
-        return this.createBassin(new Props(params));
-    }
-
-    /**
-     * ajoute un Nub PabCloisons
-     * @param after position après laquelle insérer la structure, à la fin sinon
-     */
-    private addPabCloisons(st: PabCloisons, after?: number) {
-        this.pabNub.addChild(st, after);
-    }
-
-    private get pabNub(): Pab {
+    public get pabNub(): Pab {
         return this.currentNub as Pab;
     }
 
-    /**
-     * Asks JaLHyd to create a PabCloisons Nub as a child of the current Calculator Module
-     * and return it; does not store it in the Session (for PabCloisons, not for Calculator Modules)
-     * @param p properties for the new Nub
-     */
-    protected createBassin(p: Props): PabCloisons {
-        return Session.getInstance().createNub(p, this.currentNub as Pab) as PabCloisons;
-    }
-
-    /**
-     * Replaces the current Nub in the current calculator module, with a new one built with properties "params"
-     * @param params properties to build the new Nub (calcType, loiDebit...)
-     */
-    protected replaceNub(sn: PabCloisons, params: Props): Nub {
-        const parent = (this.currentNub as Pab);
-        const newBassin = this.createBassin(params);
-        parent.replaceChildInplace(sn, newBassin);
-        return newBassin;
-    }
-
-    /**
-     * Deleted the given child Nub in the current calculator module
-     * @param params properties to build the new Nub (calcType, loiDebit...)
-     */
-    protected deleteNub(sn: PabCloisons) {
-        const parent = (this.currentNub as PabCloisons);
-        parent.deleteChild(parent.getIndexForChild(sn));
-    }
-
-    public doCompute() {
+    /* public doCompute() {
         this.dumpPabStructure(this.pabNub);
         super.doCompute();
-    }
-
-    public createFieldset(parent: FormulaireNode, json: {}, data?: {}, nub?: Nub): FieldSet {
-        if (json["calcType"] === "PabCloisons") {
-            // indice après lequel insérer le nouveau FieldSet
-            const after = data["after"];
-
-            const res: FieldSet = new FieldSet(parent);
-            let n: Nub;
-            if (nub) { // use existing Nub (build interface based on model)
-                n = nub;
-            } else {
-                n = this.createDummyPabCloisons(data["template"]);
-                this.addPabCloisons(n as PabCloisons, after);
-            }
-            res.setNub(n, false);
-
-            if (after !== undefined) {
-                parent.kids.splice(after + 1, 0, res);
-            } else {
-                parent.kids.push(res);
-            }
-
-            this.resetResults();
-
-            return res;
-        } else {
-            return super.createFieldset(parent, json, data);
-        }
-    }
-
-    protected parseOptions(json: {}) {
-        super.parseOptions(json);
-        // id du select configurant les modèles de cloisons
-        this._modeleCloisonsSelectId = this.getOption(json, "modeleCloisonsSelectId");
-        // id du select configurant le modèle de cloison aval
-        this._modeleCloisonAvalSelectId = this.getOption(json, "modeleCloisonAvalSelectId");
-    }
-
-    public afterParseFieldset(fs: FieldSet) {
-        // si le FieldSet contient le select de modèles de cloisons
-        if (this._modeleCloisonsSelectId) {
-            const node = fs.getFormulaireNodeById(this._modeleCloisonsSelectId);
-            const sel = (node as SelectField);
-            if (sel) {
-                // on abonne le formulaire aux propriétés du FieldSet
-                fs.properties.addObserver(this);
-            }
-        }
-        // si le FieldSet contient le select du modèle de la cloison aval
-        if (this._modeleCloisonAvalSelectId) {
-            const node = fs.getFormulaireNodeById(this._modeleCloisonAvalSelectId);
-            const sel = (node as SelectField);
-            if (sel) {
-                // on abonne le formulaire aux propriétés du FieldSet
-                fs.properties.addObserver(this);
-            }
-        }
-    }
-
-    public moveFieldsetUp(fs: FieldSet) {
-        if (fs.nub instanceof PabCloisons) {
-            // déplacement du nub
-            fs.nub.parent.moveChildUp(fs.nub);
-            // déplacement du fieldset
-            this.fieldsetContainer.moveFieldsetUp(fs);
-
-            this.resetResults();
-        } else {
-            super.moveFieldsetUp(fs);
-        }
-    }
-
-    public moveFieldsetDown(fs: FieldSet) {
-        if (fs.nub instanceof PabCloisons) {
-            // déplacement du nub
-            fs.nub.parent.moveChildDown(fs.nub);
-            // déplacement du fieldset
-            this.fieldsetContainer.moveFieldsetDown(fs);
-
-            this.resetResults();
-        } else {
-            super.moveFieldsetDown(fs);
-        }
-    }
-
-    public removeFieldset(fs: FieldSet) {
-        if (fs.nub instanceof PabCloisons) {
-            // suppression du sous-nub dans le Nub parent
-            this.deleteNub(fs.nub);
-
-            // suppression du fieldset
-            this.fieldsetContainer.removeFieldset(fs);
-
-            this.resetResults();
-        } else {
-            super.removeFieldset(fs);
-        }
-    }
-
-    protected completeParse(json: {}) {
-        this.subscribeFieldsetContainer();
-    }
-
-    private get fieldsetContainer(): FieldsetContainer {
-        const n = this.getFormulaireNodeById("bassin_container");
-        if (n === undefined || !(n instanceof FieldsetContainer)) {
-            throw new Error("l'élément 'bassin_container' n'est pas du type FieldsetContainer");
-        }
-        return n as FieldsetContainer;
-    }
-
-    /**
-     * abonnement en tant qu'observateur du FieldsetContainer
-     */
-    private subscribeFieldsetContainer() {
-        this.fieldsetContainer.addObserver(this);
-    }
-
-    /**
-     * abonnement en tant qu'observateur des NgParameter des FieldSet contenus dans le FieldsetContainer
-     */
-    private subscribeBasinFields(fs: FieldSet) {
-        for (const n of fs.allFormElements) {
-            if (n instanceof NgParameter || n instanceof SelectField) {
-                n.addObserver(this);
-            }
-        }
-    }
-
-    // interface Observer
-
-    public update(sender: any, data: any) {
-
-        super.update(sender, data);
-
-        if (sender instanceof FieldsetContainer) {
-            switch (data.action) {
-                case "newFieldset":
-                    this.reset();
-                    this.subscribeBasinFields(data["fieldset"]);
-            }
-        } else if (sender instanceof FieldSet && data.action === "propertyChange") {
-            switch (sender.id) {
-                case "fs_bassin":
-                    switch (data.name) {
-                        case "modeleCloisons":
-                            // change PabCloisons property "modeleCloisons" and reinit it
-                            // with new Cloisons values
-                            const newModeleUID = sender.properties.getPropValue("modeleCloisons");
-                            (sender.nub as PabCloisons).setModel(newModeleUID);
-                            // treat the fieldset as new to re-subscribe to Nub properties change events
-                            this.afterParseFieldset(sender);
-                            this.reset();
-                            break;
-                    }
-                    break;
-
-                case "fs_cloison_aval":
-                    switch (data.name) {
-                        case "modeleCloisonAval":
-                            // change Pab property "modeleCloisonAval" and reinit it
-                            // with new ParallelStructure values
-                            const newModeleAvalUID = sender.properties.getPropValue("modeleCloisonAval");
-                            this.pabNub.setDownWall(newModeleAvalUID);
-                            // treat the fieldset as new to re-subscribe to Nub properties change events
-                            this.afterParseFieldset(sender);
-                            this.reset();
-                            break;
-                    }
-                    break;
-            }
-        }
-    }
+    } */
 
     // debug method
     private dumpPabStructure(pab: Pab) {
         console.log(`PAB: ${pab.uid}, ${pab.children.length} children`);
         for (const c of pab.children) {
             console.log(
-                ` * child: ${c.uid}, based on ${c.properties.getPropValue("modeleCloisons")}`
-                + ` (cote amont ${c.prms.Z1.singleValue}, longueur ${c.prms.LB.singleValue})`
+                ` * child: ${c.uid} (cote amont ${c.prms.Z1.singleValue}, longueur ${c.prms.LB.singleValue})`
             );
         }
         if (pab.downWall) {
             console.log(`+ downstream wall: ${pab.downWall.uid}`);
         }
+        console.log(`>> calculating: ${pab.calculatedParam.symbol}`);
     }
 }
diff --git a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
index d7d7c8fbe48721db62d1cfbd09bb5f80afe4ef79..79ad66e52b6a2007bab08a85c9657a44a447a170 100644
--- a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
+++ b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
@@ -1,8 +1,7 @@
-import { IObservable, Observer, Session } from "jalhyd";
+import { IObservable, Observer, Session, SectionNub } from "jalhyd";
 import { FormDefSection } from "../form-def-section";
 import { FieldSet } from "../../fieldset";
 import { FormulaireBase } from "./form-base";
-import { SectionNub } from "jalhyd/build/section/section_nub";
 
 export class FormulaireRegimeUniforme extends FormulaireBase implements Observer {
 
diff --git a/src/app/formulaire/definition/form-compute-pab.ts b/src/app/formulaire/definition/form-compute-pab.ts
index c5cfd59c22f8dd2953b6d5f099868fdd30775a59..bcde3d155e6f9944a28fa8311c8c1db4d3d8f347 100644
--- a/src/app/formulaire/definition/form-compute-pab.ts
+++ b/src/app/formulaire/definition/form-compute-pab.ts
@@ -15,18 +15,15 @@ export class FormComputePab extends FormCompute {
         return this._formResult as FormResultPab;
     }
 
-    private getVariatedParameter(): NgParameter {
-        const res = this._formBase.getDisplayedParamFromState(ParamRadioConfig.VAR);
-        if (res !== undefined) {
-            return res;
-        }
-
+    private getVariatedParameters(): NgParameter[] {
+        const res = this._formBase.getDisplayedParamListFromState(ParamRadioConfig.VAR);
         const pms = this._formBase.getDisplayedParamListFromState(ParamRadioConfig.LINK);
         for (const p of pms) {
             if (p.paramDefinition.hasMultipleValues) {
-                return p;
+                res.push(p);
             }
         }
+        return res;
     }
 
     protected compute() {
@@ -37,7 +34,7 @@ export class FormComputePab extends FormCompute {
     protected reaffectResultComponents() {
         const pab: Pab = (this._formBase.currentNub as Pab);
         const computedParam: NgParameter = this.getComputedParameter();
-        const varParam: NgParameter = this.getVariatedParameter();
+        const varParams: NgParameter[] = this.getVariatedParameters();
 
         // résultat de calcul de la passe à bassins
         const pabr = this.formResult.pabResults;
@@ -46,28 +43,35 @@ export class FormComputePab extends FormCompute {
 
         // résultat de chaque cloison
         const cr: Result[] = [];
-        // valeur de Q pour chaque cloison
-        const cq: number[] = [];
-        // valeur de QA pour chaque bassin
-        const bqa: number[] = [];
         for (const c of pab.children) {
             cr.push(c.result);
-            cq.push(c.prms.Q.v);
-            bqa.push(c.prms.QA.v);
         }
         pabr.cloisonsResults = cr,
-        pabr.cloisonsQ = cq,
-        pabr.bassinsQA = bqa;
         // résultat de la cloison aval
         pabr.cloisonAvalResults = pab.downWall.result;
-        // débit de la cloison aval
-        pabr.cloisonAvalQ = pab.downWall.prms.Q.v;
+
         // cote aval de la passe
-        pabr.Z2 = pab.prms.Z2.v;
+        if (varParams.length > 0) {
+            // find longest list
+            let longest = 0;
+            for (let i = 0; i < varParams.length; i++) {
+                const vs = varParams[i].valuesIterator.count();
+                if (vs > longest) {
+                    longest = vs;
+                }
+            }
+            // get extended values lists for Z2
+            const iter = pab.prms.Z2.getExtendedValuesIterator(longest);
+            while (iter.hasNext) {
+                const nv = iter.next();
+                pabr.Z2.push(nv.value);
+            }
+        } else {
+            pabr.Z2 = [ pab.prms.Z2.singleValue ];
+        }
 
-        if (varParam) {
-            pabr.variatedParameter = varParam;
-            // pabr.update(false);
+        if (varParams) {
+            pabr.variatedParameters = varParams;
         }
     }
 
diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index 859cad4fb3953c45572c4a808327d0bad025fb20..6569081e7588e22c576acc9b0f859860f7daf4b1 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -13,6 +13,7 @@ import { DeepFormulaireElementIterator } from "../form-iterator/deep-element-ite
 import { TopFormulaireElementIterator } from "../form-iterator/top-element-iterator";
 import { CalculatorResults } from "../../results/calculator-results";
 import { ServiceFactory } from "../../services/service-factory";
+import { PabTable } from "../pab-table";
 
 /**
  * classe de base pour tous les formulaires
@@ -187,6 +188,12 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
         this.kids.push(fsc);
     }
 
+    private parse_pab_table(json: {}) {
+        const tab: PabTable = new PabTable(this);
+        tab.parseConfig(json);
+        this.kids.push(tab);
+    }
+
     public parseDependencies(json: {}) {
         // tslint:disable-next-line:forin
         for (const conf_index in json) {
@@ -271,6 +278,10 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
                     this.parse_template_container(conf, templates);
                     break;
 
+                case "pab_table": // not generic at all
+                    this.parse_pab_table(conf);
+                    break;
+
                 default:
                     throw new Error(`type d'objet de module de calcul ${type} non pris en charge`);
             }
diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts
index a1eb7b57d4f7b8d49864fad5e3c97f80147b64d0..8d814877614571d60234b3543cf99252e804eb43 100644
--- a/src/app/formulaire/fieldset.ts
+++ b/src/app/formulaire/fieldset.ts
@@ -8,8 +8,6 @@ import { NgParameter, ParamRadioConfig } from "./ngparam";
 import { FormulaireDefinition } from "./definition/form-definition";
 import { StringMap } from "../stringmap";
 import { FormulaireNode } from "./formulaire-node";
-import { SelectFieldModelCloisons } from "./select-field-model-cloisons";
-import { SelectFieldModelCloisonAval } from "./select-field-model-cloison-aval";
 import { FieldsetContainer } from "./fieldset-container";
 
 export class FieldSet extends FormulaireElement implements Observer {
@@ -87,24 +85,6 @@ export class FieldSet extends FormulaireElement implements Observer {
         return res;
     }
 
-    // non-generic version of parse_select for SelectFieldModel because
-    // downcasting is not possible with @Input() apparently
-    private parse_select_cloisons(json: {}): SelectField {
-        const res: SelectFieldModel = new SelectFieldModelCloisons(this, CalculatorType.Cloisons);
-        res.parseConfig(json);
-        res.addObserver(this);
-        return res;
-    }
-
-    // non-generic version of parse_select for SelectFieldCloisonAval because
-    // downcasting is not possible with @Input() apparently
-    private parse_select_cloison_aval(json: {}): SelectField {
-        const res: SelectFieldModel = new SelectFieldModelCloisonAval(this, CalculatorType.ParallelStructure);
-        res.parseConfig(json);
-        res.addObserver(this);
-        return res;
-    }
-
     public get properties(): Props {
         return this.nub.properties;
     }
@@ -182,16 +162,6 @@ export class FieldSet extends FormulaireElement implements Observer {
                     this.addField(param);
                     break;
 
-                case "select_cloisons":
-                    param = this.parse_select_cloisons(field);
-                    this.addField(param);
-                    break;
-
-                case "select_cloison_aval":
-                    param = this.parse_select_cloison_aval(field);
-                    this.addField(param);
-                    break;
-
             }
         }
     }
@@ -410,20 +380,6 @@ export class FieldSet extends FormulaireElement implements Observer {
                         case "select_target": // courbes de remous, variable à calculer
                             this.setPropValue("varCalc", data.value.value);
                             break;
-                        case "select_modele_cloisons": // passe à bassins, modèle de cloisons
-                            let valToSet;
-                            if (data.value) {
-                                valToSet = data.value.id;
-                            }
-                            this.setPropValue("modeleCloisons", valToSet);
-                            break;
-                        case "select_modele_cloison_aval": // passe à bassins, modèle de la cloison aval
-                            let valToSet2;
-                            if (data.value) {
-                                valToSet2 = data.value.id;
-                            }
-                            this.setPropValue("modeleCloisonAval", valToSet2);
-                            break;
                     }
                     break;
             }
diff --git a/src/app/formulaire/pab-table.ts b/src/app/formulaire/pab-table.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9464eeef8ba8baaacf454afd9e3eb201b5c3e103
--- /dev/null
+++ b/src/app/formulaire/pab-table.ts
@@ -0,0 +1,40 @@
+import { Pab } from "jalhyd";
+
+import { FormulaireElement } from "./formulaire-element";
+import { FormulaireNode } from "./formulaire-node";
+import { FormulairePab } from "./definition/concrete/form-pab";
+
+/**
+ * The big editable data grid for calculator type "Pab" (form element).
+ *
+ * This is just a gateway between the model (FormPab and its underlying Pab)
+ * and the user interface (PabTableComponent)
+ */
+export class PabTable extends FormulaireElement {
+
+    constructor(parent: FormulaireNode) {
+        super(parent);
+    }
+
+    public parseDependencies(json: {}) { } // implements abstract method of FormulaireNode
+
+    public parseConfig(json: {}) {
+        this._confId = json["id"];
+    }
+
+    /**
+     * Returns the parent FormulairePab
+     */
+    public get form(): FormulairePab {
+        return this.parentForm as FormulairePab;
+    }
+
+    /**
+     * Returns the Pab model associated to the parent form
+     */
+    public get pab(): Pab {
+        if (this.form) {
+            return this.form.pabNub;
+        }
+    }
+}
diff --git a/src/app/formulaire/select-field-model-cloison-aval.ts b/src/app/formulaire/select-field-model-cloison-aval.ts
deleted file mode 100644
index b178d9af3530ae24ab4d8a691bc3d6877afa7ea9..0000000000000000000000000000000000000000
--- a/src/app/formulaire/select-field-model-cloison-aval.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { Session } from "jalhyd";
-import { SelectEntry } from "./select-entry";
-import { FieldSet } from "./fieldset";
-import { SelectFieldModel } from "./select-field-model";
-
-/**
- * A select field that populates itself with references to
- * available ParallelStructure modules (used by PAB)
- */
-export class SelectFieldModelCloisonAval extends SelectFieldModel {
-
-    protected initSelectedValue() {
-        if (this.parent instanceof FieldSet) {
-            const mc = this.parent.nub.properties.getPropValue("modeleCloisonAval");
-            if (mc) {
-                this._selectedEntry = new SelectEntry(mc, {});
-            } // else if current model is undefined, do not select it so that default ParallelStructures will be chosen (if available)
-        }
-    }
-
-    /**
-     * Populates entries with available ParallelStructures
-     */
-    protected populate() {
-        const psNubs = Session.getInstance().getParallelStructureNubs();
-        for (const cl of psNubs) {
-            const e = new SelectEntry(cl.uid, cl);
-            this.addEntry(e);
-        }
-    }
-}
diff --git a/src/app/formulaire/select-field-model-cloisons.ts b/src/app/formulaire/select-field-model-cloisons.ts
deleted file mode 100644
index 8a0241a4d83ddcd027fd98771c42bcb1d91570b4..0000000000000000000000000000000000000000
--- a/src/app/formulaire/select-field-model-cloisons.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { Session } from "jalhyd";
-import { SelectEntry } from "./select-entry";
-import { FieldSet } from "./fieldset";
-import { SelectFieldModel } from "./select-field-model";
-
-/**
- * A select field that populates itself with references to
- * available Cloisons modules (used by PAB)
- */
-export class SelectFieldModelCloisons extends SelectFieldModel {
-
-    protected initSelectedValue() {
-        if (this.parent instanceof FieldSet) {
-            const mc = this.parent.nub.properties.getPropValue("modeleCloisons");
-            if (mc) {
-                this._selectedEntry = new SelectEntry(mc, {});
-            } // else if current model is undefined, do not select it so that default Cloisons will be chosen (if available)
-        }
-    }
-
-    /**
-     * Populates entries with available Cloisons
-     */
-    protected populate() {
-        const cloisonsNubs = Session.getInstance().getCloisonsNubs();
-        for (const cl of cloisonsNubs) {
-            const e = new SelectEntry(cl.uid, cl);
-            this.addEntry(e);
-        }
-    }
-}
diff --git a/src/app/results/pab-results.ts b/src/app/results/pab-results.ts
index d87b882b30b8eaaffb928613d707cc1b5b077c86..317914470e2565bcb1e82d22ce853dc42ae0560b 100644
--- a/src/app/results/pab-results.ts
+++ b/src/app/results/pab-results.ts
@@ -9,23 +9,20 @@ export class PabResults extends CalculatedParamResults {
     /** résultats des modules Cloisons avant chaque bassin */
     public cloisonsResults: Result[];
 
-    /** valeur de Q pour chaque module Cloisons */
-    public cloisonsQ: number[];
-
-    /** valeur de QA pour chaque bassin */
-    public bassinsQA: number[];
-
     /** résultats du module ParallelStructure pour la cloison aval */
     public cloisonAvalResults: Result;
 
-    /** débit de la cloison aval */
-    public cloisonAvalQ: number;
+    /**
+     * valeurs de la cote aval de l'ensemble de la passe, pour chaque
+     * itération : si aucun paramètre ne varie, ne contient qu'un élément
+     * */
+    public Z2: number[];
 
-    /** cote aval de l'ensemble de la passe */
-    public Z2: number;
+    /** paramètres variés */
+    public variatedParameters: NgParameter[];
 
-    /** paramètre varié */
-    public variatedParameter: NgParameter;
+    /** index de la valeur du paramètre varié à afficher dans les résultats */
+    public variableIndex = 0;
 
     /** symboles des colonnes de résultat */
     protected _columns: string[];
@@ -42,7 +39,7 @@ export class PabResults extends CalculatedParamResults {
             "Q",
             "PV",
             "YMOY",
-            "ZRB",
+            "ZRMB",
             "QA"
         ];
     }
@@ -68,11 +65,31 @@ export class PabResults extends CalculatedParamResults {
     public reset() {
         super.reset();
         this.cloisonsResults = [];
-        this.cloisonsQ = [];
-        this.bassinsQA = [];
         this.cloisonAvalResults = undefined;
-        this.cloisonAvalQ = undefined;
-        this.Z2 = undefined;
+        this.Z2 = [];
+    }
+
+    /**
+     * Returns true if at least one log message is present in the PAB result or any
+     * of the children results
+     */
+    public get hasLog(): boolean {
+        if (this.cloisonsResults) {
+            for (const cr of this.cloisonsResults) {
+                if (cr && cr.hasLog) {
+                    return true;
+                }
+            }
+        }
+        return (
+            (this.result && this.result.hasLog)
+            || (this.cloisonAvalResults && this.cloisonAvalResults.hasLog)
+        );
+    }
+
+    // do not test result.ok else log messages will prevent partial results from being displayed
+    public get hasResults(): boolean {
+        return this.result !== undefined && ! this.result.hasOnlyErrors;
     }
 
     /** retourne true si au moins un calcul a échoué (le log a un code négatif) */
@@ -89,4 +106,32 @@ export class PabResults extends CalculatedParamResults {
 
         return err;
     }
+
+    /** retourne true si le calcul à l'itération i a échoué */
+    public iterationHasError(i: number): boolean {
+        let err = this.result.resultElements[i].hasErrorMessages;
+        // logs des cloisons
+        for (const c of this.cloisonsResults) {
+            err = (err || c.resultElements[i].hasErrorMessages);
+        }
+        // log de la cloison aval
+        err = (err || this.cloisonAvalResults.resultElements[i].hasErrorMessages);
+
+        return err;
+    }
+
+    /** retourne true si tous les calculs ont échoué */
+    public hasOnlyErrors(): boolean {
+        let err = true;
+        // log principal
+        err = (err && this.result.hasOnlyErrors);
+        // logs des cloisons
+        for (const c of this.cloisonsResults) {
+            err = (err && c.hasOnlyErrors);
+        }
+        // log de la cloison aval
+        err = (err && this.cloisonAvalResults.hasOnlyErrors);
+
+        return err;
+    }
 }
diff --git a/src/app/results/plottable-pab-results.ts b/src/app/results/plottable-pab-results.ts
index 27e5685d9d0c257798729acf56326acf3c22d1dc..57a4dd318b9c815ad3f36d5c65959ea61c78c7d8 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 */
@@ -30,7 +30,11 @@ export class PlottablePabResults implements PlottableData {
      * @param symbol parameter / result symbol (ex: "Q")
      */
     public getChartAxisLabel(symbol: string): string {
-        return this.pabResults.headers[this.pabResults.columns.indexOf(symbol)];
+        if (symbol === "x") { // specific case for wall abscissa
+            return ServiceFactory.instance.i18nService.localizeText("INFO_LIB_ABSCISSE_CLOISON");
+        } else {
+            return this.pabResults.headers[this.pabResults.columns.indexOf(symbol)];
+        }
     }
 
     public expandLabelFromSymbol(symbol: string): string {
@@ -42,7 +46,8 @@ export class PlottablePabResults implements PlottableData {
      * as X or Y chart axis
      */
     public getAvailableChartAxis(): string[] {
-        return this.pabResults.columns;
+        // add wall abscissa on the fly
+        return [ "x" ].concat(this.pabResults.columns);
     }
 
     // just to implement interface
@@ -59,95 +64,65 @@ export class PlottablePabResults implements PlottableData {
         const pr = this.pabResults;
         const nDigits = ServiceFactory.instance.applicationSetupService.displayDigits;
         const l = this.pabResults.cloisonsResults.length;
+        // when a parameter is variating, index of the variating parameter
+        // values to build the data from
+        const vi = this.pabResults.variableIndex;
 
-        if (this.hasError()) {
+        if (this.pabResults.iterationHasError(vi)) {
             return [];
         }
 
         switch (symbol) {
             case "CLOISON":
                 data.push("");
-                for (let i = 0; i < l; i++) {
+                for (let i = 0; i <= l; i++) { // <= for one extra step (downwall)
                     data.push("" + (i + 1));
                 }
-                data.push(ServiceFactory.instance.i18nService.localizeText("INFO_LIB_AVAL"));
                 break;
 
             case "DH":
-                data.push("");
-                for (let i = 0; i < l - 1; i++) {
-                    data.push((pr.cloisonsResults[i].vCalc - pr.cloisonsResults[i + 1].vCalc).toFixed(nDigits));
-                }
-                data.push((pr.cloisonsResults[l - 1].vCalc - pr.cloisonAvalResults.vCalc).toFixed(nDigits));
-                data.push((pr.cloisonAvalResults.vCalc - pr.Z2).toFixed(nDigits));
-                break;
-
             case "ZRAM":
+            case "Q":
                 data.push("");
                 for (let i = 0; i < l; i++) {
-                    const er = pr.cloisonsResults[i].getExtraResult("ZRAM");
+                    const er = pr.cloisonsResults[i].resultElements[vi].getExtraResult(symbol);
                     data.push((er !== undefined) ? er.toFixed(nDigits) : "");
                 }
-                const zrAval = pr.cloisonAvalResults.getExtraResult("ZRAM");
+                const zrAval = pr.cloisonAvalResults.resultElements[vi].getExtraResult(symbol);
                 data.push((zrAval !== undefined) ? zrAval.toFixed(nDigits) : "");
                 break;
 
-            case "Q":
-                data.push("");
-                for (let i = 0; i < l; i++) {
-                    data.push(pr.cloisonsQ[i].toFixed(nDigits));
-                }
-                data.push(pr.cloisonAvalQ.toFixed(nDigits));
-                break;
-
             case "Z":
-                for (let i = 0; i < l - 1; i++) {
-                    data.push(pr.cloisonsResults[i].vCalc.toFixed(nDigits));
-                }
-                data.push(pr.cloisonAvalResults.vCalc.toFixed(nDigits));
-                data.push(pr.Z2.toFixed(nDigits));
-                break;
-
-            case "PV":
-                data.push("");
                 for (let i = 0; i < l; i++) {
-                    const er = pr.cloisonsResults[i].getExtraResult("PV");
-                    data.push((er !== undefined) ? er.toFixed(nDigits) : "");
+                    data.push(pr.cloisonsResults[i].resultElements[vi].vCalc.toFixed(nDigits));
                 }
-                data.push("");
+                data.push(pr.cloisonAvalResults.resultElements[vi].vCalc.toFixed(nDigits));
+                data.push(pr.Z2[vi].toFixed(nDigits));
                 break;
 
+            case "PV":
             case "YMOY":
+            case "ZRMB":
+            case "QA":
                 data.push("");
                 for (let i = 0; i < l; i++) {
-                    const er = pr.cloisonsResults[i].getExtraResult("YMOY");
+                    const er = pr.cloisonsResults[i].resultElements[vi].getExtraResult(symbol);
                     data.push((er !== undefined) ? er.toFixed(nDigits) : "");
                 }
                 data.push("");
                 break;
 
-            case "ZRB":
+            case "x": // wall abscissa
                 data.push("");
                 for (let i = 0; i < l; i++) {
-                    const er = pr.cloisonsResults[i].getExtraResult("ZRB");
+                    const er = pr.cloisonsResults[i].resultElements[vi].getExtraResult(symbol);
                     data.push((er !== undefined) ? er.toFixed(nDigits) : "");
                 }
-                data.push("");
-                break;
-
-            case "QA":
-                data.push("");
-                for (let i = 0; i < l; i++) {
-                    data.push(pr.bassinsQA[i].toFixed(nDigits));
-                }
-                data.push("");
+                const erXdw = pr.cloisonAvalResults.resultElements[vi].getExtraResult(symbol);
+                data.push((erXdw !== undefined) ? erXdw.toFixed(nDigits) : "");
                 break;
         }
 
         return data;
     }
-
-    protected hasError(): boolean {
-        return this.pabResults.hasError();
-    }
 }
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index d33770c26e587986bf63dca797c6f4ffc4774856..c9dea5a285aba0180f1733aa4b870a6526096026 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -3,7 +3,19 @@ import { Injectable } from "@angular/core";
 import { decode } from "he";
 import { saveAs } from "file-saver";
 
-import { CalculatorType, LinkedValue, Observable, ParamDefinition, Session, Nub, ParallelStructure, Pab } from "jalhyd";
+import {
+    CalculatorType,
+    LinkedValue,
+    Observable,
+    ParamDefinition,
+    Session,
+    Nub,
+    ParallelStructure,
+    Pab,
+    Props,
+    Cloisons,
+    CloisonAval
+} from "jalhyd";
 
 import { HttpService } from "../../services/http/http.service";
 import { I18nService } from "../../services/internationalisation/internationalisation.service";
@@ -333,16 +345,44 @@ export class FormulaireService extends Observable {
                 }
             }
 
-            // add fieldsets for existing PabCloisons if needed
-            // (when loading session only)
-            if (f.currentNub instanceof Pab) {
-                for (const child of f.currentNub.children) {
-                    for (const e of f.allFormElements) {
-                        if (e instanceof FieldsetContainer) { // @TODO manage many containers one day ?
-                            e.addFromTemplate(0, undefined, child);
-                        }
-                    }
-                }
+            // when creating a new Pab, add one wall with one device, plus the downwall
+            // (when loading session, those items are already present)
+            if (
+                f instanceof FormulairePab
+                && f.currentNub instanceof Pab
+                && f.currentNub.children.length === 0
+                && f.currentNub.downWall === undefined
+            ) {
+                // 1. one wall
+                const newWall = Session.getInstance().createNub(
+                    new Props({
+                        calcType: CalculatorType.Cloisons
+                    })
+                ) as Cloisons;
+                // add new default device for new wall
+                const newDevice = Session.getInstance().createNub(
+                    new Props({
+                        calcType: CalculatorType.Structure,
+                        loiDebit: newWall.getDefaultLoiDebit()
+                    })
+                );
+                newWall.addChild(newDevice);
+                f.pabNub.addChild(newWall);
+                // 2. downwall
+                const newDownWall = Session.getInstance().createNub(
+                    new Props({
+                        calcType: CalculatorType.CloisonAval
+                    })
+                ) as CloisonAval;
+                // add new default device for new downwall
+                const newDownwallDevice = Session.getInstance().createNub(
+                    new Props({
+                        calcType: CalculatorType.Structure,
+                        loiDebit: newDownWall.getDefaultLoiDebit()
+                    })
+                );
+                newDownWall.addChild(newDownwallDevice);
+                f.pabNub.downWall = newDownWall;
             }
 
             return f;
@@ -584,21 +624,6 @@ export class FormulaireService extends Observable {
                             }
                         });
                     }
-                    // list Cloisons / downWall models dependencies for each PAB Nub
-                    if (e.props.calcType === CalculatorType.Pab) {
-                        // Cloisons models
-                        e.children.forEach((c) => {
-                            if (c.props.calcType === CalculatorType.PabCloisons) { // who knows ?
-                                if (c.props.modeleCloisons && ! nubInfo.requires.includes(c.props.modeleCloisons)) {
-                                    nubInfo.requires.push(c.props.modeleCloisons);
-                                }
-                            }
-                        });
-                        // Downstream wall
-                        if (e.props.modeleCloisonAval && ! nubInfo.requires.includes(e.props.modeleCloisonAval)) {
-                            nubInfo.requires.push(e.props.modeleCloisonAval);
-                        }
-                    }
                     // list children nubs for each Nub
                     if (e.children) {
                         e.children.forEach((p) => {
diff --git a/src/app/services/internationalisation/internationalisation.service.ts b/src/app/services/internationalisation/internationalisation.service.ts
index bd560fae2a17b65ead3d8a281f61c04dc3a0e958..f95bcc9ba2a26d4f29cc52698770142a585453a8 100644
--- a/src/app/services/internationalisation/internationalisation.service.ts
+++ b/src/app/services/internationalisation/internationalisation.service.ts
@@ -1,6 +1,6 @@
 import { Injectable, isDevMode } from "@angular/core";
 
-import { Message, MessageCode, Observable, Observer, CalculatorType } from "jalhyd";
+import { Message, MessageCode, Observable, Observer, CalculatorType, LoiDebit } from "jalhyd";
 
 import { StringMap } from "../../stringmap";
 import { ApplicationSetupService } from "../app-setup/app-setup.service";
@@ -173,6 +173,14 @@ export class I18nService extends Observable implements Observer {
         return m;
     }
 
+    /**
+     * Finds the localized title for a LoiDebit item
+     * @TODO add StructureType context ? (ex: cem88d / cem88v)
+     */
+    public localizeLoiDebit(l: LoiDebit) {
+        return this.localizeText("INFO_LOIDEBIT_" + LoiDebit[l]);
+    }
+
     private replaceAll(str: string, find: string, replace: string) {
         return str.replace(new RegExp(find, "g"), replace);
     }
diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json
index 18aff4ce809251b42820e1da10648c4da458b43e..78e3384d637e0a52cca6d3e71361631bd9639783 100644
--- a/src/locale/messages.en.json
+++ b/src/locale/messages.en.json
@@ -1,13 +1,14 @@
 {
+    "ERROR_CLOISON_AVAL_UN_OUVRAGE_REGULE": "Only one regulated device is allowed on the downstream wall",
     "ERROR_DICHO_CONVERGE": "Dichotomy could not converge",
     "ERROR_DICHO_FUNCTION_VARIATION": "unable to determinate function direction of variation",
     "ERROR_DICHO_INIT_DOMAIN": "Dichotomy: target %targetSymbol%=%targetValue% does not exist for variable %variableSymbol% valued in interval %variableInterval%",
-    "ERROR_DICHO_INITVALUE_HIGH": "Dichotomy: initial value %variableSymbol%=%variableInitValue% is too high (target is %targetSymbol%=%targetValue%, %targetSymbol%(%variableSymbol%=%variableInitValue%)=%initTarget%)",
-    "ERROR_DICHO_INITVALUE_LOW": "Dichotomy: initial value %variableSymbol%=%variableInitValue% is too low (target is %targetSymbol%=%targetValue%, %targetSymbol%(%variableSymbol%=%variableInitValue%)=%initTarget%)",
     "ERROR_DICHO_INVALID_STEP_GROWTH": "Dichotomy (initial interval search): invalid null step growth",
     "ERROR_DICHO_NULL_STEP": "Dichotomy (initial interval search): invalid null step",
+    "ERROR_DICHO_TARGET_TOO_HIGH": "Dichotomy: the solution %targetSymbol%=%targetValue% is greater than the maximum computable value %targetSymbol%(%variableSymbol%=%variableExtremeValue%)=%extremeTarget%)",
+    "ERROR_DICHO_TARGET_TOO_LOW": "Dichotomy: the solution %targetSymbol%=%targetValue%  is lower than the minimum computable value %targetSymbol%(%variableSymbol%=%variableExtremeValue%)=%extremeTarget%)",
     "ERROR_ELEVATION_ZI_LOWER_THAN_Z2": "Upstream elevation is lower than downstream elevation",
-    "ERROR_INTERVAL_OUTSIDE": "Interval: value %value% is outside of %interval",
+    "ERROR_INTERVAL_OUTSIDE": "Interval: value %value% is outside of %interval%",
     "ERROR_INTERVAL_UNDEF": "Interval: invalid 'undefined' value",
     "ERROR_INVALID_AT_POSITION": "Position %s:",
     "ERROR_LOADING_SESSION": "Unable to load session",
@@ -15,7 +16,10 @@
     "ERROR_MINMAXSTEP_MIN": "Value is not in [%s,%s[",
     "ERROR_MINMAXSTEP_STEP": "Value is not in %s",
     "ERROR_NEWTON_DERIVEE_NULLE": "Null function derivative in Newton computation",
+    "ERROR_PAB_Z1_LOWER_THAN_Z2": "Upstream water elevation should be higher than downstream water elevation",
+    "ERROR_PAB_Z1_LOWER_THAN_UPSTREAM_WALL": "Upstream water elevation is too low for water to flow through the first wall",
     "ERROR_PARAM_MUST_BE_A_NUMBER": "Please type a numeric value",
+    "ERROR_PARAM_MUST_BE_AT_LEAST": "Value must be >= %s",
     "ERROR_PARAM_MUST_BE_POSITIVE": "Please type a positive numeric value",
     "ERROR_PARAM_NULL": "Parameter value must not be NULL",
     "ERROR_PARAMDEF_CALC_UNDEFINED": "calculability of '%symbol%' parameter is undefined",
@@ -58,6 +62,15 @@
     "INFO_DEVER_TITRE_COURT": "Free weir",
     "INFO_DEVER_TITRE": "Free flow weir stage-discharge laws",
     "INFO_DIALOG_COMPUTED_VALUE_TITLE": "Edit initial value",
+    "INFO_DIALOG_EDIT_PAB_INTERPOLATION_BOUNDS": "between %s and %s",
+    "INFO_DIALOG_EDIT_PAB_N_DEVICES": "%s device(s)",
+    "INFO_DIALOG_EDIT_PAB_N_WALLS": "%s wall(s) : %s device(s)",
+    "INFO_DIALOG_EDIT_PAB_N_WALLS_P_DEVICES": "%s wall(s) + %s device(s) : %s devices",
+    "INFO_DIALOG_EDIT_PAB_OPTION_VARIABLE": "Variable to edit",
+    "INFO_DIALOG_EDIT_PAB_OPTION_SET_VALUE": "Fixed value",
+    "INFO_DIALOG_EDIT_PAB_OPTION_DELTA": "Delta",
+    "INFO_DIALOG_EDIT_PAB_OPTION_INTERPOLATE": "Interpolate",
+    "INFO_DIALOG_EDIT_PAB_TITLE": "Edit values",
     "INFO_DIALOG_EMPTY_CURRENT_SESSION": "Empty current session",
     "INFO_DIALOG_FIX_MISSING_DEPENDENCIES": "Fix missing dependencies",
     "INFO_DIALOG_FORMAT_VERSIONS_MISMATCH": "File format versions mismatch (file: %s, jalhyd: %s)",
@@ -88,26 +101,42 @@
     "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_1": "Partially submerged",
     "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_2": "Submerged",
     "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_3": "Zero flow",
-    "INFO_FSC_FS_ADDED": "1 device added",
-    "INFO_FSC_FS_ADDED_N_TIMES": "%s devices added",
-    "INFO_FSC_FS_COPIED": "Device #%s copied",
-    "INFO_FSC_FS_COPIED_N_TIMES": "Device #%s copied %s times",
-    "INFO_FSC_FS_MOVED": "Device #%s moved",
-    "INFO_FSC_FS_REMOVED": "Device #%s removed",
+    "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",
+    "INFO_DEVICE_COPIED_N_TIMES": "Device #%s copied %s times",
+    "INFO_DEVICE_MOVED": "Device #%s moved",
+    "INFO_DEVICE_REMOVED": "Device #%s removed",
+    "INFO_WALL_ADDED": "1 wall added",
+    "INFO_WALL_ADDED_N_TIMES": "%s walls added",
+    "INFO_WALL_COPIED": "Wall #%s copied",
+    "INFO_WALL_COPIED_N_TIMES": "Wall #%s copied %s times",
+    "INFO_WALL_MOVED": "Wall #%s moved",
+    "INFO_WALL_REMOVED": "Wall #%s removed",
     "INFO_LECHAPTCALMON_TITRE_COURT": "Lechapt-C.",
     "INFO_LECHAPTCALMON_TITRE": "Lechapt-Calmon",
+    "INFO_LIB_ABSCISSE_CLOISON": "Wall abscissa",
     "INFO_LIB_ALPHA": "Alpha coefficient",
     "INFO_LIB_ALPHA2": "Half-angle at the apex",
+    "INFO_LIB_AMONT": "Upstream",
     "INFO_LIB_AVAL": "Downstream",
     "INFO_LIB_B": "Surface width",
+    "INFO_LIB_BB": "Pool width",
     "INFO_LIB_BETA": "Beta coefficient",
     "INFO_LIB_BT": "Half opening of the triangle",
     "INFO_LIB_CD": "Discharge coefficient",
     "INFO_LIB_CLOISON": "Cross wall #",
+    "INFO_LIB_COTE": "Elevation (m)",
+    "INFO_LIB_COTE_VANNE_LEVANTE": "Lift gate elevation",
     "INFO_LIB_CV": "Cv: Velocity coefficient",
     "INFO_LIB_CVQT": "CV.QT: Corrected discharge",
     "INFO_LIB_DH": "Fall",
     "INFO_LIB_DHR": "DHR : Residual fall",
+    "INFO_LIB_DISTANCE_AMONT": "Distance from upstream (m)",
     "INFO_LIB_EC": "EC: Kinetic energy",
     "INFO_LIB_ENUM_MACRORUGOFLOWTYPE": "Flow type",
     "INFO_LIB_FLU": "Subcritical water line",
@@ -115,16 +144,23 @@
     "INFO_LIB_FS_OUVRAGE": "Device",
     "INFO_LIB_FS_PARAM_CALC": "Calculation parameters",
     "INFO_LIB_FS_PARAM_HYDRO": "Hydraulic parameters",
+    "INFO_LIB_H1": "Head",
     "INFO_LIB_HS": "Specific head",
     "INFO_LIB_HSC": "Critical head",
     "INFO_LIB_I-J": "Linear variation of specific head",
     "INFO_LIB_IMP": "Impulse",
     "INFO_LIB_J": "Head loss",
     "INFO_LIB_L": "Weir width",
-    "INFO_LIB_OUVRAGE_Q_MODE": "Mode",
-    "INFO_LIB_OUVRAGE_Q_REGIME": "Regime",
+    "INFO_LIB_LB": "Pool length",
+    "INFO_LIB_LIGNE_D_EAU": "Water line",
+    "INFO_LIB_MINZDV": "Minimal crest elevation",
+    "INFO_LIB_MAXZDV": "Maximal crest elevation",
+    "INFO_LIB_OUVRAGE_Q_ENUM_STRUCTUREFLOWMODE": "Mode",
+    "INFO_LIB_OUVRAGE_Q_ENUM_STRUCTUREFLOWREGIME": "Regime",
     "INFO_LIB_OUVRAGE_Q": "Discharge",
+    "INFO_LIB_OUVRAGE_ZDV": "Sill elevation",
     "INFO_LIB_P": "Wetted perimeter",
+    "INFO_LIB_PB": "Basin mean depth",
     "INFO_LIB_PR": "Display accuracy",
     "INFO_LIB_PV": "Volumic dissipated power",
     "INFO_LIB_Q_GUIDETECH": "Technical guide flow",
@@ -132,6 +168,7 @@
     "INFO_LIB_QA": "Attraction flow",
     "INFO_LIB_R": "Hydraulic radius",
     "INFO_LIB_S": "Orifice area",
+    "INFO_LIB_RADIER": "Basin bottom",
     "INFO_LIB_SELECT_LOIDEBIT": "Stage-discharge law",
     "INFO_LIB_SELECT_LOIDEBIT1_KIVI": "Kindsvater-Carter and Villemonte",
     "INFO_LIB_SELECT_LOIDEBIT1": "Stage-discharge law",
@@ -147,6 +184,7 @@
     "INFO_LIB_V": "Average speed",
     "INFO_LIB_VDEB": "Conveyance speed",
     "INFO_LIB_VMAX": "Maximal speed",
+    "INFO_LIB_W": "Gate opening",
     "INFO_LIB_YC": "Critical depth",
     "INFO_LIB_YCO": "Conjugate depth",
     "INFO_LIB_YF": "Subcritical depth",
@@ -159,7 +197,7 @@
     "INFO_LIB_ZDV": "Crest weir elevation or gate base",
     "INFO_LIB_ZF2": "Downstream bottom elevation",
     "INFO_LIB_ZRAM": "Upstream apron elevation",
-    "INFO_LIB_ZRB": "Downstream basin bottom elevation",
+    "INFO_LIB_ZRMB": "Downstream basin bottom elevation",
     "INFO_LIB_ZT": "Triangle top elevation",
     "INFO_LINKED_VALUE_DEVICE_RESULT": "%s (%s, device %s)",
     "INFO_LINKED_VALUE_DEVICE": "%s (%s, device %s)",
@@ -168,6 +206,22 @@
     "INFO_LINKED_VALUE_RESULT": "%s (%s)",
     "INFO_LINKED_VALUE_SECTION_RESULT": "%s (%s, section)",
     "INFO_LINKED_VALUE_SECTION": "%s (%s, section)",
+    "INFO_LOIDEBIT_KIVI": "Kindsvater-Carter and Villemonte",
+    "INFO_LOIDEBIT_WeirSubmergedLarinier": "Submerged slot (Larinier 1992)",
+    "INFO_LOIDEBIT_OrificeSubmerged": "Submerged orifice",
+    "INFO_LOIDEBIT_TriangularWeirFree": "Free flow triangular weir",
+    "INFO_LOIDEBIT_TriangularTruncWeirFree": "Free flow truncated triangular weir",
+    "INFO_LOIDEBIT_GateCem88d": "Weir/Orifice Cemagref 88",
+    "INFO_LOIDEBIT_GateCem88v": "Weir/Undershot gate Cemagref 88",
+    "INFO_LOIDEBIT_WeirCem88d": "Weir/Orifice Cemagref 88",
+    "INFO_LOIDEBIT_WeirCem88v": "Weir/Undershot gate Cemagref 88",
+    "INFO_LOIDEBIT_Cunge80": "Cunge 80",
+    "INFO_LOIDEBIT_RectangularOrificeFree": "Free flow gate",
+    "INFO_LOIDEBIT_RectangularOrificeSubmerged": "Submerged gate",
+    "INFO_LOIDEBIT_VanLevLarinier": "Regulated submerged slot (Larinier 1992)",
+    "INFO_LOIDEBIT_VanLevVillemonte": "Regulated notch (Villemonte 1957)",
+    "INFO_LOIDEBIT_WeirFree": "Free flow weir",
+    "INFO_LOIDEBIT_WeirVillemonte": "Notch (Villemonte 1957)",
     "INFO_MACRORUGO_TITRE_COURT": "Rock-ramp",
     "INFO_MACRORUGO_TITRE": "Rock-ramp fishpasses",
     "INFO_MENU_EMPTY_SESSION_TITLE": "New session",
@@ -193,8 +247,17 @@
     "INFO_OPTION_VALIDATE": "Validate",
     "INFO_OPTION_YES": "Yes",
     "INFO_OUVRAGE": "Structure",
+    "INFO_PAB_BASSIN": "Basin",
+    "INFO_PAB_NUM_BASSIN": "Basin #",
+    "INFO_PAB_HEADER_TYPE": "Type",
+    "INFO_PAB_HEADER_PARAMETERS": "Parameters",
+    "INFO_PAB_HEADER_VALUES": "Values",
+    "INFO_PAB_CLOISON_OUVRAGE_N": "Wall : device #%s",
+    "INFO_PAB_PARAMETRES_FIXES": "All parameters must be fixed",
     "INFO_PAB_TITRE_COURT": "Fish ladder",
     "INFO_PAB_TITRE": "Fish ladder",
+    "INFO_PAB_TITRE_PROFIL": "Fish ladder longitudinal profile",
+    "INFO_PAB_TABLE": "Fish ladder geometry",
     "INFO_PABCHUTE_TITRE_COURT": "FL: fall",
     "INFO_PABCHUTE_TITRE": "Fish ladder: fall",
     "INFO_PABDIMENSIONS_TITRE_COURT": "FL: dimensions",
@@ -206,6 +269,7 @@
     "INFO_PARALLELSTRUCTURE_TITRE_COURT": "// structures",
     "INFO_PARALLELSTRUCTURE_TITRE": "Parallel structures",
     "INFO_PARAMFIELD_AWAITING_CALCULATION": "Awaiting calculation",
+    "INFO_PARAMFIELD_BOUNDARY_CONDITIONS": "Boundary conditions",
     "INFO_PARAMFIELD_CALCULATED": "Calculated",
     "INFO_PARAMFIELD_CALCULATION_FAILED": "Calculation failed",
     "INFO_PARAMFIELD_GRAPH_SELECT_X_AXIS": "Variable for X axis",
@@ -238,6 +302,7 @@
     "INFO_PARAMFIELD_VARIATED": "Variated",
     "INFO_PARAMMODE_LIST": "Values list",
     "INFO_PARAMMODE_MINMAX": "Min/max",
+    "WARNING_PROBLEMS_ENCOUNTERED": "Problems occurred during calculation (info: %info%, warning: %warning%, error: %error%)",
     "INFO_REGIMEUNIFORME_TITRE_COURT": "Uniform flow",
     "INFO_REGIMEUNIFORME_TITRE": "Uniform flow calculation",
     "INFO_REMOUS_CALCUL_FLUVIAL": "Downstream boundary condition >= Critical elevation: calculation of subcritical part from downstream",
@@ -282,9 +347,12 @@
     "INFO_THEME_PASSE_NATURELLE_DESCRIPTION": "Tools for sizing a natural fish pass also called macroroughness pass or rock-ramp fish pass",
     "INFO_THEME_PASSE_NATURELLE_TITRE": "Natural pass",
     "INFO_TITREJOURNAL": "Calculation log",
+    "INFO_TITREJOURNAL_GLOBAL": "Calculation log synthesis",
     "INFO_WELCOME_CONTENT": "<p>The Cassiopée software was developed by <a href=\"https://www.afbiodiversite.fr\" target=\"_blank\">AFB</a> (French Agency for Biodiversity) and <a href=\"http://g-eau.fr/index.php/en/\" target=\"_blank\">UMR G-EAU</a> (Joint Research Unit \"Water Management, Actors, Territories\").</p><p>It includes tools for designing fish passes, and hydraulic calculation tools useful for environmental and agricultural engineering.</p><p>For more information, consult <a href=\"assets/docs-fr/mentions_legales.html\" target=\"_blank\">legal notice</a> and <a href=\"assets/docs-fr/index.html\" target=\"_blank\">documentation</a>.</p>",
     "INFO_WELCOME_SUBTITLE": "Hydraulic calculators",
     "WARNING_REMOUS_ARRET_CRITIQUE": "Calculation stopped: critical elevation reached at abscissa %x%",
     "WARNING_STRUCTUREKIVI_HP_TROP_ELEVE": "h/p must not be greater than 2.5. h/p is forced to 2.5",
-    "WARNING_STRUCTUREKIVI_PELLE_TROP_FAIBLE": "Threshold height should be greater than 0.1 m. Beta coefficient is forced to 0"
+    "WARNING_STRUCTUREKIVI_PELLE_TROP_FAIBLE": "Threshold height should be greater than 0.1 m. Beta coefficient is forced to 0",
+    "WARNING_VANLEV_ZDV_INF_MIN": "Regulated weir: minimum sill elevation reached",
+    "WARNING_VANLEV_ZDV_SUP_MAX": "Regulated weir: maximum sill elevation reached"
 }
diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json
index 1f1db90016de2f86a955686a596e8f1173854573..e9135bada0233eb094b618f0933407f3d1f5a239 100644
--- a/src/locale/messages.fr.json
+++ b/src/locale/messages.fr.json
@@ -1,13 +1,14 @@
 {
+    "ERROR_CLOISON_AVAL_UN_OUVRAGE_REGULE": "Un seul ouvrage régulé est autorisé sur la cloison aval",
     "ERROR_DICHO_CONVERGE": "La dichotomie n'a pas pu converger",
     "ERROR_DICHO_FUNCTION_VARIATION": "Dichotomie&nbsp;: impossible de determiner le sens de  variation de la fonction",
     "ERROR_DICHO_INIT_DOMAIN": "Dichotomie&nbsp;: la valeur cible %targetSymbol%=%targetValue% n'existe pas pour la variable %variableSymbol% prise dans l'intervalle %variableInterval%",
-    "ERROR_DICHO_INITVALUE_HIGH": "Dichotomie&nbsp;: la valeur initiale %variableSymbol%=%variableInitValue% est trop grande (la valeur cible est %targetSymbol%=%targetValue%, %targetSymbol%(%variableSymbol%=%variableInitValue%)=%initTarget%)",
-    "ERROR_DICHO_INITVALUE_LOW": "Dichotomie&nbsp;: la valeur initiale %variableSymbol%=%variableInitValue% est trop petite (la valeur cible est %targetSymbol%=%targetValue%, %targetSymbol%(%variableSymbol%=%variableInitValue%)=%initTarget%)",
     "ERROR_DICHO_INVALID_STEP_GROWTH": "Dichotomie&nbsp;: l'augmentation du pas pour la recherche de l'intervalle de départ est incorrecte (=0)",
     "ERROR_DICHO_NULL_STEP": "Dichotomie&nbsp;: le pas pour la recherche de l'intervalle de départ ne devrait pas être nul",
+    "ERROR_DICHO_TARGET_TOO_HIGH": "Dichotomie&nbsp;: la solution %targetSymbol%=%targetValue% est supérieure à la valeur maximale calculable %targetSymbol%(%variableSymbol%=%variableExtremeValue%)=%extremeTarget%)",
+    "ERROR_DICHO_TARGET_TOO_LOW": "Dichotomie&nbsp;: la solution %targetSymbol%=%targetValue% est inférieure à la valeur minimale calculable %targetSymbol%(%variableSymbol%=%variableExtremeValue%)=%extremeTarget%)",
     "ERROR_ELEVATION_ZI_LOWER_THAN_Z2": "La cote amont est plus basse que la cote aval",
-    "ERROR_INTERVAL_OUTSIDE": "Interval&nbsp;: la valeur %value% est hors de l'intervalle %interval",
+    "ERROR_INTERVAL_OUTSIDE": "Intervalle&nbsp;: la valeur %value% est hors de l'intervalle %interval%",
     "ERROR_INTERVAL_UNDEF": "Interval&nbsp;: valeur 'undefined' incorrecte",
     "ERROR_INVALID_AT_POSITION": "Position %s :",
     "ERROR_LOADING_SESSION": "Impossible de charger la session",
@@ -15,7 +16,10 @@
     "ERROR_MINMAXSTEP_MIN": "La valeur n'est pas dans [%s,%s[",
     "ERROR_MINMAXSTEP_STEP": "La valeur n'est pas dans %s",
     "ERROR_NEWTON_DERIVEE_NULLE": "Dérivée nulle dans un calcul par la méthode de Newton",
+    "ERROR_PAB_Z1_LOWER_THAN_Z2": "La cote de l'eau amont doit être supérieure à la cote de l'eau aval",
+    "ERROR_PAB_Z1_LOWER_THAN_UPSTREAM_WALL": "La cote de l'eau amont est trop basse pour que l'eau s'écoule à travers la première cloison",
     "ERROR_PARAM_MUST_BE_A_NUMBER": "Veuillez entrer une valeur numérique",
+    "ERROR_PARAM_MUST_BE_AT_LEAST": "La valeur doit être >= %s",
     "ERROR_PARAM_MUST_BE_POSITIVE": "Veuillez entrer une valeur numérique positive",
     "ERROR_PARAM_NULL": "La valeur du paramètre ne peut pas être NULL",
     "ERROR_PARAMDEF_CALC_UNDEFINED": "La calculabilité du paramètre %symbol% n'est pas définie",
@@ -58,6 +62,15 @@
     "INFO_DEVER_TITRE_COURT": "Déver. dénoyés",
     "INFO_DEVER_TITRE": "Lois de déversoirs dénoyés",
     "INFO_DIALOG_COMPUTED_VALUE_TITLE": "Modifier la valeur initiale",
+    "INFO_DIALOG_EDIT_PAB_INTERPOLATION_BOUNDS": "entre %s et %s",
+    "INFO_DIALOG_EDIT_PAB_N_DEVICES": "%s ouvrage(s)",
+    "INFO_DIALOG_EDIT_PAB_N_WALLS": "%s cloison(s) : %s ouvrage(s)",
+    "INFO_DIALOG_EDIT_PAB_N_WALLS_P_DEVICES": "%s cloison(s) + %s ouvrage(s) : %s ouvrages",
+    "INFO_DIALOG_EDIT_PAB_OPTION_VARIABLE": "Variable à modifier",
+    "INFO_DIALOG_EDIT_PAB_OPTION_SET_VALUE": "Valeur fixe",
+    "INFO_DIALOG_EDIT_PAB_OPTION_DELTA": "Delta",
+    "INFO_DIALOG_EDIT_PAB_OPTION_INTERPOLATE": "Interpoler",
+    "INFO_DIALOG_EDIT_PAB_TITLE": "Modifier les valeurs",
     "INFO_DIALOG_EMPTY_CURRENT_SESSION": "Vider la session courante",
     "INFO_DIALOG_FIX_MISSING_DEPENDENCIES": "Résoudre les dépendances",
     "INFO_DIALOG_FORMAT_VERSIONS_MISMATCH": "Mauvaise version du format de fichier (fichier: %s, jalhyd: %s)",
@@ -88,26 +101,42 @@
     "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_1": "Partiellement noyé",
     "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_2": "Noyé",
     "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_3": "Débit nul",
-    "INFO_FSC_FS_ADDED": "1 ouvrage ajouté",
-    "INFO_FSC_FS_ADDED_N_TIMES": "%s ouvrages ajoutés",
-    "INFO_FSC_FS_COPIED": "Ouvrage n°%s copié",
-    "INFO_FSC_FS_COPIED_N_TIMES": "Ouvrage n°%s copié %s fois",
-    "INFO_FSC_FS_MOVED": "Ouvrage n°%s déplacé",
-    "INFO_FSC_FS_REMOVED": "Ouvrage n°%s supprimé",
+    "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é",
+    "INFO_DEVICE_COPIED_N_TIMES": "Ouvrage n°%s copié %s fois",
+    "INFO_DEVICE_MOVED": "Ouvrage n°%s déplacé",
+    "INFO_DEVICE_REMOVED": "Ouvrage n°%s supprimé",
+    "INFO_WALL_ADDED": "1 cloison ajoutée",
+    "INFO_WALL_ADDED_N_TIMES": "%s cloisons ajoutées",
+    "INFO_WALL_COPIED": "Cloison n°%s copiée",
+    "INFO_WALL_COPIED_N_TIMES": "Cloison n°%s copiée %s fois",
+    "INFO_WALL_MOVED": "Cloison n°%s déplacée",
+    "INFO_WALL_REMOVED": "Cloison n°%s supprimée",
     "INFO_LECHAPTCALMON_TITRE_COURT": "Lechapt-C.",
     "INFO_LECHAPTCALMON_TITRE": "Lechapt-Calmon",
+    "INFO_LIB_ABSCISSE_CLOISON": "Abscisse de la cloison",
     "INFO_LIB_ALPHA": "Coefficient alpha",
     "INFO_LIB_ALPHA2": "Demi-angle au sommet",
+    "INFO_LIB_AMONT": "Amont",
     "INFO_LIB_AVAL": "Aval",
     "INFO_LIB_B": "Largeur au miroir",
+    "INFO_LIB_BB": "Largeur du bassin",
     "INFO_LIB_BETA": "Coefficient béta",
     "INFO_LIB_BT": "Demi-ouverture du triangle",
     "INFO_LIB_CD": "Coefficient de débit",
     "INFO_LIB_CLOISON": "Cloison n°",
+    "INFO_LIB_COTE": "Cote (m)",
+    "INFO_LIB_COTE_VANNE_LEVANTE": "Cote vanne levante",
     "INFO_LIB_CV": "Cv: Coefficient de vitesse d'approche",
     "INFO_LIB_CVQT": "CV.QT: Débit corrigé",
     "INFO_LIB_DH": "Chute",
     "INFO_LIB_DHR": "DHR: Chute résiduelle",
+    "INFO_LIB_DISTANCE_AMONT": "Distance depuis l'amont (m)",
     "INFO_LIB_EC": "EC: Énergie cinétique",
     "INFO_LIB_ENUM_MACRORUGOFLOWTYPE": "Type d'écoulement",
     "INFO_LIB_FLU": "Ligne d'eau fluviale",
@@ -115,22 +144,30 @@
     "INFO_LIB_FS_OUVRAGE": "Ouvrage",
     "INFO_LIB_FS_PARAM_CALC": "Paramètres de calcul",
     "INFO_LIB_FS_PARAM_HYDRO": "Paramètres hydrauliques",
+    "INFO_LIB_H1": "Charge",
     "INFO_LIB_HS": "Charge spécifique",
     "INFO_LIB_HSC": "Charge critique",
     "INFO_LIB_I-J": "Variation linéaire de l'énergie spécifique",
     "INFO_LIB_IMP": "Impulsion",
     "INFO_LIB_J": "Perte de charge",
     "INFO_LIB_L": "Largeur du déversoir",
+    "INFO_LIB_LB": "Longueur du bassin",
+    "INFO_LIB_LIGNE_D_EAU": "Ligne d'eau",
+    "INFO_LIB_MINZDV": "Cote minimale de la crête",
+    "INFO_LIB_MAXZDV": "Cote maximale de la crête",
     "INFO_LIB_OUVRAGE_Q_ENUM_STRUCTUREFLOWMODE": "Type d'écoulement",
     "INFO_LIB_OUVRAGE_Q_ENUM_STRUCTUREFLOWREGIME": "Régime",
     "INFO_LIB_OUVRAGE_Q": "Débit",
+    "INFO_LIB_OUVRAGE_ZDV": "Cote du seuil",
     "INFO_LIB_P": "Périmètre mouillé",
+    "INFO_LIB_PB": "Profondeur moyenne du bassin",
     "INFO_LIB_PR": "Précision de calcul",
     "INFO_LIB_PV": "Puissance volumique dissipée",
     "INFO_LIB_Q_GUIDETECH": "Débit Guide technique",
     "INFO_LIB_Q": "Débit",
     "INFO_LIB_QA": "Débit d'attrait",
     "INFO_LIB_R": "Rayon hydraulique",
+    "INFO_LIB_RADIER": "Radier",
     "INFO_LIB_S": "Surface de l'orifice",
     "INFO_LIB_SELECT_LOIDEBIT": "Loi de débit",
     "INFO_LIB_SELECT_LOIDEBIT1_KIVI": "Kindsvater-Carter et Villemonte",
@@ -147,6 +184,7 @@
     "INFO_LIB_V": "Vitesse moyenne",
     "INFO_LIB_VDEB": "Vitesse débitante",
     "INFO_LIB_VMAX": "Vitesse maximale",
+    "INFO_LIB_W": "Ouverture de vanne",
     "INFO_LIB_YC": "Tirant d'eau critique",
     "INFO_LIB_YCO": "Tirant d'eau conjugué",
     "INFO_LIB_YF": "Tirant d'eau fluvial",
@@ -159,7 +197,7 @@
     "INFO_LIB_ZDV": "Cote de la crête du déversoir ou du radier de la vanne",
     "INFO_LIB_ZF2": "Cote de fond aval",
     "INFO_LIB_ZRAM": "Cote du radier amont",
-    "INFO_LIB_ZRB": "Cote de radier du bassin aval",
+    "INFO_LIB_ZRMB": "Cote de radier mi-bassin",
     "INFO_LIB_ZT": "Cote haute du triangle",
     "INFO_LINKED_VALUE_DEVICE_RESULT": "%s (%s, ouvrage %s)",
     "INFO_LINKED_VALUE_DEVICE": "%s (%s, ouvrage %s)",
@@ -168,6 +206,22 @@
     "INFO_LINKED_VALUE_RESULT": "%s (%s)",
     "INFO_LINKED_VALUE_SECTION_RESULT": "%s (%s, section)",
     "INFO_LINKED_VALUE_SECTION": "%s (%s, section)",
+    "INFO_LOIDEBIT_KIVI": "Kindsvater-Carter et Villemonte",
+    "INFO_LOIDEBIT_WeirSubmergedLarinier": "Fente noyée (Larinier 1992)",
+    "INFO_LOIDEBIT_OrificeSubmerged": "Orifice noyé",
+    "INFO_LOIDEBIT_TriangularWeirFree": "Déversoir triangulaire dénoyé",
+    "INFO_LOIDEBIT_TriangularTruncWeirFree": "Déversoir triangulaire tronqué dénoyé",
+    "INFO_LOIDEBIT_GateCem88d": "Déversoir/Orifice Cemagref 88",
+    "INFO_LOIDEBIT_GateCem88v": "Déversoir/Vanne de fond Cemagref 88",
+    "INFO_LOIDEBIT_WeirCem88d": "Déversoir/Orifice Cemagref 88",
+    "INFO_LOIDEBIT_WeirCem88v": "Déversoir/Vanne de fond Cemagref 88",
+    "INFO_LOIDEBIT_Cunge80": "Cunge 80",
+    "INFO_LOIDEBIT_RectangularOrificeFree": "Vanne dénoyé",
+    "INFO_LOIDEBIT_RectangularOrificeSubmerged": "Vanne noyé",
+    "INFO_LOIDEBIT_VanLevLarinier": "Fente noyée régulée (Larinier 1992)",
+    "INFO_LOIDEBIT_VanLevVillemonte": "Échancrure régulée (Villemonte 1957)",
+    "INFO_LOIDEBIT_WeirFree": "Seuil dénoyé",
+    "INFO_LOIDEBIT_WeirVillemonte": "Échancrure (Villemonte 1957)",
     "INFO_MACRORUGO_TITRE_COURT": "Macro-rugo.",
     "INFO_MACRORUGO_TITRE": "Passe à macro-rugosités",
     "INFO_MENU_EMPTY_SESSION_TITLE": "Nouvelle session",
@@ -193,8 +247,17 @@
     "INFO_OPTION_VALIDATE": "Valider",
     "INFO_OPTION_YES": "Oui",
     "INFO_OUVRAGE": "Ouvrage",
+    "INFO_PAB_BASSIN": "Bassin",
+    "INFO_PAB_NUM_BASSIN": "N° de bassin",
+    "INFO_PAB_HEADER_TYPE": "Type",
+    "INFO_PAB_HEADER_PARAMETERS": "Paramètres",
+    "INFO_PAB_HEADER_VALUES": "Valeurs",
+    "INFO_PAB_CLOISON_OUVRAGE_N": "Cloison : ouvrage n° %s",
+    "INFO_PAB_PARAMETRES_FIXES": "Tous les paramètres doivent être fixés",
     "INFO_PAB_TITRE_COURT": "PAB",
     "INFO_PAB_TITRE": "Passe à bassins",
+    "INFO_PAB_TITRE_PROFIL": "Profil en long de la passe",
+    "INFO_PAB_TABLE": "Géométrie de la passe",
     "INFO_PABCHUTE_TITRE_COURT": "PAB&nbsp;: chute",
     "INFO_PABCHUTE_TITRE": "Passe à bassins&nbsp;: chute",
     "INFO_PABDIMENSIONS_TITRE_COURT": "PAB&nbsp;: dimensions",
@@ -206,6 +269,7 @@
     "INFO_PARALLELSTRUCTURE_TITRE_COURT": "Ouvrages",
     "INFO_PARALLELSTRUCTURE_TITRE": "Lois d'ouvrages",
     "INFO_PARAMFIELD_AWAITING_CALCULATION": "En attente de calcul",
+    "INFO_PARAMFIELD_BOUNDARY_CONDITIONS": "Conditions aux limites",
     "INFO_PARAMFIELD_CALCULATED": "Calculé",
     "INFO_PARAMFIELD_CALCULATION_FAILED": "Échec du calcul",
     "INFO_PARAMFIELD_GRAPH_SELECT_X_AXIS": "Variable en abscisse",
@@ -238,6 +302,7 @@
     "INFO_PARAMFIELD_VARIATED": "Varié",
     "INFO_PARAMMODE_LIST": "Liste de valeurs",
     "INFO_PARAMMODE_MINMAX": "Min/max",
+    "WARNING_PROBLEMS_ENCOUNTERED": "Des problèmes sont survenus durant le calcul (info: %info%, avertissement: %warning%, erreur: %error%)",
     "INFO_REGIMEUNIFORME_TITRE_COURT": "R. uniforme",
     "INFO_REGIMEUNIFORME_TITRE": "Régime uniforme",
     "INFO_REMOUS_CALCUL_FLUVIAL": "Condition limite aval >= Hauteur critique&nbsp;: calcul de la partie fluviale à partir de l'aval",
@@ -282,9 +347,12 @@
     "INFO_THEME_PASSE_NATURELLE_DESCRIPTION": "Outils de dimensionnement d'une passe à poissons de type passe naturelle ou encore appelée passe à macro-rugosités",
     "INFO_THEME_PASSE_NATURELLE_TITRE": "Passe naturelle",
     "INFO_TITREJOURNAL": "Journal de calcul",
+    "INFO_TITREJOURNAL_GLOBAL": "Synthèse du journal de calcul",
     "INFO_WELCOME_CONTENT": "<p>Le logiciel Cassiopée a été développé par l'<a href=\"https://www.afbiodiversite.fr\" target=\"_blank\">AFB</a> (Agence Française pour la Biodiversité) et <a href=\"http://g-eau.fr\" target=\"_blank\">L'UMR G-EAU</a> (UMR Gestion de l'Eau, Acteurs, Usages).</p><p>Il regroupe des outils d'aide à la conception de passes à poissons et des outils de calcul hydraulique utiles pour l'ingénierie en environnement et agriculture.</p><p>Pour plus d'informations, consulter les <a href=\"assets/docs-fr/mentions_legales.html\" target=\"_blank\">mentions légales</a> et la <a href=\"assets/docs-fr/index.html\" target=\"_blank\">documentation</a>.</p>",
     "INFO_WELCOME_SUBTITLE": "Modules de calcul d'hydraulique",
     "WARNING_REMOUS_ARRET_CRITIQUE": "Arrêt du calcul&nbsp;: hauteur critique atteinte à l'abscisse %x%",
     "WARNING_STRUCTUREKIVI_HP_TROP_ELEVE": "h/p ne doit pas être supérieur à 2,5. h/p est forcé à 2,5",
-    "WARNING_STRUCTUREKIVI_PELLE_TROP_FAIBLE": "La pelle du seuil doit mesurer au moins 0,1 m. Le coefficient béta est forcé à 0"
+    "WARNING_STRUCTUREKIVI_PELLE_TROP_FAIBLE": "La pelle du seuil doit mesurer au moins 0,1 m. Le coefficient béta est forcé à 0",
+    "WARNING_VANLEV_ZDV_INF_MIN": "Seuil régulé&nbsp;: cote minimale de seuil atteinte",
+    "WARNING_VANLEV_ZDV_SUP_MAX": "Seuil régulé&nbsp;: cote maximale de seuil atteinte"
 }
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();
 }
diff --git a/src/theme.scss b/src/theme.scss
index 7c38e7153a7ffc8bddc72f8fedaaa3bb948c16b2..951487e7019af1ec6232938437127c43e6907e13 100644
--- a/src/theme.scss
+++ b/src/theme.scss
@@ -171,6 +171,7 @@ $mat-irstea-rouille: (
 $nghyd-primary: mat-palette($mat-irstea-marine);
 $nghyd-accent:  mat-palette($mat-irstea-ocean);
 $nghyd-warn:    mat-palette($mat-irstea-prune);
+$nghyd-error:   mat-palette($mat-irstea-rouille);
 // $nghyd-warn:    mat-palette($mat-irstea-rouille);
 
 $nghyd-theme: mat-light-theme($nghyd-primary, $nghyd-accent, $nghyd-warn);
@@ -181,14 +182,29 @@ $nghyd-theme: mat-light-theme($nghyd-primary, $nghyd-accent, $nghyd-warn);
 $primary: map-get($nghyd-theme, primary);
 $accent: map-get($nghyd-theme, accent);
 $warn: map-get($nghyd-theme, warn);
+// $error: map-get($nghyd-error);
 
 // convenience classes (functions mat-* cannot be used outside of this file)
+
 .color-primary {
     color: mat-color($primary);
 }
 .bg-primary {
     background-color: mat-color($primary);
 }
+.bg-primary-light {
+    background-color: mat-color($primary, 300);
+}
+.bg-primary-lighter {
+    background-color: mat-color($primary, 200);
+}
+.bg-primary-verylight {
+    background-color: mat-color($primary, 100);
+}
+.bg-primary-extralight {
+    background-color: mat-color($primary, 50);
+}
+
 .color-accent {
     color: mat-color($accent);
 }
@@ -198,12 +214,41 @@ $warn: map-get($nghyd-theme, warn);
 .bg-accent-light {
     background-color: mat-color($accent, 300);
 }
+.bg-accent-lighter {
+    background-color: mat-color($accent, 200);
+}
+.bg-accent-verylight {
+    background-color: mat-color($accent, 100);
+}
+.bg-accent-extralight {
+    background-color: mat-color($accent, 50);
+}
+
 .color-warn {
     color: mat-color($warn);
 }
 .bg-warn {
     background-color: mat-color($warn);
 }
+.bg-warn-light {
+    background-color: mat-color($warn, 300);
+}
+.bg-warn-lighter {
+    background-color: mat-color($warn, 200);
+}
+.bg-warn-verylight {
+    background-color: mat-color($warn, 100);
+}
+.bg-warn-extralight {
+    background-color: mat-color($warn, 50);
+}
+
+.color-error {
+    color: mat-color($nghyd-error);
+}
+.bg-error {
+    background-color: mat-color($nghyd-error);
+}
 
 .mat-button-toggle-checked {
     background-color: mat-color($accent);