# HG changeset patch # User Andre Heinecke # Date 1379517197 -7200 # Node ID 6ab1464021ae6694911396dbb5859023506a1d11 # Parent 06a9a241faac9c2ca3b1bfba60d654c357692598 Add DiagramGenerator which should mainly replace xygenerator diff -r 06a9a241faac -r 6ab1464021ae artifacts/src/main/java/org/dive4elements/river/exports/DiagramGenerator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/artifacts/src/main/java/org/dive4elements/river/exports/DiagramGenerator.java Wed Sep 18 17:13:17 2013 +0200 @@ -0,0 +1,1016 @@ +/* Copyright (C) 2013 by Bundesanstalt für Gewässerkunde + * Software engineering by Intevation GmbH + * + * This file is Free Software under the GNU AGPL (>=v3) + * and comes with ABSOLUTELY NO WARRANTY! Check out the + * documentation coming with Dive4Elements River for details. + */ + +package org.dive4elements.river.exports; + +import java.awt.Color; +import java.awt.Font; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.ImageIcon; + +import org.apache.log4j.Logger; +import org.jfree.chart.ChartFactory; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.LegendItem; +import org.jfree.chart.annotations.XYAnnotation; +import org.jfree.chart.annotations.XYImageAnnotation; +import org.jfree.chart.annotations.XYTextAnnotation; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.axis.LogarithmicAxis; +import org.jfree.chart.plot.Marker; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.XYPlot; +import org.jfree.data.Range; +import org.jfree.data.general.Series; +import org.jfree.data.xy.XYDataset; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; +import org.json.JSONArray; +import org.json.JSONException; + +import org.dive4elements.artifactdatabase.state.ArtifactAndFacet; +import org.dive4elements.artifactdatabase.state.Facet; +import org.dive4elements.river.jfree.AxisDataset; +import org.dive4elements.river.jfree.AnnotationHelper; +import org.dive4elements.river.jfree.Bounds; +import org.dive4elements.river.jfree.CollisionFreeXYTextAnnotation; +import org.dive4elements.river.jfree.DoubleBounds; +import org.dive4elements.river.jfree.RiverAnnotation; +import org.dive4elements.river.jfree.StyledAreaSeriesCollection; +import org.dive4elements.river.jfree.StyledXYSeries; +import org.dive4elements.river.themes.ThemeDocument; + +/* TODO remove after hackish testing */ +import org.dive4elements.river.exports.process.Processor; +import org.dive4elements.river.exports.process.BedDiffHeightYearProcessor; +import org.dive4elements.river.exports.process.BedDiffYearProcessor; +import org.dive4elements.river.exports.process.BedheightProcessor; +import org.dive4elements.river.exports.process.QOutProcessor; +import org.dive4elements.river.exports.process.WOutProcessor; +/* end TODO*/ + +/** + * The main diagram creation class. + * + * This class is the glue between output processors and facets. + * The generator creates one diagram and calls the appropiate + * processors for the state and + * + * With respect to datasets, ranges and axis, there are following requirements: + * + */ +public abstract class DiagramGenerator extends ChartGenerator2 { + + /** Enumerator over existing axes. */ + @Override + protected abstract YAxisWalker getYAxisWalker(); + + public static final int AXIS_SPACE = 5; + + /** The logger that is used in this generator. */ + private static Logger logger = Logger.getLogger(DiagramGenerator.class); + + protected List domainMarkers = new ArrayList(); + + protected List valueMarkers = new ArrayList(); + + /** The max X range to include all X values of all series for each axis. */ + protected Map xBounds; + + /** The max Y range to include all Y values of all series for each axis. */ + protected Map yBounds; + + /** Whether or not the plot is inverted (left-right). */ + private boolean inverted; + + public DiagramGenerator() { + super(); + + xBounds = new HashMap(); + yBounds = new HashMap(); + } + + + /** + * Generate the chart anew (including localized axis and all). + */ + @Override + public JFreeChart generateChart() { + logger.debug("DiagramGenerator.generateChart"); + + JFreeChart chart = ChartFactory.createXYLineChart( + getChartTitle(), + getXAxisLabel(), + getYAxisLabel(0), + null, + PlotOrientation.VERTICAL, + isLegendVisible(), + false, + false); + + XYPlot plot = (XYPlot) chart.getPlot(); + ValueAxis axis = createXAxis(getXAxisLabel()); + plot.setDomainAxis(axis); + + chart.setBackgroundPaint(Color.WHITE); + plot.setBackgroundPaint(Color.WHITE); + addSubtitles(chart); + adjustPlot(plot); + + //debugAxis(plot); + + addDatasets(plot); + + //debugDatasets(plot); + + addMarkers(plot); + + recoverEmptyPlot(plot); + preparePointRanges(plot); + + //debugAxis(plot); + + localizeAxes(plot); + adjustAxes(plot); + if (!(axis instanceof LogarithmicAxis)) { + // XXX: + // The auto zoom without a range tries + // to include 0 in a logarithmic axis + // which triggers a bug in jfreechart that causes + // the values to be drawn carthesian + autoZoom(plot); + } + + //debugAxis(plot); + + // These have to go after the autozoom. + AnnotationHelper.addAnnotationsToRenderer(annotations, plot, + getChartSettings(), datasets); + + // Add a logo (maybe). + addLogo(plot); + + aggregateLegendEntries(plot); + + return chart; + } + + + /** + * Return left most data points x value (on first axis). + * Shortcut, especially to be overridden in (LS) charts where + * axis could be inverted. + */ + protected double getLeftX() { + return (Double)getXBounds(0).getLower(); + } + + + /** + * Return right most data points x value (on first axis). + * Shortcut, especially to be overridden in (LS) charts where + * axis could be inverted. + */ + protected double getRightX() { + return (Double)getXBounds(0).getUpper(); + } + + + /** Add a logo as background annotation to plot. */ + protected void addLogo(XYPlot plot) { + String logo = showLogo(); + if (logo == null) { + logger.debug("No logo to show chosen"); + return; + } + + ImageIcon imageIcon = null; + if (logo.equals("none")) { + return; + } + /* + If you want to add images, remember to change code in these places: + flys-artifacts: + DiagramGenerator.java + Timeseries*Generator.java and + in the flys-client projects Chart*Propert*Editor.java. + Also, these images have to be put in + flys-artifacts/src/main/resources/images/ + flys-client/src/main/webapp/images/ + */ + java.net.URL imageURL; + if (logo.equals("Intevation")) { + imageURL = DiagramGenerator.class.getResource("/images/intevation.png"); + } + else { // TODO else if ... + imageURL = DiagramGenerator.class.getResource("/images/bfg_logo.gif"); + } + imageIcon = new ImageIcon(imageURL); + + + double xPos = 0d, yPos = 0d; + + String placeh = logoHPlace(); + String placev = logoVPlace(); + + if (placev == null || placev.equals("none")) { + placev = "top"; + } + if (placev.equals("top")) { + yPos = (Double)getYBounds(0).getUpper(); + } + else if (placev.equals("bottom")) { + yPos = (Double)getYBounds(0).getLower(); + } + else if (placev.equals("center")) { + yPos = ((Double)getYBounds(0).getUpper() + (Double)getYBounds(0).getLower())/2d; + } + else { + logger.debug("Unknown place-v value: " + placev); + } + + if (placeh == null || placeh.equals("none")) { + placeh = "center"; + } + if (placeh.equals("left")) { + xPos = getLeftX(); + } + else if (placeh.equals("right")) { + xPos = getRightX(); + } + else if (placeh.equals("center")) { + xPos = ((Double)getXBounds(0).getUpper() + (Double)getXBounds(0).getLower())/2d; + } + else { + logger.debug("Unknown place-h value: " + placeh); + } + + logger.debug("logo position: " + xPos + "/" + yPos); + + org.jfree.ui.RectangleAnchor anchor + = org.jfree.ui.RectangleAnchor.TOP; + if (placev.equals("top")) { + if (placeh.equals("left")) { + anchor = org.jfree.ui.RectangleAnchor.TOP_LEFT; + } + else if (placeh.equals("right")) { + anchor = org.jfree.ui.RectangleAnchor.TOP_RIGHT; + } + else if (placeh.equals("center")) { + anchor = org.jfree.ui.RectangleAnchor.TOP; + } + } + else if (placev.equals("bottom")) { + if (placeh.equals("left")) { + anchor = org.jfree.ui.RectangleAnchor.BOTTOM_LEFT; + } + else if (placeh.equals("right")) { + anchor = org.jfree.ui.RectangleAnchor.BOTTOM_RIGHT; + } + else if (placeh.equals("center")) { + anchor = org.jfree.ui.RectangleAnchor.BOTTOM; + } + } + else if (placev.equals("center")) { + if (placeh.equals("left")) { + anchor = org.jfree.ui.RectangleAnchor.LEFT; + } + else if (placeh.equals("right")) { + anchor = org.jfree.ui.RectangleAnchor.RIGHT; + } + else if (placeh.equals("center")) { + anchor = org.jfree.ui.RectangleAnchor.CENTER; + } + } + + XYAnnotation xyannotation = + new XYImageAnnotation(xPos, yPos, imageIcon.getImage(), anchor); + plot.getRenderer().addAnnotation(xyannotation, org.jfree.ui.Layer.BACKGROUND); + } + + + protected NumberAxis createXAxis(String label) { + return new NumberAxis(label); + } + + + @Override + protected Series getSeriesOf(XYDataset dataset, int idx) { + return ((XYSeriesCollection) dataset).getSeries(idx); + } + + + @Override + protected AxisDataset createAxisDataset(int idx) { + logger.debug("Create new AxisDataset for index: " + idx); + return new AxisDataset(idx); + } + + + /** + * Put debug output about datasets. + */ + public void debugDatasets(XYPlot plot) { + logger.debug("Number of datasets: " + plot.getDatasetCount()); + for (int i = 0, P = plot.getDatasetCount(); i < P; i++) { + if (plot.getDataset(i) == null) { + logger.debug("Dataset #" + i + " is null"); + continue; + } + logger.debug("Dataset #" + i + ":" + plot.getDataset(i)); + XYSeriesCollection series = (XYSeriesCollection) plot.getDataset(i); + logger.debug("X-Extend of Dataset: " + series.getSeries(0).getMinX() + + " " + series.getSeries(0).getMaxX()); + logger.debug("Y-Extend of Dataset: " + series.getSeries(0).getMinY() + + " " + series.getSeries(0).getMaxY()); + } + } + + + /** + * Put debug output about axes. + */ + public void debugAxis(XYPlot plot) { + logger.debug("..............."); + for (int i = 0, P = plot.getRangeAxisCount(); i < P; i++) { + if (plot.getRangeAxis(i) == null) + logger.debug("Range-Axis #" + i + " == null"); + else { + logger.debug("Range-Axis " + i + " != null [" + + plot.getRangeAxis(i).getRange().getLowerBound() + + " " + plot.getRangeAxis(i).getRange().getUpperBound() + + "]"); + } + } + for (int i = 0, P = plot.getDomainAxisCount(); i < P; i++) { + if (plot.getDomainAxis(i) == null) + logger.debug("Domain-Axis #" + i + " == null"); + else { + logger.debug("Domain-Axis " + i + " != null [" + + plot.getDomainAxis(i).getRange().getLowerBound() + + " " + plot.getDomainAxis(i).getRange().getUpperBound() + + "]"); + } + } + logger.debug("..............."); + } + + + /** + * Registers an area to be drawn. + * @param area Area to be drawn. + * @param index 'axis index' + * @param visible Whether or not to be visible (important for range calculations). + */ + public void addAreaSeries(StyledAreaSeriesCollection area, int index, boolean visible) { + if (area == null) { + logger.warn("Cannot yet render above/under curve."); + return; + } + + AxisDataset axisDataset = (AxisDataset) getAxisDataset(index); + + if (visible) { + axisDataset.addArea(area); + } + else { + /* No range merging, for areas extending to infinity this + * causes problems. */ + } + } + + + /** + * Add given series if visible, if not visible adjust ranges (such that + * all points in data would be plotted once visible). + * @param series the data series to include in plot. + * @param index ('symbolic') index of the series and of its axis. + * @param visible whether or not the data should be plotted. + */ + public void addAxisSeries(XYSeries series, int index, boolean visible) { + if (series == null) { + return; + } + + logger.debug("Y Range of XYSeries: " + + series.getMinY() + " | " + series.getMaxY()); + + addAxisDataset(new XYSeriesCollection(series), index, visible); + + AxisDataset axisDataset = (AxisDataset) getAxisDataset(index); + } + + + /** + * Add the given vertical marker to the chart. + */ + public void addDomainMarker(Marker marker) { + addDomainMarker(marker, true); + } + + + /** + * Add the given vertical marker to the chart.Note: the marker is + * added to the chart only if it is not null and if visible is true. + * @param marker The marker that should be added to the chart. + * @param visible The visibility of the marker. + */ + public void addDomainMarker(Marker marker, boolean visible) { + if (visible && marker != null) { + domainMarkers.add(marker); + } + } + + + /** + * Add the given vertical marker to the chart. + */ + public void addValueMarker(Marker marker) { + addValueMarker(marker, true); + } + + + /** + * Add the given horizontal marker to the chart.Note: the marker is + * added to the chart only if it is not null and if visible is true. + * @param marker The marker that should be added to the chart. + * @param visible The visibility of the marker. + */ + public void addValueMarker(Marker marker, boolean visible) { + if (visible && marker != null) { + valueMarkers.add(marker); + } + } + + + protected void addMarkers(XYPlot plot) { + for(Marker marker : domainMarkers) { + plot.addDomainMarker(marker); + } + for(Marker marker : valueMarkers) { + plot.addRangeMarker(marker); + } + } + + + /** + * Effect: extend range of x axis to include given limits. + * + * @param bounds the given ("minimal") bounds. + * @param index index of axis to be merged. + */ + @Override + protected void combineXBounds(Bounds bounds, int index) { + if (!(bounds instanceof DoubleBounds)) { + logger.warn("Unsupported Bounds type: " + bounds.getClass()); + return; + } + + DoubleBounds dBounds = (DoubleBounds) bounds; + + if (dBounds == null + || Double.isNaN((Double) dBounds.getLower()) + || Double.isNaN((Double) dBounds.getUpper())) { + return; + } + + Bounds old = getXBounds(index); + + if (old != null) { + dBounds = (DoubleBounds) dBounds.combine(old); + } + + setXBounds(index, dBounds); + } + + + @Override + protected void combineYBounds(Bounds bounds, int index) { + if (!(bounds instanceof DoubleBounds)) { + logger.warn("Unsupported Bounds type: " + bounds.getClass()); + return; + } + + DoubleBounds dBounds = (DoubleBounds) bounds; + + if (dBounds == null + || Double.isNaN((Double) dBounds.getLower()) + || Double.isNaN((Double) dBounds.getUpper())) { + return; + } + + Bounds old = getYBounds(index); + + if (old != null) { + dBounds = (DoubleBounds) dBounds.combine(old); + } + + setYBounds(index, dBounds); + } + + + /** + * If no data is visible, draw at least empty axis. + */ + private void recoverEmptyPlot(XYPlot plot) { + if (plot.getRangeAxis() == null) { + logger.debug("debug: No range axis"); + plot.setRangeAxis(createYAxis(0)); + } + } + + + /** + * Expands X axes if only a point is shown. + */ + private void preparePointRanges(XYPlot plot) { + for (int i = 0, num = plot.getDomainAxisCount(); i < num; i++) { + + Integer key = Integer.valueOf(i); + Bounds b = getXBounds(key); + + + if (b != null && b.getLower().equals(b.getUpper())) { + logger.debug("Check whether to expand a x axis.i ("+b.getLower() + "-" + b.getUpper()+")"); + setXBounds(key, ChartHelper.expandBounds(b, 5)); + } + } + } + + + /** + * This method zooms the plot to the specified ranges in the attribute + * document or to the ranges specified by the min/max values in the + * datasets. Note: We determine the range manually if no zoom ranges + * are given, because JFreeCharts auto-zoom adds a margin to the left and + * right of the data area. + * + * @param plot The XYPlot. + */ + protected void autoZoom(XYPlot plot) { + logger.debug("Zoom to specified ranges."); + + Range xrange = getDomainAxisRange(); + Range yrange = getValueAxisRange(); + + ValueAxis xAxis = plot.getDomainAxis(); + + Range fixedXRange = getRangeForAxisFromSettings("X"); + if (fixedXRange != null) { + xAxis.setRange(fixedXRange); + } + else { + zoomX(plot, xAxis, getXBounds(0), xrange); + } + + for (int i = 0, num = plot.getRangeAxisCount(); i < num; i++) { + ValueAxis yaxis = plot.getRangeAxis(i); + + if (yaxis instanceof IdentifiableNumberAxis) { + IdentifiableNumberAxis idAxis = (IdentifiableNumberAxis) yaxis; + + Range fixedRange = getRangeForAxisFromSettings(idAxis.getId()); + if (fixedRange != null) { + yaxis.setRange(fixedRange); + continue; + } + } + + if (yaxis == null) { + logger.debug("Zoom problem: no Y Axis for index: " + i); + continue; + } + + logger.debug("Prepare zoom settings for y axis at index: " + i); + zoomY(plot, yaxis, getYBounds(Integer.valueOf(i)), yrange); + } + } + + + protected Range getDomainAxisRange() { + String[] ranges = getDomainAxisRangeFromRequest(); + + if (ranges == null || ranges.length < 2) { + logger.debug("No zoom range for domain axis specified."); + return null; + } + + if (ranges[0].length() > 0 && ranges[1].length() > 0) { + try { + double from = Double.parseDouble(ranges[0]); + double to = Double.parseDouble(ranges[1]); + + if (from == 0 && to == 0) { + logger.debug("No range specified. Lower and upper X == 0"); + return null; + } + + if (from > to) { + double tmp = to; + to = from; + from = tmp; + } + + return new Range(from, to); + } + catch (NumberFormatException nfe) { + logger.warn("Wrong values for domain axis range."); + } + } + + return null; + } + + + protected Range getValueAxisRange() { + String[] ranges = getValueAxisRangeFromRequest(); + + if (ranges == null || ranges.length < 2) { + logger.debug("No range specified. Lower and upper Y == 0"); + return null; + } + + if (ranges[0].length() > 0 && ranges[1].length() > 0) { + try { + double from = Double.parseDouble(ranges[0]); + double to = Double.parseDouble(ranges[1]); + + if (from == 0 && to == 0) { + logger.debug("No range specified. Lower and upper Y == 0"); + return null; + } + + return from > to + ? new Range(to, from) + : new Range(from, to); + } + catch (NumberFormatException nfe) { + logger.warn("Wrong values for value axis range."); + } + } + + return null; + } + + + protected boolean zoomX(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) { + return zoom(plot, axis, bounds, x); + } + + + protected boolean zoomY(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) { + return zoom(plot, axis, bounds, x); + } + + + /** + * Zooms the x axis to the range specified in the attribute document. + * + * @param plot The XYPlot. + * @param axis The axis the shoud be modified. + * @param bounds The whole range specified by a dataset. + * @param x A user defined range (null permitted). + * + * @return true, if a zoom range was specified, otherwise false. + */ + protected boolean zoom(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) { + + if (bounds == null) { + return false; + } + + if (x != null) { + Bounds computed = calculateZoom(bounds, x); + computed.applyBounds(axis, AXIS_SPACE); + + logger.debug("Zoom axis to: " + computed); + + return true; + } + + bounds.applyBounds(axis, AXIS_SPACE); + return false; + } + + /** + * Calculates the start and end km for zoomed charts. + * @param bounds The given total bounds (unzoomed). + * @param range The range specifying the zoom. + * + * @return The start and end km for the zoomed chart. + */ + protected Bounds calculateZoom(Bounds bounds, Range range) { + double min = bounds.getLower().doubleValue(); + double max = bounds.getUpper().doubleValue(); + + if (logger.isDebugEnabled()) { + logger.debug("Minimum is: " + min); + logger.debug("Maximum is: " + max); + logger.debug("Lower zoom is: " + range.getLowerBound()); + logger.debug("Upper zoom is: " + range.getUpperBound()); + } + + double diff = max > min ? max - min : min - max; + + DoubleBounds computed = new DoubleBounds( + min + range.getLowerBound() * diff, + min + range.getUpperBound() * diff); + return computed; + } + + /** + * Extract the minimum and maximum values for x and y axes + * which are stored in xRanges and yRanges. + * + * @param index The index of the y-Axis. + * + * @return a Range[] as follows: [x-Range, y-Range]. + */ + @Override + public Range[] getRangesForAxis(int index) { + logger.debug("getRangesForAxis " + index); + + Bounds rx = getXBounds(Integer.valueOf(0)); + Bounds ry = getYBounds(Integer.valueOf(index)); + + if (rx == null) { + logger.warn("Range for x axis not set." + + " Using default values: 0 - 1."); + rx = new DoubleBounds(0, 1); + } + if (ry == null) { + logger.warn("Range for y" + index + + " axis not set. Using default values: 0 - 1."); + ry = new DoubleBounds(0, 1); + } + + return new Range[] { + new Range(rx.getLower().doubleValue(), rx.getUpper().doubleValue()), + new Range(ry.getLower().doubleValue(), ry.getUpper().doubleValue()) + }; + } + + + /** Get X (usually horizontal) extent for given axis. */ + @Override + public Bounds getXBounds(int axis) { + return xBounds.get(axis); + } + + + /** Set X (usually horizontal) extent for given axis. */ + @Override + protected void setXBounds(int axis, Bounds bounds) { + if (bounds.getLower() == bounds.getUpper()) { + xBounds.put(axis, ChartHelper.expandBounds(bounds, 5d)); + } + else { + xBounds.put(axis, bounds); + } + } + + + /** Get Y (usually vertical) extent for given axis. */ + @Override + public Bounds getYBounds(int axis) { + return yBounds.get(axis); + } + + + /** Set Y (usually vertical) extent for given axis. */ + @Override + protected void setYBounds(int axis, Bounds bounds) { + yBounds.put(axis, bounds); + } + + + /** + * Adjusts the axes of a plot. This method sets the labelFont of the + * X axis. + * + * (Duplicate in TimeseriesChartGenerator) + * + * @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); + } + + + /** + * This method walks over all axes (domain and range) of plot and + * calls localizeDomainAxis() for domain axes or localizeRangeAxis() for + * range axes. + * + * @param plot The XYPlot. + */ + private void localizeAxes(XYPlot plot) { + for (int i = 0, num = plot.getDomainAxisCount(); i < num; i++) { + ValueAxis axis = plot.getDomainAxis(i); + + if (axis != null) { + localizeDomainAxis(axis); + } + else { + logger.warn("Domain axis at " + i + " is null."); + } + } + + for (int i = 0, num = plot.getRangeAxisCount(); i < num; i++) { + ValueAxis axis = plot.getRangeAxis(i); + + if (axis != null) { + localizeRangeAxis(axis); + } + else { + logger.warn("Range axis at " + i + " is null."); + } + } + } + + + /** + * Overrides the NumberFormat with the NumberFormat for the current locale + * that is provided by getLocale(). + * + * @param domainAxis The domain axis that needs localization. + */ + protected void localizeDomainAxis(ValueAxis domainAxis) { + NumberFormat nf = NumberFormat.getInstance(getLocale()); + ((NumberAxis) domainAxis).setNumberFormatOverride(nf); + } + + + /** + * Overrides the NumberFormat with the NumberFormat for the current locale + * that is provided by getLocale(). + * + * @param rangeAxis The domain axis that needs localization. + */ + protected void localizeRangeAxis(ValueAxis rangeAxis) { + NumberFormat nf = NumberFormat.getInstance(getLocale()); + ((NumberAxis) rangeAxis).setNumberFormatOverride(nf); + } + + + /** + * Do Points out. + */ + protected void doPoints( + Object o, + ArtifactAndFacet aandf, + ThemeDocument theme, + boolean visible, + int axisIndex + ) { + String seriesName = aandf.getFacetDescription(); + XYSeries series = new StyledXYSeries(seriesName, theme); + + // Add text annotations for single points. + List xy = new ArrayList(); + + try { + JSONArray points = new JSONArray((String) o); + for (int i = 0, P = points.length(); i < P; 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; + } + //logger.debug(" x " + x + " y " + y ); + series.add(x, y, false); + xy.add(new CollisionFreeXYTextAnnotation(name, x, y)); + } + } + catch(JSONException e){ + logger.error("Could not decode json."); + } + + RiverAnnotation annotation = new RiverAnnotation(null, null, null, theme); + annotation.setTextAnnotations(xy); + + // Do not generate second legend entry. (null was passed for the aand before). + if (visible) { + annotations.add(annotation); + } +// doAnnotations(annotations, null, theme, visible); + addAxisSeries(series, axisIndex, visible); + } + + + /** + * Create a hash from a legenditem. + * This hash can then be used to merge legend items labels. + * @return hash for given legenditem to identify mergeables. + */ + public static String legendItemHash(LegendItem li) { + // TODO Do proper implementation. Ensure that only mergable sets are created. + // getFillPaint() + // getFillPaintTransformer() + // getLabel() + // getLine() + // getLinePaint() + // getLineStroke() + // getOutlinePaint() + // getOutlineStroke() + // Shape getShape() + // String getToolTipText() + // String getURLText() + // boolean isLineVisible() + // boolean isShapeFilled() + // boolean isShapeOutlineVisible() + // boolean isShapeVisible() + String hash = li.getLinePaint().toString(); + String label = li.getLabel(); + if (label.startsWith("W (") || label.startsWith("W(")) { + hash += "-W-"; + } + else if (label.startsWith("Q(") || label.startsWith("Q (")) { + hash += "-Q-"; + } + + // WQ.java holds example of using regex Matcher/Pattern. + + return hash; + } + + /** True if x axis has been inverted. */ + public boolean isInverted() { + return inverted; + } + + + /** Set to true if x axis has been inverted. */ + public void setInverted(boolean inverted) { + this.inverted = inverted; + } + + /** Add the acutal data to the diagram according to the processors. + * For every outable facets, this function is + * called and handles the data accordingly. */ + @Override + public void doOut(ArtifactAndFacet bundle, ThemeDocument theme, + boolean visible) + { + String facetName = bundle.getFacetName(); + Facet facet = bundle.getFacet(); + + /* A conservative security check */ + if (facetName == null || facet == null) { + /* Can't happen,.. */ + logger.error("doOut called with null facet."); + } + + logger.debug("DoOut for facet: " + facetName); + + /* TODO Here should the configured processors come into play */ + List processors = new ArrayList(); + processors.add(new WOutProcessor()); + processors.add(new QOutProcessor()); + processors.add(new BedheightProcessor()); + processors.add(new BedDiffYearProcessor()); + processors.add(new BedDiffHeightYearProcessor()); + + for (Processor pr: processors) { + if (pr.canHandle(facetName)) { + // pr.doOut(this, bundle, theme, visible, 0); + } + } + } + + +}