088455a0cf24e2871ff28c3012422e4a8e8c3f07
[profile/ivi/qtdeclarative.git] / examples / declarative / canvas / stockchart / stock.qml
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: http://www.qt-project.org/
6 **
7 ** This file is part of the examples of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:BSD$
10 ** You may use this file under the terms of the BSD license as follows:
11 **
12 ** "Redistribution and use in source and binary forms, with or without
13 ** modification, are permitted provided that the following conditions are
14 ** met:
15 **   * Redistributions of source code must retain the above copyright
16 **     notice, this list of conditions and the following disclaimer.
17 **   * Redistributions in binary form must reproduce the above copyright
18 **     notice, this list of conditions and the following disclaimer in
19 **     the documentation and/or other materials provided with the
20 **     distribution.
21 **   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
22 **     the names of its contributors may be used to endorse or promote
23 **     products derived from this software without specific prior written
24 **     permission.
25 **
26 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40 import QtQuick 2.0
41 import com.nokia.StockChartExample 1.0
42 import "../contents"
43
44 Rectangle {
45     id:container
46     width: 360; height: 600
47     color: "#343434";
48     Image { source: "contents/images/stripes.png"; fillMode: Image.Tile; anchors.fill: parent; opacity: 1 }
49
50
51     TitleBar {
52         id: titleBar
53         width: parent.width
54         anchors.top : container.top
55         height: 40
56         opacity: 0.9
57     }
58
59     StockModel {
60         id:stockModel
61         dataCycle: StockModel.Daily
62         function dataCycleName() {
63             if (dataCycle === StockModel.Weekly)
64                 return "Weekly";
65             else if (dataCycle === StockModel.Monthly)
66                 return "Monthly";
67             return "Daily";
68         }
69
70         onDataChanged: {
71             if (view.viewType == "chart") {
72                 canvas.requestPaint();
73             }
74         }
75         onDownloadProgress: {
76             if (bytesReceived == bytesTotal && bytesTotal != -1) {
77                 progress.opacity = 0;
78             } else {
79                 progress.opacity = 0.8;
80                 progress.text = "downloading " + stockModel.dataCycleName() + " data ..."+ Math.round(bytesReceived/1000) + " KB";
81             }
82         }
83
84         property string description:"";
85     }
86
87     Stocks {id:stocks}
88
89     Rectangle {
90         id: header
91         width: parent.width
92         height: 20
93         color: "steelblue"
94         opacity: 0
95         Row {
96             spacing: 2
97             Text {
98                 id:t
99                 font.pointSize:15
100                 horizontalAlignment:Text.AlignHCenter
101                 font.bold: true
102                 font.underline:true
103             }
104             Rectangle {
105               height:20
106               width:50
107               Text {text:"Stock list"; font.pointSize:15; font.bold: true}
108             }
109         }
110     }
111
112     ListView {
113         id:stockList
114         width: parent.width
115         anchors.bottom: container.bottom
116         anchors.top : titleBar.bottom
117         focus: true
118         keyNavigationWraps: true
119         spacing:1
120         opacity: 1
121         model: stocks
122
123         Component.onCompleted: opacity = 0.9;
124         onOpacityChanged: {
125             titleBar.title = "Top 100 NASDAQ stocks"
126         }
127
128
129         delegate : Rectangle {
130                 height: 30
131                 width: view.width
132                 color: {
133                     if (ListView.isCurrentItem)
134                         return focus ? "lightyellow" : "pink";
135                     return index % 2 == 0 ? "lightblue" : "lightsteelblue"
136                 }
137                 Text {
138                        font.pointSize:20
139                        text: index + ". " + stockId + " \t(" + name + ")";
140                 }
141                 MouseArea {
142                     anchors.fill: parent;
143                     onDoubleClicked: {
144                         stockList.opacity = 0;
145                         stockModel.stockName = stockId;
146                         stockModel.description = "NASDAQ:" + stockId + " (" + name + ")";
147                         view.opacity = 1;
148                         view.viewType = "chart";
149                         canvas.opacity = 0.7;
150                     }
151                     onClicked: stockList.currentIndex = index
152                 }//mousearea
153         }//delegate
154     }
155
156     ListView {
157         id:view
158         width: container.width
159         height: container.height - 50
160         anchors.bottom: container.bottom
161         focus: true
162         keyNavigationWraps: true
163
164         spacing:1
165         opacity: 0
166         model: stockModel
167         highlightFollowsCurrentItem: false
168         highlightRangeMode: ListView.StrictlyEnforceRange
169         preferredHighlightBegin:50
170         preferredHighlightEnd : height - 50
171         highlight: listHighlight
172
173         //header : Text {}
174         delegate: listDelegate
175         snapMode: ListView.SnapToItem
176
177         property string viewType : "list"
178         property int topIndex:indexAt(0,contentY);
179         property int bottomIndex:indexAt(0, contentY+height);
180
181         onCountChanged:  {
182
183             titleBar.title = stockModel.description + " " + Qt.formatDate(stockModel.startDate, "yyyy-MM-dd") + " - " +
184                     Qt.formatDate(stockModel.endDate, "yyyy-MM-dd") + " " + stockModel.dataCycleName() +
185                              " records:" + view.count;
186
187         }
188
189         Component {
190             id: listDelegate
191             Rectangle {
192                 height: 20
193                 width: view.width
194                 border.color: "lightsteelblue"
195                 border.width: 1
196                 color: {
197                     if (ListView.isCurrentItem)
198                         return focus ? "lightyellow" : "pink";
199
200                     return index % 2 == 0 ? "lightblue" : "lightsteelblue"
201                 }
202                 Text {
203                        font.pointSize:13
204                        text: index + ". " + Qt.formatDate(date, "yyyy-MM-dd")
205                              + "\t " + Math.round(openPrice*100)/100
206                              + "\t " + Math.round(highPrice*100)/100
207                              + "\t " + Math.round(lowPrice*100)/100
208                              + "\t " + Math.round(closePrice*100)/100
209                              + "\t " + volume + "\t "
210                              + Math.round(adjustedPrice*100)/100;
211                 }
212                 MouseArea {anchors.fill: parent; onClicked: view.currentIndex = index}
213             }
214         }
215
216         Component {
217             id: chartDelegate
218             Rectangle {
219                 height: 20
220                 width: view.width/view.count * canvas.scaleX
221                 border.color: "lightsteelblue"
222                 border.width: 1
223                 color: {
224                     if (ListView.isCurrentItem)
225                         return focus ? "lightyellow" : "pink";
226
227                     return index % 2 == 0 ? "lightblue" : "lightsteelblue"
228                 }
229
230                 Text {
231                     anchors.bottom: parent.bottom
232                     font.pointSize: {
233                         if (parent.width <= 4)
234                             return  1;
235                         if (parent.width <= 50)
236                             return parent.width/4;
237                         return 15;
238                     }
239                     horizontalAlignment:Text.AlignHCenter
240                     verticalAlignment:Text.AlignBottom
241                     text:font.pointSize > 1 ? Qt.formatDate(date, "d/M/yy") : ""
242                 }
243                 MouseArea {anchors.fill: parent; onClicked: view.currentIndex = index}
244             }
245         }
246
247         Component {
248             id:chartHighlight
249             Rectangle { radius: 5; width:40; height: 20; color: "lightsteelblue" }
250         }
251
252         Component {
253             id:listHighlight
254             Rectangle { radius: 5; width:container.width; height: 20; color: "lightsteelblue" }
255         }
256
257
258
259
260         onViewTypeChanged : {
261             if (viewType == "list") {
262                 view.orientation = ListView.Vertical;
263                 view.delegate = listDelegate;
264 //                view.section.property = "year";
265 //                view.section.criteria = ViewSection.FullString;
266 //                view.section.delegate = sectionHeading;
267                 view.highlight = listHighlight;
268                 view.opacity = 1;
269                 canvas.opacity = 0;
270                // comment.opacity = 0;
271
272             } else if (viewType == "chart") {
273                 view.orientation = ListView.Horizontal;
274                 view.delegate = chartDelegate;
275                 //comment.opacity = 0.6;
276
277                 view.opacity = 1;
278                 view.height = 30
279
280                 canvas.opacity = 0.7;
281                 canvas.requestPaint();
282             } else {
283                 viewType = "list";
284             }
285         }
286
287
288         onCurrentIndexChanged: {
289             //header.updateCurrent(stockModel.stockPriceAtIndex(view.currentIndex));
290             if (viewType == "chart") {
291                 canvas.first = Math.round(view.currentIndex - view.currentIndex / canvas.scaleX);
292                 canvas.last = Math.round(view.currentIndex + (view.count - view.currentIndex) / canvas.scaleX);
293
294                 canvas.requestPaint();
295             }
296         }
297         onContentYChanged: {    // keep "current" item visible
298             topIndex = indexAt(0,contentY);
299             bottomIndex = indexAt(0, contentY+height);
300
301             if (topIndex != -1 && currentIndex <= topIndex)
302                 currentIndex = topIndex+1;
303             else if (bottomIndex != -1 && currentIndex >= bottomIndex)
304                 currentIndex = bottomIndex-1;
305             if (viewType == "chart")
306                 canvas.requestPaint();
307         }
308
309         onContentXChanged: {    // keep "current" item visible
310             topIndex = indexAt(contentX,0);
311             bottomIndex = indexAt(contentX+width, 0);
312
313             if (topIndex != -1 && currentIndex <= topIndex)
314                 currentIndex = topIndex+1;
315             else if (bottomIndex != -1 && currentIndex >= bottomIndex)
316                 currentIndex = bottomIndex-1;
317             if (viewType == "chart")
318                 canvas.requestPaint();
319         }
320
321         MouseArea {
322             anchors.fill: parent
323             onDoubleClicked: {
324                 if (view.viewType == "list")
325                     view.viewType = "chart";
326                 else
327                     view.viewType = "list";
328             }
329         }
330     }
331
332
333
334     Canvas {
335         id:canvas
336         anchors.top : titleBar.bottom
337         anchors.bottom : view.top
338         width:container.width;
339         opacity:0
340         renderInThread:false
341         renderTarget: Canvas.Image
342         property bool running:false
343         property int frames:first
344         property int mouseX:0;
345         property int mouseY:0;
346         property int mousePressedX:0;
347         property int mousePressedY:0;
348         property int movedY:0
349         property real scaleX:1.0;
350         property real scaleY:1.0;
351         property int first:0;
352         property int last:view.count - 1;
353
354         onOpacityChanged: {
355             if (opacity > 0)
356                 requestPaint();
357         }
358         Text {
359            id:comment
360            x:100
361            y:50
362            font.pointSize: 20
363            color:"white"
364            opacity: 0.7
365            focus:false
366            text: stockModel.description
367            function updateCurrent(price)
368            {
369                if (price !== undefined) {
370                    text =stockModel.description + "\n"
371                            + Qt.formatDate(price.date, "yyyy-MM-dd") + " OPEN:"
372                            + Math.round(price.openPrice*100)/100 + " HIGH:"
373                            + Math.round(price.highPrice*100)/100 + " LOW:"
374                            + Math.round(price.lowPrice*100)/100 + " CLOSE:"
375                            + Math.round(price.closePrice*100)/100 + " VOLUME:"
376                            + price.volume;
377                }
378            }
379         }
380
381         Text {
382             id:priceAxis
383             x:25
384             y:25
385             font.pointSize: 15
386             color:"yellow"
387             opacity: 0.7
388             focus: false
389         }
390         Text {
391             id:volumeAxis
392             x:canvas.width - 200
393             y:25
394             font.pointSize: 15
395             color:"yellow"
396             opacity: 0.7
397         }
398
399         Rectangle {
400             id:progress
401             x:canvas.width/2 - 100
402             y:canvas.height/2
403             width:childrenRect.width
404             height: childrenRect.height
405             opacity: 0
406             color:"white"
407             property string text;
408             Text {
409                 text:parent.text
410                 font.pointSize: 20
411             }
412         }
413
414         Button {
415             id:runButton
416             text:"Run this chart"
417             y:0
418             x:canvas.width/2 - 50
419             opacity: 0.5
420             onClicked:  {
421                 if (canvas.running) {
422                     canvas.running = false;
423                     canvas.frames = canvas.first;
424                     canvas.requestPaint();
425                     text = "Run this chart";
426                     comment.text = stockModel.description;
427                 } else {
428                     text = " Stop running ";
429                     canvas.runChart();
430                 }
431             }
432         }
433         Button {
434             id:returnButton
435             text:"Stocks"
436             y:0
437             anchors.left : runButton.right
438             anchors.leftMargin : 20
439             opacity: 0.5
440             onClicked:  {
441                 stockList.opacity = 1;
442                 canvas.opacity = 0;
443             }
444         }
445         PinchArea {
446             anchors.fill: parent
447             onPinchUpdated : {
448                 var current = pinch.center;
449                 var scale = pinch.scale;
450                 console.log("center:" + pinch.center + " scale:" + pinch.scale);
451                 //canvas.requestPaint();
452             }
453         }
454
455         MouseArea {
456             anchors.fill: parent
457
458             onDoubleClicked: {
459                 if (stockModel.dataCycle == StockModel.Daily)
460                     stockModel.dataCycle = StockModel.Weekly;
461                 else if (stockModel.dataCycle == StockModel.Weekly)
462                     stockModel.dataCycle = StockModel.Monthly;
463                 else
464                     stockModel.dataCycle = StockModel.Daily;
465             }
466
467             onPositionChanged: {
468                 if (mouse.modifiers & Qt.ControlModifier) {
469                     if (canvas.mouseX == 0 && canvas.mouseY == 0) {
470                         canvas.mouseX = mouse.x;
471                         canvas.mouseY = mouse.y;
472                     }
473                 } else{
474                     var w = (view.width/view.count)*canvas.scaleX;
475
476                     //canvas.movedY += Math.round((canvas.mousePressedY - mouse.y)/2);
477
478                     var movedX = Math.round((canvas.mousePressedX - mouse.x)/w);
479                     if (movedX != 0 || canvas.movedY != 0) {
480                         if (canvas.first + movedX >= 0 && canvas.last + movedX < view.count) {
481                             canvas.first += movedX;
482                             canvas.last += movedX;
483                         }
484                         canvas.requestPaint();
485                     }
486                 }
487             }
488
489             onPressed:  {
490                 canvas.mousePressedX = mouse.x;
491                 canvas.mousePressedY = mouse.y;
492             }
493
494             onReleased : {
495                 if (mouse.modifiers & Qt.ControlModifier) {
496                     var sx = mouse.x - canvas.mouseX;
497                     var sy = canvas.mouseY - mouse.y;
498
499                     if (Math.abs(sx) < 50) sx = 0;
500                     if (Math.abs(sy) < 50) sy = 0;
501
502                     if (sx > 0)
503                         canvas.scaleX *= sx/100 +1;
504                     else
505                         canvas.scaleX *= 1/(-sx/100 + 1);
506
507                     if (sy > 0)
508                         canvas.scaleY *= sy/100 +1;
509                     else
510                         canvas.scaleY *= 1/(-sy/100 + 1);
511
512                     if (canvas.scaleX < 1)
513                         canvas.scaleX = 1;
514
515                     //console.log("scaleX:" + canvas.scaleX + ", scaleY:" + canvas.scaleY);
516
517                     canvas.first = Math.round(view.currentIndex - view.currentIndex / canvas.scaleX);
518                     canvas.last = Math.round(view.currentIndex + (view.count - view.currentIndex) / canvas.scaleX);
519
520                     canvas.mouseX = 0;
521                     canvas.mouseY = 0;
522                     canvas.mousePressedX = 0;
523                     canvas.mousePressedY = 0;
524                     canvas.requestPaint();
525                 }
526             }
527         }
528
529         function runChart() {
530            canvas.running = true;
531             requestPaint();
532         }
533
534         function showPriceAt(x) {
535             var w = (view.width/view.count)*canvas.scaleX;
536             //header.updateCurrent(stockModel.stockPriceAtIndex(canvas.first + Math.round(x/w)));
537             //console.log("x:" + x + " w:" + w + " index:" + (canvas.first + Math.round(x/w)));
538         }
539
540         function drawPrice(ctx, from, to, color, price, points, highest)
541         {
542             ctx.globalAlpha = 0.7;
543             ctx.strokeStyle = color;
544             ctx.lineWidth = 1;
545             ctx.beginPath();
546
547             //price x axis
548             priceAxis.text = "price:" + Math.round(highest);
549             ctx.font = "bold 12px sans-serif";
550
551             ctx.strokeText("price", 25, 25);
552             for (var j = 1; j < 30; j++) {
553                 var val = (highest * j) / 30;
554                 val = canvas.height * (1 - val/highest);
555                 ctx.beginPath();
556
557                 ctx.moveTo(10, val);
558                 if (j % 5)
559                     ctx.lineTo(15, val);
560                 else
561                     ctx.lineTo(20, val);
562                 ctx.stroke();
563             }
564
565             ctx.beginPath();
566             ctx.moveTo(10, 0);
567             ctx.lineTo(10, canvas.height);
568             ctx.stroke();
569
570
571             var w = canvas.width/points.length;
572             var end = canvas.running? canvas.frames - canvas.first :points.length;
573             for (var i = 0; i < end; i++) {
574                 var x = points[i].x;
575                 var y = points[i][price];
576                 y += canvas.movedY;
577
578                 y = canvas.height * (1 - y/highest);
579                 if (i == 0) {
580                     ctx.moveTo(x+w/2, y);
581                 } else {
582                     ctx.lineTo(x+w/2, y);
583                 }
584             }
585             ctx.stroke();
586         }
587
588         function drawKLine(ctx, from, to, points, highest)
589         {
590             ctx.globalAlpha = 0.4;
591             ctx.lineWidth = 2;
592             var end = canvas.running? canvas.frames - canvas.first :points.length;
593             for (var i = 0; i < end; i++) {
594                 var x = points[i].x;
595                 var open = canvas.height * (1 - points[i].open/highest) - canvas.movedY;
596                 var close = canvas.height * (1 - points[i].close/highest) - canvas.movedY;
597                 var high = canvas.height * (1 - points[i].high/highest) - canvas.movedY;
598                 var low = canvas.height * (1 - points[i].low/highest) - canvas.movedY;
599
600                 var top, bottom;
601                 if (close <= open) {
602                     ctx.fillStyle = Qt.rgba(1, 0, 0, 1);
603                     ctx.strokeStyle = Qt.rgba(1, 0, 0, 1);
604                     top = close;
605                     bottom = open;
606                 } else {
607                     ctx.fillStyle = Qt.rgba(0, 1, 0, 1);
608                     ctx.strokeStyle = Qt.rgba(0, 1, 0, 1);
609                     top = open;
610                     bottom = close;
611                 }
612
613                 var w1, w2;
614                 w1 = canvas.width/points.length;
615                 w2 = w1 > 10 ? w1/2 : w1;
616
617                 ctx.fillRect(x + (w1 - w2)/2, top, w2, bottom - top);
618                 ctx.beginPath();
619                 ctx.moveTo(x+w1/2, high);
620                 ctx.lineTo(x+w1/2, low);
621                 ctx.stroke();
622             }
623             ctx.globalAlpha = 1;
624
625         }
626
627         function drawVolume(ctx, from, to, color, price, points, highest)
628         {
629             ctx.fillStyle = color;
630             ctx.globalAlpha = 0.6;
631             ctx.strokeStyle = Qt.rgba(0.8, 0.8, 0.8, 1);
632             ctx.lineWidth = 1;
633
634             //volume x axis
635             volumeAxis.text = "volume:" + Math.round(highest/(1000*100))  + "M";
636             for (var j = 1; j < 30; j++) {
637                 var val = (highest * j) / 30;
638                 val = canvas.height * (1 - val/highest);
639                 ctx.beginPath();
640                 if (j % 5)
641                     ctx.moveTo(canvas.width - 15, val);
642                 else
643                     ctx.moveTo(canvas.width - 20, val);
644                 ctx.lineTo(canvas.width - 10, val);
645                 ctx.stroke();
646             }
647
648             ctx.beginPath();
649             ctx.moveTo(canvas.width - 10, 0);
650             ctx.lineTo(canvas.width - 10, canvas.height);
651             ctx.stroke();
652
653             var end = canvas.running? canvas.frames - canvas.first :points.length;
654             for (var i = 0; i < end; i++) {
655                 var x = points[i].x;
656                 var y = points[i][price];
657                 y = canvas.height * (1 - y/highest);
658                 ctx.fillRect(x, y, canvas.width/points.length, canvas.height - y);
659             }
660         }
661 /*
662         onPainted : {
663             if (canvas.running) {
664                 if (frames >= last) {
665                     canvas.running = false;
666                     canvas.frames = first;
667                     runButton.text = "Run this chart";
668                     comment.text = stockModel.description;
669                     requestPaint();
670                 } else {
671                     frames += Math.round(view.count / 100);
672                     if (frames > last) frames = last;
673                     var price = stockModel.stockPriceAtIndex(frames);
674                     if (price) {
675                         comment.updateCurrent(price);
676                     }
677
678                     requestPaint();
679                 }
680             }
681         }
682 */
683         onPaint: {
684             if (view.currentIndex <= 0)
685                 first = 0;
686             if (last >= view.count)
687                 last = view.count - 1;
688
689             //console.log("painting...  first:" + first + ", last:" + last + " current:" + view.currentIndex);
690             var ctx = canvas.getContext("2d");
691             ctx.reset();
692
693             ctx.globalCompositeOperation = "source-over";
694             ctx.lineWidth = 1;
695             ctx.lineJoin = "round";
696             ctx.fillStyle = "rgba(0,0,0,0)";
697
698             ctx.fillRect(0, 0, canvas.width, canvas.height);
699
700
701
702             var highestPrice = 500/canvas.scaleY;
703             var highestValume = 600 * 1000 * 1000/canvas.scaleY;
704             var points = [];
705             for (var i = 0; i <= last - first; i++) {
706                 var price = stockModel.stockPriceAtIndex(i + first);
707                 points.push({
708                              x: i*canvas.width/(last-first+1),
709                              open: price.openPrice,
710                              close: price.closePrice,
711                              high:price.highPrice,
712                              low:price.lowPrice,
713                              volume:price.volume
714                             });
715             }
716
717             drawPrice(ctx, first, last, Qt.rgba(1, 0, 0, 1),"high", points, highestPrice);
718             drawPrice(ctx, first, last, Qt.rgba(0, 1, 0, 1),"low", points, highestPrice);
719             drawPrice(ctx, first, last, Qt.rgba(0, 0, 1, 1),"open", points, highestPrice);
720             drawPrice(ctx, first, last, Qt.rgba(0.5, 0.5, 0.5, 1),"close", points, highestPrice);
721             drawVolume(ctx, first, last, Qt.rgba(0.3, 0.5, 0.7, 1),"volume", points, highestValume);
722             drawKLine(ctx, first, last, points, highestPrice);
723         }
724     }
725 }