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