ingo@369: package de.intevation.flys.exports; ingo@369: ingo@369: import java.awt.Color; ingo@2053: import java.awt.Font; ingo@1645: import java.text.NumberFormat; ingo@1679: import java.util.ArrayList; ingo@1684: import java.util.HashMap; ingo@1679: import java.util.List; ingo@1684: import java.util.Map; ingo@1679: christian@3906: import javax.swing.ImageIcon; ingo@1679: ingo@369: import org.apache.log4j.Logger; ingo@369: import org.jfree.chart.ChartFactory; ingo@369: import org.jfree.chart.JFreeChart; felix@2584: import org.jfree.chart.LegendItem; christian@3906: import org.jfree.chart.annotations.XYAnnotation; christian@3906: import org.jfree.chart.annotations.XYImageAnnotation; ingo@1679: import org.jfree.chart.annotations.XYTextAnnotation; ingo@369: import org.jfree.chart.axis.NumberAxis; ingo@652: import org.jfree.chart.axis.ValueAxis; christian@3906: import org.jfree.chart.plot.Marker; ingo@369: import org.jfree.chart.plot.PlotOrientation; ingo@369: import org.jfree.chart.plot.XYPlot; ingo@652: import org.jfree.data.Range; ingo@2242: import org.jfree.data.general.Series; christian@3906: import org.jfree.data.xy.XYDataset; ingo@923: import org.jfree.data.xy.XYSeries; ingo@923: import org.jfree.data.xy.XYSeriesCollection; christian@3906: import org.json.JSONArray; christian@3906: import org.json.JSONException; christian@3906: import org.w3c.dom.Document; ingo@369: ingo@2325: import de.intevation.artifactdatabase.state.ArtifactAndFacet; ingo@2398: import de.intevation.flys.jfree.Bounds; sascha@3257: import de.intevation.flys.jfree.CollisionFreeXYTextAnnotation; ingo@2587: import de.intevation.flys.jfree.DoubleBounds; ingo@1679: import de.intevation.flys.jfree.FLYSAnnotation; ingo@2074: import de.intevation.flys.jfree.StyledAreaSeriesCollection; ingo@2074: import de.intevation.flys.jfree.StyledXYSeries; ingo@369: felix@2206: ingo@369: /** ingo@369: * An abstract base class for creating XY charts. ingo@369: * felix@1940: * With respect to datasets, ranges and axis, there are following requirements: felix@1940: * felix@1940: * ingo@369: * @author Ingo Weinzierl ingo@369: */ ingo@369: public abstract class XYChartGenerator extends ChartGenerator { ingo@369: christian@3242: public class XYAxisDataset implements AxisDataset { felix@1940: /** Symbolic integer, but also coding the priority (0 goes first). */ felix@1940: protected int axisSymbol; christian@3906: felix@1940: /** List of assigned datasets (in order). */ felix@1940: protected List datasets; christian@3906: felix@1940: /** Range to use to include all given datasets. */ felix@1940: protected Range range; felix@1940: felix@2163: /** Index of axis in plot. */ felix@2163: protected int plotAxisIndex; felix@1940: felix@1940: /** Create AxisDataset. */ ingo@2238: public XYAxisDataset(int symb) { felix@1940: this.axisSymbol = symb; felix@1940: datasets = new ArrayList(); felix@1940: } felix@1940: felix@1940: /** Merge (or create given range with range so far (if any). */ felix@1940: private void mergeRanges(Range subRange) { felix@1959: // Avoid merging NaNs, as they take min/max place forever. felix@1959: if (subRange == null || felix@1959: Double.isNaN(subRange.getLowerBound()) || felix@1959: Double.isNaN(subRange.getUpperBound())) { felix@1959: return; felix@1959: } felix@1940: if (range == null) { felix@1940: range = subRange; felix@1940: return; felix@1940: } felix@1940: range = Range.combine(range, subRange); felix@1940: } felix@1940: ingo@2238: felix@2710: /** Add a dataset to internal list for this axis. */ ingo@2238: @Override ingo@2238: public void addDataset(XYDataset dataset) { ingo@2238: datasets.add(dataset); ingo@2238: includeYRange(((XYSeriesCollection) dataset).getSeries(0)); ingo@2238: } ingo@2238: felix@1940: /** Add a dataset, include its range. */ ingo@2238: public void addDataset(XYSeries series) { ingo@2238: addDataset(new XYSeriesCollection(series)); felix@1940: } felix@1940: ingo@2242: felix@2710: /** Set Range for this axis. */ ingo@2242: @Override ingo@2242: public void setRange(Range range) { ingo@2242: this.range = range; ingo@2242: } ingo@2242: ingo@2242: felix@2710: /** Get Range for this axis. */ ingo@2242: @Override ingo@2242: public Range getRange() { ingo@2242: return range; ingo@2242: } ingo@2242: ingo@2242: felix@2710: /** Get Array of Datasets. */ ingo@2242: @Override ingo@2242: public XYDataset[] getDatasets() { christian@3906: return datasets.toArray(new XYDataset[datasets.size()]); ingo@2242: } ingo@2242: felix@2710: felix@2710: /** Add a Dataset that describes an area. */ felix@2020: public void addArea(StyledAreaSeriesCollection series) { felix@2020: this.datasets.add(series); christian@3906: List allSeries = series.getSeries(); christian@3906: for (int n = 0; n < allSeries.size(); n++) { christian@3906: includeYRange((XYSeries)allSeries.get(n)); christian@3906: } felix@2005: } felix@2005: felix@2574: /** True if to be rendered as area. */ ingo@2242: @Override ingo@2242: public boolean isArea(XYDataset series) { felix@2020: return (series instanceof StyledAreaSeriesCollection); felix@2005: } felix@2005: felix@1940: /** Adjust range to include given dataset. */ felix@1958: public void includeYRange(XYSeries dataset) { felix@1940: mergeRanges(new Range(dataset.getMinY(), dataset.getMaxY())); felix@1940: } felix@1940: felix@1940: /** True if no datasets given. */ ingo@2242: @Override felix@1940: public boolean isEmpty() { felix@1940: return this.datasets.isEmpty(); felix@1940: } felix@2163: felix@2163: /** Set the 'real' axis index that this axis is mapped to. */ ingo@2242: @Override felix@2163: public void setPlotAxisIndex(int axisIndex) { felix@2163: this.plotAxisIndex = axisIndex; felix@2163: } felix@2163: felix@2163: /** Get the 'real' axis index that this axis is mapped to. */ ingo@2242: @Override felix@2163: public int getPlotAxisIndex() { felix@2163: return this.plotAxisIndex; felix@2163: } felix@2005: } // class AxisDataset felix@1940: felix@2710: /** Enumerator over existing axes. */ christian@3906: @Override felix@2246: protected abstract YAxisWalker getYAxisWalker(); ingo@2000: ingo@2587: public static final int AXIS_SPACE = 5; ingo@2587: felix@1048: /** The logger that is used in this generator. */ ingo@654: private static Logger logger = Logger.getLogger(XYChartGenerator.class); ingo@369: christian@3064: protected List domainMarkers = new ArrayList(); ingo@923: raimund@3169: protected List valueMarkers = new ArrayList(); ingo@1679: felix@1685: /** The max X range to include all X values of all series for each axis. */ ingo@2587: protected Map xBounds; ingo@1684: felix@1685: /** The max Y range to include all Y values of all series for each axis. */ ingo@2587: protected Map yBounds; ingo@1684: felix@1931: public XYChartGenerator() { ingo@2238: super(); ingo@2238: ingo@2587: xBounds = new HashMap(); ingo@2587: yBounds = new HashMap(); felix@1931: } felix@1931: felix@1931: ingo@369: /** felix@1930: * Generate the chart anew (including localized axis and all). felix@1930: */ christian@3906: @Override ingo@653: public JFreeChart generateChart() { ingo@653: logger.debug("XYChartGenerator.generateChart"); ingo@653: ingo@369: JFreeChart chart = ChartFactory.createXYLineChart( ingo@369: getChartTitle(), ingo@369: getXAxisLabel(), ingo@2051: getYAxisLabel(0), ingo@375: null, ingo@369: PlotOrientation.VERTICAL, ingo@2047: isLegendVisible(), ingo@369: false, ingo@369: false); ingo@369: felix@1931: XYPlot plot = (XYPlot) chart.getPlot(); ingo@3079: plot.setDomainAxis(createXAxis(getXAxisLabel())); ingo@3079: ingo@369: chart.setBackgroundPaint(Color.WHITE); felix@1931: plot.setBackgroundPaint(Color.WHITE); ingo@414: addSubtitles(chart); ingo@419: adjustPlot(plot); ingo@923: felix@1940: //debugAxis(plot); felix@1940: felix@1940: addDatasets(plot); felix@1940: felix@1940: //debugDatasets(plot); ingo@923: christian@3063: addMarkers(plot); christian@3063: felix@1935: recoverEmptyPlot(plot); felix@1940: preparePointRanges(plot); felix@1935: felix@1940: //debugAxis(plot); felix@1940: felix@1940: localizeAxes(plot); felix@1940: adjustAxes(plot); ingo@673: autoZoom(plot); ingo@652: felix@3249: //debugAxis(plot); felix@3249: felix@2138: // These have to go after the autozoom. felix@2161: addAnnotationsToRenderer(plot); felix@2138: felix@3616: // Add a logo (maybe). felix@3616: addLogo(plot); felix@3616: felix@3154: aggregateLegendEntries(plot); felix@2584: felix@1940: return chart; felix@1940: } ingo@924: felix@1940: felix@1940: /** felix@3621: * Return left most data points x value (on first axis). felix@3621: * Shortcut, especially to be overridden in (LS) charts where felix@3621: * axis could be inverted. felix@3621: */ felix@3621: protected double getLeftX() { felix@3621: return (Double)getXBounds(0).getLower(); felix@3621: } felix@3621: felix@3621: felix@3621: /** felix@3621: * Return right most data points x value (on first axis). felix@3621: * Shortcut, especially to be overridden in (LS) charts where felix@3621: * axis could be inverted. felix@3621: */ felix@3621: protected double getRightX() { felix@4434: return (Double)getXBounds(0).getUpper(); felix@3621: } felix@3621: felix@3621: felix@3616: /** Add a logo as background annotation to plot. */ felix@3616: protected void addLogo(XYPlot plot) { felix@3616: String logo = showLogo(); felix@3616: if (logo == null) { felix@3616: logger.debug("No logo to show chosen"); felix@3616: return; felix@3616: } felix@3616: sascha@3633: ImageIcon imageIcon = null; felix@3618: if (logo.equals("none")) { felix@3618: return; felix@3618: } felix@3623: /* felix@3623: If you want to add images, remember to change code in these places: felix@3623: flys-artifacts: felix@3623: XYChartGenerator.java felix@3623: Timeseries*Generator.java and felix@3623: in the flys-client projects Chart*Propert*Editor.java. felix@3638: Also, these images have to be put in felix@3638: flys-artifacts/src/main/resources/images/ felix@3638: flys-client/src/main/webapp/images/ felix@3623: */ felix@3623: java.net.URL imageURL; felix@3616: if (logo.equals("Intevation")) { felix@3623: imageURL = XYChartGenerator.class.getResource("/images/intevation.png"); felix@3616: } felix@3618: else { // TODO else if ... felix@3623: imageURL = XYChartGenerator.class.getResource("/images/bfg_logo.gif"); felix@3616: } felix@3623: imageIcon = new ImageIcon(imageURL); felix@3623: felix@4434: felix@3617: double xPos = 0d, yPos = 0d; felix@3617: felix@3618: String placeh = logoHPlace(); felix@3618: String placev = logoVPlace(); felix@3620: felix@3618: if (placev == null || placev.equals("none")) { felix@3618: placev = "top"; felix@3617: } felix@3618: if (placev.equals("top")) { felix@4434: yPos = (Double)getYBounds(0).getUpper(); felix@3617: } felix@3618: else if (placev.equals("bottom")) { felix@4434: yPos = (Double)getYBounds(0).getLower(); felix@3617: } felix@3618: else if (placev.equals("center")) { felix@4434: yPos = ((Double)getYBounds(0).getUpper() + (Double)getYBounds(0).getLower())/2d; felix@3617: } felix@3620: else { felix@3620: logger.debug("Unknown place-v value: " + placev); felix@3620: } felix@3618: felix@3618: if (placeh == null || placeh.equals("none")) { felix@3618: placeh = "center"; felix@3618: } felix@3618: if (placeh.equals("left")) { felix@3621: xPos = getLeftX(); felix@3618: } felix@3618: else if (placeh.equals("right")) { felix@3621: xPos = getRightX(); felix@3617: } felix@3618: else if (placeh.equals("center")) { felix@3618: xPos = ((Double)getXBounds(0).getUpper() + (Double)getXBounds(0).getLower())/2d; felix@3618: } felix@3620: else { felix@3620: logger.debug("Unknown place-h value: " + placeh); felix@3620: } felix@3620: felix@3617: logger.debug("logo position: " + xPos + "/" + yPos); felix@3617: felix@3620: org.jfree.ui.RectangleAnchor anchor felix@3620: = org.jfree.ui.RectangleAnchor.TOP; felix@3620: if (placev.equals("top")) { felix@3620: if (placeh.equals("left")) { felix@3620: anchor = org.jfree.ui.RectangleAnchor.TOP_LEFT; felix@3620: } felix@3620: else if (placeh.equals("right")) { felix@3620: anchor = org.jfree.ui.RectangleAnchor.TOP_RIGHT; felix@3620: } felix@3620: else if (placeh.equals("center")) { felix@3620: anchor = org.jfree.ui.RectangleAnchor.TOP; felix@3620: } felix@3620: } felix@3620: else if (placev.equals("bottom")) { felix@3620: if (placeh.equals("left")) { felix@3620: anchor = org.jfree.ui.RectangleAnchor.BOTTOM_LEFT; felix@3620: } felix@3620: else if (placeh.equals("right")) { felix@3620: anchor = org.jfree.ui.RectangleAnchor.BOTTOM_RIGHT; felix@3620: } felix@3620: else if (placeh.equals("center")) { felix@3620: anchor = org.jfree.ui.RectangleAnchor.BOTTOM; felix@3620: } felix@3620: } felix@3620: else if (placev.equals("center")) { felix@3620: if (placeh.equals("left")) { felix@3620: anchor = org.jfree.ui.RectangleAnchor.LEFT; felix@3620: } felix@3620: else if (placeh.equals("right")) { felix@3620: anchor = org.jfree.ui.RectangleAnchor.RIGHT; felix@3620: } felix@3620: else if (placeh.equals("center")) { felix@3620: anchor = org.jfree.ui.RectangleAnchor.CENTER; felix@3620: } felix@3620: } felix@3620: felix@3616: XYAnnotation xyannotation = sascha@3633: new XYImageAnnotation(xPos, yPos, imageIcon.getImage(), anchor); sascha@3633: plot.getRenderer().addAnnotation(xyannotation, org.jfree.ui.Layer.BACKGROUND); felix@3616: } felix@3616: felix@3616: ingo@3079: protected NumberAxis createXAxis(String label) { ingo@3079: return new NumberAxis(label); ingo@3079: } ingo@3079: ingo@3079: ingo@2238: @Override ingo@2242: protected Series getSeriesOf(XYDataset dataset, int idx) { ingo@2242: return ((XYSeriesCollection) dataset).getSeries(idx); ingo@2242: } ingo@2242: ingo@2242: ingo@2242: @Override ingo@2238: protected AxisDataset createAxisDataset(int idx) { ingo@2238: logger.debug("Create new XYAxisDataset for index: " + idx); ingo@2238: return new XYAxisDataset(idx); ingo@2238: } ingo@2238: ingo@2238: felix@1940: /** felix@1940: * Put debug output about datasets. felix@1940: */ felix@1940: public void debugDatasets(XYPlot plot) { felix@1940: logger.debug("Number of datasets: " + plot.getDatasetCount()); sascha@3087: for (int i = 0, P = plot.getDatasetCount(); i < P; i++) { felix@1940: if (plot.getDataset(i) == null) { felix@1940: logger.debug("Dataset #" + i + " is null"); felix@1940: continue; felix@1940: } felix@1940: logger.debug("Dataset #" + i + ":" + plot.getDataset(i)); felix@1958: XYSeriesCollection series = (XYSeriesCollection) plot.getDataset(i); felix@1958: logger.debug("X-Extend of Dataset: " + series.getSeries(0).getMinX() felix@1958: + " " + series.getSeries(0).getMaxX()); felix@1958: logger.debug("Y-Extend of Dataset: " + series.getSeries(0).getMinY() felix@1958: + " " + series.getSeries(0).getMaxY()); felix@1940: } felix@1940: } felix@1940: felix@1940: felix@1940: /** felix@1940: * Put debug output about axes. felix@1940: */ felix@1940: public void debugAxis(XYPlot plot) { felix@1940: logger.debug("..............."); sascha@3087: for (int i = 0, P = plot.getRangeAxisCount(); i < P; i++) { felix@1940: if (plot.getRangeAxis(i) == null) felix@2005: logger.debug("Range-Axis #" + i + " == null"); felix@1940: else { felix@2005: logger.debug("Range-Axis " + i + " != null [" + felix@1940: plot.getRangeAxis(i).getRange().getLowerBound() + felix@1940: " " + plot.getRangeAxis(i).getRange().getUpperBound() + felix@1940: "]"); felix@1940: } felix@3249: } felix@3249: for (int i = 0, P = plot.getDomainAxisCount(); i < P; i++) { felix@3249: if (plot.getDomainAxis(i) == null) felix@3249: logger.debug("Domain-Axis #" + i + " == null"); felix@3249: else { felix@3249: logger.debug("Domain-Axis " + i + " != null [" + felix@3249: plot.getDomainAxis(i).getRange().getLowerBound() + felix@3249: " " + plot.getDomainAxis(i).getRange().getUpperBound() + felix@3249: "]"); felix@3249: } felix@1940: } felix@1940: logger.debug("..............."); ingo@369: } ingo@369: ingo@369: felix@1685: /** felix@2005: * Registers an area to be drawn. felix@2163: * @param area Area to be drawn. felix@2163: * @param index 'axis index' felix@2163: * @param visible Whether or not to be visible (important for range calculations). felix@2005: */ felix@2020: public void addAreaSeries(StyledAreaSeriesCollection area, int index, boolean visible) { felix@2020: if (area == null) { felix@2005: logger.warn("Cannot yet render above/under curve."); felix@2005: return; felix@2005: } felix@2005: ingo@2238: XYAxisDataset axisDataset = (XYAxisDataset) getAxisDataset(index); felix@2005: felix@2020: if (visible) { felix@2020: axisDataset.addArea(area); felix@2020: } felix@2005: else { felix@2005: // TODO only range merging. felix@2005: } felix@2005: //TODO range merging. felix@2005: } felix@2005: felix@2020: felix@2005: /** felix@1935: * Add given series if visible, if not visible adjust ranges (such that felix@1935: * all points in data would be plotted once visible). christian@3906: * @param series the data series to include in plot. felix@2163: * @param index ('symbolic') index of the series and of its axis. felix@1931: * @param visible whether or not the data should be plotted. felix@1931: */ felix@1931: public void addAxisSeries(XYSeries series, int index, boolean visible) { felix@1931: if (series == null) { ingo@1684: return; ingo@1684: } ingo@1684: ingo@2242: logger.debug("Y Range of XYSeries: " + ingo@2242: series.getMinY() + " | " + series.getMaxY()); ingo@2242: ingo@2242: addAxisDataset(new XYSeriesCollection(series), index, visible); ingo@2242: ingo@2238: XYAxisDataset axisDataset = (XYAxisDataset) getAxisDataset(index); ingo@1684: ingo@2238: if (!visible) { felix@1940: // Do this also when not visible to have axis scaled by default such felix@1940: // that every data-point could be seen (except for annotations). felix@1958: axisDataset.includeYRange(series); felix@1940: } felix@1931: } felix@1931: felix@1940: felix@1931: /** christian@3063: * Add the given vertical marker to the chart. felix@1931: */ christian@3063: public void addDomainMarker(Marker marker) { ingo@4282: addDomainMarker(marker, true); ingo@4282: } ingo@4282: ingo@4282: ingo@4282: /** ingo@4282: * Add the given vertical marker to the chart.Note: the marker is ingo@4282: * added to the chart only if it is not null and if visible is true. ingo@4282: * @param marker The marker that should be added to the chart. ingo@4282: * @param visible The visibility of the marker. ingo@4282: */ ingo@4282: public void addDomainMarker(Marker marker, boolean visible) { ingo@4282: if (visible && marker != null) { ingo@4282: domainMarkers.add(marker); felix@1959: } ingo@1684: } ingo@1684: ingo@1684: felix@1930: /** raimund@3169: * Add the given vertical marker to the chart. felix@1711: */ raimund@3169: public void addValueMarker(Marker marker) { ingo@4282: addValueMarker(marker, true); ingo@4282: } ingo@4282: ingo@4282: ingo@4282: /** ingo@4282: * Add the given horizontal marker to the chart.Note: the marker is ingo@4282: * added to the chart only if it is not null and if visible is true. ingo@4282: * @param marker The marker that should be added to the chart. ingo@4282: * @param visible The visibility of the marker. ingo@4282: */ ingo@4282: public void addValueMarker(Marker marker, boolean visible) { ingo@4282: if (visible && marker != null) { ingo@4282: valueMarkers.add(marker); ingo@1684: } raimund@3169: } raimund@3169: raimund@3169: christian@3063: protected void addMarkers(XYPlot plot) { christian@3063: for(Marker marker : domainMarkers) { christian@3064: plot.addDomainMarker(marker); christian@3063: } raimund@3169: for(Marker marker : valueMarkers) { raimund@3169: plot.addRangeMarker(marker); raimund@3169: } felix@1931: } felix@1931: felix@1940: felix@1931: /** felix@1931: * Effect: extend range of x axis to include given limits. ingo@2587: * felix@3270: * @param bounds the given ("minimal") bounds. felix@1931: * @param index index of axis to be merged. felix@1931: */ ingo@2587: @Override ingo@2587: protected void combineXBounds(Bounds bounds, int index) { ingo@2587: if (!(bounds instanceof DoubleBounds)) { ingo@2587: logger.warn("Unsupported Bounds type: " + bounds.getClass()); felix@1959: return; felix@1959: } felix@1959: ingo@2587: DoubleBounds dBounds = (DoubleBounds) bounds; ingo@2587: ingo@2587: if (dBounds == null ingo@2587: || Double.isNaN((Double) dBounds.getLower()) ingo@2587: || Double.isNaN((Double) dBounds.getUpper())) { ingo@2587: return; ingo@2587: } ingo@2587: ingo@2587: Bounds old = getXBounds(index); felix@1931: felix@1931: if (old != null) { ingo@2587: dBounds = (DoubleBounds) dBounds.combine(old); felix@1931: } felix@1931: ingo@2587: setXBounds(index, dBounds); ingo@2587: } ingo@2587: ingo@2587: ingo@2587: @Override ingo@2587: protected void combineYBounds(Bounds bounds, int index) { ingo@2587: if (!(bounds instanceof DoubleBounds)) { ingo@2587: logger.warn("Unsupported Bounds type: " + bounds.getClass()); ingo@2587: return; ingo@2587: } ingo@2587: ingo@2587: DoubleBounds dBounds = (DoubleBounds) bounds; ingo@2587: ingo@2587: if (dBounds == null ingo@2587: || Double.isNaN((Double) dBounds.getLower()) ingo@2587: || Double.isNaN((Double) dBounds.getUpper())) { ingo@2587: return; ingo@2587: } ingo@2587: ingo@2587: Bounds old = getYBounds(index); ingo@2587: ingo@2587: if (old != null) { ingo@2587: dBounds = (DoubleBounds) dBounds.combine(old); ingo@2587: } ingo@2587: ingo@2587: setYBounds(index, dBounds); ingo@1684: } ingo@1684: ingo@1684: felix@1930: /** felix@1935: * If no data is visible, draw at least empty axis. felix@1935: */ felix@1935: private void recoverEmptyPlot(XYPlot plot) { felix@1935: if (plot.getRangeAxis() == null) { felix@1935: logger.debug("debug: No range axis"); sascha@2414: plot.setRangeAxis(createYAxis(0)); felix@1935: } ingo@923: } ingo@923: ingo@923: felix@1931: /** felix@1940: * Expands X axes if only a point is shown. felix@1931: */ ingo@1686: private void preparePointRanges(XYPlot plot) { ingo@1686: for (int i = 0, num = plot.getDomainAxisCount(); i < num; i++) { ingo@1686: ingo@2587: Integer key = Integer.valueOf(i); ingo@2587: Bounds b = getXBounds(key); ingo@2587: ingo@2587: felix@3249: if (b != null && b.getLower().equals(b.getUpper())) { raimund@3296: logger.debug("Check whether to expand a x axis.i ("+b.getLower() + "-" + b.getUpper()+")"); felix@3249: setXBounds(key, ChartHelper.expandBounds(b, 5)); ingo@1686: } ingo@1686: } ingo@1686: } ingo@1686: ingo@1686: felix@1931: /** ingo@654: * This method zooms the plot to the specified ranges in the attribute ingo@654: * document or to the ranges specified by the min/max values in the ingo@654: * datasets. Note: We determine the range manually if no zoom ranges ingo@654: * are given, because JFreeCharts auto-zoom adds a margin to the left and ingo@654: * right of the data area. ingo@653: * ingo@653: * @param plot The XYPlot. ingo@653: */ ingo@673: protected void autoZoom(XYPlot plot) { ingo@652: logger.debug("Zoom to specified ranges."); ingo@654: ingo@673: Range xrange = getDomainAxisRange(); ingo@673: Range yrange = getValueAxisRange(); ingo@654: ingo@2050: ValueAxis xAxis = plot.getDomainAxis(); ingo@2050: ingo@2050: Range fixedXRange = getRangeForAxisFromSettings("X"); ingo@2050: if (fixedXRange != null) { ingo@2050: xAxis.setRange(fixedXRange); ingo@2050: } ingo@2050: else { ingo@2587: zoomX(plot, xAxis, getXBounds(0), xrange); ingo@2050: } ingo@923: ingo@1699: for (int i = 0, num = plot.getRangeAxisCount(); i < num; i++) { ingo@673: ValueAxis yaxis = plot.getRangeAxis(i); ingo@654: ingo@2049: if (yaxis instanceof IdentifiableNumberAxis) { ingo@2050: IdentifiableNumberAxis idAxis = (IdentifiableNumberAxis) yaxis; ingo@2050: ingo@2050: Range fixedRange = getRangeForAxisFromSettings(idAxis.getId()); ingo@2050: if (fixedRange != null) { ingo@2050: yaxis.setRange(fixedRange); ingo@2050: continue; ingo@2050: } ingo@2049: } ingo@2049: ingo@673: if (yaxis == null) { ingo@1699: logger.debug("Zoom problem: no Y Axis for index: " + i); ingo@673: continue; ingo@673: } ingo@673: ingo@1699: logger.debug("Prepare zoom settings for y axis at index: " + i); ingo@2587: zoomY(plot, yaxis, getYBounds(Integer.valueOf(i)), yrange); ingo@654: } ingo@653: } ingo@652: ingo@653: ingo@2395: protected Range getDomainAxisRange() { ingo@2395: String[] ranges = getDomainAxisRangeFromRequest(); ingo@2395: ingo@2395: if (ranges == null || ranges.length < 2) { ingo@2395: logger.debug("No zoom range for domain axis specified."); ingo@2395: return null; ingo@2395: } ingo@2395: ingo@2395: if (ranges[0].length() > 0 && ranges[1].length() > 0) { ingo@2395: try { ingo@2395: double from = Double.parseDouble(ranges[0]); ingo@2395: double to = Double.parseDouble(ranges[1]); ingo@2395: ingo@2395: if (from == 0 && to == 0) { ingo@2395: logger.debug("No range specified. Lower and upper X == 0"); ingo@2395: return null; ingo@2395: } ingo@2395: ingo@2395: if (from > to) { ingo@2395: double tmp = to; ingo@2395: to = from; ingo@2395: from = tmp; ingo@2395: } ingo@2395: ingo@2395: return new Range(from, to); ingo@2395: } ingo@2395: catch (NumberFormatException nfe) { ingo@2395: logger.warn("Wrong values for domain axis range."); ingo@2395: } ingo@2395: } ingo@2395: ingo@2395: return null; ingo@2395: } ingo@2395: ingo@2395: ingo@2398: protected Range getValueAxisRange() { ingo@2398: String[] ranges = getValueAxisRangeFromRequest(); ingo@2398: ingo@2398: if (ranges == null || ranges.length < 2) { felix@4434: logger.debug("No range specified. Lower and upper Y == 0"); ingo@2398: return null; ingo@2398: } ingo@2398: ingo@2398: if (ranges[0].length() > 0 && ranges[1].length() > 0) { ingo@2398: try { ingo@2398: double from = Double.parseDouble(ranges[0]); ingo@2398: double to = Double.parseDouble(ranges[1]); ingo@2398: ingo@2398: if (from == 0 && to == 0) { ingo@2398: logger.debug("No range specified. Lower and upper Y == 0"); ingo@2398: return null; ingo@2398: } ingo@2398: ingo@2398: return from > to ingo@2398: ? new Range(to, from) ingo@2398: : new Range(from, to); ingo@2398: } ingo@2398: catch (NumberFormatException nfe) { ingo@2398: logger.warn("Wrong values for value axis range."); ingo@2398: } ingo@2398: } ingo@2398: ingo@2398: return null; ingo@2398: } ingo@2398: ingo@2398: ingo@2587: protected boolean zoomX(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) { ingo@2587: return zoom(plot, axis, bounds, x); ingo@718: } ingo@718: ingo@718: ingo@2587: protected boolean zoomY(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) { ingo@2587: return zoom(plot, axis, bounds, x); ingo@718: } ingo@718: ingo@718: ingo@653: /** ingo@653: * Zooms the x axis to the range specified in the attribute document. ingo@653: * felix@1958: * @param plot The XYPlot. felix@1958: * @param axis The axis the shoud be modified. felix@3270: * @param bounds The whole range specified by a dataset. felix@1958: * @param x A user defined range (null permitted). ingo@654: * ingo@654: * @return true, if a zoom range was specified, otherwise false. ingo@653: */ ingo@2587: protected boolean zoom(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) { sascha@1736: ingo@2587: if (bounds == null) { sascha@1736: return false; sascha@1736: } sascha@1736: ingo@673: if (x != null) { ingo@2587: double min = bounds.getLower().doubleValue(); ingo@2587: double max = bounds.getUpper().doubleValue(); ingo@2587: ingo@2587: if (logger.isDebugEnabled()) { ingo@2587: logger.debug("Minimum is: " + min); ingo@2587: logger.debug("Maximum is: " + max); ingo@2587: logger.debug("Lower zoom is: " + x.getLowerBound()); ingo@2587: logger.debug("Upper zoom is: " + x.getUpperBound()); ingo@2587: } ingo@2587: ingo@673: double diff = max > min ? max - min : min - max; ingo@652: ingo@2587: DoubleBounds computed = new DoubleBounds( ingo@673: min + x.getLowerBound() * diff, ingo@673: min + x.getUpperBound() * diff); ingo@673: ingo@2587: computed.applyBounds(axis, AXIS_SPACE); ingo@673: ingo@673: logger.debug("Zoom axis to: " + computed); ingo@654: ingo@654: return true; ingo@652: } ingo@654: ingo@2587: bounds.applyBounds(axis, AXIS_SPACE); ingo@654: return false; ingo@654: } ingo@654: ingo@654: ingo@654: /** felix@2710: * Extract the minimum and maximum values for x and y axes ingo@1684: * which are stored in xRanges and yRanges. ingo@654: * ingo@1684: * @param index The index of the y-Axis. ingo@654: * ingo@654: * @return a Range[] as follows: [x-Range, y-Range]. ingo@654: */ ingo@2261: @Override felix@1944: public Range[] getRangesForAxis(int index) { felix@1944: logger.debug("getRangesForAxis " + index); raimund@2132: ingo@2587: Bounds rx = getXBounds(Integer.valueOf(0)); ingo@2587: Bounds ry = getYBounds(Integer.valueOf(index)); raimund@2132: raimund@2132: if (rx == null) { raimund@2132: logger.warn("Range for x axis not set." + raimund@2132: " Using default values: 0 - 1."); ingo@2587: rx = new DoubleBounds(0, 1); raimund@2132: } raimund@2132: if (ry == null) { raimund@2132: logger.warn("Range for y" + index + raimund@2132: " axis not set. Using default values: 0 - 1."); ingo@2587: ry = new DoubleBounds(0, 1); raimund@2132: } ingo@2587: ingo@2587: return new Range[] { ingo@2587: new Range(rx.getLower().doubleValue(), rx.getUpper().doubleValue()), ingo@2587: new Range(ry.getLower().doubleValue(), ry.getUpper().doubleValue()) ingo@2587: }; ingo@652: } ingo@652: ingo@652: felix@2710: /** Get X (usually horizontal) extent for given axis. */ ingo@2398: @Override ingo@2398: public Bounds getXBounds(int axis) { ingo@2587: return xBounds.get(axis); ingo@2398: } ingo@2398: ingo@2398: felix@2710: /** Set X (usually horizontal) extent for given axis. */ ingo@2398: @Override ingo@2398: protected void setXBounds(int axis, Bounds bounds) { felix@3249: if (bounds.getLower() == bounds.getUpper()) { felix@3249: xBounds.put(axis, ChartHelper.expandBounds(bounds, 5d)); felix@2138: } felix@2138: else { felix@3249: xBounds.put(axis, bounds); felix@2138: } ingo@2398: } ingo@2398: ingo@2398: felix@2710: /** Get Y (usually vertical) extent for given axis. */ ingo@2398: @Override ingo@2398: public Bounds getYBounds(int axis) { ingo@2587: return yBounds.get(axis); ingo@2398: } ingo@2398: ingo@2398: felix@2710: /** Set Y (usually vertical) extent for given axis. */ ingo@2398: @Override ingo@2398: protected void setYBounds(int axis, Bounds bounds) { ingo@2587: yBounds.put(axis, bounds); ingo@2398: } ingo@2398: ingo@2398: ingo@369: /** ingo@2054: * Adjusts the axes of a plot. This method sets the labelFont of the ingo@2054: * X axis. ingo@2054: * ingo@3650: * (Duplicate in TimeseriesChartGenerator) ingo@3650: * ingo@369: * @param plot The XYPlot of the chart. ingo@369: */ ingo@369: protected void adjustAxes(XYPlot plot) { ingo@2054: ValueAxis xaxis = plot.getDomainAxis(); ingo@2054: ingo@2054: ChartSettings chartSettings = getChartSettings(); ingo@2054: if (chartSettings == null) { ingo@2054: return; felix@1931: } ingo@2054: ingo@2054: Font labelFont = new Font( ingo@2054: DEFAULT_FONT_NAME, ingo@2054: Font.BOLD, ingo@2054: getXAxisLabelFontSize()); ingo@2054: ingo@2054: xaxis.setLabelFont(labelFont); ingo@2590: xaxis.setTickLabelFont(labelFont); ingo@369: } ingo@414: ingo@414: felix@1940: /** ingo@1645: * This method walks over all axes (domain and range) of plot and ingo@1645: * calls localizeDomainAxis() for domain axes or localizeRangeAxis() for ingo@1645: * range axes. ingo@1645: * ingo@1645: * @param plot The XYPlot. ingo@1645: */ ingo@1645: private void localizeAxes(XYPlot plot) { ingo@1645: for (int i = 0, num = plot.getDomainAxisCount(); i < num; i++) { ingo@1645: ValueAxis axis = plot.getDomainAxis(i); ingo@1645: ingo@1645: if (axis != null) { ingo@1645: localizeDomainAxis(axis); ingo@1645: } ingo@1645: else { ingo@1645: logger.warn("Domain axis at " + i + " is null."); ingo@1645: } ingo@1645: } ingo@1645: ingo@1645: for (int i = 0, num = plot.getRangeAxisCount(); i < num; i++) { ingo@1645: ValueAxis axis = plot.getRangeAxis(i); ingo@1645: ingo@1645: if (axis != null) { ingo@1645: localizeRangeAxis(axis); ingo@1645: } ingo@1645: else { ingo@1645: logger.warn("Range axis at " + i + " is null."); ingo@1645: } ingo@1645: } ingo@1645: } ingo@1645: ingo@1645: ingo@1645: /** ingo@1645: * Overrides the NumberFormat with the NumberFormat for the current locale ingo@1645: * that is provided by getLocale(). ingo@1645: * ingo@1645: * @param domainAxis The domain axis that needs localization. ingo@1645: */ ingo@1645: protected void localizeDomainAxis(ValueAxis domainAxis) { ingo@1645: NumberFormat nf = NumberFormat.getInstance(getLocale()); ingo@1645: ((NumberAxis) domainAxis).setNumberFormatOverride(nf); ingo@1645: } ingo@1645: ingo@1645: ingo@1645: /** ingo@1645: * Overrides the NumberFormat with the NumberFormat for the current locale ingo@1645: * that is provided by getLocale(). ingo@1645: * felix@3270: * @param rangeAxis The domain axis that needs localization. ingo@1645: */ ingo@1645: protected void localizeRangeAxis(ValueAxis rangeAxis) { ingo@1645: NumberFormat nf = NumberFormat.getInstance(getLocale()); ingo@1645: ((NumberAxis) rangeAxis).setNumberFormatOverride(nf); ingo@1645: } ingo@1645: ingo@1645: felix@1932: /** felix@2206: * Do Points out. felix@2206: */ felix@2206: protected void doPoints( felix@2206: Object o, ingo@2325: ArtifactAndFacet aandf, felix@2206: Document theme, felix@2206: boolean visible, felix@2206: int axisIndex felix@2206: ) { ingo@2325: String seriesName = aandf.getFacetDescription(); felix@2206: XYSeries series = new StyledXYSeries(seriesName, theme); felix@2206: felix@2206: // Add text annotations for single points. felix@2206: List xy = new ArrayList(); felix@2206: felix@2206: try { felix@2206: JSONArray points = new JSONArray((String) o); sascha@3087: for (int i = 0, P = points.length(); i < P; i++) { felix@2206: JSONArray array = points.getJSONArray(i); felix@2206: double x = array.getDouble(0); felix@2206: double y = array.getDouble(1); felix@2206: String name = array.getString(2); felix@2206: boolean act = array.getBoolean(3); felix@2206: if (!act) { felix@2206: continue; felix@2206: } felix@2206: //logger.debug(" x " + x + " y " + y ); felix@2206: series.add(x, y, false); felix@2206: xy.add(new CollisionFreeXYTextAnnotation(name, x, y)); felix@2206: } felix@2206: } felix@2206: catch(JSONException e){ felix@2206: logger.error("Could not decode json."); felix@2206: } ingo@924: felix@2206: FLYSAnnotation annotations = new FLYSAnnotation(null, null, null, theme); felix@2206: annotations.setTextAnnotations(xy); felix@2206: felix@2574: // Do not generate second legend entry. (null was passed for the aand before). felix@2574: doAnnotations(annotations, null, theme, visible); felix@2206: addAxisSeries(series, axisIndex, visible); felix@2206: } felix@2206: felix@2206: felix@2584: /** felix@2584: * Create a hash from a legenditem. felix@2584: * This hash can then be used to merge legend items labels. felix@2584: * @return hash for given legenditem to identify mergeables. felix@2584: */ felix@2584: public static String legendItemHash(LegendItem li) { felix@2584: // TODO Do proper implementation. Ensure that only mergable sets are created. felix@2584: // getFillPaint() felix@2584: // getFillPaintTransformer() felix@2584: // getLabel() felix@2584: // getLine() felix@2584: // getLinePaint() felix@2584: // getLineStroke() felix@2584: // getOutlinePaint() sascha@3076: // getOutlineStroke() felix@2584: // Shape getShape() felix@2584: // String getToolTipText() felix@2584: // String getURLText() felix@2584: // boolean isLineVisible() felix@2584: // boolean isShapeFilled() felix@2584: // boolean isShapeOutlineVisible() felix@2584: // boolean isShapeVisible() felix@2711: String hash = li.getLinePaint().toString(); felix@2711: String label = li.getLabel(); felix@2711: if (label.startsWith("W (") || label.startsWith("W(")) { felix@2711: hash += "-W-"; felix@2584: } felix@2711: else if (label.startsWith("Q(") || label.startsWith("Q (")) { felix@2711: hash += "-Q-"; felix@2584: } felix@2584: felix@2711: // WQ.java holds example of using regex Matcher/Pattern. felix@2584: felix@2711: return hash; felix@2584: } felix@4434: ingo@369: } ingo@369: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :