Generate Dynamic StackedBarChart - javafx

I want to create dynamic StackedBarChart from Java Object. I tried this code:
Java Object:
public class EventsObj
{
private String date;
private int info;
private int error;
private int warning;
private int critical;
public EventsObj()
{
}
public EventsObj(String date, int info, int error, int warning, int critical)
{
this.date = date;
this.info = info;
this.error = error;
this.warning = warning;
this.critical = critical;
}
public String getDate()
{
return date;
}
public void setDate(String date)
{
this.date = date;
}
public int getInfo()
{
return info;
}
public void setInfo(int info)
{
this.info = info;
}
public int getError()
{
return error;
}
public void setError(int error)
{
this.error = error;
}
public int getWarning()
{
return warning;
}
public void setWarning(int warning)
{
this.warning = warning;
}
public int getCritical()
{
return critical;
}
public void setCritical(int critical)
{
this.critical = critical;
}
}
Java code:
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.StackedBarChart;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class MainApp extends Application
{
private StackedBarChart<String, Number> stackedChart;
private List<EventsObj> eventsObj;
#Override
public void start(Stage stage) throws Exception
{
createStackedChart();
List<EventsObj> testData = generateTestData();
addStackedChartData(testData);
HBox hb = new HBox(20);
hb.setPadding(new Insets(10, 20, 20, 40));
hb.getChildren().add(stackedChart);
Scene scene = new Scene(hb);
stage.setTitle("JavaFX and Maven");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args)
{
launch(args);
}
private void createStackedChart()
{
CategoryAxis xAxis = new CategoryAxis();
xAxis.setLabel("Days");
NumberAxis yAxis = new NumberAxis();
stackedChart = new StackedBarChart<>(xAxis, yAxis);
stackedChart.setCategoryGap(20);
stackedChart.setMinSize(500, 400);
stackedChart.setMaxSize(500, 400);
}
private List<EventsObj> generateTestData()
{
eventsObj = new ArrayList<>();
for (int i = 0; i < 20; i++)
{
eventsObj.add(new EventsObj(String.valueOf(randomDate()), random(2, 60), random(2, 60), random(2, 60), random(2, 60)));
}
return eventsObj;
}
public static int random(int lowerBound, int upperBound)
{
return (lowerBound + (int) Math.round(Math.random() * (upperBound - lowerBound)));
}
private LocalDate randomDate()
{
Random random = new Random();
int minDay = (int) LocalDate.of(1900, 1, 1).toEpochDay();
int maxDay = (int) LocalDate.of(2015, 1, 1).toEpochDay();
long randomDay = minDay + random.nextInt(maxDay - minDay);
LocalDate randomBirthDate = LocalDate.ofEpochDay(randomDay);
return randomBirthDate;
}
private void addStackedChartData(List<EventsObj> data)
{
ObservableList<XYChart.Series<String, Number>> observableArrayList = FXCollections.observableArrayList();
ObservableList<XYChart.Series<String, Number>> observabt = FXCollections.observableArrayList();
for (EventsObj data1 : data)
{
final XYChart.Series<String, Number> series1 = new XYChart.Series<>();
EventsObj get = data1;
series1.setName(get.getDate());
series1.getData().add(new XYChart.Data<>(get.getDate(), get.getInfo()));
series1.getData().add(new XYChart.Data<>(get.getDate(), get.getWarning()));
series1.getData().add(new XYChart.Data<>(get.getDate(), get.getCritical()));
series1.getData().add(new XYChart.Data<>(get.getDate(), get.getError()));
observabt.addAll(series1);
}
stackedChart.getData().addAll(observabt);
}
}
I need to generate a StackedBarChart that contains one bar for every day that contains different parts for the event types Info, Warning, Critical and Error. But for some reason I can't get it right. It should be something like this:
Countries should be replaced by dates and years should be replaced by event types.
Can you help me fix the code?

You want to create a chart that has one column per date and each column should contain parts Info, Warning, Error and Critical. To do this you need to create a different series for each event type. However you use date as series name and column name. You need to change this:
private static XYChart.Series<String, Number> createSeries(String name) {
XYChart.Series<String, Number> series = new XYChart.Series<>();
series.setName(name);
return series;
}
private void addStackedChartData(List<EventsObj> data) {
XYChart.Series<String, Number> infoSeries = createSeries("Info");
XYChart.Series<String, Number> warningSeries = createSeries("Warning");
XYChart.Series<String, Number> criticalSeries = createSeries("Critical");
XYChart.Series<String, Number> errorSeries = createSeries("Error");
for (EventsObj data1 : data) {
String date = data1.getDate();
infoSeries.getData().add(new XYChart.Data<>(date, data1.getInfo()));
warningSeries.getData().add(new XYChart.Data<>(date, data1.getWarning()));
errorSeries.getData().add(new XYChart.Data<>(date, data1.getError()));
criticalSeries.getData().add(new XYChart.Data<>(date, data1.getCritical()));
}
stackedChart.getData().setAll(errorSeries, warningSeries, infoSeries, criticalSeries);
}
The series has to be used this way:
series.setName(<Color Key>);
series.getData().add(new XYChart.Data<>(<Column>, <Bar Size>));

Related

How to create a read only tableview and add data to it?

I'm trying to learn how TableView work's in JavaFX. I wrote a simple class to add a list of movies to a table but when I run it I'm getting this error:
javafx.scene.control.cell.PropertyValueFactory getCellDataReflectively
WARNING: Can not retrieve property 'Price' in PropertyValueFactory: javafx.scene.control.cell.PropertyValueFactory#3aebd1d5 with provided class type: class application.MovieClass
java.lang.IllegalStateException: Cannot read from unreadable property Price
there are 3 variables in the movie class and I get the same error for all of them.
here are the 2 classes that I wrote
main:
package application;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
public class Main extends Application {
TableView<MovieClass> table;
#Override
public void start(Stage primaryStage) {
Label lblHeading = new Label("Movie Inventory");
TableView<MovieClass> table = new TableView<MovieClass>();
table.setItems(loadData());
TableColumn<MovieClass, String> colTitle = new TableColumn<MovieClass, String>("Title");
colTitle.setMinWidth(300);
colTitle.setCellValueFactory(
new PropertyValueFactory<MovieClass, String>("title"));
TableColumn<MovieClass, Integer> colYear = new TableColumn<MovieClass, Integer>("year");
colYear.setMinWidth(100);
colYear.setCellValueFactory(
new PropertyValueFactory<MovieClass, Integer>("Year"));
TableColumn<MovieClass, Double> colPrice = new TableColumn<MovieClass, Double>("price");
colPrice.setMinWidth(100);
colPrice.setCellValueFactory(
new PropertyValueFactory<MovieClass, Double>("Price"));
table.getColumns().addAll(colTitle, colYear, colPrice);
print(loadData());
VBox paneMain = new VBox();
paneMain.setSpacing(10);
paneMain.setPadding(new Insets(10, 10, 10, 10));
paneMain.getChildren().addAll(lblHeading, table);
Scene scene = new Scene(paneMain);
primaryStage.setScene(scene);
primaryStage.setTitle("Movie Inventory");
primaryStage.show();
}
public ObservableList<MovieClass> loadData()
{
ObservableList<MovieClass> data = FXCollections.observableArrayList();
data.add(new MovieClass("It's a Wonderful Life",1946, 14.95));
data.add(new MovieClass("Young Frankenstein",1974, 16.95));
data.add(new MovieClass("Star Wars Episode 4",1976, 17.95));
data.add(new MovieClass("The Princess Bride",1987, 16.95));
data.add(new MovieClass("Glory",1989, 14.95));
data.add(new MovieClass("The Game",1997, 14.95));
data.add(new MovieClass("Shakespeare in Love",1998, 19.95));
data.add(new MovieClass("The Invention of Lying",2009, 18.95));
data.add(new MovieClass("The King's Speech",2010, 19.95));
return data; }
public void print(ObservableList<MovieClass> list) {
for(int i = 0; i > list.size(); i++) {
System.out.println("The Title = " + list.get(i).getMyTitle()
+ " " + "The Year = " + list.get(i).getMyYear()
+ " " + "The Price = " + list.get(i).getMyPrice());
}
}
public static void main(String[] args) {
launch(args);
}
}
movie class:
package application;
public class MovieClass {
private String title;
private int year;
private double price;
public MovieClass()
{
this.title = "";
this.year = 0;
this.price = 0.0;
}
public MovieClass(String title, int year, double price)
{
this.title = title;
this.year = year;
this.price = price;
}
public String getTitle() {
return this.title;
}
public void setTitle(String title) {
this.title = title;
}
public int getYear() {
return this.year;
}
public void setYear(int year) {
this.year = year;
}
public double getPrice() {
return this.price;
}
public void setPrice(double price) {
this.price = price;
}
}

Bind column to multiplication of other columns [duplicate]

This question already has answers here:
JavaFX Tableview - column value dependent on other columns
(2 answers)
Closed 6 years ago.
I would like to bind amount column to price and quantity columns such that everytime either quantity or price, amount updates.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.converter.NumberStringConverter;
/**
*
* #author Yunus
*/
public class ColumnBinding extends Application{
private TableView<Product> table = new TableView<Product>();
private final ObservableList<Product> data = FXCollections.observableArrayList();
final HBox hb = new HBox();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(new Group());
primaryStage.setTitle("Book Store Sample");
primaryStage.setWidth(650);
primaryStage.setHeight(550);
final Label label = new Label("Testing");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
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 amount = new TableColumn("Amount");
amount.setMinWidth(200);
amount.setCellValueFactory(
new PropertyValueFactory<Product, String>("amount"));
data.addAll(new Product(10, 12, 120),
new Product(20, 12, 240),
new Product(30, 12, 360),
new Product(40, 12, 480),
new Product(50, 12, 600));
table.setItems(data);
table.getColumns().addAll(priceCol, quantityCol, amount);
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);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class Product{
Product(){}
public Product(float quantity, float price, float amount) {
this.quantity = quantity;
this.price = price;
this.amount = amount;
}
private float quantity;
private float price;
private float amount;
public float getQuantity() {
return quantity;
}
public void setQuantity(float quantity) {
this.quantity = quantity;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public float getAmount() {
return amount;
}
public void setAmount(float amount) {
this.amount = amount;
}
}
}
The challenge though is not to change POJO class(Product) with property fields
I would just use JavaFX properties in the model class. Then you can establish the relationship by binding in the model:
public static class Product{
private final FloatProperty quantity = new SimpleFloatProperty();
private final FloatProperty price = new SimpleFloatProperty();
private final ReadOnlyFloatWrapper amount = new ReadOnlyFloatWrapper();
Product(){
this(0f, 0f);
}
// if amount is supposed to depend on quantity and price, it makes
// no sense at all to have a constructor taking parameters for all
// three values...
public Product(float quantity, float price) {
setQuantity(quantity);
setPrice(price);
this.amount.bind(this.quantity.multiply(this.price));
}
public float getQuantity() {
return quantityProperty().get();
}
public void setQuantity(float quantity) {
quantityProperty().set(quantity);
}
public FloatProperty quantityProperty() {
return quantity ;
}
public float getPrice() {
return priceProperty().get();
}
public void setPrice(float price) {
priceProperty().set(price);
}
public FloatProperty priceProperty() {
return price ;
}
public float getAmount() {
return amountProperty.get();
}
// Again, it makes no sense at all to have this method
// if amount depends on the other values
// public void setAmount(float amount) {
// this.amount = amount;
// }
public ReadOnlyFloatProperty amountProperty() {
return amount.getReadOnlyProperty();
}
}
Now your table columns are easy:
TableColumn<Product, Float> priceColumn = new TableColumn<>("Price");
priceColumn.setCellValueFactory(cellData -> cellData.getValue().priceProperty().asObject());
TableColumn<Product, Float> quantityColumn = new TableColumn<>("Quantity");
quantityColumn.setCellValueFactory(cellData -> cellData.getValue().quantityProperty().asObject());
TableColumn<Product, Float> amountColumn = new TableColumn<>("Amount");
amountColumn.setCellValueFactory(cellData -> cellData.getValue().amountProperty().asObject());

Convert chart value in easy readable format

I have this example of Bar chart in which I want to display data in easy readable format:
import java.text.DecimalFormat;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.AreaChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Tooltip;
import javafx.stage.Stage;
public class MainApp extends Application
{
private static final int MAX_DATA_POINTS = 50;
private Series series;
private Series series2;
private int xSeriesData = 0;
private ConcurrentLinkedQueue<Number> dataQ = new ConcurrentLinkedQueue<Number>();
private ConcurrentLinkedQueue<Number> dataQ2 = new ConcurrentLinkedQueue<Number>();
private ExecutorService executor;
private AddToQueue addToQueue;
private NumberAxis xAxis;
private void init(Stage primaryStage)
{
xAxis = new NumberAxis(0, MAX_DATA_POINTS, MAX_DATA_POINTS / 10);
xAxis.setForceZeroInRange(false);
xAxis.setAutoRanging(false);
NumberAxis yAxis = new NumberAxis();
yAxis.setAutoRanging(true);
//-- Chart
final AreaChart<Number, Number> sc = new AreaChart<Number, Number>(xAxis, yAxis);
sc.setAnimated(false);
sc.setCreateSymbols(false);
sc.setId("liveAreaChart");
sc.setTitle("Animated Area Chart");
//-- Chart Series
series = new AreaChart.Series<Number, Number>();
series.setName("Area Chart Series");
series2 = new AreaChart.Series<Number, Number>();
series2.setName("Area Chart Series");
sc.getData().addAll(series, series2);
xAxis.setTickLabelsVisible(false);
xAxis.setTickMarkVisible(false);
xAxis.setMinorTickVisible(false);
primaryStage.setScene(new Scene(sc));
}
#Override
public void start(Stage primaryStage) throws Exception
{
init(primaryStage);
primaryStage.show();
//-- Prepare Executor Services
executor = Executors.newCachedThreadPool(new ThreadFactory()
{
#Override
public Thread newThread(Runnable r)
{
Thread thread = new Thread(r);
thread.setDaemon(true);
return thread;
}
});
addToQueue = new AddToQueue();
executor.execute(addToQueue);
//-- Prepare Timeline
prepareTimeline();
}
public static void main(String[] args)
{
launch(args);
}
private class AddToQueue implements Runnable
{
#Override
public void run()
{
try
{
// add a item of random data to queue
dataQ.add(randomInteger());
dataQ2.add(randomInteger());
Thread.sleep(400);
executor.execute(this);
}
catch (InterruptedException ex)
{
Logger.getLogger(MainApp.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public int randomInteger()
{
int min = 100;
int max = 900000000;
int randomNum = min + (int) (Math.random() * ((max - min) + 1));
return randomNum;
}
private static String readableFileSize(long size)
{
if (size <= 0)
return "0";
final String[] units = new String[]
{
"B", "kB", "MB", "GB", "TB"
};
int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
}
//-- Timeline gets called in the JavaFX Main thread
private void prepareTimeline()
{
// Every frame to take any data from queue and add to chart
new AnimationTimer()
{
#Override
public void handle(long now)
{
addDataToSeries();
}
}.start();
}
private void addDataToSeries()
{
for (int i = 0; i < 20; i++)
{ //-- add 20 numbers to the plot+
if (dataQ.isEmpty())
break;
// series.getData().add(new AreaChart.Data(xSeriesData++, dataQ.remove()));
Data data = new AreaChart.Data(xSeriesData++, dataQ.remove());
series.getData().add(data);
data.nodeProperty().addListener(new ChangeListener<Node>()
{
#Override
public void changed(ObservableValue<? extends Node> arg0, Node arg1,
Node arg2)
{
Tooltip t = new Tooltip(data.getYValue().toString() + '\n' + data.getXValue());
Tooltip.install(arg2, t);
data.nodeProperty().removeListener(this);
}
});
if (dataQ2.isEmpty())
break;
series2.getData().add(new AreaChart.Data(xSeriesData, dataQ2.remove()));
}
// remove points to keep us at no more than MAX_DATA_POINTS
if (series.getData().size() > MAX_DATA_POINTS)
{
series.getData().remove(0, series.getData().size() - MAX_DATA_POINTS);
}
// remove points to keep us at no more than MAX_DATA_POINTS
if (series2.getData().size() > MAX_DATA_POINTS)
{
series2.getData().remove(0, series2.getData().size() - MAX_DATA_POINTS);
}
// update
xAxis.setLowerBound(xSeriesData - MAX_DATA_POINTS);
xAxis.setUpperBound(xSeriesData - 1);
}
}
The original values are in bytes. Based on the value I want to convert the value in bytes/GB/megabytes and etc using the method readableFileSize(long size) before I insert the value into the chart. The problem is that this method returns String.
How I can refactor the code in order to use this method?
PS. Based on the proposal I tried this:
import java.text.DecimalFormat;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.AreaChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Tooltip;
import javafx.stage.Stage;
public class MainApp extends Application
{
private static final int MAX_DATA_POINTS = 50;
private Series series;
private Series series2;
private int xSeriesData = 0;
private ConcurrentLinkedQueue<Number> dataQ = new ConcurrentLinkedQueue<Number>();
private ConcurrentLinkedQueue<Number> dataQ2 = new ConcurrentLinkedQueue<Number>();
private ExecutorService executor;
private AddToQueue addToQueue;
private NumberAxis xAxis;
private NumberAxis yAxis;
private void init(Stage primaryStage)
{
xAxis = new NumberAxis(0, MAX_DATA_POINTS, MAX_DATA_POINTS / 10);
xAxis.setForceZeroInRange(false);
xAxis.setAutoRanging(false);
yAxis = new NumberAxis();
yAxis.setAutoRanging(true);
//-- Chart
final AreaChart<Number, Number> sc = new AreaChart<Number, Number>(xAxis, yAxis);
sc.setAnimated(false);
sc.setCreateSymbols(false);
sc.setId("liveAreaChart");
sc.setTitle("Animated Area Chart");
//-- Chart Series
series = new AreaChart.Series<Number, Number>();
series.setName("Area Chart Series");
series2 = new AreaChart.Series<Number, Number>();
series2.setName("Area Chart Series");
sc.getData().addAll(series, series2);
xAxis.setTickLabelsVisible(false);
xAxis.setTickMarkVisible(false);
xAxis.setMinorTickVisible(false);
primaryStage.setScene(new Scene(sc));
}
#Override
public void start(Stage primaryStage) throws Exception
{
init(primaryStage);
primaryStage.show();
//-- Prepare Executor Services
executor = Executors.newCachedThreadPool(new ThreadFactory()
{
#Override
public Thread newThread(Runnable r)
{
Thread thread = new Thread(r);
thread.setDaemon(true);
return thread;
}
});
addToQueue = new AddToQueue();
executor.execute(addToQueue);
//-- Prepare Timeline
prepareTimeline();
}
public static void main(String[] args)
{
launch(args);
}
private class AddToQueue implements Runnable
{
#Override
public void run()
{
try
{
int size = randomInteger();
final String[] units = new String[]
{
"B", "kB", "MB", "GB", "TB"
};
int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
Long valueOf = Long.valueOf(new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)));
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis)
{
#Override
public String toString(Number object)
{
return String.format("%6.4f " + units[digitGroups], object);
}
});
// add a item of random data to queue
dataQ.add(valueOf);
dataQ2.add(randomInteger());
Thread.sleep(400);
executor.execute(this);
}
catch (InterruptedException ex)
{
Logger.getLogger(MainApp.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public int randomInteger()
{
int min = 100;
int max = 900000000;
int randomNum = min + (int) (Math.random() * ((max - min) + 1));
return randomNum;
}
private static String readableFileSize(long size)
{
if (size <= 0)
return "0";
final String[] units = new String[]
{
"B", "kB", "MB", "GB", "TB"
};
int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
}
//-- Timeline gets called in the JavaFX Main thread
private void prepareTimeline()
{
// Every frame to take any data from queue and add to chart
new AnimationTimer()
{
#Override
public void handle(long now)
{
addDataToSeries();
}
}.start();
}
private void addDataToSeries()
{
for (int i = 0; i < 20; i++)
{ //-- add 20 numbers to the plot+
if (dataQ.isEmpty())
break;
// series.getData().add(new AreaChart.Data(xSeriesData++, dataQ.remove()));
Data data = new AreaChart.Data(xSeriesData++, dataQ.remove());
series.getData().add(data);
data.nodeProperty().addListener(new ChangeListener<Node>()
{
#Override
public void changed(ObservableValue<? extends Node> arg0, Node arg1,
Node arg2)
{
Tooltip t = new Tooltip(data.getYValue().toString() + '\n' + data.getXValue());
Tooltip.install(arg2, t);
data.nodeProperty().removeListener(this);
}
});
if (dataQ2.isEmpty())
break;
series2.getData().add(new AreaChart.Data(xSeriesData, dataQ2.remove()));
}
// remove points to keep us at no more than MAX_DATA_POINTS
if (series.getData().size() > MAX_DATA_POINTS)
{
series.getData().remove(0, series.getData().size() - MAX_DATA_POINTS);
}
// remove points to keep us at no more than MAX_DATA_POINTS
if (series2.getData().size() > MAX_DATA_POINTS)
{
series2.getData().remove(0, series2.getData().size() - MAX_DATA_POINTS);
}
// update
xAxis.setLowerBound(xSeriesData - MAX_DATA_POINTS);
xAxis.setUpperBound(xSeriesData - 1);
}
}
You can use setTickLabelFormatter for the axis tick labels.
A quick example:
import java.text.DecimalFormat;
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;
import javafx.util.StringConverter;
public class LineChartSample extends Application {
#Override
public void start(Stage stage) {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
final LineChart<Number, Number> lineChart = new LineChart<Number, Number>(xAxis, yAxis);
XYChart.Series<Number,Number> series = new XYChart.Series<Number,Number>();
for (int i = 0; i < 10000000; i += 1000) {
series.getData().add(new XYChart.Data(i, i));
}
yAxis.setTickLabelFormatter(new StringConverter<Number>() {
#Override
public String toString(Number object) {
// conversion code from:
// http://stackoverflow.com/questions/13539871/converting-kb-to-mb-gb-tb-dynamicaly
double size = object.doubleValue();
String hrSize = null;
double b = size;
double k = size / 1024.0;
double m = ((size / 1024.0) / 1024.0);
double g = (((size / 1024.0) / 1024.0) / 1024.0);
double t = ((((size / 1024.0) / 1024.0) / 1024.0) / 1024.0);
DecimalFormat dec = new DecimalFormat("0.00");
if (t > 1) {
hrSize = dec.format(t).concat(" TB");
} else if (g > 1) {
hrSize = dec.format(g).concat(" GB");
} else if (m > 1) {
hrSize = dec.format(m).concat(" MB");
} else if (k > 1) {
hrSize = dec.format(k).concat(" KB");
} else {
hrSize = dec.format(b).concat(" Bytes");
}
return hrSize;
}
#Override
public Number fromString(String string) {
// TODO: convert from text to number
return null;
}
});
Scene scene = new Scene(lineChart, 800, 600);
lineChart.getData().add(series);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX Tableview - column value dependent on other columns

I have a TableView in JavaFX. It has a field subTotal which depends on the value of the fields quantity and price. I added a new column for the subTotal.
I have textfields present to add a new row to the table. But, the add button wants to have another textfield for the subTotal, although it does not really necessary for the subtotal column.
What I have tried so far :
TableColumn columnCodeProduct = new TableColumn("Product Code");
columnCodeProduct.setMinWidth(100);
columnCodeProduct.setCellValueFactory(new PropertyValueFactory<Data , Integer>("productname "));
TableColumn columnProductName = new TableColumn("Product Name");
columnProductName.setMinWidth(140);
columnProductName.setCellValueFactory(new PropertyValueFactory<Data , String>("codeproduct"));
TableColumn columnPrice = new TableColumn("Price");
columnPrice.setMinWidth(100);
columnPrice.setCellValueFactory(new PropertyValueFactory<Data , Integer>("price"));
TableColumn columQuantity = new TableColumn("Quantity");
columQuantity.setMinWidth(100);
columQuantity.setCellValueFactory(new PropertyValueFactory<Data , Integer>("quantity"));
TableColumn columnTotal = new TableColumn("Sub Total");
columnTotal.setMinWidth(100);
columQuantity.setCellValueFactory(new PropertyValueFactory<Data , Integer>("sub"));
tableData.getColumns().addAll(columnCodeProduct , columnProductName , columnPrice , columQuantity );
tableData.setItems(data);
addButton = new Button("Add Item");
addButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event)
{
if(addproCodeTextfield.getText().isEmpty() || addproNameTextfield.getText().isEmpty()
|| addPriceTextfield.getText().isEmpty() || quantityTextField.getText().isEmpty())
{
System.out.println("Please Add information to all the fields");
} else {
data.add(new Data (
addproCodeTextfield.getText(),
addproNameTextfield.getText(),
addPriceTextfield.getText(),
quantityTextField.getText()));
methodTotal();
}
}
});
Data Class
public class Data
{
private final SimpleStringProperty codeproduct;
private final SimpleStringProperty productname;
private final SimpleStringProperty price ;
private final SimpleStringProperty quantity;
public Data (String code , String proname , String presyo , String quant )
{
this.codeproduct = new SimpleStringProperty(code);
this.productname = new SimpleStringProperty(proname);
this.price = new SimpleStringProperty(presyo);
this.quantity = new SimpleStringProperty(quant);
}
public String getcodeProduct()
{
return codeproduct.get();
}
public String getproductName()
{
return productname.get();
}
public String getPrice()
{
return price.get();
}
public String getQuantity()
{
return quantity.get();
}
}
I would restructure your model class as #ItachiUchiha suggests. If you feel you need to keep the data stored with String representations, you can just create a binding for the subtotal column:
TableColumn<Data, Number> subtotalColumn = new TableColumn<>("Sub Total");
subTotalColumn.setCellValueFactory(cellData -> {
Data data = cellData.getValue();
return Bindings.createDoubleBinding(
() -> {
try {
double price = Double.parseDouble(data.getPrice());
int quantity = Integer.parseInt(data.getQuantity());
return price * quantity ;
} catch (NumberFormatException nfe) {
return 0 ;
}
},
data.priceProperty(),
data.quantityProperty()
);
});
You can take benefit from JavaFX's power to bind value.
Few points to take care of while implementing a scenario as stated above:
The POJO class(in your case Data) fields must have correct types. For example price and quantity must be of SimpleIntegerProperty instead of SimpleStringProperty. This will help us in using Bindings.
SubTotal field depends on the values of price and quantity. The best way to achieve this is to bind subTotalProperty to a multiply Binding of price and quantity.
I have created a (not so) simple example basic editable tableview to show the approach. It has additional features, like editable cells, that you (or others seeking the same problem) might need ;)
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.converter.NumberStringConverter;
public class TableViewSample extends Application {
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);
}
}
}
Screenshot
Note - I have defined setCellFactory and setOnCommit to each of the columns. This is because the name, price and quantity columns are editable. You are most welcome to remove them in case you do not seek editable property.

Line chart with ConcurrentLinkedQueue

I want to create JavaFX line chart with 2 lines.
I tested this code:
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.AreaChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.stage.Stage;
public class MainApp extends Application
{
private static final int MAX_DATA_POINTS = 50;
private Series series;
private Series series2;
private int xSeriesData = 0;
private ConcurrentLinkedQueue<Number> dataQ = new ConcurrentLinkedQueue<Number>();
private ConcurrentLinkedQueue<Number> dataQ2 = new ConcurrentLinkedQueue<Number>();
private ExecutorService executor;
private AddToQueue addToQueue;
private NumberAxis xAxis;
private void init(Stage primaryStage)
{
xAxis = new NumberAxis(0, MAX_DATA_POINTS, MAX_DATA_POINTS / 10);
xAxis.setForceZeroInRange(false);
xAxis.setAutoRanging(false);
NumberAxis yAxis = new NumberAxis();
yAxis.setAutoRanging(true);
//-- Chart
final AreaChart<Number, Number> sc = new AreaChart<Number, Number>(xAxis, yAxis)
{
// Override to remove symbols on each data point
#Override
protected void dataItemAdded(Series<Number, Number> series, int itemIndex, Data<Number, Number> item)
{
}
};
sc.setAnimated(false);
sc.setId("liveAreaChart");
sc.setTitle("Animated Area Chart");
//-- Chart Series
series = new AreaChart.Series<Number, Number>();
series.setName("Area Chart Series");
series2 = new AreaChart.Series<Number, Number>();
series2.setName("Area Chart Series");
sc.getData().addAll(series, series2);
xAxis.setTickLabelsVisible(false);
xAxis.setTickMarkVisible(false);
xAxis.setMinorTickVisible(false);
primaryStage.setScene(new Scene(sc));
}
#Override
public void start(Stage primaryStage) throws Exception
{
init(primaryStage);
primaryStage.show();
//-- Prepare Executor Services
executor = Executors.newCachedThreadPool(new ThreadFactory()
{
#Override
public Thread newThread(Runnable r)
{
Thread thread = new Thread(r);
thread.setDaemon(true);
return thread;
}
});
addToQueue = new AddToQueue();
executor.execute(addToQueue);
//-- Prepare Timeline
prepareTimeline();
}
public static void main(String[] args)
{
launch(args);
}
private class AddToQueue implements Runnable
{
#Override
public void run()
{
try
{
// add a item of random data to queue
dataQ.add(Math.random());
dataQ2.add(Math.random());
Thread.sleep(200);
executor.execute(this);
}
catch (InterruptedException ex)
{
Logger.getLogger(MainApp.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
//-- Timeline gets called in the JavaFX Main thread
private void prepareTimeline()
{
// Every frame to take any data from queue and add to chart
new AnimationTimer()
{
#Override
public void handle(long now)
{
addDataToSeries();
}
}.start();
}
private void addDataToSeries()
{
for (int i = 0; i < 20; i++)
{ //-- add 20 numbers to the plot+
if (dataQ.isEmpty())
break;
series.getData().add(new AreaChart.Data(xSeriesData++, dataQ.remove()));
if (dataQ2.isEmpty())
break;
series2.getData().add(new AreaChart.Data(xSeriesData, dataQ2.remove()));
}
// remove points to keep us at no more than MAX_DATA_POINTS
if (series.getData().size() > MAX_DATA_POINTS)
{
series.getData().remove(0, series.getData().size() - MAX_DATA_POINTS);
}
// remove points to keep us at no more than MAX_DATA_POINTS
if (series2.getData().size() > MAX_DATA_POINTS)
{
series2.getData().remove(0, series2.getData().size() - MAX_DATA_POINTS);
}
// update
xAxis.setLowerBound(xSeriesData - MAX_DATA_POINTS);
xAxis.setUpperBound(xSeriesData - 1);
}
}
For some reason new data is added but old is always displayed. I want to delete the old line history and display the line chart as animation. Can you give some advice?
EDIT
I noticed this space between the lines.

Resources