Java tutorial
/* * Copyright (c) 2013-2014 mgm technology partners GmbH * * 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 com.mgmtp.perfload.perfalyzer.reportpreparation; import com.mgmtp.perfload.perfalyzer.annotations.IntFormat; import com.mgmtp.perfload.perfalyzer.util.Marker; import org.jfree.chart.JFreeChart; import org.jfree.chart.StandardChartTheme; import org.jfree.chart.axis.LogarithmicAxis; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.block.LineBorder; import org.jfree.chart.plot.CombinedDomainXYPlot; import org.jfree.chart.plot.DefaultDrawingSupplier; import org.jfree.chart.plot.IntervalMarker; import org.jfree.chart.plot.Plot; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.xy.XYItemRenderer; import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; import org.jfree.chart.title.LegendTitle; import org.jfree.ui.Layer; import org.jfree.ui.RectangleAnchor; import org.jfree.ui.RectangleInsets; import javax.imageio.ImageIO; import javax.inject.Inject; import javax.inject.Singleton; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.GradientPaint; import java.awt.Polygon; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.text.NumberFormat; import java.util.List; import java.util.ResourceBundle; import static com.google.common.io.Files.createParentDirs; import static com.mgmtp.perfload.perfalyzer.util.PerfAlyzerUtils.selectDisplayData; /** * @author rnaegele */ @Singleton public class PlotCreator { private static final StandardChartTheme CHART_THEME = new StandardChartTheme("JFree"); static { CHART_THEME.setDrawingSupplier(new PerfAlyzerDrawingSupplier()); } public static enum AxisType { LINEAR, LOGARITHMIC } public static enum RendererType { LINES { @Override XYItemRenderer createRenderer() { return new XYLineAndShapeRenderer(true, false); } }, SHAPES { @Override XYItemRenderer createRenderer() { return new XYLineAndShapeRenderer(false, true); } }, LINES_AND_SHAPES { @Override XYItemRenderer createRenderer() { return new XYLineAndShapeRenderer(true, true); } }; abstract XYItemRenderer createRenderer(); } public static enum ChartDimensions { DEFAULT(640, 400), LARGE(1115, 800), WIDE(1115, 400); private final int width; private final int height; private ChartDimensions(final int width, final int height) { this.width = width; this.height = height; } /** * @return the width */ public int getWidth() { return width; } /** * @return the height */ public int getHeight() { return height; } } private final NumberFormat numberFormat; private final ResourceBundle resourceBundle; private final List<DisplayData> displayDataList; private final List<Marker> markers; @Inject public PlotCreator(@IntFormat final NumberFormat numberFormat, final ResourceBundle resourceBundle, final List<DisplayData> displayDataList, final List<Marker> markers) { this.numberFormat = numberFormat; this.resourceBundle = resourceBundle; this.displayDataList = displayDataList; this.markers = markers; } public JFreeChart createPlot(final AxisType xAxisType, final AxisType yAxisType, final RendererType rendererType, final DisplayData displayData, final DataRange dataRange, boolean showMarkers, final NumberDataSet... dataSets) { NumberAxis xAxis = createAxis(xAxisType, resourceBundle.getString(displayData.getUnitX())); XYPlot plot; if (dataSets.length == 1) { NumberAxis yAxis = createAxis(yAxisType, resourceBundle.getString(displayData.getUnitY())); plot = new XYPlot(dataSets[0], xAxis, yAxis, rendererType.createRenderer()); } else { CombinedDomainXYPlot combinedPlot = new CombinedDomainXYPlot(xAxis); for (int i = 0; i < dataSets.length; ++i) { NumberDataSet dataSet = dataSets[i]; // no range for y-axis! NumberAxis yAxis = createAxis(yAxisType, resourceBundle.getString(displayData.getUnitYList().get(i))); XYPlot subPlot = new XYPlot(dataSet, null, yAxis, rendererType.createRenderer()); combinedPlot.add(subPlot); formatPlot(subPlot); } plot = combinedPlot; } JFreeChart chart = new JFreeChart(plot); CHART_THEME.apply(chart); formatPlot(plot); if (showMarkers) { for (Marker marker : markers) { IntervalMarker im = new IntervalMarker(marker.getLeftMillis() / 1000L, marker.getRightMillis() / 1000L); im.setLabel(marker.getName()); im.setLabelFont(new Font("Sans Serif", Font.ITALIC | Font.BOLD, 14)); im.setLabelAnchor(RectangleAnchor.TOP); im.setLabelOffset(new RectangleInsets(8d, 0d, 0d, 0d)); im.setLabelPaint(Color.BLACK); im.setAlpha(.2f); im.setPaint(Color.WHITE); im.setOutlinePaint(Color.BLACK); im.setOutlineStroke(new BasicStroke(1.0f)); plot.addDomainMarker(im, Layer.BACKGROUND); } } LegendTitle legend = chart.getLegend(); legend.setBackgroundPaint(new Color(229, 229, 229)); legend.setFrame( new LineBorder(new Color(213, 213, 213), new BasicStroke(1.0f), legend.getFrame().getInsets())); // only for non-logarithmic axes // range must be set after plot is created, otherwise nothing is drawn if (dataRange != null && !xAxisType.equals(AxisType.LOGARITHMIC)) { xAxis.setRange(dataRange.getLowerSeconds(), dataRange.getUpperSeconds()); } return chart; } private NumberAxis createAxis(final AxisType axisType, final String axisLabel) { NumberAxis numberAxis = axisType.equals(AxisType.LOGARITHMIC) ? new LogarithmicAxis(axisLabel) : new NumberAxis(axisLabel); numberAxis.setNumberFormatOverride((NumberFormat) numberFormat.clone()); return numberAxis; } private void formatPlot(final Plot plot) { plot.setBackgroundPaint( new GradientPaint(0f, 0f, new Color(213, 213, 213), 0f, 0f, new Color(156, 156, 156))); plot.setOutlinePaint(new Color(221, 221, 221)); } public void writePlotFile(final File file, final AxisType xAxisType, final AxisType yAxisType, final RendererType rendererType, final DisplayData displayData, final ChartDimensions dimensions, final DataRange dataRange, boolean showMarkers, final NumberDataSet... dataSets) throws IOException { createParentDirs(file); for (NumberDataSet dataSet : dataSets) { dataSet.sortSeries(); } JFreeChart chart = createPlot(xAxisType, yAxisType, rendererType, displayData, dataRange, showMarkers, dataSets); BufferedImage chartImage = chart.createBufferedImage(dimensions.getWidth(), dimensions.getHeight()); ImageIO.write(chartImage, "png", file); } public void writePlotFile(final File file, final AxisType xAxisType, final AxisType yAxisType, final RendererType rendererType, final ChartDimensions dimensions, final DataRange dataRange, boolean showMarkers, final NumberDataSet... dataSets) throws IOException { DisplayData displayData = selectDisplayData(file, displayDataList); writePlotFile(file, xAxisType, yAxisType, rendererType, displayData, dimensions, dataRange, showMarkers, dataSets); } static class PerfAlyzerDrawingSupplier extends DefaultDrawingSupplier { public PerfAlyzerDrawingSupplier() { super(DEFAULT_PAINT_SEQUENCE, DEFAULT_FILL_PAINT_SEQUENCE, DEFAULT_OUTLINE_PAINT_SEQUENCE, new Stroke[] { new BasicStroke(1.5f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL) }, DEFAULT_OUTLINE_STROKE_SEQUENCE, createStandardSeriesShapes()); } /** * Creates an array of standard shapes to display for the items in series on charts. * * @return The array of shapes. */ public static Shape[] createStandardSeriesShapes() { Shape[] result = new Shape[10]; double size = 4.0; double delta = size / 2.0; // square result[0] = new Rectangle2D.Double(-delta, -delta, size, size); // circle result[1] = new Ellipse2D.Double(-delta, -delta, size, size); // up-pointing triangle int[] xpoints = intArray(0.0, delta, -delta); int[] ypoints = intArray(-delta, delta, delta); result[2] = new Polygon(xpoints, ypoints, 3); // diamond xpoints = intArray(0.0, delta, 0.0, -delta); ypoints = intArray(-delta, 0.0, delta, 0.0); result[3] = new Polygon(xpoints, ypoints, 4); // horizontal rectangle result[4] = new Rectangle2D.Double(-delta, -delta / 2, size, size / 2); // down-pointing triangle xpoints = intArray(-delta, +delta, 0.0); ypoints = intArray(-delta, -delta, delta); result[5] = new Polygon(xpoints, ypoints, 3); // horizontal ellipse result[6] = new Ellipse2D.Double(-delta, -delta / 2, size, size / 2); // right-pointing triangle xpoints = intArray(-delta, delta, -delta); ypoints = intArray(-delta, 0.0, delta); result[7] = new Polygon(xpoints, ypoints, 3); // vertical rectangle result[8] = new Rectangle2D.Double(-delta / 2, -delta, size / 2, size); // left-pointing triangle xpoints = intArray(-delta, delta, delta); ypoints = intArray(0.0, -delta, +delta); result[9] = new Polygon(xpoints, ypoints, 3); return result; } /** * Helper method to avoid lots of explicit casts in getShape(). Returns an array containing * the provided doubles cast to ints. * * @param a * x * @param b * y * @param c * z * @return int[3] with converted params. */ private static int[] intArray(final double a, final double b, final double c) { return new int[] { (int) a, (int) b, (int) c }; } /** * Helper method to avoid lots of explicit casts in getShape(). Returns an array containing * the provided doubles cast to ints. * * @param a * x * @param b * y * @param c * z * @param d * t * @return int[4] with converted params. */ private static int[] intArray(final double a, final double b, final double c, final double d) { return new int[] { (int) a, (int) b, (int) c, (int) d }; } } }