[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