001    package org.expasy.jpl.commons.collection.render;
002    
003    
004    import java.awt.Color;
005    import java.awt.Dimension;
006    import java.awt.Paint;
007    import java.awt.image.BufferedImage;
008    import java.io.File;
009    import java.io.IOException;
010    import java.util.Arrays;
011    import org.apache.commons.math.stat.StatUtils;
012    import org.apache.commons.math.util.MathUtils;
013    import org.expasy.jpl.commons.base.render.ChartRenderer;
014    import org.expasy.jpl.commons.base.render.ImageFormat;
015    import org.expasy.jpl.commons.collection.Interval;
016    import org.expasy.jpl.commons.collection.stat.HistogramDataSet;
017    import org.expasy.jpl.commons.collection.stat.HistogramDataSetExporter;
018    import org.expasy.jpl.commons.collection.stat.StatisticalCategory;
019    import org.jfree.chart.ChartRenderingInfo;
020    import org.jfree.chart.ChartUtilities;
021    import org.jfree.chart.JFreeChart;
022    import org.jfree.chart.axis.AxisLocation;
023    import org.jfree.chart.axis.NumberAxis;
024    import org.jfree.chart.plot.DatasetRenderingOrder;
025    import org.jfree.chart.plot.XYPlot;
026    import org.jfree.chart.renderer.xy.StandardXYBarPainter;
027    import org.jfree.chart.renderer.xy.XYBarRenderer;
028    import org.jfree.data.statistics.SimpleHistogramBin;
029    import org.jfree.data.statistics.SimpleHistogramDataset;
030    import org.jfree.data.xy.IntervalXYDataset;
031    import org.jfree.ui.RectangleInsets;
032    
033    
034    /**
035     * This object renders {@code HistogramDataSet} objects.
036     * 
037     * @author nikitin
038     * 
039     * @version 1.0
040     * 
041     */
042    public final class HistogramDataSetRenderer implements ChartRenderer {
043            
044            private static final XYBarRenderer DEFAULT_RENDERER;
045            private static final HistogramDataSetExporter HISTO_EXPORTER =
046                HistogramDataSetExporter.newInstance();
047            
048            /** dimension of rendered image */
049            private Dimension dimension;
050            
051            /** image format */
052            private ImageFormat format;
053            
054            private XYPlot plot;
055            private IntervalXYDataset dataset;
056            
057            /** render the graph */
058            private XYBarRenderer renderer;
059            
060            /** rendering info for interactive graphs **/
061            private ChartRenderingInfo renderingInfo;
062            
063            /**
064             * if true histogram centers of all bins will be added for rendering else
065             * all values will be rendered (slow if lots of data)
066             */
067            private boolean isBinData;
068            
069            static {
070                    XYBarRenderer.setDefaultShadowsVisible(false);
071                    XYBarRenderer.setDefaultBarPainter(new StandardXYBarPainter());
072                    
073                    DEFAULT_RENDERER = new XYBarRenderer();
074                    DEFAULT_RENDERER.setMargin(0.0);
075            }
076            
077            private HistogramDataSetRenderer(ImageFormat format, Dimension dim) {
078                    this.format = format;
079                    this.dimension = dim;
080                    init();
081            }
082            
083            public static HistogramDataSetRenderer newInstance() {
084                    return new HistogramDataSetRenderer(ImageFormat.PNG, new Dimension(600,
085                        400));
086            }
087            
088            /**
089             * The values of all bins will be added for rendering (quick rendering).
090             */
091            public void enableBinRenderMode() {
092                    isBinData = true;
093            }
094            
095            /**
096             * All the values will be rendered (slow down rendering if lots of data).
097             */
098            public void disableBinRenderMode() {
099                    isBinData = false;
100            }
101            
102            /**
103             * Set plot background color.
104             * 
105             * @param color the background color.
106             */
107            public void setBackgroundColor(Paint color) {
108                    plot.setBackgroundPaint(color);
109            }
110            
111            /**
112             * Set image dimension.
113             * 
114             * @param dim the image dimension.
115             */
116            public void setDimension(Dimension dim) {
117                    this.dimension = dim;
118            }
119            
120            /**
121             * Set image format.
122             * 
123             * @param format the image format (png or jpeg).
124             */
125            public void setImageFormat(ImageFormat format) {
126                    this.format = format;
127            }
128            
129            /**
130             * Get image format.
131             * 
132             * @return format the image format (png or jpeg).
133             */
134            public ImageFormat getImageFormat() {
135                    return format;
136            }
137            
138            private void init() {
139                    plot = new XYPlot();
140                    plot.setDomainAxis(new NumberAxis("x"));
141                    plot.setRangeAxis(new NumberAxis("frequency"));
142                    plot.setRangeAxisLocation(AxisLocation.BOTTOM_OR_LEFT);
143                    plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0));
144                    
145                    plot.setDatasetRenderingOrder(DatasetRenderingOrder.REVERSE);
146                    renderer = DEFAULT_RENDERER;
147                    plot.setRenderer(renderer);
148                    
149                    renderingInfo = new ChartRenderingInfo();
150            }
151            
152            /**
153             * Set the legend for the var axis.
154             * 
155             * @param legend the x-axis legend.
156             */
157            public void setXAxisLegend(String legend) {
158                    plot.setDomainAxis(new NumberAxis(legend));
159            }
160            
161            /**
162             * Set the legend for the frequency axis.
163             * 
164             * @param legend the y-axis legend.
165             */
166            public void setYAxisLegend(String legend) {
167                    plot.setRangeAxis(new NumberAxis(legend));
168            }
169            
170            /**
171             * Empty data set.
172             */
173            public void clearDataSet() {
174                    dataset = null;
175            }
176            
177            /**
178             * Add the statistical data set (default colors: 1st serie (blue), 2nd serie
179             * (red)).
180             * 
181             * @param statSerie the statistical serie to render.
182             * @param title the title associated to the data set.
183             */
184            public void addDataSet(HistogramDataSet histo, String title) {
185                    addDataSet(histo, title, null);
186            }
187            
188            /**
189             * Add the statistical data set.
190             * 
191             * @param statSerie the statistical serie to render.
192             * @param title the title associated to the data set.
193             * @param color the color of the serie.
194             */
195            public void addDataSet(HistogramDataSet histo, String title, Color color) {
196                    if (!isBinData && histo.getValues() != null
197                        && histo.getValues().length > 10000) {
198                            System.err
199                                .println("Warning: "
200                                    + histo.getValues().length
201                                    + " data to load for rendering !\n"
202                                    + "Please switch to the bin render mode (see enableBinRenderMode() method).");
203                    }
204                    HISTO_EXPORTER.exportBinStatus(isBinData);
205                    dataset =
206                        HistogramDataSetRenderer.toXYDataset(HISTO_EXPORTER, title, histo);
207            }
208            
209            /**
210             * @return a new chart that wrap data set.
211             */
212            private JFreeChart flushIntoChart() {
213                    plot.setDataset(dataset);
214                    
215                    return new JFreeChart(plot);
216            }
217            
218            /**
219             * Create an image corresponding to the given MS spectrum.
220             * 
221             * @param pl the peak list to render.
222             * @param title the image title.
223             * @return a new buffered image.
224             */
225            public BufferedImage render(HistogramDataSet histo, String title) {
226                    addDataSet(histo, title);
227                    
228                    JFreeChart chart = flushIntoChart();
229                    
230                    return chart.createBufferedImage((int) dimension.getWidth(),
231                        (int) dimension.getHeight(), renderingInfo);
232            }
233            
234            /**
235             * Retrieve rendering info (for interactive graphs).
236             * 
237             * @return the rendering information associated to the chart.
238             */
239            public ChartRenderingInfo getRenderingInfo() {
240                    return renderingInfo;
241            }
242            
243            /**
244             * Export as image file.
245             * 
246             * @param statSerie the statistical serie to render and export.
247             * @param dir the directory to put new file in.
248             * @param title the file name.
249             */
250            public void exportChart(HistogramDataSet histo, String title,
251                String filename) {
252                    addDataSet(histo, title);
253                    exportChart(filename);
254            }
255            
256            /**
257             * Create an image (prerequisite: data sets have already been entered via
258             * {@code addDataSet} method).
259             * 
260             * @return a new buffered image.
261             */
262            public BufferedImage render() {
263                    JFreeChart chart = flushIntoChart();
264                    return chart.createBufferedImage((int) dimension.getWidth(),
265                        (int) dimension.getHeight(), renderingInfo);
266            }
267            
268            /**
269             * Export a chart (prerequisite: data sets have already been entered via
270             * {@code addDataSet} method).
271             * 
272             * @param filename the image file name without extension (defined in {@code
273             *        ImageFormat}).
274             */
275            public void exportChart(String filename) {
276                    JFreeChart chart = flushIntoChart();
277                    
278                    try {
279                            switch (format) {
280                            case JPG:
281                                    ChartUtilities.saveChartAsJPEG(new File(filename + ".jpg"),
282                                        chart, (int) dimension.getWidth(), (int) dimension
283                                            .getHeight());
284                                    break;
285                            case PNG:
286                                    ChartUtilities.saveChartAsPNG(new File(filename + ".png"),
287                                        chart, (int) dimension.getWidth(), (int) dimension
288                                            .getHeight());
289                            }
290                    } catch (IOException e) {
291                            System.err.println("Problem occurred creating chart.");
292                    }
293            }
294            
295            public void setChartColor(int chartIndex, Paint color) {
296                    renderer.setSeriesPaint(chartIndex, color);
297            }
298            
299            public Paint getChartColor(int chartIndex) {
300                    return renderer.getSeriesPaint(chartIndex);
301            }
302            
303            /**
304             * Convert the histogram for jfreechart display (add values and weights).
305             * 
306             * @param title the title of the histogram.
307             * @param isLoadBinData true if load bin centers instead of data.
308             * 
309             * @return an instance of {@code IntervalXYDataset}.
310             */
311            public static IntervalXYDataset toXYDataset(
312                HistogramDataSetExporter exporter, String title, HistogramDataSet histo) {
313                    
314                    StringBuilder note = new StringBuilder();
315                    
316                    double[] vs = histo.getValues();
317                    double[] ws = histo.getWeights();
318                    double[] bs = histo.getBins();
319                    
320                    // if no data, render bins
321                    if (vs == null || vs.length == 0 || ws == null || ws.length == 0) {
322                            exporter.setBinExport(true);
323                    }
324                    
325                    if (!exporter.isBinExport()) {
326                            note.append(vs.length + " data, ");
327                    } else {
328                            note.append(bs.length + " bins, bin width=" + histo.getBinWidth()
329                                + ", ");
330                    }
331                    
332                    if (histo.isDataNormalized()) {
333                            if (histo.isNormalizedSum()) {
334                                    note.append("norm sum=" + histo.getNormalizedValue() + ", ");
335                            } else {
336                                    note.append("max value (+norm)=" + histo.getNormalizedValue()
337                                        + ", ");
338                            }
339                    } else {
340                            double mean = 0;
341                            if (exporter.isBinExport()) {
342                                    mean = StatUtils.mean(bs);
343                                    
344                                    if (mean < 1 || mean > 1000) {
345                                            bs =
346                                                MathUtils
347                                                    .normalizeArray(bs, histo.getNormalizedValue());
348                                            note
349                                                .append("norm sum=" + histo.getNormalizedValue() + ", ");
350                                    }
351                            } else {
352                                    mean = StatUtils.mean(ws);
353                                    
354                                    if (mean < 1 || mean > 1000) {
355                                            ws =
356                                                MathUtils
357                                                    .normalizeArray(ws, histo.getNormalizedValue());
358                                            note
359                                                .append("norm sum=" + histo.getNormalizedValue() + ", ");
360                                    }
361                            }
362                    }
363                    
364                    if (note.length() > 0) {
365                            note.delete(note.length() - 2, note.length());
366                            title += " [" + note + "]";
367                    }
368                    
369                    SimpleHistogramDataset dataset = new SimpleHistogramDataset(title);
370                    
371                    for (int i = 0; i < bs.length; i++) {
372                            Interval interval = histo.getIntervalBinAt(i);
373                            
374                            SimpleHistogramBin bin =
375                                new SimpleHistogramBin(interval.getLowerBound(), interval
376                                    .getUpperBound(), interval.isLowerBoundIncluded(), interval
377                                    .isUpperBoundIncluded());
378                            
379                            dataset.addBin(bin);
380                            
381                            // new bin data
382                            if (exporter.isBinExport()) {
383                                    StatisticalCategory catego = histo.toCategory(i);
384                                    
385                                    int freq = (int) Math.round(bs[i]);
386                                    double binVal = 0;
387                                    
388                                    if (exporter.isLoadCenterBin()) {
389                                            binVal = catego.getCenter();
390                                    } else {
391                                            binVal = catego.getInterval().getLowerBound();
392                                    }
393                                    
394                                    if (freq == 1) {
395                                            dataset.addObservation(binVal);
396                                    } else {
397                                            double[] obs = new double[freq];
398                                            Arrays.fill(obs, binVal);
399                                            
400                                            dataset.addObservations(obs);
401                                    }
402                            }
403                    }
404                    
405                    // add data into bins
406                    if (!exporter.isBinExport()) {
407                            if (ws.length == 0) {
408                                    dataset.addObservations(vs);
409                            } else {
410                                    for (int i = 0; i < vs.length; i++) {
411                                            if (histo.getIntervalOfDefinition().contains(vs[i])) {
412                                                    
413                                                    if (ws[i] > 0) {
414                                                            double[] obs = new double[(int) Math.round(ws[i])];
415                                                            Arrays.fill(obs, vs[i]);
416                                                            
417                                                            dataset.addObservations(obs);
418                                                    }
419                                            }
420                                    }
421                            }
422                    }
423                    return dataset;
424            }
425            
426            @Override
427            public void setXAxisDefaultInterval() {
428            // TODO Auto-generated method stub
429            
430            }
431            
432            @Override
433            public void setXAxisInterval(double from, double to) {
434            // TODO Auto-generated method stub
435            
436            }
437    }