/*******************************************************************************
* Copyright (c) 2007, 2010 The Planets Project Partners.
*
* All rights reserved. This program and the accompanying
* materials are made available under the terms of the
* Apache License, Version 2.0 which accompanies
* this distribution, and is available at
* http://www.apache.org/licenses/LICENSE-2.0
*
*******************************************************************************/
/**
*
*/
package eu.planets_project.tb.impl.chart;
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Paint;
import java.awt.RenderingHints;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.batik.dom.GenericDOMImplementation;
import org.apache.batik.svggen.SVGGraphics2D;
import org.apache.batik.svggen.SVGGraphics2DIOException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.LogAxis;
import org.jfree.chart.labels.StandardCategoryToolTipGenerator;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.category.StandardBarPainter;
import org.jfree.chart.util.LogFormat;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.time.Day;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.date.MonthConstants;
import org.jfree.ui.RectangleInsets;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import eu.planets_project.tb.api.data.util.DigitalObjectRefBean;
import eu.planets_project.tb.api.model.Experiment;
import eu.planets_project.tb.api.persistency.ExperimentPersistencyRemote;
import eu.planets_project.tb.gui.backing.exp.ResultsForDigitalObjectBean;
import eu.planets_project.tb.impl.data.util.DataHandlerImpl;
import eu.planets_project.tb.impl.model.eval.mockup.TecRegMockup;
import eu.planets_project.tb.impl.model.exec.BatchExecutionRecordImpl;
import eu.planets_project.tb.impl.model.exec.ExecutionRecordImpl;
import eu.planets_project.tb.impl.model.exec.ExecutionStageRecordImpl;
import eu.planets_project.tb.impl.persistency.ExperimentPersistencyImpl;
/**
* @author <a href="mailto:Andrew.Jackson@bl.uk">Andy Jackson</a>
*
*/
public class ExperimentChartServlet extends HttpServlet {
/**
* @author anj
*/
public class RunComparator implements Comparator<ExecutionRecordImpl> {
/* (non-Javadoc)
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare(ExecutionRecordImpl arg0, ExecutionRecordImpl arg1) {
if( arg0 != null && arg1 != null && arg0.getStartDate() != null ) {
return arg0.getStartDate().compareTo(arg1.getStartDate());
}
return 0;
}
}
/** */
private static Log log = LogFactory.getLog(ExperimentChartServlet.class);
/**
*
*/
private static final long serialVersionUID = -417368403030414811L;
/**
* Default constructor.
*/
public ExperimentChartServlet() {
// nothing required
}
/**
* Process a GET request.
*
* @param request the request.
* @param response the response.
*
* @throws ServletException if there is a servlet related problem.
* @throws IOException if there is an I/O problem.
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
OutputStream out = response.getOutputStream();
try {
String type = request.getParameter("type");
String format = request.getParameter("format");
String eid = request.getParameter("eid");
JFreeChart chart = null;
if ( "pie".equalsIgnoreCase(type) ) {
chart = createPieChart();
}
else if ( "bar".equalsIgnoreCase(type) ) {
chart = createBarChart();
}
else if ( "time".equalsIgnoreCase(type) ) {
chart = createTimeSeriesChart();
}
else if ( "exp".equalsIgnoreCase(type) ) {
chart = createXYChart(eid);
}
else if ( "wall".equalsIgnoreCase(type) ) {
chart = createWallclockChart(eid);
} else {
chart = null;
}
// Render
if (chart != null) {
if( "svg".equalsIgnoreCase(format) ) {
response.setContentType("image/svg+xml");
writeChartAsSVG(out, chart, 600, 500);
} else {
response.setContentType("image/png");
// force aliasing of the rendered content..
chart.getRenderingHints().put( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON );
ChartUtilities.writeChartAsPNG(out, chart, 600, 500);
}
}
}
catch (Exception e) {
System.err.println(e.toString());
e.printStackTrace();
}
finally {
out.close();
}
}
/**
* Creates a sample pie chart.
*
* @return a pie chart.
*/
private JFreeChart createPieChart() {
// create a dataset...
DefaultPieDataset data = new DefaultPieDataset();
data.setValue("One", new Double(43.2));
data.setValue("Two", new Double(10.0));
data.setValue("Three", new Double(27.5));
data.setValue("Four", new Double(17.5));
data.setValue("Five", new Double(11.0));
data.setValue("Six", new Double(19.4));
JFreeChart chart = ChartFactory.createPieChart(
"Pie Chart", data, true, true, false
);
return chart;
}
/**
* Creates a sample bar chart.
*
* @return a bar chart.
*/
private JFreeChart createBarChart() {
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
dataset.addValue(10.0, "S1", "C1");
dataset.addValue(4.0, "S1", "C2");
dataset.addValue(15.0, "S1", "C3");
dataset.addValue(14.0, "S1", "C4");
dataset.addValue(-5.0, "S2", "C1");
dataset.addValue(-7.0, "S2", "C2");
dataset.addValue(14.0, "S2", "C3");
dataset.addValue(-3.0, "S2", "C4");
dataset.addValue(6.0, "S3", "C1");
dataset.addValue(17.0, "S3", "C2");
dataset.addValue(-12.0, "S3", "C3");
dataset.addValue(7.0, "S3", "C4");
dataset.addValue(7.0, "S4", "C1");
dataset.addValue(15.0, "S4", "C2");
dataset.addValue(11.0, "S4", "C3");
dataset.addValue(0.0, "S4", "C4");
dataset.addValue(-8.0, "S5", "C1");
dataset.addValue(-6.0, "S5", "C2");
dataset.addValue(10.0, "S5", "C3");
dataset.addValue(-9.0, "S5", "C4");
dataset.addValue(9.0, "S6", "C1");
dataset.addValue(8.0, "S6", "C2");
dataset.addValue(null, "S6", "C3");
dataset.addValue(6.0, "S6", "C4");
dataset.addValue(-10.0, "S7", "C1");
dataset.addValue(9.0, "S7", "C2");
dataset.addValue(7.0, "S7", "C3");
dataset.addValue(7.0, "S7", "C4");
dataset.addValue(11.0, "S8", "C1");
dataset.addValue(13.0, "S8", "C2");
dataset.addValue(9.0, "S8", "C3");
dataset.addValue(9.0, "S8", "C4");
dataset.addValue(-3.0, "S9", "C1");
dataset.addValue(7.0, "S9", "C2");
dataset.addValue(11.0, "S9", "C3");
dataset.addValue(-10.0, "S9", "C4");
JFreeChart chart = ChartFactory.createBarChart3D(
"Bar Chart",
"Category",
"Value",
dataset,
PlotOrientation.VERTICAL,
true,
true,
false
);
return chart;
}
/**
* Creates a sample time series chart.
*
* @return a time series chart.
*/
private JFreeChart createTimeSeriesChart() {
// here we just populate a series with random data...
TimeSeries series = new TimeSeries("Random Data");
Day current = new Day(1, MonthConstants.JANUARY, 2001);
for (int i = 0; i < 100; i++) {
series.add(current, Math.random() * 100);
current = (Day) current.next();
}
XYDataset data = new TimeSeriesCollection(series);
JFreeChart chart = ChartFactory.createTimeSeriesChart(
"Time Series Chart", "Date", "Rate",
data, true, true, false
);
return chart;
}
/**
* Creates a sample dataset.
*
* @return A sample dataset.
*/
private static XYDataset createDataset() {
XYSeries series = new XYSeries("Random Data");
series.add(1.0, 500.2);
series.add(5.0, 694.1);
series.add(4.0, 100.0);
series.add(12.5, 734.4);
series.add(17.3, 453.2);
series.add(21.2, 500.2);
series.add(21.9, null);
series.add(25.6, 734.4);
series.add(30.0, 453.2);
return new XYSeriesCollection(series);
}
public JFreeChart createXYChart(String expId) {
ExperimentPersistencyRemote edao = ExperimentPersistencyImpl.getInstance();
long eid = Long.parseLong(expId);
log.info("Building experiment chart for eid = "+eid);
Experiment exp = edao.findExperiment(eid);
final String expName = exp.getExperimentSetup().getBasicProperties().getExperimentName();
final XYSeries series = new XYSeries(expName);
for( BatchExecutionRecordImpl batch : exp.getExperimentExecutable().getBatchExecutionRecords() ) {
int i = 1;
for( ExecutionRecordImpl exr : batch.getRuns() ) {
//log.info("Found Record... "+exr+" stages: "+exr.getStages());
if( exr != null && exr.getStages() != null ) {
// Look up the object, so we can get the name.
DigitalObjectRefBean dh = new DataHandlerImpl().get(exr.getDigitalObjectReferenceCopy());
String dobName = "Object "+i;
if( dh != null ) dobName = dh.getName();
for( ExecutionStageRecordImpl exsr : exr.getStages() ) {
Double time = exsr.getDoubleMeasurement( TecRegMockup.PROP_SERVICE_TIME );
Double size = exsr.getDoubleMeasurement(TecRegMockup.PROP_DO_SIZE);
// Look for timing:
if( time != null && size != null && size.doubleValue() > 0.0 && time.doubleValue() > 0.0 ) {
series.add(size, time);
/*
if( exsr.isMarkedAsSuccessful() ) {
dataset.addValue( time, "Succeded", dobName);
} else {
dataset.addValue( time, "Failed", dobName);
}
*/
}
}
}
// Increment, for the next run.
i++;
}
}
// Create the chart.
JFreeChart chart = ChartFactory.createScatterPlot(
null, "Size [bytes]", "Time [s]", new XYSeriesCollection(series), PlotOrientation.VERTICAL, true, true, false
);
XYPlot plot = (XYPlot) chart.getPlot();
LogAxis xAxis = new LogAxis("Size [bytes]");
// Set the base appropriately:
xAxis.setBase(1024.0);
// xAxis.setTickUnit( new NumberTickUnit(128.0) );
// xAxis.getTickUnit().getMinorTickCount();
// FIXME This should really be a KB/MB/etc number formatter...
xAxis.setNumberFormatOverride(new LogFormat(1024.0, "1024", true));
// LogAxis yAxis = new LogAxis("Y");
// yAxis.setNumberFormatOverride(new LogFormat(10.0, "10", true));
plot.setDomainAxis(xAxis);
// plot.setRangeAxis(yAxis);
// Add some tool-tips
plot.getRenderer().setBaseToolTipGenerator( new StandardXYToolTipGenerator() );
return chart;
}
public JFreeChart createWallclockChart(String expId) {
ExperimentPersistencyRemote edao = ExperimentPersistencyImpl.getInstance();
long eid = Long.parseLong(expId);
log.info("Building experiment chart for eid = "+eid);
Experiment exp = edao.findExperiment(eid);
final DefaultCategoryDataset dataset = new DefaultCategoryDataset();
final String expName = exp.getExperimentSetup().getBasicProperties().getExperimentName();
boolean hasSuccesses = false;
boolean hasFails = false;
for( BatchExecutionRecordImpl batch : exp.getExperimentExecutable().getBatchExecutionRecords() ) {
int i = 1;
List<ExecutionRecordImpl> runs = new ArrayList<ExecutionRecordImpl>(batch.getRuns());
Collections.sort(runs, new RunComparator() );
for( ExecutionRecordImpl exr : runs ) {
//log.info("Found Record... "+exr+" stages: "+exr.getStages());
if( exr != null && exr.getStages() != null ) {
// Look up the object, so we can get the name.
DigitalObjectRefBean dh = new DataHandlerImpl().get(exr.getDigitalObjectReferenceCopy());
String dobName = "Object "+i;
if( dh != null ) dobName = dh.getName();
ResultsForDigitalObjectBean res = new ResultsForDigitalObjectBean(exr.getDigitalObjectReferenceCopy());
Double time = null;
boolean success = false;
// First, attempt to pull from stage records:
// FIXME: Note that this record is really at the wrong level.
/*
if( exr.getStages().size() == 1 ) {
for( ExecutionStageRecordImpl exsr : exr.getStages() ) {
Double stageTime = exsr.getDoubleMeasurement( TecRegMockup.PROP_SERVICE_TIME );
if( stageTime != null ) {
time = stageTime;
success = exsr.isMarkedAsSuccessful();
}
}
}
*/
// Pick up from record duration:
if( time == null && res.getExecutionDuration()!=null){
//convert from milli seconds to seconds
time = (double)res.getExecutionDuration()/1000.0;
success = res.getHasExecutionSucceededOK();
}
log.info("Found DOB: {"+exr.getDigitalObjectReferenceCopy()+"} {"+dobName+"} w/ time "+time);
if( res.getExecutionRecord() != null )
log.info("Timing: "+res.getExecutionRecord().getStartDate()+" "+res.getExecutionRecord().getEndDate());
if( time != null ) {
if( success ) {
dataset.addValue( time, "Succeeded", dobName);
hasSuccesses = true;
} else {
dataset.addValue( time, "Failed", dobName);
hasFails = true;
}
}
}
// Increment, for the next run.
i++;
}
}
int si = dataset.getRowIndex("Succeeded");
int ri = dataset.getRowIndex("Failed");
// Create the chart.
JFreeChart chart = ChartFactory.createStackedBarChart(
null, "Digital Object", "Time [s]", dataset, PlotOrientation.VERTICAL, true, true, false
);
// set the background color for the chart...
chart.setBackgroundPaint(Color.white);
// get a reference to the plot for further customisation...
final CategoryPlot plot = chart.getCategoryPlot();
plot.setBackgroundPaint(Color.lightGray);
plot.setDomainGridlinePaint(Color.gray);
plot.setRangeGridlinePaint(Color.gray);
// set the range axis to display integers only...
//final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
//rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
// disable bar outlines...
final BarRenderer renderer = (BarRenderer) plot.getRenderer();
renderer.setDrawBarOutline(true);
renderer.setShadowVisible(false);
renderer.setBarPainter(new StandardBarPainter());
// set up gradient paints for series...
final GradientPaint gp0 = new GradientPaint(
0.0f, 0.0f, Color.green,
0.0f, 0.0f, new Color(0.0f, 0.9f, 0.0f)
);
final GradientPaint gp1 = new GradientPaint(
0.0f, 0.0f, Color.red,
0.0f, 0.0f, new Color(0.9f, 0.0f, 0.0f)
);
if( hasSuccesses ) renderer.setSeriesPaint(si, gp0);
if( hasFails ) renderer.setSeriesPaint(ri, gp1);
// Set the tooltips...
//renderer.setBaseItemURLGenerator(new StandardCategoryURLGenerator("xy_chart.jsp","series","section"));
renderer.setBaseToolTipGenerator(new MeasurementToolTipGenerator());
final CategoryAxis domainAxis = plot.getDomainAxis();
domainAxis.setCategoryLabelPositions(
CategoryLabelPositions.createUpRotationLabelPositions(Math.PI / 4.0)
);
// More settings
chart.getRenderingHints().put( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON );
// Remove the border, as the SVG renderer has problems with the text overflowing.
chart.getLegend().setBorder(0.0, 0.0, 0.0, 0.0);
// Remove the padding between the axes and the plot:
chart.getPlot().setInsets(new RectangleInsets(0.0, 0.0, 0.0, 0.0));
// Set a gradient fill, fading towards the top:
final GradientPaint gpb0 = new GradientPaint(
0.0f, 0.0f, new Color(245, 245, 245),
0.0f, 0.0f, Color.white
);
chart.getPlot().setBackgroundPaint( gpb0 );
return chart;
}
static class MeasurementToolTipGenerator extends StandardCategoryToolTipGenerator {
/**
*
*/
private static final long serialVersionUID = 5318030018610824973L;
/* (non-Javadoc)
* @see org.jfree.chart.labels.StandardCategoryToolTipGenerator#generateToolTip(org.jfree.data.category.CategoryDataset, int, int)
*/
@Override
public String generateToolTip(CategoryDataset dataset, int row,
int column) {
String toolTip = super.generateToolTip(dataset, row, column);
toolTip = super.generateColumnLabel(dataset, column)+" - "+super.generateRowLabel(dataset, row)+" in " + dataset.getValue(row, column) + "s";
return toolTip;
}
}
/**
* A custom renderer that returns a different color for each item in a
* single series.
*/
static class CustomRenderer extends BarRenderer {
/**
*
*/
private static final long serialVersionUID = 1404034457254212917L;
/** The colors. */
private Paint[] colors;
/**
* Creates a new renderer.
*
* @param colors the colors.
*/
public CustomRenderer(Paint[] colors) {
this.colors = colors;
}
/**
* Returns the paint for an item. Overrides the default behaviour
* inherited from AbstractSeriesRenderer.
*
* @param row the series.
* @param column the category.
*
* @return The item color.
*/
public Paint getItemPaint(int row, int column) {
return this.colors[column % this.colors.length];
}
}
public static void writeChartAsSVG( OutputStream outstream, JFreeChart chart, int width, int height )
throws UnsupportedEncodingException, SVGGraphics2DIOException {
Writer out = new OutputStreamWriter( outstream, "UTF-8" );
// THE FOLLOWING CODE BASED ON THE EXAMPLE IN THE BATIK DOCUMENTATION...
// Get a DOMImplementation
DOMImplementation domImpl
= GenericDOMImplementation.getDOMImplementation();
// Create an instance of org.w3c.dom.Document
Document document = domImpl.createDocument(null, "svg", null);
// Create an instance of the SVG Generator
SVGGraphics2D svgGenerator = new SVGGraphics2D(document);
// set the precision to avoid a null pointer exception in Batik 1.5
svgGenerator.getGeneratorContext().setPrecision(3);
// Other opts:
svgGenerator.getGeneratorContext().setEmbeddedFontsOn(true);
// svgGenerator.setFont( new Font("SansSerif", Font.PLAIN, 8) );
// Ask the chart to render into the SVG Graphics2D implementation
chart.draw(svgGenerator, new Rectangle2D.Double(0, 0, width, height), null);
// Finally, stream out SVG to a file using UTF-8 character to
// byte encoding
boolean useCSS = true;
svgGenerator.stream(out, useCSS);
}
}