UI.MainStageController.java Source code

Java tutorial

Introduction

Here is the source code for UI.MainStageController.java

Source

package UI;

import analysis.GraphAnalysis;
import analysis.SampleComparison;
import graph.MyEdge;
import graph.MyGraph;
import graph.MyVertex;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.chart.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.stage.FileChooser;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javafx.util.StringConverter;
import javafx.util.converter.NumberStringConverter;
import main.GlobalConstants;
import main.Main;
import main.UserSettings;
import model.AnalysisData;
import model.LoadedData;
import model.Sample;
import model.TaxonNode;
import org.apache.commons.math3.linear.RealMatrix;
import org.controlsfx.control.RangeSlider;
import org.controlsfx.glyphfont.FontAwesome;
import org.controlsfx.glyphfont.GlyphFontRegistry;
import sampleParser.BiomV1Parser;
import sampleParser.BiomV2Parser;
import sampleParser.ReadName2TaxIdCSVParser;
import sampleParser.TaxonId2CountCSVParser;
import util.SaveAndLoadOptions;
import view.*;

import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static main.Main.getPrimaryStage;
import static model.AnalysisData.*;

/**
 * <h1>This is the main GUI class. It implements most methods for the main stage.</h1>
 * <p>
 * Most of the functionality is channeled in this class. This is the class where all of the functionality comes
 * together. It contains the method for initializing all required Services and it implements all buttons in the main
 * stage.
 * </p>
 *
 * @version This class should be divided into more classes which separate behaviour.
 * @see SpringAnimationService
 * @see ViewPane
 * @see MySpringLayout
 * @see MyColours
 * @see Palette
 */
public class MainStageController implements Initializable {
    //Default constants for the analysis sliders
    public static final double DEFAULT_POSITIVE_CORRELATION_LOW = 0.5;
    public static final int DEFAULT_POSITIVE_CORRELATION_HIGH = 1;
    public static final int DEFAULT_NEGATIVE_CORRELATION_LOW = -1;
    public static final double DEFAULT_NEGATIVE_CORRELATION_HIGH = -0.5;
    public static final double DEFAULT_MAX_P_VALUE_SLIDER = 0.05;
    public static final int DEFAULT_FREQUENCY_RANGE_SLIDER_LOW = 0;
    public static final int DEFAULT_FREQUENCY_RANGE_SLIDER_HIGH = 1;

    //Default constants for the Graph settings
    public static final int DEFAULT_ANIMATION_SPEED = 25;
    public static final double DEFAULT_SLIDER_EDGE_FORCE = 1.5;
    public static final int DEFAULT_NODE_REPULSION = 30;
    public static final double DEFAULT_SLIDER_STRECH_PARAMETER = 0.9;
    public static final int DEFAULT_SLIDER_NODE_RADIUS = 15;
    public static final int DEFAULT_SLIDER_EDGE_WIDTH = 5;
    public static final int DEFAULT_SLIDER_EDGE_LENGTH_LOW = 50;
    public static final int DEFAULT_SLIDER_EDGE_LENGTH_HIGH = 500;

    private static Stage optionsStage;
    private static Stage exportImagesStage;

    private ViewPane viewPane;

    private static final int MAX_WIDTH_OF_SIDEPANES = 220;

    public MainStageController() {
    }

    private enum FileType {
        taxonId2Count, readName2TaxonId, biomV1, biomV2, qiime
    }

    public static boolean isMainViewMaximized = false;

    // alerts
    private Alert fileNotFoundAlert, confirmQuitAlert, aboutAlert, fileAlreadyLoadedAlert, wrongFileAlert,
            insufficientDataAlert;

    // FXML elements
    @FXML
    private AnchorPane leftPane, rightPane;

    @FXML
    private Tab mainViewTab;

    @FXML
    private Label leftLabel;

    @FXML
    private TreeView<String> treeViewFiles;

    @FXML
    private Accordion preferencesAccordion;

    /**
     * BUTTON ELEMENTS
     */
    @FXML
    private RadioButton collapseAllButton;

    /**
     * FILTER OPTION ELEMENTS
     **/
    @FXML
    private ChoiceBox<String> rankChoiceBox;

    //List of possible choices of the choice box
    ObservableList<String> ranksList = FXCollections.observableArrayList("Domain", "Kingdom", "Phylum", "Class",
            "Order", "Family", "Genus", "Species");

    @FXML
    private RadioButton compareSelectedSamplesButton;

    @FXML
    private RadioButton pearsonCorrelationButton, spearmanCorrelationButton, kendallCorrelationButton;

    @FXML
    private TextField minPosCorrelationText, maxPosCorrelationText, minNegCorrelationText, maxNegCorrelationText;

    @FXML
    private Slider maxPValueSlider;

    @FXML
    private TextField maxPValueText;

    @FXML
    private TextField minFrequencyText;

    @FXML
    private TextField maxFrequencyText;

    @FXML
    private RangeSlider posCorrelationRangeSlider, negCorrelationRangeSlider, frequencyRangeSlider;

    /**
     * STARTUP PANE ELEMENTS
     **/
    @FXML
    private Label startupLabel;

    @FXML
    private ProgressIndicator startupSpinner;

    /**
     * STATUS FOOTER ELEMENTS
     **/
    @FXML
    private Label statusRightLabel;

    /**
     * GRAPH VIEW SETTING ELEMENTS
     **/
    @FXML
    private Slider sliderNodeRadius;

    @FXML
    private Slider sliderEdgeWidth;

    @FXML
    private RangeSlider sliderEdgeLength;

    @FXML
    private ToggleButton buttonPauseAnimation;

    @FXML
    private CheckBox checkAdvancedGraphSettings;

    @FXML
    private Label labelNodeRepulsion;

    @FXML
    private Slider sliderNodeRepulsion;

    @FXML
    private Label labelStretchParameter;

    @FXML
    private Slider sliderStretchParameter;

    @FXML
    private Label labelAnimationSpeed;

    @FXML
    private Slider sliderAnimationSpeed;

    @FXML
    private Label labelEdgeForce;

    @FXML
    private Slider sliderEdgeForce;

    @FXML
    private Button buttonResetGraphDefaults;

    @FXML
    private CheckBox showLabelsCheckBox;

    /**
     * COLOUR SETTINGS ELEMENTS
     */
    @FXML
    private RadioButton colourRadioNodeFix;

    @FXML
    private RadioButton colourRadioNodeParent;

    @FXML
    private RadioButton colourRadioNodeAlpha;

    @FXML
    private RadioButton colourRadioNodeFrequency;

    @FXML
    private ToggleGroup colourToggleNodes;

    @FXML
    private StackPane colourNodeComboContainer;

    ComboBox<Palette> nodeColourCombo = new ComboBox<>();

    @FXML
    private RadioButton colourRadioEdgeCorrelation;

    @FXML
    private RadioButton colourRadioEdgeDistance;

    @FXML
    private RadioButton colourRadioEdgePvalue;

    @FXML
    private ToggleGroup colourToggleEdges;

    @FXML
    private StackPane colourEdgeComboContainer;

    ComboBox<Palette> edgeColourCombo = new ComboBox<>();

    @FXML
    private Slider excludeFrequencySlider;

    @FXML
    private TextField excludeFrequencyText;

    /**
     * ANALYSIS PANE ELEMENTS
     */

    @FXML
    private AnchorPane analysisPane;

    @FXML
    private PieChart frequencyChart;

    @FXML
    private BarChart<String, Double> degreeDistributionChart;

    @FXML
    private TextArea graphStatText, dataStatText;

    @FXML
    private TextArea modularityText;

    /**
     * INFO PANE
     */
    @FXML
    private TextFlow infoTextFlow;

    @FXML
    private TextArea infoTextArea;

    @FXML
    private Button abundancePlotButton;

    /**
     * Initializes every needed service
     *
     * @param location
     * @param resources
     */
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        // Load FontAweSome
        GlyphFontRegistry
                .register(new FontAwesome(getClass().getResourceAsStream("/fonts/fontawesome-webfont.ttf")));

        startTreePreloadService();
        initializeAccordion();
        initializeRankChoiceBox();
        initializeGraphSettings();
        initializeAnalysisPane();
        initializeGraphAnalysis();
        initializeInfoPane();
        initializeBindings();
        initializeColorComboBox();
        initializeButtonsOnLeftPane();
        //preload settings
        SaveAndLoadOptions.loadSettings();

        //Display the info text in the bottom left pane
        displayInfoText();
    }

    @FXML
    /**
     * Should be called when the user clicks a button to analyze the loaded samples and display the graphview
     * Creates correlation data, creates the internal graph, applies default filter, displays the graph
     */
    public void startAnalysis() {
        for (Sample sample : LoadedData.getSamplesToAnalyze()) {
            sample.filterTaxaPrimary();
        }

        String correlationType = "";
        if (pearsonCorrelationButton.isSelected())
            correlationType = "pearson";
        else if (spearmanCorrelationButton.isSelected())
            correlationType = "spearman";
        else if (kendallCorrelationButton.isSelected())
            correlationType = "kendall";

        boolean isAnalysisSuccessful = AnalysisData
                .performCorrelationAnalysis(new ArrayList<>(LoadedData.getSamplesToAnalyze()), correlationType);
        if (isAnalysisSuccessful) {
            LoadedData.createGraph();
            LoadedData.getTaxonGraph().filterEdges();
            LoadedData.getTaxonGraph().filterVertices();
            displayGraph(LoadedData.getTaxonGraph());
            displayAnalysisTextsAndGraphs();
            performGraphAnalysis();
            displayGraphAnalysis();
            displayInfoText();
            setHubsInView();
        } else {//The analysis couldn't be done because of insufficient data
            showInsufficientDataAlert();
        }
    }

    @FXML
    public void showAllSamples() {
        if (treeViewFiles.getRoot() != null && treeViewFiles.getRoot().getChildren().isEmpty()) {
            System.out.println("I will show every node.");
        }
    }

    @FXML
    public void showNoSamples() {
        if (treeViewFiles.getRoot() != null && !treeViewFiles.getRoot().getChildren().isEmpty()) {
            System.out.println("I will hide every node.");
            treeViewFiles.getRoot().getChildren().remove(0, treeViewFiles.getRoot().getChildren().size());
        }
    }

    @FXML
    public void showReverseSamples() {

    }

    /**
     * chooses which text to display on the bottom left pane
     * TODO: This isn't called every time it should be, add some more listeners!
     */
    public void displayInfoText() {
        String infoText = "";
        if (LoadedData.getSamplesToAnalyze() == null || LoadedData.getSamples().size() < 3) {
            infoText = "Please import at least 3 samples \nto begin correlation analysis!";
        } else if (compareSelectedSamplesButton.isSelected() && LoadedData.getSelectedSamples().size() < 3) {
            infoText = "If you want to analyse selected \nsamples only, please select \nat least 3 samples!";
        } else if (rankChoiceBox.getValue() == null) {
            infoText = "Choose a rank to display the graph!";
        } else if (LoadedData.getGraphView() != null
                && LoadedData.getGraphView().getSelectionModel().getSelectedItems().size() > 1) {
            StringBuilder builder = new StringBuilder("Selected Taxa:\n");
            ObservableList selectedItems = LoadedData.getGraphView().getSelectionModel().getSelectedItems();
            for (Object selectedItem : selectedItems) {
                MyVertex vertex = (MyVertex) selectedItem;
                builder.append(vertex.getTaxonName());
                builder.append("\n");
            }
            infoText = builder.toString();
        } else if (LoadedData.getGraphView() != null
                && LoadedData.getGraphView().getSelectionModel().getSelectedItems().size() == 1) {
            MyVertex selectedVertex = (MyVertex) LoadedData.getGraphView().getSelectionModel().getSelectedItems()
                    .get(0);
            GraphAnalysis analysis = AnalysisData.getAnalysis();
            infoText = "Selected Taxon:\n" + selectedVertex.getTaxonName() + "\nID: "
                    + selectedVertex.getTaxonNode().getTaxonId() + "\nFrequency: "
                    + String.format("%.3f",
                            AnalysisData.getMaximumRelativeFrequencies().get(selectedVertex.getTaxonNode()))
                    + "\nParent Taxon: " + selectedVertex.getAttributesMap().get("parentName")
                    + "\nNo. of visible edges: " + analysis.getNodeDegrees().get(selectedVertex.getTaxonNode());
        } else if (LoadedData.getGraphView() != null) {
            GraphAnalysis analysis = AnalysisData.getAnalysis();
            infoText = "Network Overview: \nNo. of visible taxa: "
                    + analysis.getFilteredGraph().getVertices().size() + "\nNo. of visible edges: "
                    + analysis.getFilteredGraph().getEdges().size() + "\nAverage Degree: "
                    + String.format("%.2f", analysis.getMeanDegree());
        }

        infoTextArea.setText(infoText);
    }

    /**
     * Displays an abundance plot of the selected taxa
     */
    @FXML
    private void displayAbundancePlot() {
        ObservableList selectedItems = LoadedData.getGraphView().getSelectionModel().getSelectedItems();
        List<TaxonNode> nodesList = new LinkedList<>();
        for (Object selectedItem : selectedItems) {
            nodesList.add(((MyVertex) selectedItem).getTaxonNode());
        }
        HashMap<Sample, HashMap<TaxonNode, Integer>> abundancesMap = SampleComparison.calcAbundances(nodesList);

        final CategoryAxis xAxis = new CategoryAxis();
        final NumberAxis yAxis = new NumberAxis();
        xAxis.setLabel("Taxa");
        yAxis.setLabel("Abundance");
        BarChart<String, Number> abundancePlot = new BarChart<>(xAxis, yAxis);

        for (Map.Entry<Sample, HashMap<TaxonNode, Integer>> entry : abundancesMap.entrySet()) {
            XYChart.Series<String, Number> sampleSeries = new XYChart.Series<>();
            sampleSeries.setName(entry.getKey().getName());
            for (Map.Entry<TaxonNode, Integer> innerMapEntry : entry.getValue().entrySet()) {
                sampleSeries.getData()
                        .add(new XYChart.Data<>(innerMapEntry.getKey().getName(), innerMapEntry.getValue()));
            }
            abundancePlot.getData().add(sampleSeries);
        }

        //Display chart on a new pane
        Stage chartStage = new Stage();
        chartStage.setTitle("Abundance Plot");
        Scene chartScene = new Scene(abundancePlot);
        chartStage.setScene(chartScene);
        chartStage.show();
    }

    /**
     * shows the graph in the main view
     *
     * @param taxonGraph
     */
    private void displayGraph(MyGraph<MyVertex, MyEdge> taxonGraph) {
        MyGraphView graphView = new MyGraphView(taxonGraph);
        LoadedData.setGraphView(graphView);
        ViewPane viewPane = new ViewPane(graphView);
        viewPane = new ViewPane(graphView);

        // Bind node hover status text
        statusRightLabel.textProperty().bind(viewPane.hoverInfo);

        // Settings need to be initialized with graphView
        bindGraphSettings(graphView);
        bindColourSettings(graphView);

        mainViewTab.setContent(viewPane);

        //Bind the showLabels-Checkbox to the visibility properties of the MyVertexView labels
        for (Node node : LoadedData.getGraphView().getMyVertexViewGroup().getChildren()) {
            MyVertexView vertexView = (MyVertexView) node;
            vertexView.getVertexLabel().visibleProperty().bind(showLabelsCheckBox.selectedProperty());
        }

        //call displayInfoText whenever the selection changes + decide whether or not to enable the abundancePlotButton
        LoadedData.getGraphView().getSelectionModel().getSelectedItems().addListener((InvalidationListener) e -> {
            displayInfoText();
            if (LoadedData.getGraphView().getSelectionModel().getSelectedItems().size() == 0)
                abundancePlotButton.setDisable(true);
            else
                abundancePlotButton.setDisable(false);
        });

    }

    /**
     * shows the correlation table in the analysis view
     */
    @FXML
    private void displayCorrelationTable() {
        //Delete whatever's been in the table before
        TableView<String[]> analysisTable = new TableView<>();

        //We want to display correlations and p-Values of every node combination
        double[][] correlationMatrix = AnalysisData.getCorrelationMatrix().getData();
        double[][] pValueMatrix = AnalysisData.getPValueMatrix().getData();
        LinkedList<TaxonNode> taxonList = SampleComparison.getUnifiedTaxonList(LoadedData.getSamplesToAnalyze(),
                AnalysisData.getLevelOfAnalysis());

        //Table will consist of strings
        String[][] tableValues = new String[correlationMatrix.length][correlationMatrix[0].length + 1];

        //Add the values as formatted strings
        for (int i = 0; i < tableValues.length; i++) {
            tableValues[i][0] = taxonList.get(i).getName();
            for (int j = 1; j < tableValues[0].length; j++) {
                tableValues[i][j] = String.format("%.3f", correlationMatrix[i][j - 1]).replace(",", ".") + "\n("
                        + String.format("%.2f", pValueMatrix[i][j - 1]).replace(",", ".") + ")";
            }
        }

        for (int i = 0; i < tableValues[0].length; i++) {
            String columnTitle;
            if (i > 0) {
                columnTitle = taxonList.get(i - 1).getName();
            } else {
                columnTitle = "";
            }
            TableColumn<String[], String> column = new TableColumn<>(columnTitle);
            final int columnIndex = i;
            column.setCellValueFactory(cellData -> {
                String[] row = cellData.getValue();
                return new SimpleStringProperty(row[columnIndex]);
            });
            analysisTable.getColumns().add(column);

            //First column contains taxon names and should be italic
            if (i == 0)
                column.setStyle("-fx-font-style:italic;");
        }

        for (int i = 0; i < tableValues.length; i++) {
            analysisTable.getItems().add(tableValues[i]);
        }

        //Display table on a new stage
        Stage tableStage = new Stage();
        tableStage.setTitle("Correlation Table");
        BorderPane tablePane = new BorderPane();
        Button exportCorrelationsButton = new Button("Save correlation table to CSV");
        Button exportPValuesButton = new Button("Save p-value table to CSV");
        exportCorrelationsButton.setOnAction(e -> exportTableToCSV(tableValues, false));
        exportPValuesButton.setOnAction(e -> exportTableToCSV(tableValues, true));
        HBox exportBox = new HBox(exportCorrelationsButton, exportPValuesButton);
        exportBox.setPadding(new Insets(10));
        exportBox.setSpacing(10);
        tablePane.setTop(exportBox);
        tablePane.setCenter(analysisTable);
        Scene tableScene = new Scene(tablePane);
        tableStage.setScene(tableScene);
        tableStage.show();
    }

    /**
     * exports the created table to a .csv file
     * uses a fileChooser to determine where to save the .csv file
     *
     * @param tableValues
     * @param isPValue
     */
    private void exportTableToCSV(String[][] tableValues, boolean isPValue) {
        //We'll split up the table values into two parts - if we need correlations, we take the 0th one,
        // if we want p values, we take the 1st
        int splitNumber;
        splitNumber = isPValue ? 1 : 0;

        FileChooser chooser = new FileChooser();
        chooser.setInitialDirectory(new File((String) UserSettings.userSettings.get("defaultFileChooserLocation")));
        File outputFile = chooser.showSaveDialog(getPrimaryStage());
        try {
            BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile));

            //Write names into first row
            writer.write(",");
            for (int i = 0; i < tableValues.length; i++) {
                writer.write(tableValues[i][0] + ",");
            }
            writer.write("\n");

            for (String[] tableRow : tableValues) {
                for (String s : tableRow) {
                    String[] split = s.split("\n");

                    //                    System.out.println(s + split.length);
                    //                    for (String s1 : split) {
                    //                        System.out.println(s1);
                    //                    }
                    if (split.length > 1)
                        writer.write(split[splitNumber] + ",");
                    else
                        writer.write(split[0] + ",");
                }
                writer.write("\n");
            }

            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * creates the data for the analysis pane and displays it
     * data created includes:
     * highest positive/negative correlations
     * average counts
     */
    private void displayAnalysisTextsAndGraphs() {
        //Display node with highest frequency
        double highestFrequency = AnalysisData.getHighestFrequency();
        TaxonNode nodeWithHighestFrequency = AnalysisData.getNodeWithHighestFrequency();
        dataStatText.setText("Highest Frequency:\n" + nodeWithHighestFrequency.getName() + " ("
                + String.format("%.3f", highestFrequency) + ")\n");

        //Display nodes with highest positive/negative correlation
        RealMatrix correlationMatrix = AnalysisData.getCorrelationMatrix();
        int[] highestPositiveCorrelationCoordinates = AnalysisData.getHighestPositiveCorrelationCoordinates();
        int[] highestNegativeCorrelationCoordinates = AnalysisData.getHighestNegativeCorrelationCoordinates();
        LinkedList<TaxonNode> taxonList = SampleComparison.getUnifiedTaxonList(LoadedData.getSamplesToAnalyze(),
                AnalysisData.getLevelOfAnalysis());
        TaxonNode hPCNode1 = taxonList.get(highestPositiveCorrelationCoordinates[0]);
        TaxonNode hPCNode2 = taxonList.get(highestPositiveCorrelationCoordinates[1]);
        TaxonNode hNCNode1 = taxonList.get(highestNegativeCorrelationCoordinates[0]);
        TaxonNode hNCNode2 = taxonList.get(highestNegativeCorrelationCoordinates[1]);

        dataStatText.setText(dataStatText.getText() + "\nHighest Positive Correlation:\n" + hPCNode1.getName()
                + " - " + hPCNode2.getName() + " ("
                + String.format("%.3f", correlationMatrix.getEntry(highestPositiveCorrelationCoordinates[0],
                        highestPositiveCorrelationCoordinates[1]))
                + ")\n");
        dataStatText.setText(dataStatText.getText() + "\nHighest Negative Correlation:\n" + hNCNode1.getName()
                + " - " + hNCNode2.getName() + " ("
                + String.format("%.3f", correlationMatrix.getEntry(highestNegativeCorrelationCoordinates[0],
                        highestNegativeCorrelationCoordinates[1]))
                + ")");

        //Generate Data for the pie chart
        frequencyChart.getData().clear();
        HashMap<TaxonNode, Double> averageCounts = SampleComparison
                .calcAverageCounts(LoadedData.getSamplesToAnalyze(), AnalysisData.getLevelOfAnalysis());
        for (TaxonNode taxonNode : averageCounts.keySet()) {
            PieChart.Data data = new PieChart.Data(taxonNode.getName(), averageCounts.get(taxonNode));
            frequencyChart.getData().add(data);
        }

        analysisPane.setVisible(true);
    }

    /**
     * starts the Graph analysis
     */
    public void performGraphAnalysis() {
        AnalysisData.setAnalysis(new GraphAnalysis(LoadedData.getTaxonGraph()));
    }

    /**
     * collects the data for the barChart
     * data includes:
     * degree distribution
     * hubs
     */
    public void displayGraphAnalysis() {
        //Generate Data for the BarChart
        GraphAnalysis analysis = AnalysisData.getAnalysis();
        HashMap<Integer, Double> degreeDistribution = analysis.getDegreeDistribution();
        XYChart.Series<String, Double> degreeSeries = new XYChart.Series<>();

        for (Map.Entry<Integer, Double> entry : degreeDistribution.entrySet()) {
            degreeSeries.getData().add(new XYChart.Data<>(entry.getKey().toString(), entry.getValue()));
        }
        degreeDistributionChart.getData().clear();
        degreeDistributionChart.getData().add(degreeSeries);

        //Generate Graph Statistics to display in the TextArea
        HashMap<TaxonNode, Integer> hubs = analysis.getHubsList();
        graphStatText.setText("List of Hubs:\n\n");

        //Sort hubs by descending values
        Map<TaxonNode, Integer> hubsSorted = hubs.entrySet().stream()
                .sorted(Map.Entry.comparingByValue(Collections.reverseOrder())).collect(Collectors
                        .toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));

        for (Map.Entry<TaxonNode, Integer> entry : hubsSorted.entrySet()) {
            graphStatText
                    .setText(graphStatText.getText() + entry.getKey().getName() + " (" + entry.getValue() + ")\n");
        }
    }

    /**
     * displays the modularity
     */
    public void displayMaximalModularity() {
        GraphAnalysis analysis = AnalysisData.getAnalysis();
        double maxModularity = analysis.findGlobalMaximumModularity();
        modularityText.setText("Maximal modularity for current graph:\n" + String.format("%.3f", maxModularity));
    }

    @FXML
    /**
     * <p>Opens recent project files</p>
     * Opens a file chooser and give the user the option to open recently used project files
     * @version Not yet implemented
     */
    public void openRecentFile() {
        /*openFileWindow();*/
    }

    @FXML
    /**<h1>Closes the current project and empties the tree view</h1>
     * Empties every data structure that holds something sample related.
     */
    public void closeProject() {
        if (treeViewFiles.getRoot() != null) {
            LoadedData.closeProject(treeViewFiles);
            if (mainViewTab.getContent() != null) {
                mainViewTab.setContent(null);
            }
            if (LoadedData.getGraphView() != null && LoadedData.getGraphView().animationService.isRunning()) {
                LoadedData.getGraphView().animationService.cancel();
            }
            collapseAllButton.setDisable(true);
        }
        analysisPane.setVisible(false);
        rankChoiceBox.setValue(null);
    }

    @FXML
    public void openId2CountFiles() {
        openFiles(FileType.taxonId2Count);
    }

    @FXML
    public void openReadName2TaxonIdFiles() {
        openFiles(FileType.readName2TaxonId);
    }

    @FXML
    public void openBiomV1Files() {
        openFiles(FileType.biomV1);
    }

    @FXML
    public void openBiomV2Files() {
        openFiles(FileType.biomV1);
    }

    @FXML
    public void openQiimeFiles() {
        openFiles(FileType.qiime);
    }

    /**
     * Exits the program
     * quitButton
     */
    @FXML
    public void quit() {
        confirmQuit();
    }

    /**
     * sets the panes width to the passed value
     *
     * @param width
     */
    private void setPanesWidth(int width) {
        leftPane.setMaxWidth(width);
        rightPane.setMaxWidth(width);
        leftPane.setMinWidth(width);
        rightPane.setMinWidth(width);
    }

    /**
     * <h1>Lets the user choose a file / files to load.</h1>
     * Distinguishes which filetype is about to be loaded by the user
     * and calls the associated methods.
     *
     * @param fileType Enum to differentiate which type of file is loaded by the user.
     */
    private void openFiles(FileType fileType) {
        FileChooser fileChooser = new FileChooser();
        String fileChooserTitle = "Load from ";

        if ((Boolean) UserSettings.userSettings.get("isDefaultFileChooserLocation")) {
            setDefaultOpenDirectory(fileChooser);
        } else {
            fileChooser.setInitialDirectory(
                    new File((String) UserSettings.userSettings.get("defaultFileChooserLocation")));
        }

        switch (fileType) {
        case taxonId2Count:
            fileChooser.setTitle(fileChooserTitle + "taxonId2Count file");
            break;
        case readName2TaxonId:
            fileChooser.setTitle(fileChooserTitle + "readName2TaxonId file");
            break;
        case biomV1:
            fileChooser.setTitle(fileChooserTitle + "biomV1 file");
            break;
        case biomV2:
            fileChooser.setTitle(fileChooserTitle + "biomV2 file");
            break;
        case qiime:
            fileChooser.setTitle(fileChooserTitle + "qiime file");
            break;
        }

        //Choose the file / files

        List<File> selectedFiles = fileChooser.showOpenMultipleDialog(getPrimaryStage());

        if (selectedFiles != null) {
            //Keeps every file that has been loaded before in a list to show only one alert
            //for multiple files
            ArrayList<String> namesOfAlreadyLoadedFiles = new ArrayList<>();
            for (File file : selectedFiles) {
                String foundFilePath = file.getAbsolutePath();
                if (LoadedData.getOpenFiles() != null && LoadedData.getOpenFiles().contains(foundFilePath)) {
                    namesOfAlreadyLoadedFiles.add(file.getName());
                } else {
                    switch (fileType) {
                    case taxonId2Count:
                        addId2CountFileToTreeView(file);
                        break;
                    case readName2TaxonId:
                        addReadName2TaxonIdFileToTreeView(file);
                        break;
                    case biomV1:
                        addBiomV1FileToTreeView(file);
                        break;
                    case biomV2:
                        addBiomV2FileToTreeView(file);
                        break;
                    case qiime:
                        //TODO HANDLE METADATA PROVIDED BY QIIME
                        break;
                    }
                }
            }
            //Shows an alert if the user chose to load one or multiple files
            //that have already been loaded before.
            if (namesOfAlreadyLoadedFiles.size() != 0) {
                showFileAlreadyLoadedAlert(namesOfAlreadyLoadedFiles);
            }
        }

    }

    /**
     * sets the default directory for openings files
     *
     * @param fileChooser
     */
    private void setDefaultOpenDirectory(FileChooser fileChooser) {
        //Set to user directory or go to default if cannot access
        //TODO: osx?
        String userDirectoryString = System.getProperty("user.home");
        File userDirectory = new File(userDirectoryString);
        if (!userDirectory.canRead()) {
            userDirectory = new File("c:/");
            userDirectoryString = "c:/";
        }
        fileChooser.setInitialDirectory(userDirectory);
        UserSettings.userSettings.put("defaultFileChooserLocation", userDirectoryString);
    }

    /**
     * <h1>Parses a given readName2TaxId file</h1>
     * Passes the parsed samples to the LoadedData class.
     *
     * @param file The file the user choses to load
     */
    private void addReadName2TaxonIdFileToTreeView(File file) {
        ReadName2TaxIdCSVParser readName2TaxIdCSVParser = new ReadName2TaxIdCSVParser(TreePreloadService.taxonTree);

        ArrayList<Sample> samples;

        try {
            samples = readName2TaxIdCSVParser.parse(file.getAbsolutePath());
        } catch (IOException e) {
            showWrongFileAlert();
            return;
            //In case the user chose to load a file with a wrong file type
        } catch (NumberFormatException e) {
            showWrongFileAlert();
            return;
        }

        LoadedData.addSamplesToDatabase(samples, treeViewFiles, file);
        activateButtons();
    }

    /**
     * <h1>Parses a given biomV1 file</h1>
     * Passes the parsed samples to the LoadedData class.
     *
     * @param file The file the user choses to load
     */
    private void addBiomV1FileToTreeView(File file) {
        BiomV1Parser biomV1Parser = new BiomV1Parser(TreePreloadService.taxonTree);

        ArrayList<Sample> samples;

        try {
            samples = biomV1Parser.parse(file.getAbsolutePath());
        } catch (NumberFormatException e) {
            showWrongFileAlert();
            return;
        } catch (Exception e) {
            showWrongFileAlert();
            return;
            //In case the user chose to load a file with a wrong file type
        }

        LoadedData.addSamplesToDatabase(samples, treeViewFiles, file);
        activateButtons();
    }

    /**
     * <h1>Parses a given biomV2 file</h1>
     * Passes the parsed samples to the LoadedData class.
     * @param file The file the user choses to load
     */
    private void addBiomV2FileToTreeView(File file) {
        BiomV2Parser biomV2Parser = new BiomV2Parser(TreePreloadService.taxonTree);

        ArrayList<Sample> samples;

        try {
            samples = biomV2Parser.parse(file.getAbsolutePath());
        } catch (IOException e) {
            showWrongFileAlert();
            return;
            //In case the user chose to load a file with a wrong file type
        } catch (NumberFormatException e) {
            showWrongFileAlert();
            return;
        }

        LoadedData.addSamplesToDatabase(samples, treeViewFiles, file);
        activateButtons();
    }

    /**
     * <h1>Parses a given id2Count file</h1>
     * Passes the parsed samples to the LoadedData class.
     *
     * @param file The file the user choses to load
     */
    private void addId2CountFileToTreeView(File file) {
        TaxonId2CountCSVParser taxonId2CountCSVParser = new TaxonId2CountCSVParser(TreePreloadService.taxonTree);

        ArrayList<Sample> samples;

        try {
            samples = taxonId2CountCSVParser.parse(file.getAbsolutePath());
        } catch (IOException e) {
            showWrongFileAlert();
            return;
            //In case the user chose to load a file with a wrong file type
        } catch (NumberFormatException e) {
            showWrongFileAlert();
            return;
        }

        LoadedData.addSamplesToDatabase(samples, treeViewFiles, file);
        activateButtons();
    }

    /**
     * verifies the opened file
     * should be not null and possible to add to the taxonView
     *
     * @param selectedFile
     */
    private void verifyOpenedFile(File selectedFile) {
        boolean isFileFound = selectedFile != null;
        if (!isFileFound) {
            fileNotFoundAlertBox();
        }
        //leftLabel.setText(isFileFound ? selectedFile.getName() : "No such file found.");
        if (isFileFound) {
            //addFileToTreeView(selectedFile);
        }
    }

    /**
     * Helper method for setting up the buttons on the left pane
     */
    public void initializeButtonsOnLeftPane() {
        collapseAllButton.setDisable(true);
        collapseAllButton.setTooltip(new Tooltip("collapse all"));
    }

    @FXML
    /**
     * <h1>Collapses all nodes in the treeview element</h1>
     * Does not effect the current extended state of the treeitems
     */
    public void collapseAll() {
        //If no sample has been loaded yet
        if (treeViewFiles.getRoot() == null || treeViewFiles.getRoot().getChildren().isEmpty()) {
            collapseAllButton.disarm();
            collapseAllButton.setSelected(false);
        } else {
            if (collapseAllButton.isSelected()) {
                for (TreeItem<String> treeItem : treeViewFiles.getRoot().getChildren()) {
                    treeItem.setExpanded(false);
                }
                collapseAllButton.disarm();
                collapseAllButton.setSelected(false);
            } else {
                for (TreeItem<String> treeItem : treeViewFiles.getRoot().getChildren()) {
                    treeItem.setExpanded(true);
                }
                collapseAllButton.setSelected(true);
                collapseAllButton.arm();
            }
        }
    }

    @FXML
    /**
     * Deselects all nodes in the treeview element
     */
    public void deselectAll() {
        changeSelectionForSamples(true);
    }

    @FXML
    /**
     * Selects all nodes in the treeview element
     */
    public void selectAll() {
        changeSelectionForSamples(false);
    }

    /**
     * <h1>Changes the selection of every treeitem representating a sample</h1>
     * <p>
     * <p>Note: </p>May not be used for selection by metadata because it changes every treeitem
     * independent of any characteristics of the represented samples
     * </p>
     * @param isDeselectAll Boolean whether every treeitem should be selected or deselected
     */
    private void changeSelectionForSamples(boolean isDeselectAll) {
        if (treeViewFiles.getRoot() != null && !treeViewFiles.getRoot().getChildren().isEmpty()) {
            for (int i = 0; i < treeViewFiles.getRoot().getChildren().size(); i++) {
                //Replaces the found tree item with a CheckBoxTreeItem -> Actually the found tree item
                //is already a CheckBoxTreeItem but the TreeView does not now anythin from its CellFactory
                //at this point.
                TreeItem<String> foundTreeItem = treeViewFiles.getRoot().getChildren().get(i);
                CheckBoxTreeItem<String> checkBoxTreeItemRepresentation = (CheckBoxTreeItem<String>) foundTreeItem;
                checkBoxTreeItemRepresentation.setSelected(!isDeselectAll);
                treeViewFiles.getRoot().getChildren().set(i, checkBoxTreeItemRepresentation);
            }
        }
    }

    /**
     * Starts the tree preload service
     */
    private void startTreePreloadService() {
        TreePreloadService treePreloadService = new TreePreloadService();
        treePreloadService.setOnSucceeded(e -> mainViewTab.setContent(null));
        startupLabel.textProperty().bind(treePreloadService.messageProperty());
        treePreloadService.start();
    }

    /**
     * Initializes the accordion on the right pane
     */
    private void initializeAccordion() {
        preferencesAccordion.setExpandedPane(preferencesAccordion.getPanes().get(0));
    }

    /**
     * Activates the buttons on the right pane
     */
    private void activateButtons() {
        rankChoiceBox.setDisable(false);
        collapseAllButton.setDisable(false);
        LoadedData.getSelectedSamples().addListener((InvalidationListener) e -> displayInfoText());
    }

    /**
     * Initializes the rank selection toggle group and adds a listener to the rank selection
     */
    private void initializeRankChoiceBox() {
        rankChoiceBox.setDisable(true);
        rankChoiceBox.setItems(ranksList);

    }

    /**
     * Hides all the components of the analysis pane, since they should only be displayed when data is loaded
     */
    private void initializeAnalysisPane() {
        analysisPane.setVisible(false);

    }

    /**
     * Sets up listeners that update graph everytime the graph changes
     */
    private void initializeGraphAnalysis() {
        degreeDistributionChart.getXAxis().setLabel("Degree");
        degreeDistributionChart.getYAxis().setLabel("Node Fraction");

        posCorrelationLowerFilterProperty().addListener(observable -> updateView());
        posCorrelationUpperFilterProperty().addListener(observable -> updateView());
        negCorrelationLowerFilterProperty().addListener(observable -> updateView());
        negCorrelationUpperFilterProperty().addListener(observable -> updateView());
        maxPValueProperty().addListener(observable -> updateView());
        minFrequencyProperty().addListener(observable -> updateView());
        maxFrequencyProperty().addListener(observable -> updateView());

    }

    /**
     * updates the view of the whole analysis pane
     */
    private void updateView() {
        if (LoadedData.getTaxonGraph() == null) {
            return;
        }

        LoadedData.getTaxonGraph().filterEdges();
        LoadedData.getTaxonGraph().filterVertices();
        performGraphAnalysis();
        displayGraphAnalysis();
        displayInfoText();
        setHubsInView();
    }

    /**
     * creates the view of the calculated hubs
     */
    private void setHubsInView() {
        HashMap<TaxonNode, Integer> hubsList = AnalysisData.getAnalysis().getHubsList();
        HashMap<TaxonNode, MyVertex> taxonNodeToVertexMap = LoadedData.getTaxonGraph().getTaxonNodeToVertexMap();
        for (Map.Entry<TaxonNode, MyVertex> entry : taxonNodeToVertexMap.entrySet()) {
            if (hubsList.containsKey(entry.getKey()))
                entry.getValue().setIsHub(true);
            else
                entry.getValue().setIsHub(false);
        }
    }

    /**
     * Sets up listeners to call displayInfoText() whenever it is necessary
     */
    private void initializeInfoPane() {
        rankChoiceBox.valueProperty().addListener(e -> displayInfoText());
        compareSelectedSamplesButton.selectedProperty().addListener(e -> displayInfoText());
        abundancePlotButton.setDisable(true);

    }

    /**
     * initializes the bindings of the sliders and the analysis pane
     */
    private void initializeBindings() {
        //First, bind the LoadedData.analyzeAll boolean property to the radio buttons
        LoadedData.analyzeSelectedProperty().bind(compareSelectedSamplesButton.selectedProperty());

        //Since the slider value property is double and the text field property is a string, we need to convert them
        //Defining own class to avoid exceptions
        class MyNumberStringConverter extends NumberStringConverter {
            @Override
            public Number fromString(String value) {
                try {
                    return super.fromString(value);
                } catch (RuntimeException ex) {
                    return 0;
                }
            }
        }
        StringConverter<Number> converter = new MyNumberStringConverter();
        //Bind every slider to its corresponding text field and vice versa
        Bindings.bindBidirectional(minPosCorrelationText.textProperty(),
                posCorrelationRangeSlider.lowValueProperty(), converter);
        Bindings.bindBidirectional(maxPosCorrelationText.textProperty(),
                posCorrelationRangeSlider.highValueProperty(), converter);
        Bindings.bindBidirectional(minNegCorrelationText.textProperty(),
                negCorrelationRangeSlider.lowValueProperty(), converter);
        Bindings.bindBidirectional(maxNegCorrelationText.textProperty(),
                negCorrelationRangeSlider.highValueProperty(), converter);
        Bindings.bindBidirectional(maxPValueText.textProperty(), maxPValueSlider.valueProperty(), converter);
        Bindings.bindBidirectional(minFrequencyText.textProperty(), frequencyRangeSlider.lowValueProperty(),
                converter);
        Bindings.bindBidirectional(maxFrequencyText.textProperty(), frequencyRangeSlider.highValueProperty(),
                converter);
        Bindings.bindBidirectional(excludeFrequencyText.textProperty(), excludeFrequencySlider.valueProperty(),
                converter);

        //Bind the internal filter properties to the slider values
        AnalysisData.posCorrelationLowerFilterProperty().bind(posCorrelationRangeSlider.lowValueProperty());
        AnalysisData.posCorrelationUpperFilterProperty().bind(posCorrelationRangeSlider.highValueProperty());
        AnalysisData.negCorrelationLowerFilterProperty().bind(negCorrelationRangeSlider.lowValueProperty());
        AnalysisData.negCorrelationUpperFilterProperty().bind(negCorrelationRangeSlider.highValueProperty());
        AnalysisData.minFrequencyProperty().bind(frequencyRangeSlider.lowValueProperty());
        AnalysisData.maxFrequencyProperty().bind(frequencyRangeSlider.highValueProperty());
        AnalysisData.maxPValueProperty().bind(maxPValueSlider.valueProperty());
        AnalysisData.excludeFrequencyThresholdProperty().bind(excludeFrequencySlider.valueProperty());

        //The values of the negative slider can't be set to values below 0 via FXML for reasons beyond human understanding,
        // so we set them manually
        negCorrelationRangeSlider.setLowValue(-1);
        negCorrelationRangeSlider.setHighValue(-0.5);

        //We want the graph to be redone if one of the following occurs:
        //1. Radio button switches between "Analyze All" and "Analyze Selected"
        compareSelectedSamplesButton.selectedProperty().addListener(observable -> {
            if ((!compareSelectedSamplesButton.isSelected() || LoadedData.getSelectedSamples().size() >= 3)
                    && rankChoiceBox.getValue() != null)
                startAnalysis();
        });
        //2. Rank selection changes
        rankChoiceBox.valueProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue != null && LoadedData.getSamplesToAnalyze().size() >= 3) {
                AnalysisData.setLevel_of_analysis(newValue.toLowerCase());
                startAnalysis();
            }
        });
        //3. Sample selection changes while "Analyze Selected" is selected AND at least three samples are selected
        LoadedData.getSelectedSamples().addListener((InvalidationListener) observable -> {
            if (compareSelectedSamplesButton.isSelected() && LoadedData.getSelectedSamples().size() >= 3) {
                startAnalysis();
            }
        });
        //4. Correlation radio button is changed
        pearsonCorrelationButton.selectedProperty().addListener(o -> startAnalysis());
        spearmanCorrelationButton.selectedProperty().addListener(o -> startAnalysis());
        kendallCorrelationButton.selectedProperty().addListener(o -> startAnalysis());
        //5. Global frequency threshold is changed
        excludeFrequencySlider.valueProperty().addListener(o -> startAnalysis());
    }

    /**
     * Initialize advanced setting elements to only be visible when checkbox is activated
     */
    public void initializeGraphSettings() {

        labelAnimationSpeed.visibleProperty().bind(checkAdvancedGraphSettings.selectedProperty());
        sliderAnimationSpeed.visibleProperty().bind(checkAdvancedGraphSettings.selectedProperty());
        labelEdgeForce.visibleProperty().bind(checkAdvancedGraphSettings.selectedProperty());
        sliderEdgeForce.visibleProperty().bind(checkAdvancedGraphSettings.selectedProperty());
        labelNodeRepulsion.visibleProperty().bind(checkAdvancedGraphSettings.selectedProperty());
        sliderNodeRepulsion.visibleProperty().bind(checkAdvancedGraphSettings.selectedProperty());
        labelStretchParameter.visibleProperty().bind(checkAdvancedGraphSettings.selectedProperty());
        sliderStretchParameter.visibleProperty().bind(checkAdvancedGraphSettings.selectedProperty());
        buttonResetGraphDefaults.setOnAction(e -> setGraphSettingsDefault());
        setGraphSettingsDefault();
    }

    /**
     * Bind graph setting controls to MyGraphView instance.
     *
     * @param graphView instance to which controls are bound
     */
    public void bindGraphSettings(MyGraphView graphView) {
        // Bind Node Radius Slider to all Nodes in Graph
        for (Node node : graphView.getMyVertexViewGroup().getChildren()) {
            ((MyVertexView) node).getRadiusProperty().bind(sliderNodeRadius.valueProperty());
        }
        // Bind Edge Width Slider to all Edges in Graph
        for (Node node : graphView.getMyEdgeViewGroup().getChildren()) {
            ((MyEdgeView) node).getWidthProperty().bind(sliderEdgeWidth.valueProperty()
                    .multiply(Math.abs(((MyEdgeView) node).getMyEdge().getCorrelation()) + 0.1));
        }

        /**buttonPauseAnimation.setOnAction(e -> {
         boolean isRunning = graphView.animationService.isRunning();
         if (isRunning) graphView.animationService.cancel();
         if (!isRunning) graphView.animationService.restart();
         }); **/

        sliderEdgeLength.lowValueProperty().addListener((o, e, n) -> {
            graphView.animationService.setEdgeLengthLow(n.doubleValue());
        });

        sliderEdgeLength.highValueProperty().addListener((o, e, n) -> {
            graphView.animationService.setEdgeLengthHigh(n.doubleValue());
        });

        sliderNodeRepulsion.valueProperty().addListener((o, e, n) -> {
            graphView.animationService.setNodeRepulsion(n.intValue());
        });

        sliderStretchParameter.valueProperty().addListener((o, e, n) -> {
            graphView.animationService.setStretchForce(n.doubleValue());
        });

        sliderEdgeForce.valueProperty().addListener((o, e, n) -> {
            graphView.animationService.setForce(n.doubleValue());
        });

        sliderAnimationSpeed.valueProperty().addListener((o, e, n) -> {
            Double fr = sliderAnimationSpeed.getMax() - n.doubleValue();
            graphView.animationService.setFrameRate(fr.intValue());
        });

        buttonPauseAnimation.selectedProperty().bindBidirectional(graphView.pausedProperty);

    }

    @FXML
    /**
     * resets the filter values to the default values
     */
    public void resetFilterSettings() {
        posCorrelationRangeSlider.setLowValue(DEFAULT_POSITIVE_CORRELATION_LOW);
        posCorrelationRangeSlider.setHighValue(DEFAULT_POSITIVE_CORRELATION_HIGH);
        negCorrelationRangeSlider.setLowValue(DEFAULT_NEGATIVE_CORRELATION_LOW);
        negCorrelationRangeSlider.setHighValue(DEFAULT_NEGATIVE_CORRELATION_HIGH);
        maxPValueSlider.setValue(DEFAULT_MAX_P_VALUE_SLIDER);
        frequencyRangeSlider.setLowValue(DEFAULT_FREQUENCY_RANGE_SLIDER_LOW);
        frequencyRangeSlider.setHighValue(DEFAULT_FREQUENCY_RANGE_SLIDER_HIGH);
    }

    /**
     * Initializes the radio buttons for the colour selection
     */
    private void bindColourSettings(MyGraphView graphView) {
        //Node settings
        colourRadioNodeFix.setOnAction(e -> {
            setPalette(nodeColourCombo, Palette.CATEGORICAL);
            graphView.setNodeAttribute("fix");
            graphView.setNodeColour(nodeColourCombo.getValue());
        });

        colourRadioNodeParent.setOnAction(e -> {
            setPalette(nodeColourCombo, Palette.CATEGORICAL);
            graphView.setNodeAttribute("parentName");
            graphView.setNodeColour(nodeColourCombo.getValue());
        });

        colourRadioNodeFrequency.setOnAction(e -> {
            setPalette(nodeColourCombo, Palette.SEQ);
            graphView.setNodeAttribute("frequency");
            graphView.setNodeColour(nodeColourCombo.getValue());
        });

        nodeColourCombo.setOnAction(e -> {
            graphView.setNodeColour(nodeColourCombo.getValue());
        });

        // Edge settings
        colourRadioEdgeCorrelation.setOnAction(e -> {
            setPalette(edgeColourCombo, Palette.DIV);
            graphView.setEdgeAttribute("correlation");
            graphView.setEdgeColour(edgeColourCombo.getValue());
        });

        /**colourRadioEdgeDistance.setOnAction( e-> {
            setPalette(edgeColourCombo, Palette.DIV);
            graphView.set
        }); **/

        colourRadioEdgePvalue.setOnAction(e -> {
            setPalette(edgeColourCombo, Palette.SEQ);
            graphView.setEdgeAttribute("pValue");
            graphView.setEdgeColour(edgeColourCombo.getValue());
        });

        edgeColourCombo.setOnAction(e -> {
            graphView.setEdgeColour(edgeColourCombo.getValue());
        });
    }

    /**
     * Helper function to set new Palette Spetrum in ComboBox
     * @param cb ComboBox which should be adapted
     * @param p EnumSet of Palette values to be displayed
     */
    private void setPalette(ComboBox cb, EnumSet p) {
        cb.setItems(FXCollections.observableArrayList(p));
        cb.getSelectionModel().selectFirst();
    }

    /**
     * Initializes custom Combo selection Box for color gradient choosers
     */
    private void initializeColorComboBox() {
        // Node Parameters
        nodeColourCombo.setPrefWidth(200);
        colourNodeComboContainer.getChildren().add(nodeColourCombo);

        // Edge parameters
        edgeColourCombo.setPrefWidth(200);
        colourEdgeComboContainer.getChildren().add(edgeColourCombo);
    }

    //ALERTS

    /**
     * creates the file not found alert box
     */
    private void fileNotFoundAlertBox() {
        fileNotFoundAlert = new Alert(Alert.AlertType.ERROR);
        fileNotFoundAlert.setTitle("File not found");
        fileNotFoundAlert.setHeaderText("File not found");
        fileNotFoundAlert.setContentText("Could not find the file you were looking for");

        //style the alert
        DialogPane dialogPane = fileNotFoundAlert.getDialogPane();
        dialogPane.getStylesheets().add("/UI/alertStyle.css");

        Exception fileNotFoundException = new FileNotFoundException("Could not find your selected file");

        //create expandable exception
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        fileNotFoundException.printStackTrace(pw);
        String exceptionText = sw.toString();

        Label label = new Label("The exception stacktrace was: ");

        TextArea textArea = new TextArea(exceptionText);
        textArea.setEditable(false);
        textArea.setWrapText(true);

        textArea.setMaxWidth(Double.MAX_VALUE);
        textArea.setMaxHeight(Double.MAX_VALUE);
        GridPane.setVgrow(textArea, Priority.ALWAYS);
        GridPane.setHgrow(textArea, Priority.ALWAYS);

        GridPane expContent = new GridPane();
        expContent.setMaxWidth(Double.MAX_VALUE);
        expContent.add(label, 0, 0);
        expContent.add(textArea, 0, 1);

        // Set expandable Exception into the dialog pane.
        fileNotFoundAlert.getDialogPane().setExpandableContent(expContent);
        fileNotFoundAlert.showAndWait();
    }

    @FXML
    /**
     *
     * Shows information about the software.
     */
    private void showAboutAlert() {
        Hyperlink hyperlink = new Hyperlink();
        hyperlink.setText("https://github.com/jmueller95/CORNETTO");
        Text text = new Text("Cornetto is a modern tool to visualize and calculate correlations between"
                + "samples.\nIt was created in 2017 by students of the group of Professor Huson in Tbingen.\nThe group"
                + "was supervised by Caner Bagci.\n\n" + "This project is licensed under the MIT License.\n\n"
                + "For more information go to: ");

        TextFlow textFlow = new TextFlow(text, hyperlink);

        text.setWrappingWidth(500);
        aboutAlert = new Alert(Alert.AlertType.INFORMATION);
        aboutAlert.setTitle("About " + GlobalConstants.NAME_OF_PROGRAM);
        aboutAlert.setHeaderText("What is " + GlobalConstants.NAME_OF_PROGRAM);
        aboutAlert.getDialogPane().setContent(textFlow);
        aboutAlert.show();
    }

    /**
     * Prompts an alert if the user tries to load a file that does not match the requirements.
     */
    //TODO: If multiple files are wrong, not every file should get its own alert.
    private void showWrongFileAlert() {
        wrongFileAlert = new Alert(Alert.AlertType.ERROR);
        wrongFileAlert.setTitle("File not loaded");
        wrongFileAlert.setHeaderText("Invalid file.");
        wrongFileAlert.setContentText("You tried to load a file with a wrong file type.");
        wrongFileAlert.show();
    }

    /**
     * Prompts an alert that the selected file is already part of the current project.
     */
    private void showFileAlreadyLoadedAlert(ArrayList<String> fileNames) {
        if (fileNames.size() > 1) {
            fileNames = fileNames.stream().map(string -> "'" + string + "'")
                    .collect(Collectors.toCollection(ArrayList::new));
        }
        String name = String.join(",\n", fileNames);

        String oneFileAlreadyLoaded = "The file \n'" + name + "'\nis already loaded in your project.";
        String multipleFilesAlreadyLoaded = "The files\n" + name + "\n are already loaded in your project.";
        fileAlreadyLoadedAlert = new Alert(Alert.AlertType.ERROR);
        fileAlreadyLoadedAlert.setTitle("File not loaded.");
        fileAlreadyLoadedAlert
                .setContentText(fileNames.size() == 1 ? oneFileAlreadyLoaded : multipleFilesAlreadyLoaded);
        fileAlreadyLoadedAlert.show();
    }

    /**
     * Prompts an alert telling the user that the chosen data is not sufficient for an analysis
     */
    private void showInsufficientDataAlert() {
        insufficientDataAlert = new Alert(Alert.AlertType.ERROR);
        insufficientDataAlert.setTitle("Insufficient data for Analysis");
        insufficientDataAlert.setHeaderText("Not enough data to perform the analysis.");
        insufficientDataAlert.setContentText("Try choosing a more specific rank!");
        insufficientDataAlert.show();
    }

    /**
     * Opens new PopUp Window with Image Export options.
     */
    @FXML
    private void exportImages() {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("src/UI/exportImageGUI"));
            loader.setLocation(
                    new URL("file:" + new File("").getCanonicalPath().concat("/src/UI/exportImageGUI.fxml")));
            //Parent root = loader.load();
            ExportImageController exportImageController = new ExportImageController(viewPane);
            //ExportImageController exportImageController = loader.getController();
            //exportImageController.setViewPane(viewPane);
            loader.setController(exportImageController);
            Parent root = loader.load();
            exportImagesStage = new Stage();
            exportImagesStage.setTitle("Export Image");
            Scene exportImageScene = new Scene(root, 300, 200);
            exportImagesStage.setScene(exportImageScene);
            exportImageScene.getStylesheets().add(GlobalConstants.DARKTHEME);
            exportImagesStage.show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * method for the quit button
     * opens an alert box
     * asks whether to save/quit/continue running the program
     */
    private void confirmQuit() {
        confirmQuitAlert = new Alert(Alert.AlertType.CONFIRMATION);
        confirmQuitAlert.setTitle("Remember to save your files!");
        confirmQuitAlert.setHeaderText("Quit?");
        confirmQuitAlert.setContentText("Do you really want to quit?");

        ButtonType quitButton = new ButtonType("Quit");
        ButtonType saveAndQuitButton = new ButtonType("Save and quit");
        ButtonType cancelButton = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);

        confirmQuitAlert.initModality(Modality.APPLICATION_MODAL);
        confirmQuitAlert.initOwner(getPrimaryStage());
        confirmQuitAlert.getButtonTypes().setAll(quitButton, saveAndQuitButton, cancelButton);

        Optional<ButtonType> result = confirmQuitAlert.showAndWait();

        if (result.get() == quitButton) {
            Platform.exit();
        } else if (result.get() == saveAndQuitButton) {
            confirmQuitAlert.close();
        } else {
            confirmQuitAlert.close();
        }
    }

    @FXML
    /**
     * runs when the optionsButton is clicked
     * opens the Options stage
     *
     */
    private void optionsButtonClicked() {
        FXMLLoader fxmlLoader = new FXMLLoader();
        Parent root = null;
        try {
            fxmlLoader.setLocation(Main.class.getClassLoader().getResource("UI/optionsGui.fxml"));
            root = fxmlLoader.load();

        } catch (Exception e) {
            try {
                fxmlLoader.setLocation(
                        new URL("file:" + new File("").getCanonicalPath().concat("src/UI/optionsGui.fxml")));
                root = fxmlLoader.load();
            } catch (Exception e2) {
                System.err.println("ERROR: Couldn't find optionsGui.fxml!");
            }
        }

        this.optionsStage = new Stage();
        optionsStage.setTitle("Options");
        Scene optionsScene = new Scene(root, 1000, 700);
        optionsStage.setScene(optionsScene);
        optionsScene.getStylesheets().add(GlobalConstants.LIGHTTHEME);
        optionsStage.show();
    }

    /**
     * handler for Window Events
     * opens an alert which asks whether to quit or not
     * use when setting handlers of the X button
     */
    public EventHandler<WindowEvent> confirmCloseEventHandler = (WindowEvent event) -> {
        confirmQuitAlert = new Alert(Alert.AlertType.CONFIRMATION);
        confirmQuitAlert.setTitle("Remember to save your files!");
        confirmQuitAlert.setHeaderText("Quit?");
        confirmQuitAlert.setContentText("Do you really want to quit?");

        ButtonType quitButton = new ButtonType("Quit");
        ButtonType saveAndQuitButton = new ButtonType("Save and quit");
        ButtonType cancelButton = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);

        confirmQuitAlert.initModality(Modality.APPLICATION_MODAL);
        confirmQuitAlert.initOwner(getPrimaryStage());
        confirmQuitAlert.getButtonTypes().setAll(quitButton, saveAndQuitButton, cancelButton);

        Optional<ButtonType> result = confirmQuitAlert.showAndWait();

        if (result.get() == quitButton) {
            Platform.exit();
        } else if (result.get() == saveAndQuitButton) {
            Platform.exit();
        } else {
            event.consume();
        }
    };

    /**
     * Sets all slider elements in the graph settings menu to default values
     */
    private void setGraphSettingsDefault() {
        sliderAnimationSpeed.setValue(DEFAULT_ANIMATION_SPEED);
        sliderEdgeForce.setValue(DEFAULT_SLIDER_EDGE_FORCE);
        sliderNodeRepulsion.setValue(DEFAULT_NODE_REPULSION);
        sliderStretchParameter.setValue(DEFAULT_SLIDER_STRECH_PARAMETER);
        sliderNodeRadius.setValue(DEFAULT_SLIDER_NODE_RADIUS);
        sliderEdgeWidth.setValue(DEFAULT_SLIDER_EDGE_WIDTH);
        sliderEdgeLength.setLowValue(DEFAULT_SLIDER_EDGE_LENGTH_LOW);
        sliderEdgeLength.setHighValue(DEFAULT_SLIDER_EDGE_LENGTH_HIGH);
        buttonPauseAnimation.setSelected(true);
    }

    public static Stage getOptionsStage() {
        return optionsStage;
    }
}