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