/*
* Copyright (C) 2011 Alasdair C. Hamilton
*
* 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 ketUI.panel;
import geom.Offset;
import java.util.*;
import java.awt.Stroke;
import java.awt.BasicStroke;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Color;
import geom.Position;
import ket.*;
import ket.display.*;
import ket.display.box.BorderedBox;
import ket.display.box.Box;
import ket.display.box.BoxFactory;
import ket.display.box.BoxText;
import ket.display.box.BoxTools;
import ket.math.*;
import ketUI.Document;
/**
* Represent the current equation as a graph.
*/
public class GraphDisplay implements Display {
static final int ICON_SIZE = 7;
static final Random random = new Random();
public static final double LABEL_SCALE_FACTOR = 0.4; // fractional label scale factor
public static final Stroke ROOT_STROKE = new BasicStroke(1.7f,
BasicStroke.CAP_SQUARE,
BasicStroke.JOIN_MITER,
10.0f,
new float[] {1.0f},
0.0f);
public static final Stroke LINE_STROKE = new BasicStroke(1.0f,
BasicStroke.CAP_SQUARE,
BasicStroke.JOIN_MITER,
10.0f,
new float[] {2.0f, 2.0f},
0.0f);
static final boolean TREE_MAP = false;
static final boolean ANIMATE = false;
static final int SAMPLE_MAX = 10000;
/**
* When the equation is drawn, record its box from which the arguments
* associated with a mouse click may be determined.
*/
final Document document;
final MathCollection mathCollection;
Box equationBox;
Position actualEquationTopLeft;
Map<Label, Vector<Node>> groups;
Vector<Node> nodes;
Map<Node, Vector<Position>> cloud;
boolean repaint;
//- double edgeLength = 65.0;
public GraphDisplay(Document document, MathCollection mathCollection) {
this.document = document;
this.mathCollection = mathCollection;
groups = new TreeMap<Label, Vector<Node>>();
nodes = new Vector<Node>();
cloud = new IdentityHashMap<Node, Vector<Position>>();
repaint = false;
}
public double getEdgeLength() {
return 65.0 * document.getBoxFontSize() / 25.0;
}
@Override
public Box findDeepestBox(Position p) {
return null;
}
public Node findNearestNode(Position p, boolean anywhere) {
if (nodes==null) {
return null;
}
Node closest = null;
double best = Double.MAX_VALUE;
for (Node node : nodes) { // Can be empty.
if (node.actualX==null || node.actualY==null) {
// Has not yet been drawn.
continue;
}
double dx = node.actualX - p.x;
double dy = node.actualY - p.y;
double norm = Math.sqrt(dx*dx + dy*dy);
if (norm < best) {
closest = node;
best = norm;
}
}
if (anywhere || closest==null) {
return closest;
}
// Otherwise find the next two closest family members in order
// to produce a smooth halo around the edges.
Node nextClosest = null;
double nextBest = Double.MAX_VALUE;
for (Node node : nodes) { // Can be empty.
if (node.actualX==null || node.actualY==null) {
continue;
}
if (node==closest) {
continue;
}
double dx = node.actualX - p.x;
double dy = node.actualY - p.y;
double norm = Math.sqrt(dx*dx + dy*dy);
if (norm<nextBest && node.family(closest)) {
nextClosest = node;
nextBest = norm;
}
}
if (nextClosest==null) {
return closest;
}
int power = 2;
double val = 1.0/Math.pow(best, power) + 1.0/Math.pow(nextBest, power);
double scale = 1.0/Math.pow(getEdgeLength(), power);
if (7.0*scale<val) {
return closest;
} else {
return null;
}
}
@Override
public Argument findDeepestArgument(Position p) {
Node closest = findNearestNode(p, true);
return closest!=null ? closest.argument : null;
}
@Override
public Vector<Integer> findVisibleEquationIndices() {
Vector<Integer> indices = new Vector<Integer>();
if (nodes==null) return indices;
for (Node n : nodes) {
Integer index = n.getEquationIndex();
if (index!=null) {
indices.add(index);
}
}
return indices;
}
@Override
public Equation pointToEquation(Position p) {
if (equationBox!=null) {
Argument argument = equationBox.getArgument();
if (argument==null) {
return null;
}
return argument.getEquation();
} else {
Argument argument = findDeepestArgument(p);
return argument!=null ? argument.getEquation() : null;
}
}
@Override
public boolean generateBoxes(Graphics2D g2D, ColourScheme colourScheme, int fontSize, Offset panelRectangle) {
Equation equation = mathCollection.getCursor().getEquation();
if (equation.isText()) {
generateText(g2D, colourScheme, fontSize, panelRectangle, equation);
return true;
}
equationBox = null; // Clear past text.
cloud = new IdentityHashMap<Node, Vector<Position>>();
TreeMap<Argument, Node> ids = new TreeMap<Argument, Node>();
ArgumentVector v = new ArgumentVector(equation.getVisibleRoot(), ArgumentVector.INCLUDE_ROOT|ArgumentVector.EXCLUDE_HIDDEN_CHILDREN);
nodes = new Vector<Node>();
TreeSet<String> names = new TreeSet<String>();
repaint = TREE_MAP;
for (Argument a : v) {
double x = 0.0;
double y = 0.0;
Branch branch = a.getParentBranch();
Node parent = branch!=null ? ids.get(branch) : null;
Node n = new Node(a, parent, colourScheme, getEdgeLength());
names.add(n.name);
ids.put(a, n);
nodes.add(n);
}
for (Node node : nodes) {
Offset original = new Offset(node.x, node.y);
Offset rotate = original.rotate(Math.PI/12.0); // 15 deg
node.x = rotate.width;
node.y = rotate.height;
}
relax(panelRectangle);
moveToCentreOfMass();
groups = new TreeMap<Label, Vector<Node>>();
for (String string : names) {
Vector<Node> value = new Vector<Node>();
double sumX = 0.0;
double sumY = 0.0;
for (Node n : nodes) { // group by name
if (string.equals(n.name)) {
value.add(n);
sumX += n.x;
sumY += n.y;
}
}
groups.put(
new Label(
string,
sumX/nodes.size(),
sumY/nodes.size()),
value);
}
shiftLabels(panelRectangle);
relaxLabels();
return true;
}
public void generateText(Graphics2D g2D, ColourScheme colourScheme, int fontSize, Offset panelRectangle, Equation equation) {
equationBox = equation.toBox(colourScheme);
repaint = false;
equationBox.setupInnerRectangle(fontSize);
double minimumHeight = equationBox.getInnerRectangle().height;
double shapeWidth = panelRectangle.width - KetPanel.BORDER_OFFSET.width;
Offset windowWithoutLabel = new Offset(shapeWidth, minimumHeight);
equationBox.setupOuterRectangle(windowWithoutLabel);
double panelHeightOfNextBox = KetPanel.TOP_BORDER_SIZE;
actualEquationTopLeft = new Position(KetPanel.LEFT_BORDER_SIZE, panelHeightOfNextBox);
}
public void shiftLabels(Offset panelRectangle) {
long rand = 1;
//! groups = new TreeMap<Label, Vector<Node>>();
for (Map.Entry<Label, Vector<Node>> entry : groups.entrySet()) {
//! Vector<Node> value = new Vector<Node>();
double sumX = 0.0;
double sumY = 0.0;
for (Node n : entry.getValue()) {
sumX += n.x;
sumY += n.y;
}
// Ellipse
double beta = Math.atan2(sumY, sumX);
double a = LABEL_SCALE_FACTOR*panelRectangle.width;
double b = LABEL_SCALE_FACTOR*panelRectangle.height;
double denA = b*Math.cos(beta);
double denB = a*Math.sin(beta);
// Repeatedly consistent random number generation (using the linear congruential method).
rand = (LCM_A * rand + LCM_C) % LCM_M;
double r = a * b * (0.8 + 0.2 * (rand*1.0/LCM_M)) / Math.sqrt(denA*denA + denB*denB);
entry.getKey().x = r * Math.cos(beta);
entry.getKey().y = r * Math.sin(beta);
}
}
public static final long LCM_A = 16807;
public static final long LCM_M = Integer.MAX_VALUE;
public static final long LCM_C = 0;
// used:?
public static int sign(double x) {
return x<0.0 ? -1 : +1;
}
public static int abs(int x) {
return x<0 ? -x : +x;
}
public static double sq(double x) {
return x*x;
}
public void relax(Offset panelRectangle) {
/*
for (Node node : nodes) { //D Random noise to check stability and response rate.
node.x += (Math.random() - 0.5)/2.0;
node.y += (Math.random() - 0.5)/2.0;
}*/
Offset[] diff = new Offset[nodes.size()];
int N = 100;
double edgeLength = getEdgeLength();
for (int q=0; q<N; q++) {
for (int i=0; i<nodes.size(); i++) {
Node m = nodes.get(i);
double forceX = 0.0;
double forceY = 0.0;
double scale = 1.0E-4;
forceX = scale * sign(m.x) * m.x * m.x;
forceY = scale * sign(m.y) * m.y * m.y;
for (int j=0; j<nodes.size(); j++) {
Node n = nodes.get(j);
Offset r = m.inversePower(n);
forceX += r.width;
forceY += r.height;
}
Offset tension = m.tension(edgeLength);
diff[i] = new Offset(tension.width + forceX/nodes.size(), tension.height + forceY/nodes.size());
}
/* DEBUG INFO
if (q==0 || q==N-1) {
double sum = 0.0;
for (Offset offset : diff) {
sum += offset.length();
}
Ket.out.println("sum = " + sum);
}
*/
}
for (int i=0; i<nodes.size(); i++) {
Node node = nodes.get(i);
node.x += bound(diff[i].width);
node.y += bound(diff[i].height);
}
}
public void relaxLabels() {
Vector<Label> labels = new Vector<Label>(groups.keySet());
Offset[] diff = new Offset[labels.size()];
int N = 100;
for (int q=0; q<N; q++) {
for (int i=0; i<labels.size(); i++) {
diff[i] = new Offset(0.0, 0.0);
Label m = labels.get(i);
if (groups.get(m).size()<2) {
continue;
}
double forceX = 0.0;
double forceY = 0.0;
for (int j=0; j<labels.size(); j++) {
if (groups.get(m).size()<2) {
continue;
}
Label n = labels.get(j);
Offset r = m.inversePower(n);
forceX += r.width;
forceY += r.height;
}
diff[i].width += forceX;
diff[i].height += forceY;
}
if (q==0 || q==N-1) {
double sum = 0.0;
for (Offset offset : diff) {
sum += offset.length();
}
//! Ket.out.println("sum = " + sum);
}
}
for (int i=0; i<labels.size(); i++) {
Label label = labels.get(i);
label.x += bound(diff[i].width);
label.y += bound(diff[i].height);
}
}
public double bound(double x) {
double range = 2.0E3;
return Math.min(Math.max(x, -range), range);
}
public Offset calcCentreOfMass() {
double sumX = 0.0;
double sumY = 0.0;
for (Node n : nodes) {
sumX += n.x;
sumY += n.y;
}
return new Offset(sumX/nodes.size(), sumY/nodes.size());
}
public void moveToCentreOfMass() {
Offset centreOfMass = calcCentreOfMass();
for (Node n : nodes) {
n.x -= centreOfMass.width;
n.y -= centreOfMass.height;
}
// Shift the labels
for (Label label : groups.keySet()) {
label.x -= centreOfMass.width;
label.y -= centreOfMass.height;
}
}
/**
* Draw a single equation in the centre of the screen or text to the
* top left. In either case a label is also displayed.
*/
@Override
public void paint(Graphics2D g2D, ColourScheme colourScheme, int fontSize, Offset panelRectangle) {
if (equationBox!=null) {
// (Just in case) If ket panel is painted before boxes have been created in generate boxes.
// g2D.setColor(colourScheme.getBackgroundColour());
equationBox.paint(g2D, actualEquationTopLeft, colourScheme);
return;
}
double centreX = KetPanel.LEFT_BORDER_SIZE + panelRectangle.width/2.0;
double centreY = KetPanel.TOP_BORDER_SIZE + panelRectangle.height/2.0;
if (ANIMATE) {
relax(panelRectangle);
relaxLabels();
moveToCentreOfMass();
shiftLabels(panelRectangle);
}
drawTreeMap(g2D, panelRectangle);
if ( ! TREE_MAP ) {
drawEdges(g2D, centreX, centreY, colourScheme);
}
drawLabelLines(g2D, centreX, centreY, colourScheme);
drawLabelText(g2D, centreX, centreY, colourScheme);
drawNodeSymbols(g2D, centreX, centreY, colourScheme);
drawSimpleFunctionIcons(g2D, centreX, centreY, colourScheme);
}
// --- <NEW BIT> ---
public double norm(Position a, Position b) {
double dx = b.x - a.x;
double dy = b.y - a.y;
return Math.sqrt(dx*dx + dy*dy);
}
static final double MARGIN = 5.0;
public boolean lt(double s, double u, double v) {
return s+MARGIN<u && s+MARGIN<v;
}
public boolean isDIn(double ab, double ac, double ad, double bc, double bd, double cd) {
return lt(cd, ac, bc) && lt(ad, ab, ac) && lt(bd, ab, bc);
}
public Position contains(Position a, Position b, Position c, Position d) {
double ab = norm(a, b);
double ac = norm(a, c);
double ad = norm(a, d);
double bc = norm(b, c);
double bd = norm(b, d);
double cd = norm(c, d);
if (isDIn(bc, bd, ab, cd, ac, ad)) {
return a;
} else if (isDIn(cd, ac, bc, ad, bd, ab)) {
return b;
} else if (isDIn(ad, bd, cd, ab, ac, bc)) {
return c;
} else if (isDIn(ab, ac, ad, bc, bd, cd)) {
return d;
} else {
return null;
}
}
// --- </NEW BIT> ---
public void drawTreeMap(Graphics g2D, Offset panelRectangle) {
if ( ! TREE_MAP ) {
return;
}
for (int q=0; q<100; q++) { // was 100
Position p = new Position(Math.random()*panelRectangle.width, Math.random()*panelRectangle.height);
Node nearest = findNearestNode(p, false);
if (nearest==null) continue;
if (random.nextInt(1+nearest.depth)!=0) continue;
if (!cloud.containsKey(nearest)) {
cloud.put(nearest, new Vector<Position>());
}
if (cloud.get(nearest).size()<SAMPLE_MAX) {
cloud.get(nearest).add(p);
} else {
repaint = false;
}
}
// Draw background cloud.
int i=0;
for (Map.Entry<Node, Vector<Position>> entry : cloud.entrySet()) {
i += 1;
float hue = i*1.0f/cloud.size();
float saturation = Math.min(1.0f, 1.0f/entry.getKey().depth);
float brightness = 1.0f - Math.min(1.0f, 1.0f/entry.getKey().depth)/3.0f;
g2D.setColor(Color.getHSBColor(hue, saturation, brightness));
Vector<Position> points = entry.getValue(); //- cloud.get(entry.getKey());
for (Position p : points) {
g2D.fillRect((int) p.x-1, (int) p.y-1, 3, 3); // square
//| g2D.fillRect((int) p.x, (int) p.y, 1, 1); // dot
}
}
}
public void drawEdges(Graphics2D g2D, double centreX, double centreY, ColourScheme colourScheme) {
Stroke oldStroke = g2D.getStroke();
g2D.setColor(colourScheme.getBorderColour());
for (Node n : nodes) {
double x = centreX + n.x;
double y = centreY + n.y;
if (n.parent!=null) {
if (n.parent.parent==null) { // This is a line to 'root'.
g2D.setStroke(ROOT_STROKE);
}
double px = centreX + n.parent.x;
double py = centreY + n.parent.y;
g2D.drawLine((int) px, (int) py, (int) x, (int) y);
if (n.parent.parent==null) {
g2D.setStroke(oldStroke);
}
}
}
}
public void drawLabelLines(Graphics2D g2D, double centreX, double centreY, ColourScheme colourScheme) {
// TODO: Use colourScheme.
Stroke oldStroke = g2D.getStroke();
for (Map.Entry<Label, Vector<Node>> entry : groups.entrySet()) {
if (entry.getValue().firstElement().isSimpleFunction()) {
continue;
} else if (entry.getValue().size()<2) {
continue;
}
boolean stack = Math.atan(Math.abs(entry.getKey().y/entry.getKey().x)) < Math.PI/4.0;
double xEdge = centreX + entry.getKey().x;
double yEdge = centreY + entry.getKey().y;
Double min = stack ? yEdge : xEdge;
Double max = stack ? yEdge : xEdge;
for (Node node : entry.getValue()) {
double x2 = centreX + node.x;
double y2 = centreY + node.y;
double dx = Math.abs(x2-xEdge);
double dy = Math.abs(y2-yEdge);
if (stack) {
min = Math.min(min, y2);
max = Math.max(max, y2);
g2D.setStroke(LINE_STROKE);
g2D.setColor(colourScheme.getBorderColour()); //<
g2D.drawLine((int) xEdge, (int) y2, (int) x2, (int) y2); // const y
g2D.setStroke(oldStroke);
g2D.setColor(colourScheme.getBackgroundColour());
g2D.drawLine((int) xEdge+1, (int) y2+1, (int) x2+1, (int) y2+1); // const y
} else {
min = Math.min(min, x2);
max = Math.max(max, x2);
g2D.setStroke(LINE_STROKE);
g2D.setColor(colourScheme.getBorderColour()); //<
g2D.drawLine((int) x2, (int) yEdge, (int) x2, (int) y2); // const x
g2D.setStroke(oldStroke);
g2D.setColor(colourScheme.getBackgroundColour());
g2D.drawLine((int) x2+1, (int) yEdge+1, (int) x2+1, (int) y2+1); // const x
}
}
if (min!=null && max!=null) {
int iMin = (int) ((double) min);
int iMax = (int) ((double) max);
if (stack) {
g2D.setStroke(LINE_STROKE);
g2D.setColor(colourScheme.getBorderColour()); //<
g2D.drawLine((int) xEdge, iMin, (int) xEdge, iMax);
g2D.setStroke(oldStroke);
g2D.setColor(colourScheme.getBackgroundColour());
g2D.drawLine((int) xEdge+1, iMin+1, (int) xEdge+1, iMax+1);
} else {
g2D.setStroke(LINE_STROKE);
g2D.setColor(colourScheme.getBorderColour()); //<
g2D.drawLine(iMin, (int) yEdge, iMax, (int) yEdge);
g2D.setStroke(oldStroke);
g2D.setColor(colourScheme.getBackgroundColour());
g2D.drawLine(iMin+1, (int) yEdge+1, iMax+1, (int) yEdge+1);
}
}
}
}
public void drawLabelText(Graphics2D g2D, double centreX, double centreY, ColourScheme colourScheme) {
for (Map.Entry<Label, Vector<Node>> entry : groups.entrySet()) {
Label label;
boolean highlight;
boolean background;
int size = entry.getValue().size();
if (entry.getValue().firstElement().isSimpleFunction()) {
continue;
} else if (size==1) {
label = entry.getValue().firstElement();
highlight = entry.getValue().firstElement().highlight;
background = true;
} else {
label = entry.getKey();
highlight = false;
background = false;
}
double large = 50.0; // bigger than box's yet-to-be-determined width and height.
Offset bounds = new Offset(2.0*large, 2.0*large);
Color borderColour = colourScheme.getBorderColour();
BoxText box = new BoxText(null, " "+label.name+" " , Box.RIGHT_ALIGN|Box.BOTTOM_ALIGN);
label.actualX = centreX + label.x - large;
label.actualY = centreY + label.y - large;
BoxText content = new BoxText(null, " " + label.name + " " , Box.RIGHT_ALIGN|Box.BOTTOM_ALIGN);
Color colour = colourScheme.getBorderColour(); // This doesn't appear appear to be used.
Box border = new BorderedBox(null, content, Box.X_CENTRE_ALIGN|Box.Y_CENTRE_ALIGN, highlight, colourScheme);
Position centre = new Position(label.actualX, label.actualY);
border.setupAndPaint(g2D, colourScheme, Box.DEFAULT_BOX_FONT_SIZE, centre, new Offset(2*large, 2*large), background);
}
}
public void drawNodeSymbols(Graphics2D g2D, double centreX, double centreY, ColourScheme colourScheme) {
// Draw node symbols.
for (Map.Entry<Label, Vector<Node>> entry : groups.entrySet()) {
if ( !entry.getValue().firstElement().isSimpleFunction() && entry.getValue().size()==1) {
// Skip multiple arguments that are not simple functions.
continue;
}
for (Node n : entry.getValue()) {
double x = centreX + n.x;
double y = centreY + n.y;
n.actualX = x;
n.actualY = y;
//if (TREE_MAP) {
// continue;
//}
if (n.parent==null) { // root
g2D.setColor(n.colour);
g2D.fillOval(-(int) (1.6*ICON_SIZE) + (int) x, -(int) (1.6*ICON_SIZE) + (int) y, (int) (2*1.6*ICON_SIZE), (int) (2*1.6*ICON_SIZE));
g2D.setColor(colourScheme.getBackgroundColour());
g2D.fillOval(-ICON_SIZE + (int) x, -ICON_SIZE + (int) y, 2*ICON_SIZE, 2*ICON_SIZE);
g2D.setColor(colourScheme.getBorderColour());
g2D.drawOval(-(int) (1.6*ICON_SIZE) + (int) x, -(int) (1.6*ICON_SIZE) + (int) y, (int) (2*1.6*ICON_SIZE), (int) (2*1.6*ICON_SIZE));
g2D.drawOval(-ICON_SIZE + (int) x, -ICON_SIZE + (int) y, 2*ICON_SIZE, 2*ICON_SIZE);
} else {
g2D.setColor(colourScheme.getBorderColour());
if (n.leaf) {
g2D.setColor(n.colour);
g2D.fillRect((int) (x-ICON_SIZE), (int) (y-ICON_SIZE), 2*ICON_SIZE, 2*ICON_SIZE);
g2D.setColor(colourScheme.getBorderColour());
g2D.drawRect((int) (x-ICON_SIZE), (int) (y-ICON_SIZE), 2*ICON_SIZE, 2*ICON_SIZE);
} else {
g2D.setColor(n.colour);
g2D.fillOval(-ICON_SIZE + (int) x, -ICON_SIZE + (int) y, 2*ICON_SIZE, 2*ICON_SIZE);
g2D.setColor(colourScheme.getBorderColour());
g2D.drawOval(-ICON_SIZE + (int) x, -ICON_SIZE + (int) y, 2*ICON_SIZE, 2*ICON_SIZE);
}
}
}
}
}
public void drawSimpleFunctionIcons(Graphics2D g2D, double centreX, double centreY, ColourScheme colourScheme) {
Stroke oldStroke = g2D.getStroke();
for (Map.Entry<Label, Vector<Node>> entry : groups.entrySet()) {
for (Node n : entry.getValue()) {
double x = centreX + n.x;
double y = centreY + n.y;
if (TREE_MAP) {
g2D.setColor(n.colour);
} else {
g2D.setColor(colourScheme.getBorderColour());
}
if (entry.getValue().firstElement().isSimpleFunction()) {
g2D.setStroke(ROOT_STROKE);
double scale = 1.0 / Math.sqrt(2.0);
Function f = entry.getValue().firstElement().argument.getFunction();
if (f==Function.ADD) {
g2D.drawLine(-ICON_SIZE+(int) x, (int) y, ICON_SIZE+(int) x, (int) y);
g2D.drawLine((int) x, -ICON_SIZE+(int) y, (int) x, ICON_SIZE+(int) y);
} else if (f==Function.MINUS) {
g2D.drawLine(-ICON_SIZE+(int) x, (int) y, ICON_SIZE+(int) x, (int) y);
} else if (f==Function.TIMES) {
g2D.drawLine((int) (-scale*ICON_SIZE+x), (int) (-scale*ICON_SIZE+y), (int) (scale*ICON_SIZE+x), (int) (scale*ICON_SIZE+y));
g2D.drawLine((int) (-scale*ICON_SIZE+x), (int) (scale*ICON_SIZE+y), (int) (scale*ICON_SIZE+x), (int) (-scale*ICON_SIZE+y));
} else if (f==Function.FRACTION) {
g2D.drawLine((int) (-scale*ICON_SIZE+x), (int) (scale*ICON_SIZE+y), (int) (scale*ICON_SIZE+x), (int) (-scale*ICON_SIZE+y));
} else if (f==Function.POWER) {
// remove second scale
g2D.drawLine((int) (-scale*ICON_SIZE + x), (int) y, (int) x, (int) (-scale*ICON_SIZE + y));
g2D.drawLine((int) (scale*ICON_SIZE + x), (int) y, (int) x, (int) (-scale*ICON_SIZE + y)); // remove second scale
} else if (f==Function.EQUALS) {
g2D.drawLine((int) (-scale*ICON_SIZE+x), (int) (-0.4*ICON_SIZE+y), (int) (scale*ICON_SIZE+x), (int) (-0.4*ICON_SIZE+y));
g2D.drawLine((int) (-scale*ICON_SIZE+x), (int) (0.4*ICON_SIZE+y), (int) (scale*ICON_SIZE+x), (int) (0.4*ICON_SIZE+y));
}
g2D.setStroke(oldStroke);
}
}
}
}
@Override
public boolean isArgumentVisible(Argument argument) {
if (equationBox==null) {
return false;
}
return equationBox.containsArgument(argument);
}
/*+
private int getFontSize() {
// WARNING: The panel decoration scales more slowely than large equations.
return (document.getBoxFontSize() + 2*Box.DEFAULT_BOX_FONT_SIZE) / 3;
}*/
@Override
public boolean requiresRepaint() {
// return true;
// return false;
return repaint || ANIMATE;
}
@Override
public void noteChange(Graphics2D g2D, ColourScheme colourScheme, Argument before, Equation afterEquation, int fontSize, Offset panelRectangle) {
// Do nothing?
}
}