diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 41bff133d431306b8bce693a2f12cec5b8bb6459..73c869b682e941d2d2a4d3c2eaed0b4e19d47747 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -97,8 +97,8 @@ benchmark:
   artifacts:
     when: always
     paths:
-      - ./*.log
       - ./*.csv
+      - ./*.log
       - ./scripts/results/
 
 database:
@@ -113,8 +113,9 @@ database:
   artifacts:
     when: always
     paths:
-      - ./*.sqlite3
       - ./*.csv
+      - ./*.png
+      - ./*.sqlite3
       - ./scripts/results/
 
 pages:
diff --git a/scripts/database.sh b/scripts/database.sh
index f2e95b87a78472475c405734c7779d707b15b2e5..53d5741efeeec01830e93abc7dae75780a17afd3 100755
--- a/scripts/database.sh
+++ b/scripts/database.sh
@@ -36,3 +36,8 @@ fi
 python3 ./scripts/add_result.py -e https://elasticsearch.bordeaux.inria.fr -t concace -p scalfmm -n accuracy scalfmm_accuracy.csv
 python3 ./scripts/add_result.py -e https://elasticsearch.bordeaux.inria.fr -t concace -p scalfmm -n timeseq scalfmm_timeseq.csv
 python3 ./scripts/add_result.py -e https://elasticsearch.bordeaux.inria.fr -t concace -p scalfmm -n timeomp scalfmm_timeomp.csv
+
+# plot some figures at this precise commit
+python3 ./scripts/plot.py -f scalfmm_accuracy.csv -n accuracy
+python3 ./scripts/plot.py -f scalfmm_timeseq.csv -n timeseq
+python3 ./scripts/plot.py -f scalfmm_timeomp.csv -n timeomp
diff --git a/scripts/plot.py b/scripts/plot.py
new file mode 100644
index 0000000000000000000000000000000000000000..d56a62c9a21092e823958e1114345efd2ddec329
--- /dev/null
+++ b/scripts/plot.py
@@ -0,0 +1,52 @@
+import click
+import pandas as pd
+import matplotlib.pyplot as plt
+
+@click.command()
+@click.option("-f", "--file", required=True, help="input csv file")
+@click.option("-n", "--name", required=True, help="benchmark name")
+def main(
+    file: str,
+    name: str,
+):
+    """Generate figure from a csv file"""
+
+    df = pd.read_csv(file)
+
+    if name == "accuracy":
+        for (ndim, kernel_type, interp_type, tree_height), group in df.groupby(['ndim', 'kernel_type', 'interp_type', 'tree_height']):
+            x = group['interp_order']
+            y = group['error']
+            plt.plot(x, y, label=f"{ndim}-{kernel_type}-{interp_type}-{tree_height}")
+            plt.yscale('log')
+
+        plt.xlabel('Order')
+        plt.ylabel('Relative norm-2 error')
+        plt.title('Error vs. Order')
+        plt.legend()
+        plt.savefig('scalfmm_accuracy.png', bbox_inches='tight')
+    elif name == "timeseq":
+        for (ndim, kernel_type, interp_type, tree_height, interp_order), group in df.groupby(['ndim', 'kernel_type', 'interp_type', 'tree_height', 'interp_order']):
+            x = group['size']
+            y = group['timefull_avg']
+            plt.plot(x, y, label=f"{ndim}-{kernel_type}-{interp_type}-{tree_height}-{interp_order}")
+
+        plt.xlabel('Size')
+        plt.ylabel('Time (s)')
+        plt.title('Time vs. Size')
+        plt.legend()
+        plt.savefig('scalfmm_timeseq.png', bbox_inches='tight')
+    elif name == "timeomp":
+        for (ndim, kernel_type, interp_type, tree_height, interp_order, size, groupsize), group in df.groupby(['ndim', 'kernel_type', 'interp_type', 'tree_height', 'interp_order', 'size', 'groupsize']):
+            x = group['nthread']
+            y = group['timefull_avg']
+            plt.plot(x, y, label=f"{ndim}-{kernel_type}-{interp_type}-{tree_height}-{interp_order}-{size}-{groupsize}")
+
+        plt.xlabel('Number of threads')
+        plt.ylabel('Time (s)')
+        plt.title('Time vs. Number of threads')
+        plt.legend()
+        plt.savefig('scalfmm_timeomp.png', bbox_inches='tight')
+
+if __name__ == "__main__":
+    main()
\ No newline at end of file