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