ChartFx 是一个由 GSI 为 FAIR 开发的科学绘图库,专注于性能优化的实时数据可视化,可以处理每秒 25 次更新率的数据集,数据点数量从几万到 500 万不等,常用于数字信号处理应用。 基于早期在 GSI 和 CERN 使用的基于 Swing 的设计,它是对 JavaFX 默认 Chart 实现的重写,旨在保留早期和其他类似基于 Swing 库的丰富功能和可扩展性,同时解决性能瓶颈和 API 问题。 重新设计的动机已在 IPAC'19 上展示(论文,海报)。您可以在 JFX Days 上观看最近的演示 这里。
功能和特性
该库提供了科学信号处理领域常见的各种绘图类型、灵活的插件系统以及实验室仪器中常见的在线参数测量。其一些特性包括(详情请参见演示):
DataSet
:基本的 XY 类型数据集,可通过DataSetError
扩展以考虑测量不确定性,DataSetMetaData
、EditableDataSet
、Histogram
或DataSet3D
接口;- 数学子库:FFT、小波和其他谱分析和线性代数例程,数值稳健的积分和微分,IIR 和 FIR 类型滤波,线性回归和非线性 chi-square 类型函数拟合;
Chart
:提供欧几里得、极坐标或 3D 数据集的 2D 投影,以及可配置的图例;Axis
:一个或多个线性、对数、时间序列、反转、动态自动(增长)范围、基于范围的自动 SI 和时间单位转换的轴;Renderer
:散点图、折线图、面积图、误差条和误差面、垂直条形图、贝塞尔曲线、阶梯图、1D/2D 直方图、山脉图显示、真实等高线图、热图、淡出数据集历史、标记图表范围和指示标记、六边形图、元数据(例如用于指示常见测量错误、警告或信息,如超出或低于范围、设备或配置错误等);ChartPlugin
:带历史记录的数据缩放器、缩放至原点、并可选择限制为 X 和/或 Y 坐标、平移器、数据值和范围指示器、十字光标指示器、数据点工具提示、DataSet
编辑、表格视图、导出为 CSV 和系统剪贴板、在线轴编辑、数据集参数测量(如上升时间、最小值、最大值、均方根等)。
为了在使用 Canvas
作为图形后端的同时提供一些场景图级别的功能,每个模块的功能都经过扩展,可以通过直接的 API 方法以及外部 CSS 类型样式表轻松自定义。
使用示例
将库添加到您的项目中
所有 chart-fx 版本都部署到 Maven 中央仓库,对于 Maven,您可以将其添加到 pom.xml 中,如下所示:
<dependencies>
<dependency>
<groupId>io.fair-acc</groupId>
<artifactId>chartfx</artifactId>
<version>11.3.1</version>
</dependency>
</dependencies>
或者在 build.gradle 中这样添加:
implementation 'io.fair-acc:chartfx:11.3.1'
要使用不同的构建系统或库版本,请查看 maven central 上的代码片段。
虽然大多数用户需要 io.fair-acc:chartfx
构件,但也可以独立使用来自 io.fair-acc:dataset
的数据容器和来自 io.fair-acc:math
的算法,而无需依赖较重的 UI 组件。
使用快照仓库
如果您想尝试主分支或某个特性分支中未发布的功能,无需下载源代码并自行构建 chart-fx。您可以直接使用 sonatype 快照仓库中的 <分支名>-SNAPSHOT
版本,例如通过在 pom.xml 中添加以下内容来使用当前的主分支。
所有可用的快照版本都可以在 sonatype 快照仓库 中找到。
当前主分支的 pom.xml 示例(点击展开)
<dependencies>
<dependency>
<groupId>io.fair-acc.chartfx</groupId>
<artifactId>chartfx</artifactId>
<version>main-SNAPSHOT</version>
<!-- <version>main-20200320.180638-78</version> 固定到特定的快照构建-->
</dependency>
</dependencies>
<repositories>
<repository>
<id>oss.sonatype.org-snapshot</id>
<url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
代码示例
以下最小工作示例可用作入门 chart-fx 的样板项目。
相应的源代码 `ChartFxSample.java`(展开)
package com.example.chartfx;
import io.fair_acc.chartfx.XYChart;
import io.fair_acc.chartfx.axes.spi.DefaultNumericAxis;
import io.fair_acc.dataset.spi.DoubleDataSet;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class SimpleChartSample extends Application {
private static final int N_SAMPLES = 100;
@Override
public void start(final Stage primaryStage) {
final StackPane root = new StackPane();
final XYChart chart = new XYChart(new DefaultNumericAxis(), new DefaultNumericAxis());
root.getChildren().add(chart);
final DoubleDataSet dataSet1 = new DoubleDataSet("数据集 #1");
final DoubleDataSet dataSet2 = new DoubleDataSet("数据集 #2");
// lineChartPlot.getDatasets().add(dataSet1); // 用于单个数据集
chart.getDatasets().addAll(dataSet1, dataSet2); // 两个数据集
final double[] xValues = new double[N_SAMPLES];
final double[] yValues1 = new double[N_SAMPLES];
final double[] yValues2 = new double[N_SAMPLES];
for (int n = 0; n < N_SAMPLES; n++) {
xValues[n] = n;
yValues1[n] = Math.cos(Math.toRadians(10.0 * n));
yValues2[n] = Math.sin(Math.toRadians(10.0 * n));
}
dataSet1.set(xValues, yValues1);
dataSet2.set(xValues, yValues2);
final Scene scene = new Scene(root, 800, 600);
}
public static void main(final String[] args) {
Application.launch(args);
}
}
对应的构建规范(展开)
pom.xml:<project>
<groupId>com.example.chartfx</groupId>
<artifactId>chartfx-sample</artifactId>
<name>chart-fx 示例</name>
<dependencies>
<dependency>
<groupId>io.fair-acc</groupId>
<artifactId>chartfx</artifactId>
<version>11.3.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.9</version>
</dependency>
</dependencies>
</project>
运行方式(展开)
mvn compile install
mvn exec:java
示例
chart-fx 示例子模块包含大量示例,展示了该库的功能和用法。 如果你想自己尝试,请运行:
mvn compile install
mvn exec:java
金融相关示例
金融图表是一种可视化追踪各种业务和金融指标(如流动性、价格变动、支出、现金流等)随时间变化的图表类型。金融图表是表达业务或金融市场(金融工具、金融资产)故事的绝佳方式。
chart-fx samples子模块包含金融图表和工具箱示例。
如果你想自己尝试,请运行:
mvn compile install
mvn exec:java
金融足迹图
数学和信号处理相关示例
数学示例可以通过以下命令启动:
mvn compile install
mvn exec:java@math
其他示例
还有dataset和accelerator UI子模块的示例,随着新功能的添加,这些示例将随时间扩展。
mvn compile install
mvn exec:java@dataset
mvn exec:java@acc-ui
性能比较
除了上述扩展功能外,ChartFx的优化目标还包括实现数据集从几万到500万个数据点的25 Hz实时更新率。为了优化性能并与其他图表库(尤其是功能较少的库)进行比较,我们选择了一个简化的示波器风格测试用例(参见演示中的RollingBufferSample
),该用例显示两条具有独立自动调整y轴、共同滑动时间序列轴的曲线,且不使用其他ChartPlugin
。下面展示了ChartFx和JavaFX图表库在25 Hz和2 Hz更新率下的测试用例和直接性能比较。
虽然ChartFx实现已经在功能上有所改进,并且在处理超大数据集时性能提升了两个数量级,但基本测试场景也与流行的现有Java-Swing和非Java基础UI图表框架进行了对比。下图总结了在25 Hz更新率和1k样本条件下评估的图表库。
一些思考
从最初通过扩展来改进JDK的JavaFX图表功能和性能开始,然后逐步替换瓶颈,最终重新设计并替换原始实现,由此产生的ChartFx库提供了实质性的更大功能,并实现了约两个数量级的性能提升。 然而,撇开功能改进不谈,即使是JavaFX最佳场景(静态轴)与其他非JavaFX库的直接性能对比也表明,尽管经过重新设计,JavaFX原始图形性能仍落后于现有的基于Java Swing的JDataViewer,尤其是Qt Charts实现。该库将继续在GitHub上维护,并用于GSI现有和未来基于JavaFX的控制室UI。 获得的经验和接口将为计划中的基于C++的对应实现提供起点,该实现将使用Qt或其他合适的低级图表库。
处理源代码
如果你想处理chart-fx源代码,无论是想尝试样例还是为chartFX贡献一些改进,这里有一些说明,介绍如何获取源代码并使用命令行maven或eclipse进行编译。
命令行Maven
只需克隆仓库并从顶级目录运行maven。exec:java
目标可用于执行样例。
Maven使用相应的选项调用java,以使JavaFX正常工作。由于项目的设置方式,只有chartfx-samples项目中的类可以通过这种方式启动。
git clone
cd chart-fx
mvn compile install
mvn exec:java
Eclipse
以下内容已在eclipse-2019-03上测试,并使用m2e maven插件。其他版本或IDE可能类似工作。
使用Import -> Existing Maven Project
导入仓库。
这应该会导入父项目和四个子项目。
不幸的是,由于chartfx不使用jigsaw模块系统,而javafx使用,使用"run as Java Application"运行样例将导致错误,抱怨缺少JavaFX运行时。
作为解决方法,我们包含了一个小型辅助类LaunchJFX
,可以用"run as Java Application"调用,它会启动样例应用。
它接受类名作为参数,所以如果你编辑运行配置并将${java_type_name}
作为参数,它将尝试启动在项目资源管理器中选择的类作为JavaFX应用。
JavaFX jvm命令行选项
如果你无法使用前两种方法,也可以手动指定模块系统的访问规则作为jvm标志。将以下内容添加到java命令行调用或IDE的运行配置中,使所需模块可用并可被chartfx访问:
--add-modules=javafx.graphics,javafx.fxml,javafx.media
--add-reads javafx.graphics=ALL-UNNAMED
--add-opens javafx.controls/com.sun.javafx.charts=ALL-UNNAMED
--add-opens javafx.controls/com.sun.javafx.scene.control.inputmap=ALL-UNNAMED
--add-opens javafx.graphics/com.sun.javafx.iio=ALL-UNNAMED
--add-opens javafx.graphics/com.sun.javafx.iio.common=ALL-UNNAMED
--add-opens javafx.graphics/com.sun.javafx.css=ALL-UNNAMED
--add-opens javafx.base/com.sun.javafx.runtime=ALL-UNNAMED
--add-exports javafx.controls/com.sun.javafx.scene.control.behavior=ALL-UNNAMED
由于这些参数可能会随依赖项更新而改变,并根据项目设置方式的不同,如果遇到模块可访问性问题,请查看以下资源:
扩展chartfx
如果你发现缺少某些功能或无法访问特定的图表内部,通常的做法是实现自定义插件或渲染器。
插件是向chart-fx添加新可视化和交互功能的简单方法。实际上,chart-fx的许多功能(如缩放、数据编辑、测量)都是作为插件实现的,正如你在样例应用中看到的那样。
你的插件可以直接扩展ChartPlugin或扩展任何内置插件。
Plugin Base类通过getChart()
提供对图表对象的访问。
你的插件应始终向chartProperty添加监听器,因为创建时不会有关联的图表,所以在创建时调用例如getChart()
将返回null。
使用自定义插件归结为通过chart.getPlugins().add(new MyPlugin())
将其添加到图表中。
如果你编写的插件可能对chart-fx的其他用户有用,请考虑对chart-fx进行pull请求。
渲染器是实际执行将图表组件绘制到画布上的重要组件。
可以使用chart.getRenderers().add(...)
向图表添加多个渲染器
有些渲染器可视化实际数据,如ErrorDataSetRenderer
,它也是默认添加到新图表的渲染器。
这些渲染器对添加到图表的所有DatasSets(chart.getDatasets.add(...)
)以及添加到渲染器本身的数据集进行操作。
根据经验,如果你需要可视化大量数据点或想在图表本身后面绘制内容,就需要实现自定义渲染器。
致谢
我们向JavaFX社区表示感谢,特别是CERN的@GregKrug和Vito Baggiolini,感谢他们在这个主题上提供的宝贵见解、讨论和反馈。