2023年6月20日发(作者:)

(转载)翻译本⽂翻译在CodeProject上的介绍(主要还是⾕歌翻译,看不太明⽩的地⽅,请对⽐原⽂,敬请原谅),⽅便⾃⼰和后⾯⼈的学习(花费了两天时间,希望是值得的)。推荐⼀个前辈写的东西:TeeChart替代品,MFC下好⽤的⾼速绘图控件-(Hight-Speed Charting),⾃⼰也转载了这篇⽂章,在转载的⽂章中根据⾃⼰的实验修改了⼀些东西,修改了什么现在也不记得了,⽂章地址:TeeChart替代品,MFC下好⽤的⾼速绘图控件-(Hight-Speed Charting) .下⾯是我能找到的资料(点击超链接下载)前三个是原⽂提供的(要想找到最新的,到原⽂中找),后三个是⾃⼰在其他的博客中找到的。ChartCtrl_rtCtrl_rtCtrl_动态绘制曲线图-HightSpeedChart实现.rarTeeChart和HightSpeedChart动态绘图.rar2018年5⽉6⽇补充⼀点使⽤过程中遇到的问题:遇到的问题以及解决⽅案⽂章⽬录综述介绍免责声明主要特点⽂档结构⼊门学习⼿动插⼊使⽤资源管理器操作数据序列柱状图系列在点上添加标签对轴的操作处于离散模式下的轴使⽤⽇期/时间轴⾃定义外观响应⿏标事件响应图表上的⿏标事件响应系列上的⿏标事件使⽤光标使⽤平移和缩放功能利⽤⾼速功能扩展功能Upgrading from Version 1.x to Version 2.0Upgrading from Version 2.x to Version 3.x例⼦FeedbackHistoryThanksLinksLicense遇到的问题以及解决⽅案 Assertion Failed! File: Line:639吐槽综述这个控件是⽤来展⽰2D数据,如下⾯的图⽚。介绍对于我之前的⼀个项⽬,我需要在图表控件上显⽰连续的数据流。 我决定开发⾃⼰的控件,因为我找不到任何可以提供所需灵活性的⾃由软件控件。 其中⼀个主要的限制是,控件必须绘制⼤量的数据,并能够迅速显⽰它(在Pocket PC上)。 控件能够通过仅绘制新的数据点⽽不是完整的数据序列来做到这⼀点并且图表还能够显⽰静态数据。这种控件是我长时间⼯作的结果,⽽且费尽周折地为了提供⾜够的灵活性来供需要它的⼈使⽤。 对于使⽤者反馈我表⽰由衷的感谢:⼀个邮件,留⾔板中的⼀⼀句话或只是对本⽂评级。 当我不知道是否还有⼈使⽤它时,我就没有必要维护这个控件了。免责声明这个控件是我花费很长时间的开发的结果,因此我对代码的使⽤放置⼀些⼩条件:该代码可以以编译的形式⽤于任何⾮商业和商业⽬的。代码可以被重新开发,只要它提供作者名字和完整的免责声明。 更改源代码需要得到作者的同意。此代码不提供任何安全保证。 我不会对使⽤此代码造成的损失负责。 使⽤它需要⾃⼰承担风险。123This code may be used for any non-commercialand commercial purposes in a compiled code may be redistributed as long as it remainsunmodified and providing that the author nameand the disclaimer remain intact. The sourcescan be modified with the author consent code is provided without any guarantees.I cannot be held responsible for the damage orthe loss of time it causes. Use it at your own risks.鉴于开发这个控件所付出的努⼒,下⾯的要求并不过分: 如果你在在商业应⽤程序中使⽤这个控件,那么请给我发邮件让我知道。主要特点控件的主要特点是:⾼速绘图(轴固定时),允许快速绘制数据⽆限数量的数据序列(内存是限制)每个数据序列的数据量不受限制⽀持线图,点图,平⾯图,柱状图,K线图 和⽢特图系列最多四个轴(左,下,右和上轴)标准轴,对数轴或⽇期/时间轴⾃动伸缩的坐标轴, 翻转的坐标轴(相互独⽴)轴标签点标签平滑的曲线⽹格图例和标题交互性(在控件中发⽣特定事件时的通知)⽀持⼿动缩放和⿏标平移⽀持⿏标指针⽀持轴上的滚动条⾼度可定制(颜⾊,标题,标签,边缘,字体等)⽀持UNICODE⽀持打印和保存到图像⽂件⽂档结构本⽂通过⼀系列简短的教程来涵盖控件的⼤部分功能。 阅读本⽂后,您将能够快速地在⾃⼰的应⽤程序中使⽤本控件。我决定从⽂章中删除所有的类和函数的⽂档,因为它不是⾮常友好并且我很难维护。 此外,随着代码的增长,要记录的类和函数的列表变得过于⼴泛以⾄于不能将所有内容放在⽂章中。 作为替代,我提供了⼀个doxygen⽂档,您可以从本⽂中(⽂章的开头)下载:只需下载“Doxygen⽂档”zip⽂件,解压所有⽂件,双击“”⽂件,进⾏查看。⼊门学习此图表控件允许您在屏幕上绘制⼀系列数据。 此控件可以添加⼏个不同类型数据序列并且最多可以使⽤四个轴。 添加到图表的数据序列与⼀个⽔平轴(底部或顶部)和⼀个垂直轴(右侧或左侧)相关联。 这两个轴控制数据序列在图表上的显⽰⽅式。为了能够在应⽤程序中使⽤次图表控件,您⾸先需要在⾃⼰的⼯程⾥添加源代码zip中包含的⽂件。**注意:**控件在内部使⽤动态转型,因此必须启⽤RTTI(RunTime Type Information 运⾏时⾃动类型识别的机制),否则可能会发⽣崩溃。默认情况下,VC6没有启⽤RTTI,因此要启⽤它打开项⽬设置 - >“C / C ++”选项卡 - >“C ++语⾔”类别,并确保“Enable Run-TimeType Information (RTTI) "选项已选中。在应⽤程序中使⽤图表控件有两种⽅法:⼿动插⼊,或通过资源编辑器插⼊。⼿动插⼊1.#include "ChartCtrl"添加在对话框(Dialog)类的头⽂件中2.在对话框类中添加变量CChartCtrl://{{AFX_DATA(CChartDemoDlg)//}}AFX_DATACChartCtrl m_ChartCtrl;12343.在对话框类的OnInitDialog⽅法中添加这个控件的Create⽅法。使⽤资源管理器1.向对话框资源添加⾃定义控件,打开控件的属性,并为Class属性指定ChartCtrl。 为了避免滚动条上的闪烁,必须设置WS_CLIPCHILDREN样式(0x02000000L),如图所⽰。2.#include "ChartCtrl.h"添加在对话框(Dialog)类的头⽂件中3.在对话框类中添加变量CChartCtrl://{{AFX_DATA(CChartDemoDlg)//}}AFX_DATACChartCtrl m_ChartCtrl;12344.在DoDataExchange函数中添加DDX_Control(不要忘了更改ID号和控件名字):voidCChartDemoDlg::DoDataExchange(CDataExchange*pDX){CDialog::DoDataExchange(pDX);//{{AFX_DATA_MAP(CChartDemoDlg)// Add this line with the appropriate ID and variablenameDDX_Control(pDX,IDC_CHARTCTRL,m_ChartCtrl);//}}AFX_DATA_MAP}12345678操作数据序列⼏种类型的数据序列可以添加到控制:点序列,线序列,曲⾯序列,柱状图序列,K线图序列或⽢特图序列。 点的数据格式可能因序列⽽异(例如,K线图和⽢特图系列使⽤不同的点格式)。Series typeDescriptionCreate functionPoint typePoint seriesEach data point is represented by a single point on the screen. The appearance of the point can PointsSerieSChartXYPointLine seriesThe data points are connected through a line. The appearance of this line can be customized and it can also LineSerieSChartXYPointSurface seriesThe data points are connected through a line and the area under this line is filled with a specific brush. Theseries can also be displayed SurfaceSerieSChartXYPointBar seriesEach data point is plotted as a vertical bar of a certain width. Multiple bar series can be stacked next to each otherwithout overlapping. The bars can also be plotted BarSerieSChartXYPointCandlestick seriesEach data point is made of five attributes: the low value, the high value, the open value, the close value andthe X value (time). Each point is drawn as a candlestick. This series is used for plotting CandlestickSerieSChartCandlestickPointGantt seriesEach data point is made of three attributes: the start and end time and a Y value. Each point is drawn as ahorizontal bar starting at the start time and finishing at the end time. The bar is positioned along the Y axis at its GanttSerieSChartGanttPoint⼀旦你选择了⼀种系列,你可以通过调⽤上表中列出的CChartCtrl类的辅助函数之⼀将其添加到图表中。 这些函数接受两个可选参数:两个布尔值来确定描述该系列是连接到副⽔平轴(顶轴)或者是连接⼤副垂直轴(右轴)。 如果未指定参数,则数据系列将附加到主⽔平轴(底部轴)和主垂直轴(左轴)。**警告: **在将任何系列添加到图表之前,您需要创建该系列所连接的两个轴。 如果不这样做,将导致控件失效(assert)。 有关详细信息,请参见“操纵轴”⼀节。⼀旦将系列添加到图表后,我们就可以使⽤数据填充该图表。 有两种⽅法:将数据放到⼀个单元中⼀起添加,或者逐点添加。 后者⽤于有动态数据时:每次调⽤函数时都会更新图表。 虽然这个调⽤是快速的(在某些特定条件下),但是最好尽可能地将数据放到⼀个单元中。 下⾯是⼀个简单代码⽰例,它在图表中创建两个系列,并⽤数据填充它们:⼀个系列在初始化时完全填充,另⼀个系列在调⽤OnDataReceived函数(仅存在于此⽰例的⽬的)时填充。 m_pLineSeries,m_pPointsSeries和m_ChartCtrl是CMyClass类的成员变量。voidCMyClass::Init(){....// SNIP: Creation of the axes in the chart. This MUST be donebefore.m_pLineSeries=m_LineSerie();m_pPointsSeries=m_PointsSerie();doubleYValues[10];for(inti=0;i<10;i++)XValues[i]=YValues[i]=i;m_pLineSerie->SetPoints(XValues,YValues,10);}voidCMyClass::OnDataReceived(doubleX,doubleY){m_pPointsSeries->AddPoint(X,Y);}所有系列类继承⾃同⼀抽象基类:CChartSerie。该类处理所有系列通⽤的功能,但对具体的数据点没有任何处理功能。点的概念在⼦类CChartSerieBase中引⼊,它是⼀个模板类,模板参数是要操作为点的数据类型。这很重要,因为序列可能必须处理不同的数据类型:例如点序列操作具有X和Y值的点,但是K线图系列操纵具有5个值(打开,关闭,⾼,低和时间值)的点。其他系列继承⾃CChartSerieBase并提供他们操作的数据类型。 CChartSerieBase类已经处理了⼤多数数据管理,并通过纯虚函数将渲染委托给⼦类。每个系列在创建时也会分配⼀个Id。此标识可通过CChartSerie :: GetSerieId()检索,并可⽤于从图表中删除该系列。该系列的⼀个重要特征是控制点的顺序:该系列中的所有点将根据它们的值重新排序。 默认情况下,点是基于它们的X值排序的,但您可以通过对它们的Y值排序或不对它们进⾏排序来改变这种⾏为(在这种情况下,系列保持将点添加到系列中的顺序 )。 对点进⾏排序会对性能产⽣影响:如果点是有序的,则控件能够从完整系列中检索第⼀个和最后⼀个可见点,并且仅绘制两个点之间的点。 另⼀⽅⾯,你将不能绘制像椭圆形的曲线。 您可以通过调⽤CChartSerieBase :: SetSeriesOrdering来更改点的顺序。控件中的不同系列的功能通常是不⾔⾃明的。 然⽽,柱状图系列需要⼀些解释。柱状图系列这个系列有点特别,如果其中⼏个在同⼀个控件上绘制在⼀起,他们将互相影响。 ⽬的是能够绘制多个条形图系列,⽽不会重叠:它们是彼此相邻绘制的。 为此,您需要指定每个所属的组(⼀个简单的整数标识符)。 同⼀组的系列彼此相邻地绘制(或者对于⽔平条在彼此的顶部):参见两个图形的⽰例。 设置组ID是通过SetGroupId函数完成的。Bar series with the same group IdBar series with different group Id您还可以通过调⽤SetInterSpace静态函数来控制所有柱形图之间剩余的空间的宽度。 这将为所有系列设置以像素为单位的空间(因此,如果显⽰多于两个系列,则在任何位置使⽤相同的空间)。 注意,您可以通过调⽤SetBarWidth单独设置柱状图系列的宽度。在点上添加标签⼀旦使⽤数据填充您的系列,您还可以在系列的特定点上添加标签:这个标签始终附加到特定点。 现在,只提供⼀种类型的标签,⽓泡标签:包含⽂本的圆⾓矩形并⽤线连接到特定点上。 当然,如果需要,您也可以提供⾃⼰的⾃定义标签(参见“扩展功能”⼀节)。有两种⽅式创建⽂本标签:静态创建标签时,或动态注册⼀个对象,当标签请求时,它将提供⽂本。 第⼀种⽅法是最简单的,但也不太灵活。 下⾯是⼀个代码⽚段,显⽰如何做(假设m_pSeries已经创建并填充⾜够的数据):voidCMyClass::Init(){// _pSeries->CreateBalloonLabel(5,_T("This is a simple label"));}12345此调⽤将创建⼀个带有“This is a simple label”⽂本的标签,并将其附加到带索引为5的点。该函数返回⼀个指向新创建的标签的指针,以便您可以修改其某些属性或存储以供以后使⽤。第⼆种⽅法有点复杂,但提供了更多的灵活性:例如,您可以以更⽅便的⽅式在标签中显⽰点属性(例如X值,Y值,…)。 为此,您必须创建⼀个继承⾃CChartLabelProvider 的类,并在创建标签时提供此类的实例。 此类是模板类,模板参数是标签附加到的系列的点类型。 这个类是⼀个简单的接⼝,你必须覆盖TChartString GetText(CChartSerieBase * pSerie,unsigned uPtIndex)⽅法。 此函数应返回必须在标签中显⽰的⽂本。 它接收指向标签所附加的系列和点索引的指针。 这⾥有⼀个这样的标签提供程序类的例⼦:classCCustomLabelProvider:publicCChartLabelProvider{public:TChartStringGetText(CChartSerieBase*pSeries,unsigneduPtIndex){TChartStringStreamssText;SChartXYPoint Point=pSeries->GetPoint(uPtIndex);ssText<<_T("X value=")<CreateBalloonLabel(5,m_pLabelProvider);}1234567控件不获取指针的所有权,因此,当你不再需要时,你有责任删除它。 在上⾯的例⼦中,它通常会在CMyClass析构函数中被删除。 在上⾯的⽰例中,您可以为所有要添加的标签地⽅重复使⽤相同的标签类, 这也带来另⼀个优点:如果你想在运⾏时改变标签的格式,你只需要在CustomLabelProvider中添加代码。 不需要遍历所有现有标签并更改其⽂本。

当然,在这种情况下,需要刷新控件,因为必须重新绘制标签。还要注意TChartStringStream类的⽤法,TChartStringStream类是由控件提供的别名(类似于TChartString)。 当UNICODE被定义时,它解析为std :: wstringstream,当未定义UNICODE时,解析为std :: stringstream。对轴的操作轴是图表的⼀个重要特征,因为它们控制不同系列在控制中的显⽰⽅式。 控件中最多可使⽤四个轴:底部,顶部,左侧和右侧。 控件的每个系列必须和⼀个⽔平轴和⼀个垂直轴相连接。 在图表中添加系列时指定这些轴。 底部和左侧轴是主轴,顶部和右侧轴是辅助轴(您将在控件的某些功能中遇到此问题)。 现在有三种类型的轴供选择:标准轴,对数轴和⽇期/时间轴。 您可以在不同位置选⽤不同类型的轴。⼀旦您选择了在不同位置使⽤哪些轴,您需要先创建它们,然后才能向控件添加任何数据。 为此,通过指定轴附加在哪个位置,简单地调⽤CreateStandardAxis,CreateLogarithmicAxis或CreateDateTimeAxis。 如果已经在该位置创建了轴,则控件将销毁它并且⽤新的轴替换它。 这⾥有⼀个简单的代码⽚段,显⽰如何在底部创建⽇期/时间,在左侧创建⼀个标准轴:voidCMyClass::Init(){CChartStandardAxis*pBottomAxis=m_StandardAxis(CChartCtrl::BottomAxis);CChartLogarithmicAxis*pLeftAxis=m_LogarithmicAxis(CChartCtrl::LeftAxis);}1234567⼀旦创建了这些轴,就可以对它们设置⼀些属性。 ⼤多数属性在所有轴类型之间共享(例如⾃动模式,最⼩值和最⼤值,轴标签,…)。 轴可以设置为三种“⾃动”模式:全⾃动,屏幕⾃动和⼿动模式。全⾃动模式基于附加到该轴的所有系列计算轴最⼩值和最⼤值(所有系列的所有点的最⼩值⽤作轴的最⼩值,并使⽤所有系列的所有点的最⼤值作为轴的最⼤值)。屏幕⾃动模式基于与该轴相关的所有系列的所有可见点计算轴最⼩值和最⼤值。 例如,如果图表仅显⽰连接到⼿动底部轴和屏幕⾃动左侧轴的⼀个系列,则左侧轴将⾃适应于当前可见的点,并且不考虑这些点有可能超过底轴的范围(在全⾃动模式下,底轴外部的点将被考虑)。 **警告:**如果系列的两个轴都处于屏幕⾃动模式,则结果未定义。在⼿动模式下,轴最⼩和最⼤值由⽤户设置,不由控件计算。在使⽤⾃动轴模式下,如果将数据动态添加到控件,如果新的数据点位于轴的范围之外,那么控件将⾃动刷新。 这⾥是⼀个代码⽚段(继续前⼀个代码段),显⽰⼀个全⾃动轴(底部轴)和⼀个⼿动轴(左轴,它是⼀个对数轴):voidCMyClass::Init(){// SNIP ...pBottomAxis->SetAutomaticMode(CChartAxis::FullAutomatic);// The call toSetAutomaticMode(CChartAxis::NotAutomatic) is not// really needed because this is the xis->SetAutomaticMode(CChartAxis::NotAutomatic);pLeftAxis->SetMinMax(0.01,1000);}123456789处于离散模式下的轴轴有⼀个模式是离散模式(默认禁⽤)。此模式指定轴不显⽰连续值,⽽只显⽰离散值,这些值是轴上刻度指定的值,⽽轴将不显⽰其他的值。尝试绘制不同于显⽰的节拍值的值是不可能的。让我们举⼀个例⼦:假设你有⼀个底部标准轴,间隔为1.0(所以,显⽰的蜱是1,2,3等等)。尝试绘制X值为0.5的点将在相同位置显⽰该点,就好像它的值为1.0。事实上,你可以认为两个刻度之间的区域是⼀个常量值。这就是为什么刻度标签显⽰在两个刻度的中间,⽽不是刻度本⾝。这⾥有⼀个⼩代码⽚段,显⽰离散轴对系列显⽰⽅式的影响。代码⽚段下的两个图像显⽰启⽤离散模式(第⼀个图像)或禁⽤(第⼆个图像)的结果。voidCMyClass::Init(){CChartStandardAxis*pBottomAxis=m_StandardAxis(CChartCtrl::BottomAxis);pBottomAxis->SetMinMax(0,10);CChartStandardAxis*pLeftAxis=m_StandardAxis(CChartCtrl::LeftAxis);pLeftAxis->SetMinMax(0,10);pBottomAxis->SetTickIncrement(false,1.0);pBottomAxis->SetDiscrete(true);CChartLineSerie*pSeries=m_LineSerie();doubleXVal[20];doubleYVal[20];for(inti=0;i<20;i++){XVal[i]=YVal[i]=i/2.0;}pSeries->SetPoints(XVal,YVal,20);}17181920Discrete mode enabledDiscrete mode disabled使⽤⽇期/时间轴使⽤⽇期/时间轴有点特别,下⾯是如何利⽤这个功能的解释。要了解⽇期/时间轴的重要⼀点是它们在COleDateTime对象内部⼯作。原因很简单:COleDateTime中有DATE类型的类,DATE类型是⼀个双精度型。由于图表中的点表⽰为双精度值,因此它⾮常适合:使⽤标准点(⾮⽇期/时间)和⽇期/时间点之间没有差异,这使得后者的使⽤不太复杂。所有点仍然存储为双精度型,⽆论是否是⽇期/时间。创建⽇期/时间轴后,可以在控件中填充数据。为此⽬的,没有改变:你必须从CChartSerie类调⽤void AddPoint(double X,double Y)或void SetPoints(double * X,double * Y,int Count)。 CChartCtrl类提供了两个静态函数,让你从COleDateTime转换为双精度,反之亦然:doubleDateToValue(constCOleDateTime&Date)COleDateTimeValueToDate(doubleValue)12如果您有另⼀种格式的⽇期(例如time_t或SYSTEMTIME),这不是⼀个问题,因为COleDateTime对象可以从不同的时间格式构造(检查COleDateTime类的MSDN⽂档,以了解从哪种格式可以构造它)。填充数据后,可以配置轴以显⽰所需的内容。 与⽇期/时间轴相关的⼏个功能可⽤:voidSetDateTimeIncrement(TimeIntervalInterval,intMultiplier)voidSetDateTimeFormat(boolbAutomatic,constTChartString&strFormat)voidSetReferenceTick(COleDateTime referenceTick)123第⼀个允许您指定轴上显⽰的两个节拍之间的间隔。两个节拍之间的间隔将遵守正确的时间,这意味着如果指定1个⽉的节拍增量(Interval=CChartAxis::tiMonth and Multiplier=1),则两个节拍之间的间隔将是不规则的(28,30或31天)。第⼆个函数允许您指定刻度标签的格式。控件根据刻度间隔⾃动格式化刻度标签,但您可以通过调⽤此函数覆盖它。检查MSDN上的COleDateTime :: Format函数的⽂档以获取更多信息。最后,SetReferenceTick(COleDateTime referenceTick)函数允许您为轴指定⼀个参考标记。参考标记是⽤作绘制标记的参考的⽇期:在该⽇期总是存在标记。当您在SetDateTimeIncrement函数中指定的multiplier 不是1时,这很有⽤。例如,假设您指定了3个⽉的单位增量,并且您希望在2⽉(因此,5⽉,8⽉,…)有⼀个单位,那么您可以调⽤此函数将2⽉1⽇设置为参考单位。默认设置为2000年1⽉1⽇。下⾯是⼀个简单的代码⽚段,它创建⼀个⽇期/时间轴,并显⽰不同函数的⽤法:voidCMyClass::Init(){// Sets the axis min value to January 1st 2006 and the axis// max value to December teTimeminValue(2006,1,1,0,0,0);COleDateTimemaxValue(2007,12,31,0,0,0);pBottomAxis->SetMinMax(CChartCtrl::DateToValue(minValue),CChartCtrl::DateToValue(maxValue));// Sets the tick increment to 4 months(disable automatic tick increment)pBottomAxis->SetTickIncrement(false,CChartDateTimeAxis::tiMonth,4);// Sets the tick labelformat for instance "Jan 2006"pBottomAxis->SetTickLabelFormat(false,_T("%b %Y"));}111213⾃定义外观控件的外观⽅⾯可以根据不同的应⽤场景做出更改,⽐如控件的不同部分(图例,标题,背景,…)都可以修改。 所有与这些对象的交互是通过CChartCtrl类来实现:⼀些将根据需要创建(例如axes或series),⼀些在创建控件时创建(legend,titles,…)。 ⼀般来说,你永远不会⾃⼰创建这些对象,⽽是将该任务委派给CChartCtrl类。 唯⼀的例外是当您要使⽤⾃定义轴或⾃定义系列(请参阅“扩展功能”部分)。 例如,下⾯是⼀个代码段,设置渐变背景,并将图例放在控件的底部:voidCMyClass::Init(){// SNIP// Disable the refresh of the controlm_Refresh(false);// Set the gradient for thebackgroundm_kGradient(RGB(255,255,255),RGB(125,125,255),gtVertical);// Dock the legend at thebottomm_end()->DockLegend(CChartLegend::dsDockBottom);// Specifies that the legend entries arehorizontally stackedm_end()->SetHorizontalMode(true);// Re-enable the refresh of thecontrolm_Refresh(true);}1234567891**重要:**从版本1.4的控件,每次调⽤控件上的⼀个属性将导致控件的完全刷新(即使像改变⼀些⽂本的字体或对象的颜⾊)。 为了避免在没有必要时刷新控件(例如,当您同时更改多个属性时),应⾸先禁⽤刷新,更改属性,然后重新启⽤刷新,如上⾯的代码段所⽰ 。⾃从1.5版的控件开始⽀持UNICODE。 所有出现的std :: string对象已被TChartString对象替换,这只是⼀个typedef,如果未启⽤UNICODE,则解析为std :: string,并在启⽤UNICODE时解析为std :: wstring。响应⿏标事件有时,应⽤程序需要响应⽤户⿏标操作。 例如,如果⽤户点击点,则程序可以显⽰关于被点击的点的信息,这⼀节将解释如何做到。虽然原理是有点不同,但是⽆论你想听在图表上的⼀般⿏标事件本⾝(点击轴,图例,…)或你是否对特定系列的⿏标事件感兴趣。 这两种情况都很容易实现。响应图表上的⿏标事件你必须实现CChartMouseListener接⼝,覆盖你感兴趣的⽅法,并通过调⽤CChartCtrl ::RegisterMouseListener(CChartMouseListener * pMouseListener)将该类的实例注册到图表控件。 根据⿏标事件发⽣在控件的哪个部分:标题,图例,轴或绘图区,调⽤该接⼝上的不同函数。 对于所有这些函数,总是传递两个参数:MouseEvent,它是列出⿏标事件类型(⿏标移动,左键单击,…)的枚举,以及⼀个CPoint对象,它包含的发⽣事件的点的屏幕坐标。

对于某些函数,需要时传递⼀些其他参数。 例如,当单击⼀个轴时,指向该轴的指针被传递给该函数。下⾯是CChartMouseListener的实现,它对轴的点击作出反应,并显⽰⼀个消息框:classCCustomMouseListener:publicCChartMouseListener{public:voidOnMouseEventAxis(MouseEvent mouseEvent,CPointpoint,CChartAxis*pAxisClicked){if(mouseEvent==CChartMouseListener::LButtonDoubleClick){MessageBox(_T("Axisclicked"),_T("Info"),MB_OK);}}};1112然后你必须创建⼀个这个类的实例并注册它:m_pMouseListener=newCCustomMouseListener();m_erMouseListener(m_pMouseListener);12这⾥也需要⾃⼰删除指针。响应系列上的⿏标事件响应系列上的事件与响应⼀般事件⾮常相似,只是监听器是CChartSeriesMouseListener的⼀个实例,它是⼀个模板类,模板参数是系列的点类型。 这是需要的,以避免当您要检索点的特定值时不必要的转型。 另⼀个区别是,您必须在系列本⾝上注册监听器,⽽不是在图表控件上注册。下⾯是CChartSeriesMouseListener的实现,它对系列的点击做出反应,如果点击发⽣在点上,它将显⽰⼀个带有点的Y值的消息框:classCCustomMouseListener:publicCChartSeriesMouseListener{public:voidOnMouseEventSeries(MouseEventmouseEvent,CPoint point,CChartSerieBase*pSerie,unsigneduPointIndex){if(mouseEvent==CChartMouseListener::LButtonDoubleClick&&uPointIndex!=INVALID_POINT){TChartStringStreamssText;SChartXYPoint Point=pSeries->GetPoint(uPointIndex);ssText<<_T("Y value=")<AddString(_T("An example of oscilloscope"));// Change the colorof the line seriespLineSeries->SetColor(SerieColor);// Finally re-enable the refresh of the control. This will refresh the// controlif any refresh was still 'pending'.m_Refresh(true);829363738394647484956575859“Income over 2008” example:srand((unsignedint)time(NULL));// Disable therefreshm_Refresh(false);COleDateTimeMin(2008,1,1,0,0,0);COleDateTimeMax(2008,10,1,0,0,0);// Create thebottom axis and configure itproperlyCChartDateTimeAxis*pBottomAxis=m_DateTimeAxis(CChartCtrl::BottomAxis);pBottomAxis->SetMinMax(Min,Max);pBottomAxis->SetDiscrete(true);pBottomAxis->SetTickIncrement(false,CChartDateTimeAxis::tiMonth,1);pBottomAxis->SetTickLabelFormat(false,_T("%b"));// Create the leftaxis and configure it properlyCChartStandardAxis*pLeftAxis=m_StandardAxis(CChartCtrl::LeftAxis);pLeftAxis->SetMinMax(0,100);pLeftAxis->GetLabel()->SetText(_T("Units sold"));// Create the right axis and configure itproperlyCChartStandardAxis*pRightAxis=m_StandardAxis(CChartCtrl::RightAxis);pRightAxis->SetVisible(true);pRightAxis->GetLabel()->SetText(_T("Income (kEuros)"));pRightAxis->SetMinMax(0,200);// Configure thelegendm_end()->SetVisible(true);m_end()->SetHorizontalMode(true);m_end()->UndockLegend(80,50);// Add text to the title and set the font & colorm_le()->AddString(_T("Income over2008"));CChartFont titleFont;t(_T("Arial Black"),120,true,false,true);m_le()->SetFont(titleFont);m_le()->SetColor(RGB(0,0,128));// Sets a gradientbackgroundm_kGradient(RGB(255,255,255),RGB(150,150,255),gtVertical);// Create two bar series and a lineseries and populate them withdataCChartBarSerie*pBarSeries1=m_BarSerie();CChartBarSerie*pBarSeries2=m_BarSerie();CChartLineSerie*pLineSeries=m_LineSerie(false,true);intlowIndex=-1;intlowVal=999;for(inti=0;i<9;i++){COleDateTimeTimeVal(2008,i+1,1,0,0,0);intDesktopVal=20+rand()%(100-30);pBarSeries1->AddPoint(TimeVal,DesktopVal);intLaptopVal=10+rand()%(80-20);pBarSeries2->AddPoint(TimeVal,LaptopVal);intIncome=DesktopVal+LaptopVal*1.5;if(IncomeAddPoint(TimeVal,Income);}// Configure the series properlypBarSeries1->SetColor(RGB(255,0,0));pBarSeries1->SetName(_T("Desktops"));pBarSeries2->SetColor(RGB(68,68,255));pBarSeries2->SetGradient(RGB(200,200,255),gtVerticalDouble);pBarSeries2->SetName(_T("Laptops"));pBarSeries2->SetBorderColor(RGB(0,0,255));pBarSeries2->SetBorderWidth(3);pLineSeries->SetColor(RGB(0,180,0));pLineSeries->SetName(_T("Total income"));pLineSeries->SetWidth(2);pLineSeries->EnableShadow(true);// Add a label on the StringStream labelStream;labelStream<<_T("Min income: ")<*pLabel=pLineSeries->CreateBalloonLabel(lowIndex,()+_T("kEuros"));CChartFont labelFont;t(_T("Microsoft Sans Serif"),100,false,true,false);pLabel->SetFont(labelFont);// Reenable the refreshm_Refresh(true);829363738394647484956575859666768697677787980818283FeedbackQuite a lot of work is involved in the development of this control and, as any other software project, it might still contain bugsor errors in the documentation. If you encounter such a problem, please let me know (even if you fixed it yourself) so that Ican fix the issue as soon as possible. Other users of the control will thank you for that. The same if you encounter errors inthe documentation or typos in the article.I’m also more or less constantly working on this control to add new features. If you have some requirement for a nicefeature that could be useful for others, please let me know and I’ll add it to my wishlist. However, as I’m working on thiscontrol in my spare time, my time is rather y, if you liked this control, do not hesitate to drop me a word in the discussion forum or to rate the article, this is muchappreciated. Thank y08/05/2006: Release of version 1.019/08/2006: Release of version 1.1Bug fix in ScreenToValue function (CChartAxis)Bug fix in RemoveAllSeries function (CChartCtrl)Added support for manual zoomAdded support for mouse panningAbility to specify a tick increment on the axisAdded support for resizing the control09/04/2007: Release of version 1.2GDI leak correctedInvisible series are not taken in account for auto axis and legend (thanks to jerminator-jp)Ability to change the text color of the axisAbility to change the color of the border of the drawing areaSurface series added16/02/2008: Release of version 1.3Added date/time axisBug fix in how the logarithmic labels are displayed (trailing 0)Ability to change the color of the zoom rectangleRemoved compiler warnings for VC2005Bug fix in the zoom14/04/2008: Release of version 1.4Added support for scrollbarsBar series addedLegend can be docked on any side or floatingSupport for legend in horizontal modeSupport for transparent background on the legendSupport for shadow for several objectsRemovePointsFromBegin, RemovePointsFromEnd and AddPoints in the CChartSeries classSupport for gradient backgroundEnableRefresh and UndoPanZoom functions added in CChartCtrlPossibility to enable/disable the zoom for a specific axis and to set its limitSpeed improvement on the series (min and max cached, ordering of the series)Series can be removed using their pointersBug fix for invisible series in the legendBug fix for logarithmic axis (1 digit was not displayed)Bug fix when removing series from the controlBug fix if the pen width is bigger than 1 for line seriesBug fix for automatic axis20/08/2008: Release of version 1.5Added support for UNICODEAdded support for printingAuto-hide scrollbarsBaseline selection for bar seriesPerformance patchScrollbar flickering removed (see here)Bug fix: scrollbar is now updated when axis is pannedBug fix: calling AddPoint was not drawing the new pointBug fix: tick labels for log axis were not always correct (rounding error)Bug fix: last point of ChartPointSerie was not displayedBug fix: moving the mouse outside the control doesn’t stop the zoom or pan operation (the button can be released outsidethe control)13/04/2009: Release of version 2.0The different axis types are now separated into different classesModified the way to add series to the control for improved flexibilityAdded cursorsAbility to display discrete axesAbility to be notified about mouse events occurring on the controlAdded labels on pointsAbility to display a smooth curveAdded ChartFont: allows for italic, bold or underlined fontsAdded the SetReferenceTick function for date/time axisAbility to store user data for each pointSeries now have an IdRemoved the CChartObject classPoints are now stored in a standard array instead of a std::vector for efficiencyBinary search implemented for finding the first and last visible points (for efficiency)The line series now uses PolyLine instead of MoveTo/LineTo (efficiency)Bug fix when using date/time axis with a tick interval in yearsBug fix: bar series were drawn from the wrong axis11/06/2009: Release of version 2.0.1Optimization: the pan feature has been smoothedOptimization: points with the same X and Y values are not plotted anymore for the line fix: in some situations, the code was crashing when accessing points outside the valid rangeBug fix: when series were removed, the legend was accessing removed series (which crashed)Bug fix: when a series was cleared, new points were not drawn properlyBug fix: inserting a point for which the X value already existed in the series did not add the point properlyBug fix with the CChartFont class07/08/2009: Release of version 2.0.2Bug fix: the control was crashing when a series with no points and no ordering was addedBug fix: the shadow of the line was not drawn correctlyBug fix: when an automatic date/time axis was used without any data, the code crashed28/12/2009: Release of version 3.0.0Series are now template classes with the template parameter being the point type. This allows the control to manipulate anytype of pointsAdded candlestick and Gantt seriesAdded support to save the chart to an image fileBar series can be stackedAdded a new automatic mode for axes: the screen automatic modeListening for mouse events on a series has been moved to a CChartSeriesMouseListener classBug fix: when a point X or Y value is modified, the series is reorderedBug fix: setting a tick increment on a standard axis did not show the digits properly17/01/2010: Release of version 3.0.1Bug fix: when using labels with the points series, the border of the points was changing color. Fixed by providing a way tospecify the border fix: the code was crashing when clicking on a series without having registered a mouse listener on the fix: detection of mouse events on certain series type was crashingBug fix: CChartTitle::SetVisible was not implemented13/07/2010: Release of version 3.0.2Bug fix: the high-speed functionality has been removed by mistakeBug fix: the draw function of the line series was not drawing pointsBug fix: replaced Clear() by clear() in the ClearSerie fix: Added implementation of ctor/dtor for the CChartCursorListener classBug fix: memory leak when the series was cleared (labels were not deleted)ThanksI would like to thank all the people from this community, they were a great help when I started programming. Thanks also to allthe people who contributed to this control with their various help or feedback: toxcct, Chris Maunder, Kevin Hoffman,jerminator-jp, Laurie Gellatly, Eugene Pustovoyt, Andrej Ritter, Nick Holgate, Nick Schultz, Johann Obermayr, Pierre Schrammand Kevin Winter. A special thanks to Bruno Lavier for the time spent working on the control. I hope I didn’t forget hris Maunder’s Colour Picker Control (used in the demo application)Drawing smooth curves with Bezier PrimitivesLicenseThis article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)遇到的问题以及解决⽅案 Assertion Failed! File: Line:639这个BUG是多线程使⽤过程中遇到的,ChartControl类不能在多线程中直接使⽤,直接使⽤可能出现抢DC的情况出现,多谢前辈的指点:使⽤CDC:GetTextExtent出现了有关问题的奇怪有关问题,m_hDC 是有效的,但GetTextExtent内部调⽤GetExtentPoint32有时失败这个BUG的特殊之处就是你使⽤断点调试使调不出来的( ……这个是不是很厉害…… ),所以⼀定的注意许多函数并不是线程安全的。解决办法: 所有绘图操作都使⽤消息响应的⽅式进⾏实现,定义消息,并在绘图界⾯对话框类中直接定义响应函数,进⾏绘制,⽽不是在⼯作线解决办法:程中直接绘图,这个样⼦就不会和对话框争夺DC,从⽽避免失效。

2023年6月20日发(作者:)

(转载)翻译本⽂翻译在CodeProject上的介绍(主要还是⾕歌翻译,看不太明⽩的地⽅,请对⽐原⽂,敬请原谅),⽅便⾃⼰和后⾯⼈的学习(花费了两天时间,希望是值得的)。推荐⼀个前辈写的东西:TeeChart替代品,MFC下好⽤的⾼速绘图控件-(Hight-Speed Charting),⾃⼰也转载了这篇⽂章,在转载的⽂章中根据⾃⼰的实验修改了⼀些东西,修改了什么现在也不记得了,⽂章地址:TeeChart替代品,MFC下好⽤的⾼速绘图控件-(Hight-Speed Charting) .下⾯是我能找到的资料(点击超链接下载)前三个是原⽂提供的(要想找到最新的,到原⽂中找),后三个是⾃⼰在其他的博客中找到的。ChartCtrl_rtCtrl_rtCtrl_动态绘制曲线图-HightSpeedChart实现.rarTeeChart和HightSpeedChart动态绘图.rar2018年5⽉6⽇补充⼀点使⽤过程中遇到的问题:遇到的问题以及解决⽅案⽂章⽬录综述介绍免责声明主要特点⽂档结构⼊门学习⼿动插⼊使⽤资源管理器操作数据序列柱状图系列在点上添加标签对轴的操作处于离散模式下的轴使⽤⽇期/时间轴⾃定义外观响应⿏标事件响应图表上的⿏标事件响应系列上的⿏标事件使⽤光标使⽤平移和缩放功能利⽤⾼速功能扩展功能Upgrading from Version 1.x to Version 2.0Upgrading from Version 2.x to Version 3.x例⼦FeedbackHistoryThanksLinksLicense遇到的问题以及解决⽅案 Assertion Failed! File: Line:639吐槽综述这个控件是⽤来展⽰2D数据,如下⾯的图⽚。介绍对于我之前的⼀个项⽬,我需要在图表控件上显⽰连续的数据流。 我决定开发⾃⼰的控件,因为我找不到任何可以提供所需灵活性的⾃由软件控件。 其中⼀个主要的限制是,控件必须绘制⼤量的数据,并能够迅速显⽰它(在Pocket PC上)。 控件能够通过仅绘制新的数据点⽽不是完整的数据序列来做到这⼀点并且图表还能够显⽰静态数据。这种控件是我长时间⼯作的结果,⽽且费尽周折地为了提供⾜够的灵活性来供需要它的⼈使⽤。 对于使⽤者反馈我表⽰由衷的感谢:⼀个邮件,留⾔板中的⼀⼀句话或只是对本⽂评级。 当我不知道是否还有⼈使⽤它时,我就没有必要维护这个控件了。免责声明这个控件是我花费很长时间的开发的结果,因此我对代码的使⽤放置⼀些⼩条件:该代码可以以编译的形式⽤于任何⾮商业和商业⽬的。代码可以被重新开发,只要它提供作者名字和完整的免责声明。 更改源代码需要得到作者的同意。此代码不提供任何安全保证。 我不会对使⽤此代码造成的损失负责。 使⽤它需要⾃⼰承担风险。123This code may be used for any non-commercialand commercial purposes in a compiled code may be redistributed as long as it remainsunmodified and providing that the author nameand the disclaimer remain intact. The sourcescan be modified with the author consent code is provided without any guarantees.I cannot be held responsible for the damage orthe loss of time it causes. Use it at your own risks.鉴于开发这个控件所付出的努⼒,下⾯的要求并不过分: 如果你在在商业应⽤程序中使⽤这个控件,那么请给我发邮件让我知道。主要特点控件的主要特点是:⾼速绘图(轴固定时),允许快速绘制数据⽆限数量的数据序列(内存是限制)每个数据序列的数据量不受限制⽀持线图,点图,平⾯图,柱状图,K线图 和⽢特图系列最多四个轴(左,下,右和上轴)标准轴,对数轴或⽇期/时间轴⾃动伸缩的坐标轴, 翻转的坐标轴(相互独⽴)轴标签点标签平滑的曲线⽹格图例和标题交互性(在控件中发⽣特定事件时的通知)⽀持⼿动缩放和⿏标平移⽀持⿏标指针⽀持轴上的滚动条⾼度可定制(颜⾊,标题,标签,边缘,字体等)⽀持UNICODE⽀持打印和保存到图像⽂件⽂档结构本⽂通过⼀系列简短的教程来涵盖控件的⼤部分功能。 阅读本⽂后,您将能够快速地在⾃⼰的应⽤程序中使⽤本控件。我决定从⽂章中删除所有的类和函数的⽂档,因为它不是⾮常友好并且我很难维护。 此外,随着代码的增长,要记录的类和函数的列表变得过于⼴泛以⾄于不能将所有内容放在⽂章中。 作为替代,我提供了⼀个doxygen⽂档,您可以从本⽂中(⽂章的开头)下载:只需下载“Doxygen⽂档”zip⽂件,解压所有⽂件,双击“”⽂件,进⾏查看。⼊门学习此图表控件允许您在屏幕上绘制⼀系列数据。 此控件可以添加⼏个不同类型数据序列并且最多可以使⽤四个轴。 添加到图表的数据序列与⼀个⽔平轴(底部或顶部)和⼀个垂直轴(右侧或左侧)相关联。 这两个轴控制数据序列在图表上的显⽰⽅式。为了能够在应⽤程序中使⽤次图表控件,您⾸先需要在⾃⼰的⼯程⾥添加源代码zip中包含的⽂件。**注意:**控件在内部使⽤动态转型,因此必须启⽤RTTI(RunTime Type Information 运⾏时⾃动类型识别的机制),否则可能会发⽣崩溃。默认情况下,VC6没有启⽤RTTI,因此要启⽤它打开项⽬设置 - >“C / C ++”选项卡 - >“C ++语⾔”类别,并确保“Enable Run-TimeType Information (RTTI) "选项已选中。在应⽤程序中使⽤图表控件有两种⽅法:⼿动插⼊,或通过资源编辑器插⼊。⼿动插⼊1.#include "ChartCtrl"添加在对话框(Dialog)类的头⽂件中2.在对话框类中添加变量CChartCtrl://{{AFX_DATA(CChartDemoDlg)//}}AFX_DATACChartCtrl m_ChartCtrl;12343.在对话框类的OnInitDialog⽅法中添加这个控件的Create⽅法。使⽤资源管理器1.向对话框资源添加⾃定义控件,打开控件的属性,并为Class属性指定ChartCtrl。 为了避免滚动条上的闪烁,必须设置WS_CLIPCHILDREN样式(0x02000000L),如图所⽰。2.#include "ChartCtrl.h"添加在对话框(Dialog)类的头⽂件中3.在对话框类中添加变量CChartCtrl://{{AFX_DATA(CChartDemoDlg)//}}AFX_DATACChartCtrl m_ChartCtrl;12344.在DoDataExchange函数中添加DDX_Control(不要忘了更改ID号和控件名字):voidCChartDemoDlg::DoDataExchange(CDataExchange*pDX){CDialog::DoDataExchange(pDX);//{{AFX_DATA_MAP(CChartDemoDlg)// Add this line with the appropriate ID and variablenameDDX_Control(pDX,IDC_CHARTCTRL,m_ChartCtrl);//}}AFX_DATA_MAP}12345678操作数据序列⼏种类型的数据序列可以添加到控制:点序列,线序列,曲⾯序列,柱状图序列,K线图序列或⽢特图序列。 点的数据格式可能因序列⽽异(例如,K线图和⽢特图系列使⽤不同的点格式)。Series typeDescriptionCreate functionPoint typePoint seriesEach data point is represented by a single point on the screen. The appearance of the point can PointsSerieSChartXYPointLine seriesThe data points are connected through a line. The appearance of this line can be customized and it can also LineSerieSChartXYPointSurface seriesThe data points are connected through a line and the area under this line is filled with a specific brush. Theseries can also be displayed SurfaceSerieSChartXYPointBar seriesEach data point is plotted as a vertical bar of a certain width. Multiple bar series can be stacked next to each otherwithout overlapping. The bars can also be plotted BarSerieSChartXYPointCandlestick seriesEach data point is made of five attributes: the low value, the high value, the open value, the close value andthe X value (time). Each point is drawn as a candlestick. This series is used for plotting CandlestickSerieSChartCandlestickPointGantt seriesEach data point is made of three attributes: the start and end time and a Y value. Each point is drawn as ahorizontal bar starting at the start time and finishing at the end time. The bar is positioned along the Y axis at its GanttSerieSChartGanttPoint⼀旦你选择了⼀种系列,你可以通过调⽤上表中列出的CChartCtrl类的辅助函数之⼀将其添加到图表中。 这些函数接受两个可选参数:两个布尔值来确定描述该系列是连接到副⽔平轴(顶轴)或者是连接⼤副垂直轴(右轴)。 如果未指定参数,则数据系列将附加到主⽔平轴(底部轴)和主垂直轴(左轴)。**警告: **在将任何系列添加到图表之前,您需要创建该系列所连接的两个轴。 如果不这样做,将导致控件失效(assert)。 有关详细信息,请参见“操纵轴”⼀节。⼀旦将系列添加到图表后,我们就可以使⽤数据填充该图表。 有两种⽅法:将数据放到⼀个单元中⼀起添加,或者逐点添加。 后者⽤于有动态数据时:每次调⽤函数时都会更新图表。 虽然这个调⽤是快速的(在某些特定条件下),但是最好尽可能地将数据放到⼀个单元中。 下⾯是⼀个简单代码⽰例,它在图表中创建两个系列,并⽤数据填充它们:⼀个系列在初始化时完全填充,另⼀个系列在调⽤OnDataReceived函数(仅存在于此⽰例的⽬的)时填充。 m_pLineSeries,m_pPointsSeries和m_ChartCtrl是CMyClass类的成员变量。voidCMyClass::Init(){....// SNIP: Creation of the axes in the chart. This MUST be donebefore.m_pLineSeries=m_LineSerie();m_pPointsSeries=m_PointsSerie();doubleYValues[10];for(inti=0;i<10;i++)XValues[i]=YValues[i]=i;m_pLineSerie->SetPoints(XValues,YValues,10);}voidCMyClass::OnDataReceived(doubleX,doubleY){m_pPointsSeries->AddPoint(X,Y);}所有系列类继承⾃同⼀抽象基类:CChartSerie。该类处理所有系列通⽤的功能,但对具体的数据点没有任何处理功能。点的概念在⼦类CChartSerieBase中引⼊,它是⼀个模板类,模板参数是要操作为点的数据类型。这很重要,因为序列可能必须处理不同的数据类型:例如点序列操作具有X和Y值的点,但是K线图系列操纵具有5个值(打开,关闭,⾼,低和时间值)的点。其他系列继承⾃CChartSerieBase并提供他们操作的数据类型。 CChartSerieBase类已经处理了⼤多数数据管理,并通过纯虚函数将渲染委托给⼦类。每个系列在创建时也会分配⼀个Id。此标识可通过CChartSerie :: GetSerieId()检索,并可⽤于从图表中删除该系列。该系列的⼀个重要特征是控制点的顺序:该系列中的所有点将根据它们的值重新排序。 默认情况下,点是基于它们的X值排序的,但您可以通过对它们的Y值排序或不对它们进⾏排序来改变这种⾏为(在这种情况下,系列保持将点添加到系列中的顺序 )。 对点进⾏排序会对性能产⽣影响:如果点是有序的,则控件能够从完整系列中检索第⼀个和最后⼀个可见点,并且仅绘制两个点之间的点。 另⼀⽅⾯,你将不能绘制像椭圆形的曲线。 您可以通过调⽤CChartSerieBase :: SetSeriesOrdering来更改点的顺序。控件中的不同系列的功能通常是不⾔⾃明的。 然⽽,柱状图系列需要⼀些解释。柱状图系列这个系列有点特别,如果其中⼏个在同⼀个控件上绘制在⼀起,他们将互相影响。 ⽬的是能够绘制多个条形图系列,⽽不会重叠:它们是彼此相邻绘制的。 为此,您需要指定每个所属的组(⼀个简单的整数标识符)。 同⼀组的系列彼此相邻地绘制(或者对于⽔平条在彼此的顶部):参见两个图形的⽰例。 设置组ID是通过SetGroupId函数完成的。Bar series with the same group IdBar series with different group Id您还可以通过调⽤SetInterSpace静态函数来控制所有柱形图之间剩余的空间的宽度。 这将为所有系列设置以像素为单位的空间(因此,如果显⽰多于两个系列,则在任何位置使⽤相同的空间)。 注意,您可以通过调⽤SetBarWidth单独设置柱状图系列的宽度。在点上添加标签⼀旦使⽤数据填充您的系列,您还可以在系列的特定点上添加标签:这个标签始终附加到特定点。 现在,只提供⼀种类型的标签,⽓泡标签:包含⽂本的圆⾓矩形并⽤线连接到特定点上。 当然,如果需要,您也可以提供⾃⼰的⾃定义标签(参见“扩展功能”⼀节)。有两种⽅式创建⽂本标签:静态创建标签时,或动态注册⼀个对象,当标签请求时,它将提供⽂本。 第⼀种⽅法是最简单的,但也不太灵活。 下⾯是⼀个代码⽚段,显⽰如何做(假设m_pSeries已经创建并填充⾜够的数据):voidCMyClass::Init(){// _pSeries->CreateBalloonLabel(5,_T("This is a simple label"));}12345此调⽤将创建⼀个带有“This is a simple label”⽂本的标签,并将其附加到带索引为5的点。该函数返回⼀个指向新创建的标签的指针,以便您可以修改其某些属性或存储以供以后使⽤。第⼆种⽅法有点复杂,但提供了更多的灵活性:例如,您可以以更⽅便的⽅式在标签中显⽰点属性(例如X值,Y值,…)。 为此,您必须创建⼀个继承⾃CChartLabelProvider 的类,并在创建标签时提供此类的实例。 此类是模板类,模板参数是标签附加到的系列的点类型。 这个类是⼀个简单的接⼝,你必须覆盖TChartString GetText(CChartSerieBase * pSerie,unsigned uPtIndex)⽅法。 此函数应返回必须在标签中显⽰的⽂本。 它接收指向标签所附加的系列和点索引的指针。 这⾥有⼀个这样的标签提供程序类的例⼦:classCCustomLabelProvider:publicCChartLabelProvider{public:TChartStringGetText(CChartSerieBase*pSeries,unsigneduPtIndex){TChartStringStreamssText;SChartXYPoint Point=pSeries->GetPoint(uPtIndex);ssText<<_T("X value=")<CreateBalloonLabel(5,m_pLabelProvider);}1234567控件不获取指针的所有权,因此,当你不再需要时,你有责任删除它。 在上⾯的例⼦中,它通常会在CMyClass析构函数中被删除。 在上⾯的⽰例中,您可以为所有要添加的标签地⽅重复使⽤相同的标签类, 这也带来另⼀个优点:如果你想在运⾏时改变标签的格式,你只需要在CustomLabelProvider中添加代码。 不需要遍历所有现有标签并更改其⽂本。

当然,在这种情况下,需要刷新控件,因为必须重新绘制标签。还要注意TChartStringStream类的⽤法,TChartStringStream类是由控件提供的别名(类似于TChartString)。 当UNICODE被定义时,它解析为std :: wstringstream,当未定义UNICODE时,解析为std :: stringstream。对轴的操作轴是图表的⼀个重要特征,因为它们控制不同系列在控制中的显⽰⽅式。 控件中最多可使⽤四个轴:底部,顶部,左侧和右侧。 控件的每个系列必须和⼀个⽔平轴和⼀个垂直轴相连接。 在图表中添加系列时指定这些轴。 底部和左侧轴是主轴,顶部和右侧轴是辅助轴(您将在控件的某些功能中遇到此问题)。 现在有三种类型的轴供选择:标准轴,对数轴和⽇期/时间轴。 您可以在不同位置选⽤不同类型的轴。⼀旦您选择了在不同位置使⽤哪些轴,您需要先创建它们,然后才能向控件添加任何数据。 为此,通过指定轴附加在哪个位置,简单地调⽤CreateStandardAxis,CreateLogarithmicAxis或CreateDateTimeAxis。 如果已经在该位置创建了轴,则控件将销毁它并且⽤新的轴替换它。 这⾥有⼀个简单的代码⽚段,显⽰如何在底部创建⽇期/时间,在左侧创建⼀个标准轴:voidCMyClass::Init(){CChartStandardAxis*pBottomAxis=m_StandardAxis(CChartCtrl::BottomAxis);CChartLogarithmicAxis*pLeftAxis=m_LogarithmicAxis(CChartCtrl::LeftAxis);}1234567⼀旦创建了这些轴,就可以对它们设置⼀些属性。 ⼤多数属性在所有轴类型之间共享(例如⾃动模式,最⼩值和最⼤值,轴标签,…)。 轴可以设置为三种“⾃动”模式:全⾃动,屏幕⾃动和⼿动模式。全⾃动模式基于附加到该轴的所有系列计算轴最⼩值和最⼤值(所有系列的所有点的最⼩值⽤作轴的最⼩值,并使⽤所有系列的所有点的最⼤值作为轴的最⼤值)。屏幕⾃动模式基于与该轴相关的所有系列的所有可见点计算轴最⼩值和最⼤值。 例如,如果图表仅显⽰连接到⼿动底部轴和屏幕⾃动左侧轴的⼀个系列,则左侧轴将⾃适应于当前可见的点,并且不考虑这些点有可能超过底轴的范围(在全⾃动模式下,底轴外部的点将被考虑)。 **警告:**如果系列的两个轴都处于屏幕⾃动模式,则结果未定义。在⼿动模式下,轴最⼩和最⼤值由⽤户设置,不由控件计算。在使⽤⾃动轴模式下,如果将数据动态添加到控件,如果新的数据点位于轴的范围之外,那么控件将⾃动刷新。 这⾥是⼀个代码⽚段(继续前⼀个代码段),显⽰⼀个全⾃动轴(底部轴)和⼀个⼿动轴(左轴,它是⼀个对数轴):voidCMyClass::Init(){// SNIP ...pBottomAxis->SetAutomaticMode(CChartAxis::FullAutomatic);// The call toSetAutomaticMode(CChartAxis::NotAutomatic) is not// really needed because this is the xis->SetAutomaticMode(CChartAxis::NotAutomatic);pLeftAxis->SetMinMax(0.01,1000);}123456789处于离散模式下的轴轴有⼀个模式是离散模式(默认禁⽤)。此模式指定轴不显⽰连续值,⽽只显⽰离散值,这些值是轴上刻度指定的值,⽽轴将不显⽰其他的值。尝试绘制不同于显⽰的节拍值的值是不可能的。让我们举⼀个例⼦:假设你有⼀个底部标准轴,间隔为1.0(所以,显⽰的蜱是1,2,3等等)。尝试绘制X值为0.5的点将在相同位置显⽰该点,就好像它的值为1.0。事实上,你可以认为两个刻度之间的区域是⼀个常量值。这就是为什么刻度标签显⽰在两个刻度的中间,⽽不是刻度本⾝。这⾥有⼀个⼩代码⽚段,显⽰离散轴对系列显⽰⽅式的影响。代码⽚段下的两个图像显⽰启⽤离散模式(第⼀个图像)或禁⽤(第⼆个图像)的结果。voidCMyClass::Init(){CChartStandardAxis*pBottomAxis=m_StandardAxis(CChartCtrl::BottomAxis);pBottomAxis->SetMinMax(0,10);CChartStandardAxis*pLeftAxis=m_StandardAxis(CChartCtrl::LeftAxis);pLeftAxis->SetMinMax(0,10);pBottomAxis->SetTickIncrement(false,1.0);pBottomAxis->SetDiscrete(true);CChartLineSerie*pSeries=m_LineSerie();doubleXVal[20];doubleYVal[20];for(inti=0;i<20;i++){XVal[i]=YVal[i]=i/2.0;}pSeries->SetPoints(XVal,YVal,20);}17181920Discrete mode enabledDiscrete mode disabled使⽤⽇期/时间轴使⽤⽇期/时间轴有点特别,下⾯是如何利⽤这个功能的解释。要了解⽇期/时间轴的重要⼀点是它们在COleDateTime对象内部⼯作。原因很简单:COleDateTime中有DATE类型的类,DATE类型是⼀个双精度型。由于图表中的点表⽰为双精度值,因此它⾮常适合:使⽤标准点(⾮⽇期/时间)和⽇期/时间点之间没有差异,这使得后者的使⽤不太复杂。所有点仍然存储为双精度型,⽆论是否是⽇期/时间。创建⽇期/时间轴后,可以在控件中填充数据。为此⽬的,没有改变:你必须从CChartSerie类调⽤void AddPoint(double X,double Y)或void SetPoints(double * X,double * Y,int Count)。 CChartCtrl类提供了两个静态函数,让你从COleDateTime转换为双精度,反之亦然:doubleDateToValue(constCOleDateTime&Date)COleDateTimeValueToDate(doubleValue)12如果您有另⼀种格式的⽇期(例如time_t或SYSTEMTIME),这不是⼀个问题,因为COleDateTime对象可以从不同的时间格式构造(检查COleDateTime类的MSDN⽂档,以了解从哪种格式可以构造它)。填充数据后,可以配置轴以显⽰所需的内容。 与⽇期/时间轴相关的⼏个功能可⽤:voidSetDateTimeIncrement(TimeIntervalInterval,intMultiplier)voidSetDateTimeFormat(boolbAutomatic,constTChartString&strFormat)voidSetReferenceTick(COleDateTime referenceTick)123第⼀个允许您指定轴上显⽰的两个节拍之间的间隔。两个节拍之间的间隔将遵守正确的时间,这意味着如果指定1个⽉的节拍增量(Interval=CChartAxis::tiMonth and Multiplier=1),则两个节拍之间的间隔将是不规则的(28,30或31天)。第⼆个函数允许您指定刻度标签的格式。控件根据刻度间隔⾃动格式化刻度标签,但您可以通过调⽤此函数覆盖它。检查MSDN上的COleDateTime :: Format函数的⽂档以获取更多信息。最后,SetReferenceTick(COleDateTime referenceTick)函数允许您为轴指定⼀个参考标记。参考标记是⽤作绘制标记的参考的⽇期:在该⽇期总是存在标记。当您在SetDateTimeIncrement函数中指定的multiplier 不是1时,这很有⽤。例如,假设您指定了3个⽉的单位增量,并且您希望在2⽉(因此,5⽉,8⽉,…)有⼀个单位,那么您可以调⽤此函数将2⽉1⽇设置为参考单位。默认设置为2000年1⽉1⽇。下⾯是⼀个简单的代码⽚段,它创建⼀个⽇期/时间轴,并显⽰不同函数的⽤法:voidCMyClass::Init(){// Sets the axis min value to January 1st 2006 and the axis// max value to December teTimeminValue(2006,1,1,0,0,0);COleDateTimemaxValue(2007,12,31,0,0,0);pBottomAxis->SetMinMax(CChartCtrl::DateToValue(minValue),CChartCtrl::DateToValue(maxValue));// Sets the tick increment to 4 months(disable automatic tick increment)pBottomAxis->SetTickIncrement(false,CChartDateTimeAxis::tiMonth,4);// Sets the tick labelformat for instance "Jan 2006"pBottomAxis->SetTickLabelFormat(false,_T("%b %Y"));}111213⾃定义外观控件的外观⽅⾯可以根据不同的应⽤场景做出更改,⽐如控件的不同部分(图例,标题,背景,…)都可以修改。 所有与这些对象的交互是通过CChartCtrl类来实现:⼀些将根据需要创建(例如axes或series),⼀些在创建控件时创建(legend,titles,…)。 ⼀般来说,你永远不会⾃⼰创建这些对象,⽽是将该任务委派给CChartCtrl类。 唯⼀的例外是当您要使⽤⾃定义轴或⾃定义系列(请参阅“扩展功能”部分)。 例如,下⾯是⼀个代码段,设置渐变背景,并将图例放在控件的底部:voidCMyClass::Init(){// SNIP// Disable the refresh of the controlm_Refresh(false);// Set the gradient for thebackgroundm_kGradient(RGB(255,255,255),RGB(125,125,255),gtVertical);// Dock the legend at thebottomm_end()->DockLegend(CChartLegend::dsDockBottom);// Specifies that the legend entries arehorizontally stackedm_end()->SetHorizontalMode(true);// Re-enable the refresh of thecontrolm_Refresh(true);}1234567891**重要:**从版本1.4的控件,每次调⽤控件上的⼀个属性将导致控件的完全刷新(即使像改变⼀些⽂本的字体或对象的颜⾊)。 为了避免在没有必要时刷新控件(例如,当您同时更改多个属性时),应⾸先禁⽤刷新,更改属性,然后重新启⽤刷新,如上⾯的代码段所⽰ 。⾃从1.5版的控件开始⽀持UNICODE。 所有出现的std :: string对象已被TChartString对象替换,这只是⼀个typedef,如果未启⽤UNICODE,则解析为std :: string,并在启⽤UNICODE时解析为std :: wstring。响应⿏标事件有时,应⽤程序需要响应⽤户⿏标操作。 例如,如果⽤户点击点,则程序可以显⽰关于被点击的点的信息,这⼀节将解释如何做到。虽然原理是有点不同,但是⽆论你想听在图表上的⼀般⿏标事件本⾝(点击轴,图例,…)或你是否对特定系列的⿏标事件感兴趣。 这两种情况都很容易实现。响应图表上的⿏标事件你必须实现CChartMouseListener接⼝,覆盖你感兴趣的⽅法,并通过调⽤CChartCtrl ::RegisterMouseListener(CChartMouseListener * pMouseListener)将该类的实例注册到图表控件。 根据⿏标事件发⽣在控件的哪个部分:标题,图例,轴或绘图区,调⽤该接⼝上的不同函数。 对于所有这些函数,总是传递两个参数:MouseEvent,它是列出⿏标事件类型(⿏标移动,左键单击,…)的枚举,以及⼀个CPoint对象,它包含的发⽣事件的点的屏幕坐标。

对于某些函数,需要时传递⼀些其他参数。 例如,当单击⼀个轴时,指向该轴的指针被传递给该函数。下⾯是CChartMouseListener的实现,它对轴的点击作出反应,并显⽰⼀个消息框:classCCustomMouseListener:publicCChartMouseListener{public:voidOnMouseEventAxis(MouseEvent mouseEvent,CPointpoint,CChartAxis*pAxisClicked){if(mouseEvent==CChartMouseListener::LButtonDoubleClick){MessageBox(_T("Axisclicked"),_T("Info"),MB_OK);}}};1112然后你必须创建⼀个这个类的实例并注册它:m_pMouseListener=newCCustomMouseListener();m_erMouseListener(m_pMouseListener);12这⾥也需要⾃⼰删除指针。响应系列上的⿏标事件响应系列上的事件与响应⼀般事件⾮常相似,只是监听器是CChartSeriesMouseListener的⼀个实例,它是⼀个模板类,模板参数是系列的点类型。 这是需要的,以避免当您要检索点的特定值时不必要的转型。 另⼀个区别是,您必须在系列本⾝上注册监听器,⽽不是在图表控件上注册。下⾯是CChartSeriesMouseListener的实现,它对系列的点击做出反应,如果点击发⽣在点上,它将显⽰⼀个带有点的Y值的消息框:classCCustomMouseListener:publicCChartSeriesMouseListener{public:voidOnMouseEventSeries(MouseEventmouseEvent,CPoint point,CChartSerieBase*pSerie,unsigneduPointIndex){if(mouseEvent==CChartMouseListener::LButtonDoubleClick&&uPointIndex!=INVALID_POINT){TChartStringStreamssText;SChartXYPoint Point=pSeries->GetPoint(uPointIndex);ssText<<_T("Y value=")<AddString(_T("An example of oscilloscope"));// Change the colorof the line seriespLineSeries->SetColor(SerieColor);// Finally re-enable the refresh of the control. This will refresh the// controlif any refresh was still 'pending'.m_Refresh(true);829363738394647484956575859“Income over 2008” example:srand((unsignedint)time(NULL));// Disable therefreshm_Refresh(false);COleDateTimeMin(2008,1,1,0,0,0);COleDateTimeMax(2008,10,1,0,0,0);// Create thebottom axis and configure itproperlyCChartDateTimeAxis*pBottomAxis=m_DateTimeAxis(CChartCtrl::BottomAxis);pBottomAxis->SetMinMax(Min,Max);pBottomAxis->SetDiscrete(true);pBottomAxis->SetTickIncrement(false,CChartDateTimeAxis::tiMonth,1);pBottomAxis->SetTickLabelFormat(false,_T("%b"));// Create the leftaxis and configure it properlyCChartStandardAxis*pLeftAxis=m_StandardAxis(CChartCtrl::LeftAxis);pLeftAxis->SetMinMax(0,100);pLeftAxis->GetLabel()->SetText(_T("Units sold"));// Create the right axis and configure itproperlyCChartStandardAxis*pRightAxis=m_StandardAxis(CChartCtrl::RightAxis);pRightAxis->SetVisible(true);pRightAxis->GetLabel()->SetText(_T("Income (kEuros)"));pRightAxis->SetMinMax(0,200);// Configure thelegendm_end()->SetVisible(true);m_end()->SetHorizontalMode(true);m_end()->UndockLegend(80,50);// Add text to the title and set the font & colorm_le()->AddString(_T("Income over2008"));CChartFont titleFont;t(_T("Arial Black"),120,true,false,true);m_le()->SetFont(titleFont);m_le()->SetColor(RGB(0,0,128));// Sets a gradientbackgroundm_kGradient(RGB(255,255,255),RGB(150,150,255),gtVertical);// Create two bar series and a lineseries and populate them withdataCChartBarSerie*pBarSeries1=m_BarSerie();CChartBarSerie*pBarSeries2=m_BarSerie();CChartLineSerie*pLineSeries=m_LineSerie(false,true);intlowIndex=-1;intlowVal=999;for(inti=0;i<9;i++){COleDateTimeTimeVal(2008,i+1,1,0,0,0);intDesktopVal=20+rand()%(100-30);pBarSeries1->AddPoint(TimeVal,DesktopVal);intLaptopVal=10+rand()%(80-20);pBarSeries2->AddPoint(TimeVal,LaptopVal);intIncome=DesktopVal+LaptopVal*1.5;if(IncomeAddPoint(TimeVal,Income);}// Configure the series properlypBarSeries1->SetColor(RGB(255,0,0));pBarSeries1->SetName(_T("Desktops"));pBarSeries2->SetColor(RGB(68,68,255));pBarSeries2->SetGradient(RGB(200,200,255),gtVerticalDouble);pBarSeries2->SetName(_T("Laptops"));pBarSeries2->SetBorderColor(RGB(0,0,255));pBarSeries2->SetBorderWidth(3);pLineSeries->SetColor(RGB(0,180,0));pLineSeries->SetName(_T("Total income"));pLineSeries->SetWidth(2);pLineSeries->EnableShadow(true);// Add a label on the StringStream labelStream;labelStream<<_T("Min income: ")<*pLabel=pLineSeries->CreateBalloonLabel(lowIndex,()+_T("kEuros"));CChartFont labelFont;t(_T("Microsoft Sans Serif"),100,false,true,false);pLabel->SetFont(labelFont);// Reenable the refreshm_Refresh(true);829363738394647484956575859666768697677787980818283FeedbackQuite a lot of work is involved in the development of this control and, as any other software project, it might still contain bugsor errors in the documentation. If you encounter such a problem, please let me know (even if you fixed it yourself) so that Ican fix the issue as soon as possible. Other users of the control will thank you for that. The same if you encounter errors inthe documentation or typos in the article.I’m also more or less constantly working on this control to add new features. If you have some requirement for a nicefeature that could be useful for others, please let me know and I’ll add it to my wishlist. However, as I’m working on thiscontrol in my spare time, my time is rather y, if you liked this control, do not hesitate to drop me a word in the discussion forum or to rate the article, this is muchappreciated. Thank y08/05/2006: Release of version 1.019/08/2006: Release of version 1.1Bug fix in ScreenToValue function (CChartAxis)Bug fix in RemoveAllSeries function (CChartCtrl)Added support for manual zoomAdded support for mouse panningAbility to specify a tick increment on the axisAdded support for resizing the control09/04/2007: Release of version 1.2GDI leak correctedInvisible series are not taken in account for auto axis and legend (thanks to jerminator-jp)Ability to change the text color of the axisAbility to change the color of the border of the drawing areaSurface series added16/02/2008: Release of version 1.3Added date/time axisBug fix in how the logarithmic labels are displayed (trailing 0)Ability to change the color of the zoom rectangleRemoved compiler warnings for VC2005Bug fix in the zoom14/04/2008: Release of version 1.4Added support for scrollbarsBar series addedLegend can be docked on any side or floatingSupport for legend in horizontal modeSupport for transparent background on the legendSupport for shadow for several objectsRemovePointsFromBegin, RemovePointsFromEnd and AddPoints in the CChartSeries classSupport for gradient backgroundEnableRefresh and UndoPanZoom functions added in CChartCtrlPossibility to enable/disable the zoom for a specific axis and to set its limitSpeed improvement on the series (min and max cached, ordering of the series)Series can be removed using their pointersBug fix for invisible series in the legendBug fix for logarithmic axis (1 digit was not displayed)Bug fix when removing series from the controlBug fix if the pen width is bigger than 1 for line seriesBug fix for automatic axis20/08/2008: Release of version 1.5Added support for UNICODEAdded support for printingAuto-hide scrollbarsBaseline selection for bar seriesPerformance patchScrollbar flickering removed (see here)Bug fix: scrollbar is now updated when axis is pannedBug fix: calling AddPoint was not drawing the new pointBug fix: tick labels for log axis were not always correct (rounding error)Bug fix: last point of ChartPointSerie was not displayedBug fix: moving the mouse outside the control doesn’t stop the zoom or pan operation (the button can be released outsidethe control)13/04/2009: Release of version 2.0The different axis types are now separated into different classesModified the way to add series to the control for improved flexibilityAdded cursorsAbility to display discrete axesAbility to be notified about mouse events occurring on the controlAdded labels on pointsAbility to display a smooth curveAdded ChartFont: allows for italic, bold or underlined fontsAdded the SetReferenceTick function for date/time axisAbility to store user data for each pointSeries now have an IdRemoved the CChartObject classPoints are now stored in a standard array instead of a std::vector for efficiencyBinary search implemented for finding the first and last visible points (for efficiency)The line series now uses PolyLine instead of MoveTo/LineTo (efficiency)Bug fix when using date/time axis with a tick interval in yearsBug fix: bar series were drawn from the wrong axis11/06/2009: Release of version 2.0.1Optimization: the pan feature has been smoothedOptimization: points with the same X and Y values are not plotted anymore for the line fix: in some situations, the code was crashing when accessing points outside the valid rangeBug fix: when series were removed, the legend was accessing removed series (which crashed)Bug fix: when a series was cleared, new points were not drawn properlyBug fix: inserting a point for which the X value already existed in the series did not add the point properlyBug fix with the CChartFont class07/08/2009: Release of version 2.0.2Bug fix: the control was crashing when a series with no points and no ordering was addedBug fix: the shadow of the line was not drawn correctlyBug fix: when an automatic date/time axis was used without any data, the code crashed28/12/2009: Release of version 3.0.0Series are now template classes with the template parameter being the point type. This allows the control to manipulate anytype of pointsAdded candlestick and Gantt seriesAdded support to save the chart to an image fileBar series can be stackedAdded a new automatic mode for axes: the screen automatic modeListening for mouse events on a series has been moved to a CChartSeriesMouseListener classBug fix: when a point X or Y value is modified, the series is reorderedBug fix: setting a tick increment on a standard axis did not show the digits properly17/01/2010: Release of version 3.0.1Bug fix: when using labels with the points series, the border of the points was changing color. Fixed by providing a way tospecify the border fix: the code was crashing when clicking on a series without having registered a mouse listener on the fix: detection of mouse events on certain series type was crashingBug fix: CChartTitle::SetVisible was not implemented13/07/2010: Release of version 3.0.2Bug fix: the high-speed functionality has been removed by mistakeBug fix: the draw function of the line series was not drawing pointsBug fix: replaced Clear() by clear() in the ClearSerie fix: Added implementation of ctor/dtor for the CChartCursorListener classBug fix: memory leak when the series was cleared (labels were not deleted)ThanksI would like to thank all the people from this community, they were a great help when I started programming. Thanks also to allthe people who contributed to this control with their various help or feedback: toxcct, Chris Maunder, Kevin Hoffman,jerminator-jp, Laurie Gellatly, Eugene Pustovoyt, Andrej Ritter, Nick Holgate, Nick Schultz, Johann Obermayr, Pierre Schrammand Kevin Winter. A special thanks to Bruno Lavier for the time spent working on the control. I hope I didn’t forget hris Maunder’s Colour Picker Control (used in the demo application)Drawing smooth curves with Bezier PrimitivesLicenseThis article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)遇到的问题以及解决⽅案 Assertion Failed! File: Line:639这个BUG是多线程使⽤过程中遇到的,ChartControl类不能在多线程中直接使⽤,直接使⽤可能出现抢DC的情况出现,多谢前辈的指点:使⽤CDC:GetTextExtent出现了有关问题的奇怪有关问题,m_hDC 是有效的,但GetTextExtent内部调⽤GetExtentPoint32有时失败这个BUG的特殊之处就是你使⽤断点调试使调不出来的( ……这个是不是很厉害…… ),所以⼀定的注意许多函数并不是线程安全的。解决办法: 所有绘图操作都使⽤消息响应的⽅式进⾏实现,定义消息,并在绘图界⾯对话框类中直接定义响应函数,进⾏绘制,⽽不是在⼯作线解决办法:程中直接绘图,这个样⼦就不会和对话框争夺DC,从⽽避免失效。