Mercurial > dive4elements > river
view flys-artifacts/src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java @ 2779:e57816cf41d5
Replaced another silly "@attribute" XPATH with direct getAttribute() call.
flys-artifacts/trunk@4518 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Fri, 25 May 2012 13:33:59 +0000 |
parents | 31772c144725 |
children | 4a76da133144 |
line wrap: on
line source
package de.intevation.flys.exports; import java.awt.Color; import java.awt.Font; import java.awt.Paint; import java.awt.BasicStroke; import java.awt.Stroke; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Date; import org.json.JSONArray; import org.json.JSONException; import org.apache.log4j.Logger; import org.w3c.dom.Document; import org.jfree.chart.ChartFactory; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.annotations.XYTextAnnotation; import org.jfree.chart.LegendItemCollection; import org.jfree.data.Range; import org.jfree.data.time.TimeSeriesCollection; import org.jfree.data.general.Series; import org.jfree.data.xy.XYDataset; import org.jfree.data.time.Day; import org.jfree.data.time.TimeSeries; import de.intevation.artifactdatabase.state.ArtifactAndFacet; import de.intevation.artifactdatabase.state.Facet; import de.intevation.flys.jfree.Bounds; import de.intevation.flys.jfree.DoubleBounds; import de.intevation.flys.jfree.TimeBounds; import de.intevation.flys.jfree.StyledTimeSeries; import de.intevation.flys.jfree.FLYSAnnotation; import de.intevation.flys.jfree.CollisionFreeXYTextAnnotation; import de.intevation.flys.utils.ThemeAccess; /** * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a> */ public abstract class TimeseriesChartGenerator extends ChartGenerator { /** * Inner class TimeseriesAxisDataset stores TimeSeriesCollection. */ public class TimeseriesAxisDataset implements AxisDataset { protected int axisSymbol; protected List<TimeSeriesCollection> datasets; protected Range range; protected int plotAxisIndex; public TimeseriesAxisDataset(int axisSymbol) { this.axisSymbol = axisSymbol; this.datasets = new ArrayList<TimeSeriesCollection>(); } @Override public void addDataset(XYDataset dataset) { if (!(dataset instanceof TimeSeriesCollection)) { logger.warn("Skip non TimeSeriesCollection dataset."); return; } TimeSeriesCollection tsc = (TimeSeriesCollection) dataset; datasets.add(tsc); mergeRanges(tsc); } @Override public XYDataset[] getDatasets() { return (XYDataset[]) datasets.toArray(new XYDataset[datasets.size()]); } @Override public boolean isEmpty() { return datasets.isEmpty(); } @Override public void setRange(Range range) { this.range = range; } @Override public Range getRange() { return range; } @Override public void setPlotAxisIndex(int plotAxisIndex) { this.plotAxisIndex = plotAxisIndex; } @Override public int getPlotAxisIndex() { return plotAxisIndex; } @Override public boolean isArea(XYDataset dataset) { logger.warn("This AxisDataset doesn't support Areas yet!"); return false; } protected void mergeRanges(TimeSeriesCollection dataset) { logger.debug("Range after merging: " + range); Bounds[] xyRanges = ChartHelper.getBounds(dataset); // TODO COMBINE BOUNDS! logger.debug("Range after merging: " + range); } } // end of TimeseriesAxisDataset class /** List of annotations to insert in plot. */ protected List<FLYSAnnotation> annotations; private static final Logger logger = Logger.getLogger(TimeseriesChartGenerator.class); public static final int AXIS_SPACE = 5; protected Map<Integer, Bounds> xBounds; protected Map<Integer, Bounds> yBounds; /** * The default constructor that initializes internal datastructures. */ public TimeseriesChartGenerator() { super(); xBounds = new HashMap<Integer, Bounds>(); yBounds = new HashMap<Integer, Bounds>(); } @Override public JFreeChart generateChart() { logger.info("Generate Timeseries Chart."); JFreeChart chart = ChartFactory.createTimeSeriesChart( getChartTitle(), getXAxisLabel(), getYAxisLabel(0), null, isLegendVisible(), false, false); XYPlot plot = (XYPlot) chart.getPlot(); chart.setBackgroundPaint(Color.WHITE); plot.setBackgroundPaint(Color.WHITE); addSubtitles(chart); adjustPlot(plot); addDatasets(plot); adjustAxes(plot); adaptZoom(plot); addAnnotationsToRenderer(plot); return chart; } @Override protected Series getSeriesOf(XYDataset dataset, int idx) { return ((TimeSeriesCollection) dataset).getSeries(idx); } /** * This method creates new instances of TimeseriesAxisDataset. * * @param idx The symbol for the new TimeseriesAxisDataset. */ @Override protected AxisDataset createAxisDataset(int idx) { logger.debug("Create a new AxisDataset for index: " + idx); return new TimeseriesAxisDataset(idx); } @Override protected void combineXBounds(Bounds bounds, int index) { if (bounds != null) { Bounds old = getXBounds(index); if (old != null) { bounds = bounds.combine(old); } setXBounds(index, bounds); } } @Override protected void combineYBounds(Bounds bounds, int index) { if (bounds != null) { Bounds old = getYBounds(index); if (old != null) { bounds = bounds.combine(old); } setYBounds(index, bounds); } } // TODO REPLACE THIS METHOD WITH getBoundsForAxis(index) @Override public Range[] getRangesForAxis(int index) { // TODO Bounds[] bounds = getBoundsForAxis(index); return new Range[] { new Range( bounds[0].getLower().doubleValue(), bounds[0].getUpper().doubleValue()), new Range( bounds[1].getLower().doubleValue(), bounds[1].getUpper().doubleValue()) }; } @Override public Bounds getXBounds(int axis) { return xBounds.get(axis); } @Override protected void setXBounds(int axis, Bounds bounds) { xBounds.put(axis, bounds); } @Override public Bounds getYBounds(int axis) { return yBounds.get(axis); } @Override protected void setYBounds(int axis, Bounds bounds) { if (bounds != null) { yBounds.put(axis, bounds); } } public Bounds[] getBoundsForAxis(int index) { logger.debug("Return x and y bounds for axis at: " + index); Bounds rx = getXBounds(Integer.valueOf(index)); Bounds ry = getYBounds(Integer.valueOf(index)); if (rx == null) { logger.warn("Range for x axis not set." + " Using default values: 0 - 1."); rx = new TimeBounds(0l, 1l); } if (ry == null) { logger.warn("Range for y axis not set." + " Using default values: 0 - 1."); ry = new DoubleBounds(0l, 1l); } logger.debug("X Bounds at index " + index + " is: " + rx); logger.debug("Y Bounds at index " + index + " is: " + ry); return new Bounds[] {rx, ry}; } public Bounds getDomainAxisRange() { String[] ranges = getDomainAxisRangeFromRequest(); if (ranges == null || ranges.length < 2) { logger.debug("No zoom range for domain axis specified."); return null; } if (ranges[0] == null || ranges[1] == null) { logger.warn("Invalid ranges for domain axis specified!"); return null; } try { double lower = Double.parseDouble(ranges[0]); double upper = Double.parseDouble(ranges[1]); return new DoubleBounds(lower, upper); } catch (NumberFormatException nfe) { logger.warn("Invalid ranges for domain axis specified: " + nfe); } return null; } public Bounds getValueAxisRange() { String[] ranges = getValueAxisRangeFromRequest(); if (ranges == null || ranges.length < 2) { logger.debug("No zoom range for domain axis specified."); return null; } if (ranges[0] == null || ranges[1] == null) { logger.warn("Invalid ranges for domain axis specified!"); return null; } try { double lower = Double.parseDouble(ranges[0]); double upper = Double.parseDouble(ranges[1]); return new DoubleBounds(lower, upper); } catch (NumberFormatException nfe) { logger.warn("Invalid ranges for domain axis specified: " + nfe); } return null; } protected void adaptZoom(XYPlot plot) { logger.debug("Adapt zoom of Timeseries chart."); zoomX(plot, plot.getDomainAxis(), getXBounds(0), getDomainAxisRange()); Bounds valueAxisBounds = getValueAxisRange(); for (int j = 0, n = plot.getRangeAxisCount(); j < n; j++) { zoomY( plot, plot.getRangeAxis(j), getYBounds(j), valueAxisBounds); } } protected void zoomX( XYPlot plot, ValueAxis axis, Bounds total, Bounds user ) { if (logger.isDebugEnabled()) { logger.debug("== Zoom X axis =="); logger.debug(" Total axis range : " + total); logger.debug(" User defined range: " + user); } if (user != null) { long min = total.getLower().longValue(); long max = total.getUpper().longValue(); long diff = max > min ? max - min : min - max; long newMin = (long) Math.round(min + user.getLower().doubleValue() * diff); long newMax = (long) Math.round(min + user.getUpper().doubleValue() * diff); TimeBounds newBounds = new TimeBounds(newMin, newMax); logger.debug(" Zoom axis to: " + newBounds); newBounds.applyBounds(axis, AXIS_SPACE); } else { logger.debug("No user specified zoom values found!"); total.applyBounds(axis, AXIS_SPACE); } } protected void zoomY( XYPlot plot, ValueAxis axis, Bounds total, Bounds user ) { if (logger.isDebugEnabled()) { logger.debug("== Zoom Y axis =="); logger.debug(" Total axis range : " + total); logger.debug(" User defined range: " + user); } if (user != null) { double min = total.getLower().doubleValue(); double max = total.getUpper().doubleValue(); double diff = max > min ? max - min : min - max; double newMin = min + user.getLower().doubleValue() * diff; double newMax = min + user.getUpper().doubleValue() * diff; DoubleBounds newBounds = new DoubleBounds(newMin, newMax); logger.debug(" Zoom axis to: " + newBounds); newBounds.applyBounds(axis, AXIS_SPACE); } else { logger.debug("No user specified zoom values found!"); total.applyBounds(axis, AXIS_SPACE); } } /** * Adjusts the axes of a plot. This method sets the <i>labelFont</i> of the * X axis. * * @param plot The XYPlot of the chart. */ protected void adjustAxes(XYPlot plot) { ValueAxis xaxis = plot.getDomainAxis(); ChartSettings chartSettings = getChartSettings(); if (chartSettings == null) { return; } Font labelFont = new Font( DEFAULT_FONT_NAME, Font.BOLD, getXAxisLabelFontSize()); xaxis.setLabelFont(labelFont); xaxis.setTickLabelFont(labelFont); } /** * Do Points out. */ protected void doPoints( Object o, ArtifactAndFacet aandf, Document theme, boolean visible, int axisIndex ) { String seriesName = aandf.getFacetDescription(); TimeSeries series = new StyledTimeSeries(seriesName, theme); // Add text annotations for single points. List<XYTextAnnotation> xy = new ArrayList<XYTextAnnotation>(); HashMap<Day, String> names = new HashMap<Day, String>(); try { JSONArray points = new JSONArray((String) o); for (int i = 0; i < points.length(); i++) { JSONArray array = points.getJSONArray(i); double x = array.getDouble(0); double y = array.getDouble(1); String name = array.getString(2); boolean act = array.getBoolean(3); if (!act) { continue; } long l = (new Double(x)).longValue(); Date date = new Date(l); Day day = new Day(date); series.add(day, y, false); names.put(day, name); } } catch(JSONException e){ logger.error("Could not decode json."); } TimeSeriesCollection tsc = new TimeSeriesCollection(); tsc.addSeries(series); // Add Annotations. for (int i = 0; i < series.getItemCount(); i++) { double x = tsc.getXValue(0, i); double y = tsc.getYValue(0, i); xy.add(new CollisionFreeXYTextAnnotation( names.get(series.getTimePeriod(i)), x, y)); } FLYSAnnotation annotations = new FLYSAnnotation(null, null, null, theme); annotations.setTextAnnotations(xy); // Do not generate second legend entry. (null was passed for the aand before). doAnnotations(annotations, null, theme, visible); addAxisDataset(tsc, axisIndex, visible); } /** * Register annotations like MainValues for later plotting * * @param o list of annotations (data of facet). * @param facet The facet. This facet does NOT support any data objects. Use * FLYSArtifact.getNativeFacet() instead to retrieve a Facet which supports * data. * @param theme Theme document for given annotations. * @param visible The visibility of the annotations. */ protected void doAnnotations( FLYSAnnotation annotations, ArtifactAndFacet aandf, Document theme, boolean visible ){ // Running into trouble here. logger.debug("doAnnotations"); // Add all annotations to our annotation pool. annotations.setTheme(theme); if (aandf != null) { Facet facet = aandf.getFacet(); annotations.setLabel(aandf.getFacetDescription()); } else { logger.debug( "Art/Facet for Annotations is null. " + "This should never happen!"); } addAnnotations(annotations, visible); } /** * Adds annotations to list (if visible is true). */ public void addAnnotations(FLYSAnnotation annotation, boolean visible) { if (!visible) { return; } if (annotations == null) { annotations = new ArrayList<FLYSAnnotation>(); } annotations.add(annotation); } /** Add annotations (Sticky, Text and hyk zones). */ public void addAnnotationsToRenderer(XYPlot plot) { logger.debug("XYChartGenerator.addAnnotationsToRenderer"); if (annotations == null) { logger.debug("XYChartGenerator.addBoxAnnotations: no annotations."); return; } // Paints for the boxes/lines. Stroke basicStroke = new BasicStroke(1.0f); Paint linePaint = new Color(255, 0,0,60); Paint fillPaint = new Color(0, 255,0,60); Paint tranPaint = new Color(0, 0,0, 0); // OPTMIMIZE: Pre-calculate positions Area area = new Area( plot.getDomainAxis(0).getRange(), plot.getRangeAxis().getRange()); // Walk over all Annotation sets. for (FLYSAnnotation fa: annotations) { // Access text styling, if any. Document theme = fa.getTheme(); ThemeAccess.TextStyle textStyle = null; ThemeAccess.LineStyle lineStyle = null; // Get Themeing information and add legend item. if (theme != null) { ThemeAccess themeAccess = new ThemeAccess(theme); textStyle = themeAccess.parseTextStyle(); lineStyle = themeAccess.parseLineStyle(); if (fa.getLabel() != null) { LegendItemCollection lic = new LegendItemCollection(); LegendItemCollection old = plot.getFixedLegendItems(); lic.add(createLegendItem(theme, fa.getLabel())); // (Re-)Add prior legend entries. if (old != null) { old.addAll(lic); } else { old = lic; } plot.setFixedLegendItems(old); } } // Other Text Annotations (e.g. labels of manual points). for (XYTextAnnotation ta: fa.getTextAnnotations()) { // Style the text. if (textStyle != null) { textStyle.apply(ta); } ta.setY(area.above(0.05d, ta.getY())); plot.getRenderer().addAnnotation(ta, org.jfree.ui.Layer.FOREGROUND); } } } /** Two Ranges that span a rectangular area. */ public static class Area { protected Range xRange; protected Range yRange; public Area(Range rangeX, Range rangeY) { this.xRange = rangeX; this.yRange = rangeY; } public Area(ValueAxis axisX, ValueAxis axisY) { this.xRange = axisX.getRange(); this.yRange = axisY.getRange(); } public double ofLeft(double percent) { return xRange.getLowerBound() + xRange.getLength() * percent; } public double ofRight(double percent) { return xRange.getUpperBound() - xRange.getLength() * percent; } public double ofGround(double percent) { return yRange.getLowerBound() + yRange.getLength() * percent; } public double atTop() { return yRange.getUpperBound(); } public double atGround() { return yRange.getLowerBound(); } public double atRight() { return xRange.getUpperBound(); } public double atLeft() { return xRange.getLowerBound(); } public double above(double percent, double base) { return base + yRange.getLength() * percent; } } } // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :