☰
SWT桌面图表选型:从JFreeChart迁移到SWTChart的实践指南
2026/10/16 10:03:22 网站建设 项目流程

简介:SWTChart是一款基于SWT的Java图表类库,面向需要在Eclipse RCP或SWT桌面应用中嵌入数据可视化的Java开发者,尤其适合追求轻量、无需额外依赖的图表绘制场景。它支持线图、散点图、堆栈图、柱形图、面积图、步骤图、对数标度、分类轴、多轴、轴取向与系列标签等多种图表类型,可满足常规统计与工程数据展示需求。资源包共36个文件,约287KB,以24个java源码文件为核心,另含2个jar包、2个project与classpath配置、2个html说明文档及gif图标、properties、mf、xml等辅助文件,目录中同时提供org.swtchart主库与examples示例工程,便于直接导入Eclipse运行调试。目前已有640人学习下载。通过阅读源码与示例,读者可掌握SWTChart的图表构建流程、坐标轴与系列配置方式,并借鉴其扩展接口实现自定义图表,适合作为学习SWT绘图或集成轻量图表组件的参考素材。

1. 桌面端图表选型:为什么我最终把 JFreeChart 换成了 SWTChart

做过 Eclipse RCP 或者纯 SWT 桌面应用的人大概都有过这个纠结:界面已经用 SWT 搭好了,到了要画折线、柱状、饼图的时候,第一反应往往是上 JFreeChart。JFreeChart 确实成熟,文档多,但它是基于 AWT/Swing 的,塞进 SWT 容器里要么用SWT_AWT.new_Frame桥接,要么干脆另开一个 Swing 窗口。桥接方案在 Windows 上勉强能跑,到了 Linux GTK 或者 macOS 上,轻则闪烁重绘异常,重则直接崩给你看,这种玄学问题排查起来非常折磨。

SWTChart 就是冲着这个痛点来的。它是一个纯 SWT 实现的图表类库,不依赖 AWT/Swing,直接继承Canvas用 GC 绘制,所以能无缝嵌进任何 SWT Composite 里,跟你的 RCP 视图、对话框、编辑器融为一体。它支持折线图、散点图、柱状图、面积图、饼图、气泡图这些常见类型,坐标轴、图例、网格线、标注、缩放、导出图片都有对应 API。适合谁?正在做 Eclipse 插件、桌面监控工具、工业上位机、测试数据可视化这类 SWT 技术栈的开发者。如果你用的是 JavaFX 或者 Web 前端,那这篇可以直接划走了。

2. 把 SWTChart 跑起来:依赖、坐标轴与数据系列

2.1 依赖引入与最小可运行骨架

SWTChart 的坐标是org.eclipse.swtchart,Maven Central 上有发布。我一般直接在pom.xml里引,版本按你项目实际用的 SWT 版本对齐,别乱跳大版本。

<dependency> <groupId>org.eclipse.swtchart</groupId> <artifactId>org.eclipse.swtchart</artifactId> <version>0.14.0</version> </dependency>

如果你不用 Maven,那就去项目发布页下 jar 包手动加到 classpath。注意它依赖 SWT,而 SWT 是跟平台绑定的,Windows、Linux、macOS 的 jar 不一样,别拿错。

最小骨架长这样:

import org.eclipse.swt.SWT; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.swtchart.Chart; import org.eclipse.swtchart.ILineSeries; import org.eclipse.swtchart.LineStyle; public class MinimalChart { public static void main(String[] args) { Display display = new Display(); Shell shell = new Shell(display); shell.setText("SWTChart Demo"); shell.setSize(600, 400); shell.setLayout(new FillLayout()); // 创建图表,风格用 FLAT,避免 3D 效果干扰读数 Chart chart = new Chart(shell, SWT.NONE); // 设置 X/Y 轴标题 chart.getAxisSet().getXAxis(0).getTitle().setText("采样点"); chart.getAxisSet().getYAxis(0).getTitle().setText("电压 (mV)"); // 创建一条折线系列 ILineSeries series = (ILineSeries) chart.getSeriesSet() .createSeries(ILineSeries.class); series.setYSeries(new double[]{12, 35, 28, 47, 52, 41, 60}); series.setLineStyle(LineStyle.SOLID); series.setLineWidth(2); // 自动调整坐标轴范围 chart.getAxisSet().adjustRange(); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } display.dispose(); } }

逻辑说明:Chart构造时传入父容器和样式,它本身是个Canvas子类,所以FillLayout能直接管它。createSeries用接口类型做参数,返回对应实现,这是 SWTChart 的一个设计特点——你声明什么接口,它就给你什么类型的系列。adjustRange()会根据数据自动算坐标轴上下限,省得手动设。

参数说明:setLineWidth(2)单位是像素,太细在高分屏上会糊。LineStyle.SOLID还有DASH、DOT、DASHDOT可选。setYSeries传的是double[],X 轴默认按索引 0、1、2… 排,如果你要自定义 X 值,得用setXSeries。

2.2 多系列、坐标轴与图例配置

实际项目里很少只有一条线。多系列叠加时,图例和颜色区分就很重要。

// 第二条系列,用虚线区分 ILineSeries series2 = (ILineSeries) chart.getSeriesSet() .createSeries(ILineSeries.class); series2.setYSeries(new double[]{8, 22, 30, 38, 44, 50, 55}); series2.setLineStyle(LineStyle.DASH); series2.setLineWidth(2); series2.setDescription("参考通道"); // 图例文字 // 第一条也补上图例文字 series.setDescription("实测通道"); // 图例位置:右下角 chart.getLegend().setPosition(SWT.BOTTOM); // 网格线 chart.getAxisSet().getXAxis(0).getGrid().setForeground( display.getSystemColor(SWT.COLOR_GRAY)); chart.getAxisSet().getYAxis(0).getGrid().setVisible(true);

逻辑说明:setDescription是图例显示的文字,不设的话图例里就是空白。setPosition接受 SWT 的方位常量,SWT.BOTTOM、SWT.TOP、SWT.LEFT、SWT.RIGHT都行。网格线默认是开的,但颜色可能跟背景撞,手动设一下更稳。

参数说明:getAxisSet().getXAxis(0)里的 0 是轴索引,多轴场景下会用到 1、2。SWTChart 支持多 Y 轴,比如左边一个量纲、右边一个量纲,做法是getYAxis(1)然后给系列setYAxisId(1)。这个功能在双通道对比时很实用,但要注意两个轴的范围得分别adjustRange,不然会互相干扰。

2.3 数据动态更新与重绘

监控类场景数据是实时来的,不能每次重建 Chart。正确做法是持有 series 引用,更新数据后调update()。

// 假设在某个定时器或事件回调里 double[] newData = fetchLatestSamples(); // 你的取数逻辑 series.setYSeries(newData); chart.getAxisSet().adjustRange(); // 数据范围变了才需要 chart.redraw(); // 触发重绘

逻辑说明:setYSeries会替换整个数组,不是追加。如果你要做滚动窗口,得自己维护一个固定长度的数组,每次移一位再塞新值。redraw()是必须的,SWT 不会自动感知数据变化。

参数说明:频繁调adjustRange()会导致坐标轴跳动,视觉上很难受。我一般只在数据首次加载或范围明显越界时才调,平时固定范围。如果数据量很大(比如上万点),setYSeries每次全量替换会有性能压力,常见做法是降采样或者只画窗口内的点。

3. 避坑与排查:SWTChart 实际项目里的五个翻车点

3.1 现象:图表一片空白,什么都不显示

原因:最常见的是Chart创建后没有触发adjustRange(),坐标轴范围是 0 到 0,数据全在可视区外。其次是父容器布局没设,Chart尺寸为 0。

解决:先确认shell.setLayout(new FillLayout())或者 GridLayout 给了 Chart 合理的GridData。然后检查是否调了adjustRange()。如果还不行,在shell.open()之后手动chart.redraw()一次。

3.2 现象:Linux 下字体发虚、中文显示成方块

原因:SWTChart 默认用系统字体,Linux 某些发行版缺中文字体或者 GC 的字体渲染跟 Windows 不一致。

解决:手动给轴标题和图例设字体。

Font font = new Font(display, "Noto Sans CJK SC", 10, SWT.NORMAL); chart.getAxisSet().getXAxis(0).getTitle().setFont(font); chart.getLegend().setFont(font);

注意Font用完要dispose(),否则会泄漏 GDI 资源。我一般把字体对象挂在 Shell 的dispose监听里统一释放。

3.3 现象:数据点超过几千个后界面卡顿

原因:SWTChart 默认每个点都画,GC 绘制开销随点数线性增长,加上redraw()全量重绘,卡是必然的。

解决:两条路。一是降采样,比如每 10 个点取一个;二是用ISeries的setVisible(false)把不需要的系列关掉。如果必须全画,考虑用ScatterSeries替代LineSeries,散点绘制比连线略快。另外别在 UI 线程里做大数据量的setYSeries,放到后台线程算好再display.asyncExec回 UI 更新。

3.4 现象:导出图片是黑的或者尺寸不对

原因:SWTChart 导出用chart.saveAsBitmap或自己拿 GC 画到Image,如果没指定背景色,透明区域在某些查看器里显示为黑。

解决:

chart.setBackground(display.getSystemColor(SWT.COLOR_WHITE)); chart.saveAsBitmap("/path/to/output.png", SWT.IMAGE_PNG, 800, 600);

宽高参数是像素,不传的话用当前控件尺寸。导出前确保chart.redraw()过,否则可能拿到旧画面。

3.5 现象:多 Y 轴时数据画到了错误的轴上

原因:创建系列后忘了setYAxisId,默认全挂在 0 号轴上。

解决:先chart.getAxisSet().createYAxis()拿到新轴,再给对应系列series.setYAxisId(newAxis.getId())。顺序不能反,轴 ID 是创建时分配的。这个坑我在一个双通道采集项目里踩过,两条曲线叠在一起,排查了半天才发现是轴 ID 没设。

4. 进阶技巧:自定义绘制与交互联动

4.1 用 PaintListener 叠加自定义标注

SWTChart 本身提供了series.setLabel做数据点标注,但样式有限。如果要画阈值线、区域高亮、十字准星这类东西,直接给 Chart 加PaintListener更灵活。

chart.addPaintListener(e -> { // 在 Y=50 处画一条红色阈值线 Point origin = chart.getPlotArea().getBounds().x > 0 ? new Point(chart.getPlotArea().getBounds().x, 0) : new Point(0, 0); Rectangle plot = chart.getPlotArea().getBounds(); int y = plot.y + plot.height - (int) ((50 - chart.getAxisSet().getYAxis(0).getRange().lower) / (chart.getAxisSet().getYAxis(0).getRange().upper - chart.getAxisSet().getYAxis(0).getRange().lower) * plot.height); e.gc.setForeground(display.getSystemColor(SWT.COLOR_RED)); e.gc.setLineStyle(SWT.LINE_DASH); e.gc.drawLine(plot.x, y, plot.x + plot.width, y); });

逻辑说明:getPlotArea().getBounds()拿到绘图区矩形,然后把数据值 50 按当前 Y 轴范围映射成像素坐标。PaintListener在 Chart 自身绘制完成后触发,所以你的线会叠在图表上面。

参数说明:getRange().lower和upper是当前轴范围,如果调了adjustRange它们会变,所以这段映射逻辑得放在监听器里实时算,不能缓存。阈值线的位置随缩放变化是符合预期的。

4.2 鼠标交互:获取点击位置对应的数据值

做数据回放或者点选查询时,需要把鼠标坐标反算成数据值。

chart.addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { Rectangle plot = chart.getPlotArea().getBounds(); if (!plot.contains(e.x, e.y)) return; double xRatio = (double) (e.x - plot.x) / plot.width; double yRatio = (double) (plot.y + plot.height - e.y) / plot.height; double xVal = chart.getAxisSet().getXAxis(0).getRange().lower + xRatio * (chart.getAxisSet().getXAxis(0).getRange().upper - chart.getAxisSet().getXAxis(0).getRange().lower); double yVal = chart.getAxisSet().getYAxis(0).getRange().lower + yRatio * (chart.getAxisSet().getYAxis(0).getRange().upper - chart.getAxisSet().getYAxis(0).getRange().lower); System.out.printf("点击数据坐标: (%.2f, %.2f)%n", xVal, yVal); } });

逻辑说明:X 方向是线性的,直接按比例映射。Y 方向要注意屏幕坐标向下增长,而数据坐标向上增长,所以用plot.y + plot.height - e.y翻转一下。

参数说明:plot.contains判断点击是否落在绘图区内,避免点到轴标签也触发。这个反算假设轴是线性的,如果用了对数轴,得换成对数映射,SWTChart 的LogAxis有对应的转换方法。

4.3 一个我常用的验证习惯

每次改完图表配置,我不会只看界面,而是先跑一遍导出,把 PNG 存下来用图片查看器放大看细节。因为屏幕上有些问题(比如线宽 1 像素的锯齿、图例文字被截断、网格线跟数据线颜色太近)在运行时窗口里不明显,导出成固定尺寸的图之后一眼就能看出来。这个习惯帮我提前发现过好几次图例遮挡数据、轴标签重叠的问题。

从那以后我每次调完 SWTChart 的样式参数,都强制走一遍「导出 800x600 PNG → 放大检查 → 再调」的流程,比在窗口里反复拖拽靠谱得多。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询