Skip to content
Snippets Groups Projects
Commit 295fbd37 authored by Hugo Debuyser's avatar Hugo Debuyser
Browse files

Merge remote-tracking branch 'origin/master' into hugodebuyser

parents 75870310 3d8a9acf
No related branches found
No related tags found
No related merge requests found
Showing
with 298 additions and 79 deletions
......@@ -3,11 +3,11 @@
### Équipe H4
- [ANTOINE Maxence](maxence.antoine.etu@univ-lille.fr)
- [DEBUYSER Hugo](hugo.debuyser.etu@univ-lille.fr)
- [DEKEISER Matisse](matisse.dekeiser.etu@univ-lille.fr)
- [DESMONS Hugo](hugo.desmons.etu@univ-lille.fr)
- [MENNECART Matias](matias.mennecart.etu@univ-lille.fr)
- [ANTOINE Maxence](mailto:maxence.antoine.etu@univ-lille.fr)
- [DEBUYSER Hugo](mailto:hugo.debuyser.etu@univ-lille.fr)
- [DEKEISER Matisse](mailto:matisse.dekeiser.etu@univ-lille.fr)
- [DESMONS Hugo](mailto:hugo.desmons.etu@univ-lille.fr)
- [MENNECART Matias](mailto:matias.mennecart.etu@univ-lille.fr)
___
......@@ -32,6 +32,8 @@ ___
## Diagramme de cas d'utilisation
Systeme: Application de classification de données
![Diagramme de cas d'utilisation](./ressources/DiagrammeUtilisation.png)
### Fiches descriptives
......@@ -116,56 +118,103 @@ ___
1) L'utilisateur actionne le bouton "classifier les données".
2) Le système classifie les points de façon aléatoire et modifie leurs couleurs en fonction de la classe choisis.
*Inclure les fiches descriptives pour ces fonctionnalités:*
- *Charger l'ensemble de données*
- *Ajouter une donnée*
- *Classifier la donnée non classifiée*
### Prototypes pour l'interface
[Prototype figma](https://www.figma.com/design/J7CNIyIPHg0QBvoMKEAZ2L/Untitled?node-id=0-1&t=rzTi4oB0jeOOZTxv-1)
Vous pouvez retrouver le [Prototype figma](https://www.figma.com/design/J7CNIyIPHg0QBvoMKEAZ2L/Untitled?node-id=0-1&t=rzTi4oB0jeOOZTxv-1) afin de tester une démonstration de la maquette de l'application.
#### Interface principale
La première page de l’interface se compose des éléments suivants :
- **Nuage de points** : Pour l'instant vide, permettra d'afficher le nuage de points..
- **Icône d'engrenage** :Située en haut à droite de la zone d’affichage permet d'accéder à un menu de paramètres pour configurer l'affichage.
- **Bouton charger des données** : Ce bouton ouvre une fenêtre qui permet à l’utilisateur d'importer un fichier contenant les données avec lesquelles il souhaite travailler.
- **Bouton ajouter une donnée** : Cette option permet à l’utilisateur d’ajouter une donnée au nuage de point.
- **Bouton classifier les données** : Ce bouton permet la classification des données non classifiées grâce a un algorithme (Pour le jalon 1, cette classification est aléatoire).
---
<img src="./ressources/MaquetteFigma.png" width="60%" height="60%" alt="Maquette Figma">
#### Interface principale
La première page de l’interface se compose des éléments suivants :
- **Zone principale d’affichage** : Pour l'instant vide.
- **Icône d'engrenage** :Située en haut à droite de la zone d’affichage permet d'accéder à un menu de paramètres de configuration pour l’application.
- **Bouton charger des données** : Ce bouton ouvre une fenêtre qui permet à l’utilisateur d'importer un fichier contenant des données dans l'application.
- **Bouton ajouter une donnée** : Cette option permet à l’utilisateur d’ajouter une donnée spécifique.
- **Bouton classifier les données** : Ce bouton permet la classification des données grâce a un algorithme d’apprentissage automatique.
---
<img src="./ressources/ChargerDonnées.png" width="60%" height="60%" alt="Charger les données">
*Inclure des prototypes de l'interface utilisateur pour ces fonctionnalités:*
- *Ajouter une donnée*
#### Fenêtre de chargement de fichier
- **Bouton "PARCOURIR"** : Ce bouton permet à l’utilisateur d'ouvrir une fenêtre de navigation dans ses fichiers locaux afin de sélectionner le fichier de données à importer dans l’application.
- **Nom fichier** : une zone texte où s’affichera le chemin vers le fichier sélectionné après avoir utilisé le bouton "PARCOURIR".
- **Bouton "Valider"** : Une fois le fichier sélectionné, ce bouton permet de confirmer le choix et de lancer le chargement du fichier dans le nuage de points.
---
<img src="./ressources/AjouterDonnées.png" width="60%" height="60%" alt="Ajouter une donnée">
<img src="./ressources/AjoutDonnéesVisible.png" width="60%" height="60%" alt="Classifier une donnée">
<img src="./ressources/AjoutDonnéesVisible.png" width="60%" height="60%" alt="Afficher les données">
- *Classifier la donnée non classifiée*
#### Ajouter une donnée
Cette page présente une interface permettant à l’utilisateur d'ajouter manuellement de nouvelles données pour lesquelles il souhaite déterminer une classification.
- **Champs de saisie pour les valeurs** : Quatre champs de texte sont affichés permettant d'inscrire les 4 valeurs necessaires pour ajouter une donnée (Uniquement des Iris pour le jalon 1).
- **Bouton "Valider"** : Après avoir rempli les champs de saisie, l’utilisateur peut cliquer sur ce bouton pour valider l'entrée. Cela ajoutera la donnée dans le nuage de points.
- **Ajout d'une donnée** : On remarque qu'après avoir cliqué sur le bouton "Valider", une valeur a été ajouté sous une forme et une couleur diférenciées des autres.
---
<img src="./ressources/AjoutDonnéesVisible.png" width="60%" height="60%" alt="Afficher les données">
<img src="./ressources/ClassifierDonnées.png" width="60%" height="60%" alt="Classifier une donnée">
- *Modifier les attributs pour l'affichage*
#### Visualisation des données classifiées
<img src="./ressources/ModifierAttributs.png" width="60%" height="60%" alt="Modifier les attibuts">
Cette page représente la visualisation graphique des données après l’étape de classification (réalisé par l'action sur le bouton `classifier les données`, ainsi tous les points non classifiés le sont).
- **Bouton "Classifier les données"** : En appuyant sur ce bouton, la donnée ajoutée se classifie selon ses valeurs renseignés.
*Chaque prototype est constitué d'une suite d'écrans, ou d'une arborescence d'écrans si plusieurs chemins d'interaction sont possibles.*
La donnée qui avait été ajoutée par l'utilisateur garde un symbole différenciateur mais adopte la couleur de la classification qui lui a été déterminer.
*Pour les deux fonctionnalités dont on demande le prototype et la fiche descriptive, vous ferez le lien entre le prototype et la fiche descriptive. Plus précisément, pour chaque étape de la fiche descriptive, vous indiquerez à quel écran elle correspond. Vous pouvez par exemple mettre une légende sous l'écran, par ex. "Écran pour l'étape 3 de la fiche descriptive du UC Ajouter une donnée."*
---
*Les prototypes peuvent être en faible fidélité.*
*Les prototypes peuvent être dessinés à la main ou générés en utilisant un logiciel. Dans les deux cas, veillez à ce que les images soient lisibles et avec une bonne résolution (possibilité de zoomer pour lire le texte qui s'y trouve).*
<img src="./ressources/ModifierAttributs.png" width="60%" height="60%" alt="Modifier les attibuts">
## Diagramme de classes
#### Modification des attributs d'affichage
<img src="./ressources/DiagrammeClasse.png" width="60%" height="60%" alt="Diagramme de classe">
Cette page permet à l’utilisateur de modifier les attributs utilisés pour représenter les données sur le graphique, en particulier les valeurs des axes X (abscisses) et Y (ordonnées).
- **Champ "Valeur Ordonnée"** : L'utilisateur peut selectioner ici la valeur qu'il souhaite voir représentée sur l'axe des ordonnées (Y) du graphique.
- **Champ "Valeur Abscisse"** : L'utilisateur peut selectioner ici la valeur qu'il souhaite voir représentée sur l'axe des abscisses (X) du graphique.
- **Bouton "Valider"** : Une fois les valeurs des axes définies, l’utilisateur peut cliquer sur ce bouton pour appliquer ces paramètres et afficher les données selon les nouveaux attributs choisis.
*Inclure un diagramme de classes qui permet d'**implémenter toutes les fonctionnalités**.*
Cette page permet à l’utilisateur de modifier les attributs utilisés pour représenter les données sur le graphique, en particulier les valeurs des axes X (abscisses) et Y (ordonnées).
*Le diagramme de classes doit suivre le design pattern MVC, mais vous ne ferez pas figurer les classes de la vue. Il doit être clair quelles classes font partie du contrôleur (par exemple grâce à un nom de classe qui contient 'Controleur'); les classes restantes seront considérées faisant partie du modèle.*
- **Champ "Valeur Ordonnée"** : L'utilisateur peut selectioner ici la valeur qu'il souhaite voir représentée sur l'axe des ordonnées (Y) du graphique.
- **Champ "Valeur Abscisse"** : L'utilisateur peut selectioner ici la valeur qu'il souhaite voir représentée sur l'axe des abscisses (X) du graphique.
- **Bouton "Valider"** : Une fois les valeurs des axes définies, l’utilisateur peut cliquer sur ce bouton pour appliquer ces paramètres et afficher les données selon les nouveaux attributs choisis.
*L'image du diagramme doit être de résolution suffisante permettant de zoomer et lire le texte qui y figure.*
---
---
## Diagramme de classes
<img src="./ressources/DiagrammeClasse.png" width="60%" height="60%" alt="Diagramme de classe">
Nous avons fais le choix d'implémenter la classe `ClassificationModel`comme un singleton afin d'éviter que plusieurs model se retrouve au sein de la même application et créé des incohérences.
\ No newline at end of file
Analyse/ressources/AjoutDonnéesVisible.png

11.7 KiB | W: | H:

Analyse/ressources/AjoutDonnéesVisible.png

12.1 KiB | W: | H:

Analyse/ressources/AjoutDonnéesVisible.png
Analyse/ressources/AjoutDonnéesVisible.png
Analyse/ressources/AjoutDonnéesVisible.png
Analyse/ressources/AjoutDonnéesVisible.png
  • 2-up
  • Swipe
  • Onion skin
Analyse/ressources/ClassifierDonnées.png

13 KiB | W: | H:

Analyse/ressources/ClassifierDonnées.png

11.7 KiB | W: | H:

Analyse/ressources/ClassifierDonnées.png
Analyse/ressources/ClassifierDonnées.png
Analyse/ressources/ClassifierDonnées.png
Analyse/ressources/ClassifierDonnées.png
  • 2-up
  • Swipe
  • Onion skin
Analyse/ressources/DiagrammeClasse.png

396 KiB | W: | H:

Analyse/ressources/DiagrammeClasse.png

811 KiB | W: | H:

Analyse/ressources/DiagrammeClasse.png
Analyse/ressources/DiagrammeClasse.png
Analyse/ressources/DiagrammeClasse.png
Analyse/ressources/DiagrammeClasse.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -31,7 +31,7 @@
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.4</version>
<configuration>
<mainClass>fr.univlille.sae.classification.view.MainStage</mainClass>
<mainClass>fr.univlille.sae.classification.Main</mainClass>
</configuration>
</plugin>
</plugins>
......@@ -67,5 +67,10 @@
<artifactId>javafx-fxml</artifactId>
<version>11.0.1</version>
</dependency>
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.9</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
......@@ -16,9 +16,9 @@
<HBox prefHeight="356.0" prefWidth="691.0">
<children>
<Region prefHeight="338.0" prefWidth="65.0" />
<ScatterChart prefHeight="342.0" prefWidth="609.0">
<ScatterChart fx:id="scatterChart" prefHeight="342.0" prefWidth="609.0">
<xAxis>
<CategoryAxis fx:id="absAxe" prefHeight="21.0" prefWidth="498.0" side="BOTTOM" />
<NumberAxis fx:id="absAxe" prefHeight="21.0" prefWidth="498.0" side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis fx:id="ordAxe" side="LEFT" />
......
......@@ -3,18 +3,15 @@ package fr.univlille.sae.classification;
import fr.univlille.sae.classification.model.ClassificationModel;
import fr.univlille.sae.classification.view.MainStageView;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class Main extends Application {
public void start(Stage stage) throws IOException {
ClassificationModel model = new ClassificationModel();
ClassificationModel model = ClassificationModel.getClassificationModel();
MainStageView view = new MainStageView(model);
view.show();
......
package fr.univlille.sae.classification.controller;
import fr.univlille.sae.classification.model.ClassificationModel;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
......@@ -8,6 +9,7 @@ import javafx.stage.FileChooser;
import javafx.stage.Stage;
import java.io.File;
import java.io.IOException;
public class LoadDataController {
......@@ -42,19 +44,21 @@ public class LoadDataController {
this.file = fileChooser.showOpenDialog(stage);
if(file != null) {
filePath.setText(file.getName());
filePath.setText(file.getPath());
}
}
public void validate() {
public void validate() throws IOException {
if (file == null) {
stage.close();
//throw exception
}
loadData();
ClassificationModel.getClassificationModel().loadData(file);
stage.close();
}
......
package fr.univlille.sae.classification.controller;
import fr.univlille.sae.classification.model.ClassificationModel;
import fr.univlille.sae.classification.view.LoadDataView;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.chart.*;
......@@ -17,7 +19,7 @@ public class MainStageController {
Stage stage;
@FXML
CategoryAxis absAxe;
NumberAxis absAxe;
@FXML
NumberAxis ordAxe;
......@@ -34,6 +36,8 @@ public class MainStageController {
@FXML
Button classifyData;
@FXML
ScatterChart scatterChart;
Stage loadStage;
......@@ -43,24 +47,15 @@ public class MainStageController {
* @throws IOException
*/
public void openLoadData() throws IOException {
FXMLLoader loader = new FXMLLoader();
URL fxmlFileUrl = new File(System.getProperty("user.dir") + File.separator + "res" + File.separator + "stages" + File.separator + "load-data-stage.fxml").toURI().toURL();
if (fxmlFileUrl == null) {
System.out.println("Impossible de charger le fichier fxml");
System.exit(-1);
}
loader.setLocation(fxmlFileUrl);
loadStage = loader.load();
LoadDataView loadDataView = new LoadDataView(ClassificationModel.getClassificationModel(), stage);
loadDataView.show();
loadStage.setResizable(false);
loadStage.initOwner(stage);
loadStage.initModality(Modality.APPLICATION_MODAL);
loadStage.setTitle("Chargement des donées");
loadStage.showAndWait();
}
public ScatterChart getScatterChart() {
return this.scatterChart;
}
}
package fr.univlille.sae.classification.model;
import com.opencsv.bean.CsvToBeanBuilder;
import fr.univlille.sae.classification.utils.Observable;
import javax.xml.crypto.Data;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
public class ClassificationModel extends Observable {
......@@ -14,18 +17,42 @@ public class ClassificationModel extends Observable {
private List<LoadableData> datas;
private List<LoadableData> dataToClass;
public ClassificationModel() {
this.datas = new ArrayList<>();
private DataType type;
}
private static ClassificationModel model;
/**
* TODO
* @param x
* @param y
* Renvoie une instance unique du model. Par default le type de ce modele est Iris.
* Modifier en .setType(DataType).
* @return l'instance du model
*/
private void ajouterDonnee(double x, double y) {
public static ClassificationModel getClassificationModel() {
if(model == null) model = new ClassificationModel();
return model;
}
private ClassificationModel() {
this(DataType.IRIS);
}
private ClassificationModel(DataType type) {
this.datas = new ArrayList<>();
this.dataToClass = new ArrayList<>();
this.type = type;
}
/**
* Ajoute un point au nuage de points avec toutes les données de ce point
* @param coords toutes les données du points
* @throws IllegalArgumentException Exception levée si le nombre de parametres est insuffisant pour creer un point du type du model
*/
public void ajouterDonnee(double... coords) {
LoadableData newData = PointFactory.createPoint(type, coords);
this.dataToClass.add(newData);
notifyObservers(newData);
}
......@@ -33,21 +60,50 @@ public class ClassificationModel extends Observable {
* TODO
* @param file
*/
private void loadData(File file) {
public void loadData(File file) throws IOException {
this.datas = new CsvToBeanBuilder<LoadableData>(Files.newBufferedReader(file.toPath()))
.withSeparator(',')
.withType(Iris.class)
.build().parse();
Set<String> types = new HashSet<>();
for (LoadableData d : datas) {
types.add(d.getClassification());
}
LoadableData.setClassificationTypes(types);
notifyObservers();
}
/**
* TODO
* @param data
*/
private void classifierDonnee(LoadableData data) {
public void classifierDonnee(LoadableData data) {
List<String> classes = new ArrayList<>(data.getClassificationTypes());
Random rdm = new Random();
data.setClassification(classes.get(rdm.nextInt(classes.size())));
notifyObservers(data);
}
public void setType(DataType type) {
this.type = type;
}
public List<LoadableData> getDatas() {
return datas;
}
public List<LoadableData> getDataToClass() {
return dataToClass;
}
public DataType getType() {
return type;
}
}
package fr.univlille.sae.classification.model;
public enum DataType {
IRIS;
}
package fr.univlille.sae.classification.model;
import java.util.Set;
import com.opencsv.bean.*;
public class Iris extends LoadableData{
@CsvBindByName(column = "sepal.width")
private double sepalWidth;
@CsvBindByName(column = "sepal.length")
private double sepalLength;
@CsvBindByName(column = "petal.width")
private double petalWidth;
@CsvBindByName(column = "petal.length")
private double petalLength;
public Iris(double sepalWidth, double sepalLength, double petalWidth, double petalLength) {
this(sepalWidth, sepalLength, petalWidth, petalLength, "undefined");
}
public Iris() {
//
}
public Iris(double sepalWidth, double sepalLength, double petalWidth, double petalLength, String classification) {
super();
this.sepalWidth = sepalWidth;
......@@ -25,9 +30,6 @@ public class Iris extends LoadableData{
this.petalLength = petalLength;
}
public double getSepalWidth() {
return sepalWidth;
}
......@@ -43,4 +45,14 @@ public class Iris extends LoadableData{
public double getPetalLength() {
return petalLength;
}
@Override
public String toString() {
return "Iris{" +
"sepalWidth=" + sepalWidth +
", sepalLength=" + sepalLength +
", petalWidth=" + petalWidth +
", petalLength=" + petalLength +
'}';
}
}
......@@ -2,23 +2,28 @@ package fr.univlille.sae.classification.model;
import java.util.Set;
public class LoadableData {
public abstract class LoadableData {
private static Set<String> classificationTypes;
private String classification;
public LoadableData() {
protected LoadableData() {
}
public String getClassification() {
return this.classification;
}
Set<String> getClassificationTypes() {
public static Set<String> getClassificationTypes() {
return classificationTypes;
}
void setClassification(String classification) {
public static void setClassificationTypes(Set<String> classificationTypes) {
LoadableData.classificationTypes = classificationTypes;
}
public void setClassification(String classification) {
this.classification = classification;
}
......
package fr.univlille.sae.classification.model;
public class PointFactory {
public static LoadableData createPoint(DataType type, double[] coords) {
int size = coords.length;
LoadableData data;
switch (type) {
case IRIS:
if(size != 4) throw new IllegalArgumentException();
data = new Iris(coords[0],coords[1],coords[2],coords[3]);
break;
default:
throw new IllegalArgumentException();
}
return data;
}
}
package fr.univlille.sae.classification.view;
import fr.univlille.sae.classification.model.ClassificationModel;
import javafx.fxml.FXMLLoader;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class LoadDataView {
private ClassificationModel model;
private Stage owner;
public LoadDataView(ClassificationModel model, Stage owner) {
this.model = model;
this.owner = owner;
}
public void show() throws IOException {
FXMLLoader loader = new FXMLLoader();
URL fxmlFileUrl = new File(System.getProperty("user.dir") + File.separator + "res" + File.separator + "stages" + File.separator + "load-data-stage.fxml").toURI().toURL();
if (fxmlFileUrl == null) {
System.out.println("Impossible de charger le fichier fxml");
System.exit(-1);
}
loader.setLocation(fxmlFileUrl);
Stage root = loader.load();
root.setResizable(false);
root.initOwner(owner);
root.initModality(Modality.APPLICATION_MODAL);
root.setTitle("Chargement des donées");
root.showAndWait();
}
}
package fr.univlille.sae.classification.view;
import fr.univlille.sae.classification.controller.LoadDataController;
import fr.univlille.sae.classification.controller.MainStageController;
import fr.univlille.sae.classification.model.ClassificationModel;
import fr.univlille.sae.classification.model.DataType;
import fr.univlille.sae.classification.model.Iris;
import fr.univlille.sae.classification.model.LoadableData;
import fr.univlille.sae.classification.utils.Observable;
import fr.univlille.sae.classification.utils.Observer;
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
import java.io.File;
......@@ -14,8 +22,12 @@ import java.net.URL;
public class MainStageView implements Observer {
private ClassificationModel model;
private ScatterChart scatterChart;
public MainStageView(ClassificationModel model) {
this.model = model;
model.attach(this);
......@@ -36,11 +48,28 @@ public class MainStageView implements Observer {
root.setResizable(false);
root.setTitle("SAE3.3 - Logiciel de classification");
root.show();
loader.getController();
MainStageController controller = loader.getController();
scatterChart = controller.getScatterChart();
}
@Override
public void update(Observable observable) {
if(scatterChart == null) throw new IllegalStateException();
if(!(observable instanceof ClassificationModel)) throw new IllegalStateException();
XYChart.Series series1 = new XYChart.Series();
series1.setName("Dice Launch");
scatterChart.getData().add(series1);
for(LoadableData i : model.getDatas()) {
if(model.getType() == DataType.IRIS) {
series1.getData().add(new XYChart.Data<>(((Iris)i).getPetalLength(),
((Iris)i).getPetalWidth()));
}
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment