diff --git a/ivette/src/dome/renderer/controls/buttons.tsx b/ivette/src/dome/renderer/controls/buttons.tsx
index 1904aa78410d17a45c1e47785e4312d7c8c59946..f9daf26c85797c1bfbd594c5b5273f0d37a9978e 100644
--- a/ivette/src/dome/renderer/controls/buttons.tsx
+++ b/ivette/src/dome/renderer/controls/buttons.tsx
@@ -570,6 +570,7 @@ export function Spinner(props: SpinnerProps): JSX.Element {
   return (
     <input
       id={props.id}
+      title={props.title}
       type="number"
       value={props.value}
       min={props.vmin}
diff --git a/ivette/src/dome/renderer/controls/gallery.json b/ivette/src/dome/renderer/controls/gallery.json
index a82336982c7b90bc4e359b84d4930e6ea9118d56..a0dfa165279f7565753ef23a0bae10ef7a562c32 100644
--- a/ivette/src/dome/renderer/controls/gallery.json
+++ b/ivette/src/dome/renderer/controls/gallery.json
@@ -412,6 +412,12 @@
     "viewBox": "0 0 16 16",
     "path": "M3.646 9.146a.5.5 0 0 1 .708 0L8 12.793l3.646-3.647a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 0-.708zm0-2.292a.5.5 0 0 0 .708 0L8 3.207l3.646 3.647a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 0 0 0 .708z"
   },
+  "REDO": {
+    "section": "Arrows",
+    "title": "Redo",
+    "viewBox": "0 0 32 32",
+    "path": "M0 18c0 4.779 2.095 9.068 5.417 12l2.646-3c-2.491-2.199-4.063-5.416-4.063-9 0-6.627 5.373-12 12-12 3.314 0 6.314 1.343 8.485 3.515l-4.485 4.485h12v-12l-4.687 4.687c-2.895-2.896-6.895-4.687-11.313-4.687-8.837 0-16 7.163-16 16z"
+  },
   "MEDIA.PREV": {
     "section": "Media",
     "title": "Previous",
diff --git a/ivette/src/dome/renderer/data/json.ts b/ivette/src/dome/renderer/data/json.ts
index fe6de213a20bcb96149cea9e5cc8b533f1c1f049..7708684b9b3005f474c6c11fd76a090ae32f8cb8 100644
--- a/ivette/src/dome/renderer/data/json.ts
+++ b/ivette/src/dome/renderer/data/json.ts
@@ -57,7 +57,7 @@ export class JsonError extends Error {
   }
 }
 
-class JsonTypeError extends JsonError {
+export class JsonTypeError extends JsonError {
   expected: string;
 
   constructor(expected: string, given: json) {
diff --git a/ivette/src/frama-c/kernel/Properties.tsx b/ivette/src/frama-c/kernel/Properties.tsx
index dd0d72fc948378fba002bcd75405f79840c513d1..19313c95f70dcaef7d82de6746604d12a9626032 100644
--- a/ivette/src/frama-c/kernel/Properties.tsx
+++ b/ivette/src/frama-c/kernel/Properties.tsx
@@ -45,7 +45,6 @@ import { RSplit } from 'dome/layout/splitters';
 import { TitleBar } from 'ivette';
 import { menuItem, setting } from './Globals';
 
-
 import * as Ast from 'frama-c/kernel/api/ast';
 import * as Eva from 'frama-c/plugins/eva/api/general';
 import * as Properties from 'frama-c/kernel/api/properties';
@@ -306,7 +305,7 @@ function filterNames(names: string[]): boolean {
   return regex.test(strNames);
 }
 
-function filterProperty(p: Property): boolean {
+export function filterProperty(p: Property): boolean {
   return filterStatus(p.status)
     && filterKind(p.kind)
     && filterAlarm(p.alarm)
@@ -417,7 +416,7 @@ const renderPriority: Renderer<boolean> =
   (prio: boolean): JSX.Element | null =>
     (prio ? <Icon id="ATTENTION" /> : null);
 
-const renderTaint: Renderer<States.Tag> =
+export const renderTaint: Renderer<States.Tag> =
   (taint: States.Tag): JSX.Element | null => {
     let id = null;
     let color = 'black';
diff --git a/ivette/src/frama-c/plugins/callgraph/callgraph.css b/ivette/src/frama-c/plugins/callgraph/callgraph.css
index 4360e1461c2db03e11e4e3d205ce09e17ea74809..a52a39b484898654bdb2b4bb933e0d00f4c22355 100644
--- a/ivette/src/frama-c/plugins/callgraph/callgraph.css
+++ b/ivette/src/frama-c/plugins/callgraph/callgraph.css
@@ -1,6 +1,151 @@
-.callgraph-computing {
+.cg-graph-computing {
   max-width: 90%;
   max-height: 200px;
   fill: var(--info-text-discrete);
   margin: auto;
 }
+
+.cg-graph-container {
+  position: relative;
+  width: 100%;
+  height:100%;
+
+  .dome-xPanel-list {
+    width: 100%;
+  }
+
+  .dome-xBoxes-hbox.dome-xBoxes-box:has(.cg-panel-sidebuttons) {
+    justify-content: space-between;
+  }
+
+  .cg-panel-element {
+    margin:5px;
+    background-color: var(--background-alterning-even);
+    overflow-x: hidden;
+    border-radius: 0 10px;
+
+    .cg-panel-element-name {
+      background-color: var(--background-alterning-odd);
+      font-weight: bold;
+      width: 100%;
+      padding: 5px;
+
+      &:hover {
+        cursor: pointer;
+        background-color: var(--background-interaction);
+      }
+    }
+
+    .cg-panel-element-content {
+      padding: 4px;
+
+      .cg-panel-priority {
+        fill: var(--warning);
+      }
+
+      .cg-panel-taint {
+        padding: 2px;
+        margin-bottom: 5px;
+        border-radius: 7px;
+        background-color: var(--background-softer);
+      }
+
+      .cg-panel-errors {
+        display: flex;
+        flex-direction: column;
+        border-radius: 0 7px;
+
+        background-color: var(--background-softer);
+        .cg-panel-error {
+          display: flex;
+          flex-direction:row;
+          align-items: center;
+
+          &:hover,
+          &:hover label {
+            background-color: var(--background-profound);
+            cursor: pointer;
+          }
+        }
+      }
+    }
+  }
+
+  .cg-filter-panel {
+    display: flex;
+
+    .dome-xToolBar-buttongroup button:first-child {
+      border-right: solid 1px rgb(from var(--text) r g b / .3);
+    }
+  }
+
+  .node-graph {
+    display: flex;
+    align-items: center;
+    display: flex;
+    background-color: rgb(from var(--background-profound) r g b / .5);
+    padding: .5em;
+    border-radius: .5em;
+    pointer-events: auto;
+
+    &:hover {
+      background-color: var(--background-softer);
+      cursor: pointer;
+    }
+
+    &.node-selected {
+      border: solid var(--activated-button-color) 2px;
+    }
+
+    .dome-xButton-led {
+      display: block;
+    }
+  }
+
+  .cg-display-mode {
+    display: flex;
+  }
+
+}
+
+div.cg-spinner {
+  padding-left: 5px;
+}
+
+.cg-three-states {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+
+  .cg-number-button {
+    display: flex;
+    flex-direction:row;
+    height: 22px;
+
+    .cg-plus-minus {
+      margin: 0;
+      padding: 2px;
+    }
+  }
+
+  .dome-xToolBar-control label:hover,
+  .dome-xToolBar-control:hover {
+    cursor: pointer;
+  }
+
+  .three-button-label,
+  .three-button-label:hover {
+    background: none;
+  }
+
+  .three-button-label:hover,
+  .three-button-label label:hover {
+    cursor: default;
+  }
+
+  .dome-xToolBar-buttongroup {
+    padding: 0;
+    margin: 0;
+    border-bottom: solid 1px var(--selected-button-img);
+  }
+}
diff --git a/ivette/src/frama-c/plugins/callgraph/components/node.tsx b/ivette/src/frama-c/plugins/callgraph/components/node.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b61a91242829747fb5e9a82e02088859378cbca7
--- /dev/null
+++ b/ivette/src/frama-c/plugins/callgraph/components/node.tsx
@@ -0,0 +1,92 @@
+/* ************************************************************************ */
+/*                                                                          */
+/*   This file is part of Frama-C.                                          */
+/*                                                                          */
+/*   Copyright (C) 2007-2024                                                */
+/*     CEA (Commissariat à l'énergie atomique et aux énergies               */
+/*          alternatives)                                                   */
+/*                                                                          */
+/*   you can redistribute it and/or modify it under the terms of the GNU    */
+/*   Lesser General Public License as published by the Free Software        */
+/*   Foundation, version 2.1.                                               */
+/*                                                                          */
+/*   It is distributed in the hope that it will be useful,                  */
+/*   but WITHOUT ANY WARRANTY; without even the implied warranty of         */
+/*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          */
+/*   GNU Lesser General Public License for more details.                    */
+/*                                                                          */
+/*   See the GNU Lesser General Public License version 2.1                  */
+/*   for more details (enclosed in the file licenses/LGPLv2.1).             */
+/*                                                                          */
+/* ************************************************************************ */
+
+import React from 'react';
+import { NodeObject as NodeObject3D } from 'react-force-graph-3d';
+
+import { classes } from 'dome/misc/utils';
+import { LED } from 'dome/controls/displays';
+import { Icon } from 'dome/controls/icons';
+import { renderTaint } from 'frama-c/kernel/Properties';
+
+import { SelectedNodes, CGNode } from "../definitions";
+
+const isTaintedScope = (node: NodeObject3D<CGNode>): boolean => {
+  return Boolean(
+    node.taintStatus && node.taintStatus.length > 0 &&
+    (
+      node.taintStatus.find((elt) => elt.name === "direct_taint") ||
+      node.taintStatus.find((elt) => elt.name === "indirect_taint")
+    )
+  );
+};
+
+const getNodeAlarms = (node: CGNode): JSX.Element => {
+  return <>
+    <div>
+      {node.alarmStatuses && node.alarmStatuses.invalid > 0 && LED({
+        status: "negative",
+        title: node.alarmStatuses.invalid+" invalid",
+        })}
+      {node.alarmStatuses && node.alarmStatuses.unknown > 0 && LED({
+        status: "warning",
+        title: node.alarmStatuses.unknown+" unknown",
+        })}
+    </div>
+  </>;
+};
+
+const getNodeText = (node: CGNode): string => {
+  return node.label || "";
+};
+
+export const getNode = (
+  node: NodeObject3D<CGNode>,
+  selectedNodes: SelectedNodes,
+  multiSelectFunction:
+    (id: string, event: MouseEvent | React.MouseEvent) => void
+): JSX.Element => {
+  const className = classes(
+    'node-graph',
+    selectedNodes.set.has(node.id) && "node-selected"
+  );
+
+  const select = (event: React.MouseEvent): void => {
+    multiSelectFunction(node.id, event);
+  };
+
+  return (
+    <div className={className} onClick={select}>
+      <div>
+        { getNodeText(node) }
+      </div>
+      { node.isRecursive &&
+        <Icon
+          id={"REDO"} size={11}
+          fill={"orange"} title={"Recursive function"}
+        />
+      }
+      { getNodeAlarms(node) }
+      { isTaintedScope(node) && renderTaint({ name: "direct_taint" }) }
+    </div>
+  );
+};
diff --git a/ivette/src/frama-c/plugins/callgraph/components/panel.tsx b/ivette/src/frama-c/plugins/callgraph/components/panel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..939b6dae5e252ac54e2e61e683a9169336c4985a
--- /dev/null
+++ b/ivette/src/frama-c/plugins/callgraph/components/panel.tsx
@@ -0,0 +1,341 @@
+/* ************************************************************************ */
+/*                                                                          */
+/*   This file is part of Frama-C.                                          */
+/*                                                                          */
+/*   Copyright (C) 2007-2024                                                */
+/*     CEA (Commissariat à l'énergie atomique et aux énergies               */
+/*          alternatives)                                                   */
+/*                                                                          */
+/*   you can redistribute it and/or modify it under the terms of the GNU    */
+/*   Lesser General Public License as published by the Free Software        */
+/*   Foundation, version 2.1.                                               */
+/*                                                                          */
+/*   It is distributed in the hope that it will be useful,                  */
+/*   but WITHOUT ANY WARRANTY; without even the implied warranty of         */
+/*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          */
+/*   GNU Lesser General Public License for more details.                    */
+/*                                                                          */
+/*   See the GNU Lesser General Public License version 2.1                  */
+/*   for more details (enclosed in the file licenses/LGPLv2.1).             */
+/*                                                                          */
+/* ************************************************************************ */
+
+import React from 'react';
+
+import * as Dome from 'dome';
+import { Label } from 'dome/controls/labels';
+import { Panel as DPanel, ListElement } from 'dome/frame/panel';
+import { ButtonGroup, Button } from 'dome/frame/toolbars';
+import { LED } from 'dome/controls/displays';
+import { Icon } from 'dome/controls/icons';
+
+import { decl } from 'frama-c/kernel/api/ast';
+import { statusData } from 'frama-c/kernel/api/properties';
+import { onContextMenu, EFilterType } from 'frama-c/kernel/Properties';
+import * as Eva from 'frama-c/plugins/eva/api/general';
+import * as States from 'frama-c/states';
+import {
+  renderTaint, useEvaPropertiesFilter, useKindPropertiesFilter, useStatusFilter
+} from 'frama-c/kernel/Properties';
+
+import { CGData, SelectedNodes } from "../definitions";
+import { IconButton } from 'dome/controls/buttons';
+
+
+/* -------------------------------------------------------------------------- */
+/* --- Callgraph Panel component                                          --- */
+/* -------------------------------------------------------------------------- */
+
+interface PanelFilterParams {
+  contextMenuStatus: Dome.PopupMenuItem[];
+  contextMenuKind: Dome.PopupMenuItem[];
+  contextMenuEva: Dome.PopupMenuItem[];
+}
+
+function getPanelFilters(params: PanelFilterParams): JSX.Element {
+  const { contextMenuStatus, contextMenuKind, contextMenuEva } = params;
+
+  return (
+    <>
+      <ButtonGroup>
+        <Button
+          label="Status"
+          title={`Select visible status`}
+          onClick={() => Dome.popupMenu(contextMenuStatus)}
+          />
+        <Button
+          icon={'TUNINGS'}
+          title={`Select visible status`}
+          onClick={() => onContextMenu(EFilterType.STATUS)}
+          />
+      </ButtonGroup>
+
+      <ButtonGroup>
+        <Button
+          label="Kind"
+          title={`Select visible kind`}
+          onClick={() => Dome.popupMenu(contextMenuKind)}
+          />
+        <Button
+          icon={'TUNINGS'}
+          title={`Select visible status`}
+          onClick={() => onContextMenu(EFilterType.KIND)}
+          />
+      </ButtonGroup>
+
+      <Button
+          label="Eva"
+          title={`Select visible Eva properties`}
+          onClick={() => Dome.popupMenu(contextMenuEva)}
+          />
+    </>
+  );
+}
+
+interface IGetElementParams {
+  graph?: CGData;
+  id?: string;
+  properties: statusData[];
+  evaProperties: Eva.propertiesData[];
+  style: CSSStyleDeclaration;
+  showStatus: (status: statusData) => boolean;
+  showKind: (status: statusData) => boolean;
+  showEva: (status: statusData) => boolean;
+}
+
+function getElement(
+  params: IGetElementParams
+): JSX.Element | undefined {
+  const { graph, id, properties, evaProperties,
+    style, showStatus, showKind, showEva } = params;
+
+  if (!id || !graph) return undefined;
+
+  const allProperties = properties.map((elt) => {
+    const evap = evaProperties.find((evaps) => evaps.key === elt.key);
+    return Object.assign(elt, evap);
+  });
+
+  const error = allProperties
+    .filter(showStatus)
+    .filter(showKind)
+    .filter(showEva)
+    .map((elt) => {
+      const priority = elt?.priority;
+      const cssVarName ='--status-'+elt.status.replaceAll("_", '-');
+      const color = style.getPropertyValue(cssVarName);
+
+      return (
+        <div
+          key={elt.key}
+          className="cg-panel-error"
+          title={elt.descr}
+          onClick={() => {
+            States.setCurrentLocation({ scope: elt.scope, marker: elt.key });
+          }}
+        >
+          {LED({
+              title: "status = "+elt.status+"\nkind = "+elt.kind,
+              style: { background: color },
+            })
+          }
+          { priority && <Icon id="ATTENTION" className='cg-panel-priority'/> }
+          <Label>{elt.predicate ?? (elt.status+" : "+elt.kind)}</Label>
+        </div>
+      );
+    }
+  );
+
+  const taint = evaProperties.filter(
+    (ps) => ps.taint === "direct_taint" || ps.taint === "indirect_taint"
+  ).map(
+    (ps) => {
+      let id = null;
+      let color = 'black';
+      switch (ps.taint) {
+        case 'not_tainted': id = 'DROP.EMPTY'; color = '#00B900'; break;
+        case 'direct_taint': id = 'DROP.FILLED'; color = '#882288'; break;
+        default:
+      }
+      return (id ? <Icon key={ps.key} id={id} fill={color} title={ps.key}
+        onClick={() => {
+          const scope = properties.find((elt) => elt.key === ps.key)?.scope;
+          if(scope) States.setCurrentLocation({ scope: scope, marker: ps.key });
+        }}/> : null);
+    }
+  );
+
+  const node = graph?.nodes.find((elt) => elt.id === id);
+  const content: JSX.Element =  (
+    <div key={id} className='cg-panel-element'>
+      <div
+        className='cg-panel-element-name'
+        onClick={() => States.setCurrentLocation({ scope: id as decl })}
+      >
+        {node?.label || ""}
+      </div>
+      <div className='cg-panel-element-content'>
+        { taint.length > 0 &&
+          <div className='cg-panel-taint'>
+            <Label>Taint :</Label>
+            {taint}
+          </div>
+        }
+        { error.length > 0 &&
+          <div className="cg-panel-errors" key={id}>{error}</div>
+        }
+      </div>
+    </div>
+  );
+
+
+  if (id) return content;
+  return undefined;
+}
+
+interface IGetElementListParams {
+  selectedNodes: SelectedNodes;
+  graph?: CGData;
+  properties: statusData[];
+  evaProperties: Eva.propertiesData[];
+  selected?: number;
+  nodes?: number;
+  links?: number;
+  tainted?: boolean;
+  style: CSSStyleDeclaration;
+  showStatus: (status: statusData) => boolean;
+  showKind: (status: statusData) => boolean;
+  showEva: (status: statusData) => boolean;
+}
+
+function getElementList(
+  params: IGetElementListParams
+): JSX.Element[] {
+  const { graph, selectedNodes, properties, evaProperties,
+    style, showStatus, showKind, showEva } = params;
+
+  const list: JSX.Element[] = [];
+
+  selectedNodes.set.forEach((elt: string) => {
+    const propsKeys: string[] = [];
+    const prs = properties.filter((prop) => {
+      if(prop.scope === elt) {
+        propsKeys.push(prop.key);
+      }
+      return prop.scope === elt;
+    });
+    const evaps = evaProperties.filter((evaprop) =>
+      propsKeys.includes(evaprop.key));
+    const newElement = getElement({
+      graph: graph,
+      id: elt,
+      properties: prs,
+      evaProperties: evaps,
+      style,
+      showStatus,
+      showKind,
+      showEva,
+    });
+    if(newElement) list.push(newElement);
+  });
+
+  return list;
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- Panel content                                                      --- */
+/* -------------------------------------------------------------------------- */
+interface PanelContentProps {
+  graphData: CGData;
+  selectedNodes: SelectedNodes;
+  tainted: number;
+  properties: statusData[];
+  evaProperties: Eva.propertiesData[];
+  style: CSSStyleDeclaration;
+  panelVisibleState: [boolean, () => void];
+}
+
+export function Panel(
+  props: PanelContentProps
+): JSX.Element {
+  const { graphData, selectedNodes, tainted,
+    properties, evaProperties, style, panelVisibleState } = props;
+  const countNodes = graphData.nodes.length;
+  const countLink = graphData.links.length;
+  const countSelected = selectedNodes.set.size;
+  const [ panelVisible, flipPanelVisible ] = panelVisibleState;
+
+  const [ positionDefault, flipPositionDefault ] =
+    Dome.useFlipSettings("ivette.callgraph.panel.position.default", true);
+  const { contextMenu: contextMenuStatus,
+    show: showStatus } = useStatusFilter();
+  const { contextMenu: contextMenuKind,
+    show: showKind } = useKindPropertiesFilter();
+  const { contextMenu: contextMenuEva,
+    show: showEva } = useEvaPropertiesFilter();
+
+  return (
+    <DPanel
+      position={ positionDefault ? 'right' : 'left'}
+      visible={panelVisible}
+    >
+      <>
+        <Label label={ countNodes.toString()+" / "+countLink.toString() } >
+          {'( Nodes / links )'}
+        </Label>
+        <div className='cg-panel-sidebuttons'>
+          <IconButton
+            icon={positionDefault ? 'ANGLE.LEFT' : 'ANGLE.RIGHT'}
+            title={"Change the side of the panel"}
+            onClick={flipPositionDefault}
+            />
+          <IconButton
+            icon={'CROSS'}
+            size={13}
+            title={"Hide panel"}
+            onClick={flipPanelVisible}
+            />
+        </div>
+      </>
+      <Label
+        label={
+          countSelected.toString()+" node"+
+          (countSelected > 1 ? "s" : "")+" selected"
+        }
+      />
+      { tainted > 0 ?
+        <Label
+        label='Taint legend:'
+        >
+          {
+            <>
+            {renderTaint({ name: "direct_taint", descr: "direct_taint" })}
+            {renderTaint({ name: "indirect_taint", descr: "indirect_taint" })}
+            </>
+          }
+        </Label> : <></>
+        }
+      <Label className='cg-filter-panel'>
+        {getPanelFilters({
+          contextMenuStatus,
+          contextMenuKind,
+          contextMenuEva
+        })}
+      </Label>
+      <ListElement>
+        { getElementList({
+          graph: graphData,
+          selectedNodes,
+          properties,
+          evaProperties,
+          nodes: graphData.nodes.length,
+          links: graphData.links.length,
+          tainted: tainted > 0,
+          style,
+          showKind, showStatus, showEva
+        }).map((elt) => elt )
+        }
+      </ListElement>
+    </DPanel>
+  );
+}
diff --git a/ivette/src/frama-c/plugins/callgraph/components/threeStateButton.tsx b/ivette/src/frama-c/plugins/callgraph/components/threeStateButton.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..23a1081270bcb019deb697790030883b016da8d5
--- /dev/null
+++ b/ivette/src/frama-c/plugins/callgraph/components/threeStateButton.tsx
@@ -0,0 +1,80 @@
+/* ************************************************************************ */
+/*                                                                          */
+/*   This file is part of Frama-C.                                          */
+/*                                                                          */
+/*   Copyright (C) 2007-2024                                                */
+/*     CEA (Commissariat à l'énergie atomique et aux énergies               */
+/*          alternatives)                                                   */
+/*                                                                          */
+/*   you can redistribute it and/or modify it under the terms of the GNU    */
+/*   Lesser General Public License as published by the Free Software        */
+/*   Foundation, version 2.1.                                               */
+/*                                                                          */
+/*   It is distributed in the hope that it will be useful,                  */
+/*   but WITHOUT ANY WARRANTY; without even the implied warranty of         */
+/*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          */
+/*   GNU Lesser General Public License for more details.                    */
+/*                                                                          */
+/*   See the GNU Lesser General Public License version 2.1                  */
+/*   for more details (enclosed in the file licenses/LGPLv2.1).             */
+/*                                                                          */
+/* ************************************************************************ */
+
+import React from 'react';
+import { Button, ButtonGroup } from 'dome/frame/toolbars';
+
+/* -------------------------------------------------------------------------- */
+/* --- ThreeStateButton component                                         --- */
+/* -------------------------------------------------------------------------- */
+export interface IThreeStateButton {
+  active: boolean,
+  max: boolean,
+  value: number,
+}
+
+interface ThreeStateButtonProps {
+  label?: string;
+  icon?: string;
+  title?: string;
+  buttonState: [
+    IThreeStateButton,
+    (newValue: IThreeStateButton) => void
+  ];
+}
+
+export function ThreeStateButton(
+  props: ThreeStateButtonProps
+): JSX.Element {
+  const { label, icon, title, buttonState } = props;
+  const [ button, setButton ] = buttonState;
+
+  const onClickAll = (): void =>
+    setButton({ ...button, active: !button.max, max: !button.max });
+  const onClickVal = (): void => {
+    const newVal = button.max ? true : !button.active;
+    setButton({ ...button, active: newVal, max: false }
+  ); };
+  const onUpVal = (): void =>
+    setButton({ ...button, value: button.value ? button.value + 1 : 1 });
+  const onDownVal = (): void =>
+    setButton({ ...button, value: button.value ? button.value - 1 : 0 });
+
+  return (
+    <div className='cg-three-states'>
+      <ButtonGroup className='cg-number-button'>
+        <Button
+          className="three-button-label"
+          label={label} icon={icon} title={title}
+        />
+        <Button label="All" selected={button.max} onClick={onClickAll} />
+        <Button
+          label={button.value.toString()}
+          selected={button.active && !button.max}
+          onClick={onClickVal}
+        />
+        <Button icon='MINUS' className='cg-plus-minus' onClick={onDownVal} />
+        <Button icon='PLUS'  className='cg-plus-minus' onClick={onUpVal} />
+      </ButtonGroup>
+    </div>
+  );
+}
diff --git a/ivette/src/frama-c/plugins/callgraph/components/titlebar.tsx b/ivette/src/frama-c/plugins/callgraph/components/titlebar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6f6254556ae5637a8bee67142bd116c0285b4616
--- /dev/null
+++ b/ivette/src/frama-c/plugins/callgraph/components/titlebar.tsx
@@ -0,0 +1,77 @@
+/* ************************************************************************ */
+/*                                                                          */
+/*   This file is part of Frama-C.                                          */
+/*                                                                          */
+/*   Copyright (C) 2007-2024                                                */
+/*     CEA (Commissariat à l'énergie atomique et aux énergies               */
+/*          alternatives)                                                   */
+/*                                                                          */
+/*   you can redistribute it and/or modify it under the terms of the GNU    */
+/*   Lesser General Public License as published by the Free Software        */
+/*   Foundation, version 2.1.                                               */
+/*                                                                          */
+/*   It is distributed in the hope that it will be useful,                  */
+/*   but WITHOUT ANY WARRANTY; without even the implied warranty of         */
+/*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          */
+/*   GNU Lesser General Public License for more details.                    */
+/*                                                                          */
+/*   See the GNU Lesser General Public License version 2.1                  */
+/*   for more details (enclosed in the file licenses/LGPLv2.1).             */
+/*                                                                          */
+/* ************************************************************************ */
+
+import React from 'react';
+
+import * as Ivette from 'ivette';
+import * as Dome from 'dome';
+
+import { IconButton } from 'dome/controls/buttons';
+
+/* -------------------------------------------------------------------------- */
+/* --- Callgraph titlebar component                                       --- */
+/* -------------------------------------------------------------------------- */
+interface CallgraphTitleBarProps {
+  /** Context menu to filtering nodes */
+  contextMenuItems: Dome.PopupMenuItem[],
+  /** automatic graph centering */
+  autoCenterState: [boolean, () => void],
+  /** automatic selection */
+  autoSelectState: [boolean, () => void]
+}
+
+export function CallgraphTitleBar(props: CallgraphTitleBarProps): JSX.Element {
+  const { autoCenterState, autoSelectState, contextMenuItems } = props;
+  const [ autoCenter, flipAutoCenter ] = autoCenterState;
+  const [ autoSelect, flipAutoSelect] = autoSelectState;
+
+  return (
+    <Ivette.TitleBar>
+      <IconButton
+        icon={'TUNINGS'}
+        title={`Functions filter`}
+        onClick={() => Dome.popupMenu(contextMenuItems)}
+      />
+      <IconButton
+        icon={"TARGET"}
+        onClick={flipAutoCenter}
+        kind={autoCenter ? "positive" : "default"}
+        title={
+          "If selected, the camera will be moved to show "+
+          "each node after each render"}
+      />
+      <IconButton
+        icon={"PIN"}
+        onClick={flipAutoSelect}
+        kind={autoSelect ? "positive" : "default"}
+        title={"Selected nodes is sync with the current scope"}
+      />
+      <IconButton
+        icon={"HELP"}
+        title={"click: select element\n"+
+          "ctrl+click: Multiselection\n"+
+          "alt+click: change scope"}
+        className="titlebar-thin-icon"
+      />
+    </Ivette.TitleBar>
+  );
+}
diff --git a/ivette/src/frama-c/plugins/callgraph/components/toolbar.tsx b/ivette/src/frama-c/plugins/callgraph/components/toolbar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..39263405a710ecade44da222d2319beb4dfe43dd
--- /dev/null
+++ b/ivette/src/frama-c/plugins/callgraph/components/toolbar.tsx
@@ -0,0 +1,176 @@
+/* ************************************************************************ */
+/*                                                                          */
+/*   This file is part of Frama-C.                                          */
+/*                                                                          */
+/*   Copyright (C) 2007-2024                                                */
+/*     CEA (Commissariat à l'énergie atomique et aux énergies               */
+/*          alternatives)                                                   */
+/*                                                                          */
+/*   you can redistribute it and/or modify it under the terms of the GNU    */
+/*   Lesser General Public License as published by the Free Software        */
+/*   Foundation, version 2.1.                                               */
+/*                                                                          */
+/*   It is distributed in the hope that it will be useful,                  */
+/*   but WITHOUT ANY WARRANTY; without even the implied warranty of         */
+/*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          */
+/*   GNU Lesser General Public License for more details.                    */
+/*                                                                          */
+/*   See the GNU Lesser General Public License version 2.1                  */
+/*   for more details (enclosed in the file licenses/LGPLv2.1).             */
+/*                                                                          */
+/* ************************************************************************ */
+
+import React from 'react';
+
+import * as Dome from 'dome';
+
+import { State } from 'dome/data/states';
+import {  Spinner } from 'dome/controls/buttons';
+import { ToolBar, ButtonGroup, Button, Filler } from 'dome/frame/toolbars';
+
+import {
+  ModeDisplay, SelectedNodesData
+} from "frama-c/plugins/callgraph/definitions";
+
+import { IThreeStateButton, ThreeStateButton } from "./threeStateButton";
+
+/* -------------------------------------------------------------------------- */
+/* --- Callgraph Toolsbar component                                       --- */
+/* -------------------------------------------------------------------------- */
+interface CallgraphToolsBarProps {
+  /* eslint-disable max-len */
+  displayModeState: [ModeDisplay, (newValue: ModeDisplay) => void],
+  selectedParentsState:
+  [IThreeStateButton, (newValue: IThreeStateButton) => void],
+  selectedChildrenState:
+  [IThreeStateButton, (newValue: IThreeStateButton) => void],
+  panelVisibleState: [boolean, () => void],
+  verticalSpacingState: State<number>,
+  horizontalSpacingState: State<number>,
+  selectedFunctions:SelectedNodesData,
+  taintedFunctions: string[],
+  unprovenPropertiesFunctions: SelectedNodesData,
+  cycleFunctions: string[],
+  dagMode?: string;
+  updateNodes: (newSet: SelectedNodesData) => void;
+  /* eslint-enable max-len */
+}
+
+export function CallgraphToolsBar(props: CallgraphToolsBarProps): JSX.Element {
+  const {
+    displayModeState, selectedParentsState,
+    selectedChildrenState, panelVisibleState,
+    verticalSpacingState, horizontalSpacingState,
+    selectedFunctions, taintedFunctions,
+    unprovenPropertiesFunctions, cycleFunctions, dagMode,
+    updateNodes
+  } = props;
+
+  const [displayMode, setDisplayMode] = displayModeState;
+  const [showInfos, flipShowInfos] = panelVisibleState;
+  const [verticalSpacing, setVerticalSpacing] = verticalSpacingState;
+  const [horizontalSpacing, setHorizontalSpacing] = horizontalSpacingState;
+
+  function menuItem(label: string, onClick: ()=>void, enabled?: boolean)
+    : Dome.PopupMenuItem {
+    return {
+      label: label,
+      enabled: enabled !== undefined ? enabled : true,
+      onClick: onClick,
+    };
+  }
+
+  const selectMenuItems: Dome.PopupMenuItem[] = [
+    menuItem('Select unproven properties',
+      () => updateNodes(unprovenPropertiesFunctions),
+      unprovenPropertiesFunctions.size !== 0),
+    menuItem('Select scope from studia',
+      () => updateNodes(selectedFunctions),
+      selectedFunctions.size !== 0),
+    menuItem('Select tainted scope',
+      () => updateNodes(new Set(taintedFunctions)),
+      taintedFunctions.length !== 0),
+    menuItem('Select cycles',
+      () => updateNodes(new Set(cycleFunctions)),
+      cycleFunctions.length !== 0),
+  ];
+
+  return (
+    <ToolBar>
+      <div className='cg-display-mode'>
+        <ButtonGroup className='show-mode-button-group'>
+          <Button
+            label='all'
+            selected={displayMode === 'all'}
+            onClick={() => setDisplayMode("all")}
+            />
+          <Button
+            label='linked'
+            selected={displayMode === 'linked'}
+            onClick={() => setDisplayMode("linked")}
+            />
+          <Button
+            label='selected'
+            selected={displayMode === 'selected'}
+            onClick={() => setDisplayMode("selected")}
+            />
+
+        { displayMode === "selected" ? (
+          <>
+            <ThreeStateButton
+              label={"Parents"}
+              title={"Choose how many parents you want to see."}
+              buttonState={selectedParentsState}
+              />
+            <ThreeStateButton
+              label={"Children"}
+              title={"Choose how many children you want to see."}
+              buttonState={selectedChildrenState}
+              />
+          </>
+        ) : <></>
+        }
+        </ButtonGroup>
+      </div>
+
+      <Button
+        label="select"
+        icon={'TUNINGS'}
+        title={`Select nodes`}
+        onClick={() => Dome.popupMenu(selectMenuItems)}
+      />
+
+      <Filler/>
+
+      <div className='cg-spinner'>
+        hor: <Spinner
+        value={horizontalSpacing}
+        title="Distance between the different graph depths"
+        className='cg-spinner-hor'
+        vmin={0}
+        vstep={100}
+        onChange={setHorizontalSpacing}
+        />
+      </div>
+      <div className='cg-spinner'>
+        ver: <Spinner
+        disabled={dagMode === undefined}
+        title={dagMode === undefined ?
+          "Disabled if the graph has cycles":
+          "Distance between the different graph depths"}
+        value={verticalSpacing}
+        className='cg-spinner-ver'
+        vmin={0}
+        vstep={20}
+        onChange={setVerticalSpacing}
+        />
+      </div>
+      <Button
+        icon="SIDEBAR"
+        title={showInfos ? "Hide panel" : "Show panel"}
+        selected={showInfos}
+        onClick={flipShowInfos}
+      />
+    </ToolBar>
+  );
+}
diff --git a/ivette/src/frama-c/plugins/callgraph/definitions.tsx b/ivette/src/frama-c/plugins/callgraph/definitions.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b6a3525240f0079dabc9612b29c543c0b6207e12
--- /dev/null
+++ b/ivette/src/frama-c/plugins/callgraph/definitions.tsx
@@ -0,0 +1,231 @@
+/* ************************************************************************ */
+/*                                                                          */
+/*   This file is part of Frama-C.                                          */
+/*                                                                          */
+/*   Copyright (C) 2007-2024                                                */
+/*     CEA (Commissariat à l'énergie atomique et aux énergies               */
+/*          alternatives)                                                   */
+/*                                                                          */
+/*   you can redistribute it and/or modify it under the terms of the GNU    */
+/*   Lesser General Public License as published by the Free Software        */
+/*   Foundation, version 2.1.                                               */
+/*                                                                          */
+/*   It is distributed in the hope that it will be useful,                  */
+/*   but WITHOUT ANY WARRANTY; without even the implied warranty of         */
+/*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          */
+/*   GNU Lesser General Public License for more details.                    */
+/*                                                                          */
+/*   See the GNU Lesser General Public License version 2.1                  */
+/*   for more details (enclosed in the file licenses/LGPLv2.1).             */
+/*                                                                          */
+/* ************************************************************************ */
+
+import React from 'react';
+import { Edge, Node } from 'dome/graph/graph';
+import * as Eva from 'frama-c/plugins/eva/api/general';
+import * as States from 'frama-c/states';
+import * as Ast from 'frama-c/kernel/api/ast';
+import {
+  NodeObject as NodeObject3D,
+  LinkObject as LinkObject3D,
+} from 'react-force-graph-3d';
+
+import { IThreeStateButton } from "./components/threeStateButton";
+
+export type ModeDisplay = "all" | "linked" | "selected"
+
+export type SelectedNodesData = Set<string>
+export interface SelectedNodes {
+  tic: boolean;
+  set: SelectedNodesData;
+}
+export interface SetSelectedNodes
+  extends React.Dispatch<React.SetStateAction<SelectedNodes>>{}
+
+export interface CallGraphFunc {
+  /** Update the selected nodes state */
+  updateSelectedNodes: (newSet: SelectedNodesData) => void;
+  /** Check if a node is selected */
+  isSelectedNode: (id: string) => boolean;
+  /** MultiSelect on node click */
+  onNodeClickMultiSelect:
+    (id: string, event: MouseEvent | React.MouseEvent) => void;
+  /** Get link color */
+  getLinkColor: (node: LinkObject3D<CGNode, CGLink>) => string;
+  /** Get link visibility */
+  getLinkVisibility: (node: LinkObject3D<CGNode, CGLink>) => boolean;
+  /** Get link width */
+  getLinkWidth: (node: LinkObject3D<CGNode, CGLink>) => number;
+  /** Get node visibility */
+  getNodeVisibility: (id: string) => boolean;
+}
+
+export interface CGNode extends Node {
+  /** Coverage of the Eva analysis */
+  coverage?: { reachable: number, dead: number };
+  /** Alarms raised by the Eva analysis by category */
+  alarmCount?: Eva.alarmEntry[];
+  /** Alarms statuses emitted by the Eva analysis */
+  alarmStatuses?: Eva.statusesEntry;
+  /** Taint status */
+  taintStatus?: States.Tag[];
+  /** is Recursive function */
+  isRecursive?: boolean;
+}
+
+export interface CGLink extends Edge {}
+
+export interface CGData {
+  nodes: CGNode[];
+  links: CGLink[];
+}
+
+type nodeType = "parents" | "children";
+
+function getIDFromLink(link: LinkObject3D<CGNode, CGLink>)
+: {sourceId:string, targetId: string} {
+  const sourceId = typeof link.source === 'string' ?
+    link.source : (link.source as NodeObject3D<CGNode>).id;
+  const targetId = typeof link.target === 'string' ?
+    link.target : (link.target as NodeObject3D<CGNode>).id;
+  return { sourceId, targetId };
+}
+
+export const callGraphFunction = (
+  selectNodes: [SelectedNodes, SetSelectedNodes],
+  graphData: CGData,
+  displayMode: ModeDisplay,
+  style: CSSStyleDeclaration,
+  selectedParents: IThreeStateButton,
+  selectedChildren: IThreeStateButton,
+): CallGraphFunc => {
+  const [selectedNodes, setSelectedNodes] = selectNodes;
+  const { links } = graphData;
+
+  function removeCycle(toTraited: string[], ids: string[]): string[] {
+    const ret: string[] = [];
+    for (const elt of toTraited) {
+      if(!ids.includes(elt)) ret.push(elt);
+    }
+    return ret;
+  }
+
+  function getNextNodes(type: nodeType, ids: string[]): string[] {
+    const ret: string[] = [];
+    for (const elt of links) {
+      const { sourceId, targetId } = getIDFromLink(elt);
+
+      if(type === "children" && ids.includes(sourceId))
+          ret.push(targetId);
+      else if (type === "parents" && ids.includes(targetId))
+          ret.push(sourceId);
+    }
+    return ret;
+  }
+
+  function getNodes(type: nodeType, depth?: number): string[] {
+    let ids: string[] = Array.from(selectedNodes.set);
+    if (depth === 0) return ids;
+    let nodes = ids;
+    let i = 0;
+    do {
+      const news =
+        getNextNodes(type, nodes).map((elt) => nodes.includes(elt) ? "" : elt);
+      nodes = removeCycle(news, ids);
+      ids = ids.concat(news);
+      i++;
+    } while(nodes.length > 0 && (depth === undefined || i < depth));
+
+    return ids;
+  }
+
+  function getDepth(v: IThreeStateButton): number | undefined {
+    return v.active ? (v.max ? undefined : (v.value ? v.value : 0)) : 0;
+  }
+
+  const successor = getNodes("children", getDepth(selectedChildren));
+  const predecessors = getNodes("parents", getDepth(selectedParents));
+
+  const updateSelectedNodes = (newSet: SelectedNodesData): void => {
+    setSelectedNodes((elt) => {
+      return { tic: !elt.tic, set: new Set(newSet) };
+    });
+  };
+
+  const isSelectedNode = (id: string): boolean => selectedNodes.set.has(id);
+
+  const onNodeClickMultiSelect = (
+    id: string, event: MouseEvent | React.MouseEvent
+  ): void => {
+    const s = selectedNodes.set;
+    if (event.ctrlKey) { // multi-selection
+      s.has(id) ? s.delete(id) : s.add(id);
+    } else if (event.altKey) {
+      States.setCurrentScope(id as Ast.decl);
+      return;
+    } else { // single-selection
+      s.clear();
+      s.add(id);
+    }
+    updateSelectedNodes(s);
+  };
+
+  const getLinkColor = (node: LinkObject3D<CGNode, CGLink>): string => {
+    const { sourceId, targetId } = getIDFromLink(node);
+    let color = "grey";
+    const isDst = isSelectedNode(targetId);
+    const isSrc = isSelectedNode(sourceId);
+
+    if(isDst && isSrc)
+      color = style.getPropertyValue('--graph-ed-color-green');
+    else if(isDst)
+      color = style.getPropertyValue('--graph-ed-color-red');
+    else if(isSrc)
+      color = style.getPropertyValue('--graph-ed-color-blue');
+    return color;
+  };
+
+  const getLinkVisibility = (node: LinkObject3D<CGNode, CGLink>): boolean => {
+    const { sourceId, targetId } = getIDFromLink(node);
+    switch(displayMode) {
+      case "selected":
+        return Boolean(
+          (successor.includes(sourceId) || predecessors.includes(sourceId)) &&
+          (successor.includes(targetId) || predecessors.includes(targetId))
+        );
+      case "linked":
+      case "all":
+      default: return true;
+    }
+  };
+
+  const getLinkWidth = (node: LinkObject3D<CGNode, CGLink>): number => {
+    const { sourceId, targetId } = getIDFromLink(node);
+    return (isSelectedNode(sourceId) || isSelectedNode(targetId)) ? 2 : 1;
+  };
+
+  const getNodeVisibility = (id: string): boolean => {
+    switch(displayMode) {
+      case "linked":
+        if(!links.find((elt:LinkObject3D<CGNode, CGLink>) => {
+          const { sourceId, targetId } = getIDFromLink(elt);
+            return Boolean(sourceId === id || targetId === id);
+          }
+        )) return false;
+        return true;
+      case "selected":
+        return successor.includes(id) || predecessors.includes(id);
+      default: return true;
+    }
+  };
+
+  return {
+    updateSelectedNodes: updateSelectedNodes,
+    isSelectedNode: isSelectedNode,
+    onNodeClickMultiSelect: onNodeClickMultiSelect,
+    getLinkColor: getLinkColor,
+    getLinkWidth: getLinkWidth,
+    getLinkVisibility: getLinkVisibility,
+    getNodeVisibility: getNodeVisibility,
+  };
+};
diff --git a/ivette/src/frama-c/plugins/callgraph/index.tsx b/ivette/src/frama-c/plugins/callgraph/index.tsx
index b1cc5160cc048c2c8d1cb60f88329c8c782463bb..778e18119070d6a05f3af9bacf43d82f9f7a1bcc 100644
--- a/ivette/src/frama-c/plugins/callgraph/index.tsx
+++ b/ivette/src/frama-c/plugins/callgraph/index.tsx
@@ -21,168 +21,344 @@
 /* ************************************************************************ */
 
 import React from 'react';
-import _ from 'lodash';
-import { Icon } from 'dome/controls/icons';
-import * as Ivette from 'ivette';
-import * as Server from 'frama-c/server';
+import {
+  NodeObject as NodeObject3D,
+} from 'react-force-graph-3d';
 
-import * as AstAPI from 'frama-c/kernel/api/ast';
-import * as CgAPI from './api';
-import * as ValuesAPI from 'frama-c/plugins/eva/api/values';
+import * as Ivette from 'ivette';
 
-import Cy from 'cytoscape';
-import CytoscapeComponent from 'react-cytoscapejs';
-import 'frama-c/plugins/dive/cytoscape_libs';
-import 'cytoscape-panzoom/cytoscape.js-panzoom.css';
-import style from './graph-style.json';
+import * as Dome from 'dome';
+import {
+  Graph, IGraphOptions3D, ILinksOptions, INodesOptions
+} from 'dome/graph/graph';
+import { Icon } from 'dome/controls/icons';
+import * as Themes from 'dome/themes';
+import { Decoder, Encoder, json, JsonTypeError } from 'dome/data/json';
+import { useWindowSettingsData } from 'dome/data/settings';
 
-import { useGlobalState } from 'dome/data/states';
+import * as Server from 'frama-c/server';
+import { computeFcts, useFunctionFilter } from 'frama-c/kernel/Globals';
+import * as Ast from 'frama-c/kernel/api/ast';
+import * as Properties from 'frama-c/kernel/api/properties';
 import * as States from 'frama-c/states';
-
-import { CallstackState } from 'frama-c/plugins/eva/valuetable';
+import * as Eva from 'frama-c/plugins/eva/api/general';
+import {
+  callGraphFunction, SelectedNodes, ModeDisplay,
+  CGNode, CGLink, CGData,
+  CallGraphFunc, SelectedNodesData
+} from "frama-c/plugins/callgraph/definitions";
 
 import './callgraph.css';
+import * as Node from "./components/node";
+import { Panel } from './components/panel';
+import { CallgraphToolsBar } from "./components/toolbar";
+import { IThreeStateButton } from "./components/threeStateButton";
+import { CallgraphTitleBar } from "./components/titlebar";
 
+import * as CgAPI from './api';
 
 // --------------------------------------------------------------------------
-// --- Nodes label measurement
+// --- Graph functions
 // --------------------------------------------------------------------------
 
-/* eslint-disable @typescript-eslint/no-explicit-any */
-function getWidth(node: any): string {
-  const padding = 10;
-  const min = 50;
-  const canvas = document.querySelector('canvas[data-id="layer2-node"]');
-  if (canvas instanceof HTMLCanvasElement) {
-    const context = canvas.getContext('2d');
-    if (context) {
-      const fStyle = node.pstyle('font-style').strValue;
-      const weight = node.pstyle('font-weight').strValue;
-      const size = node.pstyle('font-size').pfValue;
-      const family = node.pstyle('font-family').strValue;
-      context.font = `${fStyle} ${weight} ${size}px ${family}`;
-      const width = context.measureText(node.data('id')).width;
-      return `${Math.max(min, width + padding)}px`;
+function convertGraph(
+  graph: CgAPI.graph | undefined,
+  functionStats: Eva.functionStatsData[],
+  properties: Properties.statusData[],
+  evaps: Eva.propertiesData[],
+): CGData
+{
+  const nodes: CGNode[] = [];
+  const links: CGLink[] = [];
+
+  const getScopeTaint = (id: Ast.decl): States.Tag[] => {
+    const taint: States.Tag[] = [];
+
+    properties.filter((elt) => elt.scope === id).forEach((elt) => {
+      const n = evaps.find((ps) => ps.key === elt.key);
+      taint.push(({ name: n?.taint || "not_computed" }));
+    });
+    return taint;
+  };
+
+  if (graph) {
+    for (const v of graph.vertices) {
+      const stats = functionStats.find((elt) => elt.key === v.decl);
+      const scopeTaint = getScopeTaint(v.decl);
+      const node: CGNode = {
+        id: v.decl,
+        label: v.name,
+        alarmCount: stats?.alarmCount,
+        alarmStatuses: stats?.alarmStatuses,
+        coverage: stats?.coverage,
+        taintStatus: scopeTaint
+      };
+      nodes.push(node);
+    }
+    for (const e of graph.edges) {
+      // Check if is recursive function
+      if (e.src === e.dst) {
+        nodes[nodes.findIndex((elt) => elt.id === e.src)].isRecursive = true;
+      } else {
+        const link: CGLink = { source: e.src, target: e.dst };
+        links.push(link);
+      }
     }
   }
-  return `${min}px`;
+  return { nodes, links };
 }
-/* eslint-enable @typescript-eslint/no-explicit-any */
 
-(style as unknown[]).push({
-    selector: 'node',
-    style: { width: getWidth }
-  });
+function filterGraph(graph?: CgAPI.graph, ids: string[] = []): CgAPI.graph {
+  if (!graph) return { vertices: [], edges: [] };
+  return {
+    vertices: graph.vertices.filter(elt => ids.includes(elt.decl)),
+    edges: graph.edges.filter(elt =>
+      Boolean(ids.includes(elt.src) && ids.includes(elt.dst)))
+  };
+}
 
+/* -------------------------------------------------------------------------- */
+/* --- Callgraph component                                                --- */
+/* -------------------------------------------------------------------------- */
 
-// --------------------------------------------------------------------------
-// --- Graph
-// --------------------------------------------------------------------------
+function Callgraph(): JSX.Element {
+  const isComputed = States.useSyncValue(CgAPI.isComputed);
+  if(isComputed === false) Server.send(CgAPI.compute, null);
 
-function edgeId(source: AstAPI.decl, target: AstAPI.decl): string {
-  return `${source}-${target}`;
-}
+  const graph = States.useSyncValue(CgAPI.callgraph);
+  const alarms = States.useSyncArrayData(Eva.functionStats);
 
-function convertGraph(graph: CgAPI.graph): object[] {
-  const elements = [];
-  for (const v of graph.vertices) {
-    elements.push({ data: { ...v, id: v.decl } });
-  }
-  for (const e of graph.edges) {
-    const id = edgeId(e.src, e.dst);
-    elements.push({ data: { ...e, id, source: e.src, target: e.dst } });
-  }
-  return elements;
-}
+  /** Function list and properties */
+  const ker = States.useSyncArrayProxy(Ast.functions);
+  const eva = States.useSyncArrayProxy(Eva.functions);
+  const functions = React.useMemo(() => computeFcts(ker, eva), [ker, eva]);
+  const properties = States.useSyncArrayData(Properties.status);
+  const evaps = States.useSyncArrayData(Eva.properties);
+  const functionFilter = useFunctionFilter();
 
-function selectNode(cy: Cy.Core, nodeId: States.Scope): void {
-  const className = 'marker-selected';
-  cy.$(`.${className}`).removeClass(className);
-  if (nodeId) {
-    cy.$(`node[id='${nodeId}']`).addClass(className);
-  }
-}
+  const {
+    contextMenuItems, multipleSelection, showFunction
+  } = functionFilter;
 
-function selectCallstack(cy: Cy.Core, callstack: ValuesAPI.callsite[]): void {
-  const className = 'callstack-selected';
-  cy.$(`.${className}`).removeClass(className);
-  callstack.forEach((call) => {
-    cy.$(`node[id='${call.callee}']`).addClass(className);
-    if (call.caller) {
-      const id = edgeId(call.caller, call.callee);
-      cy.$(`edge[id='${id}']`).addClass(className);
-    }
+  const filteredFunctions =  React.useMemo(() => {
+    const test = functions ? functions.filter(showFunction) : [];
+    return test;
+  }, [functions, showFunction]);
+
+  /** Current location */
+  const { scope } = States.useCurrentLocation();
+
+  /** Specific nodes*/
+  const selectedFunctions = React.useMemo<Set<string>>(() => {
+    return new Set(multipleSelection.map(elt => elt as string));
+  }, [multipleSelection]);
+
+  const taintedFunctions =  React.useMemo(() => {
+    const scope: string[] = [];
+    evaps.forEach((ps) => {
+      if(ps.taint === "direct_taint" || ps.taint === "indirect_taint") {
+        const prop = properties.find((elt) => elt.key === ps.key);
+        if(prop && prop.scope && !scope.includes(prop.scope))
+          scope.push(prop.scope);
+      }
+    });
+    return scope;
+  }, [properties, evaps]);
+
+  const unprovenPropertiesFunctions = React.useMemo<Set<string>>(() => {
+    const ids: SelectedNodesData = new Set();
+    alarms.forEach(elt => {
+      if (elt.alarmCount.length > 0) ids.add(elt.key);
+    });
+    return ids;
+  }, [alarms]);
+
+  /** Graph */
+  const filteredGraph = React.useMemo<CgAPI.graph>(() => {
+    return filterGraph(graph, filteredFunctions.map(elt => elt.decl));
+  }, [graph, filteredFunctions]);
+
+  const graphData = React.useMemo<CGData>(() => {
+    return convertGraph(filteredGraph, alarms, properties, evaps);
+  }, [filteredGraph, alarms, properties, evaps]);
+
+  const [selectedNodes, setSelectedNodes] = React.useState<SelectedNodes>({
+    tic: false,
+    set: new Set<string>()
   });
-}
 
-function Callgraph() : JSX.Element {
-  const isComputed = States.useSyncValue(CgAPI.isComputed);
-  const graph = States.useSyncValue(CgAPI.callgraph);
-  const [cy, setCy] = React.useState<Cy.Core>();
-  const [cs] = useGlobalState(CallstackState);
-  const callstack = States.useRequestValue(ValuesAPI.getCallstackInfo, cs);
-  const scope = States.useCurrentScope();
-  const layout = { name: 'cola', nodeSpacing: 32 };
-  const computedStyle = getComputedStyle(document.documentElement);
-  const styleVariables =
-    { ['code-select']: computedStyle.getPropertyValue("--code-select") };
-
-  const completeStyle = [
-    ...style,
-    {
-      "selector": ".marker-selected",
-      "style": { "background-color": styleVariables['code-select'] }
-    }
-  ];
+  /** Control */
+  /* eslint-disable max-len */
+  const decodeMode: Decoder<ModeDisplay> = (js: json) => {
+    if (js === 'all' || js === "linked" || js === "selected" ) return js;
+    else throw new JsonTypeError("ModeDisplay", js);
+  };
+  const [ displayMode, setDisplayMode ] = Dome.useWindowSettings<ModeDisplay>(
+    "ivette.callgraph.displaymode", decodeMode, "all"
+  );
+  const encodeButton: Encoder<IThreeStateButton> = (js: IThreeStateButton) => {
+    return JSON.stringify(js);
+  };
+  const decodeButton: Decoder<IThreeStateButton> = (js: json) => {
+    if (typeof js === 'string') return JSON.parse(js);
+    else throw new JsonTypeError("string", js);
+  };
+  const [ selectedParents, setSelectedParents ] =
+    useWindowSettingsData<IThreeStateButton>(
+      "ivette.callgraph.selectedparents",
+      decodeButton, encodeButton,
+      { active: true, max: true, value: 1 }
+  );
+  const [ selectedChildren, setSelectedChildren ] =
+    useWindowSettingsData<IThreeStateButton>(
+      "ivette.callgraph.selectedChildren",
+      decodeButton, encodeButton,
+      { active: true, max: true, value: 1 }
+  );
 
-  // Marker selection
-  React.useEffect(() => { cy && selectNode(cy, scope); }, [cy, scope]);
+  const panelVisibleState = Dome.useFlipSettings("ivette.callgraph.panelVisible", true);
+  const [ verticalSpacing, setVerticalSpacing ] = Dome.useNumberSettings("ivette.callgraph.verticalspacing", 75);
+  const [ horizontalSpacing, setHorizontalSpacing ] = Dome.useNumberSettings("ivette.callgraph.horizontalspacing", 500);
+  const [ autoCenter, flipAutoCenter ] = Dome.useFlipSettings("eva.callgraph.autocenter", true);
+  const [ autoSelect, flipAutoSelect ] = Dome.useFlipSettings('eva.callgraph.autoselect', false);
+  /* eslint-enable max-len */
 
-  // Callstack selection
-  React.useEffect(() => {
-    cy && selectCallstack(cy, callstack);
-  }, [cy, callstack]);
+  const style = Themes.useStyle();
+
+  const C = React.useMemo<CallGraphFunc>(() => {
+    return callGraphFunction(
+      [selectedNodes, setSelectedNodes],
+       graphData, displayMode, style,
+       selectedParents, selectedChildren
+    );
+  }, [ selectedNodes, setSelectedNodes, graphData, displayMode,
+     style, selectedChildren, selectedParents ]);
+
+  const getNode = React.useMemo(() => {
+    return (node: NodeObject3D<CGNode>) => {
+      return Node.getNode(node, selectedNodes, C.onNodeClickMultiSelect);
+    };
+  }, [selectedNodes, C.onNodeClickMultiSelect]);
 
-  // Click on graph
   React.useEffect(() => {
-    if (cy) {
-      cy.off('click');
-      cy.on('click', 'node', (event) => {
-        const { id } = event.target.data();
-        States.setCurrentScope(id);
+    if(autoSelect && scope)
+      setSelectedNodes((elt) => {
+        return { tic: !elt.tic, set: new Set([scope]) };
       });
+  }, [scope, autoSelect]);
+
+  React.useEffect(() => {
+    if(autoSelect && selectedFunctions.size > 0)
+      setSelectedNodes((elt) => {
+    return { tic: !elt.tic, set: selectedFunctions };
+  });
+  }, [selectedFunctions, autoSelect]);
+
+  const cycles = React.useRef<string[][]>([]);
+
+  const onDagError = (val: string[]): void => {
+    const isAlreadySave = (): boolean => {
+      for (const i in cycles.current) {
+        if (val.length === cycles.current[i].length) {
+          for( const j in cycles.current[i] ) {
+            if (cycles.current[i][j] !== val[j]) break;
+          }
+          return true;
+        }
+      }
+      return false;
+    };
+    if(!isAlreadySave()) {
+      cycles.current.push(val);
+      C.updateSelectedNodes(new Set(cycles.current.flat()));
     }
-  }, [cy]);
-
-  if (isComputed === false) {
-    Server.send(CgAPI.compute, null);
-    return (
-      <Icon
-        id={"SPINNER"}
-        className={"callgraph-computing"}
-        size={130}
+  };
+
+  const nodesOptions: INodesOptions = {
+    visibility: (node) => { return C.getNodeVisibility(node.id); },
+  };
+
+  const linkOptions: ILinksOptions = {
+    width: (link) => { return C.getLinkWidth(link); },
+    color: (link) => { return C.getLinkColor(link); },
+    visibility: (link) => { return C.getLinkVisibility(link); },
+    directionalArrow: 3,
+    directionalParticle: 3,
+    particleWidth: (link) => { return C.getLinkWidth(link); },
+    particleColor: (link) => { return C.getLinkColor(link); },
+  };
+
+  const options3D: IGraphOptions3D = {
+    backgroundColor: style.getPropertyValue('--background'),
+    autoCenter: autoCenter,
+    displayMode: 'td',
+    depthSpacing: verticalSpacing,
+    horizontalSpacing: horizontalSpacing,
+    onDagError,
+    htmlNode: getNode,
+    linkOptions,
+    nodesOptions,
+  };
+
+
+  return (
+    <>
+      <CallgraphTitleBar
+        contextMenuItems={contextMenuItems}
+        autoCenterState={[ autoCenter, flipAutoCenter ]}
+        autoSelectState={[ autoSelect, flipAutoSelect ]}
+      />
+      <CallgraphToolsBar
+        displayModeState={[ displayMode, setDisplayMode ]}
+        selectedParentsState={[ selectedParents, setSelectedParents ]}
+        selectedChildrenState={[ selectedChildren, setSelectedChildren ]}
+        panelVisibleState={panelVisibleState}
+        verticalSpacingState={[ verticalSpacing, setVerticalSpacing ]}
+        horizontalSpacingState={[ horizontalSpacing, setHorizontalSpacing ]}
+        selectedFunctions={selectedFunctions}
+        taintedFunctions={taintedFunctions}
+        unprovenPropertiesFunctions={unprovenPropertiesFunctions}
+        cycleFunctions={cycles.current.flat()}
+        dagMode={displayMode}
+        updateNodes={C.updateSelectedNodes}
       />
-    );
-  }
-  else if (graph !== undefined) {
-    return (
-      <CytoscapeComponent
-        elements={convertGraph(graph)}
-        stylesheet={completeStyle}
-        cy={setCy}
-        layout={layout}
-        style={{ width: '100%', height: '100%' }}
-      />);
-  }
-  else {
-    return (<></>);
-  }
-}
 
+      {!isComputed &&
+          <Icon
+            id={"SPINNER"}
+            className={"cg-graph-computing"}
+            size={130}
+          />
+      }
 
-// --------------------------------------------------------------------------
-// --- Ivette Component
-// --------------------------------------------------------------------------
+      {isComputed &&
+        <div className='cg-graph-container'>
+          <Graph
+            layout='3D'
+            nodes={graphData.nodes}
+            edges={graphData.links}
+            selected={undefined}
+            options3D={options3D}
+          />
+          <Panel
+            graphData={graphData}
+            selectedNodes={selectedNodes}
+            tainted={taintedFunctions.length}
+            properties={properties}
+            evaProperties={evaps}
+            style={style}
+            panelVisibleState={panelVisibleState}
+          />
+        </div>
+      }
+
+    </>
+  );
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- Register component                                                 --- */
+/* -------------------------------------------------------------------------- */
 
 Ivette.registerComponent({
   id: 'fc.callgraph',
@@ -191,3 +367,13 @@ Ivette.registerComponent({
     'Display a graph showing calls between functions.',
   children: <Callgraph />,
 });
+
+Ivette.registerView({
+  id: 'fc.callgraph',
+  label: 'Callgraph',
+  layout: {
+    ABCD: 'fc.callgraph',
+  }
+});
+
+// --------------------------------------------------------------------------