Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deterministic layout #592

Merged
merged 54 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
ff735d4
initial test with hardcoded layout
ennerf Jun 22, 2023
fd16e24
moved layout into a separate layout container
ennerf Jun 22, 2023
d8a9d2b
simplified hierarchy and removed unused parts
ennerf Jun 22, 2023
6d28dc2
changed chart to be a region
ennerf Jun 23, 2023
a83e5fa
changed the canvas drawing to be done post layout
ennerf Jun 23, 2023
0e01828
added outline for an axis-specific layout
ennerf Jun 26, 2023
e7253c8
added a chart pane based on generic node properties
ennerf Jun 27, 2023
a79fdf0
migrated chart to generic layout pane
ennerf Jun 27, 2023
90e771d
removed wrapper layer for axes
ennerf Jun 28, 2023
1dc75fd
added support for unmanaged nodes
ennerf Jun 28, 2023
e0a2673
removed wrapper layer for legend
ennerf Jun 28, 2023
6525cd1
changed measurement bars to be handled externally
ennerf Jun 28, 2023
3faf547
added utility for removing children
ennerf Jun 28, 2023
cde3c4b
fixed an issue with the contour chart z axis layout
ennerf Jun 28, 2023
c8ea38c
changed contour renderer to work with the new axis layout
ennerf Jun 28, 2023
6fea7d6
added missing import
ennerf Jun 28, 2023
c48de82
moved tick calculation to computing the pref dimensions
ennerf Jun 29, 2023
b097a0a
fixed non-public sample
ennerf Jun 29, 2023
0a33bbc
removed unnecessary css updates for nodes that are already in the Sce…
ennerf Jun 29, 2023
5d3330b
cleaned up axis style nodes
ennerf Jun 29, 2023
ed48416
split tickUnits into a user property and a read-only system property
ennerf Jun 30, 2023
5b8cafd
added copy setter for axis range
ennerf Jun 30, 2023
872f95c
axis drawing work in progress
ennerf Jun 30, 2023
b8cace0
removed unnecessary length dependency from tick computation
ennerf Jul 17, 2023
deb08f6
cleaned up auto range code
ennerf Jul 17, 2023
1f20582
added pref size computation based on range
ennerf Jul 17, 2023
28ac466
major changes to styling tick labels and mostly working size computation
ennerf Jul 19, 2023
90583b9
added axis padding
ennerf Jul 19, 2023
cd4f10c
improved overlap computation
ennerf Jul 19, 2023
cf36e78
refactored color gradient axis to make refactoring easier (untested)
ennerf Jul 19, 2023
9b61dc0
cleaned up tickmark drawing
ennerf Jul 19, 2023
054d7b7
fixed label overlap computation
ennerf Jul 19, 2023
3229960
fixed rotated labels and font scaling
ennerf Jul 19, 2023
0e6cf3f
added missing compatibility methods for older tests
ennerf Jul 19, 2023
123055b
added a heuristic to get a better estimate of the label placement
ennerf Jul 19, 2023
5912cf8
migrated tick label alignment tests
ennerf Jul 19, 2023
e19264d
minor readability cleanup
ennerf Jul 19, 2023
b93f097
significant cleanup of drawing axis labels
ennerf Jul 19, 2023
18637f0
fixed an issue where axes in renderes would not be displayed in children
ennerf Jul 19, 2023
ebfa2fe
fixed center axes and label drawing behavior
ennerf Jul 19, 2023
eeaf9da
cosmetic changes
ennerf Jul 19, 2023
005570b
Fixed unit tests
wirew0rm Jul 20, 2023
cc7290a
added minimum size computation to provide a good baseline guess
ennerf Jul 20, 2023
814b8af
added a settable gap between the axis line and the main canvas area (…
ennerf Jul 20, 2023
ae2b43b
Merge branch 'ennerf/layout-axis-wip' into ennerf/layout
ennerf Jul 20, 2023
71fe6c5
added missing file
ennerf Jul 20, 2023
e9813a9
minor snap cleanup
ennerf Jul 20, 2023
7bc290f
added layout pixel snapping for crisper rendering
ennerf Jul 20, 2023
935eac0
removed unnecessary gaps
ennerf Jul 20, 2023
979fe46
disconnected axis length from physical layout
ennerf Jul 20, 2023
6ec03b0
fixed invalidation conditions and examples
ennerf Jul 21, 2023
d84ecba
removed wrong import
ennerf Jul 21, 2023
fcd0ecb
updated test values
ennerf Jul 22, 2023
096ecd8
reverted userTickUnit split
ennerf Jul 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
362 changes: 168 additions & 194 deletions chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java

Large diffs are not rendered by default.

95 changes: 26 additions & 69 deletions chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import io.fair_acc.chartfx.axes.spi.AxisRange;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
Expand All @@ -17,7 +18,6 @@
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
Expand Down Expand Up @@ -60,7 +60,6 @@ public class XYChart extends Chart {
protected final ChangeListener<? super Boolean> gridLineVisibilitychange = (ob, o, n) -> requestLayout();
private long lastCanvasUpdate;
private boolean callCanvasUpdateLater;
private final ChangeListener<Side> axisSideChangeListener = this::axisSideChanged;

/**
* Construct a new XYChart with the given axes.
Expand Down Expand Up @@ -300,59 +299,19 @@ protected void axesChanged(final ListChangeListener.Change<? extends Axis> chang
while (change.next()) {
change.getRemoved().forEach(axis -> {
AssertUtils.notNull("to be removed axis is null", axis);
// check if axis is associated with an existing renderer, if yes
// -&gt; throw an exception
// remove from axis.side property side listener
removeFromAllAxesPanes(axis);
axis.sideProperty().removeListener(axisSideChangeListener);
// TODO: throw an exception if axis is associated with an existing renderer?
});

change.getAddedSubList().forEach(axis -> {
// check if axis is associated with an existing renderer,
// if yes -&gt; throw an exception
AssertUtils.notNull("to be added axis is null", axis);

final Side side = axis.getSide();
if (side == null) {
throw new InvalidParameterException("axis '" + axis.getName() + "' has 'null' as side being set");
}
if (axis instanceof Node && !getAxesPane(axis.getSide()).getChildren().contains(axis)) {
getAxesPane(axis.getSide()).getChildren().add((Node) axis);
}

axis.sideProperty().addListener(axisSideChangeListener);
});
}

requestLayout();
}

protected void axisSideChanged(final ObservableValue<? extends Side> change, final Side oldValue, final Side newValue) {
if (newValue != null && newValue.equals(oldValue)) {
return;
}
// loop through all registered axis
for (final Axis axis : axesList) {
if (axis.getSide() == null) {
// remove axis from all axis panes
removeFromAllAxesPanes(axis);
}

// check if axis is in correct pane
if (axis instanceof Node && getAxesPane(axis.getSide()).getChildren().contains(axis)) {
// yes, it is continue with next axis
continue;
}
// axis needs to be moved to new pane location
// first: remove axis from all axis panes
removeFromAllAxesPanes(axis);

// second: add axis to correct axis pane
getAxesPane(axis.getSide()).getChildren().add((Node) axis);
}
requestLayout();
}

/**
* checks whether renderer has required x and y axes and adds the first x or y from the chart itself if necessary
* <p>
Expand Down Expand Up @@ -447,52 +406,50 @@ protected static void updateNumericAxis(final Axis axis, final List<DataSet> dat
if (dataSets == null || dataSets.isEmpty()) {
return;
}
final boolean oldAutoState = axis.autoNotification().getAndSet(false);
final double oldMin = axis.getAutoRange().getMin();
final double oldMax = axis.getAutoRange().getMax();
final double oldLength = axis.getLength();

final boolean isHorizontal = axis.getSide().isHorizontal();
final boolean oldAutoState = axis.autoNotification().getAndSet(false);
final Side side = axis.getSide();
axis.getAutoRange().clear();
final boolean isHorizontal = side.isHorizontal();

// Determine the range of all datasets for this axis
final AxisRange dsRange = new AxisRange();
dsRange.clear();
dataSets.stream().filter(DataSet::isVisible).forEach(dataset -> dataset.lock().readLockGuard(() -> {
if (dataset.getDimension() > 2 && (side == Side.RIGHT || side == Side.TOP)) {
if (!dataset.getAxisDescription(DataSet.DIM_Z).isDefined()) {
dataset.recomputeLimits(DataSet.DIM_Z);
}
axis.getAutoRange().add(dataset.getAxisDescription(DataSet.DIM_Z).getMin());
axis.getAutoRange().add(dataset.getAxisDescription(DataSet.DIM_Z).getMax());
dsRange.add(dataset.getAxisDescription(DataSet.DIM_Z).getMin());
dsRange.add(dataset.getAxisDescription(DataSet.DIM_Z).getMax());
} else {
final int nDim = isHorizontal ? DataSet.DIM_X : DataSet.DIM_Y;
if (!dataset.getAxisDescription(nDim).isDefined()) {
dataset.recomputeLimits(nDim);
}
axis.getAutoRange().add(dataset.getAxisDescription(nDim).getMin());
axis.getAutoRange().add(dataset.getAxisDescription(nDim).getMax());
dsRange.add(dataset.getAxisDescription(nDim).getMin());
dsRange.add(dataset.getAxisDescription(nDim).getMax());
}
}));

// handling of numeric axis and auto-range or auto-grow setting only
if (!axis.isAutoRanging() && !axis.isAutoGrowRanging()) {
if (oldMin != axis.getMin() || oldMax != axis.getMax() || oldLength != axis.getLength()) {
axis.requestAxisLayout();
}
axis.autoNotification().set(oldAutoState);
return;
}

// Update the auto range
final boolean changed;
if (axis.isAutoGrowRanging()) {
axis.getAutoRange().add(oldMin);
axis.getAutoRange().add(oldMax);
changed = axis.getAutoRange().add(dsRange);
} else {
changed = axis.getAutoRange().set(dsRange);
}

axis.getAutoRange().setAxisLength(axis.getLength() == 0 ? 1 : axis.getLength(), side);
axis.getUserRange().setAxisLength(axis.getLength() == 0 ? 1 : axis.getLength(), side);
axis.invalidateRange(null);

if (oldMin != axis.getMin() || oldMax != axis.getMax() || oldLength != axis.getLength()) {
// Trigger a redraw
if (changed && (axis.isAutoRanging() || axis.isAutoGrowRanging())) {
axis.invalidateRange();
axis.requestAxisLayout();
}

// TODO: is this used for anything? can it be removed?
double axisLength = axis.getLength() == 0 ? 1 : axis.getLength();
axis.getAutoRange().setAxisLength(axisLength, side);
axis.getUserRange().setAxisLength(axisLength, side);

axis.autoNotification().set(oldAutoState);
}
}
17 changes: 13 additions & 4 deletions chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/Axis.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ public interface Axis extends AxisDescription {
*/
void drawAxis(GraphicsContext gc, double axisWidth, double axisHeight);

/**
* Draws the axis into the axis Canvas. This needs to be called from the containing Chart and
* before drawing other items. The axis may omit drawing if nothing has changed.
*/
void drawAxis();

/**
* forces redrawing of axis (via layoutChildren()). This is used to force an update while the main chart area is
* being updated (a requestLayout()) would be executed only during the next pulse. This is used explicitly in the
Expand Down Expand Up @@ -117,6 +123,11 @@ public interface Axis extends AxisDescription {

StringConverter<Number> getTickLabelFormatter();

/**
* @return the gap between the tick mark lines and the chart canvas
*/
double getTickMarkGap();

/**
* @return the gap between tick labels and the tick mark lines
*/
Expand Down Expand Up @@ -170,13 +181,11 @@ public interface Axis extends AxisDescription {
double getZeroPosition();

/**
* Called when data has changed and the range may not be valid any more. This is only called by the chart if
* Called when data has changed and the range may not be valid anymore. This is only called by the chart if
* isAutoRanging() returns true. If we are auto ranging it will cause layout to be requested and auto ranging to
* happen on next layout pass.
*
* @param data The current set of all data that needs to be plotted on this axis
*/
void invalidateRange(List<Number> data);
void invalidateRange();

/**
* This is {@code true} when the axis labels and data point order should be inverted
Expand Down
Loading