Saturday, March 2, 2013

JavaFX 2: Update StackedBarChart dynamically, with TableView.

In the former article "Create dynamic StackedBarChart of JavaFX 2" demonstrate how to update StackedBarChart dynamically, with TableView. BUT it's not a good example! Both the TableView and one data series of the StackedBarChart work with the same ObservableList<XYChart.Data>, only one data series of the StackedBarChart can be modified with the TableView. And hard to extend to update more data series.

Last article demonstrate how to implement "Editable multi-column TableView". It's modified to work with StackedBarChart, all the data series in StackedBarChart can be changed with the TableView.


Instead of share a common ObservableList<XYChart.Data>, a class MyList is implemented. It hold a ObservableList<Record> for TableView, and two ObservableList<XYChart.Data> for StackedBarChart, and also keep their content consistency.

Once editing of the table committed, the handler update the ObservableList<Record> for TableView, and also the corresponding ObservableList<XYChart.Data> for StackedBarChart.

package javafxmulticolumnchart;

import java.util.Arrays;
import java.util.List;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Group;
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.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;

/**
 *
 * @web http://java-buddy.blogspot.com/
 */
public class JavaFXMultiColumnChart extends Application {
    
    public class Record{
        private SimpleStringProperty fieldMonth;
        private SimpleDoubleProperty fieldValue1;
        private SimpleDoubleProperty fieldValue2;
        
        Record(String fMonth, double fValue1, double fValue2){
            this.fieldMonth = new SimpleStringProperty(fMonth);
            this.fieldValue1 = new SimpleDoubleProperty(fValue1);
            this.fieldValue2 = new SimpleDoubleProperty(fValue2);
        }
        
        public String getFieldMonth() {
            return fieldMonth.get();
        }
        
        public double getFieldValue1() {
            return fieldValue1.get();
        }
        
        public double getFieldValue2() {
            return fieldValue2.get();
        }
        
        public void setFieldMonth(String fMonth) {
            fieldMonth.set(fMonth);
        }
     
        public void setFieldValue1(Double fValue1) {
            fieldValue1.set(fValue1);
        }
        
        public void setFieldValue2(Double fValue2) {
            fieldValue2.set(fValue2);
        }
    }
    
    class MyList {
        
        ObservableList<Record> dataList;
        ObservableList<XYChart.Data> xyList1;
        ObservableList<XYChart.Data> xyList2;
        
        MyList(){
            dataList = FXCollections.observableArrayList();
            xyList1 = FXCollections.observableArrayList();
            xyList2 = FXCollections.observableArrayList();
        }
        
        public void add(Record r){
            dataList.add(r);
            xyList1.add(new XYChart.Data(r.getFieldMonth(), r.getFieldValue1()));
            xyList2.add(new XYChart.Data(r.getFieldMonth(), r.getFieldValue2()));
        }
        
        public void update1(int pos, Double val){
            xyList1.set(pos, new XYChart.Data(xyList1.get(pos).getXValue(), val));
        }
        
        public void update2(int pos, Double val){
            xyList2.set(pos, new XYChart.Data(xyList2.get(pos).getXValue(), val));
        }
    }
    
    MyList myList;
    
    private TableView<Record> tableView = new TableView<>();
    
    public static void main(String[] args) {
        launch(args);
    }
    
    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("java-buddy.blogspot.com");
        
        //prepare myList
        myList = new MyList();
        myList.add(new Record("January", 100, 120));
        myList.add(new Record("February", 200, 210));
        myList.add(new Record("March", 50, 70));
        myList.add(new Record("April", 75, 50));
        myList.add(new Record("May", 110, 120));
        myList.add(new Record("June", 300, 200));
        myList.add(new Record("July", 111, 100));
        myList.add(new Record("August", 30, 50));
        myList.add(new Record("September", 75, 70));
        myList.add(new Record("October", 55, 50));
        myList.add(new Record("November", 225, 225));
        myList.add(new Record("December", 99, 100));
        
        Group root = new Group();
        
        tableView.setEditable(true);

        Callback<TableColumn, TableCell> cellFactory = 
                new Callback<TableColumn, TableCell>() {
                          
                          @Override
                          public TableCell call(TableColumn p) {
                              return new EditingCell();
                          }
                      };
        
        TableColumn columnMonth = new TableColumn("Month");
        columnMonth.setCellValueFactory(
                new PropertyValueFactory<Record,String>("fieldMonth"));
        columnMonth.setMinWidth(60);
        
        TableColumn columnValue1 = new TableColumn("Value 1");
        columnValue1.setCellValueFactory(
                new PropertyValueFactory<Record,Double>("fieldValue1"));
        columnValue1.setMinWidth(60);
        
        TableColumn columnValue2 = new TableColumn("Value 2");
        columnValue2.setCellValueFactory(
                new PropertyValueFactory<Record,Double>("fieldValue2"));
        columnValue2.setMinWidth(60);
     
        //--- Add for Editable Cell of Value field, in Double
        columnValue1.setCellFactory(cellFactory);
        columnValue1.setOnEditCommit(
                new EventHandler<TableColumn.CellEditEvent<Record, Double>>() {
                    
                    @Override public void handle(TableColumn.CellEditEvent<Record, Double> t) {
                        ((Record)t.getTableView().getItems().get(
                                t.getTablePosition().getRow())).setFieldValue1(t.getNewValue());
                        
                        int pos = t.getTablePosition().getRow();
                        myList.update1(pos, t.getNewValue());
                    }
                });
        
        columnValue2.setCellFactory(cellFactory);
        columnValue2.setOnEditCommit(
                new EventHandler<TableColumn.CellEditEvent<Record, Double>>() {
                    
                    @Override public void handle(TableColumn.CellEditEvent<Record, Double> t) {
                        ((Record)t.getTableView()
                                .getItems()
                                .get(t.getTablePosition().getRow())).setFieldValue2(t.getNewValue());
                        
                        int pos = t.getTablePosition().getRow();
                        myList.update2(pos, t.getNewValue());
                    }
                });
      
        //--- Prepare StackedBarChart
        
        List<String> monthLabels = Arrays.asList(
                "January", 
                "February",
                "March",
                "April",
                "May",
                "June",
                "July",
                "August",
                "September",
                "October",
                "November",
                "December");
                
        final CategoryAxis xAxis1 = new CategoryAxis();
        final NumberAxis yAxis1 = new NumberAxis();
        xAxis1.setLabel("Month");
        xAxis1.setCategories(FXCollections.<String> observableArrayList(monthLabels));
        yAxis1.setLabel("Value 1");
        XYChart.Series XYSeries1 = new XYChart.Series(myList.xyList1);
        XYSeries1.setName("XYChart.Series 1");
        
        final CategoryAxis xAxis2 = new CategoryAxis();
        final NumberAxis yAxis2 = new NumberAxis();
        xAxis2.setLabel("Month");
        xAxis2.setCategories(FXCollections.<String> observableArrayList(monthLabels));
        yAxis2.setLabel("Value 2");
        XYChart.Series XYSeries2 = new XYChart.Series(myList.xyList2);
        XYSeries2.setName("XYChart.Series 2");
        
        final StackedBarChart<String,Number> stackedBarChart = new StackedBarChart<>(xAxis1,yAxis1);
        stackedBarChart.setTitle("StackedBarChart");
        stackedBarChart.getData().addAll(XYSeries1, XYSeries2);
        //---
        tableView.setItems(myList.dataList);
        tableView.getColumns().addAll(columnMonth, columnValue1, columnValue2);
        
        HBox hBox = new HBox();
        hBox.setSpacing(10);
        hBox.getChildren().addAll(tableView, stackedBarChart);
 
        root.getChildren().add(hBox);
     
      primaryStage.setScene(new Scene(root, 750, 400));
      primaryStage.show();
    }

    class EditingCell extends TableCell<Record, Double> {
        private TextField textField;
        
        public EditingCell() {}
        
        @Override
        public void startEdit() {
            super.startEdit();
            
            if (textField == null) {
                createTextField();
            }
            
            setGraphic(textField);
            setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
            textField.selectAll();
        }
        
        @Override
        public void cancelEdit() {
            super.cancelEdit();
            
            setText(String.valueOf(getItem()));
            setContentDisplay(ContentDisplay.TEXT_ONLY);
        }
        
        @Override
        public void updateItem(Double item, boolean empty) {
            super.updateItem(item, empty);
         
            if (empty) {
                setText(null);
                setGraphic(null);
            } else {
                if (isEditing()) {
                    if (textField != null) {
                        textField.setText(getString());
                    }
                    
                    setGraphic(textField);
                    setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
                } else {
                    setText(getString());
                    setContentDisplay(ContentDisplay.TEXT_ONLY);
                }
            }
        }
        
        private void createTextField() {
            textField = new TextField(getString());
            textField.setMinWidth(this.getWidth() - this.getGraphicTextGap()*2);
            textField.setOnKeyPressed(new EventHandler<KeyEvent>() {
             
                @Override
                public void handle(KeyEvent t) {
                    if (t.getCode() == KeyCode.ENTER) {
                        commitEdit(Double.parseDouble(textField.getText()));
                    } else if (t.getCode() == KeyCode.ESCAPE) {
                        cancelEdit();
                    }
                }
            });
        }
     
        private String getString() {
            return getItem() == null ? "" : getItem().toString();
        }
    }
    
}


Another example to make PieChart and LineChart update dynamically with TableView.


2 comments:

  1. n the below example i have 3 series Region 1,2 & 3.. i want these series also to be dyanamic what ive done is used a mysql query using the DISTINCT CLAUSE got all the values of the attribute now depending on how many distinct values i have i ned that many Series. n then the values for these Series again needs an invocation of the Mysql query.

    public class StackedBarChartSmple extends Application {

    private void init(Stage primaryStage) {
    Group root = new Group();
    primaryStage.setScene(new Scene(root));
    String[] years = {"2007", "2008", "2009"};
    CategoryAxis xAxis = CategoryAxisBuilder.create()
    .categories(FXCollections.observableArrayList(years)).build();
    NumberAxis yAxis = NumberAxisBuilder.create()
    .label("Units Sold")
    .lowerBound(0.0d)
    .upperBound(10000.0d)
    .tickUnit(1000.0d).build();
    ObservableList barChartData = FXCollections.observableArrayList(
    new StackedBarChart.Series("Region 1", FXCollections.observableArrayList(
    new StackedBarChart.Data(years[0], 567d),
    new StackedBarChart.Data(years[1], 1292d),
    new StackedBarChart.Data(years[2], 1292d)
    )),
    new StackedBarChart.Series("Region 2", FXCollections.observableArrayList(
    new StackedBarChart.Data(years[0], 956),
    new StackedBarChart.Data(years[1], 1665),
    new StackedBarChart.Data(years[2], 2559)
    )),
    new StackedBarChart.Series("Region 3", FXCollections.observableArrayList(
    new StackedBarChart.Data(years[0], 1154),
    new StackedBarChart.Data(years[1], 1927),
    new StackedBarChart.Data(years[2], 2774)
    ))
    );

    StackedBarChart chart = new StackedBarChart(xAxis, yAxis, barChartData, 25.0d);
    root.getChildren().add(chart);
    }

    @Override public void start(Stage primaryStage) throws Exception {
    init(primaryStage);
    primaryStage.show();
    }
    public static void main(String[] args) { launch(args); }
    }

    ReplyDelete
  2. The main problem is I want the "series" to be Dynamic.For e.g in my proposed Systwm if the user select location then let say 7 series should be generated ,if he selects "sub - practice" then 5 series have to be generated and then i should be able to populate this series via SQL I have done something very similar using pie and bar but i can't figure out this one

    ReplyDelete