JavaFX Chart - space between legends [duplicate] - java

This question already has an answer here:
Fix spacing between different series in JavaFX line chart
(1 answer)
Closed 3 years ago.
I have this code:
public static void main(String[] args){ launch(args); }
#Override
public void start(Stage primaryStage){
NumberAxis xAxis = new NumberAxis(0, 23, 1);
xAxis.setLabel("XX");
NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("YY");
LineChart<Number, Number> lineChart = new LineChart<>(xAxis, yAxis);
XYChart.Series<Number, Number> series1 = new XYChart.Series<>();
series1.setName("Loooooooooooooongest");
XYChart.Series<Number, Number> series2 = new XYChart.Series<>();
series2.setName("Short");
XYChart.Series<Number, Number> series3 = new XYChart.Series<>();
series3.setName("Loooooong");
lineChart.getData().addAll(series1, series2, series3);
primaryStage.setScene(new Scene(lineChart));
primaryStage.sizeToScene();
primaryStage.centerOnScreen();
primaryStage.show();
}
It looks like this:
Every legend has allocated space like the longest legend.
How I can remove all that space?

You can probably use a CSS Property. Maybe something like -fx-padding or -fx-margin. Not sure right now.
This could give you a good idea about for using CSS Properties in JavaFX: How to set specific color to JavaFX XYChart.Series?
Also it's a great idea to use the JavaFX-SceneBuilder. You can apply CSS there and directly see what it does.

Related

How to speed up drawing graphs (charts) in Java? (own library)

How to write a java library that would display graphs (charts) with large datasets (100-200 thousand points)? I think that such a large graph does not need to be displayed entirely, I probably only need to display a part of it, for example, a thousand points. But I need to be able to move around the graph and scale it.
It is also necessary that the charts are built in real time. As I understand it, for any action with the chart, the data must be displayed again, that is, thereby updated.
Maybe for speed it will be possible to do parallel execution of some parts of the code for drawing the chart. Because the speed of execution will be the main thing in this library.
I also plan to convert the chart to svg to be inserted into the html page.
Now I just build a graph using JavaFX, and it is very slow to build on a couple of thousand points and JavaFX does not seem to have tools for scaling.
public class FXLineChart {
private List<Long> dataset;
public FXLineChart(List<Long> dataset) {
this.dataset = dataset;
}
public void drawChart(Stage stage) {
stage.setTitle("Chart");
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
final LineChart<Number, Number> lineChart =
new LineChart<Number, Number>(xAxis, yAxis);
lineChart.setTitle("Check dataset");
XYChart.Series series = new XYChart.Series();
for (int i = 0; i < dataset.size(); i++) {
series.getData().add(new XYChart.Data(i, dataset.get(i)));
}
lineChart.getData().add(series);
Scene scene = new Scene(lineChart, 800, 600);
stage.setScene(scene);
stage.show();
}
}
JFreeChart is slightly better, but the chart slows down at several tens of thousands of points. I did not find information about real time in JFreeChart.
Maybe someone has a code for a similar task or idea? The challenge is that according to the assignment it should be its own library.

JavaFX BarChart doesn't display double or float values(only int,long and short)

I generated a BarChart in JavaFx using code(without SceneBuilder) and it fails to display double values on NumberAxis.I tried with int,long and short values and it works fine, but for some reason I cannot configure it to work with real numbers.
Here is my code:
private BarChart<String, Number> createBarChartMedii(Map<Student, Double> studentAndAverageGrades) {
CategoryAxis xAxis = new CategoryAxis();
xAxis.setLabel("Student");
NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("Average");
BarChart<String, Number> barChart = new BarChart<>(xAxis, yAxis);
barChart.setTitle("Grades Average");
XYChart.Series<String, Number> series = new XYChart.Series<>();
studentAndAverageGrades.forEach((student, average) -> {
series.getData().add(new XYChart.Data<>(student.getNume(),average));
});
barChart.getData().addAll(series);
return barChart;
}
This is the output:
click for image
If I modify the line which adds data into the series(inside the lambda function in foreach) with this one:
series.getData().add(new XYChart.Data<>(student.getNume(),average.intValue()));
it populates the BarChart

How to Remove Line from LineChart?

I have been trying to figure out how to remove a plotted line from a LineChart once it has already been made. I have tried just recreating the scene with the series I do want, but that didn't work. I have done some troubleshooting and found that the series is in fact changed and reduced to null, but the line persists even though the series is empty. Then once you change the series to new numbers it tries to connect the points to the old points that aren't supposed to exist. If someone could help me with this it would be very much appreciated.
public class GraphSection {
NumberAxis xAxis;
NumberAxis yAxis;
LineChart<Number, Number> lineChart;
Scene scene;
XYChart.Series series1, series2, series3, series4;
public GraphSection(){
xAxis = new NumberAxis();
yAxis = new NumberAxis();
lineChart = new LineChart<Number,Number>(xAxis,yAxis);
lineChart.setTitle("Test Graph");
lineChart.setCreateSymbols(false);
lineChart.setLegendVisible(false);
series1 = new XYChart.Series();
series2 = new XYChart.Series();
series3 = new XYChart.Series();
series4 = new XYChart.Series();
lineChart.getData().addAll(series1, series2, series3, series4);
scene = new Scene(lineChart,400,300);
}
public Scene getGraph(){
return this.scene;
}
public void addPoints(int n){
if(n == 1){
//Test Points
series1.getData().add(new XYChart.Data(1,50));
series1.getData().add(new XYChart.Data(2,60));
series1.getData().add(new XYChart.Data(3,70));
series1.getData().add(new XYChart.Data(4,80));
series1.getData().add(new XYChart.Data(5,90));
}
else if(n == 2){
//More Test Points
series1.getData().add(new XYChart.Data(1,70));
series1.getData().add(new XYChart.Data(2,80));
series1.getData().add(new XYChart.Data(3,90));
series1.getData().add(new XYChart.Data(4,100));
series1.getData().add(new XYChart.Data(5,110));
}
else if(n == 3){
}
else if(n == 4){
}
}
public void removePoints(int n){
Series s;
LineChart l = this.lineChart;
if(n == 1){
s = this.series1;
s.getData().clear();
l.removeSeriesFromDisplay(s); //This is the method I would guess I need but
//it is "not visible" and thus produces
// an error when I try to compile.
}
else if(n==2){
series2 = this.series2;
}
else if(n==3){
series3 = this.series3;
}
else if(n==4){
series4 = this.series4;
}
}
}
EDIT: So while .remove() will work to get it to remove the line from the graph it also throws the IllegalStateException. This, while ugly, doesn't cause the program to crash, however, then when I add data points back into the series it will not graph the new points. So a potential solution would be to dynamically add a series into the LineChart. If anyone knows how to do this, that might solve my problem.
EDIT: I have changed .addPoints(2); to add points to the series1 and I am including pictures below (Adding after the remove() just gives a blank chart).I am also including the code I am calling the class from.
//Bottom
JFXPanel bottom = new JFXPanel();
final GraphSection gs = new GraphSection();
bottom.setScene(gs.getGraph());
gs.addPoints(1);
//ActionListeners
testButton1.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
gs.removePoints(1);
System.out.println(gs.series1.getData()); //Used for debugging
}
});
testButton2.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
gs.addPoints(2);
System.out.println(gs.series1.getData());
}
});
Start with .addPoints(1);
This is what happens with just .getData().clear();
Yes, the problem was in fact trying to use a JFXPanel within a Swing JFrame. This meant that illegalStateException was popping up as a result. By altering the code to run on a JavaFX stage this allowed for remove() to work, and then you can easily dynamically add new series with lineChart.getData().add(Series);. You can incorporate swing components with SwingNodes, though my advice would be to recode your GUI into all JavaFX.

Updating JavaFX BarChart data causes memory leak

I've discovered what I believe is a memory leak in JavaFX (1.8u40 GA and 1.8u60 b10 EA) BarChart triggered by replacing all the data values in the series. This is exasperated by our application which does this several times a second.
Using jvisualvm shows an uncontrolled growth in the number of javafx.scene.layout.StackPane instances which eventually results in an OutOfMemoryError. Styled StackPane nodes are used internally by BarChart for the bars.
I've tried a different strategies to update the list. All exhibit the same issue.
// 1
series.getData().clear();
series.getData().addAll(list);
// 2
series.getData().setAll(list);
// 3
series.setData(list)
Interestingly the example in the Oracle BarChart tutorial updates values by adding all the bars/points first, and then mutating them using XYChart.Data.setYValue(). This could work, but is not convenient for us as the number of data points can vary dynamically.
Specific questions
Is there anyway to avoid this issue, other than a extra logic using the setYValue() approach above.
Have I stumbled across an actual memory leak in JavaFX? or just an artefact of my misuse of the API? Surely freeing internal nodes when data is updated is JavaFX responsibility
Example
public class ChartUpdate extends Application {
private int clock;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
CategoryAxis xAxis = new CategoryAxis();
NumberAxis yAxis = new NumberAxis(0, 100, 10);
yAxis.setAutoRanging(false);
BarChart<String, Number> graph = new BarChart<>(xAxis, yAxis);
graph.setAnimated(false);
Series<String, Number> series = new Series<>();
graph.getData().add(series);
stage.setScene(new Scene(graph));
stage.show();
Timeline timeLine = new Timeline();
timeLine.getKeyFrames().add(
new KeyFrame(Duration.millis(500),
(e) -> {
ObservableList<Data<String, Number>> list = FXCollections.observableArrayList();
for (int i = 0; i < 100; i++) {
list.add(new Data<>(String.valueOf(i), (clock + i) % 100));
}
series.setData(list);
clock++;
}));
timeLine.setCycleCount(Animation.INDEFINITE);
timeLine.play();
}
}
Update (16th April 2015) Raised https://bugs.openjdk.java.net/browse/JDK-8094805 and posted to OpenJFX mailing list
In summary this was a genuine issue accepted by JavaFX team and fixed by JDK-8094805 in 8u60
See https://bugs.openjdk.java.net/browse/JDK-8094805 for more detail.

Downcast object to unknown class

I would like to cut down in code by using the same variable name for two possible objects (JavaFX Chart objects in this case). The reason for this is because after this for loop some processing to the chart happens that is identically.
I tried downcasting the chart but it gives me an "unknown class" error.
Chart chart;
if (chartClass.equals(LineChart.class))
chart = new LineChart<Number, Number>(xAxis, yAxis);
else
chart = new AreaChart<Number, Number>(xAxis, yAxis);
for (int i = 0; i < variablesToPlot.length; i++) {
series[i] = new LineChart.Series<Number, Number>();
// this chart must be LineChart or AreaChart (but not general chart) for getData() to work
((chartClass) chart).getData().add(series[i]);
}
// here some chart processing that is equal for both charts
What I am looking for kind of sounds like dynamic typing (which Java is not capable of as far as I know), but is there a way to solve this problem other than writing the code twice?
Change the declaration of Chart chart to XYChart chart.
The common superclass of LineChart and AreaChart is XYChart.
You will then be able to cast it to XYChart and use the getData method on it.
It will result in:
XYChart chart;
if (chartClass.equals(LineChart.class))
chart = new LineChart<Number, Number>(xAxis, yAxis);
else
chart = new AreaChart<Number, Number>(xAxis, yAxis);
for (int i = 0; i < variablesToPlot.length; i++) {
series[i] = new LineChart.Series<Number, Number>();
// this chart must be LineChart or AreaChart (but not general chart) for getData() to work
chart.getData().add(series[i]);
}

Categories

Resources