From e6d79d3a5b38be44eb162530e77164161eb89869 Mon Sep 17 00:00:00 2001
From: Marilyne HU <marilyne.hu@student-cs.fr>
Date: Mon, 31 Mar 2025 04:08:42 +0200
Subject: [PATCH] Mon commit

---
 __pycache__/utils_graph.cpython-310.pyc | Bin 0 -> 5600 bytes
 __pycache__/utils_graph.cpython-312.pyc | Bin 0 -> 7735 bytes
 app.py                                  | 142 +++++++++++++++++++++++
 data/df_cfa_anonyme.xlsx                | Bin 0 -> 7123 bytes
 data/df_rncp.xlsx                       | Bin 0 -> 8579 bytes
 utils_graph.py                          | 143 ++++++++++++++++++++++++
 6 files changed, 285 insertions(+)
 create mode 100644 __pycache__/utils_graph.cpython-310.pyc
 create mode 100644 __pycache__/utils_graph.cpython-312.pyc
 create mode 100644 app.py
 create mode 100644 data/df_cfa_anonyme.xlsx
 create mode 100644 data/df_rncp.xlsx
 create mode 100644 utils_graph.py

diff --git a/__pycache__/utils_graph.cpython-310.pyc b/__pycache__/utils_graph.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f990fcc15c9303ee8ee797fb4d641c9cae66269d
GIT binary patch
literal 5600
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
I92E3_0%=gIzyJUM

literal 0
HcmV?d00001

diff --git a/__pycache__/utils_graph.cpython-312.pyc b/__pycache__/utils_graph.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..764d9f4d7ce68a27ea9b5961014d09a63ac6f7d0
GIT binary patch
literal 7735
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
N2EzFO;aZJ>e*tefAgcfX

literal 0
HcmV?d00001

diff --git a/app.py b/app.py
new file mode 100644
index 0000000..4b4edb5
--- /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
GIT binary patch
literal 7123
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

literal 0
HcmV?d00001

diff --git a/data/df_rncp.xlsx b/data/df_rncp.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..ffa62d4548e54bfc782c95049369b292b9cf1afe
GIT binary patch
literal 8579
zcmeHMg;yN;(j6Rv4ek!X-6aHfx8M-mZO{Ng0|^pl2M@AXfDk;mOK=MWw*i8L;0&(c
zY~H?aciDY^!FxStx=)|(s+sP(x2k@(v{aFh2?3}8bN~QA2e8`Daxg~#0Kmuq0096U
z(L~nW&D++^+d?nE!xm)5<?rf3n}>|Zk^?}5-~YeuzxWN5CJkwH^8n?~m2PC$IaL;`
zrO*ZU!u#;qv?V(}C-#<Dn`ApW-e<qL2Fm0TISE(ck1YoB9gR3tySdhdzwT|;!HNy*
z@6b1=5a#ab+huAeA%l1s8y(~jQb_TVSy;r|WdPDV>$`OX<W{9sO359Fh$Tb6WzF{)
z;6ZyEu}X}KMf(>zS2PTk@k9ncd<A|RCzy0{WDPWjedAO{j-?M=B=$9buSI95%W9?5
z=yjE<+6H22;GeRzMqK8J0=MEUjY3hI+k~mr1<0#E#GI5E_kx|-+k;GlUbM5kjXlx2
zY7!!Hu;4VwmI|W_pO^(`Imgxzm+`rc1%G}@l&>~)*hRZ^&9q8D`58)K)0CY6w$UJ^
zcjZ`l-<K_?DJsks{<5flph2WVbI+PG$gOZ3Z5>(u;-2sfZ@UTBAp;!fpkoY<-dpM4
z77yJ0vSAXjf0VKv!gq%X0Nmdr0kr-?%UXRNh7&l})ZnbcgwxW(%hm<N&Gp;<e{}pW
z=HQ<my%eIZ+0BC$b)a+;-G4r{1P02e`bjG_)9JkoRbIrcjn8MKSZZgc1nQATASs5l
zzPuS+SdxhU(NA}_CQurSPbkh%=T{b$dgbYZ#`f4NMZvRl^)sID)ale&x}r(|i*IWJ
z+sB6DT-5<6gTmJ%xhmio*E4bqg7=hB#81-0jC<9MmaQ(z5vF8x_sXIw>xFZEB#vc-
z%%l`<V?TW@s<u0kPSg*wvY#st>UW_#y(HJybr5x|w957pqYtpKap{1`WwaCA261X+
z_Uq6K;9u~KDfThtoQ4?J2pkP%1$-99=>FJ$I1nC7Q@jkXP=6;0FQuZ$M>s(k;1t4z
zzZriQZa;S~XDfGi=ilm9f!>1K8V@ko!tuWIIPjgSvPynbjEwd#b%d_&#;o{!Z|DpN
z#GS=a^iH9EnLy(~)%y63xBLS~qWtzjzHMAyCu;Al!WCzqc=6;(;IV!iLWe@*OSu$O
z2(lrj9^|Xo`WG$du)aB!{-Q>pCOV)Y#-q@OY`#u~&lu>#s4_@b4XmamPw{g6sG|cZ
ziPJG#uiFv{GXwT|MgRxLEMND8GkLQ>N^(qvX(5|(NWsZfI<*e+$7BuR{Y51;_}WPT
zUVG868RUfd^ca1rT#V^kw1In94xFnb9Z(<3!}%;)?@IkneAXnx?+l@J6ccDyW_`X(
zt%RkxphMAux18Jj{DO;BkWLaK6!Fp(>|vj__|5QIr;12$3wPe7xEu9;7N?;G4fzhe
zP6~len#u<0hTQg0lH)st3*;0l`E%<2l9pma9PNsoK!mzLadAy1j-0bwQ{90#g-D%(
zhOVl&&2=SJXz2igeCUaL596z41k5l>Qe}Kd<2(GUT$N2c>d2#3#8<omw47_x=WK0{
zAsZAL_Rr3bzT5FNP(an(#!tqUrbZYI$jQq9++zaY-|FTm-;9>6R|?*BS1|H!SJTPI
z$8FwOH%Y}~nP_U%%6UAcz6)<!gxDA3`_nB69gz;MP>=c3%le(I;c`XGLbr_>iUJ*d
zuKGx(w^zD8UG?3*>=XBF)pfskWMu<<9Ihv}K;9vx*nl)8iEftYL*bcKX|+uePhuZv
zWOv6?Q}v8tmeL0|u6!~>e;B`vhD@{P5u<mLq(a8+MhT8&wVwT9)K?!VKiF<ewJmB~
zF!ZBnV3c9xIXX6l3_}A4|1MrNl(%F|&(dJ*^-Qpb^QaLCjywO1Xs=R~9j>UxQd88P
zJ;p%d2RS=FZZV(AC(yuqIBNfn>oCcrR2ck96CB&*0CWU6uK$()|BUW`*8&81YX)b>
zfA?0Zqptdy2iS^v7tQ0B;Y)-!=gm#OtG|PbK2XED@R*MO<q4F6t;x)AT9q5gJu2W}
zq|f(U5N8dE;IxyyI0hg21%b<fIEwoSYy=72YOjnsToHu;fA`1E$30A(cOZgBNk~6O
zHlfJo1q%~dbbcYd+)3j^lz`JHj}M;{=nK^#JQohttcPAj>h6|>*VNb3zGJo9xNnqQ
zI79t4Jb-2#Muwp*fgJ!C|2|2_e$gp^hJM^~W2YW4>fD)sg|$KXQI)zx@JUdVH*Ivk
zB25S@1hkQt-mHJH>Me6U$lAVe%rSIwV7ETdHwA@9`tOiI1<WzE!{HJSPlcrLJAO}v
zAO~ApZxHv-1MhD+F*8xsJ)Z~IcaVHV#Q}{7!=KCnmn4+c)jKseKcV19W`9{(2b(B?
z2BTHeC40Sz>A>PYJ9ui13v13Yut6GCbyj&N$IE0HS@zy2<z!}TYk-qU(nA+yi=E_+
zo4w3gUa@JiW6*|zj*QWpT7#X-P72FI840!#=OJ}0{VK6iWWom(T{*2(Vo&My92H27
zqDFt!9Qiy$;gNr}Ras{w(&w->1lW1m?#(ZSJxn4aOHNDbkyBLkY#(f9akW%xSj<y;
z#mret15<5G!CBqnJzJYB{1;<MHH4!t79!CHcEPZKOMW41)3^fI6?YdS8L0A?TZC+_
z?HL`FLI(CHoo;RDLyCs&X61%MO$8y}?q`sp%1=KRt&(2n#0GF4MP8S@jwmcRS3Uyk
z)}`Uz!c4lRL#mZmrYKl|2wDue1@2>m^ZHMZP|SGoZ+UW=z3|pisii+aUUskF{3?y4
zPH36mWq)0KCLwV0k%8#^&|#{%R5liJ@on<UVeKR4EshwNL@dl(3^inS&ksB#v;yJy
zfh3DYbb5!;(vi}um+ul2aD(()fPY3gX=40Q1TA6c>N5-)ODAKB=UsUuc+9&c@mLGX
zFPVikRw##?i=VcE_%<p=IrEL;3}CKho7o;Zm(A%Crapons7=Wi%(C-g)9R|4v(iFx
z?9H`XoNp^SkZ}DX>|#*Mif;+K{W`01r1CJQ@3}s+OusSg6BS7eP)rg5<}=_c%58ZM
z^H)cPk){XlUc5mgBOBjLFZ}KF@tmFb*PP1)*Li$YE1R<_=t9je$_Y{X?W>so|L3b&
zmDDs()j3qghS?4JmDSm+^!dK<3+wB!462QVf>e$%U6Qeq2_eU-D#xJC;ke1qNt5(o
zr<}E#n;U#mMd+wPyF@!nvj~G=?Pr{S1pRg)Q-d8G@VXSgtzCbD-rK>})t39`{wLmd
zjYixkgn?}&-^GD}njCWwE3#jZR8!W3JgsI`VkkeDIi=$HHPW(*HxUg~NrmM(j;n?(
zzhIe1RSGqqDDuq6Qx9vAiC$MRHr8`tM!h1Ta^XDxb~EM?)%0RddhiP!9^|-`NRRs&
zqFgmtQ6Sc&GwbES#EZ~^E~7}YF(vjiAerex(0%Jie_19*CiF9VFQoc#xer0sw(?Px
zaT~VTiCD|njY?JFig*=rh%^XGNoA!5Z1Ks2Upb`6G{ScGR>4uor-B2PAeEM(6<%E(
z?npIChl$T~&nw4^OYpqt#np0Thm8>q-&|<_Dxrm7Wyt0~{)lappwz|OtdpYWabe0X
z=iW}bW>v*3Fx~)L6A#x;?PIGkZ160u4S}cj^^R|ImYa{l7{G%xywD(pZ^YQWbv}E7
zBW!m@em+a3RR?s$bmff6C%mxd+iKKr^@bdLYi!a{@r=B-Odri%irB)UV`knYOQXA!
z0^B1^eAvo9qA=1!q1ELeWGv+zef9$7l+31o?k!0h&f%#F!ms(X<aXoljj#d=Q+kYJ
z4nX5bp{RVN4pEbg%(9d+gnk}kKtg9!|A>_die|#WS4<<A16p59Yr}CtSY*6K=RYpb
zE3%);!@r_0FLp0hHTx2%sh-}9Pk0IKuW)d=2VL&jhrGPyz52E-%uN%r+Bd{^H$x+N
zxv@2NE7hOVD97CvbiDA1nY-=k<otj-aK!`>ckobB{f%6GKi>Qn>vub>Zz>w7YeC{y
z7=@!anE+SZ?Q#{$l`;%FNz;sm-e&e3|BKL>zH=%gTzp=G$?i)9h**c&s8I%H1%jJ{
zITPl2pV6*8SFi1&WN)<OGF7H%`QBSl@w=}%-Y!t(*9@D~-RWJt1FvPSsA%xk#`me;
zzcur7WNBEJeYLMW**obBk)#p{6M)vp>K+b#F5+*PuNv7mo7@~OIu#Q2{sjz73y3@*
z*i1VTqh`;%QabY-97Jt@qrXOQY)Rk6SI$o4FZMVt-%YOIWUrA^3JUq^Zdv@yy#AnC
z`3L>0JO-MPj5HXZS4M&uh6bo&SU`1f7Iz(8hA{t0dC;NRzTNAWs83g1J~SWbtTkMp
z$aqBezEy$&#@&1Kisv<M)g8~8tcF?izWMd|zYufn9A8&`lSRPfPquGi=wE*%vcLe`
ziA5W0Zn`etN4>vnYUW5CGj`oVA%3?WVBylV?(LW;Q4mK}jKLuMemjwgl2{v;SRKi|
zuwm{?67yj#%RUYl@$9a2_{W4|6&(Cb*#t8|1`7_%i7GkKRGx)&TmB#jLCuJWa_)#w
z;&ZbxtU79?Qr`fZ)S1Cgt8sDZgx35a8f0aTi1xO1z8iaK`ek~mBby}#f_dpWTg5`U
zWI+wZ<Q$`=7-s0<%P6Ibp?GFPIG-@=7$<z|L9j{Acc-tKlpc%Fze=cCj+HDSXI8hi
zC0C%P*%mz@q)BcN6r8JH@g-(Xo|{Uk%y@C&+9E*Zwq-sM5J*pDWT2sulACYvrL<xi
ziEyoj2<v(2s|rUP^VFuJZjPwtTrCWWv!chJFlHKsGwv!&yVPIRUWa#SQ1!pkTQGbq
zBryDu9@)??%7fwFiE8+T_M=&)5G2;=X*$w7A=Zw!<<0{hN;3MszK%VM+N4t2Dwl?M
zo?tmehmKw=nuu$k;&4g4WDoJ@a1A2#qkyX`Y>;zQej=H>5^J>FI)%A`rCv<FDBWfC
zae_Tx8Z&zt9<DcR&@D;4o$c`kw^Fhf>HV1A_!fpKNvZri65ZnZqd4!jIz%qLuG~i(
z7}$Y`T#agbp$=g?hW^;nDCYy4?X=NX?IWV9-35Yb!A`&SDrt!%=+UFkMO5459S~Yg
zt?gDb8cFxd=_#A0n^d8#gDcz0!?1(v&{)E>gr^EvkWl^j?iRP`BT%c{yzV|^C8%%0
z>d>0RD>+k^LVC>$4A`#T=jUucxmME@WJjwIogE99oR6u^IdZxkR&>Qll~lS9gC)d(
z=V+Ku!sfTGwM_7CrY#rPk+p*dgs7EBluW46zKG1pQ>eX5ay5+EKkHgxY50XpY>CXG
ze?oZMd(3gy2((!Z@;W+Q+PaK0Gn}pRY5aPcMh-c%iTQEnAl6UXmu68r_*CMkGuY!q
z$n+>79dhP$k6?P#ne;q_F?79tRM15D9^P^Ny%!73=t8H47x5D`008@6WeMaR;9?8<
zxhU0Na9iNP3Z|(H0iNs+`hMfUa3N9GMM7zME+uYR5yQzW<HKw79wfF4RlI~WIMid!
zsnTmtj?>{P_dSaz{GOiv&N+EyrK|7t2U8MLSNxkjUYH%}R7y$sqcX$r^U~gD+kLp6
zuIJ%TT*yA5)9Pidl+{j!gT<hH$~%~5yeQ1?g&@YW@323xcROe|tLlKioEORIlzT*1
zf-l=cm7KDDlT)3Iw1;L)J(}ICcp#T+ZuvF2&Io!LmNKA8WL<*1UaW+gY2gE<YzniT
z33C9~xa|PwEM-4)7-J+sDKv;$2Z;AHh$zr{RPG(HJG(Ivx5)EDa+!9y-{4~r<UB5v
zSmyU3#luu#U9Hc^XSlzoKdMAdEAT9TCDb484Q`yyE4In%!zK!HZ2+-<Shs?v%i23k
z?cXv+FN!mGN{9(Lw)mP?EWaRWZ2x*U<&lcX8&kkvz^mkzu1%Ji_wu%(BD6aE$8L61
ze&RBP?71{)JQw&(+VZ11!ji}Es-=o`rwV2jY1>>=$Vc`IGiT>qtBHmc?|dR6t{a^7
zQ}yrk4r#~>oP;-E_oh!xn!=ukPp*`qDYB}o1?*oa4YN=7!ULYBNV0$*+sjBaShl4`
zOEgwHsTBC~C7zWl@eJcVL+-|x+fFreYl<J5A}#Bt?u@eU%qx^wxqqVvxLIaEIa?LV
z(`Py>ch8hta+^!(^9<p<P*9$3EZ`MTsh2l#y}&f}p-Sl%jX*%Qo<3eQl1i#OdsjUM
z_$;Ba`>}=^KhhM+co9ocY$XS3{nq7ms-c4L$>A;i<-Yn(lr=kamFP=}CDRhdwZDkk
zq33NnQ4sSMT1CZ;(E>4jXWwb-PORmRWy|>1irlnSnmsJd!?A^pqRSsbn62sUnE{Ea
z!~rme#^bz!5wF_8&6nSyH{|i(?@7Pxk)V|Drg1nSSTCM*p3zBCx?)hfid~hlNd*oE
zQ)}GrupGATPT;l-B#pMro$wu3v!FlwI&dsBn1^@%eOP)wk@mYJ#@nEpN>jehshp{t
zFSC#6JI}bK!O|FBH>O4H|ETY5^f;XlzzL)Zze@N!ksO|T+1luOdpWw<|KyVkSXvc{
z7x?NR{GM)j>G1;7o&n@U>ySm~gyXs2E~-$XU?S1&d0{ktKQ;9Fg1S5Hn_uQ<5<8qX
z90C+5h#xd%b7EiB&lE-DAd_fD{EDCZipEBy=~}$oRL<uOXWaYY1RbxZRwpgW{5niD
ztcn&+!z2SBg{OzF4c8d!RrYL7!lX_dbFH4^TIC)#u571dt*)qfo)QL)O^zuL+ppKQ
zggM=DkBt?j8tiCPAWfC%f1#|~W1DrXm8?DRhS~RDVV(c%m>=rD;1EthJNRRV!7~gV
zTmrChx7PA<_W*HQyL;LGK9u?2${&u2IEaO6HxFSC6s29ZKT)z~n}&*^hDwA?QUqwA
z9fUcsWZ?1@T;O|qR=8&%e9~D2UIc}lXR1Paa@od79CRo7dU9zjks&jA;AX=1O?LOU
zzf`F`GML8KSq$rp-MwO4jX5^%jx8s0FbS1@%ZR<f))VMV#Ps>_iQe#q2A!Fw$xe)s
z-;{VB>Aj6Otu}C22_*%?j%+(D``zn{*MvbbB?_rXQXYs*dbDM9xik=(G8D7)rxa0x
z>D6p5MW%)MOQ5<HYfl{-HC~K`y3+;!%GI0b_2AnUvb-0scKvz~?BA>;Ia}6E8~tkG
z6U|3wpKyz)YPUPT_rfI9|9iFISNS<*DNXFAxmkq<@7G2(-bHpyF60}0u!YE*eIHnq
z|2HjQKUtx5i|z)ex#T8$ro6QG0ZJ9g2+Tv_n>cwdO>1rJon*&_78Ukg1Swho;e<$`
zw1<KOLUrf)M$tgqKk|~PXm&XQegzd?<bZz$=W`E_|6vmj%HOw4$e#vgPkfAlGRfZ_
z5dkrQ$>g&X_7jF8zBZcqDHZmR*Fq=;yPF&0eKW+dX0D0EG6KuCE@H|?h#J>j4eGt-
zu;^Hd6gj<et!eT8S!#Ndei(n%qqeY6R4W;LF1u10m(5H?GwHIm$oaQo?{RBoRJx<C
zs|CpSTb8F+z+)39@49h1iL^`w(4>V}Mon%i99Dh$NTA2TvK=~$Lh*vebW1vt6jVVc
zOpnjSu?-GAb%9IdCqkr)LwbjvY}3X@hLB2*<apYPDq3o3pTe>v4DA@=El!6Mx;LAN
zE*VwpX7b-X_@+xNlVrPo^hOmVxDxR8qs@{#56mFq8yD9{Dn-bC!tJx~t!jjPeHj}5
zwNt|qkk`xV<OWV@O=2fH#68|lYq2M7K+E(&3GE)`wFvQ<Kass2Xh^u2U%%6!3kvi%
zCfrd$Vd+1M=!*{bXS@#IlS@hVqF`yWe<|EV5<<~dn`y2&F_c6N(j&OJ2-_fmhu~kG
z83H0Fym<WcxclFM`uFi4hTmGMe|7NJ!Q8(Me><ka+3=?^-9y8N!-_vlTi_DLgVDu9
z<G(7xf0zOQNf<wk|ARjK(9c7e@DERY@G|tDM8k((9_lWCcxizPHE=HvRhWkk9(Kro
zIM5~f>EMrk`Jw5<p5PDDOt|g||83C24&kAPzn0cN?BNk40|5S3XdjyYHTnJ7T!8XV
a=D(*xEmahFbOC@z@J}ebmhVyjcJ_b!h|5<1

literal 0
HcmV?d00001

diff --git a/utils_graph.py b/utils_graph.py
new file mode 100644
index 0000000..c97694e
--- /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
-- 
GitLab