diff --git a/pom.xml b/pom.xml index 33a6c60..48304fc 100644 --- a/pom.xml +++ b/pom.xml @@ -1,7 +1,6 @@ - + 4.0.0 com.github.weichware10 @@ -75,12 +74,19 @@ ${javafx.version} ${platform} + + + org.openjfx + javafx-swing + 11-ea+24 + + com.github.weichware10 util - v3.2 + v3.3 @@ -306,15 +312,15 @@ coveralls-maven-plugin 4.3.0 - + - javax.xml.bind - jaxb-api - 2.2.3 + javax.xml.bind + jaxb-api + 2.2.3 - + @@ -334,7 +340,7 @@ validate - + diff --git a/src/main/java/github/weichware10/analyse/Main.java b/src/main/java/github/weichware10/analyse/Main.java index 78d93eb..5f972dc 100644 --- a/src/main/java/github/weichware10/analyse/Main.java +++ b/src/main/java/github/weichware10/analyse/Main.java @@ -1,6 +1,7 @@ package github.weichware10.analyse; import github.weichware10.analyse.gui.general.Login; +import github.weichware10.util.Files; import github.weichware10.util.Logger; import github.weichware10.util.db.DataBaseClient; import github.weichware10.util.gui.Log; @@ -33,6 +34,8 @@ public static void main(String[] args) { String logfile = String.format( Dotenv.load().get("LOGS") + "/%s.log", DateTime.now().toString("yMMdd-HHmmss")); Logger.setLogfile(logfile); + // delete temp dir + Runtime.getRuntime().addShutdownHook(Files.deleteTempDir()); launch(args); } diff --git a/src/main/java/github/weichware10/analyse/config/HeatmapConfig.java b/src/main/java/github/weichware10/analyse/config/HeatmapConfig.java index 092ac4c..e97d96c 100644 --- a/src/main/java/github/weichware10/analyse/config/HeatmapConfig.java +++ b/src/main/java/github/weichware10/analyse/config/HeatmapConfig.java @@ -95,4 +95,18 @@ public String toString() { isGrid(), isImage()); } + + /** + * Konvertiert javafx Color in awt Color. + * + * @param color - javafx Color + * @return awt Color + */ + public static java.awt.Color fxToAwtColor(Color color) { + return new java.awt.Color( + (float) color.getRed(), + (float) color.getGreen(), + (float) color.getBlue(), + (float) color.getOpacity()); + } } diff --git a/src/main/java/github/weichware10/analyse/enums/AnalyseType.java b/src/main/java/github/weichware10/analyse/enums/AnalyseType.java index 8887fa0..77deb86 100644 --- a/src/main/java/github/weichware10/analyse/enums/AnalyseType.java +++ b/src/main/java/github/weichware10/analyse/enums/AnalyseType.java @@ -7,6 +7,5 @@ public enum AnalyseType { HEATPMAP, COMPHEATMAP, VERLAUF, - RELFRQIMGAREA, - VIEWTIMEDISTR + RELDEPTHDISTR } diff --git a/src/main/java/github/weichware10/analyse/enums/DiagrammType.java b/src/main/java/github/weichware10/analyse/enums/DiagrammType.java deleted file mode 100644 index 1969cd0..0000000 --- a/src/main/java/github/weichware10/analyse/enums/DiagrammType.java +++ /dev/null @@ -1,9 +0,0 @@ -package github.weichware10.analyse.enums; - -/** - * Enum der möglichen Diagramm-Typen. - */ -public enum DiagrammType { - RELFRQIMGAREA, - VIEWTIMEDISTR -} diff --git a/src/main/java/github/weichware10/analyse/gui/analyse/Analyzer.java b/src/main/java/github/weichware10/analyse/gui/analyse/Analyzer.java index efc3ba4..ba3d5ac 100644 --- a/src/main/java/github/weichware10/analyse/gui/analyse/Analyzer.java +++ b/src/main/java/github/weichware10/analyse/gui/analyse/Analyzer.java @@ -5,10 +5,25 @@ import github.weichware10.analyse.config.HeatmapConfig; import github.weichware10.analyse.enums.AnalyseType; import github.weichware10.analyse.gui.general.MainMenuBar; +import github.weichware10.analyse.logic.Heatmap; +import github.weichware10.analyse.logic.Verlauf; import github.weichware10.util.Logger; import github.weichware10.util.data.TrialData; import github.weichware10.util.gui.AbsScene; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import javafx.application.Platform; +import javafx.embed.swing.SwingFXUtils; import javafx.scene.Parent; +import javafx.scene.SnapshotParameters; +import javafx.scene.chart.LineChart; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.image.WritableImage; +import javafx.stage.FileChooser; +import javafx.stage.FileChooser.ExtensionFilter; +import javax.imageio.ImageIO; /** * Analysefenster der App. @@ -22,6 +37,8 @@ public class Analyzer extends AbsScene { private static TrialData trialComp; private static HeatmapConfig hmConfig = new HeatmapConfig(); private static DiagramConfig diaConfig = new DiagramConfig(); + private static String heatmapImage; + private static LineChart verlaufLineChart; /** * Startet die App. @@ -53,6 +70,12 @@ public static void setAnalyseType(AnalyseType analyseType) { diaConfig = new DiagramConfig(); controller.errorLabel.setVisible(false); + controller.statusLabel.setVisible(false); + controller.exportButton.setDisable(true); + controller.exportRawButton.setDisable(true); + controller.analysePane.getChildren().clear(); + heatmapImage = null; + verlaufLineChart = null; if (Analyzer.analyseType != AnalyseType.COMPHEATMAP) { trialComp = null; @@ -83,9 +106,15 @@ public static void setTrialId() { controller.analyseButton.setDisable(true); controller.selectCompTrialButton.setVisible(false); controller.analyseTypMenuButton.setText("Analyse-Typ"); + controller.errorLabel.setVisible(false); + controller.statusLabel.setVisible(false); + controller.exportButton.setDisable(true); + controller.exportRawButton.setDisable(true); + controller.analysePane.getChildren().clear(); + heatmapImage = null; + verlaufLineChart = null; hmConfig = new HeatmapConfig(); diaConfig = new DiagramConfig(); - controller.errorLabel.setVisible(false); } controller.analyseTypMenuButton.setDisable(false); Logger.info("trialId set to " + trial.trialId); @@ -104,7 +133,7 @@ public static void setTrialIdComp() { } else { controller.analyseButton.setDisable(true); controller.errorLabel.setText( - "Trial und Vergleichs-Trial sind identisch. Wähle ein anderes Vergleichs-Trial!"); + "Trial & Vergleichs-Trial sind identisch. Wähle ein anderes Vergleichs-Trial!"); controller.errorLabel.setVisible(true); } } @@ -113,12 +142,14 @@ public static void setTrialIdComp() { * Setzt die Konfiguration für Heatmap bzw. Diagramm Analyse */ public static void setConfig() { + controller.errorLabel.setVisible(false); + controller.statusLabel.setVisible(false); + if (analyseType == AnalyseType.COMPHEATMAP || analyseType == AnalyseType.HEATPMAP) { HeatmapConfigurator.start(hmConfig); Logger.info("Start Heatmap Configurator"); - } else if (analyseType == AnalyseType.RELFRQIMGAREA - || analyseType == AnalyseType.VIEWTIMEDISTR) { + } else if (analyseType == AnalyseType.RELDEPTHDISTR) { DiagramConfigurator.start(diaConfig, analyseType); Logger.info("Start Diagram Configurator"); } @@ -136,6 +167,157 @@ public static void analyse() { hmConfig.toString(), diaConfig.toString()); Logger.info(output); + + controller.statusLabel.setVisible(false); + + switch (analyseType) { + case HEATPMAP: + heatmapImage = Heatmap.createHeatmap(trial, hmConfig); + break; + case COMPHEATMAP: + heatmapImage = Heatmap.createHeatmapComp(trial, trialComp, hmConfig); + break; + case VERLAUF: + verlaufLineChart = Verlauf.createVerlauf(trial); + break; + case RELDEPTHDISTR: + break; + default: + // sollte niemals eintreten + break; + } + if (heatmapImage != null) { + // Pane leeren + controller.analysePane.getChildren().clear(); + + // ImageView mit Heatmap setzen + ImageView imageView = new ImageView(new Image(heatmapImage)); + imageView.setFitWidth(680.0f); + imageView.setFitHeight(405.0f); + imageView.setPreserveRatio(true); + controller.analysePane.getChildren().addAll(imageView); + + // Export aktivieren + controller.exportButton.setDisable(false); + controller.exportRawButton.setDisable(false); + } else if (verlaufLineChart != null) { + // Pane leeren + controller.analysePane.getChildren().clear(); + + // LineChart vom Verlauf setzen + controller.analysePane.getChildren().addAll(verlaufLineChart); + + // Export aktivieren + controller.exportButton.setDisable(false); + controller.exportRawButton.setDisable(false); + } + } + + /** + * Exportiert erstelltes Bild oder Diagramm. + */ + public static void export() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Analyse Bild speichern unter"); + fileChooser.getExtensionFilters().add( + new ExtensionFilter("PNG Image", "*.png")); + + // Dateipfad als String speichern + String location = fileChooser.showSaveDialog(Main.primaryStage).getAbsolutePath(); + if (location != null) { + if (analyseType.equals(AnalyseType.HEATPMAP)) { + Thread taskThread = new Thread(new Runnable() { + @Override + public void run() { + if (saveImage(location)) { + Platform.runLater(() -> controller.setExportStatus( + String.format("Bild unter %s gesepeichert", location))); + } else { + Platform.runLater(() -> controller.setExportStatus( + "Bild konnte nicht gespeichert werden")); + } + } + }); + taskThread.start(); + } else if (analyseType.equals(AnalyseType.VERLAUF)) { + if (saveAsPng(location)) { + controller.setExportStatus(String.format( + "Diagramm unter %s gesepeichert", location)); + } else { + controller.setExportStatus("Diagramm konnte nicht gespeichert werden"); + } + } + } + } + + // TODO: in util verschieben?(ja -> analysedImage als Paramter dazu) + /** + * Speichert Bild unter location. + * + * @param location - Speicherort + * @return Erfolgsboolean + */ + private static boolean saveImage(String location) { + BufferedImage image = null; + try { + image = ImageIO.read(new File(heatmapImage)); + ImageIO.write(image, "png", new File(location)); + } catch (IOException e) { + Logger.error("Failed to save image", e, true); + return false; + } + Logger.info("Image saved: " + location); + return true; + } + + // TODO: in util verschieben? + /** + * Speichert Diagramm als Bild unter location. + * + * @param location - Speicherort + * @return Erfolgsboolean + */ + private static boolean saveAsPng(String location) { + WritableImage image = controller.analysePane.snapshot(new SnapshotParameters(), null); + + File file = new File(location); + + try { + ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", file); + } catch (IOException e) { + Logger.error("Failed to save Snapshot", e, true); + return false; + } + return true; + } + + /** + * Export von Raw-Daten des Versuchs. + */ + public static void exportRaw() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Versuchs-Daten speichern unter"); + fileChooser.getExtensionFilters().add( + new ExtensionFilter("JSON File", "*.json")); + + // Dateipfad als String speichern und json laden + String location = fileChooser.showSaveDialog(Main.primaryStage).getAbsolutePath(); + if (location != null) { + Thread taskThread = new Thread(new Runnable() { + @Override + public void run() { + if (TrialData.toJson(location, trial)) { + Platform.runLater(() -> controller.setExportStatus( + String.format("Versuchs-Daten unter %s gespeichert", location))); + } else { + Platform.runLater(() -> controller.setExportStatus( + "Versuchs-Daten konnten nicht gespeichert werden")); + } + } + }); + taskThread.start(); + } + } /** @@ -148,10 +330,17 @@ public static void reset() { controller.exportButton.setDisable(true); controller.exportRawButton.setDisable(true); controller.selectCompTrialButton.setVisible(false); + controller.exportButton.setDisable(true); + controller.exportRawButton.setDisable(true); + controller.errorLabel.setVisible(false); + controller.statusLabel.setVisible(false); + controller.analysePane.getChildren().clear(); Analyzer.analyseType = null; Analyzer.trial = null; Analyzer.trialComp = null; Analyzer.hmConfig = new HeatmapConfig(); Analyzer.diaConfig = new DiagramConfig(); + Analyzer.heatmapImage = null; + Analyzer.verlaufLineChart = null; } } diff --git a/src/main/java/github/weichware10/analyse/gui/analyse/AnalyzerController.java b/src/main/java/github/weichware10/analyse/gui/analyse/AnalyzerController.java index 6592274..fde23df 100644 --- a/src/main/java/github/weichware10/analyse/gui/analyse/AnalyzerController.java +++ b/src/main/java/github/weichware10/analyse/gui/analyse/AnalyzerController.java @@ -8,6 +8,7 @@ import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.MenuButton; +import javafx.scene.layout.StackPane; /** * Kontroller für {@link Analyzer}. @@ -17,6 +18,8 @@ public class AnalyzerController extends AbsSceneController { @FXML protected Button analyseButton; @FXML + protected StackPane analysePane; + @FXML protected MenuButton analyseTypMenuButton; @FXML protected Button backButton; @@ -30,10 +33,11 @@ public class AnalyzerController extends AbsSceneController { protected Button exportRawButton; @FXML protected Button selectCompTrialButton; + @FXML + protected Label statusLabel; @FXML protected void analyze(ActionEvent event) { - // TODO: Analyse Analyzer.analyse(); } @@ -44,12 +48,12 @@ void back(ActionEvent event) { @FXML void export(ActionEvent event) { - // TODO: Export + Analyzer.export(); } @FXML void exportRaw(ActionEvent event) { - // TODO: ExportRaw + Analyzer.exportRaw(); } @FXML @@ -86,12 +90,12 @@ protected void setHeatmap(ActionEvent event) { } @FXML - protected void setRelFrqImgArea(ActionEvent event) { + protected void setRelDepthDistr(ActionEvent event) { configButton.setDisable(false); analyseButton.setDisable(false); selectCompTrialButton.setVisible(false); - Analyzer.setAnalyseType(AnalyseType.RELFRQIMGAREA); - analyseTypMenuButton.setText("Häufigkeitsverteilung Bildbereiche"); + Analyzer.setAnalyseType(AnalyseType.RELDEPTHDISTR); + analyseTypMenuButton.setText("Verteilung Relative Tiefe"); } @FXML @@ -103,13 +107,9 @@ protected void setVerlauf(ActionEvent event) { analyseTypMenuButton.setText("Verlauf"); } - @FXML - protected void setViewTimeDistr(ActionEvent event) { - configButton.setDisable(false); - analyseButton.setDisable(false); - selectCompTrialButton.setVisible(false); - Analyzer.setAnalyseType(AnalyseType.VIEWTIMEDISTR); - analyseTypMenuButton.setText("Verteilung Betrachtungszeit"); + protected void setExportStatus(String text) { + statusLabel.setText(text); + statusLabel.setVisible(true); } @FXML @@ -120,6 +120,8 @@ protected void initialize() { : "fx:id=\"analyseTypMenuButton\" not injected: check 'Analyzer.fxml'."; assert backButton != null : "fx:id=\"backButton\" was injected: check 'Analyzer.fxml'."; + assert analysePane != null + : "fx:id=\"chartPane\" was injected: check 'Analyzer.fxml'."; assert configButton != null : "fx:id=\"configButton\" not injected: check 'Analyzer.fxml'."; assert errorLabel != null @@ -130,6 +132,8 @@ protected void initialize() { : "fx:id=\"exportRawButton\" not injected: check 'Analyzer.fxml'."; assert selectCompTrialButton != null : "fx:id=\"selectCompTrialButton\" not injected: check 'Analyzer.fxml'."; + assert statusLabel != null + : "fx:id=\"statusLabel\" not injected: check 'Analyzer.fxml'."; } } diff --git a/src/main/java/github/weichware10/analyse/gui/analyse/DiagramConfigurator.java b/src/main/java/github/weichware10/analyse/gui/analyse/DiagramConfigurator.java index e3ef6dd..28636d6 100644 --- a/src/main/java/github/weichware10/analyse/gui/analyse/DiagramConfigurator.java +++ b/src/main/java/github/weichware10/analyse/gui/analyse/DiagramConfigurator.java @@ -37,45 +37,20 @@ public static void start(DiagramConfig diaConfig, AnalyseType analyseType) { stage.getIcons().add( new Image(Main.class.getResource("app-icon.png").toString())); - InitResult ir = - initialize(DiagramConfigurator.class.getResource("DiagramConfigurator.fxml")); + InitResult ir = initialize( + DiagramConfigurator.class.getResource("DiagramConfigurator.fxml")); root = (VBox) ir.root; controller = (DiagramConfiguratorController) ir.controller; - // Sichtbarkeitseinstellungen - if (analyseType == AnalyseType.RELFRQIMGAREA) { - controller.amountAreas.setDisable(false); - controller.minTimeSlider.setDisable(true); - controller.maxTimeSlider.setDisable(true); - controller.stepsSlider.setDisable(true); - controller.minTimeLabel.setVisible(false); - controller.maxTimeLabel.setVisible(false); - controller.stepsLabel.setVisible(false); - } else if (analyseType == AnalyseType.VIEWTIMEDISTR) { - controller.amountAreas.setDisable(true); - controller.minTimeSlider.setDisable(false); - controller.maxTimeSlider.setDisable(false); - controller.stepsSlider.setDisable(false); - controller.minTimeLabel.setVisible(true); - controller.maxTimeLabel.setVisible(true); - controller.stepsLabel.setVisible(true); - } - // Beim erneueten Aufrufen bereits gesetzte Konfiguration wieder setzten - controller.initAmountBox(diaConfig.getAmountAreas()); controller.initMinTimeSlider(diaConfig.getMinTime()); controller.initMaxTimeSlider(diaConfig.getMaxTime()); controller.initStepsSlider(diaConfig.getStepsBetween()); - final Button ok = (Button) configDialog.getDialogPane().lookupButton(applyButtonType); ok.addEventFilter(ActionEvent.ACTION, applyEvent -> { - if (analyseType == AnalyseType.RELFRQIMGAREA) { - diaConfig.setAmountAreas(controller.getAmountAreas()); - } else if (analyseType == AnalyseType.VIEWTIMEDISTR) { - diaConfig.setNewTime(controller.getMinTime(), controller.getMaxTime()); - diaConfig.setStepsBetween(controller.getSteps()); - } + diaConfig.setNewTime(controller.getMinTime(), controller.getMaxTime()); + diaConfig.setStepsBetween(controller.getSteps()); }); configDialog.getDialogPane().setContent(root); configDialog.showAndWait(); diff --git a/src/main/java/github/weichware10/analyse/gui/analyse/DiagramConfiguratorController.java b/src/main/java/github/weichware10/analyse/gui/analyse/DiagramConfiguratorController.java index cf44e5f..df7dc35 100644 --- a/src/main/java/github/weichware10/analyse/gui/analyse/DiagramConfiguratorController.java +++ b/src/main/java/github/weichware10/analyse/gui/analyse/DiagramConfiguratorController.java @@ -5,7 +5,6 @@ import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; -import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.Slider; @@ -18,8 +17,6 @@ public class DiagramConfiguratorController extends AbsSceneController { private double minTime; private int steps; @FXML - protected ComboBox amountAreas; - @FXML protected Slider maxTimeSlider; @FXML protected Label maxTimeLabel; @@ -32,13 +29,6 @@ public class DiagramConfiguratorController extends AbsSceneController { @FXML protected Label stepsLabel; - protected void initAmountBox(int current) { - for (Integer i = 2; i <= 8; i += 2) { - amountAreas.getItems().add(i); - } - amountAreas.setValue(current); - } - protected void initMaxTimeSlider(double current) { maxTimeSlider.adjustValue(current); } @@ -51,10 +41,6 @@ protected void initStepsSlider(int current) { stepsSlider.adjustValue(current); } - public int getAmountAreas() { - return amountAreas.getValue(); - } - public double getMaxTime() { return maxTime; } @@ -69,8 +55,6 @@ public int getSteps() { @FXML protected void initialize() { - assert amountAreas != null - : "fx:id=\"amountAreas\" not injected: check 'DiagramConfigurator.fxml'."; assert maxTimeSlider != null : "fx:id=\"maxTime\" not injected: check 'DiagramConfigurator.fxml'."; assert maxTimeLabel != null diff --git a/src/main/java/github/weichware10/analyse/logic/Analyse.java b/src/main/java/github/weichware10/analyse/logic/Analyse.java index 450e7fd..99cdb23 100644 --- a/src/main/java/github/weichware10/analyse/logic/Analyse.java +++ b/src/main/java/github/weichware10/analyse/logic/Analyse.java @@ -1,31 +1,110 @@ package github.weichware10.analyse.logic; -import github.weichware10.util.data.TrialData; +import github.weichware10.util.Files; +import github.weichware10.util.Logger; +import github.weichware10.util.data.DataPoint; +import java.io.IOException; import java.util.List; +import javafx.geometry.Rectangle2D; /** * beinhaltet Methoden die zur Analyse benötigt werden. */ -public abstract class Analyse { +public class Analyse { /** - * berechnet die relativen Häufigkeiten der Bildkoordinaten. + * Speichert das Versuchs-Bild. * - * @return Liste mit den relativen Häufigkeiten der Bildkoordinaten + * @param imageUrl - URL des Versuchs-Bilds + * @return Pfad zum gespeicherten Bild */ - protected List> calcRelFreq(TrialData data) { - return null; + public static String saveImage(String imageUrl) { + String imageLocation = null; + try { + imageLocation = Files.saveImage(imageUrl); + } catch (IllegalArgumentException | IOException e) { + Logger.error("Failed to save img", e, true); + } + return imageLocation; } /** - * erstellt eine Tabelle mit den Zeitpunkten und dazugehörigen Bildkoordinaten - * bzw. Zoomstärken + * Berechnet maximale Tiefe eines ZoomMaps-Versuchs. * - * @return Tabelle mit Zeitpunkten und dazugehörigen Bildkoordinaten bzw. - * Zoomstärken + * @param data - Datenpunkte des Versuchs + * @param width - Breite des verwendeten Bilds + * @param height - Höhe des verwendeten Bilds + * @return maximale Tiefe des Versuchs */ - protected List> createTimeTable(TrialData data) { - return null; + protected static double findMaxDepth(List data, double width, double height) { + double maxDepth = Float.MAX_VALUE; + for (DataPoint dataPoint : data) { + double currentWidth = dataPoint.viewport.getWidth(); + double currentHeight = dataPoint.viewport.getHeight(); + double depth = (currentWidth * currentHeight) / (width * height); + if (depth < maxDepth) { + maxDepth = depth; + } + } + return java.lang.Math.log10(1 / maxDepth); } + /** + * Berechnet die relative Tiefe eines Datenpunktes (ZoomMaps). + * + * @param viewport - Viewport des Datenpunktes + * @param imageWidth - Breite Versuchs-Bild + * @param imageHeight - Höhe Versuchs-Bild + * @param maxDepth - maximale Tiefe des Versuchs + * @return relative Tiefe des Datenpunkts + */ + protected static double calcRelDepthZm(Rectangle2D viewport, double imageWidth, + double imageHeight, double maxDepth) { + // Tiefe berechnen + double currentWidth = viewport.getWidth(); + double currentHeight = viewport.getHeight(); + double tempRelDepth = 1 / ((currentWidth * currentHeight) / (imageWidth * imageHeight)); + + // Logarithmus der Tiefe für linearen Verlauf + tempRelDepth = java.lang.Math.log10(tempRelDepth); + + // relative Tiefe berechnen + double relDepth = tempRelDepth / maxDepth; + + return relDepth; + } + + /** + * Berechnet die relative Tiefe eines Datenpunktes (CodeCharts). + * + * @param depth - Tiefe des Datenpunktes + * @param maxDepth - Maximale Tiefe des Versuchs + * @return relative Tiefe des Datenpunkts + */ + protected static double calcRelDepthCc(double depth, double maxDepth) { + return depth / maxDepth; + } + + /** + * Berechnet die Tiefe eines Viewports. Falls min-/maxDepth nicht gegeben sind, + * wird die unkorrigierte Tiefe zurückgegeben (Zur Berechnug von min / max Depth) + * + * @param dataPoint - der betroffene DataPoint + * @param imageWidth - die Größe des Bildes + * @param imageHeight - die Höhe des Bildes + * @param minDepth - die minimale Tiefe, oder {@code null}, falls diese berechnet werden soll + * @param maxDepth - die maximale Tiefe, oder {@code null}, falls diese berechnet werden soll + * @return die berechnete Tiefe + */ + protected static double calculateDepth(DataPoint dataPoint, + double imageWidth, double imageHeight, Double minDepth, Double maxDepth) { + double localDepth = 1 - ((dataPoint.viewport.getWidth() * dataPoint.viewport.getHeight()) + / (imageWidth * imageHeight)); + + if (minDepth == null || maxDepth == null) { + return localDepth; + } else { + return (Math.pow(100, (localDepth - minDepth) / (maxDepth - minDepth)) - 1) / 99; + } + } } diff --git a/src/main/java/github/weichware10/analyse/logic/Client.java b/src/main/java/github/weichware10/analyse/logic/Client.java deleted file mode 100644 index 61e4f30..0000000 --- a/src/main/java/github/weichware10/analyse/logic/Client.java +++ /dev/null @@ -1,162 +0,0 @@ -package github.weichware10.analyse.logic; - -import github.weichware10.analyse.config.DiagramConfig; -import github.weichware10.analyse.config.HeatmapConfig; -import github.weichware10.analyse.enums.AnalyseType; -import github.weichware10.util.ToolType; -import github.weichware10.util.data.TrialData; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.joda.time.DateTime; - -/** - * Grundlegende Klasse für den Analyse-Client. - */ -@SuppressWarnings("unused") -public class Client { - private TrialData data; - private TrialData dataForComp; - private List analyseTypes; - private List analyzedData; - private HeatmapConfig confHm; - private DiagramConfig confDia; - - public Client() { - analyseTypes = new ArrayList(); - analyzedData = new ArrayList(); - } - - /** - * setzt die ausgewählten Analyse-Typen. - * - * @param selectedAnalyseTypes - die ausgewählten Analyse-Typen - * @return - * true, falls Analyse-Typen gesetzt wurden; - * false, falls keine Analyse-Typen gesetzt wurden - */ - public boolean setAnalyseTypes(List selectedAnalyseTypes) { - if (selectedAnalyseTypes.isEmpty()) { - return false; - } - this.analyseTypes = selectedAnalyseTypes; - return true; - } - - /** - * gibt die Anaylse-Typen zurück. - * - * @return Analyse-Typen - */ - public List getAnalyseTypes() { - return this.analyseTypes; - } - - /** - * holt die angeforderten Daten vom Speichermedium, falls diese existieren. - * - * @param start - Startzeitpunkt der benötigten Daten - * @param end - Endzeitpunkt der benötigten Daten - * @param dataType - Tool-Typ der benötigten Daten - * @return - * true, falls benötigte Daten gefunden und gesetzt wurden; - * false, falls benötigte Daten nicht gefunden wurden - */ - public boolean getData(DateTime start, DateTime end, ToolType dataType) { - if (start.isAfter(end) || start.isAfter(DateTime.now()) || end.isAfter(DateTime.now())) { - return false; - } - - // * Provisorische Lösung für Test, da er sonst nicht funktionieren würde - int amountData = 3; - List dataStart = new ArrayList(Arrays.asList( - new DateTime(2021, 11, 28, 16, 0, 0), new DateTime(2021, 11, 28, 15, 0, 0), - new DateTime(2021, 11, 28, 19, 0, 0))); - List dataEnd = new ArrayList(Arrays.asList( - new DateTime(2021, 11, 28, 19, 0, 0), new DateTime(2021, 11, 28, 18, 0, 0), - new DateTime(2021, 11, 28, 20, 0, 0))); - List dataToolType = new ArrayList(Arrays.asList( - ToolType.ZOOMMAPS, ToolType.EYETRACKING, ToolType.CODECHARTS)); - - for (int id = 0; id < amountData; id++) { - if (dataStart.get(id).equals(start) && dataEnd.get(id).equals(end) - && dataToolType.get(id).equals(dataType)) { - return true; - } - } - return false; - } - - /** - * holt die angeforderten Daten zum Vergleich vom Speichermedium, falls diese - * existieren. - * - * @param start - Startzeitpunkt der benötigten Daten zum Vergleich - * @param end - Endzeitpunkt der benötigten Daten zum Vergleich - * @return - * true, falls benötigte Daten zum Vergleich gefunden und gesetzt - * wurden; - * false, falls benötigte Daten zum Vergleich nicht gefunden wurden - */ - public boolean getDataForComp(DateTime start, DateTime end, ToolType dataType) { - if (start.isAfter(end) || start.isAfter(DateTime.now()) || end.isAfter(DateTime.now())) { - return false; - } - return true; - } - - /** - * verändert die Standard-Konfiguration für die Heatmap-Analyse. - * - * @param confHm - Konfiguration der Heatmap-Analyse - */ - public void setConfigAnalyseHm(HeatmapConfig confHm) { - this.confHm = confHm; - } - - /** - * verändert die Standard-Konfiguration für die Diagramm-Analyse. - * - * @param confDia - Konfiguration der Diagramm-Analyse - */ - public void setConfigAnalyseDia(DiagramConfig confDia) { - this.confDia = confDia; - } - - /** - * führt die ausgewählten Analysen durch. - */ - public void analyseData() { - ; - } - - /** - * zeigt die analysierten Daten an. - */ - public void displayAnalyzedData() { - ; - } - - /** - * exportiert die analysierten Daten. - * - * @return - * true, wenn Export erfolreich war; - * false, wenn Fehler aufgetreten ist - */ - public boolean export() { - return true; - } - - /** - * exportiert die rohen Daten der Analyse. - * - * @return - * true, wenn Export erfolreich war; - * false, wenn Fehler aufgetreten ist - */ - public boolean exportRaw() { - return true; - } - -} diff --git a/src/main/java/github/weichware10/analyse/logic/Diagram.java b/src/main/java/github/weichware10/analyse/logic/Diagram.java index 734a348..1449232 100644 --- a/src/main/java/github/weichware10/analyse/logic/Diagram.java +++ b/src/main/java/github/weichware10/analyse/logic/Diagram.java @@ -1,7 +1,6 @@ package github.weichware10.analyse.logic; import github.weichware10.analyse.config.DiagramConfig; -import github.weichware10.analyse.enums.DiagrammType; import github.weichware10.util.data.TrialData; import java.util.List; @@ -24,16 +23,6 @@ public Diagram(TrialData data, DiagramConfig confDia) { this.confDia = confDia; } - /** - * erstellt Diagramm, welches die relativen Häufigkeiten pro Bildbereich - * darstellt. - * - * @return Pfad des Bildes des erstellten Diagramms - */ - public String createRelFreqImgArea() { - return drawDiagramm(null, DiagrammType.RELFRQIMGAREA); - } - /** * erstellt Diagramm, welches die relativen Häufigkeiten der Blickedauer bzw. * Zoomstärken darstellt. @@ -41,7 +30,7 @@ public String createRelFreqImgArea() { * @return Pfad des Bildes des erstellten Diagramms */ public String createViewTimeDistr() { - return drawDiagramm(null, DiagrammType.VIEWTIMEDISTR); + return drawDiagramm(null); } /** @@ -62,9 +51,8 @@ private List calcViewTimeDistr(List> timeTableData) { * @param type - Diagramm-Typ des zu erstellenden Diagramms * @return Pfad des Bildes des erstellten Diagramms */ - private String drawDiagramm(List diagrammData, DiagrammType type) { - return ".../diagramm/" + type + "_" + this.data.toolType.toString() - + "_" + this.data.trialId + ".jpg"; + private String drawDiagramm(List diagrammData) { + return null; } } diff --git a/src/main/java/github/weichware10/analyse/logic/Heatmap.java b/src/main/java/github/weichware10/analyse/logic/Heatmap.java index 3ae1af1..0b8501f 100644 --- a/src/main/java/github/weichware10/analyse/logic/Heatmap.java +++ b/src/main/java/github/weichware10/analyse/logic/Heatmap.java @@ -1,50 +1,145 @@ package github.weichware10.analyse.logic; +import github.weichware10.analyse.Main; import github.weichware10.analyse.config.HeatmapConfig; +import github.weichware10.util.Files; +import github.weichware10.util.Logger; +import github.weichware10.util.config.Configuration; +import github.weichware10.util.data.DataPoint; import github.weichware10.util.data.TrialData; +import java.awt.AlphaComposite; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; +import javax.imageio.ImageIO; /** * verantwortlich für die Erstellung der Heatmap. */ -@SuppressWarnings("unused") -public class Heatmap extends Analyse { - private final TrialData data; - private final HeatmapConfig confHm; - private List> heatmap; +public class Heatmap { + + private static DataPointComparator comparator = new DataPointComparator(); + private static final float ALPHA = .75f; /** - * verantwortlich für die Erstellung der Heatmap. + * Erstellt Heatmap. * - * @param data - Daten die zur Erstellung der Heatmaps benötigt werden - * @param confHm - Konfiguration der Heatmap + * @param hmConfig - Konfiguration + * @return ? */ - public Heatmap(TrialData data, HeatmapConfig confHm) { - this.data = data; - this.confHm = confHm; + public static String createHeatmap(TrialData trial, HeatmapConfig hmConfig) { + Configuration config = Main.dataBaseClient.configurations.get(trial.configId); + + // Bild laden für Bildbreite und -höhe + String sourceImgLocation = Analyse.saveImage(config.getImageUrl()); + BufferedImage image = null; + try { + image = ImageIO.read(new File(sourceImgLocation)); + } catch (Exception e) { + Logger.error("Failed to save the image", e, true); + return null; + } + int width = (int) image.getWidth(); + int height = (int) image.getHeight(); + + // Heatmap Bild erstellen + BufferedImage heatmap = null; + + heatmap = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D heatmapGraphics = heatmap.createGraphics(); + heatmapGraphics.setBackground(new java.awt.Color(1, 1, 1, 0)); + heatmapGraphics.clearRect(0, 0, width, height); + + // so sortieren, dass immer weiter reingezoomt wird + List sortedDataPoints = trial.getData().stream() + .sorted(comparator).collect(Collectors.toList()); + + createHeatmapFromData(heatmapGraphics, hmConfig, sortedDataPoints, width, height); + heatmapGraphics.dispose(); + + // Generierte Heatmap-Bild speichern + String heatmapLocation = null; + try { + heatmapLocation = Files.saveGeneratedImage(heatmap, "HEATMAP" + trial.trialId + ".png"); + } catch (IllegalArgumentException | IOException e) { + Logger.error("Failed to save the image", e, true); + } + + if (image == null || !hmConfig.isImage()) { + return heatmapLocation; + } + + Graphics2D imageGraphics = image.createGraphics(); + AlphaComposite acomp = AlphaComposite.getInstance( + AlphaComposite.SRC_OVER, ALPHA); + imageGraphics.setComposite(acomp); + imageGraphics.drawImage(heatmap, 0, 0, null); + imageGraphics.dispose(); + + String imgLocation = null; + try { + imgLocation = Files.saveGeneratedImage(image, "IMGHEATMAP" + trial.trialId + ".png"); + } catch (IllegalArgumentException | IOException e) { + Logger.error("Failed to save the image", e, true); + } + + return imgLocation; + } + + private static void createHeatmapFromData(Graphics2D graphic, HeatmapConfig hmConfig, + List sortedDataPoints, double imageWidth, double imageHeight) { + + if (sortedDataPoints.size() == 0) { + return; + } + + DataPoint minDataPoint = sortedDataPoints.get(0); + DataPoint maxDataPoint = sortedDataPoints.get(sortedDataPoints.size() - 1); + + double minDepth = Analyse.calculateDepth(minDataPoint, imageWidth, imageHeight, null, null); + double maxDepth = Analyse.calculateDepth(maxDataPoint, imageWidth, imageHeight, null, null); + + + for (DataPoint dataPoint : sortedDataPoints) { + double relDepth = Analyse.calculateDepth( + dataPoint, imageWidth, imageHeight, minDepth, maxDepth); + graphic.setColor(HeatmapConfig.fxToAwtColor(hmConfig.getMinColorDiff() + .interpolate(hmConfig.getMaxColorDiff(), relDepth))); + graphic.fillRect( + (int) dataPoint.viewport.getMinX(), + (int) dataPoint.viewport.getMinY(), + (int) dataPoint.viewport.getWidth(), + (int) dataPoint.viewport.getHeight()); + } } /** - * erstellt die Heatmap, welche die relativen Häufigkeiten der betrachteten - * Bildkoordinaten darstellt. + * Erstellt Heatmap-Vergleich. * - * @return Pfad des Bildes der erstellten Heatmap + * @param trial - 1. Versuch + * @param trialComp - 2. Versuch + * @param hmConfig - Konfiguration + * @return ? */ - public String createHeatmap() { - return ".../heatmap/HEATMAP_" + this.data.toolType.toString() - + "_" + this.data.trialId + ".jpg"; + public static String createHeatmapComp(TrialData trial, + TrialData trialComp, HeatmapConfig hmConfig) { + return null; } /** - * vergleicht zwei Heatmaps und erstellt aus dem Vergleich eine Heatmap. - * - * @param heatmap1 - erste Heatmap für den Vergleich - * @param heatmap2 - zweite Heatmap für den Vergleich - * @return Pfad des Bildes der erstellten Heatmap + * DataPointComparator. */ - public static String compHeatmaps(Heatmap heatmap1, Heatmap heatmap2) { - return ".../heatmap/COMPHEATMAP_" + heatmap1.data.toolType.toString() - + "_" + heatmap1.data.trialId + "_" + heatmap2.data.toolType.toString() - + "_" + heatmap2.data.trialId + ".jpg"; + private static class DataPointComparator implements Comparator { + + @Override + public int compare(DataPoint dp1, DataPoint dp2) { + int area1 = (int) (dp1.viewport.getWidth() * dp1.viewport.getHeight()); + int area2 = (int) (dp2.viewport.getWidth() * dp2.viewport.getHeight()); + return area2 - area1; + } } } diff --git a/src/main/java/github/weichware10/analyse/logic/Verlauf.java b/src/main/java/github/weichware10/analyse/logic/Verlauf.java index 1281d64..89f8d16 100644 --- a/src/main/java/github/weichware10/analyse/logic/Verlauf.java +++ b/src/main/java/github/weichware10/analyse/logic/Verlauf.java @@ -1,42 +1,109 @@ package github.weichware10.analyse.logic; +import github.weichware10.analyse.Main; +import github.weichware10.util.Logger; +import github.weichware10.util.ToolType; +import github.weichware10.util.config.Configuration; +import github.weichware10.util.data.DataPoint; import github.weichware10.util.data.TrialData; +import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; +import javafx.scene.chart.LineChart; +import javafx.scene.chart.NumberAxis; +import javafx.scene.chart.XYChart; +import javafx.scene.image.Image; /** * verantwortlich für die Erstellung des Verlauf-Diagramms. */ -public class Verlauf extends Analyse { - private final TrialData data; +public class Verlauf { - /** - * verantwortlich für die Erstellung des Verlauf-Diagramms. - * - * @param data - Daten die zur Erstellung des Diagramms benötigt werden - */ - public Verlauf(TrialData data) { - this.data = data; - } + private static DataPointComparator comparator = new DataPointComparator(); /** - * erstellt Diagramm, welches den Verlauf der betrachteten Bildkoordinaten - * darstellt. + * Erstellt Verlauf-Liniendiagramm. * - * @return Pfad des Bildes des erstellten Diagramms + * @param trial - Versuch */ - public String createVerlauf() { - return drawDiagramm(null); + public static LineChart createVerlauf(TrialData trial) { + Configuration config = Main.dataBaseClient.configurations.get(trial.configId); + + // Bild laden für Bildbreite und -höhe + String imageUrl = Analyse.saveImage(config.getImageUrl()); + Image image = null; + try { + image = new Image(imageUrl); + } catch (Exception e) { + Logger.error("Failed to save the image", e, true); + return null; + } + + int width = (int) image.getWidth(); + int height = (int) image.getHeight(); + + // Liniendiagramm erstellen + final NumberAxis xAxis = new NumberAxis(); + final NumberAxis yAxis = new NumberAxis(); + if (trial.toolType == ToolType.ZOOMMAPS) { + xAxis.setLabel("Zeit in ms"); + } else { + xAxis.setLabel("Zeit in s"); + } + yAxis.setLabel("relDepth"); + final LineChart lineChart = new LineChart(xAxis, yAxis); + lineChart.setTitle(String.format("Verlauf %s", trial.toolType.toString())); + + // Graph für Liniendiagramm erstellen + XYChart.Series series = new XYChart.Series(); + series.setName(trial.getTrialId()); + + if (trial.toolType == ToolType.ZOOMMAPS) { + lineChart.setCreateSymbols(false); + + // so sortieren, dass immer weiter reingezoomt wird + List sortedDataPoints = trial.getData().stream() + .sorted(comparator).collect(Collectors.toList()); + // Maximale Tiefe ermitteln + DataPoint minDataPoint = sortedDataPoints.get(0); + DataPoint maxDataPoint = sortedDataPoints.get(sortedDataPoints.size() - 1); + + double minDepth = Analyse.calculateDepth(minDataPoint, width, height, null, null); + double maxDepth = Analyse.calculateDepth(maxDataPoint, width, height, null, null); + for (DataPoint dataPoint : trial.getData()) { + // Relative Tiefe berechnen + double relDepth = Analyse.calculateDepth( + dataPoint, width, height, minDepth, maxDepth); + + // Punkt im Diagramm setzen + series.getData().add( + new XYChart.Data(dataPoint.timeOffset, relDepth)); + } + } else if (trial.toolType == ToolType.CODECHARTS) { + for (DataPoint dataPoint : trial.getData()) { + // Relative Tiefe berechnen + double relDepth = Analyse.calcRelDepthCc((double) dataPoint.depth, + (double) config.getCodeChartsConfiguration().getMaxDepth()); + + // Punkt im Diagramm setzen + series.getData().add( + new XYChart.Data(dataPoint.timeOffset / 1000, relDepth)); + } + } + lineChart.getData().add(series); + return lineChart; } /** - * zeichnet das Diagramm. - * - * @param diagrammData - Daten die zur Erstellung des Diagramms benötigt werden - * @return Pfad des Bildes des erstellten Diagramms + * DataPointComparator. */ - private String drawDiagramm(List> diagrammData) { - return ".../verlauf/VERLAUF_" + this.data.toolType.toString() - + "_" + this.data.trialId + ".jpg"; - } + private static class DataPointComparator implements Comparator { + @Override + public int compare(DataPoint dp1, DataPoint dp2) { + int area1 = (int) (dp1.viewport.getWidth() * dp1.viewport.getHeight()); + int area2 = (int) (dp2.viewport.getWidth() * dp2.viewport.getHeight()); + return area2 - area1; + } + } } diff --git a/src/main/resources/github/weichware10/analyse/gui/analyse/Analyzer.fxml b/src/main/resources/github/weichware10/analyse/gui/analyse/Analyzer.fxml index e888e69..32d92df 100644 --- a/src/main/resources/github/weichware10/analyse/gui/analyse/Analyzer.fxml +++ b/src/main/resources/github/weichware10/analyse/gui/analyse/Analyzer.fxml @@ -1,40 +1,45 @@ - - - + - + - + -