/*
* This code is part of Project Waterloo from King's College London
*
*
* Copyright King's College London 2011-
*
* @author Malcolm Lidierth, King's College London [Contact]
*
* Project Waterloo 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 3 of the License, or
* (at your option) any later version.
*
* Project Waterloo 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 program. If not, see .
*
*/
package kcl.waterloo.graphics;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import kcl.waterloo.common.deploy.AbstractDeployableGraphics2D;
import org.jdesktop.swingx.JXLabel;
/**
* {@code GJAxisPanel}- provides axis support for graph layers within a graph
* container.
*
* Note that a default constructor is made public only for use in XML
* serialization/de-serialization.
*
* To create instances of this class use the graph {@code setXXXXAxisPainted()}
* methods which will create the instance through the package-private
* constructor.
*
* This code is part of the Waterloo Scientific Graphics package from King's
* College London
*
* @author Malcolm Lidierth, King's College London [Contact]
*/
public final class GJAxisPanel extends GJBasicPanel implements PropertyChangeListener {
private static final int minimumMargin = 30;
private static final int interAxisSpace = 10;
/**
* Title of this axis including any units
*/
private JLabel label = new JXLabel("");
/**
* Position of the axis - one of
* SwingConstants LEFT, RIGHT, TOP or BOTTOM
*/
public enum Position {
RIGHT, TOP, LEFT, BOTTOM
}
public enum Orientation {
X, Y, Z
}
private GJAxisPanel.Position position;
/**
* Offset (in pixels) of this axis from the View of the parent container.
* This allows a gap to be left between the axis and graph. Default=0
*/
private int offset = 0;
/**
* Length of the major tick marks (in pixels). Default=5
*/
private int majorTickLength = 5;
/**
* Set true for outward ticks, false for inward (= default)
*/
private boolean ticksOutward = false;
/**
* Set true to paint the tick labels
*/
private boolean tickLabelsPainted = false;
/**
* The graph this axis is associated with (N.B. axes are painted in the
* container for the graph)
*/
private GJGraphInterface parentGraph = null;
private Dimension dim = new Dimension();
private double dynamicExtent = 20;
private int extent;
/**
* Factory method for creating a new GJAxisPanel instance. This instantiates
* a GJAxisPanel and associates it with a graph.
*
* @param gr GJGraphInterface this axis is associated with
* @param pos axis Position as defined by {@code GJAxisPanel#Position}.
* @return a new GJAxisPanel instance.
*/
static GJAxisPanel createInstance(GJGraphInterface gr, GJAxisPanel.Position pos) {
GJAxisPanel ax = new GJAxisPanel(gr, pos);
if (gr.getGraphContainer() != null) {
gr.getGraphContainer().add(ax);
ax.addMouseMotionListener(gr.getGraphContainer().getContainerMouseHandler());
ax.addMouseListener(gr.getGraphContainer().getContainerMouseHandler());
}
return ax;
}
/**
* Public constructor used for XML serialization/de-serialization. Do not
* use.
*/
public GJAxisPanel() {
super(true);
setOpaque(true);
//setBackground(new Color(0f, 0f, 0f, 0f));
setPreferredSize(dim);
setLayout(new AxisLayout());
add(label);
label.setVisible(tickLabelsPainted);
setLabelFont(getFont());
}
/**
* Constructor used by the factory method.
*
* @param source
* @param pos
*/
private GJAxisPanel(GJGraphInterface source, GJAxisPanel.Position pos) {
this();
setForeground(((JComponent) source).getForeground());
setParentGraph(source);
setPosition(pos);
// getParentGraph().getGraphContainer().add((Component) this);
// addMouseMotionListener(getParentGraph().getGraphContainer().getContainerMouseHandler());
// addMouseListener(getParentGraph().getGraphContainer().getContainerMouseHandler());
}
/**
* Called during construction. Not intended for public use.
*
* @param source
*/
public final void setParentGraph(GJGraphInterface source) {
parentGraph = source;
}
/**
* Returns the graph this axis is associated with
*
* @return the parent GJGraphInterface
*/
public final GJGraphInterface getParentGraph() {
return parentGraph;
}
/**
* Called during construction. Not intended for public use.
*
* @param pos
*/
public final void setPosition(GJAxisPanel.Position pos) {
position = pos;
switch (pos) {
case LEFT:
setTickLabelsPainted(true);
setTextRotation(label, 3 * Math.PI / 2);
dim = new Dimension(extent, 32767);
break;
case RIGHT:
dim = new Dimension(extent, 32767);
setTextRotation(label, 3 * Math.PI / 2);
break;
case TOP:
dim = new Dimension(32767, extent);
break;
case BOTTOM:
setTickLabelsPainted(true);
dim = new Dimension(32767, extent);
break;
default:
}
}
/**
* Private static method called to rotate the axis labels. This works for
* objects implementing {@code setTextRotation} method (e.g. JXLabel and any
* components from the GPL kcl.gpl.tex package).
*/
private void setTextRotation(Object label, double theta) {
try {
Method m = label.getClass().getMethod("setTextRotation", new Class[]{double.class});
try {
m.invoke(label, theta);
} catch (IllegalAccessException ex) {
} catch (IllegalArgumentException ex) {
} catch (InvocationTargetException ex) {
}
} catch (NoSuchMethodException ex) {
} catch (SecurityException ex) {
}
}
/**
* Returns an integer constant (as defined in {@code SwingConstants}) which
* defines this axis location as LEFT, RIGHT, TOP, BOTTOM.
*
* @return the int Position
*/
public GJAxisPanel.Position getPosition() {
return position;
}
public GJAxisPanel.Orientation getOrientation() {
switch (position) {
case LEFT:
case RIGHT:
return Orientation.Y;
case TOP:
case BOTTOM:
return Orientation.X;
default:
return Orientation.Z;
}
}
/**
* Returns a reference to the {@code MouseAdapter} that this axis will use.
*
* @return the MouseAdapter
*/
public final MouseAdapter getMouseHandler() {
return parentGraph.getAxisMouseHandler();
}
/**
* Sets the font for the axis title.
*
* @return the lable {@code Font}
*/
public Font getLabelFont() {
return label.getFont();
}
/**
* Returns the font for the axis title
*
* @param f
*/
public final void setLabelFont(final Font f) {
Font old = label.getFont();
label.setFont(f);
localValidate();
firePropertyChange("Font", old, label.getFont());
}
/**
* Sets the title string
*
* @param s
*/
public final void setText(String s) {
if (label != null) {
String old = label.getText();
label.setText(s);
localValidate();
firePropertyChange("text", old, label.getText());
}
}
public final String getText() {
return label.getText();
}
public final void setTickLabelsPainted(boolean flag) {
boolean old = tickLabelsPainted;
if (flag) {
if (label.getParent() != this) {
this.add(label);
}
} else {
this.remove(label);
}
tickLabelsPainted = flag;
label.setVisible(flag);
localValidate();
firePropertyChange("tickLabelsPainted", old, tickLabelsPainted);
}
public final boolean isTickLabelsPainted() {
return tickLabelsPainted;
}
public final JLabel getLabel() {
return label;
}
public final void setLabel(JLabel lbl) {
JLabel old = label;
remove(label);
label = lbl;
add(label);
setPosition(position);
firePropertyChange("label", old, label);
}
/**
* Revalidate - recalculates the SpringLayout
*
*/
@Override
public final void revalidate() {
if (EventQueue.isDispatchThread()) {
if (getLayout() instanceof AxisLayout) {
// Call localRevalidate before revalidate
AxisLayout layout = (AxisLayout) getLayout();
layout.localRevalidate(this);
}
super.revalidate();
} else {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
revalidate();
}
});
}
}
/**
* Need double validation for some changes as the layout is dependent of the
* view Position.
*
*/
private void localValidate() {
if (getParent() != null) {
getParent().validate();
} else {
super.validate();
}
}
/**
* Sets the rotation of the title text.
*
* @param theta
*/
public final void setTitleRotation(double theta) {
if (label != null) {
setTextRotation(label, theta);
}
}
/**
* Sets the direction for drawing the ticks
*
* @param flag
*/
public final void setTicksOutward(boolean flag) {
ticksOutward = flag;
}
/**
*
* @return the tick direction - true for outward ticks
*/
public final boolean getTicksOutward() {
return ticksOutward;
}
/**
* Sets the major tick length
*
* @param x tick length in pixels
*/
public final void setMajorTickLength(int x) {
majorTickLength = x;
}
/**
* Returns the major tick length
*
* @return length (in pixels)
*/
public final int getMajorTickLength() {
return majorTickLength;
}
/**
* Sets the minor tick length
*
* @param x tick length in pixels
*/
public final void setMinorTickLength(int x) {
majorTickLength = x;
}
/**
* Returns the minor tick length
*
* @return length (in pixels)
*/
public final int getMinorTickLength() {
return majorTickLength;
}
/**
* @return the offset
*/
final int getOffset() {
return offset;
}
/**
* @param offset the offset to set
*/
final void setOffset(int offset) {
double old = this.offset;
this.offset = offset;
firePropertyChange("offset", old, offset);
}
/**
* @return the dim
*/
public final Dimension getDim() {
return dim;
}
// /**
// * @param dim the dim to set
// */
// public final void setDim(Dimension dim) {
// this.dim = dim;
// }
/**
* @return the extent
*/
final int getExtent() {
if (isTickLabelsPainted()) {
return extent;
} else {
// 5-pixel width for mouse listener activity
return 5;
}
}
/**
* @param extent the extent to set
*/
final void setExtent(int extent) {
this.extent = extent;
}
/**
* @return the interAxisSpace
*/
public static int getInterAxisSpace() {
return interAxisSpace;
}
/**
* @return the minimumMargin
*/
public static int getMinimumMargin() {
return minimumMargin;
}
/**
* @return the dynamicExtent
*/
public double getDynamicExtent() {
return dynamicExtent;
}
/**
* @param dynamicExtent the dynamicExtent to set
*/
public void setDynamicExtent(double dynamicExtent) {
this.dynamicExtent = dynamicExtent;
}
// /**
// * Added at 1.1Beta. No need for super call here. Axis panels are dealt with
// * from the graph paint mechanism.
// */
// @Override
// public void paintComponent(Graphics g) {
// super.paintComponent(g);
// draw((Graphics2D)g);
// }
final void draw(Graphics2D g2) {
Paint p = g2.getPaint();
g2.setPaint(getForeground());
if (g2 instanceof AbstractDeployableGraphics2D) {
((AbstractDeployableGraphics2D) g2).setPaintObject(this);
}
paintBackground(g2);
switch (position) {
case LEFT:
drawVerticalAxisLeft(g2);
break;
case BOTTOM:
drawHorizontalAxisBottom(g2);
break;
case RIGHT:
drawVerticalAxisRight(g2);
break;
case TOP:
drawHorizontalAxisTop(g2);
break;
default:
}
g2.setPaint(p);
}
/**
* Axis painting method called from the graph container. As the Graphics2D
* object supplied on input is from the container, it honours the clip
* region of the container, not of the View.
*
* @param g2 Graphics2D supplied from the container
*/
private void drawHorizontalAxisBottom(Graphics2D g2) {
Path2D ax = new Path2D.Double();
FontMetrics metrics = g2.getFontMetrics();
double pos;
int tick = this.majorTickLength;
if (!this.ticksOutward) {
tick = -tick;
}
ax.moveTo(this.getX(), this.getY());
ax.lineTo(this.getX() + this.getWidth(), this.getY());
double thisMin, thisMax;
thisMin = this.getParentGraph().getXMin();
thisMax = this.getParentGraph().getXMax();
double startX = Math.floor(thisMin / this.getParentGraph().getMajorXHint()) * this.getParentGraph().getMajorXHint();
ArrayList it = this.getParentGraph().getXTransform().getAxisTickPositions(
startX,
thisMax + this.getParentGraph().getMajorXHint(),
this.getParentGraph().getMajorXHint());
int y = this.getY();
double x2;
g2.setPaint(getParentGraph().getAxisColor());
//g2.setPaint(((Component) getParentGraph()).getForeground());
for (Double s : it) {
x2 = s.doubleValue();
int xpixel = (int) this.getParentGraph().xPositionToPixel(x2);
if (x2 >= thisMin && x2 <= thisMax) {
Point point = SwingUtilities.convertPoint(this, xpixel, 0, this.getParentGraph().getGraphContainer());
ax.moveTo(point.getX(), y);
ax.lineTo(point.getX(), y + tick);
if (isTickLabelsPainted()) {
String str = this.getParentGraph().formatXAxisLabel(x2);
pos = point.getX() - metrics.stringWidth(str) / 2d;
if (this.getParentGraph().isCategorical(GJAxisPanel.Orientation.X)) {
// Categorical labels - so rotate them
AffineTransform af = g2.getTransform();
double r = this.getParentGraph().getXAxisLabelRotation(x2);
g2.rotate(r, pos + metrics.stringWidth(str) / 2d, y + 10);
g2.drawString(str, (int) (pos - metrics.stringWidth(str) / 2d), y + 10);
g2.setTransform(af);
} else {
g2.drawString(str, (int) pos,
y + metrics.getAscent() + this.majorTickLength);
}
}
}
}
g2.setPaint(getParentGraph().getAxisColor());
g2.setStroke(new BasicStroke(getParentGraph().getAxisStrokeWeight()));
g2.draw(ax);
}
/**
* Axis painting method called from the graph container. As the Graphics2D
* object supplied on input is from the container, it honours the clip
* region of the container, not of the View.
*
* @param g2 Graphics2D supplied from the container
*/
private void drawHorizontalAxisTop(Graphics2D g2) {
Path2D ax = new Path2D.Double();
FontMetrics metrics = g2.getFontMetrics();
double pos;
int tick = this.majorTickLength;
if (this.ticksOutward) {
tick = -tick;
}
ax.moveTo(this.getX(), this.getY() + this.getHeight());
ax.lineTo(this.getX() + this.getWidth(), this.getY() + this.getHeight());
double startX = Math.floor(this.getParentGraph().getXMin() / this.getParentGraph().getMajorXHint()) * this.getParentGraph().getMajorXHint();
ArrayList it = this.getParentGraph().getXTransform().getAxisTickPositions(
startX,
this.getParentGraph().getXMax() + this.getParentGraph().getMajorXHint(),
this.getParentGraph().getMajorXHint());
int y = this.getY() + this.getHeight();
double x2;
g2.setPaint(getParentGraph().getAxisColor());
for (Double s : it) {
x2 = s.doubleValue();
int xpixel = (int) this.getParentGraph().xPositionToPixel(x2);
if (x2 >= this.getParentGraph().getXMin() && x2 <= this.getParentGraph().getXMax()) {
Point point = SwingUtilities.convertPoint(this, xpixel, 0, this.getParentGraph().getGraphContainer());
ax.moveTo(point.getX(), y);
ax.lineTo(point.getX(), y + tick);
if (isTickLabelsPainted()) {
//g2.setPaint(((Component) getParentGraph()).getForeground());
String str = this.getParentGraph().formatXAxisLabel(x2);
pos = point.getX() - metrics.stringWidth(str) / 2d;
if (this.getParentGraph().isCategorical(GJAxisPanel.Orientation.X)) {
// Categorical labels - so rotate them
AffineTransform af = g2.getTransform();
double r = this.getParentGraph().getXAxisLabelRotation(x2);
g2.rotate(r, pos + metrics.stringWidth(str) / 2d, y - 2);
g2.drawString(str, (int) (pos + metrics.stringWidth(str) / 2d), y - 2);
g2.setTransform(af);
} else {
g2.drawString(str, (int) pos,
y - metrics.getDescent() - this.majorTickLength);
}
}
}
}
g2.setPaint(getParentGraph().getAxisColor());
g2.setStroke(new BasicStroke(getParentGraph().getAxisStrokeWeight()));
g2.draw(ax);
}
/**
* Axis painting method called from the graph container. As the Graphics2D
* object supplied on input is from the container, it honours the clip
* region of the container, not of the View.
*
* @param g2 Graphics2D supplied from the container
*/
private void drawVerticalAxisLeft(Graphics2D g2) {
final Path2D ax = new Path2D.Double();
FontMetrics metrics = g2.getFontMetrics();
double pos;
int tick = this.majorTickLength;
if (this.ticksOutward) {
tick = -tick;
}
ax.moveTo(this.getX() + this.getWidth(), this.getY());
ax.lineTo(this.getX() + this.getWidth(), this.getY() + this.getHeight());
double startY = Math.floor(this.getParentGraph().getYMin() / this.getParentGraph().getMajorYHint()) * this.getParentGraph().getMajorYHint();
int x = this.getX() + this.getWidth();
ArrayList it = this.getParentGraph().getYTransform().getAxisTickPositions(
startY,
this.getParentGraph().getYMax() + this.getParentGraph().getMajorYHint(),
this.getParentGraph().getMajorYHint());
double y2;
g2.setPaint(getParentGraph().getAxisColor());
for (Double s : it) {
y2 = s.doubleValue();
int ypixel = (int) this.getParentGraph().yPositionToPixel(y2);
if (y2 >= this.getParentGraph().getYMin() && y2 <= this.getParentGraph().getYMax()) {
Point point = SwingUtilities.convertPoint(this, 0, ypixel, this.getParentGraph().getGraphContainer());
ax.moveTo(x, point.getY());
ax.lineTo(x + tick, (int) point.getY());
//g2.drawLine(x, (int) point.getY(), x + tick, (int) point.getY());
if (isTickLabelsPainted()) {
//g2.setPaint(((Component) getParentGraph()).getForeground());
String str = this.getParentGraph().formatYAxisLabel(y2);
pos = point.getY() + 2 * metrics.getDescent();
g2.drawString(str, x - metrics.stringWidth(str) - this.majorTickLength,
(int) pos);
}
}
}
g2.setPaint(getParentGraph().getAxisColor());
g2.setStroke(new BasicStroke(getParentGraph().getAxisStrokeWeight()));
g2.draw(ax);
}
/**
* Axis painting method called from the graph container. As the Graphics2D
* object supplied on input is from the container, it honours the clip
* region of the container, not of the View.
*
* @param g2 Graphics2D supplied from the container
*/
private void drawVerticalAxisRight(Graphics2D g2) {
final Path2D ax = new Path2D.Double();
FontMetrics metrics = g2.getFontMetrics();
double pos;
int tick = this.majorTickLength;
if (!this.ticksOutward) {
tick = -tick;
}
ax.moveTo(this.getX(), this.getY());
ax.lineTo(this.getX(), this.getY() + this.getHeight());
double startY = Math.floor(this.getParentGraph().getYMin() / this.getParentGraph().getMajorYHint()) * this.getParentGraph().getMajorYHint();
int x = this.getX();
ArrayList it = this.getParentGraph().getYTransform().getAxisTickPositions(
startY,
this.getParentGraph().getYMax() + this.getParentGraph().getMajorYHint(),
this.getParentGraph().getMajorYHint());
double y2;
g2.setPaint(getParentGraph().getAxisColor());
for (Double s : it) {
//g2.setPaint(getParentGraph().getAxisColor());
y2 = s.doubleValue();
int ypixel = (int) this.getParentGraph().yPositionToPixel(y2);
if (y2 >= this.getParentGraph().getYMin() && y2 <= this.getParentGraph().getYMax()) {
Point point = SwingUtilities.convertPoint(this, 0, ypixel, this.getParentGraph().getGraphContainer());
ax.moveTo(x, point.getY());
ax.lineTo(x + tick, point.getY());
//g2.drawLine(x, (int) point.getY(), x + tick, (int) point.getY());
if (isTickLabelsPainted()) {
//g2.setPaint(((Component) getParentGraph()).getForeground());
String str = this.getParentGraph().formatYAxisLabel(y2);
pos = point.getY() + 2 * metrics.getDescent();
g2.drawString(str, x + this.majorTickLength, (int) pos);
}
}
}
g2.setPaint(getParentGraph().getAxisColor());
g2.setStroke(new BasicStroke(getParentGraph().getAxisStrokeWeight()));
g2.draw(ax);
}
@Override
public void propertyChange(PropertyChangeEvent pce) {
}
}
Ads help maintain this website.