Java tutorial
/************************************************************************************** Copyright 2015 Applied Research Associates, Inc. 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 mil.tatrc.physiology.utilities.csv.plots; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.io.File; import java.io.IOException; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartUtilities; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.LogarithmicAxis; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.xy.StandardXYItemRenderer; import org.jfree.chart.renderer.xy.XYItemRenderer; import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; import org.jfree.data.xy.XYSeries; import org.jfree.data.xy.XYSeriesCollection; import mil.tatrc.physiology.datamodel.substance.SESubstanceManager; import mil.tatrc.physiology.utilities.DoubleUtils; import mil.tatrc.physiology.utilities.FileUtils; import mil.tatrc.physiology.utilities.Log; import mil.tatrc.physiology.utilities.LogListener; import mil.tatrc.physiology.utilities.csv.CSVContents; import mil.tatrc.physiology.utilities.csv.ConvexHullMaker; import mil.tatrc.physiology.utilities.csv.plots.PlotDriver.PlotJob; public class MultiPlotter implements Plotter { protected Map<String, List<Double>> data = new HashMap<String, List<Double>>(); protected Map<String, List<Double>> expData = new HashMap<String, List<Double>>(); public static void main(String[] args) { PlotDriver.main(args); } @Override public void plot(LogListener listener, SESubstanceManager subMgr) { //fill PlotJob with needed data if it doesn't exist PlotJob job = (PlotJob) listener; if (job.dataPath == null || job.dataPath.isEmpty()) { job.dataPath = "../verification/Scenarios/" + job.verificationDirectory + "/Current Baseline/"; } if (job.dataFile == null || job.dataFile.isEmpty()) { job.dataFile = job.name + "Results.zip"; } //Get data contents for all headers when all data is in one file if ((data.isEmpty() || data == null) && job.experimentalData == null) { try { CSVContents csv = new CSVContents(job.dataPath + job.dataFile); csv.abbreviateContents = job.resultsSkipNum; for (int i = 0; i < job.headers.size(); i++) { List<Double> headerData = new ArrayList<Double>(); csv.readHeader(csv.unitUnderscoreToSpace(job.headers.get(i)), headerData); data.put(job.headers.get(i), headerData); } } catch (IOException e) { Log.error("Could not analyze file " + job.dataPath + job.dataFile); } } //Get data contents when we're also using experimental files else if ((data.isEmpty() || data == null) && job.experimentalData != null) { CSVContents dataCsv = null; CSVContents expCsv = null; try { dataCsv = new CSVContents(job.dataPath + job.dataFile); dataCsv.abbreviateContents = job.resultsSkipNum; } catch (IOException e) { Log.error("Could not analyze file " + job.dataPath + job.dataFile); } try { expCsv = new CSVContents(job.experimentalData); expCsv.abbreviateContents = 0; //we'll use all of the experimental data } catch (IOException e) { Log.error("Could not analyze file " + job.experimentalData); } try { //Get data from X1 and Y1, which should always be from our normal data file for (int i = 0; i < job.Y1headers.size(); i++) { List<Double> headerData = new ArrayList<Double>(); dataCsv.readHeader(dataCsv.unitUnderscoreToSpace(job.Y1headers.get(i)), headerData); data.put(job.Y1headers.get(i), headerData); } List<Double> headerData = new ArrayList<Double>(); dataCsv.readHeader(dataCsv.unitUnderscoreToSpace(job.X1header), headerData); data.put(job.X1header, headerData); for (int i = 0; i < job.Y2headers.size(); i++) { List<Double> headerData2 = new ArrayList<Double>(); expCsv.readHeader(expCsv.unitUnderscoreToSpace(job.Y2headers.get(i)), headerData2); expData.put(job.Y2headers.get(i), headerData2); } List<Double> headerData2 = new ArrayList<Double>(); expCsv.readHeader(expCsv.unitUnderscoreToSpace(job.X2header), headerData2); expData.put(job.X2header, headerData2); } catch (Exception e) { Log.error("A problem was encountered reading headers from files."); } } //Catch some errors if (job.Y2headers.size() > 0 && job.X2header == null) { Log.error("No X2 header specified for job " + job.name + ". Each Y axis must have a corresponding X axis."); return; } //Make a dataSeries for desired headers and add to collection(s) CSVPlotTool plotTool = new CSVPlotTool(); //to leverage existing functions String title = job.name + "_"; XYSeriesCollection dataSet1 = new XYSeriesCollection(); XYSeriesCollection dataSet2 = new XYSeriesCollection(); double maxY1 = 0; double minY1 = Double.MAX_VALUE; double maxY2 = 0; double minY2 = Double.MAX_VALUE; for (int i = 0; i < job.Y1headers.size(); i++) { XYSeries dataSeries; if (job.experimentalData != null) dataSeries = plotTool.createXYSeries("BioGears " + job.Y1headers.get(i), data.get(job.X1header), data.get(job.Y1headers.get(i))); else dataSeries = plotTool.createXYSeries(job.Y1headers.get(i), data.get(job.X1header), data.get(job.Y1headers.get(i))); dataSet1.addSeries(dataSeries); title = title + job.Y1headers.get(i) + "_"; maxY1 = maxY1 < dataSeries.getMaxY() ? dataSeries.getMaxY() : maxY1; minY1 = minY1 > dataSeries.getMinY() ? dataSeries.getMinY() : minY1; } for (int i = 0; i < job.Y2headers.size(); i++) { XYSeries dataSeries; if (job.experimentalData != null) dataSeries = plotTool.createXYSeries("Experimental " + job.Y2headers.get(i), expData.get(job.X2header), expData.get(job.Y2headers.get(i))); else dataSeries = plotTool.createXYSeries(job.Y2headers.get(i), data.get(job.X2header), data.get(job.Y2headers.get(i))); dataSet2.addSeries(dataSeries); title = title + job.Y2headers.get(i) + "_"; maxY2 = maxY2 < dataSeries.getMaxY() ? dataSeries.getMaxY() : maxY2; minY2 = minY2 > dataSeries.getMinY() ? dataSeries.getMinY() : minY2; } title = title + "vs_" + job.X1header; if (job.X2header != null && !job.X1header.equalsIgnoreCase(job.X2header)) title = title + "_" + job.X2header; //Override the constructed title if desired if (job.titleOverride != null && !job.titleOverride.isEmpty() && !job.titleOverride.equalsIgnoreCase("None")) title = job.titleOverride; //set labels String XAxisLabel = job.X1header; String YAxisLabel = job.Y1headers.get(0); JFreeChart chart = ChartFactory.createXYLineChart( job.titleOverride != null && job.titleOverride.equalsIgnoreCase("None") ? "" : title, // chart title XAxisLabel, // x axis label YAxisLabel, // y axis label dataSet1, // data PlotOrientation.VERTICAL, // orientation true, // include legend true, // tooltips false // urls ); Log.info("Creating Graph " + title); XYPlot plot = (XYPlot) chart.getPlot(); if (!job.logAxis) { // Determine Y1 range double resMax0 = maxY1; double resMin0 = minY1; if (Double.isNaN(resMax0) || Double.isNaN(resMin0)) plot.getDomainAxis(0).setLabel("Range is NaN"); if (DoubleUtils.isZero(resMin0)) resMin0 = -0.001; if (DoubleUtils.isZero(resMax0)) resMax0 = 0.001; double rangeLength = resMax0 - resMin0; ValueAxis yAxis = plot.getRangeAxis(0); if (rangeLength != 0) yAxis.setRange(resMin0 - 0.15 * rangeLength, resMax0 + 0.15 * rangeLength);//15% buffer so we can see top and bottom clearly //Override the bounds if desired try { if (job.Y1LowerBound != null) yAxis.setLowerBound(job.Y1LowerBound); if (job.Y1UpperBound != null) yAxis.setUpperBound(job.Y1UpperBound); } catch (Exception e) { Log.error( "Couldn't set Y bounds. You probably tried to set a bound on an axis that doesn't exist."); } plot.setRangeAxis(0, yAxis); //Add the second Y axis to the right side if (!job.Y2headers.isEmpty()) { ValueAxis rightYAxis = new NumberAxis(); // Determine Y2 range double resMax1 = maxY2; double resMin1 = minY2; if (Double.isNaN(resMax1) || Double.isNaN(resMin1)) plot.getDomainAxis(1).setLabel("Range is NaN"); if (DoubleUtils.isZero(resMin1)) resMin1 = -0.001; if (DoubleUtils.isZero(resMax1)) resMax1 = 0.001; rangeLength = resMax1 - resMin1; if (rangeLength != 0) rightYAxis.setRange(resMin1 - 0.15 * rangeLength, resMax1 + 0.15 * rangeLength); rightYAxis.setLabel(job.Y2headers.get(0)); //Override the bounds if desired try { if (job.Y2LowerBound != null) rightYAxis.setLowerBound(job.Y2LowerBound); if (job.Y2UpperBound != null) rightYAxis.setUpperBound(job.Y2UpperBound); } catch (Exception e) { Log.error( "Couldn't set Y bounds. You probably tried to set a bound on an axis that doesn't exist."); } plot.setRangeAxis(1, rightYAxis); } } else { double resMin = minY1 < minY2 ? minY1 : minY2; if (resMin <= 0.0) resMin = .00001; LogarithmicAxis yAxis = new LogarithmicAxis("Log(" + YAxisLabel + ")"); yAxis.setLowerBound(resMin); //Override the bounds if desired try { if (job.Y1LowerBound != null) yAxis.setLowerBound(job.Y1LowerBound); if (job.Y1UpperBound != null) yAxis.setUpperBound(job.Y1UpperBound); } catch (Exception e) { Log.error( "Couldn't set Y bounds. You probably tried to set a bound on an axis that doesn't exist."); } plot.setRangeAxis(0, yAxis); if (!job.Y2headers.isEmpty()) { LogarithmicAxis rightYAxis = new LogarithmicAxis("Log(" + job.Y2headers.get(0) + ")"); rightYAxis.setLowerBound(resMin); //Override the bounds if desired try { if (job.Y2LowerBound != null) rightYAxis.setLowerBound(job.Y2LowerBound); if (job.Y2UpperBound != null) rightYAxis.setUpperBound(job.Y2UpperBound); } catch (Exception e) { Log.error( "Couldn't set Y bounds. You probably tried to set a bound on an axis that doesn't exist."); } plot.setRangeAxis(1, rightYAxis); } } //Override X bounds if desired try { if (job.X1LowerBound != null) plot.getDomainAxis(0).setLowerBound(job.X1LowerBound); if (job.X1UpperBound != null) plot.getDomainAxis(0).setUpperBound(job.X1UpperBound); if (job.X2LowerBound != null) plot.getDomainAxis(1).setLowerBound(job.X2LowerBound); if (job.X2UpperBound != null) plot.getDomainAxis(1).setUpperBound(job.X2UpperBound); } catch (Exception e) { Log.error("Couldn't set X bounds. You probably tried to set a bound on an axis that doesn't exist."); } //Add the second dataset if necessary if (!job.Y2headers.isEmpty()) { plot.setDataset(1, dataSet2); plot.mapDatasetToRangeAxis(1, 1); } //Override labels if desired if (job.X1Label != null && !plot.getDomainAxis(0).getLabel().contains("NaN")) plot.getDomainAxis(0).setLabel(job.X1Label.equalsIgnoreCase("None") ? "" : job.X1Label); if (job.X2Label != null && plot.getDomainAxis(1) != null) plot.getDomainAxis(1).setLabel(job.X2Label.equalsIgnoreCase("None") ? "" : job.X2Label); if (job.Y1Label != null) plot.getRangeAxis(0).setLabel(job.Y1Label.equalsIgnoreCase("None") ? "" : job.Y1Label); if (job.Y2Label != null && plot.getRangeAxis(1) != null) plot.getRangeAxis(1).setLabel(job.Y2Label.equalsIgnoreCase("None") ? "" : job.Y2Label); //Format lines and colors plot.setDomainGridlinesVisible(job.showGridLines); plot.setRangeGridlinesVisible(job.showGridLines); formatMultiPlot(job, chart, dataSet1, dataSet2); //Handle legends if (job.removeAllLegends) chart.removeLegend(); //Make the file try { FileUtils.createDirectory(job.outputDir); String filename = job.outputFilename == null ? job.outputDir + "/" + plotTool.MakeFileName(title) + ".jpg" : job.outputDir + "/" + job.outputFilename; if (!filename.endsWith(".jpg")) filename = filename + ".jpg"; File JPGFile = new File(filename); if (job.imageHeight != null && job.imageWidth != null) ChartUtilities.saveChartAsJPEG(JPGFile, chart, job.imageWidth, job.imageHeight); else ChartUtilities.saveChartAsJPEG(JPGFile, chart, 1600, 800); } catch (IOException e) { Log.error(e.getMessage()); } } protected void formatMultiPlot(PlotJob job, JFreeChart chart, XYSeriesCollection dataSet1, XYSeriesCollection dataSet2) { Color[] blueColors = { Color.blue, Color.cyan, new Color(0, 160, 255), new Color(0, 100, 255), new Color(0, 160, 255), new Color(14, 0, 145), new Color(70, 105, 150) }; Color[] redColors = { Color.red, Color.magenta, new Color(255, 0, 100), new Color(255, 0, 160), Color.pink, new Color(145, 0, 0), new Color(132, 58, 58) }; Color[] variedColors = { Color.red, Color.blue, Color.green, Color.orange, Color.magenta, Color.cyan, Color.gray, new Color(255, 165, 0), new Color(42, 183, 136), new Color(87, 158, 186) }; XYPlot plot = (XYPlot) chart.getPlot(); XYLineAndShapeRenderer renderer1 = (XYLineAndShapeRenderer) plot.getRenderer(); BasicStroke wideLine = new BasicStroke(2.0f); //For Scientific notation NumberFormat formatter = new DecimalFormat("0.######E0"); for (int i = 0; i < plot.getDomainAxisCount(); i++) { plot.getDomainAxis(i).setLabelFont(new Font("SansSerif", Font.PLAIN, job.fontSize)); plot.getDomainAxis(i).setTickLabelFont(new Font("SansSerif", Font.PLAIN, 15)); plot.getDomainAxis(i).setLabelPaint(job.bgColor == Color.red ? Color.white : Color.black); plot.getDomainAxis(i).setTickLabelPaint(job.bgColor == Color.red ? Color.white : Color.black); } for (int i = 0; i < plot.getRangeAxisCount(); i++) { plot.getRangeAxis(i).setLabelFont(new Font("SansSerif", Font.PLAIN, job.fontSize)); plot.getRangeAxis(i).setTickLabelFont(new Font("SansSerif", Font.PLAIN, 15)); plot.getRangeAxis(i).setLabelPaint(job.bgColor == Color.red ? Color.white : Color.black); plot.getRangeAxis(i).setTickLabelPaint(job.bgColor == Color.red ? Color.white : Color.black); NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(i); rangeAxis.setNumberFormatOverride(formatter); } //White background outside of plottable area chart.setBackgroundPaint(job.bgColor); plot.setBackgroundPaint(Color.white); plot.setDomainGridlinePaint(Color.black); plot.setRangeGridlinePaint(Color.black); plot.setDomainCrosshairVisible(true); plot.setRangeCrosshairVisible(true); chart.getLegend().setItemFont(new Font("SansSerif", Font.PLAIN, 15)); chart.getTitle().setFont(new Font("SansSerif", Font.PLAIN, job.fontSize)); chart.getTitle().setPaint(job.bgColor == Color.red ? Color.white : Color.black); //If there is only one Y axis, we just need to color the data series differently if (job.Y2headers == null || job.Y2headers.isEmpty()) { for (int i = 0, cIndex = 0; i < dataSet1.getSeriesCount(); i++, cIndex++) { renderer1.setSeriesStroke(i, wideLine); renderer1.setBaseShapesVisible(false); if (cIndex > 9) cIndex = 0; renderer1.setSeriesFillPaint(i, variedColors[cIndex]); renderer1.setSeriesPaint(i, variedColors[cIndex]); } } //If there are 2 Y axes, we should color the axes to correspond with the data so it isn't (as) confusing else { StandardXYItemRenderer renderer2 = new StandardXYItemRenderer(); plot.setRenderer(1, renderer2); for (int i = 0, cIndex = 0; i < dataSet1.getSeriesCount(); i++, cIndex++) { renderer1.setSeriesStroke(i, wideLine); renderer1.setBaseShapesVisible(false); if (cIndex > 6) cIndex = 0; renderer1.setSeriesFillPaint(i, redColors[cIndex]); renderer1.setSeriesPaint(i, redColors[cIndex]); } for (int i = 0, cIndex = 0; i < dataSet2.getSeriesCount(); i++, cIndex++) { renderer2.setSeriesStroke(i, wideLine); renderer2.setBaseShapesVisible(false); if (cIndex > 6) cIndex = 0; renderer2.setSeriesFillPaint(i, blueColors[cIndex]); renderer2.setSeriesPaint(i, blueColors[cIndex]); } plot.getRangeAxis(0).setLabelPaint(redColors[0]); plot.getRangeAxis(0).setTickLabelPaint(redColors[0]); plot.getRangeAxis(1).setLabelPaint(blueColors[0]); plot.getRangeAxis(1).setTickLabelPaint(blueColors[0]); } } }