From d45b68fe149f0fec8208d585a28a4d467062267a Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Fri, 4 Aug 2023 20:48:48 +0200 Subject: [PATCH 01/19] switched css file to sass (cherry picked from commit 6716a2a21d4b041ce58548f3d948da412484cc4e) --- chartfx-chart/pom.xml | 30 +++ .../io/fair_acc/chartfx/chart-icons.css | 28 +-- .../io/fair_acc/chartfx/chart-icons.scss | 65 ++++++ .../resources/io/fair_acc/chartfx/chart.css | 15 +- .../resources/io/fair_acc/chartfx/chart.scss | 197 ++++++++++++++++++ 5 files changed, 310 insertions(+), 25 deletions(-) create mode 100644 chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart-icons.scss create mode 100644 chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss diff --git a/chartfx-chart/pom.xml b/chartfx-chart/pom.xml index 3b180f8d3..018af00d4 100644 --- a/chartfx-chart/pom.xml +++ b/chartfx-chart/pom.xml @@ -13,6 +13,9 @@ chartfx-chart io.fair-acc.chartfx + 1.64.2 + ${project.basedir}/src/main/resources/io/fair_acc/chartfx/ + ${scss.inputDir} This charting library ${project.artifactId}- is an extension @@ -93,5 +96,32 @@ 2.1.0 + + + + + us.hebi.sass + sass-cli-maven-plugin + 1.0.3 + + ${sass.version} + + ${scss.inputDir}:${css.outputDir} + --no-source-map + + + + + sass-exec + generate-resources + + run + + + + + + + diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart-icons.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart-icons.css index 6b005f3b9..2954e3034 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart-icons.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart-icons.css @@ -1,61 +1,55 @@ @font-face { - font-family: 'fair-chart-icons'; - src: url('fonts/fair-chart-icons.eot?yr8ymj'); - src: - url('fonts/fair-chart-icons.eot?yr8ymj#iefix') format('embedded-opentype'), - url('fonts/fair-chart-icons.ttf?yr8ymj') format('truetype'), - url('fonts/fair-chart-icons.woff?yr8ymj') format('woff'), - url('fonts/fair-chart-icons.svg?yr8ymj#fair-chart-icons') format('svg'); + font-family: "fair-chart-icons"; + src: url("fonts/fair-chart-icons.eot?yr8ymj"); + src: url("fonts/fair-chart-icons.eot?yr8ymj#iefix") format("embedded-opentype"), url("fonts/fair-chart-icons.ttf?yr8ymj") format("truetype"), url("fonts/fair-chart-icons.woff?yr8ymj") format("woff"), url("fonts/fair-chart-icons.svg?yr8ymj#fair-chart-icons") format("svg"); font-weight: normal; font-style: normal; font-display: block; } - -[class^="icon-"], +[class^=icon-], [class*=" icon-"] { /* use !important to prevent issues with browser extensions that change fonts */ - font-family: 'fair-chart-icons' !important; + font-family: "fair-chart-icons" !important; speak: never; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; - /* Better Font Rendering =========== */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .icon-info_icon .path1::before { - content: "\49"; + content: "I"; color: rgb(0, 0, 205); } .icon-info_icon .path2::before { - content: "\4a"; + content: "J"; margin-left: -1em; color: rgb(255, 255, 255); } .icon-warn_icon .path1::before { - content: "\57"; + content: "W"; color: rgb(255, 215, 0); } .icon-warn_icon .path2::before { - content: "\58"; + content: "X"; margin-left: -1.1376953125em; color: rgb(0, 0, 0); } .icon-error_icon .path1::before { - content: "\45"; + content: "E"; color: rgb(237, 28, 36); } .icon-error_icon .path2::before { - content: "\46"; + content: "F"; margin-left: -1em; color: rgb(255, 255, 255); } diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart-icons.scss b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart-icons.scss new file mode 100644 index 000000000..6b005f3b9 --- /dev/null +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart-icons.scss @@ -0,0 +1,65 @@ +@font-face { + font-family: 'fair-chart-icons'; + src: url('fonts/fair-chart-icons.eot?yr8ymj'); + src: + url('fonts/fair-chart-icons.eot?yr8ymj#iefix') format('embedded-opentype'), + url('fonts/fair-chart-icons.ttf?yr8ymj') format('truetype'), + url('fonts/fair-chart-icons.woff?yr8ymj') format('woff'), + url('fonts/fair-chart-icons.svg?yr8ymj#fair-chart-icons') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +[class^="icon-"], +[class*=" icon-"] { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'fair-chart-icons' !important; + speak: never; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-info_icon .path1::before { + content: "\49"; + color: rgb(0, 0, 205); +} + +.icon-info_icon .path2::before { + content: "\4a"; + margin-left: -1em; + color: rgb(255, 255, 255); +} + +.icon-warn_icon .path1::before { + content: "\57"; + color: rgb(255, 215, 0); +} + +.icon-warn_icon .path2::before { + content: "\58"; + margin-left: -1.1376953125em; + color: rgb(0, 0, 0); +} + +.icon-error_icon .path1::before { + content: "\45"; + color: rgb(237, 28, 36); +} + +.icon-error_icon .path2::before { + content: "\46"; + margin-left: -1em; + color: rgb(255, 255, 255); +} + +.text { + -fx-font-family: "fair-chart-icons"; +} diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index a5c35cd6f..b43d34b8a 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -28,7 +28,6 @@ -fx-pref-width: 500px; -fx-max-width: 4096px; -fx-max-height: 4096px; - /* no padding by default */ -fx-padding: 0px; } @@ -79,7 +78,7 @@ .chart-series-line { -fx-stroke-width: 1px; - -fx-effect: null; + -fx-effect: NULL; } .value-indicator-label { @@ -134,12 +133,12 @@ .value-watch-indicator-marker { -fx-stroke-width: 0.5; -fx-stroke: black; - -fx-fill: #416ef4ff; + -fx-fill: #416ef4; } .range-indicator-rect { -fx-stroke: transparent; - -fx-fill: #416ef468; + -fx-fill: rgba(65, 110, 244, 0.4078431373); } .chart-major-grid-lines { @@ -180,13 +179,13 @@ } .chart-alternative-column-fill { - -fx-fill: null; - -fx-stroke: null; + -fx-fill: NULL; + -fx-stroke: NULL; } .chart-alternative-row-fill { - -fx-fill: null; - -fx-stroke: null; + -fx-fill: NULL; + -fx-stroke: NULL; } .chart-vertical-zero-line, diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss new file mode 100644 index 000000000..fb38da21d --- /dev/null +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss @@ -0,0 +1,197 @@ +$null: NULL; // lowercase gets removed from Sass, but uppercase seems to also be interpreted as null + +.chart-datapoint-tooltip-label { + -fx-background-color: rgb(153, 204, 204); + -fx-border-color: black; + -fx-border-radius: 3; + -fx-font-size: 12; + -fx-font-weight: bold; + -fx-text-alignment: center; +} + +.axis-label { + -fx-axis-label-alignment: center; + -fx-stroke: transparent; +} + +.axis-tick-label { + -fx-stroke: transparent; +} + +.chart, .chart-content, .chart-plot-area { + /* + Set some reasonable default sizes so users + can override them via CSS and JavaFX panes + won't flicker (usually +/- 1px) or act odd. + */ + -fx-min-height: 100px; + -fx-min-width: 100px; + -fx-pref-height: 500px; + -fx-pref-width: 500px; + -fx-max-width: 4096px; + -fx-max-height: 4096px; + + /* no padding by default */ + -fx-padding: 0px; +} + +.chart-crosshair-path { + -fx-stroke-width: 1; +} + +.chart-zoom-rect { + -fx-fill: dodgerblue; + -fx-stroke: #002966; + -fx-stroke-type: inside; + -fx-stroke-width: 1; + -fx-opacity: 0.2; +} + +.chart-select-rect { + -fx-fill: transparent; + -fx-stroke: gray; + -fx-stroke-width: 1.5; + -fx-stroke-dash-offset: 6; + -fx-stroke-dash-array: 12 2 4 2; + -fx-stroke-line-cap: butt; + -fx-effect: dropshadow(three-pass-box, derive(gray, -20%), 10, 0, 4, 4); +} + +.chart-select-marker { + -fx-fill: transparent; + -fx-stroke: lightGreen; + -fx-stroke-width: 1.5; + -fx-stroke-dash-offset: 6; + -fx-stroke-dash-array: 12 2 4 2; + -fx-stroke-line-cap: butt; + -fx-opacity: 1; + -fx-effect: dropshadow(three-pass-box, green, 5, 0.5, 0, 0); +} + +.chart-select-marker:noedit { + -fx-fill: transparent; + -fx-stroke: red; + -fx-stroke-width: 1.5; + -fx-stroke-dash-offset: 6; + -fx-stroke-dash-array: 12 2 4 2; + -fx-stroke-line-cap: butt; + -fx-opacity: 1; + -fx-effect: dropshadow(three-pass-box, red, 5, 0.5, 0, 0); +} + +.chart-series-line { + -fx-stroke-width: 1px; + -fx-effect: $null; +} + +.value-indicator-label { + -fx-background-color: #f4e242; + -fx-border-color: black; + -fx-border-radius: 2; + -fx-font-size: 12; + -fx-font-weight: bold; + -fx-text-alignment: center; + -fx-padding: 1 2 1 2; +} + +.range-indicator-label { + -fx-background-color: #f4e242; + -fx-border-color: black; + -fx-border-radius: 2; + -fx-font-size: 12; + -fx-font-weight: bold; + -fx-text-alignment: center; + -fx-padding: 1 2 1 2; +} + +.value-indicator-line { + -fx-stroke-width: 1; + -fx-stroke: black; + -fx-stroke-dash-array: 8; +} + +.value-indicator-marker { + -fx-stroke-width: 0.5; + -fx-stroke: derive(-fx-background, -30%); + -fx-fill: dodgerblue; +} + +.value-watch-indicator-label { + -fx-background-color: transparent; + -fx-border-color: transparent; + -fx-border-radius: 0; + -fx-font-size: 11; + -fx-font-weight: bold; + -fx-text-fill: white; + -fx-text-alignment: center; + -fx-padding: 2.5 4 1 8; +} + +.value-watch-indicator-line { + -fx-stroke-width: 1; + -fx-stroke: black; + -fx-stroke-dash-array: 8; +} + +.value-watch-indicator-marker { + -fx-stroke-width: 0.5; + -fx-stroke: black; + -fx-fill: #416ef4ff; +} + +.range-indicator-rect { + -fx-stroke: transparent; + -fx-fill: #416ef468; +} + +.chart-major-grid-lines { + -fx-stroke: derive(-fx-background, -10%); + -fx-stroke-dash-array: 4.5, 2.5; + -fx-stroke-width: 0.5; + visibility: visible; +} + +.chart-major-grid-lines:withMinor { + -fx-stroke: derive(-fx-background, -20%); +} + +.chart-minor-grid-lines { + -fx-stroke: derive(-fx-background, -5%); + -fx-stroke-width: 0.5; + visibility: hidden; +} + +.chart-grid-line-on-top { + visibility: visible; /* 'visible' for front, 'hidden' for back */ +} + +.chart-major-vertical-lines .chart-major-grid-lines { + /* use this to override v-specific settings */ +} + +.chart-major-horizontal-grid-lines .chart-major-grid-lines { + /* use this to override h-specific settings */ +} + +.chart-minor-vertical-grid-lines .chart-minor-grid-lines { + /* use this to override v-specific settings */ +} + +.chart-minor-horizontal-grid-lines .chart-minor-grid-lines { + /* use this to override h-specific settings */ +} + +.chart-alternative-column-fill { + -fx-fill: $null; + -fx-stroke: $null; +} + +.chart-alternative-row-fill { + -fx-fill: $null; + -fx-stroke: $null; +} + +.chart-vertical-zero-line, +.chart-horizontal-zero-line { + -fx-stroke: derive(-fx-text-background-color, 40%); +} From 9a69247262abb10f0f64c625091a342315d07794 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Fri, 4 Aug 2023 23:06:44 +0200 Subject: [PATCH 02/19] cleaned up grid renderer styling (cherry picked from commit a12bc179a7d8187dccad5d0245b8a30b1bb8b4b3) --- .../main/java/io/fair_acc/chartfx/Chart.java | 29 -------- .../java/io/fair_acc/chartfx/XYChart.java | 10 +-- .../chartfx/renderer/spi/GridRenderer.java | 37 ++--------- .../io/fair_acc/chartfx/ui/css/LineStyle.java | 23 +------ .../fair_acc/chartfx/ui/css/StyleGroup.java | 51 ++++++++++++++ .../io/fair_acc/chartfx/ui/css/StyleUtil.java | 45 +++++++++++-- .../io/fair_acc/chartfx/ui/css/TextStyle.java | 8 +-- .../resources/io/fair_acc/chartfx/chart.css | 24 +++---- .../resources/io/fair_acc/chartfx/chart.scss | 66 +++++++++++-------- 9 files changed, 152 insertions(+), 141 deletions(-) create mode 100644 chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/StyleGroup.java diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index f4ff8d123..76828c16a 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -997,33 +997,4 @@ protected static Group createChildGroup() { return group; } - protected static class ChartHBox extends HBox { - public ChartHBox(Node... nodes) { - super(); - setAlignment(Pos.CENTER); - setPrefSize(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE); - getChildren().addAll(nodes); - visibleProperty().addListener((obs, o, n) -> getChildren().forEach(node -> node.setVisible(n))); - } - - public ChartHBox(final boolean fill) { - this(); - setFillHeight(fill); - } - } - - protected static class ChartVBox extends VBox { - public ChartVBox(Node... nodes) { - super(); - setAlignment(Pos.CENTER); - setPrefSize(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE); - getChildren().addAll(nodes); - visibleProperty().addListener((obs, o, n) -> getChildren().forEach(node -> node.setVisible(n))); - } - - public ChartVBox(final boolean fill) { - this(); - setFillWidth(fill); - } - } } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java index 5f9299041..2e83ea329 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java @@ -88,16 +88,10 @@ public XYChart(final Axis... axes) { getChildren().add(0, gridRenderer); PropUtil.runOnChange(getBitState().onAction(ChartBits.ChartCanvas), - gridRenderer.horizontalGridLinesVisibleProperty(), - gridRenderer.verticalGridLinesVisibleProperty(), - gridRenderer.getHorizontalMinorGrid().visibleProperty(), - gridRenderer.getVerticalMinorGrid().visibleProperty(), - gridRenderer.drawOnTopProperty(), gridRenderer.getHorizontalMajorGrid().changeCounterProperty(), - gridRenderer.getVerticalMajorGrid().changeCounterProperty(), gridRenderer.getHorizontalMinorGrid().changeCounterProperty(), - gridRenderer.getVerticalMinorGrid().changeCounterProperty() - ); + gridRenderer.getVerticalMajorGrid().changeCounterProperty(), + gridRenderer.getVerticalMinorGrid().changeCounterProperty()); this.setAnimated(false); getRenderers().addListener(this::rendererChanged); 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..6ae12f2f1 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 @@ -5,6 +5,7 @@ import java.util.List; 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; @@ -39,39 +40,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 LineStyle drawGridOnTopNode = styles.newLineStyle(STYLE_CLASS_GRID_ON_TOP); 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()); } @@ -425,9 +405,6 @@ public final BooleanProperty verticalMinorGridLinesVisibleProperty() { 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/ui/css/LineStyle.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/LineStyle.java index 714133408..4b6b3af9f 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,12 +3,8 @@ 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 @@ -19,19 +15,10 @@ * * @author ennerf */ -public class LineStyle extends Line { +public class LineStyle extends Line implements StyleUtil.ChangeCounter { public LineStyle(String... styles) { - this(true, styles); - } - - 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.styleNode(this, styles); StyleUtil.registerShapeListener(this, StyleUtil.incrementOnChange(changeCounter)); } @@ -39,14 +26,10 @@ public void copyStyleTo(GraphicsContext gc) { StyleUtil.copyShapeStyle(this, gc); } - public long getChangeCounter() { - return changeCounter.get(); - } - 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..3c08b8b2b --- /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(0, 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..cce286357 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,8 +1,11 @@ package io.fair_acc.chartfx.ui.css; +import io.fair_acc.chartfx.utils.FXUtils; import io.fair_acc.chartfx.utils.PropUtil; +import javafx.application.Platform; 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.css.PseudoClass; @@ -11,6 +14,8 @@ import javafx.scene.shape.Shape; import javafx.scene.text.Text; +import java.util.List; + /** * Utility class for styleable nodes * @@ -21,9 +26,24 @@ public class StyleUtil { private StyleUtil() { } + public interface ChangeCounter { + default long getChangeCounter() { + return changeCounterProperty().get(); + } + + 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; } @@ -66,10 +86,7 @@ static void copyShapeStyle(Shape style, GraphicsContext gc) { // style.isSmooth(); // no equivalent gc.setStroke(style.getStroke()); // style.getStrokeType(); // no equivalent - if (style.getStrokeDashArray() != null && !style.getStrokeDashArray().isEmpty()) { - double[] dashes = style.getStrokeDashArray().stream().mapToDouble(Double::doubleValue).toArray(); - gc.setLineDashes(dashes); - } + gc.setLineDashes(toLineDashArray(style.getStrokeDashArray())); gc.setLineDashOffset(style.getStrokeDashOffset()); gc.setLineCap(style.getStrokeLineCap()); gc.setLineJoin(style.getStrokeLineJoin()); @@ -113,4 +130,22 @@ static ChangeListener incrementOnChange(LongProperty counter) { return (obs, old, value) -> counter.set(counter.get() + 1); } + private static double[] toLineDashArray(List numbers) { + if (numbers == null || numbers.isEmpty()) { + return null; + } + FXUtils.assertJavaFxThread(); + double[] array = cachedDashArray; + if (array.length != numbers.size()) { + array = new double[numbers.size()]; + } + int i = 0; + for (Double number : numbers) { + array[i++] = number.doubleValue(); + } + return cachedDashArray = array; + } + // small and only called from JavaFX thread, so we can cache statically + private static double[] cachedDashArray = new double[2]; + } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java index dd9074a88..c5209d4f8 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java @@ -16,7 +16,7 @@ * * @author ennerf */ -public class TextStyle extends Text { +public class TextStyle extends Text implements StyleUtil.ChangeCounter { public TextStyle(String... styles) { StyleUtil.hiddenStyleNode(this, styles); @@ -31,14 +31,10 @@ public void copyStyleTo(GraphicsContext gc) { StyleUtil.copyTextStyle(this, gc); } - public long getChangeCounter() { - return changeCounter.get(); - } - public ReadOnlyLongProperty changeCounterProperty() { return changeCounter; } - LongProperty changeCounter = new SimpleLongProperty(0); + private final LongProperty changeCounter = new SimpleLongProperty(0); } diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index b43d34b8a..29169081a 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -141,40 +141,34 @@ -fx-fill: rgba(65, 110, 244, 0.4078431373); } -.chart-major-grid-lines { +.grid-renderer .chart-major-grid-lines { -fx-stroke: derive(-fx-background, -10%); -fx-stroke-dash-array: 4.5, 2.5; -fx-stroke-width: 0.5; visibility: visible; } - -.chart-major-grid-lines:withMinor { +.grid-renderer .chart-major-grid-lines:withMinor { -fx-stroke: derive(-fx-background, -20%); } - -.chart-minor-grid-lines { +.grid-renderer .chart-minor-grid-lines { -fx-stroke: derive(-fx-background, -5%); + -fx-stroke-dash-array: 4.5, 2.5; -fx-stroke-width: 0.5; visibility: hidden; } - -.chart-grid-line-on-top { +.grid-renderer .chart-grid-line-on-top { visibility: visible; /* 'visible' for front, 'hidden' for back */ } - -.chart-major-vertical-lines .chart-major-grid-lines { +.grid-renderer .chart-major-vertical-lines .chart-major-grid-lines { /* use this to override v-specific settings */ } - -.chart-major-horizontal-grid-lines .chart-major-grid-lines { +.grid-renderer .chart-major-horizontal-grid-lines .chart-major-grid-lines { /* use this to override h-specific settings */ } - -.chart-minor-vertical-grid-lines .chart-minor-grid-lines { +.grid-renderer .chart-minor-vertical-grid-lines .chart-minor-grid-lines { /* use this to override v-specific settings */ } - -.chart-minor-horizontal-grid-lines .chart-minor-grid-lines { +.grid-renderer .chart-minor-horizontal-grid-lines .chart-minor-grid-lines { /* use this to override h-specific settings */ } diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss index fb38da21d..908ea3036 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss @@ -144,41 +144,51 @@ $null: NULL; // lowercase gets removed from Sass, but uppercase seems to also be -fx-fill: #416ef468; } -.chart-major-grid-lines { - -fx-stroke: derive(-fx-background, -10%); - -fx-stroke-dash-array: 4.5, 2.5; - -fx-stroke-width: 0.5; - visibility: visible; -} +.grid-renderer { -.chart-major-grid-lines:withMinor { - -fx-stroke: derive(-fx-background, -20%); -} + $DEFAULT_GRID_DASH_PATTERN: 4.5, 2.5; + $DEFAULT_LINE_WIDTH: 0.5; -.chart-minor-grid-lines { - -fx-stroke: derive(-fx-background, -5%); - -fx-stroke-width: 0.5; - visibility: hidden; -} + .chart-major-grid-lines { + -fx-stroke: derive(-fx-background, -10%); + -fx-stroke-dash-array: $DEFAULT_GRID_DASH_PATTERN; + -fx-stroke-width: $DEFAULT_LINE_WIDTH; + visibility: visible; -.chart-grid-line-on-top { - visibility: visible; /* 'visible' for front, 'hidden' for back */ -} + // less subtle if we also show the minor grid lines + &:withMinor { + -fx-stroke: derive(-fx-background, -20%); + } -.chart-major-vertical-lines .chart-major-grid-lines { - /* use this to override v-specific settings */ -} + } -.chart-major-horizontal-grid-lines .chart-major-grid-lines { - /* use this to override h-specific settings */ -} + .chart-minor-grid-lines { + -fx-stroke: derive(-fx-background, -5%); + -fx-stroke-dash-array: $DEFAULT_GRID_DASH_PATTERN; + -fx-stroke-width: $DEFAULT_LINE_WIDTH; + visibility: hidden; + } -.chart-minor-vertical-grid-lines .chart-minor-grid-lines { - /* use this to override v-specific settings */ -} + .chart-grid-line-on-top { + visibility: visible; /* 'visible' for front, 'hidden' for back */ + } + + .chart-major-vertical-lines .chart-major-grid-lines { + /* use this to override v-specific settings */ + } + + .chart-major-horizontal-grid-lines .chart-major-grid-lines { + /* use this to override h-specific settings */ + } + + .chart-minor-vertical-grid-lines .chart-minor-grid-lines { + /* use this to override v-specific settings */ + } + + .chart-minor-horizontal-grid-lines .chart-minor-grid-lines { + /* use this to override h-specific settings */ + } -.chart-minor-horizontal-grid-lines .chart-minor-grid-lines { - /* use this to override h-specific settings */ } .chart-alternative-column-fill { From 67d9da0a5122edab1d9221b722fbb33a0c027dff Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sat, 5 Aug 2023 01:39:24 +0200 Subject: [PATCH 03/19] cleaned up axis styling (cherry picked from commit cede075bccbfc78321252f691f7d1cd08fd63920) --- .../chartfx/axes/spi/AbstractAxis.java | 9 +- .../axes/spi/AbstractAxisParameter.java | 38 ++--- .../io/fair_acc/chartfx/ui/css/LineStyle.java | 16 +- .../io/fair_acc/chartfx/ui/css/StyleUtil.java | 158 ++++++++++++------ .../io/fair_acc/chartfx/ui/css/TextStyle.java | 23 +-- .../resources/io/fair_acc/chartfx/chart.css | 34 +++- .../resources/io/fair_acc/chartfx/chart.scss | 43 +++-- 7 files changed, 196 insertions(+), 125 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 5a237dd08..5bb18ecc8 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -40,6 +40,7 @@ public abstract class AbstractAxis extends AbstractAxisParameter implements Axis protected static final double MAX_NARROW_FONT_SCALE = 1.0; protected static final double MIN_TICK_GAP = 1.0; private final transient Canvas canvas = new ResizableCanvas(); + private boolean drawAxisLabel; private boolean shiftLabels; protected boolean labelOverlap; protected double scaleFont = 1.0; @@ -426,6 +427,7 @@ private double computePrefSize(final double axisLength) { scaleFont = 1.0; maxLabelHeight = 0; maxLabelWidth = 0; + drawAxisLabel = false; shiftLabels = false; labelOverlap = false; @@ -460,6 +462,7 @@ private double computePrefSize(final double axisLength) { // Size of the axis label w/ units final double axisLabelSize = getAxisLabelSize(); + drawAxisLabel = axisLabelSize > 0; // Remove gaps between empty space final double tickLabelGap = tickLabelSize <= 0 ? 0 : getTickLabelGap(); @@ -525,7 +528,7 @@ private double computeMinSize() { private double getAxisLabelSize() { final Text axisLabel = getAxisLabel(); - if (!PropUtil.isNullOrEmpty(axisLabel.getText())) { + if (axisLabel.isVisible() && !PropUtil.isNullOrEmpty(axisLabel.getText())) { var bounds = axisLabel.getBoundsInParent(); return getSide().isHorizontal() ? bounds.getHeight() : bounds.getWidth(); } @@ -685,6 +688,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; 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..5fe382daf 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; @@ -26,7 +24,6 @@ import io.fair_acc.chartfx.Chart; import io.fair_acc.chartfx.axes.Axis; import io.fair_acc.chartfx.axes.AxisLabelOverlapPolicy; -import io.fair_acc.chartfx.ui.css.CssPropertyFactory; import io.fair_acc.chartfx.ui.geometry.Side; /** @@ -43,13 +40,6 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { */ 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), @@ -189,15 +179,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 StyleGroup styles = new StyleGroup(this, "axis"); + private final transient LineStyle majorTickStyle = styles.newLineStyle("axis-tick-mark"); + private final transient LineStyle minorTickStyle = styles.newLineStyle("axis-minor-tick-mark"); + private final transient TextStyle tickLabelStyle = styles.newTextStyle("axis-tick-label"); + private final transient TextStyle axisLabel = styles.newTextStyle("axis-label"); protected final transient DoubleArrayList majorTickMarkValues = new DoubleArrayList(); protected final transient DoubleArrayList minorTickMarkValues = new DoubleArrayList(); @@ -234,7 +220,7 @@ public AbstractAxisParameter() { /** * axis label alignment */ - private final transient StyleableObjectProperty axisLabelTextAlignment = CSS.createObjectProperty(this, "axisLabelTextAlignment", TextAlignment.CENTER, StyleConverter.getEnumConverter(TextAlignment.class)); + private final transient ObjectProperty axisLabelTextAlignment = axisLabel.textAlignmentProperty(); /** * The axis label @@ -244,7 +230,7 @@ public AbstractAxisParameter() { /** * true if tick marks should be displayed */ - private final transient StyleableBooleanProperty tickMarkVisible = CSS.createBooleanProperty(this, "tickMarkVisible", true); + private final transient BooleanProperty tickMarkVisible = majorTickStyle.visibleProperty(); /** * true if tick mark labels should be displayed @@ -279,12 +265,12 @@ 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); + private final transient ObjectProperty tickLabelFont = tickLabelStyle.fontProperty(); /** * The fill for all tick labels */ - private final transient StyleableObjectProperty tickLabelFill = CSS.createObjectProperty(this, "tickLabelFill", Color.BLACK, StyleConverter.getPaintConverter()); + private final transient ObjectProperty tickLabelFill = tickLabelStyle.fillProperty(); /** * The gap between tick marks and the canvas area @@ -324,12 +310,12 @@ public AbstractAxisParameter() { /** * Rotation in degrees of tick mark labels from their normal horizontal. */ - protected final transient StyleableDoubleProperty tickLabelRotation = CSS.createDoubleProperty(this, "tickLabelRotation", 0.0); + protected final transient DoubleProperty tickLabelRotation = tickLabelStyle.rotateProperty(); /** * true if minor tick marks should be displayed */ - private final transient StyleableBooleanProperty minorTickVisible = CSS.createBooleanProperty(this, "minorTickVisible", true); + private final transient BooleanProperty minorTickVisible = minorTickStyle.visibleProperty(); /** * The scale factor from data units to visual units 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 4b6b3af9f..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,27 +3,21 @@ import javafx.beans.property.LongProperty; import javafx.beans.property.ReadOnlyLongProperty; import javafx.beans.property.SimpleLongProperty; -import javafx.scene.canvas.GraphicsContext; import javafx.scene.shape.Line; /** - * 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 implements StyleUtil.ChangeCounter { +public class LineStyle extends Line implements StyleUtil.StyleNode { public LineStyle(String... styles) { StyleUtil.styleNode(this, styles); - StyleUtil.registerShapeListener(this, StyleUtil.incrementOnChange(changeCounter)); + StyleUtil.forEachStyleProp(this, StyleUtil.incrementOnChange(changeCounter)); } - public void copyStyleTo(GraphicsContext gc) { - StyleUtil.copyShapeStyle(this, gc); + @Override + public String toString() { + return StyleUtil.toStyleString(this); } public ReadOnlyLongProperty changeCounterProperty() { 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 cce286357..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 @@ -2,19 +2,21 @@ import io.fair_acc.chartfx.utils.FXUtils; import io.fair_acc.chartfx.utils.PropUtil; -import javafx.application.Platform; 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 @@ -26,11 +28,30 @@ public class StyleUtil { private StyleUtil() { } - public interface ChangeCounter { + /** + * 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(); } @@ -68,66 +89,101 @@ 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 - // rotate, translate, etc. would mess up the coordinate frame - gc.setGlobalAlpha(style.getOpacity()); - } - - static void registerNodeListener(Node style, ChangeListener listener) { - style.opacityProperty().addListener(listener); - style.rotateProperty().addListener(listener); - style.visibleProperty().addListener(listener); - } + action.accept(node.visibleProperty()); + action.accept(node.rotateProperty()); + action.accept(node.opacityProperty()); - static void copyShapeStyle(Shape style, GraphicsContext gc) { // https://docs.oracle.com/javafx/2/api/javafx/scene/doc-files/cssref.html#shape - gc.setFill(style.getFill()); - // style.isSmooth(); // no equivalent - gc.setStroke(style.getStroke()); - // style.getStrokeType(); // no equivalent - gc.setLineDashes(toLineDashArray(style.getStrokeDashArray())); - gc.setLineDashOffset(style.getStrokeDashOffset()); - gc.setLineCap(style.getStrokeLineCap()); - gc.setLineJoin(style.getStrokeLineJoin()); - gc.setMiterLimit(style.getStrokeMiterLimit()); - gc.setLineWidth(style.getStrokeWidth()); - copyNodeStyle(style, gc); + 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()); + } + } } - static void registerShapeListener(Shape style, ChangeListener listener) { - style.fillProperty().addListener(listener); - style.strokeProperty().addListener(listener); - Bindings.size(style.getStrokeDashArray()).addListener(listener); - style.strokeDashOffsetProperty().addListener(listener); - style.strokeLineCapProperty().addListener(listener); - style.strokeLineJoinProperty().addListener(listener); - style.strokeMiterLimitProperty().addListener(listener); - style.strokeWidthProperty().addListener(listener); - registerNodeListener(style, listener); + public static void copyStyle(Node style, GraphicsContext gc) { + // rotate, translate, etc. would mess up the coordinate frame + gc.setGlobalAlpha(style.getOpacity()); + + if (style instanceof Shape) { + Shape shape = (Shape) style; + gc.setFill(shape.getFill()); + // style.isSmooth(); // no equivalent + gc.setStroke(shape.getStroke()); + // style.getStrokeType(); // no equivalent + gc.setLineDashes(toLineDashArray(shape.getStrokeDashArray())); + gc.setLineDashOffset(shape.getStrokeDashOffset()); + gc.setLineCap(shape.getStrokeLineCap()); + gc.setLineJoin(shape.getStrokeLineJoin()); + gc.setMiterLimit(shape.getStrokeMiterLimit()); + gc.setLineWidth(shape.getStrokeWidth()); + + if (style instanceof Text) { + Text text = (Text) style; + gc.setFont(text.getFont()); + gc.setFontSmoothingType(text.getFontSmoothingType()); + // style.isStrikethrough(); // no equivalent + gc.setTextAlign(text.getTextAlignment()); + gc.setTextBaseline(text.getTextOrigin()); + // style.isUnderline(); // no equivalent + } + } } - static void copyTextStyle(Text style, GraphicsContext gc) { - // https://docs.oracle.com/javafx/2/api/javafx/scene/doc-files/cssref.html#text - gc.setFont(style.getFont()); - gc.setFontSmoothingType(style.getFontSmoothingType()); - // style.isStrikethrough(); // no equivalent - gc.setTextAlign(style.getTextAlignment()); - gc.setTextBaseline(style.getTextOrigin()); - // style.isUnderline(); // no equivalent - copyShapeStyle(style, gc); + public static String toStyleString(Node style) { + StringBuilder builder = new StringBuilder(); + for (String styleClass : style.getStyleClass()) { + builder.append(".").append(styleClass).append(", "); + } + removeEndIf(builder, ", "); + builder.append(" {"); + forEachStyleProp(style, obs -> { + if (!(obs instanceof StyleableProperty)) { + return; + } + var prop = (StyleableProperty) obs; + builder.append("\n ").append(prop.getCssMetaData().getProperty()) + .append(": ").append(prop.getValue()).append(";"); + }); + builder.append("\n}"); + return builder.toString(); } - static void registerTextListener(Text style, ChangeListener listener) { - style.fontProperty().addListener(listener); - style.fontSmoothingTypeProperty().addListener(listener); - style.textAlignmentProperty().addListener(listener); - style.textOriginProperty().addListener(listener); - registerShapeListener(style, listener); + private static boolean removeEndIf(StringBuilder builder, String end) { + if (builder.length() < end.length()) { + return false; + } + for (int i = 0; i < end.length(); i++) { + char a = end.charAt(end.length() - 1 - i); + char b = builder.charAt(builder.length() - 1 - i); + if (a != b) { + return false; + } + } + builder.setLength(builder.length() - end.length()); + return true; } - static ChangeListener incrementOnChange(LongProperty counter) { - return (obs, old, value) -> counter.set(counter.get() + 1); + static Consumer> incrementOnChange(LongProperty counter) { + ChangeListener listener = (obs, old, value) -> counter.set(counter.get() + 1); + return prop -> prop.addListener(listener); } private static double[] toLineDashArray(List numbers) { diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java index c5209d4f8..315aa5023 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java @@ -3,32 +3,21 @@ import javafx.beans.property.LongProperty; import javafx.beans.property.ReadOnlyLongProperty; import javafx.beans.property.SimpleLongProperty; -import javafx.beans.value.ChangeListener; -import javafx.scene.canvas.GraphicsContext; import javafx.scene.text.Text; /** - * 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 TextStyle extends Text implements StyleUtil.ChangeCounter { +public class TextStyle extends Text implements StyleUtil.StyleNode { public TextStyle(String... styles) { - StyleUtil.hiddenStyleNode(this, styles); - StyleUtil.registerTextListener(this, StyleUtil.incrementOnChange(changeCounter)); + StyleUtil.styleNode(this, styles); + StyleUtil.forEachStyleProp(this, StyleUtil.incrementOnChange(changeCounter)); } - /** - * Copies all style parameters except for rotate - * @param gc target context - */ - public void copyStyleTo(GraphicsContext gc) { - StyleUtil.copyTextStyle(this, gc); + @Override + public String toString() { + return StyleUtil.toStyleString(this); } public ReadOnlyLongProperty changeCounterProperty() { diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index 29169081a..80eb80ca7 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -7,15 +7,6 @@ -fx-text-alignment: center; } -.axis-label { - -fx-axis-label-alignment: center; - -fx-stroke: transparent; -} - -.axis-tick-label { - -fx-stroke: transparent; -} - .chart, .chart-content, .chart-plot-area { /* Set some reasonable default sizes so users @@ -141,6 +132,31 @@ -fx-fill: rgba(65, 110, 244, 0.4078431373); } +.axis { + overlapPolicy: SKIP_ALT; + axisCenterPosition: 0.5; +} +.axis .axis-label, .axis .axis-tick-label { + visibility: visible; + -fx-font-family: "System"; + -fx-font-smoothing-type: gray; + -fx-stroke: transparent; + -fx-fill: black; + -fx-text-alignment: CENTER; + -fx-rotate: 0; +} +.axis .axis-label { + -fx-font-size: 12px; +} +.axis .axis-tick-label { + -fx-font-size: 10px; +} +.axis .axis-tick-mark, .axis .axis-minor-tick-mark { + visibility: visible; + -fx-stroke: #c3c3c3; + -fx-stroke-width: 1; +} + .grid-renderer .chart-major-grid-lines { -fx-stroke: derive(-fx-background, -10%); -fx-stroke-dash-array: 4.5, 2.5; diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss index 908ea3036..07848afa4 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss @@ -1,4 +1,4 @@ -$null: NULL; // lowercase gets removed from Sass, but uppercase seems to also be interpreted as null +$null: NULL; // lowercase gets removed from Sass. Maybe replace after generation? .chart-datapoint-tooltip-label { -fx-background-color: rgb(153, 204, 204); @@ -9,15 +9,6 @@ $null: NULL; // lowercase gets removed from Sass, but uppercase seems to also be -fx-text-alignment: center; } -.axis-label { - -fx-axis-label-alignment: center; - -fx-stroke: transparent; -} - -.axis-tick-label { - -fx-stroke: transparent; -} - .chart, .chart-content, .chart-plot-area { /* Set some reasonable default sizes so users @@ -144,6 +135,38 @@ $null: NULL; // lowercase gets removed from Sass, but uppercase seems to also be -fx-fill: #416ef468; } +.axis { + + // TODO: were any of these custom properties tested to work? + overlapPolicy: SKIP_ALT; + axisCenterPosition: 0.5; + + .axis-label, .axis-tick-label { + visibility: visible; + -fx-font-family: 'System'; + -fx-font-smoothing-type: gray; // gray | lcd + -fx-stroke: transparent; + -fx-fill: black; + -fx-text-alignment: CENTER; + -fx-rotate: 0; + } + + .axis-label { + -fx-font-size: 12px; + } + + .axis-tick-label { + -fx-font-size: 10px; + } + + .axis-tick-mark, .axis-minor-tick-mark { + visibility: visible; + -fx-stroke: #c3c3c3ff; + -fx-stroke-width: 1.0; + } + +} + .grid-renderer { $DEFAULT_GRID_DASH_PATTERN: 4.5, 2.5; From 12500a0effeb09c7fe3c81b19b514747bb961892 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sat, 5 Aug 2023 16:00:40 +0200 Subject: [PATCH 04/19] added all stylable properties for axes (cherry picked from commit 5b1d94ad4bf70ae7869b66dc78f38563e796b43e) --- .../main/java/io/fair_acc/chartfx/Chart.java | 29 ++++----- .../java/io/fair_acc/chartfx/XYChart.java | 2 +- .../axes/spi/AbstractAxisParameter.java | 2 +- .../fair_acc/chartfx/ui/css/StyleGroup.java | 2 +- .../resources/io/fair_acc/chartfx/chart.css | 26 +++++++- .../resources/io/fair_acc/chartfx/chart.scss | 64 +++++++++++++++++-- 6 files changed, 96 insertions(+), 29 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index 76828c16a..113b7da1a 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -123,14 +123,14 @@ public abstract class Chart extends Region implements EventSource { protected final Group pluginsArea = Chart.createChildGroup(); // Area where plots get drawn - protected final Pane plotBackground = new Pane(); - protected final HiddenSidesPane plotArea = new HiddenSidesPane(); - protected final Pane plotForeGround = new Pane(); + protected final Pane plotBackground = StyleUtil.addStyles(new Pane(), "chart-plot-background"); + protected final HiddenSidesPane plotArea = StyleUtil.addStyles(new HiddenSidesPane(), "chart-plot-content"); + protected final Pane plotForeGround = StyleUtil.addStyles(new Pane(), "chart-plot-foreground"); // Outer chart elements - protected final ChartPane measurementPane = new ChartPane(); - protected final ChartPane titleLegendPane = new ChartPane(); - protected final ChartPane axesAndCanvasPane = new ChartPane(); + protected final ChartPane measurementPane = StyleUtil.addStyles(new ChartPane(), "chart-measurement-pane"); + protected final ChartPane titleLegendPane = StyleUtil.addStyles(new ChartPane(),"chart-title-pane", "chart-legend-pane"); + protected final ChartPane axesAndCanvasPane = StyleUtil.addStyles(new ChartPane(), "chart-content"); // Outer area with hidden toolbars protected final HiddenSidesPane menuPane = new HiddenSidesPane(); @@ -149,8 +149,10 @@ public abstract class Chart extends Region implements EventSource { // > canvas foreground // > plugins // > plot background/foreground - plotArea.setContent(StyleUtil.addStyles(new PlotAreaPane(getCanvas(), getCanvasForeground(), pluginsArea), "chart-plot-area")); - axesAndCanvasPane.addCenter(getPlotBackground(), getPlotArea(), getPlotForeground()); + StyleUtil.addStyles(this, "chart"); + var canvasPane = StyleUtil.addStyles(new PlotAreaPane(canvas, canvasForeground, pluginsArea), "chart-plot-area"); + plotArea.setContent(canvasPane); + axesAndCanvasPane.addCenter(plotBackground, plotArea, plotForeGround); titleLegendPane.addCenter(axesAndCanvasPane); measurementPane.addCenter(titleLegendPane); menuPane.setContent(measurementPane); @@ -168,7 +170,7 @@ public abstract class Chart extends Region implements EventSource { getAxes().addListener(axesChangeListenerLocal); } - protected final Label titleLabel = new Label(); + protected final Label titleLabel = StyleUtil.addStyles(new Label(), "chart-title"); protected final StringProperty title = new StringPropertyBase() { @Override @@ -323,10 +325,6 @@ public Chart(Axis... axes) { getCanvasForeground().toFront(); pluginsArea.toFront(); - plotArea.getStyleClass().setAll("plot-content"); - - plotBackground.getStyleClass().setAll("chart-plot-background"); - if (!canvas.isCache()) { canvas.setCache(true); canvas.setCacheHint(CacheHint.QUALITY); @@ -357,11 +355,6 @@ public Chart(Axis... axes) { getLegend().getNode().setVisible(visible); getLegend().getNode().setManaged(visible); }); - - // set CSS stuff - titleLabel.getStyleClass().add("chart-title"); - getStyleClass().add("chart"); - axesAndCanvasPane.getStyleClass().add("chart-content"); } @Override diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java index 2e83ea329..d7213a971 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java @@ -86,7 +86,7 @@ public XYChart(final Axis... axes) { getAxes().add(axis); } - getChildren().add(0, gridRenderer); + getChildren().add(gridRenderer); PropUtil.runOnChange(getBitState().onAction(ChartBits.ChartCanvas), gridRenderer.getHorizontalMajorGrid().changeCounterProperty(), gridRenderer.getHorizontalMinorGrid().changeCounterProperty(), 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 5fe382daf..3b5bd25d7 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 @@ -235,7 +235,7 @@ public AbstractAxisParameter() { /** * true if tick mark labels should be displayed */ - private final transient StyleableBooleanProperty tickLabelsVisible = CSS.createBooleanProperty(this, "tickLabelsVisible", true); + private final transient BooleanProperty tickLabelsVisible = tickLabelStyle.visibleProperty(); /** * The length of tick mark lines 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 index 3c08b8b2b..3cc754827 100644 --- 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 @@ -32,7 +32,7 @@ public StyleGroup(ObservableList children) { StyleUtil.hiddenStyleNode(this); setAutoSizeChildren(false); relocate(0, 0); - children.add(0, this); + children.add(this); } public LineStyle newLineStyle(String... styles) { diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index 80eb80ca7..c5e02302e 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -133,8 +133,21 @@ } .axis { - overlapPolicy: SKIP_ALT; - axisCenterPosition: 0.5; + -fx-border-width: 0px; + -fx-auto-ranging: true; + -fx-auto-grow-ranging: false; + -fx-auto-range-rounding: false; + -fx-axis-center-position: 0.5; + -fx-overlap-policy: SKIP_ALT; + -fx-axis-padding: 15px; + -fx-tick-label-spacing: 3px; + -fx-max-major-tick-label-count: 20; + -fx-minor-tick-count: 10; + -fx-tick-mark-gap: 0px; + -fx-tick-length: 8px; + -fx-minor-tick-length: 5px; + -fx-tick-label-gap: 3px; + -fx-axis-label-gap: 3px; } .axis .axis-label, .axis .axis-tick-label { visibility: visible; @@ -142,13 +155,20 @@ -fx-font-smoothing-type: gray; -fx-stroke: transparent; -fx-fill: black; - -fx-text-alignment: CENTER; -fx-rotate: 0; } +.axis:left .axis-label, .axis:right .axis-label, .axis:center-ver .axis-label { + -fx-rotate: -90; +} +.axis:center-ver .axis-label, .axis:center-hor .axis-label { + -fx-text-alignment: RIGHT; +} .axis .axis-label { + -fx-text-alignment: CENTER; -fx-font-size: 12px; } .axis .axis-tick-label { + -fx-text-alignment: NULL; -fx-font-size: 10px; } .axis .axis-tick-mark, .axis .axis-minor-tick-mark { diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss index 07848afa4..b3c42f815 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss @@ -1,4 +1,4 @@ -$null: NULL; // lowercase gets removed from Sass. Maybe replace after generation? +$null: NULL; // null gets removed from Sass. Maybe replace after generation? .chart-datapoint-tooltip-label { -fx-background-color: rgb(153, 204, 204); @@ -135,11 +135,51 @@ $null: NULL; // lowercase gets removed from Sass. Maybe replace after generation -fx-fill: #416ef468; } +// Full Pane hierarchy +.chart { + .chart-measurement-pane { + .chart-title-pane, .chart-legend-pane { + .chart-content { + .axis { + } + .chart-plot-background { + } + .chart-plot-content { + .chart-plot-area { + } + } + .chart-plot-foreground { + } + } + } + } + .grid-renderer { + } +} + .axis { - // TODO: were any of these custom properties tested to work? - overlapPolicy: SKIP_ALT; - axisCenterPosition: 0.5; + -fx-border-width: 0px; + + // Determining data range + -fx-auto-ranging: true; // always updates to the range of the data + -fx-auto-grow-ranging: false; // range updates, but won't shrink + -fx-auto-range-rounding: false; // rounds range up + + // Tick spacing (along the width on horizontal axes) + -fx-axis-center-position: 0.5; // only for centered axes: relative position of the center line + -fx-overlap-policy: SKIP_ALT; // DO_NOTHING, SKIP_ALT, NARROW_FONT, SHIFT_ALT, FORCED_SHIFT_ALT + -fx-axis-padding: 15px; // overdraw area to the sides of the axis + -fx-tick-label-spacing: 3px; // min spacing between two tick labels + -fx-max-major-tick-label-count: 20; + -fx-minor-tick-count: 10; + + // Axis size (maps to height on horizontal axes) + -fx-tick-mark-gap: 0px; // gap between canvas and axis line + -fx-tick-length: 8px; // length of tick mark lines + -fx-minor-tick-length: 5px; // length of minor tick mark lines + -fx-tick-label-gap: 3px; // gap between tick marks and labels + -fx-axis-label-gap: 3px; // gap between tick labels and axis label .axis-label, .axis-tick-label { visibility: visible; @@ -147,18 +187,32 @@ $null: NULL; // lowercase gets removed from Sass. Maybe replace after generation -fx-font-smoothing-type: gray; // gray | lcd -fx-stroke: transparent; -fx-fill: black; - -fx-text-alignment: CENTER; -fx-rotate: 0; } + &:left, &:right, &:center-ver { + .axis-label { + -fx-rotate: -90; + } + } + + &:center-ver, &:center-hor { + .axis-label { + -fx-text-alignment: RIGHT; + } + } + .axis-label { + -fx-text-alignment: CENTER; -fx-font-size: 12px; } .axis-tick-label { + -fx-text-alignment: $null; -fx-font-size: 10px; } + .axis-tick-mark, .axis-minor-tick-mark { visibility: visible; -fx-stroke: #c3c3c3ff; From 71ed490e4dd8ab24c33268570e563188f2e978c9 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sat, 5 Aug 2023 16:13:20 +0200 Subject: [PATCH 05/19] added chart properties (seem broken) (cherry picked from commit 171baf51b620bf6fee415a45d7bdc4c56d90b6b8) --- .../src/main/resources/io/fair_acc/chartfx/chart.css | 7 +++++++ .../src/main/resources/io/fair_acc/chartfx/chart.scss | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index c5e02302e..e39954bbe 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -132,6 +132,13 @@ -fx-fill: rgba(65, 110, 244, 0.4078431373); } +.chart { + -fx-title-side: TOP; + -fx-tool-bar-side: TOP; + -fx-legend-side: BOTTOM; + -fx-legend-visibility: true; +} + .axis { -fx-border-width: 0px; -fx-auto-ranging: true; diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss index b3c42f815..38d014002 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss @@ -157,6 +157,16 @@ $null: NULL; // null gets removed from Sass. Maybe replace after generation? } } +// XY Chart styles +.chart { + // TODO: they don't seem to work and maybe we should create panes with a side property? deprecate these? + -fx-title-side: TOP; + -fx-tool-bar-side: TOP; + -fx-legend-side: BOTTOM; + -fx-legend-visibility: true; +} + +// Axis styles .axis { -fx-border-width: 0px; From 88148a0e50e1db61ff36eae11b751961a2b27745 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sat, 5 Aug 2023 18:50:16 +0200 Subject: [PATCH 06/19] minor cleanup (cherry picked from commit 3b481ac3d26929c5de03b8ef12a1cf0ae0f53ec8) --- .../resources/io/fair_acc/chartfx/chart.css | 43 ++++++--------- .../resources/io/fair_acc/chartfx/chart.scss | 52 +++++++++++-------- 2 files changed, 46 insertions(+), 49 deletions(-) diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index e39954bbe..cbdb7cc53 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -7,22 +7,6 @@ -fx-text-alignment: center; } -.chart, .chart-content, .chart-plot-area { - /* - Set some reasonable default sizes so users - can override them via CSS and JavaFX panes - won't flicker (usually +/- 1px) or act odd. - */ - -fx-min-height: 100px; - -fx-min-width: 100px; - -fx-pref-height: 500px; - -fx-pref-width: 500px; - -fx-max-width: 4096px; - -fx-max-height: 4096px; - /* no padding by default */ - -fx-padding: 0px; -} - .chart-crosshair-path { -fx-stroke-width: 1; } @@ -69,7 +53,6 @@ .chart-series-line { -fx-stroke-width: 1px; - -fx-effect: NULL; } .value-indicator-label { @@ -132,6 +115,21 @@ -fx-fill: rgba(65, 110, 244, 0.4078431373); } +/* + Set some reasonable default sizes so users + can override them via CSS and JavaFX panes + won't flicker (usually +/- 1px) or act odd. + */ +.chart, .chart-content, .chart-plot-area { + -fx-padding: 0px; + -fx-min-width: 100px; + -fx-min-height: 100px; + -fx-pref-width: 600px; + -fx-pref-height: 450px; + -fx-max-height: 4096px; + -fx-max-width: 4096px; +} + .chart { -fx-title-side: TOP; -fx-tool-bar-side: TOP; @@ -175,7 +173,6 @@ -fx-font-size: 12px; } .axis .axis-tick-label { - -fx-text-alignment: NULL; -fx-font-size: 10px; } .axis .axis-tick-mark, .axis .axis-minor-tick-mark { @@ -215,16 +212,6 @@ /* use this to override h-specific settings */ } -.chart-alternative-column-fill { - -fx-fill: NULL; - -fx-stroke: NULL; -} - -.chart-alternative-row-fill { - -fx-fill: NULL; - -fx-stroke: NULL; -} - .chart-vertical-zero-line, .chart-horizontal-zero-line { -fx-stroke: derive(-fx-text-background-color, 40%); diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss index 38d014002..3579e8d69 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss @@ -1,4 +1,4 @@ -$null: NULL; // null gets removed from Sass. Maybe replace after generation? +$null: null; // null gets removed from Sass. Maybe create a placeholder and replace after generation? .chart-datapoint-tooltip-label { -fx-background-color: rgb(153, 204, 204); @@ -9,23 +9,6 @@ $null: NULL; // null gets removed from Sass. Maybe replace after generation? -fx-text-alignment: center; } -.chart, .chart-content, .chart-plot-area { - /* - Set some reasonable default sizes so users - can override them via CSS and JavaFX panes - won't flicker (usually +/- 1px) or act odd. - */ - -fx-min-height: 100px; - -fx-min-width: 100px; - -fx-pref-height: 500px; - -fx-pref-width: 500px; - -fx-max-width: 4096px; - -fx-max-height: 4096px; - - /* no padding by default */ - -fx-padding: 0px; -} - .chart-crosshair-path { -fx-stroke-width: 1; } @@ -135,12 +118,20 @@ $null: NULL; // null gets removed from Sass. Maybe replace after generation? -fx-fill: #416ef468; } -// Full Pane hierarchy +// Full Chart hierarchy for reference .chart { .chart-measurement-pane { .chart-title-pane, .chart-legend-pane { .chart-content { .axis { + .axis-tick-mark { + } + .axis-minor-tick-mark { + } + .axis-label { + } + .axis-tick-label { + } } .chart-plot-background { } @@ -153,10 +144,30 @@ $null: NULL; // null gets removed from Sass. Maybe replace after generation? } } } + .grid-renderer { + .chart-major-grid-lines { + } + .chart-minor-grid-lines { + } } } +/* + Set some reasonable default sizes so users + can override them via CSS and JavaFX panes + won't flicker (usually +/- 1px) or act odd. + */ +.chart, .chart-content, .chart-plot-area { + -fx-padding: 0px; + -fx-min-width: 100px; + -fx-min-height: 100px; + -fx-pref-width: 600px; + -fx-pref-height: 450px; + -fx-max-height: 4096px; + -fx-max-width: 4096px; +} + // XY Chart styles .chart { // TODO: they don't seem to work and maybe we should create panes with a side property? deprecate these? @@ -174,7 +185,7 @@ $null: NULL; // null gets removed from Sass. Maybe replace after generation? // Determining data range -fx-auto-ranging: true; // always updates to the range of the data -fx-auto-grow-ranging: false; // range updates, but won't shrink - -fx-auto-range-rounding: false; // rounds range up + -fx-auto-range-rounding: false; // auto range w/ rounding // Tick spacing (along the width on horizontal axes) -fx-axis-center-position: 0.5; // only for centered axes: relative position of the center line @@ -222,7 +233,6 @@ $null: NULL; // null gets removed from Sass. Maybe replace after generation? -fx-font-size: 10px; } - .axis-tick-mark, .axis-minor-tick-mark { visibility: visible; -fx-stroke: #c3c3c3ff; From 5a16cd3bbf65c025559f96614676090bf1ecaea0 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sun, 6 Aug 2023 16:24:51 +0200 Subject: [PATCH 07/19] added more pseudo styles for sides --- .../io/fair_acc/chartfx/ui/geometry/Side.java | 26 +++++++++++++++++++ .../fair_acc/chartfx/ui/layout/ChartPane.java | 4 +++ .../resources/io/fair_acc/chartfx/chart.css | 12 ++++----- .../resources/io/fair_acc/chartfx/chart.scss | 18 +++++-------- 4 files changed, 43 insertions(+), 17 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/geometry/Side.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/geometry/Side.java index 54f783700..609d83031 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/geometry/Side.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/geometry/Side.java @@ -1,5 +1,8 @@ package io.fair_acc.chartfx.ui.geometry; +import javafx.css.PseudoClass; +import javafx.scene.Node; + /** * Re-implementation of JavaFX's {@code javafx.geometry.Side} implementation to also include centre axes. * @@ -57,4 +60,27 @@ public boolean isVertical() { public boolean isCenter() { return this == CENTER_VER || this == CENTER_HOR; } + + public void applyPseudoClasses(Node node) { + node.pseudoClassStateChanged(CSS_TOP, this == TOP); + node.pseudoClassStateChanged(CSS_BOTTOM, this == BOTTOM); + node.pseudoClassStateChanged(CSS_LEFT, this == LEFT); + node.pseudoClassStateChanged(CSS_RIGHT, this == RIGHT); + node.pseudoClassStateChanged(CSS_CENTER_HOR, this == CENTER_HOR); + node.pseudoClassStateChanged(CSS_CENTER_VER, this == CENTER_VER); + node.pseudoClassStateChanged(CSS_HORIZONTAL, isHorizontal()); + node.pseudoClassStateChanged(CSS_VERTICAL, isVertical()); + node.pseudoClassStateChanged(CSS_CENTER, isCenter()); + } + + private static final PseudoClass CSS_TOP = PseudoClass.getPseudoClass("top"); + private static final PseudoClass CSS_BOTTOM = PseudoClass.getPseudoClass("bottom"); + private static final PseudoClass CSS_LEFT = PseudoClass.getPseudoClass("left"); + private static final PseudoClass CSS_RIGHT = PseudoClass.getPseudoClass("right"); + private static final PseudoClass CSS_CENTER_HOR = PseudoClass.getPseudoClass("center-hor"); + private static final PseudoClass CSS_CENTER_VER = PseudoClass.getPseudoClass("center-ver"); + private static final PseudoClass CSS_HORIZONTAL = PseudoClass.getPseudoClass("horizontal"); + private static final PseudoClass CSS_VERTICAL = PseudoClass.getPseudoClass("vertical"); + private static final PseudoClass CSS_CENTER = PseudoClass.getPseudoClass("center"); + } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/ChartPane.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/ChartPane.java index 355ab63dc..ddd83b1a7 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/ChartPane.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/ChartPane.java @@ -5,6 +5,8 @@ import io.fair_acc.chartfx.ui.geometry.Side; import io.fair_acc.chartfx.utils.FXUtils; import io.fair_acc.dataset.spi.fastutil.DoubleArrayList; +import io.fair_acc.dataset.utils.AssertUtils; +import javafx.css.PseudoClass; import javafx.scene.Node; import javafx.scene.layout.Pane; @@ -46,6 +48,8 @@ public static Object getLocation(Node node) { } public static void setSide(Node node, Side value) { + AssertUtils.notNull("Side must not be null", value); + value.applyPseudoClasses(node); FXUtils.setConstraint(node, CHART_ELEMENT, value); } diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index cbdb7cc53..7795dcdfb 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -162,16 +162,16 @@ -fx-fill: black; -fx-rotate: 0; } -.axis:left .axis-label, .axis:right .axis-label, .axis:center-ver .axis-label { - -fx-rotate: -90; -} -.axis:center-ver .axis-label, .axis:center-hor .axis-label { - -fx-text-alignment: RIGHT; -} .axis .axis-label { -fx-text-alignment: CENTER; -fx-font-size: 12px; } +.axis:center .axis-label { + -fx-text-alignment: RIGHT; +} +.axis:vertical .axis-label { + -fx-rotate: -90; +} .axis .axis-tick-label { -fx-font-size: 10px; } diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss index 3579e8d69..33e09d3f0 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss @@ -211,21 +211,17 @@ $null: null; // null gets removed from Sass. Maybe create a placeholder and repl -fx-rotate: 0; } - &:left, &:right, &:center-ver { - .axis-label { - -fx-rotate: -90; - } + .axis-label { + -fx-text-alignment: CENTER; + -fx-font-size: 12px; } - &:center-ver, &:center-hor { - .axis-label { - -fx-text-alignment: RIGHT; - } + &:center .axis-label { + -fx-text-alignment: RIGHT; } - .axis-label { - -fx-text-alignment: CENTER; - -fx-font-size: 12px; + &:vertical .axis-label { + -fx-rotate: -90; } .axis-tick-label { From bd57e64e45c9484eca27decc52a753da0a32245b Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sun, 6 Aug 2023 18:22:33 +0200 Subject: [PATCH 08/19] moved title properties into a dedicated TitleLabel class --- .../main/java/io/fair_acc/chartfx/Chart.java | 59 +++---------- .../axes/spi/AbstractAxisParameter.java | 3 +- .../css/FinancialColorSchemeConfig.java | 4 +- .../chartfx/ui/css/CssPropertyFactory.java | 28 +++++++ .../chartfx/ui/layout/TitleLabel.java | 84 +++++++++++++++++++ .../fair_acc/chartfx/utils/RotatedBounds.java | 80 ++++++++++++++++++ .../resources/io/fair_acc/chartfx/chart.css | 15 +++- .../resources/io/fair_acc/chartfx/chart.scss | 22 ++++- .../java/io/fair_acc/chartfx/ChartTest.java | 11 +-- .../chartfx/plugins/ScreenshotTest.java | 4 +- 10 files changed, 246 insertions(+), 64 deletions(-) create mode 100644 chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/TitleLabel.java create mode 100644 chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/RotatedBounds.java diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index 113b7da1a..ffab15956 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -6,6 +6,7 @@ import java.util.stream.Collectors; import io.fair_acc.chartfx.ui.css.StyleUtil; +import io.fair_acc.chartfx.ui.layout.TitleLabel; import io.fair_acc.chartfx.ui.layout.ChartPane; import io.fair_acc.chartfx.ui.layout.PlotAreaPane; import io.fair_acc.chartfx.ui.*; @@ -29,7 +30,6 @@ import javafx.scene.Node; import javafx.scene.canvas.Canvas; import javafx.scene.control.Control; -import javafx.scene.control.Label; import javafx.scene.layout.*; import javafx.scene.paint.Paint; import javafx.util.Duration; @@ -170,34 +170,7 @@ public abstract class Chart extends Region implements EventSource { getAxes().addListener(axesChangeListenerLocal); } - protected final Label titleLabel = StyleUtil.addStyles(new Label(), "chart-title"); - - protected final StringProperty title = new StringPropertyBase() { - @Override - public Object getBean() { - return Chart.this; - } - - @Override - public String getName() { - return "title"; - } - - @Override - protected void invalidated() { - titleLabel.setText(get()); - } - }; - - /** - * The side of the chart where the title is displayed default Side.TOP - */ - private final StyleableObjectProperty titleSide = CSS.createObjectProperty(this, "titleSide", Side.TOP, false, - StyleConverter.getEnumConverter(Side.class), (oldVal, newVal) -> { - AssertUtils.notNull("Side must not be null", newVal); - ChartPane.setSide(titleLabel, newVal); - return newVal; - }, state.onAction(ChartBits.ChartLayout)); + protected final TitleLabel titleLabel = StyleUtil.addStyles(new TitleLabel(), "chart-title"); /** * The side of the chart where the legend should be displayed default value Side.BOTTOM @@ -346,7 +319,7 @@ public Chart(Axis... axes) { toolBar.registerListener(); menuPane.setTop(getToolBar()); - getTitleLegendPane().addSide(Side.TOP, titleLabel); + getTitleLegendPane().getChildren().add(titleLabel); legendVisibleProperty().addListener((ch, old, visible) -> { if (getLegend() == null) { @@ -503,15 +476,15 @@ public ObservableList getRenderers() { } public final String getTitle() { - return title.get(); + return titleProperty().get(); } - public final ChartPane getTitleLegendPane() { - return titleLegendPane; + public final TitleLabel getTitleLabel() { + return titleLabel; } - public final Side getTitleSide() { - return titleSide.get(); + public final ChartPane getTitleLegendPane() { + return titleLegendPane; } public final FlowPane getToolBar() { @@ -730,15 +703,7 @@ public final void setLegendVisible(final boolean value) { } public final void setTitle(final String value) { - title.set(value); - } - - public final void setTitleSide(final Side value) { - titleSide.set(value); - } - - public final void setTitlePaint(final Paint paint) { - titleLabel.setTextFill(paint); + titleProperty().set(value); } public Chart setToolBarPinned(boolean value) { @@ -758,11 +723,7 @@ public ReadOnlyBooleanProperty showingProperty() { } public final StringProperty titleProperty() { - return title; - } - - public final ObjectProperty titleSideProperty() { - return titleSide; + return titleLabel.textProperty(); } public BooleanProperty toolBarPinnedProperty() { 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 3b5bd25d7..3d338bb47 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 @@ -204,8 +204,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 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..fc6c786b3 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 @@ -214,7 +214,7 @@ 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); } @@ -231,7 +231,7 @@ 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)); } 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/layout/TitleLabel.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/TitleLabel.java new file mode 100644 index 000000000..fc4a83171 --- /dev/null +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/TitleLabel.java @@ -0,0 +1,84 @@ +package io.fair_acc.chartfx.ui.layout; + +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.PropUtil; +import io.fair_acc.chartfx.utils.RotatedBounds; +import javafx.beans.binding.Bindings; +import javafx.css.CssMetaData; +import javafx.css.Styleable; +import javafx.css.StyleableObjectProperty; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; + +import java.util.List; + +/** + * A label that has a styleable side and accounts + * for rotation in the size computations. + * + * @author ennerf + */ +public class TitleLabel extends Label { + + private static final CssPropertyFactory CSS = new CssPropertyFactory<>(Label.getClassCssMetaData()); + + public TitleLabel() { + managedProperty().bind(visibleProperty().and(textProperty().isNotEmpty())); + PropUtil.runOnChange(this::applyCss, sideProperty(), rotateProperty()); + } + + /** + * The side of the chart where the title is displayed default Side.TOP + */ + private final StyleableObjectProperty side = CSS.createSideProperty(this, Side.TOP); + + public static List> getClassCssMetaData() { + return CSS.getCssMetaData(); + } + + @Override + public List> getControlCssMetaData() { + return TitleLabel.getClassCssMetaData(); + } + + @Override + protected double computePrefWidth(double length) { + return getRotate() == 0 ? super.computePrefWidth(length) : + bounds.setSize(super.computePrefWidth(length), super.computePrefHeight(length)) + .rotateCenter(getRotate()) + .getWidth(); + } + + @Override + protected double computePrefHeight(double length) { + return getRotate() == 0 ? super.computePrefHeight(length) : + bounds.setSize(super.computePrefWidth(length), super.computePrefHeight(length)) + .rotateCenter(getRotate()) + .getHeight(); + } + + @Override + public void resizeRelocate(double x, double y, double width, double height) { + // We need to set the bounds rotated so that the label gets computed + // correctly without messing up text cutoff and word wrap etc. + bounds.setBounds(x, y, width, height).rotateCenter(getRotate()); + super.resizeRelocate(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); + } + + public Side getSide() { + return side.get(); + } + + public StyleableObjectProperty sideProperty() { + return side; + } + + public void setSide(Side side) { + this.side.set(side); + } + + private final RotatedBounds bounds = new RotatedBounds(); + +} diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/RotatedBounds.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/RotatedBounds.java new file mode 100644 index 000000000..078858f35 --- /dev/null +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/RotatedBounds.java @@ -0,0 +1,80 @@ +package io.fair_acc.chartfx.utils; + +/** + * Utility class for rotating bounds + * + * @author ennerf + */ +public class RotatedBounds { + + public RotatedBounds setSize(double width, double height) { + this.width = width; + this.height = height; + return this; + } + + public RotatedBounds setBounds(double x, double y, double width, double height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + return this; + } + + /** + * Applies a 2D rotation around the center point. + * See Rotation Matrix + * + * @param rotate angle in degrees + */ + public RotatedBounds rotateCenter(double rotate) { + // adapted from https://stackoverflow.com/a/71878932/3574093 + if (rotate != 0) { + var rot = rotate * DEG_TO_RAD; + var cos = Math.abs(Math.cos(rot)); + var sin = Math.abs(Math.sin(rot)); + + var rotWidth = width * cos + height * sin; + var rotHeight = width * sin + height * cos; + + var rotX = x - (rotWidth - width) / 2; + var rotY = y - (rotHeight - height) / 2; + + setBounds(rotX, rotY, rotWidth, rotHeight); + } + return this; + } + + public double getX() { + return x; + } + + public double getY() { + return y; + } + + public double getWidth() { + return width; + } + + public double getHeight() { + return height; + } + + @Override + public String toString() { + return "RotatedBounds{" + + "x=" + x + + ", y=" + y + + ", width=" + width + + ", height=" + height + + '}'; + } + + private double x = 0; + private double y = 0; + private double width = 0; + private double height = 0; + private static final double DEG_TO_RAD = Math.PI / 180.0; + +} diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index 7795dcdfb..ea7406d49 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -131,11 +131,24 @@ } .chart { - -fx-title-side: TOP; -fx-tool-bar-side: TOP; -fx-legend-side: BOTTOM; -fx-legend-visibility: true; } +.chart .chart-content { + -fx-padding: 5 0 0 0px; +} +.chart .chart-title { + visibility: visible; + -fx-side: top; + -fx-rotate: 0; +} +.chart .chart-title:left { + -fx-rotate: -90; +} +.chart .chart-title:right { + -fx-rotate: 90; +} .axis { -fx-border-width: 0px; diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss index 33e09d3f0..499771a9d 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss @@ -171,10 +171,30 @@ $null: null; // null gets removed from Sass. Maybe create a placeholder and repl // XY Chart styles .chart { // TODO: they don't seem to work and maybe we should create panes with a side property? deprecate these? - -fx-title-side: TOP; -fx-tool-bar-side: TOP; -fx-legend-side: BOTTOM; -fx-legend-visibility: true; + + .chart-content { + // leave some top-axis overdraw-padding if there is no title + -fx-padding: 5 0 0 0px + } + + .chart-title { + visibility: visible; + -fx-side: top; + -fx-rotate: 0; + + &:left { + -fx-rotate: -90; + } + + &:right { + -fx-rotate: 90; + } + + } + } // Axis styles diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/ChartTest.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/ChartTest.java index f94e5c264..d2887aa4a 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/ChartTest.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/ChartTest.java @@ -29,14 +29,14 @@ public void setup() throws Exception { @TestFx public void setTitlePaint() { - chart.setTitlePaint(Color.BLUE); - assertEquals(Color.BLUE, chart.getTitlePaint().getTextFill()); + chart.getTitleLabel().setTextFill(Color.BLUE); + assertEquals(Color.BLUE, chart.getTitleLabel().getTextFill()); } @TestFx public void setTitleSide() { - chart.setTitleSide(Side.RIGHT); - assertEquals(Side.RIGHT, chart.getTitleSide()); + chart.getTitleLabel().setSide(Side.RIGHT); + assertEquals(Side.RIGHT, chart.getTitleLabel().getSide()); } private static class TestChart extends Chart { @@ -52,8 +52,5 @@ protected void axesChanged(ListChangeListener.Change change) { protected void redrawCanvas() { } - public Label getTitlePaint() { - return titleLabel; - } } } diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/ScreenshotTest.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/ScreenshotTest.java index 42b66b7f2..22abd8d0a 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/ScreenshotTest.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/ScreenshotTest.java @@ -140,8 +140,8 @@ public void filenamePatternTests() { assertEquals("testDataSet_4.5_4.500000e-06.png", screenshotPlugin.generateScreenshotName()); screenshotPlugin.setPattern(""); chart.setTitle(""); - chart.setTitleSide(Side.RIGHT); - chart.setTitlePaint(Color.BLUE); + chart.getTitleLabel().setSide(Side.RIGHT); + chart.getTitleLabel().setTextFill(Color.BLUE); assertEquals("testDataSet", screenshotPlugin.generateScreenshotName()); //first data set name chart.getDatasets().clear(); From f6839c858658c3570dd15eaa6d7a5512e7e5f58e Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sun, 6 Aug 2023 19:08:12 +0200 Subject: [PATCH 09/19] moved rotation into a generic utility class --- .../chartfx/ui/layout/TitleLabel.java | 29 +++------ .../fair_acc/chartfx/utils/RotatedRegion.java | 62 +++++++++++++++++++ 2 files changed, 72 insertions(+), 19 deletions(-) create mode 100644 chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/RotatedRegion.java diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/TitleLabel.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/TitleLabel.java index fc4a83171..c74216173 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/TitleLabel.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/TitleLabel.java @@ -1,16 +1,13 @@ package io.fair_acc.chartfx.ui.layout; 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.PropUtil; -import io.fair_acc.chartfx.utils.RotatedBounds; -import javafx.beans.binding.Bindings; +import io.fair_acc.chartfx.utils.RotatedRegion; import javafx.css.CssMetaData; import javafx.css.Styleable; import javafx.css.StyleableObjectProperty; import javafx.scene.control.Label; -import javafx.scene.layout.Region; import java.util.List; @@ -44,27 +41,18 @@ public TitleLabel() { } @Override - protected double computePrefWidth(double length) { - return getRotate() == 0 ? super.computePrefWidth(length) : - bounds.setSize(super.computePrefWidth(length), super.computePrefHeight(length)) - .rotateCenter(getRotate()) - .getWidth(); + protected double computePrefWidth(double height) { + return rotated.computePrefWidth(height); } @Override - protected double computePrefHeight(double length) { - return getRotate() == 0 ? super.computePrefHeight(length) : - bounds.setSize(super.computePrefWidth(length), super.computePrefHeight(length)) - .rotateCenter(getRotate()) - .getHeight(); + protected double computePrefHeight(double width) { + return rotated.computePrefHeight(width); } @Override public void resizeRelocate(double x, double y, double width, double height) { - // We need to set the bounds rotated so that the label gets computed - // correctly without messing up text cutoff and word wrap etc. - bounds.setBounds(x, y, width, height).rotateCenter(getRotate()); - super.resizeRelocate(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); + rotated.resizeRelocate(x, y, width, height); } public Side getSide() { @@ -79,6 +67,9 @@ public void setSide(Side side) { this.side.set(side); } - private final RotatedBounds bounds = new RotatedBounds(); + private final RotatedRegion rotated = new RotatedRegion(this, + super::computePrefWidth, + super::computePrefHeight, + super::resizeRelocate); } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/RotatedRegion.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/RotatedRegion.java new file mode 100644 index 000000000..a5b79d76a --- /dev/null +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/RotatedRegion.java @@ -0,0 +1,62 @@ +package io.fair_acc.chartfx.utils; + +import javafx.scene.Node; + +import java.util.function.DoubleUnaryOperator; + +/** + * Utility class for working with rotating regions/controls that are + * inside layout containers that do not properly account for rotations. + * + * @author ennerf + */ +public class RotatedRegion { + + @FunctionalInterface + public interface ResizeRelocateMethod { + void apply(double x, double y, double width, double height); + } + + public RotatedRegion(Node node, DoubleUnaryOperator computePrefWidth, DoubleUnaryOperator computePrefHeight, ResizeRelocateMethod resizeRelocate) { + this.node = node; + this.computePrefWidth = computePrefWidth; + this.computePrefHeight = computePrefHeight; + this.resizeRelocate = resizeRelocate; + } + + public double computePrefWidth(double length) { + return getRotate() == 0 ? computePrefWidth.applyAsDouble(length) : + bounds.setSize(computePrefWidth.applyAsDouble(length), computePrefHeight.applyAsDouble(length)) + .rotateCenter(getRotate()) + .getWidth(); + } + + public double computePrefHeight(double length) { + return getRotate() == 0 ? computePrefHeight.applyAsDouble(length) : + bounds.setSize(computePrefWidth.applyAsDouble(length), computePrefHeight.applyAsDouble(length)) + .rotateCenter(getRotate()) + .getHeight(); + } + + public void resizeRelocate(double x, double y, double width, double height) { + if (getRotate() == 0) { + resizeRelocate.apply(x, y, width, height); + } else { + // The bounds need to be set rotated so that the underlying container + // computes a layout with appropriate word wrap, cutoff etc. + bounds.setBounds(x, y, width, height).rotateCenter(getRotate()); + resizeRelocate.apply(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); + } + } + + private double getRotate() { + return node.getRotate(); + } + + private final Node node; + private final DoubleUnaryOperator computePrefWidth; + private final DoubleUnaryOperator computePrefHeight; + private final ResizeRelocateMethod resizeRelocate; + private final RotatedBounds bounds = new RotatedBounds(); + +} From da6dde07ad66630d4738d4f1528c092c97b4195a Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Mon, 7 Aug 2023 15:41:33 +0200 Subject: [PATCH 10/19] added comments --- .../main/java/io/fair_acc/chartfx/utils/RotatedBounds.java | 6 ++++-- .../main/java/io/fair_acc/chartfx/utils/RotatedRegion.java | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/RotatedBounds.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/RotatedBounds.java index 078858f35..940ad8e91 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/RotatedBounds.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/RotatedBounds.java @@ -1,7 +1,7 @@ package io.fair_acc.chartfx.utils; /** - * Utility class for rotating bounds + * Utility class for rotating bounding boxes. * * @author ennerf */ @@ -22,7 +22,7 @@ public RotatedBounds setBounds(double x, double y, double width, double height) } /** - * Applies a 2D rotation around the center point. + * Applies a 2D rotation around the center point of the current bounding box. * See Rotation Matrix * * @param rotate angle in degrees @@ -34,9 +34,11 @@ public RotatedBounds rotateCenter(double rotate) { var cos = Math.abs(Math.cos(rot)); var sin = Math.abs(Math.sin(rot)); + // 2D rotation matrix applied to width/height var rotWidth = width * cos + height * sin; var rotHeight = width * sin + height * cos; + // Translate the origin to the new center position var rotX = x - (rotWidth - width) / 2; var rotY = y - (rotHeight - height) / 2; diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/RotatedRegion.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/RotatedRegion.java index a5b79d76a..126859071 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/RotatedRegion.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/RotatedRegion.java @@ -7,6 +7,9 @@ /** * Utility class for working with rotating regions/controls that are * inside layout containers that do not properly account for rotations. + *

+ * Note: it currently works with our custom layout panes, but not + * with some built-in ones that do not call resizeRelocate. * * @author ennerf */ From 66b2123ac42c9e5773dd026216e6bf53b9e85e3a Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Mon, 7 Aug 2023 15:58:34 +0200 Subject: [PATCH 11/19] made draw-grid-on-top a top-level css property --- .../chartfx/renderer/spi/GridRenderer.java | 23 +++++++++++++++---- .../resources/io/fair_acc/chartfx/chart.css | 6 ++--- .../resources/io/fair_acc/chartfx/chart.scss | 5 +--- 3 files changed, 23 insertions(+), 11 deletions(-) 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 6ae12f2f1..d4c68d4e0 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,13 +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; @@ -45,7 +49,7 @@ public class GridRenderer extends Parent implements Renderer { 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 LineStyle drawGridOnTopNode = styles.newLineStyle(STYLE_CLASS_GRID_ON_TOP); + private final StyleableBooleanProperty drawGridOnTop = CSS.createBooleanProperty(this, "drawGridOnTop", true); protected final ObservableList axesList = FXCollections.observableList(new NoDuplicatesList<>()); @@ -121,7 +125,7 @@ public Canvas drawLegendSymbol(DataSet dataSet, int dsIndex, int width, int heig * @return drawOnTop property */ public final BooleanProperty drawOnTopProperty() { - return drawGridOnTopNode.visibleProperty(); + return drawGridOnTop; } protected void drawPolarCircle(final GraphicsContext gc, final Axis yAxis, final double yRange, @@ -340,7 +344,7 @@ public final BooleanProperty horizontalMinorGridLinesVisibleProperty() { * @return drawOnTop state */ public final boolean isDrawOnTop() { - return drawGridOnTopNode.isVisible(); + return drawOnTopProperty().get(); } @Override @@ -367,7 +371,7 @@ public List render(final GraphicsContext gc, final Chart chart, final i * @param state true: draw on top */ public final void setDrawOnTop(boolean state) { - drawGridOnTopNode.setVisible(state); + drawOnTopProperty().set(state); } @Override @@ -403,6 +407,17 @@ public final BooleanProperty verticalMinorGridLinesVisibleProperty() { return verMinorGridStyleNode.visibleProperty(); } + @Override + public List> getCssMetaData() { + return getClassCssMetaData(); + } + + 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); } diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index ea7406d49..0b33cdcd4 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -194,6 +194,9 @@ -fx-stroke-width: 1; } +.grid-renderer { + -fx-draw-grid-on-top: true; +} .grid-renderer .chart-major-grid-lines { -fx-stroke: derive(-fx-background, -10%); -fx-stroke-dash-array: 4.5, 2.5; @@ -209,9 +212,6 @@ -fx-stroke-width: 0.5; visibility: hidden; } -.grid-renderer .chart-grid-line-on-top { - visibility: visible; /* 'visible' for front, 'hidden' for back */ -} .grid-renderer .chart-major-vertical-lines .chart-major-grid-lines { /* use this to override v-specific settings */ } diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss index 499771a9d..7292ad079 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss @@ -261,6 +261,7 @@ $null: null; // null gets removed from Sass. Maybe create a placeholder and repl $DEFAULT_GRID_DASH_PATTERN: 4.5, 2.5; $DEFAULT_LINE_WIDTH: 0.5; + -fx-draw-grid-on-top: true; .chart-major-grid-lines { -fx-stroke: derive(-fx-background, -10%); @@ -282,10 +283,6 @@ $null: null; // null gets removed from Sass. Maybe create a placeholder and repl visibility: hidden; } - .chart-grid-line-on-top { - visibility: visible; /* 'visible' for front, 'hidden' for back */ - } - .chart-major-vertical-lines .chart-major-grid-lines { /* use this to override v-specific settings */ } From 50031084a500a94debc0ae0a8c5b31322bfd34af Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Mon, 7 Aug 2023 17:01:42 +0200 Subject: [PATCH 12/19] made legend fully styleable --- .../main/java/io/fair_acc/chartfx/Chart.java | 58 +------ .../java/io/fair_acc/chartfx/XYChart.java | 3 +- .../io/fair_acc/chartfx/legend/Legend.java | 5 + .../chartfx/legend/spi/DefaultLegend.java | 156 +++++++++--------- .../io/fair_acc/chartfx/utils/FXUtils.java | 17 ++ .../resources/io/fair_acc/chartfx/chart.css | 16 +- .../resources/io/fair_acc/chartfx/chart.scss | 25 ++- .../sample/chart/OscilloscopeAxisSample.java | 2 +- .../sample/chart/SimpleChartSample.java | 2 +- 9 files changed, 151 insertions(+), 133 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index ffab15956..3d6c2e955 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -102,11 +102,6 @@ public abstract class Chart extends Region implements EventSource { */ protected final ChartLayoutAnimator animator = new ChartLayoutAnimator(this); - /** - * When true the chart will display a legend if the chart implementation supports a legend. - */ - private final StyleableBooleanProperty legendVisible = CSS.createBooleanProperty(this, "legendVisible", true, state.onAction(ChartBits.ChartLegend)); - protected final ObservableList axesList = FXCollections.observableList(new NoDuplicatesList<>()); private final Map pluginGroups = new HashMap<>(); private final ObservableList plugins = FXCollections.observableList(new LinkedList<>()); @@ -172,23 +167,6 @@ public abstract class Chart extends Region implements EventSource { protected final TitleLabel titleLabel = StyleUtil.addStyles(new TitleLabel(), "chart-title"); - /** - * The side of the chart where the legend should be displayed default value Side.BOTTOM - */ - private final StyleableObjectProperty legendSide = CSS.createObjectProperty(this, "legendSide", Side.BOTTOM, false, - StyleConverter.getEnumConverter(Side.class), (oldVal, newVal) -> { - AssertUtils.notNull("Side must not be null", newVal); - - final Legend legend = getLegend(); - if (legend == null) { - return newVal; - } - ChartPane.setSide(legend.getNode(), newVal); - legend.setVertical(newVal.isVertical()); - - return newVal; - }, state.onAction(ChartBits.ChartLayout)); - /** * The node to display as the Legend. Subclasses can set a node here to be displayed on a side as the legend. If no * legend is wanted then this can be set to null @@ -196,7 +174,7 @@ public abstract class Chart extends Region implements EventSource { private final ObjectProperty legend = new SimpleObjectProperty<>(this, "legend", new DefaultLegend()) { private Legend oldLegend = get(); { - getTitleLegendPane().addSide(getLegendSide(), oldLegend.getNode()); + getTitleLegendPane().addSide(oldLegend.getSide(), oldLegend.getNode()); } @Override @@ -208,12 +186,11 @@ protected void invalidated() { } if (newLegend != null) { - newLegend.getNode().setVisible(isLegendVisible()); - getTitleLegendPane().addSide(getLegendSide(), newLegend.getNode()); + getTitleLegendPane().addSide(newLegend.getSide(), newLegend.getNode()); } super.set(newLegend); oldLegend = newLegend; - updateLegend(getDatasets(), getRenderers()); + fireInvalidated(ChartBits.ChartLegend); } }; @@ -320,14 +297,6 @@ public Chart(Axis... axes) { menuPane.setTop(getToolBar()); getTitleLegendPane().getChildren().add(titleLabel); - - legendVisibleProperty().addListener((ch, old, visible) -> { - if (getLegend() == null) { - return; - } - getLegend().getNode().setVisible(visible); - getLegend().getNode().setManaged(visible); - }); } @Override @@ -435,10 +404,6 @@ public final Legend getLegend() { return legend.getValue(); } - public final Side getLegendSide() { - return legendSide.get(); - } - public final ChartPane getMeasurementPane() { return measurementPane; } @@ -508,10 +473,6 @@ public final boolean isAnimated() { return animated.get(); } - public final boolean isLegendVisible() { - return legendVisible.getValue(); - } - /** * @return true: if chart is being visible in Scene/Window */ @@ -678,12 +639,9 @@ public final ObjectProperty legendProperty() { return legend; } - public final ObjectProperty legendSideProperty() { - return legendSide; - } - + @Deprecated // TODO: used in tests/examples. Should be replaced with getLegend().setVisible(value)? public final BooleanProperty legendVisibleProperty() { - return legendVisible; + return getLegend().getNode().visibleProperty(); } public final void setAnimated(final boolean value) { @@ -694,12 +652,8 @@ public final void setLegend(final Legend value) { legend.set(value); } - public final void setLegendSide(final Side value) { - legendSide.set(value); - } - public final void setLegendVisible(final boolean value) { - legendVisible.set(value); + getLegend().getNode().setVisible(value); } public final void setTitle(final String value) { diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java index d7213a971..d5745df6c 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java @@ -91,7 +91,8 @@ public XYChart(final Axis... axes) { gridRenderer.getHorizontalMajorGrid().changeCounterProperty(), gridRenderer.getHorizontalMinorGrid().changeCounterProperty(), gridRenderer.getVerticalMajorGrid().changeCounterProperty(), - gridRenderer.getVerticalMinorGrid().changeCounterProperty()); + gridRenderer.getVerticalMinorGrid().changeCounterProperty(), + gridRenderer.drawOnTopProperty()); this.setAnimated(false); getRenderers().addListener(this::rendererChanged); 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..eb5cb8d25 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,33 @@ * @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); + managedProperty().bind(visibleProperty().and(Bindings.size(items).isNotEqualTo(0))); + 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 +79,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 +118,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 +127,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 +206,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/utils/FXUtils.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java index 4e04c54e5..5b6893430 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java @@ -9,6 +9,7 @@ import java.util.function.Function; import java.util.function.Supplier; +import io.fair_acc.chartfx.Chart; import io.fair_acc.dataset.events.StateListener; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; @@ -16,6 +17,7 @@ import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableBooleanValue; import javafx.scene.Node; +import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Window; @@ -324,4 +326,19 @@ public static ObservableBooleanValue getShowingBinding(Node node) { return showing; } + /** + * @param node child + * @return the containing parent chart if there is one + */ + public static Optional tryGetChartParent(Node node) { + Parent parent = node.getParent(); + while (parent != null) { + if (parent instanceof Chart) { + return Optional.of((Chart) parent); + } + parent = parent.getParent(); + } + return Optional.empty(); + } + } diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index 0b33cdcd4..5caa47ba7 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -132,8 +132,6 @@ .chart { -fx-tool-bar-side: TOP; - -fx-legend-side: BOTTOM; - -fx-legend-visibility: true; } .chart .chart-content { -fx-padding: 5 0 0 0px; @@ -149,6 +147,20 @@ .chart .chart-title:right { -fx-rotate: 90; } +.chart .chart-legend { + visibility: visible; + -fx-orientation: horizontal; + -fx-side: bottom; + -fx-vgap: 5px; + -fx-hgap: 5px; + -fx-alignment: center; + -fx-symbol-width: 20; + -fx-symbol-height: 20; +} +.chart .chart-legend .chart-legend-item { + -fx-alignment: center-left; + -fx-content-display: left; +} .axis { -fx-border-width: 0px; diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss index 7292ad079..17539cbe6 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss @@ -122,6 +122,12 @@ $null: null; // null gets removed from Sass. Maybe create a placeholder and repl .chart { .chart-measurement-pane { .chart-title-pane, .chart-legend-pane { + .chart-title { + } + .chart-legend { + .chart-legend-item { + } + } .chart-content { .axis { .axis-tick-mark { @@ -172,8 +178,6 @@ $null: null; // null gets removed from Sass. Maybe create a placeholder and repl .chart { // TODO: they don't seem to work and maybe we should create panes with a side property? deprecate these? -fx-tool-bar-side: TOP; - -fx-legend-side: BOTTOM; - -fx-legend-visibility: true; .chart-content { // leave some top-axis overdraw-padding if there is no title @@ -195,6 +199,23 @@ $null: null; // null gets removed from Sass. Maybe create a placeholder and repl } + .chart-legend { + visibility: visible; + -fx-orientation: horizontal; + -fx-side: bottom; + -fx-vgap: 5px; + -fx-hgap: 5px; + -fx-alignment: center; + -fx-symbol-width: 20; + -fx-symbol-height: 20; + + .chart-legend-item { + -fx-alignment: center-left; + -fx-content-display: left; + } + + } + } // Axis styles diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/OscilloscopeAxisSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/OscilloscopeAxisSample.java index c72e2c2a0..04e4253a2 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/OscilloscopeAxisSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/OscilloscopeAxisSample.java @@ -263,7 +263,7 @@ private XYChart getChart(final boolean defaultAxis) { final XYChart chart = new XYChart(xAxis, yAxis1); chart.setTitle(defaultAxis ? "Chart with DefaultNumericAxis" : "Chart with OscilloscopeAxis"); - chart.legendVisibleProperty().set(false); + chart.setLegendVisible(false); chart.getYAxis().setName(rollingBufferBeamIntensity.getName()); final ErrorDataSetRenderer beamIntensityRenderer = (ErrorDataSetRenderer) chart.getRenderers().get(0); ((DefaultDataReducer) beamIntensityRenderer.getRendererDataReducer()).setMinPointPixelDistance(MIN_PIXEL_DISTANCE); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/SimpleChartSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/SimpleChartSample.java index 39561bf35..68156ee77 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/SimpleChartSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/SimpleChartSample.java @@ -58,7 +58,7 @@ public Node getChartPanel(final Stage primaryStage) { // TODO: fix legend layouting, for now works only for top and is not really nice chart.setLegendVisible(true); - chart.setLegendSide(Side.TOP); + chart.getLegend().setSide(Side.TOP); return new StackPane(chart); } From 3f52d6546136e39dae76cb80f183388d34940c80 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Tue, 8 Aug 2023 08:44:49 +0200 Subject: [PATCH 13/19] removed duplicate styling properties from axis --- .../java/io/fair_acc/chartfx/axes/Axis.java | 22 ++- .../chartfx/axes/spi/AbstractAxis.java | 23 +-- .../axes/spi/AbstractAxisParameter.java | 169 ++++-------------- .../chartfx/renderer/spi/GridRenderer.java | 8 +- .../css/FinancialColorSchemeConfig.java | 12 +- .../io/fair_acc/chartfx/utils/PropUtil.java | 5 + .../resources/io/fair_acc/chartfx/chart.css | 6 +- .../resources/io/fair_acc/chartfx/chart.scss | 9 +- .../axes/spi/AbstractAxisParameterTests.java | 25 ++- .../chartfx/axes/spi/AbstractAxisTests.java | 10 +- .../financial/CandleStickRendererTest.java | 2 +- .../spi/financial/FootprintRendererTest.java | 2 +- .../spi/financial/HighLowRendererTest.java | 2 +- ...tionFinancialRendererPaintAfterEPTest.java | 2 +- .../sample/chart/ChartAnatomySample.java | 4 +- .../sample/chart/ChartIndicatorSample.java | 4 +- .../sample/chart/HistogramSample.java | 2 +- .../sample/chart/RollingBufferSample.java | 2 +- .../chart/RollingBufferSortedTreeSample.java | 2 +- .../sample/chart/RotatedAxisLabelSample.java | 13 +- 20 files changed, 112 insertions(+), 212 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/Axis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/Axis.java index 434ac5a93..75d8e0bc8 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/Axis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/Axis.java @@ -2,6 +2,8 @@ import java.util.List; +import io.fair_acc.chartfx.ui.css.LineStyle; +import io.fair_acc.chartfx.ui.css.TextStyle; import io.fair_acc.dataset.events.BitState; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; @@ -112,17 +114,27 @@ public interface Axis extends AxisDescription { */ Side getSide(); + StringConverter getTickLabelFormatter(); + /** - * @return the fill for all tick labels + * @return the style for the axis label */ - Paint getTickLabelFill(); + TextStyle getAxisLabel(); /** - * @return the font for all tick labels + * @return the style for all tick labels */ - Font getTickLabelFont(); + TextStyle getTickLabelStyle(); - StringConverter getTickLabelFormatter(); + /** + * @return the style for all major tick marks + */ + LineStyle getMajorTickStyle(); + + /** + * @return the style for all minor tick marks + */ + LineStyle getMinorTickStyle(); /** * @return the gap between the tick mark lines and the chart canvas diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 5bb18ecc8..05eecac9a 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -17,9 +17,7 @@ import javafx.scene.CacheHint; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; -import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; -import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.text.Text; import javafx.scene.text.TextAlignment; @@ -83,11 +81,6 @@ protected void invalidated() { }; protected AbstractAxis() { - super(); - // Can we remove these? Axes without a chart don't work anymore. - VBox.setVgrow(this, Priority.ALWAYS); - HBox.setHgrow(this, Priority.ALWAYS); - // Canvas settings setMouseTransparent(false); setPickOnBounds(true); @@ -100,15 +93,11 @@ protected AbstractAxis() { getChildren().add(canvas); // set default axis title/label alignment - updateTickLabelAlignment(); - updateAxisLabelAlignment(); - sideProperty().addListener((ch, o, n) -> { - updateAxisLabelAlignment(); - updateTickLabelAlignment(); - }); - tickLabelRotationProperty().addListener((ch, o, n) -> { - updateTickLabelAlignment(); - }); + PropUtil.initAndRunOnChange(this::updateAxisLabelAlignment, + sideProperty()); + PropUtil.initAndRunOnChange(this::updateTickLabelAlignment, + sideProperty(), + getTickLabelStyle().rotateProperty()); } protected AbstractAxis(final double lowerBound, final double upperBound) { @@ -920,7 +909,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 3d338bb47..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 @@ -24,6 +24,7 @@ import io.fair_acc.chartfx.Chart; import io.fair_acc.chartfx.axes.Axis; import io.fair_acc.chartfx.axes.AxisLabelOverlapPolicy; +import io.fair_acc.chartfx.ui.css.CssPropertyFactory; import io.fair_acc.chartfx.ui.geometry.Side; /** @@ -39,8 +40,6 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { * Create a auto-ranging AbstractAxisParameter */ public AbstractAxisParameter() { - super(); - // 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 @@ -49,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, @@ -102,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 @@ -179,11 +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 StyleGroup styles = new StyleGroup(this, "axis"); - private final transient LineStyle majorTickStyle = styles.newLineStyle("axis-tick-mark"); - private final transient LineStyle minorTickStyle = styles.newLineStyle("axis-minor-tick-mark"); - private final transient TextStyle tickLabelStyle = styles.newTextStyle("axis-tick-label"); - private final transient TextStyle axisLabel = styles.newTextStyle("axis-label"); + 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(); @@ -216,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 ObjectProperty axisLabelTextAlignment = axisLabel.textAlignmentProperty(); - /** * The axis label */ private final transient StyleableStringProperty axisName = CSS.createStringProperty(this, "axisName", ""); - /** - * true if tick marks should be displayed - */ - private final transient BooleanProperty tickMarkVisible = majorTickStyle.visibleProperty(); - - /** - * true if tick mark labels should be displayed - */ - private final transient BooleanProperty tickLabelsVisible = tickLabelStyle.visibleProperty(); - /** * The length of tick mark lines - */ + */ private final transient StyleableDoubleProperty axisPadding = CSS.createDoubleProperty(this, "axisPadding", 15.0); /** @@ -261,16 +239,6 @@ public AbstractAxisParameter() { } }); - /** - * The font for all tick labels - */ - private final transient ObjectProperty tickLabelFont = tickLabelStyle.fontProperty(); - - /** - * The fill for all tick labels - */ - private final transient ObjectProperty tickLabelFill = tickLabelStyle.fillProperty(); - /** * The gap between tick marks and the canvas area */ @@ -306,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 DoubleProperty tickLabelRotation = tickLabelStyle.rotateProperty(); - - /** - * true if minor tick marks should be displayed - */ - private final transient BooleanProperty minorTickVisible = minorTickStyle.visibleProperty(); - /** * The scale factor from data units to visual units */ @@ -356,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(); }); @@ -372,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 @@ -382,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 @@ -489,10 +447,6 @@ public DoubleProperty axisLabelGapProperty() { return axisLabelGap; } - public ObjectProperty axisLabelTextAlignmentProperty() { - return axisLabelTextAlignment; - } - public DoubleProperty axisPaddingProperty() { return axisPadding; } @@ -549,10 +503,6 @@ public double getAxisLabelGap() { return axisLabelGapProperty().get(); } - public TextAlignment getAxisLabelTextAlignment() { - return axisLabelTextAlignmentProperty().get(); - } - public double getAxisPadding() { return axisPaddingProperty().get(); } @@ -662,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(); } @@ -687,10 +626,6 @@ public double getTickLabelGap() { return tickLabelGapProperty().get(); } - public double getTickLabelRotation() { - return tickLabelRotationProperty().getValue(); - } - @Override public double getTickLabelSpacing() { return tickLabelSpacingProperty().get(); @@ -813,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 * @@ -854,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. * @@ -977,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); } @@ -1020,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); @@ -1038,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); } @@ -1058,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 * @@ -1111,14 +1002,6 @@ public ObjectProperty sideProperty() { return side; } - public ObjectProperty tickLabelFillProperty() { - return tickLabelFill; - } - - public ObjectProperty tickLabelFontProperty() { - return tickLabelFont; - } - public ObjectProperty> tickLabelFormatterProperty() { return tickLabelFormatter; } @@ -1131,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 * @@ -1171,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/renderer/spi/GridRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/GridRenderer.java index d4c68d4e0..287feec6e 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 @@ -149,8 +149,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); @@ -198,8 +198,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) { 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 fc6c786b3..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; @@ -216,10 +216,10 @@ public void applyTo(String theme, String customColorScheme, XYChart chart) throw chart.getGridRenderer().getHorizontalMajorGrid().setVisible(false); 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; @@ -233,10 +233,10 @@ public void applyTo(String theme, String customColorScheme, XYChart chart) throw chart.getGridRenderer().getHorizontalMajorGrid().setStroke(Color.rgb(106, 106, 106)); 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/utils/PropUtil.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java index 043e7960c..f4ddcbb6e 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java @@ -62,6 +62,11 @@ public static ObjectProperty createObjectProperty(Object bean, String nam return prop; } + public static void initAndRunOnChange(Runnable action, ObservableValue... conditions) { + action.run(); + runOnChange(action, conditions); + } + /** * subscribes to property changes without requiring value boxing */ diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index 5caa47ba7..b63af9a1e 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -167,6 +167,9 @@ -fx-auto-ranging: true; -fx-auto-grow-ranging: false; -fx-auto-range-rounding: false; + -fx-auto-range-padding: 0; + -fx-auto-unit-scaling: false; + -fx-invert-axis: false; -fx-axis-center-position: 0.5; -fx-overlap-policy: SKIP_ALT; -fx-axis-padding: 15px; @@ -198,9 +201,10 @@ -fx-rotate: -90; } .axis .axis-tick-label { + -fx-fill: black; -fx-font-size: 10px; } -.axis .axis-tick-mark, .axis .axis-minor-tick-mark { +.axis .axis-major-tick-mark, .axis .axis-minor-tick-mark { visibility: visible; -fx-stroke: #c3c3c3; -fx-stroke-width: 1; diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss index 17539cbe6..a3483c564 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss @@ -130,7 +130,7 @@ $null: null; // null gets removed from Sass. Maybe create a placeholder and repl } .chart-content { .axis { - .axis-tick-mark { + .axis-major-tick-mark { } .axis-minor-tick-mark { } @@ -228,6 +228,10 @@ $null: null; // null gets removed from Sass. Maybe create a placeholder and repl -fx-auto-grow-ranging: false; // range updates, but won't shrink -fx-auto-range-rounding: false; // auto range w/ rounding + -fx-auto-range-padding: 0; + -fx-auto-unit-scaling: false; + -fx-invert-axis: false; + // Tick spacing (along the width on horizontal axes) -fx-axis-center-position: 0.5; // only for centered axes: relative position of the center line -fx-overlap-policy: SKIP_ALT; // DO_NOTHING, SKIP_ALT, NARROW_FONT, SHIFT_ALT, FORCED_SHIFT_ALT @@ -266,11 +270,12 @@ $null: null; // null gets removed from Sass. Maybe create a placeholder and repl } .axis-tick-label { + -fx-fill: black; -fx-text-alignment: $null; -fx-font-size: 10px; } - .axis-tick-mark, .axis-minor-tick-mark { + .axis-major-tick-mark, .axis-minor-tick-mark { visibility: visible; -fx-stroke: #c3c3c3ff; -fx-stroke-width: 1.0; diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java index 089772e36..e4df9e429 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java @@ -231,11 +231,6 @@ void testPositionGetterSetters() { assertEquals(0.2, axis.getAxisCenterPosition()); axis.setAxisCenterPosition(0.5); - assertEquals(TextAlignment.CENTER, axis.getAxisLabelTextAlignment()); //TODO: rename function w.r.t. setter - axis.setAxisLabelTextAlignment(TextAlignment.LEFT); - assertEquals(TextAlignment.LEFT, axis.getAxisLabelTextAlignment()); //TODO: rename function w.r.t. setter - axis.setAxisLabelTextAlignment(TextAlignment.CENTER); - axis.setAxisLabelGap(5); assertEquals(5, axis.getAxisLabelGap()); @@ -248,11 +243,11 @@ void testTickLabelGetterSetters() { AbstractAxisParameter axis = new EmptyAbstractAxisParameter(); axis.set(0.0, 10.0); - axis.setTickLabelFill(Color.RED); - assertEquals(Color.RED, axis.getTickLabelFill()); + axis.getTickLabelStyle().setFill(Color.RED); + assertEquals(Color.RED, axis.getTickLabelStyle().getFill()); final Font font = Font.font("System", 10); - axis.setTickLabelFont(font); + axis.getTickLabelStyle().setFont(font); assertEquals(font, axis.getTickLabelFont()); StringConverter myConverter = new StringConverter<>() { @@ -275,13 +270,13 @@ public String toString(Number object) { axis.setTickLabelSpacing(5); assertEquals(5, axis.getTickLabelSpacing()); - axis.setTickLabelRotation(10); + axis.getTickLabelStyle().setRotate(10); assertEquals(10, axis.getTickLabelRotation()); assertTrue(axis.isTickLabelsVisible()); - axis.setTickLabelsVisible(false); + axis.getTickLabelStyle().setVisible(false); assertFalse(axis.isTickLabelsVisible()); - axis.setTickLabelsVisible(true); + axis.getTickLabelStyle().setVisible(true); } @Test @@ -316,17 +311,17 @@ void testTickMarkGetterSetters() { assertEquals(20, axis.getMinorTickLength()); assertTrue(axis.isMinorTickVisible()); - axis.setMinorTickVisible(false); + axis.getMinorTickStyle().setVisible(false); assertFalse(axis.isMinorTickVisible()); - axis.setMinorTickVisible(true); + axis.getMinorTickStyle().setVisible(true); axis.setTickLength(20); assertEquals(20, axis.getTickLength()); assertTrue(axis.isTickMarkVisible()); - axis.setTickMarkVisible(false); + axis.getMajorTickStyle().setVisible(false); assertFalse(axis.isTickMarkVisible()); - axis.setTickMarkVisible(true); + axis.getMajorTickStyle().setVisible(true); assertNotNull(axis.getTickMarks()); assertNotNull(axis.getTickMarkValues()); diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java index 32021e29b..ea8dc63b9 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java @@ -95,8 +95,8 @@ void testDrawRoutines(final Side side) { assertDoesNotThrow(() -> axis.drawAxis(null, 100, 100)); assertDoesNotThrow(() -> AbstractAxis.drawTickMarkLabel(gc, 10, 10, 1.0, new TickMark(Side.BOTTOM, 1.0, 1.0, 0.0, "label"))); assertDoesNotThrow(() -> AbstractAxis.drawTickMarkLabel(gc, 10, 10, 0.9, new TickMark(Side.BOTTOM, 1.0, 1.0, 90.0, "label"))); - axis.setTickMarkVisible(false); - axis.setMinorTickVisible(false); + axis.getMajorTickStyle().setVisible(false); + axis.getMinorTickStyle().setVisible(false); assertDoesNotThrow(() -> axis.drawAxis(gc, 100, 100)); } @@ -278,7 +278,7 @@ public void tickMarkLabelAlignment() { var style = axis.getTickLabelStyle(); // No rotation - axis.setTickLabelRotation(0); + axis.getTickLabelStyle().setRotate(0); axis.setSide(Side.TOP); assertEquals(TextAlignment.CENTER, style.getTextAlignment()); assertEquals(VPos.BOTTOM, style.getTextOrigin()); @@ -296,7 +296,7 @@ public void tickMarkLabelAlignment() { assertEquals(VPos.CENTER, style.getTextOrigin()); // 90 deg - axis.setTickLabelRotation(90); + axis.getTickLabelStyle().setRotate(90); axis.setSide(Side.TOP); assertEquals(TextAlignment.LEFT, style.getTextAlignment()); assertEquals(VPos.CENTER, style.getTextOrigin()); @@ -315,7 +315,7 @@ public void tickMarkLabelAlignment() { // special non 'n x 90 degree' rotation cases for top/bottom - axis.setTickLabelRotation(45); + axis.getTickLabelStyle().setRotate(45); axis.setSide(Side.TOP); assertEquals(TextAlignment.LEFT, style.getTextAlignment()); diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/CandleStickRendererTest.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/CandleStickRendererTest.java index d687fe7e1..021b7fb3d 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/CandleStickRendererTest.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/CandleStickRendererTest.java @@ -86,7 +86,7 @@ public void start(Stage stage) throws Exception { @TestFx public void categoryAxisTest() { final CategoryAxis xAxis = new CategoryAxis("time [iso]"); - xAxis.setTickLabelRotation(90); + xAxis.getTickLabelStyle().setRotate(90); xAxis.setOverlapPolicy(AxisLabelOverlapPolicy.SKIP_ALT); ohlcvDataSet.setCategoryBased(true); diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/FootprintRendererTest.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/FootprintRendererTest.java index fea585138..bbb1a0104 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/FootprintRendererTest.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/FootprintRendererTest.java @@ -100,7 +100,7 @@ private void financialComponentTest(Stage stage, String scheme) throws Exception @TestFx public void categoryAxisTest() { final CategoryAxis xAxis = new CategoryAxis("time [iso]"); - xAxis.setTickLabelRotation(90); + xAxis.getTickLabelStyle().setRotate(90); xAxis.setOverlapPolicy(AxisLabelOverlapPolicy.SKIP_ALT); ohlcvDataSet.setCategoryBased(true); diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/HighLowRendererTest.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/HighLowRendererTest.java index 4f715102c..f48dff3d7 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/HighLowRendererTest.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/HighLowRendererTest.java @@ -86,7 +86,7 @@ void start(Stage stage) throws Exception { @TestFx void categoryAxisTest() { final CategoryAxis xAxis = new CategoryAxis("time [iso]"); - xAxis.setTickLabelRotation(90); + xAxis.getTickLabelStyle().setRotate(90); xAxis.setOverlapPolicy(AxisLabelOverlapPolicy.SKIP_ALT); ohlcvDataSet.setCategoryBased(true); diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/PositionFinancialRendererPaintAfterEPTest.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/PositionFinancialRendererPaintAfterEPTest.java index 464233442..d1ef7017b 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/PositionFinancialRendererPaintAfterEPTest.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/PositionFinancialRendererPaintAfterEPTest.java @@ -97,7 +97,7 @@ public void start(Stage stage) throws Exception { @TestFx public void categoryAxisTest() { final CategoryAxis xAxis = new CategoryAxis("time [iso]"); - xAxis.setTickLabelRotation(90); + xAxis.getTickLabelStyle().setRotate(90); xAxis.setOverlapPolicy(AxisLabelOverlapPolicy.SKIP_ALT); ohlcvDataSet.setCategoryBased(true); chart.getAxes().add(0, xAxis); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartAnatomySample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartAnatomySample.java index 8bb6a71b8..e3286b8a7 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartAnatomySample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartAnatomySample.java @@ -48,13 +48,13 @@ public void start(final Stage primaryStage) { xAxis2.setSide(Side.TOP); xAxis3.setSide(Side.CENTER_HOR); xAxis3.setMinorTickCount(2); - xAxis3.setAxisLabelTextAlignment(TextAlignment.RIGHT); + xAxis3.getAxisLabel().setTextAlignment(TextAlignment.RIGHT); yAxis1.setSide(Side.LEFT); yAxis2.setSide(Side.RIGHT); yAxis3.setSide(Side.RIGHT); yAxis4.setSide(Side.CENTER_VER); yAxis4.setMinorTickCount(2); - yAxis4.setAxisLabelTextAlignment(TextAlignment.RIGHT); + yAxis4.getAxisLabel().setTextAlignment(TextAlignment.RIGHT); final Chart chart = new Chart() { @Override diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartIndicatorSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartIndicatorSample.java index 11304c6fc..ec7ca8169 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartIndicatorSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartIndicatorSample.java @@ -200,8 +200,8 @@ public BorderPane initComponents() { xAxis1.setAutoRangeRounding(false); xAxis2.setAutoRangeRounding(false); - xAxis1.setTickLabelRotation(45); - xAxis2.setTickLabelRotation(45); + xAxis1.getTickLabelStyle().setRotate(45); + xAxis2.getTickLabelStyle().setRotate(45); xAxis1.invertAxis(false); xAxis2.invertAxis(false); xAxis1.setTimeAxis(true); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/HistogramSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/HistogramSample.java index f00854c5f..fe321927b 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/HistogramSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/HistogramSample.java @@ -74,7 +74,7 @@ private void fillDemoData() { public Node getChartPanel(final Stage primaryStage) { final StackPane root = new StackPane(); final CategoryAxis xAxis1 = new CategoryAxis("months"); - xAxis1.setTickLabelRotation(90); + xAxis1.getTickLabelStyle().setRotate(90); final DefaultNumericAxis xAxis2 = new DefaultNumericAxis("x-Axis"); xAxis2.setAutoRangeRounding(false); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RollingBufferSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RollingBufferSample.java index c6dfefa69..71996c1e0 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RollingBufferSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RollingBufferSample.java @@ -165,7 +165,7 @@ public BorderPane initComponents() { final DefaultNumericAxis xAxis1 = new DefaultNumericAxis("time"); xAxis1.setAutoRangeRounding(false); - xAxis1.setTickLabelRotation(45); + xAxis1.getTickLabelStyle().setRotate(45); xAxis1.setMinorTickCount(30); xAxis1.invertAxis(false); xAxis1.setTimeAxis(true); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RollingBufferSortedTreeSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RollingBufferSortedTreeSample.java index 4db151502..9efa86ed3 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RollingBufferSortedTreeSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RollingBufferSortedTreeSample.java @@ -146,7 +146,7 @@ public BorderPane initComponents() { final DefaultNumericAxis xAxis1 = new DefaultNumericAxis("time"); xAxis1.setAutoRangeRounding(false); - xAxis1.setTickLabelRotation(45); + xAxis1.getTickLabelStyle().setRotate(45); xAxis1.invertAxis(false); xAxis1.setTimeAxis(true); final DefaultNumericAxis yAxis1 = new DefaultNumericAxis("beam intensity", "ppp"); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RotatedAxisLabelSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RotatedAxisLabelSample.java index 8542242e8..d434ed261 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RotatedAxisLabelSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RotatedAxisLabelSample.java @@ -45,7 +45,7 @@ public Node getChartPanel(final Stage primaryStage) { final DefaultNumericAxis yAxis1 = getSynchedAxis(yAxis0, "y-axis (-90°, " + policy + ")"); yAxis1.setSide(Side.LEFT); - yAxis1.setTickLabelRotation(-90); + yAxis1.getTickLabelStyle().setRotate(-90); yAxis1.setOverlapPolicy(policy); chart.getAxes().addAll(xAxis1, yAxis1); @@ -58,7 +58,7 @@ public Node getChartPanel(final Stage primaryStage) { final DefaultNumericAxis yAxis2 = getSynchedAxis(yAxis0, "y-axis (-90°, " + policy + ") + extra label spacing"); yAxis2.setSide(Side.LEFT); - yAxis2.setTickLabelRotation(-90); + yAxis2.getTickLabelStyle().setRotate(-90); yAxis2.setOverlapPolicy(policy); yAxis2.setTickLabelSpacing(10); @@ -69,13 +69,13 @@ public Node getChartPanel(final Stage primaryStage) { final DefaultNumericAxis xAxis1 = getSynchedAxis(xAxis0, "x-axis (45°)"); xAxis1.setSide(Side.BOTTOM); xAxis1.setOverlapPolicy(AxisLabelOverlapPolicy.DO_NOTHING); - xAxis1.setTickLabelRotation(45); + xAxis1.getTickLabelStyle().setRotate(45); xAxis1.setMaxMajorTickLabelCount(40); final DefaultNumericAxis xAxis2 = getSynchedAxis(xAxis0, "x-axis (90°)"); xAxis2.setSide(Side.BOTTOM); xAxis2.setOverlapPolicy(AxisLabelOverlapPolicy.DO_NOTHING); - xAxis2.setTickLabelRotation(90); + xAxis2.getTickLabelStyle().setRotate(90); xAxis2.setMaxMajorTickLabelCount(40); chart.getAxes().addAll(xAxis1, xAxis2); @@ -83,7 +83,7 @@ public Node getChartPanel(final Stage primaryStage) { final DefaultNumericAxis yAxis1 = getSynchedAxis(yAxis0, "y-axis (-45°)"); yAxis1.setSide(Side.LEFT); yAxis1.setOverlapPolicy(AxisLabelOverlapPolicy.DO_NOTHING); - yAxis1.setTickLabelRotation(-45); + yAxis1.getTickLabelStyle().setRotate(-45); chart.getAxes().addAll(yAxis1); @@ -100,8 +100,7 @@ private static DefaultNumericAxis getSynchedAxis(DefaultNumericAxis orig, String final DefaultNumericAxis axis = new DefaultNumericAxis(newAxisName); axis.minProperty().bind(orig.minProperty()); axis.maxProperty().bind(orig.maxProperty()); - axis.setTickLabelRotation(orig.getTickLabelRotation()); - + axis.getTickLabelStyle().setRotate(orig.getTickLabelStyle().getRotate()); return axis; } From 7d63d23f39c72326d0d44d789c0533b262857ee0 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Tue, 8 Aug 2023 09:00:01 +0200 Subject: [PATCH 14/19] =?UTF-8?q?=EF=BB=BFupdated=20unit=20tests=20to=20ma?= =?UTF-8?q?tch=20updated=20axis=20default=20font?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chartfx/axes/spi/AbstractAxisParameterTests.java | 2 +- .../io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java index e4df9e429..af2de6da4 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java @@ -173,7 +173,7 @@ void testBasicGetterSetters() { assertTrue(state.isDirty(ChartBits.AxisLayout)); state.clear(ChartBits.AxisLayout); } - axis.setSide(null); + assertThrows(IllegalArgumentException.class, () -> axis.setSide(null)); assertEquals(Double.NaN, axis.getLength()); axis.setSide(Side.LEFT); diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java index ea8dc63b9..acc50b609 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java @@ -160,20 +160,20 @@ void testHelper() { axis.setUnit(null); axis.setSide(Side.BOTTOM); assertEquals(+1.0, axis.calculateNewScale(10, -5.0, +5.0)); - assertEquals(+25, axis.computePrefHeight(100), 2); + assertEquals(+30, axis.computePrefHeight(100), 2); assertEquals(+150.0, axis.computePrefWidth(-1)); axis.setSide(Side.LEFT); assertEquals(-1.0, axis.calculateNewScale(10, -5.0, +5.0)); assertEquals(+150, axis.computePrefHeight(-1)); - assertEquals(+22, axis.computePrefWidth(100), 2); + assertEquals(+26, axis.computePrefWidth(100), 2); axis.setUnit(""); axis.setSide(Side.BOTTOM); - assertEquals(+44, axis.computePrefHeight(100), 2); + assertEquals(+49, axis.computePrefHeight(100), 2); assertEquals(+150.0, axis.computePrefWidth(-1)); axis.setSide(Side.LEFT); assertEquals(+150, axis.computePrefHeight(-1)); - assertEquals(+44, axis.computePrefWidth(100), 2); + assertEquals(+48, axis.computePrefWidth(100), 2); assertDoesNotThrow(axis::clear); assertDoesNotThrow(axis::forceRedraw); From 6a8f7be2a99370bc765ba36a3da04c96f7268508 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Tue, 8 Aug 2023 09:50:43 +0200 Subject: [PATCH 15/19] removed delegating methods for the grid renderer --- .../java/io/fair_acc/chartfx/XYChart.java | 54 ----------------- .../chartfx/renderer/spi/GridRenderer.java | 58 ++++--------------- .../ErrorDataSetRendererStylingSample.java | 4 +- 3 files changed, 13 insertions(+), 103 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java index d5745df6c..8a8c165de 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java @@ -161,24 +161,6 @@ public Axis getYAxis() { return getFirstAxis(Orientation.VERTICAL); } - /** - * Indicates whether horizontal grid lines are visible or not. - * - * @return horizontalGridLinesVisible property - */ - public final BooleanProperty horizontalGridLinesVisibleProperty() { - return gridRenderer.horizontalGridLinesVisibleProperty(); - } - - /** - * Indicates whether horizontal grid lines are visible. - * - * @return {@code true} if horizontal grid lines are visible else {@code false}. - */ - public final boolean isHorizontalGridLinesVisible() { - return horizontalGridLinesVisibleProperty().get(); - } - /** * whether renderer should use polar coordinates (x -> interpreted as phi, y as radial coordinate) * @@ -188,15 +170,6 @@ public final boolean isPolarPlot() { return polarPlotProperty().get(); } - /** - * Indicates whether vertical grid lines are visible. - * - * @return {@code true} if vertical grid lines are visible else {@code false}. - */ - public final boolean isVerticalGridLinesVisible() { - return verticalGridLinesVisibleProperty().get(); - } - /** * Sets whether renderer should use polar coordinates (x -> interpreted as phi, y as radial coordinate) * @@ -210,15 +183,6 @@ public ObjectProperty polarStepSizeProperty() { return polarStepSize; } - /** - * Sets the value of the {@link #verticalGridLinesVisibleProperty()}. - * - * @param value {@code true} to make vertical lines visible - */ - public final void setHorizontalGridLinesVisible(final boolean value) { - horizontalGridLinesVisibleProperty().set(value); - } - /** * Sets whether renderer should use polar coordinates (x -> interpreted as phi, y as radial coordinate) * @@ -234,15 +198,6 @@ public void setPolarStepSize(final PolarTickStep step) { polarStepSizeProperty().set(step); } - /** - * Sets the value of the {@link #verticalGridLinesVisibleProperty()}. - * - * @param value {@code true} to make vertical lines visible - */ - public final void setVerticalGridLinesVisible(final boolean value) { - verticalGridLinesVisibleProperty().set(value); - } - @Override public void updateAxisRange() { if (isDataEmpty()) { @@ -262,15 +217,6 @@ public void updateAxisRange() { } - /** - * Indicates whether vertical grid lines are visible or not. - * - * @return verticalGridLinesVisible property - */ - public final BooleanProperty verticalGridLinesVisibleProperty() { - return gridRenderer.verticalGridLinesVisibleProperty(); - } - private boolean isDataEmpty() { return getAllDatasets() == null || getAllDatasets().isEmpty(); } 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 287feec6e..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 @@ -119,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 drawGridOnTop; - } - 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()) { @@ -321,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 drawOnTopProperty().get(); + public final BooleanProperty drawOnTopProperty() { + return drawGridOnTop; } @Override @@ -365,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) { - drawOnTopProperty().set(state); - } - @Override public Renderer setShowInLegend(final boolean state) { return this; @@ -389,24 +371,6 @@ 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(); - } - - /** - * Indicates whether vertical minor grid lines are visible or not. - * - * @return verticalGridLinesVisible property - */ - public final BooleanProperty verticalMinorGridLinesVisibleProperty() { - return verMinorGridStyleNode.visibleProperty(); - } - @Override public List> getCssMetaData() { return getClassCssMetaData(); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ErrorDataSetRendererStylingSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ErrorDataSetRendererStylingSample.java index 9dbe5be4f..0d9fce341 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ErrorDataSetRendererStylingSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ErrorDataSetRendererStylingSample.java @@ -195,7 +195,7 @@ private Tab getChartTab(XYChart chart) { final CheckBox gridVisibleX = new CheckBox(""); gridVisibleX.setSelected(true); - chart.horizontalGridLinesVisibleProperty().bindBidirectional(gridVisibleX.selectedProperty()); + chart.getGridRenderer().getHorizontalMajorGrid().visibleProperty().bindBidirectional(gridVisibleX.selectedProperty()); pane.addToParameterPane("Show X-Grid: ", gridVisibleX); final CheckBox gridVisibleXMinor = new CheckBox(""); @@ -205,7 +205,7 @@ private Tab getChartTab(XYChart chart) { final CheckBox gridVisibleY = new CheckBox(""); gridVisibleY.setSelected(true); - chart.verticalGridLinesVisibleProperty().bindBidirectional(gridVisibleY.selectedProperty()); + chart.getGridRenderer().getVerticalMajorGrid().visibleProperty().bindBidirectional(gridVisibleY.selectedProperty()); pane.addToParameterPane("Show Y-Grid: ", gridVisibleY); final CheckBox gridVisibleYMinor = new CheckBox(""); From c174f4f2a9a21fd73b3fa0e16188d6f54fdb31a1 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Tue, 8 Aug 2023 17:44:15 +0200 Subject: [PATCH 16/19] turned abstract renderer into a styleable node --- .../main/java/io/fair_acc/chartfx/Chart.java | 18 ++++- .../java/io/fair_acc/chartfx/XYChart.java | 2 +- ...stractContourDataSetRendererParameter.java | 2 +- ...AbstractErrorDataSetRendererParameter.java | 2 +- ...ava => AbstractPointReducingRenderer.java} | 45 ++++++++---- ...tManagement.java => AbstractRenderer.java} | 72 +++++++++++++++++-- .../renderer/spi/LabelledMarkerRenderer.java | 16 +---- .../renderer/spi/ReducingLineRenderer.java | 2 +- .../financial/AbstractFinancialRenderer.java | 4 +- .../resources/io/fair_acc/chartfx/chart.css | 7 ++ .../resources/io/fair_acc/chartfx/chart.scss | 11 +++ 11 files changed, 139 insertions(+), 42 deletions(-) rename chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/{AbstractPointReductionManagment.java => AbstractPointReducingRenderer.java} (79%) rename chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/{AbstractDataSetManagement.java => AbstractRenderer.java} (66%) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index 3d6c2e955..76d0ee996 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -5,6 +5,8 @@ import java.util.function.Consumer; import java.util.stream.Collectors; +import io.fair_acc.chartfx.renderer.spi.AbstractRenderer; +import io.fair_acc.chartfx.ui.css.StyleGroup; import io.fair_acc.chartfx.ui.css.StyleUtil; import io.fair_acc.chartfx.ui.layout.TitleLabel; import io.fair_acc.chartfx.ui.layout.ChartPane; @@ -132,6 +134,9 @@ public abstract class Chart extends Region implements EventSource { protected final ToolBarFlowPane toolBar = new ToolBarFlowPane(this); protected final BooleanProperty toolBarPinned = new SimpleBooleanProperty(this, "toolBarPinned", false); + // Other nodes that need to be styled via CSS + protected final StyleGroup styleableNodes = new StyleGroup(this, getChildren(), "chart"); + { // Build hierarchy // > menuPane (hidden toolbars that slide in from top/bottom) @@ -144,7 +149,6 @@ public abstract class Chart extends Region implements EventSource { // > canvas foreground // > plugins // > plot background/foreground - StyleUtil.addStyles(this, "chart"); var canvasPane = StyleUtil.addStyles(new PlotAreaPane(canvas, canvasForeground, pluginsArea), "chart-plot-area"); plotArea.setContent(canvasPane); axesAndCanvasPane.addCenter(plotBackground, plotArea, plotForeGround); @@ -848,6 +852,13 @@ protected void rendererChanged(final ListChangeListener.Change) renderer; + node.setChart(this); + if (!styleableNodes.getChildren().contains(node)) { + styleableNodes.getChildren().add(node); + } + } } // handle removed renderer @@ -856,6 +867,11 @@ protected void rendererChanged(final ListChangeListener.Change) renderer; + styleableNodes.getChildren().remove(node); + node.setChart(null); + } } } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java index 8a8c165de..ab7da835a 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java @@ -86,7 +86,7 @@ public XYChart(final Axis... axes) { getAxes().add(axis); } - getChildren().add(gridRenderer); + styleableNodes.getChildren().add(gridRenderer); PropUtil.runOnChange(getBitState().onAction(ChartBits.ChartCanvas), gridRenderer.getHorizontalMajorGrid().changeCounterProperty(), gridRenderer.getHorizontalMinorGrid().changeCounterProperty(), 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..20533456b 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 @@ -33,7 +33,7 @@ @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", 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..5870bce72 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,28 @@ package io.fair_acc.chartfx.renderer.spi; +import io.fair_acc.chartfx.ui.css.CssPropertyFactory; 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 +158,16 @@ public R setPointReduction(final boolean state) { pointReduction.set(state); return getThis(); } + + @Override + public List> getCssMetaData() { + return getClassCssMetaData(); + } + + public static List> getClassCssMetaData() { + return CSS.getCssMetaData(); + } + + private static final CssPropertyFactory> CSS = new CssPropertyFactory<>(AbstractRenderer.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..ff7cb7f8b 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, this::invalidateCanvas); + 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); + } + } + + @Override + public List> getCssMetaData() { + return getClassCssMetaData(); + } + + public static List> getClassCssMetaData() { + 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/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/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index b63af9a1e..b765901e4 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -161,6 +161,13 @@ -fx-alignment: center-left; -fx-content-display: left; } +.chart .renderer { + -fx-show-in-legend: true; + -fx-assume-sorted-data: true; + -fx-min-required-reduction-size: 5; + -fx-parallel-implementation: true; + -fx-point-reduction: true; +} .axis { -fx-border-width: 0px; diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss index a3483c564..60e404394 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss @@ -216,6 +216,17 @@ $null: null; // null gets removed from Sass. Maybe create a placeholder and repl } + .renderer { + // abstract renderer + -fx-show-in-legend: true; + + // point reducing renderers + -fx-assume-sorted-data: true; + -fx-min-required-reduction-size: 5; + -fx-parallel-implementation: true; + -fx-point-reduction: true; + } + } // Axis styles From 2d307f8dcbadea8c815a0123deffddbd1472bafa Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 9 Aug 2023 12:45:37 +0200 Subject: [PATCH 17/19] added a check that hides all tick labels that would get cut off by the parent container --- .../chartfx/axes/spi/AbstractAxis.java | 51 +++++++++++++++++-- .../resources/io/fair_acc/chartfx/chart.css | 3 -- .../resources/io/fair_acc/chartfx/chart.scss | 5 -- 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 05eecac9a..93b585121 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -17,7 +17,7 @@ import javafx.scene.CacheHint; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; -import javafx.scene.layout.Priority; +import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.text.Text; import javafx.scene.text.TextAlignment; @@ -542,11 +542,18 @@ private boolean isTickLabelRendered() { } private void applyOverlapPolicy(List tickMarks) { - // Default to all visible + if (tickMarks.isEmpty()) { + return; + } + + // Start with everything being visible for (TickMark tickMark : tickMarks) { tickMark.setVisible(true); } + // Hide labels that would get cut off by the parent + hideLabelsOutsideParentBounds(tickMarks); + // Check whether any labels overlap. // Note: We technically only need to compute it for cases that // hide/modify labels, but we leave it in for diagnostics. @@ -573,7 +580,8 @@ private void applyOverlapPolicy(List tickMarks) { case SKIP_ALT: // make every other label visible to gain a factor 2 margin - for (int i = 1; i < tickMarks.size(); i += 2) { + int firstHidden = !tickMarks.get(0).isVisible() ? 0 : 1; + for (int i = firstHidden; i < tickMarks.size(); i += 2) { tickMarks.get(i).setVisible(false); } break; @@ -590,6 +598,43 @@ private void applyOverlapPolicy(List 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) { diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index b765901e4..77046a552 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -133,9 +133,6 @@ .chart { -fx-tool-bar-side: TOP; } -.chart .chart-content { - -fx-padding: 5 0 0 0px; -} .chart .chart-title { visibility: visible; -fx-side: top; diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss index 60e404394..6a3c084c8 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss @@ -179,11 +179,6 @@ $null: null; // null gets removed from Sass. Maybe create a placeholder and repl // TODO: they don't seem to work and maybe we should create panes with a side property? deprecate these? -fx-tool-bar-side: TOP; - .chart-content { - // leave some top-axis overdraw-padding if there is no title - -fx-padding: 5 0 0 0px - } - .chart-title { visibility: visible; -fx-side: top; From dad3ac9c82da52047bf639b5085683218d7fc221 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Thu, 10 Aug 2023 14:59:06 +0200 Subject: [PATCH 18/19] made error ds renderer properties stylable --- .../chartfx/legend/spi/DefaultLegend.java | 3 +- ...AbstractErrorDataSetRendererParameter.java | 58 ++++++++++++------- .../spi/AbstractPointReducingRenderer.java | 19 +++--- .../renderer/spi/AbstractRenderer.java | 12 ++-- .../resources/io/fair_acc/chartfx/chart.css | 16 +++++ .../resources/io/fair_acc/chartfx/chart.scss | 19 ++++++ 6 files changed, 86 insertions(+), 41 deletions(-) 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 eb5cb8d25..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 @@ -51,8 +51,7 @@ public class DefaultLegend extends FlowPane implements Legend { private final ObservableList items = FXCollections.observableArrayList(); public DefaultLegend() { - getStyleClass().setAll("chart-legend"); - managedProperty().bind(visibleProperty().and(Bindings.size(items).isNotEqualTo(0))); + StyleUtil.addStyles(this, "chart-legend"); items.addListener((ListChangeListener) c -> getChildren().setAll(items)); PropUtil.runOnChange(this::applyCss, sideProperty()); 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 20533456b..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 @@ -36,33 +42,34 @@ public abstract class AbstractErrorDataSetRendererParameter { // 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/AbstractPointReducingRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractPointReducingRenderer.java index 5870bce72..fae9f0f7f 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractPointReducingRenderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractPointReducingRenderer.java @@ -1,6 +1,7 @@ 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; @@ -15,12 +16,12 @@ public abstract class AbstractPointReducingRenderer { 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", + 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", + private final BooleanProperty parallelImplementation = registerCanvasProp(css().createBooleanProperty(this, "parallelImplementation", true)); - private final BooleanProperty pointReduction = CSS.createBooleanProperty(this, "pointReduction", true); + private final BooleanProperty pointReduction = css().createBooleanProperty(this, "pointReduction", true); public AbstractPointReducingRenderer() { super(); @@ -160,14 +161,10 @@ public R setPointReduction(final boolean state) { } @Override - public List> getCssMetaData() { - return getClassCssMetaData(); + protected CssPropertyFactory> css() { + return CSS; } - public static List> getClassCssMetaData() { - return CSS.getCssMetaData(); - } - - private static final CssPropertyFactory> CSS = new CssPropertyFactory<>(AbstractRenderer.getClassCssMetaData()); + private static final CssPropertyFactory> CSS = new CssPropertyFactory<>(AbstractPointReducingRenderer.getClassCssMetaData()); } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractRenderer.java index ff7cb7f8b..35c7edadb 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractRenderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractRenderer.java @@ -33,7 +33,7 @@ */ public abstract class AbstractRenderer extends Parent implements Renderer { - protected final StyleableBooleanProperty showInLegend = CSS.createBooleanProperty(this, "showInLegend", true, this::invalidateCanvas); + 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<>(); @@ -184,13 +184,13 @@ protected void fireInvalidated(IntSupplier bit) { } } - @Override - public List> getCssMetaData() { - return getClassCssMetaData(); + protected CssPropertyFactory> css() { + return CSS; // subclass specific CSS due to inheritance issues otherwise } - public static List> getClassCssMetaData() { - return CSS.getCssMetaData(); + @Override + public List> getCssMetaData() { + return css().getCssMetaData(); } private static final CssPropertyFactory> CSS = new CssPropertyFactory<>(Parent.getClassCssMetaData()); diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index 77046a552..0617fa31d 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -165,6 +165,22 @@ -fx-parallel-implementation: true; -fx-point-reduction: true; } +.chart .error-dataset-renderer { + -fx-poly-line-style: normal; + -fx-error-style: errorcombo; + -fx-dash-size: 3; + -fx-allow-nans: false; + -fx-draw-bubbles: false; + -fx-draw-marker: true; + -fx-marker-size: 1.5; + -fx-draw-bars: false; + -fx-shift-bar: true; + -fx-shift-bar-offset: 3; + -fx-dynamic-bar-width: true; + -fx-bar-width-percentage: 70; + -fx-bar-width: 5; + -fx-intensity-fading: 0.65; +} .axis { -fx-border-width: 0px; diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss index 6a3c084c8..104d21461 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss @@ -222,6 +222,25 @@ $null: null; // null gets removed from Sass. Maybe create a placeholder and repl -fx-point-reduction: true; } + .error-dataset-renderer { + -fx-poly-line-style: normal; // normal, area, zero-order-holder, stair-case, histogram, histogram-filled, bezier-curve + -fx-error-style: errorcombo; // none, errorbars, errorsurface, errorcombo + -fx-dash-size: 3; + -fx-allow-nans: false; + + -fx-draw-bubbles: false; + -fx-draw-marker: true; + -fx-marker-size: 1.5; + + -fx-draw-bars: false; + -fx-shift-bar: true; + -fx-shift-bar-offset: 3; + -fx-dynamic-bar-width: true; + -fx-bar-width-percentage: 70.0; + -fx-bar-width: 5; + -fx-intensity-fading: 0.65; + } + } // Axis styles From af73ef274e5da315c77030a892e7b420dc6bc006 Mon Sep 17 00:00:00 2001 From: Alexander Krimm Date: Thu, 10 Aug 2023 18:09:32 +0200 Subject: [PATCH 19/19] Tests: sprinkle some @TestFx annotations --- .../renderer/spi/ErrorDataSetRendererTests.java | 14 +++++++++++--- .../renderer/spi/LabelledMarkerRendererTests.java | 4 ++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/ErrorDataSetRendererTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/ErrorDataSetRendererTests.java index 29aa1971c..3951f7dbe 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/ErrorDataSetRendererTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/ErrorDataSetRendererTests.java @@ -5,6 +5,7 @@ import java.util.Collections; +import io.fair_acc.chartfx.ui.utils.TestFx; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.stage.Stage; @@ -74,13 +75,20 @@ public void start(Stage stage) { @EnumSource(LineStyle.class) public void testRendererNominal(final LineStyle lineStyle) throws Exception { for (ErrorStyle eStyle : ErrorStyle.values()) { - renderer.setErrorType(eStyle); - testRenderer(lineStyle); + FXUtils.runAndWait(() -> { + renderer.setErrorType(eStyle); + try { + testRenderer(lineStyle); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); } } @Test - public void testRendererSepcialCases() throws Exception { + @TestFx + public void testRendererSpecialCases() throws Exception { final LineStyle lineStyle = LineStyle.NORMAL; renderer.setPointReduction(false); diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/LabelledMarkerRendererTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/LabelledMarkerRendererTests.java index 5e4105537..03c7a9097 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/LabelledMarkerRendererTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/LabelledMarkerRendererTests.java @@ -74,11 +74,11 @@ public void testSetterGetter() { localRenderer.enableHorizontalMarker(true); assertTrue(localRenderer.isHorizontalMarker()); - assertNull(localRenderer.getStyle()); + assertEquals("", localRenderer.getStyle()); localRenderer.setStyle("arbitrary"); assertEquals("arbitrary", localRenderer.getStyle()); localRenderer.setStyle(null); - assertNull(localRenderer.getStyle()); + assertEquals("", localRenderer.getStyle()); } @Test