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.swing.chart;
18  
19  import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
20  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
21  import io.github.tonywasher.joceanus.tethys.core.chart.TethysUICoreAreaChart;
22  import io.github.tonywasher.joceanus.tethys.core.factory.TethysUICoreFactory;
23  import io.github.tonywasher.joceanus.tethys.swing.base.TethysUISwingNode;
24  import org.jfree.chart.ChartFactory;
25  import org.jfree.chart.ChartMouseEvent;
26  import org.jfree.chart.ChartMouseListener;
27  import org.jfree.chart.ChartPanel;
28  import org.jfree.chart.JFreeChart;
29  import org.jfree.chart.axis.DateAxis;
30  import org.jfree.chart.axis.DateTick;
31  import org.jfree.chart.axis.NumberAxis;
32  import org.jfree.chart.axis.Tick;
33  import org.jfree.chart.entity.ChartEntity;
34  import org.jfree.chart.entity.XYItemEntity;
35  import org.jfree.chart.plot.XYPlot;
36  import org.jfree.chart.renderer.xy.StackedXYAreaRenderer2;
37  import org.jfree.chart.ui.RectangleEdge;
38  import org.jfree.chart.util.HexNumberFormat;
39  import org.jfree.data.time.Day;
40  import org.jfree.data.time.TimeTableXYDataset;
41  
42  import java.awt.Graphics2D;
43  import java.awt.geom.Rectangle2D;
44  import java.io.Serial;
45  import java.text.FieldPosition;
46  import java.text.SimpleDateFormat;
47  import java.util.ArrayList;
48  import java.util.List;
49  
50  /**
51   * Swing areaChart.
52   */
53  public class TethysUISwingAreaChart
54          extends TethysUICoreAreaChart {
55      /**
56       * The angle for labels.
57       */
58      protected static final double LABEL_RADIANS = Math.PI * ((double) LABEL_ANGLE / 180);
59  
60      /**
61       * The Node.
62       */
63      private final TethysUISwingNode theNode;
64  
65      /**
66       * The dataSet.
67       */
68      private final TimeTableXYDataset theDataSet;
69  
70      /**
71       * The chart.
72       */
73      private final JFreeChart theChart;
74  
75      /**
76       * The panel.
77       */
78      private final ChartPanel thePanel;
79  
80      /**
81       * Constructor.
82       *
83       * @param pFactory the Gui Factory
84       */
85      TethysUISwingAreaChart(final TethysUICoreFactory<?> pFactory) {
86          /* Initialise underlying class */
87          super(pFactory);
88  
89          /* Create the dataSet */
90          theDataSet = new TimeTableXYDataset();
91  
92          /* Create the chart */
93          theChart = ChartFactory.createTimeSeriesChart(null, null, null, theDataSet);
94          final XYPlot myPlot = (XYPlot) theChart.getPlot();
95          final StackedXYAreaRenderer2 myRenderer = new StackedXYAreaRenderer2();
96          myRenderer.setDefaultToolTipGenerator((pDataset, pSeries, pItem) -> {
97              final OceanusMoney myValue = new OceanusMoney(pDataset.getY(pSeries, pItem).toString());
98              return getToolTip(pDataset.getSeriesKey(pSeries).toString(), myValue);
99          });
100         myPlot.setRenderer(0, myRenderer);
101         final NumberAxis myYAxis = (NumberAxis) myPlot.getRangeAxis();
102         myYAxis.setNumberFormatOverride(new MoneyFormat());
103         final TethysDateAxis myXAxis = new TethysDateAxis();
104         myXAxis.setDateFormatOverride(new SimpleDateFormat("dd-MMM-yyyy"));
105         myXAxis.setVerticalTickLabels(true);
106         myPlot.setDomainAxis(myXAxis);
107 
108         /* Create the panel */
109         thePanel = new ChartPanel(theChart);
110         thePanel.setOpaque(true);
111         thePanel.addChartMouseListener(new ChartMouseListener() {
112             @Override
113             public void chartMouseMoved(final ChartMouseEvent e) {
114                 /* NoOp */
115             }
116 
117             @Override
118             public void chartMouseClicked(final ChartMouseEvent e) {
119                 final ChartEntity entity = e.getEntity();
120                 if (entity instanceof XYItemEntity section) {
121                     selectSeries((String) theDataSet.getSeriesKey(section.getSeriesIndex()));
122                 }
123             }
124         });
125 
126         /* Create the node */
127         theNode = new TethysUISwingNode(thePanel);
128     }
129 
130     @Override
131     public TethysUISwingNode getNode() {
132         return theNode;
133     }
134 
135     @Override
136     public void setVisible(final boolean pVisible) {
137         theNode.setVisible(pVisible);
138     }
139 
140     @Override
141     public void setEnabled(final boolean pEnabled) {
142         thePanel.setEnabled(pEnabled);
143     }
144 
145     @Override
146     public void setPreferredWidth(final Integer pWidth) {
147         theNode.setPreferredWidth(pWidth);
148     }
149 
150     @Override
151     public void setPreferredHeight(final Integer pHeight) {
152         theNode.setPreferredHeight(pHeight);
153     }
154 
155     @Override
156     public void updateAreaChart(final TethysUIAreaChartData pData) {
157         /* Update underlying data */
158         super.updateAreaChart(pData);
159 
160         /* Set the chart title and Axis labels */
161         theChart.setTitle(pData.getTitle());
162         final XYPlot myPlot = (XYPlot) theChart.getPlot();
163         final DateAxis myXAxis = (DateAxis) myPlot.getDomainAxis();
164         myXAxis.setLabel(pData.getXAxisLabel());
165         final NumberAxis myYAxis = (NumberAxis) myPlot.getRangeAxis();
166         myYAxis.setLabel(pData.getYAxisLabel());
167 
168         /* Declare changes */
169         theChart.fireChartChanged();
170     }
171 
172     @Override
173     protected void resetData() {
174         /* Clear existing data  */
175         theDataSet.clear();
176 
177         /* Clear underlying data  */
178         super.resetData();
179     }
180 
181     @Override
182     protected void createPoint(final String pName,
183                                final TethysUIAreaChartDataPoint pPoint) {
184         /* Add the point */
185         theDataSet.add(dateToDay(pPoint.getDate()), pPoint.getValue().doubleValue(), pName);
186     }
187 
188     private static Day dateToDay(final OceanusDate pDate) {
189         return new Day(pDate.getDay(), pDate.getMonth(), pDate.getYear());
190     }
191 
192     /**
193      * Money Format class.
194      */
195     private final class MoneyFormat extends HexNumberFormat {
196         @Serial
197         private static final long serialVersionUID = 1200795726700321267L;
198 
199         @Override
200         public StringBuffer format(final double pValue,
201                                    final StringBuffer pBuffer,
202                                    final FieldPosition pLoc) {
203             final OceanusMoney myMoney = new OceanusMoney(Double.toString(pValue));
204             return new StringBuffer(getFormatter().formatMoney(myMoney));
205         }
206     }
207 
208     /**
209      * Extended DateAxis.
210      */
211     private static final class TethysDateAxis extends DateAxis {
212         @Serial
213         private static final long serialVersionUID = 1976939393800292546L;
214 
215         @SuppressWarnings("unchecked")
216         @Override
217         protected List<Tick> refreshTicksHorizontal(final Graphics2D g2,
218                                                     final Rectangle2D dataArea,
219                                                     final RectangleEdge edge) {
220             final List<Tick> ticks = super.refreshTicksHorizontal(g2, dataArea, edge);
221             final List<Tick> ret = new ArrayList<>();
222             for (Tick tick : ticks) {
223                 if (tick instanceof DateTick dateTick) {
224                     ret.add(new DateTick(dateTick.getDate(), dateTick.getText(), dateTick.getTextAnchor(),
225                             dateTick.getRotationAnchor(), LABEL_RADIANS));
226                 } else {
227                     ret.add(tick);
228                 }
229             }
230             return ret;
231         }
232 
233         @Override
234         protected double findMaximumTickLabelHeight(final List ticks,
235                                                     final Graphics2D g2,
236                                                     final Rectangle2D drawArea,
237                                                     final boolean vertical) {
238             return super.findMaximumTickLabelHeight(ticks, g2, drawArea, vertical) * Math.sin(-LABEL_RADIANS);
239         }
240     }
241 }