/*******************************************************************************
* Breakout Cave Survey Visualizer
*
* Copyright (C) 2014 James Edwards
*
* jedwards8 at fastmail dot fm
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*******************************************************************************/
package com.andork.plot;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.swing.SwingConstants;
public class PlotUtils {
public static final int LEFT = SwingConstants.LEFT;
public static final int RIGHT = SwingConstants.RIGHT;
public static final int CENTER = SwingConstants.CENTER;
public static double calcHorizontalGridLineLabelsWidth(Graphics2D g2, double topDomain, double bottomDomain,
double step, NumberFormat format) {
double start;
double end;
if (topDomain < bottomDomain) {
start = GridMath.modCeiling(topDomain, step);
end = GridMath.modFloor(bottomDomain, step);
} else {
start = GridMath.modCeiling(bottomDomain, step);
end = GridMath.modFloor(topDomain, step);
}
int count = (int) Math.round((end - start) / step) + 1;
FontMetrics fm = g2.getFontMetrics();
double width = 0;
for (int i = 0; i < count; i++) {
double domain = start + step * i;
String label = format.format(domain);
Rectangle2D labelBounds = fm.getStringBounds(label, g2);
width = Math.max(width, labelBounds.getWidth());
}
return width;
}
public static double convertDomain(double d, double srcStart, double srcEnd, double destStart, double destEnd) {
return destStart + (d - srcStart) / (srcEnd - srcStart) * (destEnd - destStart);
}
public static void drawHorizontalGridLineLabels(Graphics2D g2, Rectangle bounds, int justify, double topDomain,
double bottomDomain, double step, NumberFormat format) {
double start;
double end;
if (topDomain < bottomDomain) {
start = GridMath.modCeiling(topDomain, step);
end = GridMath.modFloor(bottomDomain, step);
} else {
start = GridMath.modCeiling(bottomDomain, step);
end = GridMath.modFloor(topDomain, step);
}
int count = (int) Math.round((end - start) / step) + 1;
FontMetrics fm = g2.getFontMetrics();
for (int i = 0; i < count; i++) {
double domain = start + step * i;
double y = convertDomain(domain, topDomain, bottomDomain, bounds.getMinY(), bounds.getMaxY());
String label = format.format(domain);
Rectangle2D labelBounds = fm.getStringBounds(label, g2);
double w = labelBounds.getWidth();
double x = 0;
switch (justify) {
case RIGHT:
x = bounds.getMaxX() - w;
break;
case CENTER:
x = bounds.getCenterX() - w / 2.0;
break;
default:
x = bounds.getMinX();
break;
}
g2.drawString(label, (int) Math.round(x), (int) Math.round(y + fm.getAscent() / 2));
}
}
public static void drawHorizontalGridLines(Graphics2D g2, Rectangle bounds, double topDomain, double bottomDomain,
double step) {
double start;
double end;
if (topDomain < bottomDomain) {
start = GridMath.modCeiling(topDomain, step);
end = GridMath.modFloor(bottomDomain, step);
} else {
start = GridMath.modCeiling(bottomDomain, step);
end = GridMath.modFloor(topDomain, step);
}
int count = (int) Math.round((end - start) / step) + 1;
Line2D.Double line = new Line2D.Double();
for (int i = 0; i < count; i++) {
double y = convertDomain(start + step * i, topDomain, bottomDomain, bounds.getMinY(), bounds.getMaxY());
line.setLine(bounds.getMinX(), y, bounds.getMaxX(), y);
g2.draw(line);
}
}
public static void drawVerticalGridLineLabels(Graphics2D g2, Rectangle bounds, double leftDomain,
double rightDomain, double step, NumberFormat format) {
double start;
double end;
if (leftDomain < rightDomain) {
start = GridMath.modCeiling(leftDomain, step);
end = GridMath.modFloor(rightDomain, step);
} else {
start = GridMath.modCeiling(rightDomain, step);
end = GridMath.modFloor(leftDomain, step);
}
int count = (int) Math.round((end - start) / step) + 1;
FontMetrics fm = g2.getFontMetrics();
Rectangle2D lastLabelBounds = null;
for (int i = 0; i < count; i++) {
double domain = start + step * i;
double x = convertDomain(domain, leftDomain, rightDomain, bounds.getMinX(), bounds.getMaxX());
String label = format.format(domain);
Rectangle2D labelBounds = fm.getStringBounds(label, g2);
double w = labelBounds.getWidth();
double h = labelBounds.getHeight();
labelBounds.setFrame(x - w / 2.0, bounds.y + bounds.height - fm.getAscent(), w, h);
if (lastLabelBounds == null || !lastLabelBounds.intersects(labelBounds)) {
g2.drawString(label, (float) labelBounds.getMinX(), (float) labelBounds.getMinY() + fm.getAscent());
lastLabelBounds = labelBounds;
}
}
}
public static void drawVerticalGridLines(Graphics2D g2, Rectangle bounds, double leftDomain, double rightDomain,
double step) {
double start;
double end;
if (leftDomain < rightDomain) {
start = GridMath.modCeiling(leftDomain, step);
end = GridMath.modFloor(rightDomain, step);
} else {
start = GridMath.modCeiling(rightDomain, step);
end = GridMath.modFloor(leftDomain, step);
}
int count = (int) Math.round((end - start) / step) + 1;
Line2D.Double line = new Line2D.Double();
for (int i = 0; i < count; i++) {
double x = convertDomain(start + step * i, leftDomain, rightDomain, bounds.getMinX(), bounds.getMaxX());
line.setLine(x, bounds.getMinY(), x, bounds.getMaxY());
g2.draw(line);
}
}
public static void drawVerticalLines(Graphics2D g2, Rectangle bounds, double leftDomain, double rightDomain,
double[] lines) {
int startIndex;
int endIndex;
if (leftDomain < rightDomain) {
startIndex = ArrayUtils.ceilingIndex(lines, leftDomain);
endIndex = ArrayUtils.floorIndex(lines, rightDomain);
} else {
startIndex = ArrayUtils.ceilingIndex(lines, rightDomain);
endIndex = ArrayUtils.floorIndex(lines, leftDomain);
}
if (startIndex < 0 || endIndex < 0) {
return;
}
Line2D.Double line = new Line2D.Double();
double lastX = 0;
for (int i = startIndex; i <= endIndex; i++) {
double x = convertDomain(lines[i], leftDomain, rightDomain, bounds.getMinX(), bounds.getMaxX());
if (i == startIndex || Math.round(x) != Math.round(lastX)) {
line.setLine(x, bounds.getMinY(), x, bounds.getMaxY());
g2.draw(line);
}
lastX = x;
}
}
public static boolean equals(Object o1, Object o2) {
return o1 == null && o2 == null || o1 != null && o1.equals(o2) || o2 != null && o2.equals(o1);
}
public static double evalContinuous(double[] domain, double[] values, double x) {
int fi = ArrayUtils.floorIndex(domain, x);
if (fi < 0 || x > domain[domain.length - 1]) {
return Double.NaN;
}
if (domain[fi] == x) {
return values[fi];
}
double ld = domain[fi];
double lv = values[fi];
double hd = domain[fi + 1];
double hv = values[fi + 1];
double f = (x - ld) / (hd - ld);
return lv * (1 - f) + hv * f;
}
public static double evalLine(double x1, double y1, double x2, double y2, double x) {
if (x == x1) {
return y1;
}
if (x == x2) {
return y2;
}
double f = (x - x1) / (x2 - x1);
return y1 * (1 - f) + y2 * f;
}
public static double evalMappedTrace(double[] domain, double[] values, List<Double> anchorDest,
List<Double> anchorSrc, double x) {
double srcDomain = evalPolyline(anchorDest, anchorSrc, x);
return evalPolyline(domain, values, srcDomain);
}
public static double evalPolyline(double[] xx, double[] yy, double x) {
int index = ArrayUtils.floorIndex(xx, x);
if (index < 0) {
return Double.NaN;
}
if (index == xx.length - 1) {
return x == xx[index] ? yy[index] : Double.NaN;
}
return evalLine(xx[index], yy[index], xx[index + 1], yy[index + 1], x);
}
public static double evalPolyline(List<Double> xx, List<Double> yy, double x) {
int index = ListUtils.floorIndex(xx, x);
if (index < 0) {
return Double.NaN;
}
if (index == xx.size() - 1) {
return x == xx.get(index) ? yy.get(index) : Double.NaN;
}
return evalLine(xx.get(index), yy.get(index), xx.get(index + 1), yy.get(index + 1), x);
}
public static double evalStep(double[] domain, double[] values, double x) {
int fi = ArrayUtils.floorIndex(domain, x);
return fi < 0 || x > domain[domain.length - 1] ? Double.NaN : values[fi];
}
public static double evalStepCentered(double[] domain, double[] values, double x) {
int fi = ArrayUtils.floorIndex(domain, x);
if (fi < 0 || x > domain[domain.length - 1]) {
return Double.NaN;
}
if (domain[fi] == x) {
return values[fi];
}
double ld = domain[fi];
double lv = values[fi];
double hd = domain[fi + 1];
double hv = values[fi + 1];
if (Double.isNaN(lv) || Double.isNaN(hv)) {
return Double.NaN;
}
return hd - x < x - ld ? hv : lv;
}
public static boolean greater(double a, double b, boolean backward) {
return backward ? b > a : a > b;
}
public static boolean greaterOrEqual(double a, double b, boolean backward) {
return backward ? b >= a : a >= b;
}
public static double[] insert(double[] sortedSet, double key) {
int insertIndex = ArrayUtils.ceilingIndex(sortedSet, key);
if (insertIndex < 0) {
insertIndex = sortedSet.length;
} else if (sortedSet[insertIndex] == key) {
return sortedSet;
}
double[] newSet = Arrays.copyOf(sortedSet, sortedSet.length + 1);
System.arraycopy(sortedSet, 0, newSet, 0, insertIndex);
sortedSet[insertIndex] = key;
System.arraycopy(sortedSet, insertIndex, newSet, insertIndex + 1, sortedSet.length - insertIndex);
return newSet;
}
public static double[] insert(double[] array, double key, int index) {
double[] newArray = Arrays.copyOf(array, array.length + 1);
System.arraycopy(array, 0, newArray, 0, index);
newArray[index] = key;
System.arraycopy(array, index, newArray, index + 1, array.length - index);
return newArray;
}
public static boolean lessOrEqual(double a, double b, boolean backward) {
return backward ? b <= a : a <= b;
}
public static Color lighterColor(Color lineColor, int adj) {
int r = Math.max(0, Math.min(255, lineColor.getRed() + adj));
int g = Math.max(0, Math.min(255, lineColor.getGreen() + adj));
int b = Math.max(0, Math.min(255, lineColor.getBlue() + adj));
return new Color(r, g, b, lineColor.getAlpha());
}
public static double max(double a, double b, boolean backward) {
return backward ? Math.min(a, b) : Math.max(a, b);
}
public static double maxValue(double[] values) {
double max = Double.NaN;
for (double d : values) {
if (Double.isNaN(max) || d > max) {
max = d;
}
}
return max;
}
public static double min(double a, double b, boolean backward) {
return backward ? Math.max(a, b) : Math.min(a, b);
}
public static double minValue(double[] values) {
double min = Double.NaN;
for (double d : values) {
if (Double.isNaN(min) || d < min) {
min = d;
}
}
return min;
}
public static double[] remove(double[] array, int index) {
double[] newArray = new double[array.length - 1];
System.arraycopy(array, 0, newArray, 0, index);
if (index < array.length - 1) {
System.arraycopy(array, index + 1, newArray, index, array.length - index - 1);
}
return newArray;
}
public static double[] toArray(Collection<Double> collection) {
double[] result = new double[collection.size()];
int k = 0;
for (Double d : collection) {
result[k++] = d;
}
return result;
}
public static ArrayList<Double> toList(double[] array) {
ArrayList<Double> result = new ArrayList<Double>();
for (double d : array) {
result.add(d);
}
return result;
}
}