gernotbelger@9104: /** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde gernotbelger@9104: * Software engineering by gernotbelger@9104: * Björnsen Beratende Ingenieure GmbH gernotbelger@9104: * Dr. Schumacher Ingenieurbüro für Wasser und Umwelt gernotbelger@9104: * gernotbelger@9104: * This file is Free Software under the GNU AGPL (>=v3) gernotbelger@9104: * and comes with ABSOLUTELY NO WARRANTY! Check out the gernotbelger@9104: * documentation coming with Dive4Elements River for details. gernotbelger@9104: */ gernotbelger@9104: package org.dive4elements.river.exports; gernotbelger@9104: gernotbelger@9123: import java.awt.BasicStroke; gernotbelger@9123: import java.awt.Color; gernotbelger@9123: import java.awt.Font; gernotbelger@9123: import java.awt.Paint; gernotbelger@9123: import java.awt.Stroke; gernotbelger@9123: import java.awt.TexturePaint; gernotbelger@9123: import java.awt.geom.Rectangle2D; gernotbelger@9123: import java.awt.image.BufferedImage; gernotbelger@9123: import java.io.IOException; gernotbelger@9123: import java.io.OutputStream; gernotbelger@9123: import java.text.DateFormat; gernotbelger@9123: import java.util.ArrayList; gernotbelger@9123: import java.util.Date; gernotbelger@9123: import java.util.List; gernotbelger@9123: import java.util.Locale; gernotbelger@9123: import java.util.Map; gernotbelger@9123: import java.util.SortedMap; gernotbelger@9123: import java.util.TreeMap; gernotbelger@9123: gernotbelger@9104: import javax.xml.xpath.XPathConstants; gernotbelger@9104: gernotbelger@9123: import org.apache.log4j.Logger; gernotbelger@9123: import org.dive4elements.artifactdatabase.state.ArtifactAndFacet; gernotbelger@9123: import org.dive4elements.artifactdatabase.state.Settings; gernotbelger@9123: import org.dive4elements.artifacts.Artifact; gernotbelger@9104: import org.dive4elements.artifacts.ArtifactNamespaceContext; gernotbelger@9104: import org.dive4elements.artifacts.CallContext; gernotbelger@9123: import org.dive4elements.artifacts.CallMeta; gernotbelger@9123: import org.dive4elements.artifacts.PreferredLocale; gernotbelger@9104: import org.dive4elements.artifacts.common.utils.XMLUtils; gernotbelger@9123: import org.dive4elements.river.FLYS; gernotbelger@9104: import org.dive4elements.river.artifacts.D4EArtifact; gernotbelger@9104: import org.dive4elements.river.artifacts.access.RangeAccess; gernotbelger@9104: import org.dive4elements.river.artifacts.access.RiverAccess; gernotbelger@9123: import org.dive4elements.river.artifacts.resources.Resources; gernotbelger@9123: import org.dive4elements.river.artifacts.sinfo.util.CalculationUtils; gernotbelger@9123: import org.dive4elements.river.collections.D4EArtifactCollection; gernotbelger@9123: import org.dive4elements.river.jfree.AxisDataset; gernotbelger@9123: import org.dive4elements.river.jfree.Bounds; gernotbelger@9123: import org.dive4elements.river.jfree.DoubleBounds; gernotbelger@9123: import org.dive4elements.river.jfree.EnhancedLineAndShapeRenderer; gernotbelger@9123: import org.dive4elements.river.jfree.RiverAnnotation; gernotbelger@9123: import org.dive4elements.river.jfree.StableXYDifferenceRenderer; gernotbelger@9123: import org.dive4elements.river.jfree.Style; gernotbelger@9123: import org.dive4elements.river.jfree.StyledAreaSeriesCollection; gernotbelger@9123: import org.dive4elements.river.jfree.StyledSeries; gernotbelger@9123: import org.dive4elements.river.themes.ThemeDocument; gernotbelger@9123: import org.dive4elements.river.utils.Formatter; gernotbelger@9104: import org.jfree.chart.JFreeChart; gernotbelger@9123: import org.jfree.chart.LegendItem; gernotbelger@9123: import org.jfree.chart.LegendItemCollection; gernotbelger@9123: import org.jfree.chart.axis.NumberAxis; gernotbelger@9123: import org.jfree.chart.plot.XYPlot; gernotbelger@9123: import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; gernotbelger@9104: import org.jfree.chart.title.TextTitle; gernotbelger@9123: import org.jfree.data.Range; gernotbelger@9123: import org.jfree.data.general.Series; gernotbelger@9123: import org.jfree.data.xy.XYDataset; gernotbelger@9123: import org.jfree.ui.RectangleInsets; gernotbelger@9104: import org.w3c.dom.Document; gernotbelger@9123: import org.w3c.dom.Element; gernotbelger@9104: gernotbelger@9104: /** gernotbelger@9123: * This class re-unites the tremendous copy/paste code from ChartGenerator and ChartGenerator2. Code is still awful and gernotbelger@9123: * encapsulation is broken in too many places. gernotbelger@9123: * TODO: instead of deep inheritances, delegate to classes that define the various behaviors. gernotbelger@9123: * gernotbelger@9104: * @author Gernot Belger gernotbelger@9104: */ gernotbelger@9104: abstract class AbstractChartGenerator implements OutGenerator { gernotbelger@9123: gernotbelger@9123: protected static final Logger log = Logger.getLogger(AbstractChartGenerator.class); gernotbelger@9123: gernotbelger@9123: private static final int DEFAULT_CHART_WIDTH = 600; gernotbelger@9123: gernotbelger@9123: private static final int DEFAULT_CHART_HEIGHT = 400; gernotbelger@9123: gernotbelger@9123: private static final Color DEFAULT_GRID_COLOR = Color.GRAY; gernotbelger@9123: gernotbelger@9123: private static final float DEFAULT_GRID_LINE_WIDTH = 0.3f; gernotbelger@9123: gernotbelger@9123: protected static final String DEFAULT_FONT_NAME = "Tahoma"; gernotbelger@9123: gernotbelger@9123: protected static final int DEFAULT_FONT_SIZE = 12; gernotbelger@9123: gernotbelger@9123: private static final String DEFAULT_CHART_FORMAT = "png"; gernotbelger@9123: gernotbelger@9104: private static final String XPATH_CHART_EXPORT = "/art:action/art:attributes/art:export/@art:value"; gernotbelger@9104: gernotbelger@9123: private static final String XPATH_CHART_SIZE = "/art:action/art:attributes/art:size"; gernotbelger@9123: gernotbelger@9123: private static final String XPATH_CHART_FORMAT = "/art:action/art:attributes/art:format/@art:value"; gernotbelger@9123: gernotbelger@9123: private static final String XPATH_CHART_X_RANGE = "/art:action/art:attributes/art:xrange"; gernotbelger@9123: gernotbelger@9123: private static final String XPATH_CHART_Y_RANGE = "/art:action/art:attributes/art:yrange"; gernotbelger@9123: gernotbelger@9123: /** The document of the incoming out() request. */ gernotbelger@9123: private Document request; gernotbelger@9123: gernotbelger@9123: /** The output stream where the data should be written to. */ gernotbelger@9123: private OutputStream out; gernotbelger@9123: gernotbelger@9123: /** Artifact that is used to decorate the chart with meta information. */ gernotbelger@9123: private Artifact master; gernotbelger@9123: gernotbelger@9123: /** Map of datasets ("index"). */ gernotbelger@9123: private final SortedMap datasets = new TreeMap<>(); gernotbelger@9123: gernotbelger@9123: /** List of annotations to insert in plot. */ gernotbelger@9123: private final List annotations = new ArrayList<>(); gernotbelger@9123: gernotbelger@9123: private String outName; gernotbelger@9123: gernotbelger@9123: /** The settings that should be used during output creation. */ gernotbelger@9123: private Settings settings; gernotbelger@9104: gernotbelger@9104: /** The CallContext object. */ gernotbelger@9123: private CallContext context; gernotbelger@9123: gernotbelger@9123: @Override gernotbelger@9123: public void init(final String outName, final Document request, final OutputStream out, final CallContext context) { gernotbelger@9123: log.debug("ChartGenerator.init"); gernotbelger@9123: gernotbelger@9123: this.outName = outName; gernotbelger@9123: this.request = request; gernotbelger@9123: this.out = out; gernotbelger@9123: this.context = context; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: @Override gernotbelger@9123: public void setup(final Object config) { gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** Sets the master artifact. */ gernotbelger@9123: @Override gernotbelger@9123: public void setMasterArtifact(final Artifact master) { gernotbelger@9123: this.master = master; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Gets the master artifact. gernotbelger@9123: * gernotbelger@9123: * @return the master artifact. gernotbelger@9123: */ gernotbelger@9123: public Artifact getMaster() { gernotbelger@9123: return this.master; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: protected final Map getDatasets() { gernotbelger@9123: return this.datasets; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: @Override gernotbelger@9123: public void setCollection(final D4EArtifactCollection collection) { gernotbelger@9123: /* we do not need it */ gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: protected final D4EArtifact getArtifact() { gernotbelger@9123: // FIXME: should already made sure when this member is set gernotbelger@9123: return (D4EArtifact) this.master; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: public final CallContext getContext() { gernotbelger@9123: return this.context; gernotbelger@9123: } gernotbelger@9104: gernotbelger@9104: /** The document of the incoming out() request. */ gernotbelger@9123: protected final Document getRequest() { gernotbelger@9123: return this.request; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Adds annotations to list. The given annotation will be visible. gernotbelger@9123: */ gernotbelger@9123: public final void addAnnotations(final RiverAnnotation annotation) { gernotbelger@9123: this.annotations.add(annotation); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * This method needs to be implemented by concrete subclasses to create new gernotbelger@9123: * instances of JFreeChart. gernotbelger@9123: * gernotbelger@9123: * @param context2 gernotbelger@9123: * gernotbelger@9123: * @return a new instance of a JFreeChart. gernotbelger@9123: */ gernotbelger@9123: protected abstract JFreeChart generateChart(CallContext context2); gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * For every outable (i.e. facets), this function is gernotbelger@9123: * called and handles the data accordingly. gernotbelger@9123: */ gernotbelger@9123: @Override gernotbelger@9123: public abstract void doOut(ArtifactAndFacet bundle, ThemeDocument attr, boolean visible); gernotbelger@9123: gernotbelger@9123: @Override gernotbelger@9123: public void generate() throws IOException { gernotbelger@9123: doGenerate(this.context, this.out, this.outName); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: protected abstract void doGenerate(CallContext context, OutputStream out, String outName) throws IOException; gernotbelger@9123: gernotbelger@9123: protected abstract Series getSeriesOf(XYDataset dataset, int idx); gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Returns the default title of a chart. gernotbelger@9123: * gernotbelger@9123: * @param context2 gernotbelger@9123: * gernotbelger@9123: * @return the default title of a chart. gernotbelger@9123: */ gernotbelger@9123: protected abstract String getDefaultChartTitle(CallContext context); gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * This method is used to create new AxisDataset instances which may differ gernotbelger@9123: * in concrete subclasses. gernotbelger@9123: * gernotbelger@9123: * @param idx gernotbelger@9123: * The index of an axis. gernotbelger@9123: */ gernotbelger@9123: protected abstract AxisDataset createAxisDataset(int idx); gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Combines the ranges of the X axis at index idx. gernotbelger@9123: * gernotbelger@9123: * @param bounds gernotbelger@9123: * A new Bounds. gernotbelger@9123: * @param idx gernotbelger@9123: * The index of the X axis that should be comined with gernotbelger@9123: * range. gernotbelger@9123: */ gernotbelger@9123: protected abstract void combineXBounds(Bounds bounds, int idx); gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Combines the ranges of the Y axis at index idx. gernotbelger@9123: * gernotbelger@9123: * @param bounds gernotbelger@9123: * A new Bounds. gernotbelger@9123: * @param index gernotbelger@9123: * The index of the Y axis that should be comined with. gernotbelger@9123: * range. gernotbelger@9123: */ gernotbelger@9123: protected abstract void combineYBounds(Bounds bounds, int index); gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * This method is used to determine the ranges for axes at a given index. gernotbelger@9123: * gernotbelger@9123: * @param index gernotbelger@9123: * The index of the axes at the plot. gernotbelger@9123: * gernotbelger@9123: * @return a Range[] with [xrange, yrange]; gernotbelger@9123: */ gernotbelger@9123: protected abstract Range[] getRangesForAxis(int index); gernotbelger@9123: gernotbelger@9123: protected abstract Bounds getXBounds(int axis); gernotbelger@9123: gernotbelger@9123: protected abstract void setXBounds(int axis, Bounds bounds); gernotbelger@9123: gernotbelger@9123: protected abstract Bounds getYBounds(int axis); gernotbelger@9123: gernotbelger@9123: protected abstract void setYBounds(int axis, Bounds bounds); gernotbelger@9123: gernotbelger@9123: // /** gernotbelger@9123: // * Retuns the call context. May be null if init hasn't been called yet. gernotbelger@9123: // * gernotbelger@9123: // * @return the CallContext instance gernotbelger@9123: // */ gernotbelger@9123: // protected final CallContext getCallContext() { gernotbelger@9123: // return this.context; gernotbelger@9123: // } gernotbelger@9123: // gernotbelger@9123: gernotbelger@9123: @Override gernotbelger@9123: public final void setSettings(final Settings settings) { gernotbelger@9123: this.settings = settings; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Returns the settings as ChartSettings. gernotbelger@9123: * gernotbelger@9123: * @return the settings as ChartSettings or null, if gernotbelger@9123: * settings is not an instance of ChartSettings. gernotbelger@9123: */ gernotbelger@9123: protected final ChartSettings getChartSettings() { gernotbelger@9123: if (this.settings instanceof ChartSettings) { gernotbelger@9123: return (ChartSettings) this.settings; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: return null; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Return instance of ChartSettings with a chart specific section gernotbelger@9123: * but with no axes settings. gernotbelger@9123: * gernotbelger@9123: * @return an instance of ChartSettings. gernotbelger@9123: */ gernotbelger@9123: @Override gernotbelger@9123: public final Settings getSettings() { gernotbelger@9123: if (this.settings != null) gernotbelger@9123: return this.settings; gernotbelger@9123: gernotbelger@9123: final ChartSettings settings = new ChartSettings(); gernotbelger@9123: gernotbelger@9123: final ChartSection chartSection = buildChartSection(this.context); gernotbelger@9123: final LegendSection legendSection = buildLegendSection(); gernotbelger@9123: final ExportSection exportSection = buildExportSection(); gernotbelger@9123: gernotbelger@9123: settings.setChartSection(chartSection); gernotbelger@9123: settings.setLegendSection(legendSection); gernotbelger@9123: settings.setExportSection(exportSection); gernotbelger@9123: gernotbelger@9123: final List axisSections = buildAxisSections(); gernotbelger@9123: for (final AxisSection axisSection : axisSections) gernotbelger@9123: settings.addAxisSection(axisSection); gernotbelger@9123: gernotbelger@9123: return settings; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: protected abstract ChartSection buildChartSection(CallContext context); gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Creates a new LegendSection. gernotbelger@9123: * gernotbelger@9123: * @return a new LegendSection. gernotbelger@9123: */ gernotbelger@9123: private LegendSection buildLegendSection() { gernotbelger@9123: final LegendSection legendSection = new LegendSection(); gernotbelger@9123: legendSection.setVisibility(isLegendVisible()); gernotbelger@9123: legendSection.setFontSize(getLegendFontSize()); gernotbelger@9123: legendSection.setAggregationThreshold(10); gernotbelger@9123: return legendSection; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Creates a new ExportSection with default values WIDTH=600 gernotbelger@9123: * and HEIGHT=400. gernotbelger@9123: * gernotbelger@9123: * @return a new ExportSection. gernotbelger@9123: */ gernotbelger@9123: private ExportSection buildExportSection() { gernotbelger@9123: final ExportSection exportSection = new ExportSection(); gernotbelger@9123: exportSection.setWidth(DEFAULT_CHART_WIDTH); gernotbelger@9123: exportSection.setHeight(DEFAULT_CHART_HEIGHT); gernotbelger@9123: exportSection.setMetadata(true); gernotbelger@9123: return exportSection; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Creates a list of Sections that contains all axes of the chart (including gernotbelger@9123: * X and Y axes). gernotbelger@9123: * gernotbelger@9123: * @return a list of Sections for each axis in this chart. gernotbelger@9123: */ gernotbelger@9123: private List buildAxisSections() { gernotbelger@9123: final List axisSections = new ArrayList<>(); gernotbelger@9123: gernotbelger@9123: axisSections.addAll(buildXAxisSections()); gernotbelger@9123: axisSections.addAll(buildYAxisSections()); gernotbelger@9123: gernotbelger@9123: return axisSections; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Creates a new Section for chart's X axis. gernotbelger@9123: * gernotbelger@9123: * @return a List that contains a Section for the X axis. gernotbelger@9123: */ gernotbelger@9123: protected List buildXAxisSections() { gernotbelger@9123: final List axisSections = new ArrayList<>(); gernotbelger@9123: gernotbelger@9123: final String identifier = "X"; gernotbelger@9123: gernotbelger@9123: final AxisSection axisSection = new AxisSection(); gernotbelger@9123: axisSection.setIdentifier(identifier); gernotbelger@9123: axisSection.setLabel(getXAxisLabel()); gernotbelger@9123: axisSection.setFontSize(14); gernotbelger@9123: axisSection.setFixed(false); gernotbelger@9123: gernotbelger@9123: // XXX We are able to find better default ranges that [0,0], but the Y gernotbelger@9123: // axes currently have no better ranges set. gernotbelger@9123: axisSection.setUpperRange(0d); gernotbelger@9123: axisSection.setLowerRange(0d); gernotbelger@9123: gernotbelger@9123: axisSections.add(axisSection); gernotbelger@9123: gernotbelger@9123: return axisSections; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Returns the X-Axis label of a chart. gernotbelger@9123: * gernotbelger@9123: * @return the X-Axis label of a chart. gernotbelger@9123: */ gernotbelger@9123: protected final String getXAxisLabel() { gernotbelger@9123: final ChartSettings chartSettings = getChartSettings(); gernotbelger@9123: if (chartSettings == null) { gernotbelger@9123: return getDefaultXAxisLabel(this.context); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: final AxisSection as = chartSettings.getAxisSection("X"); gernotbelger@9123: if (as != null) { gernotbelger@9123: final String label = as.getLabel(); gernotbelger@9123: gernotbelger@9123: if (label != null) { gernotbelger@9123: return label; gernotbelger@9123: } gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: return getDefaultXAxisLabel(this.context); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: protected abstract List buildYAxisSections(); gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Returns the default X-Axis label of a chart. gernotbelger@9123: * gernotbelger@9123: * @param context2 gernotbelger@9123: * gernotbelger@9123: * @return the default X-Axis label of a chart. gernotbelger@9123: */ gernotbelger@9123: protected abstract String getDefaultXAxisLabel(final CallContext context2); gernotbelger@9123: gernotbelger@9123: /** Generate the diagram as an image. */ gernotbelger@9123: protected final void generateImage(final CallContext context) throws IOException { gernotbelger@9123: log.debug("ChartGenerator2.generateImage"); gernotbelger@9123: gernotbelger@9123: final JFreeChart chart = generateChart(context); gernotbelger@9123: gernotbelger@9123: final String format = getFormat(); gernotbelger@9123: int[] size = getSize(); gernotbelger@9123: gernotbelger@9123: if (size == null) gernotbelger@9123: size = getExportDimension(); gernotbelger@9123: gernotbelger@9123: this.context.putContextValue("chart.width", size[0]); gernotbelger@9123: this.context.putContextValue("chart.height", size[1]); gernotbelger@9123: gernotbelger@9123: if (format.equals(ChartExportHelper.FORMAT_PNG)) { gernotbelger@9123: this.context.putContextValue("chart.image.format", "png"); gernotbelger@9123: gernotbelger@9123: ChartExportHelper.exportImage(this.out, chart, this.context); gernotbelger@9123: } else if (format.equals(ChartExportHelper.FORMAT_PDF)) { gernotbelger@9123: preparePDFContext(this.context); gernotbelger@9123: gernotbelger@9123: ChartExportHelper.exportPDF(this.out, chart, this.context); gernotbelger@9123: } else if (format.equals(ChartExportHelper.FORMAT_SVG)) { gernotbelger@9123: prepareSVGContext(this.context); gernotbelger@9123: gernotbelger@9123: ChartExportHelper.exportSVG(this.out, chart, this.context); gernotbelger@9123: } else if (format.equals(ChartExportHelper.FORMAT_CSV)) { gernotbelger@9123: this.context.putContextValue("chart.image.format", "csv"); gernotbelger@9123: gernotbelger@9123: ChartExportHelper.exportCSV(this.out, chart, this.context); gernotbelger@9123: } gernotbelger@9123: } gernotbelger@9104: gernotbelger@9104: /** gernotbelger@9104: * Adds a metadata sub-title to the chart if it gets exported gernotbelger@9104: */ gernotbelger@9123: protected final void addMetadataSubtitle(final CallContext context, final JFreeChart chart) { gernotbelger@9123: if ((!isExport() || !isExportMetadata())) gernotbelger@9123: return; gernotbelger@9123: gernotbelger@9123: final String version = FLYS.VERSION; gernotbelger@9123: final String user = CalculationUtils.findArtifactUser(context, getArtifact()); gernotbelger@9123: final Locale locale = Resources.getLocale(context.getMeta()); gernotbelger@9123: final DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale); gernotbelger@9123: final String dateText = df.format(new Date()); gernotbelger@9123: gernotbelger@9123: final String text = Resources.getMsg(context.getMeta(), "chart.subtitle.metadata", "default", version, user, dateText); gernotbelger@9123: gernotbelger@9123: chart.addSubtitle(new TextTitle(text)); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: private boolean isExportMetadata() { gernotbelger@9123: final ChartSettings chartSettings = getChartSettings(); gernotbelger@9123: if (chartSettings == null) gernotbelger@9123: return true; gernotbelger@9123: gernotbelger@9123: final ExportSection exportSection = chartSettings.getExportSection(); gernotbelger@9123: if (exportSection == null) gernotbelger@9123: return true; gernotbelger@9123: gernotbelger@9123: return exportSection.getMetadata(); gernotbelger@9104: } gernotbelger@9104: gernotbelger@9104: /** gernotbelger@9104: * This method returns the export flag specified in the request document gernotbelger@9104: * or false if no export is specified in request. gernotbelger@9104: */ gernotbelger@9123: private boolean isExport() { gernotbelger@9104: final Boolean export = (Boolean) XMLUtils.xpath(getRequest(), XPATH_CHART_EXPORT, XPathConstants.BOOLEAN, ArtifactNamespaceContext.INSTANCE); gernotbelger@9104: gernotbelger@9104: return export == null ? false : export; gernotbelger@9104: } gernotbelger@9104: gernotbelger@9104: protected final String getRiverName() { gernotbelger@9104: return new RiverAccess(getArtifact()).getRiver().getName(); gernotbelger@9104: } gernotbelger@9104: gernotbelger@9104: protected final String getRiverUnit() { gernotbelger@9104: return new RiverAccess(getArtifact()).getRiver().getWstUnit().getName(); gernotbelger@9104: } gernotbelger@9104: gernotbelger@9104: protected final double[] getRange() { gernotbelger@9104: final D4EArtifact flys = getArtifact(); gernotbelger@9104: gernotbelger@9104: final RangeAccess rangeAccess = new RangeAccess(flys); gernotbelger@9104: return rangeAccess.getKmRange(); gernotbelger@9104: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Returns a boolean object that determines if the chart grid should be gernotbelger@9123: * visible or not. This information needs to be provided by settings, gernotbelger@9123: * otherwise the default is true. gernotbelger@9123: * gernotbelger@9123: * @param settings gernotbelger@9123: * A ChartSettings object. gernotbelger@9123: * gernotbelger@9123: * @return true, if the chart grid should be visible otherwise false. gernotbelger@9123: * gernotbelger@9123: * @throws NullPointerException gernotbelger@9123: * if settings is null. gernotbelger@9123: */ gernotbelger@9123: private boolean isGridVisible(final ChartSettings settings) { gernotbelger@9123: final ChartSection cs = settings.getChartSection(); gernotbelger@9123: return cs.getDisplayGrid(); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * This method is used to determine, if the chart's legend is visible or gernotbelger@9123: * not. If a settings instance is set, this instance determines the gernotbelger@9123: * visibility otherwise, this method returns true as default if no gernotbelger@9123: * settings is set. gernotbelger@9123: * gernotbelger@9123: * @return true, if the legend should be visible, otherwise false. gernotbelger@9123: */ gernotbelger@9123: protected final boolean isLegendVisible() { gernotbelger@9123: final ChartSettings chartSettings = getChartSettings(); gernotbelger@9123: if (chartSettings == null) gernotbelger@9123: return true; gernotbelger@9123: gernotbelger@9123: final LegendSection ls = chartSettings.getLegendSection(); gernotbelger@9123: return ls.getVisibility(); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * This method returns the font size for the X axis. If the font size is gernotbelger@9123: * specified in ChartSettings (if chartSettings is set), this size is gernotbelger@9123: * returned. Otherwise the default font size 12 is returned. gernotbelger@9123: * gernotbelger@9123: * @return the font size for the x axis. gernotbelger@9123: */ gernotbelger@9123: protected final int getXAxisLabelFontSize() { gernotbelger@9123: final ChartSettings chartSettings = getChartSettings(); gernotbelger@9123: if (chartSettings == null) { gernotbelger@9123: return DEFAULT_FONT_SIZE; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: final AxisSection as = chartSettings.getAxisSection("X"); gernotbelger@9123: final Integer fontSize = as.getFontSize(); gernotbelger@9123: gernotbelger@9123: return fontSize != null ? fontSize : DEFAULT_FONT_SIZE; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * This method is used to determine the font size of the chart's legend. If gernotbelger@9123: * a settings instance is set, this instance determines the font gernotbelger@9123: * size, otherwise this method returns 12 as default if no settings gernotbelger@9123: * is set or if it doesn't provide a legend font size. gernotbelger@9123: * gernotbelger@9123: * @return a legend font size. gernotbelger@9123: */ gernotbelger@9123: private int getLegendFontSize() { gernotbelger@9123: gernotbelger@9123: final ChartSettings chartSettings = getChartSettings(); gernotbelger@9123: if (chartSettings == null) gernotbelger@9123: return DEFAULT_FONT_SIZE; gernotbelger@9123: gernotbelger@9123: final LegendSection ls = chartSettings.getLegendSection(); gernotbelger@9123: if (ls == null) gernotbelger@9123: return DEFAULT_FONT_SIZE; gernotbelger@9123: gernotbelger@9123: final Integer fontSize = ls.getFontSize(); gernotbelger@9123: if (fontSize == null) gernotbelger@9123: return DEFAULT_FONT_SIZE; gernotbelger@9123: gernotbelger@9123: return fontSize; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Creates a new LegendItem with name and font provided by gernotbelger@9123: * createLegendLabelFont(). gernotbelger@9123: * gernotbelger@9123: * @param theme gernotbelger@9123: * The theme of the chart line. gernotbelger@9123: * @param name gernotbelger@9123: * The displayed name of the item. gernotbelger@9123: * gernotbelger@9123: * @return a new LegendItem instance. gernotbelger@9123: */ gernotbelger@9123: protected final LegendItem createLegendItem(final ThemeDocument theme, final String name) { gernotbelger@9123: // OPTIMIZE Pass font, parsed Theme items. gernotbelger@9123: gernotbelger@9123: Color color = theme.parseLineColorField(); gernotbelger@9123: if (color == null) gernotbelger@9123: color = Color.BLACK; gernotbelger@9123: gernotbelger@9123: final LegendItem legendItem = new LegendItem(name, color); gernotbelger@9123: legendItem.setLabelFont(createLegendLabelFont()); gernotbelger@9123: return legendItem; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Create new legend entries, dependent on settings. gernotbelger@9123: * gernotbelger@9123: * @param plot gernotbelger@9123: * The plot for which to modify the legend. gernotbelger@9123: */ gernotbelger@9123: protected final void aggregateLegendEntries(final XYPlot plot) { gernotbelger@9123: gernotbelger@9123: final ChartSettings chartSettings = getChartSettings(); gernotbelger@9123: if (chartSettings == null) gernotbelger@9123: return; gernotbelger@9123: gernotbelger@9123: final Integer threshold = chartSettings.getLegendSection().getAggregationThreshold(); gernotbelger@9123: gernotbelger@9123: final int aggrThreshold = threshold != null ? threshold.intValue() : 0; gernotbelger@9123: gernotbelger@9123: LegendProcessor.aggregateLegendEntries(plot, aggrThreshold); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Creates Font (Family and size) to use when creating Legend Items. The gernotbelger@9123: * font size depends in the return value of getLegendFontSize(). gernotbelger@9123: * gernotbelger@9123: * @return a new Font instance with DEFAULT_FONT_NAME. gernotbelger@9123: */ gernotbelger@9123: private final Font createLegendLabelFont() { gernotbelger@9123: return new Font(DEFAULT_FONT_NAME, Font.PLAIN, getLegendFontSize()); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Adjust some Stroke/Grid parameters for plot. The chart gernotbelger@9123: * Settings are applied in this method. gernotbelger@9123: * gernotbelger@9123: * @param plot gernotbelger@9123: * The XYPlot which is adapted. gernotbelger@9123: */ gernotbelger@9123: protected void adjustPlot(final XYPlot plot) { gernotbelger@9123: final Stroke gridStroke = new BasicStroke(DEFAULT_GRID_LINE_WIDTH, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 3.0f, new float[] { 3.0f }, 0.0f); gernotbelger@9123: gernotbelger@9123: final ChartSettings cs = getChartSettings(); gernotbelger@9123: final boolean isGridVisible = cs != null ? isGridVisible(cs) : true; gernotbelger@9123: gernotbelger@9123: plot.setDomainGridlineStroke(gridStroke); gernotbelger@9123: plot.setDomainGridlinePaint(DEFAULT_GRID_COLOR); gernotbelger@9123: plot.setDomainGridlinesVisible(isGridVisible); gernotbelger@9123: gernotbelger@9123: plot.setRangeGridlineStroke(gridStroke); gernotbelger@9123: plot.setRangeGridlinePaint(DEFAULT_GRID_COLOR); gernotbelger@9123: plot.setRangeGridlinesVisible(isGridVisible); gernotbelger@9123: gernotbelger@9123: plot.setAxisOffset(new RectangleInsets(0d, 0d, 0d, 0d)); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * This helper method is used to extract the current locale from instance variable context. gernotbelger@9123: * gernotbelger@9123: * @return the current locale. gernotbelger@9123: */ gernotbelger@9123: protected final Locale getLocale() { gernotbelger@9123: final CallMeta meta = this.context.getMeta(); gernotbelger@9123: final PreferredLocale[] prefs = meta.getLanguages(); gernotbelger@9123: gernotbelger@9123: final int len = prefs != null ? prefs.length : 0; gernotbelger@9123: gernotbelger@9123: final Locale[] locales = new Locale[len]; gernotbelger@9123: gernotbelger@9123: for (int i = 0; i < len; i++) { gernotbelger@9123: locales[i] = prefs[i].getLocale(); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: return meta.getPreferredLocale(locales); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Look up \param key in i18n dictionary. gernotbelger@9123: * gernotbelger@9123: * @param key gernotbelger@9123: * key for which to find i18nd version. gernotbelger@9123: * @param def gernotbelger@9123: * default, returned if lookup failed. gernotbelger@9123: * @return value found in i18n dictionary, \param def if no value found. gernotbelger@9123: */ gernotbelger@9123: public final String msg(final String key, final String def) { gernotbelger@9123: return Resources.getMsg(this.context.getMeta(), key, def); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Look up \param key in i18n dictionary. gernotbelger@9123: * gernotbelger@9123: * @param key gernotbelger@9123: * key for which to find i18nd version. gernotbelger@9123: * @return value found in i18n dictionary, key itself if failed. gernotbelger@9123: */ gernotbelger@9123: public final String msg(final String key) { gernotbelger@9123: return Resources.getMsg(this.context.getMeta(), key, key); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: public final String msg(final String key, final String def, final Object[] args) { gernotbelger@9123: return Resources.getMsg(this.context.getMeta(), key, def, args); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Add datasets stored in instance variable datasets to plot. gernotbelger@9123: * datasets actually stores instances of AxisDataset, so each of this gernotbelger@9123: * datasets is mapped to a specific axis as well. gernotbelger@9123: * gernotbelger@9123: * @param plot gernotbelger@9123: * plot to add datasets to. gernotbelger@9123: */ gernotbelger@9123: protected void addDatasets(final XYPlot plot) { gernotbelger@9123: log.debug("addDatasets()"); gernotbelger@9123: gernotbelger@9123: // AxisDatasets are sorted, but some might be empty. gernotbelger@9123: // Thus, generate numbering on the fly. gernotbelger@9123: int axisIndex = 0; gernotbelger@9123: int datasetIndex = 0; gernotbelger@9123: gernotbelger@9123: for (final Map.Entry entry : this.datasets.entrySet()) { gernotbelger@9123: if (!entry.getValue().isEmpty()) { gernotbelger@9123: // Add axis and range information. gernotbelger@9123: final AxisDataset axisDataset = entry.getValue(); gernotbelger@9123: final NumberAxis axis = createYAxis(entry.getKey()); gernotbelger@9123: gernotbelger@9123: plot.setRangeAxis(axisIndex, axis); gernotbelger@9123: gernotbelger@9123: if (axis.getAutoRangeIncludesZero()) { gernotbelger@9123: axisDataset.setRange(Range.expandToInclude(axisDataset.getRange(), 0d)); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: setYBounds(axisIndex, expandPointRange(axisDataset.getRange())); gernotbelger@9123: gernotbelger@9123: // Add contained datasets, mapping to axis. gernotbelger@9123: for (final XYDataset dataset : axisDataset.getDatasets()) { gernotbelger@9123: try { gernotbelger@9123: plot.setDataset(datasetIndex, dataset); gernotbelger@9123: plot.mapDatasetToRangeAxis(datasetIndex, axisIndex); gernotbelger@9123: gernotbelger@9123: applyThemes(plot, dataset, datasetIndex, axisDataset.isArea(dataset)); gernotbelger@9123: gernotbelger@9123: datasetIndex++; gernotbelger@9123: } gernotbelger@9123: catch (final RuntimeException re) { gernotbelger@9123: log.error(re); gernotbelger@9123: } gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: axisDataset.setPlotAxisIndex(axisIndex); gernotbelger@9123: axisIndex++; gernotbelger@9123: } gernotbelger@9123: } gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Create Y (range) axis for given index. gernotbelger@9123: * Shall be implemented by subclasses. gernotbelger@9123: */ gernotbelger@9123: protected abstract NumberAxis createYAxis(final int index); gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * @param idx gernotbelger@9123: * "index" of dataset/series (first dataset to be drawn has gernotbelger@9123: * index 0), correlates with renderer index. gernotbelger@9123: * @param isArea gernotbelger@9123: * true if the series describes an area and shall be rendered gernotbelger@9123: * as such. gernotbelger@9123: */ gernotbelger@9123: private void applyThemes(final XYPlot plot, final XYDataset series, final int idx, final boolean isArea) { gernotbelger@9186: if (isArea) gernotbelger@9123: applyAreaTheme(plot, (StyledAreaSeriesCollection) series, idx); gernotbelger@9186: else gernotbelger@9123: applyLineTheme(plot, series, idx); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Expands a given range if it collapses into one point. gernotbelger@9123: * gernotbelger@9123: * @param range gernotbelger@9123: * Range to be expanded if upper == lower bound. gernotbelger@9123: * gernotbelger@9123: * @return Bounds of point plus 5 percent in each direction. gernotbelger@9123: */ gernotbelger@9123: private Bounds expandPointRange(final Range range) { gernotbelger@9123: if (range == null) { gernotbelger@9123: return null; gernotbelger@9123: } else if (range.getLowerBound() == range.getUpperBound()) { gernotbelger@9123: final Range expandedRange = ChartHelper.expandRange(range, 5d); gernotbelger@9123: return new DoubleBounds(expandedRange.getLowerBound(), expandedRange.getUpperBound()); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: return new DoubleBounds(range.getLowerBound(), range.getUpperBound()); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Creates a new instance of EnhancedLineAndShapeRenderer. gernotbelger@9123: * gernotbelger@9123: * @param plot gernotbelger@9123: * The plot which is set for the new renderer. gernotbelger@9123: * @param idx gernotbelger@9123: * This value is not used in the current implementation. gernotbelger@9123: * gernotbelger@9123: * @return a new instance of EnhancedLineAndShapeRenderer. gernotbelger@9123: */ gernotbelger@9123: private XYLineAndShapeRenderer createRenderer(final XYPlot plot, final int idx) { gernotbelger@9123: log.debug("Create EnhancedLineAndShapeRenderer for idx: " + idx); gernotbelger@9123: gernotbelger@9123: final EnhancedLineAndShapeRenderer r = new EnhancedLineAndShapeRenderer(true, false); gernotbelger@9123: gernotbelger@9123: r.setPlot(plot); gernotbelger@9123: gernotbelger@9123: return r; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * This method applies the themes defined in the series itself. Therefore, gernotbelger@9123: * StyledXYSeries.applyTheme() is called, which modifies the renderer gernotbelger@9123: * for the series. gernotbelger@9123: * gernotbelger@9123: * @param plot gernotbelger@9123: * The plot. gernotbelger@9123: * @param dataset gernotbelger@9123: * The XYDataset which needs to support Series objects. gernotbelger@9123: * @param idx gernotbelger@9123: * The index of the renderer / dataset. gernotbelger@9123: */ gernotbelger@9123: private void applyLineTheme(final XYPlot plot, final XYDataset dataset, final int idx) { gernotbelger@9123: log.debug("Apply LineTheme for dataset at index: " + idx); gernotbelger@9123: gernotbelger@9123: final LegendItemCollection lic = new LegendItemCollection(); gernotbelger@9123: final LegendItemCollection anno = plot.getFixedLegendItems(); gernotbelger@9123: gernotbelger@9123: final Font legendFont = createLegendLabelFont(); gernotbelger@9123: gernotbelger@9123: final XYLineAndShapeRenderer renderer = createRenderer(plot, idx); gernotbelger@9123: gernotbelger@9123: for (int s = 0, num = dataset.getSeriesCount(); s < num; s++) { gernotbelger@9123: final Series series = getSeriesOf(dataset, s); gernotbelger@9123: gernotbelger@9123: if (series instanceof StyledSeries) { gernotbelger@9123: final Style style = ((StyledSeries) series).getStyle(); gernotbelger@9123: style.applyTheme(renderer, s); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: // special case: if there is just one single item, we need to enable gernotbelger@9123: // points for this series, otherwise we would not see anything in gernotbelger@9123: // the chart area. gernotbelger@9123: if (series.getItemCount() == 1) { gernotbelger@9123: renderer.setSeriesShapesVisible(s, true); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: LegendItem legendItem = renderer.getLegendItem(idx, s); gernotbelger@9123: if (legendItem.getLabel().endsWith(" ") || legendItem.getLabel().endsWith("interpol")) { gernotbelger@9123: legendItem = null; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: if (legendItem != null) { gernotbelger@9123: legendItem.setLabelFont(legendFont); gernotbelger@9123: lic.add(legendItem); gernotbelger@9123: } else { gernotbelger@9123: log.warn("Could not get LegentItem for renderer: " + idx + ", series-idx " + s); gernotbelger@9123: } gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: if (anno != null) { gernotbelger@9123: lic.addAll(anno); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: plot.setFixedLegendItems(lic); gernotbelger@9123: gernotbelger@9123: plot.setRenderer(idx, renderer); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * @param plot gernotbelger@9123: * The plot. gernotbelger@9123: * @param area gernotbelger@9123: * A StyledAreaSeriesCollection object. gernotbelger@9123: * @param idx gernotbelger@9123: * The index of the dataset. gernotbelger@9123: */ gernotbelger@9123: private final void applyAreaTheme(final XYPlot plot, final StyledAreaSeriesCollection area, final int idx) { gernotbelger@9123: final LegendItemCollection lic = new LegendItemCollection(); gernotbelger@9123: final LegendItemCollection anno = plot.getFixedLegendItems(); gernotbelger@9123: gernotbelger@9123: final Font legendFont = createLegendLabelFont(); gernotbelger@9123: gernotbelger@9123: log.debug("Registering an 'area'renderer at idx: " + idx); gernotbelger@9123: gernotbelger@9123: final StableXYDifferenceRenderer dRenderer = new StableXYDifferenceRenderer(); gernotbelger@9123: gernotbelger@9123: if (area.getMode() == StyledAreaSeriesCollection.FILL_MODE.UNDER) { gernotbelger@9123: dRenderer.setPositivePaint(createTransparentPaint()); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: plot.setRenderer(idx, dRenderer); gernotbelger@9123: gernotbelger@9123: area.applyTheme(dRenderer); gernotbelger@9123: gernotbelger@9123: // i18n gernotbelger@9123: dRenderer.setAreaLabelNumberFormat(Formatter.getFormatter(this.context.getMeta(), 2, 4)); gernotbelger@9123: gernotbelger@9123: dRenderer.setAreaLabelTemplate(Resources.getMsg(this.context.getMeta(), "area.label.template", "Area=%sm2")); gernotbelger@9123: gernotbelger@9123: final LegendItem legendItem = dRenderer.getLegendItem(idx, 0); gernotbelger@9123: if (legendItem != null) { gernotbelger@9123: legendItem.setLabelFont(legendFont); gernotbelger@9123: lic.add(legendItem); gernotbelger@9123: } else { gernotbelger@9123: log.warn("Could not get LegentItem for renderer: " + idx + ", series-idx " + 0); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: if (anno != null) { gernotbelger@9123: lic.addAll(anno); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: plot.setFixedLegendItems(lic); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Returns a transparently textured paint. gernotbelger@9123: * gernotbelger@9123: * @return a transparently textured paint. gernotbelger@9123: */ gernotbelger@9123: private static Paint createTransparentPaint() { gernotbelger@9123: // TODO why not use a transparent color? gernotbelger@9123: final BufferedImage texture = new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR); gernotbelger@9123: gernotbelger@9123: return new TexturePaint(texture, new Rectangle2D.Double(0d, 0d, 0d, 0d)); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: private void preparePDFContext(final CallContext context) { gernotbelger@9123: final int[] dimension = getExportDimension(); gernotbelger@9123: gernotbelger@9123: context.putContextValue("chart.width", dimension[0]); gernotbelger@9123: context.putContextValue("chart.height", dimension[1]); gernotbelger@9123: context.putContextValue("chart.marginLeft", 5f); gernotbelger@9123: context.putContextValue("chart.marginRight", 5f); gernotbelger@9123: context.putContextValue("chart.marginTop", 5f); gernotbelger@9123: context.putContextValue("chart.marginBottom", 5f); gernotbelger@9123: context.putContextValue("chart.page.format", ChartExportHelper.DEFAULT_PAGE_SIZE); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: private void prepareSVGContext(final CallContext context) { gernotbelger@9123: final int[] dimension = getExportDimension(); gernotbelger@9123: gernotbelger@9123: context.putContextValue("chart.width", dimension[0]); gernotbelger@9123: context.putContextValue("chart.height", dimension[1]); gernotbelger@9123: context.putContextValue("chart.encoding", ChartExportHelper.DEFAULT_ENCODING); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * This method retrieves the chart subtitle by calling getChartSubtitle() gernotbelger@9123: * and adds it as TextTitle to the chart. gernotbelger@9123: * The default implementation of getChartSubtitle() returns the same gernotbelger@9123: * as getDefaultChartSubtitle() which must be implemented by derived gernotbelger@9123: * classes. If you want to add multiple subtitles to the chart override gernotbelger@9123: * this method and add your subtitles manually. gernotbelger@9123: * gernotbelger@9123: * @param chart gernotbelger@9123: * The JFreeChart chart object. gernotbelger@9123: */ gernotbelger@9123: protected void addSubtitles(final CallContext context, final JFreeChart chart) { gernotbelger@9123: final String subtitle = getChartSubtitle(this.context); gernotbelger@9123: gernotbelger@9123: if (subtitle != null && subtitle.length() > 0) { gernotbelger@9123: chart.addSubtitle(new TextTitle(subtitle)); gernotbelger@9123: } gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: protected abstract String getChartSubtitle(CallContext context); gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Adds a new AxisDataset which contains dataset at index idx. gernotbelger@9123: * gernotbelger@9123: * @param dataset gernotbelger@9123: * An XYDataset. gernotbelger@9123: * @param idx gernotbelger@9123: * The axis index. gernotbelger@9123: * @param visible gernotbelger@9123: * Determines, if the dataset should be visible or not. gernotbelger@9123: */ gernotbelger@9123: protected final void addAxisDataset(final XYDataset dataset, final int idx, final boolean visible) { gernotbelger@9123: if (dataset == null || idx < 0) { gernotbelger@9123: return; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: final AxisDataset axisDataset = getAxisDataset(idx); gernotbelger@9123: gernotbelger@9123: final Bounds[] xyBounds = ChartHelper.getBounds(dataset); gernotbelger@9123: gernotbelger@9123: if (xyBounds == null) { gernotbelger@9123: log.warn("Skip XYDataset for Axis (invalid ranges): " + idx); gernotbelger@9123: return; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: if (visible) { gernotbelger@9123: if (log.isDebugEnabled()) { gernotbelger@9123: log.debug("Add new AxisDataset at index: " + idx); gernotbelger@9123: log.debug("X extent: " + xyBounds[0]); gernotbelger@9123: log.debug("Y extent: " + xyBounds[1]); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: axisDataset.addDataset(dataset); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: combineXBounds(xyBounds[0], 0); gernotbelger@9123: combineYBounds(xyBounds[1], idx); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * This method grants access to the AxisDatasets stored in datasets. gernotbelger@9123: * If no AxisDataset exists for index idx, a new AxisDataset is gernotbelger@9123: * created using createAxisDataset(). gernotbelger@9123: * gernotbelger@9123: * @param idx gernotbelger@9123: * The index of the desired AxisDataset. gernotbelger@9123: * gernotbelger@9123: * @return an existing or new AxisDataset. gernotbelger@9123: */ gernotbelger@9123: protected final AxisDataset getAxisDataset(final int idx) { gernotbelger@9123: AxisDataset axisDataset = this.datasets.get(idx); gernotbelger@9123: gernotbelger@9123: if (axisDataset == null) { gernotbelger@9123: axisDataset = createAxisDataset(idx); gernotbelger@9123: this.datasets.put(idx, axisDataset); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: return axisDataset; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Returns the size of a chart export as array which has been specified by gernotbelger@9123: * the incoming request document. gernotbelger@9123: * gernotbelger@9123: * @return the size of a chart as [width, height] or null if no width or gernotbelger@9123: * height are given in the request document. gernotbelger@9123: */ gernotbelger@9123: protected final int[] getSize() { gernotbelger@9123: final int[] size = new int[2]; gernotbelger@9123: gernotbelger@9123: final Element sizeEl = (Element) XMLUtils.xpath(this.request, XPATH_CHART_SIZE, XPathConstants.NODE, ArtifactNamespaceContext.INSTANCE); gernotbelger@9123: gernotbelger@9123: if (sizeEl != null) { gernotbelger@9123: final String uri = ArtifactNamespaceContext.NAMESPACE_URI; gernotbelger@9123: gernotbelger@9123: final String w = sizeEl.getAttributeNS(uri, "width"); gernotbelger@9123: final String h = sizeEl.getAttributeNS(uri, "height"); gernotbelger@9123: gernotbelger@9123: if (w.length() > 0 && h.length() > 0) { gernotbelger@9123: try { gernotbelger@9123: size[0] = Integer.parseInt(w); gernotbelger@9123: size[1] = Integer.parseInt(h); gernotbelger@9123: } gernotbelger@9123: catch (final NumberFormatException nfe) { gernotbelger@9123: log.warn("Wrong values for chart width/height."); gernotbelger@9123: } gernotbelger@9123: } gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: return size[0] > 0 && size[1] > 0 ? size : null; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * This method returns the format specified in the request document gernotbelger@9123: * or DEFAULT_CHART_FORMAT if no format is specified in gernotbelger@9123: * request. gernotbelger@9123: * gernotbelger@9123: * @return the format used to export this chart. gernotbelger@9123: */ gernotbelger@9123: private String getFormat() { gernotbelger@9123: final String format = (String) XMLUtils.xpath(this.request, XPATH_CHART_FORMAT, XPathConstants.STRING, ArtifactNamespaceContext.INSTANCE); gernotbelger@9123: gernotbelger@9123: return format == null || format.length() == 0 ? DEFAULT_CHART_FORMAT : format; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Returns the X-Axis range as String array from request document. gernotbelger@9123: * If the (x|y)range elements are not found in request document, return gernotbelger@9123: * null (i.e. not zoomed). gernotbelger@9123: * gernotbelger@9123: * @return a String array with [lower, upper], null if not in document. gernotbelger@9123: */ gernotbelger@9123: protected final String[] getDomainAxisRangeFromRequest() { gernotbelger@9123: final Element xrange = (Element) XMLUtils.xpath(this.request, XPATH_CHART_X_RANGE, XPathConstants.NODE, ArtifactNamespaceContext.INSTANCE); gernotbelger@9123: gernotbelger@9123: if (xrange == null) { gernotbelger@9123: return null; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: final String uri = ArtifactNamespaceContext.NAMESPACE_URI; gernotbelger@9123: gernotbelger@9123: final String lower = xrange.getAttributeNS(uri, "from"); gernotbelger@9123: final String upper = xrange.getAttributeNS(uri, "to"); gernotbelger@9123: gernotbelger@9123: return new String[] { lower, upper }; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Returns null if the (x|y)range-element was not found in gernotbelger@9123: * request document. gernotbelger@9123: * This usally means that the axis are not manually zoomed, i.e. showing gernotbelger@9123: * full data extent. gernotbelger@9123: */ gernotbelger@9123: protected final String[] getValueAxisRangeFromRequest() { gernotbelger@9123: final Element yrange = (Element) XMLUtils.xpath(this.request, XPATH_CHART_Y_RANGE, XPathConstants.NODE, ArtifactNamespaceContext.INSTANCE); gernotbelger@9123: gernotbelger@9123: if (yrange == null) { gernotbelger@9123: return null; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: final String uri = ArtifactNamespaceContext.NAMESPACE_URI; gernotbelger@9123: gernotbelger@9123: final String lower = yrange.getAttributeNS(uri, "from"); gernotbelger@9123: final String upper = yrange.getAttributeNS(uri, "to"); gernotbelger@9123: gernotbelger@9123: return new String[] { lower, upper }; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Returns the default size of a chart export as array. gernotbelger@9123: * gernotbelger@9123: * @return the default size of a chart as [width, height]. gernotbelger@9123: */ gernotbelger@9123: protected final int[] getDefaultSize() { gernotbelger@9123: return new int[] { DEFAULT_CHART_WIDTH, DEFAULT_CHART_HEIGHT }; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * This method returns the export dimension specified in ChartSettings as gernotbelger@9123: * int array [width,height]. gernotbelger@9123: * gernotbelger@9123: * @return an int array with [width,height]. gernotbelger@9123: */ gernotbelger@9123: private int[] getExportDimension() { gernotbelger@9123: final ChartSettings chartSettings = getChartSettings(); gernotbelger@9123: if (chartSettings == null) gernotbelger@9123: return new int[] { DEFAULT_CHART_WIDTH, DEFAULT_CHART_HEIGHT }; gernotbelger@9123: gernotbelger@9123: final ExportSection export = chartSettings.getExportSection(); gernotbelger@9123: final Integer width = export.getWidth(); gernotbelger@9123: final Integer height = export.getHeight(); gernotbelger@9123: gernotbelger@9123: if (width != null && height != null) { gernotbelger@9123: return new int[] { width, height }; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: return new int[] { 600, 400 }; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Returns the chart title provided by settings. gernotbelger@9123: * gernotbelger@9123: * @param settings gernotbelger@9123: * A ChartSettings object. gernotbelger@9123: * gernotbelger@9123: * @return the title provided by settings or null if no gernotbelger@9123: * ChartSection is provided by settings. gernotbelger@9123: * gernotbelger@9123: * @throws NullPointerException gernotbelger@9123: * if settings is null. gernotbelger@9123: */ gernotbelger@9123: private String getChartTitle(final ChartSettings settings) { gernotbelger@9123: final ChartSection cs = settings.getChartSection(); gernotbelger@9123: return cs != null ? cs.getTitle() : null; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Returns the chart subtitle provided by settings. gernotbelger@9123: * gernotbelger@9123: * @param settings gernotbelger@9123: * A ChartSettings object. gernotbelger@9123: * gernotbelger@9123: * @return the subtitle provided by settings or null if no gernotbelger@9123: * ChartSection is provided by settings. gernotbelger@9123: * gernotbelger@9123: * @throws NullPointerException gernotbelger@9123: * if settings is null. gernotbelger@9123: */ gernotbelger@9123: protected final String getChartSubtitle(final ChartSettings settings) { gernotbelger@9123: final ChartSection cs = settings.getChartSection(); gernotbelger@9123: return cs != null ? cs.getSubtitle() : null; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Returns the title of a chart. The return value depends on the existence gernotbelger@9123: * of ChartSettings: if there are ChartSettings set, this method returns the gernotbelger@9123: * chart title provided by those settings. Otherwise, this method returns gernotbelger@9123: * getDefaultChartTitle(). gernotbelger@9123: * gernotbelger@9123: * @return the title of a chart. gernotbelger@9123: */ gernotbelger@9123: protected String getChartTitle(final CallContext context) { gernotbelger@9123: final ChartSettings chartSettings = getChartSettings(); gernotbelger@9123: gernotbelger@9123: if (chartSettings != null) { gernotbelger@9123: return getChartTitle(chartSettings); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: return getDefaultChartTitle(context); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * This method always returns null. Override it in subclasses that require gernotbelger@9123: * subtitles. gernotbelger@9123: * gernotbelger@9123: * @return null. gernotbelger@9123: */ gernotbelger@9123: protected String getDefaultChartSubtitle(final CallContext context) { gernotbelger@9123: // Override this method in subclasses gernotbelger@9123: return null; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** Where to place the logo. */ gernotbelger@9123: protected final String logoHPlace() { gernotbelger@9123: final ChartSettings chartSettings = getChartSettings(); gernotbelger@9123: if (chartSettings != null) { gernotbelger@9123: final ChartSection cs = chartSettings.getChartSection(); gernotbelger@9123: final String place = cs.getLogoHPlacement(); gernotbelger@9123: gernotbelger@9123: return place; gernotbelger@9123: } gernotbelger@9123: return "center"; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** Where to place the logo. */ gernotbelger@9123: protected final String logoVPlace() { gernotbelger@9123: final ChartSettings chartSettings = getChartSettings(); gernotbelger@9123: if (chartSettings != null) { gernotbelger@9123: final ChartSection cs = chartSettings.getChartSection(); gernotbelger@9123: final String place = cs.getLogoVPlacement(); gernotbelger@9123: gernotbelger@9123: return place; gernotbelger@9123: } gernotbelger@9123: return "top"; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** Return the logo id from settings. */ gernotbelger@9123: private String showLogo(final ChartSettings chartSettings) { gernotbelger@9123: if (chartSettings != null) { gernotbelger@9123: final ChartSection cs = chartSettings.getChartSection(); gernotbelger@9123: final String logo = cs.getDisplayLogo(); gernotbelger@9123: gernotbelger@9123: return logo; gernotbelger@9123: } gernotbelger@9123: return "none"; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * This method is used to determine if a logo should be added to the plot. gernotbelger@9123: * gernotbelger@9123: * @return logo name (null if none). gernotbelger@9123: */ gernotbelger@9123: protected final String showLogo() { gernotbelger@9123: final ChartSettings chartSettings = getChartSettings(); gernotbelger@9123: return showLogo(chartSettings); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * This method is used to determine if the resulting chart should display gernotbelger@9123: * grid lines or not. Note: this method always returns true! gernotbelger@9123: * gernotbelger@9123: * @return true, if the chart should display grid lines, otherwise false. gernotbelger@9123: */ gernotbelger@9123: protected final boolean isGridVisible() { gernotbelger@9123: return true; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: protected final void addAnnotationsToRenderer(final XYPlot plot) { gernotbelger@9123: gernotbelger@9123: final AnnotationRenderer annotationRenderer = new AnnotationRenderer(getChartSettings(), this.datasets, DEFAULT_FONT_NAME); gernotbelger@9123: annotationRenderer.addAnnotationsToRenderer(plot, this.annotations); gernotbelger@9123: gernotbelger@9123: doAddFurtherAnnotations(plot, this.annotations); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Allow further annotation processing, override to implement. gernotbelger@9123: * gernotbelger@9123: * Does nothing by default. gernotbelger@9123: */ gernotbelger@9123: protected void doAddFurtherAnnotations(final XYPlot plot, final List annotations) { gernotbelger@9123: gernotbelger@9123: } gernotbelger@9104: }