在我的项目中,我创建了多个AreaCharts,如下所示:
事物是显示的图表的最大数量是10,但X轴的长度可能不同。在每张图表中,我使用的是4个系列:
问题是当图表的xAxis长度达到大约1200时,滚动scrollPane根本不平滑而且几乎不滚动。
我的问题是:有没有办法让我的图表显示效率更高?
编辑:布局层次结构如下所示:
滚动窗格(垂直框(图表))
ETA: 这是独立版本,可以显示平均效率,图表中显示的值更高。 注意:运行此程序可能需要几秒钟才能显示图表。我怎样才能使这些视图滚动得更顺畅呢?
计划类:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.AreaChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Program2 extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
try {
ScrollPane scrollPane = new ScrollPane();
VBox vBox = new VBox();
scrollPane.setContent(vBox);
Scene scene = new Scene(scrollPane, 600, 600);
primaryStage.setMaximized(true);
primaryStage.setScene(scene);
primaryStage.show();
for (int i = 0; i < 10; i++) {
CustomChart2 customChart2 = new CustomChart2();
AreaChart.Series<Number, Number> series = new AreaChart.Series<>();
for (int j = 0; j < 2500; j += i + 2) {
series.getData().add(new XYChart.Data<>(j, -0.2));
series.getData().add(new XYChart.Data<>(j, 1));
series.getData().add(new XYChart.Data<>(j + 1, 1));
series.getData().add(new XYChart.Data<>(j + 1, -0.2));
}
customChart2.getData().add(series);
AreaChart.Series<Number, Number> series2 = new AreaChart.Series<>();
for (int j = 0; j < 2500; j += i + 1) {
series2.getData().add(new XYChart.Data<>(j, -0.2));
series2.getData().add(new XYChart.Data<>(j, 1.5));
series2.getData().add(new XYChart.Data<>(j + 0.01, 1.5));
series2.getData().add(new XYChart.Data<>(j + 0.01, -0.2));
}
customChart2.getData().add(series2);
AreaChart.Series<Number, Number> series3 = new AreaChart.Series<>();
for (int j = 2; j < 2500; j += i + 1) {
series3.getData().add(new XYChart.Data<>(j, -0.2));
series3.getData().add(new XYChart.Data<>(j, 1.2));
series3.getData().add(new XYChart.Data<>(j + 0.01, 1.2));
series3.getData().add(new XYChart.Data<>(j + 0.01, -0.2));
}
customChart2.getData().add(series3);
vBox.getChildren().add(customChart2);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
修改过的图表类:
import javafx.scene.chart.AreaChart;
import javafx.scene.chart.NumberAxis;
public class CustomChart2 extends AreaChart<Number, Number> {
private NumberAxis xAxis;
private NumberAxis yAxis;
public CustomChart2() {
super(new NumberAxis(0, 2500, 5), new NumberAxis(0, 1.5, 1.5));
this.xAxis = (NumberAxis) getXAxis();
this.yAxis = (NumberAxis) getYAxis();
configure();
configureXAxis();
configureYAxis();
}
private void configure() {
setAnimated(false);
setCreateSymbols(false);
setMinWidth(2500 * 20);
computeMaxHeight(60);
setMaxHeight(60);
maxHeight(60);
setMinHeight(60);
setHorizontalGridLinesVisible(false);
setVerticalGridLinesVisible(false);
setLegendVisible(false);
setVerticalZeroLineVisible(false);
setHorizontalZeroLineVisible(false);
}
private void configureXAxis() {
xAxis.setAutoRanging(false);
}
private void configureYAxis() {
yAxis.setPrefSize(0, 0);
yAxis.setAutoRanging(false);
yAxis.setTickMarkVisible(false);
yAxis.setTickLabelsVisible(false);
yAxis.setMinorTickVisible(false);
}
}
答案 0 :(得分:4)
你提到的性能问题有点奇怪:图像看起来并不太复杂。没有看到mcve就很难提供任何建议。
如果您生成的节点太多(数万个)并且无法减少该数量,那么您可以使用以下选项:
其他general performance tips(例如启用节点缓存)可能有助于提高性能。
虚拟网格似乎是我的问题的解决方案,但我没有看到水平滚动的任何选项所以在这种情况下,它是无用的
我从未使用过ControlsFX GridView,但我的猜测是它与其他虚拟化控件(如ListView或TableView)的工作原理相同,它会在必要时隐式显示适当的滚动(例如,如果可用高度&gt; =首选高度并且可用宽度&lt;首选宽度,则不显示垂直滚动,但将显示水平滚动)。因为滚动行为和显示是隐式的,所以GridView的控件API上不需要任何选项。只是一个猜测。
更新
我尝试了GridView,不幸的是,它并不像我想的那样简单,让水平滚动工作。隐式它只是垂直滚动,如果有一个API来使水平滚动与当前实现一起工作(我怀疑),它没有记录,并且使用起来不直观。添加或记录功能会对ControlsFX提出一个很好的feature request请求,但这对你现在没有帮助...所以也许坚持我有的其他性能优化建议,或者想出更多你的拥有或来自另一个回答者。
使用示例代码进行更新
所以我有点攻击了ControlsFX GridView控件,允许它使用水平滚动条。可能我做的方式不是它应该做的方式,但似乎有效。
无论如何,这里有一个示例,它在虚拟网格视图中显示超过9000个图表。 ChartGridCell和unpad-chart.css是从How to make the chart content area take up the maximum area available to it?的答案派生而来的,并没有真正添加任何感兴趣的东西,除了一些测试代码和数据(它们不是解决方案的真正组成部分)。
示例代码对每个图表使用相同的系列数据,但您可以将系列数据放在网格视图项列表中,并在项目更新时更新图表(类似于图表标题和颜色的更新方式)提供的样本)。
ManyChartsSample.java
import impl.org.controlsfx.skin.FixedWidthGridViewSkin;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import org.controlsfx.control.GridView;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.Random;
public class ManyChartsSample extends Application {
private static final double FIXED_GRID_WIDTH = 3_000;
private static final int OVER_9000 = 9001;
private static final Random r = new Random(42);
@Override
public void start(Stage stage) throws URISyntaxException, MalformedURLException {
GridView<ChartGridCell.CellData> grid = new GridView<>();
grid.setSkin(new FixedWidthGridViewSkin<>(grid, FIXED_GRID_WIDTH));
grid.setCellFactory(gridView -> new ChartGridCell());
grid.setHorizontalCellSpacing(5);
grid.setVerticalCellSpacing(5);
grid.setCellHeight(200);
grid.setCellWidth(200);
for(int i = 0; i < OVER_9000; i++) {
Color c1 = new Color(r.nextDouble(), r.nextDouble(), r.nextDouble(), 1.0);
Color c2 = new Color(r.nextDouble(), r.nextDouble(), r.nextDouble(), 1.0);
ChartGridCell.CellData data = new ChartGridCell.CellData(i, c1, c2);
grid.getItems().add(data);
}
grid.setPrefSize(300, 200);
Scene scene = new Scene(grid);
scene.getStylesheets().add(
getClass().getResource("unpad-chart.css").toURI().toURL().toExternalForm()
);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
impl.org.controlsfx.skin.FixedWidthGridViewSkin
package impl.org.controlsfx.skin;
import org.controlsfx.control.GridView;
public class FixedWidthGridViewSkin<T> extends GridViewSkin<T> {
private final double fixedWidth;
public FixedWidthGridViewSkin(GridView<T> control, double fixedWidth) {
super(control);
this.fixedWidth = fixedWidth;
}
@Override public GridRow<T> createCell() {
GridRow<T> row = new GridRow<>();
row.setPrefWidth(fixedWidth);
row.updateGridView(getSkinnable());
return row;
}
protected double computeRowWidth() {
return Math.max(getSkinnable().getWidth() - 18, fixedWidth);
}
}
ChartGridCell.java
import javafx.scene.chart.AreaChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.paint.Color;
import org.controlsfx.control.GridCell;
public class ChartGridCell extends GridCell<ChartGridCell.CellData> {
private final AreaChart<Number,Number> chart;
public static class CellData {
private final int idx;
private final Color c1;
private final Color c2;
private final Color c1t;
private final Color c2t;
public CellData(int idx, Color c1, Color c2) {
this.idx = idx;
this.c1 = c1;
this.c2 = c2;
c1t = new Color(c1.getRed(), c1.getGreen(), c1.getBlue(), 0.2);
c2t = new Color(c2.getRed(), c2.getGreen(), c2.getBlue(), 0.2);
}
public int getIdx() {
return idx;
}
public Color getC1() {
return c1;
}
public Color getC2() {
return c2;
}
public Color getC1t() {
return c1t;
}
public Color getC2t() {
return c2t;
}
}
/**
* Creates a default ColorGridCell instance.
*/
public ChartGridCell() {
getStyleClass().add("chart-grid-cell"); //$NON-NLS-1$
final NumberAxis xAxis = new NumberAxis(1, 31, 1);
final NumberAxis yAxis = new NumberAxis(0, 28, 1);
xAxis.setAutoRanging(false);
xAxis.setMinorTickVisible(false);
xAxis.setTickMarkVisible(false);
xAxis.setTickLabelsVisible(false);
xAxis.setPrefSize(0, 0);
yAxis.setAutoRanging(false);
yAxis.setMinorTickVisible(false);
yAxis.setTickMarkVisible(false);
yAxis.setTickLabelsVisible(false);
yAxis.setPrefSize(0, 0);
chart = new AreaChart<>(xAxis,yAxis);
chart.setHorizontalGridLinesVisible(false);
chart.setVerticalGridLinesVisible(false);
chart.setLegendVisible(false);
chart.setVerticalZeroLineVisible(false);
chart.setHorizontalZeroLineVisible(false);
chart.setAnimated(false);
XYChart.Series seriesApril= new XYChart.Series();
seriesApril.setName("April");
seriesApril.getData().add(new XYChart.Data(1, 4));
seriesApril.getData().add(new XYChart.Data(3, 10));
seriesApril.getData().add(new XYChart.Data(6, 15));
seriesApril.getData().add(new XYChart.Data(9, 8));
seriesApril.getData().add(new XYChart.Data(12, 5));
seriesApril.getData().add(new XYChart.Data(15, 18));
seriesApril.getData().add(new XYChart.Data(18, 15));
seriesApril.getData().add(new XYChart.Data(21, 13));
seriesApril.getData().add(new XYChart.Data(24, 19));
seriesApril.getData().add(new XYChart.Data(27, 21));
seriesApril.getData().add(new XYChart.Data(30, 21));
seriesApril.getData().add(new XYChart.Data(31, 19));
XYChart.Series seriesMay = new XYChart.Series();
seriesMay.setName("May");
seriesMay.getData().add(new XYChart.Data(1, 20));
seriesMay.getData().add(new XYChart.Data(3, 15));
seriesMay.getData().add(new XYChart.Data(6, 13));
seriesMay.getData().add(new XYChart.Data(9, 12));
seriesMay.getData().add(new XYChart.Data(12, 14));
seriesMay.getData().add(new XYChart.Data(15, 18));
seriesMay.getData().add(new XYChart.Data(18, 25));
seriesMay.getData().add(new XYChart.Data(21, 25));
seriesMay.getData().add(new XYChart.Data(24, 23));
seriesMay.getData().add(new XYChart.Data(27, 26));
seriesMay.getData().add(new XYChart.Data(31, 26));
chart.getData().addAll(seriesApril, seriesMay);
}
@Override protected void updateItem(ChartGridCell.CellData item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
chart.setTitle(item.getIdx() + ": " + toRGBCode(item.getC1()));
chart.setStyle(
"CHART_COLOR_1: " + toRGBCode(item.getC1()) + "; " +
"CHART_COLOR_1_TRANS_20: " + toRGBCode(item.getC1t()) + ";" +
"CHART_COLOR_2: " + toRGBCode(item.getC2()) + "; " +
"CHART_COLOR_2_TRANS_20: " + toRGBCode(item.getC2t()) + ";"
);
setGraphic(chart);
}
}
private String toRGBCode( Color color ) {
return String.format( "#%02X%02X%02X%02X",
(int)( color.getRed() * 255 ),
(int)( color.getGreen() * 255 ),
(int)( color.getBlue() * 255 ),
(int)( color.getOpacity() * 255 )
);
}
}
unpad-chart.css
.chart {
-fx-padding: 0px;
}
.chart-content {
-fx-padding: 0px;
}
.axis {
AXIS_COLOR: transparent;
}
.axis:top > .axis-label,
.axis:left > .axis-label {
-fx-padding: 0;
}
.axis:bottom > .axis-label,
.axis:right > .axis-label {
-fx-padding: 0;
}
答案 1 :(得分:2)
执行此操作的最佳方法是使用JFreeChart
。这与您创建图形的代码一样快速,我只使用了您所使用的相同值,然后在图像中缓冲图形。这意味着您正在渲染图形的图像而不是实际的图形本身。因此,您不要一次性定位数千个数据点,而只是定位图像。
根据你的例子,这是我做的一个例子。
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Pos;
import javafx.scene.CacheHint;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Program2 extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
try {
StackPane p = new StackPane();
primaryStage.setTitle("Chart Application");
Label loader = new Label("Loading...");
loader.setGraphic(new ImageView(new Image("https://media.giphy.com/media/FmcNeI0PnsAKs/giphy.gif")));
loader.setFont(new javafx.scene.text.Font(35));
p.setStyle("-fx-background: #FFFFFF;");
p.getChildren().add(loader);
StackPane.setAlignment(loader, Pos.CENTER);
Scene scene = new Scene(p, 600, 600);
primaryStage.setScene(scene);
primaryStage.setMaximized(true);
Task<ArrayList<ImageView>> loadInitial = new Task<ArrayList<ImageView>>() {
@Override
public ArrayList<ImageView> call() {
ArrayList<ImageView> images = new ArrayList<ImageView>();
for (int i = 0; i < 10; i++) {
XYSeries data = new XYSeries(1);
XYSeries data2 = new XYSeries(2);
XYSeries data3 = new XYSeries(3);
System.out.println("Calcuating values for graph" + (i + 1));
for (int j = 0; j < 2500; j += i + 2) {
data.add(j, -0.2);
data.add(j, 1);
data.add(j + 1, 1);
data.add(j + 1, -0.2);
}
for (int j = 0; j < 2500; j += i + 1) {
data2.add(j, -0.2);
data2.add(j, 1.5);
data2.add(j + 0.01, 1.5);
data2.add(j + 0.01, -0.2);
}
for (int j = 2; j < 2500; j += i + 1) {
data3.add(j, -0.2);
data3.add(j, 1.2);
data3.add(j + 0.01, 1.2);
data3.add(j + 0.01, -0.2);
}
System.out.println("Finished values for graph" + (i + 1));
XYSeriesCollection dataset = new XYSeriesCollection();
dataset.addSeries(data);
dataset.addSeries(data2);
dataset.addSeries(data3);
JFreeChart chart = ChartFactory.createXYAreaChart("", "", "", dataset, PlotOrientation.VERTICAL, false, false, false);
chart.setBackgroundPaint(Color.WHITE);
chart.setBorderVisible(false);
chart.setAntiAlias(true);
XYPlot plot = (XYPlot) chart.getPlot();
ValueAxis range = plot.getRangeAxis();
range.setLowerMargin(0);
range.setUpperMargin(0);
range.setVisible(false);
ValueAxis domainAxis = plot.getDomainAxis();
domainAxis.setLowerMargin(0);
domainAxis.setUpperMargin(0);
double maxX = 0;
for(Object temp : dataset.getSeries()) {
double max = ((XYSeries) temp).getMaxX();
if(maxX < max)
maxX = max;
}
Long L = Math.round(maxX);
int maxVal = Integer.valueOf(L.intValue());
int width = maxVal * 16; //Works out length to nice scale.
//If you want all values to be the same use 40,000 (commented out)
if(width > 250000)
width = 250000; //Lags out after this
System.out.println("Buffering graph" + (i + 1));
BufferedImage capture = chart.createBufferedImage(width, 50);
//BufferedImage capture = chart.createBufferedImage(40000, 50);
System.out.println("Finished buffering graph" + (i + 1));
ImageView imageView = new ImageView();
Image chartImg = SwingFXUtils.toFXImage(capture, null);
imageView.setImage(chartImg);
imageView.setCache(true);
imageView.setCacheHint(CacheHint.SPEED);
images.add(imageView);
}
System.out.println("Finished all processes. Loading graphs");
return images;
}
};
loadInitial.setOnSucceeded(e -> {
VBox images = new VBox();
ArrayList<ImageView> result = loadInitial.getValue();
for(ImageView image : result) {
images.getChildren().add(image);
}
ScrollPane scrollPane = new ScrollPane(images);
scrollPane.setStyle("-fx-background: #FFFFFF;");
scene.setRoot(scrollPane);
});
new Thread(loadInitial).start();
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
}
我还添加了一个加载屏幕,以便应用程序在加载数据时没有抓住。
依赖项为jcommon-1.0.23.jar
和jfreechart-1.0.19.jar
。 Download here if you wish或main download place
编辑
值得注意的是,因为您使用的是JavaFX
,所以有一个类允许您查看具有更多实用程序的图表,它只是赢了&#39 ; t看起来像你发布的内容。这称为ChartViewer
。这将允许您复制图形或放大和缩小等...
唯一的问题是您需要将jfreechart-1.0.19.jar
替换为jfreechart-1.0.19-fx.jar
并执行此操作,您需要构建jfreechart-1.0.19-fx.jar
。如果你之前从未使用过蚂蚁,那就不那么容易了。因此,如果您希望使用此功能,您可以在官方build-fx.xml
下载的ant文件夹中构建JFreeChart
,或者您可以使用我构建并放置在此google drive文件夹中的文件。