I wrote a simple program to monitor my ping. I'm currently using NumberAxis
with auto ranging, and after each ping, I add the new data at the end, remove the first one and increment totalCount
variable for X axis position.
I would like the X axis label to either:
- show time elapsed since start. So for example, the label for 1100th ping at 0.25s ping intervals would be
4m 35s
- show time since the ping. This would require the labels to remain static (not move with the plot) and be in reversed order.
Is either of these two (preferably 1st) possible to implement? I suppose I would have to use CategoryAxis
for this, but I'm not sure how to create unlimited number of categories and choose to show only full minutes. Is it possible to keep the NumberAxis
for easier use with incoming data and just change the label text formatting? I already have a method that converts seconds to 00h 00m 00s
format.
Also another thing, I think related to auto ranging, the chart doesn't refresh after every input, but only once it exceeds 10% of given range. So for 1000 range as in the picture, it will draw 100 new pings, and then move everything 100 positions to the left. Can I change it somehow to move after just 1 ping?
Not sure if relevant, but I'll post the code:
Controller
public class GuiController implements Initializable {
@FXML
Button startButton, stopButton;
@FXML
TextField sField, nField, ipField;
@FXML
LineChart<Integer, Integer> chart;
@FXML
Label timeLabel, pingLabel;
ScheduledService<Integer> scheduler;
ObservableList<Data<Integer, Integer>> data;
public static int totalCount = 0;
private String getTime(double seconds) {
int h = (int) (seconds / 3600);
int m = (int) ((seconds % 3600) / 60);
int s = (int) (seconds % 60);
return String.format("%dh %dm %ds", h, m, s);
}
public void start() {
if (sField.getText().isEmpty() || Double.parseDouble(sField.getText()) == 0)
sField.setText("0.1");
data = FXCollections.observableArrayList();
int size = Integer.parseInt(nField.getText());
stop = false;
flip();
XYChart.Series<Integer, Integer> series = new Series<>();
for (int i = 0; i < size; i++) {
series.getData().add(new XYChart.Data<Integer, Integer>(totalCount++, 0));
}
chart.getData().clear();
chart.getData().add(series);
scheduler.setPeriod(Duration.seconds(Double.parseDouble(sField.getText())));
scheduler.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
@Override
public void handle(WorkerStateEvent event) {
if (series.getData().size() >= size)
series.getData().remove(0);
series.getData().add(new XYChart.Data<>(totalCount++, scheduler.getValue()));
updatePingLabel(scheduler.getValue());
}
});
scheduler.restart();
}
public void stop() {
scheduler.cancel();
stop = true;
flip();
totalCount = 0;
}
public static boolean isNumeric(String str) {
return str.matches("?\\d+(\\.\\d+)?");
}
public void flip() {
ipField.setDisable(!ipField.isDisabled());
nField.setDisable(!nField.isDisabled());
sField.setDisable(!sField.isDisabled());
startButton.setDisable(!startButton.isDisabled());
stopButton.setDisable(!stopButton.isDisabled());
}
public void updatePingLabel(int ping) {
pingLabel.setText(ping + "ms");
if (ping < 80)
pingLabel.setTextFill(Color.LAWNGREEN);
if (ping >= 80 && ping < 150)
pingLabel.setTextFill(Color.GOLD);
if (ping >=150 && ping < 400)
pingLabel.setTextFill(Color.ORANGE);
if (ping >= 400)
pingLabel.setTextFill(Color.RED);
}
@Override
public void initialize(URL arg0, ResourceBundle arg1) {
chart.getXAxis().setVisible(false);
chart.getXAxis().setAutoRanging(true);
stopButton.setDisable(true);
chart.getYAxis().setAutoRanging(true);
sField.textProperty().addListener(new ParamsChangeListener());
nField.textProperty().addListener(new ParamsChangeListener());
scheduler = new ScheduledService<Integer>() {
@Override
protected Task<Integer> createTask() {
return new PingTask(ipField.getText());
}
};
}
class ParamsChangeListener implements ChangeListener<String> {
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue,
String newValue) {
if (isNumeric(newValue))
timeLabel.setText(getTime(Double.parseDouble(sField.getText())
* Integer.parseInt(nField.getText())));
}
}
}
Ping Task
public class PingTask extends Task<Integer> {
int time;
String address;
public PingTask(String text) {
address = text;
}
@Override
protected Integer call() throws Exception {
try {
String cmd = "";
if (System.getProperty("os.name").startsWith("Windows")) {
cmd = "ping -n 1 " + address;
} else {
cmd = "ping -c 1 " + address;
}
Process process = Runtime.getRuntime().exec(cmd);
process.waitFor();
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
String inputLine = in.readLine();
while ((inputLine != null)) {
if (inputLine.startsWith("Reply from")) {
String[] parts = inputLine.split("[ =ms]");
time = Integer.parseInt(parts[9]);
break;
}
inputLine = in.readLine();
}
} catch (Exception e) {
e.printStackTrace();
}
return time;
}
}
FXML
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.GuiController">
<center>
<LineChart fx:id="chart" alternativeRowFillVisible="false" animated="false" createSymbols="false" horizontalZeroLineVisible="false" legendVisible="false" maxHeight="1.7976931348623157E308" verticalGridLinesVisible="false" verticalZeroLineVisible="false" BorderPane.alignment="CENTER">
<xAxis>
<NumberAxis animated="false" forceZeroInRange="false" minorTickCount="0" minorTickLength="0.0" minorTickVisible="false" side="BOTTOM" tickMarkVisible="false" tickUnit="1.0" upperBound="200.0" />
</xAxis>
<yAxis>
<NumberAxis animated="false" autoRanging="false" forceZeroInRange="true" minorTickCount="0" minorTickLength="0.0" minorTickVisible="false" side="LEFT" tickLabelGap="5.0" tickUnit="20.0" />
</yAxis>
</LineChart>
</center>
<left>
<VBox alignment="TOP_CENTER" spacing="10.0" BorderPane.alignment="CENTER">
<children>
<HBox alignment="CENTER" VBox.vgrow="NEVER">
<children>
<Label text="IP " />
<TextField fx:id="ipField" prefWidth="100.0" text="euw.leagueoflegends.com">
<opaqueInsets>
<Insets />
</opaqueInsets>
</TextField>
</children>
<padding>
<Insets top="5.0" />
</padding>
</HBox>
<HBox alignment="CENTER" VBox.vgrow="NEVER">
<children>
<Label text="Ping co " />
<TextField fx:id="sField" alignment="TOP_RIGHT" prefWidth="60.0" text="0.25" HBox.hgrow="NEVER">
<HBox.margin>
<Insets right="5.0" />
</HBox.margin>
</TextField>
<Label text="s" />
</children>
<padding>
<Insets top="5.0" />
</padding>
</HBox>
<HBox alignment="CENTER">
<children>
<Label text="Rysuj " />
<TextField fx:id="nField" alignment="CENTER_RIGHT" prefWidth="60.0" text="1000" HBox.hgrow="NEVER" />
<Label text=" próbek" />
</children>
<padding>
<Insets top="5.0" />
</padding>
</HBox>
<HBox alignment="CENTER" VBox.vgrow="NEVER">
<children>
<Label text="Pokaż " />
<Label fx:id="timeLabel" text="0h 4m 10s" />
</children>
</HBox>
<HBox alignment="CENTER" spacing="10.0" VBox.vgrow="NEVER">
<children>
<Button fx:id="startButton" mnemonicParsing="false" onAction="#start" prefWidth="50.0" text="Start" />
<Button fx:id="stopButton" mnemonicParsing="false" onAction="#stop" prefWidth="50.0" text="Stop" />
</children>
</HBox>
<Label fx:id="pingLabel" text="0ms" textAlignment="CENTER">
<font>
<Font name="System Bold" size="40.0" />
</font>
</Label>
</children>
<padding>
<Insets left="5.0" right="5.0" />
</padding>
</VBox>
</left>
</BorderPane>
EDIT
I tried using the formatter, but I'm getting ClassCastException: java.lang.Double cannot be cast to java.lang.Integer
at application.GuiController$XAxisLabelConverter.toString(GuiController.java:1) and I don't know what to do with it.
class XAxisLabelConverter extends StringConverter<Integer> {
double interval;
int n;
public XAxisLabelConverter(double interval, int n) {
this.interval = interval;
this.n = n;
}
@Override
public Integer fromString(String arg0) {
return null;
}
@Override
public String toString(Integer value) {
if (value < n) {
return "";
} else {
return getTime(value.intValue() * interval);
}
}
}
In the start()
method
((ValueAxis<Integer>) chart.getXAxis()).setTickLabelFormatter(new XAxisLabelConverter(
Double.parseDouble(sField.getText()),size));
public final class NumberAxis extends ValueAxis<Number>
. I'm no expert on types, but I think it's better to just use Number and get the intValue(). – brianNumberAxis
instances as the axes, andNumberAxis extends ValueAxis<Number> extends Axis<Number>
, you must make your chart aLineChart<Number, Number>
. Then you needXYChart.Series<Number, Number>
andXYChart.Data<Number, Number>
, and finally you need to define aStringConverter<Number>
as the formatter. As @brian says, you can callintValue()
on anyNumber
instance, so you really lose nothing by doing this. The only other way is to implementValueAxis<Integer>
youself, and you don't want to do that... – James_D@FXML
-inject the axis directly:@FXML private NumberAxis xAxis ;
and<NumberAxis fx:id="xAxis" .../>
– James_D