/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
* http://www.geo-solutions.it/
* Copyright 2014 GeoSolutions
* 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 it.geosolutions.rendered.viewer;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import javax.media.jai.Histogram;
import javax.media.jai.PlanarImage;
import javax.media.jai.operator.HistogramDescriptor;
import javax.swing.JComponent;
/**
* This class displays a histogram (instance of Histogram) as a component.
* Only the first histogram band ins considered for plotting.
* The component has a tooltip which displays the bin index and bin count for the
* bin under the mouse cursor.
*/
public class DisplayHistogram extends JComponent implements MouseMotionListener
{
/**
*
*/
private static final long serialVersionUID = -8640931037312978101L;
// The histogram and its title.
private Histogram histogram;
private String title;
// Some data and hints for the histogram plot.
private int[] counts;
private double maxCount;
private int indexMultiplier = 1;
private int skipIndexes = 8;
// The components' dimensions.
private int width, height = 250;
// Some constants for this component.
private int verticalTicks = 10;
private Insets border = new Insets(40, 70, 40, 30);
private int binWidth = 3;
private Color backgroundColor = Color.BLACK;
private Color barColor = new Color(255, 255, 200);
private Color marksColor = new Color(100, 180, 255);
private Font fontSmall = new Font("monospaced", 0, 10);
private Font fontLarge = new Font("default", Font.ITALIC, 20);
/**
* The constructor for this class, which will set its fields' values and get some information
* about the histogram.
* @param histogram the histogram to be plotted.
* @param title the title of the plot.
*/
public DisplayHistogram(Histogram histogram, String title)
{
this(title);
setHistogram(histogram);
}
public DisplayHistogram(String title)
{
this.title = title;
addMouseMotionListener(this);
}
private void setHistogram(Histogram histogram)
{
this.histogram = histogram;
if(histogram != null) {
// Calculate the components dimensions.
width = histogram.getNumBins(0) * binWidth;
// Get the histogram data.
counts = histogram.getBins(0);
// Get the max and min counts.
maxCount = Integer.MIN_VALUE;
for (int c = 0; c < counts.length; c++)
{
maxCount = Math.max(maxCount, counts[c]);
}
}
repaint();
}
/**
* Override the default bin width (for plotting)
*/
public void setBinWidth(int newWidth)
{
binWidth = newWidth;
width = histogram.getNumBins(0) * binWidth;
}
/**
* Override the default height for the plot.
* @param h the new height.
*/
public void setHeight(int h)
{
height = h;
}
/**
* Override the index multiplying factor (for bins with width != 1)
*/
public void setIndexMultiplier(int i)
{
indexMultiplier = i;
}
/**
* Override the index skipping factor (determines how many labels will be
* printed on the index axis).
*/
public void setSkipIndexes(int i)
{
skipIndexes = i;
}
/**
* Set the maximum value (used to scale the histogram y-axis). The default value
* is defined in the constructor and can be overriden with this method.
*/
public void setMaxCount(int m)
{
maxCount = m;
}
/**
* This method informs the maximum size of this component, which will be the same as the preferred size.
*/
@Override
public Dimension getMaximumSize()
{
return getPreferredSize();
}
/**
* This method informs the minimum size of this component, which will be the same as the preferred size.
*/
@Override
public Dimension getMinimumSize()
{
return getPreferredSize();
}
/**
* This method informs the preferred size of this component, which will be constant.
*/
@Override
public Dimension getPreferredSize()
{
return new Dimension(width + border.left + border.right, height + border.top + border.bottom);
}
/**
* This method will paint the component.
*/
@Override
protected void paintComponent(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
// Draw the background.
g2d.setColor(backgroundColor);
g2d.fillRect(0, 0, getSize().width, getSize().height);
// Draw some marks.
g2d.setColor(marksColor);
g2d.drawRect(border.left, border.top, width, height);
// Draw the histogram bars.
g2d.setColor(barColor);
if(histogram != null) {
for (int bin = 0; bin < histogram.getNumBins(0); bin++)
{
int x = border.left + (bin * binWidth);
double barStarts = border.top + (height * (maxCount - counts[bin]) / (1.0 * maxCount));
double barEnds = Math.ceil(height * counts[bin] / (1.0 * maxCount));
g2d.drawRect(x, (int) barStarts, binWidth, (int) barEnds);
}
// Draw the values on the horizontal axis. We will plot only 1/8th of them.
g2d.setColor(marksColor);
g2d.setFont(fontSmall);
FontMetrics metrics = g2d.getFontMetrics();
int halfFontHeight = metrics.getHeight() / 2;
for (int bin = 0; bin <= histogram.getNumBins(0); bin++)
{
if ((bin % skipIndexes) == 0)
{
String label = String.valueOf((indexMultiplier * bin));
int textHeight = metrics.stringWidth(label); // remember it will be rotated!
g2d.translate(border.left + (bin * binWidth) + halfFontHeight, border.top + height + textHeight + 2);
g2d.rotate(-Math.PI / 2);
g2d.drawString(label, 0, 0);
g2d.rotate(Math.PI / 2);
g2d.translate(-(border.left + (bin * binWidth) + halfFontHeight),
-(border.top + height + textHeight + 2));
}
}
// Draw the values on the vertical axis. Let's draw only some of them.
double step = (int) (maxCount / verticalTicks);
for (int l = 0; l <= verticalTicks; l++) // last will be done separately
{
String label;
if (l == verticalTicks)
{
label = String.valueOf(maxCount);
}
else
{
label = String.valueOf((l * step));
}
int textWidth = metrics.stringWidth(label);
g2d.drawString(label, border.left - 2 - textWidth, border.top + height - (l * (height / verticalTicks)));
}
// Draw the title.
g2d.setFont(fontLarge);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
metrics = g2d.getFontMetrics();
int textWidth = metrics.stringWidth(title);
g2d.drawString(title, (border.left + width + border.right - textWidth) / 2, 28);
}
}
/**
* This method does not do anything, it is here to keep the MouseMotionListener interface happy.
*/
public void mouseDragged(MouseEvent e)
{
}
/**
* This method will be called when the mouse is moved over the component. It will
* set the tooltip text on the component to show the histogram data.
*/
public void mouseMoved(MouseEvent e)
{
int x = e.getX();
int y = e.getY();
// Don't show anything out of the plot region.
if ((x > border.left) && (x < (border.left + width)) && (y > border.top) && (y < (border.top + height)))
{
// Convert the X to an index on the histogram.
x = (x - border.left) / binWidth;
y = counts[x];
setToolTipText((indexMultiplier * x) + ": " + y);
}
else
{
setToolTipText(null);
}
}
public void setImage(PlanarImage wrapRenderedImage)
{
try {
setHistogram((Histogram) HistogramDescriptor.create(
wrapRenderedImage,
null,
1,
1,
new int[] { 65536 },
new double[] { 0 },
new double[] { 65535 },
null).getProperty("histogram"));
} catch(Exception e) {
e.printStackTrace();
setHistogram(null);
}
}
} // end class