diff --git a/__pycache__/utils_graph.cpython-310.pyc b/__pycache__/utils_graph.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f990fcc15c9303ee8ee797fb4d641c9cae66269d
Binary files /dev/null and b/__pycache__/utils_graph.cpython-310.pyc differ
diff --git a/__pycache__/utils_graph.cpython-312.pyc b/__pycache__/utils_graph.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..764d9f4d7ce68a27ea9b5961014d09a63ac6f7d0
Binary files /dev/null and b/__pycache__/utils_graph.cpython-312.pyc differ
diff --git a/app.py b/app.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b4edb57a2524540673bfb4ec6f7bedf7355e67b
--- /dev/null
+++ b/app.py
@@ -0,0 +1,142 @@
+import pandas as pd
+import numpy as np
+import streamlit as st
+from matplotlib.colors import LinearSegmentedColormap, ListedColormap, to_hex
+import matplotlib.patches as mpatches
+from io import BytesIO
+
+import utils_graph
+from utils_graph import plot_graph
+
+st.title('Logiciel cartographique')
+
+upload_file = st.file_uploader('Charger vos données en format .csv ou .xlsx.')
+
+if upload_file is not None : 
+
+    # chargement des données
+    filename = upload_file.name 
+
+    # vérifie la forme des données
+    if filename.endswith('.csv'):
+        df = pd.read_csv(upload_file)
+        st.success("Fichier CSV chargé avec succès.")
+
+    elif filename.endswith('.xlsx') or filename.endswith('.xls'):
+        df = pd.read_excel(upload_file)
+        st.success("Fichier Excel chargé avec succès.")
+
+    else:
+        st.error("Format de fichier non supporté. Veuillez charger un .csv ou un .xlsx.")
+
+    # visualisation des données
+    st.header('Visualisation des données :')
+    st.write(df.head(5)) # pour 5 lignes
+
+    # bloc sur les infos du dataframe 
+    st.subheader('Information sur les données :')
+    with st.expander("📊 Aperçu du DataFrame"):
+        st.markdown(f"**Dimensions :** {df.shape[0]} lignes × {df.shape[1]} colonnes")
+        st.markdown("**Colonnes disponibles :**")
+        st.write(list(df.columns))
+        st.markdown("**Types de données :**")
+        st.write(df.dtypes)
+
+    st.subheader('Choix d\'option  :')
+    if df.shape[0]>12 : 
+        nb_line = st.number_input(
+            "Nombre de lignes à afficher",
+            min_value=1,
+            max_value=len(df),
+            value=5,
+            step=1,
+            format="%d"  # ← force l'affichage et le retour d'un entier
+        )
+
+    # choix des colonnes à tracer
+    columns = df.columns.tolist()
+
+    col_x = st.selectbox('Choisir la colonne des valeurs (x)', columns)
+    col_y = st.selectbox('Choisir la colonne des catégories (y)', columns)
+    
+    title_choice = st.checkbox("Voulez-vous mettre un titre au graphique ?")
+    if title_choice:
+        title = st.text_input("Entrez le titre du graphique :", key="title_input")
+    else:
+        title = None
+
+    x_choice = st.checkbox("Voulez-vous nommer l'axe des abscisses ?")
+    if x_choice:
+        xlabel = st.text_input("Entrez le nom de l'axe des abscisses :", key="xlabel_input")
+    else:
+        xlabel = None
+
+    y_choice = st.checkbox("Voulez-vous nommer l'axe des ordonnées ?")
+    if y_choice:
+        ylabel = st.text_input("Entrez le nom de l'axe des ordonnées :", key="ylabel_input")
+    else:
+        ylabel = None
+
+    model_graph = utils_graph.plot_graph(df = df.loc[:nb_line-1, :], x = col_x, y = col_y)
+    legend_choice = st.checkbox('Voulez-vous mettre une légende de couleurs ?')
+
+    if legend_choice:
+        col_color = st.selectbox('Choisir la colonne des numéros de couleurs', columns)
+
+        try : 
+            # Premier tracé pour générer list_colors
+            model_graph.barh_subplot(colors=col_color, show=False, title= title, xlabel= xlabel, ylabel=ylabel)
+            colors_rgba = model_graph.list_colors
+
+            custom_colors = {}
+            legend_labels = []
+            legend_indices = []
+
+            st.markdown("Personnalisation des couleurs et des légendes :")
+
+            for i, rgba in enumerate(colors_rgba):
+                hex_color = to_hex(rgba)
+                col1, col2 = st.columns(2)
+                with col1:
+                    picked_color = st.color_picker(f"Couleur {i+1}", hex_color)
+                with col2:
+                    label = st.text_input(f"Légende {i+1}", key=f"legend_{i}")
+
+                if label.strip():  # légende non vide
+                    legend_labels.append(label)
+                    legend_indices.append(i)
+                    custom_colors[i] = {"color": picked_color, "label": label}
+
+            # Retracer avec légendes non vides
+            barh = model_graph.barh_subplot(
+                colors=col_color,
+                legend_list=legend_labels,
+                title_legend="Légende",
+                legend_indices=legend_indices, 
+                title= title, 
+                xlabel= xlabel,
+                ylabel= ylabel
+            )
+
+            st.pyplot(barh)
+        
+        except Exception as e:
+            st.warning("⚠️ Cette colonne ne semble pas contenir des numéros de couleurs valides.")
+
+    else:
+        # Tracer sans légende
+        barh = model_graph.barh_subplot(title= title, xlabel= xlabel, ylabel= ylabel)
+        st.pyplot(barh)
+
+    # Sauvegarde du graphique dans un buffer en mémoire
+    img_buffer = BytesIO()
+    barh.savefig(img_buffer, format='png', dpi=300, bbox_inches='tight')
+    img_buffer.seek(0)  # Revenir au début du buffer
+
+    # Bouton de téléchargement
+    st.download_button(
+        label="📥 Télécharger le graphique",
+        data=img_buffer,
+        file_name="mon_graphique.png",
+        mime="image/png"
+    )
diff --git a/data/df_cfa_anonyme.xlsx b/data/df_cfa_anonyme.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..d5830d42fb2020969e45dffd3d0208ea1e94e6b0
Binary files /dev/null and b/data/df_cfa_anonyme.xlsx differ
diff --git a/data/df_rncp.xlsx b/data/df_rncp.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..ffa62d4548e54bfc782c95049369b292b9cf1afe
Binary files /dev/null and b/data/df_rncp.xlsx differ
diff --git a/utils_graph.py b/utils_graph.py
new file mode 100644
index 0000000000000000000000000000000000000000..c97694ee34db567bb906553580c87d76568288e0
--- /dev/null
+++ b/utils_graph.py
@@ -0,0 +1,143 @@
+import numpy as np 
+import matplotlib.pyplot as plt 
+import pandas as pd
+from matplotlib.colors import LinearSegmentedColormap, ListedColormap
+import matplotlib.patches as mpatches
+
+# partie sur les légendes à revoir
+
+c_map = ['#000091', '#AB0345']
+cmap = LinearSegmentedColormap.from_list("custom_cmap", c_map)
+
+class plot_graph() : 
+    def __init__(self,df, x, y, figsize = (10,6), ax = None, fig = None):
+        self.df = df
+        self.x = self.df[x]
+        self.y = self.df[y]
+        self.figsize = figsize
+        self.ax = ax
+        self.fig = fig
+        
+    def barh_subplot(self, title = None, xlabel = None,  ylabel = None,
+                                path = None, yticklabels = None, xticklabels = None, colors = None,
+                                legend_list = None, title_legend = None, show = True, force_new_fig= True, 
+                                legend_indices=None):
+
+        # couleurs des barres 
+        if colors in self.df.columns and not self.df[colors].empty:
+            valid_colors = [c for c in self.df[colors] if not pd.isna(c)]
+            n_color = max(valid_colors) + 1
+            self.list_colors = cmap(np.linspace(0,1,n_color))
+            colors_bars = ['lightgray' if pd.isna(n) else self.list_colors[n] for n in self.df[colors]]
+
+            hatches = ['//' if pd.isna(n) else None for n in self.df[colors]]
+
+        elif colors is None :
+            self.list_colors = None
+            colors_bars = colors
+            hatches = None
+
+        # Tracer le graphique
+        if self.ax is None or self.fig is None or force_new_fig:
+            self.fig, self.ax = plt.subplots(figsize=self.figsize)
+        
+        bars = self.ax.barh(self.y, self.x, color = colors_bars, hatch = hatches)
+
+        if legend_list and colors is not None:
+            legend_elements = []
+
+            # Toujours ajouter la légende pour les valeurs manquantes (None)
+            if any(pd.isna(c) or c is None for c in self.df[colors]):
+                legend_elements.append(mpatches.Patch(facecolor='lightgray', hatch='//', label='Indisponible'))
+
+            if legend_indices is None:
+                legend_indices = list(range(len(legend_list)))  # toutes les légendes par défaut
+
+            for idx in legend_indices:
+                if idx < len(self.list_colors) and idx < len(legend_list):
+                    legend_elements.append(mpatches.Patch(facecolor=self.list_colors[idx], label=legend_list[idx]))
+
+            self.ax.legend(handles=legend_elements, loc='best', title=title_legend)
+
+        if xticklabels is False :
+            self.ax.set_xticklabels([])
+        elif xticklabels is not None : 
+            self.ax.set_xticklabels(xticklabels)
+
+        if yticklabels is False :
+            self.ax.set_yticklabels([])
+        elif yticklabels is not None : 
+            self.ax.set_yticklabels(yticklabels)
+
+        self.ax.set_xlabel(xlabel)
+        self.ax.set_ylabel(ylabel)
+        self.ax.set_title(title, pad=30, color='black', fontsize=16)
+
+        self.ax.grid(axis='x', linestyle='-', alpha=0.2)
+
+        plt.tight_layout()
+
+        if show :
+            plt.show()
+            
+        if path:
+            plt.savefig(path, dpi=500, bbox_inches='tight')
+
+        return self.fig
+
+    # partie à revoir selon les besoins des missions
+    def annotation(self) : 
+
+        """        # Ajouter des annotations sur les barres
+        if pourcentage_list == False : 
+            for n, bar in enumerate(bars):
+                width = bar.get_width()
+                if np.isnan(width):
+                    # Gérer le cas où la largeur est NaN
+                    text = "N/A"
+                elif nombre and slash : 
+                    text = f"{round(width)}/{self.df.loc[n,slash]}"
+                elif nombre and slash == False : 
+                    text = f"{round(width)}"
+                else:
+                    if pourcentage and self.df.shape[0]<10:
+                        text = f"{round((width / self.df['count'].sum()) * 100, 1)}% ({int(width)})"
+                    elif pourcentage and self.df.shape[0]>=10:
+                        text = f"{round((width / total) * 100, 1)}% ({int(width)})"
+                    else :
+                        text = f"{round(width)}%"
+
+                if width == 0 :
+                    ax.text(2 if not np.isnan(width) else 0 , bar.get_y() + bar.get_height() / 2,
+                        text,va='center', ha='left', color='gray', fontsize=9)
+                else : 
+                    ax.text(width * 1.01 if not np.isnan(width) else 0 , bar.get_y() + bar.get_height() / 2,
+                            text, va='center', ha='left', color='gray', fontsize=9)
+
+        elif pourcentage_list != False:
+            for n,bar in enumerate(bars):
+                width = bar.get_width()
+                if np.isnan(width):
+                    # Gérer le cas où la largeur est NaN
+                    text = "N/A"
+                elif inverse : 
+                    text = f"{width}% ({self.df.loc[n,pourcentage_list]})"
+
+                else : 
+                    text = f"{self.df.loc[n,pourcentage_list]}% ({width})"
+                if width == 0 :
+                    ax.text(2 if not np.isnan(width) else 0 , bar.get_y() + bar.get_height() / 2,
+                        text,va='center', ha='left', color='gray', fontsize=9)
+                else : 
+                    ax.text(width * 1.01 if not np.isnan(width) else 0 , bar.get_y() + bar.get_height() / 2,
+                            text, va='center', ha='left', color='gray', fontsize=9)"""
+        
+        pass
+
+    def encadrer(self) : 
+
+        """iscod_index = self.df[self.df['denomination_cfa'] == 'ISCOD'].index.values[0]
+        bars[iscod_index].set_edgecolor('black')  # Couleur de la bordure
+        bars[iscod_index].set_linewidth(2)        # Épaisseur de la bordure """
+
+        pass
\ No newline at end of file