/** * Copyright Mikael Högdahl - triyana@users.sourceforge.net * * This source is distributed under the terms of the Q Public License version 1.0, * created by Trolltech (www.trolltech.com). */ #include "MHDayChart.h" #include "MHDC.h" #include "MHDebug.h" #include "MHPrice.h" #define DSTART ((aDateStart < 0) ? 0 : abs(aDateStart)) #define XSTART (aDateStart < 0) ? ((aWidth - aRightBorder) - ((aTicks + aDateStart) * aTickWidth)) : ((aWidth - aRightBorder) - (aTicks * aTickWidth)) #define XADJUST(xcurr,f,xadjust) \ if (xcurr > (aLeftBorder + 1) && f > 0) {\ xadjust = xcurr - aLeftBorder;\ xcurr = aLeftBorder;\ xcurr++;\ f--;\ } #define XNEXT(xcurr,xadjust,aTickWidth) \ xcurr += xadjust;\ if (xadjust != aTickWidth) xadjust = aTickWidth; /** * Create new extreme value object */ MHXValue::MHXValue () : MH () { Clear (); } /** * Clear data. Set min and max value to the opposite max range value. */ void MHXValue::Clear (bool left) { aMin = MHPrice::MAX_VALUE; aMax = MHPrice::MIN_VALUE; aTick = 0.0; aYPixel = 0.0; aMinDate = ""; aMaxDate = ""; aLeft = left; } /** * Dump info to file */ void MHXValue::Debug () { DBG ("MHXValue::Debug") DBG_LOG1 ("aMin = %.2f", aMin) DBG_LOG1 ("aMinDate = %s", aMinDate()) DBG_LOG1 ("aMax = %.2f", aMax) DBG_LOG1 ("aMaxDate = %s", aMaxDate()) DBG_LOG1 ("aTick = %.2f", aTick) DBG_LOG1 ("aYPixel = %.8f", aYPixel) } /** * Create new area structure. */ MHArea::MHArea () : MH () { aSize = 0.0; Clear (); } /** * Clear data */ void MHArea::Clear () { aXPos = aYPos = aWidth = aHeight = 0.0; aLeft.Clear (true); aRight.Clear (false); } /** * Dump info to file */ void MHArea::Debug () { DBG ("MHArea::Debug") DBG_LOG1 ("aXPos = %.0f", aXPos) DBG_LOG1 ("aYPos = %.0f", aYPos) DBG_LOG1 ("aWidth = %.0f", aWidth) DBG_LOG1 ("aHeight = %.0f", aHeight) DBG_LOG1 ("aSize = %.0f", aSize) aLeft.Debug (); aRight.Debug (); } /** * Create chart line storage object */ MHChartLine::MHChartLine () : MH () { aPrices = aClosed = 0; Clear (); } /** * Clear and delete data */ void MHChartLine::Clear () { if (aPrices) aPrices->Erase (); if (aClosed) aClosed->Erase (); delete aPrices; delete aClosed; aPrices = 0; aClosed = 0; aType = 0; aAlign = 0; aPos = 0; aName = ""; } /** * Dump info to file */ void MHChartLine::Debug () { DBG ("MHChartLine::Debug") DBG_LOG1 ("aPrices = %.d", aPrices ? aPrices->Size() : 0) DBG_LOG1 ("aType = %d", aType) DBG_LOG1 ("aAlign = %d", aAlign) DBG_LOG1 ("aPos = %d", aPos) DBG_LOG1 ("aName = %s", aName()) } /** * Create new chart. * @param int - Vertical space between chart areas in pixel * @param int - Left, right border space, number of caharacters * @param int - Height for area 1 in percent * @param int - Height for area 2 in percent, default 0 % * @param int - Height for area 3 in percent, default 0 % * @param int - Height for area 4 in percent, default 0 % * @param int - Height for area 5 in percent, default 0 % */ MHDayChart::MHDayChart (int vspace, int bspace, int vsize1, int vsize2, int vsize3, int vsize4, int vsize5) : MH () { aVSpace = vspace; aBSpace = bspace; aAreas[0].aSize = vsize1; aAreas[1].aSize = vsize2; aAreas[2].aSize = vsize3; aAreas[3].aSize = vsize4; aAreas[4].aSize = vsize5; Clear (); } /** * Destroy chart and delete all data series */ MHDayChart::~MHDayChart () { Clear (); } /** * Add new list of chart data to draw. * @param const char* - Name * @param int - Date type for x axis * @param int - LEFT or RIGHT align the labels * @param int - What chart areas, 0 - 2 * @param MHVector* - Vector with price data in the form of MHPrice, do not use the vector after this method has been called. * @param MHVector* - Vector with closed days */ void MHDayChart::AddData (const char* name, int type, int align, int pos, MHVector* ve, MHVector* ve2) { if (aLines < MAX_DATA_SERIES && pos >= 0 && pos < MAX_DATA_AREAS) { aFirstTime = true; aLine[aLines].aName = name; aLine[aLines].aType = type; aLine[aLines].aAlign = align; aLine[aLines].aPos = pos; aLine[aLines].aPrices = ve; aLine[aLines].aClosed = ve2; aLines++; } else { delete ve; } } /** * Create reference date serie * @param int - MHDate::WEEKDAY, MHDate::FRIDAY, MHDate::SUNDAY, MHDate::MONTH, MHDate::SUNDAY, MHDate::ALL */ void MHDayChart::AddDates (int timeType) { DBG ("MHDayChart::CreateDates") aTimeType = timeType; MHString min = "20300101"; MHString max = "19000101"; int i; for (i = 0; i < MAX_DATA_SERIES; i++) { if (aLine[i].aPrices && aLine[i].aPrices->Size() > 1) { MHPrice* first = (MHPrice*) aLine[i].aPrices->First(); MHPrice* last = (MHPrice*) aLine[i].aPrices->Last(); if (first && last) { if (min > first->aDate) min = first->aDate; if (max < last->aDate) max = last->aDate; } } } aDates.Erase (); MHVector closed; for (i = 0; i < MAX_DATA_SERIES; i++) { if (aLine[i].aClosed) { for (int f = 0; f < aLine[i].aClosed->Size(); f++) { closed.Push (aLine[i].aClosed->Get (f)); } } } if (min < max) MHDate::CreateTimeSerie (min(), max(), aTimeType, &closed, &aDates); closed.Empty(); } /** * Set size of all chart areas * @param MHDC* - Device context */ void MHDayChart::calcSize (MHDC* dc) { DBG("MHDayChart::calcSize") int i; int align = 0; int last = 0; int addh = 0; int swidth = dc->GetStringWidth ("0"); int sheight = dc->GetCharHeight (); aWidth = dc->GetWidth(); aHeight = dc->GetHeight(); aLeftBorder = swidth * 2; aRightBorder = swidth * 2; aBottomBorder = sheight * 3; aTopBorder = sheight; // Fix width (y label space) for (i = 0; i < aLines; i++) { if (aLine[i].aAlign == LEFT) align |= 1; else if (aLine[i].aAlign == RIGHT) align |= 2; } if (align == 1 || align == 3) aLeftBorder = swidth * aBSpace; if (align == 2 || align == 3) aRightBorder = swidth * aBSpace; int width = aWidth - (aLeftBorder + aRightBorder); // Fix heights int height = aHeight - (aBottomBorder + aTopBorder); for (i = 1; i < MAX_DATA_AREAS; i++) if (aAreas[i].aSize >= MIN_AREA_HEIGHT) height -= aVSpace; for (i = 0; i < MAX_DATA_AREAS; i++) { aAreas[i].Clear(); aAreas[i].aXPos = aLeftBorder; aAreas[i].aWidth = width; } aAreas[0].aYPos = aTopBorder; aAreas[0].aHeight = (int) ((aAreas[0].aSize / 100.0) * height); for (i = 1; i < MAX_DATA_AREAS; i++) { if (aAreas[i].aSize > MIN_AREA_HEIGHT) { aAreas[i].aYPos = aAreas[i - 1].aYPos + aAreas[i - 1].aHeight + aVSpace; aAreas[i].aHeight = (int) ((aAreas[1].aSize / 100.0) * height); addh += (int) aAreas[i - 1].aHeight; last = i; DBG_LOG4("%04d - %04d - %d (%04.3f)", aHeight, height, i, aAreas[i].aHeight) } } if (last > 0) { aAreas[last].aHeight = (int) (height - addh); DBG_LOG4("%04d - %04d - %d (%04.3f)", aHeight, height, last, aAreas[last].aHeight) } // Set maximum data size aTicks = int(width / aTickWidth); if (aDateStart == 0 && aFirstTime == true) { aDateStart = aDates.Size() - aTicks; aFirstTime = false; } else if (aDates.Size() < aTicks && abs(aDateStart - aTicks) > aDates.Size()) aDateStart = aDates.Size() - aTicks; else if (abs(aDateStart + aTicks) > aDates.Size()) aDateStart = aDates.Size() - aTicks; } /** * Calc min and max y values for all areas */ void MHDayChart::calcYMinYMax () { for (int e = 0; e < MAX_DATA_AREAS; e++) { aAreas[e].aLeft.Clear (true); aAreas[e].aRight.Clear (false); } for (int i = 0; i < aLines && aLine[i].aPrices; i++) { MHString* date = 0; MHPrice point1; MHPrice* point2 = 0; int index = 0; int pos = aLine[i].aPos; int align = aLine[i].aAlign; int start = DSTART; double min; double max; for (int f = start; f <= (start + aTicks); f++) { date = (MHString*) aDates.Get(f); if (date) { point1.SetDate (date); if (index == 0) point2 = (MHPrice*) aLine[i].aPrices->Find (&point1, 0, 0, &index); else point2 = (MHPrice*) aLine[i].aPrices->Search (&point1, index, 0, &index); if (point2 && point2->aClose > MHPrice::MIN_VALUE && point2->aClose < MHPrice::MAX_VALUE) { switch (aLine[i].aType) { case LINE_CHART: min = point2->aClose; max = point2->aClose; break; case VOLUME_CHART: min = 0; max = point2->aVol; break; default: min = point2->aLow; max = point2->aHigh; break; } if (align == LEFT) { if (min < aAreas[pos].aLeft.aMin) { aAreas[pos].aLeft.aMin = min; aAreas[pos].aLeft.aMinDate = point2->aDate; } if (max > aAreas[pos].aLeft.aMax) { aAreas[pos].aLeft.aMax = max; aAreas[pos].aLeft.aMaxDate = point2->aDate; } } else { if (min < aAreas[pos].aRight.aMin) { aAreas[pos].aRight.aMin = min; aAreas[pos].aRight.aMinDate = point2->aDate; } if (max > aAreas[pos].aRight.aMax) { aAreas[pos].aRight.aMax = max; aAreas[pos].aRight.aMaxDate = point2->aDate; } } } } else break; } } } /** * Original Copyrigth (c) 2000, Michael P.D Bramley. * Modified by me. * Calc ytick magic number. * @param MHXValue* - Structure with min and max data, store ytick in same structure. */ void MHDayChart::calcYTick (MHXValue* val) { double test_inc = 0.0; double test_min = 0.0; double test_max = 0.0; double range = val->Diff(); int i = 0; val->aTick = 0.0; if (val->aMin > val->aMax) return; if (range < MHPrice::ZERO_VALUE) return; test_inc = pow ((double)10, (double)ceil (log10 (range / 10))); test_max = ((long)(val->aMax / test_inc)) * test_inc; if (test_max < val->aMax) test_max += test_inc; test_min = test_max; do { ++i; test_min -= test_inc; } while (test_min > val->aMin); if (i < 6) { test_inc /= 2; if ((test_min + test_inc) <= val->aMin) test_min += test_inc; if ((test_max - test_inc) >= val->aMax) test_max -= test_inc; } val->aMin = test_min; val->aMax = test_max; val->aTick = test_inc; } /** * Clear and delete data */ void MHDayChart::Clear () { int i; aDates.Erase (); for (i = 0; i < MAX_DATA_SERIES; i++) aLine[i].Clear (); for (i = 0; i < MAX_DATA_AREAS; i++) aAreas[i].Clear (); aLines = 0; aWidth = 0; aHeight = 0; aTickWidth = 3; aDateStart = 0; aBgColor = -1; aHorLines = false; aVerLines = false; aFirstTime = true; aVerPos[0] = -1; aTimeType = 0; aTopBorder = 0; aBottomBorder = 0; aLeftBorder = 0; aRightBorder = 0; } /** * Dump info to file */ void MHDayChart::Debug () { #ifdef BUGG DBG ("MHDayChart::Debug") MHString* first = (MHString*) aDates.First(); MHString* last = (MHString*) aDates.Last(); DBG_LOG1 ("aWidth = %d", aWidth) DBG_LOG1 ("aHeight = %d", aHeight) DBG_LOG1 ("aTicks = %d", aTicks) DBG_LOG1 ("aDates = %d", aDates.Size()) DBG_LOG1 ("aDateStart = %d", aDateStart) DBG_LOG1 ("start = %s", first ? first->Get() : "") DBG_LOG1 ("stop = %s", last ? last->Get() : "") DBG_LOG1 ("aTopBorder = %d", aTopBorder) DBG_LOG1 ("aBottomBorder = %d", aBottomBorder) DBG_LOG1 ("aLeftBorder = %d", aLeftBorder) DBG_LOG1 ("aRightBorder = %d", aRightBorder) for (int i = 0; i < aLines && aLine[i].aPrices; i++) { aLine[i].Debug(); } aAreas[0].Debug(); aAreas[1].Debug(); aAreas[2].Debug(); aAreas[3].Debug(); aAreas[4].Debug(); #endif } /** * Draw chart area border. * @param int - Area index * @param MHDC* - Device context */ void MHDayChart::drawArea (int f, MHDC* dc) { DBG("MHDayChart::drawArea") int x = (int) aAreas[f].aXPos; int y = (int) aAreas[f].aYPos; int w = (int) aAreas[f].aWidth; int h = (int) aAreas[f].aHeight; dc->SetPenColor (MHDC::BLACK); if (aVSpace == 0 && f > 0) ; else dc->DrawLine (x, y - 1, x + w, y - 1); if (aVSpace == 0 && aAreas[f + 1].aSize > MIN_AREA_HEIGHT) ; else dc->DrawLine (x, y + h, x + w, y + h); dc->DrawLine (x, y, x, y + h); dc->DrawLine (x + w, y, x + w, y + h); DBG_LOG5("%d, %04d - %04d - %04d - %04d", f, x, y, w, h) } /** * Draw chart name * @param int - X pos for name * @param int - Y pos for name * @param int - Current row for name * @param MHChartLine* - Line storage object * @param MHDC* - Device context */ void MHDayChart::drawLabel (int x, int y, int labelpos, MHChartLine* line, MHDC* dc) { x += dc->GetStringWidth ("0"); dc->DrawText (line->aName(), x, y + (labelpos * dc->GetCharHeight())); } /** * Draw chart line with current color. * @param MHChartLine* - Line data * @param MHArea* - Areato to be used * @param MHDC* - Device context */ void MHDayChart::drawLine (MHChartLine* line, MHArea* area, MHDC* dc) { if (area->aWidth < 20) return; int xcurr = XSTART; int y1 = int(area->aYPos); int ystart = int(area->aYPos + area->aHeight); int xstop = int(area->aXPos + area->aWidth); int lastX = -1; int lastY = -1; int yh; int yl; int yc; int yv; int yv2; int xadjust = aTickWidth; int rectW = aTickWidth > 8 ? 3 : 2; int f = DSTART; int first = 0; MHXValue* xval = line->aAlign == LEFT ? &area->aLeft : &area->aRight; MHPrice* price; XADJUST(xcurr,f,xadjust) for (; f < aDates.Size() && xcurr < xstop; f++) { if (!first) price = (MHPrice*) line->aPrices->Find (aDates[f], 0, 0, &first); else price = (MHPrice*) line->aPrices->Search (aDates[f], 0, 0, &first); if (price) { yh = int(ystart - ((price->aHigh - xval->aMin) * xval->aYPixel)); yl = int(ystart - ((price->aLow - xval->aMin) * xval->aYPixel)); yc = int(ystart - ((price->aClose - xval->aMin) * xval->aYPixel)); yv = int(ystart - ((price->aVol - xval->aMin) * xval->aYPixel)); yv2= int(ystart - ((0 - xval->aMin) * xval->aYPixel)); if (yh < y1) yh = y1; if (yh > ystart) yh = ystart; if (yc > ystart) yc = ystart; if (yl > ystart) yl = ystart; switch (line->aType) { case BAR_CHART: dc->DrawLine (xcurr, yl, xcurr, yh); dc->DrawLine (xcurr, yc, xcurr + xadjust - 1, yc); break; case LINE_CHART: if (lastX > 0 && lastY > 0) dc->DrawLine (lastX, lastY, xcurr, yc); break; case LINE_CHART_HORIZONTAL_BLACK: case LINE_CHART_HORIZONTAL_GRAY: dc->DrawLine (xcurr, yc, xstop, yc); return; case RECTANGLE_CHART: case GREY_RECTANGLE_CHART: if (yl < yh) dc->DrawRect (xcurr, yl, xcurr + xadjust - rectW, yh); else dc->DrawRect (xcurr, yh, xcurr + xadjust - rectW, yl); break; case VERTICAL_CHART: dc->DrawLine (xcurr, y1, xcurr, ystart); break; case VOLUME_CHART: //dc->DrawRect (xcurr, yv, xcurr + xadjust - rectW, ystart); dc->DrawRect (xcurr, yv, xcurr + xadjust - rectW, yv2); break; } lastX = xcurr; lastY = yc; } XNEXT(xcurr,xadjust,aTickWidth) if ((xadjust + xcurr) > xstop) xadjust--; } } /** * Draw grey vertical border lines * @param int - Area index * @param MHDC* - Device context */ void MHDayChart::drawVerLines (int f, MHDC* dc) { dc->SetPenColor (MHDC::GRAY); for (int i = 0; i < 100; i++) { if (aVerPos[i] < 0) break; dc->DrawLine (aVerPos[i], int(aAreas[f].aYPos + 1), aVerPos[i], int(aAreas[f].aYPos + aAreas[f].aHeight - 2)); } } /** * Draw x labels (dates) * @param MHDC* - Device context */ void MHDayChart::drawXLabels (MHDC* dc) { int xcurr = XSTART; int y = aHeight - aBottomBorder; int xstop = aWidth - aRightBorder; int yadj1 = int(dc->GetCharHeight() / 2); int yadj2 = dc->GetCharHeight() + yadj1; int swidth = int(dc->GetStringWidth("000") / 2); int swidthy = dc->GetStringWidth("00"); int month = 0; int year = 0; int lastX = 0; int xadjust = aTickWidth; int f = DSTART; int verIdx = 0; MHDate date; MHString label; XADJUST(xcurr,f,xadjust) aVerPos[verIdx] = -1; for (; f < aDates.Size() && xcurr < xstop; f++) { date.Set (((MHString*) aDates[f])->Get()); if (date.GetMonth() != month) { month = date.GetMonth(); if (xcurr > lastX) { dc->SetPenColor (MHDC::BLACK); if (aTimeType != MHDate::FRIDAY) { lastX = xcurr + swidth + swidthy; date.Get (MHDate::ABBR_OF_MONTH, label); dc->DrawLine (xcurr, y, xcurr, y + yadj1); dc->DrawText (label(), xcurr - swidth, y + yadj1); if (date.GetYear() != year) { year = date.GetYear(); date.Get (MHDate::YEAR, label); dc->DrawText (label(), xcurr - swidthy, y + yadj2); } } else if (date.GetYear() != year) { // Draw only months lastX = xcurr + swidth + swidthy; year = date.GetYear(); date.Get (MHDate::YEAR, label); dc->DrawLine (xcurr, y, xcurr, y + yadj1); dc->DrawText (label(), xcurr - swidthy, y + yadj1); } if (xcurr > (aLeftBorder + xadjust) && xcurr < (xstop - xadjust)) { aVerPos[verIdx++] = xcurr; aVerPos[verIdx + 1] = -1; } } } XNEXT(xcurr,xadjust,aTickWidth) } } /** * Draw y labels on the left or right. * @param int - left x value * @param int - right x value * @param double - Start y value * @param double - Stop y value * @param MHXValue* - Left or right max/min values * @param MHDC* - Device context */ void MHDayChart::drawYLabels (int x1, int x2, double start, double stop, MHXValue* value, MHDC* dc) { if (value->aMin >= value->aMax) return; int x = 0; int cw = dc->GetStringWidth ("0"); int ch = dc->GetCharHeight(); int dw = aWidth - (aLeftBorder + aRightBorder); double last = MHPrice::MAX_VALUE; double min = value->aMin; double stop2 = aVSpace < ch ? (stop + ch) : stop; int fr, in; MHString label; // If y numbers are to big (too wide) make them smaller, what do to with small decimal numbers??? if (value->aTick < 1000) { in = MHUtil::GetFractionSize (value->aMax); fr = MHUtil::GetFractionSize (value->aTick); } else if (value->aTick < 1000000) { in = MHUtil::GetFractionSize (value->aMax / 1000.0); fr = MHUtil::GetFractionSize (value->aTick / 1000.0); } else { in = MHUtil::GetFractionSize (value->aMax / 1000000.0); fr = MHUtil::GetFractionSize (value->aTick / 1000000.0); } // Start from bottom on screen and go up (from min y value to max value and from max screen value to min value) while (start > stop) { if (value->aTick < 1000.0) label.Sprintf (50, "%.*f", fr, min); else if (value->aTick < 1000000.0) label.Sprintf (50, "%.*f'", fr, min / 1000.0); else label.Sprintf (50, "%.*f\"", fr, min / 1000000.0); // Draw tick and horisontal lines on the left or right if (value->aLeft) { dc->SetPenColor (MHDC::BLACK); dc->DrawLine (x2 - int(cw / 2), int(start), x2, int(start)); if (aHorLines && last < MHPrice::MAX_VALUE && start > (stop2 + ch)) { dc->SetPenColor (MHDC::GRAY); dc->DrawLine (x2 + 1, int(start), x2 + dw - 2, int(start)); } } else { dc->SetPenColor (MHDC::BLACK); dc->DrawLine (x1, int(start), x1 + int(cw / 2), int(start)); if (aHorLines && last < MHPrice::MAX_VALUE && start > (stop2 + ch)) { dc->SetPenColor (MHDC::GRAY); dc->DrawLine (x1 - dw + 1, int(start), x1 - 2, int(start)); } } // Draw y labels if there is enough space since last text draw if ((last - ch) > start && start > stop2) { x = x2 - dc->GetStringWidth (label()) - cw; if (x < (x1 + cw) && value->aLeft == false) x = x1 + cw; dc->SetPenColor (MHDC::BLACK); dc->DrawText (label(), x , int(start), true); last = start; } start -= (value->aYPixel * value->aTick); min += value->aTick; } } /** * Initiate data. Do it when size change or scrolling through the data. * @param MHDC* - Device context */ void MHDayChart::Init (MHDC* dc) { calcSize (dc); calcYMinYMax (); for (int f = 0; f < MAX_DATA_AREAS; f++) { MHArea* area = &aAreas[f]; calcYTick (&area->aLeft); calcYTick (&area->aRight); if (area->aLeft.aTick > MHPrice::ZERO_VALUE) area->aLeft.aYPixel = area->aHeight / area->aLeft.Diff(); if (area->aRight.aTick > MHPrice::ZERO_VALUE) area->aRight.aYPixel = area->aHeight / area->aRight.Diff(); } } /** * Paint chart * @param MHDC* - Device context */ void MHDayChart::Paint (MHDC* dc) { int tmp[MAX_DATA_AREAS][2]; int color[MAX_DATA_AREAS]; int maxlen[MAX_DATA_AREAS];; int f; // Draw background if the canvas doesn't manage that by itself if (aBgColor >= 0) { dc->SetBrushColor (aBgColor); dc->DrawRect (0, 0, aWidth, aHeight); } // Draw area boxes for (f = 0; f < MAX_DATA_AREAS; f++) if (aAreas[f].aSize > MIN_AREA_HEIGHT) drawArea (f, dc); // Draw y Labels for (f = 0; f < MAX_DATA_AREAS; f++) { MHArea* area = &aAreas[f]; drawYLabels (0, aLeftBorder, area->aYPos + area->aHeight, area->aYPos, &area->aLeft, dc); drawYLabels (aWidth - aRightBorder, aWidth, area->aYPos + area->aHeight, area->aYPos, &area->aRight, dc); } // Draw dates drawXLabels (dc); // Draw vertical gray support lines for (f = 0; f < MAX_DATA_AREAS; f++) if (aVerLines && aAreas[f].aSize > MIN_AREA_HEIGHT) drawVerLines (f, dc); // Draw Chart lines for (f = 0; f < MAX_DATA_AREAS; f++) color[f] = MHDC::BLUE; for (f = 0; f < MAX_DATA_SERIES; f++) { if (aLine[f].aPrices) { MHArea* area = &aAreas[aLine[f].aPos]; MHChartLine* line = &aLine[f]; if (aLine[f].aType == LINE_CHART_HORIZONTAL_BLACK) dc->SetPenColor (MHDC::BLACK); else if (aLine[f].aType == LINE_CHART_HORIZONTAL_GRAY || aLine[f].aType == VERTICAL_CHART || aLine[f].aType == GREY_RECTANGLE_CHART) dc->SetPenColor (MHDC::GRAY); else dc->SetPenColor (color[line->aPos]++); drawLine (line, area, dc); if (color[line->aPos] == MHDC::BLACK) color[line->aPos] = MHDC::BLUE; } } for (f = 0; f < MAX_DATA_AREAS; f++) { color[f] = MHDC::BLUE; tmp[f][0] = 0; tmp[f][1] = 0; maxlen[f] = 0; } // Find out largets label width for the labels on the right side for (f = 0; f < MAX_DATA_SERIES; f++) { MHChartLine* line = &aLine[f]; if (aLine[f].aName.Size() > 0 && aLine[f].aPrices && aLine[f].aAlign == RIGHT) { int w = dc->GetStringWidth(aLine[f].aName()); if (w > maxlen[line->aPos]) maxlen[line->aPos] = w; } } // Don't draw the labels to close to the border for (f = 0; f < MAX_DATA_AREAS; f++) maxlen[f] += dc->GetStringWidth("00"); // Draw line labels for (f = 0; f < MAX_DATA_SERIES; f++) { if (aLine[f].aPrices) { if (aLine[f].aName.Size() > 0) { MHChartLine* line = &aLine[f]; MHArea* area = &aAreas[line->aPos]; int* labelpos = &tmp[line->aPos][line->aAlign]; if (aLine[f].aType == LINE_CHART_HORIZONTAL_BLACK) dc->SetPenColor (MHDC::BLACK); else if (aLine[f].aType == LINE_CHART_HORIZONTAL_GRAY || aLine[f].aType == GREY_RECTANGLE_CHART) dc->SetPenColor (MHDC::GRAY); else dc->SetPenColor (color[aLine[f].aPos]++); if (line->aAlign == LEFT) drawLabel (int(area->aXPos), int(area->aYPos), *labelpos, line, dc); else drawLabel (int(area->aXPos + area->aWidth) - maxlen[line->aPos], int(area->aYPos), *labelpos, line, dc); (*labelpos)++; if (color[line->aPos] == MHDC::BLACK) color[line->aPos] = MHDC::BLUE; } } } } /** * Return y value for current (mouse) position * @param int - X pos * @param int - Y pos * @param MHPrice* - Left value, vol is less than 0 if mouse is outside area, aClose contains y value * @param MHPrice* - Right value, vol is less than 0 if mouse is outside area, aClose contains y value */ void MHDayChart::Query (int x, int y, MHPrice* left, MHPrice* right) { left->aVol = -1; right->aVol = -1; for (int f = 0; f < MAX_DATA_AREAS; f++) { MHArea* area = &aAreas[f]; 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)) { // We are inside an chart area int xcurr = XSTART;//XSTART(area); int xadjust = aTickWidth; int f = DSTART; int xstop = int(area->aXPos + area->aWidth); MHString* date = 0; // Find date for (; f < aDates.Size() && xcurr < xstop; f++) { if (x >= xcurr && x <= (xcurr + xadjust)) { date = (MHString*) aDates.Get(f); break; } XNEXT(xcurr,xadjust,aTickWidth) } // Get y value for left if (area->aLeft.aYPixel > MHPrice::ZERO_VALUE) { left->aClose = area->aLeft.aMax - ((y - int(area->aYPos)) / area->aLeft.aYPixel); left->aVol = 1; left->aOpen = area->aLeft.aTick; if (date) strncpy (left->aDate, date->Get(), 9); else *left->aDate = '\0'; } // Get y value for right if (area->aRight.aYPixel > MHPrice::ZERO_VALUE) { right->aClose = area->aRight.aMax - ((y - int(area->aYPos)) / area->aRight.aYPixel); right->aVol = 1; right->aOpen = area->aRight.aTick; if (date) strncpy (right->aDate, date->Get(), 9); else *right->aDate = '\0'; } break; } } }