org.jfree.chart.title.PaintScaleLegend Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jfreechart Show documentation
Show all versions of jfreechart Show documentation
JFreeChart is a class library, written in Java, for generating charts.
Utilising the Java2D API, it supports a wide range of chart types including
bar charts, pie charts, line charts, XY-plots, time series plots, Sankey charts
and more.
/* ===========================================================
* JFreeChart : a free chart library for the Java(tm) platform
* ===========================================================
*
* (C) Copyright 2000-present, by David Gilbert and Contributors.
*
* Project Info: http://www.jfree.org/jfreechart/index.html
*
* This library 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 2.1 of the License, or
* (at your option) any later version.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
* Other names may be trademarks of their respective owners.]
*
* ---------------------
* PaintScaleLegend.java
* ---------------------
* (C) Copyright 2007-present, by David Gilbert.
*
* Original Author: David Gilbert;
* Contributor(s): Peter Kolb - see patch 2686872;
* Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier);
*
*/
package org.jfree.chart.title;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Stroke;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Objects;
import org.jfree.chart.HashUtils;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.AxisSpace;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.block.LengthConstraintType;
import org.jfree.chart.block.RectangleConstraint;
import org.jfree.chart.event.AxisChangeEvent;
import org.jfree.chart.event.AxisChangeListener;
import org.jfree.chart.event.TitleChangeEvent;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.PaintScale;
import org.jfree.chart.ui.RectangleEdge;
import org.jfree.chart.ui.Size2D;
import org.jfree.chart.util.PaintUtils;
import org.jfree.chart.util.Args;
import org.jfree.chart.util.PublicCloneable;
import org.jfree.chart.util.SerialUtils;
import org.jfree.data.Range;
/**
* A legend that shows a range of values and their associated colors, driven
* by an underlying {@link PaintScale} implementation.
*/
public class PaintScaleLegend extends Title implements AxisChangeListener,
PublicCloneable {
/** For serialization. */
static final long serialVersionUID = -1365146490993227503L;
/** The paint scale (never {@code null}). */
private PaintScale scale;
/** The value axis (never {@code null}). */
private ValueAxis axis;
/**
* The axis location (handles both orientations, never
* {@code null}).
*/
private AxisLocation axisLocation;
/** The offset between the axis and the paint strip (in Java2D units). */
private double axisOffset;
/** The thickness of the paint strip (in Java2D units). */
private double stripWidth;
/**
* A flag that controls whether or not an outline is drawn around the
* paint strip.
*/
private boolean stripOutlineVisible;
/** The paint used to draw an outline around the paint strip. */
private transient Paint stripOutlinePaint;
/** The stroke used to draw an outline around the paint strip. */
private transient Stroke stripOutlineStroke;
/** The background paint (never {@code null}). */
private transient Paint backgroundPaint;
/**
* The number of subdivisions for the scale when rendering.
*/
private int subdivisions;
/**
* Creates a new instance.
*
* @param scale the scale ({@code null} not permitted).
* @param axis the axis ({@code null} not permitted).
*/
public PaintScaleLegend(PaintScale scale, ValueAxis axis) {
Args.nullNotPermitted(axis, "axis");
this.scale = scale;
this.axis = axis;
this.axis.addChangeListener(this);
this.axisLocation = AxisLocation.BOTTOM_OR_LEFT;
this.axisOffset = 0.0;
this.axis.setRange(scale.getLowerBound(), scale.getUpperBound());
this.stripWidth = 15.0;
this.stripOutlineVisible = true;
this.stripOutlinePaint = Color.GRAY;
this.stripOutlineStroke = new BasicStroke(0.5f);
this.backgroundPaint = Color.WHITE;
this.subdivisions = 100;
}
/**
* Returns the scale used to convert values to colors.
*
* @return The scale (never {@code null}).
*
* @see #setScale(PaintScale)
*/
public PaintScale getScale() {
return this.scale;
}
/**
* Sets the scale and sends a {@link TitleChangeEvent} to all registered
* listeners.
*
* @param scale the scale ({@code null} not permitted).
*
* @see #getScale()
*/
public void setScale(PaintScale scale) {
Args.nullNotPermitted(scale, "scale");
this.scale = scale;
notifyListeners(new TitleChangeEvent(this));
}
/**
* Returns the axis for the paint scale.
*
* @return The axis (never {@code null}).
*
* @see #setAxis(ValueAxis)
*/
public ValueAxis getAxis() {
return this.axis;
}
/**
* Sets the axis for the paint scale and sends a {@link TitleChangeEvent}
* to all registered listeners.
*
* @param axis the axis ({@code null} not permitted).
*
* @see #getAxis()
*/
public void setAxis(ValueAxis axis) {
Args.nullNotPermitted(axis, "axis");
this.axis.removeChangeListener(this);
this.axis = axis;
this.axis.addChangeListener(this);
notifyListeners(new TitleChangeEvent(this));
}
/**
* Returns the axis location.
*
* @return The axis location (never {@code null}).
*
* @see #setAxisLocation(AxisLocation)
*/
public AxisLocation getAxisLocation() {
return this.axisLocation;
}
/**
* Sets the axis location and sends a {@link TitleChangeEvent} to all
* registered listeners.
*
* @param location the location ({@code null} not permitted).
*
* @see #getAxisLocation()
*/
public void setAxisLocation(AxisLocation location) {
Args.nullNotPermitted(location, "location");
this.axisLocation = location;
notifyListeners(new TitleChangeEvent(this));
}
/**
* Returns the offset between the axis and the paint strip.
*
* @return The offset between the axis and the paint strip.
*
* @see #setAxisOffset(double)
*/
public double getAxisOffset() {
return this.axisOffset;
}
/**
* Sets the offset between the axis and the paint strip and sends a
* {@link TitleChangeEvent} to all registered listeners.
*
* @param offset the offset.
*/
public void setAxisOffset(double offset) {
this.axisOffset = offset;
notifyListeners(new TitleChangeEvent(this));
}
/**
* Returns the width of the paint strip, in Java2D units.
*
* @return The width of the paint strip.
*
* @see #setStripWidth(double)
*/
public double getStripWidth() {
return this.stripWidth;
}
/**
* Sets the width of the paint strip and sends a {@link TitleChangeEvent}
* to all registered listeners.
*
* @param width the width.
*
* @see #getStripWidth()
*/
public void setStripWidth(double width) {
this.stripWidth = width;
notifyListeners(new TitleChangeEvent(this));
}
/**
* Returns the flag that controls whether or not an outline is drawn
* around the paint strip.
*
* @return A boolean.
*
* @see #setStripOutlineVisible(boolean)
*/
public boolean isStripOutlineVisible() {
return this.stripOutlineVisible;
}
/**
* Sets the flag that controls whether or not an outline is drawn around
* the paint strip, and sends a {@link TitleChangeEvent} to all registered
* listeners.
*
* @param visible the flag.
*
* @see #isStripOutlineVisible()
*/
public void setStripOutlineVisible(boolean visible) {
this.stripOutlineVisible = visible;
notifyListeners(new TitleChangeEvent(this));
}
/**
* Returns the paint used to draw the outline of the paint strip.
*
* @return The paint (never {@code null}).
*
* @see #setStripOutlinePaint(Paint)
*/
public Paint getStripOutlinePaint() {
return this.stripOutlinePaint;
}
/**
* Sets the paint used to draw the outline of the paint strip, and sends
* a {@link TitleChangeEvent} to all registered listeners.
*
* @param paint the paint ({@code null} not permitted).
*
* @see #getStripOutlinePaint()
*/
public void setStripOutlinePaint(Paint paint) {
Args.nullNotPermitted(paint, "paint");
this.stripOutlinePaint = paint;
notifyListeners(new TitleChangeEvent(this));
}
/**
* Returns the stroke used to draw the outline around the paint strip.
*
* @return The stroke (never {@code null}).
*
* @see #setStripOutlineStroke(Stroke)
*/
public Stroke getStripOutlineStroke() {
return this.stripOutlineStroke;
}
/**
* Sets the stroke used to draw the outline around the paint strip and
* sends a {@link TitleChangeEvent} to all registered listeners.
*
* @param stroke the stroke ({@code null} not permitted).
*
* @see #getStripOutlineStroke()
*/
public void setStripOutlineStroke(Stroke stroke) {
Args.nullNotPermitted(stroke, "stroke");
this.stripOutlineStroke = stroke;
notifyListeners(new TitleChangeEvent(this));
}
/**
* Returns the background paint.
*
* @return The background paint.
*/
public Paint getBackgroundPaint() {
return this.backgroundPaint;
}
/**
* Sets the background paint and sends a {@link TitleChangeEvent} to all
* registered listeners.
*
* @param paint the paint ({@code null} permitted).
*/
public void setBackgroundPaint(Paint paint) {
this.backgroundPaint = paint;
notifyListeners(new TitleChangeEvent(this));
}
/**
* Returns the number of subdivisions used to draw the scale.
*
* @return The subdivision count.
*/
public int getSubdivisionCount() {
return this.subdivisions;
}
/**
* Sets the subdivision count and sends a {@link TitleChangeEvent} to
* all registered listeners.
*
* @param count the count.
*/
public void setSubdivisionCount(int count) {
if (count <= 0) {
throw new IllegalArgumentException("Requires 'count' > 0.");
}
this.subdivisions = count;
notifyListeners(new TitleChangeEvent(this));
}
/**
* Receives notification of an axis change event and responds by firing
* a title change event.
*
* @param event the event.
*/
@Override
public void axisChanged(AxisChangeEvent event) {
if (this.axis == event.getAxis()) {
notifyListeners(new TitleChangeEvent(this));
}
}
/**
* Arranges the contents of the block, within the given constraints, and
* returns the block size.
*
* @param g2 the graphics device.
* @param constraint the constraint ({@code null} not permitted).
*
* @return The block size (in Java2D units, never {@code null}).
*/
@Override
public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
RectangleConstraint cc = toContentConstraint(constraint);
LengthConstraintType w = cc.getWidthConstraintType();
LengthConstraintType h = cc.getHeightConstraintType();
Size2D contentSize = null;
if (w == LengthConstraintType.NONE) {
if (h == LengthConstraintType.NONE) {
contentSize = new Size2D(getWidth(), getHeight());
}
else if (h == LengthConstraintType.RANGE) {
throw new RuntimeException("Not yet implemented.");
}
else if (h == LengthConstraintType.FIXED) {
throw new RuntimeException("Not yet implemented.");
}
}
else if (w == LengthConstraintType.RANGE) {
if (h == LengthConstraintType.NONE) {
throw new RuntimeException("Not yet implemented.");
}
else if (h == LengthConstraintType.RANGE) {
contentSize = arrangeRR(g2, cc.getWidthRange(),
cc.getHeightRange());
}
else if (h == LengthConstraintType.FIXED) {
throw new RuntimeException("Not yet implemented.");
}
}
else if (w == LengthConstraintType.FIXED) {
if (h == LengthConstraintType.NONE) {
throw new RuntimeException("Not yet implemented.");
}
else if (h == LengthConstraintType.RANGE) {
throw new RuntimeException("Not yet implemented.");
}
else if (h == LengthConstraintType.FIXED) {
throw new RuntimeException("Not yet implemented.");
}
}
assert contentSize != null; // suppress compiler warning
return new Size2D(calculateTotalWidth(contentSize.getWidth()),
calculateTotalHeight(contentSize.getHeight()));
}
/**
* Returns the content size for the title. This will reflect the fact that
* a text title positioned on the left or right of a chart will be rotated
* 90 degrees.
*
* @param g2 the graphics device.
* @param widthRange the width range.
* @param heightRange the height range.
*
* @return The content size.
*/
protected Size2D arrangeRR(Graphics2D g2, Range widthRange,
Range heightRange) {
RectangleEdge position = getPosition();
if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) {
float maxWidth = (float) widthRange.getUpperBound();
// determine the space required for the axis
AxisSpace space = this.axis.reserveSpace(g2, null,
new Rectangle2D.Double(0, 0, maxWidth, 100),
RectangleEdge.BOTTOM, null);
return new Size2D(maxWidth, this.stripWidth + this.axisOffset
+ space.getTop() + space.getBottom());
}
else if (position == RectangleEdge.LEFT || position
== RectangleEdge.RIGHT) {
float maxHeight = (float) heightRange.getUpperBound();
AxisSpace space = this.axis.reserveSpace(g2, null,
new Rectangle2D.Double(0, 0, 100, maxHeight),
RectangleEdge.RIGHT, null);
return new Size2D(this.stripWidth + this.axisOffset
+ space.getLeft() + space.getRight(), maxHeight);
}
else {
throw new RuntimeException("Unrecognised position.");
}
}
/**
* Draws the legend within the specified area.
*
* @param g2 the graphics target ({@code null} not permitted).
* @param area the drawing area ({@code null} not permitted).
*/
@Override
public void draw(Graphics2D g2, Rectangle2D area) {
draw(g2, area, null);
}
/**
* Draws the legend within the specified area.
*
* @param g2 the graphics target ({@code null} not permitted).
* @param area the drawing area ({@code null} not permitted).
* @param params drawing parameters (ignored here).
*
* @return {@code null}.
*/
@Override
public Object draw(Graphics2D g2, Rectangle2D area, Object params) {
Rectangle2D target = (Rectangle2D) area.clone();
target = trimMargin(target);
if (this.backgroundPaint != null) {
g2.setPaint(this.backgroundPaint);
g2.fill(target);
}
getFrame().draw(g2, target);
getFrame().getInsets().trim(target);
target = trimPadding(target);
double base = this.axis.getLowerBound();
double increment = this.axis.getRange().getLength() / this.subdivisions;
Rectangle2D r = new Rectangle2D.Double();
if (RectangleEdge.isTopOrBottom(getPosition())) {
RectangleEdge axisEdge = Plot.resolveRangeAxisLocation(
this.axisLocation, PlotOrientation.HORIZONTAL);
if (axisEdge == RectangleEdge.TOP) {
for (int i = 0; i < this.subdivisions; i++) {
double v = base + (i * increment);
Paint p = this.scale.getPaint(v);
double vv0 = this.axis.valueToJava2D(v, target,
RectangleEdge.TOP);
double vv1 = this.axis.valueToJava2D(v + increment, target,
RectangleEdge.TOP);
double ww = Math.abs(vv1 - vv0) + 1.0;
r.setRect(Math.min(vv0, vv1), target.getMaxY()
- this.stripWidth, ww, this.stripWidth);
g2.setPaint(p);
g2.fill(r);
}
if (isStripOutlineVisible()) {
g2.setPaint(this.stripOutlinePaint);
g2.setStroke(this.stripOutlineStroke);
g2.draw(new Rectangle2D.Double(target.getMinX(),
target.getMaxY() - this.stripWidth,
target.getWidth(), this.stripWidth));
}
this.axis.draw(g2, target.getMaxY() - this.stripWidth
- this.axisOffset, target, target, RectangleEdge.TOP,
null);
}
else if (axisEdge == RectangleEdge.BOTTOM) {
for (int i = 0; i < this.subdivisions; i++) {
double v = base + (i * increment);
Paint p = this.scale.getPaint(v);
double vv0 = this.axis.valueToJava2D(v, target,
RectangleEdge.BOTTOM);
double vv1 = this.axis.valueToJava2D(v + increment, target,
RectangleEdge.BOTTOM);
double ww = Math.abs(vv1 - vv0) + 1.0;
r.setRect(Math.min(vv0, vv1), target.getMinY(), ww,
this.stripWidth);
g2.setPaint(p);
g2.fill(r);
}
if (isStripOutlineVisible()) {
g2.setPaint(this.stripOutlinePaint);
g2.setStroke(this.stripOutlineStroke);
g2.draw(new Rectangle2D.Double(target.getMinX(),
target.getMinY(), target.getWidth(),
this.stripWidth));
}
this.axis.draw(g2, target.getMinY() + this.stripWidth
+ this.axisOffset, target, target,
RectangleEdge.BOTTOM, null);
}
}
else {
RectangleEdge axisEdge = Plot.resolveRangeAxisLocation(
this.axisLocation, PlotOrientation.VERTICAL);
if (axisEdge == RectangleEdge.LEFT) {
for (int i = 0; i < this.subdivisions; i++) {
double v = base + (i * increment);
Paint p = this.scale.getPaint(v);
double vv0 = this.axis.valueToJava2D(v, target,
RectangleEdge.LEFT);
double vv1 = this.axis.valueToJava2D(v + increment, target,
RectangleEdge.LEFT);
double hh = Math.abs(vv1 - vv0) + 1.0;
r.setRect(target.getMaxX() - this.stripWidth,
Math.min(vv0, vv1), this.stripWidth, hh);
g2.setPaint(p);
g2.fill(r);
}
if (isStripOutlineVisible()) {
g2.setPaint(this.stripOutlinePaint);
g2.setStroke(this.stripOutlineStroke);
g2.draw(new Rectangle2D.Double(target.getMaxX()
- this.stripWidth, target.getMinY(),
this.stripWidth, target.getHeight()));
}
this.axis.draw(g2, target.getMaxX() - this.stripWidth
- this.axisOffset, target, target, RectangleEdge.LEFT,
null);
}
else if (axisEdge == RectangleEdge.RIGHT) {
for (int i = 0; i < this.subdivisions; i++) {
double v = base + (i * increment);
Paint p = this.scale.getPaint(v);
double vv0 = this.axis.valueToJava2D(v, target,
RectangleEdge.LEFT);
double vv1 = this.axis.valueToJava2D(v + increment, target,
RectangleEdge.LEFT);
double hh = Math.abs(vv1 - vv0) + 1.0;
r.setRect(target.getMinX(), Math.min(vv0, vv1),
this.stripWidth, hh);
g2.setPaint(p);
g2.fill(r);
}
if (isStripOutlineVisible()) {
g2.setPaint(this.stripOutlinePaint);
g2.setStroke(this.stripOutlineStroke);
g2.draw(new Rectangle2D.Double(target.getMinX(),
target.getMinY(), this.stripWidth,
target.getHeight()));
}
this.axis.draw(g2, target.getMinX() + this.stripWidth
+ this.axisOffset, target, target, RectangleEdge.RIGHT,
null);
}
}
return null;
}
/**
* Tests this legend for equality with an arbitrary object.
*
* @param obj the object ({@code null} permitted).
*
* @return A boolean.
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof PaintScaleLegend)) {
return false;
}
PaintScaleLegend that = (PaintScaleLegend) obj;
if (!Objects.equals(this.scale, that.scale)) {
return false;
}
if (!Objects.equals(this.axis, that.axis)) {
return false;
}
if (!Objects.equals(this.axisLocation, that.axisLocation)) {
return false;
}
if (Double.doubleToLongBits(this.axisOffset) !=
Double.doubleToLongBits(that.axisOffset)) {
return false;
}
if (Double.doubleToLongBits(this.stripWidth) !=
Double.doubleToLongBits(that.stripWidth)) {
return false;
}
if (this.stripOutlineVisible != that.stripOutlineVisible) {
return false;
}
if (!PaintUtils.equal(this.stripOutlinePaint,
that.stripOutlinePaint)) {
return false;
}
if (!Objects.equals(this.stripOutlineStroke, that.stripOutlineStroke)) {
return false;
}
if (!PaintUtils.equal(this.backgroundPaint, that.backgroundPaint)) {
return false;
}
if (this.subdivisions != that.subdivisions) {
return false;
}
if (!that.canEqual(this)) {
return false;
}
return super.equals(obj);
}
/**
* Ensures symmetry between super/subclass implementations of equals. For
* more detail, see http://jqno.nl/equalsverifier/manual/inheritance.
*
* @param other Object
*
* @return true ONLY if the parameter is THIS class type
*/
@Override
public boolean canEqual(Object other) {
// fix the "equals not symmetric" problem
return (other instanceof PaintScaleLegend);
}
@Override
public int hashCode() {
int hash = super.hashCode(); // equals calls superclass, hashCode must also
hash = 53 * hash + Objects.hashCode(this.scale);
hash = 53 * hash + Objects.hashCode(this.axis);
hash = 53 * hash + Objects.hashCode(this.axisLocation);
hash = 53 * hash + (int) (Double.doubleToLongBits(this.axisOffset) ^
(Double.doubleToLongBits(this.axisOffset) >>> 32));
hash = 53 * hash + (int) (Double.doubleToLongBits(this.stripWidth) ^
(Double.doubleToLongBits(this.stripWidth) >>> 32));
hash = 53 * hash + (this.stripOutlineVisible ? 1 : 0);
hash = 53 * hash + HashUtils.hashCodeForPaint(this.stripOutlinePaint);
hash = 53 * hash + Objects.hashCode(this.stripOutlineStroke);
hash = 53 * hash + HashUtils.hashCodeForPaint(this.backgroundPaint);
hash = 53 * hash + this.subdivisions;
return hash;
}
/**
* Provides serialization support.
*
* @param stream the output stream.
*
* @throws IOException if there is an I/O error.
*/
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
SerialUtils.writePaint(this.backgroundPaint, stream);
SerialUtils.writePaint(this.stripOutlinePaint, stream);
SerialUtils.writeStroke(this.stripOutlineStroke, stream);
}
/**
* Provides serialization support.
*
* @param stream the input stream.
*
* @throws IOException if there is an I/O error.
* @throws ClassNotFoundException if there is a classpath problem.
*/
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
this.backgroundPaint = SerialUtils.readPaint(stream);
this.stripOutlinePaint = SerialUtils.readPaint(stream);
this.stripOutlineStroke = SerialUtils.readStroke(stream);
}
}