[Schmitzm-commits] r754 - in trunk/src/schmitzm/jfree: feature feature/style resource/locales

scm-commit@wald.intevation.org scm-commit at wald.intevation.org
Wed Mar 3 20:13:23 CET 2010


Author: mojays
Date: 2010-03-03 20:13:22 +0100 (Wed, 03 Mar 2010)
New Revision: 754

Modified:
   trunk/src/schmitzm/jfree/feature/AggregationFunction.java
   trunk/src/schmitzm/jfree/feature/style/FeatureChartStyle.java
   trunk/src/schmitzm/jfree/feature/style/FeatureChartUtil.java
   trunk/src/schmitzm/jfree/resource/locales/JFreeResourceBundle.properties
   trunk/src/schmitzm/jfree/resource/locales/JFreeResourceBundle_de.properties
Log:
- new AggregationFunctions SUM_WEIGHTED, SUM_ABS
- getResult(.) moved from AggregationFunction to FeatureChartUtil
- Dataset generation in FeatureChartUtil restructured with private utility methods

Modified: trunk/src/schmitzm/jfree/feature/AggregationFunction.java
===================================================================
--- trunk/src/schmitzm/jfree/feature/AggregationFunction.java	2010-03-03 15:35:55 UTC (rev 753)
+++ trunk/src/schmitzm/jfree/feature/AggregationFunction.java	2010-03-03 19:13:22 UTC (rev 754)
@@ -15,121 +15,129 @@
 
 /**
  * Defines possible aggregation function to calculate on attribute categories.
- * 
  * @see FeatureChartStyle#setAttributeAggregation(int, AggregationFunction)
  * @see FeatureChartStyle#getAttributeAggregation(int)
  * @see optionally FeatureChartStyle#setNoDataWeightValues(int, java.util.Set))
- * @see optionally FeatureChartStyle#getAttributeAggregationWeightAttributeName(int)
+ * @see optionally
+ *      FeatureChartStyle#getAttributeAggregationWeightAttributeName(int)
  */
 public enum AggregationFunction {
 
-	/** Count of the attribute values. */
-	COUNT(false),
-	/** Sum of the attribute values. */
-	SUM(false),
-	// /** Sum of the absolute attribute values. */
-	// SUM_ABS,
-	/** Average of the attribute values. */
-	AVG(false),
-	/**
-	 * Weighted average of the attribute values. Needs an attribute for
-	 * weighting
-	 **/
-	AVG_WEIGHTED(true),
-	 /** Median of the attribute values. */
-	 MEDIAN(false),
-	/** Minimum attribute value. */
-	MIN(false),
-	/** Maximum attribute value. */
-	MAX(false),
-	/** Variance of the attribute values. */
-	VARIANCE(false),
-	/** Standard deviation of the attribute values. */
-	STND_DEV(false);
+  /** Count of the attribute values. */
+  COUNT(),
+  /** Sum of the attribute values. */
+  SUM(),
+  /** Sum of the absolute attribute values. */
+  SUM_ABS(true,false, false),
+  /** Weighted sum of the attribute values. Needs an attribute for weighting. */
+  SUM_WEIGHTED(false, true, false),
+  /** Average of the attribute values. */
+  AVG(),
+  /**
+   * Weighted average of the attribute values. Needs an attribute for weighting.
+   */
+  AVG_WEIGHTED(false, true, true),
+  /** Median of the attribute values. */
+  MEDIAN(),
+  /** Minimum attribute value. */
+  MIN(),
+  /** Maximum attribute value. */
+  MAX(),
+  /** Variance of the attribute values. */
+  VARIANCE(),
+  /** Standard deviation of the attribute values. */
+  STND_DEV();
 
-	private final boolean weighted;
+  // Indicates whether the attribute is must be transformed to positive
+  // values
+  private final boolean absoluted;
+  // Indicates whether the attribute is weighted with another attribute
+  private final boolean weighted;
+  // Indicates whether the attribute is weighted and averaged with the weight
+  // sum
+  private final boolean averaged;
 
-	/**
-	 * Constructed for this {@link Enum}, defining whether it uses a weight. 
-	 * @param weighted
-	 */
-	private AggregationFunction(boolean weighted) {
-		this.weighted = weighted;
-	}
+  /**
+   * Creates a default {@link Enum}, which is not absoluted, not weighted and
+   * not averaged.
+   */
+  private AggregationFunction() {
+    this(false,false,false);
+  }
 
-	/**
-	 * Returns <code>true</code>, if this aggregation method is used a second attribute for
-	 * weighting.
-	 **/
-	public boolean isWeighted() {
-		return weighted;
-	}
+  /**
+   * Creates an {@link Enum} instance.
+   * @param absoluted Indicates whether the attribute is must be transformed to
+   *          positive values
+   * @param weighted Indicates whether the attribute is weighted with another
+   *          attribute
+   * @param averaged Indicates whether the attribute is weighted and averaged
+   *          with the weight sum (if averaged, weighted is automatically
+   *          implied!)
+   */
+  private AggregationFunction(boolean absoluted, boolean weighted,
+      boolean averaged) {
+    this.absoluted = absoluted;
+    this.weighted = weighted || averaged; // average implies weighted
+    this.averaged = averaged;
+  }
 
-	/**
-	 * Prefix for the title (.TITLE) and description (.DESC) key in the resource
-	 * bundle.
-	 * 
-	 * @see #getTitle()
-	 * @see #getDescription()
-	 */
-	public final String RESOURCE_PREFIX = getClass().getSimpleName() + "."
-			+ toString();
+  /**
+   * Returns <code>true</code>, if this aggregation method needs the
+   * absolute attribute value.
+   * @see Math#abs(double)
+   **/
+  public boolean isAbsoluted() {
+    return absoluted;
+  }
 
-	/**
-	 * Returns the result of the function from a statistic.
-	 * 
-	 * @param statistics
-	 *            Statistic to take the result from.
-	 */
-	public Double getResult(QuantileBin1D statistics) {
-		switch (this) {
-		case AVG:
-			return statistics.mean();
-		case MEDIAN:
-			return statistics.median();
-		case MAX:
-			return statistics.max();
-		case MIN:
-			return statistics.min();
-			// case SUM_ABS: // SK: Man müsste beim
-			// "packen der daten in die Statistik" schon abs() auf die werte
-			// anwenden und dann diese zeile einkommentieren
-		case AVG_WEIGHTED:
-		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 yet supported: " + this);
-	}
+  /**
+   * Returns <code>true</code>, if this aggregation method is used a second
+   * attribute for weighting.
+   **/
+  public boolean isWeighted() {
+    return weighted;
+  }
 
-	/**
-	 * Returns a description of this kind of chart. Can be used for tool-tips.
-	 * May return <code>null</code> if no localized String found.
-	 */
-	public String getDescription() {
-		final String resource = JFreeChartUtil.R(RESOURCE_PREFIX + ".Desc");
-		if (resource == null
-				|| resource.equals(ResourceProvider.MISSING_RESOURCE_STRING))
-			return null;
-		return resource;
-	}
+  /**
+   * Returns <code>true</code>, if this aggregation method is used a second
+   * attribute for averaging.
+   **/
+  public boolean isAveraged() {
+    return averaged;
+  }
 
-	/**
-	 * Returns a localized title of this kind of chart. If no localized string
-	 * found, return the Enum.toString()
-	 */
-	public String getTitle() {
-		final String resource = JFreeChartUtil.R(RESOURCE_PREFIX + ".Title");
-		if (resource == null
-				|| resource.equals(ResourceProvider.MISSING_RESOURCE_STRING))
-			return toString();
-		return resource;
-	}
+  /**
+   * Prefix for the title (.TITLE) and description (.DESC) key in the resource
+   * bundle.
+   * @see #getTitle()
+   * @see #getDescription()
+   */
+  public final String RESOURCE_PREFIX = getClass().getSimpleName() + "." +
+                                        toString();
 
+  /**
+   * Returns a description of this kind of chart. Can be used for tool-tips. May
+   * return <code>null</code> if no localized String found.
+   */
+  public String getDescription() {
+    final String resource = JFreeChartUtil.R(RESOURCE_PREFIX + ".Desc");
+    if (resource == null ||
+        resource.equals(ResourceProvider.MISSING_RESOURCE_STRING))
+      return null;
+    return resource;
+  }
+
+  /**
+   * Returns a localized title of this kind of chart. If no localized string
+   * found, return the Enum.toString()
+   */
+  public String getTitle() {
+    final String resource = JFreeChartUtil.R(RESOURCE_PREFIX + ".Title");
+    if (resource == null ||
+        resource.equals(ResourceProvider.MISSING_RESOURCE_STRING))
+      return toString();
+    return resource;
+  }
+
 }

Modified: trunk/src/schmitzm/jfree/feature/style/FeatureChartStyle.java
===================================================================
--- trunk/src/schmitzm/jfree/feature/style/FeatureChartStyle.java	2010-03-03 15:35:55 UTC (rev 753)
+++ trunk/src/schmitzm/jfree/feature/style/FeatureChartStyle.java	2010-03-03 19:13:22 UTC (rev 754)
@@ -625,7 +625,7 @@
      */
     @Override
     public void setAttributeAggregation(int idx, AggregationFunction func) {
-      if (idx == 0)
+      if (idx == 0 && func != null)
         throw new IllegalArgumentException(
             "Aggregation function not allowed for attribute 0 (domain attribute)!");
       aggrFuncs.put(idx, func);
@@ -736,7 +736,7 @@
     @Override
     public void setAttributeAggregationWeightAttributeName(int idx,
         String weightAttrName) {
-      if (idx == 0)
+      if (idx == 0 && weightAttrName != null)
         throw new IllegalArgumentException(
             "Weight attribute not allowed for attribute 0 because aggregation function not allowed for domain attribute!");
       weightAttrNames.put(idx, weightAttrName);
@@ -767,7 +767,7 @@
      */
     @Override
     public void setWeightAttributeNoDataValues(int idx, Set<Object> noDataValues) {
-      if (idx == 0)
+      if (idx == 0 && noDataValues != null)
         throw new IllegalArgumentException(
             "Weight attribute not allowed for attribute 0 because aggregation function not allowed for domain attribute!");
       weightAttrNoDataValues.put(idx, noDataValues);

Modified: trunk/src/schmitzm/jfree/feature/style/FeatureChartUtil.java
===================================================================
--- trunk/src/schmitzm/jfree/feature/style/FeatureChartUtil.java	2010-03-03 15:35:55 UTC (rev 753)
+++ trunk/src/schmitzm/jfree/feature/style/FeatureChartUtil.java	2010-03-03 19:13:22 UTC (rev 754)
@@ -68,11 +68,13 @@
 import schmitzm.jfree.chart.selection.DatasetSelectionModelProvider;
 import schmitzm.jfree.chart.style.ChartStyle;
 import schmitzm.jfree.chart.style.ChartStyleXMLFactory;
+import schmitzm.jfree.chart.style.ChartType;
 import schmitzm.jfree.feature.AggregationFunction;
 import schmitzm.jfree.feature.Feature2CategoryDatasetMapping;
 import schmitzm.jfree.feature.Feature2SeriesDatasetMapping;
 import schmitzm.jfree.feature.FeatureDatasetMetaData;
 import schmitzm.jfree.feature.FeatureDatasetSelectionModel;
+import schmitzm.lang.LangUtil;
 import skrueger.AttributeMetadata;
 import skrueger.geotools.AttributeMetadataMap;
 
@@ -210,24 +212,32 @@
     // check data types of X attribute
     checkAttributeType(fc.getSchema(), xAttrName, Number.class,
                        "Domain attribute", "XYDataset");
-    // check data types of Y attribute
+    // 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");
 
+    // 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 series for each range attribute (note: the 0
     // array element useless, but it is more comfortable to
     // use an array of the same size as "attributes"
     XYSeries[] xySeries = new XYSeries[attrCount];
     for (int i = 1; i < xySeries.length; i++)
-      xySeries[i] = new XYSeries(chartStyle.getAttributeName(i), false, true); // IMPORTANT
-    // :
-    // Do
-    // not
-    // use
-    // autosort
-    // !
-    // !
+      // IMPORTANT: Do not use autosort!!
+      xySeries[i] = new XYSeries(chartStyle.getAttributeName(i), false, true);
 
     // Create a new dataset and insert the series
     XYSeriesCollection dataset = new XYSeriesCollection();
@@ -237,17 +247,6 @@
     for (int i = 1; i < xySeries.length; i++)
       dataset.addSeries(xySeries[i]);
 
-    // Calculate any statistics needed for normalization.
-    HashMap<String, QuantileBin1D> statisticsForNormalization = calcStatisticsForNotAggregatedNormalization(
-                                                                                               fc,
-                                                                                               chartStyle);
-    // Prepare set for statistics to calculate aggregation
-    // functions (one statistic for each range attribute of
-    // each domain value)
-    HashMap<Number, QuantileBin1D>[] statisticsForAggregation = new HashMap[attrCount];
-    for (int i = 1; i < attrCount; i++)
-      statisticsForAggregation[i] = new HashMap<Number, QuantileBin1D>();
-
     // 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:
@@ -259,101 +258,182 @@
     //
     // 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()) {
       Vector<SimpleFeature> sortedFeatures = FeatureUtil.sortFeatures(fc,
                                                                       xAttrName);
       // Create an Iterator for the sorted Features
       fi = sortedFeatures.iterator();
-    } else
-      fi = new PipedFeatureIterator(fc.features());
+    } else {
+      features = fc.features();
+      fi = new PipedFeatureIterator(features);
+    }
 
-    // Iterate the FeatureCollection and fill the series
-    int datasetIdx = 0;
-    for (; fi.hasNext();) {
-      SimpleFeature f = fi.next();
-      // Determine X value (NULL not permitted for XYDateset!)
-      Number xValue = (Number) f.getAttribute(xAttrName);
+    // Iterate the FeatureCollection and fill the dataset
+    try {
+      int datasetIdx = 0;
+      for (; fi.hasNext();) {
+        SimpleFeature f = fi.next();
+        // Determine X value (NULL not permitted for XYDateset!)
+        Number xValue = (Number) f.getAttribute(xAttrName);
 
-      // Filter out NODATA values
-      xValue = chartStyle.filterNoDataValue(ChartStyle.DOMAIN_AXIS, xValue);
-      if (xValue == null)
-        continue;
+        // Filter out NODATA values
+        xValue = chartStyle.filterNoDataValue(ChartStyle.DOMAIN_AXIS, xValue);
+        if (xValue == null)
+          continue;
 
-      /*
-       * Normalization of the domain axis value (if they are not handled as
-       * categories)
-       */
-      if (chartStyle.isAttributeNormalized(ChartStyle.DOMAIN_AXIS))
-        xValue = normalize(xValue, xAttrName, statisticsForNormalization);
+        // Normalization of the domain axis value (if they are not handled as
+        // categories)
+        if (chartStyle.isAttributeNormalized(ChartStyle.DOMAIN_AXIS))
+          xValue = normalize(xValue, xAttrName, statisticsForNormalization);
 
-      // Determine the Y values and fill the series
-      for (int attrIdx = 1; attrIdx < attrCount; attrIdx++) {
-        String yAttrName = chartStyle.getAttributeName(attrIdx);
-        Number yValue = (Number) f.getAttribute(yAttrName);
-        // AggregationFunction attributeAggregation = chartStyle
-        // .getAttributeAggregation(attrIdx);
+        // 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)
+            continue;
 
-        // Filter out NoDataValues
-        yValue = chartStyle.filterNoDataValue(attrIdx, yValue);
-        if (yValue == null)
-          continue;
+          // Fill series, if no aggregation function is defined.
+          // Otherwise fill statistic (dataset is filled later!)
+          if (chartStyle.getAttributeAggregation(attrIdx) == null) {
+            // Fill series
+            xySeries[attrIdx].add(xValue, yValue);
+            // Mapping between FID and data index in series
+            String yAttrName = chartStyle.getAttributeName(attrIdx);
+            mapping.setMapping(f.getID(), yAttrName, datasetIdx++);
+          } else {
+            QuantileBin1D aggrStat = statisticsForAggregation[attrIdx].get(xValue);
+            if (aggrStat == null) {
+              aggrStat = new DynamicBin1D();
+              statisticsForAggregation[attrIdx].put((Double) xValue, aggrStat);
+            }
+            aggrStat.add(yValue.doubleValue());
+            // TODO: Mapping vormerken (??)
+            // Problem: siehe unten
 
-        /* Normalization of a range axis value */
+          }
+        }
+      }
+    } finally {
+      if (features != null) {
+        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;
+      for (Comparable<?> xValue : statisticsForAggregation[attrIdx].keySet()) {
+        QuantileBin1D aggrStat = statisticsForAggregation[attrIdx].get(xValue);
+        Number yValue = getAggregationResult(aggrFunc, aggrStat);
+
+        // Normalize the aggregated value
         if (chartStyle.isAttributeNormalized(attrIdx))
           yValue = normalize(yValue, yAttrName, statisticsForNormalization);
 
-        // Fill series, if no aggregation function is defined.
-        // Otherwise fill statistic (dataset is filled later!)
-        // if (attributeAggregation == null) {
         // Fill series
-        xySeries[attrIdx].add(xValue, yValue);
+        xySeries[attrIdx].add((Double) 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++);
-        // } else {
-        // // The values for this are aggregated
-        // StaticBin1D aggrStat = statisticsForAggregation[attrIdx]
-        // .get(xValue);
-        // if (aggrStat == null) {
-        // aggrStat = new DynamicBin1D();
-        // statisticsForAggregation[attrIdx].put(xValue, aggrStat);
-        // }
-        //
-        // // The default weight is 1
-        // // for (int i = 0; i < yWeightValue; i++)
-        // aggrStat.add(yValue.doubleValue());
-        //
-        // // TODO: Mapping vormerken (??)
-        // // Problem: siehe unten
-        // }
+        // mapping.setMapping(f.getID() ??, yAttrName,
+        // datasetIdx++);
       }
     }
+    return dataset;
+  }
 
-    // // Fill series for aggregated range attributes
-    // for (int attrIdx = 1; attrIdx < attrCount; attrIdx++) {
-    // // String yAttrName = chartStyle.getAttributeName(attrIdx);
-    // AggregationFunction aggrFunc = chartStyle
-    // .getAttributeAggregation(attrIdx);
-    // if (aggrFunc == null)
-    // continue;
-    // for (Number xValue : statisticsForAggregation[attrIdx].keySet()) {
-    // StaticBin1D aggrStat = statisticsForAggregation[attrIdx]
-    // .get(xValue);
-    // Number yValue = aggrFunc.getResult(aggrStat);
-    // // Fill series
-    // xySeries[attrIdx].add(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++);
-    // }
-    // }
+  /**
+   * 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>();
 
-    return dataset;
+    // 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;
+
+        // 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;
+
+          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);
+      }
+    }
+
+    return weightSumsForAggregation;
   }
 
-
   /**
    * Calculates statistics needed to normalize data. If normalization is not
    * used, this function returns an empty map.
@@ -362,33 +442,34 @@
    *          normalized.
    */
   private static HashMap<String, QuantileBin1D> calcStatisticsForAggregatedNormalization(
-      HashMap<Comparable<?>,QuantileBin1D>[] aggrAttrValues, FeatureChartStyle chartStyle) {
-    
+      HashMap<Comparable<?>, QuantileBin1D>[] aggrAttrValues,
+      FeatureChartStyle chartStyle) {
+
     HashMap<String, QuantileBin1D> normStats = new HashMap<String, QuantileBin1D>();
 
     for (int attrIdx = 0; attrIdx < chartStyle.getAttributeCount(); attrIdx++) {
-      if ( aggrAttrValues[attrIdx] == null )
+      if (aggrAttrValues[attrIdx] == null)
         continue;
       if (!chartStyle.isAttributeNormalized(attrIdx))
         continue;
       String attrName = chartStyle.getAttributeName(attrIdx);
-      QuantileBin1D stat = normStats.get(attrName); 
+      QuantileBin1D stat = normStats.get(attrName);
       if (stat == null) {
         stat = new DynamicBin1D();
         normStats.put(attrName, stat);
       }
-      
+
       for (Comparable<?> catValue : aggrAttrValues[attrIdx].keySet())
-        stat.add( chartStyle.getAttributeAggregation(attrIdx).getResult(aggrAttrValues[attrIdx].get(catValue)) );
+        stat.add(getAggregationResult(
+                                      chartStyle.getAttributeAggregation(attrIdx),
+                                      aggrAttrValues[attrIdx].get(catValue)));
     }
 
-    
     return normStats;
   }
-    
+
   /**
-  }
-   * Calculates statistics needed to normalize data. If normalization is not
+   * } 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
@@ -411,7 +492,8 @@
     // skip the whole iteration
     boolean doNormalization = false;
     for (int attrIdx = 0; attrIdx < chartStyle.getAttributeCount(); attrIdx++) {
-      if (chartStyle.isAttributeNormalized(attrIdx) && chartStyle.getAttributeAggregation(attrIdx) == null) {
+      if (chartStyle.isAttributeNormalized(attrIdx) &&
+          chartStyle.getAttributeAggregation(attrIdx) == null) {
         doNormalization = true;
         break;
       }
@@ -427,7 +509,8 @@
           SimpleFeature f = fIt.next();
 
           for (int attrIdx = 0; attrIdx < chartStyle.getAttributeCount(); attrIdx++) {
-            if (chartStyle.isAttributeNormalized(attrIdx) && chartStyle.getAttributeAggregation(attrIdx) == null) {
+            if (chartStyle.isAttributeNormalized(attrIdx) &&
+                chartStyle.getAttributeAggregation(attrIdx) == null) {
 
               String attrName = chartStyle.getAttributeName(attrIdx);
 
@@ -483,10 +566,11 @@
   private static Number normalize(Number yValue, String attrName,
       HashMap<String, QuantileBin1D> statisticsForNormalization) {
     Number zValue;
-    
-    // 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.;
+
+    // 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.;
     }
 
     if (statisticsForNormalization.get(attrName) == null) {
@@ -502,9 +586,9 @@
   }
 
   /**
-   * Creates a {@link XYDataset} for 2 (or more) attributes of a
-   * {@link FeatureCollection}. XYDateset can only be created for <b>numeric</b>
-   * attributes.
+   * Creates a {@link CategoryDataset} for 2 (or more) attributes of a
+   * {@link FeatureCollection}. CategoryDataset can only be created for
+   * <b>numeric</b> range attributes.
    * @param fc a {@link FeatureCollection}
    * @param style defines the attributes used to create the dataset from, as
    *          well as the sorting and normalization properties
@@ -529,13 +613,16 @@
       checkAttributeType(fc.getSchema(), chartStyle.getAttributeName(i),
                          Number.class, "Range attribute", "CategoryDataset");
 
-    // Calculate any statistics needed for normalization.
+    // Calculate statistics to normalize not-aggregated attributes
     HashMap<String, QuantileBin1D> statisticsForNormalization = calcStatisticsForNotAggregatedNormalization(
-                                                                                               fc,
-                                                                                               chartStyle);
-    // Prepare set for statistics to calculate aggregation
-    // functions (one statistic for each range attribute of
-    // each domain value)
+                                                                                                            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>();
@@ -546,70 +633,11 @@
         fc, dataset);
     dataset.setGroup(new FeatureDatasetMetaData(mapping));
 
-    FeatureIterator<SimpleFeature> features = null;
-    // Für die Gewichtung muss zuerst Summe aller Gewichte (pro Category!)
-    // ermittelt werden
-    HashMap<Comparable<?>, Double>[] weightSumsForAggregation = new HashMap[attrCount];
-    for (int i = 1; i < attrCount; i++)
-      weightSumsForAggregation[i] = new HashMap<Comparable<?>, Double>();
-    features = fc.features();
-    try {
-      while (features.hasNext()) {
-        SimpleFeature f = features.next();
-        // ignore features with NODATA in domain attribute
-        Comparable<?> catValue = (Comparable<?>) f.getAttribute(xAttrName);
-        catValue = chartStyle.filterNoDataValue(ChartStyle.DOMAIN_AXIS,
-                                                catValue);
-        if (catValue == null)
-          continue;
-
-        // 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;
-
-          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);
-      }
-    }
-
     // 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;
     if (chartStyle.isSortDomainAxis()) {
       Vector<SimpleFeature> sortedFeatures = FeatureUtil.sortFeatures(fc,
@@ -621,8 +649,8 @@
       fi = new PipedFeatureIterator(features);
     }
 
+    // Iterate the FeatureCollection and fill the dataset
     try {
-      // Iterate the FeatureCollection and fill the series
       for (int fIdx = 0; fi.hasNext(); fIdx++) {
         SimpleFeature f = fi.next();
         // Determine the category (NULL not permitted!)
@@ -635,30 +663,22 @@
 
         // Determine the Y values and fill the dataset
         for (int attrIdx = 1; attrIdx < attrCount; attrIdx++) {
-          AggregationFunction attributeAggregation = chartStyle.getAttributeAggregation(attrIdx);
-
-          String yAttrName = chartStyle.getAttributeName(attrIdx);
-          Number yValue = (Number) f.getAttribute(yAttrName);
-
-          // By default the wight is 1 => no weighting. If the
-          // aggregation uses weighting, read the weight attribnute
-          // if (attributeAggregation != null &&
-          // attributeAggregation.isWeighted())
-          // yWeightValue = ((Number) f.getAttribute(WEIGHTATTRIB))
-          // .longValue();
-
-          // Filter out NoDataValues
-          yValue = chartStyle.filterNoDataValue(attrIdx, yValue);
+          Double yValue = determineRangeValueFromFeature(
+                                                         f,
+                                                         catValue,
+                                                         attrIdx,
+                                                         chartStyle,
+                                                         statisticsForNormalization,
+                                                         weightSumsForAggregation);
+          // Ignore feature if value is invalid
           if (yValue == null)
             continue;
 
           // Fill series, if no aggregation function is defined.
           // Otherwise fill statistic (dataset is filled later!)
-          if (attributeAggregation == null) {
-            /* Normalization of a range axis value */
-            if (chartStyle.isAttributeNormalized(attrIdx))
-              yValue = normalize(yValue, yAttrName, statisticsForNormalization);
+          if (chartStyle.getAttributeAggregation(attrIdx) == null) {
             // Add data to dataset
+            String yAttrName = chartStyle.getAttributeName(attrIdx);
             dataset.addValue(yValue, yAttrName, catValue);
             // Mapping between FID and data index in series
             mapping.setMapping(f.getID(), yAttrName, catValue);
@@ -668,57 +688,46 @@
               aggrStat = new DynamicBin1D();
               statisticsForAggregation[attrIdx].put(catValue, aggrStat);
             }
-
-            if (attributeAggregation.isWeighted()) {
-              // weight attribute must not be NULL
-              Object weightObj = f.getAttribute(chartStyle.getAttributeAggregationWeightAttributeName(attrIdx));
-              Number weight    = (Number)chartStyle.filterWeightAttributeNoDataValue(attrIdx, weightObj);
-              if ( weight == null )
-                continue;
-              aggrStat.add(yValue.doubleValue() * weight.doubleValue() / weightSumsForAggregation[attrIdx].get(catValue));
-            } else {
-              aggrStat.add(yValue.doubleValue());
-            }
+            aggrStat.add(yValue.doubleValue());
             // TODO: Mapping vormerken (??)
             // Problem: siehe unten
           }
         }
-
       }
-
-      // 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;
-        for (Comparable<?> catValue : statisticsForAggregation[attrIdx].keySet()) {
-          QuantileBin1D aggrStat = statisticsForAggregation[attrIdx].get(catValue);
-          Number yValue = aggrFunc.getResult(aggrStat);
-          
-          // Normalize the aggregated value
-          if ( chartStyle.isAttributeNormalized(attrIdx) ){
-        	  
-        	  yValue = normalize(yValue, yAttrName, statisticsForNormalization);
-          }
-          
-          // 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++);
-        }
-      }
     } finally {
       if (features != null) {
         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;
+      for (Comparable<?> catValue : statisticsForAggregation[attrIdx].keySet()) {
+        QuantileBin1D aggrStat = statisticsForAggregation[attrIdx].get(catValue);
+        Number yValue = getAggregationResult(aggrFunc, aggrStat);
+
+        // Normalize the aggregated value
+        if (chartStyle.isAttributeNormalized(attrIdx))
+          yValue = normalize(yValue, yAttrName, statisticsForNormalization);
+
+        // 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 dataset;
   }
 
@@ -814,4 +823,129 @@
       }
     }
   }
+
+  /**
+   * 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 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();
+  }
+
+  /**
+   * 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;
+  }
+
 }

Modified: trunk/src/schmitzm/jfree/resource/locales/JFreeResourceBundle.properties
===================================================================
--- trunk/src/schmitzm/jfree/resource/locales/JFreeResourceBundle.properties	2010-03-03 15:35:55 UTC (rev 753)
+++ trunk/src/schmitzm/jfree/resource/locales/JFreeResourceBundle.properties	2010-03-03 19:13:22 UTC (rev 754)
@@ -61,6 +61,8 @@
 AggregationFunction.SUM.Desc=Sum of attribute values
 AggregationFunction.SUM_ABS.Title=Absolutes sum
 AggregationFunction.SUM_ABS.Desc=Sum of the absolute attribute values
+AggregationFunction.SUM_WEIGHTED.Title=Weighted sum
+AggregationFunction.SUM_WEIGHTED.Desc=Weighted sum of attribute values
 AggregationFunction.AVG.Title=Average
 AggregationFunction.AVG.Desc=Average attribute value
 AggregationFunction.AVG_WEIGHTED.Title=Weighted average

Modified: trunk/src/schmitzm/jfree/resource/locales/JFreeResourceBundle_de.properties
===================================================================
--- trunk/src/schmitzm/jfree/resource/locales/JFreeResourceBundle_de.properties	2010-03-03 15:35:55 UTC (rev 753)
+++ trunk/src/schmitzm/jfree/resource/locales/JFreeResourceBundle_de.properties	2010-03-03 19:13:22 UTC (rev 754)
@@ -59,6 +59,8 @@
 AggregationFunction.SUM.Desc=Summe der Attribut-Werte
 AggregationFunction.SUM_ABS.Title=Absolut-Summe
 AggregationFunction.SUM_ABS.Desc=Summe der Attribut-Wert-Betr\u00e4ge
+AggregationFunction.SUM_WEIGHTED.Title=Gewichtete Summe
+AggregationFunction.SUM_WEIGHTED.Desc=Gewichtete Summe der Attribute-Werte
 AggregationFunction.AVG.Title=Durschnitt
 AggregationFunction.AVG.Desc=Durchschnittlicher Attribut-Wert
 AggregationFunction.MEDIAN.Title=Median



More information about the Schmitzm-commits mailing list