
简介SWTChart是一款基于SWT的Java图表类库面向需要在Eclipse RCP或SWT桌面应用中嵌入数据可视化的Java开发者尤其适合追求轻量、无需额外依赖的图表绘制场景。它支持线图、散点图、柱形图、面积图、堆栈图、步骤图、对数标度、分类轴、多轴、轴取向与系列标签等多种图表类型可满足常规统计与工程数据展示需求。资源包共36个文件约287KB以24个java源码文件为核心辅以2个jar库、2个project与classpath配置、2个html说明文档及gif图标、properties、mf、xml等元数据结构上区分了主库、扩展库与示例工程便于直接导入Eclipse运行调试。目前已有640人学习下载。通过阅读源码与示例读者可掌握SWTChart的图表构建流程、坐标轴与系列配置方式并借鉴其扩展模块实现自定义图表适合作为SWT图表开发的入门与二次开发参考。1. 桌面端图表选型为什么我最终把 SWTChart 塞进了工具箱做 Java 桌面端的人大概都有过这种经历界面用 SWT 搭得挺顺一到画图表就卡住了。JFreeChart 是 Swing 体系的嵌进 SWT 要么用 SWT_AWT 桥接要么直接放弃原生渲染JavaFX 的图表组件倒是好看但引入 JavaFX 到纯 SWT 项目里线程模型和渲染管线完全是两套东西调起来让人头大。我前几年做一个工业数据采集的上位机主界面是 SWT 的需要实时画折线图和柱状图试了三种方案之后最后选了 SWTChart。它就是一个专门为 SWT 写的轻量级图表类库纯 Java 实现直接继承 Canvas 绘制没有额外依赖API 也足够直白。如果你也在用 SWT 做桌面应用需要嵌入折线图、柱状图、饼图、面积图这类常见图表又不想引入一整套渲染框架SWTChart 值得花半小时了解一下。它适合中小规模数据量的实时展示场景不适合做复杂 3D 或大规模数据可视化。2. SWTChart 核心架构与渲染机制它到底怎么把线画到 Canvas 上的2.1 从 Chart 到 Series 的对象模型SWTChart 的整体设计不复杂核心对象就几个Chart是整个图表的容器继承自Canvas你把它当成一个普通 SWT 控件加到父容器里就行。Chart内部持有AxisSet管理 X 轴和 Y 轴、PlotArea绘图区域、Title、Legend这些组件。数据层面每种图表类型对应一个Series接口的实现类比如LineSeries、BarSeries、PieSeries、AreaSeries。每个 Series 内部维护自己的数据集通过add或setXSeries/setYSeries方法往里塞数据。这个模型的好处是职责清晰Chart 管布局和绘制调度Series 管数据存储和自身图元的绘制逻辑Axis 管刻度和坐标映射。你不需要关心谁先画谁后画Chart 在paintControl里会按顺序遍历。我一般会这样初始化一个最基本的折线图// 创建 Chart 容器指定父容器和样式 Chart chart new Chart(parent, SWT.NONE); // 设置图表标题 chart.getTitle().setText(实时温度曲线); // 获取 X 轴和 Y 轴设置轴标签 chart.getAxisSet().getXAxis(0).getTitle().setText(时间 (s)); chart.getAxisSet().getYAxis(0).getTitle().setText(温度 (°C)); // 创建折线系列 LineSeries series (LineSeries) chart.getSeriesSet().createSeries(SeriesType.LINE, 传感器A); // 设置数据 series.setXSeries(new double[]{0, 1, 2, 3, 4, 5}); series.setYSeries(new double[]{23.5, 24.1, 23.8, 24.5, 25.0, 24.7}); // 让坐标轴根据数据自动调整范围 chart.getAxisSet().adjustRange();这段代码里createSeries的第一个参数决定图表类型第二个参数是系列名称会显示在图例里。setXSeries和setYSeries接收double[]长度必须一致否则运行时会抛异常。adjustRange()是个关键调用它让坐标轴根据当前所有系列的数据自动计算最小值和最大值不调用的话轴范围是默认的 0 到 1你的数据可能完全看不见。2.2 绘制管线与坐标映射SWTChart 的绘制发生在Chart.paintControl方法里整个流程大致是先计算绘图区域扣除标题、图例、轴标签占用的空间然后绘制背景和网格线接着遍历所有 Series 调用各自的draw方法最后画坐标轴刻度和标签。坐标映射的核心在Axis类里它维护了数据值到像素坐标的线性变换。X 轴和 Y 轴各自独立计算映射公式就是简单的线性插值pixel (value - min) * (pixelRange / valueRange) pixelOffset。这个映射逻辑意味着两件事第一如果你的数据范围变化很大每次更新数据后都要调adjustRange()或者手动setRange否则新数据可能画到画布外面第二坐标轴默认是线性刻度不支持对数轴如果你的数据跨越几个数量级得自己预处理。Series 的绘制用的是 GCGraphics ContextSWTChart 内部从 Canvas 拿到 GC 之后设置前景色、线宽、抗锯齿等属性然后调用gc.drawPolyline或gc.drawLine来画。柱状图用的是gc.fillRectangle饼图用的是gc.fillArc。这些都是 SWT 原生绘图 API性能上比 Swing 的 Graphics2D 在 Windows 平台略好因为直接调了系统 GDI。2.3 与 SWT 布局系统的集成方式因为Chart继承自Canvas它就是一个标准 SWT 控件你可以用任何 SWT 布局管理器来摆放它。常见做法是给父容器设FillLayout或GridLayout然后把 Chart 加进去并设置GridData的grabExclusive为 true这样窗口缩放时图表会跟着变。// 父容器使用 GridLayout Composite parent new Composite(shell, SWT.NONE); parent.setLayout(new GridLayout(1, false)); // Chart 占满整个空间 Chart chart new Chart(parent, SWT.NONE); chart.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));这里有个细节Chart 在 resize 的时候会自动重绘不需要你手动调redraw()。但如果你是在非 UI 线程更新数据必须用Display.getDefault().asyncExec把更新操作切回 UI 线程否则会抛SWTException: Invalid thread access。这是 SWT 的铁律跟 Swing 的 EDT 是一个道理。3. 从零搭建一个可交互的实时折线图完整代码与参数调优3.1 项目依赖与最小可运行骨架SWTChart 的依赖非常干净只需要 SWT 本身的 jar 包和 SWTChart 的 jar。如果你用 Maven可以这样配dependency groupIdorg.eclipse.swt/groupId artifactIdorg.eclipse.swt.win32.win32.x86_64/artifactId version3.120.0/version /dependency dependency groupIdorg.eclipse.swtchart/groupId artifactIdorg.eclipse.swtchart/artifactId version0.12.0/version /dependency注意 SWT 的 artifactId 跟平台相关Windows 64 位是org.eclipse.swt.win32.win32.x86_64Linux 和 macOS 各有对应的。版本号根据你项目实际情况选SWTChart 0.12.0 对应 SWT 3.120 左右比较稳。如果不用 Maven直接下 jar 扔 lib 目录也行SWTChart 的 jar 很小几百 KB。最小可运行骨架就是一个 Shell 加一个 Chart下面这段代码可以直接跑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; import org.eclipse.swtchart.ISeries.SeriesType; public class MinimalChart { public static void main(String[] args) { Display display new Display(); Shell shell new Shell(display); shell.setText(SWTChart Demo); shell.setSize(800, 600); shell.setLayout(new FillLayout()); Chart chart new Chart(shell, SWT.NONE); chart.getTitle().setText(最小示例); ILineSeries series (ILineSeries) chart.getSeriesSet() .createSeries(SeriesType.LINE, line1); series.setXSeries(new double[]{1, 2, 3, 4, 5}); series.setYSeries(new double[]{2, 4, 1, 5, 3}); // 设置线条颜色为蓝色 series.setLineColor(display.getSystemColor(SWT.COLOR_BLUE)); // 设置线宽 series.setLineWidth(2); // 显示数据点 series.setSymbolType(ILineSeries.PlotSymbolType.CIRCLE); series.setSymbolSize(5); chart.getAxisSet().adjustRange(); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } display.dispose(); } }setLineColor接收一个Color对象用display.getSystemColor拿系统预定义色最省事也可以new Color(display, 255, 0, 0)自定义。setSymbolType控制数据点标记的形状可选NONE、CIRCLE、SQUARE、TRIANGLE等。setSymbolSize是像素值设太大点会挤在一起。3.2 实时数据更新与双缓冲防闪烁实时场景下数据是不断追加的你需要定期更新 Series 并重绘。常见做法是用一个Runnable配合display.timerExec定时触发// 模拟数据源每秒追加一个点 final double[] xData new double[100]; final double[] yData new double[100]; // 初始化 for (int i 0; i 100; i) { xData[i] i; yData[i] 50 20 * Math.sin(i * 0.1); } series.setXSeries(xData); series.setYSeries(yData); // 定时器每 500ms 更新一次 display.timerExec(500, new Runnable() { int index 100; public void run() { if (chart.isDisposed()) return; // 滚动窗口整体左移一位末尾追加新值 System.arraycopy(yData, 1, yData, 0, yData.length - 1); yData[yData.length - 1] 50 20 * Math.sin(index * 0.1); index; // 更新 Series 数据 series.setYSeries(yData); // 重新调整 Y 轴范围 chart.getAxisSet().getYAxis(0).adjustRange(); // 重绘 chart.redraw(); // 继续下一次 display.timerExec(500, this); } });这里有几个关键点。第一setYSeries会替换整个数组不是追加所以你要自己维护一个固定长度的数组做滚动窗口。第二adjustRange每次更新都调会有性能开销如果数据范围变化不大可以手动setRange固定范围只在超出时调整。第三redraw()是必须的否则界面不会刷新。闪烁问题在 SWT 里通常不严重因为 SWT 在 Windows 上默认就是双缓冲的。但如果你在paintControl里做了复杂计算可能会感觉到卡顿。SWTChart 本身没有暴露双缓冲开关但你可以通过chart.setRedraw(false)暂停重绘批量更新完再setRedraw(true)加redraw()减少中间状态的绘制次数。3.3 坐标轴、图例与交互事件的参数配置坐标轴的配置直接影响可读性。X 轴如果表示时间你可能想自定义刻度标签的格式IAxis xAxis chart.getAxisSet().getXAxis(0); // 设置刻度数量 xAxis.getTick().setTickCount(10); // 自定义刻度标签格式 xAxis.getTick().setFormat(new DecimalFormat(0.0)); // 设置轴范围 xAxis.setRange(0, 100); // 网格线样式 xAxis.getGrid().setStyle(LineStyle.DOT); xAxis.getGrid().setForeground(display.getSystemColor(SWT.COLOR_GRAY));setTickCount是建议值实际渲染时 SWTChart 会根据可用空间微调。setFormat接收NumberFormat对时间轴你可以用SimpleDateFormat但需要自己包一层。网格线默认是实线改成DOT或DASH视觉上更轻。图例的位置和可见性chart.getLegend().setVisible(true); chart.getLegend().setPosition(SWT.BOTTOM);setPosition支持SWT.TOP、SWT.BOTTOM、SWT.LEFT、SWT.RIGHT。如果系列很多图例放底部比较省空间。交互方面SWTChart 本身不提供缩放和拖拽但你可以监听鼠标事件自己实现。比如滚轮缩放chart.addMouseWheelListener(new MouseWheelListener() { public void mouseScrolled(MouseEvent e) { IAxis xAxis chart.getAxisSet().getXAxis(0); double range xAxis.getRange().upper - xAxis.getRange().lower; double factor e.count 0 ? 0.9 : 1.1; double center (xAxis.getRange().upper xAxis.getRange().lower) / 2; double newRange range * factor; xAxis.setRange(center - newRange / 2, center newRange / 2); chart.redraw(); } });这段代码以当前视图中心为基准缩放 X 轴。e.count的正负表示滚轮方向。注意缩放后要调redraw()而且如果同时有多个轴要同步处理。4. 避坑与排查SWTChart 实际使用中的五个血泪教训4.1 数据更新后图表不刷新现象调了setYSeries但界面没变化。原因SWTChart 不会自动监听数据变化必须手动调chart.redraw()。解决每次更新数据后显式调用redraw()如果在非 UI 线程用asyncExec包起来。4.2 坐标轴范围不对导致曲线跑出可视区现象数据明明在 0 到 100 之间但图上只看到一条直线或者空白。原因没有调adjustRange()轴范围还是默认的 0 到 1。解决初始化数据后调一次chart.getAxisSet().adjustRange()后续如果数据范围动态变化每次更新后都要调或者手动setRange固定范围。4.3 大量数据点时绘制卡顿现象数据超过几千个点后拖动窗口或更新数据明显卡顿。原因SWTChart 默认每个点都画 symbol而且drawPolyline在点数过多时效率下降。解决数据量超过 2000 时关掉 symbolsetSymbolType(PlotSymbolType.NONE)或者对数据进行降采样只画可视区域内的点。另外setLineWidth不要设太大抗锯齿在大量线条时开销明显。4.4 多线程更新数据抛 Invalid thread access现象在后台线程里调series.setYSeries或chart.redraw()时抛SWTException。原因SWT 控件只能在 UI 线程操作。解决用Display.getDefault().asyncExec(() - { ... })把更新逻辑切回 UI 线程。注意asyncExec是异步的如果更新频率很高可能会积压大量 Runnable建议用syncExec或者加个标志位控制。4.5 图例文字太长被截断现象系列名称较长时图例显示不全。原因图例的宽度计算基于文字长度但布局空间不够时会截断。解决缩短系列名称或者把图例放到右侧并给 Chart 留出足够空间。SWTChart 没有提供图例文字换行或省略号配置只能从命名上控制。5. 进阶技巧用 SWTChart 做多轴叠加与自定义渲染多轴叠加是工业场景里的常见需求比如同一个时间轴上既要显示温度又要显示压力量纲不同需要两个 Y 轴。SWTChart 支持通过AxisSet创建额外的轴// 创建第二个 Y 轴 IAxis yAxis2 chart.getAxisSet().createYAxis(); // 设置第二个轴的位置在右侧 yAxis2.setPosition(IAxis.Position.SECONDARY); // 创建第二个系列绑定到第二个 Y 轴 LineSeries series2 (LineSeries) chart.getSeriesSet().createSeries(SeriesType.LINE, 压力); series2.setYAxis(yAxis2); series2.setXSeries(new double[]{0, 1, 2, 3, 4, 5}); series2.setYSeries(new double[]{100, 120, 110, 130, 125, 140}); // 分别调整两个轴的范围 chart.getAxisSet().getYAxis(0).adjustRange(); yAxis2.adjustRange();createYAxis返回一个新的IAxis实例setPosition(SECONDARY)把它放到右侧。关键点是series2.setYAxis(yAxis2)这行把系列绑定到指定轴否则默认还是用第一个 Y 轴。两个轴的颜色最好跟对应系列一致方便区分。自定义渲染方面SWTChart 允许你通过继承LineSeries并重写drawSeries方法来实现特殊效果比如画渐变填充或者标注特定点。但更简单的做法是给 Chart 加一个PaintListener在 SWTChart 绘制完成后叠加自己的图形chart.addPaintListener(new PaintListener() { public void paintControl(PaintEvent e) { // 在图表上叠加一条水平参考线 GC gc e.gc; gc.setForeground(display.getSystemColor(SWT.COLOR_RED)); gc.setLineStyle(SWT.LINE_DASH); // 假设 Y25 对应的像素位置 int yPixel chart.getAxisSet().getYAxis(0).getPixelCoordinate(25.0); gc.drawLine(0, yPixel, chart.getBounds().width, yPixel); } });getPixelCoordinate是IAxis提供的方法把数据值转成像素坐标。这个技巧在画阈值线、报警线的时候特别有用。注意PaintListener的触发时机在 SWTChart 自身绘制之后所以你的图形会覆盖在图表上面。还有一个实用技巧是导出图表为图片。SWTChart 没有内置导出功能但你可以用GC把 Chart 画到一个Image上Image image new Image(display, chart.getBounds().width, chart.getBounds().height); GC gc new GC(image); chart.print(gc); // 把 Chart 绘制到指定 GC gc.dispose(); ImageLoader loader new ImageLoader(); loader.data new ImageData[]{image.getImageData()}; loader.save(chart.png, SWT.IMAGE_PNG); image.dispose();chart.print(gc)是 SWTControl类的方法会把控件内容画到指定 GC。导出的图片分辨率跟当前控件大小一致如果需要更高清可以先临时调整 Chart 大小再导出。从那以后我每次在 SWT 项目里用图表都会先把adjustRange和redraw的调用时机在纸上画一遍确认没有遗漏再写代码。这个习惯帮我省了不少调试时间。希望帮到你。本文还有配套的精品资源点击获取