Refactor
- Plot class to use JList for statistics selection and improve chart update logic
This commit is contained in:
@@ -1,19 +1,24 @@
|
||||
package net.berack.upo.valpre;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.GridLayout;
|
||||
import java.awt.Font;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.jfree.chart.ChartFactory;
|
||||
import org.jfree.chart.ChartPanel;
|
||||
import org.jfree.chart.axis.CategoryLabelPositions;
|
||||
import org.jfree.chart.plot.PlotOrientation;
|
||||
import org.jfree.data.category.DefaultCategoryDataset;
|
||||
|
||||
@@ -27,9 +32,9 @@ import net.berack.upo.valpre.sim.stats.Statistics;
|
||||
*/
|
||||
public class Plot {
|
||||
public final ResultSummary summary;
|
||||
private final ChartPanel chartPanel;
|
||||
private final ChartPanel panelBarChart;
|
||||
private final JComboBox<String> nodeComboBox;
|
||||
private final JComboBox<String> statComboBox;
|
||||
private final JList<JListEntry> statList;
|
||||
|
||||
/**
|
||||
* Create a new plot object.
|
||||
@@ -47,45 +52,71 @@ public class Plot {
|
||||
this.summary = new ResultSummary(results);
|
||||
|
||||
var nodes = this.summary.getNodes().toArray(new String[0]);
|
||||
this.chartPanel = new ChartPanel(null);
|
||||
this.panelBarChart = new ChartPanel(null);
|
||||
|
||||
this.nodeComboBox = new JComboBox<>(nodes);
|
||||
this.statComboBox = new JComboBox<>(Statistics.getOrderOfApply());
|
||||
this.nodeComboBox.addActionListener(_ -> update());
|
||||
|
||||
var order = Statistics.getOrderOfApply();
|
||||
var panels = new JListEntry[order.length];
|
||||
for (int i = 0; i < order.length; i++)
|
||||
panels[i] = new JListEntry(order[i]);
|
||||
|
||||
this.statList = new JList<>(panels);
|
||||
this.statList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
this.statList.addListSelectionListener(_ -> update());
|
||||
this.statList.setFixedCellHeight(25);
|
||||
this.statList.setCellRenderer((list, val, _, selected, _) -> {
|
||||
var bgColor = list.getBackground();
|
||||
var bgSelColor = list.getSelectionBackground();
|
||||
val.setBackground(selected ? bgSelColor : bgColor);
|
||||
|
||||
return val;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the plot of the results.
|
||||
* This method creates the GUI and shows the plot of the results.
|
||||
* The user can select the node and the statistic to show.
|
||||
* The plot is updated when the user selects a different node or statistic.
|
||||
* The plot shows the distribution of the runs and the mean and error of the
|
||||
* statistic.
|
||||
* The plot is shown in a new window.
|
||||
*/
|
||||
public void show() {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
var nodeLabel = new JLabel("Node: ");
|
||||
var statLabel = new JLabel("Stat: ");
|
||||
|
||||
var filterPanel = new JPanel();
|
||||
filterPanel.setLayout(new GridLayout(2, 2));
|
||||
filterPanel.add(nodeLabel);
|
||||
filterPanel.add(nodeComboBox);
|
||||
filterPanel.add(statLabel);
|
||||
filterPanel.add(statComboBox);
|
||||
|
||||
nodeComboBox.addActionListener(_ -> updateChart());
|
||||
statComboBox.addActionListener(_ -> updateChart());
|
||||
|
||||
var rootPane = new JPanel();
|
||||
rootPane.setLayout(new BorderLayout());
|
||||
rootPane.add(filterPanel, BorderLayout.NORTH);
|
||||
rootPane.add(chartPanel, BorderLayout.CENTER);
|
||||
|
||||
chartPanel.setChart(ChartFactory.createBarChart(
|
||||
"Title",
|
||||
"Run",
|
||||
"Value",
|
||||
// Create charts with empty data
|
||||
this.panelBarChart.setChart(ChartFactory.createBarChart(
|
||||
"Run Distributions",
|
||||
"",
|
||||
"",
|
||||
null,
|
||||
PlotOrientation.VERTICAL,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false));
|
||||
updateChart();
|
||||
this.panelBarChart.getChart().getCategoryPlot().getDomainAxis()
|
||||
.setCategoryLabelPositions(CategoryLabelPositions.UP_45);
|
||||
|
||||
// Create the GUI with the various layouts and components
|
||||
var filterPanel = new JPanel();
|
||||
filterPanel.setLayout(new BorderLayout());
|
||||
filterPanel.add(new JLabel("Node: "), BorderLayout.WEST);
|
||||
filterPanel.add(this.nodeComboBox, BorderLayout.CENTER);
|
||||
filterPanel.setBorder(BorderFactory.createEmptyBorder(20, 0, 20, 0));
|
||||
|
||||
var rootPane = new JPanel();
|
||||
rootPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
rootPane.setLayout(new BorderLayout());
|
||||
rootPane.add(filterPanel, BorderLayout.NORTH);
|
||||
rootPane.add(this.statList, BorderLayout.WEST);
|
||||
rootPane.add(this.panelBarChart, BorderLayout.CENTER);
|
||||
|
||||
// update the charts by triggering the event
|
||||
this.statList.setSelectedIndex(0);
|
||||
|
||||
// Show the frame
|
||||
var frame = new JFrame("Graph of the Simulation");
|
||||
frame.add(rootPane);
|
||||
frame.setSize(800, 600);
|
||||
@@ -97,22 +128,30 @@ public class Plot {
|
||||
/**
|
||||
* Update the chart with the selected node and stat.
|
||||
*/
|
||||
private void updateChart() {
|
||||
private void update() {
|
||||
try {
|
||||
var node = this.nodeComboBox.getSelectedItem().toString();
|
||||
var stat = this.statComboBox.getSelectedItem().toString();
|
||||
var stat = this.statList.getSelectedValue().name.getText();
|
||||
|
||||
var summary = this.summary.getSummaryOf(node, stat);
|
||||
var frequency = summary.getFrequency(20);
|
||||
var summary = this.summary.getSummaryOf(node);
|
||||
var statSummary = summary.get(stat);
|
||||
var frequency = statSummary.getFrequency(15);
|
||||
|
||||
var dataset = new DefaultCategoryDataset();
|
||||
var bucket = (statSummary.max - statSummary.min) / frequency.length;
|
||||
for (int i = 0; i < frequency.length; i++) {
|
||||
dataset.addValue(frequency[i], "Frequency", Integer.valueOf(i));
|
||||
var columnVal = statSummary.min + i * bucket;
|
||||
var columnKey = String.format("%.3f", columnVal);
|
||||
dataset.addValue(frequency[i], "Frequency", columnKey);
|
||||
}
|
||||
this.panelBarChart.getChart().getCategoryPlot().setDataset(dataset);
|
||||
|
||||
var chart = chartPanel.getChart();
|
||||
chart.getCategoryPlot().setDataset(dataset);
|
||||
chart.setTitle(String.format("Avg %.3f", summary.average));
|
||||
var model = this.statList.getModel();
|
||||
for (int i = 0; i < model.getSize(); i++) {
|
||||
var entry = model.getElementAt(i);
|
||||
var value = summary.get(entry.name.getText());
|
||||
entry.value.setText(String.format("%8.3f ±% 9.3f", value.average, value.error95));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -133,4 +172,32 @@ public class Plot {
|
||||
|
||||
return Parameters.getArgsOrHelper(args, "-", arguments, descriptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to create a panel with a name and a value.
|
||||
* The name is on the left and the value is on the right.
|
||||
* The name is in bold and the value is in plain.
|
||||
*/
|
||||
private static class JListEntry extends JPanel {
|
||||
public static final Font fontName = new Font("Consolas", Font.BOLD, 14);
|
||||
public static final Font fontValue = new Font("Consolas", Font.PLAIN, 12);
|
||||
|
||||
public final JLabel name = new JLabel();
|
||||
public final JLabel value = new JLabel();
|
||||
|
||||
public JListEntry(String text) {
|
||||
this.name.setText(text);
|
||||
|
||||
this.setLayout(new BorderLayout());
|
||||
this.add(this.name, BorderLayout.WEST);
|
||||
this.add(Box.createHorizontalStrut(100), BorderLayout.CENTER);
|
||||
this.add(this.value, BorderLayout.EAST);
|
||||
|
||||
this.name.setHorizontalAlignment(JLabel.LEFT);
|
||||
this.value.setHorizontalAlignment(JLabel.RIGHT);
|
||||
|
||||
this.name.setFont(fontName);
|
||||
this.value.setFont(fontValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,14 +32,13 @@ public class StatisticsSummary {
|
||||
Arrays.sort(values);
|
||||
var sum = Arrays.stream(values).sum();
|
||||
var avg = sum / values.length;
|
||||
var median = values.length / 2;
|
||||
var varianceSum = Arrays.stream(values).map(value -> Math.pow(value - avg, 2)).sum();
|
||||
|
||||
this.name = name;
|
||||
this.values = values;
|
||||
this.average = avg;
|
||||
this.stdDev = Math.sqrt(varianceSum / values.length);
|
||||
this.median = values.length % 2 == 0 ? (values[median - 1] + values[median]) / 2.0 : values[median];
|
||||
this.median = this.getPercentile(0.50);
|
||||
this.min = values[0];
|
||||
this.max = values[values.length - 1];
|
||||
this.error95 = this.calcError(0.95);
|
||||
@@ -82,6 +81,17 @@ public class StatisticsSummary {
|
||||
return buckets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the percentile of the values in the array.
|
||||
*
|
||||
* @param percentile the percentile to calculate
|
||||
* @return the value at the selected percentile
|
||||
*/
|
||||
public double getPercentile(double percentile) {
|
||||
var index = (int) Math.floor(percentile * (this.values.length - 1));
|
||||
return this.values[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a summary of the statistics.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user