Skip to content
Snippets Groups Projects
LineChartFunctionality.java 19.9 KiB
Newer Older
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 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
        xAxis = new CategoryAxis();
        yAxis = new NumberAxis();
        lineChart = new LineChart<>(xAxis, yAxis);
        lineChart.setTitle("Drying Processes");
        lineChart.setAnimated(false);
        xAxis.setAnimated(false);
        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() {
        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();
                    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);
                }
    /**
     * 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) {
            // 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
        // 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();
            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);
        // 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];
        for (int i = 0; i < confidenceIntervalData.size(); i++) {
            ArrayList<Double> list = confidenceIntervalData.get(i);
            for (Double aDouble : list) {
                data[index][1] = aDouble;
        // 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);
    /**
     * 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);
        getTimeLeft(userInput.size());

        for (Map.Entry<String, Number> entryKwh : userInput.entrySet()) {
            int index = (int) (findDifference(Constants.START_TIME, entryKwh.getKey()) / 10);
            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;
            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();