View Javadoc
1   /*
2    * Tethys: GUI Utilities
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
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   * javaFX AreaChart.
40   */
41  public class TethysUIFXAreaChart
42          extends TethysUICoreAreaChart {
43      /**
44       * The border factor.
45       */
46      private static final double BORDER_FACTOR = 0.05;
47  
48      /**
49       * The border factor.
50       */
51      private static final double TICK_FACTOR = 0.05;
52  
53      /**
54       * The Node.
55       */
56      private final TethysUIFXNode theNode;
57  
58      /**
59       * The chart.
60       */
61      private final StackedAreaChart<Number, Number> theChart;
62  
63      /**
64       * The series map.
65       */
66      private final Map<String, Series<Number, Number>> theSeries;
67  
68      /**
69       * The minimun date.
70       */
71      private Number theMinimum;
72  
73      /**
74       * The maximun date.
75       */
76      private Number theMaximum;
77  
78      /**
79       * Constructor.
80       *
81       * @param pFactory the Gui Factory
82       */
83      TethysUIFXAreaChart(final TethysUICoreFactory<?> pFactory) {
84          /* initialise underlying class */
85          super(pFactory);
86  
87          /* Create chart */
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         /* Create the map */
120         theSeries = new HashMap<>();
121 
122         /* Create Node */
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         /* Update underlying data */
155         super.updateAreaChart(pData);
156 
157         /* Set the chart title and Axis labels */
158         theChart.setTitle(pData.getTitle());
159 
160         /* Adjust XAxis */
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         /* Adjust Y Axis */
171         theChart.getYAxis().setLabel(pData.getYAxisLabel());
172     }
173 
174     @Override
175     protected void resetData() {
176         /* Clear existing data  */
177         final ObservableList<Series<Number, Number>> myData = theChart.getData();
178         myData.clear();
179 
180         /* Clear max/min dates */
181         theMaximum = null;
182         theMinimum = null;
183 
184         /* Clear underlying data  */
185         super.resetData();
186     }
187 
188     /**
189      * Determine border adjustment.
190      *
191      * @return the border adjustment
192      */
193     private double getBorderAdjust() {
194         final double myRange = theMaximum.doubleValue() - theMinimum.doubleValue();
195         return myRange * BORDER_FACTOR;
196     }
197 
198     /**
199      * Determine tick count.
200      *
201      * @return the tick count
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         /* Access the series */
212         Series<Number, Number> mySeries = theSeries.get(pName);
213         if (mySeries == null) {
214             /* Create the series */
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             /* Install click handler for node */
222             mySeries.getNode().addEventHandler(MouseEvent.MOUSE_CLICKED, e -> selectSeries(pName));
223         }
224 
225         /* Add the point */
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         /* Create the toolTip */
232         final String myTooltip = getToolTip(pName, pPoint.getValue());
233         Tooltip.install(myNode, new Tooltip(myTooltip));
234 
235         /* Adjust max/min */
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      * Convert date to epoch.
249      *
250      * @param pDate the date
251      * @return the epoch
252      */
253     private static long dateToEpoch(final OceanusDate pDate) {
254         return pDate.getDate().toEpochDay();
255     }
256 }