diff --git a/src/lib/calculations.svelte.ts b/src/lib/calculations.svelte.ts
index 0b84a3f4cec546e643406e6844c497a71ef1ad3d..3e4e8df8cf02f816d49d74eceeb939ad202f97ac 100644
--- a/src/lib/calculations.svelte.ts
+++ b/src/lib/calculations.svelte.ts
@@ -13,16 +13,8 @@ import {
   updateEvaluations,
   waterfalls,
 } from "$lib/decompositions"
-import {
-  type Axis,
-  type Calculation,
-  type CalculationByName,
-  getPopulationReservedKeys,
-  type Situation,
-  type SituationWithAxes,
-  type TestCasesCalculationInput,
-} from "$lib/situations"
 import { entityByKey } from "$lib/entities"
+import { metadata } from "$lib/metadata"
 import type { ParametricReform } from "$lib/reforms"
 import {
   billActive,
@@ -30,8 +22,23 @@ import {
   revaluationName,
   shared,
   year,
+  yearPLF,
 } from "$lib/shared.svelte"
 import {
+  type Axis,
+  type Calculation,
+  type CalculationByName,
+  getPopulationReservedKeys,
+  type Situation,
+  type SituationWithAxes,
+  type TestCasesCalculationInput,
+} from "$lib/situations"
+import {
+  budgetEditableParametersName,
+  type BudgetVariable,
+  budgetVariableNameByVariableName,
+  budgetVariablesConfig,
+  budgetVariablesName,
   otherCalculatedVariablesName,
   summaryCalculatedVariablesName,
   variableSummaryByName,
@@ -64,6 +71,8 @@ export const requestedCalculations = $state({
   situationIndexByCalculationName: {},
 }) as RequestedCalculations
 
+let budgetSimulationAbortController = new AbortController()
+
 const testCasesAbortControllers: {
   [calculationName: string]: AbortController
 } = Object.fromEntries(
@@ -73,11 +82,87 @@ const testCasesAbortControllers: {
   ]),
 )
 
-export async function calculateTestCases(
+export function buildBudgetDemandBody(
+  variableName: string,
+  outputVariables: string[],
+  quantileBaseVariable: string[],
+  quantileCompareVariables: string[],
+  includeDisplay = true,
+) {
+  const budgetParametricReform = Object.fromEntries(
+    Object.entries(shared.parametricReform).filter(([parameterName]) =>
+      budgetEditableParametersName.has(parameterName),
+    ),
+  )
+  return {
+    amendement: budgetParametricReform,
+    base: yearPLF,
+    displayMode: includeDisplay ? shared.displayMode : undefined,
+    metadata,
+    output_variables: outputVariables,
+    quantile_base_variable: quantileBaseVariable,
+    quantile_compare_variables: quantileCompareVariables,
+    winners_loosers_variable: variableName,
+    quantile_nb: 10,
+    plf: billName === undefined ? undefined : yearPLF,
+  }
+}
+
+export async function calculateBudget(
+  budgetVariableName: string,
+  // budgetCalculationNames: Set<CalculationName>,
+): Promise<void> {
+  budgetSimulationAbortController.abort()
+  budgetSimulationAbortController = new AbortController()
+  if (!budgetVariablesName.has(budgetVariableName)) {
+    console.error(
+      `Budget calculation for variable ${budgetVariableName} is not available`,
+    )
+    shared.budgetSimulation = undefined
+    return
+  }
+  const variableName = budgetVariableNameByVariableName[budgetVariableName]
+  const variableConfig: BudgetVariable = budgetVariablesConfig[variableName]
+  const body = JSON.stringify(
+    buildBudgetDemandBody(
+      variableName,
+      variableConfig.outputVariables,
+      variableConfig.quantileBaseVariable,
+      variableConfig.quantileCompareVariables,
+    ),
+  )
+  shared.budgetSimulation = undefined
+  const response = await fetch("/budgets", {
+    body,
+    headers: {
+      Accept: "application/json",
+      "Content-Type": "application/json; charset=utf-8",
+    },
+    method: "POST",
+    signal: budgetSimulationAbortController.signal,
+  })
+  if (!response.ok) {
+    console.error(
+      `"/budgets": Error while calculating budget:\n${body}\n${response.status} ${response.statusText}`,
+    )
+    console.error(await response.text())
+    shared.budgetSimulation = {
+      errors: [
+        `Une erreur inattendue (${response.status} ${response.statusText}) s'est produite et les impacts budgétaires ne sont pas disponibles pour le moment. Écrivez-nous à leximpact@assemblee-nationale.fr.`,
+      ],
+      isPublic: true,
+    }
+    shared.budgetSimulation = undefined
+    return
+  }
+  shared.budgetSimulation = await response.json()
+}
+
+export function calculateTestCases(
   situationIndexByCalculationName: RequestedSituationIndexByCalculationName,
 ) {
   const aggregatedSituation: SituationWithAxes = {}
-  const axesData: { situationIndex: number; variables: string[] } =
+  const axesData: { situationIndex?: number; variables: string[] } =
     shared.axes.reduce(
       (
         result: { situationIndex?: number; variables: string[] },
@@ -108,7 +193,7 @@ export async function calculateTestCases(
         shared.inputInstantsByVariableNameArray[situationIndex]
       const situationPrefix = `Cas type n°${situationIndex + 1} | `
       for (const entity of entities) {
-        let entitySituation = situation[entity.key_plural as string]
+        const entitySituation = situation[entity.key_plural as string]
         if (entitySituation === undefined) {
           continue
         }
@@ -392,9 +477,9 @@ function cleanSituation(
   situationToClean: Situation,
   axes: Axis[][],
 ): SituationWithAxes {
-  let situation: SituationWithAxes = {}
+  const situation: SituationWithAxes = {}
   for (const entity of Object.values(entityByKey)) {
-    let entitySituation = situationToClean[entity.key_plural as string]
+    const entitySituation = situationToClean[entity.key_plural as string]
     if (entitySituation === undefined) {
       continue
     }
@@ -431,9 +516,9 @@ export function requestAllBudgetCalculations(variableName: string): void {
   }
 }
 
-export async function requestAllTestCasesCalculations(
+export function requestAllTestCasesCalculations(
   requestedSituationIndex: number | null,
-): Promise<void> {
+): void {
   if (
     calculationNames.some((calculationName) => {
       const situationIndex =
@@ -488,6 +573,61 @@ export function requestTestCasesCalculation(
   }
 }
 
+export async function sendBudgetSimulationDemand() {
+  const budgetVariableName = shared.displayMode?.parametersVariableName
+  if (
+    budgetVariableName === undefined ||
+    !budgetVariablesName.has(budgetVariableName)
+  ) {
+    console.error(
+      `Budget calculation for variable ${budgetVariableName} is not available`,
+    )
+    shared.budgetSimulation = undefined
+    return
+  }
+  const variableName = budgetVariableNameByVariableName[budgetVariableName]
+  const variableConfig: BudgetVariable = budgetVariablesConfig[variableName]
+  const simulationBody = buildBudgetDemandBody(
+    variableName,
+    variableConfig.outputVariables,
+    variableConfig.quantileBaseVariable,
+    variableConfig.quantileCompareVariables,
+    false,
+  )
+  const urlString = "/budgets/demands"
+  const res = await fetch(urlString, {
+    body: JSON.stringify(
+      {
+        displayMode: shared.displayMode,
+        email: shared.requestedSimulationEmail,
+        request: Object.fromEntries(
+          Object.entries(shared.parametricReform).filter(([parameterName]) =>
+            budgetEditableParametersName.has(parameterName),
+          ),
+        ),
+        simulation: simulationBody,
+      },
+      null,
+      2,
+    ),
+    headers: {
+      Accept: "application/json",
+      "Content-Type": "application/json; charset=utf-8",
+    },
+    method: "POST",
+  })
+  if (!res.ok) {
+    shared.requestedSimulationEmail = undefined
+    console.error(
+      `Error ${
+        res.status
+      } while sending a simulation request at ${urlString}\n\n${await res.text()}`,
+    )
+    return
+  }
+  shared.requestedSimulationEmail = undefined
+}
+
 async function sendTestCasesSimulation(
   calculationName: CalculationName,
   {
@@ -641,49 +781,60 @@ async function sendTestCasesSimulation(
           // Variable has been computed for all test cases.
 
           // First, update delta and values of evaluations.
-          updatedEvaluationByNameArray = updatedEvaluationByNameArray.map(
-            (evaluationByName, situationIndex): EvaluationByName => {
-              const situation = shared.testCases[situationIndex]
-              const values = valuesByCalculationNameByVariableNameArray[
-                situationIndex
-              ][variableName][calculationName] as VariableValues
-              const entitySituation =
-                situation[entity.key_plural as string] ?? {}
-              const situationPopulationCount =
-                Object.keys(entitySituation).length
-              let delta = new Array(shared.vectorLength).fill(0)
-              for (const situationPersonIndex of Object.keys(
-                entitySituation,
-              ).keys()) {
-                for (
-                  let index = situationPersonIndex, vectorIndex = 0;
-                  vectorIndex < shared.vectorLength;
-                  index += situationPopulationCount, vectorIndex++
-                ) {
-                  delta[vectorIndex] += values[index]
-                }
-              }
-              const evaluation = evaluationByName[variableName]
-              const calculationEvaluationByName =
-                evaluation?.calculationEvaluationByName ?? {}
-              return {
-                ...evaluationByName,
-                [variableName]: {
-                  ...(evaluation ?? {}),
-                  calculationEvaluationByName: {
-                    ...calculationEvaluationByName,
-                    [calculationName]: {
-                      ...(calculationEvaluationByName[calculationName] ?? {}),
-                      delta,
-                      deltaAtVectorIndex:
-                        delta[situation.slider?.vectorIndex ?? 0] ?? 0,
-                    },
-                  },
-                  fromOpenFisca: true,
-                },
+          for (const [
+            situationIndex,
+            evaluationByName,
+          ] of updatedEvaluationByNameArray.entries()) {
+            const situation = shared.testCases[situationIndex]
+            const values = valuesByCalculationNameByVariableNameArray[
+              situationIndex
+            ][variableName][calculationName] as VariableValues
+            const entitySituation = situation[entity.key_plural as string] ?? {}
+            const situationPopulationCount = Object.keys(entitySituation).length
+            const delta = new Array(shared.vectorLength).fill(0)
+            for (const situationPersonIndex of Object.keys(
+              entitySituation,
+            ).keys()) {
+              for (
+                let index = situationPersonIndex, vectorIndex = 0;
+                vectorIndex < shared.vectorLength;
+                index += situationPopulationCount, vectorIndex++
+              ) {
+                delta[vectorIndex] += values[index]
               }
-            },
-          )
+            }
+
+            if (evaluationByName[variableName] === undefined) {
+              evaluationByName[variableName] = {}
+            }
+
+            if (
+              evaluationByName[variableName].calculationEvaluationByName ===
+              undefined
+            ) {
+              evaluationByName[variableName].calculationEvaluationByName = {}
+            }
+
+            if (
+              evaluationByName[variableName].calculationEvaluationByName[
+                calculationName
+              ] === undefined
+            ) {
+              evaluationByName[variableName].calculationEvaluationByName[
+                calculationName
+              ] = {}
+            }
+
+            evaluationByName[variableName].calculationEvaluationByName[
+              calculationName
+            ]!.delta = delta
+            evaluationByName[variableName].calculationEvaluationByName[
+              calculationName
+            ]!.deltaAtVectorIndex =
+              delta[situation.slider?.vectorIndex ?? 0] ?? 0
+
+            evaluationByName[variableName].fromOpenFisca = true
+          }
         } else {
           // Variable has been computed for a single test case.
 
@@ -694,7 +845,7 @@ async function sendTestCasesSimulation(
           ][variableName][calculationName] as VariableValues
           const entitySituation = situation[entity.key_plural as string] ?? {}
           const situationPopulationCount = Object.keys(entitySituation).length
-          let delta = new Array(shared.vectorLength).fill(0)
+          const delta = new Array(shared.vectorLength).fill(0)
           for (const situationPersonIndex of Object.keys(
             entitySituation,
           ).keys()) {
@@ -709,22 +860,36 @@ async function sendTestCasesSimulation(
           const evaluationByName = {
             ...updatedEvaluationByNameArray[calculation.situationIndex],
           }
-          const evaluation = evaluationByName[variableName]
-          const calculationEvaluationByName =
-            evaluation?.calculationEvaluationByName ?? {}
-          evaluationByName[variableName] = {
-            ...(evaluation ?? {}),
-            calculationEvaluationByName: {
-              ...calculationEvaluationByName,
-              [calculationName]: {
-                ...(calculationEvaluationByName[calculationName] ?? {}),
-                delta,
-                deltaAtVectorIndex:
-                  delta[situation.slider?.vectorIndex ?? 0] ?? 0,
-              },
-            },
-            fromOpenFisca: true,
+
+          if (evaluationByName[variableName] === undefined) {
+            evaluationByName[variableName] = {}
           }
+
+          if (
+            evaluationByName[variableName].calculationEvaluationByName ===
+            undefined
+          ) {
+            evaluationByName[variableName].calculationEvaluationByName = {}
+          }
+
+          if (
+            evaluationByName[variableName].calculationEvaluationByName[
+              calculationName
+            ] === undefined
+          ) {
+            evaluationByName[variableName].calculationEvaluationByName[
+              calculationName
+            ] = {}
+          }
+
+          evaluationByName[variableName].calculationEvaluationByName[
+            calculationName
+          ]!.delta = delta
+          evaluationByName[variableName].calculationEvaluationByName[
+            calculationName
+          ]!.deltaAtVectorIndex = delta[situation.slider?.vectorIndex ?? 0] ?? 0
+
+          evaluationByName[variableName].fromOpenFisca = true
         }
       }
     }
diff --git a/src/lib/components/test_cases/TestCaseGraph.svelte b/src/lib/components/test_cases/TestCaseGraph.svelte
index d82aea47f6af8978b014cc9207a5893acef17632..17b5e397cf1eecaffe4306d553dfa0b893d1214c 100644
--- a/src/lib/components/test_cases/TestCaseGraph.svelte
+++ b/src/lib/components/test_cases/TestCaseGraph.svelte
@@ -1,6 +1,4 @@
 <script lang="ts">
-  import { run } from "svelte/legacy"
-
   import {
     type NumberValue,
     type PopulationWithoutId,
@@ -11,10 +9,9 @@
     type RateBracketAtInstant,
     scaleByInstantFromBrackets,
   } from "@openfisca/json-model"
-  import { createEventDispatcher } from "svelte"
+  import { createEventDispatcher, untrack } from "svelte"
   import { fade } from "svelte/transition"
 
-  import { page } from "$app/stores"
   import type { CalculationName } from "$lib/calculations.svelte"
   import PersistentPopover from "$lib/components/PersistentPopover.svelte"
   import PictoBigAdulteRetraite from "$lib/components/pictos/PictoBigAdulteRetraite.svelte"
@@ -517,287 +514,293 @@
     }
     requestAxesCalculation()
   }
-  let visibleDecompositionsGraph = $derived(
-    situation.slider !== undefined
-      ? buildVisibleDecompositionsForGraph(
-          shared.decompositionByName,
-          entityByKey,
-          evaluationByName,
-          situation,
-          variableSummaryByName,
-          waterfall,
-          false,
-          useRevaluationInsteadOfLaw,
-          vectorLength,
-          year,
-        )
-      : undefined,
-  )
-  run(() => {
+  let visibleDecompositionsGraph: VisibleDecompositionForGraph[] | undefined =
+    $state(undefined)
+  $effect(() => {
+    visibleDecompositionsGraph =
+      situation.slider !== undefined
+        ? buildVisibleDecompositionsForGraph(
+            shared.decompositionByName,
+            entityByKey,
+            evaluationByName,
+            situation,
+            variableSummaryByName,
+            waterfall,
+            false,
+            useRevaluationInsteadOfLaw,
+            vectorLength,
+            year,
+          )
+        : undefined
+  })
+  $effect(() => {
     if (
       situation.slider !== undefined &&
       evaluationByNameArray !== undefined &&
       visibleDecompositionsGraph !== undefined
     ) {
-      const ressourcesBrutes = [
-        "remuneration_brute",
-        "chomage_brut",
-        "retraite_brute",
-      ]
-
-      if (waterfall.name === "brut_to_disponible") {
-        const decompositions = visibleDecompositionsGraph.filter(
-          (decomposition) =>
-            !decomposition.trunk ||
-            ressourcesBrutes.includes(decomposition.decomposition.name) ||
-            decomposition.decomposition.name === "revenu_disponible",
-        )
-
-        const versementBrutIndex = decompositions.findIndex((decomposition) =>
-          ressourcesBrutes.includes(decomposition.decomposition.name),
-        )
-        const prestationsIndex = decompositions.findIndex(
-          (decomposition) =>
-            decomposition.decomposition.name === "prestations_sociales",
-        )
-        const revenuIndex = decompositions.findIndex(
-          (decomposition) =>
-            decomposition.decomposition.name === "revenu_disponible",
-        )
-
-        const prelevementsDecompositions = [
-          ...decompositions.slice(0, versementBrutIndex),
-          ...decompositions
-            .slice(versementBrutIndex, prestationsIndex)
-            .filter(
-              (decomposition) =>
-                ressourcesBrutes.includes(decomposition.decomposition.name) ||
-                getLatestVisibleCalculation(decomposition.rows)?.isNegative,
-            ),
+      untrack(() => {
+        const ressourcesBrutes = [
+          "remuneration_brute",
+          "chomage_brut",
+          "retraite_brute",
         ]
 
-        const complementsDeRessourcesDecompositions = decompositions
-          .slice(versementBrutIndex, prestationsIndex)
-          .filter(
+        if (waterfall.name === "brut_to_disponible") {
+          const decompositions = visibleDecompositionsGraph.filter(
             (decomposition) =>
-              !ressourcesBrutes.includes(decomposition.decomposition.name) &&
-              !getLatestVisibleCalculation(decomposition.rows)?.isNegative,
+              !decomposition.trunk ||
+              ressourcesBrutes.includes(decomposition.decomposition.name) ||
+              decomposition.decomposition.name === "revenu_disponible",
           )
 
-        const prestationsDecompositions = decompositions.slice(
-          prestationsIndex,
-          revenuIndex,
-        )
+          const versementBrutIndex = decompositions.findIndex((decomposition) =>
+            ressourcesBrutes.includes(decomposition.decomposition.name),
+          )
+          const prestationsIndex = decompositions.findIndex(
+            (decomposition) =>
+              decomposition.decomposition.name === "prestations_sociales",
+          )
+          const revenuIndex = decompositions.findIndex(
+            (decomposition) =>
+              decomposition.decomposition.name === "revenu_disponible",
+          )
 
-        const revenuDisponibleDecomposition = decompositions[revenuIndex]
-        const revenuDisponibleValues = generateValues([
-          revenuDisponibleDecomposition,
-        ])
-
-        const [
-          prelevementsValues,
-          complementsDeRessourcesValues,
-          prestationsValues,
-        ] = stackValues(
-          generateValues(prelevementsDecompositions),
-          generateValues(complementsDeRessourcesDecompositions),
-          generateValues(prestationsDecompositions, true),
-        )
+          const prelevementsDecompositions = [
+            ...decompositions.slice(0, versementBrutIndex),
+            ...decompositions
+              .slice(versementBrutIndex, prestationsIndex)
+              .filter(
+                (decomposition) =>
+                  ressourcesBrutes.includes(decomposition.decomposition.name) ||
+                  getLatestVisibleCalculation(decomposition.rows)?.isNegative,
+              ),
+          ]
 
-        variableValues = [
-          prelevementsValues,
-          complementsDeRessourcesValues,
-          prestationsValues,
-          revenuDisponibleValues,
-        ]
+          const complementsDeRessourcesDecompositions = decompositions
+            .slice(versementBrutIndex, prestationsIndex)
+            .filter(
+              (decomposition) =>
+                !ressourcesBrutes.includes(decomposition.decomposition.name) &&
+                !getLatestVisibleCalculation(decomposition.rows)?.isNegative,
+            )
 
-        const splitIndex =
-          prelevementsValues.findIndex(
-            (variable) => variable.name === "remuneration_brute",
-          ) + 1
-
-        variableGroups = [
-          {
-            variables: [
-              ...prelevementsValues.slice(0, splitIndex).reverse(),
-              ...complementsDeRessourcesValues,
-              ...revenuDisponibleValues,
-            ],
-          },
-          {
-            css: "text-red-600",
-            label: "Prélèvements",
-            name: "prelevements",
-            hideOpen: true,
-            variables: prelevementsValues.slice(splitIndex),
-          },
-          {
-            css: "text-green-800",
-            label: "Prestations",
-            name: "prestations",
-            hideOpen: true,
-            variables: prestationsValues,
-          },
-        ]
+          const prestationsDecompositions = decompositions.slice(
+            prestationsIndex,
+            revenuIndex,
+          )
 
-        for (const [groupIndex, group] of variableGroups.entries()) {
-          for (const variable of group.variables) {
-            for (const row of variable.rows) {
-              const key = `${variable.name}_${row.calculationName}`
-              if (
-                !Object.keys(variableCustomizations).includes(key) &&
-                ((row.calculationName !== "law" &&
-                  row.calculationName !== "revaluation") ||
-                  !Object.keys(variableCustomizations).includes(variable.name))
-              ) {
-                const types = ["base", "prelevement", "prestation"]
-                variableCustomizations = {
-                  ...variableCustomizations,
-                  [key]: structuredClone(
-                    variableCustomizations[
-                      getVariableCustomizationName(
-                        variable.parent,
-                        row.calculationName,
-                      )
-                    ] ??
-                      defaultCustomization[waterfall.name][types[groupIndex]][
-                        row.calculationName === "revaluation"
-                          ? "law"
-                          : row.calculationName
-                      ],
-                  ),
+          const revenuDisponibleDecomposition = decompositions[revenuIndex]
+          const revenuDisponibleValues = generateValues([
+            revenuDisponibleDecomposition,
+          ])
+
+          const [
+            prelevementsValues,
+            complementsDeRessourcesValues,
+            prestationsValues,
+          ] = stackValues(
+            generateValues(prelevementsDecompositions),
+            generateValues(complementsDeRessourcesDecompositions),
+            generateValues(prestationsDecompositions, true),
+          )
+
+          variableValues = [
+            prelevementsValues,
+            complementsDeRessourcesValues,
+            prestationsValues,
+            revenuDisponibleValues,
+          ]
+
+          const splitIndex =
+            prelevementsValues.findIndex(
+              (variable) => variable.name === "remuneration_brute",
+            ) + 1
+
+          variableGroups = [
+            {
+              variables: [
+                ...prelevementsValues.slice(0, splitIndex).reverse(),
+                ...complementsDeRessourcesValues,
+                ...revenuDisponibleValues,
+              ],
+            },
+            {
+              css: "text-red-600",
+              label: "Prélèvements",
+              name: "prelevements",
+              hideOpen: true,
+              variables: prelevementsValues.slice(splitIndex),
+            },
+            {
+              css: "text-green-800",
+              label: "Prestations",
+              name: "prestations",
+              hideOpen: true,
+              variables: prestationsValues,
+            },
+          ]
+
+          for (const [groupIndex, group] of variableGroups.entries()) {
+            for (const variable of group.variables) {
+              for (const row of variable.rows) {
+                const key = `${variable.name}_${row.calculationName}`
+                if (
+                  !Object.keys(variableCustomizations).includes(key) &&
+                  ((row.calculationName !== "law" &&
+                    row.calculationName !== "revaluation") ||
+                    !Object.keys(variableCustomizations).includes(
+                      variable.name,
+                    ))
+                ) {
+                  const types = ["base", "prelevement", "prestation"]
+                  variableCustomizations[key] = JSON.parse(
+                    JSON.stringify(
+                      variableCustomizations[
+                        getVariableCustomizationName(
+                          variable.parent,
+                          row.calculationName,
+                        )
+                      ] ??
+                        defaultCustomization[waterfall.name][types[groupIndex]][
+                          row.calculationName === "revaluation"
+                            ? "law"
+                            : row.calculationName
+                        ],
+                    ),
+                  )
                 }
               }
             }
           }
-        }
-      } else {
-        const decompositions = visibleDecompositionsGraph.filter(
-          (decomposition) =>
-            !decomposition.trunk ||
-            decomposition.decomposition.name === "remuneration_brute" ||
-            decomposition.decomposition.name === "salaire_super_brut",
-        )
-
-        const allValues = generateValues(decompositions)
-
-        const versementBrutIndex = allValues.findIndex(
-          (value) => value.name === "remuneration_brute",
-        )
-        const allegementGeneralIndex = allValues.findIndex(
-          (value) => value.name === "allegement_general",
-        )
+        } else {
+          const decompositions = visibleDecompositionsGraph.filter(
+            (decomposition) =>
+              !decomposition.trunk ||
+              decomposition.decomposition.name === "remuneration_brute" ||
+              decomposition.decomposition.name === "salaire_super_brut",
+          )
 
-        const versementBrutValues = allValues.slice(0, versementBrutIndex + 1)
+          const allValues = generateValues(decompositions)
 
-        const superBrutValues = allValues.slice(-1)
+          const versementBrutIndex = allValues.findIndex(
+            (value) => value.name === "remuneration_brute",
+          )
+          const allegementGeneralIndex = allValues.findIndex(
+            (value) => value.name === "allegement_general",
+          )
 
-        const cotisationsValues = allValues.slice(
-          versementBrutIndex + 1,
-          allegementGeneralIndex,
-        )
+          const versementBrutValues = allValues.slice(0, versementBrutIndex + 1)
 
-        const allegementsValues = allValues.slice(allegementGeneralIndex, -1)
+          const superBrutValues = allValues.slice(-1)
 
-        variableValues = [
-          versementBrutValues,
-          cotisationsValues,
-          allegementsValues,
-          superBrutValues,
-        ]
+          const cotisationsValues = allValues.slice(
+            versementBrutIndex + 1,
+            allegementGeneralIndex,
+          )
 
-        variableGroups = [
-          {
-            variables: [
-              ...allValues.slice(0, versementBrutIndex + 1),
-              ...allValues.slice(-1),
-            ],
-          },
-          {
-            css: "text-green-800",
-            label: "Cotisations employeur",
-            name: "cotisations_employeur",
-            hideOpen: true,
-            variables: allValues.slice(
-              allValues.findIndex(
-                (value) =>
-                  value.name === "cotisations_employeur_securite_sociale",
+          const allegementsValues = allValues.slice(allegementGeneralIndex, -1)
+
+          variableValues = [
+            versementBrutValues,
+            cotisationsValues,
+            allegementsValues,
+            superBrutValues,
+          ]
+
+          variableGroups = [
+            {
+              variables: [
+                ...allValues.slice(0, versementBrutIndex + 1),
+                ...allValues.slice(-1),
+              ],
+            },
+            {
+              css: "text-green-800",
+              label: "Cotisations employeur",
+              name: "cotisations_employeur",
+              hideOpen: true,
+              variables: allValues.slice(
+                allValues.findIndex(
+                  (value) =>
+                    value.name === "cotisations_employeur_securite_sociale",
+                ),
+                allegementGeneralIndex,
               ),
-              allegementGeneralIndex,
-            ),
-          },
-          {
-            css: "text-red-600",
-            label: "Allègements",
-            name: "allegements",
-            hideOpen: true,
-            variables: allValues.slice(allegementGeneralIndex, -1),
-          },
-        ]
-
-        for (const [groupIndex, group] of variableGroups.entries()) {
-          for (const variable of group.variables) {
-            for (const row of variable.rows) {
-              const key = `${variable.name}_${row.calculationName}`
-              if (
-                !Object.keys(variableCustomizations).includes(key) &&
-                ((row.calculationName !== "law" &&
-                  row.calculationName !== "revaluation") ||
-                  !Object.keys(variableCustomizations).includes(variable.name))
-              ) {
-                const types = ["base", "cotisation", "allegement"]
-                variableCustomizations = {
-                  ...variableCustomizations,
-                  [key]: structuredClone(
-                    variableCustomizations[
-                      getVariableCustomizationName(
-                        variable.parent,
-                        row.calculationName,
-                      )
-                    ] ??
-                      defaultCustomization[waterfall.name][types[groupIndex]][
-                        row.calculationName === "revaluation"
-                          ? "law"
-                          : row.calculationName
-                      ],
-                  ),
+            },
+            {
+              css: "text-red-600",
+              label: "Allègements",
+              name: "allegements",
+              hideOpen: true,
+              variables: allValues.slice(allegementGeneralIndex, -1),
+            },
+          ]
+
+          for (const [groupIndex, group] of variableGroups.entries()) {
+            for (const variable of group.variables) {
+              for (const row of variable.rows) {
+                const key = `${variable.name}_${row.calculationName}`
+                if (
+                  !Object.keys(variableCustomizations).includes(key) &&
+                  ((row.calculationName !== "law" &&
+                    row.calculationName !== "revaluation") ||
+                    !Object.keys(variableCustomizations).includes(
+                      variable.name,
+                    ))
+                ) {
+                  const types = ["base", "cotisation", "allegement"]
+                  variableCustomizations[key] = JSON.parse(
+                    JSON.stringify(
+                      variableCustomizations[
+                        getVariableCustomizationName(
+                          variable.parent,
+                          row.calculationName,
+                        )
+                      ] ??
+                        defaultCustomization[waterfall.name][types[groupIndex]][
+                          row.calculationName === "revaluation"
+                            ? "law"
+                            : row.calculationName
+                        ],
+                    ),
+                  )
                 }
               }
             }
           }
         }
-      }
 
-      maxVariableValue = variableValues.reduce(
-        (max: number, values: VariableGraph[]) => {
-          max = Math.max(
-            max,
-            Math.max(
-              ...values.map(({ rows }) =>
-                Math.max(
-                  ...rows.map(({ summedValues }) => Math.max(...summedValues)),
+        maxVariableValue = variableValues.reduce(
+          (max: number, values: VariableGraph[]) => {
+            max = Math.max(
+              max,
+              Math.max(
+                ...values.map(({ rows }) =>
+                  Math.max(
+                    ...rows.map(({ summedValues }) =>
+                      Math.max(...summedValues),
+                    ),
+                  ),
                 ),
               ),
-            ),
-          )
-          return max
-        },
-        0,
-      )
+            )
+            return max
+          },
+          0,
+        )
+      })
     }
   })
-  let domain
-  run(() => {
-    domain = {
-      x: {
-        min: situation.slider?.min,
-        max: situation.slider?.max,
-      },
-      y: {
-        min: situation.slider?.yMin ?? 0,
-        max: situation.slider?.yMax ?? maxVariableValue ?? 100000,
-      },
-    }
+  let domain = $state({
+    x: {
+      min: situation.slider?.min,
+      max: situation.slider?.max,
+    },
+    y: {
+      min: situation.slider?.yMin ?? 0,
+      max: situation.slider?.yMax ?? maxVariableValue ?? 100000,
+    },
   })
   // Note: A reform parameters tree is always more complete than a parameters tree before reform.
   // And the children of a reform node parameter always contain the children of the node parameter
@@ -900,7 +903,7 @@
   let smicValue = $derived(smicLatestInstantValueCouple?.[1] as NumberValue)
 </script>
 
-{#if situation.slider !== undefined}
+{#if situation.slider !== undefined && visibleDecompositionsGraph !== undefined}
   <div class="relative">
     {#if isCalculationRunning}
       <div class="absolute inset-0 z-20 bg-white" out:fade>
diff --git a/src/lib/components/variables/VariableInput.svelte b/src/lib/components/variables/VariableInput.svelte
index ff05e9d7c665915187efde449b64d3aad3d391de..7e88820ba183ce8ba7d6d0e7a846e7622dea679a 100644
--- a/src/lib/components/variables/VariableInput.svelte
+++ b/src/lib/components/variables/VariableInput.svelte
@@ -44,7 +44,7 @@
     inputInstantsByVariableName = $bindable(),
     situation = $bindable(),
     situationIndex,
-    valuesByCalculationNameByVariableName,
+    valuesByCalculationNameByVariableName = $bindable(),
     variable,
     year,
   }: Props = $props()
diff --git a/src/lib/shared.svelte.ts b/src/lib/shared.svelte.ts
index f6e037ad79ce8750a1b6ee612cef29d51ff9e4aa..706090873bee86e848c9c70d4be26e5e17613509 100644
--- a/src/lib/shared.svelte.ts
+++ b/src/lib/shared.svelte.ts
@@ -4,40 +4,27 @@ import { type Waterfall } from "@openfisca/json-model"
 import "iconify-icon"
 
 import type { BudgetSimulation } from "$lib/budgets"
-import { requestAllTestCasesCalculations } from "$lib/calculations.svelte"
 import {
   buildDecompositionByNameFromCore,
   decompositionCoreByName,
   decompositionCoreByNameByReformName,
-  updateEvaluations,
-  updateEvaluationsVectorIndex,
   waterfalls,
   type EvaluationByName,
   type DecompositionByName,
 } from "$lib/decompositions"
 import type { DisplayMode } from "$lib/displays"
-import { entityByKey } from "$lib/entities"
 import { trackTestCaseShowAllVariables } from "$lib/matomo"
-import { metadata } from "$lib/metadata"
 import { getNavbarConfig, type NavbarConfig } from "$lib/navbar"
 import publicConfig from "$lib/public_config"
 import { type ParametricReform, reformMetadataByName } from "$lib/reforms"
 import {
   extractInputInstantsFromTestCases,
-  indexOfSituationPopulationId,
   testCasesCore,
   type Axis,
   type CalculationByName,
   type Situation,
 } from "$lib/situations"
-import {
-  budgetEditableParametersName,
-  budgetVariableNameByVariableName,
-  budgetVariablesConfig,
-  budgetVariablesName,
-  type BudgetVariable,
-  type ValuesByCalculationNameByVariableName,
-} from "$lib/variables"
+import { type ValuesByCalculationNameByVariableName } from "$lib/variables"
 
 export interface Shared {
   axes: Axis[][]
@@ -66,15 +53,8 @@ export interface Shared {
   waterfall: Waterfall
 }
 
-const {
-  baseUrl,
-  reformName: reformBillName,
-  revaluationName: reformRevaluationName,
-} = publicConfig
-
-let axisBySituationIndex: { [situationIndex: string]: Axis } = {}
-
-let budgetSimulationAbortController = new AbortController()
+const { reformName: reformBillName, revaluationName: reformRevaluationName } =
+  publicConfig
 
 /**
  * Bill (PLF) and revaluation (contrefactuel)
@@ -136,266 +116,6 @@ export const shared: Shared = $state({
   waterfall: waterfalls[0],
 })
 
-const vectorIndexes = new Array(testCasesCore.length).fill(0)
-
 if (shared.showNulls) {
   trackTestCaseShowAllVariables()
 }
-
-function buildBudgetDemandBody(
-  variableName: string,
-  outputVariables: string[],
-  quantileBaseVariable: string[],
-  quantileCompareVariables: string[],
-  includeDisplay = true,
-) {
-  const budgetParametricReform = Object.fromEntries(
-    Object.entries(shared.parametricReform).filter(([parameterName]) =>
-      budgetEditableParametersName.has(parameterName),
-    ),
-  )
-  return {
-    amendement: budgetParametricReform,
-    base: yearPLF,
-    displayMode: includeDisplay ? shared.displayMode : undefined,
-    metadata,
-    output_variables: outputVariables,
-    quantile_base_variable: quantileBaseVariable,
-    quantile_compare_variables: quantileCompareVariables,
-    winners_loosers_variable: variableName,
-    quantile_nb: 10,
-    plf: billName === undefined ? undefined : yearPLF,
-  }
-}
-
-export async function calculateBudget(
-  budgetVariableName: string,
-  // budgetCalculationNames: Set<CalculationName>,
-): Promise<void> {
-  budgetSimulationAbortController.abort()
-  budgetSimulationAbortController = new AbortController()
-  if (!budgetVariablesName.has(budgetVariableName)) {
-    console.error(
-      `Budget calculation for variable ${budgetVariableName} is not available`,
-    )
-    shared.budgetSimulation = undefined
-    return
-  }
-  const variableName = budgetVariableNameByVariableName[budgetVariableName]
-  const variableConfig: BudgetVariable = budgetVariablesConfig[variableName]
-  const body = JSON.stringify(
-    buildBudgetDemandBody(
-      variableName,
-      variableConfig.outputVariables,
-      variableConfig.quantileBaseVariable,
-      variableConfig.quantileCompareVariables,
-    ),
-  )
-  shared.budgetSimulation = undefined
-  const response = await fetch("/budgets", {
-    body,
-    headers: {
-      Accept: "application/json",
-      "Content-Type": "application/json; charset=utf-8",
-    },
-    method: "POST",
-    signal: budgetSimulationAbortController.signal,
-  })
-  if (!response.ok) {
-    console.error(
-      `${baseUrl}: Error while calculating budget:\n${body}\n${response.status} ${response.statusText}`,
-    )
-    console.error(await response.text())
-    shared.budgetSimulation = {
-      errors: [
-        `Une erreur inattendue (${response.status} ${response.statusText}) s'est produite et les impacts budgétaires ne sont pas disponibles pour le moment. Écrivez-nous à leximpact@assemblee-nationale.fr.`,
-      ],
-      isPublic: true,
-    }
-    shared.budgetSimulation = undefined
-    return
-  }
-  shared.budgetSimulation = await response.json()
-}
-
-export async function sendBudgetSimulationDemand() {
-  const budgetVariableName = shared.displayMode?.parametersVariableName
-  if (
-    budgetVariableName === undefined ||
-    !budgetVariablesName.has(budgetVariableName)
-  ) {
-    console.error(
-      `Budget calculation for variable ${budgetVariableName} is not available`,
-    )
-    shared.budgetSimulation = undefined
-    return
-  }
-  const variableName = budgetVariableNameByVariableName[budgetVariableName]
-  const variableConfig: BudgetVariable = budgetVariablesConfig[variableName]
-  const simulationBody = buildBudgetDemandBody(
-    variableName,
-    variableConfig.outputVariables,
-    variableConfig.quantileBaseVariable,
-    variableConfig.quantileCompareVariables,
-    false,
-  )
-  const urlString = "/budgets/demands"
-  const res = await fetch(urlString, {
-    body: JSON.stringify(
-      {
-        displayMode: shared.displayMode,
-        email: shared.requestedSimulationEmail,
-        request: Object.fromEntries(
-          Object.entries(shared.parametricReform).filter(([parameterName]) =>
-            budgetEditableParametersName.has(parameterName),
-          ),
-        ),
-        simulation: simulationBody,
-      },
-      null,
-      2,
-    ),
-    headers: {
-      Accept: "application/json",
-      "Content-Type": "application/json; charset=utf-8",
-    },
-    method: "POST",
-  })
-  if (!res.ok) {
-    shared.requestedSimulationEmail = undefined
-    console.error(
-      `Error ${
-        res.status
-      } while sending a simulation request at ${urlString}\n\n${await res.text()}`,
-    )
-    return
-  }
-  shared.requestedSimulationEmail = undefined
-}
-
-export function updateOnSliderChange(situations: Situation[]): void {
-  let changedAxesSituationIndex: number[] = []
-  let evaluationByNameArrayChanged = false
-  const updatedEvaluationByNameArray = [...shared.evaluationByNameArray]
-  for (const [situationIndex, situation] of situations.entries()) {
-    const slider = situation.slider
-    if (slider === undefined) {
-      if (axisBySituationIndex[situationIndex] !== undefined) {
-        axisBySituationIndex = { ...axisBySituationIndex }
-        delete axisBySituationIndex[situationIndex]
-        changedAxesSituationIndex.push(situationIndex)
-
-        // Change only vectorIndexes content, because vectorIndexes reactivity
-        // is not needed.
-        vectorIndexes[situationIndex] = 0
-      }
-    } else {
-      const currentAxis = axisBySituationIndex[situationIndex]
-      if (
-        currentAxis === undefined ||
-        currentAxis.name !== slider.name ||
-        currentAxis.min !== slider.min ||
-        currentAxis.max !== slider.max
-      ) {
-        const entity = entityByKey[slider.entity]
-
-        let previousSituationsPopulationCount = 0
-        for (const situation of shared.testCases.slice(0, situationIndex)) {
-          const entitySituation = situation[entity.key_plural as string] ?? {}
-          const populationCount = Object.keys(entitySituation).length
-          previousSituationsPopulationCount += populationCount
-        }
-        const axis = {
-          count: 101,
-          index:
-            previousSituationsPopulationCount +
-            indexOfSituationPopulationId(entity, situation, slider.id),
-          max: slider.max,
-          min: slider.min,
-          name: slider.name,
-          period: year.toString(), // Previous years are added later.
-          situationIndex: previousSituationsPopulationCount,
-        }
-        axisBySituationIndex = {
-          ...axisBySituationIndex,
-          [situationIndex]: axis,
-        }
-        changedAxesSituationIndex.push(situationIndex)
-      }
-
-      const vectorIndex = slider.vectorIndex
-      if (vectorIndex !== vectorIndexes[situationIndex]) {
-        // Change only vectorIndexes content, because vectorIndexes reactivity
-        // is not needed.
-        vectorIndexes[situationIndex] = vectorIndex
-
-        // Update evaluations.
-        let evaluationByName = updatedEvaluationByNameArray[situationIndex]
-        const updatedEvaluationByName = updateEvaluationsVectorIndex(
-          evaluationByName,
-          vectorIndex,
-        )
-        if (updatedEvaluationByName !== evaluationByName) {
-          updatedEvaluationByNameArray[situationIndex] = updatedEvaluationByName
-          evaluationByNameArrayChanged = true
-        }
-      }
-    }
-  }
-
-  if (changedAxesSituationIndex.length > 0) {
-    const parallelAxes = Object.entries(axisBySituationIndex)
-      .sort(([situationIndex1], [situationIndex2]) =>
-        situationIndex1.localeCompare(situationIndex2),
-      )
-      .map(([, axis]) => axis)
-      .reduce((parallelAxes, axis) => {
-        // Add previous years of each axis.
-        const year = parseInt(axis.period as string)
-        parallelAxes.push(
-          {
-            ...axis,
-            period: (year - 2).toString(),
-          },
-          {
-            ...axis,
-            period: (year - 1).toString(),
-          },
-          axis,
-        )
-        return parallelAxes
-      }, [] as Axis[])
-
-    shared.axes = parallelAxes.length === 0 ? [] : [parallelAxes]
-
-    let updatedVectorLength = 1
-    for (const parallelAxes of shared.axes) {
-      // All the parallel axes have the same count.
-      const axis = parallelAxes[0]
-      updatedVectorLength *= axis.count
-    }
-    shared.vectorLength = updatedVectorLength
-
-    for (const situationIndex of changedAxesSituationIndex) {
-      updatedEvaluationByNameArray[situationIndex] = updateEvaluations(
-        shared.decompositionByName,
-        updatedEvaluationByNameArray[situationIndex],
-        vectorIndexes[situationIndex],
-        shared.vectorLength,
-        waterfalls,
-      )
-      evaluationByNameArrayChanged = true
-    }
-
-    // Launch calculations if the axes have not been deleted (ie shared.vectorLength > 1).
-    const situationIndex =
-      changedAxesSituationIndex.length === 1
-        ? changedAxesSituationIndex[0]
-        : undefined
-    requestAllTestCasesCalculations(situationIndex ?? null)
-  }
-
-  if (evaluationByNameArrayChanged) {
-    shared.evaluationByNameArray = updatedEvaluationByNameArray
-  }
-}
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index b3c1cca51a2533aff7af5e285fd46302e18b3ef0..2716dbae8a69f0e1ce391018c645f7e19c779b93 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -11,21 +11,30 @@
   import { onNavigate } from "$app/navigation"
   import { page } from "$app/stores"
   import {
+    calculateBudget,
     calculateTestCases,
     calculateTestCasesAdditionalVariables,
     requestAllTestCasesCalculations,
     requestedCalculations,
+    sendBudgetSimulationDemand,
   } from "$lib/calculations.svelte"
   import NavBar from "$lib/components/NavBar.svelte"
   import { trackPageView, trackParametricReform } from "$lib/matomo"
   import { getNavbarConfig } from "$lib/navbar"
   import publicConfig from "$lib/public_config"
+  import { shared, year } from "$lib/shared.svelte"
   import {
-    calculateBudget,
-    sendBudgetSimulationDemand,
-    shared,
-    updateOnSliderChange,
-  } from "$lib/shared.svelte"
+    type Axis,
+    indexOfSituationPopulationId,
+    type Situation,
+    testCasesCore,
+  } from "$lib/situations"
+  import { entityByKey } from "$lib/entities"
+  import {
+    updateEvaluations,
+    updateEvaluationsVectorIndex,
+    waterfalls,
+  } from "$lib/decompositions"
 
   interface Props {
     data: LayoutData
@@ -38,6 +47,8 @@
 
   let { url } = $derived($page)
 
+  let axisBySituationIndex: { [situationIndex: string]: Axis } = {}
+
   const customOpenGraphRoutes = [
     /\/fonctionnement/,
     /\/test_cases\/simulations\/.*/,
@@ -47,6 +58,8 @@
     shared.navbarConfig = getNavbarConfig($page.route.id)
   }
 
+  const vectorIndexes = new Array(testCasesCore.length).fill(0)
+
   $effect(() => {
     if (matomoConfig !== undefined) {
       matomo(matomoConfig)
@@ -108,30 +121,15 @@
     })()
   }
 
-  $effect(() => {
-    updateOnSliderChange(shared.testCases)
-  })
   onMount(() => {
     // Launch first simulation.
     requestAllTestCasesCalculations(null)
+
+    // Initialize showTutorial from localStorage
+    shared.showTutorial = localStorage.getItem("hideTutorial") === null
   })
-  $effect(() => {
-    if (
-      requestedCalculations.budgetVariableName !== undefined &&
-      requestedCalculations.budgetCalculationNames !== undefined
-    ) {
-      delete requestedCalculations.budgetCalculationNames
-      calculateBudget(
-        requestedCalculations.budgetVariableName /*, budgetCalculationNames */,
-      ) // Don't await
-      shared.requestedSimulationSent = false
-    }
-  })
-  $effect(() => {
-    if (shared.requestedSimulationEmail !== undefined) {
-      sendBudgetSimulationDemand()
-    }
-  })
+
+  // Test cases calculations
   $effect(() => {
     if (
       Object.keys(requestedCalculations.situationIndexByCalculationName)
@@ -152,14 +150,158 @@
     }
   })
   $effect(() => {
-    shared.showTutorial = localStorage.getItem("hideTutorial") === null
+    updateOnSliderChange(shared.testCases)
   })
 
+  // Budget calculations
+  $effect(() => {
+    if (
+      requestedCalculations.budgetVariableName !== undefined &&
+      requestedCalculations.budgetCalculationNames !== undefined
+    ) {
+      delete requestedCalculations.budgetCalculationNames
+      calculateBudget(
+        requestedCalculations.budgetVariableName /*, budgetCalculationNames */,
+      ) // Don't await
+      shared.requestedSimulationSent = false
+    }
+  })
+  $effect(() => {
+    if (shared.requestedSimulationEmail !== undefined) {
+      sendBudgetSimulationDemand()
+    }
+  })
+
+  // Change navbar layout when navigating
   onNavigate((navigation) => {
     if (navigation.to?.route?.id !== navigation.from?.route?.id) {
       shared.navbarConfig = getNavbarConfig(navigation.to?.route?.id ?? "/")
     }
   })
+
+  function updateOnSliderChange(situations: Situation[]): void {
+    let changedAxesSituationIndex: number[] = []
+    let evaluationByNameArrayChanged = false
+    const updatedEvaluationByNameArray = [...shared.evaluationByNameArray]
+    for (const [situationIndex, situation] of situations.entries()) {
+      const slider = situation.slider
+      if (slider === undefined) {
+        if (axisBySituationIndex[situationIndex] !== undefined) {
+          axisBySituationIndex = { ...axisBySituationIndex }
+          delete axisBySituationIndex[situationIndex]
+          changedAxesSituationIndex.push(situationIndex)
+
+          // Change only vectorIndexes content, because vectorIndexes reactivity
+          // is not needed.
+          vectorIndexes[situationIndex] = 0
+        }
+      } else {
+        const currentAxis = axisBySituationIndex[situationIndex]
+        if (
+          currentAxis === undefined ||
+          currentAxis.name !== slider.name ||
+          currentAxis.min !== slider.min ||
+          currentAxis.max !== slider.max
+        ) {
+          const entity = entityByKey[slider.entity]
+
+          let previousSituationsPopulationCount = 0
+          for (const situation of shared.testCases.slice(0, situationIndex)) {
+            const entitySituation = situation[entity.key_plural as string] ?? {}
+            const populationCount = Object.keys(entitySituation).length
+            previousSituationsPopulationCount += populationCount
+          }
+          axisBySituationIndex[situationIndex] = {
+            count: 101,
+            index:
+              previousSituationsPopulationCount +
+              indexOfSituationPopulationId(entity, situation, slider.id),
+            max: slider.max,
+            min: slider.min,
+            name: slider.name,
+            period: year.toString(), // Previous years are added later.
+            situationIndex: previousSituationsPopulationCount,
+          }
+          changedAxesSituationIndex.push(situationIndex)
+        }
+
+        const vectorIndex = slider.vectorIndex
+        if (vectorIndex !== vectorIndexes[situationIndex]) {
+          // Change only vectorIndexes content, because vectorIndexes reactivity
+          // is not needed.
+          vectorIndexes[situationIndex] = vectorIndex
+
+          // Update evaluations.
+          let evaluationByName = updatedEvaluationByNameArray[situationIndex]
+          const updatedEvaluationByName = updateEvaluationsVectorIndex(
+            evaluationByName,
+            vectorIndex,
+          )
+          if (updatedEvaluationByName !== evaluationByName) {
+            updatedEvaluationByNameArray[situationIndex] =
+              updatedEvaluationByName
+            evaluationByNameArrayChanged = true
+          }
+        }
+      }
+    }
+
+    if (changedAxesSituationIndex.length > 0) {
+      const parallelAxes = Object.entries(axisBySituationIndex)
+        .sort(([situationIndex1], [situationIndex2]) =>
+          situationIndex1.localeCompare(situationIndex2),
+        )
+        .map(([, axis]) => axis)
+        .reduce((parallelAxes, axis) => {
+          // Add previous years of each axis.
+          const year = parseInt(axis.period as string)
+          parallelAxes.push(
+            {
+              ...axis,
+              period: (year - 2).toString(),
+            },
+            {
+              ...axis,
+              period: (year - 1).toString(),
+            },
+            axis,
+          )
+          return parallelAxes
+        }, [] as Axis[])
+
+      shared.axes = parallelAxes.length === 0 ? [] : [parallelAxes]
+
+      let updatedVectorLength = 1
+      for (const parallelAxes of shared.axes) {
+        // All the parallel axes have the same count.
+        const axis = parallelAxes[0]
+        updatedVectorLength *= axis.count
+      }
+      shared.vectorLength = updatedVectorLength
+
+      for (const situationIndex of changedAxesSituationIndex) {
+        updatedEvaluationByNameArray[situationIndex] = updateEvaluations(
+          shared.decompositionByName,
+          updatedEvaluationByNameArray[situationIndex],
+          vectorIndexes[situationIndex],
+          shared.vectorLength,
+          waterfalls,
+        )
+        evaluationByNameArrayChanged = true
+      }
+
+      // Launch calculations if the axes have not been deleted (ie shared.vectorLength > 1).
+      const situationIndex =
+        changedAxesSituationIndex.length === 1
+          ? changedAxesSituationIndex[0]
+          : undefined
+      requestAllTestCasesCalculations(situationIndex ?? null)
+    }
+
+    if (evaluationByNameArrayChanged) {
+      shared.evaluationByNameArray = updatedEvaluationByNameArray
+    }
+  }
 </script>
 
 <svelte:head>
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 095dfc7d0860a00a04acdfb299f9819ecd5f5b00..30eaf8c4f9a5ae2bf3e556f721d4863a92188f54 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -467,9 +467,7 @@
   }
 
   function changeSituation(situationIndex: number, situation: Situation): void {
-    const situations = [...shared.testCases]
-    situations[situationIndex] = situation
-    shared.testCases = situations
+    shared.testCases[situationIndex] = situation
   }
 
   function changeTestCasesIndex(testCasesIndex: number[]): void {