/*
* Copyright (c) 2010-2012 Grid Dynamics Consulting Services, Inc, All Rights Reserved
* http://www.griddynamics.com
*
* This library is free software; you can redistribute it and/or modify it under the terms of
* the Apache License; either
* version 2.0 of the License, or any later version.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.griddynamics.jagger.reporting.chart;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import com.griddynamics.jagger.util.Pair;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.*;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.category.CategoryItemRenderer;
import org.jfree.chart.renderer.category.StandardBarPainter;
import org.jfree.chart.renderer.xy.*;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.time.Millisecond;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.*;
public class ChartHelper {
public enum ColorTheme {LIGHT, DARK}
public static JFreeChart createTimeSeriesChart(String title, List<TimeSeriesChartSpecification> specifications, String xLabel, String yLabel, ColorTheme theme) {
TimeSeriesCollection dataCollection = new TimeSeriesCollection();
for (TimeSeriesChartSpecification specification : specifications) {
TimeSeries series = new TimeSeries(specification.getLabel(), Millisecond.class);
for (Map.Entry<Date, Double> point : specification.getData().entrySet()) {
series.add(new Millisecond(point.getKey()), point.getValue());
}
dataCollection.addSeries(series);
}
JFreeChart chart = ChartFactory.createTimeSeriesChart(title, xLabel, yLabel, dataCollection, true, false, false);
formatColorTheme(chart, theme);
XYPlot plot = chart.getXYPlot();
Color[] colors = generateJetSpectrum(specifications.size());
for (int i = 0; i < specifications.size(); i++) {
plot.getRenderer().setSeriesPaint(i, colors[i]);
}
DateAxis axis = (DateAxis) plot.getDomainAxis();
axis.setDateFormatOverride(new SimpleDateFormat("dd/MM hh:mm:ss"));
return chart;
}
public static JFreeChart createXYChart(String title, XYDataset data, String xLabel, String yLabel, int pointRadius, int lineThickness, ColorTheme theme) {
JFreeChart chart = ChartFactory.createXYLineChart(title, xLabel, yLabel, data, PlotOrientation.VERTICAL, true, false, false);
formatColorTheme(chart, theme);
XYPlot plot = (XYPlot) chart.getPlot();
Shape icon = new Ellipse2D.Double(-pointRadius, -pointRadius, pointRadius * 2, pointRadius * 2);
XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
plot.setRenderer(renderer);
Color[] colors = generateJetSpectrum(data.getSeriesCount());
for (int i = 0; i < data.getSeriesCount(); i++) {
plot.getRenderer().setSeriesStroke(i, new BasicStroke(lineThickness));
plot.getRenderer().setSeriesShape(i, icon);
((XYLineAndShapeRenderer) plot.getRenderer()).setSeriesShapesVisible(i, true);
((XYLineAndShapeRenderer) plot.getRenderer()).setSeriesShapesFilled(i, true);
plot.getRenderer().setSeriesPaint(i, colors[i]);
}
LegendTitle legend = chart.getLegend();
Font legendFont = legend.getItemFont();
float legendFontSize = legendFont.getSize();
Font newLegendFont = legendFont.deriveFont(legendFontSize * 0.6f);
legend.setItemFont(newLegendFont);
ValueAxis domainAxis = ((XYPlot) chart.getPlot()).getDomainAxis();
Font domainAxisLabelFont = domainAxis.getLabelFont();
float domainAxisLabelFontSize = domainAxisLabelFont.getSize();
domainAxis.setLabelFont(domainAxisLabelFont.deriveFont(domainAxisLabelFontSize * 0.6f));
Font domainAxisTickLabelFont = domainAxis.getTickLabelFont();
float domainAxisTickLabelFontSize = domainAxisTickLabelFont.getSize();
domainAxis.setTickLabelFont(domainAxisTickLabelFont.deriveFont(domainAxisTickLabelFontSize * 0.6f));
ValueAxis rangeAxis = ((XYPlot) chart.getPlot()).getRangeAxis();
Font rangeAxisLabelFont = rangeAxis.getLabelFont();
float rangeAxisLabelFontSize = rangeAxisLabelFont.getSize();
rangeAxis.setLabelFont(rangeAxisLabelFont.deriveFont(rangeAxisLabelFontSize * 0.6f));
Font rangeAxisTickLabelFont = rangeAxis.getTickLabelFont();
float rangeAxisTickLabelFontSize = rangeAxisTickLabelFont.getSize();
rangeAxis.setTickLabelFont(rangeAxisTickLabelFont.deriveFont(rangeAxisTickLabelFontSize * 0.6f));
return chart;
}
public static JFreeChart createStackedBarChart(String title, List<List<Double>> bars, List<String> zoneLabels, String xLabel, String yLabel, ColorTheme theme) {
DefaultCategoryDataset ds = new DefaultCategoryDataset();
Integer barId = 0;
int maxStack = 0;
for (List<Double> bar : bars) {
int i = 0;
maxStack = Math.max(maxStack, bar.size());
for (double value : bar) {
ds.addValue(value, zoneLabels.get(i), barId);
i++;
}
barId++;
}
JFreeChart chart = ChartFactory.createStackedBarChart(title, xLabel, yLabel, ds, PlotOrientation.VERTICAL, true, false, false);
formatColorTheme(chart, theme);
formatBars(chart);
CategoryPlot plot = (CategoryPlot) chart.getPlot();
plot.getDomainAxis().setCategoryMargin(0);
plot.getDomainAxis().setLowerMargin(0);
plot.getDomainAxis().setUpperMargin(0);
Color[] colors = generateJetSpectrum(maxStack);
for (int i = 0; i < maxStack; i++) {
plot.getRenderer().setSeriesPaint(i, colors[i]);
plot.getRenderer().setSeriesOutlinePaint(i, Color.white);
}
return chart;
}
public static JFreeChart createStackedAreaChart(String title, XYSeriesCollection areaData, XYSeriesCollection lineData, String xLabel, String yLabel, ColorTheme theme) {
final ValueAxis xAxis = new NumberAxis(xLabel);
final ValueAxis yAxis = new NumberAxis(yLabel);
XYPlot mainPlot = new XYPlot();
mainPlot.setDomainAxis(xAxis);
mainPlot.setRangeAxis(yAxis);
mainPlot.setForegroundAlpha(0.9f);
//[ stacked area
DefaultTableXYDataset areaDs = new DefaultTableXYDataset();
for (int i = 0; i < areaData.getSeriesCount(); i++) {
areaDs.addSeries(areaData.getSeries(i));
}
XYItemRenderer stackedRenderer = new StackedXYAreaRenderer2();
mainPlot.setDataset(areaDs);
mainPlot.setRenderer(stackedRenderer);
Color[] colors = generateJetSpectrum(areaData.getSeriesCount());
for (int i = 0; i < areaData.getSeriesCount(); i++) {
stackedRenderer.setSeriesPaint(i, colors[i]);
}
//]
//[ lines
if (lineData != null) {
XYItemRenderer lineRenderer = new StandardXYItemRenderer();
DefaultTableXYDataset lineDs = new DefaultTableXYDataset();
for (int i = 0; i < lineData.getSeriesCount(); i++) {
lineDs.addSeries(lineData.getSeries(i));
}
mainPlot.setDataset(1, lineDs);
mainPlot.setRenderer(1, lineRenderer);
colors = new Color[]{Color.black, Color.red, Color.darkGray};
for (int i = 0; i < lineData.getSeriesCount(); i++) {
lineRenderer.setSeriesPaint(i, colors[i % colors.length]);
lineRenderer.setSeriesStroke(i, new BasicStroke(2f));
}
}
//]
mainPlot.setDatasetRenderingOrder(DatasetRenderingOrder.FORWARD);
JFreeChart chart = new JFreeChart(title, JFreeChart.DEFAULT_TITLE_FONT, mainPlot, true);
formatColorTheme(chart, theme);
return chart;
}
public static Pair<String, XYSeriesCollection> adjustTime(XYSeriesCollection chartsCollection, Collection<IntervalMarker> markers) {
int maxTime = 0;
for (int i = 0; i < chartsCollection.getSeriesCount(); i++) {
XYSeries series = chartsCollection.getSeries(i);
for (int j = 0; j < series.getItemCount(); j++) {
int x = series.getX(j).intValue();
if (x > maxTime) {
maxTime = x;
}
}
}
String type = "ms";
int div = 1;
if (maxTime > 10 * 60 * 1000) {
div = 60 * 1000;
type = "min";
}
if (maxTime > 30 * 1000) {
div = 1000;
type = "sec";
}
XYSeriesCollection result = new XYSeriesCollection();
for (int i = 0; i < chartsCollection.getSeriesCount(); i++) {
XYSeries old = chartsCollection.getSeries(i);
XYSeries series = new XYSeries(old.getKey(), old.getAutoSort(), old.getAllowDuplicateXValues());
for (int j = 0; j < old.getItemCount(); j++) {
Number x = old.getX(j).doubleValue() / div;
Number y = old.getY(j);
series.add(x, y);
}
result.addSeries(series);
}
if (markers != null) {
for (IntervalMarker marker : markers) {
marker.setStartValue(marker.getStartValue() / div);
marker.setEndValue(marker.getEndValue() / div);
}
}
return Pair.of(type, result);
}
private static void formatColorTheme(JFreeChart chart, ColorTheme theme) {
Plot rawPlot = chart.getPlot();
Paint background = Color.BLACK;
Paint foregroung = Color.WHITE;
if (theme == ColorTheme.LIGHT) {
background = Color.WHITE;
foregroung = Color.BLACK;
}
chart.setBackgroundPaint(background);
chart.getLegend().setBackgroundPaint(background);
chart.getLegend().setItemPaint(foregroung);
if (rawPlot instanceof XYPlot) {
XYPlot plot = (XYPlot) rawPlot;
plot.getDomainAxis().setLabelPaint(foregroung);
plot.getRangeAxis().setLabelPaint(foregroung);
plot.getDomainAxis().setTickLabelPaint(foregroung);
plot.getDomainAxis().setTickMarkPaint(foregroung);
plot.getRangeAxis().setTickLabelPaint(foregroung);
plot.getRangeAxis().setTickMarkPaint(foregroung);
plot.setBackgroundPaint(background);
plot.setDomainGridlinePaint(foregroung);
plot.setRangeGridlinePaint(foregroung);
}
if (rawPlot instanceof CategoryPlot) {
CategoryPlot plot = (CategoryPlot) rawPlot;
plot.getDomainAxis().setLabelPaint(foregroung);
plot.getRangeAxis().setLabelPaint(foregroung);
plot.getDomainAxis().setTickLabelPaint(foregroung);
plot.getDomainAxis().setTickMarkPaint(foregroung);
plot.getRangeAxis().setTickLabelPaint(foregroung);
plot.getRangeAxis().setTickMarkPaint(foregroung);
plot.setBackgroundPaint(background);
plot.setDomainGridlinePaint(foregroung);
plot.setRangeGridlinePaint(foregroung);
}
}
private static void formatBars(JFreeChart chart) {
if (chart != null) {
Plot p = chart.getPlot();
if (p instanceof CategoryPlot) {
CategoryPlot cp = (CategoryPlot) p;
CategoryItemRenderer cir = cp.getRenderer();
if (cir instanceof BarRenderer) {
BarRenderer br = (BarRenderer) cir;
br.setShadowVisible(false);
br.setBarPainter(new StandardBarPainter());
br.setDrawBarOutline(true);
}
}
}
}
private static Color[] generateJetSpectrum(int n) {
Color[] colors = new Color[n];
for (int i = 0; i < n; i++) {
colors[i] = Color.getHSBColor(-(0.75f * i / n + 0.3f), 0.85f, 1.0f);
}
return colors;
}
public static class TimeSeriesChartSpecification {
private Map<Date, Double> data;
private String label;
public Map<Date, Double> getData() {
return data;
}
public void setData(Map<Date, Double> data) {
this.data = data;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
}
}