Skip to content

Commit 1fb8755

Browse files
Markus Mackkevinrushforth
Markus Mack
authored andcommitted
8198830: BarChart: auto-range of CategoryAxis not working on dynamically setting data
Reviewed-by: angorya
1 parent ca04c87 commit 1fb8755

File tree

3 files changed

+119
-8
lines changed

3 files changed

+119
-8
lines changed

modules/javafx.controls/src/main/java/javafx/scene/chart/BarChart.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ public BarChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAx
384384
@Override protected void layoutPlotChildren() {
385385
double catSpace = categoryAxis.getCategorySpacing();
386386
// calculate bar spacing
387-
final double availableBarSpace = catSpace - (getCategoryGap() + getBarGap());
387+
final double availableBarSpace = catSpace - getCategoryGap() + getBarGap();
388388
double barWidth = (availableBarSpace / getSeriesSize()) - getBarGap();
389389
final double barOffset = -((catSpace - getCategoryGap()) / 2);
390390
final double zeroPos = (valueAxis.getLowerBound() > 0) ?
@@ -423,9 +423,9 @@ public BarChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAx
423423
bar.resizeRelocate( bottom, categoryPos + barOffset + (barWidth + getBarGap()) * index,
424424
top-bottom, barWidth);
425425
}
426-
427-
index++;
428426
}
427+
428+
index++;
429429
}
430430
catIndex++;
431431
}

modules/javafx.controls/src/main/java/javafx/scene/chart/CategoryAxis.java

+21-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -68,8 +68,17 @@ public final class CategoryAxis extends Axis<String> {
6868
// -------------- PRIVATE FIELDS -------------------------------------------
6969
private List<String> allDataCategories = new ArrayList<>();
7070
private boolean changeIsLocal = false;
71-
/** This is the gap between one category and the next along this axis */
72-
private final DoubleProperty firstCategoryPos = new SimpleDoubleProperty(this, "firstCategoryPos", 0);
71+
72+
/** This is the position of the first category along this axis */
73+
private final DoubleProperty firstCategoryPos =
74+
new SimpleDoubleProperty(this, "firstCategoryPos", 0) {
75+
@Override
76+
protected void invalidated() {
77+
requestAxisLayout();
78+
measureInvalid = true;
79+
}
80+
};
81+
7382
private Object currentAnimationID;
7483
private final ChartLayoutAnimator animator = new ChartLayoutAnimator(this);
7584
private ListChangeListener<String> itemsListener = c -> {
@@ -239,7 +248,15 @@ public final ObservableList<String> getCategories() {
239248
}
240249

241250
/** This is the gap between one category and the next along this axis */
242-
private final ReadOnlyDoubleWrapper categorySpacing = new ReadOnlyDoubleWrapper(this, "categorySpacing", 1);
251+
private final ReadOnlyDoubleWrapper categorySpacing =
252+
new ReadOnlyDoubleWrapper(this, "categorySpacing", 1) {
253+
@Override
254+
protected void invalidated() {
255+
requestAxisLayout();
256+
measureInvalid = true;
257+
}
258+
};
259+
243260
public final double getCategorySpacing() {
244261
return categorySpacing.get();
245262
}

modules/javafx.controls/src/test/java/test/javafx/scene/chart/BarChartTest.java

+95-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -29,6 +29,10 @@
2929
import java.util.List;
3030

3131
import javafx.collections.FXCollections;
32+
import javafx.scene.Scene;
33+
import javafx.scene.shape.MoveTo;
34+
import javafx.scene.shape.Path;
35+
import javafx.stage.Stage;
3236
import org.junit.Test;
3337
import static org.junit.Assert.assertEquals;
3438
import javafx.collections.*;
@@ -271,4 +275,94 @@ public void testAddingMultipleSeriesWithDuplicateCategories() {
271275
assertEquals("5", categories.get(3));
272276
assertEquals("4", categories.get(4));
273277
}
278+
279+
@Test
280+
public void testTickMarksMatchBarPositionsAfterAnimation() {
281+
startApp();
282+
CategoryAxis xAxis = new CategoryAxis();
283+
NumberAxis yAxis = new NumberAxis();
284+
BarChart<String, Number> chart = new BarChart<>(xAxis, yAxis);
285+
Series<String, Number> series = new Series<>();
286+
chart.getData().add(series);
287+
chart.setAnimated(true);
288+
getTestScene().setRoot(chart);
289+
290+
// add some categories, starting axis animation
291+
series.getData().add(new XYChart.Data<>("1", 1));
292+
series.getData().add(new XYChart.Data<>("2", 2));
293+
series.getData().add(new XYChart.Data<>("3", 3));
294+
pulse();
295+
// forward time until after animation is finished
296+
toolkit.setAnimationTime(1000);
297+
298+
List<Node> bars = series.getData().stream().map(XYChart.Data::getNode).toList();
299+
300+
List<Double> barCenterXValues = series.getData().stream()
301+
.map(XYChart.Data::getNode)
302+
.map(bar -> bar.getLayoutX() + bar.getLayoutBounds().getCenterX())
303+
.toList();
304+
305+
List<Double> tickXValues = xAxis.getChildrenUnmodifiable().stream()
306+
.filter(obj -> obj instanceof Path && obj.getStyleClass().contains("axis-tick-mark"))
307+
.flatMap(obj -> ((Path) obj).getElements().stream())
308+
.filter(path -> path instanceof MoveTo)
309+
.map(moveTo -> ((MoveTo) moveTo).getX())
310+
.toList();
311+
312+
double delta = 0.001;
313+
assertEquals(barCenterXValues.size(), tickXValues.size());
314+
for (int i = 0; i < barCenterXValues.size(); i++) {
315+
assertEquals(barCenterXValues.get(i), tickXValues.get(i), delta);
316+
}
317+
}
318+
319+
@Test
320+
public void testBarPositionsWithMultipleIncompleteSeries() {
321+
startApp();
322+
CategoryAxis xAxis = new CategoryAxis();
323+
NumberAxis yAxis = new NumberAxis();
324+
BarChart<String, Number> chart = new BarChart<>(xAxis, yAxis);
325+
chart.setAnimated(false);
326+
chart.setBarGap(0.0);
327+
chart.setCategoryGap(0.0);
328+
getTestScene().setRoot(chart);
329+
330+
XYChart.Series<String, Number> series1 = new XYChart.Series<>();
331+
series1.setName("S1");
332+
chart.getData().setAll(List.of(series1));
333+
series1.getData().add(new XYChart.Data<>("1", 1));
334+
series1.getData().add(new XYChart.Data<>("2", 2));
335+
336+
XYChart.Series<String, Number> series2 = new XYChart.Series<>();
337+
series2.setName("S2");
338+
series2.getData().add(new XYChart.Data<>("2", 3)); // duplicate category with series1
339+
series2.getData().add(new XYChart.Data<>("3", 4)); // new category
340+
chart.getData().add(series2);
341+
342+
pulse();
343+
344+
// check bar layout
345+
List<Node> s1bars = series1.getData().stream().map(XYChart.Data::getNode).toList();
346+
List<Node> s2bars = series2.getData().stream().map(XYChart.Data::getNode).toList();
347+
348+
double x0 = s1bars.getFirst().getLayoutX();
349+
double barWidth = s1bars.getFirst().getBoundsInLocal().getWidth();
350+
351+
// normalize bar positions with respect to the first bar position and width
352+
List<Double> normalized1 = s1bars.stream()
353+
.map(node -> (node.getLayoutX() - x0) / barWidth)
354+
.toList();
355+
356+
List<Double> normalized2 = s2bars.stream()
357+
.map(node -> (node.getLayoutX() - x0) / barWidth)
358+
.toList();
359+
360+
// expect even integers for series1 and odd integers for series2
361+
double delta = 0.001;
362+
assertEquals(0, normalized1.get(0), delta);
363+
assertEquals(2, normalized1.get(1), delta);
364+
assertEquals(3, normalized2.get(0), delta);
365+
assertEquals(5, normalized2.get(1), delta);
366+
}
367+
274368
}

0 commit comments

Comments
 (0)