# 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. ![Donut de Kate Raworth](Images/8_doughnut_economy.png)

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. ![Donut de Kate Raworth](Images/donut.png)

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 ![Donut de Kate Raworth](Images/tkinter.PNG)

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("<>", action_scenario_list) ``` Ce morceau de code permet de générer un liste déroulante interactive (de type Combobox). La valeur de la liste déroulante est pa.List_name, qui contient la liste des scénarios du projet. La fonction associé à la liste est `action_scenario_list()`. Ainsi, à chaque clique sur l'un des composants que la liste affiche, la fonction est appelée. ``` > main.py def action_scenario_list(event): pa.scenario_name = scenario_var.get() ``` La fonction action_scenario_list(), une fois appelée, va simplement enregistrer sa valeur actuelle dans la variable pa.scenario_name. #### Les différents caractéristiques: La fenêtre est divisée en quatre partie. La première partie "Import files" permet d'importer un fichier à l'aide du bouton *Import*. *Import* ouvre une fenêtre du navigateur de fichier et laisse l'utilisateur trouver et sélectionner celui-ci. Le fichier correspond bien sûr à un tableur *.xlsx* ou *.ods* qui contient les LCI des scénarios et autres bases de données. Si le fichier est bien importé, son nom doit apparaitre à la droite du bouton. La seconde partie "Parameters Comparison" est composée de deux listes déroulantes, le contenu des listes déroulantes est identique: c'est une liste des noms des scénarios du fichier importé. S'il n'y a pas de fichier importé, la liste est vide. La première liste permet de choisir le scénario à étudier. La deuxième liste laisse choisir la référence à laquelle comparer le scénario. La troisième partie "Mode" permet de choisir le modèle d'affichage de Donut souhaité. L'utilisateur a le choix entre *Multiple stakeholder* et *Specific stakeholder*. Le choix spécifique stakeholder se poursuit avec l'apparition d'une liste déroulante pour indiquer le stakeholder en question. L'application ne tracera alors que le Donut qui correspond aux impacts sur cet catégorie de partie prenante. Dans le cas du *multiple stakeholder*, l'application trace sur la même image 5 Donuts, un pour chacune des parties prenantes. La quatrième partie "Let's draw !" est constitué de deux boutons : - *Generate*. Il suffit de cliquer pour lancer les calculs de l'outil. L'avancement est indiqué sur la droite, à 100%, le graphique est prêt et s'affiche.* - *Export*. Cliquez dessus après avoir générer les résultats, il exportera la base de données *database_endpoint_impact* avec deux nouvelles colonnes: l'impact réel sur les Endpoints ainsi que la précision. ### 3.2/ Class Parameters() L'usage de ce menu à paramètre dans le fichier main.py demande plusieurs variables pour stocker les choix utilisateurs. Or, ces variables sont utilisés dans plusieurs fonctions et actualisé dans plusieurs fichier. Afin de permettre cela, tous les paramètres sont stocké dans une classe: Parameters(). ``` > Graph.py class Parameters: def __init__(self): self.mode = 0 self.ref_name = 0 self.unity = '' self.type = 0 self.stakeholder = 'Local_Community' self.scenario_name = 0 self.List_name = [''] self.error_shown = 0 ``` La classe Parameters() ne possède aucune méthode, seulement les attributs ci-dessus. Voir la documentation de la classe pour plus de détails. ### 3.3/ Graphique avec Matplotlib Le tracé des graphique Donut se fait avec la librairie python mathplotlib, ainsi que numpy. Tout le code correspondant est écrit dans le fichier Graph.py. Graph.py contient la classe Parameters ainsi que trois fonctions: - plot_donut: utilisé pour tracer un donut - plot_type_1: Utilisé pour créer la figure qui recevra le Donut dans le cas *Specific stakeholder* - plot_type_2: Utilisé pour créer la figure et les Donuts dans le cas *Multiple stakeholder* #### Fonction plot_donut() ![plotdonut](Images/tool_donut.PNG)

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. ![donut_plot_type_1](Images/tool_donut_full.PNG)

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() ![donut_plot_type_2](Images/tool_donut_multi.PNG)

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.