package org.geogebra.common.euclidian.draw;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;
import org.geogebra.common.awt.GArea;
import org.geogebra.common.awt.GGraphics2D;
import org.geogebra.common.awt.GRectangle;
import org.geogebra.common.euclidian.BoundingBox;
import org.geogebra.common.euclidian.Drawable;
import org.geogebra.common.euclidian.EuclidianView;
import org.geogebra.common.euclidian.GeneralPathClipped;
import org.geogebra.common.factories.AwtFactory;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.arithmetic.FunctionalNVar;
import org.geogebra.common.kernel.arithmetic.IneqTree;
import org.geogebra.common.kernel.arithmetic.Inequality;
import org.geogebra.common.kernel.arithmetic.Inequality.IneqType;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoFunction;
import org.geogebra.common.plugin.Operation;
import org.geogebra.common.util.debug.Log;
import edu.uci.ics.jung.graph.util.Pair;
/**
* Graphical representation of inequality
*
* @author Zbynek Konecny
*
*/
public class DrawInequality extends Drawable {
private boolean isVisible;
/** true if label is visible */
boolean labelVisible;
private Drawable drawable;
private Operation operation = Operation.NO_OPERATION;
private DrawInequality left, right;
private Inequality ineq;
private FunctionalNVar function;
private boolean wasOR = false;
private double maxBound = 1000000;
private Drawable max;
private double minBound = -1000000;
private Drawable min;
private ArrayList<Pair<Map<Double, Drawable>>> orBounds = new ArrayList<Pair<Map<Double, Drawable>>>();
/**
* Creates new drawable linear inequality
*
* @param view
* view
* @param function
* boolean 2-var function
*/
public DrawInequality(EuclidianView view, FunctionalNVar function) {
this.view = view;
geo = (GeoElement) function;
this.function = function;
operation = function.getIneqs().getOperation();
if (function.getIneqs().getLeft() != null) {
left = new DrawInequality(function.getIneqs().getLeft(), view, geo);
}
if (function.getIneqs().getRight() != null) {
right = new DrawInequality(function.getIneqs().getRight(), view,
geo);
}
if (function.getIneqs().getIneq() != null) {
ineq = function.getIneqs().getIneq();
}
update();
}
/**
* method to remove extra borders of inequality
*/
public void update2() {
if (left == null || (operation != Operation.NOT && right == null)) {
return;
}
if (this.operation.equals(Operation.AND_INTERVAL)
&& left.drawable instanceof DrawInequality1Var) {
if (((DrawInequality1Var) left.drawable).isMinBoundSet()) {
double minLeft = ((DrawInequality1Var) left.drawable)
.getMinBound();
if (Kernel.isGreater(minLeft, minBound)) {
minBound = minLeft;
if (this.min != null) {
((DrawInequality1Var) this.min).ignoreLines();
}
this.min = left.drawable;
} else if (Kernel.isGreater(minBound, minLeft)
|| ((DrawInequality1Var) left.drawable)
.isGrtLessEqual()) {
((DrawInequality1Var) left.drawable).ignoreLines();
}
} else {
double maxLeft = ((DrawInequality1Var) left.drawable)
.getMaxBound();
if (Kernel.isGreater(maxBound, maxLeft)) {
maxBound = maxLeft;
if (this.max != null) {
((DrawInequality1Var) this.max).ignoreLines();
}
this.max = left.drawable;
} else if (Kernel.isGreater(maxLeft, maxBound)
|| ((DrawInequality1Var) left.drawable)
.isGrtLessEqual()) {
((DrawInequality1Var) left.drawable).ignoreLines();
}
}
if (((DrawInequality1Var) right.drawable).isMinBoundSet()) {
double minRight = ((DrawInequality1Var) right.drawable)
.getMinBound();
if (Kernel.isGreater(minRight, minBound)) {
minBound = minRight;
if (this.min != null) {
((DrawInequality1Var) this.min).ignoreLines();
}
this.min = right.drawable;
} else if (Kernel.isGreater(minBound, minRight)
|| ((DrawInequality1Var) right.drawable)
.isGrtLessEqual()) {
((DrawInequality1Var) right.drawable).ignoreLines();
}
} else {
double maxRight = ((DrawInequality1Var) right.drawable)
.getMaxBound();
if (Kernel.isGreater(maxBound, maxRight)) {
maxBound = maxRight;
if (this.max != null) {
((DrawInequality1Var) this.max).ignoreLines();
}
this.max = right.drawable;
} else if (Kernel.isGreater(maxRight, maxBound)
|| ((DrawInequality1Var) right.drawable)
.isGrtLessEqual()) {
((DrawInequality1Var) right.drawable).ignoreLines();
}
}
return;
}
if ((left.drawable == null
|| left.operation.equals(Operation.AND_INTERVAL))) {
left.min = min;
left.max = max;
left.minBound = minBound;
left.maxBound = maxBound;
left.update2();
if (this.operation.equals(Operation.OR)) {
if (!left.orBounds.isEmpty()) {
orBounds.addAll(left.orBounds);
}
Map<Double, Drawable> minEntry = new HashMap<Double, Drawable>();
if (left.min == null) {
minEntry.put(left.minBound, null);
} else {
minEntry.put(left.minBound, left.min);
}
Map<Double, Drawable> maxEntry = new HashMap<Double, Drawable>();
if (left.max == null) {
maxEntry.put(left.maxBound, null);
} else {
maxEntry.put(left.maxBound, left.max);
}
Pair<Map<Double, Drawable>> pair = new Pair<Map<Double, Drawable>>(
minEntry, maxEntry);
orBounds.add(pair);
}
this.min = left.min;
this.max = left.max;
this.minBound = left.minBound;
this.maxBound = left.maxBound;
} else if (left.drawable instanceof DrawInequality1Var && !wasOR) {
if (((DrawInequality1Var) left.drawable).isMinBoundSet()) {
double minLeft = ((DrawInequality1Var) left.drawable)
.getMinBound();
if (Kernel.isGreater(minLeft, minBound)) {
minBound = minLeft;
if (this.min != null) {
((DrawInequality1Var) this.min).ignoreLines();
}
this.min = left.drawable;
} else if (Kernel.isGreater(minBound, minLeft)
|| ((DrawInequality1Var) left.drawable)
.isGrtLessEqual()) {
((DrawInequality1Var) left.drawable).ignoreLines();
}
} else {
double maxLeft = ((DrawInequality1Var) left.drawable)
.getMaxBound();
if (Kernel.isGreater(maxBound, maxLeft)) {
maxBound = maxLeft;
if (this.max != null) {
((DrawInequality1Var) this.max).ignoreLines();
}
this.max = left.drawable;
} else if (Kernel.isGreater(maxLeft, maxBound)
|| ((DrawInequality1Var) left.drawable)
.isGrtLessEqual()) {
((DrawInequality1Var) left.drawable).ignoreLines();
}
}
}
/*
* if (this.operation.equals(Operation.OR)) { wasOR = true; }
*/
if ((right.drawable == null
|| right.operation.equals(Operation.AND_INTERVAL))) {
if (this.operation.equals(Operation.OR)) {
right.min = null;
right.max = null;
right.minBound = -1000000;
right.maxBound = 1000000;
right.orBounds = orBounds;
right.update2();
if (!right.orBounds.isEmpty()) {
orBounds.addAll(right.orBounds);
}
if (!orBounds.isEmpty()) {
isRightInOrBounds(orBounds, right);
} else {
if (Kernel.isGreater(right.minBound, minBound)
&& Kernel.isGreater(maxBound, right.minBound)
&& right.min != null) {
((DrawInequality1Var) right.min).ignoreLines();
}
if (Kernel.isGreater(right.maxBound, minBound)
&& Kernel.isGreater(maxBound, right.maxBound)
&& right.max != null) {
((DrawInequality1Var) right.max).ignoreLines();
}
}
Map<Double, Drawable> minEntry = new HashMap<Double, Drawable>();
if (right.min == null) {
minEntry.put(right.minBound, null);
} else {
minEntry.put(right.minBound, right.min);
}
Map<Double, Drawable> maxEntry = new HashMap<Double, Drawable>();
if (right.max == null) {
maxEntry.put(right.maxBound, null);
} else {
maxEntry.put(right.maxBound, right.max);
}
Pair<Map<Double, Drawable>> pair = new Pair<Map<Double, Drawable>>(
minEntry, maxEntry);
orBounds.add(pair);
} else {
right.min = min;
right.max = max;
right.minBound = minBound;
right.maxBound = maxBound;
right.update2();
this.min = right.min;
this.max = right.max;
this.minBound = right.minBound;
this.maxBound = right.maxBound;
}
} else if (right.drawable instanceof DrawInequality1Var
&& !operation.equals(Operation.OR)) {
if (((DrawInequality1Var) right.drawable).isMinBoundSet()) {
double minRight = ((DrawInequality1Var) right.drawable)
.getMinBound();
if (Kernel.isGreater(minRight, minBound)) {
minBound = minRight;
if (this.min != null) {
((DrawInequality1Var) this.min).ignoreLines();
}
this.min = right.drawable;
} else if (Kernel.isGreater(minBound, minRight)
|| ((DrawInequality1Var) right.drawable)
.isGrtLessEqual()) {
((DrawInequality1Var) right.drawable).ignoreLines();
}
} else {
double maxRight = ((DrawInequality1Var) right.drawable)
.getMaxBound();
if (Kernel.isGreater(maxBound, maxRight)) {
maxBound = maxRight;
if (this.max != null) {
((DrawInequality1Var) this.max).ignoreLines();
}
this.max = right.drawable;
} else if (Kernel.isGreater(maxRight, maxBound)
|| ((DrawInequality1Var) right.drawable)
.isGrtLessEqual()) {
((DrawInequality1Var) right.drawable).ignoreLines();
}
}
}
if (this.operation.equals(Operation.OR)) {
if (right.drawable instanceof DrawInequality1Var) {
if (((DrawInequality1Var) right.drawable).isMinBoundSet()) {
double minRight = ((DrawInequality1Var) right.drawable)
.getMinBound();
if (Kernel.isGreater(minBound, minRight)) {
minBound = minRight;
if (this.min != null) {
((DrawInequality1Var) this.min).ignoreLines();
}
this.min = right.drawable;
} else if (Kernel.isGreater(minRight, minBound)
&& Kernel.isGreater(maxBound, minRight)) {
((DrawInequality1Var) right.drawable).ignoreLines();
if (!Kernel.isEqual(maxBound, 1000000)
&& this.max != null) {
((DrawInequality1Var) this.max).ignoreLines();
}
} else if (Kernel.isEqual(maxBound, minRight)) {
((DrawInequality1Var) right.drawable).ignoreLines();
if (this.max != null) {
((DrawInequality1Var) this.max).ignoreLines();
}
}
} else {
double maxRight = ((DrawInequality1Var) right.drawable)
.getMaxBound();
if (Kernel.isGreater(maxRight, maxBound)) {
maxBound = maxRight;
if (this.max != null) {
((DrawInequality1Var) this.max).ignoreLines();
}
this.max = right.drawable;
} else if (Kernel.isGreater(maxBound, maxRight)
&& Kernel.isGreater(maxRight, minBound)) {
((DrawInequality1Var) right.drawable).ignoreLines();
if (Kernel.isEqual(maxBound, 1000000)
&& this.min != null) {
((DrawInequality1Var) this.min).ignoreLines();
}
} else if (Kernel.isEqual(maxRight, minBound)) {
((DrawInequality1Var) right.drawable).ignoreLines();
if (this.min != null) {
((DrawInequality1Var) this.min).ignoreLines();
}
}
}
return;
}
}
}
private static void isRightInOrBounds(
ArrayList<Pair<Map<Double, Drawable>>> orBounds2,
DrawInequality right2) {
for (int i = 0; i < orBounds2.size(); i++) {
double minCurrOrBound = orBounds2.get(i).getFirst().keySet()
.iterator().next();
double maxCurrOrBound = orBounds2.get(i).getSecond().keySet()
.iterator().next();
if (Kernel.isGreater(right2.minBound, minCurrOrBound)
&& Kernel.isGreater(maxCurrOrBound, right2.minBound)
&& right2.min != null) {
((DrawInequality1Var) right2.min).ignoreLines();
}
if (Kernel.isGreater(right2.maxBound, minCurrOrBound)
&& Kernel.isGreater(maxCurrOrBound, right2.maxBound)
&& right2.max != null) {
((DrawInequality1Var) right2.max).ignoreLines();
}
}
}
private DrawInequality(IneqTree tree, EuclidianView view, GeoElement geo) {
this.view = view;
this.geo = geo;
setForceNoFill(true);
updateRecursive(tree);
}
private GeneralPathClipped[] gpAxis;
@Override
final public void update() {
// take line g here, not geo this object may be used for conics too
isVisible = geo.isEuclidianVisible();
if (!isVisible) {
return;
}
labelVisible = geo.isLabelVisible();
// init gp
updateRecursive(function.getIneqs());
labelDesc = geo.getLabelDescription();
if ((geo instanceof GeoFunction) && ((GeoFunction) geo).showOnAxis()
&& !"y".equals(((GeoFunction) geo)
.getVarString(StringTemplate.defaultTemplate))) {
TreeSet<Double> zeros = new TreeSet<Double>();
((GeoFunction) geo).getIneqs().getZeros(zeros);
// radius of the dots
double radius = geo.getLineThickness()
* DrawInequality1Var.DOT_RADIUS;
// we add poits 2*radius to the left and right of the screen
zeros.add(view.getXmin() - 2 * radius * view.getXscale());
zeros.add(view.getXmax() + 2 * radius * view.getXscale());
gpAxis = new GeneralPathClipped[zeros.size()];
Double last = null;
int gpCount = 0;
for (Double zero : zeros) {
if (last != null) {
boolean value = ((GeoFunction) geo)
.evaluateBoolean(0.5 * (last + zero));
if (value) {
gpAxis[gpCount] = new GeneralPathClipped(view);
gpAxis[gpCount].moveTo(
view.toScreenCoordXd(last) + radius,
view.toScreenCoordYd(0));
gpAxis[gpCount].lineTo(
view.toScreenCoordXd(zero) - radius,
view.toScreenCoordYd(0));
gpCount++;
}
}
last = zero;
}
updateStrokes(geo);
} else {
gpAxis = null;
}
if (left != null && right != null) {
maxBound = 1000000;
minBound = -1000000;
max = null;
min = null;
orBounds = new ArrayList<Pair<Map<Double, Drawable>>>();
update2();
if (maxBound < minBound) {
if (max != null) {
((DrawInequality1Var) max).ignoreLines();
}
if (min != null) {
((DrawInequality1Var) min).ignoreLines();
}
}
}
}
private void updateRecursive(IneqTree it) {
updateTrees(it);
operation = it.getOperation();
updateShape();
if (left != null) {
yLabel = left.yLabel;
xLabel = left.xLabel;
}
if (ineq != it.getIneq()) {
ineq = it.getIneq();
}
if (ineq != null) {
if (drawable == null || !matchBorder(ineq.getBorder(), drawable)) {
createDrawable();
} else if (ineq.getType() == IneqType.INEQUALITY_CONIC) {
ineq.getConicBorder().setInverseFill(ineq.isAboveBorder());
if (drawable instanceof DrawConic) {
((DrawConic) drawable).setIgnoreSingularities(
!ineq.isStrict() == ineq.isAboveBorder());
}
}
drawable.update();
setShape(drawable.getShape());
xLabel = drawable.xLabel;
yLabel = drawable.yLabel;
}
if (geo.isInverseFill() && !isForceNoFill()) {
GArea b = AwtFactory.getPrototype().newArea(view.getBoundingPath());
b.subtract(getShape());
setShape(b);
}
}
private void createDrawable() {
switch (ineq.getType()) {
case INEQUALITY_PARAMETRIC_Y:
drawable = new DrawParametricInequality(ineq, view, geo);
break;
case INEQUALITY_PARAMETRIC_X:
drawable = new DrawParametricInequality(ineq, view, geo);
break;
case INEQUALITY_1VAR_X:
drawable = new DrawInequality1Var(ineq, view, geo, false);
break;
case INEQUALITY_1VAR_Y:
drawable = new DrawInequality1Var(ineq, view, geo, true);
break;
case INEQUALITY_CONIC:
drawable = new DrawConic(view, ineq.getConicBorder(),
!ineq.isStrict() == ineq.isAboveBorder());
ineq.getConicBorder().setInverseFill(ineq.isAboveBorder());
break;
case INEQUALITY_LINEAR:
drawable = new DrawLine(view, ineq.getLineBorder());
ineq.getLineBorder().setInverseFill(ineq.isAboveBorder());
break;
/*
* case IneqType.INEQUALITY_IMPLICIT: drawable = new
* DrawImplicitPoly(view, ineq.getImpBorder()); break; TODO put this
* back when implicit polynomial can be shaded
*/
default:
Log.debug("Unhandled inequality type");
return;
}
drawable.setGeoElement(geo);
drawable.setForceNoFill(true);
}
private void updateShape() {
if (operation.equals(Operation.AND)
|| operation.equals(Operation.AND_INTERVAL)) {
setShape(left.getShape());
getShape().intersect(right.getShape());
} else if (operation.equals(Operation.OR)) {
setShape(left.getShape());
getShape().add(right.getShape());
} else if (operation.equals(Operation.EQUAL_BOOLEAN)) {
setShape(AwtFactory.getPrototype().newArea(view.getBoundingPath()));
left.getShape().exclusiveOr(right.getShape());
getShape().subtract(left.getShape());
} else if (operation.equals(Operation.NOT_EQUAL)) {
setShape(left.getShape());
getShape().exclusiveOr(right.getShape());
} else if (operation.equals(Operation.NOT)) {
setShape(AwtFactory.getPrototype().newArea(view.getBoundingPath()));
getShape().subtract(left.getShape());
}
}
private void updateTrees(IneqTree it) {
if (it.getLeft() != null && left == null) {
left = new DrawInequality(it.getLeft(), view, geo);
}
if (it.getLeft() != null) {
left.updateRecursive(it.getLeft());
} else {
left = null;
}
if (it.getRight() != null && right == null) {
right = new DrawInequality(it.getRight(), view, geo);
}
if (it.getRight() != null) {
right.updateRecursive(it.getRight());
} else {
right = null;
}
}
private static boolean matchBorder(GeoElement border, Drawable d) {
if (d instanceof DrawConic && ((DrawConic) d).getConic().equals(border)) {
return true;
}
/*
* if (d instanceof DrawImplicitPoly && ((DrawImplicitPoly)
* d).getPoly().equals(border)) return true;
*/
if (d instanceof DrawParametricInequality
&& ((DrawParametricInequality) d).getBorder().equals(border)) {
return ((DrawParametricInequality) d).isXparametric();
}
return false;
}
@Override
public void draw(GGraphics2D g2) {
if (!isForceNoFill() && !isVisible) {
return;
}
if (operation.equals(Operation.NO_OPERATION)) {
if (drawable != null) {
drawable.updateStrokesJustLineThickness(geo);
if (geo.getLineThickness() > 0) {
drawable.draw(g2);
}
}
} else {
if (left != null) {
left.updateStrokesJustLineThickness(geo);
left.draw(g2);
}
if (right != null) {
right.updateStrokesJustLineThickness(geo);
right.draw(g2);
}
}
if (!isForceNoFill()) {
if (gpAxis != null) {
if (geo.doHighlighting()) {
g2.setPaint(geo.getSelColor());
g2.setStroke(selStroke);
for (int i = 0; gpAxis[i] != null; i++) {
g2.draw(gpAxis[i]);
}
}
g2.setPaint(getObjectColor());
g2.setStroke(objStroke);
for (int i = 0; gpAxis[i] != null; i++) {
g2.draw(gpAxis[i]);
}
} else {
if (geo.getFillType() != GeoElement.FillType.IMAGE) {
// make sure line thickness set for hatching
updateStrokes(geo);
}
fill(g2, getShape());
}
}
if (labelVisible) {
g2.setFont(view.getFontConic());
g2.setPaint(geo.getLabelColor());
drawLabel(g2);
}
}
@Override
public GeoElement getGeoElement() {
return geo;
}
private boolean hit2(int x, int y) {
double[] coords = new double[] { view.toRealWorldCoordX(x),
view.toRealWorldCoordY(y) };
if (geo instanceof GeoFunction && ((GeoFunction) geo)
.getVarString(StringTemplate.defaultTemplate).equals("y")) {
return ((GeoFunction) geo).getFunction().evaluateBoolean(coords[1]);
}
return ((FunctionalNVar) geo).getFunction().evaluateBoolean(coords);
}
@Override
public boolean hit(int x, int y, int hitThreshold) {
if (!geo.isEuclidianVisible()) {
return false;
}
if (geo instanceof GeoFunction && ((GeoFunction) geo).showOnAxis()
&& Math.abs(y - view.toScreenCoordY(0)) > hitThreshold) {
return false;
}
return hit2(x, y) || hit2(x - 4, y) || hit2(x + 4, y) || hit2(x, y - 4)
|| hit2(x, y + 4);
}
@Override
public boolean isInside(GRectangle rect) {
// TODO Auto-generated method stub
return false;
}
@Override
public void setGeoElement(GeoElement geo) {
this.geo = geo;
}
@Override
public BoundingBox getBoundingBox() {
// TODO Auto-generated method stub
return null;
}
@Override
public void updateBoundingBox() {
// TODO Auto-generated method stub
}
}