Axis.java
package de.slothsoft.charts.common;
import java.util.Objects;
import java.util.function.DoubleUnaryOperator;
import de.slothsoft.charts.Chart;
import de.slothsoft.charts.ChartPart;
import de.slothsoft.charts.GraphicContext;
import de.slothsoft.charts.RefreshListener;
import de.slothsoft.charts.internal.RefreshListeners;
/**
* The X or Y axis of a {@link Chart}. You have the following methods to implement this
* abstract class:
* <ul>
* <li>{@link #paintHorizontalAxis(GraphicContext, double, double, double)}</li>
* <li>{@link #paintVerticalAxis(GraphicContext, double, double, double)}</li>
* </ul>
*
* @author Stef Schulz
* @since 0.1.0
*/
public abstract class Axis implements ChartPart {
protected final RefreshListeners refreshListeners = new RefreshListeners(this);
int tickSteps = 1;
int tickSize = 1;
int bigTickSteps = 5;
int bigTickSize = 3;
double arrowSize = 3;
protected DoubleUnaryOperator chartXConverter;
protected DoubleUnaryOperator chartYConverter;
/**
* Creates an axis using no converters for the coordinates.
*/
public Axis() {
this(DoubleUnaryOperator.identity(), DoubleUnaryOperator.identity());
}
/**
* Creates an axis using converters for the coordinates.
*
* @param chartXConverter converter for graph x to chart x
* @param chartYConverter converter for graph y to chart y
*/
public Axis(DoubleUnaryOperator chartXConverter, DoubleUnaryOperator chartYConverter) {
this.chartXConverter = Objects.requireNonNull(chartXConverter);
this.chartYConverter = Objects.requireNonNull(chartYConverter);
}
/**
* Paints the horizontal axis using the chart.
*
* @param gc the graphic context to draw on
* @param graphStartX the graph's start x
* @param graphEndX the graph's end x
* @param y the y coordinate this axis get drawn on (chart coordinate!)
*/
protected void paintHorizontalAxis(GraphicContext gc, double graphStartX, double graphEndX, double y) {
final double xMin = this.chartXConverter.applyAsDouble(graphStartX);
final double xMax = this.chartXConverter.applyAsDouble(graphEndX);
// paint the line
gc.setColor(0xFF000000);
gc.drawLine(xMin, y, xMax, y);
// paint the big and little ticks
final int end = (int) Math.ceil(graphEndX);
for (int i = (int) Math.floor(graphStartX); i < end; i++) {
final double x = this.chartXConverter.applyAsDouble(i);
if (i % this.tickSteps == 0) {
gc.drawLine(x, y - this.tickSize, x, y + this.tickSize);
}
if (i % this.bigTickSteps == 0) {
gc.drawLine(x, y - this.bigTickSize, x, y + this.bigTickSize);
}
}
// paint the arrow at the end
final double[] arrowX = {xMax, xMax - this.arrowSize, xMax - this.arrowSize};
final double[] arrowY = {y, y + this.arrowSize, y - this.arrowSize};
gc.fillPolygon(arrowX, arrowY);
}
/**
* Paints the vertical axis using the chart.
*
* @param gc the graphic context to draw on
* @param graphStartX the graph's start y
* @param graphEndY the graph's end y
* @param x the y coordinate this axis get drawn on (chart coordinate!)
*/
protected void paintVerticalAxis(GraphicContext gc, double graphStartX, double graphEndY, double x) {
final double yMin = this.chartYConverter.applyAsDouble(graphStartX);
final double yMax = this.chartYConverter.applyAsDouble(graphEndY);
// paint the line
gc.setColor(0xFF000000);
gc.drawLine(x, yMin, x, yMax);
// paint the big and little ticks
final int end = (int) Math.ceil(graphEndY);
for (int i = (int) Math.floor(graphStartX); i < end; i++) {
final double y = this.chartYConverter.applyAsDouble(i);
if (i % this.tickSteps == 0) {
gc.drawLine(x - this.tickSize, y, x + this.tickSize, y);
}
if (i % this.bigTickSteps == 0) {
gc.drawLine(x - this.bigTickSize, y, x + this.bigTickSize, y);
}
}
// paint the arrow at the end
final double[] arrowX = {x, x + this.arrowSize, x - this.arrowSize};
final double[] arrowY = {yMax - this.arrowSize, yMax, yMax};
gc.fillPolygon(arrowX, arrowY);
}
@Override
public void addRefreshListener(RefreshListener listener) {
this.refreshListeners.addRefreshListener(listener);
}
@Override
public void removeRefreshListener(RefreshListener listener) {
this.refreshListeners.removeRefreshListener(listener);
}
/**
* Returns the size of the ticks in pixels.
*
* @return the tick size
*/
public int getTickSize() {
return this.tickSize;
}
/**
* Sets the size of the ticks in pixels.
*
* @param newTickSize the tick size
* @return this instance
*/
public Axis tickSize(int newTickSize) {
setTickSize(newTickSize);
return this;
}
/**
* Sets the size of the ticks in pixels.
*
* @param tickSize the tick size
*/
public void setTickSize(int tickSize) {
final int oldTickSize = this.tickSize;
this.tickSize = tickSize;
if (oldTickSize != tickSize) {
this.refreshListeners.fireRefreshNeeded();
}
}
/**
* Returns the size of the big ticks in pixels.
*
* @return the big tick size
*/
public int getBigTickSize() {
return this.bigTickSize;
}
/**
* Sets the size of the big ticks in pixels.
*
* @param newBigTickSize the big tick size
* @return this instance
*/
public Axis bigTickSize(int newBigTickSize) {
setBigTickSize(newBigTickSize);
return this;
}
/**
* Sets the size of the big ticks in pixels.
*
* @param bigTickSize the big tick size
*/
public void setBigTickSize(int bigTickSize) {
final int oldBigTickSize = this.bigTickSize;
this.bigTickSize = bigTickSize;
if (oldBigTickSize != bigTickSize) {
this.refreshListeners.fireRefreshNeeded();
}
}
/**
* Returns the range after which a big tick is painted on the graph.
*
* @return the big tick step length
*/
public int getBigTickSteps() {
return this.bigTickSteps;
}
/**
* Sets the range after which a big tick is painted on the graph.
*
* @param newBigTickSteps the big tick step length
* @return this instance
*/
public Axis bigTickSteps(int newBigTickSteps) {
setBigTickSteps(newBigTickSteps);
return this;
}
/**
* Sets the range after which a big tick is painted on the graph.
*
* @param bigTickSteps the big tick step length
*/
public void setBigTickSteps(int bigTickSteps) {
final int oldBigTickSteps = this.bigTickSteps;
this.bigTickSteps = bigTickSteps;
if (oldBigTickSteps != bigTickSteps) {
this.refreshListeners.fireRefreshNeeded();
}
}
/**
* Returns the range after which a tick is painted on the graph.
*
* @return the tick step length
*/
public int getTickSteps() {
return this.tickSteps;
}
/**
* Sets the range after which a tick is painted on the graph.
*
* @param newTickSteps the tick step length
* @return this instance
*/
public Axis tickSteps(int newTickSteps) {
setTickSteps(newTickSteps);
return this;
}
/**
* Sets the range after which a tick is painted on the graph.
*
* @param tickSteps the tick step length
*/
public void setTickSteps(int tickSteps) {
final int oldTickSteps = this.tickSteps;
this.tickSteps = tickSteps;
if (oldTickSteps != tickSteps) {
this.refreshListeners.fireRefreshNeeded();
}
}
/**
* Returns the size of the arrow at the end of the graph.
*
* @return the arrow size in pixels
*/
public double getArrowSize() {
return this.arrowSize;
}
/**
* Sets the size of the arrow at the end of the graph.
*
* @param newArrowSize the arrow size in pixels
* @return this instance
*/
public Axis arrowSize(double newArrowSize) {
setArrowSize(newArrowSize);
return this;
}
/**
* Sets the size of the arrow at the end of the graph.
*
* @param arrowSize the arrow size in pixels
*/
public void setArrowSize(double arrowSize) {
final double oldArrowSize = this.arrowSize;
this.arrowSize = arrowSize;
if (oldArrowSize != arrowSize) {
this.refreshListeners.fireRefreshNeeded();
}
}
}