// Copyright 2012 Georg-August-Universität Göttingen, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package de.ugoe.cs.autoquest.ui.swt;
import java.awt.BasicStroke;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.LogarithmicAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.ValueMarker;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.xy.DefaultTableXYDataset;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.ui.RectangleAnchor;
import org.jfree.ui.RectangleInsets;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.DefaultFontMapper;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfTemplate;
import com.lowagie.text.pdf.PdfWriter;
import de.ugoe.cs.autoquest.tasktrees.treeifc.DefaultTaskInstanceTraversingVisitor;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTask;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequence;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInfo;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel;
import de.ugoe.cs.autoquest.tasktrees.treeifc.TaskMetric;
import de.ugoe.cs.autoquest.tasktrees.treeifc.TaskTreeUtils;
/**
*
* TODO comment
*
*
* @author Patrick Harms
*/
public class TaskTreePlotUtils {
/** name for visualizing a task count to event coverage plot */
public static final String TASK_COUNT__EVENT_COVERAGE_PLOT = "event coverage plot";
/** name for visualizing the instance counts of tasks ordered by the event coverage */
public static final String ORDERED_TASK_COUNT_PLOT = "task instance count - coverage plot";
/** name for visualizing a cumulative coverage of events by the tasks orded by their event
* coverage */
public static final String CUMULATIVE_TASK_COVERAGE_PLOT = "cumulative task coverage plot";
/** colors */
public static final java.awt.Color[] colors = new java.awt.Color[] {
java.awt.Color.BLACK, java.awt.Color.GRAY, java.awt.Color.RED, java.awt.Color.CYAN,
java.awt.Color.ORANGE, java.awt.Color.GREEN, java.awt.Color.MAGENTA, java.awt.Color.BLUE,
java.awt.Color.PINK, java.awt.Color.DARK_GRAY, java.awt.Color.LIGHT_GRAY,
java.awt.Color.YELLOW
};
/**
*
*/
public static JFreeChart createPlot(String name,
List models,
List modelNames,
List groups)
{
JFreeChart chart = null;
if (TASK_COUNT__EVENT_COVERAGE_PLOT.equals(name)) {
XYDataset dataset1 = createTaskCountEventCoveragePlotDataSet(models.get(0));
chart = ChartFactory.createXYLineChart
(TASK_COUNT__EVENT_COVERAGE_PLOT, "task counts", "event coverage",
dataset1, PlotOrientation.VERTICAL, models.size() > 1, false, false);
for (int i = 1; i < models.size(); i++) {
chart.getXYPlot().setDataset
(i, createTaskCountEventCoveragePlotDataSet(models.get(i)));
}
XYPlot plot = chart.getXYPlot();
NumberAxis domainAxis = new LogarithmicAxis("task counts");
NumberAxis rangeAxis = new LogarithmicAxis("event coverage");
plot.setDomainAxis(domainAxis);
plot.setRangeAxis(rangeAxis);
}
else if (CUMULATIVE_TASK_COVERAGE_PLOT.equals(name)) {
Set mostProminent =
TaskTreeUtils.getMostProminentTasks(models.get(0), models.get(0).getTasks());
XYDataset dataset = createCumulativeTaskCoveragePlotDataSet(models.get(0),
modelNames.get(0));
chart = ChartFactory.createXYLineChart
(CUMULATIVE_TASK_COVERAGE_PLOT,
"Sequences ordered by coverage in % of sequences",
"Cumulative action instance coverage in % of all recorded action instances",
dataset, PlotOrientation.VERTICAL, models.size() > 1, false, false);
final XYPlot plot = chart.getXYPlot();
plot.setBackgroundPaint(java.awt.Color.WHITE);
plot.setDomainAxisLocation(AxisLocation.BOTTOM_OR_RIGHT);
double mpMarker = (double) 100 * mostProminent.size() / dataset.getItemCount(0);
ValueMarker marker = new ValueMarker(mpMarker);
marker.setLabel(new DecimalFormat("#0.0").format(mpMarker));
marker.setLabelAnchor(RectangleAnchor.BOTTOM);
marker.setLabelOffset(new RectangleInsets(0, -12, 10, 15));
plot.addDomainMarker(marker);
double xLow = 0;
double yLow = 0;
double xHigh = 100;
double yHigh = 100;
double maxY = 0;
for (int i = 0; i < dataset.getItemCount(0); i++) {
double xValue = dataset.getXValue(0, i);
double yValue = dataset.getYValue(0, i);
if ((xLow < xValue) && (xValue < mpMarker)) {
xLow = xValue;
yLow = yValue;
}
else if ((xHigh > xValue) && (xValue > mpMarker)) {
xHigh = xValue;
yHigh = yValue;
}
maxY = Math.max(maxY, yValue);
}
marker = new ValueMarker((yHigh + yLow) / 2);
marker.setLabel(new DecimalFormat("#0.0").format((yHigh + yLow) / 2));
marker.setLabelOffset(new RectangleInsets(5, 15, 0, 10));
plot.addRangeMarker(marker);
marker = new ValueMarker(maxY);
marker.setLabel(new DecimalFormat("#0.0").format(maxY));
marker.setLabelOffset(new RectangleInsets(5, 15, 0, 10));
plot.addRangeMarker(marker);
plot.getRenderer(0).setSeriesPaint(0, colors[0]);
plot.getRenderer(0).setSeriesStroke(0, new BasicStroke(3.0f, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND));
for (int i = 1; i < models.size(); i++) {
plot.setDataset(i, createCumulativeTaskCoveragePlotDataSet(models.get(i),
modelNames.get(i)));
plot.setRenderer(i, new XYLineAndShapeRenderer(true, false));
plot.getRenderer(i).setSeriesPaint(0, colors[groups.get(i)]);
plot.getRenderer(i).setSeriesStroke(0, new BasicStroke(1.0f, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND));
}
NumberAxis domainAxis = new NumberAxis
("Sequences ordered by coverage in % of sequences");
domainAxis.setUpperBound(100);
plot.setDomainAxis(domainAxis);
NumberAxis rangeAxis = new NumberAxis
("Cumulative action instance coverage in % of all recorded action instances");
rangeAxis.setUpperBound(100);
plot.setRangeAxis(rangeAxis);
}
else if (ORDERED_TASK_COUNT_PLOT.equals(name)) {
XYDataset dataset = createOrderedTaskCountPlotDataSet(models.get(0), modelNames.get(0));
chart = ChartFactory.createXYLineChart
(ORDERED_TASK_COUNT_PLOT, "tasks ordered by coverage",
"number of instances", dataset, PlotOrientation.VERTICAL, models.size() > 1,
false, false);
final XYPlot plot = chart.getXYPlot();
plot.setBackgroundPaint(java.awt.Color.WHITE);
plot.setDomainAxisLocation(AxisLocation.BOTTOM_OR_RIGHT);
XYLineAndShapeRenderer scatterRenderer = new XYLineAndShapeRenderer(false, true);
scatterRenderer.setSeriesShapesFilled(0, false);
scatterRenderer.setSeriesShape(0, new Ellipse2D.Float(-3.0f, -3.0f, 6.0f, 6.0f));
scatterRenderer.setSeriesPaint(0, colors[0]);
plot.setRenderer(0, scatterRenderer);
for (int i = 1; i < models.size(); i++) {
chart.getXYPlot().setDataset
(i, createOrderedTaskCountPlotDataSet(models.get(i), modelNames.get(i)));
scatterRenderer = new XYLineAndShapeRenderer(false, true);
scatterRenderer.setSeriesShapesFilled(0, false);
scatterRenderer.setSeriesShape(0, new Ellipse2D.Float(-3.0f, -3.0f, 6.0f, 6.0f));
scatterRenderer.setSeriesPaint(0, colors[i]);
plot.setRenderer(i, scatterRenderer);
}
NumberAxis domainAxis = new NumberAxis("sequences ordered by coverage");
domainAxis.setUpperBound(100);
plot.setDomainAxis(domainAxis);
NumberAxis rangeAxis = new LogarithmicAxis("number of instances");
plot.setRangeAxis(rangeAxis);
}
return chart;
}
/**
*
*/
public static void saveChartToPDF(JFreeChart chart, String fileName, int width, int height)
throws Exception
{
if (chart != null) {
BufferedOutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(fileName));
//convert chart to PDF with iText:
Rectangle pagesize = new Rectangle(width, height);
com.lowagie.text.Document document =
new com.lowagie.text.Document(pagesize, 50, 50, 50, 50);
try {
PdfWriter writer = PdfWriter.getInstance(document, out);
document.addAuthor("JFreeChart");
document.open();
PdfContentByte cb = writer.getDirectContent();
PdfTemplate tp = cb.createTemplate(width, height);
Graphics2D g2 = tp.createGraphics(width, height, new DefaultFontMapper());
Rectangle2D r2D = new Rectangle2D.Double(0, 0, width, height);
TextTitle title = chart.getTitle();
chart.setTitle((TextTitle) null);
chart.draw(g2, r2D, null);
chart.setTitle(title);
g2.dispose();
cb.addTemplate(tp, 0, 0);
}
finally {
document.close();
}
}
finally {
if (out != null) {
out.close();
}
}
}
}
/**
*
*/
private static XYDataset createTaskCountEventCoveragePlotDataSet(ITaskModel taskModel) {
// tree maps required to have sorted maps
TreeMap minValues = new TreeMap<>();
TreeMap> allValues = new TreeMap<>();
TreeMap maxValues = new TreeMap<>();
for (ITask task : taskModel.getTasks()) {
if (task instanceof ISequence) {
ITaskInfo info = taskModel.getTaskInfo(task);
int count = task.getInstances().size();
int eventCoverage = info.getMeasureValue(TaskMetric.EVENT_COVERAGE);
Integer minValue = minValues.get(count);
if ((minValue == null) || (minValue > eventCoverage)) {
minValues.put(count, eventCoverage);
}
Integer maxValue = maxValues.get(count);
if ((maxValue == null) || (maxValue < eventCoverage)) {
maxValues.put(count, eventCoverage);
}
List values = allValues.get(count);
if (values == null) {
values = new LinkedList();
allValues.put(count, values);
}
values.add(eventCoverage);
}
}
DefaultTableXYDataset dataset = new DefaultTableXYDataset();
XYSeries minSeries = new XYSeries("min values", true, false);
XYSeries avgSeries = new XYSeries("avg values", true, false);
XYSeries maxSeries = new XYSeries("max values", true, false);
// entries are correctly sorted due to the map
for (Map.Entry> entry : allValues.entrySet()) {
int count = entry.getKey();
minSeries.add(count, minValues.get(count));
maxSeries.add(count, maxValues.get(count));
avgSeries.add(count, getAverage(entry.getValue()));
}
dataset.addSeries(minSeries);
dataset.addSeries(avgSeries);
dataset.addSeries(maxSeries);
return dataset;
}
/**
*
*/
private static XYDataset createCumulativeTaskCoveragePlotDataSet(ITaskModel taskModel,
String taskModelName)
{
Map> coverageCounts = new HashMap<>();
Map> coverages = new HashMap<>();
Set allEvents = new HashSet<>();
int maxCoverage = 0;
for (ITask task : taskModel.getTasks()) {
if (task instanceof ISequence) {
final Set coveredEvents = new HashSet<>();
for (ITaskInstance instance : task.getInstances()) {
instance.accept(new DefaultTaskInstanceTraversingVisitor() {
@Override
public void visit(IEventTaskInstance eventTaskInstance) {
coveredEvents.add(eventTaskInstance);
}
});
}
coverages.put((ISequence) task, coveredEvents);
List tasksWithSameCoverage = coverageCounts.get(coveredEvents.size());
if (tasksWithSameCoverage == null) {
tasksWithSameCoverage = new LinkedList<>();
coverageCounts.put(coveredEvents.size(), tasksWithSameCoverage);
}
tasksWithSameCoverage.add((ISequence) task);
maxCoverage = Math.max(maxCoverage, coveredEvents.size());
}
else if (task instanceof IEventTask) {
for (ITaskInstance instance : task.getInstances()) {
allEvents.add((IEventTaskInstance) instance);
}
}
}
XYSeries cumulativeSeries = new XYSeries(taskModelName, true, false);
Set cumulativeCoverage = new HashSet<>();
// entries are correctly sorted due to the map
int sequenceIndex = 1;
for (int i = maxCoverage; i > 0; i--) {
List sequencesWithSameCoverage = coverageCounts.get(i);
if (sequencesWithSameCoverage == null) {
continue;
}
for (ISequence sequence : sequencesWithSameCoverage) {
cumulativeCoverage.addAll(coverages.get(sequence));
double xvalue = (double) 100 * sequenceIndex / coverages.size();
double yvalue = (double) 100 * cumulativeCoverage.size() / allEvents.size();
cumulativeSeries.add(xvalue, yvalue);
sequenceIndex++;
}
}
DefaultTableXYDataset dataset = new DefaultTableXYDataset();
dataset.addSeries(cumulativeSeries);
return dataset;
}
/**
*
*/
private static XYDataset createOrderedTaskCountPlotDataSet(ITaskModel taskModel,
String taskModelName)
{
Map> coverageCounts = new HashMap<>();
Map> coverages = new HashMap<>();
int maxCoverage = 0;
for (ITask task : taskModel.getTasks()) {
if (task instanceof ISequence) {
final Set coveredEvents = new HashSet<>();
for (ITaskInstance instance : task.getInstances()) {
instance.accept(new DefaultTaskInstanceTraversingVisitor() {
@Override
public void visit(IEventTaskInstance eventTaskInstance) {
coveredEvents.add(eventTaskInstance);
}
});
}
coverages.put((ISequence) task, coveredEvents);
List tasksWithSameCoverage = coverageCounts.get(coveredEvents.size());
if (tasksWithSameCoverage == null) {
tasksWithSameCoverage = new LinkedList<>();
coverageCounts.put(coveredEvents.size(), tasksWithSameCoverage);
}
tasksWithSameCoverage.add((ISequence) task);
maxCoverage = Math.max(maxCoverage, coveredEvents.size());
}
}
XYSeries counts = new XYSeries(taskModelName, true, false);
// entries are correctly sorted due to the map
int sequenceIndex = 1;
for (int i = maxCoverage; i > 0; i--) {
List sequencesWithSameCoverage = coverageCounts.get(i);
if (sequencesWithSameCoverage == null) {
continue;
}
for (ISequence sequence : sequencesWithSameCoverage) {
double xvalue = (double) 100 * sequenceIndex / coverages.size();
counts.add(xvalue, sequence.getInstances().size());
sequenceIndex++;
}
}
DefaultTableXYDataset dataset = new DefaultTableXYDataset();
dataset.addSeries(counts);
return dataset;
}
/**
*
*/
private static double getAverage(List values) {
int all = 0;
for (Integer value : values) {
all += value;
}
return ((double) all) / values.size();
}
}