teichmann@5863: /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
teichmann@5863: * Software engineering by Intevation GmbH
teichmann@5863: *
teichmann@5994: * This file is Free Software under the GNU AGPL (>=v3)
teichmann@5863: * and comes with ABSOLUTELY NO WARRANTY! Check out the
teichmann@5994: * documentation coming with Dive4Elements River for details.
teichmann@5863: */
teichmann@5863:
teichmann@5831: package org.dive4elements.river.exports;
ingo@348:
teichmann@5831: import org.dive4elements.artifactdatabase.state.ArtifactAndFacet;
teichmann@5831: import org.dive4elements.artifactdatabase.state.Settings;
teichmann@5831: import org.dive4elements.artifacts.Artifact;
teichmann@5831: import org.dive4elements.artifacts.ArtifactNamespaceContext;
teichmann@5831: import org.dive4elements.artifacts.CallContext;
teichmann@5831: import org.dive4elements.artifacts.CallMeta;
teichmann@5831: import org.dive4elements.artifacts.PreferredLocale;
teichmann@5831: import org.dive4elements.artifacts.common.utils.XMLUtils;
tom@8248:
tom@8248: import org.dive4elements.river.artifacts.access.RiverAccess;
teichmann@5831: import org.dive4elements.river.artifacts.access.RangeAccess;
teichmann@5867: import org.dive4elements.river.artifacts.D4EArtifact;
teichmann@5831: import org.dive4elements.river.artifacts.resources.Resources;
teichmann@5867: import org.dive4elements.river.collections.D4EArtifactCollection;
teichmann@5831: import org.dive4elements.river.jfree.Bounds;
teichmann@5831: import org.dive4elements.river.jfree.DoubleBounds;
teichmann@5831: import org.dive4elements.river.jfree.EnhancedLineAndShapeRenderer;
teichmann@5864: import org.dive4elements.river.jfree.RiverAnnotation;
teichmann@5831: import org.dive4elements.river.jfree.StableXYDifferenceRenderer;
teichmann@5831: import org.dive4elements.river.jfree.Style;
teichmann@5831: import org.dive4elements.river.jfree.StyledAreaSeriesCollection;
teichmann@5831: import org.dive4elements.river.jfree.StyledSeries;
aheinecke@7034: import org.dive4elements.river.jfree.AxisDataset;
teichmann@6905: import org.dive4elements.river.themes.ThemeDocument;
christian@3242:
sascha@3257: import java.awt.BasicStroke;
sascha@3257: import java.awt.Color;
sascha@3257: import java.awt.Font;
sascha@3257: import java.awt.Paint;
sascha@3257: import java.awt.Stroke;
sascha@3257: import java.awt.TexturePaint;
sascha@3257: import java.awt.geom.Rectangle2D;
sascha@3257: import java.awt.image.BufferedImage;
sascha@3257: import java.io.IOException;
sascha@3257: import java.io.OutputStream;
sascha@3257: import java.util.ArrayList;
sascha@3257: import java.util.List;
sascha@3257: import java.util.Locale;
sascha@3257: import java.util.Map;
sascha@3257: import java.util.SortedMap;
sascha@3257: import java.util.TreeMap;
sascha@3257:
sascha@3257: import javax.xml.xpath.XPathConstants;
sascha@3257:
sascha@3257: import org.apache.log4j.Logger;
sascha@3257: import org.jfree.chart.JFreeChart;
sascha@3257: import org.jfree.chart.LegendItem;
sascha@3257: import org.jfree.chart.LegendItemCollection;
sascha@3257: import org.jfree.chart.axis.NumberAxis;
sascha@3257: import org.jfree.chart.plot.XYPlot;
sascha@3257: import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
christian@3409: import org.jfree.chart.title.TextTitle;
sascha@3257: import org.jfree.data.Range;
sascha@3257: import org.jfree.data.general.Series;
sascha@3257: import org.jfree.data.xy.XYDataset;
sascha@3257: import org.jfree.ui.RectangleInsets;
sascha@3257: import org.w3c.dom.Document;
sascha@3257: import org.w3c.dom.Element;
ingo@348:
teichmann@5831: import org.dive4elements.river.utils.Formatter;
ingo@348:
ingo@348: /**
ingo@348: * The base class for chart creation. It should provide some basic things that
ingo@348: * equal in all chart types.
ingo@348: *
teichmann@5864: * Annotations are added as RiverAnnotations and come in mutliple basic forms:
felix@4276: * TextAnnotations are labels somewhere in data space, StickyAnnotations are
felix@4276: * labels of a slice or line in one data dimension (i.e. visualized as label
felix@4276: * on a single axis).
felix@4276: *
ingo@348: * @author Ingo Weinzierl
ingo@348: */
ingo@348: public abstract class ChartGenerator implements OutGenerator {
ingo@348:
teichmann@8202: private static Logger log = Logger.getLogger(ChartGenerator.class);
ingo@348:
ingo@2234: public static final int DEFAULT_CHART_WIDTH = 600;
ingo@2234: public static final int DEFAULT_CHART_HEIGHT = 400;
ingo@2234: public static final String DEFAULT_CHART_FORMAT = "png";
ingo@2234: public static final Color DEFAULT_GRID_COLOR = Color.GRAY;
ingo@2234: public static final float DEFAULT_GRID_LINE_WIDTH = 0.3f;
ingo@2234: public static final int DEFAULT_FONT_SIZE = 12;
ingo@2234: public static final String DEFAULT_FONT_NAME = "Tahoma";
ingo@423:
christian@3242: protected static float ANNOTATIONS_AXIS_OFFSET = 0.02f;
ingo@423:
ingo@423: public static final String XPATH_CHART_SIZE =
ingo@423: "/art:action/art:attributes/art:size";
ingo@423:
ingo@1735: public static final String XPATH_CHART_FORMAT =
ingo@1735: "/art:action/art:attributes/art:format/@art:value";
ingo@1735:
ingo@652: public static final String XPATH_CHART_X_RANGE =
ingo@652: "/art:action/art:attributes/art:xrange";
ingo@652:
ingo@652: public static final String XPATH_CHART_Y_RANGE =
ingo@652: "/art:action/art:attributes/art:yrange";
ingo@652:
ingo@423:
ingo@348: /** The document of the incoming out() request.*/
ingo@348: protected Document request;
ingo@348:
ingo@348: /** The output stream where the data should be written to.*/
ingo@348: protected OutputStream out;
ingo@348:
ingo@348: /** The CallContext object.*/
ingo@348: protected CallContext context;
ingo@348:
teichmann@5867: protected D4EArtifactCollection collection;
ingo@3422:
ingo@412: /** The artifact that is used to decorate the chart with meta information.*/
ingo@412: protected Artifact master;
ingo@412:
ingo@2047: /** The settings that should be used during output creation.*/
ingo@2047: protected Settings settings;
ingo@2047:
ingo@2238: /** Map of datasets ("index"). */
ingo@2238: protected SortedMap datasets;
ingo@2238:
christian@3212: /** List of annotations to insert in plot. */
teichmann@5864: protected List annotations = new ArrayList();
ingo@348:
teichmann@7077: protected String outName;
teichmann@7077:
ingo@2233: /**
ingo@2233: * A mini interface that allows to walk over the YAXIS enums defined in
ingo@2233: * subclasses.
ingo@2233: */
ingo@2233: public interface YAxisWalker {
ingo@2238:
ingo@2233: int length();
ingo@2238:
ingo@2233: String getId(int idx);
ingo@2238: } // end of YAxisWalker interface
ingo@2238:
ingo@2238:
ingo@2238: /**
ingo@2238: * Default constructor that initializes internal data structures.
ingo@2238: */
ingo@2238: public ChartGenerator() {
ingo@2238: datasets = new TreeMap();
ingo@2233: }
ingo@2233:
teichmann@7037: @Override
teichmann@7087: public void setup(Object config) {
teichmann@8202: log.debug("ChartGenerator.setup");
teichmann@7037: }
ingo@2233:
christian@3212: /**
christian@3212: * Adds annotations to list. The given annotation will be visible.
christian@3212: */
teichmann@5864: public void addAnnotations(RiverAnnotation annotation) {
christian@3212: annotations.add(annotation);
christian@3212: }
christian@3245:
ingo@2238:
ingo@2234: /**
ingo@2234: * This method needs to be implemented by concrete subclasses to create new
ingo@2234: * instances of JFreeChart.
ingo@2234: *
ingo@2234: * @return a new instance of a JFreeChart.
ingo@2234: */
ingo@2234: public abstract JFreeChart generateChart();
ingo@2234:
ingo@2234:
felix@4589: /** For every outable (i.e. facets), this function is
felix@4589: * called and handles the data accordingly. */
christian@3278: @Override
ingo@2234: public abstract void doOut(
ingo@2234: ArtifactAndFacet bundle,
teichmann@6905: ThemeDocument attr,
ingo@2234: boolean visible);
ingo@2234:
ingo@2234:
ingo@2233: protected abstract YAxisWalker getYAxisWalker();
ingo@2233:
ingo@2234:
ingo@2242: protected abstract Series getSeriesOf(XYDataset dataset, int idx);
ingo@2242:
ingo@2234: /**
ingo@2234: * Returns the default title of a chart.
ingo@2234: *
ingo@2234: * @return the default title of a chart.
ingo@2234: */
ingo@2233: protected abstract String getDefaultChartTitle();
ingo@2233:
ingo@2234:
ingo@2234: /**
ingo@2234: * Returns the default X-Axis label of a chart.
ingo@2234: *
ingo@2234: * @return the default X-Axis label of a chart.
ingo@2234: */
ingo@2233: protected abstract String getDefaultXAxisLabel();
ingo@2233:
ingo@2234:
ingo@2234: /**
ingo@2234: * This method is called to retrieve the default label for an Y axis at
ingo@2234: * position pos.
ingo@2234: *
ingo@2234: * @param pos The position of an Y axis.
ingo@2234: *
ingo@2234: * @return the default Y axis label at position pos.
ingo@2234: */
ingo@2233: protected abstract String getDefaultYAxisLabel(int pos);
ingo@2233:
ingo@2234:
ingo@2234: /**
ingo@2238: * This method is used to create new AxisDataset instances which may differ
ingo@2238: * in concrete subclasses.
ingo@2238: *
ingo@2238: * @param idx The index of an axis.
ingo@2238: */
ingo@2238: protected abstract AxisDataset createAxisDataset(int idx);
ingo@2238:
ingo@2238:
ingo@2238: /**
ingo@2238: * Combines the ranges of the X axis at index idx.
ingo@2238: *
felix@3270: * @param bounds A new Bounds.
ingo@2238: * @param idx The index of the X axis that should be comined with
ingo@2238: * range.
ingo@2238: */
ingo@2587: protected abstract void combineXBounds(Bounds bounds, int idx);
ingo@2587:
ingo@2587:
ingo@2587: /**
ingo@2587: * Combines the ranges of the Y axis at index idx.
ingo@2587: *
felix@3270: * @param bounds A new Bounds.
felix@3284: * @param index The index of the Y axis that should be comined with.
ingo@2587: * range.
ingo@2587: */
ingo@2587: protected abstract void combineYBounds(Bounds bounds, int index);
ingo@2238:
ingo@2238:
ingo@2238: /**
ingo@2261: * This method is used to determine the ranges for axes at a given index.
ingo@2261: *
ingo@2261: * @param index The index of the axes at the plot.
ingo@2261: *
ingo@2261: * @return a Range[] with [xrange, yrange];
ingo@2261: */
ingo@2261: public abstract Range[] getRangesForAxis(int index);
ingo@2261:
ingo@2398: public abstract Bounds getXBounds(int axis);
ingo@2398:
ingo@2398: protected abstract void setXBounds(int axis, Bounds bounds);
ingo@2398:
ingo@2398: public abstract Bounds getYBounds(int axis);
ingo@2398:
ingo@2398: protected abstract void setYBounds(int axis, Bounds bounds);
ingo@2398:
ingo@2261:
ingo@2261: /**
christian@3409: * This method retrieves the chart subtitle by calling getChartSubtitle()
christian@3409: * and adds it as TextTitle to the chart.
christian@3409: * The default implementation of getChartSubtitle() returns the same
christian@3409: * as getDefaultChartSubtitle() which must be implemented by derived
christian@3409: * classes. If you want to add multiple subtitles to the chart override
christian@3409: * this method and add your subtitles manually.
ingo@2234: *
ingo@2234: * @param chart The JFreeChart chart object.
ingo@2234: */
ingo@2234: protected void addSubtitles(JFreeChart chart) {
christian@3409: String subtitle = getChartSubtitle();
christian@3409:
christian@3409: if (subtitle != null && subtitle.length() > 0) {
christian@3409: chart.addSubtitle(new TextTitle(subtitle));
christian@3409: }
ingo@2234: }
ingo@2233:
ingo@2233:
ingo@2234: /**
christian@3242: * Register annotations like MainValues for later plotting
christian@3242: *
christian@3242: * @param annotations list of annotations (data of facet).
christian@3242: * @param aandf Artifact and the facet.
christian@3242: * @param theme Theme document for given annotations.
christian@3242: * @param visible The visibility of the annotations.
christian@3242: */
felix@6900: public void doAnnotations(
teichmann@5864: RiverAnnotation annotations,
christian@3242: ArtifactAndFacet aandf,
teichmann@6905: ThemeDocument theme,
christian@3242: boolean visible
christian@3242: ){
teichmann@8202: log.debug("doAnnotations");
christian@3242:
christian@3242: // Add all annotations to our annotation pool.
christian@3242: annotations.setTheme(theme);
christian@3242: if (aandf != null) {
christian@3242: annotations.setLabel(aandf.getFacetDescription());
christian@3242: }
christian@3242: else {
teichmann@8202: log.error(
christian@3242: "Art/Facet for Annotations is null. " +
christian@3242: "This should never happen!");
christian@3242: }
christian@3242:
christian@3242: if (visible) {
christian@3242: addAnnotations(annotations);
christian@3242: }
christian@3242: }
christian@3245:
christian@3242:
christian@3242: /**
ingo@2234: * Generate chart.
ingo@2234: */
ingo@2234: @Override
ingo@2234: public void generate()
ingo@2234: throws IOException
ingo@2234: {
teichmann@8202: log.debug("ChartGenerator.generate");
ingo@2234:
ingo@2234: JFreeChart chart = generateChart();
ingo@2234:
ingo@2234: String format = getFormat();
ingo@2234: int[] size = getSize();
ingo@2234:
ingo@2234: if (size == null) {
ingo@2234: size = getExportDimension();
ingo@2234: }
ingo@2234:
ingo@2234: context.putContextValue("chart.width", size[0]);
ingo@2234: context.putContextValue("chart.height", size[1]);
ingo@2234:
ingo@2234: if (format.equals(ChartExportHelper.FORMAT_PNG)) {
ingo@2234: context.putContextValue("chart.image.format", "png");
ingo@2234:
ingo@2234: ChartExportHelper.exportImage(
ingo@2234: out,
ingo@2234: chart,
ingo@2234: context);
ingo@2234: }
ingo@2234: else if (format.equals(ChartExportHelper.FORMAT_PDF)) {
ingo@2234: preparePDFContext(context);
ingo@2234:
ingo@2234: ChartExportHelper.exportPDF(
ingo@2234: out,
ingo@2234: chart,
ingo@2234: context);
ingo@2234: }
ingo@2234: else if (format.equals(ChartExportHelper.FORMAT_SVG)) {
ingo@2234: prepareSVGContext(context);
ingo@2234:
ingo@2234: ChartExportHelper.exportSVG(
ingo@2234: out,
ingo@2234: chart,
ingo@2234: context);
ingo@2234: }
ingo@2234: else if (format.equals(ChartExportHelper.FORMAT_CSV)) {
ingo@2234: context.putContextValue("chart.image.format", "csv");
ingo@2234:
ingo@2234: ChartExportHelper.exportCSV(
ingo@2234: out,
ingo@2234: chart,
ingo@2234: context);
ingo@2234: }
ingo@2234: }
ingo@2234:
ingo@2234:
ingo@2234: @Override
teichmann@7077: public void init(String outName, Document request, OutputStream out, CallContext context) {
teichmann@8202: log.debug("ChartGenerator.init");
ingo@348:
teichmann@7077: this.outName = outName;
ingo@348: this.request = request;
ingo@348: this.out = out;
ingo@348: this.context = context;
ingo@348: }
ingo@348:
ingo@348:
felix@4589: /** Sets the master artifact. */
ingo@2234: @Override
ingo@412: public void setMasterArtifact(Artifact master) {
ingo@412: this.master = master;
ingo@412: }
ingo@412:
ingo@412:
felix@6422: /**
felix@6422: * Gets the master artifact.
felix@6422: * @return the master artifact.
felix@6422: */
felix@6422: public Artifact getMaster() {
felix@6422: return master;
felix@6422: }
felix@6422:
felix@6422:
felix@4589: /** Sets the collection. */
ingo@2047: @Override
teichmann@5867: public void setCollection(D4EArtifactCollection collection) {
ingo@3422: this.collection = collection;
ingo@3422: }
ingo@3422:
ingo@3422:
ingo@3422: @Override
ingo@2047: public void setSettings(Settings settings) {
ingo@2047: this.settings = settings;
ingo@2047: }
ingo@2047:
ingo@2047:
ingo@2047: /**
ingo@2236: * Returns an instance of ChartSettings with a chart specific section
ingo@2236: * but with no axes settings.
ingo@2236: *
ingo@2236: * @return an instance of ChartSettings.
ingo@2236: */
ingo@2236: @Override
ingo@2236: public Settings getSettings() {
ingo@2236: if (this.settings != null) {
ingo@2236: return this.settings;
ingo@2236: }
ingo@2236:
ingo@2236: ChartSettings settings = new ChartSettings();
ingo@2236:
ingo@2236: ChartSection chartSection = buildChartSection();
ingo@2236: LegendSection legendSection = buildLegendSection();
ingo@2236: ExportSection exportSection = buildExportSection();
ingo@2236:
ingo@2236: settings.setChartSection(chartSection);
ingo@2236: settings.setLegendSection(legendSection);
ingo@2236: settings.setExportSection(exportSection);
ingo@2236:
ingo@2236: List axisSections = buildAxisSections();
ingo@2236: for (AxisSection axisSection: axisSections) {
ingo@2236: settings.addAxisSection(axisSection);
ingo@2236: }
ingo@2236:
ingo@2236: return settings;
ingo@2236: }
ingo@2236:
ingo@2236:
ingo@2236: /**
ingo@2236: * Creates a new ChartSection.
ingo@2236: *
ingo@2236: * @return a new ChartSection.
ingo@2236: */
ingo@2236: protected ChartSection buildChartSection() {
ingo@2236: ChartSection chartSection = new ChartSection();
ingo@2236: chartSection.setTitle(getChartTitle());
ingo@2236: chartSection.setSubtitle(getChartSubtitle());
felix@3613: chartSection.setDisplayGrid(isGridVisible());
felix@3615: chartSection.setDisplayLogo(showLogo());
felix@3618: chartSection.setLogoVPlacement(logoVPlace());
felix@3618: chartSection.setLogoHPlacement(logoHPlace());
ingo@2236: return chartSection;
ingo@2236: }
ingo@2236:
ingo@2236:
ingo@2236: /**
ingo@2236: * Creates a new LegendSection.
ingo@2236: *
ingo@2236: * @return a new LegendSection.
ingo@2236: */
ingo@2236: protected LegendSection buildLegendSection() {
ingo@2236: LegendSection legendSection = new LegendSection();
ingo@2236: legendSection.setVisibility(isLegendVisible());
ingo@2236: legendSection.setFontSize(getLegendFontSize());
felix@3150: legendSection.setAggregationThreshold(10);
ingo@2236: return legendSection;
ingo@2236: }
ingo@2236:
ingo@2236:
ingo@2236: /**
ingo@2236: * Creates a new ExportSection with default values WIDTH=600
ingo@2236: * and HEIGHT=400.
ingo@2236: *
ingo@2236: * @return a new ExportSection.
ingo@2236: */
ingo@2236: protected ExportSection buildExportSection() {
ingo@2236: ExportSection exportSection = new ExportSection();
ingo@2236: exportSection.setWidth(600);
ingo@2236: exportSection.setHeight(400);
ingo@2236: return exportSection;
ingo@2236: }
ingo@2236:
ingo@2236:
ingo@2236: /**
ingo@2236: * Creates a list of Sections that contains all axes of the chart (including
ingo@2236: * X and Y axes).
ingo@2236: *
ingo@2236: * @return a list of Sections for each axis in this chart.
ingo@2236: */
ingo@2236: protected List buildAxisSections() {
ingo@2236: List axisSections = new ArrayList();
ingo@2236:
ingo@2236: axisSections.addAll(buildXAxisSections());
ingo@2236: axisSections.addAll(buildYAxisSections());
ingo@2236:
ingo@2236: return axisSections;
ingo@2236: }
ingo@2236:
ingo@2236:
ingo@2236: /**
ingo@2236: * Creates a new Section for chart's X axis.
ingo@2236: *
ingo@2236: * @return a List that contains a Section for the X axis.
ingo@2236: */
ingo@2236: protected List buildXAxisSections() {
ingo@2236: List axisSections = new ArrayList();
ingo@2236:
ingo@2236: String identifier = "X";
ingo@2236:
ingo@2236: AxisSection axisSection = new AxisSection();
ingo@2236: axisSection.setIdentifier(identifier);
ingo@2236: axisSection.setLabel(getXAxisLabel());
ingo@2236: axisSection.setFontSize(14);
ingo@2236: axisSection.setFixed(false);
ingo@2236:
ingo@2236: // XXX We are able to find better default ranges that [0,0], but the Y
ingo@2236: // axes currently have no better ranges set.
ingo@2236: axisSection.setUpperRange(0d);
ingo@2236: axisSection.setLowerRange(0d);
ingo@2236:
ingo@2236: axisSections.add(axisSection);
ingo@2236:
ingo@2236: return axisSections;
ingo@2236: }
ingo@2236:
ingo@2236:
ingo@2236: /**
ingo@2236: * Creates a list of Section for the chart's Y axes. This method makes use
ingo@2236: * of getYAxisWalker to be able to access all Y axes defined in
ingo@2236: * subclasses.
ingo@2236: *
ingo@2236: * @return a list of Y axis sections.
ingo@2236: */
ingo@2236: protected List buildYAxisSections() {
ingo@2236: List axisSections = new ArrayList();
ingo@2236:
ingo@2236: YAxisWalker walker = getYAxisWalker();
ingo@2236: for (int i = 0, n = walker.length(); i < n; i++) {
ingo@2236: AxisSection ySection = new AxisSection();
ingo@2236: ySection.setIdentifier(walker.getId(i));
ingo@2236: ySection.setLabel(getYAxisLabel(i));
ingo@2236: ySection.setFontSize(14);
ingo@2236: ySection.setFixed(false);
ingo@2236:
ingo@2236: // XXX We are able to find better default ranges that [0,0], the
ingo@2236: // only problem is, that we do NOT have a better range than [0,0]
ingo@2236: // for each axis, because the initial chart will not have a dataset
ingo@2236: // for each axis set!
ingo@2236: ySection.setUpperRange(0d);
ingo@2236: ySection.setLowerRange(0d);
ingo@2236:
ingo@2236: axisSections.add(ySection);
ingo@2236: }
ingo@2236:
ingo@2236: return axisSections;
ingo@2236: }
ingo@2236:
ingo@2236:
ingo@2236: /**
ingo@2236: * Returns the settings as ChartSettings.
ingo@2236: *
ingo@2236: * @return the settings as ChartSettings or null, if
ingo@2236: * settings is not an instance of ChartSettings.
ingo@2236: */
ingo@2236: public ChartSettings getChartSettings() {
ingo@2236: if (settings instanceof ChartSettings) {
ingo@2236: return (ChartSettings) settings;
ingo@2236: }
ingo@2236:
ingo@2236: return null;
ingo@2236: }
ingo@2236:
ingo@2236:
ingo@2236: /**
ingo@2047: * Returns the chart title provided by settings.
ingo@2047: *
ingo@2047: * @param settings A ChartSettings object.
ingo@2047: *
ingo@2047: * @return the title provided by settings or null if no
ingo@2047: * ChartSection is provided by settings.
ingo@2047: *
ingo@2047: * @throws NullPointerException if settings is null.
ingo@2047: */
ingo@2047: public String getChartTitle(ChartSettings settings) {
ingo@2047: ChartSection cs = settings.getChartSection();
ingo@2047: return cs != null ? cs.getTitle() : null;
ingo@2047: }
ingo@2047:
ingo@2047:
ingo@2047: /**
ingo@2047: * Returns the chart subtitle provided by settings.
ingo@2047: *
ingo@2047: * @param settings A ChartSettings object.
ingo@2047: *
ingo@2047: * @return the subtitle provided by settings or null if no
ingo@2047: * ChartSection is provided by settings.
ingo@2047: *
ingo@2047: * @throws NullPointerException if settings is null.
ingo@2047: */
ingo@2047: public String getChartSubtitle(ChartSettings settings) {
ingo@2047: ChartSection cs = settings.getChartSection();
ingo@2047: return cs != null ? cs.getSubtitle() : null;
ingo@2047: }
ingo@2047:
ingo@2047:
ingo@2047: /**
ingo@2047: * Returns a boolean object that determines if the chart grid should be
ingo@2047: * visible or not. This information needs to be provided by settings,
ingo@2047: * otherweise the default is true.
ingo@2047: *
ingo@2047: * @param settings A ChartSettings object.
ingo@2047: *
ingo@2047: * @return true, if the chart grid should be visible otherwise false.
ingo@2047: *
ingo@2047: * @throws NullPointerException if settings is null.
ingo@2047: */
ingo@2047: public boolean isGridVisible(ChartSettings settings) {
ingo@2047: ChartSection cs = settings.getChartSection();
ingo@2047: Boolean displayGrid = cs.getDisplayGrid();
ingo@2047:
ingo@2047: return displayGrid != null ? displayGrid : true;
ingo@2047: }
ingo@2047:
ingo@2047:
ingo@2047: /**
ingo@2047: * Returns a boolean object that determines if the chart legend should be
ingo@2047: * visible or not. This information needs to be provided by settings,
ingo@2047: * otherwise the default is true.
ingo@2047: *
ingo@2047: * @param settings A ChartSettings object.
ingo@2047: *
ingo@2047: * @return true, if the chart legend should be visible otherwise false.
ingo@2047: *
ingo@2047: * @throws NullPointerException if settings is null.
ingo@2047: */
ingo@2047: public boolean isLegendVisible(ChartSettings settings) {
ingo@2047: LegendSection ls = settings.getLegendSection();
ingo@2047: Boolean displayLegend = ls.getVisibility();
ingo@2047:
ingo@2047: return displayLegend != null ? displayLegend : true;
ingo@2047: }
ingo@2047:
ingo@2047:
ingo@2047: /**
ingo@2047: * Returns the legend font size specified in settings or null if no
ingo@2047: * LegendSection is provided by settings.
ingo@2047: *
ingo@2047: * @param settings A ChartSettings object.
ingo@2047: *
ingo@2047: * @return the legend font size or null.
ingo@2047: *
ingo@2047: * @throws NullPointerException if settings is null.
ingo@2047: */
ingo@2047: public Integer getLegendFontSize(ChartSettings settings) {
ingo@2047: LegendSection ls = settings.getLegendSection();
ingo@2047: return ls != null ? ls.getFontSize() : null;
ingo@2047: }
ingo@2047:
ingo@2047:
ingo@2233: /**
ingo@2234: * Returns the title of a chart. The return value depends on the existence
ingo@2234: * of ChartSettings: if there are ChartSettings set, this method returns the
ingo@2234: * chart title provided by those settings. Otherwise, this method returns
ingo@2234: * getDefaultChartTitle().
ingo@2234: *
ingo@2234: * @return the title of a chart.
ingo@2234: */
ingo@2234: protected String getChartTitle() {
ingo@2234: ChartSettings chartSettings = getChartSettings();
ingo@2234:
ingo@2234: if (chartSettings != null) {
ingo@2234: return getChartTitle(chartSettings);
ingo@2234: }
ingo@2234:
ingo@2234: return getDefaultChartTitle();
ingo@2234: }
ingo@2234:
ingo@2234:
ingo@2234: /**
ingo@2234: * Returns the subtitle of a chart. The return value depends on the
ingo@2234: * existence of ChartSettings: if there are ChartSettings set, this method
ingo@2234: * returns the chart title provided by those settings. Otherwise, this
ingo@2234: * method returns getDefaultChartSubtitle().
ingo@2234: *
ingo@2234: * @return the subtitle of a chart.
ingo@2234: */
ingo@2234: protected String getChartSubtitle() {
ingo@2234: ChartSettings chartSettings = getChartSettings();
ingo@2234:
ingo@2234: if (chartSettings != null) {
ingo@2234: return getChartSubtitle(chartSettings);
ingo@2234: }
ingo@2234:
ingo@2234: return getDefaultChartSubtitle();
ingo@2234: }
ingo@2234:
ingo@2234:
ingo@2234: /**
ingo@2234: * This method always returns null. Override it in subclasses that require
ingo@2234: * subtitles.
ingo@2234: *
ingo@2234: * @return null.
ingo@2234: */
ingo@2234: protected String getDefaultChartSubtitle() {
ingo@2234: // Override this method in subclasses
ingo@2234: return null;
ingo@2234: }
ingo@2234:
ingo@2234:
ingo@2234: /**
ingo@2234: * This method is used to determine, if the chart's legend is visible or
ingo@2234: * not. If a settings instance is set, this instance determines the
ingo@2234: * visibility otherwise, this method returns true as default if no
ingo@2234: * settings is set.
ingo@2234: *
ingo@2234: * @return true, if the legend should be visible, otherwise false.
ingo@2234: */
ingo@2234: protected boolean isLegendVisible() {
ingo@2234: ChartSettings chartSettings = getChartSettings();
ingo@2234: if (chartSettings != null) {
ingo@2234: return isLegendVisible(chartSettings);
ingo@2234: }
ingo@2234:
ingo@2234: return true;
ingo@2234: }
ingo@2234:
ingo@2234:
felix@3618: /** Where to place the logo. */
felix@3618: protected String logoHPlace() {
felix@3617: ChartSettings chartSettings = getChartSettings();
felix@3617: if (chartSettings != null) {
felix@3617: ChartSection cs = chartSettings.getChartSection();
felix@3618: String place = cs.getLogoHPlacement();
felix@3618:
felix@3618: return place;
felix@3618: }
felix@3639: return "center";
felix@3618: }
felix@3618:
felix@3618:
felix@3618: /** Where to place the logo. */
felix@3618: protected String logoVPlace() {
felix@3618: ChartSettings chartSettings = getChartSettings();
felix@3618: if (chartSettings != null) {
felix@3618: ChartSection cs = chartSettings.getChartSection();
felix@3618: String place = cs.getLogoVPlacement();
felix@3617:
felix@3617: return place;
felix@3617: }
felix@3639: return "top";
felix@3617: }
felix@3617:
felix@3617:
felix@3615: /** Return the logo id from settings. */
felix@3615: protected String showLogo(ChartSettings chartSettings) {
felix@3615: if (chartSettings != null) {
felix@3615: ChartSection cs = chartSettings.getChartSection();
felix@3615: String logo = cs.getDisplayLogo();
felix@3615:
felix@3615: return logo;
felix@3615: }
felix@3617: return "none";
felix@3615: }
felix@3615:
felix@3615:
felix@3615: /**
felix@3615: * This method is used to determine if a logo should be added to the plot.
felix@3615: *
felix@3615: * @return logo name (null if none).
felix@3615: */
felix@3615: protected String showLogo() {
felix@3615: ChartSettings chartSettings = getChartSettings();
felix@3615: return showLogo(chartSettings);
felix@3615: }
felix@3615:
felix@3615:
ingo@2234: /**
ingo@2234: * This method is used to determine the font size of the chart's legend. If
ingo@2234: * a settings instance is set, this instance determines the font
ingo@2234: * size, otherwise this method returns 12 as default if no settings
ingo@2234: * is set or if it doesn't provide a legend font size.
ingo@2234: *
ingo@2234: * @return a legend font size.
ingo@2234: */
ingo@2234: protected int getLegendFontSize() {
ingo@2234: Integer fontSize = null;
ingo@2234:
ingo@2234: ChartSettings chartSettings = getChartSettings();
ingo@2234: if (chartSettings != null) {
ingo@2234: fontSize = getLegendFontSize(chartSettings);
ingo@2234: }
ingo@2234:
ingo@2234: return fontSize != null ? fontSize : DEFAULT_FONT_SIZE;
ingo@2234: }
ingo@2234:
ingo@2234:
ingo@2234: /**
ingo@2234: * This method is used to determine if the resulting chart should display
ingo@2234: * grid lines or not. Note: this method always returns true!
ingo@2234: *
ingo@2234: * @return true, if the chart should display grid lines, otherwise false.
ingo@2234: */
ingo@2234: protected boolean isGridVisible() {
ingo@2234: return true;
ingo@2234: }
ingo@2234:
ingo@2234:
ingo@2234: /**
ingo@2234: * Returns the X-Axis label of a chart.
ingo@2234: *
ingo@2234: * @return the X-Axis label of a chart.
ingo@2234: */
ingo@2234: protected String getXAxisLabel() {
ingo@2234: ChartSettings chartSettings = getChartSettings();
ingo@2234: if (chartSettings == null) {
ingo@2234: return getDefaultXAxisLabel();
ingo@2234: }
ingo@2234:
ingo@2234: AxisSection as = chartSettings.getAxisSection("X");
ingo@2234: if (as != null) {
ingo@2234: String label = as.getLabel();
ingo@2234:
ingo@2234: if (label != null) {
ingo@2234: return label;
ingo@2234: }
ingo@2234: }
ingo@2234:
ingo@2234: return getDefaultXAxisLabel();
ingo@2234: }
ingo@2234:
ingo@2234:
ingo@2234: /**
ingo@2234: * This method returns the font size for the X axis. If the font size is
ingo@2234: * specified in ChartSettings (if chartSettings is set), this size is
ingo@2234: * returned. Otherwise the default font size 12 is returned.
ingo@2234: *
ingo@2234: * @return the font size for the x axis.
ingo@2234: */
ingo@2234: protected int getXAxisLabelFontSize() {
ingo@2234: ChartSettings chartSettings = getChartSettings();
ingo@2234: if (chartSettings == null) {
ingo@2234: return DEFAULT_FONT_SIZE;
ingo@2234: }
ingo@2234:
ingo@2234: AxisSection as = chartSettings.getAxisSection("X");
ingo@2234: Integer fontSize = as.getFontSize();
ingo@2234:
ingo@2234: return fontSize != null ? fontSize : DEFAULT_FONT_SIZE;
ingo@2234: }
ingo@2234:
ingo@2234:
ingo@2234: /**
ingo@2234: * This method returns the font size for an Y axis. If the font size is
ingo@2234: * specified in ChartSettings (if chartSettings is set), this size is
ingo@2234: * returned. Otherwise the default font size 12 is returned.
ingo@2234: *
ingo@2234: * @return the font size for the x axis.
ingo@2234: */
ingo@2234: protected int getYAxisFontSize(int pos) {
ingo@2234: ChartSettings chartSettings = getChartSettings();
ingo@2234: if (chartSettings == null) {
ingo@2234: return DEFAULT_FONT_SIZE;
ingo@2234: }
ingo@2234:
ingo@2234: YAxisWalker walker = getYAxisWalker();
ingo@2234:
ingo@2234: AxisSection as = chartSettings.getAxisSection(walker.getId(pos));
bjoern@4376: if (as == null) {
bjoern@4376: return DEFAULT_FONT_SIZE;
bjoern@4376: }
ingo@2234: Integer fontSize = as.getFontSize();
ingo@2234:
ingo@2234: return fontSize != null ? fontSize : DEFAULT_FONT_SIZE;
ingo@2234: }
ingo@2234:
ingo@2234:
ingo@2234: /**
ingo@2234: * This method returns the export dimension specified in ChartSettings as
ingo@2234: * int array [width,height].
ingo@2234: *
ingo@2234: * @return an int array with [width,height].
ingo@2234: */
ingo@2234: protected int[] getExportDimension() {
ingo@2234: ChartSettings chartSettings = getChartSettings();
ingo@2234: if (chartSettings == null) {
ingo@2234: return new int[] { 600, 400 };
ingo@2234: }
ingo@2234:
ingo@2234: ExportSection export = chartSettings.getExportSection();
ingo@2234: Integer width = export.getWidth();
ingo@2234: Integer height = export.getHeight();
ingo@2234:
ingo@2234: if (width != null && height != null) {
ingo@2234: return new int[] { width, height };
ingo@2234: }
ingo@2234:
ingo@2234: return new int[] { 600, 400 };
ingo@2234: }
ingo@2234:
ingo@2234:
ingo@2234: /**
ingo@2233: * Returns the Y-Axis label of a chart at position pos.
ingo@2233: *
ingo@2233: * @return the Y-Axis label of a chart at position 0.
ingo@2233: */
ingo@2233: protected String getYAxisLabel(int pos) {
ingo@2233: ChartSettings chartSettings = getChartSettings();
ingo@2233: if (chartSettings == null) {
ingo@2233: return getDefaultYAxisLabel(pos);
ingo@2233: }
ingo@2233:
ingo@2233: YAxisWalker walker = getYAxisWalker();
ingo@2233: AxisSection as = chartSettings.getAxisSection(walker.getId(pos));
ingo@2233: if (as != null) {
ingo@2233: String label = as.getLabel();
ingo@2233:
ingo@2233: if (label != null) {
ingo@2233: return label;
ingo@2233: }
ingo@2233: }
ingo@2233:
ingo@2233: return getDefaultYAxisLabel(pos);
ingo@2233: }
ingo@2233:
ingo@2233:
ingo@2234: /**
ingo@2236: * This method searches for a specific axis in the settings if
ingo@2236: * settings is set. If the axis was found, this method returns the
ingo@2236: * specified axis range if the axis range is fixed. Otherwise, this method
ingo@2236: * returns null.
ingo@2236: *
ingo@2236: * @param axisId The identifier of an axis.
ingo@2236: *
ingo@2236: * @return the specified axis range from settings if the axis is
ingo@2236: * fixed, otherwise null.
ingo@2236: */
ingo@2236: public Range getRangeForAxisFromSettings(String axisId) {
ingo@2236: ChartSettings chartSettings = getChartSettings();
ingo@2236: if (chartSettings == null) {
ingo@2236: return null;
ingo@2236: }
ingo@2236:
ingo@2236: AxisSection as = chartSettings.getAxisSection(axisId);
bjoern@4376:
bjoern@4376: if (as == null) {
bjoern@4376: return null;
bjoern@4376: }
bjoern@4376:
felix@7622: Boolean fixed = as.isFixed();
felix@7622:
andre@8455: if (fixed != null && fixed) {
ingo@2236:
andre@8455: /* Only time series charts have time ranges so prefer those. */
andre@8455: if (axisId.equals("X")) {
andre@8455: Long lowerTime = as.getLowerTimeRange();
andre@8455: Long upperTime = as.getUpperTimeRange();
andre@8455: if ( lowerTime != null && upperTime != null ) {
andre@8455: log.debug("Using time range: " + lowerTime + " - " + upperTime);
andre@8455: return lowerTime < upperTime
andre@8455: ? new Range(lowerTime, upperTime)
andre@8455: : new Range(upperTime, lowerTime);
andre@8455: }
andre@8455: }
andre@8455:
ingo@2236: Double upper = as.getUpperRange();
ingo@2236: Double lower = as.getLowerRange();
ingo@2236:
ingo@2236: if (upper != null && lower != null) {
ingo@2236: return lower < upper
ingo@2236: ? new Range(lower, upper)
ingo@2236: : new Range(upper, lower);
ingo@2236: }
ingo@2236: }
ingo@2236:
ingo@2236: return null;
ingo@2236: }
ingo@2236:
ingo@2236:
ingo@2236: /**
ingo@2238: * Adds a new AxisDataset which contains dataset at index idx.
ingo@2238: *
ingo@2238: * @param dataset An XYDataset.
ingo@2238: * @param idx The axis index.
ingo@2238: * @param visible Determines, if the dataset should be visible or not.
ingo@2238: */
ingo@2238: public void addAxisDataset(XYDataset dataset, int idx, boolean visible) {
ingo@2238: if (dataset == null || idx < 0) {
ingo@2238: return;
ingo@2238: }
ingo@2238:
ingo@2238: AxisDataset axisDataset = getAxisDataset(idx);
ingo@2238:
ingo@2587: Bounds[] xyBounds = ChartHelper.getBounds(dataset);
ingo@2238:
ingo@2587: if (xyBounds == null) {
teichmann@8202: log.warn("Skip XYDataset for Axis (invalid ranges): " + idx);
ingo@2242: return;
ingo@2242: }
ingo@2242:
ingo@2238: if (visible) {
teichmann@8202: if (log.isDebugEnabled()) {
teichmann@8202: log.debug("Add new AxisDataset at index: " + idx);
teichmann@8202: log.debug("X extent: " + xyBounds[0]);
teichmann@8202: log.debug("Y extent: " + xyBounds[1]);
ingo@2242: }
ingo@2242:
ingo@2238: axisDataset.addDataset(dataset);
ingo@2238: }
ingo@2238:
ingo@2587: combineXBounds(xyBounds[0], 0);
ingo@2587: combineYBounds(xyBounds[1], idx);
ingo@2238: }
ingo@2238:
ingo@2238:
ingo@2238: /**
ingo@2238: * This method grants access to the AxisDatasets stored in datasets.
ingo@2238: * If no AxisDataset exists for index idx, a new AxisDataset is
ingo@2238: * created using createAxisDataset().
ingo@2238: *
ingo@2238: * @param idx The index of the desired AxisDataset.
ingo@2238: *
ingo@2238: * @return an existing or new AxisDataset.
ingo@2238: */
ingo@2238: public AxisDataset getAxisDataset(int idx) {
ingo@2238: AxisDataset axisDataset = datasets.get(idx);
ingo@2238:
ingo@2238: if (axisDataset == null) {
ingo@2238: axisDataset = createAxisDataset(idx);
ingo@2238: datasets.put(idx, axisDataset);
ingo@2238: }
ingo@2238:
ingo@2238: return axisDataset;
ingo@2238: }
ingo@2238:
ingo@2238:
ingo@2238: /**
ingo@2422: * Adjust some Stroke/Grid parameters for plot. The chart
ingo@2422: * Settings are applied in this method.
ingo@2422: *
ingo@2422: * @param plot The XYPlot which is adapted.
ingo@2422: */
ingo@2422: protected void adjustPlot(XYPlot plot) {
ingo@2422: Stroke gridStroke = new BasicStroke(
ingo@2422: DEFAULT_GRID_LINE_WIDTH,
ingo@2422: BasicStroke.CAP_BUTT,
ingo@2422: BasicStroke.JOIN_MITER,
ingo@2422: 3.0f,
ingo@2422: new float[] { 3.0f },
ingo@2422: 0.0f);
ingo@2422:
ingo@2422: ChartSettings cs = getChartSettings();
ingo@2422: boolean isGridVisible = cs != null ? isGridVisible(cs) : true;
ingo@2422:
ingo@2422: plot.setDomainGridlineStroke(gridStroke);
ingo@2422: plot.setDomainGridlinePaint(DEFAULT_GRID_COLOR);
ingo@2422: plot.setDomainGridlinesVisible(isGridVisible);
ingo@2422:
ingo@2422: plot.setRangeGridlineStroke(gridStroke);
ingo@2422: plot.setRangeGridlinePaint(DEFAULT_GRID_COLOR);
ingo@2422: plot.setRangeGridlinesVisible(isGridVisible);
ingo@2422:
ingo@2422: plot.setAxisOffset(new RectangleInsets(0d, 0d, 0d, 0d));
ingo@2422: }
ingo@2422:
ingo@2422:
ingo@2422: /**
ingo@2234: * This helper mehtod is used to extract the current locale from instance
ingo@2234: * vairable context.
ingo@2234: *
ingo@2234: * @return the current locale.
ingo@2234: */
ingo@1645: protected Locale getLocale() {
ingo@1645: CallMeta meta = context.getMeta();
ingo@1645: PreferredLocale[] prefs = meta.getLanguages();
ingo@1645:
ingo@1645: int len = prefs != null ? prefs.length : 0;
ingo@1645:
ingo@1645: Locale[] locales = new Locale[len];
ingo@1645:
ingo@1645: for (int i = 0; i < len; i++) {
ingo@1645: locales[i] = prefs[i].getLocale();
ingo@1645: }
ingo@1645:
ingo@1645: return meta.getPreferredLocale(locales);
ingo@1645: }
ingo@1645:
ingo@1645:
felix@4589: /**
felix@4589: * Look up \param key in i18n dictionary.
felix@4589: * @param key key for which to find i18nd version.
felix@4589: * @param def default, returned if lookup failed.
felix@4589: * @return value found in i18n dictionary, \param def if no value found.
felix@4589: */
ingo@408: protected String msg(String key, String def) {
ingo@408: return Resources.getMsg(context.getMeta(), key, def);
ingo@408: }
ingo@408:
felix@4589: /**
felix@4589: * Look up \param key in i18n dictionary.
felix@4589: * @param key key for which to find i18nd version.
felix@4589: * @return value found in i18n dictionary, key itself if failed.
felix@4589: */
sascha@2407: protected String msg(String key) {
sascha@2407: return Resources.getMsg(context.getMeta(), key, key);
sascha@2407: }
ingo@408:
tom@8248: protected String msg(String key, Object[] args) {
tom@8248: return Resources.getMsg(context.getMeta(), key, key, args);
tom@8248: }
tom@8248:
ingo@412: protected String msg(String key, String def, Object[] args) {
ingo@412: return Resources.getMsg(context.getMeta(), key, def, args);
ingo@412: }
ingo@412:
ingo@412:
ingo@412: protected String getRiverName() {
tom@8248: return new RiverAccess((D4EArtifact)master).getRiver().getName();
ingo@412: }
ingo@412:
tom@8248: protected String getRiverUnit() {
tom@8248: return new RiverAccess((D4EArtifact)master).getRiver()
tom@8248: .getWstUnit().getName();
tom@8248: }
ingo@412:
ingo@412: protected double[] getRange() {
teichmann@5867: D4EArtifact flys = (D4EArtifact) master;
ingo@412:
teichmann@6101: RangeAccess rangeAccess = new RangeAccess(flys);
felix@4858: return rangeAccess.getKmRange();
ingo@412: }
ingo@412:
ingo@412:
ingo@423: /**
ingo@423: * Returns the size of a chart export as array which has been specified by
ingo@423: * the incoming request document.
ingo@423: *
ingo@2057: * @return the size of a chart as [width, height] or null if no width or
ingo@2057: * height are given in the request document.
ingo@423: */
ingo@423: protected int[] getSize() {
ingo@423: int[] size = new int[2];
ingo@423:
sascha@719: Element sizeEl = (Element)XMLUtils.xpath(
ingo@423: request,
ingo@423: XPATH_CHART_SIZE,
ingo@423: XPathConstants.NODE,
ingo@423: ArtifactNamespaceContext.INSTANCE);
ingo@423:
ingo@423: if (sizeEl != null) {
sascha@719: String uri = ArtifactNamespaceContext.NAMESPACE_URI;
ingo@423:
sascha@719: String w = sizeEl.getAttributeNS(uri, "width");
sascha@719: String h = sizeEl.getAttributeNS(uri, "height");
ingo@423:
sascha@719: if (w.length() > 0 && h.length() > 0) {
ingo@423: try {
ingo@423: size[0] = Integer.parseInt(w);
ingo@423: size[1] = Integer.parseInt(h);
ingo@423: }
ingo@423: catch (NumberFormatException nfe) {
teichmann@8202: log.warn("Wrong values for chart width/height.");
ingo@423: }
ingo@423: }
ingo@423: }
ingo@423:
ingo@2057: return size[0] > 0 && size[1] > 0 ? size : null;
ingo@423: }
ingo@423:
ingo@423:
ingo@2234: /**
ingo@2234: * This method returns the format specified in the request document
ingo@2234: * or DEFAULT_CHART_FORMAT if no format is specified in
ingo@2234: * request.
ingo@2234: *
ingo@2234: * @return the format used to export this chart.
ingo@2234: */
ingo@1735: protected String getFormat() {
ingo@1735: String format = (String) XMLUtils.xpath(
ingo@1735: request,
ingo@1735: XPATH_CHART_FORMAT,
ingo@1735: XPathConstants.STRING,
ingo@1735: ArtifactNamespaceContext.INSTANCE);
ingo@1735:
ingo@1735: return format == null || format.length() == 0
ingo@1735: ? DEFAULT_CHART_FORMAT
ingo@1735: : format;
ingo@1735: }
ingo@1735:
ingo@1735:
felix@1944: /**
ingo@2395: * Returns the X-Axis range as String array from request document.
felix@4276: * If the (x|y)range elements are not found in request document, return
felix@4276: * null (i.e. not zoomed).
ingo@2395: *
felix@4276: * @return a String array with [lower, upper], null if not in document.
felix@1944: */
ingo@2395: protected String[] getDomainAxisRangeFromRequest() {
sascha@719: Element xrange = (Element)XMLUtils.xpath(
ingo@652: request,
ingo@652: XPATH_CHART_X_RANGE,
ingo@652: XPathConstants.NODE,
ingo@652: ArtifactNamespaceContext.INSTANCE);
ingo@652:
ingo@652: if (xrange == null) {
ingo@652: return null;
ingo@652: }
ingo@652:
sascha@719: String uri = ArtifactNamespaceContext.NAMESPACE_URI;
ingo@652:
sascha@719: String lower = xrange.getAttributeNS(uri, "from");
sascha@719: String upper = xrange.getAttributeNS(uri, "to");
ingo@652:
ingo@2395: return new String[] { lower, upper };
ingo@652: }
ingo@652:
ingo@652:
felix@4276: /** Returns null if the (x|y)range-element was not found in request document.
felix@4276: * This usally means that the axis are not manually zoomed, i.e. showing
felix@4276: * full data extent. */
ingo@2398: protected String[] getValueAxisRangeFromRequest() {
sascha@719: Element yrange = (Element)XMLUtils.xpath(
ingo@652: request,
ingo@652: XPATH_CHART_Y_RANGE,
ingo@652: XPathConstants.NODE,
ingo@652: ArtifactNamespaceContext.INSTANCE);
ingo@652:
ingo@652: if (yrange == null) {
ingo@652: return null;
ingo@652: }
ingo@652:
felix@4433:
sascha@719: String uri = ArtifactNamespaceContext.NAMESPACE_URI;
ingo@652:
sascha@719: String lower = yrange.getAttributeNS(uri, "from");
sascha@719: String upper = yrange.getAttributeNS(uri, "to");
ingo@652:
ingo@2398: return new String[] { lower, upper };
ingo@652: }
ingo@652:
ingo@652:
ingo@423: /**
ingo@423: * Returns the default size of a chart export as array.
ingo@423: *
ingo@423: * @return the default size of a chart as [width, height].
ingo@423: */
ingo@423: protected int[] getDefaultSize() {
ingo@423: return new int[] { DEFAULT_CHART_WIDTH, DEFAULT_CHART_HEIGHT };
ingo@423: }
ingo@423:
ingo@423:
ingo@1979: /**
ingo@2242: * Add datasets stored in instance variable datasets to plot.
ingo@2242: * datasets actually stores instances of AxisDataset, so each of this
ingo@2242: * datasets is mapped to a specific axis as well.
ingo@2242: *
ingo@2242: * @param plot plot to add datasets to.
ingo@2242: */
ingo@2242: protected void addDatasets(XYPlot plot) {
teichmann@8202: log.debug("addDatasets()");
sascha@3160:
ingo@2242: // AxisDatasets are sorted, but some might be empty.
ingo@2242: // Thus, generate numbering on the fly.
ingo@2242: int axisIndex = 0;
ingo@2242: int datasetIndex = 0;
ingo@2242:
ingo@2242: for (Map.Entry entry: datasets.entrySet()) {
ingo@2242: if (!entry.getValue().isEmpty()) {
ingo@2242: // Add axis and range information.
ingo@2242: AxisDataset axisDataset = entry.getValue();
christian@3155: NumberAxis axis = createYAxis(entry.getKey());
ingo@2242:
ingo@2242: plot.setRangeAxis(axisIndex, axis);
ingo@2242:
ingo@2242: if (axis.getAutoRangeIncludesZero()) {
ingo@2242: axisDataset.setRange(
ingo@2242: Range.expandToInclude(axisDataset.getRange(), 0d));
ingo@2242: }
ingo@2242:
ingo@2587: setYBounds(axisIndex, expandPointRange(axisDataset.getRange()));
ingo@2242:
ingo@2242: // Add contained datasets, mapping to axis.
ingo@2242: for (XYDataset dataset: axisDataset.getDatasets()) {
ingo@2242: plot.setDataset(datasetIndex, dataset);
ingo@2242: plot.mapDatasetToRangeAxis(datasetIndex, axisIndex);
ingo@2242:
felix@2677: applyThemes(plot, dataset,
ingo@2242: datasetIndex,
felix@2677: axisDataset.isArea(dataset));
ingo@2242:
ingo@2242: datasetIndex++;
ingo@2242: }
ingo@2242:
ingo@2242: axisDataset.setPlotAxisIndex(axisIndex);
ingo@2242: axisIndex++;
ingo@2242: }
ingo@2242: }
ingo@2242: }
ingo@2242:
ingo@2242:
felix@2677: /**
ingo@2242: * @param idx "index" of dataset/series (first dataset to be drawn has
ingo@2242: * index 0), correlates with renderer index.
ingo@2242: * @param isArea true if the series describes an area and shall be rendered
ingo@2242: * as such.
ingo@2242: */
ingo@2242: protected void applyThemes(
ingo@2242: XYPlot plot,
ingo@2242: XYDataset series,
ingo@2242: int idx,
ingo@2242: boolean isArea
ingo@2242: ) {
ingo@2242: if (isArea) {
ingo@2242: applyAreaTheme(plot, (StyledAreaSeriesCollection) series, idx);
ingo@2242: }
ingo@2242: else {
ingo@2242: applyLineTheme(plot, series, idx);
ingo@2242: }
ingo@2242: }
ingo@2242:
ingo@2242:
ingo@2242: /**
ingo@2242: * This method applies the themes defined in the series itself. Therefore,
ingo@2242: * StyledXYSeries.applyTheme() is called, which modifies the renderer
ingo@2242: * for the series.
ingo@2242: *
ingo@2242: * @param plot The plot.
ingo@2242: * @param dataset The XYDataset which needs to support Series objects.
ingo@2242: * @param idx The index of the renderer / dataset.
ingo@2242: */
ingo@2242: protected void applyLineTheme(XYPlot plot, XYDataset dataset, int idx) {
teichmann@8202: log.debug("Apply LineTheme for dataset at index: " + idx);
ingo@2321:
ingo@2242: LegendItemCollection lic = new LegendItemCollection();
ingo@2242: LegendItemCollection anno = plot.getFixedLegendItems();
ingo@2242:
ingo@2242: Font legendFont = createLegendLabelFont();
ingo@2242:
ingo@2242: XYLineAndShapeRenderer renderer = createRenderer(plot, idx);
ingo@2242:
ingo@2242: for (int s = 0, num = dataset.getSeriesCount(); s < num; s++) {
ingo@2242: Series series = getSeriesOf(dataset, s);
ingo@2242:
ingo@2321: if (series instanceof StyledSeries) {
ingo@2321: Style style = ((StyledSeries) series).getStyle();
ingo@2321: style.applyTheme(renderer, s);
ingo@2242: }
ingo@2242:
ingo@2242: // special case: if there is just one single item, we need to enable
ingo@2242: // points for this series, otherwise we would not see anything in
ingo@2242: // the chart area.
ingo@2242: if (series.getItemCount() == 1) {
ingo@2242: renderer.setSeriesShapesVisible(s, true);
ingo@2242: }
ingo@2242:
ingo@2242: LegendItem legendItem = renderer.getLegendItem(idx, s);
raimund@3169: if (legendItem.getLabel().endsWith(" ") ||
raimund@3169: legendItem.getLabel().endsWith("interpol")) {
raimund@3134: legendItem = null;
raimund@3134: }
raimund@3167:
ingo@2242: if (legendItem != null) {
ingo@2242: legendItem.setLabelFont(legendFont);
ingo@2242: lic.add(legendItem);
ingo@2242: }
ingo@2242: else {
teichmann@8202: log.warn("Could not get LegentItem for renderer: "
ingo@2242: + idx + ", series-idx " + s);
ingo@2242: }
ingo@2242: }
ingo@2242:
ingo@2242: if (anno != null) {
ingo@2242: lic.addAll(anno);
ingo@2242: }
ingo@2242:
ingo@2242: plot.setFixedLegendItems(lic);
ingo@2242:
ingo@2242: plot.setRenderer(idx, renderer);
ingo@2242: }
ingo@2242:
ingo@2242:
ingo@2242: /**
ingo@2242: * @param plot The plot.
ingo@2242: * @param area A StyledAreaSeriesCollection object.
ingo@2242: * @param idx The index of the dataset.
ingo@2242: */
ingo@2242: protected void applyAreaTheme(
ingo@2242: XYPlot plot,
ingo@2242: StyledAreaSeriesCollection area,
ingo@2242: int idx
ingo@2242: ) {
ingo@2242: LegendItemCollection lic = new LegendItemCollection();
ingo@2242: LegendItemCollection anno = plot.getFixedLegendItems();
ingo@2242:
ingo@2242: Font legendFont = createLegendLabelFont();
ingo@2242:
teichmann@8202: log.debug("Registering an 'area'renderer at idx: " + idx);
ingo@2242:
ingo@2242: StableXYDifferenceRenderer dRenderer =
ingo@2242: new StableXYDifferenceRenderer();
ingo@2242:
ingo@2242: if (area.getMode() == StyledAreaSeriesCollection.FILL_MODE.UNDER) {
ingo@2242: dRenderer.setPositivePaint(createTransparentPaint());
ingo@2242: }
ingo@2242:
ingo@2242: plot.setRenderer(idx, dRenderer);
ingo@2242:
ingo@2242: area.applyTheme(dRenderer);
ingo@2242:
ingo@3785: // i18n
ingo@3785: dRenderer.setAreaLabelNumberFormat(Formatter.getFormatter(context.getMeta(), 2, 4));
ingo@3785:
ingo@3785: dRenderer.setAreaLabelTemplate(Resources.getMsg(
ingo@3785: context.getMeta(), "area.label.template", "Area=%sm2"));
ingo@3785:
ingo@2242: LegendItem legendItem = dRenderer.getLegendItem(idx, 0);
ingo@2242: if (legendItem != null) {
ingo@2242: legendItem.setLabelFont(legendFont);
ingo@2242: lic.add(legendItem);
ingo@2242: }
ingo@2242: else {
teichmann@8202: log.warn("Could not get LegentItem for renderer: "
ingo@2242: + idx + ", series-idx " + 0);
ingo@2242: }
ingo@2242:
ingo@2242: if (anno != null) {
ingo@2242: lic.addAll(anno);
ingo@2242: }
ingo@2242:
ingo@2242: plot.setFixedLegendItems(lic);
ingo@2242: }
ingo@2242:
ingo@2242:
ingo@2242: /**
ingo@2242: * Expands a given range if it collapses into one point.
ingo@2242: *
felix@3270: * @param range Range to be expanded if upper == lower bound.
felix@3248: *
felix@3248: * @return Bounds of point plus 5 percent in each direction.
ingo@2242: */
ingo@2587: private Bounds expandPointRange(Range range) {
ingo@2587: if (range == null) {
ingo@2587: return null;
ingo@2242: }
ingo@2587: else if (range.getLowerBound() == range.getUpperBound()) {
felix@3248: Range expandedRange = ChartHelper.expandRange(range, 5d);
felix@3248: return new DoubleBounds(expandedRange.getLowerBound(), expandedRange.getUpperBound());
ingo@2587: }
ingo@2587:
ingo@2587: return new DoubleBounds(range.getLowerBound(), range.getUpperBound());
ingo@2242: }
ingo@2242:
ingo@2242:
ingo@2242: /**
ingo@2242: * Creates a new instance of EnhancedLineAndShapeRenderer.
ingo@2242: *
ingo@2242: * @param plot The plot which is set for the new renderer.
ingo@2242: * @param idx This value is not used in the current implementation.
ingo@2242: *
ingo@2242: * @return a new instance of EnhancedLineAndShapeRenderer.
ingo@2242: */
ingo@2242: protected XYLineAndShapeRenderer createRenderer(XYPlot plot, int idx) {
teichmann@8202: log.debug("Create EnhancedLineAndShapeRenderer for idx: " + idx);
ingo@2242:
ingo@2242: EnhancedLineAndShapeRenderer r =
ingo@2242: new EnhancedLineAndShapeRenderer(true, false);
ingo@2242:
ingo@2242: r.setPlot(plot);
ingo@2242:
ingo@2242: return r;
ingo@2242: }
ingo@2242:
ingo@2242:
ingo@2242: /**
ingo@2233: * Creates a new instance of IdentifiableNumberAxis.
ingo@2233: *
ingo@2233: * @param idx The index of the new axis.
ingo@2233: * @param label The label of the new axis.
ingo@2233: *
ingo@2233: * @return an instance of IdentifiableNumberAxis.
ingo@2233: */
ingo@2233: protected NumberAxis createNumberAxis(int idx, String label) {
ingo@2233: YAxisWalker walker = getYAxisWalker();
ingo@2233:
ingo@2233: return new IdentifiableNumberAxis(walker.getId(idx), label);
ingo@2233: }
ingo@2234:
ingo@2234:
ingo@2236: /**
ingo@2238: * Create Y (range) axis for given index.
ingo@2238: * Shall be overriden by subclasses.
ingo@2238: */
ingo@2238: protected NumberAxis createYAxis(int index) {
ingo@2238: YAxisWalker walker = getYAxisWalker();
ingo@2238:
ingo@2238: Font labelFont = new Font(
ingo@2238: DEFAULT_FONT_NAME,
ingo@2238: Font.BOLD,
ingo@2238: getYAxisFontSize(index));
ingo@2238:
ingo@2238: IdentifiableNumberAxis axis = new IdentifiableNumberAxis(
ingo@2238: walker.getId(index),
ingo@2238: getYAxisLabel(index));
ingo@2238:
ingo@2238: axis.setAutoRangeIncludesZero(false);
ingo@2238: axis.setLabelFont(labelFont);
ingo@2590: axis.setTickLabelFont(labelFont);
ingo@2238:
ingo@2238: return axis;
ingo@2238: }
ingo@2238:
ingo@2238:
ingo@2238: /**
ingo@2236: * Creates a new LegendItem with name and font provided by
ingo@2236: * createLegendLabelFont().
ingo@2236: *
ingo@2236: * @param theme The theme of the chart line.
felix@3270: * @param name The displayed name of the item.
ingo@2236: *
ingo@2236: * @return a new LegendItem instance.
ingo@2236: */
teichmann@6905: public LegendItem createLegendItem(ThemeDocument theme, String name) {
ingo@2236: // OPTIMIZE Pass font, parsed Theme items.
ingo@2236:
teichmann@6908: Color color = theme.parseLineColorField();
teichmann@6908: if (color == null) {
teichmann@6908: color = Color.BLACK;
teichmann@6908: }
teichmann@6908:
ingo@2236: LegendItem legendItem = new LegendItem(name, color);
ingo@2236:
ingo@2236: legendItem.setLabelFont(createLegendLabelFont());
ingo@2236: return legendItem;
ingo@2236: }
ingo@2236:
ingo@2236:
ingo@2236: /**
ingo@2236: * Creates Font (Family and size) to use when creating Legend Items. The
ingo@2236: * font size depends in the return value of getLegendFontSize().
ingo@2236: *
ingo@2236: * @return a new Font instance with DEFAULT_FONT_NAME.
ingo@2236: */
ingo@2236: protected Font createLegendLabelFont() {
ingo@2236: return new Font(
ingo@2236: DEFAULT_FONT_NAME,
ingo@2236: Font.PLAIN,
ingo@2236: getLegendFontSize()
ingo@2236: );
ingo@2236: }
ingo@2236:
ingo@2236:
ingo@2242: /**
felix@3184: * Create new legend entries, dependent on settings.
felix@3184: * @param plot The plot for which to modify the legend.
felix@3184: */
felix@3184: public void aggregateLegendEntries(XYPlot plot) {
felix@3184: int AGGR_THRESHOLD = 0;
felix@3184:
raimund@3296: if (getChartSettings() == null) {
raimund@3296: return;
raimund@3296: }
felix@3184: Integer threshold = getChartSettings().getLegendSection()
felix@3184: .getAggregationThreshold();
felix@3184:
felix@3184: AGGR_THRESHOLD = (threshold != null) ? threshold.intValue() : 0;
felix@3184:
felix@3184: LegendProcessor.aggregateLegendEntries(plot, AGGR_THRESHOLD);
felix@3184: }
felix@3184:
felix@3184:
felix@3184: /**
ingo@2242: * Returns a transparently textured paint.
ingo@2242: *
ingo@2242: * @return a transparently textured paint.
ingo@2242: */
ingo@2242: protected static Paint createTransparentPaint() {
ingo@2242: // TODO why not use a transparent color?
ingo@2242: BufferedImage texture = new BufferedImage(
ingo@2242: 1, 1, BufferedImage.TYPE_4BYTE_ABGR);
ingo@2242:
ingo@2242: return new TexturePaint(
ingo@2242: texture, new Rectangle2D.Double(0d, 0d, 0d, 0d));
ingo@2242: }
ingo@2242:
ingo@2242:
ingo@2234: protected void preparePDFContext(CallContext context) {
ingo@2234: int[] dimension = getExportDimension();
ingo@2234:
ingo@2234: context.putContextValue("chart.width", dimension[0]);
ingo@2234: context.putContextValue("chart.height", dimension[1]);
ingo@2234: context.putContextValue("chart.marginLeft", 5f);
ingo@2234: context.putContextValue("chart.marginRight", 5f);
ingo@2234: context.putContextValue("chart.marginTop", 5f);
ingo@2234: context.putContextValue("chart.marginBottom", 5f);
ingo@2234: context.putContextValue(
ingo@2234: "chart.page.format",
ingo@2234: ChartExportHelper.DEFAULT_PAGE_SIZE);
ingo@2234: }
ingo@2234:
ingo@2234:
ingo@2234: protected void prepareSVGContext(CallContext context) {
ingo@2234: int[] dimension = getExportDimension();
ingo@2234:
ingo@2234: context.putContextValue("chart.width", dimension[0]);
ingo@2234: context.putContextValue("chart.height", dimension[1]);
ingo@2234: context.putContextValue(
ingo@2234: "chart.encoding",
ingo@2234: ChartExportHelper.DEFAULT_ENCODING);
ingo@2234: }
bjoern@4444:
bjoern@4444: /**
bjoern@4444: * Retuns the call context. May be null if init hasn't been called yet.
bjoern@4444: *
bjoern@4444: * @return the CallContext instance
bjoern@4444: */
bjoern@4444: public CallContext getCallContext() {
bjoern@4444: return context;
bjoern@4444: }
ingo@348: }
ingo@348: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :