Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 54 additions & 18 deletions app/rtplot/src/main/java/org/csstudio/javafx/rtplot/RTTank.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@
/** Current value, i.e. fill level */
private volatile double value = 5.0;

/** Requested value range, see {@link #setRange}; NaN until set */
private volatile double range_low = Double.NaN;

Check warning on line 101 in app/rtplot/src/main/java/org/csstudio/javafx/rtplot/RTTank.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this field "range_low" to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AaC2QGJhTRWCblpTKWBM&open=AaC2QGJhTRWCblpTKWBM&pullRequest=3929
private volatile double range_high = Double.NaN;

Check warning on line 102 in app/rtplot/src/main/java/org/csstudio/javafx/rtplot/RTTank.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this field "range_high" to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AaC2QGJhTRWCblpTKWBN&open=AaC2QGJhTRWCblpTKWBN&pullRequest=3929

/** Does layout need to be re-computed? */
protected final AtomicBoolean need_layout = new AtomicBoolean(true);

Expand Down Expand Up @@ -273,6 +277,7 @@
{
scale.setLogarithmic(logscale);
right_scale.setLogarithmic(logscale);
applyRange();
requestUpdate();
}

Expand Down Expand Up @@ -441,34 +446,65 @@
requestUpdate();
}

/** Set value range
* @param low Lower limit
* @param high Upper limit
/** Set value range.
*
* <p>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
*/
public void setRange(final double low, final double high)
{
// Guard against NaN, Infinite, or inverted/flat range
if (!Double.isFinite(low) || !Double.isFinite(high) || low >= high)
if (!Double.isFinite(low) || !Double.isFinite(high) || low == high)
return;
range_low = low;
range_high = high;
applyRange();
}

/** @return Current value range of the scale */
public AxisRange<Double> getValueRange()
{
return scale.getValueRange();
}

/** Push the requested range to both scales, ascending for a log scale */
private void applyRange()
{
double low = range_low;
double high = range_high;
if (Double.isNaN(low))
return;
if (scale.isLogarithmic() && low > high)
{
low = range_high;
high = range_low;
}
scale.setValueRange(low, high);
right_scale.setValueRange(low, high);
}

/** @param value Set value */
/** @param value Set value; a non-finite value shows an empty tank */
public void setValue(final double value)
{
if (Double.isFinite(value))
this.value = value;
else
this.value = scale.getValueRange().getLow();
{
final AxisRange<Double> range = scale.getValueRange();
this.value = Math.min(range.getLow(), range.getHigh());
}
requestUpdate();
}

/** Map a value to a Y pixel within the plot bounds (low value at bottom).
/** Map a value to a Y pixel within the plot bounds.
* 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 double v, final boolean logscale, final boolean normal)
{
final double frac;
if (logscale)
Expand All @@ -479,17 +515,17 @@
}
else
frac = (v - min) / (max - min);
return (int) (pb.y + pb.height * (1.0 - frac));
return (int) (pb.y + pb.height * (normal ? 1.0 - frac : 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 double min, final double max, final boolean normal,
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());
final int y = valueToY(pb, min, max, limit, scale.isLogarithmic(), normal);
if (y < pb.y || y > pb.y + pb.height)
return;
gc.setColor(color);
Expand Down Expand Up @@ -642,18 +678,18 @@
final double lim_lo = limit_lo;
final double lim_hi = limit_hi;
final double lim_hihi = limit_hihi;
if (normal && (!Double.isNaN(lim_lolo) || !Double.isNaN(lim_lo) ||
!Double.isNaN(lim_hi) || !Double.isNaN(lim_hihi)))
if (!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, 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);
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);
gc.setStroke(new BasicStroke(1f));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ public class LinearTicks extends Ticks<Double>
/** Threshold for order-of-magnitude to use exponential notation */
private long exponential_threshold = 4;

/** 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.
/** 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.
*/
private volatile NumberFormat label_fmt_override = null;

Expand Down Expand Up @@ -93,21 +93,6 @@ 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<MajorTick<Double>> ticks,
final NumberFormat fmt)
{
for (int i = 0; i < ticks.size(); i++)
{
final MajorTick<Double> 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)
{
Expand Down Expand Up @@ -194,7 +179,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.
Expand Down Expand Up @@ -280,6 +265,7 @@ 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;
}
Expand Down Expand Up @@ -385,9 +371,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"
if (Math.abs(num) < zero_threshold)
return num_fmt.format(0.0);
return num_fmt.format(num);
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);
}

/** {@inheritDoc} */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,6 @@ 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,36 @@ public void testConstruction()
assertThat(tank, not(nullValue()));
}

/** setRange should reject invalid ranges */
/** setRange should ignore non-finite and zero-width ranges */
@Test
public void testSetRangeRejectsInvalid()
public void testSetRangeIgnoresInvalid()
{
final RTTank tank = new RTTank();
// Should silently ignore these — no exception
tank.setRange(0, 100);
tank.setRange(Double.NaN, 100);
tank.setRange(0, Double.NaN);
tank.setRange(100, 100); // flat
tank.setRange(100, 0); // inverted
tank.setRange(100, 100);
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 */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@

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
Expand Down Expand Up @@ -47,6 +49,25 @@ 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<Double> 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()
{
Expand Down
Loading