package com.application.GUI; import com.application.DB.Constants; import com.application.DB.Settings; import com.application.GUI.PopUpWindows.NotificationPopUp; import com.application.Main; import javafx.scene.chart.CategoryAxis; import javafx.scene.chart.LineChart; import javafx.scene.chart.NumberAxis; import javafx.scene.chart.XYChart; import org.apache.commons.math3.distribution.TDistribution; import org.apache.commons.math3.exception.MathIllegalArgumentException; import org.apache.commons.math3.stat.descriptive.SummaryStatistics; import org.apache.commons.math3.stat.regression.SimpleRegression; import java.text.SimpleDateFormat; import java.util.*; import static com.application.Main.*; /** * This class handle all the GUI functionality and statistics calculations * * @author Eilert Tunheim, Karin Pettersen, Mads Arnesen * @version 1.0.0 */ public class LineChartFunctionality { private static LineChart<String, Number> lineChart; private static XYChart.Series<String, Number> liveDataSeries; private static XYChart.Series<String, Number> regressionSeries; private static XYChart.Series<String, Number> regressionSeriesConfidenceInterval; private static CategoryAxis xAxis; private static NumberAxis yAxis; private static int dataPointsXAxis = 0; private static int dataPointsYAxis = 0; private static Map<String, Number> liveData; private static ArrayList<XYChart.Series<String, Number>> previousData; private static boolean printRegression; private static boolean printRegressionConfidenceInterval; private static boolean printLiveData; private static boolean printPreviousData; // Constructor public LineChartFunctionality() { xAxis = new CategoryAxis(); yAxis = new NumberAxis(); lineChart = new LineChart<>(xAxis, yAxis); lineChart.setTitle("Drying Processes"); lineChart.setAnimated(false); xAxis.setLabel("Data Points"); xAxis.setAnimated(false); yAxis.setLabel("Kwh"); yAxis.setAnimated(false); regressionSeries = new XYChart.Series<String, Number>(); regressionSeriesConfidenceInterval = new XYChart.Series<String, Number>(); liveDataSeries = new XYChart.Series<String, Number>(); previousData = new ArrayList<>(); printRegression = true; printRegressionConfidenceInterval = false; printLiveData = true; printPreviousData = true; } /** * Prints the selected graphs to the line chart * * Note: Something wrong when exceeding 8 series, think the next series choose a random series color */ public static void printGraphs() { getLineChart().getData().clear(); if(isPrintRegressionConfidenceInterval()){ updateLineChart(getRegressionSeriesConfidenceInterval()); getMenuViewRegressionShadow().setSelected(true); getRegressionConfidenceIntervalBox().setSelected(true); } else { updateLineChart(new XYChart.Series<>()); getMenuViewRegressionShadow().setSelected(false); getRegressionConfidenceIntervalBox().setSelected(false); } if(isPrintRegression()){ updateLineChart(getRegressionSeries()); getMenuViewRegression().setSelected(true); getRegressionBox().setSelected(true); } else { updateLineChart(new XYChart.Series<>()); getMenuViewRegression().setSelected(false); getRegressionBox().setSelected(false); } if(isPrintLiveData()){ updateLineChart(getLiveDataSeries()); getMenuViewLiveData().setSelected(true); getLiveDataBox().setSelected(true); } else { updateLineChart(new XYChart.Series<>()); getMenuViewLiveData().setSelected(false); getLiveDataBox().setSelected(false); } if(isPrintPreviousData()){ getPreviousBox().setSelected(true); getMenuViewPreviousData().setSelected(true); // If there are more than 5 series, adds 3 new empty series to keep the color scheme for (int i = 0; i < getPreviousData().size(); i++) { int index = getLineChart().getData().size(); if (index % 8 == 0 ){ for (int j = 0; j < 3; j++) { updateLineChart(new XYChart.Series<>()); } } updateLineChart(getPreviousData().get(i)); } } else { getPreviousBox().setSelected(false); getMenuViewPreviousData().setSelected(false); } } /** * This function calculates the confidence interval * * @param multiMap input containing the drying cycle data * @param CIShadow input to indicate if the confidence interval shadow is stored or not * @return a map containing the processed data */ private static Map<Integer, ArrayList<Double>> statistics(Map<Integer, ArrayList<Double>> multiMap, boolean CIShadow){ // Iterates through all the data for (Map.Entry<Integer, ArrayList<Double>> entry : multiMap.entrySet()) { SummaryStatistics data = new SummaryStatistics(); for (double val : entry.getValue()) { data.addValue(val); } // Calculate the confidence interval double confidenceInterval = calcMeanConfidenceInterval(data); double lower = data.getMean() - confidenceInterval; double upper = data.getMean() + confidenceInterval; // Deletes entries if they are out of bounds with the confidence interval entry.getValue().removeIf(value -> Double.compare(value, lower) < 0 || Double.compare(value, upper) > 0); // Checks if the confidence interval shall be returned or not if(CIShadow){ ArrayList<Double> lowerUpperBounds = new ArrayList<>(); lowerUpperBounds.add(lower); lowerUpperBounds.add(upper); multiMap.replace(entry.getKey(), lowerUpperBounds); } } return multiMap; } /** * This function calculates the confidence interval mean value * * @param data the data to base the calculation on * @return returns the confidence interval mean value */ private static double calcMeanConfidenceInterval(SummaryStatistics data) { try { // Create a T distribution TDistribution tDistribution = new TDistribution(data.getN() - 1); // Calculate the critical value double criticalValue = tDistribution.inverseCumulativeProbability(1.0 - (1 - Settings.CONFIDENCE_INTERVAL) / 2); // Calculate the confidence interval return criticalValue * data.getStandardDeviation() / Math.sqrt(data.getN()); } catch (MathIllegalArgumentException e) { return Double.NaN; } } /** * This function serves as the main function that controls all the functionality. * This function takes the data inputted and plots it and calculates the statistics. * * @param userInput data regarding the drying cycles * @throws Exception throws exception if an error occurs */ public static void loadSingleSeries(Map<Integer, Map<String, Number>> userInput) throws Exception { // Clears the linechart if there are previous graphs plotted clearLineChart(); // Defining a map to store data Map<Integer, ArrayList<Double>> multiMap = new HashMap<>(); // Iterates through the data for (Map.Entry<Integer, Map<String, Number>> entryKwh : userInput.entrySet()) { Map data = entryKwh.getValue(); int index = 0; for (Object entryData : data.entrySet()) { String entryString = entryData.toString(); String[] arr = entryString.split("="); Double kwhValue = Double.parseDouble(arr[1]); // Checks if the index already got an arraylist, if not one is created multiMap.computeIfAbsent(index, k -> new ArrayList<Double>()); multiMap.get(index).add(kwhValue); index++; } } // Finds the end datapoint at the end of each graph int numberOfGraphs = 0; // Defines lists to store data ArrayList<Double> dataArraylistXAxis = new ArrayList<>(); ArrayList<Double> dataArraylistYAxis = new ArrayList<>(); Map<Integer, ArrayList<Double>> endOfGraphPointsXAxis = new HashMap<>(); Map<Integer, ArrayList<Double>> endOfGraphPointsYAxis = new HashMap<>(); // Iterates through the data for (int i = 0; i < multiMap.size(); i++) { ArrayList<Double> list = multiMap.get(i); for (int j = 0; j < list.size(); j++) { if (numberOfGraphs < list.size()) { numberOfGraphs = list.size(); } if (list.size() < numberOfGraphs) { dataArraylistXAxis.add((double) i); dataArraylistYAxis.add(multiMap.get(i).get(j)); numberOfGraphs = list.size(); } } } dataArraylistXAxis.add((double) multiMap.size()); dataArraylistYAxis.add(multiMap.get(multiMap.size()-1).get(0)); endOfGraphPointsXAxis.put(0,dataArraylistXAxis); endOfGraphPointsYAxis.put(0,dataArraylistYAxis); Map<Integer, ArrayList<Double>> endOfGraphPointsConfidenceXAxis = statistics(endOfGraphPointsXAxis,false); Map<Integer, ArrayList<Double>> endOfGraphPointsConfidenceYaxis = statistics(endOfGraphPointsYAxis,false); // X-axis dataPointsXAxis = 0; for (Map.Entry<Integer, ArrayList<Double>> entry : endOfGraphPointsConfidenceXAxis.entrySet()) { for (int i = 0; i < entry.getValue().size(); i++) { dataPointsXAxis += entry.getValue().get(i); } } dataPointsXAxis = dataPointsXAxis /endOfGraphPointsConfidenceXAxis.get(0).size(); // Y-axis dataPointsYAxis = 0; for (Map.Entry<Integer, ArrayList<Double>> entry : endOfGraphPointsConfidenceYaxis.entrySet()) { for (int i = 0; i < entry.getValue().size(); i++) { dataPointsYAxis += entry.getValue().get(i); } } dataPointsYAxis = dataPointsYAxis /endOfGraphPointsConfidenceYaxis.get(0).size(); // Stores the data from the confidence interval in a new map Map<Integer, ArrayList<Double>> confidenceIntervalData = statistics(multiMap,false); // Checks the max size for the arraylists needed for the data array later int jMaxSize = 0; for (int i = 0; i < confidenceIntervalData.size(); i++) { if(confidenceIntervalData.get(i).size() > jMaxSize){ jMaxSize = confidenceIntervalData.get(i).size(); } } // Defines an array to be used for the linear regression double[][] data = new double[confidenceIntervalData.size()*jMaxSize][2]; int index = 0; for (int i = 0; i < confidenceIntervalData.size(); i++) { ArrayList<Double> list = confidenceIntervalData.get(i); for (Double aDouble : list) { data[index][0] = i; data[index][1] = aDouble; index++; } } // Adds the data to the confidence interval shadow getRegressionSeriesConfidenceInterval().getData().clear(); Map<Integer, ArrayList<Double>> confidenceIntervalShadow = statistics(multiMap,true); for ( Map.Entry<Integer, ArrayList<Double>> entry : confidenceIntervalShadow.entrySet()) { for (int i = 0; i < entry.getValue().size(); i++) { Double doubleData = entry.getValue().get(i); getRegressionSeriesConfidenceInterval().getData().add(new XYChart.Data<String, Number>(String.valueOf(entry.getKey()), doubleData.intValue())); } } // Does the linear regression SimpleRegression simpleRegression = new SimpleRegression(); simpleRegression.addData(data); // Does the logistic regression getRegressionSeries().getData().clear(); for (int i = 0; i <= getDataPointsXAxis(); i++) { getRegressionSeries().getData().add(new XYChart.Data<String, Number>( String.valueOf(i), getNonLinearRegression( confidenceIntervalData, Math.sqrt(Math.pow(simpleRegression.getIntercept(),2)), simpleRegression.getSlope(), i, getDataPointsXAxis() ))); } // Adds the previous data to series getPreviousData().clear(); for (Map.Entry<Integer, Map<String, Number>> entryKwh : userInput.entrySet()) { XYChart.Series<String, Number> newSeries = new XYChart.Series<String, Number>(); index = 0; for (Object entryData : entryKwh.getValue().entrySet()) { String entryString = entryData.toString(); Double kwhValue = Double.parseDouble(entryString.split("=")[1]); // Connect the data to a series newSeries.getData().add(new XYChart.Data<String, Number>(String.valueOf(index), kwhValue)); index++; } addPreviousData(newSeries); } getTimeLeft(0); printGraphs(); } /** * This function loads the live data and plots the data in the graph * * @param userInput the input parameter for the live data * @throws Exception throws an exception if an error occur */ public static void loadLiveData(Map<String, Number> userInput) throws Exception { // Clears any data already there getLiveDataSeries().getData().clear(); // Sets the livedata in series setLiveData(userInput); // Updates time left getTimeLeft(userInput.size()); for (Map.Entry<String, Number> entryKwh : userInput.entrySet()) { // Finds the index value int index = (int) (findDifference(Constants.START_TIME, entryKwh.getKey()) / 10); // Gets kwh value Double kwhValue = entryKwh.getValue().doubleValue(); // Connect the data to a series getLiveDataSeries().getData().add(new XYChart.Data<String, Number>(String.valueOf(index), kwhValue)); } } /** * Finds the difference between two dates * * @param start_date input parameter for the first date * @param end_date input parameter for the second date * @return returns the differnce in minutes */ private static long findDifference(String start_date, String end_date) { // Defining a simple date format SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try{ // try to convert the string to Date datatype Date dateStart = dateFormat.parse(start_date); Date dateEnd = dateFormat.parse(end_date); // Finds the difference in millis long differenceMillis = dateEnd.getTime() - dateStart.getTime(); // Finds the difference in minutes return (differenceMillis / (1000 * 60 )) % 60; } catch (Exception e) { System.out.println(e.getMessage()); } return 0; } /** * This function calculates the remaining time of the current drying cycle * * @param liveData input parameter for the data to base the calculations on */ public static void getTimeLeft(int liveData){ // Defines variables int minutes = 0; int hours = 0; if(liveData == 0) { // There are 10 minutes between each datapoint minutes = getDataPointsXAxis()*10; } else { minutes = (getDataPointsXAxis()/liveData)*10; } if(minutes > 60){ hours = minutes/60; Main.setTimeLeftText(hours + " h"); if(hours == 3){ NotificationPopUp.displayNotificationWindow("3 Hours Left!");} } else { if(minutes == 60){NotificationPopUp.displayNotificationWindow("1 Hours Left!");} Main.setTimeLeftText(minutes + " min"); } if(minutes == 0 && getDataPointsXAxis() != 0){ NotificationPopUp.displayNotificationWindow("Drying Process Finished!"); } } /** * This function handles the logistic regression * * @param confidenceIntervalData the input parameter for the data to base the regression on * @param y0 the input parameter for the intercept from the linear regression * @param alpha the input parameter for the slope value from the linear regression * @param j the input parameter for current x-axis datapoint to predict the value for * @param n the input parameter for the total number of points along the x-axis * @return the predicted value at a given point */ public static double getNonLinearRegression(Map<Integer, ArrayList<Double>> confidenceIntervalData, double y0, double alpha, double j, double n) { double beta = getDataPointsYAxis()+y0; return (((beta * y0))/(y0 +((beta- y0)*Math.exp(-alpha*j/n/((alpha/10))))))-y0; } // Getters and setters public static int getDataPointsXAxis() { return dataPointsXAxis; } public static int getDataPointsYAxis() { return dataPointsYAxis; } public static Map<String, Number> getLiveData() { return liveData; } public static void setLiveData(Map<String, Number> input) { liveData = input; } public static XYChart.Series<String, Number> getLiveDataSeries() { return liveDataSeries; } public static XYChart.Series<String, Number> getRegressionSeries() { return regressionSeries; } public static boolean isPrintRegression() { return printRegression; } public static void setPrintRegression(boolean printRegression) { LineChartFunctionality.printRegression = printRegression; } public static boolean isPrintRegressionConfidenceInterval() { return printRegressionConfidenceInterval; } public static void setPrintRegressionConfidenceInterval(boolean printRegressionConfidenceInterval) { LineChartFunctionality.printRegressionConfidenceInterval = printRegressionConfidenceInterval; } public static boolean isPrintLiveData() { return printLiveData; } public static void setPrintLiveData(boolean printLiveData) { LineChartFunctionality.printLiveData = printLiveData; } public static boolean isPrintPreviousData() { return printPreviousData; } public static void setPrintPreviousData(boolean printPreviousData) { LineChartFunctionality.printPreviousData = printPreviousData; } public static ArrayList<XYChart.Series<String, Number>> getPreviousData() { return previousData; } public static void addPreviousData(XYChart.Series<String, Number> previousData) { LineChartFunctionality.previousData.add(previousData); } public static XYChart.Series<String, Number> getRegressionSeriesConfidenceInterval() { return regressionSeriesConfidenceInterval; } public static LineChart<String, Number> getLineChart() { return lineChart; } public static void updateLineChart(XYChart.Series<String, Number> series) { getLineChart().getData().add(series); series.getNode().setId("dataGraphs"); getLineChart().getStylesheets().add(LineChartFunctionality.class.getResource("/com.application/GUI/graphStyles.css").toExternalForm()); } public static void clearLineChart() { getLineChart().getData().clear(); } }