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