-
Notifications
You must be signed in to change notification settings - Fork 89
/
ChartIndicatorSample.java
334 lines (275 loc) · 14.3 KB
/
ChartIndicatorSample.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
package io.fair_acc.sample.chart;
import static io.fair_acc.dataset.DataSet.DIM_X;
import static io.fair_acc.dataset.DataSet.DIM_Y;
import java.time.ZoneOffset;
import java.util.Timer;
import java.util.TimerTask;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.fair_acc.chartfx.XYChart;
import io.fair_acc.chartfx.axes.spi.DefaultNumericAxis;
import io.fair_acc.chartfx.axes.spi.format.DefaultTimeFormatter;
import io.fair_acc.chartfx.plugins.*;
import io.fair_acc.chartfx.renderer.ErrorStyle;
import io.fair_acc.chartfx.renderer.datareduction.DefaultDataReducer;
import io.fair_acc.chartfx.renderer.spi.ErrorDataSetRenderer;
import io.fair_acc.chartfx.ui.ProfilerInfoBox;
import io.fair_acc.chartfx.ui.geometry.Side;
import io.fair_acc.dataset.spi.FifoDoubleErrorDataSet;
import io.fair_acc.dataset.testdata.spi.RandomDataGenerator;
import io.fair_acc.dataset.utils.ProcessingProfiler;
public class ChartIndicatorSample extends ChartSample {
private static final Logger LOGGER = LoggerFactory.getLogger(ChartIndicatorSample.class);
private static final int MIN_PIXEL_DISTANCE = 0; // 0: just drop points that are drawn on the same pixel
private static final int N_SAMPLES = 3000; // default: 1000000
private static final int UPDATE_DELAY = 1000; // [ms]
private static final int UPDATE_PERIOD = 40; // [ms]
private static final int BUFFER_CAPACITY = 750; // 750 samples @ 25 Hz <-> 30 s
private static final double MAX_DISTANCE = ChartIndicatorSample.BUFFER_CAPACITY * ChartIndicatorSample.UPDATE_PERIOD * 1e-3 * 0.90;
public final FifoDoubleErrorDataSet rollingBufferDipoleCurrent = new FifoDoubleErrorDataSet("dipole current [A]",
ChartIndicatorSample.BUFFER_CAPACITY, ChartIndicatorSample.MAX_DISTANCE);
public final FifoDoubleErrorDataSet rollingBufferBeamIntensity = new FifoDoubleErrorDataSet("beam intensity [ppp]",
ChartIndicatorSample.BUFFER_CAPACITY, ChartIndicatorSample.MAX_DISTANCE);
private final FifoDoubleErrorDataSet rollingSine = new FifoDoubleErrorDataSet("sine [A]",
ChartIndicatorSample.BUFFER_CAPACITY, ChartIndicatorSample.MAX_DISTANCE);
private final ErrorDataSetRenderer beamIntensityRenderer = new ErrorDataSetRenderer();
private final ErrorDataSetRenderer dipoleCurrentRenderer = new ErrorDataSetRenderer();
private Timer timer;
private long startTime;
private void generateData() {
startTime = ProcessingProfiler.getTimeStamp();
final double now = System.currentTimeMillis() / 1000.0 + 1; // N.B. '+1' to check for resolution
if (rollingBufferDipoleCurrent.getDataCount() == 0) {
for (int n = ChartIndicatorSample.N_SAMPLES; n > 0; n--) {
final double t = now - n * ChartIndicatorSample.UPDATE_PERIOD / 1000.0;
final double y = 25 * ChartIndicatorSample.rampFunctionDipoleCurrent(t);
final double y2 = 100 * ChartIndicatorSample.rampFunctionBeamIntensity(t);
final double ey = 1;
rollingBufferDipoleCurrent.add(t, y, ey, ey);
rollingBufferBeamIntensity.add(
t + ChartIndicatorSample.UPDATE_PERIOD / 1000.0 * RandomDataGenerator.random(), y2, ey, ey);
rollingSine.add(t + 1 + ChartIndicatorSample.UPDATE_PERIOD / 1000.0 * RandomDataGenerator.random(),
y * 0.8, ey, ey);
}
} else {
final double y = 25 * ChartIndicatorSample.rampFunctionDipoleCurrent(now);
final double y2 = 100 * ChartIndicatorSample.rampFunctionBeamIntensity(now);
final double ey = 1;
rollingBufferDipoleCurrent.add(now, y, ey, ey);
rollingBufferBeamIntensity.add(now, y2, ey, ey);
final double val = 1500 + 1000.0 * Math.sin(Math.PI * 2 * 0.1 * now);
rollingSine.add(now + 1, val, ey, ey);
}
ProcessingProfiler.getTimeDiff(startTime, "adding data into DataSet");
}
private HBox getHeaderBar(final TimerTask task) {
final Button newDataSet = new Button("new DataSet");
newDataSet.setOnAction(evt -> Platform.runLater(task));
final Button startTimer = new Button("timer");
startTimer.setOnAction(evt -> {
rollingBufferBeamIntensity.reset();
rollingBufferDipoleCurrent.reset();
if (timer == null) {
timer = new Timer("sample-update-timer", true);
timer.scheduleAtFixedRate(task, UPDATE_DELAY, UPDATE_PERIOD);
} else {
timer.cancel();
timer = null;
}
});
// H-Spacer
final Region spacer = new Region();
spacer.setMinWidth(Region.USE_PREF_SIZE);
HBox.setHgrow(spacer, Priority.ALWAYS);
// JavaFX and Chart Performance metrics
final ProfilerInfoBox profiler = new ProfilerInfoBox();
profiler.setDebugLevel(ProfilerInfoBox.DebugLevel.VERSION);
return new HBox(newDataSet, startTimer, spacer, profiler);
}
public BorderPane initComponents() {
final BorderPane root = new BorderPane();
generateData();
initErrorDataSetRenderer(beamIntensityRenderer);
initErrorDataSetRenderer(dipoleCurrentRenderer);
final DefaultNumericAxis xAxis1 = new DefaultNumericAxis();
final DefaultNumericAxis xAxis2 = new DefaultNumericAxis();
xAxis2.setAnimated(false);
final DefaultNumericAxis yAxis1 = new DefaultNumericAxis("beam intensity", "ppp");
final DefaultNumericAxis yAxis2 = new DefaultNumericAxis("dipole current", "A");
yAxis2.setSide(Side.RIGHT);
yAxis2.setAutoUnitScaling(true);
yAxis2.setAutoRanging(true);
yAxis2.setAnimated(false);
final DefaultNumericAxis yAxis3 = new DefaultNumericAxis("test", 0, 1, 0.1);
yAxis3.setSide(Side.RIGHT);
final DefaultNumericAxis xAxis3 = new DefaultNumericAxis("test", 0, 1, 0.1);
xAxis3.setSide(Side.TOP);
// N.B. it's important to set secondary axis on the 2nd renderer before adding the renderer to the chart
dipoleCurrentRenderer.getAxes().addAll(yAxis2);
final XYChart chart = new XYChart(xAxis1, yAxis1);
chart.legendVisibleProperty().set(true);
chart.setAnimated(false);
chart.getXAxis().setName("time 1");
chart.getXAxis().setAutoRanging(true);
chart.getYAxis().setName("beam intensity");
chart.getYAxis().setAutoRanging(true);
chart.getYAxis().setSide(Side.LEFT);
chart.getRenderers().set(0, beamIntensityRenderer);
chart.getRenderers().add(dipoleCurrentRenderer);
chart.getPlugins().add(new ParameterMeasurements());
chart.getPlugins().add(new EditAxis());
// chart.getPlugins().add(new CrosshairIndicator());
chart.getPlugins().add(new DataPointTooltip());
chart.getPlugins().add(new BenchPlugin());
final Zoomer zoom = new Zoomer();
zoom.setSliderVisible(false);
chart.getPlugins().add(zoom);
final double minX = rollingBufferDipoleCurrent.getAxisDescription(DIM_X).getMin();
final double maxX = rollingBufferDipoleCurrent.getAxisDescription(DIM_X).getMax();
final double minY1 = rollingBufferBeamIntensity.getAxisDescription(DIM_Y).getMin();
final double maxY1 = rollingBufferBeamIntensity.getAxisDescription(DIM_Y).getMax();
final double minY2 = rollingBufferDipoleCurrent.getAxisDescription(DIM_Y).getMin();
final double maxY2 = rollingBufferDipoleCurrent.getAxisDescription(DIM_Y).getMax();
final double rangeX = maxX - minX;
final double rangeY1 = maxY1 - minY1;
final double rangeY2 = maxY2 - minY2;
final XRangeIndicator xRange = new XRangeIndicator(xAxis1, minX + 0.1 * rangeX, minX + 0.2 * rangeX, "range-X");
chart.getPlugins().add(xRange);
xRange.upperBoundProperty().bind(xAxis1.maxProperty().subtract(0.1));
xRange.lowerBoundProperty().bind(xAxis1.maxProperty().subtract(1.0));
final YRangeIndicator yRange1 = new YRangeIndicator(yAxis1, minY1 + 0.1 * rangeY1, minY1 + 0.2 * rangeY1,
"range-Y1");
chart.getPlugins().add(yRange1);
final YRangeIndicator yRange2 = new YRangeIndicator(yAxis2, 2100, 2200, "range-Y2 (2100-2200 A)");
chart.getPlugins().add(yRange2);
final XValueIndicator xValueIndicator = new XValueIndicator(xAxis1, minX + 0.5 * rangeX, "mid-range label -X");
chart.getPlugins().add(xValueIndicator);
// xValueIndicator.valueProperty().bind(xAxis1.lowerBoundProperty().add(5));
final YValueIndicator yValueIndicator1 = new YValueIndicator(yAxis1, minY1 + 0.5 * rangeY1, "mid-range label -Y1");
chart.getPlugins().add(yValueIndicator1);
final YValueIndicator yValueIndicator2 = new YValueIndicator(yAxis2, minY2 + 0.2 * rangeY2, "mid-range label -Y2");
chart.getPlugins().add(yValueIndicator2);
beamIntensityRenderer.getDatasets().add(rollingBufferBeamIntensity);
dipoleCurrentRenderer.getDatasets().add(rollingBufferDipoleCurrent);
dipoleCurrentRenderer.getDatasets().add(rollingSine);
xAxis1.setAutoRangeRounding(false);
xAxis2.setAutoRangeRounding(false);
xAxis1.getTickLabelStyle().setRotate(45);
xAxis2.getTickLabelStyle().setRotate(45);
xAxis1.invertAxis(false);
xAxis2.invertAxis(false);
xAxis1.setTimeAxis(true);
xAxis2.setTimeAxis(true);
// set localised time offset
if (xAxis1.isTimeAxis() && xAxis1.getAxisLabelFormatter() instanceof DefaultTimeFormatter) {
final DefaultTimeFormatter axisFormatter = (DefaultTimeFormatter) xAxis1.getAxisLabelFormatter();
axisFormatter.setTimeZoneOffset(ZoneOffset.UTC);
axisFormatter.setTimeZoneOffset(ZoneOffset.ofHoursMinutes(5, 0));
}
yAxis1.setForceZeroInRange(true);
yAxis2.setForceZeroInRange(true);
yAxis1.setAutoRangeRounding(true);
yAxis2.setAutoRangeRounding(true);
chart.getAxes().addAll(yAxis3, xAxis3);
chart.getPlugins().add(new YValueIndicator(yAxis3, 0.4));
chart.getPlugins().add(new XValueIndicator(xAxis3, 0.3));
final TimerTask task = new TimerTask() {
int updateCount = 0;
@Override
public void run() {
Platform.runLater(() -> {
generateData();
if (updateCount % 20 == 0) {
LOGGER.atInfo().log("update iteration #" + updateCount);
}
// if (updateCount % 40 == 0) {
// //test dynamic left right axis change
// yAxis2.setSide(yAxis2.getSide().equals(Side.RIGHT)?Side.LEFT:Side.RIGHT);
// }
// if ((updateCount+20) % 40 == 0) {
// //test dynamic bottom top axis change
// xAxis1.setSide(xAxis1.getSide().equals(Side.BOTTOM)?Side.TOP:Side.BOTTOM);
// }
updateCount++;
});
}
};
root.setTop(getHeaderBar(task));
startTime = ProcessingProfiler.getTimeStamp();
ProcessingProfiler.getTimeDiff(startTime, "adding data to chart");
startTime = ProcessingProfiler.getTimeStamp();
root.setCenter(chart);
ProcessingProfiler.getTimeDiff(startTime, "adding chart into StackPane");
return root;
}
protected void initErrorDataSetRenderer(final ErrorDataSetRenderer eRenderer) {
eRenderer.setErrorStyle(ErrorStyle.ERRORSURFACE);
eRenderer.setDashSize(ChartIndicatorSample.MIN_PIXEL_DISTANCE); // plot pixel-to-pixel distance
eRenderer.setDrawMarker(false);
final DefaultDataReducer reductionAlgorithm = (DefaultDataReducer) eRenderer.getRendererDataReducer();
reductionAlgorithm.setMinPointPixelDistance(ChartIndicatorSample.MIN_PIXEL_DISTANCE);
}
@Override
public Node getChartPanel(final Stage primaryStage) {
ProcessingProfiler.setDebugState(false);
final BorderPane root = new BorderPane();
root.setCenter(initComponents());
startTime = ProcessingProfiler.getTimeStamp();
return root;
}
/**
* @param args the command line arguments
*/
public static void main(final String[] args) {
Application.launch(args);
}
private static double rampFunctionBeamIntensity(final double t) {
final int second = (int) Math.floor(t);
final double subSecond = t - second;
double offset = 0.3;
final double y = (1 - 0.1 * subSecond) * 1e9;
double gate = ChartIndicatorSample.square(2, subSecond - offset)
* ChartIndicatorSample.square(1, subSecond - offset);
// every 5th cycle is a booster mode cycle
if (second % 5 == 0) {
offset = 0.1;
gate = Math.pow(ChartIndicatorSample.square(3, subSecond - offset), 2);
}
if (gate <= 0 || subSecond < offset) {
gate = 0;
}
return gate * y;
}
private static double rampFunctionDipoleCurrent(final double t) {
final int second = (int) Math.floor(t);
final double subSecond = t - second;
double offset = 0.3;
double y = 100 * ChartIndicatorSample.sine(1, subSecond - offset);
// every 5th cycle is a booster mode cycle
if (second % 5 == 0) {
offset = 0.1;
y = 100 * Math.pow(ChartIndicatorSample.sine(1.5, subSecond - offset), 2);
}
if (y <= 0 || subSecond < offset) {
y = 0;
}
return y + 10;
}
private static double sine(final double frequency, final double t) {
return Math.sin(2.0 * Math.PI * frequency * t);
}
private static double square(final double frequency, final double t) {
final double sine = 100 * Math.sin(2.0 * Math.PI * frequency * t);
final double squarePoint = Math.signum(sine);
return squarePoint >= 0 ? squarePoint : 0.0;
}
}