简介:这份 TEECHART 8.06 完整源码包面向需要在 Delphi、C++ Builder 及 Kylix 等开发环境中构建高质量图表的程序员,尤其适合希望研读图表库内部实现、进行组件二次开发或解决 PDF 导出中文显示问题的技术人群。压缩包共 2362 个文件、约 11.49MB,以 dcu 编译单元、pas 源码、res 资源、dpk 包工程、dfm/xfm 窗体文件以及 bmp 图像和 cpp 源码为主,兼顾组件安装、示例工程与文档说明,目录组织便于按模块检索。已有 186 人学习下载,说明其在图表开发与源码研究场景中具有一定参考价值。读者可获得整套可编译的图表组件源码,用于理解线图、柱状图、饼图、散点图等图形的绘制逻辑,并依据项目需求扩展功能、优化性能;其中 PDF 导出中文支持的修正可减少报告生成时的乱码困扰,配合安装与许可说明能较快完成环境集成,跨平台目录与决策立方体相关内容也方便进一步探索多维数据展示。
1. 一个老项目被迫读源码的夜晚:TeeChart 8.06 full source 能给你什么
某公司一套跑了八年的 Delphi 进销存,报表页上的图表全靠第三方控件支撑。客户现场一点「导出」,程序就弹 Access Violation,日志里只有一串十六进制地址,连个函数名都不给。这种黑匣子最难受的地方在于:你明知问题出在绘制或导出那一步,却没有任何入口能进去看一眼。后来拿到 TeeChart 8.06 full source 这套完整源码包,整条链路才被拆开:哪个单元负责计算刻度、哪个单元负责画柱子、导出 PDF 时中文字体死在哪一步,全都有据可查。
它适合两类人:手上还压着 Delphi 7 到 2009 老工程、被二进制包卡住多年的维护者;以及想给现有桌面系统加一套可控图表、又不愿意为此引入一堆前端依赖的开发者。源码在手上,出问题才有后悔药。
2. 技术底座拆解:一套 VCL 图表库是怎么组织起来的
TeeChart 8.06 是原生 VCL / C++Builder 的图表组件库,跟后来 .NET、Java 那些同名产品线不是一套东西。它的世界观非常朴素:一个TChart容器,挂一堆Series,共用一块Canvas。整套 full source 的价值不在于代码量,而在于这三层的边界切得干净——你想改「什么时候画」就去动 Chart,想改「画成什么样」就去动 Series,想改「用什么画」才需要碰 Canvas。搞清这条分界线,后面读几万行 Pascal 才不会迷路。
2.1 单元地图:full source 里到底有哪些 .pas
解压后第一眼会被文件数量劝退,但真正需要反复看的就那么十来个。8.x 各个小版本之间有过合并与拆分,下面这份地图按职责划分,具体文件名以你手上那份为准。
| 单元 | 职责 | 什么时候会改到它 |
|---|---|---|
TeEngine.pas | TCustomChart/TChart/TChartAxis/TChartSeries基类、标题、图例、墙 | 改绘制主流程、加全局事件 |
Series.pas | TLineSeries、TBarSeries、TPieSeries、TFastLineSeries等标准序列 | 改柱子间距、标签位置、配色规则 |
TeCanvas.pas | TTeeCanvas/TTeeCanvas3D,画笔、画刷、字体的抽象层 | 换渲染后端、修抗锯齿、修字体度量 |
TeeProcs.pas | 数学、几何、颜色、字符串工具函数 | 补工具函数,或追查坐标算错 |
TeeTools.pas | 光标、标注、跟随提示、色带等交互浮层 | 加十字光标、加数据点提示 |
TeeThemes.pas | 主题集合与Apply机制 | 统一整个系统的图表视觉风格 |
TeeStore.pas | .tee模板的二进制 / XML 读写 | 做图表模板持久化 |
TeExport.pas+TeePNG/TeeJPEG/ PDF 相关单元 | 图片与 PDF 导出 | 导出糊、导出中文变方框 |
TeeGDIPlus.pas | GDI+ 抗锯齿画布 | 高分屏、要平滑曲线 |
TeeOpenGL.pas | OpenGL 三维渲染 | 三维曲面数据量大时救性能 |
TeeEdit*.pas | 设计期图表编辑器对话框 | 定制自己的一套属性面板 |
看这份表有个小技巧:先找TeEngine.pas,它是整个库的心脏,几乎所有类型声明都从这里出发;其余的单元基本是「被 TeEngine 引用」或「引用 TeEngine」两种关系。把uses关系顺着捋一遍,比从第一个文件顺序读要快得多。
2.2 编译器适配:D7 到 D2009 的问题先在编译期爆发
8.06 这个年代的代码,条件编译符号密度非常高。它要同时兼容 Delphi 7、2005、2006、2007、2009 这几代编译器,而 2009 是个分水岭——String从AnsiString变成了UnicodeString,一堆PChar、AnsiString、Char相关的代码全要重写。所以你在源码里会看到大量这种结构:
// 读源码前先认这几个符号,它们决定了当前分支走哪条路 {$IFDEF D9} // Delphi 2005 及以后 // 新版本才有的特性分支 {$ENDIF} {$IFDEF UNICODE} // Delphi 2009 及以后,String = UnicodeString // 字符与字符串处理走这套 {$ELSE} // 老版本走 AnsiString 那套 {$ENDIF} {$IFDEF MSWINDOWS} // 平台相关的 GDI 调用 {$ENDIF}这段不是让你背,是让你在读某个函数时先确认自己站在哪个分支里。血泪经验是:很多「看起来莫名其妙的类型不匹配」错误,根源是你在 D2009 以上编译,但源码里某处的PChar转换还停留在AnsiChar语义,编译器直接报E2010 Incompatible types。解决办法不是去改那一行强转,而是先往上翻,看这个函数有没有{$IFDEF UNICODE}的对应实现,大概率作者已经写好了,只是你编译的包和引用的路径不是同一套。
动手前建议先做三件事:确认编译器版本(dcc32的版本号决定了很多分支)、确认工程里原来的图表控件是哪一代(老工程常见的坑是同时存在两代控件)、确认 Search Path 里源码目录排在 Delphi 自带 lib 目录之前。第三点后面第 5 章会专门讲,它是「装了新版却还是老样子」这类玄学问题的头号元凶。
2.3 三层职责:Chart 管布局,Series 管数据,Canvas 管像素
把这三层的职责说透,比读十遍源码有用。
TCustomChart是布局总控。它手里握着所有 Series 的列表、四个轴对象、标题、图例、墙,负责算「画布上哪块区域归绘图区」「左轴要占多宽」「图例放右上角还是底部」。所有跟「排版」有关的逻辑都在这层,包括三维模式下的深度偏移。
TChartSeries是数据与图元的结合体。它内部持有XValues、YValues这些TChartValueList,同时知道怎么把第 N 个点画成一个柱子、一段折线或一个扇形。它向上暴露的是Add、AddXY、Clear这类数据接口,向下调用的是Canvas的绘图方法。
TTeeCanvas是像素层。它把「画一条线」抽象成MoveTo/LineTo,「画一个矩形」抽象成Rectangle,底下可能是原生 GDI,也可能是 GDI+ 或 OpenGL。这层做得好,你换渲染后端时上面两层几乎不用改。
判断改哪里有个口诀:改「什么时候画」动 Chart,改「画成什么样」动 Series,改「用什么画」动 Canvas。比如「图例挡住数据了」——是 Chart 的布局问题;「柱子太宽了」——是 Series 的绘制问题;「曲线有锯齿」——是 Canvas 的问题,挂个 GDI+ 就行,别去动 Series。
3. 核心链路拆解:一个点从 AddXY 到出现在屏幕上
理解了分层,接下来把链路走通。很多人用这类库用了好几年,只知道「拖个控件、加个 Series、调 AddXY 就有图」,一旦要改绘制细节,就完全不知道该从哪里下手。这一章就顺着一次重绘,从入口一直追到像素。
3.1 重绘入口:TCustomChart.Draw 里的调用顺序
TChart的重绘入口是Draw,它本身很薄,真正干活的是内部的绘制流程。8.x 的签名大致如下,以你手上源码的接口声明为准:
// 示意:TeeChart 8.x 的重绘入口 procedure TCustomChart.Draw; begin if Assigned(FCanvas) then InternalDraw(FCanvas); // 真正干活的地方 end; procedure TCustomChart.InternalDraw(const ACanvas: TTeeCanvas); begin if FIsDrawing then Exit; // 重入保护,防止事件里再触发重绘 FIsDrawing := True; try ACanvas.AssignFont(Font); DrawChartBackground(ACanvas); // 背景、边框、渐变 DrawWalls(ACanvas); // 三维墙体 DrawAxes(ACanvas); // 先算刻度,Series 后面要用 DrawSeries(ACanvas); // 逐个 Series 绘制 DrawTitles(ACanvas); DrawLegend(ACanvas); DrawTools(ACanvas); // 光标、标注等浮层,必须最后画 finally FIsDrawing := False; end; end;这段顺序是整份源码里最不能乱动的地方。DrawAxes必须排在DrawSeries之前,因为 Series 画点时调用的CalcXPos/CalcYPos依赖轴已经算好的像素区间;DrawTools必须排在最后,否则光标线会被柱子盖住。我见过有人为了「让标注显示在背景上层」把DrawTools挪到前面,结果整个浮层被墙和柱子遮得只剩一半,排查半天才发现是顺序问题。
FIsDrawing这个重入标志位也值得留意。很多人会在OnBeforeDrawSeries这类事件里顺手改一下 Series 的属性,而改属性又触发一次重绘,很容易出现无限递归或者画面闪烁。源码里加了这层保护,你改代码时别把它删掉。
3.2 Series 的绘制:DrawAllValues 如何遍历到 DrawValue
Chart 层调完,球踢给 Series。基类TChartSeries里的DrawAllValues负责遍历所有数据点,把工作甩给虚方法DrawValue。每个具体序列类重写DrawValue,就决定了第 N 个点长什么样。
type // 自定义柱状序列的骨架:只重写 DrawValue 就能改外观 TMyBarSeries = class(TChartSeries) protected procedure DrawValue(ValueIndex: Integer); override; public constructor Create(AOwner: TComponent); override; end; procedure TMyBarSeries.DrawValue(ValueIndex: Integer); var X, Y, HalfW: Integer; R: TRect; begin // CalcXPos / CalcYPos:把数据值映射成屏幕坐标,依赖轴已算好 X := CalcXPos(ValueIndex); Y := CalcYPos(ValueIndex); HalfW := BarWidth div 2; // BarWidth 由 BarWidthPercent 和点数算出 R := Rect(X - HalfW, Y, X + HalfW, GetVertAxis.IPos); Canvas.Brush.Color := ValueColor[ValueIndex]; // 每点独立颜色 Canvas.Pen.Color := Canvas.Brush.Color; if ParentChart.View3D then Canvas.Pyramid(R, ParentChart.Chart3DPercent) // 三维柱子 else Canvas.Rectangle(R); end;几个参数值得单独说。ValueColor[ValueIndex]取的是这个点自己的颜色,如果你只设了Series.Color而没设Colors,它就会回落到统一色;GetVertAxis返回当前序列绑定的纵轴(左轴或右轴),多轴图表里这一步非常关键,取错轴坐标就全飘到画面外;GetVertAxis.IPos是轴在屏幕上的像素位置,也就是柱子的底边,用它而不是写死Chart1.Height,才能适配任意布局。
这段代码也解释了为什么改外观不难:加阴影就是在Rectangle之前多画一个偏移的深色矩形,加圆角就是换成RoundRect,加渐变就是换Canvas.GradientFill。难点从来不在画,而在于你得知道DrawValue这个入口在哪。
3.3 坐标映射:CalcXPos 与轴的 IValueRange
CalcYPos本身很短,真正的换算在轴里:
function TChartSeries.CalcYPos(ValueIndex: Integer): Integer; var Axis: TChartAxis; tmpY: Double; begin Axis := GetVertAxis; // 关键:拿到自己绑定的那根轴 tmpY := YValues[ValueIndex]; // 原始数据值 Result := Axis.CalcYPosValue(tmpY); // 值 → 像素,交给轴负责 end;CalcYPosValue内部用的是轴的IValueRange(最小值和最大值)和IStartPos/IEndPos(绘图区上下边界像素),做一次线性映射;如果这根轴是Logarithmic,就走Log10分支;如果是Inverted,映射结果还要翻转。
这解释了三个常见现象。第一,数据全画出界了——因为轴的Automatic没打开,Maximum被写死成了比数据小的值。第二,对数轴上有数据点消失——因为数据里存在 0 或负数,Log10无定义,源码里通常直接跳过不画。第三,时间轴的点挤成一团——XValues.DateTime没设成True,日期被当成普通浮点数序号处理了。
3.4 数据接入:TChartValueList、AddXY 与批量刷新
数据进来的入口是Add/AddXY,它们最终都写进TChartValueList。这里有个性能细节,几乎每个做实时曲线的人都会踩:
var i: Integer; S: TFastLineSeries; begin Chart1.AutoRepaint := False; // 先关自动重绘,这一行是性能命门 try S := TFastLineSeries.Create(Chart1); S.ParentChart := Chart1; // 挂到图表上,同时建立 Owner 关系 S.Title := '实时采样'; S.XValues.DateTime := True; // X 轴按时间解释 for i := 0 to DataCount - 1 do S.AddXY(Now + i / 86400 / 24, Sample[i]); // 单位是天,1 小时 = 1/24 finally Chart1.AutoRepaint := True; Chart1.Repaint; // 一次性重绘 end; end;逻辑说明:AutoRepaint := False之后,AddXY只改数据不触发绘制,循环结束再统一刷一次。不加这一行,一万个点就是一万次重绘,界面直接假死,这是新手最容易翻车的地方之一。
参数说明:AddXY的第三个参数是标签文本,不传则为空;第四个参数是颜色,clTeeColor表示「用序列自己的颜色」而不是某个具体色值。XValues.DateTime要在加数据之前设好,事后设的话已有点的 X 值语义不会自动转换。另外ParentChart一旦赋值,序列就归 Chart 所有了,释放的事交给 Chart,别再手动Free,这一点第 5 章还会展开。
4. 跑起来:编译安装、动态建图表、导出图片与 PDF
源码包躺在硬盘上永远只是源码。要让 IDE 的组件面板上出现那个图标,并且工程能编译通过,中间隔着包编译、路径配置、DCU 冲突三道关。这一章按实际动手顺序走一遍。
4.1 编译与安装:运行期包和设计期包的区别
VCL 组件的安装机制是「先编译运行期包,再编译设计期包,最后 Install」。运行期包里只有组件本身的代码,设计期包会requires运行期包,并额外注册组件到 IDE 面板。源码包里通常两套.dpk都有,名字里带Dcl前缀的基本就是设计期那一套。
先在 IDE 里做一次干净环境:Tools → Options → Library里,把源码目录加到Library Path的最前面。顺序很重要,Delphi 是按路径顺序找单元的,自带 lib 目录里那份老图表库只要排在前面,你编译出来的就永远是旧的。
包可以手动开.dpk点Build,也可以用命令行批量跑:
# 路径按自己的 Delphi 安装位置改,这里只是示意 dcc32 -B -U"你的源码目录" TeeChart8.dpk dcc32 -B -U"你的源码目录" -LU"输出目录" DclTeeChart8.dpk-B是强制全部重新编译,-U指定单元搜索路径,-LU指定生成的.dcp输出目录。命令行的好处是可以在 CI 上复现一份干净的构建,出问题时能对比「IDE 里能过、命令行不能过」到底差在哪——通常差的就是路径顺序。
装完后打开Component → Install Packages,确认列表里只有一套图表包勾选。如果出现两个名字相似、版本号不同的,说明老版本残留没清干净,这个问题留到第 5 章细说。
提示:源码包在设计期安装前,先把整个目录备份一份。很多人在改源码调 bug 的过程中把原始版本覆盖掉了,想回退时才发现没有基线。
4.2 不拖控件:纯代码建一个可复用的图表工厂
设计期拖控件适合做 Demo,真正在项目里我一般写一个工厂函数,参数化配置,窗体上不放任何图表控件。这样图表可以随时销毁重建,也不会在设计期就被一堆属性污染。
uses TeEngine, Series, TeExport, TeeGDIPlus; function TReportForm.CreateChart(AOwner: TComponent; AParent: TWinControl): TChart; var S: TLineSeries; i: Integer; begin Result := TChart.Create(AOwner); // Owner 决定谁负责释放 Result.Parent := AParent; Result.Align := alClient; Result.View3D := False; // 二维更快,也更好看 Result.Legend.Visible := True; Result.Title.Text.Text := '近 7 日趋势'; S := TLineSeries.Create(Result); // Owner 交给 Chart,别自己 Free S.ParentChart := Result; S.Title := '访问量'; S.LinePen.Width := 2; for i := 0 to 6 do S.AddXY(i + 1, 100 + Random(50)); Result.Axes.Bottom.Title.Text := '日期'; Result.Axes.Left.Title.Text := '数量'; end;逻辑说明:TChart.Create(AOwner)里的AOwner决定了生命周期归属,传Self(窗体)意味着窗体销毁时图表跟着销毁;S.ParentChart := Result建立了序列与图表的归属关系,序列会被 Chart 的SeriesList接管。Align := alClient让图表自动铺满父容器,配合Parent就能塞进任何TPanel里。
参数说明:View3D := False关掉三维视图,二维绘制路径更短,几万个点时不明显,几十万个点时差别很大;LinePen.Width控制线宽,但注意它在 GDI 下是整数像素,高 DPI 屏上会显得偏细,这时候要配合缩放或者在OnBeforeDrawSeries里按 DPI 动态调。
4.3 导出与打印:PNG 模糊、PDF 中文乱码的根因
导出是这类库被用得最多、也最容易出问题的功能。基本写法是先uses TeExport,然后:
uses TeeGDIPlus, TeExport; procedure TReportForm.ExportChart(const AFileName: string); var G: TTeeGDIPlus; begin // GDI+ 画布:抗锯齿靠它,不挂这一层导出的图会是硬锯齿 G := TTeeGDIPlus.Create(Self); try G.TeePanel := Chart1; G.Active := True; Chart1.Export.SaveToFile(AFileName); // 扩展名决定格式:png/jpg/pdf finally G.Free; end; end;逻辑说明:TTeeGDIPlus是一个「画布替换器」,挂上去之后图表的绘制会走 GDI+ 而不是原生 GDI,曲线和文字的抗锯齿效果立刻不一样。Export.SaveToFile按扩展名分派到对应的导出器,所以文件名后缀必须写对。
参数说明:导出 PDF 时,中文字体是个绕不过去的点。PDF 是自描述文档格式,它不假设阅读者机器上装了什么字体,字体要么嵌入、要么用标准 14 字体。中文字体不在标准字体列表里,所以必须嵌入字体子集。如果导出的 PDF 中文全是方框,八成是字体没嵌入,或者指定的字体名在系统里对不上(中文字体常有中英文两个名字,写错一个就回落成默认字体)。
打印预览走的是另一套,通常是TChartPreview之类的东西,本质是把 Chart 画到一个打印画布上,再按页切分。这里有个细节:打印画布的 DPI 和屏幕不同,字号需要按比例换算,否则屏幕上 9 号字打到纸上小得看不清。源码里一般有专门的换算函数,搜ScreenToPrinter之类的关键字能翻到。
5. 避坑与排查:源码版最容易翻车的五处
有源码不等于没问题,反而是问题变多了——因为你能改的地方多了,改错的地方也就多了。下面这五条都是实际踩过的,按「现象 → 原因 → 解决」写,能省不少时间。
5.1 安装与编译阶段:两个 TChart 的玄学
第一条:组件面板上出现两个图表图标,拖上去属性少一半。
现象:装完设计期包,工具栏里有两个长得差不多的图标,拖其中一个到窗体上,属性面板里找不到View3D、SeriesList这些属性,代码里Series加不进去。
原因:Delphi 自带的 lib 目录里本来就有一份老版本图表库的.dcu,源码包里又注册了一份新的。两份同名单元在搜索路径里共存,IDE 加载的是先找到的那份,所以你看到的图标是新版,实际编进去的类是老版。
解决:Component → Install Packages里把旧的图表包取消勾选并移除;Library Path里把源码目录顶到最前;如果还有问题,去 Delphi 的 lib 目录下找到那批老的图表.dcu,备份后移走再重编。这一步要小心,动系统目录之前先整个复制一份出来。
第二条:编译报Unit TeCanvas was compiled with a different version of TeeProcs。
现象:明明全部源码都在工程里,编译器却抱怨单元版本不一致。
原因:工程同时用了源码和.dcu。常见于「运行期包开着,但工程里又直接uses了源码单元」的混合模式,一条路径进来的单元是现编的,另一条路径进来的是预编译的,两边的接口签名对不上。
解决:二选一,别混。要么工程里全部走源码(Build with runtime packages取消勾选,源码目录进 Search Path),要么全部走运行期包(源码目录从 Search Path 移除,只留.dcp)。改完记得Project → Build All,别只点Compile,增量编译不认路径变更。
第三条:D2009 以上编译,一堆PChar类型不匹配。
现象:E2010 Incompatible types: 'PAnsiChar' and 'PWideChar',报错位置集中在画布和文本测量相关单元。
原因:8.06 主体是 Unicode 之前写的,虽然加了{$IFDEF UNICODE}分支,但某些第三方封装或者你自己工程里的调用还传着PChar。真正的矛盾在于「谁负责转换」。
解决:别急着在报错行加强制转换,先往上翻找有没有 Unicode 分支的对应实现;确认调用链上到底该用AnsiString还是String;实在要转换,用TEncoding显式转,别用隐式强转——隐式强转在老代码里是最常见的乱码源头。
5.2 运行阶段:释放顺序、大数据量与后台线程
第四条:关窗体时崩,或者切换页面几次后内存一路涨。
现象:Free图表时弹Access Violation,堆栈落在TChartSeries.Destroy里。
原因:手动Free了已经被 Chart 拥有的序列。ParentChart一赋值,序列就进了 Chart 的SeriesList,Chart 销毁时会遍历列表逐个释放,你提前释放一次,就成了典型的重复释放。
解决:要么统一交给 Chart(推荐,只Create不Free),要么释放前先解除归属关系。日常做动态图表,我一般用一个ClearSeries方法集中管理,把所有序列从SeriesList里摘掉再统一释放,避免散落在各处的Free互相打架。
第五条:几十万个点的实时曲线,界面卡成幻灯片。
现象:数据一多,UI 线程直接不响应,鼠标拖一下窗口白屏好几秒。
原因:三件事叠在一起——每次AddXY都触发重绘;三维视图开着,每个点都要算深度偏移;没用轻量序列,普通TLineSeries每个点都带完整的标记、标签、颜色对象。
解决:换成轻量折线序列(名字里带Fast的那类),关掉View3D,循环前后用AutoRepaint := False/Repaint包起来,再配合「只保留最近 N 个点」的滑动窗口。如果还要更快,就上MaxPointsPerPage做抽样,或者干脆走 OpenGL 渲染。这套组合拳下来,同样的数据量能快一个数量级。
还有一条不在上面编号里但同样常见:在后台线程里创建图表。VCL 的绘制依赖 GDI 句柄,不是线程安全的,后台线程直接AddXY加绘制,崩溃是随机的、不崩是运气。常见做法是把数据准备好,用Synchronize或者TThread.Queue回主线程再更新图表;如果非要后台画,就画到一个离屏位图,主线程只负责贴图,边界划清楚。
6. 二次开发:自己写一个 Series、一套 Theme,再验证它
改这个库最高性价比的两个方向,一个是自定义序列,一个是自定义主题。前者解决「内置图形不够用」,后者解决「全系统图表风格不统一」。两者都不需要动核心引擎,风险可控。
先说主题,因为它最安全。思路是继承主题基类,重写应用方法,把配色、字体、网格线一次性刷到图表上:
type TDarkReportTheme = class(TTheme) public procedure Apply(ACanvas: TTeeCanvas; Chart: TCustomChart); override; end; procedure TDarkReportTheme.Apply(ACanvas: TTeeCanvas; Chart: TCustomChart); begin Chart.BackColor := $00202020; // 深色底 Chart.Gradient.Visible := False; // 渐变在深色下显脏 Chart.Axes.Left.Axis.Color := clGray; Chart.Axes.Left.LabelsFont.Color := clWhite; Chart.Axes.Bottom.LabelsFont.Color := clWhite; Chart.Legend.Font.Color := clWhite; end;ACanvas参数是绘制画布,Chart是要应用主题的图表实例,两个都由框架传进来,你只管往里刷属性。写完注册到主题列表,设计期编辑器里就能选到,全系统的报表页统一调一次即可。
再说自定义序列。最小可用的做法是继承TChartSeries,只重写DrawValue,把每个点画成你要的形状;如果还想支持设计期编辑器,再补一个编辑器类的注册。改动面稍微大一点,但收益是「别人做不出来的图你能做出来」。
改造时有几个验证点建议固定成流程,别凭眼睛看:
| 改动点 | 影响面 | 验证方式 |
|---|---|---|
重写DrawValue | 单个序列的外观 | 造 1 个点、2 个点、0 个点三组数据,看边界不越界 |
| 改轴刻度算法 | 全部序列的定位 | 打开对数轴 + 反向轴组合,确认点不飞出绘图区 |
| 换画布实现 | 全局绘制 | 同一份数据分别在 GDI 和 GDI+ 下导出 PNG 对比 |
| 改主题 | 全局视觉 | 深浅两套主题来回切,确认没有硬编码颜色残留 |
最后说个教训。早年我改这类源码,图省事直接在主干的TeEngine.pas里加逻辑,改完能跑,交付也没问题。半年后要升级小版本,合并时发现十几处改动全压在核心单元里,逐行比对花了整整两天,还漏了一处导致导出尺寸算错。从那以后我每次动这个库,都强制走一遍「新单元继承 + 原文件零改动」的路子,改动点集中在自己那几个.pas里,升级时整个目录替换即可。
这份 8.06 的 full source 解压后,建议先去跑一遍自带的示例工程,确认环境通了再动源码;改的时候把原始目录留一份当基线,每次改完用上面那张表过一遍验证点。图表这种东西,画错一个像素没人看得出来,画错一个数量级客户立马打电话。希望帮到你。
本文还有配套的精品资源,点击获取