/* Copyright 2008-2010 Gephi Authors : Eduardo Ramos <eduramiba@gmail.com> Website : http://www.gephi.org This file is part of Gephi. DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. Copyright 2011 Gephi Consortium. All rights reserved. The contents of this file are subject to the terms of either the GNU General Public License Version 3 only ("GPL") or the Common Development and Distribution License("CDDL") (collectively, the "License"). You may not use this file except in compliance with the License. You can obtain a copy of the License at http://gephi.org/about/legal/license-notice/ or /cddl-1.0.txt and /gpl-3.0.txt. See the License for the specific language governing permissions and limitations under the License. When distributing the software, include this License Header Notice in each file and include the License files at /cddl-1.0.txt and /gpl-3.0.txt. If applicable, add the following below the License Header, with the fields enclosed by brackets [] replaced by your own identifying information: "Portions Copyrighted [year] [name of copyright owner]" If you wish your version of this file to be governed by only the CDDL or only the GPL Version 3, indicate your decision by adding "[Contributor] elects to include this software in this distribution under the [CDDL or GPL Version 3] license." If you do not indicate a single choice of license, a recipient has the option to distribute your version of this file under either the CDDL, the GPL Version 3 or to extend the choice of license to its licensees as provided above. However, if you add GPL Version 3 code and therefore, elected the GPL Version 3 license, then the option applies only if the new code is made subject to such option by the copyright holder. Contributor(s): Portions Copyrighted 2011 Gephi Consortium. */ 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); } }