TeeChart 8.06源码编译与定制:VCL图表组件从集成到渐变色序列实战
简介TEECHART 8.06 完整源代码是面向 Delphi、C Builder 等环境的专业图表库资源适合需要高质量数据可视化或深度定制图表功能的开发者也适合希望学习商业级图表实现原理的中高级程序员。压缩包共 2362 个文件、约 11.49MB包含 567 个 dcu 编译单元、333 个 pas 源文件、307 个 res 资源、229 个 dpk 包文件以及 bmp、dfm、xfm 等界面与窗体描述文件dpk/bpk 分别对应不同编译环境的工程配置便于按需重编译和源码研读。通过完整源码可深入理解坐标轴、序列、图表面板等内部机制并修正了 PDF 导出中文乱码问题同时涵盖 Kylix、C Builder、Delphi 多工程组与决策立方体多维分析功能附加安装、许可等文档整体结构完整并附带 TeeRecompile 重编译工具便于在不同 IDE 中集成。已有 186 人学习对于想掌握高级图表库设计思路、扩展自定义图表类型的开发者是一份紧凑而有深度的参考源码包。1. TeeChart 8.06 FULL SOURCE 是什么老项目的救星还是坑一个带图表的遗留系统如果图表组件是黑盒出问题的时候就只能对着黑匣子猜为什么这条曲线多了一个毛刺为什么导出图片时坐标轴缺了一个刻度当项目里有 TeeChart 8.06 FULL SOURCE 这种完整源码包时这些问题的答案就在眼前。它不是一个安装版控件而是把 TChart、TChartSeries、TeeCanvas 这一整套图表体系的源码全部摊开让你能改渲染、加序列、修兼容性。适合维护老 VCL 项目、做工业上位机图表、或者想研究图表库内部实现的开发者。不过拿到源码只是开始我把话说在前面编译和路径配置会消耗整个接入时间的一大半。这篇文章按“源码结构 → 编译 → 定制 → 避坑 → 进阶回归”的顺序把这条路走一遍每一步都落到能复现的命令和代码上。2. TeeChart 8.06 源码包结构先拆目录再决定从哪编译2.1 核心单元职责从 Chart 到 Series 到 Canvas 的依赖关系在源码包的 Source 目录下VCL 分支那一堆 .pas 文件决定了这套图表库的天花板。先捡核心的四个单元说Chart.pas 是 TChart 组件本体所有序列、坐标轴、图例都挂在它下面Series.pas 是所有序列类型的基座TLineSeries 这类基础序列也在这里TeeCanvas.pas 是对底层绘制的封装TChart 在屏幕上画的所有东西最后都经过它TeeProcs.pas 是公共工具和类型注册表IDE 组件面板上能看到哪些序列就是由这里的注册机制决定的。这四个单元构成了一条从数据到像素的主链路。数据进来以后先落在序列内部的 XValues 和 YValues 里这两个值列表是 TChartValueList 类型支持按索引访问和统计最大最小值。绘图时 TChart 会触发序列的 DrawValues序列内部逐个调用 DrawValueDrawValue 里再通过 CalcXPos / CalcYPos 把数据坐标换算成屏幕坐标最后落到 Canvas 的 LineTo、Rectangle 这类绘制调用上。换句话说只要在 DrawValue 里动手就能改变这个序列在屏幕上的全部表现。单个序列的职责可以用这张表理清序列侧成员职责XValues / YValues数据存储绘图范围的计算来源CalcXPos / CalcYPos数据坐标到像素坐标的换算DrawValue每个数据点的具体绘制CalcMinMax坐标轴自动范围计算DrawLegend图例里的示例图形这个链路对后面自定义序列很重要换一种绘制方式不需要改 TChart 本体也不需要改坐标轴管理只要替换序列自己的绘制方法。这也是为什么很多定制方案都从新增序列类型入手而不是去改 Chart.pas。2.2 运行时包与设计期包先确认交付介质里的可编译范围源码包一般同时带运行时包和设计期包分别对应 Tee 开头的 BPL 和 DclTee 开头的 BPL。运行时包提供类和函数设计期包负责把组件注册进 IDE 面板。如果只编译运行时包程序能跑但 IDE 面板上看不见组件如果只编译设计期包编译时逻辑又会缺一半。所以顺序永远是先编译运行时包再编译设计期包反过来一定会出幺蛾子。路径配置是第一道坎。常见做法是打开 IDE 的 Tools Options Library Library Path把源码包的 Source\VCL 目录加进去这一步缺了编译时直接报 File not found。我一般会把 Browsing Path 一起加上否则 IDE 跳转到源码时打不开文件排查问题要多花很多时间。然后是 IDE 版本匹配。8.06 是偏老的版本官方在发布时配套的 IDE 版本是固定的新 IDE 强行打开 .dpk 大概率会在第一个包就翻车。我的习惯是先看源码包里带了哪些 .dproj / .dpk优先用官方支持的 IDE 版本如果工作和学习环境必须用新 IDE就做好给源码打补丁的准备第 5 章会有专门排查。3. 把 TeeChart 8.06 源码编译成 BPL最小验证工程与路径参数3.1 用 RAD Studio 打开包工程的正确顺序先打开源码目录找到 .dpk 包文件。注意这里不要双击设计期包直接点 Install正确顺序是先编译运行时包再编译设计期包最后在 Components Install Packages 里勾选设计期包。如果一上来就 Install 设计期包IDE 会提示找不到运行时包然后留下一堆半装状态的注册信息。我通常用一个构建脚本固定这个顺序避免每次手工点错。批处理内容如下# 编译 TeeChart 运行时包与设计期包Windows 命令 # IDE_BIN 换成你的 IDE 实际安装目录包文件名以源码包里的 .dpk 为准 set IDE_BINC:\Program Files\...\bin set SRC.\Source\VCL set OUT.\Output # 1. 先编译基础运行时包-B 全量构建-U 指定单元路径-E 指定输出目录 %IDE_BIN%\dcc32.exe -Q -B -U%SRC% -E%OUT% %SRC%\Tee8.dpk # 2. 再编译数据库扩展运行时包如果项目需要绑定数据集的序列 %IDE_BIN%\dcc32.exe -Q -B -U%SRC% -E%OUT% %SRC%\TeeDB8.dpk # 3. 最后编译设计期包编译完用 IDE 里的 Install Packages 手动勾选 %IDE_BIN%\dcc32.exe -Q -B -U%SRC% -E%OUT% %SRC%\DclTee8.dpk第二个 dcc32 命令里的 -Q 是安静模式只输出错误和警告-B 表示全量重建避免增量编译拿到旧 .dcu-U 后面跟的路径是单元搜索路径编译 .dpk 时编译器会根据这里的路径去找依赖单元-E 指定 .bpl 和 .dcu 输出目录。刻意把输出目录集中到 .\Output是为了不让编译产物散落在源码目录里后续打补丁时源码目录保持干净。如果脚本在第一步就报 File not found先检查 SRC 路径是否真的指向了包含 Chart.pas 的目录如果报的是“无法定位TeeDB8.dpk”说明你的源码包没有这个模块去掉第二步即可不必强求。3.2 最小编译验证一个只依赖 TeeChart 的图表 Demo包编译通过、设计期包也装好了接下来要验证这套源码编译出来的组件真的能跑。我习惯新建一个空的 VCL Forms 工程不拖任何第三方组件用纯代码建一个 TChart 和一条折线把问题上限控制在最小范围。下面是一个可以直接替换 .dpr 内容的验证工程program TeeSourceVerify; uses Vcl.Forms, Vcl.Controls, Chart, Series, TeeProcs; var AForm: TForm; AChart: TChart; ASeries: TLineSeries; begin Application.Initialize; AForm : TForm.Create(Application); AForm.Caption : TeeChart 8.06 Source Verify; AForm.SetBounds(200, 100, 640, 420); AChart : TChart.Create(AForm); AChart.Parent : AForm; AChart.Align : alClient; ASeries : TLineSeries.Create(AChart); AChart.AddSeries(ASeries); ASeries.Title : sample data; ASeries.AddXY(1, 72); ASeries.AddXY(2, 68); ASeries.AddXY(3, 75); AForm.Show; Application.Run; end.逻辑说明Application.Initialize 是 VCL 应用初始化必须先执行TChart.Create(AForm) 把图表挂到窗体上Owner 是 AForm 所以不需要手动 FreeTLineSeries.Create(AChart) 和 AddSeries 把序列挂到图表里AddXY 每次写入一对 X、Y 数值。最后 AForm.Show 之后进入 Application.Run 消息循环窗体显示时序列会立即绘制。这个工程如果能编译、能弹窗、能看到三条线段连成的折线说明源码包已经完整接入 IDE。注意这里没有手工设置坐标轴范围坐标轴范围由 CalcMinMax 根据数据自动撑开也就是上一章说的 XValues / YValues 统计逻辑在起作用。如果坐标轴范围异常问题也大概率出在序列的 CalcMinMax 实现上而不是 TChart 本体。3.3 编译选项与路径参数防止冗余单元拖慢链接很多 Delphi 源码包会在根目录放一个条件编译配置文件用于切换平台、开关调试输出、控制功能模块。打开后通常能看到类似下面这种结构// 条件编译配置示例实际文件名以你的源码包为准 {$IFDEF TEETRACE} // 保留调试输出适合跟踪序列绘制流程 {$ELSE} // 生产环境关掉 Trace减小 DCU 体积 {$ENDIF} {$IFDEF TEEDB} // 启用数据库序列单元 {$ENDIF}参数说明TEETRACE 这类开关默认打开会让每次绘制都往 Output 窗口刷调试信息Release 构建时务必关掉TEEDB 决定要不要把数据库相关单元编进来。项目里不用 TDBChart 的建议关掉少编译一组单元链接速度和运行体积都有改善。还有一个容易踩的路径参数项目的 Project Options 里如果勾了 Use runtime packages运行时需要 .bpl 在搜索路径里。我建议验证阶段先取消勾选让程序静态链接 .dcu跑通之后再按需开启。这样出问题的时候至少不会被“系统 PATH 里另一个版本的 TeeChart.bpl 先被加载”这种问题干扰。路径多配、少配、配错顺序是源码包接入阶段最常见的时间黑洞值得一次性把环境理清楚。4. 在 TeeChart 8.06 源码上定制新增渐变序列并注册到设计期4.1 为什么要新增序列类型而不是改 TLineSeries拿到源码之后很多人的第一反应是去改 TLineSeries 的 DrawValue把折线绘制改成渐变。这个思路能跑通但副作用很大TLineSeries 是所有折线图的基础类项目里所有折线图都会跟着变。我见过有开发者改完基类之后全系统几十个窗口的图表样式一夜之间全变最后只能靠回滚收场。正确做法是新增一个序列类型继承 TLineSeries 或者直接继承 TChartSeries。这样改动被隔离在新类型内部老代码一行不动。TChartSeries 是抽象基类提供了 XValues / YValues、CalcXPos / CalcYPos、Count 这些基础设施子类只需要补齐绘制和范围计算两个核心方法就能被 TChart 正常调度。4.2 新增渐变线序列继承 TChartSeries 的最小实现下面这个类型实现了一条从红色渐变到蓝色的折线序列绘制是按 16 段插值完成的。整个过程不碰 TeeCanvas 源码只覆写序列自己的绘制方法type TGradientLineSeries class(TChartSeries) private FStartColor: TColor; FEndColor: TColor; FLineWidth: Integer; protected procedure DrawValue(ValueIndex: Integer); override; procedure CalcMinMax; override; public constructor Create(AOwner: TComponent); override; published property StartColor: TColor read FStartColor write FStartColor default clRed; property EndColor: TColor read FEndColor write FEndColor default clBlue; property LineWidth: Integer read FLineWidth write FLineWidth default 2; end; function InterpolateColor(const C1, C2: TColor; const Ratio: Double): TColor; var R1, G1, B1, R2, G2, B2: Byte; begin R1 : GetRValue(ColorToRGB(C1)); G1 : GetGValue(ColorToRGB(C1)); B1 : GetBValue(ColorToRGB(C1)); R2 : GetRValue(ColorToRGB(C2)); G2 : GetGValue(ColorToRGB(C2)); B2 : GetBValue(ColorToRGB(C2)); Result : RGB( Round(R1 (R2 - R1) * Ratio), Round(G1 (G2 - G1) * Ratio), Round(B1 (B2 - B1) * Ratio)); end; constructor TGradientLineSeries.Create(AOwner: TComponent); begin inherited Create(AOwner); FStartColor : clRed; FEndColor : clBlue; FLineWidth : 2; end; procedure TGradientLineSeries.DrawValue(ValueIndex: Integer); var X1, Y1, X2, Y2: Integer; Steps, I: Integer; begin if ValueIndex Count - 1 then Exit; X1 : CalcXPos(ValueIndex); Y1 : CalcYPos(ValueIndex); X2 : CalcXPos(ValueIndex 1); Y2 : CalcYPos(ValueIndex 1); Steps : 16; for I : 0 to Steps - 1 do begin ParentChart.Canvas.Pen.Color : InterpolateColor(FStartColor, FEndColor, (I 1) / Steps); ParentChart.Canvas.Pen.Width : FLineWidth; ParentChart.Canvas.MoveTo( X1 (X2 - X1) * I div Steps, Y1 (Y2 - Y1) * I div Steps); ParentChart.Canvas.LineTo( X1 (X2 - X1) * (I 1) div Steps, Y1 (Y2 - Y1) * (I 1) div Steps); end; end; procedure TGradientLineSeries.CalcMinMax; begin if Count 0 then begin MinXValue : 0; MaxXValue : 0; MinYValue : 0; MaxYValue : 0; Exit; end; MinXValue : XValues.MinValue; MaxXValue : XValues.MaxValue; MinYValue : YValues.MinValue; MaxYValue : YValues.MaxValue; end;逐段解释。InterpolateColor 做的是两个颜色在 RGB 空间的线性插值Ratio 取值 0 到 10 返回 C11 返回 C2ColorToRGB 先把系统颜色比如 clRed 转成标准 RGB避免 Color 常量里的高位标志位干扰计算。DrawValue 是绘制核心先用 CalcXPos / CalcYPos 取当前点和下一点的像素坐标然后在两点之间切 16 段每段改一次画笔颜色并画一条短线段。Steps 越大渐变越平滑但小于 8 时肉眼能看到明显色带大于 32 后性能开始下降16 是个折中值。CalcMinMax 决定坐标轴范围。注意 Count 为 0 时必须给 MinXValue / MaxXValue / MinYValue / MaxYValue 赋 0否则坐标轴范围会残留上一次计算的结果。XValues.MinValue 是 TeeChart 内置的 TChartValueList 统计方法会自动扫一遍数据列表取最小值省得自己循环。如果编译器提示还有未实现的抽象方法说明当前版本 TChartSeries 还声明了其他抽象接口按编译错误提示补上对应 override 即可。不同小版本之间的抽象方法数量略有差别这是正常现象。4.3 注册到设计期把自定义序列放进组件面板自定义类型写好了如果只通过代码创建那每次都要手写 Create、AddSeries设计期也拖拽不了。想让它出现在 IDE 组件面板上需要一个注册单元。下面是完整的注册代码unit GradientLineReg; interface uses Classes, GradientLineSeries; procedure Register; implementation procedure Register; begin RegisterComponents(MyTee, [TGradientLineSeries]); end; end.RegisterComponents 的第一个参数是组件面板上的分组名第二个参数是要注册的组件类数组。注册完成之后打开组件面板能找到 MyTee 分组把 TGradientLineSeries 拖到窗体上就行。这里有一个关键细节注册单元必须编译进设计期包而不是运行时包。新建 Package 工程把 GradientLineReg.pas 加进去requires 里引用上一章编译好的运行时包然后点 Compile再点 Install。如果只 Compile 不 Install组件不会出现在面板上。如果注册代码被误放进了运行时包编译不会报错但面板上永远看不到组件这是新手最容易翻车的地方。如果暂时不想建设计期包也可以退一步在 Form 的 OnCreate 里用代码创建 TGradientLineSeries效果一样只是少了设计期可视化编辑的便利。源码定制和高频调试阶段我一般先用代码方式跑通逻辑再补设计期注册两步分开能少踩一半坑。5. TeeChart 8.06 源码使用避坑编译、运行期与设计期的常见问题5.1 编译时报 File not foundChart.dcu 找不到现象编译 .dpk 或项目时几十秒后报 File not found: Chart.dcu位置指向某个 uses 子句。原因编译器在单元搜索路径里找不到 TChart 所在的 .dcu 或 .pas。99% 的情况是 Source\VCL 没被加进 Library Path或者加了但 IDE 没有生效尤其是改了路径之后没有重启 IDE。解决打开 Tools Options Library Library Path把源码包的 Source\VCL 目录加进去点 Save 后重新编译。如果还报错打开 Browsing Path 确认同一路径也在里面。另外要看 IDE 底部状态栏显示的目标平台Win32 和 Win64 的库路径是分开配置的只配了 Win32切到 Win64 编译一样找不到。5.2 程序一运行就 Access Violation版本冲突现象编译通过点击运行程序弹出 Access Violation崩溃地址每次都不一样。更奇怪的是同一个工程在同事机器上跑得好好的。原因开发环境里装了不止一套 TeeChart或者用了 runtime packages。程序启动时按 PATH 顺序加载 .bpl加载到的是另一个版本的 TeeChart 运行库而 .dcu 是源码版编译出来的两者接口对不上调用时直接踩内存。解决先把 Project Options 里的 Use runtime packages 勾掉让程序静态链接源码版逻辑跑通再说。如果必须用 runtime packages把源码版输出的 .bpl 复制到 exe 同目录并确保系统 PATH 里没有旧版 .bpl。查这个问题的最快方式是 Process Explorer 看进程加载的 bpl 列表确认实际加载路径。5.3 组件面板里没有 TChart设计期包没装对现象源码包编译全部成功但打开窗体设计器组件面板里找不到 TChart也找不到 TeeChart 相关分组。原因只编译了运行时包没装设计期包或者安装了设计期包但 Components Install Packages 里没有勾选。还有一种隐蔽原因注册代码被写进了运行时包设计期包是空的注册过程根本没执行。解决进入 Components Install Packages看列表里有没有 DclTee 开头的设计期包没有就点 Add 手动添加 .bpl。如果有但依然不显示组件检查注册单元是不是被编译进了设计期包。想快速验证写一个空窗体手动引用 TGradientLineSeries 所在单元如果编译能过说明类本身没问题问题只在注册链路。5.4 老源码在新 IDE 上编不过兼容性适配现象用近几年的 IDE 打开 8.06 的包报错集中在字符串类型、类继承、废弃 API 这几个方向比如 Undeclared identifier 或 Incompatible types。原因8.06 源码编写年代的编译器和现在的 IDE 在 RTTI 行为、字符串类型、部分系统单元接口上都有差异。严格来说这是源码和 IDE 之间的兼容性问题不是代码写错了。解决先确认源码包官方支持的 IDE 版本范围最省事的路径是用配套的老版本 IDE 编译。必须用新 IDE 时按错误位置逐一适配常见的改法包括把 PAnsiChar 相关的旧写法换成 PChar 或直接传 string删掉 uses 里已废弃的系统单元把旧式的注册调用改成新语法。这类补丁建议集中记录形成补丁文件方便下次拿到其他机器时直接应用。5.5 中文轴标题乱码字符集问题现象界面其他地方中文显示正常唯独图表坐标轴标题、图例里的中文变成问号或者乱码。原因老版本源码中部分文本绘制路径仍按 Ansi 编码处理宽字符串被截断成单字节后丢失高位字节中文就变成了乱码。解决先给相关组件的字体设置 Font.Charset : DEFAULT_CHARSET能解决一部分场景。剩下的需要改源码在 Canvas 绘制文本的位置找到调用 TextOut / TextOutA 的代码改成 TextOutW入参从 PAnsiChar 换成 PChar。如果序列标题和图例标题都正常而只有轴标题乱码重点检查坐标轴标题绘制分支那个分支和通用文本绘制通常不是同一段代码。6. 进阶玩法给 TeeChart 8.06 源码打补丁分支并做回归验证6.1 用 DUnitX 给自定义序列写回归测试改源码的人最怕一件事改了 A 功能B 功能悄悄退化。给自定义序列补单元测试是最直接的防线。下面这个用例验证 TGradientLineSeries 的数据存储行为uses DUnitX.TestFramework, GradientLineSeries; type [TestFixture] TGradientSeriesTest class public [Test] procedure AddXY_ShouldKeepTwoValues; end; implementation procedure TGradientSeriesTest.AddXY_ShouldKeepTwoValues; var S: TGradientLineSeries; begin S : TGradientLineSeries.Create(nil); try S.AddXY(1, 10); S.AddXY(2, 20); Assert.AreEqual(2, S.Count); Assert.AreEqual(20, S.YValues[1], 0.001); finally S.Free; end; end;Assert.AreEqual 的第三个参数 0.001 是浮点比较精度避免因为浮点表示误差导致误报。TChartSeries 没有绑定窗体时也能独立工作所以这类测试可以直接跑不需要 UI 环境。6.2 性能剖析定位绘制瓶颈的采样方法怀疑绘制性能有问题时别用眼睛猜先量化。最简单的手法是用 GetTickCount 包住绘制入口var T0, T1: Cardinal; begin T0 : GetTickCount; Chart1.Draw; T1 : GetTickCount; Caption : Format(draw %d ms, [T1 - T0]); end;如果一次 Draw 耗时超过预期优先检查三处坐标轴 Label 密度是否过高调大 Labels 的 Separation序列的 Pointer.Visible 是否开着点数上千时关掉能省大量绘制调用是否在数据加载过程中反复触发绘制数据量大时先 Chart1.AutoRepaint : False数据加载完再统一刷新。6.3 我的习惯本地补丁分支和回归验证我拿到源码包的第一件事不是改代码而是先把干净源码提交到一个基线版本所有改动都放在独立分支上。每次要改渲染逻辑就从基线切出新分支改完用 6.1 的测试用例回归通过后再合并回本地主分支。这样即使改坏了随时能和干净源码做 diff也能快速出一份补丁文件# 生成从基线到当前改动的补丁文件便于备份和换机器 git diff v8.06-clean tee8-local.patch这个习惯帮我躲过很多次“改完源码忘了改了什么”的尴尬。源码包这种东西改动一旦散落在几十个文件里没有版本管理就等于没有后悔药。先立基线再改改完能回退能对比这条路才能走得长久。希望帮到你。本文还有配套的精品资源点击获取