本系列所有文章可以在這里查看[http://blog.csdn.net/cloud_castle/article/category/2123873](http://blog.csdn.net/cloud_castle/article/category/2123873)
接上文[Qt5官方demo解析集30——Extending QML - Binding Example](http://blog.csdn.net/cloud_castle/article/details/37534507)
最近在做QML制表,因此想找一些相關曲線繪制的demo看看,結果發現了這個例子,覺得挺不錯,它相比于我們之前的Extend和Particle系列顯得更大一些,涉及到的面也更廣一些。因此想拿過來給大家分享~
這個例子是基于QML的股票走勢圖繪制,數據來源于yahoo的納達克斯-100指數,向左滑動可以選擇股票。
曲線頁面:

列表頁面:

工程目錄如下:

這個例子的結構會稍微復雜一些,我建議大家在QtCreator中將這個例子打開,這樣有什么想法或是疑慮時我們可以將代碼改改,或是添加一些輸出信息,舉一反三的學習總是比較有效的。
ok,我們自頂而下的跟著調用的順序來:
stocqt.qml:
~~~
import QtQuick 2.0
import QtQml.Models 2.1
import "./content" // 添加其他qml文件路徑
Rectangle {
id: mainRect
width: 1000
height: 700
property int listViewActive: 0 // 該頁面實際是一個ListView的一部分,這個屬性用來表明前面的列表頁面是否被激活
Rectangle { // 導航欄
id: banner
height: 80
anchors.top: parent.top
width: parent.width
color: "#000000"
Image {
id: arrow
source: "./content/images/icon-left-arrow.png"
anchors.left: banner.left
anchors.leftMargin: 20
anchors.verticalCenter: banner.verticalCenter
visible: root.currentIndex == 1 ? true : false // 曲線頁面是ListView的第二個頁面,currentIndex的值也就是1
MouseArea {
anchors.fill: parent
onClicked: listViewActive = 1;
}
}
Item { // 將相關組件放在一個Item容器中
id: textItem
width: stocText.width + qtText.width // 容器的尺寸由組件決定
height: stocText.height + qtText.height
anchors.horizontalCenter: banner.horizontalCenter
anchors.verticalCenter: banner.verticalCenter
Text { // Stoc
id: stocText
anchors.verticalCenter: textItem.verticalCenter
color: "#ffffff"
font.family: "Abel"
font.pointSize: 40
text: "Stoc"
}
Text { // Qt
id: qtText
anchors.verticalCenter: textItem.verticalCenter
anchors.left: stocText.right
color: "#5caa15"
font.family: "Abel"
font.pointSize: 40
text: "Qt"
}
}
}
ListView { // 標題欄下方則是ListView的內容,它為列表頁面與曲線頁面提供了滑動切換的能力
id: root
width: parent.width
anchors.top: banner.bottom
anchors.bottom: parent.bottom
snapMode: ListView.SnapOneItem // 設置該屬性使View停止在一個完整的頁面上
highlightRangeMode: ListView.StrictlyEnforceRange
highlightMoveDuration: 250 // 這里設置了一個無形的高亮,它不顯示,但提供了0.25秒的切換動畫
focus: false
orientation: ListView.Horizontal
boundsBehavior: Flickable.StopAtBounds // 滑動時停在邊界,默認可以拉拽更遠并在彈回時帶有超調效果
currentIndex: listViewActive == 0 ? 1 : 0 // 使用屬性綁定,當listViewActive被激活時,使currentIndex置0,從而跳轉到列表頁面
onCurrentIndexChanged: {
if (currentIndex == 1)
listViewActive = 0;
}
StockModel { // 初始化數據模型
id: stock
stockId: listView.currentStockId // 注意這個listView不是其父ListView,而是下面那個,即列表頁面
stockName: listView.currentStockName // 列表頁面將當前所選的股票ID及名字賦給StockModel,使其取得相應的數據
onStockIdChanged: stock.updateStock(); // 當ID改變時數據更新
onDataReady: {
console.log(listView.currentStockId, listView.currentStockName)
root.positionViewAtIndex(1, ListView.SnapPosition) // 上面的高亮即為這里的ListView.SnapPosition做準備,數據變更后切換到曲線頁面,帶有0.25秒的過渡動畫
stockView.update() // 頁面更新
}
}
model: ObjectModel { // ObjectModel源自上面import的Qt.QML.Model 2.1,它使得ListView可以使用一組對象作為模型
StockListView { // 列表頁面
id: listView
width: root.width
height: root.height
}
StockView { // 曲線頁面
id: stockView
width: root.width
height: root.height
stocklist: listView
stock: stock
}
}
}
}
~~~
有人說數據是一個程序骨架,那就看看這個架子吧~
StockModel.qml:
~~~
import QtQuick 2.0
ListModel { // ListModel作為根項目,自定義屬性作為接口,并定義了多個函數。但他本身在初始化時并不進行運算
id: model
property string stockId: "" // 股票ID
property string stockName: "" // 股票名
property string stockDataCycle: "d" // 數據周期
property bool ready: false // 標志位
property real stockPrice: 0.0 // 股票價格
property real stockPriceChanged: 0.0 // 價格變化
signal dataReady // 耗時的數據類通常需要定義這個信號
function indexOf(date) { // 返回從特定date的數據在數據集中的位置
var newest = new Date(model.get(0).date); // 獲取第一個數據對象的日期
var oldest = new Date(model.get(model.count - 1).date); // 最后一個數據對象的日期
if (newest <= date)
return -1; // 在最新日期之后直接返回
if (oldest >= date)
return model.count - 1; // 在最先日期之前全部返回
var currDiff = 0;
var bestDiff = Math.abs(date.getTime() - newest.getTime());
var retval = 0; // 返回變量
for (var i = 0; i < model.count; i++) {
var d = new Date(model.get(i).date);
currDiff = Math.abs(d.getTime() - date.getTime()); // 計算時間差值
if (currDiff < bestDiff) { // 從最新時間向目標時間推進
bestDiff = currDiff;
retval = i; // retval記錄數據位置
}
if (currDiff > bestDiff) // 當達到目標時間后
return retval; // 將數據位置返回
}
return -1;
}
function requestUrl() { // 創建請求數據的url字符串函數
if (stockId === "")
return;
var startDate = new Date(2011, 4, 25); // 指定一個開始時間
var endDate = new Date(); // 結束時間為當前時間
if (stockDataCycle !== "d" && stockDataCycle !== "w" && stockDataCycle !== "m")
stockDataCycle = "d"; // 如果數據周期不是'天'、'周'、'月',則定義為'天'
/* // 注釋給出了向yahoo請求數據的格式
Fetch stock data from yahoo finance:
url: http://ichart.finance.yahoo.com/table.csv?s=NOK&a=5&b=11&c=2010&d=7&e=23&f=2010&g=d&ignore=.csv
s:stock name/id, a:start day, b:start month, c:start year default: 25 April 1995, oldest c= 1962
d:end day, e:end month, f:end year, default:today (data only available 3 days before today)
g:data cycle(d daily, w weekly, m monthly, v Dividend)
*/
var request = "http://ichart.finance.yahoo.com/table.csv?";
request += "s=" + stockId;
request += "&a=" + startDate.getMonth();
request += "&b=" + startDate.getDate();
request += "&c=" + startDate.getFullYear();
request += "&d=" + endDate.getMonth();
request += "&e=" + endDate.getDate();
request += "&f=" + endDate.getFullYear();
request += "&g=" + stockDataCycle;
request += "&ignore=.csv";
return request; // 返回這一長串url
}
function createStockPrice(r) { // 存儲數據對象函數
return { // 用來接收下面分離的7位數據,以類似結構體的形式存儲下來
"date": r[0], // 這也是該model真正存儲的數據類型格式
"open":r[1],
"high":r[2],
"low":r[3],
"close":r[4],
"volume":r[5],
"adjusted":r[6]
};
}
function updateStock() { // 數據更新
var req = requestUrl(); // 得到請求數據的url字符串
if (!req)
return;
var xhr = new XMLHttpRequest; // 創建一個XMLHttp的請求對象
xhr.open("GET", req, true); // 初始化請求參數,還未發送請求
model.ready = false; // 標志位置false
model.clear(); // 數據清空
var i = 1; // 輸出一下調試信息可知,返回的數據第一行為描述符,因此將其跳過
xhr.onreadystatechange = function() { // readyState是XMLHttpRequest的一個屬性,其值從0變化到4
if (xhr.readyState === XMLHttpRequest.LOADING || xhr.readyState === XMLHttpRequest.DONE) {
var records = xhr.responseText.split('\n'); // LOADING為3,DONE為4,分別表示數據正在載入和載入完成
// 以換行符分割數據
for (;i < records.length; i++ ) {
var r = records[i].split(','); // 以逗號將數據分割
if (r.length === 7) // 數據校驗
model.append(createStockPrice(r)); // 函數調用,向model中添加數據
}
if (xhr.readyState === XMLHttpRequest.DONE) {
if (model.count > 0) {
model.ready = true;
model.stockPrice = model.get(0).adjusted; // 將最新的的調整收盤價賦予stockPrice
model.stockPriceChanged = model.count > 1 ? (Math.round((model.stockPrice - model.get(1).close) * 100) / 100) : 0; // 相比前一天的收盤價變化率
model.dataReady(); //emit signal
}
}
}
}
xhr.send() // 實際發出數據請求
}
}
~~~
我們將records的數據部分貼出來:

實際的數據被'\n'分開,也就是說,類似下面這個樣子:
Data,Open,High,Low,Close,Volume,Adj Close,
2014-7-28,97.82,99.24,97.55,99.02,55239000,99.02,
2014-7-25 ......(7-26,7-27?周末休市啦......)
為了得到各股的變化率等,StockListView也采取了類似的實現方式:
StockListView.qml:
~~~
import QtQuick 2.0
Rectangle {
id: root
width: 320
height: 410
anchors.top: parent.top
anchors.bottom: parent.bottom
color: "white"
property string currentStockId: ""
property string currentStockName: ""
ListView {
id: view
anchors.fill: parent
width: parent.width
clip: true // clip以延時加載數據
keyNavigationWraps: true
highlightMoveDuration: 0
focus: true
snapMode: ListView.SnapToItem
model: StockListModel{} // 定義model
function requestUrl(stockId) { // 最近5天的url創建函數,與StockModel不同的是,由于未定義stockId屬性,它帶有這樣一個參數
var endDate = new Date(""); // today
var startDate = new Date()
startDate.setDate(startDate.getDate() - 5);
var request = "http://ichart.finance.yahoo.com/table.csv?";
request += "s=" + stockId;
request += "&g=d";
request += "&a=" + startDate.getMonth();
request += "&b=" + startDate.getDate();
request += "&c=" + startDate.getFullYear();
request += "&d=" + endDate.getMonth();
request += "&e=" + endDate.getDate();
request += "&f=" + endDate.getFullYear();
request += "&g=d";
request += "&ignore=.csv";
return request;
}
function getCloseValue(index) {
var req = requestUrl(model.get(index).stockId); // 得到對應的股票Id
if (!req)
return;
var xhr = new XMLHttpRequest;
xhr.open("GET", req, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.LOADING || xhr.readyState === XMLHttpRequest.DONE) {
var records = xhr.responseText.split('\n');
if (records.length > 0) {
var r = records[1].split(','); // 第一條數據,即最新一天的數據
model.setProperty(index, "value", r[4]); // 與StockModel類似,第五個數據為'Close',即收盤價
// 這里將model中index位置數據的"value"值設置為該收盤價 // 注意這個model是StockListModel而不是StockModel
var today = parseFloat(r[4]); // parseFloat()將字符串轉化成浮點數,變量聲明為var而不是real
r = records[2].split(','); // 再取前一天數據
var yesterday = parseFloat(r[4]);
var change = today - yesterday; // 計算變化值
if (change >= 0.0)
model.setProperty(index, "change", "+" + change.toFixed(2)); // 同樣對model賦值大于零則帶+號,保留兩位小數
else
model.setProperty(index, "change", change.toFixed(2));
var changePercentage = (change / yesterday) * 100.0; // 變化率百分比
if (changePercentage >= 0.0)
model.setProperty(index, "changePercentage", "+" + changePercentage.toFixed(2) + "%");
else
model.setProperty(index, "changePercentage", changePercentage.toFixed(2) + "%");
}
}
}
xhr.send() // 發送請求
}
onCurrentIndexChanged: { // 當該ListView中的某個項目被選中
mainRect.listViewActive = 0; // 切換主ListView的頁面
root.currentStockId = model.get(currentIndex).stockId; // 獲取 Id 與 name
root.currentStockName = model.get(currentIndex).name;
}
delegate: Rectangle { // 委托組件,基本都是布局,不多說了
height: 102
width: parent.width
color: "transparent"
MouseArea {
anchors.fill: parent;
onClicked: {
view.currentIndex = index;
}
}
Text {
id: stockIdText
anchors.top: parent.top
anchors.topMargin: 15
anchors.left: parent.left
anchors.leftMargin: 15
width: 125
height: 40
color: "#000000"
font.family: "Open Sans" // 我的機器貌似不支持這種字體
font.pointSize: 20
font.weight: Font.Bold
verticalAlignment: Text.AlignVCenter
text: stockId
}
Text {
id: stockValueText
anchors.top: parent.top
anchors.topMargin: 15
anchors.right: parent.right
anchors.rightMargin: 0.31 * parent.width
width: 190
height: 40
color: "#000000"
font.family: "Open Sans"
font.pointSize: 20
font.bold: true
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
text: value
Component.onCompleted: view.getCloseValue(index);
}
Text {
id: stockValueChangeText
anchors.top: parent.top
anchors.topMargin: 15
anchors.right: parent.right
anchors.rightMargin: 20
width: 135
height: 40
color: "#328930"
font.family: "Open Sans"
font.pointSize: 20
font.bold: true
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
text: change
onTextChanged: {
if (parseFloat(text) >= 0.0) // 正為綠色,負為紅色
color = "#328930";
else
color = "#d40000";
}
}
Text {
id: stockNameText
anchors.top: stockIdText.bottom
anchors.left: parent.left
anchors.leftMargin: 15
width: 330
height: 30
color: "#000000"
font.family: "Open Sans"
font.pointSize: 16
font.bold: false
elide: Text.ElideRight
maximumLineCount: 1
verticalAlignment: Text.AlignVCenter
text: name
}
Text {
id: stockValueChangePercentageText
anchors.top: stockIdText.bottom
anchors.right: parent.right
anchors.rightMargin: 20
width: 120
height: 30
color: "#328930"
font.family: "Open Sans"
font.pointSize: 18
font.bold: false
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
text: changePercentage
onTextChanged: {
if (parseFloat(text) >= 0.0)
color = "#328930";
else
color = "#d40000";
}
}
Rectangle {
id: endingLine
anchors.bottom: parent.bottom
anchors.left: parent.left
height: 1
width: parent.width
color: "#d7d7d7"
}
}
highlight: Rectangle {
width: parent.width
color: "#eeeeee"
}
}
}
~~~
為了支撐這個StockListView,我們還需要一個StockListModel.qml:
它同樣是一個ListModel,由許多個ListElement構成,代碼也很明了。除了name和stockId 被賦值外,value,change,changePercentage都是在view中被動態賦值的,因此均初始化為0.0。
~~~
import QtQuick 2.0
ListModel {
id: stocks
// Data from : http://en.wikipedia.org/wiki/NASDAQ-100 // 這里告訴了我們數據來源
ListElement {name: "Apple Inc."; stockId: "AAPL"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Adobe Systems Inc."; stockId: "ADBE"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Analog Devices, Inc."; stockId: "ADI"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Automatic Data Processing, Inc."; stockId: "ADP"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Autodesk, Inc."; stockId: "ADSK"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Akamai Technologies, Inc."; stockId: "AKAM"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Altera Corp."; stockId: "ALTR"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Alexion Pharmaceuticals, Inc."; stockId: "ALXN"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Applied Materials, Inc."; stockId: "AMAT"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Amgen Inc."; stockId: "AMGN"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Amazon.com Inc."; stockId: "AMZN"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Activision Blizzard, Inc."; stockId: "ATVI"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Avago Technologies Limited"; stockId: "AVGO"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Bed Bath & Beyond Inc."; stockId: "BBBY"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Baidu, Inc."; stockId: "BIDU"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Biogen Idec Inc."; stockId: "BIIB"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Broadcom Corp."; stockId: "BRCM"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "CA Technologies"; stockId: "CA"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Celgene Corporation"; stockId: "CELG"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Cerner Corporation"; stockId: "CERN"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Check Point Software Technologies Ltd."; stockId: "CHKP"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "CH Robinson Worldwide Inc."; stockId: "CHRW"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Charter Communications, Inc."; stockId: "CHTR"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Comcast Corporation"; stockId: "CMCSA"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Costco Wholesale Corporation"; stockId: "COST"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Cisco Systems, Inc."; stockId: "CSCO"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Catamaran Corporation"; stockId: "CTRX"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Cognizant Technology Solutions Corporation"; stockId: "CTSH"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Citrix Systems, Inc."; stockId: "CTXS"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Discovery Communications, Inc."; stockId: "DISCA"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Dish Network Corp."; stockId: "DISH"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Dollar Tree, Inc."; stockId: "DLTR"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "DIRECTV"; stockId: "DTV"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "eBay Inc."; stockId: "EBAY"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Equinix, Inc."; stockId: "EQIX"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Express Scripts Holding Company"; stockId: "ESRX"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Expeditors International of Washington Inc."; stockId: "EXPD"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Expedia Inc."; stockId: "EXPE"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Fastenal Company"; stockId: "FAST"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Facebook, Inc."; stockId: "FB"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "F5 Networks, Inc."; stockId: "FFIV"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Fiserv, Inc."; stockId: "FISV"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Twenty-First Century Fox, Inc."; stockId: "FOXA"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Gilead Sciences Inc."; stockId: "GILD"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Keurig Green Mountain, Inc."; stockId: "GMCR"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Google Inc."; stockId: "GOOG"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Google Inc."; stockId: "GOOGL"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Garmin Ltd."; stockId: "GRMN"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Henry Schein, Inc."; stockId: "HSIC"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Illumina Inc."; stockId: "ILMN"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Intel Corporation"; stockId: "INTC"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Intuit Inc."; stockId: "INTU"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Intuitive Surgical, Inc."; stockId: "ISRG"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "KLA-Tencor Corporation"; stockId: "KLAC"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Kraft Foods Group, Inc."; stockId: "KRFT"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Liberty Global plc"; stockId: "LBTYA"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Liberty Interactive Corporation"; stockId: "LINTA"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Linear Technology Corporation"; stockId: "LLTC"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Liberty Media Corporation"; stockId: "LMCA"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Marriott International, Inc."; stockId: "MAR"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Mattel, Inc"; stockId: "MAT"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Mondelez International, Inc."; stockId: "MDLZ"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Monster Beverage Corporation"; stockId: "MNST"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Microsoft Corporation"; stockId: "MSFT"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Micron Technology Inc."; stockId: "MU"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Maxim Integrated Products, Inc."; stockId: "MXIM"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Mylan, Inc."; stockId: "MYL"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Netflix, Inc."; stockId: "NFLX"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "NetApp, Inc."; stockId: "NTAP"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "NVIDIA Corporation"; stockId: "NVDA"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "NXP Semiconductors NV"; stockId: "NXPI"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "O'Reilly Automotive Inc."; stockId: "ORLY"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Paychex, Inc."; stockId: "PAYX"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "PACCAR Inc."; stockId: "PCAR"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "The Priceline Group Inc."; stockId: "PCLN"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "QUALCOMM Incorporated"; stockId: "QCOM"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Regeneron Pharmaceuticals, Inc."; stockId: "REGN"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Ross Stores Inc."; stockId: "ROST"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "SBA Communications Corp."; stockId: "SBAC"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Starbucks Corporation"; stockId: "SBUX"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Sigma-Aldrich Corporation"; stockId: "SIAL"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Sirius XM Holdings Inc."; stockId: "SIRI"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "SanDisk Corp."; stockId: "SNDK"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Staples, Inc."; stockId: "SPLS"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Stericycle, Inc."; stockId: "SRCL"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Seagate Technology Public Limited Company"; stockId: "STX"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Symantec Corporation"; stockId: "SYMC"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "TripAdvisor Inc."; stockId: "TRIP"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Tractor Supply Company"; stockId: "TSCO"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Tesla Motors, Inc."; stockId: "TSLA"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Texas Instruments Inc."; stockId: "TXN"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Viacom, Inc."; stockId: "VIAB"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "VimpelCom Ltd."; stockId: "VIP"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Vodafone Group Public Limited Company"; stockId: "VOD"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Verisk Analytics, Inc."; stockId: "VRSK"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Vertex Pharmaceuticals Incorporated"; stockId: "VRTX"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Western Digital Corporation"; stockId: "WDC"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Whole Foods Market, Inc."; stockId: "WFM"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Wynn Resorts Ltd."; stockId: "WYNN"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
ListElement {name: "Xilinx Inc."; stockId: "XLNX"; value: "0.0"; change: "0.0"; changePercentage: "0.0"}
}
~~~
骨架已成,我們接下來就要為它添加血肉了。我在網上看到有人認為底層先于界面開發更快,也有人認為先搭界面框架再做底層更好。嗯...我倒覺得談不上好壞之分,關鍵在于動手之前得有一個清晰的架構。不過底層在設計之初能夠擁有豐富的API 減少日后改動,界面最初設計不要要求太高(因為總是會改的。。。),這樣應該會讓人比較舒服一些~
好了,扯遠了,下面的StockView即是我們一開始看到的曲線界面,看看它包含哪些東西:
~~~
import QtQuick 2.0
import QtQuick.Window 2.1 // 下面的代碼使用了Screen因此引入這個模塊
Rectangle {
id: root
width: 320
height: 480
color: "transparent"
property var stock: null
property var stocklist: null
signal settingsClicked
function update() { // 用來更新圖表顯示
chart.update()
}
Rectangle {
id: mainRect
color: "transparent"
anchors.fill: parent
StockInfo { // 提供左上方的股票信息
id: stockInfo
anchors.left: parent.left
anchors.leftMargin: 10
anchors.top: parent.top
anchors.topMargin: 15
height: 160
anchors.right: Screen.primaryOrientation === Qt.PortraitOrientation ? parent.right : chart.left // 基于屬性綁定的屏幕轉向后布局方式的變化
anchors.rightMargin: 20
stock: root.stock
}
StockChart { // 右方的曲線繪制部分
id: chart
anchors.bottom: Screen.primaryOrientation === Qt.PortraitOrientation ? settingsPanel.top : parent.bottom
anchors.bottomMargin: 20
anchors.top : Screen.primaryOrientation === Qt.PortraitOrientation ? stockInfo.bottom : parent.top
anchors.topMargin: 20
anchors.right: parent.right
anchors.rightMargin: 20
width: Screen.primaryOrientation === Qt.PortraitOrientation ? parent.width - 40 : 0.6 * parent.width
stockModel: root.stock
settings: settingsPanel
}
StockSettingsPanel { // 左下方的顯示設置面板
id: settingsPanel
anchors.left: parent.left
anchors.leftMargin: 20
anchors.right: Screen.primaryOrientation === Qt.PortraitOrientation ? parent.right : chart.left
anchors.rightMargin: 20
anchors.bottom: parent.bottom
onDrawOpenPriceChanged: root.update() // 更新
onDrawClosePriceChanged: root.update();
onDrawHighPriceChanged: root.update();
onDrawLowPriceChanged: root.update();
}
}
}
~~~
可以看到StockView由3個主要部分構成,分別是顯示當前股票信息的StockInfo、設置顯示曲線的StockSettingPanel、以及最后的繪圖部分StockChart。
我們按順序來看,StockInfo.qml:
~~~
import QtQuick 2.0
Rectangle { // 根項目是一個透明的Rectangle。為什么不用Item,我想可能是因為當時布局的時候把color設置出來可能更方便一些
id: root
width: 440
height: 160
color: "transparent"
property var stock: null // var類型的stock屬性,它接受的是stocqt.qml中定義的StockModel
Text { // id
id: stockIdText
anchors.left: parent.left
anchors.leftMargin: 5
anchors.top: parent.top
anchors.topMargin: 15
color: "#000000"
font.family: "Open Sans"
font.pointSize: 38
font.weight: Font.DemiBold
text: root.stock.stockId // 類似的,對顯示文本賦值
}
Text { // name
id: stockNameText
anchors.left: parent.left
anchors.leftMargin: 5
anchors.bottom: priceChangePercentage.bottom
anchors.right: priceChangePercentage.left
anchors.rightMargin: 15
color: "#000000"
font.family: "Open Sans"
font.pointSize: 16
elide: Text.ElideRight
text: root.stock.stockName
}
Text { // 價格
id: price
anchors.right: parent.right
anchors.rightMargin: 5
anchors.top: parent.top
anchors.topMargin: 15
horizontalAlignment: Text.AlignRight
color: "#000000"
font.family: "Open Sans"
font.pointSize: 30
font.weight: Font.DemiBold
text: root.stock.stockPrice
}
Text { // 價格變化
id: priceChange
anchors.right: parent.right
anchors.rightMargin: 20
anchors.top: price.bottom
anchors.topMargin: 5
horizontalAlignment: Text.AlignRight
color: root.stock.stockPriceChanged < 0 ? "#d40000" : "#328930"
font.family: "Open Sans"
font.pointSize: 20
font.weight: Font.Bold
text: root.stock.stockPriceChanged
}
Text { // 價格變化百分比
id: priceChangePercentage
anchors.right: parent.right
anchors.rightMargin: 20
anchors.top: priceChange.bottom
anchors.topMargin: 5
horizontalAlignment: Text.AlignRight
color: root.stock.stockPriceChanged < 0 ? "#d40000" : "#328930"
font.family: "Open Sans"
font.pointSize: 18
font.weight: Font.Bold
text: Math.abs(Math.round(root.stock.stockPriceChanged/(root.stock.stockPrice - root.stock.stockPriceChanged) * 100))/100 +"%"
}
}
~~~
stockSettingPanel.qml:
~~~
import QtQuick 2.0
Rectangle {
id: root
width: 440
height: 160
color: "transparent"
property bool drawOpenPrice: openButton.buttonEnabled // 對外的標志位
property bool drawClosePrice: closeButton.buttonEnabled
property bool drawHighPrice: highButton.buttonEnabled
property bool drawLowPrice: lowButton.buttonEnabled
property string openColor: "#face20" // 各曲線的顏色設置
property string closeColor: "#14aaff"
property string highColor: "#80c342"
property string lowColor: "#f30000"
property string volumeColor: "#14aaff" // 成交量繪制顏色
Text {
id: openText
anchors.left: root.left
anchors.top: root.top
color: "#000000"
font.family: "Open Sans"
font.pointSize: 19
text: "Open"
}
Text {
id: closeText
anchors.left: root.left
anchors.top: openText.bottom
anchors.topMargin: 10
color: "#000000"
font.family: "Open Sans"
font.pointSize: 19
text: "Close"
}
Text {
id: highText
anchors.left: root.left
anchors.top: closeText.bottom
anchors.topMargin: 10
color: "#000000"
font.family: "Open Sans"
font.pointSize: 19
text: "High"
}
Text {
id: lowText
anchors.left: root.left
anchors.top: highText.bottom
anchors.topMargin: 10
color: "#000000"
font.family: "Open Sans"
font.pointSize: 19
text: "Low"
}
Rectangle {
height: 4
anchors.left: root.left
anchors.leftMargin: 114
anchors.right: openButton.left
anchors.rightMargin: 65
anchors.verticalCenter: openText.verticalCenter
color: openColor
}
Rectangle {
height: 4
anchors.left: root.left
anchors.leftMargin: 114
anchors.right: closeButton.left
anchors.rightMargin: 65
anchors.verticalCenter: closeText.verticalCenter
color: closeColor
}
Rectangle {
height: 4
anchors.left: root.left
anchors.leftMargin: 114
anchors.right: highButton.left
anchors.rightMargin: 65
anchors.verticalCenter: highText.verticalCenter
color: highColor
}
Rectangle {
height: 4
anchors.left: root.left
anchors.leftMargin: 114
anchors.right: lowButton.left
anchors.rightMargin: 65
anchors.verticalCenter: lowText.verticalCenter
color: lowColor
}
CheckBox { // 自定義的CheckBox,提供了屬性buttonEnabled表明是否被checked
id: openButton
buttonEnabled: false
anchors.verticalCenter: openText.verticalCenter
anchors.right: root.right
anchors.rightMargin: 40
}
CheckBox {
id: closeButton
buttonEnabled: false
anchors.verticalCenter: closeText.verticalCenter
anchors.right: root.right
anchors.rightMargin: 40
}
CheckBox {
id: highButton
buttonEnabled: true
anchors.verticalCenter: highText.verticalCenter
anchors.right: root.right
anchors.rightMargin: 40
}
CheckBox {
id: lowButton
buttonEnabled: true
anchors.verticalCenter: lowText.verticalCenter
anchors.right: root.right
anchors.rightMargin: 40
}
}
~~~
自定義的CheckBox.qml:
~~~
import QtQuick 2.0
Item {
id: button
property bool buttonEnabled: true
width: 30
height: 30
x: 5
MouseArea {
id: mouse
anchors.fill: parent
onClicked: {
if (buttonEnabled)
buttonEnabled = false;
else
buttonEnabled = true;
}
}
Rectangle {
id: checkbox
width: 30
height: 30
anchors.left: parent.left
border.color: "#999999"
border.width: 1
antialiasing: true
radius: 2
color: "transparent"
Rectangle {
anchors.fill: parent
anchors.margins: 5
antialiasing: true
radius: 1
color: mouse.pressed || buttonEnabled ? "#999999" : "transparent"
}
}
}
~~~
接著還有一個自定義的控件Button.qml,它在下面的StockChart將會用到:
~~~
import QtQuick 2.0
Rectangle {
id: button
signal clicked
property alias text: txt.text // 設置txt.text的屬性別名為text,這樣Rectangle就不用再聲明一個屬性以訪問txt的text
property bool buttonEnabled: false
width: Math.max(64, txt.width + 16)
height: 32
color: "transparent"
MouseArea {
anchors.fill: parent
onClicked: button.clicked()
}
Text {
anchors.centerIn: parent
font.family: "Open Sans"
font.pointSize: 19
font.weight: Font.DemiBold
color: button.buttonEnabled ? "#000000" : "#14aaff"
id: txt
}
}
~~~
最終的繪制部分終于來了,StockChart.qml:
~~~
import QtQuick 2.0
Rectangle {
id: chart
width: 320
height: 200
property var stockModel: null
property var startDate: new Date() // new Date()初始化得到的是當前時間
property var endDate: new Date()
property string activeChart: "year" // 設置表格顯示的時間跨度為一年
property var settings
property int gridSize: 4 // 每個網格寬度為4px
property real gridStep: gridSize ? (width - canvas.tickMargin) / gridSize : canvas.xGridStep // 網格數為(寬度 - 最右方那一欄的寬度)/ 網格寬度,如果gridSize為0,采用畫布中的計算方式
function update() { // 更新函數
endDate = new Date();
if (chart.activeChart === "year") { // 顯示一年數據
chart.startDate = new Date(chart.endDate.getFullYear() - 1, // 在當前時間的基礎上減一年
chart.endDate.getMonth(),
chart.endDate.getDate());
chart.gridSize = 12; // 設置網格寬度為12,以固定網格數
}
else if (chart.activeChart === "month") { // 顯示一個月數據
chart.startDate = new Date(chart.endDate.getFullYear(), // 在當前時間基礎上減一個月
chart.endDate.getMonth() - 1,
chart.endDate.getDate());
gridSize = 0; // gridSize為0時,采用canvas中定義的網格寬度計算。使每個數據都繪制在坐標線上
}
else if (chart.activeChart === "week") { // 顯示一周數據
chart.startDate = new Date(chart.endDate.getFullYear(), // 在當前時間基礎上減七天
chart.endDate.getMonth(),
chart.endDate.getDate() - 7);
gridSize = 0;
}
else {
chart.startDate = new Date(2005, 3, 25); // 否則以2005年為初始年,并定義網格寬度為4
gridSize = 4;
}
canvas.requestPaint(); // 當更新時需要調用畫布的這個函數
}
Row { // Row布局了4個自定義的按鈕
id: activeChartRow
anchors.left: chart.left
anchors.right: chart.right
anchors.top: chart.top
anchors.topMargin: 4
spacing: 52
onWidthChanged: { // 該函數保證寬度變化時優先壓縮spacing,且不會造成按鈕重疊
var buttonsLen = maxButton.width + yearButton.width + monthButton.width + weekButton.width;
var space = (width - buttonsLen) / 3;
spacing = Math.max(space, 10);
}
Button {
id: maxButton
text: "Max"
buttonEnabled: chart.activeChart === "max"
onClicked: {
chart.activeChart = "max"; // 改變當前圖表顯示模式,這里的max 實際對應update中的'else',即2005年作為起始年
chart.update(); // 更新
}
}
Button {
id: yearButton
text: "Year"
buttonEnabled: chart.activeChart === "year"
onClicked: {
chart.activeChart = "year";
chart.update();
}
}
Button {
id: monthButton
text: "Month"
buttonEnabled: chart.activeChart === "month"
onClicked: {
chart.activeChart = "month";
chart.update();
}
}
Button {
id: weekButton
text: "Week"
buttonEnabled: chart.activeChart === "week"
onClicked: {
chart.activeChart = "week";
chart.update();
}
}
}
Text { // 下方的起始日期顯示
id: fromDate
color: "#000000"
font.family: "Open Sans"
font.pointSize: 8
anchors.left: parent.left
anchors.bottom: parent.bottom
text: "| " + startDate.toDateString()
}
Text { // 結束日期顯示
id: toDate
color: "#000000"
font.family: "Open Sans"
font.pointSize: 8
anchors.right: parent.right
anchors.rightMargin: canvas.tickMargin
anchors.bottom: parent.bottom
text: endDate.toDateString() + " |"
}
Canvas { // 畫布,基本上與HTML的Canvas相同
id: canvas
// 注釋介紹將下面兩行語句取消注釋以獲得OpenGL的硬件加速渲染,為什么沒有開?有些平臺不支持嘛...
// Uncomment below lines to use OpenGL hardware accelerated rendering.
// See Canvas documentation for available options.
//renderTarget: Canvas.FramebufferObject // 渲染到OpenGL的幀緩沖
//renderStrategy: Canvas.Threaded // 渲染工作在一個私有渲染線程中進行
anchors.top: activeChartRow.bottom // 作為Item的派生類型,我們同樣可以設置它的布局與屬性
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: fromDate.top
property int pixelSkip: 1
property int numPoints: 1 // 存儲數據點的個數
property int tickMargin: 32 // 右邊格的寬度
property real xGridStep: (width - tickMargin) / numPoints // 網格寬度
property real yGridOffset: height / 26 // y方向向下的偏移度,用在水平線的繪制
property real yGridStep: height / 12 // 網格高度
function drawBackground(ctx) { // 界面的繪制由onPaint開始,這是繪制背景的一個函數。ctx作為傳參,類似C++中的painter
ctx.save(); // 保存之前繪制內容
ctx.fillStyle = "#ffffff"; // 填充顏色,之所以叫Style是因為它還可以使用漸變等等...
ctx.fillRect(0, 0, canvas.width, canvas.height); // fillRect是一個便利函數,用來填充一個矩形區域
ctx.strokeStyle = "#d7d7d7"; // 描邊顏色
ctx.beginPath();
// 水平網格線
for (var i = 0; i < 12; i++) {
ctx.moveTo(0, canvas.yGridOffset + i * canvas.yGridStep);
ctx.lineTo(canvas.width, canvas.yGridOffset + i * canvas.yGridStep);
}
// 垂直網格線
var height = 35 * canvas.height / 36; // 垂直線的高度為畫布高度的 35/36
var yOffset = canvas.height - height; // 垂直線離頂部距離為高度的 1/36
var xOffset = 0;
for (i = 0; i < chart.gridSize; i++) {
ctx.moveTo(xOffset + i * chart.gridStep, yOffset);
ctx.lineTo(xOffset + i * chart.gridStep, height);
}
ctx.stroke(); // 描線
// 右方以及下方顏色較深的那幾根線
ctx.strokeStyle = "#666666";
ctx.beginPath();
var xStart = canvas.width - tickMargin; // x = 畫布寬度 - 價格部分寬度
ctx.moveTo(xStart, 0);
ctx.lineTo(xStart, canvas.height); // 向下畫直線
for (i = 0; i < 12; i++) {
ctx.moveTo(xStart, canvas.yGridOffset + i * canvas.yGridStep); // 12根短橫線
ctx.lineTo(canvas.width, canvas.yGridOffset + i * canvas.yGridStep);
}
ctx.moveTo(0, canvas.yGridOffset + 9 * canvas.yGridStep); // 移動繪制點到第九根橫線左端
ctx.lineTo(canvas.width, canvas.yGridOffset + 9 * canvas.yGridStep); // 向右繪制橫線
ctx.closePath(); // 完成路徑
ctx.stroke(); // 描邊
ctx.restore(); // 載入保存的內容
}
function drawScales(ctx, high, low, vol) // 繪制右方股票價格標尺函數
{
ctx.save();
ctx.strokeStyle = "#888888";
ctx.font = "10px Open Sans"
ctx.beginPath();
// prices on y-axis
var x = canvas.width - tickMargin + 3; // 離右邊實線3px
var priceStep = (high - low) / 9.0; // 相隔最高價與最低價的差值除以9
for (var i = 0; i < 10; i += 2) { // 隔一級顯示
var price = parseFloat(high - i * priceStep).toFixed(1);
ctx.text(price, x, canvas.yGridOffset + i * yGridStep - 2); // 繪制text的坐標在文字的左下角
}
// highest volume
ctx.text(vol, 0, canvas.yGridOffset + 9 * yGridStep + 12); // 繪制最高成交量
ctx.closePath();
ctx.stroke();
ctx.restore();
}
function drawPrice(ctx, from, to, color, price, points, highest, lowest) // 數據曲線繪制
{
ctx.save();
ctx.globalAlpha = 0.7; // 透明度
ctx.strokeStyle = color; // color由StockSettingPanel指定
ctx.lineWidth = 3;
ctx.beginPath();
var end = points.length; // 數據長度
var range = highest - lowest; // 取值范圍
if (range == 0) {
range = 1; // range作為被除數不能為0
}
for (var i = 0; i < end; i += pixelSkip) {
var x = points[i].x;
var y = points[i][price]; // 取出對應設置的價格數據
var h = 9 * yGridStep; // 設置繪制高度為九倍的網格高度
y = h * (lowest - y)/range + h + yGridOffset; // lowest - y為非正數,h + yGridOffset為曲線繪制的底部
if (i == 0) {
ctx.moveTo(x, y); // 移動到初始點
} else {
ctx.lineTo(x, y); // 向后繪制
}
}
ctx.stroke();
ctx.restore();
}
function drawVolume(ctx, from, to, color, price, points, highest) // 成交量繪制函數
{
ctx.save();
ctx.fillStyle = color;
ctx.globalAlpha = 0.8;
ctx.lineWidth = 0; // 由于線寬影響繪制邊界(參考HTML),這里將線寬設置為0
ctx.beginPath();
var end = points.length;
var margin = 0;
if (chart.activeChart === "month" || chart.activeChart === "week") {
margin = 8;
ctx.shadowOffsetX = 4; // x方向的陰影
ctx.shadowBlur = 3.5; // 模糊效果
ctx.shadowColor = Qt.darker(color);
}
// 由于柱狀圖的寬度限制,柱狀圖比實際的數據少一個
// To match the volume graph with price grid, skip drawing the initial
// volume of the first day on chart.
for (var i = 1; i < end; i += pixelSkip) {
var x = points[i - 1].x;
var y = points[i][price];
y = canvas.height * (y / highest);
y = 3 * y / 12; // 柱狀圖高度占畫布的1/4
ctx.fillRect(x, canvas.height - y + yGridOffset,
canvas.xGridStep - margin, y); // "周"與"月"時有間隔,其他則沒有
}
ctx.stroke();
ctx.restore();
}
onPaint: { // 繪制入口
if (!stockModel.ready) { // 等待數據完成
return;
}
numPoints = stockModel.indexOf(chart.startDate); // 由StockModel取得startDate到現在的數據數
if (chart.gridSize == 0)
chart.gridSize = numPoints // 使gridStep綁定到(width - canvas.tickMargin) / numPoints上
var ctx = canvas.getContext("2d"); // 創建ctx
ctx.globalCompositeOperation = "source-over"; // 混合模式
ctx.lineWidth = 1;
drawBackground(ctx); // 背景繪制
var highestPrice = 0;
var highestVolume = 0;
var lowestPrice = -1;
var points = []; // 創建一個數組
for (var i = numPoints, j = 0; i >= 0 ; i -= pixelSkip, j += pixelSkip) { // pixelSkip被定義為 1
var price = stockModel.get(i);
if (parseFloat(highestPrice) < parseFloat(price.high)) // 得到最高價
highestPrice = price.high;
if (parseInt(highestVolume, 10) < parseInt(price.volume, 10)) // 得到最低價
highestVolume = price.volume;
if (lowestPrice < 0 || parseFloat(lowestPrice) > parseFloat(price.low)) // 注意這里如果設置lowestPrice = 0或是別的數
lowestPrice = price.low; // 就有可能一直無法滿足條件,因此添加這個負數使第一個price.low被賦予lowestPrice
points.push({ // 插入數據,它類似于Model,但多了一個 x 的坐標值
x: j * xGridStep,
open: price.open,
close: price.close,
high: price.high,
low: price.low,
volume: price.volume
});
}
if (settings.drawHighPrice) // 判斷StockSettingPanel中相應的選項是否被勾選,然后繪制數據線段
drawPrice(ctx, 0, numPoints, settings.highColor, "high", points, highestPrice, lowestPrice);
if (settings.drawLowPrice)
drawPrice(ctx, 0, numPoints, settings.lowColor, "low", points, highestPrice, lowestPrice);
if (settings.drawOpenPrice)
drawPrice(ctx, 0, numPoints,settings.openColor, "open", points, highestPrice, lowestPrice);
if (settings.drawClosePrice)
drawPrice(ctx, 0, numPoints, settings.closeColor, "close", points, highestPrice, lowestPrice);
drawVolume(ctx, 0, numPoints, settings.volumeColor, "volume", points, highestVolume); // 成交量繪制
drawScales(ctx, highestPrice, lowestPrice, highestVolume); // 價格標尺繪制
}
}
}
~~~
- 前言
- 1——Fortune Server/Client
- 2——Multicast Sender/Receiverz
- 3——Broadcast Sender/Receiver
- 4——Blocking Fortune Client
- 5——Threaded Fortune Server
- 5(總結)——Fortune例程的各個實現區別
- 6——Loopback Example
- 7——Analog Clock Example
- 8——Shaped Clock Example
- 9——Analog Clock Window Example
- 10——Qt Quick Particles Examples - Emitters
- 11——Qt Quick Particles Examples - Affectors
- 12——Qt Quick Particles Examples - CustomParticles
- 13——Qt Quick Particles Examples - Image Particles
- 14——Qt Quick Particles Examples - System
- 15——Chapter 1: Creating a New Type
- 16——Chapter 2: Connecting to C++ Methods and Signals
- 17——Chapter 3: Adding Property Bindings
- 18——Chapter 4: Using Custom Property Types
- 19——Chapter 5: Using List Property Types
- 20——Chapter 6: Writing an Extension Plugin
- 21——Extending QML - Adding Types Example
- 22——Extending QML - Object and List Property Types Example
- 23——Extending QML - Inheritance and Coercion Example
- 24——Extending QML - Default Property Example
- 25——Extending QML - Methods Example
- 26——Extending QML - Grouped Properties Example
- 27——Extending QML - Attached Properties Example
- 28——Extending QML - Signal Support Example
- 29——Extending QML - Property Value Source Example
- 30——Extending QML - Binding Example
- 31——StocQt
- 32——Qt Quick Examples - Threading
- 33——Qt Quick Examples - Window and Screen
- 34——Concentric Circles Example
- 35——Music Player
- 36——Wiggly Example
- 37——Vector Deformation