de.javagl.viewer.painters.CoordinateSystemPainter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of viewer-core Show documentation
Show all versions of viewer-core Show documentation
A Java Swing Panel that allows rotation, translation and zooming
The newest version!
/*
* www.javagl.de - Viewer
*
* Copyright (c) 2013-2015 Marco Hutter - http://www.javagl.de
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package de.javagl.viewer.painters;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.function.DoubleFunction;
import java.util.function.IntSupplier;
import de.javagl.geom.AffineTransforms;
import de.javagl.geom.Lines;
import de.javagl.geom.Points;
import de.javagl.geom.Rectangles;
import de.javagl.viewer.Painter;
/**
* Implementation of the {@link Painter} interface that paints a coordinate
* system, consisting of a grid and labeled coordinate axes.
*/
public final class CoordinateSystemPainter implements Painter
{
/**
* A line object, used internally in various methods
*/
private static final Line2D.Double TEMP_LINE = new Line2D.Double();
/**
* A point object, used internally in various methods
*/
private static final Point2D.Double TEMP_POINT = new Point2D.Double();
/**
* The stroke for the axes
*/
private final Stroke stroke = new BasicStroke(1.0f);
/**
* The color of the x-axis. If this is null
, then the
* x-axis will not be painted.
*/
private Color axisColorX = Color.GRAY;
/**
* The color of the y-axis. If this is null
, then the
* y-axis will not be painted.
*/
private Color axisColorY = Color.GRAY;
/**
* The size of the tick marks on the screen
*/
private final double tickSizeScreen = 5;
/**
* The minimum distance that two ticks should have on the screen
*/
private final double minScreenTickDistanceX = 20;
/**
* The minimum distance that two ticks should have on the screen
*/
private final double minScreenTickDistanceY = 20;
/**
* Whether the "minScreenTickDistanceX" should be adjusted
* based on the strings that are printed for the labels
*/
private final boolean adjustForStringLengths = true;
/**
* The color for the x-axis tick grid lines. If this is null
,
* then no grid lines will be painted
*/
private Color gridColorX = new Color(240,240,240);
/**
* The color for the y-axis tick grid lines. If this is null
,
* then no grid lines will be painted
*/
private Color gridColorY = new Color(240,240,240);
/**
* The fixed distance between ticks on the x-axis, in world coordinates
*/
private double fixedWorldTickDistanceX = Double.NaN;
/**
* The fixed distance between ticks on the y-axis, in world coordinates
*/
private double fixedWorldTickDistanceY = Double.NaN;
/**
* The tick positions of the x-axis, in world coordinates
*/
private double worldTicksX[];
/**
* The label format for the x-axis. May be null
* if the {@link #labelFormatterX} is used.
*/
private String labelFormatX;
/**
* The formatter that will receive values for ticks at the x-axis, and
* return the corresponding label string. May be null
if
* the default {@link #labelFormatX} should be used.
*/
private DoubleFunction labelFormatterX;
/**
* The tick positions of the y-axis, in world coordinates
*/
private double worldTicksY[];
/**
* The label format for the y-axis. May be null
* if the {@link #labelFormatterY} is used.
*/
private String labelFormatY;
/**
* The formatter that will receive values for ticks at the y-axis, and
* return the corresponding label string. May be null
if
* the default {@link #labelFormatY} should be used.
*/
private DoubleFunction labelFormatterY;
/**
* The bounds of the currently visible area, in world coordinates
*/
private final Rectangle2D worldBounds = new Rectangle2D.Double();
/**
* The supplier that provides the x-coordinate on the screen
* where the x-axis should start
*/
private IntSupplier supplierScreenMinX = null;
/**
* The supplier that provides the x-coordinate on the screen
* where the x-axis should end
*/
private IntSupplier supplierScreenMaxX = null;
/**
* The supplier that provides the y-coordinate on the screen
* where the x-axis should be located
*/
private IntSupplier supplierScreenY = null;
/**
* The supplier that provides the y-coordinate on the screen
* where the y-axis should start
*/
private IntSupplier supplierScreenMinY = null;
/**
* The supplier that provides the y-coordinate on the screen
* where the y-axis should end
*/
private IntSupplier supplierScreenMaxY = null;
/**
* The supplier that provides the x-coordinate on the screen
* where the y-axis should be located
*/
private IntSupplier supplierScreenX = null;
/**
* The minimum value for the x-axis, in world coordinates
*
* @see #setAxisRangeX(double, double)
*/
private double worldMinAxisX = Double.NaN;
/**
* The maximum value for the x-axis, in world coordinates
*
* @see #setAxisRangeX(double, double)
*/
private double worldMaxAxisX = Double.NaN;
/**
* The minimum value for the Y-axis, in world coordinates
*
* @see #setAxisRangeY(double, double)
*/
private double worldMinAxisY = Double.NaN;
/**
* The maximum value for the y-axis, in world coordinates
*
* @see #setAxisRangeY(double, double)
*/
private double worldMaxAxisY = Double.NaN;
/**
* The x-coordinate where the y-axis should be, in world coordinates
*/
private double worldXofY = 0.0;
/**
* The y-coordinate where the x-axis should be, in world coordinates
*/
private double worldYofX = 0.0;
/**
* Whether ticks on the x-axis should be oriented along the positive y-axis
*/
private boolean tickOrientationPositiveX = false;
/**
* Whether the y-component of the label painter for the x-axis labels
* should be adjusted automatically (to either be 0.0 or 1.0),
* depending on the {@link #tickOrientationPositiveX} flag and
* the current y-scaling of the world-to-screen transform
*/
private boolean adjustLabelAnchorX = false;
/**
* Whether ticks on the y-axis should be oriented along the positive x-axis
*/
private boolean tickOrientationPositiveY = false;
/**
* Whether the x-component of the label painter for the y-axis labels
* should be adjusted automatically (to either be 0.0 or 1.0),
* depending on the {@link #tickOrientationPositiveY} flag and
* the current x-scaling of the world-to-screen transform
*/
private boolean adjustLabelAnchorY = false;
/**
* The {@link LabelPainter} for the labels on the x-axis
*/
private final LabelPainter labelPainterX;
/**
* The {@link LabelPainter} for the labels on the y-axis
*/
private final LabelPainter labelPainterY;
/**
* Creates a new default coordinate system painter
*/
public CoordinateSystemPainter()
{
Font font = new Font("Dialog", Font.PLAIN, 9);
labelPainterX = new LabelPainter();
labelPainterX.setPaint(axisColorX);
labelPainterX.setTransformingLabels(false);
labelPainterX.setFont(font);
labelPainterX.setLabelAnchor(0.5, 0.0);
labelPainterY = new LabelPainter();
labelPainterY.setPaint(axisColorY);
labelPainterY.setTransformingLabels(false);
labelPainterY.setFont(font);
labelPainterY.setLabelAnchor(1.0, 0.5);
}
/**
* Returns the {@link LabelPainter} that is used for painting the labels
* along the x-axis
*
* @return The {@link LabelPainter}
*/
public LabelPainter getLabelPainterX()
{
return labelPainterX;
}
/**
* Set the formatter that will receive x-values for ticks, and return
* the string that should be painted at this coordinate. This formatter
* may be null, in which case a string representation of the x-value
* will be painted.
*
* @param labelFormatterX The formatter
*/
public void setLabelFormatterX(DoubleFunction labelFormatterX)
{
this.labelFormatterX = labelFormatterX;
}
/**
* Returns the {@link LabelPainter} that is used for painting the labels
* along the y-axis
*
* @return The {@link LabelPainter}
*/
public LabelPainter getLabelPainterY()
{
return labelPainterY;
}
/**
* Set the formatter that will receive y-values for ticks, and return
* the string that should be painted at this coordinate. This formatter
* may be null, in which case a string representation of the y-value
* will be painted.
*
* @param labelFormatterY The formatter
*/
public void setLabelFormatterY(DoubleFunction labelFormatterY)
{
this.labelFormatterY = labelFormatterY;
}
/**
* Set the fixed distance between ticks on the x-axis, in world coordinates.
* If the given value is NaN, then the distance will be computed
* automatically.
*
* @param fixedWorldTickDistanceX The tick distance
* @throws IllegalArgumentException If the given distance is not positive
*/
public void setFixedWorldTickDistanceX(double fixedWorldTickDistanceX)
{
if (fixedWorldTickDistanceX <= 0)
{
throw new IllegalArgumentException(
"Tick distance must be positive, "
+ "but is "+fixedWorldTickDistanceX);
}
this.fixedWorldTickDistanceX = fixedWorldTickDistanceX;
}
/**
* Set the fixed distance between ticks on the y-axis, in world coordinates.
* If the given value is NaN, then the distance will be computed
* automatically.
*
* @param fixedWorldTickDistanceY The tick distance
* @throws IllegalArgumentException If the given distance is not positive
*/
public void setFixedWorldTickDistanceY(double fixedWorldTickDistanceY)
{
if (fixedWorldTickDistanceY <= 0)
{
throw new IllegalArgumentException(
"Tick distance must be positive, "
+ "but is "+fixedWorldTickDistanceY);
}
this.fixedWorldTickDistanceY = fixedWorldTickDistanceY;
}
/**
* Set the color for the grid lines that should be painted at the x-axis
* ticks in the background. If the given color is null
, then
* the grid lines will not be painted.
*
* @param gridColorX The grid color
*/
public void setGridColorX(Color gridColorX)
{
this.gridColorX = gridColorX;
}
/**
* Set the color for the grid lines that should be painted at the y-axis
* ticks in the background. If the given color is null
, then
* the grid lines will not be painted.
*
* @param gridColorY The grid color
*/
public void setGridColorY(Color gridColorY)
{
this.gridColorY = gridColorY;
}
/**
* Set the color for the x-axis. If this is null
, then
* the x-axis will not be painted.
*
* @param axisColorX The color for the x-axis
*/
public void setAxisColorX(Color axisColorX)
{
this.axisColorX = axisColorX;
}
/**
* Set whether ticks on the x-axis should be oriented along the positive
* y-axis
*
* @param tickOrientationPositiveX Whether the ticks should be oriented
* along the positive axis
*/
void setTickOrientationPositiveX(boolean tickOrientationPositiveX)
{
this.tickOrientationPositiveX = tickOrientationPositiveX;
}
/**
* Set the range of the x-axis that should be displayed. If either
* of the given values is Double.NaN
, then the minimum
* or maximum value of the currently visible world area will be
* used, respectively
*
* @param worldMinAxisX The minimum value, in world coordinates
* @param worldMaxAxisX The maximum value, in world coordinates
*/
public void setAxisRangeX(double worldMinAxisX, double worldMaxAxisX)
{
this.worldMinAxisX = worldMinAxisX;
this.worldMaxAxisX = worldMaxAxisX;
}
/**
* Set the location where the x-axis should be painted
*
* @param worldYofX The y-coordinate where the x-axis should be painted
*/
public void setAxisLocationX(double worldYofX)
{
this.worldYofX = worldYofX;
}
/**
* Set the layout for the x-axis. The given suppliers will provide
* the screen coordinates that determine how the x-axis should be
* displayed. If any of the given suppliers is null
,
* then the axis will be painted in world coordinates.
*
* Note that this screen-relative layout is not sensibly applicable
* when the view is rotated.
*
* @param supplierScreenMinX The supplier for the x-coordinate on the
* screen where the x-axis should start
* @param supplierScreenMaxX The supplier for the x-coordinate on the
* screen where the x-axis should end
* @param supplierScreenY The supplier for the y-coordinate on the
* screen where the x-axis should be located
*/
public void setScreenAxisLayoutX(
IntSupplier supplierScreenMinX,
IntSupplier supplierScreenMaxX,
IntSupplier supplierScreenY)
{
this.supplierScreenMinX = supplierScreenMinX;
this.supplierScreenMaxX = supplierScreenMaxX;
this.supplierScreenY = supplierScreenY;
}
/**
* Set the color for the y-axis. If this is null
, then
* the y-axis will not be painted.
*
* @param axisColorY The color for the y-axis
*/
public void setAxisColorY(Color axisColorY)
{
this.axisColorY = axisColorY;
}
/**
* Set the range of the y-axis that should be displayed. If either
* of the given values is Double.NaN
, then the minimum
* or maximum value of the currently visible world area will be
* used, respectively
*
* @param worldMinAxisY The minimum value, in world coordinates
* @param worldMaxAxisY The maximum value, in world coordinates
*/
public void setAxisRangeY(double worldMinAxisY, double worldMaxAxisY)
{
this.worldMinAxisY = worldMinAxisY;
this.worldMaxAxisY = worldMaxAxisY;
}
/**
* Set the location where the y-axis should be painted
*
* @param worldXofY The x-coordinate where the y-axis should be painted
*/
public void setAxisLocationY(double worldXofY)
{
this.worldXofY = worldXofY;
}
/**
* Set whether ticks on the y-axis should be oriented along the positive
* x-axis
*
* @param tickOrientationPositiveY Whether the ticks should be oriented
* along the positive axis
*/
void setTickOrientationPositiveY(boolean tickOrientationPositiveY)
{
this.tickOrientationPositiveY = tickOrientationPositiveY;
}
/**
* Set the layout for the y-axis. The given suppliers will provide
* the screen coordinates that determine how the y-axis should be
* displayed. If any of the given suppliers is null
,
* then the axis will be painted in world coordinates.
*
* Note that this screen-relative layout is not sensibly applicable
* when the view is rotated.
*
* @param supplierScreenMinY The supplier for the y-coordinate on the
* screen where the y-axis should start
* @param supplierScreenMaxY The supplier for the y-coordinate on the
* screen where the y-axis should end
* @param supplierScreenX The supplier for the x-coordinate on the
* screen where the y-axis should be located
*/
public void setScreenAxisLayoutY(
IntSupplier supplierScreenMinY,
IntSupplier supplierScreenMaxY,
IntSupplier supplierScreenX)
{
this.supplierScreenMinY = supplierScreenMinY;
this.supplierScreenMaxY = supplierScreenMaxY;
this.supplierScreenX = supplierScreenX;
}
/**
* Update the data that is used internally for painting the x-axis,
* namely the {@link #worldTicksX} and the {@link #labelFormatX}
*
* @param worldToScreen The world-to-screen transform
* @param worldMinX The minimum x-coordinate
* @param worldMaxX The maximum x-coordinate
*/
private void updateX(AffineTransform worldToScreen,
double worldMinX, double worldMaxX)
{
Paint labelPaintX = labelPainterX.getPaint();
double worldTickDistanceX = fixedWorldTickDistanceX;
if (!Double.isFinite(worldTickDistanceX))
{
worldTickDistanceX = Axes.computeWorldTickDistanceX(
worldToScreen, minScreenTickDistanceX);
labelFormatX = Axes.formatStringFor(worldTickDistanceX);
if (labelPaintX != null && adjustForStringLengths)
{
double adjusted = computeAdjustedWorldTickDistanceX(
worldToScreen, worldMinX, worldMaxX);
if (adjusted > 0)
{
worldTickDistanceX = adjusted;
}
}
}
worldTicksX = Axes.computeWorldTicks(
worldMinX, worldMaxX, worldTickDistanceX);
if (labelPaintX != null)
{
labelFormatX = Axes.formatStringFor(worldTickDistanceX);
}
if (adjustLabelAnchorX)
{
Point2D anchor = labelPainterX.getLabelAnchor();
if (tickOrientationPositiveX)
{
if (worldToScreen.getScaleY() > 0)
{
labelPainterX.setLabelAnchor(anchor.getX(), 0.0);
}
else
{
labelPainterX.setLabelAnchor(anchor.getX(), 1.0);
}
}
else
{
if (worldToScreen.getScaleY() > 0)
{
labelPainterX.setLabelAnchor(anchor.getX(), 1.0);
}
else
{
labelPainterX.setLabelAnchor(anchor.getX(), 0.0);
}
}
}
}
/**
* Compute the distance between ticks on the x-axis, in world coordinates,
* that is necessary so that the labels can be painted with the current
* label painter
*
* @param worldToScreen The world to screen transform
* @param worldMinX The minimum x-coordinate
* @param worldMaxX The maximum x-coordinate
* @return The adjusted distance, or 0.0 if no matching distance
* could be computed (which should never be the case)
*/
private double computeAdjustedWorldTickDistanceX(
AffineTransform worldToScreen, double worldMinX, double worldMaxX)
{
String labelString0 = createLabelStringX(worldMinX);
String labelString1 = createLabelStringX(worldMaxX);
String labelString;
if (labelString0.length() > labelString1.length())
{
labelString = " " + labelString0 + " ";
}
else
{
labelString = " " + labelString1 + " ";
}
labelPainterX.setLabelLocation(0, 0);
Shape labelBounds = labelPainterX.computeLabelBounds(
worldToScreen, labelString);
// Note: This is not very efficient. The solution could probably be
// computed analytically. But considering the possible label texts,
// fonts, and anchor settings, this is the most pragmatic solution:
int max = labelBounds.getBounds().width;
for (int i = 1; i < max; i++)
{
double candidate =
Axes.computeWorldTickDistanceX(worldToScreen, i);
labelPainterX.setLabelLocation(0, 0);
Shape bounds0 = labelPainterX.computeLabelBounds(
worldToScreen, labelString);
labelPainterX.setLabelLocation(candidate, 0);
Shape bounds1 = labelPainterX.computeLabelBounds(
worldToScreen, labelString);
Area a0 = new Area(bounds0);
Area a1 = new Area(bounds1);
a0.intersect(a1);
if (a0.isEmpty())
{
return candidate;
}
}
return 0.0;
}
/**
* Update the data that is used internally for painting the y-axis,
* namely the {@link #worldTicksY} and the {@link #labelFormatY}
*
* @param worldToScreen The world-to-screen transform
* @param worldMinY The minimum y-coordinate
* @param worldMaxY The maximum y-coordinate
*/
private void updateY(AffineTransform worldToScreen,
double worldMinY, double worldMaxY)
{
Paint labelPaintY = labelPainterY.getPaint();
double worldTickDistanceY = fixedWorldTickDistanceY;
if (!Double.isFinite(worldTickDistanceY))
{
worldTickDistanceY =
Axes.computeWorldTickDistanceY(
worldToScreen, minScreenTickDistanceY);
}
worldTicksY = Axes.computeWorldTicks(
worldMinY, worldMaxY, worldTickDistanceY);
if (labelPaintY != null)
{
labelFormatY = Axes.formatStringFor(worldTickDistanceY);
}
if (adjustLabelAnchorY)
{
Point2D anchor = labelPainterY.getLabelAnchor();
if (tickOrientationPositiveY)
{
if (worldToScreen.getScaleX() > 0)
{
labelPainterY.setLabelAnchor(0.0, anchor.getY());
}
else
{
labelPainterY.setLabelAnchor(1.0, anchor.getY());
}
}
else
{
if (worldToScreen.getScaleX() > 0)
{
labelPainterY.setLabelAnchor(1.0, anchor.getY());
}
else
{
labelPainterY.setLabelAnchor(0.0, anchor.getY());
}
}
}
}
@Override
public final void paint(Graphics2D g, AffineTransform worldToScreen,
double w, double h)
{
Rectangle2D screenBounds = new Rectangle2D.Double(0, 0, w, h);
AffineTransform screenToWorld =
AffineTransforms.invert(worldToScreen, null);
Rectangles.computeBounds(
screenToWorld, screenBounds, worldBounds);
updateX(worldToScreen, worldBounds.getMinX(), worldBounds.getMaxX());
updateY(worldToScreen, worldBounds.getMinY(), worldBounds.getMaxY());
g.setStroke(stroke);
if (gridColorX != null)
{
g.setColor(gridColorX);
paintInternalGridX(g, worldToScreen);
}
if (gridColorY != null)
{
g.setColor(gridColorY);
paintInternalGridY(g, worldToScreen);
}
if (axisColorX != null)
{
g.setColor(axisColorX);
paintAxisX(g, worldToScreen);
}
if (axisColorY != null)
{
g.setColor(axisColorY);
paintAxisY(g, worldToScreen);
}
}
/**
* Paint the x-axis.
*
* @param g The graphics to paint to
* @param worldToScreen The world-to-screen transform
*/
private void paintAxisX(
Graphics2D g, AffineTransform worldToScreen)
{
if (supplierScreenMinX == null ||
supplierScreenMaxX == null ||
supplierScreenY == null)
{
paintAxisX(g, worldToScreen,
getValue(worldMinAxisX, worldBounds.getMinX()),
getValue(worldMaxAxisX, worldBounds.getMaxX()),
worldYofX);
}
else
{
int screenMinX = supplierScreenMinX.getAsInt();
int screenMaxX = supplierScreenMaxX.getAsInt();
int screenY = supplierScreenY.getAsInt();
Point2D pxMin = new Point2D.Double(screenMinX, screenY);
Point2D pxMax = new Point2D.Double(screenMaxX, screenY);
Points.inverseTransform(worldToScreen, pxMin, pxMin);
Points.inverseTransform(worldToScreen, pxMax, pxMax);
updateX(worldToScreen, pxMin.getX(), pxMax.getX());
paintAxisX(g, worldToScreen,
pxMin.getX(), pxMax.getX(), pxMin.getY());
}
}
/**
* Paint the y-axis.
*
* @param g The graphics to paint to
* @param worldToScreen The world-to-screen transform
*/
protected void paintAxisY(
Graphics2D g, AffineTransform worldToScreen)
{
if (supplierScreenMinY == null ||
supplierScreenMaxY == null ||
supplierScreenX == null)
{
paintAxisY(g, worldToScreen,
getValue(worldMinAxisY, worldBounds.getMinY()),
getValue(worldMaxAxisY, worldBounds.getMaxY()),
worldXofY);
}
else
{
int screenMinY = supplierScreenMinY.getAsInt();
int screenMaxY = supplierScreenMaxY.getAsInt();
int screenX = supplierScreenX.getAsInt();
Point2D pyMin = new Point2D.Double(screenX, screenMinY);
Point2D pyMax = new Point2D.Double(screenX, screenMaxY);
Points.inverseTransform(worldToScreen, pyMin, pyMin);
Points.inverseTransform(worldToScreen, pyMax, pyMax);
paintAxisY(g, worldToScreen,
pyMin.getY(), pyMax.getY(), pyMin.getX());
}
}
/**
* Paint the x-axis after it has been made sure that the
* {@link #worldTicksX} and {@link #labelFormatX} are up to date
*
* @param g The graphics to paint to
* @param worldToScreen The world-to-screen transform
* @param worldMinX The minimum world coordinate of the axis
* @param worldMaxX The maximum world coordinate of the axis
* @param worldY The world coordinate at which the axis should be painted
*/
private void paintAxisX(
Graphics2D g, AffineTransform worldToScreen,
double worldMinX, double worldMaxX, double worldY)
{
TEMP_LINE.setLine(worldMinX,worldY,worldMaxX,worldY);
Lines.transform(worldToScreen, TEMP_LINE, TEMP_LINE);
g.draw(TEMP_LINE);
for (int i=0; i= worldMinX && worldTickX <= worldMaxX)
{
paintTickX(g, worldToScreen, worldTickX, worldY);
}
}
}
/**
* Paint the y-axis after it has been made sure that the
* {@link #worldTicksY} and {@link #labelFormatY} are up to date
*
* @param g The graphics to paint to
* @param worldToScreen The world-to-screen transform
* @param worldMinY The minimum world coordinate of the axis
* @param worldMaxY The maximum world coordinate of the axis
* @param worldX The world coordinate at which the axis should be painted
*/
private void paintAxisY(
Graphics2D g, AffineTransform worldToScreen,
double worldMinY, double worldMaxY, double worldX)
{
TEMP_LINE.setLine(worldX,worldMinY,worldX,worldMaxY);
Lines.transform(worldToScreen, TEMP_LINE, TEMP_LINE);
g.draw(TEMP_LINE);
for (int i=0; i= worldMinY && worldTickY <= worldMaxY)
{
paintTickY(g, worldToScreen, worldX, worldTickY);
}
}
}
/**
* Paint the coordinate grid in the background, after it has been
* made sure that the data for painting the grid and axes is up
* to date
*
* @param g The graphics to paint to
* @param worldToScreen The world-to-screen transform
*/
private void paintInternalGridX(Graphics2D g, AffineTransform worldToScreen)
{
double worldMinX = getValue(worldMinAxisX, worldBounds.getMinX());
double worldMaxX = getValue(worldMaxAxisX, worldBounds.getMaxX());
double worldMinY = getValue(worldMinAxisY, worldBounds.getMinY());
double worldMaxY = getValue(worldMaxAxisY, worldBounds.getMaxY());
for (int i=0; i= worldMinX && worldTickX <= worldMaxX)
{
paintGridLineX(g, worldToScreen, worldTickX,
worldMinY, worldMaxY);
}
}
}
/**
* Paint the coordinate grid in the background, after it has been
* made sure that the data for painting the grid and axes is up
* to date
*
* @param g The graphics to paint to
* @param worldToScreen The world-to-screen transform
*/
private void paintInternalGridY(Graphics2D g, AffineTransform worldToScreen)
{
double worldMinX = getValue(worldMinAxisX, worldBounds.getMinX());
double worldMaxX = getValue(worldMaxAxisX, worldBounds.getMaxX());
double worldMinY = getValue(worldMinAxisY, worldBounds.getMinY());
double worldMaxY = getValue(worldMaxAxisY, worldBounds.getMaxY());
for (int i=0; i= worldMinY && worldTickY <= worldMaxY)
{
paintGridLineY(g, worldToScreen, worldTickY,
worldMinX, worldMaxX);
}
}
}
/**
* Paints a single grid line at the given x-coordinate
*
* @param g The graphics context
* @param worldToScreen The world-to-screen transform
* @param worldX The world coordinate of the grid line
* @param worldMinY The minimum y-coordinate
* @param worldMaxY The maximum y-coordinate
*/
private void paintGridLineX(Graphics2D g, AffineTransform worldToScreen,
double worldX, double worldMinY, double worldMaxY)
{
TEMP_LINE.setLine(worldX, worldMinY, worldX, worldMaxY);
Lines.transform(worldToScreen, TEMP_LINE, TEMP_LINE);
g.draw(TEMP_LINE);
}
/**
* Paints a single grid line at the given y-coordinate
*
* @param g The graphics context
* @param worldToScreen The world-to-screen transform
* @param worldY The world coordinate of the tick
* @param worldMinX The minimum x-coordinate
* @param worldMaxX The maximum x-coordinate
*/
private void paintGridLineY(Graphics2D g, AffineTransform worldToScreen,
double worldY, double worldMinX, double worldMaxX)
{
TEMP_LINE.setLine(worldMinX, worldY, worldMaxX, worldY);
Lines.transform(worldToScreen, TEMP_LINE, TEMP_LINE);
g.draw(TEMP_LINE);
}
/**
* Paints a single tick of the x-axis
*
* @param g The graphics context
* @param worldToScreen The world-to-screen transform
* @param worldX The x-world coordinate of the tick
* @param worldY The y-world coordinate of the tick
*/
private void paintTickX(Graphics2D g, AffineTransform worldToScreen,
double worldX, double worldY)
{
TEMP_LINE.setLine(worldX, worldY, worldX, worldY+1);
Lines.transform(worldToScreen, TEMP_LINE, TEMP_LINE);
double length = -tickSizeScreen;
if (tickOrientationPositiveX)
{
length = -length;
}
Lines.scaleToLength(length, TEMP_LINE, TEMP_LINE);
g.draw(TEMP_LINE);
Paint labelPaintX = labelPainterX.getPaint();
if (labelPaintX != null )
{
TEMP_POINT.setLocation(TEMP_LINE.getX2(), TEMP_LINE.getY2());
Points.inverseTransform(worldToScreen, TEMP_POINT, TEMP_POINT);
paintLabelX(g, worldToScreen, TEMP_POINT.getX(), TEMP_POINT.getY());
}
}
/**
* Paints a single label of the x-axis
*
* @param g The graphics context
* @param worldToScreen The world-to-screen transform
* @param worldX The x-world coordinate of the label
* @param worldY The y-world coordinate of the label
*/
private void paintLabelX(Graphics2D g, AffineTransform worldToScreen,
double worldX, double worldY)
{
String string = createLabelStringX(worldX);
labelPainterX.setLabelLocation(worldX, worldY);
labelPainterX.paint(g, worldToScreen, 0, 0, string);
}
/**
* Create the string for a label at the x-axis at the given position
*
* @param worldX The world coordinate
* @return The string
*/
private String createLabelStringX(double worldX)
{
if (labelFormatterX == null)
{
return String.format(labelFormatX, worldX);
}
return labelFormatterX.apply(worldX);
}
/**
* Paints a single tick of the y-axis
*
* @param g The graphics context
* @param worldToScreen The world-to-screen transform
* @param worldX The x-world coordinate of the tick
* @param worldY The y-world coordinate of the tick
*/
private void paintTickY(Graphics2D g, AffineTransform worldToScreen,
double worldX, double worldY)
{
TEMP_LINE.setLine(worldX, worldY, worldX+1.0, worldY);
Lines.transform(worldToScreen, TEMP_LINE, TEMP_LINE);
double length = -tickSizeScreen;
if (tickOrientationPositiveY)
{
length = -length;
}
Lines.scaleToLength(length, TEMP_LINE, TEMP_LINE);
g.draw(TEMP_LINE);
Paint labelPaintY = labelPainterY.getPaint();
if (labelPaintY != null )
{
TEMP_POINT.setLocation(TEMP_LINE.getX2(), TEMP_LINE.getY2());
Points.inverseTransform(worldToScreen, TEMP_POINT, TEMP_POINT);
paintLabelY(g, worldToScreen, TEMP_POINT.getX(), TEMP_POINT.getY());
}
}
/**
* Paints a single label of the y-axis
*
* @param g The graphics context
* @param worldToScreen The world-to-screen transform
* @param worldX The x-world coordinate of the label
* @param worldY The y-world coordinate of the label
*/
private void paintLabelY(Graphics2D g, AffineTransform worldToScreen,
double worldX, double worldY)
{
String string = createLabelStringY(worldY);
labelPainterY.setLabelLocation(worldX, worldY);
labelPainterY.paint(g, worldToScreen, 0, 0, string);
}
/**
* Create the string for a label at the y-axis at the given position
*
* @param worldY The world coordinate
* @return The string
*/
private String createLabelStringY(double worldY)
{
if (labelFormatterY == null)
{
return String.format(labelFormatY, worldY);
}
return labelFormatterY.apply(worldY);
}
/**
* Returns the given optional value if it is not Double.NaN
,
* and the given value otherwise
*
* @param optionalValue The optional value
* @param value The value
* @return The respective value
*/
private static double getValue(double optionalValue, double value)
{
if (!Double.isNaN(optionalValue))
{
return optionalValue;
}
return value;
}
}