The source code I posted above had an error. Therefore below a corrected version.
The corrected results still yield a travel time less than half of the reported one and a temperature of more than 5000K.
import static java.awt.BasicStroke.CAP_ROUND;
import static java.awt.BasicStroke.JOIN_ROUND;
import static java.awt.image.BufferedImage.TYPE_4BYTE_ABGR;
import static java.lang.Math.PI;
import static java.lang.Math.abs;
import static java.lang.Math.atan2;
import static java.lang.Math.cos;
import static java.lang.Math.exp;
import static java.lang.Math.sin;
import static java.lang.Math.sqrt;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Desktop;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import javax.imageio.ImageIO;
/**
* Simulation of the re-entry of Yuri Gagarin.
*
* @see https://en.wikipedia.org/wiki/Vostok_1
* @see https://de.wikipedia.org/wiki/Fall_mit_Luftwiderstand#Fall_mit_Luftwiderstand:_Newton-Reibung
* @see https://de.wikipedia.org/wiki/Methode_der_kleinen_Schritte
* @see https://de.wikipedia.org/wiki/Barometrische_Höhenformel
* @see https://de.wikipedia.org/wiki/Stefan-Boltzmann-Gesetz
* @see http://www.astronautix.com/v/vostok1.html
* @see http://www.spacefacts.de/mission/english/vostok-1.htm
* @see http://heiwaco.tripod.com/moontravelw1.htm#REE
*
* @see http://www.iaassconference2013.space-safety.org/wp-content/uploads/sites/32/2013/06/1600_Feistel.pdf
* @see http://articles.adsabs.harvard.edu/cgi-bin/nph-iarticle_query?2013ESASP.715E..75F&data_type=PDF_HIGH&whole_paper=YES&type=PRINTER&filetype=.pdf
*
* @author Struthio
* @since October 2018
*
*/
public class GagarinsReentry
{
/** Whether to use ATV-3 and not Gagarin. */
private static boolean ATV3 = false;
/** Title of output. */
private static final String title = "Re-entry " + (ATV3 ? "ATV-3" : "Yuri Gagarin");
@Doc("Drag coefficient of the shape of the capsule.")
private static final double cwShape = ATV3 ? 1.0 : 0.47;
@Doc("Length of cylidrical ATV-3 capsule in m.")
private static final double l = 10.27;
@Doc("Diameter of the spherical or cylindrical capsule in m.")
private static final double d = ATV3 ? 4.48 : 2.3;
@Doc("Weight of the capsule in kg.")
private static final double m = ATV3 ? 10470 : 2400;
@Doc("Sectional area of the capsule in m^2.")
private static final double Asectional = PI * sqr(0.5 * d);
@Doc("Surface area of the capsule in m^2.")
private static final double Asurface = ATV3 ? 2 * Asectional + PI * d * l : 4 * Asectional;
@Doc("Gravitational acceleration at sea level in m/s^2.")
private static final double g = 9.81;
@Doc("Radius of the earth in m.")
private static final double r = 6370000;
@Doc("Mass density of air at sea level in kg/m^3.")
private static final double rho0 = 1.293;
@Doc("Speed of sound in m/s.")
private static final double cAir = 331.5;
@Doc("Calculation time interval in s.")
private static final double dt = 0.01;
@Doc("Current time in s.")
private double t = 0;
@Doc("Horizontal distance m.")
private double x = 0;
@Doc("Height above sea level in m.")
private double y = ATV3 ? 120000d : 130000d;
@Doc("Initial flight path angle in rad.")
private static final double argV = (ATV3 ? -1.663 : -2.0) * PI / 180d;
@Doc("Initial velocity.")
private static final double absV = ATV3 ? 7593.0 : 8000;
@Doc("Horizontal velocity in m/s.")
private double vx = absV * cos(argV);
@Doc("Vertical velocity in m/s.")
private double vy = absV * sin(argV);
@Doc("Horizontal acceleration in m/s.")
private double ax = 0;
@Doc("Vertical acceleration in m/s.")
private double ay = 0;
@Doc("Mass density of air at current height above sea level in kg/m^3.")
private double rho = 0;
@Doc("Drag coefficient.")
private double cw = 0;
@Doc("Braking force in N")
private double F = 0;
@Doc("Braking power in W")
private double P = 0;
@Doc("Maximum braking force in N")
private double Pmax = 0;
@Doc("Maximum acceleration in m/s^2")
private double aMax = 0;
private final DragCoefficient dragCoefficient = new DragCoefficient();
private final List<List<Double>> signals = new ArrayList<>();
{
signals.add(new ArrayList<>());
signals.add(new ArrayList<>());
signals.add(new ArrayList<>());
signals.add(new ArrayList<>());
signals.add(new ArrayList<>());
signals.add(new ArrayList<>());
signals.add(new ArrayList<>());
}
/**
* Program entry point.
* <p>
* prints text to stdout and opens a *.png-image in the systems image viewer.
*
* @param args
* arguments are ignored.
*/
public static void main(final String... args)
{
try
{
final GagarinsReentry reentry = new GagarinsReentry();
reentry.run();
final Plotter.Config config = new Plotter.Config(title, "", "y, Cw, T, \u03C1, y', y\"",
reentry.time());
new Plotter(config, reentry.signals).openRasterImageInEditor();
}
catch (final Throwable t)
{
t.printStackTrace();
}
}
public GagarinsReentry()
{
}
private void run()
{
final double Ekin = 0.5 * m * (sqr(vx) + sqr(vy));
step(true);
out("%s\n", title);
out("\n");
printDoc();
out("\n");
out(" t[s] ax ay rho cw F vx vy x h P \n");
out("------ ------- ------- -------- -------- ----------- ------------ ------------ -------- -------- --------\n");
print();
int noOfSteps;
for (noOfSteps = 0; y > 0; noOfSteps++)
{
step(false);
if (noOfSteps % 100 == 0)
{
print();
}
}
if (noOfSteps % 10 != 0)
{
print();
}
out("noOfSteps: %d\n", noOfSteps);
out("%s %.1fg %.2fMW %.0fK\n", time(), aMax / g, 1E-6 * Pmax, temperatureInK(Pmax));
out("\n");
final double P = Ekin / t;
out("mean: %.2fMW %.0fK\n", 1E-6 * P, temperatureInK(P));
out("total energy: %.2fGJ\n", 1E-9 * Ekin);
}
private void printDoc()
{
for (final Field field : GagarinsReentry.class.getDeclaredFields())
{
final Doc doc = field.getAnnotation(Doc.class);
if (doc != null)
{
final boolean accessible = field.isAccessible();
try
{
field.setAccessible(true);
final double value = (Double) field.get(this);
String sValue;
if (value == 0)
{
sValue = String.format(Locale.US, "%12s", "0");
}
else if (abs(value) < 0.001 || abs(value) > 1E8)
{
sValue = String.format(Locale.US, "%12.3E", value);
}
else
{
sValue = String.format(Locale.US, "%12.3f", value);
}
out("%12s = %s %s\n", field.getName(), sValue, doc.value());
}
catch (final Exception e)
{
throw new IllegalStateException("unreached", e);
}
finally
{
field.setAccessible(accessible);
}
}
}
}
private void print()
{
out("%s %7.2f %7.2f %8.2E %8.2f %10.2fN %8.2fkm/h %8.2fkm/h %7.0fm %7.0fm %6.1fMW\n", time(),
ax, ay, rho, cw, F, 3.6 * vx, 3.6 * vy, x, y, 1E-6 * P);
}
private String time()
{
final int s = (int) (t + 0.5);
return String.format("%02d'%02d\"", s / 60, s % 60);
}
private void step(final boolean initialize)
{
rho = rho0 * exp(-y / 8400);
final double absV = sqrt(sqr(vx) + sqr(vy));
final double argV = atan2(vy, vx);
F = airFrictionForceInN(rho, y, absV);
final double aF = F / m; // air friction acceleration
final double aG = -g * (r / (r + y)); // gravitational acceleration
final double aC = sqr(vx) / (r + y); // centrifugal acceleration
ax = aF * cos(argV);
ay = aF * sin(argV) + aG + aC;
if (!initialize)
{
t += dt;
vx += ax * dt;
vy += ay * dt;
x += vx * dt;
y += vy * dt;
}
final double a = sqrt(ax * ax + ay * ay);
if (aMax < a)
{
aMax = a;
}
P = m * absV * a;
if (Pmax < P)
{
Pmax = P;
}
signals.get(0).add(y);
// signals.get(1).add(vy);
// signals.get(2).add(ay);
signals.get(1).add(sqrt(sqr(vx) + sqr(vy)));
signals.get(2).add(sqrt(sqr(ax) + sqr(ay)));
signals.get(3).add(cw);
signals.get(4).add(rho);
signals.get(5).add(temperatureInK(P));
// signals.get(6).add(x);
}
/**
* Returns the temperature in Kelvin of the black sphere emitting the given power.
*/
private static double temperatureInK(final double powerInWatts)
{
// https://de.wikipedia.org/wiki/Stefan-Boltzmann-Gesetz
final double sigma = 5.670367E-8;
return sqrt(sqrt(powerInWatts / (sigma * Asurface)));
}
/**
* Returns the air friction force is Newtons.
*
* @param rho
* The mass desity of the air in kg/m^3.
* @param h
* The height above sea level.
* @param v
* The speed in m/s.
* @return the air friction force is Newtons.
* @throws IllegalArgumentException
* If the height above sea level exceeds 150km.
* @see https://de.wikipedia.org/wiki/Str%C3%B6mungswiderstandskoeffizient
*/
private double airFrictionForceInN(final double rho, final double h, final double v)
throws IllegalArgumentException
{
if (h > 150000)
{
throw new IllegalArgumentException("formula valid up to 150km height only");
}
cw = cwShape / 0.47 * dragCoefficient.getValue(abs(v / cAir));
return -0.5 * rho * cw * Asectional * sqr(v);
}
private static double sqr(final double x)
{
return x * x;
}
private static void out(final String format, final Object... args)
{
System.out.format(Locale.US, format, args);
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
@interface Doc {
String value();
}
/**
* A function defined by a polygon.
*/
class Function
{
private final double[][] xyValues;
public Function(final double[][] xyValues)
{
this.xyValues = xyValues;
}
/**
* Returns {@code f(x)}.
*/
public double getValue(final double x)
{
double[] point = xyValues[0];
if (x <= point[0])
{
return point[1];
}
final int N = xyValues.length;
point = xyValues[N - 1];
if (x >= point[0])
{
return point[1];
}
for (int k = 1; k < N; k++)
{
point = xyValues[k];
if (x <= point[0])
{
final double[] prev = xyValues[k - 1];
final double dx = point[0] - prev[0];
final double dy = point[1] - prev[1];
return prev[1] + dy * (x - prev[0]) / dx;
}
}
throw new IllegalStateException("unreached");
}
}
/**
* The drag coefficient as a function of the mach number.
*
* Values generated semi-automatically from the image at
* {@code https://de.wikipedia.org/wiki/Str%C3%B6mungswiderstandskoeffizient}.
*/
class DragCoefficient extends Function
{
public DragCoefficient()
{
super(new double[][] { //
{ 0.00000, 0.46595 }, { 0.05928, 0.46965 }, { 0.11855, 0.47519 }, { 0.17783, 0.47889 },
{ 0.23710, 0.48259 }, { 0.29638, 0.48629 }, { 0.35565, 0.49183 }, { 0.41493, 0.49738 },
{ 0.47420, 0.50108 }, { 0.53348, 0.51032 }, { 0.59276, 0.52142 }, { 0.65203, 0.54176 },
{ 0.71131, 0.57319 }, { 0.77058, 0.61941 }, { 0.82986, 0.68043 }, { 0.88913, 0.74700 },
{ 0.94841, 0.80986 }, { 1.00768, 0.86533 }, { 1.06696, 0.90971 }, { 1.12623, 0.94299 },
{ 1.18551, 0.96518 }, { 1.24479, 0.97997 }, { 1.30406, 0.98737 }, { 1.36334, 0.99476 },
{ 1.42261, 0.99846 }, { 1.48189, 1.00031 }, { 1.54116, 1.00216 }, { 1.60044, 1.00216 },
{ 1.65971, 1.00216 }, { 1.71899, 1.00031 }, { 1.77827, 1.00031 }, { 1.83754, 0.99846 },
{ 1.89682, 0.99661 }, { 1.95609, 0.99476 }, { 2.01537, 0.99291 }, { 2.07464, 0.99106 },
{ 2.13392, 0.98921 }, { 2.19319, 0.98552 }, { 2.25247, 0.98367 }, { 2.31175, 0.98182 },
{ 2.37102, 0.97812 }, { 2.43030, 0.97627 }, { 2.48957, 0.97257 }, { 2.54885, 0.97072 },
{ 2.60812, 0.96703 }, { 2.66740, 0.96518 }, { 2.72667, 0.96148 }, { 2.78595, 0.95963 },
{ 2.84523, 0.95778 }, { 2.90450, 0.95593 }, { 2.96378, 0.95408 }, { 3.02305, 0.95223 },
{ 3.08233, 0.95039 }, { 3.14160, 0.94854 }, { 3.20088, 0.94669 }, { 3.26015, 0.94484 },
{ 3.31943, 0.94299 }, { 3.37870, 0.94114 }, { 3.43798, 0.93929 }, { 3.49726, 0.93744 },
{ 3.55653, 0.93559 }, { 3.61581, 0.93559 }, { 3.67508, 0.93374 }, { 3.73436, 0.93190 },
{ 3.79363, 0.93005 }, { 3.85291, 0.93005 }, { 3.91218, 0.92820 }, { 3.97146, 0.92635 },
{ 4.03074, 0.92635 }, { 4.09001, 0.92450 }, { 4.14929, 0.92265 }, { 4.20856, 0.92265 },
{ 4.26784, 0.92080 }, { 4.32711, 0.92080 }, { 4.38639, 0.91895 }, { 4.44566, 0.91895 },
{ 4.50494, 0.91710 }, { 4.56422, 0.91710 }, { 4.62349, 0.91525 }, { 4.68277, 0.91525 },
{ 4.74204, 0.91341 }, { 4.80132, 0.91341 }, { 4.86059, 0.91341 }, { 4.91987, 0.91156 },
{ 4.97914, 0.91156 }, { 5.03842, 0.91156 }, { 5.09769, 0.90971 }, { 5.15697, 0.90971 },
{ 5.21625, 0.90971 }, { 5.27552, 0.90971 }, { 5.33480, 0.90786 }, { 5.39407, 0.90786 },
{ 5.45335, 0.90786 }, { 5.51262, 0.90786 }, { 5.57190, 0.90786 }, { 5.63117, 0.90786 },
{ 5.69045, 0.90786 }, { 5.74973, 0.90786 }, { 5.80900, 0.90786 }, { 5.86828, 0.90786 },
{ 5.92755, 0.90786 }, { 5.98683, 0.90601 }, });
}
}
/**
* Plotter to show an image in the systems image viewer.
*
* @author Struthio
* @since October 2018
*/
class Plotter
{
private final int width;
private final int height;
private final Config config;
private final double[][] signals;
private final double[] xValues;
private final double samplesPerDiv;
public Plotter(final Config config, final List<List<Double>> signals)
{
this.config = config;
this.signals = new double[signals.size()][];
this.xValues = null;
int maxLength = 0;
for (int n = 0; n < signals.size(); n++)
{
final List<Double> signal = signals.get(n);
final int N = signal.size();
if (maxLength < N)
{
maxLength = N;
}
this.signals[n] = new double[N];
for (int k = 0; k < N; k++)
{
this.signals[n][k] = signal.get(k);
}
normalize(this.signals[n]);
}
this.width = (2 * config.noOfMarginDivsX + config.noOfDivsX) * config.divWidth;
this.height = (2 * config.noOfMarginDivsY + config.noOfDivsY) * config.divHeight;
this.samplesPerDiv = maxLength / (double) config.noOfDivsX;
}
public void openRasterImageInEditor()
{
try
{
final File file = File.createTempFile(Plotter.class.getName(), ".png");
createRasterImageFile(file);
Desktop.getDesktop().edit(file);
}
catch (final IOException e)
{
throw new IllegalStateException("failed to create temporary image file", e);
}
catch (final HeadlessException e)
{
throw new IllegalStateException("platform not supported", e);
}
catch (final UnsupportedOperationException e)
{
throw new IllegalStateException("platform not supported", e);
}
}
private void createRasterImageFile(final File file) throws IOException
{
final RenderedImage image = createRasterImage();
ImageIO.write(image, "PNG", file);
}
private BufferedImage createRasterImage()
{
final BufferedImage image = new BufferedImage(width, height, TYPE_4BYTE_ABGR);
final Graphics2D graphics2D = image.createGraphics();
try
{
paint(graphics2D);
}
finally
{
graphics2D.dispose();
}
return image;
}
private void paint(final Graphics2D graphics2D)
{
graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
paintBackground(graphics2D);
paintGrid(graphics2D);
paintCurves(graphics2D);
}
private void paintBackground(final Graphics2D graphics2D)
{
graphics2D.setColor(config.bgColor);
graphics2D.fill(new Rectangle2D.Double(0d, 0d, width, height));
}
private void paintGrid(final Graphics2D graphics2D)
{
final String spd = String.valueOf((int) samplesPerDiv);
final String abscissaeText = String.format("%staps/div, %dtaps", spd, signals[0].length);
final String ordinatesText = String.format("%s/div", String.valueOf(2.0 / config.noOfDivsY));
final GridPlotter plotter = new GridPlotter(config, abscissaeText, ordinatesText);
plotter.plot(graphics2D);
}
private void paintCurves(final Graphics2D graphics2D)
{
final float penWidth = config.signalPenWidth * config.divWidth / 32f;
graphics2D.setStroke(new BasicStroke(penWidth, BasicStroke.CAP_ROUND, BasicStroke.CAP_ROUND));
final double x0 = config.noOfMarginDivsX * config.divWidth;
final double y0 = (config.noOfMarginDivsY + 0.5f * config.noOfDivsY) * config.divHeight;
for (int kSignal = 0; kSignal < signals.length; kSignal++)
{
plotCurve(graphics2D, kSignal, x0, y0);
}
}
private void plotCurve(final Graphics2D graphics2D, final int kSignal, final double x0,
final double y0)
{
final boolean samples = samplesPerDiv <= 10;
final Shape originalClip = graphics2D.getClip();
try
{
final Rectangle2D signalClip;
{
final double x = config.noOfMarginDivsX * config.divWidth - 0.25 * config.divWidth;
final double y = config.noOfMarginDivsY * config.divHeight - 0.25 * config.divHeight;
final double w = config.noOfDivsX * config.divWidth + 0.50 * config.divWidth;
final double h = config.noOfDivsY * config.divHeight + 0.50 * config.divHeight;
signalClip = new Rectangle2D.Double(x - 2, y - 2, w + 4, h + 4);
}
graphics2D.clip(signalClip);
plotSamplesOrCurve(graphics2D, kSignal, samples, x0, y0, samplesPerDiv, signalClip,
originalClip);
}
finally
{
graphics2D.setClip(originalClip);
}
}
private void plotSamplesOrCurve(final Graphics2D graphics2D, final int kSignal,
final boolean samples, final double x0, final double y0, final double samplesPerDiv,
final Shape signalClip, final Shape originalClip)
{
final double rx = 0.12 * config.divWidth;
final double ry = 0.12 * config.divHeight;
graphics2D.setColor(config.signalColors[kSignal % config.signalColors.length]);
final double scaleX = 0.5 * (config.noOfDivsX * config.divHeight);
final double scaleY = 0.5 * (config.noOfDivsY * config.divHeight);
final double[] signal = signals[kSignal];
if (signal.length < 2)
{
return;
}
double xPrev = 0;
double yPrev = 0;
for (int n = 0; n <= signal.length; n++)
{
final int k = n % signal.length;
final double vx = xValues != null ? scaleX * xValues[k]
: k * config.divWidth / samplesPerDiv;
final double vy = scaleY * signal[k];
final double x = x0 + vx;
final double y = y0 - vy;
if (samples)
{
if (0 <= n && n < signal.length)
{
graphics2D.draw(new Line2D.Double(x, y0, x, y));
if (signalClip.contains(x, y))
{
graphics2D.setClip(originalClip);
graphics2D.draw(new Ellipse2D.Double(x - 0.5 * rx, y - 0.5 * ry, rx, ry));
graphics2D.fill(new Ellipse2D.Double(x - 0.5 * rx, y - 0.5 * ry, rx, ry));
graphics2D.clip(signalClip);
}
}
}
else
{
graphics2D.clip(new Rectangle2D.Double(0, 0, width, height));
if (0 < n && n < signal.length)
{
graphics2D.draw(new Line2D.Double(xPrev, yPrev, x, y));
}
}
xPrev = x;
yPrev = y;
}
}
private static void normalize(final double[] array)
{
final int N = array.length;
if (N > 0)
{
double max = 0;
for (final double value : array)
{
final double c = abs(value);
if (max < c)
{
max = c;
}
}
if (max > 0 && max != 1)
{
final double scale = 1d / max;
for (int k = 0; k < N; k++)
{
array[k] *= scale;
}
}
}
}
private static final class GridPlotter
{
private final Config config;
private final String abscissaeText;
private final String ordinatesText;
public GridPlotter(final Config config, final String abscissaeText,
final String ordinatesText)
{
this.config = config;
this.abscissaeText = abscissaeText;
this.ordinatesText = ordinatesText;
}
public void plot(final Graphics2D graphics2D)
{
graphics2D.setColor(config.gridColor);
final double x0 = config.noOfMarginDivsX * config.divWidth;
final double x1 = (config.noOfMarginDivsX + config.noOfDivsX) * config.divWidth;
final double y0 = config.noOfMarginDivsY * config.divHeight;
final double y1 = (config.noOfMarginDivsY + config.noOfDivsY) * config.divHeight;
plot(graphics2D, x0, y0, x1, y1, abscissaeText, ordinatesText);
}
private void plot(final Graphics2D graphics2D, final double x0, final double y0,
final double x1, final double y1, final String abscissaeText,
final String ordinatesText)
{
final Composite composite = graphics2D.getComposite();
try
{
final float penWidth = config.framePenWidth * config.divWidth / 32f;
graphics2D.setStroke(new BasicStroke(penWidth, CAP_ROUND, JOIN_ROUND));
final double xm = (x0 + x1) / 2;
final double ym = (y0 + y1) / 2;
graphics2D.draw(new Line2D.Double(x0, y0, x1, y0));
graphics2D.draw(new Line2D.Double(x0, y1, x1, y1));
graphics2D.draw(new Line2D.Double(x0, y0, x0, y1));
graphics2D.draw(new Line2D.Double(x1, y0, x1, y1));
graphics2D.draw(new Line2D.Double(x0, ym, x1, ym));
graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
config.gridOpacity));
for (double x = x0; x <= x1; x += config.divWidth)
{
graphics2D.draw(new Line2D.Double(x, y0, x, y1));
}
for (double y = y0; y <= y1; y += config.divHeight)
{
graphics2D.draw(new Line2D.Double(x0, y, x1, y));
}
final Font font = config.font.deriveFont(0.40f * config.divHeight);
final FontRenderContext frc = graphics2D.getFontRenderContext();
graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
config.textOpacity));
if (config.topLeft != null && !config.topLeft.isEmpty())
{
final String text = config.topLeft;
final double x = x0;
graphics2D.setFont(font);
final TextLayout layout = new TextLayout(text, font, frc);
final Rectangle2D bounds = layout.getBounds();
final double h = bounds.getHeight();
final double dy = 0.5 * (config.divHeight - h)
+ (config.noOfDivsY + 1) * config.divHeight;
final double dx = 0;
graphics2D.drawString(text, (float) (x - dx), (float) (y1 + config.divHeight - dy));
}
if (config.topCenter != null && !config.topCenter.isEmpty())
{
final String text = config.topCenter;
final double x = xm;
graphics2D.setFont(font);
final TextLayout layout = new TextLayout(text, font, frc);
final Rectangle2D bounds = layout.getBounds();
final double h = bounds.getHeight();
final double dy = 0.5 * (config.divHeight - h)
+ (config.noOfDivsY + 1) * config.divHeight;
final double dx = 0.5 * bounds.getWidth();
graphics2D.drawString(text, (float) (x - dx), (float) (y1 + config.divHeight - dy));
}
if (config.topRight != null && !config.topRight.isEmpty())
{
final String text = config.topRight;
final double x = x1;
graphics2D.setFont(font);
final TextLayout layout = new TextLayout(text, font, frc);
final Rectangle2D bounds = layout.getBounds();
final double h = bounds.getHeight();
final double dy = 0.5 * (config.divHeight - h)
+ (config.noOfDivsY + 1) * config.divHeight;
final double dx = bounds.getWidth();
graphics2D.drawString(text, (float) (x - dx), (float) (y1 + config.divHeight - dy));
}
if (config.bottomRight != null && !config.bottomRight.isEmpty())
{
final String text = config.bottomRight;
final double x = x1;
graphics2D.setFont(font);
final TextLayout layout = new TextLayout(text, font, frc);
final Rectangle2D bounds = layout.getBounds();
final double h = bounds.getHeight();
final double dy = 0.5 * (config.divHeight - h);
final double dx = bounds.getWidth();
graphics2D.drawString(text, (float) (x - dx), (float) (y1 + config.divHeight - dy));
}
if (abscissaeText != null && !abscissaeText.isEmpty())
{
final String text = abscissaeText;
final double x = x0;
graphics2D.setFont(font);
final TextLayout layout = new TextLayout(text, font, frc);
final Rectangle2D bounds = layout.getBounds();
final double h = bounds.getHeight();
final double dy = 0.5 * (config.divHeight - h);
final double dx = x == xm ? 0.5 * bounds.getWidth() : 0;
graphics2D.drawString(text, (float) (x - dx), (float) (y1 + config.divHeight - dy));
}
if (ordinatesText != null && !ordinatesText.isEmpty())
{
final String text = ordinatesText;
graphics2D.setFont(font.deriveFont(AffineTransform.getQuadrantRotateInstance(-1)));
final TextLayout layout = new TextLayout(text, font, frc);
final Rectangle2D bounds = layout.getBounds();
final double h = bounds.getHeight();
final double d = 0.5 * (config.divHeight - h);
graphics2D.drawString(text, (float) (x0 - d), (float) y1);
}
}
finally
{
graphics2D.setComposite(composite);
}
}
}
public static class Config
{
public final int divWidth = 48;
public final int divHeight = 48;
public final int noOfDivsX = 16;
public final int noOfDivsY = 10;
public final int noOfMarginDivsX = 1;
public final int noOfMarginDivsY = 1;
public final Color bgColor = new Color(0xEF, 0xFF, 0xEF);
public final Color gridColor = Color.LIGHT_GRAY;
public final Color[] signalColors = new Color[] { //
//
new Color(0xE0, 0x00, 0x00), //
new Color(0xE0, 0x00, 0x40), //
new Color(0xE0, 0x00, 0x80), //
new Color(0x00, 0x70, 0xE0), //
new Color(0x70, 0x00, 0xE0), //
new Color(0x70, 0xE0, 0x70), //
new Color(0x00, 0x00, 0xE0), //
};
public final Font font = new Font("Comic Sans MS", Font.PLAIN, 1);
public final float framePenWidth = 1.0f;
public final float gridPenWidth = 0.3f;
public final float signalPenWidth = 0.8f;
public final float gridOpacity = 1.f;
public final float textOpacity = 1.f;
public final String topLeft;
public final String topCenter;
public final String topRight;
public final String bottomRight;
public Config(final String topLeft, final String topCenter, final String topRight,
final String bottomRight)
{
this.topLeft = topLeft;
this.topCenter = topCenter;
this.topRight = topRight;
this.bottomRight = bottomRight;
}
}
}