/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* 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/
*/
package org.esa.snap.ui.diagram;
import org.esa.snap.core.util.ObjectUtils;
import javax.swing.JPanel;
import javax.swing.event.MouseInputAdapter;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.text.DecimalFormat;
/**
* The <code>DiagramCanvas</code> class is a UI component used to display simple X/Y plots represented by objects of
* type <code>{@link Diagram}</code>.
*/
public class DiagramCanvas extends JPanel {
private Diagram diagram;
private String messageText;
private Insets insets;
private DiagramGraph selectedGraph;
private Point dragPoint;
private DiagramChangeHandler diagramChangeHandler;
public DiagramCanvas() {
setName("diagram");
diagramChangeHandler = new DiagramChangeHandler();
addComponentListener(new ComponentAdapter() {
/**
* Invoked when the component's size changes.
*/
@Override
public void componentResized(ComponentEvent e) {
if (diagram != null) {
diagram.invalidate();
}
}
});
MouseInputAdapter mouseHandler = new IndicatorHandler();
addMouseListener(mouseHandler);
addMouseMotionListener(mouseHandler);
setPreferredSize(new Dimension(320, 200));
}
public Diagram getDiagram() {
return diagram;
}
public void setDiagram(Diagram diagram) {
Diagram oldDiagram = this.diagram;
if (oldDiagram != diagram) {
if (oldDiagram != null) {
oldDiagram.removeChangeListener(diagramChangeHandler);
}
this.diagram = diagram;
if (this.diagram != null) {
diagram.addChangeListener(diagramChangeHandler);
}
firePropertyChange("diagram", oldDiagram, diagram);
repaint();
}
}
public String getMessageText() {
return messageText;
}
public void setMessageText(String messageText) {
String oldValue = this.messageText;
if (!ObjectUtils.equalObjects(oldValue, messageText)) {
this.messageText = messageText;
repaint();
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (!(g instanceof Graphics2D)) {
return;
}
final Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
insets = getInsets(insets);
final int width = getWidth() - (insets.left + insets.right);
final int height = getHeight() - (insets.top + insets.bottom);
final int x0 = insets.left;
final int y0 = insets.top;
if (diagram != null) {
diagram.render(g2d, x0, y0, width, height);
if (dragPoint != null && selectedGraph != null) {
drawValueIndicator(g2d);
}
}
if (messageText != null) {
drawTextBox(g2d, this.messageText, x0 + width / 2, y0 + height / 2, new Color(255, 192, 102));
}
}
private void drawValueIndicator(Graphics2D g2d) {
Diagram.RectTransform transform = diagram.getTransform();
Point2D a = transform.transformB2A(dragPoint, null);
double x = a.getX();
if (x < selectedGraph.getXMin()) {
x = selectedGraph.getXMin();
}
if (x > selectedGraph.getXMax()) {
x = selectedGraph.getXMax();
}
final Stroke oldStroke = g2d.getStroke();
final Color oldColor = g2d.getColor();
double y = getY(selectedGraph, x);
Point2D b = transform.transformA2B(new Point2D.Double(x, y), null);
g2d.setStroke(new BasicStroke(1.0f));
g2d.setColor(diagram.getForegroundColor());
Ellipse2D.Double marker = new Ellipse2D.Double(b.getX() - 4.0, b.getY() - 4.0, 8.0, 8.0);
g2d.draw(marker);
g2d.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{6, 6}, 12));
g2d.setColor(diagram.getForegroundColor());
final Rectangle graphArea = diagram.getGraphArea();
g2d.draw(new Line2D.Double(b.getX(), graphArea.y + graphArea.height, b.getX(), b.getY()));
g2d.draw(new Line2D.Double(graphArea.x, b.getY(), b.getX(), b.getY()));
DecimalFormat decimalFormat = new DecimalFormat("0.#####E0");
String text = selectedGraph.getYName() + ": x = " + decimalFormat.format(x) + ", y = " + decimalFormat.format(y);
g2d.setStroke(oldStroke);
g2d.setColor(oldColor);
drawTextBox(g2d, text, graphArea.x + 6, graphArea.y + 6 + 16, new Color(255, 255, 255, 128));
}
private void drawTextBox(Graphics2D g2D, String text, int x0, int y0, Color color) {
final FontMetrics fontMetrics = g2D.getFontMetrics();
final Rectangle2D textBounds = fontMetrics.getStringBounds(text, g2D);
x0 -= textBounds.getWidth() / 2;
textBounds.setRect(textBounds.getX() - 1, textBounds.getY(), textBounds.getWidth(), textBounds.getHeight());
Rectangle2D.Double r = new Rectangle2D.Double(x0 + textBounds.getX() - 2.0,
y0 + textBounds.getY() - 2.0,
textBounds.getWidth() + 4.0,
textBounds.getHeight() + 4.0);
if (r.getMaxX() > getWidth()) {
r.setRect(getWidth() - r.getWidth(), r.getY(), r.getWidth(), r.getHeight());
}
if (r.getMinX() < 0) {
r.setRect(0, r.getY(), r.getWidth(), r.getHeight());
}
if (r.getMaxY() > getHeight()) {
r.setRect(r.getX(), getHeight() - r.getHeight(), r.getWidth(), r.getHeight());
}
if (r.getMinY() < 0) {
r.setRect(r.getX(), 0, r.getWidth(), r.getHeight());
}
g2D.setColor(color);
g2D.fill(r);
g2D.setColor(Color.black);
g2D.draw(r);
g2D.drawString(text, x0, y0);
}
public double getY(DiagramGraph graph, double x) {
int n = graph.getNumValues();
double x1, y1, x2 = 0, y2 = 0;
for (int i = 0; i < n; i++) {
x1 = x2;
y1 = y2;
x2 = graph.getXValueAt(i);
y2 = graph.getYValueAt(i);
if (i > 0) {
if (x >= x1 && x <= x2) {
return y1 + (x - x1) / (x2 - x1) * (y2 - y1);
}
}
}
throw new IllegalArgumentException("x out of bounds: " + x);
}
private class IndicatorHandler extends MouseInputAdapter {
@Override
public void mouseDragged(MouseEvent e) {
if (getDiagram() == null) {
return;
}
if (selectedGraph == null) {
selectedGraph = getDiagram().getClosestGraph(e.getX(), e.getY());
}
if (selectedGraph != null) {
dragPoint = e.getPoint();
} else {
dragPoint = null;
}
repaint();
}
@Override
public void mouseReleased(MouseEvent e) {
selectedGraph = null;
dragPoint = null;
repaint();
}
}
private class DiagramChangeHandler implements DiagramChangeListener {
public void diagramChanged(Diagram diagram) {
repaint();
}
}
}