/*******************************************************************************
* Copyright (c) 2010 Haifeng Li
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package smile.plot;
import java.awt.Color;
import smile.math.Math;
import smile.sort.QuickSort;
/**
* A histogram is a graphical display of tabulated frequencies, shown as bars.
* It shows what proportion of cases fall into each of several categories:
* it is a form of data binning. The categories are usually specified as
* non-overlapping intervals of some variable. The categories (bars) must
* be adjacent. The intervals (or bands, or bins) are generally of the same
* size, and are most easily interpreted if they are.
*
* @author Haifeng Li
*/
public class Histogram3D extends Plot {
/**
* The frequencies/probabilities of bins.
*/
private double[][] freq;
/**
* The location of bars.
*/
private double[][] topNW;
private double[][] topNE;
private double[][] topSW;
private double[][] topSE;
private double[][] bottomNW;
private double[][] bottomNE;
private double[][] bottomSW;
private double[][] bottomSE;
/**
* Z-values in camera coordinates.
*/
private double[] zTopNW;
private double[] zTopNE;
private double[] zTopSW;
private double[] zTopSE;
private double[] zBottomNW;
private double[] zBottomNE;
private double[] zBottomSW;
private double[] zBottomSE;
/**
* Average z-values of each surface of bars.
*/
private double[] z;
/**
* Index of surfaces in descending order of z-values.
*/
private int[] order;
/**
* The maximum of the frequency.
*/
private double max;
/**
* The window width of values for each color.
*/
private double width = 1.0;
/**
* The color palette to represent values.
*/
private Color[] palette = null;
/**
* Constructor.
* @param data a sample set.
*/
public Histogram3D(double[][] data) {
this(data, true);
}
/**
* Constructor.
* @param data a sample set.
* @param palette the color palette.
*/
public Histogram3D(double[][] data, Color[] palette) {
this(data, true, palette);
}
/**
* Constructor.
* @param data a sample set.
* @param prob if true, the z-axis will be in the probability scale.
* Otherwise, z-axis will be in the frequency scale.
*/
public Histogram3D(double[][] data, boolean prob) {
this(data, 10);
}
/**
* Constructor.
* @param data a sample set.
* @param prob if true, the z-axis will be in the probability scale.
* Otherwise, z-axis will be in the frequency scale.
* @param palette the color palette.
*/
public Histogram3D(double[][] data, boolean prob, Color[] palette) {
this(data, 10, palette);
}
/**
* Constructor.
* @param data a sample set.
* @param nbins the number of bins.
*/
public Histogram3D(double[][] data, int nbins) {
this(data, nbins, nbins, true);
}
/**
* Constructor.
* @param data a sample set.
* @param nbins the number of bins.
* @param palette the color palette.
*/
public Histogram3D(double[][] data, int nbins, Color[] palette) {
this(data, nbins, nbins, true, palette);
}
/**
* Constructor.
* @param data a sample set.
* @param nbins the number of bins.
* @param prob if true, the z-axis will be in the probability scale.
* Otherwise, z-axis will be in the frequency scale.
*/
public Histogram3D(double[][] data, int nbins, boolean prob) {
this(data, nbins, nbins, prob);
}
/**
* Constructor.
* @param data a sample set.
* @param nbins the number of bins.
* @param prob if true, the z-axis will be in the probability scale.
* Otherwise, z-axis will be in the frequency scale.
* @param palette the color palette.
*/
public Histogram3D(double[][] data, int nbins, boolean prob, Color[] palette) {
this(data, nbins, nbins, prob, palette);
}
/**
* Constructor.
* @param data a sample set.
* @param xbins the number of bins on x-axis.
* @param ybins the number of bins on y-axis.
*/
public Histogram3D(double[][] data, int xbins, int ybins) {
this(data, xbins, ybins, true);
}
/**
* Constructor.
* @param data a sample set.
* @param xbins the number of bins on x-axis.
* @param ybins the number of bins on y-axis.
* @param palette the color palette.
*/
public Histogram3D(double[][] data, int xbins, int ybins, Color[] palette) {
this(data, xbins, ybins, true, palette);
}
/**
* Constructor.
* @param data a sample set.
* @param xbins the number of bins on x-axis.
* @param ybins the number of bins on y-axis.
* @param prob if true, the z-axis will be in the probability scale.
* Otherwise, z-axis will be in the frequency scale.
*/
public Histogram3D(double[][] data, int xbins, int ybins, boolean prob) {
super(Color.LIGHT_GRAY);
init(data, xbins, ybins, prob);
}
/**
* Constructor.
* @param data a sample set.
* @param xbins the number of bins on x-axis.
* @param ybins the number of bins on y-axis.
* @param prob if true, the z-axis will be in the probability scale.
* Otherwise, z-axis will be in the frequency scale.
* @param palette the color palette.
*/
public Histogram3D(double[][] data, int xbins, int ybins, boolean prob, Color[] palette) {
super(Color.LIGHT_GRAY);
this.palette = palette;
init(data, xbins, ybins, prob);
}
/**
* Generate the frequency table.
*/
private void init(double[][] data, int xbins, int ybins, boolean prob) {
// Generate the histogram.
if (data.length == 0) {
throw new IllegalArgumentException("array is empty.");
}
if (data[0].length != 2) {
throw new IllegalArgumentException("dimension is not 2.");
}
double xmin = data[0][0];
double xmax = data[0][0];
double ymin = data[0][1];
double ymax = data[0][1];
for (int i = 1; i < data.length; i++) {
if (xmin > data[i][0]) {
xmin = data[i][0];
}
if (xmax < data[i][0]) {
xmax = data[i][0];
}
if (ymin > data[i][1]) {
ymin = data[i][1];
}
if (ymax < data[i][1]) {
ymax = data[i][1];
}
}
double xspan = xmax - xmin;
double xwidth = xspan / xbins;
double yspan = ymax - ymin;
double ywidth = yspan / ybins;
freq = new double[xbins * ybins][3];
freq[0][0] = xmin + xwidth / 2;
freq[0][1] = ymin + ywidth / 2;
for (int i = 0; i < xbins; i++) {
for (int j = 0; j < ybins; j++) {
freq[j * xbins + i][0] = freq[0][0] + xwidth * i;
freq[j * xbins + i][1] = freq[0][1] + ywidth * j;
}
}
for (int k = 0; k < data.length; k++) {
int i = (int) ((data[k][0] - xmin) / xwidth);
if (i >= xbins) {
i = xbins - 1;
}
int j = (int) ((data[k][1] - ymin) / ywidth);
if (j >= ybins) {
j = ybins - 1;
}
freq[j * xbins + i][2]++;
}
if (prob) {
for (int i = 0; i < freq.length; i++) {
freq[i][2] /= data.length;
}
}
max = Double.NEGATIVE_INFINITY;
for (int i = 0; i < freq.length; i++) {
if (freq[i][2] > max) {
max = freq[i][2];
}
}
if (palette != null) {
width = max / palette.length;
}
// calculate cube coordinates.
topNW = new double[freq.length][3];
topNE = new double[freq.length][3];
topSW = new double[freq.length][3];
topSE = new double[freq.length][3];
bottomNW = new double[freq.length][3];
bottomNE = new double[freq.length][3];
bottomSW = new double[freq.length][3];
bottomSE = new double[freq.length][3];
for (int i = 0; i < freq.length; i++) {
topNW[i][0] = freq[i][0] - xwidth / 2;
topNW[i][1] = freq[i][1] + ywidth / 2;
topNW[i][2] = freq[i][2];
topNE[i][0] = freq[i][0] + xwidth / 2;
topNE[i][1] = freq[i][1] + ywidth / 2;
topNE[i][2] = freq[i][2];
topSW[i][0] = freq[i][0] - xwidth / 2;
topSW[i][1] = freq[i][1] - ywidth / 2;
topSW[i][2] = freq[i][2];
topSE[i][0] = freq[i][0] + xwidth / 2;
topSE[i][1] = freq[i][1] - ywidth / 2;
topSE[i][2] = freq[i][2];
bottomNW[i][0] = freq[i][0] - xwidth / 2;
bottomNW[i][1] = freq[i][1] + ywidth / 2;
bottomNW[i][2] = 0;
bottomNE[i][0] = freq[i][0] + xwidth / 2;
bottomNE[i][1] = freq[i][1] + ywidth / 2;
bottomNE[i][2] = 0;
bottomSW[i][0] = freq[i][0] - xwidth / 2;
bottomSW[i][1] = freq[i][1] - ywidth / 2;
bottomSW[i][2] = 0;
bottomSE[i][0] = freq[i][0] + xwidth / 2;
bottomSE[i][1] = freq[i][1] - ywidth / 2;
bottomSE[i][2] = 0;
}
z = new double[6 * freq.length];
order = new int[6 * freq.length];
zTopNW = new double[freq.length];
zTopNE = new double[freq.length];
zTopSW = new double[freq.length];
zTopSE = new double[freq.length];
zBottomNW = new double[freq.length];
zBottomNE = new double[freq.length];
zBottomSW = new double[freq.length];
zBottomSE = new double[freq.length];
}
@Override
public void paint(Graphics g) {
Color c = g.getColor();
Projection3D p3d = (Projection3D) g.projection;
/**
* Calculates z-axis values in camera coordinates.
*/
for (int i = 0; i < freq.length; i++) {
zTopNW[i] = p3d.z(topNW[i]);
zTopNE[i] = p3d.z(topNE[i]);
zTopSW[i] = p3d.z(topSW[i]);
zTopSE[i] = p3d.z(topSE[i]);
zBottomNW[i] = p3d.z(bottomNW[i]);
zBottomNE[i] = p3d.z(bottomNE[i]);
zBottomSW[i] = p3d.z(bottomSW[i]);
zBottomSE[i] = p3d.z(bottomSE[i]);
}
/**
* Calculate (average) z-value for each surface.
* Note that this is actually just sum, which is sufficient
* for us to sort them.
*/
for (int i = 0, k = 0; i < freq.length; i++, k += 6) {
z[k] = (zTopNW[i] + zTopNE[i] + zTopSE[i] + zTopSW[i]);
z[k + 1] = (zTopNW[i] + zTopNE[i] + zBottomNE[i] + zBottomNW[i]);
z[k + 2] = (zTopSW[i] + zTopSE[i] + zBottomSE[i] + zBottomSW[i]);
z[k + 3] = (zTopNE[i] + zTopSE[i] + zBottomSE[i] + zBottomNE[i]);
z[k + 4] = (zTopNW[i] + zTopSW[i] + zBottomSW[i] + zBottomNW[i]);
z[k + 5] = (zBottomNW[i] + zBottomNE[i] + zBottomSE[i] + zBottomSW[i]);
}
/**
* Sorts surfaces by z-values and paint them from furthest to
* nearest, i.e. painter's algorithm. Although the painter's
* algorithm is computationally and conceptually much easier than
* most alternatives, it does suffer from several flaws. The most
* obvious example of where the painter's algorithm falls short
* is with intersecting surfaces.
*/
for (int i = 0; i < order.length; i++) {
order[i] = i;
}
QuickSort.sort(z, order);
for (int k : order) {
int i = k / 6;
if (topNW[i][2] != bottomNW[i][2]) {
if (palette == null) {
g.setColor(getColor());
} else {
int p = (int) (freq[i][2] / width);
if (p == palette.length) {
p = palette.length - 1;
}
g.setColor(palette[p]);
}
int j = k % 6;
switch (j) {
case 0:
g.fillPolygon(topNW[i], topNE[i], topSE[i], topSW[i]);
g.setColor(Color.BLACK);
g.drawLine(topNW[i], topNE[i]);
g.drawLine(topNE[i], topSE[i]);
g.drawLine(topSE[i], topSW[i]);
g.drawLine(topSW[i], topNW[i]);
break;
case 1:
g.fillPolygon(topNW[i], topNE[i], bottomNE[i], bottomNW[i]);
g.setColor(Color.BLACK);
g.drawLine(topNW[i], topNE[i]);
g.drawLine(bottomNW[i], topNW[i]);
g.drawLine(bottomNE[i], topNE[i]);
g.drawLine(bottomNE[i], bottomNW[i]);
break;
case 2:
g.fillPolygon(topSW[i], topSE[i], bottomSE[i], bottomSW[i]);
g.setColor(Color.BLACK);
g.drawLine(topSW[i], topSE[i]);
g.drawLine(bottomSW[i], topSW[i]);
g.drawLine(bottomSE[i], topSE[i]);
g.drawLine(bottomSE[i], bottomSW[i]);
break;
case 3:
g.fillPolygon(topNE[i], topSE[i], bottomSE[i], bottomNE[i]);
g.setColor(Color.BLACK);
g.drawLine(topNE[i], topSE[i]);
g.drawLine(bottomSE[i], topSE[i]);
g.drawLine(bottomNE[i], topNE[i]);
g.drawLine(bottomSE[i], bottomNE[i]);
break;
case 4:
g.fillPolygon(topNW[i], topSW[i], bottomSW[i], bottomNW[i]);
g.setColor(Color.BLACK);
g.drawLine(topNW[i], topSW[i]);
g.drawLine(bottomNW[i], topNW[i]);
g.drawLine(bottomSW[i], topSW[i]);
g.drawLine(bottomNW[i], bottomSW[i]);
break;
case 5:
g.fillPolygon(bottomNW[i], bottomNE[i], bottomSE[i], bottomSW[i]);
g.setColor(Color.BLACK);
g.drawLine(bottomNW[i], bottomNE[i]);
g.drawLine(bottomNE[i], bottomSE[i]);
g.drawLine(bottomSE[i], bottomSW[i]);
g.drawLine(bottomSW[i], bottomNW[i]);
break;
}
}
}
g.setColor(c);
}
/**
* Get the frequencies/probabilities table.
*/
public double[][] getHistogram() {
return freq;
}
/**
* Create a plot canvas with the histogram plot.
* @param data a sample set.
*/
public static PlotCanvas plot(double[][] data) {
return plot(data);
}
/**
* Create a plot canvas with the histogram plot.
* @param data a sample set.
* @param palette the color palette.
*/
public static PlotCanvas plot(double[][] data, Color[] palette) {
Histogram3D histogram = new Histogram3D(data, palette);
double[] min = Math.colMin(data);
double[] max = Math.colMax(data);
double[] lowerBound = {min[0], min[1], 0};
double[] upperBound = {max[0], max[1], 0};
double[][] freq = histogram.getHistogram();
for (int i = 0; i < freq.length; i++) {
if (freq[i][2] > upperBound[2]) {
upperBound[2] = freq[i][2];
}
}
PlotCanvas canvas = new PlotCanvas(lowerBound, upperBound);
canvas.add(histogram);
return canvas;
}
/**
* Create a plot canvas with the histogram plot of given data.
* @param data a sample set.
* @param k the number of bins.
*/
public static PlotCanvas plot(double[][] data, int k) {
return plot(data, k, null);
}
/**
* Create a plot canvas with the histogram plot of given data.
* @param data a sample set.
* @param k the number of bins.
* @param palette the color palette.
*/
public static PlotCanvas plot(double[][] data, int k, Color[] palette) {
return plot(data, k, false, palette);
}
/**
* Create a plot canvas with the histogram plot of given data.
* @param data a sample set.
* @param k the number of bins.
* @param prob if true, the z-axis will be in the probability scale.
* @param palette the color palette.
*/
public static PlotCanvas plot(double[][] data, int k, boolean prob, Color[] palette) {
return plot(data, k, k, prob, palette);
}
/**
* Create a plot canvas with the histogram plot of given data.
* @param data a sample set.
* @param xbins the number of bins on x-axis.
* @param ybins the number of bins on y-axis.
* @param prob if true, the z-axis will be in the probability scale.
* @param palette the color palette.
*/
public static PlotCanvas plot(double[][] data, int xbins, int ybins, boolean prob, Color[] palette) {
Histogram3D histogram = new Histogram3D(data, xbins, ybins, prob, palette);
double[] min = Math.colMin(data);
double[] max = Math.colMax(data);
double[] lowerBound = {min[0], min[1], 0};
double[] upperBound = {max[0], max[1], 0};
double[][] freq = histogram.getHistogram();
for (int i = 0; i < freq.length; i++) {
if (freq[i][2] > upperBound[2]) {
upperBound[2] = freq[i][2];
}
}
PlotCanvas canvas = new PlotCanvas(lowerBound, upperBound);
canvas.add(histogram);
return canvas;
}
/**
* Create a plot canvas with the histogram plot of given data.
* @param data a sample set.
* @param xbins the number of bins on x-axis.
* @param ybins the number of bins on y-axis.
*/
public static PlotCanvas plot(double[][] data, int xbins, int ybins) {
return plot(data, xbins, ybins, null);
}
/**
* Create a plot canvas with the histogram plot of given data.
* @param data a sample set.
* @param xbins the number of bins on x-axis.
* @param ybins the number of bins on y-axis.
* @param palette the color palette.
*/
public static PlotCanvas plot(double[][] data, int xbins, int ybins, Color[] palette) {
Histogram3D histogram = new Histogram3D(data, xbins, ybins, palette);
double[] min = Math.colMin(data);
double[] max = Math.colMax(data);
double[] lowerBound = {min[0], min[1], 0};
double[] upperBound = {max[0], max[1], 0};
double[][] freq = histogram.getHistogram();
for (int i = 0; i < freq.length; i++) {
if (freq[i][2] > upperBound[2]) {
upperBound[2] = freq[i][2];
}
}
PlotCanvas canvas = new PlotCanvas(lowerBound, upperBound);
canvas.add(histogram);
return canvas;
}
}
Ads help maintain this website.