CChart发布已有多年,QQ交流群也成立了很久。在和网友的交流中,发行了CChart的很多问题,也进行了很多改进和完善。
网友们接触CChart的时间有早有晚,不同的网友经常在群里或私聊的时候提出的相近的问题,本节课准备对这些共性问题进行一些介绍。
A56.1 刷新问题
问:为什么我添加数据或修改数据后,屏幕的图像没有变化,或者是需要鼠标在图像上动一下图像才有变化?
答:CChart一般不会自动画图。
有时候你觉得好像我没有主动调用绘图函数啊,怎么也画图了呢?这是由于你使用CChartWnd的时候,在窗口的OnPaint函数里面调用了CChart的OnDraw函数。只要不触发OnPaint,图像是画不出来的。而鼠标移动有可能会触发OnPaint,所以有时候需要鼠标在图像上动一下图像才有变化。
为了主动刷新图像,在添加完数据后,可以调用一下CChartWnd::ReDraw(),或者CChart::OnDraw(HWND hWnd),也可以用Win32API的InvalidateRect刷新。
A56.2 默认背景问题
问:我不喜欢CChart的默认背景,请问怎么取消?
答:一般软件都有一个无法取消的默认背景,起到版权宣示或者产品推广的作用,
CChart的默认背景就温柔得多了,如下:
这个背景并不是强制性的,可以取消,也可以换成别的背景。参考本系列教程的第A17课,“深入浅出CChart 每日一课——快乐高四第十七课 麦田的守望,默认界面的设置方法_baita96的博客-CSDN博客”。
默认背景界面的设置内容其实也是很丰富的。
A56.3多线程问题
问:我在多线程下使用CChart,偶尔程序崩溃,但也不一定崩溃,是什么原因。
答:所有的操作系统,刷新UI界面都不建议在工作线程中进行,包括Windows、Linux、Android、MacOS等。因为各大系统的UI元素都不是线程安全的。
Windows推荐的在线程中刷新界面的标准方法是发消息,具体的方法是调用InvalidateRect,使得窗口某一部分失效,这样Windows系统会在主线程的消息队列里插入一个WM_PAINT消息,从而刷新窗口。当然,这样的话,由于WM_PAINT消息在队列里排队,有时候刷新可能不即时,可以在InvalidateRect之后,立马调用一下UpdateWindow或RedrawWindow,这样系统会立马处理WM_PAINT消息。
具体到CChart中,有两种情况。
如果使用的是CChart这个类,一般情况下在主线程里面可以调用OnDraw(HWND hWnd)这个接口刷新界面。但在主线程之外的其它线程里面,不要这样使用,还是用InvalidateRect吧。当然如果是MFC,可以调用Invalidate这个函数,它是InvalidateRect的一个包装。
如果使用的是CChartWnd,那么CChartWnd::ReDraw()这个接口就是刷新绘图区域。但这里要注意CChart::SetDirectRedraw(bool bRedraw)这个函数。默认情况下这个函数是设置为false的,也就是说,CChartWnd内部不是直接绘图,而是调用消息刷新的,也就是说这是安全的。如果SetDirectRedraw(true),那么CChartWnd内部调用OnDraw重绘,线程不安全。
A56.4曲线颜色问题
问:我怎么才能设置曲线的颜色,SetDataColor似乎不起作用?
答:CChart的曲线颜色具有很丰富的选项,具体设置方法请参考上一课“深入浅出CChart 每日一课——快乐高四第五十五课 天然去雕饰,出水芙蓉之美轮美奂_baita96的博客-CSDN博客”。
此外,上一课主要介绍的是绘图类型为kTypeXY的折线图的曲线颜色设置。对于多层视图,包括分层视图和共享X轴视图,默认情况下,每一层的所有曲线颜色均设置为和该层的坐标轴颜色相同,以便区分不同的图层。在这种情况下,如果想单独设置曲线颜色,请先调用CChart::SetUniteLayerColor(bool bUnite)这个接口,参数设置为false,以解除曲线颜色的绑定。
A56.5 曲线名称问题
问:怎么修改屏幕上“未命名000”、“未命名001”等显示的曲线名称为我想要的名字?
答:屏幕上曲线名称是显示在图例之中的。每条曲线都有一个名称,可以用CChart::SetDataTitle()这个接口设置。如果不设置,在图例中显示的名称就是“未命名XXX”。
A56.6图像缩放问题
问:图像怎么放大缩小?可以用鼠标滚轮缩放图像吗?
答:在默认状态下,CChart是关闭图像缩放功能的。
打开图像缩放功能用两种方式。
一种是在程序运行时,通过右键菜单打开,“绘图状态”-->“特别功能”-->“坐标缩放”。
一种是通过代码打开,调用CChart::SetRangeZoomMode();
CChart坐标缩放功能是按住鼠标左键,往右拖动放大,往左拖动缩小(还原上一次拖动状态),很方便实用。
如图,在往右拖动的时候,会出现一个拖动框,松开鼠标左键,图像就放大到拖动框的范围。
此外,打开坐标缩放功能后,默认鼠标滚轮就可以缩放图像了。
A56.7图像显示范围问题
问:图像默认的坐标轴范围和我想要的不一样,我想设置一下,但调用SetXRange或SetYRange后,根本没有任何作用,请问怎么办?
答:调用SetXRange、SetYRange或SetPlotRange后,不起作用的原因是CChart默认情况下是自动计算坐标轴显示范围的。为了让这些设置其作用,需要先调用一下SetXAutoRange(false)或SetYAutoRange(false)。
这里还有一个问题,如果图像中增加了曲线或某曲线增加了数据点,即使调用了SetXAutoRange或SetYAutoRange,显示范围也会重新计算。
为了彻底固定显示范围,可以调用SetXStaticRange(true)或SetYStaticRange(true)。
A56.8数据点驱动的等高线图、云图、三维曲面图问题
问:我已有一些数据点,怎么绘制等高线图、云图、三维曲面图?
答:CChart的等高线图、云图、三维曲面图,都是由场函数驱动的。场函数的基本形式是h=f(x,y),需要调用SetFieldFcn(f)以设置场函数。
如果已知一些数据点,怎么画图呢?理论上,可以根据这些数据点编写自己的场函数,然后SetFieldFcn即可。
但实际上,根据数据点获得场函数是一项难度极大的工作,这也是一个庞大的研究课题,用户自己实现的可能性极小。
CChart内部已经集成了多种算法,包括克里金法、自然邻域法、反距离权重法等。
具体的使用方法是需要先调用一下下面这个函数
voidCChart::SetContourByArbPoints();
CChart内部就建立了一个插值函数作为场函数,默认的插值算法是自然领域法。
然后添加数据点即可。添加数据点的接口为
voidCChart::AddContourPoint(double x, double y, double h);
详细的用法请参考本教程第A6课“深入浅出CChart 每日一课——快乐高四第六课 二丫的青梅,返璞归真之普通窗口多区域绘图_baita96的博客-CSDN博客”。
A56.9同一个窗口绘制多个图像问题
问:我想在一个窗口上画多个图像,请问怎么办?
答:单个窗口分成多个区域,然后每个区域画一张图像,这在CChart库中有多种实现方式。
方式1、利用分裂视图。
分裂视图直接将窗口客户区分成多块,每个区域绘制一个图像。
分裂视图划分窗口的方式有很多种,最简单的是按行列划分,也可以按左一右二、左二右一、上一下二、上二下一的方式划分,比较灵活。
详细的使用方式见本教程第十课“深入浅出CChart 每日一课——第十课 分裂视图,锤子肖哥之错位人格_baita96的博客-CSDN博客”。
分裂视图最大的优点是Chart内部对这种视图进行了一下处理,可以实现一些自己处理较难实现的功能,比如各个区域之间可以用鼠标拖动调整大小,Ctrl+鼠标双击可以切换单区域独占窗口功能等。
分裂视图也有一些问题,最大的问题是发布的版本里面,分开的每个区域都只能绘制折线图,不能画其它类型的图像,更不用说每个区域画不同类型图像了。
方式2、利用多个CChart类对象。
这种方式是设置多个CChart类对象,每一个对应窗口的一个区域。
本系列教程的A06、A07、A08三课都是讲述这个方式,详见“深入浅出CChart 每日一课——快乐高四第六课 二丫的青梅,返璞归真之普通窗口多区域绘图_baita96的博客-CSDN博客”、“深入浅出CChart 每日一课——快乐高四第七课 铁蛋的竹马,返璞归真之对话框窗口多区域绘图_baita96的博客-CSDN博客”、“深入浅出CChart 每日一课——快乐高四第八课 懵懂的童年,返璞归真之Duilib窗口多区域绘图_baita96的博客-CSDN博客_duilib多窗口”。
采用多个CChart类对象的方法,比较灵活。缺点是需要自己编写消息响应代码。当然如果不需要消息响应的话,这种方法是最好的。
方式3、利用多个ChartCtrl控件。
这种方式是在窗口每个区域放置一个ChartCtrl控件。
ChartCtrl控件是CChart内部已经注册的窗口类,可以看作和Windows内置的Button、Edit、Label等窗口类是等同的。
详细用法参见本系列教程的A15课,“深入浅出CChart 每日一课——快乐高四第十五课 罗马通途,利用ChartCtrl控件实现多区域绘图_baita96的博客-CSDN博客_chartctrl”。
此方法最关键的是一个函数:
CChart *GetChart(HWND hWnd);
用这个函数获得CChart指针,然后进行各种操作。
其中hWnd这个句柄是ChartCtrl控件的窗口,如果不是的话,GetChart将返回空指针。
方式4、利用多个CChartWnd变量。
这种方式是窗口每个区域对应一个CChartWnd变量,Attach的时候,注意要给出该区域的RECT。
此方法的关键函数是:
int CChartWnd::Attach(HWND hWnd, RECT rect);
注意最好不同区域的RECT不要有重叠的部分,以避免不必要的问题。
另一个关键函数是:
CChart *CChartWnd::GetChart();
这个函数获得CChart指针,然后就可以开展各种操作了。
注意这个函数没有形参。
方式5、利用单个CChartWnd变量,多次Attach
与上一个方式的差别在于,只有一个CChartWnd变量。CChart库已经实现了同一个CChartWnd多次Attach的功能,用户可以放心使用。
此方法的关键函数仍然是:
int CChartWnd::Attach(HWND hWnd, RECT rect);
同样,Attach的时候,rect之间最好不要有重叠。
但是,可能同学们会疑惑,那怎么区分不同的图像呢?那就看看下面这个关键函数。
CChart *CChartWnd::GetChart(int nChartIndex);
真的吗?这个和上面那个方法不是同样的函数吗?
的确,还是上面那个函数,但这里带了形参。nChartIndex是图像序号。简单地说:第一次Attach的图像,其序号为0;第二次Attach的图像,其序号为1;以此类推。
方式6、前两种方式混用。
也就是说,可以设置多个CChartWnd变量,其中一些CChartWnd变量对应单个图像,另一些CChartWnd变量对应多个图像。
CChart库非常灵活,这样用是完全没有问题的。
A56.10时间坐标轴问题
问:我想让X轴显示为时间,请问怎么办?
答:CChart支持时间坐标轴,把一个坐标轴设置为时间轴的接口如下。
void CChart::SetAxisToTime(bool bTime, int location);
其中location表示哪个坐标轴,具体来说,0是左轴,1是下轴,2是右轴,3是上轴。
也可以设置时间刻度的显示格式。
void CChart::SetAxisTimeFormat(TCHAR *format, int location);
注意这里的format格式字符串,这个字符串格式和C语言标准函数strftime是完全一样的,可以查阅strftime的资料。其中一部分,%Y表示年份,%m表示月份,%d表示日,%H表示小时,%M表示分,%S表示秒。
但是要注意,光把坐标轴设置为时间轴,数据也得是时间才对。
CChart内部的时间数据采用C语言的标准类型time_t数据。对于VC来说,time_t就是一个64位长整数,
也就是说,如果需要X轴显示时间,您的数据的X坐标应该是time_t数据。
数据输入的时候,可以直接给time_t数据,也可以利用时间字符串输入。相关的函数有
int CChart::AddPoint2D(TCHAR *strTime, TCHAR *fmt, double y, int nDataIndex=0, int nPlotIndex=0);int AddCurve(TCHAR **pStrTime, TCHAR *fmt, double *pY, int nLen, int nPlotIndex=0);
这两个函数的x数据由字符串给定,其中字符串的格式由fmt格式字符串指定,这个格式仍然需要参考strftime这个API。例如,假定时间是6月20日16点50分30秒,时间字符串可以是”-06-20 16:50:30”,格式字符串为”%Y-%m-%d %H:%M:%S”,当然格式也可以调整。
注意,这里用于输入的字符串格式,可以和上面坐标轴显示的格式不一样。
当然,您也可以不用上面两个函数输入数据,仍然把时间数据用time_t直接输入。
不过,自己操作time_t可能比较困难,笨笨提供了一个时间字符串转数值的接口。
double StringToTime(TCHAR *str, TCHAR *fmt);
这里返回值就是time_t,但是转换为double了。
问:X轴显示为时间时,各个刻度的数据比较乱,比如显示多少分多少秒,我想让它稍微凑整一点可以吗?
答:这是可以的,可以利用下面这两个函数:
void CChart::SetXAtom(double atom);void CChart::SetYAtom(double atom);
这两个函数的参数就表示刻度之间的间隔。对于time_t数据,就是多少秒。比如,您希望刻度间隔为5分钟,可以调用SetXAtom(300)。
当然,这个函数在内部实现的时候,不是硬性的。如果您设置的atom离实际情况差别比较大,这个函数不起作用。例如比如您的坐标总的范围只有1分钟,但您这里仍然设置atom为300,那明显超出太多。
问:X轴显示为时间时,时间精度能优于秒吗?
答:CChart的时间精度不能直接优于秒,但笨笨加了一些补丁,可以让时间精度优于秒。
CChart的时间精度不能直接优于秒的原因是内部采用time_t作为时间数据,time_t的最小只能表示到秒,不能再小。
当然,Windows系统提供的另一种时间数据COleTime,其精度完全可以优于秒,但CChart一开始就没有选择这种方案。
为了让时间精度优于秒。笨笨在CChart内部做了如下处理。
首先设置一个基准时间;然后具体的时间数据都是在基准时间的基础上变化,变化的精度当然是可以优于秒的;最后在绘制坐标轴的时候,显示坐标刻度时加上精确的秒数。
相关的接口如下:
void CChart::SetAxisTimeMixMode(bool bMix, int location);
此接口需要在SetAxisToTime之后调用,表示坐标轴是混合模式。
void CChart::SetAxisDecimalTimeFormat(TCHAR *format, int location);
此接口表示混合模式的时候,小数部分的显示格式。
注意这里的format格式字符串不是参考strftime,而是参考printf这个最基本的函数。
void CChart::SetAxisTimeBase(TCHAR *strTime, TCHAR *strFmt, int location);
此接口设置时间基准。
在混合模式下,数据点的输入时最好用StringToTime这个接口获得基准时间的数值,然后再加上相应的时间偏离,作为数据值。
A56.11对数坐标轴问题
问:我想让X轴显示对数刻度,请问可以吗?
答:CChart支持对数坐标轴,只需要调用这个接口即可。
void CChart::SetXLogarithm(bool bLog);void CChart::SetYLogarithm(bool bLog);
当然,设置为对数坐标轴的前提是该坐标轴的所有数据都是正数,没有0或负数,不然会发生不可预计的错误。
A56.12坐标轴反向问题
问:我想让X轴从大到小显示,请问可以吗?
答:CChart默认情况下坐标轴数据都是从小到大显示的。
CChart支持坐标轴反向,只需要调用这个接口即可。
void CChart::SetXOpposite(bool bOpposite);void CChart::SetYOpposite(bool bOpposite);
A56.13坐标轴浮动刻度问题
问:matlab等很多软件,其坐标轴刻度显示似乎和CChart不一样,这是怎么回事?
答:CChart在默认情况下,坐标轴的两端是有刻度的,并且刻度值是在一定程度上进行了“凑整”处理。其实大部分软件似乎都是这样处理的,只是凑整的算法不一样。
还有很多软件,包括matlab,采用了另外一种方法。这种是坐标轴的两端不一定有刻度线,刻度线是在坐标轴的内部比较“靠整”的位置显示。例如,假定坐标范围是0.5到5.5,其刻度线分别位于1、2、3、4、5这5个位置,起点和终点没有刻度线。
CChart实际上也支持这种刻度显示方式,并把这种方式称为“浮动刻度”模式,相应的接口如下:
void CChart::SetXFloatTicks(bool flt);void CChart::SetYFloatTicks(bool flt);
A56.14控制台程序问题
问:CChart可以在Dos程序中使用吗?
答:所谓Dos程序,在Windows中称为控制台。
CChart是一个基于Win32API的软件库,纯粹的Dos程序,CChart是不支持的。但目前应该很难找到纯粹的Dos环境。
在Windows环境里面,CChart完美支持控制台程序。
实际上,在控制台程序里,既可以弹出一个Windows窗口,用于CChart绘图,也可以让CChart直接在控制台那个黑乎乎的窗口里绘图。
本系列教程里面,有多课内容都是讲述控制台编程的,包括A33课“深入浅出CChart 每日一课——快乐高四第三十三课 叶落归根,返璞归真之控制台程序_baita96的博客-CSDN博客”、A34课“深入浅出CChart 每日一课——快乐高四第三十四课 不忘初心,Fortran控制台绘制曲线图_baita96的博客-CSDN博客_fortran绘制函数图像”、A35课“深入浅出CChart 每日一课——快乐高四第三十五课 砥砺前行,Fortran控制台绘制等高线云图_baita96的博客-CSDN博客”、A49课“深入浅出CChart 每日一课——快乐高四第四十九课 旧石器时代,老血狂喷之控制台窗口绘图_baita96的博客-CSDN博客”、A50课“深入浅出CChart 每日一课——快乐高四第五十课 新石器时代,轻装上阵之EasyX窗口绘图_baita96的博客-CSDN博客”。
重点请参考A49课。
A56.15 CChart数据量问题
问:CChart最大可以支持多少数据点?
答:理论上,CChart支持的数据点由系统的内存决定。一个数据点由两个double数据组成,共占据16Byte。因此,32位版总共大致能用2G内存,可以支持的数据点数大概是几千万个,对于64位版,则基本没有限制。试验也可以发现,32位版如果数据点达到亿量级,程序直接崩溃,而64位版则没有问题。
在实用层面,还要考虑软件的响应速度等问题,因此支持的数据点没有上面那么多。
这里要分三种情况分析。
情况1、静态图像,无消息响应。
静态图像是指图像中不需要动态增删数据点。
在这种情况下,CChart支持的数据点可以达到理论上那么多。基本上,支持千万量级数据点是没有问题的,64位版甚至可以支持10亿、百亿。当然,数据点太多,您在程序开始读入数据的时候要花较长的时间。
情况2、静态图像,有消息响应。
这种情况下,由于消息响应时图像可能需要重绘,大数据量时重绘也比较耗时。
CChart对这种情况下的重绘进行了优化,如果数据量为几百万,同时图像不改变大小,一般情况下重绘时间不会超过1秒,这是可以接受的。
如果图像大小改变,重绘时间会长一些。只要不是经常发生,我们认为改变图像大小的情况可以忽略。
因此,在这种情况下,CChart支持的数据量大概为百万量级。
情况3、动态图像。
动态图像是指图像中不断增删数据点。
这种情况下,一般数据量达到10万,可能就能感觉到一些卡顿了。
因此,此时CChart支持的数据量大概为十万量级。
作为一款免费开源软件,CChart能达到这个水平,笨笨已经做了很大努力。
这里对比一款商业软件ProEssentials的情况。ProEssentials有三种版本:Pro、Standard和Lite。Pro版是最完整的版本,开发无任何限制;Standard版最多支持8000个数据点和800个注释;Lite版支持1000个数据点和100个注释。可以看到,如果不买最贵的Pro版,您花了钱还受到了很多限制。同时笨笨认为,大量数据点主要用于曲线图绘制,单就曲线图绘制来看,CChart的功能并不比ProEssentials弱。当然其它类型的图像的确不是CChart的强项。
为了提高大数据量时的性能,笨笨在CChart的基础上,专门开发了一个拓展库,名为CHugeChart,这个库在千万数据量的情况下,无论是键盘鼠标消息还是增删数据,响应都很丝滑,也有朋友把这个库用到了项目中,反应良好。