diff --git a/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/RTTank.java b/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/RTTank.java
index 9623ceb72b..8c294c7b05 100644
--- a/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/RTTank.java
+++ b/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/RTTank.java
@@ -97,10 +97,6 @@ public class RTTank extends Canvas
/** Current value, i.e. fill level */
private volatile double value = 5.0;
- /** Requested value range, see {@link #setRange}; NaN until set */
- private volatile double rangeLow = Double.NaN;
- private volatile double rangeHigh = Double.NaN;
-
/** Does layout need to be re-computed? */
protected final AtomicBoolean need_layout = new AtomicBoolean(true);
@@ -277,7 +273,6 @@ public void setLogScale(final boolean logscale)
{
scale.setLogarithmic(logscale);
right_scale.setLogarithmic(logscale);
- applyRange();
requestUpdate();
}
@@ -446,65 +441,34 @@ public void setPerpendicularTickLabels(final boolean perpendicular)
requestUpdate();
}
- /** Set value range.
- *
- *
An inverted range ({@code low > high}) runs the scale top-down
- * and fills the tank from the top. A logarithmic scale always runs
- * bottom-up. Non-finite and zero-width ranges are ignored.
- *
- * @param low Value at the bottom of the tank
- * @param high Value at the top of the tank
+ /** Set value range
+ * @param low Lower limit
+ * @param high Upper limit
*/
public void setRange(final double low, final double high)
{
- if (!Double.isFinite(low) || !Double.isFinite(high) || low == high)
- return;
- rangeLow = low;
- rangeHigh = high;
- applyRange();
- }
-
- /** @return Current value range of the scale */
- public AxisRange getValueRange()
- {
- return scale.getValueRange();
- }
-
- /** Push the requested range to both scales, ascending for a log scale */
- private void applyRange()
- {
- double low = rangeLow;
- double high = rangeHigh;
- if (Double.isNaN(low))
+ // Guard against NaN, Infinite, or inverted/flat range
+ if (!Double.isFinite(low) || !Double.isFinite(high) || low >= high)
return;
- if (scale.isLogarithmic() && low > high)
- {
- low = rangeHigh;
- high = rangeLow;
- }
scale.setValueRange(low, high);
right_scale.setValueRange(low, high);
}
- /** @param value Set value; a non-finite value shows an empty tank */
+ /** @param value Set value */
public void setValue(final double value)
{
if (Double.isFinite(value))
this.value = value;
else
- {
- final AxisRange range = scale.getValueRange();
- this.value = Math.min(range.getLow(), range.getHigh());
- }
+ this.value = scale.getValueRange().getLow();
requestUpdate();
}
- /** Map a value to a Y pixel within the plot bounds.
+ /** Map a value to a Y pixel within the plot bounds (low value at bottom).
* Returns -1 when the mapping is undefined (e.g. log scale with non-positive inputs).
- * @param normal Range runs bottom-up? Otherwise the low value is at the top
*/
private int valueToY(final Rectangle pb, final double min, final double max,
- final double v, final boolean logscale, final boolean normal)
+ final double v, final boolean logscale)
{
final double frac;
if (logscale)
@@ -515,17 +479,17 @@ private int valueToY(final Rectangle pb, final double min, final double max,
}
else
frac = (v - min) / (max - min);
- return (int) (pb.y + pb.height * (normal ? 1.0 - frac : frac));
+ return (int) (pb.y + pb.height * (1.0 - frac));
}
/** Draw a single horizontal limit line across the tank area at the given value. */
private void drawLimitLineAt(final Graphics2D gc, final Rectangle pb,
- final double min, final double max, final boolean normal,
+ final double min, final double max,
final double limit, final Color color)
{
if (!Double.isFinite(limit) || limit <= min || limit >= max)
return;
- final int y = valueToY(pb, min, max, limit, scale.isLogarithmic(), normal);
+ final int y = valueToY(pb, min, max, limit, scale.isLogarithmic());
if (y < pb.y || y > pb.y + pb.height)
return;
gc.setColor(color);
@@ -678,18 +642,18 @@ protected Image updateImageBuffer()
final double lim_lo = limit_lo;
final double lim_hi = limit_hi;
final double lim_hihi = limit_hihi;
- if (!Double.isNaN(lim_lolo) || !Double.isNaN(lim_lo) ||
- !Double.isNaN(lim_hi) || !Double.isNaN(lim_hihi))
+ if (normal && (!Double.isNaN(lim_lolo) || !Double.isNaN(lim_lo) ||
+ !Double.isNaN(lim_hi) || !Double.isNaN(lim_hihi)))
{
if (limits_from_pv)
gc.setStroke(new BasicStroke(2f));
else
gc.setStroke(new BasicStroke(2f, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER, 10f, new float[]{6f, 4f}, 0f));
- drawLimitLineAt(gc, plot_bounds, min, max, normal, lim_lolo, limit_major_color);
- drawLimitLineAt(gc, plot_bounds, min, max, normal, lim_lo, limit_minor_color);
- drawLimitLineAt(gc, plot_bounds, min, max, normal, lim_hi, limit_minor_color);
- drawLimitLineAt(gc, plot_bounds, min, max, normal, lim_hihi, limit_major_color);
+ drawLimitLineAt(gc, plot_bounds, min, max, lim_lolo, limit_major_color);
+ drawLimitLineAt(gc, plot_bounds, min, max, lim_lo, limit_minor_color);
+ drawLimitLineAt(gc, plot_bounds, min, max, lim_hi, limit_minor_color);
+ drawLimitLineAt(gc, plot_bounds, min, max, lim_hihi, limit_major_color);
gc.setStroke(new BasicStroke(1f));
}
diff --git a/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/LinearTicks.java b/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/LinearTicks.java
index 16f50d7481..6703336ff7 100644
--- a/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/LinearTicks.java
+++ b/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/LinearTicks.java
@@ -53,9 +53,9 @@ public class LinearTicks extends Ticks
/** Threshold for order-of-magnitude to use exponential notation */
private long exponential_threshold = 4;
- /** User-specified format for the tick labels, or {@code null} for the
- * automatic format. Applied by {@link #format(Double)}, so it covers
- * every label that {@code compute()} creates.
+ /** User-specified override format, or {@code null} for automatic selection.
+ * When set, it is applied to all non-empty major tick labels after
+ * {@code compute()} finishes its internal layout.
*/
private volatile NumberFormat label_fmt_override = null;
@@ -93,6 +93,21 @@ public boolean isPerpendicularTickLabels()
return perpendicular_tick_labels;
}
+ /** Re-apply {@code fmt} to every non-empty major tick label in {@code ticks}.
+ * @param ticks List to mutate in-place
+ * @param fmt Format to use
+ */
+ protected static void relabelTicks(final List> ticks,
+ final NumberFormat fmt)
+ {
+ for (int i = 0; i < ticks.size(); i++)
+ {
+ final MajorTick t = ticks.get(i);
+ if (!t.getLabel().isEmpty())
+ ticks.set(i, new MajorTick<>(t.getValue(), fmt.format(t.getValue())));
+ }
+ }
+
/** @param order_of_magnitude determines when to use exponential notation */
public void setExponentialThreshold(long order_of_magnitude)
{
@@ -179,7 +194,7 @@ public void compute(Double low, Double high, final Graphics2D gc, final int scre
double distance = selectNiceStep(min_distance);
if (distance == 0.0)
throw new Error("Broken tickmark computation");
-
+
// Update num_fmt based on distance between major tick labels.
// For example, an axis with range 0 .. 10 would ordinarily use precision 0
// and axis markers like 0, 2, 4, 6, 8, 10.
@@ -265,7 +280,6 @@ public void compute(Double low, Double high, final Graphics2D gc, final int scre
major_ticks.add(0, new MajorTick<>(low, format(low)));
major_ticks.add(new MajorTick<>(high, format(high)));
}
-
this.major_ticks = major_ticks;
this.minor_ticks = minor_ticks;
}
@@ -371,9 +385,9 @@ public String format(final Double num)
return "Inf";
// Patch numbers that are "very close to zero"
// to avoid "-0.00" or "0.0e-22"
- final double val = Math.abs(num) < zero_threshold ? 0.0 : num;
- final NumberFormat override = getLabelFormatOverride();
- return (override != null) ? override.format(val) : num_fmt.format(val);
+ if (Math.abs(num) < zero_threshold)
+ return num_fmt.format(0.0);
+ return num_fmt.format(num);
}
/** {@inheritDoc} */
diff --git a/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/LogTicks.java b/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/LogTicks.java
index 36fae4ff4d..3a76def321 100644
--- a/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/LogTicks.java
+++ b/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/LogTicks.java
@@ -136,6 +136,11 @@ else if (decadeExps.size() >= 2)
major_ticks.add( new MajorTick<>(high, format(high)));
}
+ // Apply user-specified label format override if set.
+ final NumberFormat override = getLabelFormatOverride();
+ if (override != null)
+ relabelTicks(major_ticks, override);
+
this.major_ticks = major_ticks;
this.minor_ticks = minor_ticks;
}
diff --git a/app/rtplot/src/test/java/org/csstudio/javafx/rtplot/RTTankTest.java b/app/rtplot/src/test/java/org/csstudio/javafx/rtplot/RTTankTest.java
index 2ad37ffbf3..c9e5177051 100644
--- a/app/rtplot/src/test/java/org/csstudio/javafx/rtplot/RTTankTest.java
+++ b/app/rtplot/src/test/java/org/csstudio/javafx/rtplot/RTTankTest.java
@@ -39,36 +39,17 @@ public void testConstruction()
assertThat(tank, not(nullValue()));
}
- /** setRange should ignore non-finite and zero-width ranges */
+ /** setRange should reject invalid ranges */
@Test
- public void testSetRangeIgnoresInvalid()
+ public void testSetRangeRejectsInvalid()
{
final RTTank tank = new RTTank();
- tank.setRange(0, 100);
+ // Should silently ignore these — no exception
tank.setRange(Double.NaN, 100);
tank.setRange(0, Double.NaN);
- tank.setRange(100, 100);
+ tank.setRange(100, 100); // flat
+ tank.setRange(100, 0); // inverted
tank.setRange(Double.POSITIVE_INFINITY, 100);
- assertThat(tank.getValueRange().getLow(), equalTo(0.0));
- assertThat(tank.getValueRange().getHigh(), equalTo(100.0));
- }
-
- /** An inverted range is kept, except on a log scale which is always ascending */
- @Test
- public void testInvertedRange()
- {
- final RTTank tank = new RTTank();
- tank.setRange(100, 1);
- assertThat(tank.getValueRange().getLow(), equalTo(100.0));
- assertThat(tank.getValueRange().getHigh(), equalTo(1.0));
-
- tank.setLogScale(true);
- assertThat(tank.getValueRange().getLow(), equalTo(1.0));
- assertThat(tank.getValueRange().getHigh(), equalTo(100.0));
-
- tank.setLogScale(false);
- assertThat(tank.getValueRange().getLow(), equalTo(100.0));
- assertThat(tank.getValueRange().getHigh(), equalTo(1.0));
}
/** setValue should handle NaN and Infinity */
diff --git a/app/rtplot/src/test/java/org/csstudio/javafx/rtplot/internal/LinearTicksTest.java b/app/rtplot/src/test/java/org/csstudio/javafx/rtplot/internal/LinearTicksTest.java
index 25c00171f7..074cb4f8e5 100644
--- a/app/rtplot/src/test/java/org/csstudio/javafx/rtplot/internal/LinearTicksTest.java
+++ b/app/rtplot/src/test/java/org/csstudio/javafx/rtplot/internal/LinearTicksTest.java
@@ -9,11 +9,9 @@
import org.csstudio.javafx.rtplot.TicksTestBase;
import org.csstudio.javafx.rtplot.internal.LinearTicks;
-import org.csstudio.javafx.rtplot.internal.MajorTick;
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
/** JUnit test
@@ -49,25 +47,6 @@ public void testNiceDistance()
}
}
- /** A label format set by the user applies to every label, and a value
- * that is numerically almost zero still reads as zero, not "-0.00" */
- @Test
- public void testLabelFormat()
- {
- final LinearTicks ticks = new LinearTicks();
- ticks.setLabelFormat(LinearTicks.createDecimalFormat(2));
- ticks.compute(-0.7, 0.7, gc, buf.getWidth());
- for (MajorTick tick : ticks.getMajorTicks())
- {
- final String label = tick.getLabel();
- if (label.isEmpty())
- continue;
- assertThat(label, not(equalTo("-0.00")));
- assertThat(label + " has two decimals", label.matches("-?\\d+\\.\\d\\d"), equalTo(true));
- }
- assertThat(ticks.format(-1e-17), equalTo("0.00"));
- }
-
@Test
public void testNormalTicks()
{