/* ===========================================================
* Orson Charts : a 3D chart library for the Java(tm) platform
* ===========================================================
*
* (C)opyright 2013-2016, by Object Refinery Limited. All rights reserved.
*
* http://www.object-refinery.com/orsoncharts/index.html
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
* Other names may be trademarks of their respective owners.]
*
* If you do not wish to be bound by the terms of the GPL, an alternative
* commercial license can be purchased. For details, please see visit the
* Orson Charts home page:
*
* http://www.object-refinery.com/orsoncharts/index.html
*
*/
package com.orsoncharts.legend;
import java.awt.Shape;
import java.util.ArrayList;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.geom.Dimension2D;
import java.awt.geom.Rectangle2D;
import java.util.List;
import java.util.Map;
import java.awt.FontMetrics;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import com.orsoncharts.renderer.ColorScale;
import com.orsoncharts.table.AbstractTableElement;
import com.orsoncharts.table.ElementDimension;
import com.orsoncharts.table.TableElement;
import com.orsoncharts.util.Orientation;
import com.orsoncharts.util.TextAnchor;
import com.orsoncharts.util.TextUtils;
import com.orsoncharts.Range;
import com.orsoncharts.table.TableElementOnDraw;
import com.orsoncharts.table.TableElementVisitor;
import com.orsoncharts.util.ArgChecks;
import com.orsoncharts.util.Fit2D;
/**
* A {@link TableElement} that displays a {@link ColorScale}.
* <br><br>
* NOTE: This class is serializable, but the serialization format is subject
* to change in future releases and should not be relied upon for persisting
* instances of this class.
*
* @since 1.1
*/
@SuppressWarnings("serial")
public class ColorScaleElement extends AbstractTableElement
implements TableElement {
/** The color scale. */
private ColorScale scale;
/** The orientation (horizontal or vertical). */
private Orientation orientation;
/** The length of the bar. */
private double barLength;
/** The width of the bar. */
private double barWidth;
/** The gap between the color scale bar and the text labels. */
private double textOffset;
/** The font for the text labels. */
private Font font;
/** The text color. */
private Color textColor;
/** The number formatter. */
private NumberFormat formatter;
/**
* Creates a new {@code ColorScaleElement} with the specified
* attributes.
*
* @param scale the color scale ({@code null} not permitted).
* @param orientation the orientation ({@code null} not permitted).
* @param barWidth the bar width (in Java2D units).
* @param barLength the bar length (in Java2D units).
* @param font the font ({@code null} not permitted).
* @param textColor the text color ({@code null} not permitted).
*
* @since 1.2
*/
public ColorScaleElement(ColorScale scale, Orientation orientation,
double barWidth, double barLength, Font font, Color textColor) {
super();
ArgChecks.nullNotPermitted(scale, "scale");
ArgChecks.nullNotPermitted(orientation, "orientation");
ArgChecks.nullNotPermitted(font, "font");
this.scale = scale;
this.orientation = orientation;
this.barWidth = barWidth;
this.barLength = barLength;
this.textOffset = 2;
this.font = font;
this.textColor = textColor;
this.formatter = new DecimalFormat("0.00");
}
/**
* Returns the color scale.
*
* @return The color scale (never {@code null}).
*/
public ColorScale getColorScale() {
return this.scale;
}
/**
* Returns the orientation.
*
* @return The orientation (never {@code null}).
*/
public Orientation getOrientation() {
return this.orientation;
}
/**
* Returns the bar width.
*
* @return The bar width.
*/
public double getBarWidth() {
return this.barWidth;
}
/**
* Returns the bar length.
*
* @return The bar length.
*/
public double getBarLength() {
return this.barLength;
}
/**
* Returns the font used to display the labels on the color scale.
*
* @return The font (never {@code null}).
*/
public Font getFont() {
return this.font;
}
/**
* Returns the text color.
*
* @return The text color (never {@code null}).
*/
public Color getTextColor() {
return this.textColor;
}
/**
* Receives a visitor. This is part of a general mechanism to perform
* operations on an arbitrary hierarchy of table elements. You will not
* normally call this method directly.
*
* @param visitor the visitor ({@code null} not permitted).
*
* @since 1.2
*/
@Override
public void receive(TableElementVisitor visitor) {
visitor.visit(this);
}
/**
* Returns the preferred size for this element.
*
* @param g2 the graphics target.
* @param bounds the available drawing space.
*
* @return The preferred size (never {@code null}).
*/
@Override
public Dimension2D preferredSize(Graphics2D g2, Rectangle2D bounds) {
return preferredSize(g2, bounds, null);
}
/**
* Returns the preferred size for this element.
*
* @param g2 the graphics target.
* @param bounds the available drawing space.
* @param constraints layout constraints (ignored here).
*
* @return The preferred size (never {@code null}).
*/
@Override
public Dimension2D preferredSize(Graphics2D g2, Rectangle2D bounds,
Map<String, Object> constraints) {
g2.setFont(this.font);
FontMetrics fm = g2.getFontMetrics();
Range r = this.scale.getRange();
String minStr = this.formatter.format(r.getMin());
String maxStr = this.formatter.format(r.getMax());
Rectangle2D minStrBounds = TextUtils.getTextBounds(minStr, fm);
Rectangle2D maxStrBounds = TextUtils.getTextBounds(maxStr, fm);
double maxStrWidth = Math.max(minStrBounds.getWidth(),
maxStrBounds.getWidth());
Insets insets = getInsets();
double w, h;
if (this.orientation == Orientation.HORIZONTAL) {
w = Math.min(this.barLength + insets.left + insets.right,
bounds.getWidth());
h = Math.min(insets.top + this.barWidth + this.textOffset
+ minStrBounds.getHeight() + insets.bottom,
bounds.getHeight());
} else {
w = Math.min(insets.left + this.barWidth + this.textOffset
+ maxStrWidth + insets.right, bounds.getWidth());
h = Math.min(insets.top + this.barLength + this.textOffset
+ minStrBounds.getHeight() + insets.bottom,
bounds.getHeight());
}
return new ElementDimension(w, h);
}
@Override
public List<Rectangle2D> layoutElements(Graphics2D g2, Rectangle2D bounds,
Map<String, Object> constraints) {
List<Rectangle2D> result = new ArrayList<Rectangle2D>(1);
Dimension2D prefDim = preferredSize(g2, bounds);
Fit2D fitter = Fit2D.getNoScalingFitter(getRefPoint());
Rectangle2D dest = fitter.fit(prefDim, bounds);
result.add(dest);
return result;
}
/**
* Draws the element within the specified bounds.
*
* @param g2 the graphics target ({@code null} not permitted).
* @param bounds the bounds ({@code null} not permitted).
*/
@Override
public void draw(Graphics2D g2, Rectangle2D bounds) {
draw(g2, bounds, null);
}
/**
* Draws the element within the specified bounds.
*
* @param g2 the graphics target ({@code null} not permitted).
* @param bounds the bounds ({@code null} not permitted).
* @param onDrawHandler receives notification before and after the element
* is drawn ({@code null} permitted);
*
* @since 1.3
*/
@Override
public void draw(Graphics2D g2, Rectangle2D bounds,
TableElementOnDraw onDrawHandler) {
if (onDrawHandler != null) {
onDrawHandler.beforeDraw(this, g2, bounds);
}
Shape savedClip = g2.getClip();
g2.clip(bounds);
List<Rectangle2D> layoutInfo = layoutElements(g2, bounds, null);
Rectangle2D dest = layoutInfo.get(0);
if (getBackground() != null) {
getBackground().fill(g2, dest);
}
g2.setFont(this.font);
FontMetrics fm = g2.getFontMetrics();
Range r = this.scale.getRange();
String minStr = this.formatter.format(r.getMin());
String maxStr = this.formatter.format(r.getMax());
Rectangle2D minStrBounds = TextUtils.getTextBounds(minStr, fm);
Rectangle2D maxStrBounds = TextUtils.getTextBounds(maxStr, fm);
Insets insets = getInsets();
if (this.orientation == Orientation.HORIZONTAL) {
double x0 = dest.getX() + insets.left
+ minStrBounds.getWidth() / 2.0;
double x1 = dest.getMaxX() - insets.right
- maxStrBounds.getWidth() / 2.0;
double y0 = dest.getY() + insets.top;
double y1 = y0 + this.barWidth;
drawHorizontalScale(this.scale, g2, new Rectangle2D.Double(
(int) x0, (int) y0, (int) (x1 - x0), (int) this.barWidth));
// fill the bar with the color scale
g2.setPaint(this.textColor);
TextUtils.drawAlignedString(minStr, g2, (float) x0,
(float) (y1 + this.textOffset), TextAnchor.TOP_CENTER);
TextUtils.drawAlignedString(maxStr, g2, (float) x1,
(float) (y1 + this.textOffset), TextAnchor.TOP_CENTER);
} else { // VERTICAL
double maxStrWidth = Math.max(minStrBounds.getWidth(),
maxStrBounds.getWidth());
double x1 = dest.getMaxX() - insets.right - maxStrWidth
- this.textOffset;
double x0 = x1 - this.barWidth;
double y0 = dest.getY() + insets.top
+ maxStrBounds.getHeight() / 2.0;
double y1 = y0 + this.barLength;
drawVerticalScale(this.scale, g2, new Rectangle2D.Double(
(int) x0, (int) y0, (int) (x1 - x0), (int) this.barLength));
g2.setPaint(this.textColor);
TextUtils.drawAlignedString(minStr, g2,
(float) (x1 + this.textOffset), (float) y1,
TextAnchor.HALF_ASCENT_LEFT);
TextUtils.drawAlignedString(maxStr, g2,
(float) (x1 + this.textOffset), (float) y0,
TextAnchor.HALF_ASCENT_LEFT);
}
g2.setClip(savedClip);
if (onDrawHandler != null) {
onDrawHandler.afterDraw(this, g2, bounds);
}
}
/**
* Draws the color scale horizontally within the specified bounds.
*
* @param colorScale the color scale.
* @param g2 the graphics target.
* @param bounds the bounds.
*/
private void drawHorizontalScale(ColorScale colorScale, Graphics2D g2,
Rectangle2D bounds) {
g2.setStroke(new BasicStroke(1.0f));
for (int x = (int) bounds.getX(); x < bounds.getMaxX(); x++) {
double p = (x - bounds.getX()) / bounds.getWidth();
double value = colorScale.getRange().value(p);
g2.setColor(colorScale.valueToColor(value));
g2.drawLine(x, (int) bounds.getMinY(), x, (int) bounds.getMaxY());
}
}
/**
* Draws the color scale vertically within the specified bounds.
*
* @param colorScale the color scale.
* @param g2 the graphics target.
* @param bounds the bounds.
*/
private void drawVerticalScale(ColorScale colorScale, Graphics2D g2,
Rectangle2D bounds) {
g2.setStroke(new BasicStroke(1.0f));
for (int y = (int) bounds.getY(); y < bounds.getMaxY(); y++) {
double p = (y - bounds.getY()) / bounds.getHeight();
double value = colorScale.getRange().value(1 - p);
g2.setColor(this.scale.valueToColor(value));
g2.drawLine((int) bounds.getX(), y, (int) bounds.getMaxX(), y);
}
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ColorScaleElement)) {
return false;
}
ColorScaleElement that = (ColorScaleElement) obj;
if (!this.scale.equals(that.scale)) {
return false;
}
if (!this.orientation.equals(that.orientation)) {
return false;
}
if (this.barLength != that.barLength) {
return false;
}
if (this.barWidth != that.barWidth) {
return false;
}
if (!this.font.equals(that.font)) {
return false;
}
return super.equals(obj);
}
}