2014-10-09 183 views
1

我想創建一個包含多個系列的條形圖並從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在JavaFX上動態更新BarChart多個系列

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

    } 

回答

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提交一個bug。

EDIT

當添加到圖表較高的值,則Y軸不正確地更新。這是解決這個問題的解決方法。

集的數據的類型:

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

嗨,你的方法作品真棒。我想它必須檢測圖表中的現有系列以保持差距。但是,現在還有另一個問題。如果我嘗試更新任何超出yAxis上限的值(例如上限爲350,並將值更改爲400),則圖表可以正確顯示兩個系列的比例,但yAxis不會得到相應更新。它只會在我第二次嘗試輸入相同的值時更新。有什麼解決方案可以在值更改時更新yAxis? – 2014-10-10 04:09:41

+0

我編輯了我的答案以包含所需的更改。 – 2014-10-10 13:27:58

1

您的代碼基於的鏈接非常過時,因此代碼對它有着相當「遺留代碼」的感覺。您可以利用很多新的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); 
    } 


}