tickMarks) {
}
+ /**
+ * Hides labels that can't be fully displayed due to the
+ * bounds of the parent container. This often manifests
+ * on the top of y axes when there is no title padding.
+ *
+ * Note that the layout bounds may not be available
+ * until the bounds phase, so we use layout x/y directly.
+ */
+ private void hideLabelsOutsideParentBounds(List tickMarks) {
+ if (getParent() == null || !(getParent() instanceof Pane)) {
+ return;
+ }
+ if (getSide().isHorizontal()) {
+ final double offset = getLayoutX();
+ final double parentWidth = ((Pane) getParent()).getWidth();
+ for (TickMark tickMark : tickMarks) {
+ double width = tickMark.getWidth();
+ double min = tickMark.getPosition() - width / 2 + offset;
+ double max = tickMark.getPosition() + width / 2 + offset;
+ if (min < 0 || max > parentWidth) {
+ tickMark.setVisible(false);
+ }
+ }
+ } else {
+ final double offset = getLayoutY();
+ final double parentHeight = ((Pane) getParent()).getHeight();
+ for (TickMark tickMark : tickMarks) {
+ double height = tickMark.getHeight();
+ double min = tickMark.getPosition() - height / 2 + offset;
+ double max = tickMark.getPosition() + height / 2 + offset;
+ if (min < 0 || max > parentHeight) {
+ tickMark.setVisible(false);
+ }
+ }
+ }
+ }
+
@Deprecated // for testing purposes
List computeTickMarks(AxisRange range, boolean major) {
if (major) {
@@ -685,6 +722,10 @@ protected void updateTickMarkPositions(List tickMarks) {
protected void drawAxisLabel(final GraphicsContext gc, final double axisWidth, final double axisHeight,
final TextStyle axisLabel, final double tickLength) {
+ if (!drawAxisLabel) {
+ return;
+ }
+
// relative positioning of the label based on the text alignment
// TODO: why tickLabelGap instead of axisLabelGap?
final double labelPosition;
@@ -913,7 +954,7 @@ public void drawAxis() {
}
- protected double measureTickMarkLength(final Double major) {
+ protected double measureTickMarkLength(final double major) {
// N.B. this is a known performance hot-spot -> start optimisation here
tmpTickMark.setValue(major, getTickMarkLabel(major));
return getSide().isHorizontal() ? tmpTickMark.getWidth() : tmpTickMark.getHeight();
diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java
index edb902a72..37a09b5bf 100644
--- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java
+++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java
@@ -3,9 +3,7 @@
import java.util.List;
import java.util.Objects;
-import io.fair_acc.chartfx.ui.css.LineStyle;
-import io.fair_acc.chartfx.ui.css.StyleUtil;
-import io.fair_acc.chartfx.ui.css.TextStyle;
+import io.fair_acc.chartfx.ui.css.*;
import io.fair_acc.chartfx.ui.layout.ChartPane;
import io.fair_acc.chartfx.utils.PropUtil;
import io.fair_acc.dataset.events.BitState;
@@ -42,15 +40,6 @@ public abstract class AbstractAxisParameter extends Pane implements Axis {
* Create a auto-ranging AbstractAxisParameter
*/
public AbstractAxisParameter() {
- super();
- // Styles changes that can be removed after moving the styling to sub-nodes
- tickLabelStyle.rotateProperty().bindBidirectional(tickLabelRotation);
- tickLabelStyle.fontProperty().bindBidirectional(tickLabelFont);
- tickLabelStyle.fillProperty().bindBidirectional(tickLabelFill);
- axisLabel.textAlignmentProperty().bindBidirectional(axisLabelTextAlignmentProperty()); // NOPMD
-
- axisPadding.addListener(state.onPropChange(ChartBits.AxisLayout)::set);
-
// Properties that may be relevant to the layout and must always at least redraw the canvas
PropUtil.runOnChange(invalidateLayout = state.onAction(ChartBits.AxisLayout, ChartBits.AxisCanvas),
// distance to main line
@@ -59,17 +48,13 @@ public AbstractAxisParameter() {
tickMarkGap,
// tick marks
- tickMarkVisible,
tickLength,
majorTickStyle.changeCounterProperty(),
// tick labels
- tickLabelsVisible,
tickLabelGap,
- tickLabelRotation,
overlapPolicy,
tickLabelStyle.changeCounterProperty(),
- tickLabelFont, // already in style
// axis label
axisLabelGap,
@@ -112,14 +97,12 @@ public AbstractAxisParameter() {
// Properties that require a redraw of the canvas but won't affect the placement of ticks
PropUtil.runOnChange(invalidateCanvas = state.onAction(ChartBits.AxisCanvas),
// minor ticks
- minorTickVisible,
minorTickStyle.changeCounterProperty(), // not used for layout calculation
minorTickCount,
minorTickLength,
// item placement
axisCenterPosition,
- axisLabelTextAlignment,
// the main properties of what the axis currently shows.
// Used for internal computation, so we don't want them to
@@ -189,15 +172,11 @@ public AbstractAxisParameter() {
* Note that we can use the tick label style as a temporary node for getting the font metrics w/ the correct style.
* Unmanaged nodes do not trigger a re-layout of the parent, but invisible text still computes valid font metrics.
*/
- private final transient LineStyle majorTickStyle = new LineStyle("axis-tick-mark");
- private final transient LineStyle minorTickStyle = new LineStyle("axis-minor-tick-mark");
- private final transient TextStyle tickLabelStyle = new TextStyle("axis-tick-label");
- private final transient TextStyle axisLabel = new TextStyle("axis-label");
-
- {
- StyleUtil.addStyles(this, "axis");
- getChildren().addAll(axisLabel, tickLabelStyle, majorTickStyle, minorTickStyle);
- }
+ private final transient StyleGroup styleGroup = new StyleGroup(this, "axis");
+ private final transient LineStyle majorTickStyle = styleGroup.newLineStyle("axis-major-tick-mark");
+ private final transient LineStyle minorTickStyle = styleGroup.newLineStyle("axis-minor-tick-mark");
+ private final transient TextStyle tickLabelStyle = styleGroup.newTextStyle("axis-tick-label");
+ private final transient TextStyle axisLabel = styleGroup.newTextStyle("axis-label");
protected final transient DoubleArrayList majorTickMarkValues = new DoubleArrayList();
protected final transient DoubleArrayList minorTickMarkValues = new DoubleArrayList();
@@ -218,8 +197,7 @@ public AbstractAxisParameter() {
* 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,
- () -> ChartPane.setSide(this, getSide()));
+ private final transient StyleableObjectProperty side = CSS.createSideProperty(this, Side.BOTTOM);
/**
* The side of the plot which this axis is being drawn on
@@ -231,29 +209,14 @@ public AbstractAxisParameter() {
*/
private final transient StyleableDoubleProperty axisCenterPosition = CSS.createDoubleProperty(this, "axisCenterPosition", 0.5, true, (oldVal, newVal) -> Math.max(0.0, Math.min(newVal, 1.0)));
- /**
- * axis label alignment
- */
- private final transient StyleableObjectProperty axisLabelTextAlignment = CSS.createObjectProperty(this, "axisLabelTextAlignment", TextAlignment.CENTER, StyleConverter.getEnumConverter(TextAlignment.class));
-
/**
* The axis label
*/
private final transient StyleableStringProperty axisName = CSS.createStringProperty(this, "axisName", "");
- /**
- * true if tick marks should be displayed
- */
- private final transient StyleableBooleanProperty tickMarkVisible = CSS.createBooleanProperty(this, "tickMarkVisible", true);
-
- /**
- * true if tick mark labels should be displayed
- */
- private final transient StyleableBooleanProperty tickLabelsVisible = CSS.createBooleanProperty(this, "tickLabelsVisible", true);
-
/**
* The length of tick mark lines
- */
+ */
private final transient StyleableDoubleProperty axisPadding = CSS.createDoubleProperty(this, "axisPadding", 15.0);
/**
@@ -276,16 +239,6 @@ public AbstractAxisParameter() {
}
});
- /**
- * The font for all tick labels
- */
- private final transient StyleableObjectProperty tickLabelFont = CSS.createObjectProperty(this, "tickLabelFont", Font.font("System", 8), false, StyleConverter.getFontConverter(), null);
-
- /**
- * The fill for all tick labels
- */
- private final transient StyleableObjectProperty tickLabelFill = CSS.createObjectProperty(this, "tickLabelFill", Color.BLACK, StyleConverter.getPaintConverter());
-
/**
* The gap between tick marks and the canvas area
*/
@@ -321,16 +274,6 @@ public AbstractAxisParameter() {
*/
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);
-
- /**
- * true if minor tick marks should be displayed
- */
- private final transient StyleableBooleanProperty minorTickVisible = CSS.createBooleanProperty(this, "minorTickVisible", true);
-
/**
* The scale factor from data units to visual units
*/
@@ -371,7 +314,7 @@ public AbstractAxisParameter() {
private final transient StyleableIntegerProperty minorTickCount = CSS.createIntegerProperty(this, "minorTickCount", 10);
protected boolean isInvertedAxis = false; // internal use (for performance reason)
- private final transient BooleanProperty invertAxis = PropUtil.createBooleanProperty(this, "invertAxis", isInvertedAxis, () -> {
+ private final transient StyleableBooleanProperty invertAxis = CSS.createBooleanProperty(this, "invertAxis", isInvertedAxis, () -> {
isInvertedAxis = invertAxisProperty().get();
});
@@ -387,7 +330,7 @@ public AbstractAxisParameter() {
private final transient StyleableBooleanProperty autoRangeRounding = CSS.createBooleanProperty(this, "autoRangeRounding", false);
- private final transient DoubleProperty autoRangePadding = PropUtil.createDoubleProperty(this, "autoRangePadding", 0);
+ private final transient StyleableDoubleProperty autoRangePadding = CSS.createDoubleProperty(this, "autoRangePadding", 0);
/**
* The axis unit label
@@ -397,7 +340,7 @@ public AbstractAxisParameter() {
/**
* The axis unit label
*/
- private final transient BooleanProperty autoUnitScaling = PropUtil.createBooleanProperty(this, "autoUnitScaling", false);
+ private final transient StyleableBooleanProperty autoUnitScaling = CSS.createBooleanProperty(this, "autoUnitScaling", false);
/**
* The axis unit label
@@ -504,10 +447,6 @@ public DoubleProperty axisLabelGapProperty() {
return axisLabelGap;
}
- public ObjectProperty axisLabelTextAlignmentProperty() {
- return axisLabelTextAlignment;
- }
-
public DoubleProperty axisPaddingProperty() {
return axisPadding;
}
@@ -564,10 +503,6 @@ public double getAxisLabelGap() {
return axisLabelGapProperty().get();
}
- public TextAlignment getAxisLabelTextAlignment() {
- return axisLabelTextAlignmentProperty().get();
- }
-
public double getAxisPadding() {
return axisPaddingProperty().get();
}
@@ -677,17 +612,6 @@ 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();
}
@@ -702,10 +626,6 @@ public double getTickLabelGap() {
return tickLabelGapProperty().get();
}
- public double getTickLabelRotation() {
- return tickLabelRotationProperty().getValue();
- }
-
@Override
public double getTickLabelSpacing() {
return tickLabelSpacingProperty().get();
@@ -828,18 +748,6 @@ 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
*
@@ -869,10 +777,6 @@ public DoubleProperty minorTickLengthProperty() {
return minorTickLength;
}
- public BooleanProperty minorTickVisibleProperty() {
- return minorTickVisible;
- }
-
/**
* The value between each major tick mark in data units. This is automatically set if we are auto-ranging.
*
@@ -992,10 +896,6 @@ 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);
}
@@ -1035,10 +935,6 @@ 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);
@@ -1053,14 +949,6 @@ 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);
}
@@ -1073,26 +961,14 @@ 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);
- }
-
/**
* This is {@code true} when the axis labels and data point should be plotted according to some time-axis definition
*
@@ -1126,14 +1002,6 @@ public ObjectProperty sideProperty() {
return side;
}
- public ObjectProperty tickLabelFillProperty() {
- return tickLabelFill;
- }
-
- public ObjectProperty tickLabelFontProperty() {
- return tickLabelFont;
- }
-
public ObjectProperty> tickLabelFormatterProperty() {
return tickLabelFormatter;
}
@@ -1146,26 +1014,14 @@ 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;
- }
-
/**
* This is {@code true} when the axis labels and data point should be plotted according to some time-axis definition
*
@@ -1186,6 +1042,26 @@ public DoubleProperty unitScalingProperty() {
return unitScaling;
}
+ protected boolean isMinorTickVisible() {
+ return minorTickStyle.isVisible();
+ }
+
+ protected boolean isTickLabelsVisible() {
+ return tickLabelStyle.isVisible();
+ }
+
+ protected boolean isTickMarkVisible() {
+ return majorTickStyle.isVisible();
+ }
+
+ protected double getTickLabelRotation() {
+ return tickLabelStyle.getRotate();
+ }
+
+ protected Font getTickLabelFont() {
+ return getTickLabelStyle().getFont();
+ }
+
protected void setScale(final double scale) {
scalePropertyImpl().set(scale);
}
diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/Legend.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/Legend.java
index bdea8512a..e2fedf5ac 100644
--- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/Legend.java
+++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/Legend.java
@@ -3,6 +3,7 @@
import java.util.List;
import io.fair_acc.chartfx.renderer.Renderer;
+import io.fair_acc.chartfx.ui.geometry.Side;
import io.fair_acc.dataset.DataSet;
import javafx.scene.Node;
@@ -10,6 +11,10 @@ public interface Legend {
Node getNode();
+ Side getSide();
+
+ void setSide(Side side);
+
boolean isVertical();
void setVertical(boolean value);
diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/spi/DefaultLegend.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/spi/DefaultLegend.java
index 591c0a34a..618c2d3d5 100644
--- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/spi/DefaultLegend.java
+++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/spi/DefaultLegend.java
@@ -1,20 +1,22 @@
package io.fair_acc.chartfx.legend.spi;
-import java.lang.ref.PhantomReference;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
+import io.fair_acc.chartfx.ui.css.CssPropertyFactory;
+import io.fair_acc.chartfx.ui.css.StyleUtil;
+import io.fair_acc.chartfx.ui.geometry.Side;
+import io.fair_acc.chartfx.utils.FXUtils;
+import io.fair_acc.chartfx.utils.PropUtil;
import io.fair_acc.dataset.events.ChartBits;
import io.fair_acc.dataset.events.StateListener;
+import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.SimpleBooleanProperty;
-import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
-import javafx.css.PseudoClass;
+import javafx.css.*;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Node;
@@ -28,9 +30,6 @@
import io.fair_acc.chartfx.renderer.Renderer;
import io.fair_acc.chartfx.utils.StyleParser;
import io.fair_acc.dataset.DataSet;
-import io.fair_acc.dataset.event.EventListener;
-import io.fair_acc.dataset.event.UpdateEvent;
-import io.fair_acc.dataset.event.UpdatedMetaDataEvent;
/**
* A chart legend that displays a list of items with symbols in a box
@@ -38,64 +37,32 @@
* @author rstein
*/
public class DefaultLegend extends FlowPane implements Legend {
- // TODO: transform static integers to styleable property fields
- private static final int GAP = 5;
- private static final int SYMBOL_WIDTH = 20;
- private static final int SYMBOL_HEIGHT = 20;
private static final PseudoClass disabledClass = PseudoClass.getPseudoClass("disabled");
- // -------------- PRIVATE FIELDS ------------------------------------------
-
- private final ListChangeListener itemsListener = c -> {
- getChildren().setAll(getItems());
- if (isVisible()) {
- requestLayout();
- }
- };
-
// -------------- PUBLIC PROPERTIES ----------------------------------------
- /**
- * The legend items should be laid out vertically in columns rather than horizontally in rows
- */
- private final BooleanProperty vertical = new SimpleBooleanProperty(this, "vertical", false) {
- @Override
- protected void invalidated() {
- setOrientation(get() ? Orientation.VERTICAL : Orientation.HORIZONTAL);
- }
- };
+
+ StyleableObjectProperty side = CSS.createSideProperty(this, Side.BOTTOM);
+ StyleableDoubleProperty symbolWidth = CSS.createDoubleProperty(this, "symbolWidth", 20);
+ StyleableDoubleProperty symbolHeight = CSS.createDoubleProperty(this, "symbolHeight", 20);
/**
* The legend items to display in this legend
*/
- private final ObjectProperty> items = new SimpleObjectProperty<>(
- this, "items") {
- private ObservableList oldItems = null;
-
- @Override
- protected void invalidated() {
- if (oldItems != null) {
- oldItems.removeListener(itemsListener);
- }
-
- final ObservableList newItems = get();
- if (newItems == null) {
- getChildren().clear();
- } else {
- newItems.addListener(itemsListener);
- getChildren().setAll(newItems);
- }
- oldItems = get();
- if (isVisible()) {
- requestLayout();
- }
- }
- };
+ private final ObservableList items = FXCollections.observableArrayList();
public DefaultLegend() {
- super(GAP, GAP);
- setItems(FXCollections.observableArrayList());
- getStyleClass().setAll("chart-legend");
- setAlignment(Pos.CENTER);
+ StyleUtil.addStyles(this, "chart-legend");
+ items.addListener((ListChangeListener) c -> getChildren().setAll(items));
+ PropUtil.runOnChange(this::applyCss, sideProperty());
+
+ // TODO:
+ // (1) The legend does not have a reference to the chart, so for now do a hack
+ // and try to get it out of the hierarchy.
+ // (2) The items are currently created with a fixed size before the styling phase,
+ // so live-updates w/ CSSFX dont work properly without re-instantiating the chart.
+ PropUtil.runOnChange(() -> FXUtils.tryGetChartParent(this)
+ .ifPresent(chart -> chart.fireInvalidated(ChartBits.ChartLegend)),
+ symbolHeight, symbolWidth);
}
@Override
@@ -111,11 +78,18 @@ protected double computePrefWidth(final double forHeight) {
}
public final ObservableList getItems() {
- return items.get();
+ return items;
+ }
+
+ public final void setItems(List items) {
+ // TODO: remove after changing unit tests
+ this.items.setAll(items);
}
public LegendItem getNewLegendItem(final Renderer renderer, final DataSet series, final int seriesIndex) {
- final Canvas symbol = renderer.drawLegendSymbol(series, seriesIndex, SYMBOL_WIDTH, SYMBOL_HEIGHT);
+ final Canvas symbol = renderer.drawLegendSymbol(series, seriesIndex,
+ (int) Math.round(getSymbolWidth()),
+ (int) Math.round(getSymbolHeight()));
var item = new LegendItem(series.getName(), symbol);
item.setOnMouseClicked(event -> series.setVisible(!series.isVisible()));
Runnable updateCss = () -> item.pseudoClassStateChanged(disabledClass, !series.isVisible());
@@ -143,15 +117,7 @@ public Node getNode() {
*/
@Override
public final boolean isVertical() {
- return verticalProperty().get();
- }
-
- public final ObjectProperty> itemsProperty() {
- return items;
- }
-
- public final void setItems(final ObservableList value) {
- itemsProperty().set(value);
+ return getOrientation() == Orientation.VERTICAL;
}
/*
@@ -160,8 +126,8 @@ public final void setItems(final ObservableList value) {
* @see io.fair_acc.chartfx.legend.Legend#setVertical(boolean)
*/
@Override
- public final void setVertical(final boolean value) {
- verticalProperty().set(value);
+ public final void setVertical(final boolean vertical) {
+ setOrientation(vertical ? Orientation.VERTICAL : Orientation.HORIZONTAL);
}
/*
@@ -239,19 +205,60 @@ public void updateLegend(final List dataSets, final List rend
}
}
- public final BooleanProperty verticalProperty() {
- return vertical;
+ public Side getSide() {
+ return side.get();
+ }
+
+ public StyleableObjectProperty sideProperty() {
+ return side;
+ }
+
+ public void setSide(Side side) {
+ this.side.set(side);
+ }
+
+ public double getSymbolWidth() {
+ return symbolWidth.get();
+ }
+
+ public StyleableDoubleProperty symbolWidthProperty() {
+ return symbolWidth;
+ }
+
+ public void setSymbolWidth(double symbolWidth) {
+ this.symbolWidth.set(symbolWidth);
+ }
+
+ public double getSymbolHeight() {
+ return symbolHeight.get();
}
+ public StyleableDoubleProperty symbolHeightProperty() {
+ return symbolHeight;
+ }
+
+ public void setSymbolHeight(double symbolHeight) {
+ this.symbolHeight.set(symbolHeight);
+ }
+
+ @Override
+ public List> getCssMetaData() {
+ return getClassCssMetaData();
+ }
+
+ public static List> getClassCssMetaData() {
+ return CSS.getCssMetaData();
+ }
+
+ private static final CssPropertyFactory CSS = new CssPropertyFactory<>(FlowPane.getClassCssMetaData());
+
/**
* A item to be displayed on a Legend
*/
public static class LegendItem extends Label {
public LegendItem(final String text, final Node symbol) {
+ StyleUtil.addStyles(this, "chart-legend-item");
setText(text);
- getStyleClass().add("chart-legend-item");
- setAlignment(Pos.CENTER_LEFT);
- setContentDisplay(ContentDisplay.LEFT);
setSymbol(symbol);
}
diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractContourDataSetRendererParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractContourDataSetRendererParameter.java
index d03d698f7..ae5ddbf55 100644
--- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractContourDataSetRendererParameter.java
+++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractContourDataSetRendererParameter.java
@@ -13,7 +13,7 @@
import io.fair_acc.chartfx.renderer.spi.utils.ColorGradient;
public abstract class AbstractContourDataSetRendererParameter>
- extends AbstractPointReductionManagment {
+ extends AbstractPointReducingRenderer {
private final BooleanProperty altImplementation = new SimpleBooleanProperty(this, "altImplementation", false);
private final IntegerProperty reductionFactorX = new SimpleIntegerProperty(this, "reductionFactorX", 2);
private final IntegerProperty reductionFactorY = new SimpleIntegerProperty(this, "reductionFactorY", 2);
diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractErrorDataSetRendererParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractErrorDataSetRendererParameter.java
index 9e9c0010b..17a55cb4c 100644
--- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractErrorDataSetRendererParameter.java
+++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractErrorDataSetRendererParameter.java
@@ -1,8 +1,12 @@
package io.fair_acc.chartfx.renderer.spi;
+import java.util.List;
import java.util.Objects;
+import io.fair_acc.chartfx.ui.css.CssPropertyFactory;
+import io.fair_acc.chartfx.ui.css.StyleUtil;
import io.fair_acc.chartfx.utils.PropUtil;
+import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
@@ -22,6 +26,8 @@
import io.fair_acc.chartfx.renderer.datareduction.RamanDouglasPeukerDataReducer;
import io.fair_acc.chartfx.renderer.datareduction.VisvalingamMaheswariWhyattDataReducer;
import io.fair_acc.dataset.utils.AssertUtils;
+import javafx.css.CssMetaData;
+import javafx.css.Styleable;
/**
* simple class to move the various parameters out of the class containing the algorithms uses the shadow field pattern
@@ -33,36 +39,37 @@
@SuppressWarnings({ "PMD.TooManyMethods", "PMD.TooManyFields", "PMD.ExcessivePublicCount" }) // designated purpose of
// this class
public abstract class AbstractErrorDataSetRendererParameter>
- extends AbstractPointReductionManagment {
+ extends AbstractPointReducingRenderer {
// intensity fading factor per stage
protected static final double DEFAULT_HISTORY_INTENSITY_FADING = 0.65;
- private final ObjectProperty errorStyle = new SimpleObjectProperty<>(this, "errorStyle",
- ErrorStyle.ERRORCOMBO);
+ private final ObjectProperty errorStyle = css().createEnumPropertyWithPseudoclasses(this, "errorStyle",
+ ErrorStyle.ERRORCOMBO, ErrorStyle.class);
private final ObjectProperty rendererDataReducer = new SimpleObjectProperty<>(this,
"rendererDataReducer", new DefaultDataReducer());
- private final IntegerProperty dashSize = new SimpleIntegerProperty(this, "dashSize", 3);
- private final DoubleProperty markerSize = new SimpleDoubleProperty(this, "markerSize", 1.5);
- private final BooleanProperty drawMarker = new SimpleBooleanProperty(this, "drawMarker", true);
- private final ObjectProperty polyLineStyle = new SimpleObjectProperty<>(this, "polyLineStyle",
- LineStyle.NORMAL);
+ private final IntegerProperty dashSize = css().createIntegerProperty(this, "dashSize", 3);
+ private final DoubleProperty markerSize = css().createDoubleProperty(this, "markerSize", 1.5);
+ private final BooleanProperty drawMarker = css().createBooleanProperty(this, "drawMarker", true);
+ private final ObjectProperty polyLineStyle = css().createEnumPropertyWithPseudoclasses(this, "polyLineStyle",
+ LineStyle.NORMAL, LineStyle.class);
private final BooleanProperty drawChartDataSets = new SimpleBooleanProperty(this, "drawChartDataSets", true);
- private final BooleanProperty drawBars = new SimpleBooleanProperty(this, "drawBars", false);
- private final BooleanProperty shiftBar = new SimpleBooleanProperty(this, "shiftBar", true);
- private final IntegerProperty shiftBarOffset = new SimpleIntegerProperty(this, "shiftBarOffset", 3);
- private final BooleanProperty dynamicBarWidth = new SimpleBooleanProperty(this, "dynamicBarWidth", true);
- private final DoubleProperty barWidthPercentage = new SimpleDoubleProperty(this, "barWidthPercentage", 70.0);
- private final IntegerProperty barWidth = new SimpleIntegerProperty(this, "barWidth", 5);
- private final DoubleProperty intensityFading = new SimpleDoubleProperty(this, "intensityFading",
+ private final BooleanProperty drawBars = css().createBooleanProperty(this, "drawBars", false);
+ private final BooleanProperty shiftBar = css().createBooleanProperty(this, "shiftBar", true);
+ private final IntegerProperty shiftBarOffset = css().createIntegerProperty(this, "shiftBarOffset", 3);
+ private final BooleanProperty dynamicBarWidth = css().createBooleanProperty(this, "dynamicBarWidth", true);
+ private final DoubleProperty barWidthPercentage = css().createDoubleProperty(this, "barWidthPercentage", 70.0);
+ private final IntegerProperty barWidth = css().createIntegerProperty(this, "barWidth", 5);
+ private final DoubleProperty intensityFading = css().createDoubleProperty(this, "intensityFading",
AbstractErrorDataSetRendererParameter.DEFAULT_HISTORY_INTENSITY_FADING);
- private final BooleanProperty drawBubbles = new SimpleBooleanProperty(this, "drawBubbles", false);
- private final BooleanProperty allowNaNs = new SimpleBooleanProperty(this, "allowNaNs", false);
+ private final BooleanProperty drawBubbles = css().createBooleanProperty(this, "drawBubbles", false);
+ private final BooleanProperty allowNans = css().createBooleanProperty(this, "allowNans", false);
/**
*
*/
public AbstractErrorDataSetRendererParameter() {
super();
+ StyleUtil.addStyles(this,"error-dataset-renderer");
PropUtil.runOnChange(this::invalidateCanvas,
errorStyle,
rendererDataReducer,
@@ -79,17 +86,14 @@ public AbstractErrorDataSetRendererParameter() {
barWidth,
intensityFading,
drawBubbles,
- allowNaNs);
- }
-
- protected void invalidateCanvas() {
+ allowNans);
}
/**
* @return the drawBubbles property
*/
public BooleanProperty allowNaNsProperty() {
- return allowNaNs;
+ return allowNans;
}
public DoubleProperty barWidthPercentageProperty() {
@@ -178,7 +182,9 @@ public int getDashSize() {
* @see ErrorDataSetRenderer#setErrorType(ErrorStyle style) for details
*/
public ErrorStyle getErrorType() {
- return errorStyleProperty().get();
+ // TODO: figure out why 'none' in CSS maps to null
+ var type = errorStyleProperty().get();
+ return type == null ? ErrorStyle.NONE : type;
}
/**
@@ -542,4 +548,12 @@ protected R unbind() {
return getThis();
}
+
+ @Override
+ protected CssPropertyFactory> css() {
+ return CSS;
+ }
+
+ private static final CssPropertyFactory> CSS = new CssPropertyFactory<>(AbstractPointReducingRenderer.getClassCssMetaData());
+
}
diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractPointReductionManagment.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractPointReducingRenderer.java
similarity index 79%
rename from chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractPointReductionManagment.java
rename to chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractPointReducingRenderer.java
index 870e0e47e..fae9f0f7f 100644
--- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractPointReductionManagment.java
+++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractPointReducingRenderer.java
@@ -1,25 +1,29 @@
package io.fair_acc.chartfx.renderer.spi;
+import io.fair_acc.chartfx.ui.css.CssPropertyFactory;
+import io.fair_acc.chartfx.ui.css.StyleUtil;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
-import javafx.beans.property.SimpleBooleanProperty;
-import javafx.beans.property.SimpleIntegerProperty;
-
-public abstract class AbstractPointReductionManagment>
- extends AbstractDataSetManagement {
- private final ReadOnlyBooleanWrapper actualPointReduction = new ReadOnlyBooleanWrapper(this, "actualPointReduction",
- true);
- private final BooleanProperty assumeSortedData = new SimpleBooleanProperty(this, "assumeSortedData", true);
- private final IntegerProperty minRequiredReductionSize = new SimpleIntegerProperty(this, "minRequiredReductionSize",
- 5);
- private final BooleanProperty parallelImplementation = new SimpleBooleanProperty(this, "parallelImplementation",
- true);
- private final BooleanProperty pointReduction = new SimpleBooleanProperty(this, "pointReduction", true);
-
- public AbstractPointReductionManagment() {
+import javafx.css.CssMetaData;
+import javafx.css.Styleable;
+
+import java.util.List;
+
+public abstract class AbstractPointReducingRenderer>
+ extends AbstractRenderer {
+ private final ReadOnlyBooleanWrapper actualPointReduction = registerCanvasProp(new ReadOnlyBooleanWrapper(this, "actualPointReduction",
+ true));
+ private final BooleanProperty assumeSortedData = css().createBooleanProperty(this, "assumeSortedData", true);
+ private final IntegerProperty minRequiredReductionSize = registerCanvasProp(css().createIntegerProperty(this, "minRequiredReductionSize",
+ 5));
+ private final BooleanProperty parallelImplementation = registerCanvasProp(css().createBooleanProperty(this, "parallelImplementation",
+ true));
+ private final BooleanProperty pointReduction = css().createBooleanProperty(this, "pointReduction", true);
+
+ public AbstractPointReducingRenderer() {
super();
actualPointReduction.bind(Bindings.and(pointReduction, assumeSortedData));
}
@@ -155,4 +159,12 @@ public R setPointReduction(final boolean state) {
pointReduction.set(state);
return getThis();
}
+
+ @Override
+ protected CssPropertyFactory> css() {
+ return CSS;
+ }
+
+ private static final CssPropertyFactory> CSS = new CssPropertyFactory<>(AbstractPointReducingRenderer.getClassCssMetaData());
+
}
diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractDataSetManagement.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractRenderer.java
similarity index 66%
rename from chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractDataSetManagement.java
rename to chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractRenderer.java
index 7863a3d0c..35c7edadb 100644
--- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractDataSetManagement.java
+++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractRenderer.java
@@ -1,9 +1,16 @@
package io.fair_acc.chartfx.renderer.spi;
-import javafx.beans.property.BooleanProperty;
-import javafx.beans.property.SimpleBooleanProperty;
+import io.fair_acc.chartfx.Chart;
+import io.fair_acc.chartfx.ui.css.CssPropertyFactory;
+import io.fair_acc.chartfx.ui.css.StyleUtil;
+import io.fair_acc.chartfx.utils.PropUtil;
+import io.fair_acc.dataset.events.ChartBits;
+import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
+import javafx.css.CssMetaData;
+import javafx.css.Styleable;
+import javafx.css.StyleableBooleanProperty;
import javafx.geometry.Orientation;
import io.fair_acc.chartfx.XYChart;
@@ -15,16 +22,26 @@
import io.fair_acc.dataset.spi.DoubleErrorDataSet;
import io.fair_acc.dataset.utils.NoDuplicatesList;
import io.fair_acc.dataset.utils.ProcessingProfiler;
+import javafx.scene.Parent;
+
+import java.util.List;
+import java.util.function.IntSupplier;
/**
* @author rstein
* @param renderer generics
*/
-public abstract class AbstractDataSetManagement implements Renderer {
- private final ObservableList datasets = FXCollections.observableArrayList();
- protected final BooleanProperty showInLegend = new SimpleBooleanProperty(this, "showInLegend", true);
+public abstract class AbstractRenderer extends Parent implements Renderer {
+ protected final StyleableBooleanProperty showInLegend = css().createBooleanProperty(this, "showInLegend", true);
+ private final ObservableList datasets = FXCollections.observableArrayList();
private final ObservableList axesList = FXCollections.observableList(new NoDuplicatesList<>());
+ private final ObjectProperty chart = new SimpleObjectProperty<>();
+
+ public AbstractRenderer() {
+ StyleUtil.addStyles(this, "renderer");
+ PropUtil.runOnChange(() -> fireInvalidated(ChartBits.ChartLegend), showInLegend);
+ }
@Override
public ObservableList getAxes() {
@@ -102,6 +119,18 @@ protected Axis getFirstAxis(final Orientation orientation, final XYChart fallbac
*/
protected abstract R getThis();
+ public Chart getChart() {
+ return chart.get();
+ }
+
+ public ObjectProperty chartProperty() {
+ return chart;
+ }
+
+ public void setChart(Chart chart) {
+ this.chart.set(chart);
+ }
+
/**
* Sets whether DataSets attached to this renderer shall be shown in the legend
*
@@ -133,4 +162,37 @@ public boolean showInLegend() {
public final BooleanProperty showInLegendProperty() {
return showInLegend;
}
+
+ /**
+ * @param prop property that causes the canvas to invalidate
+ * @return property
+ * @param any type of property
+ */
+ protected > T registerCanvasProp(T prop){
+ PropUtil.runOnChange(this::invalidateCanvas, prop);
+ return prop;
+ }
+
+ protected void invalidateCanvas() {
+ fireInvalidated(ChartBits.ChartCanvas);
+ }
+
+ protected void fireInvalidated(IntSupplier bit) {
+ var chart = getChart();
+ if (chart != null) {
+ chart.fireInvalidated(bit);
+ }
+ }
+
+ protected CssPropertyFactory> css() {
+ return CSS; // subclass specific CSS due to inheritance issues otherwise
+ }
+
+ @Override
+ public List> getCssMetaData() {
+ return css().getCssMetaData();
+ }
+
+ private static final CssPropertyFactory> CSS = new CssPropertyFactory<>(Parent.getClassCssMetaData());
+
}
diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/GridRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/GridRenderer.java
index 033c4dc59..41505edc2 100644
--- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/GridRenderer.java
+++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/GridRenderer.java
@@ -4,12 +4,17 @@
import java.util.Collections;
import java.util.List;
+import io.fair_acc.chartfx.ui.css.CssPropertyFactory;
import io.fair_acc.chartfx.ui.css.LineStyle;
+import io.fair_acc.chartfx.ui.css.StyleGroup;
import io.fair_acc.chartfx.ui.css.StyleUtil;
import javafx.beans.property.BooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
+import javafx.css.CssMetaData;
import javafx.css.PseudoClass;
+import javafx.css.Styleable;
+import javafx.css.StyleableBooleanProperty;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.Parent;
@@ -39,39 +44,18 @@ public class GridRenderer extends Parent implements Renderer {
private static final String STYLE_CLASS_GRID_ON_TOP = "chart-grid-line-on-top";
private static final PseudoClass WITH_MINOR_PSEUDO_CLASS = PseudoClass.getPseudoClass("withMinor");
- private static final double[] DEFAULT_GRID_DASH_PATTERM = { 4.5, 2.5 };
-
- private final LineStyle horMajorGridStyleNode = new LineStyle(false,
- STYLE_CLASS_MAJOR_GRID_LINE,
- STYLE_CLASS_MAJOR_GRID_LINE_H);
- private final LineStyle verMajorGridStyleNode = new LineStyle(false,
- STYLE_CLASS_MAJOR_GRID_LINE,
- STYLE_CLASS_MAJOR_GRID_LINE_V
- );
- private final LineStyle horMinorGridStyleNode = new LineStyle(false,
- STYLE_CLASS_MINOR_GRID_LINE,
- STYLE_CLASS_MINOR_GRID_LINE_H
- );
- private final LineStyle verMinorGridStyleNode = new LineStyle(false,
- STYLE_CLASS_MINOR_GRID_LINE,
- STYLE_CLASS_MINOR_GRID_LINE_V
- );
- private final LineStyle drawGridOnTopNode = new LineStyle(false,
- STYLE_CLASS_GRID_ON_TOP
- );
+ private final StyleGroup styles = new StyleGroup(getChildren());
+ private final LineStyle horMajorGridStyleNode = styles.newLineStyle(STYLE_CLASS_MAJOR_GRID_LINE, STYLE_CLASS_MAJOR_GRID_LINE_H);
+ private final LineStyle verMajorGridStyleNode = styles.newLineStyle(STYLE_CLASS_MAJOR_GRID_LINE, STYLE_CLASS_MAJOR_GRID_LINE_V);
+ private final LineStyle horMinorGridStyleNode = styles.newLineStyle(STYLE_CLASS_MINOR_GRID_LINE, STYLE_CLASS_MINOR_GRID_LINE_H);
+ private final LineStyle verMinorGridStyleNode = styles.newLineStyle(STYLE_CLASS_MINOR_GRID_LINE, STYLE_CLASS_MINOR_GRID_LINE_V);
+ private final StyleableBooleanProperty drawGridOnTop = CSS.createBooleanProperty(this, "drawGridOnTop", true);
protected final ObservableList axesList = FXCollections.observableList(new NoDuplicatesList<>());
public GridRenderer() {
super();
StyleUtil.hiddenStyleNode(this, STYLE_CLASS_GRID_RENDERER);
- getChildren().addAll(
- horMajorGridStyleNode,
- verMajorGridStyleNode,
- horMinorGridStyleNode,
- verMinorGridStyleNode,
- drawGridOnTopNode
- );
StyleUtil.applyPseudoClass(horMajorGridStyleNode, GridRenderer.WITH_MINOR_PSEUDO_CLASS, horMinorGridStyleNode.visibleProperty());
StyleUtil.applyPseudoClass(verMajorGridStyleNode, GridRenderer.WITH_MINOR_PSEUDO_CLASS, verMinorGridStyleNode.visibleProperty());
}
@@ -135,15 +119,6 @@ public Canvas drawLegendSymbol(DataSet dataSet, int dsIndex, int width, int heig
return null;
}
- /**
- * Indicates whether grid lines should be drawn on top or beneath graphs
- *
- * @return drawOnTop property
- */
- public final BooleanProperty drawOnTopProperty() {
- return drawGridOnTopNode.visibleProperty();
- }
-
protected void drawPolarCircle(final GraphicsContext gc, final Axis yAxis, final double yRange,
final double xCentre, final double yCentre, final double maxRadius) {
if (!horMajorGridStyleNode.isVisible() && !horMinorGridStyleNode.isVisible()) {
@@ -165,8 +140,8 @@ protected void drawPolarCircle(final GraphicsContext gc, final Axis yAxis, final
gc.strokeOval(xCentre - yNorm, yCentre - yNorm, 2 * yNorm, 2 * yNorm);
gc.save();
- gc.setFont(yAxis.getTickLabelFont());
- gc.setStroke(yAxis.getTickLabelFill());
+ gc.setFont(yAxis.getTickLabelStyle().getFont());
+ gc.setStroke(yAxis.getTickLabelStyle().getFill()); // TODO: why stroke rather than fill?
gc.setLineDashes((double[]) null);
gc.setTextBaseline(VPos.CENTER);
gc.strokeText(label, xCentre + (int) yAxis.getTickLabelGap(), yCentre - yNorm);
@@ -214,8 +189,8 @@ protected void drawPolarGrid(final GraphicsContext gc, XYChart xyChart) {
gc.strokeLine(xCentre, yCentre, x, y);
gc.save();
- gc.setFont(yAxis.getTickLabelFont());
- gc.setStroke(yAxis.getTickLabelFill());
+ gc.setFont(yAxis.getTickLabelStyle().getFont());
+ gc.setStroke(yAxis.getTickLabelStyle().getFill()); // TODO: why stroke rather than fill?
gc.setLineDashes((double[]) null);
gc.setTextBaseline(VPos.CENTER);
if (phi < 350) {
@@ -337,30 +312,30 @@ public LineStyle getVerticalMinorGrid() {
}
/**
- * Indicates whether horizontal major grid lines are visible or not.
+ * Indicates whether grid lines should be drawn on top or beneath graphs
*
- * @return verticalGridLinesVisible property
+ * @param state true: draw on top
*/
- public final BooleanProperty horizontalGridLinesVisibleProperty() {
- return horMajorGridStyleNode.visibleProperty();
+ public final void setDrawOnTop(boolean state) {
+ drawOnTopProperty().set(state);
}
/**
- * Indicates whether horizontal minor grid lines are visible or not.
+ * Indicates whether grid lines should be drawn on top or beneath graphs
*
- * @return verticalGridLinesVisible property
+ * @return drawOnTop state
*/
- public final BooleanProperty horizontalMinorGridLinesVisibleProperty() {
- return horMinorGridStyleNode.visibleProperty();
+ public final boolean isDrawOnTop() {
+ return drawOnTopProperty().get();
}
/**
* Indicates whether grid lines should be drawn on top or beneath graphs
*
- * @return drawOnTop state
+ * @return drawOnTop property
*/
- public final boolean isDrawOnTop() {
- return drawGridOnTopNode.isVisible();
+ public final BooleanProperty drawOnTopProperty() {
+ return drawGridOnTop;
}
@Override
@@ -381,15 +356,6 @@ public List render(final GraphicsContext gc, final Chart chart, final i
return Collections.emptyList();
}
- /**
- * Indicates whether grid lines should be drawn on top or beneath graphs
- *
- * @param state true: draw on top
- */
- public final void setDrawOnTop(boolean state) {
- drawGridOnTopNode.setVisible(state);
- }
-
@Override
public Renderer setShowInLegend(final boolean state) {
return this;
@@ -405,29 +371,19 @@ public BooleanProperty showInLegendProperty() {
return null;
}
- /**
- * Indicates whether vertical major grid lines are visible or not.
- *
- * @return verticalGridLinesVisible property
- */
- public final BooleanProperty verticalGridLinesVisibleProperty() {
- return verMajorGridStyleNode.visibleProperty();
+ @Override
+ public List> getCssMetaData() {
+ return getClassCssMetaData();
}
- /**
- * Indicates whether vertical minor grid lines are visible or not.
- *
- * @return verticalGridLinesVisible property
- */
- public final BooleanProperty verticalMinorGridLinesVisibleProperty() {
- return verMinorGridStyleNode.visibleProperty();
+ public static List> getClassCssMetaData() {
+ return CSS.getCssMetaData();
}
+ private static final CssPropertyFactory CSS = new CssPropertyFactory<>(Parent.getClassCssMetaData());
+
protected static void applyGraphicsStyleFromLineStyle(final GraphicsContext gc, final LineStyle style) {
style.copyStyleTo(gc);
- if (style.getStrokeDashArray() == null || style.getStrokeDashArray().isEmpty()) {
- gc.setLineDashes(DEFAULT_GRID_DASH_PATTERM);
- }
}
private static double snap(final double value) {
diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/LabelledMarkerRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/LabelledMarkerRenderer.java
index c7605e1ca..cc0559bc4 100644
--- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/LabelledMarkerRenderer.java
+++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/LabelledMarkerRenderer.java
@@ -38,7 +38,7 @@
*
* Points without any label data are ignored by the renderer.
*/
-public class LabelledMarkerRenderer extends AbstractDataSetManagement implements Renderer {
+public class LabelledMarkerRenderer extends AbstractRenderer implements Renderer {
private static final Logger LOGGER = LoggerFactory.getLogger(LabelledMarkerRenderer.class);
private static final String STYLE_CLASS_LABELLED_MARKER = "chart-labelled-marker";
private static final String DEFAULT_FONT = "Helvetica";
@@ -46,7 +46,6 @@ public class LabelledMarkerRenderer extends AbstractDataSetManagement implements Renderer {
+public class ReducingLineRenderer extends AbstractRenderer implements Renderer {
private int maxPoints;
public ReducingLineRenderer() {
diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/AbstractFinancialRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/AbstractFinancialRenderer.java
index d075f2831..e48769e47 100644
--- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/AbstractFinancialRenderer.java
+++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/AbstractFinancialRenderer.java
@@ -12,7 +12,7 @@
import io.fair_acc.chartfx.axes.Axis;
import io.fair_acc.chartfx.renderer.Renderer;
-import io.fair_acc.chartfx.renderer.spi.AbstractDataSetManagement;
+import io.fair_acc.chartfx.renderer.spi.AbstractRenderer;
import io.fair_acc.chartfx.renderer.spi.financial.service.OhlcvRendererEpData;
import io.fair_acc.chartfx.renderer.spi.financial.service.PaintBarMarker;
import io.fair_acc.dataset.DataSet;
@@ -30,7 +30,7 @@
* @author afischer
*/
@SuppressWarnings({ "PMD.ExcessiveParameterList" })
-public abstract class AbstractFinancialRenderer extends AbstractDataSetManagement implements Renderer {
+public abstract class AbstractFinancialRenderer extends AbstractRenderer implements Renderer {
protected PaintBarMarker paintBarMarker;
private final BooleanProperty computeLocalYRange = new SimpleBooleanProperty(this, "computeLocalYRange", true);
diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/css/FinancialColorSchemeConfig.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/css/FinancialColorSchemeConfig.java
index 24f6c26ea..77f955ccd 100644
--- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/css/FinancialColorSchemeConfig.java
+++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/css/FinancialColorSchemeConfig.java
@@ -200,10 +200,10 @@ public void applyTo(String theme, String customColorScheme, XYChart chart) throw
chart.getGridRenderer().getHorizontalMajorGrid().setStroke(Color.DARKGREY);
chart.getGridRenderer().getVerticalMajorGrid().setStroke(Color.DARKGREY);
if (chart.getXAxis() instanceof AbstractAxisParameter) {
- ((AbstractAxisParameter) chart.getXAxis()).setTickLabelFill(Color.BLACK);
+ ((AbstractAxisParameter) chart.getXAxis()).getTickLabelStyle().setFill(Color.BLACK);
}
if (chart.getYAxis() instanceof AbstractAxisParameter) {
- ((AbstractAxisParameter) chart.getYAxis()).setTickLabelFill(Color.BLACK);
+ ((AbstractAxisParameter) chart.getYAxis()).getTickLabelStyle().setFill(Color.BLACK);
}
break;
@@ -214,12 +214,12 @@ public void applyTo(String theme, String customColorScheme, XYChart chart) throw
chart.getGridRenderer().getVerticalMajorGrid().setVisible(false);
chart.getGridRenderer().getHorizontalMajorGrid().setVisible(false);
chart.getGridRenderer().getHorizontalMajorGrid().setVisible(false);
- chart.setTitlePaint(Color.WHITE);
+ chart.getTitleLabel().setTextFill(Color.WHITE);
if (chart.getXAxis() instanceof AbstractAxisParameter) {
- ((AbstractAxisParameter) chart.getXAxis()).setTickLabelFill(Color.WHITESMOKE);
+ ((AbstractAxisParameter) chart.getXAxis()).getTickLabelStyle().setFill(Color.WHITESMOKE);
}
if (chart.getYAxis() instanceof AbstractAxisParameter) {
- ((AbstractAxisParameter) chart.getYAxis()).setTickLabelFill(Color.WHITESMOKE);
+ ((AbstractAxisParameter) chart.getYAxis()).getTickLabelStyle().setFill(Color.WHITESMOKE);
}
break;
@@ -231,12 +231,12 @@ public void applyTo(String theme, String customColorScheme, XYChart chart) throw
chart.getGridRenderer().getHorizontalMajorGrid().setVisible(true);
chart.getGridRenderer().getHorizontalMinorGrid().setVisible(false);
chart.getGridRenderer().getHorizontalMajorGrid().setStroke(Color.rgb(106, 106, 106));
- chart.setTitlePaint(Color.WHITE);
+ chart.getTitleLabel().setTextFill(Color.WHITE);
if (chart.getXAxis() instanceof AbstractAxisParameter) {
- ((AbstractAxisParameter) chart.getXAxis()).setTickLabelFill(Color.rgb(194, 194, 194));
+ ((AbstractAxisParameter) chart.getXAxis()).getTickLabelStyle().setFill(Color.rgb(194, 194, 194));
}
if (chart.getYAxis() instanceof AbstractAxisParameter) {
- ((AbstractAxisParameter) chart.getYAxis()).setTickLabelFill(Color.rgb(194, 194, 194));
+ ((AbstractAxisParameter) chart.getYAxis()).getTickLabelStyle().setFill(Color.rgb(194, 194, 194));
}
break;
}
diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/CssPropertyFactory.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/CssPropertyFactory.java
index e690a893e..a5050a5b4 100644
--- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/CssPropertyFactory.java
+++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/CssPropertyFactory.java
@@ -4,6 +4,9 @@
import java.util.*;
import java.util.function.*;
+import io.fair_acc.chartfx.ui.geometry.Side;
+import io.fair_acc.chartfx.ui.layout.ChartPane;
+import io.fair_acc.dataset.utils.AssertUtils;
import javafx.beans.property.Property;
import javafx.css.*;
import javafx.scene.Node;
@@ -286,6 +289,31 @@ public final StyleableStringProperty createStringProperty(S styleableBean, Strin
return createStringProperty(styleableBean, propertyName, initialValue, true, null, invalidateActions);
}
+ /**
+ * Creates a non-null styleable side property that automatically updates the node's side in the chart. The
+ * field must be named "side".
+ *
+ * @param styleableBean the {@code this} reference of the returned property. This is also the property bean.
+ * @param initialValue the initial value of the property. CSS may reset the property to this value.
+ * @param invalidateActions zero, one or two {@code Runnable}s (vararg) first one will be executed before and second one after invalidation
+ * @return a StyleableProperty created with initial value
+ */
+ public final StyleableObjectProperty createSideProperty(S styleableBean, Side initialValue, Runnable... invalidateActions) {
+ var converter = StyleConverter.getEnumConverter(Side.class);
+ BinaryOperator filter = (old, side) -> {
+ AssertUtils.notNull("Side must not be null", side);
+ var target = styleableBean.getStyleableNode();
+ if(target == null && styleableBean instanceof Node) {
+ target = (Node) styleableBean;
+ }
+ AssertUtils.notNull("Bean does not specify a styleable node", target);
+ ChartPane.setSide(target, side);
+ return side;
+ };
+ filter.apply(null, initialValue);
+ return createObjectProperty(styleableBean, "side", initialValue, false, converter, filter, invalidateActions);
+ }
+
/**
* Create a StyleableProperty<Boolean> with initial value and inherit flag.
* This also creates pseudoclasses for each enum value and keeps them up to date with the property.
diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/LineStyle.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/LineStyle.java
index 714133408..c62bdb060 100644
--- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/LineStyle.java
+++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/LineStyle.java
@@ -3,50 +3,27 @@
import javafx.beans.property.LongProperty;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.SimpleLongProperty;
-import javafx.beans.value.ChangeListener;
-import javafx.scene.Node;
-import javafx.scene.canvas.GraphicsContext;
import javafx.scene.shape.Line;
-import javafx.scene.shape.Path;
-import javafx.scene.shape.Shape;
/**
- * An invisible node that lets users change styles
- * via CSS. The actual drawing is done in a canvas to
- * reduce the number of nodes on the SceneGraph. Each change
- * increments a counter, so that it is easy to invalidate outdated
- * renderings.
- *
* @author ennerf
*/
-public class LineStyle extends Line {
+public class LineStyle extends Line implements StyleUtil.StyleNode {
public LineStyle(String... styles) {
- this(true, styles);
+ StyleUtil.styleNode(this, styles);
+ StyleUtil.forEachStyleProp(this, StyleUtil.incrementOnChange(changeCounter));
}
- public LineStyle(boolean hide, String... styles) {
- StyleUtil.addStyles(this, styles);
- setManaged(false);
- // It looks like a manual set will overwrite any CSS styling
- if (hide) {
- setVisible(false);
- }
- StyleUtil.registerShapeListener(this, StyleUtil.incrementOnChange(changeCounter));
- }
-
- public void copyStyleTo(GraphicsContext gc) {
- StyleUtil.copyShapeStyle(this, gc);
- }
-
- public long getChangeCounter() {
- return changeCounter.get();
+ @Override
+ public String toString() {
+ return StyleUtil.toStyleString(this);
}
public ReadOnlyLongProperty changeCounterProperty() {
return changeCounter;
}
- LongProperty changeCounter = new SimpleLongProperty(0);
+ private final LongProperty changeCounter = new SimpleLongProperty(0);
}
diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/StyleGroup.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/StyleGroup.java
new file mode 100644
index 000000000..3cc754827
--- /dev/null
+++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/StyleGroup.java
@@ -0,0 +1,51 @@
+package io.fair_acc.chartfx.ui.css;
+
+import io.fair_acc.chartfx.utils.PropUtil;
+import javafx.beans.property.LongProperty;
+import javafx.beans.property.ReadOnlyLongProperty;
+import javafx.beans.property.SimpleLongProperty;
+import javafx.collections.ObservableList;
+import javafx.scene.Group;
+import javafx.scene.Node;
+import javafx.scene.layout.Pane;
+
+import javax.swing.event.ChangeListener;
+
+/**
+ * A hidden group that holds styles. This hides style nodes even
+ * if the visibility property is set to true.
+ *
+ * @author ennerf
+ */
+public class StyleGroup extends Group {
+
+ public StyleGroup(Pane pane, String... paneStyles) {
+ this(pane, pane.getChildren(), paneStyles);
+ }
+
+ public StyleGroup(Node parent, ObservableList children, String... parentStyles) {
+ this(children);
+ StyleUtil.addStyles(parent, parentStyles);
+ }
+
+ public StyleGroup(ObservableList children) {
+ StyleUtil.hiddenStyleNode(this);
+ setAutoSizeChildren(false);
+ relocate(0, 0);
+ children.add(this);
+ }
+
+ public LineStyle newLineStyle(String... styles) {
+ return addToChildren(new LineStyle(styles));
+ }
+
+ public TextStyle newTextStyle(String... styles) {
+ return addToChildren(new TextStyle(styles));
+ }
+
+ private T addToChildren(T style) {
+ getChildren().add(style);
+ return style;
+ }
+
+}
diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/StyleUtil.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/StyleUtil.java
index 9380d9b5f..90b552d1e 100644
--- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/StyleUtil.java
+++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/StyleUtil.java
@@ -1,16 +1,23 @@
package io.fair_acc.chartfx.ui.css;
+import io.fair_acc.chartfx.utils.FXUtils;
import io.fair_acc.chartfx.utils.PropUtil;
import javafx.beans.binding.Bindings;
import javafx.beans.property.LongProperty;
+import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableBooleanValue;
+import javafx.beans.value.ObservableValue;
import javafx.css.PseudoClass;
+import javafx.css.StyleableProperty;
import javafx.scene.Node;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.shape.Shape;
import javafx.scene.text.Text;
+import java.util.List;
+import java.util.function.Consumer;
+
/**
* Utility class for styleable nodes
*
@@ -21,9 +28,43 @@ public class StyleUtil {
private StyleUtil() {
}
+ /**
+ * A node that lets users change styles via CSS. The
+ * actual drawing is done in a canvas to reduce the
+ * number of nodes on the SceneGraph. Each change
+ * increments a counter, so that it is easy to
+ * invalidate outdated renderings.
+ */
+ public interface StyleNode {
+
+ /**
+ * Copies all style parameters except for rotate
+ * @param gc target context
+ */
+ default void copyStyleTo(GraphicsContext gc) {
+ copyStyle((Node)this, gc);
+ }
+
+ default long getChangeCounter() {
+ return changeCounterProperty().get();
+ }
+
+ /**
+ * @return a counter with the total number of style changes
+ */
+ ReadOnlyLongProperty changeCounterProperty();
+ }
+
public static NODE hiddenStyleNode(NODE node, String... styles) {
- hide(node);
+ styleNode(node, styles);
+ node.setVisible(false); // don't let CSS modify visibility
+ return node;
+ }
+
+ public static NODE styleNode(NODE node, String... styles) {
+ // Note: we can't modify visibility, otherwise it can't be set via CSS anymore
addStyles(node, styles);
+ node.setManaged(false);
return node;
}
@@ -48,69 +89,119 @@ public static void applyPseudoClass(Node node, PseudoClass clazz, ObservableBool
}, condition);
}
- static void copyNodeStyle(Node style, GraphicsContext gc) {
+ public static void forEachStyleProp(Node node, Consumer> action) {
// https://docs.oracle.com/javafx/2/api/javafx/scene/doc-files/cssref.html#node
+ action.accept(node.visibleProperty());
+ action.accept(node.rotateProperty());
+ action.accept(node.opacityProperty());
+
+ // https://docs.oracle.com/javafx/2/api/javafx/scene/doc-files/cssref.html#shape
+ if (node instanceof Shape) {
+ Shape shape = (Shape) node;
+ action.accept(shape.fillProperty());
+ action.accept(shape.strokeProperty());
+ action.accept(Bindings.size(shape.getStrokeDashArray()));
+ action.accept(shape.strokeDashOffsetProperty());
+ action.accept(shape.strokeLineCapProperty());
+ action.accept(shape.strokeLineJoinProperty());
+ action.accept(shape.strokeMiterLimitProperty());
+ action.accept(shape.strokeWidthProperty());
+
+ // https://docs.oracle.com/javafx/2/api/javafx/scene/doc-files/cssref.html#text
+ if (node instanceof Text) {
+ Text text = (Text) node;
+ action.accept(text.fontProperty());
+ action.accept(text.fontSmoothingTypeProperty());
+ action.accept(text.textAlignmentProperty());
+ action.accept(text.textOriginProperty());
+ }
+ }
+ }
+
+ public static void copyStyle(Node style, GraphicsContext gc) {
// rotate, translate, etc. would mess up the coordinate frame
gc.setGlobalAlpha(style.getOpacity());
- }
- static void registerNodeListener(Node style, ChangeListener