package bs.bs2d.gui.plot;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JPanel;
/**
*
* @author Djen
*/
public class PlotPanel extends JPanel implements UpdateListener{
public static final Color SELECTION_RECT_COLOR = Color.GRAY;
private static final int LEGEND_FRAME_PADDING = 10;
public static final int UPPER_LEFT = 1;
public static final int UPPER_RIGHT = 2;
public static final int LOWER_LEFT = 3;
public static final int LOWER_RIGHT = 4;
// settings
private Plottable plot;
private Rectangle selectionRect;
private JPanel canvas;
private Dimension cnvsSize;
private Ruler ruler;
private final MeshPlotInfoBar infoBar;
private AffineTransform transform;
private AffineTransform invTransform;
private AffineTransform legendTransform;
private Rectangle2D legendBounds;
private int legendPos;
public PlotPanel() {
super(new BorderLayout());
setMinimumSize(new Dimension(300, 300));
transform = AffineTransform.getScaleInstance(1, 1);
invTransform = transform;
legendTransform = transform;
infoBar = new MeshPlotInfoBar();
initCanvas();
add(canvas);
add(infoBar, BorderLayout.PAGE_END);
legendPos = UPPER_LEFT;
}
private void initCanvas() {
canvas = new JPanel(null, true) {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
paintCanvas(g2);
}
};
canvas.setBackground(Color.WHITE);
canvas.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
Dimension ocs = cnvsSize; // old size
cnvsSize = canvas.getSize();
if (ocs == null) {
cnvsSize = canvas.getSize();
resetView();
} else {
//translate center of viewport
double dx = (cnvsSize.width - ocs.width) / 2.0;
double dy = (cnvsSize.height - ocs.height) / 2.0;
translateView(dx, dy);
if (ruler != null) {
ruler.updateBounds(cnvsSize);
}
// update legend
if(plot != null && plot.hasLegend()){
legendBounds = plot.getLegendBounds();
legendTransform = getLegendTransform(legendBounds);
}
}
}
});
MouseAdapter ma = new MouseAdapter() {
private Point oldPos;
boolean selecting = false;
// MouseListener methods
@Override
public void mousePressed(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON3) {
oldPos = e.getPoint();
selecting = false;
} else if (e.getButton() == MouseEvent.BUTTON1) {
oldPos = e.getPoint();
selecting = true;
} else {
oldPos = null;
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (!selecting || selectionRect == null)
return;
//widen rectangle to include points that are exactly on the line
selectionRect.width++;
selectionRect.height++;
select(selectionRect, e.isShiftDown(), e.isControlDown());
selectionRect = null;
selecting = false;
updateCanvas();
}
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
// single node selection
Point p = e.getPoint();
Rectangle r = new Rectangle(p.x-3, p.y-3, 7, 7);
select(r, e.isShiftDown(), e.isControlDown());
} else if (e.getButton() == MouseEvent.BUTTON2) {
resetView();
}
}
//MouseMotionListener methods
@Override
public void mouseMoved(MouseEvent e) {
Point2D p = new Point2D.Double();
invTransform.transform(e.getPoint(), p);
infoBar.setMousePosition(p);
}
@Override
public void mouseDragged(MouseEvent e) {
if (oldPos == null) // shouldn't happen anyway
return;
Point pos = e.getPoint();
if (selecting) {
selectionRect = getRectangle(oldPos, pos);
canvas.repaint();
} else {
translateView(pos.x - oldPos.x, pos.y - oldPos.y);
oldPos = pos;
}
}
//MouseWheelListener methods
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if(plot == null)
return;
double r = e.getPreciseWheelRotation();
double s = -r / 30 + 1;
zoomView(e.getX(), e.getY(), s);
}
// utility methods
private Rectangle getRectangle(Point p1, Point p2) {
Rectangle r = new Rectangle(p1);
r.add(p2);
return r;
}
private void select(Rectangle rect, boolean shift, boolean ctrl){
if(!(plot instanceof Selectable))
return;
Selectable selectable = (Selectable) plot;
// transform selection rectangle back into world coordinates
Rectangle2D trect;
trect = invTransform.createTransformedShape(rect).getBounds2D();
if (ctrl) {
selectable.removeSelection(trect);
} else if(shift){
selectable.addSelection(trect);
} else {
selectable.setSelection(trect);
}
}
};
canvas.addMouseListener(ma);
canvas.addMouseMotionListener(ma);
canvas.addMouseWheelListener(ma);
}
private void paintCanvas(Graphics2D g) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if(plot != null)
plot.paint(g, transform);
if(selectionRect != null){
g.setColor(SELECTION_RECT_COLOR);
g.draw(selectionRect);
}
//paint ruler
if (ruler != null && ruler.isVisible()) {
ruler.paint(g);
}
// paint legend
if(plot == null || !plot.hasLegend())
return;
AffineTransform gt = g.getTransform();
g.transform(legendTransform);
plot.paintLegend(g);
g.setTransform(gt);
}
private AffineTransform getLegendTransform(Rectangle2D legendBounds){
double x, y, s;
double bx = legendBounds.getX();
double by = legendBounds.getY();
double bwidth = legendBounds.getWidth();
double bheight = legendBounds.getHeight();
// usable canvas bounds
int rlr = ruler.isVisible() ? Ruler.RULER_WIDTH : 0;
Rectangle cb = new Rectangle(rlr, 0, cnvsSize.width - rlr,
cnvsSize.height - rlr);
// determine scale
double sy = (cb.height - 2 * LEGEND_FRAME_PADDING) / bheight;
s = Math.min(sy, 1);
// determine translation
bx *= s;
by *= s;
double xtarget = 0, ytarget = 0;
switch(legendPos){
case UPPER_LEFT:
xtarget = cb.x + LEGEND_FRAME_PADDING;
ytarget = cb.y + LEGEND_FRAME_PADDING;
break;
case UPPER_RIGHT:
xtarget = cb.getMaxX() - bwidth - LEGEND_FRAME_PADDING;
ytarget = cb.y + LEGEND_FRAME_PADDING;
break;
case LOWER_LEFT:
xtarget = cb.x + LEGEND_FRAME_PADDING;
ytarget = cb.getMaxY() - bheight - LEGEND_FRAME_PADDING;
break;
case LOWER_RIGHT:
xtarget = cb.getMaxX() - bwidth - LEGEND_FRAME_PADDING;
ytarget = cb.getMaxY() - bheight - LEGEND_FRAME_PADDING;
}
x = xtarget - bx;
y = ytarget - by;
// compile transform
AffineTransform t = AffineTransform.getTranslateInstance(x, y);
t.concatenate(AffineTransform.getScaleInstance(s, s));
return t;
}
/**
* @return the current plot object
*/
public Plottable getPlotObject(){
return plot;
}
/**
* @param plot the object to plot
* @param reset set true to reset the view upon showing the new plot
*/
public void setPlotObject(Plottable plot, boolean reset) {
if(this.plot == plot)
return;
this.plot = plot;
if(plot != null)
plot.setUpdateListener(this);
if(reset)
resetView();
else
setLegendPosition(legendPos); // updates legend and issues repaint
}
/**
* Scales and translates the current plot to fill the canvas
*/
public void resetView() {
if(cnvsSize == null)// component is not yet visible
return;
ruler = new Ruler(cnvsSize);
//reset transform
transform = AffineTransform.getScaleInstance(1, 1);
invTransform = transform;
if(plot == null){
updateCanvas();
return;
}
// determine plot bounds and available space
Rectangle2D pb = plot.getBounds(); //plot bounds
Point pmin = new Point();
Point pmax = new Point(cnvsSize.width, cnvsSize.height);
if(ruler.isVisible()){
pmin.translate(Ruler.RULER_WIDTH, 0);
pmax.translate(0, -Ruler.RULER_WIDTH);
}
// update legend and subtract legend space from plot space
if(plot.hasLegend()){
legendBounds = plot.getLegendBounds();
legendTransform = getLegendTransform(legendBounds);
Shape tb = legendTransform.createTransformedShape(legendBounds);
Rectangle lb = tb.getBounds();
if(legendPos == UPPER_LEFT || legendPos == LOWER_LEFT)
pmin.x = (int)lb.getMaxX();
else if (legendPos == UPPER_RIGHT || legendPos == LOWER_RIGHT)
pmax.x = (int)lb.getMinX();
}
Rectangle ups = new Rectangle(pmin); // usable plot space
ups.add(pmax);
double scale = Math.min((ups.getWidth() - 40.0) / pb.getWidth(),
(ups.getHeight() - 40.0) / pb.getHeight());
AffineTransform atS = AffineTransform.getScaleInstance(scale, -scale);
pb = atS.createTransformedShape(pb).getBounds2D();
double dx, dy;
dx = ups.getCenterX() - pb.getCenterX();
dy = ups.getCenterY() - pb.getCenterY();
AffineTransform atT = AffineTransform.getTranslateInstance(dx, dy);
atT.concatenate(atS);
appendTransform(atT);
updateCanvas();
}
private void translateView(double dx, double dy) {
AffineTransform t = AffineTransform.getTranslateInstance(dx, dy);
appendTransform(t);
updateCanvas();
}
/**
* Scales the current view around a given center
* @param cx x coordinate of the scale center
* @param cy y coordinate of the scale center
* @param scale the scale factor
*/
private void zoomView(double cx, double cy, double scale) {
// move center of scaling (cx|cy) to (0|0)
AffineTransform t1 = AffineTransform.getTranslateInstance(-cx, -cy);
// scale
AffineTransform ts = AffineTransform.getScaleInstance(scale, scale);
// move back to original position
AffineTransform t2 = AffineTransform.getTranslateInstance(cx, cy);
t2.concatenate(ts);
t2.concatenate(t1);
appendTransform(t2);
updateCanvas();
}
private void appendTransform(AffineTransform t) {
t.concatenate(transform);
transform = t;
try {
invTransform = t.createInverse();
} catch (NoninvertibleTransformException e) {//shouldn't ever happen
System.err.println("invertible transformation matrix!");
}
}
/**
* Issues a repaint of the canvas and updates the ruler if necessary.
*/
private void updateCanvas() {
if (ruler.isVisible()) {
ruler.updateTicks(transform, invTransform);
}
canvas.repaint();
}
@Override
public void update(String info) {
if(info != null)
infoBar.setInfo(info);
setLegendPosition(legendPos);
}
public void setInfo(String info){
infoBar.setInfo(info);
}
public void setLegendPosition (int legendPos) {
if(legendPos < 1 || legendPos > 4)
throw new IllegalArgumentException("Illegal legend position!");
this.legendPos = legendPos;
if(plot.hasLegend() && cnvsSize != null){ // if cnvsIze is null the canvas is not visible
legendBounds = plot.getLegendBounds();
legendTransform = getLegendTransform(legendBounds);
}
canvas.repaint();
}
}