diff options
Diffstat (limited to 'examples/quick/canvas/stockchart')
-rw-r--r-- | examples/quick/canvas/stockchart/README | 5 | ||||
-rw-r--r-- | examples/quick/canvas/stockchart/com/nokia/StockChartExample/qmldir | 1 | ||||
-rw-r--r-- | examples/quick/canvas/stockchart/model.cpp | 255 | ||||
-rw-r--r-- | examples/quick/canvas/stockchart/model.h | 166 | ||||
-rw-r--r-- | examples/quick/canvas/stockchart/plugin.cpp | 60 | ||||
-rw-r--r-- | examples/quick/canvas/stockchart/stock.qml | 726 | ||||
-rw-r--r-- | examples/quick/canvas/stockchart/stockchart.pro | 20 |
7 files changed, 1233 insertions, 0 deletions
diff --git a/examples/quick/canvas/stockchart/README b/examples/quick/canvas/stockchart/README new file mode 100644 index 0000000000..2652866ed6 --- /dev/null +++ b/examples/quick/canvas/stockchart/README @@ -0,0 +1,5 @@ +To run: + + make install + QML_IMPORT_PATH=$PWD qmlscene stock.qml + diff --git a/examples/quick/canvas/stockchart/com/nokia/StockChartExample/qmldir b/examples/quick/canvas/stockchart/com/nokia/StockChartExample/qmldir new file mode 100644 index 0000000000..4c60e556d4 --- /dev/null +++ b/examples/quick/canvas/stockchart/com/nokia/StockChartExample/qmldir @@ -0,0 +1 @@ +plugin qmlstockchartexampleplugin diff --git a/examples/quick/canvas/stockchart/model.cpp b/examples/quick/canvas/stockchart/model.cpp new file mode 100644 index 0000000000..42eb2d592f --- /dev/null +++ b/examples/quick/canvas/stockchart/model.cpp @@ -0,0 +1,255 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "model.h" + +#include <QtCore/QUrl> +#include <QtCore/QDate> +#include <QtCore/QList> +#include <QtCore/QStringList> +#include <QtCore/QDebug> + +#include <QtNetwork/QNetworkAccessManager> +#include <QtNetwork/QNetworkReply> + +StockModel::StockModel(QObject *parent) + : QAbstractListModel(parent) + , _startDate(QDate(1995, 4, 25)) + , _endDate(QDate::currentDate()) + , _dataCycle(StockModel::Daily) + , _manager(0) + , _updating(false) +{ + QHash<int, QByteArray> roles; + roles[StockModel::DateRole] = "date"; + roles[StockModel::SectionRole] = "year"; + roles[StockModel::OpenPriceRole] = "openPrice"; + roles[StockModel::ClosePriceRole] = "closePrice"; + roles[StockModel::HighPriceRole] = "highPrice"; + roles[StockModel::LowPriceRole] = "lowPrice"; + roles[StockModel::VolumeRole] = "volume"; + roles[StockModel::AdjustedPriceRole] = "adjustedPrice"; + setRoleNames(roles); + + connect(this, SIGNAL(stockNameChanged()), SLOT(requestData())); + connect(this, SIGNAL(startDateChanged()), SLOT(requestData())); + connect(this, SIGNAL(endDateChanged()), SLOT(requestData())); + connect(this, SIGNAL(dataCycleChanged()), SLOT(requestData())); + + _manager = new QNetworkAccessManager(this); + connect(_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(update(QNetworkReply*))); + +} + +int StockModel::rowCount(const QModelIndex & parent) const { + Q_UNUSED(parent); + return _prices.count(); +} + +StockPrice* StockModel::stockPriceAtIndex(int idx) const +{ + if (idx >=0 && idx < _prices.size()) { + return _prices[idx]; + } + return 0; +} + + +void StockModel::requestData() +{ + if (!_updating) { + _updating = true; + QMetaObject::invokeMethod(this, "doRequest", Qt::QueuedConnection); + } +} + +void StockModel::doRequest() +{ + /* + 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) + */ + if (_manager && !_stockName.isEmpty() && _endDate > _startDate) { + QString query("http://ichart.finance.yahoo.com/table.csv?s=%1&a=%2&b=%3&c=%4&d=%5&e=%6&f=%7&g=%8&ignore=.csv"); + query = query.arg(_stockName) + .arg(_startDate.day()).arg(_startDate.month()).arg(_startDate.year()) + .arg(_endDate.day()).arg(_endDate.month()).arg(_endDate.year()) + .arg(dataCycleString()); + + qDebug() << "request stock data:" << query; + QNetworkReply* reply = _manager->get(QNetworkRequest(QUrl(query))); + connect(reply, SIGNAL(downloadProgress(qint64,qint64)), SIGNAL(downloadProgress(qint64,qint64))); + } +} + +void StockModel::update(QNetworkReply *reply) +{ + _updating = false; + + if (reply) { + if (reply->error() == QNetworkReply::NoError) { + beginResetModel(); + + foreach (StockPrice* p, _prices) { + p->deleteLater(); + } + + _prices.clear(); + + while (!reply->atEnd()) { + QString line = reply->readLine(); + QStringList fields = line.split(','); + + //data format:Date,Open,High,Low,Close,Volume,Adjusted close price + //example: 2011-06-24,6.03,6.04,5.88,5.88,20465200,5.88 + if (fields.size() == 7) { + StockPrice* price = new StockPrice(this); + price->setDate(QDate::fromString(fields[0], Qt::ISODate)); + price->setOpenPrice(fields[1].toFloat()); + price->setHighPrice(fields[2].toFloat()); + price->setLowPrice(fields[3].toFloat()); + price->setClosePrice(fields[4].toFloat()); + price->setVolume(fields[5].toInt()); + price->setAdjustedPrice(fields[6].toFloat()); + _prices.prepend(price); + } + } + qDebug() << "get stock data successfully, total:" << _prices.count() << "records."; + } else { + qDebug() << "get stock data failed:" << reply->errorString(); + } + reply->deleteLater(); + endResetModel(); + emit dataChanged(QModelIndex(), QModelIndex()); + } +} + +QVariant StockModel::data(const QModelIndex & index, int role) const { + if (index.row() < 0 || index.row() > _prices.count()) + return QVariant(); + + const StockPrice* price = _prices[index.row()]; + if (role == StockModel::DateRole) + return price->date(); + else if (role == StockModel::OpenPriceRole) + return price->openPrice(); + else if (role == StockModel::ClosePriceRole) + return price->closePrice(); + else if (role == StockModel::HighPriceRole) + return price->highPrice(); + else if (role == StockModel::LowPriceRole) + return price->lowPrice(); + else if (role == StockModel::AdjustedPriceRole) + return price->adjustedPrice(); + else if (role == StockModel::VolumeRole) + return price->volume(); + else if (role == StockModel::SectionRole) + return price->date().year(); + return QVariant(); +} + +QString StockModel::stockName() const +{ + return _stockName; +} +void StockModel::setStockName(const QString& name) +{ + if (_stockName != name) { + _stockName = name; + emit stockNameChanged(); + } +} + +QDate StockModel::startDate() const +{ + return _startDate; +} +void StockModel::setStartDate(const QDate& date) +{ + if (_startDate.isValid() && _startDate != date) { + _startDate = date; + emit startDateChanged(); + } +} + +QDate StockModel::endDate() const +{ + return _endDate; +} +void StockModel::setEndDate(const QDate& date) +{ + if (_endDate.isValid() && _endDate != date) { + _endDate = date; + emit endDateChanged(); + } +} + +StockModel::StockDataCycle StockModel::dataCycle() const +{ + return _dataCycle; +} + +QString StockModel::dataCycleString() const +{ + switch (_dataCycle) { + case StockModel::Daily: + return QString('d'); + break; + case StockModel::Weekly: + return QString('w'); + case StockModel::Monthly: + return QString('m'); + case StockModel::Dividend: + return QString('v'); + } + + return QString('d'); +} + + +void StockModel::setDataCycle(StockModel::StockDataCycle cycle) +{ + if (_dataCycle != cycle) { + _dataCycle = cycle; + emit dataCycleChanged(); + } +} diff --git a/examples/quick/canvas/stockchart/model.h b/examples/quick/canvas/stockchart/model.h new file mode 100644 index 0000000000..95e6f4891c --- /dev/null +++ b/examples/quick/canvas/stockchart/model.h @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QtCore/QAbstractListModel> +#include <QtCore/QDate> + +class StockPrice : public QObject +{ + Q_OBJECT + Q_PROPERTY(QDate date READ date) + Q_PROPERTY(qreal openPrice READ openPrice) + Q_PROPERTY(qreal closePrice READ closePrice) + Q_PROPERTY(qreal highPrice READ highPrice) + Q_PROPERTY(qreal lowPrice READ lowPrice) + Q_PROPERTY(qint32 volume READ volume) + Q_PROPERTY(qreal adjustedPrice READ adjustedPrice) +public: + + StockPrice(QObject *parent = 0) + : QObject(parent) + , _openPrice(-1) + , _closePrice(-1) + , _highPrice(-1) + , _lowPrice(-1) + , _volume(-1) + , _adjustedPrice(-1) + { + } + QDate date() const {return _date;} + qreal openPrice() const {return _openPrice; } + qreal closePrice() const {return _closePrice;} + qreal highPrice() const {return _highPrice;} + qreal lowPrice() const{return _lowPrice;} + qreal adjustedPrice() const{return _adjustedPrice;} + qint32 volume() const{return _volume;} + + void setDate(const QDate& date){_date = date;} + void setOpenPrice(qreal price){_openPrice = price;} + void setClosePrice(qreal price){_closePrice = price;} + void setHighPrice(qreal price){_highPrice = price;} + void setLowPrice(qreal price){_lowPrice = price;} + void setAdjustedPrice(qreal price) {_adjustedPrice = price;} + void setVolume(qint32 volume) {_volume = volume;} + +private: + QDate _date; + qreal _openPrice; + qreal _closePrice; + qreal _highPrice; + qreal _lowPrice; + qint32 _volume; + qreal _adjustedPrice; +}; + +class QNetworkReply; +class QNetworkAccessManager; +class StockModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(QString stockName READ stockName WRITE setStockName NOTIFY stockNameChanged) + Q_PROPERTY(QDate startDate READ startDate WRITE setStartDate NOTIFY startDateChanged) + Q_PROPERTY(QDate endDate READ endDate WRITE setEndDate NOTIFY endDateChanged) + Q_PROPERTY(StockDataCycle dataCycle READ dataCycle WRITE setDataCycle NOTIFY dataCycleChanged) + + Q_ENUMS(StockDataCycle) +public: + enum StockDataCycle { + Daily, + Weekly, + Monthly, + Dividend + }; + + enum StockModelRoles { + DateRole = Qt::UserRole + 1, + SectionRole, + OpenPriceRole, + ClosePriceRole, + HighPriceRole, + LowPriceRole, + VolumeRole, + AdjustedPriceRole + }; + + StockModel(QObject *parent = 0); + + QString stockName() const; + void setStockName(const QString& name); + + QDate startDate() const; + void setStartDate(const QDate& date); + + QDate endDate() const; + void setEndDate(const QDate& date); + + StockDataCycle dataCycle() const; + void setDataCycle(StockDataCycle cycle); + + int rowCount(const QModelIndex & parent = QModelIndex()) const; + + QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; + +signals: + void stockNameChanged(); + void startDateChanged(); + void endDateChanged(); + void dataCycleChanged(); + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + +public slots: + void requestData(); + StockPrice* stockPriceAtIndex(int idx) const; +private slots: + void doRequest(); + void update(QNetworkReply* reply); +private: + QString dataCycleString() const; + QList<StockPrice*> _prices; + QString _stockName; + QDate _startDate; + QDate _endDate; + StockDataCycle _dataCycle; + QNetworkAccessManager* _manager; + bool _updating; +}; + + + + diff --git a/examples/quick/canvas/stockchart/plugin.cpp b/examples/quick/canvas/stockchart/plugin.cpp new file mode 100644 index 0000000000..3b354e2b31 --- /dev/null +++ b/examples/quick/canvas/stockchart/plugin.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtQml/QQmlExtensionPlugin> +#include <QtQml/qqml.h> +#include <QtGui/QGuiApplication> +#include "model.h" + +class QStockChartExampleQmlPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT +public: + void registerTypes(const char *uri) + { + Q_ASSERT(uri == QLatin1String("com.nokia.StockChartExample")); + qmlRegisterType<StockModel>(uri, 1, 0, "StockModel"); + qmlRegisterType<StockPrice>(uri, 1, 0, "StockPrice"); + } +}; + +#include "plugin.moc" + +Q_EXPORT_PLUGIN2(qmlstockchartexampleplugin, QStockChartExampleQmlPlugin); diff --git a/examples/quick/canvas/stockchart/stock.qml b/examples/quick/canvas/stockchart/stock.qml new file mode 100644 index 0000000000..1c95fde2ce --- /dev/null +++ b/examples/quick/canvas/stockchart/stock.qml @@ -0,0 +1,726 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +import QtQuick 2.0 +import com.nokia.StockChartExample 1.0 +import "../contents" + +Rectangle { + id:container + width: 360; height: 600 + color: "#343434"; + Image { source: "contents/images/stripes.png"; fillMode: Image.Tile; anchors.fill: parent; opacity: 1 } + + + TitleBar { + id: titleBar + width: parent.width + anchors.top : container.top + height: 40 + opacity: 0.9 + } + + StockModel { + id:stockModel + dataCycle: StockModel.Daily + function dataCycleName() { + if (dataCycle === StockModel.Weekly) + return "Weekly"; + else if (dataCycle === StockModel.Monthly) + return "Monthly"; + return "Daily"; + } + + onDataChanged: { + if (view.viewType == "chart") { + canvas.requestPaint(); + } + } + onDownloadProgress: { + if (bytesReceived == bytesTotal && bytesTotal != -1) { + progress.opacity = 0; + } else { + progress.opacity = 0.8; + progress.text = "downloading " + stockModel.dataCycleName() + " data ..."+ Math.round(bytesReceived/1000) + " KB"; + } + } + + property string description:""; + } + + Stocks {id:stocks} + + Rectangle { + id: header + width: parent.width + height: 20 + color: "steelblue" + opacity: 0 + Row { + spacing: 2 + Text { + id:t + font.pointSize:15 + horizontalAlignment:Text.AlignHCenter + font.bold: true + font.underline:true + } + Rectangle { + height:20 + width:50 + Text {text:"Stock list"; font.pointSize:15; font.bold: true} + } + } + } + + ListView { + id:stockList + width: parent.width + anchors.bottom: container.bottom + anchors.top : titleBar.bottom + focus: true + keyNavigationWraps: true + spacing:1 + opacity: 1 + model: stocks + + Component.onCompleted: opacity = 0.9; + onOpacityChanged: { + titleBar.title = "Top 100 NASDAQ stocks" + } + + + delegate : Rectangle { + height: 30 + width: view.width + color: { + if (ListView.isCurrentItem) + return focus ? "lightyellow" : "pink"; + return index % 2 == 0 ? "lightblue" : "lightsteelblue" + } + Text { + font.pointSize:20 + text: index + ". " + stockId + " \t(" + name + ")"; + } + MouseArea { + anchors.fill: parent; + onDoubleClicked: { + stockList.opacity = 0; + stockModel.stockName = stockId; + stockModel.description = "NASDAQ:" + stockId + " (" + name + ")"; + view.opacity = 1; + view.viewType = "chart"; + canvas.opacity = 0.7; + } + onClicked: stockList.currentIndex = index + }//mousearea + }//delegate + } + + ListView { + id:view + width: container.width + height: container.height - 50 + anchors.bottom: container.bottom + focus: true + keyNavigationWraps: true + + spacing:1 + opacity: 0 + model: stockModel + highlightFollowsCurrentItem: false + highlightRangeMode: ListView.StrictlyEnforceRange + preferredHighlightBegin:50 + preferredHighlightEnd : height - 50 + highlight: listHighlight + + //header : Text {} + delegate: listDelegate + snapMode: ListView.SnapToItem + + property string viewType : "list" + property int topIndex:indexAt(0,contentY); + property int bottomIndex:indexAt(0, contentY+height); + + onCountChanged: { + + titleBar.title = stockModel.description + " " + Qt.formatDate(stockModel.startDate, "yyyy-MM-dd") + " - " + + Qt.formatDate(stockModel.endDate, "yyyy-MM-dd") + " " + stockModel.dataCycleName() + + " records:" + view.count; + + } + + Component { + id: listDelegate + Rectangle { + height: 20 + width: view.width + border.color: "lightsteelblue" + border.width: 1 + color: { + if (ListView.isCurrentItem) + return focus ? "lightyellow" : "pink"; + + return index % 2 == 0 ? "lightblue" : "lightsteelblue" + } + Text { + font.pointSize:13 + text: index + ". " + Qt.formatDate(date, "yyyy-MM-dd") + + "\t " + Math.round(openPrice*100)/100 + + "\t " + Math.round(highPrice*100)/100 + + "\t " + Math.round(lowPrice*100)/100 + + "\t " + Math.round(closePrice*100)/100 + + "\t " + volume + "\t " + + Math.round(adjustedPrice*100)/100; + } + MouseArea {anchors.fill: parent; onClicked: view.currentIndex = index} + } + } + + Component { + id: chartDelegate + Rectangle { + height: 20 + width: view.width/view.count * canvas.scaleX + border.color: "lightsteelblue" + border.width: 1 + color: { + if (ListView.isCurrentItem) + return focus ? "lightyellow" : "pink"; + + return index % 2 == 0 ? "lightblue" : "lightsteelblue" + } + + Text { + anchors.bottom: parent.bottom + font.pointSize: { + if (parent.width <= 4) + return 1; + if (parent.width <= 50) + return parent.width/4; + return 15; + } + horizontalAlignment:Text.AlignHCenter + verticalAlignment:Text.AlignBottom + text:font.pointSize > 1 ? Qt.formatDate(date, "d/M/yy") : "" + } + MouseArea {anchors.fill: parent; onClicked: view.currentIndex = index} + } + } + + Component { + id:chartHighlight + Rectangle { radius: 5; width:40; height: 20; color: "lightsteelblue" } + } + + Component { + id:listHighlight + Rectangle { radius: 5; width:container.width; height: 20; color: "lightsteelblue" } + } + + + + + onViewTypeChanged : { + if (viewType == "list") { + view.orientation = ListView.Vertical; + view.delegate = listDelegate; +// view.section.property = "year"; +// view.section.criteria = ViewSection.FullString; +// view.section.delegate = sectionHeading; + view.highlight = listHighlight; + view.opacity = 1; + canvas.opacity = 0; + // comment.opacity = 0; + + } else if (viewType == "chart") { + view.orientation = ListView.Horizontal; + view.delegate = chartDelegate; + //comment.opacity = 0.6; + + view.opacity = 1; + view.height = 30 + + canvas.opacity = 0.7; + canvas.requestPaint(); + } else { + viewType = "list"; + } + } + + + onCurrentIndexChanged: { + //header.updateCurrent(stockModel.stockPriceAtIndex(view.currentIndex)); + if (viewType == "chart") { + canvas.first = Math.round(view.currentIndex - view.currentIndex / canvas.scaleX); + canvas.last = Math.round(view.currentIndex + (view.count - view.currentIndex) / canvas.scaleX); + + canvas.requestPaint(); + } + } + onContentYChanged: { // keep "current" item visible + topIndex = indexAt(0,contentY); + bottomIndex = indexAt(0, contentY+height); + + if (topIndex != -1 && currentIndex <= topIndex) + currentIndex = topIndex+1; + else if (bottomIndex != -1 && currentIndex >= bottomIndex) + currentIndex = bottomIndex-1; + if (viewType == "chart") + canvas.requestPaint(); + } + + onContentXChanged: { // keep "current" item visible + topIndex = indexAt(contentX,0); + bottomIndex = indexAt(contentX+width, 0); + + if (topIndex != -1 && currentIndex <= topIndex) + currentIndex = topIndex+1; + else if (bottomIndex != -1 && currentIndex >= bottomIndex) + currentIndex = bottomIndex-1; + if (viewType == "chart") + canvas.requestPaint(); + } + + MouseArea { + anchors.fill: parent + onDoubleClicked: { + if (view.viewType == "list") + view.viewType = "chart"; + else + view.viewType = "list"; + } + } + } + + + + Canvas { + id:canvas + anchors.top : titleBar.bottom + anchors.bottom : view.top + width:container.width; + opacity:0 + renderTarget: Canvas.Image + renderStrategy: Canvas.Immediate + property bool running:false + property int frames:first + property int mouseX:0; + property int mouseY:0; + property int mousePressedX:0; + property int mousePressedY:0; + property int movedY:0 + property real scaleX:1.0; + property real scaleY:1.0; + property int first:0; + property int last:view.count - 1; + + onOpacityChanged: { + if (opacity > 0) + requestPaint(); + } + Text { + id:comment + x:100 + y:50 + font.pointSize: 20 + color:"white" + opacity: 0.7 + focus:false + text: stockModel.description + function updateCurrent(price) + { + if (price !== undefined) { + text =stockModel.description + "\n" + + Qt.formatDate(price.date, "yyyy-MM-dd") + " OPEN:" + + Math.round(price.openPrice*100)/100 + " HIGH:" + + Math.round(price.highPrice*100)/100 + " LOW:" + + Math.round(price.lowPrice*100)/100 + " CLOSE:" + + Math.round(price.closePrice*100)/100 + " VOLUME:" + + price.volume; + } + } + } + + Text { + id:priceAxis + x:25 + y:25 + font.pointSize: 15 + color:"yellow" + opacity: 0.7 + focus: false + } + Text { + id:volumeAxis + x:canvas.width - 200 + y:25 + font.pointSize: 15 + color:"yellow" + opacity: 0.7 + } + + Rectangle { + id:progress + x:canvas.width/2 - 100 + y:canvas.height/2 + width:childrenRect.width + height: childrenRect.height + opacity: 0 + color:"white" + property string text; + Text { + text:parent.text + font.pointSize: 20 + } + } + + Button { + id:runButton + text:"Run this chart" + y:0 + x:canvas.width/2 - 50 + opacity: 0.5 + onClicked: { + if (canvas.running) { + canvas.running = false; + canvas.frames = canvas.first; + canvas.requestPaint(); + text = "Run this chart"; + comment.text = stockModel.description; + } else { + text = " Stop running "; + canvas.runChart(); + } + } + } + Button { + id:returnButton + text:"Stocks" + y:0 + anchors.left : runButton.right + anchors.leftMargin : 20 + opacity: 0.5 + onClicked: { + stockList.opacity = 1; + canvas.opacity = 0; + } + } + PinchArea { + anchors.fill: parent + onPinchUpdated : { + var current = pinch.center; + var scale = pinch.scale; + console.log("center:" + pinch.center + " scale:" + pinch.scale); + //canvas.requestPaint(); + } + } + + MouseArea { + anchors.fill: parent + + onDoubleClicked: { + if (stockModel.dataCycle == StockModel.Daily) + stockModel.dataCycle = StockModel.Weekly; + else if (stockModel.dataCycle == StockModel.Weekly) + stockModel.dataCycle = StockModel.Monthly; + else + stockModel.dataCycle = StockModel.Daily; + } + + onPositionChanged: { + if (mouse.modifiers & Qt.ControlModifier) { + if (canvas.mouseX == 0 && canvas.mouseY == 0) { + canvas.mouseX = mouse.x; + canvas.mouseY = mouse.y; + } + } else{ + var w = (view.width/view.count)*canvas.scaleX; + + //canvas.movedY += Math.round((canvas.mousePressedY - mouse.y)/2); + + var movedX = Math.round((canvas.mousePressedX - mouse.x)/w); + if (movedX != 0 || canvas.movedY != 0) { + if (canvas.first + movedX >= 0 && canvas.last + movedX < view.count) { + canvas.first += movedX; + canvas.last += movedX; + } + canvas.requestPaint(); + } + } + } + + onPressed: { + canvas.mousePressedX = mouse.x; + canvas.mousePressedY = mouse.y; + } + + onReleased : { + if (mouse.modifiers & Qt.ControlModifier) { + var sx = mouse.x - canvas.mouseX; + var sy = canvas.mouseY - mouse.y; + + if (Math.abs(sx) < 50) sx = 0; + if (Math.abs(sy) < 50) sy = 0; + + if (sx > 0) + canvas.scaleX *= sx/100 +1; + else + canvas.scaleX *= 1/(-sx/100 + 1); + + if (sy > 0) + canvas.scaleY *= sy/100 +1; + else + canvas.scaleY *= 1/(-sy/100 + 1); + + if (canvas.scaleX < 1) + canvas.scaleX = 1; + + //console.log("scaleX:" + canvas.scaleX + ", scaleY:" + canvas.scaleY); + + canvas.first = Math.round(view.currentIndex - view.currentIndex / canvas.scaleX); + canvas.last = Math.round(view.currentIndex + (view.count - view.currentIndex) / canvas.scaleX); + + canvas.mouseX = 0; + canvas.mouseY = 0; + canvas.mousePressedX = 0; + canvas.mousePressedY = 0; + canvas.requestPaint(); + } + } + } + + function runChart() { + canvas.running = true; + requestPaint(); + } + + function showPriceAt(x) { + var w = (view.width/view.count)*canvas.scaleX; + //header.updateCurrent(stockModel.stockPriceAtIndex(canvas.first + Math.round(x/w))); + //console.log("x:" + x + " w:" + w + " index:" + (canvas.first + Math.round(x/w))); + } + + function drawPrice(ctx, from, to, color, price, points, highest) + { + ctx.globalAlpha = 0.7; + ctx.strokeStyle = color; + ctx.lineWidth = 1; + ctx.beginPath(); + + //price x axis + priceAxis.text = "price:" + Math.round(highest); + ctx.font = "bold 12px sans-serif"; + + ctx.strokeText("price", 25, 25); + for (var j = 1; j < 30; j++) { + var val = (highest * j) / 30; + val = canvas.height * (1 - val/highest); + ctx.beginPath(); + + ctx.moveTo(10, val); + if (j % 5) + ctx.lineTo(15, val); + else + ctx.lineTo(20, val); + ctx.stroke(); + } + + ctx.beginPath(); + ctx.moveTo(10, 0); + ctx.lineTo(10, canvas.height); + ctx.stroke(); + + + var w = canvas.width/points.length; + var end = canvas.running? canvas.frames - canvas.first :points.length; + for (var i = 0; i < end; i++) { + var x = points[i].x; + var y = points[i][price]; + y += canvas.movedY; + + y = canvas.height * (1 - y/highest); + if (i == 0) { + ctx.moveTo(x+w/2, y); + } else { + ctx.lineTo(x+w/2, y); + } + } + ctx.stroke(); + } + + function drawKLine(ctx, from, to, points, highest) + { + ctx.globalAlpha = 0.4; + ctx.lineWidth = 2; + var end = canvas.running? canvas.frames - canvas.first :points.length; + for (var i = 0; i < end; i++) { + var x = points[i].x; + var open = canvas.height * (1 - points[i].open/highest) - canvas.movedY; + var close = canvas.height * (1 - points[i].close/highest) - canvas.movedY; + var high = canvas.height * (1 - points[i].high/highest) - canvas.movedY; + var low = canvas.height * (1 - points[i].low/highest) - canvas.movedY; + + var top, bottom; + if (close <= open) { + ctx.fillStyle = Qt.rgba(1, 0, 0, 1); + ctx.strokeStyle = Qt.rgba(1, 0, 0, 1); + top = close; + bottom = open; + } else { + ctx.fillStyle = Qt.rgba(0, 1, 0, 1); + ctx.strokeStyle = Qt.rgba(0, 1, 0, 1); + top = open; + bottom = close; + } + + var w1, w2; + w1 = canvas.width/points.length; + w2 = w1 > 10 ? w1/2 : w1; + + ctx.fillRect(x + (w1 - w2)/2, top, w2, bottom - top); + ctx.beginPath(); + ctx.moveTo(x+w1/2, high); + ctx.lineTo(x+w1/2, low); + ctx.stroke(); + } + ctx.globalAlpha = 1; + + } + + function drawVolume(ctx, from, to, color, price, points, highest) + { + ctx.fillStyle = color; + ctx.globalAlpha = 0.6; + ctx.strokeStyle = Qt.rgba(0.8, 0.8, 0.8, 1); + ctx.lineWidth = 1; + + //volume x axis + volumeAxis.text = "volume:" + Math.round(highest/(1000*100)) + "M"; + for (var j = 1; j < 30; j++) { + var val = (highest * j) / 30; + val = canvas.height * (1 - val/highest); + ctx.beginPath(); + if (j % 5) + ctx.moveTo(canvas.width - 15, val); + else + ctx.moveTo(canvas.width - 20, val); + ctx.lineTo(canvas.width - 10, val); + ctx.stroke(); + } + + ctx.beginPath(); + ctx.moveTo(canvas.width - 10, 0); + ctx.lineTo(canvas.width - 10, canvas.height); + ctx.stroke(); + + var end = canvas.running? canvas.frames - canvas.first :points.length; + for (var i = 0; i < end; i++) { + var x = points[i].x; + var y = points[i][price]; + y = canvas.height * (1 - y/highest); + ctx.fillRect(x, y, canvas.width/points.length, canvas.height - y); + } + } +/* + onPainted : { + if (canvas.running) { + if (frames >= last) { + canvas.running = false; + canvas.frames = first; + runButton.text = "Run this chart"; + comment.text = stockModel.description; + requestPaint(); + } else { + frames += Math.round(view.count / 100); + if (frames > last) frames = last; + var price = stockModel.stockPriceAtIndex(frames); + if (price) { + comment.updateCurrent(price); + } + + requestPaint(); + } + } + } +*/ + onPaint: { + if (view.currentIndex <= 0) + first = 0; + if (last >= view.count) + last = view.count - 1; + + //console.log("painting... first:" + first + ", last:" + last + " current:" + view.currentIndex); + var ctx = canvas.getContext("2d"); + ctx.save(); + + ctx.globalCompositeOperation = "source-over"; + ctx.lineWidth = 1; + ctx.lineJoin = "round"; + ctx.fillStyle = "rgba(0,0,0,0)"; + + ctx.fillRect(0, 0, canvas.width, canvas.height); + + + + var highestPrice = 500/canvas.scaleY; + var highestValume = 600 * 1000 * 1000/canvas.scaleY; + var points = []; + for (var i = 0; i <= last - first; i++) { + var price = stockModel.stockPriceAtIndex(i + first); + points.push({ + x: i*canvas.width/(last-first+1), + open: price.openPrice, + close: price.closePrice, + high:price.highPrice, + low:price.lowPrice, + volume:price.volume + }); + } + + drawPrice(ctx, first, last, Qt.rgba(1, 0, 0, 1),"high", points, highestPrice); + drawPrice(ctx, first, last, Qt.rgba(0, 1, 0, 1),"low", points, highestPrice); + drawPrice(ctx, first, last, Qt.rgba(0, 0, 1, 1),"open", points, highestPrice); + drawPrice(ctx, first, last, Qt.rgba(0.5, 0.5, 0.5, 1),"close", points, highestPrice); + drawVolume(ctx, first, last, Qt.rgba(0.3, 0.5, 0.7, 1),"volume", points, highestValume); + drawKLine(ctx, first, last, points, highestPrice); + ctx.restore(); + } + } +} diff --git a/examples/quick/canvas/stockchart/stockchart.pro b/examples/quick/canvas/stockchart/stockchart.pro new file mode 100644 index 0000000000..e368746db5 --- /dev/null +++ b/examples/quick/canvas/stockchart/stockchart.pro @@ -0,0 +1,20 @@ +TEMPLATE = lib +CONFIG += qt plugin +QT += qml network + +DESTDIR = com/nokia/StockChartExample +TARGET = qmlstockchartexampleplugin + +SOURCES += model.cpp plugin.cpp +HEADERS += model.h +qdeclarativesources.files += \ + com/nokia/StockChartExample/qmldir \ + stock.qml + +qdeclarativesources.path += $$[QT_INSTALL_EXAMPLES]/qtdeclarative/qml/plugins/com/nokia/StockChartExample + +sources.files += stockchart.pro model.h model.cpp plugin.cpp README +sources.path += $$[QT_INSTALL_EXAMPLES]/qtdeclarative/qml/plugins +target.path += $$[QT_INSTALL_EXAMPLES]/qtdeclarative/qml/plugins/com/nokia/StockChartExample + +INSTALLS += qdeclarativesources sources target |