diff --git a/README.md b/README.md index e25536b..c6cce7e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Work in progress UI for data coming from the rocket. # Add labels -Make a folder called `data` and a file called `labels[NUMBER]txt` in data. The number represents the data source index, starting at `0`. This should be a comma separated file of all the labels in the dataset. The number of labels should be the same as the `DATA_LENGTH` variable. +Make a folder called `data` and a file called `config.json` in data. This is a JSON file. Follow the format from `data-example/config.json`. For convenience, you can rename the `data-example` folder to `data` to get the labels. @@ -16,11 +16,15 @@ For convenience, you can rename the `data-example` folder to `data` to get the l Load this folder in your preferred Java IDE (ex. Eclipse). +Add the libraries from `libs` + +Setup the labels as described above. + Run `Main.java` # Simulation -Save your file in the `data` folder with the name `data[NUMBER].txt`. Replace `number` with an index of the data source starting at `0`. +Save your file in the `data` folder with the name `data[NUMBER].txt`. Replace `number` with an index of the data source starting at `0`. Make sure you have labels in `data/config.json` for every data file you have. # Window Management diff --git a/data-example/config.json b/data-example/config.json new file mode 100644 index 0000000..d3d617e --- /dev/null +++ b/data-example/config.json @@ -0,0 +1,30 @@ +{ + "datasets": [ + { + "name": "Rocket", + "color": "DD000000", + "labels": [ + "Timestamp (ms)", + "Altitude (m)", + "Latitude", + "Longitude", + "Pitch", + "Roll", + "Yaw", + "Acceleration X (g)", + "Acceleration Y (g)", + "Acceleration Z (g)", + "Velocity (m/s)", + "Brake Percentage", + "Actual Brake Value", + "GPS Fix", + "GPS Fix Quality" + ], + "coordinateIndexes": { + "altitude": 1, + "latitude": 2, + "longitude": 3 + } + } + ] +} \ No newline at end of file diff --git a/data-example/labels0.txt b/data-example/labels0.txt deleted file mode 100644 index e9c5ecc..0000000 --- a/data-example/labels0.txt +++ /dev/null @@ -1 +0,0 @@ -Timestamp (ms),Altitude (m),Latitude,Longitude,Pitch,Roll,Yaw,Acceleration X (g),Acceleration Y (g),Acceleration Z (g),Velocity (m/s),Brake Percentage,Actual Brake Value,GPS Fix,GPS Fix Quality \ No newline at end of file diff --git a/libs/json-20190722.jar b/libs/json-20190722.jar new file mode 100644 index 0000000..6db21f6 Binary files /dev/null and b/libs/json-20190722.jar differ diff --git a/libs/xchart-3.6.0.jar b/libs/xchart-3.6.0.jar deleted file mode 100644 index 537ffef..0000000 Binary files a/libs/xchart-3.6.0.jar and /dev/null differ diff --git a/libs/xchart-3.6.1.jar b/libs/xchart-3.6.1.jar new file mode 100644 index 0000000..1d9b08b Binary files /dev/null and b/libs/xchart-3.6.1.jar differ diff --git a/src/uorocketry/basestation/DataHandler.java b/src/uorocketry/basestation/DataHandler.java index fc2c52a..aee7372 100644 --- a/src/uorocketry/basestation/DataHandler.java +++ b/src/uorocketry/basestation/DataHandler.java @@ -3,6 +3,8 @@ import javax.swing.JTable; import javax.swing.table.TableModel; +import org.json.JSONObject; + public class DataHandler { static final DataType TIMESTAMP = new DataType(0, 0); @@ -46,9 +48,9 @@ public void updateTableUIWithData(JTable table, String[] labels) { } } - public void set(int index, String currentData) { + public void set(int index, String currentData, JSONObject coordinateIndexes) { // Check for special cases first - if (LATITUDE.equals(index, tableIndex) || LONGITUDE.equals(index, tableIndex)) { + if (coordinateIndexes.getInt("latitude") == index || coordinateIndexes.getInt("longitude") == index) { float degrees = 0; float minutes = 0; diff --git a/src/uorocketry/basestation/GoogleEarthUpdater.java b/src/uorocketry/basestation/GoogleEarthUpdater.java index 4c18b6b..1ca6b43 100644 --- a/src/uorocketry/basestation/GoogleEarthUpdater.java +++ b/src/uorocketry/basestation/GoogleEarthUpdater.java @@ -10,6 +10,9 @@ import java.util.Timer; import java.util.TimerTask; +import org.json.JSONArray; +import org.json.JSONObject; + public class GoogleEarthUpdater { /** @@ -27,7 +30,7 @@ public GoogleEarthUpdater() { * * @param main */ - public String generateKMLFile(List allData, int currentDataIndex) { + public String generateKMLFile(List> allData, List minDataIndex, List currentDataIndex, JSONArray dataSets) { StringBuilder content = new StringBuilder(); content.append("\r\n" + @@ -41,75 +44,106 @@ public String generateKMLFile(List allData, int currentDataIndex) { " " + " "); - content.append("\r\n"); - content.append("Rocket Path\r\n"); - content.append("#blackLine"); - content.append("absolute\r\n"); - - for (int i = 0; i <= currentDataIndex; i++) { - String currentString = getCoordinateString(allData.get(i)); - - if (currentString != null) { - content.append(currentString + "\r\n"); - } + for (int i = 0; i < allData.size(); i++) { + // Add style + content.append(""); - } - - content.append("\r\n"); - content.append("\r\n"); - - //Add the latest coordinate as a placemark - String latestDataString = getCoordinateString(allData.get(currentDataIndex)); - if (latestDataString != null) { content.append("\r\n"); - content.append("Latest Position\r\n"); - content.append("\r\nabsolute\r\n"); + content.append("Path of " + dataSets.getJSONObject(i).getString("name")); + content.append("\r\n"); + content.append("#blackLine"); + content.append("absolute\r\n"); + + for (int j = minDataIndex.get(i); j <= currentDataIndex.get(i); j++) { + String currentString = getCoordinateString(allData.get(i).get(j), dataSets.getJSONObject(i).getJSONObject("coordinateIndexes")); + + if (currentString != null) { + content.append(currentString + "\r\n"); + } + + } - content.append(latestDataString); + content.append("\r\n"); + content.append("\r\n"); - content.append("\r\n\r\n\r\n"); + //Add the latest coordinate as a placemark + String latestDataString = getCoordinateString(allData.get(i).get(currentDataIndex.get(i)), dataSets.getJSONObject(i).getJSONObject("coordinateIndexes")); + if (latestDataString != null) { + content.append("\r\n"); + content.append("Latest Position of " + dataSets.getJSONObject(i).getString("name")); + content.append("\r\n"); + content.append("\r\nabsolute\r\n"); + + content.append(latestDataString); + + content.append("\r\n\r\n\r\n"); + } } - content.append(""); return content.toString(); } - public void updateKMLFile(int tableIndex, List allData, int currentDataIndex) { - String fileContent = generateKMLFile(allData, currentDataIndex); + /** + * Updates the KML file with the data up to currentDataIndex. + * + * @param tableIndex + * @param allData + * @param currentDataIndex + * @param dataSets + * @param secondRun Is this a second run? This is true if it is being run from a task called by this function. + * The task is run to force Google Earth to update the display. + */ + public void updateKMLFile(List> allData, List minDataIndex, List currentDataIndex, JSONArray dataSets, boolean secondRun) { + String fileContent = generateKMLFile(allData, minDataIndex, currentDataIndex, dataSets); - try (Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(Main.GOOGLE_EARTH_DATA_LOCATION), StandardCharsets.UTF_8))) { + try (Writer writer = new BufferedWriter(new OutputStreamWriter( + new FileOutputStream(Main.GOOGLE_EARTH_DATA_LOCATION), StandardCharsets.UTF_8))) { writer.write(fileContent); } catch (IOException e) { e.printStackTrace(); } - if (mapRefreshTaskTimer != null) { + if (!secondRun) { + if (mapRefreshTaskTimer != null) { + try { + mapRefreshTaskTimer.cancel(); + } catch (IllegalStateException e) { + // Ignore if it is already canceled + } + } + + // Start a new task + mapRefreshTaskTimer = new TimerTask() { + @Override + public void run() { + updateKMLFile(allData, minDataIndex, currentDataIndex, dataSets, true); + } + }; try { - mapRefreshTaskTimer.cancel(); + mapRefreshTimer.schedule(mapRefreshTaskTimer, 1000); } catch (IllegalStateException e) { - // Ignore if it is already canceled + // Ignore as another has already started } } - - // Start a new task - mapRefreshTaskTimer = new TimerTask() { - @Override - public void run() { - updateKMLFile(tableIndex, allData, currentDataIndex); - } - }; - try { - mapRefreshTimer.schedule(mapRefreshTaskTimer, 50); - } catch (IllegalStateException e) { - // Ignore as another has already started - } } - public String getCoordinateString(DataHandler dataPoint) { - if (dataPoint != null && dataPoint.data[DataHandler.LONGITUDE.index].data != 0 && dataPoint.data[DataHandler.LATITUDE.index].data != 0) { - return "-" + dataPoint.data[DataHandler.LONGITUDE.index].getDecimalCoordinate() + "," + dataPoint.data[DataHandler.LATITUDE.index].getDecimalCoordinate() + "," + dataPoint.data[DataHandler.ALTITUDE.index].data; + public String getCoordinateString(DataHandler dataPoint, JSONObject coordinateIndexes) { + if (dataPoint == null) return null; + + Data altitudeData = dataPoint.data[coordinateIndexes.getInt("altitude")]; + Data longitudeData = dataPoint.data[coordinateIndexes.getInt("longitude")]; + Data latitudeData = dataPoint.data[coordinateIndexes.getInt("latitude")]; + + if (longitudeData.data != 0 && latitudeData.data != 0) { + return "-" + longitudeData.getDecimalCoordinate() + "," + latitudeData.getDecimalCoordinate() + "," + altitudeData.data; } return null; diff --git a/src/uorocketry/basestation/Main.java b/src/uorocketry/basestation/Main.java index 85be72e..ae9e351 100644 --- a/src/uorocketry/basestation/Main.java +++ b/src/uorocketry/basestation/Main.java @@ -15,16 +15,21 @@ import java.io.FileReader; import java.io.IOException; import java.io.OutputStreamWriter; +import java.io.PrintWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.swing.BorderFactory; +import javax.swing.JFileChooser; import javax.swing.JList; import javax.swing.JOptionPane; +import javax.swing.JPanel; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.border.Border; @@ -34,11 +39,16 @@ import javax.swing.event.ListSelectionListener; import javax.swing.table.TableModel; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; import org.knowm.xchart.XChartPanel; import org.knowm.xchart.XYChart; import org.knowm.xchart.XYChartBuilder; +import org.knowm.xchart.XYSeries; import org.knowm.xchart.XYSeries.XYSeriesRenderStyle; import org.knowm.xchart.style.Styler.LegendPosition; +import org.knowm.xchart.style.XYStyler; import com.fazecast.jSerialComm.SerialPort; import com.fazecast.jSerialComm.SerialPortEvent; @@ -48,8 +58,7 @@ public class Main implements ComponentListener, ChangeListener, ActionListener, /** Constants */ /** The location of the comma separated labels without the extension. */ - public static final String LABELS_LOCATION = "data/labels"; - public static final String LABELS_EXTENSION = ".txt"; + public static final String CONFIG_LOCATION = "data/config.json"; /** How many data points are there. By default, it is the number of labels */ public static List dataLength = new ArrayList<>(); /** Separator for the data */ @@ -58,6 +67,8 @@ public class Main implements ComponentListener, ChangeListener, ActionListener, public static final String SIM_DATA_LOCATION = "data/data"; public static final String SIM_DATA_EXTENSION = ".txt"; + public static final Color LEGEND_BACKGROUND_COLOR = new Color(255, 255, 255, 100); + /** Whether to update Google Earth file */ public static boolean googleEarth = false; /** Where the updating Google Earth kml file is stored */ @@ -69,8 +80,8 @@ public class Main implements ComponentListener, ChangeListener, ActionListener, /** Will have a number appended to the end to not overwrite old logs */ String currentLogFileName = DEFAULT_LOG_FILE_NAME; - /** How many data sources to record data from. For now, it much be set at launch time */ - public static final int DATA_SOURCE_COUNT = 2; + /** How many data sources to record data from. It is set when the config is loaded. */ + public static int dataSourceCount = 1; /** Is this running in simulation mode. Must be set at the beginning as it changes the setup. */ public static boolean simulation = false; @@ -78,6 +89,7 @@ public class Main implements ComponentListener, ChangeListener, ActionListener, List> allData = new ArrayList<>(); List labels = new ArrayList<>(); + JSONObject config = null; /** Index of the current data point being looked at */ ArrayList currentDataIndex = new ArrayList<>(); @@ -99,7 +111,7 @@ public class Main implements ComponentListener, ChangeListener, ActionListener, List connectingToSerial = new ArrayList(); /** Used for the map view */ - GoogleEarthUpdater googleEarthUpdater = new GoogleEarthUpdater(); + GoogleEarthUpdater googleEarthUpdater; /** The chart last clicked */ DataChart selectedChart; @@ -124,20 +136,6 @@ public static void main(String[] args) { case "--sim": simulation = Boolean.parseBoolean(args[i + 1]); - break; - case "--dataLength": - - // See how many data lengths were specified - for (int j = i; j + 1 < args.length; j++) { - if (args[j].startsWith("--")) break; - - try { - dataLength.add(Integer.parseInt(args[i + j + 1])); - } catch (NumberFormatException e) { - System.err.println("Failed to interpret " + args[i] + " " + args[i + 1]); - } - } - break; } } @@ -147,15 +145,10 @@ public static void main(String[] args) { public Main() { // Load labels - loadLabels(); - - // Set a default data length - for (int i = 0; i < labels.size(); i++) { - dataLength.add(labels.get(i).length); - } + loadConfig(); // Create window - window = new Window(); + window = new Window(this); window.addComponentListener(this); @@ -166,7 +159,7 @@ public Main() { // Setup Google Earth map support if (googleEarth) { - googleEarthUpdater = new GoogleEarthUpdater(); + setupGoogleEarth(); } // Update UI once @@ -175,7 +168,7 @@ public Main() { public void setupData() { allData = new ArrayList<>(); - for (int i = 0; i < DATA_SOURCE_COUNT; i++) { + for (int i = 0; i < dataSourceCount; i++) { allData.add(new ArrayList<>()); // Add data indexes @@ -219,10 +212,10 @@ public void setupSerialComs() { } // Create required lists - for (int i = 0; i < Main.DATA_SOURCE_COUNT; i++) { + for (int i = 0; i < dataSourceCount; i++) { activeSerialPort.add(null); } - for (int i = 0; i < Main.DATA_SOURCE_COUNT; i++) { + for (int i = 0; i < dataSourceCount; i++) { connectingToSerial.add(false); } } @@ -282,6 +275,8 @@ public void initialisePort(int tableIndex, SerialPort serialPort) { } public void setupUI() { + addChart(); + // Add slider listeners window.maxSlider.addChangeListener(this); window.minSlider.addChangeListener(this); @@ -292,8 +287,8 @@ public void setupUI() { window.addChartButton.addActionListener(this); - window.dataLengthButton.addActionListener(this); - window.dataLengthTextBox.setText(dataLength + ""); + window.saveLayout.addActionListener(this); + window.loadLayout.addActionListener(this); // Checkboxes window.googleEarthCheckBox.addActionListener(this); @@ -321,6 +316,13 @@ public void setupUI() { snapPanelSelected(selectedChart.snapPanel); } + public void setupGoogleEarth() { + googleEarthUpdater = new GoogleEarthUpdater(); + + // Setup updater file +// googleEarthUpdater.createKMLUpdaterFile(); + } + public void updateUI() { // If not ready yet if (allData.size() == 0) return; @@ -349,7 +351,9 @@ public void updateUI() { // Only record google earth data for the first one for now // There is no way to change the filename yet - if (googleEarth) googleEarthUpdater.updateKMLFile(0, allData.get(0), currentDataIndex.get(0)); + if (googleEarth) { + googleEarthUpdater.updateKMLFile(allData, minDataIndex, currentDataIndex, config.getJSONArray("datasets"), false); + } // Update every chart for (DataChart chart : window.charts) { @@ -433,12 +437,16 @@ public void updateChart(DataChart chart) { chart.xyChart.setYAxisTitle(xTypeTitle); + XYSeries series = null; + if (chart.activeSeries.length > i) { - chart.xyChart.updateXYSeries("series" + i, altitudeDataX, altitudeDataY.get(i), null); + series = chart.xyChart.updateXYSeries("series" + i, altitudeDataX, altitudeDataY.get(i), null); } else { - chart.xyChart.addSeries("series" + i, altitudeDataX, altitudeDataY.get(i), null); + series = chart.xyChart.addSeries("series" + i, altitudeDataX, altitudeDataY.get(i), null); } + series.setLabel(xTypeTitle); + newActiveSeries[i] = "series" + i; } @@ -467,7 +475,7 @@ public void updateChart(DataChart chart) { */ public void loadSimulationData() { // Load simulation data - for (int i = 0; i < DATA_SOURCE_COUNT; i++) { + for (int i = 0; i < dataSourceCount; i++) { loadSimulationData(i, SIM_DATA_LOCATION + i + SIM_DATA_EXTENSION); } } @@ -515,9 +523,9 @@ public DataHandler parseData(String data, int tableIndex) { return null; } - + JSONArray dataSets = config.getJSONArray("datasets"); for (int i = 0; i < splitData.length; i++) { - dataHandler.set(i, splitData[i]); + dataHandler.set(i, splitData[i], dataSets.getJSONObject(tableIndex).getJSONObject("coordinateIndexes")); } return dataHandler; @@ -526,38 +534,42 @@ public DataHandler parseData(String data, int tableIndex) { /** * Run once at the beginning of simulation mode */ - public void loadLabels() { - // Load simulation data - for (int i = 0; i < DATA_SOURCE_COUNT; i++) { - loadLabels(LABELS_LOCATION + i + LABELS_EXTENSION); - } + public void loadConfig() { + loadConfig(CONFIG_LOCATION); } - public void loadLabels(String fileName) { - BufferedReader br = null; + public void loadConfig(String fileName) { + String configString = null; try { - br = new BufferedReader(new FileReader(fileName)); - } catch (FileNotFoundException e) { + configString = new String(Files.readAllBytes(Paths.get(fileName)), StandardCharsets.UTF_8); + } catch (IOException e) { e.printStackTrace(); + + return; } - String[] currentLabelStrings = null; + config = new JSONObject(configString); - try { - String line = null; - - if ((line = br.readLine()) != null) { - //this line contains all of the labels - //this one is comma separated, not the same as the actual data - currentLabelStrings = line.split(","); - } - - br.close(); - } catch(IOException e) { - e.printStackTrace(); - } + JSONArray datasetsJSONArray = config.getJSONArray("datasets"); + dataSourceCount = datasetsJSONArray.length(); - labels.add(currentLabelStrings); + // Add all data + for (int i = 0; i < datasetsJSONArray.length(); i++) { + JSONObject currentDataset = datasetsJSONArray.getJSONObject(i); + + JSONArray labelsJsonArray = currentDataset.getJSONArray("labels"); + + // Load labels + String[] labelsArray = new String[labelsJsonArray.length()]; + + for (int j = 0; j < labelsArray.length; j++) { + labelsArray[j] = labelsJsonArray.getString(j); + } + + labels.add(labelsArray); + + dataLength.add(labelsArray.length); + } } /** @@ -671,6 +683,8 @@ public void actionPerformed(ActionEvent e) { addChart(); } else if (e.getSource() == window.googleEarthCheckBox) { googleEarth = window.googleEarthCheckBox.isSelected(); + + if (googleEarth) setupGoogleEarth(); } else if (e.getSource() == window.simulationCheckBox && window.simulationCheckBox.isSelected() != simulation) { String warningMessage = ""; if (window.simulationCheckBox.isSelected()) { @@ -687,40 +701,155 @@ public void actionPerformed(ActionEvent e) { } else { window.simulationCheckBox.setSelected(simulation); } - } else if (e.getSource() == window.dataLengthButton) { - String warningMessage = ""; - if (!simulation) { - warningMessage = "Are you sure you would like to change the data length?\n\n" - + "The current data will be deleted from the UI. You can find it in " + LOG_FILE_SAVE_LOCATION + currentLogFileName; - } else { - warningMessage = "Are you sure you would like to change the data length? The data will be reloaded."; + } else if (e.getSource() == window.saveLayout) { + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setAcceptAllFileFilterUsed(false); + fileChooser.addChoosableFileFilter(new LayoutFileFilter()); + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + + // Start the in current working directory + fileChooser.setCurrentDirectory(new File(".")); + + int result = fileChooser.showSaveDialog(window); + + if (result == JFileChooser.APPROVE_OPTION) { + File saveFile = fileChooser.getSelectedFile(); + + // Add extension + if (!saveFile.getName().endsWith(".rlay")) { + saveFile = new File(saveFile.getPath() + ".rlay"); + } + + // Prep file + JSONObject saveObject = new JSONObject(); + + JSONArray chartsArray = new JSONArray(); + saveObject.put("charts", chartsArray); + + for (DataChart chart: window.charts) { + JSONObject chartData = new JSONObject(); + + chartData.put("x", chart.snapPanel.relX); + chartData.put("y", chart.snapPanel.relY); + chartData.put("width", chart.snapPanel.relWidth); + chartData.put("height", chart.snapPanel.relHeight); + + // Add xTypes + JSONArray xTypeArray = new JSONArray(); + for (DataType dataType: chart.xTypes) { + JSONObject xTypeData = new JSONObject(); + + xTypeData.put("index", dataType.index); + xTypeData.put("tableIndex", dataType.tableIndex); + + xTypeArray.put(xTypeData); + } + chartData.put("xTypes", xTypeArray); + + // Add yType + JSONObject yTypeData = new JSONObject(); + yTypeData.put("index", chart.yType.index); + yTypeData.put("tableIndex", chart.yType.tableIndex); + chartData.put("yType", yTypeData); + + chartsArray.put(chartData); + } + + // Save file + try (PrintWriter out = new PrintWriter(saveFile)) { + out.println(saveObject.toString()); + } catch (FileNotFoundException e1) { + e1.printStackTrace(); + } } - if (JOptionPane.showConfirmDialog(window, warningMessage) == 0) { - // Set data length + } else if (e.getSource() == window.loadLayout) { + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setAcceptAllFileFilterUsed(false); + fileChooser.addChoosableFileFilter(new LayoutFileFilter()); + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + + // Start the in current working directory + fileChooser.setCurrentDirectory(new File(".")); + + int result = fileChooser.showOpenDialog(window); + + if (result == JFileChooser.APPROVE_OPTION) { + File saveFile = fileChooser.getSelectedFile(); + + // Load file + JSONObject loadedLayout = null; try { - dataLength.set(0, Integer.parseInt(window.dataLengthTextBox.getText())); - } catch (NumberFormatException error) { - JOptionPane.showMessageDialog(window, "'" + window.dataLengthTextBox.getText() + "' is not a number"); + loadedLayout = new JSONObject(new String(Files.readAllBytes(saveFile.toPath()), StandardCharsets.UTF_8)); + } catch (JSONException e1) { + e1.printStackTrace(); + } catch (IOException e1) { + e1.printStackTrace(); } - // Load labels - loadLabels(LABELS_LOCATION); + JSONArray chartsArray = loadedLayout.getJSONArray("charts"); - // Different setups depending on if simulation or not - setupData(); + // Clear current charts + for (DataChart dataChart: window.charts) { + // Remove from the UI + window.centerChartPanel.remove(dataChart.chartPanel); + } + + // Finally, remove it from the list + window.charts.clear(); + + for (int i = 0; i < chartsArray.length(); i++) { + JSONObject chartData = chartsArray.getJSONObject(i); + + addChart(true); + + DataChart chart = window.charts.get(i); + + // Get location + chart.snapPanel.relX = chartData.getDouble("x"); + chart.snapPanel.relY = chartData.getDouble("y"); + chart.snapPanel.relWidth = chartData.getDouble("width"); + chart.snapPanel.relHeight = chartData.getDouble("height"); + + chart.snapPanel.updateBounds(window.centerChartPanel.getWidth(), window.centerChartPanel.getHeight()); + + // Get xTypes + JSONArray xTypeArray = chartData.getJSONArray("xTypes"); + chart.xTypes = new DataType[xTypeArray.length()]; + for (int j = 0; j < chart.xTypes.length; j++) { + JSONObject xTypeData = xTypeArray.getJSONObject(j); + + chart.xTypes[j] = new DataType(xTypeData.getInt("index"), xTypeData.getInt("tableIndex")); + } + + // Add yType + JSONObject yTypeData = chartData.getJSONObject("yType"); + chart.yType = new DataType(yTypeData.getInt("index"), yTypeData.getInt("tableIndex")); + } updateUI(); - } + } } } public void addChart() { + addChart(false); + } + + /** + * @param silent Will not perform tasks such as updating the UI or selecting the chart + */ + public void addChart(boolean silent) { XYChart xyChart = new XYChartBuilder().title("Altitude vs Timestamp (s)").xAxisTitle("Timestamp (s)").yAxisTitle("Altitude (m)").build(); // Customize Chart - xyChart.getStyler().setLegendPosition(LegendPosition.InsideNE); - xyChart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Scatter); + XYStyler firstChartStyler = xyChart.getStyler(); + + firstChartStyler.setLegendPosition(LegendPosition.InsideNE); + firstChartStyler.setLegendVisible(true); + firstChartStyler.setLegendBackgroundColor(LEGEND_BACKGROUND_COLOR); + firstChartStyler.setToolTipsEnabled(true); + firstChartStyler.setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Scatter); // Series xyChart.addSeries("series0", new double[] { 0 }, new double[] { 0 }); @@ -740,12 +869,15 @@ public void addChart() { window.centerChartPanel.setComponentZOrder(chartPanel, 0); dataChart.snapPanel.setSnapPanelListener(this); - selectedChart.chartPanel.setBorder(null); - selectedChart = dataChart; - - snapPanelSelected(selectedChart.snapPanel); + if (selectedChart != null) selectedChart.chartPanel.setBorder(null); - updateUI(); + if (!silent) { + selectedChart = dataChart; + + snapPanelSelected(selectedChart.snapPanel); + + updateUI(); + } } /** For com selector JList */ @@ -853,8 +985,6 @@ public void moveSelectionsToNewTable(int newTableIndex, boolean changingX) { window.dataTables.get(newTableIndex).setRowSelectionInterval(1, 1); window.dataTables.get(newTableIndex).setColumnSelectionInterval(0, 0); window.dataTables.get(newTableIndex).repaint(); - - updateUI(); } // Move yType selection if needed @@ -963,3 +1093,21 @@ public void mouseReleased(MouseEvent e) { } } + +class LayoutFileFilter extends javax.swing.filechooser.FileFilter { + + @Override + public boolean accept(File pathname) { + if (pathname.isDirectory()) { + return true; + } else { + return pathname.getName().endsWith(".rlay"); + } + } + + @Override + public String getDescription() { + return "Rocket Layout File (.rlay)"; + } + +} diff --git a/src/uorocketry/basestation/SnapPanel.java b/src/uorocketry/basestation/SnapPanel.java index 2849cd2..d2f02eb 100644 --- a/src/uorocketry/basestation/SnapPanel.java +++ b/src/uorocketry/basestation/SnapPanel.java @@ -303,6 +303,17 @@ public int[] setRelSize(int absoluteWidth, int absoluteHeight) { return mousePosition; } + /** + * Updates the bounds based on the rel bounds and the screen size given. + * + * @param width + * @param height + */ + public void updateBounds(int width, int height) { + panel.setBounds((int) (relX * width), (int) (relY * height), (int) (relWidth * width), (int) (relHeight * height)); + + } + /** * Called whenever the parent is resized to change the layout to the new size. * @@ -310,8 +321,7 @@ public int[] setRelSize(int absoluteWidth, int absoluteHeight) { * @param yFactor The factor the y is stretched by (new/old) */ public void containerResized(int newWidth, int newHeight) { - panel.setBounds((int) (relX * newWidth), (int) (relY * newHeight), (int) (relWidth * newWidth), (int) (relHeight * newHeight)); - + updateBounds(newWidth, newHeight); } @Override diff --git a/src/uorocketry/basestation/Window.java b/src/uorocketry/basestation/Window.java index f200026..d18d79a 100644 --- a/src/uorocketry/basestation/Window.java +++ b/src/uorocketry/basestation/Window.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Vector; +import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JCheckBox; @@ -20,18 +21,14 @@ import javax.swing.JScrollPane; import javax.swing.JSlider; import javax.swing.JTable; -import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import javax.swing.border.LineBorder; -import org.knowm.xchart.XChartPanel; -import org.knowm.xchart.XYChart; -import org.knowm.xchart.XYChartBuilder; -import org.knowm.xchart.XYSeries.XYSeriesRenderStyle; -import org.knowm.xchart.style.Styler.LegendPosition; +import org.json.JSONObject; +import javax.swing.border.TitledBorder; public class Window extends JFrame { @@ -54,10 +51,6 @@ public class Window extends JFrame { JSlider minSlider; JButton latestButton; JButton pauseButton; - private JPanel dataLengthPanel; - JTextField dataLengthTextBox; - JButton dataLengthButton; - private JLabel dataLengthLabel; JLabel savingToLabel; private List comPanels = new ArrayList<>(); @@ -73,8 +66,12 @@ public class Window extends JFrame { JButton addChartButton; private JPanel savingToPanel; + private JPanel layoutTools; - public Window() { + JButton saveLayout; + JButton loadLayout; + + public Window(Main main) { // Set look and feel try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); @@ -101,8 +98,8 @@ public Window() { leftPanel.add(dataTablePanel); - for (int i = 0; i < Main.DATA_SOURCE_COUNT; i++) { - addJTable(i); + for (int i = 0; i < Main.dataSourceCount; i++) { + addJTable(i, main.config.getJSONArray("datasets").getJSONObject(i)); } scrollPane = new JScrollPane(leftPanel); @@ -113,22 +110,16 @@ public Window() { simulationCheckBox = new JCheckBox("Simulation"); leftPanel.add(simulationCheckBox); - dataLengthPanel = new JPanel(); - dataLengthPanel.setAlignmentX(Component.LEFT_ALIGNMENT); - dataLengthPanel.setAlignmentY(Component.TOP_ALIGNMENT); - leftPanel.add(dataLengthPanel); - dataLengthPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 5)); - - dataLengthLabel = new JLabel("Data Length:"); - dataLengthPanel.add(dataLengthLabel); + layoutTools = new JPanel(); + layoutTools.setBorder(new TitledBorder(null, "Layout", TitledBorder.LEADING, TitledBorder.TOP, null, null)); + leftPanel.add(layoutTools); + layoutTools.setLayout(new BoxLayout(layoutTools, BoxLayout.Y_AXIS)); - dataLengthTextBox = new JTextField(); - dataLengthPanel.add(dataLengthTextBox); - dataLengthTextBox.setText("0"); - dataLengthTextBox.setColumns(5); + saveLayout = new JButton("Save Layout"); + layoutTools.add(saveLayout); - dataLengthButton = new JButton("Save"); - dataLengthPanel.add(dataLengthButton); + loadLayout = new JButton("Load Layout"); + layoutTools.add(loadLayout); savingToPanel = new JPanel(); savingToPanel.setAlignmentX(Component.LEFT_ALIGNMENT); @@ -180,40 +171,19 @@ public Window() { getContentPane().add(sidePanel, BorderLayout.EAST); sidePanel.setLayout(new GridLayout(2, 1, 0, 0)); - for (int i = 0; i < Main.DATA_SOURCE_COUNT; i++) { + for (int i = 0; i < Main.dataSourceCount; i++) { addComSelectorPanel(); } centerChartPanel = new JPanel(); getContentPane().add(centerChartPanel, BorderLayout.CENTER); - - // Create Chart - XYChart firstChart = new XYChartBuilder().title("Altitude vs Timestamp (s)").xAxisTitle("Timestamp (s)").yAxisTitle("Altitude (m)").build(); - - // Customize Chart - firstChart.getStyler().setLegendPosition(LegendPosition.InsideNE); - firstChart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Scatter); - - // Series - firstChart.addSeries("series0", new double[] { 0 }, new double[] { 0 }); centerChartPanel.setLayout(null); - - XChartPanel chart1Panel = new XChartPanel<>(firstChart); - centerChartPanel.add(chart1Panel); - - // Create the data chart container - DataChart dataChart = new DataChart(this, firstChart, chart1Panel); - - // Add these default charts to the list - charts.add(dataChart); - + setVisible(true); - // Set default chart size - dataChart.snapPanel.setRelSize(600, 450); } - public void addJTable(int tableIndex) { + public void addJTable(int tableIndex, JSONObject dataSet) { JTable dataTable = new JTable(Main.dataLength.get(tableIndex), 2); dataTable.setBorder(new LineBorder(new Color(0, 0, 0))); dataTable.setDefaultRenderer(Object.class, new DataTableCellRenderer()); @@ -234,7 +204,13 @@ public void addJTable(int tableIndex) { dataTable.setFont(new Font("Arial", Font.PLAIN, 15)); - dataTablePanel.add(dataTable); + // Make outer title + JPanel borderPanel = new JPanel(); + borderPanel.setBorder(BorderFactory.createTitledBorder(dataSet.getString("name"))); + + borderPanel.add(dataTable); + + dataTablePanel.add(borderPanel); dataTables.add(dataTable); }