aheinecke@7044: /* Copyright (C) 2013 by Bundesanstalt für Gewässerkunde aheinecke@7044: * Software engineering by Intevation GmbH aheinecke@7044: * aheinecke@7044: * This file is Free Software under the GNU AGPL (>=v3) aheinecke@7044: * and comes with ABSOLUTELY NO WARRANTY! Check out the aheinecke@7044: * documentation coming with Dive4Elements River for details. aheinecke@7044: */ aheinecke@7044: aheinecke@7044: package org.dive4elements.river.exports; aheinecke@7044: aheinecke@7044: import java.awt.Color; aheinecke@7044: import java.awt.Font; teichmann@7099: aheinecke@7044: import java.text.NumberFormat; teichmann@7099: aheinecke@7044: import java.util.ArrayList; aheinecke@7044: import java.util.HashMap; rrenkert@7806: import java.util.HashSet; aheinecke@7597: import java.util.LinkedHashSet; aheinecke@7044: import java.util.List; aheinecke@7044: import java.util.Map; aheinecke@7597: import java.util.Set; aheinecke@7597: aheinecke@7597: import java.util.regex.Pattern; aheinecke@7597: import java.util.regex.Matcher; aheinecke@7044: aheinecke@7123: import java.io.OutputStream; aheinecke@7123: aheinecke@7044: import javax.swing.ImageIcon; aheinecke@7044: aheinecke@7044: import org.apache.log4j.Logger; teichmann@7099: teichmann@7099: import org.dive4elements.artifactdatabase.state.ArtifactAndFacet; teichmann@7099: import org.dive4elements.artifactdatabase.state.Facet; teichmann@7099: aheinecke@7123: import org.dive4elements.artifacts.CallContext; aheinecke@7123: teichmann@7099: import org.dive4elements.river.artifacts.D4EArtifact; teichmann@7099: teichmann@7099: import org.dive4elements.river.exports.process.Processor; teichmann@7099: teichmann@7099: import org.dive4elements.river.jfree.AnnotationHelper; teichmann@7099: import org.dive4elements.river.jfree.AxisDataset; teichmann@7099: import org.dive4elements.river.jfree.Bounds; teichmann@7099: import org.dive4elements.river.jfree.DoubleBounds; teichmann@7099: import org.dive4elements.river.jfree.StyledAreaSeriesCollection; rrenkert@7892: import org.dive4elements.river.jfree.XYMetaSeriesCollection; teichmann@7099: teichmann@7099: import org.dive4elements.river.themes.ThemeDocument; aheinecke@7123: import org.dive4elements.river.utils.RiverUtils; teichmann@7099: aheinecke@7044: import org.jfree.chart.ChartFactory; aheinecke@7044: import org.jfree.chart.JFreeChart; aheinecke@7044: import org.jfree.chart.LegendItem; teichmann@7099: aheinecke@7044: import org.jfree.chart.annotations.XYAnnotation; aheinecke@7044: import org.jfree.chart.annotations.XYImageAnnotation; teichmann@7099: teichmann@7099: import org.jfree.chart.axis.LogarithmicAxis; aheinecke@7044: import org.jfree.chart.axis.NumberAxis; aheinecke@7044: import org.jfree.chart.axis.ValueAxis; teichmann@7099: aheinecke@7044: import org.jfree.chart.plot.Marker; aheinecke@7044: import org.jfree.chart.plot.PlotOrientation; aheinecke@7044: import org.jfree.chart.plot.XYPlot; teichmann@7099: aheinecke@7044: import org.jfree.data.Range; teichmann@7099: aheinecke@7044: import org.jfree.data.general.Series; teichmann@7099: aheinecke@7044: import org.jfree.data.xy.XYDataset; aheinecke@7044: import org.jfree.data.xy.XYSeries; aheinecke@7044: import org.jfree.data.xy.XYSeriesCollection; aheinecke@7044: aheinecke@7123: import org.w3c.dom.Document; aheinecke@7123: aheinecke@7597: import org.apache.commons.lang.StringUtils; aheinecke@7597: teichmann@7051: aheinecke@7044: /** aheinecke@7044: * The main diagram creation class. aheinecke@7044: * aheinecke@7044: * This class is the glue between output processors and facets. aheinecke@7044: * The generator creates one diagram and calls the appropiate teichmann@7248: * processors for the state and aheinecke@7044: * aheinecke@7044: * With respect to datasets, ranges and axis, there are following requirements: aheinecke@7044: * aheinecke@7044: */ aheinecke@7068: public class DiagramGenerator extends ChartGenerator2 { aheinecke@7044: aheinecke@7044: public static final int AXIS_SPACE = 5; aheinecke@7044: aheinecke@7044: /** The logger that is used in this generator. */ aheinecke@7044: private static Logger logger = Logger.getLogger(DiagramGenerator.class); aheinecke@7044: aheinecke@7044: protected List domainMarkers = new ArrayList(); aheinecke@7044: aheinecke@7044: protected List valueMarkers = new ArrayList(); aheinecke@7044: aheinecke@7044: /** The max X range to include all X values of all series for each axis. */ aheinecke@7044: protected Map xBounds; aheinecke@7044: aheinecke@7044: /** The max Y range to include all Y values of all series for each axis. */ aheinecke@7044: protected Map yBounds; aheinecke@7044: aheinecke@7044: /** Whether or not the plot is inverted (left-right). */ aheinecke@7044: private boolean inverted; aheinecke@7044: aheinecke@7597: private static final Pattern UNIT_PATTERN = aheinecke@7597: Pattern.compile("\\s*\\[[\\w\\s\\+\\-]*\\]\\s*"); aheinecke@7597: aheinecke@7597: protected Map> axesLabels; aheinecke@7597: teichmann@7099: protected DiagramAttributes.Instance diagramAttributes; teichmann@7051: rrenkert@7806: protected HashSet subTitleParts; rrenkert@7806: aheinecke@7044: public DiagramGenerator() { aheinecke@7044: super(); aheinecke@7044: aheinecke@7597: axesLabels = new HashMap>(); aheinecke@7044: xBounds = new HashMap(); aheinecke@7044: yBounds = new HashMap(); rrenkert@7806: subTitleParts = new LinkedHashSet(); aheinecke@7044: } aheinecke@7044: teichmann@7051: @Override teichmann@7087: public void setup(Object config) { teichmann@7099: teichmann@7099: if (!(config instanceof DiagramAttributes)) { teichmann@7099: logger.error("invalid config type"); teichmann@7099: return; teichmann@7086: } teichmann@7099: DiagramAttributes da = (DiagramAttributes)config; teichmann@7099: diagramAttributes = da.new Instance(); teichmann@7051: } teichmann@7051: aheinecke@7123: @Override teichmann@7143: public void init( teichmann@7143: String outName, teichmann@7143: Document request, teichmann@7143: OutputStream out, teichmann@7143: CallContext context teichmann@7143: ) { aheinecke@7123: super.init(outName, request, out, context); aheinecke@7123: aheinecke@7123: RiverUtils.setKMFromRequestInContext(request, context); aheinecke@7123: } aheinecke@7123: teichmann@7143: private void setInvertedFromConfig() { teichmann@7143: DiagramAttributes.DomainAxisAttributes dx = teichmann@7143: diagramAttributes.getDomainAxis(); teichmann@7143: teichmann@7143: if (dx != null) { teichmann@7143: inverted = (Boolean)dx.isInverted() teichmann@7143: .evaluate((D4EArtifact)getMaster(), context); aheinecke@7167: logger.debug("setInvertedFromConfig: " + inverted); aheinecke@7167: } else { aheinecke@7167: logger.debug("setInvertedFromConfig no domain axis found?"); teichmann@7143: } teichmann@7143: } aheinecke@7123: aheinecke@7044: /** aheinecke@7044: * Generate the chart anew (including localized axis and all). aheinecke@7044: */ aheinecke@7044: @Override aheinecke@7044: public JFreeChart generateChart() { aheinecke@7044: logger.debug("DiagramGenerator.generateChart"); aheinecke@7044: aheinecke@7044: JFreeChart chart = ChartFactory.createXYLineChart( aheinecke@7044: getChartTitle(), aheinecke@7084: "", aheinecke@7084: "", aheinecke@7044: null, aheinecke@7044: PlotOrientation.VERTICAL, aheinecke@7044: isLegendVisible(), aheinecke@7044: false, aheinecke@7044: false); aheinecke@7044: aheinecke@7044: XYPlot plot = (XYPlot) chart.getPlot(); aheinecke@7044: ValueAxis axis = createXAxis(getXAxisLabel()); aheinecke@7044: plot.setDomainAxis(axis); aheinecke@7044: aheinecke@7044: chart.setBackgroundPaint(Color.WHITE); aheinecke@7044: plot.setBackgroundPaint(Color.WHITE); aheinecke@7044: addSubtitles(chart); aheinecke@7044: adjustPlot(plot); aheinecke@7044: aheinecke@7044: //debugAxis(plot); aheinecke@7044: aheinecke@7044: addDatasets(plot); aheinecke@7044: aheinecke@7044: //debugDatasets(plot); aheinecke@7044: aheinecke@7044: addMarkers(plot); aheinecke@7044: aheinecke@7044: recoverEmptyPlot(plot); aheinecke@7044: preparePointRanges(plot); aheinecke@7044: aheinecke@7044: //debugAxis(plot); aheinecke@7044: aheinecke@7044: localizeAxes(plot); aheinecke@7246: aheinecke@7246: setInvertedFromConfig(); aheinecke@7246: aheinecke@7044: adjustAxes(plot); aheinecke@7044: if (!(axis instanceof LogarithmicAxis)) { aheinecke@7044: // XXX: aheinecke@7044: // The auto zoom without a range tries aheinecke@7044: // to include 0 in a logarithmic axis aheinecke@7044: // which triggers a bug in jfreechart that causes aheinecke@7044: // the values to be drawn carthesian aheinecke@7044: autoZoom(plot); aheinecke@7044: } aheinecke@7044: aheinecke@7044: //debugAxis(plot); aheinecke@7044: aheinecke@7044: // These have to go after the autozoom. aheinecke@7044: AnnotationHelper.addAnnotationsToRenderer(annotations, plot, aheinecke@7044: getChartSettings(), datasets); aheinecke@7044: aheinecke@7044: // Add a logo (maybe). aheinecke@7044: addLogo(plot); aheinecke@7044: aheinecke@7044: aggregateLegendEntries(plot); aheinecke@7044: aheinecke@7044: return chart; aheinecke@7044: } aheinecke@7044: aheinecke@7084: public String getOutName() { aheinecke@7084: return outName; aheinecke@7084: } aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Return left most data points x value (on first axis). aheinecke@7044: */ aheinecke@7044: protected double getLeftX() { aheinecke@7106: if (inverted) { aheinecke@7104: return (Double)getXBounds(0).getUpper(); aheinecke@7104: } aheinecke@7044: return (Double)getXBounds(0).getLower(); aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Return right most data points x value (on first axis). aheinecke@7044: */ aheinecke@7044: protected double getRightX() { aheinecke@7106: if (inverted) { aheinecke@7104: return (Double)getXBounds(0).getLower(); aheinecke@7104: } aheinecke@7044: return (Double)getXBounds(0).getUpper(); aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** Add a logo as background annotation to plot. */ aheinecke@7044: protected void addLogo(XYPlot plot) { aheinecke@7044: String logo = showLogo(); aheinecke@7044: if (logo == null) { aheinecke@7044: logger.debug("No logo to show chosen"); aheinecke@7044: return; aheinecke@7044: } aheinecke@7044: aheinecke@7044: ImageIcon imageIcon = null; aheinecke@7044: if (logo.equals("none")) { aheinecke@7044: return; aheinecke@7044: } aheinecke@7044: /* aheinecke@7044: If you want to add images, remember to change code in these places: aheinecke@7044: flys-artifacts: aheinecke@7044: DiagramGenerator.java aheinecke@7044: Timeseries*Generator.java and aheinecke@7044: in the flys-client projects Chart*Propert*Editor.java. aheinecke@7044: Also, these images have to be put in aheinecke@7044: flys-artifacts/src/main/resources/images/ aheinecke@7044: flys-client/src/main/webapp/images/ aheinecke@7044: */ aheinecke@7044: java.net.URL imageURL; aheinecke@7044: if (logo.equals("Intevation")) { aheinecke@7044: imageURL = DiagramGenerator.class.getResource("/images/intevation.png"); aheinecke@7044: } aheinecke@7044: else { // TODO else if ... aheinecke@7044: imageURL = DiagramGenerator.class.getResource("/images/bfg_logo.gif"); aheinecke@7044: } aheinecke@7044: imageIcon = new ImageIcon(imageURL); aheinecke@7044: aheinecke@7044: aheinecke@7044: double xPos = 0d, yPos = 0d; aheinecke@7044: aheinecke@7044: String placeh = logoHPlace(); aheinecke@7044: String placev = logoVPlace(); aheinecke@7044: aheinecke@7044: if (placev == null || placev.equals("none")) { aheinecke@7044: placev = "top"; aheinecke@7044: } aheinecke@7044: if (placev.equals("top")) { aheinecke@7044: yPos = (Double)getYBounds(0).getUpper(); aheinecke@7044: } aheinecke@7044: else if (placev.equals("bottom")) { aheinecke@7044: yPos = (Double)getYBounds(0).getLower(); aheinecke@7044: } aheinecke@7044: else if (placev.equals("center")) { aheinecke@7044: yPos = ((Double)getYBounds(0).getUpper() + (Double)getYBounds(0).getLower())/2d; aheinecke@7044: } aheinecke@7044: else { aheinecke@7044: logger.debug("Unknown place-v value: " + placev); aheinecke@7044: } aheinecke@7044: aheinecke@7044: if (placeh == null || placeh.equals("none")) { aheinecke@7044: placeh = "center"; aheinecke@7044: } aheinecke@7044: if (placeh.equals("left")) { aheinecke@7044: xPos = getLeftX(); aheinecke@7044: } aheinecke@7044: else if (placeh.equals("right")) { aheinecke@7044: xPos = getRightX(); aheinecke@7044: } aheinecke@7044: else if (placeh.equals("center")) { aheinecke@7044: xPos = ((Double)getXBounds(0).getUpper() + (Double)getXBounds(0).getLower())/2d; aheinecke@7044: } aheinecke@7044: else { aheinecke@7044: logger.debug("Unknown place-h value: " + placeh); aheinecke@7044: } aheinecke@7044: aheinecke@7044: logger.debug("logo position: " + xPos + "/" + yPos); aheinecke@7044: aheinecke@7044: org.jfree.ui.RectangleAnchor anchor aheinecke@7044: = org.jfree.ui.RectangleAnchor.TOP; aheinecke@7044: if (placev.equals("top")) { aheinecke@7044: if (placeh.equals("left")) { aheinecke@7044: anchor = org.jfree.ui.RectangleAnchor.TOP_LEFT; aheinecke@7044: } aheinecke@7044: else if (placeh.equals("right")) { aheinecke@7044: anchor = org.jfree.ui.RectangleAnchor.TOP_RIGHT; aheinecke@7044: } aheinecke@7044: else if (placeh.equals("center")) { aheinecke@7044: anchor = org.jfree.ui.RectangleAnchor.TOP; aheinecke@7044: } aheinecke@7044: } aheinecke@7044: else if (placev.equals("bottom")) { aheinecke@7044: if (placeh.equals("left")) { aheinecke@7044: anchor = org.jfree.ui.RectangleAnchor.BOTTOM_LEFT; aheinecke@7044: } aheinecke@7044: else if (placeh.equals("right")) { aheinecke@7044: anchor = org.jfree.ui.RectangleAnchor.BOTTOM_RIGHT; aheinecke@7044: } aheinecke@7044: else if (placeh.equals("center")) { aheinecke@7044: anchor = org.jfree.ui.RectangleAnchor.BOTTOM; aheinecke@7044: } aheinecke@7044: } aheinecke@7044: else if (placev.equals("center")) { aheinecke@7044: if (placeh.equals("left")) { aheinecke@7044: anchor = org.jfree.ui.RectangleAnchor.LEFT; aheinecke@7044: } aheinecke@7044: else if (placeh.equals("right")) { aheinecke@7044: anchor = org.jfree.ui.RectangleAnchor.RIGHT; aheinecke@7044: } aheinecke@7044: else if (placeh.equals("center")) { aheinecke@7044: anchor = org.jfree.ui.RectangleAnchor.CENTER; aheinecke@7044: } aheinecke@7044: } aheinecke@7044: aheinecke@7044: XYAnnotation xyannotation = aheinecke@7044: new XYImageAnnotation(xPos, yPos, imageIcon.getImage(), anchor); aheinecke@7044: plot.getRenderer().addAnnotation(xyannotation, org.jfree.ui.Layer.BACKGROUND); aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: protected NumberAxis createXAxis(String label) { aheinecke@7241: boolean logarithmic = (Boolean)diagramAttributes.getDomainAxis(). aheinecke@7241: isLog().evaluate((D4EArtifact)getMaster(), context); aheinecke@7241: aheinecke@7241: if (logarithmic) { teichmann@8147: LogarithmicAxis la = new LogarithmicAxis(label); teichmann@8147: la.setStrictValuesFlag(false); teichmann@8147: la.setAllowNegativesFlag(true); teichmann@8147: return la; aheinecke@7241: } teichmann@8147: return new NumberAxis(label); aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: @Override aheinecke@7044: protected Series getSeriesOf(XYDataset dataset, int idx) { aheinecke@7044: return ((XYSeriesCollection) dataset).getSeries(idx); aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: @Override aheinecke@7044: protected AxisDataset createAxisDataset(int idx) { aheinecke@7044: logger.debug("Create new AxisDataset for index: " + idx); aheinecke@7044: return new AxisDataset(idx); aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Put debug output about datasets. aheinecke@7044: */ aheinecke@7044: public void debugDatasets(XYPlot plot) { aheinecke@7044: logger.debug("Number of datasets: " + plot.getDatasetCount()); aheinecke@7044: for (int i = 0, P = plot.getDatasetCount(); i < P; i++) { aheinecke@7044: if (plot.getDataset(i) == null) { aheinecke@7044: logger.debug("Dataset #" + i + " is null"); aheinecke@7044: continue; aheinecke@7044: } aheinecke@7044: logger.debug("Dataset #" + i + ":" + plot.getDataset(i)); aheinecke@7044: XYSeriesCollection series = (XYSeriesCollection) plot.getDataset(i); aheinecke@7044: logger.debug("X-Extend of Dataset: " + series.getSeries(0).getMinX() aheinecke@7044: + " " + series.getSeries(0).getMaxX()); aheinecke@7044: logger.debug("Y-Extend of Dataset: " + series.getSeries(0).getMinY() aheinecke@7044: + " " + series.getSeries(0).getMaxY()); aheinecke@7044: } aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Put debug output about axes. aheinecke@7044: */ aheinecke@7044: public void debugAxis(XYPlot plot) { aheinecke@7044: logger.debug("..............."); aheinecke@7044: for (int i = 0, P = plot.getRangeAxisCount(); i < P; i++) { aheinecke@7044: if (plot.getRangeAxis(i) == null) aheinecke@7044: logger.debug("Range-Axis #" + i + " == null"); aheinecke@7044: else { aheinecke@7044: logger.debug("Range-Axis " + i + " != null [" + aheinecke@7044: plot.getRangeAxis(i).getRange().getLowerBound() + aheinecke@7044: " " + plot.getRangeAxis(i).getRange().getUpperBound() + aheinecke@7044: "]"); aheinecke@7044: } aheinecke@7044: } aheinecke@7044: for (int i = 0, P = plot.getDomainAxisCount(); i < P; i++) { aheinecke@7044: if (plot.getDomainAxis(i) == null) aheinecke@7044: logger.debug("Domain-Axis #" + i + " == null"); aheinecke@7044: else { aheinecke@7044: logger.debug("Domain-Axis " + i + " != null [" + aheinecke@7044: plot.getDomainAxis(i).getRange().getLowerBound() + aheinecke@7044: " " + plot.getDomainAxis(i).getRange().getUpperBound() + aheinecke@7044: "]"); aheinecke@7044: } aheinecke@7044: } aheinecke@7044: logger.debug("..............."); aheinecke@7044: } aheinecke@7044: aheinecke@7068: /** aheinecke@7068: * Registers an area to be drawn. aheinecke@7068: * @param area Area to be drawn. aheinecke@7068: * @param axisName Name of the axis. aheinecke@7068: * @param visible Whether or not to be visible (important for range calculations). aheinecke@7068: */ aheinecke@7068: public void addAreaSeries(StyledAreaSeriesCollection area, String axisName, boolean visible) { aheinecke@7068: addAreaSeries(area, diagramAttributes.getAxisIndex(axisName), visible); aheinecke@7068: } aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Registers an area to be drawn. aheinecke@7044: * @param area Area to be drawn. aheinecke@7044: * @param index 'axis index' aheinecke@7044: * @param visible Whether or not to be visible (important for range calculations). aheinecke@7044: */ aheinecke@7044: public void addAreaSeries(StyledAreaSeriesCollection area, int index, boolean visible) { aheinecke@7044: if (area == null) { aheinecke@7044: logger.warn("Cannot yet render above/under curve."); aheinecke@7044: return; aheinecke@7044: } aheinecke@7044: aheinecke@7044: AxisDataset axisDataset = (AxisDataset) getAxisDataset(index); aheinecke@7044: aheinecke@7044: if (visible) { aheinecke@7044: axisDataset.addArea(area); aheinecke@7044: } aheinecke@7044: else { aheinecke@7044: /* No range merging, for areas extending to infinity this aheinecke@7044: * causes problems. */ aheinecke@7044: } aheinecke@7044: } aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Add given series if visible, if not visible adjust ranges (such that aheinecke@7044: * all points in data would be plotted once visible). aheinecke@7068: * @param series the data series to include in plot. aheinecke@7068: * @param index index of the axis. aheinecke@7068: * @param visible whether or not the data should be plotted. aheinecke@7044: */ aheinecke@7044: public void addAxisSeries(XYSeries series, int index, boolean visible) { aheinecke@7044: if (series == null) { aheinecke@7044: return; aheinecke@7044: } aheinecke@7044: aheinecke@7044: logger.debug("Y Range of XYSeries: " + aheinecke@7044: series.getMinY() + " | " + series.getMaxY()); aheinecke@7044: rrenkert@7892: addAxisDataset(new XYMetaSeriesCollection(series), index, visible); aheinecke@7068: } aheinecke@7044: aheinecke@7068: /** aheinecke@7068: * Add given series if visible, if not visible adjust ranges (such that aheinecke@7068: * all points in data would be plotted once visible). aheinecke@7068: * @param series the data series to include in plot. aheinecke@7068: * @param axisName name of the axis. aheinecke@7068: * @param visible whether or not the data should be plotted. aheinecke@7068: */ aheinecke@7068: public void addAxisSeries(XYSeries series, String axisName, boolean visible) { aheinecke@7068: addAxisSeries(series, diagramAttributes.getAxisIndex(axisName), visible); aheinecke@7044: } aheinecke@7044: aheinecke@7124: public void addAxisDataset(XYDataset dataset, String axisName, boolean visible) { aheinecke@7124: addAxisDataset(dataset, diagramAttributes.getAxisIndex(axisName), visible); aheinecke@7124: } aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Add the given vertical marker to the chart. aheinecke@7044: */ aheinecke@7044: public void addDomainMarker(Marker marker) { aheinecke@7044: addDomainMarker(marker, true); aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Add the given vertical marker to the chart.Note: the marker is aheinecke@7044: * added to the chart only if it is not null and if visible is true. aheinecke@7044: * @param marker The marker that should be added to the chart. aheinecke@7044: * @param visible The visibility of the marker. aheinecke@7044: */ aheinecke@7044: public void addDomainMarker(Marker marker, boolean visible) { aheinecke@7044: if (visible && marker != null) { aheinecke@7044: domainMarkers.add(marker); aheinecke@7044: } aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Add the given vertical marker to the chart. aheinecke@7044: */ aheinecke@7044: public void addValueMarker(Marker marker) { aheinecke@7044: addValueMarker(marker, true); aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Add the given horizontal marker to the chart.Note: the marker is aheinecke@7044: * added to the chart only if it is not null and if visible is true. aheinecke@7044: * @param marker The marker that should be added to the chart. aheinecke@7044: * @param visible The visibility of the marker. aheinecke@7044: */ aheinecke@7044: public void addValueMarker(Marker marker, boolean visible) { aheinecke@7044: if (visible && marker != null) { aheinecke@7044: valueMarkers.add(marker); aheinecke@7044: } aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: protected void addMarkers(XYPlot plot) { aheinecke@7044: for(Marker marker : domainMarkers) { aheinecke@7044: plot.addDomainMarker(marker); aheinecke@7044: } aheinecke@7044: for(Marker marker : valueMarkers) { aheinecke@7044: plot.addRangeMarker(marker); aheinecke@7044: } aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Effect: extend range of x axis to include given limits. aheinecke@7044: * aheinecke@7044: * @param bounds the given ("minimal") bounds. aheinecke@7044: * @param index index of axis to be merged. aheinecke@7044: */ aheinecke@7044: @Override aheinecke@7044: protected void combineXBounds(Bounds bounds, int index) { aheinecke@7044: if (!(bounds instanceof DoubleBounds)) { aheinecke@7044: logger.warn("Unsupported Bounds type: " + bounds.getClass()); aheinecke@7044: return; aheinecke@7044: } aheinecke@7044: aheinecke@7044: DoubleBounds dBounds = (DoubleBounds) bounds; aheinecke@7044: aheinecke@7044: if (dBounds == null aheinecke@7044: || Double.isNaN((Double) dBounds.getLower()) aheinecke@7044: || Double.isNaN((Double) dBounds.getUpper())) { aheinecke@7044: return; aheinecke@7044: } aheinecke@7044: aheinecke@7044: Bounds old = getXBounds(index); aheinecke@7044: aheinecke@7044: if (old != null) { aheinecke@7044: dBounds = (DoubleBounds) dBounds.combine(old); aheinecke@7044: } aheinecke@7044: aheinecke@7044: setXBounds(index, dBounds); aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: @Override aheinecke@7044: protected void combineYBounds(Bounds bounds, int index) { aheinecke@7044: if (!(bounds instanceof DoubleBounds)) { aheinecke@7044: logger.warn("Unsupported Bounds type: " + bounds.getClass()); aheinecke@7044: return; aheinecke@7044: } aheinecke@7044: aheinecke@7044: DoubleBounds dBounds = (DoubleBounds) bounds; aheinecke@7044: aheinecke@7044: if (dBounds == null aheinecke@7044: || Double.isNaN((Double) dBounds.getLower()) aheinecke@7044: || Double.isNaN((Double) dBounds.getUpper())) { aheinecke@7044: return; aheinecke@7044: } aheinecke@7044: aheinecke@7044: Bounds old = getYBounds(index); aheinecke@7044: aheinecke@7044: if (old != null) { aheinecke@7044: dBounds = (DoubleBounds) dBounds.combine(old); aheinecke@7044: } aheinecke@7044: aheinecke@7044: setYBounds(index, dBounds); aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * If no data is visible, draw at least empty axis. aheinecke@7044: */ aheinecke@7044: private void recoverEmptyPlot(XYPlot plot) { aheinecke@7044: if (plot.getRangeAxis() == null) { aheinecke@7044: logger.debug("debug: No range axis"); aheinecke@7044: plot.setRangeAxis(createYAxis(0)); aheinecke@7044: } aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Expands X axes if only a point is shown. aheinecke@7044: */ aheinecke@7044: private void preparePointRanges(XYPlot plot) { aheinecke@7044: for (int i = 0, num = plot.getDomainAxisCount(); i < num; i++) { aheinecke@7044: aheinecke@7044: Integer key = Integer.valueOf(i); aheinecke@7044: Bounds b = getXBounds(key); aheinecke@7044: aheinecke@7044: aheinecke@7044: if (b != null && b.getLower().equals(b.getUpper())) { aheinecke@7044: logger.debug("Check whether to expand a x axis.i ("+b.getLower() + "-" + b.getUpper()+")"); aheinecke@7044: setXBounds(key, ChartHelper.expandBounds(b, 5)); aheinecke@7044: } aheinecke@7044: } aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * This method zooms the plot to the specified ranges in the attribute aheinecke@7044: * document or to the ranges specified by the min/max values in the aheinecke@7044: * datasets. Note: We determine the range manually if no zoom ranges aheinecke@7044: * are given, because JFreeCharts auto-zoom adds a margin to the left and aheinecke@7044: * right of the data area. aheinecke@7044: * aheinecke@7044: * @param plot The XYPlot. aheinecke@7044: */ aheinecke@7044: protected void autoZoom(XYPlot plot) { aheinecke@7044: logger.debug("Zoom to specified ranges."); aheinecke@7044: aheinecke@7044: Range xrange = getDomainAxisRange(); aheinecke@7044: Range yrange = getValueAxisRange(); aheinecke@7044: aheinecke@7044: ValueAxis xAxis = plot.getDomainAxis(); aheinecke@7044: aheinecke@7044: Range fixedXRange = getRangeForAxisFromSettings("X"); aheinecke@7044: if (fixedXRange != null) { aheinecke@7044: xAxis.setRange(fixedXRange); aheinecke@7044: } aheinecke@7044: else { aheinecke@7044: zoomX(plot, xAxis, getXBounds(0), xrange); aheinecke@7044: } aheinecke@7044: aheinecke@7044: for (int i = 0, num = plot.getRangeAxisCount(); i < num; i++) { aheinecke@7044: ValueAxis yaxis = plot.getRangeAxis(i); aheinecke@7044: aheinecke@7044: if (yaxis instanceof IdentifiableNumberAxis) { aheinecke@7044: IdentifiableNumberAxis idAxis = (IdentifiableNumberAxis) yaxis; aheinecke@7044: aheinecke@7044: Range fixedRange = getRangeForAxisFromSettings(idAxis.getId()); aheinecke@7044: if (fixedRange != null) { aheinecke@7044: yaxis.setRange(fixedRange); aheinecke@7044: continue; aheinecke@7044: } aheinecke@7044: } aheinecke@7044: aheinecke@7044: if (yaxis == null) { aheinecke@7044: logger.debug("Zoom problem: no Y Axis for index: " + i); aheinecke@7044: continue; aheinecke@7044: } aheinecke@7044: aheinecke@7044: logger.debug("Prepare zoom settings for y axis at index: " + i); aheinecke@7044: zoomY(plot, yaxis, getYBounds(Integer.valueOf(i)), yrange); aheinecke@7044: } aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: protected Range getDomainAxisRange() { aheinecke@7044: String[] ranges = getDomainAxisRangeFromRequest(); aheinecke@7044: aheinecke@7044: if (ranges == null || ranges.length < 2) { aheinecke@7044: logger.debug("No zoom range for domain axis specified."); aheinecke@7044: return null; aheinecke@7044: } aheinecke@7044: aheinecke@7044: if (ranges[0].length() > 0 && ranges[1].length() > 0) { aheinecke@7044: try { aheinecke@7044: double from = Double.parseDouble(ranges[0]); aheinecke@7044: double to = Double.parseDouble(ranges[1]); aheinecke@7044: aheinecke@7044: if (from == 0 && to == 0) { aheinecke@7044: logger.debug("No range specified. Lower and upper X == 0"); aheinecke@7044: return null; aheinecke@7044: } aheinecke@7044: aheinecke@7044: if (from > to) { aheinecke@7044: double tmp = to; aheinecke@7044: to = from; aheinecke@7044: from = tmp; aheinecke@7044: } aheinecke@7044: aheinecke@7044: return new Range(from, to); aheinecke@7044: } aheinecke@7044: catch (NumberFormatException nfe) { aheinecke@7044: logger.warn("Wrong values for domain axis range."); aheinecke@7044: } aheinecke@7044: } aheinecke@7044: aheinecke@7044: return null; aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: protected Range getValueAxisRange() { aheinecke@7044: String[] ranges = getValueAxisRangeFromRequest(); aheinecke@7044: aheinecke@7044: if (ranges == null || ranges.length < 2) { aheinecke@7044: logger.debug("No range specified. Lower and upper Y == 0"); aheinecke@7044: return null; aheinecke@7044: } aheinecke@7044: aheinecke@7044: if (ranges[0].length() > 0 && ranges[1].length() > 0) { aheinecke@7044: try { aheinecke@7044: double from = Double.parseDouble(ranges[0]); aheinecke@7044: double to = Double.parseDouble(ranges[1]); aheinecke@7044: aheinecke@7044: if (from == 0 && to == 0) { aheinecke@7044: logger.debug("No range specified. Lower and upper Y == 0"); aheinecke@7044: return null; aheinecke@7044: } aheinecke@7044: aheinecke@7044: return from > to aheinecke@7044: ? new Range(to, from) aheinecke@7044: : new Range(from, to); aheinecke@7044: } aheinecke@7044: catch (NumberFormatException nfe) { aheinecke@7044: logger.warn("Wrong values for value axis range."); aheinecke@7044: } aheinecke@7044: } aheinecke@7044: aheinecke@7044: return null; aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: protected boolean zoomX(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) { aheinecke@7044: return zoom(plot, axis, bounds, x); aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: protected boolean zoomY(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) { aheinecke@7044: return zoom(plot, axis, bounds, x); aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Zooms the x axis to the range specified in the attribute document. aheinecke@7044: * aheinecke@7044: * @param plot The XYPlot. aheinecke@7044: * @param axis The axis the shoud be modified. aheinecke@7044: * @param bounds The whole range specified by a dataset. aheinecke@7044: * @param x A user defined range (null permitted). aheinecke@7044: * aheinecke@7044: * @return true, if a zoom range was specified, otherwise false. aheinecke@7044: */ aheinecke@7044: protected boolean zoom(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) { aheinecke@7044: aheinecke@7044: if (bounds == null) { aheinecke@7044: return false; aheinecke@7044: } aheinecke@7044: aheinecke@7044: if (x != null) { aheinecke@7044: Bounds computed = calculateZoom(bounds, x); aheinecke@7044: computed.applyBounds(axis, AXIS_SPACE); aheinecke@7044: aheinecke@7044: logger.debug("Zoom axis to: " + computed); aheinecke@7044: aheinecke@7044: return true; aheinecke@7044: } aheinecke@7044: aheinecke@7044: bounds.applyBounds(axis, AXIS_SPACE); aheinecke@7044: return false; aheinecke@7044: } aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Calculates the start and end km for zoomed charts. aheinecke@7044: * @param bounds The given total bounds (unzoomed). aheinecke@7044: * @param range The range specifying the zoom. aheinecke@7044: * aheinecke@7044: * @return The start and end km for the zoomed chart. aheinecke@7044: */ aheinecke@7044: protected Bounds calculateZoom(Bounds bounds, Range range) { aheinecke@7044: double min = bounds.getLower().doubleValue(); aheinecke@7044: double max = bounds.getUpper().doubleValue(); aheinecke@7044: aheinecke@7044: if (logger.isDebugEnabled()) { aheinecke@7044: logger.debug("Minimum is: " + min); aheinecke@7044: logger.debug("Maximum is: " + max); aheinecke@7044: logger.debug("Lower zoom is: " + range.getLowerBound()); aheinecke@7044: logger.debug("Upper zoom is: " + range.getUpperBound()); aheinecke@7044: } aheinecke@7044: aheinecke@7044: double diff = max > min ? max - min : min - max; aheinecke@7044: aheinecke@7044: DoubleBounds computed = new DoubleBounds( aheinecke@7044: min + range.getLowerBound() * diff, aheinecke@7044: min + range.getUpperBound() * diff); aheinecke@7044: return computed; aheinecke@7044: } aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Extract the minimum and maximum values for x and y axes aheinecke@7044: * which are stored in xRanges and yRanges. aheinecke@7044: * aheinecke@7044: * @param index The index of the y-Axis. aheinecke@7044: * aheinecke@7044: * @return a Range[] as follows: [x-Range, y-Range]. aheinecke@7044: */ aheinecke@7044: @Override aheinecke@7044: public Range[] getRangesForAxis(int index) { aheinecke@7044: logger.debug("getRangesForAxis " + index); aheinecke@7044: aheinecke@7044: Bounds rx = getXBounds(Integer.valueOf(0)); aheinecke@7044: Bounds ry = getYBounds(Integer.valueOf(index)); aheinecke@7044: aheinecke@7044: if (rx == null) { aheinecke@7044: logger.warn("Range for x axis not set." + aheinecke@7044: " Using default values: 0 - 1."); aheinecke@7044: rx = new DoubleBounds(0, 1); aheinecke@7044: } aheinecke@7044: if (ry == null) { aheinecke@7044: logger.warn("Range for y" + index + aheinecke@7044: " axis not set. Using default values: 0 - 1."); aheinecke@7044: ry = new DoubleBounds(0, 1); aheinecke@7044: } aheinecke@7044: aheinecke@7044: return new Range[] { aheinecke@7044: new Range(rx.getLower().doubleValue(), rx.getUpper().doubleValue()), aheinecke@7044: new Range(ry.getLower().doubleValue(), ry.getUpper().doubleValue()) aheinecke@7044: }; aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** Get X (usually horizontal) extent for given axis. */ aheinecke@7044: @Override aheinecke@7044: public Bounds getXBounds(int axis) { aheinecke@7044: return xBounds.get(axis); aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** Set X (usually horizontal) extent for given axis. */ aheinecke@7044: @Override aheinecke@7044: protected void setXBounds(int axis, Bounds bounds) { aheinecke@7044: if (bounds.getLower() == bounds.getUpper()) { aheinecke@7044: xBounds.put(axis, ChartHelper.expandBounds(bounds, 5d)); aheinecke@7044: } aheinecke@7044: else { aheinecke@7044: xBounds.put(axis, bounds); aheinecke@7044: } aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** Get Y (usually vertical) extent for given axis. */ aheinecke@7044: @Override aheinecke@7044: public Bounds getYBounds(int axis) { aheinecke@7044: return yBounds.get(axis); aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** Set Y (usually vertical) extent for given axis. */ aheinecke@7044: @Override aheinecke@7044: protected void setYBounds(int axis, Bounds bounds) { aheinecke@7044: yBounds.put(axis, bounds); aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Adjusts the axes of a plot. This method sets the labelFont of the aheinecke@7106: * X axis and applies the inversion if inverted is true. aheinecke@7044: * aheinecke@7044: * (Duplicate in TimeseriesChartGenerator) aheinecke@7044: * aheinecke@7044: * @param plot The XYPlot of the chart. aheinecke@7044: */ aheinecke@7044: protected void adjustAxes(XYPlot plot) { aheinecke@7044: ValueAxis xaxis = plot.getDomainAxis(); aheinecke@7044: aheinecke@7044: ChartSettings chartSettings = getChartSettings(); aheinecke@7044: if (chartSettings == null) { aheinecke@7044: return; aheinecke@7044: } aheinecke@7044: aheinecke@7044: Font labelFont = new Font( aheinecke@7044: DEFAULT_FONT_NAME, aheinecke@7044: Font.BOLD, aheinecke@7044: getXAxisLabelFontSize()); aheinecke@7044: aheinecke@7044: xaxis.setLabelFont(labelFont); aheinecke@7044: xaxis.setTickLabelFont(labelFont); aheinecke@7104: aheinecke@7112: logger.debug("Adjusting xAxis. Inverted?: " + inverted); aheinecke@7112: if (inverted) { aheinecke@7104: xaxis.setInverted(true); aheinecke@7104: } aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * This method walks over all axes (domain and range) of plot and aheinecke@7044: * calls localizeDomainAxis() for domain axes or localizeRangeAxis() for aheinecke@7044: * range axes. aheinecke@7044: * aheinecke@7044: * @param plot The XYPlot. aheinecke@7044: */ aheinecke@7044: private void localizeAxes(XYPlot plot) { aheinecke@7044: for (int i = 0, num = plot.getDomainAxisCount(); i < num; i++) { aheinecke@7044: ValueAxis axis = plot.getDomainAxis(i); aheinecke@7044: aheinecke@7044: if (axis != null) { aheinecke@7044: localizeDomainAxis(axis); aheinecke@7044: } aheinecke@7044: else { aheinecke@7044: logger.warn("Domain axis at " + i + " is null."); aheinecke@7044: } aheinecke@7044: } aheinecke@7044: aheinecke@7044: for (int i = 0, num = plot.getRangeAxisCount(); i < num; i++) { aheinecke@7044: ValueAxis axis = plot.getRangeAxis(i); aheinecke@7044: aheinecke@7044: if (axis != null) { aheinecke@7044: localizeRangeAxis(axis); aheinecke@7044: } aheinecke@7044: else { aheinecke@7044: logger.warn("Range axis at " + i + " is null."); aheinecke@7044: } aheinecke@7044: } aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Overrides the NumberFormat with the NumberFormat for the current locale aheinecke@7044: * that is provided by getLocale(). aheinecke@7044: * aheinecke@7044: * @param domainAxis The domain axis that needs localization. aheinecke@7044: */ aheinecke@7044: protected void localizeDomainAxis(ValueAxis domainAxis) { aheinecke@7044: NumberFormat nf = NumberFormat.getInstance(getLocale()); aheinecke@7044: ((NumberAxis) domainAxis).setNumberFormatOverride(nf); aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Overrides the NumberFormat with the NumberFormat for the current locale aheinecke@7044: * that is provided by getLocale(). aheinecke@7044: * aheinecke@7044: * @param rangeAxis The domain axis that needs localization. aheinecke@7044: */ aheinecke@7044: protected void localizeRangeAxis(ValueAxis rangeAxis) { aheinecke@7044: NumberFormat nf = NumberFormat.getInstance(getLocale()); aheinecke@7044: ((NumberAxis) rangeAxis).setNumberFormatOverride(nf); aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7044: /** aheinecke@7044: * Create a hash from a legenditem. aheinecke@7044: * This hash can then be used to merge legend items labels. aheinecke@7044: * @return hash for given legenditem to identify mergeables. aheinecke@7044: */ aheinecke@7044: public static String legendItemHash(LegendItem li) { aheinecke@7044: // TODO Do proper implementation. Ensure that only mergable sets are created. aheinecke@7044: // getFillPaint() aheinecke@7044: // getFillPaintTransformer() aheinecke@7044: // getLabel() aheinecke@7044: // getLine() aheinecke@7044: // getLinePaint() aheinecke@7044: // getLineStroke() aheinecke@7044: // getOutlinePaint() aheinecke@7044: // getOutlineStroke() aheinecke@7044: // Shape getShape() aheinecke@7044: // String getToolTipText() aheinecke@7044: // String getURLText() aheinecke@7044: // boolean isLineVisible() aheinecke@7044: // boolean isShapeFilled() aheinecke@7044: // boolean isShapeOutlineVisible() aheinecke@7044: // boolean isShapeVisible() aheinecke@7044: String hash = li.getLinePaint().toString(); aheinecke@7044: String label = li.getLabel(); aheinecke@7044: if (label.startsWith("W (") || label.startsWith("W(")) { aheinecke@7044: hash += "-W-"; aheinecke@7044: } aheinecke@7044: else if (label.startsWith("Q(") || label.startsWith("Q (")) { aheinecke@7044: hash += "-Q-"; aheinecke@7044: } aheinecke@7044: aheinecke@7044: // WQ.java holds example of using regex Matcher/Pattern. aheinecke@7044: aheinecke@7044: return hash; aheinecke@7044: } aheinecke@7044: aheinecke@7044: /** True if x axis has been inverted. */ aheinecke@7044: public boolean isInverted() { aheinecke@7044: return inverted; aheinecke@7044: } aheinecke@7044: aheinecke@7044: aheinecke@7106: /** Set to true if x axis should be inverted. aheinecke@7106: * This can not be set to false afterwards. */ aheinecke@7112: public void setInverted(boolean value) { aheinecke@7112: /* One request to invert dominates. */ aheinecke@7112: if (!inverted) { aheinecke@7112: inverted = value; aheinecke@7112: } aheinecke@7044: } aheinecke@7044: aheinecke@7068: @Override aheinecke@7068: public String getDefaultChartTitle() { aheinecke@7068: DiagramAttributes.Title dTitle = diagramAttributes.getTitle(); aheinecke@7068: if (dTitle == null) { aheinecke@7068: return "Title not configured in conf.xml"; aheinecke@7068: } aheinecke@7068: aheinecke@7068: return dTitle.evaluate((D4EArtifact)getMaster(), context); aheinecke@7068: } aheinecke@7068: aheinecke@7068: @Override aheinecke@7068: public String getDefaultChartSubtitle() { rrenkert@7806: String parts = ""; rrenkert@7877: DiagramAttributes.Title dTitle = diagramAttributes.getSubtitle(); rrenkert@7877: if (dTitle == null && rrenkert@7877: (subTitleParts == null || subTitleParts.isEmpty())) { rrenkert@7877: /* Subtitle is optional */ rrenkert@7877: return null; rrenkert@7877: } rrenkert@7806: if (subTitleParts != null && !subTitleParts.isEmpty()) { rrenkert@7877: boolean first = true; rrenkert@7877: if (dTitle != null) { rrenkert@7877: first = false; rrenkert@7877: } rrenkert@7806: for (String p : subTitleParts) { rrenkert@7877: if (!first) { rrenkert@7877: parts += ", "; rrenkert@7877: } rrenkert@7877: parts += p; rrenkert@7877: first = false; rrenkert@7806: } rrenkert@7806: } rrenkert@7877: if (dTitle == null && parts.length() > 0) { rrenkert@7877: return parts; aheinecke@7068: } rrenkert@7806: return dTitle.evaluate((D4EArtifact)getMaster(), context) + parts; aheinecke@7068: } aheinecke@7068: aheinecke@7068: /** aheinecke@7068: * Get internationalized label for the x axis. aheinecke@7068: */ aheinecke@7068: @Override aheinecke@7068: protected String getDefaultXAxisLabel() { teichmann@7143: DiagramAttributes.DomainAxisAttributes dx = teichmann@7143: diagramAttributes.getDomainAxis(); teichmann@7143: teichmann@7143: if (dx != null) { teichmann@7143: DiagramAttributes.Title t = dx.getTitle(); teichmann@7143: if (t != null) { teichmann@7143: return t.evaluate((D4EArtifact)getMaster(), context); teichmann@7143: } aheinecke@7084: } teichmann@7143: return "Domain Axis Title not configured in conf.xml"; aheinecke@7068: } aheinecke@7068: aheinecke@7068: @Override aheinecke@7084: protected String getDefaultYAxisLabel(String axisName) { aheinecke@7597: Set labelSet = axesLabels.get(diagramAttributes.getAxisIndex(axisName)); aheinecke@7597: logger.debug("Labels for axis: " + labelSet); aheinecke@7603: if (labelSet != null && !labelSet.isEmpty()) { aheinecke@7597: String label = StringUtils.join(labelSet, ", "); aheinecke@7597: Matcher units = UNIT_PATTERN.matcher(label); aheinecke@7597: if (units.find()) { aheinecke@7597: String firstUnit = units.group(); aheinecke@7597: label = units.replaceAll(""); aheinecke@7597: label += firstUnit; aheinecke@7597: } aheinecke@7597: return label; aheinecke@7597: } aheinecke@7122: for (Processor pr: diagramAttributes.getProcessorsForAxisName(axisName)) { aheinecke@7597: String label = pr.getAxisLabel(this); aheinecke@7122: if (label != null) { aheinecke@7122: return label; aheinecke@7122: } aheinecke@7122: } aheinecke@7122: return "No configured axis label"; aheinecke@7068: } aheinecke@7068: aheinecke@7068: aheinecke@7068: /** aheinecke@7068: * Creates a list of Section for the chart's Y axes. aheinecke@7068: * aheinecke@7068: * @return a list of Y axis sections. aheinecke@7068: */ aheinecke@7068: protected List buildYAxisSections() { aheinecke@7068: List axisSections = new ArrayList(); aheinecke@7068: aheinecke@7068: List axesAttrs = diagramAttributes.getAxesAttributes(); aheinecke@7068: aheinecke@7068: for (int i = 0, n = axesAttrs.size(); i < n; i++) { aheinecke@7068: AxisSection ySection = new AxisSection(); aheinecke@7084: String axisName = diagramAttributes.getAxisName(i); aheinecke@7084: ySection.setIdentifier(axisName); aheinecke@7084: ySection.setLabel(getYAxisLabel(axisName)); aheinecke@7597: ySection.setSuggestedLabel(getDefaultYAxisLabel(axisName)); aheinecke@7068: ySection.setFontSize(14); aheinecke@7068: ySection.setFixed(false); aheinecke@7068: aheinecke@7068: // XXX We are able to find better default ranges that [0,0], the aheinecke@7068: // only problem is, that we do NOT have a better range than [0,0] aheinecke@7068: // for each axis, because the initial chart will not have a dataset aheinecke@7068: // for each axis set! aheinecke@7068: ySection.setUpperRange(0d); aheinecke@7068: ySection.setLowerRange(0d); aheinecke@7068: aheinecke@7068: axisSections.add(ySection); aheinecke@7068: } aheinecke@7068: aheinecke@7068: return axisSections; aheinecke@7068: } aheinecke@7068: aheinecke@7241: protected String getYAxisLabel(int index) { aheinecke@7241: return getYAxisLabel(diagramAttributes.getAxisName(index)); aheinecke@7241: } aheinecke@7241: aheinecke@7084: /** aheinecke@7084: * Returns the Y-Axis label of a chart at position pos. aheinecke@7084: * aheinecke@7084: * @return the Y-Axis label of a chart at position 0. aheinecke@7084: */ aheinecke@7084: protected String getYAxisLabel(String axisName) { aheinecke@7084: ChartSettings chartSettings = getChartSettings(); aheinecke@7084: if (chartSettings == null) { aheinecke@7084: return getDefaultYAxisLabel(axisName); aheinecke@7084: } aheinecke@7084: AxisSection as = chartSettings.getAxisSection(axisName); aheinecke@7084: if (as != null) { aheinecke@7084: String label = as.getLabel(); aheinecke@7597: if (label != null && !label.equals(as.getSuggestedLabel())) { aheinecke@7597: // Only if the suggested label is not the current label aheinecke@7597: // the user has modified the label. Otherwise lets aheinecke@7597: // recalculate the label aheinecke@7084: return label; aheinecke@7084: } aheinecke@7084: } aheinecke@7084: aheinecke@7084: return getDefaultYAxisLabel(axisName); aheinecke@7084: } aheinecke@7084: aheinecke@7068: protected String axisIndexToName(int index) { aheinecke@7068: return diagramAttributes.getAxisName(index); aheinecke@7068: } aheinecke@7068: aheinecke@7044: /** Add the acutal data to the diagram according to the processors. aheinecke@7044: * For every outable facets, this function is aheinecke@7044: * called and handles the data accordingly. */ aheinecke@7044: @Override teichmann@7052: public void doOut( teichmann@7052: ArtifactAndFacet bundle, teichmann@7052: ThemeDocument theme, teichmann@7052: boolean visible teichmann@7052: ) { aheinecke@7044: String facetName = bundle.getFacetName(); aheinecke@7044: Facet facet = bundle.getFacet(); aheinecke@7044: aheinecke@7044: /* A conservative security check */ aheinecke@7044: if (facetName == null || facet == null) { aheinecke@7044: /* Can't happen,.. */ aheinecke@7044: logger.error("doOut called with null facet."); teichmann@7052: return; aheinecke@7044: } aheinecke@7044: aheinecke@7044: logger.debug("DoOut for facet: " + facetName); aheinecke@7044: aheinecke@7096: boolean found = false; aheinecke@7096: List prL = diagramAttributes.getProcessors(); aheinecke@7096: for (Processor pr: prL) { aheinecke@7044: if (pr.canHandle(facetName)) { aheinecke@7096: found = true; aheinecke@7068: pr.doOut(this, bundle, theme, visible); aheinecke@7597: aheinecke@7597: if (visible) { aheinecke@7597: // Save the label that should be added for this processor aheinecke@7597: int axisIdx = diagramAttributes.getAxisIndex(pr.getAxisName()); aheinecke@7597: LinkedHashSet curLabels = axesLabels.get(axisIdx); aheinecke@7597: if (curLabels == null) { aheinecke@7597: curLabels = new LinkedHashSet(5); aheinecke@7597: } aheinecke@7597: curLabels.add(pr.getAxisLabel(this)); aheinecke@7597: axesLabels.put(axisIdx, curLabels); aheinecke@7597: } aheinecke@7044: } aheinecke@7044: } aheinecke@7096: if (!found) { aheinecke@7096: logger.warn("No processor found for: " + facetName); aheinecke@7096: if (logger.isDebugEnabled()) { aheinecke@7096: logger.debug("Configured processors for this diagram are:"); aheinecke@7096: for (Processor pr: prL) { aheinecke@7096: logger.debug(pr.getClass().getName()); aheinecke@7096: } aheinecke@7096: } aheinecke@7096: } aheinecke@7044: } aheinecke@7107: aheinecke@7107: @Override aheinecke@7107: protected NumberAxis createYAxis(int index) { aheinecke@7241: NumberAxis axis; aheinecke@7241: boolean logarithmic = (Boolean)diagramAttributes.getAxesAttributes(). aheinecke@7241: get(index).isLog().evaluate((D4EArtifact)getMaster(), context); aheinecke@7241: aheinecke@7241: if (logarithmic) { aheinecke@7241: axis = new LogarithmicAxis(getYAxisLabel(index)); aheinecke@7241: } else { aheinecke@7241: axis = super.createYAxis(index); aheinecke@7241: } aheinecke@7107: aheinecke@7107: if (diagramAttributes.getAxesAttributes().get(index).includeZero()) { aheinecke@7107: axis.setAutoRangeIncludesZero(true); aheinecke@7107: } aheinecke@7107: return axis; aheinecke@7107: } rrenkert@7806: rrenkert@7806: /** rrenkert@7806: * @return the subtitle parts rrenkert@7806: */ rrenkert@7806: public HashSet getSubTitleParts() { rrenkert@7806: return subTitleParts; rrenkert@7806: } rrenkert@7806: rrenkert@7806: /** rrenkert@7806: * @param part the subtitle part to set rrenkert@7806: */ rrenkert@7806: public void addSubtitle(String part) { rrenkert@7806: this.subTitleParts.add(part); rrenkert@7806: } aheinecke@7044: }