Highspeed Charting ControlMFC绘制图表控件.docx
《Highspeed Charting ControlMFC绘制图表控件.docx》由会员分享,可在线阅读,更多相关《Highspeed Charting ControlMFC绘制图表控件.docx(21页珍藏版)》请在冰豆网上搜索。
HighspeedChartingControlMFC绘制图表控件
High-speedChartingControl--MFC绘制图表(折线图、饼图、柱形图)控件
介绍
对于我之前的一个项目,我需要在图表控件上显示连续的数据流。
我决定开发自己的控件,因为我找不到任何可以提供所需灵活性的自由软件控件。
其中一个主要的限制是,控件必须绘制大量的数据,并能够迅速显示它(在PocketPC上)。
控件能够通过仅绘制新的数据点而不是完整的数据序列来做到这一点并且图表还能够显示静态数据。
这种控件是我长时间工作的结果,而且费尽周折地为了提供足够的灵活性来供需要它的人使用。
对于使用者反馈我表示由衷的感谢:
一个邮件,留言板中的一一句话或只是对本文评级。
当我不知道是否还有人使用它时,我就没有必要维护这个控件了。
免责声明
这个控件是我花费很长时间的开发的结果,因此我对代码的使用放置一些小条件:
该代码可以以编译的形式用于任何非商业和商业目的。
代码可以被重新开发,只要它提供作者名字和完整的免责声明。
更改源代码需要得到作者的同意。
此代码不提供任何安全保证。
我不会对使用此代码造成的损失负责。
使用它需要自己承担风险。
Thiscodemaybeusedforanynon-commercial
andcommercialpurposesinacompiledform.
Thecodemayberedistributedaslongasitremains
unmodifiedandprovidingthattheauthorname
andthedisclaimerremainintact.Thesources
canbemodifiedwiththeauthorconsentonly.
Thiscodeisprovidedwithoutanyguarantees.
Icannotbeheldresponsibleforthedamageor
thelossoftimeitcauses.Useitatyourownrisks.
鉴于开发这个控件所付出的努力,下面的要求并不过分:
如果你在在商业应用程序中使用这个控件,那么请给我发邮件让我知道。
主要特点
控件的主要特点是:
高速绘图(轴固定时),允许快速绘制数据
无限数量的数据序列(内存是限制)
每个数据序列的数据量不受限制
支持线图,点图,平面图,柱状图,K线图和甘特图系列
最多四个轴(左,下,右和上轴)
标准轴,对数轴或日期/时间轴
自动伸缩的坐标轴,翻转的坐标轴(相互独立)
轴标签
点标签
平滑的曲线
网格
图例和标题
交互性(在控件中发生特定事件时的通知)
支持手动缩放和鼠标平移
支持鼠标指针
支持轴上的滚动条
高度可定制(颜色,标题,标签,边缘,字体等)
支持UNICODE
支持打印和保存到图像文件
文档结构
本文通过一系列简短的教程来涵盖控件的大部分功能。
阅读本文后,您将能够快速地在自己的应用程序中使用本控件。
我决定从文章中删除所有的类和函数的文档,因为它不是非常友好并且我很难维护。
此外,随着代码的增长,要记录的类和函数的列表变得过于广泛以至于不能将所有内容放在文章中。
作为替代,我提供了一个doxygen文档,您可以从本文中(文章的开头)下载:
只需下载“Doxygen文档”zip文件,解压所有文件,双击“”文件,进行查看。
入门学习
此图表控件允许您在屏幕上绘制一系列数据。
此控件可以添加几个不同类型数据序列并且最多可以使用四个轴。
添加到图表的数据序列与一个水平轴(底部或顶部)和一个垂直轴(右侧或左侧)相关联。
这两个轴控制数据序列在图表上的显示方式。
为了能够在应用程序中使用次图表控件,您首先需要在自己的工程里添加源代码zip中包含的文件。
注意:
控件在内部使用动态转型,因此必须启用RTTI(RunTimeTypeInformation运行时自动类型识别的机制),否则可能会发生崩溃。
默认情况下,VC6没有启用RTTI,因此要启用它打开项目设置->“C/C++”选项卡->“C++语言”类别,并确保“EnableRun-TimeTypeInformation(RTTI)“选项已选中。
在应用程序中使用图表控件有两种方法:
手动插入,或通过资源编辑器插入。
手动插入
1.#include"ChartCtrl"添加在对话框(Dialog)类的头文件中
2.在对话框类中添加变量CChartCtrl:
对话框类的OnInitDialog方法中添加这个控件的Create方法。
使用资源管理器
1.向对话框资源添加自定义控件,打开控件的属性,并为Class属性指定ChartCtrl。
为了避免滚动条上的闪烁,必须设置WS_CLIPCHILDREN样式(0x02000000L),如图所示。
2.#include""添加在对话框(Dialog)类的头文件中
3.在对话框类中添加变量CChartCtrl:
DoDataExchange函数中添加DDX_Control(不要忘了更改ID号和控件名字):
AddavariableoftypeCChartCtrlinyourdialogclass:
HideCopyCode
..ThisMUSTbedonebefore.
m_pLineSeries=();
m_pPointsSeries=();
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函数完成的。
您还可以通过调用SetInterSpace静态函数来控制所有柱形图之间剩余的空间的宽度。
这将为所有系列设置以像素为单位的空间(因此,如果显示多于两个系列,则在任何位置使用相同的空间)。
注意,您可以通过调用SetBarWidth单独设置柱状图系列的宽度。
在点上添加标签
一旦使用数据填充您的系列,您还可以在系列的特定点上添加标签:
这个标签始终附加到特定点。
现在,只提供一种类型的标签,气泡标签:
包含文本的圆角矩形并用线连接到特定点上。
当然,如果需要,您也可以提供自己的自定义标签(参见“扩展功能”一节)。
有两种方式创建文本标签:
静态创建标签时,或动态注册一个对象,当标签请求时,它将提供文本。
第一种方法是最简单的,但也不太灵活。
下面是一个代码片段,显示如何做(假设m_pSeries已经创建并填充足够的数据):
voidCMyClass:
:
Init()
{
.
m_pSeries->CreateBalloonLabel(5,_T("Thisisasimplelabel"));
}
此调用将创建一个带有“Thisisasimplelabel”文本的标签,并将其附加到带索引为5的点。
该函数返回一个指向新创建的标签的指针,以便您可以修改其某些属性或存储以供以后使用。
第二种方法有点复杂,但提供了更多的灵活性:
例如,您可以以更方便的方式在标签中显示点属性(例如X值,Y值,…)。
为此,您必须创建一个继承自CChartLabelProvider的类,并在创建标签时提供此类的实例。
此类是模板类,模板参数是标签附加到的系列的点类型。
这个类是一个简单的接口,你必须覆盖TChartStringGetText(CChartSerieBase*pSerie,unsigneduPtIndex)方法。
此函数应返回必须在标签中显示的文本。
它接收指向标签所附加的系列和点索引的指针。
这里有一个这样的标签提供程序类的例子:
classCCustomLabelProvider:
publicCChartLabelProvider
{
public:
TChartStringGetText(CChartSerieBase*pSeries,unsigneduPtIndex)
{
TChartStringStreamssText;
SChartXYPointPoint=pSeries->GetPoint(uPtIndex);
ssText<<_T("Xvalue=")<<;
return();
}
};
此代码段显示如何将其与标签一起使用。
注意m_pSeries应该是一个操作SChartXYPoint点(点,线,面或者柱系列)的系列。
如果不是这样,你的代码将给出一个编译错误。
voidCMyClass:
:
Init()
{
.
m_pLabelProvider=newCCustomLabelProvider();
m_pSeries->CreateBalloonLabel(5,m_pLabelProvider);
}
控件不获取指针的所有权,因此,当你不再需要时,你有责任删除它。
在上面的例子中,它通常会在CMyClass析构函数中被删除。
在上面的示例中,您可以为所有要添加的标签地方重复使用相同的标签类,这也带来另一个优点:
如果你想在运行时改变标签的格式,你只需要在CustomLabelProvider中添加代码。
不需要遍历所有现有标签并更改其文本。
当然,在这种情况下,需要刷新控件,因为必须重新绘制标签。
还要注意TChartStringStream类的用法,TChartStringStream类是由控件提供的别名(类似于TChartString)。
当UNICODE被定义时,它解析为std:
:
wstringstream,当未定义UNICODE时,解析为std:
:
stringstream。
对轴的操作
轴是图表的一个重要特征,因为它们控制不同系列在控制中的显示方式。
控件中最多可使用四个轴:
底部,顶部,左侧和右侧。
控件的每个系列必须和一个水平轴和一个垂直轴相连接。
在图表中添加系列时指定这些轴。
底部和左侧轴是主轴,顶部和右侧轴是辅助轴(您将在控件的某些功能中遇到此问题)。
现在有三种类型的轴供选择:
标准轴,对数轴和日期/时间轴。
您可以在不同位置选用不同类型的轴。
一旦您选择了在不同位置使用哪些轴,您需要先创建它们,然后才能向控件添加任何数据。
为此,通过指定轴附加在哪个位置,简单地调用CreateStandardAxis,CreateLogarithmicAxis或CreateDateTimeAxis。
如果已经在该位置创建了轴,则控件将销毁它并且用新的轴替换它。
这里有一个简单的代码片段,显示如何在底部创建日期/时间,在左侧创建一个标准轴:
voidCMyClass:
:
Init()
{
CChartStandardAxis*pBottomAxis=
(CChartCtrl:
:
BottomAxis);
CChartLogarithmicAxis*pLeftAxis=
(CChartCtrl:
:
LeftAxis);
}
一旦创建了这些轴,就可以对它们设置一些属性。
大多数属性在所有轴类型之间共享(例如自动模式,最小值和最大值,轴标签,…)。
轴可以设置为三种“自动”模式:
全自动,屏幕自动和手动模式。
全自动模式基于附加到该轴的所有系列计算轴最小值和最大值(所有系列的所有点的最小值用作轴的最小值,并使用所有系列的所有点的最大值作为轴的最大值)。
屏幕自动模式基于与该轴相关的所有系列的所有可见点计算轴最小值和最大值。
例如,如果图表仅显示连接到手动底部轴和屏幕自动左侧轴的一个系列,则左侧轴将自适应于当前可见的点,并且不考虑这些点有可能超过底轴的范围(在全自动模式下,底轴外部的点将被考虑)。
警告:
如果系列的两个轴都处于屏幕自动模式,则结果未定义。
在手动模式下,轴最小和最大值由用户设置,不由控件计算。
在使用自动轴模式下,如果将数据动态添加到控件,如果新的数据点位于轴的范围之外,那么控件将自动刷新。
这里是一个代码片段(继续前一个代码段),显示一个全自动轴(底部轴)和一个手动轴(左轴,它是一个对数轴):
voidCMyClass:
:
Init()
{
.
pBottomAxis->SetAutomaticMode(CChartAxis:
:
FullAutomatic);
pLeftAxis->SetAutomaticMode(CChartAxis:
:
NotAutomatic);
pLeftAxis->SetMinMax,1000);
}
处于离散模式下的轴
轴有一个模式是离散模式(默认禁用)。
此模式指定轴不显示连续值,而只显示离散值,这些值是轴上刻度指定的值,而轴将不显示其他的值。
尝试绘制不同于显示的节拍值的值是不可能的。
让我们举一个例子:
假设你有一个底部标准轴,间隔为(所以,显示的蜱是1,2,3等等)。
尝试绘制X值为的点将在相同位置显示该点,就好像它的值为。
事实上,你可以认为两个刻度之间的区域是一个常量值。
这就是为什么刻度标签显示在两个刻度的中间,而不是刻度本身。
这里有一个小代码片段,显示离散轴对系列显示方式的影响。
代码片段下的两个图像显示启用离散模式(第一个图像)或禁用(第二个图像)的结果。
voidCMyClass:
:
Init()
{
CChartStandardAxis*pBottomAxis=
(CChartCtrl:
:
BottomAxis);
pBottomAxis->SetMinMax(0,10);
CChartStandardAxis*pLeftAxis=
(CChartCtrl:
:
LeftAxis);
pLeftAxis->SetMinMax(0,10);
pBottomAxis->SetTickIncrement(false,;
pBottomAxis->SetDiscrete(true);
CChartLineSerie*pSeries=();
doubleXVal[20];
doubleYVal[20];
for(inti=0;i<20;i++)
{
XVal[i]=YVal[i]=i/;
}
pSeries->SetPoints(XVal,YVal,20);
}
使用日期/时间轴
使用日期/时间轴有点特别,下面是如何利用这个功能的解释。
要了解日期/时间轴的重要一点是它们在COleDateTime对象内部工作。
原因很简单:
COleDateTime中有DATE类型的类,DATE类型是一个双精度型。
由于图表中的点表示为双精度值,因此它非常适合:
使用标准点(非日期/时间)和日期/时间点之间没有差异,这使得后者的使用不太复杂。
所有点仍然存储为双精度型,无论是否是日期/时间。
创建日期/时间轴后,可以在控件中填充数据。
为此目的,没有改变:
你必须从CChartSerie类调用voidAddPoint(doubleX,doubleY)或voidSetPoints(double*X,double*Y,intCount)。
CChartCtrl类提供了两个静态函数,让你从COleDateTime转换为双精度,反之亦然:
doubleDateToValue(constCOleDateTime&Date)
COleDateTimeValueToDate(doubleValue)
如果您有另一种格式的日期(例如time_t或SYSTEMTIME),这不是一个问题,因为COleDateTime对象可以从不同的时间格式构造(检查COleDateTime类的MSDN文档,以了解从哪种格式可以构造它)。
填充数据后,可以配置轴以显示所需的内容。
与日期/时间轴相关的几个功能可用:
voidSetDateTimeIncrement(TimeIntervalInterval,intMultiplier)
voidSetDateTimeFormat(boolbAutomatic,constTChartString&strFormat)
voidSetReferenceTick(COleDateTimereferenceTick)
第一个允许您指定轴上显示的两个节拍之间的间隔。
两个节拍之间的间隔将遵守正确的时间,这意味着如果指定1个月的节拍增量
(Interval=CChartAxis:
:
tiMonthandMultiplier=1),则两个节拍之间的间隔将是不规则的(28,30或31天)。
第二个函数允许您指定刻度标
签的格式。
控件根据刻度间隔自动格式化刻度标签,但您可以通过调用此函数覆盖它。
检查MSDN上的COleDateTime:
:
Format函数的文档以获
取更多信息。
最后,SetReferenceTick(COleDateTimereferenceTick)函数允许您为轴指定一个参考标记。
参考标记是用作绘制标记的参考
的日期:
在该日期总是存在标记。
当您在SetDateTimeIncrement函数中指定的multiplier不是1时,这很有用。
例如,假设您指定了3个月的单
位增量,并且您希望在2月(因此,5月,8月,…)有一个单位,那么您可以调用此函数将2月1日设置为参考单位。
默认设置为2000年1月1日。
下面是一个简单的代码片段,它创建一个日期/时间轴,并显示不同函数的用法:
voidCMyClass:
:
Init()
{
COleDateTimeminValue(2006,1,1,0,0,0);
COleDateTimemaxValue(2007,12,31,0,0,0);
pBottomAxis->SetMinMax(CChartCtrl:
:
DateToValue(minValue),
CChartCtrl:
:
DateToValue(maxValue));
CChartCrossHairCursor*pCrossHair=
();
CChartDragLineCursor*pDragLine=
(CChartCtrl:
:
BottomAxis);
(false);
注意到对CChartCtrl:
:
ShowMouseCursor的调用结束。
默认情况下,鼠标总是可见的,但是当您使用十字光标时,当它在绘图区域时隐藏有时是需要的。
如果希望在光标位置更改时收到通知,则必须实现CChartCursorListener接口,创建其实例并使用光标注册它:
classCCustomCursorListener:
publicCChartCursorListener
{
public:
voidOnCursorMoved(CChartCursor*pCursor,doublexValue,doubleyValue)
{
TChartStringStreamssText;
ssText<<_T("Cursormoved:
xPos=")<.
}
};
CCustomCursorListener*pCursorListener=newCCustomCursorListener;
pDragLine->RegisterListener(pCursorListener);
OnCursorMoved函数接收一个X和Y值,但对于拖动光标,只使用这些值中的一个:
如果光标与水平轴相关联,则使用X值,否则使用Y值。
使用平移和缩放功能
在版本的控件中,缩放和平移功能已被添加到控件。
使用鼠标左键控制缩放,用鼠标右键控制平移。
要缩放图表的特定部分,只需左键单击图表(这将是缩放矩形的左上角),然后拖动到右下角。
将出现一个矩形。
一旦松开鼠标按钮,四个轴将自动调整到您选择的区域。
默认情况下启用缩放,但您可以通过调用CChartCtrl:
:
SetZoomEnabled(boolbEnabled)来禁用缩放。
您还可以通过调用CChartAxis:
:
SetZoomLimit(doubledLimit)为每个轴指定缩放限制。
它指定缩放时轴的最小范围。
默认值为。
要平移控件,右键单击控件上的某处并移动鼠标。
鼠标下的点将“跟随”鼠标的移动(实际上,轴的最小和最大值将改变)。
默认情况下启用平移,但您可以通过调用CChartCtrl:
:
SetPanEnabled(boolbEnabled)来禁用它。
如果您左键单击图表(例如开始缩放),但如果您移动到左上角,所有使用缩放和平移功能所做的修改将被取消(控制将处于它的状态在使用平移和缩放操作之前)。
最后,还有一种方法通过调用CChartAxis:
:
SetPanZoomEnabled(boolbEnabled)禁用特定轴的平移和缩放功能。
利用高速功能
线和点系列允许以高速率绘制数据。
这通常在要绘制来自外部设备(例如,传感器)的数据时完成。
这是可能的,因为当您向此类系列添加点时,控件不会完全刷新,只会绘制最后一个点(或最后一个线段),