1 /**
2 *  Copyright Mikael H�gdahl - triyana@users.sourceforge.net
3 *
4 *  This source is distributed under the terms of the Q Public License version 1.0,
5 *  created by Trolltech (www.trolltech.com).
6 */
7 
8 #include "MHDayChart.h"
9 #include "MHDC.h"
10 #include "MHDebug.h"
11 #include "MHPrice.h"
12 
13 #define DSTART      ((aDateStart < 0) ? 0 : abs(aDateStart))
14 #define XSTART      (aDateStart < 0) ? ((aWidth - aRightBorder) - ((aTicks + aDateStart) * aTickWidth)) : ((aWidth - aRightBorder)  - (aTicks * aTickWidth))
15 #define XADJUST(xcurr,f,xadjust) \
16 if (xcurr > (aLeftBorder + 1) && f > 0) {\
17     xadjust = xcurr - aLeftBorder;\
18     xcurr   = aLeftBorder;\
19     xcurr++;\
20     f--;\
21 }
22 #define XNEXT(xcurr,xadjust,aTickWidth) \
23 xcurr += xadjust;\
24 if (xadjust != aTickWidth) xadjust = aTickWidth;
25 
26 
27 /**
28 * Create new extreme value object
29 */
MHXValue()30 MHXValue::MHXValue () :
31 MH () {
32     Clear ();
33 }
34 
35 
36 
37 /**
38 * Clear data. Set min and max value to the opposite max range value.
39 */
Clear(bool left)40 void MHXValue::Clear (bool left) {
41     aMin     = MHPrice::MAX_VALUE;
42     aMax     = MHPrice::MIN_VALUE;
43     aTick    = 0.0;
44     aYPixel  = 0.0;
45     aMinDate = "";
46     aMaxDate = "";
47     aLeft    = left;
48 }
49 
50 
51 
52 /**
53 * Dump info to file
54 */
Debug()55 void MHXValue::Debug () {
56     DBG ("MHXValue::Debug")
57 
58     DBG_LOG1 ("aMin             = %.2f", aMin)
59     DBG_LOG1 ("aMinDate         = %s", aMinDate())
60     DBG_LOG1 ("aMax             = %.2f", aMax)
61     DBG_LOG1 ("aMaxDate         = %s", aMaxDate())
62     DBG_LOG1 ("aTick            = %.2f", aTick)
63     DBG_LOG1 ("aYPixel          = %.8f", aYPixel)
64 }
65 
66 
67 
68 
69 
70 
71 /**
72 * Create new area structure.
73 */
MHArea()74 MHArea::MHArea () :
75 MH () {
76     aSize = 0.0;
77     Clear ();
78 }
79 
80 
81 
82 /**
83 * Clear data
84 */
Clear()85 void MHArea::Clear () {
86     aXPos = aYPos = aWidth = aHeight = 0.0;
87     aLeft.Clear (true);
88     aRight.Clear (false);
89 }
90 
91 
92 
93 /**
94 * Dump info to file
95 */
Debug()96 void MHArea::Debug () {
97     DBG ("MHArea::Debug")
98 
99     DBG_LOG1 ("aXPos            = %.0f", aXPos)
100     DBG_LOG1 ("aYPos            = %.0f", aYPos)
101     DBG_LOG1 ("aWidth           = %.0f", aWidth)
102     DBG_LOG1 ("aHeight          = %.0f", aHeight)
103     DBG_LOG1 ("aSize            = %.0f", aSize)
104 
105     aLeft.Debug ();
106     aRight.Debug ();
107 }
108 
109 
110 
111 
112 
113 
114 /**
115 * Create chart line storage object
116 */
MHChartLine()117 MHChartLine::MHChartLine () :
118 MH () {
119     aPrices = aClosed = 0;
120     Clear ();
121 }
122 
123 
124 
125 /**
126 * Clear and delete data
127 */
Clear()128 void MHChartLine::Clear () {
129     if (aPrices)
130         aPrices->Erase ();
131     if (aClosed)
132         aClosed->Erase ();
133     delete aPrices;
134     delete aClosed;
135 
136     aPrices    = 0;
137     aClosed    = 0;
138     aType      = 0;
139     aAlign     = 0;
140     aPos       = 0;
141     aName      = "";
142 }
143 
144 
145 
146 /**
147 * Dump info to file
148 */
Debug()149 void MHChartLine::Debug () {
150     DBG ("MHChartLine::Debug")
151 
152     DBG_LOG1 ("aPrices          = %.d", aPrices ? aPrices->Size() : 0)
153     DBG_LOG1 ("aType            = %d", aType)
154     DBG_LOG1 ("aAlign           = %d", aAlign)
155     DBG_LOG1 ("aPos             = %d", aPos)
156     DBG_LOG1 ("aName            = %s", aName())
157 }
158 
159 
160 
161 
162 
163 
164 
165 /**
166 * Create new chart.
167 * @param int - Vertical space between chart areas in pixel
168 * @param int - Left, right border space, number of caharacters
169 * @param int - Height for area 1 in percent
170 * @param int - Height for area 2 in percent, default 0 %
171 * @param int - Height for area 3 in percent, default 0 %
172 * @param int - Height for area 4 in percent, default 0 %
173 * @param int - Height for area 5 in percent, default 0 %
174 */
MHDayChart(int vspace,int bspace,int vsize1,int vsize2,int vsize3,int vsize4,int vsize5)175 MHDayChart::MHDayChart (int vspace, int bspace, int vsize1, int vsize2, int vsize3, int vsize4, int vsize5) :
176 MH () {
177     aVSpace = vspace;
178     aBSpace = bspace;
179     aAreas[0].aSize = vsize1;
180     aAreas[1].aSize = vsize2;
181     aAreas[2].aSize = vsize3;
182     aAreas[3].aSize = vsize4;
183     aAreas[4].aSize = vsize5;
184 
185     Clear ();
186 }
187 
188 
189 
190 /**
191 * Destroy chart and delete all data series
192 */
~MHDayChart()193 MHDayChart::~MHDayChart () {
194     Clear ();
195 }
196 
197 
198 
199 /**
200 *  Add new list of chart data to draw.
201 *  @param const char* - Name
202 *  @param int         - Date type for x axis
203 *  @param int         - LEFT or RIGHT align the labels
204 *  @param int         - What chart areas, 0 - 2
205 *  @param MHVector*   - Vector with price data in the form of MHPrice, do not use the vector after this method has been called.
206 *  @param MHVector*   - Vector with closed days
207 */
AddData(const char * name,int type,int align,int pos,MHVector * ve,MHVector * ve2)208 void MHDayChart::AddData (const char* name, int type, int align, int pos, MHVector* ve, MHVector* ve2) {
209     if (aLines < MAX_DATA_SERIES && pos >= 0 && pos < MAX_DATA_AREAS) {
210         aFirstTime            = true;
211         aLine[aLines].aName   = name;
212         aLine[aLines].aType   = type;
213         aLine[aLines].aAlign  = align;
214         aLine[aLines].aPos    = pos;
215         aLine[aLines].aPrices = ve;
216         aLine[aLines].aClosed = ve2;
217         aLines++;
218     }
219     else {
220         delete ve;
221     }
222 }
223 
224 
225 
226 /**
227 * Create reference date serie
228 *  @param int - MHDate::WEEKDAY, MHDate::FRIDAY, MHDate::SUNDAY, MHDate::MONTH, MHDate::SUNDAY, MHDate::ALL
229 */
AddDates(int timeType)230 void MHDayChart::AddDates (int timeType) {
231     DBG ("MHDayChart::CreateDates")
232 
233     aTimeType = timeType;
234 
235     MHString   min = "20300101";
236     MHString   max = "19000101";
237     int        i;
238 
239     for (i = 0; i < MAX_DATA_SERIES; i++) {
240         if (aLine[i].aPrices && aLine[i].aPrices->Size() > 1) {
241             MHPrice* first = (MHPrice*) aLine[i].aPrices->First();
242             MHPrice* last  = (MHPrice*) aLine[i].aPrices->Last();
243 
244             if (first && last) {
245                 if (min > first->aDate) min = first->aDate;
246                 if (max < last->aDate) max = last->aDate;
247             }
248         }
249     }
250     aDates.Erase ();
251 
252     MHVector closed;
253     for (i = 0; i < MAX_DATA_SERIES; i++) {
254         if (aLine[i].aClosed) {
255             for (int f = 0; f < aLine[i].aClosed->Size(); f++) {
256                 closed.Push (aLine[i].aClosed->Get (f));
257             }
258         }
259     }
260 
261     if (min < max)
262         MHDate::CreateTimeSerie (min(), max(), aTimeType, &closed, &aDates);
263 
264     closed.Empty();
265 }
266 
267 
268 
269 /**
270 * Set size of all chart areas
271 * @param MHDC* - Device context
272 */
calcSize(MHDC * dc)273 void MHDayChart::calcSize (MHDC* dc) {
274     DBG("MHDayChart::calcSize")
275 
276     int i;
277     int align   = 0;
278     int last    = 0;
279     int addh    = 0;
280     int swidth  = dc->GetStringWidth ("0");
281     int sheight = dc->GetCharHeight ();
282 
283     aWidth        = dc->GetWidth();
284     aHeight       = dc->GetHeight();
285     aLeftBorder   = swidth * 2;
286     aRightBorder  = swidth * 2;
287     aBottomBorder = sheight * 3;
288     aTopBorder    = sheight;
289 
290     // Fix width (y label space)
291     for (i = 0; i < aLines; i++) {
292         if (aLine[i].aAlign == LEFT)
293             align |= 1;
294         else if (aLine[i].aAlign == RIGHT)
295             align |= 2;
296     }
297 
298     if (align == 1 || align == 3) aLeftBorder  = swidth * aBSpace;
299     if (align == 2 || align == 3) aRightBorder = swidth * aBSpace;
300 
301     int width   = aWidth - (aLeftBorder + aRightBorder);
302 
303     // Fix heights
304     int height  = aHeight - (aBottomBorder + aTopBorder);
305 
306     for (i = 1; i < MAX_DATA_AREAS; i++)
307         if (aAreas[i].aSize >= MIN_AREA_HEIGHT) height -= aVSpace;
308 
309     for (i = 0; i < MAX_DATA_AREAS; i++) {
310         aAreas[i].Clear();
311         aAreas[i].aXPos  = aLeftBorder;
312         aAreas[i].aWidth = width;
313     }
314 
315     aAreas[0].aYPos   = aTopBorder;
316     aAreas[0].aHeight = (int) ((aAreas[0].aSize / 100.0) * height);
317 
318     for (i = 1; i < MAX_DATA_AREAS; i++) {
319         if (aAreas[i].aSize > MIN_AREA_HEIGHT) {
320             aAreas[i].aYPos      = aAreas[i - 1].aYPos + aAreas[i - 1].aHeight + aVSpace;
321             aAreas[i].aHeight    = (int) ((aAreas[1].aSize / 100.0) * height);
322             addh                += (int) aAreas[i - 1].aHeight;
323             last                 = i;
324             DBG_LOG4("%04d - %04d - %d (%04.3f)", aHeight, height, i, aAreas[i].aHeight)
325         }
326     }
327     if (last > 0) {
328         aAreas[last].aHeight = (int) (height - addh);
329         DBG_LOG4("%04d - %04d - %d (%04.3f)", aHeight, height, last, aAreas[last].aHeight)
330     }
331 
332     // Set maximum data size
333     aTicks = int(width / aTickWidth);
334     if (aDateStart == 0 && aFirstTime == true) {
335         aDateStart = aDates.Size() - aTicks;
336         aFirstTime = false;
337     }
338     else if (aDates.Size() < aTicks && abs(aDateStart - aTicks) > aDates.Size())
339         aDateStart = aDates.Size() - aTicks;
340     else if (abs(aDateStart + aTicks) > aDates.Size())
341         aDateStart = aDates.Size() - aTicks;
342 }
343 
344 
345 
346 /**
347 * Calc min and max y values for all areas
348 */
calcYMinYMax()349 void MHDayChart::calcYMinYMax () {
350     for (int e = 0; e < MAX_DATA_AREAS; e++) {
351         aAreas[e].aLeft.Clear (true);
352         aAreas[e].aRight.Clear (false);
353     }
354 
355     for (int i = 0; i < aLines && aLine[i].aPrices; i++) {
356         MHString* date    = 0;
357         MHPrice   point1;
358         MHPrice*  point2  = 0;
359         int       index   = 0;
360         int       pos     = aLine[i].aPos;
361         int       align   = aLine[i].aAlign;
362         int       start   = DSTART;
363         double    min;
364         double    max;
365 
366         for (int f = start; f <= (start + aTicks); f++) {
367             date = (MHString*) aDates.Get(f);
368             if (date) {
369                 point1.SetDate (date);
370                 if (index == 0)
371                     point2 = (MHPrice*) aLine[i].aPrices->Find (&point1, 0, 0, &index);
372                 else
373                     point2 = (MHPrice*) aLine[i].aPrices->Search (&point1, index, 0, &index);
374 
375                 if (point2 && point2->aClose > MHPrice::MIN_VALUE && point2->aClose < MHPrice::MAX_VALUE) {
376                     switch (aLine[i].aType) {
377                         case LINE_CHART:
378                             min = point2->aClose;
379                             max = point2->aClose;
380                             break;
381 
382                         case VOLUME_CHART:
383                             min = 0;
384                             max = point2->aVol;
385                             break;
386 
387                         default:
388                             min = point2->aLow;
389                             max = point2->aHigh;
390                             break;
391                     }
392 
393                     if (align == LEFT) {
394                         if (min < aAreas[pos].aLeft.aMin) {
395                             aAreas[pos].aLeft.aMin     = min;
396                             aAreas[pos].aLeft.aMinDate = point2->aDate;
397                         }
398                         if (max > aAreas[pos].aLeft.aMax) {
399                             aAreas[pos].aLeft.aMax     = max;
400                             aAreas[pos].aLeft.aMaxDate = point2->aDate;
401                         }
402                     }
403                     else {
404                         if (min < aAreas[pos].aRight.aMin) {
405                             aAreas[pos].aRight.aMin     = min;
406                             aAreas[pos].aRight.aMinDate = point2->aDate;
407                         }
408                         if (max > aAreas[pos].aRight.aMax) {
409                             aAreas[pos].aRight.aMax     = max;
410                             aAreas[pos].aRight.aMaxDate = point2->aDate;
411                         }
412                     }
413                 }
414             }
415             else
416                 break;
417         }
418     }
419 }
420 
421 
422 
423 /**
424 *  Original Copyrigth (c) 2000, Michael P.D Bramley.
425 *  Modified by me.
426 *  Calc ytick magic number.
427 *  @param MHXValue* - Structure with min and max data, store ytick in same structure.
428 */
calcYTick(MHXValue * val)429 void MHDayChart::calcYTick (MHXValue* val) {
430     double test_inc   = 0.0;
431     double test_min   = 0.0;
432     double test_max   = 0.0;
433     double range      = val->Diff();
434     int    i          = 0;
435 
436     val->aTick = 0.0;
437     if (val->aMin > val->aMax) return;
438     if (range < MHPrice::ZERO_VALUE) return;
439 
440     test_inc = pow ((double)10, (double)ceil (log10 (range / 10)));
441     test_max = ((long)(val->aMax / test_inc)) * test_inc;
442 
443     if (test_max < val->aMax)
444         test_max += test_inc;
445 
446     test_min = test_max;
447 
448     do {
449         ++i;
450         test_min -= test_inc;
451     } while (test_min > val->aMin);
452 
453     if (i < 6) {
454         test_inc /= 2;
455         if ((test_min + test_inc) <= val->aMin)
456             test_min += test_inc;
457         if ((test_max - test_inc) >= val->aMax)
458             test_max -= test_inc;
459     }
460 
461     val->aMin    = test_min;
462     val->aMax    = test_max;
463     val->aTick   = test_inc;
464 }
465 
466 
467 
468 /**
469 * Clear and delete data
470 */
Clear()471 void MHDayChart::Clear () {
472     int i;
473 
474     aDates.Erase ();
475     for (i = 0; i < MAX_DATA_SERIES; i++) aLine[i].Clear ();
476     for (i = 0; i < MAX_DATA_AREAS; i++) aAreas[i].Clear ();
477 
478     aLines          = 0;
479     aWidth          = 0;
480     aHeight         = 0;
481     aTickWidth      = 3;
482     aDateStart      = 0;
483     aBgColor        = -1;
484     aHorLines       = false;
485     aVerLines       = false;
486     aFirstTime      = true;
487     aVerPos[0]      = -1;
488     aTimeType       = 0;
489     aTopBorder      = 0;
490     aBottomBorder   = 0;
491     aLeftBorder     = 0;
492     aRightBorder    = 0;
493 }
494 
495 
496 
497 /**
498 * Dump info to file
499 */
Debug()500 void MHDayChart::Debug () {
501     #ifdef BUGG
502     DBG ("MHDayChart::Debug")
503 
504     MHString* first = (MHString*) aDates.First();
505     MHString* last  = (MHString*) aDates.Last();
506 
507     DBG_LOG1 ("aWidth           = %d", aWidth)
508     DBG_LOG1 ("aHeight          = %d", aHeight)
509     DBG_LOG1 ("aTicks           = %d", aTicks)
510     DBG_LOG1 ("aDates           = %d", aDates.Size())
511     DBG_LOG1 ("aDateStart       = %d", aDateStart)
512     DBG_LOG1 ("start            = %s", first ? first->Get() : "")
513     DBG_LOG1 ("stop             = %s", last ? last->Get() : "")
514     DBG_LOG1 ("aTopBorder       = %d", aTopBorder)
515     DBG_LOG1 ("aBottomBorder    = %d", aBottomBorder)
516     DBG_LOG1 ("aLeftBorder      = %d", aLeftBorder)
517     DBG_LOG1 ("aRightBorder     = %d", aRightBorder)
518 
519     for (int i = 0; i < aLines && aLine[i].aPrices; i++) {
520         aLine[i].Debug();
521     }
522     aAreas[0].Debug();
523     aAreas[1].Debug();
524     aAreas[2].Debug();
525     aAreas[3].Debug();
526     aAreas[4].Debug();
527     #endif
528 }
529 
530 
531 
532 /**
533 * Draw chart area border.
534 * @param int     - Area index
535 * @param MHDC*   - Device context
536 */
drawArea(int f,MHDC * dc)537 void MHDayChart::drawArea (int f, MHDC* dc) {
538     DBG("MHDayChart::drawArea")
539 
540     int x = (int) aAreas[f].aXPos;
541     int y = (int) aAreas[f].aYPos;
542     int w = (int) aAreas[f].aWidth;
543     int h = (int) aAreas[f].aHeight;
544 
545     dc->SetPenColor (MHDC::BLACK);
546 
547     if (aVSpace == 0 && f > 0)
548         ;
549     else
550         dc->DrawLine (x, y - 1, x + w, y - 1);
551 
552     if (aVSpace == 0 && aAreas[f + 1].aSize > MIN_AREA_HEIGHT)
553         ;
554     else
555         dc->DrawLine (x, y + h, x + w, y + h);
556 
557     dc->DrawLine (x, y, x, y + h);
558     dc->DrawLine (x + w, y, x + w, y + h);
559     DBG_LOG5("%d, %04d - %04d - %04d - %04d", f, x, y, w, h)
560 }
561 
562 
563 
564 /**
565 * Draw chart name
566 * @param int          - X pos for name
567 * @param int          - Y pos for name
568 * @param int          - Current row for name
569 * @param MHChartLine* - Line storage object
570 * @param MHDC*        - Device context
571 */
drawLabel(int x,int y,int labelpos,MHChartLine * line,MHDC * dc)572 void MHDayChart::drawLabel (int x, int y, int labelpos, MHChartLine* line, MHDC* dc) {
573     x += dc->GetStringWidth ("0");
574     dc->DrawText (line->aName(), x, y + (labelpos * dc->GetCharHeight()));
575 }
576 
577 
578 
579 /**
580 * Draw chart line with current color.
581 * @param MHChartLine* - Line data
582 * @param MHArea*      - Areato to be used
583 * @param MHDC*        - Device context
584 */
drawLine(MHChartLine * line,MHArea * area,MHDC * dc)585 void MHDayChart::drawLine (MHChartLine* line, MHArea* area, MHDC* dc) {
586     if (area->aWidth < 20) return;
587     int        xcurr   = XSTART;
588     int        y1      = int(area->aYPos);
589     int        ystart  = int(area->aYPos + area->aHeight);
590     int        xstop   = int(area->aXPos + area->aWidth);
591     int        lastX   = -1;
592     int        lastY   = -1;
593     int        yh;
594     int        yl;
595     int        yc;
596     int        yv;
597     int        yv2;
598     int        xadjust = aTickWidth;
599     int        rectW   = aTickWidth > 8 ? 3 : 2;
600     int        f       = DSTART;
601     int        first   = 0;
602     MHXValue*  xval    = line->aAlign == LEFT ? &area->aLeft : &area->aRight;
603     MHPrice*   price;
604 
605     XADJUST(xcurr,f,xadjust)
606 
607     for (; f < aDates.Size() && xcurr < xstop; f++) {
608         if (!first)
609             price = (MHPrice*) line->aPrices->Find (aDates[f], 0, 0, &first);
610         else
611             price = (MHPrice*) line->aPrices->Search (aDates[f], 0, 0, &first);
612 
613         if (price) {
614             yh = int(ystart - ((price->aHigh  - xval->aMin) * xval->aYPixel));
615             yl = int(ystart - ((price->aLow   - xval->aMin) * xval->aYPixel));
616             yc = int(ystart - ((price->aClose - xval->aMin) * xval->aYPixel));
617             yv = int(ystart - ((price->aVol   - xval->aMin) * xval->aYPixel));
618             yv2= int(ystart - ((0             - xval->aMin) * xval->aYPixel));
619             if (yh < y1) yh = y1;
620             if (yh > ystart) yh = ystart;
621             if (yc > ystart) yc = ystart;
622             if (yl > ystart) yl = ystart;
623 
624             switch (line->aType) {
625                 case BAR_CHART:
626                     dc->DrawLine (xcurr, yl, xcurr, yh);
627                     dc->DrawLine (xcurr, yc, xcurr + xadjust - 1, yc);
628                     break;
629 
630                 case LINE_CHART:
631                     if (lastX > 0 && lastY > 0)
632                         dc->DrawLine (lastX, lastY, xcurr, yc);
633                     break;
634 
635                 case LINE_CHART_HORIZONTAL_BLACK:
636                 case LINE_CHART_HORIZONTAL_GRAY:
637                     dc->DrawLine (xcurr, yc, xstop, yc);
638                     return;
639 
640                 case RECTANGLE_CHART:
641                 case GREY_RECTANGLE_CHART:
642                     if (yl < yh)
643                         dc->DrawRect (xcurr, yl, xcurr + xadjust - rectW, yh);
644                     else
645                         dc->DrawRect (xcurr, yh, xcurr + xadjust - rectW, yl);
646                     break;
647 
648                 case VERTICAL_CHART:
649                     dc->DrawLine (xcurr, y1, xcurr, ystart);
650                     break;
651 
652                 case VOLUME_CHART:
653                     //dc->DrawRect (xcurr, yv, xcurr + xadjust - rectW, ystart);
654                     dc->DrawRect (xcurr, yv, xcurr + xadjust - rectW, yv2);
655                     break;
656             }
657 
658             lastX = xcurr;
659             lastY = yc;
660         }
661         XNEXT(xcurr,xadjust,aTickWidth)
662         if ((xadjust + xcurr) > xstop) xadjust--;
663     }
664 }
665 
666 
667 /**
668 * Draw grey vertical border lines
669 * @param int    - Area index
670 * @param MHDC*  - Device context
671 */
drawVerLines(int f,MHDC * dc)672 void MHDayChart::drawVerLines (int f, MHDC* dc) {
673     dc->SetPenColor (MHDC::GRAY);
674     for (int i = 0; i < 100; i++) {
675         if (aVerPos[i] < 0)
676             break;
677         dc->DrawLine (aVerPos[i], int(aAreas[f].aYPos + 1), aVerPos[i], int(aAreas[f].aYPos + aAreas[f].aHeight - 2));
678     }
679 }
680 
681 
682 
683 /**
684 * Draw x labels (dates)
685 * @param MHDC* - Device context
686 */
drawXLabels(MHDC * dc)687 void MHDayChart::drawXLabels (MHDC* dc) {
688     int        xcurr   = XSTART;
689     int        y       = aHeight - aBottomBorder;
690     int        xstop   = aWidth - aRightBorder;
691     int        yadj1   = int(dc->GetCharHeight() / 2);
692     int        yadj2   = dc->GetCharHeight() + yadj1;
693     int        swidth  = int(dc->GetStringWidth("000") / 2);
694     int        swidthy = dc->GetStringWidth("00");
695     int        month   = 0;
696     int        year    = 0;
697     int        lastX   = 0;
698     int        xadjust = aTickWidth;
699     int        f       = DSTART;
700     int        verIdx  = 0;
701     MHDate     date;
702     MHString   label;
703 
704     XADJUST(xcurr,f,xadjust)
705     aVerPos[verIdx] = -1;
706 
707     for (; f < aDates.Size() && xcurr < xstop; f++) {
708         date.Set (((MHString*) aDates[f])->Get());
709 
710         if (date.GetMonth() != month) {
711             month = date.GetMonth();
712             if (xcurr > lastX) {
713                 dc->SetPenColor (MHDC::BLACK);
714 
715                 if (aTimeType != MHDate::FRIDAY) {
716                     lastX = xcurr + swidth + swidthy;
717                     date.Get (MHDate::ABBR_OF_MONTH, label);
718                     dc->DrawLine (xcurr, y, xcurr, y + yadj1);
719                     dc->DrawText (label(), xcurr - swidth, y + yadj1);
720 
721                     if (date.GetYear() != year) {
722                         year = date.GetYear();
723                         date.Get (MHDate::YEAR, label);
724                         dc->DrawText (label(), xcurr - swidthy, y + yadj2);
725                     }
726                 }
727                 else if (date.GetYear() != year) {
728                     // Draw only months
729                     lastX = xcurr + swidth + swidthy;
730                     year  = date.GetYear();
731                     date.Get (MHDate::YEAR, label);
732                     dc->DrawLine (xcurr, y, xcurr, y + yadj1);
733                     dc->DrawText (label(), xcurr - swidthy, y + yadj1);
734                 }
735 
736                 if (xcurr > (aLeftBorder + xadjust) && xcurr < (xstop - xadjust)) {
737                     aVerPos[verIdx++]   = xcurr;
738                     aVerPos[verIdx + 1] = -1;
739                 }
740             }
741         }
742         XNEXT(xcurr,xadjust,aTickWidth)
743     }
744 }
745 
746 
747 
748 /**
749 * Draw y labels on the left or right.
750 * @param int       - left x value
751 * @param int       - right x value
752 * @param double    - Start y value
753 * @param double    - Stop y value
754 * @param MHXValue* - Left or right max/min values
755 * @param MHDC*     - Device context
756 */
drawYLabels(int x1,int x2,double start,double stop,MHXValue * value,MHDC * dc)757 void MHDayChart::drawYLabels (int x1, int x2, double start, double stop, MHXValue* value, MHDC* dc) {
758     if (value->aMin >= value->aMax) return;
759 
760     int      x      = 0;
761     int      cw     = dc->GetStringWidth ("0");
762     int      ch     = dc->GetCharHeight();
763     int      dw     = aWidth - (aLeftBorder + aRightBorder);
764     double   last   = MHPrice::MAX_VALUE;
765     double   min    = value->aMin;
766     double   stop2  = aVSpace < ch ? (stop + ch) : stop;
767     int      fr, in;
768     MHString label;
769 
770     // If y numbers are to big (too wide) make them smaller, what do to with small decimal numbers???
771     if (value->aTick < 1000) {
772         in = MHUtil::GetFractionSize (value->aMax);
773         fr = MHUtil::GetFractionSize (value->aTick);
774     }
775     else if (value->aTick < 1000000) {
776         in = MHUtil::GetFractionSize (value->aMax / 1000.0);
777         fr = MHUtil::GetFractionSize (value->aTick / 1000.0);
778     }
779     else {
780         in = MHUtil::GetFractionSize (value->aMax / 1000000.0);
781         fr = MHUtil::GetFractionSize (value->aTick / 1000000.0);
782     }
783 
784     // Start from bottom on screen and go up (from min y value to max value and from max screen value to min value)
785 
786     while (start > stop) {
787         if (value->aTick < 1000.0)
788             label.Sprintf (50, "%.*f", fr, min);
789         else if (value->aTick < 1000000.0)
790             label.Sprintf (50, "%.*f'", fr, min / 1000.0);
791         else
792             label.Sprintf (50, "%.*f\"", fr, min / 1000000.0);
793 
794         // Draw tick and horisontal lines on the left or right
795         if (value->aLeft) {
796             dc->SetPenColor (MHDC::BLACK);
797             dc->DrawLine (x2 - int(cw / 2), int(start), x2, int(start));
798 
799             if (aHorLines && last < MHPrice::MAX_VALUE && start > (stop2 + ch)) {
800                 dc->SetPenColor (MHDC::GRAY);
801                 dc->DrawLine (x2 + 1, int(start), x2 + dw - 2, int(start));
802             }
803 
804         }
805         else {
806             dc->SetPenColor (MHDC::BLACK);
807             dc->DrawLine (x1, int(start), x1 + int(cw / 2), int(start));
808 
809             if (aHorLines && last < MHPrice::MAX_VALUE && start > (stop2 + ch)) {
810                 dc->SetPenColor (MHDC::GRAY);
811                 dc->DrawLine (x1 - dw + 1, int(start), x1 - 2, int(start));
812             }
813         }
814 
815         // Draw y labels if there is enough space since last text draw
816         if ((last - ch) > start && start > stop2) {
817             x = x2 - dc->GetStringWidth (label()) - cw;
818             if (x < (x1 + cw) && value->aLeft == false) x = x1 + cw;
819             dc->SetPenColor (MHDC::BLACK);
820             dc->DrawText (label(), x , int(start), true);
821             last = start;
822         }
823 
824         start -= (value->aYPixel * value->aTick);
825         min += value->aTick;
826     }
827 }
828 
829 
830 
831 /**
832 * Initiate data. Do it when size change or scrolling through the data.
833 * @param MHDC* - Device context
834 */
Init(MHDC * dc)835 void MHDayChart::Init (MHDC* dc) {
836     calcSize (dc);
837     calcYMinYMax ();
838 
839     for (int f = 0; f < MAX_DATA_AREAS; f++) {
840         MHArea* area = &aAreas[f];
841 
842         calcYTick (&area->aLeft);
843         calcYTick (&area->aRight);
844 
845         if (area->aLeft.aTick > MHPrice::ZERO_VALUE)
846             area->aLeft.aYPixel = area->aHeight / area->aLeft.Diff();
847         if (area->aRight.aTick > MHPrice::ZERO_VALUE)
848             area->aRight.aYPixel = area->aHeight / area->aRight.Diff();
849     }
850 }
851 
852 
853 
854 /**
855 * Paint chart
856 * @param MHDC* - Device context
857 */
Paint(MHDC * dc)858 void MHDayChart::Paint (MHDC* dc) {
859     int tmp[MAX_DATA_AREAS][2];
860     int color[MAX_DATA_AREAS];
861     int maxlen[MAX_DATA_AREAS];;
862     int f;
863 
864     // Draw background if the canvas doesn't manage that by itself
865     if (aBgColor >= 0) {
866         dc->SetBrushColor (aBgColor);
867         dc->DrawRect (0, 0, aWidth, aHeight);
868     }
869     // Draw area boxes
870     for (f = 0; f < MAX_DATA_AREAS; f++)
871         if (aAreas[f].aSize > MIN_AREA_HEIGHT) drawArea (f, dc);
872 
873     // Draw y Labels
874     for (f = 0; f < MAX_DATA_AREAS; f++) {
875         MHArea* area = &aAreas[f];
876         drawYLabels (0,                      aLeftBorder, area->aYPos + area->aHeight, area->aYPos, &area->aLeft,  dc);
877         drawYLabels (aWidth - aRightBorder,  aWidth,      area->aYPos + area->aHeight, area->aYPos, &area->aRight, dc);
878     }
879 
880     // Draw dates
881     drawXLabels (dc);
882 
883 
884     // Draw vertical gray support lines
885     for (f = 0; f < MAX_DATA_AREAS; f++)
886         if (aVerLines && aAreas[f].aSize > MIN_AREA_HEIGHT) drawVerLines (f, dc);
887 
888     // Draw Chart lines
889     for (f = 0; f < MAX_DATA_AREAS; f++) color[f] = MHDC::BLUE;
890     for (f = 0; f < MAX_DATA_SERIES; f++) {
891         if (aLine[f].aPrices) {
892             MHArea*      area  = &aAreas[aLine[f].aPos];
893             MHChartLine* line  = &aLine[f];
894 
895             if (aLine[f].aType == LINE_CHART_HORIZONTAL_BLACK)
896                 dc->SetPenColor (MHDC::BLACK);
897             else if (aLine[f].aType == LINE_CHART_HORIZONTAL_GRAY || aLine[f].aType == VERTICAL_CHART || aLine[f].aType == GREY_RECTANGLE_CHART)
898                 dc->SetPenColor (MHDC::GRAY);
899             else
900                 dc->SetPenColor (color[line->aPos]++);
901             drawLine (line, area, dc);
902             if (color[line->aPos] == MHDC::BLACK) color[line->aPos] = MHDC::BLUE;
903         }
904     }
905 
906     for (f = 0; f < MAX_DATA_AREAS; f++) {
907         color[f] = MHDC::BLUE;
908         tmp[f][0] = 0;
909         tmp[f][1] = 0;
910         maxlen[f] = 0;
911     }
912 
913 
914     // Find out largets label width for the labels on the right side
915     for (f = 0; f < MAX_DATA_SERIES; f++) {
916         MHChartLine* line  = &aLine[f];
917 
918         if (aLine[f].aName.Size() > 0 && aLine[f].aPrices && aLine[f].aAlign == RIGHT) {
919             int w = dc->GetStringWidth(aLine[f].aName());
920             if (w > maxlen[line->aPos]) maxlen[line->aPos] = w;
921         }
922     }
923 
924     // Don't draw the labels to close to the border
925     for (f = 0; f < MAX_DATA_AREAS; f++)
926         maxlen[f] += dc->GetStringWidth("00");
927 
928     // Draw line labels
929     for (f = 0; f < MAX_DATA_SERIES; f++) {
930         if (aLine[f].aPrices) {
931             if (aLine[f].aName.Size() > 0) {
932                 MHChartLine* line     = &aLine[f];
933                 MHArea*      area     = &aAreas[line->aPos];
934                 int*         labelpos = &tmp[line->aPos][line->aAlign];
935 
936                 if (aLine[f].aType == LINE_CHART_HORIZONTAL_BLACK)
937                     dc->SetPenColor (MHDC::BLACK);
938                 else if (aLine[f].aType == LINE_CHART_HORIZONTAL_GRAY || aLine[f].aType == GREY_RECTANGLE_CHART)
939                     dc->SetPenColor (MHDC::GRAY);
940                 else
941                     dc->SetPenColor (color[aLine[f].aPos]++);
942 
943                 if (line->aAlign == LEFT)
944                     drawLabel (int(area->aXPos), int(area->aYPos), *labelpos, line, dc);
945                 else
946                     drawLabel (int(area->aXPos + area->aWidth) - maxlen[line->aPos], int(area->aYPos), *labelpos, line, dc);
947 
948                 (*labelpos)++;
949                 if (color[line->aPos] == MHDC::BLACK) color[line->aPos] = MHDC::BLUE;
950             }
951         }
952     }
953 }
954 
955 
956 
957 /**
958 * Return y value for current (mouse) position
959 * @param int      - X pos
960 * @param int      - Y pos
961 * @param MHPrice* - Left value, vol is less than 0 if mouse is outside area, aClose contains y value
962 * @param MHPrice* - Right value, vol is less than 0 if mouse is outside area, aClose contains y value
963 */
Query(int x,int y,MHPrice * left,MHPrice * right)964 void MHDayChart::Query (int x, int y, MHPrice* left, MHPrice* right) {
965     left->aVol  = -1;
966     right->aVol = -1;
967 
968     for (int f = 0; f < MAX_DATA_AREAS; f++) {
969         MHArea* area  = &aAreas[f];
970 
971         if (area->aSize > MHPrice::ZERO_VALUE && x >= int(area->aXPos) && y >= int(area->aYPos) && x <= int(area->aXPos + area->aWidth) && y <= int(area->aYPos + area->aHeight + 1)) {
972             // We are inside an chart area
973 
974             int        xcurr   = XSTART;//XSTART(area);
975             int        xadjust = aTickWidth;
976             int        f       = DSTART;
977             int        xstop   = int(area->aXPos + area->aWidth);
978             MHString*  date    = 0;
979 
980             // Find date
981             for (; f < aDates.Size() && xcurr < xstop; f++) {
982                 if (x >= xcurr && x <= (xcurr + xadjust)) {
983                     date = (MHString*) aDates.Get(f);
984                     break;
985                 }
986                 XNEXT(xcurr,xadjust,aTickWidth)
987             }
988 
989             // Get y value for left
990             if (area->aLeft.aYPixel > MHPrice::ZERO_VALUE) {
991                 left->aClose = area->aLeft.aMax - ((y - int(area->aYPos)) / area->aLeft.aYPixel);
992                 left->aVol  = 1;
993                 left->aOpen = area->aLeft.aTick;
994                 if (date)
995                     strncpy (left->aDate, date->Get(), 9);
996                 else
997                     *left->aDate = '\0';
998             }
999 
1000             // Get y value for right
1001             if (area->aRight.aYPixel > MHPrice::ZERO_VALUE) {
1002                 right->aClose = area->aRight.aMax - ((y - int(area->aYPos)) / area->aRight.aYPixel);
1003                 right->aVol = 1;
1004                 right->aOpen = area->aRight.aTick;
1005                 if (date)
1006                     strncpy (right->aDate, date->Get(), 9);
1007                 else
1008                     *right->aDate = '\0';
1009             }
1010 
1011             break;
1012         }
1013     }
1014 }
1015