ingo@297: package de.intevation.gnv.chart;
ingo@297:
ingo@297: import java.awt.Color;
ingo@327: import java.awt.geom.Ellipse2D;
ingo@315: import java.text.NumberFormat;
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:
ingo@297: import org.jfree.chart.JFreeChart;
ingo@297: import org.jfree.chart.ChartFactory;
ingo@315: import org.jfree.chart.axis.Axis;
ingo@297: import org.jfree.chart.axis.NumberAxis;
ingo@297: import org.jfree.chart.axis.NumberTickUnit;
ingo@297: import org.jfree.chart.axis.AxisLocation;
ingo@297: import org.jfree.chart.plot.PlotOrientation;
ingo@297: import org.jfree.chart.plot.XYPlot;
ingo@327: import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
ingo@331: import org.jfree.chart.title.TextTitle;
ingo@364: import org.jfree.data.xy.XYDataset;
ingo@364: import org.jfree.data.Range;
ingo@297: import org.jfree.data.general.Series;
ingo@297:
ingo@297: import de.intevation.gnv.geobackend.base.Result;
tim@335: import de.intevation.gnv.state.describedata.KeyValueDescibeData;
ingo@297:
ingo@333:
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@767: * @param local 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@774: protected static synchronized int nextColor() {
ingo@774: return nextColor++;
ingo@774: }
ingo@774:
ingo@297:
ingo@767: /**
ingo@767: * 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@344: }
ingo@344:
ingo@344:
ingo@767: /**
ingo@767: * 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@767: * @param local java.util.Locale object used specify the format.
ingo@767: *
ingo@767: * @param axis
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@767: *
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 :