1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
52
53 public class TethysUISwingAreaChart
54 extends TethysUICoreAreaChart {
55
56
57
58 protected static final double LABEL_RADIANS = Math.PI * ((double) LABEL_ANGLE / 180);
59
60
61
62
63 private final TethysUISwingNode theNode;
64
65
66
67
68 private final TimeTableXYDataset theDataSet;
69
70
71
72
73 private final JFreeChart theChart;
74
75
76
77
78 private final ChartPanel thePanel;
79
80
81
82
83
84
85 TethysUISwingAreaChart(final TethysUICoreFactory<?> pFactory) {
86
87 super(pFactory);
88
89
90 theDataSet = new TimeTableXYDataset();
91
92
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
109 thePanel = new ChartPanel(theChart);
110 thePanel.setOpaque(true);
111 thePanel.addChartMouseListener(new ChartMouseListener() {
112 @Override
113 public void chartMouseMoved(final ChartMouseEvent e) {
114
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
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
158 super.updateAreaChart(pData);
159
160
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
169 theChart.fireChartChanged();
170 }
171
172 @Override
173 protected void resetData() {
174
175 theDataSet.clear();
176
177
178 super.resetData();
179 }
180
181 @Override
182 protected void createPoint(final String pName,
183 final TethysUIAreaChartDataPoint pPoint) {
184
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
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
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 }