1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.github.tonywasher.joceanus.tethys.javafx.chart;
18
19 import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
20 import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
21 import javafx.collections.ObservableList;
22 import javafx.scene.Node;
23 import javafx.scene.chart.NumberAxis;
24 import javafx.scene.chart.StackedAreaChart;
25 import javafx.scene.chart.XYChart.Data;
26 import javafx.scene.chart.XYChart.Series;
27 import javafx.scene.control.Tooltip;
28 import javafx.scene.input.MouseEvent;
29 import javafx.util.StringConverter;
30 import io.github.tonywasher.joceanus.tethys.core.chart.TethysUICoreAreaChart;
31 import io.github.tonywasher.joceanus.tethys.core.factory.TethysUICoreFactory;
32 import io.github.tonywasher.joceanus.tethys.javafx.base.TethysUIFXNode;
33
34 import java.time.LocalDate;
35 import java.util.HashMap;
36 import java.util.Map;
37
38
39
40
41 public class TethysUIFXAreaChart
42 extends TethysUICoreAreaChart {
43
44
45
46 private static final double BORDER_FACTOR = 0.05;
47
48
49
50
51 private static final double TICK_FACTOR = 0.05;
52
53
54
55
56 private final TethysUIFXNode theNode;
57
58
59
60
61 private final StackedAreaChart<Number, Number> theChart;
62
63
64
65
66 private final Map<String, Series<Number, Number>> theSeries;
67
68
69
70
71 private Number theMinimum;
72
73
74
75
76 private Number theMaximum;
77
78
79
80
81
82
83 TethysUIFXAreaChart(final TethysUICoreFactory<?> pFactory) {
84
85 super(pFactory);
86
87
88 final NumberAxis myXAxis = new NumberAxis();
89 myXAxis.setAutoRanging(false);
90 myXAxis.setForceZeroInRange(false);
91 myXAxis.setTickLabelRotation(LABEL_ANGLE);
92 myXAxis.setTickLabelFormatter(new StringConverter<>() {
93 @Override
94 public String toString(final Number pValue) {
95 return new OceanusDate(LocalDate.ofEpochDay(pValue.longValue())).toString();
96 }
97
98 @Override
99 public Number fromString(final String pValue) {
100 return null;
101 }
102 });
103 final NumberAxis myYAxis = new NumberAxis();
104 myYAxis.setTickLabelFormatter(new StringConverter<>() {
105 @Override
106 public String toString(final Number pValue) {
107 return getFormatter().formatMoney(new OceanusMoney(pValue.toString()));
108 }
109
110 @Override
111 public Number fromString(final String pValue) {
112 return null;
113 }
114 });
115 theChart = new StackedAreaChart<>(myXAxis, myYAxis);
116 theChart.setHorizontalGridLinesVisible(false);
117 theChart.setVerticalGridLinesVisible(false);
118
119
120 theSeries = new HashMap<>();
121
122
123 theNode = new TethysUIFXNode(theChart);
124 }
125
126 @Override
127 public TethysUIFXNode getNode() {
128 return theNode;
129 }
130
131 @Override
132 public void setVisible(final boolean pVisible) {
133 theNode.setManaged(pVisible);
134 theNode.setVisible(pVisible);
135 }
136
137 @Override
138 public void setEnabled(final boolean pEnabled) {
139 theChart.setDisable(!pEnabled);
140 }
141
142 @Override
143 public void setPreferredWidth(final Integer pWidth) {
144 theChart.setPrefWidth(pWidth);
145 }
146
147 @Override
148 public void setPreferredHeight(final Integer pHeight) {
149 theChart.setPrefHeight(pHeight);
150 }
151
152 @Override
153 public void updateAreaChart(final TethysUIAreaChartData pData) {
154
155 super.updateAreaChart(pData);
156
157
158 theChart.setTitle(pData.getTitle());
159
160
161 final NumberAxis myAxis = (NumberAxis) theChart.getXAxis();
162 myAxis.setLabel(pData.getXAxisLabel());
163 if (theMinimum != null) {
164 final double myAdjust = getBorderAdjust();
165 myAxis.setLowerBound(theMinimum.doubleValue() - myAdjust);
166 myAxis.setUpperBound(theMaximum.doubleValue() + myAdjust);
167 myAxis.setTickUnit(getTickCount());
168 }
169
170
171 theChart.getYAxis().setLabel(pData.getYAxisLabel());
172 }
173
174 @Override
175 protected void resetData() {
176
177 final ObservableList<Series<Number, Number>> myData = theChart.getData();
178 myData.clear();
179
180
181 theMaximum = null;
182 theMinimum = null;
183
184
185 super.resetData();
186 }
187
188
189
190
191
192
193 private double getBorderAdjust() {
194 final double myRange = theMaximum.doubleValue() - theMinimum.doubleValue();
195 return myRange * BORDER_FACTOR;
196 }
197
198
199
200
201
202
203 private long getTickCount() {
204 final double myRange = theMaximum.doubleValue() - theMinimum.doubleValue();
205 return (long) (myRange * TICK_FACTOR);
206 }
207
208 @Override
209 protected void createPoint(final String pName,
210 final TethysUIAreaChartDataPoint pPoint) {
211
212 Series<Number, Number> mySeries = theSeries.get(pName);
213 if (mySeries == null) {
214
215 mySeries = new Series<>();
216 mySeries.setName(pName);
217 final ObservableList<Series<Number, Number>> myData = theChart.getData();
218 myData.add(mySeries);
219 theSeries.put(pName, mySeries);
220
221
222 mySeries.getNode().addEventHandler(MouseEvent.MOUSE_CLICKED, e -> selectSeries(pName));
223 }
224
225
226 final ObservableList<Data<Number, Number>> myPoints = mySeries.getData();
227 final Data<Number, Number> myData = new Data<>(dateToEpoch(pPoint.getDate()), pPoint.getValue().doubleValue());
228 myPoints.add(myData);
229 final Node myNode = myData.getNode();
230
231
232 final String myTooltip = getToolTip(pName, pPoint.getValue());
233 Tooltip.install(myNode, new Tooltip(myTooltip));
234
235
236 final Number myDate = myData.getXValue();
237 if (theMinimum == null
238 || theMinimum.longValue() > myDate.longValue()) {
239 theMinimum = myDate;
240 }
241 if (theMaximum == null
242 || theMaximum.longValue() < myDate.longValue()) {
243 theMaximum = myDate;
244 }
245 }
246
247
248
249
250
251
252
253 private static long dateToEpoch(final OceanusDate pDate) {
254 return pDate.getDate().toEpochDay();
255 }
256 }