diff --git a/.gitignore b/.gitignore index c8a24c7..4963691 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,7 @@ gradle-app.setting *.iml *.ipr *.iws + +api/out +cli/out +client/out diff --git a/README.md b/README.md index 2c047b7..b58257a 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,40 @@ ## What's New +### Release Notes - 0.2.0 - August 17th, 2018 + +#### Improvements + +* “Input Data” screen has been overhauled + * Datasets now display as a spreadsheet + * This spreadsheet is editable and updates automatically in local memory + * Edited dataset can be exported to a file + * Will prompt to create a new project when loading dataset if one doesn’t exist + * Will alert the user to select a project when importing a dataset, if one exists and is not selected +* Under the hood changes to the data structure used to store and process datasets in memory +* Improved support for header rows in the dataset + * If the first line of the dataset contains all strings, it is assumed to be a header row. +* This is a temporary workaround and is due to change + * Otherwise, it is assumed there is no header row and default headers will be given + * Default headers follow the A-Z, AA-ZZ format +* Data normalisation and smoothing have been updated to work with new data structure + * Other pre-processing features to come + * The smoothing function by default takes the 2 neighbours on either side of the current value and takes the average of those numbers, which becomes the new value for that index. This continues for all values in the feature and updates values once complete +* Target Expression now updates when a dataset is loaded + * Displays in the form of y=f(x,w,…,z) + * Where y is the last feature in the dataset + * x,w,…,z are each of the other features + * This is a GUI change only, which represents the default target expression used by the search + * The GUI and API are unlinked in this respect. The target expression still cannot be customized in the search + * This will be fixed in the next release +* A variety of new building blocks for expressions have been added under the hood. + * A list of these should be available soon + * These are not currently functional in the GUI + + +#### Fixes +* “Load Dataset” button now working + ### Release Notes - 0.1.0 - May 28th, 2018 #### Improvements @@ -17,6 +51,8 @@ * The back end has changed so much practically everything's been fixed in some way. Or at least broken more nicely. +## Building + ### Compiling All commands are assumed to be made from the root project folder diff --git a/api/build.gradle b/api/build.gradle index 3ac4b3a..19d37e6 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -9,10 +9,6 @@ compileTestJava { options.compilerArgs << "-Xlint:all" << "-Xlint:-processing" } -test { - useJUnitPlatform() -} - dependencies { testCompileOnly group: 'org.apiguardian', name: 'apiguardian-api', version: '1.0.0' testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.1.0' @@ -21,7 +17,7 @@ dependencies { testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.1.0' } -version = '0.1.0' +version = '0.2.0' jar { baseName = 'iconic-api' diff --git a/api/src/main/java/org/iconic/ea/data/DataManager.java b/api/src/main/java/org/iconic/ea/data/DataManager.java index f1ebe52..5689278 100644 --- a/api/src/main/java/org/iconic/ea/data/DataManager.java +++ b/api/src/main/java/org/iconic/ea/data/DataManager.java @@ -32,6 +32,35 @@ public DataManager(String fileName) { } } + public void saveDatasetToFile(File fileName) throws IOException { + FileWriter fileWriter = null; + + try{ + fileWriter = new FileWriter(fileName); + for(int i = 0; i < sampleSize; i++){ + List currentRow = getSampleRow(i); + for(int j = 0; j < currentRow.size()-1; j ++) { + fileWriter.append(String.valueOf(currentRow.get(j))); + fileWriter.append(","); + } + fileWriter.append(String.valueOf(currentRow.get(currentRow.size()-1))); + fileWriter.append(System.getProperty("line.separator")); + } + } catch (Exception ex){ + log.error("Error when saving file. File: {}", () -> fileName); + log.error("Exception: {}", ex); + } finally{ + try{ + fileWriter.flush(); + fileWriter.close(); + } catch (IOException ex){ + log.error("Error when closing FileWriter. File: {}", () -> fileName); + log.error("Exception: {}", ex); + } + } + + } + private void importData(String fileName) throws IOException { this.fileName = fileName; sampleSize = 0; @@ -210,7 +239,22 @@ public int getSampleSize() { return sampleSize; } - public List getSampleHeaders() { - return sampleHeaders; + public List getSampleHeaders() { return sampleHeaders; } + + // Replaces all data within a header column, identified by headerIndex + // e.g. updating stored data after normalisation + public void setSampleColumn(int headerIndex, ArrayList values) { + for (int i = 0; i < sampleSize; i++) { + Number value = values.get(i); + dataset.get(sampleHeaders.get(headerIndex)).updateModifiedSample(i, value); + } + } + + // Resets all data within a header column, identified by headerIndex, to + // the original data entered by the user + public void resetSampleColumn(int headerIndex) { + for (int i=0; i < sampleSize; i++) { + dataset.get(sampleHeaders.get(headerIndex)).resetModifiedSample(i); + } } } \ No newline at end of file diff --git a/api/src/main/java/org/iconic/ea/data/FeatureClass.java b/api/src/main/java/org/iconic/ea/data/FeatureClass.java index 8f93fbb..ad73352 100644 --- a/api/src/main/java/org/iconic/ea/data/FeatureClass.java +++ b/api/src/main/java/org/iconic/ea/data/FeatureClass.java @@ -54,4 +54,12 @@ public boolean isOutput() { public void setOutput(boolean value) { this.output = value; } + + public void updateModifiedSample(int index, T value) { + modifiedSamples.set(index, value); + } + + public void resetModifiedSample(int index) { + modifiedSamples.set(index, originalSamples.get(index)); + } } \ No newline at end of file diff --git a/api/src/main/java/org/iconic/ea/data/NumericFeatureClass.java b/api/src/main/java/org/iconic/ea/data/NumericFeatureClass.java index 1a0326e..4d6d995 100644 --- a/api/src/main/java/org/iconic/ea/data/NumericFeatureClass.java +++ b/api/src/main/java/org/iconic/ea/data/NumericFeatureClass.java @@ -6,7 +6,7 @@ public class NumericFeatureClass extends FeatureClass { public NumericFeatureClass(boolean output) { super(output); - getPreprocessors().add(new Normalise()); - getPreprocessors().add(new Offset()); + //getPreprocessors().add(new Normalise()); + //getPreprocessors().add(new Smooth()); } } \ No newline at end of file diff --git a/api/src/main/java/org/iconic/ea/data/preprocessing/Normalise.java b/api/src/main/java/org/iconic/ea/data/preprocessing/Normalise.java index 970fbe6..8ba9b30 100644 --- a/api/src/main/java/org/iconic/ea/data/preprocessing/Normalise.java +++ b/api/src/main/java/org/iconic/ea/data/preprocessing/Normalise.java @@ -2,15 +2,10 @@ import java.util.ArrayList; -public class Normalise extends Preprocessor{ - private Number oldMin, oldMax, newMin, newMax; - - /** - * - * @param values - */ - @Override - public void apply(ArrayList values) { +public class Normalise { + private static Number oldMin, oldMax; + + public static ArrayList apply(ArrayList values, Number newMin, Number newMax) { oldMin = values.get(0); oldMax = values.get(0); @@ -28,14 +23,14 @@ public void apply(ArrayList values) { Number value = map(values.get(i), oldMin, oldMax, newMin, newMax); values.set(i, value); } + + return values; } - private double map(Number value, Number oldMin, Number oldMax, Number newMin, Number newMax) { + private static double map(Number value, Number oldMin, Number oldMax, Number newMin, Number newMax) { return newMin.doubleValue() + ((value.doubleValue() - oldMin.doubleValue()) * (newMax.doubleValue() - newMin.doubleValue())) / (oldMax.doubleValue() - oldMin.doubleValue()); } - - public void setRange(Number newMin, Number newMax) { this.newMin = newMin; this.newMax = newMax; } } \ No newline at end of file diff --git a/api/src/main/java/org/iconic/ea/data/preprocessing/Smooth.java b/api/src/main/java/org/iconic/ea/data/preprocessing/Smooth.java new file mode 100644 index 0000000..b8be94d --- /dev/null +++ b/api/src/main/java/org/iconic/ea/data/preprocessing/Smooth.java @@ -0,0 +1,72 @@ +package org.iconic.ea.data.preprocessing; + +import java.util.ArrayList; +import java.lang.*; + +public class Smooth { + private static int N = 2; // N is the number of neighboring data points on either side of the value + + /** + *

+ * Smooths the values of an Array. Alternatively known as "Moving Average Filtering". + *

+ * + *

+ * Given an array of values, the function will take the 'N' neighbouring values on either side of the index, take + * the sum of all these values, then update the value of the index to the average of the sum. + *

+ * + *

+ * If the span window is outside of the Array bounds, the window will become the minimum reach of both sides. + *

+ * + * @param values the array that will be smoothed + */ + public static ArrayList apply(ArrayList values) { + ArrayList newValues = new ArrayList<>(); + + for (int i = 0; i < values.size(); i++) { + // The index of the lowest span from the point + int lowerBound = Math.max(0, i - N); + + // The size of the lowest span + int lowerBoundRange = i - lowerBound; + + // The index of the highest span from the point + int upperBound = Math.min(values.size() - 1, i + N); + + // The size of the highest span + int upperBoundRange = upperBound - i; + + // The smallest span of reach for both sides + int span = Math.min(lowerBoundRange, upperBoundRange); + + // Find the sum of all values in the span + Double sum = 0.0; + for (int j = i - span; j <= i + span; j++) { + sum += values.get(j).doubleValue(); + } + + // Average of the span size + newValues.add( 1.0 / (2.0 * span + 1.0) * sum ); + } + + // Update the old values to the new values + for (int i = 0; i < values.size(); i++) { + values.set(i, newValues.get(i)); + } + + return newValues; + } + + /** + *

+ * Sets the window size of values to use when smoothing the array of data. + *

+ * + * @param neighbourSize the number of neighbours on either side of each point to be used for smoothing + */ + public void setNeighbourSize(int neighbourSize) { + this.N = neighbourSize; + } +} \ No newline at end of file diff --git a/api/src/test/java/org/iconic/ea/operator/primitive/AdditionTest.java b/api/src/test/java/org/iconic/ea/operator/primitive/AdditionTest.java index 498268f..a0f34f3 100644 --- a/api/src/test/java/org/iconic/ea/operator/primitive/AdditionTest.java +++ b/api/src/test/java/org/iconic/ea/operator/primitive/AdditionTest.java @@ -11,31 +11,33 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class AdditionTest { +/** + * Test class for {@link org.iconic.ea.operator.primitive.Addition} + * @author Jasbir Shah + */ +class AdditionTest { @DisplayName("Test addition using doubles") @MethodSource("doubleListProvider") @ParameterizedTest void addDoublesTest(final List args, final double expected) { final FunctionalPrimitive add = new Addition(); - final double delta = 0.001d; + final double delta = 0.001; final double actual = add.apply(args); assertEquals(expected, actual, delta); } /** - *

- * Returns a stream of double n-tuples, where the last member of the tuple is the sum of all the preceeding - * members - *

+ *

Returns a stream of double n-tuples, where the last member of the tuple is the sum of all the preceeding + * members

* * @return a stream of double n-tuples */ private static Stream doubleListProvider() { return Stream.of( - Arguments.of(Arrays.asList(1.d, -1.d), 0.d), - Arguments.of(Arrays.asList(0.d, -1.d), -1.d), - Arguments.of(Arrays.asList(0.d, 1.d), 1.d) + Arguments.of(Arrays.asList(1.0, -1.0), 0.0), + Arguments.of(Arrays.asList(0.0, -1.0), -1.0), + Arguments.of(Arrays.asList(0.0, 1.0), 1.0) ); } } diff --git a/api/src/test/java/org/iconic/ea/operator/primitive/ConstantTest.java b/api/src/test/java/org/iconic/ea/operator/primitive/ConstantTest.java new file mode 100644 index 0000000..17134ce --- /dev/null +++ b/api/src/test/java/org/iconic/ea/operator/primitive/ConstantTest.java @@ -0,0 +1,41 @@ +package org.iconic.ea.operator.primitive; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.ArrayList; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test class for {@link org.iconic.ea.operator.primitive.Constant} + * @author Scott Walker + */ +class ConstantTest { + @DisplayName("Test constants using doubles") + @MethodSource("doubleListProvider") + @ParameterizedTest + void constantDoublesTest(final double arg, final double expected) { + final FunctionalPrimitive constant = new Constant<>(arg); + final double delta = 0.001; + final double actual = constant.apply(new ArrayList<>()); + + assertEquals(expected, actual, delta); + } + + /** + *

Returns a stream of two identical doubles

+ * + * @return a stream of double n-tuples + */ + private static Stream doubleListProvider() { + return Stream.of( + Arguments.of(1.0, 1.0), + Arguments.of(0.0, 0.0), + Arguments.of(10.0, 10.0) + ); + } +} \ No newline at end of file diff --git a/api/src/test/java/org/iconic/ea/operator/primitive/CosTest.java b/api/src/test/java/org/iconic/ea/operator/primitive/CosTest.java new file mode 100644 index 0000000..b77d88c --- /dev/null +++ b/api/src/test/java/org/iconic/ea/operator/primitive/CosTest.java @@ -0,0 +1,49 @@ +package org.iconic.ea.operator.primitive; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test class for {@link org.iconic.ea.operator.primitive.Cos} + * @author Scott Walker + */ +class CosTest { + + /** Hard-coded value of pi for testing purposes */ + private static final double PI = 3.14159; + + @DisplayName("Test cosine using doubles") + @MethodSource("doubleListProvider") + @ParameterizedTest + void cosDoublesTest(final List args, final double expected) { + final FunctionalPrimitive cos = new Cos(); + final double delta = 0.001; + final double actual = cos.apply(args); + + assertEquals(expected, actual, delta); + } + + /** + *

Returns a stream of double n-tuples, where the last number cos of the first

+ * + * @return a stream of double n-tuples + */ + private static Stream doubleListProvider() { + return Stream.of( + Arguments.of(Arrays.asList(0.0), 1.0), + Arguments.of(Arrays.asList(PI/2), 0.0), + Arguments.of(Arrays.asList(PI), -1.0), + Arguments.of(Arrays.asList(-PI/2), 0.0), + Arguments.of(Arrays.asList(PI/4), 0.70711), + Arguments.of(Arrays.asList(-PI/4), 0.70711) + ); + } +} \ No newline at end of file diff --git a/api/src/test/java/org/iconic/ea/operator/primitive/DivisionTest.java b/api/src/test/java/org/iconic/ea/operator/primitive/DivisionTest.java index cfe3881..4707488 100644 --- a/api/src/test/java/org/iconic/ea/operator/primitive/DivisionTest.java +++ b/api/src/test/java/org/iconic/ea/operator/primitive/DivisionTest.java @@ -11,33 +11,36 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class DivisionTest { +/** + * Test class for {@link org.iconic.ea.operator.primitive.Division} + * @author Jasbir Shah + */ +class DivisionTest { @DisplayName("Test division using doubles") @MethodSource("doubleListProvider") @ParameterizedTest void divideDoublesTest(final List args, final double expected) { final FunctionalPrimitive division = new Division(); - final double delta = 0.001d; + final double delta = 0.001; final double actual = division.apply(args); assertEquals(expected, actual, delta); } /** - *

- * Returns a stream of double n-tuples, where the last member of the tuple is the sum of all the preceeding - * members - *

+ *

Returns a stream of double n-tuples, where the last member is the result of dividing + * the first by the second

* * @return a stream of double n-tuples */ private static Stream doubleListProvider() { return Stream.of( - Arguments.of(Arrays.asList(1.d, 1.d), 1.d), - Arguments.of(Arrays.asList(1.d, -1.d), -1.d), - Arguments.of(Arrays.asList(0.d, -1.d), 0.d), - Arguments.of(Arrays.asList(0.d, 1.d), 0.d), - Arguments.of(Arrays.asList(1.d, 0.d), 1.d) + Arguments.of(Arrays.asList(1.0, 1.0), 1.0), + Arguments.of(Arrays.asList(1.0, -1.0), -1.0), + Arguments.of(Arrays.asList(0.0, -1.0), 0.0), + Arguments.of(Arrays.asList(0.0, 1.0), 0.0), + Arguments.of(Arrays.asList(1.0, 0.0), 1.0), + Arguments.of(Arrays.asList(1024.0, 512.0), 2.0) ); } } diff --git a/api/src/test/java/org/iconic/ea/operator/primitive/MultiplicationTest.java b/api/src/test/java/org/iconic/ea/operator/primitive/MultiplicationTest.java index ec822d6..1803fc1 100644 --- a/api/src/test/java/org/iconic/ea/operator/primitive/MultiplicationTest.java +++ b/api/src/test/java/org/iconic/ea/operator/primitive/MultiplicationTest.java @@ -11,32 +11,35 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class MultiplicationTest { +/** + * Test class for {@link org.iconic.ea.operator.primitive.Multiplication} + * @author Jasbir Shah + */ +class MultiplicationTest { @DisplayName("Test multiplication using doubles") @MethodSource("doubleListProvider") @ParameterizedTest void multiplyDoublesTest(final List args, final double expected) { final FunctionalPrimitive multiplication = new Multiplication(); - final double delta = 0.001d; + final double delta = 0.001; final double actual = multiplication.apply(args); assertEquals(expected, actual, delta); } /** - *

- * Returns a stream of double n-tuples, where the last member of the tuple is the sum of all the preceeding - * members - *

+ *

Returns a stream of double n-tuples, where the last member is the multiplication + * of the first two

* * @return a stream of double n-tuples */ private static Stream doubleListProvider() { return Stream.of( - Arguments.of(Arrays.asList(1.d, 1.d), 1.d), - Arguments.of(Arrays.asList(1.d, -1.d), -1.d), - Arguments.of(Arrays.asList(0.d, -1.d), 0.d), - Arguments.of(Arrays.asList(0.d, 1.d), 0.d) + Arguments.of(Arrays.asList(1.0, 1.0), 1.0), + Arguments.of(Arrays.asList(1.0, -1.0), -1.0), + Arguments.of(Arrays.asList(0.0, -1.0), 0.0), + Arguments.of(Arrays.asList(0.0, 1.0), 0.0), + Arguments.of(Arrays.asList(32.0, 32.0), 1024.0) ); } } diff --git a/api/src/test/java/org/iconic/ea/operator/primitive/PowerTest.java b/api/src/test/java/org/iconic/ea/operator/primitive/PowerTest.java new file mode 100644 index 0000000..828c2cc --- /dev/null +++ b/api/src/test/java/org/iconic/ea/operator/primitive/PowerTest.java @@ -0,0 +1,47 @@ +package org.iconic.ea.operator.primitive; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test class for {@link org.iconic.ea.operator.primitive.Power} + * @author Scott Walker + */ +class PowerTest { + @DisplayName("Test powers using doubles") + @MethodSource("doubleListProvider") + @ParameterizedTest + void powerDoublesTest(final List args, final double expected) { + final FunctionalPrimitive power = new Power(); + final double delta = 0.001; + final double actual = power.apply(args); + + assertEquals(expected, actual, delta); + } + + /** + *

Returns a stream of double n-tuples, where the last number is the first raised to the power + * of the second

+ * + * @return a stream of double n-tuples + */ + private static Stream doubleListProvider() { + return Stream.of( + Arguments.of(Arrays.asList(1.0, 1.0), 1.0), + Arguments.of(Arrays.asList(100.0, 0.0), 1.0), + Arguments.of(Arrays.asList(2.0, 2.0), 4.0), + Arguments.of(Arrays.asList(32.0, 2.0), 1024.0), + Arguments.of(Arrays.asList(10.0, 4.0), 10000.0), + Arguments.of(Arrays.asList(-2.0, 2.0), 4.0), + Arguments.of(Arrays.asList(-2.0, 3.0), -8.0) + ); + } +} \ No newline at end of file diff --git a/api/src/test/java/org/iconic/ea/operator/primitive/RootTest.java b/api/src/test/java/org/iconic/ea/operator/primitive/RootTest.java new file mode 100644 index 0000000..e95fd75 --- /dev/null +++ b/api/src/test/java/org/iconic/ea/operator/primitive/RootTest.java @@ -0,0 +1,49 @@ +package org.iconic.ea.operator.primitive; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test class for {@link org.iconic.ea.operator.primitive.Root} + * @author Scott Walker + */ +@Disabled +class RootTest { + @DisplayName("Test root using doubles") + @MethodSource("doubleListProvider") + @ParameterizedTest + void rootDoublesTest(final List args, final double expected) { + final FunctionalPrimitive root = new Root(); + final double delta = 0.001; + final double actual = root.apply(args); + + assertEquals(expected, actual, delta); + } + + /** + *

Returns a stream of double n-tuples, where the last number is the first raised to the inverse power + * of the second (the nth root)

+ * + * @return a stream of double n-tuples + */ + private static Stream doubleListProvider() { + return Stream.of( + Arguments.of(Arrays.asList(1.0, 1.0), 1.0), + Arguments.of(Arrays.asList(100.0, 1.0), 100.0), + Arguments.of(Arrays.asList(4.0, 2.0), 2.0), + Arguments.of(Arrays.asList(1024.0, 2.0), 32.0), + Arguments.of(Arrays.asList(1024.0, 10.0), 2.0), + Arguments.of(Arrays.asList(-8.0, 3.0), -2.0), + Arguments.of(Arrays.asList(8.0, -3.0), 0.5) + ); + } +} \ No newline at end of file diff --git a/api/src/test/java/org/iconic/ea/operator/primitive/SinTest.java b/api/src/test/java/org/iconic/ea/operator/primitive/SinTest.java new file mode 100644 index 0000000..cc86bf9 --- /dev/null +++ b/api/src/test/java/org/iconic/ea/operator/primitive/SinTest.java @@ -0,0 +1,49 @@ +package org.iconic.ea.operator.primitive; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test class for {@link org.iconic.ea.operator.primitive.Sin} + * @author Scott Walker + */ +class SinTest { + + /** Hard-coded value of pi for testing purposes */ + private static final double PI = 3.14159; + + @DisplayName("Test sine using doubles") + @MethodSource("doubleListProvider") + @ParameterizedTest + void sinDoublesTest(final List args, final double expected) { + final FunctionalPrimitive sin = new Sin(); + final double delta = 0.001; + final double actual = sin.apply(args); + + assertEquals(expected, actual, delta); + } + + /** + *

Returns a stream of double n-tuples, where the last number sin of the first

+ * + * @return a stream of double n-tuples + */ + private static Stream doubleListProvider() { + return Stream.of( + Arguments.of(Arrays.asList(0.0), 0.0), + Arguments.of(Arrays.asList(PI/2), 1.0), + Arguments.of(Arrays.asList(PI), 0.0), + Arguments.of(Arrays.asList(-PI/2), -1.0), + Arguments.of(Arrays.asList(PI/4), 0.70711), + Arguments.of(Arrays.asList(-PI/4), -0.70711) + ); + } +} \ No newline at end of file diff --git a/api/src/test/java/org/iconic/ea/operator/primitive/SubtractionTest.java b/api/src/test/java/org/iconic/ea/operator/primitive/SubtractionTest.java index 5a7b0b9..326e7bf 100644 --- a/api/src/test/java/org/iconic/ea/operator/primitive/SubtractionTest.java +++ b/api/src/test/java/org/iconic/ea/operator/primitive/SubtractionTest.java @@ -11,33 +11,36 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class SubtractionTest { +/** + * Test class for {@link org.iconic.ea.operator.primitive.Subtraction} + * @author Jasbir Shah + */ +class SubtractionTest { @DisplayName("Test subtraction using doubles") @MethodSource("doubleListProvider") @ParameterizedTest void subtractDoublesTest(final List args, final double expected) { final FunctionalPrimitive subtract = new Subtraction(); - final double delta = 0.001d; + final double delta = 0.001; final double actual = subtract.apply(args); assertEquals(expected, actual, delta); } /** - *

- * Returns a stream of double n-tuples, where the last member of the tuple is the sum of all the preceeding - * members - *

+ *

Returns a stream of double n-tuples, where the last member is the subraction of + * all numbers in the list (a - b - c - d - ...)

* * @return a stream of double n-tuples */ private static Stream doubleListProvider() { return Stream.of( - Arguments.of(Arrays.asList(1.d, 1.d), 0.d), - Arguments.of(Arrays.asList(1.d, -1.d), 2.d), - Arguments.of(Arrays.asList(0.d, -1.d), 1.d), - Arguments.of(Arrays.asList(0.d, 1.d), -1.d), - Arguments.of(Arrays.asList(87.d, 27.d, 9.8d, 4.d, 28.d, 93.d, 92.d, 63.d, 55.d, 30.d, -105.3d), -209.5d) + Arguments.of(Arrays.asList(1.0, 1.0), 0.0), + Arguments.of(Arrays.asList(1.0, -1.0), 2.0), + Arguments.of(Arrays.asList(0.0, -1.0), 1.0), + Arguments.of(Arrays.asList(0.0, 1.0), -1.0), + Arguments.of(Arrays.asList(87.0, 27.0, 9.8, 4.0, 28.0, 93.0, 92.0, 63.0, 55.0, 30.0, -105.3), -209.5d), + Arguments.of(Arrays.asList(10.0, 1.0, 2.0, 3.0, 4.0), 0.0) ); } } diff --git a/api/src/test/java/org/iconic/ea/operator/primitive/TanTest.java b/api/src/test/java/org/iconic/ea/operator/primitive/TanTest.java new file mode 100644 index 0000000..faa339e --- /dev/null +++ b/api/src/test/java/org/iconic/ea/operator/primitive/TanTest.java @@ -0,0 +1,48 @@ +package org.iconic.ea.operator.primitive; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test class for {@link org.iconic.ea.operator.primitive.Tan} + * @author Scott Walker + */ +class TanTest { + + /** Hard-coded value of pi for testing purposes */ + private static final double PI = 3.14159; + + @DisplayName("Test tan using doubles") + @MethodSource("doubleListProvider") + @ParameterizedTest + void tanDoublesTest(final List args, final double expected) { + final FunctionalPrimitive tan = new Tan(); + final double delta = 0.001; + final double actual = tan.apply(args); + + assertEquals(expected, actual, delta); + } + + /** + *

Returns a stream of double n-tuples, where the last number tan of the first

+ * + * @return a stream of double n-tuples + */ + private static Stream doubleListProvider() { + return Stream.of( + Arguments.of(Arrays.asList(0.0), 0.0), + Arguments.of(Arrays.asList(PI), 0.0), + Arguments.of(Arrays.asList(PI/4), 1.0), + Arguments.of(Arrays.asList(-PI/4), -1.0), + Arguments.of(Arrays.asList(PI/3), 1.73205) + ); + } +} \ No newline at end of file diff --git a/cli/build.gradle b/cli/build.gradle index 69b2542..5fc9e8c 100644 --- a/cli/build.gradle +++ b/cli/build.gradle @@ -16,7 +16,7 @@ dependencies { jar { baseName = 'iconic-cli' - version = '0.1.0' + version = '0.2.0' // The Java plugin doesn't specify any of the // manifest attributes, so we need at least diff --git a/client/build.gradle b/client/build.gradle index 94428c0..1f2fea0 100644 --- a/client/build.gradle +++ b/client/build.gradle @@ -11,7 +11,7 @@ buildscript { } } -version = '0.1.0' +version = '0.2.0' dependencies { compile 'org.controlsfx:controlsfx:8.40.14' diff --git a/client/src/main/java/org/iconic/config/InMemoryModule.java b/client/src/main/java/org/iconic/config/InMemoryModule.java index 9f4ba6f..9a856a5 100644 --- a/client/src/main/java/org/iconic/config/InMemoryModule.java +++ b/client/src/main/java/org/iconic/config/InMemoryModule.java @@ -3,6 +3,8 @@ import com.google.inject.AbstractModule; import org.iconic.project.ProjectService; import org.iconic.project.TransientProjectService; +import org.iconic.project.definition.DefineSearchController; +import org.iconic.project.definition.DefineSearchService; import org.iconic.project.search.SearchService; import org.iconic.project.search.TransientSearchService; import org.iconic.workspace.DefaultWorkspaceService; @@ -24,5 +26,6 @@ protected void configure() { bind(ProjectService.class).to(TransientProjectService.class); bind(SearchService.class).to(TransientSearchService.class); bind(WorkspaceService.class).to(DefaultWorkspaceService.class); + bind(DefineSearchService.class).to(DefineSearchController.class); } } diff --git a/client/src/main/java/org/iconic/project/DefineSearchController.java b/client/src/main/java/org/iconic/project/DefineSearchController.java deleted file mode 100644 index 0addd0e..0000000 --- a/client/src/main/java/org/iconic/project/DefineSearchController.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.iconic.project; - -import javafx.fxml.Initializable; - -import java.net.URL; -import java.util.ResourceBundle; - -public class DefineSearchController implements Initializable { - - @Override - public void initialize(URL location, ResourceBundle resources) { - - } -} diff --git a/client/src/main/java/org/iconic/project/InputDataController.java b/client/src/main/java/org/iconic/project/InputDataController.java new file mode 100644 index 0000000..dfbb693 --- /dev/null +++ b/client/src/main/java/org/iconic/project/InputDataController.java @@ -0,0 +1,209 @@ +package org.iconic.project; + +import javafx.beans.InvalidationListener; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Alert; +import javafx.scene.control.TextInputDialog; +import javafx.stage.FileChooser; +import lombok.val; +import org.controlsfx.control.spreadsheet.*; +import org.iconic.ea.data.DataManager; +import org.iconic.project.dataset.DatasetModel; +import org.iconic.workspace.WorkspaceService; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.Optional; +import java.util.ResourceBundle; + +public class InputDataController implements Initializable { + private final ProjectService projectService; + private final WorkspaceService workspaceService; + + @FXML + private SpreadsheetView spreadsheet; + + @Inject + public InputDataController(final ProjectService projectService, final WorkspaceService workspaceService) { + this.projectService = projectService; + this.workspaceService = workspaceService; + + + // Update the workspace whenever the active dataset changes + InvalidationListener selectionChangedListener = observable -> updateWorkspace(); + getWorkspaceService().activeWorkspaceItemProperty().addListener(selectionChangedListener); + } + + private void updateWorkspace() { + val item = getWorkspaceService().getActiveWorkspaceItem(); + + // If no dataset is selected clear the UI + if (!(item instanceof DatasetModel)) { + clearUI(); + } + else{ + fillSpreadsheetByRow(); + } + } + + + + private void clearUI() { + spreadsheet.setGrid(new GridBase(0,0)); + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + updateWorkspace(); + } + + private void fillSpreadsheetByRow(){ + Optional> dataManager = getDataManager(); + int rowCount = dataManager.get().getSampleSize(); + int columnCount = dataManager.get().getFeatureSize(); + GridBase grid = new GridBase(rowCount, columnCount); + + ObservableList> rows = FXCollections.observableArrayList(); + for (int row = 0; row < grid.getRowCount(); ++row) { + final ObservableList list = FXCollections.observableArrayList(); + for (int column = 0; column < grid.getColumnCount(); ++column) { + String cellContents = String.valueOf(dataManager.get().getSampleRow(row).get(column)); + SpreadsheetCell nextCell = SpreadsheetCellType.STRING.createCell(row, column, 1, 1, cellContents); + int changedRow = row; + int changedColumn = column; + nextCell.itemProperty().addListener((observable, oldValue, newValue) -> { + try{ + Number newNumber = Double.parseDouble(String.valueOf(newValue)); + updateProjectDataset(changedRow,changedColumn,newNumber); + }catch (Exception e) { + //handle exception here + nextCell.setItem(oldValue); + } + }); + list.add(nextCell); + } + rows.add(list); + } + grid.setRows(rows); + + spreadsheet.setGrid(grid); + } + + private void updateProjectDataset(int row, int column, Number newValue){ + Optional> dataManager = getDataManager(); + dataManager.get().getSampleColumn(column).set(row,newValue); + } + + private Optional> getDataManager() { + Displayable item = getWorkspaceService().getActiveWorkspaceItem(); + + if (item instanceof DatasetModel) { + DatasetModel dataset = (DatasetModel) item; + return Optional.of(dataset.getDataManager()); + } else { + return Optional.empty(); + } + } + + /** + *

+ * Returns the workspace service of this controller + *

+ * + * @return the workspace service of the controller + */ + private WorkspaceService getWorkspaceService() { + return workspaceService; + } + + /** + *

+ * Returns the project service of this controller + *

+ * + * @return the project service of the controller + */ + private ProjectService getProjectService() { + return projectService; + } + + public void saveDataset(ActionEvent actionEvent) throws IOException { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Save Dataset"); + // Show only .txt and .csv files + fileChooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("Text files", "*.txt", "*.csv") + ); + + // Show the file dialog over the parent window + File f = fileChooser.showSaveDialog(spreadsheet.getScene().getWindow()); + + if(f != null){ + getDataManager().get().saveDatasetToFile(f); + } + } + + /** + *

+ * Opens a file dialog for choosing a dataset to import. + *

+ * + * @param actionEvent The action that triggered the event + */ + public void importDataset(ActionEvent actionEvent) { + //Temporary fix to account for project not being selected in menu + if((!(getWorkspaceService().getActiveWorkspaceItem() instanceof ProjectModel) && getProjectService().getProjects().size() > 0)){ + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setTitle("Please Select a Project"); + alert.setHeaderText(null); + alert.setContentText("Please select/highlight the project in the menu that you would like add your new dataset to."); + alert.showAndWait(); + return; + } + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Import Dataset"); + // Show only .txt and .csv files + fileChooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("Text files", "*.txt", "*.csv") + ); + + // Show the file dialog over the parent window + File f = fileChooser.showOpenDialog(spreadsheet.getScene().getWindow()); + + // If the user selected a file add it to the current active item as a dataset + if (f != null) { + DatasetModel dataset = new DatasetModel(f.getName(), f.getAbsolutePath()); + Displayable currentItem = getWorkspaceService().getActiveWorkspaceItem(); + // If the current active item isn't a project create a new project + if ((!(currentItem instanceof ProjectModel)) && (!(currentItem instanceof DatasetModel))){ + TextInputDialog dialog = new TextInputDialog("Project1"); + dialog.setTitle("Create a Project"); + dialog.setHeaderText("To load a dataset, you need to create a project."); + dialog.setContentText("Please enter a project name:"); + // Create the project only if a name was provided + dialog.showAndWait().ifPresent( + name -> { + final ProjectModel project = ProjectModel.builder().name(name).build(); + getWorkspaceService().setActiveWorkspaceItem(project); + getProjectService().getProjects().add(project); + } + ); + } + //If statement required to check if user clicked cancel in previous dialog + if (getWorkspaceService().getActiveWorkspaceItem() instanceof ProjectModel) { + ProjectModel project = (ProjectModel) getWorkspaceService().getActiveWorkspaceItem(); + ProjectModel newProject = project.toBuilder().dataset(dataset).build(); + getWorkspaceService().setActiveWorkspaceItem(null); + getProjectService().getProjects().set(getProjectService().getProjects().indexOf(project),newProject); + getWorkspaceService().setActiveWorkspaceItem(newProject); + } + //TODO If a project is not highlighted get the project associated with the current view + } + } +} diff --git a/client/src/main/java/org/iconic/project/ProjectTreeController.java b/client/src/main/java/org/iconic/project/ProjectTreeController.java index 48a1b32..16f0de1 100644 --- a/client/src/main/java/org/iconic/project/ProjectTreeController.java +++ b/client/src/main/java/org/iconic/project/ProjectTreeController.java @@ -211,8 +211,7 @@ private void importDataset(ActionEvent actionEvent) { val newProject = project.toBuilder().dataset(dataset).build(); getWorkspaceService().setActiveWorkspaceItem(null); - getProjectService().getProjects().remove(project); - getProjectService().getProjects().add(newProject); + getProjectService().getProjects().set(getProjectService().getProjects().indexOf(project),(ProjectModel) newProject); getWorkspaceService().setActiveWorkspaceItem(newProject); } } @@ -244,8 +243,7 @@ private void renameProject(ActionEvent actionEvent) { if (!name.trim().equals(project.getLabel())) { val newProject = project.toBuilder().name(name).build(); getWorkspaceService().setActiveWorkspaceItem(null); - getProjectService().getProjects().remove(project); - getProjectService().getProjects().add(newProject); + getProjectService().getProjects().set(getProjectService().getProjects().indexOf(project),(ProjectModel) newProject); getWorkspaceService().setActiveWorkspaceItem(newProject); } } diff --git a/client/src/main/java/org/iconic/project/definition/DefineSearchController.java b/client/src/main/java/org/iconic/project/definition/DefineSearchController.java new file mode 100644 index 0000000..84d5179 --- /dev/null +++ b/client/src/main/java/org/iconic/project/definition/DefineSearchController.java @@ -0,0 +1,133 @@ +package org.iconic.project.definition; + +import com.google.inject.Inject; +import javafx.beans.InvalidationListener; +import javafx.fxml.Initializable; +import javafx.fxml.FXML; +import javafx.scene.control.TextField; + +import lombok.NonNull; +import lombok.extern.log4j.Log4j2; + +import java.net.URL; +import java.util.List; +import java.util.HashMap; +import java.util.Optional; +import java.util.ResourceBundle; +import java.util.regex.Pattern; + +import org.iconic.ea.data.DataManager; +import org.iconic.project.Displayable; +import org.iconic.project.ProjectService; +import org.iconic.project.dataset.DatasetModel; +import org.iconic.workspace.WorkspaceService; + +@Log4j2 +public class DefineSearchController implements Initializable, DefineSearchService { + + private final ProjectService projectService; + private final WorkspaceService workspaceService; + + private HashMap functionDefinitions; + + @FXML + private TextField tfTargetExpression; + + @Inject + public DefineSearchController(final ProjectService projectService, final WorkspaceService workspaceService) + { + this.projectService = projectService; + this.workspaceService = workspaceService; + this.functionDefinitions = new HashMap(); + + InvalidationListener selectionChangedListener = observable -> loadFunction(); + workspaceService.activeWorkspaceItemProperty().addListener(selectionChangedListener); + } + + @Override + public void initialize(URL location, ResourceBundle resources) {} + + @Override + public String getFunction() + { + String functionStr = null; + + Optional> dataset = getDataManager(); + + if(dataset.isPresent()) { + // Get the ID of the dataset + String[] splitString = dataset.toString().split("@"); + String datasetID = splitString[splitString.length - 1].replace("]", ""); // There's a trailing ']' from the toString + + // Get the dataset, if exists + functionStr = functionDefinitions.get(datasetID); + } + + return functionStr; + } + + private void loadFunction() + { + Optional> dataset = getDataManager(); + + if(dataset.isPresent()) + { + // Get the ID of the dataset + String[] splitString = dataset.toString().split("@"); + String datasetID = splitString[splitString.length - 1].replace("]", ""); // There's a trailing ']' from the toString + + // No need to redefine the function if one already exists, just insert instead + String functionStr = functionDefinitions.get(datasetID); + if(functionStr == null) + { + List headers = dataset.get().getSampleHeaders(); + + if(!headers.isEmpty()) + { + functionStr = generateDefaultFunction(headers); + + // Save the function defined in the hashmap of all the functions definitions + functionDefinitions.put(datasetID, functionStr); + } + else + { + log.error("No headers found in this dataset"); + } + } + + // NOTE(Meyer): Must check if not null otherwise injection will cause an NPE (it's dumb, I know) + if(tfTargetExpression != null) + { + tfTargetExpression.setText(functionStr); + } + } + } + + private String generateDefaultFunction(List headers) + { + // Get the last value in arraylist to get the target variable + String functionResultStr = "(" + headers.get(headers.size() - 1) + ")"; + // Get the first value in list to start the function going + String functionDefinitionStr = "(" + headers.get(0) + ")"; + + // Get all the values bar the first and last column, which we already have + for(int i = 1; i < headers.size() - 1; i++) + { + functionDefinitionStr += ", (" + headers.get(i) + ")"; + } + + return functionResultStr + " = f(" + functionDefinitionStr + ")"; + } + + private Optional> getDataManager() { + Displayable item = workspaceService.getActiveWorkspaceItem(); + + if (item instanceof DatasetModel) { + DatasetModel dataset = (DatasetModel) item; + return Optional.of(dataset.getDataManager()); + } else { + return Optional.empty(); + } + } +} +; \ No newline at end of file diff --git a/client/src/main/java/org/iconic/project/definition/DefineSearchService.java b/client/src/main/java/org/iconic/project/definition/DefineSearchService.java new file mode 100644 index 0000000..e83c293 --- /dev/null +++ b/client/src/main/java/org/iconic/project/definition/DefineSearchService.java @@ -0,0 +1,12 @@ +package org.iconic.project.definition; + +public interface DefineSearchService { + /** + *

+ * Returns a list of projects owned by this service. + *

+ * + * @return The function defined by the user + */ + String getFunction(); +} diff --git a/client/src/main/java/org/iconic/project/search/SearchModel.java b/client/src/main/java/org/iconic/project/search/SearchModel.java index 0603b94..21e61cf 100644 --- a/client/src/main/java/org/iconic/project/search/SearchModel.java +++ b/client/src/main/java/org/iconic/project/search/SearchModel.java @@ -48,7 +48,7 @@ public SearchModel(@NonNull final DatasetModel datasetModel) { ExpressionChromosomeFactory supplier = new ExpressionChromosomeFactory<>( 10, - datasetModel.getDataManager().getFeatureSize() + datasetModel.getDataManager().getFeatureSize() - 1 ); // Add in the functions the chromosomes can use @@ -73,8 +73,9 @@ public SearchModel(@NonNull final DatasetModel datasetModel) { // Add in the objectives the algorithm should aim for ea.addObjective( - new DefaultObjective<>( - new MeanSquaredError(), datasetModel.getDataManager()) + new DefaultObjective<>( + new MeanSquaredError(), datasetModel.getDataManager() + ) ); } diff --git a/client/src/main/java/org/iconic/workspace/WorkspaceController.java b/client/src/main/java/org/iconic/workspace/WorkspaceController.java index 3de4021..7b3399e 100644 --- a/client/src/main/java/org/iconic/workspace/WorkspaceController.java +++ b/client/src/main/java/org/iconic/workspace/WorkspaceController.java @@ -21,12 +21,17 @@ import org.iconic.ea.data.DataManager; import org.iconic.project.Displayable; import org.iconic.project.dataset.DatasetModel; +import org.iconic.project.definition.DefineSearchService; import org.iconic.project.search.SearchModel; import org.iconic.project.search.SearchService; +import org.iconic.ea.data.preprocessing.Normalise; +import org.iconic.ea.data.preprocessing.Smooth; import java.net.URL; import java.util.Optional; import java.util.ResourceBundle; +import java.util.*; +import java.util.ArrayList; /** *

@@ -38,8 +43,9 @@ */ @Log4j2 public class WorkspaceController implements Initializable { - private final WorkspaceService workspaceService; private final SearchService searchService; + private final WorkspaceService workspaceService; + private final DefineSearchService defineSearchService; @FXML private Button btnSearch; @@ -70,9 +76,13 @@ public class WorkspaceController implements Initializable { @FXML private CheckBox cbFilter; @FXML + private VBox vbFilter; + @FXML private TextField tfNormaliseMin; @FXML private TextField tfNormaliseMax; + @FXML + private TextField tfTargetExpression; @Getter(AccessLevel.PRIVATE) @@ -88,9 +98,10 @@ public class WorkspaceController implements Initializable { *

*/ @Inject - public WorkspaceController(final WorkspaceService workspaceService, final SearchService searchService) { - this.workspaceService = workspaceService; + public WorkspaceController(final WorkspaceService workspaceService, final SearchService searchService, final DefineSearchService defineSearchService) { + this.defineSearchService = defineSearchService; this.searchService = searchService; + this.workspaceService = workspaceService; this.defaultName = ""; this.defaultWelcomeMessage = "Select a dataset on the left to get started."; @@ -118,6 +129,7 @@ public void initialize(URL arg1, ResourceBundle arg2) { addListenerToHideElement(cbHandleMissingValues, vbHandleMissingValues); addListenerToHideElement(cbRemoveOutliers, vbRemoveOutliers); addListenerToHideElement(cbNormalise, vbNormalise); + addListenerToHideElement(cbFilter, vbFilter); } private void addListenerToHideElement(CheckBox cb, VBox vb) { @@ -140,6 +152,9 @@ public void startSearch(ActionEvent actionEvent) { // Check that there's an active dataset before starting the search if (item instanceof DatasetModel) { + // TODO(Meyer): Use the function defined to determine what data is used, and what to calculate to + log.info("Function for use: " + defineSearchService.getFunction()); + DatasetModel dataset = (DatasetModel) item; SearchModel search = getSearchService().searchesProperty().get(dataset.getId()); @@ -188,6 +203,9 @@ public void stopSearch(ActionEvent actionEvent) { public void featureSelected(int selectedIndex) { // Update lcDataView if (lcDataView != null) { + // Stores the currently selected header in the lvFeatures list + String selectedHeader = ""; + // defining a series XYChart.Series series = new XYChart.Series<>(); @@ -196,14 +214,23 @@ public void featureSelected(int selectedIndex) { Optional> dataManager = getDataManager(); if (dataManager.isPresent() && selectedIndex >= 0) { -// List values = dataManager.get().getSampleColumn(selectedIndex); + ArrayList values = dataManager.get().getSampleColumn(selectedIndex); -// for (int sample = 0; sample < values.size(); sample++) { -// double value = values.get(sample); -// series.getData().add(new XYChart.Data<>(sample, value)); -// } + for (int sample = 0; sample < values.size(); sample++) { + double value = values.get(sample).doubleValue(); + series.getData().add(new XYChart.Data<>(sample, value)); + } + + selectedHeader = String.valueOf(dataManager.get().getSampleHeaders().get(selectedIndex)); } lcDataView.getData().add(series); + + // Updates the selected header in the transformation text fields + cbSmoothData.setText("Smooth data points of (" + selectedHeader + ")"); + cbHandleMissingValues.setText("Handle missing values of (" + selectedHeader + ")"); + cbRemoveOutliers.setText("Remove outliers of (" + selectedHeader + ")"); + cbNormalise.setText("Normalise scale and offset of (" + selectedHeader + ")"); + cbFilter.setText("Filter data of (" + selectedHeader + ")"); } } @@ -215,23 +242,47 @@ public void normalizeDatasetFeature() { Optional> dataManager = getDataManager(); if (cbNormalise.isSelected() && dataManager.isPresent()) { -// List values = dataManager.get().getSampleColumn(selectedIndex); + ArrayList values = dataManager.get().getSampleColumn(selectedIndex); try { double min = Double.parseDouble(tfNormaliseMin.getText()); double max = Double.parseDouble(tfNormaliseMax.getText()); if (min < max) { -// values = Normalise.apply(values, min, max); - -// dataManager.get().setSampleColumn(selectedIndex, values); + values = Normalise.apply(values, min, max); + dataManager.get().setSampleColumn(selectedIndex, values); } } catch (Exception e) { log.error("Min and Max values must be a Number"); } } // Otherwise reset the sample column -// else dataManager.ifPresent(doubleDataManager -> doubleDataManager.resetSampleColumn(selectedIndex)); + else if (dataManager.isPresent()) { + dataManager.get().resetSampleColumn(selectedIndex); + } + + featureSelected(selectedIndex); + } + } + } + + public void smoothDatasetFeature() { + if (lvFeatures != null) { + int selectedIndex = lvFeatures.getSelectionModel().getSelectedIndex(); + + if (selectedIndex != -1) { + Optional> dataManager = getDataManager(); + + if (cbSmoothData.isSelected() && dataManager.isPresent()) { + ArrayList values = dataManager.get().getSampleColumn(selectedIndex); + + values = Smooth.apply(values); + dataManager.get().setSampleColumn(selectedIndex, values); + } + // Otherwise reset the sample column + else if (dataManager.isPresent()) { + dataManager.get().resetSampleColumn(selectedIndex); + } featureSelected(selectedIndex); } diff --git a/client/src/main/resources/views/workspace/DefineSearchView.fxml b/client/src/main/resources/views/workspace/DefineSearchView.fxml index 0b1ae9f..97463be 100644 --- a/client/src/main/resources/views/workspace/DefineSearchView.fxml +++ b/client/src/main/resources/views/workspace/DefineSearchView.fxml @@ -12,7 +12,7 @@ - + @@ -31,7 +31,7 @@ - + diff --git a/client/src/main/resources/views/workspace/InputDataView.fxml b/client/src/main/resources/views/workspace/InputDataView.fxml index dba8e2e..2dd9724 100644 --- a/client/src/main/resources/views/workspace/InputDataView.fxml +++ b/client/src/main/resources/views/workspace/InputDataView.fxml @@ -6,31 +6,40 @@ - - - - - - - - + + + + + + + + + + + + + +