原文地址:https://www.codeproject.com/articles/14075/high-speed-charting-control
本文翻譯在CodeProject上的介紹(主要仍是谷歌翻譯,看不太明白的地方,請對比原文,敬請原諒),方便本身和後面人的學習(花費了兩天時間,但願是值得的)。推薦一個前輩寫的東西:TeeChart替代品,MFC下好用的高速繪圖控件-(Hight-Speed Charting),本身也轉載了這篇文章,在轉載的文章中根據本身的實驗修改了一些東西,修改了什麼如今也不記得了,文章地址:TeeChart替代品,MFC下好用的高速繪圖控件-(Hight-Speed Charting) .
下面是我能找到的資料前三個是原文提供的(要想找到最新的,到原文中找),後三個是本身在其餘的博客中找到的。
ChartCtrl_demo.zip
ChartCtrl_doxygen.zip
ChartCtrl_source.zip
ChartCtrl.pdf
MFC動態繪製曲線圖-HightSpeedChart實現.rar–>上傳還沒成功
TeeChart和HightSpeedChart動態繪圖.rar–>上傳還沒成功html
這個控件是用來展現2D數據,以下面的圖片。
git
對於我以前的一個項目,我須要在圖表控件上顯示連續的數據流。 我決定開發本身的控件,由於我找不到任何能夠提供所需靈活性的自由軟件控件。 其中一個主要的限制是,控件必須繪製大量的數據,並可以迅速顯示它(在Pocket PC上)。 控件可以經過僅繪製新的數據點而不是完整的數據序列來作到這一點而且圖表還可以顯示靜態數據。
這種控件是我長時間工做的結果,並且費盡周折地爲了提供足夠的靈活性來供須要它的人使用。 對於使用者反饋我表示由衷的感謝:一個郵件,留言板中的一一句話或只是對本文評級。 當我不知道是否還有人使用它時,我就沒有必要維護這個控件了。redis
這個控件是我花費很長時間的開發的結果,所以我對代碼的使用放置一些小條件:安全
該代碼能夠以編譯的形式用於任何非商業和商業目的。代碼能夠被從新開發,只要它提供做者名字和完整的免責聲明。 更改源代碼須要獲得做者的贊成。 此代碼不提供任何安全保證。 我不會對使用此代碼形成的損失負責。 使用它須要本身承擔風險。
This code may be used for any non-commercial and commercial purposes in a compiled form. The code may be redistributed as long as it remains unmodified and providing that the author name and the disclaimer remain intact. The sources can be modified with the author consent only. This code is provided without any guarantees. I cannot be held responsible for the damage or the loss of time it causes. Use it at your own risks.
鑑於開發這個控件所付出的努力,下面的要求並不過度: 若是你在在商業應用程序中使用這個控件,那麼請給我發郵件讓我知道。app
控件的主要特色是:less
高速繪圖(軸固定時),容許快速繪製數據 無限數量的數據序列(內存是限制) 每一個數據序列的數據量不受限制 支持線圖,點圖,平面圖,柱狀圖,K線圖 和甘特圖系列 最多四個軸(左,下,右和上軸) 標準軸,對數軸或日期/時間軸 自動伸縮的座標軸, 翻轉的座標軸(相互獨立) 軸標籤 點標籤 平滑的曲線 網格 圖例和標題 交互性(在控件中發生特定事件時的通知) 支持手動縮放和鼠標平移 支持鼠標指針 支持軸上的滾動條 高度可定製(顏色,標題,標籤,邊緣,字體等) 支持UNICODE 支持打印和保存到圖像文件編輯器
本文經過一系列簡短的教程來涵蓋控件的大部分功能。 閱讀本文後,您將可以快速地在本身的應用程序中使用本控件。ide
我決定從文章中刪除全部的類和函數的文檔,由於它不是很是友好而且我很難維護。 此外,隨着代碼的增加,要記錄的類和函數的列表變得過於普遍以致於不能將全部內容放在文章中。 做爲替代,我提供了一個doxygen文檔,您能夠從本文中(文章的開頭)下載:只需下載「Doxygen文檔」zip文件,解壓全部文件,雙擊「Index.html」文件,進行查看。函數
此圖表控件容許您在屏幕上繪製一系列數據。 此控件能夠添加幾個不一樣類型數據序列而且最多可使用四個軸。 添加到圖表的數據序列與一個水平軸(底部或頂部)和一個垂直軸(右側或左側)相關聯。 這兩個軸控制數據序列在圖表上的顯示方式。
爲了可以在應用程序中使用次圖表控件,您首先須要在本身的工程裏添加源代碼zip中包含的文件。性能
注意:控件在內部使用動態轉型,所以必須啓用RTTI(RunTime Type Information 運行時自動類型識別的機制),不然可能會發生崩潰。 默認狀況下,VC6沒有啓用RTTI,所以要啓用它打開項目設置 - >「C / C ++」選項卡 - >「C ++語言」類別,並確保「Enable Run-Time Type Information (RTTI) 「選項已選中。
在應用程序中使用圖表控件有兩種方法:手動插入,或經過資源編輯器插入。
1.#include "ChartCtrl"
添加在對話框(Dialog)類的頭文件中
2.在對話框類中添加變量CChartCtrl
:
//{{AFX_DATA(CChartDemoDlg) //}}AFX_DATA CChartCtrl m_ChartCtrl;
3.在對話框類的OnInitDialog
方法中添加這個控件的Create
方法。
1.向對話框資源添加自定義控件,打開控件的屬性,併爲Class屬
性指定ChartCtrl
。 爲了不滾動條上的閃爍,必須設置WS_CLIPCHILDREN
樣式(0x02000000L)
,如圖所示。
2.#include "ChartCtrl.h"
添加在對話框(Dialog)類的頭文件中
3.在對話框類中添加變量CChartCtrl
:
//{{AFX_DATA(CChartDemoDlg) //}}AFX_DATA CChartCtrl m_ChartCtrl;
4.在DoDataExchange
函數中添加DDX_Control
(不要忘了更改ID號和控件名字):
Add a variable of type CChartCtrl in your dialog class: Hide Copy Code //{{AFX_DATA(CChartDemoDlg) //}}AFX_DATA
幾種類型的數據序列能夠添加到控制:點序列,線序列,曲面序列,柱狀圖序列,K線圖序列或甘特圖序列。 點的數據格式可能因序列而異(例如,K線圖和甘特圖系列使用不一樣的點格式)。
Series type Description Create function Point type Point series Each data point is represented by a single point on the screen. The appearance of the point can be customized. CreatePointsSerie SChartXYPoint Line series The data points are connected through a line. The appearance of this line can be customized and it can also be smoothed. CreateLineSerie SChartXYPoint Surface series The data points are connected through a line and the area under this line is filled with a specific brush. The series can also be displayed vertically. CreateSurfaceSerie SChartXYPoint Bar series Each data point is plotted as a vertical bar of a certain width. Multiple bar series can be stacked next to each other without overlapping. The bars can also be plotted horizontally. CreateBarSerie SChartXYPoint Candlestick series Each data point is made of five attributes: the low value, the high value, the open value, the close value and the X value (time). Each point is drawn as a candlestick. This series is used for plotting financial data. CreateCandlestickSerie SChartCandlestickPoint Gantt series Each data point is made of three attributes: the start and end time and a Y value. Each point is drawn as a horizontal bar starting at the start time and finishing at the end time. The bar is positioned along the Y axis at its Y value. CreateGanttSerie SChartGanttPoint
一旦你選擇了一種系列,你能夠經過調用上表中列出的CChartCtrl
類的輔助函數之一將其添加到圖表中。 這些函數接受兩個可選參數:兩個布爾值來肯定描述該系列是鏈接到副水平軸(頂軸)或者是鏈接大副垂直軸(右軸)。 若是未指定參數,則數據系列將附加到主水平軸(底部軸)和主垂直軸(左軸)。
警告: 在將任何系列添加到圖表以前,您須要建立該系列所鏈接的兩個軸。 若是不這樣作,將致使控件失效(assert)。 有關詳細信息,請參見「操縱軸」一節。
一旦將系列添加到圖表後,咱們就可使用數據填充該圖表。 有兩種方法:將數據放到一個單元中一塊兒添加,或者逐點添加。 後者用於有動態數據時:每次調用函數時都會更新圖表。 雖然這個調用是快速的(在某些特定條件下),可是最好儘量地將數據放到一個單元中。 下面是一個簡單代碼示例,它在圖表中建立兩個系列,並用數據填充它們:一個系列在初始化時徹底填充,另外一個系列在調用OnDataReceived
函數(僅存在於此示例的目的)時填充。 m_pLineSeries
,m_pPointsSeries
和m_ChartCtrl
是CMyClass
類的成員變量。
void CMyClass::Init() { .... // SNIP: Creation of the axes in the chart. This MUST be done before. m_pLineSeries = m_ChartCtrl.CreateLineSerie(); m_pPointsSeries = m_ChartCtrl.CreatePointsSerie(); double YValues[10]; for (inti=0;i<10;i++) XValues[i] = YValues[i] = i; m_pLineSerie->SetPoints(XValues,YValues,10); } void CMyClass::OnDataReceived(double X, double Y) { 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 Id Bar series with different group Id
您還能夠經過調用SetInterSpace
靜態函數來控制全部柱形圖之間剩餘的空間的寬度。 這將爲全部系列設置以像素爲單位的空間(所以,若是顯示多於兩個系列,則在任何位置使用相同的空間)。 注意,您能夠經過調用SetBarWidth
單獨設置柱狀圖系列的寬度。
一旦使用數據填充您的系列,您還能夠在系列的特定點上添加標籤:這個標籤始終附加到特定點。 如今,只提供一種類型的標籤,氣泡標籤:包含文本的圓角矩形並用線鏈接到特定點上。 固然,若是須要,您也能夠提供本身的自定義標籤(參見「擴展功能」一節)。
有兩種方式建立文本標籤:靜態建立標籤時,或動態註冊一個對象,當標籤請求時,它將提供文本。 第一種方法是最簡單的,但也不太靈活。 下面是一個代碼片斷,顯示如何作(假設m_pSeries
已經建立並填充足夠的數據):
void CMyClass::Init() { // SNIP... m_pSeries->CreateBalloonLabel(5,_T("This is a simple label")); }
此調用將建立一個帶有「This is a simple label」文本的標籤,並將其附加到帶索引爲5的點。該函數返回一個指向新建立的標籤的指針,以便您能夠修改其某些屬性或存儲以供之後使用。
第二種方法有點複雜,但提供了更多的靈活性:例如,您能夠以更方便的方式在標籤中顯示點屬性(例如X值,Y值,…)。 爲此,您必須建立一個繼承自CChartLabelProvider <PointType>
的類,並在建立標籤時提供此類的實例。 此類是模板類,模板參數是標籤附加到的系列的點類型。 這個類是一個簡單的接口,你必須覆蓋TChartString GetText(CChartSerieBase <PointType> * pSerie,unsigned uPtIndex)
方法。 此函數應返回必須在標籤中顯示的文本。 它接收指向標籤所附加的系列和點索引的指針。 這裏有一個這樣的標籤提供程序類的例子:
class CCustomLabelProvider : public CChartLabelProvider<SChartXYPoint> { public: TChartString GetText(CChartSerieBase<SChartXYPoint>* pSeries, unsigned uPtIndex) { TChartStringStream ssText; SChartXYPoint Point = pSeries->GetPoint(uPtIndex); ssText << _T("X value=") << Point.X; return ssText.str(); } };
此代碼段顯示如何將其與標籤一塊兒使用。 注意m_pSeries應該是一個操做SChartXYPoint點(點,線,面或者柱系列)的系列。 若是不是這樣,你的代碼將給出一個編譯錯誤。
void CMyClass::Init() { // SNIP... m_pLabelProvider = new CCustomLabelProvider(); m_pSeries->CreateBalloonLabel(5, m_pLabelProvider); }
控件不獲取指針的全部權,所以,當你再也不須要時,你有責任刪除它。 在上面的例子中,它一般會在CMyClass
析構函數中被刪除。 在上面的示例中,您能夠爲全部要添加的標籤地方重複使用相同的標籤類, 這也帶來另外一個優勢:若是你想在運行時改變標籤的格式,你只須要在CustomLabelProvider
中添加代碼。 不須要遍歷全部現有標籤並更改其文本。 固然,在這種狀況下,須要刷新控件,由於必須從新繪製標籤。 還要注意TChartStringStream
類的用法,TChartStringStream
類是由控件提供的別名(相似於TChartString
)。 當UNICODE被定義時,它解析爲std :: wstringstream
,當未定義UNICODE時,解析爲std :: stringstream
。
軸是圖表的一個重要特徵,由於它們控制不一樣系列在控制中的顯示方式。 控件中最多可以使用四個軸:底部,頂部,左側和右側。 控件的每一個系列必須和一個水平軸和一個垂直軸相鏈接。 在圖表中添加系列時指定這些軸。 底部和左側軸是主軸,頂部和右側軸是輔助軸(您將在控件的某些功能中遇到此問題)。 如今有三種類型的軸供選擇:標準軸,對數軸和日期/時間軸。 您能夠在不一樣位置選用不一樣類型的軸。
一旦您選擇了在不一樣位置使用哪些軸,您須要先建立它們,而後才能向控件添加任何數據。 爲此,經過指定軸附加在哪一個位置,簡單地調用CreateStandardAxis
,CreateLogarithmicAxis
或CreateDateTimeAxis
。 若是已經在該位置建立了軸,則控件將銷燬它而且用新的軸替換它。 這裏有一個簡單的代碼片斷,顯示如何在底部建立日期/時間,在左側建立一個標準軸:
void CMyClass::Init() { CChartStandardAxis* pBottomAxis = m_ChartCtrl.CreateStandardAxis(CChartCtrl::BottomAxis); CChartLogarithmicAxis* pLeftAxis = m_ChartCtrl.CreateLogarithmicAxis(CChartCtrl::LeftAxis); }
一旦建立了這些軸,就能夠對它們設置一些屬性。 大多數屬性在全部軸類型之間共享(例如自動模式,最小值和最大值,軸標籤,…)。 軸能夠設置爲三種「自動」模式:全自動,屏幕自動和手動模式。
全自動模式基於附加到該軸的全部系列計算軸最小值和最大值(全部系列的全部點的最小值用做軸的最小值,並使用全部系列的全部點的最大值 做爲軸的最大值)。 屏幕自動模式基於與該軸相關的全部系列的全部可見點計算軸最小值和最大值。 例如,若是圖表僅顯示鏈接到手動底部軸和屏幕自動左側軸的一個系列,則左側軸將自適應於當前可見的點,而且不考慮這些點有可能超過底軸的範圍(在全自動模式下,底軸外部的點將被考慮)。 警告:若是系列的兩個軸都處於屏幕自動模式,則結果未定義。 在手動模式下,軸最小和最大值由用戶設置,不禁控件計算。
在使用自動軸模式下,若是將數據動態添加到控件,若是新的數據點位於軸的範圍以外,那麼控件將自動刷新。 這裏是一個代碼片斷(繼續前一個代碼段),顯示一個全自動軸(底部軸)和一個手動軸(左軸,它是一個對數軸):
void CMyClass::Init() { // SNIP ... pBottomAxis->SetAutomaticMode(CChartAxis::FullAutomatic); // The call to SetAutomaticMode(CChartAxis::NotAutomatic) is not // really needed because this is the default. pLeftAxis->SetAutomaticMode(CChartAxis::NotAutomatic); pLeftAxis->SetMinMax(0.01,1000); }
軸有一個模式是離散模式(默認禁用)。此模式指定軸不顯示連續值,而只顯示離散值,這些值是軸上刻度指定的值,而軸將不顯示其餘的值。嘗試繪製不一樣於顯示的節拍值的值是不可能的。讓咱們舉一個例子:假設你有一個底部標準軸,間隔爲1.0(因此,顯示的蜱是1,2,3等等)。嘗試繪製X值爲0.5的點將在相同位置顯示該點,就好像它的值爲1.0。事實上,你能夠認爲兩個刻度之間的區域是一個常量值。這就是爲何刻度標籤顯示在兩個刻度的中間,而不是刻度自己。
這裏有一個小代碼片斷,顯示離散軸對系列顯示方式的影響。代碼片斷下的兩個圖像顯示啓用離散模式(第一個圖像)或禁用(第二個圖像)的結果。
void CMyClass::Init() { CChartStandardAxis* pBottomAxis = m_ChartCtrl.CreateStandardAxis(CChartCtrl::BottomAxis); pBottomAxis->SetMinMax(0, 10); CChartStandardAxis* pLeftAxis = m_ChartCtrl.CreateStandardAxis(CChartCtrl::LeftAxis); pLeftAxis->SetMinMax(0, 10); pBottomAxis->SetTickIncrement(false, 1.0); pBottomAxis->SetDiscrete(true); CChartLineSerie* pSeries = m_ChartCtrl.CreateLineSerie(); double XVal[20]; double YVal[20]; for (int i=0; i<20; i++) { XVal[i] = YVal[i] = i/2.0; } pSeries->SetPoints(XVal,YVal,20); }
Discrete mode enabled Discrete mode disabled
使用日期/時間軸有點特別,下面是如何利用這個功能的解釋。要了解日期/時間軸的重要一點是它們在COleDateTime
對象內部工做。緣由很簡單:COleDateTime
中有DATE
類型的類,DATE
類型是一個雙精度型。因爲圖表中的點表示爲雙精度值,所以它很是適合:使用標準點(非日期/時間)和日期/時間點之間沒有差別,這使得後者的使用不太複雜。全部點仍然存儲爲雙精度型,不管是不是日期/時間。
建立日期/時間軸後,能夠在控件中填充數據。爲此目的,沒有改變:你必須從CChartSerie類調用void AddPoint(double X,double Y)或void SetPoints(double * X,double * Y,int Count)。 CChartCtrl類提供了兩個靜態函數,讓你從COleDateTime轉換爲雙精度,反之亦然:
double DateToValue(const COleDateTime& Date) COleDateTime ValueToDate(double Value)
若是您有另外一種格式的日期(例如time_t
或SYSTEMTIME
),這不是一個問題,由於COleDateTime
對象能夠從不一樣的時間格式構造(檢查COleDateTime
類的MSDN文檔,以瞭解從哪一種格式能夠構造它)。
填充數據後,能夠配置軸以顯示所需的內容。 與日期/時間軸相關的幾個功能可用:
void SetDateTimeIncrement(TimeInterval Interval, int Multiplier) void SetDateTimeFormat(bool bAutomatic, const TChartString& strFormat) void SetReferenceTick(COleDateTime referenceTick)
第一個容許您指定軸上顯示的兩個節拍之間的間隔。兩個節拍之間的間隔將遵照正確的時間,這意味着若是指定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日。
下面是一個簡單的代碼片斷,它建立一個日期/時間軸,並顯示不一樣函數的用法:
void CMyClass::Init() { // Sets the axis min value to January 1st 2006 and the axis // max value to December 31st 2007. COleDateTime minValue(2006,1,1,0,0,0); COleDateTime maxValue(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 label format for instance "Jan 2006" pBottomAxis->SetTickLabelFormat(false, _T("%b %Y")); }
控件的外觀方面能夠根據不一樣的應用場景作出更改,好比控件的不一樣部分(圖例,標題,背景,…)均可以修改。 全部與這些對象的交互是經過CChartCtrl
類來實現:一些將根據須要建立(例如axes或series),一些在建立控件時建立(legend,titles,…)。 通常來講,你永遠不會本身建立這些對象,而是將該任務委派給CChartCtrl類。 惟一的例外是當您要使用自定義軸或自定義系列(請參閱「擴展功能」部分)。 例如,下面是一個代碼段,設置漸變背景,並將圖例放在控件的底部:
void CMyClass::Init() { // SNIP // Disable the refresh of the control m_ChartCtrl.EnableRefresh(false); // Set the gradient for the background m_ChartCtrl.SetBackGradient(RGB(255,255,255),RGB(125,125,255),gtVertical); // Dock the legend at the bottom m_ChartCtrl.GetLegend()->DockLegend(CChartLegend::dsDockBottom); // Specifies that the legend entries are horizontally stacked m_ChartCtrl.GetLegend()->SetHorizontalMode(true); // Re-enable the refresh of the control m_ChartCtrl.EnableRefresh(true); }
重要:從版本1.4的控件,每次調用控件上的一個屬性將致使控件的徹底刷新(即便像改變一些文本的字體或對象的顏色)。 爲了不在沒有必要時刷新控件(例如,當您同時更改多個屬性時),應首先禁用刷新,更改屬性,而後從新啓用刷新,如上面的代碼段所示 。
自從1.5版的控件開始支持UNICODE。 全部出現的std :: string
對象已被TChartString
對象替換,這只是一個typedef
,若是未啓用UNICODE,則解析爲std :: string
,並在啓用UNICODE時解析爲std :: wstring
。
有時,應用程序須要響應用戶鼠標操做。 例如,若是用戶點擊點,則程序能夠顯示關於被點擊的點的信息,這一節將解釋如何作到。
雖然原理是有點不一樣,可是不管你想聽在圖表上的通常鼠標事件自己(點擊軸,圖例,…)或你是否對特定系列的鼠標事件感興趣。 這兩種狀況都很容易實現。
你必須實現CChartMouseListener
接口,覆蓋你感興趣的方法,並經過調用CChartCtrl :: RegisterMouseListener(CChartMouseListener * pMouseListener)
將該類的實例註冊到圖表控件。 根據鼠標事件發生在控件的哪一個部分:標題,圖例,軸或繪圖區,調用該接口上的不一樣函數。 對於全部這些函數,老是傳遞兩個參數:MouseEvent
,它是列出鼠標事件類型(鼠標移動,左鍵單擊,…)的枚舉,以及一個CPoint
對象,它包含的發生事件的點的屏幕座標。 對於某些函數,須要時傳遞一些其餘參數。 例如,當單擊一個軸時,指向該軸的指針被傳遞給該函數。
下面是CChartMouseListener
的實現,它對軸的點擊做出反應,並顯示一個消息框:
class CCustomMouseListener : public CChartMouseListener { public: void OnMouseEventAxis(MouseEvent mouseEvent, CPoint point, CChartAxis* pAxisClicked) { if (mouseEvent == CChartMouseListener::LButtonDoubleClick) { MessageBox(_T("Axis clicked"), _T("Info"), MB_OK); } } };
而後你必須建立一個這個類的實例並註冊它:
m_pMouseListener = new CCustomMouseListener(); m_ChartCtrl.RegisterMouseListener(m_pMouseListener);
這裏也須要本身刪除指針。
響應系列上的事件與響應通常事件很是類似,只是監聽器是CChartSeriesMouseListener
的一個實例,它是一個模板類,模板參數是系列的點類型。 這是須要的,以免當您要檢索點的特定值時沒必要要的轉型。 另外一個區別是,您必須在系列自己上註冊監聽器,而不是在圖表控件上註冊。
下面是CChartSeriesMouseListener
的實現,它對系列的點擊作出反應,若是點擊發生在點上,它將顯示一個帶有點的Y值的消息框:
class CCustomMouseListener : public CChartSeriesMouseListener<SChartXYPoint> { public: void OnMouseEventSeries(MouseEvent mouseEvent, CPoint point, CChartSerieBase<SChartXYPoint>* pSerie, unsigned uPointIndex) { if (mouseEvent == CChartMouseListener::LButtonDoubleClick && uPointIndex != INVALID_POINT) { TChartStringStream ssText; SChartXYPoint Point = pSeries->GetPoint(uPointIndex); ssText << _T("Y value=") << Point.Y; TChartString strText = ssText.str(); MessageBox(NULL,strText.c_str(), _T("Info"), MB_OK); } } };
注意:當用戶不點擊一個點時,OnMouseEventSeries
函數也能夠被調用。 例如當用戶在兩個點之間但仍然在該系列上點擊時狀況。 在這種狀況下,爲uPointIndex
參數傳遞INVALID_POINT
。
而後,您必須建立此類的實例並將其註冊到系列中:
m_pMouseListener = new CCustomMouseListener(); m_pSeries.RegisterMouseListener(m_pMouseListener);
注意:只有當系列操做SChartXYPoint
類型的點(點,線,面或者柱狀圖系列)時,這纔會起做用。 若是不是這樣,您的代碼將生成編譯錯誤。
出於性能緣由,禁止檢測系列上的鼠標移動事件。 要啓用它,請參閱doxygen文檔中的CChartSerie :: EnableMouseNotifications函數。
您還能夠向控件添加光標。 支持兩種類型的光標:「十字線」光標和「拉線」光標。 第一個是在鼠標移動的繪圖區域上顯示的簡單十字,第二個是與特定軸關聯的水平或垂直線,您能夠經過單擊它並使用鼠標移動來拖動。 對於每一個光標,您能夠註冊一個偵聽器,以便在移動光標時通知它。 這裏是一段代碼,用於建立與底部和左側軸相關聯的「十字準線」光標以及與底部軸相關聯的「拉線」光標:
// Creates a cross-hair cursor associated with the two primary axes. CChartCrossHairCursor* pCrossHair = m_ChartCtrl.CreateCrossHairCursor(); // Creates a dragline cursor associated with the bottom axis. CChartDragLineCursor* pDragLine = m_ChartCtrl.CreateDragLineCursor(CChartCtrl::BottomAxis); // Hides the mouse when it is over the plotting area. m_ChartCtrl.ShowMouseCursor(false);
注意到對CChartCtrl :: ShowMouseCursor
的調用結束。 默認狀況下,鼠標老是可見的,可是當您使用十字光標時,當它在繪圖區域時隱藏有時是須要的。
若是但願在光標位置更改時收到通知,則必須實現CChartCursorListener
接口,建立其實例並使用光標註冊它:
class CCustomCursorListener : public CChartCursorListener { public: void OnCursorMoved(CChartCursor *pCursor, double xValue, double yValue) { TChartStringStream ssText; ssText << _T("Cursor moved: xPos=") << xValue << _T(", yPos=") << yValue; // Do something with the string... } };
CCustomCursorListener* pCursorListener = new CCustomCursorListener; pDragLine->RegisterListener(pCursorListener);
OnCursorMoved
函數接收一個X和Y值,但對於拖動光標,只使用這些值中的一個:若是光標與水平軸相關聯,則使用X值,不然使用Y值。
在版本1.1的控件中,縮放和平移功能已被添加到控件。 使用鼠標左鍵控制縮放,用鼠標右鍵控制平移。 要縮放圖表的特定部分,只需左鍵單擊圖表(這將是縮放矩形的左上角),而後拖動到右下角。 將出現一個矩形。 一旦鬆開鼠標按鈕,四個軸將自動調整到您選擇的區域。 默認狀況下啓用縮放,但您能夠經過調用CChartCtrl :: SetZoomEnabled(bool bEnabled)
來禁用縮放。 您還能夠經過調用CChartAxis :: SetZoomLimit(double dLimit)
爲每一個軸指定縮放限制。 它指定縮放時軸的最小範圍。 默認值爲0.001。
要平移控件,右鍵單擊控件上的某處並移動鼠標。 鼠標下的點將「跟隨」鼠標的移動(實際上,軸的最小和最大值將改變)。 默認狀況下啓用平移,但您能夠經過調用CChartCtrl :: SetPanEnabled(bool bEnabled)
來禁用它。
若是您左鍵單擊圖表(例如開始縮放),但若是您移動到左上角,全部使用縮放和平移功能所作的修改將被取消(控制將處於它的狀態 在使用平移和縮放操做以前)。 最後,還有一種方法經過調用CChartAxis :: SetPanZoomEnabled(bool bEnabled)
禁用特定軸的平移和縮放功能。
線和點系列容許以高速率繪製數據。 這一般在要繪製來自外部設備(例如,傳感器)的數據時完成。 這是可能的,由於當您向此類系列添加點時,控件不會徹底刷新,只會繪製最後一個點(或最後一個線段),這是很是有效的。 可是,若是但願控件可以足夠快地繪製數據,則必須考慮幾點。
一個重要的事情是,使用自動軸可能會下降不少性能。這是由於若是一個點繪製在軸範圍以外,則軸範圍將被自動調整,這意味着控制將被徹底刷新。所以,若是您使用自動底部軸線並具備「滾動」軌跡,則每一個新點都將位於軸的當前範圍以外,而且將對每一個點執行控制刷新。處理的更好的方法是使用固定軸而且每秒手動地增長軸的範圍(或以合理的速率)。
另外一個重要的點是,你不該該在向一個系列添加一個新點以後調用RefreshCtrl
。這固然會徹底地刷新控件,可是應該避免這樣作。最後,若是您須要同時應用幾個修改或添加幾個點到控件,您應該在EnableRefresh(false)
和EnableRefresh(true)
之間封裝這些調用(請參閱「自定義外觀」部分)。
在某些特定狀況下,您須要使用新功能擴展控件,例如新的系列類型。目前,您能夠自定義四個組件:序列,軸,點標籤和光標。
要提供新軸,新標籤或新光標,您只需繼承基類(CChartAxis
,CChartLabel
或CChartCursor
)並實現所需的虛擬函數。一旦完成,您能夠經過調用不一樣函數的自定義版本(CChartCtrl :: AttachCustomAxis
,CChartCtrl :: AttachCustomLabel
或CChartCtrl :: AttachCustomCursor
)附加您的新對象。 CChartLabel
類是一個模板類。這個主題有點普遍,進入了不少細節,但最簡單的方法是看看不一樣的現有類。
若是你想提供新的系列,這有點不一樣:你首先要考慮你想要在你的系列中操縱的點的類型。若是你只須要使用X和Y值來操做點,那麼你能夠繼承CChartXYSerie
,它提供了不少功能來操做這些點。而後,您必須實現所需的虛擬函數。看看下面的系列:CChartLineSerie
,CChartPointSerie
,CChartSurfaceSerie
和CChartBarSerie
具體示例。
若是你的系列操縱其餘類型的點,那麼你首先必須爲點包含如下方法建立一個結構:double GetX()
,double GetXMin()
,double GetXMax()
,double GetY()
,double GetYMin()
和double GetYMax()
。一旦完成,您必須繼承CChartSerieBase
並將此點做爲模板參數。而後,您必須提供所需的虛擬功能。看看下面的系列具體例子:CChartCandlestickSerie
和CChartGanttSerie
。
在版本2.0中,對控件進行重構,致使API的更改。 主要的可見變化是每一個軸類型如今有其單獨的類(CChartStandardAxis,CChartDateTimeAxis和CChartLogarithmicAxis)。 這也意味着默認狀況下沒有建立軸,而且您必須在向圖表添加系列以前本身建立軸(不然代碼將斷言)。 這包括在「操縱軸」部分。
另外一個變化是添加系列到圖表的方式:AddSerie已經在CChartCtrl類中刪除,並已被幫助函數替代,以建立特定的系列類型(CreateLineSerie,CreatePointsSerie,…)。 這些函數返回確切的系列類型,所以再也不須要鑄造。 這在「操縱系列」一節中有詳細描述。
版本3.0.0的主要變化是,系列基類如今已經做爲模板類,模板參數是系列操做的點類型。若是您沒有經過提供新的系列類型擴展控件,這將不會在您的代碼中有所不一樣。若是你提供了一個新的系列類型,你的類必須繼承CCharSerieBase並提供它操做的點的類型。若是你的系列使用只有X和Y值的點,你能夠簡單地繼承CChartXYSerie。看看現有的系列更多的例子。
另外一個小的修改是標籤提供程序如今也是模板類(出於一樣的緣由)。而且監聽系列中的鼠標事件如今從圖表上的鼠標事件中分離出來。這兩點在「在點上添加標籤」部分和「鼠標事件通知」部分中有很好的解釋。
最後,CChartAxis :: SetAutomatic方法已被標記爲已棄用,您應該使用CChartAxis :: SetAutomaticMode(已經引入了一個額外的自動模式)。
本節只是兩個代碼片斷,顯示瞭如何使用控件。 第一個片斷再現了示波器示例的圖像(參見本文頂部),第二個示例再現了「2008年收入」圖像。 代碼是文檔化的,因此它不該該太難理解。
Oscilloscope example:
// Disable the refresh of the control (avoid multiple refresh). m_ChartCtrl.EnableRefresh(false); // Create a bottom and left axes CChartStandardAxis* pBottomAxis = m_ChartCtrl.CreateStandardAxis(CChartCtrl::BottomAxis); CChartStandardAxis* pLeftAxis = m_ChartCtrl.CreateStandardAxis(CChartCtrl::LeftAxis); // Sets the min and max values of the bottom and left axis to -15 -> 15 pBottomAxis->SetMinMax(-15,15); pLeftAxis->SetMinMax(-15,15); // Add a new series of type line to the control and add data to it CChartLineSerie* pLineSeries = m_ChartCtrl.CreateLineSerie(); // Specifies that the points in the series are not ordered (needed to be able // to draw an ellipse). pLineSeries->SetSeriesOrdering(poNoOrdering); for (int i=0;i<361;i++) { double X = 10 * sin(i/360.0 * 2 * 3.141592); double Y = 10 * cos( (i-60)/360.0 * 2 * 3.141592); pLineSeries->AddPoint(X,Y); } // Defines the different colors (back color, axes color, ...) COLORREF BackColor = RGB(0,50,0); COLORREF GridColor = RGB(0,180,0); COLORREF TextColor = RGB(0,180,0); COLORREF SerieColor = RGB(0,255,0); // Specifies a sunken border for the control m_ChartCtrl.SetEdgeType(EDGE_SUNKEN); // Sets the color of the border and the back color m_ChartCtrl.SetBorderColor(TextColor); m_ChartCtrl.SetBackColor(BackColor); //Sets the color of the different elements of the bottom axis m_ChartCtrl.GetBottomAxis()->SetAxisColor(TextColor); m_ChartCtrl.GetBottomAxis()->SetTextColor(TextColor); m_ChartCtrl.GetBottomAxis()->GetGrid()->SetColor(GridColor); // Sets the color of the different elements of the left axis m_ChartCtrl.GetLeftAxis()->SetAxisColor(TextColor); m_ChartCtrl.GetLeftAxis()->SetTextColor(TextColor); m_ChartCtrl.GetLeftAxis()->GetGrid()->SetColor(GridColor); // Sets the color of the title, change the font to Times New Roman // and add a string m_ChartCtrl.GetTitle()->SetColor(TextColor); m_ChartCtrl.GetTitle()->SetFont(140,_T("Times New Roman")); m_ChartCtrl.GetTitle()->AddString(_T("An example of oscilloscope")); // Change the color of the line series pLineSeries->SetColor(SerieColor); // Finally re-enable the refresh of the control. This will refresh the // control if any refresh was still 'pending'. m_ChartCtrl.EnableRefresh(true);
「Income over 2008」 example:
srand((unsigned int)time(NULL)); // Disable the refresh m_ChartCtrl.EnableRefresh(false); COleDateTime Min(2008,1,1,0,0,0); COleDateTime Max(2008,10,1,0,0,0); // Create the bottom axis and configure it properly CChartDateTimeAxis* pBottomAxis = m_ChartCtrl.CreateDateTimeAxis(CChartCtrl::BottomAxis); pBottomAxis->SetMinMax(Min,Max); pBottomAxis->SetDiscrete(true); pBottomAxis->SetTickIncrement(false,CChartDateTimeAxis::tiMonth,1); pBottomAxis->SetTickLabelFormat(false,_T("%b")); // Create the left axis and configure it properly CChartStandardAxis* pLeftAxis = m_ChartCtrl.CreateStandardAxis(CChartCtrl::LeftAxis); pLeftAxis->SetMinMax(0,100); pLeftAxis->GetLabel()->SetText(_T("Units sold")); // Create the right axis and configure it properly CChartStandardAxis* pRightAxis = m_ChartCtrl.CreateStandardAxis(CChartCtrl::RightAxis); pRightAxis->SetVisible(true); pRightAxis->GetLabel()->SetText(_T("Income (kEuros)")); pRightAxis->SetMinMax(0,200); // Configure the legend m_ChartCtrl.GetLegend()->SetVisible(true); m_ChartCtrl.GetLegend()->SetHorizontalMode(true); m_ChartCtrl.GetLegend()->UndockLegend(80,50); // Add text to the title and set the font & color m_ChartCtrl.GetTitle()->AddString(_T("Income over 2008")); CChartFont titleFont; titleFont.SetFont(_T("Arial Black"),120,true,false,true); m_ChartCtrl.GetTitle()->SetFont(titleFont); m_ChartCtrl.GetTitle()->SetColor(RGB(0,0,128)); // Sets a gradient background m_ChartCtrl.SetBackGradient(RGB(255,255,255),RGB(150,150,255),gtVertical); // Create two bar series and a line series and populate them with data CChartBarSerie* pBarSeries1 = m_ChartCtrl.CreateBarSerie(); CChartBarSerie* pBarSeries2 = m_ChartCtrl.CreateBarSerie(); CChartLineSerie* pLineSeries = m_ChartCtrl.CreateLineSerie(false,true); int lowIndex = -1; int lowVal = 999; for (int i=0;i<9;i++) { COleDateTime TimeVal(2008,i+1,1,0,0,0); int DesktopVal = 20 + rand()%(100-30); pBarSeries1->AddPoint(TimeVal,DesktopVal); int LaptopVal = 10 + rand()%(80-20); pBarSeries2->AddPoint(TimeVal,LaptopVal); int Income = DesktopVal + LaptopVal*1.5; if (Income < lowVal) { lowVal = Income; lowIndex = i; } pLineSeries->AddPoint(TimeVal,Income); } // Configure the series properly pBarSeries1->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 line series. TChartStringStream labelStream; labelStream << _T("Min income: ") << lowVal; CChartBalloonLabel<SChartXYPoint>* pLabel = pLineSeries->CreateBalloonLabel(lowIndex, labelStream.str() + _T(" kEuros")); CChartFont labelFont; labelFont.SetFont(_T("Microsoft Sans Serif"),100,false,true,false); pLabel->SetFont(labelFont); // Re enable the refresh m_ChartCtrl.EnableRefresh(true);
Quite a lot of work is involved in the development of this control and, as any other software project, it might still contain bugs or errors in the documentation. If you encounter such a problem, please let me know (even if you fixed it yourself) so that I can fix the issue as soon as possible. Other users of the control will thank you for that. The same if you encounter errors in the 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 nice feature that could be useful for others, please let me know and I’ll add it to my wishlist. However, as I’m working on this control in my spare time, my time is rather limited.
Finally, if you liked this control, do not hesitate to drop me a word in the discussion forum or to rate the article, this is much appreciated. Thank you.
08/05/2006: Release of version 1.0 19/08/2006: Release of version 1.1
Bug fix in ScreenToValue function (CChartAxis) Bug fix in RemoveAllSeries function (CChartCtrl) Added support for manual zoom Added support for mouse panning Ability to specify a tick increment on the axis Added support for resizing the control 09/04/2007: Release of version 1.2
GDI leak corrected Invisible series are not taken in account for auto axis and legend (thanks to jerminator-jp) Ability to change the text color of the axis Ability to change the color of the border of the drawing area Surface series added 16/02/2008: Release of version 1.3
Added date/time axis Bug fix in how the logarithmic labels are displayed (trailing 0) Ability to change the color of the zoom rectangle Removed compiler warnings for VC2005 Bug fix in the zoom 14/04/2008: Release of version 1.4
Added support for scrollbars Bar series added Legend can be docked on any side or floating Support for legend in horizontal mode Support for transparent background on the legend Support for shadow for several objects RemovePointsFromBegin, RemovePointsFromEnd and AddPoints in the CChartSeries class Support for gradient background EnableRefresh and UndoPanZoom functions added in CChartCtrl Possibility to enable/disable the zoom for a specific axis and to set its limit Speed improvement on the series (min and max cached, ordering of the series) Series can be removed using their pointers Bug fix for invisible series in the legend Bug fix for logarithmic axis (1 digit was not displayed) Bug fix when removing series from the control Bug fix if the pen width is bigger than 1 for line series Bug fix for automatic axis 20/08/2008: Release of version 1.5 Added support for UNICODE Added support for printing Auto-hide scrollbars Baseline selection for bar series Performance patch Scrollbar flickering removed (see here) Bug fix: scrollbar is now updated when axis is panned Bug fix: calling AddPoint was not drawing the new point Bug fix: tick labels for log axis were not always correct (rounding error) Bug fix: last point of ChartPointSerie was not displayed Bug fix: moving the mouse outside the control doesn’t stop the zoom or pan operation (the button can be released outside the control) 13/04/2009: Release of version 2.0
The different axis types are now separated into different classes Modified the way to add series to the control for improved flexibility Added cursors Ability to display discrete axes Ability to be notified about mouse events occurring on the control Added labels on points Ability to display a smooth curve Added ChartFont: allows for italic, bold or underlined fonts Added the SetReferenceTick function for date/time axis Ability to store user data for each point Series now have an Id Removed the CChartObject class Points are now stored in a standard array instead of a std::vector for efficiency Binary 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 years Bug fix: bar series were drawn from the wrong axis 11/06/2009: Release of version 2.0.1
Optimization: the pan feature has been smoothed Optimization: points with the same X and Y values are not plotted anymore for the line series. Bug fix: in some situations, the code was crashing when accessing points outside the valid range Bug 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 properly Bug fix: inserting a point for which the X value already existed in the series did not add the point properly Bug fix with the CChartFont class 07/08/2009: Release of version 2.0.2
Bug fix: the control was crashing when a series with no points and no ordering was added Bug fix: the shadow of the line was not drawn correctly Bug fix: when an automatic date/time axis was used without any data, the code crashed 28/12/2009: Release of version 3.0.0
Series are now template classes with the template parameter being the point type. This allows the control to manipulate any type of points Added candlestick and Gantt series Added support to save the chart to an image file Bar series can be stacked Added a new automatic mode for axes: the screen automatic mode Listening for mouse events on a series has been moved to a CChartSeriesMouseListener class Bug fix: when a point X or Y value is modified, the series is reordered Bug fix: setting a tick increment on a standard axis did not show the digits properly 17/01/2010: Release of version 3.0.1
Bug fix: when using labels with the points series, the border of the points was changing color. Fixed by providing a way to specify the border color. Bug fix: the code was crashing when clicking on a series without having registered a mouse listener on the series. Bug fix: detection of mouse events on certain series type was crashing Bug fix: CChartTitle::SetVisible was not implemented 13/07/2010: Release of version 3.0.2
Bug fix: the high-speed functionality has been removed by mistake Bug fix: the draw function of the line series was not drawing points Bug fix: replaced Clear() by clear() in the ClearSerie function. Bug fix: Added implementation of ctor/dtor for the CChartCursorListener class Bug fix: memory leak when the series was cleared (labels were not deleted)
I would like to thank all the people from this community, they were a great help when I started programming. Thanks also to all the 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 Schramm and Kevin Winter. A special thanks to Bruno Lavier for the time spent working on the control. I hope I didn’t forget anybody.
Chris Maunder’s Colour Picker Control (used in the demo application) Drawing smooth curves with Bezier Primitives
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
$(function () { $('pre.prettyprint code').each(function () { var lines = $(this).text().split('\n').length; var $numbering = $('').addClass('pre-numbering').hide(); $(this).addClass('has-numbering').parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($('').text(i)); }; $numbering.fadeIn(1700); }); });