1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
| public class TimelineView extends Region {
private final Region axis = new Region();
private final Path ticks = new Path();
private final Pane tickLabels = new Pane();
public TimelineView() {
super();
setId("timelimeView");
getStyleClass().add("timeline-view");
axis.setId("axis");
axis.getStyleClass().add("axis");
ticks.setId("ticks");
ticks.getStyleClass().add("ticks");
tickLabels.setId("tickLabels");
tickLabels.getStyleClass().add("tick-labels");
getChildren().addAll(axis, ticks, tickLabels);
startDateProperty().addListener(timelineChangeListener);
endDateProperty().addListener(timelineChangeListener);
tickUnitProperty().addListener(timelineChangeListener);
tickLengthProperty().addListener(timelineChangeListener);
widthProperty().addListener(timelineChangeListener);
heightProperty().addListener(timelineChangeListener);
}
@Override
public String getUserAgentStylesheet() {
final URL url = getClass().getResource("timelineview.css");
return (url == null) ? null : url.toExternalForm();
}
@Override
protected void layoutChildren() {
super.layoutChildren();
final double width = getWidth();
final double height = getHeight();
final Insets insets = getInsets();
final double areaX = insets.getLeft();
final double areaY = insets.getTop();
final double areaW = Math.max(0, width - insets.getLeft() - insets.getRight());
final double areaH = Math.max(0, height - insets.getTop() - insets.getBottom());
layoutChildrenInArea(areaX, areaY, areaW, areaH);
}
private int clamp(final double position) {
return (int) Math.round(position);
}
private void layoutChildrenInArea(final double areaX, final double areaY, final double areaW, final double areaH) {
final int axisX = clamp(areaX);
final int axisY = clamp(areaY + areaH * 2 / 3d);
final int axisW = clamp(areaW);
final int axisH = clamp(axis.getPrefHeight());
layoutInArea(axis, axisX, axisY, axisW, axisH, 0, HPos.LEFT, VPos.TOP);
//
final Period tickUnit = getTickUnit();
LocalDate startDate = getStartDate();
LocalDate endDate = getEndDate();
if (startDate.isAfter(endDate)) {
LocalDate tmp = startDate;
startDate = endDate;
endDate = tmp;
}
final List<LocalDate> calendar = new LinkedList();
for (LocalDate currentDate = startDate; currentDate.isBefore(endDate) || currentDate.isEqual(endDate); currentDate = currentDate.plus(tickUnit)) {
calendar.add(currentDate);
}
final int tickCount = calendar.size();
if (tickCount > 0 && ticks.getElements().isEmpty()) {
final DateTimeFormatter yearExtractor = DateTimeFormatter.ofPattern("YYYY");
final double tickDistance = axisW / Math.max(1, tickCount - 1);
final int tickY = axisY + axisH;
final double tickLength = Math.max(0, getTickLength());
for (int tickIndex = 0; tickIndex < tickCount; tickIndex++) {
final int tickX = clamp(axisX + tickIndex * tickDistance);
if (tickLength > 0) {
ticks.getElements().add(new MoveTo(tickX, tickY));
ticks.getElements().add(new LineTo(tickX, tickY + tickLength));
}
final LocalDate currentDate = calendar.get(tickIndex);
final String text = currentDate.format(yearExtractor);
final Text tickLabel = new Text(text);
tickLabel.setId("tickLabel");
tickLabel.getStyleClass().add("tick-label");
tickLabel.setTextOrigin(VPos.TOP);
tickLabels.getChildren().add(tickLabel);
}
// We need to apply CSS 1st for proper layout.
applyCss();
for (int tickIndex = 0; tickIndex < tickCount; tickIndex++) {
final int tickX = clamp(axisX + tickIndex * tickDistance);
final Text tickLabel = (Text) tickLabels.getChildren().get(tickIndex);
final int labelX = clamp(tickX - tickLabel.getBoundsInLocal().getWidth() / 2d);
final int labelY = clamp(tickY + tickLength + 5);
tickLabel.setLayoutX(labelX);
tickLabel.setLayoutY(labelY);
}
}
}
private final ReadOnlyObjectWrapper<LocalDate> startDate = new ReadOnlyObjectWrapper<>(this, "startDate", LocalDate.parse("1900-01-01"));
public final LocalDate getStartDate() {
return startDate.get();
}
public final void setStartDate(final LocalDate value) throws NullPointerException {
Objects.requireNonNull(value);
startDate.set(value);
}
public final ReadOnlyObjectProperty<LocalDate> startDateProperty() {
return startDate.getReadOnlyProperty();
}
private final ReadOnlyObjectWrapper<LocalDate> endDate = new ReadOnlyObjectWrapper<>(this, "endDate", LocalDate.parse("2000-01-01"));
public final LocalDate getEndDate() {
return endDate.get();
}
public final void setEndDate(final LocalDate value) throws NullPointerException {
Objects.requireNonNull(value);
endDate.set(value);
}
public final ReadOnlyObjectProperty<LocalDate> endDateProperty() {
return endDate.getReadOnlyProperty();
}
private final ReadOnlyObjectWrapper<Period> tickUnit = new ReadOnlyObjectWrapper<>(this, "tickUnit", Period.ofYears(10));
public final Period getTickUnit() {
return tickUnit.get();
}
public final void setTickUnit(final Period value) throws NullPointerException {
Objects.requireNonNull(value);
tickUnit.set(value);
}
public final ReadOnlyObjectProperty<Period> tickUnitProperty() {
return tickUnit.getReadOnlyProperty();
}
private final DoubleProperty tickLength = new SimpleDoubleProperty(this, "tickLength", 15);
public final double getTickLength() {
return tickLength.get();
}
public final void setTickLength(final double value) {
tickLength.set(value);
}
public final DoubleProperty tickLengthProperty() {
return tickLength;
}
private final ChangeListener timelineChangeListener = (observable, oldValue, newValue) -> {
ticks.getElements().clear();
tickLabels.getChildren().clear();
requestLayout();
};
} |
Partager