diff --git a/PL2/PL2-gui/pom.xml b/PL2/PL2-gui/pom.xml
index a98d3d25..2b6f24d0 100644
--- a/PL2/PL2-gui/pom.xml
+++ b/PL2/PL2-gui/pom.xml
@@ -40,6 +40,11 @@
mockito-core
1.10.19
+
+ org.controlsfx
+ controlsfx
+ 8.40.10
+
\ No newline at end of file
diff --git a/PL2/PL2-gui/src/main/java/nl/tudelft/pl2016gr2/gui/view/SearchPaneController.java b/PL2/PL2-gui/src/main/java/nl/tudelft/pl2016gr2/gui/view/SearchPaneController.java
index a15a0d42..c12817c2 100644
--- a/PL2/PL2-gui/src/main/java/nl/tudelft/pl2016gr2/gui/view/SearchPaneController.java
+++ b/PL2/PL2-gui/src/main/java/nl/tudelft/pl2016gr2/gui/view/SearchPaneController.java
@@ -1,22 +1,21 @@
package nl.tudelft.pl2016gr2.gui.view;
+import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
-import javafx.collections.ObservableSet;
-import javafx.collections.SetChangeListener;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
-import javafx.event.ActionEvent;
-import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
+import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.Button;
-import javafx.scene.control.ComboBox;
+import javafx.scene.control.Label;
import javafx.scene.control.SelectionMode;
+import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
@@ -25,21 +24,33 @@
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
+import javafx.scene.layout.Background;
+import javafx.scene.layout.BackgroundFill;
+import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import nl.tudelft.pl2016gr2.gui.view.graph.GraphPaneController;
import nl.tudelft.pl2016gr2.gui.view.selection.SelectionManager;
import nl.tudelft.pl2016gr2.model.GenomeMap;
import nl.tudelft.pl2016gr2.model.MetaData;
+import nl.tudelft.pl2016gr2.model.metadata.LineageColor;
+import org.controlsfx.control.CheckComboBox;
import java.net.URL;
+import java.util.Arrays;
import java.util.Collection;
-import java.util.HashSet;
+import java.util.HashMap;
+import java.util.Map;
import java.util.ResourceBundle;
-import java.util.Set;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
+/**
+ * This is the controller for the searchPane.fxml.
+ *
+ *
+ * It allows searching through {@link MetaData}s.
+ *
+ */
@SuppressWarnings("AbbreviationAsWordInName")
public class SearchPaneController implements Initializable {
@@ -53,23 +64,10 @@ public class SearchPaneController implements Initializable {
@FXML private TableColumn specimenIdColumn;
@FXML private TableColumn specimentTypeColumn;
- @FXML private TableColumn genotypicDSTPatternColumn;
- @FXML private TableColumn phenotypicDSTPatternColumn;
- @FXML private TableColumn lineageColumn;
+ @FXML private TableColumn lineageColumn;
- private final ObservableSet setGenotypicDSTpattern
- = FXCollections.observableSet(new HashSet<>());
- private final ObservableSet setPhenotypicDSTPattern
- = FXCollections.observableSet(new HashSet<>());
-
- @FXML
- private ComboBox genotypicDSTPatternComboBox;
- @FXML
- private Button genotypicDSTPatternButton;
@FXML
- private ComboBox phenotypicDSTPatternComboBox;
- @FXML
- private Button phenotypicDSTPatternButton;
+ private GridPane categoricalGridPane;
@FXML
private TextField goToField;
@@ -80,6 +78,8 @@ public class SearchPaneController implements Initializable {
private final FilteredList filteredData = new FilteredList<>(masterData, p -> true);
+ private Map map = new HashMap<>();
+
private SelectionManager selectionManager;
private GraphPaneController graphPaneController;
@@ -103,6 +103,32 @@ public void initialize(URL url, ResourceBundle resourceBundle) {
initializeExtendedSearchPane();
}
+ /**
+ * This class is a helper class to represent a categorical property.
+ *
+ *
+ * It holds the combobox together with a name.
+ *
+ */
+ private static class CategoricalProperty {
+
+ final String name;
+ final CheckComboBox checkComboBox;
+
+ CategoricalProperty(String name) {
+ this.name = name;
+ checkComboBox = new CheckComboBox<>();
+ checkComboBox.setMaxWidth(150);
+ }
+
+ void addValue(String value) {
+ if (!checkComboBox.getItems().contains(value)) {
+ checkComboBox.getItems().add(value);
+ }
+ }
+
+ }
+
@SuppressWarnings("checkstyle:MethodLength")
private void initializeTable() {
// from http://code.makery.ch/blog/javafx-8-tableview-sorting-filtering/
@@ -122,19 +148,30 @@ private void initializeTable() {
cellData -> new SimpleStringProperty(cellData.getValue().specimenId));
specimentTypeColumn.setCellValueFactory(
cellData -> new SimpleStringProperty(cellData.getValue().specimenType));
- genotypicDSTPatternColumn.setCellValueFactory(
- cellData -> new SimpleStringProperty(cellData.getValue().genotypicDSTPattern));
- phenotypicDSTPatternColumn.setCellValueFactory(
- cellData -> new SimpleStringProperty(cellData.getValue().phenotypicDSTPattern));
- lineageColumn.setCellValueFactory(
- cellData -> new SimpleStringProperty(cellData.getValue().lineage));
+
+ lineageColumn.setCellValueFactory(cellData ->
+ new SimpleObjectProperty<>(LineageColor.toLineage(cellData.getValue().lineage)));
+ lineageColumn.setCellFactory(metaDataStringTableColumn ->
+ new TableCell() {
+ @Override
+ protected void updateItem(LineageColor item, boolean empty) {
+ super.updateItem(item, empty);
+ Color color = Color.TRANSPARENT;
+ if (!empty && item != null) {
+ color = item.getColor();
+ }
+ setBackground(new Background(new BackgroundFill(
+ color, null, Insets.EMPTY
+ )));
+ }
+ }
+ );
// Set the filter Predicate whenever the filter changes.
filterField.textProperty().addListener((observable, oldValue, newValue) -> {
updateTable();
});
-
// Wrap the FilteredList in a SortedList.
SortedList sortedData = new SortedList<>(filteredData);
@@ -180,34 +217,124 @@ public void setup(SelectionManager selectionManager, GraphPaneController graphPa
/**
* Call this whenever data changes.
*/
+ @SuppressWarnings("checkstyle:MethodLength")
private void updateTable() {
+ annotationTable.getSelectionModel().clearSelection();
filteredData.setPredicate(annotation -> {
- // If filter text is empty
- String searchString = filterField.getText();
- if (isNotSelected(genotypicDSTPatternComboBox, annotation.genotypicDSTPattern)
- || isNotSelected(phenotypicDSTPatternComboBox, annotation.phenotypicDSTPattern)) {
- return false;
+ for (CategoricalProperty property : map.values()) {
+ String valueOfRow = getValueForColumnName(property.name, annotation);
+ ObservableList items = property.checkComboBox.getCheckModel().getCheckedItems();
+ if (items.size() > 0 && !items.stream().anyMatch(s -> s.equals(valueOfRow))) {
+ return false;
+ }
}
- if (searchString == null || searchString.isEmpty()) {
+ String lowerCaseFilter = filterField.getText().toLowerCase();
+ if (lowerCaseFilter.isEmpty()) {
return true;
}
- String lowerCaseFilter = searchString.toLowerCase();
+ for (CategoricalProperty property : map.values()) {
+ String valueOfRow = getValueForColumnName(property.name, annotation);
+ if (valueOfRow.toLowerCase().contains(lowerCaseFilter)) {
+ return true;
+ }
+ }
+
return annotation.specimenId.toLowerCase().contains(lowerCaseFilter)
- || annotation.specimenType.toLowerCase().contains(lowerCaseFilter)
- || annotation.genotypicDSTPattern.toLowerCase().contains(lowerCaseFilter)
- || annotation.phenotypicDSTPattern.toLowerCase().contains(lowerCaseFilter);
+ || annotation.specimenType.toLowerCase().contains(lowerCaseFilter);
});
}
/**
- * @return true when ComboBox has NOT selected checkAgainst.
+ * This is a list of column names that are known to be categorical.
+ *
+ *
+ * In an ideal situation the Metadata class would also support this and
+ * the reader would be able to read any sheet.
+ *
+ */
+ private static final String[] knownCategoricalColumns = {
+ "genotypicDST",
+ "phenotypicDST",
+ "Lineage",
+ "hivStatus",
+ "cohort",
+ "studyGeographicDistrict",
+ "specimenType",
+ "microscopySmearStatus",
+ "dnaIsolation",
+ "capreomycin",
+ "ethambutol",
+ "ethionamide",
+ "isoniazid",
+ "kanamycin",
+ "pyrazinamide",
+ "ofloxacin",
+ "rifampin",
+ "streptomycin",
+ "digitalSpoligotype",
+ "sex",
+ };
+
+ /**
+ * This method take the correct field from the metaData class for a given name.
+ *
+ *
+ * See {@link #knownCategoricalColumns}, when the metadata+its reader would understand
+ * dynamic columns, this method would be unnecessary.
+ *
+ * @param columnName the name of the column.
+ * @param metaData the metaData instanec you'd like the value of.
+ * @return the value corresponding to the column.
*/
- private boolean isNotSelected(ComboBox comboBox, String checkAgainst) {
- String selectedItem = comboBox.getSelectionModel().getSelectedItem();
- return selectedItem != null && !checkAgainst.equals(selectedItem);
+ @SuppressWarnings("checkstyle:MethodLength")
+ private static String getValueForColumnName(String columnName, MetaData metaData) {
+ switch (columnName) {
+ case "genotypicDST":
+ return metaData.genotypicDSTPattern;
+ case "phenotypicDST":
+ return metaData.phenotypicDSTPattern;
+ case "Lineage":
+ return metaData.lineage;
+ case "hivStatus":
+ return metaData.hivStatus.name();
+ case "cohort":
+ return metaData.cohort;
+ case "studyGeographicDistrict":
+ return metaData.studyGeographicDistrict;
+ case "specimenType":
+ return metaData.specimenType;
+ case "microscopySmearStatus":
+ return metaData.microscopySmearStatus.name();
+ case "dnaIsolation":
+ return metaData.dnaIsolation.name();
+ case "capreomycin":
+ return metaData.capreomycin;
+ case "ethambutol":
+ return metaData.ethambutol;
+ case "ethionamide":
+ return metaData.ethionamide;
+ case "isoniazid":
+ return metaData.isoniazid;
+ case "kanamycin":
+ return metaData.kanamycin;
+ case "pyrazinamide":
+ return metaData.pyrazinamide;
+ case "ofloxacin":
+ return metaData.ofloxacin;
+ case "rifampin":
+ return metaData.rifampin;
+ case "streptomycin":
+ return metaData.streptomycin;
+ case "digitalSpoligotype":
+ return metaData.digitalSpoligotype;
+ case "sex":
+ return metaData.sex.name();
+ default:
+ throw new RuntimeException("Undefined column in temp method");
+ }
}
private void initializeGoTo() {
@@ -221,42 +348,64 @@ private void initializeGoTo() {
});
}
+ /**
+ * initializes the categorical comboboxes.
+ */
@SuppressWarnings("checkstyle:MethodLength")
private void initializeExtendedSearchPane() {
masterData.addListener((ListChangeListener) c -> {
- Stream.of(setGenotypicDSTpattern, setPhenotypicDSTPattern).forEach(Set::clear);
- c.getList().forEach(annotation -> {
- setGenotypicDSTpattern.add(annotation.genotypicDSTPattern);
- setPhenotypicDSTPattern.add(annotation.phenotypicDSTPattern);
+
+ categoricalGridPane.getChildren().clear();
+
+ map.clear();
+ masterData.forEach(metaData -> {
+ Arrays.stream(knownCategoricalColumns).forEach(columnName -> {
+ final CategoricalProperty property;
+ if (map.containsKey(columnName)) {
+ property = map.get(columnName);
+ } else {
+ property = new CategoricalProperty(columnName);
+ property.checkComboBox.getCheckModel().getCheckedItems()
+ .addListener((ListChangeListener super String>) change -> updateTable());
+ map.put(property.name, property);
+ }
+ property.addValue(getValueForColumnName(columnName, metaData));
+ });
});
- });
- // add set listeners
- setGenotypicDSTpattern.addListener(setChangeListener(genotypicDSTPatternComboBox));
- setPhenotypicDSTPattern.addListener(setChangeListener(phenotypicDSTPatternComboBox));
- // add comboBox listeners
- genotypicDSTPatternComboBox.valueProperty().addListener((observable, oldValue, newValue) -> {
- updateTable();
- });
- phenotypicDSTPatternComboBox.valueProperty().addListener((observable, oldValue, newValue) -> {
- updateTable();
- });
+ int rowIndex = 1;
+ for (CategoricalProperty property : map.values()) {
- // set button actions
- genotypicDSTPatternButton.setOnAction(getButtonAction(genotypicDSTPatternComboBox));
- phenotypicDSTPatternButton.setOnAction(getButtonAction(phenotypicDSTPatternComboBox));
- }
+ Label label = new Label(property.name);
+ Button button = new Button("clear");
- private SetChangeListener setChangeListener(ComboBox comboBox) {
- return change -> comboBox.setItems(FXCollections.observableArrayList(change.getSet()));
- }
+ button.setOnAction(actionEvent -> {
+ property.checkComboBox.getCheckModel().clearChecks();
+ });
+
+ GridPane.setColumnIndex(label, 1);
+ GridPane.setColumnIndex(property.checkComboBox, 2);
+ GridPane.setColumnIndex(button, 3);
+
+ GridPane.setRowIndex(label, rowIndex);
+ GridPane.setRowIndex(property.checkComboBox, rowIndex);
+ GridPane.setRowIndex(button, rowIndex);
+ rowIndex++;
- private EventHandler getButtonAction(ComboBox genotypicDSTPatternComboBox) {
- return event -> genotypicDSTPatternComboBox.getSelectionModel().clearSelection();
+ categoricalGridPane.getChildren().add(label);
+ categoricalGridPane.getChildren().add(property.checkComboBox);
+ categoricalGridPane.getChildren().add(button);
+ }
+
+ });
}
- public void setData(Collection metaDatas) {
+ /**
+ * Sets the data for this controller. When not called, no genomes are shown.
+ * @param metaData the metadata object.
+ */
+ public void setData(Collection metaData) {
masterData.clear();
- masterData.addAll(metaDatas);
+ masterData.addAll(metaData);
}
}
diff --git a/PL2/PL2-gui/src/main/resources/pages/SearchPane.fxml b/PL2/PL2-gui/src/main/resources/pages/SearchPane.fxml
index 9aae4fa2..97625869 100644
--- a/PL2/PL2-gui/src/main/resources/pages/SearchPane.fxml
+++ b/PL2/PL2-gui/src/main/resources/pages/SearchPane.fxml
@@ -1,7 +1,5 @@
-
-
@@ -12,14 +10,14 @@
+
+
-
+
-
-
@@ -31,17 +29,15 @@
+
-
-
-
-
-
-
+
+
+