blob: 0205a4260193f269226c694d21989bd48feeb8c4 [file] [log] [blame]
package com.pivotal.jvsd.fx;
import java.io.File;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.util.Duration;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URL;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.ResourceBundle;
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Side;
import javafx.scene.chart.Axis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import com.pivotal.chart.AdvancedLineChart;
import com.pivotal.javafx.scene.chart.BasicSeries;
import com.pivotal.javafx.scene.chart.Data;
import com.pivotal.javafx.scene.chart.DateAxis;
import com.pivotal.jvsd.model.stats.StatArchiveFile.StatValue;
import com.pivotal.jvsd.stats.Utility;
/**
* An advanced line chart with a variety of actions and settable properties.
*
* @see javafx.scene.chart.LineChart
* @see javafx.scene.chart.Chart
* @see javafx.scene.chart.NumberAxis
* @see javafx.scene.chart.XYChart
*/
public class VSDChartWindow implements Initializable {
private int chartId;
private Stage stage;
private AdvancedLineChart<Number, Number> chart;
private ChartManager manager = ChartManager.getInstance();
@FXML
private Pane root;
@FXML
private Button resetButton;
@FXML
private Label coordLabel;
public void setStage(Stage stage) {
this.stage = stage;
this.stage.setOnCloseRequest(eh -> {
manager.removeChart(chartId);
});
}
public void setId(int chartId) {
this.chartId = chartId;
}
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
chart = createChart(root);
root.getChildren().add(chart);
// TODO better way to do this?
coordLabel.textProperty().bind(Bindings.format("%s x %s",
Bindings.createStringBinding(() -> DateFormat.getInstance().format(
chart.getXAxis().getValueForDisplay(
chart.crosshairXProperty().doubleValue())),
chart.crosshairXProperty()),
Bindings.createStringBinding(() -> {
final NumberFormat formatter = NumberFormat.getInstance();
formatter.setGroupingUsed(true);
return formatter.format(
chart.getPrimaryYAxis().getValueForDisplay(
chart.crosshairYProperty().doubleValue()));
}, chart.crosshairYProperty(), chart.primaryYAxisProperty())));
}
@FXML
private void resetRangeClicked() {
chart.getXAxis().setAutoRanging(true);
for (Axis<Number> yAxis : chart.getYAxes()) {
yAxis.setAutoRanging(true);
}
}
protected AdvancedLineChart<Number, Number> createChart(Pane root) {
final DateAxis xAxis = new DateAxis();
xAxis.setAutoRanging(true);
final NumberAxis yAxis = new NumberAxis();
final AdvancedLineChart<Number, Number> lc =
new AdvancedLineChart<>(xAxis, yAxis);
lc.setCreateSymbols(false);
lc.prefWidthProperty().bind(root.widthProperty());
lc.prefHeightProperty().bind(root.heightProperty());
if (ChartManager.MMTEST) {
//
// try (final RandomAccessFile memoryMappedFile = new RandomAccessFile("/tmp/test.vsd.stat1.vss", "rw")) {
//
// final MappedByteBuffer in = memoryMappedFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, memoryMappedFile.length());
//// in.limit(1000000);
// final LongDoubleMemoryMappedSeries s = new LongDoubleMemoryMappedSeries(in);
// lc.getData().add(s);
//
//// Timeline animation = new Timeline();
//// animation.getKeyFrames().add(new KeyFrame(Duration.seconds(1), actionEvent -> {
//// in.limit(in.limit()+16);
//// System.out.println(in.limit());
////// lc.toFront();
////// Data<Number, Number> last = s.getData().get(s.getData().size() - 1);
////// s.getData().add(new Data<>(last.getXValue().longValue() + 1000, last.getYValue().doubleValue() + (new Random().nextDouble() * 1000) - 500));
////
//// }));
//// animation.setCycleCount(Animation.INDEFINITE);
//// animation.play();
//
//
// } catch (FileNotFoundException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// } catch (IOException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
}
// lc.getData().add(new LongDoubleFileSeries(new File("/tmp/test.vsd.stat1.vss")));
return lc;
}
public void addToChart(StatValue sv) {
if (!stage.titleProperty().isBound()) {
stage.setTitle(
"Chart" + chartId + "_" + sv.getDescriptor().getName());
}
Axis<Number> yAxis;
final String units = sv.getDescriptor().getUnits();
yAxis = findOrCreateYAxis(units);
final long[] timestamps = sv.getRawAbsoluteTimeStamps();
final double[] values = sv.getRawSnapshots();
long timeSampleAverage = Utility.computeTimeSampleAverage(timestamps);
long samplePeriod = timeSampleAverage / 1000;
final List<Data<Number, Number>> data;
switch (sv.getFilter()) {
case StatValue.FILTER_NONE:
data = doFilterNone(samplePeriod, timestamps, values);
break;
case StatValue.FILTER_PERSEC:
data = doFilterPerSec(samplePeriod, timestamps, values);
break;
default:
data = null;
}
// add series data
if (ChartManager.MMTEST) {
addToChartMM(sv, yAxis, data);
} else {
BasicSeries<Number, Number> series = new BasicSeries<>(FXCollections.observableList(data));
series.setYAxis(yAxis);
series.setName(sv.getDescriptor().getName());
chart.getData().add(series);
}
}
private void addToChartMM(StatValue sv, Axis<Number> yAxis, List<Data<Number, Number>> data) {
try {
final File tmp = File.createTempFile(sv.getDescriptor().getName(), ".vss");
tmp.deleteOnExit();
try (final RandomAccessFile memoryMappedFile = new RandomAccessFile(tmp, "rw")) {
final MappedByteBuffer out = memoryMappedFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, data.size() * 16);
for (Data<Number, Number> d : data) {
out.putLong(d.getXValue().longValue());
out.putDouble(d.getYValue().doubleValue());
}
out.force();
final LongDoubleMemoryMappedSeries s = new LongDoubleMemoryMappedSeries(out);
s.setYAxis(yAxis);
s.setName(sv.getDescriptor().getName());
chart.getData().add(s);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
protected List<Data<Number, Number>> doFilterNone(long samplePeriod, final long[] timestamps, final double[] values) {
final ArrayList<Data<Number, Number>> list = new ArrayList<>(timestamps.length - 1);
final NumberFormat numberFormat = NumberFormat.getInstance();
// skip first value to calc value/s.
for (int i = 1; i < timestamps.length; i++) {
final Data<Number, Number> data = new Data<Number, Number>(timestamps[i], values[i]);
//TODO speed up data.setNode(new HoveredThresholdNode(numberFormat.format(values[i])));
list.add(data);
}
return list;
}
protected List<Data<Number, Number>> doFilterPerSec(long samplePeriod, final long[] timestamps, final double[] values) {
final ArrayList<Data<Number, Number>> list = new ArrayList<>(timestamps.length - 1);
final NumberFormat numberFormat = NumberFormat.getInstance();
// skip first value to calc value/s.
for (int i = 1; i < timestamps.length; i++) {
final long timestamp = timestamps[i];
final double psValue = Utility.computePerSecondValue(timestamp, timestamps[i - 1], values[i], values[i - 1], samplePeriod);
final Data<Number, Number> data = new Data<Number, Number>(timestamp, psValue);
//TODO speed up data.setNode(new HoveredThresholdNode(numberFormat.format(psValue)));
list.add(data);
}
return list;
}
private Axis<Number> findOrCreateYAxis(final String units) {
Axis<Number> yAxis = findYAxis(units);
if (null == yAxis) {
yAxis = new NumberAxis();
yAxis.setSide(Side.RIGHT);
chart.getYAxes().add(yAxis);
}
if (null == yAxis.getLabel()) {
yAxis.setLabel(units);
}
return yAxis;
}
private Axis<Number> findYAxis(final String units) {
for (Axis<Number> y : chart.getYAxes()) {
if (null == y.getLabel() || y.getLabel().equals(units)) {
return y;
}
}
return null;
}
public String getTitle() {
return stage.getTitle();
}
}