[Schmitzm-commits] r1543 - trunk/schmitzm-jfree-gt/src/main/java/de/schmitzm/jfree/feature/style

scm-commit@wald.intevation.org scm-commit at wald.intevation.org
Wed Mar 30 20:26:56 CEST 2011


Author: alfonx
Date: 2011-03-30 20:26:40 +0200 (Wed, 30 Mar 2011)
New Revision: 1543

Modified:
   trunk/schmitzm-jfree-gt/src/main/java/de/schmitzm/jfree/feature/style/FeatureChartUtil.java
Log:


Modified: trunk/schmitzm-jfree-gt/src/main/java/de/schmitzm/jfree/feature/style/FeatureChartUtil.java
===================================================================
--- trunk/schmitzm-jfree-gt/src/main/java/de/schmitzm/jfree/feature/style/FeatureChartUtil.java	2011-03-30 11:00:15 UTC (rev 1542)
+++ trunk/schmitzm-jfree-gt/src/main/java/de/schmitzm/jfree/feature/style/FeatureChartUtil.java	2011-03-30 18:26:40 UTC (rev 1543)
@@ -84,1255 +84,1387 @@
 /**
  * This class contains static utility methods related to chart styles based on
  * {@link FeatureCollection}.
+ * 
  * @author <a href="mailto:Martin.Schmitz at koeln.de">Martin Schmitz</a>
  * @version 1.0
  */
 public class FeatureChartUtil {
-  final static Logger LOGGER = Logger.getLogger(FeatureChartUtil.class);
+	final static Logger LOGGER = Logger.getLogger(FeatureChartUtil.class);
 
-  /** Instance of {@link ChartStyleXMLFactory}. */
-  public static final FeatureChartStyleXMLFactory FEATURE_CHART_STYLE_FACTORY = new FeatureChartStyleXMLFactory();
-  
-  /**
-   * Returns all {@link DatasetSelectionModel DatasetSelectionModels} that can
-   * be reached via the renderers of a chart.
-   * @param chart a chart
-   */
-  public static List<FeatureDatasetSelectionModel<?, ?, ?>> getFeatureDatasetSelectionModelFor(
-      JFreeChart chart) {
-    ArrayList<FeatureDatasetSelectionModel<?, ?, ?>> renderers = new ArrayList<FeatureDatasetSelectionModel<?, ?, ?>>();
+	/** Instance of {@link ChartStyleXMLFactory}. */
+	public static final FeatureChartStyleXMLFactory FEATURE_CHART_STYLE_FACTORY = new FeatureChartStyleXMLFactory();
 
-    Plot plot = chart.getPlot();
+	/**
+	 * Returns all {@link DatasetSelectionModel DatasetSelectionModels} that can
+	 * be reached via the renderers of a chart.
+	 * 
+	 * @param chart
+	 *            a chart
+	 */
+	public static List<FeatureDatasetSelectionModel<?, ?, ?>> getFeatureDatasetSelectionModelFor(
+			JFreeChart chart) {
+		ArrayList<FeatureDatasetSelectionModel<?, ?, ?>> renderers = new ArrayList<FeatureDatasetSelectionModel<?, ?, ?>>();
 
-    /** Collect renderers from XYPlot */
-    if (plot instanceof XYPlot) {
-      XYPlot xyplot = (XYPlot) plot;
+		Plot plot = chart.getPlot();
 
-      for (int i = 0; i < xyplot.getRendererCount(); i++) {
-        XYItemRenderer renderer = xyplot.getRenderer(i);
-        if (renderer instanceof DatasetSelectionModelProvider &&
-            ((DatasetSelectionModelProvider) renderer).getSelectionModel() instanceof FeatureDatasetSelectionModel) {
-          renderers.add((FeatureDatasetSelectionModel) ((DatasetSelectionModelProvider) renderer).getSelectionModel());
-        }
-      }
-    }
-    /** Collect renderers from CategoryPlot */
-    if (plot instanceof CategoryPlot) {
-      CategoryPlot catPlot = (CategoryPlot) plot;
+		/** Collect renderers from XYPlot */
+		if (plot instanceof XYPlot) {
+			XYPlot xyplot = (XYPlot) plot;
 
-      for (int i = 0; i < catPlot.getRendererCount(); i++) {
-        CategoryItemRenderer renderer = catPlot.getRenderer(i);
-        if (renderer instanceof DatasetSelectionModelProvider &&
-            ((DatasetSelectionModelProvider) renderer).getSelectionModel() instanceof FeatureDatasetSelectionModel) {
-          renderers.add((FeatureDatasetSelectionModel) ((DatasetSelectionModelProvider) renderer).getSelectionModel());
-        }
-      }
-    }
+			for (int i = 0; i < xyplot.getRendererCount(); i++) {
+				XYItemRenderer renderer = xyplot.getRenderer(i);
+				if (renderer instanceof DatasetSelectionModelProvider
+						&& ((DatasetSelectionModelProvider) renderer)
+								.getSelectionModel() instanceof FeatureDatasetSelectionModel) {
+					renderers
+							.add((FeatureDatasetSelectionModel) ((DatasetSelectionModelProvider) renderer)
+									.getSelectionModel());
+				}
+			}
+		}
+		/** Collect renderers from CategoryPlot */
+		if (plot instanceof CategoryPlot) {
+			CategoryPlot catPlot = (CategoryPlot) plot;
 
-    return renderers;
-  }
+			for (int i = 0; i < catPlot.getRendererCount(); i++) {
+				CategoryItemRenderer renderer = catPlot.getRenderer(i);
+				if (renderer instanceof DatasetSelectionModelProvider
+						&& ((DatasetSelectionModelProvider) renderer)
+								.getSelectionModel() instanceof FeatureDatasetSelectionModel) {
+					renderers
+							.add((FeatureDatasetSelectionModel) ((DatasetSelectionModelProvider) renderer)
+									.getSelectionModel());
+				}
+			}
+		}
 
-  /**
-   * Creates a {@link Dataset} for 1 or more attributes of a
-   * {@link FeatureCollection}. According to the feature attribute type the
-   * method decides whether a {@link XYDataset} or a {@link CategoryDataset} is
-   * created:<br>
-   * In case of an non-numeric X attribute a {@link CategoryDataset} is created
-   * always. Otherwise the default is to create a {@link XYDataset}. The flag
-   * {@code forceCat} can be used to create {@link CategoryDataset} for numeric
-   * X attributes.<br><br>
-   * If the given style defines a secondary range axis for at least one
-   * attribute the method returns 2 datasets.
-   * @param fc a {@link FeatureCollection}
-   * @param forceCat forces a {@link CategoryDataset} also for numeric X
-   *          attributes
-   * @param sort sorts the features according to {@code xAttr} before creating
-   *          the dataset
-   * @param xAttr feature attribute used for the X-value
-   * @param yAttr feature attribute(s) used for the Y-value (at least one; for
-   *          each a series is created in the dataset)
-   */
-  public static Dataset[] createDataset(
-      FeatureCollection<SimpleFeatureType, SimpleFeature> fc,
-      FeatureChartStyle style) {
-    String xAttr = style.getAttributeName(0);
-    // Check the X attribute to differ between XY- and CategoryDataset
-    AttributeDescriptor xDesc = fc.getSchema().getDescriptor(xAttr);
-    if (xDesc == null)
-      throw new UnsupportedOperationException("Unknown attribute: " + xAttr);
+		return renderers;
+	}
 
-    // If domain attribute is numeric (and category not forced) create
-    // a XYDataset
-    if (!style.isForceCategories() &&
-        Number.class.isAssignableFrom(xDesc.getType().getBinding()))
-      return createXYDataset(fc, style);
-    else
-      return createCategoryDataset(fc, style);
-  }
+	/**
+	 * Creates a {@link Dataset} for 1 or more attributes of a
+	 * {@link FeatureCollection}. According to the feature attribute type the
+	 * method decides whether a {@link XYDataset} or a {@link CategoryDataset}
+	 * is created:<br>
+	 * In case of an non-numeric X attribute a {@link CategoryDataset} is
+	 * created always. Otherwise the default is to create a {@link XYDataset}.
+	 * The flag {@code forceCat} can be used to create {@link CategoryDataset}
+	 * for numeric X attributes.<br>
+	 * <br>
+	 * If the given style defines a secondary range axis for at least one
+	 * attribute the method returns 2 datasets.
+	 * 
+	 * @param fc
+	 *            a {@link FeatureCollection}
+	 * @param forceCat
+	 *            forces a {@link CategoryDataset} also for numeric X attributes
+	 * @param sort
+	 *            sorts the features according to {@code xAttr} before creating
+	 *            the dataset
+	 * @param xAttr
+	 *            feature attribute used for the X-value
+	 * @param yAttr
+	 *            feature attribute(s) used for the Y-value (at least one; for
+	 *            each a series is created in the dataset)
+	 */
+	public static Dataset[] createDataset(
+			FeatureCollection<SimpleFeatureType, SimpleFeature> fc,
+			FeatureChartStyle style) {
+		String xAttr = style.getAttributeName(0);
+		// Check the X attribute to differ between XY- and CategoryDataset
+		AttributeDescriptor xDesc = fc.getSchema().getDescriptor(xAttr);
+		if (xDesc == null)
+			throw new UnsupportedOperationException("Unknown attribute: "
+					+ xAttr);
 
-  /**
-   * Checks whether an attribute exists and whether it is of a specific type.
-   * Otherwise an {@link IllegalArgumentException} is thrown.
-   * @param fType a feature type
-   * @param aName the attribute name checked for numeric type
-   * @param typeToCheck the type the attribute must support (if {@code null}
-   *          only the attribute existing is checked!)
-   */
-  private static void checkAttributeType(SimpleFeatureType fType, String aName,
-      Class<?> typeToCheck, String... errDesc) {
-    AttributeDescriptor aDesc = fType.getDescriptor(aName);
-    if (aDesc == null)
-      throw new UnsupportedOperationException("Unknown attribute: " + aName);
+		// If domain attribute is numeric (and category not forced) create
+		// a XYDataset
+		if (!style.isForceCategories()
+				&& Number.class.isAssignableFrom(xDesc.getType().getBinding()))
+			return createXYDataset(fc, style);
+		else
+			return createCategoryDataset(fc, style);
+	}
 
-    String attrDesc = errDesc.length > 0 ? errDesc[0] : "Attribute";
-    String datasetDesc = errDesc.length > 1 ? errDesc[1] : "Dataset";
+	/**
+	 * Checks whether an attribute exists and whether it is of a specific type.
+	 * Otherwise an {@link IllegalArgumentException} is thrown.
+	 * 
+	 * @param fType
+	 *            a feature type
+	 * @param aName
+	 *            the attribute name checked for numeric type
+	 * @param typeToCheck
+	 *            the type the attribute must support (if {@code null} only the
+	 *            attribute existing is checked!)
+	 */
+	private static void checkAttributeType(SimpleFeatureType fType,
+			String aName, Class<?> typeToCheck, String... errDesc) {
+		AttributeDescriptor aDesc = fType.getDescriptor(aName);
+		if (aDesc == null)
+			throw new UnsupportedOperationException("Unknown attribute: "
+					+ aName);
 
-    if (typeToCheck != null &&
-        !typeToCheck.isAssignableFrom(aDesc.getType().getBinding()))
-      throw new UnsupportedOperationException(attrDesc + " must be " +
-                                              typeToCheck.getSimpleName() +
-                                              " for " + datasetDesc + ": " +
-                                              aName);
-  }
+		String attrDesc = errDesc.length > 0 ? errDesc[0] : "Attribute";
+		String datasetDesc = errDesc.length > 1 ? errDesc[1] : "Dataset";
 
-  /**
-   * Creates a {@link XYDataset} for 2 (or more) attributes of a
-   * {@link FeatureCollection}. XYDateset can only be created for <b>numeric</b>
-   * attributes. If the given style defines a secondary range axis for at least one
-   * attribute the method returns 2 datasets.
-   * @param fc a {@link FeatureCollection}
-   * @param style defines the attributes used to create the dataset from, as
-   *          well as the sorting and normalization properties
-   * @throws IllegalArgumentException if less then 2 attributes are specified
-   * @throws UnsupportedOperationException if attributes are not numeric
-   */
-  public static XYSeriesCollection[] createXYDataset(
-      FeatureCollection<SimpleFeatureType, SimpleFeature> fc,
-      FeatureChartStyle chartStyle) {
-    int attrCount = chartStyle.getAttributeCount();
-    if (attrCount < 2)
-      throw new IllegalArgumentException(
-          "FeatureChartStyle must define at least 2 attributes to create XYDataset: " +
-              attrCount);
-    String xAttrName = chartStyle.getAttributeName(ChartStyle.DOMAIN_AXIS);
-    // check data types of X attribute
-    checkAttributeType(fc.getSchema(), xAttrName, Number.class,
-                       "Domain attribute", "XYDataset");
-    // check data types of Y attribute (only numeric allowed)
-    for (int i = 1; i < attrCount; i++)
-      checkAttributeType(fc.getSchema(), chartStyle.getAttributeName(i),
-                         Number.class, "Range attribute", "XYDataset");
+		if (typeToCheck != null
+				&& !typeToCheck.isAssignableFrom(aDesc.getType().getBinding()))
+			throw new UnsupportedOperationException(attrDesc + " must be "
+					+ typeToCheck.getSimpleName() + " for " + datasetDesc
+					+ ": " + aName);
+	}
 
-    // Calculate statistics to normalize not-aggregated attributes
-    HashMap<String, QuantileBin1D> statisticsForNormalization = calcStatisticsForNotAggregatedNormalization(
-                                                                                                            fc,
-                                                                                                            chartStyle);
-    // Calculate weight sums for all weight-aggregated attributes
-    HashMap<Comparable<?>, Double>[] weightSumsForAggregation = calcWeightSumForAggregation(
-                                                                                            fc,
-                                                                                            chartStyle);
-    // Prepare set for statistics to calculate aggregation functions (one
-    // statistic for each range attribute of each domain value)
-    HashMap<Comparable<?>, QuantileBin1D>[] statisticsForAggregation = new HashMap[attrCount];
-    for (int i = 1; i < attrCount; i++)
-      statisticsForAggregation[i] = new HashMap<Comparable<?>, QuantileBin1D>();
-    
-    // determine the count of dataset, which must be created (one
-    // for each range axis)
-    int datasetCount = chartStyle.determineRangeAxisCount();
-    
-    // Create a new dataset and insert the series
-    XYSeriesCollection[] datasets = new XYSeriesCollection[datasetCount];
-    for (int i=0; i<datasets.length; i++)
-      datasets[i] = new XYSeriesCollection();
-    
-    // Initalize mapping between features and dataset
-    // TODO: mapping also necessary for secondary dataset!!
-    Feature2SeriesDatasetMapping mapping = new Feature2SeriesDatasetMapping(fc,
-        datasets[0]);
-    datasets[0].setGroup(new FeatureDatasetMetaData(mapping));
+	/**
+	 * Creates a {@link XYDataset} for 2 (or more) attributes of a
+	 * {@link FeatureCollection}. XYDateset can only be created for
+	 * <b>numeric</b> attributes. If the given style defines a secondary range
+	 * axis for at least one attribute the method returns 2 datasets.
+	 * 
+	 * @param fc
+	 *            a {@link FeatureCollection}
+	 * @param style
+	 *            defines the attributes used to create the dataset from, as
+	 *            well as the sorting and normalization properties
+	 * @throws IllegalArgumentException
+	 *             if less then 2 attributes are specified
+	 * @throws UnsupportedOperationException
+	 *             if attributes are not numeric
+	 */
+	public static XYSeriesCollection[] createXYDataset(
+			FeatureCollection<SimpleFeatureType, SimpleFeature> fc,
+			FeatureChartStyle chartStyle) {
+		int attrCount = chartStyle.getAttributeCount();
+		if (attrCount < 2)
+			throw new IllegalArgumentException(
+					"FeatureChartStyle must define at least 2 attributes to create XYDataset: "
+							+ attrCount);
+		String xAttrName = chartStyle.getAttributeName(ChartStyle.DOMAIN_AXIS);
+		// check data types of X attribute
+		checkAttributeType(fc.getSchema(), xAttrName, Number.class,
+				"Domain attribute", "XYDataset");
+		// check data types of Y attribute (only numeric allowed)
+		for (int i = 1; i < attrCount; i++)
+			checkAttributeType(fc.getSchema(), chartStyle.getAttributeName(i),
+					Number.class, "Range attribute", "XYDataset");
 
-    // If dataset should be sorted, the features must be sorted first
-    // according to the domain attribute. The "autoSort" functionality
-    // of XYSeries can NOT BE USED because of the following reason:
-    // To realize the synchronized feature/dataitem a mapping
-    // between the feature IDs and the data item index (in the series)
-    // must be created. Using the "autoSort" functionality, the
-    // indices inside the series change with every XYSeries.add(..)
-    // call, so that the mapping is destroyed!!
-    //
-    // Only solution: Pre-sort the features so that the XYSeries internal
-    // order does not change!
-    FeatureIterator<SimpleFeature> features = null;
-    Iterator<SimpleFeature> fi = null;
-    if (chartStyle.isSortDomainAxis()) {
-      // Sorting attribute(s) -> Category attribute
-      String[] sortAttr = new String[] {xAttrName};
-      // If grouping is used so create series, also sort by
-      // this attribute, so that the series are also sorted
-      // "inside" every category
-      if ( chartStyle.getSeriesAttributeName() != null )
-        sortAttr = LangUtil.extendArray(sortAttr, chartStyle.getSeriesAttributeName());
-      Vector<SimpleFeature> sortedFeatures = FeatureUtil.sortFeatures(fc,sortAttr);
-      // Create an Iterator for the sorted Features
-      fi = sortedFeatures.iterator();
-    } else {
-      features = fc.features();
-      fi = new PipedFeatureIterator(features);
-    }
+		// Calculate statistics to normalize not-aggregated attributes
+		HashMap<String, QuantileBin1D> statisticsForNormalization = calcStatisticsForNotAggregatedNormalization(
+				fc, chartStyle);
+		// Calculate weight sums for all weight-aggregated attributes
+		HashMap<Comparable<?>, Double>[] weightSumsForAggregation = calcWeightSumForAggregation(
+				fc, chartStyle);
+		// Prepare set for statistics to calculate aggregation functions (one
+		// statistic for each range attribute of each domain value)
+		HashMap<Comparable<?>, QuantileBin1D>[] statisticsForAggregation = new HashMap[attrCount];
+		for (int i = 1; i < attrCount; i++)
+			statisticsForAggregation[i] = new HashMap<Comparable<?>, QuantileBin1D>();
 
-    // Iterate the FeatureCollection and fill the dataset
-    try {
-      int datasetIdx = 0; // index for the data item in the dataset
-      for (; fi.hasNext();) {
-        SimpleFeature f = fi.next();
-        // Determine X value (NULL not permitted for XYDateset!)
-        Number xValue = (Number) f.getAttribute(xAttrName);
+		// determine the count of dataset, which must be created (one
+		// for each range axis)
+		int datasetCount = chartStyle.determineRangeAxisCount();
 
-        // Filter out NODATA values
-        xValue = chartStyle.filterNoDataValue(ChartStyle.DOMAIN_AXIS, xValue);
-        if (xValue == null)
-          continue;
+		// Create a new dataset and insert the series
+		XYSeriesCollection[] datasets = new XYSeriesCollection[datasetCount];
+		for (int i = 0; i < datasets.length; i++)
+			datasets[i] = new XYSeriesCollection();
 
-        // If grouping series attribute is used, filter out
-        // its no data values
-        Object seriesID = null;
-        if ( chartStyle.getSeriesAttributeName() != null ) {
-          seriesID = determineSeriesAttributeValueFromFeature(f, chartStyle);
-          if ( seriesID == null )
-            continue;
-        }
+		// Initalize mapping between features and dataset
+		// TODO: mapping also necessary for secondary dataset!!
+		Feature2SeriesDatasetMapping mapping = new Feature2SeriesDatasetMapping(
+				fc, datasets[0]);
+		datasets[0].setGroup(new FeatureDatasetMetaData(mapping));
 
-        // Normalization of the domain axis value (if they are not handled as
-        // categories)
-        if (chartStyle.isAttributeNormalized(ChartStyle.DOMAIN_AXIS))
-          xValue = normalize(xValue, xAttrName, statisticsForNormalization);
+		// If dataset should be sorted, the features must be sorted first
+		// according to the domain attribute. The "autoSort" functionality
+		// of XYSeries can NOT BE USED because of the following reason:
+		// To realize the synchronized feature/dataitem a mapping
+		// between the feature IDs and the data item index (in the series)
+		// must be created. Using the "autoSort" functionality, the
+		// indices inside the series change with every XYSeries.add(..)
+		// call, so that the mapping is destroyed!!
+		//
+		// Only solution: Pre-sort the features so that the XYSeries internal
+		// order does not change!
+		FeatureIterator<SimpleFeature> features = null;
+		Iterator<SimpleFeature> fi = null;
+		if (chartStyle.isSortDomainAxis()) {
+			// Sorting attribute(s) -> Category attribute
+			String[] sortAttr = new String[] { xAttrName };
+			// If grouping is used so create series, also sort by
+			// this attribute, so that the series are also sorted
+			// "inside" every category
+			if (chartStyle.getSeriesAttributeName() != null)
+				sortAttr = LangUtil.extendArray(sortAttr,
+						chartStyle.getSeriesAttributeName());
+			Vector<SimpleFeature> sortedFeatures = FeatureUtil.sortFeatures(fc,
+					sortAttr);
+			// Create an Iterator for the sorted Features
+			fi = sortedFeatures.iterator();
+		} else {
+			features = fc.features();
+			fi = new PipedFeatureIterator(features);
+		}
 
-        // Determine the Y values and fill the series
-        for (int attrIdx = 1; attrIdx < attrCount; attrIdx++) {
-          Double yValue = determineRangeValueFromFeature(
-                                                         f,
-                                                         (Comparable<?>) xValue,
-                                                         attrIdx,
-                                                         chartStyle,
-                                                         statisticsForNormalization,
-                                                         weightSumsForAggregation);
-          // Ignore feature if value is invalid
-          if (yValue == null) {
-            if ( chartStyle.isIgnoreNoDataRangeValues() )
-              continue;
-            else
-              yValue = 0.0;
-          }
-          
-          // determine the dataset according to the axis
-          // the attribute should deal with
-          XYSeriesCollection dataset = determineDatasetForAttribute(chartStyle,attrIdx,datasets);
+		// Iterate the FeatureCollection and fill the dataset
+		try {
+			int datasetIdx = 0; // index for the data item in the dataset
+			for (; fi.hasNext();) {
+				SimpleFeature f = fi.next();
+				// Determine X value (NULL not permitted for XYDateset!)
+				Number xValue = (Number) f.getAttribute(xAttrName);
 
-          // Fill series, if no aggregation function is defined.
-          // Otherwise fill statistic (dataset is filled later!)
-          if (chartStyle.getAttributeAggregation(attrIdx) == null) {
-            // Fill series
-            String yAttrName = chartStyle.getAttributeName(attrIdx);
-            // If grouping series attribute is used, add its value
-            // to the series ID
-            if ( seriesID != null )
-              yAttrName = yAttrName + "_" + seriesID.toString();
+				// Filter out NODATA values
+				xValue = chartStyle.filterNoDataValue(ChartStyle.DOMAIN_AXIS,
+						xValue);
+				if (xValue == null)
+					continue;
 
-            XYSeries xySeries = JFreeChartUtil.getOrAddSeriesFromDataset(dataset, yAttrName, false, true);
-            xySeries.add(xValue, yValue);
+				// If grouping series attribute is used, filter out
+				// its no data values
+				Object seriesID = null;
+				if (chartStyle.getSeriesAttributeName() != null) {
+					seriesID = determineSeriesAttributeValueFromFeature(f,
+							chartStyle);
+					if (seriesID == null)
+						continue;
+				}
 
-            // If grouping series attribute is used, and a "legend title" attribute
-            // is set, overwrite the legend label in the chart style 
-            if ( seriesID != null && chartStyle.getSeriesLegendTitleAttributeName() != null ) {
-              // determine the series index for the current series key
-              int seriesIdx = JFreeChartUtil.getSeriesIndexFromDataset(dataset,yAttrName);
-              // overwrite the series legend label in chart style
-              redefineSeriesLegendTitle(chartStyle, seriesIdx, f, attrIdx);
-            }
+				// Normalization of the domain axis value (if they are not
+				// handled as
+				// categories)
+				if (chartStyle.isAttributeNormalized(ChartStyle.DOMAIN_AXIS))
+					xValue = normalize(xValue, xAttrName,
+							statisticsForNormalization);
 
-            // Mapping between FID and data index in series
-            // TODO: currently only for primary dataset! But in the future also
-            //       the items in the secondary dataset must be mapped!!
-            if ( dataset == datasets[0] )
-              mapping.setMapping(f.getID(), yAttrName, datasetIdx++);
-          } else {
-            QuantileBin1D aggrStat = statisticsForAggregation[attrIdx].get(xValue);
-            if (aggrStat == null) {
-              aggrStat = new DynamicBin1D();
-              statisticsForAggregation[attrIdx].put(((Number) xValue).doubleValue(), aggrStat);
-            }
-            aggrStat.add(yValue.doubleValue());
-            // TODO: Mapping vormerken (??)
-            // Problem: siehe unten
+				// Determine the Y values and fill the series
+				for (int attrIdx = 1; attrIdx < attrCount; attrIdx++) {
+					Double yValue = determineRangeValueFromFeature(f,
+							(Comparable<?>) xValue, attrIdx, chartStyle,
+							statisticsForNormalization,
+							weightSumsForAggregation);
+					// Ignore feature if value is invalid
+					if (yValue == null) {
+						if (chartStyle.isIgnoreNoDataRangeValues())
+							continue;
+						else
+							yValue = 0.0;
+					}
 
-          }
-        }
-      }
-    } finally {
-      if (features != null) {
-        fc.close(features);
-      }
-    }
+					// determine the dataset according to the axis
+					// the attribute should deal with
+					XYSeriesCollection dataset = determineDatasetForAttribute(
+							chartStyle, attrIdx, datasets);
 
-    // Fill series for aggregated range attributes
-    statisticsForNormalization = calcStatisticsForAggregatedNormalization(
-                                                                          statisticsForAggregation,
-                                                                          chartStyle);
-    for (int attrIdx = 1; attrIdx < attrCount; attrIdx++) {
-      String yAttrName = chartStyle.getAttributeName(attrIdx);
-      AggregationFunction aggrFunc = chartStyle.getAttributeAggregation(attrIdx);
-      if (aggrFunc == null)
-        continue;
-      XYSeriesCollection dataset = determineDatasetForAttribute(chartStyle,attrIdx,datasets);
-      for (Comparable<?> xValue : statisticsForAggregation[attrIdx].keySet()) {
-        QuantileBin1D aggrStat = statisticsForAggregation[attrIdx].get(xValue);
-        Number yValue = getAggregationResult(aggrFunc, aggrStat);
+					// Fill series, if no aggregation function is defined.
+					// Otherwise fill statistic (dataset is filled later!)
+					if (chartStyle.getAttributeAggregation(attrIdx) == null) {
+						// Fill series
+						String yAttrName = chartStyle.getAttributeName(attrIdx);
+						// If grouping series attribute is used, add its value
+						// to the series ID
+						if (seriesID != null)
+							yAttrName = yAttrName + "_" + seriesID.toString();
 
-        // Normalize the aggregated value
-        if (chartStyle.isAttributeNormalized(attrIdx))
-          yValue = normalize(yValue, yAttrName, statisticsForNormalization);
+						XYSeries xySeries = JFreeChartUtil
+								.getOrAddSeriesFromDataset(dataset, yAttrName,
+										false, true);
+						xySeries.add(xValue, yValue);
 
-        // Fill series
-        XYSeries xySeries = JFreeChartUtil.getOrAddSeriesFromDataset(dataset, yAttrName, false, true);
-        xySeries.add((Number)xValue, yValue);
-        
-        // TODO: Mapping setzen
-        // Problem: Pro dataset item kann nur EINE FeatureID gesetzt
-        // werden (Feature2DatasetMapping, Zeile 124)
-        // Mapping between FID and data index in series
-        // mapping.setMapping(f.getID() ??, yAttrName,
-        // datasetIdx++);
-      }
-    }
-    return datasets;
-  }
-  
-  /**
-   * Calculates the attribute sum for all weight-aggregated range attributes
-   * grouped by the domain attribute values.<br>
-   * The function returns a {@link HashMap} for each range attribute, but only
-   * the maps of {@linkplain AggregationFunction#isWeighted() weight-aggregated}
-   * attributes contain the weight sums (for each domain attribute value).
-   * @param fc the feature collection the data is taken from
-   * @param chartStyle the chart style
-   * @return an array indexed for awhich contains a {@link HashMap} for
-   */
-  private static HashMap<Comparable<?>, Double>[] calcWeightSumForAggregation(
-      FeatureCollection fc, FeatureChartStyle chartStyle) {
-    int attrCount = chartStyle.getAttributeCount();
-    String domAttrName = chartStyle.getAttributeName(ChartStyle.DOMAIN_AXIS);
-    HashMap<Comparable<?>, Double>[] weightSumsForAggregation = new HashMap[attrCount];
-    for (int i = 1; i < attrCount; i++)
-      weightSumsForAggregation[i] = new HashMap<Comparable<?>, Double>();
+						// If grouping series attribute is used, and a
+						// "legend title" attribute
+						// is set, overwrite the legend label in the chart style
+						if (seriesID != null
+								&& chartStyle
+										.getSeriesLegendTitleAttributeName() != null) {
+							// determine the series index for the current series
+							// key
+							int seriesIdx = JFreeChartUtil
+									.getSeriesIndexFromDataset(dataset,
+											yAttrName);
+							// overwrite the series legend label in chart style
+							redefineSeriesLegendTitle(chartStyle, seriesIdx, f,
+									attrIdx);
+						}
 
-    // Iterate all features
-    FeatureIterator<SimpleFeature> features = fc.features();
-    try {
-      while (features.hasNext()) {
-        SimpleFeature f = features.next();
-        // ignore features with NODATA in domain attribute
-        Comparable<?> catValue = (Comparable<?>) f.getAttribute(domAttrName);
-        catValue = chartStyle.filterNoDataValue(ChartStyle.DOMAIN_AXIS,
-                                                catValue);
-        if (catValue == null)
-          continue;
+						// Mapping between FID and data index in series
+						// TODO: currently only for primary dataset! But in the
+						// future also
+						// the items in the secondary dataset must be mapped!!
+						if (dataset == datasets[0])
+							mapping.setMapping(f.getID(), yAttrName,
+									datasetIdx++);
+					} else {
+						QuantileBin1D aggrStat = statisticsForAggregation[attrIdx]
+								.get(xValue);
+						if (aggrStat == null) {
+							aggrStat = new DynamicBin1D();
+							statisticsForAggregation[attrIdx].put(
+									(xValue).doubleValue(), aggrStat);
+						}
+						aggrStat.add(yValue.doubleValue());
+						// TODO: Mapping vormerken (??)
+						// Problem: siehe unten
 
-        // Iterate the feature attributes
-        for (int attrIdx = 1; attrIdx < attrCount; attrIdx++) {
-          AggregationFunction attributeAggregation = chartStyle.getAttributeAggregation(attrIdx);
-          // Ignore
-          // - not aggregated attributes
-          // - not weighted aggregation attributes
-          if (attributeAggregation == null)
-            continue;
-          if (!attributeAggregation.isWeighted())
-            continue;
-          // weights must be defined for weighted aggregation
-          if (chartStyle.getAttributeAggregationWeightAttributeName(attrIdx) == null)
-            throw new RuntimeException(
-                JFreeChartUtil.R("Exception.noWeightAttributeDefinedforAWeightedAggregation"));
-          // ignore features with NODATA in range attribute
-          String yAttrName = chartStyle.getAttributeName(attrIdx);
-          Number yValue = (Number) chartStyle.filterNoDataValue(
-                                                                attrIdx,
-                                                                f.getAttribute(yAttrName));
-          if (yValue == null)
-            continue;
-          // ignore features with NODATA in weight attribute
-          String weightAttrName = chartStyle.getAttributeAggregationWeightAttributeName(attrIdx);
-          Number weight = (Number) chartStyle.filterWeightAttributeNoDataValue(
-                                                                               attrIdx,
-                                                                               f.getAttribute(weightAttrName));
-          if (weight == null)
-            continue;
+					}
+				}
+			}
+		} finally {
+			if (features != null) {
+				fc.close(features);
+			}
+		}
 
-          Double weightSum = weightSumsForAggregation[attrIdx].get(catValue);
-          if (weightSum == null)
-            weightSum = 0.0;
-          weightSum += weight.doubleValue();
-          weightSumsForAggregation[attrIdx].put(catValue, weightSum);
-        }
-      }
-    } finally {
-      if (features != null) {
-        // this is a hint
-        fc.close(features);
-      }
-    }
+		// Fill series for aggregated range attributes
+		statisticsForNormalization = calcStatisticsForAggregatedNormalization(
+				statisticsForAggregation, chartStyle);
+		for (int attrIdx = 1; attrIdx < attrCount; attrIdx++) {
+			String yAttrName = chartStyle.getAttributeName(attrIdx);
+			AggregationFunction aggrFunc = chartStyle
+					.getAttributeAggregation(attrIdx);
+			if (aggrFunc == null)
+				continue;
+			XYSeriesCollection dataset = determineDatasetForAttribute(
+					chartStyle, attrIdx, datasets);
+			for (Comparable<?> xValue : statisticsForAggregation[attrIdx]
+					.keySet()) {
+				QuantileBin1D aggrStat = statisticsForAggregation[attrIdx]
+						.get(xValue);
+				Number yValue = getAggregationResult(aggrFunc, aggrStat);
 
-    return weightSumsForAggregation;
-  }
+				// Normalize the aggregated value
+				if (chartStyle.isAttributeNormalized(attrIdx))
+					yValue = normalize(yValue, yAttrName,
+							statisticsForNormalization);
 
-  /**
-   * Calculates statistics needed to normalize data. If normalization is not
-   * used, this function returns an empty map.
-   * @param fc {@link FeatureCollection} where the data comes from
-   * @param chartStyle {@link ChartStyle} to determine which attributes shall be
-   *          normalized.
-   */
-  private static HashMap<String, QuantileBin1D> calcStatisticsForAggregatedNormalization(
-      HashMap<Comparable<?>, QuantileBin1D>[] aggrAttrValues,
-      FeatureChartStyle chartStyle) {
+				// Fill series
+				XYSeries xySeries = JFreeChartUtil.getOrAddSeriesFromDataset(
+						dataset, yAttrName, false, true);
+				xySeries.add((Number) xValue, yValue);
 
-    HashMap<String, QuantileBin1D> normStats = new HashMap<String, QuantileBin1D>();
+				// TODO: Mapping setzen
+				// Problem: Pro dataset item kann nur EINE FeatureID gesetzt
+				// werden (Feature2DatasetMapping, Zeile 124)
+				// Mapping between FID and data index in series
+				// mapping.setMapping(f.getID() ??, yAttrName,
+				// datasetIdx++);
+			}
+		}
+		return datasets;
+	}
 
-    for (int attrIdx = 0; attrIdx < chartStyle.getAttributeCount(); attrIdx++) {
-      if (aggrAttrValues[attrIdx] == null)
-        continue;
-      if (!chartStyle.isAttributeNormalized(attrIdx))
-        continue;
-      String attrName = chartStyle.getAttributeName(attrIdx);
-      QuantileBin1D stat = normStats.get(attrName);
-      if (stat == null) {
-        stat = new DynamicBin1D();
-        normStats.put(attrName, stat);
-      }
+	/**
+	 * Calculates the attribute sum for all weight-aggregated range attributes
+	 * grouped by the domain attribute values.<br>
+	 * The function returns a {@link HashMap} for each range attribute, but only
+	 * the maps of {@linkplain AggregationFunction#isWeighted()
+	 * weight-aggregated} attributes contain the weight sums (for each domain
+	 * attribute value).
+	 * 
+	 * @param fc
+	 *            the feature collection the data is taken from
+	 * @param chartStyle
+	 *            the chart style
+	 * @return an array indexed for awhich contains a {@link HashMap} for
+	 */
+	private static HashMap<Comparable<?>, Double>[] calcWeightSumForAggregation(
+			FeatureCollection fc, FeatureChartStyle chartStyle) {
+		int attrCount = chartStyle.getAttributeCount();
+		String domAttrName = chartStyle
+				.getAttributeName(ChartStyle.DOMAIN_AXIS);
+		HashMap<Comparable<?>, Double>[] weightSumsForAggregation = new HashMap[attrCount];
+		for (int i = 1; i < attrCount; i++)
+			weightSumsForAggregation[i] = new HashMap<Comparable<?>, Double>();
 
-      for (Comparable<?> catValue : aggrAttrValues[attrIdx].keySet())
-        stat.add(getAggregationResult(
-                                      chartStyle.getAttributeAggregation(attrIdx),
-                                      aggrAttrValues[attrIdx].get(catValue)));
-    }
+		// Iterate all features
+		FeatureIterator<SimpleFeature> features = fc.features();
+		try {
+			while (features.hasNext()) {
+				SimpleFeature f = features.next();
+				// ignore features with NODATA in domain attribute
+				Comparable<?> catValue = (Comparable<?>) f
+						.getAttribute(domAttrName);
+				catValue = chartStyle.filterNoDataValue(ChartStyle.DOMAIN_AXIS,
+						catValue);
+				if (catValue == null)
+					continue;
 
-    return normStats;
-  }
+				// Iterate the feature attributes
+				for (int attrIdx = 1; attrIdx < attrCount; attrIdx++) {
+					AggregationFunction attributeAggregation = chartStyle
+							.getAttributeAggregation(attrIdx);
+					// Ignore
+					// - not aggregated attributes
+					// - not weighted aggregation attributes
+					if (attributeAggregation == null)
+						continue;
+					if (!attributeAggregation.isWeighted())
+						continue;
+					// weights must be defined for weighted aggregation
+					if (chartStyle
+							.getAttributeAggregationWeightAttributeName(attrIdx) == null)
+						throw new RuntimeException(
+								JFreeChartUtil
+										.R("Exception.noWeightAttributeDefinedforAWeightedAggregation"));
+					// ignore features with NODATA in range attribute
+					String yAttrName = chartStyle.getAttributeName(attrIdx);
+					Number yValue = (Number) chartStyle.filterNoDataValue(
+							attrIdx, f.getAttribute(yAttrName));
+					if (yValue == null)
+						continue;
+					// ignore features with NODATA in weight attribute
+					String weightAttrName = chartStyle
+							.getAttributeAggregationWeightAttributeName(attrIdx);
+					Number weight = (Number) chartStyle
+							.filterWeightAttributeNoDataValue(attrIdx,
+									f.getAttribute(weightAttrName));
+					if (weight == null)
+						continue;
 
-  /**
-   * Calculates statistics needed to normalize data. If normalization is not
-   * used, this function returns an empty map.
-   * @param fc {@link FeatureCollection} where the data comes from
-   * @param chartStyle {@link ChartStyle} to determine which attributes shall be
-   *          normalized.
-   */
-  private static HashMap<String, QuantileBin1D> calcStatisticsForNotAggregatedNormalization(
-      FeatureCollection<SimpleFeatureType, SimpleFeature> fc,
-      FeatureChartStyle chartStyle) {
-    // NORMALIZATION:
-    // We have to create a descriptive statistic of all items before we can
-    // transform the first one. So we iterate over all items before we do
-    // optional sorting and insertion.
-    /**
-     * Holds the statistics needed to normalize the attribute values. Key =
-     * attributeName
-     */
-    HashMap<String, QuantileBin1D> attribStats = new HashMap<String, QuantileBin1D>();
+					Double weightSum = weightSumsForAggregation[attrIdx]
+							.get(catValue);
+					if (weightSum == null)
+						weightSum = 0.0;
+					weightSum += weight.doubleValue();
+					weightSumsForAggregation[attrIdx].put(catValue, weightSum);
+				}
+			}
+		} finally {
+			if (features != null) {
+				// this is a hint
+				fc.close(features);
+			}
+		}
 
-    // First check if any attribute needs normalization. If not we can
-    // skip the whole iteration
-    boolean doNormalization = false;
-    for (int attrIdx = 0; attrIdx < chartStyle.getAttributeCount(); attrIdx++) {
-      if (chartStyle.isAttributeNormalized(attrIdx) &&
-          chartStyle.getAttributeAggregation(attrIdx) == null) {
-        doNormalization = true;
-        break;
-      }
-    }
+		return weightSumsForAggregation;
+	}
 
-    if (doNormalization) {
-      final FeatureIterator<SimpleFeature> fIt = fc.features();
-      try {
+	/**
+	 * Calculates statistics needed to normalize data. If normalization is not
+	 * used, this function returns an empty map.
+	 * 
+	 * @param fc
+	 *            {@link FeatureCollection} where the data comes from
+	 * @param chartStyle
+	 *            {@link ChartStyle} to determine which attributes shall be
+	 *            normalized.
+	 */
+	private static HashMap<String, QuantileBin1D> calcStatisticsForAggregatedNormalization(
+			HashMap<Comparable<?>, QuantileBin1D>[] aggrAttrValues,
+			FeatureChartStyle chartStyle) {
 
-        // Loop over all features, and collect the attribute values we
-        // are interested in.
-        while (fIt.hasNext()) {
-          SimpleFeature f = fIt.next();
+		HashMap<String, QuantileBin1D> normStats = new HashMap<String, QuantileBin1D>();
 
-          for (int attrIdx = 0; attrIdx < chartStyle.getAttributeCount(); attrIdx++) {
-            if (chartStyle.isAttributeNormalized(attrIdx) &&
-                chartStyle.getAttributeAggregation(attrIdx) == null) {
+		for (int attrIdx = 0; attrIdx < chartStyle.getAttributeCount(); attrIdx++) {
+			if (aggrAttrValues[attrIdx] == null)
+				continue;
+			if (!chartStyle.isAttributeNormalized(attrIdx))
+				continue;
+			String attrName = chartStyle.getAttributeName(attrIdx);
+			QuantileBin1D stat = normStats.get(attrName);
+			if (stat == null) {
+				stat = new DynamicBin1D();
+				normStats.put(attrName, stat);
+			}
 
-              String attrName = chartStyle.getAttributeName(attrIdx);
+			for (Comparable<?> catValue : aggrAttrValues[attrIdx].keySet())
+				stat.add(getAggregationResult(
+						chartStyle.getAttributeAggregation(attrIdx),
+						aggrAttrValues[attrIdx].get(catValue)));
+		}
 
-              // SK: Since normalization is now usually set for
-              // all
-              // attributes (even when they are not numeric), we
-              // double check here, whether it is really a numeric
-              // attribute
-              if (!Number.class.isAssignableFrom(f.getFeatureType()
-                                                  .getDescriptor(attrName)
-                                                  .getType()
-                                                  .getBinding())) {
-                continue;
-              }
+		return normStats;
+	}
 
-              // Look for a cached version of the Statistics or
-              // create a new one
-              QuantileBin1D stat = attribStats.get(attrName);
-              if (stat == null) {
-                stat = new DynamicBin1D();
-                attribStats.put(attrName, stat);
-              }
+	/**
+	 * Calculates statistics needed to normalize data. If normalization is not
+	 * used, this function returns an empty map.
+	 * 
+	 * @param fc
+	 *            {@link FeatureCollection} where the data comes from
+	 * @param chartStyle
+	 *            {@link ChartStyle} to determine which attributes shall be
+	 *            normalized.
+	 */
+	private static HashMap<String, QuantileBin1D> calcStatisticsForNotAggregatedNormalization(
+			FeatureCollection<SimpleFeatureType, SimpleFeature> fc,
+			FeatureChartStyle chartStyle) {
+		// NORMALIZATION:
+		// We have to create a descriptive statistic of all items before we can
+		// transform the first one. So we iterate over all items before we do
+		// optional sorting and insertion.
+		/**
+		 * Holds the statistics needed to normalize the attribute values. Key =
+		 * attributeName
+		 */
+		HashMap<String, QuantileBin1D> attribStats = new HashMap<String, QuantileBin1D>();
 
-              // Filter out NoDataValues
-              Object rawValue = chartStyle.filterNoDataValue(
-                                                             attrIdx,
-                                                             f.getAttribute(attrName));
-              if (rawValue == null)
-                continue;
+		// First check if any attribute needs normalization. If not we can
+		// skip the whole iteration
+		boolean doNormalization = false;
+		for (int attrIdx = 0; attrIdx < chartStyle.getAttributeCount(); attrIdx++) {
+			if (chartStyle.isAttributeNormalized(attrIdx)
+					&& chartStyle.getAttributeAggregation(attrIdx) == null) {
+				doNormalization = true;
+				break;
+			}
+		}
 
-              final Double doubleValue = new Double(
-                  ((Number) rawValue).doubleValue());
+		if (doNormalization) {
+			final FeatureIterator<SimpleFeature> fIt = fc.features();
+			try {
 
-              stat.add(doubleValue);
-            }
-          }
-        }
-      } // Feature iterator
-      finally {
-        fc.close(fIt);
-      }
-    } // doNormlization
+				// Loop over all features, and collect the attribute values we
+				// are interested in.
+				while (fIt.hasNext()) {
+					SimpleFeature f = fIt.next();
 
-    return attribStats;
-  }
+					for (int attrIdx = 0; attrIdx < chartStyle
+							.getAttributeCount(); attrIdx++) {
+						if (chartStyle.isAttributeNormalized(attrIdx)
+								&& chartStyle.getAttributeAggregation(attrIdx) == null) {
 
-  /**
-   * Do a z-transformation on the data item.
-   * @param statisticsForNormalization The statistics needed for normalization.
-   * @see #calcStatisticsForNormalization(FeatureCollection, FeatureChartStyle,
-   *      int)
-   */
-  private static Number normalize(Number yValue, String attrName,
-      HashMap<String, QuantileBin1D> statisticsForNormalization) {
-    Number zValue;
+							String attrName = chartStyle
+									.getAttributeName(attrIdx);
 
-    // Variance and SD can only be calculated for n>1 elements in a category. In
-    // case of n = 1 it return NaN. We interpretet it as 0.0 here
-    if (Double.isNaN(yValue.doubleValue())) {
-      return 0.;
-    }
+							// SK: Since normalization is now usually set for
+							// all
+							// attributes (even when they are not numeric), we
+							// double check here, whether it is really a numeric
+							// attribute
+							if (!Number.class.isAssignableFrom(f
+									.getFeatureType().getDescriptor(attrName)
+									.getType().getBinding())) {
+								continue;
+							}
 
-    if (statisticsForNormalization.get(attrName) == null) {
-      throw new IllegalArgumentException(
-          "The statistics needed for the normlization need to be created before normalizing.");
-    }
+							// Look for a cached version of the Statistics or
+							// create a new one
+							QuantileBin1D stat = attribStats.get(attrName);
+							if (stat == null) {
+								stat = new DynamicBin1D();
+								attribStats.put(attrName, stat);
+							}
 
-    StaticBin1D stats = statisticsForNormalization.get(attrName);
+							// Filter out NoDataValues
+							Object rawValue = chartStyle.filterNoDataValue(
+									attrIdx, f.getAttribute(attrName));
+							if (rawValue == null)
+								continue;
 
-    zValue = (yValue.doubleValue() - stats.mean()) / stats.standardDeviation();
+							final Double doubleValue = new Double(
+									((Number) rawValue).doubleValue());
 
-    return zValue;
-  }
-static int a = 10000;
-  /**
-   * Creates a {@link CategoryDataset} for 2 (or more) attributes of a
-   * {@link FeatureCollection}. CategoryDataset can only be created for
-   * <b>numeric</b> range attributes. If the given style defines a
-   * secondary range axis for at least one attribute the method returns 2 datasets.
-   * @param fc a {@link FeatureCollection}
-   * @param style defines the attributes used to create the dataset from, as
-   *          well as the sorting and normalization properties
-   * @throws IllegalArgumentException if less then 2 attributes are specified
-   * @throws UnsupportedOperationException if attributes are not numeric
-   */
-  public static DefaultCategoryDataset[] createCategoryDataset(
-      FeatureCollection<SimpleFeatureType, SimpleFeature> fc,
-      FeatureChartStyle chartStyle) {
-    int attrCount = chartStyle.getAttributeCount();
-    if (attrCount < 2)
-      throw new IllegalArgumentException(
-          "FeatureChartStyle must define at least 2 attributes to create CategoryDataset: " +
-              attrCount);
-    String xAttrName = chartStyle.getAttributeName(ChartStyle.DOMAIN_AXIS);
-    // only check whether X attribute exists (numeric and not-numeric
-    // allowed)
-    checkAttributeType(fc.getSchema(), xAttrName, Comparable.class,
-                       "Domain attribute", "CategoryDataset");
-    // check data types of Y attribute (only numeric allowed)
-    for (int i = 1; i < attrCount; i++)
-      checkAttributeType(fc.getSchema(), chartStyle.getAttributeName(i),
-                         Number.class, "Range attribute", "CategoryDataset");
+							stat.add(doubleValue);
+						}
+					}
+				}
+			} // Feature iterator
+			finally {
+				fc.close(fIt);
+			}
+		} // doNormlization
 
-    // Calculate statistics to normalize not-aggregated attributes
-    HashMap<String, QuantileBin1D> statisticsForNormalization = calcStatisticsForNotAggregatedNormalization(
-                                                                                                            fc,
-                                                                                                            chartStyle);
-    // Calculate weight sums for all weight-aggregated attributes
-    HashMap<Comparable<?>, Double>[] weightSumsForAggregation = calcWeightSumForAggregation(
-                                                                                            fc,
-                                                                                            chartStyle);
-    // Prepare set for statistics to calculate aggregation functions (one
-    // statistic for each range attribute of each domain value)
-    HashMap<Comparable<?>, QuantileBin1D>[] statisticsForAggregation = new HashMap[attrCount];
-    for (int i = 1; i < attrCount; i++)
-      statisticsForAggregation[i] = new HashMap<Comparable<?>, QuantileBin1D>();
-    
-    // determine the count of dataset, which must be created (one
-    // for each range axis)
-    int datasetCount = chartStyle.determineRangeAxisCount();
+		return attribStats;
+	}
 
-    // Create a new dataset
-    DefaultCategoryDataset[] datasets = new DefaultCategoryDataset[datasetCount];
-    for (int i=0; i<datasets.length; i++)
-      datasets[i] = new DefaultCategoryDataset();
-    
-    // Initalize mapping between features and dataset
-    // TODO: mapping also necessary for secondary dataset!!    
-    Feature2CategoryDatasetMapping mapping = new Feature2CategoryDatasetMapping(
-        fc, datasets[0]);
-    datasets[0].setGroup(new FeatureDatasetMetaData(mapping));
+	/**
+	 * Do a z-transformation on the data item.
+	 * 
+	 * @param statisticsForNormalization
+	 *            The statistics needed for normalization.
+	 * @see #calcStatisticsForNormalization(FeatureCollection,
+	 *      FeatureChartStyle, int)
+	 */
+	private static Number normalize(Number yValue, String attrName,
+			HashMap<String, QuantileBin1D> statisticsForNormalization) {
+		Number zValue;
 
-    // If dataset should be sorted, the features must be sorted first
-    // according to the domain attribute. CategoryDataset has no
-    // "autoSort", and this - at all - would not work (see
-    // createXYDataset(..))
-    FeatureIterator<SimpleFeature> features = null;
-    Iterator<SimpleFeature> fi = null;
-    
-    
-    Iterator<SimpleFeature> fi2 = null; //TODO Martin Schmitz: Never used?!
-    
-    
-    if (chartStyle.isSortDomainAxis()) {
-      // Sorting attribute(s) -> Category attribute
-      String[] sortAttr = new String[] {xAttrName};
-      // If grouping is used so create series, also sort by
-      // this attribute, so that the series are also sorted
-      // "inside" every category
-      if ( chartStyle.getSeriesAttributeName() != null )
-        sortAttr = LangUtil.extendArray(sortAttr, chartStyle.getSeriesAttributeName());
-      Vector<SimpleFeature> sortedFeatures = FeatureUtil.sortFeatures(fc,sortAttr);
-      // Create an Iterator for the sorted Features
-      fi = sortedFeatures.iterator();
-      fi2 = sortedFeatures.iterator();
-    } else {
-      features = fc.features();
-      fi = new PipedFeatureIterator(features);
-      fi2 = new PipedFeatureIterator(features);
-    }
-    
-    // Iterate the FeatureCollection and fill the dataset
-    try {
-      for (int fIdx = 0; fi.hasNext(); fIdx++) {
-        SimpleFeature f = fi.next();
-        // Determine the category (NULL not permitted!)
-        Comparable<?> catValue = (Comparable<?>) f.getAttribute(xAttrName);
-        // Filter out NODATA values
-        catValue = chartStyle.filterNoDataValue(ChartStyle.DOMAIN_AXIS,
-                                                catValue);
-        if (catValue == null)
-          continue;
-        
-        // If grouping series attribute is used, filter out
-        // its no data values
-        Object seriesID = null;
-        if ( chartStyle.getSeriesAttributeName() != null ) {
-          seriesID = determineSeriesAttributeValueFromFeature(f, chartStyle);
-          if ( seriesID == null )
-            continue;
-        }
-        
-        // Determine the Y values and fill the dataset
-        for (int attrIdx = 1; attrIdx < attrCount; attrIdx++) {
-          Double yValue = determineRangeValueFromFeature(
-                                                         f,
-                                                         catValue,
-                                                         attrIdx,
-                                                         chartStyle,
-                                                         statisticsForNormalization,
-                                                         weightSumsForAggregation);
-          // Ignore feature if value is invalid
-          if (yValue == null) {
-            if ( chartStyle.isIgnoreNoDataRangeValues() )
-              continue;
-            else
-              yValue = 0.0;
-          }
-          // determine the dataset according to the axis
-          // the attribute should deal with
-          DefaultCategoryDataset dataset = determineDatasetForAttribute(chartStyle,attrIdx,datasets);
-          
-          // Fill series, if no aggregation function is defined.
-          // Otherwise fill statistic (dataset is filled later!)
-          if (chartStyle.getAttributeAggregation(attrIdx) == null) {
-            // Add data to dataset
-            String yAttrName = chartStyle.getAttributeName(attrIdx);
-            // If grouping series attribute is used, add its value
-            // to the series ID
-            if ( seriesID != null )
-              yAttrName = yAttrName + "_" + seriesID.toString();
-            dataset.setValue(yValue, yAttrName, catValue);
+		// Variance and SD can only be calculated for n>1 elements in a
+		// category. In
+		// case of n = 1 it return NaN. We interpretet it as 0.0 here
+		if (Double.isNaN(yValue.doubleValue())) {
+			return 0.;
+		}
 
-            //#########################
-            // Bei Bar-Charts mit mehreren Achsen ueberlagern sich
-            // die Balken der beiden Datasets, da die Balken unterschiedlicher
-            // Renderer nicht versetzt dargestellt werden, sondern einfach
-            // uebereinander!!
-            //
-            // Workaround (wie in JFreeChart-Demo "DualAxisDemo5"):
-            // Das Versetzen der Balken wird "simuliert", indem in allen
-            // Datasets Werte fuer ALLE Series hinterlegt werden.
-            // -> Im Haupt-Dataset wird der normale Wert hinterlegt
-            // -> In allen anderen Dataset wird NULL hintelegt, damit
-            //    kein Balken erscheint
-            if ( datasets.length > 1 && ChartType.BAR.equals(chartStyle.getType()) ) {
-              for (DefaultCategoryDataset d : datasets)
-                if ( d != dataset ) {
-                  d.setValue(null, JFreeChartUtil.DUMMY_SERIES_PREFIX+yAttrName, catValue);
-                }
-            }
-            //#########################
-            
-            // If grouping series attribute is used, and a "legend title" attribute
-            // is set, overwrite the legend label in the chart style 
-            if ( seriesID != null && chartStyle.getSeriesLegendTitleAttributeName() != null ) {
-              // determine the series index for the current series key
-              int seriesIdx = JFreeChartUtil.getSeriesIndexFromDataset(dataset,yAttrName);
-              // overwrite the series legend label in chart style
-              redefineSeriesLegendTitle(chartStyle, seriesIdx, f, attrIdx);
-            }
-            
-            // Mapping between FID and data index in series
-            // TODO: currently only for primary dataset! But in the future also
-            //       the items in the secondary dataset must be mapped!!
-            if ( dataset == datasets[0] )
-              mapping.setMapping(f.getID(), yAttrName, catValue);
-          } else {
-            QuantileBin1D aggrStat = statisticsForAggregation[attrIdx].get(catValue);
-            if (aggrStat == null) {
-              aggrStat = new DynamicBin1D();
-              statisticsForAggregation[attrIdx].put(catValue, aggrStat);
-            }
-            aggrStat.add(yValue.doubleValue());
-            // TODO: Mapping vormerken (??)
-            // Problem: siehe unten
-          }
-        }
-      }
-    } finally {
-      if (features != null) {
-        fc.close(features);
-      }
-    }
+		if (statisticsForNormalization.get(attrName) == null) {
+			throw new IllegalArgumentException(
+					"The statistics needed for the normlization need to be created before normalizing.");
+		}
 
-    // Fill series for aggregated range attributes
-    statisticsForNormalization = calcStatisticsForAggregatedNormalization(
-                                                                          statisticsForAggregation,
-                                                                          chartStyle);
-    for (int attrIdx = 1; attrIdx < attrCount; attrIdx++) {
-      String yAttrName = chartStyle.getAttributeName(attrIdx);
-      AggregationFunction aggrFunc = chartStyle.getAttributeAggregation(attrIdx);
-      if (aggrFunc == null)
-        continue;
-      DefaultCategoryDataset dataset = determineDatasetForAttribute(chartStyle,attrIdx,datasets);
-      for (Comparable<?> catValue : statisticsForAggregation[attrIdx].keySet()) {
-        QuantileBin1D aggrStat = statisticsForAggregation[attrIdx].get(catValue);
-        Number yValue = getAggregationResult(aggrFunc, aggrStat);
+		StaticBin1D stats = statisticsForNormalization.get(attrName);
 
-        // Normalize the aggregated value
-        if (chartStyle.isAttributeNormalized(attrIdx))
-          yValue = normalize(yValue, yAttrName, statisticsForNormalization);
+		zValue = (yValue.doubleValue() - stats.mean())
+				/ stats.standardDeviation();
 
-        // Fill series
-        dataset.addValue(yValue, yAttrName, catValue);
-        // TODO: Mapping setzen
-        // Problem: Pro dataset item kann nur EINE FeatureID gesetzt
-        // werden (Feature2DatasetMapping, Zeile 124)
-        // // Mapping between FID and data index in series
-        // mapping.setMapping(f.getID() ??, yAttrName,
-        // datasetIdx++);
-      }
-    }
+		return zValue;
+	}
 
-    return datasets;
-  }
+	static int a = 10000;
 
-  /**
-   * Creates a {@link PieDataset} for 2 attributes of a
-   * {@link FeatureCollection}. PieDataset can only be created for a
-   * <b>numeric</b> range attribute.
-   * @param fc a {@link FeatureCollection}
-   * @param style defines the attributes used to create the dataset from, as
-   *          well as the sorting and normalization properties
-   * @throws IllegalArgumentException if not exactly 2 attributes are specified
-   * @throws UnsupportedOperationException if attributes are not numeric
-   */
-  public static DefaultPieDataset createPieDataset(
-      FeatureCollection<SimpleFeatureType, SimpleFeature> fc,
-      FeatureChartStyle chartStyle) {
-    int attrCount = chartStyle.getAttributeCount();
-    if (attrCount != 2)
-      throw new IllegalArgumentException(
-          "FeatureChartStyle must define at exactly 2 attributes to create PieDataset: " +
-              attrCount);
-    
-    String catAttrName = chartStyle.getAttributeName(ChartStyle.DOMAIN_AXIS);
-    // check whether category attribute exists (numeric and not-numeric allowed)
-    checkAttributeType(fc.getSchema(), catAttrName, Comparable.class,
-                       "Domain attribute", "PieDataset");
-    // check data type of range attribute (only numeric allowed)
-    String rangeAttrName = chartStyle.getAttributeName(ChartStyle.RANGE_AXIS);
-    int    rangeAttrIdx  = ChartStyle.RANGE_AXIS;
-    checkAttributeType(fc.getSchema(), rangeAttrName, Number.class,
-                       "Range attribute", "PieDataset");
+	/**
+	 * Creates a {@link CategoryDataset} for 2 (or more) attributes of a
+	 * {@link FeatureCollection}. CategoryDataset can only be created for
+	 * <b>numeric</b> range attributes. If the given style defines a secondary
+	 * range axis for at least one attribute the method returns 2 datasets.
+	 * 
+	 * @param fc
+	 *            a {@link FeatureCollection}
+	 * @param style
+	 *            defines the attributes used to create the dataset from, as
+	 *            well as the sorting and normalization properties
+	 * @throws IllegalArgumentException
+	 *             if less then 2 attributes are specified
+	 * @throws UnsupportedOperationException
+	 *             if attributes are not numeric
+	 */
+	public static DefaultCategoryDataset[] createCategoryDataset(
+			FeatureCollection<SimpleFeatureType, SimpleFeature> fc,
+			FeatureChartStyle chartStyle) {
+		int attrCount = chartStyle.getAttributeCount();
+		if (attrCount < 2)
+			throw new IllegalArgumentException(
+					"FeatureChartStyle must define at least 2 attributes to create CategoryDataset: "
+							+ attrCount);
+		String xAttrName = chartStyle.getAttributeName(ChartStyle.DOMAIN_AXIS);
+		// only check whether X attribute exists (numeric and not-numeric
+		// allowed)
+		checkAttributeType(fc.getSchema(), xAttrName, Comparable.class,
+				"Domain attribute", "CategoryDataset");
+		// check data types of Y attribute (only numeric allowed)
+		for (int i = 1; i < attrCount; i++)
+			checkAttributeType(fc.getSchema(), chartStyle.getAttributeName(i),
+					Number.class, "Range attribute", "CategoryDataset");
 
-    // Calculate statistics to normalize not-aggregated attributes
-    HashMap<String, QuantileBin1D> statisticsForNormalization = calcStatisticsForNotAggregatedNormalization(
-                                                                                                            fc,
-                                                                                                            chartStyle);
-    // Calculate weight sums for all weight-aggregated attributes
-    HashMap<Comparable<?>, Double>[] weightSumsForAggregation = calcWeightSumForAggregation(
-                                                                                            fc,
-                                                                                            chartStyle);
-    // Prepare set for statistics to calculate aggregation functions (one
-    // statistic for each range attribute of each domain value)
-    HashMap<Comparable<?>, QuantileBin1D> statisticsForAggregation = new HashMap<Comparable<?>, QuantileBin1D>();
+		// Calculate statistics to normalize not-aggregated attributes
+		HashMap<String, QuantileBin1D> statisticsForNormalization = calcStatisticsForNotAggregatedNormalization(
+				fc, chartStyle);
+		// Calculate weight sums for all weight-aggregated attributes
+		HashMap<Comparable<?>, Double>[] weightSumsForAggregation = calcWeightSumForAggregation(
+				fc, chartStyle);
+		// Prepare set for statistics to calculate aggregation functions (one
+		// statistic for each range attribute of each domain value)
+		HashMap<Comparable<?>, QuantileBin1D>[] statisticsForAggregation = new HashMap[attrCount];
+		for (int i = 1; i < attrCount; i++)
+			statisticsForAggregation[i] = new HashMap<Comparable<?>, QuantileBin1D>();
 
-    // Create a new dataset and insert the series
-    DefaultPieDataset dataset = new DefaultPieDataset();
-    Feature2PieDatasetMapping mapping = new Feature2PieDatasetMapping(
-        fc, dataset);
-    dataset.setGroup(new FeatureDatasetMetaData(mapping));
+		// determine the count of dataset, which must be created (one
+		// for each range axis)
+		int datasetCount = chartStyle.determineRangeAxisCount();
 
-    // If dataset should be sorted, the features must be sorted first
-    // according to the domain attribute. The sort functionality can not be
-    // used because of the lost of the "data item to feature"-mapping (see
-    // createXYDataset(..))
-    FeatureIterator<SimpleFeature> features = null;
-    Iterator<SimpleFeature> fi = null;
-    if (chartStyle.isSortDomainAxis()) {
-      Vector<SimpleFeature> sortedFeatures = FeatureUtil.sortFeatures(fc,
-                                                                      catAttrName);
-      // Create an Iterator for the sorted Features
-      fi = sortedFeatures.iterator();
-    } else {
-      features = fc.features();
-      fi = new PipedFeatureIterator(features);
-    }
+		// Create a new dataset
+		DefaultCategoryDataset[] datasets = new DefaultCategoryDataset[datasetCount];
+		for (int i = 0; i < datasets.length; i++)
+			datasets[i] = new DefaultCategoryDataset();
 
-    // Iterate the FeatureCollection and fill the dataset
-    try {
-      for (int fIdx = 0; fi.hasNext(); fIdx++) {
-        SimpleFeature f = fi.next();
-        // Determine the category (NULL not permitted!)
-        Comparable<?> catValue = (Comparable<?>) f.getAttribute(catAttrName);
-        // Filter out NODATA values
-        catValue = chartStyle.filterNoDataValue(ChartStyle.DOMAIN_AXIS,
-                                                catValue);
-        if (catValue == null)
-          continue;
-        
-// SERIES GROUPING IS NOT SUPPORTED FOR PIE CHARTS BECAUSE THERE IST
-// ONLY ONE SERIES
-        // If grouping series attribute is used, filter out
-        // its no data values
-        Object seriesID = null;
-//        if ( chartStyle.getSeriesAttributeName() != null ) {
-//          seriesID = determineSeriesAttributeValueFromFeature(f, chartStyle);
-//          if ( seriesID == null )
-//            continue;
-//        }
-        
-        // Determine the pie piece value and fill the dataset
-        Double rangeValue = determineRangeValueFromFeature(
-                                                       f,
-                                                       catValue,
-                                                       rangeAttrIdx, 
-                                                       chartStyle,
-                                                       statisticsForNormalization,
-                                                       weightSumsForAggregation);
-        // Ignore feature if value is invalid
-        if (rangeValue == null) {
-          if ( chartStyle.isIgnoreNoDataRangeValues() )
-            continue;
-          else
-            rangeValue = 0.0;
-        }
+		// Initalize mapping between features and dataset
+		// TODO: mapping also necessary for secondary dataset!!
+		Feature2CategoryDatasetMapping mapping = new Feature2CategoryDatasetMapping(
+				fc, datasets[0]);
+		datasets[0].setGroup(new FeatureDatasetMetaData(mapping));
 
-        // Fill series, if no aggregation function is defined.
-        // Otherwise fill statistic (dataset is filled later!)
-        if (chartStyle.getAttributeAggregation(rangeAttrIdx) == null) {
-          // Add data to dataset
-// SERIES GROUPING IS NOT SUPPORTED FOR PIE CHARTS BECAUSE THERE IST
-// ONLY ONE SERIES
-//          // If grouping series attribute is used, add its value
-//          // to the series ID
-//          if ( seriesID != null )
-//            yAttrName = yAttrName + "_" + seriesID.toString();
-          dataset.setValue(catValue,rangeValue);
-          // Mapping between FID and data item
-          mapping.setMapping(f.getID(), catValue);
-        } else {
-          QuantileBin1D aggrStat = statisticsForAggregation.get(catValue);
-          if (aggrStat == null) {
-            aggrStat = new DynamicBin1D();
-            statisticsForAggregation.put(catValue, aggrStat);
-          }
-          aggrStat.add(rangeValue.doubleValue());
-          // TODO: Mapping vormerken (??)
-          // Problem: siehe unten
-        }
-      }
-    } finally {
-      if (features != null) {
-        fc.close(features);
-      }
-    }
+		// If dataset should be sorted, the features must be sorted first
+		// according to the domain attribute. CategoryDataset has no
+		// "autoSort", and this - at all - would not work (see
+		// createXYDataset(..))
+		FeatureIterator<SimpleFeature> features = null;
+		Iterator<SimpleFeature> fi = null;
 
-    // Fill series for aggregated range attributes
-    statisticsForNormalization = calcStatisticsForAggregatedNormalization(
-                                                                          new HashMap[] { statisticsForAggregation },
-                                                                          chartStyle);
-    AggregationFunction aggrFunc = chartStyle.getAttributeAggregation(rangeAttrIdx);
-    if (aggrFunc != null) {
-      for (Comparable<?> catValue : statisticsForAggregation.keySet()) {
-        QuantileBin1D aggrStat = statisticsForAggregation.get(catValue);
-        Number rangeValue = getAggregationResult(aggrFunc, aggrStat);
-  
-        // Normalize the aggregated value
-        if (chartStyle.isAttributeNormalized(rangeAttrIdx))
-          rangeValue = normalize(rangeValue, rangeAttrName, statisticsForNormalization);
-  
-        // Fill series
-        dataset.setValue(catValue, rangeValue);
-        // TODO: Mapping setzen
-        // Problem: Pro dataset item kann nur EINE FeatureID gesetzt
-        // werden (Feature2DatasetMapping, Zeile 124)
-        // // Mapping between FID and data index in series
-        // mapping.setMapping(f.getID() ??, yAttrName,
-        // datasetIdx++);
-      }
-    }
-    
-    return dataset;
-  }
+		if (chartStyle.isSortDomainAxis()) {
+			// Sorting attribute(s) -> Category attribute
+			String[] sortAttr = new String[] { xAttrName };
+			// If grouping is used so create series, also sort by
+			// this attribute, so that the series are also sorted
+			// "inside" every category
+			if (chartStyle.getSeriesAttributeName() != null)
+				sortAttr = LangUtil.extendArray(sortAttr,
+						chartStyle.getSeriesAttributeName());
+			Vector<SimpleFeature> sortedFeatures = FeatureUtil.sortFeatures(fc,
+					sortAttr);
+			// Create an Iterator for the sorted Features
+			fi = sortedFeatures.iterator();
+		} else {
+			features = fc.features();
+			fi = new PipedFeatureIterator(features);
+		}
 
-  /**
-   * Changes the series legend label in a chart style according to the
-   * value of the {@linkplain FeatureChartStyle#getSeriesLegendTitleAttributeName() legend label attribute}
-   * defined in the chart style. <br>
-   * Does nothing if the chart style does not define a series legend label
-   * attribute or the attribute value is {@code null} or empty.
-   * 
-   * @param chartStyle the chart style to update the legend title in
-   * @param seriesIdx  the series index in the {@link ChartRendererStyle} to update
-   * @param f          the feature the legend label is taken from
-   * @param attrIdx    the attribute index in the {@link ChartStyle}
-   * @see FeatureChartStyle#getSeriesLegendTitleAttributeName()
-   */
-  private static boolean redefineSeriesLegendTitle(FeatureChartStyle chartStyle, int seriesIdx, SimpleFeature f, int attrIdx) {
-    if ( chartStyle.getSeriesLegendTitleAttributeName() == null )
-      return false;
-    
-    Object seriesLabel = f.getAttribute(chartStyle.getSeriesLegendTitleAttributeName());
-    // only apply the new legend label, if the "title attribute" value is
-    // not null and not empty
-    if ( seriesLabel == null )
-      return false;
-    String seriesLabelStr = seriesLabel.toString().trim();
-    if ( "".equals(seriesLabelStr) )
-      return false;
-    
-    // Determine the renderer (primary or secondary range axis!) for
-    // the series
-    int                rendID = determineDatasetIndexForAttribute(chartStyle, attrIdx);
-    ChartRendererStyle rendStyle = chartStyle.getRendererStyle(rendID);
-    // Create a renderer style if it does not exist yet
-    if ( rendStyle == null ) {
-      rendStyle = new ChartRendererStyle();
-      chartStyle.setRendererStyle(rendID, rendStyle);
-    }
-    // Do not change the label if the existing label equals the
-    // new label
-    if ( seriesLabel.equals( rendStyle.getSeriesLegendLabel(seriesIdx) ) )
-      return false;
-     
-    // redefine the legend label for the given series
-    rendStyle.setSeriesLegendLabel( seriesIdx, new ChartLabelStyle(seriesLabelStr) );
-    return true;
-  }
-  
-  
+		// Iterate the FeatureCollection and fill the dataset
+		try {
+			for (int fIdx = 0; fi.hasNext(); fIdx++) {
+				SimpleFeature f = fi.next();
+				// Determine the category (NULL not permitted!)
+				Comparable<?> catValue = (Comparable<?>) f
+						.getAttribute(xAttrName);
+				// Filter out NODATA values
+				catValue = chartStyle.filterNoDataValue(ChartStyle.DOMAIN_AXIS,
+						catValue);
+				if (catValue == null)
+					continue;
 
-  /**
-   * After loading from XML, a {@link FeatureChartStyle} contains whatever is
-   * written in the XML. But the DBF Schema can change quickly by accident! This
-   * method checks an {@link FeatureChartStyle} against a schema and corrects
-   * upperCase/lowerCase problems where possible. Returns <code>false</code> if
-   * attributes had to be removed from the {@link FeatureChartStyle}.
-   * @author SK
-   */
-  public static boolean correctAttributeNames(
-      FeatureChartStyle featureChartStyle, SimpleFeatureType schema) {
+				// If grouping series attribute is used, filter out
+				// its no data values
+				Object seriesID = null;
+				if (chartStyle.getSeriesAttributeName() != null) {
+					seriesID = determineSeriesAttributeValueFromFeature(f,
+							chartStyle);
+					if (seriesID == null)
+						continue;
+				}
 
-    ArrayList<Integer> willRemove = new ArrayList<Integer>();
+				// Determine the Y values and fill the dataset
+				for (int attrIdx = 1; attrIdx < attrCount; attrIdx++) {
+					Double yValue = determineRangeValueFromFeature(f, catValue,
+							attrIdx, chartStyle, statisticsForNormalization,
+							weightSumsForAggregation);
+					// Ignore feature if value is invalid
+					if (yValue == null) {
+						if (chartStyle.isIgnoreNoDataRangeValues())
+							continue;
+						else
+							yValue = 0.0;
+					}
+					// determine the dataset according to the axis
+					// the attribute should deal with
+					DefaultCategoryDataset dataset = determineDatasetForAttribute(
+							chartStyle, attrIdx, datasets);
 
-    // 1. Check.. all attributes in the atm should be in the schema as well.
-    // maybe correct some upperCase/loweCase stuff
-    for (int idx = 0; idx < featureChartStyle.getAttributeCount(); idx++) {
+					// Fill series, if no aggregation function is defined.
+					// Otherwise fill statistic (dataset is filled later!)
+					if (chartStyle.getAttributeAggregation(attrIdx) == null) {
+						// Add data to dataset
+						String yAttrName = chartStyle.getAttributeName(attrIdx);
+						// If grouping series attribute is used, add its value
+						// to the series ID
+						if (seriesID != null)
+							yAttrName = yAttrName + "_" + seriesID.toString();
+						dataset.setValue(yValue, yAttrName, catValue);
 
-      // Check the main attribute
-      String attributeName = featureChartStyle.getAttributeName(idx);
+						// #########################
+						// Bei Bar-Charts mit mehreren Achsen ueberlagern sich
+						// die Balken der beiden Datasets, da die Balken
+						// unterschiedlicher
+						// Renderer nicht versetzt dargestellt werden, sondern
+						// einfach
+						// uebereinander!!
+						//
+						// Workaround (wie in JFreeChart-Demo "DualAxisDemo5"):
+						// Das Versetzen der Balken wird "simuliert", indem in
+						// allen
+						// Datasets Werte fuer ALLE Series hinterlegt werden.
+						// -> Im Haupt-Dataset wird der normale Wert hinterlegt
+						// -> In allen anderen Dataset wird NULL hintelegt,
+						// damit
+						// kein Balken erscheint
+						if (datasets.length > 1
+								&& ChartType.BAR.equals(chartStyle.getType())) {
+							for (DefaultCategoryDataset d : datasets)
+								if (d != dataset) {
+									d.setValue(null,
+											JFreeChartUtil.DUMMY_SERIES_PREFIX
+													+ yAttrName, catValue);
+								}
+						}
+						// #########################
 
-      AttributeDescriptor foundDescr = schema.getDescriptor(attributeName);
-      if (foundDescr == null) {
-        Name bestMatch = FeatureUtil.findBestMatchingAttribute(schema,
-                                                               attributeName);
-        if (bestMatch == null) {
-          willRemove.add(idx);
-          continue;
-        } else
-          featureChartStyle.setAttributeName(idx, bestMatch.getLocalPart());
-      }
+						// If grouping series attribute is used, and a
+						// "legend title" attribute
+						// is set, overwrite the legend label in the chart style
+						if (seriesID != null
+								&& chartStyle
+										.getSeriesLegendTitleAttributeName() != null) {
+							// determine the series index for the current series
+							// key
+							int seriesIdx = JFreeChartUtil
+									.getSeriesIndexFromDataset(dataset,
+											yAttrName);
+							// overwrite the series legend label in chart style
+							redefineSeriesLegendTitle(chartStyle, seriesIdx, f,
+									attrIdx);
+						}
 
-      // Check the optional weight attribute value
-      if (idx > 0) {
-        String weightAtt = featureChartStyle.getAttributeAggregationWeightAttributeName(idx);
-        if (weightAtt != null) {
-          AttributeDescriptor foundWeightDescr = schema.getDescriptor(attributeName);
-          if (foundWeightDescr == null) {
-            Name bestMatch = FeatureUtil.findBestMatchingAttribute(schema,
-                                                                   weightAtt);
-            if (bestMatch == null) {
-              featureChartStyle.setAttributeAggregationWeightAttributeName(idx,
-                                                                           null);
-            } else
-              featureChartStyle.setAttributeAggregationWeightAttributeName(
-                                                                           idx,
-                                                                           bestMatch.getLocalPart());
-          }
-        }
-      }
-    }
+						// Mapping between FID and data index in series
+						// TODO: currently only for primary dataset! But in the
+						// future also
+						// the items in the secondary dataset must be mapped!!
+						if (dataset == datasets[0])
+							mapping.setMapping(f.getID(), yAttrName, catValue);
+					} else {
+						QuantileBin1D aggrStat = statisticsForAggregation[attrIdx]
+								.get(catValue);
+						if (aggrStat == null) {
+							aggrStat = new DynamicBin1D();
+							statisticsForAggregation[attrIdx].put(catValue,
+									aggrStat);
+						}
+						aggrStat.add(yValue.doubleValue());
+						// TODO: Mapping vormerken (??)
+						// Problem: siehe unten
+					}
+				}
+			}
+		} finally {
+			if (features != null) {
+				fc.close(features);
+			}
+		}
 
-    // Remove the ones that were not findable in the schema
-    for (Integer removeIdx : willRemove) {
-      featureChartStyle.removeAttribute(removeIdx);
-    }
+		// Fill series for aggregated range attributes
+		statisticsForNormalization = calcStatisticsForAggregatedNormalization(
+				statisticsForAggregation, chartStyle);
+		for (int attrIdx = 1; attrIdx < attrCount; attrIdx++) {
+			String yAttrName = chartStyle.getAttributeName(attrIdx);
+			AggregationFunction aggrFunc = chartStyle
+					.getAttributeAggregation(attrIdx);
+			if (aggrFunc == null)
+				continue;
+			DefaultCategoryDataset dataset = determineDatasetForAttribute(
+					chartStyle, attrIdx, datasets);
+			for (Comparable<?> catValue : statisticsForAggregation[attrIdx]
+					.keySet()) {
+				QuantileBin1D aggrStat = statisticsForAggregation[attrIdx]
+						.get(catValue);
+				Number yValue = getAggregationResult(aggrFunc, aggrStat);
 
-    // * Returns false if attributes had to be removed from the {@link
-    // FeatureChartStyle}.
-    return willRemove.isEmpty();
-  }
+				// Normalize the aggregated value
+				if (chartStyle.isAttributeNormalized(attrIdx))
+					yValue = normalize(yValue, yAttrName,
+							statisticsForNormalization);
 
-  /**
-   * Passes the NODATA Values stored in the {@link AttributeMetadataMap} to the
-   * attributes (and optional weighting attributes) of the {@link ChartStyle}.
-   */
-  public static void passNoDataValues(
-      AttributeMetadataMap attributeMetaDataMap,
-      FeatureChartStyle featureChartStyle) {
+				// Fill series
+				dataset.addValue(yValue, yAttrName, catValue);
+				// TODO: Mapping setzen
+				// Problem: Pro dataset item kann nur EINE FeatureID gesetzt
+				// werden (Feature2DatasetMapping, Zeile 124)
+				// // Mapping between FID and data index in series
+				// mapping.setMapping(f.getID() ??, yAttrName,
+				// datasetIdx++);
+			}
+		}
 
-    // Pass the NODATA values for the attributes to the ChartStyle
-    for (int idx = 0; idx < featureChartStyle.getAttributeCount(); idx++) {
+		return datasets;
+	}
 
-      String attributeName = featureChartStyle.getAttributeName(idx);
-      AttributeMetadataInterface attributeMetadata = attributeMetaDataMap.get(attributeName);
+	/**
+	 * Creates a {@link PieDataset} for 2 attributes of a
+	 * {@link FeatureCollection}. PieDataset can only be created for a
+	 * <b>numeric</b> range attribute.
+	 * 
+	 * @param fc
+	 *            a {@link FeatureCollection}
+	 * @param style
+	 *            defines the attributes used to create the dataset from, as
+	 *            well as the sorting and normalization properties
+	 * @throws IllegalArgumentException
+	 *             if not exactly 2 attributes are specified
+	 * @throws UnsupportedOperationException
+	 *             if attributes are not numeric
+	 */
+	public static DefaultPieDataset createPieDataset(
+			FeatureCollection<SimpleFeatureType, SimpleFeature> fc,
+			FeatureChartStyle chartStyle) {
+		int attrCount = chartStyle.getAttributeCount();
+		if (attrCount != 2)
+			throw new IllegalArgumentException(
+					"FeatureChartStyle must define at exactly 2 attributes to create PieDataset: "
+							+ attrCount);
 
-      if (attributeMetadata != null) {
-        featureChartStyle.setNoDataValues(idx,
-                                          attributeMetadata.getNodataValues());
-      }
+		String catAttrName = chartStyle
+				.getAttributeName(ChartStyle.DOMAIN_AXIS);
+		// check whether category attribute exists (numeric and not-numeric
+		// allowed)
+		checkAttributeType(fc.getSchema(), catAttrName, Comparable.class,
+				"Domain attribute", "PieDataset");
+		// check data type of range attribute (only numeric allowed)
+		String rangeAttrName = chartStyle
+				.getAttributeName(ChartStyle.RANGE_AXIS);
+		int rangeAttrIdx = ChartStyle.RANGE_AXIS;
+		checkAttributeType(fc.getSchema(), rangeAttrName, Number.class,
+				"Range attribute", "PieDataset");
 
-      // Set NODATA-Values for the weight attributes
-      String weightAtt = featureChartStyle.getAttributeAggregationWeightAttributeName(idx);
-      if (weightAtt != null) {
-        AttributeMetadataInterface weightAttributeMetadata = attributeMetaDataMap.get(attributeName);
-        if (weightAttributeMetadata != null) {
-          featureChartStyle.setWeightAttributeNoDataValues(
-                                                           idx,
-                                                           weightAttributeMetadata.getNodataValues());
-        }
-      }
-    }
-  }
+		// Calculate statistics to normalize not-aggregated attributes
+		HashMap<String, QuantileBin1D> statisticsForNormalization = calcStatisticsForNotAggregatedNormalization(
+				fc, chartStyle);
+		// Calculate weight sums for all weight-aggregated attributes
+		HashMap<Comparable<?>, Double>[] weightSumsForAggregation = calcWeightSumForAggregation(
+				fc, chartStyle);
+		// Prepare set for statistics to calculate aggregation functions (one
+		// statistic for each range attribute of each domain value)
+		HashMap<Comparable<?>, QuantileBin1D> statisticsForAggregation = new HashMap<Comparable<?>, QuantileBin1D>();
 
-  /**
-   * Returns the result of the function from a statistic.
-   * @param statistics Statistic to take the result from.
-   */
-  private static Double getAggregationResult(AggregationFunction func,
-      QuantileBin1D statistics) {
-    switch (func) {
-      case AVG:
-        return statistics.mean();
-      case MEDIAN:
-        return statistics.median();
-      case MAX:
-        return statistics.max();
-      case MIN:
-        return statistics.min();
-      case SUM_ABS: // assumes that statistic is already filled properly
-      case SUM_WEIGHTED: // assumes that statistic is already filled properly
-      case AVG_WEIGHTED: // assumes that statistic is already filled properly
-      case SUM:
-        return statistics.sum();
-      case COUNT:
-        return ((Integer) statistics.size()).doubleValue();
-      case STND_DEV:
-        return statistics.size() > 1 ? statistics.standardDeviation() : 0.;
-      case VARIANCE:
-        return statistics.size() > 1 ? statistics.variance() : 0.;
-    }
-    throw new UnsupportedOperationException(
-        "Aggregation function not supported in " +
-            LangUtil.getSimpleClassName(statistics) + ": " + func);
-  }
+		// Create a new dataset and insert the series
+		DefaultPieDataset dataset = new DefaultPieDataset();
+		Feature2PieDatasetMapping mapping = new Feature2PieDatasetMapping(fc,
+				dataset);
+		dataset.setGroup(new FeatureDatasetMetaData(mapping));
 
-  /**
-   * Determines the dataset for an attribute of the style.
-   * @param chartStyle the chart style which defines the attribute
-   * @param attrIdx    the attribute for which the dataset should be
-   *                   determined
-   * @param datasets   the available datasets
-   */
-  private static <D extends Dataset> D determineDatasetForAttribute(FeatureChartStyle chartStyle, int attrIdx, D[] datasets) {
-    int datasetIdx = determineDatasetIndexForAttribute(chartStyle, attrIdx);
-    return datasets[datasetIdx];
-  }
+		// If dataset should be sorted, the features must be sorted first
+		// according to the domain attribute. The sort functionality can not be
+		// used because of the lost of the "data item to feature"-mapping (see
+		// createXYDataset(..))
+		FeatureIterator<SimpleFeature> features = null;
+		Iterator<SimpleFeature> fi = null;
+		if (chartStyle.isSortDomainAxis()) {
+			Vector<SimpleFeature> sortedFeatures = FeatureUtil.sortFeatures(fc,
+					catAttrName);
+			// Create an Iterator for the sorted Features
+			fi = sortedFeatures.iterator();
+		} else {
+			features = fc.features();
+			fi = new PipedFeatureIterator(features);
+		}
 
-  /**
-   * Determines the dataset index for an attribute of the style.
-   * @param chartStyle the chart style which defines the attribute
-   * @param attrIdx    the attribute for which the dataset should be
-   *                   determined
-   */
-  private static int determineDatasetIndexForAttribute(FeatureChartStyle chartStyle, int attrIdx) {
-    int axis = chartStyle.getAttributeAxis(attrIdx);
-    int datasetIdx = 0;
-    switch ( axis ) {
-      case ChartStyle.RANGE_AXIS: // primary dataset
-                                  datasetIdx = 0;
-                                  break;
-      case ChartStyle.RANGE_AXIS2: // secondary dataset
-                                  datasetIdx = 1;
-                                  break;
-      default: // should not occur, but...
-        throw new UnsupportedOperationException("Unknown range axis: "+axis);
-    }
-    
-    return datasetIdx;
-  }
+		// Iterate the FeatureCollection and fill the dataset
+		try {
+			for (int fIdx = 0; fi.hasNext(); fIdx++) {
+				SimpleFeature f = fi.next();
+				// Determine the category (NULL not permitted!)
+				Comparable<?> catValue = (Comparable<?>) f
+						.getAttribute(catAttrName);
+				// Filter out NODATA values
+				catValue = chartStyle.filterNoDataValue(ChartStyle.DOMAIN_AXIS,
+						catValue);
+				if (catValue == null)
+					continue;
 
-  /**
-   * Determines the range attribute value from a feature and transforms it
-   * according to the (optional) aggregation function or normalization.
-   * @param feature a feature
-   * @param domValue the domain value (category or x-value)
-   * @param attrIdx the index of the range attribute
-   * @param chartStyle the chart style
-   * @param statisticsForNormalization the pre-calculated statistics used for
-   *          normalization
-   * @param weightSumsForAggregation the pre-calculated sums of the (optional)
-   *          weight attribute values
-   * @return {@code null} if the feature must be ignored because of an invalid
-   *         (null) range or weight value
-   */
-  private static Double determineRangeValueFromFeature(SimpleFeature feature,
-      Comparable<?> domValue, int attrIdx, FeatureChartStyle chartStyle,
-      HashMap<String, QuantileBin1D> statisticsForNormalization,
-      HashMap<Comparable<?>, Double>[] weightSumsForAggregation) {
+				// SERIES GROUPING IS NOT SUPPORTED FOR PIE CHARTS BECAUSE THERE
+				// IST
+				// ONLY ONE SERIES
+				// If grouping series attribute is used, filter out
+				// its no data values
+				Object seriesID = null;
+				// if ( chartStyle.getSeriesAttributeName() != null ) {
+				// seriesID = determineSeriesAttributeValueFromFeature(f,
+				// chartStyle);
+				// if ( seriesID == null )
+				// continue;
+				// }
 
-    AggregationFunction aggrFunc = chartStyle.getAttributeAggregation(attrIdx);
-    String yAttrName = chartStyle.getAttributeName(attrIdx);
-    Number yValue = (Number) feature.getAttribute(yAttrName);
-    // Filter out NoDataValues
-    yValue = chartStyle.filterNoDataValue(attrIdx, yValue);
-    if (yValue == null)
-      return null;
+				// Determine the pie piece value and fill the dataset
+				Double rangeValue = determineRangeValueFromFeature(f, catValue,
+						rangeAttrIdx, chartStyle, statisticsForNormalization,
+						weightSumsForAggregation);
+				// Ignore feature if value is invalid
+				if (rangeValue == null) {
+					if (chartStyle.isIgnoreNoDataRangeValues())
+						continue;
+					else
+						rangeValue = 0.0;
+				}
 
-    // if no aggregation is set, the range value is normalized
-    // immediately
-    if (aggrFunc == null) {
-      // Normalization of a range axis value
-      if (chartStyle.isAttributeNormalized(attrIdx))
-        yValue = normalize(yValue, yAttrName, statisticsForNormalization);
-      return yValue.doubleValue();
-    }
+				// Fill series, if no aggregation function is defined.
+				// Otherwise fill statistic (dataset is filled later!)
+				if (chartStyle.getAttributeAggregation(rangeAttrIdx) == null) {
+					// Add data to dataset
+					// SERIES GROUPING IS NOT SUPPORTED FOR PIE CHARTS BECAUSE
+					// THERE IST
+					// ONLY ONE SERIES
+					// // If grouping series attribute is used, add its value
+					// // to the series ID
+					// if ( seriesID != null )
+					// yAttrName = yAttrName + "_" + seriesID.toString();
+					dataset.setValue(catValue, rangeValue);
+					// Mapping between FID and data item
+					mapping.setMapping(f.getID(), catValue);
+				} else {
+					QuantileBin1D aggrStat = statisticsForAggregation
+							.get(catValue);
+					if (aggrStat == null) {
+						aggrStat = new DynamicBin1D();
+						statisticsForAggregation.put(catValue, aggrStat);
+					}
+					aggrStat.add(rangeValue.doubleValue());
+					// TODO: Mapping vormerken (??)
+					// Problem: siehe unten
+				}
+			}
+		} finally {
+			if (features != null) {
+				fc.close(features);
+			}
+		}
 
-    // Determine weight
-    Number weight = null;
-    if (aggrFunc.isWeighted()) {
-      String weightAttrName = chartStyle.getAttributeAggregationWeightAttributeName(attrIdx);
-      weight = (Number) feature.getAttribute(weightAttrName);
-      // Filter out NoDataValues
-      weight = chartStyle.filterWeightAttributeNoDataValue(attrIdx, weight);
-    }
-    // Transform value
-    yValue = transformValueForAggregation(
-                                          aggrFunc,
-                                          yValue,
-                                          weight,
-                                          weightSumsForAggregation[attrIdx].get(domValue));
-    return yValue.doubleValue();
-  }
+		// Fill series for aggregated range attributes
+		statisticsForNormalization = calcStatisticsForAggregatedNormalization(
+				new HashMap[] { statisticsForAggregation }, chartStyle);
+		AggregationFunction aggrFunc = chartStyle
+				.getAttributeAggregation(rangeAttrIdx);
+		if (aggrFunc != null) {
+			for (Comparable<?> catValue : statisticsForAggregation.keySet()) {
+				QuantileBin1D aggrStat = statisticsForAggregation.get(catValue);
+				Number rangeValue = getAggregationResult(aggrFunc, aggrStat);
 
+				// Normalize the aggregated value
+				if (chartStyle.isAttributeNormalized(rangeAttrIdx))
+					rangeValue = normalize(rangeValue, rangeAttrName,
+							statisticsForNormalization);
 
-  /**
-   * Determines the value from the series attribute of a feature and transforms it
-   * according to the (optional) no data values.
-   * @param feature a feature
-   * @param chartStyle the chart style
-   * @return {@code null} if the feature must be ignored because of an invalid
-   *         (null) value
-   */
-  private static Object determineSeriesAttributeValueFromFeature(SimpleFeature feature,
-      FeatureChartStyle chartStyle) {
-    String seriesAttrName  = chartStyle.getSeriesAttributeName();
-    Object seriesAttrValue = feature.getAttribute(seriesAttrName);
-    // Filter out NoDataValues
-    seriesAttrValue = chartStyle.filterSeriesAttributeNoDataValue(seriesAttrValue);
-    return seriesAttrValue;
-  }
+				// Fill series
+				dataset.setValue(catValue, rangeValue);
+				// TODO: Mapping setzen
+				// Problem: Pro dataset item kann nur EINE FeatureID gesetzt
+				// werden (Feature2DatasetMapping, Zeile 124)
+				// // Mapping between FID and data index in series
+				// mapping.setMapping(f.getID() ??, yAttrName,
+				// datasetIdx++);
+			}
+		}
 
-  /**
-   * Transforms
-   * @param func
-   * @param value
-   * @param weight
-   * @param weightTotal
-   * @return
-   */
-  private static Double transformValueForAggregation(AggregationFunction func,
-      Number value, Number weight, Number weightTotal) {
-    // no aggregation function -> no transformation
-    if (func == null)
-      return value.doubleValue();
-    // NULL values should be ignored
-    if (value == null)
-      return null;
+		return dataset;
+	}
 
-    double val = value.doubleValue();
+	/**
+	 * Changes the series legend label in a chart style according to the value
+	 * of the {@linkplain FeatureChartStyle#getSeriesLegendTitleAttributeName()
+	 * legend label attribute} defined in the chart style. <br>
+	 * Does nothing if the chart style does not define a series legend label
+	 * attribute or the attribute value is {@code null} or empty.
+	 * 
+	 * @param chartStyle
+	 *            the chart style to update the legend title in
+	 * @param seriesIdx
+	 *            the series index in the {@link ChartRendererStyle} to update
+	 * @param f
+	 *            the feature the legend label is taken from
+	 * @param attrIdx
+	 *            the attribute index in the {@link ChartStyle}
+	 * @see FeatureChartStyle#getSeriesLegendTitleAttributeName()
+	 */
+	private static boolean redefineSeriesLegendTitle(
+			FeatureChartStyle chartStyle, int seriesIdx, SimpleFeature f,
+			int attrIdx) {
+		if (chartStyle.getSeriesLegendTitleAttributeName() == null)
+			return false;
 
-    // Apply transformation for aggregation
-    if (func.isAbsoluted())
-      val = Math.abs(val);
-    if (func.isWeighted()) {
-      // if weight is NULL, ignore the value
-      if (weight == null)
-        return null;
-      val = val * weight.doubleValue();
-    }
-    if (func.isAveraged()) {
-      // if weight total is NULL, ignore the value
-      if (weightTotal == null || weightTotal.doubleValue() == 0)
-        return null;
-      val = val / weightTotal.doubleValue();
-    }
+		Object seriesLabel = f.getAttribute(chartStyle
+				.getSeriesLegendTitleAttributeName());
+		// only apply the new legend label, if the "title attribute" value is
+		// not null and not empty
+		if (seriesLabel == null)
+			return false;
+		String seriesLabelStr = seriesLabel.toString().trim();
+		if ("".equals(seriesLabelStr))
+			return false;
 
-    // Aggregation needs no transformation
-    return val;
-  }
+		// Determine the renderer (primary or secondary range axis!) for
+		// the series
+		int rendID = determineDatasetIndexForAttribute(chartStyle, attrIdx);
+		ChartRendererStyle rendStyle = chartStyle.getRendererStyle(rendID);
+		// Create a renderer style if it does not exist yet
+		if (rendStyle == null) {
+			rendStyle = new ChartRendererStyle();
+			chartStyle.setRendererStyle(rendID, rendStyle);
+		}
+		// Do not change the label if the existing label equals the
+		// new label
+		if (seriesLabel.equals(rendStyle.getSeriesLegendLabel(seriesIdx)))
+			return false;
 
+		// redefine the legend label for the given series
+		rendStyle.setSeriesLegendLabel(seriesIdx, new ChartLabelStyle(
+				seriesLabelStr));
+		return true;
+	}
+
+	/**
+	 * After loading from XML, a {@link FeatureChartStyle} contains whatever is
+	 * written in the XML. But the DBF Schema can change quickly by accident!
+	 * This method checks an {@link FeatureChartStyle} against a schema and
+	 * corrects upperCase/lowerCase problems where possible. Returns
+	 * <code>false</code> if attributes had to be removed from the
+	 * {@link FeatureChartStyle}.
+	 * 
+	 * @author SK
+	 */
+	public static boolean correctAttributeNames(
+			FeatureChartStyle featureChartStyle, SimpleFeatureType schema) {
+
+		ArrayList<Integer> willRemove = new ArrayList<Integer>();
+
+		// 1. Check.. all attributes in the atm should be in the schema as well.
+		// maybe correct some upperCase/loweCase stuff
+		for (int idx = 0; idx < featureChartStyle.getAttributeCount(); idx++) {
+
+			// Check the main attribute
+			String attributeName = featureChartStyle.getAttributeName(idx);
+
+			AttributeDescriptor foundDescr = schema
+					.getDescriptor(attributeName);
+			if (foundDescr == null) {
+				Name bestMatch = FeatureUtil.findBestMatchingAttribute(schema,
+						attributeName);
+				if (bestMatch == null) {
+					willRemove.add(idx);
+					continue;
+				} else
+					featureChartStyle.setAttributeName(idx,
+							bestMatch.getLocalPart());
+			}
+
+			// Check the optional weight attribute value
+			if (idx > 0) {
+				String weightAtt = featureChartStyle
+						.getAttributeAggregationWeightAttributeName(idx);
+				if (weightAtt != null) {
+					AttributeDescriptor foundWeightDescr = schema
+							.getDescriptor(attributeName);
+					if (foundWeightDescr == null) {
+						Name bestMatch = FeatureUtil.findBestMatchingAttribute(
+								schema, weightAtt);
+						if (bestMatch == null) {
+							featureChartStyle
+									.setAttributeAggregationWeightAttributeName(
+											idx, null);
+						} else
+							featureChartStyle
+									.setAttributeAggregationWeightAttributeName(
+											idx, bestMatch.getLocalPart());
+					}
+				}
+			}
+		}
+
+		// Remove the ones that were not findable in the schema
+		for (Integer removeIdx : willRemove) {
+			featureChartStyle.removeAttribute(removeIdx);
+		}
+
+		// * Returns false if attributes had to be removed from the {@link
+		// FeatureChartStyle}.
+		return willRemove.isEmpty();
+	}
+
+	/**
+	 * Passes the NODATA Values stored in the {@link AttributeMetadataMap} to
+	 * the attributes (and optional weighting attributes) of the
+	 * {@link ChartStyle}.
+	 */
+	public static void passNoDataValues(
+			AttributeMetadataMap attributeMetaDataMap,
+			FeatureChartStyle featureChartStyle) {
+
+		// Pass the NODATA values for the attributes to the ChartStyle
+		for (int idx = 0; idx < featureChartStyle.getAttributeCount(); idx++) {
+
+			String attributeName = featureChartStyle.getAttributeName(idx);
+			AttributeMetadataInterface attributeMetadata = attributeMetaDataMap
+					.get(attributeName);
+
+			if (attributeMetadata != null) {
+				featureChartStyle.setNoDataValues(idx,
+						attributeMetadata.getNodataValues());
+			}
+
+			// Set NODATA-Values for the weight attributes
+			String weightAtt = featureChartStyle
+					.getAttributeAggregationWeightAttributeName(idx);
+			if (weightAtt != null) {
+				AttributeMetadataInterface weightAttributeMetadata = attributeMetaDataMap
+						.get(attributeName);
+				if (weightAttributeMetadata != null) {
+					featureChartStyle.setWeightAttributeNoDataValues(idx,
+							weightAttributeMetadata.getNodataValues());
+				}
+			}
+		}
+	}
+
+	/**
+	 * Returns the result of the function from a statistic.
+	 * 
+	 * @param statistics
+	 *            Statistic to take the result from.
+	 */
+	private static Double getAggregationResult(AggregationFunction func,
+			QuantileBin1D statistics) {
+		switch (func) {
+		case AVG:
+			return statistics.mean();
+		case MEDIAN:
+			return statistics.median();
+		case MAX:
+			return statistics.max();
+		case MIN:
+			return statistics.min();
+		case SUM_ABS: // assumes that statistic is already filled properly
+		case SUM_WEIGHTED: // assumes that statistic is already filled properly
+		case AVG_WEIGHTED: // assumes that statistic is already filled properly
+		case SUM:
+			return statistics.sum();
+		case COUNT:
+			return ((Integer) statistics.size()).doubleValue();
+		case STND_DEV:
+			return statistics.size() > 1 ? statistics.standardDeviation() : 0.;
+		case VARIANCE:
+			return statistics.size() > 1 ? statistics.variance() : 0.;
+		}
+		throw new UnsupportedOperationException(
+				"Aggregation function not supported in "
+						+ LangUtil.getSimpleClassName(statistics) + ": " + func);
+	}
+
+	/**
+	 * Determines the dataset for an attribute of the style.
+	 * 
+	 * @param chartStyle
+	 *            the chart style which defines the attribute
+	 * @param attrIdx
+	 *            the attribute for which the dataset should be determined
+	 * @param datasets
+	 *            the available datasets
+	 */
+	private static <D extends Dataset> D determineDatasetForAttribute(
+			FeatureChartStyle chartStyle, int attrIdx, D[] datasets) {
+		int datasetIdx = determineDatasetIndexForAttribute(chartStyle, attrIdx);
+		return datasets[datasetIdx];
+	}
+
+	/**
+	 * Determines the dataset index for an attribute of the style.
+	 * 
+	 * @param chartStyle
+	 *            the chart style which defines the attribute
+	 * @param attrIdx
+	 *            the attribute for which the dataset should be determined
+	 */
+	private static int determineDatasetIndexForAttribute(
+			FeatureChartStyle chartStyle, int attrIdx) {
+		int axis = chartStyle.getAttributeAxis(attrIdx);
+		int datasetIdx = 0;
+		switch (axis) {
+		case ChartStyle.RANGE_AXIS: // primary dataset
+			datasetIdx = 0;
+			break;
+		case ChartStyle.RANGE_AXIS2: // secondary dataset
+			datasetIdx = 1;
+			break;
+		default: // should not occur, but...
+			throw new UnsupportedOperationException("Unknown range axis: "
+					+ axis);
+		}
+
+		return datasetIdx;
+	}
+
+	/**
+	 * Determines the range attribute value from a feature and transforms it
+	 * according to the (optional) aggregation function or normalization.
+	 * 
+	 * @param feature
+	 *            a feature
+	 * @param domValue
+	 *            the domain value (category or x-value)
+	 * @param attrIdx
+	 *            the index of the range attribute
+	 * @param chartStyle
+	 *            the chart style
+	 * @param statisticsForNormalization
+	 *            the pre-calculated statistics used for normalization
+	 * @param weightSumsForAggregation
+	 *            the pre-calculated sums of the (optional) weight attribute
+	 *            values
+	 * @return {@code null} if the feature must be ignored because of an invalid
+	 *         (null) range or weight value
+	 */
+	private static Double determineRangeValueFromFeature(SimpleFeature feature,
+			Comparable<?> domValue, int attrIdx, FeatureChartStyle chartStyle,
+			HashMap<String, QuantileBin1D> statisticsForNormalization,
+			HashMap<Comparable<?>, Double>[] weightSumsForAggregation) {
+
+		AggregationFunction aggrFunc = chartStyle
+				.getAttributeAggregation(attrIdx);
+		String yAttrName = chartStyle.getAttributeName(attrIdx);
+		Number yValue = (Number) feature.getAttribute(yAttrName);
+		// Filter out NoDataValues
+		yValue = chartStyle.filterNoDataValue(attrIdx, yValue);
+		if (yValue == null)
+			return null;
+
+		// if no aggregation is set, the range value is normalized
+		// immediately
+		if (aggrFunc == null) {
+			// Normalization of a range axis value
+			if (chartStyle.isAttributeNormalized(attrIdx))
+				yValue = normalize(yValue, yAttrName,
+						statisticsForNormalization);
+			return yValue.doubleValue();
+		}
+
+		// Determine weight
+		Number weight = null;
+		if (aggrFunc.isWeighted()) {
+			String weightAttrName = chartStyle
+					.getAttributeAggregationWeightAttributeName(attrIdx);
+			weight = (Number) feature.getAttribute(weightAttrName);
+			// Filter out NoDataValues
+			weight = chartStyle.filterWeightAttributeNoDataValue(attrIdx,
+					weight);
+		}
+		// Transform value
+		yValue = transformValueForAggregation(aggrFunc, yValue, weight,
+				weightSumsForAggregation[attrIdx].get(domValue));
+		return yValue.doubleValue();
+	}
+
+	/**
+	 * Determines the value from the series attribute of a feature and
+	 * transforms it according to the (optional) no data values.
+	 * 
+	 * @param feature
+	 *            a feature
+	 * @param chartStyle
+	 *            the chart style
+	 * @return {@code null} if the feature must be ignored because of an invalid
+	 *         (null) value
+	 */
+	private static Object determineSeriesAttributeValueFromFeature(
+			SimpleFeature feature, FeatureChartStyle chartStyle) {
+		String seriesAttrName = chartStyle.getSeriesAttributeName();
+		Object seriesAttrValue = feature.getAttribute(seriesAttrName);
+		// Filter out NoDataValues
+		seriesAttrValue = chartStyle
+				.filterSeriesAttributeNoDataValue(seriesAttrValue);
+		return seriesAttrValue;
+	}
+
+	/**
+	 * Transforms
+	 * 
+	 * @param func
+	 * @param value
+	 * @param weight
+	 * @param weightTotal
+	 * @return
+	 */
+	private static Double transformValueForAggregation(
+			AggregationFunction func, Number value, Number weight,
+			Number weightTotal) {
+		// no aggregation function -> no transformation
+		if (func == null)
+			return value.doubleValue();
+		// NULL values should be ignored
+		if (value == null)
+			return null;
+
+		double val = value.doubleValue();
+
+		// Apply transformation for aggregation
+		if (func.isAbsoluted())
+			val = Math.abs(val);
+		if (func.isWeighted()) {
+			// if weight is NULL, ignore the value
+			if (weight == null)
+				return null;
+			val = val * weight.doubleValue();
+		}
+		if (func.isAveraged()) {
+			// if weight total is NULL, ignore the value
+			if (weightTotal == null || weightTotal.doubleValue() == 0)
+				return null;
+			val = val / weightTotal.doubleValue();
+		}
+
+		// Aggregation needs no transformation
+		return val;
+	}
+
 }



More information about the Schmitzm-commits mailing list