/* Copyright 2008-2010 Gephi Authors : Eduardo Ramos <eduramiba@gmail.com> Website : http://www.gephi.org This file is part of Gephi. Gephi is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gephi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Gephi. If not, see <http://www.gnu.org/licenses/>. */ package org.gephi.ui.utils; import java.awt.Color; import java.awt.Dimension; import java.io.File; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import org.gephi.utils.HTMLEscape; import org.gephi.utils.TempDirUtils; import org.gephi.utils.TempDirUtils.TempDir; 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.NumberAxis; import org.jfree.chart.labels.BoxAndWhiskerToolTipGenerator; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.category.BoxAndWhiskerRenderer; import org.jfree.chart.renderer.xy.StandardXYItemRenderer; import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; import org.jfree.data.statistics.DefaultBoxAndWhiskerCategoryDataset; import org.jfree.data.statistics.HistogramDataset; import org.jfree.data.statistics.HistogramType; import org.jfree.data.xy.XYDataset; import org.jfree.data.xy.XYSeries; import org.jfree.data.xy.XYSeriesCollection; import org.openide.util.Exceptions; import org.openide.util.NbBundle; /** * Utils class to build and change charts. * Scatter plots implemented to be able to draw or not lines and linear regression. * @author Eduardo Ramos <eduramiba@gmail.com> */ public class ChartsUtils { /** * Prepares a HTML report for the given statistics data and charts. * For preparing the statistics data see the method <code>getAllStatistics</code> of the <code>StatisticsUtils</code> class. * @param dataName Name of the data * @param statistics Statistics obtained from the method <code>getAllStatistics</code> of the <code>StatisticsUtils</code> class * @param boxPlot Box-plot jfreechart or null * @param scatterPlot Scatter-plot jfreechart or null * @param histogram Histogram-plot jfreechart or null * @param boxPlotDimension Dimension for the box-plot or null to use a default dimension * @param scatterPlotDimension Dimension for the scatter plot or null to use a default dimension * @param histogramDimension Dimension for the histogram or null to use a default dimension * @return */ public static String getStatisticsReportHTML(final String dataName, final BigDecimal[] statistics, final JFreeChart boxPlot, final JFreeChart scatterPlot, final JFreeChart histogram, final Dimension boxPlotDimension, final Dimension scatterPlotDimension, final Dimension histogramDimension) { final StringBuilder sb = new StringBuilder(); sb.append("<html>"); sb.append(NbBundle.getMessage(ChartsUtils.class, "ChartsUtils.report.header", HTMLEscape.stringToHTMLString(dataName))); sb.append("<hr>"); if (statistics != null) {//There are numbers and statistics can be shown: sb.append("<ul>"); writeStatistic(sb, "ChartsUtils.report.average", statistics[0]); writeStatistic(sb, "ChartsUtils.report.Q1", statistics[1]); writeStatistic(sb, "ChartsUtils.report.median", statistics[2]); writeStatistic(sb, "ChartsUtils.report.Q3", statistics[3]); writeStatistic(sb, "ChartsUtils.report.IQR", statistics[4]); writeStatistic(sb, "ChartsUtils.report.sum", statistics[5]); writeStatistic(sb, "ChartsUtils.report.min", statistics[6]); writeStatistic(sb, "ChartsUtils.report.max", statistics[7]); sb.append("</ul>"); try { if (boxPlot != null) { sb.append("<hr>"); writeBoxPlot(sb, boxPlot, boxPlotDimension); } if (scatterPlot != null) { sb.append("<hr>"); writeScatterPlot(sb, scatterPlot, scatterPlotDimension); } if (histogram != null) { sb.append("<hr>"); writeHistogram(sb, histogram, histogramDimension); } } catch (IOException ex) { Exceptions.printStackTrace(ex); } } else { sb.append(getMessage("ChartsUtils.report.empty")); } sb.append("</html>"); return sb.toString(); } /** * Build a new box-plot from an array of numbers using a default title and yLabel. * String dataName will be used for xLabel. * @param numbers Numbers for building box-plot * @param dataName Name of the numbers data * @return Prepared box-plot */ public static JFreeChart buildBoxPlot(final Number[] numbers, final String dataName) { if (numbers == null || numbers.length == 0) { return null; } DefaultBoxAndWhiskerCategoryDataset dataset = new DefaultBoxAndWhiskerCategoryDataset(); final ArrayList<Number> list = new ArrayList<Number>(); list.addAll(Arrays.asList(numbers)); final String valuesString = getMessage("ChartsUtils.report.box-plot.values"); dataset.add(list, valuesString, ""); final BoxAndWhiskerRenderer renderer = new BoxAndWhiskerRenderer(); renderer.setMeanVisible(false); renderer.setFillBox(false); renderer.setMaximumBarWidth(0.5); final CategoryAxis xAxis = new CategoryAxis(dataName); final NumberAxis yAxis = new NumberAxis(getMessage("ChartsUtils.report.box-plot.values-range")); yAxis.setAutoRangeIncludesZero(false); renderer.setBaseToolTipGenerator(new BoxAndWhiskerToolTipGenerator()); final CategoryPlot plot = new CategoryPlot(dataset, xAxis, yAxis, renderer); plot.setRenderer(renderer); JFreeChart boxPlot = new JFreeChart(getMessage("ChartsUtils.report.box-plot.title"), plot); return boxPlot; } /** * Build new scatter plot from numbers array using a default title and xLabel. * String dataName will be used for yLabel. * Appearance can be changed later with the other methods of ChartsUtils. * @param numbers Numbers for the scatter plot * @param dataName Name of the numbers data * @param useLines Indicates if lines have to be drawn instead of shapes * @param useLinearRegression Indicates if the scatter plot has to have linear regreesion line drawn * @return Scatter plot for the data and appearance options */ public static JFreeChart buildScatterPlot(final Number[] numbers, final String dataName, final boolean useLines, final boolean useLinearRegression) { if (numbers == null || numbers.length == 0) { return null; } XYSeriesCollection dataset = new XYSeriesCollection(); XYSeries series = new XYSeries(dataName); for (int i = 0; i < numbers.length; i++) { series.add(i, numbers[i]); } dataset.addSeries(series); JFreeChart scatterPlot = buildScatterPlot(dataset, getMessage("ChartsUtils.report.scatter-plot.title"), getMessage("ChartsUtils.report.scatter-plot.xLabel"), dataName, useLines, useLinearRegression); return scatterPlot; } /** * Build new Scatter plot. Appearance can be changed later with the other methods of ChartsUtils. * @param data Data for the plot * @param title Title for the chart * @param xLabel Text for x label * @param yLabel Text for y label * @param useLines Indicates if lines have to be drawn instead of shapes * @param useLinearRegression Indicates if the scatter plot has to have linear regreesion line drawn * @return Scatter plot for the data and appearance options */ public static JFreeChart buildScatterPlot(final XYSeriesCollection data, final String title, final String xLabel, final String yLabel, final boolean useLines, final boolean useLinearRegression) { JFreeChart scatterPlot = ChartFactory.createXYLineChart( title, xLabel, yLabel, data, PlotOrientation.VERTICAL, true, true, false); XYPlot plot = (XYPlot) scatterPlot.getPlot(); plot.setBackgroundPaint(java.awt.Color.WHITE); plot.setDomainGridlinePaint(java.awt.Color.GRAY); plot.setRangeGridlinePaint(java.awt.Color.GRAY); setScatterPlotLinesEnabled(scatterPlot, useLines); setScatterPlotLinearRegressionEnabled(scatterPlot, useLinearRegression); return scatterPlot; } /** * Build new histogram from the given numbers array using a default title and xLabel. * String dataName will be used for yLabel. * @param numbers Numbers for the histogram * @param dataName Name of the numbers data * @param divisions Divisions for the histogram * @return Prepared histogram */ public static JFreeChart buildHistogram(final Number[] numbers, final String dataName, final int divisions) { if (numbers == null || numbers.length == 0) { return null; } HistogramDataset dataset = new HistogramDataset(); dataset.setType(HistogramType.FREQUENCY); double[] doubleNumbers = new double[numbers.length]; for (int i = 0; i < doubleNumbers.length; i++) { doubleNumbers[i] = numbers[i].doubleValue(); } dataset.addSeries(dataName, doubleNumbers, divisions > 0 ? divisions : 10);//Use 10 divisions if divisions number is invalid. JFreeChart histogram = ChartFactory.createHistogram( getMessage("ChartsUtils.report.histogram.title"), dataName, getMessage("ChartsUtils.report.histogram.yLabel"), dataset, PlotOrientation.VERTICAL, true, true, false); return histogram; } /** * Modify a scatter plot to show lines instead or shapes or not. * @param scatterPlot Scatter plot to modify * @param enabled Indicates if lines have to be shown */ public static void setScatterPlotLinesEnabled(final JFreeChart scatterPlot, final boolean enabled) { XYPlot plot = (XYPlot) scatterPlot.getPlot(); XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer(); if (enabled) { renderer.setSeriesLinesVisible(0, true); renderer.setSeriesShapesVisible(0, false); } else { renderer.setSeriesLinesVisible(0, false); renderer.setSeriesShapesVisible(0, true); renderer.setSeriesShape(0, new java.awt.geom.Ellipse2D.Double(0, 0, 1, 1)); } renderer.setSeriesPaint(0, Color.RED); plot.setRenderer(0, renderer); } /** * Modify a scatter plot to show linear regression or not. * @param scatterPlot Scatter plot to modify * @param enabled Indicates if linear regression has to be shown */ public static void setScatterPlotLinearRegressionEnabled(final JFreeChart scatterPlot, final boolean enabled) { XYPlot plot = (XYPlot) scatterPlot.getPlot(); if (enabled) { StandardXYItemRenderer regressionRenderer = new StandardXYItemRenderer(); regressionRenderer.setBaseSeriesVisibleInLegend(false); plot.setDataset(1, regress((XYSeriesCollection) plot.getDataset(0))); plot.setRenderer(1, regressionRenderer); } else { plot.setDataset(1, null);//Remove linear regression } } /********Private methods*********/ /** * Calculates linear regression points from a XYSeriesCollection data set * Code obtained from http://pwnt.be/2009/08/17/simple-linear-regression-with-jfreechart */ private static XYDataset regress(XYSeriesCollection data) { // Determine bounds double xMin = Double.MAX_VALUE, xMax = 0; for (int i = 0; i < data.getSeriesCount(); i++) { XYSeries ser = data.getSeries(i); for (int j = 0; j < ser.getItemCount(); j++) { double x = ser.getX(j).doubleValue(); if (x < xMin) { xMin = x; } if (x > xMax) { xMax = x; } } } // Create 2-point series for each of the original series XYSeriesCollection coll = new XYSeriesCollection(); for (int i = 0; i < data.getSeriesCount(); i++) { XYSeries ser = data.getSeries(i); int n = ser.getItemCount(); double sx = 0, sy = 0, sxx = 0, sxy = 0, syy = 0; for (int j = 0; j < n; j++) { double x = ser.getX(j).doubleValue(); double y = ser.getY(j).doubleValue(); sx += x; sy += y; sxx += x * x; sxy += x * y; syy += y * y; } double b = (n * sxy - sx * sy) / (n * sxx - sx * sx); double a = sy / n - b * sx / n; XYSeries regr = new XYSeries(ser.getKey()); regr.add(xMin, a + b * xMin); regr.add(xMax, a + b * xMax); coll.addSeries(regr); } return coll; } private static void writeStatistic(StringBuilder sb, String resName, BigDecimal number) { sb.append("<li>"); sb.append(getMessage(resName)); sb.append(": "); sb.append(number); sb.append("</li>"); } private static void writeBoxPlot(final StringBuilder sb, JFreeChart boxPlot, Dimension dimension) throws IOException { if (dimension == null) { dimension = new Dimension(300, 500); } writeChart(sb, boxPlot, dimension, "box-plot.png"); } private static void writeScatterPlot(final StringBuilder sb, JFreeChart scatterPlot, Dimension dimension) throws IOException { if (dimension == null) { dimension = new Dimension(600, 400); } writeChart(sb, scatterPlot, dimension, "scatter-plot.png"); } private static void writeHistogram(final StringBuilder sb, final JFreeChart histogram, Dimension dimension) throws IOException { if (dimension == null) { dimension = new Dimension(600, 400); } writeChart(sb, histogram, dimension, "histogram.png"); } private static void writeChart(final StringBuilder sb, final JFreeChart chart, final Dimension dimension, final String fileName) throws IOException { TempDir tempDir = TempDirUtils.createTempDir(); String imageFile = ""; File file = tempDir.createFile(fileName); imageFile = "<center><img src=\"file:" + file.getAbsolutePath() + "\"</img></center>"; ChartUtilities.saveChartAsPNG(file, chart, dimension.width, dimension.height); sb.append(imageFile); } private static String getMessage(String resName) { return NbBundle.getMessage(ChartsUtils.class, resName); } }