Quote from EliteInterest:
Thanks.
Qt is amazing. Here are some status shots of the chart component.
...
You can't imagine how much work it was to get it to behave EXACTLY right.. even something as simple as making sure the dynamic horizontal/vertical scaling works smoothly was a pain.... but the graphics performance is blazing fast when panning/scaling/resizing. Qt 4 takes care of double-buffering.. this, among other things, makes coding with Qt a total breeze. I still have to 'pin the position of the far right candle' when the horizontal scaling happens with extra space to the right (for now it just scales proportional to the amount of space, rather than holding the position of the right candle constant).
It appears I need to look at even vs. odd rectangle widths and how that relates to the wick placements. Should be easy to fix.
I'm tired now. Later.
Just to let you know it's there: [code.] ... [/code.]Quote from cmaxb:
Code:QWMatrix chartWorldMatrix; chartWorldMatrix.rotate( -180 ); chartWorldMatrix.scale( -1, (double)height/range ); chartWorldMatrix.translate( 0, -range ); and then a candle: // body chartWorldMatrix.map( 0, bodyHigh, &x, &adjBodyHigh); chartWorldMatrix.map( 0, bodyLow, &x, adjBodyLow); paint.setBrush( someColor ); paint.drawRect( QRect( QPoint(chartX, lrint(adjBodyHigh)), QPoint(chartX+bodyWidth, lrint(adjBodyLow)) ) ); // wicks xPos = chartX + (width*0.5); chartWorldMatrix.map( 0, high, &x, &adjHigh); chartWorldMatrix.map( 0, low, &x, &adjLow); paint.drawLine( xPos, lrint(adjHigh), xPos, lrint(adjHigher) ); paint.drawLine( xPos, lrint(adjLower), xPos, lrint(adjLow) );
never thought it would get this far.
void ChartAreaWidget:: paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QVector< QRect> redrectangles;
QVector< QRect> greenrectangles;
QVector< QLine> tails;
QVector< QLine> gridlines;
candlecount = (width()/spacing); //# of candles = pixels / spacing
redrectangles.reserve(candlecount+1);
greenrectangles.reserve(candlecount+1);
tails.reserve(candlecount+1);
//if (candlecount < 0) //can't remember why I put this in - width is always > 0, and spacing should always be > 0
// candlecount=0;
candleremainder = width()%spacing; //the remainder is used to displace the origin of the transform so the candles always are right-justified
maxcandle = INT_MIN; //initialize the max and min values to make comparison easy
mincandle = INT_MAX;
if (offset < 0)
adjustForPanning();
if (rightpixelspace - (spacing-1)/2 < 0) //if necessary, add an extra bar to the left that will be partially displayed
extrabar = 1;
else
extrabar = 0;
startbar = high.size() - candlecount - offset + begincompensate; //find the first bar - this is used for the transform too
startindex = startbar-extrabar; //extrabar not used by transform
if (startindex < 0) //just in case user pans to far left..
startindex = 0;
rectanglestart = startbar-spacing*startbar; //constant in the formula for the bottom left corner of the rectangles
if (spacingChanged) { //wicks weren't centered on spacings such as 2,3,6,7,10,11..
if ( !((spacing-2)%4) || !((spacing-3)%4) ) { //(I noticed the sequence and put those tests in to reduce
rectanglewidth = spacing/2-1; //width by 1 pixel in those cases)
}
else {
rectanglewidth = spacing/2; //otherwise, each rectangle takes up 1/2 of the allocated space
} //candle width should be a user option but this will take some thought to figure out
spacingChanged = false;
}
wickx = rectanglestart+spacing/4; //constant in the formula for wick x-placement
for (int i = startindex; i != high.size()-offset+endcompensate; ++i) {
if (maxcandle < high.at(i)) //find the max and min while we're in here
maxcandle = high.at(i); //if there are problems, use value() instead of at()
if (mincandle > low.at(i))
mincandle = low.at(i);
if (redgreen.at(i)) //start each rectangle at the total offset + i*spacing
greenrectangles.push_back(QRect(rectanglestart+i*spacing,openclose_low.at(i),rectanglewidth,(openclose_high.at(i)-openclose_low.at(i))));
else
redrectangles.push_back(QRect(rectanglestart+i*spacing,openclose_low.at(i),rectanglewidth,(openclose_high.at(i)-openclose_low.at(i))));
tails.push_back(QLine(wickx+i*spacing,openclose_high.at(i),wickx+i*spacing,high.at(i)));
tails.push_back(QLine(wickx+i*spacing,openclose_low.at(i),wickx+i*spacing,low.at(i)));
}
maxcandle = maxcandle + 1 + verticalscale; //add 1 tick to top and bottom by default, and
mincandle = mincandle - 1 - verticalscale; //factor in any change in scale by user
for (int i = startindex; i != high.size()-offset+endcompensate; ++i) {
if (spacing == 1 && !(minute.at(i)%60)) {
gridlines.push_back(QLine(wickx+i*spacing,maxcandle,wickx+i*spacing,mincandle));
}
else if (spacing == 2 && !(minute.at(i)%30)) {
gridlines.push_back(QLine(wickx+i*spacing,maxcandle,wickx+i*spacing,mincandle));
}
else if (spacing >= 3 && !(minute.at(i)%15)) {
gridlines.push_back(QLine(wickx+i*spacing,maxcandle,wickx+i*spacing,mincandle));
}
}
painter.setWindow(QRect(startbar-candleremainder-(spacing-1)/2+rightpixelspace,maxcandle,width(),(mincandle-maxcandle))); //**THE TRANSFORM**//
painter.setPen(gridpen);
painter.drawLines(gridlines);
painter.setPen(Qt::lightGray);
painter.drawLines(tails);
painter.setPen(Qt::red);
painter.setBrush(Qt::red);
painter.drawRects(redrectangles);
painter.setPen(Qt::green);
painter.setBrush(Qt::green);
painter.drawRects(greenrectangles);
}
if (spacingChanged) {
if ( !((spacing-2)%4) || !((spacing-3)%4) ) {
rectanglewidth = spacing/2-1;
rectanglewidthcompensate = 1;
}
else {
rectanglewidth = spacing/2;
rectanglewidthcompensate = 0;
}
}
painter.setWindow(QRect(startbar-candleremainder-(spacing-1)/2+rightpixelspace-rectanglewidthcompensate,maxcandle,width(),(mincandle-maxcandle)));
Quote from cmaxb:
1) I can't override the scrollbar action to pan the chart a set number of pixels horizontally (e.g. one candle width) at a time.
2) How does one handle a really large number of data points, construct the adjacent pixmaps in the background and just blit them really quickly when scrolling?
3) I haven't achieved the effect of vertical rescaling when horizontal panning.
void ChartAreaWidget::mousePressEvent(QMouseEvent *event)
{
if ((event->buttons() & Qt::LeftButton)) {
mousexpos = (event->pos()).x();
//store the intial position when first clicked..
setCursor(Qt::SizeHorCursor);
}
}
void ChartAreaWidget::mouseMoveEvent(QMouseEvent *event)
{
if ((event->buttons() & Qt::LeftButton)) {
candlecount = (width()/spacing);
//set the candle count at that exact moment
offset = storedoffset + (event->pos().x()-mousexpos) / spacing;
//calculate offset based on delta-x for every spacing pixels, plus any offset.
if (offset > high.size() -1 || offset < -candlecount)
//if there is only one bar on the screen..
mousexpos = event->pos().x();
//then hold the position so when the user reverses, it will immediately turn around.
adjustForPanning();
update();
//now that the offset has moved, repaint the chart.
}
}
void ChartAreaWidget::mouseReleaseEvent(QMouseEvent *event)
{
storedoffset = offset; //store the offset upon mouse release for further drags - might be unnecessary?
setCursor(Qt::ArrowCursor); //can I just use offset since it persists in this Class Scope?
}