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