How to set DateAxis TickUnit (Date Interval) in JavaFX? - javafx

I'm developing a chart using JavaFX. I needed DateAxis, so I use
DateAxis from this code. The result is this. But I want show 2 date
interval, I didn't find solution. The result always show one day interval. This is my code.
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
ObservableList<XYChart.Series<Date, Number>> series = FXCollections.observableArrayList();
ObservableList<XYChart.Data<Date, Number>> series1Data = FXCollections.observableArrayList();
series1Data.add(new XYChart.Data<Date, Number>(new GregorianCalendar(2018,1, 12).getTime(), 2));
series1Data.add(new XYChart.Data<Date, Number>(new GregorianCalendar(2018, 1, 15).getTime(), 3));
series1Data.add(new XYChart.Data<Date, Number>(new GregorianCalendar(2018, 1, 16).getTime(), 4));
series.add(new XYChart.Series<>("Series1", series1Data));
NumberAxis numberAxis = new NumberAxis();
DateAxis dateAxis = new DateAxis();
dateAxis.setAutoRanging(false);
dateAxis.setLowerBound(new GregorianCalendar(2018,2, 12).getTime());
dateAxis.setUpperBound(new GregorianCalendar(2018,2, 16).getTime());
dateAxis.setTickLabelFormatter(new StringConverter<Date>() {
#Override
public String toString(Date object) {
return simpleDateFormat.format(object);
}
#Override
public Date fromString(String string) {
return null;
}
});
LineChart lineChart = new LineChart(dateAxis, numberAxis, series);
Scene scene = new Scene(lineChart, 900, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
How can I do that?

I might be wrong, but it seems that DateAxis API does not allow you to specify a preferred displayed interval, so you need to change it yourself.
In DateAxis class there's an Interval enum. If you change this line:
DAY(Calendar.DATE, 1),
To this one:
DAY(Calendar.DATE, 2),
And comment other intervals as necessary...
// HOUR_12(Calendar.HOUR, 12),
2-day intervals will be shown. Keep in mind that doing this is contrary to the author's intent, who wrote it this way so that the intervals are adjusted automatically, and it's a very crude solution. For a better one, you'd have to rewrite the calculateTickValues method, as well as change the private enum to some kind of public API.

Related

Execute and wait for multiple parallel and sequential Tasks by using a Arraylist of Tasks in JavaFX

I'm looking for a suitable way to display the processing time of parallel running Tasks on a separate stage.
I want to execute different tasks combined in an ArrayList - one after the other. For this case I'm using a ThreadPool. After each executed list, I want to wait until all tasks are completed. Only when the tasks have reached the status „succeeded“, I want to do something in the MainThread. After that I want to execute another list of tasks and visualize them on a separate stage as well. The following figure shows the desired processing sequence (depending on the source code listed below):
enter image description here
For this purpose I have written the classes MyLoader. The MyLoader-class contains a separate Task and binds the progress-properties with a Label and a Progressbar in the constructor:
public class MyLoader {
public Label label = null;
public ProgressBar progressBar = null;
public VBox vbox;
public Task<Integer> task = null;
public String name;
public MyLoader(String name) {
this.name = name;
this.label = new Label();
this.progressBar = new ProgressBar();
this.vbox = new VBox(2);
//UI-Layout for Progress
this.vbox.getChildren().addAll(this.label, this.progressBar);
HBox.setHgrow(this.vbox, Priority.ALWAYS);
this.vbox.setAlignment(Pos.CENTER);
this.progressBar.prefWidthProperty().bind(this.vbox.widthProperty().subtract(20));
//Counter-Size
Random r = new Random();
int max = r.nextInt((100 - 50) + 1) + 50;
//Task
this.task = new Task<Integer>() {
#Override
protected Integer call() throws Exception {
int idx = 0;
while(idx <= max) {
Thread.sleep(20); //... for long lasting processes
updateMessage(name+"-progress: "+idx);
updateProgress(idx, max);
idx++;
}
return max;
}
protected void succeeded() {
updateMessage(name+" succeeded!");
System.out.println(name+" succeeded!");
super.succeeded();
}
};
//Bind Properties
this.label.textProperty().bind(task.messageProperty());
this.progressBar.progressProperty().bind(task.progressProperty());
}
}
In the MainClass, I combine several MyLoader instances in an ArrayList and run them with an ExecutorService. To create the new stage I use the static method progressStage(List). Each Stage is shown before the ExecutorService executes the respective tasks. Here's the MainClass code:
public class MainClass extends Application{
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
//Thread-Pool
ExecutorService es = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//FirstLoaders
List<MyLoader> firstLoaders = new ArrayList<MyLoader>();
firstLoaders.add(new MyLoader("A"));
firstLoaders.add(new MyLoader("B"));
//Show 1. Stage
Stage firstStage = progressStage(firstLoaders);
firstStage.show();
//Execute firstLoaders
for(MyLoader l1 : firstLoaders)
es.execute(l1.task);
//1) TODO: How can I wait for the completion of the first loaders and start the second loaders?
//... doSomething1() ...
//SecondLoaders
List<MyLoader> secondLoaders = new ArrayList<MyLoader>();
secondLoaders.add(new MyLoader("C"));
secondLoaders.add(new MyLoader("D"));
secondLoaders.add(new MyLoader("E"));
//Show 2. Stage
Stage secondStage = progressStage(secondLoaders);
secondStage.setX(firstStage.getX());
secondStage.setY(firstStage.getY()+firstStage.getHeight());
secondStage.show();
for(MyLoader l2 : secondLoaders)
es.execute(l2.task);
//2) TODO How can I wait for the completion of the second loaders and start the primaryStage?
//... doSomething2() ...
Scene scene = new Scene(new StackPane(), 450, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
static Stage progressStage(List<MyLoader> loaderTasks) {
int count = loaderTasks.size();
VBox loadBox = new VBox(count);
for(int i=0; i<count; i++)
loadBox.getChildren().add(loaderTasks.get(i).vbox);
HBox.setHgrow(loadBox, Priority.ALWAYS);
loadBox.setAlignment(Pos.CENTER);
Stage dialogStage = new Stage();
dialogStage.setScene(new Scene(loadBox, 300, count * 50));
dialogStage.setAlwaysOnTop(true);
return dialogStage;
}
}
The program is executable so far - but the calculation sequence appears completely parallel.
What I tasted:
1) So far I have managed to get the process to be read and stopped using the get() method. But then the stage is only displayed when the threads in the background have finished their work.
//1) TODO: „doSomeThing1()“
List<Integer> integers = new ArrayList<Integer>();
for(MyLoader ml : firstLoaders)
integers.add(ml.task.get());
System.out.println(integers.toString());
2) Also with the Task.setOnSucceded() method I could not get any useful results yet. Mainly because the stage is only shown after the computing. The problem is that I am not able to query the status of all tasks at a defined time.
3) The application of a CountDownLatch has also achieved a comparable result.
4) In addition, the shutdown() method of the ExecutorService causes a termination. This solution is therefore also not suitable.
//1) TODO: „doSomeThing1()“
es.shutdown();
try {
es.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
//SecondLoaders
//...
}catch (InterruptedException e) {
e.printStackTrace();
}
Is there a suitable approach for such intentions? So far I have not come to any useful result.
When a task is done, just update a counter and check, if the task currently completed was the last one in the current set.
The following code demonstrates this. (There are certainly things in the code that could be improved though, but the concept should get clear.)
public class App extends Application {
public static void main(String[] args) {
launch(args);
}
private VBox taskViewContainer;
ExecutorService executor;
int tasksDone;
private void runTasks(List<MyTask> tasks, IntegerProperty index) {
if (tasks.isEmpty()) {
index.set(index.get()+1);
} else {
int taskCount = tasks.size();
tasksDone = 0;
for (MyTask task : tasks) {
taskViewContainer.getChildren().add(new TaskView(task));
task.setOnSucceeded(evt -> {
++tasksDone;
if (tasksDone == taskCount) {
// proceed to next task set after all tasks are done
index.set(index.get() + 1);
}
});
executor.submit(task);
}
}
}
#Override
public void init() throws Exception {
// create executor during initialisation
executor = Executors.newFixedThreadPool(4);
}
#Override
public void stop() throws Exception {
// shutdown executor when javafx shuts down
executor.shutdownNow();
}
#Override
public void start(Stage primaryStage) throws Exception {
taskViewContainer = new VBox();
Label text = new Label();
// generate random set of tasks
Random random = new Random();
List<List<MyTask>> taskLists = new ArrayList<>();
for (int i = 0; i < 20; ++i) {
int count = random.nextInt(10) + 1;
List<MyTask> tasks = new ArrayList<>(count);
taskLists.add(tasks);
for (int j = 0; j < count; ++j) {
tasks.add(new MyTask(String.format("%d.%c", i+1, (char) ('A'+j)), random.nextInt((100 - 50) + 1) + 50));
}
}
// property holding the current index in the task set list
IntegerProperty index = new SimpleIntegerProperty(-1);
index.addListener((o, oldValue, newValue) -> {
// gui update for change of task set
taskViewContainer.getChildren().clear();
text.setText(String.format("Task set %d / %d done", newValue, taskLists.size()));
int i = newValue.intValue();
if (i < taskLists.size()) {
// launch next set of tasks
runTasks(taskLists.get(i), index);
}
});
// start initial tasks
index.set(0);
text.setMinWidth(200);
text.setMaxWidth(Double.MAX_VALUE);
HBox root = new HBox(text, taskViewContainer);
root.setMinHeight(10 * 50);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
}
class TaskView extends HBox {
TaskView(MyTask task) {
setPrefSize(400, 50);
ProgressBar progress = new ProgressBar();
progress.progressProperty().bind(task.progressProperty());
Label label = new Label(task.getName());
Label message = new Label();
message.textProperty().bind(task.messageProperty());
getChildren().addAll(progress, new VBox(label, message));
}
}
class MyTask extends Task<Integer> {
private final int max;
private final String name;
public String getName() {
return name;
}
public MyTask(String name, int max) {
this.max = max;
this.name = name;
}
#Override
protected Integer call() throws Exception {
int idx = 0;
while(idx <= max) {
Thread.sleep(20); //... for long lasting processes
updateMessage(name+"-progress: "+idx);
updateProgress(idx, max);
idx++;
}
return max;
}
}
The above code does not take the possibility of canceling tasks/tasks terminating with an exception.

JavaFX manually set NumberAxis ticks

I have a LineChart that displays a market indicator such as this one:
As you see, what is interesting when using this kind of indicator is when the indicator is above or below threshold lines.
I want to do exactly as shown on the picture above: only have ticks on the YAxis at given values (middle, upper limit, lower limit).
Is it possible?
Following #kleopatra advice in the comments, I implemented my own NumberAxis:
public class FixedTicksAxis extends ValueAxis<Number> {
// List of ticks
private final List<Number> ticks;
// Formatter
private NumberAxis.DefaultFormatter defaultFormatter;
public FixedTicksAxis(Number... ticks) {
super();
this.ticks = Arrays.asList(ticks);
this.defaultFormatter = new NumberAxis.DefaultFormatter(new NumberAxis());
}
#Override
protected List<Number> calculateMinorTickMarks() {
return new ArrayList<>();
}
#Override
protected void setRange(Object range, boolean animate) {
final double[] rangeProps = (double[]) range;
final double lowerBound = rangeProps[0];
final double upperBound = rangeProps[1];
final double scale = rangeProps[2];
setLowerBound(lowerBound);
setUpperBound(upperBound);
currentLowerBound.set(lowerBound);
setScale(scale);
}
#Override
protected Object getRange() {
return new double[]{
getLowerBound(),
getUpperBound(),
getScale(),
};
}
#Override
protected List<Number> calculateTickValues(double length, Object range) {
return ticks;
}
#Override
protected String getTickMarkLabel(Number value) {
StringConverter<Number> formatter = getTickLabelFormatter();
if (formatter == null) formatter = defaultFormatter;
return formatter.toString(value);
}
}
NumberAxis is final, so I can't subclass it.
I didn't want to copy/paste the content of NumberAxis, but I cannot use a delegate for the protected methods, so I did not find any other way than copy/pasting...
But it works, using:
FixedTicksAxis yAxis = new FixedTicksAxis(30, 50, 70);
We get:

JavaFx choicebox event handler method doesnt change variable?

I'm new at javafx but I'm writing an app and I want my "to" value to change depending on which option is chosen in the choicebox but my current code always keeps it at 0..help? I want to be able to change the to depending on state
public void start(Stage primaryStage) {
double to=0;
primaryStage.setTitle("ShCal");
GridPane pane = new GridPane();
` pane.setAlignment(Pos.CENTER);
pane.setHgap(10);
pane.setVgap(10);
pane.setPadding(new Insets(25, 25, 25, 25));
Scene scene = new Scene(pane, 300, 275);
//button
Button button=new Button("to");
pane.add(button, 0, 3);
//Pick state
Label State=new Label("State");
pane.add(State,0,0);
//choicebox
ChoiceBox<String> choicesBox=new ChoiceBox<>();
choicesBox.getItems().addAll("NJ","NY");
pane.add(choicesBox,1,0);
//set default
choicesBox.setValue(null);
button.setOnAction(e->getChoice(choicesBox,to));
primaryStage.setScene(scene);
primaryStage.show();
}
private double getChoice(ChoiceBox<String> choicesBox, double tx) {
String state=choicesBox.getValue();
System.out.print(tx);
if(state=="NJ")
{
tx=10/100;
}
System.out.print(state);
return tx;
}
public static void main(String[] args) {
launch(args);
}
}
That is because your to value is of the primitive type double, defined in the scope of your start method. The method getChoice returns the new value, but you are not updating it.
Here are two approaches that you can try:
Define to as member:
private double to = 0;
private double getChoice(ChoiceBox<String> choicesBox) {
String state=choicesBox.getValue();
if(state=="NJ") {
tx=10/100;
}
}
However I personally would prefer a solution that is more inline with JavaFX: Define the to variable as member property:
private DoubleProperty to = new SimpleDoubleProperty(0);
private double getChoice(ChoiceBox<String> choicesBox) {
String state=choicesBox.getValue();
if(state=="NJ") {
tx.setValue(10/100);
}
}
Doing it this way you can then for example have a label displaying the value without the hassle of requiring to update it on each change:
Label lbl = new Label();
lbl.textProperty().bind(to.asString());

What to do with StackedAreaChart rendering bug

I often encounter a really troublesome bug in StackedAreaChart. Instead of getting
I get
The bug seems to rear its head often when data-points-per-display-pixel density is high (though not extraordinarily), non-linear, and/or when successive data series' x-components are not aligned. For example, the two images above are the of the same window, simply resized.
Here is the code that reproduces the bug on Windows 7 32-bit and 64-bit platforms:
public class ChartTest extends Application {
#Override
public void start(Stage primaryStage) {
NumberAxis xAxis = new NumberAxis(0, 100, 10);
NumberAxis yAxis = new NumberAxis(0, 100, 10);
StackedAreaChart<Number,Number> chart;
chart = new StackedAreaChart(xAxis,yAxis);
chart.setLegendVisible(false);
chart.setCreateSymbols(false);
chart.getData().add(genSeries(0));
chart.getData().add(genSeries(1));
StackPane root = new StackPane();
root.getChildren().add(chart);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
private Series<Number,Number> genSeries(int a){
Series<Number,Number> s = new Series<>();
ObservableList<XYChart.Data<Number, Number>> d = s.getData();
for(int i=0; i<50; i++){
double x = Math.sqrt(2*i+a);
d.add(new XYChart.Data<>(x, i));
}
return s;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Does anyone know anything about this bug, how to work around it, or under what conditions exactly it appears?
It can get really bothersome:

Sort ObservableList from high to low - javafx

Is there any way to sort a ObservableList based on the values from high to low?
Say I have a
ObservableList<XYChart.Data> data;
containing a String and a Double. I want the list sorted based on the Double from highest to lowest values. The reason I want this is because charts look way better if their values are shown from the highest to the lowest.
I have something like this now:
sortedData = new SortedList<>(data);
sortedData.comparatorProperty().bind(mycomparatorProperty());
You can create a Comparator, compare by the Y value, and then reverse the order:
data.sort(Comparator.comparing(XYChart.Data<String,Double>::getYValue).reversed());
This will sort your collection as intented.
Or you can return a new collection:
List<XYChart.Data<String,Double>> sortedData =
data.stream()
.sorted(Comparator.comparing(XYChart.Data<String,Double>::getYValue).reversed())
.collect(Collectors.toList());
EDIT
For the sake of clarity, this is a full sample:
public class FXMain extends Application {
private final ObservableList<XYChart.Data<String,Double>> data =
FXCollections.observableArrayList(
new XYChart.Data("P1",200d),
new XYChart.Data("P2",150d),
new XYChart.Data("P3",250d));
#Override
public void start(Stage primaryStage) {
Button btn = new Button"Sort data!");
btn.setOnAction(e -> {
ObservableList<XYChart.Data<String,Double>> data2 =
data.stream()
.sorted(Comparator.
comparing(XYChart.Data<String,Double>::getYValue).reversed())
.peek(System.out::println)
.collect(Collectors.toCollection(()->FXCollections.observableArrayList()));
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Resources