package nl.fontys.sofa.limo.view.topcomponent; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.scene.chart.BarChart; import javafx.scene.chart.CategoryAxis; import javafx.scene.chart.Chart; import javafx.scene.chart.LineChart; import javafx.scene.chart.NumberAxis; import javafx.scene.chart.PieChart; import javafx.scene.chart.XYChart; import javax.swing.BoxLayout; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.SwingUtilities; import javax.swing.event.TableModelEvent; import javax.swing.filechooser.FileNameExtensionFilter; import nl.fontys.sofa.limo.domain.component.Node; import nl.fontys.sofa.limo.externaltrader.CSVExporter; import nl.fontys.sofa.limo.simulation.result.DataEntry; import nl.fontys.sofa.limo.simulation.result.SimulationResult; import nl.fontys.sofa.limo.simulation.result.TestCaseResult; import nl.fontys.sofa.limo.view.custom.table.DataEntryTableModel; import nl.fontys.sofa.limo.view.custom.table.LimoTable; import nl.fontys.sofa.limo.view.custom.table.SingleCaseTableModel; import nl.fontys.sofa.limo.view.graphs.GraphSwitchTopComponent; import nl.fontys.sofa.limo.view.graphs.PieChartComponent; import nl.fontys.sofa.limo.view.graphs.XYChartComponent; import nl.fontys.sofa.limo.view.util.LIMOResourceBundle; import org.netbeans.api.settings.ConvertAsProperties; import org.openide.util.LookupEvent; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.Lookups; import org.openide.windows.TopComponent; import org.openide.windows.WindowManager; /** * Top component which displays something. */ @ConvertAsProperties( dtd = "-//nl.fontys.sofa.limo.view.topcomponent//Result//EN", autostore = false ) @TopComponent.Description( preferredID = "ResultTopComponent", iconBase = "icons/gui/test_tube.png", persistenceType = TopComponent.PERSISTENCE_ALWAYS ) @TopComponent.Registration(mode = "editor", openAtStartup = false) @TopComponent.OpenActionRegistration( displayName = "#CTL_ResultAction", preferredID = "ResultTopComponent" ) @Messages({ "CTL_ResultAction=Result", "CTL_ResultTopComponent=Result Window", "HINT_ResultTopComponent=This is a Result window" }) public final class ResultTopComponent extends TopComponent { private List<SimulationResult> results; private JTable totalsTable, categoryTable, nodesTable; private JPanel nodeGraphPanel, totalGraphPanel, categoryGraphPanel; private DataEntryTableModel totalDetm, categoryDetm, nodeDetm; public ResultTopComponent() { initComponents(); setName(Bundle.CTL_ResultTopComponent()); setToolTipText(Bundle.HINT_ResultTopComponent()); } /** * @param results a list which must contain at least one entry and maximal 2 * entries */ public ResultTopComponent(List<SimulationResult> results) throws InstantiationException { this(); if (results.isEmpty() || results.size() > 2) { throw new InstantiationException(LIMOResourceBundle.getString("NOT_CORRECT_NUMBER_OF_SIMULATION_RESULT")); } this.results = results; String name = LIMOResourceBundle.getString("DEFAULT_RESULT_WINDOW_NAME"); if (results.size() == 2) { name = LIMOResourceBundle.getString("COMPARISION_RESULT_WINDOW_NAME"); } name = results.stream().map((result) -> " " + result.getSupplyChain().getName().replace(".lsc", "")).reduce(name, String::concat); setName(name); jTabbedPane1.addTab(LIMOResourceBundle.getString("TOTALS"), createTotalsPane()); if (results.size() == 1) { jTabbedPane1.addTab(LIMOResourceBundle.getString("BY", LIMOResourceBundle.getString("CATEGORY")), createCategoryPane()); jTabbedPane1.addTab(LIMOResourceBundle.getString("BY", LIMOResourceBundle.getString("NODE")), createNodePane()); jTabbedPane1.addTab("Single Results", createSinglePane()); } jButton1.addActionListener((ActionEvent e) -> { JFileChooser fc = new JFileChooser() { @Override public void approveSelection() { File f = getSelectedFile(); File extended = new File(f.getAbsolutePath().concat(".csv")); if ((f.exists() || extended.exists()) && getDialogType() == SAVE_DIALOG) { int result = JOptionPane.showConfirmDialog(this, LIMOResourceBundle.getString("FILENAME_IN_USE"), LIMOResourceBundle.getString("EXISTING_FILE"), JOptionPane.YES_NO_CANCEL_OPTION); switch (result) { case JOptionPane.YES_OPTION: super.approveSelection(); f.delete(); return; case JOptionPane.NO_OPTION: return; case JOptionPane.CLOSED_OPTION: return; case JOptionPane.CANCEL_OPTION: cancelSelection(); return; } } super.approveSelection(); } }; String currentPath = fc.getFileSystemView().getDefaultDirectory().toString(); fc.setCurrentDirectory(new File(currentPath)); fc.setMultiSelectionEnabled(false); fc.setDialogTitle(LIMOResourceBundle.getString("FILE_SAVE_LOCATION")); FileNameExtensionFilter filter = new FileNameExtensionFilter(LIMOResourceBundle.getString("SIMULATION_RESULTS"), "csv"); fc.setFileFilter(filter); int selection = fc.showSaveDialog(fc); if (selection == JFileChooser.APPROVE_OPTION) { File saveLocation = fc.getSelectedFile(); String absolute = saveLocation.getAbsolutePath(); String ext = ""; int i = absolute.lastIndexOf('.'); if (i > 0) { ext = absolute.substring(i); } if (!ext.equals(".csv")) { absolute = absolute.concat(".csv"); } CSVExporter.exportTables(absolute, new JTable[]{totalsTable, categoryTable, nodesTable}, new String[]{"Totals", "By Categories", "By Nodes"}); } }); } private Component createSinglePane() { if (results.size() == 1) { Map<String, List<Double>> singleMap = new HashMap<>(); List<TestCaseResult> testCaseResults = results.get(0).getResults(); List<Double> cost = new ArrayList<>(); List<Double> leadTimes = new ArrayList<>(); List<Double> extraCosts = new ArrayList<>(); List<Double> delay = new ArrayList<>(); List<Double> eventCount = new ArrayList<>(); final List<String> name = new ArrayList<>(); for (int i = 0; i < testCaseResults.size(); i++) { TestCaseResult result = testCaseResults.get(i); cost.add(result.getTotalCosts()); leadTimes.add(result.getTotalLeadTimes()); extraCosts.add(result.getTotalExtraCosts()); delay.add(result.getTotalDelays()); eventCount.add(new Double(result.getExecutedEvents().size())); name.add("Single Run:" + i); } singleMap.put(SingleCaseTableModel.COSTS_ID, cost); singleMap.put(SingleCaseTableModel.LEAD_TIMES_ID, leadTimes); singleMap.put(SingleCaseTableModel.EXTRA_COSTS_ID, extraCosts); singleMap.put(SingleCaseTableModel.DELAYS_ID, delay); singleMap.put(SingleCaseTableModel.EVENT_ID, eventCount); SingleCaseTableModel detm = new SingleCaseTableModel(name, singleMap); totalsTable = new JTable(detm); return new JScrollPane(totalsTable); } else { return null; } } private Component createTotalsPane() { Map<String, List<DataEntry>> totalMap = new HashMap<>(); List<DataEntry> cost = new ArrayList<>(); List<DataEntry> leadTimes = new ArrayList<>(); List<DataEntry> extraCosts = new ArrayList<>(); List<DataEntry> delay = new ArrayList<>(); List<DataEntry> co2 = new ArrayList<>(); final List<String> name = new ArrayList<>(); results.stream().map((result) -> { cost.add(result.getTotalCosts()); return result; }).map((result) -> { leadTimes.add(result.getTotalLeadTimes()); return result; }).map((result) -> { extraCosts.add(result.getTotalExtraCosts()); return result; }).map((result) -> { delay.add(result.getTotalDelays()); return result; }).map((result) -> { co2.add(result.getTotalCO2()); return result; }).forEach((result) -> { name.add(result.getSupplyChain().getName().replace(".lsc", "")); }); if (results.size() > 1) { cost.add(getDifference(cost.get(0), cost.get(1))); leadTimes.add(getDifference(leadTimes.get(0), leadTimes.get(1))); extraCosts.add(getDifference(extraCosts.get(0), extraCosts.get(1))); delay.add(getDifference(delay.get(0), delay.get(1))); co2.add(getDifference(co2.get(0), co2.get(1))); name.add(LIMOResourceBundle.getString("RESULT_ABSOULTE_DIFFERENCE_ROW_NAME")); cost.add(getDifferenceAsPercentage(cost.get(0), cost.get(1))); leadTimes.add(getDifferenceAsPercentage(leadTimes.get(0), leadTimes.get(1))); extraCosts.add(getDifferenceAsPercentage(extraCosts.get(0), extraCosts.get(1))); delay.add(getDifferenceAsPercentage(delay.get(0), delay.get(1))); co2.add(getDifferenceAsPercentage(co2.get(0), co2.get(1))); name.add(LIMOResourceBundle.getString("RESULT_REALTIVE_DIFFERENCE_ROW_NAME")); } else { name.set(0, LIMOResourceBundle.getString("DEFAULT_RESULT_ROW_NAME")); } totalMap.put(DataEntryTableModel.COSTS_ID, cost); totalMap.put(DataEntryTableModel.LEAD_TIMES_ID, leadTimes); totalMap.put(DataEntryTableModel.EXTRA_COSTS_ID, extraCosts); totalMap.put(DataEntryTableModel.DELAYS_ID, delay); totalMap.put(DataEntryTableModel.CO2_ID, co2); totalDetm = new DataEntryTableModel(name, totalMap); final JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); totalsTable = new LimoTable(totalDetm); JScrollPane totalJScrollPane = new JScrollPane(totalsTable); totalJScrollPane.setPreferredSize(new Dimension(Integer.MAX_VALUE, 150)); totalGraphPanel = new JPanel(new BorderLayout()); createXYChart(totalGraphPanel, totalDetm, BarChart.class); panel.add(totalGraphPanel); panel.add(totalJScrollPane); return panel; } private void createXYChart(JPanel parent, DataEntryTableModel model, Class<? extends XYChart> chartClass) { parent.removeAll(); XYChartComponent<DataEntryTableModel> chart = new XYChartComponent<>(model, chartClass); model.setOnlyOneEnabled(false); model.removeAllListeners(); model.addTableModelListener( (TableModelEvent e) -> { Platform.runLater(() -> { chart.updateData(); }); } ); parent.setPreferredSize(new Dimension(500, 700)); Platform.setImplicitExit(false); Platform.runLater( () -> { CategoryAxis xAxis = new CategoryAxis(); ArrayList<String> names = new ArrayList<>(); for (int i = 1; i < model.getRowCount(); i++) { names.add(model.getValueAt(i, 0).toString()); } xAxis.setCategories(FXCollections.<String>observableArrayList(names)); xAxis.setLabel("Categories"); NumberAxis yAxis = new NumberAxis(); yAxis.setTickUnit(50); yAxis.setLabel(""); chart.init(parent, BorderLayout.CENTER, xAxis, yAxis); }); } private DataEntry getDifference(DataEntry one, DataEntry two) { double min, max, avg; min = Math.abs(two.getMin() - one.getMin()); max = Math.abs(two.getMax() - one.getMax()); avg = Math.abs(two.getAvg() - one.getAvg()); return new DataEntry(min, max, avg); } private DataEntry getDifferenceAsPercentage(DataEntry one, DataEntry two) { double min, max, avg; min = calculateDifferenceAsPercentage(one.getMin(), two.getMin()); max = calculateDifferenceAsPercentage(one.getMax(), two.getMax()); avg = calculateDifferenceAsPercentage(one.getAvg(), two.getAvg()); return new DataEntry(min, max, avg); } private double calculateDifferenceAsPercentage(double value1, double value2) { double diff = value2 - value1; if (value1 <= 0 && value2 > 0) { return 100; } if (value1 <= 0) { return 0; } return diff * 100 / value1; } private Component createCategoryPane() { SimulationResult result = results.get(0); Set<String> categorySet = new HashSet<>(); categorySet.addAll(result.getCostsByCategory().keySet()); categorySet.addAll(result.getLeadTimesByCategory().keySet()); categorySet.addAll(result.getExtraCostsByCategory().keySet()); categorySet.addAll(result.getDelaysByCategory().keySet()); categorySet.addAll(result.getCo2ByCategory().keySet()); final List<String> categories = new ArrayList<>(categorySet); Collections.sort(categories); List<DataEntry> costs = new ArrayList<>(); List<DataEntry> leadTimes = new ArrayList<>(); List<DataEntry> extraCosts = new ArrayList<>(); List<DataEntry> delays = new ArrayList<>(); List<DataEntry> co2Values = new ArrayList<>(); categories.stream().map((category) -> { DataEntry cost = result.getCostsByCategory().get(category); costs.add(cost == null ? new DataEntry(0, 0, 0) : cost); DataEntry leadTime = result.getLeadTimesByCategory().get(category); leadTimes.add(leadTime == null ? new DataEntry(0, 0, 0) : leadTime); DataEntry extraCost = result.getExtraCostsByCategory().get(category); extraCosts.add(extraCost == null ? new DataEntry(0, 0, 0) : extraCost); DataEntry co2Value = result.getCo2ByCategory().get(category); co2Values.add(co2Value == null ? new DataEntry(0, 0, 0) : co2Value); DataEntry delay = result.getDelaysByCategory().get(category); return delay; }).forEach((delay) -> { delays.add(delay == null ? new DataEntry(0, 0, 0) : delay); }); Map<String, List<DataEntry>> map = new HashMap<>(); map.put(DataEntryTableModel.COSTS_ID, costs); map.put(DataEntryTableModel.LEAD_TIMES_ID, leadTimes); map.put(DataEntryTableModel.EXTRA_COSTS_ID, extraCosts); map.put(DataEntryTableModel.DELAYS_ID, delays); map.put(DataEntryTableModel.CO2_ID, co2Values); categoryDetm = new DataEntryTableModel(categories, map); final JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); categoryTable = new LimoTable(categoryDetm); JScrollPane catJScrollPane = new JScrollPane(categoryTable); categoryGraphPanel = new JPanel(new BorderLayout()); createPieChart(categoryDetm, categoryGraphPanel); panel.add(categoryGraphPanel); panel.add(catJScrollPane); catJScrollPane.setPreferredSize(new Dimension(Integer.MAX_VALUE, 150)); categoryGraphPanel.setPreferredSize(new Dimension(500, 700)); return panel; } private void createPieChart(DataEntryTableModel model, JPanel parent) { parent.removeAll(); final PieChartComponent<DataEntryTableModel> chart = new PieChartComponent<>(model); model.setOnlyOneEnabled(true); model.removeAllListeners(); model.addTableModelListener((TableModelEvent e) -> { if (e.getLastRow() != e.getFirstRow()) { Platform.runLater(() -> { chart.updateData(); }); } if (parent == nodeGraphPanel) { nodesTable.tableChanged(e); } else if (parent == totalGraphPanel) { totalsTable.tableChanged(e); } else if (parent == categoryGraphPanel) { categoryTable.tableChanged(e); } }); Platform.setImplicitExit(false); Platform.runLater(() -> { chart.init(parent, BorderLayout.CENTER); }); } private Component createNodePane() { List<String> names = new ArrayList<>(); List<DataEntry> costs = new ArrayList<>(); List<DataEntry> leadTimes = new ArrayList<>(); List<DataEntry> extraCosts = new ArrayList<>(); List<DataEntry> delays = new ArrayList<>(); List<DataEntry> co2Values = new ArrayList<>(); results.stream().forEach((result) -> { Node currentNode = result.getSupplyChain().getStartHub(); while (currentNode != null) { String name = currentNode.getName(); names.add(name); DataEntry cost = result.getCostsByNode().get(name); costs.add(cost == null ? new DataEntry(0, 0, 0) : cost); DataEntry leadTime = result.getLeadTimesByNode().get(name); leadTimes.add(leadTime == null ? new DataEntry(0, 0, 0) : leadTime); DataEntry extraCost = result.getExtraCostsByNode().get(name); extraCosts.add(extraCost == null ? new DataEntry(0, 0, 0) : extraCost); DataEntry delay = result.getDelaysByNode().get(name); delays.add(delay == null ? new DataEntry(0, 0, 0) : delay); DataEntry co2Value = result.getCo2ByNode().get(name); co2Values.add(co2Value == null ? new DataEntry(0, 0, 0) : co2Value); currentNode = currentNode.getNext(); } }); Map<String, List<DataEntry>> map = new HashMap<>(); map.put(DataEntryTableModel.COSTS_ID, costs); map.put(DataEntryTableModel.LEAD_TIMES_ID, leadTimes); map.put(DataEntryTableModel.EXTRA_COSTS_ID, extraCosts); map.put(DataEntryTableModel.DELAYS_ID, delays); map.put(DataEntryTableModel.CO2_ID, co2Values); nodeDetm = new DataEntryTableModel(names, map); nodeDetm.setOnlyOneEnabled(false); final JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); nodesTable = new LimoTable(nodeDetm); nodeGraphPanel = new JPanel(new BorderLayout()); JScrollPane catJScrollPane = new JScrollPane(nodesTable); createXYChart(nodeGraphPanel, nodeDetm, LineChart.class); panel.add(nodeGraphPanel); panel.add(catJScrollPane); catJScrollPane.setPreferredSize( new Dimension(Integer.MAX_VALUE, 150)); nodeGraphPanel.setPreferredSize( new Dimension(500, 700)); return panel; } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { jToolBar1 = new javax.swing.JToolBar(); jButton1 = new javax.swing.JButton(); jTabbedPane1 = new javax.swing.JTabbedPane(); setLayout(new java.awt.BorderLayout()); jToolBar1.setRollover(true); org.openide.awt.Mnemonics.setLocalizedText(jButton1, org.openide.util.NbBundle.getMessage(ResultTopComponent.class, "ResultTopComponent.jButton1.text")); // NOI18N jButton1.setFocusable(false); jButton1.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); jButton1.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); jToolBar1.add(jButton1); add(jToolBar1, java.awt.BorderLayout.PAGE_START); jTabbedPane1.setDebugGraphicsOptions(javax.swing.DebugGraphics.NONE_OPTION); jTabbedPane1.setPreferredSize(new java.awt.Dimension(500, 500)); jTabbedPane1.setRequestFocusEnabled(false); add(jTabbedPane1, java.awt.BorderLayout.CENTER); }// </editor-fold>//GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton jButton1; private javax.swing.JTabbedPane jTabbedPane1; private javax.swing.JToolBar jToolBar1; // End of variables declaration//GEN-END:variables GraphSwitchTopComponent graphSwitch = (GraphSwitchTopComponent) WindowManager.getDefault().findTopComponent("GraphSwitchTopComponent"); @Override public void componentOpened() { GraphChangeListener listener = new GraphChangeListener(); associateLookup(Lookups.singleton(listener)); } @Override public void componentClosed() { LookupEvent le = new LookupEvent(null); graphSwitch.resultChanged(le); } void writeProperties(java.util.Properties p ) { // better to version settings since initial version as advocated at // http://wiki.apidesign.org/wiki/PropertyFiles p.setProperty("version", "1.0"); // TODO store your settings } void readProperties(java.util.Properties p ) { String version = p.getProperty("version"); // TODO read your settings according to their version } @Override public int getPersistenceType() { return TopComponent.PERSISTENCE_NEVER; } public class GraphChangeListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { if (e.getSource() instanceof Class) { Class<? extends Chart> graphClass = (Class<? extends Chart>) e.getSource(); Component selectedComponent = jTabbedPane1.getSelectedComponent(); DataEntryTableModel detm; JPanel componet; if (SwingUtilities.isDescendingFrom(nodeGraphPanel, selectedComponent)) { System.out.println("Node"); detm = nodeDetm; componet = nodeGraphPanel; } else if (SwingUtilities.isDescendingFrom(totalGraphPanel, selectedComponent)) { System.out.println("Total"); detm = totalDetm; componet = totalGraphPanel; } else if (SwingUtilities.isDescendingFrom(categoryGraphPanel, selectedComponent)) { System.out.println("Category"); detm = categoryDetm; componet = categoryGraphPanel; } else { return; } if (graphClass == PieChart.class) { createPieChart(detm, componet); } else { createXYChart(componet, detm, (Class<? extends XYChart>) graphClass); } } } } }