/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * 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 3 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, see http://www.gnu.org/licenses/ */ package org.esa.snap.ui.diagram; import org.esa.snap.core.util.ObjectUtils; import org.esa.snap.core.util.math.MathUtils; import java.io.Serializable; /** * Represents an axis in a <code>{@link Diagram}</code>. By default an axis has no name, no units and a range set to * (0,100). */ public class DiagramAxis implements Serializable { private static final double[] _tickFactors = new double[]{1.0, 1.5, 2.0, 2.5, 4.0, 5.0, 7.5, 10.0}; private Diagram diagram; private String name; private String unit; private double unitFactor; private double minValue; private double maxValue; private int numMajorTicks; private int numMinorTicks; private boolean isMinToMax; public DiagramAxis() { this(null, null); } public DiagramAxis(String name, String unit) { this.name = name; this.unit = unit; unitFactor = 1.0; minValue = 0.0; maxValue = 100.0; numMajorTicks = 3; numMinorTicks = 5; isMinToMax = true; } public String getName() { return name; } public Diagram getDiagram() { return diagram; } public void setDiagram(Diagram diagram) { this.diagram = diagram; } public void setName(String name) { if (!ObjectUtils.equalObjects(this.name, name)) { this.name = name; invalidate(); } } public String getUnit() { return unit; } public void setUnit(String unit) { if (!ObjectUtils.equalObjects(this.unit, unit)) { this.unit = unit; invalidate(); } } public double getUnitFactor() { return unitFactor; } public void setUnitFactor(double unitFactor) { if (this.unitFactor != unitFactor) { this.unitFactor = unitFactor; invalidate(); } } /** * Sets if Axis increases from min to max or decreases max to min * isMinToMax true if increases min to max */ public void setMinToMax(final boolean isMinToMax) { this.isMinToMax = isMinToMax; } /** * Does Axis increase from min to max or decrease max to min * @return true if increases min to max */ public boolean isMinToMax() { return isMinToMax; } public double getMinValue() { return minValue; } public double getMaxValue() { return maxValue; } public void setValueRange(double minValue, double maxValue) { if (minValue >= maxValue) { throw new IllegalArgumentException("minValue >= maxValue"); } if (this.minValue != minValue || this.maxValue != maxValue) { this.minValue = minValue; this.maxValue = maxValue; invalidate(); } } public int getNumMajorTicks() { return numMajorTicks; } public void setNumMajorTicks(int numMajorTicks) { if (numMajorTicks < 2) { throw new IllegalArgumentException("numMajorTicks < 2"); } if (this.numMajorTicks != numMajorTicks) { this.numMajorTicks = numMajorTicks; invalidate(); } } public int getNumMinorTicks() { return numMinorTicks; } public void setNumMinorTicks(int numMinorTicks) { if (numMinorTicks < 2) { throw new IllegalArgumentException("numMinorTicks < 2"); } if (this.numMinorTicks != numMinorTicks) { this.numMinorTicks = numMinorTicks; invalidate(); } } public double getMajorTickMarkDistance() { return (getMaxValue() - getMinValue()) / (getNumMajorTicks() - 1); } public void setSubDivision(double minValue, double maxValue, int numMajorTicks, int numMinorTicks) { setValueRange(minValue, maxValue); setNumMajorTicks(numMajorTicks); setNumMinorTicks(numMinorTicks); } public void setOptimalSubDivision(int numMajorTicksMin, int numMajorTicksMax, int numMinorTicks) { final double oldMinValue = minValue; final double oldMaxValue = maxValue; final double oldDelta = oldMaxValue - oldMinValue; double deltaDeltaMin = Double.MAX_VALUE; int numMajorTicksOpt = numMajorTicks; double newMinValueOpt = oldMinValue; double newMaxValueOpt = oldMaxValue; for (int numMajorTicks = numMajorTicksMin; numMajorTicks <= numMajorTicksMax; numMajorTicks++) { final double tickDist = getOptimalTickDistance(oldMinValue, oldMaxValue, numMajorTicks); final double newMinValue = adjustFloor(oldMinValue, tickDist); final double newMaxValue = adjustCeil(oldMaxValue, tickDist); final double newDelta = newMaxValue - newMinValue; final double deltaDelta = Math.abs(newDelta - oldDelta); if (deltaDelta < deltaDeltaMin) { deltaDeltaMin = deltaDelta; numMajorTicksOpt = numMajorTicks; newMinValueOpt = newMinValue; newMaxValueOpt = newMaxValue; } } setSubDivision(newMinValueOpt, newMaxValueOpt, numMajorTicksOpt, numMinorTicks); } public static double getOptimalTickDistance(double minValue, double maxValue, int numMajorTicks) { if (minValue >= maxValue) { throw new IllegalArgumentException("minValue >= maxValue"); } if (numMajorTicks < 2) { throw new IllegalArgumentException("numMajorTicks < 2"); } final double tickDist = (maxValue - minValue) / (numMajorTicks - 1); final double oom = MathUtils.getOrderOfMagnitude(tickDist); final double scale = Math.pow(10.0, oom); double tickDistOpt = 0.0; for (double tickFactor : _tickFactors) { tickDistOpt = tickFactor * scale; if (tickDistOpt >= tickDist) { break; } } return tickDistOpt; } private static double adjustCeil(double x, double dx) { return Math.ceil(x / dx) * dx; } private static double adjustFloor(double x, double dx) { return Math.floor(x / dx) * dx; } public String[] createTickmarkTexts() { double roundFactor = MathUtils.computeRoundFactor(getMinValue(), getMaxValue(), 3); return createTickmarkTexts(getMinValue(), getMaxValue(), getNumMajorTicks(), roundFactor); } private static String[] createTickmarkTexts(double min, double max, int n, double roundFactor) { String[] texts = new String[n]; double x; long xi; for (int i = 0; i < n; i++) { x = min + i * (max - min) / (n - 1); x = MathUtils.round(x, roundFactor); xi = (long) Math.floor(x); if (x == xi) { texts[i] = String.valueOf(xi); } else { texts[i] = String.valueOf(x); } } return texts; } private void invalidate() { if (diagram != null) { diagram.invalidate(); } } }