【问题标题】:Update BarChart with multiple series dynamically on JavaFX在 JavaFX 上使用多个系列动态更新 BarChart
【发布时间】:2014-12-04 10:54:45
【问题描述】:

我想创建一个包含多个系列的条形图并从 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();
    }
        }

    }

【问题讨论】:

    标签: java bar-chart javafx-8


    【解决方案1】:

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

    只需设置:

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

    【讨论】:

    • 嗨,你的方法效果很好。我想它必须检测图表中的现有系列才能保持差距。但是,现在还有另一个问题。如果我尝试更新任何超过 yAxis 上限的值(例如上限为 350,我将值更改为 400),图表能够正确显示这两个系列的比例,但 yAxis 没有得到相应更新。只有当我第二次尝试输入相同的值时它才会更新。当值发生变化时,是否有任何解决方案可以更新 yAxis?
    • 我已经编辑了我的答案以包含所需的更改。
    【解决方案2】:

    您的代码所基于的链接已经过时,因此该代码给人一种“遗留代码”的感觉。您可以利用许多新的 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);
        }
    
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-07-05
      • 1970-01-01
      • 1970-01-01
      • 2018-02-02
      相关资源
      最近更新 更多