Java tutorial
/* * Copyright 2008-2010 Bay Area Software, 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 com.bayareasoftware.chartengine.chart.jfree; import static com.bayareasoftware.chartengine.model.PlotType.PLOT_CATEGORY; import static com.bayareasoftware.chartengine.model.PlotType.PLOT_GANTT; import static com.bayareasoftware.chartengine.model.PlotType.PLOT_HISTOGRAM; import static com.bayareasoftware.chartengine.model.PlotType.PLOT_TIME; import static com.bayareasoftware.chartengine.model.PlotType.PLOT_XY; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Paint; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.ImageObserver; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import javax.swing.ImageIcon; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.freehep.graphicsio.emf.EMFGraphics2D; import org.freehep.graphicsio.ps.PSGraphics2D; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartRenderingInfo; import org.jfree.chart.JFreeChart; import org.jfree.chart.LegendItem; import org.jfree.chart.LegendItemCollection; import org.jfree.chart.LegendItemSource; import org.jfree.chart.StandardChartTheme; import org.jfree.chart.annotations.XYPointerAnnotation; import org.jfree.chart.axis.Axis; import org.jfree.chart.axis.AxisLocation; import org.jfree.chart.axis.DateAxis; import org.jfree.chart.axis.DateTickUnit; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.axis.NumberTickUnit; import org.jfree.chart.axis.PeriodAxis; import org.jfree.chart.axis.TickUnits; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.block.Arrangement; import org.jfree.chart.block.Block; import org.jfree.chart.block.BlockContainer; import org.jfree.chart.block.BorderArrangement; import org.jfree.chart.encoders.EncoderUtil; import org.jfree.chart.encoders.ImageFormat; import org.jfree.chart.entity.ChartEntity; import org.jfree.chart.entity.EntityCollection; import org.jfree.chart.entity.TitleEntity; import org.jfree.chart.imagemap.ImageMapUtilities; import org.jfree.chart.imagemap.StandardToolTipTagFragmentGenerator; import org.jfree.chart.imagemap.StandardURLTagFragmentGenerator; import org.jfree.chart.imagemap.ToolTipTagFragmentGenerator; import org.jfree.chart.imagemap.URLTagFragmentGenerator; import org.jfree.chart.labels.ItemLabelAnchor; import org.jfree.chart.labels.ItemLabelPosition; import org.jfree.chart.labels.StandardPieSectionLabelGenerator; import org.jfree.chart.labels.StandardXYItemLabelGenerator; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.DatasetRenderingOrder; import org.jfree.chart.plot.DrawingSupplier; import org.jfree.chart.plot.IntervalMarker; import org.jfree.chart.plot.Marker; import org.jfree.chart.plot.PiePlot; import org.jfree.chart.plot.Plot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.PlotRenderingInfo; import org.jfree.chart.plot.SeriesRenderingOrder; import org.jfree.chart.plot.ValueMarker; import org.jfree.chart.plot.XYPlot; 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.DefaultXYItemRenderer; import org.jfree.chart.renderer.xy.StandardXYBarPainter; import org.jfree.chart.renderer.xy.XYBarRenderer; import org.jfree.chart.renderer.xy.XYBubbleRenderer; import org.jfree.chart.renderer.xy.XYItemRenderer; import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; import org.jfree.chart.title.CompositeTitle; import org.jfree.chart.title.ImageTitle; import org.jfree.chart.title.LegendTitle; import org.jfree.chart.title.TextTitle; import org.jfree.chart.urls.CategoryURLGenerator; import org.jfree.chart.urls.PieURLGenerator; import org.jfree.chart.urls.XYURLGenerator; import org.jfree.data.Range; import org.jfree.data.category.CategoryDataset; import org.jfree.data.category.DefaultCategoryDataset; import org.jfree.data.category.IntervalCategoryDataset; import org.jfree.data.gantt.Task; import org.jfree.data.gantt.TaskSeries; import org.jfree.data.gantt.TaskSeriesCollection; import org.jfree.data.general.Dataset; import org.jfree.data.general.DefaultPieDataset; import org.jfree.data.general.PieDataset; import org.jfree.data.statistics.SimpleHistogramDataset; import org.jfree.data.time.RegularTimePeriod; import org.jfree.data.time.TimePeriod; import org.jfree.data.time.TimeSeries; import org.jfree.data.time.TimeSeriesCollection; import org.jfree.data.time.TimeSeriesDataItem; import org.jfree.data.time.TimeTableXYDataset; import org.jfree.data.xy.DefaultTableXYDataset; import org.jfree.data.xy.DefaultXYZDataset; import org.jfree.data.xy.XYDataset; import org.jfree.ui.HorizontalAlignment; import org.jfree.ui.Layer; import org.jfree.ui.RectangleEdge; import org.jfree.ui.RectangleInsets; import org.jfree.ui.TextAnchor; import org.jfree.ui.VerticalAlignment; import com.bayareasoftware.chartengine.chart.ChartDiskResult; import com.bayareasoftware.chartengine.chart.ChartDriver; import com.bayareasoftware.chartengine.chart.ChartResult; import com.bayareasoftware.chartengine.chart.ShapeUtil; import com.bayareasoftware.chartengine.chart.jfree.JFreeNoodle.MyXYLabeler; import com.bayareasoftware.chartengine.ds.DataStream; import com.bayareasoftware.chartengine.ds.JoinTimeStream; import com.bayareasoftware.chartengine.ds.StringDataStream; import com.bayareasoftware.chartengine.model.BaseInfo; import com.bayareasoftware.chartengine.model.ChartConstants; import com.bayareasoftware.chartengine.model.ChartInfo; import com.bayareasoftware.chartengine.model.DataType; import com.bayareasoftware.chartengine.model.LogoInfo; import com.bayareasoftware.chartengine.model.MarkerDescriptor; import com.bayareasoftware.chartengine.model.MarkerValue; import com.bayareasoftware.chartengine.model.Metadata; import com.bayareasoftware.chartengine.model.PieItemLabelFormats; import com.bayareasoftware.chartengine.model.PlotType; import com.bayareasoftware.chartengine.model.Rectangle; import com.bayareasoftware.chartengine.model.SeriesDescriptor; import com.bayareasoftware.chartengine.model.SimpleProps; import com.bayareasoftware.chartengine.model.StringUtil; import com.bayareasoftware.chartengine.model.TimeConstants; import com.bayareasoftware.chartengine.model.types.ChartTypeSystem; import com.bayareasoftware.chartengine.util.FileUtil; import com.bayareasoftware.chartengine.util.URLUtil; import com.lowagie.text.Document; import com.lowagie.text.DocumentException; import com.lowagie.text.pdf.DefaultFontMapper; import com.lowagie.text.pdf.FontMapper; import com.lowagie.text.pdf.PdfContentByte; import com.lowagie.text.pdf.PdfTemplate; import com.lowagie.text.pdf.PdfWriter; /** * JFreeChartDriver is the main entry point for the Chart Engine APIs */ public class JFreeChartDriver implements ChartDriver { private final Log log = LogFactory.getLog(JFreeChartDriver.class); /** * the typeSystem contains the bean properties that describe the various kinds of * plots, renderers, etc. */ private ChartTypeSystem typeSystem; /** * the name of the typesystem file can be overridded by a java system property named TYPESYSTEM_PROPNAME */ private final static String TYPESYSTEM_PROPNAME = "chartengine.typeSystem"; static { initializeCMDefaults(); } /** * central place where we override various defaults from JFreeChart */ public static void initializeCMDefaults() { /** JFreeChart 1.0.11 introduces "ChartTheme", the default of which * interferes with our typesystem. Just use legacy no-op chart theme, at * least for now... */ ChartFactory.setChartTheme(StandardChartTheme.createLegacyTheme()); /* make 'plain' bar renderers the default, not gradient */ BarRenderer.setDefaultBarPainter(new StandardBarPainter()); XYBarRenderer.setDefaultBarPainter(new StandardXYBarPainter()); } private ChartLogo defaultLogo = new ChartLogo(); // keep an LRU cache of LogoInfo->ChartLogo mappings private static int lru_size = 5000; private LinkedHashMap<LogoInfo, ChartLogo> logoCache; // // watermark in all images // private Image watermarkImage = null; // private int watermark_width = 0; // private int watermark_height = 0; // // // a scaled version of the watermark to be used if the chart is too small // private Image smallWatermarkImage = null; // // private URL watermarkURL; // private boolean useWatermark = true; /** * create a new ChartDriver that uses jFreeChart as the rendering technology * */ public JFreeChartDriver() { // the typeFile is a properties file that describes all the JFreeChart specific capabilities // (pre-generated via reflection from TypeInspector) String typeFile = System.getProperties().getProperty(TYPESYSTEM_PROPNAME, "/jfreechart-types.xml"); try { typeSystem = TypeInspector.load(typeFile); } catch (Exception e) { String msg = "cannot load typesystem file '" + typeFile + "'"; log.error(msg, e); handleException(msg, e); } BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = bi.createGraphics(); SUBTITLE_FONTMETRICS = g2.getFontMetrics(SUBTITLE_FONT); logoCache = new LinkedHashMap<LogoInfo, ChartLogo>(1000, 0.75f, true) { // since this is a access-order LinkedHashMap // eldest means least recently accessed map // we remove the LRU entry when we need to protected boolean removeEldestEntry(Map.Entry<LogoInfo, ChartLogo> eldest) { if (size() > lru_size) { // log.warn("removing eldest entry due to size: " + size() + " exceeding LRU size of " + PER_CHART_LRU_SIZE + " chartresult:" + eldest.getValue()); ChartLogo cl = eldest.getValue(); if (cl.img != null) cl.img.flush(); if (cl.smallImg != null) cl.smallImg.flush(); return true; } return false; } }; } // used by getData() // to return date values, either as coarse or fine representation, depending on whether the timeperiod of the chart is coarser than day or not private String coarseDateFmt = "yyyy-MM-dd"; private String fineDateFmt = "HH:mm:ss"; private SimpleDateFormat coarseDateFormatter = new SimpleDateFormat(coarseDateFmt); private SimpleDateFormat fineDateFormatter = new SimpleDateFormat(fineDateFmt); /** * if coarseDates if true, return a coarse string representation of the Date ('yyyy-MM-dd'), otherwise * return a fine date representation * @param coarserDates * @param d * @return */ private String formatDate(boolean coarserDates, Date d) { if (coarserDates) { return coarseDateFormatter.format(d); } else { return fineDateFormatter.format(d); } } // /** // * toggle whether to use a water mark in each image // * @param useWatermark // */ // public void setUseWaterMark(boolean useWatermark) { // this.useWatermark = useWatermark; // } // public static final int SUBTITLE_MAXLINES = 5; public static Font SUBTITLE_FONT = Font.decode("Arial-10"); public static Font TEXTLOGO_FONT = Font.decode("Arial-14"); public static FontMetrics SUBTITLE_FONTMETRICS; /** * add a logo to the chart, for now, has some hardcoded positioning and margins * @param chart * @param w - width of the chart * @param h - height of the chart */ private void addLogo(ChartInfo ci, JFreeChart chart, int w, int h, ChartLogo chartLogo) { ImageTitle imageLogo = null; TextTitle textLogo = null; Block logoBlock = null; // either imageLogo or textLogo; TextTitle subTitle = null; int logoWidth = -1; if (chartLogo.visible) { if (chartLogo.txt == null) { // use an image logo Image img = chartLogo.img; logoWidth = chartLogo.width; if (chartLogo.width > w / 4 || chartLogo.height > h / 4) { // if the img is bigger than 20% of the height and width of the chart, then use the smaller image // if it exists if (chartLogo.smallImg != null) { logoWidth = 2 * chartLogo.width / 3; img = chartLogo.smallImg; } } imageLogo = new ImageTitle(img); imageLogo.setVerticalAlignment(VerticalAlignment.BOTTOM); imageLogo.setPosition(RectangleEdge.BOTTOM); logoBlock = imageLogo; } else { logoWidth = 200; // use a text logo textLogo = new TextTitle(); textLogo.setHorizontalAlignment(HorizontalAlignment.RIGHT); textLogo.setTextAlignment(HorizontalAlignment.RIGHT); textLogo.setText(chartLogo.txt); textLogo.setFont(TEXTLOGO_FONT); logoBlock = textLogo; } } String subTitleText = StringUtil.trim(ci.getDescription()); if (subTitleText != null) { int ind = subTitleText.lastIndexOf("\n=props=\n"); if (ind != -1) { subTitleText = subTitleText.substring(0, ind); } } // make sure that the subTitleText fits within the maxWidth; // if there's not enough room, don't show the subTitleText, leave the logo double maxWidth; if (logoBlock != null && logoWidth != -1) { maxWidth = w - logoWidth - 15; // 15 is a fudge factor for specing } else { maxWidth = w; } if (subTitleText != null && maxWidth > 0) { // work around font metrics jfreechart bug - make sure there are // no empty lines, with just newline \n String[] sa = StringUtil.splitCompletely(subTitleText, '\n', true); StringBuffer sb = new StringBuffer(); for (int i = 0; i < SUBTITLE_MAXLINES && i < sa.length; i++) { if (sa[i] == null || sa[i].length() == 0) { sa[i] = " "; } if (sb.length() != 0) { sb.append('\n'); } char[] s = sa[i].toCharArray(); int len = s.length; // ensure that each line's width fits into maxWidth while (len > 0 && SUBTITLE_FONTMETRICS != null && SUBTITLE_FONTMETRICS.charsWidth(s, 0, len) > maxWidth) { len = len - 1; } if (len < s.length) { // if we need to chop off some characters, try to do it at a word-break int l = len; while (l > 1 && !Character.isWhitespace(s[l])) { l--; } if (l > 1) { String fittedString = new String(s, 0, l); String remainderString = new String(s, l + 1, s.length - (l + 1)); // l+1 to skip the whitespace char sb.append(fittedString); if (i < sa.length - 1) { // and as long as we're not in the last line, // add the remainder to the next line and let the while loop take care of it sa[i + 1] = remainderString + "\n" + sa[i + 1]; } else { // we are at the end of all the original characters, check to // see if we have room left to append if (i < SUBTITLE_MAXLINES - 1) { sb.append("\n" + remainderString); } } } else { // we need to chop off some characters but there's no good word-break // just truncate sb.append(new String(s, 0, len)); } } else { // no need to truncate anywhere, just stick the original line in there sb.append(sa[i]); } } subTitle = new TextTitle(); subTitle.setHorizontalAlignment(HorizontalAlignment.LEFT); subTitle.setTextAlignment(HorizontalAlignment.LEFT); subTitle.setMaximumLinesToDisplay(SUBTITLE_MAXLINES); subTitle.setText(sb.toString()); subTitle.setFont(SUBTITLE_FONT); // subTitle.setFrame(new BlockBorder(Color.red)); } // Arrangement a; a = new BorderArrangement(); //a = new FlowArrangement(); BlockContainer container = new BlockContainer(a); if (subTitle != null) { container.add(subTitle, RectangleEdge.LEFT); } // trick copied from jfree chart examples. stuff a big empty block to force // the subtitle and logo to the edges TextTitle emptyBlock = new TextTitle(); emptyBlock.setExpandToFitSpace(true); container.add(emptyBlock); if (logoBlock != null) { container.add(logoBlock, RectangleEdge.RIGHT); } CompositeTitle ct = new CompositeTitle(container); ct.setPosition(RectangleEdge.BOTTOM); // adding this at index 0 should help put it below the legend, if any chart.addSubtitle(0, ct); } public ChartTypeSystem getTypeSystem() { return typeSystem; } private Rectangle makeRect(ChartEntity ce) { Rectangle ret = null; if (ce != null) { Shape shape = ce.getArea(); if (shape != null) { java.awt.Rectangle jar = shape.getBounds(); ret = new Rectangle(jar.x, jar.y, jar.width, jar.height); } } return ret; } private Rectangle makeRect(Rectangle2D r) { Rectangle ret = null; if (r != null) { int x, y, w, h; x = (int) r.getX(); y = (int) r.getY(); w = (int) r.getWidth(); h = (int) r.getHeight(); ret = new Rectangle(x, y, w, h); } return ret; } private BufferedImage createThumbnail(JFreeChart chart, ChartRenderingInfo cri) { BufferedImage ret = null; LegendTitle lt = chart.getLegend(); boolean oldVis = false; try { if (lt != null) { oldVis = lt.isVisible(); lt.setVisible(false); } ret = chart.createBufferedImage(150, 110, 360, 240, null); } finally { if (lt != null) lt.setVisible(oldVis); } return ret; } private void writeImageAsPNG(BufferedImage bi, String filename) throws IOException { OutputStream out = null; try { out = new BufferedOutputStream(new FileOutputStream(new File(filename))); EncoderUtil.writeBufferedImage(bi, ImageFormat.PNG, out); } finally { if (out != null) out.close(); } //log.warn("||||||||||||||||| JFreeChartDriver.writeImageAsPNG: wrote image to : " + filename); } /** * given a chart, calculate the datastream that is the 'JOIN' of all the data that is being plotted * * TODO: may be more efficient to calculate this as a side-effect of creating the chart * * @param ci * @param sMap * @param template * @param maxRows - if > 0, limit the number of rows returned * @return data stream of results * @throws Exception */ public DataStream getData(ChartInfo ci, Map<Integer, DataStream> sMap, SimpleProps template, int maxRows) throws Exception { ChartContext ctxt = new ChartContext(ci, sMap); DataStream ret = null; int numDatasets = ctxt.getDatasetCount(); DataStream[] results = new DataStream[numDatasets]; if (maxRows <= 0) { maxRows = Integer.MAX_VALUE; } if (numDatasets > 0) { PlotType ptype = ci.getPlotType(); switch (ptype) { case PLOT_TIME: { // for time data, we return the time value as either as yyyy-MM-dd // or as a string HH:mm:ss, depending on whether the time period of the plot // is coarser than a DAY boolean coarseDates = ci.getTimePeriod() >= TimeConstants.TIME_DAY; for (int i = 0; i < numDatasets; i++) { Dataset dset = ctxt.getDataset(i); if (dset instanceof TimeTableXYDataset) { TimeTableXYDataset ttxy = (TimeTableXYDataset) ctxt.getDataset(i); int numSeries = ttxy.getSeriesCount(); List<String[]> data = new ArrayList<String[]>(); int item = 0; int itemCount = ttxy.getItemCount(); // every series has the same itemcount in TimeTableXYDataset for (int si = 0; si < itemCount; si++) { for (int s = 0; s < numSeries; s++) { TimePeriod tp = ttxy.getTimePeriod(si); Comparable seriesKey = ttxy.getSeriesKey(s); Number y = ttxy.getY(s, si); if (y != null) { String[] dataRow = new String[3]; // every row has a time, a series, and a Y value dataRow[0] = formatDate(coarseDates, tp.getStart()); dataRow[1] = seriesKey.toString(); dataRow[2] = y.toString(); data.add(dataRow); item++; } } if (item == maxRows) { break; } } Metadata md = new Metadata(3); md.setColumnType(1, DataType.DATE); md.setColumnName(1, "Date"); if (coarseDates) { md.setColumnFormat(1, coarseDateFmt); } else { md.setColumnFormat(1, fineDateFmt); } md.setColumnType(2, DataType.STRING); md.setColumnName(2, "Series"); md.setColumnType(3, DataType.DOUBLE); md.setColumnName(3, "Value"); results[i] = new StringDataStream(data, md, 0, item); results[i].reset(); } else if (dset instanceof TimeSeriesCollection) { TimeSeriesCollection tsc = (TimeSeriesCollection) ctxt.getDataset(i); int numSeries = tsc.getSeriesCount(); // to join the stream's we create a tree-map (sorted map) that // maps Long (date value in msecs) to a 'row' of numeric values TreeMap<Long, Number[]> tmap = new TreeMap<Long, Number[]>(); for (int s = 0; s < numSeries; s++) { TimeSeries tseries = tsc.getSeries(s); for (int item = 0; item < tseries.getItemCount(); item++) { TimeSeriesDataItem tdata = tseries.getDataItem(item); RegularTimePeriod rtp = tdata.getPeriod(); if (rtp != null) { Long key = new Long(rtp.getFirstMillisecond()); Number[] row = tmap.get(key); if (row == null) { // this is the first time, we've seen this key, add a new row row = new Number[numSeries]; tmap.put(key, row); } row[s] = tdata.getValue(); } } } List<String[]> data = new ArrayList<String[]>(); int endRow = 0; // convert the sorted TreeMap of values to an array of strings for use as a StringDataStream for (Long k : tmap.keySet()) { Number[] row = tmap.get(k); String[] dataRow = new String[numSeries + 1]; dataRow[0] = formatDate(coarseDates, new Date(k)); for (int j = 0; j < numSeries; j++) { if (row[j] != null) { dataRow[j + 1] = row[j].toString(); // FIXME: better formatting of numbers } } data.add(dataRow); endRow++; if (endRow == maxRows) { break; } } Metadata md = new Metadata(numSeries + 1); md.setColumnType(1, DataType.DATE); md.setColumnName(1, "Date"); //md.setColumnFormat(1,Metadata.INTERNAL_DATE_FORMAT); if (coarseDates) { md.setColumnFormat(1, coarseDateFmt); } else { md.setColumnFormat(1, fineDateFmt); } for (int j = 0; j < numSeries; j++) { md.setColumnType(2 + j, DataType.DOUBLE); md.setColumnName(2 + j, tsc.getSeriesKey(j).toString()); } results[i] = new StringDataStream(data, md, 0, endRow); results[i].reset(); } else if (dset instanceof DefaultXYZDataset) { // we use defaultXYZdataset for TimeSeries bubble plots // all datasets have common columns (though potentially in different order) DefaultXYZDataset dxyz = (DefaultXYZDataset) ctxt.getDataset(i); int numSeries = dxyz.getSeriesCount(); List<String[]> data = new ArrayList<String[]>(); int item = 0; for (int s = 0; s < numSeries && item < maxRows; s++) { for (int si = 0; si < dxyz.getItemCount(s); si++) { Number x = dxyz.getX(s, si); Number y = dxyz.getY(s, si); Number z = dxyz.getZ(s, si); log.warn("s = " + s + " si = " + si + " x = " + x + " y = " + y + " z = " + z); String[] dataRow = new String[3]; // every row has a time, a series, and a Y value //dataRow[0] = String.valueOf(x.longValue()); // the first column is the time in msecs dataRow[0] = formatDate(coarseDates, new Date(x.longValue())); dataRow[1] = y.toString(); dataRow[2] = z.toString(); data.add(dataRow); item++; if (item == maxRows) { break; } } } Metadata md = new Metadata(3); md.setColumnType(1, DataType.DATE); md.setColumnName(1, "Date"); //md.setColumnFormat(1,Metadata.INTERNAL_DATE_FORMAT); if (coarseDates) { md.setColumnFormat(1, coarseDateFmt); } else { md.setColumnFormat(1, fineDateFmt); } md.setColumnType(2, DataType.DOUBLE); md.setColumnName(2, "Y"); md.setColumnType(3, DataType.DOUBLE); md.setColumnName(3, "Z"); results[i] = new StringDataStream(data, md, 0, item); results[i].reset(); } } // join the time streams together using JoinTimeStream if (numDatasets == 1) { ret = results[0]; } else { int[] dateCols = new int[numDatasets]; for (int i = 0; i < numDatasets; i++) dateCols[i] = 1; // the date col is in the 1st column in all the stringdatastreams we've created List<DataStream> dstreams = new ArrayList<DataStream>(); for (int i = 0; i < results.length; i++) { if (results[i] != null) dstreams.add(results[i]); } // if we have to use join time stream, the date columns will not be coarse/fine, but only coarse, (i.e. yyyy-MM-dd) ret = new JoinTimeStream(dstreams.toArray(new DataStream[(dstreams.size())]), dateCols); } } break; case PLOT_CATEGORY: { // for the category datasets, the "categories" are the columns // and all datasets have common columns (though potentially in different order) // the String key is the column key // and there are as many columns as there are row keys (series names) LinkedHashMap<Comparable, List<Number>> map = new LinkedHashMap<Comparable, List<Number>>(); int numCols = 0; List<Comparable> rowKeys = new ArrayList<Comparable>(); for (int i = 0; i < numDatasets; i++) { DefaultCategoryDataset cds = (DefaultCategoryDataset) ctxt.getDataset(i); for (int r = 0; r < cds.getRowCount(); r++) { rowKeys.add(cds.getRowKey(r)); numCols = cds.getColumnCount(); for (int col = 0; col < numCols; col++) { Comparable colKey = cds.getColumnKey(col); List<Number> row = map.get(colKey); if (row == null) { row = new ArrayList<Number>(); map.put(colKey, row); } row.add(cds.getValue(r, col)); } } } List<String[]> data = new ArrayList<String[]>(); int endRow = 0; Metadata md = new Metadata(rowKeys.size() + 1); md.setColumnType(1, DataType.STRING); md.setColumnName(1, "Category"); for (int c = 0; c < rowKeys.size(); c++) { md.setColumnType(c + 2, DataType.DOUBLE); md.setColumnName(c + 2, rowKeys.get(c).toString()); } for (Comparable k : map.keySet()) { List<Number> row = map.get(k); String[] dataRow = new String[row.size() + 1]; String key = k.toString(); dataRow[0] = key; for (int j = 0; j < row.size(); j++) { if (row.get(j) != null) { dataRow[j + 1] = row.get(j).toString(); // FIXME: better formatting of numbers } } data.add(dataRow); endRow++; if (endRow == maxRows) { break; } } ret = new StringDataStream(data, md, 0, endRow); ret.reset(); } break; case PLOT_GANTT: { TaskSeriesCollection tsc = (TaskSeriesCollection) ctxt.getDataset(0); List<String[]> data = new ArrayList<String[]>(); Metadata md = new Metadata(4); md.setColumnType(1, DataType.STRING); md.setColumnName(1, "Task Description"); md.setColumnType(2, DataType.STRING); md.setColumnName(2, "Series"); md.setColumnType(3, DataType.DATE); md.setColumnFormat(3, Metadata.INTERNAL_DATE_FORMAT); md.setColumnName(3, "Start Date"); md.setColumnType(4, DataType.DATE); md.setColumnFormat(4, Metadata.INTERNAL_DATE_FORMAT); md.setColumnName(4, "End Date"); int endRow = 0; for (int s = 0; s < tsc.getSeriesCount() && endRow < maxRows; s++) { Comparable seriesKey = tsc.getSeriesKey(s); //log.warn("TaskSeriesCollection, series key = " + seriesKey); TaskSeries series = tsc.getSeries(s); for (int si = 0; si < series.getItemCount(); si++) { Task task = series.get(si); String description = task.getDescription(); TimePeriod tp = task.getDuration(); Date start = tp.getStart(); Date end = tp.getEnd(); // log.warn("description = " + description + " start = " + start + " end = " + end); String[] dataRow = new String[4]; dataRow[0] = description; dataRow[1] = seriesKey.toString(); dataRow[2] = String.valueOf(start.getTime()); dataRow[3] = String.valueOf(end.getTime()); data.add(dataRow); endRow++; if (endRow == maxRows) { break; } } } ret = new StringDataStream(data, md, 0, endRow); ret.reset(); } break; case PLOT_HISTOGRAM: { // for histogram's, only handle one dataset SimpleHistogramDataset shd = (SimpleHistogramDataset) ctxt.getDataset(0); // note that the series count is always 1 for SimpleHistogramDataset List<String[]> data = new ArrayList<String[]>(); Metadata md = new Metadata(3); md.setColumnType(1, DataType.DOUBLE); md.setColumnName(1, "Start X Value"); md.setColumnType(2, DataType.DOUBLE); md.setColumnName(2, "End X Value"); md.setColumnType(3, DataType.DOUBLE); md.setColumnName(3, "Y Value"); int endRow = 0; for (int si = 0; si < shd.getItemCount(0); si++) { Number startX = shd.getStartX(0, si); Number endX = shd.getEndX(0, si); Number y = shd.getY(0, si); // log.warn("SeriesKey: " + shd.getSeriesKey(0) + " startX = " + startX + " endX = " + endX + "y = " + y); String[] dataRow = new String[3]; dataRow[0] = startX.toString(); dataRow[1] = endX.toString(); dataRow[2] = y.toString(); data.add(dataRow); endRow++; if (endRow == maxRows) { break; } } ret = new StringDataStream(data, md, 0, endRow); ret.reset(); } break; case PLOT_PIE: case PLOT_PIE3D: case PLOT_RING: { DefaultPieDataset pieds = (DefaultPieDataset) ctxt.getDataset(0); List<String[]> data = new ArrayList<String[]>(); int endRow = 0; Metadata md = new Metadata(2); md.setColumnType(1, DataType.STRING); md.setColumnName(1, "Category"); md.setColumnType(1, DataType.DOUBLE); md.setColumnName(2, "Value"); for (int i = 0; i < pieds.getItemCount(); i++) { String[] dataRow = new String[2]; Comparable key = pieds.getKey(i); Number value = pieds.getValue(i); if (value != null) { dataRow[0] = key.toString(); dataRow[1] = value.toString(); data.add(dataRow); endRow++; } if (endRow == maxRows) { break; } } ret = new StringDataStream(data, md, 0, endRow); ret.reset(); } break; case PLOT_XY: { // DefaultTableXYDataset is used of XY Plots // the key is the X value // and there are as many columns as there are different series LinkedHashMap<Number, List<Number>> map = new LinkedHashMap<Number, List<Number>>(); // the number of columns is the sum of all the series in every dataset List<Comparable> seriesKeys = new ArrayList<Comparable>(); for (int i = 0; i < numDatasets; i++) { DefaultTableXYDataset dtxy = (DefaultTableXYDataset) ctxt.getDataset(i); for (int s = 0; s < dtxy.getSeriesCount(); s++) { seriesKeys.add(dtxy.getSeriesKey(s)); for (int si = 0; si < dtxy.getItemCount(s); si++) { Number x = dtxy.getX(s, si); Number y = dtxy.getY(s, si); if (x != null) { List<Number> row = map.get(x); if (row == null) { row = new ArrayList<Number>(); map.put(x, row); } row.add(y); } } } } List<String[]> data = new ArrayList<String[]>(); Metadata md = new Metadata(seriesKeys.size() + 1); md.setColumnType(1, DataType.DOUBLE); md.setColumnName(1, "X"); for (int c = 0; c < seriesKeys.size(); c++) { md.setColumnType(c + 2, DataType.DOUBLE); md.setColumnName(c + 2, seriesKeys.get(c).toString()); } int endRow = 0; for (Number k : map.keySet()) { List<Number> row = map.get(k); String[] dataRow = new String[row.size() + 1]; dataRow[0] = k.toString(); for (int j = 0; j < row.size(); j++) { if (row.get(j) != null) { dataRow[j + 1] = row.get(j).toString(); // FIXME: better formatting of numbers } } data.add(dataRow); endRow++; if (endRow == maxRows) { break; } } ret = new StringDataStream(data, md, 0, endRow); ret.reset(); } break; } } // if numDataSets > 0 return ret; } /** * main entry point for creating charts, given a ChartInfo, create a chart and return it. Does not cache the chart * * @param ci ChartInfo that describes the UI properties for the chart * @param logo logo for this chart (if null, use default logo) * @param sMap the map of descriptor sid's to DataStream that supply all the data for the chart series and markers * @param template Template of default properties, can be null * @param ret an optional ChartResult can be passed in to guide the creation of the object. * the dimensions (width/height) in the ChartResult override the dimensions in the ChartInfo * If the ChartResult has filePaths set, then those files will be use to materialize * the image, thumbnail, and image maps on disk * if not the, the data will be kept inline in the ChartResult * if res is null, then a new ChartResult is created and data will be kept in that object * @return the ChartResult * @throws rethrows any exception raised during chart creation. but if the ChartResult passed in has isShowErrorImage() set then * the exception is converted into a chart image and returned instead of being re-thrown */ public ChartResult create(ChartInfo ci, LogoInfo logo, Map<Integer, DataStream> sMap, SimpleProps template, ChartResult ret) throws Exception { try { if (ret == null) ret = new ChartResult(); long start, now; // how long does it take to do various stages long queryTime, createTime, persistTime, logoTime, imageTime, vectorTime, thumbnailTime; int WIDTH = ret.getWidth() > 0 ? ret.getWidth() : ci.getWidth(); WIDTH = Math.min(WIDTH, MAX_WIDTH); int HEIGHT = ret.getHeight() > 0 ? ret.getHeight() : ci.getHeight(); HEIGHT = Math.min(HEIGHT, MAX_HEIGHT); ret.setSize(WIDTH, HEIGHT); ret.setPlotType(ci.getPlotType()); start = System.currentTimeMillis(); long createStart = start; // ====== Step 1: create the necessary data sets ChartContext ctxt = new ChartContext(ci, sMap); //getData(ctxt); now = System.currentTimeMillis(); queryTime = now - start; start = now; ret.colorMap = new HashMap<Integer, String>(); // ====== Step 2: create the actual JFreeChart // using the ChartBundle and the dsMap //JFreeChart chart = createChart(ci, template, dsMap, markerValues); JFreeChart chart = createChart(ci, template, ctxt, ret.colorMap); // if (ret.colorMap != null) { // log.warn("*############################ colorMap ########################"); // for (Integer k : ret.colorMap.keySet()) { // log.warn("colorMap(" + k + ") = " + ret.colorMap.get(k)); // } // } now = System.currentTimeMillis(); createTime = now - start; start = now; // ====== Step 3: add ChartMechanic logo to the chart // TODO: add other fixtures like ads, etc. ChartLogo chartLogo = defaultLogo; if (logo != null) { chartLogo = logoCache.get(logo); //System.err.println("*******************logoCache.get(" + logo + ") returned = " + chartLogo); if (chartLogo == null) { chartLogo = logoinfo2chartlogo(logo); //System.err.println("*******************logoinfo2chartlogo(" + logo + ") returned = " + chartLogo); logoCache.put(logo, chartLogo); } } addLogo(ci, chart, WIDTH, HEIGHT, chartLogo); now = System.currentTimeMillis(); logoTime = now - start; start = now; ChartRenderingInfo cri = new ChartRenderingInfo(); ChartDiskResult diskResult = ret.getDiskResult(); // ====== Step 4: generate the full image // either as raw PNGdata or to a file as specified in the ChartResult BufferedImage bi = chart.createBufferedImage(WIDTH, HEIGHT, cri); // record geometry of various regions of the chart into the // chart result... EntityCollection ecoll = cri.getEntityCollection(); if (ecoll != null && ecoll.getEntityCount() > 0) { int ecount = ecoll.getEntityCount(); TextTitle title = chart.getTitle(); // the first entity is the entire chart itself int r = 1; if (title != null) { String s = title.getText(); if (s != null && !s.trim().equals("") && r < ecount) { // the 2nd entity is the title as long as it's not // null ChartEntity ce = ecoll.getEntity(r); if (isTitleEntity(ce)) { Rectangle re = makeRect(ce); ret.setTitleRect(re); } r++; } } if (chart.getSubtitleCount() > 0) { // the next entity is the subtitle (either the first or // second entry depending on whether there is a title String desc = ci.getDescription(); if (desc != null && !desc.trim().equals("") && r < ecount) { ChartEntity ce = ecoll.getEntity(r); if (isTitleEntity(ce)) { Rectangle re = makeRect(ce); ret.setSubtitleRect(re); } } } /* * for (int i = 0; i < ecoll.getEntityCount(); i++) { * ChartEntity ce = ecoll.getEntity(i); System.out.println( * "[JFreeDriver] entity #" + i + " =" + ce.getClass().getName() * + " coords=" + ce.getShapeCoords()); } */ } now = System.currentTimeMillis(); imageTime = now - start; start = now; if (diskResult.hasImagePath()) { writeImageAsPNG(bi, diskResult.getImagePath()); } // step 4.1: record plot geometry { PlotRenderingInfo pri = cri.getPlotInfo(); ret.setPlotRect(makeRect(pri.getPlotArea())); ret.setPlotDataRect(makeRect(pri.getDataArea())); } now = System.currentTimeMillis(); persistTime = now - start; start = now; // ====== Step 4a: generate a vector file (e.g. pdf) if necessary // if (diskResult.isGeneratePDF()) { if (diskResult.hasPdfPath()) { OutputStream out = null; try { out = new FileOutputStream(new File(diskResult.getPdfPath())); writeChartAsPDF(out, chart, WIDTH, HEIGHT, new DefaultFontMapper()); } finally { if (out != null) out.close(); } } } else if (diskResult.isGeneratePS()) { if (diskResult.hasPSPath()) { OutputStream out = null; try { out = new FileOutputStream(new File(diskResult.getPSPath())); writeChartAsPS(out, chart, WIDTH, HEIGHT); } finally { if (out != null) out.close(); } } } else if (diskResult.isGenerateEMF()) { if (diskResult.hasEMFPath()) { OutputStream out = null; try { out = new FileOutputStream(new File(diskResult.getEMFPath())); writeChartAsEMF(out, chart, WIDTH, HEIGHT); } finally { if (out != null) out.close(); } } } now = System.currentTimeMillis(); vectorTime = now - start; start = now; // ====== Step 5: generate the thumbnail (if necessary) // either as raw PNGdata or to a file as specified in the ChartResult if (diskResult.isGenerateThumbnail()) { BufferedImage thumbnail = createThumbnail(chart, cri); if (diskResult.hasThumbPath()) { writeImageAsPNG(thumbnail, diskResult.getThumbPath()); } } now = System.currentTimeMillis(); thumbnailTime = now - start; start = now; // ====== Step 6: generate the imageMap (if necessary) if (diskResult.isGenerateImageMap()) { URLTagFragmentGenerator utag = new StandardURLTagFragmentGenerator(); ToolTipTagFragmentGenerator ttgen = new StandardToolTipTagFragmentGenerator(); String imapId = diskResult.getImageMapId(); String imap = ImageMapUtilities.getImageMap(imapId, cri, ttgen, utag); if (diskResult.hasImageMapPath()) { FileUtil.writeString(new File(diskResult.getImageMapPath()), imap); } // else { // diskResult.setImageMap(imap); // } } // === Step 7: record axis ranges { Range range; Plot plot = chart.getPlot(); if (plot instanceof XYPlot) { XYPlot xyp = (XYPlot) plot; ValueAxis domain = xyp.getDomainAxis(); range = domain.getRange(); ret.setAxisRange(0, range.getLowerBound(), range.getUpperBound()); for (int i = 0; i < xyp.getRangeAxisCount(); i++) { ValueAxis va = xyp.getRangeAxis(i); if (va == null) { // range axis may not exist continue; } range = va.getRange(); double lower = range.getLowerBound(); double upper = range.getUpperBound(); //p("rangeAxis[" + i + "] lower=" + lower + " upper=" + upper); ret.setAxisRange(i + 1, lower, upper); } } else if (plot instanceof CategoryPlot) { CategoryPlot cplot = (CategoryPlot) plot; for (int i = 0; i < cplot.getRangeAxisCount(); i++) { ValueAxis va = cplot.getRangeAxis(i); if (va == null) { // range axis may not exist continue; } range = va.getRange(); ret.setAxisRange(i + 1, range.getLowerBound(), range.getUpperBound()); } } } if (log.isDebugEnabled()) { log.debug("created chart: id=" + ci.getId() + "/' in " + (System.currentTimeMillis() - createStart) + " ms." + " create time= " + createTime + " query time=" + queryTime + " image time = " + imageTime + " logo time = " + imageTime + " persist time =" + persistTime + " vector time = " + vectorTime + " thumbnail time = " + thumbnailTime); } ret.setQueryTime(queryTime); ret.setCreateTime(createTime); ret.setPersistTime(persistTime); return ret; } catch (Exception e) { e.printStackTrace(); if (ret.getDiskResult().isShowErrorImage()) { throwable2chart(ret, e); return ret; } throw e; } } private boolean isTitleEntity(ChartEntity ce) { return ce != null && ce instanceof TitleEntity; } /** * convert a logoInfo into a chartInfo * @param logoInfo * @return */ private ChartLogo logoinfo2chartlogo(LogoInfo logo) { final ChartLogo ret = new ChartLogo(); ret.visible = false; if (logo.isVisible()) { if (logo.getTxt() != null) { ret.txt = logo.getTxt(); } else { String urlStr = logo.getUrl(); try { if (urlStr != null) { URL u; if (urlStr.startsWith("/com/bayareasoftware/chartengine")) { // if the URL starts with /com/bayareasoftware/chartengine // treat it as a resource u = JFreeChartDriver.class.getResource(urlStr); } else { // otherwise, try to make a URL out of the string if (!(urlStr.startsWith("vfs:")) && !(urlStr.contains(":"))) { // if it's just a flat string, look up the file name from // vfs:admin/images/ urlStr = "vfs:admin/images/" + urlStr; } u = URLUtil.safeURL(urlStr); //u = new URL(urlStr); } ImageIcon ii = new ImageIcon(u); ret.img = ii.getImage(); ret.width = ii.getIconWidth(); ret.height = ii.getIconHeight(); if (ret.width > 10 && ret.height > 10) { int w = ret.width * 2 / 3; int h = ret.height * 2 / 3; ret.smallImg = null; // instead of using getScaledInstance, which is asynchronous // use the scaling version of Graphics2D.drawImage. // it is also asynchronous but we can add set an ImageObserver on it BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); final Graphics2D g2 = bi.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); boolean ok = g2.drawImage(ret.img, 0, 0, w, h, new ImageObserver() { public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { ret.smallImg = img; boolean complete = (infoflags & ImageObserver.ALLBITS) != 0; if (complete) { ret.smallImg = img; g2.dispose(); } return !complete; } }); if (ok) { ret.smallImg = bi; } } // log.info("############################################setting chart logo to " + u.toExternalForm() + // "ret.img = " + ret.img + " ret.width = " + ret.width + " ret.height = " + ret.height); } } catch (MalformedURLException e) { log.error("Malformed url: " + urlStr + " for chart_logo"); return ret; } } ret.visible = true; } return ret; } /** * when we encounter an error, generate a chart with the textual content of that exception * and stick it in the ChartResult as a PNG * @param res * @param t * @throws IOException */ private void throwable2chart(ChartResult res, Throwable t) throws IOException { BufferedImage bi = new BufferedImage(res.getWidth(), res.getHeight(), BufferedImage.TYPE_INT_RGB); res.getDiskResult().setErrorMessage(t.toString()); Graphics2D g2 = bi.createGraphics(); Font f = Font.decode("monospaced-9"); g2.setFont(f); int lineHeight = f.getSize() + 3; int x = 4; int y = 10; g2.drawString(t.toString(), x, y); y += lineHeight; StackTraceElement[] elems = t.getStackTrace(); for (StackTraceElement elem : elems) { g2.drawString(elem.toString(), x, y); y += lineHeight; } g2.dispose(); if (res.getDiskResult().hasImagePath()) { writeImageAsPNG(bi, res.getDiskResult().getImagePath()); } // else { // res.setPNGData(ChartUtilities.encodeAsPNG(bi)); // } } private SimpleProps getExtraProps(ChartInfo ci) { SimpleProps ret = new SimpleProps(); String desc = ci.getDescription(); if (desc != null) { int ind = desc.lastIndexOf("\n=props=\n"); if (ind != -1) { ret.load(desc.substring(ind + 9)); //ci.setDescription(desc.substring(0, ind)); } } return ret; } /** * create the actual JFreeChart object * @param ci - ChartInfo chart specification * @param templateProps - chart template properties that serve as defaults for the chart * @param ctxt - ChartContext * @param colorMap - map of BaseDescriptor sid to String value of color * @return - the JFreeChart object */ private JFreeChart createChart(ChartInfo ci, SimpleProps templateProps, final ChartContext ctxt, Map<Integer, String> colorMap) { //Dataset dset = ctxt.getDefaultDataset(); Dataset dset = null; JFreeChart ret = null; // use the last part of the ID as the default title; //String defaultChartTitle = BaseInfo.lastIDPart(ci.getId(),""); String defaultChartTitle = ""; SimpleProps extras = getExtraProps(ci); PlotType ptype = ci.getPlotType(); if (ptype == null) { throw new RuntimeException("Unexpected null plot type"); } switch (ptype) { case PLOT_XY: ret = ChartFactory.createXYLineChart(defaultChartTitle, null, null, (XYDataset) dset, PlotOrientation.VERTICAL, true, true, true); break; case PLOT_TIME: ret = ChartFactory.createTimeSeriesChart(defaultChartTitle, null, null, (XYDataset) dset, true, true, true); break; case PLOT_CATEGORY: ret = ChartFactory.createBarChart(defaultChartTitle, null, null, (CategoryDataset) dset, PlotOrientation.VERTICAL, true, true, true); break; case PLOT_GANTT: ret = ChartFactory.createGanttChart(defaultChartTitle, null, null, (IntervalCategoryDataset) dset, true, /* legend*/ false, /* tooltips*/ false /* urls*/ ); break; case PLOT_PIE: ret = ChartFactory.createPieChart(defaultChartTitle, (PieDataset) dset, true, true, true); break; case PLOT_PIE3D: ret = ChartFactory.createPieChart3D(defaultChartTitle, (PieDataset) dset, true, true, true); break; case PLOT_RING: ret = ChartFactory.createRingChart(defaultChartTitle, (PieDataset) dset, true, true, true); break; case PLOT_HISTOGRAM: ret = ChartFactory.createHistogram(defaultChartTitle, null, null, (SimpleHistogramDataset) dset, PlotOrientation.VERTICAL, true, true, true); break; } SimpleProps opaqueProps = ci.getProps(); if (templateProps != null) { SimpleProps tmp = (SimpleProps) templateProps.clone(); tmp.putAll(opaqueProps); opaqueProps = tmp; } TextTitle txtTitle = ret.getTitle(); BeanUtil.setProps(txtTitle, opaqueProps, "title."); LegendTitle legend = ret.getLegend(); if (legend != null) { BeanUtil.setProps(legend, opaqueProps, "legend."); Paint p = legend.getBackgroundPaint(); if (p != null && p instanceof Color) { float alpha = (float) ChartUtil.decodeDouble( opaqueProps.get("legend." + ChartConstants.CM_PROP_PREFIX + "backgroundAlpha"), 1.0); if (alpha != 1.0) { int aint = (int) (alpha * 255); Color c = (Color) p; c = new Color(c.getRed(), c.getGreen(), c.getBlue(), aint); legend.setBackgroundPaint(c); } //int //c = new Color(c.getRed(), c.getGreen(), c) } //Paint p = new Color(0xff,0xff,0xff,0); //legend.setBackgroundPaint(p); //legend.getBackgroundPaint() } Axis domain = null; if (PLOT_XY == ptype || PLOT_TIME == ptype || PLOT_HISTOGRAM == ptype) { domain = ret.getXYPlot().getDomainAxis(); } else if (PLOT_CATEGORY == ptype || PLOT_GANTT == ptype) { domain = ret.getCategoryPlot().getDomainAxis(); } if (false) { // experiment with PeriodAxis for timeseries if (PLOT_TIME.equals(ci.getPlotType())) { XYPlot xyplot = (XYPlot) ret.getPlot(); String lbl = xyplot.getDomainAxis().getLabel(); PeriodAxis pa = new PeriodAxis(lbl); xyplot.setDomainAxis(0, pa, true); //xyplot.setDomainAxis(pa); domain = pa; } } /* if (domain != null) { String domainAxisPrefix = ChartInfo.getDomainAxisPropertyPrefix(); BeanUtil.setProps(domain,opaqueProps,domainAxisPrefix+"."); if (domain instanceof ValueAxis) { ValueAxis va = (ValueAxis) domain; String autoRange = opaqueProps.get(domainAxisPrefix+".autoRange"); if (!"false".equals(autoRange)) { // autoRange is by default true // so if it not set, make sure we set it to override any minimum/maximum settings the user may have left in va.setAutoRange(true); } makeAxisTicksSane(va,ci); } } for (int i = 0; i < ChartConstants.MAX_RANGE_AXES; i++) { if (i != 0 && !ctxt.isRangeAxisVisible(i)) { continue; } AxisLocation location = ctxt.getRangeAxisLocation(i); ValueAxis range = ChartUtil.decodeValueAxis(ctxt.getRangeAxisType(i)); if (range == null && PLOT_GANTT != ptype) { range = new NumberAxis(); } if (PLOT_XY == ptype || PLOT_TIME == ptype || PLOT_HISTOGRAM == ptype) { ret.getXYPlot().setRangeAxis(i, range); if (location != null) { ret.getXYPlot().setRangeAxisLocation(i, location); } } else if (PLOT_CATEGORY == ptype) { ret.getCategoryPlot().setRangeAxis(i, range); if (location != null) { ret.getCategoryPlot().setRangeAxisLocation(i, location); } } else if (PLOT_GANTT == ptype) { if (location != null) { ret.getCategoryPlot().setRangeAxisLocation(i, location); } continue; } else { continue; } String rangeAxisPrefix = ChartInfo.getRangeAxisPropertyPrefix(i); BeanUtil.setProps(range,opaqueProps,rangeAxisPrefix+"."); String autoRange = opaqueProps.get(rangeAxisPrefix+".autoRange"); if (!"false".equals(autoRange)) { // autoRange is by default true // so if it not set, make sure we set it to override any minimum/maximum settings the user may have left in if (range != null) range.setAutoRange(true); } // sanity check tick size this.makeAxisTicksSane(range,ci); } */ Plot plot = ret.getPlot(); // there can be up to 4 default renderers, one per axis // if the user didn't specify per-series renderers but he chose to put the series // on different axes List<Object> defaultRenderers = new ArrayList<Object>(); if (plot instanceof XYPlot) { XYURLGenerator urlGen = new XYURLGenerator() { public String generateURL(XYDataset ds, int series, int item) { try { String seriesName = (String) ds.getSeriesKey(series); double x = ds.getXValue(series, item); double y = ds.getYValue(series, item); String ret = ctxt.getImageMapUrl(ds, seriesName, x, y); return ret; } catch (RuntimeException re) { re.printStackTrace(); throw re; } } }; // TimeSeriesURLGenerator urlGen = new TimeSeriesURLGenerator(); XYPlot xyplot = (XYPlot) plot; xyplot.setDatasetRenderingOrder(DatasetRenderingOrder.REVERSE); xyplot.setSeriesRenderingOrder(SeriesRenderingOrder.REVERSE); String defaultRenderType = ci.getRenderType(); if (defaultRenderType == null) { defaultRenderType = DEFAULT_XY_RENDERER; } for (int i = 0; i < ctxt.getDatasetCount(); i++) { XYDataset xyd = (XYDataset) ctxt.getDataset(i); String rname = ctxt.getRendererForDataset(i); XYItemRenderer render; if (rname != null && !rname.equals("")) { // empty string means use the default renderer // this DataSet has its own renderer, separate from the default for this chart render = getXYRenderer(rname); // set its custom renderer properties SimpleProps sp = ctxt.getRendererPropsForDataset(i); BeanUtil.setProps(render, sp, "renderer."); } else { // clone of default renderer render = getXYRenderer(defaultRenderType); defaultRenderers.add(render); } xyplot.setRenderer(i, render); xyplot.setDataset(i, xyd); render.setURLGenerator(urlGen); int axis = ctxt.getRangeAxisForDataset(i); xyplot.mapDatasetToRangeAxis(i, axis); } DrawingSupplier dsup = xyplot.getDrawingSupplier(); int[] seriesCounts = new int[ctxt.getDatasetCount()]; //for (int i = ctxt.getSeriesCount() - 1; i >= 0; i--) { for (int i = 0; i < ctxt.getSeriesCount(); i++) { SeriesDescriptor sd = ctxt.getSeries(i); // calculate the series paint regardless of whether the series is visible or not // to avoid having series colors be unnecessarily jostled due when series are // set visible or not visible Paint seriesPaint = null; String color = ctxt.getColor(i); if (color != null) { seriesPaint = ChartUtil.decodePaint(color); } if (seriesPaint == null) { seriesPaint = dsup.getNextPaint(); } if (seriesPaint instanceof Color) { // only encode the paints that are Color, not the GradientPaints since the front-end can't handle those colorMap.put(sd.getSid(), ChartUtil.encodeColor((Color) seriesPaint)); } if (!ctxt.isSeriesVisible(i)) { continue; } XYDataset xyd = (XYDataset) ctxt.getDatasetForSeries(sd); int dsIndex = ctxt.getIndexOfDataset(xyd); XYItemRenderer render = xyplot.getRenderer(dsIndex); if (sd.getSeriesNameFromData() == -1) { //p("setting series paint for #" + i + "/" + sd.getName() + " to " + seriesPaint // + " at index " + seriesCounts[dsIndex]); // set the series paint as long as we don't // have a dynamic number of series created from a data column render.setSeriesPaint(seriesCounts[dsIndex]++, seriesPaint); /*int ind = getIndexOfSeries(xyd, sd.getName()); if (ind != -1) { render.setSeriesPaint(ind, seriesPaint); } else { log.warn("cannot find series " + sd.getName() + " in dsIndex=" + dsIndex); } */ } // String prop(String defalt, SimpleProps props, String...names) { String lfix = "label." + i; if ("true".equals(extras.get("labels")) && !"false".equals(extras.get(lfix))) { String paintStr = prop("#ffffff", extras, lfix + ".paint", "label.paint"); String fontStr = prop("Arial-10", extras, lfix + ".font", "label.font"); String textAnchorStr = prop("CENTER", extras, lfix + ".textAnchor", "label.textAnchor"); String itemAnchorStr = prop("CENTER", extras, lfix + ".itemAnchor", "label.itemAnchor"); render.setBaseItemLabelGenerator(new StandardXYItemLabelGenerator()); render.setBaseItemLabelsVisible(true); render.setBaseItemLabelPaint(ChartUtil.decodePaint(paintStr)); Font f = Font.decode(fontStr); render.setBaseItemLabelFont(f); ItemLabelPosition pos = new ItemLabelPosition(ChartUtil.decodeItemLabelAnchor(itemAnchorStr), ChartUtil.decodeTextAnchor(textAnchorStr)); render.setBasePositiveItemLabelPosition(pos); } } for (int i = 0; i < ci.getMarkerCount(); i++) { MarkerDescriptor md = ci.getMarker(i); if (md.isVisible()) { this.addMarker(ctxt, md, xyplot, ctxt.getMarkerValue(i)); } } /* // try a pointer annotation if (false) { int annoItem = 8; XYDataset xyd = (XYDataset) ctxt.getDataset(0); if (xyd.getItemCount(0) > annoItem) { double x = xyd.getXValue(0, annoItem); double y = xyd.getYValue(0, annoItem); XYPointerAnnotation pointer = new XYPointerAnnotation( "Data Point Eight", x, y, 0); pointer.setAngle(-1); pointer.setLabelOffset(pointer.getLabelOffset() + 10); xyplot.addAnnotation(pointer); } } */ } else if (plot instanceof CategoryPlot) { CategoryPlot cplot = (CategoryPlot) plot; cplot.setDatasetRenderingOrder(DatasetRenderingOrder.REVERSE); String defaultRenderType = ci.getRenderType(); if (defaultRenderType == null) { defaultRenderType = DEFAULT_CATEGORY_RENDERER; } CategoryURLGenerator urlGen = new CategoryURLGenerator() { public String generateURL(CategoryDataset ds, int series, int item) { // reminder: rows are series, columns are categories try { String sname = (String) ds.getRowKey(series); Comparable xValue = ds.getColumnKey(item); Number value = ds.getValue(series, item); String ret = null; if (value != null) ret = ctxt.getImageMapUrl(ds, sname, xValue, value.doubleValue()); //p("caturl: series=" + series + "/" + sname + " item=" + item + "/" + ret); return ret; //return ctxt.getItemURL(ds, series, item); } catch (RuntimeException re) { re.printStackTrace(); throw re; } } }; /* We create a dataset and renderer for each distinct (renderType,rangeAxis) * SeriesDescriptor tuple on the chart. Each series is then mapped to a * series paint within the renderer. We create a default renderer for each * of the possible range axes. Note that each of these renderers needn't * actually be used.... */ for (int i = 0; i < ChartConstants.MAX_RANGE_AXES; i++) { CategoryItemRenderer defaultCategoryRenderer = getCategoryRenderer(defaultRenderType); //cplot.setRenderer(defaultCategoryRenderer); defaultCategoryRenderer.setBaseItemURLGenerator(urlGen); defaultRenderers.add(defaultCategoryRenderer); } for (int i = 0; i < ctxt.getDatasetCount(); i++) { CategoryDataset cd = (CategoryDataset) ctxt.getDataset(i); String rname = ctxt.getRendererForDataset(i); CategoryItemRenderer render; if (rname != null && !rname.equals("") && !rname.equals(ci.getRenderType())) { // empty string means use the default renderer // this DataSet has its own renderer, separate from the default for this chart render = getCategoryRenderer(rname); // set its custom renderer properties SimpleProps sp = ctxt.getRendererPropsForDataset(i); BeanUtil.setProps(render, sp, "renderer."); // set url gen render.setBaseItemURLGenerator(urlGen); } else { int axis = ctxt.getRangeAxisForDataset(i); render = (CategoryItemRenderer) defaultRenderers.get(axis); } cplot.setRenderer(i, render); cplot.setDataset(i, cd); int axis = ctxt.getRangeAxisForDataset(i); cplot.mapDatasetToRangeAxis(i, axis); } DrawingSupplier dsup = cplot.getDrawingSupplier(); // keep track of how many series are attached to each renderer int[] seriesCounts = new int[ctxt.getDatasetCount()]; //for (int i = ctxt.getSeriesCount() - 1; i >= 0; i--) { for (int i = 0; i < ctxt.getSeriesCount(); i++) { SeriesDescriptor sd = ci.getSeriesDescriptor(i); // calculate the series paint regardless of whether the series is visible or not // to avoid having series colors be unnecessarily jostled due when series are // set visible or not visible Paint seriesPaint = null; String color = ctxt.getColor(i); if (color != null) { seriesPaint = ChartUtil.decodePaint(color); } if (seriesPaint == null) { seriesPaint = dsup.getNextPaint(); } if (seriesPaint instanceof Color) { // only encode the paints that are Color, not the GradientPaints since the front-end can't handle those colorMap.put(sd.getSid(), ChartUtil.encodeColor((Color) seriesPaint)); } if (!ctxt.isSeriesVisible(i)) { continue; } CategoryDataset cd = (CategoryDataset) ctxt.getDatasetForSeries(sd); int dsIndex = ctxt.getIndexOfDataset(cd); CategoryItemRenderer renderer = cplot.getRenderer(dsIndex); renderer.setSeriesPaint(seriesCounts[dsIndex]++, seriesPaint); } List<MarkerDescriptor> markers = ci.getMarkers(); for (int i = 0; i < markers.size(); i++) { MarkerDescriptor md = markers.get(i); if ((md.getType() == MarkerDescriptor.MARKER_TYPE_NUMERIC || md.getType() == MarkerDescriptor.MARKER_TYPE_NUMERIC_INTERVAL) && md.isVisible() && md.isRange() && ctxt.isRangeAxisVisible(md.getAxisIndex())) { if (md.getType() == MarkerDescriptor.MARKER_TYPE_NUMERIC) { //Marker m = this.createValueMarker(md,markerValues.get(i)); Marker m = this.createValueMarker(md, ctxt.getMarkerValue(i)); if (m != null) { Layer l; l = ChartUtil.decodeLayer(md.getLayer(), Layer.BACKGROUND); cplot.addRangeMarker(md.getAxisIndex(), m, l); } } else { //MarkerValue mv = markerValues.get(i); MarkerValue mv = ctxt.getMarkerValue(i); this.addIntervalMarker(cplot, md, mv); } } } } else if (plot instanceof PiePlot) { PiePlot pplot = (PiePlot) plot; // only one dataset for pie plots, use the first one in the dsmap. if (ctxt.getSeriesCount() > 0) { Dataset ds = ctxt.getDataset(0); if (ds == null) { log.warn("unexpected null data set for pie plot"); } else { pplot.setDataset((PieDataset) ds); final String sname = ctxt.getSeries(0).getName(); PieURLGenerator purl = new PieURLGenerator() { public String generateURL(PieDataset pds, Comparable comp, int pieIndex) { String url = null; Number value = pds.getValue(comp); if (value != null) url = ctxt.getImageMapUrl(pds, sname, comp, value.doubleValue()); return url; } }; pplot.setURLGenerator(purl); String pieLabelFormat = opaqueProps.get("plot." + PieItemLabelFormats.PIE_ITEM_FORMAT_PROPERTY); pplot.setLabelGenerator(getPieLabelGenerator(pieLabelFormat)); } } else { log.warn("no visible data set for pie plot"); } } BeanUtil.setProps(ret, opaqueProps, "chart."); BeanUtil.setProps(plot, opaqueProps, "plot."); for (Object renderer : defaultRenderers) { BeanUtil.setProps(renderer, opaqueProps, "renderer."); } for (int i = 0; i < ci.getMarkers().size(); i++) { MarkerDescriptor md = ci.getMarkers().get(i); SimpleProps markerProps = md.getMarkerProps(); if (markerProps != null) { String pstr = markerProps.get("paint"); if (pstr != null) { colorMap.put(md.getSid(), pstr); } } } final LegendItemCollection lcoll = new LegendItemCollection(); // add interval markers to the legend if (plot instanceof XYPlot || plot instanceof CategoryPlot) { List<MarkerDescriptor> markers = ci.getMarkers(); for (int i = 0; i < markers.size(); i++) { MarkerDescriptor md = markers.get(i); if (md.isInterval() && md.isVisible()) { // SimpleProps markerProps = md.getMarkerProps(); // String label = markerProps.get("label"); String label = md.getLabel(); if (label == null) label = md.getName(); if (label == null) label = ""; //String pstr = markerProps.get("paint"); String pstr = md.getPaint(); if (pstr == null) { pstr = "#808080"; // default for IntervalMarker } colorMap.put(md.getSid(), pstr); float alpha = 0.8f; // default for IntervalMarker //String fstr = markerProps.get("alpha"); String fstr = md.getAlpha(); if (fstr != null) { try { alpha = Float.parseFloat(fstr); } catch (NumberFormatException ignore) { } } Paint paint = ChartUtil.decodeColor(pstr, alpha); LegendItem le = new LegendItem(label, null, null, null, ShapeUtil.SQUARE, paint); lcoll.add(le); } } if (lcoll.getItemCount() > 0) { LegendItemSource src = new LegendItemSource() { public LegendItemCollection getLegendItems() { return lcoll; } }; LegendTitle lt = ret.getLegend(); LegendItemSource[] srcs = lt.getSources(); LegendItemSource[] tmp = null; if (srcs == null) { tmp = new LegendItemSource[1]; tmp[0] = src; } else { tmp = new LegendItemSource[srcs.length + 1]; System.arraycopy(srcs, 0, tmp, 1, srcs.length); tmp[0] = src; } lt.setSources(tmp); } } // LegendTitle lt = ret.getLegend(); // if (lt != null && !opaqueProps.containsKey("legend.itemFont")) { // // smaller fonts in legend if user didn't set it // lt.setItemFont(SUBTITLE_FONT); // } if (domain != null) { String domainAxisPrefix = ChartInfo.getDomainAxisPropertyPrefix(); BeanUtil.setProps(domain, opaqueProps, domainAxisPrefix + "."); if (domain instanceof ValueAxis) { ValueAxis va = (ValueAxis) domain; String autoRange = opaqueProps.get(domainAxisPrefix + ".autoRange"); if (!"false".equals(autoRange)) { // autoRange is by default true // so if it not set, make sure we set it to override any minimum/maximum settings the user may have left in va.setAutoRange(true); } makeAxisTicksSane(va, ci); } } for (int i = 0; i < ChartConstants.MAX_RANGE_AXES; i++) { if (!ctxt.isRangeAxisVisible(i)) { if (i == 0) { if (PLOT_XY == ptype || PLOT_TIME == ptype || PLOT_HISTOGRAM == ptype) { ret.getXYPlot().getRangeAxis(0).setVisible(false); } else if (PLOT_CATEGORY == ptype || PLOT_GANTT == ptype) { ret.getCategoryPlot().getRangeAxis(0).setVisible(false); } } continue; } AxisLocation location = ctxt.getRangeAxisLocation(i); ValueAxis range = ChartUtil.decodeValueAxis(ctxt.getRangeAxisType(i)); if (range == null && PLOT_GANTT != ptype) { range = new NumberAxis(); } if (PLOT_XY == ptype || PLOT_TIME == ptype || PLOT_HISTOGRAM == ptype) { ret.getXYPlot().setRangeAxis(i, range); if (location != null) { ret.getXYPlot().setRangeAxisLocation(i, location); } } else if (PLOT_CATEGORY == ptype) { ret.getCategoryPlot().setRangeAxis(i, range); if (location != null) { ret.getCategoryPlot().setRangeAxisLocation(i, location); } } else if (PLOT_GANTT == ptype) { if (location != null) { ret.getCategoryPlot().setRangeAxisLocation(i, location); } continue; } else { continue; } String rangeAxisPrefix = ChartInfo.getRangeAxisPropertyPrefix(i); BeanUtil.setProps(range, opaqueProps, rangeAxisPrefix + "."); String autoRange = opaqueProps.get(rangeAxisPrefix + ".autoRange"); if (!"false".equals(autoRange)) { // autoRange is by default true // so if it not set, make sure we set it to override any minimum/maximum settings the user may have left in if (range != null) range.setAutoRange(true); } // sanity check tick size this.makeAxisTicksSane(range, ci); } return ret; } private static String prop(String defalt, SimpleProps props, String... names) { for (String name : names) { String val = props.get(name); if (val != null) return val; } return defalt; } private HashMap<String, StandardPieSectionLabelGenerator> pieLabelGeneratorsMap = new HashMap<String, StandardPieSectionLabelGenerator>(); /** * return the StandardPieLabelGenerator based on the string setting (one of the legal PieItemLabelFormats values) * @param s * @return */ private StandardPieSectionLabelGenerator getPieLabelGenerator(String s) { if (s == null) s = PieItemLabelFormats.DEFAULT.toString(); StandardPieSectionLabelGenerator gen = pieLabelGeneratorsMap.get(s); if (gen == null) { DecimalFormat numberFormat = new DecimalFormat("##"); DecimalFormat number2Format = new DecimalFormat("##.##"); DecimalFormat percentFormat = new DecimalFormat("##%"); DecimalFormat percent2Format = new DecimalFormat("##.##%"); PieItemLabelFormats pfmt = PieItemLabelFormats.get(s); if (pfmt == null) { pfmt = PieItemLabelFormats.DEFAULT; } switch (pfmt) { case NAME_ONLY: gen = new StandardPieSectionLabelGenerator("{0}"); break; case NAME_PERCENTAGE: gen = new StandardPieSectionLabelGenerator("{0} {2}", numberFormat, percentFormat); break; case NAME_PERCENTAGE2: gen = new StandardPieSectionLabelGenerator("{0} {2}", numberFormat, percent2Format); break; case NAME_PERCENTAGE2_IN_PAREN: gen = new StandardPieSectionLabelGenerator("{0} ({2})", numberFormat, percent2Format); break; case NAME_PERCENTAGE_IN_PAREN: gen = new StandardPieSectionLabelGenerator("{0} ({2})", numberFormat, percentFormat); break; case NAME_EQ_VALUE2_PERCENTAGE2: gen = new StandardPieSectionLabelGenerator("{0} = {1} ({2})", number2Format, percent2Format); break; case NAME_EQ_VALUE_PERCENTAGE: gen = new StandardPieSectionLabelGenerator("{0} = {1} ({2})", numberFormat, percentFormat); break; case PERCENTAGE2_ONLY: gen = new StandardPieSectionLabelGenerator("{2}", numberFormat, percent2Format); break; case PERCENTAGE_ONLY: gen = new StandardPieSectionLabelGenerator("{2}", numberFormat, percentFormat); break; case VALUE2_ONLY: gen = new StandardPieSectionLabelGenerator("{1}", number2Format, percentFormat); break; case VALUE_ONLY: gen = new StandardPieSectionLabelGenerator("{1}", numberFormat, percentFormat); break; } pieLabelGeneratorsMap.put(s, gen); } return gen; } /** * add a marker to the xyplot * @param ci * @param dsmap * @param md * @param xyplot * @param mv */ private void addMarker(ChartContext ctxt, MarkerDescriptor md, XYPlot xyplot, MarkerValue mv) { if (!md.isInterval()) { Marker marker = createValueMarker(md, mv); if (marker != null) { Layer layer; layer = ChartUtil.decodeLayer(md.getLayer(), Layer.BACKGROUND); if (md.isRange()) { if (ctxt.isRangeAxisVisible(md.getAxisIndex())) { xyplot.addRangeMarker(md.getAxisIndex(), marker, layer); } } else { xyplot.addDomainMarker(0, marker, layer); } } } else { this.addIntervalMarker(xyplot, md, mv); } } private void addIntervalMarker(Plot plot, MarkerDescriptor md, MarkerValue mv) { CategoryPlot cplot = null; XYPlot xyplot = null; if (plot instanceof CategoryPlot) { cplot = (CategoryPlot) plot; } else if (plot instanceof XYPlot) { xyplot = (XYPlot) plot; } Layer layer; layer = ChartUtil.decodeLayer(md.getLayer(), Layer.BACKGROUND); SimpleProps markerProps = md.getMarkerProps(); List<double[]> valList = mv.getValues(); for (int j = 0; j < valList.size(); j++) { double[] vals = valList.get(j); if (vals.length >= 2 && vals[0] <= vals[1]) { IntervalMarker im = new IntervalMarker(vals[0], vals[1]); setMarkerInsets(im); BeanUtil.setProps(im, markerProps, null); // use no label if (!md.isLabelVisible()) { // no label im.setLabel(null); } if (cplot != null) { if (md.isRange()) { cplot.addRangeMarker(md.getAxisIndex(), im, layer); } else { // FIXME: does domain (interval) marker on category plot even work? } } else if (xyplot != null) { if (md.isRange()) { xyplot.addRangeMarker(md.getAxisIndex(), im, layer); } else { xyplot.addDomainMarker(im, layer); } } } } } private void setMarkerInsets(Marker marker) { RectangleInsets ri = new RectangleInsets(10, 10, 10, 10); marker.setLabelOffset(ri); } private ValueMarker createValueMarker(MarkerDescriptor md, MarkerValue mv) { ValueMarker marker = null; List<double[]> vals = mv.getValues(); if (vals != null && vals.size() > 0) { double value = vals.get(0)[0]; marker = new ValueMarker(value); setMarkerInsets(marker); marker.setLabel(md.getName()); BeanUtil.setProps(marker, md.getMarkerProps(), null); } return marker; } // private int getIndexOfSeries(SeriesDataset xyd, String seriesName) { // int ret = -1; // for (int i = 0; seriesName != null && i < xyd.getSeriesCount(); i++) { // Object sk = xyd.getSeriesKey(i); // // comparing the series name is not optimal since series name is not unique // if (seriesName.equals(sk)) { // ret = i; // break; // } // } // return ret; // } private static String DEFAULT_XY_RENDERER = DefaultXYItemRenderer.class.getName(); private XYItemRenderer getXYRenderer(String type) { XYItemRenderer ret = null; String cname = typeSystem.getXYRenderer(type); if (cname == null) { log.warn("unknown XYRenderer '" + type + "' using default XY renderer"); cname = DEFAULT_XY_RENDERER; //throw new RuntimeException("unknown XYRenderer '" + type + "' using default XY renderer"); } try { if (cname.endsWith("XYBubbleRenderer")) { ret = new XYBubbleRenderer(XYBubbleRenderer.SCALE_ON_RANGE_AXIS); // } else if ("CandlestickRenderer".equals(type)) { // ret = new CandlestickRenderer(); } else if ("Line".equals(type)) { // yes lines, no shapes ret = new XYLineAndShapeRenderer(true, false); } else { Class c = Class.forName(cname); ret = (XYItemRenderer) c.newInstance(); } } catch (Exception e) { e.printStackTrace(); log.error("failed to instantiate " + cname, e); throw new RuntimeException("Cannot instantiate: " + cname, e); } return ret; } private static String DEFAULT_CATEGORY_RENDERER = BarRenderer.class.getName(); private CategoryItemRenderer getCategoryRenderer(String type) { CategoryItemRenderer ret = null; String cname = typeSystem.getCatRenderer(type); if (cname == null) { log.warn("unknown CategoryRenderer '" + type + "' using default category renderer"); cname = DEFAULT_CATEGORY_RENDERER; //throw new RuntimeException("unknown CategoryRenderer '" + type + "' using default category renderer"); } try { Class c = Class.forName(cname); ret = (CategoryItemRenderer) c.newInstance(); } catch (Exception e) { log.error("failed to instantiate " + cname, e); throw new RuntimeException("Cannot instantiate: " + cname, e); } return ret; } /* re-throw RuntimeException as-is * wrap checked exceptions */ private void handleException(String msg, Exception e) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } if (msg == null) { msg = e.getMessage(); } throw new RuntimeException(msg, e); } // // in order to force auto ticks to be no smaller than the timePeriod, // we have to set the TickUnitSource for the DateAxis accordingly b/c JFreeChart chooses from the TickUnitSource // when calculating the auto ticks at render time. We have no earlier opportunity to intercept and change it // // a bit cumbersome, but the effect of this is to allow auto tick to do its thing normally, and then when the range is small // to ensure that we never have ticks that is smaller than the timePeriod of the chart // // logic adding of the ticks derived from JfreeChart code, see org.jfree.chart.axis.DateAxis private TickUnits yearTicks = null; private TickUnits monthTicks = null; private TickUnits qtrTicks = null; private TickUnits weekTicks = null; private TickUnits getYearTicks() { if (yearTicks == null) { TickUnits units = new TickUnits(); yearTicks = addYearlyTicks(units); } return yearTicks; } private TickUnits getMonthTicks() { if (monthTicks == null) { TickUnits units = new TickUnits(); units = addMonthlyTicks(units); monthTicks = addYearlyTicks(units); } return monthTicks; } private TickUnits getQuarterTicks() { if (qtrTicks == null) { TickUnits units = new TickUnits(); units = addQuarterlyTicks(units); qtrTicks = addYearlyTicks(units); } return qtrTicks; } private TickUnits getWeekTicks() { if (weekTicks == null) { TickUnits units = new TickUnits(); units = addWeeklyTicks(units); weekTicks = addYearlyTicks(units); } return weekTicks; } private TickUnits addWeeklyTicks(TickUnits units) { DateFormat f5 = new SimpleDateFormat("d-MMM"); // days units.add(new DateTickUnit(DateTickUnit.DAY, 7, DateTickUnit.HOUR, 1, f5)); units.add(new DateTickUnit(DateTickUnit.DAY, 14, DateTickUnit.HOUR, 1, f5)); units.add(new DateTickUnit(DateTickUnit.DAY, 21, DateTickUnit.DAY, 1, f5)); return units; } private TickUnits addMonthlyTicks(TickUnits units) { DateFormat f6 = new SimpleDateFormat("MMM-yyyy"); // months units.add(new DateTickUnit(DateTickUnit.MONTH, 1, DateTickUnit.DAY, 1, f6)); units.add(new DateTickUnit(DateTickUnit.MONTH, 2, DateTickUnit.DAY, 1, f6)); units.add(new DateTickUnit(DateTickUnit.MONTH, 3, DateTickUnit.MONTH, 1, f6)); units.add(new DateTickUnit(DateTickUnit.MONTH, 4, DateTickUnit.MONTH, 1, f6)); units.add(new DateTickUnit(DateTickUnit.MONTH, 6, DateTickUnit.MONTH, 1, f6)); return units; } private TickUnits addQuarterlyTicks(TickUnits units) { DateFormat f6 = new SimpleDateFormat("MMM-yyyy"); // months units.add(new DateTickUnit(DateTickUnit.MONTH, 3, DateTickUnit.DAY, 1, f6)); units.add(new DateTickUnit(DateTickUnit.MONTH, 6, DateTickUnit.MONTH, 1, f6)); units.add(new DateTickUnit(DateTickUnit.MONTH, 9, DateTickUnit.MONTH, 1, f6)); return units; } private TickUnits addYearlyTicks(TickUnits units) { DateFormat f7 = new SimpleDateFormat("yyyy"); // years units.add(new DateTickUnit(DateTickUnit.YEAR, 1, DateTickUnit.MONTH, 1, f7)); units.add(new DateTickUnit(DateTickUnit.YEAR, 2, DateTickUnit.MONTH, 3, f7)); units.add(new DateTickUnit(DateTickUnit.YEAR, 5, DateTickUnit.YEAR, 1, f7)); units.add(new DateTickUnit(DateTickUnit.YEAR, 10, DateTickUnit.YEAR, 1, f7)); units.add(new DateTickUnit(DateTickUnit.YEAR, 25, DateTickUnit.YEAR, 5, f7)); units.add(new DateTickUnit(DateTickUnit.YEAR, 50, DateTickUnit.YEAR, 10, f7)); units.add(new DateTickUnit(DateTickUnit.YEAR, 100, DateTickUnit.YEAR, 20, f7)); return units; } /** * make sure that our axis ticks are reasonable. * ensure that there aren't too many, and ensure that ticks are no shorter than the timePeriod of timeseries plots * @param va * @param ci */ private void makeAxisTicksSane(ValueAxis va, ChartInfo ci) { if (va == null) { return; } if (va instanceof DateAxis) { DateAxis da = (DateAxis) va; DateTickUnit dtu = da.getTickUnit(); if (dtu != null) { if (da.isAutoTickUnitSelection()) { switch (ci.getTimePeriod()) { case TimeConstants.TIME_YEAR: da.setStandardTickUnits(getYearTicks()); break; case TimeConstants.TIME_QUARTER: da.setStandardTickUnits(getQuarterTicks()); break; case TimeConstants.TIME_MONTH: da.setStandardTickUnits(getMonthTicks()); break; case TimeConstants.TIME_WEEK: da.setStandardTickUnits(getWeekTicks()); break; default: // don't worry about smaller time units break; } } Range rng = da.getRange(); double upper = rng.getUpperBound(); double lower = rng.getLowerBound(); double tick = dtu.getSize(); double width = Math.abs(upper - lower); double nticks = width / tick; if (!da.isAutoTickUnitSelection() && tick != 1.0 && nticks > 100.0) { // for now, just pick a really big unit like YEAR da.setTickUnit(new DateTickUnit(DateTickUnit.YEAR, 1)); } // DateRange dr = (DateRange) da.getRange(); // log.warn("*** dr.getLowerDate() = " + dr.getLowerDate() + // " dr.getUpperDate() = " + dr.getUpperDate() + // " da.getMinimumDate() = " + da.getMinimumDate() + // " da.getMaximumDate() = " + da.getMaximumDate() + // " da.getAutoRangeMinimumSize() = " + da.getAutoRangeMinimumSize()); // // tick = dtu.getSize(); // width = Math.abs(upper - lower); // nticks = width / tick; // log.warn("***** dtu = " + dtu + " rng = " + rng + " upper = " + upper + // " lower = " + lower + " tick = " + dtu.getSize() + " nticks = " + nticks + // "da.isAutoTickUnitSelection = " + da.isAutoTickUnitSelection() + // "chart time period = " + ci.getTimePeriod()); } } else { Range rng = va.getRange(); double lower = rng.getLowerBound(); double upper = rng.getUpperBound(); if (va instanceof NumberAxis) { NumberAxis na = (NumberAxis) va; double tick = na.getTickUnit().getSize(); if (na.isAutoTickUnitSelection()) { if (tick != 1.0d) { // give the default tick size, which is 1 na.setTickUnit(new NumberTickUnit(1)); } } else { double width = Math.abs(upper - lower); double nticks = width / tick; if (!na.isAutoTickUnitSelection() && tick != 1.0 && nticks > 50.0) { double newtick = width / 20; na.setTickUnit(new NumberTickUnit(newtick)); } } } } } /** * write a chart to an output stream in Encapsulated PostScript format * @param out * @param chart * @param width * @param height * @throws IOException */ public void writeChartAsPS(OutputStream out, JFreeChart chart, int width, int height) throws IOException { // instantiate a EPSDocumentGraphics2D instance // this is the code for using org.apache.xmlgraphics but the results seem inferior to the freeHEP // EPSDocumentGraphics2D g2d = new EPSDocumentGraphics2D(false); // g2d.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); // // g2d.setupDocument(out,width,height); // Rectangle2D r2D = new Rectangle2D.Double(0, 0, width, height); // chart.draw(g2d, r2D); // g2d.finish(); // g2d.dispose(); PSGraphics2D g2d = new PSGraphics2D(out, new Dimension(width, height)); g2d.startExport(); Rectangle2D r2D = new Rectangle2D.Double(0, 0, width, height); chart.draw(g2d, r2D); g2d.endExport(); g2d.closeStream(); } /** * write out the chart as a EMF (Microsoft Enhanced MetaFormat file) * @param out * @param chart * @param width * @param height * @throws IOException */ public void writeChartAsEMF(OutputStream out, JFreeChart chart, int width, int height) throws IOException { EMFGraphics2D g2d = new EMFGraphics2D(out, new Dimension(width, height)); g2d.startExport(); Rectangle2D r2D = new Rectangle2D.Double(0, 0, width, height); chart.draw(g2d, r2D); g2d.endExport(); g2d.closeStream(); } /** * Writes a chart to an output stream in PDF format. * * @param out the output stream. * @param chart the chart. * @param width the chart width. * @param height the chart height. * */ public void writeChartAsPDF(OutputStream out, JFreeChart chart, int width, int height, FontMapper mapper) { com.lowagie.text.Rectangle pagesize = new com.lowagie.text.Rectangle(width, height); Document document = new Document(pagesize, 50.0f, 50.0f, 50.0f, 50.0f); try { PdfWriter writer = PdfWriter.getInstance(document, out); document.addAuthor("ChartMechanic"); document.addSubject("ChartMechanic PDF"); document.open(); PdfContentByte cb = writer.getDirectContent(); PdfTemplate tp = cb.createTemplate(width, height); Graphics2D g2 = tp.createGraphics(width, height, mapper); Rectangle2D r2D = new Rectangle2D.Double(0, 0, width, height); chart.draw(g2, r2D); g2.dispose(); cb.addTemplate(tp, 0, 0); } catch (DocumentException de) { System.err.println(de.getMessage()); } document.close(); } private static void p(String s) { System.out.println("[JFreeChartDriver] " + s); } /** * set the watermark chart logo. if null, then clear it and use no logo * @param u */ public void setDefaultChartLogo(LogoInfo logo) { defaultLogo = logoinfo2chartlogo(logo); // defaultLogo.visible = false; // if (logo.isVisible()) { // if (logo.getTxt() != null) { // defaultLogo.txt = logo.getTxt(); // } else { // String urlStr = logo.getUrl(); // try { // if (urlStr != null) { // URL u; // if (urlStr.startsWith("/com/bayareasoftware/chartengine")) { // // if the URL starts with /com/bayareasoftware/chartengine // // treat it as a resource // u = JFreeChartDriver.class.getResource(urlStr); // } else { // // otherwise, try to make a URL out of the string // if (!(urlStr.startsWith("vfs:")) && !(urlStr.contains(":"))) { // // if it's just a flat string, look up the file name from // // vfs:admin/images/ // urlStr = "vfs:admin/images/" + urlStr; // } // // u = new URL(urlStr); // } // log.info("setting chart logo to " + u.toExternalForm()); // // ImageIcon ii = new ImageIcon(u); // defaultLogo.img = ii.getImage(); // defaultLogo.width = ii.getIconWidth(); // defaultLogo.height = ii.getIconHeight(); // // if (defaultLogo.width > 10 && defaultLogo.height > 10) { // int w = defaultLogo.width*2/3; // int h = defaultLogo.height*2/3; // // defaultLogo.smallImg = null; // // // instead of using getScaledInstance, which is asynchronous // // use the scaling version of Graphics2D.drawImage. // // it is also asynchronous but we can add set an ImageObserver on it // BufferedImage bi = new BufferedImage(w,h,BufferedImage.TYPE_INT_ARGB); // Graphics2D g2 = bi.createGraphics(); // // g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, // RenderingHints.VALUE_INTERPOLATION_BICUBIC); // boolean ok = g2.drawImage(defaultLogo.img,0,0,w,h,new ImageObserver() { // public boolean imageUpdate(Image img, int infoflags, int x, // int y, int width, int height) { // defaultLogo.smallImg = img; // return false; // } // // }); // if (ok) { // defaultLogo.smallImg = bi; // } // } // } // } catch (MalformedURLException e) { // log.error("Malformed url: " + urlStr + " for chart_logo"); // return; // } // } // defaultLogo.visible = true; // } } /* //@Override public void setChartLogo(URL u) { watermarkURL = u; if (u != null) { ImageIcon ii = new ImageIcon(u); watermarkImage = ii.getImage(); watermark_width = ii.getIconWidth(); watermark_height = ii.getIconHeight(); if (watermark_width > 10 && watermark_height > 10) { int w = watermark_width*2/3; int h = watermark_height*2/3; //Image reducedImage = watermarkImage.getScaledInstance(w,h,Image.SCALE_SMOOTH); smallWatermarkImage = null; // instead of using getScaledInstance, which is asynchronous // use the scaling version of Graphics2D.drawImage. // it is also asynchronous but we can add set an ImageObserver on it BufferedImage bi = new BufferedImage(w,h,BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = bi.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); boolean ok = g2.drawImage(watermarkImage,0,0,w,h,new ImageObserver() { public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { smallWatermarkImage = img; return false; } }); if (ok) { smallWatermarkImage = bi; } } } else { watermarkImage = null; watermark_width = 0; watermark_height = 0; smallWatermarkImage = null; } } */ // public URL getChartLogoURL() { // return watermarkURL; // } }