# 8/ Interface Graphique ## Résumé L'interface graphique n'est pas à délaisser. La mise en forme est primordiale. Les images ont une influence puissante dans la compréhension de système complexe. Ces représentations sont nécessaires pour une prise de décision éclairée. Le menu permet d'interagir avec les paramètres de la méthode pour effectuer de nouvelles comparaisons d'impacts. L'affichage du Donut donne une visualisation puissante pour comprendre les impacts d'un système. --- ## 1/ Une représentation visuelle: le Donut >Les images sont très puissantes pour expliquer un concept inconnu, elles s'emparent de la première couche, de la Tabula Rasa. - soutenait Paul Samuelson en 1948. Cette phrase de Samuelson nous rappelle une chose fondamentale: les images sont instantanément absorbées sans aucune médiation car les spectateurs ne sont généralement pas appelés à les analyser ou à les déconstruire comme c’est le cas quand il s’agit d’un message verbal [^1]. La perception d'une image entraine une réaction émotionnelle qui associe l'information contenu dans la représentation à une émotion. Bref, l'image est un vecteur puissant et encore mal maîtrisé pour transmettre de l'information. C'est pourquoi la représentation économique du Donut de Kate Raworth[^2] est si intéressante pour représenter des impacts. 
Image 1: Le Donut de Kate Raworth
L'espace sûre et juste, souvent en vert, est représenté comme un cercle. Les impacts environnementaux vers l'extérieur, et les dégradations sociales à l'intérieur. L'application du Donut a fait le choix de cette représentation pour illustrer les impacts du projet. Cependant, il convient d'y appliquer quelques ajustements pour le mettre au goût des capabilités. Image 2: Le Donut des capabilités
Le donut des capabilités représentent les impacts relative d'un projet, entre un scénario et une référence. Si un impact sur un Endpoint du scénario est supérieur à celui de la référence (c'est à dire si la valeur d'impact est >1) alors l'impact est représenté en bordure du Donut. Si c'est l'impact de la référence qui dépasse celui du scénario (c'est à dire si la valeur d'impact est <1) alors l'impact est représenté dans le Donut. Cette double direction d'impact met en évidence les points faibles du projet (là ou l'impact sort du Donut), mais aussi les points forts (là ou le projet améliore des capabilités). ## 2/ Accessibilité et expérience utilisateur Le développement de l'interface telle qu'elle est aujourd'hui (Aout 2023) est le fruit d'un compromis. La volonté de l'équipe est de construire une application ergonomique et facile d'utilisation. Cependant, au stade du projet, il n'est pas pertinent de passer autant de temps sur l'ajout de nombreuses fonctionnalités graphiques, stylistiques... C'est pourquoi, nous avons souhaitez gardez le développement de l'interface au juste minimum: une utilisation qui ne demande pas de compétences informatiques complexes. En effet, en suivant le tutoriel d'installation, il vous suffit ensuite de cliquer sur le fichier run.sh pour lancer l'interface graphique. Il n'y a besoin de passer en ligne de commande (celles qui font peur aux non-initiés). Dans cette volonté d'appropriation se cache néanmoins une éthique de compostabilité du code puisqu'il est possible d'installer l'application par de nombreuses autres manière: via Gitlab, avec un IDE, en ligne de commande. Libre à chacun d'utiliser le niveau qui lui correspond le mieux. ## 3/ Application Donut: Création de l'interface graphique ### 3.1/ Menu avec Tkinter Image 3: Menu de l'application Donut
#### Fonctionnement: Le code du menu est contenu dans main.py. Afin de générer une fenêtre contenant le menu de l'application, à ce stade du projet, l'utilisation de la librairie python Tkinter est suffisante. ``` > main.py import tkinter as tk from tkinter import ttk from tkinter.filedialog import askopenfilename ``` Cette librairie permet de générer des fenêtres personnalisables, de les rendre interactives... L'interactivité des différents boutons est gérée par l'appel de fonctions associé à l'objet interactif créer lorsqu'est détecté un changement. Par exemple, le bouton *Quel scénario ?*: ``` > main.py # List for scenarios tk.Label(parameters, text='Quel scenario ?').pack() scenario_list = ttk.Combobox(parameters, values=pa.List_name, textvariable=scenario_var) scenario_list.current(0) scenario_list.pack() scenario_list.bind("<Image 4: Graphique d'un Donut par plot_donut()
Cette fonction prend en paramètre une figure, un axe, des limites min et max pour l'axe, un titre, et le nom du scenario et de la partie prenante qui doit être tracé. La fonction génère un diagramme en barres avec les données qui lui sont fournies. Ce code va charger les paramètres et modifier le style de l'axe afin de le rendre moins encombrant: ``` > Graph.py # Layout parameters ax.xaxis.grid(False) ax.yaxis.grid(False) ax.spines["start"].set_color("none") ax.set_title(name, fontsize=20, ha="center", va="center") COLOR_OUT_DONUT = "#08A04B" COLOR_BAR_M = "#CD7F32" COLOR_BAR_L = "#C04000" COLOR_DONUT = "#3A5F0B" # Recover labels IN and OUT for the donut df = df.loc[df['Stakeholders'] == stakeholder] value_scenario_out = df.loc[df['Category_ID'] == 'OUT', scenario].values value_scenario_in = df.loc[df['Category_ID'] == 'IN', scenario].values label_out_ = df.loc[df['Category_ID'] == 'OUT', 'Name'].values label_in_ = df.loc[df['Category_ID'] == 'IN', 'Name'].values error_out = df.loc[df['Category_ID'] == 'OUT', 'Errorbar'].values error_in = df.loc[df['Category_ID'] == 'IN', 'Errorbar'].values # Security if there is no values to plot. if len(label_out_) == 0 or len(label_in_) == 0: return # Definition of the geometrical parameters, in MAJUSCULE. DONUT = 2 ANGLES_OUT = np.linspace(0, 2 * np.pi, len(label_out_), endpoint=False) ANGLES_IN = np.linspace(0, 2 * np.pi, len(label_in_), endpoint=False) WIDTH_OUT = 2 * np.pi / len(label_out_) # Determine the width of each bar. OFFSET_OUT = np.pi / 2 # Determines where to place the first bar. WIDTH_IN = 2 * np.pi / len(label_in_) # Determine the width of each bar. ``` Ensuite, on s'occupe d'afficher les noms des Endpoints (sous forme de label à l'exterieur et de texte à l'intérieur): ``` > Graph.py # a/ Labels # Add Labels (OUT) and text (IN) # Note the 'wrap()' function. The '10' means we want at most 10 consecutive letters in a word, # but the 'break_long_words' means we don't want to break words longer than 10 characters. for v in range(0, len(label_out_)): label_out_[v] = str(label_out_[v]) for v in range(0, len(label_in_)): label_in_[v] = str(label_in_[v]) label_out = ["\n".join(wrap(r, 10, break_long_words=False)) for r in label_out_] label_in = ["\n".join(wrap(r, 10, break_long_words=False)) for r in label_in_] # Add space for labels: XTICKS = ax.xaxis.get_major_ticks() for tick in XTICKS: tick.set_pad(6) # Add labels OUT ax.set_xticks(ANGLES_OUT) ax.set_xticklabels(label_out, size=6) # Add text for IN for i in range(0, len(label_in)): texte = label_in[i] angle = ANGLES_IN[i] ax.text(angle, -(DONUT / 2) - 2, texte, fontsize=6, ha="center", va="center") ``` La partie suivante vient imposer les limites min et max de l'axe, et créé des masques en fonction de la valeur de l'Endpoint. Cela permet de séparer, par exemple, les valeurs qui sont à coté intérieur l'intérieur du donut (mask_IN_in) de celles qui sont coté intérieur à l'exterieur du Donut (mask_IN_out). ``` > Graph.py # b/ Style and limits ax.set_ylim(-lim_in, lim_out) ax.set_theta_offset(OFFSET_OUT) # Specify offset # Mask to differentiate values for outer circle mask_OUT_low = value_scenario_out < 0 mask_OUT_in = (value_scenario_out >= 0) & (value_scenario_out < DONUT / 2) mask_OUT_out = (value_scenario_out >= DONUT / 2) & (value_scenario_out < lim_out) mask_OUT_high = value_scenario_out >= lim_out # Mask to differentiate values for inner circle mask_IN_low = value_scenario_in < 0 mask_IN_in = (value_scenario_in >= 0) & (value_scenario_in < DONUT / 2) mask_IN_out = (value_scenario_in >= DONUT / 2) & (value_scenario_in < lim_in) mask_IN_high = value_scenario_in >= lim_in ``` Enfin, on plot les barres et les barres d'erreurs de chacun des masques, en leur donnant la couleur qui leur correspond. La liste Error contient un booléen indiquant si oui on non l'on doit afficher les barres d'erreurs. Ce paramètre se règle grâce à l'interactivité dans plot_type_1(). ``` > Graph.py # d/ Plotting # Donut plotting ax.bar(0, DONUT / 2, color=COLOR_DONUT, alpha=1, width=2 * np.pi, zorder=0) ax.bar(0, -DONUT / 2, color=COLOR_DONUT, alpha=1, width=2 * np.pi, zorder=0) # Outer circle # Plotting of values outside the donut ax.bar(ANGLES_OUT[mask_OUT_high], value_scenario_out[mask_OUT_high], color=COLOR_BAR_L, alpha=0.6, width=WIDTH_OUT, zorder=-1) ax.bar(ANGLES_OUT[mask_OUT_out], value_scenario_out[mask_OUT_out], color=COLOR_BAR_M, alpha=0.6, width=WIDTH_OUT, zorder=-1) # Plotting of values inside the donut ax.bar(ANGLES_OUT[mask_OUT_in], -value_scenario_out[mask_OUT_in], yerr=error_out[mask_OUT_in], color=COLOR_OUT_DONUT, alpha=1, width=WIDTH_OUT, zorder=1, bottom=DONUT / 2) ax.bar(ANGLES_OUT[mask_OUT_low], -DONUT / 2, yerr=error_out[mask_OUT_low], color=COLOR_OUT_DONUT, alpha=1, width=WIDTH_OUT, zorder=1, bottom=DONUT / 2) # Plotting error bar if error[1]: ax.errorbar(ANGLES_OUT[mask_OUT_out], value_scenario_out[mask_OUT_out], yerr=error_out[mask_OUT_out], capsize=4, capthick=4, color="red", linestyle='') ax.errorbar(ANGLES_OUT[mask_OUT_in], -value_scenario_out[mask_OUT_in], yerr=error_out[mask_OUT_in], capsize=4, capthick=4, color="green", linestyle='') # Inner circle # Plotting of values inside the donut ax.bar(ANGLES_IN[mask_IN_high], -lim_in, yerr=error_in[mask_IN_high], color=COLOR_BAR_L, alpha=0.6, width=WIDTH_IN, zorder=-1) ax.bar(ANGLES_IN[mask_IN_out], -value_scenario_in[mask_IN_out], yerr=error_in[mask_IN_out], color=COLOR_BAR_M, alpha=0.6, width=WIDTH_IN, zorder=-1) # Plotting of values outside the donut ax.bar(ANGLES_IN[mask_IN_in], value_scenario_in[mask_IN_in], yerr=error_in[mask_IN_in], color=COLOR_OUT_DONUT, alpha=1, width=WIDTH_IN, zorder=1, bottom=-DONUT / 2) ax.bar(ANGLES_IN[mask_IN_low], DONUT / 2, yerr=error_in[mask_IN_low], color=COLOR_OUT_DONUT, alpha=1, width=WIDTH_IN, zorder=1, bottom=-DONUT / 2) # Plotting error bar if error[0]: ax.errorbar(ANGLES_IN[mask_IN_out], -value_scenario_in[mask_IN_out], yerr=error_in[mask_IN_out], capsize=4, capthick=4, color="red", linestyle='') ax.errorbar(ANGLES_IN[mask_IN_in], value_scenario_in[mask_IN_in], yerr=error_in[mask_IN_in], capsize=4, capthick=4, color="green", linestyle='') ``` Pour plus de détails, voir la documentation de plot_donut(). #### Fonction plot_type_1() Cette figure matplotlib est divisée en deux parties: ``` > Graph.py fig, (legend, ax) = plt.subplots(1, 2, figsize=(15, 10), subplot_kw={"projection": "polar"}, label='Donut Tool') ``` La diagramme de barres issu de la fonction plot_donut() est courbé grâce au choix de projection polaire pour la figure. Image 5: Affichage de l'interface Donut de plot_type_1()
La figure comprend: - Sur sa partie droite le donut tracé. - Sur sa partie gauche, au milieu, un tableau contenant les valeurs des Endpoints. - Sur sa partie gauche, en bas, des boutons pour modifier les paramètres d'affichages suivant: - "*Go Back*" pour fermer la figure. - "*Refresh*" pour réactualiser la figure. - "*Limit IN et Limit OUT*" permettent de régler l'échelle du Donut, ainsi on peut agrandir ou diminuer la taille du trou, et de la partie verte du Donut. - "*ErrorBar In ? et ErrorBar OUT ?*" permettent d'afficher ou de cacher les barres d'erreurs intérieures et extérieures. #### Fonction plot_type_2() Image 6: Affichage de l'interface Donut de plot_type_2()
Cette fois ci, la figure est en 2x3, comme une boite d'œuf. Toutes les zones comprennent un Donut d'un des stakeholders sauf la zone en bas à droite, qui contient un bouton "*Go back*" pour fermer la figure. ``` > Graph.py def plot_type_2(database_output, scenario_name): ... stakeholders = ['Local_Community', 'Society', 'Consumers', 'Workers', 'Chain_Value_Actors'] ############################################################ '''2.a/ Figure creation''' fig, axes = plt.subplots(2, 3, figsize=(25, 10), subplot_kw={"projection": "polar"}, label='Donut Tool') fig.tight_layout(pad=10.0) ############################################################ '''2.b/ Buttons and interactivity''' ax_button = plt.axes([0.8, 0.05, 0.10, 0.03], label='Go Back') button_goback = Button(ax_button, 'Go Back', color=col) ############################################################ '''2.c/ Update functions''' def back(val): plt.close(fig) ############################################################ '''2.d/ Drawing''' for i in range(0, 2): for j in range(0, 3): ax = axes[i, j] if 3*i+j < 5: plot_donut(database_output, ax, scenario_name, stakeholders[3*i+j], 10, 10, stakeholders[3*i+j]) else: ax.grid(False) ax.axis('off') button_goback.on_clicked(back) plt.show() ``` [^1]: Joffe, H., 2007. Le pouvoir de l’image : persuasion, émotion et identification. Diogène 217, 102–115. [https://doi.org/10.3917/dio.217.0102](https://doi.org/10.3917/dio.217.0102) [^2]: Raworth, K., 2017. Doughnut Economics Seven Ways to Think Like a 21st-Century Economist. Random House Business Books.