diff --git a/aidge_core/mem_info.py b/aidge_core/mem_info.py
index c7ca85bbd73bd205850b19616e53fda210749a80..87f286bcbcde3bc656e7997e65ec0078437e72e9 100644
--- a/aidge_core/mem_info.py
+++ b/aidge_core/mem_info.py
@@ -5,6 +5,9 @@ from pathlib import Path
 import aidge_core
 from typing import Tuple, List
 
+import matplotlib.pyplot as plt
+import aidge_core.mem_info
+import numpy as np
 
 # Default memory management, which can be used for development
 def compute_default_mem_info(scheduler: aidge_core.Scheduler) -> Tuple[int, List]:
@@ -41,20 +44,72 @@ def compute_default_mem_info(scheduler: aidge_core.Scheduler) -> Tuple[int, List
             mem_info[node] = [] # No meminfo for producer
     return mem_size, mem_info
 
+def log_meminfo(mem_manager:aidge_core.MemoryManager, path: Path, diplay_names:bool):
+    """Generate a graph representing the memory allocation of each ouputs.
 
-def _gnuplot_installed():
-    try:
-        # Run gnuplot with the --version flag and capture the output
-        subprocess.run(["gnuplot", "--version"])
-        return True
-    except FileNotFoundError:
-        aidge_core.Log.warn("Gnuplot is not installed.")
-        return False
-    except subprocess.CalledProcessError:
-        aidge_core.Log.warn("Gnuplot command found but failed to run.")
-        return False
+    Block with the smae color correspond to the same memory plane.
 
-def generate_optimized_memory_info(scheduler: aidge_core.Scheduler, stats_folder: Path = None, wrapping: bool = False) -> Tuple[int, List[dict]]:
+    :param mem_manager: Memory manager to log
+    :type mem_manager: aidge_core.memory_manager
+    :param path: Path where to save the figure
+    :type path: Path
+    :param diplay_names: If True Node names are diplayed alongside their block
+    :type diplay_names: bool
+    """
+
+    max_lifetime = mem_manager.get_max_lifetime()
+
+    # peak_usage in kwords
+    peak_usage = mem_manager.get_peak_usage() / 1024
+
+    # Set figure size 1920x1080 px
+    plt.figure(figsize=(19.20, 10.80))
+    # Same color for each planes
+    colors = plt.cm.viridis(np.linspace(0, 1, len(mem_manager.get_planes()) + 1))
+    color_id = 1
+    for node, planes in mem_manager.get_planes().items():
+        for plane in planes:
+            cont_offset    = plane.get_contiguous_offset()
+            cont_size      = plane.get_contiguous_size()
+            allocated      = plane.mem_space.allocated
+            released       = plane.mem_space.released
+            is_released    = released >= 0 and not plane.mem_space.dependencies
+            x_start        = allocated
+            y_start        = cont_offset / 1024.0
+            y_end          = (cont_offset + cont_size) / 1024.0
+            x_end          = max_lifetime if not is_released else released
+
+            plt.fill_betweenx(
+                [y_start, y_end],
+                x_start,
+                x_end + 1,
+                color=colors[color_id % len(colors)]
+            )
+
+            if diplay_names:
+                # Rotation for lisibility!
+                plt.text(x_end,y_end, node.name(), rotation=45)
+        color_id += 1
+
+    plt.xlim(0, max_lifetime + 1)
+    plt.ylim(0, peak_usage)
+    plt.axhline(y=peak_usage, color='red', linestyle='--')
+    plt.text(0, peak_usage, f'Peak usage = {peak_usage} KWords', color='red')
+    plt.xlabel("Time")
+    plt.ylabel("Memory usage (KWords)")
+    plt.title("Memory Usage Over Time")
+    plt.grid(True)
+    ax = plt.gca()
+    ax.spines['top'].set_visible(False)
+    ax.spines['right'].set_visible(False)
+    folder_path = path.parent
+    folder_path.mkdir(parents=True, exist_ok=True)
+    plt.savefig(path)
+    plt.close()
+    aidge_core.Log.notice(f"Generated memory management info at: {path}")
+
+
+def generate_optimized_memory_info(scheduler: aidge_core.Scheduler, stats_folder: Path = None, wrapping: bool = False, display_names: bool=True) -> Tuple[int, List[dict]]:
     """Generates optimized memory information for a computation graph managed by a scheduler.
 
     This function analyzes the memory usage of a computation graph, determining the memory peak
@@ -70,6 +125,8 @@ def generate_optimized_memory_info(scheduler: aidge_core.Scheduler, stats_folder
     :param wrapping: Boolean flag to enable or disable wrap-around buffer optimization.
                      Defaults to `False`.
     :type wrapping: bool, optional
+    :param diplay_names: If True Node names are diplayed in the memory plot alongside their block, defaults to False
+    :type diplay_names: bool, optional
     :return: A tuple containing the peak memory size and a list of memory information for each
              scheduled node. The memory information for each node includes details such as size,
              offset, stride, length, count, and optional wrap-around details.
@@ -88,18 +145,8 @@ def generate_optimized_memory_info(scheduler: aidge_core.Scheduler, stats_folder
     nodes_at_input = [n[0] for n in scheduler.graph_view().inputs()]
 
     if stats_folder is not None:
-        if _gnuplot_installed():
-            # Use gnuplot to generate the log
-            os.makedirs(str(Path(stats_folder) / "graph"), exist_ok=True)
-            mem_manager.log("memory_info")
-            os.chmod("memory_info_plot.gnu", 0o777)
-            os.system("./memory_info_plot.gnu")
-            shutil.move("memory_info", str(Path(stats_folder) / "graph" / "memory_info"))
-            shutil.move("memory_info_plot.png", str(
-                Path(stats_folder) / "graph" / "memory_info_plot.png"))
-            os.remove("memory_info_plot.gnu")
-        else:
-            aidge_core.Log.warn("Warning: gnuplot is not installed, could not generate stat folder.")
+        log_meminfo(mem_manager, Path(stats_folder) / "memory_info.png", display_names)
+
     # In the export, we currently use an unified memory buffer whose size
     # is determined by the memory peak usage
     mem_size = mem_manager.get_peak_usage()
diff --git a/pyproject.toml b/pyproject.toml
index c5c8a0600dda804ce13dfb8d4c6874ae967e87e6..610b5f8c226fcf2f040a6d6c22cffcb0498a0f8d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,8 @@ name="aidge_core"
 description="Core algorithms for operators and graph of the AIDGE framework"
 dependencies = [
     "numpy>=1.21.6",
-    "Jinja2>=3.1.2"
+    "Jinja2>=3.1.2",
+    "matplotlib"
 ]
 requires-python = ">= 3.7"
 readme = "README.md"
diff --git a/python_binding/scheduler/pybind_MemoryManager.cpp b/python_binding/scheduler/pybind_MemoryManager.cpp
index 0f18db405bec0aee9637f2e5f2ecc7b71e502cc5..3fce92349f28e3c6a897356dee60359c1797d9ca 100644
--- a/python_binding/scheduler/pybind_MemoryManager.cpp
+++ b/python_binding/scheduler/pybind_MemoryManager.cpp
@@ -36,10 +36,10 @@ void init_MemoryManager(py::module& m)
         .def_readwrite("released", &MemoryManager::MemorySpace::released);
 
     py::class_<MemoryManager::MemoryPlane, std::shared_ptr<MemoryManager::MemoryPlane>>(m, "MemoryPlane")
-        .def(py::init<std::shared_ptr<MemoryManager::MemorySpace>, 
+        .def(py::init<std::shared_ptr<MemoryManager::MemorySpace>,
                       MemoryManager::Clock_T, unsigned int, unsigned int,
                       unsigned int, unsigned int, unsigned int>(),
-                      py::arg("mem_space"), py::arg("clock"), py::arg("offset"), 
+                      py::arg("mem_space"), py::arg("clock"), py::arg("offset"),
                       py::arg("size"), py::arg("stride"), py::arg("length"), py::arg("count"))
         .def_readwrite("mem_space", &MemoryManager::MemoryPlane::memSpace)
         .def_readwrite("allocated", &MemoryManager::MemoryPlane::allocated)
@@ -101,7 +101,6 @@ void init_MemoryManager(py::module& m)
         .def("get_nb_planes", (unsigned int (MemoryManager::*)(std::shared_ptr<MemoryManager::MemorySpace>) const) &MemoryManager::getNbPlanes, py::arg("mem_space"))
         .def("get_current_tick", &MemoryManager::getCurrentTick)
         .def("tick", &MemoryManager::tick)
-        .def("log", &MemoryManager::log, py::arg("file_name"))
         ;
 }
 
diff --git a/src/scheduler/MemoryManager.cpp b/src/scheduler/MemoryManager.cpp
index ba805f919a607e0b2ae3272d173aa11360548fa7..05f461b82f16b6af4ed412b7336aa2328bcafbe1 100644
--- a/src/scheduler/MemoryManager.cpp
+++ b/src/scheduler/MemoryManager.cpp
@@ -634,152 +634,6 @@ void Aidge::MemoryManager::tick()
     ++mClock;
 }
 
-void Aidge::MemoryManager::log(const std::string& fileName) const
-{
-    auto memData = std::unique_ptr<FILE, decltype(&std::fclose)>(std::fopen(fileName.c_str(), "w"), &std::fclose);
-
-    if (!memData) {
-        AIDGE_THROW_OR_ABORT(std::runtime_error,
-            "Could not create memory layout log file: {}", fileName);
-    }
-
-    auto gnuplot = std::unique_ptr<FILE, decltype(&std::fclose)>(std::fopen((fileName + "_plot.gnu").c_str(), "w"), &std::fclose);
-
-    if (!gnuplot) {
-        AIDGE_THROW_OR_ABORT(std::runtime_error,
-            "Could not create memory layout log file: {}", (fileName + "_plot.gnu"));
-    }
-
-    const Clock_T maxLifetime = getMaxLifetime();
-    const unsigned int peakUsage = getPeakUsage();
-
-    fmt::print(gnuplot.get(), "#!/usr/bin/gnuplot\n");
-    fmt::print(gnuplot.get(), "set term pngcairo size 1280,768 noenhanced\n");
-    fmt::print(gnuplot.get(), "set output \"{}\"\n", fileName + "_plot.png");
-    fmt::print(gnuplot.get(), "set xrange [{}:{}]\n", 0, maxLifetime + 1);
-    fmt::print(gnuplot.get(), "set yrange [{}:{}]\n", 0, 1.05 * (peakUsage / 1024.0));
-    fmt::print(gnuplot.get(), "set xlabel \"Time\"\n");
-    fmt::print(gnuplot.get(), "set ylabel \"Memory usage (KWords)\"\n");
-    fmt::print(gnuplot.get(), "set grid\n");
-    fmt::print(gnuplot.get(), "set xtics 1\n");
-    fmt::print(gnuplot.get(), "unset key\n");
-    fmt::print(gnuplot.get(), "set palette rgbformulae 30,31,32\n");
-    fmt::print(gnuplot.get(), "unset colorbox\n");
-    fmt::print(gnuplot.get(), "N={}\n", mMemPlanes.size() + 1);
-
-    unsigned int objectId = 1;
-    unsigned int labelId = 1;
-
-    for (std::map<std::shared_ptr<Node>, std::vector<MemoryPlane> >
-        ::const_iterator it = mMemPlanes.begin(), itEnd = mMemPlanes.end();
-        it != itEnd; ++it)
-    {
-        const std::string name = (*it).first->name();
-        fmt::print(memData.get(), "{}\n", name);
-
-        double minX = -1;
-        unsigned int maxY = 0;
-
-        for (std::vector<MemoryPlane>::const_iterator itPlanes
-             = (*it).second.begin(), itPlanesBegin = (*it).second.begin(),
-            itPlanesEnd = (*it).second.end(); itPlanes != itPlanesEnd;
-            ++itPlanes)
-        {
-            const unsigned int contiguousOffset
-                = (*itPlanes).getContiguousOffset();
-            const unsigned int contiguousSize = (*itPlanes).getContiguousSize();
-            const unsigned int wrappedOffset = (*itPlanes).getWrappedOffset();
-            const unsigned int wrappedSize = (*itPlanes).getWrappedSize();
-
-            const Clock_T allocated = (*itPlanes).allocated;
-            const Clock_T released = (*itPlanes).memSpace->released;
-            const bool isReleased = (released >= 0
-                                && (*itPlanes).memSpace->dependencies.empty());
-
-            fmt::print(memData.get(), "  {} {} ({:#08x}U) -> {} ({:#08x}U)",
-                (itPlanes - itPlanesBegin), contiguousOffset, contiguousOffset,
-                (contiguousOffset + contiguousSize), (contiguousOffset + contiguousSize));
-
-            if (wrappedSize > 0) {
-                fmt::print(memData.get(), " + {} ({:#08x}U) -> {} ({:#08x}U)",
-                    wrappedOffset, wrappedOffset,
-                    (wrappedOffset + wrappedSize), (wrappedOffset + wrappedSize));
-            }
-
-            fmt::print(memData.get(), " [{}] @ {}", (*itPlanes).getSize(), allocated);
-
-            if (isReleased) {
-                fmt::print(memData.get(), " to {}", released);
-            }
-
-            fmt::print(memData.get(), "\n");
-
-            // Gnuplot
-            const double startX = allocated;
-
-            if (startX < minX || minX < 0) {
-                minX = startX;
-                maxY = contiguousOffset + contiguousSize;
-            }
-
-            if ((*itPlanes).size != (*itPlanes).stride) {
-                for (unsigned int offset = contiguousOffset;
-                    offset < contiguousOffset + contiguousSize;
-                    offset += (*itPlanes).stride)
-                {
-                    fmt::print(gnuplot.get(), "set object {} rectangle from {},{} to {},{} fc palette frac ({} * 1./N)\n",
-                        (allocated * 100 + objectId), startX, (offset / 1024.0),
-                        (((isReleased) ? released : maxLifetime) + 1),
-                        (std::min((offset + (*itPlanes).size),
-                                        contiguousOffset + contiguousSize) / 1024.0),
-                        labelId);
-                    ++objectId;
-                }
-            }
-            else {
-                fmt::print(gnuplot.get(), "set object {} rectangle from {},{} to {},{} fc palette frac ({} * 1./N)\n",
-                    (allocated * 100 + objectId), startX, (contiguousOffset / 1024.0),
-                    (((isReleased) ? released : maxLifetime) + 1),
-                    ((contiguousOffset + contiguousSize) / 1024.0),
-                    labelId);
-                ++objectId;
-            }
-
-            if (wrappedSize > 0) {
-                fmt::print(gnuplot.get(), "set object {} rectangle from {},{} to {},{} fc palette frac ({} * 1./N)\n",
-                    (allocated * 100 + objectId), startX, (wrappedOffset / 1024.0),
-                    (((isReleased) ? released : maxLifetime) + 1),
-                    ((wrappedOffset + contiguousSize) / 1024.0),
-                    labelId);
-                ++objectId;
-
-                fmt::print(gnuplot.get(), "set arrow from {},{} to {},{} nohead\n",
-                    startX, (contiguousOffset / 1024.0),
-                    (startX + 0.1), (contiguousOffset / 1024.0));
-
-                fmt::print(gnuplot.get(), "set arrow from {},{} to {},{} nohead\n",
-                    (startX + 0.05), ((contiguousOffset + contiguousSize) / 1024.0),
-                    (startX + 0.05), (wrappedOffset / 1024.0));
-            }
-        }
-
-        fmt::print(gnuplot.get(), "set label {} '{}' at {},{} rotate by 30 font \",8\" offset char 0.5,0.5\n",
-            labelId, name, minX, (maxY / 1024.0));
-        ++labelId;
-
-        fmt::print(memData.get(), "\n");
-    }
-
-    fmt::print(gnuplot.get(), "set arrow from 0,{} to {},{} nohead lc rgb \"red\"\n",
-        (peakUsage / 1024.0), (maxLifetime + 1),
-        (peakUsage / 1024.0));
-
-    fmt::print(gnuplot.get(), "set label {} 'Peak usage = {} KWords' at 0,{} textcolor rgb \"red\" offset char 0.5,0.5\n",
-        labelId, (peakUsage / 1024.0), (peakUsage / 1024.0));
-
-    fmt::print(gnuplot.get(), "plot 0\n");
-}
-
 unsigned int Aidge::MemoryManager::onStack(unsigned int size)
 {
     unsigned int offset = 0;
diff --git a/unit_tests/scheduler/Test_MemoryManager.cpp b/unit_tests/scheduler/Test_MemoryManager.cpp
index a4941203644b7ba291682f3932926a36fa83b745..b6cedfac47d53e0f8ab464fad8a2f6cc6c8dcc15 100644
--- a/unit_tests/scheduler/Test_MemoryManager.cpp
+++ b/unit_tests/scheduler/Test_MemoryManager.cpp
@@ -136,7 +136,6 @@ TEST_CASE("allocate1", "[MemoryManager]") {
     REQUIRE(memManager.getPlanes(node4).back().memSpace->allocated == 3);
     REQUIRE(memManager.getPlanes(node4).back().memSpace->released == 4);
 
-    memManager.log("MemoryManager_allocate1.log");
 }
 
 TEST_CASE("allocate2", "[MemoryManager]") {
@@ -281,7 +280,6 @@ TEST_CASE("allocate2", "[MemoryManager]") {
     REQUIRE(memManager.getPlanes(node4).back().memSpace->allocated == 3);
     REQUIRE(memManager.getPlanes(node4).back().memSpace->released == 4);
 
-    memManager.log("MemoryManager_allocate2.log");
 }
 
 TEST_CASE("allocate3", "[MemoryManager]") {
@@ -438,7 +436,6 @@ TEST_CASE("allocate3", "[MemoryManager]") {
     REQUIRE(memManager.getPlanes(node4).back().memSpace->allocated == 0);
     REQUIRE(memManager.getPlanes(node4).back().memSpace->released == 4);
 
-    memManager.log("MemoryManager_allocate3.log");
 }
 
 TEST_CASE("allocate3_wrapAround", "[MemoryManager]") {
@@ -595,5 +592,4 @@ TEST_CASE("allocate3_wrapAround", "[MemoryManager]") {
     REQUIRE(memManager.getPlanes(node4).back().memSpace->allocated == 0);
     REQUIRE(memManager.getPlanes(node4).back().memSpace->released == 4);
 
-    memManager.log("MemoryManager_allocate3_wrapAround.log");
 }