import java.awt.*;
import java.awt.event.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.tree.*;
import ket.*;
import ket.Calculate;
import ket.display.ColourScheme;
import ket.display.ColourSchemeDecorator;
import ket.math.*;
import ket.math.convert.Like;
import ket.math.purpose.DoubleValue;
import ket.math.purpose.NumberValue;
import ket.math.purpose.Word;
import ketUI.Document;
import ketUI.DocumentManager;
import ketUI.panel.KetPanel;
public class GraphPanel
extends JPanel
implements KeyListener, MouseMotionListener, MouseListener, MouseWheelListener {
static final double delta = Math.PI/36.0; // rad
double alpha = 0.0;
double beta = 0.0;
double gamma = 0.0;
int u0, v0;
Vector<Line> lines;
Document document;
double minX, maxX;
double minY, maxY;
public class Line {
public final Vector<Double> dataX;
public final Vector<Double> dataY;
public final Vector<Double> dataZ;
public Line() {
dataX = new Vector<Double>();
dataY = new Vector<Double>();
dataZ = new Vector<Double>();
}
public double[] get(int i) {
return new double[]{dataX.get(i), dataY.get(i), dataZ.get(i)};
}
}
public GraphPanel(Document document) {
this.document = document;
setFocusable(true);
addKeyListener(this);
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
lines = new Vector<Line>();
lines.add(new Line()); //?
reset();
calc();
}
public void reset() {
minX = -10.0;
maxX = 10.0;
minY = -10.0;
maxY = 10.0;
alpha = 0.0;
beta = 0.0;
gamma = 0.0;
}
public Vector<Equation> getEquations() {
return document.getEquationList().getEquations();
}
public void calc() {
lines.clear();
Word horizontal = new Word("x");
Word time = new Word("t");
for (Equation e : getEquations()) {
Argument root = e.getRoot();
Set<Token> variables = root.getVariables();
if (hasOnly(variables, horizontal)) {
// i.e. A simple plot, f(x).
Token target = new Token(horizontal);
Line l = new Line();
double step = (maxX-minX)/2.0/getWidth();
double dx = maxX - minX; // Include extra to handle mouse drags.
for (double x=minX - dx; x<=maxX+dx; x+=step) {
l.dataX.add(x);
l.dataY.add(Calculate.eval(root, x, target));
l.dataZ.add(0.0);
}
lines.add(l);
} else if (hasOnly(variables, time) && Like.hasForm(root, Function.VECTOR, 2)) {
// i.e. An xy-plot [x(t), y(t)].
Argument x = root.asBranch().firstChild();
Argument y = root.asBranch().lastChild();
Token target = new Token(time);
Line l = new Line();
double step = 1.0 / 500.0;
for (double t=0.0; t<1.0; t+=step) {
l.dataX.add(Calculate.eval(x, t, target) );
l.dataY.add(Calculate.eval(y, t, target));
l.dataZ.add(0.0);
}
lines.add(l);
} else if (hasOnly(variables, time) && Like.hasForm(root, Function.VECTOR, 3)) {
// i.e. An xy-plot [x(t), y(t), z(t)].
Argument x = root.asBranch().getChild(0);
Argument y = root.asBranch().getChild(1);
Argument z = root.asBranch().getChild(2);
Token target = new Token(time);
Line l = new Line();
double step = 1.0 / 500.0;
for (double t=0.0; t<1.0; t+=step) {
l.dataX.add(Calculate.eval(x, t, target) );
l.dataY.add(Calculate.eval(y, t, target));
l.dataZ.add((Calculate.eval(z, t, target)));
}
lines.add(l);
}
}
repaint();
}
public boolean hasOnly(Set<Token> variables, Word word) {
if (variables.size()!=1) {
return false;
}
Iterator<Token> iterator = variables.iterator();
return iterator.next().getState().equals(word);
}
public void setupAntialiasing(Graphics2D g2D) {
g2D.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2D.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2D.setRenderingHint(
RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
}
/*
* Five point fininte difference formula.
* */
public double dash(double Fhm2, double Fhm1, double Fh, double Fhp1, double Fhp2, double h) {
// TODO: Use this to filter or over-sample graphs to maximize dv/du variations.
return (-Fhp2 + 8.0*Fhp1 - 8.0*Fhm1 + Fhm2) / 12.0 / h;
}
public int getU(double x) {
return (int) ((x-minX) * width() / (maxX-minX));
}
public int getV(double y) {
return (int) height() - (int) ((y-minY)*height()/(maxY-minY));
}
public double getU(double[] mu, double[] v) {
return getU(dot(mu, v));
}
public double getV(double[] nu, double[] v) {
return getV(dot(nu, v));
}
public double getX(double u) {
return u*(maxX - minX)/getWidth() + minX;
}
public double getY(double v) {
return v*(maxY - minY)/getHeight() + minY;
}
public double[] rotate(double[] t) {
// rot z
double[] u = new double[]{
Math.cos(alpha)*t[0]-Math.sin(alpha)*t[1],
Math.sin(alpha)*t[0]+Math.cos(alpha)*t[1],
t[2]};
// rot x
double[] v = new double[]{
u[0],
Math.cos(beta)*u[1]-Math.sin(beta)*u[2],
Math.cos(beta)*u[2]+Math.sin(beta)*u[1]};
// rot z
double[] w = new double[]{
Math.cos(gamma)*v[0]-Math.sin(gamma)*v[1],
Math.sin(gamma)*v[0]+Math.cos(gamma)*v[1],
v[2]};
return w;
}
public double dot(double[] a, double[] b) {
double sum = 0.0;
sum += a[0]*b[0];
sum += a[1]*b[1];
sum += a[2]*b[2];
return sum;
}
public double width() {
return this.getWidth()-1;
}
public double height() {
return this.getHeight()-1;
}
public void paint(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
setupAntialiasing(g2D);
ColourSchemeDecorator colourScheme = document.getColourScheme();
Color backgroundColour = colourScheme.getBackgroundColour();
Color argColour = colourScheme.getArgColour(0);
Color borderColour = colourScheme.getBorderColour();
// (Don't cut the last pixel off).
g2D.setColor(backgroundColour);
g2D.fillRect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
double[] mu = rotate(new double[]{1.0, 0.0, 0.0});
double[] nu = rotate(new double[]{0.0, 1.0, 0.0});
int c=0;
for (Line l : lines) {
Double oldU = null;
Double oldV = null;
for (int i=0; i<l.dataX.size(); i++) {
double u = getU(mu, l.get(i));
double v = getV(nu, l.get(i));
float hue = c * 1.0f / lines.size();
int rgb = Color.HSBtoRGB(hue, 1.0f, 1.0f);
g2D.setColor(new Color(rgb));
if (oldU!=null && oldV!=null) {
g2D.draw(new Line2D.Double(oldU, oldV, u, v));
}
oldU = u;
oldV = v;
}
c += 1;
}
g2D.setColor(borderColour);
double xNegX = getU(mu, new double[]{-1.0, 0.0, 0.0});
double xNegY = getV(nu, new double[]{-1.0, 0.0, 0.0});
double xPosX = getU(mu, new double[]{+1.0, 0.0, 0.0});
double xPosY = getV(nu, new double[]{+1.0, 0.0, 0.0});
double yNegX = getU(mu, new double[]{0.0, -1.0, 0.0});
double yNegY = getV(nu, new double[]{0.0, -1.0, 0.0});
double yPosX = getU(mu, new double[]{0.0, +1.0, 0.0});
double yPosY = getV(nu, new double[]{0.0, +1.0, 0.0});
double zNegX = getU(mu, new double[]{0.0, 0.0, -1.0});
double zNegY = getV(nu, new double[]{0.0, 0.0, -1.0});
double zPosX = getU(mu, new double[]{0.0, 0.0, +1.0});
double zPosY = getV(nu, new double[]{0.0, 0.0, +1.0});
double axisX = maxAbs(xPosX-xNegX, yPosX-yNegX, zPosX-zNegX);
double axisY = maxAbs(xPosY-xNegY, yPosY-yNegY, zPosY-zNegY);
double scaleX = width() / axisX;
double scaleY = height() / axisY;
double scale = 0.90 * Math.min(scaleX, scaleY);
// int exponent = (int) Math.ceil(Math.log10(scale)) - 1;
//D System.out.println(exponent);
g2D.draw(new Line2D.Double(
getU(mu, vscale(new double[]{-1.0, 0.0, 0.0}, scale)),
getV(nu, vscale(new double[]{-1.0, 0.0, 0.0}, scale)),
getU(mu, vscale(new double[]{+1.0, 0.0, 0.0}, scale)),
getV(nu, vscale(new double[]{+1.0, 0.0, 0.0}, scale))));
g2D.draw(new Line2D.Double(
getU(mu, vscale(new double[]{0.0, -1.0, 0.0}, scale)),
getV(nu, vscale(new double[]{0.0, -1.0, 0.0}, scale)),
getU(mu, vscale(new double[]{0.0, +1.0, 0.0}, scale)),
getV(nu, vscale(new double[]{0.0, +1.0, 0.0}, scale))));
g2D.draw(new Line2D.Double(
getU(mu, vscale(new double[]{0.0, 0.0, -1.0}, scale)),
getV(nu, vscale(new double[]{0.0, 0.0, -1.0}, scale)),
getU(mu, vscale(new double[]{0.0, 0.0, +1.0}, scale)),
getV(nu, vscale(new double[]{0.0, 0.0, +1.0}, scale))));
}
public double[] vscale(double[] v, double scale) {
return new double[]{scale*v[0], scale*v[1], scale*v[2]};
}
public double maxAbs(double a, double b, double c) {
if (a*a>b*b) {
if (a*a>c*c) {
return Math.abs(a);
} else {
return Math.abs(c);
}
} else {
return Math.abs(b);
}
}
public void moveIn() {
double dx = maxX - minX;
double dy = maxY - minY;
minX += dx/4;
maxX -= dx/4;
minY += dy/4;
maxY -= dy/4;
calc();
}
public void moveOut() {
double dx = maxX - minX;
double dy = maxY - minY;
minX -= dx/4;
maxX += dx/4;
minY -= dy/4;
maxY += dy/4;
calc();
}
public void moveLeft() {
double dx = maxX - minX;
minX -= dx/10;
maxX -= dx/10;
calc();
}
public void moveRight() {
double dx = maxX - minX;
minX += dx/10;
maxX += dx/10;
calc();
}
public void moveUp() {
double dy = maxY - minY;
minY += dy/10;
maxY += dy/10;
calc();
}
public void moveDown() {
double dy = maxY - minY;
minY -= dy/10;
maxY -= dy/10;
calc();
}
@Override
public void keyPressed(KeyEvent keyEvent) {
char c = keyEvent.getKeyChar();
switch (keyEvent.getKeyCode()) {
case KeyEvent.VK_LEFT: moveLeft(); return;
case KeyEvent.VK_RIGHT: moveRight(); return;
case KeyEvent.VK_UP: moveUp(); return;
case KeyEvent.VK_DOWN: moveDown(); return;
case KeyEvent.VK_SPACE: moveOut(); return;
}
switch (c) {
case 'L':
alpha += delta; calc(); return;
case 'H':
alpha -= delta; calc(); return;
case 'O':
beta += delta; calc(); return;
case 'I':
beta -= delta; calc(); return;
case 'K':
gamma += delta; calc(); return;
case 'J':
gamma -= delta; calc(); return;
case '-':
case ' ': moveOut(); return;
case '+':
case 'i': moveIn(); return;
case 'o': moveOut(); return;
case '0': reset(); calc(); return;
case 'k': moveUp(); return;
case 'j': moveDown(); return;
case 'h': moveLeft(); return;
case 'l': moveRight(); return;
}
}
@Override public void keyReleased(KeyEvent keyEvent) { }
@Override public void keyTyped(KeyEvent keyEvent) { }
@Override public void mouseClicked(MouseEvent e) {
/*
boolean leftButton = e.getButton()==MouseEvent.BUTTON1;
boolean singleClick = e.getClickCount()==1;
if (leftButton) {
moveIn();
} else {
moveOut();
}
*/
}
@Override public void mouseExited(MouseEvent e) { }
@Override public void mouseEntered(MouseEvent e) { }
@Override public void mouseMoved(MouseEvent e) { }
@Override
public void mouseReleased(MouseEvent e) {
// What is the difference?
requestFocusInWindow();
calc();
}
@Override
public void mousePressed(MouseEvent e) {
requestFocusInWindow();
u0 = e.getX();
v0 = e.getY();
}
@Override
public void mouseDragged(MouseEvent e) { // TODO: Right mouse drag rotates view.
int u1 = e.getX();
int v1 = e.getY();
double dx = getX(u1) - getX(u0);
double dy = getY(v1) - getY(v0);
minX -= dx;
maxX -= dx;
minY += dy; // Negative height?
maxY += dy;
repaint();
u0 = u1;
v0 = v1;
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
int rotation = e.getWheelRotation();
if (rotation>0) {
moveOut();
} else {
moveIn();
}
repaint();
}
}