position xAxis values under the bars in JavaFx - javafx

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:

Related

JavaFX NumberAxis displays doubles

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();
}
}

Keeping axes synchronized when stacking two XYCharts

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;
}

javafx adding all subtotal of all column in tableview

I have Tableview here that allows me to multiply the price and the quantity and put that into the subtotal column my question is i want to get the sum of all the subtotals for all the items in the table , thanks
private TableView<Product> table = new TableView<Product>();
private final ObservableList<Product> data =
FXCollections.observableArrayList(
new Product("Notebook", 10, 12),
new Product("Eraser", 20, 12),
new Product("Pencil", 30, 12),
new Product("Pen", 40, 12),
new Product("Glue", 50, 12));
final HBox hb = new HBox();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setTitle("Book Store Sample");
stage.setWidth(650);
stage.setHeight(550);
final Label label = new Label("Book Store");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
TableColumn name = new TableColumn("Name");
name.setMinWidth(100);
name.setCellValueFactory(
new PropertyValueFactory<Product, String>("name"));
name.setCellFactory(TextFieldTableCell.forTableColumn());
name.setOnEditCommit(
new EventHandler<CellEditEvent<Product, String>>() {
#Override
public void handle(CellEditEvent<Product, String> t) {
((Product) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setName(t.getNewValue());
}
}
);
TableColumn priceCol = new TableColumn("Price");
priceCol.setMinWidth(100);
priceCol.setCellValueFactory(
new PropertyValueFactory<Product, String>("price"));
priceCol.setCellFactory(TextFieldTableCell.<Product, Number>forTableColumn(new NumberStringConverter()));
priceCol.setOnEditCommit(
new EventHandler<CellEditEvent<Product, Number>>() {
#Override
public void handle(CellEditEvent<Product, Number> t) {
((Product) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setPrice(t.getNewValue().intValue());
}
}
);
TableColumn quantityCol = new TableColumn("Quantity");
quantityCol.setMinWidth(200);
quantityCol.setCellValueFactory(
new PropertyValueFactory<Product, Number>("quantity"));
quantityCol.setCellFactory(TextFieldTableCell.<Product, Number>forTableColumn(new NumberStringConverter()));
quantityCol.setOnEditCommit(
new EventHandler<CellEditEvent<Product, Number>>() {
#Override
public void handle(CellEditEvent<Product, Number> t) {
((Product) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setQuantity(t.getNewValue().intValue());
}
}
);
TableColumn subTotalCol = new TableColumn("Sub Total");
subTotalCol.setMinWidth(200);
subTotalCol.setCellValueFactory(
new PropertyValueFactory<Product, String>("subTotal"));
table.setItems(data);
table.getColumns().addAll(name, priceCol, quantityCol, subTotalCol);
final TextField addName = new TextField();
addName.setPromptText("Name");
addName.setMaxWidth(name.getPrefWidth());
final TextField addPrice = new TextField();
addPrice.setMaxWidth(priceCol.getPrefWidth());
addPrice.setPromptText("Price");
final TextField addQuantity = new TextField();
addQuantity.setMaxWidth(quantityCol.getPrefWidth());
addQuantity.setPromptText("Quantity");
final Button addButton = new Button("Add");
addButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
data.add(new Product(
name.getText(),
Integer.parseInt(addPrice.getText()),
Integer.parseInt(addQuantity.getText())));
addName.clear();
addPrice.clear();
addQuantity.clear();
}
});
hb.getChildren().addAll(addName, addPrice, addQuantity, addButton);
hb.setSpacing(3);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table, hb);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
public static class Product {
private final SimpleStringProperty name;
private final SimpleIntegerProperty price;
private final SimpleIntegerProperty quantity;
private final SimpleIntegerProperty subTotal;
private Product(String name, int price, int quantity) {
this.name = new SimpleStringProperty(name);
this.price = new SimpleIntegerProperty(price);
this.quantity = new SimpleIntegerProperty(quantity);
this.subTotal = new SimpleIntegerProperty();
NumberBinding multiplication = Bindings.multiply(this.priceProperty(), this.quantityProperty());
this.subTotalProperty().bind(multiplication);
}
public String getName() {
return name.get();
}
public SimpleStringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public int getPrice() {
return price.get();
}
public SimpleIntegerProperty priceProperty() {
return price;
}
public void setPrice(int price) {
this.price.set(price);
}
public int getQuantity() {
return quantity.get();
}
public SimpleIntegerProperty quantityProperty() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity.set(quantity);
}
public int getSubTotal() {
return subTotal.get();
}
public SimpleIntegerProperty subTotalProperty() {
return subTotal;
}
public void setSubTotal(int subTotal) {
this.subTotal.set(subTotal);
}
}
}
You can create a binding that tracks the total of all the subtotal values:
IntegerBinding total = Bindings.createIntegerBinding(() ->
table.getItems().stream().collect(Collectors.summingInt(Product::getSubTotal)),
table.getItems());
The first parameter to this is a function that computes the total of the results of calling product.getSubTotal() on each element in table.getItems(). The second argument ensures that this binding is marked as invalid (so can be recomputed) any time table.getItems() is invalidated.
Using the default mechanism of constructing a list, as you do with ObservableList<Product> data = FXCollections.observableArrayList(...), the list will only be invalidated when items are added, removed, or replaced, but not if the subTotal of an existing item changes. To make this happen, you need to modify that line to use a extractor:
private final ObservableList<Product> data = FXCollections.observableList(Arrays.asList(
new Product("Notebook", 10, 12),
new Product("Eraser", 20, 12),
new Product("Pencil", 30, 12),
new Product("Pen", 40, 12),
new Product("Glue", 50, 12)),
product -> new Observable[] {product.subTotalProperty()});
Now you can do something like
Label totalLabel = new Label();
totalLabel.textProperty().bind(Bindings.format("Total: %d", total));
Update
Here is what the IntegerBinding code looks like without a lambda expression, though it is far less clear and I don't recommend doing this:
IntegerBinding total = Bindings.createIntegerBinding(new Callable<Integer>() {
#Override
public Integer call() {
return table.getItems().stream().collect(Collectors.summingInt(
new ToIntFunction<Product>() {
#Override
public int applyAsInt(Product product) {
return product.getSubTotal();
}
}));
}
}, table.getItems());

Center bars in a bar chart

I modified the bar chart example of the Oracle website and reduced the bars to 1 per series. Unfortunately the bars aren't centered above the tick marks.
Does anyone know how to get the bars centered horizontally above the tick marks?
Here's the code:
public class BarChartDemo extends Application {
final static String austria = "Austria";
final static String brazil = "Brazil";
final static String france = "France";
#Override
public void start(Stage stage) {
stage.setTitle("Bar Chart Sample");
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
final BarChart<String, Number> bc = new BarChart<String, Number>(xAxis, yAxis);
bc.setTitle("Country Summary");
xAxis.setLabel("Country");
yAxis.setLabel("Value");
XYChart.Series series1 = new XYChart.Series();
series1.setName("2003");
series1.getData().add(new XYChart.Data(austria, 25601.34));
XYChart.Series series2 = new XYChart.Series();
series2.setName("2004");
series2.getData().add(new XYChart.Data(brazil, 41941.19));
XYChart.Series series3 = new XYChart.Series();
series3.setName("2005");
series3.getData().add(new XYChart.Data(france, 18722.18));
Scene scene = new Scene(bc, 800, 600);
bc.getData().addAll(series1, series2, series3);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
And this is how it looks like:
Thank you very much!
Problem
The problem with your BarChart is that you are adding 3 Series, but just one unique country in each of the series. The BarChart inherently leaves space for 2 other series.
Solution
The correct way of achieving this is using one XYChart.Series and later, add colors to each of the bars. Here is a sample application, which does the same.
public class BarChartDemo extends Application {
final static String austria = "Austria";
final static String brazil = "Brazil";
final static String france = "France";
#Override
public void start(Stage stage) {
stage.setTitle("Bar Chart Sample");
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
final BarChart<String, Number> bc = new BarChart<String, Number>(xAxis, yAxis);
bc.setTitle("Country Summary");
xAxis.setLabel("Country");
yAxis.setLabel("Value");
XYChart.Series series1 = new XYChart.Series();
series1.setName("2003");
final XYChart.Data<String, Number> dataAustria = new XYChart.Data(austria, 25601.34);
final XYChart.Data<String, Number> dataBrazil = new XYChart.Data(brazil, 41941.19);
final XYChart.Data<String, Number> dataFrance = new XYChart.Data(france, 35000.19);
series1.getData().add(dataAustria);
series1.getData().add(dataBrazil);
series1.getData().add(dataFrance);
Scene scene = new Scene(bc, 800, 600);
bc.getData().addAll(series1);
stage.setScene(scene);
stage.show();
dataAustria.getNode().setStyle("-fx-bar-fill: CHART_COLOR_1;");
dataBrazil.getNode().setStyle("-fx-bar-fill: CHART_COLOR_2;");
dataFrance.getNode().setStyle("-fx-bar-fill: CHART_COLOR_3;");
}
public static void main(String[] args) {
launch(args);
}
}
The output looks something that you are looking for:
Note : You need to apply style to the nodes after they have been displayed on the Stage. Alternatively, you can use xyChartData.nodeProperty().addListener() and apply style into it.
dataAustria.nodeProperty().addListener((ov, oldNode, newNode) -> {
if(null != newNode) {
newNode.setStyle("-fx-bar-fill: red;");
}
});

How to zoom-out javafx linechart after it was zoomed in?

I've got this code right now.
public class ScalableChart extends VBox implements Initializable{
#FXML
private LineChart<Number, Number> chart;
#FXML
private Rectangle zoomRect;
#FXML
private StackPane pane;
private boolean selectionGestureStarted = false;
private ObjectProperty<javafx.geometry.Point2D> mouseAnchor = new SimpleObjectProperty<>();;
public ScalableChart() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(
"hello.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
zoomRect.setManaged(true);
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
#FXML
protected void reset(MouseEvent event) {
System.out.println("Reset");
chart.getXAxis().setAutoRanging(true);
chart.getYAxis().setAutoRanging(true);
final NumberAxis xAxis = (NumberAxis)chart.getXAxis();
xAxis.setLowerBound(0);
xAxis.invalidateRange(chart.getData().);
xAxis.setUpperBound(Collections.max(chart.getData().));
final NumberAxis yAxis = (NumberAxis)chart.getYAxis();
yAxis.setLowerBound(0);
yAxis.setUpperBound(1000);
zoomRect.setWidth(0);
zoomRect.setHeight(0);
}
#FXML
protected void startSettingZoom(MouseEvent event) {
setSelectionGestureStarted(true);
mouseAnchor.set(new javafx.geometry.Point2D(event.getX(), event.getY()));
zoomRect.setWidth(0);
zoomRect.setHeight(0);
}
#FXML
protected void changeZoomSettings(MouseEvent event) {
if (isSelectionGestureStarted()) {
zoomRect.setX(Math.min(event.getX(), mouseAnchor.get().getX()));
zoomRect.setY(Math.min(event.getY(), mouseAnchor.get().getY()));
zoomRect.setWidth(Math.abs(event.getX() - mouseAnchor.get().getX()));
zoomRect.setHeight(Math.abs(event.getY() - mouseAnchor.get().getY()));
}
}
#FXML
protected void stopSettingZoom(MouseEvent event) {
chart.getXAxis().setAutoRanging(false);
chart.getYAxis().setAutoRanging(false);
doZoom(zoomRect, chart);
zoomRect.setWidth(0);
zoomRect.setHeight(0);
setSelectionGestureStarted(false);
}
private void doZoom(javafx.scene.shape.Rectangle zoomRect, LineChart<Number, Number> chart) {
javafx.geometry.Point2D zoomTopLeft = new javafx.geometry.Point2D(zoomRect.getX(), zoomRect.getY());
javafx.geometry.Point2D zoomBottomRight = new javafx.geometry.Point2D(zoomRect.getX() + zoomRect.getWidth(), zoomRect.getY() + zoomRect.getHeight());
final NumberAxis yAxis = (NumberAxis) chart.getYAxis();
javafx.geometry.Point2D yAxisInScene = yAxis.localToScene(0, 0);
final NumberAxis xAxis = (NumberAxis) chart.getXAxis();
javafx.geometry.Point2D xAxisInScene = xAxis.localToScene(0, 0);
double xOffset = zoomTopLeft.getX() - yAxisInScene.getX() ;
double yOffset = zoomBottomRight.getY() - xAxisInScene.getY();
double xAxisScale = xAxis.getScale();
double yAxisScale = yAxis.getScale();
xAxis.setLowerBound(xAxis.getLowerBound() + xOffset / xAxisScale);
xAxis.setUpperBound(xAxis.getLowerBound() + zoomRect.getWidth() / xAxisScale);
yAxis.setLowerBound(yAxis.getLowerBound() + yOffset / yAxisScale);
yAxis.setUpperBound(yAxis.getLowerBound() - zoomRect.getHeight() / yAxisScale);
System.out.println(yAxis.getLowerBound() + " " + yAxis.getUpperBound());
zoomRect.setWidth(0);
zoomRect.setHeight(0);
}
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
chart.setTitle("Title");
LineChart.Series<Number, Number> series = new LineChart.Series<Number, Number>();
series.getData().add(new XYChart.Data<Number, Number>(10, 10));
series.getData().add(new XYChart.Data<Number, Number>(20, 20));
chart.getData().add(series);
}
public boolean isSelectionGestureStarted() {
return selectionGestureStarted;
}
public void setSelectionGestureStarted(boolean selectionGestureStarted) {
this.selectionGestureStarted = selectionGestureStarted;
}
}
Also right now I can't see selection rectangle when it's on chart. I'm using the stackpane and I tried to add rectangle before the chart and also after the chart. None of this is working.
Make a transparent rectangle with red border:
zoomRect.setStyle("-fx-fill:Transparent; -fx-stroke:RED");
Add it to the Scene root and call setX() adn seY() to place the upper left corner in the right position.

Resources