FunctionLine.java
package de.slothsoft.charts.linechart;
import java.util.Objects;
import de.slothsoft.charts.Area;
import de.slothsoft.charts.GraphicContext;
import de.slothsoft.charts.PaintInstructions;
/**
* A {@link Line} that is based on a function.
*
* @author Stef Schulz
* @since 0.1.0
*/
public class FunctionLine extends Line {
/**
* The function a {@link FunctionLine} is based on.
*
* @author Stef Schulz
* @since 0.1.0
*/
public static interface Function {
/**
* Applies this function to the given argument.
*
* @param x the function argument
* @return the function result (y)
*/
double apply(double x);
}
final Function function;
private Area preferredArea;
private double stepSize = 0.5;
/**
* Constructor.
*
* @param function the function; cannot be null
*/
public FunctionLine(Function function) {
this.function = Objects.requireNonNull(function);
this.preferredArea = calculateInitialPreferredArea();
}
private Area calculateInitialPreferredArea() {
double startY = this.function.apply(DEFAULT_START_X);
double endY = startY;
for (int x = DEFAULT_START_X + 1; x <= DEFAULT_END_X; x++) {
final double yValue = this.function.apply(x);
startY = Math.min(yValue, startY);
endY = Math.max(yValue, endY);
}
return new Area().startX(DEFAULT_START_X).startY(Math.min(0, startY)).endX(DEFAULT_END_X)
.endY(Math.max(0, endY));
}
@Override
protected Area calculatePreferredArea() {
return this.preferredArea;
}
@Override
public void paintOn(GraphicContext gc, PaintInstructions instructions) {
final Area drawnArea = instructions.getArea();
final double startX = drawnArea.getStartX();
final double endX = drawnArea.getEndX();
final int xLength = (int) ((endX - startX) / this.stepSize + 1);
final double[] x = new double[xLength];
final double[] y = new double[xLength];
for (int i = 0; i < xLength; i++) {
x[i] = startX + i * this.stepSize;
y[i] = this.function.apply(x[i]);
}
gc.setColor(this.color);
gc.drawPolyline(x, y);
}
/**
* Returns the area this line would like to display. The line chart still has to merge
* this area with the preferred area of other {@link Line}s.
*
* @return the preferred area; never null
*/
public Area getPreferredArea() {
return this.preferredArea;
}
/**
* Sets the area this line would like to display. The line chart still has to merge
* this area with the preferred area of other {@link Line}s.
*
* @param newPreferredArea the preferred area; cannot be null
* @return this instance
*/
public FunctionLine preferredArea(Area newPreferredArea) {
setPreferredArea(newPreferredArea);
return this;
}
/**
* Sets the area this line would like to display. The line chart still has to merge
* this area with the preferred area of other {@link Line}s.
*
* @param preferredArea the preferred area; cannot be null
*/
public void setPreferredArea(Area preferredArea) {
this.preferredArea = Objects.requireNonNull(preferredArea);
}
/**
* Returns the value that is used to determine which x values are drawn in
* {@link #paintOn(GraphicContext, PaintInstructions)}, i.e. a step size of 0.5 will
* draw the x values: 0, 0.5, 1, 1.5, ...
*
* @return the step size
*/
public double getStepSize() {
return this.stepSize;
}
/**
* Sets the value that is used to determine which x values are drawn in
* {@link #paintOn(GraphicContext, PaintInstructions)}, i.e. a step size of 0.5 will
* draw the x values: 0, 0.5, 1, 1.5, ...
*
* @param newStepSize the step size
* @return this instance;
*/
public FunctionLine stepSize(double newStepSize) {
setStepSize(newStepSize);
return this;
}
/**
* Sets the value that is used to determine which x values are drawn in
* {@link #paintOn(GraphicContext, PaintInstructions)}, i.e. a step size of 0.5 will
* draw the x values: 0, 0.5, 1, 1.5, ...
*
* @param stepSize the step size
*/
public void setStepSize(double stepSize) {
this.stepSize = stepSize;
}
}