Mercurial > dive4elements > gnv-client
diff gnv-artifacts/src/main/java/de/intevation/gnv/chart/AbstractXYLineChart.java @ 1119:7c4f81f74c47
merged gnv-artifacts
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Fri, 28 Sep 2012 12:14:00 +0200 |
parents | f953c9a559d8 |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gnv-artifacts/src/main/java/de/intevation/gnv/chart/AbstractXYLineChart.java Fri Sep 28 12:14:00 2012 +0200 @@ -0,0 +1,461 @@ +/* + * Copyright (c) 2010 by Intevation GmbH + * + * This program is free software under the LGPL (>=v2.1) + * Read the file LGPL.txt coming with the software for details + * or visit http://www.gnu.org/licenses/ if it does not exist. + */ + +package de.intevation.gnv.chart; + +import de.intevation.gnv.geobackend.base.Result; + +import de.intevation.gnv.state.describedata.KeyValueDescibeData; + +import java.awt.Color; + +import java.awt.geom.Ellipse2D; + +import java.text.NumberFormat; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +import org.apache.log4j.Logger; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.JFreeChart; + +import org.jfree.chart.axis.Axis; +import org.jfree.chart.axis.AxisLocation; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.axis.NumberTickUnit; + +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.XYPlot; + +import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; + +import org.jfree.chart.title.TextTitle; + +import org.jfree.data.Range; + +import org.jfree.data.general.Series; + +import org.jfree.data.xy.XYDataset; + +import org.jfree.ui.RectangleInsets; + +/** + * This abstract class defines some methods to adjust chart settings after its + * creation. + * + * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a> + */ +public abstract class AbstractXYLineChart +extends AbstractChart +{ + /** + * Constant field used to expand the area between data and chart border. Its + * value is {@value}.<br> + * A value of 0.05 equals 5 percent. + */ + public static final double LOWER_MARGIN = 0.05D; + + /** + * Constant field used to expand the area between data and chart border. Its + * value is {@value}.<br> + * A value of 0.05 equals 5 percent. + */ + public static final double UPPER_MARGIN = 0.05D; + + /** + * Logger used to log with log4j. + */ + private static Logger log = Logger.getLogger(AbstractXYLineChart.class); + + /** + * Field of supported colors used for lines and data points in charts. + */ + protected static Color[] COLOR = { + Color.black, Color.red, Color.green, Color.blue, Color.yellow, + Color.gray, Color.orange, Color.pink, Color.cyan + }; + + /** + * Static field to remember the index of the previously used color. + */ + protected static int nextColor = 0; + + /** + * Default <code>PlotOrientation</code>. + */ + protected PlotOrientation PLOT_ORIENTATION = PlotOrientation.VERTICAL; + + /** + * Map to store datasets for each parameter. + */ + protected Map datasets; + + /** + * Map to store max ranges of each parameter (axis.setAutoRange(true) + * doesn't seem to work */ + protected Map ranges; + + /** + * This method is called by <code>Chart</code> to bring the data into the + * right form fitting to JFreeChart objects. + */ + protected abstract void initData(); + + /** + * Add a value of <code>row</code> to <code>series</code>. + * + * @param row <code>Result</code> Object returned from database. Contains + * a value used to add to <code>series</code> + * @param series A JFreeChart Series object. + */ + protected abstract void addValue(Result row, Series series); + + /** + * Add <code>series</code> to JFreeChart's Dataset object currently which is + * processing. + * + * @param series Series to add. + * @param label Label used show in legend. + * @param idx Currently not used. + */ + protected abstract void addSeries(Series series, String label, int idx); + + /** + * Abstract method which is called by <code>Chart</code> interface after + * chart creation. It turns an axis' label into a locale specific format. + * + * @param axis Axis to adjust. + * @param locale java.util.Locale object used specify the format. + */ + protected abstract void localizeDomainAxis(Axis axis, Locale locale); + + /** + * Abstract method to create a label for a series of parameters. + * + * @param breakPoint1 Identifier returned from database. These identifier + * are used to identify the results from database which are all stored in + * one big java.util.Collection. + * @param breakPoint2 Identifier returned from database. + * @param breakPoint3 Identifier returned from database. + * + * @return Concatinated string of parameter name and measurement. + */ + protected abstract String createSeriesName( + String breakPoint1, + String breakPoint2, + String breakPoint3 + ); + + + /** + * @see de.intevation.gnv.chart.Chart#generateChart() + */ + public JFreeChart generateChart() { + log.debug("generate XYLineChart"); + nextColor = 0; + + if (chart != null) + return chart; + + initChart(); + + chart.addSubtitle(new TextTitle(labels.getSubtitle())); + + theme.apply(chart); + initData(); + + adjustPlot((XYPlot)chart.getPlot()); + + return chart; + } + + + protected void initChart() { + chart = ChartFactory.createXYLineChart( + labels.getTitle(), + labels.getDomainAxisLabel(), + null, + null, + PLOT_ORIENTATION, + true, + false, + false + ); + } + + + /** + * Method used to adjust the axes after chart generation. Methods for i18n + * support ({@link #localizeDomainAxis} and {@link #localizeRangeAxis}) are + * called and axes of this series are expanded. + * + * @param seriesKey Identifier of an axis which have to be adjusted. + * @param idx Set the axis identified by <code>seriesKey</code> to position + * <code>idx</code>. + */ + protected void prepareAxis(String seriesKey, int idx) { + log.debug("prepare axis of xychart"); + + XYPlot plot = chart.getXYPlot(); + Axis xAxis = plot.getDomainAxis(); + NumberAxis yAxis = new NumberAxis(seriesKey); + + localizeDomainAxis(xAxis, locale); + localizeRangeAxis(yAxis, locale); + + // litte workarround to adjust the max range of axes. + // NumberAxis.setAutoRange(true) doesn't seem to work properly. + Range yRange = (Range) ranges.get(seriesKey); + double lo = yRange.getLowerBound(); + double hi = yRange.getUpperBound(); + + if (lo == hi) { + yRange = new Range( + lo - (lo / 100 * LOWER_MARGIN), + hi + (hi / 100 * UPPER_MARGIN)); + } + else { + yRange = Range.expand(yRange, LOWER_MARGIN, UPPER_MARGIN); + } + yAxis.setRange(yRange); + log.debug("Max Range of dataset is: " + yRange.toString()); + + if (seriesKey.contains("richtung")) { + yAxis.setTickUnit(new NumberTickUnit(30.0)); + yAxis.setUpperBound(360.0); + yAxis.setLowerBound(0.0); + } + else { + yAxis.setFixedDimension(10.0); + yAxis.setAutoRangeIncludesZero(false); + } + + plot.setRangeAxis(idx, yAxis); + yAxis.configure(); + + if (idx % 2 != 0) + plot.setRangeAxisLocation(idx, AxisLocation.BOTTOM_OR_RIGHT); + else + plot.setRangeAxisLocation(idx, AxisLocation.BOTTOM_OR_LEFT); + + plot.mapDatasetToRangeAxis(idx, idx); + } + + + /** + * Method to adjust the rendering of a series in a chart. Line color and + * symbols of vertices are configured here. + * + * @param idx Position of the renderer. + * @param seriesCount Maximum number of series in this chart. + * @param renderLines Lines are displayed if true, otherwise they are not. + * @param renderShapes Vertices are displayed if true, otherwise they are + * not. + */ + protected void adjustRenderer( + int idx, + int seriesCount, + boolean renderLines, + boolean renderShapes + ) { + log.debug("Adjust render of series"); + XYLineAndShapeRenderer renderer = null; + XYPlot plot = chart.getXYPlot(); + + try { + renderer = (XYLineAndShapeRenderer)((XYLineAndShapeRenderer) + (plot.getRenderer())).clone(); + } + catch (CloneNotSupportedException cnse) { + log.warn("Error while cloning renderer.", cnse); + renderer = new XYLineAndShapeRenderer(renderLines, renderShapes); + renderer.setBaseShape(new Ellipse2D.Double(-2,-2,4,4)); + } + + for (int i = 0; i < seriesCount; i++) { + renderer.setSeriesShape(i, renderer.getSeriesShape(0)); + renderer.setSeriesPaint(i, COLOR[nextColor() % COLOR.length]); + renderer.setSeriesShapesVisible(i, renderShapes); + renderer.setSeriesLinesVisible(i, renderLines); + } + plot.setRenderer(idx, renderer); + } + + + /** + * @return Index of the next color + */ + protected static synchronized int nextColor() { + return nextColor++; + } + + + /** + * Method to adjust the plot rendering. Disable horizontal grid lines if + * <code>plot</code> contains only a single y-axis. + * + * @param plot JFreeChart Plot object to be adjusted. + */ + protected void adjustPlot(XYPlot plot) { + if (plot.getRangeAxisCount() > 1) + plot.setRangeGridlinesVisible(false); + + plot.setAxisOffset(new RectangleInsets(0, 0, 0, 15)); + } + + + /** + * Abstract method which is called after chart creation. It turns an + * axis' label into a locale specific format. + * + * @param axis Axis to adjust. + * @param locale java.util.Locale object used specify the format. + * + */ + protected void localizeRangeAxis(Axis axis, Locale locale) { + if (locale == null) + return; + + log.debug( + "Set language of axis [" + axis.getLabel() + "] " + + "to " + locale.toString() + ); + + NumberFormat format = NumberFormat.getInstance(locale); + ((NumberAxis) axis).setNumberFormatOverride(format); + } + + + /** + * Return the maximum y-range of <code>dataset</code>. + * + * @param dataset Dataset to be scaned. + * + * @return JFreeChart Range object containing min and max y-value. + */ + public Range getMaxRangeOfDataset(XYDataset dataset) { + int seriesCount = dataset.getSeriesCount(); + double upper = Double.NEGATIVE_INFINITY; + double lower = Double.POSITIVE_INFINITY; + + for (int i = 0; i < seriesCount; i++) { + int itemCount = dataset.getItemCount(i); + + for (int j = 0; j < itemCount; j++) { + Number num = dataset.getY(i, j); + + if (num != null) { + double y = num.doubleValue(); + lower = y < lower ? y : lower; + upper = y > upper ? y : upper; + } + } + } + + return new Range(lower, upper); + } + + + /** + * Return the maximum y-range of <code>dataset</code> with a margin of + * <code>percent</code> percent. + * + * @param dataset Dataset to be scaned. + * @param percent Percent used to expand the range. + * @return JFreeChart Range object containing min and max y-value with a + * margin. + */ + public Range getMaxRangeOfDatasetWithMargin( + XYDataset dataset, + double percent + ) { + Range range = getMaxRangeOfDataset(dataset); + double length = range.getLength(); + double upper = range.getUpperBound() + length /100 * percent; + double lower = range.getLowerBound() - length /100 * percent; + + return new Range(lower, upper); + } + + + /** + * Method to find a parameter specified by its value. + * + * @param label Search string. + * + * @return Value of a parameter with the given label. + */ + protected String findParameter(String label) { + Iterator iter = parameters.iterator(); + + while (iter.hasNext()) { + KeyValueDescibeData data = (KeyValueDescibeData) iter.next(); + String key = data.getValue(); + + if (label.indexOf(key) > -1) + return key; + } + + return label; + } + + + /** + * Method to find a description of a given collection of values. + * + * @param values Collection to be scaned. + * @param id Identifier and search string of the searched value. + * + * @return title + */ + protected String findValueTitle(Collection values, String id) { + log.debug("find description of dataset"); + + if (values != null){ + Iterator it = values.iterator(); + while (it.hasNext()) { + KeyValueDescibeData data = (KeyValueDescibeData) it.next(); + + if (id.equals(data.getKey())) + return data.getValue(); + } + } + return ""; + } + + + /** + * Method to store the maximum range. Since we need to adjust the range of + * each range axis, we have to memorize its range - each parameter uses an + * own range axis. + * + * @param ranges Map where ranges of each axis will be stored in. + * @param value A given value on an axis. + * @param parameter Parameter name which belongs to <code>value</code>. + */ + protected void storeMaxRange(Map ranges, double value, String parameter) { + Range range = null; + + range = ranges.containsKey(parameter) + ? (Range) ranges.get(parameter) + : new Range(value, value); + + double lower = range.getLowerBound(); + double upper = range.getUpperBound(); + + lower = value < lower ? value : lower; + upper = value > upper ? value : upper; + + ranges.put(parameter, new Range(lower, upper)); + } +} +// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :