/*
 * GRAL: GRAphing Library for Java(R)
 *
 * (C) Copyright 2009-2016 Erich Seifert ,
 * Michael Seifert 
 *
 * This file is part of GRAL.
 *
 * GRAL is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * GRAL is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with GRAL.  If not, see .
 */
package de.erichseifert.gral.data.statistics;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import de.erichseifert.gral.data.DataSource;
import de.erichseifert.gral.graphics.Orientation;


/**
 * 

View that aggregates the column values of an other data source into * a histogram with cells. The cells size can be equally sized by defining * a number of cells or breakpoints between histogram cells can be passed * as an array to create unequally sized cells.

*

For ease of use the histogram is a data source itself.

*/ public class Histogram2D extends AbstractHistogram2D { /** Version id for serialization. */ private static final long serialVersionUID = -4841658606362408312L; /** Direction in which all values will be aggregated. */ private final Orientation orientation; /** Intervals that will be used for aggregation. */ private final List breaks; /** Bin cells that store all aggregation counts. */ private final List cellList; /** Minimum values for cells. */ private transient Map cacheMin; /** Maximum values for cells. */ private transient Map cacheMax; private Histogram2D(DataSource data, Orientation orientation) { super(data); this.orientation = orientation; breaks = new ArrayList(); cellList = new ArrayList(); cacheMin = new HashMap(); cacheMax = new HashMap(); } /** * Creates a new AbstractHistogram2D object with the specified DataSource and * cell count. * @param data DataSource so be analyzed. * @param orientation Orientation of the histogram values. * @param breakCount Number of subdivisions for analysis. */ public Histogram2D(DataSource data, Orientation orientation, int breakCount) { this(data, orientation); // Create equally spaced breaks int count = getData().getColumnCount(); if (orientation == Orientation.HORIZONTAL) { count = getData().getRowCount(); } for (int index = 0; index < count; index++) { double min, max; if (orientation == Orientation.HORIZONTAL) { min = ((Number) getData().getRowStatistics(Statistics.MIN).get(0, index)).doubleValue(); max = ((Number) getData().getRowStatistics(Statistics.MAX).get(0, index)).doubleValue(); } else { min = ((Number) getData().getColumnStatistics(Statistics.MIN).get(index, 0)).doubleValue(); max = ((Number) getData().getColumnStatistics(Statistics.MAX).get(index, 0)).doubleValue(); } double delta = (max - min + Double.MIN_VALUE) / breakCount; Number[] breaks = new Double[breakCount + 1]; for (int i = 0; i < breaks.length; i++) { breaks[i] = min + i*delta; } this.breaks.add(breaks); } dataUpdated(getData()); } /** * Initializes a new histogram with the specified data source and * subdivisions at the specified positions. * @param data Data source to be analyzed. * @param orientation Orientation in which the data should be sampled. * @param breaks Values of where a subdivision should occur. */ public Histogram2D(DataSource data, Orientation orientation, Number[]... breaks) { this(data, orientation); int count = getData().getColumnCount(); if (orientation == Orientation.HORIZONTAL) { count = getData().getRowCount(); } if (breaks.length != count) { throw new IllegalArgumentException(MessageFormat.format( "Invalid number of breaks: got {0,number,integer}, expected {1,number,integer}.", //$NON-NLS-1$ breaks.length, count)); } Collections.addAll(this.breaks, breaks); dataUpdated(getData()); } /** * (Re-)populates the cells of this AbstractHistogram2D. */ @Override protected void rebuildCells() { // FIXME Very naive implementation cellList.clear(); cacheMin.clear(); cacheMax.clear(); // Iterate over histogram data sets int breakIndex = 0; for (Number[] brk : breaks) { long[] cells = new long[brk.length - 1]; long colMin = Long.MAX_VALUE; long colMax = Long.MIN_VALUE; Iterable> data; if (orientation == Orientation.VERTICAL) { data = getData().getColumn(breakIndex); } else { data = getData().getRecord(breakIndex); } // Iterate over data cells for (Comparable cell : data) { if (!(cell instanceof Number)) { continue; } Number numericCell = (Number) cell; double val = numericCell.doubleValue(); // Iterate over histogram rows for (int i = 0; i < brk.length - 1; i++) { // Put the value into corresponding class if ((val >= brk[i].doubleValue()) && (val < brk[i + 1].doubleValue())) { cells[i]++; if (cells[i] > colMax) { colMax = cells[i]; } if (cells[i] < colMin) { colMin = cells[i]; } break; } } } cellList.add(cells); cacheMin.put(breakIndex, colMin); cacheMax.put(breakIndex, colMax); breakIndex++; } } /** * Returns the direction in which the histogram values will be accumulated. * @return Horizontal or vertical orientation. */ public Orientation getOrientation() { return orientation; } /** * Returns the minimum and maximum value of the specified cell. * @param col Column index. * @param cell Cell index. * @return Extent of the cell. */ public Number[] getCellLimits(int col, int cell) { Number[] breaks = this.breaks.get(col); Number lower = breaks[cell]; Number upper = breaks[cell + 1]; return new Number[] {lower, upper}; } /** * Returns the row with the specified index. * @param col index of the column to return * @param row index of the row to return * @return the specified value of the data cell */ public Comparable get(int col, int row) { return cellList.get(col)[row]; } /** * Returns the number of rows of the data source. * @return number of rows in the data source. */ public int getRowCount() { int rowCount = 0; for (long[] cells : this.cellList) { rowCount = Math.max(cells.length, rowCount); } return rowCount; } @Override public int getColumnCount() { return cellList.size(); } @Override @SuppressWarnings("unchecked") public Class>[] getColumnTypes() { Class>[] types = new Class[getColumnCount()]; Arrays.fill(types, Long.class); return types; } /** * Custom deserialization method. * @param in Input stream. * @throws ClassNotFoundException if a serialized class doesn't exist anymore. * @throws IOException if there is an error while reading data from the * input stream. */ private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException { // Normal deserialization in.defaultReadObject(); // Handle transient fields cacheMin = new HashMap(); cacheMax = new HashMap(); } }

Ads help maintain this website.