Skip to content
Snippets Groups Projects
Commit ecc77e09 authored by Eddie Groh's avatar Eddie Groh
Browse files

Updated UI with two more ChoiceBoxes and updated the data in the graphs

Issue-Ref: 4310
Issue-Url: af3#4310



Signed-off-by: default avatarEddie Groh <groh@fortiss.org>
parent 9b8743b5
No related branches found
No related tags found
1 merge request!210Setting up Metric extraction plugin for AF3 : Issue 4310
......@@ -11,6 +11,7 @@
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.text.Text?>
<SplitPane fx:id="metricsSplitPane" dividerPositions="0.5" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" orientation="VERTICAL" prefHeight="400.0" prefWidth="1035.0" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1">
......@@ -28,7 +29,7 @@
<PieChart fx:id="pieChart" prefHeight="184.0" prefWidth="1033.0" BorderPane.alignment="CENTER" />
</center>
<top>
<ChoiceBox fx:id="choiceBox" onAction="#onChoiceBoxChange" prefWidth="150.0" BorderPane.alignment="CENTER" />
<ChoiceBox fx:id="childMetricChoiceBox" onAction="#onChildMetricChoiceBoxChange" prefHeight="24.0" prefWidth="270.0" BorderPane.alignment="CENTER" />
</top>
</BorderPane>
<BorderPane fx:id="borderPane" prefHeight="158.0" prefWidth="81.0" />
......@@ -44,14 +45,26 @@
<children>
<SplitPane dividerPositions="0.5" prefHeight="336.0" prefWidth="1033.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<items>
<ScatterChart fx:id="scatterChart">
<xAxis>
<NumberAxis side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis side="LEFT" />
</yAxis>
</ScatterChart>
<BorderPane prefHeight="200.0" prefWidth="200.0">
<center>
<ScatterChart fx:id="scatterChart" prefHeight="322.0" prefWidth="540.0" BorderPane.alignment="CENTER">
<xAxis>
<NumberAxis side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis side="LEFT" />
</yAxis>
</ScatterChart>
</center>
<top>
<HBox BorderPane.alignment="CENTER">
<children>
<ChoiceBox fx:id="yScatterMetricChoiceBox" onAction="#onScatterMetricChoiceBoxChange" prefHeight="24.0" prefWidth="270.0" />
<ChoiceBox fx:id="xScatterMetricChoiceBox" onAction="#onScatterMetricChoiceBoxChange" prefHeight="24.0" prefWidth="270.0" />
</children>
</HBox>
</top>
</BorderPane>
<BarChart fx:id="barChart">
<xAxis>
<CategoryAxis side="BOTTOM" />
......
IModelQualityViewPart.java 708f8089645df12098ea67190805cce343045d2e YELLOW
ModelQualityFXController.java 4272d7043944effc9532d4a122fd50b63b79fb82 YELLOW
ModelQualityFXController.java 78b503c4344226585c0d57cb548787a3ddd649a6 RED
ModelQualityFXViewPart.java 179abf18d6e3b6c844076620f53b43ac8a42c217 YELLOW
......@@ -17,7 +17,6 @@ package org.fortiss.tooling.ext.quality.ui.view.fx;
import static javafx.scene.paint.Color.BLUE;
import static javafx.scene.paint.Color.DARKGRAY;
import static javafx.scene.paint.Color.GREEN;
import static javafx.scene.paint.Color.LIGHTGRAY;
import static org.fortiss.tooling.common.ui.javafx.style.FontStyle.BLACK_VERDANA_10PT;
import static org.fortiss.tooling.common.ui.javafx.style.FontStyle.BLACK_VERDANA_12PT;
......@@ -29,9 +28,12 @@ import static org.fortiss.tooling.kernel.utils.EcoreUtils.getFirstParentWithType
import java.text.DecimalFormat;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.viewers.ISelection;
......@@ -60,6 +62,7 @@ import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.PieChart;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
......@@ -67,7 +70,9 @@ import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.util.Pair;
/**
* FX Controller for the Metrics view.
......@@ -79,22 +84,41 @@ import javafx.scene.text.Text;
public class ModelQualityFXController extends CompositeFXControllerBase<SplitPane, Node>
implements ISelectionListener, MetricUpdateListener {
/**
* Selection of colors for the overlays in the spider chart. The length of this array also
* limits
* the depth of the {@link ModelQualityFXController#lastViewedElements} list
*/
private static Color[] SPIDER_CHART_OVERLAY_COLORS = {Color.GREEN, Color.BLUE, Color.RED};
/** ChoiceBox for selecting the metric which shall be displayed. */
@FXML
private ChoiceBox<MetricKey> choiceBox;
private ChoiceBox<MetricKey> childMetricChoiceBox;
/** PieChart displaying the metrics. */
@FXML
private PieChart pieChart;
/** The bottom {@link BorderPane} for the spider chart. */
@FXML
private BorderPane borderPane;
/** PieChart displaying the metrics. */
/** ChoiceBox for selecting the metric displayed on the x axis of the scatter chart. */
@FXML
private PieChart pieChart;
private ChoiceBox<MetricKey> xScatterMetricChoiceBox;
/** ChoiceBox for selecting the metric displayed on the y axis of the scatter chart. */
@FXML
private ChoiceBox<MetricKey> yScatterMetricChoiceBox;
/** A scatter chart for displaying metrics. */
@FXML
private ScatterChart<Number, Number> scatterChart;
/** A bar chart for displaying metrics. */
@FXML
private BarChart<String, Number> barChart;
/** {@link Text} at the bottom of the view. */
@FXML
private Text bottomText;
......@@ -102,6 +126,15 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
/** Time format when displaying updating time. */
private final DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HH:mm:ss");
/**
* The last {@link EObject} which the user has clicked. Might be null when no or an invalid
* Object is selected.
*/
private EObject lastSelectedElement;
/** List of elements for which metrics were displayed */
private List<Pair<MetricTreeNode, Set<MetricKey>>> lastViewedElements = new ArrayList<>();
/** {@inheritDoc} */
@Override
public String getFXMLLocation() {
......@@ -111,15 +144,9 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
/** {@inheritDoc} */
@Override
public void initialize() {
choiceBox.getItems().addAll(MetricKey.values());
childMetricChoiceBox.getItems().addAll(MetricKey.values());
}
/**
* The last {@link EObject} which the user has clicked. Might be null when no or an invalid
* Object is selected.
*/
private EObject lastSelectedElement;
/** {@inheritDoc} */
@Override
public void selectionChanged(IWorkbenchPart part, ISelection selection) {
......@@ -162,9 +189,16 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
}
/**
* Is triggered when the choice box changes.
* Is triggered when the child metric choice box changes.
*/
public void onChildMetricChoiceBoxChange() {
updateCharts("Invalid selection or metrics not available");
}
/**
* Is triggered when one of the scatter choice boxes changes.
*/
public void onChoiceBoxChange() {
public void onScatterMetricChoiceBoxChange() {
updateCharts("Invalid selection or metrics not available");
}
......@@ -183,69 +217,17 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
if(node != null) {
Set<MetricKey> validKeys = new HashSet<>();
validKeys.addAll(node.getStoredDoubles().keySet());
validKeys.addAll(node.getStoredIntegers().keySet());
for(MetricTreeNode child : node.getChildren()) {
validKeys.addAll(child.getStoredDoubles().keySet());
validKeys.addAll(child.getStoredIntegers().keySet());
}
ObservableList<MetricKey> choices = choiceBox.getItems();
choices.retainAll(validKeys);
for(MetricKey key : validKeys) {
if(!choices.contains(key)) {
choices.add(key);
}
}
if(choiceBox.getValue() == null && !choices.isEmpty()) {
// Set choice box to first element in choices, if there are any choices and none
// is currently selected
choiceBox.setValue(choices.get(0));
}
updateChoiceBoxes(node);
if(!node.getChildren().isEmpty()) {
if(choiceBox.getValue() != null) {
SpiderChartViewer viewer = buildSpiderChart(node, choiceBox.getValue());
borderPane.setCenter(viewer.getViewerPane());
// Create PieChart and add data
pieChart.getData().clear();
pieChart.setLegendVisible(false);
pieChart.setTitle("");
DecimalFormat format = new DecimalFormat("0.#");
for(var child : node.getChildren()) {
Double value = child.getValueAsDouble(choiceBox.getValue());
if(value != null) {
PieChart.Data slice = new PieChart.Data(
child.getName() + ": " + format.format(value), value);
pieChart.getData().add(slice);
}
}
}
// scatterChart
scatterChart.getXAxis().setLabel("NUMBER_OF_TOTAL_LEAF_ELEMENTS");
scatterChart.getYAxis().setLabel("NUMBER_OF_TOTAL_CYCLOMATIC_COMPLEXITY");
scatterChart.getData().clear();
for(var child : node.getChildren()) {
Integer leafs = child.getStoredIntegers()
.get(MetricKey.NUMBER_OF_TOTAL_LEAF_ELEMENTS);
Integer complexity = child.getStoredIntegers()
.get(MetricKey.NUMBER_OF_TOTAL_CODE_CYCLOMATIC_COMPLEXITY);
if(leafs != null && complexity != null) {
// Add data to the chart
XYChart.Series<Number, Number> dataSeries = new XYChart.Series<>();
dataSeries.getData().add(new XYChart.Data<>(leafs, complexity));
scatterChart.getData().add(dataSeries);
}
}
updatePieChart(node);
updateScatterChart(node);
updateBarChart(node);
updateLastViewedElements(node);
SpiderChartViewer viewer = buildSpiderChart();
borderPane.setCenter(viewer.getViewerPane());
// Return, otherwise the data will be cleared
return;
......@@ -263,56 +245,158 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
}
/**
* Updates the {@link #pieChart} with data from the provided {@link MetricTreeNode}
*
* @param node
* of which the data will be visualized
* @param key
* {@link MetricKey} of the data to visualize
* @return {@link SpiderChartViewer} with the matching metrics
* which provides the data for this chart
*/
private static SpiderChartViewer buildSpiderChart(MetricTreeNode node, MetricKey key) {
SpiderChart spiderChart = new SpiderChart();
spiderChart.setLegendLabel("Legend");
ChartStyle chartStyle = new ChartStyle(true, true, true);
private void updatePieChart(MetricTreeNode node) {
MetricKey selectedMetric = childMetricChoiceBox.getValue();
if(selectedMetric != null) {
// Create PieChart and add data
pieChart.getData().clear();
pieChart.setLegendVisible(false);
pieChart.setTitle("");
DecimalFormat format = new DecimalFormat("0.#");
for(var child : node.getChildren()) {
Double value = child.getValueAsDouble(selectedMetric);
if(value != null) {
PieChart.Data slice =
new PieChart.Data(child.getName() + ": " + format.format(value), value);
pieChart.getData().add(slice);
}
}
}
}
/**
* Updates the {@link #scatterChart} with data from the provided {@link MetricTreeNode}
*
* @param node
* which provides the data for this chart
*/
private void updateScatterChart(MetricTreeNode node) {
MetricKey xScatterKey = xScatterMetricChoiceBox.getValue();
MetricKey yScatterKey = yScatterMetricChoiceBox.getValue();
if(xScatterKey != null && yScatterKey != null) {
scatterChart.getXAxis().setLabel(xScatterKey.toString());
scatterChart.getYAxis().setLabel(yScatterKey.toString());
List<MetricTreeNode> children = node.getChildren();
scatterChart.getData().clear();
if(children.isEmpty()) {
return null;
for(var child : node.getChildren()) {
Double xValue = child.getValueAsDouble(xScatterKey);
Double yValue = child.getValueAsDouble(yScatterKey);
if(xValue != null && yValue != null) {
// Add data to the chart
XYChart.Series<Number, Number> dataSeries = new XYChart.Series<>();
dataSeries.setName(child.getName());
dataSeries.getData().add(new XYChart.Data<>(xValue, yValue));
scatterChart.getData().add(dataSeries);
}
}
}
}
double maxval = children.stream().map(p -> p.getValueAsDouble(key)).filter(p -> p != null)
.mapToDouble(p -> p).max().orElse(0.0);
/**
* Updates the {@link #barChart} with data from the provided {@link MetricTreeNode}
*
* @param node
* which provides the data for this chart
*/
private void updateBarChart(MetricTreeNode node) {
MetricKey yScatterKey = yScatterMetricChoiceBox.getValue();
if(yScatterKey != null) {
barChart.getYAxis().setLabel(yScatterKey.toString());
barChart.getData().clear();
for(var child : node.getChildren()) {
Double yValue = child.getValueAsDouble(yScatterKey);
if(yValue != null) {
// Add data to the chart
XYChart.Series<String, Number> dataSeries = new XYChart.Series<>();
dataSeries.setName(child.getName());
dataSeries.getData().add(new XYChart.Data<>(child.getName(), yValue));
barChart.getData().add(dataSeries);
}
}
}
}
/**
* Creates a SpiderChart using the elements in
* {@link ModelQualityFXController#lastViewedElements}. A set of keys which are present in all
* elements in the list is created, and then these elements are overlaid.
*/
private SpiderChartViewer buildSpiderChart() {
// Calculate the intersection of the keys, i.e. a set of keys which have a non-null value in
// all elements of the lastViewedElements list
Set<MetricKey> keysIntersection = lastViewedElements.get(0).getValue();
for(var p : lastViewedElements) {
keysIntersection.retainAll(p.getValue());
}
// Create DataSeries for all elements
List<Pair<MetricTreeNode, DataSeries>> dataSeriesList = lastViewedElements.stream()
.map(pair -> new Pair<>(pair.getKey(), new DataSeries(pair.getKey().getName())))
.collect(Collectors.toList());
// Create SpiderChart and set basic styling
SpiderChart spiderChart = new SpiderChart();
spiderChart.setLegendLabel("Legend");
ChartStyle chartStyle = new ChartStyle(true, true, true);
chartStyle.setUseIndividualAxisSegments(false);
chartStyle.setTitleStyle(new FontStyle("Verdana", 14, BLUE.brighter()));
DataSeries elementData = new DataSeries("Data");
for(var child : node.getChildren()) {
Double value = child.getValueAsDouble(key);
String name = child.getName();
if(value == null) {
value = 0.0;
}
if(name == null) {
name = "Impossible null";
// Iterate over all keys, and add data points
for(MetricKey key : keysIntersection) {
// Calculate maximumValue for the range of the axis
Double maxmiumValue = lastViewedElements.stream()
.mapToDouble(p -> p.getKey().getValueAsDouble(key)).max().getAsDouble();
if(maxmiumValue == 0.0) {
// Don't display metrics where all values are zero
continue;
}
DoubleAxis testing = new DoubleAxis(name, 0.0, maxval);
// Create Axis and apply Styling
DoubleAxis doubleAxis = new DoubleAxis(key.toString(), 0.0, maxmiumValue);
AxisStyle aStyle3Segs = new AxisStyle(SOLID_BLACK_1PT, BLACK_VERDANA_14PT, 3,
BLACK_VERDANA_8PT, new DecimalFormat("#.##"));
chartStyle.setAxisStyle(testing, aStyle3Segs);
chartStyle.setAxisStyle(doubleAxis, aStyle3Segs);
spiderChart.addAxis(testing);
elementData.setPoint(testing, value);
spiderChart.addAxis(doubleAxis);
// Add the data point for this axis to all elements
for(var pair : dataSeriesList) {
pair.getValue().setPoint(doubleAxis, pair.getKey().getValueAsDouble(key));
}
}
spiderChart.addData(elementData);
LineStyle olive1pt = new LineStyle(GREEN);
FillStyle oliveFill = new FillStyle(GREEN, 0.25);
DataSeriesStyle iphoneStyle = new DataSeriesStyle(olive1pt, oliveFill, true, true,
BLACK_VERDANA_10PT, 7, new DecimalFormat("#.#"));
chartStyle.setDataSeriesStyle(elementData, iphoneStyle);
// Iterate over all dataSeries and add them to the spider chart
for(int i = 0; i < dataSeriesList.size(); i++) {
var pair = dataSeriesList.get(i);
spiderChart.addData(pair.getValue());
// Assign different colors to different data series
Color color = SPIDER_CHART_OVERLAY_COLORS[i];
LineStyle olive1pt = new LineStyle(color);
FillStyle oliveFill = new FillStyle(color, 0.25);
DataSeriesStyle style = new DataSeriesStyle(olive1pt, oliveFill, true, true,
BLACK_VERDANA_10PT, 7, new DecimalFormat("#.#"));
chartStyle.setDataSeriesStyle(pair.getValue(), style);
}
// Configure legend
LegendStyle legendStyle = new LegendStyle(false, 5, BLACK_VERDANA_12PT);
chartStyle.setLegendStyle(legendStyle);
......@@ -347,4 +431,107 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
updateCharts("Selection does not have metric information");
});
}
/**
* Updates the {@link ModelQualityFXController#lastViewedElements} variable. It will add the
* provided {@link MetricTreeNode} in the back of the list, and if the list is too long it will
* remove the first element.
*
* @param node
* which was selected
*/
private void updateLastViewedElements(MetricTreeNode node) {
// Create set of keys which store values in the ndoe
Set<MetricKey> keys = new HashSet<>();
keys.addAll(node.getStoredDoubles().keySet());
keys.addAll(node.getStoredIntegers().keySet());
// Check if the node is already part of the list, and skip adding it if it is
if(lastViewedElements.stream().allMatch(p -> p.getKey() != node)) {
lastViewedElements.add(new Pair<>(node, keys));
}
// Limit the amount of elements in the list to the amount of colors
if(lastViewedElements.size() > SPIDER_CHART_OVERLAY_COLORS.length) {
lastViewedElements.remove(0);
}
}
/**
* Updates all choice boxes, such that they only allow the selection of keys for which we can
* display data.
* Keys for which the metric is 0.0 in all children are removed
*
* @param node
* used to calculate the allowed keys
*/
private void updateChoiceBoxes(MetricTreeNode node) {
Set<MetricKey> validKeys = getAllChildrenKeys(node);
// This code removes choices for which all children have zero as value, which is not
// useful data to display
Iterator<MetricKey> iterator = validKeys.iterator();
while(iterator.hasNext()) {
MetricKey key = iterator.next();
// Check if the metric is non zero for any child
if(node.getChildren().stream().anyMatch(child -> child.getValueAsDouble(key) != 0.0)) {
// Continue to next child
continue;
}
// If all children have a zero value for the metric, remove the key
iterator.remove();
}
updateChoiceBox(childMetricChoiceBox, validKeys);
updateChoiceBox(xScatterMetricChoiceBox, validKeys);
updateChoiceBox(yScatterMetricChoiceBox, validKeys);
}
/**
* Collects all keys for which values can be retrieved in at least on child
*
* @param node
* for which children the keys should be selected
* @return set of keys which have stored values
*/
private static Set<MetricKey> getAllChildrenKeys(MetricTreeNode node) {
// Get all keys which are stored in the children
Set<MetricKey> childrenKeys = new HashSet<>();
for(MetricTreeNode child : node.getChildren()) {
childrenKeys.addAll(child.getStoredDoubles().keySet());
childrenKeys.addAll(child.getStoredIntegers().keySet());
}
return childrenKeys;
}
/**
* Updates the choice box, such that it will only give the option to select one of the provided
* keys
*
* @param choiceBox
* to be updated
* @param validKeys
* which will be displayed for selection
*/
private static void updateChoiceBox(ChoiceBox<MetricKey> choiceBox, Set<MetricKey> validKeys) {
// Update choice box, so only valid choices can be selected
ObservableList<MetricKey> choices = choiceBox.getItems();
// Remove all non valid keys
choices.retainAll(validKeys);
// Add all keys not
for(MetricKey key : validKeys) {
if(!choices.contains(key)) {
choices.add(key);
}
}
// Check if a choice is selected, and that the choice is valid
if(!choices.isEmpty() &&
(choiceBox.getValue() == null || !choices.contains(choiceBox.getValue()))) {
// Set choice box to first element in choices, if there are any valid choices
choiceBox.setValue(choices.get(0));
}
}
}
MetricDataManager.java 36dc2d04485702ce18dd914789d06d873eb99564 YELLOW
DataRootElement.java d54ab629fc0dc9069586a3c7edfec0734e35dd86 RED
MetricDataManager.java f9e2d04edeb31f0af094d5ad92f6a573c1b90373 RED
MetricKey.java aeca14c4ee3ee4bdcb7d18d8332981aa3d04604d YELLOW
MetricTreeNode.java d89ef9aab7171a66ff7fc30c904e06f50d164ff9 YELLOW
IModelQualityService.java 5168db33728ae6ea4cae4a698ce21a26dce5ec79 YELLOW
ModelQualityService.java 75d1301f64d64c273a1f649ff7a02e396eb98b35 YELLOW
IModelQualityService.java f653e228ce55ad3a03d60a4a561664ee3213d7a5 RED
ModelQualityService.java eb4009f2ed7c2188fccce6505b627684cdfa925e RED
......@@ -157,7 +157,7 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
/**
* Collects all metrics for all elements in the provided {@link ITopLevelElement}.
*
* @param topLvl
* @param topLevelElement
* the {@link ITopLevelElement} on which the analysis should be performed
*/
private void performMetricCollection(ITopLevelElement topLevelElement) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment