I've create a small app which uses a live animated line chart to display the number of files in a certain folder every 2 seconds. All works well but the chart/yAxis keeps displaying double values after every update. The folder can contain between 0 and 30.000 files ...
#FXML
public LineChart<String, Integer> myChart;
#FXML
public CategoryAxis xAxis;
#FXML
public NumberAxis yAxis;
...
yAxis.setMinorTickVisible(false);
yAxis.setTickMarkVisible(false);
yAxis.setTickUnit(1);
How do I make sure the y-Axis only contains/uses integer values ... ?
The x,5 values/rows should never be used.
The tick unit is automatically set if autoranging is enabled (which it is by default). See the documentation.
So one way is to turn off autoranging, and set the range manually. This may need you to update the range when the data change, but the following demonstrates the idea:
public class ChartTest extends Application {
#Override
public void start(Stage stage) throws IOException {
CategoryAxis xAxis = new CategoryAxis();
NumberAxis yAxis = new NumberAxis();
yAxis.setTickUnit(1);
yAxis.setMinorTickVisible(false);
yAxis.setTickMarkVisible(false);
yAxis.setAutoRanging(false);
Random rng = new Random();
XYChart.Series<String, Integer> data = new XYChart.Series<>();
int max = Integer.MIN_VALUE;
for (String x : "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("")) {
int y = rng.nextInt(10);
if (y > max) max = y ;
data.getData().add(new XYChart.Data<>(x, y));
}
yAxis.setUpperBound(max + 1);
data.setName("Data");
LineChart<String, Integer> chart = new LineChart(xAxis, yAxis);
chart.getData().add(data);
Scene scene = new Scene(new BorderPane(chart), 600, 800);
stage.setTitle("Chart Test");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
If you want to retain autoranging, you can use a tick label formatter that returns an empty string for non-integer values:
public class ChartTest extends Application {
#Override
public void start(Stage stage) throws IOException {
CategoryAxis xAxis = new CategoryAxis();
NumberAxis yAxis = new NumberAxis();
yAxis.setMinorTickVisible(false);
yAxis.setTickMarkVisible(false);
yAxis.setTickLabelFormatter(new StringConverter<Number>() {
#Override
public String toString(Number number) {
double d = number.doubleValue();
if (d == Math.rint(d)) {
return Integer.toString(number.intValue());
}
return "" ;
}
// Not used
#Override
public Number fromString(String s) {
return null;
}
});
Random rng = new Random();
XYChart.Series<String, Integer> data = new XYChart.Series<>();
for (String x : "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("")) {
int y = rng.nextInt(10);
data.getData().add(new XYChart.Data<>(x, y));
}
data.setName("Data");
LineChart<String, Integer> chart = new LineChart(xAxis, yAxis);
chart.getData().add(data);
Scene scene = new Scene(new BorderPane(chart), 600, 800);
stage.setTitle("Chart Test");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
I want to stack two different XYCharts as done here.
In this example however, the bounds of the axes are the same and data is static.
In my case, I have dynamic data to plot: new values are added to the data series as they become available. So the y axis (for instance) gets updated when new data arrives.
Moreover, the two data sets are not exactly in the same range.
Here is a first attempt:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
/**
* Demonstrates how to draw layers of XYCharts.
* https://forums.oracle.com/forums/thread.jspa?threadID=2435995 "Using StackPane to layer more different type charts"
*/
public class LayeredXyChartsSample extends Application {
private XYChart.Series<String, Number> barSeries;
private XYChart.Series<String, Number> lineSeries;
public static void main(String[] args) { launch(args); }
#Override public void start(Stage stage) {
initSeries();
// Close the application when the window is closed
stage.setOnCloseRequest(t -> {
Platform.exit();
System.exit(0);
});
stage.setScene(
new Scene(
layerCharts(
createBarChart(),
createLineChart()
)
)
);
stage.show();
updateSeries();
}
#SuppressWarnings("unchecked")
private void initSeries() {
barSeries = new XYChart.Series(
FXCollections.observableArrayList(
new XYChart.Data("Jan", 2),
new XYChart.Data("Feb", 10),
new XYChart.Data("Mar", 8),
new XYChart.Data("Apr", 4),
new XYChart.Data("May", 7),
new XYChart.Data("Jun", 5),
new XYChart.Data("Jul", 4),
new XYChart.Data("Aug", 8),
new XYChart.Data("Sep", 16.5),
new XYChart.Data("Oct", 13.9),
new XYChart.Data("Nov", 17),
new XYChart.Data("Dec", 10)
)
);
lineSeries = new XYChart.Series(
FXCollections.observableArrayList(
new XYChart.Data("Jan", 1),
new XYChart.Data("Feb", 2),
new XYChart.Data("Mar", 1.5),
new XYChart.Data("Apr", 3),
new XYChart.Data("May", 2.5),
new XYChart.Data("Jun", 5),
new XYChart.Data("Jul", 4),
new XYChart.Data("Aug", 8),
new XYChart.Data("Sep", 6.5),
new XYChart.Data("Oct", 13),
new XYChart.Data("Nov", 10),
new XYChart.Data("Dec", 20)
)
);
}
private void updateSeries() {
new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
final int index = i;
final double value = 20 * Math.random();
Platform.runLater(() -> {
barSeries.getData().remove(0);
barSeries.getData().add(new XYChart.Data<>(String.valueOf(index), value));
lineSeries.getData().remove(0);
lineSeries.getData().add(new XYChart.Data<>(String.valueOf(index), value * 2));
});
}
Platform.exit();
System.exit(0);
}).start();
}
private NumberAxis createYaxis() {
final NumberAxis axis = new NumberAxis();
axis.setAutoRanging(true);
axis.setPrefWidth(35);
axis.setMinorTickCount(10);
axis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(axis) {
#Override public String toString(Number object) {
return String.format("%7.2f", object.floatValue());
}
});
return axis;
}
#SuppressWarnings("unchecked")
private BarChart<String, Number> createBarChart() {
final BarChart<String, Number> chart = new BarChart<>(new CategoryAxis(), createYaxis());
setDefaultChartProperties(chart);
chart.getData().addAll(barSeries);
return chart;
}
#SuppressWarnings("unchecked")
private LineChart<String, Number> createLineChart() {
final LineChart<String, Number> chart = new LineChart<>(new CategoryAxis(), createYaxis());
setDefaultChartProperties(chart);
chart.setCreateSymbols(false);
chart.getData().addAll(lineSeries);
return chart;
}
private void setDefaultChartProperties(final XYChart<String, Number> chart) {
chart.setLegendVisible(false);
chart.setAnimated(false);
}
#SafeVarargs
private final StackPane layerCharts(final XYChart<String, Number>... charts) {
for (int i = 1; i < charts.length; i++) {
configureOverlayChart(charts[i]);
}
StackPane stackpane = new StackPane();
stackpane.getChildren().addAll(charts);
return stackpane;
}
private void configureOverlayChart(final XYChart<String, Number> chart) {
chart.setAlternativeRowFillVisible(false);
chart.setAlternativeColumnFillVisible(false);
chart.setHorizontalGridLinesVisible(false);
chart.setVerticalGridLinesVisible(false);
chart.getXAxis().setVisible(false);
chart.getYAxis().setVisible(false);
chart.getStylesheets().addAll(getClass().getResource("/overlay-chart.css").toExternalForm());
}
}
The result looks like this:
The Y axis is not looking good: there are two axes and as they don't have the same bounds anymore, they don't overlay properly.
Next attempt consists in creating a single axis and assigning it to both charts. A few changes:
a class variable is created: private NumberAxis yAxis;
the createYaxis method is modified as follows (it's a void method and sets the variable):
private void createYaxis() {
yAxis = new NumberAxis();
yAxis.setAutoRanging(true);
yAxis.setPrefWidth(35);
yAxis.setMinorTickCount(10);
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) {
#Override public String toString(Number object) {
return String.format("%7.2f", object.floatValue());
}
});
}
the createYaxis method is called at the end of the initSeries method.
the charts are created with the same yAxis, e.g.:
BarChart chart = new BarChart<>(new CategoryAxis(), yAxis);
Now, the yAxis looks good, but the graphs are not displayed on the same scale as soon as a new value for the line chart exceeds the axis bounds (note that the new line series values are 2x those of the bar series new values; the chart does not take it into account).
So my next move would be to create a BoundAxis class that takes a reference axis and update it's bound when the reference axis bounds are modified. Something like this:
public class BoundAxis<T> extends Axis<T> {
private final Axis<T> originalAxis;
public BoundAxis(Axis<T> originalAxis) {
this.originalAxis = originalAxis;
}
#Override
protected Object autoRange(double length) {
return originalAxis.autoRange(length); // Compilation error
}
#Override
protected void setRange(Object range, boolean animate) {
originalAxis.setRange(range, animate); // Compilation error
}
#Override
protected Object getRange() {
return originalAxis.getRange(); // Compilation error
}
#Override
public double getZeroPosition() {
return originalAxis.getZeroPosition();
}
#Override
public double getDisplayPosition(T value) {
return originalAxis.getDisplayPosition(value);
}
#Override
public T getValueForDisplay(double displayPosition) {
return originalAxis.getValueForDisplay(displayPosition);
}
#Override
public boolean isValueOnAxis(T value) {
return originalAxis.isValueOnAxis(value);
}
#Override
public double toNumericValue(T value) {
return originalAxis.toNumericValue(value);
}
#Override
public T toRealValue(double value) {
return originalAxis.toRealValue(value);
}
#Override
protected List<T> calculateTickValues(double length, Object range) {
return originalAxis.calculateTickValues(length, range); // Compilation error
}
#Override
protected String getTickMarkLabel(T value) {
return originalAxis.getTickMarkLabel(value); // Compilation error
}
}
But this does not compile, because there are protected methods I cannot call.
One last thing, I want something rather generic: the BoundAxis must be extend Axis so I can ue it with not only the NumberAxis.
Edit: This question is related to that one.
This is the solution I found. Basically, I set the upper and lower bounds manually.
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
/**
* Demonstrates how to draw layers of XYCharts.
* https://forums.oracle.com/forums/thread.jspa?threadID=2435995 "Using StackPane to layer more different type charts"
*/
public class LayeredXyChartsSample extends Application {
private XYChart.Series<String, Number> barSeries;
private XYChart.Series<String, Number> lineSeries;
private NumberAxis yAxis;
private double lowerBound = Double.MAX_VALUE;
private double upperBound = Double.MIN_VALUE;
public static void main(String[] args) { launch(args); }
#Override public void start(Stage stage) {
initSeries();
// Close the application when the window is closed
stage.setOnCloseRequest(t -> {
Platform.exit();
System.exit(0);
});
stage.setScene(
new Scene(
layerCharts(
createBarChart(),
createLineChart()
)
)
);
stage.show();
updateSeries();
}
#SuppressWarnings("unchecked")
private void initSeries() {
barSeries = new XYChart.Series(
FXCollections.observableArrayList(
new XYChart.Data("Jan", 2),
new XYChart.Data("Feb", 10),
new XYChart.Data("Mar", 8),
new XYChart.Data("Apr", 4),
new XYChart.Data("May", 7),
new XYChart.Data("Jun", 5),
new XYChart.Data("Jul", 4),
new XYChart.Data("Aug", 8),
new XYChart.Data("Sep", 16.5),
new XYChart.Data("Oct", 13.9),
new XYChart.Data("Nov", 17),
new XYChart.Data("Dec", 10)
)
);
lineSeries = new XYChart.Series(
FXCollections.observableArrayList(
new XYChart.Data("Jan", 1),
new XYChart.Data("Feb", 2),
new XYChart.Data("Mar", 1.5),
new XYChart.Data("Apr", 3),
new XYChart.Data("May", 2.5),
new XYChart.Data("Jun", 5),
new XYChart.Data("Jul", 4),
new XYChart.Data("Aug", 8),
new XYChart.Data("Sep", 6.5),
new XYChart.Data("Oct", 13),
new XYChart.Data("Nov", 10),
new XYChart.Data("Dec", 20)
)
);
createYaxis();
}
private void updateSeries() {
new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
final int index = i;
final double value = 20 * Math.random();
Platform.runLater(() -> {
barSeries.getData().remove(0);
barSeries.getData().add(new XYChart.Data<>(String.valueOf(index), value));
lineSeries.getData().remove(0);
lineSeries.getData().add(new XYChart.Data<>(String.valueOf(index), value * 2));
lowerBound = Double.MAX_VALUE;
upperBound = Double.MIN_VALUE;
for (int j = 0; j < barSeries.getData().size(); j++) {
lowerBound = Math.min(lowerBound, barSeries.getData().get(j).getYValue().doubleValue());
lowerBound = Math.min(lowerBound, lineSeries.getData().get(j).getYValue().doubleValue());
upperBound = Math.max(upperBound, barSeries.getData().get(j).getYValue().doubleValue());
upperBound = Math.max(upperBound, lineSeries.getData().get(j).getYValue().doubleValue());
}
yAxis.setLowerBound(lowerBound);
yAxis.setUpperBound(upperBound);
});
}
Platform.exit();
System.exit(0);
}).start();
}
private void createYaxis() {
yAxis = new NumberAxis();
yAxis.setAutoRanging(false);
yAxis.setPrefWidth(35);
yAxis.setMinorTickCount(10);
yAxis.setLowerBound(0);
yAxis.setUpperBound(20);
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) {
#Override public String toString(Number object) {
return String.format("%7.2f", object.floatValue());
}
});
}
#SuppressWarnings("unchecked")
private BarChart<String, Number> createBarChart() {
final BarChart<String, Number> chart = new BarChart<>(new CategoryAxis(), yAxis);
setDefaultChartProperties(chart);
chart.getData().addAll(barSeries);
return chart;
}
#SuppressWarnings("unchecked")
private LineChart<String, Number> createLineChart() {
final LineChart<String, Number> chart = new LineChart<>(new CategoryAxis(), yAxis);
setDefaultChartProperties(chart);
chart.setCreateSymbols(false);
chart.getData().addAll(lineSeries);
return chart;
}
private void setDefaultChartProperties(final XYChart<String, Number> chart) {
chart.setLegendVisible(false);
chart.setAnimated(false);
}
#SafeVarargs
private final StackPane layerCharts(final XYChart<String, Number>... charts) {
for (int i = 1; i < charts.length; i++) {
configureOverlayChart(charts[i]);
}
StackPane stackpane = new StackPane();
stackpane.getChildren().addAll(charts);
return stackpane;
}
private void configureOverlayChart(final XYChart<String, Number> chart) {
chart.setAlternativeRowFillVisible(false);
chart.setAlternativeColumnFillVisible(false);
chart.setHorizontalGridLinesVisible(false);
chart.setVerticalGridLinesVisible(false);
chart.getXAxis().setVisible(false);
chart.getYAxis().setVisible(false);
chart.getStylesheets().addAll(getClass().getResource("/overlay-chart.css").toExternalForm());
}
}
Here is the result:
Just a little note that one doesn't need to write source code to stack charts over eachother: You can also set the attributes without events and threading within the FXML file as shown below:
<BarChart fx:id="bc_stock_c" alternativeRowFillVisible="false" animated="false" horizontalZeroLineVisible="false" legendVisible="false" prefHeight="200.0" prefWidth="200.0" title="Produkt C" verticalGridLinesVisible="false" verticalZeroLineVisible="false" GridPane.rowIndex="1">
<xAxis>
<CategoryAxis side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis animated="false" autoRanging="false" lowerBound="0.0" minorTickCount="100" prefHeight="135.0" prefWidth="35.0" side="LEFT" tickUnit="100.0" upperBound="1000.0" />
</yAxis>
</BarChart>
<LineChart fx:id="lc_demand_c" alternativeRowFillVisible="false" animated="false" createSymbols="false" horizontalGridLinesVisible="false" horizontalZeroLineVisible="false" legendVisible="false" prefHeight="200.0" prefWidth="200.0" stylesheets="#style.css" title=" " verticalGridLinesVisible="false" verticalZeroLineVisible="false" GridPane.rowIndex="1">
<xAxis>
<CategoryAxis side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis animated="false" autoRanging="false" lowerBound="0.0" minorTickCount="100" prefHeight="135.0" prefWidth="35.0" side="LEFT" tickUnit="100.0" upperBound="1000.0" />
</yAxis>
</LineChart>
Just set pref_width, pref_height, autoRanging, minorTickCount, tickUnit, upperBound and lowerBound for each Y-axis and it should work. You also should add the following css style to make the chart area transparent for each line chart:
.chart-plot-background {
-fx-background-color: transparent;
}
hey guys i am an it student and i just started javafx and i need help.
i have created a class that allows me to generate a Menubar and a chart they both work in an independent way, but my probleme is how to add the Menu bar at every chart bar class, so i can navigate easly between the charts i have really been looking for it, and it most be done tonight
and this is the source code
package mto.cr.GUI;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class MenuMain extends Application {
Stage window;
BorderPane layout;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
window = primaryStage;
window.setTitle("Crowd Rise");
//statistique menu
Menu statisqueMenu = new Menu("Statistique ");
//Attribution Experience declaration setonaction et add dans le menu
MenuItem attributExperience = new MenuItem("Attribution Experience");
attributExperience.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Statistique1 st1=new Statistique1() ;
st1.start(window);
}
});
statisqueMenu.getItems().add(attributExperience);
//financement projet
MenuItem FinancementProjet = new MenuItem("Financement Projet");
FinancementProjet.setOnAction( new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
}
}
);
statisqueMenu.getItems().add(FinancementProjet);
//projet
MenuItem projet = new MenuItem("projet");
projet.setOnAction( new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
}
});
statisqueMenu.getItems().add(projet);
//separeteur avec un trait
statisqueMenu.getItems().add(new SeparatorMenuItem());
//user
MenuItem user = new MenuItem("User");
user.setOnAction( new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
}
});
statisqueMenu.getItems().add(user);
statisqueMenu.getItems().add(new SeparatorMenuItem());
statisqueMenu.getItems().add(new MenuItem("Exit..."));
//recherche menu
Menu rechercheMenu = new Menu("Recherche");
//user
MenuItem userR = new MenuItem("User");
userR.setOnAction( new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
}
});
rechercheMenu.getItems().add(userR);
//projet
MenuItem projetR = new MenuItem("Projet");
projetR.setOnAction( new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
}
});
rechercheMenu.getItems().add(projetR);
//probleme
MenuItem problemeR = new MenuItem("Probleme recherche");
problemeR.setOnAction( new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
}
});
rechercheMenu.getItems().add(problemeR);
//Main menu bar
MenuBar menuBar = new MenuBar();
menuBar.getMenus().addAll(statisqueMenu,rechercheMenu);
layout = new BorderPane();
layout.setTop(menuBar);
Scene scene = new Scene(layout, 400, 300);
window.setScene(scene);
window.show();
}
}
And i also have another class that allows me to create a chart and this is the source code
package mto.cr.GUI;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
public class Statistique1 extends Application {
#Override public void start(Stage stage) {
MenuMain menumain= new MenuMain();
menumain.start(stage);
stage.setTitle("Line Chart Sample");
//defining the axes
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("Number of Month");
//creating the chart
final LineChart<Number,Number> lineChart =
new LineChart<Number,Number>(xAxis,yAxis);
lineChart.setTitle("Stock Monitoring, 2010");
//defining a series
XYChart.Series series = new XYChart.Series();
series.setName("My portfolio");
//populating the series with data
series.getData().add(new XYChart.Data(1, 23));
series.getData().add(new XYChart.Data(2, 14));
series.getData().add(new XYChart.Data(3, 15));
series.getData().add(new XYChart.Data(4, 24));
series.getData().add(new XYChart.Data(5, 34));
series.getData().add(new XYChart.Data(6, 36));
series.getData().add(new XYChart.Data(7, 22));
series.getData().add(new XYChart.Data(8, 45));
series.getData().add(new XYChart.Data(9, 43));
series.getData().add(new XYChart.Data(10, 17));
series.getData().add(new XYChart.Data(11, 29));
series.getData().add(new XYChart.Data(12, 25));
Scene scene = new Scene(lineChart,800,600);
lineChart.getData().add(series);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You cannot launch more than one Application. This is why you should seperate any code you want to use independent from the Application to classes not extending Application. Also you have repetitive code, which makes it harder to maintain the code (see DRY).
Since your intention seems to be to replace the center node of the BoderPane, I recommend creating factory classes for the content to be displayed in the center instead of Application subclasses and creating a function for creating the menus items:
MenuItem createMenuItem(String text, final Supplier<Node> factory) {
MenuItem item = new MenuItem(text);
item.setOnAction(evt -> {
setContent(factory);
});
}
void setContent(Supplier<Node> factory) {
Node content = factory.get();
layout.setCenter(content);
}
#Override
public void start(Stage primaryStage) {
window = primaryStage;
window.setTitle("Crowd Rise");
//statistique menu
Menu statisqueMenu = new Menu("Statistique", null,
createMenuItem("Attribution Experience", new Statistique1()),
createMenuItem("Financement Projet", new Statistique2()),
...);
...
public class Statistique1 implements Supplier<Node> {
#Override
public Node get() {
//defining the axes
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("Number of Month");
//creating the chart
final LineChart<Number,Number> lineChart =
new LineChart<Number,Number>(xAxis,yAxis);
lineChart.setTitle("Stock Monitoring, 2010");
//defining a series
XYChart.Series series = new XYChart.Series();
series.setName("My portfolio");
//populating the series with data
series.getData().add(new XYChart.Data(1, 23));
series.getData().add(new XYChart.Data(2, 14));
series.getData().add(new XYChart.Data(3, 15));
series.getData().add(new XYChart.Data(4, 24));
series.getData().add(new XYChart.Data(5, 34));
series.getData().add(new XYChart.Data(6, 36));
series.getData().add(new XYChart.Data(7, 22));
series.getData().add(new XYChart.Data(8, 45));
series.getData().add(new XYChart.Data(9, 43));
series.getData().add(new XYChart.Data(10, 17));
series.getData().add(new XYChart.Data(11, 29));
series.getData().add(new XYChart.Data(12, 25));
lineChart.getData().add(series);
return lineChart;
}
}
I am creating a bar chart as you can see in photo. The problem is that the name of bars on XAxis are some how not match to the bar.
The tick label rotation is set to -45 in fxml. How can I shift them exactly under the bar?
public class MostComputerizedController {
#FXML
private BarChart<String, Number> barChart;
#FXML
private CategoryAxis orgNameAxis;
#FXML
private NumberAxis yAxis;
#FXML
private Label itsfField;
private ObservableList<String> orgNames = FXCollections
.observableArrayList();
private DataConstructor dc = new DataConstructor();
private int numberOfOrganizations;
private List<Double> sumOfOrgsITSF = new ArrayList<Double>();
/**
* sets the name of x axis, with the name of organizations
*/
#FXML
private void initialize() {
dc.findSortedAssignedOrg();
dc.orgFuncFuncType();
orgNames.addAll(dc.getOrgFuncFunctype().keySet());
orgNameAxis.setCategories(orgNames);
orgNameAxis.setLabel("Name of Organizations");
orgNameAxis.tickLabelFontProperty().set(Font.font(9));
yAxis.setLabel("Saturation");
numberOfOrganizations = dc.getSortedAssignedOrg().size();
}
/**
* sets organization and their saturation
*/
public void setOrgData() {
XYChart.Series<String, Number> seriesGreen = new XYChart.Series<>();
XYChart.Series<String, Number> seriesYellow = new XYChart.Series<>();
seriesGreen.setName("IT Saturation Satisfying");
seriesYellow.setName("IT Saturation not Satisfying");
for (Entry<String, List<Double>> entry : dc.getOrgFuncFunctype()
.entrySet()) {
sumOfOrgsITSF.add(entry.getValue().get(0));
if (entry.getValue().get(0) > 50) {
seriesGreen.getData().add(
new XYChart.Data<String, Number>(entry.getKey(), entry
.getValue().get(0)));
} else if ((entry.getValue().get(0) <= 50)) {
seriesYellow.getData().add(
new XYChart.Data<String, Number>(entry.getKey(), entry
.getValue().get(0)));
}
}
double value = sumOfOrgsITSF.stream().mapToDouble(Double::doubleValue)
.sum()
/ numberOfOrganizations;
itsfField.setText(String.format("%.0f", value) + "%");
barChart.setBarGap(1);
barChart.setCategoryGap(10);
barChart.getData().addAll(seriesGreen, seriesYellow);
}
Put all your data in a single series.
series1.getData().add(new Data<Number, String>(Integer1),(Title1)));
series1.getData().add(new Data<Number, String>(Integer2),(Title2)));
series1.getData().add(new Data<Number, String>(Integer3),(Title3)));
Results:
I have created a gui application that reads some data and stores them in a file. I want to call from java the JavaFX_Charts application to create and show me on screen the data from file.
The file contains two columns with numbers one for each axis's. I read the file and create two
List<Integer> l1 = new ArrayList<Integer>();
List<Integer> l2 = new ArrayList<Integer>();
How can I call JavaFX_Charts and update it with my values on the 2 lists?
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
public class JavaFX_Charts extends Application {
private void init(Stage primaryStage) {
Group root = new Group();
primaryStage.setScene(new Scene(root));
root.getChildren().add(createChart());
}
protected LineChart<Number, Number> createChart() {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
final LineChart<Number, Number> lc = new LineChart<Number, Number>(
xAxis, yAxis);
// setup chart
lc.setTitle("Basic LineChart");
xAxis.setLabel("X Axis");
yAxis.setLabel("Y Axis");
// add starting data
XYChart.Series<Number, Number> series = new XYChart.Series<Number, Number>();
series.setName("Data Series 1");
series.getData().add(new XYChart.Data<Number, Number>(20d, 50d));
series.getData().add(new XYChart.Data<Number, Number>(40d, 80d));
series.getData().add(new XYChart.Data<Number, Number>(50d, 90d));
series.getData().add(new XYChart.Data<Number, Number>(70d, 30d));
series.getData().add(new XYChart.Data<Number, Number>(170d, 122d));
lc.getData().add(series);
return lc;
}
#Override
public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I have created two list and filled it up with Random data, then parsing the list to fill the data into the chart
An example to show populating data from list
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
public class JavaFX_Charts extends Application {
private void init(Stage primaryStage) {
Group root = new Group();
primaryStage.setScene(new Scene(root));
root.getChildren().add(createChart());
}
protected LineChart<Number, Number> createChart() {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
final LineChart<Number, Number> lc = new LineChart<Number, Number>(
xAxis, yAxis);
// setup chart
lc.setTitle("Basic LineChart");
xAxis.setLabel("X Axis");
yAxis.setLabel("Y Axis");
// add starting data
List<Integer> l1 = new ArrayList<Integer>();
List<Integer> l2 = new ArrayList<Integer>();
//Populating the List with Random data
for(int i=0; i<10; i++){
l1.add((int) (Math.random()*10*i));
l2.add((int) (Math.random()*10*i));
}
XYChart.Series<Number, Number> series = new XYChart.Series<Number, Number>();
series.setName("Data Series 1");
//Fetching data from the list
/**
* Using the size of l1, taking into
* consideration that l1 and l2 are of same size
*/
for(int i=0; i<l1.size(); i++){
series.getData().add(new XYChart.Data<Number, Number>(l1.get(i), l2.get(i)));
}
lc.getData().add(series);
return lc;
}
#Override
public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Update as per comments
To run the same thing in a Swing application, you just need to use a JFXPanel and use the setScene() to set a JavaFX scene into it
Example with Swing
import java.util.ArrayList;
import java.util.List;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
public class JFX_Charts_Swing {
private static void initAndShowGUI() {
// This method is invoked on Swing thread
JFrame frame = new JFrame("Chart FX");
final JFXPanel fxPanel = new JFXPanel();
frame.add(fxPanel);
frame.setBounds(400, 400, 600, 600);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
// To run on the Javafx thread
Platform.runLater(new Runnable() {
#Override
public void run() {
initFX(fxPanel);
}
});
}
private static void initFX(JFXPanel fxPanel) {
Group root = new Group();
Scene scene = new Scene(root);
root.getChildren().add(createChart());
fxPanel.setScene(scene);
}
protected static LineChart<Number, Number> createChart() {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
final LineChart<Number, Number> lc = new LineChart<Number, Number>(
xAxis, yAxis);
// setup chart
lc.setTitle("Basic LineChart");
xAxis.setLabel("X Axis");
yAxis.setLabel("Y Axis");
// add starting data
List<Integer> l1 = new ArrayList<Integer>();
List<Integer> l2 = new ArrayList<Integer>();
// Populating the List with Random data
for (int i = 0; i < 10; i++) {
l1.add((int) (Math.random() * 10 * i));
l2.add((int) (Math.random() * 10 * i));
}
XYChart.Series<Number, Number> series = new XYChart.Series<Number, Number>();
series.setName("Data Series 1");
// Fetching data from the list
/**
* Using the size of l1, taking into consideration that l1 and l2 are of
* same size
*/
for (int i = 0; i < l1.size(); i++) {
series.getData().add(
new XYChart.Data<Number, Number>(l1.get(i), l2.get(i)));
}
lc.getData().add(series);
return lc;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
initAndShowGUI();
}
});
}
}