Visitar URL original
Merge pull request #3929 from emilioheredia-source/rtplot/rttank-fixes · ControlSystemStudio/phoebus@5de3627 · GitHub
Skip to content

Commit 5de3627

Browse files
authored
Merge pull request #3929 from emilioheredia-source/rtplot/rttank-fixes
fix (rtplot): RTTank ignores inverted ranges and the label format on linear scales
2 parents 6dfbe7a + 866f645 commit 5de3627

5 files changed

Lines changed: 107 additions & 50 deletions

File tree

‎app/rtplot/src/main/java/org/csstudio/javafx/rtplot/RTTank.java‎

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ public class RTTank extends Canvas
9797
/** Current value, i.e. fill level */
9898
private volatile double value = 5.0;
9999

100+
/** Requested value range, see {@link #setRange}; NaN until set */
101+
private volatile double rangeLow = Double.NaN;
102+
private volatile double rangeHigh = Double.NaN;
103+
100104
/** Does layout need to be re-computed? */
101105
protected final AtomicBoolean need_layout = new AtomicBoolean(true);
102106

@@ -273,6 +277,7 @@ public void setLogScale(final boolean logscale)
273277
{
274278
scale.setLogarithmic(logscale);
275279
right_scale.setLogarithmic(logscale);
280+
applyRange();
276281
requestUpdate();
277282
}
278283

@@ -441,34 +446,65 @@ public void setPerpendicularTickLabels(final boolean perpendicular)
441446
requestUpdate();
442447
}
443448

444-
/** Set value range
445-
* @param low Lower limit
446-
* @param high Upper limit
449+
/** Set value range.
450+
*
451+
* <p>An inverted range ({@code low > high}) runs the scale top-down
452+
* and fills the tank from the top. A logarithmic scale always runs
453+
* bottom-up. Non-finite and zero-width ranges are ignored.
454+
*
455+
* @param low Value at the bottom of the tank
456+
* @param high Value at the top of the tank
447457
*/
448458
public void setRange(final double low, final double high)
449459
{
450-
// Guard against NaN, Infinite, or inverted/flat range
451-
if (!Double.isFinite(low) || !Double.isFinite(high) || low >= high)
460+
if (!Double.isFinite(low) || !Double.isFinite(high) || low == high)
461+
return;
462+
rangeLow = low;
463+
rangeHigh = high;
464+
applyRange();
465+
}
466+
467+
/** @return Current value range of the scale */
468+
public AxisRange<Double> getValueRange()
469+
{
470+
return scale.getValueRange();
471+
}
472+
473+
/** Push the requested range to both scales, ascending for a log scale */
474+
private void applyRange()
475+
{
476+
double low = rangeLow;
477+
double high = rangeHigh;
478+
if (Double.isNaN(low))
452479
return;
480+
if (scale.isLogarithmic() && low > high)
481+
{
482+
low = rangeHigh;
483+
high = rangeLow;
484+
}
453485
scale.setValueRange(low, high);
454486
right_scale.setValueRange(low, high);
455487
}
456488

457-
/** @param value Set value */
489+
/** @param value Set value; a non-finite value shows an empty tank */
458490
public void setValue(final double value)
459491
{
460492
if (Double.isFinite(value))
461493
this.value = value;
462494
else
463-
this.value = scale.getValueRange().getLow();
495+
{
496+
final AxisRange<Double> range = scale.getValueRange();
497+
this.value = Math.min(range.getLow(), range.getHigh());
498+
}
464499
requestUpdate();
465500
}
466501

467-
/** Map a value to a Y pixel within the plot bounds (low value at bottom).
502+
/** Map a value to a Y pixel within the plot bounds.
468503
* Returns -1 when the mapping is undefined (e.g. log scale with non-positive inputs).
504+
* @param normal Range runs bottom-up? Otherwise the low value is at the top
469505
*/
470506
private int valueToY(final Rectangle pb, final double min, final double max,
471-
final double v, final boolean logscale)
507+
final double v, final boolean logscale, final boolean normal)
472508
{
473509
final double frac;
474510
if (logscale)
@@ -479,17 +515,17 @@ private int valueToY(final Rectangle pb, final double min, final double max,
479515
}
480516
else
481517
frac = (v - min) / (max - min);
482-
return (int) (pb.y + pb.height * (1.0 - frac));
518+
return (int) (pb.y + pb.height * (normal ? 1.0 - frac : frac));
483519
}
484520

485521
/** Draw a single horizontal limit line across the tank area at the given value. */
486522
private void drawLimitLineAt(final Graphics2D gc, final Rectangle pb,
487-
final double min, final double max,
523+
final double min, final double max, final boolean normal,
488524
final double limit, final Color color)
489525
{
490526
if (!Double.isFinite(limit) || limit <= min || limit >= max)
491527
return;
492-
final int y = valueToY(pb, min, max, limit, scale.isLogarithmic());
528+
final int y = valueToY(pb, min, max, limit, scale.isLogarithmic(), normal);
493529
if (y < pb.y || y > pb.y + pb.height)
494530
return;
495531
gc.setColor(color);
@@ -642,18 +678,18 @@ protected Image updateImageBuffer()
642678
final double lim_lo = limit_lo;
643679
final double lim_hi = limit_hi;
644680
final double lim_hihi = limit_hihi;
645-
if (normal && (!Double.isNaN(lim_lolo) || !Double.isNaN(lim_lo) ||
646-
!Double.isNaN(lim_hi) || !Double.isNaN(lim_hihi)))
681+
if (!Double.isNaN(lim_lolo) || !Double.isNaN(lim_lo) ||
682+
!Double.isNaN(lim_hi) || !Double.isNaN(lim_hihi))
647683
{
648684
if (limits_from_pv)
649685
gc.setStroke(new BasicStroke(2f));
650686
else
651687
gc.setStroke(new BasicStroke(2f, BasicStroke.CAP_BUTT,
652688
BasicStroke.JOIN_MITER, 10f, new float[]{6f, 4f}, 0f));
653-
drawLimitLineAt(gc, plot_bounds, min, max, lim_lolo, limit_major_color);
654-
drawLimitLineAt(gc, plot_bounds, min, max, lim_lo, limit_minor_color);
655-
drawLimitLineAt(gc, plot_bounds, min, max, lim_hi, limit_minor_color);
656-
drawLimitLineAt(gc, plot_bounds, min, max, lim_hihi, limit_major_color);
689+
drawLimitLineAt(gc, plot_bounds, min, max, normal, lim_lolo, limit_major_color);
690+
drawLimitLineAt(gc, plot_bounds, min, max, normal, lim_lo, limit_minor_color);
691+
drawLimitLineAt(gc, plot_bounds, min, max, normal, lim_hi, limit_minor_color);
692+
drawLimitLineAt(gc, plot_bounds, min, max, normal, lim_hihi, limit_major_color);
657693
gc.setStroke(new BasicStroke(1f));
658694
}
659695

‎app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/LinearTicks.java‎

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ public class LinearTicks extends Ticks<Double>
5353
/** Threshold for order-of-magnitude to use exponential notation */
5454
private long exponential_threshold = 4;
5555

56-
/** User-specified override format, or {@code null} for automatic selection.
57-
* When set, it is applied to all non-empty major tick labels after
58-
* {@code compute()} finishes its internal layout.
56+
/** User-specified format for the tick labels, or {@code null} for the
57+
* automatic format. Applied by {@link #format(Double)}, so it covers
58+
* every label that {@code compute()} creates.
5959
*/
6060
private volatile NumberFormat label_fmt_override = null;
6161

@@ -93,21 +93,6 @@ public boolean isPerpendicularTickLabels()
9393
return perpendicular_tick_labels;
9494
}
9595

96-
/** Re-apply {@code fmt} to every non-empty major tick label in {@code ticks}.
97-
* @param ticks List to mutate in-place
98-
* @param fmt Format to use
99-
*/
100-
protected static void relabelTicks(final List<MajorTick<Double>> ticks,
101-
final NumberFormat fmt)
102-
{
103-
for (int i = 0; i < ticks.size(); i++)
104-
{
105-
final MajorTick<Double> t = ticks.get(i);
106-
if (!t.getLabel().isEmpty())
107-
ticks.set(i, new MajorTick<>(t.getValue(), fmt.format(t.getValue())));
108-
}
109-
}
110-
11196
/** @param order_of_magnitude determines when to use exponential notation */
11297
public void setExponentialThreshold(long order_of_magnitude)
11398
{
@@ -194,7 +179,7 @@ public void compute(Double low, Double high, final Graphics2D gc, final int scre
194179
double distance = selectNiceStep(min_distance);
195180
if (distance == 0.0)
196181
throw new Error("Broken tickmark computation");
197-
182+
198183
// Update num_fmt based on distance between major tick labels.
199184
// For example, an axis with range 0 .. 10 would ordinarily use precision 0
200185
// and axis markers like 0, 2, 4, 6, 8, 10.
@@ -280,6 +265,7 @@ public void compute(Double low, Double high, final Graphics2D gc, final int scre
280265
major_ticks.add(0, new MajorTick<>(low, format(low)));
281266
major_ticks.add(new MajorTick<>(high, format(high)));
282267
}
268+
283269
this.major_ticks = major_ticks;
284270
this.minor_ticks = minor_ticks;
285271
}
@@ -385,9 +371,9 @@ public String format(final Double num)
385371
return "Inf";
386372
// Patch numbers that are "very close to zero"
387373
// to avoid "-0.00" or "0.0e-22"
388-
if (Math.abs(num) < zero_threshold)
389-
return num_fmt.format(0.0);
390-
return num_fmt.format(num);
374+
final double val = Math.abs(num) < zero_threshold ? 0.0 : num;
375+
final NumberFormat override = getLabelFormatOverride();
376+
return (override != null) ? override.format(val) : num_fmt.format(val);
391377
}
392378

393379
/** {@inheritDoc} */

‎app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/LogTicks.java‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,6 @@ else if (decadeExps.size() >= 2)
136136
major_ticks.add( new MajorTick<>(high, format(high)));
137137
}
138138

139-
// Apply user-specified label format override if set.
140-
final NumberFormat override = getLabelFormatOverride();
141-
if (override != null)
142-
relabelTicks(major_ticks, override);
143-
144139
this.major_ticks = major_ticks;
145140
this.minor_ticks = minor_ticks;
146141
}

‎app/rtplot/src/test/java/org/csstudio/javafx/rtplot/RTTankTest.java‎

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,36 @@ public void testConstruction()
3939
assertThat(tank, not(nullValue()));
4040
}
4141

42-
/** setRange should reject invalid ranges */
42+
/** setRange should ignore non-finite and zero-width ranges */
4343
@Test
44-
public void testSetRangeRejectsInvalid()
44+
public void testSetRangeIgnoresInvalid()
4545
{
4646
final RTTank tank = new RTTank();
47-
// Should silently ignore these — no exception
47+
tank.setRange(0, 100);
4848
tank.setRange(Double.NaN, 100);
4949
tank.setRange(0, Double.NaN);
50-
tank.setRange(100, 100); // flat
51-
tank.setRange(100, 0); // inverted
50+
tank.setRange(100, 100);
5251
tank.setRange(Double.POSITIVE_INFINITY, 100);
52+
assertThat(tank.getValueRange().getLow(), equalTo(0.0));
53+
assertThat(tank.getValueRange().getHigh(), equalTo(100.0));
54+
}
55+
56+
/** An inverted range is kept, except on a log scale which is always ascending */
57+
@Test
58+
public void testInvertedRange()
59+
{
60+
final RTTank tank = new RTTank();
61+
tank.setRange(100, 1);
62+
assertThat(tank.getValueRange().getLow(), equalTo(100.0));
63+
assertThat(tank.getValueRange().getHigh(), equalTo(1.0));
64+
65+
tank.setLogScale(true);
66+
assertThat(tank.getValueRange().getLow(), equalTo(1.0));
67+
assertThat(tank.getValueRange().getHigh(), equalTo(100.0));
68+
69+
tank.setLogScale(false);
70+
assertThat(tank.getValueRange().getLow(), equalTo(100.0));
71+
assertThat(tank.getValueRange().getHigh(), equalTo(1.0));
5372
}
5473

5574
/** setValue should handle NaN and Infinity */

‎app/rtplot/src/test/java/org/csstudio/javafx/rtplot/internal/LinearTicksTest.java‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99

1010
import org.csstudio.javafx.rtplot.TicksTestBase;
1111
import org.csstudio.javafx.rtplot.internal.LinearTicks;
12+
import org.csstudio.javafx.rtplot.internal.MajorTick;
1213
import org.junit.jupiter.api.Test;
1314

1415
import static org.hamcrest.CoreMatchers.equalTo;
16+
import static org.hamcrest.CoreMatchers.not;
1517
import static org.hamcrest.MatcherAssert.assertThat;
1618

1719
/** JUnit test
@@ -47,6 +49,25 @@ public void testNiceDistance()
4749
}
4850
}
4951

52+
/** A label format set by the user applies to every label, and a value
53+
* that is numerically almost zero still reads as zero, not "-0.00" */
54+
@Test
55+
public void testLabelFormat()
56+
{
57+
final LinearTicks ticks = new LinearTicks();
58+
ticks.setLabelFormat(LinearTicks.createDecimalFormat(2));
59+
ticks.compute(-0.7, 0.7, gc, buf.getWidth());
60+
for (MajorTick<Double> tick : ticks.getMajorTicks())
61+
{
62+
final String label = tick.getLabel();
63+
if (label.isEmpty())
64+
continue;
65+
assertThat(label, not(equalTo("-0.00")));
66+
assertThat(label + " has two decimals", label.matches("-?\\d+\\.\\d\\d"), equalTo(true));
67+
}
68+
assertThat(ticks.format(-1e-17), equalTo("0.00"));
69+
}
70+
5071
@Test
5172
public void testNormalTicks()
5273
{

0 commit comments

Comments
 (0)