ingo@369: package de.intevation.flys.exports; ingo@369: ingo@419: import java.awt.BasicStroke; ingo@369: import java.awt.Color; ingo@2053: import java.awt.Font; felix@2020: import java.awt.Paint; ingo@419: import java.awt.Stroke; ingo@369: ingo@1645: import java.text.NumberFormat; ingo@1645: 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: ingo@1679: import org.w3c.dom.Document; ingo@1679: ingo@369: import org.apache.log4j.Logger; ingo@369: ingo@369: import org.jfree.chart.ChartFactory; ingo@369: import org.jfree.chart.JFreeChart; ingo@1679: import org.jfree.chart.LegendItemCollection; felix@2138: import org.jfree.chart.annotations.XYBoxAnnotation; felix@2161: import org.jfree.chart.annotations.XYLineAnnotation; ingo@1679: import org.jfree.chart.annotations.XYTextAnnotation; ingo@369: import org.jfree.chart.axis.NumberAxis; ingo@652: import org.jfree.chart.axis.ValueAxis; 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; ingo@923: import org.jfree.data.xy.XYSeries; ingo@923: import org.jfree.data.xy.XYSeriesCollection; felix@1931: import org.jfree.data.xy.XYDataset; ingo@654: ingo@654: import org.jfree.ui.RectangleInsets; felix@2161: import org.jfree.ui.TextAnchor; ingo@369: felix@1849: import de.intevation.artifactdatabase.state.Facet; felix@1849: ingo@1679: import de.intevation.flys.jfree.FLYSAnnotation; raimund@1738: import de.intevation.flys.jfree.StickyAxisAnnotation; felix@2161: import de.intevation.flys.jfree.CollisionFreeXYTextAnnotation; ingo@2074: import de.intevation.flys.jfree.StyledAreaSeriesCollection; ingo@2074: import de.intevation.flys.jfree.StyledXYSeries; ingo@369: sascha@1754: import de.intevation.flys.utils.ThemeAccess; ingo@369: felix@2138: import de.intevation.flys.artifacts.model.HYKFactory; felix@2138: felix@2206: import org.json.JSONArray; felix@2206: import org.json.JSONException; felix@2206: 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: felix@2020: // TODO Consider storing the renderer here. ingo@2238: private class XYAxisDataset implements AxisDataset { felix@1940: /** Symbolic integer, but also coding the priority (0 goes first). */ felix@1940: protected int axisSymbol; felix@1940: /** List of assigned datasets (in order). */ felix@1940: protected List datasets; felix@1940: /** Range to use to include all given datasets. */ felix@1940: protected Range range; 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: 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: ingo@2242: @Override ingo@2242: public void setRange(Range range) { ingo@2242: this.range = range; ingo@2242: } ingo@2242: ingo@2242: ingo@2242: @Override ingo@2242: public Range getRange() { ingo@2242: return range; ingo@2242: } ingo@2242: ingo@2242: ingo@2242: @Override ingo@2242: public XYDataset[] getDatasets() { ingo@2242: return (XYDataset[]) ingo@2242: datasets.toArray(new XYDataset[datasets.size()]); ingo@2242: } ingo@2242: felix@2020: public void addArea(StyledAreaSeriesCollection series) { felix@2020: this.datasets.add(series); felix@2005: } felix@2005: felix@2005: /** True if to be renedered 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@1940: felix@2020: /** Override to make axis information available. */ ingo@2000: protected YAxisWalker getYAxisWalker() { ingo@2000: return new YAxisWalker() { felix@2020: /** Get number of items. */ ingo@2000: @Override ingo@2000: public int length() { ingo@2000: return 0; ingo@2000: } ingo@2000: felix@2020: /** Get identifier for this index. */ ingo@2000: @Override ingo@2000: public String getId(int idx) { ingo@2000: return null; ingo@2000: } ingo@2000: }; ingo@2000: } ingo@2000: ingo@2000: felix@1048: /** The logger that is used in this generator. */ ingo@654: private static Logger logger = Logger.getLogger(XYChartGenerator.class); ingo@369: ingo@1679: /** List of annotations to insert in plot. */ ingo@1679: protected List annotations; ingo@1679: felix@1685: /** The max X range to include all X values of all series for each axis. */ ingo@1684: protected Map xRanges; ingo@1684: felix@1685: /** The max Y range to include all Y values of all series for each axis. */ ingo@1684: protected Map yRanges; ingo@1684: ingo@419: felix@1931: public XYChartGenerator() { ingo@2238: super(); ingo@2238: felix@1931: xRanges = new HashMap(); felix@1931: yRanges = new HashMap(); felix@1931: } felix@1931: felix@1931: ingo@369: /** felix@1930: * Generate the chart anew (including localized axis and all). felix@1930: */ 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@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: 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@2138: // These have to go after the autozoom. felix@2161: addAnnotationsToRenderer(plot); felix@2138: felix@1940: return chart; felix@1940: } ingo@924: felix@1940: 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@2242: protected void setXRange(int axis, Range range) { ingo@2242: xRanges.put(Integer.valueOf(axis), range); ingo@2242: } ingo@2242: ingo@2242: ingo@2242: @Override ingo@2242: protected void setYRange(int axis, Range range) { ingo@2242: yRanges.put(Integer.valueOf(axis), range); 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()); felix@1940: for (int i = 0; i < plot.getDatasetCount(); 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("..............."); felix@1940: for (int i = 0; i < plot.getRangeAxisCount(); 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@1940: 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). felix@1931: * @param series the dataseries 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: /** felix@1931: * Effect: extend range of x axis to include given limits. felix@1931: * @param range the given ("minimal") range. felix@1931: * @param index index of axis to be merged. felix@1931: */ ingo@2238: protected void combineXRanges(Range range, int index) { felix@1931: felix@1959: if (range == null felix@1959: || Double.isNaN(range.getLowerBound()) felix@1959: || Double.isNaN(range.getUpperBound())) { felix@1959: return; felix@1959: } felix@1959: felix@1931: Range old = xRanges.get(index); felix@1931: felix@1931: if (old != null) { felix@1931: range = Range.combine(old, range); felix@1931: } felix@1931: felix@1931: xRanges.put(index, range); ingo@1684: } ingo@1684: ingo@1684: felix@1930: /** felix@1711: * Adds annotations to list (if visible is true). felix@1711: */ ingo@1684: public void addAnnotations(FLYSAnnotation annotation, boolean visible) { ingo@1684: if (!visible) { ingo@1684: return; ingo@1684: } ingo@1684: ingo@1679: if (annotations == null) { ingo@1679: annotations = new ArrayList(); ingo@1679: } ingo@1679: ingo@1679: annotations.add(annotation); ingo@1679: } ingo@1679: ingo@1679: felix@1931: /** 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"); felix@1935: 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++) { felix@1940: logger.debug("Check whether to expand a x axis."); sascha@1698: Integer key = Integer.valueOf(i); ingo@1686: ingo@1686: Range r = xRanges.get(key); ingo@1687: if (r != null && r.getLowerBound() == r.getUpperBound()) { ingo@2242: setXRange(key, ChartHelper.expandRange(r, 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@2050: zoomX(plot, xAxis, xRanges.get(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@1699: zoomY(plot, yaxis, yRanges.get(Integer.valueOf(i)), yrange); ingo@654: } ingo@653: } ingo@652: ingo@653: ingo@718: protected boolean zoomX(XYPlot plot, ValueAxis axis, Range range, Range x) { ingo@718: return zoom(plot, axis, range, x); ingo@718: } ingo@718: ingo@718: ingo@718: protected boolean zoomY(XYPlot plot, ValueAxis axis, Range range, Range x) { ingo@718: return zoom(plot, axis, range, 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. ingo@673: * @param range 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@673: protected boolean zoom(XYPlot plot, ValueAxis axis, Range range, Range x) { sascha@1736: sascha@1736: if (range == null) { sascha@1736: return false; sascha@1736: } sascha@1736: ingo@673: if (x != null) { ingo@673: double min = range.getLowerBound(); ingo@673: double max = range.getUpperBound(); ingo@673: double diff = max > min ? max - min : min - max; ingo@652: ingo@673: Range computed = new Range( ingo@673: min + x.getLowerBound() * diff, ingo@673: min + x.getUpperBound() * diff); ingo@673: ingo@717: axis.setRangeWithMargins(computed); ingo@673: ingo@673: logger.debug("Zoom axis to: " + computed); ingo@654: ingo@654: return true; ingo@652: } ingo@654: ingo@717: axis.setRangeWithMargins(range); ingo@654: return false; ingo@654: } ingo@654: ingo@654: ingo@654: /** ingo@1684: * This method extracts 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: */ felix@1944: public Range[] getRangesForAxis(int index) { felix@1944: logger.debug("getRangesForAxis " + index); raimund@2132: raimund@2132: Range rx = xRanges.get(Integer.valueOf(0)); raimund@2132: Range ry = yRanges.get(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."); raimund@2132: rx = new Range(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."); raimund@2132: ry = new Range(0, 1); raimund@2132: } raimund@2132: return new Range[] {rx, ry}; ingo@652: } ingo@652: ingo@652: felix@2138: /** Get color for hyk zones by their type (which is the name). */ felix@2138: public Paint colorForHYKZone(String zoneName) { felix@2138: if (zoneName.startsWith("R")) { felix@2138: // Brownish. felix@2138: return new Color(153, 60, 0); felix@2138: } felix@2138: else if (zoneName.startsWith("V")) { felix@2138: // Greenish. felix@2138: return new Color(0, 255, 0); felix@2138: } felix@2138: else if (zoneName.startsWith("B")) { felix@2138: // Grayish. felix@2138: return new Color(128, 128, 128); felix@2138: } felix@2138: else if (zoneName.startsWith("H")) { felix@2138: // Blueish. felix@2138: return new Color(0, 0, 255); felix@2138: } felix@2138: else { felix@2138: // Default. felix@2138: logger.debug("Unknown zone type found."); felix@2138: return new Color(255, 0, 0); felix@2138: } felix@2138: } felix@2138: felix@2138: felix@2161: /** felix@2161: * Add a text and a line annotation. felix@2161: */ felix@2161: public void addStickyAnnotation( felix@2161: StickyAxisAnnotation annotation, felix@2161: XYPlot plot, felix@2161: Area area, felix@2161: ThemeAccess.LineStyle lineStyle, felix@2161: ThemeAccess.TextStyle textStyle felix@2161: ) { felix@2161: // OPTIMIZE pre-calculate area-related values felix@2161: final float TEXT_OFF = 0.03f; felix@2161: final float LINE_OFF = 0.02f; felix@2161: felix@2161: XYLineAnnotation lineAnnotation = null; felix@2161: XYTextAnnotation textAnnotation = null; felix@2161: felix@2163: int rendererIndex = 0; felix@2163: felix@2161: if (annotation.atX()) { felix@2161: textAnnotation = new CollisionFreeXYTextAnnotation( felix@2161: annotation.getText(), annotation.getPos(), area.ofGround(TEXT_OFF)); felix@2161: // OPTIMIZE externalize the calculation involving PI. felix@2161: textAnnotation.setRotationAngle(270f*Math.PI/180f); felix@2161: // Style the line. felix@2161: if (lineStyle != null) { felix@2161: lineAnnotation = new XYLineAnnotation(annotation.getPos(), felix@2161: area.atGround(), annotation.getPos(), area.ofGround(LINE_OFF), felix@2161: new BasicStroke(lineStyle.getWidth()),lineStyle.getColor()); felix@2163: textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT); felix@2163: textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT); felix@2161: } felix@2161: else { felix@2161: lineAnnotation = new XYLineAnnotation(annotation.getPos(), felix@2161: area.atGround(), annotation.getPos(), area.ofGround(LINE_OFF)); felix@2163: textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT); felix@2163: textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT); felix@2161: } felix@2161: } felix@2161: else { felix@2163: // Do the more complicated case where we stick to the Y-Axis. felix@2163: // There is one nasty case (duration curves, where annotations felix@2163: // might stick to the second y-axis). ingo@2238: XYAxisDataset dataset = (XYAxisDataset) getAxisDataset( felix@2163: new Integer(annotation.getAxisSymbol())); felix@2163: if (dataset == null) { felix@2163: logger.warn("Annotation should stick to unfindable y-axis: " felix@2163: + annotation.getAxisSymbol()); felix@2163: rendererIndex = 0; felix@2161: } felix@2161: else { felix@2163: rendererIndex = dataset.getPlotAxisIndex(); felix@2163: } felix@2163: felix@2163: if (rendererIndex != 0) { felix@2163: // OPTIMIZE: Pass a different area to this function, felix@2163: // do the adding to renderer outside (let this felix@2163: // function return the annotations). felix@2163: // Note that this path is travelled rarely. felix@2163: Area area2 = new Area(plot.getDomainAxis(), plot.getRangeAxis(rendererIndex)); felix@2163: textAnnotation = new CollisionFreeXYTextAnnotation( felix@2163: annotation.getText(), area2.ofRight(TEXT_OFF), annotation.getPos()); felix@2163: textAnnotation.setRotationAnchor(TextAnchor.CENTER_RIGHT); felix@2163: textAnnotation.setTextAnchor(TextAnchor.CENTER_RIGHT); felix@2163: // Style the line. felix@2163: if (lineStyle != null) { felix@2163: lineAnnotation = new XYLineAnnotation(area2.ofRight(LINE_OFF), felix@2163: annotation.getPos(), area2.atRight(), felix@2163: annotation.getPos(), new BasicStroke(lineStyle.getWidth()), felix@2163: lineStyle.getColor()); felix@2163: } felix@2163: else { felix@2163: lineAnnotation = new XYLineAnnotation(area2.atRight(), felix@2163: annotation.getPos(), area2.ofRight(LINE_OFF), annotation.getPos()); felix@2163: } felix@2163: } felix@2163: else { felix@2163: textAnnotation = new CollisionFreeXYTextAnnotation( felix@2163: annotation.getText(), area.ofLeft(TEXT_OFF), annotation.getPos()); felix@2163: textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT); felix@2163: textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT); felix@2163: // Style the line. felix@2163: if (lineStyle != null) { felix@2163: lineAnnotation = new XYLineAnnotation(area.atLeft(), felix@2163: annotation.getPos(), area.ofLeft(LINE_OFF), felix@2163: annotation.getPos(), new BasicStroke(lineStyle.getWidth()), felix@2163: lineStyle.getColor()); felix@2163: } felix@2163: else { felix@2163: lineAnnotation = new XYLineAnnotation(area.atLeft(), felix@2163: annotation.getPos(), area.ofLeft(LINE_OFF), annotation.getPos()); felix@2163: } felix@2161: } felix@2161: } felix@2161: felix@2161: // Style the text. felix@2161: if (textStyle != null) { felix@2161: textStyle.apply(textAnnotation); felix@2161: } felix@2161: felix@2161: // Add the Annotations to renderer. felix@2163: plot.getRenderer(rendererIndex).addAnnotation(textAnnotation, felix@2161: org.jfree.ui.Layer.BACKGROUND); felix@2163: plot.getRenderer(rendererIndex).addAnnotation(lineAnnotation, felix@2161: org.jfree.ui.Layer.BACKGROUND); felix@2161: } felix@2161: felix@2161: felix@2161: /** Add annotations (Sticky, Text and hyk zones). */ felix@2161: public void addAnnotationsToRenderer(XYPlot plot) { felix@2161: logger.debug("XYChartGenerator.addAnnotationsToRenderer"); felix@2138: felix@2143: if (annotations == null) { felix@2143: logger.debug("XYChartGenerator.addBoxAnnotations: no annotations."); felix@2143: return; felix@2143: } felix@2143: felix@2152: // Paints for the boxes/lines. felix@2138: Stroke basicStroke = new BasicStroke(1.0f); felix@2138: felix@2161: Paint linePaint = new Color(255, 0,0,60); felix@2161: Paint fillPaint = new Color(0, 255,0,60); felix@2161: Paint tranPaint = new Color(0, 0,0, 0); felix@2138: felix@2161: // OPTMIMIZE: Pre-calculate positions felix@2161: Area area = new Area( felix@2161: plot.getDomainAxis(0).getRange(), felix@2161: plot.getRangeAxis().getRange()); felix@2138: felix@2161: // Walk over all Annotation sets. felix@2161: for (FLYSAnnotation fa: annotations) { felix@2152: felix@2152: // Access text styling, if any. felix@2152: Document theme = fa.getTheme(); felix@2152: ThemeAccess.TextStyle textStyle = null; felix@2161: ThemeAccess.LineStyle lineStyle = null; felix@2161: felix@2161: // Get Themeing information and add legend item. felix@2152: if (theme != null) { felix@2152: ThemeAccess themeAccess = new ThemeAccess(theme); felix@2152: textStyle = themeAccess.parseTextStyle(); felix@2161: lineStyle = themeAccess.parseLineStyle(); felix@2184: if (fa.getLabel() != null) { felix@2184: LegendItemCollection lic = new LegendItemCollection(); felix@2184: LegendItemCollection old = plot.getFixedLegendItems(); felix@2184: lic.add(createLegendItem(theme, fa.getLabel())); felix@2184: // (Re-)Add prior legend entries. felix@2184: if (old != null) { felix@2184: old.addAll(lic); felix@2184: } felix@2184: else { felix@2184: old = lic; felix@2184: } felix@2184: plot.setFixedLegendItems(old); felix@2161: } felix@2152: } felix@2152: felix@2161: // The 'Sticky' Annotations (at axis, with line and text). felix@2161: for (StickyAxisAnnotation sta: fa.getAxisTextAnnotations()) { felix@2161: addStickyAnnotation(sta, plot, area, lineStyle, textStyle); felix@2161: } felix@2161: felix@2183: // Other Text Annotations. felix@2161: for (XYTextAnnotation ta: fa.getTextAnnotations()) { felix@2193: // Style the text. felix@2193: if (textStyle != null) { felix@2193: textStyle.apply(ta); felix@2193: } felix@2193: ta.setY(area.above(0.05d, ta.getY())); felix@2183: plot.getRenderer().addAnnotation(ta, org.jfree.ui.Layer.FOREGROUND); felix@2161: } felix@2161: felix@2161: // Hyks. felix@2138: for (HYKFactory.Zone zone: fa.getBoxes()) { felix@2161: // For each zone, create a box to fill with color, a box to draw felix@2161: // the lines and a text to display the type. felix@2138: fillPaint = colorForHYKZone(zone.getName()); felix@2138: felix@2161: XYBoxAnnotation boxA = new XYBoxAnnotation(zone.getFrom(), area.atGround(), felix@2161: zone.getTo(), area.ofGround(0.03f), basicStroke, tranPaint, fillPaint); felix@2161: XYBoxAnnotation boxB = new XYBoxAnnotation(zone.getFrom(), area.atGround(), felix@2161: zone.getTo(), area.atTop(), basicStroke, fillPaint, tranPaint); felix@2138: felix@2138: XYTextAnnotation tex = new XYTextAnnotation(zone.getName(), felix@2138: zone.getFrom() + (zone.getTo() - zone.getFrom()) / 2.0d, felix@2161: area.ofGround(0.015f)); felix@2152: if (textStyle != null) { felix@2152: textStyle.apply(tex); felix@2152: } felix@2138: felix@2161: plot.getRenderer().addAnnotation(boxA, org.jfree.ui.Layer.BACKGROUND); felix@2161: plot.getRenderer().addAnnotation(boxB, org.jfree.ui.Layer.BACKGROUND); felix@2161: plot.getRenderer().addAnnotation(tex, org.jfree.ui.Layer.BACKGROUND); felix@2138: } felix@2138: } felix@2138: } ingo@1679: felix@2152: ingo@369: /** ingo@2054: * Adjusts the axes of a plot. This method sets the labelFont of the ingo@2054: * X axis. ingo@2054: * 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@369: } ingo@414: ingo@414: felix@1940: /** felix@1940: * Set some Stroke/Grid defaults. felix@1940: */ ingo@419: protected void adjustPlot(XYPlot plot) { ingo@419: Stroke gridStroke = new BasicStroke( ingo@419: DEFAULT_GRID_LINE_WIDTH, ingo@419: BasicStroke.CAP_BUTT, ingo@419: BasicStroke.JOIN_MITER, ingo@419: 3.0f, ingo@419: new float[] { 3.0f }, ingo@419: 0.0f); ingo@419: ingo@2047: ChartSettings cs = getChartSettings(); ingo@2047: boolean isGridVisible = cs != null ? isGridVisible(cs) : true; ingo@2047: ingo@419: plot.setDomainGridlineStroke(gridStroke); ingo@419: plot.setDomainGridlinePaint(DEFAULT_GRID_COLOR); ingo@2047: plot.setDomainGridlinesVisible(isGridVisible); ingo@419: ingo@419: plot.setRangeGridlineStroke(gridStroke); ingo@419: plot.setRangeGridlinePaint(DEFAULT_GRID_COLOR); ingo@2047: plot.setRangeGridlinesVisible(isGridVisible); ingo@654: ingo@654: plot.setAxisOffset(new RectangleInsets(0d, 0d, 0d, 0d)); ingo@419: } ingo@419: ingo@419: ingo@1645: /** 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: * ingo@1645: * @param domainAxis 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@1861: * Register annotations like MainValues for later plotting felix@1861: * felix@1861: * @param o list of annotations (data of facet). felix@1861: * @param facet The facet. This facet does NOT support any data objects. Use felix@1861: * FLYSArtifact.getNativeFacet() instead to retrieve a Facet which supports felix@1861: * data. felix@1861: * @param theme Theme document for given annotations. felix@1861: * @param visible The visibility of the annotations. felix@1849: */ felix@1849: protected void doAnnotations( felix@1849: FLYSAnnotation annotations, felix@1849: Facet facet, felix@1849: Document theme, felix@1849: boolean visible felix@1849: ){ felix@1849: logger.debug("doAnnotations"); felix@1849: felix@1861: // Add all annotations to our annotation pool. felix@1849: annotations.setTheme(theme); felix@2183: if (facet != null) felix@2183: annotations.setLabel(facet.getDescription()); felix@1849: addAnnotations(annotations, visible); felix@1849: } ingo@1986: ingo@1986: ingo@1986: /** felix@2206: * Do Points out. felix@2206: */ felix@2206: protected void doPoints( felix@2206: Object o, felix@2206: String seriesName, felix@2206: Document theme, felix@2206: boolean visible, felix@2206: int axisIndex felix@2206: ) { 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); felix@2206: for (int i = 0; i < points.length(); 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: } felix@2206: felix@2206: FLYSAnnotation annotations = new FLYSAnnotation(null, null, null, theme); felix@2206: annotations.setTextAnnotations(xy); felix@2206: felix@2206: doAnnotations(annotations, null, theme, visible); felix@2206: addAxisSeries(series, axisIndex, visible); felix@2206: } felix@2206: felix@2206: felix@2161: /** Two Ranges that span a rectangular area. */ felix@2161: public static class Area { felix@2161: protected Range xRange; felix@2161: protected Range yRange; felix@2161: felix@2161: public Area(Range rangeX, Range rangeY) { felix@2161: this.xRange = rangeX; felix@2161: this.yRange = rangeY; felix@2161: } felix@2161: felix@2163: public Area(ValueAxis axisX, ValueAxis axisY) { felix@2163: this.xRange = axisX.getRange(); felix@2163: this.yRange = axisY.getRange(); felix@2163: } felix@2163: felix@2161: public double ofLeft(double percent) { felix@2161: return xRange.getLowerBound() felix@2161: + xRange.getLength() * percent; felix@2161: } felix@2161: felix@2163: public double ofRight(double percent) { felix@2163: return xRange.getUpperBound() felix@2163: - xRange.getLength() * percent; felix@2163: } felix@2163: felix@2161: public double ofGround(double percent) { felix@2161: return yRange.getLowerBound() felix@2161: + yRange.getLength() * percent; felix@2161: } felix@2161: felix@2161: public double atTop() { felix@2161: return yRange.getUpperBound(); felix@2161: } felix@2161: felix@2161: public double atGround() { felix@2161: return yRange.getLowerBound(); felix@2161: } felix@2161: felix@2163: public double atRight() { felix@2163: return xRange.getUpperBound(); felix@2163: } felix@2163: felix@2161: public double atLeft() { felix@2161: return xRange.getLowerBound(); felix@2161: } felix@2193: felix@2193: public double above(double percent, double base) { felix@2193: return base + yRange.getLength() * percent; felix@2193: } felix@2161: } ingo@369: } ingo@369: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :