Skip to content

Commit

Permalink
FEATURE: Zooming!
Browse files Browse the repository at this point in the history
  • Loading branch information
itays123 committed Nov 2, 2023
1 parent 5d7e5cb commit 9244a24
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 23 deletions.
4 changes: 2 additions & 2 deletions FunctionCanvas.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import curve.CartesianAxesCanvas;
import curve.boundable.BoundableCanvas;
import function.Function;
import javafx.scene.paint.Color;

/**
* Expands the curve canvas to directly add functions and their derivatives
*/
public class FunctionCanvas extends CartesianAxesCanvas {
public class FunctionCanvas extends BoundableCanvas {

public FunctionCanvas(double width, double height) {
super(width, height);
Expand Down
24 changes: 19 additions & 5 deletions FunctionCurve.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import curve.Curve;
import curve.boundable.Boundable;
import function.Function;
import javafx.geometry.Point2D;
import javafx.scene.paint.Paint;

/**
* Represents evaluation that is done based on a rule
*/
public class FunctionCurve extends Curve {
public class FunctionCurve extends Curve implements Boundable {

public static final double EVALUATION_INTERVAL = 0.01;

public static final double JUMP_TRESHOLD = 2000; // the maximal amount of units that can be jumped in one x inteval

private Function function;

/**
* Constructs a new function path
*
Expand All @@ -22,13 +25,24 @@ public class FunctionCurve extends Curve {
*/
public FunctionCurve(Function func, double startX, double endX, Paint paint) {
super(paint);
Function derivative = func.derive();
this.function = func;
evaluate(startX, endX);
}

@Override
public void setBounds(double startX, double endX) {
clear();
evaluate(startX, endX);
}

private void evaluate(double startX, double endX) {
Function derivative = function.derive();
double y, dy;
for (double x = startX; x <= endX; x += EVALUATION_INTERVAL) {
try {
y = func.evaluate(x);
dy = derivative.evaluate(x);
if (Math.abs(dy * EVALUATION_INTERVAL) >= JUMP_TRESHOLD)
y = function.evaluate(x);
dy = derivative.evaluate(x) * EVALUATION_INTERVAL;
if (Math.abs(dy) >= JUMP_TRESHOLD)
throw new ArithmeticException("Jump is too big!");
points.add(new Point2D(x, y));
} catch (ArithmeticException e) {
Expand Down
21 changes: 20 additions & 1 deletion axis/Axis.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ private void initNegativeShare() {
if (this.getLength() == 0)
this.negativeShare = 0.5;
else
this.negativeShare = Math.min(Math.max((-this.start) / getLength(), 0), 1);
this.negativeShare = -this.start / getLength();
}

@Override
Expand All @@ -39,11 +39,30 @@ public double getPixelsPerUnit() {
}

public double getOriginLocation() {
return Math.min(pxSize, Math.max(0, getOriginLocationUnsafe()));
}

protected double getOriginLocationUnsafe() {
return pxSize * negativeShare;
}

public double getPxSize() {
return pxSize;
}

public double getPixelsOfUnits(double units) {
return ((units - this.start) / getLength()) * pxSize;
}

public double getUnitsOfPixels(double pixels) {
double axisShare = pixels / pxSize;
return start + axisShare * getLength();
}

@Override
public void setRange(double start, double end) {
super.setRange(start, end);
initNegativeShare();
}

}
16 changes: 6 additions & 10 deletions axis/Range.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ public Range() {
}

public Range(double start, double end) {
this.start = start;
this.end = end;
setRange(start, end);
}

public Range(Range other) {
Expand All @@ -25,18 +24,10 @@ public double getStart() {
return start;
}

public void expandStart(double x) {
start = Math.min(start, x);
}

public double getEnd() {
return end;
}

public void expandEnd(double x) {
end = Math.max(end, x);
}

/**
* Expand the range to include the number x
*
Expand All @@ -59,4 +50,9 @@ public double getLength() {
return end - start;
}

public void setRange(double start, double end) {
this.start = start;
this.end = end;
}

}
27 changes: 25 additions & 2 deletions axis/SteppedAxis.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,20 @@ public boolean expandTo(double x) {
boolean modified = super.expandTo(x);
if (!modified)
return false;
return setSteps();
}

/**
* Set the steps. Return false if cannot instanciate, since axis is too small to
* contain range
*
* @return
*/
private boolean setSteps() {
double maxNumOfSteps = pxSize / MIN_PX_PER_STEP;
unitsPerStep = (int) Math.ceil(getLength() / (maxNumOfSteps - STEP_MARGIN)); // divide the number of units by
// the number of usable steps (not
if (unitsPerStep < 0) // this will happen when the length is 0, or where the axis size is too small
if (unitsPerStep <= 0) // this will happen when the length is 0, or where the axis size is too small
return false;
double numOfSteps = getLength() / unitsPerStep + STEP_MARGIN;
pxPerStep = pxSize / numOfSteps; // including the step margin)
Expand All @@ -41,7 +51,14 @@ public double getPixelsOfUnits(double units) {
return super.getPixelsOfUnits(units);
double stepsFromOrigin = units / unitsPerStep;
double pxFromOrigin = stepsFromOrigin * pxPerStep;
return getOriginLocation() + pxFromOrigin;
return getOriginLocationUnsafe() + pxFromOrigin;
}

@Override
public double getUnitsOfPixels(double pixels) {
double pxFromOrigin = pixels - getOriginLocationUnsafe();
double stepsFromOrigin = pxFromOrigin / pxPerStep;
return stepsFromOrigin * unitsPerStep;
}

public Iterable<Integer> getSteps() {
Expand All @@ -61,4 +78,10 @@ public Iterable<Integer> getSteps() {
return steps;
}

@Override
public void setRange(double start, double end) {
super.setRange(start, end);
setSteps();
}

}
4 changes: 2 additions & 2 deletions curve/CartesianAxesCanvas.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ public class CartesianAxesCanvas extends CurveCanvas {
public static final int MARK_SIZE_PX = 10;
public static final Paint AXES_COLOR = Color.BLACK;

private SteppedAxis xAxis;
private SteppedAxis yAxis;
protected SteppedAxis xAxis;
protected SteppedAxis yAxis;

public CartesianAxesCanvas(double width, double height) {
super(width, height);
Expand Down
5 changes: 4 additions & 1 deletion curve/CurveCanvas.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public void addCurve(Curve curve) {
curves.add(curve);
}

public boolean isDrawn() {
return isDrawn;
}

private void strokeCurve(Curve curve) {
setStroke(curve.getPaint());
Point2D prev = null;
Expand All @@ -42,5 +46,4 @@ public void draw() {
strokeCurve(curve);
}
}

}
8 changes: 8 additions & 0 deletions curve/boundable/Boundable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package curve.boundable;

/**
* Represents a curve that can be expanded
*/
public interface Boundable {
void setBounds(double startX, double endX);
}
57 changes: 57 additions & 0 deletions curve/boundable/BoundableCanvas.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package curve.boundable;

import axis.Axis;
import curve.CartesianAxesCanvas;
import curve.Curve;
import javafx.event.EventHandler;
import javafx.scene.input.ScrollEvent;

/**
* Represents a cartesian axes canvas that supports zooming
*/
public class BoundableCanvas extends CartesianAxesCanvas implements EventHandler<ScrollEvent> {

public static final double ZOOM_SENSITIVITY = 1.01; // this is the zoom factor when scrolling one pixel

public BoundableCanvas(double width, double height) {
super(width, height);
setOnScroll(this);
}

@Override
public void addCurve(Curve curve) {
if (!(curve instanceof Boundable))
throw new UnsupportedOperationException("Cannot add non-boundable curve to a boundable canvas");
super.addCurve(curve);
}

private void zoomAxis(Axis axis, double fixedPx, double zoomFactor) {
if (fixedPx < 0 || fixedPx > axis.getPxSize())
throw new IllegalArgumentException("fixed point must be inside axis");
double edgeWeight = 1 / zoomFactor;
double fixedWeight = 1 - edgeWeight;
double fixedUnits = axis.getUnitsOfPixels(fixedPx);
double newStart = fixedWeight * fixedUnits + edgeWeight * axis.getStart();
double newEnd = fixedWeight * fixedUnits + edgeWeight * axis.getEnd();
axis.setRange(newStart, newEnd);
}

@Override
public void handle(ScrollEvent event) {
if (!isDrawn())
return;
clear();
double x = event.getX();
double y = getHeight() - event.getY();
double zoomFactor = Math.pow(ZOOM_SENSITIVITY, event.getDeltaY());
// if we zoom by x2, each unit will double its pixel range
zoomAxis(xAxis, x, zoomFactor);
zoomAxis(yAxis, y, zoomFactor);

for (Curve curve : curves) {
((Boundable) curve).setBounds(xAxis.getStart(), xAxis.getEnd());
}
draw();
}

}

0 comments on commit 9244a24

Please sign in to comment.