SWTChart桌面图表开发:线程刷新、多Y轴与降采样

发布时间:2026/10/26 9:16:37
SWTChart桌面图表开发:线程刷新、多Y轴与降采样 简介面向Java桌面开发者的SWTChart图表类库资源基于SWT构建适合在Eclipse RCP或SWT界面中快速加入轻量级图表能力解决原生SWT缺少可视化组件的问题。类库覆盖线图、散点图、堆栈图、对数标度、分类轴、多轴、柱形图、面积图、步骤图、轴取向与系列标签等常见需求便于按业务数据选择合适呈现方式。压缩包共36个文件约287KB包含24个Java源文件、2个JAR包以及HTML说明和工程配置文件兼顾直接引用与源码阅读、二次调整。已有640人学习或下载可作为SWT图表集成、示例参考与轻量可视化实践的入门素材。1. 为什么在 SWT 桌面里选 SWTChart三个场景和一条选型边界桌面工具里最容易被低估的不是业务逻辑而是现场人员要看的那张趋势图。SWTChart 是跑在 SWT 上的 Java 图表类库做 SWT 图表时它把线图、散点图、柱状图、饼图直接嵌进 Composite不依赖浏览器内核启动和打包都轻。它适合已经用 SWT 做桌面客户端、监控面板、设备调试工具和离线报表的人不适合要把图表塞进 Web 页面的场景。选它的理由很直接控件模型和现有 SWT 界面一致事件、资源释放、布局都能沿用同一套习惯代价是样式定制和复杂交互要自己补。下面按能复现的顺序拆。2. 把第一张 SWTChart 跑起来环境检查、最小窗口和四个必调参数2.1 资源包落位SWT 本地库、SWTChart jar 和类路径三个检查点SWTChart 本身是 Java 类库但它依赖 SWT而 SWT 在不同桌面环境里需要对应的本地库。很多人第一次跑不起来不是图表 API 写错而是类路径里只有 SWTChart 的 jar没有 SWT 的 jar 和本地库或者本地库位数和 JVM 位数对不上。我的习惯是先做一个最小窗口只放一个空 Chart确认 SWT 能启动再往里加数据。这样出问题时排查面小不会一上来就混着数据解析、线程和图表配置一起查。资源包拿到后先做三个检查第一构建路径里同时有 SWT 的 jar 和 SWTChart 的 jar第二SWT 的本地库和目标桌面环境匹配32 位 JVM 配 32 位库64 位 JVM 配 64 位库第三启动命令或工程配置里没有把资源包里的示例类误当成主类。下面这个命令只是示意类路径怎么拼实际路径按你的工程替换不要照抄目录名。# 示意把资源包里的库和编译输出一起加入类路径 # 实际路径按你的工程结构调整重点是 SWT jar、SWTChart jar、本地库都要在 java -cp lib/*:classes your.Main这段命令的关键在-cplib/*表示把库目录下的 jar 都加进来classes是编译输出目录。参数说明上lib/*的通配符由 JVM 展开不同 shell 对通配符处理不一样命令不生效时先把 jar 全路径写出来试一次。本地库如果不在 jar 里还要通过-Djava.library.path指过去这一步经常被漏掉现象是启动就报找不到本地库连 Shell 都出不来。先把这个黑匣子排掉后面的图表问题才有意义。提示不要用 AWT/Swing 的 EDT 思路去套 SWT。SWT 的 UI 操作有单独的 Display 线程约束后面第 4 章会展开。2.2 最小可运行窗口从 Shell 到第一根折线最小示例不要一上来就做多系列、多轴、图例定制。先创建一个 Shell再创建 SWTChart挂一个线系列设置 X/Y 数组调整范围最后进事件循环。下面这段代码省略了 import按资源包里的实际类路径补全即可老版本和新版本的泛型写法略有差异ILineSeries如果带泛型用原始类型也能先跑通编译警告不影响验证。// 省略 import按资源包中的实际类路径导入 SWT 与 SWTChart 相关类 public class FirstChart { public static void main(String[] args) { Display display new Display(); Shell shell new Shell(display); shell.setText(SWTChart 最小示例); shell.setSize(800, 600); // 创建图表控件父容器是 Shell样式先用 NONE SWTChart chart new SWTChart(shell, SWT.NONE); chart.setBounds(20, 20, 740, 520); // 创建线系列名字会出现在图例里 ILineSeries series chart.getSeriesSet().createLineSeries(温度); series.setXArray(new double[]{0, 1, 2, 3, 4, 5}); series.setYArray(new double[]{21.5, 22.1, 21.8, 23.0, 22.6, 22.9}); series.setLineColor(display.getSystemColor(SWT.COLOR_BLUE)); series.setLineWidth(2); // 标题和轴标题先确认文字能正常显示 chart.getTitle().setText(设备温度趋势); chart.getAxisSet().getXAxis(0).getTitle().setText(采样点); chart.getAxisSet().getYAxis(0).getTitle().setText(温度 / ℃); // 让轴范围覆盖数据再触发重绘 chart.getAxisSet().adjustRange(); chart.update(); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } display.dispose(); } }这段代码的逻辑顺序是先有 Display再有 Shell再把 Chart 放到 Shell 上。SWT.NONE是控件样式先用默认样式避免一上来被边框、滚动条之类的样式干扰。setBounds是给 Chart 一个初始大小SWT 的布局体系里如果不设大小控件可能显示成一条线或者干脆看不见。createLineSeries返回系列对象名字用于图例setXArray和setYArray必须长度一致不一致时常见现象是图不出来或者只画出一部分。setLineColor用系统颜色不要自己new Color之后忘了释放setLineWidth控制线宽桌面高分屏下太细的线看起来会发虚。adjustRange()是让轴范围自动包住数据update()是触发重绘。很多人只调了setXArray就以为图会自己变实际要更新范围或显式重绘。如果编译时报ILineSeries泛型错误先看资源包里库的 API 签名用原始类型或者补上对应的数组泛型不要在这个阶段改业务代码先把最小图跑出来。2.3 四个必调参数标题、图例、轴范围和重绘时机最小图跑出来后先调四个参数不要急着做样式。第一个是标题chart.getTitle().setText(...)只影响图表顶部标题不影响轴标题第二个是图例chart.getLegend().setVisible(true)控制图例显示系列多了以后图例位置和字号要一起看第三个是轴范围axis.setRange(new Range(min, max))是手动范围adjustRange()是自动范围两者顺序不能反第四个是重绘数据改完、范围改完最后统一chart.update()不要在每次setXArray后面都update。下面这段代码把 X 轴固定成 0 到 24Y 轴固定成 0 到 100并设置刻度格式。手动范围适合监控面板这种“坐标轴不能抖”的场景自动范围适合探索式查看。注意Range的上下界是数值不是像素DecimalFormat只影响刻度文字不影响数据精度。// 省略 import IAxis xAxis chart.getAxisSet().getXAxis(0); IAxis yAxis chart.getAxisSet().getYAxis(0); // 手动范围适合固定量程的面板避免刷新时轴来回跳 xAxis.setRange(new Range(0, 24)); yAxis.setRange(new Range(0, 100)); // 刻度格式X 轴整数Y 轴一位小数 xAxis.getTick().setFormat(new DecimalFormat(0)); yAxis.getTick().setFormat(new DecimalFormat(0.0)); // 图例和标题 chart.getLegend().setVisible(true); chart.getTitle().setText(24 小时采样); chart.update();参数说明上Range(0, 24)的两个参数是下界和上界设反了会出现轴倒挂或者图跑出可视区。DecimalFormat(0)表示不要小数0.0表示保留一位小数格式串写错不会崩但刻度会变成很奇怪的形式。chart.update()放在最后前面所有设置批量生效。这里有个血泪经验如果先setRange再adjustRange手动范围会被自动范围覆盖现象是“我明明设了 0 到 100刷新后又变了”。顺序应该是先adjustRange做兜底再setRange做固定最后update。3. 数据系列与坐标轴线图、柱状图、散点图的组合和 CSV 装载3.1 三种系列的创建差异与适用场景SWTChart 的系列创建入口都在chart.getSeriesSet()上线图用createLineSeries柱状图用createBarSeries散点图用createScatterSeries。它们的数据设置方式接近都是 X 数组加 Y 数组但视觉语义不同线图强调连续趋势柱状图强调离散分类或区间对比散点图强调两个变量之间的关系。选错系列不是“能不能画”的问题而是读图的人会不会误判。比如设备温度随时间变化用线图不同工位的产量对比用柱状图温度与压力的相关性用散点图。系列类型创建方法适合场景参数注意点线图createLineSeries时间序列、趋势、连续采样X 最好递增线宽和颜色要区分系列柱状图createBarSeries分类对比、离散区间X 轴刻度容易挤分类多时要旋转标签或减柱散点图createScatterSeries相关性、离群点点大小和透明度影响可读性点数多要降采样代码上线图和柱状图的骨架几乎一样差别在系列类型和颜色设置。下面这段代码同时创建线图和柱状图线图放温度柱状图放产量。注意两个系列的 X 数组长度可以不同但同一个系列内部 X/Y 必须等长。// 省略 import ILineSeries line chart.getSeriesSet().createLineSeries(温度); line.setXArray(new double[]{0, 1, 2, 3, 4, 5}); line.setYArray(new double[]{21.5, 22.1, 21.8, 23.0, 22.6, 22.9}); line.setLineColor(display.getSystemColor(SWT.COLOR_BLUE)); line.setLineWidth(2); IBarSeries bar chart.getSeriesSet().createBarSeries(产量); bar.setXArray(new double[]{0, 1, 2, 3, 4, 5}); bar.setYArray(new double[]{12, 18, 9, 22, 16, 20}); bar.setBarColor(display.getSystemColor(SWT.COLOR_DARK_GREEN)); chart.getAxisSet().adjustRange(); chart.update();这段代码的逻辑是两个系列共用同一组 X 轴刻度但 Y 轴量纲不同。如果温度和产量都挂在同一根 Y 轴上产量会把温度压成一条平线读图的人会以为温度没变化。实际项目里量纲不同的系列要么拆成两张图要么开第二根 Y 轴。setBarColor控制柱体颜色柱状图系列多的时候颜色要错开adjustRange()会把两根轴的范围都包住所有系列但不会解决量纲问题只解决“看不看得见”的问题。3.2 多 Y 轴第二根轴什么时候值得开第二根 Y 轴不是越多越好。开第二根轴的条件通常是两个系列量纲不同、变化趋势都需要看、并且读者能通过颜色或轴标题区分。比如温度是 20 到 30压力是 0 到 100共用一根轴时温度几乎是一条直线。SWTChart 支持创建额外的 Y 轴然后把系列绑定到该轴。不同版本对第二根轴的位置枚举和创建方法略有差异核心步骤是创建轴、设标题和格式、把系列绑到轴 id 上。// 省略 import IAxis pressureAxis chart.getAxisSet().createYAxis(); pressureAxis.getTitle().setText(压力 / kPa); pressureAxis.getTick().setFormat(new DecimalFormat(0.00)); ILineSeries pressureSeries chart.getSeriesSet().createLineSeries(压力); pressureSeries.setXArray(new double[]{0, 1, 2, 3, 4, 5}); pressureSeries.setYArray(new double[]{101.2, 102.5, 100.8, 103.1, 102.0, 101.6}); pressureSeries.setYAxisId(pressureAxis.getId()); pressureSeries.setLineColor(display.getSystemColor(SWT.COLOR_RED)); chart.getAxisSet().adjustRange(); chart.update();这段代码的关键是setYAxisId(pressureAxis.getId())不绑 id 的话系列还挂在默认 Y 轴上第二根轴就是空的。pressureAxis.getTitle().setText(...)和刻度格式要设否则两条轴的颜色和文字一样读图的人分不清哪条线对应哪根轴。第二根轴的位置如果默认在错误的一侧需要查资源包里对应版本的轴位置设置不要硬编码一个枚举值编译不过就看 IDE 提示。多轴场景下图例颜色和轴线颜色要一致这是最低成本的防误读手段。3.3 从 CSV 到 Series一个可复用的装载器真实数据很少是手写数组常见做法是从 CSV 或数据库读出来再塞给系列。下面这个装载器只做三件事跳过表头、按列解析 double、把列表转成数组。它故意不做复杂容错先让主流程跑通空值、NaN、Infinity 在后面过滤。参数上分隔符写死成逗号列索引 0 和 1 分别代表 X 和 Y实际项目里把这两个参数提出来即可。// 省略 import public final class CsvSeriesLoader { public static void loadLine(SWTChart chart, String seriesName, Path csv) throws IOException { ListDouble xs new ArrayList(); ListDouble ys new ArrayList(); try (BufferedReader br Files.newBufferedReader(csv)) { String line; boolean first true; while ((line br.readLine()) ! null) { if (first) { first false; continue; } // 跳过表头 String[] cols line.split(,); if (cols.length 2) continue; String xText cols[0].trim(); String yText cols[1].trim(); if (xText.isEmpty() || yText.isEmpty()) continue; xs.add(Double.parseDouble(xText)); ys.add(Double.parseDouble(yText)); } } double[] x xs.stream().mapToDouble(Double::doubleValue).toArray(); double[] y ys.stream().mapToDouble(Double::doubleValue).toArray(); ILineSeries series chart.getSeriesSet().createLineSeries(seriesName); series.setXArray(x); series.setYArray(y); chart.getAxisSet().adjustRange(); chart.update(); } }逻辑说明Files.newBufferedReader默认字符集在部分环境里可能和文件不一致CSV 有中文或特殊符号时显式指定字符集更稳first标记只跳第一行如果文件有多段表头要另加判断cols.length 2跳过残行空字符串跳过避免Double.parseDouble直接抛异常。参数说明seriesName会进图例重复创建同名系列不会自动合并反而会出现两个图例项Path csv建议用绝对路径调试相对路径受工作目录影响常见翻车是“文件明明在程序说找不到”。这段代码跑通后再考虑把解析和 UI 更新分开。读文件可能慢不要在主 UI 线程里读大文件常见做法是后台线程读成数组再回到 Display 线程设置系列这部分第 4 章会写。4. 动态刷新与大数据量线程模型、批量更新和降采样4.1 SWT 线程模型为什么图表更新必须回到 Display 线程SWT 的 UI 控件有线程约束后台线程直接改 Chart 的数据或调用update()轻则界面不刷新重则抛Invalid thread access。这个错误不是 SWTChart 的锅是 SWT 的控件模型决定的。正确做法是把“改数据 重绘”包在Display.asyncExec或Display.syncExec里。asyncExec不阻塞后台线程适合高频采集syncExec会等 UI 线程执行完适合需要立刻拿到结果的场景但不要在 UI 线程里再调syncExec会死锁。// 省略 import public void appendPoint(Display display, SWTChart chart, ILineSeries series, double x, double y) { display.asyncExec(() - { if (chart.isDisposed()) return; // 窗口已关别再碰控件 double[] oldX series.getXArray(); double[] oldY series.getYArray(); double[] newX Arrays.copyOf(oldX, oldX.length 1); double[] newY Arrays.copyOf(oldY, oldY.length 1); newX[newX.length - 1] x; newY[newY.length - 1] y; series.setXArray(newX); series.setYArray(newY); chart.getAxisSet().adjustRange(); chart.update(); }); }这段代码的逻辑是后台线程只调appendPoint真正的控件操作被投递到 Display 线程。chart.isDisposed()是后悔药窗口关闭后后台线程还在跑时不加判断会报控件已释放。参数上x和y是本次追加的点series必须是同一个系列对象每次追加都复制整个数组点数少时没问题点数上万时复制开销会变成瓶颈。高频场景不要用这个写法应该用环形缓冲或批量数组下一节写。4.2 批量更新setRedraw、adjustRange 和 update 的顺序批量更新比单点追加更适合监控面板。做法是先把一批数据准备好关掉重绘设置数组调范围开重绘最后统一update()。setRedraw(false)是 Composite 的方法能减少中间状态的重绘次数adjustRange()放在数据设置之后、update()之前update()只调一次。顺序错了会出现“数据设置了但图没变”或者“刷新过程中闪屏”。// 省略 import chart.setRedraw(false); try { series.setXArray(batchX); series.setYArray(batchY); chart.getAxisSet().adjustRange(); } finally { chart.setRedraw(true); } chart.update();逻辑说明try/finally保证异常时重绘开关能恢复否则整个图表会一直不刷新看起来像卡死。batchX和batchY必须等长长度不一致时 SWTChart 的行为依赖版本常见是只画较短的那部分。参数上批量大小不是越大越好一次几千到一万点足够再大就要降采样。adjustRange()每批都调会让轴范围频繁变化固定量程的面板应该改用手动setRange只在初始化时调一次。4.3 降采样与定时刷新把 10 万点压到可交互10 万点全画出来桌面控件也能画但交互会明显变慢尤其是窗口拖动和鼠标悬停。我的习惯是超过 5000 点就降采样趋势预览用等步长采样峰值敏感的场景用最大最小桶。下面这个等步长版本最简单按步长取点保留首尾参数maxPoints控制输出点数通常 2000 到 5000 之间。它的缺点是可能漏掉尖峰所以只适合趋势展示不适合报警判定。// 省略 import public static double[][] downsample(double[] x, double[] y, int maxPoints) { if (x.length maxPoints) { return new double[][]{x, y}; } int step (int) Math.ceil(x.length / (double) maxPoints); int n (x.length step - 1) / step; double[] nx new double[n]; double[] ny new double[n]; for (int i 0, j 0; i x.length; i step, j) { nx[j] x[i]; ny[j] y[i]; } return new double[][]{nx, ny}; }逻辑说明step是采样步长n是输出点数循环按步长取点。参数maxPoints越小绘制越快但细节越少maxPoints小于 2 时数组会退化调用前要做边界判断。峰值敏感的场景不要用这个版本改成每个桶保留最大值和最小值两个点能明显减少“尖峰被抹平”的误判。定时刷新用display.timerExec(interval, runnable)比后台线程自己 sleep 更稳interval一般 100 到 200 毫秒太快没有意义人眼也看不出来。5. 避坑与排查五类从空白图到导出失真的翻车现场5.1 图不出来先查数据、轴和线程现象一坐标轴有刻度图表区域是空白图例里也没有系列名。原因通常是系列创建失败或者数据数组长度不一致。SWTChart 的多个系列 API 对 X/Y 长度有隐含要求长度不一致时有的版本直接不画有的版本画到较短的长度。解决方法是先在设置数组前加断言长度不一致直接抛异常把问题暴露在数据装载层同时检查createLineSeries的返回值有没有被丢弃系列对象没被引用不代表没创建但后续设置必须落在同一个对象上。数据里含NaN或Infinity也会导致整条线不画装载时用Double.isFinite过滤。现象二运行时报Invalid thread access或者窗口一闪就没了。原因是从后台线程直接操作了 Chart或者 Display 已经 dispose 还在投递 UI 任务。解决方法是所有控件操作走Display.asyncExec并在 runnable 开头判断chart.isDisposed()。窗口关闭时取消定时器后台线程用标志位退出不要依赖异常来结束循环。这个坑在采集类工具里很常见血泪经验是只要看到Invalid thread access先搜代码里有没有在非 UI 线程调setXArray、update、setRange。现象三图能出来但鼠标拖动窗口时图表重影或闪烁。原因多半是重绘开关和update()顺序不对或者在高频刷新时每来一个点就重绘一次。解决方法是批量更新setRedraw(false)包住数据设置最后只update()一次固定量程的面板不要每批都adjustRange()轴范围变化会触发额外布局。SWT 控件本身有双缓冲但中间状态太多时仍然会闪。5.2 图能出来但不好用查资源、刷新和导出现象四程序跑几个小时后越来越卡最后报资源句柄相关错误。原因是每次刷新都new Color或new Font没有 dispose。SWT 的 Color 和 Font 是系统资源系统颜色用display.getSystemColor(...)拿到的不要手动释放自己创建的资源要挂在控件的DisposeListener上统一释放或者在初始化时创建一次、复用到结束。图表系列颜色如果每次刷新都换先检查是不是在循环里 new Color。现象五导出的 PNG 尺寸不对文字发虚或者图只导出可见区域。原因是导出前没有按目标尺寸布局直接抓当前控件高 DPI 下字体按固定像素设置放大后自然模糊。解决方法是导出前把 Chart 设成目标尺寸调layout()和update()再用库自带的 save 方法导出不同版本的 save 签名和格式常量可能不同编译不过就看 IDE 提示不要硬编码。字体按 DPI 缩放导出尺寸和屏幕尺寸分开不要指望屏幕截图能当报表图。注意避坑章里的“现象→原因→解决”不是让你背而是排错顺序。先确认图能不能出来再确认刷新稳不稳最后才调样式和导出。6. 进阶验证与导出用基准线核对轴范围再把 PNG 输出钉死6.1 基准线验证用 y2x1 检查轴范围和刻度图表类库最容易出的问题不是画不出来而是画出来了但轴范围不对读图的人被误导。我一般会先用一条已知斜线做基准X 取 0 到 4Y 取 1 到 9跑adjustRange()后读回轴范围确认下界不大于最小值、上界不小于最大值。这个验证不依赖业务数据能快速区分“数据问题”和“轴问题”。// 省略 import double[] x {0, 1, 2, 3, 4}; double[] y {1, 3, 5, 7, 9}; ILineSeries series chart.getSeriesSet().createLineSeries(基准); series.setXArray(x); series.setYArray(y); chart.getAxisSet().adjustRange(); Range xr chart.getAxisSet().getXAxis(0).getRange(); Range yr chart.getAxisSet().getYAxis(0).getRange(); if (xr.lower 0 || xr.upper 4 || yr.lower 1 || yr.upper 9) { throw new IllegalStateException(轴范围没有覆盖基准数据); }这段代码只做一件事轴范围必须包住数据。xr.lower和xr.upper是读回来的下界和上界adjustRange()之后如果还不覆盖说明系列没绑定到默认轴或者数据设置顺序有问题。基准线跑通后再换成真实数据轴范围问题就不会和业务逻辑混在一起。6.2 导出前先定尺寸PNG 输出别靠屏幕截图导出 PNG 的常见做法是先把 Chart 设成目标尺寸布局完再保存。屏幕截图受当前窗口大小和缩放影响报表本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询