在JavaFX上动态更新多个系列的BarChart

时间:2014-10-09 13:25:51

标签: java bar-chart javafx-8

我想创建一个包含多个系列的条形图,并从tableview动态更新。我从http://java-buddy.blogspot.sg/2013/03/javafx-2-update-stackedbarchart.html找到了这段代码,但我将代码更改为适用于BarChart。但是我不知道为什么它能够处理他的视频,但条形图上的系列似乎没有为我正确更新。这两个系列似乎像StackedBarChart一样合并在一起。我意识到下面的代码与多个系列在BarChart / StackedBarChart上无法正常工作,但在LineChart上完美运行。任何人都可以告诉我什么是错的,为什么它不适用于BarChart?以下是代码在我的计算机上运行的预览http://www.youtube.com/watch?v=mApuxJbt92g。感谢您的时间。 注意:我使用的是JRE 1.8.0_20

    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.BarChart;
    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);
    */

    final BarChart<String,Number> barChart = new BarChart<>(xAxis1,yAxis1);
    barChart.setTitle("BarChart");
    barChart.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, barChart);

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

    }

2 个答案:

答案 0 :(得分:2)

您基于代码的链接已经过时了,因此代码中有相当多的遗留代码&#34;感受到它。您可以利用许多新的API,例如TextFieldTableCell。如果在模型类中使用此项和properly use JavaFX properties,则可以消除从表格单元格到数据的更新的所有连接。

此外,如果您使用EasyBind framework,则可以非常轻松地将表格数据(模型对象列表)直接映射到XYChart.Data个对象列表。

这些更改似乎消除了您描述的问题,并且还会使您的代码更容易维护:

import java.time.Month;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.Stream;

import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import javafx.util.converter.NumberStringConverter;

import org.fxmisc.easybind.EasyBind;


public class UpdatableBarChart extends Application {


    @Override
    public void start(Stage primaryStage) {

        ObservableList<Item> items = FXCollections.observableArrayList(
            item -> new Observable[] {
                item.nameProperty(), item.value1Property(), item.value2Property()
        });

        Random rng = new Random();
        Stream.of(Month.values())
            .map(month -> new Item(month.toString(), rng.nextInt(2000)+ 1000, rng.nextInt(2000)+1000))
            .forEach(items::add);

        ObservableList<XYChart.Data<String, Number>> dataSet1 = EasyBind.map(items, 
                item -> new XYChart.Data<>(item.getName(), item.getValue1()));

        ObservableList<XYChart.Data<String, Number>> dataSet2 = EasyBind.map(items, 
                item -> new XYChart.Data<>(item.getName(), item.getValue2()));

        BarChart<String, Number> chart = 
            new BarChart<String, Number>(new CategoryAxis(), new NumberAxis());
        chart.getData().add(new Series<>("Value 1", dataSet1));
        chart.getData().add(new Series<>("Value 2", dataSet2));

        TableView<Item> table = new TableView<>();
        table.setItems(items);
        table.setEditable(true);
        NumberStringConverter numConverter = new NumberStringConverter();
        table.getColumns().add(createCol("Name", Item::nameProperty, null));
        table.getColumns().add(createCol("Value 1", Item::value1Property, numConverter));
        table.getColumns().add(createCol("Value 2", Item::value2Property, numConverter));

        BorderPane root = new BorderPane(table, chart, null, null, null);
        Scene scene = new Scene(root);

        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private <S, T> TableColumn<S,T> createCol(String title, 
            Function<S,ObservableValue<T>> propertySelector, 
            StringConverter<T> converter) {

        TableColumn<S,T> col = new TableColumn<>(title);
        col.setCellFactory(TextFieldTableCell.forTableColumn(converter));
        col.setCellValueFactory(cellData -> propertySelector.apply(cellData.getValue()));
        return col ;
    }

    public static void main(String[] args) {
        launch(args);
    }
}

使用如下的模型类:

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class Item {
    private final StringProperty name = new SimpleStringProperty(this, "name");
    private final DoubleProperty value1 = new SimpleDoubleProperty(this, "value1");
    private final DoubleProperty value2 = new SimpleDoubleProperty(this, "value2");

    public Item(String name, double value1, double value2) {
        setName(name);
        setValue1(value1);
        setValue2(value2);
    }


    public final StringProperty nameProperty() {
        return this.name;
    }
    public final java.lang.String getName() {
        return this.nameProperty().get();
    }
    public final void setName(final java.lang.String name) {
        this.nameProperty().set(name);
    }
    public final DoubleProperty value1Property() {
        return this.value1;
    }
    public final double getValue1() {
        return this.value1Property().get();
    }
    public final void setValue1(final double value1) {
        this.value1Property().set(value1);
    }
    public final DoubleProperty value2Property() {
        return this.value2;
    }
    public final double getValue2() {
        return this.value2Property().get();
    }
    public final void setValue2(final double value2) {
        this.value2Property().set(value2);
    }


}

答案 1 :(得分:0)

默认情况下,图表启用了动画,当数据更改时,系列会从旧值动画到新值。

只需设置:

barChart.setAnimated(false);

事实证明问题消失了,没有更奇怪的行为,两个系列都保持分离。

但如果您在更改数据时仍想要动画,请设置:

barChart.setAnimated(true);

并且为了避免你注意到的奇怪行为(两个系列都是在同一个位置绘制,它们之间没有间隙),请使用这个(丑陋的)解决方法:

class MyList {
...
    public void update1(int pos, Double val){
        xyList1.set(pos, new BarChart.Data(xyList1.get(pos).getXValue(), val));

        // Workaround:
        xyList2.set(pos, new BarChart.Data(xyList2.get(pos).getXValue(),
                                           xyList2.get(pos).getYValue()));
    }

    public void update2(int pos, Double val){
        // Workaround:
        xyList1.set(pos, new BarChart.Data(xyList1.get(pos).getXValue(),
                                           xyList1.get(pos).getYValue()));

        xyList2.set(pos, new BarChart.Data(xyList2.get(pos).getXValue(), val));
    }
}

通过更新这两个系列,即使只有一个系列发生变化,它们都会被动画化,并保持它们的位置和它们之间的差距。

无论如何,请考虑向JIRA提交错误。

修改

向图表添加更高的值时,yAxis未正确更新。这是解决此问题的解决方法。

设置数据类型:

private class MyList {

    ObservableList<Record> dataList;
    ObservableList<BarChart.Data<String,Double>> xyList1;
    ObservableList<BarChart.Data<String,Double>> xyList2;
    ...
}

现在为图表设置一对轴(你有两对)。

对于yAxis设置自动范围为false,并设置正确的边界和tickUnit属性:

final CategoryAxis xAxis = new CategoryAxis();
xAxis.setLabel("Month");
xAxis.setCategories(FXCollections.<String> observableArrayList(monthLabels));

final NumberAxis yAxis = new NumberAxis();    
yAxis.setLabel("Values");
yAxis.setAutoRanging(false);

yAxis.setLowerBound(0d);
double max= Math.max(
         myList.xyList1.stream().mapToDouble(d->d.getYValue()).max().getAsDouble(),
         myList.xyList2.stream().mapToDouble(d->d.getYValue()).max().getAsDouble());
yAxis.setUpperBound(max);
yAxis.setTickUnit((yAxis.getUpperBound()-yAxis.getLowerBound())/10);

final BarChart<String,Number> barChart = new BarChart<>(xAxis,yAxis);

最后,您必须在数据发生任何变化后更新边界:

columnValue1.setOnEditCommit(t-> {
        t.getRowValue().setFieldValue1(t.getNewValue());

        myList.update1(t.getTablePosition().getRow(), t.getNewValue());
        yAxis.setUpperBound(Math.max(myList.xyList1.stream()
                  .mapToDouble(d->d.getYValue()).max().getAsDouble(),
                                     myList.xyList2.stream()
                  .mapToDouble(d->d.getYValue()).max().getAsDouble()));
        yAxis.setTickUnit((yAxis.getUpperBound()-yAxis.getLowerBound())/10);
    });
columnValue2.setOnEditCommit(t-> {
        t.getRowValue().setFieldValue2(t.getNewValue());

        myList.update2(t.getTablePosition().getRow(), t.getNewValue());
        yAxis.setUpperBound(Math.max(myList.xyList1.stream()
                  .mapToDouble(d->d.getYValue()).max().getAsDouble(),
                                     myList.xyList2.stream()
                  .mapToDouble(d->d.getYValue()).max().getAsDouble()));
        yAxis.setTickUnit((yAxis.getUpperBound()-yAxis.getLowerBound())/10);
    });