/*******************************************************************************
* 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 java.util.Arrays;
import smile.math.Math;
/**
* Hexmap is a variant of heat map by replacing rectangle cells with hexagon cells.
*
* @author Haifeng Li
*/
public class Hexmap extends Plot {
/**
* The two-dimensional data matrix.
*/
private double[][] z;
/**
* Descriptions for each cell in data matrix.
*/
private String[][] labels;
/**
* The coordinates of hexagons for each cell in data matrix.
*/
private double[][][][] hexagon;
/**
* The minimum of the data.
*/
private double min;
/**
* The minimum of the data.
*/
private double max;
/**
* The window width of values for each color.
*/
private double width;
/**
* The color palette to represent values.
*/
private Color[] palette;
/**
* Constructor. Use 16-color jet color palette.
* @param z a data matrix to be shown in hexmap.
*/
public Hexmap(double[][] z) {
this(z, 16);
}
/**
* Constructor. Use jet color palette.
* @param z a data matrix to be shown in hexmap.
* @param k the number of colors in the palette.
*/
public Hexmap(double[][] z, int k) {
this(z, Palette.jet(k, 1.0f));
}
/**
* Constructor.
* @param z a data matrix to be shown in hexmap.
* @param palette the color palette.
*/
public Hexmap(double[][] z, Color[] palette) {
this(null, z, palette);
}
/**
* Constructor. Use 16-color jet color palette.
* @param labels the descriptions of each cell in the data matrix.
* @param z a data matrix to be shown in hexmap.
*/
public Hexmap(String[][] labels, double[][] z) {
this(labels, z, 16);
}
/**
* Constructor. Use jet color palette.
* @param labels the descriptions of each cell in the data matrix.
* @param z a data matrix to be shown in hexmap.
* @param k the number of colors in the palette.
*/
public Hexmap(String[][] labels, double[][] z, int k) {
this(labels, z, Palette.jet(k, 1.0f));
}
/**
* Constructor.
* @param labels the descriptions of each cell in the data matrix.
* @param z a data matrix to be shown in hexmap.
* @param palette the color palette.
*/
public Hexmap(String[][] labels, double[][] z, Color[] palette) {
this.labels = labels;
this.z = z;
this.palette = palette;
double s = Math.sqrt(0.75);
hexagon = new double[z.length][z[0].length][6][2];
for (int i = 0; i < z.length; i++) {
for (int j = 0; j < z[i].length; j++) {
for (int r = 0; r < hexagon[i][j].length; r++) {
double a = Math.PI / 3.0 * r;
hexagon[i][j][r][0] = j + Math.sin(a)/2;
if (i % 2 == 1) hexagon[i][j][r][0] += 0.5;
hexagon[i][j][r][1] = (z.length-i)*s + Math.cos(a)/2;
}
}
}
// In case of outliers, we use 1% and 99% quantiles as lower and
// upper limits instead of min and max.
int n = z.length * z[0].length;
double[] values = new double[n];
int i = 0;
for (double[] zi : z) {
for (double zij : zi) {
if (!Double.isNaN(zij)) {
values[i++] = zij;
}
}
}
if (i > 0) {
Arrays.sort(values, 0, i);
min = values[(int)Math.round(0.01 * i)];
max = values[(int)Math.round(0.99 * (i-1))];
width = (max - min) / palette.length;
}
}
@Override
public String getToolTip(double[] coord) {
if (labels == null) {
return null;
}
if (coord[0] < -0.5 || coord[0] > z[0].length || coord[1] < 0.36 || coord[1] > z.length * 0.87 + 0.5) {
return null;
}
int x = (int) (coord[0] + 0.5);
int y = (int) (z.length - (coord[1]-0.5) / 0.87);
for (int i = -3; i < 3; i++) {
for (int j = -3; j < 3; j++) {
int xi = x + i;
int yj = y + j;
if (xi >= 0 && xi < hexagon[0].length && yj >= 0 && yj < hexagon.length) {
if (Math.contains(hexagon[yj][xi], coord)) {
return labels[yj][xi];
}
}
}
}
return null;
}
@Override
public void paint(Graphics g) {
Color c = g.getColor();
for (int i = 0; i < z.length; i++) {
for (int j = 0; j < z[i].length; j++) {
if (Double.isNaN(z[i][j])) {
g.setColor(Color.WHITE);
} else {
int k = (int) ((z[i][j] - min) / width);
if (k < 0) {
k = 0;
}
if (k >= palette.length) {
k = palette.length - 1;
}
g.setColor(palette[k]);
}
g.fillPolygon(hexagon[i][j]);
}
}
g.clearClip();
double height = 0.7 / palette.length;
double[] start = new double[2];
start[0] = 1.1;
start[1] = 0.15;
double[] end = new double[2];
end[0] = 1.13;
end[1] = start[1] - height;
for (int i = 0; i < palette.length; i++) {
g.setColor(palette[i]);
g.fillRectBaseRatio(start, end);
start[1] += height;
end[1] += height;
}
g.setColor(Color.BLACK);
start[1] -= height;
end[1] = 0.15 - height;
g.drawRectBaseRatio(start, end);
start[0] = 1.14;
double log = Math.log10(Math.abs(max));
int decimal = 1;
if (log < 0) decimal = (int) -log + 1;
g.drawTextBaseRatio(String.valueOf(Math.round(max, decimal)), 0.0, 1.0, start);
start[1] = 0.15 - height;
log = Math.log10(Math.abs(min));
decimal = 1;
if (log < 0) decimal = (int) -log + 1;
g.drawTextBaseRatio(String.valueOf(Math.round(min, decimal)), 0.0, 0.0, start);
g.setColor(c);
}
/**
* Create a plot canvas with the pseudo hexmap plot of given data.
* @param data a data matrix to be shown in hexmap.
*/
public static PlotCanvas plot(double[][] data) {
double[] lowerBound = {-0.5, 0.36};
double[] upperBound = {data[0].length, data.length * 0.87 + 0.5};
PlotCanvas canvas = new PlotCanvas(lowerBound, upperBound, false);
canvas.add(new Hexmap(data));
canvas.getAxis(0).setFrameVisible(false);
canvas.getAxis(0).setLabelVisible(false);
canvas.getAxis(0).setGridVisible(false);
canvas.getAxis(1).setFrameVisible(false);
canvas.getAxis(1).setLabelVisible(false);
canvas.getAxis(1).setGridVisible(false);
return canvas;
}
/**
* Create a plot canvas with the pseudo hexmap plot of given data.
* @param data a data matrix to be shown in hexmap.
* @param palette the color palette.
*/
public static PlotCanvas plot(double[][] data, Color[] palette) {
double[] lowerBound = {-0.5, 0.36};
double[] upperBound = {data[0].length, data.length * 0.87 + 0.5};
PlotCanvas canvas = new PlotCanvas(lowerBound, upperBound, false);
canvas.add(new Hexmap(data, palette));
canvas.getAxis(0).setFrameVisible(false);
canvas.getAxis(0).setLabelVisible(false);
canvas.getAxis(0).setGridVisible(false);
canvas.getAxis(1).setFrameVisible(false);
canvas.getAxis(1).setLabelVisible(false);
canvas.getAxis(1).setGridVisible(false);
return canvas;
}
/**
* Create a plot canvas with the pseudo hexmap plot of given data.
* @param labels the descriptions of each cell in the data matrix.
* @param data a data matrix to be shown in hexmap.
*/
public static PlotCanvas plot(String[][] labels, double[][] data) {
double[] lowerBound = {-0.5, 0.36};
double[] upperBound = {data[0].length, data.length * 0.87 + 0.5};
PlotCanvas canvas = new PlotCanvas(lowerBound, upperBound, false);
canvas.add(new Hexmap(labels, data));
canvas.getAxis(0).setFrameVisible(false);
canvas.getAxis(0).setLabelVisible(false);
canvas.getAxis(0).setGridVisible(false);
canvas.getAxis(1).setFrameVisible(false);
canvas.getAxis(1).setLabelVisible(false);
canvas.getAxis(1).setGridVisible(false);
return canvas;
}
/**
* Create a plot canvas with the pseudo hexmap plot of given data.
* @param labels the descriptions of each cell in the data matrix.
* @param data a data matrix to be shown in hexmap.
* @param palette the color palette.
*/
public static PlotCanvas plot(String[][] labels, double[][] data, Color[] palette) {
double[] lowerBound = {-0.5, 0.36};
double[] upperBound = {data[0].length, data.length * 0.87 + 0.5};
PlotCanvas canvas = new PlotCanvas(lowerBound, upperBound, false);
canvas.add(new Hexmap(labels, data, palette));
canvas.getAxis(0).setFrameVisible(false);
canvas.getAxis(0).setLabelVisible(false);
canvas.getAxis(0).setGridVisible(false);
canvas.getAxis(1).setFrameVisible(false);
canvas.getAxis(1).setLabelVisible(false);
canvas.getAxis(1).setGridVisible(false);
return canvas;
}
}