Java tutorial
/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * Copyright (c) 2002-2017 Hitachi Vantara.. All rights reserved. */ package org.pentaho.chart.plugin.jfreechart.chart; import java.awt.Color; import java.awt.Font; import java.awt.Paint; import java.util.ArrayList; import java.util.Arrays; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.AxisLocation; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.block.BlockBorder; import org.jfree.chart.labels.ItemLabelAnchor; import org.jfree.chart.labels.ItemLabelPosition; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.Plot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.renderer.category.AreaRenderer; import org.jfree.chart.renderer.category.BarRenderer; import org.jfree.chart.renderer.category.BarRenderer3D; import org.jfree.chart.renderer.category.CategoryItemRenderer; import org.jfree.chart.renderer.category.GroupedStackedBarRenderer; import org.jfree.chart.renderer.category.LayeredBarRenderer; import org.jfree.data.category.DefaultCategoryDataset; import org.jfree.ui.StandardGradientPaintTransformer; import org.jfree.ui.TextAnchor; import org.pentaho.chart.ChartDocumentContext; import org.pentaho.chart.ChartUtils; import org.pentaho.chart.core.AxisSeriesLinkInfo; import org.pentaho.chart.core.ChartDocument; import org.pentaho.chart.core.ChartElement; import org.pentaho.chart.core.ChartSeriesDataLinkInfo; import org.pentaho.chart.css.keys.ChartStyleKeys; import org.pentaho.chart.css.styles.ChartAxisLocationType; import org.pentaho.chart.css.styles.ChartOrientationStyle; import org.pentaho.chart.data.ChartTableModel; import org.pentaho.chart.plugin.api.ChartItemLabelGenerator; import org.pentaho.chart.plugin.jfreechart.dataset.DatasetGeneratorFactory; import org.pentaho.chart.plugin.jfreechart.utils.ColorFactory; import org.pentaho.chart.plugin.jfreechart.utils.CylinderRenderer; import org.pentaho.chart.plugin.jfreechart.utils.JFreeChartUtils; import org.pentaho.reporting.libraries.css.dom.LayoutStyle; import org.pentaho.reporting.libraries.css.keys.border.BorderStyleKeys; import org.pentaho.reporting.libraries.css.keys.color.ColorStyleKeys; import org.pentaho.reporting.libraries.css.values.CSSNumericValue; import org.pentaho.reporting.libraries.css.values.CSSStringValue; import org.pentaho.reporting.libraries.css.values.CSSValue; /** * Top Level class that is extended by different chart type creators. * * <pre> * JFreeChartGenerator * | * ----------------------------------------------------------------------------- * | | | * JFreeBarChartGenerator .... ... * | * ------------------------------------------------------- * | | | | * JFreeStackedGen JFreeLayeredGen JFreeCylinderGen JFreeDefault * | * --------------------------- * | | * JFreeStackedPercent JFreeStacked100Percent * </pre> * * Author: Ravi Hasija * Date: May 14, 2008 * Time: 4:24:46 PM * To change this template use File | Settings | File Templates. */ public abstract class JFreeChartGenerator implements IJFreeChartGenerator { protected final DatasetGeneratorFactory datasetGeneratorFactory = new DatasetGeneratorFactory(); /** * Gets the title of the chart defined in the chartDocument * </p> * @param chartDocument - ChartDocument that defines what the series should look like * @return String - the title */ public static String getTitle(final ChartDocument chartDocument) { final ChartElement[] children = chartDocument.getRootElement() .findChildrenByName(ChartElement.TAG_NAME_TITLE); //$NON-NLS-1$ if (children != null && children.length > 0) { return children[0].getText(); } return null; } /** * Returns a boolean value that indicates if the chart should generate tooltips * </p> * @param chartDocument - ChartDocument that defines what the series should look like * @return true if we want to show tool tips */ public static boolean getShowToolTips(final ChartDocument chartDocument) { // TODO determine this from the chartDocument return true; } /** * Returns the ValueCategoryLabel of the chart. * </p> * @param chartDocument - ChartDocument that defines what the series should look like * @return String - the value category label */ public static String getValueCategoryLabel(final ChartDocument chartDocument) { final ChartElement[] children = chartDocument.getRootElement() .findChildrenByName(ChartElement.TAG_NAME_DOMAIN_LABEL); //$NON-NLS-1$ if (children != null && children.length > 0) { return children[0].getText(); } return null; } /** * Returns the ValueAxisLabel of the chart. * * @param chartDocument - ChartDocument that defines what the series should look like * @return String - the value axis label */ public static String getValueAxisLabel(final ChartDocument chartDocument) { final ChartElement[] children = chartDocument.getRootElement() .findChildrenByName(ChartElement.TAG_NAME_RANGE_LABEL); //$NON-NLS-1$ if (children != null && children.length > 0) { return children[0].getText(); } return null; } /** * @param chartDocument - ChartDocument that defines what the series should look like * @return a boolean that indicates of if a legend should be included in the chart */ public static boolean getShowLegend(final ChartDocument chartDocument) { final ChartElement[] children = chartDocument.getRootElement() .findChildrenByName(ChartElement.TAG_NAME_LEGEND); //$NON-NLS-1$ return (children != null) && (children.length > 0); } /** * Returns the plot orientation (horizontal or vertical) for the current chart. * </p> * @param chartDocument that contains a orientation on the Plot element * @return PlotOrientation.VERTICAL or .HORIZONTAL or Null if not defined. */ public PlotOrientation getPlotOrientation(final ChartDocument chartDocument) { PlotOrientation plotOrient = null; final ChartElement plotElement = chartDocument.getPlotElement(); if (plotElement != null) { final LayoutStyle layoutStyle = plotElement.getLayoutStyle(); final CSSValue value = layoutStyle.getValue(ChartStyleKeys.ORIENTATION); if (ChartOrientationStyle.VERTICAL.equals(value)) { plotOrient = PlotOrientation.VERTICAL; } else if (ChartOrientationStyle.HORIZONTAL.equals(value)) { plotOrient = PlotOrientation.HORIZONTAL; } } return plotOrient; } /** * Returns custom dataset based on certain column positions. The column positions are retrieved by iterating * over series elements looking for a specific/given axis id. * </p> * @param chartDocContext Chart document context object for the current chart document. * @param axisElement Current axis element. * @param axisSeriesLinkInfo Holds information that links the axis id to series element(s). * @return DefaultCategoryDataset that has information from specific column positions. */ public Integer[] getColumPositions(final ChartDocumentContext chartDocContext, final ChartElement axisElement, final AxisSeriesLinkInfo axisSeriesLinkInfo) { /* * First we get the column pos information for each range axis from the columns array list. * And then we create the default category dataset based on the columns positions retrieved above. */ // Get current axis element's axis id. final Object axisID = axisElement.getAttribute("id");//$NON-NLS-1$ // Get the column positions for current axis element by looking into each series for given axis id. Integer[] columnPosArr = null; if (axisSeriesLinkInfo != null && axisID != null) { final ChartSeriesDataLinkInfo seriesDataLinkInfo = chartDocContext.getDataLinkInfo(); if (seriesDataLinkInfo != null) { final ArrayList<ChartElement> seriesElementsList = axisSeriesLinkInfo.getSeriesElements(axisID); if (seriesElementsList != null) { final int size = seriesElementsList.size(); final ArrayList<Integer> columnPosList = new ArrayList<Integer>(); for (int i = 0; i < size; i++) { final ChartElement seriesElement = seriesElementsList.get(i); final Integer columnPos = seriesDataLinkInfo.getColumnNum(seriesElement); columnPosList.add(columnPos); } final int listLength = columnPosList.size(); columnPosArr = new Integer[listLength]; System.arraycopy(columnPosList.toArray(), 0, columnPosArr, 0, listLength); Arrays.sort(columnPosArr); } } } return columnPosArr; } /** * Sets the current range axis location based on axis location style key (specified in the chart document). * * NOTE: Plot's range axis location is updated * * @param plot Plot for the current chart * @param axisElement Current axis element being proccessed * @param axisCounter Set the location and tie it to a index. */ private void setRangeAxisLocation(final CategoryPlot plot, final ChartElement axisElement, final int axisCounter) { final CSSValue cssValue = axisElement.getLayoutStyle().getValue(ChartStyleKeys.AXIS_LOCATION); final String side = cssValue.getCSSText(); if (side != null && (cssValue.equals(ChartAxisLocationType.PRIMARY))) { plot.setRangeAxisLocation(axisCounter, AxisLocation.BOTTOM_OR_LEFT); } else { plot.setRangeAxisLocation(axisCounter, AxisLocation.TOP_OR_RIGHT); } } /** * Sets the axis label and tick label color. * </p> * @param axisElement Current axis element from the chart document * @param valueAxis Current value axis. NOTE: This object will be updated in this method. * @param labelType Tag name label or tag name tick label. */ private void setAxisColor(final ChartElement axisElement, final ValueAxis valueAxis, final String labelType) { final ChartElement[] labelElements = axisElement.findChildrenByName(labelType); if (labelElements != null && labelElements.length > 0) { final CSSValue colorCSSValue = labelElements[0].getLayoutStyle().getValue(ColorStyleKeys.COLOR); final Color axisLabelColor = JFreeChartUtils.getColorFromCSSValue(colorCSSValue); if (axisLabelColor != null) { if (ChartElement.TAG_NAME_LABEL.equalsIgnoreCase(labelType)) { valueAxis.setLabelPaint(axisLabelColor); } else if (ChartElement.TAG_NAME_TICK_LABEL.equalsIgnoreCase(labelType)) { valueAxis.setTickLabelPaint(axisLabelColor); } } } } /** * Create range axis for the current chart and update the chart object with it. * </p> * @param chartDocContext Chart documument context for the current chart. * @param data Data for the current chart. * @param chart The chart object to be updated with range axis info. */ public void createRangeAxis(final ChartDocumentContext chartDocContext, final ChartTableModel data, final JFreeChart chart) { /* * Assumption: * #1. User has to provide axis type for each axis (range/domain) * #2. User have to specify the axis id for each series * The code for handling multiple axis goes here. * 1. Create multiple datasets only if there are more than one range axis. * 2. Update certain axis attributes on the given plot. */ final ChartDocument chartDocument = chartDocContext.getChartDocument(); final AxisSeriesLinkInfo axisSeriesLinkInfo = chartDocument.getAxisSeriesLinkInfo(); final ArrayList<ChartElement> rangeAxisArrayList = axisSeriesLinkInfo.getRangeAxisElements(); final int rangeAxisCount = rangeAxisArrayList.size(); if (chart != null && rangeAxisCount > 0) { final CategoryPlot plot = (CategoryPlot) chart.getPlot(); for (int i = 0; i < rangeAxisCount; i++) { final ChartElement axisElement = rangeAxisArrayList.get(i); // If there is only one range axis then we do not need to create a new jfreeDataset (that uses certain column data) // Instead we just need to update certain attributes like label text, tick label color etc. if (rangeAxisCount > 1) { // Create new jfreeDataset since there are more than one range axis and get the data corresponding to // certain columns final Integer[] columnPosArr = getColumPositions(chartDocContext, axisElement, axisSeriesLinkInfo); final DefaultCategoryDataset currDataset = datasetGeneratorFactory .createDefaultCategoryDataset(chartDocContext, data, columnPosArr); plot.setDataset(i, currDataset); plot.mapDatasetToRangeAxis(i, i); } final ValueAxis valueAxis = createRangeAxis(axisElement); if (valueAxis != null) { plot.setRangeAxis(i, valueAxis); setAxisColor(axisElement, valueAxis, ChartElement.TAG_NAME_LABEL); setAxisColor(axisElement, valueAxis, ChartElement.TAG_NAME_TICK_LABEL); setRangeAxisLocation(plot, axisElement, i); setRenderer(plot, i); } } } } /** * Creates a Range Axis * @param axisElement -- Current axis element * @return Returns the new range axis. */ private ValueAxis createRangeAxis(final ChartElement axisElement) { final String axisLabel = (String) axisElement.getAttribute("label");//$NON-NLS-1$ final ValueAxis valueAxis; if (axisLabel != null) { valueAxis = new NumberAxis(axisLabel); } else { valueAxis = new NumberAxis(); } return valueAxis; } /** * Set the renderer for the chart. * </p> * @param plot Plot element from the current chart document. * @param index Set the renderer for the index. */ //TODO: Look into this method for remaining implementations. private void setRenderer(final CategoryPlot plot, final int index) { if (plot.getRenderer() instanceof GroupedStackedBarRenderer) { } else if (plot.getRenderer() instanceof CylinderRenderer) { } else if (plot.getRenderer() instanceof BarRenderer3D) { } else if (plot.getRenderer() instanceof LayeredBarRenderer) { } else if (plot.getRenderer() instanceof BarRenderer) { final BarRenderer barRenderer = new BarRenderer(); plot.setRenderer(index, barRenderer); } else if (plot.getRenderer() instanceof AreaRenderer) { final AreaRenderer areaRenderer = new AreaRenderer(); plot.setRenderer(index, areaRenderer); } } /** * Sets the paint(gradient color) on all the series listed by the * chartDocument. * * @param categoryPlot - the active plot * @param seriesElements - Array of series elements that contains series tags * @param data - The actual chart data */ protected void setSeriesPaint(final CategoryPlot categoryPlot, final ChartElement[] seriesElements, final ChartTableModel data) { StandardGradientPaintTransformer st = null; final int length = seriesElements.length; for (int i = 0; i < length; i++) { final ChartElement seriesElement = seriesElements[i]; final Paint paint = getPaintFromSeries(seriesElement); if (paint != null) { final int column = JFreeChartUtils.getSeriesColumn(seriesElement, data, i); final int datasetCount = categoryPlot.getDatasetCount(); for (int datasetCounter = 0; datasetCounter < datasetCount; datasetCounter++) { categoryPlot.getRenderer(datasetCounter).setSeriesPaint(column, paint); } /* * JFreeChart engine cannot currently implement more than one gradient type except * when it's none. That is we cannot have one gradient as CENTER_VERTICAL and another * one as CENTER_HORIZONTAL. * * For example: * series1: none * series2: VERTICAL * series3: none * series4: HORIZONTAL * JfreeChart can render none as none, but can implement only one of the gradient * styles defined for bar2 and bar4. In our implementation we are accepting the first * not-none gradient type and then rendering bar2 and bar4 with VERTICAL. * * Check for specific renderer instances since only certain specific renderers allow * setting gradient paint transform. */ if (st == null) { st = JFreeChartUtils.getStandardGradientPaintTrans(seriesElements[i]); final float barWidthPercent = JFreeChartUtils.getMaximumBarWidth(seriesElements[i]); for (int datasetCounter = 0; datasetCounter < datasetCount; datasetCounter++) { final CategoryItemRenderer itemRenderer = categoryPlot.getRenderer(datasetCounter); // If the renderer is of type BarRenderer then set the gradient paint transform // and also set the maximum bar width if (itemRenderer instanceof BarRenderer) { final BarRenderer barRender = (BarRenderer) categoryPlot.getRenderer(datasetCounter); barRender.setGradientPaintTransformer(st); if (barWidthPercent > 0) { barRender.setMaximumBarWidth(barWidthPercent); } } } } } } } //setSeriesPaint() ends here /** * Sets the series item label(s) defined in the chartDocument * * @param categoryPlot - Plot for the current chart * @param seriesElements - Array of Series elements * @param data - the data */ protected void setSeriesItemLabel(final CategoryPlot categoryPlot, final ChartElement[] seriesElements, final ChartTableModel data) { final int numOfDatasets = categoryPlot.getDatasetCount(); for (int datasetCounter = 0; datasetCounter < numOfDatasets; datasetCounter++) { categoryPlot.getRenderer(datasetCounter) .setBaseItemLabelGenerator(new ChartItemLabelGenerator(seriesElements, data)); final int numOfSeriesElements = seriesElements.length; for (int seriesCounter = 0; seriesCounter < numOfSeriesElements; seriesCounter++) { // Get and set font information only if the item label's visibility is set to true if (JFreeChartUtils.showItemLabel(seriesElements[seriesCounter])) { final Font font = JFreeChartUtils.getFont(seriesElements[seriesCounter]); final CategoryItemRenderer categoryItemRenderer = categoryPlot.getRenderer(datasetCounter); if (categoryItemRenderer instanceof BarRenderer) { final BarRenderer barRender = (BarRenderer) categoryItemRenderer; barRender.setSeriesItemLabelFont(seriesCounter, font, true); barRender.setSeriesItemLabelsVisible(seriesCounter, Boolean.TRUE, true); } else if (categoryItemRenderer instanceof AreaRenderer) { final AreaRenderer areaRender = (AreaRenderer) categoryItemRenderer; areaRender.setSeriesItemLabelFont(seriesCounter, font, true); areaRender.setSeriesPositiveItemLabelPosition(seriesCounter, new ItemLabelPosition(ItemLabelAnchor.OUTSIDE12, TextAnchor.TOP_CENTER)); areaRender.setSeriesNegativeItemLabelPosition(seriesCounter, new ItemLabelPosition(ItemLabelAnchor.OUTSIDE6, TextAnchor.BOTTOM_CENTER)); areaRender.setSeriesItemLabelsVisible(seriesCounter, Boolean.TRUE, true); } } } } } /** * This method checks to see if there is a gradient type other than "none" set on seriesElement. * If the series element has a gradient set on it then it returns that. Otherwise if a color * is defined then that is returned. If no color or gradient is set then this method returns * a null * * @param seriesElement - Current series element * @return a Paint object defined by the seriesElement */ public Paint getPaintFromSeries(final ChartElement seriesElement) { CSSValue cssValue = seriesElement.getLayoutStyle().getValue(ChartStyleKeys.GRADIENT_TYPE); final String gradientType = cssValue != null ? cssValue.getCSSText() : null; final Paint paint; if (gradientType != null && !gradientType.equalsIgnoreCase("none")) { //$NON-NLS-1$ paint = JFreeChartUtils.getGradientPaint(seriesElement); } else { paint = (Paint) seriesElement.getLayoutStyle().getValue(ColorStyleKeys.COLOR); } return paint; } public JFreeChart createChart(ChartDocumentContext chartDocContext, ChartTableModel data) { JFreeChart chart = doCreateChart(chartDocContext, data); chart.setBackgroundPaint(getChartBackgroundColor(chartDocContext.getChartDocument())); Color chartBackgroundPaint = ColorFactory.getInstance() .getColor(chartDocContext.getChartDocument().getPlotElement(), BorderStyleKeys.BACKGROUND_COLOR); if (chartBackgroundPaint != null) { chart.getPlot().setBackgroundPaint(chartBackgroundPaint); } CSSNumericValue opacity = (CSSNumericValue) chartDocContext.getChartDocument().getPlotElement() .getLayoutStyle().getValue(ColorStyleKeys.OPACITY); if (opacity != null) { chart.getPlot().setForegroundAlpha((float) opacity.getValue()); } ChartElement rootElement = chartDocContext.getChartDocument().getRootElement(); ChartElement[] children = rootElement.findChildrenByName(ChartElement.TAG_NAME_TITLE); //$NON-NLS-1$ if (children != null && children.length > 0) { Font font = ChartUtils.getFont(children[0]); if (font != null) { chart.getTitle().setFont(font); } } if (getShowLegend(chartDocContext.getChartDocument())) { children = chartDocContext.getChartDocument().getRootElement() .findChildrenByName(ChartElement.TAG_NAME_LEGEND); //$NON-NLS-1$ if ((children != null) && (children.length > 0)) { ChartElement legendElement = children[0]; Font font = JFreeChartUtils.getFont(legendElement); if (font != null) { chart.getLegend().setItemFont(font); } CSSNumericValue value = (CSSNumericValue) legendElement.getLayoutStyle() .getValue(BorderStyleKeys.BORDER_TOP_WIDTH); if ((value == null) || (value.getValue() <= 0)) { chart.getLegend().setBorder(BlockBorder.NONE); } } } CSSValue borderWidth = rootElement.getLayoutStyle().getValue(BorderStyleKeys.BORDER_TOP_WIDTH); if ((borderWidth != null) && (borderWidth instanceof CSSNumericValue) && (((CSSNumericValue) borderWidth).getValue() > 0)) { chart.setBorderVisible(true); } else if ((borderWidth != null) && (borderWidth instanceof CSSStringValue)) { chart.setBorderVisible(true); } Color borderColor = ColorFactory.getInstance().getColor(rootElement, BorderStyleKeys.BORDER_TOP_COLOR); if (borderColor != null) { chart.setBorderPaint(borderColor); } Plot plot = chart.getPlot(); if (plot instanceof CategoryPlot) { CategoryPlot categoryPlot = (CategoryPlot) plot; children = chartDocContext.getChartDocument().getRootElement() .findChildrenByName(ChartElement.TAG_NAME_RANGE_LABEL); //$NON-NLS-1$ if (children != null && children.length > 0) { Font font = ChartUtils.getFont(children[0]); if (font != null) { categoryPlot.getRangeAxis().setLabelFont(font); } } children = chartDocContext.getChartDocument().getRootElement() .findChildrenByName(ChartElement.TAG_NAME_DOMAIN_LABEL); //$NON-NLS-1$ if (children != null && children.length > 0) { Font font = ChartUtils.getFont(children[0]); if (font != null) { categoryPlot.getDomainAxis().setLabelFont(font); } } } return chart; } protected Color getChartBackgroundColor(ChartDocument chartDoc) { Color chartBackgroundPaint = Color.white; ChartElement rootElement = chartDoc.getRootElement(); Color chartBackgroundPaintTmp = ColorFactory.getInstance().getColor(rootElement, BorderStyleKeys.BACKGROUND_COLOR); if (!Color.black.equals(chartBackgroundPaintTmp)) { // black is the default? chartBackgroundPaint = chartBackgroundPaintTmp; } return chartBackgroundPaint; } protected abstract JFreeChart doCreateChart(ChartDocumentContext chartDocContext, ChartTableModel data); }