de.gsi.chart.axes.spi.AbstractAxisParameter Maven / Gradle / Ivy
package de.gsi.chart.axes.spi;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import javafx.beans.property.*;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.css.*;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Path;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.util.StringConverter;
import de.gsi.chart.Chart;
import de.gsi.chart.axes.Axis;
import de.gsi.chart.axes.AxisLabelOverlapPolicy;
import de.gsi.chart.ui.css.CssPropertyFactory;
import de.gsi.chart.ui.geometry.Side;
import de.gsi.dataset.event.AxisChangeEvent;
import de.gsi.dataset.event.EventListener;
import de.gsi.dataset.event.UpdateEvent;
import de.gsi.dataset.utils.NoDuplicatesList;
/**
* Class containing the properties, getters and setters for the AbstractNumericAxis class
*
* intention is to move the boiler-plate code here for better readability of the AbstractNumericAxis class
*
* @author rstein
*/
public abstract class AbstractAxisParameter extends Pane implements Axis {
private static final String CHART_CSS = Objects.requireNonNull(Chart.class.getResource("chart.css")).toExternalForm();
private static final CssPropertyFactory CSS = new CssPropertyFactory<>(Region.getClassCssMetaData());
protected static final int MAX_TICK_COUNT = 20;
private static final double DEFAULT_MIN_RANGE = -1.0;
private static final double DEFAULT_MAX_RANGE = +1.0;
private static final double DEFAULT_TICK_UNIT = +5d;
// N.B. number of divisions, minor tick mark is not drawn if minorTickMark
// == majorTickMark
protected static final int DEFAULT_MINOR_TICK_COUNT = 10;
private final transient AtomicBoolean autoNotification = new AtomicBoolean(true);
private final transient List updateListeners = Collections.synchronizedList(new LinkedList<>());
private final transient StyleableIntegerProperty dimIndex = CSS.createIntegerProperty(this, "dimIndex", -1, this::requestAxisLayout);
/**
* Paths used for css-type styling. Not used for actual drawing. Used as a storage container for the settings
* applied to GraphicsContext which allow much faster (and less complex) drawing routines but do no not allow
* CSS-type styling.
*/
private final transient Path majorTickStyle = new Path();
private final transient Path minorTickStyle = new Path();
private final transient AxisLabel axisLabel = new AxisLabel();
/**
* This is the minimum/maximum current data value and it is used while auto ranging. Package private solely for test purposes
*
* auto).... actual is used to compute tick marks and defined by either user or auto (ie. auto axis is always being
* computed), ALSO add maybe a zoom range (ie. limited by user-set/auto-range range)
*/
protected double oldAxisLength = -1;
protected double oldAxisMin = -Double.MAX_VALUE;
protected double oldAxisMax = -Double.MAX_VALUE;
protected double oldTickUnit = -Double.MAX_VALUE;
protected final transient BooleanProperty valid = new SimpleBooleanProperty(this, "valid", false);
protected final transient ObservableList majorTickMarkValues = FXCollections.observableArrayList(new NoDuplicatesList<>());
protected final transient ObservableList minorTickMarkValues = FXCollections.observableArrayList(new NoDuplicatesList<>());
protected final transient ObservableList majorTickMarks = FXCollections.observableArrayList(new NoDuplicatesList<>());
protected final transient ObservableList minorTickMarks = FXCollections.observableArrayList(new NoDuplicatesList<>());
/** if available (last) auto-range that has been computed */
private final transient AxisRange autoRange = new AxisRange();
/** user-specified range (ie. limits based on [lower,upper]Bound) */
private final transient AxisRange userRange = new AxisRange();
/**
* The side of the plot which this axis is being drawn on default axis orientation is BOTTOM, can be set latter to
* another side
*/
private final transient StyleableObjectProperty side = CSS.createEnumPropertyWithPseudoclasses(this, "side", Side.BOTTOM, false, Side.class, null, () -> invokeListener(new AxisChangeEvent(AbstractAxisParameter.this)));
/** The side of the plot which this axis is being drawn on */
private final transient StyleableObjectProperty overlapPolicy = CSS.createObjectProperty(this, "overlapPolicy", AxisLabelOverlapPolicy.SKIP_ALT, StyleConverter.getEnumConverter(AxisLabelOverlapPolicy.class), () -> invokeListener(new AxisChangeEvent(AbstractAxisParameter.this)));
/**
* The relative alignment (N.B. clamped to [0.0,1.0]) of the axis if drawn on top of the main canvas (N.B. side == CENTER_HOR or CENTER_VER
*/
private final transient StyleableDoubleProperty axisCenterPosition = CSS.createDoubleProperty(this, "axisCenterPosition", 0.5, true, (oldVal, newVal) -> Math.max(0.0, Math.min(newVal, 1.0)), this::requestAxisLayout);
/** axis label alignment */
private final transient StyleableObjectProperty axisLabelTextAlignment = CSS.createObjectProperty(this, "axisLabelTextAlignment", TextAlignment.CENTER, StyleConverter.getEnumConverter(TextAlignment.class), this::requestAxisLayout);
/** The axis label */
private final transient StyleableStringProperty axisName = CSS.createStringProperty(this, "axisName", "", this::requestAxisLayout);
/** true if tick marks should be displayed */
private final transient StyleableBooleanProperty tickMarkVisible = CSS.createBooleanProperty(this, "tickMarkVisible", true, this::requestAxisLayout);
/** true if tick mark labels should be displayed */
private final transient StyleableBooleanProperty tickLabelsVisible = CSS.createBooleanProperty(this, "tickLabelsVisible", true, () -> {
getTickMarks().forEach(tick -> tick.setVisible(AbstractAxisParameter.this.tickLabelsVisible.get()));
invalidate();
invokeListener(new AxisChangeEvent(this));
});
/** The length of tick mark lines */
private final transient StyleableDoubleProperty axisPadding = CSS.createDoubleProperty(this, "axisPadding", 15.0, this::requestAxisLayout);
/** The length of tick mark lines */
private final transient StyleableDoubleProperty tickLength = CSS.createDoubleProperty(this, "tickLength", 8.0, this::requestAxisLayout);
/**
* This is true when the axis determines its range from the data automatically
*/
private final transient StyleableBooleanProperty autoRanging = CSS.createBooleanProperty(this, "autoRanging", true, this::requestAxisLayout);
/** The font for all tick labels */
private final transient StyleableObjectProperty tickLabelFont = CSS.createObjectProperty(this, "tickLabelFont", Font.font("System", 8), false, StyleConverter.getFontConverter(), null, () -> {
// TODO: remove once verified that measure isn't needed anymore
final Font f = tickLabelFontProperty().get();
getTickMarks().forEach(tm -> tm.setFont(f));
invalidate();
invokeListener(new AxisChangeEvent(this));
});
/** The fill for all tick labels */
private final transient StyleableObjectProperty tickLabelFill = CSS.createObjectProperty(this, "tickLabelFill", Color.BLACK, StyleConverter.getPaintConverter());
/** The gap between tick labels and the tick mark lines */
private final transient StyleableDoubleProperty tickLabelGap = CSS.createDoubleProperty(this, "tickLabelGap", 3.0, this::requestAxisLayout);
/** The minimum gap between tick labels */
private final transient StyleableDoubleProperty tickLabelSpacing = CSS.createDoubleProperty(this, "tickLabelSpacing", 3.0, this::requestAxisLayout);
/** The gap between tick labels and the axis label */
private final transient StyleableDoubleProperty axisLabelGap = CSS.createDoubleProperty(this, "axisLabelGap", 3.0, this::requestAxisLayout);
/** The animation duration in MS */
private final transient StyleableIntegerProperty animationDuration = CSS.createIntegerProperty(this, "animationDuration", 250, this::requestAxisLayout);
/** The maximum number of ticks */
private final transient StyleableIntegerProperty maxMajorTickLabelCount = CSS.createIntegerProperty(this, "maxMajorTickLabelCount", MAX_TICK_COUNT, this::requestAxisLayout);
/**
* When true any changes to the axis and its range will be animated.
*/
private final transient BooleanProperty animated = new SimpleBooleanProperty(this, "animated", false);
/**
* Rotation in degrees of tick mark labels from their normal horizontal.
*/
protected final transient StyleableDoubleProperty tickLabelRotation = CSS.createDoubleProperty(this, "tickLabelRotation", 0.0, this::requestAxisLayout);
/** true if minor tick marks should be displayed */
private final transient StyleableBooleanProperty minorTickVisible = CSS.createBooleanProperty(this, "minorTickVisible", true, this::requestAxisLayout);
/** The scale factor from data units to visual units */
private final transient ReadOnlyDoubleWrapper scale = new ReadOnlyDoubleWrapper(this, "scale", 1);
/**
* The value for the upper bound of this axis, ie max value. This is automatically set if auto ranging is on.
*/
protected final transient DoubleProperty maxProp = new SimpleDoubleProperty(this, "upperBound", DEFAULT_MAX_RANGE) {
@Override
public void set(final double newValue) {
final double oldValue = get();
if (oldValue != newValue) {
super.set(newValue);
invokeListener(new AxisChangeEvent(AbstractAxisParameter.this));
}
}
};
/**
* The value for the lower bound of this axis, ie min value. This is automatically set if auto ranging is on.
*/
protected final transient DoubleProperty minProp = new SimpleDoubleProperty(this, "lowerBound", DEFAULT_MIN_RANGE) {
@Override
public void set(final double newValue) {
final double oldValue = get();
if (oldValue != newValue) {
super.set(newValue);
invokeListener(new AxisChangeEvent(AbstractAxisParameter.this));
}
}
};
protected double cachedOffset; // for caching
/**
* StringConverter used to format tick mark labels. If null a default will be used
*/
private final transient ObjectProperty> tickLabelFormatter = new ObjectPropertyBase<>(null) {
@Override
public Object getBean() {
return AbstractAxisParameter.this;
}
@Override
public String getName() {
return "tickLabelFormatter";
}
@Override
protected void invalidated() {
invalidate();
invokeListener(new AxisChangeEvent(AbstractAxisParameter.this));
}
};
/**
* The length of minor tick mark lines. Set to 0 to not display minor tick marks.
*/
private final transient StyleableDoubleProperty minorTickLength = CSS.createDoubleProperty(this, "minorTickLength", 5.0, this::requestAxisLayout);
/**
* The number of minor tick divisions to be displayed between each major tick mark. The number of actual minor tick
* marks will be one less than this. N.B. number of divisions, minor tick mark is not drawn if minorTickMark ==
* majorTickMark
*/
private final transient StyleableIntegerProperty minorTickCount = CSS.createIntegerProperty(this, "minorTickCount", 10, this::requestAxisLayout);
private final transient StyleableBooleanProperty autoGrowRanging = CSS.createBooleanProperty(this, "autoGrowRanging", false, this::requestAxisLayout);
protected boolean isInvertedAxis = false; // internal use (for performance reason)
private final transient BooleanProperty invertAxis = new SimpleBooleanProperty(this, "invertAxis", false) {
@Override
protected void invalidated() {
isInvertedAxis = get();
invalidate();
invokeListener(new AxisChangeEvent(AbstractAxisParameter.this));
}
};
protected boolean isTimeAxis = false; // internal use (for performance reasons)
private final transient BooleanProperty timeAxis = new SimpleBooleanProperty(this, "timeAxis", false) {
@Override
protected void invalidated() {
isTimeAxis = get();
if (isTimeAxis) {
setMinorTickCount(0);
} else {
setMinorTickCount(AbstractAxisParameter.DEFAULT_MINOR_TICK_COUNT);
}
invalidate();
invokeListener(new AxisChangeEvent(AbstractAxisParameter.this));
}
};
private final transient StyleableBooleanProperty autoRangeRounding = CSS.createBooleanProperty(this, "autoRangeRounding", false, this::requestAxisLayout);
private final transient DoubleProperty autoRangePadding = new SimpleDoubleProperty(0);
/** The axis unit label */
private final transient StyleableStringProperty axisUnit = CSS.createStringProperty(this, "axisUnit", "", () -> {
updateAxisLabelAndUnit();
requestAxisLayout();
});
/** The axis unit label */
private final transient BooleanProperty autoUnitScaling = new SimpleBooleanProperty(this, "autoUnitScaling", false) {
@Override
protected void invalidated() {
updateAxisLabelAndUnit();
invokeListener(new AxisChangeEvent(AbstractAxisParameter.this));
}
};
/** The axis unit label */
private final transient DoubleProperty unitScaling = new SimpleDoubleProperty(this, "unitScaling", 1.0) {
@Override
protected void invalidated() {
updateAxisLabelAndUnit();
invokeListener(new AxisChangeEvent(AbstractAxisParameter.this));
}
};
protected final transient StyleableDoubleProperty tickUnit = CSS.createDoubleProperty(this, "tickUnit", DEFAULT_TICK_UNIT, () -> {
if (isAutoRanging() || isAutoGrowRanging()) {
return;
}
invalidate();
invokeListener(new AxisChangeEvent(AbstractAxisParameter.this));
});
protected final transient ChangeListener super Number> scaleChangeListener = (ch, o, n) -> {
final double axisLength = getSide().isVertical() ? getHeight() : getWidth(); // [pixel]
final double lowerBound = getMin();
final double upperBound = getMax();
if (!Double.isFinite(axisLength) || !Double.isFinite(lowerBound) || !Double.isFinite(upperBound)) {
return;
}
double newScale;
final double diff = upperBound - lowerBound;
if (getSide().isVertical()) {
newScale = diff == 0 ? -axisLength : -(axisLength / diff);
cachedOffset = axisLength;
} else { // HORIZONTAL
newScale = diff == 0 ? axisLength : axisLength / diff;
cachedOffset = 0;
}
setScale(newScale == 0 ? -1.0 : newScale);
};
/**
* Create a auto-ranging AbstractAxisParameter
*/
public AbstractAxisParameter() {
super();
getStyleClass().setAll("axis");
majorTickStyle.getStyleClass().add("axis-tick-mark");
minorTickStyle.getStyleClass().add("axis-minor-tick-mark");
getChildren().addAll(axisLabel, majorTickStyle, minorTickStyle);
autoRangingProperty().addListener(ch -> {
// disable auto grow if auto range is enabled
if (isAutoRanging()) {
setAutoGrowRanging(false);
}
});
autoGrowRangingProperty().addListener(ch -> {
// disable auto grow if auto range is enabled
if (isAutoGrowRanging()) {
setAutoRanging(false);
}
});
nameProperty().addListener(e -> {
updateAxisLabelAndUnit();
invokeListener(new AxisChangeEvent(this));
});
axisLabel.textAlignmentProperty().bindBidirectional(axisLabelTextAlignmentProperty()); // NOPMD
// bind limits to user-specified axis range
// userRange.set
final ChangeListener super Number> userLimitChangeListener = (ch, o, n) -> {
getUserRange().set(getMin(), getMax());
// axis range has been set manually -> disable auto-ranging
// TODO: enable once the new scheme checks out
// setAutoRanging(false)
// setAutoGrowRanging(false)
if (!isAutoRanging() && !isAutoGrowRanging()) {
invokeListener(new AxisChangeEvent(this));
}
invalidate();
};
minProperty().addListener(userLimitChangeListener);
maxProperty().addListener(userLimitChangeListener);
majorTickStyle.applyCss();
minorTickStyle.applyCss();
axisLabel.applyCss();
minProperty().addListener(scaleChangeListener);
maxProperty().addListener(scaleChangeListener);
widthProperty().addListener(scaleChangeListener);
heightProperty().addListener(scaleChangeListener);
}
@Override
public String getUserAgentStylesheet() {
return AbstractAxisParameter.CHART_CSS;
}
/**
* to be overwritten by derived class that want to cache variables for efficiency reasons
*/
protected void updateCachedVariables() { // NOPMD by rstein function can but does not have to be overwritten
// called once new axis parameters have been established
}
@Override
public boolean add(final double value) {
if (this.contains(value)) {
return false;
}
boolean changed = false;
final boolean oldState = autoNotification().getAndSet(false);
if (value > this.getMax()) {
this.setMax(value);
changed = true;
}
if (value < this.getMin()) {
this.setMin(value);
changed = true;
}
autoNotification().set(oldState);
if (changed) {
invokeListener(new AxisChangeEvent(this));
}
return changed;
}
@Override
public boolean add(final double[] values, final int length) {
boolean changed = false;
final boolean oldState = autoNotification().getAndSet(false);
for (int i = 0; i < length; i++) {
changed |= add(values[i]);
}
autoNotification().set(oldState);
invokeListener(new AxisChangeEvent(this));
return changed;
}
public BooleanProperty animatedProperty() {
return animated;
}
public IntegerProperty animationDurationProperty() {
return animationDuration;
}
/**
* This is true when the axis determines its range from the data automatically
*
* @return #autoGrowRangingProperty property
*/
@Override
public BooleanProperty autoGrowRangingProperty() {
return autoGrowRanging;
}
@Override
public AtomicBoolean autoNotification() {
return autoNotification;
}
/**
* Fraction of the range to be applied as padding on both sides of the axis range. E.g. if set to 0.1 (10%) on axis
* with data range [10, 20], the new automatically calculated range will be [9, 21].
*
* @return autoRangePadding property
*/
public DoubleProperty autoRangePaddingProperty() {
return autoRangePadding;
}
/**
* With {@link #autoRangingProperty()} on, defines if the range should be extended to the major tick unit value. For
* example with range [3, 74] and major tick unit [5], the range will be extended to [0, 75].
*
* Default value: {@code true}
*
*
* @return autoRangeRounding property
*/
public BooleanProperty autoRangeRoundingProperty() {
return autoRangeRounding;
}
@Override
public BooleanProperty autoRangingProperty() {
return autoRanging;
}
@Override
public BooleanProperty autoUnitScalingProperty() {
return autoUnitScaling;
}
public DoubleProperty axisLabelGapProperty() {
return axisLabelGap;
}
public ObjectProperty axisLabelTextAlignmentProperty() {
return axisLabelTextAlignment;
}
public DoubleProperty axisPaddingProperty() {
return axisPadding;
}
public DoubleProperty axisCenterPositionProperty() {
return axisCenterPosition;
}
@Override
public boolean clear() {
final boolean oldState = autoNotification().getAndSet(false);
minProp.set(DEFAULT_MIN_RANGE);
maxProp.set(DEFAULT_MAX_RANGE);
autoNotification().set(oldState);
invokeListener(new AxisChangeEvent(this));
return false;
}
@Override
public boolean contains(final double value) {
return Double.isFinite(value) && (value >= getMin()) && (value <= getMax());
}
public abstract void fireInvalidated();
/**
* @return value of the {@link #animationDurationProperty} property
*/
public int getAnimationDuration() {
return animationDurationProperty().get();
}
@Override
public AxisRange getAutoRange() {
return autoRange;
}
/**
* Returns the value of the {@link #autoRangePaddingProperty()}.
*
* @return the auto range padding
*/
public double getAutoRangePadding() {
return autoRangePaddingProperty().get();
}
public double getAxisCenterPosition() {
return axisCenterPositionProperty().get();
}
/**
* @return the axisLabel
*/
public Text getAxisLabel() {
return axisLabel;
}
public double getAxisLabelGap() {
return axisLabelGapProperty().get();
}
public TextAlignment getAxisLabelTextAlignment() {
return axisLabelTextAlignmentProperty().get();
}
public double getAxisPadding() {
return axisPaddingProperty().get();
}
@Override
public List> getCssMetaData() {
return AbstractAxisParameter.getClassCssMetaData();
}
public IntegerProperty dimIndexProperty() {
return dimIndex;
}
public void setDimIndex(final int dimIndex) {
dimIndexProperty().set(dimIndex);
}
@Override
public int getDimIndex() {
return dimIndexProperty().get();
}
/**
* @return axis length in pixel
*/
@Override
public double getLength() {
if (getSide() == null) {
return Double.NaN;
}
return getSide().isHorizontal() ? getWidth() : getHeight();
}
// JavaFx Properties
/**
* @return the majorTickStyle for custom user-code based styling
*/
public Path getMajorTickStyle() {
return majorTickStyle;
}
@Override
public double getMax() {
return maxProperty().get();
}
public int getMaxMajorTickLabelCount() {
return this.maxMajorTickLabelCountProperty().get();
}
@Override
public double getMin() {
return minProperty().get();
}
@Override
public int getMinorTickCount() {
return minorTickCountProperty().get();
}
public double getMinorTickLength() {
return minorTickLengthProperty().get();
}
/**
* @return observable list containing of each minor TickMark on this axis
*/
@Override
public ObservableList getMinorTickMarks() {
return minorTickMarks;
}
/**
* @return observable list containing of each minor TickMark values on this axis
*/
public ObservableList getMinorTickMarkValues() {
return minorTickMarkValues;
}
/**
* @return the minorTickStyle for custom user-code based styling
*/
public Path getMinorTickStyle() {
return minorTickStyle;
}
@Override
public String getName() {
return nameProperty().get();
}
public AxisLabelOverlapPolicy getOverlapPolicy() {
return overlapPolicyProperty().get();
}
public double getScale() {
return scaleProperty().get();
}
@Override
public Side getSide() {
return sideProperty().get();
}
@Override
public Paint getTickLabelFill() {
return tickLabelFillProperty().get();
}
@Override
public Font getTickLabelFont() {
return tickLabelFontProperty().get();
}
@Override
public StringConverter getTickLabelFormatter() {
return tickLabelFormatterProperty().getValue();
}
@Override
public double getTickLabelGap() {
return tickLabelGapProperty().get();
}
public double getTickLabelRotation() {
return tickLabelRotationProperty().getValue();
}
@Override
public double getTickLabelSpacing() {
return tickLabelSpacingProperty().get();
}
public double getTickLength() {
return tickLengthProperty().get();
}
/**
* @return observable list containing of each major TickMark on this axis
*/
@Override
public ObservableList getTickMarks() {
return majorTickMarks;
}
/**
* @return observable list containing of each major TickMark values on this axis
*/
public ObservableList getTickMarkValues() {
return majorTickMarkValues;
}
/**
* Returns tick unit value expressed in data units.
*
* @return major tick unit value
*/
@Override
public double getTickUnit() {
return tickUnitProperty().get();
}
@Override
public String getUnit() {
return unitProperty().get();
}
@Override
public double getUnitScaling() {
return unitScalingProperty().get();
}
@Override
public AxisRange getUserRange() {
return userRange;
}
/**
* Mark the current axis invalid, this will cause anything that depends on the axis range or physical size to be
* recalculated on the next
* layout iteration.
*/
public void invalidate() {
validProperty().set(false);
}
/**
* This is {@code true} when the axis labels and data point order should be inverted
*
* @param value {@code true} if axis shall be inverted (i.e. drawn from 'max->min', rather than the normal
* 'min->max')
*/
@Override
public void invertAxis(final boolean value) {
invertAxisProperty().set(value);
}
/**
* This is {@code true} when the axis labels and data point order should be inverted
*
* @return property
*/
@Override
public BooleanProperty invertAxisProperty() {
return invertAxis;
}
/**
* invoke object within update listener list
*
* @param updateEvent the event the listeners are notified with
* @param executeParallel {@code true} execute event listener via parallel executor service
*/
@Override
public void invokeListener(final UpdateEvent updateEvent, final boolean executeParallel) {
synchronized (autoNotification()) {
if (!autoNotification().get()) {
// avoids duplicate update events
return;
}
}
requestAxisLayout();
Axis.super.invokeListener(updateEvent, executeParallel);
}
/**
* @return value of {@link #animatedProperty} property
*/
public boolean isAnimated() {
return animatedProperty().get();
}
/**
* This is true when the axis determines its range from the data automatically and grows it if necessary
*
* @return value of the {@link #autoGrowRangingProperty} property
*/
@Override
public boolean isAutoGrowRanging() {
return autoGrowRangingProperty().get();
}
/**
* Returns the value of the {@link #autoRangeRoundingProperty()}.
*
* @return the auto range rounding flag
*/
public boolean isAutoRangeRounding() {
return autoRangeRoundingProperty().get();
}
@Override
public boolean isAutoRanging() {
return autoRangingProperty().get();
}
@Override
public boolean isAutoUnitScaling() {
return autoUnitScalingProperty().get();
}
@Override
public boolean isDefined() {
return Double.isFinite(getMin()) && Double.isFinite(getMax());
}
/**
* This is {@code true} when the axis labels and data point order should be inverted
*
* @return {@code true} if axis shall be inverted (i.e. drawn from 'max->min', rather than the normal
* 'min->max')
*/
@Override
public boolean isInvertedAxis() {
return invertAxisProperty().get();
}
public boolean isMinorTickVisible() {
return minorTickVisibleProperty().get();
}
public boolean isTickLabelsVisible() {
return tickLabelsVisibleProperty().get();
}
public boolean isTickMarkVisible() {
return tickMarkVisibleProperty().get();
}
/**
* This is true when the axis corresponds to a time-axis
*
* @return true if axis is a time scale
*/
@Override
public boolean isTimeAxis() {
return timeAxisProperty().get();
}
/**
* @return true if current axis range and physical size calculations are valid
*/
public boolean isValid() {
return validProperty().get();
}
public IntegerProperty maxMajorTickLabelCountProperty() {
return maxMajorTickLabelCount;
}
@Override
public DoubleProperty maxProperty() {
return maxProp;
}
public IntegerProperty minorTickCountProperty() {
return minorTickCount;
}
public DoubleProperty minorTickLengthProperty() {
return minorTickLength;
}
public BooleanProperty minorTickVisibleProperty() {
return minorTickVisible;
}
@Override
public DoubleProperty minProperty() {
return minProp;
}
@Override
public StringProperty nameProperty() {
return axisName;
}
public ObjectProperty overlapPolicyProperty() {
return overlapPolicy;
}
public ReadOnlyDoubleProperty scaleProperty() {
return scale.getReadOnlyProperty();
}
@Override
public boolean set(final double min, final double max) {
final double oldMin = minProp.get();
final double oldMax = maxProp.get();
final boolean oldState = autoNotification().getAndSet(false);
minProp.set(min);
maxProp.set(max);
autoNotification().set(oldState);
final boolean changed = (oldMin != min) || (oldMax != max);
if (changed) {
invalidate();
invokeListener(new AxisChangeEvent(this));
}
return changed;
}
@Override
public boolean set(final String axisName, final String... axisUnit) {
boolean changed = false;
final boolean oldState = autoNotification().getAndSet(false);
if (!equalString(axisName, getName())) {
setName(axisName);
changed = true;
}
if ((axisUnit != null) && (axisUnit.length > 0) && !equalString(axisUnit[0], getUnit())) {
setUnit(axisUnit[0]);
changed = true;
}
autoNotification().set(oldState);
if (changed) {
invokeListener(new AxisChangeEvent(this));
}
return changed;
}
@Override
public boolean set(final String axisName, final String axisUnit, final double rangeMin, final double rangeMax) {
final boolean oldState = autoNotification().getAndSet(false);
boolean changed = this.set(axisName, axisUnit);
changed |= this.set(rangeMin, rangeMax);
autoNotification().set(oldState);
if (changed) {
invokeListener(new AxisChangeEvent(this));
}
return changed;
}
/**
* Sets {@link #animatedProperty} property
*/
@Override
public void setAnimated(final boolean value) {
animatedProperty().set(value);
}
/**
* Sets the value of the {@link #animationDurationProperty} property
*
* @param value animation duration in milliseconds
*/
public void setAnimationDuration(final int value) {
animationDurationProperty().set(value);
}
/**
* Sets the value of the {@link #autoGrowRangingProperty} property
*
* @param state true if axis shall be updated to the optimal data range and grows it if necessary
*/
@Override
public void setAutoGrowRanging(final boolean state) {
// TODO: setter should probably be dumb and state changes should happen in property's invalidate
if (state) {
setAutoRanging(false);
invalidate();
requestAxisLayout();
}
autoGrowRangingProperty().set(state);
}
/**
* Sets the value of the {@link #autoRangePaddingProperty()}
*
* @param padding padding factor
*/
public void setAutoRangePadding(final double padding) {
autoRangePaddingProperty().set(padding);
}
/**
* Sets the value of the {@link #autoRangeRoundingProperty()}
*
* @param round if {@code true}, lower and upper bound will be adjusted to the tick unit value
*/
public void setAutoRangeRounding(final boolean round) {
autoRangeRoundingProperty().set(round);
}
@Override
public void setAutoRanging(final boolean value) {
autoRangingProperty().set(value);
}
@Override
public void setAutoUnitScaling(final boolean value) {
autoUnitScalingProperty().set(value);
}
public void setAxisCenterPosition(final double value) {
axisCenterPositionProperty().set(value);
}
public void setAxisLabelGap(final double value) {
axisLabelGapProperty().set(value);
}
public void setAxisLabelTextAlignment(final TextAlignment value) {
axisLabelTextAlignmentProperty().set(value);
}
public void setAxisPadding(final double value) {
axisPaddingProperty().set(value);
}
@Override
public boolean setMax(final double value) {
final double oldvalue = maxProperty().get();
maxProperty().set(value);
return oldvalue != value;
}
public void setMaxMajorTickLabelCount(final int value) {
this.maxMajorTickLabelCountProperty().set(value);
}
@Override
public boolean setMin(final double value) {
final double oldvalue = minProperty().get();
minProperty().set(value);
return oldvalue != value;
}
public void setMinorTickCount(final int value) {
minorTickCountProperty().set(value);
}
public void setMinorTickLength(final double value) {
minorTickLengthProperty().set(value);
}
public void setMinorTickVisible(final boolean value) {
minorTickVisibleProperty().set(value);
}
@Override
public void setName(final String value) {
nameProperty().set(value);
}
public void setOverlapPolicy(final AxisLabelOverlapPolicy value) {
overlapPolicyProperty().set(value);
}
@Override
public void setSide(final Side value) {
sideProperty().set(value);
}
public void setTickLabelFill(final Paint value) {
tickLabelFillProperty().set(value);
}
public void setTickLabelFont(final Font value) {
tickLabelFontProperty().set(value);
}
public void setTickLabelFormatter(final StringConverter value) {
tickLabelFormatterProperty().setValue(value);
}
public void setTickLabelGap(final double value) {
tickLabelGapProperty().set(value);
}
public void setTickLabelRotation(final double value) {
tickLabelRotationProperty().setValue(value);
}
public void setTickLabelSpacing(final double value) {
tickLabelSpacingProperty().set(value);
}
public void setTickLabelsVisible(final boolean value) {
tickLabelsVisibleProperty().set(value);
}
public void setTickLength(final double value) {
tickLengthProperty().set(value);
}
public void setTickMarkVisible(final boolean value) {
tickMarkVisibleProperty().set(value);
}
/**
* Sets the value of the {@link #tickUnitProperty()}.
*
* @param unit major tick unit
*/
@Override
public void setTickUnit(final double unit) {
tickUnitProperty().set(unit);
}
/**
* This is {@code true} when the axis labels and data point should be plotted according to some time-axis definition
*
* @param value {@code true} if axis shall be drawn with time-axis labels
*/
@Override
public void setTimeAxis(final boolean value) {
timeAxisProperty().set(value);
}
@Override
public void setUnit(final String value) {
unitProperty().set(value);
}
@Override
public void setUnitScaling(final double value) {
if (!Double.isFinite(value) || (value == 0)) {
throw new IllegalArgumentException("provided number is not finite and/or zero: " + value);
}
setTickUnit(value);
unitScalingProperty().set(value);
}
@Override
public void setUnitScaling(final MetricPrefix prefix) {
unitScalingProperty().set(prefix.getPower());
}
@Override
public ObjectProperty sideProperty() {
return side;
}
public ObjectProperty tickLabelFillProperty() {
return tickLabelFill;
}
public ObjectProperty tickLabelFontProperty() {
return tickLabelFont;
}
public ObjectProperty> tickLabelFormatterProperty() {
return tickLabelFormatter;
}
public DoubleProperty tickLabelGapProperty() {
return tickLabelGap;
}
public DoubleProperty tickLabelRotationProperty() {
return tickLabelRotation;
}
public DoubleProperty tickLabelSpacingProperty() {
return tickLabelSpacing;
}
public BooleanProperty tickLabelsVisibleProperty() {
return tickLabelsVisible;
}
public DoubleProperty tickLengthProperty() {
return tickLength;
}
public BooleanProperty tickMarkVisibleProperty() {
return tickMarkVisible;
}
/**
* The value between each major tick mark in data units. This is automatically set if we are auto-ranging.
*
* @return tickUnit property
*/
@Override
public DoubleProperty tickUnitProperty() {
return tickUnit;
}
/**
* This is {@code true} when the axis labels and data point should be plotted according to some time-axis definition
*
* @return time axis property reference
*/
@Override
public BooleanProperty timeAxisProperty() {
return timeAxis;
}
@Override
public StringProperty unitProperty() {
return axisUnit;
}
@Override
public DoubleProperty unitScalingProperty() {
return unitScaling;
}
@Override
public List updateEventListener() {
return updateListeners;
}
protected void setScale(final double scale) {
scalePropertyImpl().set(scale);
}
protected void updateAxisLabelAndUnit() {
final String axisPrimaryLabel = getName();
String localAxisUnit = getUnit();
final boolean isAutoScaling = isAutoUnitScaling();
if (isAutoScaling) {
updateScaleAndUnitPrefix();
}
final String axisPrefix = MetricPrefix.getShortPrefix(getUnitScaling());
if ((localAxisUnit == null || localAxisUnit.isBlank()) && !axisPrefix.isBlank()) {
localAxisUnit = "";
}
if (localAxisUnit == null) {
getAxisLabel().setText(axisPrimaryLabel);
} else {
getAxisLabel().setText(axisPrimaryLabel + " [" + axisPrefix + localAxisUnit + "]");
}
invalidate();
}
protected void updateScaleAndUnitPrefix() {
final double range = Math.abs(getMax() - getMin());
final double logRange = Math.log10(range);
final double power3Upper = 3.0 * Math.ceil(logRange / 3.0);
final double power3Lower = 3.0 * Math.floor(logRange / 3.0);
final double a = Math.min(power3Upper, power3Lower);
final double power = Math.pow(10, a);
final double oldPower = getUnitScaling();
if ((power != oldPower) && (power != 0) && (Double.isFinite(power))) {
this.setUnitScaling(power);
}
setTickUnit(range / getMinorTickCount());
}
/**
* valid flag property.
* This will cause anything that depends on the axis range or physical size to be recalculated on the next layout
* iteration.
*
* @return the validProperty()
*/
protected BooleanProperty validProperty() {
return valid;
}
ReadOnlyDoubleWrapper scalePropertyImpl() {
return scale;
}
/**
* @return The CssMetaData associated with this class, which may include the CssMetaData of its super classes.
*/
public static List> getClassCssMetaData() {
return CSS.getCssMetaData();
}
protected static boolean equalString(final String str1, final String str2) {
return Objects.equals(str1, str2);
}
}