天天看點

深入淺出CChart 每日一課——快樂高四第五十六課 絮絮叨叨,歲月殺豬刀之FAQ

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的預設背景就溫柔得多了,如下:

深入淺出CChart 每日一課——快樂高四第五十六課 絮絮叨叨,歲月殺豬刀之FAQ

這個背景并不是強制性的,可以取消,也可以換成别的背景。參考本系列教程的第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坐标縮放功能是按住滑鼠左鍵,往右拖動放大,往左拖動縮小(還原上一次拖動狀态),很友善實用。

深入淺出CChart 每日一課——快樂高四第五十六課 絮絮叨叨,歲月殺豬刀之FAQ

如圖,在往右拖動的時候,會出現一個拖動框,松開滑鼠左鍵,圖像就放大到拖動框的範圍。 

深入淺出CChart 每日一課——快樂高四第五十六課 絮絮叨叨,歲月殺豬刀之FAQ

此外,打開坐标縮放功能後,預設滑鼠滾輪就可以縮放圖像了。

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内部已經內建了多種算法,包括克裡金法、自然鄰域法、反距離權重法等。

具體的使用方法是需要先調用一下下面這個函數

void		CChart::SetContourByArbPoints();
           

CChart内部就建立了一個插值函數作為場函數,預設的插值算法是自然領域法。

然後添加資料點即可。添加資料點的接口為

void		CChart::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。例如,假定時間是2022年6月20日16點50分30秒,時間字元串可以是”2022-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,這個庫在千萬資料量的情況下,無論是鍵盤滑鼠消息還是增删資料,響應都很絲滑,也有朋友把這個庫用到了項目中,反應良好。

繼續閱讀