/*
* 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 java.awt.image.*;
import java.util.Vector;
import java.util.*;
import java.awt.Graphics2D;
import geom.Position;
import geom.Offset;
import ket.*;
import ket.display.*;
import ket.display.Transition;
import ket.display.box.Box;
import ket.display.box.BoxText;
import ket.math.*;
import ketUI.Ket;
/**
* A list of equations may be displayed by this class which organizes and draws
* them relative to the top, centre or bottom of the screen.
*/
public class ScatterDisplay implements Display {
static final int LIMIT = 30;
static final double ANIMATION_TIME = 250.0; // ms
static final double BOX_GAP = 10;
static final Vector<Double> T_COL = positiveIntegers();
public static Vector<Double> positiveIntegers() {
Vector<Double> tCol = new Vector<Double>();
for (int i=0; i<LIMIT; i++) {
tCol.add(new Double(i));
}
return tCol;
}
boolean update; //?
final MathCollection mathCollection;
final PanelDecoration panelDecoration;
volatile IdentityHashMap<Box, Equation> boxToEquation = new IdentityHashMap<Box, Equation>();
volatile IdentityHashMap<Equation, Position> equationToPosition = new IdentityHashMap<Equation, Position>();
Box afterRootBox;
Box equationBox;
Box labelBox;
Position equationTopLeft;
volatile Set<Box> boxSet;
boolean repaint;
int bottomBoxIndex;
int initialBoxIndex;
int middleBoxIndex;
public ScatterDisplay(MathCollection mathCollection, PanelDecoration panelDecoration) {
this.mathCollection = mathCollection;
this.panelDecoration = panelDecoration;
boxSet = null;
repaint = true;
initialBoxIndex = 0;
middleBoxIndex = 0;
bottomBoxIndex = 0;
update = false;
}
/**
* Find the equation that is closest to the given point on the
* ketPanel.
*/
@Override
public Equation pointToEquation(Position p) {
Equation e = null;
Double bestTop = null;
for (Box box : boxSet) {
if (box==null) continue;
if (box.getTopLeft()==null) continue;
double top = box.getTopLeft().y;
if (top<p.y && (bestTop==null || bestTop<top)) {
e = box.getArgument().getEquation();
Ket.out.println(e);
bestTop = top;
}
}
return e;
}
/*
* Look through boxSet and while a box contains the location within its
* inner rectangle, return that box.
*/
@Override
public Box findDeepestBox(Position p) {
for (Box next : boxSet) {
Box match = next.findDeepestBox(p);
if (match!=null && match.getArgument()!=null) {
return match;
}
}
return null;
}
/*
* Look through boxSet and while a box contains the location within its
* inner rectangle, return that box.
*/
@Override
public Argument findDeepestArgument(Position p) {
for (Box next : boxSet) {
Argument match = next.findDeepestArgument(p);
if (match!=null) {
return match;
}
}
return null;
}
@Override
public Vector<Integer> findVisibleEquationIndices() {
Vector<Integer> indices = new Vector<Integer>();
if (boxSet==null) {
Ket.out.println("[findVisibleEquationIndices::null box-set]");
return indices;
}
for (Box next : boxSet) {
if (next==null) {
Ket.out.println("[pointToEquation::null box-set element]");
continue;
}
Argument argument = next.getArgument();
if (argument==null) {
Ket.out.println("[pointToEquation::null box-set argument]");
continue;
}
Integer index = argument.getEquationIndex(); //NOTE: Integer, not int
if (index==null) {
Ket.out.println("[pointToEquation::null box-set index]");
continue;
}
indices.add(index);
}
return indices;
}
public void dragTo(Equation e, Position p) {
if (equationToPosition.containsKey(e)) {
// Find the centre of the equation's box
for (Box b : boxToEquation.keySet()) {
if (boxToEquation.get(b)!=e) continue;
Offset inner = b.getInnerRectangle();
if (inner==null) continue;
Position q = p.minus(inner.getCentre());
equationToPosition.put(e, q);
return;
}
}
equationToPosition.put(e, p);
}
public void clear() {
boxToEquation.clear();
equationToPosition.clear();
}
/**
* This is called every time the user interacts with the box model, either changing or selecting.
*/
@Override
public synchronized boolean generateBoxes(Graphics2D g2D, ColourScheme colourScheme, int fontSize, Offset panelRectangle) {
synchronized (mathCollection.getSelection()) {
boxToEquation.clear();
boxSet = createBox(colourScheme, fontSize, panelRectangle);
Equation equation = mathCollection.getCursor().getEquation(); //q
if (equation==null) { System.out.print(" #2187 "); return false; }
equationBox = equation.toBox(colourScheme);
labelBox = equation.getLabelBox(colourScheme);
labelBox.setProperty(Box.RIGHT_ALIGN);
equationBox.setupInnerRectangle(fontSize);
Offset equationRectangle = calcEquationRectangle(panelRectangle);
if (equationRectangle==null) { System.out.print(" #9862 "); return false; }
labelBox.setup(fontSize, equationRectangle);
if (equationBox.getInnerRectangle()==null) { System.out.print(" #0391 "); return false; }
Offset windowWithoutLabel = calcWindowWithoutLabel(equationRectangle);
if (windowWithoutLabel==null) { System.out.print(" #9104 "); return false; }
equationBox.setupOuterRectangle(windowWithoutLabel);
equationBox.calcRootOffset();
}
if (update) {
repaint = true;
synchronized (e2ps) {
for (Vector<Position> v : e2ps.values()) {
v.clear();
}
}
}
return true;
}
static final double ADDITIONAL_EQUATION_GAP = 10.0;
public boolean hasOverlap(Offset panelRectangle) {
boolean changed = false;
// If any of the boxes are now too big for the window, shift them inwards.
double cx = KetPanel.LEFT_BORDER_SIZE;
double cy = KetPanel.TOP_BORDER_SIZE;
for (Box b : boxSet) { // Move boxes away from edges.
Equation e = boxToEquation.get(b);
if (e==null) continue;
Position p = equationToPosition.get(e);
if (p==null) continue;
Offset inner = b.getInnerRectangle();
double mx = panelRectangle.width-KetPanel.LEFT_BORDER_SIZE-KetPanel.RIGHT_BORDER_SIZE - inner.width;
double my = panelRectangle.height-KetPanel.TOP_BORDER_SIZE-KetPanel.BOTTOM_BORDER_SIZE - inner.height - panelDecoration.getModeBoxHeight();
//- p.x = bound(p.x, KetPanel.LEFT_BORDER_SIZE, mx);
if (p.x<KetPanel.LEFT_BORDER_SIZE) {
return true;
} else if (p.x>mx) {
return true;
}
//- p.y = bound(p.y, KetPanel.TOP_BORDER_SIZE, my);
if (p.y<KetPanel.TOP_BORDER_SIZE) {
return true;
} else if (p.y>my) {
return true;
}
}
Vector<Box> bs = new Vector<Box>(boxSet);
for (int i=0; i<bs.size(); i++) { // Move boxses apart.
Box bA = bs.get(i);
Equation eA = boxToEquation.get(bA);
if (eA==null) continue;
Position pA = equationToPosition.get(eA);
if (pA==null) continue;
Offset innerA = bA.getInnerRectangle();
for (int j=i+1; j<bs.size(); j++) { // Move boxses apart.
Box bB = bs.get(j);
Equation eB = boxToEquation.get(bB);
if (eB==null) continue;
Position pB = equationToPosition.get(eB);
if (pB==null) continue;
Offset innerB = bB.getInnerRectangle();
Position rA = bA.getPosition(pA);
Position rB = bB.getPosition(pB);
Offset iA = bA.getInnerRectangle();
Offset iB = bB.getInnerRectangle();
// Positive overlap values are errors.
boolean overlap1 = rB.x + iB.width + ADDITIONAL_EQUATION_GAP > rA.x;
boolean overlap2 = rA.x + iA.width + ADDITIONAL_EQUATION_GAP > rB.x;
boolean overlap3 = rB.y + iB.height + ADDITIONAL_EQUATION_GAP > rA.y;
boolean overlap4 = rA.y + iA.height + ADDITIONAL_EQUATION_GAP > rB.y;
if (overlap1 && overlap2 && overlap3 && overlap4) {
return true;
}
}
}
return changed;
}
public synchronized Vector<Map.Entry<Box,Equation>> getEs() {
try {
return new Vector<Map.Entry<Box,Equation>>(boxToEquation.entrySet());
} catch (ConcurrentModificationException e) { //? Why
System.out.print(".");
return null;
} catch (NoSuchElementException e) { //? Why
System.out.print(".");
return null;
}
}
public synchronized void perturbLoop(double scale, Offset panelRectangle, int n) {
// All perturbations are judged by sum of square of costs:
synchronized (mathCollection.getSelection()) {
double x0 = KetPanel.LEFT_BORDER_SIZE;
double y0 = KetPanel.TOP_BORDER_SIZE;
// BUG? Why isn't RIGHT_BORDER_SIZE and BOTTOM_BORDER_SIZE not used here:
double dx = panelRectangle.width - KetPanel.LEFT_BORDER_SIZE - KetPanel.RIGHT_BORDER_SIZE;
double dy = panelRectangle.height - KetPanel.TOP_BORDER_SIZE - KetPanel.BOTTOM_BORDER_SIZE - panelDecoration.getModeBoxHeight();
Position centre = new Position(x0+dx/2.0, y0+dy/2.0);
EquationList el = getMathCollection().getSelection().getEquationList();
Vector<Map.Entry<Box,Equation>> es = getEs();
if (es==null) return;
Vector<Position> ends = calcEnds(es, el);
Vector<Box> theBoxes = new Vector<Box>();
Vector<Equation> theEquations = new Vector<Equation>();
Vector<Position> thePositions = new Vector<Position>();
for (Map.Entry<Box,Equation> entry : es) {
Box box = entry.getKey();
Equation e = entry.getValue();
if (e==null) {
System.out.print("null");
boxToEquation.remove(box);
continue;
}
if (e.getEquationList()!=el) {
System.out.print("e");
boxToEquation.remove(box);
continue;
}
Position p = equationToPosition.get(e);
if (p==null) {
System.out.print("p");
boxToEquation.remove(box);
continue;
}
theBoxes.add(box);
theEquations.add(e);
thePositions.add(p);
}
for (int loop=0; loop<n; loop++) {
Vector<Position> allPs = new Vector<Position>();
Vector<Position> allQs = new Vector<Position>();
for (int i=0; i<theBoxes.size(); i++) {
Box box = theBoxes.get(i);
Equation e = theEquations.get(i);
Position p = thePositions.get(i);
Offset inner = box.getInnerRectangle();
Position q = shift(p, scale);
double costQ = calcCost(q, centre, ends, dx, dy, inner);
double costP = calcCost(p, centre, ends, dx, dy, inner);
if (costQ <= costP) {
allPs.add(p);
allQs.add(q);
}
}
for (int i=0; i<allPs.size(); i++) {
Position p = allPs.get(i);
Position q = allQs.get(i);
p.x = (1.0*q.x+2.0*p.x) / 3.0;
p.y = (1.0*q.y+2.0*p.y) / 3.0;
}
}
if (true) { //D
for (Map.Entry<Box,Equation> entry : es) {
Box box = entry.getKey();
Equation e = entry.getValue();
if (e==null) continue;
Position p = equationToPosition.get(e);
if (p==null) continue;
Vector<Position> ps = e2ps.get(e);
if (ps==null) {
ps = new Vector<Position>();
e2ps.put(e, ps);
}
ps.add(new Position(p));
if (ps.size()>HISTORY) {
ps.removeElementAt(0);
}
//D System.out.println(e.getEquationIndex() + "\t" + p.x + "\t" + p.y); //D
}
}
}
}
static final int HISTORY = 20;
IdentityHashMap<Equation, Vector<Position>> e2ps = new IdentityHashMap<Equation, Vector<Position>>();
public static double calcMean(List<Double> changes) {
double sum = 0.0;
for (double x : changes) {
sum += x;
}
return sum / changes.size();
}
public static double calcStdev(List<Double> changes) {
double mean = calcMean(changes);
double err = 0.0;
for (double x : changes) {
err += sq(x - mean);
}
err /= changes.size();
err = Math.sqrt(err);
return err;
}
public Vector<Position> calcEnds(Vector<Map.Entry<Box,Equation>> es, EquationList el) {
Vector<Position> ends = new Vector<Position>();
for (Map.Entry<Box,Equation> entry : es) {
Box box = entry.getKey();
Equation e = entry.getValue();
if (e==null) continue;
if (e.getEquationList()!=el) continue;
Position p = equationToPosition.get(e);
if (p==null) continue;
Offset inner = box.getInnerRectangle();
ends.add(new Position(p.x + inner.width/2.0, p.y + inner.height/2.0));
}
return ends;
}
public Position shift(Position p, double scale) {
double deltaX = scale * (Math.random()-0.5);
double deltaY = scale * (Math.random()-0.5);
return new Position(p.x+deltaX, p.y+deltaY);
}
public double calcCost(Position p, Position centre, Vector<Position> ends, double dx, double dy, Offset inner) {
Position pMid = new Position(p.x+inner.width/2.0, p.y+inner.height/2.0);
double costP = sq(Position.length(centre, pMid)) / 100000.0;
double fpX = (p.x-KetPanel.LEFT_BORDER_SIZE)/(dx-inner.width - KetPanel.LEFT_BORDER_SIZE - KetPanel.RIGHT_BORDER_SIZE);
double fpY = (p.y-KetPanel.TOP_BORDER_SIZE)/(dy-inner.height - KetPanel.TOP_BORDER_SIZE - KetPanel.BOTTOM_BORDER_SIZE);
if (fpX<0.0 || fpX>1.0) costP += 1.0e4 * sq(Math.abs(fpX-0.5));
if (fpY<0.0 || fpY>1.0) costP += 1.0e4 * sq(Math.abs(fpY-0.5));
for (Position end : ends) {
double lenP = Position.length(end, pMid);
costP += 10000.0*Math.min(1000.0 , 1.0/sq(lenP));
}
return costP;
}
public static double sq(double x) {
return x*x;
}
public Set<Box> createBox(ColourScheme colourScheme, int fontSize, Offset panelRectangle) {
Set<Box> set = Collections.synchronizedSet(new HashSet<Box>());
for (int boxIndex=0; boxIndex<getNumberOfEquations(); boxIndex++) {
Equation equation = getEquation(boxIndex);
if (equation==null) continue;
Box box = equation.toBox(colourScheme);
box.setupInnerRectangle(fontSize);
Offset equationRectangle = box.getInnerRectangle();
box.setupOuterRectangle(equationRectangle);
set.add(box);
if (box==null) continue;
if (!equationToPosition.containsKey(equation)) {
Position p = suitableVoid(box, boxIndex, panelRectangle, set);
equationToPosition.put(equation, p);
}
boxToEquation.put(box, equation);
}
return Collections.unmodifiableSet(set);
}
public Position suitableVoid(Box box, int boxIndex, Offset panelRectangle, Set<Box> set) {
Offset inner = box.getInnerRectangle();
if (inner==null) { // Never called, but just in case.
inner = new Offset(0.0, 0.0);
}
double cx = KetPanel.LEFT_BORDER_SIZE;
double mx = panelRectangle.width-KetPanel.LEFT_BORDER_SIZE-KetPanel.RIGHT_BORDER_SIZE-inner.width;
//- double cy = KetPanel.TOP_BORDER_SIZE + inner.height;
double cy = KetPanel.TOP_BORDER_SIZE;
double my = panelRectangle.height-KetPanel.TOP_BORDER_SIZE-KetPanel.BOTTOM_BORDER_SIZE-inner.height - panelDecoration.getModeBoxHeight();
//! double dy = panelRectangle.height - KetPanel.TOP_BORDER_SIZE - KetPanel.BOTTOM_BORDER_SIZE - panelDecoration.getModeBoxHeight();
if (set==null) {
return new Position(mx/2.0+cx, my/2.0+cy); // fail
}
Vector<Box> oldAndNewBoxes = new Vector<Box>();
if (boxSet!=null) {
oldAndNewBoxes.addAll(boxSet);
}
oldAndNewBoxes.addAll(set);
double bestDistance = 0.0;
Position bestPosition = new Position(Math.random()*mx+cx, Math.random()*my+cy);
Equation equation = boxToEquation.get(box);
Position topLeft = equationToPosition.get(equation);
for (int i=0; i<20; i++) { // Filter for non-overlapping locations, returning the one furthest from existing boxes.
Position p = new Position(Math.random()*mx+cx, Math.random()*my+cy);
boolean outside = true;
// For the given position and box, find the nearest existing box.
double distance = 0.0;
for (Box b : oldAndNewBoxes) {
if (b==box) continue;
if (b.innerBoxesOverlap(box)) {
outside = false;
break;
}
Equation e = boxToEquation.get(b);
Position tl = equationToPosition.get(e);
double d = Position.length(box.getCentre(topLeft), b.getCentre(tl));
distance = Math.min(distance, d);
}
if (outside && distance > bestDistance) {
bestDistance = distance;
bestPosition = p;
}
}
//- return new Position(Math.random()*mx+cx, Math.random()*my+cy); // fail
return bestPosition;
}
protected Equation getEquation(int index) { //< Align
Selection selection = getMathCollection().getSelection();
return selection.getEquationList().getEquation(index);
}
// Linear regression of x and y to find gradient, m, in y=m*x+c.
public double regressGradient(Vector<Double> xCol, Vector<Double> yCol) {
double xySum = 0.0;
double xSum = 0.0;
double ySum = 0.0;
double xSqSum = 0.0;
int n = Math.min(xCol.size(), yCol.size());
for (int i=0; i<n; i++) {
double x = xCol.get(i);
double y = yCol.get(i);
xySum += x*y;
xSum += x;
ySum += y;
xSqSum += x*x;
}
double numerator = xySum - xSum*ySum/n;
double denominator = xSqSum - xSum*xSum/n;
return numerator/denominator;
}
// Find gradient, m, from sample points (x,y) on a line, y=m*x+c. Note that c=mean(ys)-m*mean(xs).
public static double linearRegression(Vector<Position> ps) { // Version II (testing purposes)
double sumX = 0.0;
double sumY = 0.0;
double sumXY = 0.0;
double sumX2 = 0.0;
int n = ps.size();
for (Position p : ps) {
double x = p.x;
double y = p.y;
sumX += x;
sumY += y;
sumXY += x*y;
sumX2 += x*x;
}
return (sumXY - sumX*sumY/n) / (sumX2 - sumX*sumX/n);
}
public boolean calcBiggest() {
double biggest = 0.0;
for (Box box : boxSet) {
Equation e = boxToEquation.get(box);
if (e==null) continue;
Position p = equationToPosition.get(e);
if (p==null) continue;
Vector<Position> ps = e2ps.get(e);
if (ps==null) return false;
if (ps.size()<HISTORY) return false;
int n = ps.size();
double tSum = 0.0;
double txSum = 0.0;
double tySum = 0.0;
double tSqSum = 0.0;
double xSum = 0.0;
double ySum = 0.0;
for (int t=0; t<n; t++) {
tSum += t;
tSqSum += t*t;
Position q = ps.get(t);
double x = q.x;
double y = q.y;
txSum += t*x;
tySum += t*y;
xSum += x;
ySum += y;
}
double denominator = tSqSum - tSum*tSum/n;
double mX = (txSum - tSum*xSum/n)/denominator;
double mY = (tySum - tSum*ySum/n)/denominator;
double m2 = mX*mX + mY*mY;
if (m2 > biggest) {
biggest = m2;
}
}
return biggest < 0.01; //D
}
@Override
public void paint(Graphics2D g2D, ColourScheme colourScheme, int fontSize, Offset panelRectangle) {
if (boxSet==null) return;
for (Box box : boxSet) {
Equation equation = boxToEquation.get(box);
if (equation==null) continue;
Position topLeft = equationToPosition.get(equation);
if (topLeft==null) continue;
box.paint(g2D, topLeft, colourScheme);
}
if (update) {
if (calcBiggest()) {
repaint = false;
//- System.out.println("[pause]");
} else {
repaint = true;
perturbLoop(0.01, panelRectangle, 1000);
}
} else if (hasOverlap(panelRectangle)) {
repaint = true;
perturbLoop(0.01, panelRectangle, 1000);
} else {
repaint = false;
}
}
@Override
public boolean isArgumentVisible(Argument argument) {
if (boxSet==null) {
System.out.println("[null box set]");
return false;
}
for (Box box : boxSet) {
if (box.containsArgument(argument)) {
return true;
}
}
return false;
}
/**
* Set the current argument from the logically significant index of
* visible equations.
*/
public void selectByVisible(Cursor cursor, EquationList equationList) {
int currentIndex = 0;
if (currentIndex<0) {
currentIndex = 0;
}
if (currentIndex>=equationList.size()) {
currentIndex = equationList.size() - 1;
}
Equation e = equationList.getEquations().get(currentIndex);
cursor.setCurrent(e.getVisibleRoot());
}
@Override
public boolean requiresRepaint() {
return repaint;
}
// --------------------
// --- SIFTING, 'V' ---
// --------------------
Vector<Equation> equations = null;
/**
* This provides a list of equations that are to be displayed (or all
* if equations is null).
*/
public void setVisibleEquations(Vector<Equation> equations) {
this.equations = equations;
}
public Vector<Equation> getVisibleEquations() {
return equations;
}
@Override
public void noteChange(Graphics2D g2D, ColourScheme colourScheme, Argument before, Equation afterEquation, int fontSize, Offset panelRectangle) {
Argument after = afterEquation.getRoot();
if ( !isSingleSelectionSet() || before.subBranchEquals(after) || afterEquation.isText()) { //+ Did the equation change?
afterRootBox = null;
return;
}
afterRootBox = after.toBox(0L, colourScheme);
labelSetup(afterEquation, colourScheme);
afterBoxSetup(fontSize, panelRectangle);
}
private void labelSetup(Equation afterEquation, ColourScheme colourScheme) {
labelBox = afterEquation.getLabelBox(colourScheme);
labelBox.setProperty(Box.RIGHT_ALIGN);
labelBox.setProperty(Box.Y_CENTRE_ALIGN);
}
/**
* Animate the box that represents the new state.
*/
private Offset afterBoxSetup(int fontSize, Offset panelRectangle) {
double shapeWidth = panelRectangle.width - KetPanel.BORDER_OFFSET.width;
afterRootBox.setupInnerRectangle(fontSize);
double minimumHeight = afterRootBox.getInnerRectangle().height;
Offset equationRectangle = new Offset(shapeWidth, minimumHeight);
labelBox.setup(fontSize, equationRectangle);
Offset windowWithoutLabel = calcWindowWithoutLabel(equationRectangle);
afterRootBox.setupOuterRectangle(windowWithoutLabel);
afterRootBox.calcRootOffset();
return windowWithoutLabel;
}
private Offset calcEquationRectangle(Offset panelRectangle) {
if (equationBox==null) { System.out.print(" #0291 "); return null; }
double shapeWidth = panelRectangle.width - KetPanel.BORDER_OFFSET.width;
if (equationBox.getInnerRectangle()==null) { System.out.print(" #4921 "); return null; } // <-- (1)
double minimumHeight = equationBox.getInnerRectangle().height; // BUG: innterRectangle can be null!
return new Offset(shapeWidth, minimumHeight);
}
private Offset calcWindowWithoutLabel(Offset equationRectangle) {
if ( equationRectangle==null ) { System.out.print(" #1342 "); return null; } // BUG?
if ( labelBox==null ) { System.out.print(" #4382 "); return null; } // BUG?
if ( labelBox.getInnerRectangle()==null ) { System.out.print(" #9889 "); return null; } // BUG?
return new Offset(equationRectangle.width-labelBox.getInnerRectangle().width, equationRectangle.height);
}
private double getBoxHeight(Box box, Offset panelRectangle) {
return KetPanel.TOP_BORDER_SIZE; //?
}
/**
* Restrict the value of index within [min, max).
*/
private int bound(int index, int min, int max) {
if (index <= min) {
return min;
} else if (max <= index) {
return max;
} else {
return index;
}
}
private double bound(double index, double min, double max) {
return Math.min(Math.max(index, min), max);
}
private int getNumberOfEquations() {
Selection selection = mathCollection.getSelection();
return selection.getEquationList().size();
}
private Selection getSelection() {
return mathCollection.getSelection();
}
private boolean isSingleSelectionSet() {
return mathCollection.isSingleSelectionSet();
}
public MathCollection getMathCollection() {
return mathCollection;
}
public void viewEquation(int index) { }
public void moveViewUp() { }
public void moveViewDown() { }
public void toggleScatter() {
update = ! update;
if (update==false) {
repaint = false;
}
}
}