orientationChangedListener =
+ this::orientationChanged;
+
+ /** Whether orientation transforms are currently applied to the tank node. */
+ private boolean was_transformed = false;
+
+ @Override
+ public Pane createJFXNode() throws Exception
+ {
+ tank = new RTTank();
+ tank.setUpdateThrottle(Preferences.image_update_delay, TimeUnit.MILLISECONDS);
+ return new Pane(tank);
+ }
+
+ /** Register listeners on the {@link ScaledPVWidget} value and limit
+ * properties, then call {@link #registerLookListeners()} for the
+ * subclass to add its widget-specific appearance listeners.
+ * The current range, value and limits are applied once at the end. */
+ @Override
+ protected void registerListeners()
+ {
+ super.registerListeners();
+
+ // Value / range
+ model_widget.propLimitsFromPV().addUntypedPropertyListener(valueListener);
+ model_widget.propMinimum().addUntypedPropertyListener(valueListener);
+ model_widget.propMaximum().addUntypedPropertyListener(valueListener);
+ model_widget.runtimePropValue().addUntypedPropertyListener(valueListener);
+
+ // Alarm limit lines
+ model_widget.propShowAlarmLimits().addUntypedPropertyListener(limitsListener);
+ model_widget.propAlarmLimitsFromPV().addUntypedPropertyListener(limitsListener);
+ model_widget.propLevelLoLo().addUntypedPropertyListener(limitsListener);
+ model_widget.propLevelLow().addUntypedPropertyListener(limitsListener);
+ model_widget.propLevelHigh().addUntypedPropertyListener(limitsListener);
+ model_widget.propLevelHiHi().addUntypedPropertyListener(limitsListener);
+
+ // Alarm color changes only affect appearance, not limits
+ model_widget.propMinorAlarmColor().addUntypedPropertyListener(lookListener);
+ model_widget.propMajorAlarmColor().addUntypedPropertyListener(lookListener);
+
+ // Widget-specific look properties (colors, scale, font, ...)
+ registerLookListeners();
+
+ // Apply the current state, range first, then limits
+ valueChanged(null, null, null);
+ limitsChanged(null, null, null);
+ }
+
+ /** Register listeners on widget-specific appearance properties.
+ * The implementation should add listeners using {@link #lookListener}
+ * (or a dedicated listener) and call nothing on the tank directly,
+ * that happens in {@link #applyLookToTank()}. */
+ protected abstract void registerLookListeners();
+
+ @Override
+ protected void unregisterListeners()
+ {
+ model_widget.propLimitsFromPV().removePropertyListener(valueListener);
+ model_widget.propMinimum().removePropertyListener(valueListener);
+ model_widget.propMaximum().removePropertyListener(valueListener);
+ model_widget.runtimePropValue().removePropertyListener(valueListener);
+
+ model_widget.propShowAlarmLimits().removePropertyListener(limitsListener);
+ model_widget.propAlarmLimitsFromPV().removePropertyListener(limitsListener);
+ model_widget.propLevelLoLo().removePropertyListener(limitsListener);
+ model_widget.propLevelLow().removePropertyListener(limitsListener);
+ model_widget.propLevelHigh().removePropertyListener(limitsListener);
+ model_widget.propLevelHiHi().removePropertyListener(limitsListener);
+
+ model_widget.propMinorAlarmColor().removePropertyListener(lookListener);
+ model_widget.propMajorAlarmColor().removePropertyListener(lookListener);
+
+ unregisterLookListeners();
+ super.unregisterListeners();
+ }
+
+ /** Unregister the listeners added by {@link #registerLookListeners()}. */
+ protected abstract void unregisterLookListeners();
+
+ /** Called on every PV value update and on range-related property changes.
+ * Updates the range, the alarm limits from the PV metadata and the
+ * fill level of the tank. */
+ private void valueChanged(final WidgetProperty> prop,
+ final Object old_value, final Object new_value)
+ {
+ final VType vtype = model_widget.runtimePropValue().getValue();
+ final boolean limits_from_pv = model_widget.propLimitsFromPV().getValue();
+ updateRange(vtype, limits_from_pv);
+
+ if (model_widget.propAlarmLimitsFromPV().getValue())
+ applyAlarmLimits(vtype);
+
+ // In edit mode there is no PV, so the widget range is the effective range
+ final double min = model_widget.propMinimum().getValue();
+ final double max = model_widget.propMaximum().getValue();
+ final double value = toolkit.isEditMode()
+ ? (min + max) / 2.0
+ : VTypeUtil.getValueNumber(vtype).doubleValue();
+ tank.setValue(value);
+ }
+
+ /** Push the display range to the tank.
+ * When {@code limits_from_pv} is {@code true}, reads the range from PV
+ * display metadata and falls back to widget properties when metadata is
+ * unavailable. When {@code false}, uses the widget properties directly.
+ *
+ * @param vtype current PV value (may be {@code null} before connect)
+ * @param limits_from_pv whether the range should come from the PV */
+ private void updateRange(final VType vtype, final boolean limits_from_pv)
+ {
+ double min = model_widget.propMinimum().getValue();
+ double max = model_widget.propMaximum().getValue();
+ if (limits_from_pv)
+ {
+ final Display display_info = Display.displayOf(vtype);
+ if (display_info != null && display_info.getDisplayRange().isFinite())
+ {
+ min = display_info.getDisplayRange().getMinimum();
+ max = display_info.getDisplayRange().getMaximum();
+ }
+ }
+ tank.setRange(min, max);
+ }
+
+ /** Triggered when any alarm limit property changes; delegates to
+ * {@link #applyAlarmLimits(VType)} with the current PV value. */
+ private void limitsChanged(final WidgetProperty> property,
+ final Object old_value, final Object new_value)
+ {
+ applyAlarmLimits(model_widget.runtimePropValue().getValue());
+ }
+
+ /** Resolves alarm limits from PV metadata or widget properties (depending on
+ * {@code alarm_limits_from_pv}) and pushes them to the tank.
+ * Clears all limit lines when {@code show_alarm_limits} is {@code false}. */
+ private void applyAlarmLimits(final VType vtype)
+ {
+ if (!model_widget.propShowAlarmLimits().getValue())
+ {
+ tank.setLimits(Double.NaN, Double.NaN, Double.NaN, Double.NaN);
+ return;
+ }
+ final double lolo, lo, hi, hihi;
+ if (model_widget.propAlarmLimitsFromPV().getValue())
+ {
+ final Display display_info = Display.displayOf(vtype);
+ if (display_info != null)
+ {
+ final Range minor = display_info.getWarningRange();
+ final Range major = display_info.getAlarmRange();
+ lo = minor.getMinimum();
+ hi = minor.getMaximum();
+ lolo = major.getMinimum();
+ hihi = major.getMaximum();
+ }
+ else
+ lolo = lo = hi = hihi = Double.NaN;
+ }
+ else
+ {
+ lolo = model_widget.propLevelLoLo().getValue();
+ lo = model_widget.propLevelLow().getValue();
+ hi = model_widget.propLevelHigh().getValue();
+ hihi = model_widget.propLevelHiHi().getValue();
+ }
+ tank.setLimits(lolo, lo, hi, hihi);
+ tank.setLimitsFromPV(model_widget.propAlarmLimitsFromPV().getValue());
+ }
+
+ /** @return whether this widget is currently in horizontal orientation */
+ protected abstract boolean isHorizontal();
+
+ /** Swaps width and height in the editor (so the widget visually rotates
+ * rather than stretching) and triggers a look update. */
+ protected void orientationChanged(final WidgetProperty prop,
+ final Boolean old, final Boolean horizontal)
+ {
+ if (toolkit.isEditMode())
+ {
+ final int w = model_widget.propWidth().getValue();
+ final int h = model_widget.propHeight().getValue();
+ model_widget.propWidth().setValue(h);
+ model_widget.propHeight().setValue(w);
+ }
+ dirty_look.mark();
+ toolkit.scheduleUpdate(this);
+ }
+
+ /** Push the current widget-specific appearance properties to the tank.
+ * Called from {@link #updateChanges()} after size and orientation
+ * have been applied. */
+ protected abstract void applyLookToTank();
+
+ @Override
+ public void updateChanges()
+ {
+ super.updateChanges();
+ if (dirty_look.checkAndClear())
+ {
+ final double width = model_widget.propWidth().getValue();
+ final double height = model_widget.propHeight().getValue();
+
+ // RTTank renders vertically; rotate 90 degrees clockwise for horizontal bars.
+ if (isHorizontal())
+ {
+ tank.getTransforms().setAll(new Translate(width, 0),
+ new Rotate(90, 0, 0));
+ was_transformed = true;
+ tank.setWidth(height);
+ tank.setHeight(width);
+ }
+ else
+ {
+ if (was_transformed)
+ tank.getTransforms().clear();
+ was_transformed = false;
+ tank.setWidth(width);
+ tank.setHeight(height);
+ }
+ jfx_node.setPrefSize(width, height);
+
+ applyLookToTank();
+ tank.setAlarmColors(
+ JFXUtil.convert(model_widget.propMinorAlarmColor().getValue()),
+ JFXUtil.convert(model_widget.propMajorAlarmColor().getValue()));
+ }
+ }
+}
diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/TankRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/TankRepresentation.java
index 8e3d373914..2d514e86b0 100644
--- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/TankRepresentation.java
+++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/TankRepresentation.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2015-2023 Oak Ridge National Laboratory.
+ * Copyright (c) 2015-2026 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -7,52 +7,31 @@
*******************************************************************************/
package org.csstudio.display.builder.representation.javafx.widgets;
-import java.util.concurrent.TimeUnit;
-
-import org.csstudio.display.builder.model.DirtyFlag;
-import org.csstudio.display.builder.model.UntypedWidgetPropertyListener;
-import org.csstudio.display.builder.model.WidgetProperty;
-import org.csstudio.display.builder.model.WidgetPropertyListener;
-import org.csstudio.display.builder.model.util.VTypeUtil;
import org.csstudio.display.builder.model.widgets.TankWidget;
-import org.csstudio.display.builder.representation.Preferences;
import org.csstudio.display.builder.representation.javafx.JFXUtil;
-import org.csstudio.javafx.rtplot.RTTank;
-import org.epics.util.stats.Range;
-import org.epics.vtype.Display;
-import org.epics.vtype.VType;
-
-import javafx.scene.layout.Pane;
-import javafx.scene.transform.Rotate;
-import javafx.scene.transform.Translate;
-/** Creates JavaFX item for model widget
+/** Creates JavaFX item for the Tank widget.
+ *
+ * All shared RTTank wiring (value updates, alarm limits,
+ * orientation handling) lives in {@link RTScaledWidgetRepresentation}.
+ * This class contributes only the Tank-specific appearance properties:
+ * background, foreground, fill and empty colors.
+ *
* @author Kay Kasemir
- * @author Heredie Delvalle — CLS, alarm limits, dual scale,
- * format/precision wiring
+ * @author Heredie Delvalle
*/
-public class TankRepresentation extends RegionBaseRepresentation
+@SuppressWarnings("nls")
+public class TankRepresentation extends RTScaledWidgetRepresentation
{
- private final DirtyFlag dirty_look = new DirtyFlag();
- private final UntypedWidgetPropertyListener lookListener = this::lookChanged;
- private final UntypedWidgetPropertyListener valueListener = this::valueChanged;
- private final UntypedWidgetPropertyListener limitsListener = this::limitsChanged;
- private final WidgetPropertyListener orientationChangedListener = this::orientationChanged;
-
- private volatile RTTank tank;
-
@Override
- public Pane createJFXNode() throws Exception
+ protected boolean isHorizontal()
{
- tank = new RTTank();
- tank.setUpdateThrottle(Preferences.image_update_delay, TimeUnit.MILLISECONDS);
- return new Pane(tank);
+ return model_widget.propHorizontal().getValue();
}
@Override
- protected void registerListeners()
+ protected void registerLookListeners()
{
- super.registerListeners();
model_widget.propWidth().addUntypedPropertyListener(lookListener);
model_widget.propHeight().addUntypedPropertyListener(lookListener);
model_widget.propFont().addUntypedPropertyListener(lookListener);
@@ -62,36 +41,18 @@ protected void registerListeners()
model_widget.propEmptyColor().addUntypedPropertyListener(lookListener);
model_widget.propScaleVisible().addUntypedPropertyListener(lookListener);
model_widget.propShowMinorTicks().addUntypedPropertyListener(lookListener);
+ model_widget.propShowScaleLabels().addUntypedPropertyListener(lookListener);
model_widget.propPerpendicularTickLabels().addUntypedPropertyListener(lookListener);
model_widget.propFormat().addUntypedPropertyListener(lookListener);
model_widget.propPrecision().addUntypedPropertyListener(lookListener);
- model_widget.propMinorAlarmColor().addUntypedPropertyListener(lookListener);
- model_widget.propMajorAlarmColor().addUntypedPropertyListener(lookListener);
model_widget.propOppositeScaleVisible().addUntypedPropertyListener(lookListener);
model_widget.propBorderWidth().addUntypedPropertyListener(lookListener);
model_widget.propLogScale().addUntypedPropertyListener(lookListener);
-
- // Range and fill-level; need re-evaluation on every PV sample
- model_widget.propLimitsFromPV().addUntypedPropertyListener(valueListener);
- model_widget.propMinimum().addUntypedPropertyListener(valueListener);
- model_widget.propMaximum().addUntypedPropertyListener(valueListener);
- model_widget.runtimePropValue().addUntypedPropertyListener(valueListener);
- // Alarm limits; only need re-evaluation when limit properties change.
- // When alarm_limits_from_pv=true, valueChanged() calls applyAlarmLimits() too.
- model_widget.propShowAlarmLimits().addUntypedPropertyListener(limitsListener);
- model_widget.propAlarmLimitsFromPV().addUntypedPropertyListener(limitsListener);
- model_widget.propLevelLoLo().addUntypedPropertyListener(limitsListener);
- model_widget.propLevelLow().addUntypedPropertyListener(limitsListener);
- model_widget.propLevelHigh().addUntypedPropertyListener(limitsListener);
- model_widget.propLevelHiHi().addUntypedPropertyListener(limitsListener);
model_widget.propHorizontal().addPropertyListener(orientationChangedListener);
- // Initial apply — order matters: range first, then limits, then value
- valueChanged(null, null, null);
- limitsChanged(null, null, null);
}
@Override
- protected void unregisterListeners()
+ protected void unregisterLookListeners()
{
model_widget.propWidth().removePropertyListener(lookListener);
model_widget.propHeight().removePropertyListener(lookListener);
@@ -102,171 +63,32 @@ protected void unregisterListeners()
model_widget.propEmptyColor().removePropertyListener(lookListener);
model_widget.propScaleVisible().removePropertyListener(lookListener);
model_widget.propShowMinorTicks().removePropertyListener(lookListener);
+ model_widget.propShowScaleLabels().removePropertyListener(lookListener);
model_widget.propPerpendicularTickLabels().removePropertyListener(lookListener);
model_widget.propFormat().removePropertyListener(lookListener);
model_widget.propPrecision().removePropertyListener(lookListener);
- model_widget.propMinorAlarmColor().removePropertyListener(lookListener);
- model_widget.propMajorAlarmColor().removePropertyListener(lookListener);
model_widget.propOppositeScaleVisible().removePropertyListener(lookListener);
model_widget.propBorderWidth().removePropertyListener(lookListener);
model_widget.propLogScale().removePropertyListener(lookListener);
-
- model_widget.propLimitsFromPV().removePropertyListener(valueListener);
- model_widget.propMinimum().removePropertyListener(valueListener);
- model_widget.propMaximum().removePropertyListener(valueListener);
- model_widget.runtimePropValue().removePropertyListener(valueListener);
- model_widget.propShowAlarmLimits().removePropertyListener(limitsListener);
- model_widget.propAlarmLimitsFromPV().removePropertyListener(limitsListener);
- model_widget.propLevelLoLo().removePropertyListener(limitsListener);
- model_widget.propLevelLow().removePropertyListener(limitsListener);
- model_widget.propLevelHigh().removePropertyListener(limitsListener);
- model_widget.propLevelHiHi().removePropertyListener(limitsListener);
model_widget.propHorizontal().removePropertyListener(orientationChangedListener);
- super.unregisterListeners();
}
- private void lookChanged(final WidgetProperty> property, final Object old_value, final Object new_value)
- {
- dirty_look.mark();
- toolkit.scheduleUpdate(this);
- }
-
- /** Update the display range and fill level. Called on every PV value change.
- * Alarm limits from PV metadata are also refreshed here (the metadata is
- * carried inside the VType on every update). Manually-configured limits
- * are managed exclusively by {@link #limitsChanged}.
- */
- private void valueChanged(final WidgetProperty> property, final Object old_value, final Object new_value)
- {
- final VType vtype = model_widget.runtimePropValue().getValue();
-
- double min_val = model_widget.propMinimum().getValue();
- double max_val = model_widget.propMaximum().getValue();
- if (model_widget.propLimitsFromPV().getValue())
- {
- final Display display_info = Display.displayOf(vtype);
- if (display_info != null && display_info.getDisplayRange().isFinite())
- {
- min_val = display_info.getDisplayRange().getMinimum();
- max_val = display_info.getDisplayRange().getMaximum();
- }
- }
- tank.setRange(min_val, max_val);
-
- // Alarm metadata is embedded in the VType, so re-check it on every update.
- // When using widget-configured limits, limitsChanged() handles updates instead.
- if (model_widget.propAlarmLimitsFromPV().getValue())
- applyAlarmLimits(vtype);
-
- final double value = toolkit.isEditMode()
- ? (min_val + max_val) / 2
- : VTypeUtil.getValueNumber(vtype).doubleValue();
- tank.setValue(value);
- }
-
- /** Re-apply alarm limit lines. Called when any limit property changes.
- * Also invoked from {@link #valueChanged} when limits come from the PV.
- */
- private void limitsChanged(final WidgetProperty> property, final Object old_value, final Object new_value)
- {
- applyAlarmLimits(model_widget.runtimePropValue().getValue());
- }
-
- /** Push the current alarm limits to the tank, reading from PV metadata or
- * widget properties depending on {@code alarm_limits_from_pv}.
- * Clears all limit lines when {@code show_alarm_limits} is {@code false}.
- */
- private void applyAlarmLimits(final VType vtype)
- {
- if (!model_widget.propShowAlarmLimits().getValue())
- {
- tank.setLimits(Double.NaN, Double.NaN, Double.NaN, Double.NaN);
- return;
- }
- final double lolo, lo, hi, hihi;
- if (model_widget.propAlarmLimitsFromPV().getValue())
- {
- final Display display_info = Display.displayOf(vtype);
- if (display_info != null)
- {
- final Range minor = display_info.getWarningRange();
- final Range major = display_info.getAlarmRange();
- lo = minor.getMinimum();
- hi = minor.getMaximum();
- lolo = major.getMinimum();
- hihi = major.getMaximum();
- }
- else
- { // PV connected but no metadata yet — show nothing
- lolo = lo = hi = hihi = Double.NaN;
- }
- }
- else
- {
- lolo = model_widget.propLevelLoLo().getValue();
- lo = model_widget.propLevelLow().getValue();
- hi = model_widget.propLevelHigh().getValue();
- hihi = model_widget.propLevelHiHi().getValue();
- }
- tank.setLimits(lolo, lo, hi, hihi);
- tank.setLimitsFromPV(model_widget.propAlarmLimitsFromPV().getValue());
- }
-
- private void orientationChanged(final WidgetProperty prop, final Boolean old, final Boolean horizontal)
- {
- if (toolkit.isEditMode())
- { // Swap width <-> height so widget basically rotates
- final int w = model_widget.propWidth().getValue();
- final int h = model_widget.propHeight().getValue();
- model_widget.propWidth().setValue(h);
- model_widget.propHeight().setValue(w);
- }
- lookChanged(prop, old, horizontal);
- }
-
- /** Track if we ever set transformations because just 'clearing' would otherwise allocate them */
- private boolean was_transformed = false;
-
@Override
- public void updateChanges()
+ protected void applyLookToTank()
{
- super.updateChanges();
- if (dirty_look.checkAndClear())
- {
- double width = model_widget.propWidth().getValue();
- double height = model_widget.propHeight().getValue();
- if (model_widget.propHorizontal().getValue())
- {
- tank.getTransforms().setAll(new Translate(width, 0),
- new Rotate(90, 0, 0));
- was_transformed = true;
- tank.setWidth(height);
- tank.setHeight(width);
- }
- else
- {
- if (was_transformed)
- tank.getTransforms().clear();
- tank.setWidth(width);
- tank.setHeight(height);
- }
- jfx_node.setPrefSize(width, height);
- tank.setFont(JFXUtil.convert(model_widget.propFont().getValue()));
- tank.setBackground(JFXUtil.convert(model_widget.propBackground().getValue()));
- tank.setForeground(JFXUtil.convert(model_widget.propForeground().getValue()));
- tank.setFillColor(JFXUtil.convert(model_widget.propFillColor().getValue()));
- tank.setEmptyColor(JFXUtil.convert(model_widget.propEmptyColor().getValue()));
- tank.setScaleVisible(model_widget.propScaleVisible().getValue());
- tank.setShowMinorTicks(model_widget.propShowMinorTicks().getValue());
- tank.setPerpendicularTickLabels(model_widget.propPerpendicularTickLabels().getValue());
- tank.setLogScale(model_widget.propLogScale().getValue());
- tank.setLabelFormat(model_widget.propFormat().getValue(),
- model_widget.propPrecision().getValue());
- tank.setAlarmColors(
- JFXUtil.convert(model_widget.propMinorAlarmColor().getValue()),
- JFXUtil.convert(model_widget.propMajorAlarmColor().getValue()));
- tank.setRightScaleVisible(model_widget.propOppositeScaleVisible().getValue());
- tank.setBorderWidth(model_widget.propBorderWidth().getValue());
- }
+ tank.setFont(JFXUtil.convert(model_widget.propFont().getValue()));
+ tank.setBackground(JFXUtil.convert(model_widget.propBackground().getValue()));
+ tank.setForeground(JFXUtil.convert(model_widget.propForeground().getValue()));
+ tank.setFillColor(JFXUtil.convert(model_widget.propFillColor().getValue()));
+ tank.setEmptyColor(JFXUtil.convert(model_widget.propEmptyColor().getValue()));
+ tank.setScaleVisible(model_widget.propScaleVisible().getValue());
+ tank.setShowMinorTicks(model_widget.propShowMinorTicks().getValue());
+ tank.setScaleLabelsVisible(model_widget.propShowScaleLabels().getValue());
+ tank.setPerpendicularTickLabels(model_widget.propPerpendicularTickLabels().getValue());
+ tank.setLogScale(model_widget.propLogScale().getValue());
+ tank.setLabelFormat(model_widget.propFormat().getValue(),
+ model_widget.propPrecision().getValue());
+ tank.setRightScaleVisible(model_widget.propOppositeScaleVisible().getValue());
+ tank.setBorderWidth(model_widget.propBorderWidth().getValue());
}
}
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 8c294c7b05..7ed55b8b34 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
@@ -284,6 +284,17 @@ public void setShowMinorTicks(final boolean show)
requestUpdate();
}
+ /** Show or hide the tick labels while keeping the tick marks.
+ * Stacked widgets can then share one labelled scale: only the first
+ * shows labels, the others show aligned tick marks.
+ * @param visible {@code true} (default) for labels, {@code false} for ticks only */
+ public void setScaleLabelsVisible(final boolean visible)
+ {
+ // The axes request layout and refresh themselves when this changes
+ scale.setScaleLabelsVisible(visible);
+ right_scale.setScaleLabelsVisible(visible);
+ }
+
/** Configure the number format used for scale tick labels.
* @param format Display format; {@code null} or {@link ScaleFormat#DEFAULT} restores automatic formatting.
* @param precision Number of decimal places; clamped to [0, 15].
diff --git a/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/YAxisImpl.java b/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/YAxisImpl.java
index 109ad205c5..0df928fe37 100644
--- a/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/YAxisImpl.java
+++ b/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/YAxisImpl.java
@@ -60,6 +60,11 @@ public class YAxisImpl> extends NumericAxis impl
/** Show on right side? */
private volatile boolean is_right = false;
+ /** When {@code false}, only the tick marks are drawn: no tick labels
+ * and no axis label. Stacked widgets can then share one labelled scale.
+ * Read on the render thread, written from the JavaFX thread. */
+ private volatile boolean show_labels = true;
+
/** When {@code true}, rotated tick labels always use the 'up' direction
* (bottom-to-top) regardless of {@link #is_right}. This keeps the text
* orientation of a right-side scale identical to a left-side scale.
@@ -161,6 +166,20 @@ public void setForceTextUp(final boolean force)
force_text_up = force;
}
+ /** Show or hide the tick labels and the axis label while keeping the
+ * tick marks. Tick positions do not change, so stacked widgets can
+ * share one labelled scale.
+ * @param show {@code true} (default) for labels, {@code false} for ticks only
+ */
+ public void setScaleLabelsVisible(final boolean show)
+ {
+ if (show_labels == show)
+ return;
+ show_labels = show;
+ requestLayout();
+ requestRefresh();
+ }
+
/** Add trace to axis
* @param trace {@link Trace}
* @throws IllegalArgumentException if trace already on axis
@@ -210,6 +229,11 @@ public int getDesiredPixelSize(final Rectangle region, final Graphics2D gc)
return 0;
this.region = region;
+
+ // Ticks only: the tick marks plus the axis line
+ if (!show_labels)
+ return TICK_LENGTH + 1;
+
gc.setFont(label_font);
FontMetrics metrics = gc.getFontMetrics();
@@ -334,6 +358,10 @@ public int[] getPixelGaps(final Graphics2D gc)
if (! isVisible())
return super.getPixelGaps(gc);
+ // Ticks-only mode: no labels extend past the tick positions.
+ if (!show_labels)
+ return new int[] { 0, 0 };
+
gc.setFont(scale_font);
final FontMetrics metrics = gc.getFontMetrics();
@@ -392,7 +420,9 @@ public void paint(final Graphics2D gc, final Rectangle plot_bounds)
// Skip the visibility pass when LogTicks already thinned the labeled set:
// a second greedy pass would destroy the intentional symmetric spacing.
final boolean skipVisibility = (ticks instanceof LogTicks) && ((LogTicks) ticks).isThinned();
- final boolean[] showLabel = skipVisibility
+ final boolean[] showLabel = !show_labels
+ ? new boolean[majorTicks.size()]
+ : skipVisibility
? allLabeled(majorTicks)
: computeTickLabelVisibility(majorTicks, gc.getFontMetrics());
@@ -428,8 +458,11 @@ public void paint(final Graphics2D gc, final Rectangle plot_bounds)
gc.setColor(old_fg);
gc.setBackground(old_bg);
- gc.setFont(label_font);
- paintLabels(gc);
+ if (show_labels)
+ {
+ gc.setFont(label_font);
+ paintLabels(gc);
+ }
}
protected void paintLabels(final Graphics2D gc)